I'm writing a program that checks the status of a couple of cloned git repositories.
How can I tell if my repository needs a "git pull" or "git push"?
I'm writing a program that checks the status of a couple of cloned git repositories.
How can I tell if my repository needs a "git pull" or "git push"?
First, pull
is just fetch
followed by merge
(or rebase
). This is important since what you first want is the answer to a related, easier question: how far ahead and/or behind is your local repository, compared to some remote repository? (For the TL;DR answer, skip ahead to the first header-ized section below.)
There are additional potential complications, but the simple thing to do here is to check your branch-tip(s) against those of some other git repository/ies. Let's give some names to them for illustration purposes. We can call your repository "L" (for local) and let's say there are two additional repositories "RA" (remote A) and "RB", which have URLs stored in your local repository L under remote names RA and RB.
The easy way to get everything you may need all at once is to run git fetch
(or git remote update
) on both RA and RB. (We'll see a way to defer fetching in a moment although I'm not sure there's any real value in it.)
Given a typical setup, fetching both remotes will copy their local branches your own "remote branches". Let's assume RA has master
and dev
and RB has master
and feature
, so that after your fetches complete:
L$ git rev-parse --short refs/remotes/RA/master
feedbee
L$ git rev-parse --short refs/remotes/RA/dev
feedbee
L$ git rev-parse --short refs/remotes/RB/master
badf00d
L$ git rev-parse --short refs/remotes/RB/feature
c0ffee1
(These numbers are made up but are meant for illustration. Also, if you're doing this in real code you wouldn't want --short
. And, you can abbreviate the branch names, leaving out the refs/remotes/
—and below, the refs/heads/
—as long as they're unambiguous. Though, if you're scripting, it's probably wiser to use the full names just in case.)
Now let's check the tips of your own local branches:
L$ git rev-parse --short refs/heads/master
feedbee
This means your master
is in sync with RA's master
which is in sync with its dev
. So there must be nothing to push or fetch there (though you've already fetched anyway in order to find this out) and hence nothing to merge or rebase either.
On the other hand, remote RB is not in sync as its master
points to commit badf00d
. Does this mean you have something to merge, or something to push? Maybe, maybe not: this is where the complications come in. Git can't really help much if manual merging is needed, but you can find out whether RB is "ahead", "behind", or both, by seeing how the commit graphs stack up against each other.
If repo L is strictly "ahead of" repo RB, i.e., you have stuff you can push, then the graph fragment must look something like this:
... - o <-- RB/master: tip-most commit = badf00d
\
o - o <-- master: tip commit = feedbee
Here L is "2 ahead", in the terms that git status
would give you, of where repo RB is. If you did a push to RB, git would hand the last two commits to RB and tell it to please set its master
to feedbee
, which would catch it up.
If repo L is strictly "behind" RB, then the graph fragment will look the same, but the labels will be reversed:
... - o <-- master
\
o - o <-- RB/master
In this case, if you did a merge to bring RB/master
into master
, git would see a fast-forward and set your master to badf00d
. At this point repo RA would be behind, and you might want to push there.
There are still two more possibilities though. L could be both ahead and behind, for instance:
... - o - o <-- master
\
o - o <-- RB/master
This requires either a rebase or a true merge, either of which could need manual hand-holding if git can't combine the various changes on its own (or even if it can, it might get them wrong).
Last, it's possible, though unlikely, that the two branch tips are completely unrelated (have no common ancestor in the ...
parts):
... - o <-- master
... - o <-- RB/master
This is the trickiest to deal with since what I describe below gives a false ahead/behind count. (It also can only happen with a cloned repo if someone has gone and rewritten all of the history of one of the clones. It might be reasonable to add a paranoia check for this case, using git merge-base
for instance. But I'll just ignore it from here on.)
With all that out of the way, here's the quick way to find the "ahead" and "behind" count for two branches (one local, one remote) that you know are related. I'll switch to remote "origin", which is the usual name for a (single) clone source, and use the short form of the branch names:
$ ahead=$(git rev-list --count origin/master..master)
$ behind=$(git rev-list --count master..origin/master)
These use the gitrevisions
range syntax to select revisions reachable from the local branch tip (the commit identified by master
) but not the remote branch tip (identified by origin/master
).
If you're ahead-but-not-behind you can safely git push
. If you're behind-but-not-ahead you can safely git merge
to get a fast-forward (there's no point, at this point, using pull
since you've already done the fetch
step). If you're both ahead and behind, so that both counts are nonzero, you must decide between merge or rebase, and what to do if those fail. And of course if both counts are zero, the two branch tips identify the same SHA-1 and there is no need to do anything.
git fetch
?Eventually you have to use git fetch
. However, if you like, you can start with git ls-remote
, which spits out a list of SHA-1s and refnames:
$ git ls-remote
From [url redacted]
a17c56c056d5fea0843b429132904c429a900229 HEAD
ca00f80b58d679e59fc271650f68cd25cdb72b09 refs/heads/maint
a17c56c056d5fea0843b429132904c429a900229 refs/heads/master
0029c496ce1b91f10b75ade16604b8e9f5d8d20b refs/heads/next
fcd56459647e0c41f2ea9c5b7e2ed827f701fc95 refs/heads/pu
e8f6847178db882bd42d5572439333ca4cb3222e refs/heads/todo
d5aef6e4d58cfe1549adef5b436f3ace984e8c86 refs/tags/gitgui-0.10.0
3d654be48f65545c4d3e35f5d3bbed5489820930 refs/tags/gitgui-0.10.0^{}
[mass snippage]
These SHA-1s do not carry all the graph information you need if the SHA-1s on the remote differ from your local SHA-1s, but if the SHA-1s you care about match, then you can tell that there's nothing to fetch or push. Moreover, if they differ, you can look to see if you have the corresponding SHA-1s. For instance, the above shows that the remote's master
points to commit a17c56c056d5fea0843b429132904c429a900229
. If I had it (I don't) I could use it as one of the specifiers in the git rev-list --count
to find out how far behind the remote was. Since I don't have it, I'm almost certainly behind: I don't know how much, but I need to fetch, and then maybe merge or rebase (I won't know if I'm ahead as well until I fetch).
This may be best determined in advance, rather than just iterating over all possible branches. However, if you do want to iterate over branches, the tool for this is git for-each-ref
, which takes quite a few arguments. To find your own local branches, for instance:
$ git for-each-ref --format='%(refname)' refs/heads
refs/heads/master
refs/heads/precious
refs/heads/stash-exp
To find remote origin
's branches:
$ git for-each-ref --format='%(refname)' refs/remotes/origin
refs/remotes/origin/maint
refs/remotes/origin/master
refs/remotes/origin/next
refs/remotes/origin/pu
refs/remotes/origin/todo
It's easy to strip off enough of these strings and match up the branch names, so as to be able to compare them. Using other options you can get both branch names and SHA-1s, if you want to check for (and skip over) matching SHA-1s, which will obviously give zeros for the ahead and behind counts.
Actually, since Git 2.5 (released today), you can quickly see which branch is ahead (need pushing) or behind (needs pulling)
See "Show git ahead and behind info for all branches, including remotes"
git for-each-ref --format="%(push:track)" refs/heads
That is because <branch>@{push}
is anew shortcut which specifically references the upstream branch used for pushing (it is not always the one used for pulling).