Splitting a Changeset/Patch

I have a common problem with revision control:

  1. Clone a tree
  2. Start working on a problem X (say, adding valgrind annotations to MMgc) with edits to GC.cpp
  3. Get distracted and start working on problem Y (say, MMgc crashing) with edits to GC.cpp
  4. I now have unrelated changes in GC.cpp that I’d like to separate.

Dear lazyweb, is there a tool I can use to interactively separate out these changes into two changesets? The solution should work with my mercurial workflow, no “use git-rebase” comments please. I happen to be using patch queues, but a solution that worked on real mercurial changesets or on raw patches would also be acceptable. It happens that usually the patch hunks for problem X and problem Y are completely separate, so a simple tool that let me throw “this hunk for X, this hunk for Y” would probably be ok. Better would be something like a three-way merge tool where I can edit an intermediate state between a fixed start-point and end-point.

Atom Feed for Comments 13 Responses to “Splitting a Changeset/Patch”

  1. ignacio Says:

    Isn’t this what branches are for?

  2. Mark Says:

    I do this using CVS / Subversion:

    – clone a tree (a simple copy of the checkout)
    – intermediate tree: revert unnecessary stuff, cleanup, test, commit
    – final tree: update, merge conflicts, cleanup, continue work

    It may be slow on very large projects, but it works well.

  3. Colin Barrett Says:

    Use the record extension that comes with Mercurial. I don’t know how I lived without it. It does your first suggestion — allows you to pick and chose which hunks go into your commit. It’s based on the darcs command record. Has become one of those nice features that I miss intensely when I go back to Subversion for anything.

  4. Peter Hosey Says:

    You need to turn on the Record extension, which comes with Mercurial release versions. This adds the hg record command, which interactively walks you through the changes to each file, letting you pick and choose which ones you want to commit.

    After using this command at least once, you will loathe going to a VCS that doesn’t have it.

  5. Franck Says:

    Huum, for the “this hunk for X, this hunk for Y” part, you could simply use the record extension, which is bundled with Mercurial[1]. It will ask you for every hunk if you want to record it or not, then ask you for a commit message and commit the changes you’ve recorded as a real mercurial changeset. I don’t think there’s a built-in way that would invoke a three-way merge tool though.

    [1] http://www.selenic.com/mercurial/wiki/index.cgi/RecordExtension

  6. Vlad Says:

    When this happens, I usually go and manually edit the patch file with emacs — the patch mode lets you work on a hunk-basis (copying/deleting hunks, even modifying hunks and keeping the context info correct), and just create a new second patch file. They both usually apply with fuzz, but applying/refreshing fixes that. You may also want to look at the record extension at http://www.selenic.com/mercurial/wiki/index.cgi/RecordExtension .

    (Note that there’s a pile of extensions at http://www.selenic.com/mercurial/wiki/index.cgi/UsingExtensions that are reqally quite useful — we should probably evangelize them heavily. I’m thinking of bisect, fetch, hgk/graphlog, transplant, and localbranch mainly; inotify may well be useful for people working on linux)

  7. djc Says:

    You should look at the record extension that’s been in recent Mercurial versions. With it, you say hg record instead of hg commit, then it asks you per-file if you want to review it, include it, or leave it. If you choose to review it, you can select your changes per-hunk. When you’ve gone through all modified files, it commits just the selected hunks. There’s also a command called qrecord which should do something similar for mq, but I haven’t used it yet.

  8. Adam Fitzpatrick Says:

    git add –interactive allows you to selectively choose which hunks within a given file are to be committed. That doesn’t directly help you with Mercurial, obviously, but the implementation is a Perl script, and the relevant code (starting at sub patch_update_file in /usr/bin/git-add–interactive) looks like it could be extracted pretty cleanly if you replaced the git diff and git apply invocations with reading and writing a patch file (with the likely restriction that you would have to split your patches into one patch per modified file).

  9. Yo Says:

    ‘hg qrecord’

    hg help record —

    interactively select changes to commit

    If a list of files is omitted, all changes reported by “hg status”
    will be candidates for recording.

    You will be prompted for whether to record changes to each
    modified file, and for files with multiple changes, for each
    change to use. For each query, the following responses are

    y – record this change
    n – skip this change

    s – skip remaining changes to this file
    f – record remaining changes to this file

    d – done, skip remaining changes and files
    a – record all changes to all remaining files
    q – quit, recording no changes

    ? – display help

  10. Jim B Says:

    Whoa —

    Do I understand your procedure correctly? You make multiple edits to a file, do some testing presumably, and upon check in, you pick and choose which pieces are submitted?

    Where I work I’d be shot for doing that. It isn’t entirely that different from checking out a file, making edits, and check it back in without testing, not even compiling. How many times have you seen a check in break the build and have the offender offer as an excuse: but it was such a simple change, I couldn’t imagine how it could have broken anything.

    Breaking the build so casually can be endured for small teams, but it doesn’t scale when you have hundreds of people committing changes daily.

  11. Benjamin Smedberg Says:

    ignacio: if I was ultimately organized, I could just “hg qnew” when I discovered I needed to make changes for problem Y. Really though, I’m not that organized, so this is more a question of “how do I clean up a mess”.

    Jim B: everything here is happening in my local repository. I have not yet shared any changes with others. Once I get the changesets separate (this problem) I can reorder them so that the bugfix (problem Y) is first before the enhancement (problem X). Then I can get the bugfix reviewed and pushed upstream while I’m still working on the enhancement.

  12. Victor Bogado Says:

    Jim B: Also, complementing on what Benjamin had said before, if you separate the commits but send them all at once the tree will have a better documentation on what steps were taken to fix a problem (even if some in-between steps broken the compilation) but at the same time the tree it self were never available to other in a broken state. Got it?

  13. Adam Says:

    A very late comment! Take a look at the shelve extension (http://mercurial.selenic.com/wiki/ShelveExtension) It solves Victor Bogado’s problem as well.

Leave a Reply