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