5

I'm attempting to cleanup a large number of topic branches, primarily so that the branch overview for master in github no longer displays spurious "n ahead" indicators in inactive topic branches due to the presence of identical changes.

Without these spurious indicators, this overview page would offer a great way to see at a glance if any commits from old topic branches were inadvertently missed and not merged back down into master.

In the diagram below, Y is a commit in branch topic that was later applied to master as Y' (so they have different sha1 hashes, but identical patch ids).

A --- B --- C --- Y' --- E    <-- master
       \ 
        X --- Y               <-- topic

git cherry master topic appropriately reports:

- Y

But if I try to clean this up by issuing a git merge topic from master, I get a merge conflict since change E in master has since altered the context against which the patch applied.

Is there a way to tell master "Hey, you really do have Y already, so you can stop reporting that you don't."? (Being able to do this in such a way that it could be applied automatically/programmatically is key.)

Araxia
  • 1,136
  • 11
  • 22
  • Not sure if it's relevant, but does X also exist in master? Or is it only in topic? – Tyler Jul 09 '11 at 01:37
  • Maybe you can amend your Y commit in the topic branch to your Y commit in master? Hmmm not sure... just thinking out loud. Seems like you need a way to say Y in topic is Y in master... – citizen conn Jul 09 '11 at 07:00
  • In most cases it isn't relevant whether `X` is in `master`. When it isn't, it is almost always a changelist that affects files orthogonal to `Y` and `Y'`. – Araxia Jul 11 '11 at 04:05
  • All of these commits are published, so I don't want to do anything that rewrites history. – Araxia Jul 11 '11 at 04:06

3 Answers3

1

You can see the effective difference in revisions between branches by passing the --cherry-pick option to git log.

My favourite incantation runs:

git log --left-right --graph --cherry-pick --oneline branch1...branch2

(which I have aliases as git lr).

From the man page:

--cherry-pick 

Omit any commit that introduces the same change as another commit on the "other side" when the set of commits are limited with symmetric difference.

For example, if you have two branches, A and B, a usual way to list all commits on only one side of them is with --left-right (see the example below in the description of the --left-right option). It however shows the commits that were cherry-picked from the other branch (for example, "3rd on b" may be cherry-picked from branch A). With this option, such pairs of commits are excluded from the output.

--cherry-mark 

Like --cherry-pick (see belowabove) but mark equivalent commits with = rather than omitting them, and inequivalent ones with +.


Permanent solution #1

In order to permanently make git stop worrying about commits that were actually cherry-picked, you can always merge the other branh. The merge commit will be marked as a child revision of both the previous commit and the tip of the merged revision tree.

This tells the revision tree traversal to stop walking the history at that point. Doing so would only be safe when merging a revision from the 'other' branch IFF you **know that all it's parents have been merged (as far as you would ever want them merged).

Permanent solution #2

There is also some way to use grafts. This really means nothing more than that you'll tell git - out of band1 - that a certain revision is a child of another revision, without having to actually rebase onto/merge from it.

1 as in, a handwritten file with pairs of sha1 hashes :)

The positive thing about this is that you don't have to rewrite history for this to work. However, if you want to, you can use git filter-branch to make the grafts permanent. You no longer need the grafts file then, but of course, you'll be back with the disadvantages of having to rewrite the history (and possibly invalidating published revision ids).

Loose ends?

If all else fails, sometimes you can be stuck with remote (topic) branches that you frequently want to merge from, but there are differences that you simply never want to take. These would probably result in the same merge conflicts over and over.

In that case I'll just point at git-rerere (Reuse recorded resolution of conflicted merges) which can make life considerably easier, albeit more complicated

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Unless I'm misunderstanding you, solution #1 doesn't work because it is already an aspect of my problem. The merge isn't clean. As for solution #2, I've never run across _grafts_ before. I'll look into that. I know about cherry-pick, and I can craft a commandline to give me a report with only the unmerged changelists without equivalents in `master`. The problem is that the github overview page that my peers will be using won't use my custom commandline, so I need to actually eliminate the spurious indicators. – Araxia Jul 11 '11 at 04:11
  • To rid spurious indicators, `rebase` is the way to go. You'll still have to resolve the conflicts manually, but hey... we all learn those things the hard way :) – sehe Jul 11 '11 at 09:45
  • This command did not show any cherry-picks, I ended up using `git cherry`: https://git-scm.com/docs/git-cherry – electronix384128 Mar 21 '17 at 21:02
0

This is sort of an odd sidestep around not wanting to rebase history since this is a public repo, but consider a solution with revert, merge, and a counter revert.

On master, revert E and Y' separately to restore master to its state at C (with commit messages saying that you are bringing in a forgotten topic branch, not removing changes):

git revert E
git revert Y'

Note down the sha1 of the revert commit for E, or git tag revert_E sha1 it.

Now you will be able to merge your topic onto master:

git merge topic

With topic properly merged at this point, and master returned to its state up to Y/Y', you can now revert your revert commit of E to restore it:

git revert revert_E

which will apply cleanly since master is in the same state it was when you originally committed E (the history to that state has just changed). Feels a bit acrobatic, but would solve your problem. I can't think of anything cleaner.

shelhamer
  • 29,752
  • 2
  • 30
  • 33
  • This is an interesting idea. Unfortunately, in practice there are usually many more commits than `E` between `Y'` and `HEAD`, so reverting `n` commits and then reapplying them is impractical and the resulting history would likely be confusing to other developers on the project. – Araxia Jul 11 '11 at 04:10
0

I'm not sure why I didn't think of this before, but as long as an identical changelist is the only difference between master and topic, git merge -s ours topic from master will easily solve the problem. The merge will obviously apply without conflict, and the presence of the merge will eliminate the spurious unmerged indicator.

My github branch overview page is now free of misleading "n ahead" indicators, so genuine unmerged commits will stick out clearly.

Araxia
  • 1,136
  • 11
  • 22
  • `git merge -s recursive -Xours topic` may be a better merge command for this in the general case—if there are additional commits to merge as well—since it favors `master` but doesn't entirely discard `topic` . – Araxia Jul 11 '11 at 19:09