Development:Helpful Mercurial Commands

From Camino Wiki
Jump to navigation Jump to search

Mercurial is modern, powerful VCS; it also makes it very easy for you to shoot yourself in the foot. Here are some Hg commands to help you harness the former and prevent (or recover from) the latter.

For more information about any of these commands, use hg --help $cmdname.

This document assumes you have general familiarity with version control systems and with the basics of cloning, pulling, and diffing with Mercurial. For an introduction, see Hg Init by Joel Spolosky (of “Joel on Software” fame) and/or Mercurial: The Definitive Guide by Bryan O'Sullivan.

rename (mv)

To move or rename a file managed by Hg, simply hg mv $source $destination, and Hg will move the contents of the file to the new name/location, mark the old name/location for deletion, and mark the new name/location for addition.

Many Mercurial tutorials instead incorrectly recommend moving the file “outside” of hg and then using hg addremove to clean up; you should not use hg addremove, because it will corrupt blame for the file(s).

rollback

To roll back the last transaction (commit, import, pull, push to a local repository you control, unbundle), simply hg rollback.

Mercurial help warns that there is only one level of rollback, rollback is not undoable, and that rollback reverts all repository changes since the last transaction (but it does not affect your working copy). rollback cannot undo changes you’ve pushed to a remote repository, e.g. to hg.mozilla.org/camino.

For example, you’ve committed but realize you’re missing a file (or committed all local changes in your working directory by accident); hg rollback removes the commit (but does not undo the actual changes in your working copy), so you can commit again with the right set of files.

revert

If you’ve made a mistake with a file, perhaps accidentally deleting it or mangling it beyond all recognition, hg revert will get you out of the mess as long as you haven’t committed. Revert will restore your entire working copy (or specific files in your working copy) to the state of the local repository (i.e., to what is stored in the hidden .hg directory, not to the state of the remote repository). This restores the contents of files (saving the current state in .orig files) and undoes any adds, removes, copies, and renames.

For example, you accidentally deleted Camino.xcodeproj/project.pbxproj; hg revert Camino.xcodeproj/project.pbxproj will restore the project to the state it was in at the time of your last pull from hg.mozilla.org/camino.

In this second example, you attempted to apply a massive whitespace patch that touched 90% of the code and manually fix up conflicts, but the result failed to build. Here, hg revert --all will make your working copy a mirror of the repo state at the time of your last pull from hg.mozilla.org/camino. (Unfortunately, it also reverts local changes you had made to year.txt before trying to apply the patch; you can recover those local changes from year.txt.orig after running hg revert --all.

outgoing

hg outgoing will tell you what changesets (commits) you are about to push to the official repository with hg push.

rebase

Mercurial has a hg rebase command, similar to git’s command of the same name. This command helps you to avoid merging by recreating your changes on top of a newer source revision. Someone who understands rebase should write the rest of this paragraph.

You should be extremely careful using hg rebase with changes that include file moves or renames, as hg rebase has a history of transforming moves/renames into separate additions and removals, which corrupts blame.

[12:05am] bz: I fundamentally don't trust rebase

Applying a patch with renames, copies, or binary files

Sometimes you may wish to apply a patch that contains changes generated by the rename (mv) or copy commands (or if a patch contains binary files, which is strongly discouraged in Camino development, but which you may encounter if you are backporting a changeset from hgweb, for instance). The venerable patch command cannot handle moves or copies and may end up patching the wrong files.

To handle these types of patches (e.g., attachment 465542 on Bug 521137), you must instead use Mercurial’s import command to apply the patch. By default import commits the changes, which you may not want to do (particularly if you are only applying the patch for review or testing purposes), so you should add the --no-commit option. In addition, even with the --no-commit option, import will refuse to apply the patch if you have local changes elsewhere in your tree, so you may have to add the --force option as well.

Thus, assuming you have no conflicting local changes in files touched by the patch, hg import --no-commit --force /path/to/patch (note the lack of < redirect compared to patch) should successfully apply a patch that contains Mercurial file moves or copies (or changeset from hgweb that includes binary files) for testing purposes, without making a commit.

cross-commit substitutes

When landing across multiple branches (e.g., CVS trunk and CAMINO_2_0_BRANCH) in CVS, the cross-commit tool was (usually) your friend. Since branches in the traditional sense are painful in Mercurial (in particular, they bloat the size of the repository everyone has to pull and store), mozilla.org development process implements traditional branches as separate repositories (e.g., http://hg.mozilla.org/mozilla-central/ and http://hg.mozilla.org/releases/mozilla-1.9.2/), cross-commit isn't applicable any longer.

Fortunately, Mercurial has a couple of tools to ease cross-landings, and, while they have their shortcomings like cross-commit, they will fail before you push to the remote repository, making it possible, with a little effort, for you to notice failures before pushing and preventing you from landing only part of a patch on the second branch.

graft

In Mercurial 2.0 and newer, the built-in graft command is a similar but slightly more flexible version of transplant extension. It offers options to adjust the username and commit dates, as well as directly edit the commit message (instead of requiring a round-about filter invocation). It will not, however, work across repositories; it only works with branches.

Not yet tested in Camino development

transplant

Option 1 is the transplant extension, an off-by-default extension that ships with Mercurial. Enable the extension in your ~/.hgrc, and then, from the destination (second) repository, run hg transplant -s $sourceRepo $REV1 $REV2 $REV3 $REV4 (if you do have a Mercurial branch, e.g. a relbranch, make sure you’re on the destination branch and use -b $sourceBranch instead). Mercurial will attempt to commit each revision from the source repository into the “branch” repository, preserving the original commit message and metadata. In case of conflicts, Mercurial will ask you to merge and then continue after resolving the conflicts. Once conflicts are resolved (or if the transplant was successful to begin with), hg outgoing will show you the commits, and you can push. You should sanity-check the contents of each outgoing commit, and which commits are outgoing, before pushing.

You’ll want to use this method of “cross-landing” for a traditional cross-landing, i.e., landing a patch on two branches at exactly the same time during normal development.

hg transplant is not recommended for backporting patches to a stable “branch” because the commit will reflect the patches’ original landing dates and comments, not the current date and any approvals. (The --filter argument is supposed to allow modifying the commit message, but not interactively—only a single, pre-specified addition—and also will not change the commit date. However, this filter script will allow you to either append a specified standard approval message or interactively edit the commit message of each changeset.)

import

Option 2 is to use the hg import command. hg import -u $user -m $path-to-patch (where $path-to-patch is a patch file, a patch on a bug, or a changeset’s raw-rev URL in a remote repository) will import the patch and let you specify the patch author and checkin comment (e.g., an approval). If you have a raw-rev URL or a patch generated by hg export, you can omit the -u $user argument. To prevent committing (e.g., if a change on a “branch” needs changes in an additional file), add the --no-commit argument.

Drawbacks of this method include the fact it requires you to have git-style diffs enabled in your ~/.hgrc, it requires you to retype the commit message to add the approval, and that the handling of binary files and moves/renames have been flaky in various versions of Mercurial.

This method is better suited to backporting patches to a stable “branch” than hg transplant; hg import is also useful for traditional cross-landing (although hg transplant is perhaps slightly simpler and generates matching timestamps).

As with hg transplant, you should sanity-check the contents of each outgoing commit generated by import, and which commits are outgoing, before pushing.

Mercurial Queues

Mercurial Queues (mq for short) are apparently a powerful patchset management tool loved by Mozilla developers, provided by the (off-by-default) mq extension to Mercurial. Unfortunately, the documentation assumes you already understand them.

philor offers this explanation:

[3:47pm] philor: irc-length explanation of mq: "say |hg qnew foo|, make a bunch of 
                 changes, |hg qref|, |hg qpop|, |hg qnew bar| and you are working on the 
                 patch for bar in a clean untouched repo, with all of foo saved and ready 
                 to reapply"

[3:48pm] ardissone: so it juggles patches in and out?

[3:48pm] philor: right

[3:50pm] ardissone: it's rare that I do that normally, but I have been doing it a lot recently 

[3:50pm] philor: very handy for chickens, too, especially with the qimport extension

[3:51pm] philor: so that |hg qimport bz:12345| imports the patch from bug 12345, but you can 
                 then shuffle order and address review comments and change the commit message 
                 and change the user and whatnot at leisure

[3:51pm] thebot: philor: Bug https://bugzilla.mozilla.org/show_bug.cgi?id=12345 nor, P1, M11, 
                 jefft, VERI FIXED, [DOGFOOD] Unable to Forward a message received as an Inline 
                 page or an attachment

[3:57pm] philor: though reading https://developer.mozilla.org/en/Mercurial_Queues with the 
                 stale eyes of having used it for going on two years, wow, we really need a 
                 Gentle Introduction

[3:57pm] ardissone: yeah

[3:57pm] ardissone: The Moz hg docs suck rocks

[3:57pm] philor: but at least when you compare them to hg's docs, they start looking good

[3:58pm] ardissone: partly, apparently, because jorendorff explicitly wanted them to be inaccessible 

[3:58pm] ardissone: yeah 

[3:58pm] ardissone: i read some stuff from joelonsoftware and someone else

[4:00pm] ardissone: which were generally useful to helping me get acquainted to the differences 
                    between hg and cvs, but not so much for the mozilla-specific stuff (or mq)

Appendix