美文网首页
git:重新认识Merging

git:重新认识Merging

作者: 涅槃快乐是金 | 来源:发表于2024-01-14 22:00 被阅读0次

    merger允许将两个(或多个)提交合并成一个单独的合并提交,同时将这两个提交中合并的所有不同文件合并在一起。如果存在合并冲突,需要逐个解决。其中一个提交是当前提交的远端的合并称为fast-forward merges,不会创建合并提交。当你从远程源pull时,实际上是在一步中进行了fetchmerge - 尽管通常情况下,你可以在进行fast-forward merges

    为了本文的目的,我将创建三个分支,分别称为“Alex”“Bob”“master”,以记录Alice和Bob。每个分支都将有一个名为Hello的文件,其中包含文本“Hello, my name is Alex”,以及一个AlexsFile、BobsFile等。

    (master) $ git ls-tree Alex
    100644 blob ada4c4c4f33cd190fe40769d5ca9826adb9fb7ce    AlexsFile
    100644 blob ca4eef2f4e3f1fe92028176cb547b590a08c2259    Hello
    (master) $ git ls-tree Bob
    100644 blob eea826732acee08a8cf83445e3b98cf58f11ce5c    BobsFile
    100644 blob ca4eef2f4e3f1fe92028176cb547b590a08c2259    Hello
    (master) $ git ls-tree master
    100644 blob ca4eef2f4e3f1fe92028176cb547b590a08c2259    Hello
    

    合并策略

    当你创建一个合并时,你可以选择说明如何处理合并。通常的策略是递归。这意味着 Git 将遍历每个目录(树)并找出哪些文件与基本修订版本相比有差异,然后使用有变化的文件。(如果两者都有变化,新文件的内容将被在文本上合并,如果这有问题,就会发生冲突。)这就是为什么在执行操作后会看到消息 “Merge made by recursive” 的原因:

    (master) $ git checkout Alex
    Switched to branch 'Alex'
    (Alex) $ git merge Bob
    Merge made by recursive.
     BobsFile |    1 +
     1 files changed, 1 insertions(+), 0 deletions(-)
     create mode 100644 BobsFile
    (Alex) $ git log --oneline --graph
    *   d612aab Merge branch 'Bob' into Alex
    |\
    | * 8afb2d3 Bob's File
    * | 4abf59e Alex's File
    |/
    * 5cba624 Hello
    (Alex) $ git show d612aa
    commit d612aab9858289ed027230d3b9a7b2a7a5e75945
    Merge: 4abf59e 8afb2d3
    …
        Merge branch 'Bob' into Alex
    

    提交 d612aa…是一个合并节点,因为它将两个不同的分支合并在一起。由于两者都不是对方的祖先,它们无法进行快进合并,因此创建了合并节点。我们可以使用HEAD^1HEAD^2来确定提交的父级:

    (Alex) $ git rev-parse HEAD^1
    4abf59ef73c186e93db25e8b7bc4423fbd11bbd0
    (Alex) $ git rev-parse HEAD^2
    8afb2d368ce26ca71cec539c31400c7001a18efc
    

    当没有需要合并的更改时,这甚至更容易:

    (Alex) $ git checkout master
    Switched to branch 'master'
    (master) $ git merge Bob
    Updating 5cba624..8afb2d3
    Fast-forward
     BobsFile |    1 +
     1 files changed, 1 insertions(+), 0 deletions(-)
     create mode 100644 BobsFile
    

    这里的fast-forward merges表示master落后于Bob,因此我们可以简单地将指针向前移动 - 结果就是,我们不需要创建一个合并节点。

    所以,Git 使用的两种策略是fast-forward merges或递归合并。这涵盖了你需要执行的 99% 的合并操作;但值得注意的是,Git 还有一些在特定情况下可以提供帮助的技巧。

    Octopus merge

    上面的示例都只有一个或两个父节点。然而,Git 合并节点能够表示多于两个父节点,并且它使用一种称为Octopus merge的策略。当你合并超过两个分支时,默认会选择这种策略:

    (master) $ git reset --hard 5cba624b94a7a622183c960697867c8bba73aa91
    HEAD is now at 5cba624 Hello
    (master) $ date > NewFile
    (master) $ git add NewFile
    (master) $ git commit -m "New File"
    [master 598ad85] New File
     1 files changed, 1 insertions(+), 0 deletions(-)
     create mode 100644 NewFile
    (master) $ git merge Alex Bob
    Trying simple merge with Alex
    Trying simple merge with Bob
    Merge made by octopus.
     AlexsFile |    1 +
     BobsFile  |    1 +
     2 files changed, 2 insertions(+), 0 deletions(-)
     create mode 100644 AlexsFile
     create mode 100644 BobsFile
    (master) $ git log --oneline --graph
    *-.   5a2aa0d Merge branches 'Alex' and 'Bob'
    |\ \
    | | * 8afb2d3 Bob's File
    | * | 4abf59e Alex's File
    | |/
    * | 598ad85 New File
    |/
    * 5cba624 Hello
    (master) $ git show
    commit 5a2aa0da3d3b3365703d710dad8aeebc0770b8ef
    Merge: 598ad85 4abf59e 8afb2d3
    …
        Merge branches 'Alex' and 'Bob'
    (master) $ git rev-parse HEAD^1 HEAD^2 HEAD^3
    598ad850114e1f7445ee8b02e93ee23060439560
    4abf59ef73c186e93db25e8b7bc4423fbd11bbd0
    8afb2d368ce26ca71cec539c31400c7001a18efc
    

    在这种情况下,我们有三个提交合并到一个合并节点中;master(已分叉的分支)、之前的 Alex 和 Bob 分支。由于我们的合并节点现在有三个父节点,我们可以使用 git rev-parseHEAD^1/2/3 转换为第一个、第二个和第三个父节点。

    如果文件存在冲突怎么办呢?
    (master) $ git checkout Alex
    Switched to branch 'Alex'
    (Alex) $ echo Hello, my name is Alex > Hello
    (Alex) $ git commit -a -m "My name is Alex"
    [Alex c2cb955] My name is Alex
     1 files changed, 1 insertions(+), 1 deletions(-)
    (Alex) $ git checkout Bob
    Switched to branch 'Bob'
    (Bob) $ echo Hello, my name is Bob > Hello
    (Bob) $ git commit -a -m "My name is Bob"
    [Bob 7cb6225] My name is Bob
     1 files changed, 1 insertions(+), 1 deletions(-)
    (Bob) $ git checkout master
    (master) $ git merge Alex Bob
    Trying simple merge with Alex
    Trying simple merge with Bob
    Simple merge did not work, trying automatic merge.
    Auto-merging Hello
    ERROR: content conflict in Hello
    fatal: merge program failed
    Automatic merge failed; fix conflicts and then commit the result.
    (master|MERGING) $ git ls-files --stage
    100644 ada4c4c4f33cd190fe40769d5ca9826adb9fb7ce 0   AlexsFile
    100644 eea826732acee08a8cf83445e3b98cf58f11ce5c 0   BobsFile
    100644 ca4eef2f4e3f1fe92028176cb547b590a08c2259 1   Hello
    100644 a5a820416bae2c7b77340e5b2120aab9595d2bfc 2   Hello
    100644 98b16693fe64acb9d002af1fe5f5162d58bd40b4 3   Hello
    100644 09d774502f97ba9a46f25f8f11601b653c376828 0   NewFile
    (master|MERGING) $
    

    我们可以使用 git mergetool启动一个three-way diff

    (master|MERGING) $ git mergetool
    (master|MERGING) $ git mergetool
    merge tool candidates: opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse ecmerge p4merge araxis bc3 emerge vimdiff
    Merging:
    Hello
    
    Normal merge conflict for 'Hello':
      {local}: modified file
      {remote}: modified file
    Hit return to start merge resolution tool (opendiff):
    …
    (master|MERGING) $ git commit -a -m "Merged"
    [master 999a938] Merged
    

    请注意,Octopus merge不能处理超过两个文件的冲突。如果有超过两个文件的冲突,你会得到一个不同的错误消息:

    (master) $ echo Hello World > Hello
    (master) $ git commit -a -m "Hello World"
    [master fe79e59] Hello World
     1 files changed, 1 insertions(+), 1 deletions(-)
    (master) $ git merge Alex Bob
    Trying simple merge with Alex
    Simple merge did not work, trying automatic merge.
    Auto-merging Hello
    ERROR: content conflict in Hello
    fatal: merge program failed
    Automated merge did not work.
    Should not be doing an Octopus.
    Merge with strategy octopus failed.
    

    ours

    ours strategy可以验证它们是相同的,因为由 HEAD 指向的树与由 HEAD^1(即父节点)指向的树相同。后缀^{tree}用于显示与提交相关联的树。

    --decorate 参数添加了分支名称到 git log 的输出中,这对于显示合并来自哪里是有用的。--graph 参数在大多数情况下与 --oneline 参数一起使用;虽然你可以运行 git --graph,但完整的提交消息往往会隐藏图的结构。

    ours 策略只在你想要编码一组先前的提交但不希望它们影响当前 master 分支时才真正有用(例如,因为你已经挑选了一些内容,不想采纳其他部分,但又想以某种方式保留它们的历史记录)。

    Merge Message and Fast Forwards

    Fast Forwards将根据你正在合并的分支的名称自动生成。然而,可以通过传递 -m 选项(与 git commit 一样)来提供额外的消息。如果合并消息需要编码附加信息(比如修复了哪个或哪些 bug),这可能会很有用。

    还可以强制执行合并,即使没有必要进行合并。如果你有基于主题的分支,将工作在一个单独的分支上进行再合并回 master 分支时可能会很有用。运行 git merge --no-ff将创建一个合并节点,无论是否可以快进合并。由于合并提交中包含了你正在合并的分支名称作为提交的一部分,因此你最终可以得到描述性的名称来显示已完成的功能:

    (master) $ git checkout -b "bug12345"
    Switched to a new branch 'bug12345'
    (bug12345) $ echo BugFix >> Hello
    (bug12345) $ git commit -a -m "Fixing bug 12345"
    [bug12345 e2bd64e] Fixing bug 12345
     1 files changed, 2 insertions(+), 0 deletions(-)
    (bug12345) $ git checkout master
    Switched to branch 'master'
    (master) $ git merge bug12345 # without --no-ff
    Updating 999a938..e2bd64e
    Fast-forward
     Hello |    2 ++
     1 files changed, 2 insertions(+), 0 deletions(-)
    (master) $ git reset --hard HEAD^
    HEAD is now at 999a938 Merged
    (master) $ git merge --no-ff bug12345 # with --no-ff
    Merge made by recursive.
     Hello |    2 ++
     1 files changed, 2 insertions(+), 0 deletions(-)
    (master) $ git log --oneline --graph --decorate
    *   4a905c8 (HEAD, master) Merge branch 'bug12345'
    |\
    | * e2bd64e (bug12345) Fixing bug 12345
    |/
    *-.   999a938 Merged
    

    在使用某些提交工作流时,能够使用--no-ff 进行合并可能是有用的,其中使用许多分支来开发单个功能,然后随后合并到主分支。还值得注意的是 --merges参数允许你仅在仓库中过滤合并提交:

    (master) apple[merge] $ git log --oneline --merges --decorate
    4a905c8 (HEAD, master) Merge branch 'bug12345'
    999a938 Merged
    

    后面我们将探讨使用 merge --no-ff进行不同 Git 工作流的各种操作。

    相关文章

      网友评论

          本文标题:git:重新认识Merging

          本文链接:https://www.haomeiwen.com/subject/cylqodtx.html