##// END OF EJS Templates
log: map None rev to wdirrev when filtering revisions with --line-range...
Denis Laxalde -
r44173:d1b9d2c6 stable
parent child Browse files
Show More
@@ -1,1070 +1,1072 b''
1 # logcmdutil.py - utility for log-like commands
1 # logcmdutil.py - utility for log-like commands
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 itertools
10 import itertools
11 import os
11 import os
12 import posixpath
12 import posixpath
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 nullid,
16 nullid,
17 wdirid,
17 wdirid,
18 wdirrev,
18 wdirrev,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 dagop,
22 dagop,
23 error,
23 error,
24 formatter,
24 formatter,
25 graphmod,
25 graphmod,
26 match as matchmod,
26 match as matchmod,
27 mdiff,
27 mdiff,
28 patch,
28 patch,
29 pathutil,
29 pathutil,
30 pycompat,
30 pycompat,
31 revset,
31 revset,
32 revsetlang,
32 revsetlang,
33 scmutil,
33 scmutil,
34 smartset,
34 smartset,
35 templatekw,
35 templatekw,
36 templater,
36 templater,
37 util,
37 util,
38 )
38 )
39 from .utils import (
39 from .utils import (
40 dateutil,
40 dateutil,
41 stringutil,
41 stringutil,
42 )
42 )
43
43
44
44
45 def getlimit(opts):
45 def getlimit(opts):
46 """get the log limit according to option -l/--limit"""
46 """get the log limit according to option -l/--limit"""
47 limit = opts.get(b'limit')
47 limit = opts.get(b'limit')
48 if limit:
48 if limit:
49 try:
49 try:
50 limit = int(limit)
50 limit = int(limit)
51 except ValueError:
51 except ValueError:
52 raise error.Abort(_(b'limit must be a positive integer'))
52 raise error.Abort(_(b'limit must be a positive integer'))
53 if limit <= 0:
53 if limit <= 0:
54 raise error.Abort(_(b'limit must be positive'))
54 raise error.Abort(_(b'limit must be positive'))
55 else:
55 else:
56 limit = None
56 limit = None
57 return limit
57 return limit
58
58
59
59
60 def diffordiffstat(
60 def diffordiffstat(
61 ui,
61 ui,
62 repo,
62 repo,
63 diffopts,
63 diffopts,
64 node1,
64 node1,
65 node2,
65 node2,
66 match,
66 match,
67 changes=None,
67 changes=None,
68 stat=False,
68 stat=False,
69 fp=None,
69 fp=None,
70 graphwidth=0,
70 graphwidth=0,
71 prefix=b'',
71 prefix=b'',
72 root=b'',
72 root=b'',
73 listsubrepos=False,
73 listsubrepos=False,
74 hunksfilterfn=None,
74 hunksfilterfn=None,
75 ):
75 ):
76 '''show diff or diffstat.'''
76 '''show diff or diffstat.'''
77 ctx1 = repo[node1]
77 ctx1 = repo[node1]
78 ctx2 = repo[node2]
78 ctx2 = repo[node2]
79 if root:
79 if root:
80 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
80 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
81 else:
81 else:
82 relroot = b''
82 relroot = b''
83 copysourcematch = None
83 copysourcematch = None
84
84
85 def compose(f, g):
85 def compose(f, g):
86 return lambda x: f(g(x))
86 return lambda x: f(g(x))
87
87
88 def pathfn(f):
88 def pathfn(f):
89 return posixpath.join(prefix, f)
89 return posixpath.join(prefix, f)
90
90
91 if relroot != b'':
91 if relroot != b'':
92 # XXX relative roots currently don't work if the root is within a
92 # XXX relative roots currently don't work if the root is within a
93 # subrepo
93 # subrepo
94 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
94 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
95 uirelroot = uipathfn(pathfn(relroot))
95 uirelroot = uipathfn(pathfn(relroot))
96 relroot += b'/'
96 relroot += b'/'
97 for matchroot in match.files():
97 for matchroot in match.files():
98 if not matchroot.startswith(relroot):
98 if not matchroot.startswith(relroot):
99 ui.warn(
99 ui.warn(
100 _(b'warning: %s not inside relative root %s\n')
100 _(b'warning: %s not inside relative root %s\n')
101 % (uipathfn(pathfn(matchroot)), uirelroot)
101 % (uipathfn(pathfn(matchroot)), uirelroot)
102 )
102 )
103
103
104 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
104 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
105 match = matchmod.intersectmatchers(match, relrootmatch)
105 match = matchmod.intersectmatchers(match, relrootmatch)
106 copysourcematch = relrootmatch
106 copysourcematch = relrootmatch
107
107
108 checkroot = repo.ui.configbool(
108 checkroot = repo.ui.configbool(
109 b'devel', b'all-warnings'
109 b'devel', b'all-warnings'
110 ) or repo.ui.configbool(b'devel', b'check-relroot')
110 ) or repo.ui.configbool(b'devel', b'check-relroot')
111
111
112 def relrootpathfn(f):
112 def relrootpathfn(f):
113 if checkroot and not f.startswith(relroot):
113 if checkroot and not f.startswith(relroot):
114 raise AssertionError(
114 raise AssertionError(
115 b"file %s doesn't start with relroot %s" % (f, relroot)
115 b"file %s doesn't start with relroot %s" % (f, relroot)
116 )
116 )
117 return f[len(relroot) :]
117 return f[len(relroot) :]
118
118
119 pathfn = compose(relrootpathfn, pathfn)
119 pathfn = compose(relrootpathfn, pathfn)
120
120
121 if stat:
121 if stat:
122 diffopts = diffopts.copy(context=0, noprefix=False)
122 diffopts = diffopts.copy(context=0, noprefix=False)
123 width = 80
123 width = 80
124 if not ui.plain():
124 if not ui.plain():
125 width = ui.termwidth() - graphwidth
125 width = ui.termwidth() - graphwidth
126 # If an explicit --root was given, don't respect ui.relative-paths
126 # If an explicit --root was given, don't respect ui.relative-paths
127 if not relroot:
127 if not relroot:
128 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
128 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
129
129
130 chunks = ctx2.diff(
130 chunks = ctx2.diff(
131 ctx1,
131 ctx1,
132 match,
132 match,
133 changes,
133 changes,
134 opts=diffopts,
134 opts=diffopts,
135 pathfn=pathfn,
135 pathfn=pathfn,
136 copysourcematch=copysourcematch,
136 copysourcematch=copysourcematch,
137 hunksfilterfn=hunksfilterfn,
137 hunksfilterfn=hunksfilterfn,
138 )
138 )
139
139
140 if fp is not None or ui.canwritewithoutlabels():
140 if fp is not None or ui.canwritewithoutlabels():
141 out = fp or ui
141 out = fp or ui
142 if stat:
142 if stat:
143 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
143 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
144 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
144 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
145 out.write(chunk)
145 out.write(chunk)
146 else:
146 else:
147 if stat:
147 if stat:
148 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
148 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
149 else:
149 else:
150 chunks = patch.difflabel(
150 chunks = patch.difflabel(
151 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
151 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
152 )
152 )
153 if ui.canbatchlabeledwrites():
153 if ui.canbatchlabeledwrites():
154
154
155 def gen():
155 def gen():
156 for chunk, label in chunks:
156 for chunk, label in chunks:
157 yield ui.label(chunk, label=label)
157 yield ui.label(chunk, label=label)
158
158
159 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
159 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
160 ui.write(chunk)
160 ui.write(chunk)
161 else:
161 else:
162 for chunk, label in chunks:
162 for chunk, label in chunks:
163 ui.write(chunk, label=label)
163 ui.write(chunk, label=label)
164
164
165 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
165 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
166 tempnode2 = node2
166 tempnode2 = node2
167 try:
167 try:
168 if node2 is not None:
168 if node2 is not None:
169 tempnode2 = ctx2.substate[subpath][1]
169 tempnode2 = ctx2.substate[subpath][1]
170 except KeyError:
170 except KeyError:
171 # A subrepo that existed in node1 was deleted between node1 and
171 # A subrepo that existed in node1 was deleted between node1 and
172 # node2 (inclusive). Thus, ctx2's substate won't contain that
172 # node2 (inclusive). Thus, ctx2's substate won't contain that
173 # subpath. The best we can do is to ignore it.
173 # subpath. The best we can do is to ignore it.
174 tempnode2 = None
174 tempnode2 = None
175 submatch = matchmod.subdirmatcher(subpath, match)
175 submatch = matchmod.subdirmatcher(subpath, match)
176 subprefix = repo.wvfs.reljoin(prefix, subpath)
176 subprefix = repo.wvfs.reljoin(prefix, subpath)
177 if listsubrepos or match.exact(subpath) or any(submatch.files()):
177 if listsubrepos or match.exact(subpath) or any(submatch.files()):
178 sub.diff(
178 sub.diff(
179 ui,
179 ui,
180 diffopts,
180 diffopts,
181 tempnode2,
181 tempnode2,
182 submatch,
182 submatch,
183 changes=changes,
183 changes=changes,
184 stat=stat,
184 stat=stat,
185 fp=fp,
185 fp=fp,
186 prefix=subprefix,
186 prefix=subprefix,
187 )
187 )
188
188
189
189
190 class changesetdiffer(object):
190 class changesetdiffer(object):
191 """Generate diff of changeset with pre-configured filtering functions"""
191 """Generate diff of changeset with pre-configured filtering functions"""
192
192
193 def _makefilematcher(self, ctx):
193 def _makefilematcher(self, ctx):
194 return scmutil.matchall(ctx.repo())
194 return scmutil.matchall(ctx.repo())
195
195
196 def _makehunksfilter(self, ctx):
196 def _makehunksfilter(self, ctx):
197 return None
197 return None
198
198
199 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
199 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
200 repo = ctx.repo()
200 repo = ctx.repo()
201 node = ctx.node()
201 node = ctx.node()
202 prev = ctx.p1().node()
202 prev = ctx.p1().node()
203 diffordiffstat(
203 diffordiffstat(
204 ui,
204 ui,
205 repo,
205 repo,
206 diffopts,
206 diffopts,
207 prev,
207 prev,
208 node,
208 node,
209 match=self._makefilematcher(ctx),
209 match=self._makefilematcher(ctx),
210 stat=stat,
210 stat=stat,
211 graphwidth=graphwidth,
211 graphwidth=graphwidth,
212 hunksfilterfn=self._makehunksfilter(ctx),
212 hunksfilterfn=self._makehunksfilter(ctx),
213 )
213 )
214
214
215
215
216 def changesetlabels(ctx):
216 def changesetlabels(ctx):
217 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
217 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
218 if ctx.obsolete():
218 if ctx.obsolete():
219 labels.append(b'changeset.obsolete')
219 labels.append(b'changeset.obsolete')
220 if ctx.isunstable():
220 if ctx.isunstable():
221 labels.append(b'changeset.unstable')
221 labels.append(b'changeset.unstable')
222 for instability in ctx.instabilities():
222 for instability in ctx.instabilities():
223 labels.append(b'instability.%s' % instability)
223 labels.append(b'instability.%s' % instability)
224 return b' '.join(labels)
224 return b' '.join(labels)
225
225
226
226
227 class changesetprinter(object):
227 class changesetprinter(object):
228 '''show changeset information when templating not requested.'''
228 '''show changeset information when templating not requested.'''
229
229
230 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
230 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
231 self.ui = ui
231 self.ui = ui
232 self.repo = repo
232 self.repo = repo
233 self.buffered = buffered
233 self.buffered = buffered
234 self._differ = differ or changesetdiffer()
234 self._differ = differ or changesetdiffer()
235 self._diffopts = patch.diffallopts(ui, diffopts)
235 self._diffopts = patch.diffallopts(ui, diffopts)
236 self._includestat = diffopts and diffopts.get(b'stat')
236 self._includestat = diffopts and diffopts.get(b'stat')
237 self._includediff = diffopts and diffopts.get(b'patch')
237 self._includediff = diffopts and diffopts.get(b'patch')
238 self.header = {}
238 self.header = {}
239 self.hunk = {}
239 self.hunk = {}
240 self.lastheader = None
240 self.lastheader = None
241 self.footer = None
241 self.footer = None
242 self._columns = templatekw.getlogcolumns()
242 self._columns = templatekw.getlogcolumns()
243
243
244 def flush(self, ctx):
244 def flush(self, ctx):
245 rev = ctx.rev()
245 rev = ctx.rev()
246 if rev in self.header:
246 if rev in self.header:
247 h = self.header[rev]
247 h = self.header[rev]
248 if h != self.lastheader:
248 if h != self.lastheader:
249 self.lastheader = h
249 self.lastheader = h
250 self.ui.write(h)
250 self.ui.write(h)
251 del self.header[rev]
251 del self.header[rev]
252 if rev in self.hunk:
252 if rev in self.hunk:
253 self.ui.write(self.hunk[rev])
253 self.ui.write(self.hunk[rev])
254 del self.hunk[rev]
254 del self.hunk[rev]
255
255
256 def close(self):
256 def close(self):
257 if self.footer:
257 if self.footer:
258 self.ui.write(self.footer)
258 self.ui.write(self.footer)
259
259
260 def show(self, ctx, copies=None, **props):
260 def show(self, ctx, copies=None, **props):
261 props = pycompat.byteskwargs(props)
261 props = pycompat.byteskwargs(props)
262 if self.buffered:
262 if self.buffered:
263 self.ui.pushbuffer(labeled=True)
263 self.ui.pushbuffer(labeled=True)
264 self._show(ctx, copies, props)
264 self._show(ctx, copies, props)
265 self.hunk[ctx.rev()] = self.ui.popbuffer()
265 self.hunk[ctx.rev()] = self.ui.popbuffer()
266 else:
266 else:
267 self._show(ctx, copies, props)
267 self._show(ctx, copies, props)
268
268
269 def _show(self, ctx, copies, props):
269 def _show(self, ctx, copies, props):
270 '''show a single changeset or file revision'''
270 '''show a single changeset or file revision'''
271 changenode = ctx.node()
271 changenode = ctx.node()
272 graphwidth = props.get(b'graphwidth', 0)
272 graphwidth = props.get(b'graphwidth', 0)
273
273
274 if self.ui.quiet:
274 if self.ui.quiet:
275 self.ui.write(
275 self.ui.write(
276 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
276 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
277 )
277 )
278 return
278 return
279
279
280 columns = self._columns
280 columns = self._columns
281 self.ui.write(
281 self.ui.write(
282 columns[b'changeset'] % scmutil.formatchangeid(ctx),
282 columns[b'changeset'] % scmutil.formatchangeid(ctx),
283 label=changesetlabels(ctx),
283 label=changesetlabels(ctx),
284 )
284 )
285
285
286 # branches are shown first before any other names due to backwards
286 # branches are shown first before any other names due to backwards
287 # compatibility
287 # compatibility
288 branch = ctx.branch()
288 branch = ctx.branch()
289 # don't show the default branch name
289 # don't show the default branch name
290 if branch != b'default':
290 if branch != b'default':
291 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
291 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
292
292
293 for nsname, ns in pycompat.iteritems(self.repo.names):
293 for nsname, ns in pycompat.iteritems(self.repo.names):
294 # branches has special logic already handled above, so here we just
294 # branches has special logic already handled above, so here we just
295 # skip it
295 # skip it
296 if nsname == b'branches':
296 if nsname == b'branches':
297 continue
297 continue
298 # we will use the templatename as the color name since those two
298 # we will use the templatename as the color name since those two
299 # should be the same
299 # should be the same
300 for name in ns.names(self.repo, changenode):
300 for name in ns.names(self.repo, changenode):
301 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
301 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
302 if self.ui.debugflag:
302 if self.ui.debugflag:
303 self.ui.write(
303 self.ui.write(
304 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
304 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
305 )
305 )
306 for pctx in scmutil.meaningfulparents(self.repo, ctx):
306 for pctx in scmutil.meaningfulparents(self.repo, ctx):
307 label = b'log.parent changeset.%s' % pctx.phasestr()
307 label = b'log.parent changeset.%s' % pctx.phasestr()
308 self.ui.write(
308 self.ui.write(
309 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
309 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
310 )
310 )
311
311
312 if self.ui.debugflag:
312 if self.ui.debugflag:
313 mnode = ctx.manifestnode()
313 mnode = ctx.manifestnode()
314 if mnode is None:
314 if mnode is None:
315 mnode = wdirid
315 mnode = wdirid
316 mrev = wdirrev
316 mrev = wdirrev
317 else:
317 else:
318 mrev = self.repo.manifestlog.rev(mnode)
318 mrev = self.repo.manifestlog.rev(mnode)
319 self.ui.write(
319 self.ui.write(
320 columns[b'manifest']
320 columns[b'manifest']
321 % scmutil.formatrevnode(self.ui, mrev, mnode),
321 % scmutil.formatrevnode(self.ui, mrev, mnode),
322 label=b'ui.debug log.manifest',
322 label=b'ui.debug log.manifest',
323 )
323 )
324 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
324 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
325 self.ui.write(
325 self.ui.write(
326 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
326 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
327 )
327 )
328
328
329 if ctx.isunstable():
329 if ctx.isunstable():
330 instabilities = ctx.instabilities()
330 instabilities = ctx.instabilities()
331 self.ui.write(
331 self.ui.write(
332 columns[b'instability'] % b', '.join(instabilities),
332 columns[b'instability'] % b', '.join(instabilities),
333 label=b'log.instability',
333 label=b'log.instability',
334 )
334 )
335
335
336 elif ctx.obsolete():
336 elif ctx.obsolete():
337 self._showobsfate(ctx)
337 self._showobsfate(ctx)
338
338
339 self._exthook(ctx)
339 self._exthook(ctx)
340
340
341 if self.ui.debugflag:
341 if self.ui.debugflag:
342 files = ctx.p1().status(ctx)[:3]
342 files = ctx.p1().status(ctx)[:3]
343 for key, value in zip([b'files', b'files+', b'files-'], files):
343 for key, value in zip([b'files', b'files+', b'files-'], files):
344 if value:
344 if value:
345 self.ui.write(
345 self.ui.write(
346 columns[key] % b" ".join(value),
346 columns[key] % b" ".join(value),
347 label=b'ui.debug log.files',
347 label=b'ui.debug log.files',
348 )
348 )
349 elif ctx.files() and self.ui.verbose:
349 elif ctx.files() and self.ui.verbose:
350 self.ui.write(
350 self.ui.write(
351 columns[b'files'] % b" ".join(ctx.files()),
351 columns[b'files'] % b" ".join(ctx.files()),
352 label=b'ui.note log.files',
352 label=b'ui.note log.files',
353 )
353 )
354 if copies and self.ui.verbose:
354 if copies and self.ui.verbose:
355 copies = [b'%s (%s)' % c for c in copies]
355 copies = [b'%s (%s)' % c for c in copies]
356 self.ui.write(
356 self.ui.write(
357 columns[b'copies'] % b' '.join(copies),
357 columns[b'copies'] % b' '.join(copies),
358 label=b'ui.note log.copies',
358 label=b'ui.note log.copies',
359 )
359 )
360
360
361 extra = ctx.extra()
361 extra = ctx.extra()
362 if extra and self.ui.debugflag:
362 if extra and self.ui.debugflag:
363 for key, value in sorted(extra.items()):
363 for key, value in sorted(extra.items()):
364 self.ui.write(
364 self.ui.write(
365 columns[b'extra'] % (key, stringutil.escapestr(value)),
365 columns[b'extra'] % (key, stringutil.escapestr(value)),
366 label=b'ui.debug log.extra',
366 label=b'ui.debug log.extra',
367 )
367 )
368
368
369 description = ctx.description().strip()
369 description = ctx.description().strip()
370 if description:
370 if description:
371 if self.ui.verbose:
371 if self.ui.verbose:
372 self.ui.write(
372 self.ui.write(
373 _(b"description:\n"), label=b'ui.note log.description'
373 _(b"description:\n"), label=b'ui.note log.description'
374 )
374 )
375 self.ui.write(description, label=b'ui.note log.description')
375 self.ui.write(description, label=b'ui.note log.description')
376 self.ui.write(b"\n\n")
376 self.ui.write(b"\n\n")
377 else:
377 else:
378 self.ui.write(
378 self.ui.write(
379 columns[b'summary'] % description.splitlines()[0],
379 columns[b'summary'] % description.splitlines()[0],
380 label=b'log.summary',
380 label=b'log.summary',
381 )
381 )
382 self.ui.write(b"\n")
382 self.ui.write(b"\n")
383
383
384 self._showpatch(ctx, graphwidth)
384 self._showpatch(ctx, graphwidth)
385
385
386 def _showobsfate(self, ctx):
386 def _showobsfate(self, ctx):
387 # TODO: do not depend on templater
387 # TODO: do not depend on templater
388 tres = formatter.templateresources(self.repo.ui, self.repo)
388 tres = formatter.templateresources(self.repo.ui, self.repo)
389 t = formatter.maketemplater(
389 t = formatter.maketemplater(
390 self.repo.ui,
390 self.repo.ui,
391 b'{join(obsfate, "\n")}',
391 b'{join(obsfate, "\n")}',
392 defaults=templatekw.keywords,
392 defaults=templatekw.keywords,
393 resources=tres,
393 resources=tres,
394 )
394 )
395 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
395 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
396
396
397 if obsfate:
397 if obsfate:
398 for obsfateline in obsfate:
398 for obsfateline in obsfate:
399 self.ui.write(
399 self.ui.write(
400 self._columns[b'obsolete'] % obsfateline,
400 self._columns[b'obsolete'] % obsfateline,
401 label=b'log.obsfate',
401 label=b'log.obsfate',
402 )
402 )
403
403
404 def _exthook(self, ctx):
404 def _exthook(self, ctx):
405 '''empty method used by extension as a hook point
405 '''empty method used by extension as a hook point
406 '''
406 '''
407
407
408 def _showpatch(self, ctx, graphwidth=0):
408 def _showpatch(self, ctx, graphwidth=0):
409 if self._includestat:
409 if self._includestat:
410 self._differ.showdiff(
410 self._differ.showdiff(
411 self.ui, ctx, self._diffopts, graphwidth, stat=True
411 self.ui, ctx, self._diffopts, graphwidth, stat=True
412 )
412 )
413 if self._includestat and self._includediff:
413 if self._includestat and self._includediff:
414 self.ui.write(b"\n")
414 self.ui.write(b"\n")
415 if self._includediff:
415 if self._includediff:
416 self._differ.showdiff(
416 self._differ.showdiff(
417 self.ui, ctx, self._diffopts, graphwidth, stat=False
417 self.ui, ctx, self._diffopts, graphwidth, stat=False
418 )
418 )
419 if self._includestat or self._includediff:
419 if self._includestat or self._includediff:
420 self.ui.write(b"\n")
420 self.ui.write(b"\n")
421
421
422
422
423 class changesetformatter(changesetprinter):
423 class changesetformatter(changesetprinter):
424 """Format changeset information by generic formatter"""
424 """Format changeset information by generic formatter"""
425
425
426 def __init__(
426 def __init__(
427 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
427 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
428 ):
428 ):
429 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
429 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
430 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
430 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
431 self._fm = fm
431 self._fm = fm
432
432
433 def close(self):
433 def close(self):
434 self._fm.end()
434 self._fm.end()
435
435
436 def _show(self, ctx, copies, props):
436 def _show(self, ctx, copies, props):
437 '''show a single changeset or file revision'''
437 '''show a single changeset or file revision'''
438 fm = self._fm
438 fm = self._fm
439 fm.startitem()
439 fm.startitem()
440 fm.context(ctx=ctx)
440 fm.context(ctx=ctx)
441 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
441 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
442
442
443 datahint = fm.datahint()
443 datahint = fm.datahint()
444 if self.ui.quiet and not datahint:
444 if self.ui.quiet and not datahint:
445 return
445 return
446
446
447 fm.data(
447 fm.data(
448 branch=ctx.branch(),
448 branch=ctx.branch(),
449 phase=ctx.phasestr(),
449 phase=ctx.phasestr(),
450 user=ctx.user(),
450 user=ctx.user(),
451 date=fm.formatdate(ctx.date()),
451 date=fm.formatdate(ctx.date()),
452 desc=ctx.description(),
452 desc=ctx.description(),
453 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
453 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
454 tags=fm.formatlist(ctx.tags(), name=b'tag'),
454 tags=fm.formatlist(ctx.tags(), name=b'tag'),
455 parents=fm.formatlist(
455 parents=fm.formatlist(
456 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
456 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
457 ),
457 ),
458 )
458 )
459
459
460 if self.ui.debugflag or b'manifest' in datahint:
460 if self.ui.debugflag or b'manifest' in datahint:
461 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid))
461 fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid))
462 if self.ui.debugflag or b'extra' in datahint:
462 if self.ui.debugflag or b'extra' in datahint:
463 fm.data(extra=fm.formatdict(ctx.extra()))
463 fm.data(extra=fm.formatdict(ctx.extra()))
464
464
465 if (
465 if (
466 self.ui.debugflag
466 self.ui.debugflag
467 or b'modified' in datahint
467 or b'modified' in datahint
468 or b'added' in datahint
468 or b'added' in datahint
469 or b'removed' in datahint
469 or b'removed' in datahint
470 ):
470 ):
471 files = ctx.p1().status(ctx)
471 files = ctx.p1().status(ctx)
472 fm.data(
472 fm.data(
473 modified=fm.formatlist(files[0], name=b'file'),
473 modified=fm.formatlist(files[0], name=b'file'),
474 added=fm.formatlist(files[1], name=b'file'),
474 added=fm.formatlist(files[1], name=b'file'),
475 removed=fm.formatlist(files[2], name=b'file'),
475 removed=fm.formatlist(files[2], name=b'file'),
476 )
476 )
477
477
478 verbose = not self.ui.debugflag and self.ui.verbose
478 verbose = not self.ui.debugflag and self.ui.verbose
479 if verbose or b'files' in datahint:
479 if verbose or b'files' in datahint:
480 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
480 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
481 if verbose and copies or b'copies' in datahint:
481 if verbose and copies or b'copies' in datahint:
482 fm.data(
482 fm.data(
483 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
483 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
484 )
484 )
485
485
486 if self._includestat or b'diffstat' in datahint:
486 if self._includestat or b'diffstat' in datahint:
487 self.ui.pushbuffer()
487 self.ui.pushbuffer()
488 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
488 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
489 fm.data(diffstat=self.ui.popbuffer())
489 fm.data(diffstat=self.ui.popbuffer())
490 if self._includediff or b'diff' in datahint:
490 if self._includediff or b'diff' in datahint:
491 self.ui.pushbuffer()
491 self.ui.pushbuffer()
492 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
492 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
493 fm.data(diff=self.ui.popbuffer())
493 fm.data(diff=self.ui.popbuffer())
494
494
495
495
496 class changesettemplater(changesetprinter):
496 class changesettemplater(changesetprinter):
497 '''format changeset information.
497 '''format changeset information.
498
498
499 Note: there are a variety of convenience functions to build a
499 Note: there are a variety of convenience functions to build a
500 changesettemplater for common cases. See functions such as:
500 changesettemplater for common cases. See functions such as:
501 maketemplater, changesetdisplayer, buildcommittemplate, or other
501 maketemplater, changesetdisplayer, buildcommittemplate, or other
502 functions that use changesest_templater.
502 functions that use changesest_templater.
503 '''
503 '''
504
504
505 # Arguments before "buffered" used to be positional. Consider not
505 # Arguments before "buffered" used to be positional. Consider not
506 # adding/removing arguments before "buffered" to not break callers.
506 # adding/removing arguments before "buffered" to not break callers.
507 def __init__(
507 def __init__(
508 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
508 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
509 ):
509 ):
510 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
510 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
511 # tres is shared with _graphnodeformatter()
511 # tres is shared with _graphnodeformatter()
512 self._tresources = tres = formatter.templateresources(ui, repo)
512 self._tresources = tres = formatter.templateresources(ui, repo)
513 self.t = formatter.loadtemplater(
513 self.t = formatter.loadtemplater(
514 ui,
514 ui,
515 tmplspec,
515 tmplspec,
516 defaults=templatekw.keywords,
516 defaults=templatekw.keywords,
517 resources=tres,
517 resources=tres,
518 cache=templatekw.defaulttempl,
518 cache=templatekw.defaulttempl,
519 )
519 )
520 self._counter = itertools.count()
520 self._counter = itertools.count()
521
521
522 self._tref = tmplspec.ref
522 self._tref = tmplspec.ref
523 self._parts = {
523 self._parts = {
524 b'header': b'',
524 b'header': b'',
525 b'footer': b'',
525 b'footer': b'',
526 tmplspec.ref: tmplspec.ref,
526 tmplspec.ref: tmplspec.ref,
527 b'docheader': b'',
527 b'docheader': b'',
528 b'docfooter': b'',
528 b'docfooter': b'',
529 b'separator': b'',
529 b'separator': b'',
530 }
530 }
531 if tmplspec.mapfile:
531 if tmplspec.mapfile:
532 # find correct templates for current mode, for backward
532 # find correct templates for current mode, for backward
533 # compatibility with 'log -v/-q/--debug' using a mapfile
533 # compatibility with 'log -v/-q/--debug' using a mapfile
534 tmplmodes = [
534 tmplmodes = [
535 (True, b''),
535 (True, b''),
536 (self.ui.verbose, b'_verbose'),
536 (self.ui.verbose, b'_verbose'),
537 (self.ui.quiet, b'_quiet'),
537 (self.ui.quiet, b'_quiet'),
538 (self.ui.debugflag, b'_debug'),
538 (self.ui.debugflag, b'_debug'),
539 ]
539 ]
540 for mode, postfix in tmplmodes:
540 for mode, postfix in tmplmodes:
541 for t in self._parts:
541 for t in self._parts:
542 cur = t + postfix
542 cur = t + postfix
543 if mode and cur in self.t:
543 if mode and cur in self.t:
544 self._parts[t] = cur
544 self._parts[t] = cur
545 else:
545 else:
546 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
546 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
547 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
547 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
548 self._parts.update(m)
548 self._parts.update(m)
549
549
550 if self._parts[b'docheader']:
550 if self._parts[b'docheader']:
551 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
551 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
552
552
553 def close(self):
553 def close(self):
554 if self._parts[b'docfooter']:
554 if self._parts[b'docfooter']:
555 if not self.footer:
555 if not self.footer:
556 self.footer = b""
556 self.footer = b""
557 self.footer += self.t.render(self._parts[b'docfooter'], {})
557 self.footer += self.t.render(self._parts[b'docfooter'], {})
558 return super(changesettemplater, self).close()
558 return super(changesettemplater, self).close()
559
559
560 def _show(self, ctx, copies, props):
560 def _show(self, ctx, copies, props):
561 '''show a single changeset or file revision'''
561 '''show a single changeset or file revision'''
562 props = props.copy()
562 props = props.copy()
563 props[b'ctx'] = ctx
563 props[b'ctx'] = ctx
564 props[b'index'] = index = next(self._counter)
564 props[b'index'] = index = next(self._counter)
565 props[b'revcache'] = {b'copies': copies}
565 props[b'revcache'] = {b'copies': copies}
566 graphwidth = props.get(b'graphwidth', 0)
566 graphwidth = props.get(b'graphwidth', 0)
567
567
568 # write separator, which wouldn't work well with the header part below
568 # write separator, which wouldn't work well with the header part below
569 # since there's inherently a conflict between header (across items) and
569 # since there's inherently a conflict between header (across items) and
570 # separator (per item)
570 # separator (per item)
571 if self._parts[b'separator'] and index > 0:
571 if self._parts[b'separator'] and index > 0:
572 self.ui.write(self.t.render(self._parts[b'separator'], {}))
572 self.ui.write(self.t.render(self._parts[b'separator'], {}))
573
573
574 # write header
574 # write header
575 if self._parts[b'header']:
575 if self._parts[b'header']:
576 h = self.t.render(self._parts[b'header'], props)
576 h = self.t.render(self._parts[b'header'], props)
577 if self.buffered:
577 if self.buffered:
578 self.header[ctx.rev()] = h
578 self.header[ctx.rev()] = h
579 else:
579 else:
580 if self.lastheader != h:
580 if self.lastheader != h:
581 self.lastheader = h
581 self.lastheader = h
582 self.ui.write(h)
582 self.ui.write(h)
583
583
584 # write changeset metadata, then patch if requested
584 # write changeset metadata, then patch if requested
585 key = self._parts[self._tref]
585 key = self._parts[self._tref]
586 self.ui.write(self.t.render(key, props))
586 self.ui.write(self.t.render(key, props))
587 self._showpatch(ctx, graphwidth)
587 self._showpatch(ctx, graphwidth)
588
588
589 if self._parts[b'footer']:
589 if self._parts[b'footer']:
590 if not self.footer:
590 if not self.footer:
591 self.footer = self.t.render(self._parts[b'footer'], props)
591 self.footer = self.t.render(self._parts[b'footer'], props)
592
592
593
593
594 def templatespec(tmpl, mapfile):
594 def templatespec(tmpl, mapfile):
595 if pycompat.ispy3:
595 if pycompat.ispy3:
596 assert not isinstance(tmpl, str), b'tmpl must not be a str'
596 assert not isinstance(tmpl, str), b'tmpl must not be a str'
597 if mapfile:
597 if mapfile:
598 return formatter.templatespec(b'changeset', tmpl, mapfile)
598 return formatter.templatespec(b'changeset', tmpl, mapfile)
599 else:
599 else:
600 return formatter.templatespec(b'', tmpl, None)
600 return formatter.templatespec(b'', tmpl, None)
601
601
602
602
603 def _lookuptemplate(ui, tmpl, style):
603 def _lookuptemplate(ui, tmpl, style):
604 """Find the template matching the given template spec or style
604 """Find the template matching the given template spec or style
605
605
606 See formatter.lookuptemplate() for details.
606 See formatter.lookuptemplate() for details.
607 """
607 """
608
608
609 # ui settings
609 # ui settings
610 if not tmpl and not style: # template are stronger than style
610 if not tmpl and not style: # template are stronger than style
611 tmpl = ui.config(b'ui', b'logtemplate')
611 tmpl = ui.config(b'ui', b'logtemplate')
612 if tmpl:
612 if tmpl:
613 return templatespec(templater.unquotestring(tmpl), None)
613 return templatespec(templater.unquotestring(tmpl), None)
614 else:
614 else:
615 style = util.expandpath(ui.config(b'ui', b'style'))
615 style = util.expandpath(ui.config(b'ui', b'style'))
616
616
617 if not tmpl and style:
617 if not tmpl and style:
618 mapfile = style
618 mapfile = style
619 if not os.path.split(mapfile)[0]:
619 if not os.path.split(mapfile)[0]:
620 mapname = templater.templatepath(
620 mapname = templater.templatepath(
621 b'map-cmdline.' + mapfile
621 b'map-cmdline.' + mapfile
622 ) or templater.templatepath(mapfile)
622 ) or templater.templatepath(mapfile)
623 if mapname:
623 if mapname:
624 mapfile = mapname
624 mapfile = mapname
625 return templatespec(None, mapfile)
625 return templatespec(None, mapfile)
626
626
627 return formatter.lookuptemplate(ui, b'changeset', tmpl)
627 return formatter.lookuptemplate(ui, b'changeset', tmpl)
628
628
629
629
630 def maketemplater(ui, repo, tmpl, buffered=False):
630 def maketemplater(ui, repo, tmpl, buffered=False):
631 """Create a changesettemplater from a literal template 'tmpl'
631 """Create a changesettemplater from a literal template 'tmpl'
632 byte-string."""
632 byte-string."""
633 spec = templatespec(tmpl, None)
633 spec = templatespec(tmpl, None)
634 return changesettemplater(ui, repo, spec, buffered=buffered)
634 return changesettemplater(ui, repo, spec, buffered=buffered)
635
635
636
636
637 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
637 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
638 """show one changeset using template or regular display.
638 """show one changeset using template or regular display.
639
639
640 Display format will be the first non-empty hit of:
640 Display format will be the first non-empty hit of:
641 1. option 'template'
641 1. option 'template'
642 2. option 'style'
642 2. option 'style'
643 3. [ui] setting 'logtemplate'
643 3. [ui] setting 'logtemplate'
644 4. [ui] setting 'style'
644 4. [ui] setting 'style'
645 If all of these values are either the unset or the empty string,
645 If all of these values are either the unset or the empty string,
646 regular display via changesetprinter() is done.
646 regular display via changesetprinter() is done.
647 """
647 """
648 postargs = (differ, opts, buffered)
648 postargs = (differ, opts, buffered)
649 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
649 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
650
650
651 # machine-readable formats have slightly different keyword set than
651 # machine-readable formats have slightly different keyword set than
652 # plain templates, which are handled by changesetformatter.
652 # plain templates, which are handled by changesetformatter.
653 # note that {b'pickle', b'debug'} can also be added to the list if needed.
653 # note that {b'pickle', b'debug'} can also be added to the list if needed.
654 if spec.ref in {b'cbor', b'json'}:
654 if spec.ref in {b'cbor', b'json'}:
655 fm = ui.formatter(b'log', opts)
655 fm = ui.formatter(b'log', opts)
656 return changesetformatter(ui, repo, fm, *postargs)
656 return changesetformatter(ui, repo, fm, *postargs)
657
657
658 if not spec.ref and not spec.tmpl and not spec.mapfile:
658 if not spec.ref and not spec.tmpl and not spec.mapfile:
659 return changesetprinter(ui, repo, *postargs)
659 return changesetprinter(ui, repo, *postargs)
660
660
661 return changesettemplater(ui, repo, spec, *postargs)
661 return changesettemplater(ui, repo, spec, *postargs)
662
662
663
663
664 def _makematcher(repo, revs, pats, opts):
664 def _makematcher(repo, revs, pats, opts):
665 """Build matcher and expanded patterns from log options
665 """Build matcher and expanded patterns from log options
666
666
667 If --follow, revs are the revisions to follow from.
667 If --follow, revs are the revisions to follow from.
668
668
669 Returns (match, pats, slowpath) where
669 Returns (match, pats, slowpath) where
670 - match: a matcher built from the given pats and -I/-X opts
670 - match: a matcher built from the given pats and -I/-X opts
671 - pats: patterns used (globs are expanded on Windows)
671 - pats: patterns used (globs are expanded on Windows)
672 - slowpath: True if patterns aren't as simple as scanning filelogs
672 - slowpath: True if patterns aren't as simple as scanning filelogs
673 """
673 """
674 # pats/include/exclude are passed to match.match() directly in
674 # pats/include/exclude are passed to match.match() directly in
675 # _matchfiles() revset but walkchangerevs() builds its matcher with
675 # _matchfiles() revset but walkchangerevs() builds its matcher with
676 # scmutil.match(). The difference is input pats are globbed on
676 # scmutil.match(). The difference is input pats are globbed on
677 # platforms without shell expansion (windows).
677 # platforms without shell expansion (windows).
678 wctx = repo[None]
678 wctx = repo[None]
679 match, pats = scmutil.matchandpats(wctx, pats, opts)
679 match, pats = scmutil.matchandpats(wctx, pats, opts)
680 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
680 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
681 if not slowpath:
681 if not slowpath:
682 follow = opts.get(b'follow') or opts.get(b'follow_first')
682 follow = opts.get(b'follow') or opts.get(b'follow_first')
683 startctxs = []
683 startctxs = []
684 if follow and opts.get(b'rev'):
684 if follow and opts.get(b'rev'):
685 startctxs = [repo[r] for r in revs]
685 startctxs = [repo[r] for r in revs]
686 for f in match.files():
686 for f in match.files():
687 if follow and startctxs:
687 if follow and startctxs:
688 # No idea if the path was a directory at that revision, so
688 # No idea if the path was a directory at that revision, so
689 # take the slow path.
689 # take the slow path.
690 if any(f not in c for c in startctxs):
690 if any(f not in c for c in startctxs):
691 slowpath = True
691 slowpath = True
692 continue
692 continue
693 elif follow and f not in wctx:
693 elif follow and f not in wctx:
694 # If the file exists, it may be a directory, so let it
694 # If the file exists, it may be a directory, so let it
695 # take the slow path.
695 # take the slow path.
696 if os.path.exists(repo.wjoin(f)):
696 if os.path.exists(repo.wjoin(f)):
697 slowpath = True
697 slowpath = True
698 continue
698 continue
699 else:
699 else:
700 raise error.Abort(
700 raise error.Abort(
701 _(
701 _(
702 b'cannot follow file not in parent '
702 b'cannot follow file not in parent '
703 b'revision: "%s"'
703 b'revision: "%s"'
704 )
704 )
705 % f
705 % f
706 )
706 )
707 filelog = repo.file(f)
707 filelog = repo.file(f)
708 if not filelog:
708 if not filelog:
709 # A zero count may be a directory or deleted file, so
709 # A zero count may be a directory or deleted file, so
710 # try to find matching entries on the slow path.
710 # try to find matching entries on the slow path.
711 if follow:
711 if follow:
712 raise error.Abort(
712 raise error.Abort(
713 _(b'cannot follow nonexistent file: "%s"') % f
713 _(b'cannot follow nonexistent file: "%s"') % f
714 )
714 )
715 slowpath = True
715 slowpath = True
716
716
717 # We decided to fall back to the slowpath because at least one
717 # We decided to fall back to the slowpath because at least one
718 # of the paths was not a file. Check to see if at least one of them
718 # of the paths was not a file. Check to see if at least one of them
719 # existed in history - in that case, we'll continue down the
719 # existed in history - in that case, we'll continue down the
720 # slowpath; otherwise, we can turn off the slowpath
720 # slowpath; otherwise, we can turn off the slowpath
721 if slowpath:
721 if slowpath:
722 for path in match.files():
722 for path in match.files():
723 if path == b'.' or path in repo.store:
723 if path == b'.' or path in repo.store:
724 break
724 break
725 else:
725 else:
726 slowpath = False
726 slowpath = False
727
727
728 return match, pats, slowpath
728 return match, pats, slowpath
729
729
730
730
731 def _fileancestors(repo, revs, match, followfirst):
731 def _fileancestors(repo, revs, match, followfirst):
732 fctxs = []
732 fctxs = []
733 for r in revs:
733 for r in revs:
734 ctx = repo[r]
734 ctx = repo[r]
735 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
735 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
736
736
737 # When displaying a revision with --patch --follow FILE, we have
737 # When displaying a revision with --patch --follow FILE, we have
738 # to know which file of the revision must be diffed. With
738 # to know which file of the revision must be diffed. With
739 # --follow, we want the names of the ancestors of FILE in the
739 # --follow, we want the names of the ancestors of FILE in the
740 # revision, stored in "fcache". "fcache" is populated as a side effect
740 # revision, stored in "fcache". "fcache" is populated as a side effect
741 # of the graph traversal.
741 # of the graph traversal.
742 fcache = {}
742 fcache = {}
743
743
744 def filematcher(ctx):
744 def filematcher(ctx):
745 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
745 return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
746
746
747 def revgen():
747 def revgen():
748 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
748 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
749 fcache[rev] = [c.path() for c in cs]
749 fcache[rev] = [c.path() for c in cs]
750 yield rev
750 yield rev
751
751
752 return smartset.generatorset(revgen(), iterasc=False), filematcher
752 return smartset.generatorset(revgen(), iterasc=False), filematcher
753
753
754
754
755 def _makenofollowfilematcher(repo, pats, opts):
755 def _makenofollowfilematcher(repo, pats, opts):
756 '''hook for extensions to override the filematcher for non-follow cases'''
756 '''hook for extensions to override the filematcher for non-follow cases'''
757 return None
757 return None
758
758
759
759
760 _opt2logrevset = {
760 _opt2logrevset = {
761 b'no_merges': (b'not merge()', None),
761 b'no_merges': (b'not merge()', None),
762 b'only_merges': (b'merge()', None),
762 b'only_merges': (b'merge()', None),
763 b'_matchfiles': (None, b'_matchfiles(%ps)'),
763 b'_matchfiles': (None, b'_matchfiles(%ps)'),
764 b'date': (b'date(%s)', None),
764 b'date': (b'date(%s)', None),
765 b'branch': (b'branch(%s)', b'%lr'),
765 b'branch': (b'branch(%s)', b'%lr'),
766 b'_patslog': (b'filelog(%s)', b'%lr'),
766 b'_patslog': (b'filelog(%s)', b'%lr'),
767 b'keyword': (b'keyword(%s)', b'%lr'),
767 b'keyword': (b'keyword(%s)', b'%lr'),
768 b'prune': (b'ancestors(%s)', b'not %lr'),
768 b'prune': (b'ancestors(%s)', b'not %lr'),
769 b'user': (b'user(%s)', b'%lr'),
769 b'user': (b'user(%s)', b'%lr'),
770 }
770 }
771
771
772
772
773 def _makerevset(repo, match, pats, slowpath, opts):
773 def _makerevset(repo, match, pats, slowpath, opts):
774 """Return a revset string built from log options and file patterns"""
774 """Return a revset string built from log options and file patterns"""
775 opts = dict(opts)
775 opts = dict(opts)
776 # follow or not follow?
776 # follow or not follow?
777 follow = opts.get(b'follow') or opts.get(b'follow_first')
777 follow = opts.get(b'follow') or opts.get(b'follow_first')
778
778
779 # branch and only_branch are really aliases and must be handled at
779 # branch and only_branch are really aliases and must be handled at
780 # the same time
780 # the same time
781 opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
781 opts[b'branch'] = opts.get(b'branch', []) + opts.get(b'only_branch', [])
782 opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
782 opts[b'branch'] = [repo.lookupbranch(b) for b in opts[b'branch']]
783
783
784 if slowpath:
784 if slowpath:
785 # See walkchangerevs() slow path.
785 # See walkchangerevs() slow path.
786 #
786 #
787 # pats/include/exclude cannot be represented as separate
787 # pats/include/exclude cannot be represented as separate
788 # revset expressions as their filtering logic applies at file
788 # revset expressions as their filtering logic applies at file
789 # level. For instance "-I a -X b" matches a revision touching
789 # level. For instance "-I a -X b" matches a revision touching
790 # "a" and "b" while "file(a) and not file(b)" does
790 # "a" and "b" while "file(a) and not file(b)" does
791 # not. Besides, filesets are evaluated against the working
791 # not. Besides, filesets are evaluated against the working
792 # directory.
792 # directory.
793 matchargs = [b'r:', b'd:relpath']
793 matchargs = [b'r:', b'd:relpath']
794 for p in pats:
794 for p in pats:
795 matchargs.append(b'p:' + p)
795 matchargs.append(b'p:' + p)
796 for p in opts.get(b'include', []):
796 for p in opts.get(b'include', []):
797 matchargs.append(b'i:' + p)
797 matchargs.append(b'i:' + p)
798 for p in opts.get(b'exclude', []):
798 for p in opts.get(b'exclude', []):
799 matchargs.append(b'x:' + p)
799 matchargs.append(b'x:' + p)
800 opts[b'_matchfiles'] = matchargs
800 opts[b'_matchfiles'] = matchargs
801 elif not follow:
801 elif not follow:
802 opts[b'_patslog'] = list(pats)
802 opts[b'_patslog'] = list(pats)
803
803
804 expr = []
804 expr = []
805 for op, val in sorted(pycompat.iteritems(opts)):
805 for op, val in sorted(pycompat.iteritems(opts)):
806 if not val:
806 if not val:
807 continue
807 continue
808 if op not in _opt2logrevset:
808 if op not in _opt2logrevset:
809 continue
809 continue
810 revop, listop = _opt2logrevset[op]
810 revop, listop = _opt2logrevset[op]
811 if revop and b'%' not in revop:
811 if revop and b'%' not in revop:
812 expr.append(revop)
812 expr.append(revop)
813 elif not listop:
813 elif not listop:
814 expr.append(revsetlang.formatspec(revop, val))
814 expr.append(revsetlang.formatspec(revop, val))
815 else:
815 else:
816 if revop:
816 if revop:
817 val = [revsetlang.formatspec(revop, v) for v in val]
817 val = [revsetlang.formatspec(revop, v) for v in val]
818 expr.append(revsetlang.formatspec(listop, val))
818 expr.append(revsetlang.formatspec(listop, val))
819
819
820 if expr:
820 if expr:
821 expr = b'(' + b' and '.join(expr) + b')'
821 expr = b'(' + b' and '.join(expr) + b')'
822 else:
822 else:
823 expr = None
823 expr = None
824 return expr
824 return expr
825
825
826
826
827 def _initialrevs(repo, opts):
827 def _initialrevs(repo, opts):
828 """Return the initial set of revisions to be filtered or followed"""
828 """Return the initial set of revisions to be filtered or followed"""
829 follow = opts.get(b'follow') or opts.get(b'follow_first')
829 follow = opts.get(b'follow') or opts.get(b'follow_first')
830 if opts.get(b'rev'):
830 if opts.get(b'rev'):
831 revs = scmutil.revrange(repo, opts[b'rev'])
831 revs = scmutil.revrange(repo, opts[b'rev'])
832 elif follow and repo.dirstate.p1() == nullid:
832 elif follow and repo.dirstate.p1() == nullid:
833 revs = smartset.baseset()
833 revs = smartset.baseset()
834 elif follow:
834 elif follow:
835 revs = repo.revs(b'.')
835 revs = repo.revs(b'.')
836 else:
836 else:
837 revs = smartset.spanset(repo)
837 revs = smartset.spanset(repo)
838 revs.reverse()
838 revs.reverse()
839 return revs
839 return revs
840
840
841
841
842 def getrevs(repo, pats, opts):
842 def getrevs(repo, pats, opts):
843 """Return (revs, differ) where revs is a smartset
843 """Return (revs, differ) where revs is a smartset
844
844
845 differ is a changesetdiffer with pre-configured file matcher.
845 differ is a changesetdiffer with pre-configured file matcher.
846 """
846 """
847 follow = opts.get(b'follow') or opts.get(b'follow_first')
847 follow = opts.get(b'follow') or opts.get(b'follow_first')
848 followfirst = opts.get(b'follow_first')
848 followfirst = opts.get(b'follow_first')
849 limit = getlimit(opts)
849 limit = getlimit(opts)
850 revs = _initialrevs(repo, opts)
850 revs = _initialrevs(repo, opts)
851 if not revs:
851 if not revs:
852 return smartset.baseset(), None
852 return smartset.baseset(), None
853 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
853 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
854 filematcher = None
854 filematcher = None
855 if follow:
855 if follow:
856 if slowpath or match.always():
856 if slowpath or match.always():
857 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
857 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
858 else:
858 else:
859 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
859 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
860 revs.reverse()
860 revs.reverse()
861 if filematcher is None:
861 if filematcher is None:
862 filematcher = _makenofollowfilematcher(repo, pats, opts)
862 filematcher = _makenofollowfilematcher(repo, pats, opts)
863 if filematcher is None:
863 if filematcher is None:
864
864
865 def filematcher(ctx):
865 def filematcher(ctx):
866 return match
866 return match
867
867
868 expr = _makerevset(repo, match, pats, slowpath, opts)
868 expr = _makerevset(repo, match, pats, slowpath, opts)
869 if opts.get(b'graph'):
869 if opts.get(b'graph'):
870 # User-specified revs might be unsorted, but don't sort before
870 # User-specified revs might be unsorted, but don't sort before
871 # _makerevset because it might depend on the order of revs
871 # _makerevset because it might depend on the order of revs
872 if repo.ui.configbool(b'experimental', b'log.topo'):
872 if repo.ui.configbool(b'experimental', b'log.topo'):
873 if not revs.istopo():
873 if not revs.istopo():
874 revs = dagop.toposort(revs, repo.changelog.parentrevs)
874 revs = dagop.toposort(revs, repo.changelog.parentrevs)
875 # TODO: try to iterate the set lazily
875 # TODO: try to iterate the set lazily
876 revs = revset.baseset(list(revs), istopo=True)
876 revs = revset.baseset(list(revs), istopo=True)
877 elif not (revs.isdescending() or revs.istopo()):
877 elif not (revs.isdescending() or revs.istopo()):
878 revs.sort(reverse=True)
878 revs.sort(reverse=True)
879 if expr:
879 if expr:
880 matcher = revset.match(None, expr)
880 matcher = revset.match(None, expr)
881 revs = matcher(repo, revs)
881 revs = matcher(repo, revs)
882 if limit is not None:
882 if limit is not None:
883 revs = revs.slice(0, limit)
883 revs = revs.slice(0, limit)
884
884
885 differ = changesetdiffer()
885 differ = changesetdiffer()
886 differ._makefilematcher = filematcher
886 differ._makefilematcher = filematcher
887 return revs, differ
887 return revs, differ
888
888
889
889
890 def _parselinerangeopt(repo, opts):
890 def _parselinerangeopt(repo, opts):
891 """Parse --line-range log option and return a list of tuples (filename,
891 """Parse --line-range log option and return a list of tuples (filename,
892 (fromline, toline)).
892 (fromline, toline)).
893 """
893 """
894 linerangebyfname = []
894 linerangebyfname = []
895 for pat in opts.get(b'line_range', []):
895 for pat in opts.get(b'line_range', []):
896 try:
896 try:
897 pat, linerange = pat.rsplit(b',', 1)
897 pat, linerange = pat.rsplit(b',', 1)
898 except ValueError:
898 except ValueError:
899 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
899 raise error.Abort(_(b'malformatted line-range pattern %s') % pat)
900 try:
900 try:
901 fromline, toline = map(int, linerange.split(b':'))
901 fromline, toline = map(int, linerange.split(b':'))
902 except ValueError:
902 except ValueError:
903 raise error.Abort(_(b"invalid line range for %s") % pat)
903 raise error.Abort(_(b"invalid line range for %s") % pat)
904 msg = _(b"line range pattern '%s' must match exactly one file") % pat
904 msg = _(b"line range pattern '%s' must match exactly one file") % pat
905 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
905 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
906 linerangebyfname.append(
906 linerangebyfname.append(
907 (fname, util.processlinerange(fromline, toline))
907 (fname, util.processlinerange(fromline, toline))
908 )
908 )
909 return linerangebyfname
909 return linerangebyfname
910
910
911
911
912 def getlinerangerevs(repo, userrevs, opts):
912 def getlinerangerevs(repo, userrevs, opts):
913 """Return (revs, differ).
913 """Return (revs, differ).
914
914
915 "revs" are revisions obtained by processing "line-range" log options and
915 "revs" are revisions obtained by processing "line-range" log options and
916 walking block ancestors of each specified file/line-range.
916 walking block ancestors of each specified file/line-range.
917
917
918 "differ" is a changesetdiffer with pre-configured file matcher and hunks
918 "differ" is a changesetdiffer with pre-configured file matcher and hunks
919 filter.
919 filter.
920 """
920 """
921 wctx = repo[None]
921 wctx = repo[None]
922
922
923 # Two-levels map of "rev -> file ctx -> [line range]".
923 # Two-levels map of "rev -> file ctx -> [line range]".
924 linerangesbyrev = {}
924 linerangesbyrev = {}
925 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
925 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
926 if fname not in wctx:
926 if fname not in wctx:
927 raise error.Abort(
927 raise error.Abort(
928 _(b'cannot follow file not in parent revision: "%s"') % fname
928 _(b'cannot follow file not in parent revision: "%s"') % fname
929 )
929 )
930 fctx = wctx.filectx(fname)
930 fctx = wctx.filectx(fname)
931 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
931 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
932 rev = fctx.introrev()
932 rev = fctx.introrev()
933 if rev is None:
934 rev = wdirrev
933 if rev not in userrevs:
935 if rev not in userrevs:
934 continue
936 continue
935 linerangesbyrev.setdefault(rev, {}).setdefault(
937 linerangesbyrev.setdefault(rev, {}).setdefault(
936 fctx.path(), []
938 fctx.path(), []
937 ).append(linerange)
939 ).append(linerange)
938
940
939 def nofilterhunksfn(fctx, hunks):
941 def nofilterhunksfn(fctx, hunks):
940 return hunks
942 return hunks
941
943
942 def hunksfilter(ctx):
944 def hunksfilter(ctx):
943 fctxlineranges = linerangesbyrev.get(ctx.rev())
945 fctxlineranges = linerangesbyrev.get(scmutil.intrev(ctx))
944 if fctxlineranges is None:
946 if fctxlineranges is None:
945 return nofilterhunksfn
947 return nofilterhunksfn
946
948
947 def filterfn(fctx, hunks):
949 def filterfn(fctx, hunks):
948 lineranges = fctxlineranges.get(fctx.path())
950 lineranges = fctxlineranges.get(fctx.path())
949 if lineranges is not None:
951 if lineranges is not None:
950 for hr, lines in hunks:
952 for hr, lines in hunks:
951 if hr is None: # binary
953 if hr is None: # binary
952 yield hr, lines
954 yield hr, lines
953 continue
955 continue
954 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
956 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
955 yield hr, lines
957 yield hr, lines
956 else:
958 else:
957 for hunk in hunks:
959 for hunk in hunks:
958 yield hunk
960 yield hunk
959
961
960 return filterfn
962 return filterfn
961
963
962 def filematcher(ctx):
964 def filematcher(ctx):
963 files = list(linerangesbyrev.get(ctx.rev(), []))
965 files = list(linerangesbyrev.get(scmutil.intrev(ctx), []))
964 return scmutil.matchfiles(repo, files)
966 return scmutil.matchfiles(repo, files)
965
967
966 revs = sorted(linerangesbyrev, reverse=True)
968 revs = sorted(linerangesbyrev, reverse=True)
967
969
968 differ = changesetdiffer()
970 differ = changesetdiffer()
969 differ._makefilematcher = filematcher
971 differ._makefilematcher = filematcher
970 differ._makehunksfilter = hunksfilter
972 differ._makehunksfilter = hunksfilter
971 return revs, differ
973 return revs, differ
972
974
973
975
974 def _graphnodeformatter(ui, displayer):
976 def _graphnodeformatter(ui, displayer):
975 spec = ui.config(b'ui', b'graphnodetemplate')
977 spec = ui.config(b'ui', b'graphnodetemplate')
976 if not spec:
978 if not spec:
977 return templatekw.getgraphnode # fast path for "{graphnode}"
979 return templatekw.getgraphnode # fast path for "{graphnode}"
978
980
979 spec = templater.unquotestring(spec)
981 spec = templater.unquotestring(spec)
980 if isinstance(displayer, changesettemplater):
982 if isinstance(displayer, changesettemplater):
981 # reuse cache of slow templates
983 # reuse cache of slow templates
982 tres = displayer._tresources
984 tres = displayer._tresources
983 else:
985 else:
984 tres = formatter.templateresources(ui)
986 tres = formatter.templateresources(ui)
985 templ = formatter.maketemplater(
987 templ = formatter.maketemplater(
986 ui, spec, defaults=templatekw.keywords, resources=tres
988 ui, spec, defaults=templatekw.keywords, resources=tres
987 )
989 )
988
990
989 def formatnode(repo, ctx):
991 def formatnode(repo, ctx):
990 props = {b'ctx': ctx, b'repo': repo}
992 props = {b'ctx': ctx, b'repo': repo}
991 return templ.renderdefault(props)
993 return templ.renderdefault(props)
992
994
993 return formatnode
995 return formatnode
994
996
995
997
996 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
998 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
997 props = props or {}
999 props = props or {}
998 formatnode = _graphnodeformatter(ui, displayer)
1000 formatnode = _graphnodeformatter(ui, displayer)
999 state = graphmod.asciistate()
1001 state = graphmod.asciistate()
1000 styles = state[b'styles']
1002 styles = state[b'styles']
1001
1003
1002 # only set graph styling if HGPLAIN is not set.
1004 # only set graph styling if HGPLAIN is not set.
1003 if ui.plain(b'graph'):
1005 if ui.plain(b'graph'):
1004 # set all edge styles to |, the default pre-3.8 behaviour
1006 # set all edge styles to |, the default pre-3.8 behaviour
1005 styles.update(dict.fromkeys(styles, b'|'))
1007 styles.update(dict.fromkeys(styles, b'|'))
1006 else:
1008 else:
1007 edgetypes = {
1009 edgetypes = {
1008 b'parent': graphmod.PARENT,
1010 b'parent': graphmod.PARENT,
1009 b'grandparent': graphmod.GRANDPARENT,
1011 b'grandparent': graphmod.GRANDPARENT,
1010 b'missing': graphmod.MISSINGPARENT,
1012 b'missing': graphmod.MISSINGPARENT,
1011 }
1013 }
1012 for name, key in edgetypes.items():
1014 for name, key in edgetypes.items():
1013 # experimental config: experimental.graphstyle.*
1015 # experimental config: experimental.graphstyle.*
1014 styles[key] = ui.config(
1016 styles[key] = ui.config(
1015 b'experimental', b'graphstyle.%s' % name, styles[key]
1017 b'experimental', b'graphstyle.%s' % name, styles[key]
1016 )
1018 )
1017 if not styles[key]:
1019 if not styles[key]:
1018 styles[key] = None
1020 styles[key] = None
1019
1021
1020 # experimental config: experimental.graphshorten
1022 # experimental config: experimental.graphshorten
1021 state[b'graphshorten'] = ui.configbool(b'experimental', b'graphshorten')
1023 state[b'graphshorten'] = ui.configbool(b'experimental', b'graphshorten')
1022
1024
1023 for rev, type, ctx, parents in dag:
1025 for rev, type, ctx, parents in dag:
1024 char = formatnode(repo, ctx)
1026 char = formatnode(repo, ctx)
1025 copies = getcopies(ctx) if getcopies else None
1027 copies = getcopies(ctx) if getcopies else None
1026 edges = edgefn(type, char, state, rev, parents)
1028 edges = edgefn(type, char, state, rev, parents)
1027 firstedge = next(edges)
1029 firstedge = next(edges)
1028 width = firstedge[2]
1030 width = firstedge[2]
1029 displayer.show(
1031 displayer.show(
1030 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1032 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1031 )
1033 )
1032 lines = displayer.hunk.pop(rev).split(b'\n')
1034 lines = displayer.hunk.pop(rev).split(b'\n')
1033 if not lines[-1]:
1035 if not lines[-1]:
1034 del lines[-1]
1036 del lines[-1]
1035 displayer.flush(ctx)
1037 displayer.flush(ctx)
1036 for type, char, width, coldata in itertools.chain([firstedge], edges):
1038 for type, char, width, coldata in itertools.chain([firstedge], edges):
1037 graphmod.ascii(ui, state, type, char, lines, coldata)
1039 graphmod.ascii(ui, state, type, char, lines, coldata)
1038 lines = []
1040 lines = []
1039 displayer.close()
1041 displayer.close()
1040
1042
1041
1043
1042 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1044 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1043 revdag = graphmod.dagwalker(repo, revs)
1045 revdag = graphmod.dagwalker(repo, revs)
1044 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1046 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1045
1047
1046
1048
1047 def displayrevs(ui, repo, revs, displayer, getcopies):
1049 def displayrevs(ui, repo, revs, displayer, getcopies):
1048 for rev in revs:
1050 for rev in revs:
1049 ctx = repo[rev]
1051 ctx = repo[rev]
1050 copies = getcopies(ctx) if getcopies else None
1052 copies = getcopies(ctx) if getcopies else None
1051 displayer.show(ctx, copies=copies)
1053 displayer.show(ctx, copies=copies)
1052 displayer.flush(ctx)
1054 displayer.flush(ctx)
1053 displayer.close()
1055 displayer.close()
1054
1056
1055
1057
1056 def checkunsupportedgraphflags(pats, opts):
1058 def checkunsupportedgraphflags(pats, opts):
1057 for op in [b"newest_first"]:
1059 for op in [b"newest_first"]:
1058 if op in opts and opts[op]:
1060 if op in opts and opts[op]:
1059 raise error.Abort(
1061 raise error.Abort(
1060 _(b"-G/--graph option is incompatible with --%s")
1062 _(b"-G/--graph option is incompatible with --%s")
1061 % op.replace(b"_", b"-")
1063 % op.replace(b"_", b"-")
1062 )
1064 )
1063
1065
1064
1066
1065 def graphrevs(repo, nodes, opts):
1067 def graphrevs(repo, nodes, opts):
1066 limit = getlimit(opts)
1068 limit = getlimit(opts)
1067 nodes.reverse()
1069 nodes.reverse()
1068 if limit is not None:
1070 if limit is not None:
1069 nodes = nodes[:limit]
1071 nodes = nodes[:limit]
1070 return graphmod.nodes(repo, nodes)
1072 return graphmod.nodes(repo, nodes)
@@ -1,954 +1,1047 b''
1 $ cat >> $HGRCPATH << EOF
1 $ cat >> $HGRCPATH << EOF
2 > [diff]
2 > [diff]
3 > git = true
3 > git = true
4 > EOF
4 > EOF
5
5
6 $ hg init
6 $ hg init
7 $ cat > foo << EOF
7 $ cat > foo << EOF
8 > 0
8 > 0
9 > 1
9 > 1
10 > 2
10 > 2
11 > 3
11 > 3
12 > 4
12 > 4
13 > EOF
13 > EOF
14 $ hg ci -Am init
14 $ hg ci -Am init
15 adding foo
15 adding foo
16 $ cat > foo << EOF
16 $ cat > foo << EOF
17 > 0
17 > 0
18 > 0
18 > 0
19 > 0
19 > 0
20 > 0
20 > 0
21 > 1
21 > 1
22 > 2
22 > 2
23 > 3
23 > 3
24 > 4
24 > 4
25 > EOF
25 > EOF
26 $ hg ci -m 'more 0'
26 $ hg ci -m 'more 0'
27 $ sed 's/2/2+/' foo > foo.new
27 $ sed 's/2/2+/' foo > foo.new
28 $ mv foo.new foo
28 $ mv foo.new foo
29 $ cat > bar << EOF
29 $ cat > bar << EOF
30 > a
30 > a
31 > b
31 > b
32 > c
32 > c
33 > d
33 > d
34 > e
34 > e
35 > EOF
35 > EOF
36 $ hg add bar
36 $ hg add bar
37 $ hg ci -Am "2 -> 2+; added bar"
37 $ hg ci -Am "2 -> 2+; added bar"
38 $ cat >> foo << EOF
38 $ cat >> foo << EOF
39 > 5
39 > 5
40 > 6
40 > 6
41 > 7
41 > 7
42 > 8
42 > 8
43 > 9
43 > 9
44 > 10
44 > 10
45 > 11
45 > 11
46 > EOF
46 > EOF
47 $ hg ci -m "to 11"
47 $ hg ci -m "to 11"
48
48
49 Add some changes with two diff hunks
49 Add some changes with two diff hunks
50
50
51 $ sed 's/^1$/ 1/' foo > foo.new
51 $ sed 's/^1$/ 1/' foo > foo.new
52 $ mv foo.new foo
52 $ mv foo.new foo
53 $ sed 's/^11$/11+/' foo > foo.new
53 $ sed 's/^11$/11+/' foo > foo.new
54 $ mv foo.new foo
54 $ mv foo.new foo
55 $ hg ci -m '11 -> 11+; leading space before "1"'
55 $ hg ci -m '11 -> 11+; leading space before "1"'
56 (make sure there are two hunks in "foo")
56 (make sure there are two hunks in "foo")
57 $ hg diff -c .
57 $ hg diff -c .
58 diff --git a/foo b/foo
58 diff --git a/foo b/foo
59 --- a/foo
59 --- a/foo
60 +++ b/foo
60 +++ b/foo
61 @@ -2,7 +2,7 @@
61 @@ -2,7 +2,7 @@
62 0
62 0
63 0
63 0
64 0
64 0
65 -1
65 -1
66 + 1
66 + 1
67 2+
67 2+
68 3
68 3
69 4
69 4
70 @@ -12,4 +12,4 @@
70 @@ -12,4 +12,4 @@
71 8
71 8
72 9
72 9
73 10
73 10
74 -11
74 -11
75 +11+
75 +11+
76 $ sed 's/3/3+/' foo > foo.new
76 $ sed 's/3/3+/' foo > foo.new
77 $ mv foo.new foo
77 $ mv foo.new foo
78 $ sed 's/^11+$/11-/' foo > foo.new
78 $ sed 's/^11+$/11-/' foo > foo.new
79 $ mv foo.new foo
79 $ mv foo.new foo
80 $ sed 's/a/a+/' bar > bar.new
80 $ sed 's/a/a+/' bar > bar.new
81 $ mv bar.new bar
81 $ mv bar.new bar
82 $ hg ci -m 'foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+'
82 $ hg ci -m 'foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+'
83 (make sure there are two hunks in "foo")
83 (make sure there are two hunks in "foo")
84 $ hg diff -c . foo
84 $ hg diff -c . foo
85 diff --git a/foo b/foo
85 diff --git a/foo b/foo
86 --- a/foo
86 --- a/foo
87 +++ b/foo
87 +++ b/foo
88 @@ -4,7 +4,7 @@
88 @@ -4,7 +4,7 @@
89 0
89 0
90 1
90 1
91 2+
91 2+
92 -3
92 -3
93 +3+
93 +3+
94 4
94 4
95 5
95 5
96 6
96 6
97 @@ -12,4 +12,4 @@
97 @@ -12,4 +12,4 @@
98 8
98 8
99 9
99 9
100 10
100 10
101 -11+
101 -11+
102 +11-
102 +11-
103
103
104 $ hg log -f -L foo,5:7 -p
104 $ hg log -f -L foo,5:7 -p
105 changeset: 5:cfdf972b3971
105 changeset: 5:cfdf972b3971
106 tag: tip
106 tag: tip
107 user: test
107 user: test
108 date: Thu Jan 01 00:00:00 1970 +0000
108 date: Thu Jan 01 00:00:00 1970 +0000
109 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
109 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
110
110
111 diff --git a/foo b/foo
111 diff --git a/foo b/foo
112 --- a/foo
112 --- a/foo
113 +++ b/foo
113 +++ b/foo
114 @@ -4,7 +4,7 @@
114 @@ -4,7 +4,7 @@
115 0
115 0
116 1
116 1
117 2+
117 2+
118 -3
118 -3
119 +3+
119 +3+
120 4
120 4
121 5
121 5
122 6
122 6
123
123
124 changeset: 4:eaec41c1a0c9
124 changeset: 4:eaec41c1a0c9
125 user: test
125 user: test
126 date: Thu Jan 01 00:00:00 1970 +0000
126 date: Thu Jan 01 00:00:00 1970 +0000
127 summary: 11 -> 11+; leading space before "1"
127 summary: 11 -> 11+; leading space before "1"
128
128
129 diff --git a/foo b/foo
129 diff --git a/foo b/foo
130 --- a/foo
130 --- a/foo
131 +++ b/foo
131 +++ b/foo
132 @@ -2,7 +2,7 @@
132 @@ -2,7 +2,7 @@
133 0
133 0
134 0
134 0
135 0
135 0
136 -1
136 -1
137 + 1
137 + 1
138 2+
138 2+
139 3
139 3
140 4
140 4
141
141
142 changeset: 2:63a884426fd0
142 changeset: 2:63a884426fd0
143 user: test
143 user: test
144 date: Thu Jan 01 00:00:00 1970 +0000
144 date: Thu Jan 01 00:00:00 1970 +0000
145 summary: 2 -> 2+; added bar
145 summary: 2 -> 2+; added bar
146
146
147 diff --git a/foo b/foo
147 diff --git a/foo b/foo
148 --- a/foo
148 --- a/foo
149 +++ b/foo
149 +++ b/foo
150 @@ -3,6 +3,6 @@
150 @@ -3,6 +3,6 @@
151 0
151 0
152 0
152 0
153 1
153 1
154 -2
154 -2
155 +2+
155 +2+
156 3
156 3
157 4
157 4
158
158
159 changeset: 0:5ae1f82b9a00
159 changeset: 0:5ae1f82b9a00
160 user: test
160 user: test
161 date: Thu Jan 01 00:00:00 1970 +0000
161 date: Thu Jan 01 00:00:00 1970 +0000
162 summary: init
162 summary: init
163
163
164 diff --git a/foo b/foo
164 diff --git a/foo b/foo
165 new file mode 100644
165 new file mode 100644
166 --- /dev/null
166 --- /dev/null
167 +++ b/foo
167 +++ b/foo
168 @@ -0,0 +1,5 @@
168 @@ -0,0 +1,5 @@
169 +0
169 +0
170 +1
170 +1
171 +2
171 +2
172 +3
172 +3
173 +4
173 +4
174
174
175 $ hg log -f --graph -L foo,5:7 -p
175 $ hg log -f --graph -L foo,5:7 -p
176 @ changeset: 5:cfdf972b3971
176 @ changeset: 5:cfdf972b3971
177 | tag: tip
177 | tag: tip
178 | user: test
178 | user: test
179 | date: Thu Jan 01 00:00:00 1970 +0000
179 | date: Thu Jan 01 00:00:00 1970 +0000
180 | summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
180 | summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
181 |
181 |
182 | diff --git a/foo b/foo
182 | diff --git a/foo b/foo
183 | --- a/foo
183 | --- a/foo
184 | +++ b/foo
184 | +++ b/foo
185 | @@ -4,7 +4,7 @@
185 | @@ -4,7 +4,7 @@
186 | 0
186 | 0
187 | 1
187 | 1
188 | 2+
188 | 2+
189 | -3
189 | -3
190 | +3+
190 | +3+
191 | 4
191 | 4
192 | 5
192 | 5
193 | 6
193 | 6
194 |
194 |
195 o changeset: 4:eaec41c1a0c9
195 o changeset: 4:eaec41c1a0c9
196 : user: test
196 : user: test
197 : date: Thu Jan 01 00:00:00 1970 +0000
197 : date: Thu Jan 01 00:00:00 1970 +0000
198 : summary: 11 -> 11+; leading space before "1"
198 : summary: 11 -> 11+; leading space before "1"
199 :
199 :
200 : diff --git a/foo b/foo
200 : diff --git a/foo b/foo
201 : --- a/foo
201 : --- a/foo
202 : +++ b/foo
202 : +++ b/foo
203 : @@ -2,7 +2,7 @@
203 : @@ -2,7 +2,7 @@
204 : 0
204 : 0
205 : 0
205 : 0
206 : 0
206 : 0
207 : -1
207 : -1
208 : + 1
208 : + 1
209 : 2+
209 : 2+
210 : 3
210 : 3
211 : 4
211 : 4
212 :
212 :
213 o changeset: 2:63a884426fd0
213 o changeset: 2:63a884426fd0
214 : user: test
214 : user: test
215 : date: Thu Jan 01 00:00:00 1970 +0000
215 : date: Thu Jan 01 00:00:00 1970 +0000
216 : summary: 2 -> 2+; added bar
216 : summary: 2 -> 2+; added bar
217 :
217 :
218 : diff --git a/foo b/foo
218 : diff --git a/foo b/foo
219 : --- a/foo
219 : --- a/foo
220 : +++ b/foo
220 : +++ b/foo
221 : @@ -3,6 +3,6 @@
221 : @@ -3,6 +3,6 @@
222 : 0
222 : 0
223 : 0
223 : 0
224 : 1
224 : 1
225 : -2
225 : -2
226 : +2+
226 : +2+
227 : 3
227 : 3
228 : 4
228 : 4
229 :
229 :
230 o changeset: 0:5ae1f82b9a00
230 o changeset: 0:5ae1f82b9a00
231 user: test
231 user: test
232 date: Thu Jan 01 00:00:00 1970 +0000
232 date: Thu Jan 01 00:00:00 1970 +0000
233 summary: init
233 summary: init
234
234
235 diff --git a/foo b/foo
235 diff --git a/foo b/foo
236 new file mode 100644
236 new file mode 100644
237 --- /dev/null
237 --- /dev/null
238 +++ b/foo
238 +++ b/foo
239 @@ -0,0 +1,5 @@
239 @@ -0,0 +1,5 @@
240 +0
240 +0
241 +1
241 +1
242 +2
242 +2
243 +3
243 +3
244 +4
244 +4
245
245
246
246
247 With --template.
247 With --template.
248
248
249 $ hg log -f -L foo,5:7 -T '{rev}:{node|short} {desc|firstline}\n'
249 $ hg log -f -L foo,5:7 -T '{rev}:{node|short} {desc|firstline}\n'
250 5:cfdf972b3971 foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
250 5:cfdf972b3971 foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
251 4:eaec41c1a0c9 11 -> 11+; leading space before "1"
251 4:eaec41c1a0c9 11 -> 11+; leading space before "1"
252 2:63a884426fd0 2 -> 2+; added bar
252 2:63a884426fd0 2 -> 2+; added bar
253 0:5ae1f82b9a00 init
253 0:5ae1f82b9a00 init
254 $ hg log -f -L foo,5:7 -T json
254 $ hg log -f -L foo,5:7 -T json
255 [
255 [
256 {
256 {
257 "bookmarks": [],
257 "bookmarks": [],
258 "branch": "default",
258 "branch": "default",
259 "date": [0, 0],
259 "date": [0, 0],
260 "desc": "foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+",
260 "desc": "foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+",
261 "node": "cfdf972b3971a2a59638bf9583c0debbffee5404",
261 "node": "cfdf972b3971a2a59638bf9583c0debbffee5404",
262 "parents": ["eaec41c1a0c9ad0a5e999611d0149d171beffb8c"],
262 "parents": ["eaec41c1a0c9ad0a5e999611d0149d171beffb8c"],
263 "phase": "draft",
263 "phase": "draft",
264 "rev": 5,
264 "rev": 5,
265 "tags": ["tip"],
265 "tags": ["tip"],
266 "user": "test"
266 "user": "test"
267 },
267 },
268 {
268 {
269 "bookmarks": [],
269 "bookmarks": [],
270 "branch": "default",
270 "branch": "default",
271 "date": [0, 0],
271 "date": [0, 0],
272 "desc": "11 -> 11+; leading space before \"1\"",
272 "desc": "11 -> 11+; leading space before \"1\"",
273 "node": "eaec41c1a0c9ad0a5e999611d0149d171beffb8c",
273 "node": "eaec41c1a0c9ad0a5e999611d0149d171beffb8c",
274 "parents": ["730a61fbaecf426c17c2c66bc42d195b5d5b0ba8"],
274 "parents": ["730a61fbaecf426c17c2c66bc42d195b5d5b0ba8"],
275 "phase": "draft",
275 "phase": "draft",
276 "rev": 4,
276 "rev": 4,
277 "tags": [],
277 "tags": [],
278 "user": "test"
278 "user": "test"
279 },
279 },
280 {
280 {
281 "bookmarks": [],
281 "bookmarks": [],
282 "branch": "default",
282 "branch": "default",
283 "date": [0, 0],
283 "date": [0, 0],
284 "desc": "2 -> 2+; added bar",
284 "desc": "2 -> 2+; added bar",
285 "node": "63a884426fd0b277fcd55895bbb2f230434576eb",
285 "node": "63a884426fd0b277fcd55895bbb2f230434576eb",
286 "parents": ["29a1e7c6b80024f63f310a2d71de979e9d2996d7"],
286 "parents": ["29a1e7c6b80024f63f310a2d71de979e9d2996d7"],
287 "phase": "draft",
287 "phase": "draft",
288 "rev": 2,
288 "rev": 2,
289 "tags": [],
289 "tags": [],
290 "user": "test"
290 "user": "test"
291 },
291 },
292 {
292 {
293 "bookmarks": [],
293 "bookmarks": [],
294 "branch": "default",
294 "branch": "default",
295 "date": [0, 0],
295 "date": [0, 0],
296 "desc": "init",
296 "desc": "init",
297 "node": "5ae1f82b9a000ff1e0967d0dac1c58b9d796e1b4",
297 "node": "5ae1f82b9a000ff1e0967d0dac1c58b9d796e1b4",
298 "parents": ["0000000000000000000000000000000000000000"],
298 "parents": ["0000000000000000000000000000000000000000"],
299 "phase": "draft",
299 "phase": "draft",
300 "rev": 0,
300 "rev": 0,
301 "tags": [],
301 "tags": [],
302 "user": "test"
302 "user": "test"
303 }
303 }
304 ]
304 ]
305
305
306 With some white-space diff option, respective revisions are skipped.
306 With some white-space diff option, respective revisions are skipped.
307
307
308 $ hg log -f -L foo,5:7 -p --config diff.ignorews=true
308 $ hg log -f -L foo,5:7 -p --config diff.ignorews=true
309 changeset: 5:cfdf972b3971
309 changeset: 5:cfdf972b3971
310 tag: tip
310 tag: tip
311 user: test
311 user: test
312 date: Thu Jan 01 00:00:00 1970 +0000
312 date: Thu Jan 01 00:00:00 1970 +0000
313 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
313 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
314
314
315 diff --git a/foo b/foo
315 diff --git a/foo b/foo
316 --- a/foo
316 --- a/foo
317 +++ b/foo
317 +++ b/foo
318 @@ -4,7 +4,7 @@
318 @@ -4,7 +4,7 @@
319 0
319 0
320 1
320 1
321 2+
321 2+
322 -3
322 -3
323 +3+
323 +3+
324 4
324 4
325 5
325 5
326 6
326 6
327
327
328 changeset: 2:63a884426fd0
328 changeset: 2:63a884426fd0
329 user: test
329 user: test
330 date: Thu Jan 01 00:00:00 1970 +0000
330 date: Thu Jan 01 00:00:00 1970 +0000
331 summary: 2 -> 2+; added bar
331 summary: 2 -> 2+; added bar
332
332
333 diff --git a/foo b/foo
333 diff --git a/foo b/foo
334 --- a/foo
334 --- a/foo
335 +++ b/foo
335 +++ b/foo
336 @@ -3,6 +3,6 @@
336 @@ -3,6 +3,6 @@
337 0
337 0
338 0
338 0
339 1
339 1
340 -2
340 -2
341 +2+
341 +2+
342 3
342 3
343 4
343 4
344
344
345 changeset: 0:5ae1f82b9a00
345 changeset: 0:5ae1f82b9a00
346 user: test
346 user: test
347 date: Thu Jan 01 00:00:00 1970 +0000
347 date: Thu Jan 01 00:00:00 1970 +0000
348 summary: init
348 summary: init
349
349
350 diff --git a/foo b/foo
350 diff --git a/foo b/foo
351 new file mode 100644
351 new file mode 100644
352 --- /dev/null
352 --- /dev/null
353 +++ b/foo
353 +++ b/foo
354 @@ -0,0 +1,5 @@
354 @@ -0,0 +1,5 @@
355 +0
355 +0
356 +1
356 +1
357 +2
357 +2
358 +3
358 +3
359 +4
359 +4
360
360
361
361
362 Regular file patterns are not allowed.
362 Regular file patterns are not allowed.
363
363
364 $ hg log -f -L foo,5:7 -p bar
364 $ hg log -f -L foo,5:7 -p bar
365 abort: FILE arguments are not compatible with --line-range option
365 abort: FILE arguments are not compatible with --line-range option
366 [255]
366 [255]
367
367
368 Option --rev acts as a restriction.
368 Option --rev acts as a restriction.
369
369
370 $ hg log -f -L foo,5:7 -p -r 'desc(2)'
370 $ hg log -f -L foo,5:7 -p -r 'desc(2)'
371 changeset: 2:63a884426fd0
371 changeset: 2:63a884426fd0
372 user: test
372 user: test
373 date: Thu Jan 01 00:00:00 1970 +0000
373 date: Thu Jan 01 00:00:00 1970 +0000
374 summary: 2 -> 2+; added bar
374 summary: 2 -> 2+; added bar
375
375
376 diff --git a/foo b/foo
376 diff --git a/foo b/foo
377 --- a/foo
377 --- a/foo
378 +++ b/foo
378 +++ b/foo
379 @@ -3,6 +3,6 @@
379 @@ -3,6 +3,6 @@
380 0
380 0
381 0
381 0
382 1
382 1
383 -2
383 -2
384 +2+
384 +2+
385 3
385 3
386 4
386 4
387
387
388 changeset: 0:5ae1f82b9a00
388 changeset: 0:5ae1f82b9a00
389 user: test
389 user: test
390 date: Thu Jan 01 00:00:00 1970 +0000
390 date: Thu Jan 01 00:00:00 1970 +0000
391 summary: init
391 summary: init
392
392
393 diff --git a/foo b/foo
393 diff --git a/foo b/foo
394 new file mode 100644
394 new file mode 100644
395 --- /dev/null
395 --- /dev/null
396 +++ b/foo
396 +++ b/foo
397 @@ -0,0 +1,5 @@
397 @@ -0,0 +1,5 @@
398 +0
398 +0
399 +1
399 +1
400 +2
400 +2
401 +3
401 +3
402 +4
402 +4
403
403
404
404
405 With several -L patterns, changes touching any files in their respective line
405 With several -L patterns, changes touching any files in their respective line
406 range are show.
406 range are show.
407
407
408 $ hg log -f -L foo,5:7 -L bar,1:2 -p
408 $ hg log -f -L foo,5:7 -L bar,1:2 -p
409 changeset: 5:cfdf972b3971
409 changeset: 5:cfdf972b3971
410 tag: tip
410 tag: tip
411 user: test
411 user: test
412 date: Thu Jan 01 00:00:00 1970 +0000
412 date: Thu Jan 01 00:00:00 1970 +0000
413 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
413 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
414
414
415 diff --git a/bar b/bar
415 diff --git a/bar b/bar
416 --- a/bar
416 --- a/bar
417 +++ b/bar
417 +++ b/bar
418 @@ -1,4 +1,4 @@
418 @@ -1,4 +1,4 @@
419 -a
419 -a
420 +a+
420 +a+
421 b
421 b
422 c
422 c
423 d
423 d
424 diff --git a/foo b/foo
424 diff --git a/foo b/foo
425 --- a/foo
425 --- a/foo
426 +++ b/foo
426 +++ b/foo
427 @@ -4,7 +4,7 @@
427 @@ -4,7 +4,7 @@
428 0
428 0
429 1
429 1
430 2+
430 2+
431 -3
431 -3
432 +3+
432 +3+
433 4
433 4
434 5
434 5
435 6
435 6
436
436
437 changeset: 4:eaec41c1a0c9
437 changeset: 4:eaec41c1a0c9
438 user: test
438 user: test
439 date: Thu Jan 01 00:00:00 1970 +0000
439 date: Thu Jan 01 00:00:00 1970 +0000
440 summary: 11 -> 11+; leading space before "1"
440 summary: 11 -> 11+; leading space before "1"
441
441
442 diff --git a/foo b/foo
442 diff --git a/foo b/foo
443 --- a/foo
443 --- a/foo
444 +++ b/foo
444 +++ b/foo
445 @@ -2,7 +2,7 @@
445 @@ -2,7 +2,7 @@
446 0
446 0
447 0
447 0
448 0
448 0
449 -1
449 -1
450 + 1
450 + 1
451 2+
451 2+
452 3
452 3
453 4
453 4
454
454
455 changeset: 2:63a884426fd0
455 changeset: 2:63a884426fd0
456 user: test
456 user: test
457 date: Thu Jan 01 00:00:00 1970 +0000
457 date: Thu Jan 01 00:00:00 1970 +0000
458 summary: 2 -> 2+; added bar
458 summary: 2 -> 2+; added bar
459
459
460 diff --git a/bar b/bar
460 diff --git a/bar b/bar
461 new file mode 100644
461 new file mode 100644
462 --- /dev/null
462 --- /dev/null
463 +++ b/bar
463 +++ b/bar
464 @@ -0,0 +1,5 @@
464 @@ -0,0 +1,5 @@
465 +a
465 +a
466 +b
466 +b
467 +c
467 +c
468 +d
468 +d
469 +e
469 +e
470 diff --git a/foo b/foo
470 diff --git a/foo b/foo
471 --- a/foo
471 --- a/foo
472 +++ b/foo
472 +++ b/foo
473 @@ -3,6 +3,6 @@
473 @@ -3,6 +3,6 @@
474 0
474 0
475 0
475 0
476 1
476 1
477 -2
477 -2
478 +2+
478 +2+
479 3
479 3
480 4
480 4
481
481
482 changeset: 0:5ae1f82b9a00
482 changeset: 0:5ae1f82b9a00
483 user: test
483 user: test
484 date: Thu Jan 01 00:00:00 1970 +0000
484 date: Thu Jan 01 00:00:00 1970 +0000
485 summary: init
485 summary: init
486
486
487 diff --git a/foo b/foo
487 diff --git a/foo b/foo
488 new file mode 100644
488 new file mode 100644
489 --- /dev/null
489 --- /dev/null
490 +++ b/foo
490 +++ b/foo
491 @@ -0,0 +1,5 @@
491 @@ -0,0 +1,5 @@
492 +0
492 +0
493 +1
493 +1
494 +2
494 +2
495 +3
495 +3
496 +4
496 +4
497
497
498
498
499 Multiple -L options with the same file yields changes touching any of
499 Multiple -L options with the same file yields changes touching any of
500 specified line ranges.
500 specified line ranges.
501
501
502 $ hg log -f -L foo,5:7 -L foo,14:15 -p
502 $ hg log -f -L foo,5:7 -L foo,14:15 -p
503 changeset: 5:cfdf972b3971
503 changeset: 5:cfdf972b3971
504 tag: tip
504 tag: tip
505 user: test
505 user: test
506 date: Thu Jan 01 00:00:00 1970 +0000
506 date: Thu Jan 01 00:00:00 1970 +0000
507 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
507 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
508
508
509 diff --git a/foo b/foo
509 diff --git a/foo b/foo
510 --- a/foo
510 --- a/foo
511 +++ b/foo
511 +++ b/foo
512 @@ -4,7 +4,7 @@
512 @@ -4,7 +4,7 @@
513 0
513 0
514 1
514 1
515 2+
515 2+
516 -3
516 -3
517 +3+
517 +3+
518 4
518 4
519 5
519 5
520 6
520 6
521 @@ -12,4 +12,4 @@
521 @@ -12,4 +12,4 @@
522 8
522 8
523 9
523 9
524 10
524 10
525 -11+
525 -11+
526 +11-
526 +11-
527
527
528 changeset: 4:eaec41c1a0c9
528 changeset: 4:eaec41c1a0c9
529 user: test
529 user: test
530 date: Thu Jan 01 00:00:00 1970 +0000
530 date: Thu Jan 01 00:00:00 1970 +0000
531 summary: 11 -> 11+; leading space before "1"
531 summary: 11 -> 11+; leading space before "1"
532
532
533 diff --git a/foo b/foo
533 diff --git a/foo b/foo
534 --- a/foo
534 --- a/foo
535 +++ b/foo
535 +++ b/foo
536 @@ -2,7 +2,7 @@
536 @@ -2,7 +2,7 @@
537 0
537 0
538 0
538 0
539 0
539 0
540 -1
540 -1
541 + 1
541 + 1
542 2+
542 2+
543 3
543 3
544 4
544 4
545 @@ -12,4 +12,4 @@
545 @@ -12,4 +12,4 @@
546 8
546 8
547 9
547 9
548 10
548 10
549 -11
549 -11
550 +11+
550 +11+
551
551
552 changeset: 3:730a61fbaecf
552 changeset: 3:730a61fbaecf
553 user: test
553 user: test
554 date: Thu Jan 01 00:00:00 1970 +0000
554 date: Thu Jan 01 00:00:00 1970 +0000
555 summary: to 11
555 summary: to 11
556
556
557 diff --git a/foo b/foo
557 diff --git a/foo b/foo
558 --- a/foo
558 --- a/foo
559 +++ b/foo
559 +++ b/foo
560 @@ -6,3 +6,10 @@
560 @@ -6,3 +6,10 @@
561 2+
561 2+
562 3
562 3
563 4
563 4
564 +5
564 +5
565 +6
565 +6
566 +7
566 +7
567 +8
567 +8
568 +9
568 +9
569 +10
569 +10
570 +11
570 +11
571
571
572 changeset: 2:63a884426fd0
572 changeset: 2:63a884426fd0
573 user: test
573 user: test
574 date: Thu Jan 01 00:00:00 1970 +0000
574 date: Thu Jan 01 00:00:00 1970 +0000
575 summary: 2 -> 2+; added bar
575 summary: 2 -> 2+; added bar
576
576
577 diff --git a/foo b/foo
577 diff --git a/foo b/foo
578 --- a/foo
578 --- a/foo
579 +++ b/foo
579 +++ b/foo
580 @@ -3,6 +3,6 @@
580 @@ -3,6 +3,6 @@
581 0
581 0
582 0
582 0
583 1
583 1
584 -2
584 -2
585 +2+
585 +2+
586 3
586 3
587 4
587 4
588
588
589 changeset: 0:5ae1f82b9a00
589 changeset: 0:5ae1f82b9a00
590 user: test
590 user: test
591 date: Thu Jan 01 00:00:00 1970 +0000
591 date: Thu Jan 01 00:00:00 1970 +0000
592 summary: init
592 summary: init
593
593
594 diff --git a/foo b/foo
594 diff --git a/foo b/foo
595 new file mode 100644
595 new file mode 100644
596 --- /dev/null
596 --- /dev/null
597 +++ b/foo
597 +++ b/foo
598 @@ -0,0 +1,5 @@
598 @@ -0,0 +1,5 @@
599 +0
599 +0
600 +1
600 +1
601 +2
601 +2
602 +3
602 +3
603 +4
603 +4
604
604
605
605
606 A file with a comma in its name.
606 A file with a comma in its name.
607
607
608 $ cat > ba,z << EOF
608 $ cat > ba,z << EOF
609 > q
609 > q
610 > w
610 > w
611 > e
611 > e
612 > r
612 > r
613 > t
613 > t
614 > y
614 > y
615 > EOF
615 > EOF
616 $ hg ci -Am 'querty'
616 $ hg ci -Am 'querty'
617 adding ba,z
617 adding ba,z
618 $ cat >> ba,z << EOF
618 $ cat >> ba,z << EOF
619 > u
619 > u
620 > i
620 > i
621 > o
621 > o
622 > p
622 > p
623 > EOF
623 > EOF
624 $ hg ci -m 'more keys'
624 $ hg ci -m 'more keys'
625 $ cat > ba,z << EOF
625 $ cat > ba,z << EOF
626 > a
626 > a
627 > z
627 > z
628 > e
628 > e
629 > r
629 > r
630 > t
630 > t
631 > y
631 > y
632 > u
632 > u
633 > i
633 > i
634 > o
634 > o
635 > p
635 > p
636 > EOF
636 > EOF
637 $ hg ci -m 'azerty'
637 $ hg ci -m 'azerty'
638 $ hg log -f -L ba,z,1:2 -p
638 $ hg log -f -L ba,z,1:2 -p
639 changeset: 8:52373265138b
639 changeset: 8:52373265138b
640 tag: tip
640 tag: tip
641 user: test
641 user: test
642 date: Thu Jan 01 00:00:00 1970 +0000
642 date: Thu Jan 01 00:00:00 1970 +0000
643 summary: azerty
643 summary: azerty
644
644
645 diff --git a/ba,z b/ba,z
645 diff --git a/ba,z b/ba,z
646 --- a/ba,z
646 --- a/ba,z
647 +++ b/ba,z
647 +++ b/ba,z
648 @@ -1,5 +1,5 @@
648 @@ -1,5 +1,5 @@
649 -q
649 -q
650 -w
650 -w
651 +a
651 +a
652 +z
652 +z
653 e
653 e
654 r
654 r
655 t
655 t
656
656
657 changeset: 6:96ba8850f316
657 changeset: 6:96ba8850f316
658 user: test
658 user: test
659 date: Thu Jan 01 00:00:00 1970 +0000
659 date: Thu Jan 01 00:00:00 1970 +0000
660 summary: querty
660 summary: querty
661
661
662 diff --git a/ba,z b/ba,z
662 diff --git a/ba,z b/ba,z
663 new file mode 100644
663 new file mode 100644
664 --- /dev/null
664 --- /dev/null
665 +++ b/ba,z
665 +++ b/ba,z
666 @@ -0,0 +1,6 @@
666 @@ -0,0 +1,6 @@
667 +q
667 +q
668 +w
668 +w
669 +e
669 +e
670 +r
670 +r
671 +t
671 +t
672 +y
672 +y
673
673
674
674
675 Exact prefix kinds work in -L options.
675 Exact prefix kinds work in -L options.
676
676
677 $ mkdir dir
677 $ mkdir dir
678 $ cd dir
678 $ cd dir
679 $ hg log -f -L path:foo,5:7 -p
679 $ hg log -f -L path:foo,5:7 -p
680 changeset: 5:cfdf972b3971
680 changeset: 5:cfdf972b3971
681 user: test
681 user: test
682 date: Thu Jan 01 00:00:00 1970 +0000
682 date: Thu Jan 01 00:00:00 1970 +0000
683 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
683 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
684
684
685 diff --git a/foo b/foo
685 diff --git a/foo b/foo
686 --- a/foo
686 --- a/foo
687 +++ b/foo
687 +++ b/foo
688 @@ -4,7 +4,7 @@
688 @@ -4,7 +4,7 @@
689 0
689 0
690 1
690 1
691 2+
691 2+
692 -3
692 -3
693 +3+
693 +3+
694 4
694 4
695 5
695 5
696 6
696 6
697
697
698 changeset: 4:eaec41c1a0c9
698 changeset: 4:eaec41c1a0c9
699 user: test
699 user: test
700 date: Thu Jan 01 00:00:00 1970 +0000
700 date: Thu Jan 01 00:00:00 1970 +0000
701 summary: 11 -> 11+; leading space before "1"
701 summary: 11 -> 11+; leading space before "1"
702
702
703 diff --git a/foo b/foo
703 diff --git a/foo b/foo
704 --- a/foo
704 --- a/foo
705 +++ b/foo
705 +++ b/foo
706 @@ -2,7 +2,7 @@
706 @@ -2,7 +2,7 @@
707 0
707 0
708 0
708 0
709 0
709 0
710 -1
710 -1
711 + 1
711 + 1
712 2+
712 2+
713 3
713 3
714 4
714 4
715
715
716 changeset: 2:63a884426fd0
716 changeset: 2:63a884426fd0
717 user: test
717 user: test
718 date: Thu Jan 01 00:00:00 1970 +0000
718 date: Thu Jan 01 00:00:00 1970 +0000
719 summary: 2 -> 2+; added bar
719 summary: 2 -> 2+; added bar
720
720
721 diff --git a/foo b/foo
721 diff --git a/foo b/foo
722 --- a/foo
722 --- a/foo
723 +++ b/foo
723 +++ b/foo
724 @@ -3,6 +3,6 @@
724 @@ -3,6 +3,6 @@
725 0
725 0
726 0
726 0
727 1
727 1
728 -2
728 -2
729 +2+
729 +2+
730 3
730 3
731 4
731 4
732
732
733 changeset: 0:5ae1f82b9a00
733 changeset: 0:5ae1f82b9a00
734 user: test
734 user: test
735 date: Thu Jan 01 00:00:00 1970 +0000
735 date: Thu Jan 01 00:00:00 1970 +0000
736 summary: init
736 summary: init
737
737
738 diff --git a/foo b/foo
738 diff --git a/foo b/foo
739 new file mode 100644
739 new file mode 100644
740 --- /dev/null
740 --- /dev/null
741 +++ b/foo
741 +++ b/foo
742 @@ -0,0 +1,5 @@
742 @@ -0,0 +1,5 @@
743 +0
743 +0
744 +1
744 +1
745 +2
745 +2
746 +3
746 +3
747 +4
747 +4
748
748
749
749
750 Renames are followed.
750 Renames are followed.
751
751
752 $ hg mv ../foo baz
752 $ hg mv ../foo baz
753 $ sed 's/1/1+/' baz > baz.new
753 $ sed 's/1/1+/' baz > baz.new
754 $ mv baz.new baz
754 $ mv baz.new baz
755 $ hg ci -m 'foo -> dir/baz; 1-1+'
755 $ hg ci -m 'foo -> dir/baz; 1-1+'
756 $ hg diff -c .
756 $ hg diff -c .
757 diff --git a/foo b/dir/baz
757 diff --git a/foo b/dir/baz
758 rename from foo
758 rename from foo
759 rename to dir/baz
759 rename to dir/baz
760 --- a/foo
760 --- a/foo
761 +++ b/dir/baz
761 +++ b/dir/baz
762 @@ -2,7 +2,7 @@
762 @@ -2,7 +2,7 @@
763 0
763 0
764 0
764 0
765 0
765 0
766 - 1
766 - 1
767 + 1+
767 + 1+
768 2+
768 2+
769 3+
769 3+
770 4
770 4
771 @@ -11,5 +11,5 @@
771 @@ -11,5 +11,5 @@
772 7
772 7
773 8
773 8
774 9
774 9
775 -10
775 -10
776 -11-
776 -11-
777 +1+0
777 +1+0
778 +1+1-
778 +1+1-
779 $ hg log -f -L relpath:baz,5:7 -p
779 $ hg log -f -L relpath:baz,5:7 -p
780 changeset: 9:6af29c3a778f
780 changeset: 9:6af29c3a778f
781 tag: tip
781 tag: tip
782 user: test
782 user: test
783 date: Thu Jan 01 00:00:00 1970 +0000
783 date: Thu Jan 01 00:00:00 1970 +0000
784 summary: foo -> dir/baz; 1-1+
784 summary: foo -> dir/baz; 1-1+
785
785
786 diff --git a/foo b/dir/baz
786 diff --git a/foo b/dir/baz
787 copy from foo
787 copy from foo
788 copy to dir/baz
788 copy to dir/baz
789 --- a/foo
789 --- a/foo
790 +++ b/dir/baz
790 +++ b/dir/baz
791 @@ -2,7 +2,7 @@
791 @@ -2,7 +2,7 @@
792 0
792 0
793 0
793 0
794 0
794 0
795 - 1
795 - 1
796 + 1+
796 + 1+
797 2+
797 2+
798 3+
798 3+
799 4
799 4
800
800
801 changeset: 5:cfdf972b3971
801 changeset: 5:cfdf972b3971
802 user: test
802 user: test
803 date: Thu Jan 01 00:00:00 1970 +0000
803 date: Thu Jan 01 00:00:00 1970 +0000
804 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
804 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
805
805
806 diff --git a/foo b/foo
806 diff --git a/foo b/foo
807 --- a/foo
807 --- a/foo
808 +++ b/foo
808 +++ b/foo
809 @@ -4,7 +4,7 @@
809 @@ -4,7 +4,7 @@
810 0
810 0
811 1
811 1
812 2+
812 2+
813 -3
813 -3
814 +3+
814 +3+
815 4
815 4
816 5
816 5
817 6
817 6
818
818
819 changeset: 4:eaec41c1a0c9
819 changeset: 4:eaec41c1a0c9
820 user: test
820 user: test
821 date: Thu Jan 01 00:00:00 1970 +0000
821 date: Thu Jan 01 00:00:00 1970 +0000
822 summary: 11 -> 11+; leading space before "1"
822 summary: 11 -> 11+; leading space before "1"
823
823
824 diff --git a/foo b/foo
824 diff --git a/foo b/foo
825 --- a/foo
825 --- a/foo
826 +++ b/foo
826 +++ b/foo
827 @@ -2,7 +2,7 @@
827 @@ -2,7 +2,7 @@
828 0
828 0
829 0
829 0
830 0
830 0
831 -1
831 -1
832 + 1
832 + 1
833 2+
833 2+
834 3
834 3
835 4
835 4
836
836
837 changeset: 2:63a884426fd0
837 changeset: 2:63a884426fd0
838 user: test
838 user: test
839 date: Thu Jan 01 00:00:00 1970 +0000
839 date: Thu Jan 01 00:00:00 1970 +0000
840 summary: 2 -> 2+; added bar
840 summary: 2 -> 2+; added bar
841
841
842 diff --git a/foo b/foo
842 diff --git a/foo b/foo
843 --- a/foo
843 --- a/foo
844 +++ b/foo
844 +++ b/foo
845 @@ -3,6 +3,6 @@
845 @@ -3,6 +3,6 @@
846 0
846 0
847 0
847 0
848 1
848 1
849 -2
849 -2
850 +2+
850 +2+
851 3
851 3
852 4
852 4
853
853
854 changeset: 0:5ae1f82b9a00
854 changeset: 0:5ae1f82b9a00
855 user: test
855 user: test
856 date: Thu Jan 01 00:00:00 1970 +0000
856 date: Thu Jan 01 00:00:00 1970 +0000
857 summary: init
857 summary: init
858
858
859 diff --git a/foo b/foo
859 diff --git a/foo b/foo
860 new file mode 100644
860 new file mode 100644
861 --- /dev/null
861 --- /dev/null
862 +++ b/foo
862 +++ b/foo
863 @@ -0,0 +1,5 @@
863 @@ -0,0 +1,5 @@
864 +0
864 +0
865 +1
865 +1
866 +2
866 +2
867 +3
867 +3
868 +4
868 +4
869
869
870
870
871 Uncommitted changes with a rename
871 Uncommitted changes with a rename
872
872
873 $ hg mv baz bazn
873 $ hg mv baz bazn
874 $ hg log -f -L bazn,5:7
874 $ hg log -f -L bazn,5:7
875 changeset: 9:6af29c3a778f
875 changeset: 9:6af29c3a778f
876 tag: tip
876 tag: tip
877 user: test
877 user: test
878 date: Thu Jan 01 00:00:00 1970 +0000
878 date: Thu Jan 01 00:00:00 1970 +0000
879 summary: foo -> dir/baz; 1-1+
879 summary: foo -> dir/baz; 1-1+
880
880
881 changeset: 5:cfdf972b3971
881 changeset: 5:cfdf972b3971
882 user: test
882 user: test
883 date: Thu Jan 01 00:00:00 1970 +0000
883 date: Thu Jan 01 00:00:00 1970 +0000
884 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
884 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
885
885
886 changeset: 4:eaec41c1a0c9
886 changeset: 4:eaec41c1a0c9
887 user: test
887 user: test
888 date: Thu Jan 01 00:00:00 1970 +0000
888 date: Thu Jan 01 00:00:00 1970 +0000
889 summary: 11 -> 11+; leading space before "1"
889 summary: 11 -> 11+; leading space before "1"
890
890
891 changeset: 2:63a884426fd0
891 changeset: 2:63a884426fd0
892 user: test
892 user: test
893 date: Thu Jan 01 00:00:00 1970 +0000
893 date: Thu Jan 01 00:00:00 1970 +0000
894 summary: 2 -> 2+; added bar
894 summary: 2 -> 2+; added bar
895
895
896 changeset: 0:5ae1f82b9a00
896 changeset: 0:5ae1f82b9a00
897 user: test
897 user: test
898 date: Thu Jan 01 00:00:00 1970 +0000
898 date: Thu Jan 01 00:00:00 1970 +0000
899 summary: init
899 summary: init
900
900
901
902 Uncommitted changes in requested line range
903
904 $ sed 's/2/ /' bazn > bazn.new
905 $ mv bazn.new bazn
906 $ hg diff
907 diff --git a/dir/baz b/dir/bazn
908 rename from dir/baz
909 rename to dir/bazn
910 --- a/dir/baz
911 +++ b/dir/bazn
912 @@ -3,7 +3,7 @@
913 0
914 0
915 1+
916 -2+
917 + +
918 3+
919 4
920 5
921 $ hg log -f -L bazn,5:7
922 changeset: 9:6af29c3a778f
923 tag: tip
924 user: test
925 date: Thu Jan 01 00:00:00 1970 +0000
926 summary: foo -> dir/baz; 1-1+
927
928 changeset: 5:cfdf972b3971
929 user: test
930 date: Thu Jan 01 00:00:00 1970 +0000
931 summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
932
933 changeset: 4:eaec41c1a0c9
934 user: test
935 date: Thu Jan 01 00:00:00 1970 +0000
936 summary: 11 -> 11+; leading space before "1"
937
938 changeset: 2:63a884426fd0
939 user: test
940 date: Thu Jan 01 00:00:00 1970 +0000
941 summary: 2 -> 2+; added bar
942
943 changeset: 0:5ae1f82b9a00
944 user: test
945 date: Thu Jan 01 00:00:00 1970 +0000
946 summary: init
947
948
949 Uncommitted changes in line-range + wdir()
950
951 $ hg log -r 'wdir()' -f -L bazn,5:7 --limit 2 -p
952 changeset: 2147483647:ffffffffffff
953 parent: 9:6af29c3a778f
954 user: test
955 date: Thu Jan 01 00:00:00 1970 +0000
956
957 diff --git a/dir/baz b/dir/bazn
958 copy from dir/baz
959 copy to dir/bazn
960 --- a/dir/baz
961 +++ b/dir/bazn
962 @@ -3,7 +3,7 @@
963 0
964 0
965 1+
966 -2+
967 + +
968 3+
969 4
970 5
971
972 changeset: 9:6af29c3a778f
973 tag: tip
974 user: test
975 date: Thu Jan 01 00:00:00 1970 +0000
976 summary: foo -> dir/baz; 1-1+
977
978 diff --git a/foo b/dir/baz
979 copy from foo
980 copy to dir/baz
981 --- a/foo
982 +++ b/dir/baz
983 @@ -2,7 +2,7 @@
984 0
985 0
986 0
987 - 1
988 + 1+
989 2+
990 3+
991 4
992
993
901 $ hg revert -a -C -q
994 $ hg revert -a -C -q
902
995
903 Binary files work but without diff hunks filtering.
996 Binary files work but without diff hunks filtering.
904 (Checking w/ and w/o diff.git option.)
997 (Checking w/ and w/o diff.git option.)
905
998
906 >>> open('binary', 'wb').write(b'this\nis\na\nbinary\0') and None
999 >>> open('binary', 'wb').write(b'this\nis\na\nbinary\0') and None
907 $ hg add binary
1000 $ hg add binary
908 $ hg ci -m 'add a binary file' --quiet
1001 $ hg ci -m 'add a binary file' --quiet
909 $ hg log -f -L binary,1:2 -p
1002 $ hg log -f -L binary,1:2 -p
910 changeset: 10:c96381c229df
1003 changeset: 10:c96381c229df
911 tag: tip
1004 tag: tip
912 user: test
1005 user: test
913 date: Thu Jan 01 00:00:00 1970 +0000
1006 date: Thu Jan 01 00:00:00 1970 +0000
914 summary: add a binary file
1007 summary: add a binary file
915
1008
916 diff --git a/dir/binary b/dir/binary
1009 diff --git a/dir/binary b/dir/binary
917 new file mode 100644
1010 new file mode 100644
918 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c2e1fbed209fe919b3f189a6a31950e9adf61e45
1011 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c2e1fbed209fe919b3f189a6a31950e9adf61e45
919 GIT binary patch
1012 GIT binary patch
920 literal 17
1013 literal 17
921 Wc$_QA$SmdpqC~Ew%)G>+N(KNlNClYy
1014 Wc$_QA$SmdpqC~Ew%)G>+N(KNlNClYy
922
1015
923
1016
924 $ hg log -f -L binary,1:2 -p --config diff.git=false
1017 $ hg log -f -L binary,1:2 -p --config diff.git=false
925 changeset: 10:c96381c229df
1018 changeset: 10:c96381c229df
926 tag: tip
1019 tag: tip
927 user: test
1020 user: test
928 date: Thu Jan 01 00:00:00 1970 +0000
1021 date: Thu Jan 01 00:00:00 1970 +0000
929 summary: add a binary file
1022 summary: add a binary file
930
1023
931 diff -r 6af29c3a778f -r c96381c229df dir/binary
1024 diff -r 6af29c3a778f -r c96381c229df dir/binary
932 Binary file dir/binary has changed
1025 Binary file dir/binary has changed
933
1026
934
1027
935 Option --follow is required.
1028 Option --follow is required.
936
1029
937 $ hg log -L foo,5:7
1030 $ hg log -L foo,5:7
938 abort: --line-range requires --follow
1031 abort: --line-range requires --follow
939 [255]
1032 [255]
940
1033
941 Non-exact pattern kinds are not allowed.
1034 Non-exact pattern kinds are not allowed.
942
1035
943 $ cd ..
1036 $ cd ..
944 $ hg log -f -L glob:*a*,1:2
1037 $ hg log -f -L glob:*a*,1:2
945 hg: parse error: line range pattern 'glob:*a*' must match exactly one file
1038 hg: parse error: line range pattern 'glob:*a*' must match exactly one file
946 [255]
1039 [255]
947
1040
948 We get an error for removed files.
1041 We get an error for removed files.
949
1042
950 $ hg rm dir/baz
1043 $ hg rm dir/baz
951 $ hg ci -m 'remove baz' --quiet
1044 $ hg ci -m 'remove baz' --quiet
952 $ hg log -f -L dir/baz,5:7 -p
1045 $ hg log -f -L dir/baz,5:7 -p
953 abort: cannot follow file not in parent revision: "dir/baz"
1046 abort: cannot follow file not in parent revision: "dir/baz"
954 [255]
1047 [255]
General Comments 0
You need to be logged in to leave comments. Login now