Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing differences across a rebase in Git

Suppose that I just rebased the branch foo on master, with conflicts. I want to make sure that I did not accidentally damage the content of foo during conflict resolution by introducing extra changes or losing changes (other than that which is appropriate for the conflict resolution). I have done this via:

diff -u <(git diff `git merge-base master foo@{1}` foo@{1}) \         <(git diff `git merge-base master foo    ` foo    ) 

(update: or the equivalent ... syntax for git-diff which I have just been reminded of:)

diff -u <(git diff master...foo@{1}) <(git diff master...foo) | mate 

This shows me all the changes that have occurred to master..foo considered as a patch, which is exactly what I want to check for being minimal. However, the invocation is complex and the output is not entirely straightforward to interpret.

Is there a better way to accomplish this task — to provide the same information, but with a better method or format — or should I just take the above and wrap it up in a script?

like image 441
Kevin Reid Avatar asked Sep 17 '12 18:09

Kevin Reid


People also ask

What is difference between rebase merge?

Merging is a safe option that preserves the entire history of your repository, while rebasing creates a linear history by moving your feature branch onto the tip of main .

Why is rebase better than merge?

Rebasing is better to streamline a complex history, you are able to change the commit history by interactive rebase. You can remove undesired commits, squash two or more commits into one or edit the commit message. Rebase will present conflicts one commit at a time whereas merge will present them all at once.

What are the key differences between Git merge and Git rebase when we shall use Git rebase instead?

Git rebase and merge both integrate changes from one branch into another. Where they differ is how it's done. Git rebase moves a feature branch into a master. Git merge adds a new commit, preserving the history.


2 Answers

What we really want to show is the conflict combined diff that is generated as if we had done a merge to get from (a) to (b) where (a) was previously based on (upstream-old) and (b) is now based on (upstream-new).

We don't want just a straight diff.

Instead, we can essentially do the merge but force the resulting tree to be $b^{tree} which we already know is the correct "end" point of what we want.

More or less, let's assume that we have

newcommit -> the new version of the series oldcommit -> the old version of the series upstream -> the (new) version of the base of the series 

We can generate the merge via

git commit-tree newcommit^{tree} -p oldcommit -p upstream -m "message" 

and then show the result with "git show" and this will generate a combined diff format that shows all the necessary bits as a conflict resolution which automatically ignores changes that weren't actually part of resolving conflicts.

This even works for simply ammending a change as well, and you can do a bit more work to ensure that the generated merge commit has exact author and commit timestamps so that it's consistent over multiple calls (since we are storing a loose ref into the object database).

Unfortunately I have not been able to figure out how to just get "git diff" to diff the same way without actually generating a tree yet. I'm not sure what arguments we'd have to pass to get to that point.

like image 99
Jake Avatar answered Oct 03 '22 22:10

Jake


Even better than interdiff, now with Git 2.19 (Q3 2018) you have git range-diff.
See "Git diff - two disjoint revision ranges"

The git range-diff documentation includes the following example:

When a rebase required merge conflicts to be resolved, compare the changes introduced by the rebase directly afterwards using:

$ git range-diff @{u} @{1} @ 

A typical output of git range-diff would look like this:

------------ -:  ------- > 1:  0ddba11 Prepare for the inevitable! 1:  c0debee = 2:  cab005e Add a helpful message at the start 2:  f00dbal ! 3:  decafe1 Describe a bug     @@ -1,3 +1,3 @@      Author: A U Thor <[email protected]>      -TODO: Describe a bug     +Describe a bug     @@ -324,5 +324,6       This is expected.      -+What is unexpected is that it will also crash.     ++Unexpectedly, it also crashes. This is a bug, and the jury is     ++still out there how to fix it best. See ticket #314 for details.      Contact 3:  bedead < -:  ------- TO-UNDO ------------ 

In this example, there are 3 old and 3 new commits, where the developer:

  • removed the 3rd,
  • added a new one before the first two, and
  • modified the commit message of the 2nd commit as well its diff.

When the output goes to a terminal, it is color-coded by default, just like regular git diff's output. In addition, the first line (adding a commit) is green, the last line (deleting a commit) is red, the second line (with a perfect match) is yellow like the commit header of git show's output, and the third line colors the old commit red, the new one green and the rest like git show's commit header.


With Git 2.20, colors are better supported for the new kind of (range) diff

See commit 2543a64, commit 8d5ccb5, commit 7648b79 (17 Aug 2018), and commit 4441067, commit f103a6f, commit 29ef759, commit 017ac45, commit 9d1e16b, commit 84120cc, commit c5e64ca, commit 991eb4f (14 Aug 2018) by Stefan Beller (stefanbeller).
(Merged by Junio C Hamano -- gitster -- in commit 30035d1, 17 Sep 2018)

range-diff: indent special lines as context

The range-diff coloring is a bit fuzzy when it comes to special lines of a diff, such as indicating new and old files with +++ and ---, as it would pickup the first character and interpret it for its coloring, which seems annoying as in regular diffs, these lines are colored bold via DIFF_METAINFO.

By indenting these lines by a white space, they will be treated as context which is much more useful, an example on the range diff series itself:

git range-diff pr-1/dscho/branch-diff-v3...pr-1/dscho/branch-diff-v4 

(from repository github.com/gitgitgadget/git)

[...]     + diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt     + new file mode 100644     + --- /dev/null     + +++ b/Documentation/git-range-diff.txt     +@@     ++git-range-diff(1) [...]     +       diff --git a/Makefile b/Makefile       --- a/Makefile       +++ b/Makefile [...] 

The first lines that introduce the new file for the man page will have the '+' sign colored and the rest of the line will be bold.

The later lines that indicate a change to the Makefile will be treated as context both in the outer and inner diff, such that those lines stay regular color.

like image 33
VonC Avatar answered Oct 03 '22 22:10

VonC