Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

git status vs fetch (or pull): How to know what "up-to-date" means (without changing anything)

Tags:

git

I do a git status and it says
Your branch is up-to-date with 'origin/master'.

But I do a git pull anyway and suddenly it says
14 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?

like image 327
candied_orange Avatar asked Jun 08 '16 06:06

candied_orange


People also ask

How do I know if my git is up to date?

ANSWER: you can use git status -uno to check if your local branch is up-to-date with the origin one.

Why does git status says up to date but not?

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.

What is difference between fetch and pull in git?

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.

How do I know if my git branch is up to date master?

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.

What is the difference between GIT fetch and git pull?

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>.

What is the use of fetch in Git?

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.

How do I fetch changes in Git from terminal?

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.

How to check if git branch is up to date?

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.


2 Answers

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)
like image 96
Tim Biegeleisen Avatar answered Oct 21 '22 09:10

Tim Biegeleisen


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:

  • your index/staging-area to your HEAD commit
  • your work-tree to your staging-area

The (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.)

like image 3
torek Avatar answered Oct 21 '22 10:10

torek