##// END OF EJS Templates
log: add a "graphwidth" template variable...
Danny Hooper -
r33860:6f6c8788 default
parent child Browse files
Show More
@@ -1,3862 +1,3866 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
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
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14 import tempfile
14 import tempfile
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23
23
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 changelog,
26 changelog,
27 copies,
27 copies,
28 crecord as crecordmod,
28 crecord as crecordmod,
29 dirstateguard,
29 dirstateguard,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 graphmod,
33 graphmod,
34 match as matchmod,
34 match as matchmod,
35 obsolete,
35 obsolete,
36 patch,
36 patch,
37 pathutil,
37 pathutil,
38 phases,
38 phases,
39 pycompat,
39 pycompat,
40 registrar,
40 registrar,
41 revlog,
41 revlog,
42 revset,
42 revset,
43 scmutil,
43 scmutil,
44 smartset,
44 smartset,
45 templatekw,
45 templatekw,
46 templater,
46 templater,
47 util,
47 util,
48 vfs as vfsmod,
48 vfs as vfsmod,
49 )
49 )
50 stringio = util.stringio
50 stringio = util.stringio
51
51
52 # templates of common command options
52 # templates of common command options
53
53
54 dryrunopts = [
54 dryrunopts = [
55 ('n', 'dry-run', None,
55 ('n', 'dry-run', None,
56 _('do not perform actions, just print output')),
56 _('do not perform actions, just print output')),
57 ]
57 ]
58
58
59 remoteopts = [
59 remoteopts = [
60 ('e', 'ssh', '',
60 ('e', 'ssh', '',
61 _('specify ssh command to use'), _('CMD')),
61 _('specify ssh command to use'), _('CMD')),
62 ('', 'remotecmd', '',
62 ('', 'remotecmd', '',
63 _('specify hg command to run on the remote side'), _('CMD')),
63 _('specify hg command to run on the remote side'), _('CMD')),
64 ('', 'insecure', None,
64 ('', 'insecure', None,
65 _('do not verify server certificate (ignoring web.cacerts config)')),
65 _('do not verify server certificate (ignoring web.cacerts config)')),
66 ]
66 ]
67
67
68 walkopts = [
68 walkopts = [
69 ('I', 'include', [],
69 ('I', 'include', [],
70 _('include names matching the given patterns'), _('PATTERN')),
70 _('include names matching the given patterns'), _('PATTERN')),
71 ('X', 'exclude', [],
71 ('X', 'exclude', [],
72 _('exclude names matching the given patterns'), _('PATTERN')),
72 _('exclude names matching the given patterns'), _('PATTERN')),
73 ]
73 ]
74
74
75 commitopts = [
75 commitopts = [
76 ('m', 'message', '',
76 ('m', 'message', '',
77 _('use text as commit message'), _('TEXT')),
77 _('use text as commit message'), _('TEXT')),
78 ('l', 'logfile', '',
78 ('l', 'logfile', '',
79 _('read commit message from file'), _('FILE')),
79 _('read commit message from file'), _('FILE')),
80 ]
80 ]
81
81
82 commitopts2 = [
82 commitopts2 = [
83 ('d', 'date', '',
83 ('d', 'date', '',
84 _('record the specified date as commit date'), _('DATE')),
84 _('record the specified date as commit date'), _('DATE')),
85 ('u', 'user', '',
85 ('u', 'user', '',
86 _('record the specified user as committer'), _('USER')),
86 _('record the specified user as committer'), _('USER')),
87 ]
87 ]
88
88
89 # hidden for now
89 # hidden for now
90 formatteropts = [
90 formatteropts = [
91 ('T', 'template', '',
91 ('T', 'template', '',
92 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
92 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
93 ]
93 ]
94
94
95 templateopts = [
95 templateopts = [
96 ('', 'style', '',
96 ('', 'style', '',
97 _('display using template map file (DEPRECATED)'), _('STYLE')),
97 _('display using template map file (DEPRECATED)'), _('STYLE')),
98 ('T', 'template', '',
98 ('T', 'template', '',
99 _('display with template'), _('TEMPLATE')),
99 _('display with template'), _('TEMPLATE')),
100 ]
100 ]
101
101
102 logopts = [
102 logopts = [
103 ('p', 'patch', None, _('show patch')),
103 ('p', 'patch', None, _('show patch')),
104 ('g', 'git', None, _('use git extended diff format')),
104 ('g', 'git', None, _('use git extended diff format')),
105 ('l', 'limit', '',
105 ('l', 'limit', '',
106 _('limit number of changes displayed'), _('NUM')),
106 _('limit number of changes displayed'), _('NUM')),
107 ('M', 'no-merges', None, _('do not show merges')),
107 ('M', 'no-merges', None, _('do not show merges')),
108 ('', 'stat', None, _('output diffstat-style summary of changes')),
108 ('', 'stat', None, _('output diffstat-style summary of changes')),
109 ('G', 'graph', None, _("show the revision DAG")),
109 ('G', 'graph', None, _("show the revision DAG")),
110 ] + templateopts
110 ] + templateopts
111
111
112 diffopts = [
112 diffopts = [
113 ('a', 'text', None, _('treat all files as text')),
113 ('a', 'text', None, _('treat all files as text')),
114 ('g', 'git', None, _('use git extended diff format')),
114 ('g', 'git', None, _('use git extended diff format')),
115 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
115 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
116 ('', 'nodates', None, _('omit dates from diff headers'))
116 ('', 'nodates', None, _('omit dates from diff headers'))
117 ]
117 ]
118
118
119 diffwsopts = [
119 diffwsopts = [
120 ('w', 'ignore-all-space', None,
120 ('w', 'ignore-all-space', None,
121 _('ignore white space when comparing lines')),
121 _('ignore white space when comparing lines')),
122 ('b', 'ignore-space-change', None,
122 ('b', 'ignore-space-change', None,
123 _('ignore changes in the amount of white space')),
123 _('ignore changes in the amount of white space')),
124 ('B', 'ignore-blank-lines', None,
124 ('B', 'ignore-blank-lines', None,
125 _('ignore changes whose lines are all blank')),
125 _('ignore changes whose lines are all blank')),
126 ]
126 ]
127
127
128 diffopts2 = [
128 diffopts2 = [
129 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
129 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
130 ('p', 'show-function', None, _('show which function each change is in')),
130 ('p', 'show-function', None, _('show which function each change is in')),
131 ('', 'reverse', None, _('produce a diff that undoes the changes')),
131 ('', 'reverse', None, _('produce a diff that undoes the changes')),
132 ] + diffwsopts + [
132 ] + diffwsopts + [
133 ('U', 'unified', '',
133 ('U', 'unified', '',
134 _('number of lines of context to show'), _('NUM')),
134 _('number of lines of context to show'), _('NUM')),
135 ('', 'stat', None, _('output diffstat-style summary of changes')),
135 ('', 'stat', None, _('output diffstat-style summary of changes')),
136 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
136 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
137 ]
137 ]
138
138
139 mergetoolopts = [
139 mergetoolopts = [
140 ('t', 'tool', '', _('specify merge tool')),
140 ('t', 'tool', '', _('specify merge tool')),
141 ]
141 ]
142
142
143 similarityopts = [
143 similarityopts = [
144 ('s', 'similarity', '',
144 ('s', 'similarity', '',
145 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
145 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
146 ]
146 ]
147
147
148 subrepoopts = [
148 subrepoopts = [
149 ('S', 'subrepos', None,
149 ('S', 'subrepos', None,
150 _('recurse into subrepositories'))
150 _('recurse into subrepositories'))
151 ]
151 ]
152
152
153 debugrevlogopts = [
153 debugrevlogopts = [
154 ('c', 'changelog', False, _('open changelog')),
154 ('c', 'changelog', False, _('open changelog')),
155 ('m', 'manifest', False, _('open manifest')),
155 ('m', 'manifest', False, _('open manifest')),
156 ('', 'dir', '', _('open directory manifest')),
156 ('', 'dir', '', _('open directory manifest')),
157 ]
157 ]
158
158
159 # special string such that everything below this line will be ingored in the
159 # special string such that everything below this line will be ingored in the
160 # editor text
160 # editor text
161 _linebelow = "^HG: ------------------------ >8 ------------------------$"
161 _linebelow = "^HG: ------------------------ >8 ------------------------$"
162
162
163 def ishunk(x):
163 def ishunk(x):
164 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
164 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
165 return isinstance(x, hunkclasses)
165 return isinstance(x, hunkclasses)
166
166
167 def newandmodified(chunks, originalchunks):
167 def newandmodified(chunks, originalchunks):
168 newlyaddedandmodifiedfiles = set()
168 newlyaddedandmodifiedfiles = set()
169 for chunk in chunks:
169 for chunk in chunks:
170 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
170 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
171 originalchunks:
171 originalchunks:
172 newlyaddedandmodifiedfiles.add(chunk.header.filename())
172 newlyaddedandmodifiedfiles.add(chunk.header.filename())
173 return newlyaddedandmodifiedfiles
173 return newlyaddedandmodifiedfiles
174
174
175 def parsealiases(cmd):
175 def parsealiases(cmd):
176 return cmd.lstrip("^").split("|")
176 return cmd.lstrip("^").split("|")
177
177
178 def setupwrapcolorwrite(ui):
178 def setupwrapcolorwrite(ui):
179 # wrap ui.write so diff output can be labeled/colorized
179 # wrap ui.write so diff output can be labeled/colorized
180 def wrapwrite(orig, *args, **kw):
180 def wrapwrite(orig, *args, **kw):
181 label = kw.pop('label', '')
181 label = kw.pop('label', '')
182 for chunk, l in patch.difflabel(lambda: args):
182 for chunk, l in patch.difflabel(lambda: args):
183 orig(chunk, label=label + l)
183 orig(chunk, label=label + l)
184
184
185 oldwrite = ui.write
185 oldwrite = ui.write
186 def wrap(*args, **kwargs):
186 def wrap(*args, **kwargs):
187 return wrapwrite(oldwrite, *args, **kwargs)
187 return wrapwrite(oldwrite, *args, **kwargs)
188 setattr(ui, 'write', wrap)
188 setattr(ui, 'write', wrap)
189 return oldwrite
189 return oldwrite
190
190
191 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
191 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
192 if usecurses:
192 if usecurses:
193 if testfile:
193 if testfile:
194 recordfn = crecordmod.testdecorator(testfile,
194 recordfn = crecordmod.testdecorator(testfile,
195 crecordmod.testchunkselector)
195 crecordmod.testchunkselector)
196 else:
196 else:
197 recordfn = crecordmod.chunkselector
197 recordfn = crecordmod.chunkselector
198
198
199 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
199 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
200
200
201 else:
201 else:
202 return patch.filterpatch(ui, originalhunks, operation)
202 return patch.filterpatch(ui, originalhunks, operation)
203
203
204 def recordfilter(ui, originalhunks, operation=None):
204 def recordfilter(ui, originalhunks, operation=None):
205 """ Prompts the user to filter the originalhunks and return a list of
205 """ Prompts the user to filter the originalhunks and return a list of
206 selected hunks.
206 selected hunks.
207 *operation* is used for to build ui messages to indicate the user what
207 *operation* is used for to build ui messages to indicate the user what
208 kind of filtering they are doing: reverting, committing, shelving, etc.
208 kind of filtering they are doing: reverting, committing, shelving, etc.
209 (see patch.filterpatch).
209 (see patch.filterpatch).
210 """
210 """
211 usecurses = crecordmod.checkcurses(ui)
211 usecurses = crecordmod.checkcurses(ui)
212 testfile = ui.config('experimental', 'crecordtest')
212 testfile = ui.config('experimental', 'crecordtest')
213 oldwrite = setupwrapcolorwrite(ui)
213 oldwrite = setupwrapcolorwrite(ui)
214 try:
214 try:
215 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
215 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
216 testfile, operation)
216 testfile, operation)
217 finally:
217 finally:
218 ui.write = oldwrite
218 ui.write = oldwrite
219 return newchunks, newopts
219 return newchunks, newopts
220
220
221 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
221 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
222 filterfn, *pats, **opts):
222 filterfn, *pats, **opts):
223 from . import merge as mergemod
223 from . import merge as mergemod
224 opts = pycompat.byteskwargs(opts)
224 opts = pycompat.byteskwargs(opts)
225 if not ui.interactive():
225 if not ui.interactive():
226 if cmdsuggest:
226 if cmdsuggest:
227 msg = _('running non-interactively, use %s instead') % cmdsuggest
227 msg = _('running non-interactively, use %s instead') % cmdsuggest
228 else:
228 else:
229 msg = _('running non-interactively')
229 msg = _('running non-interactively')
230 raise error.Abort(msg)
230 raise error.Abort(msg)
231
231
232 # make sure username is set before going interactive
232 # make sure username is set before going interactive
233 if not opts.get('user'):
233 if not opts.get('user'):
234 ui.username() # raise exception, username not provided
234 ui.username() # raise exception, username not provided
235
235
236 def recordfunc(ui, repo, message, match, opts):
236 def recordfunc(ui, repo, message, match, opts):
237 """This is generic record driver.
237 """This is generic record driver.
238
238
239 Its job is to interactively filter local changes, and
239 Its job is to interactively filter local changes, and
240 accordingly prepare working directory into a state in which the
240 accordingly prepare working directory into a state in which the
241 job can be delegated to a non-interactive commit command such as
241 job can be delegated to a non-interactive commit command such as
242 'commit' or 'qrefresh'.
242 'commit' or 'qrefresh'.
243
243
244 After the actual job is done by non-interactive command, the
244 After the actual job is done by non-interactive command, the
245 working directory is restored to its original state.
245 working directory is restored to its original state.
246
246
247 In the end we'll record interesting changes, and everything else
247 In the end we'll record interesting changes, and everything else
248 will be left in place, so the user can continue working.
248 will be left in place, so the user can continue working.
249 """
249 """
250
250
251 checkunfinished(repo, commit=True)
251 checkunfinished(repo, commit=True)
252 wctx = repo[None]
252 wctx = repo[None]
253 merge = len(wctx.parents()) > 1
253 merge = len(wctx.parents()) > 1
254 if merge:
254 if merge:
255 raise error.Abort(_('cannot partially commit a merge '
255 raise error.Abort(_('cannot partially commit a merge '
256 '(use "hg commit" instead)'))
256 '(use "hg commit" instead)'))
257
257
258 def fail(f, msg):
258 def fail(f, msg):
259 raise error.Abort('%s: %s' % (f, msg))
259 raise error.Abort('%s: %s' % (f, msg))
260
260
261 force = opts.get('force')
261 force = opts.get('force')
262 if not force:
262 if not force:
263 vdirs = []
263 vdirs = []
264 match.explicitdir = vdirs.append
264 match.explicitdir = vdirs.append
265 match.bad = fail
265 match.bad = fail
266
266
267 status = repo.status(match=match)
267 status = repo.status(match=match)
268 if not force:
268 if not force:
269 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
269 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
270 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
270 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
271 diffopts.nodates = True
271 diffopts.nodates = True
272 diffopts.git = True
272 diffopts.git = True
273 diffopts.showfunc = True
273 diffopts.showfunc = True
274 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
274 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
275 originalchunks = patch.parsepatch(originaldiff)
275 originalchunks = patch.parsepatch(originaldiff)
276
276
277 # 1. filter patch, since we are intending to apply subset of it
277 # 1. filter patch, since we are intending to apply subset of it
278 try:
278 try:
279 chunks, newopts = filterfn(ui, originalchunks)
279 chunks, newopts = filterfn(ui, originalchunks)
280 except patch.PatchError as err:
280 except patch.PatchError as err:
281 raise error.Abort(_('error parsing patch: %s') % err)
281 raise error.Abort(_('error parsing patch: %s') % err)
282 opts.update(newopts)
282 opts.update(newopts)
283
283
284 # We need to keep a backup of files that have been newly added and
284 # We need to keep a backup of files that have been newly added and
285 # modified during the recording process because there is a previous
285 # modified during the recording process because there is a previous
286 # version without the edit in the workdir
286 # version without the edit in the workdir
287 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
287 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
288 contenders = set()
288 contenders = set()
289 for h in chunks:
289 for h in chunks:
290 try:
290 try:
291 contenders.update(set(h.files()))
291 contenders.update(set(h.files()))
292 except AttributeError:
292 except AttributeError:
293 pass
293 pass
294
294
295 changed = status.modified + status.added + status.removed
295 changed = status.modified + status.added + status.removed
296 newfiles = [f for f in changed if f in contenders]
296 newfiles = [f for f in changed if f in contenders]
297 if not newfiles:
297 if not newfiles:
298 ui.status(_('no changes to record\n'))
298 ui.status(_('no changes to record\n'))
299 return 0
299 return 0
300
300
301 modified = set(status.modified)
301 modified = set(status.modified)
302
302
303 # 2. backup changed files, so we can restore them in the end
303 # 2. backup changed files, so we can restore them in the end
304
304
305 if backupall:
305 if backupall:
306 tobackup = changed
306 tobackup = changed
307 else:
307 else:
308 tobackup = [f for f in newfiles if f in modified or f in \
308 tobackup = [f for f in newfiles if f in modified or f in \
309 newlyaddedandmodifiedfiles]
309 newlyaddedandmodifiedfiles]
310 backups = {}
310 backups = {}
311 if tobackup:
311 if tobackup:
312 backupdir = repo.vfs.join('record-backups')
312 backupdir = repo.vfs.join('record-backups')
313 try:
313 try:
314 os.mkdir(backupdir)
314 os.mkdir(backupdir)
315 except OSError as err:
315 except OSError as err:
316 if err.errno != errno.EEXIST:
316 if err.errno != errno.EEXIST:
317 raise
317 raise
318 try:
318 try:
319 # backup continues
319 # backup continues
320 for f in tobackup:
320 for f in tobackup:
321 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
321 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
322 dir=backupdir)
322 dir=backupdir)
323 os.close(fd)
323 os.close(fd)
324 ui.debug('backup %r as %r\n' % (f, tmpname))
324 ui.debug('backup %r as %r\n' % (f, tmpname))
325 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
325 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
326 backups[f] = tmpname
326 backups[f] = tmpname
327
327
328 fp = stringio()
328 fp = stringio()
329 for c in chunks:
329 for c in chunks:
330 fname = c.filename()
330 fname = c.filename()
331 if fname in backups:
331 if fname in backups:
332 c.write(fp)
332 c.write(fp)
333 dopatch = fp.tell()
333 dopatch = fp.tell()
334 fp.seek(0)
334 fp.seek(0)
335
335
336 # 2.5 optionally review / modify patch in text editor
336 # 2.5 optionally review / modify patch in text editor
337 if opts.get('review', False):
337 if opts.get('review', False):
338 patchtext = (crecordmod.diffhelptext
338 patchtext = (crecordmod.diffhelptext
339 + crecordmod.patchhelptext
339 + crecordmod.patchhelptext
340 + fp.read())
340 + fp.read())
341 reviewedpatch = ui.edit(patchtext, "",
341 reviewedpatch = ui.edit(patchtext, "",
342 extra={"suffix": ".diff"},
342 extra={"suffix": ".diff"},
343 repopath=repo.path)
343 repopath=repo.path)
344 fp.truncate(0)
344 fp.truncate(0)
345 fp.write(reviewedpatch)
345 fp.write(reviewedpatch)
346 fp.seek(0)
346 fp.seek(0)
347
347
348 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
348 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
349 # 3a. apply filtered patch to clean repo (clean)
349 # 3a. apply filtered patch to clean repo (clean)
350 if backups:
350 if backups:
351 # Equivalent to hg.revert
351 # Equivalent to hg.revert
352 m = scmutil.matchfiles(repo, backups.keys())
352 m = scmutil.matchfiles(repo, backups.keys())
353 mergemod.update(repo, repo.dirstate.p1(),
353 mergemod.update(repo, repo.dirstate.p1(),
354 False, True, matcher=m)
354 False, True, matcher=m)
355
355
356 # 3b. (apply)
356 # 3b. (apply)
357 if dopatch:
357 if dopatch:
358 try:
358 try:
359 ui.debug('applying patch\n')
359 ui.debug('applying patch\n')
360 ui.debug(fp.getvalue())
360 ui.debug(fp.getvalue())
361 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
361 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
362 except patch.PatchError as err:
362 except patch.PatchError as err:
363 raise error.Abort(str(err))
363 raise error.Abort(str(err))
364 del fp
364 del fp
365
365
366 # 4. We prepared working directory according to filtered
366 # 4. We prepared working directory according to filtered
367 # patch. Now is the time to delegate the job to
367 # patch. Now is the time to delegate the job to
368 # commit/qrefresh or the like!
368 # commit/qrefresh or the like!
369
369
370 # Make all of the pathnames absolute.
370 # Make all of the pathnames absolute.
371 newfiles = [repo.wjoin(nf) for nf in newfiles]
371 newfiles = [repo.wjoin(nf) for nf in newfiles]
372 return commitfunc(ui, repo, *newfiles, **opts)
372 return commitfunc(ui, repo, *newfiles, **opts)
373 finally:
373 finally:
374 # 5. finally restore backed-up files
374 # 5. finally restore backed-up files
375 try:
375 try:
376 dirstate = repo.dirstate
376 dirstate = repo.dirstate
377 for realname, tmpname in backups.iteritems():
377 for realname, tmpname in backups.iteritems():
378 ui.debug('restoring %r to %r\n' % (tmpname, realname))
378 ui.debug('restoring %r to %r\n' % (tmpname, realname))
379
379
380 if dirstate[realname] == 'n':
380 if dirstate[realname] == 'n':
381 # without normallookup, restoring timestamp
381 # without normallookup, restoring timestamp
382 # may cause partially committed files
382 # may cause partially committed files
383 # to be treated as unmodified
383 # to be treated as unmodified
384 dirstate.normallookup(realname)
384 dirstate.normallookup(realname)
385
385
386 # copystat=True here and above are a hack to trick any
386 # copystat=True here and above are a hack to trick any
387 # editors that have f open that we haven't modified them.
387 # editors that have f open that we haven't modified them.
388 #
388 #
389 # Also note that this racy as an editor could notice the
389 # Also note that this racy as an editor could notice the
390 # file's mtime before we've finished writing it.
390 # file's mtime before we've finished writing it.
391 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
391 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
392 os.unlink(tmpname)
392 os.unlink(tmpname)
393 if tobackup:
393 if tobackup:
394 os.rmdir(backupdir)
394 os.rmdir(backupdir)
395 except OSError:
395 except OSError:
396 pass
396 pass
397
397
398 def recordinwlock(ui, repo, message, match, opts):
398 def recordinwlock(ui, repo, message, match, opts):
399 with repo.wlock():
399 with repo.wlock():
400 return recordfunc(ui, repo, message, match, opts)
400 return recordfunc(ui, repo, message, match, opts)
401
401
402 return commit(ui, repo, recordinwlock, pats, opts)
402 return commit(ui, repo, recordinwlock, pats, opts)
403
403
404 def tersestatus(root, statlist, status, ignorefn, ignore):
404 def tersestatus(root, statlist, status, ignorefn, ignore):
405 """
405 """
406 Returns a list of statuses with directory collapsed if all the files in the
406 Returns a list of statuses with directory collapsed if all the files in the
407 directory has the same status.
407 directory has the same status.
408 """
408 """
409
409
410 def numfiles(dirname):
410 def numfiles(dirname):
411 """
411 """
412 Calculates the number of tracked files in a given directory which also
412 Calculates the number of tracked files in a given directory which also
413 includes files which were removed or deleted. Considers ignored files
413 includes files which were removed or deleted. Considers ignored files
414 if ignore argument is True or 'i' is present in status argument.
414 if ignore argument is True or 'i' is present in status argument.
415 """
415 """
416 if lencache.get(dirname):
416 if lencache.get(dirname):
417 return lencache[dirname]
417 return lencache[dirname]
418 if 'i' in status or ignore:
418 if 'i' in status or ignore:
419 def match(localpath):
419 def match(localpath):
420 absolutepath = os.path.join(root, localpath)
420 absolutepath = os.path.join(root, localpath)
421 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
421 if os.path.isdir(absolutepath) and isemptydir(absolutepath):
422 return True
422 return True
423 return False
423 return False
424 else:
424 else:
425 def match(localpath):
425 def match(localpath):
426 # there can be directory whose all the files are ignored and
426 # there can be directory whose all the files are ignored and
427 # hence the drectory should also be ignored while counting
427 # hence the drectory should also be ignored while counting
428 # number of files or subdirs in it's parent directory. This
428 # number of files or subdirs in it's parent directory. This
429 # checks the same.
429 # checks the same.
430 # XXX: We need a better logic here.
430 # XXX: We need a better logic here.
431 if os.path.isdir(os.path.join(root, localpath)):
431 if os.path.isdir(os.path.join(root, localpath)):
432 return isignoreddir(localpath)
432 return isignoreddir(localpath)
433 else:
433 else:
434 # XXX: there can be files which have the ignored pattern but
434 # XXX: there can be files which have the ignored pattern but
435 # are not ignored. That leads to bug in counting number of
435 # are not ignored. That leads to bug in counting number of
436 # tracked files in the directory.
436 # tracked files in the directory.
437 return ignorefn(localpath)
437 return ignorefn(localpath)
438 lendir = 0
438 lendir = 0
439 abspath = os.path.join(root, dirname)
439 abspath = os.path.join(root, dirname)
440 # There might be cases when a directory does not exists as the whole
440 # There might be cases when a directory does not exists as the whole
441 # directory can be removed and/or deleted.
441 # directory can be removed and/or deleted.
442 try:
442 try:
443 for f in os.listdir(abspath):
443 for f in os.listdir(abspath):
444 localpath = os.path.join(dirname, f)
444 localpath = os.path.join(dirname, f)
445 if not match(localpath):
445 if not match(localpath):
446 lendir += 1
446 lendir += 1
447 except OSError:
447 except OSError:
448 pass
448 pass
449 lendir += len(absentdir.get(dirname, []))
449 lendir += len(absentdir.get(dirname, []))
450 lencache[dirname] = lendir
450 lencache[dirname] = lendir
451 return lendir
451 return lendir
452
452
453 def isemptydir(abspath):
453 def isemptydir(abspath):
454 """
454 """
455 Check whether a directory is empty or not, i.e. there is no files in the
455 Check whether a directory is empty or not, i.e. there is no files in the
456 directory and all its subdirectories.
456 directory and all its subdirectories.
457 """
457 """
458 for f in os.listdir(abspath):
458 for f in os.listdir(abspath):
459 fullpath = os.path.join(abspath, f)
459 fullpath = os.path.join(abspath, f)
460 if os.path.isdir(fullpath):
460 if os.path.isdir(fullpath):
461 # recursion here
461 # recursion here
462 ret = isemptydir(fullpath)
462 ret = isemptydir(fullpath)
463 if not ret:
463 if not ret:
464 return False
464 return False
465 else:
465 else:
466 return False
466 return False
467 return True
467 return True
468
468
469 def isignoreddir(localpath):
469 def isignoreddir(localpath):
470 """Return True if `localpath` directory is ignored or contains only
470 """Return True if `localpath` directory is ignored or contains only
471 ignored files and should hence be considered ignored.
471 ignored files and should hence be considered ignored.
472 """
472 """
473 dirpath = os.path.join(root, localpath)
473 dirpath = os.path.join(root, localpath)
474 if ignorefn(dirpath):
474 if ignorefn(dirpath):
475 return True
475 return True
476 for f in os.listdir(dirpath):
476 for f in os.listdir(dirpath):
477 filepath = os.path.join(dirpath, f)
477 filepath = os.path.join(dirpath, f)
478 if os.path.isdir(filepath):
478 if os.path.isdir(filepath):
479 # recursion here
479 # recursion here
480 ret = isignoreddir(os.path.join(localpath, f))
480 ret = isignoreddir(os.path.join(localpath, f))
481 if not ret:
481 if not ret:
482 return False
482 return False
483 else:
483 else:
484 if not ignorefn(os.path.join(localpath, f)):
484 if not ignorefn(os.path.join(localpath, f)):
485 return False
485 return False
486 return True
486 return True
487
487
488 def absentones(removedfiles, missingfiles):
488 def absentones(removedfiles, missingfiles):
489 """
489 """
490 Returns a dictionary of directories with files in it which are either
490 Returns a dictionary of directories with files in it which are either
491 removed or missing (deleted) in them.
491 removed or missing (deleted) in them.
492 """
492 """
493 absentdir = {}
493 absentdir = {}
494 absentfiles = removedfiles + missingfiles
494 absentfiles = removedfiles + missingfiles
495 while absentfiles:
495 while absentfiles:
496 f = absentfiles.pop()
496 f = absentfiles.pop()
497 par = os.path.dirname(f)
497 par = os.path.dirname(f)
498 if par == '':
498 if par == '':
499 continue
499 continue
500 # we need to store files rather than number of files as some files
500 # we need to store files rather than number of files as some files
501 # or subdirectories in a directory can be counted twice. This is
501 # or subdirectories in a directory can be counted twice. This is
502 # also we have used sets here.
502 # also we have used sets here.
503 try:
503 try:
504 absentdir[par].add(f)
504 absentdir[par].add(f)
505 except KeyError:
505 except KeyError:
506 absentdir[par] = set([f])
506 absentdir[par] = set([f])
507 absentfiles.append(par)
507 absentfiles.append(par)
508 return absentdir
508 return absentdir
509
509
510 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
510 indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
511 # get a dictonary of directories and files which are missing as os.listdir()
511 # get a dictonary of directories and files which are missing as os.listdir()
512 # won't be able to list them.
512 # won't be able to list them.
513 absentdir = absentones(statlist[2], statlist[3])
513 absentdir = absentones(statlist[2], statlist[3])
514 finalrs = [[]] * len(indexes)
514 finalrs = [[]] * len(indexes)
515 didsomethingchanged = False
515 didsomethingchanged = False
516 # dictionary to store number of files and subdir in a directory so that we
516 # dictionary to store number of files and subdir in a directory so that we
517 # don't compute that again.
517 # don't compute that again.
518 lencache = {}
518 lencache = {}
519
519
520 for st in pycompat.bytestr(status):
520 for st in pycompat.bytestr(status):
521
521
522 try:
522 try:
523 ind = indexes[st]
523 ind = indexes[st]
524 except KeyError:
524 except KeyError:
525 # TODO: Need a better error message here
525 # TODO: Need a better error message here
526 raise error.Abort("'%s' not recognized" % st)
526 raise error.Abort("'%s' not recognized" % st)
527
527
528 sfiles = statlist[ind]
528 sfiles = statlist[ind]
529 if not sfiles:
529 if not sfiles:
530 continue
530 continue
531 pardict = {}
531 pardict = {}
532 for a in sfiles:
532 for a in sfiles:
533 par = os.path.dirname(a)
533 par = os.path.dirname(a)
534 pardict.setdefault(par, []).append(a)
534 pardict.setdefault(par, []).append(a)
535
535
536 rs = []
536 rs = []
537 newls = []
537 newls = []
538 for par, files in pardict.iteritems():
538 for par, files in pardict.iteritems():
539 lenpar = numfiles(par)
539 lenpar = numfiles(par)
540 if lenpar == len(files):
540 if lenpar == len(files):
541 newls.append(par)
541 newls.append(par)
542
542
543 if not newls:
543 if not newls:
544 continue
544 continue
545
545
546 while newls:
546 while newls:
547 newel = newls.pop()
547 newel = newls.pop()
548 if newel == '':
548 if newel == '':
549 continue
549 continue
550 parn = os.path.dirname(newel)
550 parn = os.path.dirname(newel)
551 pardict[newel] = []
551 pardict[newel] = []
552 # Adding pycompat.ossep as newel is a directory.
552 # Adding pycompat.ossep as newel is a directory.
553 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
553 pardict.setdefault(parn, []).append(newel + pycompat.ossep)
554 lenpar = numfiles(parn)
554 lenpar = numfiles(parn)
555 if lenpar == len(pardict[parn]):
555 if lenpar == len(pardict[parn]):
556 newls.append(parn)
556 newls.append(parn)
557
557
558 # dict.values() for Py3 compatibility
558 # dict.values() for Py3 compatibility
559 for files in pardict.values():
559 for files in pardict.values():
560 rs.extend(files)
560 rs.extend(files)
561
561
562 rs.sort()
562 rs.sort()
563 finalrs[ind] = rs
563 finalrs[ind] = rs
564 didsomethingchanged = True
564 didsomethingchanged = True
565
565
566 # If nothing is changed, make sure the order of files is preserved.
566 # If nothing is changed, make sure the order of files is preserved.
567 if not didsomethingchanged:
567 if not didsomethingchanged:
568 return statlist
568 return statlist
569
569
570 for x in xrange(len(indexes)):
570 for x in xrange(len(indexes)):
571 if not finalrs[x]:
571 if not finalrs[x]:
572 finalrs[x] = statlist[x]
572 finalrs[x] = statlist[x]
573
573
574 return finalrs
574 return finalrs
575
575
576 def _commentlines(raw):
576 def _commentlines(raw):
577 '''Surround lineswith a comment char and a new line'''
577 '''Surround lineswith a comment char and a new line'''
578 lines = raw.splitlines()
578 lines = raw.splitlines()
579 commentedlines = ['# %s' % line for line in lines]
579 commentedlines = ['# %s' % line for line in lines]
580 return '\n'.join(commentedlines) + '\n'
580 return '\n'.join(commentedlines) + '\n'
581
581
582 def _conflictsmsg(repo):
582 def _conflictsmsg(repo):
583 # avoid merge cycle
583 # avoid merge cycle
584 from . import merge as mergemod
584 from . import merge as mergemod
585 mergestate = mergemod.mergestate.read(repo)
585 mergestate = mergemod.mergestate.read(repo)
586 if not mergestate.active():
586 if not mergestate.active():
587 return
587 return
588
588
589 m = scmutil.match(repo[None])
589 m = scmutil.match(repo[None])
590 unresolvedlist = [f for f in mergestate if m(f) and mergestate[f] == 'u']
590 unresolvedlist = [f for f in mergestate if m(f) and mergestate[f] == 'u']
591 if unresolvedlist:
591 if unresolvedlist:
592 mergeliststr = '\n'.join(
592 mergeliststr = '\n'.join(
593 [' %s' % os.path.relpath(
593 [' %s' % os.path.relpath(
594 os.path.join(repo.root, path),
594 os.path.join(repo.root, path),
595 pycompat.getcwd()) for path in unresolvedlist])
595 pycompat.getcwd()) for path in unresolvedlist])
596 msg = _('''Unresolved merge conflicts:
596 msg = _('''Unresolved merge conflicts:
597
597
598 %s
598 %s
599
599
600 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
600 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
601 else:
601 else:
602 msg = _('No unresolved merge conflicts.')
602 msg = _('No unresolved merge conflicts.')
603
603
604 return _commentlines(msg)
604 return _commentlines(msg)
605
605
606 def _helpmessage(continuecmd, abortcmd):
606 def _helpmessage(continuecmd, abortcmd):
607 msg = _('To continue: %s\n'
607 msg = _('To continue: %s\n'
608 'To abort: %s') % (continuecmd, abortcmd)
608 'To abort: %s') % (continuecmd, abortcmd)
609 return _commentlines(msg)
609 return _commentlines(msg)
610
610
611 def _rebasemsg():
611 def _rebasemsg():
612 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
612 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
613
613
614 def _histeditmsg():
614 def _histeditmsg():
615 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
615 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
616
616
617 def _unshelvemsg():
617 def _unshelvemsg():
618 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
618 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
619
619
620 def _updatecleanmsg(dest=None):
620 def _updatecleanmsg(dest=None):
621 warning = _('warning: this will discard uncommitted changes')
621 warning = _('warning: this will discard uncommitted changes')
622 return 'hg update --clean %s (%s)' % (dest or '.', warning)
622 return 'hg update --clean %s (%s)' % (dest or '.', warning)
623
623
624 def _graftmsg():
624 def _graftmsg():
625 # tweakdefaults requires `update` to have a rev hence the `.`
625 # tweakdefaults requires `update` to have a rev hence the `.`
626 return _helpmessage('hg graft --continue', _updatecleanmsg())
626 return _helpmessage('hg graft --continue', _updatecleanmsg())
627
627
628 def _mergemsg():
628 def _mergemsg():
629 # tweakdefaults requires `update` to have a rev hence the `.`
629 # tweakdefaults requires `update` to have a rev hence the `.`
630 return _helpmessage('hg commit', _updatecleanmsg())
630 return _helpmessage('hg commit', _updatecleanmsg())
631
631
632 def _bisectmsg():
632 def _bisectmsg():
633 msg = _('To mark the changeset good: hg bisect --good\n'
633 msg = _('To mark the changeset good: hg bisect --good\n'
634 'To mark the changeset bad: hg bisect --bad\n'
634 'To mark the changeset bad: hg bisect --bad\n'
635 'To abort: hg bisect --reset\n')
635 'To abort: hg bisect --reset\n')
636 return _commentlines(msg)
636 return _commentlines(msg)
637
637
638 def fileexistspredicate(filename):
638 def fileexistspredicate(filename):
639 return lambda repo: repo.vfs.exists(filename)
639 return lambda repo: repo.vfs.exists(filename)
640
640
641 def _mergepredicate(repo):
641 def _mergepredicate(repo):
642 return len(repo[None].parents()) > 1
642 return len(repo[None].parents()) > 1
643
643
644 STATES = (
644 STATES = (
645 # (state, predicate to detect states, helpful message function)
645 # (state, predicate to detect states, helpful message function)
646 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
646 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
647 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
647 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
648 ('graft', fileexistspredicate('graftstate'), _graftmsg),
648 ('graft', fileexistspredicate('graftstate'), _graftmsg),
649 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
649 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
650 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
650 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
651 # The merge state is part of a list that will be iterated over.
651 # The merge state is part of a list that will be iterated over.
652 # They need to be last because some of the other unfinished states may also
652 # They need to be last because some of the other unfinished states may also
653 # be in a merge or update state (eg. rebase, histedit, graft, etc).
653 # be in a merge or update state (eg. rebase, histedit, graft, etc).
654 # We want those to have priority.
654 # We want those to have priority.
655 ('merge', _mergepredicate, _mergemsg),
655 ('merge', _mergepredicate, _mergemsg),
656 )
656 )
657
657
658 def _getrepostate(repo):
658 def _getrepostate(repo):
659 # experimental config: commands.status.skipstates
659 # experimental config: commands.status.skipstates
660 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
660 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
661 for state, statedetectionpredicate, msgfn in STATES:
661 for state, statedetectionpredicate, msgfn in STATES:
662 if state in skip:
662 if state in skip:
663 continue
663 continue
664 if statedetectionpredicate(repo):
664 if statedetectionpredicate(repo):
665 return (state, statedetectionpredicate, msgfn)
665 return (state, statedetectionpredicate, msgfn)
666
666
667 def morestatus(repo, fm):
667 def morestatus(repo, fm):
668 statetuple = _getrepostate(repo)
668 statetuple = _getrepostate(repo)
669 label = 'status.morestatus'
669 label = 'status.morestatus'
670 if statetuple:
670 if statetuple:
671 fm.startitem()
671 fm.startitem()
672 state, statedetectionpredicate, helpfulmsg = statetuple
672 state, statedetectionpredicate, helpfulmsg = statetuple
673 statemsg = _('The repository is in an unfinished *%s* state.') % state
673 statemsg = _('The repository is in an unfinished *%s* state.') % state
674 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
674 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
675 conmsg = _conflictsmsg(repo)
675 conmsg = _conflictsmsg(repo)
676 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
676 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
677 if helpfulmsg:
677 if helpfulmsg:
678 helpmsg = helpfulmsg()
678 helpmsg = helpfulmsg()
679 fm.write('helpmsg', '%s\n', helpmsg, label=label)
679 fm.write('helpmsg', '%s\n', helpmsg, label=label)
680
680
681 def findpossible(cmd, table, strict=False):
681 def findpossible(cmd, table, strict=False):
682 """
682 """
683 Return cmd -> (aliases, command table entry)
683 Return cmd -> (aliases, command table entry)
684 for each matching command.
684 for each matching command.
685 Return debug commands (or their aliases) only if no normal command matches.
685 Return debug commands (or their aliases) only if no normal command matches.
686 """
686 """
687 choice = {}
687 choice = {}
688 debugchoice = {}
688 debugchoice = {}
689
689
690 if cmd in table:
690 if cmd in table:
691 # short-circuit exact matches, "log" alias beats "^log|history"
691 # short-circuit exact matches, "log" alias beats "^log|history"
692 keys = [cmd]
692 keys = [cmd]
693 else:
693 else:
694 keys = table.keys()
694 keys = table.keys()
695
695
696 allcmds = []
696 allcmds = []
697 for e in keys:
697 for e in keys:
698 aliases = parsealiases(e)
698 aliases = parsealiases(e)
699 allcmds.extend(aliases)
699 allcmds.extend(aliases)
700 found = None
700 found = None
701 if cmd in aliases:
701 if cmd in aliases:
702 found = cmd
702 found = cmd
703 elif not strict:
703 elif not strict:
704 for a in aliases:
704 for a in aliases:
705 if a.startswith(cmd):
705 if a.startswith(cmd):
706 found = a
706 found = a
707 break
707 break
708 if found is not None:
708 if found is not None:
709 if aliases[0].startswith("debug") or found.startswith("debug"):
709 if aliases[0].startswith("debug") or found.startswith("debug"):
710 debugchoice[found] = (aliases, table[e])
710 debugchoice[found] = (aliases, table[e])
711 else:
711 else:
712 choice[found] = (aliases, table[e])
712 choice[found] = (aliases, table[e])
713
713
714 if not choice and debugchoice:
714 if not choice and debugchoice:
715 choice = debugchoice
715 choice = debugchoice
716
716
717 return choice, allcmds
717 return choice, allcmds
718
718
719 def findcmd(cmd, table, strict=True):
719 def findcmd(cmd, table, strict=True):
720 """Return (aliases, command table entry) for command string."""
720 """Return (aliases, command table entry) for command string."""
721 choice, allcmds = findpossible(cmd, table, strict)
721 choice, allcmds = findpossible(cmd, table, strict)
722
722
723 if cmd in choice:
723 if cmd in choice:
724 return choice[cmd]
724 return choice[cmd]
725
725
726 if len(choice) > 1:
726 if len(choice) > 1:
727 clist = sorted(choice)
727 clist = sorted(choice)
728 raise error.AmbiguousCommand(cmd, clist)
728 raise error.AmbiguousCommand(cmd, clist)
729
729
730 if choice:
730 if choice:
731 return list(choice.values())[0]
731 return list(choice.values())[0]
732
732
733 raise error.UnknownCommand(cmd, allcmds)
733 raise error.UnknownCommand(cmd, allcmds)
734
734
735 def findrepo(p):
735 def findrepo(p):
736 while not os.path.isdir(os.path.join(p, ".hg")):
736 while not os.path.isdir(os.path.join(p, ".hg")):
737 oldp, p = p, os.path.dirname(p)
737 oldp, p = p, os.path.dirname(p)
738 if p == oldp:
738 if p == oldp:
739 return None
739 return None
740
740
741 return p
741 return p
742
742
743 def bailifchanged(repo, merge=True, hint=None):
743 def bailifchanged(repo, merge=True, hint=None):
744 """ enforce the precondition that working directory must be clean.
744 """ enforce the precondition that working directory must be clean.
745
745
746 'merge' can be set to false if a pending uncommitted merge should be
746 'merge' can be set to false if a pending uncommitted merge should be
747 ignored (such as when 'update --check' runs).
747 ignored (such as when 'update --check' runs).
748
748
749 'hint' is the usual hint given to Abort exception.
749 'hint' is the usual hint given to Abort exception.
750 """
750 """
751
751
752 if merge and repo.dirstate.p2() != nullid:
752 if merge and repo.dirstate.p2() != nullid:
753 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
753 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
754 modified, added, removed, deleted = repo.status()[:4]
754 modified, added, removed, deleted = repo.status()[:4]
755 if modified or added or removed or deleted:
755 if modified or added or removed or deleted:
756 raise error.Abort(_('uncommitted changes'), hint=hint)
756 raise error.Abort(_('uncommitted changes'), hint=hint)
757 ctx = repo[None]
757 ctx = repo[None]
758 for s in sorted(ctx.substate):
758 for s in sorted(ctx.substate):
759 ctx.sub(s).bailifchanged(hint=hint)
759 ctx.sub(s).bailifchanged(hint=hint)
760
760
761 def logmessage(ui, opts):
761 def logmessage(ui, opts):
762 """ get the log message according to -m and -l option """
762 """ get the log message according to -m and -l option """
763 message = opts.get('message')
763 message = opts.get('message')
764 logfile = opts.get('logfile')
764 logfile = opts.get('logfile')
765
765
766 if message and logfile:
766 if message and logfile:
767 raise error.Abort(_('options --message and --logfile are mutually '
767 raise error.Abort(_('options --message and --logfile are mutually '
768 'exclusive'))
768 'exclusive'))
769 if not message and logfile:
769 if not message and logfile:
770 try:
770 try:
771 if isstdiofilename(logfile):
771 if isstdiofilename(logfile):
772 message = ui.fin.read()
772 message = ui.fin.read()
773 else:
773 else:
774 message = '\n'.join(util.readfile(logfile).splitlines())
774 message = '\n'.join(util.readfile(logfile).splitlines())
775 except IOError as inst:
775 except IOError as inst:
776 raise error.Abort(_("can't read commit message '%s': %s") %
776 raise error.Abort(_("can't read commit message '%s': %s") %
777 (logfile, inst.strerror))
777 (logfile, inst.strerror))
778 return message
778 return message
779
779
780 def mergeeditform(ctxorbool, baseformname):
780 def mergeeditform(ctxorbool, baseformname):
781 """return appropriate editform name (referencing a committemplate)
781 """return appropriate editform name (referencing a committemplate)
782
782
783 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
783 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
784 merging is committed.
784 merging is committed.
785
785
786 This returns baseformname with '.merge' appended if it is a merge,
786 This returns baseformname with '.merge' appended if it is a merge,
787 otherwise '.normal' is appended.
787 otherwise '.normal' is appended.
788 """
788 """
789 if isinstance(ctxorbool, bool):
789 if isinstance(ctxorbool, bool):
790 if ctxorbool:
790 if ctxorbool:
791 return baseformname + ".merge"
791 return baseformname + ".merge"
792 elif 1 < len(ctxorbool.parents()):
792 elif 1 < len(ctxorbool.parents()):
793 return baseformname + ".merge"
793 return baseformname + ".merge"
794
794
795 return baseformname + ".normal"
795 return baseformname + ".normal"
796
796
797 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
797 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
798 editform='', **opts):
798 editform='', **opts):
799 """get appropriate commit message editor according to '--edit' option
799 """get appropriate commit message editor according to '--edit' option
800
800
801 'finishdesc' is a function to be called with edited commit message
801 'finishdesc' is a function to be called with edited commit message
802 (= 'description' of the new changeset) just after editing, but
802 (= 'description' of the new changeset) just after editing, but
803 before checking empty-ness. It should return actual text to be
803 before checking empty-ness. It should return actual text to be
804 stored into history. This allows to change description before
804 stored into history. This allows to change description before
805 storing.
805 storing.
806
806
807 'extramsg' is a extra message to be shown in the editor instead of
807 'extramsg' is a extra message to be shown in the editor instead of
808 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
808 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
809 is automatically added.
809 is automatically added.
810
810
811 'editform' is a dot-separated list of names, to distinguish
811 'editform' is a dot-separated list of names, to distinguish
812 the purpose of commit text editing.
812 the purpose of commit text editing.
813
813
814 'getcommiteditor' returns 'commitforceeditor' regardless of
814 'getcommiteditor' returns 'commitforceeditor' regardless of
815 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
815 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
816 they are specific for usage in MQ.
816 they are specific for usage in MQ.
817 """
817 """
818 if edit or finishdesc or extramsg:
818 if edit or finishdesc or extramsg:
819 return lambda r, c, s: commitforceeditor(r, c, s,
819 return lambda r, c, s: commitforceeditor(r, c, s,
820 finishdesc=finishdesc,
820 finishdesc=finishdesc,
821 extramsg=extramsg,
821 extramsg=extramsg,
822 editform=editform)
822 editform=editform)
823 elif editform:
823 elif editform:
824 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
824 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
825 else:
825 else:
826 return commiteditor
826 return commiteditor
827
827
828 def loglimit(opts):
828 def loglimit(opts):
829 """get the log limit according to option -l/--limit"""
829 """get the log limit according to option -l/--limit"""
830 limit = opts.get('limit')
830 limit = opts.get('limit')
831 if limit:
831 if limit:
832 try:
832 try:
833 limit = int(limit)
833 limit = int(limit)
834 except ValueError:
834 except ValueError:
835 raise error.Abort(_('limit must be a positive integer'))
835 raise error.Abort(_('limit must be a positive integer'))
836 if limit <= 0:
836 if limit <= 0:
837 raise error.Abort(_('limit must be positive'))
837 raise error.Abort(_('limit must be positive'))
838 else:
838 else:
839 limit = None
839 limit = None
840 return limit
840 return limit
841
841
842 def makefilename(repo, pat, node, desc=None,
842 def makefilename(repo, pat, node, desc=None,
843 total=None, seqno=None, revwidth=None, pathname=None):
843 total=None, seqno=None, revwidth=None, pathname=None):
844 node_expander = {
844 node_expander = {
845 'H': lambda: hex(node),
845 'H': lambda: hex(node),
846 'R': lambda: str(repo.changelog.rev(node)),
846 'R': lambda: str(repo.changelog.rev(node)),
847 'h': lambda: short(node),
847 'h': lambda: short(node),
848 'm': lambda: re.sub('[^\w]', '_', str(desc))
848 'm': lambda: re.sub('[^\w]', '_', str(desc))
849 }
849 }
850 expander = {
850 expander = {
851 '%': lambda: '%',
851 '%': lambda: '%',
852 'b': lambda: os.path.basename(repo.root),
852 'b': lambda: os.path.basename(repo.root),
853 }
853 }
854
854
855 try:
855 try:
856 if node:
856 if node:
857 expander.update(node_expander)
857 expander.update(node_expander)
858 if node:
858 if node:
859 expander['r'] = (lambda:
859 expander['r'] = (lambda:
860 str(repo.changelog.rev(node)).zfill(revwidth or 0))
860 str(repo.changelog.rev(node)).zfill(revwidth or 0))
861 if total is not None:
861 if total is not None:
862 expander['N'] = lambda: str(total)
862 expander['N'] = lambda: str(total)
863 if seqno is not None:
863 if seqno is not None:
864 expander['n'] = lambda: str(seqno)
864 expander['n'] = lambda: str(seqno)
865 if total is not None and seqno is not None:
865 if total is not None and seqno is not None:
866 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
866 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
867 if pathname is not None:
867 if pathname is not None:
868 expander['s'] = lambda: os.path.basename(pathname)
868 expander['s'] = lambda: os.path.basename(pathname)
869 expander['d'] = lambda: os.path.dirname(pathname) or '.'
869 expander['d'] = lambda: os.path.dirname(pathname) or '.'
870 expander['p'] = lambda: pathname
870 expander['p'] = lambda: pathname
871
871
872 newname = []
872 newname = []
873 patlen = len(pat)
873 patlen = len(pat)
874 i = 0
874 i = 0
875 while i < patlen:
875 while i < patlen:
876 c = pat[i:i + 1]
876 c = pat[i:i + 1]
877 if c == '%':
877 if c == '%':
878 i += 1
878 i += 1
879 c = pat[i:i + 1]
879 c = pat[i:i + 1]
880 c = expander[c]()
880 c = expander[c]()
881 newname.append(c)
881 newname.append(c)
882 i += 1
882 i += 1
883 return ''.join(newname)
883 return ''.join(newname)
884 except KeyError as inst:
884 except KeyError as inst:
885 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
885 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
886 inst.args[0])
886 inst.args[0])
887
887
888 def isstdiofilename(pat):
888 def isstdiofilename(pat):
889 """True if the given pat looks like a filename denoting stdin/stdout"""
889 """True if the given pat looks like a filename denoting stdin/stdout"""
890 return not pat or pat == '-'
890 return not pat or pat == '-'
891
891
892 class _unclosablefile(object):
892 class _unclosablefile(object):
893 def __init__(self, fp):
893 def __init__(self, fp):
894 self._fp = fp
894 self._fp = fp
895
895
896 def close(self):
896 def close(self):
897 pass
897 pass
898
898
899 def __iter__(self):
899 def __iter__(self):
900 return iter(self._fp)
900 return iter(self._fp)
901
901
902 def __getattr__(self, attr):
902 def __getattr__(self, attr):
903 return getattr(self._fp, attr)
903 return getattr(self._fp, attr)
904
904
905 def __enter__(self):
905 def __enter__(self):
906 return self
906 return self
907
907
908 def __exit__(self, exc_type, exc_value, exc_tb):
908 def __exit__(self, exc_type, exc_value, exc_tb):
909 pass
909 pass
910
910
911 def makefileobj(repo, pat, node=None, desc=None, total=None,
911 def makefileobj(repo, pat, node=None, desc=None, total=None,
912 seqno=None, revwidth=None, mode='wb', modemap=None,
912 seqno=None, revwidth=None, mode='wb', modemap=None,
913 pathname=None):
913 pathname=None):
914
914
915 writable = mode not in ('r', 'rb')
915 writable = mode not in ('r', 'rb')
916
916
917 if isstdiofilename(pat):
917 if isstdiofilename(pat):
918 if writable:
918 if writable:
919 fp = repo.ui.fout
919 fp = repo.ui.fout
920 else:
920 else:
921 fp = repo.ui.fin
921 fp = repo.ui.fin
922 return _unclosablefile(fp)
922 return _unclosablefile(fp)
923 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
923 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
924 if modemap is not None:
924 if modemap is not None:
925 mode = modemap.get(fn, mode)
925 mode = modemap.get(fn, mode)
926 if mode == 'wb':
926 if mode == 'wb':
927 modemap[fn] = 'ab'
927 modemap[fn] = 'ab'
928 return open(fn, mode)
928 return open(fn, mode)
929
929
930 def openrevlog(repo, cmd, file_, opts):
930 def openrevlog(repo, cmd, file_, opts):
931 """opens the changelog, manifest, a filelog or a given revlog"""
931 """opens the changelog, manifest, a filelog or a given revlog"""
932 cl = opts['changelog']
932 cl = opts['changelog']
933 mf = opts['manifest']
933 mf = opts['manifest']
934 dir = opts['dir']
934 dir = opts['dir']
935 msg = None
935 msg = None
936 if cl and mf:
936 if cl and mf:
937 msg = _('cannot specify --changelog and --manifest at the same time')
937 msg = _('cannot specify --changelog and --manifest at the same time')
938 elif cl and dir:
938 elif cl and dir:
939 msg = _('cannot specify --changelog and --dir at the same time')
939 msg = _('cannot specify --changelog and --dir at the same time')
940 elif cl or mf or dir:
940 elif cl or mf or dir:
941 if file_:
941 if file_:
942 msg = _('cannot specify filename with --changelog or --manifest')
942 msg = _('cannot specify filename with --changelog or --manifest')
943 elif not repo:
943 elif not repo:
944 msg = _('cannot specify --changelog or --manifest or --dir '
944 msg = _('cannot specify --changelog or --manifest or --dir '
945 'without a repository')
945 'without a repository')
946 if msg:
946 if msg:
947 raise error.Abort(msg)
947 raise error.Abort(msg)
948
948
949 r = None
949 r = None
950 if repo:
950 if repo:
951 if cl:
951 if cl:
952 r = repo.unfiltered().changelog
952 r = repo.unfiltered().changelog
953 elif dir:
953 elif dir:
954 if 'treemanifest' not in repo.requirements:
954 if 'treemanifest' not in repo.requirements:
955 raise error.Abort(_("--dir can only be used on repos with "
955 raise error.Abort(_("--dir can only be used on repos with "
956 "treemanifest enabled"))
956 "treemanifest enabled"))
957 dirlog = repo.manifestlog._revlog.dirlog(dir)
957 dirlog = repo.manifestlog._revlog.dirlog(dir)
958 if len(dirlog):
958 if len(dirlog):
959 r = dirlog
959 r = dirlog
960 elif mf:
960 elif mf:
961 r = repo.manifestlog._revlog
961 r = repo.manifestlog._revlog
962 elif file_:
962 elif file_:
963 filelog = repo.file(file_)
963 filelog = repo.file(file_)
964 if len(filelog):
964 if len(filelog):
965 r = filelog
965 r = filelog
966 if not r:
966 if not r:
967 if not file_:
967 if not file_:
968 raise error.CommandError(cmd, _('invalid arguments'))
968 raise error.CommandError(cmd, _('invalid arguments'))
969 if not os.path.isfile(file_):
969 if not os.path.isfile(file_):
970 raise error.Abort(_("revlog '%s' not found") % file_)
970 raise error.Abort(_("revlog '%s' not found") % file_)
971 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
971 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
972 file_[:-2] + ".i")
972 file_[:-2] + ".i")
973 return r
973 return r
974
974
975 def copy(ui, repo, pats, opts, rename=False):
975 def copy(ui, repo, pats, opts, rename=False):
976 # called with the repo lock held
976 # called with the repo lock held
977 #
977 #
978 # hgsep => pathname that uses "/" to separate directories
978 # hgsep => pathname that uses "/" to separate directories
979 # ossep => pathname that uses os.sep to separate directories
979 # ossep => pathname that uses os.sep to separate directories
980 cwd = repo.getcwd()
980 cwd = repo.getcwd()
981 targets = {}
981 targets = {}
982 after = opts.get("after")
982 after = opts.get("after")
983 dryrun = opts.get("dry_run")
983 dryrun = opts.get("dry_run")
984 wctx = repo[None]
984 wctx = repo[None]
985
985
986 def walkpat(pat):
986 def walkpat(pat):
987 srcs = []
987 srcs = []
988 if after:
988 if after:
989 badstates = '?'
989 badstates = '?'
990 else:
990 else:
991 badstates = '?r'
991 badstates = '?r'
992 m = scmutil.match(wctx, [pat], opts, globbed=True)
992 m = scmutil.match(wctx, [pat], opts, globbed=True)
993 for abs in wctx.walk(m):
993 for abs in wctx.walk(m):
994 state = repo.dirstate[abs]
994 state = repo.dirstate[abs]
995 rel = m.rel(abs)
995 rel = m.rel(abs)
996 exact = m.exact(abs)
996 exact = m.exact(abs)
997 if state in badstates:
997 if state in badstates:
998 if exact and state == '?':
998 if exact and state == '?':
999 ui.warn(_('%s: not copying - file is not managed\n') % rel)
999 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1000 if exact and state == 'r':
1000 if exact and state == 'r':
1001 ui.warn(_('%s: not copying - file has been marked for'
1001 ui.warn(_('%s: not copying - file has been marked for'
1002 ' remove\n') % rel)
1002 ' remove\n') % rel)
1003 continue
1003 continue
1004 # abs: hgsep
1004 # abs: hgsep
1005 # rel: ossep
1005 # rel: ossep
1006 srcs.append((abs, rel, exact))
1006 srcs.append((abs, rel, exact))
1007 return srcs
1007 return srcs
1008
1008
1009 # abssrc: hgsep
1009 # abssrc: hgsep
1010 # relsrc: ossep
1010 # relsrc: ossep
1011 # otarget: ossep
1011 # otarget: ossep
1012 def copyfile(abssrc, relsrc, otarget, exact):
1012 def copyfile(abssrc, relsrc, otarget, exact):
1013 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1013 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1014 if '/' in abstarget:
1014 if '/' in abstarget:
1015 # We cannot normalize abstarget itself, this would prevent
1015 # We cannot normalize abstarget itself, this would prevent
1016 # case only renames, like a => A.
1016 # case only renames, like a => A.
1017 abspath, absname = abstarget.rsplit('/', 1)
1017 abspath, absname = abstarget.rsplit('/', 1)
1018 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1018 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1019 reltarget = repo.pathto(abstarget, cwd)
1019 reltarget = repo.pathto(abstarget, cwd)
1020 target = repo.wjoin(abstarget)
1020 target = repo.wjoin(abstarget)
1021 src = repo.wjoin(abssrc)
1021 src = repo.wjoin(abssrc)
1022 state = repo.dirstate[abstarget]
1022 state = repo.dirstate[abstarget]
1023
1023
1024 scmutil.checkportable(ui, abstarget)
1024 scmutil.checkportable(ui, abstarget)
1025
1025
1026 # check for collisions
1026 # check for collisions
1027 prevsrc = targets.get(abstarget)
1027 prevsrc = targets.get(abstarget)
1028 if prevsrc is not None:
1028 if prevsrc is not None:
1029 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1029 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1030 (reltarget, repo.pathto(abssrc, cwd),
1030 (reltarget, repo.pathto(abssrc, cwd),
1031 repo.pathto(prevsrc, cwd)))
1031 repo.pathto(prevsrc, cwd)))
1032 return
1032 return
1033
1033
1034 # check for overwrites
1034 # check for overwrites
1035 exists = os.path.lexists(target)
1035 exists = os.path.lexists(target)
1036 samefile = False
1036 samefile = False
1037 if exists and abssrc != abstarget:
1037 if exists and abssrc != abstarget:
1038 if (repo.dirstate.normalize(abssrc) ==
1038 if (repo.dirstate.normalize(abssrc) ==
1039 repo.dirstate.normalize(abstarget)):
1039 repo.dirstate.normalize(abstarget)):
1040 if not rename:
1040 if not rename:
1041 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1041 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1042 return
1042 return
1043 exists = False
1043 exists = False
1044 samefile = True
1044 samefile = True
1045
1045
1046 if not after and exists or after and state in 'mn':
1046 if not after and exists or after and state in 'mn':
1047 if not opts['force']:
1047 if not opts['force']:
1048 if state in 'mn':
1048 if state in 'mn':
1049 msg = _('%s: not overwriting - file already committed\n')
1049 msg = _('%s: not overwriting - file already committed\n')
1050 if after:
1050 if after:
1051 flags = '--after --force'
1051 flags = '--after --force'
1052 else:
1052 else:
1053 flags = '--force'
1053 flags = '--force'
1054 if rename:
1054 if rename:
1055 hint = _('(hg rename %s to replace the file by '
1055 hint = _('(hg rename %s to replace the file by '
1056 'recording a rename)\n') % flags
1056 'recording a rename)\n') % flags
1057 else:
1057 else:
1058 hint = _('(hg copy %s to replace the file by '
1058 hint = _('(hg copy %s to replace the file by '
1059 'recording a copy)\n') % flags
1059 'recording a copy)\n') % flags
1060 else:
1060 else:
1061 msg = _('%s: not overwriting - file exists\n')
1061 msg = _('%s: not overwriting - file exists\n')
1062 if rename:
1062 if rename:
1063 hint = _('(hg rename --after to record the rename)\n')
1063 hint = _('(hg rename --after to record the rename)\n')
1064 else:
1064 else:
1065 hint = _('(hg copy --after to record the copy)\n')
1065 hint = _('(hg copy --after to record the copy)\n')
1066 ui.warn(msg % reltarget)
1066 ui.warn(msg % reltarget)
1067 ui.warn(hint)
1067 ui.warn(hint)
1068 return
1068 return
1069
1069
1070 if after:
1070 if after:
1071 if not exists:
1071 if not exists:
1072 if rename:
1072 if rename:
1073 ui.warn(_('%s: not recording move - %s does not exist\n') %
1073 ui.warn(_('%s: not recording move - %s does not exist\n') %
1074 (relsrc, reltarget))
1074 (relsrc, reltarget))
1075 else:
1075 else:
1076 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1076 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1077 (relsrc, reltarget))
1077 (relsrc, reltarget))
1078 return
1078 return
1079 elif not dryrun:
1079 elif not dryrun:
1080 try:
1080 try:
1081 if exists:
1081 if exists:
1082 os.unlink(target)
1082 os.unlink(target)
1083 targetdir = os.path.dirname(target) or '.'
1083 targetdir = os.path.dirname(target) or '.'
1084 if not os.path.isdir(targetdir):
1084 if not os.path.isdir(targetdir):
1085 os.makedirs(targetdir)
1085 os.makedirs(targetdir)
1086 if samefile:
1086 if samefile:
1087 tmp = target + "~hgrename"
1087 tmp = target + "~hgrename"
1088 os.rename(src, tmp)
1088 os.rename(src, tmp)
1089 os.rename(tmp, target)
1089 os.rename(tmp, target)
1090 else:
1090 else:
1091 util.copyfile(src, target)
1091 util.copyfile(src, target)
1092 srcexists = True
1092 srcexists = True
1093 except IOError as inst:
1093 except IOError as inst:
1094 if inst.errno == errno.ENOENT:
1094 if inst.errno == errno.ENOENT:
1095 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1095 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1096 srcexists = False
1096 srcexists = False
1097 else:
1097 else:
1098 ui.warn(_('%s: cannot copy - %s\n') %
1098 ui.warn(_('%s: cannot copy - %s\n') %
1099 (relsrc, inst.strerror))
1099 (relsrc, inst.strerror))
1100 return True # report a failure
1100 return True # report a failure
1101
1101
1102 if ui.verbose or not exact:
1102 if ui.verbose or not exact:
1103 if rename:
1103 if rename:
1104 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1104 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1105 else:
1105 else:
1106 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1106 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1107
1107
1108 targets[abstarget] = abssrc
1108 targets[abstarget] = abssrc
1109
1109
1110 # fix up dirstate
1110 # fix up dirstate
1111 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1111 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1112 dryrun=dryrun, cwd=cwd)
1112 dryrun=dryrun, cwd=cwd)
1113 if rename and not dryrun:
1113 if rename and not dryrun:
1114 if not after and srcexists and not samefile:
1114 if not after and srcexists and not samefile:
1115 repo.wvfs.unlinkpath(abssrc)
1115 repo.wvfs.unlinkpath(abssrc)
1116 wctx.forget([abssrc])
1116 wctx.forget([abssrc])
1117
1117
1118 # pat: ossep
1118 # pat: ossep
1119 # dest ossep
1119 # dest ossep
1120 # srcs: list of (hgsep, hgsep, ossep, bool)
1120 # srcs: list of (hgsep, hgsep, ossep, bool)
1121 # return: function that takes hgsep and returns ossep
1121 # return: function that takes hgsep and returns ossep
1122 def targetpathfn(pat, dest, srcs):
1122 def targetpathfn(pat, dest, srcs):
1123 if os.path.isdir(pat):
1123 if os.path.isdir(pat):
1124 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1124 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1125 abspfx = util.localpath(abspfx)
1125 abspfx = util.localpath(abspfx)
1126 if destdirexists:
1126 if destdirexists:
1127 striplen = len(os.path.split(abspfx)[0])
1127 striplen = len(os.path.split(abspfx)[0])
1128 else:
1128 else:
1129 striplen = len(abspfx)
1129 striplen = len(abspfx)
1130 if striplen:
1130 if striplen:
1131 striplen += len(pycompat.ossep)
1131 striplen += len(pycompat.ossep)
1132 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1132 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1133 elif destdirexists:
1133 elif destdirexists:
1134 res = lambda p: os.path.join(dest,
1134 res = lambda p: os.path.join(dest,
1135 os.path.basename(util.localpath(p)))
1135 os.path.basename(util.localpath(p)))
1136 else:
1136 else:
1137 res = lambda p: dest
1137 res = lambda p: dest
1138 return res
1138 return res
1139
1139
1140 # pat: ossep
1140 # pat: ossep
1141 # dest ossep
1141 # dest ossep
1142 # srcs: list of (hgsep, hgsep, ossep, bool)
1142 # srcs: list of (hgsep, hgsep, ossep, bool)
1143 # return: function that takes hgsep and returns ossep
1143 # return: function that takes hgsep and returns ossep
1144 def targetpathafterfn(pat, dest, srcs):
1144 def targetpathafterfn(pat, dest, srcs):
1145 if matchmod.patkind(pat):
1145 if matchmod.patkind(pat):
1146 # a mercurial pattern
1146 # a mercurial pattern
1147 res = lambda p: os.path.join(dest,
1147 res = lambda p: os.path.join(dest,
1148 os.path.basename(util.localpath(p)))
1148 os.path.basename(util.localpath(p)))
1149 else:
1149 else:
1150 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1150 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1151 if len(abspfx) < len(srcs[0][0]):
1151 if len(abspfx) < len(srcs[0][0]):
1152 # A directory. Either the target path contains the last
1152 # A directory. Either the target path contains the last
1153 # component of the source path or it does not.
1153 # component of the source path or it does not.
1154 def evalpath(striplen):
1154 def evalpath(striplen):
1155 score = 0
1155 score = 0
1156 for s in srcs:
1156 for s in srcs:
1157 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1157 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1158 if os.path.lexists(t):
1158 if os.path.lexists(t):
1159 score += 1
1159 score += 1
1160 return score
1160 return score
1161
1161
1162 abspfx = util.localpath(abspfx)
1162 abspfx = util.localpath(abspfx)
1163 striplen = len(abspfx)
1163 striplen = len(abspfx)
1164 if striplen:
1164 if striplen:
1165 striplen += len(pycompat.ossep)
1165 striplen += len(pycompat.ossep)
1166 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1166 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1167 score = evalpath(striplen)
1167 score = evalpath(striplen)
1168 striplen1 = len(os.path.split(abspfx)[0])
1168 striplen1 = len(os.path.split(abspfx)[0])
1169 if striplen1:
1169 if striplen1:
1170 striplen1 += len(pycompat.ossep)
1170 striplen1 += len(pycompat.ossep)
1171 if evalpath(striplen1) > score:
1171 if evalpath(striplen1) > score:
1172 striplen = striplen1
1172 striplen = striplen1
1173 res = lambda p: os.path.join(dest,
1173 res = lambda p: os.path.join(dest,
1174 util.localpath(p)[striplen:])
1174 util.localpath(p)[striplen:])
1175 else:
1175 else:
1176 # a file
1176 # a file
1177 if destdirexists:
1177 if destdirexists:
1178 res = lambda p: os.path.join(dest,
1178 res = lambda p: os.path.join(dest,
1179 os.path.basename(util.localpath(p)))
1179 os.path.basename(util.localpath(p)))
1180 else:
1180 else:
1181 res = lambda p: dest
1181 res = lambda p: dest
1182 return res
1182 return res
1183
1183
1184 pats = scmutil.expandpats(pats)
1184 pats = scmutil.expandpats(pats)
1185 if not pats:
1185 if not pats:
1186 raise error.Abort(_('no source or destination specified'))
1186 raise error.Abort(_('no source or destination specified'))
1187 if len(pats) == 1:
1187 if len(pats) == 1:
1188 raise error.Abort(_('no destination specified'))
1188 raise error.Abort(_('no destination specified'))
1189 dest = pats.pop()
1189 dest = pats.pop()
1190 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1190 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1191 if not destdirexists:
1191 if not destdirexists:
1192 if len(pats) > 1 or matchmod.patkind(pats[0]):
1192 if len(pats) > 1 or matchmod.patkind(pats[0]):
1193 raise error.Abort(_('with multiple sources, destination must be an '
1193 raise error.Abort(_('with multiple sources, destination must be an '
1194 'existing directory'))
1194 'existing directory'))
1195 if util.endswithsep(dest):
1195 if util.endswithsep(dest):
1196 raise error.Abort(_('destination %s is not a directory') % dest)
1196 raise error.Abort(_('destination %s is not a directory') % dest)
1197
1197
1198 tfn = targetpathfn
1198 tfn = targetpathfn
1199 if after:
1199 if after:
1200 tfn = targetpathafterfn
1200 tfn = targetpathafterfn
1201 copylist = []
1201 copylist = []
1202 for pat in pats:
1202 for pat in pats:
1203 srcs = walkpat(pat)
1203 srcs = walkpat(pat)
1204 if not srcs:
1204 if not srcs:
1205 continue
1205 continue
1206 copylist.append((tfn(pat, dest, srcs), srcs))
1206 copylist.append((tfn(pat, dest, srcs), srcs))
1207 if not copylist:
1207 if not copylist:
1208 raise error.Abort(_('no files to copy'))
1208 raise error.Abort(_('no files to copy'))
1209
1209
1210 errors = 0
1210 errors = 0
1211 for targetpath, srcs in copylist:
1211 for targetpath, srcs in copylist:
1212 for abssrc, relsrc, exact in srcs:
1212 for abssrc, relsrc, exact in srcs:
1213 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1213 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1214 errors += 1
1214 errors += 1
1215
1215
1216 if errors:
1216 if errors:
1217 ui.warn(_('(consider using --after)\n'))
1217 ui.warn(_('(consider using --after)\n'))
1218
1218
1219 return errors != 0
1219 return errors != 0
1220
1220
1221 ## facility to let extension process additional data into an import patch
1221 ## facility to let extension process additional data into an import patch
1222 # list of identifier to be executed in order
1222 # list of identifier to be executed in order
1223 extrapreimport = [] # run before commit
1223 extrapreimport = [] # run before commit
1224 extrapostimport = [] # run after commit
1224 extrapostimport = [] # run after commit
1225 # mapping from identifier to actual import function
1225 # mapping from identifier to actual import function
1226 #
1226 #
1227 # 'preimport' are run before the commit is made and are provided the following
1227 # 'preimport' are run before the commit is made and are provided the following
1228 # arguments:
1228 # arguments:
1229 # - repo: the localrepository instance,
1229 # - repo: the localrepository instance,
1230 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1230 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1231 # - extra: the future extra dictionary of the changeset, please mutate it,
1231 # - extra: the future extra dictionary of the changeset, please mutate it,
1232 # - opts: the import options.
1232 # - opts: the import options.
1233 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1233 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1234 # mutation of in memory commit and more. Feel free to rework the code to get
1234 # mutation of in memory commit and more. Feel free to rework the code to get
1235 # there.
1235 # there.
1236 extrapreimportmap = {}
1236 extrapreimportmap = {}
1237 # 'postimport' are run after the commit is made and are provided the following
1237 # 'postimport' are run after the commit is made and are provided the following
1238 # argument:
1238 # argument:
1239 # - ctx: the changectx created by import.
1239 # - ctx: the changectx created by import.
1240 extrapostimportmap = {}
1240 extrapostimportmap = {}
1241
1241
1242 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1242 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1243 """Utility function used by commands.import to import a single patch
1243 """Utility function used by commands.import to import a single patch
1244
1244
1245 This function is explicitly defined here to help the evolve extension to
1245 This function is explicitly defined here to help the evolve extension to
1246 wrap this part of the import logic.
1246 wrap this part of the import logic.
1247
1247
1248 The API is currently a bit ugly because it a simple code translation from
1248 The API is currently a bit ugly because it a simple code translation from
1249 the import command. Feel free to make it better.
1249 the import command. Feel free to make it better.
1250
1250
1251 :hunk: a patch (as a binary string)
1251 :hunk: a patch (as a binary string)
1252 :parents: nodes that will be parent of the created commit
1252 :parents: nodes that will be parent of the created commit
1253 :opts: the full dict of option passed to the import command
1253 :opts: the full dict of option passed to the import command
1254 :msgs: list to save commit message to.
1254 :msgs: list to save commit message to.
1255 (used in case we need to save it when failing)
1255 (used in case we need to save it when failing)
1256 :updatefunc: a function that update a repo to a given node
1256 :updatefunc: a function that update a repo to a given node
1257 updatefunc(<repo>, <node>)
1257 updatefunc(<repo>, <node>)
1258 """
1258 """
1259 # avoid cycle context -> subrepo -> cmdutil
1259 # avoid cycle context -> subrepo -> cmdutil
1260 from . import context
1260 from . import context
1261 extractdata = patch.extract(ui, hunk)
1261 extractdata = patch.extract(ui, hunk)
1262 tmpname = extractdata.get('filename')
1262 tmpname = extractdata.get('filename')
1263 message = extractdata.get('message')
1263 message = extractdata.get('message')
1264 user = opts.get('user') or extractdata.get('user')
1264 user = opts.get('user') or extractdata.get('user')
1265 date = opts.get('date') or extractdata.get('date')
1265 date = opts.get('date') or extractdata.get('date')
1266 branch = extractdata.get('branch')
1266 branch = extractdata.get('branch')
1267 nodeid = extractdata.get('nodeid')
1267 nodeid = extractdata.get('nodeid')
1268 p1 = extractdata.get('p1')
1268 p1 = extractdata.get('p1')
1269 p2 = extractdata.get('p2')
1269 p2 = extractdata.get('p2')
1270
1270
1271 nocommit = opts.get('no_commit')
1271 nocommit = opts.get('no_commit')
1272 importbranch = opts.get('import_branch')
1272 importbranch = opts.get('import_branch')
1273 update = not opts.get('bypass')
1273 update = not opts.get('bypass')
1274 strip = opts["strip"]
1274 strip = opts["strip"]
1275 prefix = opts["prefix"]
1275 prefix = opts["prefix"]
1276 sim = float(opts.get('similarity') or 0)
1276 sim = float(opts.get('similarity') or 0)
1277 if not tmpname:
1277 if not tmpname:
1278 return (None, None, False)
1278 return (None, None, False)
1279
1279
1280 rejects = False
1280 rejects = False
1281
1281
1282 try:
1282 try:
1283 cmdline_message = logmessage(ui, opts)
1283 cmdline_message = logmessage(ui, opts)
1284 if cmdline_message:
1284 if cmdline_message:
1285 # pickup the cmdline msg
1285 # pickup the cmdline msg
1286 message = cmdline_message
1286 message = cmdline_message
1287 elif message:
1287 elif message:
1288 # pickup the patch msg
1288 # pickup the patch msg
1289 message = message.strip()
1289 message = message.strip()
1290 else:
1290 else:
1291 # launch the editor
1291 # launch the editor
1292 message = None
1292 message = None
1293 ui.debug('message:\n%s\n' % message)
1293 ui.debug('message:\n%s\n' % message)
1294
1294
1295 if len(parents) == 1:
1295 if len(parents) == 1:
1296 parents.append(repo[nullid])
1296 parents.append(repo[nullid])
1297 if opts.get('exact'):
1297 if opts.get('exact'):
1298 if not nodeid or not p1:
1298 if not nodeid or not p1:
1299 raise error.Abort(_('not a Mercurial patch'))
1299 raise error.Abort(_('not a Mercurial patch'))
1300 p1 = repo[p1]
1300 p1 = repo[p1]
1301 p2 = repo[p2 or nullid]
1301 p2 = repo[p2 or nullid]
1302 elif p2:
1302 elif p2:
1303 try:
1303 try:
1304 p1 = repo[p1]
1304 p1 = repo[p1]
1305 p2 = repo[p2]
1305 p2 = repo[p2]
1306 # Without any options, consider p2 only if the
1306 # Without any options, consider p2 only if the
1307 # patch is being applied on top of the recorded
1307 # patch is being applied on top of the recorded
1308 # first parent.
1308 # first parent.
1309 if p1 != parents[0]:
1309 if p1 != parents[0]:
1310 p1 = parents[0]
1310 p1 = parents[0]
1311 p2 = repo[nullid]
1311 p2 = repo[nullid]
1312 except error.RepoError:
1312 except error.RepoError:
1313 p1, p2 = parents
1313 p1, p2 = parents
1314 if p2.node() == nullid:
1314 if p2.node() == nullid:
1315 ui.warn(_("warning: import the patch as a normal revision\n"
1315 ui.warn(_("warning: import the patch as a normal revision\n"
1316 "(use --exact to import the patch as a merge)\n"))
1316 "(use --exact to import the patch as a merge)\n"))
1317 else:
1317 else:
1318 p1, p2 = parents
1318 p1, p2 = parents
1319
1319
1320 n = None
1320 n = None
1321 if update:
1321 if update:
1322 if p1 != parents[0]:
1322 if p1 != parents[0]:
1323 updatefunc(repo, p1.node())
1323 updatefunc(repo, p1.node())
1324 if p2 != parents[1]:
1324 if p2 != parents[1]:
1325 repo.setparents(p1.node(), p2.node())
1325 repo.setparents(p1.node(), p2.node())
1326
1326
1327 if opts.get('exact') or importbranch:
1327 if opts.get('exact') or importbranch:
1328 repo.dirstate.setbranch(branch or 'default')
1328 repo.dirstate.setbranch(branch or 'default')
1329
1329
1330 partial = opts.get('partial', False)
1330 partial = opts.get('partial', False)
1331 files = set()
1331 files = set()
1332 try:
1332 try:
1333 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1333 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1334 files=files, eolmode=None, similarity=sim / 100.0)
1334 files=files, eolmode=None, similarity=sim / 100.0)
1335 except patch.PatchError as e:
1335 except patch.PatchError as e:
1336 if not partial:
1336 if not partial:
1337 raise error.Abort(str(e))
1337 raise error.Abort(str(e))
1338 if partial:
1338 if partial:
1339 rejects = True
1339 rejects = True
1340
1340
1341 files = list(files)
1341 files = list(files)
1342 if nocommit:
1342 if nocommit:
1343 if message:
1343 if message:
1344 msgs.append(message)
1344 msgs.append(message)
1345 else:
1345 else:
1346 if opts.get('exact') or p2:
1346 if opts.get('exact') or p2:
1347 # If you got here, you either use --force and know what
1347 # If you got here, you either use --force and know what
1348 # you are doing or used --exact or a merge patch while
1348 # you are doing or used --exact or a merge patch while
1349 # being updated to its first parent.
1349 # being updated to its first parent.
1350 m = None
1350 m = None
1351 else:
1351 else:
1352 m = scmutil.matchfiles(repo, files or [])
1352 m = scmutil.matchfiles(repo, files or [])
1353 editform = mergeeditform(repo[None], 'import.normal')
1353 editform = mergeeditform(repo[None], 'import.normal')
1354 if opts.get('exact'):
1354 if opts.get('exact'):
1355 editor = None
1355 editor = None
1356 else:
1356 else:
1357 editor = getcommiteditor(editform=editform, **opts)
1357 editor = getcommiteditor(editform=editform, **opts)
1358 extra = {}
1358 extra = {}
1359 for idfunc in extrapreimport:
1359 for idfunc in extrapreimport:
1360 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1360 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1361 overrides = {}
1361 overrides = {}
1362 if partial:
1362 if partial:
1363 overrides[('ui', 'allowemptycommit')] = True
1363 overrides[('ui', 'allowemptycommit')] = True
1364 with repo.ui.configoverride(overrides, 'import'):
1364 with repo.ui.configoverride(overrides, 'import'):
1365 n = repo.commit(message, user,
1365 n = repo.commit(message, user,
1366 date, match=m,
1366 date, match=m,
1367 editor=editor, extra=extra)
1367 editor=editor, extra=extra)
1368 for idfunc in extrapostimport:
1368 for idfunc in extrapostimport:
1369 extrapostimportmap[idfunc](repo[n])
1369 extrapostimportmap[idfunc](repo[n])
1370 else:
1370 else:
1371 if opts.get('exact') or importbranch:
1371 if opts.get('exact') or importbranch:
1372 branch = branch or 'default'
1372 branch = branch or 'default'
1373 else:
1373 else:
1374 branch = p1.branch()
1374 branch = p1.branch()
1375 store = patch.filestore()
1375 store = patch.filestore()
1376 try:
1376 try:
1377 files = set()
1377 files = set()
1378 try:
1378 try:
1379 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1379 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1380 files, eolmode=None)
1380 files, eolmode=None)
1381 except patch.PatchError as e:
1381 except patch.PatchError as e:
1382 raise error.Abort(str(e))
1382 raise error.Abort(str(e))
1383 if opts.get('exact'):
1383 if opts.get('exact'):
1384 editor = None
1384 editor = None
1385 else:
1385 else:
1386 editor = getcommiteditor(editform='import.bypass')
1386 editor = getcommiteditor(editform='import.bypass')
1387 memctx = context.memctx(repo, (p1.node(), p2.node()),
1387 memctx = context.memctx(repo, (p1.node(), p2.node()),
1388 message,
1388 message,
1389 files=files,
1389 files=files,
1390 filectxfn=store,
1390 filectxfn=store,
1391 user=user,
1391 user=user,
1392 date=date,
1392 date=date,
1393 branch=branch,
1393 branch=branch,
1394 editor=editor)
1394 editor=editor)
1395 n = memctx.commit()
1395 n = memctx.commit()
1396 finally:
1396 finally:
1397 store.close()
1397 store.close()
1398 if opts.get('exact') and nocommit:
1398 if opts.get('exact') and nocommit:
1399 # --exact with --no-commit is still useful in that it does merge
1399 # --exact with --no-commit is still useful in that it does merge
1400 # and branch bits
1400 # and branch bits
1401 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1401 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1402 elif opts.get('exact') and hex(n) != nodeid:
1402 elif opts.get('exact') and hex(n) != nodeid:
1403 raise error.Abort(_('patch is damaged or loses information'))
1403 raise error.Abort(_('patch is damaged or loses information'))
1404 msg = _('applied to working directory')
1404 msg = _('applied to working directory')
1405 if n:
1405 if n:
1406 # i18n: refers to a short changeset id
1406 # i18n: refers to a short changeset id
1407 msg = _('created %s') % short(n)
1407 msg = _('created %s') % short(n)
1408 return (msg, n, rejects)
1408 return (msg, n, rejects)
1409 finally:
1409 finally:
1410 os.unlink(tmpname)
1410 os.unlink(tmpname)
1411
1411
1412 # facility to let extensions include additional data in an exported patch
1412 # facility to let extensions include additional data in an exported patch
1413 # list of identifiers to be executed in order
1413 # list of identifiers to be executed in order
1414 extraexport = []
1414 extraexport = []
1415 # mapping from identifier to actual export function
1415 # mapping from identifier to actual export function
1416 # function as to return a string to be added to the header or None
1416 # function as to return a string to be added to the header or None
1417 # it is given two arguments (sequencenumber, changectx)
1417 # it is given two arguments (sequencenumber, changectx)
1418 extraexportmap = {}
1418 extraexportmap = {}
1419
1419
1420 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1420 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1421 node = scmutil.binnode(ctx)
1421 node = scmutil.binnode(ctx)
1422 parents = [p.node() for p in ctx.parents() if p]
1422 parents = [p.node() for p in ctx.parents() if p]
1423 branch = ctx.branch()
1423 branch = ctx.branch()
1424 if switch_parent:
1424 if switch_parent:
1425 parents.reverse()
1425 parents.reverse()
1426
1426
1427 if parents:
1427 if parents:
1428 prev = parents[0]
1428 prev = parents[0]
1429 else:
1429 else:
1430 prev = nullid
1430 prev = nullid
1431
1431
1432 write("# HG changeset patch\n")
1432 write("# HG changeset patch\n")
1433 write("# User %s\n" % ctx.user())
1433 write("# User %s\n" % ctx.user())
1434 write("# Date %d %d\n" % ctx.date())
1434 write("# Date %d %d\n" % ctx.date())
1435 write("# %s\n" % util.datestr(ctx.date()))
1435 write("# %s\n" % util.datestr(ctx.date()))
1436 if branch and branch != 'default':
1436 if branch and branch != 'default':
1437 write("# Branch %s\n" % branch)
1437 write("# Branch %s\n" % branch)
1438 write("# Node ID %s\n" % hex(node))
1438 write("# Node ID %s\n" % hex(node))
1439 write("# Parent %s\n" % hex(prev))
1439 write("# Parent %s\n" % hex(prev))
1440 if len(parents) > 1:
1440 if len(parents) > 1:
1441 write("# Parent %s\n" % hex(parents[1]))
1441 write("# Parent %s\n" % hex(parents[1]))
1442
1442
1443 for headerid in extraexport:
1443 for headerid in extraexport:
1444 header = extraexportmap[headerid](seqno, ctx)
1444 header = extraexportmap[headerid](seqno, ctx)
1445 if header is not None:
1445 if header is not None:
1446 write('# %s\n' % header)
1446 write('# %s\n' % header)
1447 write(ctx.description().rstrip())
1447 write(ctx.description().rstrip())
1448 write("\n\n")
1448 write("\n\n")
1449
1449
1450 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1450 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1451 write(chunk, label=label)
1451 write(chunk, label=label)
1452
1452
1453 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1453 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1454 opts=None, match=None):
1454 opts=None, match=None):
1455 '''export changesets as hg patches
1455 '''export changesets as hg patches
1456
1456
1457 Args:
1457 Args:
1458 repo: The repository from which we're exporting revisions.
1458 repo: The repository from which we're exporting revisions.
1459 revs: A list of revisions to export as revision numbers.
1459 revs: A list of revisions to export as revision numbers.
1460 fntemplate: An optional string to use for generating patch file names.
1460 fntemplate: An optional string to use for generating patch file names.
1461 fp: An optional file-like object to which patches should be written.
1461 fp: An optional file-like object to which patches should be written.
1462 switch_parent: If True, show diffs against second parent when not nullid.
1462 switch_parent: If True, show diffs against second parent when not nullid.
1463 Default is false, which always shows diff against p1.
1463 Default is false, which always shows diff against p1.
1464 opts: diff options to use for generating the patch.
1464 opts: diff options to use for generating the patch.
1465 match: If specified, only export changes to files matching this matcher.
1465 match: If specified, only export changes to files matching this matcher.
1466
1466
1467 Returns:
1467 Returns:
1468 Nothing.
1468 Nothing.
1469
1469
1470 Side Effect:
1470 Side Effect:
1471 "HG Changeset Patch" data is emitted to one of the following
1471 "HG Changeset Patch" data is emitted to one of the following
1472 destinations:
1472 destinations:
1473 fp is specified: All revs are written to the specified
1473 fp is specified: All revs are written to the specified
1474 file-like object.
1474 file-like object.
1475 fntemplate specified: Each rev is written to a unique file named using
1475 fntemplate specified: Each rev is written to a unique file named using
1476 the given template.
1476 the given template.
1477 Neither fp nor template specified: All revs written to repo.ui.write()
1477 Neither fp nor template specified: All revs written to repo.ui.write()
1478 '''
1478 '''
1479
1479
1480 total = len(revs)
1480 total = len(revs)
1481 revwidth = max(len(str(rev)) for rev in revs)
1481 revwidth = max(len(str(rev)) for rev in revs)
1482 filemode = {}
1482 filemode = {}
1483
1483
1484 write = None
1484 write = None
1485 dest = '<unnamed>'
1485 dest = '<unnamed>'
1486 if fp:
1486 if fp:
1487 dest = getattr(fp, 'name', dest)
1487 dest = getattr(fp, 'name', dest)
1488 def write(s, **kw):
1488 def write(s, **kw):
1489 fp.write(s)
1489 fp.write(s)
1490 elif not fntemplate:
1490 elif not fntemplate:
1491 write = repo.ui.write
1491 write = repo.ui.write
1492
1492
1493 for seqno, rev in enumerate(revs, 1):
1493 for seqno, rev in enumerate(revs, 1):
1494 ctx = repo[rev]
1494 ctx = repo[rev]
1495 fo = None
1495 fo = None
1496 if not fp and fntemplate:
1496 if not fp and fntemplate:
1497 desc_lines = ctx.description().rstrip().split('\n')
1497 desc_lines = ctx.description().rstrip().split('\n')
1498 desc = desc_lines[0] #Commit always has a first line.
1498 desc = desc_lines[0] #Commit always has a first line.
1499 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1499 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1500 total=total, seqno=seqno, revwidth=revwidth,
1500 total=total, seqno=seqno, revwidth=revwidth,
1501 mode='wb', modemap=filemode)
1501 mode='wb', modemap=filemode)
1502 dest = fo.name
1502 dest = fo.name
1503 def write(s, **kw):
1503 def write(s, **kw):
1504 fo.write(s)
1504 fo.write(s)
1505 if not dest.startswith('<'):
1505 if not dest.startswith('<'):
1506 repo.ui.note("%s\n" % dest)
1506 repo.ui.note("%s\n" % dest)
1507 _exportsingle(
1507 _exportsingle(
1508 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1508 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1509 if fo is not None:
1509 if fo is not None:
1510 fo.close()
1510 fo.close()
1511
1511
1512 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1512 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1513 changes=None, stat=False, fp=None, prefix='',
1513 changes=None, stat=False, fp=None, prefix='',
1514 root='', listsubrepos=False):
1514 root='', listsubrepos=False):
1515 '''show diff or diffstat.'''
1515 '''show diff or diffstat.'''
1516 if fp is None:
1516 if fp is None:
1517 write = ui.write
1517 write = ui.write
1518 else:
1518 else:
1519 def write(s, **kw):
1519 def write(s, **kw):
1520 fp.write(s)
1520 fp.write(s)
1521
1521
1522 if root:
1522 if root:
1523 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1523 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1524 else:
1524 else:
1525 relroot = ''
1525 relroot = ''
1526 if relroot != '':
1526 if relroot != '':
1527 # XXX relative roots currently don't work if the root is within a
1527 # XXX relative roots currently don't work if the root is within a
1528 # subrepo
1528 # subrepo
1529 uirelroot = match.uipath(relroot)
1529 uirelroot = match.uipath(relroot)
1530 relroot += '/'
1530 relroot += '/'
1531 for matchroot in match.files():
1531 for matchroot in match.files():
1532 if not matchroot.startswith(relroot):
1532 if not matchroot.startswith(relroot):
1533 ui.warn(_('warning: %s not inside relative root %s\n') % (
1533 ui.warn(_('warning: %s not inside relative root %s\n') % (
1534 match.uipath(matchroot), uirelroot))
1534 match.uipath(matchroot), uirelroot))
1535
1535
1536 if stat:
1536 if stat:
1537 diffopts = diffopts.copy(context=0)
1537 diffopts = diffopts.copy(context=0)
1538 width = 80
1538 width = 80
1539 if not ui.plain():
1539 if not ui.plain():
1540 width = ui.termwidth()
1540 width = ui.termwidth()
1541 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1541 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1542 prefix=prefix, relroot=relroot)
1542 prefix=prefix, relroot=relroot)
1543 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1543 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1544 width=width):
1544 width=width):
1545 write(chunk, label=label)
1545 write(chunk, label=label)
1546 else:
1546 else:
1547 for chunk, label in patch.diffui(repo, node1, node2, match,
1547 for chunk, label in patch.diffui(repo, node1, node2, match,
1548 changes, diffopts, prefix=prefix,
1548 changes, diffopts, prefix=prefix,
1549 relroot=relroot):
1549 relroot=relroot):
1550 write(chunk, label=label)
1550 write(chunk, label=label)
1551
1551
1552 if listsubrepos:
1552 if listsubrepos:
1553 ctx1 = repo[node1]
1553 ctx1 = repo[node1]
1554 ctx2 = repo[node2]
1554 ctx2 = repo[node2]
1555 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1555 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1556 tempnode2 = node2
1556 tempnode2 = node2
1557 try:
1557 try:
1558 if node2 is not None:
1558 if node2 is not None:
1559 tempnode2 = ctx2.substate[subpath][1]
1559 tempnode2 = ctx2.substate[subpath][1]
1560 except KeyError:
1560 except KeyError:
1561 # A subrepo that existed in node1 was deleted between node1 and
1561 # A subrepo that existed in node1 was deleted between node1 and
1562 # node2 (inclusive). Thus, ctx2's substate won't contain that
1562 # node2 (inclusive). Thus, ctx2's substate won't contain that
1563 # subpath. The best we can do is to ignore it.
1563 # subpath. The best we can do is to ignore it.
1564 tempnode2 = None
1564 tempnode2 = None
1565 submatch = matchmod.subdirmatcher(subpath, match)
1565 submatch = matchmod.subdirmatcher(subpath, match)
1566 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1566 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1567 stat=stat, fp=fp, prefix=prefix)
1567 stat=stat, fp=fp, prefix=prefix)
1568
1568
1569 def _changesetlabels(ctx):
1569 def _changesetlabels(ctx):
1570 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1570 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1571 if ctx.obsolete():
1571 if ctx.obsolete():
1572 labels.append('changeset.obsolete')
1572 labels.append('changeset.obsolete')
1573 if ctx.isunstable():
1573 if ctx.isunstable():
1574 labels.append('changeset.unstable')
1574 labels.append('changeset.unstable')
1575 for instability in ctx.instabilities():
1575 for instability in ctx.instabilities():
1576 labels.append('instability.%s' % instability)
1576 labels.append('instability.%s' % instability)
1577 return ' '.join(labels)
1577 return ' '.join(labels)
1578
1578
1579 class changeset_printer(object):
1579 class changeset_printer(object):
1580 '''show changeset information when templating not requested.'''
1580 '''show changeset information when templating not requested.'''
1581
1581
1582 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1582 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1583 self.ui = ui
1583 self.ui = ui
1584 self.repo = repo
1584 self.repo = repo
1585 self.buffered = buffered
1585 self.buffered = buffered
1586 self.matchfn = matchfn
1586 self.matchfn = matchfn
1587 self.diffopts = diffopts
1587 self.diffopts = diffopts
1588 self.header = {}
1588 self.header = {}
1589 self.hunk = {}
1589 self.hunk = {}
1590 self.lastheader = None
1590 self.lastheader = None
1591 self.footer = None
1591 self.footer = None
1592
1592
1593 def flush(self, ctx):
1593 def flush(self, ctx):
1594 rev = ctx.rev()
1594 rev = ctx.rev()
1595 if rev in self.header:
1595 if rev in self.header:
1596 h = self.header[rev]
1596 h = self.header[rev]
1597 if h != self.lastheader:
1597 if h != self.lastheader:
1598 self.lastheader = h
1598 self.lastheader = h
1599 self.ui.write(h)
1599 self.ui.write(h)
1600 del self.header[rev]
1600 del self.header[rev]
1601 if rev in self.hunk:
1601 if rev in self.hunk:
1602 self.ui.write(self.hunk[rev])
1602 self.ui.write(self.hunk[rev])
1603 del self.hunk[rev]
1603 del self.hunk[rev]
1604 return 1
1604 return 1
1605 return 0
1605 return 0
1606
1606
1607 def close(self):
1607 def close(self):
1608 if self.footer:
1608 if self.footer:
1609 self.ui.write(self.footer)
1609 self.ui.write(self.footer)
1610
1610
1611 def show(self, ctx, copies=None, matchfn=None, **props):
1611 def show(self, ctx, copies=None, matchfn=None, **props):
1612 props = pycompat.byteskwargs(props)
1612 props = pycompat.byteskwargs(props)
1613 if self.buffered:
1613 if self.buffered:
1614 self.ui.pushbuffer(labeled=True)
1614 self.ui.pushbuffer(labeled=True)
1615 self._show(ctx, copies, matchfn, props)
1615 self._show(ctx, copies, matchfn, props)
1616 self.hunk[ctx.rev()] = self.ui.popbuffer()
1616 self.hunk[ctx.rev()] = self.ui.popbuffer()
1617 else:
1617 else:
1618 self._show(ctx, copies, matchfn, props)
1618 self._show(ctx, copies, matchfn, props)
1619
1619
1620 def _show(self, ctx, copies, matchfn, props):
1620 def _show(self, ctx, copies, matchfn, props):
1621 '''show a single changeset or file revision'''
1621 '''show a single changeset or file revision'''
1622 changenode = ctx.node()
1622 changenode = ctx.node()
1623 rev = ctx.rev()
1623 rev = ctx.rev()
1624 if self.ui.debugflag:
1624 if self.ui.debugflag:
1625 hexfunc = hex
1625 hexfunc = hex
1626 else:
1626 else:
1627 hexfunc = short
1627 hexfunc = short
1628 # as of now, wctx.node() and wctx.rev() return None, but we want to
1628 # as of now, wctx.node() and wctx.rev() return None, but we want to
1629 # show the same values as {node} and {rev} templatekw
1629 # show the same values as {node} and {rev} templatekw
1630 revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
1630 revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
1631
1631
1632 if self.ui.quiet:
1632 if self.ui.quiet:
1633 self.ui.write("%d:%s\n" % revnode, label='log.node')
1633 self.ui.write("%d:%s\n" % revnode, label='log.node')
1634 return
1634 return
1635
1635
1636 date = util.datestr(ctx.date())
1636 date = util.datestr(ctx.date())
1637
1637
1638 # i18n: column positioning for "hg log"
1638 # i18n: column positioning for "hg log"
1639 self.ui.write(_("changeset: %d:%s\n") % revnode,
1639 self.ui.write(_("changeset: %d:%s\n") % revnode,
1640 label=_changesetlabels(ctx))
1640 label=_changesetlabels(ctx))
1641
1641
1642 # branches are shown first before any other names due to backwards
1642 # branches are shown first before any other names due to backwards
1643 # compatibility
1643 # compatibility
1644 branch = ctx.branch()
1644 branch = ctx.branch()
1645 # don't show the default branch name
1645 # don't show the default branch name
1646 if branch != 'default':
1646 if branch != 'default':
1647 # i18n: column positioning for "hg log"
1647 # i18n: column positioning for "hg log"
1648 self.ui.write(_("branch: %s\n") % branch,
1648 self.ui.write(_("branch: %s\n") % branch,
1649 label='log.branch')
1649 label='log.branch')
1650
1650
1651 for nsname, ns in self.repo.names.iteritems():
1651 for nsname, ns in self.repo.names.iteritems():
1652 # branches has special logic already handled above, so here we just
1652 # branches has special logic already handled above, so here we just
1653 # skip it
1653 # skip it
1654 if nsname == 'branches':
1654 if nsname == 'branches':
1655 continue
1655 continue
1656 # we will use the templatename as the color name since those two
1656 # we will use the templatename as the color name since those two
1657 # should be the same
1657 # should be the same
1658 for name in ns.names(self.repo, changenode):
1658 for name in ns.names(self.repo, changenode):
1659 self.ui.write(ns.logfmt % name,
1659 self.ui.write(ns.logfmt % name,
1660 label='log.%s' % ns.colorname)
1660 label='log.%s' % ns.colorname)
1661 if self.ui.debugflag:
1661 if self.ui.debugflag:
1662 # i18n: column positioning for "hg log"
1662 # i18n: column positioning for "hg log"
1663 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1663 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1664 label='log.phase')
1664 label='log.phase')
1665 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1665 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1666 label = 'log.parent changeset.%s' % pctx.phasestr()
1666 label = 'log.parent changeset.%s' % pctx.phasestr()
1667 # i18n: column positioning for "hg log"
1667 # i18n: column positioning for "hg log"
1668 self.ui.write(_("parent: %d:%s\n")
1668 self.ui.write(_("parent: %d:%s\n")
1669 % (pctx.rev(), hexfunc(pctx.node())),
1669 % (pctx.rev(), hexfunc(pctx.node())),
1670 label=label)
1670 label=label)
1671
1671
1672 if self.ui.debugflag and rev is not None:
1672 if self.ui.debugflag and rev is not None:
1673 mnode = ctx.manifestnode()
1673 mnode = ctx.manifestnode()
1674 # i18n: column positioning for "hg log"
1674 # i18n: column positioning for "hg log"
1675 self.ui.write(_("manifest: %d:%s\n") %
1675 self.ui.write(_("manifest: %d:%s\n") %
1676 (self.repo.manifestlog._revlog.rev(mnode),
1676 (self.repo.manifestlog._revlog.rev(mnode),
1677 hex(mnode)),
1677 hex(mnode)),
1678 label='ui.debug log.manifest')
1678 label='ui.debug log.manifest')
1679 # i18n: column positioning for "hg log"
1679 # i18n: column positioning for "hg log"
1680 self.ui.write(_("user: %s\n") % ctx.user(),
1680 self.ui.write(_("user: %s\n") % ctx.user(),
1681 label='log.user')
1681 label='log.user')
1682 # i18n: column positioning for "hg log"
1682 # i18n: column positioning for "hg log"
1683 self.ui.write(_("date: %s\n") % date,
1683 self.ui.write(_("date: %s\n") % date,
1684 label='log.date')
1684 label='log.date')
1685
1685
1686 if ctx.isunstable():
1686 if ctx.isunstable():
1687 # i18n: column positioning for "hg log"
1687 # i18n: column positioning for "hg log"
1688 instabilities = ctx.instabilities()
1688 instabilities = ctx.instabilities()
1689 self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
1689 self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
1690 label='log.instability')
1690 label='log.instability')
1691
1691
1692 self._exthook(ctx)
1692 self._exthook(ctx)
1693
1693
1694 if self.ui.debugflag:
1694 if self.ui.debugflag:
1695 files = ctx.p1().status(ctx)[:3]
1695 files = ctx.p1().status(ctx)[:3]
1696 for key, value in zip([# i18n: column positioning for "hg log"
1696 for key, value in zip([# i18n: column positioning for "hg log"
1697 _("files:"),
1697 _("files:"),
1698 # i18n: column positioning for "hg log"
1698 # i18n: column positioning for "hg log"
1699 _("files+:"),
1699 _("files+:"),
1700 # i18n: column positioning for "hg log"
1700 # i18n: column positioning for "hg log"
1701 _("files-:")], files):
1701 _("files-:")], files):
1702 if value:
1702 if value:
1703 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1703 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1704 label='ui.debug log.files')
1704 label='ui.debug log.files')
1705 elif ctx.files() and self.ui.verbose:
1705 elif ctx.files() and self.ui.verbose:
1706 # i18n: column positioning for "hg log"
1706 # i18n: column positioning for "hg log"
1707 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1707 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1708 label='ui.note log.files')
1708 label='ui.note log.files')
1709 if copies and self.ui.verbose:
1709 if copies and self.ui.verbose:
1710 copies = ['%s (%s)' % c for c in copies]
1710 copies = ['%s (%s)' % c for c in copies]
1711 # i18n: column positioning for "hg log"
1711 # i18n: column positioning for "hg log"
1712 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1712 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1713 label='ui.note log.copies')
1713 label='ui.note log.copies')
1714
1714
1715 extra = ctx.extra()
1715 extra = ctx.extra()
1716 if extra and self.ui.debugflag:
1716 if extra and self.ui.debugflag:
1717 for key, value in sorted(extra.items()):
1717 for key, value in sorted(extra.items()):
1718 # i18n: column positioning for "hg log"
1718 # i18n: column positioning for "hg log"
1719 self.ui.write(_("extra: %s=%s\n")
1719 self.ui.write(_("extra: %s=%s\n")
1720 % (key, util.escapestr(value)),
1720 % (key, util.escapestr(value)),
1721 label='ui.debug log.extra')
1721 label='ui.debug log.extra')
1722
1722
1723 description = ctx.description().strip()
1723 description = ctx.description().strip()
1724 if description:
1724 if description:
1725 if self.ui.verbose:
1725 if self.ui.verbose:
1726 self.ui.write(_("description:\n"),
1726 self.ui.write(_("description:\n"),
1727 label='ui.note log.description')
1727 label='ui.note log.description')
1728 self.ui.write(description,
1728 self.ui.write(description,
1729 label='ui.note log.description')
1729 label='ui.note log.description')
1730 self.ui.write("\n\n")
1730 self.ui.write("\n\n")
1731 else:
1731 else:
1732 # i18n: column positioning for "hg log"
1732 # i18n: column positioning for "hg log"
1733 self.ui.write(_("summary: %s\n") %
1733 self.ui.write(_("summary: %s\n") %
1734 description.splitlines()[0],
1734 description.splitlines()[0],
1735 label='log.summary')
1735 label='log.summary')
1736 self.ui.write("\n")
1736 self.ui.write("\n")
1737
1737
1738 self.showpatch(ctx, matchfn)
1738 self.showpatch(ctx, matchfn)
1739
1739
1740 def _exthook(self, ctx):
1740 def _exthook(self, ctx):
1741 '''empty method used by extension as a hook point
1741 '''empty method used by extension as a hook point
1742 '''
1742 '''
1743 pass
1743 pass
1744
1744
1745 def showpatch(self, ctx, matchfn):
1745 def showpatch(self, ctx, matchfn):
1746 if not matchfn:
1746 if not matchfn:
1747 matchfn = self.matchfn
1747 matchfn = self.matchfn
1748 if matchfn:
1748 if matchfn:
1749 stat = self.diffopts.get('stat')
1749 stat = self.diffopts.get('stat')
1750 diff = self.diffopts.get('patch')
1750 diff = self.diffopts.get('patch')
1751 diffopts = patch.diffallopts(self.ui, self.diffopts)
1751 diffopts = patch.diffallopts(self.ui, self.diffopts)
1752 node = ctx.node()
1752 node = ctx.node()
1753 prev = ctx.p1().node()
1753 prev = ctx.p1().node()
1754 if stat:
1754 if stat:
1755 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1755 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1756 match=matchfn, stat=True)
1756 match=matchfn, stat=True)
1757 if diff:
1757 if diff:
1758 if stat:
1758 if stat:
1759 self.ui.write("\n")
1759 self.ui.write("\n")
1760 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1760 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1761 match=matchfn, stat=False)
1761 match=matchfn, stat=False)
1762 self.ui.write("\n")
1762 self.ui.write("\n")
1763
1763
1764 class jsonchangeset(changeset_printer):
1764 class jsonchangeset(changeset_printer):
1765 '''format changeset information.'''
1765 '''format changeset information.'''
1766
1766
1767 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1767 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1768 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1768 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1769 self.cache = {}
1769 self.cache = {}
1770 self._first = True
1770 self._first = True
1771
1771
1772 def close(self):
1772 def close(self):
1773 if not self._first:
1773 if not self._first:
1774 self.ui.write("\n]\n")
1774 self.ui.write("\n]\n")
1775 else:
1775 else:
1776 self.ui.write("[]\n")
1776 self.ui.write("[]\n")
1777
1777
1778 def _show(self, ctx, copies, matchfn, props):
1778 def _show(self, ctx, copies, matchfn, props):
1779 '''show a single changeset or file revision'''
1779 '''show a single changeset or file revision'''
1780 rev = ctx.rev()
1780 rev = ctx.rev()
1781 if rev is None:
1781 if rev is None:
1782 jrev = jnode = 'null'
1782 jrev = jnode = 'null'
1783 else:
1783 else:
1784 jrev = '%d' % rev
1784 jrev = '%d' % rev
1785 jnode = '"%s"' % hex(ctx.node())
1785 jnode = '"%s"' % hex(ctx.node())
1786 j = encoding.jsonescape
1786 j = encoding.jsonescape
1787
1787
1788 if self._first:
1788 if self._first:
1789 self.ui.write("[\n {")
1789 self.ui.write("[\n {")
1790 self._first = False
1790 self._first = False
1791 else:
1791 else:
1792 self.ui.write(",\n {")
1792 self.ui.write(",\n {")
1793
1793
1794 if self.ui.quiet:
1794 if self.ui.quiet:
1795 self.ui.write(('\n "rev": %s') % jrev)
1795 self.ui.write(('\n "rev": %s') % jrev)
1796 self.ui.write((',\n "node": %s') % jnode)
1796 self.ui.write((',\n "node": %s') % jnode)
1797 self.ui.write('\n }')
1797 self.ui.write('\n }')
1798 return
1798 return
1799
1799
1800 self.ui.write(('\n "rev": %s') % jrev)
1800 self.ui.write(('\n "rev": %s') % jrev)
1801 self.ui.write((',\n "node": %s') % jnode)
1801 self.ui.write((',\n "node": %s') % jnode)
1802 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1802 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1803 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1803 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1804 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1804 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1805 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1805 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1806 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1806 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1807
1807
1808 self.ui.write((',\n "bookmarks": [%s]') %
1808 self.ui.write((',\n "bookmarks": [%s]') %
1809 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1809 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1810 self.ui.write((',\n "tags": [%s]') %
1810 self.ui.write((',\n "tags": [%s]') %
1811 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1811 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1812 self.ui.write((',\n "parents": [%s]') %
1812 self.ui.write((',\n "parents": [%s]') %
1813 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1813 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1814
1814
1815 if self.ui.debugflag:
1815 if self.ui.debugflag:
1816 if rev is None:
1816 if rev is None:
1817 jmanifestnode = 'null'
1817 jmanifestnode = 'null'
1818 else:
1818 else:
1819 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1819 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1820 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1820 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1821
1821
1822 self.ui.write((',\n "extra": {%s}') %
1822 self.ui.write((',\n "extra": {%s}') %
1823 ", ".join('"%s": "%s"' % (j(k), j(v))
1823 ", ".join('"%s": "%s"' % (j(k), j(v))
1824 for k, v in ctx.extra().items()))
1824 for k, v in ctx.extra().items()))
1825
1825
1826 files = ctx.p1().status(ctx)
1826 files = ctx.p1().status(ctx)
1827 self.ui.write((',\n "modified": [%s]') %
1827 self.ui.write((',\n "modified": [%s]') %
1828 ", ".join('"%s"' % j(f) for f in files[0]))
1828 ", ".join('"%s"' % j(f) for f in files[0]))
1829 self.ui.write((',\n "added": [%s]') %
1829 self.ui.write((',\n "added": [%s]') %
1830 ", ".join('"%s"' % j(f) for f in files[1]))
1830 ", ".join('"%s"' % j(f) for f in files[1]))
1831 self.ui.write((',\n "removed": [%s]') %
1831 self.ui.write((',\n "removed": [%s]') %
1832 ", ".join('"%s"' % j(f) for f in files[2]))
1832 ", ".join('"%s"' % j(f) for f in files[2]))
1833
1833
1834 elif self.ui.verbose:
1834 elif self.ui.verbose:
1835 self.ui.write((',\n "files": [%s]') %
1835 self.ui.write((',\n "files": [%s]') %
1836 ", ".join('"%s"' % j(f) for f in ctx.files()))
1836 ", ".join('"%s"' % j(f) for f in ctx.files()))
1837
1837
1838 if copies:
1838 if copies:
1839 self.ui.write((',\n "copies": {%s}') %
1839 self.ui.write((',\n "copies": {%s}') %
1840 ", ".join('"%s": "%s"' % (j(k), j(v))
1840 ", ".join('"%s": "%s"' % (j(k), j(v))
1841 for k, v in copies))
1841 for k, v in copies))
1842
1842
1843 matchfn = self.matchfn
1843 matchfn = self.matchfn
1844 if matchfn:
1844 if matchfn:
1845 stat = self.diffopts.get('stat')
1845 stat = self.diffopts.get('stat')
1846 diff = self.diffopts.get('patch')
1846 diff = self.diffopts.get('patch')
1847 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1847 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1848 node, prev = ctx.node(), ctx.p1().node()
1848 node, prev = ctx.node(), ctx.p1().node()
1849 if stat:
1849 if stat:
1850 self.ui.pushbuffer()
1850 self.ui.pushbuffer()
1851 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1851 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1852 match=matchfn, stat=True)
1852 match=matchfn, stat=True)
1853 self.ui.write((',\n "diffstat": "%s"')
1853 self.ui.write((',\n "diffstat": "%s"')
1854 % j(self.ui.popbuffer()))
1854 % j(self.ui.popbuffer()))
1855 if diff:
1855 if diff:
1856 self.ui.pushbuffer()
1856 self.ui.pushbuffer()
1857 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1857 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1858 match=matchfn, stat=False)
1858 match=matchfn, stat=False)
1859 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1859 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1860
1860
1861 self.ui.write("\n }")
1861 self.ui.write("\n }")
1862
1862
1863 class changeset_templater(changeset_printer):
1863 class changeset_templater(changeset_printer):
1864 '''format changeset information.'''
1864 '''format changeset information.'''
1865
1865
1866 # Arguments before "buffered" used to be positional. Consider not
1866 # Arguments before "buffered" used to be positional. Consider not
1867 # adding/removing arguments before "buffered" to not break callers.
1867 # adding/removing arguments before "buffered" to not break callers.
1868 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1868 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
1869 buffered=False):
1869 buffered=False):
1870 diffopts = diffopts or {}
1870 diffopts = diffopts or {}
1871
1871
1872 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1872 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1873 self.t = formatter.loadtemplater(ui, tmplspec,
1873 self.t = formatter.loadtemplater(ui, tmplspec,
1874 cache=templatekw.defaulttempl)
1874 cache=templatekw.defaulttempl)
1875 self._counter = itertools.count()
1875 self._counter = itertools.count()
1876 self.cache = {}
1876 self.cache = {}
1877
1877
1878 self._tref = tmplspec.ref
1878 self._tref = tmplspec.ref
1879 self._parts = {'header': '', 'footer': '',
1879 self._parts = {'header': '', 'footer': '',
1880 tmplspec.ref: tmplspec.ref,
1880 tmplspec.ref: tmplspec.ref,
1881 'docheader': '', 'docfooter': '',
1881 'docheader': '', 'docfooter': '',
1882 'separator': ''}
1882 'separator': ''}
1883 if tmplspec.mapfile:
1883 if tmplspec.mapfile:
1884 # find correct templates for current mode, for backward
1884 # find correct templates for current mode, for backward
1885 # compatibility with 'log -v/-q/--debug' using a mapfile
1885 # compatibility with 'log -v/-q/--debug' using a mapfile
1886 tmplmodes = [
1886 tmplmodes = [
1887 (True, ''),
1887 (True, ''),
1888 (self.ui.verbose, '_verbose'),
1888 (self.ui.verbose, '_verbose'),
1889 (self.ui.quiet, '_quiet'),
1889 (self.ui.quiet, '_quiet'),
1890 (self.ui.debugflag, '_debug'),
1890 (self.ui.debugflag, '_debug'),
1891 ]
1891 ]
1892 for mode, postfix in tmplmodes:
1892 for mode, postfix in tmplmodes:
1893 for t in self._parts:
1893 for t in self._parts:
1894 cur = t + postfix
1894 cur = t + postfix
1895 if mode and cur in self.t:
1895 if mode and cur in self.t:
1896 self._parts[t] = cur
1896 self._parts[t] = cur
1897 else:
1897 else:
1898 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1898 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
1899 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1899 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
1900 self._parts.update(m)
1900 self._parts.update(m)
1901
1901
1902 if self._parts['docheader']:
1902 if self._parts['docheader']:
1903 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1903 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1904
1904
1905 def close(self):
1905 def close(self):
1906 if self._parts['docfooter']:
1906 if self._parts['docfooter']:
1907 if not self.footer:
1907 if not self.footer:
1908 self.footer = ""
1908 self.footer = ""
1909 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1909 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1910 return super(changeset_templater, self).close()
1910 return super(changeset_templater, self).close()
1911
1911
1912 def _show(self, ctx, copies, matchfn, props):
1912 def _show(self, ctx, copies, matchfn, props):
1913 '''show a single changeset or file revision'''
1913 '''show a single changeset or file revision'''
1914 props = props.copy()
1914 props = props.copy()
1915 props.update(templatekw.keywords)
1915 props.update(templatekw.keywords)
1916 props['templ'] = self.t
1916 props['templ'] = self.t
1917 props['ctx'] = ctx
1917 props['ctx'] = ctx
1918 props['repo'] = self.repo
1918 props['repo'] = self.repo
1919 props['ui'] = self.repo.ui
1919 props['ui'] = self.repo.ui
1920 props['index'] = index = next(self._counter)
1920 props['index'] = index = next(self._counter)
1921 props['revcache'] = {'copies': copies}
1921 props['revcache'] = {'copies': copies}
1922 props['cache'] = self.cache
1922 props['cache'] = self.cache
1923 props = pycompat.strkwargs(props)
1923 props = pycompat.strkwargs(props)
1924
1924
1925 # write separator, which wouldn't work well with the header part below
1925 # write separator, which wouldn't work well with the header part below
1926 # since there's inherently a conflict between header (across items) and
1926 # since there's inherently a conflict between header (across items) and
1927 # separator (per item)
1927 # separator (per item)
1928 if self._parts['separator'] and index > 0:
1928 if self._parts['separator'] and index > 0:
1929 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1929 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
1930
1930
1931 # write header
1931 # write header
1932 if self._parts['header']:
1932 if self._parts['header']:
1933 h = templater.stringify(self.t(self._parts['header'], **props))
1933 h = templater.stringify(self.t(self._parts['header'], **props))
1934 if self.buffered:
1934 if self.buffered:
1935 self.header[ctx.rev()] = h
1935 self.header[ctx.rev()] = h
1936 else:
1936 else:
1937 if self.lastheader != h:
1937 if self.lastheader != h:
1938 self.lastheader = h
1938 self.lastheader = h
1939 self.ui.write(h)
1939 self.ui.write(h)
1940
1940
1941 # write changeset metadata, then patch if requested
1941 # write changeset metadata, then patch if requested
1942 key = self._parts[self._tref]
1942 key = self._parts[self._tref]
1943 self.ui.write(templater.stringify(self.t(key, **props)))
1943 self.ui.write(templater.stringify(self.t(key, **props)))
1944 self.showpatch(ctx, matchfn)
1944 self.showpatch(ctx, matchfn)
1945
1945
1946 if self._parts['footer']:
1946 if self._parts['footer']:
1947 if not self.footer:
1947 if not self.footer:
1948 self.footer = templater.stringify(
1948 self.footer = templater.stringify(
1949 self.t(self._parts['footer'], **props))
1949 self.t(self._parts['footer'], **props))
1950
1950
1951 def logtemplatespec(tmpl, mapfile):
1951 def logtemplatespec(tmpl, mapfile):
1952 if mapfile:
1952 if mapfile:
1953 return formatter.templatespec('changeset', tmpl, mapfile)
1953 return formatter.templatespec('changeset', tmpl, mapfile)
1954 else:
1954 else:
1955 return formatter.templatespec('', tmpl, None)
1955 return formatter.templatespec('', tmpl, None)
1956
1956
1957 def _lookuplogtemplate(ui, tmpl, style):
1957 def _lookuplogtemplate(ui, tmpl, style):
1958 """Find the template matching the given template spec or style
1958 """Find the template matching the given template spec or style
1959
1959
1960 See formatter.lookuptemplate() for details.
1960 See formatter.lookuptemplate() for details.
1961 """
1961 """
1962
1962
1963 # ui settings
1963 # ui settings
1964 if not tmpl and not style: # template are stronger than style
1964 if not tmpl and not style: # template are stronger than style
1965 tmpl = ui.config('ui', 'logtemplate')
1965 tmpl = ui.config('ui', 'logtemplate')
1966 if tmpl:
1966 if tmpl:
1967 return logtemplatespec(templater.unquotestring(tmpl), None)
1967 return logtemplatespec(templater.unquotestring(tmpl), None)
1968 else:
1968 else:
1969 style = util.expandpath(ui.config('ui', 'style'))
1969 style = util.expandpath(ui.config('ui', 'style'))
1970
1970
1971 if not tmpl and style:
1971 if not tmpl and style:
1972 mapfile = style
1972 mapfile = style
1973 if not os.path.split(mapfile)[0]:
1973 if not os.path.split(mapfile)[0]:
1974 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1974 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1975 or templater.templatepath(mapfile))
1975 or templater.templatepath(mapfile))
1976 if mapname:
1976 if mapname:
1977 mapfile = mapname
1977 mapfile = mapname
1978 return logtemplatespec(None, mapfile)
1978 return logtemplatespec(None, mapfile)
1979
1979
1980 if not tmpl:
1980 if not tmpl:
1981 return logtemplatespec(None, None)
1981 return logtemplatespec(None, None)
1982
1982
1983 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1983 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1984
1984
1985 def makelogtemplater(ui, repo, tmpl, buffered=False):
1985 def makelogtemplater(ui, repo, tmpl, buffered=False):
1986 """Create a changeset_templater from a literal template 'tmpl'"""
1986 """Create a changeset_templater from a literal template 'tmpl'"""
1987 spec = logtemplatespec(tmpl, None)
1987 spec = logtemplatespec(tmpl, None)
1988 return changeset_templater(ui, repo, spec, buffered=buffered)
1988 return changeset_templater(ui, repo, spec, buffered=buffered)
1989
1989
1990 def show_changeset(ui, repo, opts, buffered=False):
1990 def show_changeset(ui, repo, opts, buffered=False):
1991 """show one changeset using template or regular display.
1991 """show one changeset using template or regular display.
1992
1992
1993 Display format will be the first non-empty hit of:
1993 Display format will be the first non-empty hit of:
1994 1. option 'template'
1994 1. option 'template'
1995 2. option 'style'
1995 2. option 'style'
1996 3. [ui] setting 'logtemplate'
1996 3. [ui] setting 'logtemplate'
1997 4. [ui] setting 'style'
1997 4. [ui] setting 'style'
1998 If all of these values are either the unset or the empty string,
1998 If all of these values are either the unset or the empty string,
1999 regular display via changeset_printer() is done.
1999 regular display via changeset_printer() is done.
2000 """
2000 """
2001 # options
2001 # options
2002 matchfn = None
2002 matchfn = None
2003 if opts.get('patch') or opts.get('stat'):
2003 if opts.get('patch') or opts.get('stat'):
2004 matchfn = scmutil.matchall(repo)
2004 matchfn = scmutil.matchall(repo)
2005
2005
2006 if opts.get('template') == 'json':
2006 if opts.get('template') == 'json':
2007 return jsonchangeset(ui, repo, matchfn, opts, buffered)
2007 return jsonchangeset(ui, repo, matchfn, opts, buffered)
2008
2008
2009 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
2009 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
2010
2010
2011 if not spec.ref and not spec.tmpl and not spec.mapfile:
2011 if not spec.ref and not spec.tmpl and not spec.mapfile:
2012 return changeset_printer(ui, repo, matchfn, opts, buffered)
2012 return changeset_printer(ui, repo, matchfn, opts, buffered)
2013
2013
2014 return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
2014 return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
2015
2015
2016 def showmarker(fm, marker, index=None):
2016 def showmarker(fm, marker, index=None):
2017 """utility function to display obsolescence marker in a readable way
2017 """utility function to display obsolescence marker in a readable way
2018
2018
2019 To be used by debug function."""
2019 To be used by debug function."""
2020 if index is not None:
2020 if index is not None:
2021 fm.write('index', '%i ', index)
2021 fm.write('index', '%i ', index)
2022 fm.write('prednode', '%s ', hex(marker.prednode()))
2022 fm.write('prednode', '%s ', hex(marker.prednode()))
2023 succs = marker.succnodes()
2023 succs = marker.succnodes()
2024 fm.condwrite(succs, 'succnodes', '%s ',
2024 fm.condwrite(succs, 'succnodes', '%s ',
2025 fm.formatlist(map(hex, succs), name='node'))
2025 fm.formatlist(map(hex, succs), name='node'))
2026 fm.write('flag', '%X ', marker.flags())
2026 fm.write('flag', '%X ', marker.flags())
2027 parents = marker.parentnodes()
2027 parents = marker.parentnodes()
2028 if parents is not None:
2028 if parents is not None:
2029 fm.write('parentnodes', '{%s} ',
2029 fm.write('parentnodes', '{%s} ',
2030 fm.formatlist(map(hex, parents), name='node', sep=', '))
2030 fm.formatlist(map(hex, parents), name='node', sep=', '))
2031 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2031 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
2032 meta = marker.metadata().copy()
2032 meta = marker.metadata().copy()
2033 meta.pop('date', None)
2033 meta.pop('date', None)
2034 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2034 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
2035 fm.plain('\n')
2035 fm.plain('\n')
2036
2036
2037 def finddate(ui, repo, date):
2037 def finddate(ui, repo, date):
2038 """Find the tipmost changeset that matches the given date spec"""
2038 """Find the tipmost changeset that matches the given date spec"""
2039
2039
2040 df = util.matchdate(date)
2040 df = util.matchdate(date)
2041 m = scmutil.matchall(repo)
2041 m = scmutil.matchall(repo)
2042 results = {}
2042 results = {}
2043
2043
2044 def prep(ctx, fns):
2044 def prep(ctx, fns):
2045 d = ctx.date()
2045 d = ctx.date()
2046 if df(d[0]):
2046 if df(d[0]):
2047 results[ctx.rev()] = d
2047 results[ctx.rev()] = d
2048
2048
2049 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2049 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
2050 rev = ctx.rev()
2050 rev = ctx.rev()
2051 if rev in results:
2051 if rev in results:
2052 ui.status(_("found revision %s from %s\n") %
2052 ui.status(_("found revision %s from %s\n") %
2053 (rev, util.datestr(results[rev])))
2053 (rev, util.datestr(results[rev])))
2054 return '%d' % rev
2054 return '%d' % rev
2055
2055
2056 raise error.Abort(_("revision matching date not found"))
2056 raise error.Abort(_("revision matching date not found"))
2057
2057
2058 def increasingwindows(windowsize=8, sizelimit=512):
2058 def increasingwindows(windowsize=8, sizelimit=512):
2059 while True:
2059 while True:
2060 yield windowsize
2060 yield windowsize
2061 if windowsize < sizelimit:
2061 if windowsize < sizelimit:
2062 windowsize *= 2
2062 windowsize *= 2
2063
2063
2064 class FileWalkError(Exception):
2064 class FileWalkError(Exception):
2065 pass
2065 pass
2066
2066
2067 def walkfilerevs(repo, match, follow, revs, fncache):
2067 def walkfilerevs(repo, match, follow, revs, fncache):
2068 '''Walks the file history for the matched files.
2068 '''Walks the file history for the matched files.
2069
2069
2070 Returns the changeset revs that are involved in the file history.
2070 Returns the changeset revs that are involved in the file history.
2071
2071
2072 Throws FileWalkError if the file history can't be walked using
2072 Throws FileWalkError if the file history can't be walked using
2073 filelogs alone.
2073 filelogs alone.
2074 '''
2074 '''
2075 wanted = set()
2075 wanted = set()
2076 copies = []
2076 copies = []
2077 minrev, maxrev = min(revs), max(revs)
2077 minrev, maxrev = min(revs), max(revs)
2078 def filerevgen(filelog, last):
2078 def filerevgen(filelog, last):
2079 """
2079 """
2080 Only files, no patterns. Check the history of each file.
2080 Only files, no patterns. Check the history of each file.
2081
2081
2082 Examines filelog entries within minrev, maxrev linkrev range
2082 Examines filelog entries within minrev, maxrev linkrev range
2083 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2083 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2084 tuples in backwards order
2084 tuples in backwards order
2085 """
2085 """
2086 cl_count = len(repo)
2086 cl_count = len(repo)
2087 revs = []
2087 revs = []
2088 for j in xrange(0, last + 1):
2088 for j in xrange(0, last + 1):
2089 linkrev = filelog.linkrev(j)
2089 linkrev = filelog.linkrev(j)
2090 if linkrev < minrev:
2090 if linkrev < minrev:
2091 continue
2091 continue
2092 # only yield rev for which we have the changelog, it can
2092 # only yield rev for which we have the changelog, it can
2093 # happen while doing "hg log" during a pull or commit
2093 # happen while doing "hg log" during a pull or commit
2094 if linkrev >= cl_count:
2094 if linkrev >= cl_count:
2095 break
2095 break
2096
2096
2097 parentlinkrevs = []
2097 parentlinkrevs = []
2098 for p in filelog.parentrevs(j):
2098 for p in filelog.parentrevs(j):
2099 if p != nullrev:
2099 if p != nullrev:
2100 parentlinkrevs.append(filelog.linkrev(p))
2100 parentlinkrevs.append(filelog.linkrev(p))
2101 n = filelog.node(j)
2101 n = filelog.node(j)
2102 revs.append((linkrev, parentlinkrevs,
2102 revs.append((linkrev, parentlinkrevs,
2103 follow and filelog.renamed(n)))
2103 follow and filelog.renamed(n)))
2104
2104
2105 return reversed(revs)
2105 return reversed(revs)
2106 def iterfiles():
2106 def iterfiles():
2107 pctx = repo['.']
2107 pctx = repo['.']
2108 for filename in match.files():
2108 for filename in match.files():
2109 if follow:
2109 if follow:
2110 if filename not in pctx:
2110 if filename not in pctx:
2111 raise error.Abort(_('cannot follow file not in parent '
2111 raise error.Abort(_('cannot follow file not in parent '
2112 'revision: "%s"') % filename)
2112 'revision: "%s"') % filename)
2113 yield filename, pctx[filename].filenode()
2113 yield filename, pctx[filename].filenode()
2114 else:
2114 else:
2115 yield filename, None
2115 yield filename, None
2116 for filename_node in copies:
2116 for filename_node in copies:
2117 yield filename_node
2117 yield filename_node
2118
2118
2119 for file_, node in iterfiles():
2119 for file_, node in iterfiles():
2120 filelog = repo.file(file_)
2120 filelog = repo.file(file_)
2121 if not len(filelog):
2121 if not len(filelog):
2122 if node is None:
2122 if node is None:
2123 # A zero count may be a directory or deleted file, so
2123 # A zero count may be a directory or deleted file, so
2124 # try to find matching entries on the slow path.
2124 # try to find matching entries on the slow path.
2125 if follow:
2125 if follow:
2126 raise error.Abort(
2126 raise error.Abort(
2127 _('cannot follow nonexistent file: "%s"') % file_)
2127 _('cannot follow nonexistent file: "%s"') % file_)
2128 raise FileWalkError("Cannot walk via filelog")
2128 raise FileWalkError("Cannot walk via filelog")
2129 else:
2129 else:
2130 continue
2130 continue
2131
2131
2132 if node is None:
2132 if node is None:
2133 last = len(filelog) - 1
2133 last = len(filelog) - 1
2134 else:
2134 else:
2135 last = filelog.rev(node)
2135 last = filelog.rev(node)
2136
2136
2137 # keep track of all ancestors of the file
2137 # keep track of all ancestors of the file
2138 ancestors = {filelog.linkrev(last)}
2138 ancestors = {filelog.linkrev(last)}
2139
2139
2140 # iterate from latest to oldest revision
2140 # iterate from latest to oldest revision
2141 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2141 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
2142 if not follow:
2142 if not follow:
2143 if rev > maxrev:
2143 if rev > maxrev:
2144 continue
2144 continue
2145 else:
2145 else:
2146 # Note that last might not be the first interesting
2146 # Note that last might not be the first interesting
2147 # rev to us:
2147 # rev to us:
2148 # if the file has been changed after maxrev, we'll
2148 # if the file has been changed after maxrev, we'll
2149 # have linkrev(last) > maxrev, and we still need
2149 # have linkrev(last) > maxrev, and we still need
2150 # to explore the file graph
2150 # to explore the file graph
2151 if rev not in ancestors:
2151 if rev not in ancestors:
2152 continue
2152 continue
2153 # XXX insert 1327 fix here
2153 # XXX insert 1327 fix here
2154 if flparentlinkrevs:
2154 if flparentlinkrevs:
2155 ancestors.update(flparentlinkrevs)
2155 ancestors.update(flparentlinkrevs)
2156
2156
2157 fncache.setdefault(rev, []).append(file_)
2157 fncache.setdefault(rev, []).append(file_)
2158 wanted.add(rev)
2158 wanted.add(rev)
2159 if copied:
2159 if copied:
2160 copies.append(copied)
2160 copies.append(copied)
2161
2161
2162 return wanted
2162 return wanted
2163
2163
2164 class _followfilter(object):
2164 class _followfilter(object):
2165 def __init__(self, repo, onlyfirst=False):
2165 def __init__(self, repo, onlyfirst=False):
2166 self.repo = repo
2166 self.repo = repo
2167 self.startrev = nullrev
2167 self.startrev = nullrev
2168 self.roots = set()
2168 self.roots = set()
2169 self.onlyfirst = onlyfirst
2169 self.onlyfirst = onlyfirst
2170
2170
2171 def match(self, rev):
2171 def match(self, rev):
2172 def realparents(rev):
2172 def realparents(rev):
2173 if self.onlyfirst:
2173 if self.onlyfirst:
2174 return self.repo.changelog.parentrevs(rev)[0:1]
2174 return self.repo.changelog.parentrevs(rev)[0:1]
2175 else:
2175 else:
2176 return filter(lambda x: x != nullrev,
2176 return filter(lambda x: x != nullrev,
2177 self.repo.changelog.parentrevs(rev))
2177 self.repo.changelog.parentrevs(rev))
2178
2178
2179 if self.startrev == nullrev:
2179 if self.startrev == nullrev:
2180 self.startrev = rev
2180 self.startrev = rev
2181 return True
2181 return True
2182
2182
2183 if rev > self.startrev:
2183 if rev > self.startrev:
2184 # forward: all descendants
2184 # forward: all descendants
2185 if not self.roots:
2185 if not self.roots:
2186 self.roots.add(self.startrev)
2186 self.roots.add(self.startrev)
2187 for parent in realparents(rev):
2187 for parent in realparents(rev):
2188 if parent in self.roots:
2188 if parent in self.roots:
2189 self.roots.add(rev)
2189 self.roots.add(rev)
2190 return True
2190 return True
2191 else:
2191 else:
2192 # backwards: all parents
2192 # backwards: all parents
2193 if not self.roots:
2193 if not self.roots:
2194 self.roots.update(realparents(self.startrev))
2194 self.roots.update(realparents(self.startrev))
2195 if rev in self.roots:
2195 if rev in self.roots:
2196 self.roots.remove(rev)
2196 self.roots.remove(rev)
2197 self.roots.update(realparents(rev))
2197 self.roots.update(realparents(rev))
2198 return True
2198 return True
2199
2199
2200 return False
2200 return False
2201
2201
2202 def walkchangerevs(repo, match, opts, prepare):
2202 def walkchangerevs(repo, match, opts, prepare):
2203 '''Iterate over files and the revs in which they changed.
2203 '''Iterate over files and the revs in which they changed.
2204
2204
2205 Callers most commonly need to iterate backwards over the history
2205 Callers most commonly need to iterate backwards over the history
2206 in which they are interested. Doing so has awful (quadratic-looking)
2206 in which they are interested. Doing so has awful (quadratic-looking)
2207 performance, so we use iterators in a "windowed" way.
2207 performance, so we use iterators in a "windowed" way.
2208
2208
2209 We walk a window of revisions in the desired order. Within the
2209 We walk a window of revisions in the desired order. Within the
2210 window, we first walk forwards to gather data, then in the desired
2210 window, we first walk forwards to gather data, then in the desired
2211 order (usually backwards) to display it.
2211 order (usually backwards) to display it.
2212
2212
2213 This function returns an iterator yielding contexts. Before
2213 This function returns an iterator yielding contexts. Before
2214 yielding each context, the iterator will first call the prepare
2214 yielding each context, the iterator will first call the prepare
2215 function on each context in the window in forward order.'''
2215 function on each context in the window in forward order.'''
2216
2216
2217 follow = opts.get('follow') or opts.get('follow_first')
2217 follow = opts.get('follow') or opts.get('follow_first')
2218 revs = _logrevs(repo, opts)
2218 revs = _logrevs(repo, opts)
2219 if not revs:
2219 if not revs:
2220 return []
2220 return []
2221 wanted = set()
2221 wanted = set()
2222 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2222 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2223 opts.get('removed'))
2223 opts.get('removed'))
2224 fncache = {}
2224 fncache = {}
2225 change = repo.changectx
2225 change = repo.changectx
2226
2226
2227 # First step is to fill wanted, the set of revisions that we want to yield.
2227 # First step is to fill wanted, the set of revisions that we want to yield.
2228 # When it does not induce extra cost, we also fill fncache for revisions in
2228 # When it does not induce extra cost, we also fill fncache for revisions in
2229 # wanted: a cache of filenames that were changed (ctx.files()) and that
2229 # wanted: a cache of filenames that were changed (ctx.files()) and that
2230 # match the file filtering conditions.
2230 # match the file filtering conditions.
2231
2231
2232 if match.always():
2232 if match.always():
2233 # No files, no patterns. Display all revs.
2233 # No files, no patterns. Display all revs.
2234 wanted = revs
2234 wanted = revs
2235 elif not slowpath:
2235 elif not slowpath:
2236 # We only have to read through the filelog to find wanted revisions
2236 # We only have to read through the filelog to find wanted revisions
2237
2237
2238 try:
2238 try:
2239 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2239 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2240 except FileWalkError:
2240 except FileWalkError:
2241 slowpath = True
2241 slowpath = True
2242
2242
2243 # We decided to fall back to the slowpath because at least one
2243 # We decided to fall back to the slowpath because at least one
2244 # of the paths was not a file. Check to see if at least one of them
2244 # of the paths was not a file. Check to see if at least one of them
2245 # existed in history, otherwise simply return
2245 # existed in history, otherwise simply return
2246 for path in match.files():
2246 for path in match.files():
2247 if path == '.' or path in repo.store:
2247 if path == '.' or path in repo.store:
2248 break
2248 break
2249 else:
2249 else:
2250 return []
2250 return []
2251
2251
2252 if slowpath:
2252 if slowpath:
2253 # We have to read the changelog to match filenames against
2253 # We have to read the changelog to match filenames against
2254 # changed files
2254 # changed files
2255
2255
2256 if follow:
2256 if follow:
2257 raise error.Abort(_('can only follow copies/renames for explicit '
2257 raise error.Abort(_('can only follow copies/renames for explicit '
2258 'filenames'))
2258 'filenames'))
2259
2259
2260 # The slow path checks files modified in every changeset.
2260 # The slow path checks files modified in every changeset.
2261 # This is really slow on large repos, so compute the set lazily.
2261 # This is really slow on large repos, so compute the set lazily.
2262 class lazywantedset(object):
2262 class lazywantedset(object):
2263 def __init__(self):
2263 def __init__(self):
2264 self.set = set()
2264 self.set = set()
2265 self.revs = set(revs)
2265 self.revs = set(revs)
2266
2266
2267 # No need to worry about locality here because it will be accessed
2267 # No need to worry about locality here because it will be accessed
2268 # in the same order as the increasing window below.
2268 # in the same order as the increasing window below.
2269 def __contains__(self, value):
2269 def __contains__(self, value):
2270 if value in self.set:
2270 if value in self.set:
2271 return True
2271 return True
2272 elif not value in self.revs:
2272 elif not value in self.revs:
2273 return False
2273 return False
2274 else:
2274 else:
2275 self.revs.discard(value)
2275 self.revs.discard(value)
2276 ctx = change(value)
2276 ctx = change(value)
2277 matches = filter(match, ctx.files())
2277 matches = filter(match, ctx.files())
2278 if matches:
2278 if matches:
2279 fncache[value] = matches
2279 fncache[value] = matches
2280 self.set.add(value)
2280 self.set.add(value)
2281 return True
2281 return True
2282 return False
2282 return False
2283
2283
2284 def discard(self, value):
2284 def discard(self, value):
2285 self.revs.discard(value)
2285 self.revs.discard(value)
2286 self.set.discard(value)
2286 self.set.discard(value)
2287
2287
2288 wanted = lazywantedset()
2288 wanted = lazywantedset()
2289
2289
2290 # it might be worthwhile to do this in the iterator if the rev range
2290 # it might be worthwhile to do this in the iterator if the rev range
2291 # is descending and the prune args are all within that range
2291 # is descending and the prune args are all within that range
2292 for rev in opts.get('prune', ()):
2292 for rev in opts.get('prune', ()):
2293 rev = repo[rev].rev()
2293 rev = repo[rev].rev()
2294 ff = _followfilter(repo)
2294 ff = _followfilter(repo)
2295 stop = min(revs[0], revs[-1])
2295 stop = min(revs[0], revs[-1])
2296 for x in xrange(rev, stop - 1, -1):
2296 for x in xrange(rev, stop - 1, -1):
2297 if ff.match(x):
2297 if ff.match(x):
2298 wanted = wanted - [x]
2298 wanted = wanted - [x]
2299
2299
2300 # Now that wanted is correctly initialized, we can iterate over the
2300 # Now that wanted is correctly initialized, we can iterate over the
2301 # revision range, yielding only revisions in wanted.
2301 # revision range, yielding only revisions in wanted.
2302 def iterate():
2302 def iterate():
2303 if follow and match.always():
2303 if follow and match.always():
2304 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2304 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
2305 def want(rev):
2305 def want(rev):
2306 return ff.match(rev) and rev in wanted
2306 return ff.match(rev) and rev in wanted
2307 else:
2307 else:
2308 def want(rev):
2308 def want(rev):
2309 return rev in wanted
2309 return rev in wanted
2310
2310
2311 it = iter(revs)
2311 it = iter(revs)
2312 stopiteration = False
2312 stopiteration = False
2313 for windowsize in increasingwindows():
2313 for windowsize in increasingwindows():
2314 nrevs = []
2314 nrevs = []
2315 for i in xrange(windowsize):
2315 for i in xrange(windowsize):
2316 rev = next(it, None)
2316 rev = next(it, None)
2317 if rev is None:
2317 if rev is None:
2318 stopiteration = True
2318 stopiteration = True
2319 break
2319 break
2320 elif want(rev):
2320 elif want(rev):
2321 nrevs.append(rev)
2321 nrevs.append(rev)
2322 for rev in sorted(nrevs):
2322 for rev in sorted(nrevs):
2323 fns = fncache.get(rev)
2323 fns = fncache.get(rev)
2324 ctx = change(rev)
2324 ctx = change(rev)
2325 if not fns:
2325 if not fns:
2326 def fns_generator():
2326 def fns_generator():
2327 for f in ctx.files():
2327 for f in ctx.files():
2328 if match(f):
2328 if match(f):
2329 yield f
2329 yield f
2330 fns = fns_generator()
2330 fns = fns_generator()
2331 prepare(ctx, fns)
2331 prepare(ctx, fns)
2332 for rev in nrevs:
2332 for rev in nrevs:
2333 yield change(rev)
2333 yield change(rev)
2334
2334
2335 if stopiteration:
2335 if stopiteration:
2336 break
2336 break
2337
2337
2338 return iterate()
2338 return iterate()
2339
2339
2340 def _makefollowlogfilematcher(repo, files, followfirst):
2340 def _makefollowlogfilematcher(repo, files, followfirst):
2341 # When displaying a revision with --patch --follow FILE, we have
2341 # When displaying a revision with --patch --follow FILE, we have
2342 # to know which file of the revision must be diffed. With
2342 # to know which file of the revision must be diffed. With
2343 # --follow, we want the names of the ancestors of FILE in the
2343 # --follow, we want the names of the ancestors of FILE in the
2344 # revision, stored in "fcache". "fcache" is populated by
2344 # revision, stored in "fcache". "fcache" is populated by
2345 # reproducing the graph traversal already done by --follow revset
2345 # reproducing the graph traversal already done by --follow revset
2346 # and relating revs to file names (which is not "correct" but
2346 # and relating revs to file names (which is not "correct" but
2347 # good enough).
2347 # good enough).
2348 fcache = {}
2348 fcache = {}
2349 fcacheready = [False]
2349 fcacheready = [False]
2350 pctx = repo['.']
2350 pctx = repo['.']
2351
2351
2352 def populate():
2352 def populate():
2353 for fn in files:
2353 for fn in files:
2354 fctx = pctx[fn]
2354 fctx = pctx[fn]
2355 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2355 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2356 for c in fctx.ancestors(followfirst=followfirst):
2356 for c in fctx.ancestors(followfirst=followfirst):
2357 fcache.setdefault(c.rev(), set()).add(c.path())
2357 fcache.setdefault(c.rev(), set()).add(c.path())
2358
2358
2359 def filematcher(rev):
2359 def filematcher(rev):
2360 if not fcacheready[0]:
2360 if not fcacheready[0]:
2361 # Lazy initialization
2361 # Lazy initialization
2362 fcacheready[0] = True
2362 fcacheready[0] = True
2363 populate()
2363 populate()
2364 return scmutil.matchfiles(repo, fcache.get(rev, []))
2364 return scmutil.matchfiles(repo, fcache.get(rev, []))
2365
2365
2366 return filematcher
2366 return filematcher
2367
2367
2368 def _makenofollowlogfilematcher(repo, pats, opts):
2368 def _makenofollowlogfilematcher(repo, pats, opts):
2369 '''hook for extensions to override the filematcher for non-follow cases'''
2369 '''hook for extensions to override the filematcher for non-follow cases'''
2370 return None
2370 return None
2371
2371
2372 def _makelogrevset(repo, pats, opts, revs):
2372 def _makelogrevset(repo, pats, opts, revs):
2373 """Return (expr, filematcher) where expr is a revset string built
2373 """Return (expr, filematcher) where expr is a revset string built
2374 from log options and file patterns or None. If --stat or --patch
2374 from log options and file patterns or None. If --stat or --patch
2375 are not passed filematcher is None. Otherwise it is a callable
2375 are not passed filematcher is None. Otherwise it is a callable
2376 taking a revision number and returning a match objects filtering
2376 taking a revision number and returning a match objects filtering
2377 the files to be detailed when displaying the revision.
2377 the files to be detailed when displaying the revision.
2378 """
2378 """
2379 opt2revset = {
2379 opt2revset = {
2380 'no_merges': ('not merge()', None),
2380 'no_merges': ('not merge()', None),
2381 'only_merges': ('merge()', None),
2381 'only_merges': ('merge()', None),
2382 '_ancestors': ('ancestors(%(val)s)', None),
2382 '_ancestors': ('ancestors(%(val)s)', None),
2383 '_fancestors': ('_firstancestors(%(val)s)', None),
2383 '_fancestors': ('_firstancestors(%(val)s)', None),
2384 '_descendants': ('descendants(%(val)s)', None),
2384 '_descendants': ('descendants(%(val)s)', None),
2385 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2385 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2386 '_matchfiles': ('_matchfiles(%(val)s)', None),
2386 '_matchfiles': ('_matchfiles(%(val)s)', None),
2387 'date': ('date(%(val)r)', None),
2387 'date': ('date(%(val)r)', None),
2388 'branch': ('branch(%(val)r)', ' or '),
2388 'branch': ('branch(%(val)r)', ' or '),
2389 '_patslog': ('filelog(%(val)r)', ' or '),
2389 '_patslog': ('filelog(%(val)r)', ' or '),
2390 '_patsfollow': ('follow(%(val)r)', ' or '),
2390 '_patsfollow': ('follow(%(val)r)', ' or '),
2391 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2391 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2392 'keyword': ('keyword(%(val)r)', ' or '),
2392 'keyword': ('keyword(%(val)r)', ' or '),
2393 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2393 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2394 'user': ('user(%(val)r)', ' or '),
2394 'user': ('user(%(val)r)', ' or '),
2395 }
2395 }
2396
2396
2397 opts = dict(opts)
2397 opts = dict(opts)
2398 # follow or not follow?
2398 # follow or not follow?
2399 follow = opts.get('follow') or opts.get('follow_first')
2399 follow = opts.get('follow') or opts.get('follow_first')
2400 if opts.get('follow_first'):
2400 if opts.get('follow_first'):
2401 followfirst = 1
2401 followfirst = 1
2402 else:
2402 else:
2403 followfirst = 0
2403 followfirst = 0
2404 # --follow with FILE behavior depends on revs...
2404 # --follow with FILE behavior depends on revs...
2405 it = iter(revs)
2405 it = iter(revs)
2406 startrev = next(it)
2406 startrev = next(it)
2407 followdescendants = startrev < next(it, startrev)
2407 followdescendants = startrev < next(it, startrev)
2408
2408
2409 # branch and only_branch are really aliases and must be handled at
2409 # branch and only_branch are really aliases and must be handled at
2410 # the same time
2410 # the same time
2411 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2411 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2412 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2412 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2413 # pats/include/exclude are passed to match.match() directly in
2413 # pats/include/exclude are passed to match.match() directly in
2414 # _matchfiles() revset but walkchangerevs() builds its matcher with
2414 # _matchfiles() revset but walkchangerevs() builds its matcher with
2415 # scmutil.match(). The difference is input pats are globbed on
2415 # scmutil.match(). The difference is input pats are globbed on
2416 # platforms without shell expansion (windows).
2416 # platforms without shell expansion (windows).
2417 wctx = repo[None]
2417 wctx = repo[None]
2418 match, pats = scmutil.matchandpats(wctx, pats, opts)
2418 match, pats = scmutil.matchandpats(wctx, pats, opts)
2419 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2419 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2420 opts.get('removed'))
2420 opts.get('removed'))
2421 if not slowpath:
2421 if not slowpath:
2422 for f in match.files():
2422 for f in match.files():
2423 if follow and f not in wctx:
2423 if follow and f not in wctx:
2424 # If the file exists, it may be a directory, so let it
2424 # If the file exists, it may be a directory, so let it
2425 # take the slow path.
2425 # take the slow path.
2426 if os.path.exists(repo.wjoin(f)):
2426 if os.path.exists(repo.wjoin(f)):
2427 slowpath = True
2427 slowpath = True
2428 continue
2428 continue
2429 else:
2429 else:
2430 raise error.Abort(_('cannot follow file not in parent '
2430 raise error.Abort(_('cannot follow file not in parent '
2431 'revision: "%s"') % f)
2431 'revision: "%s"') % f)
2432 filelog = repo.file(f)
2432 filelog = repo.file(f)
2433 if not filelog:
2433 if not filelog:
2434 # A zero count may be a directory or deleted file, so
2434 # A zero count may be a directory or deleted file, so
2435 # try to find matching entries on the slow path.
2435 # try to find matching entries on the slow path.
2436 if follow:
2436 if follow:
2437 raise error.Abort(
2437 raise error.Abort(
2438 _('cannot follow nonexistent file: "%s"') % f)
2438 _('cannot follow nonexistent file: "%s"') % f)
2439 slowpath = True
2439 slowpath = True
2440
2440
2441 # We decided to fall back to the slowpath because at least one
2441 # We decided to fall back to the slowpath because at least one
2442 # of the paths was not a file. Check to see if at least one of them
2442 # of the paths was not a file. Check to see if at least one of them
2443 # existed in history - in that case, we'll continue down the
2443 # existed in history - in that case, we'll continue down the
2444 # slowpath; otherwise, we can turn off the slowpath
2444 # slowpath; otherwise, we can turn off the slowpath
2445 if slowpath:
2445 if slowpath:
2446 for path in match.files():
2446 for path in match.files():
2447 if path == '.' or path in repo.store:
2447 if path == '.' or path in repo.store:
2448 break
2448 break
2449 else:
2449 else:
2450 slowpath = False
2450 slowpath = False
2451
2451
2452 fpats = ('_patsfollow', '_patsfollowfirst')
2452 fpats = ('_patsfollow', '_patsfollowfirst')
2453 fnopats = (('_ancestors', '_fancestors'),
2453 fnopats = (('_ancestors', '_fancestors'),
2454 ('_descendants', '_fdescendants'))
2454 ('_descendants', '_fdescendants'))
2455 if slowpath:
2455 if slowpath:
2456 # See walkchangerevs() slow path.
2456 # See walkchangerevs() slow path.
2457 #
2457 #
2458 # pats/include/exclude cannot be represented as separate
2458 # pats/include/exclude cannot be represented as separate
2459 # revset expressions as their filtering logic applies at file
2459 # revset expressions as their filtering logic applies at file
2460 # level. For instance "-I a -X a" matches a revision touching
2460 # level. For instance "-I a -X a" matches a revision touching
2461 # "a" and "b" while "file(a) and not file(b)" does
2461 # "a" and "b" while "file(a) and not file(b)" does
2462 # not. Besides, filesets are evaluated against the working
2462 # not. Besides, filesets are evaluated against the working
2463 # directory.
2463 # directory.
2464 matchargs = ['r:', 'd:relpath']
2464 matchargs = ['r:', 'd:relpath']
2465 for p in pats:
2465 for p in pats:
2466 matchargs.append('p:' + p)
2466 matchargs.append('p:' + p)
2467 for p in opts.get('include', []):
2467 for p in opts.get('include', []):
2468 matchargs.append('i:' + p)
2468 matchargs.append('i:' + p)
2469 for p in opts.get('exclude', []):
2469 for p in opts.get('exclude', []):
2470 matchargs.append('x:' + p)
2470 matchargs.append('x:' + p)
2471 matchargs = ','.join(('%r' % p) for p in matchargs)
2471 matchargs = ','.join(('%r' % p) for p in matchargs)
2472 opts['_matchfiles'] = matchargs
2472 opts['_matchfiles'] = matchargs
2473 if follow:
2473 if follow:
2474 opts[fnopats[0][followfirst]] = '.'
2474 opts[fnopats[0][followfirst]] = '.'
2475 else:
2475 else:
2476 if follow:
2476 if follow:
2477 if pats:
2477 if pats:
2478 # follow() revset interprets its file argument as a
2478 # follow() revset interprets its file argument as a
2479 # manifest entry, so use match.files(), not pats.
2479 # manifest entry, so use match.files(), not pats.
2480 opts[fpats[followfirst]] = list(match.files())
2480 opts[fpats[followfirst]] = list(match.files())
2481 else:
2481 else:
2482 op = fnopats[followdescendants][followfirst]
2482 op = fnopats[followdescendants][followfirst]
2483 opts[op] = 'rev(%d)' % startrev
2483 opts[op] = 'rev(%d)' % startrev
2484 else:
2484 else:
2485 opts['_patslog'] = list(pats)
2485 opts['_patslog'] = list(pats)
2486
2486
2487 filematcher = None
2487 filematcher = None
2488 if opts.get('patch') or opts.get('stat'):
2488 if opts.get('patch') or opts.get('stat'):
2489 # When following files, track renames via a special matcher.
2489 # When following files, track renames via a special matcher.
2490 # If we're forced to take the slowpath it means we're following
2490 # If we're forced to take the slowpath it means we're following
2491 # at least one pattern/directory, so don't bother with rename tracking.
2491 # at least one pattern/directory, so don't bother with rename tracking.
2492 if follow and not match.always() and not slowpath:
2492 if follow and not match.always() and not slowpath:
2493 # _makefollowlogfilematcher expects its files argument to be
2493 # _makefollowlogfilematcher expects its files argument to be
2494 # relative to the repo root, so use match.files(), not pats.
2494 # relative to the repo root, so use match.files(), not pats.
2495 filematcher = _makefollowlogfilematcher(repo, match.files(),
2495 filematcher = _makefollowlogfilematcher(repo, match.files(),
2496 followfirst)
2496 followfirst)
2497 else:
2497 else:
2498 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2498 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2499 if filematcher is None:
2499 if filematcher is None:
2500 filematcher = lambda rev: match
2500 filematcher = lambda rev: match
2501
2501
2502 expr = []
2502 expr = []
2503 for op, val in sorted(opts.iteritems()):
2503 for op, val in sorted(opts.iteritems()):
2504 if not val:
2504 if not val:
2505 continue
2505 continue
2506 if op not in opt2revset:
2506 if op not in opt2revset:
2507 continue
2507 continue
2508 revop, andor = opt2revset[op]
2508 revop, andor = opt2revset[op]
2509 if '%(val)' not in revop:
2509 if '%(val)' not in revop:
2510 expr.append(revop)
2510 expr.append(revop)
2511 else:
2511 else:
2512 if not isinstance(val, list):
2512 if not isinstance(val, list):
2513 e = revop % {'val': val}
2513 e = revop % {'val': val}
2514 else:
2514 else:
2515 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2515 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2516 expr.append(e)
2516 expr.append(e)
2517
2517
2518 if expr:
2518 if expr:
2519 expr = '(' + ' and '.join(expr) + ')'
2519 expr = '(' + ' and '.join(expr) + ')'
2520 else:
2520 else:
2521 expr = None
2521 expr = None
2522 return expr, filematcher
2522 return expr, filematcher
2523
2523
2524 def _logrevs(repo, opts):
2524 def _logrevs(repo, opts):
2525 # Default --rev value depends on --follow but --follow behavior
2525 # Default --rev value depends on --follow but --follow behavior
2526 # depends on revisions resolved from --rev...
2526 # depends on revisions resolved from --rev...
2527 follow = opts.get('follow') or opts.get('follow_first')
2527 follow = opts.get('follow') or opts.get('follow_first')
2528 if opts.get('rev'):
2528 if opts.get('rev'):
2529 revs = scmutil.revrange(repo, opts['rev'])
2529 revs = scmutil.revrange(repo, opts['rev'])
2530 elif follow and repo.dirstate.p1() == nullid:
2530 elif follow and repo.dirstate.p1() == nullid:
2531 revs = smartset.baseset()
2531 revs = smartset.baseset()
2532 elif follow:
2532 elif follow:
2533 revs = repo.revs('reverse(:.)')
2533 revs = repo.revs('reverse(:.)')
2534 else:
2534 else:
2535 revs = smartset.spanset(repo)
2535 revs = smartset.spanset(repo)
2536 revs.reverse()
2536 revs.reverse()
2537 return revs
2537 return revs
2538
2538
2539 def getgraphlogrevs(repo, pats, opts):
2539 def getgraphlogrevs(repo, pats, opts):
2540 """Return (revs, expr, filematcher) where revs is an iterable of
2540 """Return (revs, expr, filematcher) where revs is an iterable of
2541 revision numbers, expr is a revset string built from log options
2541 revision numbers, expr is a revset string built from log options
2542 and file patterns or None, and used to filter 'revs'. If --stat or
2542 and file patterns or None, and used to filter 'revs'. If --stat or
2543 --patch are not passed filematcher is None. Otherwise it is a
2543 --patch are not passed filematcher is None. Otherwise it is a
2544 callable taking a revision number and returning a match objects
2544 callable taking a revision number and returning a match objects
2545 filtering the files to be detailed when displaying the revision.
2545 filtering the files to be detailed when displaying the revision.
2546 """
2546 """
2547 limit = loglimit(opts)
2547 limit = loglimit(opts)
2548 revs = _logrevs(repo, opts)
2548 revs = _logrevs(repo, opts)
2549 if not revs:
2549 if not revs:
2550 return smartset.baseset(), None, None
2550 return smartset.baseset(), None, None
2551 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2551 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2552 if opts.get('rev'):
2552 if opts.get('rev'):
2553 # User-specified revs might be unsorted, but don't sort before
2553 # User-specified revs might be unsorted, but don't sort before
2554 # _makelogrevset because it might depend on the order of revs
2554 # _makelogrevset because it might depend on the order of revs
2555 if not (revs.isdescending() or revs.istopo()):
2555 if not (revs.isdescending() or revs.istopo()):
2556 revs.sort(reverse=True)
2556 revs.sort(reverse=True)
2557 if expr:
2557 if expr:
2558 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2558 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2559 revs = matcher(repo, revs)
2559 revs = matcher(repo, revs)
2560 if limit is not None:
2560 if limit is not None:
2561 limitedrevs = []
2561 limitedrevs = []
2562 for idx, rev in enumerate(revs):
2562 for idx, rev in enumerate(revs):
2563 if idx >= limit:
2563 if idx >= limit:
2564 break
2564 break
2565 limitedrevs.append(rev)
2565 limitedrevs.append(rev)
2566 revs = smartset.baseset(limitedrevs)
2566 revs = smartset.baseset(limitedrevs)
2567
2567
2568 return revs, expr, filematcher
2568 return revs, expr, filematcher
2569
2569
2570 def getlogrevs(repo, pats, opts):
2570 def getlogrevs(repo, pats, opts):
2571 """Return (revs, expr, filematcher) where revs is an iterable of
2571 """Return (revs, expr, filematcher) where revs is an iterable of
2572 revision numbers, expr is a revset string built from log options
2572 revision numbers, expr is a revset string built from log options
2573 and file patterns or None, and used to filter 'revs'. If --stat or
2573 and file patterns or None, and used to filter 'revs'. If --stat or
2574 --patch are not passed filematcher is None. Otherwise it is a
2574 --patch are not passed filematcher is None. Otherwise it is a
2575 callable taking a revision number and returning a match objects
2575 callable taking a revision number and returning a match objects
2576 filtering the files to be detailed when displaying the revision.
2576 filtering the files to be detailed when displaying the revision.
2577 """
2577 """
2578 limit = loglimit(opts)
2578 limit = loglimit(opts)
2579 revs = _logrevs(repo, opts)
2579 revs = _logrevs(repo, opts)
2580 if not revs:
2580 if not revs:
2581 return smartset.baseset([]), None, None
2581 return smartset.baseset([]), None, None
2582 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2582 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2583 if expr:
2583 if expr:
2584 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2584 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2585 revs = matcher(repo, revs)
2585 revs = matcher(repo, revs)
2586 if limit is not None:
2586 if limit is not None:
2587 limitedrevs = []
2587 limitedrevs = []
2588 for idx, r in enumerate(revs):
2588 for idx, r in enumerate(revs):
2589 if limit <= idx:
2589 if limit <= idx:
2590 break
2590 break
2591 limitedrevs.append(r)
2591 limitedrevs.append(r)
2592 revs = smartset.baseset(limitedrevs)
2592 revs = smartset.baseset(limitedrevs)
2593
2593
2594 return revs, expr, filematcher
2594 return revs, expr, filematcher
2595
2595
2596 def _graphnodeformatter(ui, displayer):
2596 def _graphnodeformatter(ui, displayer):
2597 spec = ui.config('ui', 'graphnodetemplate')
2597 spec = ui.config('ui', 'graphnodetemplate')
2598 if not spec:
2598 if not spec:
2599 return templatekw.showgraphnode # fast path for "{graphnode}"
2599 return templatekw.showgraphnode # fast path for "{graphnode}"
2600
2600
2601 spec = templater.unquotestring(spec)
2601 spec = templater.unquotestring(spec)
2602 templ = formatter.maketemplater(ui, spec)
2602 templ = formatter.maketemplater(ui, spec)
2603 cache = {}
2603 cache = {}
2604 if isinstance(displayer, changeset_templater):
2604 if isinstance(displayer, changeset_templater):
2605 cache = displayer.cache # reuse cache of slow templates
2605 cache = displayer.cache # reuse cache of slow templates
2606 props = templatekw.keywords.copy()
2606 props = templatekw.keywords.copy()
2607 props['templ'] = templ
2607 props['templ'] = templ
2608 props['cache'] = cache
2608 props['cache'] = cache
2609 def formatnode(repo, ctx):
2609 def formatnode(repo, ctx):
2610 props['ctx'] = ctx
2610 props['ctx'] = ctx
2611 props['repo'] = repo
2611 props['repo'] = repo
2612 props['ui'] = repo.ui
2612 props['ui'] = repo.ui
2613 props['revcache'] = {}
2613 props['revcache'] = {}
2614 return templ.render(props)
2614 return templ.render(props)
2615 return formatnode
2615 return formatnode
2616
2616
2617 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2617 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2618 filematcher=None):
2618 filematcher=None):
2619 formatnode = _graphnodeformatter(ui, displayer)
2619 formatnode = _graphnodeformatter(ui, displayer)
2620 state = graphmod.asciistate()
2620 state = graphmod.asciistate()
2621 styles = state['styles']
2621 styles = state['styles']
2622
2622
2623 # only set graph styling if HGPLAIN is not set.
2623 # only set graph styling if HGPLAIN is not set.
2624 if ui.plain('graph'):
2624 if ui.plain('graph'):
2625 # set all edge styles to |, the default pre-3.8 behaviour
2625 # set all edge styles to |, the default pre-3.8 behaviour
2626 styles.update(dict.fromkeys(styles, '|'))
2626 styles.update(dict.fromkeys(styles, '|'))
2627 else:
2627 else:
2628 edgetypes = {
2628 edgetypes = {
2629 'parent': graphmod.PARENT,
2629 'parent': graphmod.PARENT,
2630 'grandparent': graphmod.GRANDPARENT,
2630 'grandparent': graphmod.GRANDPARENT,
2631 'missing': graphmod.MISSINGPARENT
2631 'missing': graphmod.MISSINGPARENT
2632 }
2632 }
2633 for name, key in edgetypes.items():
2633 for name, key in edgetypes.items():
2634 # experimental config: experimental.graphstyle.*
2634 # experimental config: experimental.graphstyle.*
2635 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2635 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2636 styles[key])
2636 styles[key])
2637 if not styles[key]:
2637 if not styles[key]:
2638 styles[key] = None
2638 styles[key] = None
2639
2639
2640 # experimental config: experimental.graphshorten
2640 # experimental config: experimental.graphshorten
2641 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2641 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2642
2642
2643 for rev, type, ctx, parents in dag:
2643 for rev, type, ctx, parents in dag:
2644 char = formatnode(repo, ctx)
2644 char = formatnode(repo, ctx)
2645 copies = None
2645 copies = None
2646 if getrenamed and ctx.rev():
2646 if getrenamed and ctx.rev():
2647 copies = []
2647 copies = []
2648 for fn in ctx.files():
2648 for fn in ctx.files():
2649 rename = getrenamed(fn, ctx.rev())
2649 rename = getrenamed(fn, ctx.rev())
2650 if rename:
2650 if rename:
2651 copies.append((fn, rename[0]))
2651 copies.append((fn, rename[0]))
2652 revmatchfn = None
2652 revmatchfn = None
2653 if filematcher is not None:
2653 if filematcher is not None:
2654 revmatchfn = filematcher(ctx.rev())
2654 revmatchfn = filematcher(ctx.rev())
2655 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2655 edges = edgefn(type, char, state, rev, parents)
2656 firstedge = next(edges)
2657 width = firstedge[2]
2658 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
2659 _graphwidth=width)
2656 lines = displayer.hunk.pop(rev).split('\n')
2660 lines = displayer.hunk.pop(rev).split('\n')
2657 if not lines[-1]:
2661 if not lines[-1]:
2658 del lines[-1]
2662 del lines[-1]
2659 displayer.flush(ctx)
2663 displayer.flush(ctx)
2660 edges = edgefn(type, char, lines, state, rev, parents)
2664 for type, char, width, coldata in itertools.chain([firstedge], edges):
2661 for type, char, lines, coldata in edges:
2662 graphmod.ascii(ui, state, type, char, lines, coldata)
2665 graphmod.ascii(ui, state, type, char, lines, coldata)
2666 lines = []
2663 displayer.close()
2667 displayer.close()
2664
2668
2665 def graphlog(ui, repo, pats, opts):
2669 def graphlog(ui, repo, pats, opts):
2666 # Parameters are identical to log command ones
2670 # Parameters are identical to log command ones
2667 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2671 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2668 revdag = graphmod.dagwalker(repo, revs)
2672 revdag = graphmod.dagwalker(repo, revs)
2669
2673
2670 getrenamed = None
2674 getrenamed = None
2671 if opts.get('copies'):
2675 if opts.get('copies'):
2672 endrev = None
2676 endrev = None
2673 if opts.get('rev'):
2677 if opts.get('rev'):
2674 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2678 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2675 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2679 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2676
2680
2677 ui.pager('log')
2681 ui.pager('log')
2678 displayer = show_changeset(ui, repo, opts, buffered=True)
2682 displayer = show_changeset(ui, repo, opts, buffered=True)
2679 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2683 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2680 filematcher)
2684 filematcher)
2681
2685
2682 def checkunsupportedgraphflags(pats, opts):
2686 def checkunsupportedgraphflags(pats, opts):
2683 for op in ["newest_first"]:
2687 for op in ["newest_first"]:
2684 if op in opts and opts[op]:
2688 if op in opts and opts[op]:
2685 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2689 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2686 % op.replace("_", "-"))
2690 % op.replace("_", "-"))
2687
2691
2688 def graphrevs(repo, nodes, opts):
2692 def graphrevs(repo, nodes, opts):
2689 limit = loglimit(opts)
2693 limit = loglimit(opts)
2690 nodes.reverse()
2694 nodes.reverse()
2691 if limit is not None:
2695 if limit is not None:
2692 nodes = nodes[:limit]
2696 nodes = nodes[:limit]
2693 return graphmod.nodes(repo, nodes)
2697 return graphmod.nodes(repo, nodes)
2694
2698
2695 def add(ui, repo, match, prefix, explicitonly, **opts):
2699 def add(ui, repo, match, prefix, explicitonly, **opts):
2696 join = lambda f: os.path.join(prefix, f)
2700 join = lambda f: os.path.join(prefix, f)
2697 bad = []
2701 bad = []
2698
2702
2699 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2703 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2700 names = []
2704 names = []
2701 wctx = repo[None]
2705 wctx = repo[None]
2702 cca = None
2706 cca = None
2703 abort, warn = scmutil.checkportabilityalert(ui)
2707 abort, warn = scmutil.checkportabilityalert(ui)
2704 if abort or warn:
2708 if abort or warn:
2705 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2709 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2706
2710
2707 badmatch = matchmod.badmatch(match, badfn)
2711 badmatch = matchmod.badmatch(match, badfn)
2708 dirstate = repo.dirstate
2712 dirstate = repo.dirstate
2709 # We don't want to just call wctx.walk here, since it would return a lot of
2713 # We don't want to just call wctx.walk here, since it would return a lot of
2710 # clean files, which we aren't interested in and takes time.
2714 # clean files, which we aren't interested in and takes time.
2711 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2715 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2712 True, False, full=False)):
2716 True, False, full=False)):
2713 exact = match.exact(f)
2717 exact = match.exact(f)
2714 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2718 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2715 if cca:
2719 if cca:
2716 cca(f)
2720 cca(f)
2717 names.append(f)
2721 names.append(f)
2718 if ui.verbose or not exact:
2722 if ui.verbose or not exact:
2719 ui.status(_('adding %s\n') % match.rel(f))
2723 ui.status(_('adding %s\n') % match.rel(f))
2720
2724
2721 for subpath in sorted(wctx.substate):
2725 for subpath in sorted(wctx.substate):
2722 sub = wctx.sub(subpath)
2726 sub = wctx.sub(subpath)
2723 try:
2727 try:
2724 submatch = matchmod.subdirmatcher(subpath, match)
2728 submatch = matchmod.subdirmatcher(subpath, match)
2725 if opts.get(r'subrepos'):
2729 if opts.get(r'subrepos'):
2726 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2730 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2727 else:
2731 else:
2728 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2732 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2729 except error.LookupError:
2733 except error.LookupError:
2730 ui.status(_("skipping missing subrepository: %s\n")
2734 ui.status(_("skipping missing subrepository: %s\n")
2731 % join(subpath))
2735 % join(subpath))
2732
2736
2733 if not opts.get(r'dry_run'):
2737 if not opts.get(r'dry_run'):
2734 rejected = wctx.add(names, prefix)
2738 rejected = wctx.add(names, prefix)
2735 bad.extend(f for f in rejected if f in match.files())
2739 bad.extend(f for f in rejected if f in match.files())
2736 return bad
2740 return bad
2737
2741
2738 def addwebdirpath(repo, serverpath, webconf):
2742 def addwebdirpath(repo, serverpath, webconf):
2739 webconf[serverpath] = repo.root
2743 webconf[serverpath] = repo.root
2740 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2744 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2741
2745
2742 for r in repo.revs('filelog("path:.hgsub")'):
2746 for r in repo.revs('filelog("path:.hgsub")'):
2743 ctx = repo[r]
2747 ctx = repo[r]
2744 for subpath in ctx.substate:
2748 for subpath in ctx.substate:
2745 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2749 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2746
2750
2747 def forget(ui, repo, match, prefix, explicitonly):
2751 def forget(ui, repo, match, prefix, explicitonly):
2748 join = lambda f: os.path.join(prefix, f)
2752 join = lambda f: os.path.join(prefix, f)
2749 bad = []
2753 bad = []
2750 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2754 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2751 wctx = repo[None]
2755 wctx = repo[None]
2752 forgot = []
2756 forgot = []
2753
2757
2754 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2758 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2755 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2759 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2756 if explicitonly:
2760 if explicitonly:
2757 forget = [f for f in forget if match.exact(f)]
2761 forget = [f for f in forget if match.exact(f)]
2758
2762
2759 for subpath in sorted(wctx.substate):
2763 for subpath in sorted(wctx.substate):
2760 sub = wctx.sub(subpath)
2764 sub = wctx.sub(subpath)
2761 try:
2765 try:
2762 submatch = matchmod.subdirmatcher(subpath, match)
2766 submatch = matchmod.subdirmatcher(subpath, match)
2763 subbad, subforgot = sub.forget(submatch, prefix)
2767 subbad, subforgot = sub.forget(submatch, prefix)
2764 bad.extend([subpath + '/' + f for f in subbad])
2768 bad.extend([subpath + '/' + f for f in subbad])
2765 forgot.extend([subpath + '/' + f for f in subforgot])
2769 forgot.extend([subpath + '/' + f for f in subforgot])
2766 except error.LookupError:
2770 except error.LookupError:
2767 ui.status(_("skipping missing subrepository: %s\n")
2771 ui.status(_("skipping missing subrepository: %s\n")
2768 % join(subpath))
2772 % join(subpath))
2769
2773
2770 if not explicitonly:
2774 if not explicitonly:
2771 for f in match.files():
2775 for f in match.files():
2772 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2776 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2773 if f not in forgot:
2777 if f not in forgot:
2774 if repo.wvfs.exists(f):
2778 if repo.wvfs.exists(f):
2775 # Don't complain if the exact case match wasn't given.
2779 # Don't complain if the exact case match wasn't given.
2776 # But don't do this until after checking 'forgot', so
2780 # But don't do this until after checking 'forgot', so
2777 # that subrepo files aren't normalized, and this op is
2781 # that subrepo files aren't normalized, and this op is
2778 # purely from data cached by the status walk above.
2782 # purely from data cached by the status walk above.
2779 if repo.dirstate.normalize(f) in repo.dirstate:
2783 if repo.dirstate.normalize(f) in repo.dirstate:
2780 continue
2784 continue
2781 ui.warn(_('not removing %s: '
2785 ui.warn(_('not removing %s: '
2782 'file is already untracked\n')
2786 'file is already untracked\n')
2783 % match.rel(f))
2787 % match.rel(f))
2784 bad.append(f)
2788 bad.append(f)
2785
2789
2786 for f in forget:
2790 for f in forget:
2787 if ui.verbose or not match.exact(f):
2791 if ui.verbose or not match.exact(f):
2788 ui.status(_('removing %s\n') % match.rel(f))
2792 ui.status(_('removing %s\n') % match.rel(f))
2789
2793
2790 rejected = wctx.forget(forget, prefix)
2794 rejected = wctx.forget(forget, prefix)
2791 bad.extend(f for f in rejected if f in match.files())
2795 bad.extend(f for f in rejected if f in match.files())
2792 forgot.extend(f for f in forget if f not in rejected)
2796 forgot.extend(f for f in forget if f not in rejected)
2793 return bad, forgot
2797 return bad, forgot
2794
2798
2795 def files(ui, ctx, m, fm, fmt, subrepos):
2799 def files(ui, ctx, m, fm, fmt, subrepos):
2796 rev = ctx.rev()
2800 rev = ctx.rev()
2797 ret = 1
2801 ret = 1
2798 ds = ctx.repo().dirstate
2802 ds = ctx.repo().dirstate
2799
2803
2800 for f in ctx.matches(m):
2804 for f in ctx.matches(m):
2801 if rev is None and ds[f] == 'r':
2805 if rev is None and ds[f] == 'r':
2802 continue
2806 continue
2803 fm.startitem()
2807 fm.startitem()
2804 if ui.verbose:
2808 if ui.verbose:
2805 fc = ctx[f]
2809 fc = ctx[f]
2806 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2810 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2807 fm.data(abspath=f)
2811 fm.data(abspath=f)
2808 fm.write('path', fmt, m.rel(f))
2812 fm.write('path', fmt, m.rel(f))
2809 ret = 0
2813 ret = 0
2810
2814
2811 for subpath in sorted(ctx.substate):
2815 for subpath in sorted(ctx.substate):
2812 submatch = matchmod.subdirmatcher(subpath, m)
2816 submatch = matchmod.subdirmatcher(subpath, m)
2813 if (subrepos or m.exact(subpath) or any(submatch.files())):
2817 if (subrepos or m.exact(subpath) or any(submatch.files())):
2814 sub = ctx.sub(subpath)
2818 sub = ctx.sub(subpath)
2815 try:
2819 try:
2816 recurse = m.exact(subpath) or subrepos
2820 recurse = m.exact(subpath) or subrepos
2817 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2821 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2818 ret = 0
2822 ret = 0
2819 except error.LookupError:
2823 except error.LookupError:
2820 ui.status(_("skipping missing subrepository: %s\n")
2824 ui.status(_("skipping missing subrepository: %s\n")
2821 % m.abs(subpath))
2825 % m.abs(subpath))
2822
2826
2823 return ret
2827 return ret
2824
2828
2825 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2829 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2826 join = lambda f: os.path.join(prefix, f)
2830 join = lambda f: os.path.join(prefix, f)
2827 ret = 0
2831 ret = 0
2828 s = repo.status(match=m, clean=True)
2832 s = repo.status(match=m, clean=True)
2829 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2833 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2830
2834
2831 wctx = repo[None]
2835 wctx = repo[None]
2832
2836
2833 if warnings is None:
2837 if warnings is None:
2834 warnings = []
2838 warnings = []
2835 warn = True
2839 warn = True
2836 else:
2840 else:
2837 warn = False
2841 warn = False
2838
2842
2839 subs = sorted(wctx.substate)
2843 subs = sorted(wctx.substate)
2840 total = len(subs)
2844 total = len(subs)
2841 count = 0
2845 count = 0
2842 for subpath in subs:
2846 for subpath in subs:
2843 count += 1
2847 count += 1
2844 submatch = matchmod.subdirmatcher(subpath, m)
2848 submatch = matchmod.subdirmatcher(subpath, m)
2845 if subrepos or m.exact(subpath) or any(submatch.files()):
2849 if subrepos or m.exact(subpath) or any(submatch.files()):
2846 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2850 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2847 sub = wctx.sub(subpath)
2851 sub = wctx.sub(subpath)
2848 try:
2852 try:
2849 if sub.removefiles(submatch, prefix, after, force, subrepos,
2853 if sub.removefiles(submatch, prefix, after, force, subrepos,
2850 warnings):
2854 warnings):
2851 ret = 1
2855 ret = 1
2852 except error.LookupError:
2856 except error.LookupError:
2853 warnings.append(_("skipping missing subrepository: %s\n")
2857 warnings.append(_("skipping missing subrepository: %s\n")
2854 % join(subpath))
2858 % join(subpath))
2855 ui.progress(_('searching'), None)
2859 ui.progress(_('searching'), None)
2856
2860
2857 # warn about failure to delete explicit files/dirs
2861 # warn about failure to delete explicit files/dirs
2858 deleteddirs = util.dirs(deleted)
2862 deleteddirs = util.dirs(deleted)
2859 files = m.files()
2863 files = m.files()
2860 total = len(files)
2864 total = len(files)
2861 count = 0
2865 count = 0
2862 for f in files:
2866 for f in files:
2863 def insubrepo():
2867 def insubrepo():
2864 for subpath in wctx.substate:
2868 for subpath in wctx.substate:
2865 if f.startswith(subpath + '/'):
2869 if f.startswith(subpath + '/'):
2866 return True
2870 return True
2867 return False
2871 return False
2868
2872
2869 count += 1
2873 count += 1
2870 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2874 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2871 isdir = f in deleteddirs or wctx.hasdir(f)
2875 isdir = f in deleteddirs or wctx.hasdir(f)
2872 if (f in repo.dirstate or isdir or f == '.'
2876 if (f in repo.dirstate or isdir or f == '.'
2873 or insubrepo() or f in subs):
2877 or insubrepo() or f in subs):
2874 continue
2878 continue
2875
2879
2876 if repo.wvfs.exists(f):
2880 if repo.wvfs.exists(f):
2877 if repo.wvfs.isdir(f):
2881 if repo.wvfs.isdir(f):
2878 warnings.append(_('not removing %s: no tracked files\n')
2882 warnings.append(_('not removing %s: no tracked files\n')
2879 % m.rel(f))
2883 % m.rel(f))
2880 else:
2884 else:
2881 warnings.append(_('not removing %s: file is untracked\n')
2885 warnings.append(_('not removing %s: file is untracked\n')
2882 % m.rel(f))
2886 % m.rel(f))
2883 # missing files will generate a warning elsewhere
2887 # missing files will generate a warning elsewhere
2884 ret = 1
2888 ret = 1
2885 ui.progress(_('deleting'), None)
2889 ui.progress(_('deleting'), None)
2886
2890
2887 if force:
2891 if force:
2888 list = modified + deleted + clean + added
2892 list = modified + deleted + clean + added
2889 elif after:
2893 elif after:
2890 list = deleted
2894 list = deleted
2891 remaining = modified + added + clean
2895 remaining = modified + added + clean
2892 total = len(remaining)
2896 total = len(remaining)
2893 count = 0
2897 count = 0
2894 for f in remaining:
2898 for f in remaining:
2895 count += 1
2899 count += 1
2896 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2900 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2897 warnings.append(_('not removing %s: file still exists\n')
2901 warnings.append(_('not removing %s: file still exists\n')
2898 % m.rel(f))
2902 % m.rel(f))
2899 ret = 1
2903 ret = 1
2900 ui.progress(_('skipping'), None)
2904 ui.progress(_('skipping'), None)
2901 else:
2905 else:
2902 list = deleted + clean
2906 list = deleted + clean
2903 total = len(modified) + len(added)
2907 total = len(modified) + len(added)
2904 count = 0
2908 count = 0
2905 for f in modified:
2909 for f in modified:
2906 count += 1
2910 count += 1
2907 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2911 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2908 warnings.append(_('not removing %s: file is modified (use -f'
2912 warnings.append(_('not removing %s: file is modified (use -f'
2909 ' to force removal)\n') % m.rel(f))
2913 ' to force removal)\n') % m.rel(f))
2910 ret = 1
2914 ret = 1
2911 for f in added:
2915 for f in added:
2912 count += 1
2916 count += 1
2913 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2917 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2914 warnings.append(_("not removing %s: file has been marked for add"
2918 warnings.append(_("not removing %s: file has been marked for add"
2915 " (use 'hg forget' to undo add)\n") % m.rel(f))
2919 " (use 'hg forget' to undo add)\n") % m.rel(f))
2916 ret = 1
2920 ret = 1
2917 ui.progress(_('skipping'), None)
2921 ui.progress(_('skipping'), None)
2918
2922
2919 list = sorted(list)
2923 list = sorted(list)
2920 total = len(list)
2924 total = len(list)
2921 count = 0
2925 count = 0
2922 for f in list:
2926 for f in list:
2923 count += 1
2927 count += 1
2924 if ui.verbose or not m.exact(f):
2928 if ui.verbose or not m.exact(f):
2925 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2929 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2926 ui.status(_('removing %s\n') % m.rel(f))
2930 ui.status(_('removing %s\n') % m.rel(f))
2927 ui.progress(_('deleting'), None)
2931 ui.progress(_('deleting'), None)
2928
2932
2929 with repo.wlock():
2933 with repo.wlock():
2930 if not after:
2934 if not after:
2931 for f in list:
2935 for f in list:
2932 if f in added:
2936 if f in added:
2933 continue # we never unlink added files on remove
2937 continue # we never unlink added files on remove
2934 repo.wvfs.unlinkpath(f, ignoremissing=True)
2938 repo.wvfs.unlinkpath(f, ignoremissing=True)
2935 repo[None].forget(list)
2939 repo[None].forget(list)
2936
2940
2937 if warn:
2941 if warn:
2938 for warning in warnings:
2942 for warning in warnings:
2939 ui.warn(warning)
2943 ui.warn(warning)
2940
2944
2941 return ret
2945 return ret
2942
2946
2943 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2947 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2944 err = 1
2948 err = 1
2945
2949
2946 def write(path):
2950 def write(path):
2947 filename = None
2951 filename = None
2948 if fntemplate:
2952 if fntemplate:
2949 filename = makefilename(repo, fntemplate, ctx.node(),
2953 filename = makefilename(repo, fntemplate, ctx.node(),
2950 pathname=os.path.join(prefix, path))
2954 pathname=os.path.join(prefix, path))
2951 with formatter.maybereopen(basefm, filename, opts) as fm:
2955 with formatter.maybereopen(basefm, filename, opts) as fm:
2952 data = ctx[path].data()
2956 data = ctx[path].data()
2953 if opts.get('decode'):
2957 if opts.get('decode'):
2954 data = repo.wwritedata(path, data)
2958 data = repo.wwritedata(path, data)
2955 fm.startitem()
2959 fm.startitem()
2956 fm.write('data', '%s', data)
2960 fm.write('data', '%s', data)
2957 fm.data(abspath=path, path=matcher.rel(path))
2961 fm.data(abspath=path, path=matcher.rel(path))
2958
2962
2959 # Automation often uses hg cat on single files, so special case it
2963 # Automation often uses hg cat on single files, so special case it
2960 # for performance to avoid the cost of parsing the manifest.
2964 # for performance to avoid the cost of parsing the manifest.
2961 if len(matcher.files()) == 1 and not matcher.anypats():
2965 if len(matcher.files()) == 1 and not matcher.anypats():
2962 file = matcher.files()[0]
2966 file = matcher.files()[0]
2963 mfl = repo.manifestlog
2967 mfl = repo.manifestlog
2964 mfnode = ctx.manifestnode()
2968 mfnode = ctx.manifestnode()
2965 try:
2969 try:
2966 if mfnode and mfl[mfnode].find(file)[0]:
2970 if mfnode and mfl[mfnode].find(file)[0]:
2967 write(file)
2971 write(file)
2968 return 0
2972 return 0
2969 except KeyError:
2973 except KeyError:
2970 pass
2974 pass
2971
2975
2972 for abs in ctx.walk(matcher):
2976 for abs in ctx.walk(matcher):
2973 write(abs)
2977 write(abs)
2974 err = 0
2978 err = 0
2975
2979
2976 for subpath in sorted(ctx.substate):
2980 for subpath in sorted(ctx.substate):
2977 sub = ctx.sub(subpath)
2981 sub = ctx.sub(subpath)
2978 try:
2982 try:
2979 submatch = matchmod.subdirmatcher(subpath, matcher)
2983 submatch = matchmod.subdirmatcher(subpath, matcher)
2980
2984
2981 if not sub.cat(submatch, basefm, fntemplate,
2985 if not sub.cat(submatch, basefm, fntemplate,
2982 os.path.join(prefix, sub._path), **opts):
2986 os.path.join(prefix, sub._path), **opts):
2983 err = 0
2987 err = 0
2984 except error.RepoLookupError:
2988 except error.RepoLookupError:
2985 ui.status(_("skipping missing subrepository: %s\n")
2989 ui.status(_("skipping missing subrepository: %s\n")
2986 % os.path.join(prefix, subpath))
2990 % os.path.join(prefix, subpath))
2987
2991
2988 return err
2992 return err
2989
2993
2990 def commit(ui, repo, commitfunc, pats, opts):
2994 def commit(ui, repo, commitfunc, pats, opts):
2991 '''commit the specified files or all outstanding changes'''
2995 '''commit the specified files or all outstanding changes'''
2992 date = opts.get('date')
2996 date = opts.get('date')
2993 if date:
2997 if date:
2994 opts['date'] = util.parsedate(date)
2998 opts['date'] = util.parsedate(date)
2995 message = logmessage(ui, opts)
2999 message = logmessage(ui, opts)
2996 matcher = scmutil.match(repo[None], pats, opts)
3000 matcher = scmutil.match(repo[None], pats, opts)
2997
3001
2998 dsguard = None
3002 dsguard = None
2999 # extract addremove carefully -- this function can be called from a command
3003 # extract addremove carefully -- this function can be called from a command
3000 # that doesn't support addremove
3004 # that doesn't support addremove
3001 if opts.get('addremove'):
3005 if opts.get('addremove'):
3002 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3006 dsguard = dirstateguard.dirstateguard(repo, 'commit')
3003 with dsguard or util.nullcontextmanager():
3007 with dsguard or util.nullcontextmanager():
3004 if dsguard:
3008 if dsguard:
3005 if scmutil.addremove(repo, matcher, "", opts) != 0:
3009 if scmutil.addremove(repo, matcher, "", opts) != 0:
3006 raise error.Abort(
3010 raise error.Abort(
3007 _("failed to mark all new/missing files as added/removed"))
3011 _("failed to mark all new/missing files as added/removed"))
3008
3012
3009 return commitfunc(ui, repo, message, matcher, opts)
3013 return commitfunc(ui, repo, message, matcher, opts)
3010
3014
3011 def samefile(f, ctx1, ctx2):
3015 def samefile(f, ctx1, ctx2):
3012 if f in ctx1.manifest():
3016 if f in ctx1.manifest():
3013 a = ctx1.filectx(f)
3017 a = ctx1.filectx(f)
3014 if f in ctx2.manifest():
3018 if f in ctx2.manifest():
3015 b = ctx2.filectx(f)
3019 b = ctx2.filectx(f)
3016 return (not a.cmp(b)
3020 return (not a.cmp(b)
3017 and a.flags() == b.flags())
3021 and a.flags() == b.flags())
3018 else:
3022 else:
3019 return False
3023 return False
3020 else:
3024 else:
3021 return f not in ctx2.manifest()
3025 return f not in ctx2.manifest()
3022
3026
3023 def amend(ui, repo, commitfunc, old, extra, pats, opts):
3027 def amend(ui, repo, commitfunc, old, extra, pats, opts):
3024 # avoid cycle context -> subrepo -> cmdutil
3028 # avoid cycle context -> subrepo -> cmdutil
3025 from . import context
3029 from . import context
3026
3030
3027 # amend will reuse the existing user if not specified, but the obsolete
3031 # amend will reuse the existing user if not specified, but the obsolete
3028 # marker creation requires that the current user's name is specified.
3032 # marker creation requires that the current user's name is specified.
3029 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3033 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3030 ui.username() # raise exception if username not set
3034 ui.username() # raise exception if username not set
3031
3035
3032 ui.note(_('amending changeset %s\n') % old)
3036 ui.note(_('amending changeset %s\n') % old)
3033 base = old.p1()
3037 base = old.p1()
3034
3038
3035 newid = None
3039 newid = None
3036 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3040 with repo.wlock(), repo.lock(), repo.transaction('amend'):
3037 # See if we got a message from -m or -l, if not, open the editor
3041 # See if we got a message from -m or -l, if not, open the editor
3038 # with the message of the changeset to amend
3042 # with the message of the changeset to amend
3039 message = logmessage(ui, opts)
3043 message = logmessage(ui, opts)
3040 # ensure logfile does not conflict with later enforcement of the
3044 # ensure logfile does not conflict with later enforcement of the
3041 # message. potential logfile content has been processed by
3045 # message. potential logfile content has been processed by
3042 # `logmessage` anyway.
3046 # `logmessage` anyway.
3043 opts.pop('logfile')
3047 opts.pop('logfile')
3044 # First, do a regular commit to record all changes in the working
3048 # First, do a regular commit to record all changes in the working
3045 # directory (if there are any)
3049 # directory (if there are any)
3046 ui.callhooks = False
3050 ui.callhooks = False
3047 activebookmark = repo._bookmarks.active
3051 activebookmark = repo._bookmarks.active
3048 try:
3052 try:
3049 repo._bookmarks.active = None
3053 repo._bookmarks.active = None
3050 opts['message'] = 'temporary amend commit for %s' % old
3054 opts['message'] = 'temporary amend commit for %s' % old
3051 node = commit(ui, repo, commitfunc, pats, opts)
3055 node = commit(ui, repo, commitfunc, pats, opts)
3052 finally:
3056 finally:
3053 repo._bookmarks.active = activebookmark
3057 repo._bookmarks.active = activebookmark
3054 ui.callhooks = True
3058 ui.callhooks = True
3055 ctx = repo[node]
3059 ctx = repo[node]
3056
3060
3057 # Participating changesets:
3061 # Participating changesets:
3058 #
3062 #
3059 # node/ctx o - new (intermediate) commit that contains changes
3063 # node/ctx o - new (intermediate) commit that contains changes
3060 # | from working dir to go into amending commit
3064 # | from working dir to go into amending commit
3061 # | (or a workingctx if there were no changes)
3065 # | (or a workingctx if there were no changes)
3062 # |
3066 # |
3063 # old o - changeset to amend
3067 # old o - changeset to amend
3064 # |
3068 # |
3065 # base o - parent of amending changeset
3069 # base o - parent of amending changeset
3066
3070
3067 # Update extra dict from amended commit (e.g. to preserve graft
3071 # Update extra dict from amended commit (e.g. to preserve graft
3068 # source)
3072 # source)
3069 extra.update(old.extra())
3073 extra.update(old.extra())
3070
3074
3071 # Also update it from the intermediate commit or from the wctx
3075 # Also update it from the intermediate commit or from the wctx
3072 extra.update(ctx.extra())
3076 extra.update(ctx.extra())
3073
3077
3074 if len(old.parents()) > 1:
3078 if len(old.parents()) > 1:
3075 # ctx.files() isn't reliable for merges, so fall back to the
3079 # ctx.files() isn't reliable for merges, so fall back to the
3076 # slower repo.status() method
3080 # slower repo.status() method
3077 files = set([fn for st in repo.status(base, old)[:3]
3081 files = set([fn for st in repo.status(base, old)[:3]
3078 for fn in st])
3082 for fn in st])
3079 else:
3083 else:
3080 files = set(old.files())
3084 files = set(old.files())
3081
3085
3082 # Second, we use either the commit we just did, or if there were no
3086 # Second, we use either the commit we just did, or if there were no
3083 # changes the parent of the working directory as the version of the
3087 # changes the parent of the working directory as the version of the
3084 # files in the final amend commit
3088 # files in the final amend commit
3085 if node:
3089 if node:
3086 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
3090 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
3087
3091
3088 user = ctx.user()
3092 user = ctx.user()
3089 date = ctx.date()
3093 date = ctx.date()
3090 # Recompute copies (avoid recording a -> b -> a)
3094 # Recompute copies (avoid recording a -> b -> a)
3091 copied = copies.pathcopies(base, ctx)
3095 copied = copies.pathcopies(base, ctx)
3092 if old.p2:
3096 if old.p2:
3093 copied.update(copies.pathcopies(old.p2(), ctx))
3097 copied.update(copies.pathcopies(old.p2(), ctx))
3094
3098
3095 # Prune files which were reverted by the updates: if old
3099 # Prune files which were reverted by the updates: if old
3096 # introduced file X and our intermediate commit, node,
3100 # introduced file X and our intermediate commit, node,
3097 # renamed that file, then those two files are the same and
3101 # renamed that file, then those two files are the same and
3098 # we can discard X from our list of files. Likewise if X
3102 # we can discard X from our list of files. Likewise if X
3099 # was deleted, it's no longer relevant
3103 # was deleted, it's no longer relevant
3100 files.update(ctx.files())
3104 files.update(ctx.files())
3101 files = [f for f in files if not samefile(f, ctx, base)]
3105 files = [f for f in files if not samefile(f, ctx, base)]
3102
3106
3103 def filectxfn(repo, ctx_, path):
3107 def filectxfn(repo, ctx_, path):
3104 try:
3108 try:
3105 fctx = ctx[path]
3109 fctx = ctx[path]
3106 flags = fctx.flags()
3110 flags = fctx.flags()
3107 mctx = context.memfilectx(repo,
3111 mctx = context.memfilectx(repo,
3108 fctx.path(), fctx.data(),
3112 fctx.path(), fctx.data(),
3109 islink='l' in flags,
3113 islink='l' in flags,
3110 isexec='x' in flags,
3114 isexec='x' in flags,
3111 copied=copied.get(path))
3115 copied=copied.get(path))
3112 return mctx
3116 return mctx
3113 except KeyError:
3117 except KeyError:
3114 return None
3118 return None
3115 else:
3119 else:
3116 ui.note(_('copying changeset %s to %s\n') % (old, base))
3120 ui.note(_('copying changeset %s to %s\n') % (old, base))
3117
3121
3118 # Use version of files as in the old cset
3122 # Use version of files as in the old cset
3119 def filectxfn(repo, ctx_, path):
3123 def filectxfn(repo, ctx_, path):
3120 try:
3124 try:
3121 return old.filectx(path)
3125 return old.filectx(path)
3122 except KeyError:
3126 except KeyError:
3123 return None
3127 return None
3124
3128
3125 user = opts.get('user') or old.user()
3129 user = opts.get('user') or old.user()
3126 date = opts.get('date') or old.date()
3130 date = opts.get('date') or old.date()
3127 editform = mergeeditform(old, 'commit.amend')
3131 editform = mergeeditform(old, 'commit.amend')
3128 editor = getcommiteditor(editform=editform,
3132 editor = getcommiteditor(editform=editform,
3129 **pycompat.strkwargs(opts))
3133 **pycompat.strkwargs(opts))
3130 if not message:
3134 if not message:
3131 editor = getcommiteditor(edit=True, editform=editform)
3135 editor = getcommiteditor(edit=True, editform=editform)
3132 message = old.description()
3136 message = old.description()
3133
3137
3134 pureextra = extra.copy()
3138 pureextra = extra.copy()
3135 extra['amend_source'] = old.hex()
3139 extra['amend_source'] = old.hex()
3136
3140
3137 new = context.memctx(repo,
3141 new = context.memctx(repo,
3138 parents=[base.node(), old.p2().node()],
3142 parents=[base.node(), old.p2().node()],
3139 text=message,
3143 text=message,
3140 files=files,
3144 files=files,
3141 filectxfn=filectxfn,
3145 filectxfn=filectxfn,
3142 user=user,
3146 user=user,
3143 date=date,
3147 date=date,
3144 extra=extra,
3148 extra=extra,
3145 editor=editor)
3149 editor=editor)
3146
3150
3147 newdesc = changelog.stripdesc(new.description())
3151 newdesc = changelog.stripdesc(new.description())
3148 if ((not node)
3152 if ((not node)
3149 and newdesc == old.description()
3153 and newdesc == old.description()
3150 and user == old.user()
3154 and user == old.user()
3151 and date == old.date()
3155 and date == old.date()
3152 and pureextra == old.extra()):
3156 and pureextra == old.extra()):
3153 # nothing changed. continuing here would create a new node
3157 # nothing changed. continuing here would create a new node
3154 # anyway because of the amend_source noise.
3158 # anyway because of the amend_source noise.
3155 #
3159 #
3156 # This not what we expect from amend.
3160 # This not what we expect from amend.
3157 return old.node()
3161 return old.node()
3158
3162
3159 ph = repo.ui.config('phases', 'new-commit', phases.draft)
3163 ph = repo.ui.config('phases', 'new-commit', phases.draft)
3160 try:
3164 try:
3161 if opts.get('secret'):
3165 if opts.get('secret'):
3162 commitphase = 'secret'
3166 commitphase = 'secret'
3163 else:
3167 else:
3164 commitphase = old.phase()
3168 commitphase = old.phase()
3165 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
3169 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
3166 newid = repo.commitctx(new)
3170 newid = repo.commitctx(new)
3167 finally:
3171 finally:
3168 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
3172 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
3169 if newid != old.node():
3173 if newid != old.node():
3170 # Reroute the working copy parent to the new changeset
3174 # Reroute the working copy parent to the new changeset
3171 repo.setparents(newid, nullid)
3175 repo.setparents(newid, nullid)
3172 mapping = {old.node(): (newid,)}
3176 mapping = {old.node(): (newid,)}
3173 if node:
3177 if node:
3174 mapping[node] = ()
3178 mapping[node] = ()
3175 scmutil.cleanupnodes(repo, mapping, 'amend')
3179 scmutil.cleanupnodes(repo, mapping, 'amend')
3176 return newid
3180 return newid
3177
3181
3178 def commiteditor(repo, ctx, subs, editform=''):
3182 def commiteditor(repo, ctx, subs, editform=''):
3179 if ctx.description():
3183 if ctx.description():
3180 return ctx.description()
3184 return ctx.description()
3181 return commitforceeditor(repo, ctx, subs, editform=editform,
3185 return commitforceeditor(repo, ctx, subs, editform=editform,
3182 unchangedmessagedetection=True)
3186 unchangedmessagedetection=True)
3183
3187
3184 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3188 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
3185 editform='', unchangedmessagedetection=False):
3189 editform='', unchangedmessagedetection=False):
3186 if not extramsg:
3190 if not extramsg:
3187 extramsg = _("Leave message empty to abort commit.")
3191 extramsg = _("Leave message empty to abort commit.")
3188
3192
3189 forms = [e for e in editform.split('.') if e]
3193 forms = [e for e in editform.split('.') if e]
3190 forms.insert(0, 'changeset')
3194 forms.insert(0, 'changeset')
3191 templatetext = None
3195 templatetext = None
3192 while forms:
3196 while forms:
3193 ref = '.'.join(forms)
3197 ref = '.'.join(forms)
3194 if repo.ui.config('committemplate', ref):
3198 if repo.ui.config('committemplate', ref):
3195 templatetext = committext = buildcommittemplate(
3199 templatetext = committext = buildcommittemplate(
3196 repo, ctx, subs, extramsg, ref)
3200 repo, ctx, subs, extramsg, ref)
3197 break
3201 break
3198 forms.pop()
3202 forms.pop()
3199 else:
3203 else:
3200 committext = buildcommittext(repo, ctx, subs, extramsg)
3204 committext = buildcommittext(repo, ctx, subs, extramsg)
3201
3205
3202 # run editor in the repository root
3206 # run editor in the repository root
3203 olddir = pycompat.getcwd()
3207 olddir = pycompat.getcwd()
3204 os.chdir(repo.root)
3208 os.chdir(repo.root)
3205
3209
3206 # make in-memory changes visible to external process
3210 # make in-memory changes visible to external process
3207 tr = repo.currenttransaction()
3211 tr = repo.currenttransaction()
3208 repo.dirstate.write(tr)
3212 repo.dirstate.write(tr)
3209 pending = tr and tr.writepending() and repo.root
3213 pending = tr and tr.writepending() and repo.root
3210
3214
3211 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3215 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
3212 editform=editform, pending=pending,
3216 editform=editform, pending=pending,
3213 repopath=repo.path)
3217 repopath=repo.path)
3214 text = editortext
3218 text = editortext
3215
3219
3216 # strip away anything below this special string (used for editors that want
3220 # strip away anything below this special string (used for editors that want
3217 # to display the diff)
3221 # to display the diff)
3218 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3222 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3219 if stripbelow:
3223 if stripbelow:
3220 text = text[:stripbelow.start()]
3224 text = text[:stripbelow.start()]
3221
3225
3222 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3226 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
3223 os.chdir(olddir)
3227 os.chdir(olddir)
3224
3228
3225 if finishdesc:
3229 if finishdesc:
3226 text = finishdesc(text)
3230 text = finishdesc(text)
3227 if not text.strip():
3231 if not text.strip():
3228 raise error.Abort(_("empty commit message"))
3232 raise error.Abort(_("empty commit message"))
3229 if unchangedmessagedetection and editortext == templatetext:
3233 if unchangedmessagedetection and editortext == templatetext:
3230 raise error.Abort(_("commit message unchanged"))
3234 raise error.Abort(_("commit message unchanged"))
3231
3235
3232 return text
3236 return text
3233
3237
3234 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3238 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3235 ui = repo.ui
3239 ui = repo.ui
3236 spec = formatter.templatespec(ref, None, None)
3240 spec = formatter.templatespec(ref, None, None)
3237 t = changeset_templater(ui, repo, spec, None, {}, False)
3241 t = changeset_templater(ui, repo, spec, None, {}, False)
3238 t.t.cache.update((k, templater.unquotestring(v))
3242 t.t.cache.update((k, templater.unquotestring(v))
3239 for k, v in repo.ui.configitems('committemplate'))
3243 for k, v in repo.ui.configitems('committemplate'))
3240
3244
3241 if not extramsg:
3245 if not extramsg:
3242 extramsg = '' # ensure that extramsg is string
3246 extramsg = '' # ensure that extramsg is string
3243
3247
3244 ui.pushbuffer()
3248 ui.pushbuffer()
3245 t.show(ctx, extramsg=extramsg)
3249 t.show(ctx, extramsg=extramsg)
3246 return ui.popbuffer()
3250 return ui.popbuffer()
3247
3251
3248 def hgprefix(msg):
3252 def hgprefix(msg):
3249 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3253 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
3250
3254
3251 def buildcommittext(repo, ctx, subs, extramsg):
3255 def buildcommittext(repo, ctx, subs, extramsg):
3252 edittext = []
3256 edittext = []
3253 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3257 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3254 if ctx.description():
3258 if ctx.description():
3255 edittext.append(ctx.description())
3259 edittext.append(ctx.description())
3256 edittext.append("")
3260 edittext.append("")
3257 edittext.append("") # Empty line between message and comments.
3261 edittext.append("") # Empty line between message and comments.
3258 edittext.append(hgprefix(_("Enter commit message."
3262 edittext.append(hgprefix(_("Enter commit message."
3259 " Lines beginning with 'HG:' are removed.")))
3263 " Lines beginning with 'HG:' are removed.")))
3260 edittext.append(hgprefix(extramsg))
3264 edittext.append(hgprefix(extramsg))
3261 edittext.append("HG: --")
3265 edittext.append("HG: --")
3262 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3266 edittext.append(hgprefix(_("user: %s") % ctx.user()))
3263 if ctx.p2():
3267 if ctx.p2():
3264 edittext.append(hgprefix(_("branch merge")))
3268 edittext.append(hgprefix(_("branch merge")))
3265 if ctx.branch():
3269 if ctx.branch():
3266 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3270 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
3267 if bookmarks.isactivewdirparent(repo):
3271 if bookmarks.isactivewdirparent(repo):
3268 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3272 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
3269 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3273 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
3270 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3274 edittext.extend([hgprefix(_("added %s") % f) for f in added])
3271 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3275 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
3272 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3276 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
3273 if not added and not modified and not removed:
3277 if not added and not modified and not removed:
3274 edittext.append(hgprefix(_("no files changed")))
3278 edittext.append(hgprefix(_("no files changed")))
3275 edittext.append("")
3279 edittext.append("")
3276
3280
3277 return "\n".join(edittext)
3281 return "\n".join(edittext)
3278
3282
3279 def commitstatus(repo, node, branch, bheads=None, opts=None):
3283 def commitstatus(repo, node, branch, bheads=None, opts=None):
3280 if opts is None:
3284 if opts is None:
3281 opts = {}
3285 opts = {}
3282 ctx = repo[node]
3286 ctx = repo[node]
3283 parents = ctx.parents()
3287 parents = ctx.parents()
3284
3288
3285 if (not opts.get('amend') and bheads and node not in bheads and not
3289 if (not opts.get('amend') and bheads and node not in bheads and not
3286 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3290 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3287 repo.ui.status(_('created new head\n'))
3291 repo.ui.status(_('created new head\n'))
3288 # The message is not printed for initial roots. For the other
3292 # The message is not printed for initial roots. For the other
3289 # changesets, it is printed in the following situations:
3293 # changesets, it is printed in the following situations:
3290 #
3294 #
3291 # Par column: for the 2 parents with ...
3295 # Par column: for the 2 parents with ...
3292 # N: null or no parent
3296 # N: null or no parent
3293 # B: parent is on another named branch
3297 # B: parent is on another named branch
3294 # C: parent is a regular non head changeset
3298 # C: parent is a regular non head changeset
3295 # H: parent was a branch head of the current branch
3299 # H: parent was a branch head of the current branch
3296 # Msg column: whether we print "created new head" message
3300 # Msg column: whether we print "created new head" message
3297 # In the following, it is assumed that there already exists some
3301 # In the following, it is assumed that there already exists some
3298 # initial branch heads of the current branch, otherwise nothing is
3302 # initial branch heads of the current branch, otherwise nothing is
3299 # printed anyway.
3303 # printed anyway.
3300 #
3304 #
3301 # Par Msg Comment
3305 # Par Msg Comment
3302 # N N y additional topo root
3306 # N N y additional topo root
3303 #
3307 #
3304 # B N y additional branch root
3308 # B N y additional branch root
3305 # C N y additional topo head
3309 # C N y additional topo head
3306 # H N n usual case
3310 # H N n usual case
3307 #
3311 #
3308 # B B y weird additional branch root
3312 # B B y weird additional branch root
3309 # C B y branch merge
3313 # C B y branch merge
3310 # H B n merge with named branch
3314 # H B n merge with named branch
3311 #
3315 #
3312 # C C y additional head from merge
3316 # C C y additional head from merge
3313 # C H n merge with a head
3317 # C H n merge with a head
3314 #
3318 #
3315 # H H n head merge: head count decreases
3319 # H H n head merge: head count decreases
3316
3320
3317 if not opts.get('close_branch'):
3321 if not opts.get('close_branch'):
3318 for r in parents:
3322 for r in parents:
3319 if r.closesbranch() and r.branch() == branch:
3323 if r.closesbranch() and r.branch() == branch:
3320 repo.ui.status(_('reopening closed branch head %d\n') % r)
3324 repo.ui.status(_('reopening closed branch head %d\n') % r)
3321
3325
3322 if repo.ui.debugflag:
3326 if repo.ui.debugflag:
3323 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3327 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3324 elif repo.ui.verbose:
3328 elif repo.ui.verbose:
3325 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3329 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3326
3330
3327 def postcommitstatus(repo, pats, opts):
3331 def postcommitstatus(repo, pats, opts):
3328 return repo.status(match=scmutil.match(repo[None], pats, opts))
3332 return repo.status(match=scmutil.match(repo[None], pats, opts))
3329
3333
3330 def revert(ui, repo, ctx, parents, *pats, **opts):
3334 def revert(ui, repo, ctx, parents, *pats, **opts):
3331 parent, p2 = parents
3335 parent, p2 = parents
3332 node = ctx.node()
3336 node = ctx.node()
3333
3337
3334 mf = ctx.manifest()
3338 mf = ctx.manifest()
3335 if node == p2:
3339 if node == p2:
3336 parent = p2
3340 parent = p2
3337
3341
3338 # need all matching names in dirstate and manifest of target rev,
3342 # need all matching names in dirstate and manifest of target rev,
3339 # so have to walk both. do not print errors if files exist in one
3343 # so have to walk both. do not print errors if files exist in one
3340 # but not other. in both cases, filesets should be evaluated against
3344 # but not other. in both cases, filesets should be evaluated against
3341 # workingctx to get consistent result (issue4497). this means 'set:**'
3345 # workingctx to get consistent result (issue4497). this means 'set:**'
3342 # cannot be used to select missing files from target rev.
3346 # cannot be used to select missing files from target rev.
3343
3347
3344 # `names` is a mapping for all elements in working copy and target revision
3348 # `names` is a mapping for all elements in working copy and target revision
3345 # The mapping is in the form:
3349 # The mapping is in the form:
3346 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3350 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3347 names = {}
3351 names = {}
3348
3352
3349 with repo.wlock():
3353 with repo.wlock():
3350 ## filling of the `names` mapping
3354 ## filling of the `names` mapping
3351 # walk dirstate to fill `names`
3355 # walk dirstate to fill `names`
3352
3356
3353 interactive = opts.get('interactive', False)
3357 interactive = opts.get('interactive', False)
3354 wctx = repo[None]
3358 wctx = repo[None]
3355 m = scmutil.match(wctx, pats, opts)
3359 m = scmutil.match(wctx, pats, opts)
3356
3360
3357 # we'll need this later
3361 # we'll need this later
3358 targetsubs = sorted(s for s in wctx.substate if m(s))
3362 targetsubs = sorted(s for s in wctx.substate if m(s))
3359
3363
3360 if not m.always():
3364 if not m.always():
3361 matcher = matchmod.badmatch(m, lambda x, y: False)
3365 matcher = matchmod.badmatch(m, lambda x, y: False)
3362 for abs in wctx.walk(matcher):
3366 for abs in wctx.walk(matcher):
3363 names[abs] = m.rel(abs), m.exact(abs)
3367 names[abs] = m.rel(abs), m.exact(abs)
3364
3368
3365 # walk target manifest to fill `names`
3369 # walk target manifest to fill `names`
3366
3370
3367 def badfn(path, msg):
3371 def badfn(path, msg):
3368 if path in names:
3372 if path in names:
3369 return
3373 return
3370 if path in ctx.substate:
3374 if path in ctx.substate:
3371 return
3375 return
3372 path_ = path + '/'
3376 path_ = path + '/'
3373 for f in names:
3377 for f in names:
3374 if f.startswith(path_):
3378 if f.startswith(path_):
3375 return
3379 return
3376 ui.warn("%s: %s\n" % (m.rel(path), msg))
3380 ui.warn("%s: %s\n" % (m.rel(path), msg))
3377
3381
3378 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3382 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3379 if abs not in names:
3383 if abs not in names:
3380 names[abs] = m.rel(abs), m.exact(abs)
3384 names[abs] = m.rel(abs), m.exact(abs)
3381
3385
3382 # Find status of all file in `names`.
3386 # Find status of all file in `names`.
3383 m = scmutil.matchfiles(repo, names)
3387 m = scmutil.matchfiles(repo, names)
3384
3388
3385 changes = repo.status(node1=node, match=m,
3389 changes = repo.status(node1=node, match=m,
3386 unknown=True, ignored=True, clean=True)
3390 unknown=True, ignored=True, clean=True)
3387 else:
3391 else:
3388 changes = repo.status(node1=node, match=m)
3392 changes = repo.status(node1=node, match=m)
3389 for kind in changes:
3393 for kind in changes:
3390 for abs in kind:
3394 for abs in kind:
3391 names[abs] = m.rel(abs), m.exact(abs)
3395 names[abs] = m.rel(abs), m.exact(abs)
3392
3396
3393 m = scmutil.matchfiles(repo, names)
3397 m = scmutil.matchfiles(repo, names)
3394
3398
3395 modified = set(changes.modified)
3399 modified = set(changes.modified)
3396 added = set(changes.added)
3400 added = set(changes.added)
3397 removed = set(changes.removed)
3401 removed = set(changes.removed)
3398 _deleted = set(changes.deleted)
3402 _deleted = set(changes.deleted)
3399 unknown = set(changes.unknown)
3403 unknown = set(changes.unknown)
3400 unknown.update(changes.ignored)
3404 unknown.update(changes.ignored)
3401 clean = set(changes.clean)
3405 clean = set(changes.clean)
3402 modadded = set()
3406 modadded = set()
3403
3407
3404 # We need to account for the state of the file in the dirstate,
3408 # We need to account for the state of the file in the dirstate,
3405 # even when we revert against something else than parent. This will
3409 # even when we revert against something else than parent. This will
3406 # slightly alter the behavior of revert (doing back up or not, delete
3410 # slightly alter the behavior of revert (doing back up or not, delete
3407 # or just forget etc).
3411 # or just forget etc).
3408 if parent == node:
3412 if parent == node:
3409 dsmodified = modified
3413 dsmodified = modified
3410 dsadded = added
3414 dsadded = added
3411 dsremoved = removed
3415 dsremoved = removed
3412 # store all local modifications, useful later for rename detection
3416 # store all local modifications, useful later for rename detection
3413 localchanges = dsmodified | dsadded
3417 localchanges = dsmodified | dsadded
3414 modified, added, removed = set(), set(), set()
3418 modified, added, removed = set(), set(), set()
3415 else:
3419 else:
3416 changes = repo.status(node1=parent, match=m)
3420 changes = repo.status(node1=parent, match=m)
3417 dsmodified = set(changes.modified)
3421 dsmodified = set(changes.modified)
3418 dsadded = set(changes.added)
3422 dsadded = set(changes.added)
3419 dsremoved = set(changes.removed)
3423 dsremoved = set(changes.removed)
3420 # store all local modifications, useful later for rename detection
3424 # store all local modifications, useful later for rename detection
3421 localchanges = dsmodified | dsadded
3425 localchanges = dsmodified | dsadded
3422
3426
3423 # only take into account for removes between wc and target
3427 # only take into account for removes between wc and target
3424 clean |= dsremoved - removed
3428 clean |= dsremoved - removed
3425 dsremoved &= removed
3429 dsremoved &= removed
3426 # distinct between dirstate remove and other
3430 # distinct between dirstate remove and other
3427 removed -= dsremoved
3431 removed -= dsremoved
3428
3432
3429 modadded = added & dsmodified
3433 modadded = added & dsmodified
3430 added -= modadded
3434 added -= modadded
3431
3435
3432 # tell newly modified apart.
3436 # tell newly modified apart.
3433 dsmodified &= modified
3437 dsmodified &= modified
3434 dsmodified |= modified & dsadded # dirstate added may need backup
3438 dsmodified |= modified & dsadded # dirstate added may need backup
3435 modified -= dsmodified
3439 modified -= dsmodified
3436
3440
3437 # We need to wait for some post-processing to update this set
3441 # We need to wait for some post-processing to update this set
3438 # before making the distinction. The dirstate will be used for
3442 # before making the distinction. The dirstate will be used for
3439 # that purpose.
3443 # that purpose.
3440 dsadded = added
3444 dsadded = added
3441
3445
3442 # in case of merge, files that are actually added can be reported as
3446 # in case of merge, files that are actually added can be reported as
3443 # modified, we need to post process the result
3447 # modified, we need to post process the result
3444 if p2 != nullid:
3448 if p2 != nullid:
3445 mergeadd = set(dsmodified)
3449 mergeadd = set(dsmodified)
3446 for path in dsmodified:
3450 for path in dsmodified:
3447 if path in mf:
3451 if path in mf:
3448 mergeadd.remove(path)
3452 mergeadd.remove(path)
3449 dsadded |= mergeadd
3453 dsadded |= mergeadd
3450 dsmodified -= mergeadd
3454 dsmodified -= mergeadd
3451
3455
3452 # if f is a rename, update `names` to also revert the source
3456 # if f is a rename, update `names` to also revert the source
3453 cwd = repo.getcwd()
3457 cwd = repo.getcwd()
3454 for f in localchanges:
3458 for f in localchanges:
3455 src = repo.dirstate.copied(f)
3459 src = repo.dirstate.copied(f)
3456 # XXX should we check for rename down to target node?
3460 # XXX should we check for rename down to target node?
3457 if src and src not in names and repo.dirstate[src] == 'r':
3461 if src and src not in names and repo.dirstate[src] == 'r':
3458 dsremoved.add(src)
3462 dsremoved.add(src)
3459 names[src] = (repo.pathto(src, cwd), True)
3463 names[src] = (repo.pathto(src, cwd), True)
3460
3464
3461 # determine the exact nature of the deleted changesets
3465 # determine the exact nature of the deleted changesets
3462 deladded = set(_deleted)
3466 deladded = set(_deleted)
3463 for path in _deleted:
3467 for path in _deleted:
3464 if path in mf:
3468 if path in mf:
3465 deladded.remove(path)
3469 deladded.remove(path)
3466 deleted = _deleted - deladded
3470 deleted = _deleted - deladded
3467
3471
3468 # distinguish between file to forget and the other
3472 # distinguish between file to forget and the other
3469 added = set()
3473 added = set()
3470 for abs in dsadded:
3474 for abs in dsadded:
3471 if repo.dirstate[abs] != 'a':
3475 if repo.dirstate[abs] != 'a':
3472 added.add(abs)
3476 added.add(abs)
3473 dsadded -= added
3477 dsadded -= added
3474
3478
3475 for abs in deladded:
3479 for abs in deladded:
3476 if repo.dirstate[abs] == 'a':
3480 if repo.dirstate[abs] == 'a':
3477 dsadded.add(abs)
3481 dsadded.add(abs)
3478 deladded -= dsadded
3482 deladded -= dsadded
3479
3483
3480 # For files marked as removed, we check if an unknown file is present at
3484 # For files marked as removed, we check if an unknown file is present at
3481 # the same path. If a such file exists it may need to be backed up.
3485 # the same path. If a such file exists it may need to be backed up.
3482 # Making the distinction at this stage helps have simpler backup
3486 # Making the distinction at this stage helps have simpler backup
3483 # logic.
3487 # logic.
3484 removunk = set()
3488 removunk = set()
3485 for abs in removed:
3489 for abs in removed:
3486 target = repo.wjoin(abs)
3490 target = repo.wjoin(abs)
3487 if os.path.lexists(target):
3491 if os.path.lexists(target):
3488 removunk.add(abs)
3492 removunk.add(abs)
3489 removed -= removunk
3493 removed -= removunk
3490
3494
3491 dsremovunk = set()
3495 dsremovunk = set()
3492 for abs in dsremoved:
3496 for abs in dsremoved:
3493 target = repo.wjoin(abs)
3497 target = repo.wjoin(abs)
3494 if os.path.lexists(target):
3498 if os.path.lexists(target):
3495 dsremovunk.add(abs)
3499 dsremovunk.add(abs)
3496 dsremoved -= dsremovunk
3500 dsremoved -= dsremovunk
3497
3501
3498 # action to be actually performed by revert
3502 # action to be actually performed by revert
3499 # (<list of file>, message>) tuple
3503 # (<list of file>, message>) tuple
3500 actions = {'revert': ([], _('reverting %s\n')),
3504 actions = {'revert': ([], _('reverting %s\n')),
3501 'add': ([], _('adding %s\n')),
3505 'add': ([], _('adding %s\n')),
3502 'remove': ([], _('removing %s\n')),
3506 'remove': ([], _('removing %s\n')),
3503 'drop': ([], _('removing %s\n')),
3507 'drop': ([], _('removing %s\n')),
3504 'forget': ([], _('forgetting %s\n')),
3508 'forget': ([], _('forgetting %s\n')),
3505 'undelete': ([], _('undeleting %s\n')),
3509 'undelete': ([], _('undeleting %s\n')),
3506 'noop': (None, _('no changes needed to %s\n')),
3510 'noop': (None, _('no changes needed to %s\n')),
3507 'unknown': (None, _('file not managed: %s\n')),
3511 'unknown': (None, _('file not managed: %s\n')),
3508 }
3512 }
3509
3513
3510 # "constant" that convey the backup strategy.
3514 # "constant" that convey the backup strategy.
3511 # All set to `discard` if `no-backup` is set do avoid checking
3515 # All set to `discard` if `no-backup` is set do avoid checking
3512 # no_backup lower in the code.
3516 # no_backup lower in the code.
3513 # These values are ordered for comparison purposes
3517 # These values are ordered for comparison purposes
3514 backupinteractive = 3 # do backup if interactively modified
3518 backupinteractive = 3 # do backup if interactively modified
3515 backup = 2 # unconditionally do backup
3519 backup = 2 # unconditionally do backup
3516 check = 1 # check if the existing file differs from target
3520 check = 1 # check if the existing file differs from target
3517 discard = 0 # never do backup
3521 discard = 0 # never do backup
3518 if opts.get('no_backup'):
3522 if opts.get('no_backup'):
3519 backupinteractive = backup = check = discard
3523 backupinteractive = backup = check = discard
3520 if interactive:
3524 if interactive:
3521 dsmodifiedbackup = backupinteractive
3525 dsmodifiedbackup = backupinteractive
3522 else:
3526 else:
3523 dsmodifiedbackup = backup
3527 dsmodifiedbackup = backup
3524 tobackup = set()
3528 tobackup = set()
3525
3529
3526 backupanddel = actions['remove']
3530 backupanddel = actions['remove']
3527 if not opts.get('no_backup'):
3531 if not opts.get('no_backup'):
3528 backupanddel = actions['drop']
3532 backupanddel = actions['drop']
3529
3533
3530 disptable = (
3534 disptable = (
3531 # dispatch table:
3535 # dispatch table:
3532 # file state
3536 # file state
3533 # action
3537 # action
3534 # make backup
3538 # make backup
3535
3539
3536 ## Sets that results that will change file on disk
3540 ## Sets that results that will change file on disk
3537 # Modified compared to target, no local change
3541 # Modified compared to target, no local change
3538 (modified, actions['revert'], discard),
3542 (modified, actions['revert'], discard),
3539 # Modified compared to target, but local file is deleted
3543 # Modified compared to target, but local file is deleted
3540 (deleted, actions['revert'], discard),
3544 (deleted, actions['revert'], discard),
3541 # Modified compared to target, local change
3545 # Modified compared to target, local change
3542 (dsmodified, actions['revert'], dsmodifiedbackup),
3546 (dsmodified, actions['revert'], dsmodifiedbackup),
3543 # Added since target
3547 # Added since target
3544 (added, actions['remove'], discard),
3548 (added, actions['remove'], discard),
3545 # Added in working directory
3549 # Added in working directory
3546 (dsadded, actions['forget'], discard),
3550 (dsadded, actions['forget'], discard),
3547 # Added since target, have local modification
3551 # Added since target, have local modification
3548 (modadded, backupanddel, backup),
3552 (modadded, backupanddel, backup),
3549 # Added since target but file is missing in working directory
3553 # Added since target but file is missing in working directory
3550 (deladded, actions['drop'], discard),
3554 (deladded, actions['drop'], discard),
3551 # Removed since target, before working copy parent
3555 # Removed since target, before working copy parent
3552 (removed, actions['add'], discard),
3556 (removed, actions['add'], discard),
3553 # Same as `removed` but an unknown file exists at the same path
3557 # Same as `removed` but an unknown file exists at the same path
3554 (removunk, actions['add'], check),
3558 (removunk, actions['add'], check),
3555 # Removed since targe, marked as such in working copy parent
3559 # Removed since targe, marked as such in working copy parent
3556 (dsremoved, actions['undelete'], discard),
3560 (dsremoved, actions['undelete'], discard),
3557 # Same as `dsremoved` but an unknown file exists at the same path
3561 # Same as `dsremoved` but an unknown file exists at the same path
3558 (dsremovunk, actions['undelete'], check),
3562 (dsremovunk, actions['undelete'], check),
3559 ## the following sets does not result in any file changes
3563 ## the following sets does not result in any file changes
3560 # File with no modification
3564 # File with no modification
3561 (clean, actions['noop'], discard),
3565 (clean, actions['noop'], discard),
3562 # Existing file, not tracked anywhere
3566 # Existing file, not tracked anywhere
3563 (unknown, actions['unknown'], discard),
3567 (unknown, actions['unknown'], discard),
3564 )
3568 )
3565
3569
3566 for abs, (rel, exact) in sorted(names.items()):
3570 for abs, (rel, exact) in sorted(names.items()):
3567 # target file to be touch on disk (relative to cwd)
3571 # target file to be touch on disk (relative to cwd)
3568 target = repo.wjoin(abs)
3572 target = repo.wjoin(abs)
3569 # search the entry in the dispatch table.
3573 # search the entry in the dispatch table.
3570 # if the file is in any of these sets, it was touched in the working
3574 # if the file is in any of these sets, it was touched in the working
3571 # directory parent and we are sure it needs to be reverted.
3575 # directory parent and we are sure it needs to be reverted.
3572 for table, (xlist, msg), dobackup in disptable:
3576 for table, (xlist, msg), dobackup in disptable:
3573 if abs not in table:
3577 if abs not in table:
3574 continue
3578 continue
3575 if xlist is not None:
3579 if xlist is not None:
3576 xlist.append(abs)
3580 xlist.append(abs)
3577 if dobackup:
3581 if dobackup:
3578 # If in interactive mode, don't automatically create
3582 # If in interactive mode, don't automatically create
3579 # .orig files (issue4793)
3583 # .orig files (issue4793)
3580 if dobackup == backupinteractive:
3584 if dobackup == backupinteractive:
3581 tobackup.add(abs)
3585 tobackup.add(abs)
3582 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3586 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3583 bakname = scmutil.origpath(ui, repo, rel)
3587 bakname = scmutil.origpath(ui, repo, rel)
3584 ui.note(_('saving current version of %s as %s\n') %
3588 ui.note(_('saving current version of %s as %s\n') %
3585 (rel, bakname))
3589 (rel, bakname))
3586 if not opts.get('dry_run'):
3590 if not opts.get('dry_run'):
3587 if interactive:
3591 if interactive:
3588 util.copyfile(target, bakname)
3592 util.copyfile(target, bakname)
3589 else:
3593 else:
3590 util.rename(target, bakname)
3594 util.rename(target, bakname)
3591 if ui.verbose or not exact:
3595 if ui.verbose or not exact:
3592 if not isinstance(msg, basestring):
3596 if not isinstance(msg, basestring):
3593 msg = msg(abs)
3597 msg = msg(abs)
3594 ui.status(msg % rel)
3598 ui.status(msg % rel)
3595 elif exact:
3599 elif exact:
3596 ui.warn(msg % rel)
3600 ui.warn(msg % rel)
3597 break
3601 break
3598
3602
3599 if not opts.get('dry_run'):
3603 if not opts.get('dry_run'):
3600 needdata = ('revert', 'add', 'undelete')
3604 needdata = ('revert', 'add', 'undelete')
3601 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3605 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3602 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3606 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3603
3607
3604 if targetsubs:
3608 if targetsubs:
3605 # Revert the subrepos on the revert list
3609 # Revert the subrepos on the revert list
3606 for sub in targetsubs:
3610 for sub in targetsubs:
3607 try:
3611 try:
3608 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3612 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3609 except KeyError:
3613 except KeyError:
3610 raise error.Abort("subrepository '%s' does not exist in %s!"
3614 raise error.Abort("subrepository '%s' does not exist in %s!"
3611 % (sub, short(ctx.node())))
3615 % (sub, short(ctx.node())))
3612
3616
3613 def _revertprefetch(repo, ctx, *files):
3617 def _revertprefetch(repo, ctx, *files):
3614 """Let extension changing the storage layer prefetch content"""
3618 """Let extension changing the storage layer prefetch content"""
3615 pass
3619 pass
3616
3620
3617 def _performrevert(repo, parents, ctx, actions, interactive=False,
3621 def _performrevert(repo, parents, ctx, actions, interactive=False,
3618 tobackup=None):
3622 tobackup=None):
3619 """function that actually perform all the actions computed for revert
3623 """function that actually perform all the actions computed for revert
3620
3624
3621 This is an independent function to let extension to plug in and react to
3625 This is an independent function to let extension to plug in and react to
3622 the imminent revert.
3626 the imminent revert.
3623
3627
3624 Make sure you have the working directory locked when calling this function.
3628 Make sure you have the working directory locked when calling this function.
3625 """
3629 """
3626 parent, p2 = parents
3630 parent, p2 = parents
3627 node = ctx.node()
3631 node = ctx.node()
3628 excluded_files = []
3632 excluded_files = []
3629 matcher_opts = {"exclude": excluded_files}
3633 matcher_opts = {"exclude": excluded_files}
3630
3634
3631 def checkout(f):
3635 def checkout(f):
3632 fc = ctx[f]
3636 fc = ctx[f]
3633 repo.wwrite(f, fc.data(), fc.flags())
3637 repo.wwrite(f, fc.data(), fc.flags())
3634
3638
3635 def doremove(f):
3639 def doremove(f):
3636 try:
3640 try:
3637 repo.wvfs.unlinkpath(f)
3641 repo.wvfs.unlinkpath(f)
3638 except OSError:
3642 except OSError:
3639 pass
3643 pass
3640 repo.dirstate.remove(f)
3644 repo.dirstate.remove(f)
3641
3645
3642 audit_path = pathutil.pathauditor(repo.root, cached=True)
3646 audit_path = pathutil.pathauditor(repo.root, cached=True)
3643 for f in actions['forget'][0]:
3647 for f in actions['forget'][0]:
3644 if interactive:
3648 if interactive:
3645 choice = repo.ui.promptchoice(
3649 choice = repo.ui.promptchoice(
3646 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3650 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3647 if choice == 0:
3651 if choice == 0:
3648 repo.dirstate.drop(f)
3652 repo.dirstate.drop(f)
3649 else:
3653 else:
3650 excluded_files.append(repo.wjoin(f))
3654 excluded_files.append(repo.wjoin(f))
3651 else:
3655 else:
3652 repo.dirstate.drop(f)
3656 repo.dirstate.drop(f)
3653 for f in actions['remove'][0]:
3657 for f in actions['remove'][0]:
3654 audit_path(f)
3658 audit_path(f)
3655 if interactive:
3659 if interactive:
3656 choice = repo.ui.promptchoice(
3660 choice = repo.ui.promptchoice(
3657 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3661 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3658 if choice == 0:
3662 if choice == 0:
3659 doremove(f)
3663 doremove(f)
3660 else:
3664 else:
3661 excluded_files.append(repo.wjoin(f))
3665 excluded_files.append(repo.wjoin(f))
3662 else:
3666 else:
3663 doremove(f)
3667 doremove(f)
3664 for f in actions['drop'][0]:
3668 for f in actions['drop'][0]:
3665 audit_path(f)
3669 audit_path(f)
3666 repo.dirstate.remove(f)
3670 repo.dirstate.remove(f)
3667
3671
3668 normal = None
3672 normal = None
3669 if node == parent:
3673 if node == parent:
3670 # We're reverting to our parent. If possible, we'd like status
3674 # We're reverting to our parent. If possible, we'd like status
3671 # to report the file as clean. We have to use normallookup for
3675 # to report the file as clean. We have to use normallookup for
3672 # merges to avoid losing information about merged/dirty files.
3676 # merges to avoid losing information about merged/dirty files.
3673 if p2 != nullid:
3677 if p2 != nullid:
3674 normal = repo.dirstate.normallookup
3678 normal = repo.dirstate.normallookup
3675 else:
3679 else:
3676 normal = repo.dirstate.normal
3680 normal = repo.dirstate.normal
3677
3681
3678 newlyaddedandmodifiedfiles = set()
3682 newlyaddedandmodifiedfiles = set()
3679 if interactive:
3683 if interactive:
3680 # Prompt the user for changes to revert
3684 # Prompt the user for changes to revert
3681 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3685 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3682 m = scmutil.match(ctx, torevert, matcher_opts)
3686 m = scmutil.match(ctx, torevert, matcher_opts)
3683 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3687 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3684 diffopts.nodates = True
3688 diffopts.nodates = True
3685 diffopts.git = True
3689 diffopts.git = True
3686 operation = 'discard'
3690 operation = 'discard'
3687 reversehunks = True
3691 reversehunks = True
3688 if node != parent:
3692 if node != parent:
3689 operation = 'revert'
3693 operation = 'revert'
3690 reversehunks = repo.ui.configbool('experimental',
3694 reversehunks = repo.ui.configbool('experimental',
3691 'revertalternateinteractivemode')
3695 'revertalternateinteractivemode')
3692 if reversehunks:
3696 if reversehunks:
3693 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3697 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3694 else:
3698 else:
3695 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3699 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3696 originalchunks = patch.parsepatch(diff)
3700 originalchunks = patch.parsepatch(diff)
3697
3701
3698 try:
3702 try:
3699
3703
3700 chunks, opts = recordfilter(repo.ui, originalchunks,
3704 chunks, opts = recordfilter(repo.ui, originalchunks,
3701 operation=operation)
3705 operation=operation)
3702 if reversehunks:
3706 if reversehunks:
3703 chunks = patch.reversehunks(chunks)
3707 chunks = patch.reversehunks(chunks)
3704
3708
3705 except patch.PatchError as err:
3709 except patch.PatchError as err:
3706 raise error.Abort(_('error parsing patch: %s') % err)
3710 raise error.Abort(_('error parsing patch: %s') % err)
3707
3711
3708 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3712 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3709 if tobackup is None:
3713 if tobackup is None:
3710 tobackup = set()
3714 tobackup = set()
3711 # Apply changes
3715 # Apply changes
3712 fp = stringio()
3716 fp = stringio()
3713 for c in chunks:
3717 for c in chunks:
3714 # Create a backup file only if this hunk should be backed up
3718 # Create a backup file only if this hunk should be backed up
3715 if ishunk(c) and c.header.filename() in tobackup:
3719 if ishunk(c) and c.header.filename() in tobackup:
3716 abs = c.header.filename()
3720 abs = c.header.filename()
3717 target = repo.wjoin(abs)
3721 target = repo.wjoin(abs)
3718 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3722 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3719 util.copyfile(target, bakname)
3723 util.copyfile(target, bakname)
3720 tobackup.remove(abs)
3724 tobackup.remove(abs)
3721 c.write(fp)
3725 c.write(fp)
3722 dopatch = fp.tell()
3726 dopatch = fp.tell()
3723 fp.seek(0)
3727 fp.seek(0)
3724 if dopatch:
3728 if dopatch:
3725 try:
3729 try:
3726 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3730 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3727 except patch.PatchError as err:
3731 except patch.PatchError as err:
3728 raise error.Abort(str(err))
3732 raise error.Abort(str(err))
3729 del fp
3733 del fp
3730 else:
3734 else:
3731 for f in actions['revert'][0]:
3735 for f in actions['revert'][0]:
3732 checkout(f)
3736 checkout(f)
3733 if normal:
3737 if normal:
3734 normal(f)
3738 normal(f)
3735
3739
3736 for f in actions['add'][0]:
3740 for f in actions['add'][0]:
3737 # Don't checkout modified files, they are already created by the diff
3741 # Don't checkout modified files, they are already created by the diff
3738 if f not in newlyaddedandmodifiedfiles:
3742 if f not in newlyaddedandmodifiedfiles:
3739 checkout(f)
3743 checkout(f)
3740 repo.dirstate.add(f)
3744 repo.dirstate.add(f)
3741
3745
3742 normal = repo.dirstate.normallookup
3746 normal = repo.dirstate.normallookup
3743 if node == parent and p2 == nullid:
3747 if node == parent and p2 == nullid:
3744 normal = repo.dirstate.normal
3748 normal = repo.dirstate.normal
3745 for f in actions['undelete'][0]:
3749 for f in actions['undelete'][0]:
3746 checkout(f)
3750 checkout(f)
3747 normal(f)
3751 normal(f)
3748
3752
3749 copied = copies.pathcopies(repo[parent], ctx)
3753 copied = copies.pathcopies(repo[parent], ctx)
3750
3754
3751 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3755 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3752 if f in copied:
3756 if f in copied:
3753 repo.dirstate.copy(copied[f], f)
3757 repo.dirstate.copy(copied[f], f)
3754
3758
3755 class command(registrar.command):
3759 class command(registrar.command):
3756 def _doregister(self, func, name, *args, **kwargs):
3760 def _doregister(self, func, name, *args, **kwargs):
3757 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3761 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3758 return super(command, self)._doregister(func, name, *args, **kwargs)
3762 return super(command, self)._doregister(func, name, *args, **kwargs)
3759
3763
3760 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3764 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3761 # commands.outgoing. "missing" is "missing" of the result of
3765 # commands.outgoing. "missing" is "missing" of the result of
3762 # "findcommonoutgoing()"
3766 # "findcommonoutgoing()"
3763 outgoinghooks = util.hooks()
3767 outgoinghooks = util.hooks()
3764
3768
3765 # a list of (ui, repo) functions called by commands.summary
3769 # a list of (ui, repo) functions called by commands.summary
3766 summaryhooks = util.hooks()
3770 summaryhooks = util.hooks()
3767
3771
3768 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3772 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3769 #
3773 #
3770 # functions should return tuple of booleans below, if 'changes' is None:
3774 # functions should return tuple of booleans below, if 'changes' is None:
3771 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3775 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3772 #
3776 #
3773 # otherwise, 'changes' is a tuple of tuples below:
3777 # otherwise, 'changes' is a tuple of tuples below:
3774 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3778 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3775 # - (desturl, destbranch, destpeer, outgoing)
3779 # - (desturl, destbranch, destpeer, outgoing)
3776 summaryremotehooks = util.hooks()
3780 summaryremotehooks = util.hooks()
3777
3781
3778 # A list of state files kept by multistep operations like graft.
3782 # A list of state files kept by multistep operations like graft.
3779 # Since graft cannot be aborted, it is considered 'clearable' by update.
3783 # Since graft cannot be aborted, it is considered 'clearable' by update.
3780 # note: bisect is intentionally excluded
3784 # note: bisect is intentionally excluded
3781 # (state file, clearable, allowcommit, error, hint)
3785 # (state file, clearable, allowcommit, error, hint)
3782 unfinishedstates = [
3786 unfinishedstates = [
3783 ('graftstate', True, False, _('graft in progress'),
3787 ('graftstate', True, False, _('graft in progress'),
3784 _("use 'hg graft --continue' or 'hg update' to abort")),
3788 _("use 'hg graft --continue' or 'hg update' to abort")),
3785 ('updatestate', True, False, _('last update was interrupted'),
3789 ('updatestate', True, False, _('last update was interrupted'),
3786 _("use 'hg update' to get a consistent checkout"))
3790 _("use 'hg update' to get a consistent checkout"))
3787 ]
3791 ]
3788
3792
3789 def checkunfinished(repo, commit=False):
3793 def checkunfinished(repo, commit=False):
3790 '''Look for an unfinished multistep operation, like graft, and abort
3794 '''Look for an unfinished multistep operation, like graft, and abort
3791 if found. It's probably good to check this right before
3795 if found. It's probably good to check this right before
3792 bailifchanged().
3796 bailifchanged().
3793 '''
3797 '''
3794 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3798 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3795 if commit and allowcommit:
3799 if commit and allowcommit:
3796 continue
3800 continue
3797 if repo.vfs.exists(f):
3801 if repo.vfs.exists(f):
3798 raise error.Abort(msg, hint=hint)
3802 raise error.Abort(msg, hint=hint)
3799
3803
3800 def clearunfinished(repo):
3804 def clearunfinished(repo):
3801 '''Check for unfinished operations (as above), and clear the ones
3805 '''Check for unfinished operations (as above), and clear the ones
3802 that are clearable.
3806 that are clearable.
3803 '''
3807 '''
3804 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3808 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3805 if not clearable and repo.vfs.exists(f):
3809 if not clearable and repo.vfs.exists(f):
3806 raise error.Abort(msg, hint=hint)
3810 raise error.Abort(msg, hint=hint)
3807 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3811 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3808 if clearable and repo.vfs.exists(f):
3812 if clearable and repo.vfs.exists(f):
3809 util.unlink(repo.vfs.join(f))
3813 util.unlink(repo.vfs.join(f))
3810
3814
3811 afterresolvedstates = [
3815 afterresolvedstates = [
3812 ('graftstate',
3816 ('graftstate',
3813 _('hg graft --continue')),
3817 _('hg graft --continue')),
3814 ]
3818 ]
3815
3819
3816 def howtocontinue(repo):
3820 def howtocontinue(repo):
3817 '''Check for an unfinished operation and return the command to finish
3821 '''Check for an unfinished operation and return the command to finish
3818 it.
3822 it.
3819
3823
3820 afterresolvedstates tuples define a .hg/{file} and the corresponding
3824 afterresolvedstates tuples define a .hg/{file} and the corresponding
3821 command needed to finish it.
3825 command needed to finish it.
3822
3826
3823 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3827 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3824 a boolean.
3828 a boolean.
3825 '''
3829 '''
3826 contmsg = _("continue: %s")
3830 contmsg = _("continue: %s")
3827 for f, msg in afterresolvedstates:
3831 for f, msg in afterresolvedstates:
3828 if repo.vfs.exists(f):
3832 if repo.vfs.exists(f):
3829 return contmsg % msg, True
3833 return contmsg % msg, True
3830 if repo[None].dirty(missing=True, merge=False, branch=False):
3834 if repo[None].dirty(missing=True, merge=False, branch=False):
3831 return contmsg % _("hg commit"), False
3835 return contmsg % _("hg commit"), False
3832 return None, None
3836 return None, None
3833
3837
3834 def checkafterresolved(repo):
3838 def checkafterresolved(repo):
3835 '''Inform the user about the next action after completing hg resolve
3839 '''Inform the user about the next action after completing hg resolve
3836
3840
3837 If there's a matching afterresolvedstates, howtocontinue will yield
3841 If there's a matching afterresolvedstates, howtocontinue will yield
3838 repo.ui.warn as the reporter.
3842 repo.ui.warn as the reporter.
3839
3843
3840 Otherwise, it will yield repo.ui.note.
3844 Otherwise, it will yield repo.ui.note.
3841 '''
3845 '''
3842 msg, warning = howtocontinue(repo)
3846 msg, warning = howtocontinue(repo)
3843 if msg is not None:
3847 if msg is not None:
3844 if warning:
3848 if warning:
3845 repo.ui.warn("%s\n" % msg)
3849 repo.ui.warn("%s\n" % msg)
3846 else:
3850 else:
3847 repo.ui.note("%s\n" % msg)
3851 repo.ui.note("%s\n" % msg)
3848
3852
3849 def wrongtooltocontinue(repo, task):
3853 def wrongtooltocontinue(repo, task):
3850 '''Raise an abort suggesting how to properly continue if there is an
3854 '''Raise an abort suggesting how to properly continue if there is an
3851 active task.
3855 active task.
3852
3856
3853 Uses howtocontinue() to find the active task.
3857 Uses howtocontinue() to find the active task.
3854
3858
3855 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3859 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3856 a hint.
3860 a hint.
3857 '''
3861 '''
3858 after = howtocontinue(repo)
3862 after = howtocontinue(repo)
3859 hint = None
3863 hint = None
3860 if after[1]:
3864 if after[1]:
3861 hint = after[0]
3865 hint = after[0]
3862 raise error.Abort(_('no %s in progress') % task, hint=hint)
3866 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,478 +1,481 b''
1 # Revision graph generator for Mercurial
1 # Revision graph generator for Mercurial
2 #
2 #
3 # Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
3 # Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
4 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
4 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """supports walking the history as DAGs suitable for graphical output
9 """supports walking the history as DAGs suitable for graphical output
10
10
11 The most basic format we use is that of::
11 The most basic format we use is that of::
12
12
13 (id, type, data, [parentids])
13 (id, type, data, [parentids])
14
14
15 The node and parent ids are arbitrary integers which identify a node in the
15 The node and parent ids are arbitrary integers which identify a node in the
16 context of the graph returned. Type is a constant specifying the node type.
16 context of the graph returned. Type is a constant specifying the node type.
17 Data depends on type.
17 Data depends on type.
18 """
18 """
19
19
20 from __future__ import absolute_import
20 from __future__ import absolute_import
21
21
22 from .node import nullrev
22 from .node import nullrev
23 from . import (
23 from . import (
24 dagop,
24 dagop,
25 smartset,
25 smartset,
26 util,
26 util,
27 )
27 )
28
28
29 CHANGESET = 'C'
29 CHANGESET = 'C'
30 PARENT = 'P'
30 PARENT = 'P'
31 GRANDPARENT = 'G'
31 GRANDPARENT = 'G'
32 MISSINGPARENT = 'M'
32 MISSINGPARENT = 'M'
33 # Style of line to draw. None signals a line that ends and is removed at this
33 # Style of line to draw. None signals a line that ends and is removed at this
34 # point. A number prefix means only the last N characters of the current block
34 # point. A number prefix means only the last N characters of the current block
35 # will use that style, the rest will use the PARENT style. Add a - sign
35 # will use that style, the rest will use the PARENT style. Add a - sign
36 # (so making N negative) and all but the first N characters use that style.
36 # (so making N negative) and all but the first N characters use that style.
37 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None}
37 EDGES = {PARENT: '|', GRANDPARENT: ':', MISSINGPARENT: None}
38
38
39 def dagwalker(repo, revs):
39 def dagwalker(repo, revs):
40 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
40 """cset DAG generator yielding (id, CHANGESET, ctx, [parentinfo]) tuples
41
41
42 This generator function walks through revisions (which should be ordered
42 This generator function walks through revisions (which should be ordered
43 from bigger to lower). It returns a tuple for each node.
43 from bigger to lower). It returns a tuple for each node.
44
44
45 Each parentinfo entry is a tuple with (edgetype, parentid), where edgetype
45 Each parentinfo entry is a tuple with (edgetype, parentid), where edgetype
46 is one of PARENT, GRANDPARENT or MISSINGPARENT. The node and parent ids
46 is one of PARENT, GRANDPARENT or MISSINGPARENT. The node and parent ids
47 are arbitrary integers which identify a node in the context of the graph
47 are arbitrary integers which identify a node in the context of the graph
48 returned.
48 returned.
49
49
50 """
50 """
51 if not revs:
51 if not revs:
52 return
52 return
53
53
54 gpcache = {}
54 gpcache = {}
55
55
56 for rev in revs:
56 for rev in revs:
57 ctx = repo[rev]
57 ctx = repo[rev]
58 # partition into parents in the rev set and missing parents, then
58 # partition into parents in the rev set and missing parents, then
59 # augment the lists with markers, to inform graph drawing code about
59 # augment the lists with markers, to inform graph drawing code about
60 # what kind of edge to draw between nodes.
60 # what kind of edge to draw between nodes.
61 pset = set(p.rev() for p in ctx.parents() if p.rev() in revs)
61 pset = set(p.rev() for p in ctx.parents() if p.rev() in revs)
62 mpars = [p.rev() for p in ctx.parents()
62 mpars = [p.rev() for p in ctx.parents()
63 if p.rev() != nullrev and p.rev() not in pset]
63 if p.rev() != nullrev and p.rev() not in pset]
64 parents = [(PARENT, p) for p in sorted(pset)]
64 parents = [(PARENT, p) for p in sorted(pset)]
65
65
66 for mpar in mpars:
66 for mpar in mpars:
67 gp = gpcache.get(mpar)
67 gp = gpcache.get(mpar)
68 if gp is None:
68 if gp is None:
69 # precompute slow query as we know reachableroots() goes
69 # precompute slow query as we know reachableroots() goes
70 # through all revs (issue4782)
70 # through all revs (issue4782)
71 if not isinstance(revs, smartset.baseset):
71 if not isinstance(revs, smartset.baseset):
72 revs = smartset.baseset(revs)
72 revs = smartset.baseset(revs)
73 gp = gpcache[mpar] = sorted(set(dagop.reachableroots(
73 gp = gpcache[mpar] = sorted(set(dagop.reachableroots(
74 repo, revs, [mpar])))
74 repo, revs, [mpar])))
75 if not gp:
75 if not gp:
76 parents.append((MISSINGPARENT, mpar))
76 parents.append((MISSINGPARENT, mpar))
77 pset.add(mpar)
77 pset.add(mpar)
78 else:
78 else:
79 parents.extend((GRANDPARENT, g) for g in gp if g not in pset)
79 parents.extend((GRANDPARENT, g) for g in gp if g not in pset)
80 pset.update(gp)
80 pset.update(gp)
81
81
82 yield (ctx.rev(), CHANGESET, ctx, parents)
82 yield (ctx.rev(), CHANGESET, ctx, parents)
83
83
84 def nodes(repo, nodes):
84 def nodes(repo, nodes):
85 """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
85 """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
86
86
87 This generator function walks the given nodes. It only returns parents
87 This generator function walks the given nodes. It only returns parents
88 that are in nodes, too.
88 that are in nodes, too.
89 """
89 """
90 include = set(nodes)
90 include = set(nodes)
91 for node in nodes:
91 for node in nodes:
92 ctx = repo[node]
92 ctx = repo[node]
93 parents = set((PARENT, p.rev()) for p in ctx.parents()
93 parents = set((PARENT, p.rev()) for p in ctx.parents()
94 if p.node() in include)
94 if p.node() in include)
95 yield (ctx.rev(), CHANGESET, ctx, sorted(parents))
95 yield (ctx.rev(), CHANGESET, ctx, sorted(parents))
96
96
97 def colored(dag, repo):
97 def colored(dag, repo):
98 """annotates a DAG with colored edge information
98 """annotates a DAG with colored edge information
99
99
100 For each DAG node this function emits tuples::
100 For each DAG node this function emits tuples::
101
101
102 (id, type, data, (col, color), [(col, nextcol, color)])
102 (id, type, data, (col, color), [(col, nextcol, color)])
103
103
104 with the following new elements:
104 with the following new elements:
105
105
106 - Tuple (col, color) with column and color index for the current node
106 - Tuple (col, color) with column and color index for the current node
107 - A list of tuples indicating the edges between the current node and its
107 - A list of tuples indicating the edges between the current node and its
108 parents.
108 parents.
109 """
109 """
110 seen = []
110 seen = []
111 colors = {}
111 colors = {}
112 newcolor = 1
112 newcolor = 1
113 config = {}
113 config = {}
114
114
115 for key, val in repo.ui.configitems('graph'):
115 for key, val in repo.ui.configitems('graph'):
116 if '.' in key:
116 if '.' in key:
117 branch, setting = key.rsplit('.', 1)
117 branch, setting = key.rsplit('.', 1)
118 # Validation
118 # Validation
119 if setting == "width" and val.isdigit():
119 if setting == "width" and val.isdigit():
120 config.setdefault(branch, {})[setting] = int(val)
120 config.setdefault(branch, {})[setting] = int(val)
121 elif setting == "color" and val.isalnum():
121 elif setting == "color" and val.isalnum():
122 config.setdefault(branch, {})[setting] = val
122 config.setdefault(branch, {})[setting] = val
123
123
124 if config:
124 if config:
125 getconf = util.lrucachefunc(
125 getconf = util.lrucachefunc(
126 lambda rev: config.get(repo[rev].branch(), {}))
126 lambda rev: config.get(repo[rev].branch(), {}))
127 else:
127 else:
128 getconf = lambda rev: {}
128 getconf = lambda rev: {}
129
129
130 for (cur, type, data, parents) in dag:
130 for (cur, type, data, parents) in dag:
131
131
132 # Compute seen and next
132 # Compute seen and next
133 if cur not in seen:
133 if cur not in seen:
134 seen.append(cur) # new head
134 seen.append(cur) # new head
135 colors[cur] = newcolor
135 colors[cur] = newcolor
136 newcolor += 1
136 newcolor += 1
137
137
138 col = seen.index(cur)
138 col = seen.index(cur)
139 color = colors.pop(cur)
139 color = colors.pop(cur)
140 next = seen[:]
140 next = seen[:]
141
141
142 # Add parents to next
142 # Add parents to next
143 addparents = [p for pt, p in parents if p not in next]
143 addparents = [p for pt, p in parents if p not in next]
144 next[col:col + 1] = addparents
144 next[col:col + 1] = addparents
145
145
146 # Set colors for the parents
146 # Set colors for the parents
147 for i, p in enumerate(addparents):
147 for i, p in enumerate(addparents):
148 if not i:
148 if not i:
149 colors[p] = color
149 colors[p] = color
150 else:
150 else:
151 colors[p] = newcolor
151 colors[p] = newcolor
152 newcolor += 1
152 newcolor += 1
153
153
154 # Add edges to the graph
154 # Add edges to the graph
155 edges = []
155 edges = []
156 for ecol, eid in enumerate(seen):
156 for ecol, eid in enumerate(seen):
157 if eid in next:
157 if eid in next:
158 bconf = getconf(eid)
158 bconf = getconf(eid)
159 edges.append((
159 edges.append((
160 ecol, next.index(eid), colors[eid],
160 ecol, next.index(eid), colors[eid],
161 bconf.get('width', -1),
161 bconf.get('width', -1),
162 bconf.get('color', '')))
162 bconf.get('color', '')))
163 elif eid == cur:
163 elif eid == cur:
164 for ptype, p in parents:
164 for ptype, p in parents:
165 bconf = getconf(p)
165 bconf = getconf(p)
166 edges.append((
166 edges.append((
167 ecol, next.index(p), color,
167 ecol, next.index(p), color,
168 bconf.get('width', -1),
168 bconf.get('width', -1),
169 bconf.get('color', '')))
169 bconf.get('color', '')))
170
170
171 # Yield and move on
171 # Yield and move on
172 yield (cur, type, data, (col, color), edges)
172 yield (cur, type, data, (col, color), edges)
173 seen = next
173 seen = next
174
174
175 def asciiedges(type, char, lines, state, rev, parents):
175 def asciiedges(type, char, state, rev, parents):
176 """adds edge info to changelog DAG walk suitable for ascii()"""
176 """adds edge info to changelog DAG walk suitable for ascii()"""
177 seen = state['seen']
177 seen = state['seen']
178 if rev not in seen:
178 if rev not in seen:
179 seen.append(rev)
179 seen.append(rev)
180 nodeidx = seen.index(rev)
180 nodeidx = seen.index(rev)
181
181
182 knownparents = []
182 knownparents = []
183 newparents = []
183 newparents = []
184 for ptype, parent in parents:
184 for ptype, parent in parents:
185 if parent == rev:
185 if parent == rev:
186 # self reference (should only be seen in null rev)
186 # self reference (should only be seen in null rev)
187 continue
187 continue
188 if parent in seen:
188 if parent in seen:
189 knownparents.append(parent)
189 knownparents.append(parent)
190 else:
190 else:
191 newparents.append(parent)
191 newparents.append(parent)
192 state['edges'][parent] = state['styles'].get(ptype, '|')
192 state['edges'][parent] = state['styles'].get(ptype, '|')
193
193
194 ncols = len(seen)
194 ncols = len(seen)
195 width = 1 + ncols * 2
195 nextseen = seen[:]
196 nextseen = seen[:]
196 nextseen[nodeidx:nodeidx + 1] = newparents
197 nextseen[nodeidx:nodeidx + 1] = newparents
197 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
198 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
198
199
199 seen[:] = nextseen
200 seen[:] = nextseen
200 while len(newparents) > 2:
201 while len(newparents) > 2:
201 # ascii() only knows how to add or remove a single column between two
202 # ascii() only knows how to add or remove a single column between two
202 # calls. Nodes with more than two parents break this constraint so we
203 # calls. Nodes with more than two parents break this constraint so we
203 # introduce intermediate expansion lines to grow the active node list
204 # introduce intermediate expansion lines to grow the active node list
204 # slowly.
205 # slowly.
205 edges.append((nodeidx, nodeidx))
206 edges.append((nodeidx, nodeidx))
206 edges.append((nodeidx, nodeidx + 1))
207 edges.append((nodeidx, nodeidx + 1))
207 nmorecols = 1
208 nmorecols = 1
208 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
209 width += 2
210 yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
209 char = '\\'
211 char = '\\'
210 lines = []
211 nodeidx += 1
212 nodeidx += 1
212 ncols += 1
213 ncols += 1
213 edges = []
214 edges = []
214 del newparents[0]
215 del newparents[0]
215
216
216 if len(newparents) > 0:
217 if len(newparents) > 0:
217 edges.append((nodeidx, nodeidx))
218 edges.append((nodeidx, nodeidx))
218 if len(newparents) > 1:
219 if len(newparents) > 1:
219 edges.append((nodeidx, nodeidx + 1))
220 edges.append((nodeidx, nodeidx + 1))
220 nmorecols = len(nextseen) - ncols
221 nmorecols = len(nextseen) - ncols
222 if nmorecols > 0:
223 width += 2
221 # remove current node from edge characters, no longer needed
224 # remove current node from edge characters, no longer needed
222 state['edges'].pop(rev, None)
225 state['edges'].pop(rev, None)
223 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
226 yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
224
227
225 def _fixlongrightedges(edges):
228 def _fixlongrightedges(edges):
226 for (i, (start, end)) in enumerate(edges):
229 for (i, (start, end)) in enumerate(edges):
227 if end > start:
230 if end > start:
228 edges[i] = (start, end + 1)
231 edges[i] = (start, end + 1)
229
232
230 def _getnodelineedgestail(
233 def _getnodelineedgestail(
231 echars, idx, pidx, ncols, coldiff, pdiff, fix_tail):
234 echars, idx, pidx, ncols, coldiff, pdiff, fix_tail):
232 if fix_tail and coldiff == pdiff and coldiff != 0:
235 if fix_tail and coldiff == pdiff and coldiff != 0:
233 # Still going in the same non-vertical direction.
236 # Still going in the same non-vertical direction.
234 if coldiff == -1:
237 if coldiff == -1:
235 start = max(idx + 1, pidx)
238 start = max(idx + 1, pidx)
236 tail = echars[idx * 2:(start - 1) * 2]
239 tail = echars[idx * 2:(start - 1) * 2]
237 tail.extend(["/", " "] * (ncols - start))
240 tail.extend(["/", " "] * (ncols - start))
238 return tail
241 return tail
239 else:
242 else:
240 return ["\\", " "] * (ncols - idx - 1)
243 return ["\\", " "] * (ncols - idx - 1)
241 else:
244 else:
242 remainder = (ncols - idx - 1)
245 remainder = (ncols - idx - 1)
243 return echars[-(remainder * 2):] if remainder > 0 else []
246 return echars[-(remainder * 2):] if remainder > 0 else []
244
247
245 def _drawedges(echars, edges, nodeline, interline):
248 def _drawedges(echars, edges, nodeline, interline):
246 for (start, end) in edges:
249 for (start, end) in edges:
247 if start == end + 1:
250 if start == end + 1:
248 interline[2 * end + 1] = "/"
251 interline[2 * end + 1] = "/"
249 elif start == end - 1:
252 elif start == end - 1:
250 interline[2 * start + 1] = "\\"
253 interline[2 * start + 1] = "\\"
251 elif start == end:
254 elif start == end:
252 interline[2 * start] = echars[2 * start]
255 interline[2 * start] = echars[2 * start]
253 else:
256 else:
254 if 2 * end >= len(nodeline):
257 if 2 * end >= len(nodeline):
255 continue
258 continue
256 nodeline[2 * end] = "+"
259 nodeline[2 * end] = "+"
257 if start > end:
260 if start > end:
258 (start, end) = (end, start)
261 (start, end) = (end, start)
259 for i in range(2 * start + 1, 2 * end):
262 for i in range(2 * start + 1, 2 * end):
260 if nodeline[i] != "+":
263 if nodeline[i] != "+":
261 nodeline[i] = "-"
264 nodeline[i] = "-"
262
265
263 def _getpaddingline(echars, idx, ncols, edges):
266 def _getpaddingline(echars, idx, ncols, edges):
264 # all edges up to the current node
267 # all edges up to the current node
265 line = echars[:idx * 2]
268 line = echars[:idx * 2]
266 # an edge for the current node, if there is one
269 # an edge for the current node, if there is one
267 if (idx, idx - 1) in edges or (idx, idx) in edges:
270 if (idx, idx - 1) in edges or (idx, idx) in edges:
268 # (idx, idx - 1) (idx, idx)
271 # (idx, idx - 1) (idx, idx)
269 # | | | | | | | |
272 # | | | | | | | |
270 # +---o | | o---+
273 # +---o | | o---+
271 # | | X | | X | |
274 # | | X | | X | |
272 # | |/ / | |/ /
275 # | |/ / | |/ /
273 # | | | | | |
276 # | | | | | |
274 line.extend(echars[idx * 2:(idx + 1) * 2])
277 line.extend(echars[idx * 2:(idx + 1) * 2])
275 else:
278 else:
276 line.extend([' ', ' '])
279 line.extend([' ', ' '])
277 # all edges to the right of the current node
280 # all edges to the right of the current node
278 remainder = ncols - idx - 1
281 remainder = ncols - idx - 1
279 if remainder > 0:
282 if remainder > 0:
280 line.extend(echars[-(remainder * 2):])
283 line.extend(echars[-(remainder * 2):])
281 return line
284 return line
282
285
283 def _drawendinglines(lines, extra, edgemap, seen):
286 def _drawendinglines(lines, extra, edgemap, seen):
284 """Draw ending lines for missing parent edges
287 """Draw ending lines for missing parent edges
285
288
286 None indicates an edge that ends at between this node and the next
289 None indicates an edge that ends at between this node and the next
287 Replace with a short line ending in ~ and add / lines to any edges to
290 Replace with a short line ending in ~ and add / lines to any edges to
288 the right.
291 the right.
289
292
290 """
293 """
291 if None not in edgemap.values():
294 if None not in edgemap.values():
292 return
295 return
293
296
294 # Check for more edges to the right of our ending edges.
297 # Check for more edges to the right of our ending edges.
295 # We need enough space to draw adjustment lines for these.
298 # We need enough space to draw adjustment lines for these.
296 edgechars = extra[::2]
299 edgechars = extra[::2]
297 while edgechars and edgechars[-1] is None:
300 while edgechars and edgechars[-1] is None:
298 edgechars.pop()
301 edgechars.pop()
299 shift_size = max((edgechars.count(None) * 2) - 1, 0)
302 shift_size = max((edgechars.count(None) * 2) - 1, 0)
300 while len(lines) < 3 + shift_size:
303 while len(lines) < 3 + shift_size:
301 lines.append(extra[:])
304 lines.append(extra[:])
302
305
303 if shift_size:
306 if shift_size:
304 empties = []
307 empties = []
305 toshift = []
308 toshift = []
306 first_empty = extra.index(None)
309 first_empty = extra.index(None)
307 for i, c in enumerate(extra[first_empty::2], first_empty // 2):
310 for i, c in enumerate(extra[first_empty::2], first_empty // 2):
308 if c is None:
311 if c is None:
309 empties.append(i * 2)
312 empties.append(i * 2)
310 else:
313 else:
311 toshift.append(i * 2)
314 toshift.append(i * 2)
312 targets = list(range(first_empty, first_empty + len(toshift) * 2, 2))
315 targets = list(range(first_empty, first_empty + len(toshift) * 2, 2))
313 positions = toshift[:]
316 positions = toshift[:]
314 for line in lines[-shift_size:]:
317 for line in lines[-shift_size:]:
315 line[first_empty:] = [' '] * (len(line) - first_empty)
318 line[first_empty:] = [' '] * (len(line) - first_empty)
316 for i in range(len(positions)):
319 for i in range(len(positions)):
317 pos = positions[i] - 1
320 pos = positions[i] - 1
318 positions[i] = max(pos, targets[i])
321 positions[i] = max(pos, targets[i])
319 line[pos] = '/' if pos > targets[i] else extra[toshift[i]]
322 line[pos] = '/' if pos > targets[i] else extra[toshift[i]]
320
323
321 map = {1: '|', 2: '~'}
324 map = {1: '|', 2: '~'}
322 for i, line in enumerate(lines):
325 for i, line in enumerate(lines):
323 if None not in line:
326 if None not in line:
324 continue
327 continue
325 line[:] = [c or map.get(i, ' ') for c in line]
328 line[:] = [c or map.get(i, ' ') for c in line]
326
329
327 # remove edges that ended
330 # remove edges that ended
328 remove = [p for p, c in edgemap.items() if c is None]
331 remove = [p for p, c in edgemap.items() if c is None]
329 for parent in remove:
332 for parent in remove:
330 del edgemap[parent]
333 del edgemap[parent]
331 seen.remove(parent)
334 seen.remove(parent)
332
335
333 def asciistate():
336 def asciistate():
334 """returns the initial value for the "state" argument to ascii()"""
337 """returns the initial value for the "state" argument to ascii()"""
335 return {
338 return {
336 'seen': [],
339 'seen': [],
337 'edges': {},
340 'edges': {},
338 'lastcoldiff': 0,
341 'lastcoldiff': 0,
339 'lastindex': 0,
342 'lastindex': 0,
340 'styles': EDGES.copy(),
343 'styles': EDGES.copy(),
341 'graphshorten': False,
344 'graphshorten': False,
342 }
345 }
343
346
344 def ascii(ui, state, type, char, text, coldata):
347 def ascii(ui, state, type, char, text, coldata):
345 """prints an ASCII graph of the DAG
348 """prints an ASCII graph of the DAG
346
349
347 takes the following arguments (one call per node in the graph):
350 takes the following arguments (one call per node in the graph):
348
351
349 - ui to write to
352 - ui to write to
350 - Somewhere to keep the needed state in (init to asciistate())
353 - Somewhere to keep the needed state in (init to asciistate())
351 - Column of the current node in the set of ongoing edges.
354 - Column of the current node in the set of ongoing edges.
352 - Type indicator of node data, usually 'C' for changesets.
355 - Type indicator of node data, usually 'C' for changesets.
353 - Payload: (char, lines):
356 - Payload: (char, lines):
354 - Character to use as node's symbol.
357 - Character to use as node's symbol.
355 - List of lines to display as the node's text.
358 - List of lines to display as the node's text.
356 - Edges; a list of (col, next_col) indicating the edges between
359 - Edges; a list of (col, next_col) indicating the edges between
357 the current node and its parents.
360 the current node and its parents.
358 - Number of columns (ongoing edges) in the current revision.
361 - Number of columns (ongoing edges) in the current revision.
359 - The difference between the number of columns (ongoing edges)
362 - The difference between the number of columns (ongoing edges)
360 in the next revision and the number of columns (ongoing edges)
363 in the next revision and the number of columns (ongoing edges)
361 in the current revision. That is: -1 means one column removed;
364 in the current revision. That is: -1 means one column removed;
362 0 means no columns added or removed; 1 means one column added.
365 0 means no columns added or removed; 1 means one column added.
363 """
366 """
364 idx, edges, ncols, coldiff = coldata
367 idx, edges, ncols, coldiff = coldata
365 assert -2 < coldiff < 2
368 assert -2 < coldiff < 2
366
369
367 edgemap, seen = state['edges'], state['seen']
370 edgemap, seen = state['edges'], state['seen']
368 # Be tolerant of history issues; make sure we have at least ncols + coldiff
371 # Be tolerant of history issues; make sure we have at least ncols + coldiff
369 # elements to work with. See test-glog.t for broken history test cases.
372 # elements to work with. See test-glog.t for broken history test cases.
370 echars = [c for p in seen for c in (edgemap.get(p, '|'), ' ')]
373 echars = [c for p in seen for c in (edgemap.get(p, '|'), ' ')]
371 echars.extend(('|', ' ') * max(ncols + coldiff - len(seen), 0))
374 echars.extend(('|', ' ') * max(ncols + coldiff - len(seen), 0))
372
375
373 if coldiff == -1:
376 if coldiff == -1:
374 # Transform
377 # Transform
375 #
378 #
376 # | | | | | |
379 # | | | | | |
377 # o | | into o---+
380 # o | | into o---+
378 # |X / |/ /
381 # |X / |/ /
379 # | | | |
382 # | | | |
380 _fixlongrightedges(edges)
383 _fixlongrightedges(edges)
381
384
382 # add_padding_line says whether to rewrite
385 # add_padding_line says whether to rewrite
383 #
386 #
384 # | | | | | | | |
387 # | | | | | | | |
385 # | o---+ into | o---+
388 # | o---+ into | o---+
386 # | / / | | | # <--- padding line
389 # | / / | | | # <--- padding line
387 # o | | | / /
390 # o | | | / /
388 # o | |
391 # o | |
389 add_padding_line = (len(text) > 2 and coldiff == -1 and
392 add_padding_line = (len(text) > 2 and coldiff == -1 and
390 [x for (x, y) in edges if x + 1 < y])
393 [x for (x, y) in edges if x + 1 < y])
391
394
392 # fix_nodeline_tail says whether to rewrite
395 # fix_nodeline_tail says whether to rewrite
393 #
396 #
394 # | | o | | | | o | |
397 # | | o | | | | o | |
395 # | | |/ / | | |/ /
398 # | | |/ / | | |/ /
396 # | o | | into | o / / # <--- fixed nodeline tail
399 # | o | | into | o / / # <--- fixed nodeline tail
397 # | |/ / | |/ /
400 # | |/ / | |/ /
398 # o | | o | |
401 # o | | o | |
399 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
402 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
400
403
401 # nodeline is the line containing the node character (typically o)
404 # nodeline is the line containing the node character (typically o)
402 nodeline = echars[:idx * 2]
405 nodeline = echars[:idx * 2]
403 nodeline.extend([char, " "])
406 nodeline.extend([char, " "])
404
407
405 nodeline.extend(
408 nodeline.extend(
406 _getnodelineedgestail(
409 _getnodelineedgestail(
407 echars, idx, state['lastindex'], ncols, coldiff,
410 echars, idx, state['lastindex'], ncols, coldiff,
408 state['lastcoldiff'], fix_nodeline_tail))
411 state['lastcoldiff'], fix_nodeline_tail))
409
412
410 # shift_interline is the line containing the non-vertical
413 # shift_interline is the line containing the non-vertical
411 # edges between this entry and the next
414 # edges between this entry and the next
412 shift_interline = echars[:idx * 2]
415 shift_interline = echars[:idx * 2]
413 for i in xrange(2 + coldiff):
416 for i in xrange(2 + coldiff):
414 shift_interline.append(' ')
417 shift_interline.append(' ')
415 count = ncols - idx - 1
418 count = ncols - idx - 1
416 if coldiff == -1:
419 if coldiff == -1:
417 for i in xrange(count):
420 for i in xrange(count):
418 shift_interline.extend(['/', ' '])
421 shift_interline.extend(['/', ' '])
419 elif coldiff == 0:
422 elif coldiff == 0:
420 shift_interline.extend(echars[(idx + 1) * 2:ncols * 2])
423 shift_interline.extend(echars[(idx + 1) * 2:ncols * 2])
421 else:
424 else:
422 for i in xrange(count):
425 for i in xrange(count):
423 shift_interline.extend(['\\', ' '])
426 shift_interline.extend(['\\', ' '])
424
427
425 # draw edges from the current node to its parents
428 # draw edges from the current node to its parents
426 _drawedges(echars, edges, nodeline, shift_interline)
429 _drawedges(echars, edges, nodeline, shift_interline)
427
430
428 # lines is the list of all graph lines to print
431 # lines is the list of all graph lines to print
429 lines = [nodeline]
432 lines = [nodeline]
430 if add_padding_line:
433 if add_padding_line:
431 lines.append(_getpaddingline(echars, idx, ncols, edges))
434 lines.append(_getpaddingline(echars, idx, ncols, edges))
432
435
433 # If 'graphshorten' config, only draw shift_interline
436 # If 'graphshorten' config, only draw shift_interline
434 # when there is any non vertical flow in graph.
437 # when there is any non vertical flow in graph.
435 if state['graphshorten']:
438 if state['graphshorten']:
436 if any(c in '\/' for c in shift_interline if c):
439 if any(c in '\/' for c in shift_interline if c):
437 lines.append(shift_interline)
440 lines.append(shift_interline)
438 # Else, no 'graphshorten' config so draw shift_interline.
441 # Else, no 'graphshorten' config so draw shift_interline.
439 else:
442 else:
440 lines.append(shift_interline)
443 lines.append(shift_interline)
441
444
442 # make sure that there are as many graph lines as there are
445 # make sure that there are as many graph lines as there are
443 # log strings
446 # log strings
444 extra_interline = echars[:(ncols + coldiff) * 2]
447 extra_interline = echars[:(ncols + coldiff) * 2]
445 if len(lines) < len(text):
448 if len(lines) < len(text):
446 while len(lines) < len(text):
449 while len(lines) < len(text):
447 lines.append(extra_interline[:])
450 lines.append(extra_interline[:])
448
451
449 _drawendinglines(lines, extra_interline, edgemap, seen)
452 _drawendinglines(lines, extra_interline, edgemap, seen)
450
453
451 while len(text) < len(lines):
454 while len(text) < len(lines):
452 text.append("")
455 text.append("")
453
456
454 if any(len(char) > 1 for char in edgemap.values()):
457 if any(len(char) > 1 for char in edgemap.values()):
455 # limit drawing an edge to the first or last N lines of the current
458 # limit drawing an edge to the first or last N lines of the current
456 # section the rest of the edge is drawn like a parent line.
459 # section the rest of the edge is drawn like a parent line.
457 parent = state['styles'][PARENT][-1]
460 parent = state['styles'][PARENT][-1]
458 def _drawgp(char, i):
461 def _drawgp(char, i):
459 # should a grandparent character be drawn for this line?
462 # should a grandparent character be drawn for this line?
460 if len(char) < 2:
463 if len(char) < 2:
461 return True
464 return True
462 num = int(char[:-1])
465 num = int(char[:-1])
463 # either skip first num lines or take last num lines, based on sign
466 # either skip first num lines or take last num lines, based on sign
464 return -num <= i if num < 0 else (len(lines) - i) <= num
467 return -num <= i if num < 0 else (len(lines) - i) <= num
465 for i, line in enumerate(lines):
468 for i, line in enumerate(lines):
466 line[:] = [c[-1] if _drawgp(c, i) else parent for c in line]
469 line[:] = [c[-1] if _drawgp(c, i) else parent for c in line]
467 edgemap.update(
470 edgemap.update(
468 (e, (c if len(c) < 2 else parent)) for e, c in edgemap.items())
471 (e, (c if len(c) < 2 else parent)) for e, c in edgemap.items())
469
472
470 # print lines
473 # print lines
471 indentation_level = max(ncols, ncols + coldiff)
474 indentation_level = max(ncols, ncols + coldiff)
472 for (line, logstr) in zip(lines, text):
475 for (line, logstr) in zip(lines, text):
473 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
476 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
474 ui.write(ln.rstrip() + '\n')
477 ui.write(ln.rstrip() + '\n')
475
478
476 # ... and start over
479 # ... and start over
477 state['lastcoldiff'] = coldiff
480 state['lastcoldiff'] = coldiff
478 state['lastindex'] = idx
481 state['lastindex'] = idx
@@ -1,790 +1,797 b''
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
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
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11 from .node import (
11 from .node import (
12 hex,
12 hex,
13 nullid,
13 nullid,
14 short,
14 short,
15 )
15 )
16
16
17 from . import (
17 from . import (
18 encoding,
18 encoding,
19 error,
19 error,
20 hbisect,
20 hbisect,
21 obsutil,
21 obsutil,
22 patch,
22 patch,
23 pycompat,
23 pycompat,
24 registrar,
24 registrar,
25 scmutil,
25 scmutil,
26 util,
26 util,
27 )
27 )
28
28
29 class _hybrid(object):
29 class _hybrid(object):
30 """Wrapper for list or dict to support legacy template
30 """Wrapper for list or dict to support legacy template
31
31
32 This class allows us to handle both:
32 This class allows us to handle both:
33 - "{files}" (legacy command-line-specific list hack) and
33 - "{files}" (legacy command-line-specific list hack) and
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
34 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
35 and to access raw values:
35 and to access raw values:
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
36 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
37 - "{get(extras, key)}"
37 - "{get(extras, key)}"
38 - "{files|json}"
38 - "{files|json}"
39 """
39 """
40
40
41 def __init__(self, gen, values, makemap, joinfmt):
41 def __init__(self, gen, values, makemap, joinfmt):
42 if gen is not None:
42 if gen is not None:
43 self.gen = gen
43 self.gen = gen
44 self._values = values
44 self._values = values
45 self._makemap = makemap
45 self._makemap = makemap
46 self.joinfmt = joinfmt
46 self.joinfmt = joinfmt
47 @util.propertycache
47 @util.propertycache
48 def gen(self):
48 def gen(self):
49 return self._defaultgen()
49 return self._defaultgen()
50 def _defaultgen(self):
50 def _defaultgen(self):
51 """Generator to stringify this as {join(self, ' ')}"""
51 """Generator to stringify this as {join(self, ' ')}"""
52 for i, d in enumerate(self.itermaps()):
52 for i, d in enumerate(self.itermaps()):
53 if i > 0:
53 if i > 0:
54 yield ' '
54 yield ' '
55 yield self.joinfmt(d)
55 yield self.joinfmt(d)
56 def itermaps(self):
56 def itermaps(self):
57 makemap = self._makemap
57 makemap = self._makemap
58 for x in self._values:
58 for x in self._values:
59 yield makemap(x)
59 yield makemap(x)
60 def __contains__(self, x):
60 def __contains__(self, x):
61 return x in self._values
61 return x in self._values
62 def __getitem__(self, key):
62 def __getitem__(self, key):
63 return self._values[key]
63 return self._values[key]
64 def __len__(self):
64 def __len__(self):
65 return len(self._values)
65 return len(self._values)
66 def __iter__(self):
66 def __iter__(self):
67 return iter(self._values)
67 return iter(self._values)
68 def __getattr__(self, name):
68 def __getattr__(self, name):
69 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
69 if name not in ('get', 'items', 'iteritems', 'iterkeys', 'itervalues',
70 'keys', 'values'):
70 'keys', 'values'):
71 raise AttributeError(name)
71 raise AttributeError(name)
72 return getattr(self._values, name)
72 return getattr(self._values, name)
73
73
74 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
74 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
75 """Wrap data to support both dict-like and string-like operations"""
75 """Wrap data to support both dict-like and string-like operations"""
76 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
76 return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
77 lambda d: fmt % (d[key], d[value]))
77 lambda d: fmt % (d[key], d[value]))
78
78
79 def hybridlist(data, name, fmt='%s', gen=None):
79 def hybridlist(data, name, fmt='%s', gen=None):
80 """Wrap data to support both list-like and string-like operations"""
80 """Wrap data to support both list-like and string-like operations"""
81 return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
81 return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
82
82
83 def unwraphybrid(thing):
83 def unwraphybrid(thing):
84 """Return an object which can be stringified possibly by using a legacy
84 """Return an object which can be stringified possibly by using a legacy
85 template"""
85 template"""
86 if not util.safehasattr(thing, 'gen'):
86 if not util.safehasattr(thing, 'gen'):
87 return thing
87 return thing
88 return thing.gen
88 return thing.gen
89
89
90 def showdict(name, data, mapping, plural=None, key='key', value='value',
90 def showdict(name, data, mapping, plural=None, key='key', value='value',
91 fmt='%s=%s', separator=' '):
91 fmt='%s=%s', separator=' '):
92 c = [{key: k, value: v} for k, v in data.iteritems()]
92 c = [{key: k, value: v} for k, v in data.iteritems()]
93 f = _showlist(name, c, mapping, plural, separator)
93 f = _showlist(name, c, mapping, plural, separator)
94 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
94 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
95
95
96 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
96 def showlist(name, values, mapping, plural=None, element=None, separator=' '):
97 if not element:
97 if not element:
98 element = name
98 element = name
99 f = _showlist(name, values, mapping, plural, separator)
99 f = _showlist(name, values, mapping, plural, separator)
100 return hybridlist(values, name=element, gen=f)
100 return hybridlist(values, name=element, gen=f)
101
101
102 def _showlist(name, values, mapping, plural=None, separator=' '):
102 def _showlist(name, values, mapping, plural=None, separator=' '):
103 '''expand set of values.
103 '''expand set of values.
104 name is name of key in template map.
104 name is name of key in template map.
105 values is list of strings or dicts.
105 values is list of strings or dicts.
106 plural is plural of name, if not simply name + 's'.
106 plural is plural of name, if not simply name + 's'.
107 separator is used to join values as a string
107 separator is used to join values as a string
108
108
109 expansion works like this, given name 'foo'.
109 expansion works like this, given name 'foo'.
110
110
111 if values is empty, expand 'no_foos'.
111 if values is empty, expand 'no_foos'.
112
112
113 if 'foo' not in template map, return values as a string,
113 if 'foo' not in template map, return values as a string,
114 joined by 'separator'.
114 joined by 'separator'.
115
115
116 expand 'start_foos'.
116 expand 'start_foos'.
117
117
118 for each value, expand 'foo'. if 'last_foo' in template
118 for each value, expand 'foo'. if 'last_foo' in template
119 map, expand it instead of 'foo' for last key.
119 map, expand it instead of 'foo' for last key.
120
120
121 expand 'end_foos'.
121 expand 'end_foos'.
122 '''
122 '''
123 templ = mapping['templ']
123 templ = mapping['templ']
124 strmapping = pycompat.strkwargs(mapping)
124 strmapping = pycompat.strkwargs(mapping)
125 if not plural:
125 if not plural:
126 plural = name + 's'
126 plural = name + 's'
127 if not values:
127 if not values:
128 noname = 'no_' + plural
128 noname = 'no_' + plural
129 if noname in templ:
129 if noname in templ:
130 yield templ(noname, **strmapping)
130 yield templ(noname, **strmapping)
131 return
131 return
132 if name not in templ:
132 if name not in templ:
133 if isinstance(values[0], bytes):
133 if isinstance(values[0], bytes):
134 yield separator.join(values)
134 yield separator.join(values)
135 else:
135 else:
136 for v in values:
136 for v in values:
137 yield dict(v, **strmapping)
137 yield dict(v, **strmapping)
138 return
138 return
139 startname = 'start_' + plural
139 startname = 'start_' + plural
140 if startname in templ:
140 if startname in templ:
141 yield templ(startname, **strmapping)
141 yield templ(startname, **strmapping)
142 vmapping = mapping.copy()
142 vmapping = mapping.copy()
143 def one(v, tag=name):
143 def one(v, tag=name):
144 try:
144 try:
145 vmapping.update(v)
145 vmapping.update(v)
146 except (AttributeError, ValueError):
146 except (AttributeError, ValueError):
147 try:
147 try:
148 for a, b in v:
148 for a, b in v:
149 vmapping[a] = b
149 vmapping[a] = b
150 except ValueError:
150 except ValueError:
151 vmapping[name] = v
151 vmapping[name] = v
152 return templ(tag, **pycompat.strkwargs(vmapping))
152 return templ(tag, **pycompat.strkwargs(vmapping))
153 lastname = 'last_' + name
153 lastname = 'last_' + name
154 if lastname in templ:
154 if lastname in templ:
155 last = values.pop()
155 last = values.pop()
156 else:
156 else:
157 last = None
157 last = None
158 for v in values:
158 for v in values:
159 yield one(v)
159 yield one(v)
160 if last is not None:
160 if last is not None:
161 yield one(last, tag=lastname)
161 yield one(last, tag=lastname)
162 endname = 'end_' + plural
162 endname = 'end_' + plural
163 if endname in templ:
163 if endname in templ:
164 yield templ(endname, **strmapping)
164 yield templ(endname, **strmapping)
165
165
166 def _formatrevnode(ctx):
166 def _formatrevnode(ctx):
167 """Format changeset as '{rev}:{node|formatnode}', which is the default
167 """Format changeset as '{rev}:{node|formatnode}', which is the default
168 template provided by cmdutil.changeset_templater"""
168 template provided by cmdutil.changeset_templater"""
169 repo = ctx.repo()
169 repo = ctx.repo()
170 if repo.ui.debugflag:
170 if repo.ui.debugflag:
171 hexfunc = hex
171 hexfunc = hex
172 else:
172 else:
173 hexfunc = short
173 hexfunc = short
174 return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
174 return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
175
175
176 def getfiles(repo, ctx, revcache):
176 def getfiles(repo, ctx, revcache):
177 if 'files' not in revcache:
177 if 'files' not in revcache:
178 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
178 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
179 return revcache['files']
179 return revcache['files']
180
180
181 def getlatesttags(repo, ctx, cache, pattern=None):
181 def getlatesttags(repo, ctx, cache, pattern=None):
182 '''return date, distance and name for the latest tag of rev'''
182 '''return date, distance and name for the latest tag of rev'''
183
183
184 cachename = 'latesttags'
184 cachename = 'latesttags'
185 if pattern is not None:
185 if pattern is not None:
186 cachename += '-' + pattern
186 cachename += '-' + pattern
187 match = util.stringmatcher(pattern)[2]
187 match = util.stringmatcher(pattern)[2]
188 else:
188 else:
189 match = util.always
189 match = util.always
190
190
191 if cachename not in cache:
191 if cachename not in cache:
192 # Cache mapping from rev to a tuple with tag date, tag
192 # Cache mapping from rev to a tuple with tag date, tag
193 # distance and tag name
193 # distance and tag name
194 cache[cachename] = {-1: (0, 0, ['null'])}
194 cache[cachename] = {-1: (0, 0, ['null'])}
195 latesttags = cache[cachename]
195 latesttags = cache[cachename]
196
196
197 rev = ctx.rev()
197 rev = ctx.rev()
198 todo = [rev]
198 todo = [rev]
199 while todo:
199 while todo:
200 rev = todo.pop()
200 rev = todo.pop()
201 if rev in latesttags:
201 if rev in latesttags:
202 continue
202 continue
203 ctx = repo[rev]
203 ctx = repo[rev]
204 tags = [t for t in ctx.tags()
204 tags = [t for t in ctx.tags()
205 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
205 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
206 and match(t))]
206 and match(t))]
207 if tags:
207 if tags:
208 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
208 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
209 continue
209 continue
210 try:
210 try:
211 # The tuples are laid out so the right one can be found by
211 # The tuples are laid out so the right one can be found by
212 # comparison.
212 # comparison.
213 pdate, pdist, ptag = max(
213 pdate, pdist, ptag = max(
214 latesttags[p.rev()] for p in ctx.parents())
214 latesttags[p.rev()] for p in ctx.parents())
215 except KeyError:
215 except KeyError:
216 # Cache miss - recurse
216 # Cache miss - recurse
217 todo.append(rev)
217 todo.append(rev)
218 todo.extend(p.rev() for p in ctx.parents())
218 todo.extend(p.rev() for p in ctx.parents())
219 continue
219 continue
220 latesttags[rev] = pdate, pdist + 1, ptag
220 latesttags[rev] = pdate, pdist + 1, ptag
221 return latesttags[rev]
221 return latesttags[rev]
222
222
223 def getrenamedfn(repo, endrev=None):
223 def getrenamedfn(repo, endrev=None):
224 rcache = {}
224 rcache = {}
225 if endrev is None:
225 if endrev is None:
226 endrev = len(repo)
226 endrev = len(repo)
227
227
228 def getrenamed(fn, rev):
228 def getrenamed(fn, rev):
229 '''looks up all renames for a file (up to endrev) the first
229 '''looks up all renames for a file (up to endrev) the first
230 time the file is given. It indexes on the changerev and only
230 time the file is given. It indexes on the changerev and only
231 parses the manifest if linkrev != changerev.
231 parses the manifest if linkrev != changerev.
232 Returns rename info for fn at changerev rev.'''
232 Returns rename info for fn at changerev rev.'''
233 if fn not in rcache:
233 if fn not in rcache:
234 rcache[fn] = {}
234 rcache[fn] = {}
235 fl = repo.file(fn)
235 fl = repo.file(fn)
236 for i in fl:
236 for i in fl:
237 lr = fl.linkrev(i)
237 lr = fl.linkrev(i)
238 renamed = fl.renamed(fl.node(i))
238 renamed = fl.renamed(fl.node(i))
239 rcache[fn][lr] = renamed
239 rcache[fn][lr] = renamed
240 if lr >= endrev:
240 if lr >= endrev:
241 break
241 break
242 if rev in rcache[fn]:
242 if rev in rcache[fn]:
243 return rcache[fn][rev]
243 return rcache[fn][rev]
244
244
245 # If linkrev != rev (i.e. rev not found in rcache) fallback to
245 # If linkrev != rev (i.e. rev not found in rcache) fallback to
246 # filectx logic.
246 # filectx logic.
247 try:
247 try:
248 return repo[rev][fn].renamed()
248 return repo[rev][fn].renamed()
249 except error.LookupError:
249 except error.LookupError:
250 return None
250 return None
251
251
252 return getrenamed
252 return getrenamed
253
253
254 # default templates internally used for rendering of lists
254 # default templates internally used for rendering of lists
255 defaulttempl = {
255 defaulttempl = {
256 'parent': '{rev}:{node|formatnode} ',
256 'parent': '{rev}:{node|formatnode} ',
257 'manifest': '{rev}:{node|formatnode}',
257 'manifest': '{rev}:{node|formatnode}',
258 'file_copy': '{name} ({source})',
258 'file_copy': '{name} ({source})',
259 'envvar': '{key}={value}',
259 'envvar': '{key}={value}',
260 'extra': '{key}={value|stringescape}'
260 'extra': '{key}={value|stringescape}'
261 }
261 }
262 # filecopy is preserved for compatibility reasons
262 # filecopy is preserved for compatibility reasons
263 defaulttempl['filecopy'] = defaulttempl['file_copy']
263 defaulttempl['filecopy'] = defaulttempl['file_copy']
264
264
265 # keywords are callables like:
265 # keywords are callables like:
266 # fn(repo, ctx, templ, cache, revcache, **args)
266 # fn(repo, ctx, templ, cache, revcache, **args)
267 # with:
267 # with:
268 # repo - current repository instance
268 # repo - current repository instance
269 # ctx - the changectx being displayed
269 # ctx - the changectx being displayed
270 # templ - the templater instance
270 # templ - the templater instance
271 # cache - a cache dictionary for the whole templater run
271 # cache - a cache dictionary for the whole templater run
272 # revcache - a cache dictionary for the current revision
272 # revcache - a cache dictionary for the current revision
273 keywords = {}
273 keywords = {}
274
274
275 templatekeyword = registrar.templatekeyword(keywords)
275 templatekeyword = registrar.templatekeyword(keywords)
276
276
277 @templatekeyword('author')
277 @templatekeyword('author')
278 def showauthor(repo, ctx, templ, **args):
278 def showauthor(repo, ctx, templ, **args):
279 """String. The unmodified author of the changeset."""
279 """String. The unmodified author of the changeset."""
280 return ctx.user()
280 return ctx.user()
281
281
282 @templatekeyword('bisect')
282 @templatekeyword('bisect')
283 def showbisect(repo, ctx, templ, **args):
283 def showbisect(repo, ctx, templ, **args):
284 """String. The changeset bisection status."""
284 """String. The changeset bisection status."""
285 return hbisect.label(repo, ctx.node())
285 return hbisect.label(repo, ctx.node())
286
286
287 @templatekeyword('branch')
287 @templatekeyword('branch')
288 def showbranch(**args):
288 def showbranch(**args):
289 """String. The name of the branch on which the changeset was
289 """String. The name of the branch on which the changeset was
290 committed.
290 committed.
291 """
291 """
292 return args[r'ctx'].branch()
292 return args[r'ctx'].branch()
293
293
294 @templatekeyword('branches')
294 @templatekeyword('branches')
295 def showbranches(**args):
295 def showbranches(**args):
296 """List of strings. The name of the branch on which the
296 """List of strings. The name of the branch on which the
297 changeset was committed. Will be empty if the branch name was
297 changeset was committed. Will be empty if the branch name was
298 default. (DEPRECATED)
298 default. (DEPRECATED)
299 """
299 """
300 args = pycompat.byteskwargs(args)
300 args = pycompat.byteskwargs(args)
301 branch = args['ctx'].branch()
301 branch = args['ctx'].branch()
302 if branch != 'default':
302 if branch != 'default':
303 return showlist('branch', [branch], args, plural='branches')
303 return showlist('branch', [branch], args, plural='branches')
304 return showlist('branch', [], args, plural='branches')
304 return showlist('branch', [], args, plural='branches')
305
305
306 @templatekeyword('bookmarks')
306 @templatekeyword('bookmarks')
307 def showbookmarks(**args):
307 def showbookmarks(**args):
308 """List of strings. Any bookmarks associated with the
308 """List of strings. Any bookmarks associated with the
309 changeset. Also sets 'active', the name of the active bookmark.
309 changeset. Also sets 'active', the name of the active bookmark.
310 """
310 """
311 args = pycompat.byteskwargs(args)
311 args = pycompat.byteskwargs(args)
312 repo = args['ctx']._repo
312 repo = args['ctx']._repo
313 bookmarks = args['ctx'].bookmarks()
313 bookmarks = args['ctx'].bookmarks()
314 active = repo._activebookmark
314 active = repo._activebookmark
315 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
315 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
316 f = _showlist('bookmark', bookmarks, args)
316 f = _showlist('bookmark', bookmarks, args)
317 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
317 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
318
318
319 @templatekeyword('children')
319 @templatekeyword('children')
320 def showchildren(**args):
320 def showchildren(**args):
321 """List of strings. The children of the changeset."""
321 """List of strings. The children of the changeset."""
322 args = pycompat.byteskwargs(args)
322 args = pycompat.byteskwargs(args)
323 ctx = args['ctx']
323 ctx = args['ctx']
324 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
324 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
325 return showlist('children', childrevs, args, element='child')
325 return showlist('children', childrevs, args, element='child')
326
326
327 # Deprecated, but kept alive for help generation a purpose.
327 # Deprecated, but kept alive for help generation a purpose.
328 @templatekeyword('currentbookmark')
328 @templatekeyword('currentbookmark')
329 def showcurrentbookmark(**args):
329 def showcurrentbookmark(**args):
330 """String. The active bookmark, if it is
330 """String. The active bookmark, if it is
331 associated with the changeset (DEPRECATED)"""
331 associated with the changeset (DEPRECATED)"""
332 return showactivebookmark(**args)
332 return showactivebookmark(**args)
333
333
334 @templatekeyword('activebookmark')
334 @templatekeyword('activebookmark')
335 def showactivebookmark(**args):
335 def showactivebookmark(**args):
336 """String. The active bookmark, if it is
336 """String. The active bookmark, if it is
337 associated with the changeset"""
337 associated with the changeset"""
338 active = args[r'repo']._activebookmark
338 active = args[r'repo']._activebookmark
339 if active and active in args[r'ctx'].bookmarks():
339 if active and active in args[r'ctx'].bookmarks():
340 return active
340 return active
341 return ''
341 return ''
342
342
343 @templatekeyword('date')
343 @templatekeyword('date')
344 def showdate(repo, ctx, templ, **args):
344 def showdate(repo, ctx, templ, **args):
345 """Date information. The date when the changeset was committed."""
345 """Date information. The date when the changeset was committed."""
346 return ctx.date()
346 return ctx.date()
347
347
348 @templatekeyword('desc')
348 @templatekeyword('desc')
349 def showdescription(repo, ctx, templ, **args):
349 def showdescription(repo, ctx, templ, **args):
350 """String. The text of the changeset description."""
350 """String. The text of the changeset description."""
351 s = ctx.description()
351 s = ctx.description()
352 if isinstance(s, encoding.localstr):
352 if isinstance(s, encoding.localstr):
353 # try hard to preserve utf-8 bytes
353 # try hard to preserve utf-8 bytes
354 return encoding.tolocal(encoding.fromlocal(s).strip())
354 return encoding.tolocal(encoding.fromlocal(s).strip())
355 else:
355 else:
356 return s.strip()
356 return s.strip()
357
357
358 @templatekeyword('diffstat')
358 @templatekeyword('diffstat')
359 def showdiffstat(repo, ctx, templ, **args):
359 def showdiffstat(repo, ctx, templ, **args):
360 """String. Statistics of changes with the following format:
360 """String. Statistics of changes with the following format:
361 "modified files: +added/-removed lines"
361 "modified files: +added/-removed lines"
362 """
362 """
363 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
363 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
364 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
364 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
365 return '%s: +%s/-%s' % (len(stats), adds, removes)
365 return '%s: +%s/-%s' % (len(stats), adds, removes)
366
366
367 @templatekeyword('envvars')
367 @templatekeyword('envvars')
368 def showenvvars(repo, **args):
368 def showenvvars(repo, **args):
369 """A dictionary of environment variables. (EXPERIMENTAL)"""
369 """A dictionary of environment variables. (EXPERIMENTAL)"""
370 args = pycompat.byteskwargs(args)
370 args = pycompat.byteskwargs(args)
371 env = repo.ui.exportableenviron()
371 env = repo.ui.exportableenviron()
372 env = util.sortdict((k, env[k]) for k in sorted(env))
372 env = util.sortdict((k, env[k]) for k in sorted(env))
373 return showdict('envvar', env, args, plural='envvars')
373 return showdict('envvar', env, args, plural='envvars')
374
374
375 @templatekeyword('extras')
375 @templatekeyword('extras')
376 def showextras(**args):
376 def showextras(**args):
377 """List of dicts with key, value entries of the 'extras'
377 """List of dicts with key, value entries of the 'extras'
378 field of this changeset."""
378 field of this changeset."""
379 args = pycompat.byteskwargs(args)
379 args = pycompat.byteskwargs(args)
380 extras = args['ctx'].extra()
380 extras = args['ctx'].extra()
381 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
381 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
382 makemap = lambda k: {'key': k, 'value': extras[k]}
382 makemap = lambda k: {'key': k, 'value': extras[k]}
383 c = [makemap(k) for k in extras]
383 c = [makemap(k) for k in extras]
384 f = _showlist('extra', c, args, plural='extras')
384 f = _showlist('extra', c, args, plural='extras')
385 return _hybrid(f, extras, makemap,
385 return _hybrid(f, extras, makemap,
386 lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
386 lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
387
387
388 @templatekeyword('file_adds')
388 @templatekeyword('file_adds')
389 def showfileadds(**args):
389 def showfileadds(**args):
390 """List of strings. Files added by this changeset."""
390 """List of strings. Files added by this changeset."""
391 args = pycompat.byteskwargs(args)
391 args = pycompat.byteskwargs(args)
392 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
392 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
393 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
393 return showlist('file_add', getfiles(repo, ctx, revcache)[1], args,
394 element='file')
394 element='file')
395
395
396 @templatekeyword('file_copies')
396 @templatekeyword('file_copies')
397 def showfilecopies(**args):
397 def showfilecopies(**args):
398 """List of strings. Files copied in this changeset with
398 """List of strings. Files copied in this changeset with
399 their sources.
399 their sources.
400 """
400 """
401 args = pycompat.byteskwargs(args)
401 args = pycompat.byteskwargs(args)
402 cache, ctx = args['cache'], args['ctx']
402 cache, ctx = args['cache'], args['ctx']
403 copies = args['revcache'].get('copies')
403 copies = args['revcache'].get('copies')
404 if copies is None:
404 if copies is None:
405 if 'getrenamed' not in cache:
405 if 'getrenamed' not in cache:
406 cache['getrenamed'] = getrenamedfn(args['repo'])
406 cache['getrenamed'] = getrenamedfn(args['repo'])
407 copies = []
407 copies = []
408 getrenamed = cache['getrenamed']
408 getrenamed = cache['getrenamed']
409 for fn in ctx.files():
409 for fn in ctx.files():
410 rename = getrenamed(fn, ctx.rev())
410 rename = getrenamed(fn, ctx.rev())
411 if rename:
411 if rename:
412 copies.append((fn, rename[0]))
412 copies.append((fn, rename[0]))
413
413
414 copies = util.sortdict(copies)
414 copies = util.sortdict(copies)
415 return showdict('file_copy', copies, args, plural='file_copies',
415 return showdict('file_copy', copies, args, plural='file_copies',
416 key='name', value='source', fmt='%s (%s)')
416 key='name', value='source', fmt='%s (%s)')
417
417
418 # showfilecopiesswitch() displays file copies only if copy records are
418 # showfilecopiesswitch() displays file copies only if copy records are
419 # provided before calling the templater, usually with a --copies
419 # provided before calling the templater, usually with a --copies
420 # command line switch.
420 # command line switch.
421 @templatekeyword('file_copies_switch')
421 @templatekeyword('file_copies_switch')
422 def showfilecopiesswitch(**args):
422 def showfilecopiesswitch(**args):
423 """List of strings. Like "file_copies" but displayed
423 """List of strings. Like "file_copies" but displayed
424 only if the --copied switch is set.
424 only if the --copied switch is set.
425 """
425 """
426 args = pycompat.byteskwargs(args)
426 args = pycompat.byteskwargs(args)
427 copies = args['revcache'].get('copies') or []
427 copies = args['revcache'].get('copies') or []
428 copies = util.sortdict(copies)
428 copies = util.sortdict(copies)
429 return showdict('file_copy', copies, args, plural='file_copies',
429 return showdict('file_copy', copies, args, plural='file_copies',
430 key='name', value='source', fmt='%s (%s)')
430 key='name', value='source', fmt='%s (%s)')
431
431
432 @templatekeyword('file_dels')
432 @templatekeyword('file_dels')
433 def showfiledels(**args):
433 def showfiledels(**args):
434 """List of strings. Files removed by this changeset."""
434 """List of strings. Files removed by this changeset."""
435 args = pycompat.byteskwargs(args)
435 args = pycompat.byteskwargs(args)
436 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
436 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
437 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
437 return showlist('file_del', getfiles(repo, ctx, revcache)[2], args,
438 element='file')
438 element='file')
439
439
440 @templatekeyword('file_mods')
440 @templatekeyword('file_mods')
441 def showfilemods(**args):
441 def showfilemods(**args):
442 """List of strings. Files modified by this changeset."""
442 """List of strings. Files modified by this changeset."""
443 args = pycompat.byteskwargs(args)
443 args = pycompat.byteskwargs(args)
444 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
444 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
445 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
445 return showlist('file_mod', getfiles(repo, ctx, revcache)[0], args,
446 element='file')
446 element='file')
447
447
448 @templatekeyword('files')
448 @templatekeyword('files')
449 def showfiles(**args):
449 def showfiles(**args):
450 """List of strings. All files modified, added, or removed by this
450 """List of strings. All files modified, added, or removed by this
451 changeset.
451 changeset.
452 """
452 """
453 args = pycompat.byteskwargs(args)
453 args = pycompat.byteskwargs(args)
454 return showlist('file', args['ctx'].files(), args)
454 return showlist('file', args['ctx'].files(), args)
455
455
456 @templatekeyword('graphnode')
456 @templatekeyword('graphnode')
457 def showgraphnode(repo, ctx, **args):
457 def showgraphnode(repo, ctx, **args):
458 """String. The character representing the changeset node in
458 """String. The character representing the changeset node in
459 an ASCII revision graph"""
459 an ASCII revision graph"""
460 wpnodes = repo.dirstate.parents()
460 wpnodes = repo.dirstate.parents()
461 if wpnodes[1] == nullid:
461 if wpnodes[1] == nullid:
462 wpnodes = wpnodes[:1]
462 wpnodes = wpnodes[:1]
463 if ctx.node() in wpnodes:
463 if ctx.node() in wpnodes:
464 return '@'
464 return '@'
465 elif ctx.obsolete():
465 elif ctx.obsolete():
466 return 'x'
466 return 'x'
467 elif ctx.closesbranch():
467 elif ctx.closesbranch():
468 return '_'
468 return '_'
469 else:
469 else:
470 return 'o'
470 return 'o'
471
471
472 @templatekeyword('graphwidth')
473 def showgraphwidth(repo, ctx, templ, **args):
474 """Integer. The width of the graph drawn by 'log --graph' or zero."""
475 # The value args['graphwidth'] will be this function, so we use an internal
476 # name to pass the value through props into this function.
477 return args.get('_graphwidth', 0)
478
472 @templatekeyword('index')
479 @templatekeyword('index')
473 def showindex(**args):
480 def showindex(**args):
474 """Integer. The current iteration of the loop. (0 indexed)"""
481 """Integer. The current iteration of the loop. (0 indexed)"""
475 # just hosts documentation; should be overridden by template mapping
482 # just hosts documentation; should be overridden by template mapping
476 raise error.Abort(_("can't use index in this context"))
483 raise error.Abort(_("can't use index in this context"))
477
484
478 @templatekeyword('latesttag')
485 @templatekeyword('latesttag')
479 def showlatesttag(**args):
486 def showlatesttag(**args):
480 """List of strings. The global tags on the most recent globally
487 """List of strings. The global tags on the most recent globally
481 tagged ancestor of this changeset. If no such tags exist, the list
488 tagged ancestor of this changeset. If no such tags exist, the list
482 consists of the single string "null".
489 consists of the single string "null".
483 """
490 """
484 return showlatesttags(None, **args)
491 return showlatesttags(None, **args)
485
492
486 def showlatesttags(pattern, **args):
493 def showlatesttags(pattern, **args):
487 """helper method for the latesttag keyword and function"""
494 """helper method for the latesttag keyword and function"""
488 args = pycompat.byteskwargs(args)
495 args = pycompat.byteskwargs(args)
489 repo, ctx = args['repo'], args['ctx']
496 repo, ctx = args['repo'], args['ctx']
490 cache = args['cache']
497 cache = args['cache']
491 latesttags = getlatesttags(repo, ctx, cache, pattern)
498 latesttags = getlatesttags(repo, ctx, cache, pattern)
492
499
493 # latesttag[0] is an implementation detail for sorting csets on different
500 # latesttag[0] is an implementation detail for sorting csets on different
494 # branches in a stable manner- it is the date the tagged cset was created,
501 # branches in a stable manner- it is the date the tagged cset was created,
495 # not the date the tag was created. Therefore it isn't made visible here.
502 # not the date the tag was created. Therefore it isn't made visible here.
496 makemap = lambda v: {
503 makemap = lambda v: {
497 'changes': _showchangessincetag,
504 'changes': _showchangessincetag,
498 'distance': latesttags[1],
505 'distance': latesttags[1],
499 'latesttag': v, # BC with {latesttag % '{latesttag}'}
506 'latesttag': v, # BC with {latesttag % '{latesttag}'}
500 'tag': v
507 'tag': v
501 }
508 }
502
509
503 tags = latesttags[2]
510 tags = latesttags[2]
504 f = _showlist('latesttag', tags, args, separator=':')
511 f = _showlist('latesttag', tags, args, separator=':')
505 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
512 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
506
513
507 @templatekeyword('latesttagdistance')
514 @templatekeyword('latesttagdistance')
508 def showlatesttagdistance(repo, ctx, templ, cache, **args):
515 def showlatesttagdistance(repo, ctx, templ, cache, **args):
509 """Integer. Longest path to the latest tag."""
516 """Integer. Longest path to the latest tag."""
510 return getlatesttags(repo, ctx, cache)[1]
517 return getlatesttags(repo, ctx, cache)[1]
511
518
512 @templatekeyword('changessincelatesttag')
519 @templatekeyword('changessincelatesttag')
513 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
520 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
514 """Integer. All ancestors not in the latest tag."""
521 """Integer. All ancestors not in the latest tag."""
515 latesttag = getlatesttags(repo, ctx, cache)[2][0]
522 latesttag = getlatesttags(repo, ctx, cache)[2][0]
516
523
517 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
524 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
518
525
519 def _showchangessincetag(repo, ctx, **args):
526 def _showchangessincetag(repo, ctx, **args):
520 offset = 0
527 offset = 0
521 revs = [ctx.rev()]
528 revs = [ctx.rev()]
522 tag = args[r'tag']
529 tag = args[r'tag']
523
530
524 # The only() revset doesn't currently support wdir()
531 # The only() revset doesn't currently support wdir()
525 if ctx.rev() is None:
532 if ctx.rev() is None:
526 offset = 1
533 offset = 1
527 revs = [p.rev() for p in ctx.parents()]
534 revs = [p.rev() for p in ctx.parents()]
528
535
529 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
536 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
530
537
531 @templatekeyword('manifest')
538 @templatekeyword('manifest')
532 def showmanifest(**args):
539 def showmanifest(**args):
533 repo, ctx, templ = args[r'repo'], args[r'ctx'], args[r'templ']
540 repo, ctx, templ = args[r'repo'], args[r'ctx'], args[r'templ']
534 mnode = ctx.manifestnode()
541 mnode = ctx.manifestnode()
535 if mnode is None:
542 if mnode is None:
536 # just avoid crash, we might want to use the 'ff...' hash in future
543 # just avoid crash, we might want to use the 'ff...' hash in future
537 return
544 return
538 args = args.copy()
545 args = args.copy()
539 args.update({r'rev': repo.manifestlog._revlog.rev(mnode),
546 args.update({r'rev': repo.manifestlog._revlog.rev(mnode),
540 r'node': hex(mnode)})
547 r'node': hex(mnode)})
541 return templ('manifest', **args)
548 return templ('manifest', **args)
542
549
543 def shownames(namespace, **args):
550 def shownames(namespace, **args):
544 """helper method to generate a template keyword for a namespace"""
551 """helper method to generate a template keyword for a namespace"""
545 args = pycompat.byteskwargs(args)
552 args = pycompat.byteskwargs(args)
546 ctx = args['ctx']
553 ctx = args['ctx']
547 repo = ctx.repo()
554 repo = ctx.repo()
548 ns = repo.names[namespace]
555 ns = repo.names[namespace]
549 names = ns.names(repo, ctx.node())
556 names = ns.names(repo, ctx.node())
550 return showlist(ns.templatename, names, args, plural=namespace)
557 return showlist(ns.templatename, names, args, plural=namespace)
551
558
552 @templatekeyword('namespaces')
559 @templatekeyword('namespaces')
553 def shownamespaces(**args):
560 def shownamespaces(**args):
554 """Dict of lists. Names attached to this changeset per
561 """Dict of lists. Names attached to this changeset per
555 namespace."""
562 namespace."""
556 args = pycompat.byteskwargs(args)
563 args = pycompat.byteskwargs(args)
557 ctx = args['ctx']
564 ctx = args['ctx']
558 repo = ctx.repo()
565 repo = ctx.repo()
559
566
560 namespaces = util.sortdict()
567 namespaces = util.sortdict()
561 colornames = {}
568 colornames = {}
562 builtins = {}
569 builtins = {}
563
570
564 for k, ns in repo.names.iteritems():
571 for k, ns in repo.names.iteritems():
565 namespaces[k] = showlist('name', ns.names(repo, ctx.node()), args)
572 namespaces[k] = showlist('name', ns.names(repo, ctx.node()), args)
566 colornames[k] = ns.colorname
573 colornames[k] = ns.colorname
567 builtins[k] = ns.builtin
574 builtins[k] = ns.builtin
568
575
569 f = _showlist('namespace', list(namespaces), args)
576 f = _showlist('namespace', list(namespaces), args)
570
577
571 def makemap(ns):
578 def makemap(ns):
572 return {
579 return {
573 'namespace': ns,
580 'namespace': ns,
574 'names': namespaces[ns],
581 'names': namespaces[ns],
575 'builtin': builtins[ns],
582 'builtin': builtins[ns],
576 'colorname': colornames[ns],
583 'colorname': colornames[ns],
577 }
584 }
578
585
579 return _hybrid(f, namespaces, makemap, lambda x: x['namespace'])
586 return _hybrid(f, namespaces, makemap, lambda x: x['namespace'])
580
587
581 @templatekeyword('node')
588 @templatekeyword('node')
582 def shownode(repo, ctx, templ, **args):
589 def shownode(repo, ctx, templ, **args):
583 """String. The changeset identification hash, as a 40 hexadecimal
590 """String. The changeset identification hash, as a 40 hexadecimal
584 digit string.
591 digit string.
585 """
592 """
586 return ctx.hex()
593 return ctx.hex()
587
594
588 @templatekeyword('obsolete')
595 @templatekeyword('obsolete')
589 def showobsolete(repo, ctx, templ, **args):
596 def showobsolete(repo, ctx, templ, **args):
590 """String. Whether the changeset is obsolete.
597 """String. Whether the changeset is obsolete.
591 """
598 """
592 if ctx.obsolete():
599 if ctx.obsolete():
593 return 'obsolete'
600 return 'obsolete'
594 return ''
601 return ''
595
602
596 @templatekeyword('peerpaths')
603 @templatekeyword('peerpaths')
597 def showpeerpaths(repo, **args):
604 def showpeerpaths(repo, **args):
598 """A dictionary of repository locations defined in the [paths] section
605 """A dictionary of repository locations defined in the [paths] section
599 of your configuration file. (EXPERIMENTAL)"""
606 of your configuration file. (EXPERIMENTAL)"""
600 # see commands.paths() for naming of dictionary keys
607 # see commands.paths() for naming of dictionary keys
601 paths = util.sortdict()
608 paths = util.sortdict()
602 for k, p in sorted(repo.ui.paths.iteritems()):
609 for k, p in sorted(repo.ui.paths.iteritems()):
603 d = util.sortdict()
610 d = util.sortdict()
604 d['url'] = p.rawloc
611 d['url'] = p.rawloc
605 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
612 d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
606 def f():
613 def f():
607 yield d['url']
614 yield d['url']
608 paths[k] = hybriddict(d, gen=f())
615 paths[k] = hybriddict(d, gen=f())
609
616
610 # no hybriddict() since d['path'] can't be formatted as a string. perhaps
617 # no hybriddict() since d['path'] can't be formatted as a string. perhaps
611 # hybriddict() should call templatefilters.stringify(d[value]).
618 # hybriddict() should call templatefilters.stringify(d[value]).
612 return _hybrid(None, paths, lambda k: {'name': k, 'path': paths[k]},
619 return _hybrid(None, paths, lambda k: {'name': k, 'path': paths[k]},
613 lambda d: '%s=%s' % (d['name'], d['path']['url']))
620 lambda d: '%s=%s' % (d['name'], d['path']['url']))
614
621
615 @templatekeyword("predecessors")
622 @templatekeyword("predecessors")
616 def showpredecessors(repo, ctx, **args):
623 def showpredecessors(repo, ctx, **args):
617 """Returns the list if the closest visible successors
624 """Returns the list if the closest visible successors
618 """
625 """
619 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
626 predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
620 predecessors = map(hex, predecessors)
627 predecessors = map(hex, predecessors)
621
628
622 return _hybrid(None, predecessors,
629 return _hybrid(None, predecessors,
623 lambda x: {'ctx': repo[x], 'revcache': {}},
630 lambda x: {'ctx': repo[x], 'revcache': {}},
624 lambda d: _formatrevnode(d['ctx']))
631 lambda d: _formatrevnode(d['ctx']))
625
632
626 @templatekeyword("successorssets")
633 @templatekeyword("successorssets")
627 def showsuccessorssets(repo, ctx, **args):
634 def showsuccessorssets(repo, ctx, **args):
628 """Returns a string of sets of successors for a changectx
635 """Returns a string of sets of successors for a changectx
629
636
630 Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and
637 Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and
631 ctx2 while also diverged into ctx3"""
638 ctx2 while also diverged into ctx3"""
632 if not ctx.obsolete():
639 if not ctx.obsolete():
633 return ''
640 return ''
634 args = pycompat.byteskwargs(args)
641 args = pycompat.byteskwargs(args)
635
642
636 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
643 ssets = obsutil.successorssets(repo, ctx.node(), closest=True)
637 ssets = [[hex(n) for n in ss] for ss in ssets]
644 ssets = [[hex(n) for n in ss] for ss in ssets]
638
645
639 data = []
646 data = []
640 for ss in ssets:
647 for ss in ssets:
641 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
648 h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
642 lambda d: _formatrevnode(d['ctx']))
649 lambda d: _formatrevnode(d['ctx']))
643 data.append(h)
650 data.append(h)
644
651
645 # Format the successorssets
652 # Format the successorssets
646 def render(d):
653 def render(d):
647 t = []
654 t = []
648 for i in d.gen:
655 for i in d.gen:
649 t.append(i)
656 t.append(i)
650 return "".join(t)
657 return "".join(t)
651
658
652 def gen(data):
659 def gen(data):
653 yield "; ".join(render(d) for d in data)
660 yield "; ".join(render(d) for d in data)
654
661
655 return _hybrid(gen(data), data, lambda x: {'successorset': x},
662 return _hybrid(gen(data), data, lambda x: {'successorset': x},
656 lambda d: d["successorset"])
663 lambda d: d["successorset"])
657
664
658 @templatekeyword('p1rev')
665 @templatekeyword('p1rev')
659 def showp1rev(repo, ctx, templ, **args):
666 def showp1rev(repo, ctx, templ, **args):
660 """Integer. The repository-local revision number of the changeset's
667 """Integer. The repository-local revision number of the changeset's
661 first parent, or -1 if the changeset has no parents."""
668 first parent, or -1 if the changeset has no parents."""
662 return ctx.p1().rev()
669 return ctx.p1().rev()
663
670
664 @templatekeyword('p2rev')
671 @templatekeyword('p2rev')
665 def showp2rev(repo, ctx, templ, **args):
672 def showp2rev(repo, ctx, templ, **args):
666 """Integer. The repository-local revision number of the changeset's
673 """Integer. The repository-local revision number of the changeset's
667 second parent, or -1 if the changeset has no second parent."""
674 second parent, or -1 if the changeset has no second parent."""
668 return ctx.p2().rev()
675 return ctx.p2().rev()
669
676
670 @templatekeyword('p1node')
677 @templatekeyword('p1node')
671 def showp1node(repo, ctx, templ, **args):
678 def showp1node(repo, ctx, templ, **args):
672 """String. The identification hash of the changeset's first parent,
679 """String. The identification hash of the changeset's first parent,
673 as a 40 digit hexadecimal string. If the changeset has no parents, all
680 as a 40 digit hexadecimal string. If the changeset has no parents, all
674 digits are 0."""
681 digits are 0."""
675 return ctx.p1().hex()
682 return ctx.p1().hex()
676
683
677 @templatekeyword('p2node')
684 @templatekeyword('p2node')
678 def showp2node(repo, ctx, templ, **args):
685 def showp2node(repo, ctx, templ, **args):
679 """String. The identification hash of the changeset's second
686 """String. The identification hash of the changeset's second
680 parent, as a 40 digit hexadecimal string. If the changeset has no second
687 parent, as a 40 digit hexadecimal string. If the changeset has no second
681 parent, all digits are 0."""
688 parent, all digits are 0."""
682 return ctx.p2().hex()
689 return ctx.p2().hex()
683
690
684 @templatekeyword('parents')
691 @templatekeyword('parents')
685 def showparents(**args):
692 def showparents(**args):
686 """List of strings. The parents of the changeset in "rev:node"
693 """List of strings. The parents of the changeset in "rev:node"
687 format. If the changeset has only one "natural" parent (the predecessor
694 format. If the changeset has only one "natural" parent (the predecessor
688 revision) nothing is shown."""
695 revision) nothing is shown."""
689 args = pycompat.byteskwargs(args)
696 args = pycompat.byteskwargs(args)
690 repo = args['repo']
697 repo = args['repo']
691 ctx = args['ctx']
698 ctx = args['ctx']
692 pctxs = scmutil.meaningfulparents(repo, ctx)
699 pctxs = scmutil.meaningfulparents(repo, ctx)
693 # ifcontains() needs a list of str
700 # ifcontains() needs a list of str
694 prevs = ["%d" % p.rev() for p in pctxs]
701 prevs = ["%d" % p.rev() for p in pctxs]
695 parents = [[('rev', p.rev()),
702 parents = [[('rev', p.rev()),
696 ('node', p.hex()),
703 ('node', p.hex()),
697 ('phase', p.phasestr())]
704 ('phase', p.phasestr())]
698 for p in pctxs]
705 for p in pctxs]
699 f = _showlist('parent', parents, args)
706 f = _showlist('parent', parents, args)
700 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
707 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
701 lambda d: _formatrevnode(d['ctx']))
708 lambda d: _formatrevnode(d['ctx']))
702
709
703 @templatekeyword('phase')
710 @templatekeyword('phase')
704 def showphase(repo, ctx, templ, **args):
711 def showphase(repo, ctx, templ, **args):
705 """String. The changeset phase name."""
712 """String. The changeset phase name."""
706 return ctx.phasestr()
713 return ctx.phasestr()
707
714
708 @templatekeyword('phaseidx')
715 @templatekeyword('phaseidx')
709 def showphaseidx(repo, ctx, templ, **args):
716 def showphaseidx(repo, ctx, templ, **args):
710 """Integer. The changeset phase index."""
717 """Integer. The changeset phase index."""
711 return ctx.phase()
718 return ctx.phase()
712
719
713 @templatekeyword('rev')
720 @templatekeyword('rev')
714 def showrev(repo, ctx, templ, **args):
721 def showrev(repo, ctx, templ, **args):
715 """Integer. The repository-local changeset revision number."""
722 """Integer. The repository-local changeset revision number."""
716 return scmutil.intrev(ctx)
723 return scmutil.intrev(ctx)
717
724
718 def showrevslist(name, revs, **args):
725 def showrevslist(name, revs, **args):
719 """helper to generate a list of revisions in which a mapped template will
726 """helper to generate a list of revisions in which a mapped template will
720 be evaluated"""
727 be evaluated"""
721 args = pycompat.byteskwargs(args)
728 args = pycompat.byteskwargs(args)
722 repo = args['ctx'].repo()
729 repo = args['ctx'].repo()
723 # ifcontains() needs a list of str
730 # ifcontains() needs a list of str
724 revs = ["%d" % r for r in revs]
731 revs = ["%d" % r for r in revs]
725 f = _showlist(name, revs, args)
732 f = _showlist(name, revs, args)
726 return _hybrid(f, revs,
733 return _hybrid(f, revs,
727 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
734 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
728 lambda d: d[name])
735 lambda d: d[name])
729
736
730 @templatekeyword('subrepos')
737 @templatekeyword('subrepos')
731 def showsubrepos(**args):
738 def showsubrepos(**args):
732 """List of strings. Updated subrepositories in the changeset."""
739 """List of strings. Updated subrepositories in the changeset."""
733 args = pycompat.byteskwargs(args)
740 args = pycompat.byteskwargs(args)
734 ctx = args['ctx']
741 ctx = args['ctx']
735 substate = ctx.substate
742 substate = ctx.substate
736 if not substate:
743 if not substate:
737 return showlist('subrepo', [], args)
744 return showlist('subrepo', [], args)
738 psubstate = ctx.parents()[0].substate or {}
745 psubstate = ctx.parents()[0].substate or {}
739 subrepos = []
746 subrepos = []
740 for sub in substate:
747 for sub in substate:
741 if sub not in psubstate or substate[sub] != psubstate[sub]:
748 if sub not in psubstate or substate[sub] != psubstate[sub]:
742 subrepos.append(sub) # modified or newly added in ctx
749 subrepos.append(sub) # modified or newly added in ctx
743 for sub in psubstate:
750 for sub in psubstate:
744 if sub not in substate:
751 if sub not in substate:
745 subrepos.append(sub) # removed in ctx
752 subrepos.append(sub) # removed in ctx
746 return showlist('subrepo', sorted(subrepos), args)
753 return showlist('subrepo', sorted(subrepos), args)
747
754
748 # don't remove "showtags" definition, even though namespaces will put
755 # don't remove "showtags" definition, even though namespaces will put
749 # a helper function for "tags" keyword into "keywords" map automatically,
756 # a helper function for "tags" keyword into "keywords" map automatically,
750 # because online help text is built without namespaces initialization
757 # because online help text is built without namespaces initialization
751 @templatekeyword('tags')
758 @templatekeyword('tags')
752 def showtags(**args):
759 def showtags(**args):
753 """List of strings. Any tags associated with the changeset."""
760 """List of strings. Any tags associated with the changeset."""
754 return shownames('tags', **args)
761 return shownames('tags', **args)
755
762
756 def loadkeyword(ui, extname, registrarobj):
763 def loadkeyword(ui, extname, registrarobj):
757 """Load template keyword from specified registrarobj
764 """Load template keyword from specified registrarobj
758 """
765 """
759 for name, func in registrarobj._table.iteritems():
766 for name, func in registrarobj._table.iteritems():
760 keywords[name] = func
767 keywords[name] = func
761
768
762 @templatekeyword('termwidth')
769 @templatekeyword('termwidth')
763 def showtermwidth(repo, ctx, templ, **args):
770 def showtermwidth(repo, ctx, templ, **args):
764 """Integer. The width of the current terminal."""
771 """Integer. The width of the current terminal."""
765 return repo.ui.termwidth()
772 return repo.ui.termwidth()
766
773
767 @templatekeyword('troubles')
774 @templatekeyword('troubles')
768 def showtroubles(repo, **args):
775 def showtroubles(repo, **args):
769 """List of strings. Evolution troubles affecting the changeset.
776 """List of strings. Evolution troubles affecting the changeset.
770
777
771 (DEPRECATED)
778 (DEPRECATED)
772 """
779 """
773 msg = ("'troubles' is deprecated, "
780 msg = ("'troubles' is deprecated, "
774 "use 'instabilities'")
781 "use 'instabilities'")
775 repo.ui.deprecwarn(msg, '4.4')
782 repo.ui.deprecwarn(msg, '4.4')
776
783
777 return showinstabilities(repo=repo, **args)
784 return showinstabilities(repo=repo, **args)
778
785
779 @templatekeyword('instabilities')
786 @templatekeyword('instabilities')
780 def showinstabilities(**args):
787 def showinstabilities(**args):
781 """List of strings. Evolution instabilities affecting the changeset.
788 """List of strings. Evolution instabilities affecting the changeset.
782
789
783 (EXPERIMENTAL)
790 (EXPERIMENTAL)
784 """
791 """
785 args = pycompat.byteskwargs(args)
792 args = pycompat.byteskwargs(args)
786 return showlist('instability', args['ctx'].instabilities(), args,
793 return showlist('instability', args['ctx'].instabilities(), args,
787 plural='instabilities')
794 plural='instabilities')
788
795
789 # tell hggettext to extract docstrings from these functions:
796 # tell hggettext to extract docstrings from these functions:
790 i18nfunctions = keywords.values()
797 i18nfunctions = keywords.values()
@@ -1,4321 +1,4473 b''
1 $ hg init a
1 $ hg init a
2 $ cd a
2 $ cd a
3 $ echo a > a
3 $ echo a > a
4 $ hg add a
4 $ hg add a
5 $ echo line 1 > b
5 $ echo line 1 > b
6 $ echo line 2 >> b
6 $ echo line 2 >> b
7 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
7 $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
8
8
9 $ hg add b
9 $ hg add b
10 $ echo other 1 > c
10 $ echo other 1 > c
11 $ echo other 2 >> c
11 $ echo other 2 >> c
12 $ echo >> c
12 $ echo >> c
13 $ echo other 3 >> c
13 $ echo other 3 >> c
14 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
14 $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
15
15
16 $ hg add c
16 $ hg add c
17 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
17 $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
18 $ echo c >> c
18 $ echo c >> c
19 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
19 $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
20
20
21 $ echo foo > .hg/branch
21 $ echo foo > .hg/branch
22 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
22 $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
23
23
24 $ hg co -q 3
24 $ hg co -q 3
25 $ echo other 4 >> d
25 $ echo other 4 >> d
26 $ hg add d
26 $ hg add d
27 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
27 $ hg commit -m 'new head' -d '1500000 0' -u 'person'
28
28
29 $ hg merge -q foo
29 $ hg merge -q foo
30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
30 $ hg commit -m 'merge' -d '1500001 0' -u 'person'
31
31
32 Test arithmetic operators have the right precedence:
32 Test arithmetic operators have the right precedence:
33
33
34 $ hg log -l 1 -T '{date(date, "%Y") + 5 * 10} {date(date, "%Y") - 2 * 3}\n'
34 $ hg log -l 1 -T '{date(date, "%Y") + 5 * 10} {date(date, "%Y") - 2 * 3}\n'
35 2020 1964
35 2020 1964
36 $ hg log -l 1 -T '{date(date, "%Y") * 5 + 10} {date(date, "%Y") * 3 - 2}\n'
36 $ hg log -l 1 -T '{date(date, "%Y") * 5 + 10} {date(date, "%Y") * 3 - 2}\n'
37 9860 5908
37 9860 5908
38
38
39 Test division:
39 Test division:
40
40
41 $ hg debugtemplate -r0 -v '{5 / 2} {mod(5, 2)}\n'
41 $ hg debugtemplate -r0 -v '{5 / 2} {mod(5, 2)}\n'
42 (template
42 (template
43 (/
43 (/
44 ('integer', '5')
44 ('integer', '5')
45 ('integer', '2'))
45 ('integer', '2'))
46 ('string', ' ')
46 ('string', ' ')
47 (func
47 (func
48 ('symbol', 'mod')
48 ('symbol', 'mod')
49 (list
49 (list
50 ('integer', '5')
50 ('integer', '5')
51 ('integer', '2')))
51 ('integer', '2')))
52 ('string', '\n'))
52 ('string', '\n'))
53 2 1
53 2 1
54 $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
54 $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
55 (template
55 (template
56 (/
56 (/
57 ('integer', '5')
57 ('integer', '5')
58 (negate
58 (negate
59 ('integer', '2')))
59 ('integer', '2')))
60 ('string', ' ')
60 ('string', ' ')
61 (func
61 (func
62 ('symbol', 'mod')
62 ('symbol', 'mod')
63 (list
63 (list
64 ('integer', '5')
64 ('integer', '5')
65 (negate
65 (negate
66 ('integer', '2'))))
66 ('integer', '2'))))
67 ('string', '\n'))
67 ('string', '\n'))
68 -3 -1
68 -3 -1
69 $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
69 $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
70 (template
70 (template
71 (/
71 (/
72 (negate
72 (negate
73 ('integer', '5'))
73 ('integer', '5'))
74 ('integer', '2'))
74 ('integer', '2'))
75 ('string', ' ')
75 ('string', ' ')
76 (func
76 (func
77 ('symbol', 'mod')
77 ('symbol', 'mod')
78 (list
78 (list
79 (negate
79 (negate
80 ('integer', '5'))
80 ('integer', '5'))
81 ('integer', '2')))
81 ('integer', '2')))
82 ('string', '\n'))
82 ('string', '\n'))
83 -3 1
83 -3 1
84 $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
84 $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
85 (template
85 (template
86 (/
86 (/
87 (negate
87 (negate
88 ('integer', '5'))
88 ('integer', '5'))
89 (negate
89 (negate
90 ('integer', '2')))
90 ('integer', '2')))
91 ('string', ' ')
91 ('string', ' ')
92 (func
92 (func
93 ('symbol', 'mod')
93 ('symbol', 'mod')
94 (list
94 (list
95 (negate
95 (negate
96 ('integer', '5'))
96 ('integer', '5'))
97 (negate
97 (negate
98 ('integer', '2'))))
98 ('integer', '2'))))
99 ('string', '\n'))
99 ('string', '\n'))
100 2 -1
100 2 -1
101
101
102 Filters bind closer than arithmetic:
102 Filters bind closer than arithmetic:
103
103
104 $ hg debugtemplate -r0 -v '{revset(".")|count - 1}\n'
104 $ hg debugtemplate -r0 -v '{revset(".")|count - 1}\n'
105 (template
105 (template
106 (-
106 (-
107 (|
107 (|
108 (func
108 (func
109 ('symbol', 'revset')
109 ('symbol', 'revset')
110 ('string', '.'))
110 ('string', '.'))
111 ('symbol', 'count'))
111 ('symbol', 'count'))
112 ('integer', '1'))
112 ('integer', '1'))
113 ('string', '\n'))
113 ('string', '\n'))
114 0
114 0
115
115
116 But negate binds closer still:
116 But negate binds closer still:
117
117
118 $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
118 $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
119 (template
119 (template
120 (-
120 (-
121 ('integer', '1')
121 ('integer', '1')
122 (|
122 (|
123 ('integer', '3')
123 ('integer', '3')
124 ('symbol', 'stringify')))
124 ('symbol', 'stringify')))
125 ('string', '\n'))
125 ('string', '\n'))
126 hg: parse error: arithmetic only defined on integers
126 hg: parse error: arithmetic only defined on integers
127 [255]
127 [255]
128 $ hg debugtemplate -r0 -v '{-3|stringify}\n'
128 $ hg debugtemplate -r0 -v '{-3|stringify}\n'
129 (template
129 (template
130 (|
130 (|
131 (negate
131 (negate
132 ('integer', '3'))
132 ('integer', '3'))
133 ('symbol', 'stringify'))
133 ('symbol', 'stringify'))
134 ('string', '\n'))
134 ('string', '\n'))
135 -3
135 -3
136
136
137 Keyword arguments:
137 Keyword arguments:
138
138
139 $ hg debugtemplate -r0 -v '{foo=bar|baz}'
139 $ hg debugtemplate -r0 -v '{foo=bar|baz}'
140 (template
140 (template
141 (keyvalue
141 (keyvalue
142 ('symbol', 'foo')
142 ('symbol', 'foo')
143 (|
143 (|
144 ('symbol', 'bar')
144 ('symbol', 'bar')
145 ('symbol', 'baz'))))
145 ('symbol', 'baz'))))
146 hg: parse error: can't use a key-value pair in this context
146 hg: parse error: can't use a key-value pair in this context
147 [255]
147 [255]
148
148
149 $ hg debugtemplate '{pad("foo", width=10, left=true)}\n'
149 $ hg debugtemplate '{pad("foo", width=10, left=true)}\n'
150 foo
150 foo
151
151
152 Call function which takes named arguments by filter syntax:
152 Call function which takes named arguments by filter syntax:
153
153
154 $ hg debugtemplate '{" "|separate}'
154 $ hg debugtemplate '{" "|separate}'
155 $ hg debugtemplate '{("not", "an", "argument", "list")|separate}'
155 $ hg debugtemplate '{("not", "an", "argument", "list")|separate}'
156 hg: parse error: unknown method 'list'
156 hg: parse error: unknown method 'list'
157 [255]
157 [255]
158
158
159 Second branch starting at nullrev:
159 Second branch starting at nullrev:
160
160
161 $ hg update null
161 $ hg update null
162 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
162 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
163 $ echo second > second
163 $ echo second > second
164 $ hg add second
164 $ hg add second
165 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
165 $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
166 created new head
166 created new head
167
167
168 $ echo third > third
168 $ echo third > third
169 $ hg add third
169 $ hg add third
170 $ hg mv second fourth
170 $ hg mv second fourth
171 $ hg commit -m third -d "2020-01-01 10:01"
171 $ hg commit -m third -d "2020-01-01 10:01"
172
172
173 $ hg log --template '{join(file_copies, ",\n")}\n' -r .
173 $ hg log --template '{join(file_copies, ",\n")}\n' -r .
174 fourth (second)
174 fourth (second)
175 $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
175 $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
176 second -> fourth
176 second -> fourth
177 $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
177 $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
178 8 t
178 8 t
179 7 f
179 7 f
180
180
181 Working-directory revision has special identifiers, though they are still
181 Working-directory revision has special identifiers, though they are still
182 experimental:
182 experimental:
183
183
184 $ hg log -r 'wdir()' -T '{rev}:{node}\n'
184 $ hg log -r 'wdir()' -T '{rev}:{node}\n'
185 2147483647:ffffffffffffffffffffffffffffffffffffffff
185 2147483647:ffffffffffffffffffffffffffffffffffffffff
186
186
187 Some keywords are invalid for working-directory revision, but they should
187 Some keywords are invalid for working-directory revision, but they should
188 never cause crash:
188 never cause crash:
189
189
190 $ hg log -r 'wdir()' -T '{manifest}\n'
190 $ hg log -r 'wdir()' -T '{manifest}\n'
191
191
192
192
193 Quoting for ui.logtemplate
193 Quoting for ui.logtemplate
194
194
195 $ hg tip --config "ui.logtemplate={rev}\n"
195 $ hg tip --config "ui.logtemplate={rev}\n"
196 8
196 8
197 $ hg tip --config "ui.logtemplate='{rev}\n'"
197 $ hg tip --config "ui.logtemplate='{rev}\n'"
198 8
198 8
199 $ hg tip --config 'ui.logtemplate="{rev}\n"'
199 $ hg tip --config 'ui.logtemplate="{rev}\n"'
200 8
200 8
201 $ hg tip --config 'ui.logtemplate=n{rev}\n'
201 $ hg tip --config 'ui.logtemplate=n{rev}\n'
202 n8
202 n8
203
203
204 Make sure user/global hgrc does not affect tests
204 Make sure user/global hgrc does not affect tests
205
205
206 $ echo '[ui]' > .hg/hgrc
206 $ echo '[ui]' > .hg/hgrc
207 $ echo 'logtemplate =' >> .hg/hgrc
207 $ echo 'logtemplate =' >> .hg/hgrc
208 $ echo 'style =' >> .hg/hgrc
208 $ echo 'style =' >> .hg/hgrc
209
209
210 Add some simple styles to settings
210 Add some simple styles to settings
211
211
212 $ cat <<'EOF' >> .hg/hgrc
212 $ cat <<'EOF' >> .hg/hgrc
213 > [templates]
213 > [templates]
214 > simple = "{rev}\n"
214 > simple = "{rev}\n"
215 > simple2 = {rev}\n
215 > simple2 = {rev}\n
216 > rev = "should not precede {rev} keyword\n"
216 > rev = "should not precede {rev} keyword\n"
217 > EOF
217 > EOF
218
218
219 $ hg log -l1 -Tsimple
219 $ hg log -l1 -Tsimple
220 8
220 8
221 $ hg log -l1 -Tsimple2
221 $ hg log -l1 -Tsimple2
222 8
222 8
223 $ hg log -l1 -Trev
223 $ hg log -l1 -Trev
224 should not precede 8 keyword
224 should not precede 8 keyword
225 $ hg log -l1 -T '{simple}'
225 $ hg log -l1 -T '{simple}'
226 8
226 8
227
227
228 Map file shouldn't see user templates:
228 Map file shouldn't see user templates:
229
229
230 $ cat <<EOF > tmpl
230 $ cat <<EOF > tmpl
231 > changeset = 'nothing expanded:{simple}\n'
231 > changeset = 'nothing expanded:{simple}\n'
232 > EOF
232 > EOF
233 $ hg log -l1 --style ./tmpl
233 $ hg log -l1 --style ./tmpl
234 nothing expanded:
234 nothing expanded:
235
235
236 Test templates and style maps in files:
236 Test templates and style maps in files:
237
237
238 $ echo "{rev}" > tmpl
238 $ echo "{rev}" > tmpl
239 $ hg log -l1 -T./tmpl
239 $ hg log -l1 -T./tmpl
240 8
240 8
241 $ hg log -l1 -Tblah/blah
241 $ hg log -l1 -Tblah/blah
242 blah/blah (no-eol)
242 blah/blah (no-eol)
243
243
244 $ printf 'changeset = "{rev}\\n"\n' > map-simple
244 $ printf 'changeset = "{rev}\\n"\n' > map-simple
245 $ hg log -l1 -T./map-simple
245 $ hg log -l1 -T./map-simple
246 8
246 8
247
247
248 Test template map inheritance
248 Test template map inheritance
249
249
250 $ echo "__base__ = map-cmdline.default" > map-simple
250 $ echo "__base__ = map-cmdline.default" > map-simple
251 $ printf 'cset = "changeset: ***{rev}***\\n"\n' >> map-simple
251 $ printf 'cset = "changeset: ***{rev}***\\n"\n' >> map-simple
252 $ hg log -l1 -T./map-simple
252 $ hg log -l1 -T./map-simple
253 changeset: ***8***
253 changeset: ***8***
254 tag: tip
254 tag: tip
255 user: test
255 user: test
256 date: Wed Jan 01 10:01:00 2020 +0000
256 date: Wed Jan 01 10:01:00 2020 +0000
257 summary: third
257 summary: third
258
258
259
259
260 Test docheader, docfooter and separator in template map
260 Test docheader, docfooter and separator in template map
261
261
262 $ cat <<'EOF' > map-myjson
262 $ cat <<'EOF' > map-myjson
263 > docheader = '\{\n'
263 > docheader = '\{\n'
264 > docfooter = '\n}\n'
264 > docfooter = '\n}\n'
265 > separator = ',\n'
265 > separator = ',\n'
266 > changeset = ' {dict(rev, node|short)|json}'
266 > changeset = ' {dict(rev, node|short)|json}'
267 > EOF
267 > EOF
268 $ hg log -l2 -T./map-myjson
268 $ hg log -l2 -T./map-myjson
269 {
269 {
270 {"node": "95c24699272e", "rev": 8},
270 {"node": "95c24699272e", "rev": 8},
271 {"node": "29114dbae42b", "rev": 7}
271 {"node": "29114dbae42b", "rev": 7}
272 }
272 }
273
273
274 Test docheader, docfooter and separator in [templates] section
274 Test docheader, docfooter and separator in [templates] section
275
275
276 $ cat <<'EOF' >> .hg/hgrc
276 $ cat <<'EOF' >> .hg/hgrc
277 > [templates]
277 > [templates]
278 > myjson = ' {dict(rev, node|short)|json}'
278 > myjson = ' {dict(rev, node|short)|json}'
279 > myjson:docheader = '\{\n'
279 > myjson:docheader = '\{\n'
280 > myjson:docfooter = '\n}\n'
280 > myjson:docfooter = '\n}\n'
281 > myjson:separator = ',\n'
281 > myjson:separator = ',\n'
282 > :docheader = 'should not be selected as a docheader for literal templates\n'
282 > :docheader = 'should not be selected as a docheader for literal templates\n'
283 > EOF
283 > EOF
284 $ hg log -l2 -Tmyjson
284 $ hg log -l2 -Tmyjson
285 {
285 {
286 {"node": "95c24699272e", "rev": 8},
286 {"node": "95c24699272e", "rev": 8},
287 {"node": "29114dbae42b", "rev": 7}
287 {"node": "29114dbae42b", "rev": 7}
288 }
288 }
289 $ hg log -l1 -T'{rev}\n'
289 $ hg log -l1 -T'{rev}\n'
290 8
290 8
291
291
292 Template should precede style option
292 Template should precede style option
293
293
294 $ hg log -l1 --style default -T '{rev}\n'
294 $ hg log -l1 --style default -T '{rev}\n'
295 8
295 8
296
296
297 Add a commit with empty description, to ensure that the templates
297 Add a commit with empty description, to ensure that the templates
298 below will omit the description line.
298 below will omit the description line.
299
299
300 $ echo c >> c
300 $ echo c >> c
301 $ hg add c
301 $ hg add c
302 $ hg commit -qm ' '
302 $ hg commit -qm ' '
303
303
304 Default style is like normal output. Phases style should be the same
304 Default style is like normal output. Phases style should be the same
305 as default style, except for extra phase lines.
305 as default style, except for extra phase lines.
306
306
307 $ hg log > log.out
307 $ hg log > log.out
308 $ hg log --style default > style.out
308 $ hg log --style default > style.out
309 $ cmp log.out style.out || diff -u log.out style.out
309 $ cmp log.out style.out || diff -u log.out style.out
310 $ hg log -T phases > phases.out
310 $ hg log -T phases > phases.out
311 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
311 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
312 +phase: draft
312 +phase: draft
313 +phase: draft
313 +phase: draft
314 +phase: draft
314 +phase: draft
315 +phase: draft
315 +phase: draft
316 +phase: draft
316 +phase: draft
317 +phase: draft
317 +phase: draft
318 +phase: draft
318 +phase: draft
319 +phase: draft
319 +phase: draft
320 +phase: draft
320 +phase: draft
321 +phase: draft
321 +phase: draft
322
322
323 $ hg log -v > log.out
323 $ hg log -v > log.out
324 $ hg log -v --style default > style.out
324 $ hg log -v --style default > style.out
325 $ cmp log.out style.out || diff -u log.out style.out
325 $ cmp log.out style.out || diff -u log.out style.out
326 $ hg log -v -T phases > phases.out
326 $ hg log -v -T phases > phases.out
327 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
327 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
328 +phase: draft
328 +phase: draft
329 +phase: draft
329 +phase: draft
330 +phase: draft
330 +phase: draft
331 +phase: draft
331 +phase: draft
332 +phase: draft
332 +phase: draft
333 +phase: draft
333 +phase: draft
334 +phase: draft
334 +phase: draft
335 +phase: draft
335 +phase: draft
336 +phase: draft
336 +phase: draft
337 +phase: draft
337 +phase: draft
338
338
339 $ hg log -q > log.out
339 $ hg log -q > log.out
340 $ hg log -q --style default > style.out
340 $ hg log -q --style default > style.out
341 $ cmp log.out style.out || diff -u log.out style.out
341 $ cmp log.out style.out || diff -u log.out style.out
342 $ hg log -q -T phases > phases.out
342 $ hg log -q -T phases > phases.out
343 $ cmp log.out phases.out || diff -u log.out phases.out
343 $ cmp log.out phases.out || diff -u log.out phases.out
344
344
345 $ hg log --debug > log.out
345 $ hg log --debug > log.out
346 $ hg log --debug --style default > style.out
346 $ hg log --debug --style default > style.out
347 $ cmp log.out style.out || diff -u log.out style.out
347 $ cmp log.out style.out || diff -u log.out style.out
348 $ hg log --debug -T phases > phases.out
348 $ hg log --debug -T phases > phases.out
349 $ cmp log.out phases.out || diff -u log.out phases.out
349 $ cmp log.out phases.out || diff -u log.out phases.out
350
350
351 Default style of working-directory revision should also be the same (but
351 Default style of working-directory revision should also be the same (but
352 date may change while running tests):
352 date may change while running tests):
353
353
354 $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
354 $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
355 $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
355 $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
356 $ cmp log.out style.out || diff -u log.out style.out
356 $ cmp log.out style.out || diff -u log.out style.out
357
357
358 $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
358 $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
359 $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
359 $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
360 $ cmp log.out style.out || diff -u log.out style.out
360 $ cmp log.out style.out || diff -u log.out style.out
361
361
362 $ hg log -r 'wdir()' -q > log.out
362 $ hg log -r 'wdir()' -q > log.out
363 $ hg log -r 'wdir()' -q --style default > style.out
363 $ hg log -r 'wdir()' -q --style default > style.out
364 $ cmp log.out style.out || diff -u log.out style.out
364 $ cmp log.out style.out || diff -u log.out style.out
365
365
366 $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
366 $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
367 $ hg log -r 'wdir()' --debug --style default \
367 $ hg log -r 'wdir()' --debug --style default \
368 > | sed 's|^date:.*|date:|' > style.out
368 > | sed 's|^date:.*|date:|' > style.out
369 $ cmp log.out style.out || diff -u log.out style.out
369 $ cmp log.out style.out || diff -u log.out style.out
370
370
371 Default style should also preserve color information (issue2866):
371 Default style should also preserve color information (issue2866):
372
372
373 $ cp $HGRCPATH $HGRCPATH-bak
373 $ cp $HGRCPATH $HGRCPATH-bak
374 $ cat <<EOF >> $HGRCPATH
374 $ cat <<EOF >> $HGRCPATH
375 > [extensions]
375 > [extensions]
376 > color=
376 > color=
377 > EOF
377 > EOF
378
378
379 $ hg --color=debug log > log.out
379 $ hg --color=debug log > log.out
380 $ hg --color=debug log --style default > style.out
380 $ hg --color=debug log --style default > style.out
381 $ cmp log.out style.out || diff -u log.out style.out
381 $ cmp log.out style.out || diff -u log.out style.out
382 $ hg --color=debug log -T phases > phases.out
382 $ hg --color=debug log -T phases > phases.out
383 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
383 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
384 +[log.phase|phase: draft]
384 +[log.phase|phase: draft]
385 +[log.phase|phase: draft]
385 +[log.phase|phase: draft]
386 +[log.phase|phase: draft]
386 +[log.phase|phase: draft]
387 +[log.phase|phase: draft]
387 +[log.phase|phase: draft]
388 +[log.phase|phase: draft]
388 +[log.phase|phase: draft]
389 +[log.phase|phase: draft]
389 +[log.phase|phase: draft]
390 +[log.phase|phase: draft]
390 +[log.phase|phase: draft]
391 +[log.phase|phase: draft]
391 +[log.phase|phase: draft]
392 +[log.phase|phase: draft]
392 +[log.phase|phase: draft]
393 +[log.phase|phase: draft]
393 +[log.phase|phase: draft]
394
394
395 $ hg --color=debug -v log > log.out
395 $ hg --color=debug -v log > log.out
396 $ hg --color=debug -v log --style default > style.out
396 $ hg --color=debug -v log --style default > style.out
397 $ cmp log.out style.out || diff -u log.out style.out
397 $ cmp log.out style.out || diff -u log.out style.out
398 $ hg --color=debug -v log -T phases > phases.out
398 $ hg --color=debug -v log -T phases > phases.out
399 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
399 $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
400 +[log.phase|phase: draft]
400 +[log.phase|phase: draft]
401 +[log.phase|phase: draft]
401 +[log.phase|phase: draft]
402 +[log.phase|phase: draft]
402 +[log.phase|phase: draft]
403 +[log.phase|phase: draft]
403 +[log.phase|phase: draft]
404 +[log.phase|phase: draft]
404 +[log.phase|phase: draft]
405 +[log.phase|phase: draft]
405 +[log.phase|phase: draft]
406 +[log.phase|phase: draft]
406 +[log.phase|phase: draft]
407 +[log.phase|phase: draft]
407 +[log.phase|phase: draft]
408 +[log.phase|phase: draft]
408 +[log.phase|phase: draft]
409 +[log.phase|phase: draft]
409 +[log.phase|phase: draft]
410
410
411 $ hg --color=debug -q log > log.out
411 $ hg --color=debug -q log > log.out
412 $ hg --color=debug -q log --style default > style.out
412 $ hg --color=debug -q log --style default > style.out
413 $ cmp log.out style.out || diff -u log.out style.out
413 $ cmp log.out style.out || diff -u log.out style.out
414 $ hg --color=debug -q log -T phases > phases.out
414 $ hg --color=debug -q log -T phases > phases.out
415 $ cmp log.out phases.out || diff -u log.out phases.out
415 $ cmp log.out phases.out || diff -u log.out phases.out
416
416
417 $ hg --color=debug --debug log > log.out
417 $ hg --color=debug --debug log > log.out
418 $ hg --color=debug --debug log --style default > style.out
418 $ hg --color=debug --debug log --style default > style.out
419 $ cmp log.out style.out || diff -u log.out style.out
419 $ cmp log.out style.out || diff -u log.out style.out
420 $ hg --color=debug --debug log -T phases > phases.out
420 $ hg --color=debug --debug log -T phases > phases.out
421 $ cmp log.out phases.out || diff -u log.out phases.out
421 $ cmp log.out phases.out || diff -u log.out phases.out
422
422
423 $ mv $HGRCPATH-bak $HGRCPATH
423 $ mv $HGRCPATH-bak $HGRCPATH
424
424
425 Remove commit with empty commit message, so as to not pollute further
425 Remove commit with empty commit message, so as to not pollute further
426 tests.
426 tests.
427
427
428 $ hg --config extensions.strip= strip -q .
428 $ hg --config extensions.strip= strip -q .
429
429
430 Revision with no copies (used to print a traceback):
430 Revision with no copies (used to print a traceback):
431
431
432 $ hg tip -v --template '\n'
432 $ hg tip -v --template '\n'
433
433
434
434
435 Compact style works:
435 Compact style works:
436
436
437 $ hg log -Tcompact
437 $ hg log -Tcompact
438 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
438 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
439 third
439 third
440
440
441 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
441 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user
442 second
442 second
443
443
444 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
444 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
445 merge
445 merge
446
446
447 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
447 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
448 new head
448 new head
449
449
450 4 bbe44766e73d 1970-01-17 04:53 +0000 person
450 4 bbe44766e73d 1970-01-17 04:53 +0000 person
451 new branch
451 new branch
452
452
453 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
453 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
454 no user, no domain
454 no user, no domain
455
455
456 2 97054abb4ab8 1970-01-14 21:20 +0000 other
456 2 97054abb4ab8 1970-01-14 21:20 +0000 other
457 no person
457 no person
458
458
459 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
459 1 b608e9d1a3f0 1970-01-13 17:33 +0000 other
460 other 1
460 other 1
461
461
462 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
462 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user
463 line 1
463 line 1
464
464
465
465
466 $ hg log -v --style compact
466 $ hg log -v --style compact
467 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
467 8[tip] 95c24699272e 2020-01-01 10:01 +0000 test
468 third
468 third
469
469
470 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
470 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
471 second
471 second
472
472
473 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
473 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
474 merge
474 merge
475
475
476 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
476 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person
477 new head
477 new head
478
478
479 4 bbe44766e73d 1970-01-17 04:53 +0000 person
479 4 bbe44766e73d 1970-01-17 04:53 +0000 person
480 new branch
480 new branch
481
481
482 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
482 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person
483 no user, no domain
483 no user, no domain
484
484
485 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
485 2 97054abb4ab8 1970-01-14 21:20 +0000 other@place
486 no person
486 no person
487
487
488 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
488 1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
489 other 1
489 other 1
490 other 2
490 other 2
491
491
492 other 3
492 other 3
493
493
494 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
494 0 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
495 line 1
495 line 1
496 line 2
496 line 2
497
497
498
498
499 $ hg log --debug --style compact
499 $ hg log --debug --style compact
500 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
500 8[tip]:7,-1 95c24699272e 2020-01-01 10:01 +0000 test
501 third
501 third
502
502
503 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
503 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname>
504 second
504 second
505
505
506 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
506 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person
507 merge
507 merge
508
508
509 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
509 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person
510 new head
510 new head
511
511
512 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
512 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person
513 new branch
513 new branch
514
514
515 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
515 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person
516 no user, no domain
516 no user, no domain
517
517
518 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
518 2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other@place
519 no person
519 no person
520
520
521 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
521 1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 A. N. Other <other@place>
522 other 1
522 other 1
523 other 2
523 other 2
524
524
525 other 3
525 other 3
526
526
527 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
527 0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 User Name <user@hostname>
528 line 1
528 line 1
529 line 2
529 line 2
530
530
531
531
532 Test xml styles:
532 Test xml styles:
533
533
534 $ hg log --style xml -r 'not all()'
534 $ hg log --style xml -r 'not all()'
535 <?xml version="1.0"?>
535 <?xml version="1.0"?>
536 <log>
536 <log>
537 </log>
537 </log>
538
538
539 $ hg log --style xml
539 $ hg log --style xml
540 <?xml version="1.0"?>
540 <?xml version="1.0"?>
541 <log>
541 <log>
542 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
542 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
543 <tag>tip</tag>
543 <tag>tip</tag>
544 <author email="test">test</author>
544 <author email="test">test</author>
545 <date>2020-01-01T10:01:00+00:00</date>
545 <date>2020-01-01T10:01:00+00:00</date>
546 <msg xml:space="preserve">third</msg>
546 <msg xml:space="preserve">third</msg>
547 </logentry>
547 </logentry>
548 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
548 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
549 <parent revision="-1" node="0000000000000000000000000000000000000000" />
549 <parent revision="-1" node="0000000000000000000000000000000000000000" />
550 <author email="user@hostname">User Name</author>
550 <author email="user@hostname">User Name</author>
551 <date>1970-01-12T13:46:40+00:00</date>
551 <date>1970-01-12T13:46:40+00:00</date>
552 <msg xml:space="preserve">second</msg>
552 <msg xml:space="preserve">second</msg>
553 </logentry>
553 </logentry>
554 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
554 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
555 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
555 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
556 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
556 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
557 <author email="person">person</author>
557 <author email="person">person</author>
558 <date>1970-01-18T08:40:01+00:00</date>
558 <date>1970-01-18T08:40:01+00:00</date>
559 <msg xml:space="preserve">merge</msg>
559 <msg xml:space="preserve">merge</msg>
560 </logentry>
560 </logentry>
561 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
561 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
562 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
562 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
563 <author email="person">person</author>
563 <author email="person">person</author>
564 <date>1970-01-18T08:40:00+00:00</date>
564 <date>1970-01-18T08:40:00+00:00</date>
565 <msg xml:space="preserve">new head</msg>
565 <msg xml:space="preserve">new head</msg>
566 </logentry>
566 </logentry>
567 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
567 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
568 <branch>foo</branch>
568 <branch>foo</branch>
569 <author email="person">person</author>
569 <author email="person">person</author>
570 <date>1970-01-17T04:53:20+00:00</date>
570 <date>1970-01-17T04:53:20+00:00</date>
571 <msg xml:space="preserve">new branch</msg>
571 <msg xml:space="preserve">new branch</msg>
572 </logentry>
572 </logentry>
573 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
573 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
574 <author email="person">person</author>
574 <author email="person">person</author>
575 <date>1970-01-16T01:06:40+00:00</date>
575 <date>1970-01-16T01:06:40+00:00</date>
576 <msg xml:space="preserve">no user, no domain</msg>
576 <msg xml:space="preserve">no user, no domain</msg>
577 </logentry>
577 </logentry>
578 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
578 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
579 <author email="other@place">other</author>
579 <author email="other@place">other</author>
580 <date>1970-01-14T21:20:00+00:00</date>
580 <date>1970-01-14T21:20:00+00:00</date>
581 <msg xml:space="preserve">no person</msg>
581 <msg xml:space="preserve">no person</msg>
582 </logentry>
582 </logentry>
583 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
583 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
584 <author email="other@place">A. N. Other</author>
584 <author email="other@place">A. N. Other</author>
585 <date>1970-01-13T17:33:20+00:00</date>
585 <date>1970-01-13T17:33:20+00:00</date>
586 <msg xml:space="preserve">other 1
586 <msg xml:space="preserve">other 1
587 other 2
587 other 2
588
588
589 other 3</msg>
589 other 3</msg>
590 </logentry>
590 </logentry>
591 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
591 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
592 <author email="user@hostname">User Name</author>
592 <author email="user@hostname">User Name</author>
593 <date>1970-01-12T13:46:40+00:00</date>
593 <date>1970-01-12T13:46:40+00:00</date>
594 <msg xml:space="preserve">line 1
594 <msg xml:space="preserve">line 1
595 line 2</msg>
595 line 2</msg>
596 </logentry>
596 </logentry>
597 </log>
597 </log>
598
598
599 $ hg log -v --style xml
599 $ hg log -v --style xml
600 <?xml version="1.0"?>
600 <?xml version="1.0"?>
601 <log>
601 <log>
602 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
602 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
603 <tag>tip</tag>
603 <tag>tip</tag>
604 <author email="test">test</author>
604 <author email="test">test</author>
605 <date>2020-01-01T10:01:00+00:00</date>
605 <date>2020-01-01T10:01:00+00:00</date>
606 <msg xml:space="preserve">third</msg>
606 <msg xml:space="preserve">third</msg>
607 <paths>
607 <paths>
608 <path action="A">fourth</path>
608 <path action="A">fourth</path>
609 <path action="A">third</path>
609 <path action="A">third</path>
610 <path action="R">second</path>
610 <path action="R">second</path>
611 </paths>
611 </paths>
612 <copies>
612 <copies>
613 <copy source="second">fourth</copy>
613 <copy source="second">fourth</copy>
614 </copies>
614 </copies>
615 </logentry>
615 </logentry>
616 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
616 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
617 <parent revision="-1" node="0000000000000000000000000000000000000000" />
617 <parent revision="-1" node="0000000000000000000000000000000000000000" />
618 <author email="user@hostname">User Name</author>
618 <author email="user@hostname">User Name</author>
619 <date>1970-01-12T13:46:40+00:00</date>
619 <date>1970-01-12T13:46:40+00:00</date>
620 <msg xml:space="preserve">second</msg>
620 <msg xml:space="preserve">second</msg>
621 <paths>
621 <paths>
622 <path action="A">second</path>
622 <path action="A">second</path>
623 </paths>
623 </paths>
624 </logentry>
624 </logentry>
625 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
625 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
626 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
626 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
627 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
627 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
628 <author email="person">person</author>
628 <author email="person">person</author>
629 <date>1970-01-18T08:40:01+00:00</date>
629 <date>1970-01-18T08:40:01+00:00</date>
630 <msg xml:space="preserve">merge</msg>
630 <msg xml:space="preserve">merge</msg>
631 <paths>
631 <paths>
632 </paths>
632 </paths>
633 </logentry>
633 </logentry>
634 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
634 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
635 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
635 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
636 <author email="person">person</author>
636 <author email="person">person</author>
637 <date>1970-01-18T08:40:00+00:00</date>
637 <date>1970-01-18T08:40:00+00:00</date>
638 <msg xml:space="preserve">new head</msg>
638 <msg xml:space="preserve">new head</msg>
639 <paths>
639 <paths>
640 <path action="A">d</path>
640 <path action="A">d</path>
641 </paths>
641 </paths>
642 </logentry>
642 </logentry>
643 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
643 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
644 <branch>foo</branch>
644 <branch>foo</branch>
645 <author email="person">person</author>
645 <author email="person">person</author>
646 <date>1970-01-17T04:53:20+00:00</date>
646 <date>1970-01-17T04:53:20+00:00</date>
647 <msg xml:space="preserve">new branch</msg>
647 <msg xml:space="preserve">new branch</msg>
648 <paths>
648 <paths>
649 </paths>
649 </paths>
650 </logentry>
650 </logentry>
651 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
651 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
652 <author email="person">person</author>
652 <author email="person">person</author>
653 <date>1970-01-16T01:06:40+00:00</date>
653 <date>1970-01-16T01:06:40+00:00</date>
654 <msg xml:space="preserve">no user, no domain</msg>
654 <msg xml:space="preserve">no user, no domain</msg>
655 <paths>
655 <paths>
656 <path action="M">c</path>
656 <path action="M">c</path>
657 </paths>
657 </paths>
658 </logentry>
658 </logentry>
659 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
659 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
660 <author email="other@place">other</author>
660 <author email="other@place">other</author>
661 <date>1970-01-14T21:20:00+00:00</date>
661 <date>1970-01-14T21:20:00+00:00</date>
662 <msg xml:space="preserve">no person</msg>
662 <msg xml:space="preserve">no person</msg>
663 <paths>
663 <paths>
664 <path action="A">c</path>
664 <path action="A">c</path>
665 </paths>
665 </paths>
666 </logentry>
666 </logentry>
667 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
667 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
668 <author email="other@place">A. N. Other</author>
668 <author email="other@place">A. N. Other</author>
669 <date>1970-01-13T17:33:20+00:00</date>
669 <date>1970-01-13T17:33:20+00:00</date>
670 <msg xml:space="preserve">other 1
670 <msg xml:space="preserve">other 1
671 other 2
671 other 2
672
672
673 other 3</msg>
673 other 3</msg>
674 <paths>
674 <paths>
675 <path action="A">b</path>
675 <path action="A">b</path>
676 </paths>
676 </paths>
677 </logentry>
677 </logentry>
678 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
678 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
679 <author email="user@hostname">User Name</author>
679 <author email="user@hostname">User Name</author>
680 <date>1970-01-12T13:46:40+00:00</date>
680 <date>1970-01-12T13:46:40+00:00</date>
681 <msg xml:space="preserve">line 1
681 <msg xml:space="preserve">line 1
682 line 2</msg>
682 line 2</msg>
683 <paths>
683 <paths>
684 <path action="A">a</path>
684 <path action="A">a</path>
685 </paths>
685 </paths>
686 </logentry>
686 </logentry>
687 </log>
687 </log>
688
688
689 $ hg log --debug --style xml
689 $ hg log --debug --style xml
690 <?xml version="1.0"?>
690 <?xml version="1.0"?>
691 <log>
691 <log>
692 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
692 <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
693 <tag>tip</tag>
693 <tag>tip</tag>
694 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
694 <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
695 <parent revision="-1" node="0000000000000000000000000000000000000000" />
695 <parent revision="-1" node="0000000000000000000000000000000000000000" />
696 <author email="test">test</author>
696 <author email="test">test</author>
697 <date>2020-01-01T10:01:00+00:00</date>
697 <date>2020-01-01T10:01:00+00:00</date>
698 <msg xml:space="preserve">third</msg>
698 <msg xml:space="preserve">third</msg>
699 <paths>
699 <paths>
700 <path action="A">fourth</path>
700 <path action="A">fourth</path>
701 <path action="A">third</path>
701 <path action="A">third</path>
702 <path action="R">second</path>
702 <path action="R">second</path>
703 </paths>
703 </paths>
704 <copies>
704 <copies>
705 <copy source="second">fourth</copy>
705 <copy source="second">fourth</copy>
706 </copies>
706 </copies>
707 <extra key="branch">default</extra>
707 <extra key="branch">default</extra>
708 </logentry>
708 </logentry>
709 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
709 <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
710 <parent revision="-1" node="0000000000000000000000000000000000000000" />
710 <parent revision="-1" node="0000000000000000000000000000000000000000" />
711 <parent revision="-1" node="0000000000000000000000000000000000000000" />
711 <parent revision="-1" node="0000000000000000000000000000000000000000" />
712 <author email="user@hostname">User Name</author>
712 <author email="user@hostname">User Name</author>
713 <date>1970-01-12T13:46:40+00:00</date>
713 <date>1970-01-12T13:46:40+00:00</date>
714 <msg xml:space="preserve">second</msg>
714 <msg xml:space="preserve">second</msg>
715 <paths>
715 <paths>
716 <path action="A">second</path>
716 <path action="A">second</path>
717 </paths>
717 </paths>
718 <extra key="branch">default</extra>
718 <extra key="branch">default</extra>
719 </logentry>
719 </logentry>
720 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
720 <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
721 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
721 <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
722 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
722 <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
723 <author email="person">person</author>
723 <author email="person">person</author>
724 <date>1970-01-18T08:40:01+00:00</date>
724 <date>1970-01-18T08:40:01+00:00</date>
725 <msg xml:space="preserve">merge</msg>
725 <msg xml:space="preserve">merge</msg>
726 <paths>
726 <paths>
727 </paths>
727 </paths>
728 <extra key="branch">default</extra>
728 <extra key="branch">default</extra>
729 </logentry>
729 </logentry>
730 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
730 <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
731 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
731 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
732 <parent revision="-1" node="0000000000000000000000000000000000000000" />
732 <parent revision="-1" node="0000000000000000000000000000000000000000" />
733 <author email="person">person</author>
733 <author email="person">person</author>
734 <date>1970-01-18T08:40:00+00:00</date>
734 <date>1970-01-18T08:40:00+00:00</date>
735 <msg xml:space="preserve">new head</msg>
735 <msg xml:space="preserve">new head</msg>
736 <paths>
736 <paths>
737 <path action="A">d</path>
737 <path action="A">d</path>
738 </paths>
738 </paths>
739 <extra key="branch">default</extra>
739 <extra key="branch">default</extra>
740 </logentry>
740 </logentry>
741 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
741 <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
742 <branch>foo</branch>
742 <branch>foo</branch>
743 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
743 <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
744 <parent revision="-1" node="0000000000000000000000000000000000000000" />
744 <parent revision="-1" node="0000000000000000000000000000000000000000" />
745 <author email="person">person</author>
745 <author email="person">person</author>
746 <date>1970-01-17T04:53:20+00:00</date>
746 <date>1970-01-17T04:53:20+00:00</date>
747 <msg xml:space="preserve">new branch</msg>
747 <msg xml:space="preserve">new branch</msg>
748 <paths>
748 <paths>
749 </paths>
749 </paths>
750 <extra key="branch">foo</extra>
750 <extra key="branch">foo</extra>
751 </logentry>
751 </logentry>
752 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
752 <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
753 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
753 <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
754 <parent revision="-1" node="0000000000000000000000000000000000000000" />
754 <parent revision="-1" node="0000000000000000000000000000000000000000" />
755 <author email="person">person</author>
755 <author email="person">person</author>
756 <date>1970-01-16T01:06:40+00:00</date>
756 <date>1970-01-16T01:06:40+00:00</date>
757 <msg xml:space="preserve">no user, no domain</msg>
757 <msg xml:space="preserve">no user, no domain</msg>
758 <paths>
758 <paths>
759 <path action="M">c</path>
759 <path action="M">c</path>
760 </paths>
760 </paths>
761 <extra key="branch">default</extra>
761 <extra key="branch">default</extra>
762 </logentry>
762 </logentry>
763 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
763 <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
764 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
764 <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
765 <parent revision="-1" node="0000000000000000000000000000000000000000" />
765 <parent revision="-1" node="0000000000000000000000000000000000000000" />
766 <author email="other@place">other</author>
766 <author email="other@place">other</author>
767 <date>1970-01-14T21:20:00+00:00</date>
767 <date>1970-01-14T21:20:00+00:00</date>
768 <msg xml:space="preserve">no person</msg>
768 <msg xml:space="preserve">no person</msg>
769 <paths>
769 <paths>
770 <path action="A">c</path>
770 <path action="A">c</path>
771 </paths>
771 </paths>
772 <extra key="branch">default</extra>
772 <extra key="branch">default</extra>
773 </logentry>
773 </logentry>
774 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
774 <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
775 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
775 <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
776 <parent revision="-1" node="0000000000000000000000000000000000000000" />
776 <parent revision="-1" node="0000000000000000000000000000000000000000" />
777 <author email="other@place">A. N. Other</author>
777 <author email="other@place">A. N. Other</author>
778 <date>1970-01-13T17:33:20+00:00</date>
778 <date>1970-01-13T17:33:20+00:00</date>
779 <msg xml:space="preserve">other 1
779 <msg xml:space="preserve">other 1
780 other 2
780 other 2
781
781
782 other 3</msg>
782 other 3</msg>
783 <paths>
783 <paths>
784 <path action="A">b</path>
784 <path action="A">b</path>
785 </paths>
785 </paths>
786 <extra key="branch">default</extra>
786 <extra key="branch">default</extra>
787 </logentry>
787 </logentry>
788 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
788 <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
789 <parent revision="-1" node="0000000000000000000000000000000000000000" />
789 <parent revision="-1" node="0000000000000000000000000000000000000000" />
790 <parent revision="-1" node="0000000000000000000000000000000000000000" />
790 <parent revision="-1" node="0000000000000000000000000000000000000000" />
791 <author email="user@hostname">User Name</author>
791 <author email="user@hostname">User Name</author>
792 <date>1970-01-12T13:46:40+00:00</date>
792 <date>1970-01-12T13:46:40+00:00</date>
793 <msg xml:space="preserve">line 1
793 <msg xml:space="preserve">line 1
794 line 2</msg>
794 line 2</msg>
795 <paths>
795 <paths>
796 <path action="A">a</path>
796 <path action="A">a</path>
797 </paths>
797 </paths>
798 <extra key="branch">default</extra>
798 <extra key="branch">default</extra>
799 </logentry>
799 </logentry>
800 </log>
800 </log>
801
801
802
802
803 Test JSON style:
803 Test JSON style:
804
804
805 $ hg log -k nosuch -Tjson
805 $ hg log -k nosuch -Tjson
806 []
806 []
807
807
808 $ hg log -qr . -Tjson
808 $ hg log -qr . -Tjson
809 [
809 [
810 {
810 {
811 "rev": 8,
811 "rev": 8,
812 "node": "95c24699272ef57d062b8bccc32c878bf841784a"
812 "node": "95c24699272ef57d062b8bccc32c878bf841784a"
813 }
813 }
814 ]
814 ]
815
815
816 $ hg log -vpr . -Tjson --stat
816 $ hg log -vpr . -Tjson --stat
817 [
817 [
818 {
818 {
819 "rev": 8,
819 "rev": 8,
820 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
820 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
821 "branch": "default",
821 "branch": "default",
822 "phase": "draft",
822 "phase": "draft",
823 "user": "test",
823 "user": "test",
824 "date": [1577872860, 0],
824 "date": [1577872860, 0],
825 "desc": "third",
825 "desc": "third",
826 "bookmarks": [],
826 "bookmarks": [],
827 "tags": ["tip"],
827 "tags": ["tip"],
828 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
828 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
829 "files": ["fourth", "second", "third"],
829 "files": ["fourth", "second", "third"],
830 "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
830 "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
831 "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"
831 "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"
832 }
832 }
833 ]
833 ]
834
834
835 honor --git but not format-breaking diffopts
835 honor --git but not format-breaking diffopts
836 $ hg --config diff.noprefix=True log --git -vpr . -Tjson
836 $ hg --config diff.noprefix=True log --git -vpr . -Tjson
837 [
837 [
838 {
838 {
839 "rev": 8,
839 "rev": 8,
840 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
840 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
841 "branch": "default",
841 "branch": "default",
842 "phase": "draft",
842 "phase": "draft",
843 "user": "test",
843 "user": "test",
844 "date": [1577872860, 0],
844 "date": [1577872860, 0],
845 "desc": "third",
845 "desc": "third",
846 "bookmarks": [],
846 "bookmarks": [],
847 "tags": ["tip"],
847 "tags": ["tip"],
848 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
848 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
849 "files": ["fourth", "second", "third"],
849 "files": ["fourth", "second", "third"],
850 "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n"
850 "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n"
851 }
851 }
852 ]
852 ]
853
853
854 $ hg log -T json
854 $ hg log -T json
855 [
855 [
856 {
856 {
857 "rev": 8,
857 "rev": 8,
858 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
858 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
859 "branch": "default",
859 "branch": "default",
860 "phase": "draft",
860 "phase": "draft",
861 "user": "test",
861 "user": "test",
862 "date": [1577872860, 0],
862 "date": [1577872860, 0],
863 "desc": "third",
863 "desc": "third",
864 "bookmarks": [],
864 "bookmarks": [],
865 "tags": ["tip"],
865 "tags": ["tip"],
866 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"]
866 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"]
867 },
867 },
868 {
868 {
869 "rev": 7,
869 "rev": 7,
870 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
870 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
871 "branch": "default",
871 "branch": "default",
872 "phase": "draft",
872 "phase": "draft",
873 "user": "User Name <user@hostname>",
873 "user": "User Name <user@hostname>",
874 "date": [1000000, 0],
874 "date": [1000000, 0],
875 "desc": "second",
875 "desc": "second",
876 "bookmarks": [],
876 "bookmarks": [],
877 "tags": [],
877 "tags": [],
878 "parents": ["0000000000000000000000000000000000000000"]
878 "parents": ["0000000000000000000000000000000000000000"]
879 },
879 },
880 {
880 {
881 "rev": 6,
881 "rev": 6,
882 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
882 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
883 "branch": "default",
883 "branch": "default",
884 "phase": "draft",
884 "phase": "draft",
885 "user": "person",
885 "user": "person",
886 "date": [1500001, 0],
886 "date": [1500001, 0],
887 "desc": "merge",
887 "desc": "merge",
888 "bookmarks": [],
888 "bookmarks": [],
889 "tags": [],
889 "tags": [],
890 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"]
890 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"]
891 },
891 },
892 {
892 {
893 "rev": 5,
893 "rev": 5,
894 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
894 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
895 "branch": "default",
895 "branch": "default",
896 "phase": "draft",
896 "phase": "draft",
897 "user": "person",
897 "user": "person",
898 "date": [1500000, 0],
898 "date": [1500000, 0],
899 "desc": "new head",
899 "desc": "new head",
900 "bookmarks": [],
900 "bookmarks": [],
901 "tags": [],
901 "tags": [],
902 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
902 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
903 },
903 },
904 {
904 {
905 "rev": 4,
905 "rev": 4,
906 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
906 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
907 "branch": "foo",
907 "branch": "foo",
908 "phase": "draft",
908 "phase": "draft",
909 "user": "person",
909 "user": "person",
910 "date": [1400000, 0],
910 "date": [1400000, 0],
911 "desc": "new branch",
911 "desc": "new branch",
912 "bookmarks": [],
912 "bookmarks": [],
913 "tags": [],
913 "tags": [],
914 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
914 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
915 },
915 },
916 {
916 {
917 "rev": 3,
917 "rev": 3,
918 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
918 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
919 "branch": "default",
919 "branch": "default",
920 "phase": "draft",
920 "phase": "draft",
921 "user": "person",
921 "user": "person",
922 "date": [1300000, 0],
922 "date": [1300000, 0],
923 "desc": "no user, no domain",
923 "desc": "no user, no domain",
924 "bookmarks": [],
924 "bookmarks": [],
925 "tags": [],
925 "tags": [],
926 "parents": ["97054abb4ab824450e9164180baf491ae0078465"]
926 "parents": ["97054abb4ab824450e9164180baf491ae0078465"]
927 },
927 },
928 {
928 {
929 "rev": 2,
929 "rev": 2,
930 "node": "97054abb4ab824450e9164180baf491ae0078465",
930 "node": "97054abb4ab824450e9164180baf491ae0078465",
931 "branch": "default",
931 "branch": "default",
932 "phase": "draft",
932 "phase": "draft",
933 "user": "other@place",
933 "user": "other@place",
934 "date": [1200000, 0],
934 "date": [1200000, 0],
935 "desc": "no person",
935 "desc": "no person",
936 "bookmarks": [],
936 "bookmarks": [],
937 "tags": [],
937 "tags": [],
938 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"]
938 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"]
939 },
939 },
940 {
940 {
941 "rev": 1,
941 "rev": 1,
942 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
942 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
943 "branch": "default",
943 "branch": "default",
944 "phase": "draft",
944 "phase": "draft",
945 "user": "A. N. Other <other@place>",
945 "user": "A. N. Other <other@place>",
946 "date": [1100000, 0],
946 "date": [1100000, 0],
947 "desc": "other 1\nother 2\n\nother 3",
947 "desc": "other 1\nother 2\n\nother 3",
948 "bookmarks": [],
948 "bookmarks": [],
949 "tags": [],
949 "tags": [],
950 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"]
950 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"]
951 },
951 },
952 {
952 {
953 "rev": 0,
953 "rev": 0,
954 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
954 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
955 "branch": "default",
955 "branch": "default",
956 "phase": "draft",
956 "phase": "draft",
957 "user": "User Name <user@hostname>",
957 "user": "User Name <user@hostname>",
958 "date": [1000000, 0],
958 "date": [1000000, 0],
959 "desc": "line 1\nline 2",
959 "desc": "line 1\nline 2",
960 "bookmarks": [],
960 "bookmarks": [],
961 "tags": [],
961 "tags": [],
962 "parents": ["0000000000000000000000000000000000000000"]
962 "parents": ["0000000000000000000000000000000000000000"]
963 }
963 }
964 ]
964 ]
965
965
966 $ hg heads -v -Tjson
966 $ hg heads -v -Tjson
967 [
967 [
968 {
968 {
969 "rev": 8,
969 "rev": 8,
970 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
970 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
971 "branch": "default",
971 "branch": "default",
972 "phase": "draft",
972 "phase": "draft",
973 "user": "test",
973 "user": "test",
974 "date": [1577872860, 0],
974 "date": [1577872860, 0],
975 "desc": "third",
975 "desc": "third",
976 "bookmarks": [],
976 "bookmarks": [],
977 "tags": ["tip"],
977 "tags": ["tip"],
978 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
978 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
979 "files": ["fourth", "second", "third"]
979 "files": ["fourth", "second", "third"]
980 },
980 },
981 {
981 {
982 "rev": 6,
982 "rev": 6,
983 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
983 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
984 "branch": "default",
984 "branch": "default",
985 "phase": "draft",
985 "phase": "draft",
986 "user": "person",
986 "user": "person",
987 "date": [1500001, 0],
987 "date": [1500001, 0],
988 "desc": "merge",
988 "desc": "merge",
989 "bookmarks": [],
989 "bookmarks": [],
990 "tags": [],
990 "tags": [],
991 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
991 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
992 "files": []
992 "files": []
993 },
993 },
994 {
994 {
995 "rev": 4,
995 "rev": 4,
996 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
996 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
997 "branch": "foo",
997 "branch": "foo",
998 "phase": "draft",
998 "phase": "draft",
999 "user": "person",
999 "user": "person",
1000 "date": [1400000, 0],
1000 "date": [1400000, 0],
1001 "desc": "new branch",
1001 "desc": "new branch",
1002 "bookmarks": [],
1002 "bookmarks": [],
1003 "tags": [],
1003 "tags": [],
1004 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1004 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1005 "files": []
1005 "files": []
1006 }
1006 }
1007 ]
1007 ]
1008
1008
1009 $ hg log --debug -Tjson
1009 $ hg log --debug -Tjson
1010 [
1010 [
1011 {
1011 {
1012 "rev": 8,
1012 "rev": 8,
1013 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
1013 "node": "95c24699272ef57d062b8bccc32c878bf841784a",
1014 "branch": "default",
1014 "branch": "default",
1015 "phase": "draft",
1015 "phase": "draft",
1016 "user": "test",
1016 "user": "test",
1017 "date": [1577872860, 0],
1017 "date": [1577872860, 0],
1018 "desc": "third",
1018 "desc": "third",
1019 "bookmarks": [],
1019 "bookmarks": [],
1020 "tags": ["tip"],
1020 "tags": ["tip"],
1021 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
1021 "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
1022 "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
1022 "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
1023 "extra": {"branch": "default"},
1023 "extra": {"branch": "default"},
1024 "modified": [],
1024 "modified": [],
1025 "added": ["fourth", "third"],
1025 "added": ["fourth", "third"],
1026 "removed": ["second"]
1026 "removed": ["second"]
1027 },
1027 },
1028 {
1028 {
1029 "rev": 7,
1029 "rev": 7,
1030 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
1030 "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
1031 "branch": "default",
1031 "branch": "default",
1032 "phase": "draft",
1032 "phase": "draft",
1033 "user": "User Name <user@hostname>",
1033 "user": "User Name <user@hostname>",
1034 "date": [1000000, 0],
1034 "date": [1000000, 0],
1035 "desc": "second",
1035 "desc": "second",
1036 "bookmarks": [],
1036 "bookmarks": [],
1037 "tags": [],
1037 "tags": [],
1038 "parents": ["0000000000000000000000000000000000000000"],
1038 "parents": ["0000000000000000000000000000000000000000"],
1039 "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
1039 "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
1040 "extra": {"branch": "default"},
1040 "extra": {"branch": "default"},
1041 "modified": [],
1041 "modified": [],
1042 "added": ["second"],
1042 "added": ["second"],
1043 "removed": []
1043 "removed": []
1044 },
1044 },
1045 {
1045 {
1046 "rev": 6,
1046 "rev": 6,
1047 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
1047 "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
1048 "branch": "default",
1048 "branch": "default",
1049 "phase": "draft",
1049 "phase": "draft",
1050 "user": "person",
1050 "user": "person",
1051 "date": [1500001, 0],
1051 "date": [1500001, 0],
1052 "desc": "merge",
1052 "desc": "merge",
1053 "bookmarks": [],
1053 "bookmarks": [],
1054 "tags": [],
1054 "tags": [],
1055 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
1055 "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
1056 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1056 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1057 "extra": {"branch": "default"},
1057 "extra": {"branch": "default"},
1058 "modified": [],
1058 "modified": [],
1059 "added": [],
1059 "added": [],
1060 "removed": []
1060 "removed": []
1061 },
1061 },
1062 {
1062 {
1063 "rev": 5,
1063 "rev": 5,
1064 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
1064 "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
1065 "branch": "default",
1065 "branch": "default",
1066 "phase": "draft",
1066 "phase": "draft",
1067 "user": "person",
1067 "user": "person",
1068 "date": [1500000, 0],
1068 "date": [1500000, 0],
1069 "desc": "new head",
1069 "desc": "new head",
1070 "bookmarks": [],
1070 "bookmarks": [],
1071 "tags": [],
1071 "tags": [],
1072 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1072 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1073 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1073 "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
1074 "extra": {"branch": "default"},
1074 "extra": {"branch": "default"},
1075 "modified": [],
1075 "modified": [],
1076 "added": ["d"],
1076 "added": ["d"],
1077 "removed": []
1077 "removed": []
1078 },
1078 },
1079 {
1079 {
1080 "rev": 4,
1080 "rev": 4,
1081 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
1081 "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
1082 "branch": "foo",
1082 "branch": "foo",
1083 "phase": "draft",
1083 "phase": "draft",
1084 "user": "person",
1084 "user": "person",
1085 "date": [1400000, 0],
1085 "date": [1400000, 0],
1086 "desc": "new branch",
1086 "desc": "new branch",
1087 "bookmarks": [],
1087 "bookmarks": [],
1088 "tags": [],
1088 "tags": [],
1089 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1089 "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
1090 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1090 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1091 "extra": {"branch": "foo"},
1091 "extra": {"branch": "foo"},
1092 "modified": [],
1092 "modified": [],
1093 "added": [],
1093 "added": [],
1094 "removed": []
1094 "removed": []
1095 },
1095 },
1096 {
1096 {
1097 "rev": 3,
1097 "rev": 3,
1098 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
1098 "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
1099 "branch": "default",
1099 "branch": "default",
1100 "phase": "draft",
1100 "phase": "draft",
1101 "user": "person",
1101 "user": "person",
1102 "date": [1300000, 0],
1102 "date": [1300000, 0],
1103 "desc": "no user, no domain",
1103 "desc": "no user, no domain",
1104 "bookmarks": [],
1104 "bookmarks": [],
1105 "tags": [],
1105 "tags": [],
1106 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
1106 "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
1107 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1107 "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
1108 "extra": {"branch": "default"},
1108 "extra": {"branch": "default"},
1109 "modified": ["c"],
1109 "modified": ["c"],
1110 "added": [],
1110 "added": [],
1111 "removed": []
1111 "removed": []
1112 },
1112 },
1113 {
1113 {
1114 "rev": 2,
1114 "rev": 2,
1115 "node": "97054abb4ab824450e9164180baf491ae0078465",
1115 "node": "97054abb4ab824450e9164180baf491ae0078465",
1116 "branch": "default",
1116 "branch": "default",
1117 "phase": "draft",
1117 "phase": "draft",
1118 "user": "other@place",
1118 "user": "other@place",
1119 "date": [1200000, 0],
1119 "date": [1200000, 0],
1120 "desc": "no person",
1120 "desc": "no person",
1121 "bookmarks": [],
1121 "bookmarks": [],
1122 "tags": [],
1122 "tags": [],
1123 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
1123 "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
1124 "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
1124 "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
1125 "extra": {"branch": "default"},
1125 "extra": {"branch": "default"},
1126 "modified": [],
1126 "modified": [],
1127 "added": ["c"],
1127 "added": ["c"],
1128 "removed": []
1128 "removed": []
1129 },
1129 },
1130 {
1130 {
1131 "rev": 1,
1131 "rev": 1,
1132 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
1132 "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
1133 "branch": "default",
1133 "branch": "default",
1134 "phase": "draft",
1134 "phase": "draft",
1135 "user": "A. N. Other <other@place>",
1135 "user": "A. N. Other <other@place>",
1136 "date": [1100000, 0],
1136 "date": [1100000, 0],
1137 "desc": "other 1\nother 2\n\nother 3",
1137 "desc": "other 1\nother 2\n\nother 3",
1138 "bookmarks": [],
1138 "bookmarks": [],
1139 "tags": [],
1139 "tags": [],
1140 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
1140 "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
1141 "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
1141 "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
1142 "extra": {"branch": "default"},
1142 "extra": {"branch": "default"},
1143 "modified": [],
1143 "modified": [],
1144 "added": ["b"],
1144 "added": ["b"],
1145 "removed": []
1145 "removed": []
1146 },
1146 },
1147 {
1147 {
1148 "rev": 0,
1148 "rev": 0,
1149 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
1149 "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
1150 "branch": "default",
1150 "branch": "default",
1151 "phase": "draft",
1151 "phase": "draft",
1152 "user": "User Name <user@hostname>",
1152 "user": "User Name <user@hostname>",
1153 "date": [1000000, 0],
1153 "date": [1000000, 0],
1154 "desc": "line 1\nline 2",
1154 "desc": "line 1\nline 2",
1155 "bookmarks": [],
1155 "bookmarks": [],
1156 "tags": [],
1156 "tags": [],
1157 "parents": ["0000000000000000000000000000000000000000"],
1157 "parents": ["0000000000000000000000000000000000000000"],
1158 "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
1158 "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
1159 "extra": {"branch": "default"},
1159 "extra": {"branch": "default"},
1160 "modified": [],
1160 "modified": [],
1161 "added": ["a"],
1161 "added": ["a"],
1162 "removed": []
1162 "removed": []
1163 }
1163 }
1164 ]
1164 ]
1165
1165
1166 Error if style not readable:
1166 Error if style not readable:
1167
1167
1168 #if unix-permissions no-root
1168 #if unix-permissions no-root
1169 $ touch q
1169 $ touch q
1170 $ chmod 0 q
1170 $ chmod 0 q
1171 $ hg log --style ./q
1171 $ hg log --style ./q
1172 abort: Permission denied: ./q
1172 abort: Permission denied: ./q
1173 [255]
1173 [255]
1174 #endif
1174 #endif
1175
1175
1176 Error if no style:
1176 Error if no style:
1177
1177
1178 $ hg log --style notexist
1178 $ hg log --style notexist
1179 abort: style 'notexist' not found
1179 abort: style 'notexist' not found
1180 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
1180 (available styles: bisect, changelog, compact, default, phases, show, status, xml)
1181 [255]
1181 [255]
1182
1182
1183 $ hg log -T list
1183 $ hg log -T list
1184 available styles: bisect, changelog, compact, default, phases, show, status, xml
1184 available styles: bisect, changelog, compact, default, phases, show, status, xml
1185 abort: specify a template
1185 abort: specify a template
1186 [255]
1186 [255]
1187
1187
1188 Error if style missing key:
1188 Error if style missing key:
1189
1189
1190 $ echo 'q = q' > t
1190 $ echo 'q = q' > t
1191 $ hg log --style ./t
1191 $ hg log --style ./t
1192 abort: "changeset" not in template map
1192 abort: "changeset" not in template map
1193 [255]
1193 [255]
1194
1194
1195 Error if style missing value:
1195 Error if style missing value:
1196
1196
1197 $ echo 'changeset =' > t
1197 $ echo 'changeset =' > t
1198 $ hg log --style t
1198 $ hg log --style t
1199 hg: parse error at t:1: missing value
1199 hg: parse error at t:1: missing value
1200 [255]
1200 [255]
1201
1201
1202 Error if include fails:
1202 Error if include fails:
1203
1203
1204 $ echo 'changeset = q' >> t
1204 $ echo 'changeset = q' >> t
1205 #if unix-permissions no-root
1205 #if unix-permissions no-root
1206 $ hg log --style ./t
1206 $ hg log --style ./t
1207 abort: template file ./q: Permission denied
1207 abort: template file ./q: Permission denied
1208 [255]
1208 [255]
1209 $ rm -f q
1209 $ rm -f q
1210 #endif
1210 #endif
1211
1211
1212 Include works:
1212 Include works:
1213
1213
1214 $ echo '{rev}' > q
1214 $ echo '{rev}' > q
1215 $ hg log --style ./t
1215 $ hg log --style ./t
1216 8
1216 8
1217 7
1217 7
1218 6
1218 6
1219 5
1219 5
1220 4
1220 4
1221 3
1221 3
1222 2
1222 2
1223 1
1223 1
1224 0
1224 0
1225
1225
1226 Check that recursive reference does not fall into RuntimeError (issue4758):
1226 Check that recursive reference does not fall into RuntimeError (issue4758):
1227
1227
1228 common mistake:
1228 common mistake:
1229
1229
1230 $ cat << EOF > issue4758
1230 $ cat << EOF > issue4758
1231 > changeset = '{changeset}\n'
1231 > changeset = '{changeset}\n'
1232 > EOF
1232 > EOF
1233 $ hg log --style ./issue4758
1233 $ hg log --style ./issue4758
1234 abort: recursive reference 'changeset' in template
1234 abort: recursive reference 'changeset' in template
1235 [255]
1235 [255]
1236
1236
1237 circular reference:
1237 circular reference:
1238
1238
1239 $ cat << EOF > issue4758
1239 $ cat << EOF > issue4758
1240 > changeset = '{foo}'
1240 > changeset = '{foo}'
1241 > foo = '{changeset}'
1241 > foo = '{changeset}'
1242 > EOF
1242 > EOF
1243 $ hg log --style ./issue4758
1243 $ hg log --style ./issue4758
1244 abort: recursive reference 'foo' in template
1244 abort: recursive reference 'foo' in template
1245 [255]
1245 [255]
1246
1246
1247 buildmap() -> gettemplate(), where no thunk was made:
1247 buildmap() -> gettemplate(), where no thunk was made:
1248
1248
1249 $ cat << EOF > issue4758
1249 $ cat << EOF > issue4758
1250 > changeset = '{files % changeset}\n'
1250 > changeset = '{files % changeset}\n'
1251 > EOF
1251 > EOF
1252 $ hg log --style ./issue4758
1252 $ hg log --style ./issue4758
1253 abort: recursive reference 'changeset' in template
1253 abort: recursive reference 'changeset' in template
1254 [255]
1254 [255]
1255
1255
1256 not a recursion if a keyword of the same name exists:
1256 not a recursion if a keyword of the same name exists:
1257
1257
1258 $ cat << EOF > issue4758
1258 $ cat << EOF > issue4758
1259 > changeset = '{tags % rev}'
1259 > changeset = '{tags % rev}'
1260 > rev = '{rev} {tag}\n'
1260 > rev = '{rev} {tag}\n'
1261 > EOF
1261 > EOF
1262 $ hg log --style ./issue4758 -r tip
1262 $ hg log --style ./issue4758 -r tip
1263 8 tip
1263 8 tip
1264
1264
1265 Check that {phase} works correctly on parents:
1265 Check that {phase} works correctly on parents:
1266
1266
1267 $ cat << EOF > parentphase
1267 $ cat << EOF > parentphase
1268 > changeset_debug = '{rev} ({phase}):{parents}\n'
1268 > changeset_debug = '{rev} ({phase}):{parents}\n'
1269 > parent = ' {rev} ({phase})'
1269 > parent = ' {rev} ({phase})'
1270 > EOF
1270 > EOF
1271 $ hg phase -r 5 --public
1271 $ hg phase -r 5 --public
1272 $ hg phase -r 7 --secret --force
1272 $ hg phase -r 7 --secret --force
1273 $ hg log --debug -G --style ./parentphase
1273 $ hg log --debug -G --style ./parentphase
1274 @ 8 (secret): 7 (secret) -1 (public)
1274 @ 8 (secret): 7 (secret) -1 (public)
1275 |
1275 |
1276 o 7 (secret): -1 (public) -1 (public)
1276 o 7 (secret): -1 (public) -1 (public)
1277
1277
1278 o 6 (draft): 5 (public) 4 (draft)
1278 o 6 (draft): 5 (public) 4 (draft)
1279 |\
1279 |\
1280 | o 5 (public): 3 (public) -1 (public)
1280 | o 5 (public): 3 (public) -1 (public)
1281 | |
1281 | |
1282 o | 4 (draft): 3 (public) -1 (public)
1282 o | 4 (draft): 3 (public) -1 (public)
1283 |/
1283 |/
1284 o 3 (public): 2 (public) -1 (public)
1284 o 3 (public): 2 (public) -1 (public)
1285 |
1285 |
1286 o 2 (public): 1 (public) -1 (public)
1286 o 2 (public): 1 (public) -1 (public)
1287 |
1287 |
1288 o 1 (public): 0 (public) -1 (public)
1288 o 1 (public): 0 (public) -1 (public)
1289 |
1289 |
1290 o 0 (public): -1 (public) -1 (public)
1290 o 0 (public): -1 (public) -1 (public)
1291
1291
1292
1292
1293 Missing non-standard names give no error (backward compatibility):
1293 Missing non-standard names give no error (backward compatibility):
1294
1294
1295 $ echo "changeset = '{c}'" > t
1295 $ echo "changeset = '{c}'" > t
1296 $ hg log --style ./t
1296 $ hg log --style ./t
1297
1297
1298 Defining non-standard name works:
1298 Defining non-standard name works:
1299
1299
1300 $ cat <<EOF > t
1300 $ cat <<EOF > t
1301 > changeset = '{c}'
1301 > changeset = '{c}'
1302 > c = q
1302 > c = q
1303 > EOF
1303 > EOF
1304 $ hg log --style ./t
1304 $ hg log --style ./t
1305 8
1305 8
1306 7
1306 7
1307 6
1307 6
1308 5
1308 5
1309 4
1309 4
1310 3
1310 3
1311 2
1311 2
1312 1
1312 1
1313 0
1313 0
1314
1314
1315 ui.style works:
1315 ui.style works:
1316
1316
1317 $ echo '[ui]' > .hg/hgrc
1317 $ echo '[ui]' > .hg/hgrc
1318 $ echo 'style = t' >> .hg/hgrc
1318 $ echo 'style = t' >> .hg/hgrc
1319 $ hg log
1319 $ hg log
1320 8
1320 8
1321 7
1321 7
1322 6
1322 6
1323 5
1323 5
1324 4
1324 4
1325 3
1325 3
1326 2
1326 2
1327 1
1327 1
1328 0
1328 0
1329
1329
1330
1330
1331 Issue338:
1331 Issue338:
1332
1332
1333 $ hg log --style=changelog > changelog
1333 $ hg log --style=changelog > changelog
1334
1334
1335 $ cat changelog
1335 $ cat changelog
1336 2020-01-01 test <test>
1336 2020-01-01 test <test>
1337
1337
1338 * fourth, second, third:
1338 * fourth, second, third:
1339 third
1339 third
1340 [95c24699272e] [tip]
1340 [95c24699272e] [tip]
1341
1341
1342 1970-01-12 User Name <user@hostname>
1342 1970-01-12 User Name <user@hostname>
1343
1343
1344 * second:
1344 * second:
1345 second
1345 second
1346 [29114dbae42b]
1346 [29114dbae42b]
1347
1347
1348 1970-01-18 person <person>
1348 1970-01-18 person <person>
1349
1349
1350 * merge
1350 * merge
1351 [d41e714fe50d]
1351 [d41e714fe50d]
1352
1352
1353 * d:
1353 * d:
1354 new head
1354 new head
1355 [13207e5a10d9]
1355 [13207e5a10d9]
1356
1356
1357 1970-01-17 person <person>
1357 1970-01-17 person <person>
1358
1358
1359 * new branch
1359 * new branch
1360 [bbe44766e73d] <foo>
1360 [bbe44766e73d] <foo>
1361
1361
1362 1970-01-16 person <person>
1362 1970-01-16 person <person>
1363
1363
1364 * c:
1364 * c:
1365 no user, no domain
1365 no user, no domain
1366 [10e46f2dcbf4]
1366 [10e46f2dcbf4]
1367
1367
1368 1970-01-14 other <other@place>
1368 1970-01-14 other <other@place>
1369
1369
1370 * c:
1370 * c:
1371 no person
1371 no person
1372 [97054abb4ab8]
1372 [97054abb4ab8]
1373
1373
1374 1970-01-13 A. N. Other <other@place>
1374 1970-01-13 A. N. Other <other@place>
1375
1375
1376 * b:
1376 * b:
1377 other 1 other 2
1377 other 1 other 2
1378
1378
1379 other 3
1379 other 3
1380 [b608e9d1a3f0]
1380 [b608e9d1a3f0]
1381
1381
1382 1970-01-12 User Name <user@hostname>
1382 1970-01-12 User Name <user@hostname>
1383
1383
1384 * a:
1384 * a:
1385 line 1 line 2
1385 line 1 line 2
1386 [1e4e1b8f71e0]
1386 [1e4e1b8f71e0]
1387
1387
1388
1388
1389 Issue2130: xml output for 'hg heads' is malformed
1389 Issue2130: xml output for 'hg heads' is malformed
1390
1390
1391 $ hg heads --style changelog
1391 $ hg heads --style changelog
1392 2020-01-01 test <test>
1392 2020-01-01 test <test>
1393
1393
1394 * fourth, second, third:
1394 * fourth, second, third:
1395 third
1395 third
1396 [95c24699272e] [tip]
1396 [95c24699272e] [tip]
1397
1397
1398 1970-01-18 person <person>
1398 1970-01-18 person <person>
1399
1399
1400 * merge
1400 * merge
1401 [d41e714fe50d]
1401 [d41e714fe50d]
1402
1402
1403 1970-01-17 person <person>
1403 1970-01-17 person <person>
1404
1404
1405 * new branch
1405 * new branch
1406 [bbe44766e73d] <foo>
1406 [bbe44766e73d] <foo>
1407
1407
1408
1408
1409 Keys work:
1409 Keys work:
1410
1410
1411 $ for key in author branch branches date desc file_adds file_dels file_mods \
1411 $ for key in author branch branches date desc file_adds file_dels file_mods \
1412 > file_copies file_copies_switch files \
1412 > file_copies file_copies_switch files \
1413 > manifest node parents rev tags diffstat extras \
1413 > manifest node parents rev tags diffstat extras \
1414 > p1rev p2rev p1node p2node; do
1414 > p1rev p2rev p1node p2node; do
1415 > for mode in '' --verbose --debug; do
1415 > for mode in '' --verbose --debug; do
1416 > hg log $mode --template "$key$mode: {$key}\n"
1416 > hg log $mode --template "$key$mode: {$key}\n"
1417 > done
1417 > done
1418 > done
1418 > done
1419 author: test
1419 author: test
1420 author: User Name <user@hostname>
1420 author: User Name <user@hostname>
1421 author: person
1421 author: person
1422 author: person
1422 author: person
1423 author: person
1423 author: person
1424 author: person
1424 author: person
1425 author: other@place
1425 author: other@place
1426 author: A. N. Other <other@place>
1426 author: A. N. Other <other@place>
1427 author: User Name <user@hostname>
1427 author: User Name <user@hostname>
1428 author--verbose: test
1428 author--verbose: test
1429 author--verbose: User Name <user@hostname>
1429 author--verbose: User Name <user@hostname>
1430 author--verbose: person
1430 author--verbose: person
1431 author--verbose: person
1431 author--verbose: person
1432 author--verbose: person
1432 author--verbose: person
1433 author--verbose: person
1433 author--verbose: person
1434 author--verbose: other@place
1434 author--verbose: other@place
1435 author--verbose: A. N. Other <other@place>
1435 author--verbose: A. N. Other <other@place>
1436 author--verbose: User Name <user@hostname>
1436 author--verbose: User Name <user@hostname>
1437 author--debug: test
1437 author--debug: test
1438 author--debug: User Name <user@hostname>
1438 author--debug: User Name <user@hostname>
1439 author--debug: person
1439 author--debug: person
1440 author--debug: person
1440 author--debug: person
1441 author--debug: person
1441 author--debug: person
1442 author--debug: person
1442 author--debug: person
1443 author--debug: other@place
1443 author--debug: other@place
1444 author--debug: A. N. Other <other@place>
1444 author--debug: A. N. Other <other@place>
1445 author--debug: User Name <user@hostname>
1445 author--debug: User Name <user@hostname>
1446 branch: default
1446 branch: default
1447 branch: default
1447 branch: default
1448 branch: default
1448 branch: default
1449 branch: default
1449 branch: default
1450 branch: foo
1450 branch: foo
1451 branch: default
1451 branch: default
1452 branch: default
1452 branch: default
1453 branch: default
1453 branch: default
1454 branch: default
1454 branch: default
1455 branch--verbose: default
1455 branch--verbose: default
1456 branch--verbose: default
1456 branch--verbose: default
1457 branch--verbose: default
1457 branch--verbose: default
1458 branch--verbose: default
1458 branch--verbose: default
1459 branch--verbose: foo
1459 branch--verbose: foo
1460 branch--verbose: default
1460 branch--verbose: default
1461 branch--verbose: default
1461 branch--verbose: default
1462 branch--verbose: default
1462 branch--verbose: default
1463 branch--verbose: default
1463 branch--verbose: default
1464 branch--debug: default
1464 branch--debug: default
1465 branch--debug: default
1465 branch--debug: default
1466 branch--debug: default
1466 branch--debug: default
1467 branch--debug: default
1467 branch--debug: default
1468 branch--debug: foo
1468 branch--debug: foo
1469 branch--debug: default
1469 branch--debug: default
1470 branch--debug: default
1470 branch--debug: default
1471 branch--debug: default
1471 branch--debug: default
1472 branch--debug: default
1472 branch--debug: default
1473 branches:
1473 branches:
1474 branches:
1474 branches:
1475 branches:
1475 branches:
1476 branches:
1476 branches:
1477 branches: foo
1477 branches: foo
1478 branches:
1478 branches:
1479 branches:
1479 branches:
1480 branches:
1480 branches:
1481 branches:
1481 branches:
1482 branches--verbose:
1482 branches--verbose:
1483 branches--verbose:
1483 branches--verbose:
1484 branches--verbose:
1484 branches--verbose:
1485 branches--verbose:
1485 branches--verbose:
1486 branches--verbose: foo
1486 branches--verbose: foo
1487 branches--verbose:
1487 branches--verbose:
1488 branches--verbose:
1488 branches--verbose:
1489 branches--verbose:
1489 branches--verbose:
1490 branches--verbose:
1490 branches--verbose:
1491 branches--debug:
1491 branches--debug:
1492 branches--debug:
1492 branches--debug:
1493 branches--debug:
1493 branches--debug:
1494 branches--debug:
1494 branches--debug:
1495 branches--debug: foo
1495 branches--debug: foo
1496 branches--debug:
1496 branches--debug:
1497 branches--debug:
1497 branches--debug:
1498 branches--debug:
1498 branches--debug:
1499 branches--debug:
1499 branches--debug:
1500 date: 1577872860.00
1500 date: 1577872860.00
1501 date: 1000000.00
1501 date: 1000000.00
1502 date: 1500001.00
1502 date: 1500001.00
1503 date: 1500000.00
1503 date: 1500000.00
1504 date: 1400000.00
1504 date: 1400000.00
1505 date: 1300000.00
1505 date: 1300000.00
1506 date: 1200000.00
1506 date: 1200000.00
1507 date: 1100000.00
1507 date: 1100000.00
1508 date: 1000000.00
1508 date: 1000000.00
1509 date--verbose: 1577872860.00
1509 date--verbose: 1577872860.00
1510 date--verbose: 1000000.00
1510 date--verbose: 1000000.00
1511 date--verbose: 1500001.00
1511 date--verbose: 1500001.00
1512 date--verbose: 1500000.00
1512 date--verbose: 1500000.00
1513 date--verbose: 1400000.00
1513 date--verbose: 1400000.00
1514 date--verbose: 1300000.00
1514 date--verbose: 1300000.00
1515 date--verbose: 1200000.00
1515 date--verbose: 1200000.00
1516 date--verbose: 1100000.00
1516 date--verbose: 1100000.00
1517 date--verbose: 1000000.00
1517 date--verbose: 1000000.00
1518 date--debug: 1577872860.00
1518 date--debug: 1577872860.00
1519 date--debug: 1000000.00
1519 date--debug: 1000000.00
1520 date--debug: 1500001.00
1520 date--debug: 1500001.00
1521 date--debug: 1500000.00
1521 date--debug: 1500000.00
1522 date--debug: 1400000.00
1522 date--debug: 1400000.00
1523 date--debug: 1300000.00
1523 date--debug: 1300000.00
1524 date--debug: 1200000.00
1524 date--debug: 1200000.00
1525 date--debug: 1100000.00
1525 date--debug: 1100000.00
1526 date--debug: 1000000.00
1526 date--debug: 1000000.00
1527 desc: third
1527 desc: third
1528 desc: second
1528 desc: second
1529 desc: merge
1529 desc: merge
1530 desc: new head
1530 desc: new head
1531 desc: new branch
1531 desc: new branch
1532 desc: no user, no domain
1532 desc: no user, no domain
1533 desc: no person
1533 desc: no person
1534 desc: other 1
1534 desc: other 1
1535 other 2
1535 other 2
1536
1536
1537 other 3
1537 other 3
1538 desc: line 1
1538 desc: line 1
1539 line 2
1539 line 2
1540 desc--verbose: third
1540 desc--verbose: third
1541 desc--verbose: second
1541 desc--verbose: second
1542 desc--verbose: merge
1542 desc--verbose: merge
1543 desc--verbose: new head
1543 desc--verbose: new head
1544 desc--verbose: new branch
1544 desc--verbose: new branch
1545 desc--verbose: no user, no domain
1545 desc--verbose: no user, no domain
1546 desc--verbose: no person
1546 desc--verbose: no person
1547 desc--verbose: other 1
1547 desc--verbose: other 1
1548 other 2
1548 other 2
1549
1549
1550 other 3
1550 other 3
1551 desc--verbose: line 1
1551 desc--verbose: line 1
1552 line 2
1552 line 2
1553 desc--debug: third
1553 desc--debug: third
1554 desc--debug: second
1554 desc--debug: second
1555 desc--debug: merge
1555 desc--debug: merge
1556 desc--debug: new head
1556 desc--debug: new head
1557 desc--debug: new branch
1557 desc--debug: new branch
1558 desc--debug: no user, no domain
1558 desc--debug: no user, no domain
1559 desc--debug: no person
1559 desc--debug: no person
1560 desc--debug: other 1
1560 desc--debug: other 1
1561 other 2
1561 other 2
1562
1562
1563 other 3
1563 other 3
1564 desc--debug: line 1
1564 desc--debug: line 1
1565 line 2
1565 line 2
1566 file_adds: fourth third
1566 file_adds: fourth third
1567 file_adds: second
1567 file_adds: second
1568 file_adds:
1568 file_adds:
1569 file_adds: d
1569 file_adds: d
1570 file_adds:
1570 file_adds:
1571 file_adds:
1571 file_adds:
1572 file_adds: c
1572 file_adds: c
1573 file_adds: b
1573 file_adds: b
1574 file_adds: a
1574 file_adds: a
1575 file_adds--verbose: fourth third
1575 file_adds--verbose: fourth third
1576 file_adds--verbose: second
1576 file_adds--verbose: second
1577 file_adds--verbose:
1577 file_adds--verbose:
1578 file_adds--verbose: d
1578 file_adds--verbose: d
1579 file_adds--verbose:
1579 file_adds--verbose:
1580 file_adds--verbose:
1580 file_adds--verbose:
1581 file_adds--verbose: c
1581 file_adds--verbose: c
1582 file_adds--verbose: b
1582 file_adds--verbose: b
1583 file_adds--verbose: a
1583 file_adds--verbose: a
1584 file_adds--debug: fourth third
1584 file_adds--debug: fourth third
1585 file_adds--debug: second
1585 file_adds--debug: second
1586 file_adds--debug:
1586 file_adds--debug:
1587 file_adds--debug: d
1587 file_adds--debug: d
1588 file_adds--debug:
1588 file_adds--debug:
1589 file_adds--debug:
1589 file_adds--debug:
1590 file_adds--debug: c
1590 file_adds--debug: c
1591 file_adds--debug: b
1591 file_adds--debug: b
1592 file_adds--debug: a
1592 file_adds--debug: a
1593 file_dels: second
1593 file_dels: second
1594 file_dels:
1594 file_dels:
1595 file_dels:
1595 file_dels:
1596 file_dels:
1596 file_dels:
1597 file_dels:
1597 file_dels:
1598 file_dels:
1598 file_dels:
1599 file_dels:
1599 file_dels:
1600 file_dels:
1600 file_dels:
1601 file_dels:
1601 file_dels:
1602 file_dels--verbose: second
1602 file_dels--verbose: second
1603 file_dels--verbose:
1603 file_dels--verbose:
1604 file_dels--verbose:
1604 file_dels--verbose:
1605 file_dels--verbose:
1605 file_dels--verbose:
1606 file_dels--verbose:
1606 file_dels--verbose:
1607 file_dels--verbose:
1607 file_dels--verbose:
1608 file_dels--verbose:
1608 file_dels--verbose:
1609 file_dels--verbose:
1609 file_dels--verbose:
1610 file_dels--verbose:
1610 file_dels--verbose:
1611 file_dels--debug: second
1611 file_dels--debug: second
1612 file_dels--debug:
1612 file_dels--debug:
1613 file_dels--debug:
1613 file_dels--debug:
1614 file_dels--debug:
1614 file_dels--debug:
1615 file_dels--debug:
1615 file_dels--debug:
1616 file_dels--debug:
1616 file_dels--debug:
1617 file_dels--debug:
1617 file_dels--debug:
1618 file_dels--debug:
1618 file_dels--debug:
1619 file_dels--debug:
1619 file_dels--debug:
1620 file_mods:
1620 file_mods:
1621 file_mods:
1621 file_mods:
1622 file_mods:
1622 file_mods:
1623 file_mods:
1623 file_mods:
1624 file_mods:
1624 file_mods:
1625 file_mods: c
1625 file_mods: c
1626 file_mods:
1626 file_mods:
1627 file_mods:
1627 file_mods:
1628 file_mods:
1628 file_mods:
1629 file_mods--verbose:
1629 file_mods--verbose:
1630 file_mods--verbose:
1630 file_mods--verbose:
1631 file_mods--verbose:
1631 file_mods--verbose:
1632 file_mods--verbose:
1632 file_mods--verbose:
1633 file_mods--verbose:
1633 file_mods--verbose:
1634 file_mods--verbose: c
1634 file_mods--verbose: c
1635 file_mods--verbose:
1635 file_mods--verbose:
1636 file_mods--verbose:
1636 file_mods--verbose:
1637 file_mods--verbose:
1637 file_mods--verbose:
1638 file_mods--debug:
1638 file_mods--debug:
1639 file_mods--debug:
1639 file_mods--debug:
1640 file_mods--debug:
1640 file_mods--debug:
1641 file_mods--debug:
1641 file_mods--debug:
1642 file_mods--debug:
1642 file_mods--debug:
1643 file_mods--debug: c
1643 file_mods--debug: c
1644 file_mods--debug:
1644 file_mods--debug:
1645 file_mods--debug:
1645 file_mods--debug:
1646 file_mods--debug:
1646 file_mods--debug:
1647 file_copies: fourth (second)
1647 file_copies: fourth (second)
1648 file_copies:
1648 file_copies:
1649 file_copies:
1649 file_copies:
1650 file_copies:
1650 file_copies:
1651 file_copies:
1651 file_copies:
1652 file_copies:
1652 file_copies:
1653 file_copies:
1653 file_copies:
1654 file_copies:
1654 file_copies:
1655 file_copies:
1655 file_copies:
1656 file_copies--verbose: fourth (second)
1656 file_copies--verbose: fourth (second)
1657 file_copies--verbose:
1657 file_copies--verbose:
1658 file_copies--verbose:
1658 file_copies--verbose:
1659 file_copies--verbose:
1659 file_copies--verbose:
1660 file_copies--verbose:
1660 file_copies--verbose:
1661 file_copies--verbose:
1661 file_copies--verbose:
1662 file_copies--verbose:
1662 file_copies--verbose:
1663 file_copies--verbose:
1663 file_copies--verbose:
1664 file_copies--verbose:
1664 file_copies--verbose:
1665 file_copies--debug: fourth (second)
1665 file_copies--debug: fourth (second)
1666 file_copies--debug:
1666 file_copies--debug:
1667 file_copies--debug:
1667 file_copies--debug:
1668 file_copies--debug:
1668 file_copies--debug:
1669 file_copies--debug:
1669 file_copies--debug:
1670 file_copies--debug:
1670 file_copies--debug:
1671 file_copies--debug:
1671 file_copies--debug:
1672 file_copies--debug:
1672 file_copies--debug:
1673 file_copies--debug:
1673 file_copies--debug:
1674 file_copies_switch:
1674 file_copies_switch:
1675 file_copies_switch:
1675 file_copies_switch:
1676 file_copies_switch:
1676 file_copies_switch:
1677 file_copies_switch:
1677 file_copies_switch:
1678 file_copies_switch:
1678 file_copies_switch:
1679 file_copies_switch:
1679 file_copies_switch:
1680 file_copies_switch:
1680 file_copies_switch:
1681 file_copies_switch:
1681 file_copies_switch:
1682 file_copies_switch:
1682 file_copies_switch:
1683 file_copies_switch--verbose:
1683 file_copies_switch--verbose:
1684 file_copies_switch--verbose:
1684 file_copies_switch--verbose:
1685 file_copies_switch--verbose:
1685 file_copies_switch--verbose:
1686 file_copies_switch--verbose:
1686 file_copies_switch--verbose:
1687 file_copies_switch--verbose:
1687 file_copies_switch--verbose:
1688 file_copies_switch--verbose:
1688 file_copies_switch--verbose:
1689 file_copies_switch--verbose:
1689 file_copies_switch--verbose:
1690 file_copies_switch--verbose:
1690 file_copies_switch--verbose:
1691 file_copies_switch--verbose:
1691 file_copies_switch--verbose:
1692 file_copies_switch--debug:
1692 file_copies_switch--debug:
1693 file_copies_switch--debug:
1693 file_copies_switch--debug:
1694 file_copies_switch--debug:
1694 file_copies_switch--debug:
1695 file_copies_switch--debug:
1695 file_copies_switch--debug:
1696 file_copies_switch--debug:
1696 file_copies_switch--debug:
1697 file_copies_switch--debug:
1697 file_copies_switch--debug:
1698 file_copies_switch--debug:
1698 file_copies_switch--debug:
1699 file_copies_switch--debug:
1699 file_copies_switch--debug:
1700 file_copies_switch--debug:
1700 file_copies_switch--debug:
1701 files: fourth second third
1701 files: fourth second third
1702 files: second
1702 files: second
1703 files:
1703 files:
1704 files: d
1704 files: d
1705 files:
1705 files:
1706 files: c
1706 files: c
1707 files: c
1707 files: c
1708 files: b
1708 files: b
1709 files: a
1709 files: a
1710 files--verbose: fourth second third
1710 files--verbose: fourth second third
1711 files--verbose: second
1711 files--verbose: second
1712 files--verbose:
1712 files--verbose:
1713 files--verbose: d
1713 files--verbose: d
1714 files--verbose:
1714 files--verbose:
1715 files--verbose: c
1715 files--verbose: c
1716 files--verbose: c
1716 files--verbose: c
1717 files--verbose: b
1717 files--verbose: b
1718 files--verbose: a
1718 files--verbose: a
1719 files--debug: fourth second third
1719 files--debug: fourth second third
1720 files--debug: second
1720 files--debug: second
1721 files--debug:
1721 files--debug:
1722 files--debug: d
1722 files--debug: d
1723 files--debug:
1723 files--debug:
1724 files--debug: c
1724 files--debug: c
1725 files--debug: c
1725 files--debug: c
1726 files--debug: b
1726 files--debug: b
1727 files--debug: a
1727 files--debug: a
1728 manifest: 6:94961b75a2da
1728 manifest: 6:94961b75a2da
1729 manifest: 5:f2dbc354b94e
1729 manifest: 5:f2dbc354b94e
1730 manifest: 4:4dc3def4f9b4
1730 manifest: 4:4dc3def4f9b4
1731 manifest: 4:4dc3def4f9b4
1731 manifest: 4:4dc3def4f9b4
1732 manifest: 3:cb5a1327723b
1732 manifest: 3:cb5a1327723b
1733 manifest: 3:cb5a1327723b
1733 manifest: 3:cb5a1327723b
1734 manifest: 2:6e0e82995c35
1734 manifest: 2:6e0e82995c35
1735 manifest: 1:4e8d705b1e53
1735 manifest: 1:4e8d705b1e53
1736 manifest: 0:a0c8bcbbb45c
1736 manifest: 0:a0c8bcbbb45c
1737 manifest--verbose: 6:94961b75a2da
1737 manifest--verbose: 6:94961b75a2da
1738 manifest--verbose: 5:f2dbc354b94e
1738 manifest--verbose: 5:f2dbc354b94e
1739 manifest--verbose: 4:4dc3def4f9b4
1739 manifest--verbose: 4:4dc3def4f9b4
1740 manifest--verbose: 4:4dc3def4f9b4
1740 manifest--verbose: 4:4dc3def4f9b4
1741 manifest--verbose: 3:cb5a1327723b
1741 manifest--verbose: 3:cb5a1327723b
1742 manifest--verbose: 3:cb5a1327723b
1742 manifest--verbose: 3:cb5a1327723b
1743 manifest--verbose: 2:6e0e82995c35
1743 manifest--verbose: 2:6e0e82995c35
1744 manifest--verbose: 1:4e8d705b1e53
1744 manifest--verbose: 1:4e8d705b1e53
1745 manifest--verbose: 0:a0c8bcbbb45c
1745 manifest--verbose: 0:a0c8bcbbb45c
1746 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
1746 manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
1747 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
1747 manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
1748 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1748 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1749 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1749 manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
1750 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1750 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1751 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1751 manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
1752 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
1752 manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
1753 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
1753 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
1754 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
1754 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
1755 node: 95c24699272ef57d062b8bccc32c878bf841784a
1755 node: 95c24699272ef57d062b8bccc32c878bf841784a
1756 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1756 node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1757 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1757 node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1758 node: 13207e5a10d9fd28ec424934298e176197f2c67f
1758 node: 13207e5a10d9fd28ec424934298e176197f2c67f
1759 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1759 node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1760 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1760 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1761 node: 97054abb4ab824450e9164180baf491ae0078465
1761 node: 97054abb4ab824450e9164180baf491ae0078465
1762 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1762 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1763 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1763 node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1764 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
1764 node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
1765 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1765 node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1766 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1766 node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1767 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1767 node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1768 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1768 node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1769 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1769 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1770 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1770 node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1771 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1771 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1772 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1772 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1773 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
1773 node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
1774 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1774 node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1775 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1775 node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
1776 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1776 node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1777 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1777 node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
1778 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1778 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1779 node--debug: 97054abb4ab824450e9164180baf491ae0078465
1779 node--debug: 97054abb4ab824450e9164180baf491ae0078465
1780 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1780 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1781 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1781 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1782 parents:
1782 parents:
1783 parents: -1:000000000000
1783 parents: -1:000000000000
1784 parents: 5:13207e5a10d9 4:bbe44766e73d
1784 parents: 5:13207e5a10d9 4:bbe44766e73d
1785 parents: 3:10e46f2dcbf4
1785 parents: 3:10e46f2dcbf4
1786 parents:
1786 parents:
1787 parents:
1787 parents:
1788 parents:
1788 parents:
1789 parents:
1789 parents:
1790 parents:
1790 parents:
1791 parents--verbose:
1791 parents--verbose:
1792 parents--verbose: -1:000000000000
1792 parents--verbose: -1:000000000000
1793 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
1793 parents--verbose: 5:13207e5a10d9 4:bbe44766e73d
1794 parents--verbose: 3:10e46f2dcbf4
1794 parents--verbose: 3:10e46f2dcbf4
1795 parents--verbose:
1795 parents--verbose:
1796 parents--verbose:
1796 parents--verbose:
1797 parents--verbose:
1797 parents--verbose:
1798 parents--verbose:
1798 parents--verbose:
1799 parents--verbose:
1799 parents--verbose:
1800 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
1800 parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000
1801 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1801 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1802 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1802 parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
1803 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1803 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1804 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1804 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000
1805 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
1805 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000
1806 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
1806 parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000
1807 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
1807 parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000
1808 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1808 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000
1809 rev: 8
1809 rev: 8
1810 rev: 7
1810 rev: 7
1811 rev: 6
1811 rev: 6
1812 rev: 5
1812 rev: 5
1813 rev: 4
1813 rev: 4
1814 rev: 3
1814 rev: 3
1815 rev: 2
1815 rev: 2
1816 rev: 1
1816 rev: 1
1817 rev: 0
1817 rev: 0
1818 rev--verbose: 8
1818 rev--verbose: 8
1819 rev--verbose: 7
1819 rev--verbose: 7
1820 rev--verbose: 6
1820 rev--verbose: 6
1821 rev--verbose: 5
1821 rev--verbose: 5
1822 rev--verbose: 4
1822 rev--verbose: 4
1823 rev--verbose: 3
1823 rev--verbose: 3
1824 rev--verbose: 2
1824 rev--verbose: 2
1825 rev--verbose: 1
1825 rev--verbose: 1
1826 rev--verbose: 0
1826 rev--verbose: 0
1827 rev--debug: 8
1827 rev--debug: 8
1828 rev--debug: 7
1828 rev--debug: 7
1829 rev--debug: 6
1829 rev--debug: 6
1830 rev--debug: 5
1830 rev--debug: 5
1831 rev--debug: 4
1831 rev--debug: 4
1832 rev--debug: 3
1832 rev--debug: 3
1833 rev--debug: 2
1833 rev--debug: 2
1834 rev--debug: 1
1834 rev--debug: 1
1835 rev--debug: 0
1835 rev--debug: 0
1836 tags: tip
1836 tags: tip
1837 tags:
1837 tags:
1838 tags:
1838 tags:
1839 tags:
1839 tags:
1840 tags:
1840 tags:
1841 tags:
1841 tags:
1842 tags:
1842 tags:
1843 tags:
1843 tags:
1844 tags:
1844 tags:
1845 tags--verbose: tip
1845 tags--verbose: tip
1846 tags--verbose:
1846 tags--verbose:
1847 tags--verbose:
1847 tags--verbose:
1848 tags--verbose:
1848 tags--verbose:
1849 tags--verbose:
1849 tags--verbose:
1850 tags--verbose:
1850 tags--verbose:
1851 tags--verbose:
1851 tags--verbose:
1852 tags--verbose:
1852 tags--verbose:
1853 tags--verbose:
1853 tags--verbose:
1854 tags--debug: tip
1854 tags--debug: tip
1855 tags--debug:
1855 tags--debug:
1856 tags--debug:
1856 tags--debug:
1857 tags--debug:
1857 tags--debug:
1858 tags--debug:
1858 tags--debug:
1859 tags--debug:
1859 tags--debug:
1860 tags--debug:
1860 tags--debug:
1861 tags--debug:
1861 tags--debug:
1862 tags--debug:
1862 tags--debug:
1863 diffstat: 3: +2/-1
1863 diffstat: 3: +2/-1
1864 diffstat: 1: +1/-0
1864 diffstat: 1: +1/-0
1865 diffstat: 0: +0/-0
1865 diffstat: 0: +0/-0
1866 diffstat: 1: +1/-0
1866 diffstat: 1: +1/-0
1867 diffstat: 0: +0/-0
1867 diffstat: 0: +0/-0
1868 diffstat: 1: +1/-0
1868 diffstat: 1: +1/-0
1869 diffstat: 1: +4/-0
1869 diffstat: 1: +4/-0
1870 diffstat: 1: +2/-0
1870 diffstat: 1: +2/-0
1871 diffstat: 1: +1/-0
1871 diffstat: 1: +1/-0
1872 diffstat--verbose: 3: +2/-1
1872 diffstat--verbose: 3: +2/-1
1873 diffstat--verbose: 1: +1/-0
1873 diffstat--verbose: 1: +1/-0
1874 diffstat--verbose: 0: +0/-0
1874 diffstat--verbose: 0: +0/-0
1875 diffstat--verbose: 1: +1/-0
1875 diffstat--verbose: 1: +1/-0
1876 diffstat--verbose: 0: +0/-0
1876 diffstat--verbose: 0: +0/-0
1877 diffstat--verbose: 1: +1/-0
1877 diffstat--verbose: 1: +1/-0
1878 diffstat--verbose: 1: +4/-0
1878 diffstat--verbose: 1: +4/-0
1879 diffstat--verbose: 1: +2/-0
1879 diffstat--verbose: 1: +2/-0
1880 diffstat--verbose: 1: +1/-0
1880 diffstat--verbose: 1: +1/-0
1881 diffstat--debug: 3: +2/-1
1881 diffstat--debug: 3: +2/-1
1882 diffstat--debug: 1: +1/-0
1882 diffstat--debug: 1: +1/-0
1883 diffstat--debug: 0: +0/-0
1883 diffstat--debug: 0: +0/-0
1884 diffstat--debug: 1: +1/-0
1884 diffstat--debug: 1: +1/-0
1885 diffstat--debug: 0: +0/-0
1885 diffstat--debug: 0: +0/-0
1886 diffstat--debug: 1: +1/-0
1886 diffstat--debug: 1: +1/-0
1887 diffstat--debug: 1: +4/-0
1887 diffstat--debug: 1: +4/-0
1888 diffstat--debug: 1: +2/-0
1888 diffstat--debug: 1: +2/-0
1889 diffstat--debug: 1: +1/-0
1889 diffstat--debug: 1: +1/-0
1890 extras: branch=default
1890 extras: branch=default
1891 extras: branch=default
1891 extras: branch=default
1892 extras: branch=default
1892 extras: branch=default
1893 extras: branch=default
1893 extras: branch=default
1894 extras: branch=foo
1894 extras: branch=foo
1895 extras: branch=default
1895 extras: branch=default
1896 extras: branch=default
1896 extras: branch=default
1897 extras: branch=default
1897 extras: branch=default
1898 extras: branch=default
1898 extras: branch=default
1899 extras--verbose: branch=default
1899 extras--verbose: branch=default
1900 extras--verbose: branch=default
1900 extras--verbose: branch=default
1901 extras--verbose: branch=default
1901 extras--verbose: branch=default
1902 extras--verbose: branch=default
1902 extras--verbose: branch=default
1903 extras--verbose: branch=foo
1903 extras--verbose: branch=foo
1904 extras--verbose: branch=default
1904 extras--verbose: branch=default
1905 extras--verbose: branch=default
1905 extras--verbose: branch=default
1906 extras--verbose: branch=default
1906 extras--verbose: branch=default
1907 extras--verbose: branch=default
1907 extras--verbose: branch=default
1908 extras--debug: branch=default
1908 extras--debug: branch=default
1909 extras--debug: branch=default
1909 extras--debug: branch=default
1910 extras--debug: branch=default
1910 extras--debug: branch=default
1911 extras--debug: branch=default
1911 extras--debug: branch=default
1912 extras--debug: branch=foo
1912 extras--debug: branch=foo
1913 extras--debug: branch=default
1913 extras--debug: branch=default
1914 extras--debug: branch=default
1914 extras--debug: branch=default
1915 extras--debug: branch=default
1915 extras--debug: branch=default
1916 extras--debug: branch=default
1916 extras--debug: branch=default
1917 p1rev: 7
1917 p1rev: 7
1918 p1rev: -1
1918 p1rev: -1
1919 p1rev: 5
1919 p1rev: 5
1920 p1rev: 3
1920 p1rev: 3
1921 p1rev: 3
1921 p1rev: 3
1922 p1rev: 2
1922 p1rev: 2
1923 p1rev: 1
1923 p1rev: 1
1924 p1rev: 0
1924 p1rev: 0
1925 p1rev: -1
1925 p1rev: -1
1926 p1rev--verbose: 7
1926 p1rev--verbose: 7
1927 p1rev--verbose: -1
1927 p1rev--verbose: -1
1928 p1rev--verbose: 5
1928 p1rev--verbose: 5
1929 p1rev--verbose: 3
1929 p1rev--verbose: 3
1930 p1rev--verbose: 3
1930 p1rev--verbose: 3
1931 p1rev--verbose: 2
1931 p1rev--verbose: 2
1932 p1rev--verbose: 1
1932 p1rev--verbose: 1
1933 p1rev--verbose: 0
1933 p1rev--verbose: 0
1934 p1rev--verbose: -1
1934 p1rev--verbose: -1
1935 p1rev--debug: 7
1935 p1rev--debug: 7
1936 p1rev--debug: -1
1936 p1rev--debug: -1
1937 p1rev--debug: 5
1937 p1rev--debug: 5
1938 p1rev--debug: 3
1938 p1rev--debug: 3
1939 p1rev--debug: 3
1939 p1rev--debug: 3
1940 p1rev--debug: 2
1940 p1rev--debug: 2
1941 p1rev--debug: 1
1941 p1rev--debug: 1
1942 p1rev--debug: 0
1942 p1rev--debug: 0
1943 p1rev--debug: -1
1943 p1rev--debug: -1
1944 p2rev: -1
1944 p2rev: -1
1945 p2rev: -1
1945 p2rev: -1
1946 p2rev: 4
1946 p2rev: 4
1947 p2rev: -1
1947 p2rev: -1
1948 p2rev: -1
1948 p2rev: -1
1949 p2rev: -1
1949 p2rev: -1
1950 p2rev: -1
1950 p2rev: -1
1951 p2rev: -1
1951 p2rev: -1
1952 p2rev: -1
1952 p2rev: -1
1953 p2rev--verbose: -1
1953 p2rev--verbose: -1
1954 p2rev--verbose: -1
1954 p2rev--verbose: -1
1955 p2rev--verbose: 4
1955 p2rev--verbose: 4
1956 p2rev--verbose: -1
1956 p2rev--verbose: -1
1957 p2rev--verbose: -1
1957 p2rev--verbose: -1
1958 p2rev--verbose: -1
1958 p2rev--verbose: -1
1959 p2rev--verbose: -1
1959 p2rev--verbose: -1
1960 p2rev--verbose: -1
1960 p2rev--verbose: -1
1961 p2rev--verbose: -1
1961 p2rev--verbose: -1
1962 p2rev--debug: -1
1962 p2rev--debug: -1
1963 p2rev--debug: -1
1963 p2rev--debug: -1
1964 p2rev--debug: 4
1964 p2rev--debug: 4
1965 p2rev--debug: -1
1965 p2rev--debug: -1
1966 p2rev--debug: -1
1966 p2rev--debug: -1
1967 p2rev--debug: -1
1967 p2rev--debug: -1
1968 p2rev--debug: -1
1968 p2rev--debug: -1
1969 p2rev--debug: -1
1969 p2rev--debug: -1
1970 p2rev--debug: -1
1970 p2rev--debug: -1
1971 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1971 p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1972 p1node: 0000000000000000000000000000000000000000
1972 p1node: 0000000000000000000000000000000000000000
1973 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
1973 p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
1974 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1974 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1975 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1975 p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1976 p1node: 97054abb4ab824450e9164180baf491ae0078465
1976 p1node: 97054abb4ab824450e9164180baf491ae0078465
1977 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1977 p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1978 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1978 p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
1979 p1node: 0000000000000000000000000000000000000000
1979 p1node: 0000000000000000000000000000000000000000
1980 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1980 p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1981 p1node--verbose: 0000000000000000000000000000000000000000
1981 p1node--verbose: 0000000000000000000000000000000000000000
1982 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1982 p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
1983 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1983 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1984 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1984 p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1985 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1985 p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
1986 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1986 p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1987 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1987 p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
1988 p1node--verbose: 0000000000000000000000000000000000000000
1988 p1node--verbose: 0000000000000000000000000000000000000000
1989 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1989 p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
1990 p1node--debug: 0000000000000000000000000000000000000000
1990 p1node--debug: 0000000000000000000000000000000000000000
1991 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1991 p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
1992 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1992 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1993 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1993 p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
1994 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
1994 p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
1995 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1995 p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
1996 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1996 p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
1997 p1node--debug: 0000000000000000000000000000000000000000
1997 p1node--debug: 0000000000000000000000000000000000000000
1998 p2node: 0000000000000000000000000000000000000000
1998 p2node: 0000000000000000000000000000000000000000
1999 p2node: 0000000000000000000000000000000000000000
1999 p2node: 0000000000000000000000000000000000000000
2000 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
2000 p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
2001 p2node: 0000000000000000000000000000000000000000
2001 p2node: 0000000000000000000000000000000000000000
2002 p2node: 0000000000000000000000000000000000000000
2002 p2node: 0000000000000000000000000000000000000000
2003 p2node: 0000000000000000000000000000000000000000
2003 p2node: 0000000000000000000000000000000000000000
2004 p2node: 0000000000000000000000000000000000000000
2004 p2node: 0000000000000000000000000000000000000000
2005 p2node: 0000000000000000000000000000000000000000
2005 p2node: 0000000000000000000000000000000000000000
2006 p2node: 0000000000000000000000000000000000000000
2006 p2node: 0000000000000000000000000000000000000000
2007 p2node--verbose: 0000000000000000000000000000000000000000
2007 p2node--verbose: 0000000000000000000000000000000000000000
2008 p2node--verbose: 0000000000000000000000000000000000000000
2008 p2node--verbose: 0000000000000000000000000000000000000000
2009 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
2009 p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
2010 p2node--verbose: 0000000000000000000000000000000000000000
2010 p2node--verbose: 0000000000000000000000000000000000000000
2011 p2node--verbose: 0000000000000000000000000000000000000000
2011 p2node--verbose: 0000000000000000000000000000000000000000
2012 p2node--verbose: 0000000000000000000000000000000000000000
2012 p2node--verbose: 0000000000000000000000000000000000000000
2013 p2node--verbose: 0000000000000000000000000000000000000000
2013 p2node--verbose: 0000000000000000000000000000000000000000
2014 p2node--verbose: 0000000000000000000000000000000000000000
2014 p2node--verbose: 0000000000000000000000000000000000000000
2015 p2node--verbose: 0000000000000000000000000000000000000000
2015 p2node--verbose: 0000000000000000000000000000000000000000
2016 p2node--debug: 0000000000000000000000000000000000000000
2016 p2node--debug: 0000000000000000000000000000000000000000
2017 p2node--debug: 0000000000000000000000000000000000000000
2017 p2node--debug: 0000000000000000000000000000000000000000
2018 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
2018 p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
2019 p2node--debug: 0000000000000000000000000000000000000000
2019 p2node--debug: 0000000000000000000000000000000000000000
2020 p2node--debug: 0000000000000000000000000000000000000000
2020 p2node--debug: 0000000000000000000000000000000000000000
2021 p2node--debug: 0000000000000000000000000000000000000000
2021 p2node--debug: 0000000000000000000000000000000000000000
2022 p2node--debug: 0000000000000000000000000000000000000000
2022 p2node--debug: 0000000000000000000000000000000000000000
2023 p2node--debug: 0000000000000000000000000000000000000000
2023 p2node--debug: 0000000000000000000000000000000000000000
2024 p2node--debug: 0000000000000000000000000000000000000000
2024 p2node--debug: 0000000000000000000000000000000000000000
2025
2025
2026 Filters work:
2026 Filters work:
2027
2027
2028 $ hg log --template '{author|domain}\n'
2028 $ hg log --template '{author|domain}\n'
2029
2029
2030 hostname
2030 hostname
2031
2031
2032
2032
2033
2033
2034
2034
2035 place
2035 place
2036 place
2036 place
2037 hostname
2037 hostname
2038
2038
2039 $ hg log --template '{author|person}\n'
2039 $ hg log --template '{author|person}\n'
2040 test
2040 test
2041 User Name
2041 User Name
2042 person
2042 person
2043 person
2043 person
2044 person
2044 person
2045 person
2045 person
2046 other
2046 other
2047 A. N. Other
2047 A. N. Other
2048 User Name
2048 User Name
2049
2049
2050 $ hg log --template '{author|user}\n'
2050 $ hg log --template '{author|user}\n'
2051 test
2051 test
2052 user
2052 user
2053 person
2053 person
2054 person
2054 person
2055 person
2055 person
2056 person
2056 person
2057 other
2057 other
2058 other
2058 other
2059 user
2059 user
2060
2060
2061 $ hg log --template '{date|date}\n'
2061 $ hg log --template '{date|date}\n'
2062 Wed Jan 01 10:01:00 2020 +0000
2062 Wed Jan 01 10:01:00 2020 +0000
2063 Mon Jan 12 13:46:40 1970 +0000
2063 Mon Jan 12 13:46:40 1970 +0000
2064 Sun Jan 18 08:40:01 1970 +0000
2064 Sun Jan 18 08:40:01 1970 +0000
2065 Sun Jan 18 08:40:00 1970 +0000
2065 Sun Jan 18 08:40:00 1970 +0000
2066 Sat Jan 17 04:53:20 1970 +0000
2066 Sat Jan 17 04:53:20 1970 +0000
2067 Fri Jan 16 01:06:40 1970 +0000
2067 Fri Jan 16 01:06:40 1970 +0000
2068 Wed Jan 14 21:20:00 1970 +0000
2068 Wed Jan 14 21:20:00 1970 +0000
2069 Tue Jan 13 17:33:20 1970 +0000
2069 Tue Jan 13 17:33:20 1970 +0000
2070 Mon Jan 12 13:46:40 1970 +0000
2070 Mon Jan 12 13:46:40 1970 +0000
2071
2071
2072 $ hg log --template '{date|isodate}\n'
2072 $ hg log --template '{date|isodate}\n'
2073 2020-01-01 10:01 +0000
2073 2020-01-01 10:01 +0000
2074 1970-01-12 13:46 +0000
2074 1970-01-12 13:46 +0000
2075 1970-01-18 08:40 +0000
2075 1970-01-18 08:40 +0000
2076 1970-01-18 08:40 +0000
2076 1970-01-18 08:40 +0000
2077 1970-01-17 04:53 +0000
2077 1970-01-17 04:53 +0000
2078 1970-01-16 01:06 +0000
2078 1970-01-16 01:06 +0000
2079 1970-01-14 21:20 +0000
2079 1970-01-14 21:20 +0000
2080 1970-01-13 17:33 +0000
2080 1970-01-13 17:33 +0000
2081 1970-01-12 13:46 +0000
2081 1970-01-12 13:46 +0000
2082
2082
2083 $ hg log --template '{date|isodatesec}\n'
2083 $ hg log --template '{date|isodatesec}\n'
2084 2020-01-01 10:01:00 +0000
2084 2020-01-01 10:01:00 +0000
2085 1970-01-12 13:46:40 +0000
2085 1970-01-12 13:46:40 +0000
2086 1970-01-18 08:40:01 +0000
2086 1970-01-18 08:40:01 +0000
2087 1970-01-18 08:40:00 +0000
2087 1970-01-18 08:40:00 +0000
2088 1970-01-17 04:53:20 +0000
2088 1970-01-17 04:53:20 +0000
2089 1970-01-16 01:06:40 +0000
2089 1970-01-16 01:06:40 +0000
2090 1970-01-14 21:20:00 +0000
2090 1970-01-14 21:20:00 +0000
2091 1970-01-13 17:33:20 +0000
2091 1970-01-13 17:33:20 +0000
2092 1970-01-12 13:46:40 +0000
2092 1970-01-12 13:46:40 +0000
2093
2093
2094 $ hg log --template '{date|rfc822date}\n'
2094 $ hg log --template '{date|rfc822date}\n'
2095 Wed, 01 Jan 2020 10:01:00 +0000
2095 Wed, 01 Jan 2020 10:01:00 +0000
2096 Mon, 12 Jan 1970 13:46:40 +0000
2096 Mon, 12 Jan 1970 13:46:40 +0000
2097 Sun, 18 Jan 1970 08:40:01 +0000
2097 Sun, 18 Jan 1970 08:40:01 +0000
2098 Sun, 18 Jan 1970 08:40:00 +0000
2098 Sun, 18 Jan 1970 08:40:00 +0000
2099 Sat, 17 Jan 1970 04:53:20 +0000
2099 Sat, 17 Jan 1970 04:53:20 +0000
2100 Fri, 16 Jan 1970 01:06:40 +0000
2100 Fri, 16 Jan 1970 01:06:40 +0000
2101 Wed, 14 Jan 1970 21:20:00 +0000
2101 Wed, 14 Jan 1970 21:20:00 +0000
2102 Tue, 13 Jan 1970 17:33:20 +0000
2102 Tue, 13 Jan 1970 17:33:20 +0000
2103 Mon, 12 Jan 1970 13:46:40 +0000
2103 Mon, 12 Jan 1970 13:46:40 +0000
2104
2104
2105 $ hg log --template '{desc|firstline}\n'
2105 $ hg log --template '{desc|firstline}\n'
2106 third
2106 third
2107 second
2107 second
2108 merge
2108 merge
2109 new head
2109 new head
2110 new branch
2110 new branch
2111 no user, no domain
2111 no user, no domain
2112 no person
2112 no person
2113 other 1
2113 other 1
2114 line 1
2114 line 1
2115
2115
2116 $ hg log --template '{node|short}\n'
2116 $ hg log --template '{node|short}\n'
2117 95c24699272e
2117 95c24699272e
2118 29114dbae42b
2118 29114dbae42b
2119 d41e714fe50d
2119 d41e714fe50d
2120 13207e5a10d9
2120 13207e5a10d9
2121 bbe44766e73d
2121 bbe44766e73d
2122 10e46f2dcbf4
2122 10e46f2dcbf4
2123 97054abb4ab8
2123 97054abb4ab8
2124 b608e9d1a3f0
2124 b608e9d1a3f0
2125 1e4e1b8f71e0
2125 1e4e1b8f71e0
2126
2126
2127 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
2127 $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
2128 <changeset author="test"/>
2128 <changeset author="test"/>
2129 <changeset author="User Name &lt;user@hostname&gt;"/>
2129 <changeset author="User Name &lt;user@hostname&gt;"/>
2130 <changeset author="person"/>
2130 <changeset author="person"/>
2131 <changeset author="person"/>
2131 <changeset author="person"/>
2132 <changeset author="person"/>
2132 <changeset author="person"/>
2133 <changeset author="person"/>
2133 <changeset author="person"/>
2134 <changeset author="other@place"/>
2134 <changeset author="other@place"/>
2135 <changeset author="A. N. Other &lt;other@place&gt;"/>
2135 <changeset author="A. N. Other &lt;other@place&gt;"/>
2136 <changeset author="User Name &lt;user@hostname&gt;"/>
2136 <changeset author="User Name &lt;user@hostname&gt;"/>
2137
2137
2138 $ hg log --template '{rev}: {children}\n'
2138 $ hg log --template '{rev}: {children}\n'
2139 8:
2139 8:
2140 7: 8:95c24699272e
2140 7: 8:95c24699272e
2141 6:
2141 6:
2142 5: 6:d41e714fe50d
2142 5: 6:d41e714fe50d
2143 4: 6:d41e714fe50d
2143 4: 6:d41e714fe50d
2144 3: 4:bbe44766e73d 5:13207e5a10d9
2144 3: 4:bbe44766e73d 5:13207e5a10d9
2145 2: 3:10e46f2dcbf4
2145 2: 3:10e46f2dcbf4
2146 1: 2:97054abb4ab8
2146 1: 2:97054abb4ab8
2147 0: 1:b608e9d1a3f0
2147 0: 1:b608e9d1a3f0
2148
2148
2149 Formatnode filter works:
2149 Formatnode filter works:
2150
2150
2151 $ hg -q log -r 0 --template '{node|formatnode}\n'
2151 $ hg -q log -r 0 --template '{node|formatnode}\n'
2152 1e4e1b8f71e0
2152 1e4e1b8f71e0
2153
2153
2154 $ hg log -r 0 --template '{node|formatnode}\n'
2154 $ hg log -r 0 --template '{node|formatnode}\n'
2155 1e4e1b8f71e0
2155 1e4e1b8f71e0
2156
2156
2157 $ hg -v log -r 0 --template '{node|formatnode}\n'
2157 $ hg -v log -r 0 --template '{node|formatnode}\n'
2158 1e4e1b8f71e0
2158 1e4e1b8f71e0
2159
2159
2160 $ hg --debug log -r 0 --template '{node|formatnode}\n'
2160 $ hg --debug log -r 0 --template '{node|formatnode}\n'
2161 1e4e1b8f71e05681d422154f5421e385fec3454f
2161 1e4e1b8f71e05681d422154f5421e385fec3454f
2162
2162
2163 Age filter:
2163 Age filter:
2164
2164
2165 $ hg init unstable-hash
2165 $ hg init unstable-hash
2166 $ cd unstable-hash
2166 $ cd unstable-hash
2167 $ hg log --template '{date|age}\n' > /dev/null || exit 1
2167 $ hg log --template '{date|age}\n' > /dev/null || exit 1
2168
2168
2169 >>> from datetime import datetime, timedelta
2169 >>> from datetime import datetime, timedelta
2170 >>> fp = open('a', 'w')
2170 >>> fp = open('a', 'w')
2171 >>> n = datetime.now() + timedelta(366 * 7)
2171 >>> n = datetime.now() + timedelta(366 * 7)
2172 >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
2172 >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
2173 >>> fp.close()
2173 >>> fp.close()
2174 $ hg add a
2174 $ hg add a
2175 $ hg commit -m future -d "`cat a`"
2175 $ hg commit -m future -d "`cat a`"
2176
2176
2177 $ hg log -l1 --template '{date|age}\n'
2177 $ hg log -l1 --template '{date|age}\n'
2178 7 years from now
2178 7 years from now
2179
2179
2180 $ cd ..
2180 $ cd ..
2181 $ rm -rf unstable-hash
2181 $ rm -rf unstable-hash
2182
2182
2183 Add a dummy commit to make up for the instability of the above:
2183 Add a dummy commit to make up for the instability of the above:
2184
2184
2185 $ echo a > a
2185 $ echo a > a
2186 $ hg add a
2186 $ hg add a
2187 $ hg ci -m future
2187 $ hg ci -m future
2188
2188
2189 Count filter:
2189 Count filter:
2190
2190
2191 $ hg log -l1 --template '{node|count} {node|short|count}\n'
2191 $ hg log -l1 --template '{node|count} {node|short|count}\n'
2192 40 12
2192 40 12
2193
2193
2194 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
2194 $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
2195 0 1 4
2195 0 1 4
2196
2196
2197 $ hg log -G --template '{rev}: children: {children|count}, \
2197 $ hg log -G --template '{rev}: children: {children|count}, \
2198 > tags: {tags|count}, file_adds: {file_adds|count}, \
2198 > tags: {tags|count}, file_adds: {file_adds|count}, \
2199 > ancestors: {revset("ancestors(%s)", rev)|count}'
2199 > ancestors: {revset("ancestors(%s)", rev)|count}'
2200 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
2200 @ 9: children: 0, tags: 1, file_adds: 1, ancestors: 3
2201 |
2201 |
2202 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
2202 o 8: children: 1, tags: 0, file_adds: 2, ancestors: 2
2203 |
2203 |
2204 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
2204 o 7: children: 1, tags: 0, file_adds: 1, ancestors: 1
2205
2205
2206 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
2206 o 6: children: 0, tags: 0, file_adds: 0, ancestors: 7
2207 |\
2207 |\
2208 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
2208 | o 5: children: 1, tags: 0, file_adds: 1, ancestors: 5
2209 | |
2209 | |
2210 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
2210 o | 4: children: 1, tags: 0, file_adds: 0, ancestors: 5
2211 |/
2211 |/
2212 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
2212 o 3: children: 2, tags: 0, file_adds: 0, ancestors: 4
2213 |
2213 |
2214 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
2214 o 2: children: 1, tags: 0, file_adds: 1, ancestors: 3
2215 |
2215 |
2216 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
2216 o 1: children: 1, tags: 0, file_adds: 1, ancestors: 2
2217 |
2217 |
2218 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
2218 o 0: children: 1, tags: 0, file_adds: 1, ancestors: 1
2219
2219
2220
2220
2221 Upper/lower filters:
2221 Upper/lower filters:
2222
2222
2223 $ hg log -r0 --template '{branch|upper}\n'
2223 $ hg log -r0 --template '{branch|upper}\n'
2224 DEFAULT
2224 DEFAULT
2225 $ hg log -r0 --template '{author|lower}\n'
2225 $ hg log -r0 --template '{author|lower}\n'
2226 user name <user@hostname>
2226 user name <user@hostname>
2227 $ hg log -r0 --template '{date|upper}\n'
2227 $ hg log -r0 --template '{date|upper}\n'
2228 abort: template filter 'upper' is not compatible with keyword 'date'
2228 abort: template filter 'upper' is not compatible with keyword 'date'
2229 [255]
2229 [255]
2230
2230
2231 Add a commit that does all possible modifications at once
2231 Add a commit that does all possible modifications at once
2232
2232
2233 $ echo modify >> third
2233 $ echo modify >> third
2234 $ touch b
2234 $ touch b
2235 $ hg add b
2235 $ hg add b
2236 $ hg mv fourth fifth
2236 $ hg mv fourth fifth
2237 $ hg rm a
2237 $ hg rm a
2238 $ hg ci -m "Modify, add, remove, rename"
2238 $ hg ci -m "Modify, add, remove, rename"
2239
2239
2240 Check the status template
2240 Check the status template
2241
2241
2242 $ cat <<EOF >> $HGRCPATH
2242 $ cat <<EOF >> $HGRCPATH
2243 > [extensions]
2243 > [extensions]
2244 > color=
2244 > color=
2245 > EOF
2245 > EOF
2246
2246
2247 $ hg log -T status -r 10
2247 $ hg log -T status -r 10
2248 changeset: 10:0f9759ec227a
2248 changeset: 10:0f9759ec227a
2249 tag: tip
2249 tag: tip
2250 user: test
2250 user: test
2251 date: Thu Jan 01 00:00:00 1970 +0000
2251 date: Thu Jan 01 00:00:00 1970 +0000
2252 summary: Modify, add, remove, rename
2252 summary: Modify, add, remove, rename
2253 files:
2253 files:
2254 M third
2254 M third
2255 A b
2255 A b
2256 A fifth
2256 A fifth
2257 R a
2257 R a
2258 R fourth
2258 R fourth
2259
2259
2260 $ hg log -T status -C -r 10
2260 $ hg log -T status -C -r 10
2261 changeset: 10:0f9759ec227a
2261 changeset: 10:0f9759ec227a
2262 tag: tip
2262 tag: tip
2263 user: test
2263 user: test
2264 date: Thu Jan 01 00:00:00 1970 +0000
2264 date: Thu Jan 01 00:00:00 1970 +0000
2265 summary: Modify, add, remove, rename
2265 summary: Modify, add, remove, rename
2266 files:
2266 files:
2267 M third
2267 M third
2268 A b
2268 A b
2269 A fifth
2269 A fifth
2270 fourth
2270 fourth
2271 R a
2271 R a
2272 R fourth
2272 R fourth
2273
2273
2274 $ hg log -T status -C -r 10 -v
2274 $ hg log -T status -C -r 10 -v
2275 changeset: 10:0f9759ec227a
2275 changeset: 10:0f9759ec227a
2276 tag: tip
2276 tag: tip
2277 user: test
2277 user: test
2278 date: Thu Jan 01 00:00:00 1970 +0000
2278 date: Thu Jan 01 00:00:00 1970 +0000
2279 description:
2279 description:
2280 Modify, add, remove, rename
2280 Modify, add, remove, rename
2281
2281
2282 files:
2282 files:
2283 M third
2283 M third
2284 A b
2284 A b
2285 A fifth
2285 A fifth
2286 fourth
2286 fourth
2287 R a
2287 R a
2288 R fourth
2288 R fourth
2289
2289
2290 $ hg log -T status -C -r 10 --debug
2290 $ hg log -T status -C -r 10 --debug
2291 changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c
2291 changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c
2292 tag: tip
2292 tag: tip
2293 phase: secret
2293 phase: secret
2294 parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066
2294 parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066
2295 parent: -1:0000000000000000000000000000000000000000
2295 parent: -1:0000000000000000000000000000000000000000
2296 manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567
2296 manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567
2297 user: test
2297 user: test
2298 date: Thu Jan 01 00:00:00 1970 +0000
2298 date: Thu Jan 01 00:00:00 1970 +0000
2299 extra: branch=default
2299 extra: branch=default
2300 description:
2300 description:
2301 Modify, add, remove, rename
2301 Modify, add, remove, rename
2302
2302
2303 files:
2303 files:
2304 M third
2304 M third
2305 A b
2305 A b
2306 A fifth
2306 A fifth
2307 fourth
2307 fourth
2308 R a
2308 R a
2309 R fourth
2309 R fourth
2310
2310
2311 $ hg log -T status -C -r 10 --quiet
2311 $ hg log -T status -C -r 10 --quiet
2312 10:0f9759ec227a
2312 10:0f9759ec227a
2313 $ hg --color=debug log -T status -r 10
2313 $ hg --color=debug log -T status -r 10
2314 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2314 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2315 [log.tag|tag: tip]
2315 [log.tag|tag: tip]
2316 [log.user|user: test]
2316 [log.user|user: test]
2317 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2317 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2318 [log.summary|summary: Modify, add, remove, rename]
2318 [log.summary|summary: Modify, add, remove, rename]
2319 [ui.note log.files|files:]
2319 [ui.note log.files|files:]
2320 [status.modified|M third]
2320 [status.modified|M third]
2321 [status.added|A b]
2321 [status.added|A b]
2322 [status.added|A fifth]
2322 [status.added|A fifth]
2323 [status.removed|R a]
2323 [status.removed|R a]
2324 [status.removed|R fourth]
2324 [status.removed|R fourth]
2325
2325
2326 $ hg --color=debug log -T status -C -r 10
2326 $ hg --color=debug log -T status -C -r 10
2327 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2327 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2328 [log.tag|tag: tip]
2328 [log.tag|tag: tip]
2329 [log.user|user: test]
2329 [log.user|user: test]
2330 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2330 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2331 [log.summary|summary: Modify, add, remove, rename]
2331 [log.summary|summary: Modify, add, remove, rename]
2332 [ui.note log.files|files:]
2332 [ui.note log.files|files:]
2333 [status.modified|M third]
2333 [status.modified|M third]
2334 [status.added|A b]
2334 [status.added|A b]
2335 [status.added|A fifth]
2335 [status.added|A fifth]
2336 [status.copied| fourth]
2336 [status.copied| fourth]
2337 [status.removed|R a]
2337 [status.removed|R a]
2338 [status.removed|R fourth]
2338 [status.removed|R fourth]
2339
2339
2340 $ hg --color=debug log -T status -C -r 10 -v
2340 $ hg --color=debug log -T status -C -r 10 -v
2341 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2341 [log.changeset changeset.secret|changeset: 10:0f9759ec227a]
2342 [log.tag|tag: tip]
2342 [log.tag|tag: tip]
2343 [log.user|user: test]
2343 [log.user|user: test]
2344 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2344 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2345 [ui.note log.description|description:]
2345 [ui.note log.description|description:]
2346 [ui.note log.description|Modify, add, remove, rename]
2346 [ui.note log.description|Modify, add, remove, rename]
2347
2347
2348 [ui.note log.files|files:]
2348 [ui.note log.files|files:]
2349 [status.modified|M third]
2349 [status.modified|M third]
2350 [status.added|A b]
2350 [status.added|A b]
2351 [status.added|A fifth]
2351 [status.added|A fifth]
2352 [status.copied| fourth]
2352 [status.copied| fourth]
2353 [status.removed|R a]
2353 [status.removed|R a]
2354 [status.removed|R fourth]
2354 [status.removed|R fourth]
2355
2355
2356 $ hg --color=debug log -T status -C -r 10 --debug
2356 $ hg --color=debug log -T status -C -r 10 --debug
2357 [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
2357 [log.changeset changeset.secret|changeset: 10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
2358 [log.tag|tag: tip]
2358 [log.tag|tag: tip]
2359 [log.phase|phase: secret]
2359 [log.phase|phase: secret]
2360 [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066]
2360 [log.parent changeset.secret|parent: 9:bf9dfba36635106d6a73ccc01e28b762da60e066]
2361 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2361 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2362 [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567]
2362 [ui.debug log.manifest|manifest: 8:89dd546f2de0a9d6d664f58d86097eb97baba567]
2363 [log.user|user: test]
2363 [log.user|user: test]
2364 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2364 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
2365 [ui.debug log.extra|extra: branch=default]
2365 [ui.debug log.extra|extra: branch=default]
2366 [ui.note log.description|description:]
2366 [ui.note log.description|description:]
2367 [ui.note log.description|Modify, add, remove, rename]
2367 [ui.note log.description|Modify, add, remove, rename]
2368
2368
2369 [ui.note log.files|files:]
2369 [ui.note log.files|files:]
2370 [status.modified|M third]
2370 [status.modified|M third]
2371 [status.added|A b]
2371 [status.added|A b]
2372 [status.added|A fifth]
2372 [status.added|A fifth]
2373 [status.copied| fourth]
2373 [status.copied| fourth]
2374 [status.removed|R a]
2374 [status.removed|R a]
2375 [status.removed|R fourth]
2375 [status.removed|R fourth]
2376
2376
2377 $ hg --color=debug log -T status -C -r 10 --quiet
2377 $ hg --color=debug log -T status -C -r 10 --quiet
2378 [log.node|10:0f9759ec227a]
2378 [log.node|10:0f9759ec227a]
2379
2379
2380 Check the bisect template
2380 Check the bisect template
2381
2381
2382 $ hg bisect -g 1
2382 $ hg bisect -g 1
2383 $ hg bisect -b 3 --noupdate
2383 $ hg bisect -b 3 --noupdate
2384 Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
2384 Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
2385 $ hg log -T bisect -r 0:4
2385 $ hg log -T bisect -r 0:4
2386 changeset: 0:1e4e1b8f71e0
2386 changeset: 0:1e4e1b8f71e0
2387 bisect: good (implicit)
2387 bisect: good (implicit)
2388 user: User Name <user@hostname>
2388 user: User Name <user@hostname>
2389 date: Mon Jan 12 13:46:40 1970 +0000
2389 date: Mon Jan 12 13:46:40 1970 +0000
2390 summary: line 1
2390 summary: line 1
2391
2391
2392 changeset: 1:b608e9d1a3f0
2392 changeset: 1:b608e9d1a3f0
2393 bisect: good
2393 bisect: good
2394 user: A. N. Other <other@place>
2394 user: A. N. Other <other@place>
2395 date: Tue Jan 13 17:33:20 1970 +0000
2395 date: Tue Jan 13 17:33:20 1970 +0000
2396 summary: other 1
2396 summary: other 1
2397
2397
2398 changeset: 2:97054abb4ab8
2398 changeset: 2:97054abb4ab8
2399 bisect: untested
2399 bisect: untested
2400 user: other@place
2400 user: other@place
2401 date: Wed Jan 14 21:20:00 1970 +0000
2401 date: Wed Jan 14 21:20:00 1970 +0000
2402 summary: no person
2402 summary: no person
2403
2403
2404 changeset: 3:10e46f2dcbf4
2404 changeset: 3:10e46f2dcbf4
2405 bisect: bad
2405 bisect: bad
2406 user: person
2406 user: person
2407 date: Fri Jan 16 01:06:40 1970 +0000
2407 date: Fri Jan 16 01:06:40 1970 +0000
2408 summary: no user, no domain
2408 summary: no user, no domain
2409
2409
2410 changeset: 4:bbe44766e73d
2410 changeset: 4:bbe44766e73d
2411 bisect: bad (implicit)
2411 bisect: bad (implicit)
2412 branch: foo
2412 branch: foo
2413 user: person
2413 user: person
2414 date: Sat Jan 17 04:53:20 1970 +0000
2414 date: Sat Jan 17 04:53:20 1970 +0000
2415 summary: new branch
2415 summary: new branch
2416
2416
2417 $ hg log --debug -T bisect -r 0:4
2417 $ hg log --debug -T bisect -r 0:4
2418 changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2418 changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2419 bisect: good (implicit)
2419 bisect: good (implicit)
2420 phase: public
2420 phase: public
2421 parent: -1:0000000000000000000000000000000000000000
2421 parent: -1:0000000000000000000000000000000000000000
2422 parent: -1:0000000000000000000000000000000000000000
2422 parent: -1:0000000000000000000000000000000000000000
2423 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
2423 manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
2424 user: User Name <user@hostname>
2424 user: User Name <user@hostname>
2425 date: Mon Jan 12 13:46:40 1970 +0000
2425 date: Mon Jan 12 13:46:40 1970 +0000
2426 files+: a
2426 files+: a
2427 extra: branch=default
2427 extra: branch=default
2428 description:
2428 description:
2429 line 1
2429 line 1
2430 line 2
2430 line 2
2431
2431
2432
2432
2433 changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2433 changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2434 bisect: good
2434 bisect: good
2435 phase: public
2435 phase: public
2436 parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2436 parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f
2437 parent: -1:0000000000000000000000000000000000000000
2437 parent: -1:0000000000000000000000000000000000000000
2438 manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
2438 manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
2439 user: A. N. Other <other@place>
2439 user: A. N. Other <other@place>
2440 date: Tue Jan 13 17:33:20 1970 +0000
2440 date: Tue Jan 13 17:33:20 1970 +0000
2441 files+: b
2441 files+: b
2442 extra: branch=default
2442 extra: branch=default
2443 description:
2443 description:
2444 other 1
2444 other 1
2445 other 2
2445 other 2
2446
2446
2447 other 3
2447 other 3
2448
2448
2449
2449
2450 changeset: 2:97054abb4ab824450e9164180baf491ae0078465
2450 changeset: 2:97054abb4ab824450e9164180baf491ae0078465
2451 bisect: untested
2451 bisect: untested
2452 phase: public
2452 phase: public
2453 parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2453 parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
2454 parent: -1:0000000000000000000000000000000000000000
2454 parent: -1:0000000000000000000000000000000000000000
2455 manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
2455 manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
2456 user: other@place
2456 user: other@place
2457 date: Wed Jan 14 21:20:00 1970 +0000
2457 date: Wed Jan 14 21:20:00 1970 +0000
2458 files+: c
2458 files+: c
2459 extra: branch=default
2459 extra: branch=default
2460 description:
2460 description:
2461 no person
2461 no person
2462
2462
2463
2463
2464 changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2464 changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2465 bisect: bad
2465 bisect: bad
2466 phase: public
2466 phase: public
2467 parent: 2:97054abb4ab824450e9164180baf491ae0078465
2467 parent: 2:97054abb4ab824450e9164180baf491ae0078465
2468 parent: -1:0000000000000000000000000000000000000000
2468 parent: -1:0000000000000000000000000000000000000000
2469 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2469 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2470 user: person
2470 user: person
2471 date: Fri Jan 16 01:06:40 1970 +0000
2471 date: Fri Jan 16 01:06:40 1970 +0000
2472 files: c
2472 files: c
2473 extra: branch=default
2473 extra: branch=default
2474 description:
2474 description:
2475 no user, no domain
2475 no user, no domain
2476
2476
2477
2477
2478 changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
2478 changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
2479 bisect: bad (implicit)
2479 bisect: bad (implicit)
2480 branch: foo
2480 branch: foo
2481 phase: draft
2481 phase: draft
2482 parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2482 parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47
2483 parent: -1:0000000000000000000000000000000000000000
2483 parent: -1:0000000000000000000000000000000000000000
2484 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2484 manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
2485 user: person
2485 user: person
2486 date: Sat Jan 17 04:53:20 1970 +0000
2486 date: Sat Jan 17 04:53:20 1970 +0000
2487 extra: branch=foo
2487 extra: branch=foo
2488 description:
2488 description:
2489 new branch
2489 new branch
2490
2490
2491
2491
2492 $ hg log -v -T bisect -r 0:4
2492 $ hg log -v -T bisect -r 0:4
2493 changeset: 0:1e4e1b8f71e0
2493 changeset: 0:1e4e1b8f71e0
2494 bisect: good (implicit)
2494 bisect: good (implicit)
2495 user: User Name <user@hostname>
2495 user: User Name <user@hostname>
2496 date: Mon Jan 12 13:46:40 1970 +0000
2496 date: Mon Jan 12 13:46:40 1970 +0000
2497 files: a
2497 files: a
2498 description:
2498 description:
2499 line 1
2499 line 1
2500 line 2
2500 line 2
2501
2501
2502
2502
2503 changeset: 1:b608e9d1a3f0
2503 changeset: 1:b608e9d1a3f0
2504 bisect: good
2504 bisect: good
2505 user: A. N. Other <other@place>
2505 user: A. N. Other <other@place>
2506 date: Tue Jan 13 17:33:20 1970 +0000
2506 date: Tue Jan 13 17:33:20 1970 +0000
2507 files: b
2507 files: b
2508 description:
2508 description:
2509 other 1
2509 other 1
2510 other 2
2510 other 2
2511
2511
2512 other 3
2512 other 3
2513
2513
2514
2514
2515 changeset: 2:97054abb4ab8
2515 changeset: 2:97054abb4ab8
2516 bisect: untested
2516 bisect: untested
2517 user: other@place
2517 user: other@place
2518 date: Wed Jan 14 21:20:00 1970 +0000
2518 date: Wed Jan 14 21:20:00 1970 +0000
2519 files: c
2519 files: c
2520 description:
2520 description:
2521 no person
2521 no person
2522
2522
2523
2523
2524 changeset: 3:10e46f2dcbf4
2524 changeset: 3:10e46f2dcbf4
2525 bisect: bad
2525 bisect: bad
2526 user: person
2526 user: person
2527 date: Fri Jan 16 01:06:40 1970 +0000
2527 date: Fri Jan 16 01:06:40 1970 +0000
2528 files: c
2528 files: c
2529 description:
2529 description:
2530 no user, no domain
2530 no user, no domain
2531
2531
2532
2532
2533 changeset: 4:bbe44766e73d
2533 changeset: 4:bbe44766e73d
2534 bisect: bad (implicit)
2534 bisect: bad (implicit)
2535 branch: foo
2535 branch: foo
2536 user: person
2536 user: person
2537 date: Sat Jan 17 04:53:20 1970 +0000
2537 date: Sat Jan 17 04:53:20 1970 +0000
2538 description:
2538 description:
2539 new branch
2539 new branch
2540
2540
2541
2541
2542 $ hg --color=debug log -T bisect -r 0:4
2542 $ hg --color=debug log -T bisect -r 0:4
2543 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2543 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2544 [log.bisect bisect.good|bisect: good (implicit)]
2544 [log.bisect bisect.good|bisect: good (implicit)]
2545 [log.user|user: User Name <user@hostname>]
2545 [log.user|user: User Name <user@hostname>]
2546 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2546 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2547 [log.summary|summary: line 1]
2547 [log.summary|summary: line 1]
2548
2548
2549 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2549 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2550 [log.bisect bisect.good|bisect: good]
2550 [log.bisect bisect.good|bisect: good]
2551 [log.user|user: A. N. Other <other@place>]
2551 [log.user|user: A. N. Other <other@place>]
2552 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2552 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2553 [log.summary|summary: other 1]
2553 [log.summary|summary: other 1]
2554
2554
2555 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2555 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2556 [log.bisect bisect.untested|bisect: untested]
2556 [log.bisect bisect.untested|bisect: untested]
2557 [log.user|user: other@place]
2557 [log.user|user: other@place]
2558 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2558 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2559 [log.summary|summary: no person]
2559 [log.summary|summary: no person]
2560
2560
2561 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2561 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2562 [log.bisect bisect.bad|bisect: bad]
2562 [log.bisect bisect.bad|bisect: bad]
2563 [log.user|user: person]
2563 [log.user|user: person]
2564 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2564 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2565 [log.summary|summary: no user, no domain]
2565 [log.summary|summary: no user, no domain]
2566
2566
2567 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2567 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2568 [log.bisect bisect.bad|bisect: bad (implicit)]
2568 [log.bisect bisect.bad|bisect: bad (implicit)]
2569 [log.branch|branch: foo]
2569 [log.branch|branch: foo]
2570 [log.user|user: person]
2570 [log.user|user: person]
2571 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2571 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2572 [log.summary|summary: new branch]
2572 [log.summary|summary: new branch]
2573
2573
2574 $ hg --color=debug log --debug -T bisect -r 0:4
2574 $ hg --color=debug log --debug -T bisect -r 0:4
2575 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2575 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2576 [log.bisect bisect.good|bisect: good (implicit)]
2576 [log.bisect bisect.good|bisect: good (implicit)]
2577 [log.phase|phase: public]
2577 [log.phase|phase: public]
2578 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2578 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2579 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2579 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2580 [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
2580 [ui.debug log.manifest|manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
2581 [log.user|user: User Name <user@hostname>]
2581 [log.user|user: User Name <user@hostname>]
2582 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2582 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2583 [ui.debug log.files|files+: a]
2583 [ui.debug log.files|files+: a]
2584 [ui.debug log.extra|extra: branch=default]
2584 [ui.debug log.extra|extra: branch=default]
2585 [ui.note log.description|description:]
2585 [ui.note log.description|description:]
2586 [ui.note log.description|line 1
2586 [ui.note log.description|line 1
2587 line 2]
2587 line 2]
2588
2588
2589
2589
2590 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2590 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2591 [log.bisect bisect.good|bisect: good]
2591 [log.bisect bisect.good|bisect: good]
2592 [log.phase|phase: public]
2592 [log.phase|phase: public]
2593 [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2593 [log.parent changeset.public|parent: 0:1e4e1b8f71e05681d422154f5421e385fec3454f]
2594 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2594 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2595 [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
2595 [ui.debug log.manifest|manifest: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
2596 [log.user|user: A. N. Other <other@place>]
2596 [log.user|user: A. N. Other <other@place>]
2597 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2597 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2598 [ui.debug log.files|files+: b]
2598 [ui.debug log.files|files+: b]
2599 [ui.debug log.extra|extra: branch=default]
2599 [ui.debug log.extra|extra: branch=default]
2600 [ui.note log.description|description:]
2600 [ui.note log.description|description:]
2601 [ui.note log.description|other 1
2601 [ui.note log.description|other 1
2602 other 2
2602 other 2
2603
2603
2604 other 3]
2604 other 3]
2605
2605
2606
2606
2607 [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465]
2607 [log.changeset changeset.public|changeset: 2:97054abb4ab824450e9164180baf491ae0078465]
2608 [log.bisect bisect.untested|bisect: untested]
2608 [log.bisect bisect.untested|bisect: untested]
2609 [log.phase|phase: public]
2609 [log.phase|phase: public]
2610 [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2610 [log.parent changeset.public|parent: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
2611 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2611 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2612 [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
2612 [ui.debug log.manifest|manifest: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
2613 [log.user|user: other@place]
2613 [log.user|user: other@place]
2614 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2614 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2615 [ui.debug log.files|files+: c]
2615 [ui.debug log.files|files+: c]
2616 [ui.debug log.extra|extra: branch=default]
2616 [ui.debug log.extra|extra: branch=default]
2617 [ui.note log.description|description:]
2617 [ui.note log.description|description:]
2618 [ui.note log.description|no person]
2618 [ui.note log.description|no person]
2619
2619
2620
2620
2621 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2621 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2622 [log.bisect bisect.bad|bisect: bad]
2622 [log.bisect bisect.bad|bisect: bad]
2623 [log.phase|phase: public]
2623 [log.phase|phase: public]
2624 [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465]
2624 [log.parent changeset.public|parent: 2:97054abb4ab824450e9164180baf491ae0078465]
2625 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2625 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2626 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2626 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2627 [log.user|user: person]
2627 [log.user|user: person]
2628 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2628 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2629 [ui.debug log.files|files: c]
2629 [ui.debug log.files|files: c]
2630 [ui.debug log.extra|extra: branch=default]
2630 [ui.debug log.extra|extra: branch=default]
2631 [ui.note log.description|description:]
2631 [ui.note log.description|description:]
2632 [ui.note log.description|no user, no domain]
2632 [ui.note log.description|no user, no domain]
2633
2633
2634
2634
2635 [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
2635 [log.changeset changeset.draft|changeset: 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
2636 [log.bisect bisect.bad|bisect: bad (implicit)]
2636 [log.bisect bisect.bad|bisect: bad (implicit)]
2637 [log.branch|branch: foo]
2637 [log.branch|branch: foo]
2638 [log.phase|phase: draft]
2638 [log.phase|phase: draft]
2639 [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2639 [log.parent changeset.public|parent: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
2640 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2640 [log.parent changeset.public|parent: -1:0000000000000000000000000000000000000000]
2641 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2641 [ui.debug log.manifest|manifest: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
2642 [log.user|user: person]
2642 [log.user|user: person]
2643 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2643 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2644 [ui.debug log.extra|extra: branch=foo]
2644 [ui.debug log.extra|extra: branch=foo]
2645 [ui.note log.description|description:]
2645 [ui.note log.description|description:]
2646 [ui.note log.description|new branch]
2646 [ui.note log.description|new branch]
2647
2647
2648
2648
2649 $ hg --color=debug log -v -T bisect -r 0:4
2649 $ hg --color=debug log -v -T bisect -r 0:4
2650 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2650 [log.changeset changeset.public|changeset: 0:1e4e1b8f71e0]
2651 [log.bisect bisect.good|bisect: good (implicit)]
2651 [log.bisect bisect.good|bisect: good (implicit)]
2652 [log.user|user: User Name <user@hostname>]
2652 [log.user|user: User Name <user@hostname>]
2653 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2653 [log.date|date: Mon Jan 12 13:46:40 1970 +0000]
2654 [ui.note log.files|files: a]
2654 [ui.note log.files|files: a]
2655 [ui.note log.description|description:]
2655 [ui.note log.description|description:]
2656 [ui.note log.description|line 1
2656 [ui.note log.description|line 1
2657 line 2]
2657 line 2]
2658
2658
2659
2659
2660 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2660 [log.changeset changeset.public|changeset: 1:b608e9d1a3f0]
2661 [log.bisect bisect.good|bisect: good]
2661 [log.bisect bisect.good|bisect: good]
2662 [log.user|user: A. N. Other <other@place>]
2662 [log.user|user: A. N. Other <other@place>]
2663 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2663 [log.date|date: Tue Jan 13 17:33:20 1970 +0000]
2664 [ui.note log.files|files: b]
2664 [ui.note log.files|files: b]
2665 [ui.note log.description|description:]
2665 [ui.note log.description|description:]
2666 [ui.note log.description|other 1
2666 [ui.note log.description|other 1
2667 other 2
2667 other 2
2668
2668
2669 other 3]
2669 other 3]
2670
2670
2671
2671
2672 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2672 [log.changeset changeset.public|changeset: 2:97054abb4ab8]
2673 [log.bisect bisect.untested|bisect: untested]
2673 [log.bisect bisect.untested|bisect: untested]
2674 [log.user|user: other@place]
2674 [log.user|user: other@place]
2675 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2675 [log.date|date: Wed Jan 14 21:20:00 1970 +0000]
2676 [ui.note log.files|files: c]
2676 [ui.note log.files|files: c]
2677 [ui.note log.description|description:]
2677 [ui.note log.description|description:]
2678 [ui.note log.description|no person]
2678 [ui.note log.description|no person]
2679
2679
2680
2680
2681 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2681 [log.changeset changeset.public|changeset: 3:10e46f2dcbf4]
2682 [log.bisect bisect.bad|bisect: bad]
2682 [log.bisect bisect.bad|bisect: bad]
2683 [log.user|user: person]
2683 [log.user|user: person]
2684 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2684 [log.date|date: Fri Jan 16 01:06:40 1970 +0000]
2685 [ui.note log.files|files: c]
2685 [ui.note log.files|files: c]
2686 [ui.note log.description|description:]
2686 [ui.note log.description|description:]
2687 [ui.note log.description|no user, no domain]
2687 [ui.note log.description|no user, no domain]
2688
2688
2689
2689
2690 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2690 [log.changeset changeset.draft|changeset: 4:bbe44766e73d]
2691 [log.bisect bisect.bad|bisect: bad (implicit)]
2691 [log.bisect bisect.bad|bisect: bad (implicit)]
2692 [log.branch|branch: foo]
2692 [log.branch|branch: foo]
2693 [log.user|user: person]
2693 [log.user|user: person]
2694 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2694 [log.date|date: Sat Jan 17 04:53:20 1970 +0000]
2695 [ui.note log.description|description:]
2695 [ui.note log.description|description:]
2696 [ui.note log.description|new branch]
2696 [ui.note log.description|new branch]
2697
2697
2698
2698
2699 $ hg bisect --reset
2699 $ hg bisect --reset
2700
2700
2701 Error on syntax:
2701 Error on syntax:
2702
2702
2703 $ echo 'x = "f' >> t
2703 $ echo 'x = "f' >> t
2704 $ hg log
2704 $ hg log
2705 hg: parse error at t:3: unmatched quotes
2705 hg: parse error at t:3: unmatched quotes
2706 [255]
2706 [255]
2707
2707
2708 $ hg log -T '{date'
2708 $ hg log -T '{date'
2709 hg: parse error at 1: unterminated template expansion
2709 hg: parse error at 1: unterminated template expansion
2710 [255]
2710 [255]
2711
2711
2712 Behind the scenes, this will throw TypeError
2712 Behind the scenes, this will throw TypeError
2713
2713
2714 $ hg log -l 3 --template '{date|obfuscate}\n'
2714 $ hg log -l 3 --template '{date|obfuscate}\n'
2715 abort: template filter 'obfuscate' is not compatible with keyword 'date'
2715 abort: template filter 'obfuscate' is not compatible with keyword 'date'
2716 [255]
2716 [255]
2717
2717
2718 Behind the scenes, this will throw a ValueError
2718 Behind the scenes, this will throw a ValueError
2719
2719
2720 $ hg log -l 3 --template 'line: {desc|shortdate}\n'
2720 $ hg log -l 3 --template 'line: {desc|shortdate}\n'
2721 abort: template filter 'shortdate' is not compatible with keyword 'desc'
2721 abort: template filter 'shortdate' is not compatible with keyword 'desc'
2722 [255]
2722 [255]
2723
2723
2724 Behind the scenes, this will throw AttributeError
2724 Behind the scenes, this will throw AttributeError
2725
2725
2726 $ hg log -l 3 --template 'line: {date|escape}\n'
2726 $ hg log -l 3 --template 'line: {date|escape}\n'
2727 abort: template filter 'escape' is not compatible with keyword 'date'
2727 abort: template filter 'escape' is not compatible with keyword 'date'
2728 [255]
2728 [255]
2729
2729
2730 $ hg log -l 3 --template 'line: {extras|localdate}\n'
2730 $ hg log -l 3 --template 'line: {extras|localdate}\n'
2731 hg: parse error: localdate expects a date information
2731 hg: parse error: localdate expects a date information
2732 [255]
2732 [255]
2733
2733
2734 Behind the scenes, this will throw ValueError
2734 Behind the scenes, this will throw ValueError
2735
2735
2736 $ hg tip --template '{author|email|date}\n'
2736 $ hg tip --template '{author|email|date}\n'
2737 hg: parse error: date expects a date information
2737 hg: parse error: date expects a date information
2738 [255]
2738 [255]
2739
2739
2740 $ hg tip -T '{author|email|shortdate}\n'
2740 $ hg tip -T '{author|email|shortdate}\n'
2741 abort: template filter 'shortdate' is not compatible with keyword 'author'
2741 abort: template filter 'shortdate' is not compatible with keyword 'author'
2742 [255]
2742 [255]
2743
2743
2744 $ hg tip -T '{get(extras, "branch")|shortdate}\n'
2744 $ hg tip -T '{get(extras, "branch")|shortdate}\n'
2745 abort: incompatible use of template filter 'shortdate'
2745 abort: incompatible use of template filter 'shortdate'
2746 [255]
2746 [255]
2747
2747
2748 Error in nested template:
2748 Error in nested template:
2749
2749
2750 $ hg log -T '{"date'
2750 $ hg log -T '{"date'
2751 hg: parse error at 2: unterminated string
2751 hg: parse error at 2: unterminated string
2752 [255]
2752 [255]
2753
2753
2754 $ hg log -T '{"foo{date|?}"}'
2754 $ hg log -T '{"foo{date|?}"}'
2755 hg: parse error at 11: syntax error
2755 hg: parse error at 11: syntax error
2756 [255]
2756 [255]
2757
2757
2758 Thrown an error if a template function doesn't exist
2758 Thrown an error if a template function doesn't exist
2759
2759
2760 $ hg tip --template '{foo()}\n'
2760 $ hg tip --template '{foo()}\n'
2761 hg: parse error: unknown function 'foo'
2761 hg: parse error: unknown function 'foo'
2762 [255]
2762 [255]
2763
2763
2764 Pass generator object created by template function to filter
2764 Pass generator object created by template function to filter
2765
2765
2766 $ hg log -l 1 --template '{if(author, author)|user}\n'
2766 $ hg log -l 1 --template '{if(author, author)|user}\n'
2767 test
2767 test
2768
2768
2769 Test index keyword:
2769 Test index keyword:
2770
2770
2771 $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
2771 $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
2772 10 0:a 1:b 2:fifth 3:fourth 4:third
2772 10 0:a 1:b 2:fifth 3:fourth 4:third
2773 11 0:a
2773 11 0:a
2774
2774
2775 $ hg branches -T '{index} {branch}\n'
2775 $ hg branches -T '{index} {branch}\n'
2776 0 default
2776 0 default
2777 1 foo
2777 1 foo
2778
2778
2779 Test diff function:
2779 Test diff function:
2780
2780
2781 $ hg diff -c 8
2781 $ hg diff -c 8
2782 diff -r 29114dbae42b -r 95c24699272e fourth
2782 diff -r 29114dbae42b -r 95c24699272e fourth
2783 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2783 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2784 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2784 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2785 @@ -0,0 +1,1 @@
2785 @@ -0,0 +1,1 @@
2786 +second
2786 +second
2787 diff -r 29114dbae42b -r 95c24699272e second
2787 diff -r 29114dbae42b -r 95c24699272e second
2788 --- a/second Mon Jan 12 13:46:40 1970 +0000
2788 --- a/second Mon Jan 12 13:46:40 1970 +0000
2789 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2789 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2790 @@ -1,1 +0,0 @@
2790 @@ -1,1 +0,0 @@
2791 -second
2791 -second
2792 diff -r 29114dbae42b -r 95c24699272e third
2792 diff -r 29114dbae42b -r 95c24699272e third
2793 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2793 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2794 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2794 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2795 @@ -0,0 +1,1 @@
2795 @@ -0,0 +1,1 @@
2796 +third
2796 +third
2797
2797
2798 $ hg log -r 8 -T "{diff()}"
2798 $ hg log -r 8 -T "{diff()}"
2799 diff -r 29114dbae42b -r 95c24699272e fourth
2799 diff -r 29114dbae42b -r 95c24699272e fourth
2800 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2800 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2801 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2801 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2802 @@ -0,0 +1,1 @@
2802 @@ -0,0 +1,1 @@
2803 +second
2803 +second
2804 diff -r 29114dbae42b -r 95c24699272e second
2804 diff -r 29114dbae42b -r 95c24699272e second
2805 --- a/second Mon Jan 12 13:46:40 1970 +0000
2805 --- a/second Mon Jan 12 13:46:40 1970 +0000
2806 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2806 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2807 @@ -1,1 +0,0 @@
2807 @@ -1,1 +0,0 @@
2808 -second
2808 -second
2809 diff -r 29114dbae42b -r 95c24699272e third
2809 diff -r 29114dbae42b -r 95c24699272e third
2810 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2810 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2811 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2811 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2812 @@ -0,0 +1,1 @@
2812 @@ -0,0 +1,1 @@
2813 +third
2813 +third
2814
2814
2815 $ hg log -r 8 -T "{diff('glob:f*')}"
2815 $ hg log -r 8 -T "{diff('glob:f*')}"
2816 diff -r 29114dbae42b -r 95c24699272e fourth
2816 diff -r 29114dbae42b -r 95c24699272e fourth
2817 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2817 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2818 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2818 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2819 @@ -0,0 +1,1 @@
2819 @@ -0,0 +1,1 @@
2820 +second
2820 +second
2821
2821
2822 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
2822 $ hg log -r 8 -T "{diff('', 'glob:f*')}"
2823 diff -r 29114dbae42b -r 95c24699272e second
2823 diff -r 29114dbae42b -r 95c24699272e second
2824 --- a/second Mon Jan 12 13:46:40 1970 +0000
2824 --- a/second Mon Jan 12 13:46:40 1970 +0000
2825 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2825 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2826 @@ -1,1 +0,0 @@
2826 @@ -1,1 +0,0 @@
2827 -second
2827 -second
2828 diff -r 29114dbae42b -r 95c24699272e third
2828 diff -r 29114dbae42b -r 95c24699272e third
2829 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2829 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2830 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2830 +++ b/third Wed Jan 01 10:01:00 2020 +0000
2831 @@ -0,0 +1,1 @@
2831 @@ -0,0 +1,1 @@
2832 +third
2832 +third
2833
2833
2834 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
2834 $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
2835 diff -r 29114dbae42b -r 95c24699272e fourth
2835 diff -r 29114dbae42b -r 95c24699272e fourth
2836 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2836 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2837 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2837 +++ b/fourth Wed Jan 01 10:01:00 2020 +0000
2838 @@ -0,0 +1,1 @@
2838 @@ -0,0 +1,1 @@
2839 +second
2839 +second
2840
2840
2841 $ cd ..
2841 $ cd ..
2842
2842
2843
2843
2844 latesttag:
2844 latesttag:
2845
2845
2846 $ hg init latesttag
2846 $ hg init latesttag
2847 $ cd latesttag
2847 $ cd latesttag
2848
2848
2849 $ echo a > file
2849 $ echo a > file
2850 $ hg ci -Am a -d '0 0'
2850 $ hg ci -Am a -d '0 0'
2851 adding file
2851 adding file
2852
2852
2853 $ echo b >> file
2853 $ echo b >> file
2854 $ hg ci -m b -d '1 0'
2854 $ hg ci -m b -d '1 0'
2855
2855
2856 $ echo c >> head1
2856 $ echo c >> head1
2857 $ hg ci -Am h1c -d '2 0'
2857 $ hg ci -Am h1c -d '2 0'
2858 adding head1
2858 adding head1
2859
2859
2860 $ hg update -q 1
2860 $ hg update -q 1
2861 $ echo d >> head2
2861 $ echo d >> head2
2862 $ hg ci -Am h2d -d '3 0'
2862 $ hg ci -Am h2d -d '3 0'
2863 adding head2
2863 adding head2
2864 created new head
2864 created new head
2865
2865
2866 $ echo e >> head2
2866 $ echo e >> head2
2867 $ hg ci -m h2e -d '4 0'
2867 $ hg ci -m h2e -d '4 0'
2868
2868
2869 $ hg merge -q
2869 $ hg merge -q
2870 $ hg ci -m merge -d '5 -3600'
2870 $ hg ci -m merge -d '5 -3600'
2871
2871
2872 No tag set:
2872 No tag set:
2873
2873
2874 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2874 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2875 5: null+5
2875 5: null+5
2876 4: null+4
2876 4: null+4
2877 3: null+3
2877 3: null+3
2878 2: null+3
2878 2: null+3
2879 1: null+2
2879 1: null+2
2880 0: null+1
2880 0: null+1
2881
2881
2882 One common tag: longest path wins:
2882 One common tag: longest path wins:
2883
2883
2884 $ hg tag -r 1 -m t1 -d '6 0' t1
2884 $ hg tag -r 1 -m t1 -d '6 0' t1
2885 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2885 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2886 6: t1+4
2886 6: t1+4
2887 5: t1+3
2887 5: t1+3
2888 4: t1+2
2888 4: t1+2
2889 3: t1+1
2889 3: t1+1
2890 2: t1+1
2890 2: t1+1
2891 1: t1+0
2891 1: t1+0
2892 0: null+1
2892 0: null+1
2893
2893
2894 One ancestor tag: more recent wins:
2894 One ancestor tag: more recent wins:
2895
2895
2896 $ hg tag -r 2 -m t2 -d '7 0' t2
2896 $ hg tag -r 2 -m t2 -d '7 0' t2
2897 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2897 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2898 7: t2+3
2898 7: t2+3
2899 6: t2+2
2899 6: t2+2
2900 5: t2+1
2900 5: t2+1
2901 4: t1+2
2901 4: t1+2
2902 3: t1+1
2902 3: t1+1
2903 2: t2+0
2903 2: t2+0
2904 1: t1+0
2904 1: t1+0
2905 0: null+1
2905 0: null+1
2906
2906
2907 Two branch tags: more recent wins:
2907 Two branch tags: more recent wins:
2908
2908
2909 $ hg tag -r 3 -m t3 -d '8 0' t3
2909 $ hg tag -r 3 -m t3 -d '8 0' t3
2910 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2910 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2911 8: t3+5
2911 8: t3+5
2912 7: t3+4
2912 7: t3+4
2913 6: t3+3
2913 6: t3+3
2914 5: t3+2
2914 5: t3+2
2915 4: t3+1
2915 4: t3+1
2916 3: t3+0
2916 3: t3+0
2917 2: t2+0
2917 2: t2+0
2918 1: t1+0
2918 1: t1+0
2919 0: null+1
2919 0: null+1
2920
2920
2921 Merged tag overrides:
2921 Merged tag overrides:
2922
2922
2923 $ hg tag -r 5 -m t5 -d '9 0' t5
2923 $ hg tag -r 5 -m t5 -d '9 0' t5
2924 $ hg tag -r 3 -m at3 -d '10 0' at3
2924 $ hg tag -r 3 -m at3 -d '10 0' at3
2925 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2925 $ hg log --template '{rev}: {latesttag}+{latesttagdistance}\n'
2926 10: t5+5
2926 10: t5+5
2927 9: t5+4
2927 9: t5+4
2928 8: t5+3
2928 8: t5+3
2929 7: t5+2
2929 7: t5+2
2930 6: t5+1
2930 6: t5+1
2931 5: t5+0
2931 5: t5+0
2932 4: at3:t3+1
2932 4: at3:t3+1
2933 3: at3:t3+0
2933 3: at3:t3+0
2934 2: t2+0
2934 2: t2+0
2935 1: t1+0
2935 1: t1+0
2936 0: null+1
2936 0: null+1
2937
2937
2938 $ hg log --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
2938 $ hg log --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
2939 10: t5+5,5
2939 10: t5+5,5
2940 9: t5+4,4
2940 9: t5+4,4
2941 8: t5+3,3
2941 8: t5+3,3
2942 7: t5+2,2
2942 7: t5+2,2
2943 6: t5+1,1
2943 6: t5+1,1
2944 5: t5+0,0
2944 5: t5+0,0
2945 4: at3+1,1 t3+1,1
2945 4: at3+1,1 t3+1,1
2946 3: at3+0,0 t3+0,0
2946 3: at3+0,0 t3+0,0
2947 2: t2+0,0
2947 2: t2+0,0
2948 1: t1+0,0
2948 1: t1+0,0
2949 0: null+1,1
2949 0: null+1,1
2950
2950
2951 $ hg log --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
2951 $ hg log --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
2952 10: t3, C: 8, D: 7
2952 10: t3, C: 8, D: 7
2953 9: t3, C: 7, D: 6
2953 9: t3, C: 7, D: 6
2954 8: t3, C: 6, D: 5
2954 8: t3, C: 6, D: 5
2955 7: t3, C: 5, D: 4
2955 7: t3, C: 5, D: 4
2956 6: t3, C: 4, D: 3
2956 6: t3, C: 4, D: 3
2957 5: t3, C: 3, D: 2
2957 5: t3, C: 3, D: 2
2958 4: t3, C: 1, D: 1
2958 4: t3, C: 1, D: 1
2959 3: t3, C: 0, D: 0
2959 3: t3, C: 0, D: 0
2960 2: t1, C: 1, D: 1
2960 2: t1, C: 1, D: 1
2961 1: t1, C: 0, D: 0
2961 1: t1, C: 0, D: 0
2962 0: null, C: 1, D: 1
2962 0: null, C: 1, D: 1
2963
2963
2964 $ cd ..
2964 $ cd ..
2965
2965
2966
2966
2967 Style path expansion: issue1948 - ui.style option doesn't work on OSX
2967 Style path expansion: issue1948 - ui.style option doesn't work on OSX
2968 if it is a relative path
2968 if it is a relative path
2969
2969
2970 $ mkdir -p home/styles
2970 $ mkdir -p home/styles
2971
2971
2972 $ cat > home/styles/teststyle <<EOF
2972 $ cat > home/styles/teststyle <<EOF
2973 > changeset = 'test {rev}:{node|short}\n'
2973 > changeset = 'test {rev}:{node|short}\n'
2974 > EOF
2974 > EOF
2975
2975
2976 $ HOME=`pwd`/home; export HOME
2976 $ HOME=`pwd`/home; export HOME
2977
2977
2978 $ cat > latesttag/.hg/hgrc <<EOF
2978 $ cat > latesttag/.hg/hgrc <<EOF
2979 > [ui]
2979 > [ui]
2980 > style = ~/styles/teststyle
2980 > style = ~/styles/teststyle
2981 > EOF
2981 > EOF
2982
2982
2983 $ hg -R latesttag tip
2983 $ hg -R latesttag tip
2984 test 10:9b4a630e5f5f
2984 test 10:9b4a630e5f5f
2985
2985
2986 Test recursive showlist template (issue1989):
2986 Test recursive showlist template (issue1989):
2987
2987
2988 $ cat > style1989 <<EOF
2988 $ cat > style1989 <<EOF
2989 > changeset = '{file_mods}{manifest}{extras}'
2989 > changeset = '{file_mods}{manifest}{extras}'
2990 > file_mod = 'M|{author|person}\n'
2990 > file_mod = 'M|{author|person}\n'
2991 > manifest = '{rev},{author}\n'
2991 > manifest = '{rev},{author}\n'
2992 > extra = '{key}: {author}\n'
2992 > extra = '{key}: {author}\n'
2993 > EOF
2993 > EOF
2994
2994
2995 $ hg -R latesttag log -r tip --style=style1989
2995 $ hg -R latesttag log -r tip --style=style1989
2996 M|test
2996 M|test
2997 10,test
2997 10,test
2998 branch: test
2998 branch: test
2999
2999
3000 Test new-style inline templating:
3000 Test new-style inline templating:
3001
3001
3002 $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
3002 $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
3003 modified files: .hgtags
3003 modified files: .hgtags
3004
3004
3005
3005
3006 $ hg log -R latesttag -r tip -T '{rev % "a"}\n'
3006 $ hg log -R latesttag -r tip -T '{rev % "a"}\n'
3007 hg: parse error: keyword 'rev' is not iterable
3007 hg: parse error: keyword 'rev' is not iterable
3008 [255]
3008 [255]
3009 $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "a"}\n'
3009 $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "a"}\n'
3010 hg: parse error: None is not iterable
3010 hg: parse error: None is not iterable
3011 [255]
3011 [255]
3012
3012
3013 Test the sub function of templating for expansion:
3013 Test the sub function of templating for expansion:
3014
3014
3015 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
3015 $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
3016 xx
3016 xx
3017
3017
3018 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
3018 $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
3019 hg: parse error: sub got an invalid pattern: [
3019 hg: parse error: sub got an invalid pattern: [
3020 [255]
3020 [255]
3021 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
3021 $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
3022 hg: parse error: sub got an invalid replacement: \1
3022 hg: parse error: sub got an invalid replacement: \1
3023 [255]
3023 [255]
3024
3024
3025 Test the strip function with chars specified:
3025 Test the strip function with chars specified:
3026
3026
3027 $ hg log -R latesttag --template '{desc}\n'
3027 $ hg log -R latesttag --template '{desc}\n'
3028 at3
3028 at3
3029 t5
3029 t5
3030 t3
3030 t3
3031 t2
3031 t2
3032 t1
3032 t1
3033 merge
3033 merge
3034 h2e
3034 h2e
3035 h2d
3035 h2d
3036 h1c
3036 h1c
3037 b
3037 b
3038 a
3038 a
3039
3039
3040 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
3040 $ hg log -R latesttag --template '{strip(desc, "te")}\n'
3041 at3
3041 at3
3042 5
3042 5
3043 3
3043 3
3044 2
3044 2
3045 1
3045 1
3046 merg
3046 merg
3047 h2
3047 h2
3048 h2d
3048 h2d
3049 h1c
3049 h1c
3050 b
3050 b
3051 a
3051 a
3052
3052
3053 Test date format:
3053 Test date format:
3054
3054
3055 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
3055 $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
3056 date: 70 01 01 10 +0000
3056 date: 70 01 01 10 +0000
3057 date: 70 01 01 09 +0000
3057 date: 70 01 01 09 +0000
3058 date: 70 01 01 08 +0000
3058 date: 70 01 01 08 +0000
3059 date: 70 01 01 07 +0000
3059 date: 70 01 01 07 +0000
3060 date: 70 01 01 06 +0000
3060 date: 70 01 01 06 +0000
3061 date: 70 01 01 05 +0100
3061 date: 70 01 01 05 +0100
3062 date: 70 01 01 04 +0000
3062 date: 70 01 01 04 +0000
3063 date: 70 01 01 03 +0000
3063 date: 70 01 01 03 +0000
3064 date: 70 01 01 02 +0000
3064 date: 70 01 01 02 +0000
3065 date: 70 01 01 01 +0000
3065 date: 70 01 01 01 +0000
3066 date: 70 01 01 00 +0000
3066 date: 70 01 01 00 +0000
3067
3067
3068 Test invalid date:
3068 Test invalid date:
3069
3069
3070 $ hg log -R latesttag -T '{date(rev)}\n'
3070 $ hg log -R latesttag -T '{date(rev)}\n'
3071 hg: parse error: date expects a date information
3071 hg: parse error: date expects a date information
3072 [255]
3072 [255]
3073
3073
3074 Test integer literal:
3074 Test integer literal:
3075
3075
3076 $ hg debugtemplate -v '{(0)}\n'
3076 $ hg debugtemplate -v '{(0)}\n'
3077 (template
3077 (template
3078 (group
3078 (group
3079 ('integer', '0'))
3079 ('integer', '0'))
3080 ('string', '\n'))
3080 ('string', '\n'))
3081 0
3081 0
3082 $ hg debugtemplate -v '{(123)}\n'
3082 $ hg debugtemplate -v '{(123)}\n'
3083 (template
3083 (template
3084 (group
3084 (group
3085 ('integer', '123'))
3085 ('integer', '123'))
3086 ('string', '\n'))
3086 ('string', '\n'))
3087 123
3087 123
3088 $ hg debugtemplate -v '{(-4)}\n'
3088 $ hg debugtemplate -v '{(-4)}\n'
3089 (template
3089 (template
3090 (group
3090 (group
3091 (negate
3091 (negate
3092 ('integer', '4')))
3092 ('integer', '4')))
3093 ('string', '\n'))
3093 ('string', '\n'))
3094 -4
3094 -4
3095 $ hg debugtemplate '{(-)}\n'
3095 $ hg debugtemplate '{(-)}\n'
3096 hg: parse error at 3: not a prefix: )
3096 hg: parse error at 3: not a prefix: )
3097 [255]
3097 [255]
3098 $ hg debugtemplate '{(-a)}\n'
3098 $ hg debugtemplate '{(-a)}\n'
3099 hg: parse error: negation needs an integer argument
3099 hg: parse error: negation needs an integer argument
3100 [255]
3100 [255]
3101
3101
3102 top-level integer literal is interpreted as symbol (i.e. variable name):
3102 top-level integer literal is interpreted as symbol (i.e. variable name):
3103
3103
3104 $ hg debugtemplate -D 1=one -v '{1}\n'
3104 $ hg debugtemplate -D 1=one -v '{1}\n'
3105 (template
3105 (template
3106 ('integer', '1')
3106 ('integer', '1')
3107 ('string', '\n'))
3107 ('string', '\n'))
3108 one
3108 one
3109 $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
3109 $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
3110 (template
3110 (template
3111 (func
3111 (func
3112 ('symbol', 'if')
3112 ('symbol', 'if')
3113 (list
3113 (list
3114 ('string', 't')
3114 ('string', 't')
3115 (template
3115 (template
3116 ('integer', '1'))))
3116 ('integer', '1'))))
3117 ('string', '\n'))
3117 ('string', '\n'))
3118 one
3118 one
3119 $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
3119 $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
3120 (template
3120 (template
3121 (|
3121 (|
3122 ('integer', '1')
3122 ('integer', '1')
3123 ('symbol', 'stringify'))
3123 ('symbol', 'stringify'))
3124 ('string', '\n'))
3124 ('string', '\n'))
3125 one
3125 one
3126
3126
3127 unless explicit symbol is expected:
3127 unless explicit symbol is expected:
3128
3128
3129 $ hg log -Ra -r0 -T '{desc|1}\n'
3129 $ hg log -Ra -r0 -T '{desc|1}\n'
3130 hg: parse error: expected a symbol, got 'integer'
3130 hg: parse error: expected a symbol, got 'integer'
3131 [255]
3131 [255]
3132 $ hg log -Ra -r0 -T '{1()}\n'
3132 $ hg log -Ra -r0 -T '{1()}\n'
3133 hg: parse error: expected a symbol, got 'integer'
3133 hg: parse error: expected a symbol, got 'integer'
3134 [255]
3134 [255]
3135
3135
3136 Test string literal:
3136 Test string literal:
3137
3137
3138 $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
3138 $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
3139 (template
3139 (template
3140 ('string', 'string with no template fragment')
3140 ('string', 'string with no template fragment')
3141 ('string', '\n'))
3141 ('string', '\n'))
3142 string with no template fragment
3142 string with no template fragment
3143 $ hg debugtemplate -Ra -r0 -v '{"template: {rev}"}\n'
3143 $ hg debugtemplate -Ra -r0 -v '{"template: {rev}"}\n'
3144 (template
3144 (template
3145 (template
3145 (template
3146 ('string', 'template: ')
3146 ('string', 'template: ')
3147 ('symbol', 'rev'))
3147 ('symbol', 'rev'))
3148 ('string', '\n'))
3148 ('string', '\n'))
3149 template: 0
3149 template: 0
3150 $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
3150 $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
3151 (template
3151 (template
3152 ('string', 'rawstring: {rev}')
3152 ('string', 'rawstring: {rev}')
3153 ('string', '\n'))
3153 ('string', '\n'))
3154 rawstring: {rev}
3154 rawstring: {rev}
3155 $ hg debugtemplate -Ra -r0 -v '{files % r"rawstring: {file}"}\n'
3155 $ hg debugtemplate -Ra -r0 -v '{files % r"rawstring: {file}"}\n'
3156 (template
3156 (template
3157 (%
3157 (%
3158 ('symbol', 'files')
3158 ('symbol', 'files')
3159 ('string', 'rawstring: {file}'))
3159 ('string', 'rawstring: {file}'))
3160 ('string', '\n'))
3160 ('string', '\n'))
3161 rawstring: {file}
3161 rawstring: {file}
3162
3162
3163 Test string escaping:
3163 Test string escaping:
3164
3164
3165 $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3165 $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3166 >
3166 >
3167 <>\n<[>
3167 <>\n<[>
3168 <>\n<]>
3168 <>\n<]>
3169 <>\n<
3169 <>\n<
3170
3170
3171 $ hg log -R latesttag -r 0 \
3171 $ hg log -R latesttag -r 0 \
3172 > --config ui.logtemplate='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3172 > --config ui.logtemplate='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3173 >
3173 >
3174 <>\n<[>
3174 <>\n<[>
3175 <>\n<]>
3175 <>\n<]>
3176 <>\n<
3176 <>\n<
3177
3177
3178 $ hg log -R latesttag -r 0 -T esc \
3178 $ hg log -R latesttag -r 0 -T esc \
3179 > --config templates.esc='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3179 > --config templates.esc='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3180 >
3180 >
3181 <>\n<[>
3181 <>\n<[>
3182 <>\n<]>
3182 <>\n<]>
3183 <>\n<
3183 <>\n<
3184
3184
3185 $ cat <<'EOF' > esctmpl
3185 $ cat <<'EOF' > esctmpl
3186 > changeset = '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3186 > changeset = '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
3187 > EOF
3187 > EOF
3188 $ hg log -R latesttag -r 0 --style ./esctmpl
3188 $ hg log -R latesttag -r 0 --style ./esctmpl
3189 >
3189 >
3190 <>\n<[>
3190 <>\n<[>
3191 <>\n<]>
3191 <>\n<]>
3192 <>\n<
3192 <>\n<
3193
3193
3194 Test string escaping of quotes:
3194 Test string escaping of quotes:
3195
3195
3196 $ hg log -Ra -r0 -T '{"\""}\n'
3196 $ hg log -Ra -r0 -T '{"\""}\n'
3197 "
3197 "
3198 $ hg log -Ra -r0 -T '{"\\\""}\n'
3198 $ hg log -Ra -r0 -T '{"\\\""}\n'
3199 \"
3199 \"
3200 $ hg log -Ra -r0 -T '{r"\""}\n'
3200 $ hg log -Ra -r0 -T '{r"\""}\n'
3201 \"
3201 \"
3202 $ hg log -Ra -r0 -T '{r"\\\""}\n'
3202 $ hg log -Ra -r0 -T '{r"\\\""}\n'
3203 \\\"
3203 \\\"
3204
3204
3205
3205
3206 $ hg log -Ra -r0 -T '{"\""}\n'
3206 $ hg log -Ra -r0 -T '{"\""}\n'
3207 "
3207 "
3208 $ hg log -Ra -r0 -T '{"\\\""}\n'
3208 $ hg log -Ra -r0 -T '{"\\\""}\n'
3209 \"
3209 \"
3210 $ hg log -Ra -r0 -T '{r"\""}\n'
3210 $ hg log -Ra -r0 -T '{r"\""}\n'
3211 \"
3211 \"
3212 $ hg log -Ra -r0 -T '{r"\\\""}\n'
3212 $ hg log -Ra -r0 -T '{r"\\\""}\n'
3213 \\\"
3213 \\\"
3214
3214
3215 Test exception in quoted template. single backslash before quotation mark is
3215 Test exception in quoted template. single backslash before quotation mark is
3216 stripped before parsing:
3216 stripped before parsing:
3217
3217
3218 $ cat <<'EOF' > escquotetmpl
3218 $ cat <<'EOF' > escquotetmpl
3219 > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n"
3219 > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n"
3220 > EOF
3220 > EOF
3221 $ cd latesttag
3221 $ cd latesttag
3222 $ hg log -r 2 --style ../escquotetmpl
3222 $ hg log -r 2 --style ../escquotetmpl
3223 " \" \" \\" head1
3223 " \" \" \\" head1
3224
3224
3225 $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"'
3225 $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"'
3226 valid
3226 valid
3227 $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'"
3227 $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'"
3228 valid
3228 valid
3229
3229
3230 Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested
3230 Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested
3231 _evalifliteral() templates (issue4733):
3231 _evalifliteral() templates (issue4733):
3232
3232
3233 $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n'
3233 $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n'
3234 "2
3234 "2
3235 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n'
3235 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n'
3236 "2
3236 "2
3237 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\"{rev}\\\")}\")}")}\n'
3237 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\"{rev}\\\")}\")}")}\n'
3238 "2
3238 "2
3239
3239
3240 $ hg log -r 2 -T '{if(rev, "\\\"")}\n'
3240 $ hg log -r 2 -T '{if(rev, "\\\"")}\n'
3241 \"
3241 \"
3242 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\\\\\"\")}")}\n'
3242 $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\\\\\"\")}")}\n'
3243 \"
3243 \"
3244 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3244 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3245 \"
3245 \"
3246
3246
3247 $ hg log -r 2 -T '{if(rev, r"\\\"")}\n'
3247 $ hg log -r 2 -T '{if(rev, r"\\\"")}\n'
3248 \\\"
3248 \\\"
3249 $ hg log -r 2 -T '{if(rev, "{if(rev, r\"\\\\\\\"\")}")}\n'
3249 $ hg log -r 2 -T '{if(rev, "{if(rev, r\"\\\\\\\"\")}")}\n'
3250 \\\"
3250 \\\"
3251 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, r\\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3251 $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, r\\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
3252 \\\"
3252 \\\"
3253
3253
3254 escaped single quotes and errors:
3254 escaped single quotes and errors:
3255
3255
3256 $ hg log -r 2 -T "{if(rev, '{if(rev, \'foo\')}')}"'\n'
3256 $ hg log -r 2 -T "{if(rev, '{if(rev, \'foo\')}')}"'\n'
3257 foo
3257 foo
3258 $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n'
3258 $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n'
3259 foo
3259 foo
3260 $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
3260 $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
3261 hg: parse error at 21: unterminated string
3261 hg: parse error at 21: unterminated string
3262 [255]
3262 [255]
3263 $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
3263 $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
3264 hg: parse error: trailing \ in string
3264 hg: parse error: trailing \ in string
3265 [255]
3265 [255]
3266 $ hg log -r 2 -T '{if(rev, r\"\\"")}\n'
3266 $ hg log -r 2 -T '{if(rev, r\"\\"")}\n'
3267 hg: parse error: trailing \ in string
3267 hg: parse error: trailing \ in string
3268 [255]
3268 [255]
3269
3269
3270 $ cd ..
3270 $ cd ..
3271
3271
3272 Test leading backslashes:
3272 Test leading backslashes:
3273
3273
3274 $ cd latesttag
3274 $ cd latesttag
3275 $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n'
3275 $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n'
3276 {rev} {file}
3276 {rev} {file}
3277 $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n'
3277 $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n'
3278 \2 \head1
3278 \2 \head1
3279 $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n'
3279 $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n'
3280 \{rev} \{file}
3280 \{rev} \{file}
3281 $ cd ..
3281 $ cd ..
3282
3282
3283 Test leading backslashes in "if" expression (issue4714):
3283 Test leading backslashes in "if" expression (issue4714):
3284
3284
3285 $ cd latesttag
3285 $ cd latesttag
3286 $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n'
3286 $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n'
3287 {rev} \{rev}
3287 {rev} \{rev}
3288 $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n'
3288 $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n'
3289 \2 \\{rev}
3289 \2 \\{rev}
3290 $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n'
3290 $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n'
3291 \{rev} \\\{rev}
3291 \{rev} \\\{rev}
3292 $ cd ..
3292 $ cd ..
3293
3293
3294 "string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
3294 "string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
3295
3295
3296 $ hg log -R a -r 0 --template '{if("1", "\x5c\x786e", "NG")}\n'
3296 $ hg log -R a -r 0 --template '{if("1", "\x5c\x786e", "NG")}\n'
3297 \x6e
3297 \x6e
3298 $ hg log -R a -r 0 --template '{if("1", r"\x5c\x786e", "NG")}\n'
3298 $ hg log -R a -r 0 --template '{if("1", r"\x5c\x786e", "NG")}\n'
3299 \x5c\x786e
3299 \x5c\x786e
3300 $ hg log -R a -r 0 --template '{if("", "NG", "\x5c\x786e")}\n'
3300 $ hg log -R a -r 0 --template '{if("", "NG", "\x5c\x786e")}\n'
3301 \x6e
3301 \x6e
3302 $ hg log -R a -r 0 --template '{if("", "NG", r"\x5c\x786e")}\n'
3302 $ hg log -R a -r 0 --template '{if("", "NG", r"\x5c\x786e")}\n'
3303 \x5c\x786e
3303 \x5c\x786e
3304
3304
3305 $ hg log -R a -r 2 --template '{ifeq("no perso\x6e", desc, "\x5c\x786e", "NG")}\n'
3305 $ hg log -R a -r 2 --template '{ifeq("no perso\x6e", desc, "\x5c\x786e", "NG")}\n'
3306 \x6e
3306 \x6e
3307 $ hg log -R a -r 2 --template '{ifeq(r"no perso\x6e", desc, "NG", r"\x5c\x786e")}\n'
3307 $ hg log -R a -r 2 --template '{ifeq(r"no perso\x6e", desc, "NG", r"\x5c\x786e")}\n'
3308 \x5c\x786e
3308 \x5c\x786e
3309 $ hg log -R a -r 2 --template '{ifeq(desc, "no perso\x6e", "\x5c\x786e", "NG")}\n'
3309 $ hg log -R a -r 2 --template '{ifeq(desc, "no perso\x6e", "\x5c\x786e", "NG")}\n'
3310 \x6e
3310 \x6e
3311 $ hg log -R a -r 2 --template '{ifeq(desc, r"no perso\x6e", "NG", r"\x5c\x786e")}\n'
3311 $ hg log -R a -r 2 --template '{ifeq(desc, r"no perso\x6e", "NG", r"\x5c\x786e")}\n'
3312 \x5c\x786e
3312 \x5c\x786e
3313
3313
3314 $ hg log -R a -r 8 --template '{join(files, "\n")}\n'
3314 $ hg log -R a -r 8 --template '{join(files, "\n")}\n'
3315 fourth
3315 fourth
3316 second
3316 second
3317 third
3317 third
3318 $ hg log -R a -r 8 --template '{join(files, r"\n")}\n'
3318 $ hg log -R a -r 8 --template '{join(files, r"\n")}\n'
3319 fourth\nsecond\nthird
3319 fourth\nsecond\nthird
3320
3320
3321 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", "htm\x6c")}'
3321 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", "htm\x6c")}'
3322 <p>
3322 <p>
3323 1st
3323 1st
3324 </p>
3324 </p>
3325 <p>
3325 <p>
3326 2nd
3326 2nd
3327 </p>
3327 </p>
3328 $ hg log -R a -r 2 --template '{rstdoc(r"1st\n\n2nd", "html")}'
3328 $ hg log -R a -r 2 --template '{rstdoc(r"1st\n\n2nd", "html")}'
3329 <p>
3329 <p>
3330 1st\n\n2nd
3330 1st\n\n2nd
3331 </p>
3331 </p>
3332 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", r"htm\x6c")}'
3332 $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", r"htm\x6c")}'
3333 1st
3333 1st
3334
3334
3335 2nd
3335 2nd
3336
3336
3337 $ hg log -R a -r 2 --template '{strip(desc, "\x6e")}\n'
3337 $ hg log -R a -r 2 --template '{strip(desc, "\x6e")}\n'
3338 o perso
3338 o perso
3339 $ hg log -R a -r 2 --template '{strip(desc, r"\x6e")}\n'
3339 $ hg log -R a -r 2 --template '{strip(desc, r"\x6e")}\n'
3340 no person
3340 no person
3341 $ hg log -R a -r 2 --template '{strip("no perso\x6e", "\x6e")}\n'
3341 $ hg log -R a -r 2 --template '{strip("no perso\x6e", "\x6e")}\n'
3342 o perso
3342 o perso
3343 $ hg log -R a -r 2 --template '{strip(r"no perso\x6e", r"\x6e")}\n'
3343 $ hg log -R a -r 2 --template '{strip(r"no perso\x6e", r"\x6e")}\n'
3344 no perso
3344 no perso
3345
3345
3346 $ hg log -R a -r 2 --template '{sub("\\x6e", "\x2d", desc)}\n'
3346 $ hg log -R a -r 2 --template '{sub("\\x6e", "\x2d", desc)}\n'
3347 -o perso-
3347 -o perso-
3348 $ hg log -R a -r 2 --template '{sub(r"\\x6e", "-", desc)}\n'
3348 $ hg log -R a -r 2 --template '{sub(r"\\x6e", "-", desc)}\n'
3349 no person
3349 no person
3350 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", desc)}\n'
3350 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", desc)}\n'
3351 \x2do perso\x2d
3351 \x2do perso\x2d
3352 $ hg log -R a -r 2 --template '{sub("n", "\x2d", "no perso\x6e")}\n'
3352 $ hg log -R a -r 2 --template '{sub("n", "\x2d", "no perso\x6e")}\n'
3353 -o perso-
3353 -o perso-
3354 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", r"no perso\x6e")}\n'
3354 $ hg log -R a -r 2 --template '{sub("n", r"\x2d", r"no perso\x6e")}\n'
3355 \x2do perso\x6e
3355 \x2do perso\x6e
3356
3356
3357 $ hg log -R a -r 8 --template '{files % "{file}\n"}'
3357 $ hg log -R a -r 8 --template '{files % "{file}\n"}'
3358 fourth
3358 fourth
3359 second
3359 second
3360 third
3360 third
3361
3361
3362 Test string escaping in nested expression:
3362 Test string escaping in nested expression:
3363
3363
3364 $ hg log -R a -r 8 --template '{ifeq(r"\x6e", if("1", "\x5c\x786e"), join(files, "\x5c\x786e"))}\n'
3364 $ hg log -R a -r 8 --template '{ifeq(r"\x6e", if("1", "\x5c\x786e"), join(files, "\x5c\x786e"))}\n'
3365 fourth\x6esecond\x6ethird
3365 fourth\x6esecond\x6ethird
3366 $ hg log -R a -r 8 --template '{ifeq(if("1", r"\x6e"), "\x5c\x786e", join(files, "\x5c\x786e"))}\n'
3366 $ hg log -R a -r 8 --template '{ifeq(if("1", r"\x6e"), "\x5c\x786e", join(files, "\x5c\x786e"))}\n'
3367 fourth\x6esecond\x6ethird
3367 fourth\x6esecond\x6ethird
3368
3368
3369 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", "\x5c\x786e"))}\n'
3369 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", "\x5c\x786e"))}\n'
3370 fourth\x6esecond\x6ethird
3370 fourth\x6esecond\x6ethird
3371 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", r"\x5c\x786e"))}\n'
3371 $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", r"\x5c\x786e"))}\n'
3372 fourth\x5c\x786esecond\x5c\x786ethird
3372 fourth\x5c\x786esecond\x5c\x786ethird
3373
3373
3374 $ hg log -R a -r 3:4 --template '{rev}:{sub(if("1", "\x6e"), ifeq(branch, "foo", r"\x5c\x786e", "\x5c\x786e"), desc)}\n'
3374 $ hg log -R a -r 3:4 --template '{rev}:{sub(if("1", "\x6e"), ifeq(branch, "foo", r"\x5c\x786e", "\x5c\x786e"), desc)}\n'
3375 3:\x6eo user, \x6eo domai\x6e
3375 3:\x6eo user, \x6eo domai\x6e
3376 4:\x5c\x786eew bra\x5c\x786ech
3376 4:\x5c\x786eew bra\x5c\x786ech
3377
3377
3378 Test quotes in nested expression are evaluated just like a $(command)
3378 Test quotes in nested expression are evaluated just like a $(command)
3379 substitution in POSIX shells:
3379 substitution in POSIX shells:
3380
3380
3381 $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n'
3381 $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n'
3382 8:95c24699272e
3382 8:95c24699272e
3383 $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n'
3383 $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n'
3384 {8} "95c24699272e"
3384 {8} "95c24699272e"
3385
3385
3386 Test recursive evaluation:
3386 Test recursive evaluation:
3387
3387
3388 $ hg init r
3388 $ hg init r
3389 $ cd r
3389 $ cd r
3390 $ echo a > a
3390 $ echo a > a
3391 $ hg ci -Am '{rev}'
3391 $ hg ci -Am '{rev}'
3392 adding a
3392 adding a
3393 $ hg log -r 0 --template '{if(rev, desc)}\n'
3393 $ hg log -r 0 --template '{if(rev, desc)}\n'
3394 {rev}
3394 {rev}
3395 $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
3395 $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
3396 test 0
3396 test 0
3397
3397
3398 $ hg branch -q 'text.{rev}'
3398 $ hg branch -q 'text.{rev}'
3399 $ echo aa >> aa
3399 $ echo aa >> aa
3400 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
3400 $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
3401
3401
3402 $ hg log -l1 --template '{fill(desc, "20", author, branch)}'
3402 $ hg log -l1 --template '{fill(desc, "20", author, branch)}'
3403 {node|short}desc to
3403 {node|short}desc to
3404 text.{rev}be wrapped
3404 text.{rev}be wrapped
3405 text.{rev}desc to be
3405 text.{rev}desc to be
3406 text.{rev}wrapped (no-eol)
3406 text.{rev}wrapped (no-eol)
3407 $ hg log -l1 --template '{fill(desc, "20", "{node|short}:", "text.{rev}:")}'
3407 $ hg log -l1 --template '{fill(desc, "20", "{node|short}:", "text.{rev}:")}'
3408 bcc7ff960b8e:desc to
3408 bcc7ff960b8e:desc to
3409 text.1:be wrapped
3409 text.1:be wrapped
3410 text.1:desc to be
3410 text.1:desc to be
3411 text.1:wrapped (no-eol)
3411 text.1:wrapped (no-eol)
3412 $ hg log -l1 -T '{fill(desc, date, "", "")}\n'
3412 $ hg log -l1 -T '{fill(desc, date, "", "")}\n'
3413 hg: parse error: fill expects an integer width
3413 hg: parse error: fill expects an integer width
3414 [255]
3414 [255]
3415
3415
3416 $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
3416 $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
3417 bcc7ff960b8e:desc to be
3417 bcc7ff960b8e:desc to be
3418 termwidth.1:wrapped desc
3418 termwidth.1:wrapped desc
3419 termwidth.1:to be wrapped (no-eol)
3419 termwidth.1:to be wrapped (no-eol)
3420
3420
3421 $ hg log -l 1 --template '{sub(r"[0-9]", "-", author)}'
3421 $ hg log -l 1 --template '{sub(r"[0-9]", "-", author)}'
3422 {node|short} (no-eol)
3422 {node|short} (no-eol)
3423 $ hg log -l 1 --template '{sub(r"[0-9]", "-", "{node|short}")}'
3423 $ hg log -l 1 --template '{sub(r"[0-9]", "-", "{node|short}")}'
3424 bcc-ff---b-e (no-eol)
3424 bcc-ff---b-e (no-eol)
3425
3425
3426 $ cat >> .hg/hgrc <<EOF
3426 $ cat >> .hg/hgrc <<EOF
3427 > [extensions]
3427 > [extensions]
3428 > color=
3428 > color=
3429 > [color]
3429 > [color]
3430 > mode=ansi
3430 > mode=ansi
3431 > text.{rev} = red
3431 > text.{rev} = red
3432 > text.1 = green
3432 > text.1 = green
3433 > EOF
3433 > EOF
3434 $ hg log --color=always -l 1 --template '{label(branch, "text\n")}'
3434 $ hg log --color=always -l 1 --template '{label(branch, "text\n")}'
3435 \x1b[0;31mtext\x1b[0m (esc)
3435 \x1b[0;31mtext\x1b[0m (esc)
3436 $ hg log --color=always -l 1 --template '{label("text.{rev}", "text\n")}'
3436 $ hg log --color=always -l 1 --template '{label("text.{rev}", "text\n")}'
3437 \x1b[0;32mtext\x1b[0m (esc)
3437 \x1b[0;32mtext\x1b[0m (esc)
3438
3438
3439 color effect can be specified without quoting:
3439 color effect can be specified without quoting:
3440
3440
3441 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
3441 $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
3442 \x1b[0;31mtext\x1b[0m (esc)
3442 \x1b[0;31mtext\x1b[0m (esc)
3443
3443
3444 color effects can be nested (issue5413)
3444 color effects can be nested (issue5413)
3445
3445
3446 $ hg debugtemplate --color=always \
3446 $ hg debugtemplate --color=always \
3447 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
3447 > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
3448 \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
3448 \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
3449
3449
3450 pad() should interact well with color codes (issue5416)
3450 pad() should interact well with color codes (issue5416)
3451
3451
3452 $ hg debugtemplate --color=always \
3452 $ hg debugtemplate --color=always \
3453 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
3453 > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
3454 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
3454 \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
3455
3455
3456 label should be no-op if color is disabled:
3456 label should be no-op if color is disabled:
3457
3457
3458 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
3458 $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
3459 text
3459 text
3460 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
3460 $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
3461 text
3461 text
3462
3462
3463 Test branches inside if statement:
3463 Test branches inside if statement:
3464
3464
3465 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
3465 $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
3466 no
3466 no
3467
3467
3468 Test dict constructor:
3468 Test dict constructor:
3469
3469
3470 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
3470 $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
3471 y=f7769ec2ab97 x=0
3471 y=f7769ec2ab97 x=0
3472 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
3472 $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
3473 x=0
3473 x=0
3474 y=f7769ec2ab97
3474 y=f7769ec2ab97
3475 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
3475 $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
3476 {"x": 0, "y": "f7769ec2ab97"}
3476 {"x": 0, "y": "f7769ec2ab97"}
3477 $ hg log -r 0 -T '{dict()|json}\n'
3477 $ hg log -r 0 -T '{dict()|json}\n'
3478 {}
3478 {}
3479
3479
3480 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
3480 $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
3481 rev=0 node=f7769ec2ab97
3481 rev=0 node=f7769ec2ab97
3482 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
3482 $ hg log -r 0 -T '{dict(rev, node|short)}\n'
3483 rev=0 node=f7769ec2ab97
3483 rev=0 node=f7769ec2ab97
3484
3484
3485 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
3485 $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
3486 hg: parse error: duplicated dict key 'rev' inferred
3486 hg: parse error: duplicated dict key 'rev' inferred
3487 [255]
3487 [255]
3488 $ hg log -r 0 -T '{dict(node, node|short)}\n'
3488 $ hg log -r 0 -T '{dict(node, node|short)}\n'
3489 hg: parse error: duplicated dict key 'node' inferred
3489 hg: parse error: duplicated dict key 'node' inferred
3490 [255]
3490 [255]
3491 $ hg log -r 0 -T '{dict(1 + 2)}'
3491 $ hg log -r 0 -T '{dict(1 + 2)}'
3492 hg: parse error: dict key cannot be inferred
3492 hg: parse error: dict key cannot be inferred
3493 [255]
3493 [255]
3494
3494
3495 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
3495 $ hg log -r 0 -T '{dict(x=rev, x=node)}'
3496 hg: parse error: dict got multiple values for keyword argument 'x'
3496 hg: parse error: dict got multiple values for keyword argument 'x'
3497 [255]
3497 [255]
3498
3498
3499 Test get function:
3499 Test get function:
3500
3500
3501 $ hg log -r 0 --template '{get(extras, "branch")}\n'
3501 $ hg log -r 0 --template '{get(extras, "branch")}\n'
3502 default
3502 default
3503 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
3503 $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
3504 default
3504 default
3505 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
3505 $ hg log -r 0 --template '{get(files, "should_fail")}\n'
3506 hg: parse error: get() expects a dict as first argument
3506 hg: parse error: get() expects a dict as first argument
3507 [255]
3507 [255]
3508
3508
3509 Test json filter applied to hybrid object:
3509 Test json filter applied to hybrid object:
3510
3510
3511 $ hg log -r0 -T '{files|json}\n'
3511 $ hg log -r0 -T '{files|json}\n'
3512 ["a"]
3512 ["a"]
3513 $ hg log -r0 -T '{extras|json}\n'
3513 $ hg log -r0 -T '{extras|json}\n'
3514 {"branch": "default"}
3514 {"branch": "default"}
3515
3515
3516 Test localdate(date, tz) function:
3516 Test localdate(date, tz) function:
3517
3517
3518 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
3518 $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
3519 1970-01-01 09:00 +0900
3519 1970-01-01 09:00 +0900
3520 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
3520 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
3521 1970-01-01 00:00 +0000
3521 1970-01-01 00:00 +0000
3522 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
3522 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
3523 hg: parse error: localdate expects a timezone
3523 hg: parse error: localdate expects a timezone
3524 [255]
3524 [255]
3525 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
3525 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
3526 1970-01-01 02:00 +0200
3526 1970-01-01 02:00 +0200
3527 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
3527 $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
3528 1970-01-01 00:00 +0000
3528 1970-01-01 00:00 +0000
3529 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
3529 $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
3530 1970-01-01 00:00 +0000
3530 1970-01-01 00:00 +0000
3531 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
3531 $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
3532 hg: parse error: localdate expects a timezone
3532 hg: parse error: localdate expects a timezone
3533 [255]
3533 [255]
3534 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
3534 $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
3535 hg: parse error: localdate expects a timezone
3535 hg: parse error: localdate expects a timezone
3536 [255]
3536 [255]
3537
3537
3538 Test shortest(node) function:
3538 Test shortest(node) function:
3539
3539
3540 $ echo b > b
3540 $ echo b > b
3541 $ hg ci -qAm b
3541 $ hg ci -qAm b
3542 $ hg log --template '{shortest(node)}\n'
3542 $ hg log --template '{shortest(node)}\n'
3543 e777
3543 e777
3544 bcc7
3544 bcc7
3545 f776
3545 f776
3546 $ hg log --template '{shortest(node, 10)}\n'
3546 $ hg log --template '{shortest(node, 10)}\n'
3547 e777603221
3547 e777603221
3548 bcc7ff960b
3548 bcc7ff960b
3549 f7769ec2ab
3549 f7769ec2ab
3550 $ hg log --template '{node|shortest}\n' -l1
3550 $ hg log --template '{node|shortest}\n' -l1
3551 e777
3551 e777
3552
3552
3553 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
3553 $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
3554 f7769ec2ab
3554 f7769ec2ab
3555 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
3555 $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
3556 hg: parse error: shortest() expects an integer minlength
3556 hg: parse error: shortest() expects an integer minlength
3557 [255]
3557 [255]
3558
3558
3559 $ hg log -r 'wdir()' -T '{node|shortest}\n'
3559 $ hg log -r 'wdir()' -T '{node|shortest}\n'
3560 ffff
3560 ffff
3561
3561
3562 $ cd ..
3562 $ cd ..
3563
3563
3564 Test shortest(node) with the repo having short hash collision:
3564 Test shortest(node) with the repo having short hash collision:
3565
3565
3566 $ hg init hashcollision
3566 $ hg init hashcollision
3567 $ cd hashcollision
3567 $ cd hashcollision
3568 $ cat <<EOF >> .hg/hgrc
3568 $ cat <<EOF >> .hg/hgrc
3569 > [experimental]
3569 > [experimental]
3570 > stabilization = createmarkers
3570 > stabilization = createmarkers
3571 > EOF
3571 > EOF
3572 $ echo 0 > a
3572 $ echo 0 > a
3573 $ hg ci -qAm 0
3573 $ hg ci -qAm 0
3574 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
3574 $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
3575 > hg up -q 0
3575 > hg up -q 0
3576 > echo $i > a
3576 > echo $i > a
3577 > hg ci -qm $i
3577 > hg ci -qm $i
3578 > done
3578 > done
3579 $ hg up -q null
3579 $ hg up -q null
3580 $ hg log -r0: -T '{rev}:{node}\n'
3580 $ hg log -r0: -T '{rev}:{node}\n'
3581 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
3581 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
3582 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
3582 1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
3583 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
3583 2:11407b3f1b9c3e76a79c1ec5373924df096f0499
3584 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
3584 3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
3585 4:10776689e627b465361ad5c296a20a487e153ca4
3585 4:10776689e627b465361ad5c296a20a487e153ca4
3586 5:a00be79088084cb3aff086ab799f8790e01a976b
3586 5:a00be79088084cb3aff086ab799f8790e01a976b
3587 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
3587 6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
3588 7:a0457b3450b8e1b778f1163b31a435802987fe5d
3588 7:a0457b3450b8e1b778f1163b31a435802987fe5d
3589 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
3589 8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
3590 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
3590 9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
3591 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
3591 10:c562ddd9c94164376c20b86b0b4991636a3bf84f
3592 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
3592 $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
3593 obsoleted 1 changesets
3593 obsoleted 1 changesets
3594 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
3594 $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
3595 obsoleted 1 changesets
3595 obsoleted 1 changesets
3596 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
3596 $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
3597 obsoleted 1 changesets
3597 obsoleted 1 changesets
3598
3598
3599 nodes starting with '11' (we don't have the revision number '11' though)
3599 nodes starting with '11' (we don't have the revision number '11' though)
3600
3600
3601 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
3601 $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
3602 1:1142
3602 1:1142
3603 2:1140
3603 2:1140
3604 3:11d
3604 3:11d
3605
3605
3606 '5:a00' is hidden, but still we have two nodes starting with 'a0'
3606 '5:a00' is hidden, but still we have two nodes starting with 'a0'
3607
3607
3608 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
3608 $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
3609 6:a0b
3609 6:a0b
3610 7:a04
3610 7:a04
3611
3611
3612 node '10' conflicts with the revision number '10' even if it is hidden
3612 node '10' conflicts with the revision number '10' even if it is hidden
3613 (we could exclude hidden revision numbers, but currently we don't)
3613 (we could exclude hidden revision numbers, but currently we don't)
3614
3614
3615 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
3615 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
3616 4:107
3616 4:107
3617 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
3617 $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
3618 4:107
3618 4:107
3619
3619
3620 node 'c562' should be unique if the other 'c562' nodes are hidden
3620 node 'c562' should be unique if the other 'c562' nodes are hidden
3621 (but we don't try the slow path to filter out hidden nodes for now)
3621 (but we don't try the slow path to filter out hidden nodes for now)
3622
3622
3623 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
3623 $ hg log -r 8 -T '{rev}:{node|shortest}\n'
3624 8:c5625
3624 8:c5625
3625 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
3625 $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
3626 8:c5625
3626 8:c5625
3627 9:c5623
3627 9:c5623
3628 10:c562d
3628 10:c562d
3629
3629
3630 $ cd ..
3630 $ cd ..
3631
3631
3632 Test pad function
3632 Test pad function
3633
3633
3634 $ cd r
3634 $ cd r
3635
3635
3636 $ hg log --template '{pad(rev, 20)} {author|user}\n'
3636 $ hg log --template '{pad(rev, 20)} {author|user}\n'
3637 2 test
3637 2 test
3638 1 {node|short}
3638 1 {node|short}
3639 0 test
3639 0 test
3640
3640
3641 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
3641 $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
3642 2 test
3642 2 test
3643 1 {node|short}
3643 1 {node|short}
3644 0 test
3644 0 test
3645
3645
3646 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
3646 $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
3647 2------------------- test
3647 2------------------- test
3648 1------------------- {node|short}
3648 1------------------- {node|short}
3649 0------------------- test
3649 0------------------- test
3650
3650
3651 Test template string in pad function
3651 Test template string in pad function
3652
3652
3653 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
3653 $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
3654 {0} test
3654 {0} test
3655
3655
3656 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
3656 $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
3657 \{rev} test
3657 \{rev} test
3658
3658
3659 Test width argument passed to pad function
3659 Test width argument passed to pad function
3660
3660
3661 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
3661 $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
3662 0 test
3662 0 test
3663 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
3663 $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
3664 hg: parse error: pad() expects an integer width
3664 hg: parse error: pad() expects an integer width
3665 [255]
3665 [255]
3666
3666
3667 Test invalid fillchar passed to pad function
3667 Test invalid fillchar passed to pad function
3668
3668
3669 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
3669 $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
3670 hg: parse error: pad() expects a single fill character
3670 hg: parse error: pad() expects a single fill character
3671 [255]
3671 [255]
3672 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
3672 $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
3673 hg: parse error: pad() expects a single fill character
3673 hg: parse error: pad() expects a single fill character
3674 [255]
3674 [255]
3675
3675
3676 Test boolean argument passed to pad function
3676 Test boolean argument passed to pad function
3677
3677
3678 no crash
3678 no crash
3679
3679
3680 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
3680 $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
3681 ---------0
3681 ---------0
3682
3682
3683 string/literal
3683 string/literal
3684
3684
3685 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
3685 $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
3686 ---------0
3686 ---------0
3687 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
3687 $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
3688 0---------
3688 0---------
3689 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
3689 $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
3690 0---------
3690 0---------
3691
3691
3692 unknown keyword is evaluated to ''
3692 unknown keyword is evaluated to ''
3693
3693
3694 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
3694 $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
3695 0---------
3695 0---------
3696
3696
3697 Test separate function
3697 Test separate function
3698
3698
3699 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
3699 $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
3700 a-b-c
3700 a-b-c
3701 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
3701 $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
3702 0:f7769ec2ab97 test default
3702 0:f7769ec2ab97 test default
3703 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
3703 $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
3704 a \x1b[0;31mb\x1b[0m c d (esc)
3704 a \x1b[0;31mb\x1b[0m c d (esc)
3705
3705
3706 Test boolean expression/literal passed to if function
3706 Test boolean expression/literal passed to if function
3707
3707
3708 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
3708 $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
3709 rev 0 is True
3709 rev 0 is True
3710 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
3710 $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
3711 literal 0 is True as well
3711 literal 0 is True as well
3712 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
3712 $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
3713 empty string is False
3713 empty string is False
3714 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
3714 $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
3715 empty list is False
3715 empty list is False
3716 $ hg log -r 0 -T '{if(true, "true is True")}\n'
3716 $ hg log -r 0 -T '{if(true, "true is True")}\n'
3717 true is True
3717 true is True
3718 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
3718 $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
3719 false is False
3719 false is False
3720 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
3720 $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
3721 non-empty string is True
3721 non-empty string is True
3722
3722
3723 Test ifcontains function
3723 Test ifcontains function
3724
3724
3725 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
3725 $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
3726 2 is in the string
3726 2 is in the string
3727 1 is not
3727 1 is not
3728 0 is in the string
3728 0 is in the string
3729
3729
3730 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
3730 $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
3731 2 is in the string
3731 2 is in the string
3732 1 is not
3732 1 is not
3733 0 is in the string
3733 0 is in the string
3734
3734
3735 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
3735 $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
3736 2 did not add a
3736 2 did not add a
3737 1 did not add a
3737 1 did not add a
3738 0 added a
3738 0 added a
3739
3739
3740 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
3740 $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
3741 2 is parent of 1
3741 2 is parent of 1
3742 1
3742 1
3743 0
3743 0
3744
3744
3745 Test revset function
3745 Test revset function
3746
3746
3747 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
3747 $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
3748 2 current rev
3748 2 current rev
3749 1 not current rev
3749 1 not current rev
3750 0 not current rev
3750 0 not current rev
3751
3751
3752 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
3752 $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
3753 2 match rev
3753 2 match rev
3754 1 match rev
3754 1 match rev
3755 0 not match rev
3755 0 not match rev
3756
3756
3757 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
3757 $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
3758 2 Parents: 1
3758 2 Parents: 1
3759 1 Parents: 0
3759 1 Parents: 0
3760 0 Parents:
3760 0 Parents:
3761
3761
3762 $ cat >> .hg/hgrc <<EOF
3762 $ cat >> .hg/hgrc <<EOF
3763 > [revsetalias]
3763 > [revsetalias]
3764 > myparents(\$1) = parents(\$1)
3764 > myparents(\$1) = parents(\$1)
3765 > EOF
3765 > EOF
3766 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
3766 $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
3767 2 Parents: 1
3767 2 Parents: 1
3768 1 Parents: 0
3768 1 Parents: 0
3769 0 Parents:
3769 0 Parents:
3770
3770
3771 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
3771 $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
3772 Rev: 2
3772 Rev: 2
3773 Ancestor: 0
3773 Ancestor: 0
3774 Ancestor: 1
3774 Ancestor: 1
3775 Ancestor: 2
3775 Ancestor: 2
3776
3776
3777 Rev: 1
3777 Rev: 1
3778 Ancestor: 0
3778 Ancestor: 0
3779 Ancestor: 1
3779 Ancestor: 1
3780
3780
3781 Rev: 0
3781 Rev: 0
3782 Ancestor: 0
3782 Ancestor: 0
3783
3783
3784 $ hg log --template '{revset("TIP"|lower)}\n' -l1
3784 $ hg log --template '{revset("TIP"|lower)}\n' -l1
3785 2
3785 2
3786
3786
3787 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
3787 $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
3788 2
3788 2
3789
3789
3790 a list template is evaluated for each item of revset/parents
3790 a list template is evaluated for each item of revset/parents
3791
3791
3792 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
3792 $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
3793 2 p: 1:bcc7ff960b8e
3793 2 p: 1:bcc7ff960b8e
3794 1 p: 0:f7769ec2ab97
3794 1 p: 0:f7769ec2ab97
3795 0 p:
3795 0 p:
3796
3796
3797 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
3797 $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
3798 2 p: 1:bcc7ff960b8e -1:000000000000
3798 2 p: 1:bcc7ff960b8e -1:000000000000
3799 1 p: 0:f7769ec2ab97 -1:000000000000
3799 1 p: 0:f7769ec2ab97 -1:000000000000
3800 0 p: -1:000000000000 -1:000000000000
3800 0 p: -1:000000000000 -1:000000000000
3801
3801
3802 therefore, 'revcache' should be recreated for each rev
3802 therefore, 'revcache' should be recreated for each rev
3803
3803
3804 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
3804 $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
3805 2 aa b
3805 2 aa b
3806 p
3806 p
3807 1
3807 1
3808 p a
3808 p a
3809 0 a
3809 0 a
3810 p
3810 p
3811
3811
3812 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
3812 $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
3813 2 aa b
3813 2 aa b
3814 p
3814 p
3815 1
3815 1
3816 p a
3816 p a
3817 0 a
3817 0 a
3818 p
3818 p
3819
3819
3820 a revset item must be evaluated as an integer revision, not an offset from tip
3820 a revset item must be evaluated as an integer revision, not an offset from tip
3821
3821
3822 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
3822 $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
3823 -1:000000000000
3823 -1:000000000000
3824 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
3824 $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
3825 -1:000000000000
3825 -1:000000000000
3826
3826
3827 join() should pick '{rev}' from revset items:
3827 join() should pick '{rev}' from revset items:
3828
3828
3829 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
3829 $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
3830 4, 5
3830 4, 5
3831
3831
3832 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
3832 on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
3833 default. join() should agree with the default formatting:
3833 default. join() should agree with the default formatting:
3834
3834
3835 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
3835 $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
3836 5:13207e5a10d9, 4:bbe44766e73d
3836 5:13207e5a10d9, 4:bbe44766e73d
3837
3837
3838 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
3838 $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
3839 5:13207e5a10d9fd28ec424934298e176197f2c67f,
3839 5:13207e5a10d9fd28ec424934298e176197f2c67f,
3840 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
3840 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
3841
3841
3842 Test files function
3842 Test files function
3843
3843
3844 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
3844 $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
3845 2
3845 2
3846 a
3846 a
3847 aa
3847 aa
3848 b
3848 b
3849 1
3849 1
3850 a
3850 a
3851 0
3851 0
3852 a
3852 a
3853
3853
3854 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
3854 $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
3855 2
3855 2
3856 aa
3856 aa
3857 1
3857 1
3858
3858
3859 0
3859 0
3860
3860
3861
3861
3862 Test relpath function
3862 Test relpath function
3863
3863
3864 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
3864 $ hg log -r0 -T '{files % "{file|relpath}\n"}'
3865 a
3865 a
3866 $ cd ..
3866 $ cd ..
3867 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
3867 $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
3868 r/a
3868 r/a
3869 $ cd r
3869 $ cd r
3870
3870
3871 Test active bookmark templating
3871 Test active bookmark templating
3872
3872
3873 $ hg book foo
3873 $ hg book foo
3874 $ hg book bar
3874 $ hg book bar
3875 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
3875 $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
3876 2 bar* foo
3876 2 bar* foo
3877 1
3877 1
3878 0
3878 0
3879 $ hg log --template "{rev} {activebookmark}\n"
3879 $ hg log --template "{rev} {activebookmark}\n"
3880 2 bar
3880 2 bar
3881 1
3881 1
3882 0
3882 0
3883 $ hg bookmarks --inactive bar
3883 $ hg bookmarks --inactive bar
3884 $ hg log --template "{rev} {activebookmark}\n"
3884 $ hg log --template "{rev} {activebookmark}\n"
3885 2
3885 2
3886 1
3886 1
3887 0
3887 0
3888 $ hg book -r1 baz
3888 $ hg book -r1 baz
3889 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
3889 $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
3890 2 bar foo
3890 2 bar foo
3891 1 baz
3891 1 baz
3892 0
3892 0
3893 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
3893 $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
3894 2 t
3894 2 t
3895 1 f
3895 1 f
3896 0 f
3896 0 f
3897
3897
3898 Test namespaces dict
3898 Test namespaces dict
3899
3899
3900 $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n {join(names, ",")}\n"}\n'
3900 $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n {join(names, ",")}\n"}\n'
3901 2
3901 2
3902 bookmarks color=bookmark builtin=True
3902 bookmarks color=bookmark builtin=True
3903 bar,foo
3903 bar,foo
3904 tags color=tag builtin=True
3904 tags color=tag builtin=True
3905 tip
3905 tip
3906 branches color=branch builtin=True
3906 branches color=branch builtin=True
3907 text.{rev}
3907 text.{rev}
3908 revnames color=revname builtin=False
3908 revnames color=revname builtin=False
3909 r2
3909 r2
3910
3910
3911 1
3911 1
3912 bookmarks color=bookmark builtin=True
3912 bookmarks color=bookmark builtin=True
3913 baz
3913 baz
3914 tags color=tag builtin=True
3914 tags color=tag builtin=True
3915
3915
3916 branches color=branch builtin=True
3916 branches color=branch builtin=True
3917 text.{rev}
3917 text.{rev}
3918 revnames color=revname builtin=False
3918 revnames color=revname builtin=False
3919 r1
3919 r1
3920
3920
3921 0
3921 0
3922 bookmarks color=bookmark builtin=True
3922 bookmarks color=bookmark builtin=True
3923
3923
3924 tags color=tag builtin=True
3924 tags color=tag builtin=True
3925
3925
3926 branches color=branch builtin=True
3926 branches color=branch builtin=True
3927 default
3927 default
3928 revnames color=revname builtin=False
3928 revnames color=revname builtin=False
3929 r0
3929 r0
3930
3930
3931 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
3931 $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
3932 bookmarks: bar foo
3932 bookmarks: bar foo
3933 tags: tip
3933 tags: tip
3934 branches: text.{rev}
3934 branches: text.{rev}
3935 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
3935 $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
3936 bookmarks:
3936 bookmarks:
3937 bar
3937 bar
3938 foo
3938 foo
3939 tags:
3939 tags:
3940 tip
3940 tip
3941 branches:
3941 branches:
3942 text.{rev}
3942 text.{rev}
3943 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
3943 $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
3944 bar
3944 bar
3945 foo
3945 foo
3946
3946
3947 Test stringify on sub expressions
3947 Test stringify on sub expressions
3948
3948
3949 $ cd ..
3949 $ cd ..
3950 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
3950 $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
3951 fourth, second, third
3951 fourth, second, third
3952 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
3952 $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
3953 abc
3953 abc
3954
3954
3955 Test splitlines
3955 Test splitlines
3956
3956
3957 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
3957 $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
3958 @ foo Modify, add, remove, rename
3958 @ foo Modify, add, remove, rename
3959 |
3959 |
3960 o foo future
3960 o foo future
3961 |
3961 |
3962 o foo third
3962 o foo third
3963 |
3963 |
3964 o foo second
3964 o foo second
3965
3965
3966 o foo merge
3966 o foo merge
3967 |\
3967 |\
3968 | o foo new head
3968 | o foo new head
3969 | |
3969 | |
3970 o | foo new branch
3970 o | foo new branch
3971 |/
3971 |/
3972 o foo no user, no domain
3972 o foo no user, no domain
3973 |
3973 |
3974 o foo no person
3974 o foo no person
3975 |
3975 |
3976 o foo other 1
3976 o foo other 1
3977 | foo other 2
3977 | foo other 2
3978 | foo
3978 | foo
3979 | foo other 3
3979 | foo other 3
3980 o foo line 1
3980 o foo line 1
3981 foo line 2
3981 foo line 2
3982
3982
3983 $ hg log -R a -r0 -T '{desc|splitlines}\n'
3983 $ hg log -R a -r0 -T '{desc|splitlines}\n'
3984 line 1 line 2
3984 line 1 line 2
3985 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
3985 $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
3986 line 1|line 2
3986 line 1|line 2
3987
3987
3988 Test startswith
3988 Test startswith
3989 $ hg log -Gv -R a --template "{startswith(desc)}"
3989 $ hg log -Gv -R a --template "{startswith(desc)}"
3990 hg: parse error: startswith expects two arguments
3990 hg: parse error: startswith expects two arguments
3991 [255]
3991 [255]
3992
3992
3993 $ hg log -Gv -R a --template "{startswith('line', desc)}"
3993 $ hg log -Gv -R a --template "{startswith('line', desc)}"
3994 @
3994 @
3995 |
3995 |
3996 o
3996 o
3997 |
3997 |
3998 o
3998 o
3999 |
3999 |
4000 o
4000 o
4001
4001
4002 o
4002 o
4003 |\
4003 |\
4004 | o
4004 | o
4005 | |
4005 | |
4006 o |
4006 o |
4007 |/
4007 |/
4008 o
4008 o
4009 |
4009 |
4010 o
4010 o
4011 |
4011 |
4012 o
4012 o
4013 |
4013 |
4014 o line 1
4014 o line 1
4015 line 2
4015 line 2
4016
4016
4017 Test bad template with better error message
4017 Test bad template with better error message
4018
4018
4019 $ hg log -Gv -R a --template '{desc|user()}'
4019 $ hg log -Gv -R a --template '{desc|user()}'
4020 hg: parse error: expected a symbol, got 'func'
4020 hg: parse error: expected a symbol, got 'func'
4021 [255]
4021 [255]
4022
4022
4023 Test word function (including index out of bounds graceful failure)
4023 Test word function (including index out of bounds graceful failure)
4024
4024
4025 $ hg log -Gv -R a --template "{word('1', desc)}"
4025 $ hg log -Gv -R a --template "{word('1', desc)}"
4026 @ add,
4026 @ add,
4027 |
4027 |
4028 o
4028 o
4029 |
4029 |
4030 o
4030 o
4031 |
4031 |
4032 o
4032 o
4033
4033
4034 o
4034 o
4035 |\
4035 |\
4036 | o head
4036 | o head
4037 | |
4037 | |
4038 o | branch
4038 o | branch
4039 |/
4039 |/
4040 o user,
4040 o user,
4041 |
4041 |
4042 o person
4042 o person
4043 |
4043 |
4044 o 1
4044 o 1
4045 |
4045 |
4046 o 1
4046 o 1
4047
4047
4048
4048
4049 Test word third parameter used as splitter
4049 Test word third parameter used as splitter
4050
4050
4051 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
4051 $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
4052 @ M
4052 @ M
4053 |
4053 |
4054 o future
4054 o future
4055 |
4055 |
4056 o third
4056 o third
4057 |
4057 |
4058 o sec
4058 o sec
4059
4059
4060 o merge
4060 o merge
4061 |\
4061 |\
4062 | o new head
4062 | o new head
4063 | |
4063 | |
4064 o | new branch
4064 o | new branch
4065 |/
4065 |/
4066 o n
4066 o n
4067 |
4067 |
4068 o n
4068 o n
4069 |
4069 |
4070 o
4070 o
4071 |
4071 |
4072 o line 1
4072 o line 1
4073 line 2
4073 line 2
4074
4074
4075 Test word error messages for not enough and too many arguments
4075 Test word error messages for not enough and too many arguments
4076
4076
4077 $ hg log -Gv -R a --template "{word('0')}"
4077 $ hg log -Gv -R a --template "{word('0')}"
4078 hg: parse error: word expects two or three arguments, got 1
4078 hg: parse error: word expects two or three arguments, got 1
4079 [255]
4079 [255]
4080
4080
4081 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
4081 $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
4082 hg: parse error: word expects two or three arguments, got 7
4082 hg: parse error: word expects two or three arguments, got 7
4083 [255]
4083 [255]
4084
4084
4085 Test word for integer literal
4085 Test word for integer literal
4086
4086
4087 $ hg log -R a --template "{word(2, desc)}\n" -r0
4087 $ hg log -R a --template "{word(2, desc)}\n" -r0
4088 line
4088 line
4089
4089
4090 Test word for invalid numbers
4090 Test word for invalid numbers
4091
4091
4092 $ hg log -Gv -R a --template "{word('a', desc)}"
4092 $ hg log -Gv -R a --template "{word('a', desc)}"
4093 hg: parse error: word expects an integer index
4093 hg: parse error: word expects an integer index
4094 [255]
4094 [255]
4095
4095
4096 Test word for out of range
4096 Test word for out of range
4097
4097
4098 $ hg log -R a --template "{word(10000, desc)}"
4098 $ hg log -R a --template "{word(10000, desc)}"
4099 $ hg log -R a --template "{word(-10000, desc)}"
4099 $ hg log -R a --template "{word(-10000, desc)}"
4100
4100
4101 Test indent and not adding to empty lines
4101 Test indent and not adding to empty lines
4102
4102
4103 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
4103 $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
4104 -----
4104 -----
4105 > line 1
4105 > line 1
4106 >> line 2
4106 >> line 2
4107 -----
4107 -----
4108 > other 1
4108 > other 1
4109 >> other 2
4109 >> other 2
4110
4110
4111 >> other 3
4111 >> other 3
4112
4112
4113 Test with non-strings like dates
4113 Test with non-strings like dates
4114
4114
4115 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
4115 $ hg log -T "{indent(date, ' ')}\n" -r 2:3 -R a
4116 1200000.00
4116 1200000.00
4117 1300000.00
4117 1300000.00
4118
4118
4119 Test broken string escapes:
4119 Test broken string escapes:
4120
4120
4121 $ hg log -T "bogus\\" -R a
4121 $ hg log -T "bogus\\" -R a
4122 hg: parse error: trailing \ in string
4122 hg: parse error: trailing \ in string
4123 [255]
4123 [255]
4124 $ hg log -T "\\xy" -R a
4124 $ hg log -T "\\xy" -R a
4125 hg: parse error: invalid \x escape
4125 hg: parse error: invalid \x escape
4126 [255]
4126 [255]
4127
4127
4128 json filter should escape HTML tags so that the output can be embedded in hgweb:
4128 json filter should escape HTML tags so that the output can be embedded in hgweb:
4129
4129
4130 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
4130 $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
4131 "\u003cfoo@example.org\u003e"
4131 "\u003cfoo@example.org\u003e"
4132
4132
4133 Templater supports aliases of symbol and func() styles:
4133 Templater supports aliases of symbol and func() styles:
4134
4134
4135 $ hg clone -q a aliases
4135 $ hg clone -q a aliases
4136 $ cd aliases
4136 $ cd aliases
4137 $ cat <<EOF >> .hg/hgrc
4137 $ cat <<EOF >> .hg/hgrc
4138 > [templatealias]
4138 > [templatealias]
4139 > r = rev
4139 > r = rev
4140 > rn = "{r}:{node|short}"
4140 > rn = "{r}:{node|short}"
4141 > status(c, files) = files % "{c} {file}\n"
4141 > status(c, files) = files % "{c} {file}\n"
4142 > utcdate(d) = localdate(d, "UTC")
4142 > utcdate(d) = localdate(d, "UTC")
4143 > EOF
4143 > EOF
4144
4144
4145 $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
4145 $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
4146 (template
4146 (template
4147 ('symbol', 'rn')
4147 ('symbol', 'rn')
4148 ('string', ' ')
4148 ('string', ' ')
4149 (|
4149 (|
4150 (func
4150 (func
4151 ('symbol', 'utcdate')
4151 ('symbol', 'utcdate')
4152 ('symbol', 'date'))
4152 ('symbol', 'date'))
4153 ('symbol', 'isodate'))
4153 ('symbol', 'isodate'))
4154 ('string', '\n'))
4154 ('string', '\n'))
4155 * expanded:
4155 * expanded:
4156 (template
4156 (template
4157 (template
4157 (template
4158 ('symbol', 'rev')
4158 ('symbol', 'rev')
4159 ('string', ':')
4159 ('string', ':')
4160 (|
4160 (|
4161 ('symbol', 'node')
4161 ('symbol', 'node')
4162 ('symbol', 'short')))
4162 ('symbol', 'short')))
4163 ('string', ' ')
4163 ('string', ' ')
4164 (|
4164 (|
4165 (func
4165 (func
4166 ('symbol', 'localdate')
4166 ('symbol', 'localdate')
4167 (list
4167 (list
4168 ('symbol', 'date')
4168 ('symbol', 'date')
4169 ('string', 'UTC')))
4169 ('string', 'UTC')))
4170 ('symbol', 'isodate'))
4170 ('symbol', 'isodate'))
4171 ('string', '\n'))
4171 ('string', '\n'))
4172 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
4172 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
4173
4173
4174 $ hg debugtemplate -vr0 '{status("A", file_adds)}'
4174 $ hg debugtemplate -vr0 '{status("A", file_adds)}'
4175 (template
4175 (template
4176 (func
4176 (func
4177 ('symbol', 'status')
4177 ('symbol', 'status')
4178 (list
4178 (list
4179 ('string', 'A')
4179 ('string', 'A')
4180 ('symbol', 'file_adds'))))
4180 ('symbol', 'file_adds'))))
4181 * expanded:
4181 * expanded:
4182 (template
4182 (template
4183 (%
4183 (%
4184 ('symbol', 'file_adds')
4184 ('symbol', 'file_adds')
4185 (template
4185 (template
4186 ('string', 'A')
4186 ('string', 'A')
4187 ('string', ' ')
4187 ('string', ' ')
4188 ('symbol', 'file')
4188 ('symbol', 'file')
4189 ('string', '\n'))))
4189 ('string', '\n'))))
4190 A a
4190 A a
4191
4191
4192 A unary function alias can be called as a filter:
4192 A unary function alias can be called as a filter:
4193
4193
4194 $ hg debugtemplate -vr0 '{date|utcdate|isodate}\n'
4194 $ hg debugtemplate -vr0 '{date|utcdate|isodate}\n'
4195 (template
4195 (template
4196 (|
4196 (|
4197 (|
4197 (|
4198 ('symbol', 'date')
4198 ('symbol', 'date')
4199 ('symbol', 'utcdate'))
4199 ('symbol', 'utcdate'))
4200 ('symbol', 'isodate'))
4200 ('symbol', 'isodate'))
4201 ('string', '\n'))
4201 ('string', '\n'))
4202 * expanded:
4202 * expanded:
4203 (template
4203 (template
4204 (|
4204 (|
4205 (func
4205 (func
4206 ('symbol', 'localdate')
4206 ('symbol', 'localdate')
4207 (list
4207 (list
4208 ('symbol', 'date')
4208 ('symbol', 'date')
4209 ('string', 'UTC')))
4209 ('string', 'UTC')))
4210 ('symbol', 'isodate'))
4210 ('symbol', 'isodate'))
4211 ('string', '\n'))
4211 ('string', '\n'))
4212 1970-01-12 13:46 +0000
4212 1970-01-12 13:46 +0000
4213
4213
4214 Aliases should be applied only to command arguments and templates in hgrc.
4214 Aliases should be applied only to command arguments and templates in hgrc.
4215 Otherwise, our stock styles and web templates could be corrupted:
4215 Otherwise, our stock styles and web templates could be corrupted:
4216
4216
4217 $ hg log -r0 -T '{rn} {utcdate(date)|isodate}\n'
4217 $ hg log -r0 -T '{rn} {utcdate(date)|isodate}\n'
4218 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
4218 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
4219
4219
4220 $ hg log -r0 --config ui.logtemplate='"{rn} {utcdate(date)|isodate}\n"'
4220 $ hg log -r0 --config ui.logtemplate='"{rn} {utcdate(date)|isodate}\n"'
4221 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
4221 0:1e4e1b8f71e0 1970-01-12 13:46 +0000
4222
4222
4223 $ cat <<EOF > tmpl
4223 $ cat <<EOF > tmpl
4224 > changeset = 'nothing expanded:{rn}\n'
4224 > changeset = 'nothing expanded:{rn}\n'
4225 > EOF
4225 > EOF
4226 $ hg log -r0 --style ./tmpl
4226 $ hg log -r0 --style ./tmpl
4227 nothing expanded:
4227 nothing expanded:
4228
4228
4229 Aliases in formatter:
4229 Aliases in formatter:
4230
4230
4231 $ hg branches -T '{pad(branch, 7)} {rn}\n'
4231 $ hg branches -T '{pad(branch, 7)} {rn}\n'
4232 default 6:d41e714fe50d
4232 default 6:d41e714fe50d
4233 foo 4:bbe44766e73d
4233 foo 4:bbe44766e73d
4234
4234
4235 Aliases should honor HGPLAIN:
4235 Aliases should honor HGPLAIN:
4236
4236
4237 $ HGPLAIN= hg log -r0 -T 'nothing expanded:{rn}\n'
4237 $ HGPLAIN= hg log -r0 -T 'nothing expanded:{rn}\n'
4238 nothing expanded:
4238 nothing expanded:
4239 $ HGPLAINEXCEPT=templatealias hg log -r0 -T '{rn}\n'
4239 $ HGPLAINEXCEPT=templatealias hg log -r0 -T '{rn}\n'
4240 0:1e4e1b8f71e0
4240 0:1e4e1b8f71e0
4241
4241
4242 Unparsable alias:
4242 Unparsable alias:
4243
4243
4244 $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
4244 $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
4245 (template
4245 (template
4246 ('symbol', 'bad'))
4246 ('symbol', 'bad'))
4247 abort: bad definition of template alias "bad": at 2: not a prefix: end
4247 abort: bad definition of template alias "bad": at 2: not a prefix: end
4248 [255]
4248 [255]
4249 $ hg log --config templatealias.bad='x(' -T '{bad}'
4249 $ hg log --config templatealias.bad='x(' -T '{bad}'
4250 abort: bad definition of template alias "bad": at 2: not a prefix: end
4250 abort: bad definition of template alias "bad": at 2: not a prefix: end
4251 [255]
4251 [255]
4252
4252
4253 $ cd ..
4253 $ cd ..
4254
4254
4255 Set up repository for non-ascii encoding tests:
4255 Set up repository for non-ascii encoding tests:
4256
4256
4257 $ hg init nonascii
4257 $ hg init nonascii
4258 $ cd nonascii
4258 $ cd nonascii
4259 $ $PYTHON <<EOF
4259 $ $PYTHON <<EOF
4260 > open('latin1', 'w').write('\xe9')
4260 > open('latin1', 'w').write('\xe9')
4261 > open('utf-8', 'w').write('\xc3\xa9')
4261 > open('utf-8', 'w').write('\xc3\xa9')
4262 > EOF
4262 > EOF
4263 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
4263 $ HGENCODING=utf-8 hg branch -q `cat utf-8`
4264 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
4264 $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
4265
4265
4266 json filter should try round-trip conversion to utf-8:
4266 json filter should try round-trip conversion to utf-8:
4267
4267
4268 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
4268 $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
4269 "\u00e9"
4269 "\u00e9"
4270 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
4270 $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
4271 "non-ascii branch: \u00e9"
4271 "non-ascii branch: \u00e9"
4272
4272
4273 json filter takes input as utf-8b:
4273 json filter takes input as utf-8b:
4274
4274
4275 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
4275 $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
4276 "\u00e9"
4276 "\u00e9"
4277 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
4277 $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
4278 "\udce9"
4278 "\udce9"
4279
4279
4280 utf8 filter:
4280 utf8 filter:
4281
4281
4282 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
4282 $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
4283 round-trip: c3a9
4283 round-trip: c3a9
4284 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
4284 $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
4285 decoded: c3a9
4285 decoded: c3a9
4286 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
4286 $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
4287 abort: decoding near * (glob)
4287 abort: decoding near * (glob)
4288 [255]
4288 [255]
4289 $ hg log -T "invalid type: {rev|utf8}\n" -r0
4289 $ hg log -T "invalid type: {rev|utf8}\n" -r0
4290 abort: template filter 'utf8' is not compatible with keyword 'rev'
4290 abort: template filter 'utf8' is not compatible with keyword 'rev'
4291 [255]
4291 [255]
4292
4292
4293 pad width:
4293 pad width:
4294
4294
4295 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
4295 $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
4296 \xc3\xa9- (esc)
4296 \xc3\xa9- (esc)
4297
4297
4298 $ cd ..
4298 $ cd ..
4299
4299
4300 Test that template function in extension is registered as expected
4300 Test that template function in extension is registered as expected
4301
4301
4302 $ cd a
4302 $ cd a
4303
4303
4304 $ cat <<EOF > $TESTTMP/customfunc.py
4304 $ cat <<EOF > $TESTTMP/customfunc.py
4305 > from mercurial import registrar
4305 > from mercurial import registrar
4306 >
4306 >
4307 > templatefunc = registrar.templatefunc()
4307 > templatefunc = registrar.templatefunc()
4308 >
4308 >
4309 > @templatefunc('custom()')
4309 > @templatefunc('custom()')
4310 > def custom(context, mapping, args):
4310 > def custom(context, mapping, args):
4311 > return 'custom'
4311 > return 'custom'
4312 > EOF
4312 > EOF
4313 $ cat <<EOF > .hg/hgrc
4313 $ cat <<EOF > .hg/hgrc
4314 > [extensions]
4314 > [extensions]
4315 > customfunc = $TESTTMP/customfunc.py
4315 > customfunc = $TESTTMP/customfunc.py
4316 > EOF
4316 > EOF
4317
4317
4318 $ hg log -r . -T "{custom()}\n" --config customfunc.enabled=true
4318 $ hg log -r . -T "{custom()}\n" --config customfunc.enabled=true
4319 custom
4319 custom
4320
4320
4321 $ cd ..
4321 $ cd ..
4322
4323 Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
4324 printed graphwidths 3, 5, 7, etc. should all line up in their respective
4325 columns. We don't care about other aspects of the graph rendering here.
4326
4327 $ hg init graphwidth
4328 $ cd graphwidth
4329
4330 $ wrappabletext="a a a a a a a a a a a a"
4331
4332 $ printf "first\n" > file
4333 $ hg add file
4334 $ hg commit -m "$wrappabletext"
4335
4336 $ printf "first\nsecond\n" > file
4337 $ hg commit -m "$wrappabletext"
4338
4339 $ hg checkout 0
4340 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4341 $ printf "third\nfirst\n" > file
4342 $ hg commit -m "$wrappabletext"
4343 created new head
4344
4345 $ hg merge
4346 merging file
4347 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
4348 (branch merge, don't forget to commit)
4349
4350 $ hg log --graph -T "{graphwidth}"
4351 @ 3
4352 |
4353 | @ 5
4354 |/
4355 o 3
4356
4357 $ hg commit -m "$wrappabletext"
4358
4359 $ hg log --graph -T "{graphwidth}"
4360 @ 5
4361 |\
4362 | o 5
4363 | |
4364 o | 5
4365 |/
4366 o 3
4367
4368
4369 $ hg checkout 0
4370 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4371 $ printf "third\nfirst\nsecond\n" > file
4372 $ hg commit -m "$wrappabletext"
4373 created new head
4374
4375 $ hg log --graph -T "{graphwidth}"
4376 @ 3
4377 |
4378 | o 7
4379 | |\
4380 +---o 7
4381 | |
4382 | o 5
4383 |/
4384 o 3
4385
4386
4387 $ hg log --graph -T "{graphwidth}" -r 3
4388 o 5
4389 |\
4390 ~ ~
4391
4392 $ hg log --graph -T "{graphwidth}" -r 1
4393 o 3
4394 |
4395 ~
4396
4397 $ hg merge
4398 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
4399 (branch merge, don't forget to commit)
4400 $ hg commit -m "$wrappabletext"
4401
4402 $ printf "seventh\n" >> file
4403 $ hg commit -m "$wrappabletext"
4404
4405 $ hg log --graph -T "{graphwidth}"
4406 @ 3
4407 |
4408 o 5
4409 |\
4410 | o 5
4411 | |
4412 o | 7
4413 |\ \
4414 | o | 7
4415 | |/
4416 o / 5
4417 |/
4418 o 3
4419
4420
4421 The point of graphwidth is to allow wrapping that accounts for the space taken
4422 by the graph.
4423
4424 $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
4425 @ a a a a
4426 | a a a a
4427 | a a a a
4428 o a a a
4429 |\ a a a
4430 | | a a a
4431 | | a a a
4432 | o a a a
4433 | | a a a
4434 | | a a a
4435 | | a a a
4436 o | a a
4437 |\ \ a a
4438 | | | a a
4439 | | | a a
4440 | | | a a
4441 | | | a a
4442 | o | a a
4443 | |/ a a
4444 | | a a
4445 | | a a
4446 | | a a
4447 | | a a
4448 o | a a a
4449 |/ a a a
4450 | a a a
4451 | a a a
4452 o a a a a
4453 a a a a
4454 a a a a
4455
4456 Something tricky happens when there are elided nodes; the next drawn row of
4457 edges can be more than one column wider, but the graph width only increases by
4458 one column. The remaining columns are added in between the nodes.
4459
4460 $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
4461 o 5
4462 |\
4463 | \
4464 | :\
4465 o : : 7
4466 :/ /
4467 : o 5
4468 :/
4469 o 3
4470
4471
4472 $ cd ..
4473
General Comments 0
You need to be logged in to leave comments. Login now