Mercurial > hg
comparison hgext/journal.py @ 43076:2372284d9457
formatting: blacken the codebase
This is using my patch to black
(https://github.com/psf/black/pull/826) so we don't un-wrap collection
literals.
Done with:
hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S
# skip-blame mass-reformatting only
# no-check-commit reformats foo_bar functions
Differential Revision: https://phab.mercurial-scm.org/D6971
author | Augie Fackler <augie@google.com> |
---|---|
date | Sun, 06 Oct 2019 09:45:02 -0400 |
parents | 7f63ec6969f3 |
children | 687b865b95ad |
comparison
equal
deleted
inserted
replaced
43075:57875cf423c9 | 43076:2372284d9457 |
---|---|
66 # Journal recording, register hooks and storage object | 66 # Journal recording, register hooks and storage object |
67 def extsetup(ui): | 67 def extsetup(ui): |
68 extensions.wrapfunction(dispatch, 'runcommand', runcommand) | 68 extensions.wrapfunction(dispatch, 'runcommand', runcommand) |
69 extensions.wrapfunction(bookmarks.bmstore, '_write', recordbookmarks) | 69 extensions.wrapfunction(bookmarks.bmstore, '_write', recordbookmarks) |
70 extensions.wrapfilecache( | 70 extensions.wrapfilecache( |
71 localrepo.localrepository, 'dirstate', wrapdirstate) | 71 localrepo.localrepository, 'dirstate', wrapdirstate |
72 ) | |
72 extensions.wrapfunction(hg, 'postshare', wrappostshare) | 73 extensions.wrapfunction(hg, 'postshare', wrappostshare) |
73 extensions.wrapfunction(hg, 'copystore', unsharejournal) | 74 extensions.wrapfunction(hg, 'copystore', unsharejournal) |
75 | |
74 | 76 |
75 def reposetup(ui, repo): | 77 def reposetup(ui, repo): |
76 if repo.local(): | 78 if repo.local(): |
77 repo.journal = journalstorage(repo) | 79 repo.journal = journalstorage(repo) |
78 repo._wlockfreeprefix.add('namejournal') | 80 repo._wlockfreeprefix.add('namejournal') |
82 # already instantiated dirstate isn't yet marked as | 84 # already instantiated dirstate isn't yet marked as |
83 # "journal"-ing, even though repo.dirstate() was already | 85 # "journal"-ing, even though repo.dirstate() was already |
84 # wrapped by own wrapdirstate() | 86 # wrapped by own wrapdirstate() |
85 _setupdirstate(repo, dirstate) | 87 _setupdirstate(repo, dirstate) |
86 | 88 |
89 | |
87 def runcommand(orig, lui, repo, cmd, fullargs, *args): | 90 def runcommand(orig, lui, repo, cmd, fullargs, *args): |
88 """Track the command line options for recording in the journal""" | 91 """Track the command line options for recording in the journal""" |
89 journalstorage.recordcommand(*fullargs) | 92 journalstorage.recordcommand(*fullargs) |
90 return orig(lui, repo, cmd, fullargs, *args) | 93 return orig(lui, repo, cmd, fullargs, *args) |
91 | 94 |
95 | |
92 def _setupdirstate(repo, dirstate): | 96 def _setupdirstate(repo, dirstate): |
93 dirstate.journalstorage = repo.journal | 97 dirstate.journalstorage = repo.journal |
94 dirstate.addparentchangecallback('journal', recorddirstateparents) | 98 dirstate.addparentchangecallback('journal', recorddirstateparents) |
99 | |
95 | 100 |
96 # hooks to record dirstate changes | 101 # hooks to record dirstate changes |
97 def wrapdirstate(orig, repo): | 102 def wrapdirstate(orig, repo): |
98 """Make journal storage available to the dirstate object""" | 103 """Make journal storage available to the dirstate object""" |
99 dirstate = orig(repo) | 104 dirstate = orig(repo) |
100 if util.safehasattr(repo, 'journal'): | 105 if util.safehasattr(repo, 'journal'): |
101 _setupdirstate(repo, dirstate) | 106 _setupdirstate(repo, dirstate) |
102 return dirstate | 107 return dirstate |
108 | |
103 | 109 |
104 def recorddirstateparents(dirstate, old, new): | 110 def recorddirstateparents(dirstate, old, new): |
105 """Records all dirstate parent changes in the journal.""" | 111 """Records all dirstate parent changes in the journal.""" |
106 old = list(old) | 112 old = list(old) |
107 new = list(new) | 113 new = list(new) |
108 if util.safehasattr(dirstate, 'journalstorage'): | 114 if util.safehasattr(dirstate, 'journalstorage'): |
109 # only record two hashes if there was a merge | 115 # only record two hashes if there was a merge |
110 oldhashes = old[:1] if old[1] == node.nullid else old | 116 oldhashes = old[:1] if old[1] == node.nullid else old |
111 newhashes = new[:1] if new[1] == node.nullid else new | 117 newhashes = new[:1] if new[1] == node.nullid else new |
112 dirstate.journalstorage.record( | 118 dirstate.journalstorage.record( |
113 wdirparenttype, '.', oldhashes, newhashes) | 119 wdirparenttype, '.', oldhashes, newhashes |
120 ) | |
121 | |
114 | 122 |
115 # hooks to record bookmark changes (both local and remote) | 123 # hooks to record bookmark changes (both local and remote) |
116 def recordbookmarks(orig, store, fp): | 124 def recordbookmarks(orig, store, fp): |
117 """Records all bookmark changes in the journal.""" | 125 """Records all bookmark changes in the journal.""" |
118 repo = store._repo | 126 repo = store._repo |
122 oldvalue = oldmarks.get(mark, node.nullid) | 130 oldvalue = oldmarks.get(mark, node.nullid) |
123 if value != oldvalue: | 131 if value != oldvalue: |
124 repo.journal.record(bookmarktype, mark, oldvalue, value) | 132 repo.journal.record(bookmarktype, mark, oldvalue, value) |
125 return orig(store, fp) | 133 return orig(store, fp) |
126 | 134 |
135 | |
127 # shared repository support | 136 # shared repository support |
128 def _readsharedfeatures(repo): | 137 def _readsharedfeatures(repo): |
129 """A set of shared features for this repository""" | 138 """A set of shared features for this repository""" |
130 try: | 139 try: |
131 return set(repo.vfs.read('shared').splitlines()) | 140 return set(repo.vfs.read('shared').splitlines()) |
132 except IOError as inst: | 141 except IOError as inst: |
133 if inst.errno != errno.ENOENT: | 142 if inst.errno != errno.ENOENT: |
134 raise | 143 raise |
135 return set() | 144 return set() |
145 | |
136 | 146 |
137 def _mergeentriesiter(*iterables, **kwargs): | 147 def _mergeentriesiter(*iterables, **kwargs): |
138 """Given a set of sorted iterables, yield the next entry in merged order | 148 """Given a set of sorted iterables, yield the next entry in merged order |
139 | 149 |
140 Note that by default entries go from most recent to oldest. | 150 Note that by default entries go from most recent to oldest. |
160 iterable_map[key][0] = next(it) | 170 iterable_map[key][0] = next(it) |
161 except StopIteration: | 171 except StopIteration: |
162 # this iterable is empty, remove it from consideration | 172 # this iterable is empty, remove it from consideration |
163 del iterable_map[key] | 173 del iterable_map[key] |
164 | 174 |
175 | |
165 def wrappostshare(orig, sourcerepo, destrepo, **kwargs): | 176 def wrappostshare(orig, sourcerepo, destrepo, **kwargs): |
166 """Mark this shared working copy as sharing journal information""" | 177 """Mark this shared working copy as sharing journal information""" |
167 with destrepo.wlock(): | 178 with destrepo.wlock(): |
168 orig(sourcerepo, destrepo, **kwargs) | 179 orig(sourcerepo, destrepo, **kwargs) |
169 with destrepo.vfs('shared', 'a') as fp: | 180 with destrepo.vfs('shared', 'a') as fp: |
170 fp.write('journal\n') | 181 fp.write('journal\n') |
171 | 182 |
183 | |
172 def unsharejournal(orig, ui, repo, repopath): | 184 def unsharejournal(orig, ui, repo, repopath): |
173 """Copy shared journal entries into this repo when unsharing""" | 185 """Copy shared journal entries into this repo when unsharing""" |
174 if (repo.path == repopath and repo.shared() and | 186 if ( |
175 util.safehasattr(repo, 'journal')): | 187 repo.path == repopath |
188 and repo.shared() | |
189 and util.safehasattr(repo, 'journal') | |
190 ): | |
176 sharedrepo = hg.sharedreposource(repo) | 191 sharedrepo = hg.sharedreposource(repo) |
177 sharedfeatures = _readsharedfeatures(repo) | 192 sharedfeatures = _readsharedfeatures(repo) |
178 if sharedrepo and sharedfeatures > {'journal'}: | 193 if sharedrepo and sharedfeatures > {'journal'}: |
179 # there is a shared repository and there are shared journal entries | 194 # there is a shared repository and there are shared journal entries |
180 # to copy. move shared date over from source to destination but | 195 # to copy. move shared date over from source to destination but |
182 if repo.vfs.exists('namejournal'): | 197 if repo.vfs.exists('namejournal'): |
183 journalpath = repo.vfs.join('namejournal') | 198 journalpath = repo.vfs.join('namejournal') |
184 util.rename(journalpath, journalpath + '.bak') | 199 util.rename(journalpath, journalpath + '.bak') |
185 storage = repo.journal | 200 storage = repo.journal |
186 local = storage._open( | 201 local = storage._open( |
187 repo.vfs, filename='namejournal.bak', _newestfirst=False) | 202 repo.vfs, filename='namejournal.bak', _newestfirst=False |
203 ) | |
188 shared = ( | 204 shared = ( |
189 e for e in storage._open(sharedrepo.vfs, _newestfirst=False) | 205 e |
190 if sharednamespaces.get(e.namespace) in sharedfeatures) | 206 for e in storage._open(sharedrepo.vfs, _newestfirst=False) |
207 if sharednamespaces.get(e.namespace) in sharedfeatures | |
208 ) | |
191 for entry in _mergeentriesiter(local, shared, order=min): | 209 for entry in _mergeentriesiter(local, shared, order=min): |
192 storage._write(repo.vfs, entry) | 210 storage._write(repo.vfs, entry) |
193 | 211 |
194 return orig(ui, repo, repopath) | 212 return orig(ui, repo, repopath) |
195 | 213 |
196 class journalentry(collections.namedtuple( | 214 |
215 class journalentry( | |
216 collections.namedtuple( | |
197 r'journalentry', | 217 r'journalentry', |
198 r'timestamp user command namespace name oldhashes newhashes')): | 218 r'timestamp user command namespace name oldhashes newhashes', |
219 ) | |
220 ): | |
199 """Individual journal entry | 221 """Individual journal entry |
200 | 222 |
201 * timestamp: a mercurial (time, timezone) tuple | 223 * timestamp: a mercurial (time, timezone) tuple |
202 * user: the username that ran the command | 224 * user: the username that ran the command |
203 * namespace: the entry namespace, an opaque string | 225 * namespace: the entry namespace, an opaque string |
210 Handles serialisation from and to the storage format. Fields are | 232 Handles serialisation from and to the storage format. Fields are |
211 separated by newlines, hashes are written out in hex separated by commas, | 233 separated by newlines, hashes are written out in hex separated by commas, |
212 timestamp and timezone are separated by a space. | 234 timestamp and timezone are separated by a space. |
213 | 235 |
214 """ | 236 """ |
237 | |
215 @classmethod | 238 @classmethod |
216 def fromstorage(cls, line): | 239 def fromstorage(cls, line): |
217 (time, user, command, namespace, name, | 240 ( |
218 oldhashes, newhashes) = line.split('\n') | 241 time, |
242 user, | |
243 command, | |
244 namespace, | |
245 name, | |
246 oldhashes, | |
247 newhashes, | |
248 ) = line.split('\n') | |
219 timestamp, tz = time.split() | 249 timestamp, tz = time.split() |
220 timestamp, tz = float(timestamp), int(tz) | 250 timestamp, tz = float(timestamp), int(tz) |
221 oldhashes = tuple(node.bin(hash) for hash in oldhashes.split(',')) | 251 oldhashes = tuple(node.bin(hash) for hash in oldhashes.split(',')) |
222 newhashes = tuple(node.bin(hash) for hash in newhashes.split(',')) | 252 newhashes = tuple(node.bin(hash) for hash in newhashes.split(',')) |
223 return cls( | 253 return cls( |
224 (timestamp, tz), user, command, namespace, name, | 254 (timestamp, tz), |
225 oldhashes, newhashes) | 255 user, |
256 command, | |
257 namespace, | |
258 name, | |
259 oldhashes, | |
260 newhashes, | |
261 ) | |
226 | 262 |
227 def __bytes__(self): | 263 def __bytes__(self): |
228 """bytes representation for storage""" | 264 """bytes representation for storage""" |
229 time = ' '.join(map(pycompat.bytestr, self.timestamp)) | 265 time = ' '.join(map(pycompat.bytestr, self.timestamp)) |
230 oldhashes = ','.join([node.hex(hash) for hash in self.oldhashes]) | 266 oldhashes = ','.join([node.hex(hash) for hash in self.oldhashes]) |
231 newhashes = ','.join([node.hex(hash) for hash in self.newhashes]) | 267 newhashes = ','.join([node.hex(hash) for hash in self.newhashes]) |
232 return '\n'.join(( | 268 return '\n'.join( |
233 time, self.user, self.command, self.namespace, self.name, | 269 ( |
234 oldhashes, newhashes)) | 270 time, |
271 self.user, | |
272 self.command, | |
273 self.namespace, | |
274 self.name, | |
275 oldhashes, | |
276 newhashes, | |
277 ) | |
278 ) | |
235 | 279 |
236 __str__ = encoding.strmethod(__bytes__) | 280 __str__ = encoding.strmethod(__bytes__) |
281 | |
237 | 282 |
238 class journalstorage(object): | 283 class journalstorage(object): |
239 """Storage for journal entries | 284 """Storage for journal entries |
240 | 285 |
241 Entries are divided over two files; one with entries that pertain to the | 286 Entries are divided over two files; one with entries that pertain to the |
250 This storage uses a dedicated lock; this makes it easier to avoid issues | 295 This storage uses a dedicated lock; this makes it easier to avoid issues |
251 with adding entries that added when the regular wlock is unlocked (e.g. | 296 with adding entries that added when the regular wlock is unlocked (e.g. |
252 the dirstate). | 297 the dirstate). |
253 | 298 |
254 """ | 299 """ |
300 | |
255 _currentcommand = () | 301 _currentcommand = () |
256 _lockref = None | 302 _lockref = None |
257 | 303 |
258 def __init__(self, repo): | 304 def __init__(self, repo): |
259 self.user = procutil.getuser() | 305 self.user = procutil.getuser() |
271 | 317 |
272 # track the current command for recording in journal entries | 318 # track the current command for recording in journal entries |
273 @property | 319 @property |
274 def command(self): | 320 def command(self): |
275 commandstr = ' '.join( | 321 commandstr = ' '.join( |
276 map(procutil.shellquote, journalstorage._currentcommand)) | 322 map(procutil.shellquote, journalstorage._currentcommand) |
323 ) | |
277 if '\n' in commandstr: | 324 if '\n' in commandstr: |
278 # truncate multi-line commands | 325 # truncate multi-line commands |
279 commandstr = commandstr.partition('\n')[0] + ' ...' | 326 commandstr = commandstr.partition('\n')[0] + ' ...' |
280 return commandstr | 327 return commandstr |
281 | 328 |
305 desc = _('journal of %s') % vfs.base | 352 desc = _('journal of %s') % vfs.base |
306 try: | 353 try: |
307 l = lock.lock(vfs, 'namejournal.lock', 0, desc=desc) | 354 l = lock.lock(vfs, 'namejournal.lock', 0, desc=desc) |
308 except error.LockHeld as inst: | 355 except error.LockHeld as inst: |
309 self.ui.warn( | 356 self.ui.warn( |
310 _("waiting for lock on %s held by %r\n") % (desc, inst.locker)) | 357 _("waiting for lock on %s held by %r\n") % (desc, inst.locker) |
358 ) | |
311 # default to 600 seconds timeout | 359 # default to 600 seconds timeout |
312 l = lock.lock( | 360 l = lock.lock( |
313 vfs, 'namejournal.lock', | 361 vfs, |
314 self.ui.configint("ui", "timeout"), desc=desc) | 362 'namejournal.lock', |
363 self.ui.configint("ui", "timeout"), | |
364 desc=desc, | |
365 ) | |
315 self.ui.warn(_("got lock after %s seconds\n") % l.delay) | 366 self.ui.warn(_("got lock after %s seconds\n") % l.delay) |
316 self._lockref = weakref.ref(l) | 367 self._lockref = weakref.ref(l) |
317 return l | 368 return l |
318 | 369 |
319 def record(self, namespace, name, oldhashes, newhashes): | 370 def record(self, namespace, name, oldhashes, newhashes): |
332 oldhashes = [oldhashes] | 383 oldhashes = [oldhashes] |
333 if not isinstance(newhashes, list): | 384 if not isinstance(newhashes, list): |
334 newhashes = [newhashes] | 385 newhashes = [newhashes] |
335 | 386 |
336 entry = journalentry( | 387 entry = journalentry( |
337 dateutil.makedate(), self.user, self.command, namespace, name, | 388 dateutil.makedate(), |
338 oldhashes, newhashes) | 389 self.user, |
390 self.command, | |
391 namespace, | |
392 name, | |
393 oldhashes, | |
394 newhashes, | |
395 ) | |
339 | 396 |
340 vfs = self.vfs | 397 vfs = self.vfs |
341 if self.sharedvfs is not None: | 398 if self.sharedvfs is not None: |
342 # write to the shared repository if this feature is being | 399 # write to the shared repository if this feature is being |
343 # shared between working copies. | 400 # shared between working copies. |
358 # different version of the storage. Exit early (and not | 415 # different version of the storage. Exit early (and not |
359 # write anything) if this is not a version we can handle or | 416 # write anything) if this is not a version we can handle or |
360 # the file is corrupt. In future, perhaps rotate the file | 417 # the file is corrupt. In future, perhaps rotate the file |
361 # instead? | 418 # instead? |
362 self.ui.warn( | 419 self.ui.warn( |
363 _("unsupported journal file version '%s'\n") % version) | 420 _("unsupported journal file version '%s'\n") % version |
421 ) | |
364 return | 422 return |
365 if not version: | 423 if not version: |
366 # empty file, write version first | 424 # empty file, write version first |
367 f.write(("%d" % storageversion) + '\0') | 425 f.write(("%d" % storageversion) + '\0') |
368 f.seek(0, os.SEEK_END) | 426 f.seek(0, os.SEEK_END) |
401 return local | 459 return local |
402 | 460 |
403 # iterate over both local and shared entries, but only those | 461 # iterate over both local and shared entries, but only those |
404 # shared entries that are among the currently shared features | 462 # shared entries that are among the currently shared features |
405 shared = ( | 463 shared = ( |
406 e for e in self._open(self.sharedvfs) | 464 e |
407 if sharednamespaces.get(e.namespace) in self.sharedfeatures) | 465 for e in self._open(self.sharedvfs) |
466 if sharednamespaces.get(e.namespace) in self.sharedfeatures | |
467 ) | |
408 return _mergeentriesiter(local, shared) | 468 return _mergeentriesiter(local, shared) |
409 | 469 |
410 def _open(self, vfs, filename='namejournal', _newestfirst=True): | 470 def _open(self, vfs, filename='namejournal', _newestfirst=True): |
411 if not vfs.exists(filename): | 471 if not vfs.exists(filename): |
412 return | 472 return |
429 for line in lines: | 489 for line in lines: |
430 if not line: | 490 if not line: |
431 continue | 491 continue |
432 yield journalentry.fromstorage(line) | 492 yield journalentry.fromstorage(line) |
433 | 493 |
494 | |
434 # journal reading | 495 # journal reading |
435 # log options that don't make sense for journal | 496 # log options that don't make sense for journal |
436 _ignoreopts = ('no-merges', 'graph') | 497 _ignoreopts = ('no-merges', 'graph') |
498 | |
499 | |
437 @command( | 500 @command( |
438 'journal', [ | 501 'journal', |
502 [ | |
439 ('', 'all', None, 'show history for all names'), | 503 ('', 'all', None, 'show history for all names'), |
440 ('c', 'commits', None, 'show commit metadata'), | 504 ('c', 'commits', None, 'show commit metadata'), |
441 ] + [opt for opt in cmdutil.logopts if opt[1] not in _ignoreopts], | 505 ] |
506 + [opt for opt in cmdutil.logopts if opt[1] not in _ignoreopts], | |
442 '[OPTION]... [BOOKMARKNAME]', | 507 '[OPTION]... [BOOKMARKNAME]', |
443 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION) | 508 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION, |
509 ) | |
444 def journal(ui, repo, *args, **opts): | 510 def journal(ui, repo, *args, **opts): |
445 """show the previous position of bookmarks and the working copy | 511 """show the previous position of bookmarks and the working copy |
446 | 512 |
447 The journal is used to see the previous commits that bookmarks and the | 513 The journal is used to see the previous commits that bookmarks and the |
448 working copy pointed to. By default the previous locations for the working | 514 working copy pointed to. By default the previous locations for the working |
469 opts = pycompat.byteskwargs(opts) | 535 opts = pycompat.byteskwargs(opts) |
470 name = '.' | 536 name = '.' |
471 if opts.get('all'): | 537 if opts.get('all'): |
472 if args: | 538 if args: |
473 raise error.Abort( | 539 raise error.Abort( |
474 _("You can't combine --all and filtering on a name")) | 540 _("You can't combine --all and filtering on a name") |
541 ) | |
475 name = None | 542 name = None |
476 if args: | 543 if args: |
477 name = args[0] | 544 name = args[0] |
478 | 545 |
479 fm = ui.formatter('journal', opts) | 546 fm = ui.formatter('journal', opts) |
547 | |
480 def formatnodes(nodes): | 548 def formatnodes(nodes): |
481 return fm.formatlist(map(fm.hexfunc, nodes), name='node', sep=',') | 549 return fm.formatlist(map(fm.hexfunc, nodes), name='node', sep=',') |
482 | 550 |
483 if opts.get("template") != "json": | 551 if opts.get("template") != "json": |
484 if name is None: | 552 if name is None: |
493 for count, entry in enumerate(repo.journal.filtered(name=name)): | 561 for count, entry in enumerate(repo.journal.filtered(name=name)): |
494 if count == limit: | 562 if count == limit: |
495 break | 563 break |
496 | 564 |
497 fm.startitem() | 565 fm.startitem() |
498 fm.condwrite(ui.verbose, 'oldnodes', '%s -> ', | 566 fm.condwrite( |
499 formatnodes(entry.oldhashes)) | 567 ui.verbose, 'oldnodes', '%s -> ', formatnodes(entry.oldhashes) |
568 ) | |
500 fm.write('newnodes', '%s', formatnodes(entry.newhashes)) | 569 fm.write('newnodes', '%s', formatnodes(entry.newhashes)) |
501 fm.condwrite(ui.verbose, 'user', ' %-8s', entry.user) | 570 fm.condwrite(ui.verbose, 'user', ' %-8s', entry.user) |
502 fm.condwrite( | 571 fm.condwrite( |
503 opts.get('all') or name.startswith('re:'), | 572 opts.get('all') or name.startswith('re:'), |
504 'name', ' %-8s', entry.name) | 573 'name', |
505 | 574 ' %-8s', |
506 fm.condwrite(ui.verbose, 'date', ' %s', | 575 entry.name, |
507 fm.formatdate(entry.timestamp, '%Y-%m-%d %H:%M %1%2')) | 576 ) |
577 | |
578 fm.condwrite( | |
579 ui.verbose, | |
580 'date', | |
581 ' %s', | |
582 fm.formatdate(entry.timestamp, '%Y-%m-%d %H:%M %1%2'), | |
583 ) | |
508 fm.write('command', ' %s\n', entry.command) | 584 fm.write('command', ' %s\n', entry.command) |
509 | 585 |
510 if opts.get("commits"): | 586 if opts.get("commits"): |
511 if fm.isplain(): | 587 if fm.isplain(): |
512 displayer = logcmdutil.changesetdisplayer(ui, repo, opts) | 588 displayer = logcmdutil.changesetdisplayer(ui, repo, opts) |
513 else: | 589 else: |
514 displayer = logcmdutil.changesetformatter( | 590 displayer = logcmdutil.changesetformatter( |
515 ui, repo, fm.nested('changesets'), diffopts=opts) | 591 ui, repo, fm.nested('changesets'), diffopts=opts |
592 ) | |
516 for hash in entry.newhashes: | 593 for hash in entry.newhashes: |
517 try: | 594 try: |
518 ctx = repo[hash] | 595 ctx = repo[hash] |
519 displayer.show(ctx) | 596 displayer.show(ctx) |
520 except error.RepoLookupError as e: | 597 except error.RepoLookupError as e: |