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