Vittorio Romeo
Vittorio Romeo

Reputation: 93274

Check if local git repo is ahead/behind remote

I'm developing a git plug-in, and I need to know when a local repo is changed (can commit changes), ahead (can push to remote) or behind (can pull from remote) using the command line.

This is what I am doing so far:

The can commit? part seems to work properly. Can push? only works for the master branch, and this is a huge problem.

How can I safely check if, on every branch, a git repo has changes to commit, commits to push, or needs a git pull?

Upvotes: 30

Views: 25707

Answers (7)

Joonho Park
Joonho Park

Reputation: 563

As for commit and push, git status works.

git status

'Changes to be committed: ... blabla' -> do commit

'ahead of 'origin/master' by n commits blabla': this means it is at ahead -> do push

'up to date' & 'nothing to commit'. Then you need to check pull-status by

git remote show origin

If it shows '(local out of date)', local repo is at behind. -> do pull

In case of '(up to date)', it is at the same commit with remote (origin/master).

For the issue of 'origin/master' in local and real 'origin/master' in network, refer to this page.

Upvotes: 0

aruku7230
aruku7230

Reputation: 981

From this answer.

  1. Do a fetch: git fetch.
  2. Get how many commits current branch is behind: behind_count = $(git rev-list --count HEAD..@{u}).
  3. Get how many commits current branch is ahead: ahead_count = $(git rev-list --count @{u}..HEAD). (It assumes that where you fetch from is where you push to, see push.default configuration option).
  4. If both behind_count and ahead_count are 0, then current branch is up to date.
  5. If behind_count is 0 and ahead_count is greater than 0, then current branch is ahead.
  6. If behind_count is greater than 0 and ahead_count is 0, then current branch is behind.
  7. If both behind_count and ahead_count are greater than 0, then current branch is diverged.

Explanation:

  • git rev-list list all commits of giving commits range. --count option output how many commits would have been listed, and suppress all other output.
  • HEAD names current branch.
  • @{u} refers to the local upstream of current branch (configured with branch.<name>.remote and branch.<name>.merge). There is also @{push}, it is usually points to the same as @{u}.
  • <rev1>..<rev2> specifies commits range that include commits that are reachable from but exclude those that are reachable from . When either or is omitted, it defaults to HEAD.

Upvotes: 24

Kelly Setzer
Kelly Setzer

Reputation: 304

I made a bash version of @user1115652 answer.

function branch_status() {
  local a="master" b="origin/master"
  local base=$( git merge-base $a $b )
  local aref=$( git rev-parse  $a )
  local bref=$( git rev-parse  $b )

  if [[ $aref == "$bref" ]]; then
    echo up-to-date
  elif [[ $aref == "$base" ]]; then
    echo behind
  elif [[ $bref == "$base" ]]; then
    echo ahead
  else
    echo diverged
  fi
}

Upvotes: 7

Panch93
Panch93

Reputation: 465

For future reference. As of Git v2.17.0

git status -sb

contains the word behind . So that can be used directly to check for pulls.

Note: Remember to run git fetch before running git status -sb

Upvotes: 34

user1115652
user1115652

Reputation:

Thanks to @Trebor I just threw together a simple fish function for the purpose:

#! /usr/bin/fish
#
# Echos (to stdout) whether your branch is up-to-date, behind, ahead or diverged from another branch.
# Don't forget to fetch before calling.
#
# @param branch
# @param otherbranch
#
# @echo string up-to-date/behind/ahead/diverged
#
# @example
#
#   # if master is ahead of origin/master you can find out like this:
#   #
#   if test ( branch-status master origin/master ) = ahead
#
#      echo "We should push"
#
#   end
#
function branch-status

    set -l a $argv[ 1 ]
    set -l b $argv[ 2 ]

    set -l base ( git merge-base $a $b )
    set -l aref ( git rev-parse  $a    )
    set -l bref ( git rev-parse  $b    )

         if [ $aref = $bref ]; echo up-to-date

    else if [ $aref = $base ]; echo behind
    else if [ $bref = $base ]; echo ahead

    else                     ; echo diverged
    end

end

Upvotes: 5

Trebor Rude
Trebor Rude

Reputation: 1944

You can do this with a combination of git merge-base and git rev-parse. If git merge-base <branch> <remote branch> returns the same as git rev-parse <remote branch>, then your local branch is ahead. If it returns the same as git rev-parse <branch>, then your local branch is behind. If merge-base returns a different answer than either rev-parse, then the branches have diverged and you'll need to do a merge.

It would be best to do a git fetch before checking the branches, though, otherwise your determination of whether or not you need to pull will be out of date. You'll also want to verify that each branch you check has a remote tracking branch. You can use git for-each-ref --format='%(upstream:short)' refs/heads/<branch> to do that. That command will return the remote tracking branch of <branch> or the empty string if it doesn't have one. Somewhere on SO there's a different version which will return an error if the branch doesn't haven't a remote tracking branch, which may be more useful for your purpose.

Upvotes: 10

Vittorio Romeo
Vittorio Romeo

Reputation: 93274

In the end, I implemented this in my C++11 git-ws plugin.

string currentBranch = run("git rev-parse --abbrev-ref HEAD"); 
bool canCommit = run("git diff-index --name-only --ignore-submodules HEAD --").empty();
bool canPush = stoi(run("git rev-list HEAD...origin/" + currentBranch + " --ignore-submodules --count")[0]) > 0;

Seems to work so far. canPull still needs to be tested and implemented.

Explanation:

  • currentBranch gets the console output, which is a string of the current branch name
  • canCommit gets whether the console outputs something (difference between current changes and HEAD, ignoring submodules)
  • canPush gets the count of changes between origin/currentBranch and the local repo - if > 0, the local repo can be pushed

Upvotes: 7

Related Questions