Search code examples
gitgit-pullfast-forward

How can I fast-forward a single git commit, programmatically?


I periodically get message from git that look like this:

Your branch is behind the tracked remote branch 'local-master/master' 
by 3 commits, and can be fast-forwarded.

I would like to be able to write commands in a shell script that can do the following:

  1. How can I tell if my current branch can be fast-forwarded from the remote branch it is tracking?

  2. How can I tell how many commits "behind" my branch is?

  3. How can I fast-forward by just one commit, so that for example, my local branch would go from "behind by 3 commits" to "behind by 2 commits"?

(For those who are interested, I am trying to put together a quality git/darcs mirror.)


Solution

  • The remote branch can be fast-forwarded to the local branch if the current commit is the ancestor of the remote branch head. In other words, if the "one-branch history" of the remote branch contains the current commit (because if it does, it is sure that the new commits were committed "onto" the current commit)

    So a safe way to determine whether the remote branch can be fast-forwarded:

    # Convert reference names to commit IDs
    current_commit=$(git rev-parse HEAD)
    remote_commit=$(git rev-parse remote_name/remote_branch_name)
    
    # Call git log so that it prints only commit IDs
    log=$(git log --topo-order --format='%H' $remote_commit | grep $current_commit)
    
    # Check the existence of the current commit in the log
    if [ ! -z "$log" ]
      then echo 'Remote branch can be fast-forwarded!'
    fi
    

    Note that git log was called without the --all parameter (which would list all branches), so it is not possible that the current commit is on a "side branch" and is still printed on the output.

    The number of commits ahead of the current commit equals the number of rows in $log before $current_commit.

    If you want to fast-forward only one commit, you take the row previous to the current commit (with grep -B 1, for example), and reset the local branch to this commit.

    UPDATE: you can use git log commit1..commit2 to determine the number of fast-forwarding commits:

    if [ ! -z "$log" ]
    then
      # print the number of commits ahead of the current commit
      ff_commits=$(git log --topo-order --format='%H' \
        $current_commit..$remote_commit | wc -l)
      echo "Number of fast-forwarding commits: $ff_commits"
    
      # fast-forward only one commit
      if [ $ff_commits -gt 1 ]
      then
        next_commit=$(git log --topo-order --format='%H' \
          $current_commit..$remote_commit | tail -1)
        git reset --hard $next_commit
      fi
    fi
    

    Of course, you can do this with one git log call if you save the result of the first call into a file.