代码之家  ›  专栏  ›  技术社区  ›  Bob5421

合并时Git是否创建分支

git
  •  1
  • Bob5421  · 技术社区  · 6 年前

    假设我有两个用户在同一个分支(master)上工作。

    (我知道这不是一个好的做法,但它是一个例子)

    第一个用户是什么:

    git clone <repository_url>
    cd myproject
    touch file1.cpp
    git add file1.cpp
    git commit -m "file 1 creation"
    

    第二用户:

    git clone <repository_url>
    cd myproject
    touch file2.cpp
    git add file2.cpp
    git commit -m "file 2 creation"
    

    第一个用户:

    git push origin master
    

    第二用户:

    git pull
    

    此时,Git正在进行合并。

    有时,Git会自动创建分支。在本例中,我看到master在bash提示前缀(在$之前)中合并。 有时,我什么也看不见。

    所以我的问题是:当我执行合并操作时,Git是否创建分支?

    谢谢

    5 回复  |  直到 6 年前
        1
  •  1
  •   torek    6 年前

    您的问题揭示了很多关于Git分支是什么以及合并如何在Git中工作的困惑。(这并不奇怪,因为很多关于Git的介绍都是……不好,而且Git有很多根本的复杂性,这是不能忽略的。)

    我建议任何新加入Git的人 避免 git pull . 相反,跑 git fetch ,然后如果你 希望 合并,运行 git merge . 如果您更喜欢面向REBASE的工作流程,请运行 Git获取 ,然后运行 git rebase . 这会让你更好地了解下面发生了什么。它还可以避免人们在第一次使用Git时经常犯的许多错误。 与push相反的是fetch, 不要拉。

    你提到:

    有时…我懂了 master|MERGING 在我的bash提示前缀中

    你也可以跑步 git status 看看你是否处于冲突合并的中间。

    事实上,在bash提示符中,您的bash shell和git命令之间有一组巧妙的交互,每次要打印提示时,bash都会检查git。如果Git报告您完全在一个存储库中,那么shell将在提示中包含存储库的当前分支名称。如果你正处于这些冲突合并的中间,你会 |MERGING 也添加了。

    但这又引出了一个问题: What exactly do we mean by "branch"? 单击该问题阅读更多信息。这个词 分支 可以引用 分支机构名称 喜欢 master ,或者 DAG中的子图 (我喜欢称之为 匕首; 另一个问题)。

    这个 合并分支 命令 没有 新建 名称 所以在这个意义上,它从不创建分支。

    另一方面, 合并分支 命令 可以 在DAG_____从这个意义上说,它确实创建了一个新的分支。嗯,确实如此 有时 更准确地说 联系在一起 一些 现有的 小刀。这就是不可避免的复杂性出现的地方。

    让我们花点时间检查提交

    也许在Git中最重要的是提交。在Git中,任何提交的真实名称都是它的散列ID,一些丑陋的十六进制数字字符串,显然是随机的,基本上对人类没有用处,但每个字符串对于特定提交都是唯一的,并对其进行标识。就是这样 吉特 找到它:你告诉它找一些难看的大杂烩, a1f9c32... 或者别的什么,你的Git会查到承诺。

    每个提交都存储其ID 以前的 起源 承诺。这意味着如果你告诉Git 最新的 提交,Git可以使用该提交来回顾第二次最新提交。第二个最新提交包含第三个最新提交的ID,依此类推。如果提交ID只是简单的大写字母,我们可以这样绘制它们:

    ... <-F <-G <-H
    

    在哪里? H 是最新的 指向 (包含的ID) G ,哪个指向 F 等等,回顾历史。

    这些承诺 历史;所有Git需要知道的是 哪一个是最新的 . 这就是分支机构的名称 主人 进来:Git不是让你记住疯狂的哈希ID,而是存储 最新的 一个在名字下面像 主人 .

    这意味着当您添加 新的 致力于你的 主人 ,您要做的是让git保存,在新提交中,保存链的旧尖端的ID,然后让git重写 主人 要保留新提示的ID,请执行以下操作:

    ...--F--G--H--I   <-- master
    

    现在 I 指向 H (仍然)指向 G 等等。

    涉及多个Git存储库

    你从这个开始:

    假设我有两个用户在同一个分支上工作( 主人 )

    不管这是不是好的做法,这里有一种感觉,名字, 主人 ,意味着只有一件事。但事实并非如此,因为 有一个Git存储库, 第二用户 有一个Git存储库,以及您所在的位置 git push 有一个Git存储库。 Everyone gets a car upvote meme repository! 你们每个人都有 你自己的 大师。

    你们所有人 分享 但是,有一些 承诺 . 你们都是从克隆一个存储库开始的,这个存储库有一些提交集,然后你们就得到了它们。从那以后,你可能又增加了一些。因此,对于每个提交哈希ID,您的Git存储库 如果您的Git存储库 承诺吧,就是这样 Git获取 Git推送 进来吧。

    什么 Git获取 Git推送 做是为了 连接两个Git存储库 . 在这一点上,无论是谁在发送你的Git,如果你是其中一个 Git推送 其他 Git存储库 Git获取 打包所有提交 他们 有那个吗 不要,反之亦然。发送方将提交包(及其附带的文件)传递给接收方。

    接收器现在有点问题,因为只有哈希ID标识的提交对人类来说是相当无用的。接受者需要给 最后的 提交 名称 .

    当你跑步时 git fetch origin , 您从获得新的承诺 他们的 主人 ,所以名字 你的 Git用来记住 他们的 主人 origin/master .


    在这里, origin 是一个 遥远的 . 大多数存储库只有一个远程存储库,名为 起源 :跑步时 git clone <url> 要克隆存储库,克隆过程将设置此远程服务器,其名称默认为 起源 ,以记住URL。


    提取,然后合并

    假设你们两个都是从一系列以 ,您已经添加了 I--J 不管是谁 他们 在此添加 K--L :

    ...--F--G--H--I--J   <-- master
                \
                 K--L   <-- origin/master
    

    现在是了 你的 工作到 结合 你的工作,不管你做了什么 I-J 具有 他们的 工作,不管他们做什么 K-L .

    在Git中组合的最简单方法是 合并分支 . 这个特别的 友善的 合并,我喜欢称之为 真正的合并 把你的工作和他们的工作结合起来。要做到这一点,必须从你们两个分开的地方开始。 注意这个分支与名称无关 主人 作出承诺,以及 他们 作出承诺。

    合并操作由两部分组成。第一个是我喜欢称之为 合并为动词 要合并: 结合工作。现在,从上面的图中可以清楚地看到,你们两个的最后一个共同点是提交 h . 这就是Git所说的 合并基 .

    Git现在运行 git diff 你可以自己做:

    git diff --find-renames <hash-of-H> <hash-of-J> > /tmp/what-we-did
    git diff --find-renames <hash-of-H> <hash-of-L> > /tmp/what-they-did
    

    你现在可以把你所做的和他们所做的进行比较。Git做同样的事情是为了将您的更改与它们的更改结合起来。

    如果你改变的事情“离”这些事情足够远 他们 已更改或在不同的文件中,Git将成功合并它们。更准确地说,Git会 认为 它成功地结合了它们。(这取决于你,这个比Git聪明的人,来决定真正的成功。)但是如果你改变了 相同的源代码行 如果他们改变了,Git将无法合并两个不同的改变。Git会将它的隐喻之手抛向空中,宣布 合并冲突 停下来做 收拾残局。

    这是您看到合并状态的时候。 Git已停止 在中间 冲突合并。这个 提交图形 看起来仍然像上面的图片,两个“分支”(在匕首的意义上)从一个共同的承诺中分叉出来;它们还没有聚在一起。现在你的工作就是把这些乱七八糟的东西编辑成合理的东西,快跑 git add 在结果上,并使用 git commit (或者在足够新的Git版本中, git merge --continue 但这只是运行 Git提交 )完成合并。

    不过,如果Git认为它可以自己进行合并, 会继续跑的 Git提交 它自己也一样。吉特 在冲突中停止;它将继续 Git提交 部分。

    这个提交即结束即合并,无论Git是单独完成还是停止并让您清理和完成它,都将 绑在一起 双图匕首:

    ...--F--G--H--I--J--M   <-- master
                \      /
                 K----L   <-- origin/master
    

    新合并提交 M 父母,而不是普通人。 这是什么的第二部分 合并分支 是:它创建了一个 合并提交 合并 作为形容词。合并提交将这两把匕首捆绑在一起。 合并没有创建图形片段。合并只是将它们捆绑在一起。

    进行此合并提交将结束合并过程,以便您现在回到正常的非合并状态。但是,现在您有了一个新的提交,显然,其他人没有:您有合并提交 ,这是您刚刚创建的,因此具有一个新的唯一哈希ID,其他人可能还没有。

    (现在可以使用 Git推送 分享他们没有的承诺。)


    在内部,Git使用了大量的快捷方式来避免大量的工作(如果可能的话),但最终, 结合 部分需要计算差异。

    我也在这里遗漏了很多关于 工艺工程。这主要是当你必须清理失败合并的混乱时:合并发生在你的 指数 (也叫你的 集结区 )以及在你的 工作树 (你在哪里工作)。提交、索引和工作树之间的分离更加重要,因为您开始在Git中执行更高级的操作。


    当合并不是合并时

    不是全部 合并分支 操作合并!假设你 没有 从你跑步开始就做了任何工作 git clone ,这样你就可以说:

    ...--F   <-- master, origin/master
    

    现在你跑 Git获取 接受新的承诺 G H :

    ...--F   <-- master
          \
           G--H   <-- origin/master
    

    注意 G 指向 f ,这就是你现在的位置。如果你现在跑步 git merge origin/master (或者只是 )为了让自己更进一步,Git注意到 发散 在这里。Git不需要把你的缺乏工作和他们的工作结合起来,只需 快进 名字 主人 所以它指向提交 h ,并签出提交 h ,给你:

    ...--F--G--H   <-- master, origin/master
    

    什么时候? 合并分支 这样做,它说“快进”:没有差异,没有工作组合,也没有新的合并提交。与真正的合并相比,这个过程对Git来说非常简单:最终,它基本上与 git checkout .

    您可以重新设置基片而不是合并

    我不会在这里详细讨论,但不是 合并 你和别人一起工作,你可以 重碱 你的工作 别人的工作。假设我们从需要组合图表的相同工作开始:

    …--f--g--h--i--j<--master
    \
    K--L<--origin/master
    

    我们可以用git代替合并。 复制 承诺 J 新的,有些不同 我们可以称之为承诺 I' J' 提醒我们他们很像 J 但不一样。(它们将有新的、唯一的哈希ID,不同于您的原始哈希ID J 我们只是这样安排它们:

    ...--F--G--H--I--J   <-- master
                \
                 K--L   <-- origin/master
                     \
                      I'-J'  <-- ???
    

    现在我们有了这些拷贝,我们有了Git“剥标签” 主人 远离的 J 并指出 J’ 相反:

    ...--F--G--H--I--J   <-- ???
                \
                 K--L   <-- origin/master
                     \
                      I'-J'  <-- master
    

    如果你现在选择 放弃 原件,因为您不再需要它们,我们可以完全删除问号标签,并理顺图表中的一个扭结:

    ...--F--G--H--K--L   <-- origin/master
                      \
                       I'-J'  <-- master
    

    现在你好像写下了你的两个承诺 之后 提取承诺 L ,而不是基于提交编写它们 H . 两个新提交附带的源代码 J’ 基于的是 L 而不是在里面 H .

    如果在合并时发生合并冲突,那么在重新组合时几乎肯定会发生合并冲突,并且 可以 得到许多重复的冲突(取决于你做了多少提交),并且他们 可以 比合并更难解决。但最终,您将避免合并提交本身,当您查看所有开发的历史时,它将 似乎 更简单,即使它实际上更复杂。

    是否这样做取决于你。但如果你真的选择去做,你就去做。 而不是 合并。记住,REBASE的工作方式是 复制 一些提交;注意只复制那些尚未发布的提交(使用 Git推送 或者确保其他拥有原件的人从原件切换到新的改进副本。

    最后一个注意事项 Git推送

    我们已经看到上面的fetch(而不是pull)与push相反。但还有一点不对称。当你跑步时 Git推送 ,您将您的Git移交给其他Git存储库。这一切都是通过散列ID工作的,就像fetch是通过散列ID工作的,最后,就像fetch必须在 你的 要记住的存储库 他们的 最新的 主人 , push 必须在中设置名称 他们的 要记住的存储库 你的 最后一次提交。

    但这里是不对称的,你的Git要求他们的Git设置 他们的 主人 . 不是他们的 bob/master 是他们的 主人 . 一般来说,他们的Git会 除非是快进的,否则不要允许这种行动。

    这种快进和我们看到的那种快进不是真正的合并 合并分支 . 这意味着我们只是 向添加提交 一些分支名称:新的提交最终指向它们已经拥有的提交。这就是你跑步时所做的 合并分支 Git钢筋 :你把你的工作和他们最新的结合起来,这样你的工作 添加到 他们的,而不是删除他们的一些。

    最后,Git是关于 承诺 但是分支的名称 主人 或您的远程跟踪名称,如 源站/主服务器 都是关于 找到承诺 . 这两个名字都是针对人类的(不能处理原始散列ID) 用于定位 小费最多 在分支上提交。这个技巧commit指向向后,就像git总是向后工作一样,指向更早的提交,通过这样做,它可以使那些较早的提交保持活动状态。这个名称使tip commit保持活动状态,tip commit保持较早的commit,这就是为什么 Git推送 要求我们向前推进。

    (您可以使用 git push --force 或同等产品。这样做的效果是在接受强制推送的Git中消除一些提交。如果你想杀了他们,那没关系,但其他Git用户可能有 fetch 他们犯了罪,所以要小心:他们会回来的!

        2
  •  1
  •   Tim Biegeleisen    6 年前

    git pull 和做一样吗 git fetch 然后 git merge . 在第二个用户合并结束时,Git将创建一个新的合并 犯罪 但是它本身不会创建一个新的分支。

    这个 master|MERGING 你在控制台上看到的只是意味着Git正在进行合并操作。

        3
  •  0
  •   andrybak Yong Wang    6 年前

    git pull 合并远程分支( origin/master )变成一个本地人 master 分支。正如蒂姆的答案所暗示的,它在本地创建了一个合并提交 主人 分支。

    回答您的问题:在 git clone 操作。 git fetch 更新的状态 源站/主服务器 在本地存储库中(到服务器上的当前状态)。

        4
  •  0
  •   wonderlearner    6 年前

    它不会创建新的分支。第二个用户(例如任何用户)将拥有master的本地副本。通过运行git pull,远程更改是 fetched merged . 如果有冲突的更改,合并将失败。

    最好的处理方法是,对于第二个用户来说:

    git pull --rebase 
    

    这将拉取远程更改并在本地提交的基础上应用它们,然后可以推送到远程主机。

        5
  •  0
  •   Przemysław Gęsieniec    6 年前

    是的,Git正在创建所谓的“ 合并提交”。

    如果你想避免这个,你应该 git pull --rebase 每次你拉。这样Git就可以“边拉边”了。 跳过创建“marge commit” .

    但你应该记住 rebase 选项强制您处理潜在的冲突。