I do a git status
and it saysYour branch is up-to-date with 'origin/master'.
But I do a git pull
anyway and suddenly it says14 files changed, ...
And I feel like I've been lied to. I suspect git isn't broken. Which must mean I don't understand something.
I do a bit of reading here and here and learn of two non destructive ways to ask if I'm up to date
git status -uno
git fetch --dry-run
Since fetch
is part of pull
I assume these two will disagree the same way the last two did.
What's fundamentally confusing me is I think of 'up-to-date' as meaning: "hey we've compared two copies of this repository (the master branch) and they're the same"
Ok fine, but if I can ask the question two different ways and get two different answers, when each question is about comparing two different copies then doesn't that mean there have to be at least three copies of the repository?
As in:
A == B != C
I know there is a remote copy of master
I know there is my local copy of master
What the heck is this third thing?
ANSWER: you can use git status -uno to check if your local branch is up-to-date with the origin one.
On branch master, Your branch is up to date with 'origin/master. As long as you haven't made any new commits that message is correct, unversioned/changed files that haven't been committed yet should be listed below it. If unversioned files don't show up, check if they might be hidden by a . gitignore file.
git fetch is the command that tells your local git to retrieve the latest meta-data info from the original (yet doesn't do any file transferring. It's more like just checking to see if there are any changes available). git pull on the other hand does that AND brings (copy) those changes from the remote repository.
To check if you're up-to-date with GitHub run git fetch origin before git status and you'll know you're up-to-date.
git fetch downloads commits, files and branches from the git remote. You’ll need to use this command to get the latest changes that others have made. You’ll also need to use it to checkout a new branch that someone else has pushed. git pull does two things: git fetch and then git merge origin/<branch>.
Git fetch is a command that allows you to download objects from another repository. What is Git pull? Git pull is a command that allows you to fetch from and integrate with another repository or local branch. From this definition, you can see that a Git pull is actually a Git fetch followed by an additional action (s)—typically a Git merge.
If you’re using the terminal, you will use the Git fetch command to fetch changes from your remote branches. How do you Git pull in the command line? Similarly, you will use the Git pull command to pull changes from your remote. This will update your currently checked out branch with changes from your remote.
If you use -v with git remote update ( git remote -v update) you can see which branches got updated, so you don't really need any further commands. Show activity on this post. you can use git status -uno to check if your local branch is up-to-date with the origin one. Show activity on this post.
When Git says Your branch is up-to-date with 'origin/master'
it is comparing your local master
branch against the remote tracking branch called origin/master
. This tracking branch also exists locally and is what is actually used when you update your local branch. And when you do a git fetch
, all the remote tracking branches get updated.
Your local master
branch was current with the tracking branch, but this does not mean that the local branch is current with the actual master
branch on the repository. Had you done a git fetch
after your initial call to git status
, you would have a seen a message saying that your local master
branch was "behind" origin/master
. Doing a git pull
means updating the tracking branch origin/master
, and then merging that into your local branch to sync everything.
Here is a simplistic diagram showing the flow of information from the remote to your local branch:
remote master (repo) --> origin/master (local tracking branch) --> master (local branch)
To add a bit1 to Tim Biegeleisen's answer, git status
works by performing two diffs plus also comparing your current HEAD
to its upstream.
Here's the complete(...ish) picture.
Given a remote repository R, git fetch
copies from every branch it sees on R
—you can see what it sees by running git ls-remote R
—and renames them in the process. For branches B1, B2, and B3, your Git creates or updates remote-tracking branches R/B1, R/B2, and R/B3. (More precisely, these are references whose name starts with refs/remotes/
and then continues on to name the same remote R, e.g., origin/
, and then the branch name. This guarantees that these references never collide with your own local branches, which start with refs/heads/
: your master is refs/heads/master
while that copied from remote origin
is refs/remotes/origin/master
.
(Your Git may also bring over tags, depending on flags you give to git fetch
. The default is slightly complicated: it brings over tags for any commits it brings over while bringing over branches. With --no-tags
it skips tags entirely, and with --tags
it brings over all tags. Tags, unlike branches, do not have special per-remote name spaces: your tag T1
is really refs/tags/T1
, and if your Git brings over a tag T2
from remote R, it just names it refs/tags/T2
. If two tags collide, your Git defaults to ignoring the extra one, i.e., if you already have a T2
, your Git drops their T2
on the floor.2)
In order to bring over these branches (and maybe tags), your Git must bring over the commits (and any other objects) they point-to, as identified by the SHA-1 hashes you will see in that same git ls-remote
output. To get a commit, your Git has to get any trees and blobs to which that commit object points. Your Git and their Git therefore have a conversation, leading to the object counting and compressing and so on that you see: your Git already has some set of objects and yours and theirs simply work to see what you have in common, to determine how best to get you the ones you don't have yet.
All of these objects get inserted into your repository. At this point, they are pointed-to by your remote-tracking branches, such as origin/master
. If you now run git status
, it can—and does—work entirely locally.
Let's say you're on your own master
. In this case, your HEAD
reference simply contains the string ref: refs/heads/master
.3 This is in fact how Git knows that you are on branch master
. Meanwhile, Git stores, under .git/config
, some extra data to record that your local master
has origin/master
(really refs/remotes/origin/master
; Git just abbreviates a lot) as its upstream.
So, git status
discovers that you are on master
and also looks up origin/master
. These two names—refs/heads/master
and refs/remotes/origin/master
—point to two commit IDs. Those commit IDs may be the same, or may be different. If they are the same, the two branches are in sync. If they differ, the two branches differ. One may contain more commits than the other—so that one is strictly ahead and the other strictly behind—or they may have some commits that are different on both branches, and some commits that are common to both.
(This is where Git's terminology breaks down: does "branch" mean "branch name", like master
? Or does it mean "the set of all commits reachable by starting at the branch's tip-most commit and working back through history"? The answer is that it means both, and we are supposed to figure out which meaning to use.)
To get the ahead 3
and/or behind 5
count, git status
uses git rev-list --count
:
git rev-list --count origin/master..master
git rev-list --count master..origin/master
This two-dot syntax means "find the set of all commits reachable from the identifier on the right, and subtract away the set of all commits reachable from the identifier on the left". Suppose, for instance, master
is strictly ahead of origin/master
. We can draw the commit chain like this:
... <- o <- o <- o <-- origin/master
\
o <-- master
Here there is one commit on master
that is not on origin/master
. All commits on origin/master
are on both branches: both the local branch and the remote-tracking branch. But there is one commit on master
that is not on origin/master
.
When git fetch
obtains new commits, those new commits normally point back to existing commits. So if git fetch
picks up one new commit on origin/master
, the picture changes:
... <- o <- o <- o <- o <-- origin/master
\
o <-- master
Now neither branch is strictly behind, and you will probably want to merge or rebase your work.
Meanwhile, git status
also compares:
HEAD
commitThe (single, distinguished) index contains the set of all files that will go into the next commit you can make. When you git add
new contents for existing files, this replaces the existing file in the staging area. (Strictly speaking, the index contains only the hash, plus necessary stuff like the file's path, plus a bunch of cache information to speed up git status
and git commit
. The add
step actually copies the file into the repository, computing its hash, at add
time, and stores the new hash into the index.) Adding a totally new file adds a new entry, and removing an existing file with git rm
adds a special "erase" or "white-out" entry so that Git knows not to put that file into the next commit.
When git status
shows you what is staged for commit, it does so by diff-ing the index against HEAD
.
When git status
shows you what is not staged for commit, but could be, it does so by diffing the work-tree against the index.
1OK, a lot.
2In a bug in pre-1.8.4 or so versions of Git, tags could change as long as they moved in a fast-forward manner, which is the same rule applied by default during git push
. I think this did not happen on fetch
though.
3HEAD
is a reference, just like refs/heads/master
and refs/remotes/origin/master
, but with some special handling. In particular, HEAD
is normally an indirect reference, with that ref:
prefix stuff. While any reference can be indirect, HEAD
is the only useful indirect, at least currently (Git version 2.8.x). Furthermore the only useful indirect values are to regular local branches. When HEAD
contains the name of a local branch, git status
says that you are "on" that branch.
Checking out a commit by its SHA-1 hash ID, or using --detach
, puts the raw ID into HEAD
. In this case git status
claims that you are not on any branch. In fact, you are on the (single) anonymous branch: new commits you make go into the repository as usual, but are known only by the special name HEAD
, and if you check out some other branch, the IDs of those commits become somewhat difficult to retrieve. (They're still stored in the reflog for HEAD
, until those reflog entries expire; after that point, they are eligible for being garbage-collected by git gc
.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With