comparison mercurial/localrepo.py @ 26631:e077ce385609

localrepo: restore dirstate to one before rollbacking if not parent-gone 'localrepository.rollback()' explicilty restores dirstate, only if at least one of current parents of the working directory is removed at rollbacking (a.k.a "parent-gone"). After DirstateTransactionPlan, 'dirstate.write()' will cause marking '.hg/dirstate' as a file to be restored at rollbacking. https://mercurial.selenic.com/wiki/DirstateTransactionPlan Then, 'transaction.rollback()' restores '.hg/dirstate' regardless of parents of the working directory at that time, and this causes unexpected dirstate changes if not "parent-gone" (e.g. "hg update" to another branch after "hg commit" or so, then "hg rollback"). To avoid such situation, this patch restores dirstate to one before rollbacking if not "parent-gone". before: b1. restore dirstate explicitly, if "parent-gone" after: a1. save dirstate before actual rollbacking via dirstateguard a2. restore dirstate via 'transaction.rollback()' a3. if "parent-gone" - discard backup (a1) - restore dirstate from 'undo.dirstate' a4. otherwise, restore dirstate from backup (a1) Even though restoring dirstate at (a3) after (a2) seems redundant, this patch keeps this existing code path, because: - it isn't ensured that 'dirstate.write()' was invoked at least once while transaction running If not, '.hg/dirstate' isn't restored at (a2). In addition to it, rude 3rd party extension invoking 'dirstate.write()' without 'repo' while transaction running (see subsequent patches for detail) may break consistency of a file backup-ed by transaction. - this patch mainly focuses on changes for DirstateTransactionPlan Restoring dirstate at (a3) itself should be cheaper enough than rollbacking itself. Redundancy will be removed in next step. Newly added test is almost meaningless at this point. It will be used to detect regression while implementing delayed dirstate write out.
author FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
date Tue, 13 Oct 2015 12:25:43 -0700
parents 56b2bcea2529
children 09bb1ee7e73e
comparison
equal deleted inserted replaced
26630:3111b45a2bbf 26631:e077ce385609
9 import urllib 9 import urllib
10 import peer, changegroup, subrepo, pushkey, obsolete, repoview 10 import peer, changegroup, subrepo, pushkey, obsolete, repoview
11 import changelog, dirstate, filelog, manifest, context, bookmarks, phases 11 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
12 import lock as lockmod 12 import lock as lockmod
13 import transaction, store, encoding, exchange, bundle2 13 import transaction, store, encoding, exchange, bundle2
14 import scmutil, util, extensions, hook, error, revset 14 import scmutil, util, extensions, hook, error, revset, cmdutil
15 import match as matchmod 15 import match as matchmod
16 import merge as mergemod 16 import merge as mergemod
17 import tags as tagsmod 17 import tags as tagsmod
18 from lock import release 18 from lock import release
19 import weakref, errno, os, time, inspect, random 19 import weakref, errno, os, time, inspect, random
1082 return False 1082 return False
1083 finally: 1083 finally:
1084 lock.release() 1084 lock.release()
1085 1085
1086 def rollback(self, dryrun=False, force=False): 1086 def rollback(self, dryrun=False, force=False):
1087 wlock = lock = None 1087 wlock = lock = dsguard = None
1088 try: 1088 try:
1089 wlock = self.wlock() 1089 wlock = self.wlock()
1090 lock = self.lock() 1090 lock = self.lock()
1091 if self.svfs.exists("undo"): 1091 if self.svfs.exists("undo"):
1092 return self._rollback(dryrun, force) 1092 dsguard = cmdutil.dirstateguard(self, 'rollback')
1093
1094 return self._rollback(dryrun, force, dsguard)
1093 else: 1095 else:
1094 self.ui.warn(_("no rollback information available\n")) 1096 self.ui.warn(_("no rollback information available\n"))
1095 return 1 1097 return 1
1096 finally: 1098 finally:
1097 release(lock, wlock) 1099 release(dsguard, lock, wlock)
1098 1100
1099 @unfilteredmethod # Until we get smarter cache management 1101 @unfilteredmethod # Until we get smarter cache management
1100 def _rollback(self, dryrun, force): 1102 def _rollback(self, dryrun, force, dsguard):
1101 ui = self.ui 1103 ui = self.ui
1102 try: 1104 try:
1103 args = self.vfs.read('undo.desc').splitlines() 1105 args = self.vfs.read('undo.desc').splitlines()
1104 (oldlen, desc, detail) = (int(args[0]), args[1], None) 1106 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1105 if len(args) >= 3: 1107 if len(args) >= 3:
1138 self.invalidate() 1140 self.invalidate()
1139 1141
1140 parentgone = (parents[0] not in self.changelog.nodemap or 1142 parentgone = (parents[0] not in self.changelog.nodemap or
1141 parents[1] not in self.changelog.nodemap) 1143 parents[1] not in self.changelog.nodemap)
1142 if parentgone: 1144 if parentgone:
1145 # prevent dirstateguard from overwriting already restored one
1146 dsguard.close()
1147
1143 self.vfs.rename('undo.dirstate', 'dirstate') 1148 self.vfs.rename('undo.dirstate', 'dirstate')
1144 try: 1149 try:
1145 branch = self.vfs.read('undo.branch') 1150 branch = self.vfs.read('undo.branch')
1146 self.dirstate.setbranch(encoding.tolocal(branch)) 1151 self.dirstate.setbranch(encoding.tolocal(branch))
1147 except IOError: 1152 except IOError: