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

求助 git 自动 merge 丢代码

  •  
  •   freesun165 · 3 天前 · 3029 次点击

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

    复现在这:
    分支 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  
       3 天前
    git reflog 和 git fsck 两个结合看一下应该找到丢失信息
    gesse
        2
    gesse  
       3 天前   ❤️ 1
    featA 添加了 line70 ,并合并进了 master ,你在 featA 上 fork 出 featB ,featB 上删除了 line70 后,又把 master 合并进 featB ,这不就是在 featB 上添加了 line70 吗? 一点毛病没有。
    mark2025
        3
    mark2025  
       3 天前   ❤️ 1
    为什么要在 featB 上面执行 featB 分支上 git merge master ? 这就是混乱的根源
    1. 要么是在 featB 分支上 `git merge featA`
    2. 要么是在 featB 分支上 `git rebase master`
    netabare
        4
    netabare  
       3 天前 via Android
    所以不要把 master 合入分支,分支上面只用 rebase 。
    GeruzoniAnsasu
        5
    GeruzoniAnsasu  
       3 天前   ❤️ 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  
       3 天前
    @GeruzoniAnsasu 三路合并就是人多嘴杂,你不仔细查看变动就无法确定最终合并结果是否符合预期。所以基于 rebase 的线性合并在团队开发中是最高效的( gitlab 可以设定强制线性合并)
    mark2025
        7
    mark2025  
       3 天前
    @mark2025 并且线性合并的另外一个好处是 revert 时工作量低不容易出错。
    sagaxu
        8
    sagaxu  
       3 天前
    git merge 除非是 fast forward ,在处理多个分支修改同一文件时,不一定符合你的预期。

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

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

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

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

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

    ```~/.gitconfig

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

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

    合并分支只会通过合并提交引入最终的修改,而不是合并分支中的原始提交。
    因为合并提交也是提交,但是大家潜意识都不会关注合并提交的改动,因此可能会在冲突解决中引入大量的错误的修改。
    如果一个东西容易引起错误,那么建议减少这个东西的使用,使用 Lean Branching 方案就可以。
    在与主干同步的时候,相当于检出到主干新建分支,将原有分支上的东西 cherry-pick 到新分支,因此需要逐个解决冲突。
    这个方案的核心是只在最后时合并提交,同时由于主干与功能分支的起点之间没有提交,合并提交引入的修改全部都是功能分支的改动。由于前面已经处理了冲突,这里逻辑上就不需要处理冲突,从根本上去除了冲突的处理。
    chenluo0429
        31
    chenluo0429  
       2 天前 via Android   ❤️ 2
    我猜 featA 合并到 master 时,经过了 sqlash 之类的操作,导致 master 上 featA 的修改与 featB 所基于的 featA 并不是同一笔提交,而是修改内容相同的两笔不同提交
    feelapi
        32
    feelapi  
       2 天前
    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  
       2 天前
    Git 不是万能药,要遵守 git flow
    freesun165
        34
    freesun165  
    OP
       2 天前
    @rbaloatiw 发了,大佬可以看下
    freesun165
        35
    freesun165  
    OP
       2 天前
    @jqtmviyu 就是( main ) git merge A 时加上 squash 就可以复现了,因为当时是 gitlab 上勾选了 squash commit 的选项
    freesun165
        36
    freesun165  
    OP
       2 天前
    @chenluo0429 是的
    ryougifujino
        37
    ryougifujino  
       2 天前
    不用 squash 就不会有这个问题,squash 等于把历史线改了。还有功能基于功能分支进行切换是典型的“穷人的模块化架构”,一般采用 feature flags 类似的技术比较好。建议看一下 Martin Fowler 的这篇版本控制流的文章: https://v2ex.com/t/1072486
    zhuisui
        38
    zhuisui  
       2 天前   ❤️ 1
    你要想充分利用 git merge 的自动冲突解决能力,就不要做 squash 、fixup 、amend 、cherry-pick 这类会修改 commit 的操作,你必须在 merge 时原样保留各路 branch 。
    你只要记住,如果你要 merge branch ,一定要保留 branch 原来的样子。因为 git 就是利用 branch 和 merge commit 来决定冲突解决结果的。
    proxychains
        39
    proxychains  
       2 天前
    rebase 的好
    p1gd0g
        40
    p1gd0g  
       2 天前
    之前遇到几次丢代码,几个主流的 git 软件都看不到哪一次提交丢了,只有 tortoisegit 可以。
    但我们没有用 squash ,我也没搞清楚同事到底是怎么操作的。
    wangtian2020
        41
    wangtian2020  
       2 天前
    不使用 sourcetree 喜欢用命令行操作 git 导致的
    leonshaw
        42
    leonshaw  
       2 天前
    很明显你 squash 把 A/B 分支点删除了,导致和 main 的分叉在 local/A 以前,这一行的添加删除都发生在分叉以后,互相抵消了。
    因为 A 已经合并了,所以 B 合并前应该 git rebase --onto main local/A 把 A 排除掉。
    blublu
        43
    blublu  
       2 天前 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  
       8 小时 37 分钟前
    @freesun165
    话虽如此,但我这个业务场景经常一个分支测一个月以上,一百多个提交,每次 master 更新,我挨个 rebase 下,成本太高了
    =======
    可以不用频繁 rebase ,而是功能分支要合并、发布之前集中一次 rebase 。
    mark2025
        45
    mark2025  
       8 小时 36 分钟前
    @freesun165 俺这规范就是上线前 featB 需要合并 master 推到远端,远端 master 再合并 featB ,然后就出现了这么不符合知觉的事
    ======
    这个是你们架构师、技术总监等等没设计好流程规范的问题。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3113 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 13:39 · PVG 21:39 · LAX 05:39 · JFK 08:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.