129 vfsmap[''] = opener # set default value |
129 vfsmap[''] = opener # set default value |
130 self._vfsmap = vfsmap |
130 self._vfsmap = vfsmap |
131 self.after = after |
131 self.after = after |
132 self.entries = [] |
132 self.entries = [] |
133 self.map = {} |
133 self.map = {} |
134 self.journal = journalname |
134 self._journal = journalname |
135 self._undoname = undoname |
135 self._undoname = undoname |
136 self._queue = [] |
136 self._queue = [] |
137 # A callback to validate transaction content before closing it. |
137 # A callback to validate transaction content before closing it. |
138 # should raise exception is anything is wrong. |
138 # should raise exception is anything is wrong. |
139 # target user is repository hooks. |
139 # target user is repository hooks. |
155 # transaction. |
155 # transaction. |
156 self.changes = {} |
156 self.changes = {} |
157 |
157 |
158 # a dict of arguments to be passed to hooks |
158 # a dict of arguments to be passed to hooks |
159 self.hookargs = {} |
159 self.hookargs = {} |
160 self.file = opener.open(self.journal, "w") |
160 self.file = opener.open(self._journal, "w") |
161 |
161 |
162 # a list of ('location', 'path', 'backuppath', cache) entries. |
162 # a list of ('location', 'path', 'backuppath', cache) entries. |
163 # - if 'backuppath' is empty, no file existed at backup time |
163 # - if 'backuppath' is empty, no file existed at backup time |
164 # - if 'path' is empty, this is a temporary transaction file |
164 # - if 'path' is empty, this is a temporary transaction file |
165 # - if 'location' is not empty, the path is outside main opener reach. |
165 # - if 'location' is not empty, the path is outside main opener reach. |
166 # use 'location' value as a key in a vfsmap to find the right 'vfs' |
166 # use 'location' value as a key in a vfsmap to find the right 'vfs' |
167 # (cache is currently unused) |
167 # (cache is currently unused) |
168 self._backupentries = [] |
168 self._backupentries = [] |
169 self._backupmap = {} |
169 self._backupmap = {} |
170 self._backupjournal = "%s.backupfiles" % self.journal |
170 self._backupjournal = "%s.backupfiles" % self._journal |
171 self._backupsfile = opener.open(self._backupjournal, 'w') |
171 self._backupsfile = opener.open(self._backupjournal, 'w') |
172 self._backupsfile.write('%d\n' % version) |
172 self._backupsfile.write('%d\n' % version) |
173 |
173 |
174 if createmode is not None: |
174 if createmode is not None: |
175 opener.chmod(self.journal, createmode & 0o666) |
175 opener.chmod(self._journal, createmode & 0o666) |
176 opener.chmod(self._backupjournal, createmode & 0o666) |
176 opener.chmod(self._backupjournal, createmode & 0o666) |
177 |
177 |
178 # hold file generations to be performed on commit |
178 # hold file generations to be performed on commit |
179 self._filegenerators = {} |
179 self._filegenerators = {} |
180 # hold callback to write pending data for hooks |
180 # hold callback to write pending data for hooks |
192 name = r'/'.join(self.names) |
192 name = r'/'.join(self.names) |
193 return (r'<transaction name=%s, count=%d, usages=%d>' % |
193 return (r'<transaction name=%s, count=%d, usages=%d>' % |
194 (name, self._count, self._usages)) |
194 (name, self._count, self._usages)) |
195 |
195 |
196 def __del__(self): |
196 def __del__(self): |
197 if self.journal: |
197 if self._journal: |
198 self._abort() |
198 self._abort() |
199 |
199 |
200 @active |
200 @active |
201 def startgroup(self): |
201 def startgroup(self): |
202 """delay registration of file entry |
202 """delay registration of file entry |
253 |
253 |
254 if file in self.map or file in self._backupmap: |
254 if file in self.map or file in self._backupmap: |
255 return |
255 return |
256 vfs = self._vfsmap[location] |
256 vfs = self._vfsmap[location] |
257 dirname, filename = vfs.split(file) |
257 dirname, filename = vfs.split(file) |
258 backupfilename = "%s.backup.%s" % (self.journal, filename) |
258 backupfilename = "%s.backup.%s" % (self._journal, filename) |
259 backupfile = vfs.reljoin(dirname, backupfilename) |
259 backupfile = vfs.reljoin(dirname, backupfilename) |
260 if vfs.exists(file): |
260 if vfs.exists(file): |
261 filepath = vfs.join(file) |
261 filepath = vfs.join(file) |
262 backuppath = vfs.join(backupfile) |
262 backuppath = vfs.join(backupfile) |
263 util.copyfile(filepath, backuppath, hardlink=hardlink) |
263 util.copyfile(filepath, backuppath, hardlink=hardlink) |
491 if self.after: |
491 if self.after: |
492 self.after() |
492 self.after() |
493 self.after = None # Help prevent cycles. |
493 self.after = None # Help prevent cycles. |
494 if self.opener.isfile(self._backupjournal): |
494 if self.opener.isfile(self._backupjournal): |
495 self.opener.unlink(self._backupjournal) |
495 self.opener.unlink(self._backupjournal) |
496 if self.opener.isfile(self.journal): |
496 if self.opener.isfile(self._journal): |
497 self.opener.unlink(self.journal) |
497 self.opener.unlink(self._journal) |
498 for l, _f, b, c in self._backupentries: |
498 for l, _f, b, c in self._backupentries: |
499 if l not in self._vfsmap and c: |
499 if l not in self._vfsmap and c: |
500 self.report("couldn't remove %s: unknown cache location" |
500 self.report("couldn't remove %s: unknown cache location" |
501 "%s\n" % (b, l)) |
501 "%s\n" % (b, l)) |
502 continue |
502 continue |
509 raise |
509 raise |
510 # Abort may be raise by read only opener |
510 # Abort may be raise by read only opener |
511 self.report("couldn't remove %s: %s\n" |
511 self.report("couldn't remove %s: %s\n" |
512 % (vfs.join(b), inst)) |
512 % (vfs.join(b), inst)) |
513 self._backupentries = [] |
513 self._backupentries = [] |
514 self.journal = None |
514 self._journal = None |
515 |
515 |
516 self.releasefn(self, True) # notify success of closing transaction |
516 self.releasefn(self, True) # notify success of closing transaction |
517 self.releasefn = None # Help prevent cycles. |
517 self.releasefn = None # Help prevent cycles. |
518 |
518 |
519 # run post close action |
519 # run post close action |
547 self.report("couldn't remove %s: unknown cache location" |
547 self.report("couldn't remove %s: unknown cache location" |
548 "%s\n" % (b, l)) |
548 "%s\n" % (b, l)) |
549 continue |
549 continue |
550 vfs = self._vfsmap[l] |
550 vfs = self._vfsmap[l] |
551 base, name = vfs.split(b) |
551 base, name = vfs.split(b) |
552 assert name.startswith(self.journal), name |
552 assert name.startswith(self._journal), name |
553 uname = name.replace(self.journal, self._undoname, 1) |
553 uname = name.replace(self._journal, self._undoname, 1) |
554 u = vfs.reljoin(base, uname) |
554 u = vfs.reljoin(base, uname) |
555 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True) |
555 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True) |
556 undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c)) |
556 undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c)) |
557 undobackupfile.close() |
557 undobackupfile.close() |
558 |
558 |
565 |
565 |
566 try: |
566 try: |
567 if not self.entries and not self._backupentries: |
567 if not self.entries and not self._backupentries: |
568 if self._backupjournal: |
568 if self._backupjournal: |
569 self.opener.unlink(self._backupjournal) |
569 self.opener.unlink(self._backupjournal) |
570 if self.journal: |
570 if self._journal: |
571 self.opener.unlink(self.journal) |
571 self.opener.unlink(self._journal) |
572 return |
572 return |
573 |
573 |
574 self.report(_("transaction abort!\n")) |
574 self.report(_("transaction abort!\n")) |
575 |
575 |
576 try: |
576 try: |
577 for cat in sorted(self._abortcallback): |
577 for cat in sorted(self._abortcallback): |
578 self._abortcallback[cat](self) |
578 self._abortcallback[cat](self) |
579 # Prevent double usage and help clear cycles. |
579 # Prevent double usage and help clear cycles. |
580 self._abortcallback = None |
580 self._abortcallback = None |
581 _playback(self.journal, self.report, self.opener, self._vfsmap, |
581 _playback(self._journal, self.report, self.opener, self._vfsmap, |
582 self.entries, self._backupentries, False, |
582 self.entries, self._backupentries, False, |
583 checkambigfiles=self.checkambigfiles) |
583 checkambigfiles=self.checkambigfiles) |
584 self.report(_("rollback completed\n")) |
584 self.report(_("rollback completed\n")) |
585 except BaseException: |
585 except BaseException: |
586 self.report(_("rollback failed - please run hg recover\n")) |
586 self.report(_("rollback failed - please run hg recover\n")) |
587 finally: |
587 finally: |
588 self.journal = None |
588 self._journal = None |
589 self.releasefn(self, False) # notify failure of transaction |
589 self.releasefn(self, False) # notify failure of transaction |
590 self.releasefn = None # Help prevent cycles. |
590 self.releasefn = None # Help prevent cycles. |
591 |
591 |
592 def rollback(opener, vfsmap, file, report, checkambigfiles=None): |
592 def rollback(opener, vfsmap, file, report, checkambigfiles=None): |
593 """Rolls back the transaction contained in the given file |
593 """Rolls back the transaction contained in the given file |