Search code examples
gitfast-forward

Is there a way to get Git to print out the "fast forward" tasks between two commits?


When you run git pull from a remote repo, you'll see a list of all the files updated and the number of lines changed, along with approximate ratios of insertions/deletions. You'll also see any created, renamed, deleted and mode-changed files. For example:

Updating 5524541..cff1e7a
Fast-forward
 .gitignore                                 |   4 +-
 .vscode/settings.json                      |   7 ++
 README.md                                  |  40 ++++++-
 clean.sh => dev_scripts/clean.sh           |   0
 dev_scripts/main.sh                        |   2 +-
 ... many more files here ...
 41 files changed, 1044 insertions(+), 502 deletions(-)
 create mode 100644 .vscode/settings.json
 rename clean.sh => dev_scripts/clean.sh (100%)
 delete mode 100644 dev_scripts/test_old.py
 create mode 100644 postinstall/2.0.7_00_fix_missing_models.py
 create mode 100644 test/csv_test.py

Is there a way to get output similar to this but by specifying the starting and ending commit on a local repo? I basically want the same output as the git pull fast forward output, but without actually performing any actions, just displaying what would happen if I were in a scenario where my local repo was at commit A and the remote was at commit B and I did a pull (even though both commits are already in my local repo). Or, more succinctly perhaps, I want to see a summary list of what would happen if I were to start at commit A and checkout commit B.

I know about git diff which basically performs the desired function (showing me what the changes are between two commits), but it prints out the entire diff between the two commits (I believe it comes out in something suitable for patch?), whereas I'm just looking for the summary report like the fast-forward report.


Solution

  • As KateYoak suggested in a comment, you need git diff --stat --summary here. The tricky part is finding the correct commits (or hash IDs) to feed as the two inputs to this git diff. They are, however, right there on your screen:

    Updating 5524541..cff1e7a
    

    You can also find them in HEAD@{1} and HEAD (or HEAD@{0}) for some time, although eventually these numbers increment so that they are in HEAD@{2} and HEAD@{1}, then in HEAD@{3} and HEAD@{2}, and so on. You can use the reflog for the branch you are on, as these numbers increment a bit more slowly in general. If you were on branch dev for instance these would be dev@{1} and dev@{0}, etc.

    (Note that these two commit hash IDs are unlikely ever to re-occur for anyone else, so there's no point saving them from this answer. Because they're shortened hash IDs, they're more likely than the 1-in-2160 chance for any one full hash ID, but they're still remarkably unlikely.)

    Why this works

    When you run git pull ...

    ... you are actually running git fetch and then a second Git command. In your case, the second Git command was git merge.

    The git fetch operation obtained some new commit(s) from some other Git repository. In this case:

    Updating 5524541..cff1e7a
    

    it obtained some series of commits ending in commit cff1e7a.

    At this time, your current commit, as found by HEAD connecting to some branch name, was 5524541. That is, if you were on branch dev, the name dev meant 5524541.

    New-to-you commit cff1e7a was "strictly ahead of" commit 5524541, so when your git pull ran git merge—which allowed a fast-forward operation instead of a merge—your git merge did a fast-forward operation, changing your current branch name so that it referred to cff1e7a.1 This is all quite normal, but it's moving very fast and you probably didn't see it happen.

    Finally, whatever the git merge did, Git produces a git diff --stat --summary from the old commit—i.e., 5524541—to the new commit, cff1e7a. That's the output you saw. So, if you want to reproduce that output, run git diff --stat --summary 5524541 cff1e7a.

    Note that there's no need to use the two-dot syntax:

    git diff --stat --summary 5524541 cff1e7a
    

    and:

    git diff --stat --summary 5524541..cff1e7a
    

    do exactly the same thing. It's easier to type one space than two dots, and the one-space version is more logical than the two-dot version, considering the way git diff works, so it's the one I recommend.

    You can, if you like, use only one of --stat or --summary. The --stat part is everything up to and including:

     41 files changed, 1044 insertions(+), 502 deletions(-)
    

    and the --summary part is the rest.


    1Not all merges can be fast-forwards, but all fast-forwards can be done as true merges, if you prefer that. A fast-forward occurs when the new value to be stored in some reference is a descendant of the hash ID stored in that reference now. A branch name is a particular form of reference, so branch names can be fast-forward-ed. Allowing git merge to do fast-forwards is the default. So when git pull runs git merge, fast-forward operations are common.

    Although this is a matter of opinion, some of us (myself included) think that only fast-forward operations are really appropriate when using git pull. So this is a good thing. When git pull results in a true merge, it produces what some call a foxtrot merge, which is typically in some sense "backwards". See also GIT: How can I prevent foxtrot merges in my 'master' branch? To some extent this is minor, but foxtrot merges are still not good.