##// END OF EJS Templates
journal: use changesetformatter to properly nest list of commits in JSON...
Yuya Nishihara -
r39740:aa647457 default
parent child Browse files
Show More
@@ -1,523 +1,527 b''
1 1 # journal.py
2 2 #
3 3 # Copyright 2014-2016 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 """track previous positions of bookmarks (EXPERIMENTAL)
8 8
9 9 This extension adds a new command: `hg journal`, which shows you where
10 10 bookmarks were previously located.
11 11
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import collections
17 17 import errno
18 18 import os
19 19 import weakref
20 20
21 21 from mercurial.i18n import _
22 22
23 23 from mercurial import (
24 24 bookmarks,
25 25 cmdutil,
26 26 dispatch,
27 27 encoding,
28 28 error,
29 29 extensions,
30 30 hg,
31 31 localrepo,
32 32 lock,
33 33 logcmdutil,
34 34 node,
35 35 pycompat,
36 36 registrar,
37 37 util,
38 38 )
39 39 from mercurial.utils import (
40 40 dateutil,
41 41 procutil,
42 42 stringutil,
43 43 )
44 44
45 45 cmdtable = {}
46 46 command = registrar.command(cmdtable)
47 47
48 48 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
49 49 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
50 50 # be specifying the version(s) of Mercurial they are tested with, or
51 51 # leave the attribute unspecified.
52 52 testedwith = 'ships-with-hg-core'
53 53
54 54 # storage format version; increment when the format changes
55 55 storageversion = 0
56 56
57 57 # namespaces
58 58 bookmarktype = 'bookmark'
59 59 wdirparenttype = 'wdirparent'
60 60 # In a shared repository, what shared feature name is used
61 61 # to indicate this namespace is shared with the source?
62 62 sharednamespaces = {
63 63 bookmarktype: hg.sharedbookmarks,
64 64 }
65 65
66 66 # Journal recording, register hooks and storage object
67 67 def extsetup(ui):
68 68 extensions.wrapfunction(dispatch, 'runcommand', runcommand)
69 69 extensions.wrapfunction(bookmarks.bmstore, '_write', recordbookmarks)
70 70 extensions.wrapfilecache(
71 71 localrepo.localrepository, 'dirstate', wrapdirstate)
72 72 extensions.wrapfunction(hg, 'postshare', wrappostshare)
73 73 extensions.wrapfunction(hg, 'copystore', unsharejournal)
74 74
75 75 def reposetup(ui, repo):
76 76 if repo.local():
77 77 repo.journal = journalstorage(repo)
78 78 repo._wlockfreeprefix.add('namejournal')
79 79
80 80 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
81 81 if cached:
82 82 # already instantiated dirstate isn't yet marked as
83 83 # "journal"-ing, even though repo.dirstate() was already
84 84 # wrapped by own wrapdirstate()
85 85 _setupdirstate(repo, dirstate)
86 86
87 87 def runcommand(orig, lui, repo, cmd, fullargs, *args):
88 88 """Track the command line options for recording in the journal"""
89 89 journalstorage.recordcommand(*fullargs)
90 90 return orig(lui, repo, cmd, fullargs, *args)
91 91
92 92 def _setupdirstate(repo, dirstate):
93 93 dirstate.journalstorage = repo.journal
94 94 dirstate.addparentchangecallback('journal', recorddirstateparents)
95 95
96 96 # hooks to record dirstate changes
97 97 def wrapdirstate(orig, repo):
98 98 """Make journal storage available to the dirstate object"""
99 99 dirstate = orig(repo)
100 100 if util.safehasattr(repo, 'journal'):
101 101 _setupdirstate(repo, dirstate)
102 102 return dirstate
103 103
104 104 def recorddirstateparents(dirstate, old, new):
105 105 """Records all dirstate parent changes in the journal."""
106 106 old = list(old)
107 107 new = list(new)
108 108 if util.safehasattr(dirstate, 'journalstorage'):
109 109 # only record two hashes if there was a merge
110 110 oldhashes = old[:1] if old[1] == node.nullid else old
111 111 newhashes = new[:1] if new[1] == node.nullid else new
112 112 dirstate.journalstorage.record(
113 113 wdirparenttype, '.', oldhashes, newhashes)
114 114
115 115 # hooks to record bookmark changes (both local and remote)
116 116 def recordbookmarks(orig, store, fp):
117 117 """Records all bookmark changes in the journal."""
118 118 repo = store._repo
119 119 if util.safehasattr(repo, 'journal'):
120 120 oldmarks = bookmarks.bmstore(repo)
121 121 for mark, value in store.iteritems():
122 122 oldvalue = oldmarks.get(mark, node.nullid)
123 123 if value != oldvalue:
124 124 repo.journal.record(bookmarktype, mark, oldvalue, value)
125 125 return orig(store, fp)
126 126
127 127 # shared repository support
128 128 def _readsharedfeatures(repo):
129 129 """A set of shared features for this repository"""
130 130 try:
131 131 return set(repo.vfs.read('shared').splitlines())
132 132 except IOError as inst:
133 133 if inst.errno != errno.ENOENT:
134 134 raise
135 135 return set()
136 136
137 137 def _mergeentriesiter(*iterables, **kwargs):
138 138 """Given a set of sorted iterables, yield the next entry in merged order
139 139
140 140 Note that by default entries go from most recent to oldest.
141 141 """
142 142 order = kwargs.pop(r'order', max)
143 143 iterables = [iter(it) for it in iterables]
144 144 # this tracks still active iterables; iterables are deleted as they are
145 145 # exhausted, which is why this is a dictionary and why each entry also
146 146 # stores the key. Entries are mutable so we can store the next value each
147 147 # time.
148 148 iterable_map = {}
149 149 for key, it in enumerate(iterables):
150 150 try:
151 151 iterable_map[key] = [next(it), key, it]
152 152 except StopIteration:
153 153 # empty entry, can be ignored
154 154 pass
155 155
156 156 while iterable_map:
157 157 value, key, it = order(iterable_map.itervalues())
158 158 yield value
159 159 try:
160 160 iterable_map[key][0] = next(it)
161 161 except StopIteration:
162 162 # this iterable is empty, remove it from consideration
163 163 del iterable_map[key]
164 164
165 165 def wrappostshare(orig, sourcerepo, destrepo, **kwargs):
166 166 """Mark this shared working copy as sharing journal information"""
167 167 with destrepo.wlock():
168 168 orig(sourcerepo, destrepo, **kwargs)
169 169 with destrepo.vfs('shared', 'a') as fp:
170 170 fp.write('journal\n')
171 171
172 172 def unsharejournal(orig, ui, repo, repopath):
173 173 """Copy shared journal entries into this repo when unsharing"""
174 174 if (repo.path == repopath and repo.shared() and
175 175 util.safehasattr(repo, 'journal')):
176 176 sharedrepo = hg.sharedreposource(repo)
177 177 sharedfeatures = _readsharedfeatures(repo)
178 178 if sharedrepo and sharedfeatures > {'journal'}:
179 179 # there is a shared repository and there are shared journal entries
180 180 # to copy. move shared date over from source to destination but
181 181 # move the local file first
182 182 if repo.vfs.exists('namejournal'):
183 183 journalpath = repo.vfs.join('namejournal')
184 184 util.rename(journalpath, journalpath + '.bak')
185 185 storage = repo.journal
186 186 local = storage._open(
187 187 repo.vfs, filename='namejournal.bak', _newestfirst=False)
188 188 shared = (
189 189 e for e in storage._open(sharedrepo.vfs, _newestfirst=False)
190 190 if sharednamespaces.get(e.namespace) in sharedfeatures)
191 191 for entry in _mergeentriesiter(local, shared, order=min):
192 192 storage._write(repo.vfs, entry)
193 193
194 194 return orig(ui, repo, repopath)
195 195
196 196 class journalentry(collections.namedtuple(
197 197 u'journalentry',
198 198 u'timestamp user command namespace name oldhashes newhashes')):
199 199 """Individual journal entry
200 200
201 201 * timestamp: a mercurial (time, timezone) tuple
202 202 * user: the username that ran the command
203 203 * namespace: the entry namespace, an opaque string
204 204 * name: the name of the changed item, opaque string with meaning in the
205 205 namespace
206 206 * command: the hg command that triggered this record
207 207 * oldhashes: a tuple of one or more binary hashes for the old location
208 208 * newhashes: a tuple of one or more binary hashes for the new location
209 209
210 210 Handles serialisation from and to the storage format. Fields are
211 211 separated by newlines, hashes are written out in hex separated by commas,
212 212 timestamp and timezone are separated by a space.
213 213
214 214 """
215 215 @classmethod
216 216 def fromstorage(cls, line):
217 217 (time, user, command, namespace, name,
218 218 oldhashes, newhashes) = line.split('\n')
219 219 timestamp, tz = time.split()
220 220 timestamp, tz = float(timestamp), int(tz)
221 221 oldhashes = tuple(node.bin(hash) for hash in oldhashes.split(','))
222 222 newhashes = tuple(node.bin(hash) for hash in newhashes.split(','))
223 223 return cls(
224 224 (timestamp, tz), user, command, namespace, name,
225 225 oldhashes, newhashes)
226 226
227 227 def __bytes__(self):
228 228 """bytes representation for storage"""
229 229 time = ' '.join(map(pycompat.bytestr, self.timestamp))
230 230 oldhashes = ','.join([node.hex(hash) for hash in self.oldhashes])
231 231 newhashes = ','.join([node.hex(hash) for hash in self.newhashes])
232 232 return '\n'.join((
233 233 time, self.user, self.command, self.namespace, self.name,
234 234 oldhashes, newhashes))
235 235
236 236 __str__ = encoding.strmethod(__bytes__)
237 237
238 238 class journalstorage(object):
239 239 """Storage for journal entries
240 240
241 241 Entries are divided over two files; one with entries that pertain to the
242 242 local working copy *only*, and one with entries that are shared across
243 243 multiple working copies when shared using the share extension.
244 244
245 245 Entries are stored with NUL bytes as separators. See the journalentry
246 246 class for the per-entry structure.
247 247
248 248 The file format starts with an integer version, delimited by a NUL.
249 249
250 250 This storage uses a dedicated lock; this makes it easier to avoid issues
251 251 with adding entries that added when the regular wlock is unlocked (e.g.
252 252 the dirstate).
253 253
254 254 """
255 255 _currentcommand = ()
256 256 _lockref = None
257 257
258 258 def __init__(self, repo):
259 259 self.user = procutil.getuser()
260 260 self.ui = repo.ui
261 261 self.vfs = repo.vfs
262 262
263 263 # is this working copy using a shared storage?
264 264 self.sharedfeatures = self.sharedvfs = None
265 265 if repo.shared():
266 266 features = _readsharedfeatures(repo)
267 267 sharedrepo = hg.sharedreposource(repo)
268 268 if sharedrepo is not None and 'journal' in features:
269 269 self.sharedvfs = sharedrepo.vfs
270 270 self.sharedfeatures = features
271 271
272 272 # track the current command for recording in journal entries
273 273 @property
274 274 def command(self):
275 275 commandstr = ' '.join(
276 276 map(procutil.shellquote, journalstorage._currentcommand))
277 277 if '\n' in commandstr:
278 278 # truncate multi-line commands
279 279 commandstr = commandstr.partition('\n')[0] + ' ...'
280 280 return commandstr
281 281
282 282 @classmethod
283 283 def recordcommand(cls, *fullargs):
284 284 """Set the current hg arguments, stored with recorded entries"""
285 285 # Set the current command on the class because we may have started
286 286 # with a non-local repo (cloning for example).
287 287 cls._currentcommand = fullargs
288 288
289 289 def _currentlock(self, lockref):
290 290 """Returns the lock if it's held, or None if it's not.
291 291
292 292 (This is copied from the localrepo class)
293 293 """
294 294 if lockref is None:
295 295 return None
296 296 l = lockref()
297 297 if l is None or not l.held:
298 298 return None
299 299 return l
300 300
301 301 def jlock(self, vfs):
302 302 """Create a lock for the journal file"""
303 303 if self._currentlock(self._lockref) is not None:
304 304 raise error.Abort(_('journal lock does not support nesting'))
305 305 desc = _('journal of %s') % vfs.base
306 306 try:
307 307 l = lock.lock(vfs, 'namejournal.lock', 0, desc=desc)
308 308 except error.LockHeld as inst:
309 309 self.ui.warn(
310 310 _("waiting for lock on %s held by %r\n") % (desc, inst.locker))
311 311 # default to 600 seconds timeout
312 312 l = lock.lock(
313 313 vfs, 'namejournal.lock',
314 314 self.ui.configint("ui", "timeout"), desc=desc)
315 315 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
316 316 self._lockref = weakref.ref(l)
317 317 return l
318 318
319 319 def record(self, namespace, name, oldhashes, newhashes):
320 320 """Record a new journal entry
321 321
322 322 * namespace: an opaque string; this can be used to filter on the type
323 323 of recorded entries.
324 324 * name: the name defining this entry; for bookmarks, this is the
325 325 bookmark name. Can be filtered on when retrieving entries.
326 326 * oldhashes and newhashes: each a single binary hash, or a list of
327 327 binary hashes. These represent the old and new position of the named
328 328 item.
329 329
330 330 """
331 331 if not isinstance(oldhashes, list):
332 332 oldhashes = [oldhashes]
333 333 if not isinstance(newhashes, list):
334 334 newhashes = [newhashes]
335 335
336 336 entry = journalentry(
337 337 dateutil.makedate(), self.user, self.command, namespace, name,
338 338 oldhashes, newhashes)
339 339
340 340 vfs = self.vfs
341 341 if self.sharedvfs is not None:
342 342 # write to the shared repository if this feature is being
343 343 # shared between working copies.
344 344 if sharednamespaces.get(namespace) in self.sharedfeatures:
345 345 vfs = self.sharedvfs
346 346
347 347 self._write(vfs, entry)
348 348
349 349 def _write(self, vfs, entry):
350 350 with self.jlock(vfs):
351 351 version = None
352 352 # open file in amend mode to ensure it is created if missing
353 353 with vfs('namejournal', mode='a+b') as f:
354 354 f.seek(0, os.SEEK_SET)
355 355 # Read just enough bytes to get a version number (up to 2
356 356 # digits plus separator)
357 357 version = f.read(3).partition('\0')[0]
358 358 if version and version != "%d" % storageversion:
359 359 # different version of the storage. Exit early (and not
360 360 # write anything) if this is not a version we can handle or
361 361 # the file is corrupt. In future, perhaps rotate the file
362 362 # instead?
363 363 self.ui.warn(
364 364 _("unsupported journal file version '%s'\n") % version)
365 365 return
366 366 if not version:
367 367 # empty file, write version first
368 368 f.write(("%d" % storageversion) + '\0')
369 369 f.seek(0, os.SEEK_END)
370 370 f.write(bytes(entry) + '\0')
371 371
372 372 def filtered(self, namespace=None, name=None):
373 373 """Yield all journal entries with the given namespace or name
374 374
375 375 Both the namespace and the name are optional; if neither is given all
376 376 entries in the journal are produced.
377 377
378 378 Matching supports regular expressions by using the `re:` prefix
379 379 (use `literal:` to match names or namespaces that start with `re:`)
380 380
381 381 """
382 382 if namespace is not None:
383 383 namespace = stringutil.stringmatcher(namespace)[-1]
384 384 if name is not None:
385 385 name = stringutil.stringmatcher(name)[-1]
386 386 for entry in self:
387 387 if namespace is not None and not namespace(entry.namespace):
388 388 continue
389 389 if name is not None and not name(entry.name):
390 390 continue
391 391 yield entry
392 392
393 393 def __iter__(self):
394 394 """Iterate over the storage
395 395
396 396 Yields journalentry instances for each contained journal record.
397 397
398 398 """
399 399 local = self._open(self.vfs)
400 400
401 401 if self.sharedvfs is None:
402 402 return local
403 403
404 404 # iterate over both local and shared entries, but only those
405 405 # shared entries that are among the currently shared features
406 406 shared = (
407 407 e for e in self._open(self.sharedvfs)
408 408 if sharednamespaces.get(e.namespace) in self.sharedfeatures)
409 409 return _mergeentriesiter(local, shared)
410 410
411 411 def _open(self, vfs, filename='namejournal', _newestfirst=True):
412 412 if not vfs.exists(filename):
413 413 return
414 414
415 415 with vfs(filename) as f:
416 416 raw = f.read()
417 417
418 418 lines = raw.split('\0')
419 419 version = lines and lines[0]
420 420 if version != "%d" % storageversion:
421 421 version = version or _('not available')
422 422 raise error.Abort(_("unknown journal file version '%s'") % version)
423 423
424 424 # Skip the first line, it's a version number. Normally we iterate over
425 425 # these in reverse order to list newest first; only when copying across
426 426 # a shared storage do we forgo reversing.
427 427 lines = lines[1:]
428 428 if _newestfirst:
429 429 lines = reversed(lines)
430 430 for line in lines:
431 431 if not line:
432 432 continue
433 433 yield journalentry.fromstorage(line)
434 434
435 435 # journal reading
436 436 # log options that don't make sense for journal
437 437 _ignoreopts = ('no-merges', 'graph')
438 438 @command(
439 439 'journal', [
440 440 ('', 'all', None, 'show history for all names'),
441 441 ('c', 'commits', None, 'show commit metadata'),
442 442 ] + [opt for opt in cmdutil.logopts if opt[1] not in _ignoreopts],
443 443 '[OPTION]... [BOOKMARKNAME]')
444 444 def journal(ui, repo, *args, **opts):
445 445 """show the previous position of bookmarks and the working copy
446 446
447 447 The journal is used to see the previous commits that bookmarks and the
448 448 working copy pointed to. By default the previous locations for the working
449 449 copy. Passing a bookmark name will show all the previous positions of
450 450 that bookmark. Use the --all switch to show previous locations for all
451 451 bookmarks and the working copy; each line will then include the bookmark
452 452 name, or '.' for the working copy, as well.
453 453
454 454 If `name` starts with `re:`, the remainder of the name is treated as
455 455 a regular expression. To match a name that actually starts with `re:`,
456 456 use the prefix `literal:`.
457 457
458 458 By default hg journal only shows the commit hash and the command that was
459 459 running at that time. -v/--verbose will show the prior hash, the user, and
460 460 the time at which it happened.
461 461
462 462 Use -c/--commits to output log information on each commit hash; at this
463 463 point you can use the usual `--patch`, `--git`, `--stat` and `--template`
464 464 switches to alter the log output for these.
465 465
466 466 `hg journal -T json` can be used to produce machine readable output.
467 467
468 468 """
469 469 opts = pycompat.byteskwargs(opts)
470 470 name = '.'
471 471 if opts.get('all'):
472 472 if args:
473 473 raise error.Abort(
474 474 _("You can't combine --all and filtering on a name"))
475 475 name = None
476 476 if args:
477 477 name = args[0]
478 478
479 479 fm = ui.formatter('journal', opts)
480 480 def formatnodes(nodes):
481 481 return fm.formatlist(map(fm.hexfunc, nodes), name='node', sep=',')
482 482
483 483 if opts.get("template") != "json":
484 484 if name is None:
485 485 displayname = _('the working copy and bookmarks')
486 486 else:
487 487 displayname = "'%s'" % name
488 488 ui.status(_("previous locations of %s:\n") % displayname)
489 489
490 490 limit = logcmdutil.getlimit(opts)
491 491 entry = None
492 492 ui.pager('journal')
493 493 for count, entry in enumerate(repo.journal.filtered(name=name)):
494 494 if count == limit:
495 495 break
496 496
497 497 fm.startitem()
498 498 fm.condwrite(ui.verbose, 'oldnodes', '%s -> ',
499 499 formatnodes(entry.oldhashes))
500 500 fm.write('newnodes', '%s', formatnodes(entry.newhashes))
501 501 fm.condwrite(ui.verbose, 'user', ' %-8s', entry.user)
502 502 fm.condwrite(
503 503 opts.get('all') or name.startswith('re:'),
504 504 'name', ' %-8s', entry.name)
505 505
506 506 fm.condwrite(ui.verbose, 'date', ' %s',
507 507 fm.formatdate(entry.timestamp, '%Y-%m-%d %H:%M %1%2'))
508 508 fm.write('command', ' %s\n', entry.command)
509 509
510 510 if opts.get("commits"):
511 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
511 if fm.isplain():
512 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
513 else:
514 displayer = logcmdutil.changesetformatter(
515 ui, repo, fm.nested('changesets'), diffopts=opts)
512 516 for hash in entry.newhashes:
513 517 try:
514 518 ctx = repo[hash]
515 519 displayer.show(ctx)
516 520 except error.RepoLookupError as e:
517 521 fm.plain("%s\n\n" % pycompat.bytestr(e))
518 522 displayer.close()
519 523
520 524 fm.end()
521 525
522 526 if entry is None:
523 527 ui.status(_("no recorded locations\n"))
@@ -1,242 +1,298 b''
1 1 Tests for the journal extension; records bookmark locations.
2 2
3 3 $ cat >> testmocks.py << EOF
4 4 > # mock out procutil.getuser() and util.makedate() to supply testable values
5 5 > import os
6 6 > from mercurial import util, pycompat
7 7 > from mercurial.utils import dateutil, procutil
8 8 > def mockgetuser():
9 9 > return b'foobar'
10 10 >
11 11 > def mockmakedate():
12 12 > filename = os.path.join(os.environ['TESTTMP'], 'testtime')
13 13 > try:
14 14 > with open(filename, 'rb') as timef:
15 15 > time = float(timef.read()) + 1
16 16 > except IOError:
17 17 > time = 0.0
18 18 > with open(filename, 'wb') as timef:
19 19 > timef.write(pycompat.bytestr(time))
20 20 > return (time, 0)
21 21 >
22 22 > procutil.getuser = mockgetuser
23 23 > dateutil.makedate = mockmakedate
24 24 > EOF
25 25
26 26 $ cat >> $HGRCPATH << EOF
27 27 > [extensions]
28 28 > journal=
29 29 > testmocks=`pwd`/testmocks.py
30 30 > EOF
31 31
32 32 Setup repo
33 33
34 34 $ hg init repo
35 35 $ cd repo
36 36
37 37 Test empty journal
38 38
39 39 $ hg journal
40 40 previous locations of '.':
41 41 no recorded locations
42 42 $ hg journal foo
43 43 previous locations of 'foo':
44 44 no recorded locations
45 45
46 46 Test that working copy changes are tracked
47 47
48 48 $ echo a > a
49 49 $ hg commit -Aqm a
50 50 $ hg journal
51 51 previous locations of '.':
52 52 cb9a9f314b8b commit -Aqm a
53 53 $ echo b > a
54 54 $ hg commit -Aqm b
55 55 $ hg journal
56 56 previous locations of '.':
57 57 1e6c11564562 commit -Aqm b
58 58 cb9a9f314b8b commit -Aqm a
59 59 $ hg up 0
60 60 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 61 $ hg journal
62 62 previous locations of '.':
63 63 cb9a9f314b8b up 0
64 64 1e6c11564562 commit -Aqm b
65 65 cb9a9f314b8b commit -Aqm a
66 66
67 67 Test that bookmarks are tracked
68 68
69 69 $ hg book -r tip bar
70 70 $ hg journal bar
71 71 previous locations of 'bar':
72 72 1e6c11564562 book -r tip bar
73 73 $ hg book -f bar
74 74 $ hg journal bar
75 75 previous locations of 'bar':
76 76 cb9a9f314b8b book -f bar
77 77 1e6c11564562 book -r tip bar
78 78 $ hg up
79 79 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 80 updating bookmark bar
81 81 $ hg journal bar
82 82 previous locations of 'bar':
83 83 1e6c11564562 up
84 84 cb9a9f314b8b book -f bar
85 85 1e6c11564562 book -r tip bar
86 86
87 87 Test that bookmarks and working copy tracking is not mixed
88 88
89 89 $ hg journal
90 90 previous locations of '.':
91 91 1e6c11564562 up
92 92 cb9a9f314b8b up 0
93 93 1e6c11564562 commit -Aqm b
94 94 cb9a9f314b8b commit -Aqm a
95 95
96 96 Test that you can list all entries as well as limit the list or filter on them
97 97
98 98 $ hg book -r tip baz
99 99 $ hg journal --all
100 100 previous locations of the working copy and bookmarks:
101 101 1e6c11564562 baz book -r tip baz
102 102 1e6c11564562 bar up
103 103 1e6c11564562 . up
104 104 cb9a9f314b8b bar book -f bar
105 105 1e6c11564562 bar book -r tip bar
106 106 cb9a9f314b8b . up 0
107 107 1e6c11564562 . commit -Aqm b
108 108 cb9a9f314b8b . commit -Aqm a
109 109 $ hg journal --limit 2
110 110 previous locations of '.':
111 111 1e6c11564562 up
112 112 cb9a9f314b8b up 0
113 113 $ hg journal bar
114 114 previous locations of 'bar':
115 115 1e6c11564562 up
116 116 cb9a9f314b8b book -f bar
117 117 1e6c11564562 book -r tip bar
118 118 $ hg journal foo
119 119 previous locations of 'foo':
120 120 no recorded locations
121 121 $ hg journal .
122 122 previous locations of '.':
123 123 1e6c11564562 up
124 124 cb9a9f314b8b up 0
125 125 1e6c11564562 commit -Aqm b
126 126 cb9a9f314b8b commit -Aqm a
127 127 $ hg journal "re:ba."
128 128 previous locations of 're:ba.':
129 129 1e6c11564562 baz book -r tip baz
130 130 1e6c11564562 bar up
131 131 cb9a9f314b8b bar book -f bar
132 132 1e6c11564562 bar book -r tip bar
133 133
134 134 Test that verbose, JSON, template and commit output work
135 135
136 136 $ hg journal --verbose --all
137 137 previous locations of the working copy and bookmarks:
138 138 000000000000 -> 1e6c11564562 foobar baz 1970-01-01 00:00 +0000 book -r tip baz
139 139 cb9a9f314b8b -> 1e6c11564562 foobar bar 1970-01-01 00:00 +0000 up
140 140 cb9a9f314b8b -> 1e6c11564562 foobar . 1970-01-01 00:00 +0000 up
141 141 1e6c11564562 -> cb9a9f314b8b foobar bar 1970-01-01 00:00 +0000 book -f bar
142 142 000000000000 -> 1e6c11564562 foobar bar 1970-01-01 00:00 +0000 book -r tip bar
143 143 1e6c11564562 -> cb9a9f314b8b foobar . 1970-01-01 00:00 +0000 up 0
144 144 cb9a9f314b8b -> 1e6c11564562 foobar . 1970-01-01 00:00 +0000 commit -Aqm b
145 145 000000000000 -> cb9a9f314b8b foobar . 1970-01-01 00:00 +0000 commit -Aqm a
146 146 $ hg journal --verbose -Tjson
147 147 [
148 148 {
149 149 "command": "up",
150 150 "date": [5, 0],
151 151 "name": ".",
152 152 "newnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
153 153 "oldnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
154 154 "user": "foobar"
155 155 },
156 156 {
157 157 "command": "up 0",
158 158 "date": [2, 0],
159 159 "name": ".",
160 160 "newnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
161 161 "oldnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
162 162 "user": "foobar"
163 163 },
164 164 {
165 165 "command": "commit -Aqm b",
166 166 "date": [1, 0],
167 167 "name": ".",
168 168 "newnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
169 169 "oldnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
170 170 "user": "foobar"
171 171 },
172 172 {
173 173 "command": "commit -Aqm a",
174 174 "date": [0, 0],
175 175 "name": ".",
176 176 "newnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
177 177 "oldnodes": ["0000000000000000000000000000000000000000"],
178 178 "user": "foobar"
179 179 }
180 180 ]
181 181
182 182 $ cat <<EOF >> $HGRCPATH
183 183 > [templates]
184 184 > j = "{oldnodes % '{node|upper}'} -> {newnodes % '{node|upper}'}
185 185 > - user: {user}
186 186 > - command: {command}
187 187 > - date: {date|rfc3339date}
188 188 > - newnodes: {newnodes}
189 189 > - oldnodes: {oldnodes}
190 190 > "
191 191 > EOF
192 192 $ hg journal -Tj -l1
193 193 previous locations of '.':
194 194 CB9A9F314B8B07BA71012FCDBC544B5A4D82FF5B -> 1E6C11564562B4ED919BACA798BC4338BD299D6A
195 195 - user: foobar
196 196 - command: up
197 197 - date: 1970-01-01T00:00:05+00:00
198 198 - newnodes: 1e6c11564562b4ed919baca798bc4338bd299d6a
199 199 - oldnodes: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
200 200
201 201 $ hg journal --commit
202 202 previous locations of '.':
203 203 1e6c11564562 up
204 204 changeset: 1:1e6c11564562
205 205 bookmark: bar
206 206 bookmark: baz
207 207 tag: tip
208 208 user: test
209 209 date: Thu Jan 01 00:00:00 1970 +0000
210 210 summary: b
211 211
212 212 cb9a9f314b8b up 0
213 213 changeset: 0:cb9a9f314b8b
214 214 user: test
215 215 date: Thu Jan 01 00:00:00 1970 +0000
216 216 summary: a
217 217
218 218 1e6c11564562 commit -Aqm b
219 219 changeset: 1:1e6c11564562
220 220 bookmark: bar
221 221 bookmark: baz
222 222 tag: tip
223 223 user: test
224 224 date: Thu Jan 01 00:00:00 1970 +0000
225 225 summary: b
226 226
227 227 cb9a9f314b8b commit -Aqm a
228 228 changeset: 0:cb9a9f314b8b
229 229 user: test
230 230 date: Thu Jan 01 00:00:00 1970 +0000
231 231 summary: a
232 232
233 233
234 $ hg journal --commit -Tjson
235 [
236 {
237 "changesets": [{"bookmarks": ["bar", "baz"], "branch": "default", "date": [0, 0], "desc": "b", "node": "1e6c11564562b4ed919baca798bc4338bd299d6a", "parents": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"], "phase": "draft", "rev": 1, "tags": ["tip"], "user": "test"}],
238 "command": "up",
239 "date": [5, 0],
240 "name": ".",
241 "newnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
242 "oldnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
243 "user": "foobar"
244 },
245 {
246 "changesets": [{"bookmarks": [], "branch": "default", "date": [0, 0], "desc": "a", "node": "cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b", "parents": ["0000000000000000000000000000000000000000"], "phase": "draft", "rev": 0, "tags": [], "user": "test"}],
247 "command": "up 0",
248 "date": [2, 0],
249 "name": ".",
250 "newnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
251 "oldnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
252 "user": "foobar"
253 },
254 {
255 "changesets": [{"bookmarks": ["bar", "baz"], "branch": "default", "date": [0, 0], "desc": "b", "node": "1e6c11564562b4ed919baca798bc4338bd299d6a", "parents": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"], "phase": "draft", "rev": 1, "tags": ["tip"], "user": "test"}],
256 "command": "commit -Aqm b",
257 "date": [1, 0],
258 "name": ".",
259 "newnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
260 "oldnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
261 "user": "foobar"
262 },
263 {
264 "changesets": [{"bookmarks": [], "branch": "default", "date": [0, 0], "desc": "a", "node": "cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b", "parents": ["0000000000000000000000000000000000000000"], "phase": "draft", "rev": 0, "tags": [], "user": "test"}],
265 "command": "commit -Aqm a",
266 "date": [0, 0],
267 "name": ".",
268 "newnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
269 "oldnodes": ["0000000000000000000000000000000000000000"],
270 "user": "foobar"
271 }
272 ]
273
274 $ hg journal --commit \
275 > -T'command: {command}\n{changesets % " rev: {rev}\n children: {children}\n"}'
276 previous locations of '.':
277 command: up
278 rev: 1
279 children:
280 command: up 0
281 rev: 0
282 children:
283 command: commit -Aqm b
284 rev: 1
285 children:
286 command: commit -Aqm a
287 rev: 0
288 children:
289
234 290 Test for behaviour on unexpected storage version information
235 291
236 292 $ printf '42\0' > .hg/namejournal
237 293 $ hg journal
238 294 previous locations of '.':
239 295 abort: unknown journal file version '42'
240 296 [255]
241 297 $ hg book -r tip doomed
242 298 unsupported journal file version '42'
General Comments 0
You need to be logged in to leave comments. Login now