需要澄清拉动git分支 [英] need clarification on pulling git branches

查看:113
本文介绍了需要澄清拉动git分支的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我总是为拉动git分支而苦苦挣扎,而且从来没有做到正确。我是这里的独立用户。我的工作流程是创建master和dev-stage1,dev-stage2,将代码推送到原点,然后使用分支进行进一步的开发。然后我想合并并摆脱分支。在我的本地机器上,我创建了
src / master和src / dev-stage1等文件夹。在每个文件夹中,我做了

  git init 

然后

  git remote set-url路径/ to / remote / repo 

并为每个文件夹做

  git checkout branch [branch =文件夹名称] 

然后

  git pull分支名称来源/分支名称

在几条线索和错误得到上述工作之后。这个程序是否正确。我不确定这是否会导致我的问题。你可以指点我在正确的方向提及git命令行将是一个帮助。

解决方案


我这是一个独立用户。


我认为这意味着您只能在自己的存储库上工作。然而,你然后说:


git remote set-url path / to / remote / repo

这表明您希望与其他人进行协调,这与独奏用户声明相矛盾。 p>

同时,让我们从基础开始。



版本控制,存储库和工作树



当您使用任何版本控制系统(VCS)时,您正在声明对控制版本感兴趣。也就是说,你想保留并能够访问各种文件的旧版本。为此,我们需要将每个保存的文件的每个保存版本存储在某个地方。保存这些版本的地方是一个版本库



某些版本控制系统对单个文件进行操作。 Git不会:Git一次存储提交,这是整个文件。修订或版本控制的单位是提交。如果提交按顺序编号(尽管它们不在Git中),那么第一次提交时,根据我们的计数提交#0或#1可能会有十几个文件。每个后续的提交也包含所有这些文件(加上您添加的所有文件,减去您删除的任何文件)。告诉VCS 让我第3版意味着回到我保存第3版的时间,并获取所有这些文件。



为了实现这一点,面向提交的VCS需要一个工作树(或连字符或工作树,或任何类似的主题变体)。在这个工作树中,你有你的文件。如果你提取一个旧版本,你将获得与该版本相同的所有文件。如果你跳到最新的(分支机构的头)版本,你会得到所有最新的文件。同时,您也可以更改工作树中的文件,以处理它们。最终,您会通知VCS将新工作树保存为新提交。 (Git在这里添加了一些皱纹。)



Git的提交和Git风格的分支



不同的VCS有不同的方式与分支机构打交道。 Git的很不寻常。 Git的分支由 Git的提交形成。 Git中的每个提交 - 事实上,Git存储在存储库中的每个对象,尽管您大多只会看到提交的这些对象 - 具有Git通过一些深奥的魔法指定的唯一ID 1 这是一个难以理解的(通常不可发音的)数字和字母串: 1f93ca2395be0f98 ... 或其他一些。 b $ b

我们已经提到过,提交会存储工作树的快照,就像它在提交时一样。 (Git的商店,而不是Git的索引的快照,但我们将完全保留另一个发布。)



在Git中,每个提交都有不仅是这个工作树快照,还有:


  • 提交者:的名称和电子邮件地址使用时间戳

  • 作者:撰写文件的人员的姓名和电子邮件地址(通常与提交者,但可以通过电子邮件发送补丁程序,从而使它们分开),并提供时间戳记

  • 日志消息,这是提交者写入的内容描述提交给他们自己和其他人以后回顾它

  • 父母的身份提交



parent 提交是在此新提交之前提交的提交的哈希ID。也就是说,如果我们从一个完全空的存储库开始并进行第一次提交,我们可以像这样绘制:

  A 

(使用一个大写字母代替一个不可理解的哈希ID,我们将在26次提交后用完!)



现在,当我们开始创建新的提交时,它看起来像这样:

  A< -B 

我们说新提交 B 指向第一个提交 A 。由于 A 是第一次提交,它并没有指向任何地方:它根本没有父项。它不能;它是第一个提交。技术术语是 A 是一个 root提交。



当我们做第三次提交 C ,它指向 B

  A< -B< -C 

等等。



绘制这些箭头在文字中是一种痛苦,并不是那么有用,因为这些箭头总是向后指向。您无法将提交点转发给尚不存在的子项,您只能将向后指向。而且,这些箭头永远不会改变: 任何提交都不能改变。 (如果您尝试更改某些内容,则哈希ID会发生变化,因为哈希ID是内容的加密校验和!)所以我们只是建立一条连接线:

<$ p $ <> c $ c> A - B - C - D

最新的提交,Git需要一些帮助。这是分支名称输入图片的地方:分支名称只是指向某个提交的箭头的名称。



out 提交时,从分支名称中出来的箭头是 not 固定的。随着我们添加新的提交,它一直在改变。所以我们把它们画出来:

  A  -  B  -  C  -  D < -  master 

分支名称 master 指向



为了创建一个新的分支,在Git中,我们只需选择我们已有的任何开始提交(通常是一些现有的分支提示,如 D ),并使其成为当前提交,同时还创建新分支名称<指出它:

  A  -  B  -  C  -  D < -  br1(HEAD), master 

现在我们有两个名称指向提交 D ,所以我们需要知道哪一个是我们的。这就是为什么我们在这里添加 HEAD ,这样我们就知道我们的on分支被命名为 br1 。现在让我们做一个新的提交 E 。 Git会移动当前分支名称 br1 指向新的提交。新提交将指向我们 的提交,即 D 。我们将需要在一个新行上绘制:

  A  -  B  -  C  -  D < - 主

E< - br1(HEAD)

回到 master 并添加一个新的提交,通过执行 git checkout master ,对某些文件进行一些更改, git add ing和 git commit 使它们变成 F

  A  -  B  -  C  -  D  -  F < -  master(HEAD)
\
E < - br1

我们在这里画的是提交图。该图技术上是有向无环图或DAG,所以它也被称为DAG。了解这些Git DAG是有效使用Git的关键之一。






1 这个ID实际上,目前是一个以十六进制表示的160位数字。通过计算对象内容的加密哈希来找到该ID。这保证了每一个都是独一无二的,尽管失效的概率随着时间而增长。为了让机会可以接受,最好不要将大约1.7万亿个对象放入任何一个Git存储库。请参阅 SHA如何为git中的大文件生成唯一代码以获取更多信息。






远程和分布式存储库: git fetch git push



让Git特别有趣和现代的是我们可以分发存储库的想法。也就是说,我们可以使用其中一个存储库及其提交和分支,然后使用自己的提交和分支引入第二个 Git存储库。 Git在内部完成这项工作的方式首先是造成这些奇怪哈希ID的原因:这些ID不仅是您的存储库唯一的,而且实际上是唯一的所有共享的

这意味着如果你交叉连接两个不同的Git并告诉他们共享一些提交,那么每个Git都可以判断其他Git已经拥有提交,否则。如果你从他们那里得到提交,但你已经有了特定的提交,那么你不必再次提交。如果你 尚未拥有它,那就明白了,现在你拥有它了。如果你提交它们,那么同样的方法工作方式是一样的:如果你们都有哈希ID,你们都有这个对象;如果没有,一个Git将对象的副本给予另一个,现在都拥有它。



因为每个提交父链接都是哈希ID,所以提供或获取全部他们或你还没有的提交就足够了。谁没有承诺,现在呢。无论谁得到提交(以及其他相关对象),新的DAG现在都已经完整且完整。


$ b 这个转移提交的过程是Git的 fetch push 操作。运行 git fetch 意味着调用其他一些Git,并获取( fetch )提交和来自该Git的其他对象,我的仓库。运行 git push 意味着调用其他一些Git,并将它们从 推送)提交给它们。我的仓库。



远程追踪分行



尽管这里有一个问题,特别是在 git fetch 一边。我们在上面注意到,Git通过分支名称发现了 latest 提交。当我们从某些其他 Git获得一些新的提交时,会发生一些有趣的事情。考虑我们上面绘制的图表:

  A  -  B  -  C  -  D  -  F < -  master HEAD)
\
E < - br1

并且假设我们 git fetch 并引入两个新的提交 G H ,我们这样画:

  G  -  H 
/
A - B- -C -D -F < - 主(HEAD)
\
E -b1

我们如何让Git查找提交 H ?如果它们只是这样的连续字母,我们的Git可以说,记住有八个提交,并且去找 H 。但他们不是 - 他们是不可理解的哈希ID。我们使用自己的分支名称,如 master br1 来记住的哈希ID, F E



这是远程追踪分支名字进入图片。 (这个术语,远程追踪分支,在我看来并不是一个超级名字,但它就是我们所拥有的,并且足够了。)



必须有一些分支名称 - 可能是 master -pointing to commit H 。如果我们的 Git记住它们的 Git的分支名称,但是以其他名称命名,我们可以在 中找到 H 那样。所以这里是我们得到的:

  G  -  H < - 原产地/主产地
/
A - B - C - D - F - - master(HEAD)
\
E -b1
pre>

名称 origin / master ,我们在分支名称前加上 origin / ,跟踪其他Git中的 master



名称 origin 来自Git调用远程的内容。任何其他Git的标准单一远程名称是 origin ,因为我们通常通过执行 git clone 来设置这一切。 。我们克隆了一些其他的现有的 Git仓库,获取其所有的提交和所有的分支。然后我们重命名所有的分支,这样它的 master 就是我们的 origin / master 和它的 br1 是我们的 origin / br1


$ b em>,顺便说一下,主要是URL的简称,但它也是这些远程追踪分支的前缀。)



虽然您可以 git checkout 远程跟踪分支名称(例如,尝试 git checkout origin / master ),这会立即导致Git调用一个分离的HEAD 。在这种情况下,名称HEAD不再引用任何分支。我们得到的结果如下所示:

  G  -  H < -  HEAD,origin / master 
/
A - B - C - D - F - - master
\
E -b1
pre
$ b $ HEAD 现在直接指向提交 H ,而不是包含指向提交 H 分支的名称。我们的 master 指向提交 F 和我们的 br1 指向电子;我们没有指向 H 的任何分支名称。我们只有其中一个远程追踪分支名称,而远程追踪分支不是分支:它只是一个名称。 2






2 更糟的是,Git有一个动词跟踪,这意味着与 完全不同。您现在可能会看到为什么我认为远程追踪分支不是一个出色的名字。在我们感到困惑之前,我们可以用不同的方式使用远程,跟踪和分支这几个词来指代不同的事情多少次? : - )






什么 git checkout does



我们已经提到我们可以使用 git checkout 来检出一个提交或分支。这些是它的主要两件事:签出一个提交,或签出一个分支。



它是做哪一个?那么,它喜欢分支。如果你:

  git checkout master 

然后,由于 master 分支名称,因此会检出分支名称 master ,将 HEAD 附加到 master 。同样,对于 br1 :这是一个分支名称,所以它可以作为一个分支检出。



如果 git checkout< hash-id> ,但它会检出特定的提交,并进入这个分离的HEAD模式。如果您尝试检出标签​​名称或远程跟踪分支名称,也会发生同样的情况。这两个都不是分支名称,所以你不能将它们作为分支进行,所以它只检出提交。



git checkout 检出一个提交,它重新安排工作树(和我们之前提到的Git的索引,这里仍然不会解释)匹配那个提交。检查分支也是如此。当我们 git checkout master 时,我们在分支主上获得,如 git status 会说;但是这会影响从该分支的 tip 提交填充索引和工作树。



分支意味着当我们创建一个新的提交时,Git会使分支名称指向新的提交。我们看到了 master 和 br1 增长了新的提交 E 和上面的 F :当我们 git commit 时,发生这种情况是因为我们在 / p>

合并: git merge



一些分支,并且有一个干净的索引和工作树(经常使用 git status 来检查使用 git status )! ),我们可以要求Git将我们的提交与其他提交合并,以进行新的合并提交。 3



要执行此合并操作,Git必须找到合并基。如果您已使用其他VCS,则其中一些需要您手动查找合并基础。 Git使用提交图形--DAG-为您找到合并基础。



让我们继续我们的示例,在这个示例中,我们引入了两个我们提及的提交,在我们的仓库中,通过 origin / master 。让我们来看看我们的 master ,这是我们的提交 F 。我将重新绘制图表,并在此完全省略 br1 ,因为我们现在不需要它:

  A  -  B  -  C  -  D  -  F < -  master(HEAD)
\
G - H < - - origin / master

现在我们分支 master git status 说没有提交,工作树干净,我们运行:

  git merge origin / master 

这告诉我们的Git找到提交 H ,并将其与我们当前的提交 F 合并我们的Git通过我们的HEAD发现)。 Git通过提交图搜索,找到 可到达的第一个提交, F D



然后Git运行:

  git diff DF#找出我们自D 
git diff DH#以来所做的更改,以了解它们发生了什么变化

然后合并代码尽最大努力将这些更改合并在一起。如果一切顺利,它会将组合的更改写入索引和工作树,然后运行 git commit 来创建新的合并提交



这个合并提交像往常一样在我们的 master 上,但它有点奇怪,它有两个父母。 first 父是我们以前的分支提示提交 F second 父是我们刚合并的提交,它是提交 H 。结果如下图所示:

  A  -  B  -  C  -  D  -  F  - I<  -  master(HEAD)
\\ /
G - H < - 原产地/主产地

现在我们的 master 指向这个新的合并提交 I ,并且 I 指向 F H






3 这是Git重载单词的另一个例子: merge (作为一个动词)两个提交,然后我们做一个合并提交(合并为一个形容词),我们称之为合并(合并为一个名词)。记住merge-as-a-verb是一个动作,而merge-as-a-noun(或形容词)引用合并提交是很重要的。如果我们想要,我们可以让Git做合并作为动词而不创建合并提交。但是,这也是以后的话题。






注意 git merge 并不总是合并



有时 git merge 不包含 合并事物。例如,假设我们从来没有提交 F 呢?假设我们以此开始:

  A  -  B  -  C  -  D < -  master(HEAD)

G - H < - 原产地/主产地

如果我们现在运行 git merge origin / master ,Git可以看到合并基本提交 D em> 当前提交。这意味着Git不需要做任何工作 - 它不必合并作为动词。相反,Git只需 git checkout commit H ,并且使我们的名字 master code>指向提交 H

  A-- B  -  C  -  D 
\
G - H < - master(HEAD),origin / master

现在我们不需要图中的扭结:

  A -  B  -  C  -  D  -  G  -  H  -   - 主人(HEAD),起源/主人

在另一个愚蠢的命名综合征中,尽管没有涉及合并(也没有任何磁带录制设备可以在前进中),Git称之为快进合并高速,但现在我们都习惯于通过Netflix上的数字电影快进或任何其他方式)。

$ b

关于 git pull (不要使用它)



git pull 命令应该是一个方便捷径。有时候它很方便,但它也是一个陷阱。



长久以来,在旧版本的Git中, git pull 会在某些场合破坏你的工作。我相信这些都是固定的,所以如果你有一个现代的Git,这不是一个真正的问题。但它还有其他一些缺点,例如隐藏所有它执行的事实是运行 git fetch ,然后是第二个命令,通常是 git merge < code $。

如果你使用 git pull ,你不会知道 git fetch 确实没有意识到你正在运行 git merge 。一切似乎过于神奇。而且,如果 git merge 步骤失败 - 最终它会 - 你可能会很无奈:你不会知道你在

最后,虽然它很小,但 git的语法拉很奇怪。这是因为它实际上早于遥控器和远程追踪分支名称的发明。 (事实上​​,这就是为什么它看起来像 pull ,而不是 fetch 应该与



<$ p $>

p> git fetch origin
git merge origin / master

这是有道理的),你运行:

  git pull origin master 

为什么这个起源大师而不是起源/主人?或者,如果你隐约知道有一个 git fetch 步骤涉及,为什么它不是 git pull origin origin / master ?为什么我们 git merge origin / master 但是 git pull origin master ?答案都与古代的Git历史有关,它们都不是真的有用 - 除了它们解释了为什么 git fetch origin master br1 真的不好意思(不这样做!)。

如果您避免 git pull 完全(并且记住它只是 git fetch 后跟第二个Git命令),您将学习 git fetch ,然后其他Git命令。一旦你真正理解了它们,如果你发现它更方便,你可以开始使用 git pull :当它出错时,你会知道该怎么做。但在此之前,我建议避免它。


I always struggle with pulling git branches and never get it right. I am a solo user here. My work flow is create master and dev-stage1, dev-stage2, push code to origin and then use a branch for further dev. I would then want to merge and get rid of branches. On my local machine, I created the folders src/master and src/dev-stage1 and so on. In each folder I did

    git init  

then

    git remote set-url path/to/remote/repo

and for each folder I do

    git checkout branch [branch=folder name]

and then

git pull branch name origin/branch name

after few trail and error got the above to work. Is this procedure correct. I am not sure if this is going to cause me issues down the line. Could you point me in the right direction mentioning git command line would be a help.

解决方案

I am a solo user here.

I assume this means that you work on your own repositories only. However, you then say:

git remote set-url path/to/remote/repo

which suggests that you would like to co-ordinate with others, which contradicts the "solo user" claim.

Meanwhile, let's start with the basics.

Version control, repositories, and work-trees

When you use any version control system (VCS), you are declaring an interest in, well, controlling versions. That is, you want to keep, and be able to access, older versions of various files. To do this, we need to store each saved version of each saved file somewhere. The place in which these versions are saved is a repository.

Some version control systems operate on individual files. Git does not: Git stores commits, which are whole sets of files at a time. The unit of revisioning, or versioning, is the commit. If commits were simply numbered sequentially (though they're not in Git), the very first commit, commit #0 or #1 depending on how we count, might have a dozen files in it. Each subsequent commit also has all those files (plus any you added, minus any you removed). Telling the VCS get me version 3 means "go back in time to when I saved version #3, and get all those files."

To make this work, a commit-oriented VCS needs a work tree (or hyphenated, or "working tree", or any number of similar variations on this theme). In this work tree, you have your files. If you extract an old version, you get all the files the way they were as of that version. If you jump to the latest ("head" of a branch) version, you get all the latest files. Meanwhile you can also change the files in the work tree, to do work on them. Eventually you will tell the VCS to save the new work-tree as a new commit. (Git adds several wrinkles here.)

Git's commits and Git-style branches

Different VCSes have different ways of dealing with branches. Git's is quite unusual. Git's branches are formed by Git's commits. Each commit in Git—in fact, every object that Git stores inside the repository, although you will mostly only see these for commits—has a unique ID that Git assigns, through some deep magic,1 that is an incomprehensible (and usually unpronounceable) string of digits and letters: 1f93ca2395be0f98... or some such.

We already mentioned that a commit stores a snapshot of a work-tree, as it was at the time of the commit. (Git's stores instead a snapshot of Git's index, but we'll leave that for another posting entirely.)

In Git, each commit has not only this work-tree snapshot, but also:

  • the committer: the name and email address of the person who made the commit, with a time-stamp
  • the author: the name and email address of the person who authored the files (usually the same as the committer but one can email patches around and hence get them to be separate), with a time-stamp
  • a log message, which is something the committer writes to describe the commit to themselves and others looking back at it later
  • the identity of a parent commit

The parent commit is the hash ID of the commit that comes before this new commit. That is, if we start with a completely empty repository and make a first commit, we might draw it like this:

A

(using a single uppercase letter instead of an incomprehensible hash ID—we'll run out after just 26 commits!).

Now when we go to make a new commit, it looks like this:

A <-B

We say that the new commit B "points to" the first commit A. Since A was the very first commit, it doesn't point anywhere: it has no parent at all. It can't; it was the first commit. The technical term for this is that A is a root commit.

When we make the third commit C, it points back to B:

A <-B <-C

and so on.

Drawing these arrows is a pain in text, and not all that useful since these arrows obviously always point backwards. You can't have a commit point forwards to a child that does not exist yet, you can only point backwards to the parent that does. And, these arrow can never change: nothing about any commit can ever change. (If you try to change something, the hash ID changes, because the hash ID is a cryptographic checksum of the contents!) So we just make a connecting line:

A--B--C--D

To find the latest commit, Git needs a bit of help. This is where branch names enter the picture: a branch name is just a name with an arrow pointing to some commit.

Unlike the arrows coming out of commits, the arrow coming out of a branch name is not fixed. It changes all the time, as we add new commits. So we draw them in:

A--B--C--D   <-- master

The branch name master points to the tip (most recent) commit on the branch.

To make a new branch, in Git, we simply pick any starting commit that we already have—often some existing branch tip like D—and make it the current commit, while also making a new branch name that points to it:

A--B--C--D   <-- br1 (HEAD), master

We now have two names pointing to commit D, so we need to know which one is "ours". That's why we add HEAD here, so that we know that the branch we are "on" is named br1. Now let's make a new commit E. Git will move the current branch name br1 to point to the new commit. The new commit will point back to the commit we were on, i.e., D. We will need to draw this on a new line:

A--B--C--D   <-- master
          \
           E   <-- br1 (HEAD)

Let's get back on master and add a new commit there as well, by doing git checkout master, making some changes to some files, and git adding and git commiting them to make F:

A--B--C--D--F   <-- master (HEAD)
          \
           E   <-- br1

This thing we drew here is the commit graph. This graph is technically a Directed Acyclic Graph or DAG, so it's also called "the DAG". Understanding these Git DAGs is one of the keys to using Git effectively.


1This ID is actually, currently, a 160-bit number represented in hexadecimal. The ID is found by computing a cryptographic hash over the contents of the object. This guarantees that each one is unique, though with an infinitestimal probability of failure that grows over time. To keep the chance acceptable, it's probably wise not to put more than about 1.7 quadrillion objects into any one Git repository. See How does SHA generate unique codes for big files in git for more.


Remotes and distributed repositories: git fetch and git push

What makes Git particularly interesting and modern is the idea that we can distribute repositories. That is, we can have one of these repositories, with its commits and branches, and then throw in a second Git repository, with its own commits and branches. The way Git makes this work internally is the reason for those weird hash IDs in the first place: these IDs are not only unique to your repository, but in fact unique across all shared repositories.

This means that if you cross-connect two different Gits and tell them to share some commits, each Git can tell whether the other Git already has the commit, or not. If you are getting commits from them, but you already have that particular one, you don't have to get it again. If you don't have it yet, you get it, and now you have it. If you are giving commits to them, the same method works the same way: if you both have the hash ID, you both have the object; if not, one Git gives a copy of the object to the other, and now both have it.

Because each commit parent link is a hash ID, giving or getting all the commits they or you don't have yet is sufficient. Whoever didn't have the commits, now does. The new DAG in whoever got the commits (and other related objects) is now full and complete.

This process of transferring commits is Git's fetch and push operations. Running git fetch means "call up some other Git, and get (fetch) commits and other objects from that Git, into my repository." Running git push means "call up some other Git, and give them commits and other objects from (pushed from) my repository."

Remote-tracking branches

There's a problem here though, especially on the git fetch side. We noted above that Git finds the latest commits via branch names. When we get some new commits from some other Git, something interesting happens. Consider the graph we drew above:

A--B--C--D--F   <-- master (HEAD)
          \
           E   <-- br1

and suppose that we git fetch and bring in two new commits G and H, that we draw like this:

           G--H
          /
A--B--C--D--F   <-- master (HEAD)
          \
           E   <-- br1

How will we have our Git find commit H? If they were just sequential letters like this, our Git could, say, remember that there are eight commits, and go find H. But they're not—they're incomprehensible hash IDs. We use our own branch names, like master and br1, to remember the hash IDs of F and E respectively.

This is where remote-tracking branch names enter the picture. (This term, remote-tracking branch, is not a superb name in my opinion, but it's what we have and it suffices.)

For their Git to have commit H, they must have some branch name—probably their master—pointing to commit H. If we have our Git remember their Git's branch names, but under some other name, we can have our Git locate H that way. So here's what we get:

           G--H   <-- origin/master
          /
A--B--C--D--F   <-- master (HEAD)
          \
           E   <-- br1

The name origin/master, where we prefix their branch name with origin/, keeps track of "where master was on that other Git".

The name origin comes from what Git calls a remote. The standard single remote name for any other Git is origin, because we usually get this all set up by doing git clone. We clone some other existing Git repository, getting all of its commits and all of its branches. We then rename all its branches, so that its master is our origin/master and its br1 is our origin/br1.

(This remote, by the way, is primarily a short name for the URL. But it's also the prefix of each of these remote-tracking branches.)

While you can git checkout a remote-tracking branch name (try git checkout origin/master for instance), this immediately results in what Git calls a detached HEAD. In this case, the name HEAD no longer refers to any branch. What we get looks like this:

           G--H   <-- HEAD, origin/master
          /
A--B--C--D--F   <-- master
          \
           E   <-- br1

The name HEAD now points directly to commit H, instead of containing the name of a branch that points to commit H. Our master points to commit F and our br1 points to E; we don't have any branch name pointing to H. We only have one of these remote-tracking branch names, and a remote-tracking branch is not a branch: it's just a name.2


2Worse, Git has a verb, tracking, that means something different from all of these. You might see now why I think "remote-tracking branch" is not a superb name. How many times can we use the words "remote", "tracking", and "branch", in different ways to mean different things, before we get all confused? :-)


What git checkout does

We already mentioned that we can use git checkout to check out a commit or a branch. These are the main two things that it does: check out a commit, or check out a branch.

Which one does it do? Well, it "prefers" branches. If you:

git checkout master

then, since master is a branch name, it checks out the branch name master, attaching HEAD to master. Likewise for br1: that's a branch name, so it can be checked out as a branch.

If you git checkout <hash-id>, though, it checks out the specific commit, and goes into this "detached HEAD" mode. The same happens if you try to check out a tag name, or a remote-tracking branch name. Neither of those is a branch name, so you can't be "on" those as branches, so it just checks out the commit.

When git checkout checks out a commit, it re-arranges the work-tree (and Git's index, which we mentioned before and are still not going to explain here) to match that commit. The same is actually true of checking out a branch. When we git checkout master, we get on branch master, as git status will say; but this has the effect of filling in the index and work-tree from the tip commit of that branch.

Being on a branch means that when we make a new commit, Git will make the branch name point to the new commit. We saw how master and br1 grew new commits E and F above: this happens because we are on those branches when we git commit.

Merging: git merge

Whenever we are on some branch, and have a clean index and work-tree (use git status to check—use git status often!), we can ask Git to merge our commit with some other commit, to make a new merge commit.3

To perform this merge action, Git must find the merge base. If you have used other VCSes, some of them require that you manually find the merge base. Git uses the commit graph—the DAG—to find the merge base for you.

Let's continue with our example, where we've brought in two commits that we name, in our repository, via origin/master. Let's get on our master, which is our commit F. I'm going to re-draw the graph and leave out br1 entirely here because we don't need it now:

A--B--C--D--F   <-- master (HEAD)
          \
           G--H   <-- origin/master

Now that we're on branch master, and git status says nothing to commit, working tree clean, we'll run:

git merge origin/master

This tells our Git to find commit H and merge it with our current commit F (which our Git finds through our HEAD). Git searches through the commit graph to find the first commit that is reachable from both H and F, working backwards along the parent arrows. We can see, just by looking, that this is commit D.

Git then, in effect, runs:

git diff D F    # to find out what we changed since D
git diff D H    # to find out what they changed

The merge code then does its best to combine those changes. If all goes well, it writes the combined changes into the index and work-tree, and then runs git commit to make a new merge commit.

This merge commit goes on our master, as usual, but it's a bit odd in that it has two parents. The first parent is our previous branch tip commit F. The second parent is the commit we just merged, which is commit H. The result looks like this, graph-wise:

A--B--C--D--F---I   <-- master (HEAD)
          \    /
           G--H   <-- origin/master

Our master now points to this new merge commit I, and I points back to both F and H.


3This is yet another example of Git overloading words: we merge (as a verb) two commits, and then we make a merge commit (merge as an adjective), which we call a merge (merge as a noun). It's important to keep in mind that merge-as-a-verb is an action, while merge-as-a-noun (or adjective) refers to a merge commit. We can get Git to do the merge-as-a-verb without creating a merge commit, if we want to. But that, too, is a topic for later.


Note that git merge doesn't always merge

Sometimes git merge does not have to merge things. For instance, suppose we never made commit F at all? Suppose we started with this instead:

A--B--C--D   <-- master (HEAD)
          \
           G--H   <-- origin/master

If we now run git merge origin/master, Git can see that the merge base commit D is the current commit. That means Git does not have to do any work—it does not have to merge-as-a-verb at all. Instead, Git can just git checkout commit H, and also make our name master point to commit H:

A--B--C--D
          \
           G--H   <-- master (HEAD), origin/master

and now we don't need the kink in the graph:

A--B--C--D--G--H   <-- master (HEAD), origin/master

In another fit of silly naming syndrome, Git calls this a fast-forward merge, even though there is no merging involved (nor any tape-recording devices that could be spun forward at high speed, though by now we are all used to "fast-forwarding" through digital movies on Netflix or whatever).

About git pull (don't use it)

The git pull command is meant to be a convenience short-cut. And it is convenient sometimes, but it's also a trap.

For a long time, in old versions of Git, there were numerous bugs in git pull that would destroy your work on some occasions. I believe these are all fixed, so that's not really a problem if you have a modern Git. But it has several other drawbacks, such as hiding the fact that all it does is run git fetch followed by a second command, usually git merge.

If you use git pull, you don't learn what git fetch does, and don't realize you are running git merge. Everything seems excessively magic. Moreover, if the git merge step fails—and eventually it will—you may be quite helpless: you won't know that you are in the middle of a conflicted merge, much less how to read up on what to do about that.

Last, while it's minor, the syntax for git pull is weird. This is because it actually predates the invention of remotes and remote-tracking branch names. (In fact, that's why it seems like pull, not fetch, should be the opposite of push: originally, it was!)

Instead of:

git fetch origin
git merge origin/master

(which makes sense), you run:

git pull origin master

Why is this origin master and not origin/master? Or, if you're vaguely aware that there's a git fetch step involved, why isn't it git pull origin origin/master? Why do we git merge origin/master but git pull origin master? The answers all have to do with the ancient history of Git, and none of them are really all that useful—except that they explain why git fetch origin master br1 is a really bad idea (don't do it!).

If you avoid git pull entirely (and remember that it's just git fetch followed by a second Git command), you will learn git fetch and the other Git commands. Once you really understand them, you can start using git pull if you find it more convenient: you'll know, when it has gone wrong, what to do. But until then, I recommend avoiding it.

这篇关于需要澄清拉动git分支的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆