From patchwork Sat Oct 18 11:44:55 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [3,of,5] transaction: use "location" when doing backup From: Pierre-Yves David X-Patchwork-Id: 6402 Message-Id: To: mercurial-devel@selenic.com Cc: Pierre-Yves David Date: Sat, 18 Oct 2014 04:44:55 -0700 # HG changeset patch # User Pierre-Yves David # Date 1413605075 25200 # Fri Oct 17 21:04:35 2014 -0700 # Node ID f533386d7f84a8e5ad1885d654d5983d81302b79 # Parent 580364627689487b0c13185a28d925f676fa0526 transaction: use "location" when doing backup Same logic here, we stop passing vfs object as arguments and use the location identifier to backup the right file. We persist this in the transaction state file. As this is a format change (3 values per entry instead of 2) we change the file name to "backupfiles2". Note that backup of empty file is handled by the "entries" list, not "backupentries" and will improve that in the next changeset. Some more compatibility layer between "backupfiles" and "backupfiles2" may come in the futur. diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -915,11 +915,12 @@ class localrepository(object): def recover(self): lock = self.lock() try: if self.svfs.exists("journal"): self.ui.status(_("rolling back interrupted transaction\n")) - transaction.rollback(self.sopener, "journal", + vfsmap = {'store': self.sopener, 'plain': self.opener} + transaction.rollback(self.sopener, vfsmap, "journal", self.ui.warn) self.invalidate() return True else: self.ui.warn(_("no interrupted transaction available\n")) @@ -971,11 +972,12 @@ class localrepository(object): if dryrun: return 0 parents = self.dirstate.parents() self.destroying() - transaction.rollback(self.sopener, 'undo', ui.warn) + vfsmap = {'store': self.sopener, 'plain': self.opener} + transaction.rollback(self.sopener, vfsmap, 'undo', ui.warn) if self.vfs.exists('undo.bookmarks'): self.vfs.rename('undo.bookmarks', 'bookmarks') if self.svfs.exists('undo.phaseroots'): self.svfs.rename('undo.phaseroots', 'phaseroots') self.invalidate() diff --git a/mercurial/transaction.py b/mercurial/transaction.py --- a/mercurial/transaction.py +++ b/mercurial/transaction.py @@ -21,11 +21,12 @@ def active(func): raise error.Abort(_( 'cannot use transaction when it is already committed/aborted')) return func(self, *args, **kwds) return _active -def _playback(journal, report, opener, entries, backupentries, unlink=True): +def _playback(journal, report, opener, vfsmap, entries, backupentries,\ + unlink=True): for f, o, _ignore in entries: if o or not unlink: try: fp = opener(f, 'a') fp.truncate(o) @@ -39,22 +40,23 @@ def _playback(journal, report, opener, e except (IOError, OSError), inst: if inst.errno != errno.ENOENT: raise backupfiles = [] - for f, b, _ignore in backupentries: - filepath = opener.join(f) - backuppath = opener.join(b) + for l, f, b, _ignore in backupentries: + vfs = vfsmap[l] + filepath = vfs.join(f) + backuppath = vfs.join(b) try: util.copyfile(backuppath, filepath) backupfiles.append(b) except IOError: report(_("failed to recover %s\n") % f) raise opener.unlink(journal) - backuppath = "%s.backupfiles" % journal + backuppath = "%s.backupfiles2" % journal if opener.exists(backuppath): opener.unlink(backuppath) for f in backupfiles: opener.unlink(f) @@ -90,11 +92,11 @@ class transaction(object): self.journal = journal self._queue = [] # a dict of arguments to be passed to hooks self.hookargs = {} - self.backupjournal = "%s.backupfiles" % journal + self.backupjournal = "%s.backupfiles2" % journal self.file = opener.open(self.journal, "w") self.backupsfile = opener.open(self.backupjournal, 'w') if createmode is not None: opener.chmod(self.journal, createmode & 0666) opener.chmod(self.backupjournal, createmode & 0666) @@ -119,18 +121,18 @@ class transaction(object): offsets = [] backups = [] for f, o, _data in q[0]: offsets.append((f, o)) - for f, b, _data in q[1]: - backups.append((f, b)) + for l, f, b, _data in q[1]: + backups.append((l, f, b)) d = ''.join(['%s\0%d\n' % (f, o) for f, o in offsets]) self.file.write(d) self.file.flush() - d = ''.join(['%s\0%s\0' % (f, b) for f, b in backups]) + d = ''.join(['%s\0%s\0%s\0' % entry for entry in backups]) self.backupsfile.write(d) self.backupsfile.flush() @active def add(self, file, offset, data=None): @@ -145,11 +147,11 @@ class transaction(object): # add enough data to the journal to do the truncate self.file.write("%s\0%d\n" % (file, offset)) self.file.flush() @active - def addbackup(self, file, hardlink=True, vfs=None): + def addbackup(self, file, hardlink=True, location='store'): """Adds a backup of the file to the transaction Calling addbackup() creates a hardlink backup of the specified file that is used to recover the file in the event of the transaction aborting. @@ -159,12 +161,11 @@ class transaction(object): """ if file in self.map or file in self.backupmap: return backupfile = "%s.backup.%s" % (self.journal, file) - if vfs is None: - vfs = self.opener + vfs = self._vfsmap[location] if vfs.exists(file): filepath = vfs.join(file) backuppath = self.opener.join(backupfile) util.copyfiles(filepath, backuppath, hardlink=hardlink) else: @@ -173,13 +174,13 @@ class transaction(object): if self._queue: self._queue[-1][1].append((file, backupfile)) return - self.backupentries.append((file, backupfile, None)) + self.backupentries.append((location, file, backupfile, None)) self.backupmap[file] = len(self.backupentries) - 1 - self.backupsfile.write("%s\0%s\0" % (file, backupfile)) + self.backupsfile.write("%s\0%s\0%s\0" % (location, file, backupfile)) self.backupsfile.flush() @active def addfilegenerator(self, genid, filenames, genfunc, order=0, location='store'): @@ -278,12 +279,13 @@ class transaction(object): self.after() if self.opener.isfile(self.journal): self.opener.unlink(self.journal) if self.opener.isfile(self.backupjournal): self.opener.unlink(self.backupjournal) - for _f, b, _ignore in self.backupentries: - self.opener.unlink(b) + for l, _f, b, _ignore in self.backupentries: + vfs = self._vfsmap[l] + vfs.unlink(b) self.backupentries = [] self.journal = None @active def abort(self): @@ -310,20 +312,20 @@ class transaction(object): return self.report(_("transaction abort!\n")) try: - _playback(self.journal, self.report, self.opener, + _playback(self.journal, self.report, self.opener, self._vfsmap, self.entries, self.backupentries, False) self.report(_("rollback completed\n")) except Exception: self.report(_("rollback failed - please run hg recover\n")) finally: self.journal = None -def rollback(opener, file, report): +def rollback(opener, vfsmap, file, report): """Rolls back the transaction contained in the given file Reads the entries in the specified file, and the corresponding '*.backupfiles' file, to recover from an incomplete transaction. @@ -350,10 +352,10 @@ def rollback(opener, file, report): if opener.exists(backupjournal): fp = opener.open(backupjournal) data = fp.read() if len(data) > 0: parts = data.split('\0') - for i in xrange(0, len(parts), 2): - f, b = parts[i:i + 1] - backupentries.append((f, b, None)) + for i in xrange(0, len(parts), 3): + f, b = parts[i:i + 2] + backupentries.append((l, f, b, None)) - _playback(file, report, opener, entries, backupentries) + _playback(file, report, opener, vfsmap, entries, backupentries)