gitpython-developers / GitPython

GitPython is a python library used to interact with Git repositories.
http://gitpython.readthedocs.org
BSD 3-Clause "New" or "Revised" License
4.64k stars 905 forks source link

GitPython does not support N-way diffs. #726

Open josecv opened 6 years ago

josecv commented 6 years ago

Imagine I want to merge a feature branch into master, when that feature branch contains changes that conflict with master, e.g.:

commit 87ef22e9631701222b8a461080484c1ee880c23c (HEAD -> master)
Merge: 895064f 54c3d06
Author: jose
Date:   Thu Feb 15 10:00:39 2018 -0500

    Merge branch 'feature2'

commit 895064f1ebcf08a6b031c23577e92ad16ee08abc
Author: jose
Date:   Thu Feb 15 09:59:47 2018 -0500

    Commit conflicting thing

diff --git a/example.txt b/example.txt
index 10d788f..7cd2517 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1 @@
-Initial content.
+This will conflict

commit 54c3d062efb24d8d591402f20d59f90183abb0ff (feature2)
Author: jose
Date:   Thu Feb 15 09:59:29 2018 -0500

    Commit my feature

diff --git a/example.txt b/example.txt
index 10d788f..23eb305 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1 @@
-Initial content.
+Feature feature feature 2.

commit 0c57867dbc6d27914eb03c3f3748d15d9e2565c8
Author: jose
Date:   Thu Feb 15 09:59:00 2018 -0500

    Initial commit

diff --git a/example.txt b/example.txt
new file mode 100644
index 0000000..10d788f
--- /dev/null
+++ b/example.txt
@@ -0,0 +1 @@
+Initial content.

In this case, in order to merge feature2 into master I had to resolve a conflict; so the merge commit has some content that is not in either of its parents:

commit 87ef22e9631701222b8a461080484c1ee880c23c (HEAD -> master)
Merge: 895064f 54c3d06
Author: jose
Date:   Thu Feb 15 10:00:39 2018 -0500

    Merge branch 'feature2'

diff --cc example.txt
index 7cd2517,23eb305..e64dc83
--- a/example.txt
+++ b/example.txt
@@@ -1,1 -1,1 +1,1 @@@
- This will conflict
 -Feature feature feature 2.
++Resolving conflict: feature feature feature 2.

In git, I can use a three way diff as git diff 87ef22e9631701222b8a461080484c1ee880c23c 87ef22e9631701222b8a461080484c1ee880c23c^1 87ef22e9631701222b8a461080484c1ee880c23c^2 to see only the changes that were made to resolve the merge conflict. (Incidentally this is equivalent to git diff-tree --cc 87ef22e9631701222b8a461080484c1ee880c23c, and it's what git show uses internally)

Note that this differs from doing two separate git diffs against the individual parents of the commit. If I git diff 87ef22e9631701222b8a461080484c1ee880c23c 87ef22e9631701222b8a461080484c1ee880c23c^1, it will show not only the content brought in by resolving the merge commit, but also the content that was introduced by 87ef22e9631701222b8a461080484c1ee880c23c^2 that did not conflict with the first parent.

Currently GitPython does not support this sort of three-way (or n-way) diffing, so it is actually impossible to cleanly examine changes introduced by resolving merge conflicts.

Byron commented 6 years ago

I admit that I don't know enough about it is really supposed to work, but may add here that GitPython has a few merge-related functions in the IndexFile and Tree types, which could be useful here.

josecv commented 6 years ago

I did try faking this functionality with IndexFile -- by essentially faking the merge and seeing what conflicts came out. It's capable of determining that there were merge conflicts, and where they were. But it unfortunately can't tell me what the precise fixes for the merge conflicts were. Also, unlike diffing, IndexFile requires locking the index, so it doesn't work with parallel applications.

Byron commented 6 years ago

Indeed, it will only merge on tree/blob level, never on the individual hunks. That is the part you would have to implement on your end. The default index file onrepo.index will create locks, indeed, but I vaguely remember that this happens only on the default index, and not for non-default index files. The idea is to work with temporary index files, as needed.