`git pull`和`git fetch`不能一致地解释参数:(设计)bug或(设计)特性? [英] `git pull` and `git fetch` do not interpret arguments consistently: (design) bug or (design) feature?
问题描述
这个问题比典型的SO问题更理论化,所以我觉得有一定的理由是为了。我对 git
所遇到的困难之一是我发现自己经常做的事情对我来说似乎是应该工作,但不是。即使在过去10个月左右几乎每天都使用 git
,这仍然是正确的,并且比我用于工作的任何其他软件工具更加深入地研究它。这意味着,在内部, git
对我来说根本没有意义。因此,这个问题的目的只有 有意义 git
,并希望能够减少我认为应该工作,以及实际上做了什么。 (顺便说一下,阅读文档不是解决方案;某些细节可能会被记录下来,但仍然没有意义。) 这个问题是后续更早的一个我问:如何分解git拉<远程> < BRANCH>到一个获取+合并?。特别是,我建议运行该问题中给出的示例脚本,并检查其输出。
git pull 命令应该是 git fetch
和a git merge
的组合。
因此,如果< BRANCHNAME>
是当前分支,然后运行
git pull --no-edit< REMOTENAME> < BRANCHNAME>
会更新追踪分行< REMOTENAME> /< BRANCHNAME> code> 1 ,而
git fetch< REMOTENAME> < BRANCHNAME>
离开< REMOTENAME> /< BRANCHNAME>
不变,因此尝试将它与< BRANCHNAME>
合并通常是不可操作的。
有什么办法可以理解这种明显的不一致吗?或者,为什么不 git fetch< REMOTENAME> < BRANCHNAME>
完全失败,或者至少发出警告?是否它是一个值得保留的常见用例?
总之,这种明显的不一致性在 code> git 界面,或者它是一个功能,如果是后者,情况如何?
编辑:只是澄清:这个问题是不可知的 / 漠不关心与上述行为如何与其文档一致的问题。这个问题试图弄清楚的是,在解释它们时,在 1 Plus会执行 这里是我的看法。 / p> 首先,让我们揭开refspec这个名字,因为这是需要的这条路。 refspec代表参考规范。引用是指向提交或其他引用的东西。分支是引用,标签是引用; 通常简称为refs。 一个refspec的形式如下: refspecs用于提取和推送,源和目标ref的含义在这些情况下相反,显然:当我们获取时,源代码在远程仓库中,而当w e-push,source refs在我们的本地仓库中。 使用普通的Git(我的意思是它的参考实现),ref只是名称相对于他们的仓库(对于普通仓库,这是.git目录,对于裸仓库,这是仓库根目录)。
< BRANCHNAME>
与此更新的< REMOTENAME> /< BRANCHNAME>
,或者至少它开始了这个合并。
refspecs
HEAD
是一种特殊的(所谓的符号)引用。让我们忽略当前各种引用之间的区别。
[+] source [:destination]
来源
指定参考系列。
destination
,如果指定,则指定对 update 的一系列提交来自源
。
source $ c $指向的提交行中,
(或 destination
指向的提交不完全包含
ORIG_HEAD
或 MERGE_HEAD
或 FETCH_HEAD
)位于版本库的根目录下,而其他一些(如分支和标签以及远程分支)则位于名为refs的目录中,并在那里进一步对它们各自的子目录进行排序:
- 分支位于refs / heads子目录中。
- 标签位于refs / tags / li>
- 远程分支位于refs / remotes /< remote>
- 注释在refs / notes中。
- ...
-
git fetch< git_url> <的Refspec> ...
使用指定的refspecs从远程仓库中取出(而不是HEAD
)和—对于那些包含目标部分的refspecs,它也会尝试用获取的历史记录更新这些本地参考文献。每个获取的refspec的SHA-1名称被写入到.git / FETCH_HEAD
文件中。
git fetch git://server/repo.git master devel test
从git:// server / repo .git
*分支大师 - > FETCH_HEAD
* branch devel - > FETCH_HEAD
*分支测试 - > FETCH_HEAD
请注意,虽然已提交提交,但未更新本地参考。
现在让我们以更加复杂的方式进行操作:
git fetch git:// server /repo.git master devel:foo test:bar
从git://server/repo.git
* branch master - > FETCH_HEAD
* [新分支] devel - > foo
* [new branch] test - > bar
正如您所见,
git fetch
创建了两个当地分支,富和酒吧,而主刚刚被提取,但没有用于在我们的本地回购中创建任何东西。所有三个远程引用的SHA-1名称仍然在.git / FETCH_HEAD
文件中。 -
git fetch< remote> <的Refspec> ...
—< remote>
是使用git remote add
或由git clone
—的行为与具有显式< git_url>
但git fetch 的表单完全相同,命名为remote。 从该远程分支,并尝试更新(或创建,如果它们不存在)该远程所谓的远程分支。 -
最神奇的是简单的
git fetch
—它就像上面的调用一样,只是遥控器自动启动。 > - origin / foo是一个远程分支,它捕获远程仓库origin中名为foo的分支的状态。 b
- foo是跟踪origin / foo的本地分支。 你做了
git checkout foo
git pull origin foo
Git作为结果更新你的foo分支,但这意味着foo现在至少有一个提交origin / foo会收到,如果你只是
git fetch origin
或git push origin foo
,所以Gi t继续并更新origin / foo。
source
designates the reference to take the series of commits from.destination
, if specified, designates a reference to update with the series of commits taken fromsource
.- A plus sign, if included, forces updating to happen even if the line of commits pointed to by
destination
is not completely contained in the line of commits pointed to bysource
. - Branches are in the "refs/heads" subdirectory.
- Tags are in "refs/tags".
- Remote branches are in "refs/remotes/<remote>" directory of their appropriate remote.
- Notes are in "refs/notes".
- ...
git fetch <git_url>
takes whatever the refHEAD
points to in the remote repository accessed via<git_url>
, fetches its history and writes the SHA-1 name of its tip commit to the.git/FETCH_HEAD
file.In a bare repository
HEAD
typically points at the branch named "master" (though this might be changed). In a non-bare (normal) repositoryHEAD
obviously points at what's currently checked out to the work tree.git fetch <git_url> <refspec> ...
uses the specified refspecs to fetch from the remote repository (instead ofHEAD
), and — for those refspecs which include the "destination" part, it also tries to update those local refs with the fetched history. The SHA-1 names of each fetched refspec is written to the.git/FETCH_HEAD
file.To demonstrate:
git fetch git://server/repo.git master devel test From git://server/repo.git * branch master -> FETCH_HEAD * branch devel -> FETCH_HEAD * branch test -> FETCH_HEAD
Note that while the commits were fetched, no local refs were updated.
Now let's do it in a more involved way:
git fetch git://server/repo.git master devel:foo test:bar From git://server/repo.git * branch master -> FETCH_HEAD * [new branch] devel -> foo * [new branch] test -> bar
As you can see,
git fetch
created two local branches, "foo" and "bar" while "master" was just fetched but was not used to create anything in our local repo. SHA-1 names of all three remote refs still end up in the.git/FETCH_HEAD
file.git fetch <remote> <refspec> ...
— with<remote>
being a named remote repository configured usinggit remote add
or automatically created bygit clone
— behaves exactly like the forms with explicit<git_url>
butgit fetch
obtains the url to use from the configuration of that named remote.git fetch <remote>
fetches all the branches from that remote and tries to update (or create, if they do not exist) the so-called remote branches for that remote.This is crucial to understand that this course of operation is not magic. When you add a named remote (or
git clone
creates one for you), Git adds to your repository configuration several variables describing that remote, and one of them will be theremote.<name>.fetch
parameter which is a refspec and which is used if no refspecs are passed togit fetch
directly (!).Now, for the remote named "origin" Git will create the parameter
remote.origin.fetch
set to+refs/heads/*:refs/remotes/origin/*
. You can go to your local repo and see this for yourself:$ git config --get remote.origin.fetch +refs/heads/*:refs/remotes/origin/*
"The most magic" is plain
git fetch
— it works like the call above just the remote is picked up automatically.- "origin/foo" is a remote branch which captures the state of a branch named "foo" in the remote repository "origin".
- "foo" is a local branch which tracks "origin/foo".
You do
git checkout foo git pull origin foo
Git updates your "foo" branch as the result, but this implies "foo" now has at least one commit "origin/foo" would receive if you would just
git fetch origin
orgit push origin foo
, so Git proceeds and updates "origin/foo" as well.
ul>
因此,表示名为master的分支的ref的全名实际上是refs / heads / master,并且从命名远程获取名为foo的远程分支版本库的起源是refs / remotes / origin / foo。
Git使用智能查找机制来允许您在大多数时间缩写ref名称,或更完整)的名字可能会在你想要严格时存在任何歧义。血淋淋的细节在 git rev-parse
manual。
(这些关于全文名称的解释是理解简单调用 git fetch
工作,这将在下面解释。)
提取
现在回到抓取。
git fetch
基本上有几种操作模式取决于您提供给它的参数:
$ ul
$ git fetch git_url 通过< git_url>
访问的远程存储库中的任何ref HEAD
点,获取其历史并写入它的提示的SHA-1名称提交给 .git / FETCH_HEAD
文件。
code> HEAD 通常指向名为master的分支(尽管可能会更改)。在非裸(正常)仓库 HEAD
明显指出当前检出的工作树。
这对了解这一操作过程并不神奇至关重要。当你添加一个已命名的远程(或者 git clone
为你创建一个)时,Git向你的版本库配置添加了描述该远程的一些变量,其中一个变量是 remote。< name> .fetch
这是refspec 和的参数,如果没有refspecs传递给 git fetch
直接(!)。
现在,对于远程命名的origin,Git将创建参数 remote.origin。取
设置为 + refs / heads / *:refs / remotes / origin / *
。你可以到你的本地仓库去看看:
$ $ $ $ git config --get remote.origin.fetch
+ refs / heads / *:refs / remotes / origin / *
将其定义为获取并合并所获取的内容到当前签出的分支。传递给 git pull
(或者它们缺少的)的refspec直接交给 git fetch
,因此所有上述规则都适用。
为什么 git pull< remote> < branch>
更新了匹配的远程分支,而`git fetch没有?
要理解这个神秘记住当Git在远程分支和他们的远程跟踪分支(当你做 git fetch origin
后跟 git分支foo origin / foo
,origin / foo是远程分支,foo是远程跟踪分支,因为它跟踪相应的远程分支)。另一点你应该记住的是,远程分支可以在最后一次获取时捕获远程仓库中相应分支的状态。
现在假设您将远程跟踪分支推送到远程存储库中相同名称的分支(最常见的情况)。假设你这样做了:
$ g $ f $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $
$ git chekcout -b foo origin / foo
(创建了一个新的本地分支foo跟踪origin / foo)
$ git commit ...
$ git push源代码foo
在推送时,Git注意到如果您将获取匹配的远程分支在同一时间,你会得到相同的提交,它只是推送。所以它继续,并使origin / foo指向相同的提交foo指向。
现在回到 git pull
git pull
完成之后,Git注意到你刚更新了一个远程跟踪分支,你在本地有一个远程分支,所以它继续并更新这个远程分支,因为这是它的最后一次看到的状态。 让我们回顾一下:
即我认为,更新远程你的案例中的分支不是因为 git fetch ...
运行,而是因为本地远程跟踪分支由于合并而更新。现在是否有意义?
进一步阅读
我会推荐阅读.git / config 文件在您的存储库中,以了解如何配置所有这些远程东西。
This question is more "theoretical" than the typical SO question, so I feel a certain justification is in order. One of the difficulties I have with git
is that I find myself constantly doing things that seem to me that they "should work", but don't. This remains true even after using git
pretty much daily for the last 10 months or so, and having studied it more intensively than pretty much any other software tool that I use for work. This means that, deep down, git
simply does not make sense to me. The purpose of this question, therefore, is only to make sense of git
, and hopefully reduce the mismatch between what I think "should work", and what actually does. (BTW, "read the docs" is not the solution; something can be documented in minute detail and still make no sense.)
This question is a follow-up to an earlier one I asked: How to decompose git pull <REMOTE> <BRANCH> into a fetch + merge?. In particular, I recommend running the example script given in that question, and examining its output.
A git pull
command is supposed to be a combination of a git fetch
and a git merge
.
Therefore I am puzzled by the fact that, if <BRANCHNAME>
is the current branch, then running
git pull --no-edit <REMOTENAME> <BRANCHNAME>
updates the tracking branch <REMOTENAME>/<BRANCHNAME>
1, whereas
git fetch <REMOTENAME> <BRANCHNAME>
leaves <REMOTENAME>/<BRANCHNAME>
unchanged, and therefore attempting to merge it with <BRANCHNAME>
is normally a no-op.
Is there any way to make sense of this apparent inconsistency? Alternatively, why doesn't git fetch <REMOTENAME> <BRANCHNAME>
fail outright, or at least give a warning? Is what it does a common use-case that's worth preserving?
In short, is this apparent inconsistency a bug in the design of the git
interface, or is it a feature, and if the latter is the case, how is it a feature?
EDIT: Just to clarify: this question is agnostic/unconcerned with the issue of how well the behavior described above agrees with its documentation. What this question is trying to ascertain is whether the difference in the conventions followed by git pull
and git fetch
when interpreting their arguments is a (design) bug or a (design) feature.
1Plus it carries out any necessary merge between <BRANCHNAME>
and this updated <REMOTENAME>/<BRANCHNAME>
, or at least it gets this merge started.
Here's my take on it.
The refspecs
First, let's debunk what the thing called "refspec" is, as this will be needed down the road.
The refspec stands for "reference specification". A reference is something pointing to a commit or to another reference. A branch is a reference, a tag is a reference; HEAD
is a special (the so-called "symbolic") reference. Let's ignore the distinction between various kind of references at the moment.
It's common to call references just "refs" for short.
A refspec has the form:
[+]source[:destination]
The refspecs are used for both fetching and pushing, and the meaning of source and destination refs is reversed in these cases, obviously: when we fetch, the source refs are in the remote repo, and when we push, the source refs are in our local repo.
With plain Git (I mean its reference implementation), refs are just files whose names are relative to the root of their repository (for normal repositories this is the ".git" directory, for bare repositories this is the repository root directory).
Some references, like HEAD
(or ORIG_HEAD
or MERGE_HEAD
or FETCH_HEAD
) are located right in the root of the repository while some others, like branches and tags and remote branches are located in the directory named "refs" and there they are further sorted under their respective subdirectories:
Hence a full name of the ref representing a branch named "master" is indeed "refs/heads/master", and a remote branch named "foo" fetched from a named remote repository "origin" is "refs/remotes/origin/foo".
Git uses smart lookup mechanism to allow you to abbreviate ref names most of the time, but full (or "fuller") names might be used when there is any ambiguity of just when you want to be strict. The gory details are in the git rev-parse
manual.
(These explanations about full ref names are needed to understand how simple calls to git fetch
work which are explained below.)
Fetching
Now back to fetching.
git fetch
basically several modes of operation depending on what arguments you supplied to it:
Pulling (fetching plus merging)
Pulling it defined as "fetch then merge what was fetched to the currently checked out branch". The refspec(s) passed to git pull
(or their lack thereof) are handed directly to git fetch
, and hence all the rules described above apply.
Why git pull <remote> <branch>
updates the matching remote branch while `git fetch doesn't?
To understand this "mystery", it helps to keep in mind that Git employs certain convenient magic behind the scenes when it comes to remote branches and their remote-tracking branches (when yo do git fetch origin
followed by git branch foo origin/foo
, the "origin/foo" is a remote branch and "foo" is a remote-tracking branch, as it tracks a corresponding remote branch). Another point which you should keep in mind is that remote branches are there to capture the state of the corresponding branches in their remote repository at the time of the last fetch.
Now suppose you push a remote-tracking branch to the same-named branch in the remote repository (the most usual case). Let's say you did this:
$ git fetch origin
(this created the "origin/foo" branch)
$ git chekcout -b foo origin/foo
(this created a new local branch "foo" tracking "origin/foo")
$ git commit ...
$ git push origin foo
While pushing, Git notices that if you would be fetching to the matching remote branch at the same time, you'd get the same commits in it which were just pushed. So it goes on and makes "origin/foo" point to the same commit "foo" points to.
Now back to git pull
... In your case, after git pull
completed, Git noticed that you have just updated a remote-tracking branch with the commits obtained from a branch for which you have a remote branch locally, so it goes on and updates this remote branch because this is its last seen state.
Let's recap:
That is, in my opinion, updating of the remote branch in your case happened not because git fetch ...
was run but because a local remote-tracking branch got updated as the result of merging. Does it make sense now?
Further reading
I would recommend to just read the ".git/config" file in your repository to get the idea of how all this "remote stuff" is configured.
这篇关于`git pull`和`git fetch`不能一致地解释参数:(设计)bug或(设计)特性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!