‘ git pull source myBranch’使本地 myBranch N 提前提交原始数据。为什么?

我只是注意到 git pull有点奇怪,我不明白。

星期五,我在当地的一家分公司工作。我们叫它 mybranch。在离开办公室之前,我把它推到了原点(我的 github 回购) : git push origin mybranch

昨天在家里,我把我的分支 pull到我的笔记本电脑上,做了一些编码,然后把我的更改推回到 github (原始)。

现在我又开始工作了,我试着把昨天的改动放到我的工作机器上(周末我没有改动我工作地点当地的回购单上的任何东西) :

git pull origin mybranch

这导致了一个快速前进的合并,这是罚款。然后我做了一个 git status,它说:

# On branch mybranch
# Your branch is ahead of 'origin/mybranch' by 6 commits.
#
nothing to commit (working directory clean)

啊?怎么可能是6提交提前,当我甚至没有触摸它在周末,并只是从原来的?所以我运行了一个 git diff origin/mybranch,差异正好是我刚才从远程提取的6个变化。

我只能通过运行 git fetch origin来“修复”这个问题:

From git@github.com:me/project
af8be00..88b0738  mybranch -> origin/mybranch

很显然,我的本地回购缺少了一些引用对象,但这怎么可能呢?我的意思是,一个 pull 已经执行了一次提取操作,而且除了那个分支之外,我没有做任何工作,所以 git fetch origingit fetch origin mybranch应该有相同的结果?

我应该总是使用 git pull origin而不是 git pull origin branchname吗?

我很困惑。

30969 次浏览

What does git remote -v show returns when it comes to origin?

If origin points to github, the status should be up to date, and not ahead of any remote repo. At least, with the Git1.6.5 I am using for a quick test.

Anyway, to avoid this, define explicitly the remote repo of master branch:

$ git config branch.master.remote yourGitHubRepo.git

then a git pull origin master, followed by a git status should return a clean status (no ahead).
Why? because the get fetch origin master (included in the git pull origin master) would not just update FETCH_HEAD (as Charles Bailey explains in his answer), but it would also update the "remote master branch" within your local Git repository.
In that case, your local master would not seem anymore to be "ahead" of the remote master.


I can test this, with a git1.6.5:

First I create a workrepo:

PS D:\git\tests> cd pullahead
PS D:\git\tests\pullahead> git init workrepo
Initialized empty Git repository in D:/git/tests/pullahead/workrepo/.git/
PS D:\git\tests\pullahead> cd workrepo
PS D:\git\tests\pullahead\workrepo> echo firstContent > afile.txt
PS D:\git\tests\pullahead\workrepo> git add -A
PS D:\git\tests\pullahead\workrepo> git commit -m "first commit"

I simulate a GitHub repo by creating a bare repo (one which can receive push from anywhere)

PS D:\git\tests\pullahead\workrepo> cd ..
PS D:\git\tests\pullahead> git clone --bare workrepo github

I add a modif to my working repo, that I push to github repo (added as a remote)

PS D:\git\tests\pullahead> cd workrepo
PS D:\git\tests\pullahead\workrepo> echo aModif >> afile.txt
PS D:\git\tests\pullahead\workrepo> git ci -a -m "a modif to send to github"
PS D:\git\tests\pullahead\workrepo> git remote add github d:/git/tests/pullahead/github
PS D:\git\tests\pullahead\workrepo> git push github

I create a home repo, cloned of GitHub, in which I make a couple of modifications, pushed to GitHub:

PS D:\git\tests\pullahead\workrepo> cd ..
PS D:\git\tests\pullahead> git clone github homerepo
PS D:\git\tests\pullahead> cd homerepo
PS D:\git\tests\pullahead\homerepo> type afile.txt
firstContent
aModif


PS D:\git\tests\pullahead\homerepo> echo aHomeModif1  >> afile.txt
PS D:\git\tests\pullahead\homerepo> git ci -a -m "a first home modif"
PS D:\git\tests\pullahead\homerepo> echo aHomeModif2  >> afile.txt
PS D:\git\tests\pullahead\homerepo> git ci -a -m "a second home modif"
PS D:\git\tests\pullahead\homerepo> git push github

I then clone workrepo for a first experiment

PS D:\git\tests\pullahead\workrepo4> cd ..
PS D:\git\tests\pullahead> git clone workrepo workrepo2
Initialized empty Git repository in D:/git/tests/pullahead/workrepo2/.git/
PS D:\git\tests\pullahead> cd workrepo2
PS D:\git\tests\pullahead\workrepo2> git remote add github d:/git/tests/pullahead/github
PS D:\git\tests\pullahead\workrepo2> git pull github master
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From d:/git/tests/pullahead/github
* branch            master     -> FETCH_HEAD
Updating c2763f2..75ad279
Fast forward
afile.txt |  Bin 46 -> 98 bytes
1 files changed, 0 insertions(+), 0 deletions(-)

In that repo, git status does mention master geing ahead of 'origin':

PS D:\git\tests\pullahead\workrepo5> git status
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
nothing to commit (working directory clean)

But that is only origin is not github:

PS D:\git\tests\pullahead\workrepo2> git remote -v show
github  d:/git/tests/pullahead/github (fetch)
github  d:/git/tests/pullahead/github (push)
origin  D:/git/tests/pullahead/workrepo (fetch)
origin  D:/git/tests/pullahead/workrepo (push)

But if I repeat the sequence in a repo which has an origin to github (or no origin at all, just a remote 'github' defined), status is clean:

PS D:\git\tests\pullahead\workrepo2> cd ..
PS D:\git\tests\pullahead> git clone workrepo workrepo4
PS D:\git\tests\pullahead> cd workrepo4
PS D:\git\tests\pullahead\workrepo4> git remote rm origin
PS D:\git\tests\pullahead\workrepo4> git remote add github d:/git/tests/pullahead/github
PS D:\git\tests\pullahead\workrepo4> git pull github master
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 6 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From d:/git/tests/pullahead/github
* branch            master     -> FETCH_HEAD
Updating c2763f2..75ad279
Fast forward
afile.txt |  Bin 46 -> 98 bytes
1 files changed, 0 insertions(+), 0 deletions(-)
PS D:\git\tests\pullahead\workrepo4> git status
# On branch master
nothing to commit (working directory clean)

If I had only origin pointing on github, status would be clean for git1.6.5.
It may be with a 'ahead' warning for earlier git, but anyway, a git config branch.master.remote yourGitHubRepo.git defined explicitly should be able to take care of that, even with early versions of Git.

Are you careful to add all of your remote (except origin which comes with your original clone) using git remote add NAME URL? I've seen this bug when they've just been added to the git config.

git pull calls git fetch with the appropriate parameters before merging the explicitly fetched heads (or if none the remote branch configured for merge) into the current branch.

The syntax: git fetch <repository> <ref> where <ref> is just a branch name with no colon is a 'one shot' fetch that doesn't do a standard fetch of all the tracked branches of the specified remote but instead fetches just the named branch into FETCH_HEAD.

Update: for Git versions since 1.8.4, if there is a remote tracking branch which tracks the ref that you asked to fetch then the tracking branch will now be updated by fetch. This change has been made specifically to avoid the confusion that the previous behaviour caused.

When you perform git pull <repository> <ref>, FETCH_HEAD is updated as above, then merged into your checked out HEAD but none of the standard tracking branches for the remote repository will be updated (Git <1.8.4). This means that locally it looks like you are ahead of of the remote branch, whereas in fact you are up to date with it.

Personally I always do git fetch followed by git merge <remote>/<branch> because I get to see any warnings about forced updates before I merge, and I can preview what I'm merging in. If I used git pull a bit more than I do, I would do a plain git pull with no parameters most of the time, relying on branch.<branch>.remote and branch.<branch>.merge to 'do the right thing'.