diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -49,6 +49,7 @@ pycompat, rcutil, registrar, + repair, revsetlang, rewriteutil, scmutil, @@ -2107,6 +2108,7 @@ [('r', 'rev', [], _('revisions to graft'), _('REV')), ('c', 'continue', False, _('resume interrupted graft')), ('', 'stop', False, _('stop interrupted graft')), + ('', 'abort', False, _('abort interrupted graft')), ('e', 'edit', False, _('invoke editor on commit messages')), ('', 'log', None, _('append graft info to log message')), ('f', 'force', False, _('force graft')), @@ -2204,11 +2206,24 @@ if opts.get('continue'): raise error.Abort(_("cannot use '--continue' and " "'--stop' together")) + if opts.get('abort'): + raise error.Abort(_("cannot use '--abort' and '--stop' together")) + if any((opts.get('edit'), opts.get('log'), opts.get('user'), opts.get('date'), opts.get('currentdate'), opts.get('currentuser'), opts.get('rev'))): raise error.Abort(_("cannot specify any other flag with '--stop'")) return _stopgraft(ui, repo, graftstate) + elif opts.get('abort'): + if opts.get('continue'): + raise error.Abort(_("cannot use '--continue' and " + "'--abort' together")) + if any((opts.get('edit'), opts.get('log'), opts.get('user'), + opts.get('date'), opts.get('currentdate'), + opts.get('currentuser'), opts.get('rev'))): + raise error.Abort(_("cannot specify any other flag with '--abort'")) + + return _abortgraft(ui, repo, graftstate) elif opts.get('continue'): cont = True if revs: @@ -2375,6 +2390,62 @@ return 0 +def _abortgraft(ui, repo, graftstate): + """abort the interrupted graft and rollbacks to the state before interrupted + graft""" + if not graftstate.exists(): + raise error.Abort(_("no interrupted graft to abort")) + statedata = _readgraftstate(repo, graftstate) + newnodes = statedata.get('newnodes') + if newnodes is None: + # and old graft state which does not have all the data required to abort + # the graft + raise error.Abort(_("cannot abort using an old graftstate")) + + # changeset from which graft operation was started + startctx = None + if len(newnodes) > 0: + startctx = repo[newnodes[0]].p1() + else: + startctx = repo['.'] + # whether to strip or not + cleanup = False + if newnodes: + newnodes = [repo[r].rev() for r in newnodes] + cleanup = True + # checking that none of the newnodes turned public or is public + immutable = [c for c in newnodes if not repo[c].mutable()] + if immutable: + repo.ui.warn(_("cannot clean up public changesets %s\n") + % ', '.join(bytes(repo[r]) for r in immutable), + hint=_("see 'hg help phases' for details")) + cleanup = False + + # checking that no new nodes are created on top of grafted revs + desc = set(repo.changelog.descendants(newnodes)) + if desc - set(newnodes): + repo.ui.warn(_("new changesets detected on destination " + "branch, can't strip\n")) + cleanup = False + + if cleanup: + with repo.wlock(), repo.lock(): + hg.updaterepo(repo, startctx.node(), True) + # stripping the new nodes created + strippoints = [c.node() for c in repo.set("roots(%ld)", + newnodes)] + repair.strip(repo.ui, repo, strippoints, backup=False) + + if not cleanup: + # we don't update to the startnode if we can't strip + startctx = repo['.'] + hg.updaterepo(repo, startctx.node(), True) + + ui.status(_("graft aborted\n")) + ui.status(_("working directory is now at %s\n") % startctx.hex()[:12]) + graftstate.delete() + return 0 + def _readgraftstate(repo, graftstate): """read the graft state file and return a dict of the data stored in it""" try: diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -312,7 +312,7 @@ debugwireargs: three, four, five, ssh, remotecmd, insecure debugwireproto: localssh, peer, noreadstderr, nologhandshake, ssh, remotecmd, insecure files: rev, print0, include, exclude, template, subrepos - graft: rev, continue, stop, edit, log, force, currentdate, currentuser, date, user, tool, dry-run + graft: rev, continue, stop, abort, edit, log, force, currentdate, currentuser, date, user, tool, dry-run grep: print0, all, diff, text, follow, ignore-case, files-with-matches, line-number, rev, allfiles, user, date, template, include, exclude heads: rev, topo, active, closed, style, template help: extension, command, keyword, system diff --git a/tests/test-graft.t b/tests/test-graft.t --- a/tests/test-graft.t +++ b/tests/test-graft.t @@ -1670,3 +1670,218 @@ | o 0:9092f1db7931 added a + $ cd .. + +Testing the --abort flag for `hg graft` which aborts and rollback to state +before the graft + + $ hg init abortgraft + $ cd abortgraft + $ for ch in a b c d; do echo $ch > $ch; hg add $ch; hg ci -Aqm "added "$ch; done; + + $ hg up '.^^' + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + + $ echo x > x + $ hg ci -Aqm "added x" + $ hg up '.^' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo foo > c + $ hg ci -Aqm "added foo to c" + + $ hg log -GT "{rev}:{node|short} {desc}" + @ 5:36b793615f78 added foo to c + | + | o 4:863a25e1a9ea added x + |/ + | o 3:9150fe93bec6 added d + | | + | o 2:155349b645be added c + |/ + o 1:5f6d8a4bf34a added b + | + o 0:9092f1db7931 added a + + $ hg up 9150fe93bec6 + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg graft --abort + abort: no interrupted graft to abort + [255] + +when stripping is required + $ hg graft -r 4 -r 5 + grafting 4:863a25e1a9ea "added x" + grafting 5:36b793615f78 "added foo to c" (tip) + merging c + warning: conflicts while merging c! (edit, then use 'hg resolve --mark') + abort: unresolved conflicts, can't continue + (use 'hg resolve' and 'hg graft --continue') + [255] + + $ hg graft --continue --abort + abort: cannot use '--continue' and '--abort' together + [255] + + $ hg graft --abort --stop + abort: cannot use '--abort' and '--stop' together + [255] + + $ hg graft --abort --currentuser + abort: cannot specify any other flag with '--abort' + [255] + + $ hg graft --abort --edit + abort: cannot specify any other flag with '--abort' + [255] + + $ hg graft --abort + graft aborted + working directory is now at 9150fe93bec6 + $ hg log -GT "{rev}:{node|short} {desc}" + o 5:36b793615f78 added foo to c + | + | o 4:863a25e1a9ea added x + |/ + | @ 3:9150fe93bec6 added d + | | + | o 2:155349b645be added c + |/ + o 1:5f6d8a4bf34a added b + | + o 0:9092f1db7931 added a + +when stripping is not required + $ hg graft -r 5 + grafting 5:36b793615f78 "added foo to c" (tip) + merging c + warning: conflicts while merging c! (edit, then use 'hg resolve --mark') + abort: unresolved conflicts, can't continue + (use 'hg resolve' and 'hg graft --continue') + [255] + + $ hg graft --abort + graft aborted + working directory is now at 9150fe93bec6 + $ hg log -GT "{rev}:{node|short} {desc}" + o 5:36b793615f78 added foo to c + | + | o 4:863a25e1a9ea added x + |/ + | @ 3:9150fe93bec6 added d + | | + | o 2:155349b645be added c + |/ + o 1:5f6d8a4bf34a added b + | + o 0:9092f1db7931 added a + +when some of the changesets became public + + $ hg graft -r 4 -r 5 + grafting 4:863a25e1a9ea "added x" + grafting 5:36b793615f78 "added foo to c" (tip) + merging c + warning: conflicts while merging c! (edit, then use 'hg resolve --mark') + abort: unresolved conflicts, can't continue + (use 'hg resolve' and 'hg graft --continue') + [255] + + $ hg log -GT "{rev}:{node|short} {desc}" + @ 6:6ec71c037d94 added x + | + | o 5:36b793615f78 added foo to c + | | + | | o 4:863a25e1a9ea added x + | |/ + o | 3:9150fe93bec6 added d + | | + o | 2:155349b645be added c + |/ + o 1:5f6d8a4bf34a added b + | + o 0:9092f1db7931 added a + + $ hg phase -r 6 --public + + $ hg graft --abort + cannot clean up public changesets 6ec71c037d94 + graft aborted + working directory is now at 6ec71c037d94 + +when we created new changesets on top of existing one + + $ hg up '.^^' + 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ echo y > y + $ hg ci -Aqm "added y" + $ echo z > z + $ hg ci -Aqm "added z" + + $ hg up 3 + 1 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ hg log -GT "{rev}:{node|short} {desc}" + o 8:637f9e9bbfd4 added z + | + o 7:123221671fd4 added y + | + | o 6:6ec71c037d94 added x + | | + | | o 5:36b793615f78 added foo to c + | | | + | | | o 4:863a25e1a9ea added x + | | |/ + | @ | 3:9150fe93bec6 added d + |/ / + o / 2:155349b645be added c + |/ + o 1:5f6d8a4bf34a added b + | + o 0:9092f1db7931 added a + + $ hg graft -r 8 -r 7 -r 5 + grafting 8:637f9e9bbfd4 "added z" (tip) + grafting 7:123221671fd4 "added y" + grafting 5:36b793615f78 "added foo to c" + merging c + warning: conflicts while merging c! (edit, then use 'hg resolve --mark') + abort: unresolved conflicts, can't continue + (use 'hg resolve' and 'hg graft --continue') + [255] + + $ cd .. + $ hg init pullrepo + $ cd pullrepo + $ cat >> .hg/hgrc < [phases] + > publish=False + > EOF + $ hg pull ../abortgraft --config phases.publish=False + pulling from ../abortgraft + requesting all changes + adding changesets + adding manifests + adding file changes + added 11 changesets with 9 changes to 8 files (+4 heads) + new changesets 9092f1db7931:6b98ff0062dd + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up 9 + 5 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo w > w + $ hg ci -Aqm "added w" --config phases.publish=False + + $ cd ../abortgraft + $ hg pull ../pullrepo + pulling from ../pullrepo + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + new changesets 311dfc6cf3bf + (run 'hg heads .' to see heads, 'hg merge' to merge) + + $ hg graft --abort + new changesets detected on destination branch, can't strip + graft aborted + working directory is now at 6b98ff0062dd