今天遇到个 git 合并丢代码的场景。 featB->featA->master featA 基于 master 开发,featB 基于 featA 开发。featA 合入 master 后,我直接在 featB 分支上 git merge master ,出了问题。 具体如下 featA 对于 file1 加了 line70 ,featB 对 file1 删了 line70 ,在 featB 上 merge master 后,git 自动 merge 的结果是 line70 依然还在
![]() | 1 xiaozhu5 285 天前 git reflog 和 git fsck 两个结合看一下应该找到丢失信息 |
![]() | 2 gesse 285 天前 ![]() featA 添加了 line70 ,并合并进了 master ,你在 featA 上 fork 出 featB ,featB 上删除了 line70 后,又把 master 合并进 featB ,这不就是在 featB 上添加了 line70 吗? 一点毛病没有。 |
3 mark2025 285 天前 ![]() 为什么要在 featB 上面执行 featB 分支上 git merge master ? 这就是混乱的根源 1. 要么是在 featB 分支上 `git merge featA` 2. 要么是在 featB 分支上 `git rebase master` |
![]() | 4 netabare 285 天前 via Android 所以不要把 master 合入分支,分支上面只用 rebase 。 |
5 GeruzoniAnsasu 285 天前 ![]() https://v2ex.com/t/843165#r_11508345 > (!!) 其实我本来想简单解释一下为什么三路合并会出问题,但在我搜索看了近一个小时文章后,我选择放弃解释: https://www.waynerv.com/posts/git-merge-intro/ https://git-repo.info/zh_cn/2020/03/something-about-git-merge/ https://actake.github.io/2021/03/21/git%E5%BF%85%E7%9F%A5%E5%BF%85%E4%BC%9A-%E5%88%86%E6%94%AF%E5%90%88%E5%B9%B6%E9%82%A3%E4%BA%9B%E4%BA%8B/ > (!!) 因为这已经是我至少第 4 次搜索这个问题然后仍然没有完全搞懂了 |
6 mark2025 285 天前 @GeruzoniAnsasu 三路合并就是人多嘴杂,你不仔细查看变动就无法确定最终合并结果是否符合预期。所以基于 rebase 的线性合并在团队开发中是最高效的( gitlab 可以设定强制线性合并) |
![]() | 8 sagaxu 285 天前 git merge 除非是 fast forward ,在处理多个分支修改同一文件时,不一定符合你的预期。 所以这种情况用 rebase 甚至是 reset --soft ,然后手动处理变更会更好。 |
9 GeruzoniAnsasu 285 天前 @mark2025 人多嘴杂是其次,问题在用好 merge 要制定的规范(比如不允许 pull-merge ,自己的分支不能 merge 别人分支后再 merge 到 main……等等,它一点也不直观。 你搞不清楚需要哪些规范才能保证不发生 A merge B 然后 B merge A 导致代码没了这种问题 |
10 mark2025 285 天前 @GeruzoniAnsasu 这是我所有 git 项目钩子自动执行的: git config --global i18n.commitencoding utf-8 git config --local core.autocrlf input git config --local core.eol lf if [ -z "$CI" ]; then git config --local core.filemode false git config --local core.hooksPath ./.githooks git config --local core.ignorecase false git config --local core.precomposeUnicode true git config --local fetch.prune true git config --local pull.rebase true git config --local push.autoSetupRemote true git config --local push.followTags true git config --local rebase.autoStash true git config --local remote.origin.prune true git config --local remote.origin.tagopt --tags git config --local remote.pushdefault origin git config --local rerere.enabled true fi; |
11 leonshaw 285 天前 via Android base 没有 line70 ,那严格来说 B 并没有基于 A 开发 |
12 leonshaw 285 天前 via Android B merge master 没有问题,但是不能 merge 没有进 master 的 A |
13 LeeEnzo 285 天前 强制 rebase 和 squash 开发 |
14 freesun165 OP @netabare 话虽如此,但我这个业务场景经常一个分支测一个月以上,一百多个提交,每次 master 更新,我挨个 rebase 下,成本太高了 |
15 freesun165 OP @leonshaw 我是直接在 featA 上切出去 featB |
16 freesun165 OP @mark2025 俺这规范就是上线前 featB 需要合并 master 推到远端,远端 master 再合并 featB ,然后就出现了这么不符合知觉的事 |
17 riceball 285 天前 还是 Git 操作要有规范,这个彼此合并,左右互搏,啧啧,建议使用 Git flow 规范,有 git 插件支持: https://danielkummer.github.io/git-flow-cheatsheet/index.zh_CN.html |
18 rbaloatiw 285 天前 按你的描述感觉不太可能, 你有最小可复现样例吗, 可以发出来看看 |
19 coolcoffee 285 天前 git 的分支操作应该是像一颗树一样,开叉但是不会互相交叉。 如果平行的节点需要互相同步,那么应该一方往上推到相同的节点,另外一方再去拉,这样就遇到相同修改就必定会产生冲突。 |
![]() | 20 BeautifulSoap 285 天前 via Android ![]() 唔嗯?这情况你确定真没冲突吗? |
![]() | 21 kivmi 285 天前 太危险了,master -> branch , 相当于你对同一行的修改无效啊,各种冲突吧?可以从 master 同时 fork 几个分支?一个为开发分支,一个为上线分支?当需要上线时,合并到上线分支,然后合并到 master ,一直保持上线分支跟 master 保持一致,实现快速上线。貌似 git flow hotfix 也可以做到。 |
![]() | 22 zthxxx 285 天前 老生常谈话题之「不要把 master/dev 合到自己的分支」,开发时也始终应该像 GitHub / GitLab 那样的「把自己分支合到 master/dev (主干分支)」 |
23 leonshaw 285 天前 @freesun165 #15 这样 base 应该是切出去时的 commit ,不然就是后面这个 commit 被重写掉了,最好画个图看看。 |
![]() | 24 BeautifulSoap 285 天前 我实际在本地测试了一下 最终结果是 master 合并入 feature b 后的确没报冲突,但被 feature b 删除的 line 也没再次出现 可能 lz 实际给个最小可复现例子比较好 |
25 nightwitch 285 天前 git 只允许 fast-forward 就不容易出现这个问题。 三路合并一定要小心,否则很容易出现冲掉别人的代码 / 别人的代码把自己的冲掉 / 两边的代码合并到了一起导致逻辑不对了。 |
26 FrankAdler 285 天前 我也遇到过,目前没有头绪,我是 master 拉出来的分支,修改后合并到 dev 分支,cicd 到测试 k8s ,出现过几次代码没有提示冲突,但是丢了几行。 |
![]() | 27 jqtmviyu 284 天前 |
![]() | 30 networm 284 天前 @freesun165 #14 使用 Fork 的 Leaning Branch 功能进行同步,只需要一键就可以自动同步。 另外推荐看下: Git 精干分支 - 狂飙 https://networm.me/2022/09/11/git-lean-branching/ 合并分支只会通过合并提交引入最终的修改,而不是合并分支中的原始提交。 因为合并提交也是提交,但是大家潜意识都不会关注合并提交的改动,因此可能会在冲突解决中引入大量的错误的修改。 如果一个东西容易引起错误,那么建议减少这个东西的使用,使用 Lean Branching 方案就可以。 在与主干同步的时候,相当于检出到主干新建分支,将原有分支上的东西 cherry-pick 到新分支,因此需要逐个解决冲突。 这个方案的核心是只在最后时合并提交,同时由于主干与功能分支的起点之间没有提交,合并提交引入的修改全部都是功能分支的改动。由于前面已经处理了冲突,这里逻辑上就不需要处理冲突,从根本上去除了冲突的处理。 |
31 chenluo0429 284 天前 via Android ![]() 我猜 featA 合并到 master 时,经过了 sqlash 之类的操作,导致 master 上 featA 的修改与 featB 所基于的 featA 并不是同一笔提交,而是修改内容相同的两笔不同提交 |
![]() | 32 feelapi 284 天前 merge, rebase 两种思路是冲突的。不建议混合使用。 merge ,master->A->B ,不能跨越这个流程,B 回到 master ,也要顺序回去。merge 之前,要先从 parent 更新,例如 B 回到 master: 1. merge A->B 2. Merge B->A 3. merge master->A 4. merge A->master |
![]() | 33 wgbx 284 天前 Git 不是万能药,要遵守 git flow |
34 freesun165 OP @rbaloatiw 发了,大佬可以看下 |
35 freesun165 OP @jqtmviyu 就是( main ) git merge A 时加上 squash 就可以复现了,因为当时是 gitlab 上勾选了 squash commit 的选项 |
36 freesun165 OP @chenluo0429 是的 |
![]() | 37 ryougifujino 284 天前 不用 squash 就不会有这个问题,squash 等于把历史线改了。还有功能基于功能分支进行切换是典型的“穷人的模块化架构”,一般采用 feature flags 类似的技术比较好。建议看一下 Martin Fowler 的这篇版本控制流的文章: https://v2ex.com/t/1072486 |
![]() | 38 zhuisui 284 天前 ![]() 你要想充分利用 git merge 的自动冲突解决能力,就不要做 squash 、fixup 、amend 、cherry-pick 这类会修改 commit 的操作,你必须在 merge 时原样保留各路 branch 。 你只要记住,如果你要 merge branch ,一定要保留 branch 原来的样子。因为 git 就是利用 branch 和 merge commit 来决定冲突解决结果的。 |
![]() | 39 proxychains 284 天前 rebase 的好 |
![]() | 40 p1gd0g 284 天前 之前遇到几次丢代码,几个主流的 git 软件都看不到哪一次提交丢了,只有 tortoisegit 可以。 但我们没有用 squash ,我也没搞清楚同事到底是怎么操作的。 |
![]() | 41 wangtian2020 284 天前 不使用 sourcetree 喜欢用命令行操作 git 导致的 |
42 leonshaw 284 天前 很明显你 squash 把 A/B 分支点删除了,导致和 main 的分叉在 local/A 以前,这一行的添加删除都发生在分叉以后,互相抵消了。 因为 A 已经合并了,所以 B 合并前应该 git rebase --onto main local/A 把 A 排除掉。 |
![]() | 43 blublu 284 天前 via iPhone 因为你 feat A 和 master 的 base 点( b1 )并没有 line 70 ,当你把 feat A 向 master 进行 merge 并且用 squash 方式时,新的 merge 点有了 line 70 ,记为 m1 ,当在 feat B 删掉 line70 时,和 base 点(与前面 feat A 与 master 的 base 点一样( b1 ),因为用的是 squash ,所以这个 base 点并不是 feat A 中 co 到 feat B 的那个节点)的对比中,并不会出现 line70 的变更,因为和 feat A 增加的那一行已经抵消掉了,因此会把 m1 与 b1 对比中增加的 line70 加入到 featB 中新的 merge 点 m2 不知道我这样说你清楚了么?不清楚可以再多读几遍,应该解释了整个变更过程了 |
44 mark2025 282 天前 @freesun165 话虽如此,但我这个业务场景经常一个分支测一个月以上,一百多个提交,每次 master 更新,我挨个 rebase 下,成本太高了 ======= 可以不用频繁 rebase ,而是功能分支要合并、发布之前集中一次 rebase 。 |
45 mark2025 282 天前 @freesun165 俺这规范就是上线前 featB 需要合并 master 推到远端,远端 master 再合并 featB ,然后就出现了这么不符合知觉的事 ====== 这个是你们架构师、技术总监等等没设计好流程规范的问题。 |