0

I came across this code to export a patch from a specific location in a repository

git log --author=jdoe oldbranch..newbranch -p -- path/to/subdirectory > myChangesInSubdirectory.patch

It, of course, goes by commit rather than all scrunched together, because that's what a patch does. But I would like to be able to say basically "Here are the files in a particular folder that changed between commit a and commit b and what changed in them" without having multiple changes to the same file.

I could manually create a new repo out of the original tag and then copy over the other branch and make it one commit that way, but I am trying to find a one line CLI way to output it.

Damon
  • 10,493
  • 16
  • 86
  • 144

1 Answers1

2

There are lots of ways to do that, depending on the precise results you want.

If you want the difference between two specific, already-existing commits (perhaps constrained to specific files / directories as well), git diff will do it:

git diff rev1 rev2

or:

git diff rev1 rev2 -- path1 path2 ... pathN

If you want changes whose commits were within a specific date range, excluding changes outside of that range, it's a bit more complex. For instance, consider the following graph fragment:

... - D - E - F - G - J - K ...
            \       /
              H - I

Suppose further that the date on commit D is "last Wednesday", but the date on commit E is "two months ago", because commit E was forward-ported (rebased or similar) from something done much earlier. Meanwhile commits F and G are recent, as is H, but I is also forward-ported from earlier like E, and J and K are also recent, but you decide that K is too recent. The commits you want are therefore D but not E; F and G; and H but not I.

In this case, to get all those changes (and no others) as a single patch, you will need to make a new branch starting just before D into which you cherry-pick (or equivalent) each of those commits. You can cherry-pick them individually, then use git diff to generate a patch from "just before D" to "final result". In this case there is still a one-line(ish) way to do it since you're not picking up any merges:

git checkout -b deliver D^ && git cherry-pick \
    $(git rev-list --since=... --until=...) && \
git diff D^ HEAD [-- paths]

Here, we use git rev-list to pick the desired commits and feed the resulting SHA-1s to git cherry-pick, which adds them on the new branch (deliver) created starting from commit D^. Assuming all goes well—not necessarily a good assumption—the final git diff compares D^, our starting point, with HEAD, the result after all the cherry-picking.

(This can be simplified, if you call it simplification at least :-) , by creating the deliver branch after commit D since we plan to include it. I assume, however, that the --since and --until values are how you decided which commits to include in the first place, so I think it's a bit clearer this way.)

This all falls apart if you plan to include the merge-commit J here: merge commits can be an "evil merge" (see here and here for details), and these cannot be simply cherry-picked. So if you do have some fancy requirements that require running git rev-list to choose commits to include, check carefully. (Note that you can automate finding merges with --merges.)

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you for all that detail! I realize I was unintentionally vague in my question.. but essentially changing 'log' to 'diff' in the original snippet I had gave me exactly what I needed – Damon Mar 16 '15 at 03:06
  • Ah. In that case, you should know that `git diff br1..br2` really "means" `git diff br1 br2` (and adding `--author` has no effect at all). `git log` feeds arguments to `git rev-list` and uses the entire resulting list of revisions, while `git diff` is special-cased. – torek Mar 16 '15 at 05:21