如何在不合并的情况下在Git中执行三向差异? [英] How to perform a three-way diff in Git without merging?

查看:118
本文介绍了如何在不合并的情况下在Git中执行三向差异?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在两个具有共同合并基础的git分支之间执行三向差异,并使用kdiff3查看它。



我发现了很多指导SO(以及一些非常类似的问题( 1 2 3 )),但我还没有找到直接的答案。值得注意的是,对这个答案的评论意味着我想要的是可能的,但它对我并不适用。 )希望这个用户可以在这里敲响:)

对于背景,当我执行合并时,我使用了diff3冲突风格:



git config --global merge.conflictstyle diff3



我有 git mergetool 配置为使用kdiff3。



解决合并冲突时,显示四个文件:


  1. 当前分支文件($ LOCAL)

  2. 另一个分支的文件($ REMOTE)

  3. 合并的输出文件($ MERGED)


这是两个分支共同祖先的文件($ BASE)

然而, git difftool 只会拉起两个分支提示。我也想看到基本文件。 为了清楚起见,我希望能够在合并之前执行此差异,包括没有合并冲突的文件。 ( git mergetool 只会在出现冲突时显示三向差异)。




部分解决方案1:

使用单个文件,我可以导出三个版本并手动调用diff:

  git show local_branch:filename> localfile 
git show remote_branch:filename> remotefile
git show`git merge-base local_branch remote_branch`:filename> basefile

{kdiff3_path} /kdiff3.exe --L1Base--L2Local--L3Remote-ooutputfilebasefile localfile remotefile&

有两个问题:


  1. 我希望它能够用于整个项目,而不仅仅是特定的文件。 这很丑陋!我可以编写脚本,但我希望使用标准的git过程有更简洁的方式。






< 部分解决方案2:

感谢这个答案评论为灵感。

创建一个总是返回false的自定义合并驱动程序,该驱动程序会创建冲突的合并状态而不实际进行任何自动合并。然后使用 git mergetool 执行diff。然后在完成时中止合并。


  1. 添加到 .git / config $ b $ $ p $ [mergeassert_conflict_states]
    name = assert_conflict_states
    driver = false $创建(或附加到)


  2. / code>导致所有合并使用新的驱动程序:

      * merge = assert_conflict_states 

    code>


  3. 执行合并,现在不会进行任何自动合并。


  4. 做差异。在我的情况下: git mergetool 这会引起kdiff3三向合并。 完成后,终止合并: git merge --abort


  5. 撤消步骤#2。


这个工作除了kdiff3被调用时执行automerge以外,所以我仍然不能查看预先合并的差异。不过,我可以通过删除来更改Git的库存kdiff3驱动程序文件( ... / git-core / mergetools / kdiff3 ) - $ b 即使如此,这也有以下显示停止问题:


  1. 这仅适用于两个文件都已更改的情况!在只有一个文件发生更改的情况下,更新的文件会替换较旧的文件,并且永远不会调用合并。 li>
  2. 我必须修改Git kdiff3驱动程序,它根本不可移植。

  3. 我必须修改 attributes
  4. code>在做差异化之前和之后。
  5. 当然,我希望在不合并的情况下做到这一点:)






赏金信息

给出的答案,这是标准的Git不可能的。所以现在我正在寻找一个更加开箱即用的解决方案:我如何调整Git来实现这个目标?



这里有一个领导:显然,如果这三个文件中只有一个文件发生了变化,这个新文件在合并结果中被使用,而没有实际调用合并驱动程序。这意味着我的定制冲突创建合并驱动程序在这种情况下永远不会被调用。如果是,那么我的部分解决方案#2实际上会起作用。

通过调整文件或配置可以更改此行为吗?或者也许有办法创建一个自定义diff驱动程序?我还没准备好开始玩Git源代码......



任何聪明的想法?

解决方案


我希望能够在之前执行此差异,包括没有合并冲突的文件。


你只需要设置索引就可以了,你永远不必提交结果。

  git merge  - 设置准确地查询所要求的内容的方式, s我们的--no-ff --no-commit $ your_other_tip 

一个完整的手册,其中只有git为最终决定承诺的任何事情设立父母作为结果,但最好是在正常合并的情况下完成这项工作,同时仍然可以进入并检查所有事情,

  git merge --no-ff --no-commit $ your_other_tip 

选择您的出发点,然后


  1. 为所有显示
    提示:

     #!/ bin / sh 

    git checkout -m。

    #标识显示任一提示中的更改但自动添加的路径
    scratch =`mktemp -t`
    sort<< EOD | uniq -u>$ scratch
    $(#显示修改的路径:
    (git diff --name-only ... MERGE_HEAD
    git diff --name-只有MERGE_HEAD ...
    | sort -u)
    $(#已经显示冲突的路径:
    git ls-files -u | cut -f2-)
    EOD

    #un-automerge他们:剥离已解决的内容条目并明确
    #添加基本/我们的/他们的内容条目
    git update-index --force-remove - -stdin<$ scratch
    stage_paths_from(){
    xargs -a$ 1-d \\\\
    git ls-tree -r $ 2 |
    seds / [^] * //; s / \ t / $ 3 \t /|
    git update-index --index-info

    stage_paths_from$ scratch$(git merge-base @ MERGE_HEAD)1
    stage_paths_from$ scratch@ 2
    stage_paths_from$ scratchMERGE_HEAD 3


  2. ...如果您使用的是vimdiff,第2步将只是 git mergetool 。 vimdiff从工作树中的内容开始,并且不会执行自己的automerge。它看起来像kdiff3想忽略工作树。

    太麻烦了:

      Anyhoo设置为不运行--auto也不会显示 #一次性设置:
    wip =〜/ libexec / my-git-mergetools
    mkdir -p$ wip
    cp -a$(git --exec-path)/ mergetools / kdiff3$ wip
    sed -si's / - auto // g'$ wip/ kdiff3

    然后你可以

      MERGE_TOOLS_DIR =〜/ libexec / my-git-mergetools git mergetool 




  3. code> git merge --abort git reset --hard


    I want to perform a three-way diff between two git branches with a common merge base, and view it with kdiff3.

    I've found lots of guidance on SO (and a few very similar questions (1, 2, 3) ) but I haven't found a direct answer. Notably, a comment on this answer implies that what I want is possible, but it didn't work for me. Hopefully that user might chime in here :)

    For background, when I perform merges I use a "diff3" conflict style:

    git config --global merge.conflictstyle diff3

    And I have git mergetool configured to use kdiff3.

    When resolving merge conflicts this shows me four files:

    1. The current branch's file ($LOCAL)
    2. The other branch's file ($REMOTE)
    3. The file which is the common ancestor of the two branches ($BASE)
    4. The merged output file ($MERGED)

    However, git difftool only will pull up the two branch tips. I want to see the base file, too. To be clear, I want to be able to perform this diff before merging, including on files without merge conflicts. (git mergetool only shows the three-way diffs if there are conflicts).


    Partial Solution #1:

    With an individual file, I can export the three versions and manually call the diff:

    git show local_branch:filename > localfile
    git show remote_branch:filename > remotefile
    git show `git merge-base local_branch remote_branch`:filename > basefile
    
    {kdiff3_path}/kdiff3.exe --L1 "Base" --L2 "Local" --L3 "Remote" -o "outputfile" basefile localfile remotefile &
    

    There are two problems with this:

    1. I want it to work for the whole project, not just a specific file.
    2. This is ugly! I can script it, but I hope there's a much cleaner way using standard git processes.


    Partial Solution #2:

    Thanks to this answer and comment for the inspiration.

    Create a custom merge driver that always returns "false", which creates a conflicted merge state without actually doing any auto-merging. Then perform the diff using git mergetool. Then abort the merge when you're finished.

    1. Add to .git/config:

      [merge "assert_conflict_states"]
          name = assert_conflict_states
          driver = false
      

    2. Create (or append to) .git/info/attributes to cause all merges to use the new driver:

      * merge=assert_conflict_states
      

    3. Perform the merge, which now doesn't do any automerging.

    4. Do the diff. In my case: git mergetool which brings up the kdiff3 three-way merge.

    5. When done, abort the merge: git merge --abort.

    6. Undo step #2.

    This would (sorta) work except that kdiff3 performs an automerge when called, so I still can't see the pre-merged diffs. I can fix this, though, by changing Git's stock kdiff3 driver file (.../git-core/mergetools/kdiff3 by removing the --auto switch.

    Even so, this has the following show-stopping problems:

    1. This only works when both files have changed! In the case where only one file changed, the updated file replaces the older file and the merge is never called.
    2. I have to modify the Git kdiff3 driver, which isn't portable at all.
    3. I have to modify attributes before and after doing the diff.
    4. And, of course, I was hoping to do this without merging :)


    Information for the bounty:

    According to answers given, this isn't possible with standard Git. So now I'm looking for a more out-of-the-box solution: How can I tweak Git to make this happen?

    Here's one lead: Apparently, if only one of the three files has changed, this newer file is used in the merge result without actually calling the merge driver. This means that my custom "conflict-creating" merge driver is never called in this case. If it was, then my "Partial Solution #2" would actually function.

    Could this behavior be changed by tweaking files or configurations? Or perhaps there's a way to create a custom diff driver? I'm not ready to start playing with the Git source code...

    Any clever ideas?

    解决方案

    I want to be able to perform this diff before merging, including on files without merge conflicts.

    You just have to set up the index as you like, you never have to commit results. The way to set up for exactly what was asked, straight diffs-since-base with no merge prep, is

    git merge -s ours --no-ff --no-commit $your_other_tip
    

    a complete handroll in which git only sets up parents for whatever you eventually decide to commit as the a result, but it's probably better to do this with a normal merge while still being able to get in there and examine everything,

    git merge --no-ff --no-commit $your_other_tip
    

    Pick your starting point, and then

    1. force a merge visit for all entries that show any changes in either tip:

      #!/bin/sh
      
      git checkout -m .
      
      # identify paths that show changes in either tip but were automerged
      scratch=`mktemp -t`
      sort <<EOD | uniq -u >"$scratch"
      $(  # paths that show changes at either tip:
          (   git diff --name-only  ...MERGE_HEAD
              git diff --name-only  MERGE_HEAD...
          ) | sort -u )
      $(  # paths that already show a conflict:
          git ls-files -u | cut -f2- )
      EOD
      
      # un-automerge them: strip the resolved-content entry and explicitly 
      # add the base/ours/theirs content entries
      git update-index --force-remove --stdin <"$scratch"
      stage_paths_from () {
              xargs -a "$1" -d\\n git ls-tree -r $2 |
              sed "s/ [^ ]*//;s/\t/ $3\t/" |
              git update-index --index-info
      }
      stage_paths_from "$scratch" $(git merge-base @ MERGE_HEAD) 1 
      stage_paths_from "$scratch" @ 2
      stage_paths_from "$scratch" MERGE_HEAD 3
      

    2. ... if you were using vimdiff, step 2 would be just git mergetool. vimdiff starts from what's in the worktree and doesn't do its own automerge. It looks like kdiff3 wants to ignore the worktree. Anyhoo, setting it up to run without --auto doesn't look too too hacky:

      # one-time setup:
      wip=~/libexec/my-git-mergetools
      mkdir -p "$wip"
      cp -a "$(git --exec-path)/mergetools/kdiff3" "$wip"
      sed -si 's/--auto //g' "$wip"/kdiff3
      

      and then you can

      MERGE_TOOLS_DIR=~/libexec/my-git-mergetools git mergetool
      

    Backout from this is just the usual git merge --abort or git reset --hard.

    这篇关于如何在不合并的情况下在Git中执行三向差异?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆