Git 工作流程以及 rebase 与合并问题

2023-11-23

我和另一位开发人员在一个项目中使用 Git 已有几个月了。我有多年的经验SVN,所以我想我给这段关系带来了很多包袱。

我听说 Git 非常适合分支和合并,但到目前为止,我只是没有看到它。当然,分支非常简单,但是当我尝试合并时,一切都变得一团糟。现在,我已经习惯了 SVN,但在我看来,我只是将一个低于标准的版本控制系统换成了另一个。

我的搭档告诉我,我的问题源于我不情愿地合并的愿望,并且在很多情况下我应该使用 rebase 而不是合并。例如,这是他制定的工作流程:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

本质上,创建一个功能分支,始终从主分支变基到分支,然后从分支合并回主分支。需要注意的是,分支机构始终位于本地。

这是我开始的工作流程

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature origin/my_new_feature
..work, commit, push to origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, push to origin/my_new_feature
git merge master
..finish my_new_feature, push to origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

有两个本质区别(我认为):我始终使用合并而不是变基,并且我将我的功能分支(以及我的功能分支提交)推送到远程存储库。

我对远程分支的理由是我希望在工作时备份我的工作。我们的存储库会自动备份,如果出现问题可以恢复。我的笔记本电脑不是,或者说没有那么彻底。因此,我讨厌在我的笔记本电脑上保存未在其他地方镜像的代码。

我对合并而不是变基的理由是,合并似乎是标准的,而变基似乎是一项高级功能。我的直觉是,我想做的不是高级设置,因此应该没有必要进行变基。我什至仔细阅读了关于 Git 的新实用编程书籍,它们广泛介绍了合并,但几乎没有提到 rebase。

不管怎样,我在最近的一个分支上遵循我的工作流程,当我试图将它合并回 master 时,一切都陷入了困境。与本不重要的事情发生了很多冲突。这些冲突对我来说毫无意义。我花了一天的时间来解决所有问题,最终导致强制推送到远程主机,因为我的本地主机已经解决了所有冲突,但远程主机仍然不高兴。

对于这样的事情,“正确”的工作流程是什么? Git 应该让分支和合并变得超级简单,但我只是没有看到它。

更新2011-04-15

这似乎是一个非常受欢迎的问题,所以我想我应该用我第一次问以来两年的经验来更新。

事实证明,最初的工作流程是正确的,至少在我们的例子中是这样。换句话说,这就是我们所做的并且有效:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

事实上,我们的工作流程有点不同,正如我们倾向于做的那样挤压合并而不是原始合并。 (注意:这是有争议的,见下文。)这使我们能够将整个功能分支转变为 master 上的单个提交。然后我们删除我们的功能分支。这允许我们在 master 上逻辑地构建我们的提交,即使它们在我们的分支上有点混乱。所以,这就是我们所做的:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

壁球合并争议- 正如一些评论者所指出的,挤压合并将丢弃功能分支上的所有历史记录。顾名思义,它将所有提交压缩为一个提交。对于小功能来说,这是有意义的,因为它将其压缩到一个包中。对于较大的功能,这可能不是一个好主意,特别是如果您的个人提交已经是原子的。这确实取决于个人喜好。

Github 和 Bitbucket(其他?)拉取请求- 如果您想知道合并/变基与拉取请求有何关系,我建议您遵循上述所有步骤,直到您准备好合并回 master。您只需接受 PR,而不用手动与 git 合并。请注意,这不会进行挤压合并(至少默认情况下不会),但非挤压、非快进是拉取请求社区中接受的合并约定(据我所知)。具体来说,它的工作原理是这样的:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git push # May need to force push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git push # Will probably need to force push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote prune origin

我已经爱上了 Git 并且再也不想回到 SVN 了。如果你正在挣扎,那就坚持下去,最终你会看到隧道尽头的曙光。


TL;DR

git rebase 工作流程不能保护您免受不擅长解决冲突的人或习惯 SVN 工作流程的人的影响,如建议的那样避免 Git 灾难:一个血淋淋的故事。这只会让他们的冲突解决变得更加乏味,并且更难从糟糕的冲突解决中恢复过来。相反,使用 diff3 这样一开始就不那么困难了。


Rebase 工作流程对于解决冲突来说并不是更好!

我非常赞成用 rebase 来清理历史。然而如果当我遇到冲突时,我会立即中止变基并进行合并!人们推荐将变基工作流程作为解决冲突的合并工作流程的更好替代方案(这正是这个问题的目的),这真的让我很伤心。

如果它在合并期间“一切都陷入地狱”,那么在变基期间它也会“一切都陷入地狱”,并且可能还会有更多的地狱!原因如下:

原因#1:解决冲突一次,而不是每次提交一次

当您变基而不是合并时,对于相同的冲突,您将必须执行冲突解决,最多与您提交变基的次数一样多!

真实场景

我从 master 分支出来,在分支中重构一个复杂的方法。我的重构工作总共包括 15 次提交,因为我致力于重构它并进行代码审查。我的重构的一部分涉及修复 master 中之前存在的混合制表符和空格。这是必要的,但不幸的是,它会与 master 中此方法之后所做的任何更改发生冲突。果然,当我正在研究这个方法时,有人对主分支中的同一方法进行了简单、合法的更改,该更改应该与我的更改合并。

当需要将我的分支与 master 合并时,我有两个选择:

git 合并:我遇到了冲突。我看到他们对 master 所做的更改并将其合并到我的分支(的最终产品)中。完毕。

git 变基:我和我的人发生冲突first犯罪。我解决了冲突并继续变基。 我和我的人发生冲突second犯罪。我解决了冲突并继续变基。 我和我的人发生冲突third犯罪。我解决了冲突并继续变基。 我和我的人发生冲突fourth犯罪。我解决了冲突并继续变基。 我和我的人发生冲突fifth犯罪。我解决了冲突并继续变基。 我和我的人发生冲突sixth犯罪。我解决了冲突并继续变基。 我和我的人发生冲突seventh犯罪。我解决了冲突并继续变基。 我和我的人发生冲突eighth犯罪。我解决了冲突并继续变基。 我和我的人发生冲突ninth犯罪。我解决了冲突并继续变基。 我和我的人发生冲突tenth犯罪。我解决了冲突并继续变基。 我和我的人发生冲突eleventh犯罪。我解决了冲突并继续变基。 我和我的人发生冲突twelfth犯罪。我解决了冲突并继续变基。 我和我的人发生冲突第十三犯罪。我解决了冲突并继续变基。 我和我的人发生冲突第十四犯罪。我解决了冲突并继续变基。 我和我的人发生冲突第十五犯罪。我解决了冲突并继续变基。

你一定是在跟我开玩笑this是您首选的工作流程。它所需要的只是一个与 master 上所做的一项更改冲突的空白修复,并且每次提交都会发生冲突并且必须解决。这是一个simple仅存在空格冲突的场景。但愿你不会遇到涉及文件间重大代码更改的真正冲突,并且必须解决that多次。

由于您需要解决所有额外的冲突,它只会增加以下可能性:你会犯错误的。但在 git 中犯错是可以的,因为你可以撤消,对吧?当然除了...

原因#2:使用变基,就无法撤消!

我想我们都同意,解决冲突可能很困难,而且有些人在这方面做得很糟糕。它很容易出错,这就是为什么 git 可以轻松撤销的原因!

当你合并时在一个分支上,git 创建一个合并提交,如果冲突解决效果不佳,可以将其丢弃或修改。即使您已经将错误的合并提交推送到公共/权威存储库,您也可以使用git revert撤消合并引入的更改并在新的合并提交中正确重做合并。

当你变基时一个分支,万一冲突解决方式错误,你就完蛋了。现在每个提交都包含错误的合并,并且您不能只重做 rebase*。最好的情况是,您必须返回并修改每个受影响的提交。不好玩。

变基后,无法确定哪些内容最初是提交的一部分,哪些内容是由于冲突解决不当而引入的。

*It can be possible to undo a rebase if you can dig the old refs out of git's internal logs, or if you create a third branch that points to the last commit before rebasing.

摆脱冲突解决:使用 diff3

以这个冲突为例:

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

从冲突来看,不可能知道每个分支发生了什么变化或者其意图是什么。在我看来,这是解决冲突令人困惑和困难的最大原因。

diff3 来救援!

git config --global merge.conflictstyle diff3

当您使用 diff3 时,每个新冲突都会有一个第三部分,即合并的共同祖先。

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

首先检查合并的共同祖先。然后比较每一方以确定每个分支的意图。您可以看到 HEAD 将 EmailMessage 更改为 TextMessage。其目的是更改用于 TextMessage 的类,传递相同的参数。您还可以看到,feature-branch 的意图是为 :include_timestamp 选项传递 false 而不是 true。要合并这些更改,请结合两者的意图:

TextMessage.send(:include_timestamp => false)

一般来说:

  1. 比较每个分支的共同祖先,确定哪个分支的变化最简单
  2. 将这个简单的更改应用于其他分支的代码版本,以便它包含更简单和更复杂的更改
  3. 删除除刚刚将更改合并到一起的部分之外的所有冲突代码部分

替代方案:通过手动应用分支的更改来解决

最后,即使使用 diff3,有些冲突也很难理解。尤其是当 diff 发现语义上不常见的共同行时(例如,两个分支碰巧在同一位置有一个空行!),就会发生这种情况。例如,一个分支更改类主体的缩进或重新排序类似的方法。在这些情况下,更好的解决策略可以是检查合并两侧的更改,然后手动将差异应用到另一个文件。

让我们看看如何解决合并场景中的冲突origin/feature1 where lib/message.rb冲突。

  1. 决定我们当前是否签出分支(HEAD, or --ours)或我们要合并的分支(origin/feature1, or --theirs) 是一个更容易应用的更改。使用 diff 和三点 (git diff a...b) 显示发生的变化b自从上次偏离以来a,或者换句话说,将 a 和 b 的共同祖先与 b 进行比较。

    git diff HEAD...origin/feature1 -- lib/message.rb # show the change in feature1
    git diff origin/feature1...HEAD -- lib/message.rb # show the change in our branch
    
  2. 查看该文件的更复杂版本。这将删除所有冲突标记并使用您选择的一侧。

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if origin/feature1's change is more complicated
    
  3. 签出复杂的更改后,拉出简单更改的差异(请参阅步骤 1)。将此差异中的每个更改应用于冲突文件。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Git 工作流程以及 rebase 与合并问题 的相关文章

  • Git:切换工作区(计算机)而不提交

    有没有办法在不进行提交 签出的情况下应用差异补丁或类似补丁 我的情况 我工作时经常在计算机之间切换 我的提交历史有一堆 开关机 消息 我最初的猜测是这可能会导致其他麻烦 但我想我可能会问是否有适合这种情况的合适的解决方案或工作流程 编辑 澄
  • GIT Rebase 协作的分支?

    阅读本文后 重新设置基点以收集从主分支到我的功能分支的更改是有意义的 Git 工作流程以及 rebase 与合并问题 https stackoverflow com questions 457927 git workflow and reb
  • git:键不包含节

    我使用的是 Git 版本 1 8 4 2 When I press tab to auto complete any command it prints the error below and it also completes the c
  • 如何在 Git 中重命名带注释的标签

    如何在 Git 中重命名现有的带注释的标签 我在存储库上有近一百个代表版本号的标签 每个标签都注释有有关该版本更改内容的有用描述 我想更改用于这些标签的命名样式 记录标签消息 删除标签 然后使用旧消息和新名称重新创建它 这对于手动为近一百个
  • 为什么 git-cherry pick 没有说要提交什么?

    我对以下问题进行了很多搜索 但无法获得任何实质性信息 我创建了一个临时分支 202116 并尝试对 gerrit 202116 进行挑选 并收到以下消息 为什么我无法挑选此提交以及为什么会收到此错误 lt gt git fetch ssh
  • 如何在 Mac OS X 10.9 上安装和使用最新的 Git?

    我从 sourceforge 下载了 Git 2 4 3http git scm com download mac http git scm com download mac对于我的 Macbook Pro OS X 10 9 5 然后安装
  • 如何使用 python 模块的多个 git 分支?

    我想使用 git 来同时处理我正在编写的模块中的多个功能 我目前正在使用 SVN 只有一个工作区 因此我的 PYTHONPATH 上只有该工作区 我意识到这不太理想 所以我想知道是否有人可以建议一种更 正确 的方法来做到这一点 让我用一个假
  • 是什么让 DVCS 中的合并变得如此简单?

    我读于乔尔谈软件 http www joelonsoftware com items 2010 03 17 html 通过分布式版本控制 分布式部分实际上不是 最有趣的部分 有趣的是 这些 系统根据变化来思考 而不是 就版本而言 and a
  • 将文件提交到不同的分支而不进行签出

    是否可以在 git 分支中提交文件而不检出该分支 如果是这样怎么办 本质上 我希望能够将文件保存在我的 github Pages 分支中 而无需一直切换分支 有什么想法吗 我需要以原子方式完成此操作 而不更改当前目录 因此多个命令将无法工作
  • 如何在版本控制系统中安全地保存密钥和密码?

    我在版本控制系统中保留了开发和生产服务器的主机名和端口等重要设置 但我知道这是不好的做法保留secrets 如私钥和数据库密码 位于 VCS 存储库中 但密码 就像任何其他设置一样 似乎应该进行版本控制 所以呢is保持密码版本控制的正确方法
  • 如何在 git 中使用我的更改进行合并?

    在 git 中合并时如何强制 我的更改 有人将所有 bin 目录放入 git 中 现在我遇到了可怕的合并冲突 现在它说 当你解决了这个问题后 运行 git rebase 继续 如果你 宁愿跳过这个补丁 而是运行 git rebase ski
  • 尝试匿名克隆 github 存储库时权限被拒绝

    作为匿名用户 github 上没有存储公钥 我尝试通过以下方式克隆公共存储库 git 网址 但它失败了 git clone email protected cdn cgi l email protection mikehaertl phpw
  • 你遇到过哪些 git 陷阱?

    我遇到的最糟糕的情况是 git 子模块 我在 github 上有一个项目的子模块 该项目无人维护 我想提交补丁 但无法提交 所以我分叉了 现在子模块指向原始库 而我需要它指向 fork 因此 我删除了旧的子模块 并将其替换为同一提交中新项目
  • 是否可以使用 Visual Studio 中的 git stash 命令

    我正在使用 Visual Studio 2017 的团队资源管理器来处理git存储库 每次我使用git stash or git stash pop 我需要打开 Git Bash 控制台并导航到我的项目文件夹并运行命令 有什么方法可以隐藏在
  • .gitignore 在提交、推送或到达服务器时起作用吗?

    说我有一个 gitignore忽略所有 class files 当 时这些文件是否远离远程源 我在本地提交 添加我的文件 我的吗git寻找一个 gitignore当使用添加 提交时 并根据它所说的内容 从提交中删除内容 我推动我的承诺 做g
  • 两个目录中相同文件的 Git Diff 总是会导致“重命名”

    git diff no index no prefix summary U4000 目录1 目录2 这按预期工作 因为它返回两个目录之间所有文件的差异 添加的文件按预期输出 删除的文件也会产生预期的 diff 输出 但是 由于 diff 将
  • 对于 Web 应用程序来说,您理想的 git 分支架构是什么?

    我们是一个由开发人员组成的小团队 正在构建 Web 应用程序 我们目前拥有一个实时 测试和多个开发环境 您会建议什么分支架构 以便理想情况下每个开发人员都可以处理他的功能 这些功能可以在不影响其他开发人员 功能的情况下进行测试和部署 目前
  • 如何从不同分支上的本地提交复制文件?

    我提交了一个文件master分支但未推送remote 现在我正在努力feature分支 我希望将该文件复制到feature分支来自master分支 我怎样才能做到这一点 您可以从另一个分支检出特定文件 git checkout master
  • 如果使用 Maven,是否应该忽略 VCS 中 Eclipse 特定的文件?

    我知道为什么不将 Eclipse IDE 特定的文件提交到像 Git 我实际上正在使用的 这样的 VCS 中 这就是我使用 Maven 并让它为您生成这些文件的原因之一not将它们置于版本控制之下 但我想知道 是否应该在 gitignore
  • 如何克隆 bitbucket 存储库?

    一段时间后重新开始工作 我似乎不知道如何克隆 bitbucket 存储库 知道为什么我收到 未找到 错误吗 git clone verbose https bitbucket org helllamer mod openid Cloning

随机推荐