V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
git
Pro Git
Atlassian Git Tutorial
Pro Git 简体中文翻译
GitX
freesun165
V2EX  ›  git

求助 git 自动 merge 丢代码

  •  
  •   freesun165 · 18 天前 · 3415 次点击

    今天遇到个 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 条附言  ·  18 天前
    分析了下,问题就在于 featB 合入 master 时,base 是没有 line70 的,featB 没有 line70 ,master 有 line70 ,理所当然 line70 就被加上去了。
    第 2 条附言  ·  17 天前

    复现在这:
    分支 local/A 增加一行
    ( local/A ) git co -b local/B
    ( local/A ) git co main
    ( main ) git merge local/A --squash
    ( main ) git co local/B
    ( local/B ) git ci -m 'rm line'
    ( local/B ) git merge main

    刚才删除的一行就又被加上了。
    ---
    这个场景还比较常见,A 分支是某个产品 1.0 功能,B 分支是另外一个人开发 1.1 功能,从 A 分支上切出,同时开发。
    A 分支上需要对某个暂不支持的功能加上禁用,B 分支上放开限制。
    在 A 分支合入 master 上线后,gitlab 上用的是 squash commits 。B 分支上线前,本地 git merge master 提交远端,远端再合入。
    45 条回复    2025-01-02 13:03:32 +08:00
    xiaozhu5
        1
    xiaozhu5  
       18 天前
    git reflog 和 git fsck 两个结合看一下应该找到丢失信息
    gesse
        2
    gesse  
       18 天前   ❤️ 1
    featA 添加了 line70 ,并合并进了 master ,你在 featA 上 fork 出 featB ,featB 上删除了 line70 后,又把 master 合并进 featB ,这不就是在 featB 上添加了 line70 吗? 一点毛病没有。
    mark2025
        3
    mark2025  
       18 天前   ❤️ 1
    为什么要在 featB 上面执行 featB 分支上 git merge master ? 这就是混乱的根源
    1. 要么是在 featB 分支上 `git merge featA`
    2. 要么是在 featB 分支上 `git rebase master`
    netabare
        4
    netabare  
       18 天前 via Android
    所以不要把 master 合入分支,分支上面只用 rebase 。
    GeruzoniAnsasu
        5
    GeruzoniAnsasu  
       18 天前   ❤️ 2
    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 次搜索这个问题然后仍然没有完全搞懂了
    mark2025
        6
    mark2025  
       18 天前
    @GeruzoniAnsasu 三路合并就是人多嘴杂,你不仔细查看变动就无法确定最终合并结果是否符合预期。所以基于 rebase 的线性合并在团队开发中是最高效的( gitlab 可以设定强制线性合并)
    mark2025
        7
    mark2025  
       18 天前
    @mark2025 并且线性合并的另外一个好处是 revert 时工作量低不容易出错。
    sagaxu
        8
    sagaxu  
       18 天前
    git merge 除非是 fast forward ,在处理多个分支修改同一文件时,不一定符合你的预期。

    所以这种情况用 rebase 甚至是 reset --soft ,然后手动处理变更会更好。
    GeruzoniAnsasu
        9
    GeruzoniAnsasu  
       18 天前
    @mark2025 人多嘴杂是其次,问题在用好 merge 要制定的规范(比如不允许 pull-merge ,自己的分支不能 merge 别人分支后再 merge 到 main……等等,它一点也不直观。

    你搞不清楚需要哪些规范才能保证不发生 A merge B 然后 B merge A 导致代码没了这种问题
    mark2025
        10
    mark2025  
       18 天前
    @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;
    leonshaw
        11
    leonshaw  
       18 天前 via Android
    base 没有 line70 ,那严格来说 B 并没有基于 A 开发
    leonshaw
        12
    leonshaw  
       18 天前 via Android
    B merge master 没有问题,但是不能 merge 没有进 master 的 A
    LeeEnzo
        13
    LeeEnzo  
       18 天前
    强制 rebase 和 squash 开发
    freesun165
        14
    freesun165  
    OP
       18 天前 via Android
    @netabare 话虽如此,但我这个业务场景经常一个分支测一个月以上,一百多个提交,每次 master 更新,我挨个 rebase 下,成本太高了
    freesun165
        15
    freesun165  
    OP
       18 天前 via Android
    @leonshaw 我是直接在 featA 上切出去 featB
    freesun165
        16
    freesun165  
    OP
       18 天前 via Android
    @mark2025 俺这规范就是上线前 featB 需要合并 master 推到远端,远端 master 再合并 featB ,然后就出现了这么不符合知觉的事
    riceball
        17
    riceball  
       18 天前
    还是 Git 操作要有规范,这个彼此合并,左右互搏,啧啧,建议使用 Git flow 规范,有 git 插件支持: https://danielkummer.github.io/git-flow-cheatsheet/index.zh_CN.html
    rbaloatiw
        18
    rbaloatiw  
       18 天前
    按你的描述感觉不太可能, 你有最小可复现样例吗, 可以发出来看看
    coolcoffee
        19
    coolcoffee  
       18 天前
    git 的分支操作应该是像一颗树一样,开叉但是不会互相交叉。 如果平行的节点需要互相同步,那么应该一方往上推到相同的节点,另外一方再去拉,这样就遇到相同修改就必定会产生冲突。
    BeautifulSoap
        20
    BeautifulSoap  
       18 天前 via Android   ❤️ 2
    唔嗯?这情况你确定真没冲突吗?
    kivmi
        21
    kivmi  
       18 天前
    太危险了,master -> branch , 相当于你对同一行的修改无效啊,各种冲突吧?可以从 master 同时 fork 几个分支?一个为开发分支,一个为上线分支?当需要上线时,合并到上线分支,然后合并到 master ,一直保持上线分支跟 master 保持一致,实现快速上线。貌似 git flow hotfix 也可以做到。
    zthxxx
        22
    zthxxx  
       18 天前
    老生常谈话题之「不要把 master/dev 合到自己的分支」,开发时也始终应该像 GitHub / GitLab 那样的「把自己分支合到 master/dev (主干分支)」
    leonshaw
        23
    leonshaw  
       18 天前
    @freesun165 #15 这样 base 应该是切出去时的 commit ,不然就是后面这个 commit 被重写掉了,最好画个图看看。
    BeautifulSoap
        24
    BeautifulSoap  
       18 天前
    我实际在本地测试了一下

    最终结果是 master 合并入 feature b 后的确没报冲突,但被 feature b 删除的 line 也没再次出现

    可能 lz 实际给个最小可复现例子比较好
    nightwitch
        25
    nightwitch  
       18 天前
    git 只允许 fast-forward 就不容易出现这个问题。
    三路合并一定要小心,否则很容易出现冲掉别人的代码 / 别人的代码把自己的冲掉 / 两边的代码合并到了一起导致逻辑不对了。
    FrankAdler
        26
    FrankAdler  
       18 天前
    我也遇到过,目前没有头绪,我是 master 拉出来的分支,修改后合并到 dev 分支,cicd 到测试 k8s ,出现过几次代码没有提示冲突,但是丢了几行。
    jqtmviyu
        27
    jqtmviyu  
       18 天前
    ![]( )

    我也是没有复现,main 合并到 B 没有冲突, 此时 B 比 A 和 main 领先一次提交,合并没有变化,删除的行也不会回来。
    jqtmviyu
        28
    jqtmviyu  
       18 天前
    @jqtmviyu #27 我是设置了

    ```~/.gitconfig

    [merge]
    ff = false
    [pull]
    rebase = true
    ```
    acorpe
        29
    acorpe  
       18 天前
    @jqtmviyu 好早啊
    networm
        30
    networm  
       17 天前
    @freesun165 #14 使用 Fork 的 Leaning Branch 功能进行同步,只需要一键就可以自动同步。

    另外推荐看下:
    Git 精干分支 - 狂飙
    https://networm.me/2022/09/11/git-lean-branching/

    合并分支只会通过合并提交引入最终的修改,而不是合并分支中的原始提交。
    因为合并提交也是提交,但是大家潜意识都不会关注合并提交的改动,因此可能会在冲突解决中引入大量的错误的修改。
    如果一个东西容易引起错误,那么建议减少这个东西的使用,使用 Lean Branching 方案就可以。
    在与主干同步的时候,相当于检出到主干新建分支,将原有分支上的东西 cherry-pick 到新分支,因此需要逐个解决冲突。
    这个方案的核心是只在最后时合并提交,同时由于主干与功能分支的起点之间没有提交,合并提交引入的修改全部都是功能分支的改动。由于前面已经处理了冲突,这里逻辑上就不需要处理冲突,从根本上去除了冲突的处理。
    chenluo0429
        31
    chenluo0429  
       17 天前 via Android   ❤️ 2
    我猜 featA 合并到 master 时,经过了 sqlash 之类的操作,导致 master 上 featA 的修改与 featB 所基于的 featA 并不是同一笔提交,而是修改内容相同的两笔不同提交
    feelapi
        32
    feelapi  
       17 天前
    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
    wgbx
        33
    wgbx  
       17 天前
    Git 不是万能药,要遵守 git flow
    freesun165
        34
    freesun165  
    OP
       17 天前
    @rbaloatiw 发了,大佬可以看下
    freesun165
        35
    freesun165  
    OP
       17 天前
    @jqtmviyu 就是( main ) git merge A 时加上 squash 就可以复现了,因为当时是 gitlab 上勾选了 squash commit 的选项
    freesun165
        36
    freesun165  
    OP
       17 天前
    @chenluo0429 是的
    ryougifujino
        37
    ryougifujino  
       17 天前
    不用 squash 就不会有这个问题,squash 等于把历史线改了。还有功能基于功能分支进行切换是典型的“穷人的模块化架构”,一般采用 feature flags 类似的技术比较好。建议看一下 Martin Fowler 的这篇版本控制流的文章: https://v2ex.com/t/1072486
    zhuisui
        38
    zhuisui  
       17 天前   ❤️ 1
    你要想充分利用 git merge 的自动冲突解决能力,就不要做 squash 、fixup 、amend 、cherry-pick 这类会修改 commit 的操作,你必须在 merge 时原样保留各路 branch 。
    你只要记住,如果你要 merge branch ,一定要保留 branch 原来的样子。因为 git 就是利用 branch 和 merge commit 来决定冲突解决结果的。
    proxychains
        39
    proxychains  
       17 天前
    rebase 的好
    p1gd0g
        40
    p1gd0g  
       17 天前
    之前遇到几次丢代码,几个主流的 git 软件都看不到哪一次提交丢了,只有 tortoisegit 可以。
    但我们没有用 squash ,我也没搞清楚同事到底是怎么操作的。
    wangtian2020
        41
    wangtian2020  
       17 天前
    不使用 sourcetree 喜欢用命令行操作 git 导致的
    leonshaw
        42
    leonshaw  
       17 天前
    很明显你 squash 把 A/B 分支点删除了,导致和 main 的分叉在 local/A 以前,这一行的添加删除都发生在分叉以后,互相抵消了。
    因为 A 已经合并了,所以 B 合并前应该 git rebase --onto main local/A 把 A 排除掉。
    blublu
        43
    blublu  
       17 天前 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

    不知道我这样说你清楚了么?不清楚可以再多读几遍,应该解释了整个变更过程了
    mark2025
        44
    mark2025  
       15 天前
    @freesun165
    话虽如此,但我这个业务场景经常一个分支测一个月以上,一百多个提交,每次 master 更新,我挨个 rebase 下,成本太高了
    =======
    可以不用频繁 rebase ,而是功能分支要合并、发布之前集中一次 rebase 。
    mark2025
        45
    mark2025  
       15 天前
    @freesun165 俺这规范就是上线前 featB 需要合并 master 推到远端,远端 master 再合并 featB ,然后就出现了这么不符合知觉的事
    ======
    这个是你们架构师、技术总监等等没设计好流程规范的问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   952 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 105ms · UTC 22:00 · PVG 06:00 · LAX 14:00 · JFK 17:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.