comparison mercurial/cmdutil.py @ 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
comparison
equal deleted inserted replaced
17471:ad1561723dde 17472:965fbe04fd96
1578 1578
1579 wlock = lock = None 1579 wlock = lock = None
1580 try: 1580 try:
1581 wlock = repo.wlock() 1581 wlock = repo.wlock()
1582 lock = repo.lock() 1582 lock = repo.lock()
1583 # First, do a regular commit to record all changes in the working 1583 tr = repo.transaction('amend')
1584 # directory (if there are any)
1585 ui.callhooks = False
1586 try: 1584 try:
1587 node = commit(ui, repo, commitfunc, pats, opts) 1585 # First, do a regular commit to record all changes in the working
1586 # directory (if there are any)
1587 ui.callhooks = False
1588 try:
1589 node = commit(ui, repo, commitfunc, pats, opts)
1590 finally:
1591 ui.callhooks = True
1592 ctx = repo[node]
1593
1594 # Participating changesets:
1595 #
1596 # node/ctx o - new (intermediate) commit that contains changes
1597 # | from working dir to go into amending commit
1598 # | (or a workingctx if there were no changes)
1599 # |
1600 # old o - changeset to amend
1601 # |
1602 # base o - parent of amending changeset
1603
1604 # Update extra dict from amended commit (e.g. to preserve graft
1605 # source)
1606 extra.update(old.extra())
1607
1608 # Also update it from the intermediate commit or from the wctx
1609 extra.update(ctx.extra())
1610
1611 files = set(old.files())
1612
1613 # Second, we use either the commit we just did, or if there were no
1614 # changes the parent of the working directory as the version of the
1615 # files in the final amend commit
1616 if node:
1617 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1618
1619 user = ctx.user()
1620 date = ctx.date()
1621 message = ctx.description()
1622 # Recompute copies (avoid recording a -> b -> a)
1623 copied = copies.pathcopies(base, ctx)
1624
1625 # Prune files which were reverted by the updates: if old
1626 # introduced file X and our intermediate commit, node,
1627 # renamed that file, then those two files are the same and
1628 # we can discard X from our list of files. Likewise if X
1629 # was deleted, it's no longer relevant
1630 files.update(ctx.files())
1631
1632 def samefile(f):
1633 if f in ctx.manifest():
1634 a = ctx.filectx(f)
1635 if f in base.manifest():
1636 b = base.filectx(f)
1637 return (not a.cmp(b)
1638 and a.flags() == b.flags())
1639 else:
1640 return False
1641 else:
1642 return f not in base.manifest()
1643 files = [f for f in files if not samefile(f)]
1644
1645 def filectxfn(repo, ctx_, path):
1646 try:
1647 fctx = ctx[path]
1648 flags = fctx.flags()
1649 mctx = context.memfilectx(fctx.path(), fctx.data(),
1650 islink='l' in flags,
1651 isexec='x' in flags,
1652 copied=copied.get(path))
1653 return mctx
1654 except KeyError:
1655 raise IOError
1656 else:
1657 ui.note(_('copying changeset %s to %s\n') % (old, base))
1658
1659 # Use version of files as in the old cset
1660 def filectxfn(repo, ctx_, path):
1661 try:
1662 return old.filectx(path)
1663 except KeyError:
1664 raise IOError
1665
1666 # See if we got a message from -m or -l, if not, open the editor
1667 # with the message of the changeset to amend
1668 user = opts.get('user') or old.user()
1669 date = opts.get('date') or old.date()
1670 message = logmessage(ui, opts)
1671 if not message:
1672 cctx = context.workingctx(repo, old.description(),
1673 user, date, extra,
1674 repo.status(base.node(),
1675 old.node()))
1676 message = commitforceeditor(repo, cctx, [])
1677
1678 new = context.memctx(repo,
1679 parents=[base.node(), nullid],
1680 text=message,
1681 files=files,
1682 filectxfn=filectxfn,
1683 user=user,
1684 date=date,
1685 extra=extra)
1686 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1687 try:
1688 repo.ui.setconfig('phases', 'new-commit', old.phase())
1689 newid = repo.commitctx(new)
1690 finally:
1691 repo.ui.setconfig('phases', 'new-commit', ph)
1692 if newid != old.node():
1693 # Reroute the working copy parent to the new changeset
1694 repo.setparents(newid, nullid)
1695
1696 # Move bookmarks from old parent to amend commit
1697 bms = repo.nodebookmarks(old.node())
1698 if bms:
1699 for bm in bms:
1700 repo._bookmarks[bm] = newid
1701 bookmarks.write(repo)
1702 #commit the whole amend process
1703 tr.close()
1588 finally: 1704 finally:
1589 ui.callhooks = True 1705 tr.release()
1590 ctx = repo[node] 1706 # Strip the intermediate commit (if there was one) and the amended
1591 1707 # commit
1592 # Participating changesets:
1593 #
1594 # node/ctx o - new (intermediate) commit that contains changes from
1595 # | working dir to go into amending commit (or a workingctx
1596 # | if there were no changes)
1597 # |
1598 # old o - changeset to amend
1599 # |
1600 # base o - parent of amending changeset
1601
1602 # Update extra dict from amended commit (e.g. to preserve graft source)
1603 extra.update(old.extra())
1604
1605 # Also update it from the intermediate commit or from the wctx
1606 extra.update(ctx.extra())
1607
1608 files = set(old.files())
1609
1610 # Second, we use either the commit we just did, or if there were no
1611 # changes the parent of the working directory as the version of the
1612 # files in the final amend commit
1613 if node:
1614 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
1615
1616 user = ctx.user()
1617 date = ctx.date()
1618 message = ctx.description()
1619 # Recompute copies (avoid recording a -> b -> a)
1620 copied = copies.pathcopies(base, ctx)
1621
1622 # Prune files which were reverted by the updates: if old introduced
1623 # file X and our intermediate commit, node, renamed that file, then
1624 # those two files are the same and we can discard X from our list
1625 # of files. Likewise if X was deleted, it's no longer relevant
1626 files.update(ctx.files())
1627
1628 def samefile(f):
1629 if f in ctx.manifest():
1630 a = ctx.filectx(f)
1631 if f in base.manifest():
1632 b = base.filectx(f)
1633 return (not a.cmp(b)
1634 and a.flags() == b.flags())
1635 else:
1636 return False
1637 else:
1638 return f not in base.manifest()
1639 files = [f for f in files if not samefile(f)]
1640
1641 def filectxfn(repo, ctx_, path):
1642 try:
1643 fctx = ctx[path]
1644 flags = fctx.flags()
1645 mctx = context.memfilectx(fctx.path(), fctx.data(),
1646 islink='l' in flags,
1647 isexec='x' in flags,
1648 copied=copied.get(path))
1649 return mctx
1650 except KeyError:
1651 raise IOError
1652 else:
1653 ui.note(_('copying changeset %s to %s\n') % (old, base))
1654
1655 # Use version of files as in the old cset
1656 def filectxfn(repo, ctx_, path):
1657 try:
1658 return old.filectx(path)
1659 except KeyError:
1660 raise IOError
1661
1662 # See if we got a message from -m or -l, if not, open the editor
1663 # with the message of the changeset to amend
1664 user = opts.get('user') or old.user()
1665 date = opts.get('date') or old.date()
1666 message = logmessage(ui, opts)
1667 if not message:
1668 cctx = context.workingctx(repo, old.description(), user, date,
1669 extra,
1670 repo.status(base.node(), old.node()))
1671 message = commitforceeditor(repo, cctx, [])
1672
1673 new = context.memctx(repo,
1674 parents=[base.node(), nullid],
1675 text=message,
1676 files=files,
1677 filectxfn=filectxfn,
1678 user=user,
1679 date=date,
1680 extra=extra)
1681 ph = repo.ui.config('phases', 'new-commit', phases.draft)
1682 try:
1683 repo.ui.setconfig('phases', 'new-commit', old.phase())
1684 newid = repo.commitctx(new)
1685 finally:
1686 repo.ui.setconfig('phases', 'new-commit', ph)
1687 if newid != old.node(): 1708 if newid != old.node():
1688 # Reroute the working copy parent to the new changeset
1689 repo.setparents(newid, nullid)
1690
1691 # Move bookmarks from old parent to amend commit
1692 bms = repo.nodebookmarks(old.node())
1693 if bms:
1694 for bm in bms:
1695 repo._bookmarks[bm] = newid
1696 bookmarks.write(repo)
1697
1698 # Strip the intermediate commit (if there was one) and the amended
1699 # commit
1700 if node: 1709 if node:
1701 ui.note(_('stripping intermediate changeset %s\n') % ctx) 1710 ui.note(_('stripping intermediate changeset %s\n') % ctx)
1702 ui.note(_('stripping amended changeset %s\n') % old) 1711 ui.note(_('stripping amended changeset %s\n') % old)
1703 repair.strip(ui, repo, old.node(), topic='amend-backup') 1712 repair.strip(ui, repo, old.node(), topic='amend-backup')
1704 finally: 1713 finally: