4

While trying to work out an integration between Kiln and Bamboo I ran into an issue that Fog Creek support indicated might be a problem of having too many topological heads in my repository. We typically have a pattern of closing a branch prior to merging back into a main branch but that clearly gets forgotten from time to time.

$ hg head -t | grep ^changeset: | wc -l
1361

My understanding is that those heads need to be communicated one way or the other when pulling from the hosting Mercurial server to know which heads need to by sync'd. That's a lot of wasted effort given that over 1100 of those heads are completely dead to us -- so I'd like to get rid of those heads!

It's fairly straightforward to identify the heads I want to eliminate:

$ hg log -r "heads(all()) - ( head() and not closed() )" --template "{node}\n" | wc -l
1199

I initially thought it would be fairly straightforward to clean this up:

  1. Create a new dummy branch
  2. Iteratively merge all of the closed topological heads into the dummy branch (keep local)

Unfortunately, I ran into a couple of issues:

  • If I started the dummy branch from my current default head, merges took a really long time ( I estimate it will take weeks to do all 1100+ merges )
  • If I start from changeset 0, I get quickly run into an ambiguous merge issue that I don't know how to resolve ("ambiguous merge - picked m action"?)

Any ideas or suggestions on how I should proceed? Is my iterative merge idea sane? Are one of the two ideas for where to start from more reasonable than the other?

Any help is greatly appreciated!

Chris Phillips
  • 11,607
  • 3
  • 34
  • 45

2 Answers2

2

The fastest way to do a no-op merge is via hg debugsetparents. For example:

hg update REV1
hg debugsetparents . REV2
hg commit -m "No-op merge"

Note that the . after debugsetparents is not a typo, but refers to the parent of the current checkout. Also, note that hg debugsetparents allows you to perform a pseudo-"merge" against a direct ancestor, which is not normally allowed by a merge. While this should not have any harmful effects for Mercurial itself, other tools that interact with the repository may be confused, so you should avoid misusing hg debugsetparents where one parent revision would be an ancestor of the other.

Second, note that there is usually little point in using --close-branch to close already merged branches. The closed flag is in practice only used by hg branches, and you can use hg branches --active and hg branches -a to elide branches that have no active heads (similarly, head() and heads(all()) and not closed() as a revset for all branch heads that aren't active or closed). Therefore, a simpler solution may be to get rid of all those topological heads that exist only to close branches.

Reimer Behrends
  • 8,600
  • 15
  • 19
  • Thanks @Reimer Behrends - could you clarify your first paragraph? Your suggestion makes sense to me but then you call out "you should avoid doing this". – Chris Phillips Sep 22 '15 at 15:51
  • The "avoid doing this" part is about using `debugsetparents` to do a merge with an ancestor. I'll rewrite to clarify this part. – Reimer Behrends Sep 22 '15 at 18:58
  • Ah, thanks - I don't think I will have that problem as I'm just merging a bunch of dangling heads into this dummy branch ( and by definition heads aren't ancestors of anything ). – Chris Phillips Sep 22 '15 at 20:51
  • 2
    Quick followup -- this approach worked out great for me. I closed the 1100+ bad heads in a minute or two and now my HTTP pull requests are much smaller. Thanks again for the help. – Chris Phillips Sep 24 '15 at 23:00
1

Some (possible unrelated) notes

  • Merged branches does NOT produce topological heads, thus - closing branches before merging it is just wasted time, I think most of 1361 heads are only neglected and abandoned branches (and this is "bad habits")
  • Piping to grep heads output isn't needed: hg heads is templateable command, you can (instead of grep) have -T "{node|short}\n" in heads command or something like it (string per changeset in output)

Solution

You can create temporary Merger-Branch (better - from current tip, see below) and perform dummy merges all unwanted|not needed heads into this Merger (dummy merges are fast and non-interactive, i.e. easy automated), and merge Merger back to tip (also maybe with dummy merge for sure, while it isn't needed at this step - Merger head will not be changed at all after all merges).

Merges topological heads into Merger can be (I suppose) automated - I can't have full working code for you, only hints:

  1. Three-liner from wiki can be used as shell-script with single parameter "REV to merge"

  2. In hg -y merge --tool=internal:fail REV REV is any identifier of merged revision (changeset-id, local revision number), which you can get from list of hg heads -t -T "TEMPLATE" and pipe it into xargs, which perform shell-script from above

Zoe
  • 27,060
  • 21
  • 118
  • 148
Lazy Badger
  • 94,711
  • 9
  • 78
  • 110
  • *closing branches before merging it is just wasted time* WHAT? Topological heads are "bad" and a practice of avoiding them then is "good", I won't call it waste of time... Maybe named branches are overhead and meta-info could be attached at first line of commit message (like issue code from BTS / PM tool). – gavenkoa Dec 26 '22 at 16:06