【问题标题】:need clarification on pulling git branches需要澄清拉动 git 分支
【发布时间】:2017-05-20 11:04:55
【问题描述】:

我总是在拉 git 分支上挣扎,但从来没有把它弄好。我是这里的独奏用户。我的工作流程是创建 master 和 dev-stage1、dev-stage2,将代码推送到源,然后使用分支进行进一步的开发。然后我想合并并摆脱分支。在我的本地机器上,我创建了文件夹 src/master 和 src/dev-stage1 等等。在我所做的每个文件夹中

    git init  

然后

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

对于我所做的每个文件夹

    git checkout branch [branch=folder name]

然后

git pull branch name origin/branch name

经过几次尝试和错误后,上述工作得以实现。这个程序是否正确。我不确定这是否会导致我出现问题。您能否指出正确的方向,提到 git 命令行会有所帮助。

【问题讨论】:

  • 不,这是不正确的。分支不是基于目录的。您的整个存储库(所有分支)都包含在一个目录中。您当然可以将存储库克隆到多个目录,但这与分支不同。
  • 好的,请您详细说明一下,告诉我如何正确操作
  • 对不起,我不是 git 命令行专家,我滥用 IDE。有人会来的。
  • 您可能应该从一本好的 Git 书籍开始,例如 Pro Git:git-scm.com/book/en/v2(但我会尝试回答,但如果我知道您对其他版本控制系统的了解会有所帮助...)
  • 我会通过链接,谢谢

标签: git github


【解决方案1】:

我在这里是一个单独的用户。

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

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

这表明您想与他人协调,这与“单独用户”的说法相矛盾。

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

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

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

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

要实现这项工作,面向提交的 VCS 需要一个工作树(或连字符,或“工作树”,或此主题的任何数量的类似变体)。在这个工作树中,你有你的文件。如果您提取旧版本,您将获得与该版本相同的所有文件。如果你跳转到最新的(分支的“头”)版本,你会得到所有最新的文件。同时,您还可以更改工作树中的文件,对它们进行处理。最终,您将告诉 VCS 将新的工作树保存为新的提交。 (Git 在这里增加了几处皱纹。)

Git 的提交和 Git 风格的分支

不同的 VCS 有不同的处理分支的方式。 Git的很不寻常。 Git 的分支由 Git 的提交形成。 Git 中的每个提交(实际上,Git 存储在存储库中的每个 对象,尽管您大多只会看到这些提交)都有一个唯一的 ID,Git 通过一些深奥的魔法分配, 1 那是一串难以理解(通常是无法发音)的数字和字母:1f93ca2395be0f98... 或类似的。

我们已经提到,提交存储工作树的快照,就像提交时一样。 (Git 存储了 Git index 的快照,但我们将把它完全留到另一篇文章中。)

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

  • 提交者:提交者的姓名和电子邮件地址,带有时间戳
  • 作者: 文件作者的姓名和电子邮件地址(通常与提交者相同,但可以通过电子邮件发送补丁,从而将它们分开),有时间-邮票
  • 日志消息,这是提交者编写的内容,用于向自己和其他人稍后回顾提交描述提交
  • 提交的身份

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

A

(使用单个大写字母而不是难以理解的哈希 ID——我们将在 26 次提交后用完!)。

现在,当我们进行 new 提交时,它看起来像这样:

A <-B

我们说新的提交B“指向”第一个提交A。因为A 是第一次提交,所以它没有指向任何地方:它根本没有父级。它不能;这是 first 提交。技术术语是 A 是一个 root 提交。

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

A <-B <-C

等等。

在文本中绘制这些箭头是一件痛苦的事,而且并不是很有用,因为这些箭头显然总是指向后面。您不能将提交点 forwards 指向尚不存在的子级,您只能将 backwards 指向存在的父级。而且,这些箭头永远不会改变:任何提交都不会改变。 (如果您尝试更改某些内容,则哈希 ID 会更改,因为哈希 ID 是内容的加密校验和!)所以我们只需制作一条连接线:

A--B--C--D

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

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

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

分支名称 master 指向分支上的tip(最近的)提交。

要创建一个新的分支,在 Git 中,我们只需选择我们已经拥有的任何开始提交(通常是一些现有的分支提示,如 D),并将其设为当前提交,同时也将一个指向它的新分支 name

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

我们现在有两个名称指向提交D,所以我们需要知道哪个是“我们的”。这就是我们在这里添加HEAD 的原因,以便我们知道我们“打开”的分支被命名为br1。现在让我们进行一个新的提交E。 Git 将移动当前分支名称br1 以指向新的提交。新的提交将指向我们曾经的提交,即D。我们需要在新行上绘制:

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

让我们回到master 并在那里添加一个新的提交,通过执行git checkout master,对一些文件进行一些更改,然后git adding 和git commiting 他们使F

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

我们在这里画的这个东西是提交图。该图在技术上是有向无环图或 DAG,因此也称为“DAG”。了解这些 Git DAG 是有效使用 Git 的关键之一。


1这个 ID 目前实际上是一个以十六进制表示的 160 位数字。 ID 是通过计算对象内容的加密散列来找到的。这保证了每一个都是独一无二的,尽管随着时间的推移,失败的可能性很小。为了保持可接受的机会,最好不要将超过 1.7 万亿个对象放入任何一个 Git 存储库中。请参阅How does SHA generate unique codes for big files in git 了解更多信息。


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

使 Git 特别有趣和现代的原因在于我们可以分发存储库的想法。也就是说,我们可以拥有其中一个存储库,以及它的提交和分支,然后放入一个 second Git 存储库,具有它自己的 提交和分支。 Git 在内部进行这项工作的方式首先是产生那些奇怪的哈希 ID 的原因:这些 ID 不仅对于 您的 存储库是唯一的,而且实际上在 所有共享的中都是唯一的> 存储库。

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

因为每个提交父链接都是一个哈希 ID,所以提供或获取他们或您还没有的所有提交就足够了。谁没有提交,现在有。获得提交(和其他相关对象)的人中的新 DAG 现在已满。

这个传输提交的过程是Git的fetchpush操作。运行 git fetch 意味着“调用其他 Git,并从该 Git 获取 (fetch) 提交和其他对象,进入我的存储库。”运行 git push 意味着“调用其他 Git,并为他们提供提交和其他对象来自推送)我的存储库。”

远程跟踪分支

这里有一个问题,尤其是在git fetch 方面。我们在上面提到 Git 通过分支名称找到 latest 提交。当我们从一些 other Git 获得一些新的提交时,会发生一些有趣的事情。考虑我们上面绘制的图表:

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

假设我们 git fetch 并引入了两个新的提交 GH,我们这样绘制:

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

我们如何让 Git 找到提交 H?如果它们只是像这样的连续字母,我们的 Git 可以,比如说,记住有八个提交,然后找到 H。但它们不是——它们是难以理解的哈希 ID。我们使用自己的分支名称,如masterbr1,分别记住FE 的哈希ID。

这是远程跟踪分支名称输入图片的地方。 (这个术语,remote-tracking branch,在我看来并不是一个很好的名字,但它是我们所拥有的,它就足够了。)

他们的 Git 要提交H他们必须有一些分支名称——可能是他们的master——指向提交H。如果我们有 我们的 Git 记住 他们的 Git 的分支名称,但使用其他名称,我们可以让 我们的 Git 以这种方式定位 H。所以这就是我们得到的:

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

名称origin/master,我们在其分支名称前加上origin/,跟踪“master 在另一个 Git 上的位置”。

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

(顺便说一下,这个 remote 主要是 URL 的简称。但它也是每个远程跟踪分支的前缀。)

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

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

名称HEAD 现在直接指向提交H,而不是包含指向提交H分支 的名称。我们的master 指向提交F,我们的br1 指向E;我们没有任何分支名称指向H。我们只有一个 remote-tracking 分支 名称,而 remote-tracking 分支 不是分支:它只是一个名称。2


2更糟糕的是,Git 有一个动词,tracking,它的意思与 all 不同。您现在可能会明白为什么我认为“远程跟踪分支”不是一个好名字。我们可以用不同的方式使用“远程”、“跟踪”和“分支”这几个词来表示不同的东西,在我们都感到困惑之前,我们可以使用多少次? :-)


git checkout 做了什么

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

它是做什么的?好吧,它“更喜欢”分支。如果你:

git checkout master

然后,由于master 分支名称,它检查分支名称master,将HEAD附加到masterbr1 也是如此:这是一个分支名称,因此可以将其作为分支检出。

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

git checkout 签出一个提交时,它会重新排列工作树(以及Git 的索引,我们之前提到过,但这里仍不解释)以匹配该提交。检查分支实际上也是如此。当我们git checkout master 时,我们得到on branch master,正如git status 所说;但这具有从该分支的 tip 提交中填充索引和工作树的效果。

在一个分支上 意味着当我们进行 new 提交时,Git 将使 分支名称 指向新的提交。我们在上面看到 masterbr1 如何增加新的提交 EF:这是因为当我们 git commit 时我们在这些分支上

合并: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 表示 nothing to commit, working tree clean,我们将运行:

git merge origin/master

这告诉我们的 Git 找到提交 H 并将其与我们当前的提交 F 合并(我们的 Git 通过我们的 HEAD 找到)。 Git 搜索提交图以找到第一个 可从HF 的提交,沿着父箭头向后工作。我们可以通过查看看到这是commit D

然后,Git 实际上运行:

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

然后,合并代码会尽力合并这些更改。如果一切顺利,它将合并的更改写入索引和工作树,然后运行git commit 以进行新的合并提交

与往常一样,此合并提交在我们的master 上进行,但有点奇怪,它有两个 父级。 first 父级是我们之前的分支提示提交Fsecond 父级是我们刚刚合并的提交,即提交H。结果如下图所示:

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

我们的master 现在指向这个新的合并提交II 又指向FH


3这是另一个 Git 重载单词的例子:我们 merge(作为动词)两次提交,然后我们进行 merge commit em>(作为形容词合并),我们称之为a merge(作为名词合并)。重要的是要记住,merge-as-a-verb 是一个 action,而 merge-as-a-noun(或形容词)指的是 merge commit。如果我们愿意,我们可以让 Git 在不创建合并提交的情况下执行合并作为动词。但这也是以后的话题。


请注意,git merge 并不总是合并

有时git merge 没有 来合并事物。例如,假设我们根本没有提交F?假设我们从这个开始:

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

如果我们现在运行git merge origin/master,Git 可以看到合并基础提交D 当前 提交。这意味着 Git 不需要做任何工作——它根本不需要合并动词。相反,Git 可以只git checkout 提交H,也可以让我们的名字master 指向提交H

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

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

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

在另一种愚蠢的命名综合症中,Git 称之为 快进合并,即使不涉及合并(也没有任何可以高速旋转的录音设备,虽然现在我们都习惯于“快进”通过 Netflix 上的数字电影或其他)。

关于git pull(不要用)

git pull 命令是一种方便快捷的方式。而且它有时很方便,但它也是一个陷阱。

很长一段时间以来,在旧版本的 Git 中,git pull 中存在许多错误,这些错误有时会破坏您的工作。我相信这些都是固定的,所以如果你有一个现代的 Git,这不是一个真正的问题。但它还有其他一些缺点,例如隐藏它所做的只是运行git fetch 后跟第二个命令,通常是git merge

如果您使用git pull,您不会了解git fetch 的作用,也不会意识到您正在运行git merge。一切似乎都太神奇了。此外,如果git merge 步骤失败——最终会失败——你可能会非常无助:你不会知道自己正处于冲突合并中,更不用说如何阅读了关于如何处理。

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

代替:

git fetch origin
git merge origin/master

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

git pull origin master

为什么这是origin master 而不是origin/master?或者,如果您隐约知道涉及到git fetch 步骤,那为什么不是git pull origin origin/master?为什么我们git merge origin/mastergit pull origin master?答案都与 Git 的古老历史有关,而且它们都不是真正有用的——除了它们解释了为什么 git fetch origin master br1 是一个非常糟糕的主意(不要这样做! )。

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

【讨论】:

  • 像往常一样,一个绝妙的答案,恭喜你达到100K!在远程跟踪分支上,另见stackoverflow.com/a/1070851/6309
  • @VonC: 是的 - 我不想在这里讨论它,但是 tracking-as-a-verb ("branch X tracking Y") 的意思是“它的上游设置为” ("分支 X 的上游设置为 Y")。
  • 多么令人着迷的回复,我完全冲昏了头脑,为此腾出空间。很清楚,希望你把它放在博客上供大众阅读。 Git 术语让我感到困惑,因此我很难在第一次就把它弄好。谢谢
【解决方案2】:

设置现有存储库以针对远程存储库进行推送/拉取。

CD 进入您所做的目录:

git init

然后运行:

git remote add origin https://url/to/your/repository.git
git push -u origin master

【讨论】:

    猜你喜欢
    • 2011-11-09
    • 2012-07-18
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 2021-07-11
    • 2012-06-02
    • 2017-01-18
    相关资源
    最近更新 更多