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