32

I am aware that you can close a Mercurial branch with:

hg update rev-number
hg commit --close-branch -m "Closing branch."

However, some of the repositories I work with a rather large, and after discovering a loose branch from years ago that I want to close, updating to it first can take many minutes (if not hours), only to do one commit and then update back to the original revision I was working from (more minutes, if not hours).

So my question, is there any way to close a Mercurial branch without updating the working directory to the branch revision first?

nobody
  • 19,814
  • 17
  • 56
  • 77
gbmhunter
  • 1,747
  • 3
  • 23
  • 24

3 Answers3

47

Yes you can, but this is not documented anywhere. I've used this technique for a long time, don't worry, it is safe.

Instead of updating, you can issue the following commands

hg debugsetparent <revision>
hg branch <branchOfRevision>

Note that the order is important. This will make your repo think it is on the new revision, while all your files are from the initial one. After that, you can use the --close-branch commit, but use the -X * option to make an empty commit.

hg commit --close-branch -X * -m "Closing branch."

Now, simply get back to your previous head, like nothing happened.

hg debugsetparent <InitialRevision>
hg branch <branchOfInitialRevision>

Finally, if you have sub-repos, you might want to temporarily rename the .hgsub file before committing the --close-branch, and rename back afterward.

Vince
  • 3,497
  • 2
  • 19
  • 15
  • 9
    Clever! As a Mercurial dev, this technique looks correct to me. – Martin Geisler Apr 17 '14 at 06:50
  • @MartinGeisler Thanks! Of course, this is used in a script, and if it was to fail at any step, it would revert back to the initial state automatically. Let me ask you, do you think a `debugrebuildstate` should be issued before finishing? – Vince Apr 17 '14 at 10:43
  • It doesn't look like it would be necessary. `debugrebuilddirstate` clears the dirstate which forces Mercurial to compare the files on disk with its internal representation. Since you update back to where you started, the dirstate will be correct. – Martin Geisler Apr 18 '14 at 09:31
  • @Vince, thanks! This turned out be more complicated than I thought (I was hoping for a one-liner, or an appended option to an existing well-used Mercurial command), so all the more thanks for figuring it out...this will definitely save time. – gbmhunter Apr 22 '14 at 02:39
  • @JJJ Did you ever resolve the issue? "It works here" - and not exactly sure how it would bork a server to 400. – user2864740 Jun 02 '16 at 05:07
  • 1
    `-X *` is doesn't handle subrepositories sanely. I combined this with [this method of creating an empty commit](http://stackoverflow.com/questions/18541660/perform-an-empty-commit-with-mercurial) in a script to automate the process. – user2864740 Jun 02 '16 at 05:08
  • @user2864740 It's a while ago now, but as I recall it was something about the headers of the HTTP request being too big, so IIS rejected it. I'm sorry for not posting my resolution at the time. – JJJ Jun 03 '16 at 15:27
  • @JJJ Oh, yeah, *that* issue (but it's not directly related afaik) - we switched to SMB awhile back for pulls (not sure if it still applies) :} – user2864740 Jun 04 '16 at 02:34
  • Is it possible to add this action to a context menu for a branch in Hg Tortoise? – Erik Sep 18 '18 at 13:35
  • 1
    Since Mercurial 4.8, this work-around is not needed anymore. See answer https://stackoverflow.com/a/54281046/151299 – Oben Sonne Jan 20 '19 at 21:15
19

Mercurial 4.8 and upwards ships with the core extension closehead which provides this functionality:

hg close-head <revision>

From the help:

This is equivalent to checking out each revision in a clean tree and running "hg commit --close-branch", except that it doesn't change the working directory.

At the moment you need to enable this extension explicitly in your hgrc:

[extensions]
closehead =
sth
  • 222,467
  • 53
  • 283
  • 367
Oben Sonne
  • 9,893
  • 2
  • 40
  • 61
  • 1
    Great! This looks easier than the accepted answer, this should probably be the way to do it if you have Mercurial 4.8+. – gbmhunter Jan 21 '19 at 02:31
1

Thanks to Vince for the approach. I've implemented this as a Python script - it was a little more work than I initially expected, so hopefully this will save others some time. Tested on Windows 8.1 with TortoiseHg 3.3 and Python 2.7.9.

Feedback welcome, it could probably do with some finesse, particularly with regards to error-handling.

#!/usr/bin/python

from subprocess import check_output

def close_branch( branch, message ):
    if not message:
        message = 'Closing branch "{}"'.format( branch )
    print( 'Closing branch "{}"'.format( branch ) )
    try:
        check_output( 'hg debugsetparent ' + branch )
        check_output( 'hg branch ' + branch )
        check_output( 'hg commit --close-branch -X * -m "' + message + '"' )
    except:
        print( 'Failed to close branch.' )

def main():
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('branch', help='branch name(s)', nargs = '+' )
    parser.add_argument('-m', '--message', help='message (defaults to "Closing branch <branch name>")' )
    args = parser.parse_args()

    status = check_output( 'hg status' )
    if len(status) > 0:
        print( 'Do not use this script with local changes. Commit/revert changes and try again' )
        exit(1)

    # Cache initial revision and branch.
    initial_revision = check_output( 'hg id -i -b' ).split()
    # print( 'Using: ' + initial_revision[0].decode("utf-8") )

    for branch in args.branch:
        close_branch( branch, args.message )

    # Return to original changeset
    check_output( 'hg debugsetparent ' + initial_revision[0].decode("utf-8") )
    check_output( 'hg branch ' + initial_revision[1].decode("utf-8") )

if __name__ == '__main__':
    main()
Brian Carr
  • 63
  • 1
  • 2
Kim
  • 728
  • 10
  • 20