1

I have git pull configured to do a rebase. In addition, I've also enabled rebase.stat in my config so that I can see what new changes are being introduced in my branch when I rebase it:

$ git pull
 .../zMovie/Source/FormManagers/FrmHome.cpp         | 105 +++++++++------------
 .../zMovie/Source/FormManagers/FrmTheater.cpp      |  26 ++++-
 .../zMovie/Source/FormManagers/FrmTheater.h        |   2 +-
 3 files changed, 72 insertions(+), 61 deletions(-)
First, rewinding head to replay your work on top of it...

The problem I have here is that Git seems to shorten the path to the updated files, so I can't simply copy/paste them to see individual diffs of them. Often times I like to compare what was changed in a file brought into my branch:

$ git diff master~ master -- .../zMovie/Source/FormManagers/FrmHome.cpp

However, the above won't work since I don't have the complete path to the file (relative to the repo root of course). It's omitted the top level of the path to keep it short enough for the column it is displayed in.

I'd also be happy if the output from the git pull displayed the revision range from master from which the new changes come from, that way I can just git diff on that range, but I don't get that in the output.

Is there a way to improve my workflow for this? I really just want to view a diff (preferably with difftool) of the new changes introduced since the last rebase on my master branch.

void.pointer
  • 24,859
  • 31
  • 132
  • 243
  • I tend to be more old-school-manually-do-everything about this, using `git fetch` instead of pull (with or without rebase), but what comes to mind here is using the reflogs. I'm not sure what you want to see: is it "what they did", what happened from old origin/master to new origin/master; or is it "whatever-they-did, what changed in my rebased tip" (diff old master vs new master), or maybe something else? – torek Mar 07 '14 at 22:30
  • @torek I want to look at the diff between my OLD rebased tip and my NEW (current) rebased tip. – void.pointer Mar 07 '14 at 22:40
  • I'm still a bit confused, as the previous tip commit (what your `master` pointed to before the pull) isn't what I'd call "rebased", or at least not as part of the `git pull --rebase` step; only the new (post-`pull`) one is. But (and again I have not tried this myself) it's likely name-able, post `git pull --rebase`, as `master@{1}` or similar. `git reflog show master` will tell you what `@{...}` suffix to use. – torek Mar 07 '14 at 22:50
  • @torek I'm not sure what you mean. What I'm basically asking for is: `git diff \`git merge-base master topic1\` master`. However, I want to be able to do this *after* performing a rebase. Which means I'd need some way of referring to the last point my branch was at before the rebase. – void.pointer Mar 07 '14 at 23:05
  • OK ... and, the commit your branch pointed to pre-rebase is in the reflog for the branch. So, use `git merge-base master@{1} ...` to find the starting point. See the git-pull script, which simply runs `git rebase ...` at the end, and `git rebase` updates the ref while leaving the previous tip in the reflog. – torek Mar 07 '14 at 23:29
  • @torek I had a chance to test using the `master@{n}` syntax and it works wonderfully. Please post an answer and I will give you a green checkmark :-) – void.pointer Mar 12 '14 at 14:11

1 Answers1

1

OK, based on comments, let's note:

  1. The pull script is basically the same as1 git fetch followed by either git merge or git rebase, depending on which one you ask for (in your config setup or with --rebase).
  2. Using gitrevisions syntax, we can find "the commit some branch name, like master, pointed to before the rebase ran" with master@{1}. (The reflog syntax is described in more detail in the git reflog documentation.)

Thus, you can see what the rebase did to your code with git diff branch@{1} branch (note: in some shells, not including bash but including csh/tcsh, you have to quote the braces to get this to work: git diff 'branch@{1}' branch or git diff branch@\{1} branch, for instance).

(You can get even fancier: gitrevisions syntax can be used pretty much anywhere, so you can use these as arguments to git merge-base, for instance.)


Long illustration follows, feel free to skip.

Before fetch-and-rebase, you might have something like this:

        o--o--o   <-- master
       /
c--c--c           <-- origin/master

The c commit-nodes are "common" (shared between you and everyone else), while the o commit-nodes are "ours" (or "original"), i.e., your own work, not yet shared with anyone.

When you fetch this brings in new work, let's call them C for also-common-but-new:

        o--o--o   <-- master
       /
c--c--c--C--C     <-- origin/master

(see footnote 1 for minor caveat). At this point nothing has happened to master, but then the pull does a git rebase, as you instructed it to. This copies your o commits to new, somewhat different commits (they now include stuff that was changed in the C commits):

        o--o--o   <-- master@{1}
       /
c--c--c--C--C     <-- origin/master
             \
              *--*--*   <-- master

The three original commits (o) are still in there. Normally they're invisible: I label them "abandoned" in similar drawings in other StackOverflow answers, as they have no branch-name pointing to them. The reflog, though, keeps "previous references" alive for a while (default = 30 days), and the @{1} or @{yesterday} or other similar syntax can be used to dig them out.

So git diff master@{1} master compares the last o commit with the last * commit, which shows you how "what they did" affects "what you did".

(Note that origin/master also has a reflog. If you do a regular fetch, so that origin/master is updated, you can git diff origin/master@{1} origin/master to compare the last c commit against the second C commit. If you use git pull, however, this needs git 1.8.4 or newer to get origin/master updated; see footnote 1.)


1When you git pull origin master this really does run git fetch origin master, i.e., it really does invoke git fetch directly. The thing here is that it gives fetch two arguments: the remote, and a branch name. In git versions older than 1.8.4, this changes the way fetch operates, so that fetch does not update origin/master. In 1.8.4 and newer, fetch updates the remote-branch origin/master, which makes more sense: now that "our git" knows that "their git" has new commits, we really should just update our idea of where master is over there. (That's all a "remote branch" is: a copy, locally, of where "their" branch was, the last time we checked.)

The fetch command always writes the fetched-branch information to the file FETCH_HEAD in the top level git directory. It does this no matter how it's invoked. While FETCH_HEAD looks a lot like a regular reference a la HEAD, MERGE_HEAD, ORIG_HEAD, and so on, it has some extra information on each line, left there specifically for the pull script.

In all cases, after fetch brings in new commits and maybe-updates-remote-branches, the pull script then extracts the SHA-1 ID of the newest commit from FETCH_HEAD. It supplies this raw SHA-1 ID to either merge or rebase.

There's one more special case though: when git pull is going to rebase, it has some fancy logic for detecting and recovering from a rebase on the remote. Normally this should not happen (it's not a good idea to retract published/shared commits), but git pull --rebase can handle it, while regular git rebase needs extra help from the user. (There is a new feature in git 1.9/2.0 to make this work better.)

I think it's worth looking through /usr/local/libexec/git-core/git-pull (if this is where your git-core directory lives: sometimes it's /usr/libexec instead, or somewhere else) to see how this works.

torek
  • 448,244
  • 59
  • 642
  • 775
  • +1 I have documented the new `git fetch` behavior in http://stackoverflow.com/a/20967347/6309. – VonC Mar 12 '14 at 18:46