changeset 17472:965fbe04fd96

amend: wrap all commit operations in a single transaction This allows proper recovery of an interrupted amend process. No changes are made to the logic besides: - indent operations into a single try-except clause, - some comment and code wrapping to 80 chars, - strip logic should not be contained in the transaction and is extracted from the main code.
author Pierre-Yves David <pierre-yves.david@ens-lyon.org>
date Mon, 10 Sep 2012 23:44:24 +0200
parents ad1561723dde
children 9732473aa24b
files mercurial/cmdutil.py
diffstat 1 files changed, 110 insertions(+), 101 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/cmdutil.py	Sat Aug 25 15:37:28 2012 +0200
+++ b/mercurial/cmdutil.py	Mon Sep 10 23:44:24 2012 +0200
@@ -1580,123 +1580,132 @@
     try:
         wlock = repo.wlock()
         lock = repo.lock()
-        # First, do a regular commit to record all changes in the working
-        # directory (if there are any)
-        ui.callhooks = False
+        tr = repo.transaction('amend')
         try:
-            node = commit(ui, repo, commitfunc, pats, opts)
-        finally:
-            ui.callhooks = True
-        ctx = repo[node]
+            # First, do a regular commit to record all changes in the working
+            # directory (if there are any)
+            ui.callhooks = False
+            try:
+                node = commit(ui, repo, commitfunc, pats, opts)
+            finally:
+                ui.callhooks = True
+            ctx = repo[node]
 
-        # Participating changesets:
-        #
-        # node/ctx o - new (intermediate) commit that contains changes from
-        #          |   working dir to go into amending commit (or a workingctx
-        #          |   if there were no changes)
-        #          |
-        # old      o - changeset to amend
-        #          |
-        # base     o - parent of amending changeset
+            # Participating changesets:
+            #
+            # node/ctx o - new (intermediate) commit that contains changes
+            #          |   from working dir to go into amending commit
+            #          |   (or a workingctx if there were no changes)
+            #          |
+            # old      o - changeset to amend
+            #          |
+            # base     o - parent of amending changeset
 
-        # Update extra dict from amended commit (e.g. to preserve graft source)
-        extra.update(old.extra())
+            # Update extra dict from amended commit (e.g. to preserve graft
+            # source)
+            extra.update(old.extra())
 
-        # Also update it from the intermediate commit or from the wctx
-        extra.update(ctx.extra())
+            # Also update it from the intermediate commit or from the wctx
+            extra.update(ctx.extra())
 
-        files = set(old.files())
+            files = set(old.files())
 
-        # Second, we use either the commit we just did, or if there were no
-        # changes the parent of the working directory as the version of the
-        # files in the final amend commit
-        if node:
-            ui.note(_('copying changeset %s to %s\n') % (ctx, base))
+            # Second, we use either the commit we just did, or if there were no
+            # changes the parent of the working directory as the version of the
+            # files in the final amend commit
+            if node:
+                ui.note(_('copying changeset %s to %s\n') % (ctx, base))
 
-            user = ctx.user()
-            date = ctx.date()
-            message = ctx.description()
-            # Recompute copies (avoid recording a -> b -> a)
-            copied = copies.pathcopies(base, ctx)
+                user = ctx.user()
+                date = ctx.date()
+                message = ctx.description()
+                # Recompute copies (avoid recording a -> b -> a)
+                copied = copies.pathcopies(base, ctx)
 
-            # Prune files which were reverted by the updates: if old introduced
-            # file X and our intermediate commit, node, renamed that file, then
-            # those two files are the same and we can discard X from our list
-            # of files. Likewise if X was deleted, it's no longer relevant
-            files.update(ctx.files())
+                # Prune files which were reverted by the updates: if old
+                # introduced file X and our intermediate commit, node,
+                # renamed that file, then those two files are the same and
+                # we can discard X from our list of files. Likewise if X
+                # was deleted, it's no longer relevant
+                files.update(ctx.files())
 
-            def samefile(f):
-                if f in ctx.manifest():
-                    a = ctx.filectx(f)
-                    if f in base.manifest():
-                        b = base.filectx(f)
-                        return (not a.cmp(b)
-                                and a.flags() == b.flags())
+                def samefile(f):
+                    if f in ctx.manifest():
+                        a = ctx.filectx(f)
+                        if f in base.manifest():
+                            b = base.filectx(f)
+                            return (not a.cmp(b)
+                                    and a.flags() == b.flags())
+                        else:
+                            return False
                     else:
-                        return False
-                else:
-                    return f not in base.manifest()
-            files = [f for f in files if not samefile(f)]
+                        return f not in base.manifest()
+                files = [f for f in files if not samefile(f)]
 
-            def filectxfn(repo, ctx_, path):
-                try:
-                    fctx = ctx[path]
-                    flags = fctx.flags()
-                    mctx = context.memfilectx(fctx.path(), fctx.data(),
-                                              islink='l' in flags,
-                                              isexec='x' in flags,
-                                              copied=copied.get(path))
-                    return mctx
-                except KeyError:
-                    raise IOError
-        else:
-            ui.note(_('copying changeset %s to %s\n') % (old, base))
+                def filectxfn(repo, ctx_, path):
+                    try:
+                        fctx = ctx[path]
+                        flags = fctx.flags()
+                        mctx = context.memfilectx(fctx.path(), fctx.data(),
+                                                  islink='l' in flags,
+                                                  isexec='x' in flags,
+                                                  copied=copied.get(path))
+                        return mctx
+                    except KeyError:
+                        raise IOError
+            else:
+                ui.note(_('copying changeset %s to %s\n') % (old, base))
 
-            # Use version of files as in the old cset
-            def filectxfn(repo, ctx_, path):
-                try:
-                    return old.filectx(path)
-                except KeyError:
-                    raise IOError
+                # Use version of files as in the old cset
+                def filectxfn(repo, ctx_, path):
+                    try:
+                        return old.filectx(path)
+                    except KeyError:
+                        raise IOError
 
-            # See if we got a message from -m or -l, if not, open the editor
-            # with the message of the changeset to amend
-            user = opts.get('user') or old.user()
-            date = opts.get('date') or old.date()
-            message = logmessage(ui, opts)
-            if not message:
-                cctx = context.workingctx(repo, old.description(), user, date,
-                                          extra,
-                                          repo.status(base.node(), old.node()))
-                message = commitforceeditor(repo, cctx, [])
+                # See if we got a message from -m or -l, if not, open the editor
+                # with the message of the changeset to amend
+                user = opts.get('user') or old.user()
+                date = opts.get('date') or old.date()
+                message = logmessage(ui, opts)
+                if not message:
+                    cctx = context.workingctx(repo, old.description(),
+                                              user, date, extra,
+                                              repo.status(base.node(),
+                                              old.node()))
+                    message = commitforceeditor(repo, cctx, [])
 
-        new = context.memctx(repo,
-                             parents=[base.node(), nullid],
-                             text=message,
-                             files=files,
-                             filectxfn=filectxfn,
-                             user=user,
-                             date=date,
-                             extra=extra)
-        ph = repo.ui.config('phases', 'new-commit', phases.draft)
-        try:
-            repo.ui.setconfig('phases', 'new-commit', old.phase())
-            newid = repo.commitctx(new)
+            new = context.memctx(repo,
+                                 parents=[base.node(), nullid],
+                                 text=message,
+                                 files=files,
+                                 filectxfn=filectxfn,
+                                 user=user,
+                                 date=date,
+                                 extra=extra)
+            ph = repo.ui.config('phases', 'new-commit', phases.draft)
+            try:
+                repo.ui.setconfig('phases', 'new-commit', old.phase())
+                newid = repo.commitctx(new)
+            finally:
+                repo.ui.setconfig('phases', 'new-commit', ph)
+            if newid != old.node():
+                # Reroute the working copy parent to the new changeset
+                repo.setparents(newid, nullid)
+
+                # Move bookmarks from old parent to amend commit
+                bms = repo.nodebookmarks(old.node())
+                if bms:
+                    for bm in bms:
+                        repo._bookmarks[bm] = newid
+                    bookmarks.write(repo)
+            #commit the whole amend process
+            tr.close()
         finally:
-            repo.ui.setconfig('phases', 'new-commit', ph)
+            tr.release()
+        # Strip the intermediate commit (if there was one) and the amended
+        # commit
         if newid != old.node():
-            # Reroute the working copy parent to the new changeset
-            repo.setparents(newid, nullid)
-
-            # Move bookmarks from old parent to amend commit
-            bms = repo.nodebookmarks(old.node())
-            if bms:
-                for bm in bms:
-                    repo._bookmarks[bm] = newid
-                bookmarks.write(repo)
-
-            # Strip the intermediate commit (if there was one) and the amended
-            # commit
             if node:
                 ui.note(_('stripping intermediate changeset %s\n') % ctx)
             ui.note(_('stripping amended changeset %s\n') % old)