##// END OF EJS Templates
logcmdutil: use the same data as {file*} template keywords (issue6642)...
av6 -
r50210:2f326ea1 stable
parent child Browse files
Show More
@@ -1,1287 +1,1285 b''
1 # logcmdutil.py - utility for log-like commands
1 # logcmdutil.py - utility for log-like commands
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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 nullrev, wdirrev
15 from .node import nullrev, wdirrev
16
16
17 from .thirdparty import attr
17 from .thirdparty import attr
18
18
19 from . import (
19 from . import (
20 dagop,
20 dagop,
21 error,
21 error,
22 formatter,
22 formatter,
23 graphmod,
23 graphmod,
24 match as matchmod,
24 match as matchmod,
25 mdiff,
25 mdiff,
26 merge,
26 merge,
27 patch,
27 patch,
28 pathutil,
28 pathutil,
29 pycompat,
29 pycompat,
30 revset,
30 revset,
31 revsetlang,
31 revsetlang,
32 scmutil,
32 scmutil,
33 smartset,
33 smartset,
34 templatekw,
34 templatekw,
35 templater,
35 templater,
36 util,
36 util,
37 )
37 )
38 from .utils import (
38 from .utils import (
39 dateutil,
39 dateutil,
40 stringutil,
40 stringutil,
41 )
41 )
42
42
43
43
44 if pycompat.TYPE_CHECKING:
44 if pycompat.TYPE_CHECKING:
45 from typing import (
45 from typing import (
46 Any,
46 Any,
47 Callable,
47 Callable,
48 Dict,
48 Dict,
49 Optional,
49 Optional,
50 Sequence,
50 Sequence,
51 Tuple,
51 Tuple,
52 )
52 )
53
53
54 for t in (Any, Callable, Dict, Optional, Tuple):
54 for t in (Any, Callable, Dict, Optional, Tuple):
55 assert t
55 assert t
56
56
57
57
58 def getlimit(opts):
58 def getlimit(opts):
59 """get the log limit according to option -l/--limit"""
59 """get the log limit according to option -l/--limit"""
60 limit = opts.get(b'limit')
60 limit = opts.get(b'limit')
61 if limit:
61 if limit:
62 try:
62 try:
63 limit = int(limit)
63 limit = int(limit)
64 except ValueError:
64 except ValueError:
65 raise error.InputError(_(b'limit must be a positive integer'))
65 raise error.InputError(_(b'limit must be a positive integer'))
66 if limit <= 0:
66 if limit <= 0:
67 raise error.InputError(_(b'limit must be positive'))
67 raise error.InputError(_(b'limit must be positive'))
68 else:
68 else:
69 limit = None
69 limit = None
70 return limit
70 return limit
71
71
72
72
73 def diff_parent(ctx):
73 def diff_parent(ctx):
74 """get the context object to use as parent when diffing
74 """get the context object to use as parent when diffing
75
75
76
76
77 If diff.merge is enabled, an overlayworkingctx of the auto-merged parents will be returned.
77 If diff.merge is enabled, an overlayworkingctx of the auto-merged parents will be returned.
78 """
78 """
79 repo = ctx.repo()
79 repo = ctx.repo()
80 if repo.ui.configbool(b"diff", b"merge") and ctx.p2().rev() != nullrev:
80 if repo.ui.configbool(b"diff", b"merge") and ctx.p2().rev() != nullrev:
81 # avoid cycle context -> subrepo -> cmdutil -> logcmdutil
81 # avoid cycle context -> subrepo -> cmdutil -> logcmdutil
82 from . import context
82 from . import context
83
83
84 wctx = context.overlayworkingctx(repo)
84 wctx = context.overlayworkingctx(repo)
85 wctx.setbase(ctx.p1())
85 wctx.setbase(ctx.p1())
86 with repo.ui.configoverride(
86 with repo.ui.configoverride(
87 {
87 {
88 (
88 (
89 b"ui",
89 b"ui",
90 b"forcemerge",
90 b"forcemerge",
91 ): b"internal:merge3-lie-about-conflicts",
91 ): b"internal:merge3-lie-about-conflicts",
92 },
92 },
93 b"merge-diff",
93 b"merge-diff",
94 ):
94 ):
95 with repo.ui.silent():
95 with repo.ui.silent():
96 merge.merge(ctx.p2(), wc=wctx)
96 merge.merge(ctx.p2(), wc=wctx)
97 return wctx
97 return wctx
98 else:
98 else:
99 return ctx.p1()
99 return ctx.p1()
100
100
101
101
102 def diffordiffstat(
102 def diffordiffstat(
103 ui,
103 ui,
104 repo,
104 repo,
105 diffopts,
105 diffopts,
106 ctx1,
106 ctx1,
107 ctx2,
107 ctx2,
108 match,
108 match,
109 changes=None,
109 changes=None,
110 stat=False,
110 stat=False,
111 fp=None,
111 fp=None,
112 graphwidth=0,
112 graphwidth=0,
113 prefix=b'',
113 prefix=b'',
114 root=b'',
114 root=b'',
115 listsubrepos=False,
115 listsubrepos=False,
116 hunksfilterfn=None,
116 hunksfilterfn=None,
117 ):
117 ):
118 '''show diff or diffstat.'''
118 '''show diff or diffstat.'''
119 if root:
119 if root:
120 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
120 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
121 else:
121 else:
122 relroot = b''
122 relroot = b''
123 copysourcematch = None
123 copysourcematch = None
124
124
125 def compose(f, g):
125 def compose(f, g):
126 return lambda x: f(g(x))
126 return lambda x: f(g(x))
127
127
128 def pathfn(f):
128 def pathfn(f):
129 return posixpath.join(prefix, f)
129 return posixpath.join(prefix, f)
130
130
131 if relroot != b'':
131 if relroot != b'':
132 # XXX relative roots currently don't work if the root is within a
132 # XXX relative roots currently don't work if the root is within a
133 # subrepo
133 # subrepo
134 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
134 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
135 uirelroot = uipathfn(pathfn(relroot))
135 uirelroot = uipathfn(pathfn(relroot))
136 relroot += b'/'
136 relroot += b'/'
137 for matchroot in match.files():
137 for matchroot in match.files():
138 if not matchroot.startswith(relroot):
138 if not matchroot.startswith(relroot):
139 ui.warn(
139 ui.warn(
140 _(b'warning: %s not inside relative root %s\n')
140 _(b'warning: %s not inside relative root %s\n')
141 % (uipathfn(pathfn(matchroot)), uirelroot)
141 % (uipathfn(pathfn(matchroot)), uirelroot)
142 )
142 )
143
143
144 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
144 relrootmatch = scmutil.match(ctx2, pats=[relroot], default=b'path')
145 match = matchmod.intersectmatchers(match, relrootmatch)
145 match = matchmod.intersectmatchers(match, relrootmatch)
146 copysourcematch = relrootmatch
146 copysourcematch = relrootmatch
147
147
148 checkroot = repo.ui.configbool(
148 checkroot = repo.ui.configbool(
149 b'devel', b'all-warnings'
149 b'devel', b'all-warnings'
150 ) or repo.ui.configbool(b'devel', b'check-relroot')
150 ) or repo.ui.configbool(b'devel', b'check-relroot')
151
151
152 def relrootpathfn(f):
152 def relrootpathfn(f):
153 if checkroot and not f.startswith(relroot):
153 if checkroot and not f.startswith(relroot):
154 raise AssertionError(
154 raise AssertionError(
155 b"file %s doesn't start with relroot %s" % (f, relroot)
155 b"file %s doesn't start with relroot %s" % (f, relroot)
156 )
156 )
157 return f[len(relroot) :]
157 return f[len(relroot) :]
158
158
159 pathfn = compose(relrootpathfn, pathfn)
159 pathfn = compose(relrootpathfn, pathfn)
160
160
161 if stat:
161 if stat:
162 diffopts = diffopts.copy(context=0, noprefix=False)
162 diffopts = diffopts.copy(context=0, noprefix=False)
163 width = 80
163 width = 80
164 if not ui.plain():
164 if not ui.plain():
165 width = ui.termwidth() - graphwidth
165 width = ui.termwidth() - graphwidth
166 # If an explicit --root was given, don't respect ui.relative-paths
166 # If an explicit --root was given, don't respect ui.relative-paths
167 if not relroot:
167 if not relroot:
168 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
168 pathfn = compose(scmutil.getuipathfn(repo), pathfn)
169
169
170 chunks = ctx2.diff(
170 chunks = ctx2.diff(
171 ctx1,
171 ctx1,
172 match,
172 match,
173 changes,
173 changes,
174 opts=diffopts,
174 opts=diffopts,
175 pathfn=pathfn,
175 pathfn=pathfn,
176 copysourcematch=copysourcematch,
176 copysourcematch=copysourcematch,
177 hunksfilterfn=hunksfilterfn,
177 hunksfilterfn=hunksfilterfn,
178 )
178 )
179
179
180 if fp is not None or ui.canwritewithoutlabels():
180 if fp is not None or ui.canwritewithoutlabels():
181 out = fp or ui
181 out = fp or ui
182 if stat:
182 if stat:
183 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
183 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
184 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
184 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
185 out.write(chunk)
185 out.write(chunk)
186 else:
186 else:
187 if stat:
187 if stat:
188 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
188 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
189 else:
189 else:
190 chunks = patch.difflabel(
190 chunks = patch.difflabel(
191 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
191 lambda chunks, **kwargs: chunks, chunks, opts=diffopts
192 )
192 )
193 if ui.canbatchlabeledwrites():
193 if ui.canbatchlabeledwrites():
194
194
195 def gen():
195 def gen():
196 for chunk, label in chunks:
196 for chunk, label in chunks:
197 yield ui.label(chunk, label=label)
197 yield ui.label(chunk, label=label)
198
198
199 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
199 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
200 ui.write(chunk)
200 ui.write(chunk)
201 else:
201 else:
202 for chunk, label in chunks:
202 for chunk, label in chunks:
203 ui.write(chunk, label=label)
203 ui.write(chunk, label=label)
204
204
205 node2 = ctx2.node()
205 node2 = ctx2.node()
206 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
206 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
207 tempnode2 = node2
207 tempnode2 = node2
208 try:
208 try:
209 if node2 is not None:
209 if node2 is not None:
210 tempnode2 = ctx2.substate[subpath][1]
210 tempnode2 = ctx2.substate[subpath][1]
211 except KeyError:
211 except KeyError:
212 # A subrepo that existed in node1 was deleted between node1 and
212 # A subrepo that existed in node1 was deleted between node1 and
213 # node2 (inclusive). Thus, ctx2's substate won't contain that
213 # node2 (inclusive). Thus, ctx2's substate won't contain that
214 # subpath. The best we can do is to ignore it.
214 # subpath. The best we can do is to ignore it.
215 tempnode2 = None
215 tempnode2 = None
216 submatch = matchmod.subdirmatcher(subpath, match)
216 submatch = matchmod.subdirmatcher(subpath, match)
217 subprefix = repo.wvfs.reljoin(prefix, subpath)
217 subprefix = repo.wvfs.reljoin(prefix, subpath)
218 if listsubrepos or match.exact(subpath) or any(submatch.files()):
218 if listsubrepos or match.exact(subpath) or any(submatch.files()):
219 sub.diff(
219 sub.diff(
220 ui,
220 ui,
221 diffopts,
221 diffopts,
222 tempnode2,
222 tempnode2,
223 submatch,
223 submatch,
224 changes=changes,
224 changes=changes,
225 stat=stat,
225 stat=stat,
226 fp=fp,
226 fp=fp,
227 prefix=subprefix,
227 prefix=subprefix,
228 )
228 )
229
229
230
230
231 class changesetdiffer(object):
231 class changesetdiffer(object):
232 """Generate diff of changeset with pre-configured filtering functions"""
232 """Generate diff of changeset with pre-configured filtering functions"""
233
233
234 def _makefilematcher(self, ctx):
234 def _makefilematcher(self, ctx):
235 return scmutil.matchall(ctx.repo())
235 return scmutil.matchall(ctx.repo())
236
236
237 def _makehunksfilter(self, ctx):
237 def _makehunksfilter(self, ctx):
238 return None
238 return None
239
239
240 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
240 def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
241 diffordiffstat(
241 diffordiffstat(
242 ui,
242 ui,
243 ctx.repo(),
243 ctx.repo(),
244 diffopts,
244 diffopts,
245 diff_parent(ctx),
245 diff_parent(ctx),
246 ctx,
246 ctx,
247 match=self._makefilematcher(ctx),
247 match=self._makefilematcher(ctx),
248 stat=stat,
248 stat=stat,
249 graphwidth=graphwidth,
249 graphwidth=graphwidth,
250 hunksfilterfn=self._makehunksfilter(ctx),
250 hunksfilterfn=self._makehunksfilter(ctx),
251 )
251 )
252
252
253
253
254 def changesetlabels(ctx):
254 def changesetlabels(ctx):
255 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
255 labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()]
256 if ctx.obsolete():
256 if ctx.obsolete():
257 labels.append(b'changeset.obsolete')
257 labels.append(b'changeset.obsolete')
258 if ctx.isunstable():
258 if ctx.isunstable():
259 labels.append(b'changeset.unstable')
259 labels.append(b'changeset.unstable')
260 for instability in ctx.instabilities():
260 for instability in ctx.instabilities():
261 labels.append(b'instability.%s' % instability)
261 labels.append(b'instability.%s' % instability)
262 return b' '.join(labels)
262 return b' '.join(labels)
263
263
264
264
265 class changesetprinter(object):
265 class changesetprinter(object):
266 '''show changeset information when templating not requested.'''
266 '''show changeset information when templating not requested.'''
267
267
268 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
268 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
269 self.ui = ui
269 self.ui = ui
270 self.repo = repo
270 self.repo = repo
271 self.buffered = buffered
271 self.buffered = buffered
272 self._differ = differ or changesetdiffer()
272 self._differ = differ or changesetdiffer()
273 self._diffopts = patch.diffallopts(ui, diffopts)
273 self._diffopts = patch.diffallopts(ui, diffopts)
274 self._includestat = diffopts and diffopts.get(b'stat')
274 self._includestat = diffopts and diffopts.get(b'stat')
275 self._includediff = diffopts and diffopts.get(b'patch')
275 self._includediff = diffopts and diffopts.get(b'patch')
276 self.header = {}
276 self.header = {}
277 self.hunk = {}
277 self.hunk = {}
278 self.lastheader = None
278 self.lastheader = None
279 self.footer = None
279 self.footer = None
280 self._columns = templatekw.getlogcolumns()
280 self._columns = templatekw.getlogcolumns()
281
281
282 def flush(self, ctx):
282 def flush(self, ctx):
283 rev = ctx.rev()
283 rev = ctx.rev()
284 if rev in self.header:
284 if rev in self.header:
285 h = self.header[rev]
285 h = self.header[rev]
286 if h != self.lastheader:
286 if h != self.lastheader:
287 self.lastheader = h
287 self.lastheader = h
288 self.ui.write(h)
288 self.ui.write(h)
289 del self.header[rev]
289 del self.header[rev]
290 if rev in self.hunk:
290 if rev in self.hunk:
291 self.ui.write(self.hunk[rev])
291 self.ui.write(self.hunk[rev])
292 del self.hunk[rev]
292 del self.hunk[rev]
293
293
294 def close(self):
294 def close(self):
295 if self.footer:
295 if self.footer:
296 self.ui.write(self.footer)
296 self.ui.write(self.footer)
297
297
298 def show(self, ctx, copies=None, **props):
298 def show(self, ctx, copies=None, **props):
299 props = pycompat.byteskwargs(props)
299 props = pycompat.byteskwargs(props)
300 if self.buffered:
300 if self.buffered:
301 self.ui.pushbuffer(labeled=True)
301 self.ui.pushbuffer(labeled=True)
302 self._show(ctx, copies, props)
302 self._show(ctx, copies, props)
303 self.hunk[ctx.rev()] = self.ui.popbuffer()
303 self.hunk[ctx.rev()] = self.ui.popbuffer()
304 else:
304 else:
305 self._show(ctx, copies, props)
305 self._show(ctx, copies, props)
306
306
307 def _show(self, ctx, copies, props):
307 def _show(self, ctx, copies, props):
308 '''show a single changeset or file revision'''
308 '''show a single changeset or file revision'''
309 changenode = ctx.node()
309 changenode = ctx.node()
310 graphwidth = props.get(b'graphwidth', 0)
310 graphwidth = props.get(b'graphwidth', 0)
311
311
312 if self.ui.quiet:
312 if self.ui.quiet:
313 self.ui.write(
313 self.ui.write(
314 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
314 b"%s\n" % scmutil.formatchangeid(ctx), label=b'log.node'
315 )
315 )
316 return
316 return
317
317
318 columns = self._columns
318 columns = self._columns
319 self.ui.write(
319 self.ui.write(
320 columns[b'changeset'] % scmutil.formatchangeid(ctx),
320 columns[b'changeset'] % scmutil.formatchangeid(ctx),
321 label=changesetlabels(ctx),
321 label=changesetlabels(ctx),
322 )
322 )
323
323
324 # branches are shown first before any other names due to backwards
324 # branches are shown first before any other names due to backwards
325 # compatibility
325 # compatibility
326 branch = ctx.branch()
326 branch = ctx.branch()
327 # don't show the default branch name
327 # don't show the default branch name
328 if branch != b'default':
328 if branch != b'default':
329 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
329 self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
330
330
331 for nsname, ns in pycompat.iteritems(self.repo.names):
331 for nsname, ns in pycompat.iteritems(self.repo.names):
332 # branches has special logic already handled above, so here we just
332 # branches has special logic already handled above, so here we just
333 # skip it
333 # skip it
334 if nsname == b'branches':
334 if nsname == b'branches':
335 continue
335 continue
336 # we will use the templatename as the color name since those two
336 # we will use the templatename as the color name since those two
337 # should be the same
337 # should be the same
338 for name in ns.names(self.repo, changenode):
338 for name in ns.names(self.repo, changenode):
339 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
339 self.ui.write(ns.logfmt % name, label=b'log.%s' % ns.colorname)
340 if self.ui.debugflag:
340 if self.ui.debugflag:
341 self.ui.write(
341 self.ui.write(
342 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
342 columns[b'phase'] % ctx.phasestr(), label=b'log.phase'
343 )
343 )
344 for pctx in scmutil.meaningfulparents(self.repo, ctx):
344 for pctx in scmutil.meaningfulparents(self.repo, ctx):
345 label = b'log.parent changeset.%s' % pctx.phasestr()
345 label = b'log.parent changeset.%s' % pctx.phasestr()
346 self.ui.write(
346 self.ui.write(
347 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
347 columns[b'parent'] % scmutil.formatchangeid(pctx), label=label
348 )
348 )
349
349
350 if self.ui.debugflag:
350 if self.ui.debugflag:
351 mnode = ctx.manifestnode()
351 mnode = ctx.manifestnode()
352 if mnode is None:
352 if mnode is None:
353 mnode = self.repo.nodeconstants.wdirid
353 mnode = self.repo.nodeconstants.wdirid
354 mrev = wdirrev
354 mrev = wdirrev
355 else:
355 else:
356 mrev = self.repo.manifestlog.rev(mnode)
356 mrev = self.repo.manifestlog.rev(mnode)
357 self.ui.write(
357 self.ui.write(
358 columns[b'manifest']
358 columns[b'manifest']
359 % scmutil.formatrevnode(self.ui, mrev, mnode),
359 % scmutil.formatrevnode(self.ui, mrev, mnode),
360 label=b'ui.debug log.manifest',
360 label=b'ui.debug log.manifest',
361 )
361 )
362 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
362 self.ui.write(columns[b'user'] % ctx.user(), label=b'log.user')
363 self.ui.write(
363 self.ui.write(
364 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
364 columns[b'date'] % dateutil.datestr(ctx.date()), label=b'log.date'
365 )
365 )
366
366
367 if ctx.isunstable():
367 if ctx.isunstable():
368 instabilities = ctx.instabilities()
368 instabilities = ctx.instabilities()
369 self.ui.write(
369 self.ui.write(
370 columns[b'instability'] % b', '.join(instabilities),
370 columns[b'instability'] % b', '.join(instabilities),
371 label=b'log.instability',
371 label=b'log.instability',
372 )
372 )
373
373
374 elif ctx.obsolete():
374 elif ctx.obsolete():
375 self._showobsfate(ctx)
375 self._showobsfate(ctx)
376
376
377 self._exthook(ctx)
377 self._exthook(ctx)
378
378
379 if self.ui.debugflag:
379 if self.ui.debugflag:
380 files = ctx.p1().status(ctx)
381 for key, value in zip(
380 for key, value in zip(
382 [b'files', b'files+', b'files-'],
381 [b'files', b'files+', b'files-'],
383 [files.modified, files.added, files.removed],
382 [ctx.filesmodified(), ctx.filesadded(), ctx.filesremoved()],
384 ):
383 ):
385 if value:
384 if value:
386 self.ui.write(
385 self.ui.write(
387 columns[key] % b" ".join(value),
386 columns[key] % b" ".join(value),
388 label=b'ui.debug log.files',
387 label=b'ui.debug log.files',
389 )
388 )
390 elif ctx.files() and self.ui.verbose:
389 elif ctx.files() and self.ui.verbose:
391 self.ui.write(
390 self.ui.write(
392 columns[b'files'] % b" ".join(ctx.files()),
391 columns[b'files'] % b" ".join(ctx.files()),
393 label=b'ui.note log.files',
392 label=b'ui.note log.files',
394 )
393 )
395 if copies and self.ui.verbose:
394 if copies and self.ui.verbose:
396 copies = [b'%s (%s)' % c for c in copies]
395 copies = [b'%s (%s)' % c for c in copies]
397 self.ui.write(
396 self.ui.write(
398 columns[b'copies'] % b' '.join(copies),
397 columns[b'copies'] % b' '.join(copies),
399 label=b'ui.note log.copies',
398 label=b'ui.note log.copies',
400 )
399 )
401
400
402 extra = ctx.extra()
401 extra = ctx.extra()
403 if extra and self.ui.debugflag:
402 if extra and self.ui.debugflag:
404 for key, value in sorted(extra.items()):
403 for key, value in sorted(extra.items()):
405 self.ui.write(
404 self.ui.write(
406 columns[b'extra'] % (key, stringutil.escapestr(value)),
405 columns[b'extra'] % (key, stringutil.escapestr(value)),
407 label=b'ui.debug log.extra',
406 label=b'ui.debug log.extra',
408 )
407 )
409
408
410 description = ctx.description().strip()
409 description = ctx.description().strip()
411 if description:
410 if description:
412 if self.ui.verbose:
411 if self.ui.verbose:
413 self.ui.write(
412 self.ui.write(
414 _(b"description:\n"), label=b'ui.note log.description'
413 _(b"description:\n"), label=b'ui.note log.description'
415 )
414 )
416 self.ui.write(description, label=b'ui.note log.description')
415 self.ui.write(description, label=b'ui.note log.description')
417 self.ui.write(b"\n\n")
416 self.ui.write(b"\n\n")
418 else:
417 else:
419 self.ui.write(
418 self.ui.write(
420 columns[b'summary'] % description.splitlines()[0],
419 columns[b'summary'] % description.splitlines()[0],
421 label=b'log.summary',
420 label=b'log.summary',
422 )
421 )
423 self.ui.write(b"\n")
422 self.ui.write(b"\n")
424
423
425 self._showpatch(ctx, graphwidth)
424 self._showpatch(ctx, graphwidth)
426
425
427 def _showobsfate(self, ctx):
426 def _showobsfate(self, ctx):
428 # TODO: do not depend on templater
427 # TODO: do not depend on templater
429 tres = formatter.templateresources(self.repo.ui, self.repo)
428 tres = formatter.templateresources(self.repo.ui, self.repo)
430 t = formatter.maketemplater(
429 t = formatter.maketemplater(
431 self.repo.ui,
430 self.repo.ui,
432 b'{join(obsfate, "\n")}',
431 b'{join(obsfate, "\n")}',
433 defaults=templatekw.keywords,
432 defaults=templatekw.keywords,
434 resources=tres,
433 resources=tres,
435 )
434 )
436 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
435 obsfate = t.renderdefault({b'ctx': ctx}).splitlines()
437
436
438 if obsfate:
437 if obsfate:
439 for obsfateline in obsfate:
438 for obsfateline in obsfate:
440 self.ui.write(
439 self.ui.write(
441 self._columns[b'obsolete'] % obsfateline,
440 self._columns[b'obsolete'] % obsfateline,
442 label=b'log.obsfate',
441 label=b'log.obsfate',
443 )
442 )
444
443
445 def _exthook(self, ctx):
444 def _exthook(self, ctx):
446 """empty method used by extension as a hook point"""
445 """empty method used by extension as a hook point"""
447
446
448 def _showpatch(self, ctx, graphwidth=0):
447 def _showpatch(self, ctx, graphwidth=0):
449 if self._includestat:
448 if self._includestat:
450 self._differ.showdiff(
449 self._differ.showdiff(
451 self.ui, ctx, self._diffopts, graphwidth, stat=True
450 self.ui, ctx, self._diffopts, graphwidth, stat=True
452 )
451 )
453 if self._includestat and self._includediff:
452 if self._includestat and self._includediff:
454 self.ui.write(b"\n")
453 self.ui.write(b"\n")
455 if self._includediff:
454 if self._includediff:
456 self._differ.showdiff(
455 self._differ.showdiff(
457 self.ui, ctx, self._diffopts, graphwidth, stat=False
456 self.ui, ctx, self._diffopts, graphwidth, stat=False
458 )
457 )
459 if self._includestat or self._includediff:
458 if self._includestat or self._includediff:
460 self.ui.write(b"\n")
459 self.ui.write(b"\n")
461
460
462
461
463 class changesetformatter(changesetprinter):
462 class changesetformatter(changesetprinter):
464 """Format changeset information by generic formatter"""
463 """Format changeset information by generic formatter"""
465
464
466 def __init__(
465 def __init__(
467 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
466 self, ui, repo, fm, differ=None, diffopts=None, buffered=False
468 ):
467 ):
469 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
468 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
470 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
469 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
471 self._fm = fm
470 self._fm = fm
472
471
473 def close(self):
472 def close(self):
474 self._fm.end()
473 self._fm.end()
475
474
476 def _show(self, ctx, copies, props):
475 def _show(self, ctx, copies, props):
477 '''show a single changeset or file revision'''
476 '''show a single changeset or file revision'''
478 fm = self._fm
477 fm = self._fm
479 fm.startitem()
478 fm.startitem()
480 fm.context(ctx=ctx)
479 fm.context(ctx=ctx)
481 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
480 fm.data(rev=scmutil.intrev(ctx), node=fm.hexfunc(scmutil.binnode(ctx)))
482
481
483 datahint = fm.datahint()
482 datahint = fm.datahint()
484 if self.ui.quiet and not datahint:
483 if self.ui.quiet and not datahint:
485 return
484 return
486
485
487 fm.data(
486 fm.data(
488 branch=ctx.branch(),
487 branch=ctx.branch(),
489 phase=ctx.phasestr(),
488 phase=ctx.phasestr(),
490 user=ctx.user(),
489 user=ctx.user(),
491 date=fm.formatdate(ctx.date()),
490 date=fm.formatdate(ctx.date()),
492 desc=ctx.description(),
491 desc=ctx.description(),
493 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
492 bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'),
494 tags=fm.formatlist(ctx.tags(), name=b'tag'),
493 tags=fm.formatlist(ctx.tags(), name=b'tag'),
495 parents=fm.formatlist(
494 parents=fm.formatlist(
496 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
495 [fm.hexfunc(c.node()) for c in ctx.parents()], name=b'node'
497 ),
496 ),
498 )
497 )
499
498
500 if self.ui.debugflag or b'manifest' in datahint:
499 if self.ui.debugflag or b'manifest' in datahint:
501 fm.data(
500 fm.data(
502 manifest=fm.hexfunc(
501 manifest=fm.hexfunc(
503 ctx.manifestnode() or self.repo.nodeconstants.wdirid
502 ctx.manifestnode() or self.repo.nodeconstants.wdirid
504 )
503 )
505 )
504 )
506 if self.ui.debugflag or b'extra' in datahint:
505 if self.ui.debugflag or b'extra' in datahint:
507 fm.data(extra=fm.formatdict(ctx.extra()))
506 fm.data(extra=fm.formatdict(ctx.extra()))
508
507
509 if (
508 if (
510 self.ui.debugflag
509 self.ui.debugflag
511 or b'modified' in datahint
510 or b'modified' in datahint
512 or b'added' in datahint
511 or b'added' in datahint
513 or b'removed' in datahint
512 or b'removed' in datahint
514 ):
513 ):
515 files = ctx.p1().status(ctx)
516 fm.data(
514 fm.data(
517 modified=fm.formatlist(files.modified, name=b'file'),
515 modified=fm.formatlist(ctx.filesmodified(), name=b'file'),
518 added=fm.formatlist(files.added, name=b'file'),
516 added=fm.formatlist(ctx.filesadded(), name=b'file'),
519 removed=fm.formatlist(files.removed, name=b'file'),
517 removed=fm.formatlist(ctx.filesremoved(), name=b'file'),
520 )
518 )
521
519
522 verbose = not self.ui.debugflag and self.ui.verbose
520 verbose = not self.ui.debugflag and self.ui.verbose
523 if verbose or b'files' in datahint:
521 if verbose or b'files' in datahint:
524 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
522 fm.data(files=fm.formatlist(ctx.files(), name=b'file'))
525 if verbose and copies or b'copies' in datahint:
523 if verbose and copies or b'copies' in datahint:
526 fm.data(
524 fm.data(
527 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
525 copies=fm.formatdict(copies or {}, key=b'name', value=b'source')
528 )
526 )
529
527
530 if self._includestat or b'diffstat' in datahint:
528 if self._includestat or b'diffstat' in datahint:
531 self.ui.pushbuffer()
529 self.ui.pushbuffer()
532 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
530 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
533 fm.data(diffstat=self.ui.popbuffer())
531 fm.data(diffstat=self.ui.popbuffer())
534 if self._includediff or b'diff' in datahint:
532 if self._includediff or b'diff' in datahint:
535 self.ui.pushbuffer()
533 self.ui.pushbuffer()
536 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
534 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
537 fm.data(diff=self.ui.popbuffer())
535 fm.data(diff=self.ui.popbuffer())
538
536
539
537
540 class changesettemplater(changesetprinter):
538 class changesettemplater(changesetprinter):
541 """format changeset information.
539 """format changeset information.
542
540
543 Note: there are a variety of convenience functions to build a
541 Note: there are a variety of convenience functions to build a
544 changesettemplater for common cases. See functions such as:
542 changesettemplater for common cases. See functions such as:
545 maketemplater, changesetdisplayer, buildcommittemplate, or other
543 maketemplater, changesetdisplayer, buildcommittemplate, or other
546 functions that use changesest_templater.
544 functions that use changesest_templater.
547 """
545 """
548
546
549 # Arguments before "buffered" used to be positional. Consider not
547 # Arguments before "buffered" used to be positional. Consider not
550 # adding/removing arguments before "buffered" to not break callers.
548 # adding/removing arguments before "buffered" to not break callers.
551 def __init__(
549 def __init__(
552 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
550 self, ui, repo, tmplspec, differ=None, diffopts=None, buffered=False
553 ):
551 ):
554 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
552 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
555 # tres is shared with _graphnodeformatter()
553 # tres is shared with _graphnodeformatter()
556 self._tresources = tres = formatter.templateresources(ui, repo)
554 self._tresources = tres = formatter.templateresources(ui, repo)
557 self.t = formatter.loadtemplater(
555 self.t = formatter.loadtemplater(
558 ui,
556 ui,
559 tmplspec,
557 tmplspec,
560 defaults=templatekw.keywords,
558 defaults=templatekw.keywords,
561 resources=tres,
559 resources=tres,
562 cache=templatekw.defaulttempl,
560 cache=templatekw.defaulttempl,
563 )
561 )
564 self._counter = itertools.count()
562 self._counter = itertools.count()
565
563
566 self._tref = tmplspec.ref
564 self._tref = tmplspec.ref
567 self._parts = {
565 self._parts = {
568 b'header': b'',
566 b'header': b'',
569 b'footer': b'',
567 b'footer': b'',
570 tmplspec.ref: tmplspec.ref,
568 tmplspec.ref: tmplspec.ref,
571 b'docheader': b'',
569 b'docheader': b'',
572 b'docfooter': b'',
570 b'docfooter': b'',
573 b'separator': b'',
571 b'separator': b'',
574 }
572 }
575 if tmplspec.mapfile:
573 if tmplspec.mapfile:
576 # find correct templates for current mode, for backward
574 # find correct templates for current mode, for backward
577 # compatibility with 'log -v/-q/--debug' using a mapfile
575 # compatibility with 'log -v/-q/--debug' using a mapfile
578 tmplmodes = [
576 tmplmodes = [
579 (True, b''),
577 (True, b''),
580 (self.ui.verbose, b'_verbose'),
578 (self.ui.verbose, b'_verbose'),
581 (self.ui.quiet, b'_quiet'),
579 (self.ui.quiet, b'_quiet'),
582 (self.ui.debugflag, b'_debug'),
580 (self.ui.debugflag, b'_debug'),
583 ]
581 ]
584 for mode, postfix in tmplmodes:
582 for mode, postfix in tmplmodes:
585 for t in self._parts:
583 for t in self._parts:
586 cur = t + postfix
584 cur = t + postfix
587 if mode and cur in self.t:
585 if mode and cur in self.t:
588 self._parts[t] = cur
586 self._parts[t] = cur
589 else:
587 else:
590 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
588 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
591 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
589 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
592 self._parts.update(m)
590 self._parts.update(m)
593
591
594 if self._parts[b'docheader']:
592 if self._parts[b'docheader']:
595 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
593 self.ui.write(self.t.render(self._parts[b'docheader'], {}))
596
594
597 def close(self):
595 def close(self):
598 if self._parts[b'docfooter']:
596 if self._parts[b'docfooter']:
599 if not self.footer:
597 if not self.footer:
600 self.footer = b""
598 self.footer = b""
601 self.footer += self.t.render(self._parts[b'docfooter'], {})
599 self.footer += self.t.render(self._parts[b'docfooter'], {})
602 return super(changesettemplater, self).close()
600 return super(changesettemplater, self).close()
603
601
604 def _show(self, ctx, copies, props):
602 def _show(self, ctx, copies, props):
605 '''show a single changeset or file revision'''
603 '''show a single changeset or file revision'''
606 props = props.copy()
604 props = props.copy()
607 props[b'ctx'] = ctx
605 props[b'ctx'] = ctx
608 props[b'index'] = index = next(self._counter)
606 props[b'index'] = index = next(self._counter)
609 props[b'revcache'] = {b'copies': copies}
607 props[b'revcache'] = {b'copies': copies}
610 graphwidth = props.get(b'graphwidth', 0)
608 graphwidth = props.get(b'graphwidth', 0)
611
609
612 # write separator, which wouldn't work well with the header part below
610 # write separator, which wouldn't work well with the header part below
613 # since there's inherently a conflict between header (across items) and
611 # since there's inherently a conflict between header (across items) and
614 # separator (per item)
612 # separator (per item)
615 if self._parts[b'separator'] and index > 0:
613 if self._parts[b'separator'] and index > 0:
616 self.ui.write(self.t.render(self._parts[b'separator'], {}))
614 self.ui.write(self.t.render(self._parts[b'separator'], {}))
617
615
618 # write header
616 # write header
619 if self._parts[b'header']:
617 if self._parts[b'header']:
620 h = self.t.render(self._parts[b'header'], props)
618 h = self.t.render(self._parts[b'header'], props)
621 if self.buffered:
619 if self.buffered:
622 self.header[ctx.rev()] = h
620 self.header[ctx.rev()] = h
623 else:
621 else:
624 if self.lastheader != h:
622 if self.lastheader != h:
625 self.lastheader = h
623 self.lastheader = h
626 self.ui.write(h)
624 self.ui.write(h)
627
625
628 # write changeset metadata, then patch if requested
626 # write changeset metadata, then patch if requested
629 key = self._parts[self._tref]
627 key = self._parts[self._tref]
630 self.ui.write(self.t.render(key, props))
628 self.ui.write(self.t.render(key, props))
631 self._exthook(ctx)
629 self._exthook(ctx)
632 self._showpatch(ctx, graphwidth)
630 self._showpatch(ctx, graphwidth)
633
631
634 if self._parts[b'footer']:
632 if self._parts[b'footer']:
635 if not self.footer:
633 if not self.footer:
636 self.footer = self.t.render(self._parts[b'footer'], props)
634 self.footer = self.t.render(self._parts[b'footer'], props)
637
635
638
636
639 def templatespec(tmpl, mapfile):
637 def templatespec(tmpl, mapfile):
640 assert not (tmpl and mapfile)
638 assert not (tmpl and mapfile)
641 if mapfile:
639 if mapfile:
642 return formatter.mapfile_templatespec(b'changeset', mapfile)
640 return formatter.mapfile_templatespec(b'changeset', mapfile)
643 else:
641 else:
644 return formatter.literal_templatespec(tmpl)
642 return formatter.literal_templatespec(tmpl)
645
643
646
644
647 def _lookuptemplate(ui, tmpl, style):
645 def _lookuptemplate(ui, tmpl, style):
648 """Find the template matching the given template spec or style
646 """Find the template matching the given template spec or style
649
647
650 See formatter.lookuptemplate() for details.
648 See formatter.lookuptemplate() for details.
651 """
649 """
652
650
653 # ui settings
651 # ui settings
654 if not tmpl and not style: # template are stronger than style
652 if not tmpl and not style: # template are stronger than style
655 tmpl = ui.config(b'command-templates', b'log')
653 tmpl = ui.config(b'command-templates', b'log')
656 if tmpl:
654 if tmpl:
657 return formatter.literal_templatespec(templater.unquotestring(tmpl))
655 return formatter.literal_templatespec(templater.unquotestring(tmpl))
658 else:
656 else:
659 style = util.expandpath(ui.config(b'ui', b'style'))
657 style = util.expandpath(ui.config(b'ui', b'style'))
660
658
661 if not tmpl and style:
659 if not tmpl and style:
662 mapfile = style
660 mapfile = style
663 fp = None
661 fp = None
664 if not os.path.split(mapfile)[0]:
662 if not os.path.split(mapfile)[0]:
665 (mapname, fp) = templater.try_open_template(
663 (mapname, fp) = templater.try_open_template(
666 b'map-cmdline.' + mapfile
664 b'map-cmdline.' + mapfile
667 ) or templater.try_open_template(mapfile)
665 ) or templater.try_open_template(mapfile)
668 if mapname:
666 if mapname:
669 mapfile = mapname
667 mapfile = mapname
670 return formatter.mapfile_templatespec(b'changeset', mapfile, fp)
668 return formatter.mapfile_templatespec(b'changeset', mapfile, fp)
671
669
672 return formatter.lookuptemplate(ui, b'changeset', tmpl)
670 return formatter.lookuptemplate(ui, b'changeset', tmpl)
673
671
674
672
675 def maketemplater(ui, repo, tmpl, buffered=False):
673 def maketemplater(ui, repo, tmpl, buffered=False):
676 """Create a changesettemplater from a literal template 'tmpl'
674 """Create a changesettemplater from a literal template 'tmpl'
677 byte-string."""
675 byte-string."""
678 spec = formatter.literal_templatespec(tmpl)
676 spec = formatter.literal_templatespec(tmpl)
679 return changesettemplater(ui, repo, spec, buffered=buffered)
677 return changesettemplater(ui, repo, spec, buffered=buffered)
680
678
681
679
682 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
680 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
683 """show one changeset using template or regular display.
681 """show one changeset using template or regular display.
684
682
685 Display format will be the first non-empty hit of:
683 Display format will be the first non-empty hit of:
686 1. option 'template'
684 1. option 'template'
687 2. option 'style'
685 2. option 'style'
688 3. [command-templates] setting 'log'
686 3. [command-templates] setting 'log'
689 4. [ui] setting 'style'
687 4. [ui] setting 'style'
690 If all of these values are either the unset or the empty string,
688 If all of these values are either the unset or the empty string,
691 regular display via changesetprinter() is done.
689 regular display via changesetprinter() is done.
692 """
690 """
693 postargs = (differ, opts, buffered)
691 postargs = (differ, opts, buffered)
694 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
692 spec = _lookuptemplate(ui, opts.get(b'template'), opts.get(b'style'))
695
693
696 # machine-readable formats have slightly different keyword set than
694 # machine-readable formats have slightly different keyword set than
697 # plain templates, which are handled by changesetformatter.
695 # plain templates, which are handled by changesetformatter.
698 # note that {b'pickle', b'debug'} can also be added to the list if needed.
696 # note that {b'pickle', b'debug'} can also be added to the list if needed.
699 if spec.ref in {b'cbor', b'json'}:
697 if spec.ref in {b'cbor', b'json'}:
700 fm = ui.formatter(b'log', opts)
698 fm = ui.formatter(b'log', opts)
701 return changesetformatter(ui, repo, fm, *postargs)
699 return changesetformatter(ui, repo, fm, *postargs)
702
700
703 if not spec.ref and not spec.tmpl and not spec.mapfile:
701 if not spec.ref and not spec.tmpl and not spec.mapfile:
704 return changesetprinter(ui, repo, *postargs)
702 return changesetprinter(ui, repo, *postargs)
705
703
706 return changesettemplater(ui, repo, spec, *postargs)
704 return changesettemplater(ui, repo, spec, *postargs)
707
705
708
706
709 @attr.s
707 @attr.s
710 class walkopts(object):
708 class walkopts(object):
711 """Options to configure a set of revisions and file matcher factory
709 """Options to configure a set of revisions and file matcher factory
712 to scan revision/file history
710 to scan revision/file history
713 """
711 """
714
712
715 # raw command-line parameters, which a matcher will be built from
713 # raw command-line parameters, which a matcher will be built from
716 pats = attr.ib()
714 pats = attr.ib()
717 opts = attr.ib()
715 opts = attr.ib()
718
716
719 # a list of revset expressions to be traversed; if follow, it specifies
717 # a list of revset expressions to be traversed; if follow, it specifies
720 # the start revisions
718 # the start revisions
721 revspec = attr.ib()
719 revspec = attr.ib()
722
720
723 # miscellaneous queries to filter revisions (see "hg help log" for details)
721 # miscellaneous queries to filter revisions (see "hg help log" for details)
724 bookmarks = attr.ib(default=attr.Factory(list))
722 bookmarks = attr.ib(default=attr.Factory(list))
725 branches = attr.ib(default=attr.Factory(list))
723 branches = attr.ib(default=attr.Factory(list))
726 date = attr.ib(default=None)
724 date = attr.ib(default=None)
727 keywords = attr.ib(default=attr.Factory(list))
725 keywords = attr.ib(default=attr.Factory(list))
728 no_merges = attr.ib(default=False)
726 no_merges = attr.ib(default=False)
729 only_merges = attr.ib(default=False)
727 only_merges = attr.ib(default=False)
730 prune_ancestors = attr.ib(default=attr.Factory(list))
728 prune_ancestors = attr.ib(default=attr.Factory(list))
731 users = attr.ib(default=attr.Factory(list))
729 users = attr.ib(default=attr.Factory(list))
732
730
733 # miscellaneous matcher arguments
731 # miscellaneous matcher arguments
734 include_pats = attr.ib(default=attr.Factory(list))
732 include_pats = attr.ib(default=attr.Factory(list))
735 exclude_pats = attr.ib(default=attr.Factory(list))
733 exclude_pats = attr.ib(default=attr.Factory(list))
736
734
737 # 0: no follow, 1: follow first, 2: follow both parents
735 # 0: no follow, 1: follow first, 2: follow both parents
738 follow = attr.ib(default=0)
736 follow = attr.ib(default=0)
739
737
740 # do not attempt filelog-based traversal, which may be fast but cannot
738 # do not attempt filelog-based traversal, which may be fast but cannot
741 # include revisions where files were removed
739 # include revisions where files were removed
742 force_changelog_traversal = attr.ib(default=False)
740 force_changelog_traversal = attr.ib(default=False)
743
741
744 # filter revisions by file patterns, which should be disabled only if
742 # filter revisions by file patterns, which should be disabled only if
745 # you want to include revisions where files were unmodified
743 # you want to include revisions where files were unmodified
746 filter_revisions_by_pats = attr.ib(default=True)
744 filter_revisions_by_pats = attr.ib(default=True)
747
745
748 # sort revisions prior to traversal: 'desc', 'topo', or None
746 # sort revisions prior to traversal: 'desc', 'topo', or None
749 sort_revisions = attr.ib(default=None)
747 sort_revisions = attr.ib(default=None)
750
748
751 # limit number of changes displayed; None means unlimited
749 # limit number of changes displayed; None means unlimited
752 limit = attr.ib(default=None)
750 limit = attr.ib(default=None)
753
751
754
752
755 def parseopts(ui, pats, opts):
753 def parseopts(ui, pats, opts):
756 # type: (Any, Sequence[bytes], Dict[bytes, Any]) -> walkopts
754 # type: (Any, Sequence[bytes], Dict[bytes, Any]) -> walkopts
757 """Parse log command options into walkopts
755 """Parse log command options into walkopts
758
756
759 The returned walkopts will be passed in to getrevs() or makewalker().
757 The returned walkopts will be passed in to getrevs() or makewalker().
760 """
758 """
761 if opts.get(b'follow_first'):
759 if opts.get(b'follow_first'):
762 follow = 1
760 follow = 1
763 elif opts.get(b'follow'):
761 elif opts.get(b'follow'):
764 follow = 2
762 follow = 2
765 else:
763 else:
766 follow = 0
764 follow = 0
767
765
768 if opts.get(b'graph'):
766 if opts.get(b'graph'):
769 if ui.configbool(b'experimental', b'log.topo'):
767 if ui.configbool(b'experimental', b'log.topo'):
770 sort_revisions = b'topo'
768 sort_revisions = b'topo'
771 else:
769 else:
772 sort_revisions = b'desc'
770 sort_revisions = b'desc'
773 else:
771 else:
774 sort_revisions = None
772 sort_revisions = None
775
773
776 return walkopts(
774 return walkopts(
777 pats=pats,
775 pats=pats,
778 opts=opts,
776 opts=opts,
779 revspec=opts.get(b'rev', []),
777 revspec=opts.get(b'rev', []),
780 bookmarks=opts.get(b'bookmark', []),
778 bookmarks=opts.get(b'bookmark', []),
781 # 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
782 # the same time
780 # the same time
783 branches=opts.get(b'branch', []) + opts.get(b'only_branch', []),
781 branches=opts.get(b'branch', []) + opts.get(b'only_branch', []),
784 date=opts.get(b'date'),
782 date=opts.get(b'date'),
785 keywords=opts.get(b'keyword', []),
783 keywords=opts.get(b'keyword', []),
786 no_merges=bool(opts.get(b'no_merges')),
784 no_merges=bool(opts.get(b'no_merges')),
787 only_merges=bool(opts.get(b'only_merges')),
785 only_merges=bool(opts.get(b'only_merges')),
788 prune_ancestors=opts.get(b'prune', []),
786 prune_ancestors=opts.get(b'prune', []),
789 users=opts.get(b'user', []),
787 users=opts.get(b'user', []),
790 include_pats=opts.get(b'include', []),
788 include_pats=opts.get(b'include', []),
791 exclude_pats=opts.get(b'exclude', []),
789 exclude_pats=opts.get(b'exclude', []),
792 follow=follow,
790 follow=follow,
793 force_changelog_traversal=bool(opts.get(b'removed')),
791 force_changelog_traversal=bool(opts.get(b'removed')),
794 sort_revisions=sort_revisions,
792 sort_revisions=sort_revisions,
795 limit=getlimit(opts),
793 limit=getlimit(opts),
796 )
794 )
797
795
798
796
799 def _makematcher(repo, revs, wopts):
797 def _makematcher(repo, revs, wopts):
800 """Build matcher and expanded patterns from log options
798 """Build matcher and expanded patterns from log options
801
799
802 If --follow, revs are the revisions to follow from.
800 If --follow, revs are the revisions to follow from.
803
801
804 Returns (match, pats, slowpath) where
802 Returns (match, pats, slowpath) where
805 - match: a matcher built from the given pats and -I/-X opts
803 - match: a matcher built from the given pats and -I/-X opts
806 - pats: patterns used (globs are expanded on Windows)
804 - pats: patterns used (globs are expanded on Windows)
807 - slowpath: True if patterns aren't as simple as scanning filelogs
805 - slowpath: True if patterns aren't as simple as scanning filelogs
808 """
806 """
809 # pats/include/exclude are passed to match.match() directly in
807 # pats/include/exclude are passed to match.match() directly in
810 # _matchfiles() revset, but a log-like command should build its matcher
808 # _matchfiles() revset, but a log-like command should build its matcher
811 # with scmutil.match(). The difference is input pats are globbed on
809 # with scmutil.match(). The difference is input pats are globbed on
812 # platforms without shell expansion (windows).
810 # platforms without shell expansion (windows).
813 wctx = repo[None]
811 wctx = repo[None]
814 match, pats = scmutil.matchandpats(wctx, wopts.pats, wopts.opts)
812 match, pats = scmutil.matchandpats(wctx, wopts.pats, wopts.opts)
815 slowpath = match.anypats() or (
813 slowpath = match.anypats() or (
816 not match.always() and wopts.force_changelog_traversal
814 not match.always() and wopts.force_changelog_traversal
817 )
815 )
818 if not slowpath:
816 if not slowpath:
819 if wopts.follow and wopts.revspec:
817 if wopts.follow and wopts.revspec:
820 # There may be the case that a path doesn't exist in some (but
818 # There may be the case that a path doesn't exist in some (but
821 # not all) of the specified start revisions, but let's consider
819 # not all) of the specified start revisions, but let's consider
822 # the path is valid. Missing files will be warned by the matcher.
820 # the path is valid. Missing files will be warned by the matcher.
823 startctxs = [repo[r] for r in revs]
821 startctxs = [repo[r] for r in revs]
824 for f in match.files():
822 for f in match.files():
825 found = False
823 found = False
826 for c in startctxs:
824 for c in startctxs:
827 if f in c:
825 if f in c:
828 found = True
826 found = True
829 elif c.hasdir(f):
827 elif c.hasdir(f):
830 # If a directory exists in any of the start revisions,
828 # If a directory exists in any of the start revisions,
831 # take the slow path.
829 # take the slow path.
832 found = slowpath = True
830 found = slowpath = True
833 if not found:
831 if not found:
834 raise error.StateError(
832 raise error.StateError(
835 _(
833 _(
836 b'cannot follow file not in any of the specified '
834 b'cannot follow file not in any of the specified '
837 b'revisions: "%s"'
835 b'revisions: "%s"'
838 )
836 )
839 % f
837 % f
840 )
838 )
841 elif wopts.follow:
839 elif wopts.follow:
842 for f in match.files():
840 for f in match.files():
843 if f not in wctx:
841 if f not in wctx:
844 # If the file exists, it may be a directory, so let it
842 # If the file exists, it may be a directory, so let it
845 # take the slow path.
843 # take the slow path.
846 if os.path.exists(repo.wjoin(f)):
844 if os.path.exists(repo.wjoin(f)):
847 slowpath = True
845 slowpath = True
848 continue
846 continue
849 else:
847 else:
850 raise error.StateError(
848 raise error.StateError(
851 _(
849 _(
852 b'cannot follow file not in parent '
850 b'cannot follow file not in parent '
853 b'revision: "%s"'
851 b'revision: "%s"'
854 )
852 )
855 % f
853 % f
856 )
854 )
857 filelog = repo.file(f)
855 filelog = repo.file(f)
858 if not filelog:
856 if not filelog:
859 # A file exists in wdir but not in history, which means
857 # A file exists in wdir but not in history, which means
860 # the file isn't committed yet.
858 # the file isn't committed yet.
861 raise error.StateError(
859 raise error.StateError(
862 _(b'cannot follow nonexistent file: "%s"') % f
860 _(b'cannot follow nonexistent file: "%s"') % f
863 )
861 )
864 else:
862 else:
865 for f in match.files():
863 for f in match.files():
866 filelog = repo.file(f)
864 filelog = repo.file(f)
867 if not filelog:
865 if not filelog:
868 # A zero count may be a directory or deleted file, so
866 # A zero count may be a directory or deleted file, so
869 # try to find matching entries on the slow path.
867 # try to find matching entries on the slow path.
870 slowpath = True
868 slowpath = True
871
869
872 # We decided to fall back to the slowpath because at least one
870 # We decided to fall back to the slowpath because at least one
873 # of the paths was not a file. Check to see if at least one of them
871 # of the paths was not a file. Check to see if at least one of them
874 # existed in history - in that case, we'll continue down the
872 # existed in history - in that case, we'll continue down the
875 # slowpath; otherwise, we can turn off the slowpath
873 # slowpath; otherwise, we can turn off the slowpath
876 if slowpath:
874 if slowpath:
877 for path in match.files():
875 for path in match.files():
878 if not path or path in repo.store:
876 if not path or path in repo.store:
879 break
877 break
880 else:
878 else:
881 slowpath = False
879 slowpath = False
882
880
883 return match, pats, slowpath
881 return match, pats, slowpath
884
882
885
883
886 def _fileancestors(repo, revs, match, followfirst):
884 def _fileancestors(repo, revs, match, followfirst):
887 fctxs = []
885 fctxs = []
888 for r in revs:
886 for r in revs:
889 ctx = repo[r]
887 ctx = repo[r]
890 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
888 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
891
889
892 # When displaying a revision with --patch --follow FILE, we have
890 # When displaying a revision with --patch --follow FILE, we have
893 # to know which file of the revision must be diffed. With
891 # to know which file of the revision must be diffed. With
894 # --follow, we want the names of the ancestors of FILE in the
892 # --follow, we want the names of the ancestors of FILE in the
895 # revision, stored in "fcache". "fcache" is populated as a side effect
893 # revision, stored in "fcache". "fcache" is populated as a side effect
896 # of the graph traversal.
894 # of the graph traversal.
897 fcache = {}
895 fcache = {}
898
896
899 def filematcher(ctx):
897 def filematcher(ctx):
900 return scmutil.matchfiles(repo, fcache.get(scmutil.intrev(ctx), []))
898 return scmutil.matchfiles(repo, fcache.get(scmutil.intrev(ctx), []))
901
899
902 def revgen():
900 def revgen():
903 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
901 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
904 fcache[rev] = [c.path() for c in cs]
902 fcache[rev] = [c.path() for c in cs]
905 yield rev
903 yield rev
906
904
907 return smartset.generatorset(revgen(), iterasc=False), filematcher
905 return smartset.generatorset(revgen(), iterasc=False), filematcher
908
906
909
907
910 def _makenofollowfilematcher(repo, pats, opts):
908 def _makenofollowfilematcher(repo, pats, opts):
911 '''hook for extensions to override the filematcher for non-follow cases'''
909 '''hook for extensions to override the filematcher for non-follow cases'''
912 return None
910 return None
913
911
914
912
915 def revsingle(repo, revspec, default=b'.', localalias=None):
913 def revsingle(repo, revspec, default=b'.', localalias=None):
916 """Resolves user-provided revset(s) into a single revision.
914 """Resolves user-provided revset(s) into a single revision.
917
915
918 This just wraps the lower-level scmutil.revsingle() in order to raise an
916 This just wraps the lower-level scmutil.revsingle() in order to raise an
919 exception indicating user error.
917 exception indicating user error.
920 """
918 """
921 try:
919 try:
922 return scmutil.revsingle(repo, revspec, default, localalias)
920 return scmutil.revsingle(repo, revspec, default, localalias)
923 except error.RepoLookupError as e:
921 except error.RepoLookupError as e:
924 raise error.InputError(e.args[0], hint=e.hint)
922 raise error.InputError(e.args[0], hint=e.hint)
925
923
926
924
927 def revpair(repo, revs):
925 def revpair(repo, revs):
928 """Resolves user-provided revset(s) into two revisions.
926 """Resolves user-provided revset(s) into two revisions.
929
927
930 This just wraps the lower-level scmutil.revpair() in order to raise an
928 This just wraps the lower-level scmutil.revpair() in order to raise an
931 exception indicating user error.
929 exception indicating user error.
932 """
930 """
933 try:
931 try:
934 return scmutil.revpair(repo, revs)
932 return scmutil.revpair(repo, revs)
935 except error.RepoLookupError as e:
933 except error.RepoLookupError as e:
936 raise error.InputError(e.args[0], hint=e.hint)
934 raise error.InputError(e.args[0], hint=e.hint)
937
935
938
936
939 def revrange(repo, specs, localalias=None):
937 def revrange(repo, specs, localalias=None):
940 """Resolves user-provided revset(s).
938 """Resolves user-provided revset(s).
941
939
942 This just wraps the lower-level scmutil.revrange() in order to raise an
940 This just wraps the lower-level scmutil.revrange() in order to raise an
943 exception indicating user error.
941 exception indicating user error.
944 """
942 """
945 try:
943 try:
946 return scmutil.revrange(repo, specs, localalias)
944 return scmutil.revrange(repo, specs, localalias)
947 except error.RepoLookupError as e:
945 except error.RepoLookupError as e:
948 raise error.InputError(e.args[0], hint=e.hint)
946 raise error.InputError(e.args[0], hint=e.hint)
949
947
950
948
951 _opt2logrevset = {
949 _opt2logrevset = {
952 b'no_merges': (b'not merge()', None),
950 b'no_merges': (b'not merge()', None),
953 b'only_merges': (b'merge()', None),
951 b'only_merges': (b'merge()', None),
954 b'_matchfiles': (None, b'_matchfiles(%ps)'),
952 b'_matchfiles': (None, b'_matchfiles(%ps)'),
955 b'date': (b'date(%s)', None),
953 b'date': (b'date(%s)', None),
956 b'branch': (b'branch(%s)', b'%lr'),
954 b'branch': (b'branch(%s)', b'%lr'),
957 b'_patslog': (b'filelog(%s)', b'%lr'),
955 b'_patslog': (b'filelog(%s)', b'%lr'),
958 b'keyword': (b'keyword(%s)', b'%lr'),
956 b'keyword': (b'keyword(%s)', b'%lr'),
959 b'prune': (b'ancestors(%s)', b'not %lr'),
957 b'prune': (b'ancestors(%s)', b'not %lr'),
960 b'user': (b'user(%s)', b'%lr'),
958 b'user': (b'user(%s)', b'%lr'),
961 }
959 }
962
960
963
961
964 def _makerevset(repo, wopts, slowpath):
962 def _makerevset(repo, wopts, slowpath):
965 """Return a revset string built from log options and file patterns"""
963 """Return a revset string built from log options and file patterns"""
966 opts = {
964 opts = {
967 b'branch': [b'literal:' + repo.lookupbranch(b) for b in wopts.branches],
965 b'branch': [b'literal:' + repo.lookupbranch(b) for b in wopts.branches],
968 b'date': wopts.date,
966 b'date': wopts.date,
969 b'keyword': wopts.keywords,
967 b'keyword': wopts.keywords,
970 b'no_merges': wopts.no_merges,
968 b'no_merges': wopts.no_merges,
971 b'only_merges': wopts.only_merges,
969 b'only_merges': wopts.only_merges,
972 b'prune': wopts.prune_ancestors,
970 b'prune': wopts.prune_ancestors,
973 b'user': [b'literal:' + v for v in wopts.users],
971 b'user': [b'literal:' + v for v in wopts.users],
974 }
972 }
975
973
976 if wopts.filter_revisions_by_pats and slowpath:
974 if wopts.filter_revisions_by_pats and slowpath:
977 # pats/include/exclude cannot be represented as separate
975 # pats/include/exclude cannot be represented as separate
978 # revset expressions as their filtering logic applies at file
976 # revset expressions as their filtering logic applies at file
979 # level. For instance "-I a -X b" matches a revision touching
977 # level. For instance "-I a -X b" matches a revision touching
980 # "a" and "b" while "file(a) and not file(b)" does
978 # "a" and "b" while "file(a) and not file(b)" does
981 # not. Besides, filesets are evaluated against the working
979 # not. Besides, filesets are evaluated against the working
982 # directory.
980 # directory.
983 matchargs = [b'r:', b'd:relpath']
981 matchargs = [b'r:', b'd:relpath']
984 for p in wopts.pats:
982 for p in wopts.pats:
985 matchargs.append(b'p:' + p)
983 matchargs.append(b'p:' + p)
986 for p in wopts.include_pats:
984 for p in wopts.include_pats:
987 matchargs.append(b'i:' + p)
985 matchargs.append(b'i:' + p)
988 for p in wopts.exclude_pats:
986 for p in wopts.exclude_pats:
989 matchargs.append(b'x:' + p)
987 matchargs.append(b'x:' + p)
990 opts[b'_matchfiles'] = matchargs
988 opts[b'_matchfiles'] = matchargs
991 elif wopts.filter_revisions_by_pats and not wopts.follow:
989 elif wopts.filter_revisions_by_pats and not wopts.follow:
992 opts[b'_patslog'] = list(wopts.pats)
990 opts[b'_patslog'] = list(wopts.pats)
993
991
994 expr = []
992 expr = []
995 for op, val in sorted(pycompat.iteritems(opts)):
993 for op, val in sorted(pycompat.iteritems(opts)):
996 if not val:
994 if not val:
997 continue
995 continue
998 revop, listop = _opt2logrevset[op]
996 revop, listop = _opt2logrevset[op]
999 if revop and b'%' not in revop:
997 if revop and b'%' not in revop:
1000 expr.append(revop)
998 expr.append(revop)
1001 elif not listop:
999 elif not listop:
1002 expr.append(revsetlang.formatspec(revop, val))
1000 expr.append(revsetlang.formatspec(revop, val))
1003 else:
1001 else:
1004 if revop:
1002 if revop:
1005 val = [revsetlang.formatspec(revop, v) for v in val]
1003 val = [revsetlang.formatspec(revop, v) for v in val]
1006 expr.append(revsetlang.formatspec(listop, val))
1004 expr.append(revsetlang.formatspec(listop, val))
1007
1005
1008 if wopts.bookmarks:
1006 if wopts.bookmarks:
1009 expr.append(
1007 expr.append(
1010 revsetlang.formatspec(
1008 revsetlang.formatspec(
1011 b'%lr',
1009 b'%lr',
1012 [scmutil.format_bookmark_revspec(v) for v in wopts.bookmarks],
1010 [scmutil.format_bookmark_revspec(v) for v in wopts.bookmarks],
1013 )
1011 )
1014 )
1012 )
1015
1013
1016 if expr:
1014 if expr:
1017 expr = b'(' + b' and '.join(expr) + b')'
1015 expr = b'(' + b' and '.join(expr) + b')'
1018 else:
1016 else:
1019 expr = None
1017 expr = None
1020 return expr
1018 return expr
1021
1019
1022
1020
1023 def _initialrevs(repo, wopts):
1021 def _initialrevs(repo, wopts):
1024 """Return the initial set of revisions to be filtered or followed"""
1022 """Return the initial set of revisions to be filtered or followed"""
1025 if wopts.revspec:
1023 if wopts.revspec:
1026 revs = revrange(repo, wopts.revspec)
1024 revs = revrange(repo, wopts.revspec)
1027 elif wopts.follow and repo.dirstate.p1() == repo.nullid:
1025 elif wopts.follow and repo.dirstate.p1() == repo.nullid:
1028 revs = smartset.baseset()
1026 revs = smartset.baseset()
1029 elif wopts.follow:
1027 elif wopts.follow:
1030 revs = repo.revs(b'.')
1028 revs = repo.revs(b'.')
1031 else:
1029 else:
1032 revs = smartset.spanset(repo)
1030 revs = smartset.spanset(repo)
1033 revs.reverse()
1031 revs.reverse()
1034 return revs
1032 return revs
1035
1033
1036
1034
1037 def makewalker(repo, wopts):
1035 def makewalker(repo, wopts):
1038 # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[Callable[[Any], matchmod.basematcher]]]
1036 # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[Callable[[Any], matchmod.basematcher]]]
1039 """Build (revs, makefilematcher) to scan revision/file history
1037 """Build (revs, makefilematcher) to scan revision/file history
1040
1038
1041 - revs is the smartset to be traversed.
1039 - revs is the smartset to be traversed.
1042 - makefilematcher is a function to map ctx to a matcher for that revision
1040 - makefilematcher is a function to map ctx to a matcher for that revision
1043 """
1041 """
1044 revs = _initialrevs(repo, wopts)
1042 revs = _initialrevs(repo, wopts)
1045 if not revs:
1043 if not revs:
1046 return smartset.baseset(), None
1044 return smartset.baseset(), None
1047 # TODO: might want to merge slowpath with wopts.force_changelog_traversal
1045 # TODO: might want to merge slowpath with wopts.force_changelog_traversal
1048 match, pats, slowpath = _makematcher(repo, revs, wopts)
1046 match, pats, slowpath = _makematcher(repo, revs, wopts)
1049 wopts = attr.evolve(wopts, pats=pats)
1047 wopts = attr.evolve(wopts, pats=pats)
1050
1048
1051 filematcher = None
1049 filematcher = None
1052 if wopts.follow:
1050 if wopts.follow:
1053 if slowpath or match.always():
1051 if slowpath or match.always():
1054 revs = dagop.revancestors(repo, revs, followfirst=wopts.follow == 1)
1052 revs = dagop.revancestors(repo, revs, followfirst=wopts.follow == 1)
1055 else:
1053 else:
1056 assert not wopts.force_changelog_traversal
1054 assert not wopts.force_changelog_traversal
1057 revs, filematcher = _fileancestors(
1055 revs, filematcher = _fileancestors(
1058 repo, revs, match, followfirst=wopts.follow == 1
1056 repo, revs, match, followfirst=wopts.follow == 1
1059 )
1057 )
1060 revs.reverse()
1058 revs.reverse()
1061 if filematcher is None:
1059 if filematcher is None:
1062 filematcher = _makenofollowfilematcher(repo, wopts.pats, wopts.opts)
1060 filematcher = _makenofollowfilematcher(repo, wopts.pats, wopts.opts)
1063 if filematcher is None:
1061 if filematcher is None:
1064
1062
1065 def filematcher(ctx):
1063 def filematcher(ctx):
1066 return match
1064 return match
1067
1065
1068 expr = _makerevset(repo, wopts, slowpath)
1066 expr = _makerevset(repo, wopts, slowpath)
1069 if wopts.sort_revisions:
1067 if wopts.sort_revisions:
1070 assert wopts.sort_revisions in {b'topo', b'desc'}
1068 assert wopts.sort_revisions in {b'topo', b'desc'}
1071 if wopts.sort_revisions == b'topo':
1069 if wopts.sort_revisions == b'topo':
1072 if not revs.istopo():
1070 if not revs.istopo():
1073 revs = dagop.toposort(revs, repo.changelog.parentrevs)
1071 revs = dagop.toposort(revs, repo.changelog.parentrevs)
1074 # TODO: try to iterate the set lazily
1072 # TODO: try to iterate the set lazily
1075 revs = revset.baseset(list(revs), istopo=True)
1073 revs = revset.baseset(list(revs), istopo=True)
1076 elif not (revs.isdescending() or revs.istopo()):
1074 elif not (revs.isdescending() or revs.istopo()):
1077 # User-specified revs might be unsorted
1075 # User-specified revs might be unsorted
1078 revs.sort(reverse=True)
1076 revs.sort(reverse=True)
1079 if expr:
1077 if expr:
1080 matcher = revset.match(None, expr)
1078 matcher = revset.match(None, expr)
1081 revs = matcher(repo, revs)
1079 revs = matcher(repo, revs)
1082 if wopts.limit is not None:
1080 if wopts.limit is not None:
1083 revs = revs.slice(0, wopts.limit)
1081 revs = revs.slice(0, wopts.limit)
1084
1082
1085 return revs, filematcher
1083 return revs, filematcher
1086
1084
1087
1085
1088 def getrevs(repo, wopts):
1086 def getrevs(repo, wopts):
1089 # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
1087 # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]]
1090 """Return (revs, differ) where revs is a smartset
1088 """Return (revs, differ) where revs is a smartset
1091
1089
1092 differ is a changesetdiffer with pre-configured file matcher.
1090 differ is a changesetdiffer with pre-configured file matcher.
1093 """
1091 """
1094 revs, filematcher = makewalker(repo, wopts)
1092 revs, filematcher = makewalker(repo, wopts)
1095 if not revs:
1093 if not revs:
1096 return revs, None
1094 return revs, None
1097 differ = changesetdiffer()
1095 differ = changesetdiffer()
1098 differ._makefilematcher = filematcher
1096 differ._makefilematcher = filematcher
1099 return revs, differ
1097 return revs, differ
1100
1098
1101
1099
1102 def _parselinerangeopt(repo, opts):
1100 def _parselinerangeopt(repo, opts):
1103 """Parse --line-range log option and return a list of tuples (filename,
1101 """Parse --line-range log option and return a list of tuples (filename,
1104 (fromline, toline)).
1102 (fromline, toline)).
1105 """
1103 """
1106 linerangebyfname = []
1104 linerangebyfname = []
1107 for pat in opts.get(b'line_range', []):
1105 for pat in opts.get(b'line_range', []):
1108 try:
1106 try:
1109 pat, linerange = pat.rsplit(b',', 1)
1107 pat, linerange = pat.rsplit(b',', 1)
1110 except ValueError:
1108 except ValueError:
1111 raise error.InputError(
1109 raise error.InputError(
1112 _(b'malformatted line-range pattern %s') % pat
1110 _(b'malformatted line-range pattern %s') % pat
1113 )
1111 )
1114 try:
1112 try:
1115 fromline, toline = map(int, linerange.split(b':'))
1113 fromline, toline = map(int, linerange.split(b':'))
1116 except ValueError:
1114 except ValueError:
1117 raise error.InputError(_(b"invalid line range for %s") % pat)
1115 raise error.InputError(_(b"invalid line range for %s") % pat)
1118 msg = _(b"line range pattern '%s' must match exactly one file") % pat
1116 msg = _(b"line range pattern '%s' must match exactly one file") % pat
1119 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
1117 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
1120 linerangebyfname.append(
1118 linerangebyfname.append(
1121 (fname, util.processlinerange(fromline, toline))
1119 (fname, util.processlinerange(fromline, toline))
1122 )
1120 )
1123 return linerangebyfname
1121 return linerangebyfname
1124
1122
1125
1123
1126 def getlinerangerevs(repo, userrevs, opts):
1124 def getlinerangerevs(repo, userrevs, opts):
1127 """Return (revs, differ).
1125 """Return (revs, differ).
1128
1126
1129 "revs" are revisions obtained by processing "line-range" log options and
1127 "revs" are revisions obtained by processing "line-range" log options and
1130 walking block ancestors of each specified file/line-range.
1128 walking block ancestors of each specified file/line-range.
1131
1129
1132 "differ" is a changesetdiffer with pre-configured file matcher and hunks
1130 "differ" is a changesetdiffer with pre-configured file matcher and hunks
1133 filter.
1131 filter.
1134 """
1132 """
1135 wctx = repo[None]
1133 wctx = repo[None]
1136
1134
1137 # Two-levels map of "rev -> file ctx -> [line range]".
1135 # Two-levels map of "rev -> file ctx -> [line range]".
1138 linerangesbyrev = {}
1136 linerangesbyrev = {}
1139 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
1137 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
1140 if fname not in wctx:
1138 if fname not in wctx:
1141 raise error.StateError(
1139 raise error.StateError(
1142 _(b'cannot follow file not in parent revision: "%s"') % fname
1140 _(b'cannot follow file not in parent revision: "%s"') % fname
1143 )
1141 )
1144 fctx = wctx.filectx(fname)
1142 fctx = wctx.filectx(fname)
1145 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
1143 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
1146 rev = fctx.introrev()
1144 rev = fctx.introrev()
1147 if rev is None:
1145 if rev is None:
1148 rev = wdirrev
1146 rev = wdirrev
1149 if rev not in userrevs:
1147 if rev not in userrevs:
1150 continue
1148 continue
1151 linerangesbyrev.setdefault(rev, {}).setdefault(
1149 linerangesbyrev.setdefault(rev, {}).setdefault(
1152 fctx.path(), []
1150 fctx.path(), []
1153 ).append(linerange)
1151 ).append(linerange)
1154
1152
1155 def nofilterhunksfn(fctx, hunks):
1153 def nofilterhunksfn(fctx, hunks):
1156 return hunks
1154 return hunks
1157
1155
1158 def hunksfilter(ctx):
1156 def hunksfilter(ctx):
1159 fctxlineranges = linerangesbyrev.get(scmutil.intrev(ctx))
1157 fctxlineranges = linerangesbyrev.get(scmutil.intrev(ctx))
1160 if fctxlineranges is None:
1158 if fctxlineranges is None:
1161 return nofilterhunksfn
1159 return nofilterhunksfn
1162
1160
1163 def filterfn(fctx, hunks):
1161 def filterfn(fctx, hunks):
1164 lineranges = fctxlineranges.get(fctx.path())
1162 lineranges = fctxlineranges.get(fctx.path())
1165 if lineranges is not None:
1163 if lineranges is not None:
1166 for hr, lines in hunks:
1164 for hr, lines in hunks:
1167 if hr is None: # binary
1165 if hr is None: # binary
1168 yield hr, lines
1166 yield hr, lines
1169 continue
1167 continue
1170 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
1168 if any(mdiff.hunkinrange(hr[2:], lr) for lr in lineranges):
1171 yield hr, lines
1169 yield hr, lines
1172 else:
1170 else:
1173 for hunk in hunks:
1171 for hunk in hunks:
1174 yield hunk
1172 yield hunk
1175
1173
1176 return filterfn
1174 return filterfn
1177
1175
1178 def filematcher(ctx):
1176 def filematcher(ctx):
1179 files = list(linerangesbyrev.get(scmutil.intrev(ctx), []))
1177 files = list(linerangesbyrev.get(scmutil.intrev(ctx), []))
1180 return scmutil.matchfiles(repo, files)
1178 return scmutil.matchfiles(repo, files)
1181
1179
1182 revs = sorted(linerangesbyrev, reverse=True)
1180 revs = sorted(linerangesbyrev, reverse=True)
1183
1181
1184 differ = changesetdiffer()
1182 differ = changesetdiffer()
1185 differ._makefilematcher = filematcher
1183 differ._makefilematcher = filematcher
1186 differ._makehunksfilter = hunksfilter
1184 differ._makehunksfilter = hunksfilter
1187 return smartset.baseset(revs), differ
1185 return smartset.baseset(revs), differ
1188
1186
1189
1187
1190 def _graphnodeformatter(ui, displayer):
1188 def _graphnodeformatter(ui, displayer):
1191 spec = ui.config(b'command-templates', b'graphnode')
1189 spec = ui.config(b'command-templates', b'graphnode')
1192 if not spec:
1190 if not spec:
1193 return templatekw.getgraphnode # fast path for "{graphnode}"
1191 return templatekw.getgraphnode # fast path for "{graphnode}"
1194
1192
1195 spec = templater.unquotestring(spec)
1193 spec = templater.unquotestring(spec)
1196 if isinstance(displayer, changesettemplater):
1194 if isinstance(displayer, changesettemplater):
1197 # reuse cache of slow templates
1195 # reuse cache of slow templates
1198 tres = displayer._tresources
1196 tres = displayer._tresources
1199 else:
1197 else:
1200 tres = formatter.templateresources(ui)
1198 tres = formatter.templateresources(ui)
1201 templ = formatter.maketemplater(
1199 templ = formatter.maketemplater(
1202 ui, spec, defaults=templatekw.keywords, resources=tres
1200 ui, spec, defaults=templatekw.keywords, resources=tres
1203 )
1201 )
1204
1202
1205 def formatnode(repo, ctx, cache):
1203 def formatnode(repo, ctx, cache):
1206 props = {b'ctx': ctx, b'repo': repo}
1204 props = {b'ctx': ctx, b'repo': repo}
1207 return templ.renderdefault(props)
1205 return templ.renderdefault(props)
1208
1206
1209 return formatnode
1207 return formatnode
1210
1208
1211
1209
1212 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
1210 def displaygraph(ui, repo, dag, displayer, edgefn, getcopies=None, props=None):
1213 props = props or {}
1211 props = props or {}
1214 formatnode = _graphnodeformatter(ui, displayer)
1212 formatnode = _graphnodeformatter(ui, displayer)
1215 state = graphmod.asciistate()
1213 state = graphmod.asciistate()
1216 styles = state.styles
1214 styles = state.styles
1217
1215
1218 # only set graph styling if HGPLAIN is not set.
1216 # only set graph styling if HGPLAIN is not set.
1219 if ui.plain(b'graph'):
1217 if ui.plain(b'graph'):
1220 # set all edge styles to |, the default pre-3.8 behaviour
1218 # set all edge styles to |, the default pre-3.8 behaviour
1221 styles.update(dict.fromkeys(styles, b'|'))
1219 styles.update(dict.fromkeys(styles, b'|'))
1222 else:
1220 else:
1223 edgetypes = {
1221 edgetypes = {
1224 b'parent': graphmod.PARENT,
1222 b'parent': graphmod.PARENT,
1225 b'grandparent': graphmod.GRANDPARENT,
1223 b'grandparent': graphmod.GRANDPARENT,
1226 b'missing': graphmod.MISSINGPARENT,
1224 b'missing': graphmod.MISSINGPARENT,
1227 }
1225 }
1228 for name, key in edgetypes.items():
1226 for name, key in edgetypes.items():
1229 # experimental config: experimental.graphstyle.*
1227 # experimental config: experimental.graphstyle.*
1230 styles[key] = ui.config(
1228 styles[key] = ui.config(
1231 b'experimental', b'graphstyle.%s' % name, styles[key]
1229 b'experimental', b'graphstyle.%s' % name, styles[key]
1232 )
1230 )
1233 if not styles[key]:
1231 if not styles[key]:
1234 styles[key] = None
1232 styles[key] = None
1235
1233
1236 # experimental config: experimental.graphshorten
1234 # experimental config: experimental.graphshorten
1237 state.graphshorten = ui.configbool(b'experimental', b'graphshorten')
1235 state.graphshorten = ui.configbool(b'experimental', b'graphshorten')
1238
1236
1239 formatnode_cache = {}
1237 formatnode_cache = {}
1240 for rev, type, ctx, parents in dag:
1238 for rev, type, ctx, parents in dag:
1241 char = formatnode(repo, ctx, formatnode_cache)
1239 char = formatnode(repo, ctx, formatnode_cache)
1242 copies = getcopies(ctx) if getcopies else None
1240 copies = getcopies(ctx) if getcopies else None
1243 edges = edgefn(type, char, state, rev, parents)
1241 edges = edgefn(type, char, state, rev, parents)
1244 firstedge = next(edges)
1242 firstedge = next(edges)
1245 width = firstedge[2]
1243 width = firstedge[2]
1246 displayer.show(
1244 displayer.show(
1247 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1245 ctx, copies=copies, graphwidth=width, **pycompat.strkwargs(props)
1248 )
1246 )
1249 lines = displayer.hunk.pop(rev).split(b'\n')
1247 lines = displayer.hunk.pop(rev).split(b'\n')
1250 if not lines[-1]:
1248 if not lines[-1]:
1251 del lines[-1]
1249 del lines[-1]
1252 displayer.flush(ctx)
1250 displayer.flush(ctx)
1253 for type, char, width, coldata in itertools.chain([firstedge], edges):
1251 for type, char, width, coldata in itertools.chain([firstedge], edges):
1254 graphmod.ascii(ui, state, type, char, lines, coldata)
1252 graphmod.ascii(ui, state, type, char, lines, coldata)
1255 lines = []
1253 lines = []
1256 displayer.close()
1254 displayer.close()
1257
1255
1258
1256
1259 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1257 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
1260 revdag = graphmod.dagwalker(repo, revs)
1258 revdag = graphmod.dagwalker(repo, revs)
1261 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1259 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
1262
1260
1263
1261
1264 def displayrevs(ui, repo, revs, displayer, getcopies):
1262 def displayrevs(ui, repo, revs, displayer, getcopies):
1265 for rev in revs:
1263 for rev in revs:
1266 ctx = repo[rev]
1264 ctx = repo[rev]
1267 copies = getcopies(ctx) if getcopies else None
1265 copies = getcopies(ctx) if getcopies else None
1268 displayer.show(ctx, copies=copies)
1266 displayer.show(ctx, copies=copies)
1269 displayer.flush(ctx)
1267 displayer.flush(ctx)
1270 displayer.close()
1268 displayer.close()
1271
1269
1272
1270
1273 def checkunsupportedgraphflags(pats, opts):
1271 def checkunsupportedgraphflags(pats, opts):
1274 for op in [b"newest_first"]:
1272 for op in [b"newest_first"]:
1275 if op in opts and opts[op]:
1273 if op in opts and opts[op]:
1276 raise error.InputError(
1274 raise error.InputError(
1277 _(b"-G/--graph option is incompatible with --%s")
1275 _(b"-G/--graph option is incompatible with --%s")
1278 % op.replace(b"_", b"-")
1276 % op.replace(b"_", b"-")
1279 )
1277 )
1280
1278
1281
1279
1282 def graphrevs(repo, nodes, opts):
1280 def graphrevs(repo, nodes, opts):
1283 limit = getlimit(opts)
1281 limit = getlimit(opts)
1284 nodes.reverse()
1282 nodes.reverse()
1285 if limit is not None:
1283 if limit is not None:
1286 nodes = nodes[:limit]
1284 nodes = nodes[:limit]
1287 return graphmod.nodes(repo, nodes)
1285 return graphmod.nodes(repo, nodes)
@@ -1,37 +1,35 b''
1 hg log --debug shouldn't show different data than {file_*} template keywords
1 hg log --debug shouldn't show different data than {file_*} template keywords
2 https://bz.mercurial-scm.org/show_bug.cgi?id=6642
2 https://bz.mercurial-scm.org/show_bug.cgi?id=6642
3
3
4 $ hg init issue6642
4 $ hg init issue6642
5 $ cd issue6642
5 $ cd issue6642
6
6
7 $ echo a > a
7 $ echo a > a
8 $ hg ci -qAm a
8 $ hg ci -qAm a
9 $ echo b > b
9 $ echo b > b
10 $ hg ci -qAm b
10 $ hg ci -qAm b
11 $ hg up 0 -q
11 $ hg up 0 -q
12 $ echo c > c
12 $ echo c > c
13 $ hg ci -qAm c
13 $ hg ci -qAm c
14 $ hg merge -q
14 $ hg merge -q
15 $ hg ci -m merge
15 $ hg ci -m merge
16
16
17 $ hg log -GT '{rev} {desc} file_adds: [{file_adds}], file_mods: [{file_mods}], file_dels: [{file_dels}], files: [{files}]\n'
17 $ hg log -GT '{rev} {desc} file_adds: [{file_adds}], file_mods: [{file_mods}], file_dels: [{file_dels}], files: [{files}]\n'
18 @ 3 merge file_adds: [], file_mods: [], file_dels: [], files: []
18 @ 3 merge file_adds: [], file_mods: [], file_dels: [], files: []
19 |\
19 |\
20 | o 2 c file_adds: [c], file_mods: [], file_dels: [], files: [c]
20 | o 2 c file_adds: [c], file_mods: [], file_dels: [], files: [c]
21 | |
21 | |
22 o | 1 b file_adds: [b], file_mods: [], file_dels: [], files: [b]
22 o | 1 b file_adds: [b], file_mods: [], file_dels: [], files: [b]
23 |/
23 |/
24 o 0 a file_adds: [a], file_mods: [], file_dels: [], files: [a]
24 o 0 a file_adds: [a], file_mods: [], file_dels: [], files: [a]
25
25
26
26
27 $ hg log -r . --debug | grep files
27 $ hg log -r . --debug | grep files
28 files+: b (known-bad-output !)
28 [1]
29 [1] (missing-correct-output !)
30 $ hg log -r . --debug -T json | egrep '(added|removed|modified)'
29 $ hg log -r . --debug -T json | egrep '(added|removed|modified)'
31 "added": ["b"], (known-bad-output !)
30 "added": [],
32 "added": [], (missing-correct-output !)
33 "modified": [],
31 "modified": [],
34 "removed": [],
32 "removed": [],
35 $ hg log -r . --debug -T xml | grep path
33 $ hg log -r . --debug -T xml | grep path
36 <paths>
34 <paths>
37 </paths>
35 </paths>
@@ -1,1043 +1,1042 b''
1 $ cat > $TESTTMP/hook.sh << 'EOF'
1 $ cat > $TESTTMP/hook.sh << 'EOF'
2 > echo "test-hook-close-phase: $HG_NODE: $HG_OLDPHASE -> $HG_PHASE"
2 > echo "test-hook-close-phase: $HG_NODE: $HG_OLDPHASE -> $HG_PHASE"
3 > EOF
3 > EOF
4
4
5 $ cat >> $HGRCPATH << EOF
5 $ cat >> $HGRCPATH << EOF
6 > [extensions]
6 > [extensions]
7 > phasereport=$TESTDIR/testlib/ext-phase-report.py
7 > phasereport=$TESTDIR/testlib/ext-phase-report.py
8 > [hooks]
8 > [hooks]
9 > txnclose-phase.test = sh $TESTTMP/hook.sh
9 > txnclose-phase.test = sh $TESTTMP/hook.sh
10 > EOF
10 > EOF
11
11
12 $ hglog() { hg log --template "{rev} {phaseidx} {desc}\n" $*; }
12 $ hglog() { hg log --template "{rev} {phaseidx} {desc}\n" $*; }
13 $ mkcommit() {
13 $ mkcommit() {
14 > echo "$1" > "$1"
14 > echo "$1" > "$1"
15 > hg add "$1"
15 > hg add "$1"
16 > message="$1"
16 > message="$1"
17 > shift
17 > shift
18 > hg ci -m "$message" $*
18 > hg ci -m "$message" $*
19 > }
19 > }
20
20
21 $ hg init initialrepo
21 $ hg init initialrepo
22 $ cd initialrepo
22 $ cd initialrepo
23
23
24 Cannot change null revision phase
24 Cannot change null revision phase
25
25
26 $ hg phase --force --secret null
26 $ hg phase --force --secret null
27 abort: cannot change null revision phase
27 abort: cannot change null revision phase
28 [255]
28 [255]
29 $ hg phase null
29 $ hg phase null
30 -1: public
30 -1: public
31
31
32 $ mkcommit A
32 $ mkcommit A
33 test-debug-phase: new rev 0: x -> 1
33 test-debug-phase: new rev 0: x -> 1
34 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
34 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
35
35
36 New commit are draft by default
36 New commit are draft by default
37
37
38 $ hglog
38 $ hglog
39 0 1 A
39 0 1 A
40
40
41 Following commit are draft too
41 Following commit are draft too
42
42
43 $ mkcommit B
43 $ mkcommit B
44 test-debug-phase: new rev 1: x -> 1
44 test-debug-phase: new rev 1: x -> 1
45 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> draft
45 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> draft
46
46
47 $ hglog
47 $ hglog
48 1 1 B
48 1 1 B
49 0 1 A
49 0 1 A
50
50
51 Working directory phase is secret when its parent is secret.
51 Working directory phase is secret when its parent is secret.
52
52
53 $ hg phase --force --secret .
53 $ hg phase --force --secret .
54 test-debug-phase: move rev 0: 1 -> 2
54 test-debug-phase: move rev 0: 1 -> 2
55 test-debug-phase: move rev 1: 1 -> 2
55 test-debug-phase: move rev 1: 1 -> 2
56 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: draft -> secret
56 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: draft -> secret
57 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> secret
57 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> secret
58 $ hg log -r 'wdir()' -T '{phase}\n'
58 $ hg log -r 'wdir()' -T '{phase}\n'
59 secret
59 secret
60 $ hg log -r 'wdir() and public()' -T '{phase}\n'
60 $ hg log -r 'wdir() and public()' -T '{phase}\n'
61 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
61 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
62 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
62 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
63 secret
63 secret
64
64
65 Working directory phase is draft when its parent is draft.
65 Working directory phase is draft when its parent is draft.
66
66
67 $ hg phase --draft .
67 $ hg phase --draft .
68 test-debug-phase: move rev 1: 2 -> 1
68 test-debug-phase: move rev 1: 2 -> 1
69 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: secret -> draft
69 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: secret -> draft
70 $ hg log -r 'wdir()' -T '{phase}\n'
70 $ hg log -r 'wdir()' -T '{phase}\n'
71 draft
71 draft
72 $ hg log -r 'wdir() and public()' -T '{phase}\n'
72 $ hg log -r 'wdir() and public()' -T '{phase}\n'
73 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
73 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
74 draft
74 draft
75 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
75 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
76
76
77 Working directory phase is secret when a new commit will be created as secret,
77 Working directory phase is secret when a new commit will be created as secret,
78 even if the parent is draft.
78 even if the parent is draft.
79
79
80 $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
80 $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
81 > --config phases.new-commit='secret'
81 > --config phases.new-commit='secret'
82 secret
82 secret
83
83
84 Working directory phase is draft when its parent is public.
84 Working directory phase is draft when its parent is public.
85
85
86 $ hg phase --public .
86 $ hg phase --public .
87 test-debug-phase: move rev 0: 1 -> 0
87 test-debug-phase: move rev 0: 1 -> 0
88 test-debug-phase: move rev 1: 1 -> 0
88 test-debug-phase: move rev 1: 1 -> 0
89 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: draft -> public
89 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: draft -> public
90 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
90 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
91 $ hg log -r 'wdir()' -T '{phase}\n'
91 $ hg log -r 'wdir()' -T '{phase}\n'
92 draft
92 draft
93 $ hg log -r 'wdir() and public()' -T '{phase}\n'
93 $ hg log -r 'wdir() and public()' -T '{phase}\n'
94 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
94 $ hg log -r 'wdir() and draft()' -T '{phase}\n'
95 draft
95 draft
96 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
96 $ hg log -r 'wdir() and secret()' -T '{phase}\n'
97 $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
97 $ hg log -r 'wdir() and secret()' -T '{phase}\n' \
98 > --config phases.new-commit='secret'
98 > --config phases.new-commit='secret'
99 secret
99 secret
100
100
101 Draft commit are properly created over public one:
101 Draft commit are properly created over public one:
102
102
103 $ hg phase
103 $ hg phase
104 1: public
104 1: public
105 $ hglog
105 $ hglog
106 1 0 B
106 1 0 B
107 0 0 A
107 0 0 A
108
108
109 $ mkcommit C
109 $ mkcommit C
110 test-debug-phase: new rev 2: x -> 1
110 test-debug-phase: new rev 2: x -> 1
111 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
111 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
112 $ mkcommit D
112 $ mkcommit D
113 test-debug-phase: new rev 3: x -> 1
113 test-debug-phase: new rev 3: x -> 1
114 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
114 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
115
115
116 $ hglog
116 $ hglog
117 3 1 D
117 3 1 D
118 2 1 C
118 2 1 C
119 1 0 B
119 1 0 B
120 0 0 A
120 0 0 A
121
121
122 Test creating changeset as secret
122 Test creating changeset as secret
123
123
124 $ mkcommit E --config phases.new-commit='secret'
124 $ mkcommit E --config phases.new-commit='secret'
125 test-debug-phase: new rev 4: x -> 2
125 test-debug-phase: new rev 4: x -> 2
126 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> secret
126 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> secret
127 $ hglog
127 $ hglog
128 4 2 E
128 4 2 E
129 3 1 D
129 3 1 D
130 2 1 C
130 2 1 C
131 1 0 B
131 1 0 B
132 0 0 A
132 0 0 A
133
133
134 Test the secret property is inherited
134 Test the secret property is inherited
135
135
136 $ mkcommit H
136 $ mkcommit H
137 test-debug-phase: new rev 5: x -> 2
137 test-debug-phase: new rev 5: x -> 2
138 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: -> secret
138 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: -> secret
139 $ hglog
139 $ hglog
140 5 2 H
140 5 2 H
141 4 2 E
141 4 2 E
142 3 1 D
142 3 1 D
143 2 1 C
143 2 1 C
144 1 0 B
144 1 0 B
145 0 0 A
145 0 0 A
146
146
147 Even on merge
147 Even on merge
148
148
149 $ hg up -q 1
149 $ hg up -q 1
150 $ mkcommit "B'"
150 $ mkcommit "B'"
151 test-debug-phase: new rev 6: x -> 1
151 test-debug-phase: new rev 6: x -> 1
152 created new head
152 created new head
153 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
153 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
154 $ hglog
154 $ hglog
155 6 1 B'
155 6 1 B'
156 5 2 H
156 5 2 H
157 4 2 E
157 4 2 E
158 3 1 D
158 3 1 D
159 2 1 C
159 2 1 C
160 1 0 B
160 1 0 B
161 0 0 A
161 0 0 A
162 $ hg merge 4 # E
162 $ hg merge 4 # E
163 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 (branch merge, don't forget to commit)
164 (branch merge, don't forget to commit)
165 $ hg phase
165 $ hg phase
166 6: draft
166 6: draft
167 4: secret
167 4: secret
168 $ hg ci -m "merge B' and E"
168 $ hg ci -m "merge B' and E"
169 test-debug-phase: new rev 7: x -> 2
169 test-debug-phase: new rev 7: x -> 2
170 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> secret
170 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> secret
171
171
172 $ hglog
172 $ hglog
173 7 2 merge B' and E
173 7 2 merge B' and E
174 6 1 B'
174 6 1 B'
175 5 2 H
175 5 2 H
176 4 2 E
176 4 2 E
177 3 1 D
177 3 1 D
178 2 1 C
178 2 1 C
179 1 0 B
179 1 0 B
180 0 0 A
180 0 0 A
181
181
182 Test secret changeset are not pushed
182 Test secret changeset are not pushed
183
183
184 $ hg init ../push-dest
184 $ hg init ../push-dest
185 $ cat > ../push-dest/.hg/hgrc << EOF
185 $ cat > ../push-dest/.hg/hgrc << EOF
186 > [phases]
186 > [phases]
187 > publish=False
187 > publish=False
188 > EOF
188 > EOF
189 $ hg outgoing ../push-dest --template='{rev} {phase} {desc|firstline}\n'
189 $ hg outgoing ../push-dest --template='{rev} {phase} {desc|firstline}\n'
190 comparing with ../push-dest
190 comparing with ../push-dest
191 searching for changes
191 searching for changes
192 0 public A
192 0 public A
193 1 public B
193 1 public B
194 2 draft C
194 2 draft C
195 3 draft D
195 3 draft D
196 6 draft B'
196 6 draft B'
197 $ hg outgoing -r 'branch(default)' ../push-dest --template='{rev} {phase} {desc|firstline}\n'
197 $ hg outgoing -r 'branch(default)' ../push-dest --template='{rev} {phase} {desc|firstline}\n'
198 comparing with ../push-dest
198 comparing with ../push-dest
199 searching for changes
199 searching for changes
200 0 public A
200 0 public A
201 1 public B
201 1 public B
202 2 draft C
202 2 draft C
203 3 draft D
203 3 draft D
204 6 draft B'
204 6 draft B'
205
205
206 $ hg push ../push-dest -f # force because we push multiple heads
206 $ hg push ../push-dest -f # force because we push multiple heads
207 pushing to ../push-dest
207 pushing to ../push-dest
208 searching for changes
208 searching for changes
209 adding changesets
209 adding changesets
210 adding manifests
210 adding manifests
211 adding file changes
211 adding file changes
212 added 5 changesets with 5 changes to 5 files (+1 heads)
212 added 5 changesets with 5 changes to 5 files (+1 heads)
213 test-debug-phase: new rev 0: x -> 0
213 test-debug-phase: new rev 0: x -> 0
214 test-debug-phase: new rev 1: x -> 0
214 test-debug-phase: new rev 1: x -> 0
215 test-debug-phase: new rev 2: x -> 1
215 test-debug-phase: new rev 2: x -> 1
216 test-debug-phase: new rev 3: x -> 1
216 test-debug-phase: new rev 3: x -> 1
217 test-debug-phase: new rev 4: x -> 1
217 test-debug-phase: new rev 4: x -> 1
218 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
218 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
219 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
219 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
220 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
220 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> draft
221 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
221 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> draft
222 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
222 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> draft
223 $ hglog
223 $ hglog
224 7 2 merge B' and E
224 7 2 merge B' and E
225 6 1 B'
225 6 1 B'
226 5 2 H
226 5 2 H
227 4 2 E
227 4 2 E
228 3 1 D
228 3 1 D
229 2 1 C
229 2 1 C
230 1 0 B
230 1 0 B
231 0 0 A
231 0 0 A
232 $ cd ../push-dest
232 $ cd ../push-dest
233 $ hglog
233 $ hglog
234 4 1 B'
234 4 1 B'
235 3 1 D
235 3 1 D
236 2 1 C
236 2 1 C
237 1 0 B
237 1 0 B
238 0 0 A
238 0 0 A
239
239
240 (Issue3303)
240 (Issue3303)
241 Check that remote secret changeset are ignore when checking creation of remote heads
241 Check that remote secret changeset are ignore when checking creation of remote heads
242
242
243 We add a secret head into the push destination. This secret head shadows a
243 We add a secret head into the push destination. This secret head shadows a
244 visible shared between the initial repo and the push destination.
244 visible shared between the initial repo and the push destination.
245
245
246 $ hg up -q 4 # B'
246 $ hg up -q 4 # B'
247 $ mkcommit Z --config phases.new-commit=secret
247 $ mkcommit Z --config phases.new-commit=secret
248 test-debug-phase: new rev 5: x -> 2
248 test-debug-phase: new rev 5: x -> 2
249 test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a: -> secret
249 test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a: -> secret
250 $ hg phase .
250 $ hg phase .
251 5: secret
251 5: secret
252
252
253 We now try to push a new public changeset that descend from the common public
253 We now try to push a new public changeset that descend from the common public
254 head shadowed by the remote secret head.
254 head shadowed by the remote secret head.
255
255
256 $ cd ../initialrepo
256 $ cd ../initialrepo
257 $ hg up -q 6 #B'
257 $ hg up -q 6 #B'
258 $ mkcommit I
258 $ mkcommit I
259 test-debug-phase: new rev 8: x -> 1
259 test-debug-phase: new rev 8: x -> 1
260 created new head
260 created new head
261 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
261 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
262 $ hg push ../push-dest
262 $ hg push ../push-dest
263 pushing to ../push-dest
263 pushing to ../push-dest
264 searching for changes
264 searching for changes
265 adding changesets
265 adding changesets
266 adding manifests
266 adding manifests
267 adding file changes
267 adding file changes
268 added 1 changesets with 1 changes to 1 files (+1 heads)
268 added 1 changesets with 1 changes to 1 files (+1 heads)
269 test-debug-phase: new rev 6: x -> 1
269 test-debug-phase: new rev 6: x -> 1
270 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
270 test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061: -> draft
271
271
272 :note: The "(+1 heads)" is wrong as we do not had any visible head
272 :note: The "(+1 heads)" is wrong as we do not had any visible head
273
273
274 check that branch cache with "served" filter are properly computed and stored
274 check that branch cache with "served" filter are properly computed and stored
275
275
276 $ ls ../push-dest/.hg/cache/branch2*
276 $ ls ../push-dest/.hg/cache/branch2*
277 ../push-dest/.hg/cache/branch2-base
277 ../push-dest/.hg/cache/branch2-base
278 ../push-dest/.hg/cache/branch2-served
278 ../push-dest/.hg/cache/branch2-served
279 $ cat ../push-dest/.hg/cache/branch2-served
279 $ cat ../push-dest/.hg/cache/branch2-served
280 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
280 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
281 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
281 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
282 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
282 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
283 $ hg heads -R ../push-dest --template '{rev}:{node} {phase}\n' #update visible cache too
283 $ hg heads -R ../push-dest --template '{rev}:{node} {phase}\n' #update visible cache too
284 6:6d6770faffce199f1fddd1cf87f6f026138cf061 draft
284 6:6d6770faffce199f1fddd1cf87f6f026138cf061 draft
285 5:2713879da13d6eea1ff22b442a5a87cb31a7ce6a secret
285 5:2713879da13d6eea1ff22b442a5a87cb31a7ce6a secret
286 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e draft
286 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e draft
287 $ ls ../push-dest/.hg/cache/branch2*
287 $ ls ../push-dest/.hg/cache/branch2*
288 ../push-dest/.hg/cache/branch2-base
288 ../push-dest/.hg/cache/branch2-base
289 ../push-dest/.hg/cache/branch2-served
289 ../push-dest/.hg/cache/branch2-served
290 ../push-dest/.hg/cache/branch2-visible
290 ../push-dest/.hg/cache/branch2-visible
291 $ cat ../push-dest/.hg/cache/branch2-served
291 $ cat ../push-dest/.hg/cache/branch2-served
292 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
292 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
293 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
293 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
294 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
294 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
295 $ cat ../push-dest/.hg/cache/branch2-visible
295 $ cat ../push-dest/.hg/cache/branch2-visible
296 6d6770faffce199f1fddd1cf87f6f026138cf061 6
296 6d6770faffce199f1fddd1cf87f6f026138cf061 6
297 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
297 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
298 2713879da13d6eea1ff22b442a5a87cb31a7ce6a o default
298 2713879da13d6eea1ff22b442a5a87cb31a7ce6a o default
299 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
299 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
300
300
301
301
302 Restore condition prior extra insertion.
302 Restore condition prior extra insertion.
303 $ hg -q --config extensions.mq= strip .
303 $ hg -q --config extensions.mq= strip .
304 $ hg up -q 7
304 $ hg up -q 7
305 $ cd ..
305 $ cd ..
306
306
307 Test secret changeset are not pull
307 Test secret changeset are not pull
308
308
309 $ hg init pull-dest
309 $ hg init pull-dest
310 $ cd pull-dest
310 $ cd pull-dest
311 $ hg pull ../initialrepo
311 $ hg pull ../initialrepo
312 pulling from ../initialrepo
312 pulling from ../initialrepo
313 requesting all changes
313 requesting all changes
314 adding changesets
314 adding changesets
315 adding manifests
315 adding manifests
316 adding file changes
316 adding file changes
317 added 5 changesets with 5 changes to 5 files (+1 heads)
317 added 5 changesets with 5 changes to 5 files (+1 heads)
318 new changesets 4a2df7238c3b:cf9fe039dfd6
318 new changesets 4a2df7238c3b:cf9fe039dfd6
319 test-debug-phase: new rev 0: x -> 0
319 test-debug-phase: new rev 0: x -> 0
320 test-debug-phase: new rev 1: x -> 0
320 test-debug-phase: new rev 1: x -> 0
321 test-debug-phase: new rev 2: x -> 0
321 test-debug-phase: new rev 2: x -> 0
322 test-debug-phase: new rev 3: x -> 0
322 test-debug-phase: new rev 3: x -> 0
323 test-debug-phase: new rev 4: x -> 0
323 test-debug-phase: new rev 4: x -> 0
324 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
324 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
325 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
325 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
326 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
326 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
327 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
327 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
328 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
328 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
329 (run 'hg heads' to see heads, 'hg merge' to merge)
329 (run 'hg heads' to see heads, 'hg merge' to merge)
330 $ hglog
330 $ hglog
331 4 0 B'
331 4 0 B'
332 3 0 D
332 3 0 D
333 2 0 C
333 2 0 C
334 1 0 B
334 1 0 B
335 0 0 A
335 0 0 A
336 $ cd ..
336 $ cd ..
337
337
338 But secret can still be bundled explicitly
338 But secret can still be bundled explicitly
339
339
340 $ cd initialrepo
340 $ cd initialrepo
341 $ hg bundle --base '4^' -r 'children(4)' ../secret-bundle.hg
341 $ hg bundle --base '4^' -r 'children(4)' ../secret-bundle.hg
342 4 changesets found
342 4 changesets found
343 $ cd ..
343 $ cd ..
344
344
345 Test secret changeset are not cloned
345 Test secret changeset are not cloned
346 (during local clone)
346 (during local clone)
347
347
348 $ hg clone -qU initialrepo clone-dest
348 $ hg clone -qU initialrepo clone-dest
349 test-debug-phase: new rev 0: x -> 0
349 test-debug-phase: new rev 0: x -> 0
350 test-debug-phase: new rev 1: x -> 0
350 test-debug-phase: new rev 1: x -> 0
351 test-debug-phase: new rev 2: x -> 0
351 test-debug-phase: new rev 2: x -> 0
352 test-debug-phase: new rev 3: x -> 0
352 test-debug-phase: new rev 3: x -> 0
353 test-debug-phase: new rev 4: x -> 0
353 test-debug-phase: new rev 4: x -> 0
354 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
354 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
355 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
355 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
356 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
356 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
357 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
357 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
358 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
358 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
359 $ hglog -R clone-dest
359 $ hglog -R clone-dest
360 4 0 B'
360 4 0 B'
361 3 0 D
361 3 0 D
362 2 0 C
362 2 0 C
363 1 0 B
363 1 0 B
364 0 0 A
364 0 0 A
365
365
366 Test summary
366 Test summary
367
367
368 $ hg summary -R clone-dest --verbose
368 $ hg summary -R clone-dest --verbose
369 parent: -1:000000000000 (no revision checked out)
369 parent: -1:000000000000 (no revision checked out)
370 branch: default
370 branch: default
371 commit: (clean)
371 commit: (clean)
372 update: 5 new changesets (update)
372 update: 5 new changesets (update)
373 $ hg summary -R initialrepo
373 $ hg summary -R initialrepo
374 parent: 7:17a481b3bccb tip
374 parent: 7:17a481b3bccb tip
375 merge B' and E
375 merge B' and E
376 branch: default
376 branch: default
377 commit: (clean) (secret)
377 commit: (clean) (secret)
378 update: 1 new changesets, 2 branch heads (merge)
378 update: 1 new changesets, 2 branch heads (merge)
379 phases: 3 draft, 3 secret
379 phases: 3 draft, 3 secret
380 $ hg summary -R initialrepo --quiet
380 $ hg summary -R initialrepo --quiet
381 parent: 7:17a481b3bccb tip
381 parent: 7:17a481b3bccb tip
382 update: 1 new changesets, 2 branch heads (merge)
382 update: 1 new changesets, 2 branch heads (merge)
383
383
384 Test revset
384 Test revset
385
385
386 $ cd initialrepo
386 $ cd initialrepo
387 $ hglog -r 'public()'
387 $ hglog -r 'public()'
388 0 0 A
388 0 0 A
389 1 0 B
389 1 0 B
390 $ hglog -r 'draft()'
390 $ hglog -r 'draft()'
391 2 1 C
391 2 1 C
392 3 1 D
392 3 1 D
393 6 1 B'
393 6 1 B'
394 $ hglog -r 'secret()'
394 $ hglog -r 'secret()'
395 4 2 E
395 4 2 E
396 5 2 H
396 5 2 H
397 7 2 merge B' and E
397 7 2 merge B' and E
398
398
399 test that phase are displayed in log at debug level
399 test that phase are displayed in log at debug level
400
400
401 $ hg log --debug
401 $ hg log --debug
402 changeset: 7:17a481b3bccb796c0521ae97903d81c52bfee4af
402 changeset: 7:17a481b3bccb796c0521ae97903d81c52bfee4af
403 tag: tip
403 tag: tip
404 phase: secret
404 phase: secret
405 parent: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
405 parent: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
406 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
406 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
407 manifest: 7:5e724ffacba267b2ab726c91fc8b650710deaaa8
407 manifest: 7:5e724ffacba267b2ab726c91fc8b650710deaaa8
408 user: test
408 user: test
409 date: Thu Jan 01 00:00:00 1970 +0000
409 date: Thu Jan 01 00:00:00 1970 +0000
410 files+: C D E
411 extra: branch=default
410 extra: branch=default
412 description:
411 description:
413 merge B' and E
412 merge B' and E
414
413
415
414
416 changeset: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
415 changeset: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
417 phase: draft
416 phase: draft
418 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
417 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
419 parent: -1:0000000000000000000000000000000000000000
418 parent: -1:0000000000000000000000000000000000000000
420 manifest: 6:ab8bfef2392903058bf4ebb9e7746e8d7026b27a
419 manifest: 6:ab8bfef2392903058bf4ebb9e7746e8d7026b27a
421 user: test
420 user: test
422 date: Thu Jan 01 00:00:00 1970 +0000
421 date: Thu Jan 01 00:00:00 1970 +0000
423 files+: B'
422 files+: B'
424 extra: branch=default
423 extra: branch=default
425 description:
424 description:
426 B'
425 B'
427
426
428
427
429 changeset: 5:a030c6be5127abc010fcbff1851536552e6951a8
428 changeset: 5:a030c6be5127abc010fcbff1851536552e6951a8
430 phase: secret
429 phase: secret
431 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
430 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
432 parent: -1:0000000000000000000000000000000000000000
431 parent: -1:0000000000000000000000000000000000000000
433 manifest: 5:5c710aa854874fe3d5fa7192e77bdb314cc08b5a
432 manifest: 5:5c710aa854874fe3d5fa7192e77bdb314cc08b5a
434 user: test
433 user: test
435 date: Thu Jan 01 00:00:00 1970 +0000
434 date: Thu Jan 01 00:00:00 1970 +0000
436 files+: H
435 files+: H
437 extra: branch=default
436 extra: branch=default
438 description:
437 description:
439 H
438 H
440
439
441
440
442 changeset: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
441 changeset: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
443 phase: secret
442 phase: secret
444 parent: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
443 parent: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
445 parent: -1:0000000000000000000000000000000000000000
444 parent: -1:0000000000000000000000000000000000000000
446 manifest: 4:7173fd1c27119750b959e3a0f47ed78abe75d6dc
445 manifest: 4:7173fd1c27119750b959e3a0f47ed78abe75d6dc
447 user: test
446 user: test
448 date: Thu Jan 01 00:00:00 1970 +0000
447 date: Thu Jan 01 00:00:00 1970 +0000
449 files+: E
448 files+: E
450 extra: branch=default
449 extra: branch=default
451 description:
450 description:
452 E
451 E
453
452
454
453
455 changeset: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
454 changeset: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
456 phase: draft
455 phase: draft
457 parent: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
456 parent: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
458 parent: -1:0000000000000000000000000000000000000000
457 parent: -1:0000000000000000000000000000000000000000
459 manifest: 3:6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c
458 manifest: 3:6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c
460 user: test
459 user: test
461 date: Thu Jan 01 00:00:00 1970 +0000
460 date: Thu Jan 01 00:00:00 1970 +0000
462 files+: D
461 files+: D
463 extra: branch=default
462 extra: branch=default
464 description:
463 description:
465 D
464 D
466
465
467
466
468 changeset: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
467 changeset: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
469 phase: draft
468 phase: draft
470 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
469 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
471 parent: -1:0000000000000000000000000000000000000000
470 parent: -1:0000000000000000000000000000000000000000
472 manifest: 2:66a5a01817fdf5239c273802b5b7618d051c89e4
471 manifest: 2:66a5a01817fdf5239c273802b5b7618d051c89e4
473 user: test
472 user: test
474 date: Thu Jan 01 00:00:00 1970 +0000
473 date: Thu Jan 01 00:00:00 1970 +0000
475 files+: C
474 files+: C
476 extra: branch=default
475 extra: branch=default
477 description:
476 description:
478 C
477 C
479
478
480
479
481 changeset: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
480 changeset: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
482 phase: public
481 phase: public
483 parent: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
482 parent: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
484 parent: -1:0000000000000000000000000000000000000000
483 parent: -1:0000000000000000000000000000000000000000
485 manifest: 1:cb5cbbc1bfbf24cc34b9e8c16914e9caa2d2a7fd
484 manifest: 1:cb5cbbc1bfbf24cc34b9e8c16914e9caa2d2a7fd
486 user: test
485 user: test
487 date: Thu Jan 01 00:00:00 1970 +0000
486 date: Thu Jan 01 00:00:00 1970 +0000
488 files+: B
487 files+: B
489 extra: branch=default
488 extra: branch=default
490 description:
489 description:
491 B
490 B
492
491
493
492
494 changeset: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
493 changeset: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
495 phase: public
494 phase: public
496 parent: -1:0000000000000000000000000000000000000000
495 parent: -1:0000000000000000000000000000000000000000
497 parent: -1:0000000000000000000000000000000000000000
496 parent: -1:0000000000000000000000000000000000000000
498 manifest: 0:007d8c9d88841325f5c6b06371b35b4e8a2b1a83
497 manifest: 0:007d8c9d88841325f5c6b06371b35b4e8a2b1a83
499 user: test
498 user: test
500 date: Thu Jan 01 00:00:00 1970 +0000
499 date: Thu Jan 01 00:00:00 1970 +0000
501 files+: A
500 files+: A
502 extra: branch=default
501 extra: branch=default
503 description:
502 description:
504 A
503 A
505
504
506
505
507
506
508
507
509 (Issue3707)
508 (Issue3707)
510 test invalid phase name
509 test invalid phase name
511
510
512 $ mkcommit I --config phases.new-commit='babar'
511 $ mkcommit I --config phases.new-commit='babar'
513 transaction abort!
512 transaction abort!
514 rollback completed
513 rollback completed
515 config error: phases.new-commit: not a valid phase name ('babar')
514 config error: phases.new-commit: not a valid phase name ('babar')
516 [30]
515 [30]
517 Test phase command
516 Test phase command
518 ===================
517 ===================
519
518
520 initial picture
519 initial picture
521
520
522 $ hg log -G --template "{rev} {phase} {desc}\n"
521 $ hg log -G --template "{rev} {phase} {desc}\n"
523 @ 7 secret merge B' and E
522 @ 7 secret merge B' and E
524 |\
523 |\
525 | o 6 draft B'
524 | o 6 draft B'
526 | |
525 | |
527 +---o 5 secret H
526 +---o 5 secret H
528 | |
527 | |
529 o | 4 secret E
528 o | 4 secret E
530 | |
529 | |
531 o | 3 draft D
530 o | 3 draft D
532 | |
531 | |
533 o | 2 draft C
532 o | 2 draft C
534 |/
533 |/
535 o 1 public B
534 o 1 public B
536 |
535 |
537 o 0 public A
536 o 0 public A
538
537
539
538
540 display changesets phase
539 display changesets phase
541
540
542 (mixing -r and plain rev specification)
541 (mixing -r and plain rev specification)
543
542
544 $ hg phase 1::4 -r 7
543 $ hg phase 1::4 -r 7
545 1: public
544 1: public
546 2: draft
545 2: draft
547 3: draft
546 3: draft
548 4: secret
547 4: secret
549 7: secret
548 7: secret
550
549
551
550
552 move changeset forward
551 move changeset forward
553
552
554 (with -r option)
553 (with -r option)
555
554
556 $ hg phase --public -r 2
555 $ hg phase --public -r 2
557 test-debug-phase: move rev 2: 1 -> 0
556 test-debug-phase: move rev 2: 1 -> 0
558 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
557 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
559 $ hg log -G --template "{rev} {phase} {desc}\n"
558 $ hg log -G --template "{rev} {phase} {desc}\n"
560 @ 7 secret merge B' and E
559 @ 7 secret merge B' and E
561 |\
560 |\
562 | o 6 draft B'
561 | o 6 draft B'
563 | |
562 | |
564 +---o 5 secret H
563 +---o 5 secret H
565 | |
564 | |
566 o | 4 secret E
565 o | 4 secret E
567 | |
566 | |
568 o | 3 draft D
567 o | 3 draft D
569 | |
568 | |
570 o | 2 public C
569 o | 2 public C
571 |/
570 |/
572 o 1 public B
571 o 1 public B
573 |
572 |
574 o 0 public A
573 o 0 public A
575
574
576
575
577 move changeset backward
576 move changeset backward
578
577
579 (without -r option)
578 (without -r option)
580
579
581 $ hg phase --draft --force 2
580 $ hg phase --draft --force 2
582 test-debug-phase: move rev 2: 0 -> 1
581 test-debug-phase: move rev 2: 0 -> 1
583 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: public -> draft
582 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: public -> draft
584 $ hg log -G --template "{rev} {phase} {desc}\n"
583 $ hg log -G --template "{rev} {phase} {desc}\n"
585 @ 7 secret merge B' and E
584 @ 7 secret merge B' and E
586 |\
585 |\
587 | o 6 draft B'
586 | o 6 draft B'
588 | |
587 | |
589 +---o 5 secret H
588 +---o 5 secret H
590 | |
589 | |
591 o | 4 secret E
590 o | 4 secret E
592 | |
591 | |
593 o | 3 draft D
592 o | 3 draft D
594 | |
593 | |
595 o | 2 draft C
594 o | 2 draft C
596 |/
595 |/
597 o 1 public B
596 o 1 public B
598 |
597 |
599 o 0 public A
598 o 0 public A
600
599
601
600
602 move changeset forward and backward
601 move changeset forward and backward
603
602
604 $ hg phase --draft --force 1::4
603 $ hg phase --draft --force 1::4
605 test-debug-phase: move rev 1: 0 -> 1
604 test-debug-phase: move rev 1: 0 -> 1
606 test-debug-phase: move rev 4: 2 -> 1
605 test-debug-phase: move rev 4: 2 -> 1
607 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: public -> draft
606 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: public -> draft
608 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
607 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
609 $ hg log -G --template "{rev} {phase} {desc}\n"
608 $ hg log -G --template "{rev} {phase} {desc}\n"
610 @ 7 secret merge B' and E
609 @ 7 secret merge B' and E
611 |\
610 |\
612 | o 6 draft B'
611 | o 6 draft B'
613 | |
612 | |
614 +---o 5 secret H
613 +---o 5 secret H
615 | |
614 | |
616 o | 4 draft E
615 o | 4 draft E
617 | |
616 | |
618 o | 3 draft D
617 o | 3 draft D
619 | |
618 | |
620 o | 2 draft C
619 o | 2 draft C
621 |/
620 |/
622 o 1 draft B
621 o 1 draft B
623 |
622 |
624 o 0 public A
623 o 0 public A
625
624
626 test partial failure
625 test partial failure
627
626
628 $ hg phase --public 7
627 $ hg phase --public 7
629 test-debug-phase: move rev 1: 1 -> 0
628 test-debug-phase: move rev 1: 1 -> 0
630 test-debug-phase: move rev 2: 1 -> 0
629 test-debug-phase: move rev 2: 1 -> 0
631 test-debug-phase: move rev 3: 1 -> 0
630 test-debug-phase: move rev 3: 1 -> 0
632 test-debug-phase: move rev 4: 1 -> 0
631 test-debug-phase: move rev 4: 1 -> 0
633 test-debug-phase: move rev 6: 1 -> 0
632 test-debug-phase: move rev 6: 1 -> 0
634 test-debug-phase: move rev 7: 2 -> 0
633 test-debug-phase: move rev 7: 2 -> 0
635 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
634 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: draft -> public
636 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
635 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: draft -> public
637 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: draft -> public
636 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: draft -> public
638 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: draft -> public
637 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: draft -> public
639 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: draft -> public
638 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: draft -> public
640 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> public
639 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> public
641 $ hg phase --draft '5 or 7'
640 $ hg phase --draft '5 or 7'
642 test-debug-phase: move rev 5: 2 -> 1
641 test-debug-phase: move rev 5: 2 -> 1
643 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: secret -> draft
642 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: secret -> draft
644 cannot move 1 changesets to a higher phase, use --force
643 cannot move 1 changesets to a higher phase, use --force
645 phase changed for 1 changesets
644 phase changed for 1 changesets
646 [1]
645 [1]
647 $ hg log -G --template "{rev} {phase} {desc}\n"
646 $ hg log -G --template "{rev} {phase} {desc}\n"
648 @ 7 public merge B' and E
647 @ 7 public merge B' and E
649 |\
648 |\
650 | o 6 public B'
649 | o 6 public B'
651 | |
650 | |
652 +---o 5 draft H
651 +---o 5 draft H
653 | |
652 | |
654 o | 4 public E
653 o | 4 public E
655 | |
654 | |
656 o | 3 public D
655 o | 3 public D
657 | |
656 | |
658 o | 2 public C
657 o | 2 public C
659 |/
658 |/
660 o 1 public B
659 o 1 public B
661 |
660 |
662 o 0 public A
661 o 0 public A
663
662
664
663
665 test complete failure
664 test complete failure
666
665
667 $ hg phase --draft 7
666 $ hg phase --draft 7
668 cannot move 1 changesets to a higher phase, use --force
667 cannot move 1 changesets to a higher phase, use --force
669 no phases changed
668 no phases changed
670 [1]
669 [1]
671
670
672 $ cd ..
671 $ cd ..
673
672
674 test hidden changeset are not cloned as public (issue3935)
673 test hidden changeset are not cloned as public (issue3935)
675
674
676 $ cd initialrepo
675 $ cd initialrepo
677
676
678 (enabling evolution)
677 (enabling evolution)
679 $ cat >> $HGRCPATH << EOF
678 $ cat >> $HGRCPATH << EOF
680 > [experimental]
679 > [experimental]
681 > evolution.createmarkers=True
680 > evolution.createmarkers=True
682 > EOF
681 > EOF
683
682
684 (making a changeset hidden; H in that case)
683 (making a changeset hidden; H in that case)
685 $ hg debugobsolete `hg id --debug -r 5`
684 $ hg debugobsolete `hg id --debug -r 5`
686 1 new obsolescence markers
685 1 new obsolescence markers
687 obsoleted 1 changesets
686 obsoleted 1 changesets
688
687
689 $ cd ..
688 $ cd ..
690 $ hg clone initialrepo clonewithobs
689 $ hg clone initialrepo clonewithobs
691 requesting all changes
690 requesting all changes
692 adding changesets
691 adding changesets
693 adding manifests
692 adding manifests
694 adding file changes
693 adding file changes
695 added 7 changesets with 6 changes to 6 files
694 added 7 changesets with 6 changes to 6 files
696 new changesets 4a2df7238c3b:17a481b3bccb
695 new changesets 4a2df7238c3b:17a481b3bccb
697 test-debug-phase: new rev 0: x -> 0
696 test-debug-phase: new rev 0: x -> 0
698 test-debug-phase: new rev 1: x -> 0
697 test-debug-phase: new rev 1: x -> 0
699 test-debug-phase: new rev 2: x -> 0
698 test-debug-phase: new rev 2: x -> 0
700 test-debug-phase: new rev 3: x -> 0
699 test-debug-phase: new rev 3: x -> 0
701 test-debug-phase: new rev 4: x -> 0
700 test-debug-phase: new rev 4: x -> 0
702 test-debug-phase: new rev 5: x -> 0
701 test-debug-phase: new rev 5: x -> 0
703 test-debug-phase: new rev 6: x -> 0
702 test-debug-phase: new rev 6: x -> 0
704 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
703 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> public
705 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
704 test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56: -> public
706 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
705 test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757: -> public
707 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
706 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: -> public
708 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> public
707 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: -> public
709 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
708 test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519: -> public
710 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> public
709 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: -> public
711 updating to branch default
710 updating to branch default
712 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
711 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
713 $ cd clonewithobs
712 $ cd clonewithobs
714 $ hg log -G --template "{rev} {phase} {desc}\n"
713 $ hg log -G --template "{rev} {phase} {desc}\n"
715 @ 6 public merge B' and E
714 @ 6 public merge B' and E
716 |\
715 |\
717 | o 5 public B'
716 | o 5 public B'
718 | |
717 | |
719 o | 4 public E
718 o | 4 public E
720 | |
719 | |
721 o | 3 public D
720 o | 3 public D
722 | |
721 | |
723 o | 2 public C
722 o | 2 public C
724 |/
723 |/
725 o 1 public B
724 o 1 public B
726 |
725 |
727 o 0 public A
726 o 0 public A
728
727
729
728
730 test verify repo containing hidden changesets, which should not abort just
729 test verify repo containing hidden changesets, which should not abort just
731 because repo.cancopy() is False
730 because repo.cancopy() is False
732
731
733 $ cd ../initialrepo
732 $ cd ../initialrepo
734 $ hg verify
733 $ hg verify
735 checking changesets
734 checking changesets
736 checking manifests
735 checking manifests
737 crosschecking files in changesets and manifests
736 crosschecking files in changesets and manifests
738 checking files
737 checking files
739 checked 8 changesets with 7 changes to 7 files
738 checked 8 changesets with 7 changes to 7 files
740
739
741 $ cd ..
740 $ cd ..
742
741
743 check whether HG_PENDING makes pending changes only in related
742 check whether HG_PENDING makes pending changes only in related
744 repositories visible to an external hook.
743 repositories visible to an external hook.
745
744
746 (emulate a transaction running concurrently by copied
745 (emulate a transaction running concurrently by copied
747 .hg/phaseroots.pending in subsequent test)
746 .hg/phaseroots.pending in subsequent test)
748
747
749 $ cat > $TESTTMP/savepending.sh <<EOF
748 $ cat > $TESTTMP/savepending.sh <<EOF
750 > cp .hg/store/phaseroots.pending .hg/store/phaseroots.pending.saved
749 > cp .hg/store/phaseroots.pending .hg/store/phaseroots.pending.saved
751 > exit 1 # to avoid changing phase for subsequent tests
750 > exit 1 # to avoid changing phase for subsequent tests
752 > EOF
751 > EOF
753 $ cd push-dest
752 $ cd push-dest
754 $ hg phase 6
753 $ hg phase 6
755 6: draft
754 6: draft
756 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" phase -f -s 6
755 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" phase -f -s 6
757 transaction abort!
756 transaction abort!
758 rollback completed
757 rollback completed
759 abort: pretxnclose hook exited with status 1
758 abort: pretxnclose hook exited with status 1
760 [40]
759 [40]
761 $ cp .hg/store/phaseroots.pending.saved .hg/store/phaseroots.pending
760 $ cp .hg/store/phaseroots.pending.saved .hg/store/phaseroots.pending
762
761
763 (check (in)visibility of phaseroot while transaction running in repo)
762 (check (in)visibility of phaseroot while transaction running in repo)
764
763
765 $ cat > $TESTTMP/checkpending.sh <<EOF
764 $ cat > $TESTTMP/checkpending.sh <<EOF
766 > echo '@initialrepo'
765 > echo '@initialrepo'
767 > hg -R "$TESTTMP/initialrepo" phase 7
766 > hg -R "$TESTTMP/initialrepo" phase 7
768 > echo '@push-dest'
767 > echo '@push-dest'
769 > hg -R "$TESTTMP/push-dest" phase 6
768 > hg -R "$TESTTMP/push-dest" phase 6
770 > exit 1 # to avoid changing phase for subsequent tests
769 > exit 1 # to avoid changing phase for subsequent tests
771 > EOF
770 > EOF
772 $ cd ../initialrepo
771 $ cd ../initialrepo
773 $ hg phase 7
772 $ hg phase 7
774 7: public
773 7: public
775 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" phase -f -s 7
774 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" phase -f -s 7
776 @initialrepo
775 @initialrepo
777 7: secret
776 7: secret
778 @push-dest
777 @push-dest
779 6: draft
778 6: draft
780 transaction abort!
779 transaction abort!
781 rollback completed
780 rollback completed
782 abort: pretxnclose hook exited with status 1
781 abort: pretxnclose hook exited with status 1
783 [40]
782 [40]
784
783
785 Check that pretxnclose-phase hook can control phase movement
784 Check that pretxnclose-phase hook can control phase movement
786
785
787 $ hg phase --force b3325c91a4d9 --secret
786 $ hg phase --force b3325c91a4d9 --secret
788 test-debug-phase: move rev 3: 0 -> 2
787 test-debug-phase: move rev 3: 0 -> 2
789 test-debug-phase: move rev 4: 0 -> 2
788 test-debug-phase: move rev 4: 0 -> 2
790 test-debug-phase: move rev 5: 1 -> 2
789 test-debug-phase: move rev 5: 1 -> 2
791 test-debug-phase: move rev 7: 0 -> 2
790 test-debug-phase: move rev 7: 0 -> 2
792 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: public -> secret
791 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: public -> secret
793 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: public -> secret
792 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: public -> secret
794 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: draft -> secret
793 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: draft -> secret
795 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: public -> secret
794 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: public -> secret
796 $ hg log -G -T phases
795 $ hg log -G -T phases
797 @ changeset: 7:17a481b3bccb
796 @ changeset: 7:17a481b3bccb
798 |\ tag: tip
797 |\ tag: tip
799 | | phase: secret
798 | | phase: secret
800 | | parent: 6:cf9fe039dfd6
799 | | parent: 6:cf9fe039dfd6
801 | | parent: 4:a603bfb5a83e
800 | | parent: 4:a603bfb5a83e
802 | | user: test
801 | | user: test
803 | | date: Thu Jan 01 00:00:00 1970 +0000
802 | | date: Thu Jan 01 00:00:00 1970 +0000
804 | | summary: merge B' and E
803 | | summary: merge B' and E
805 | |
804 | |
806 | o changeset: 6:cf9fe039dfd6
805 | o changeset: 6:cf9fe039dfd6
807 | | phase: public
806 | | phase: public
808 | | parent: 1:27547f69f254
807 | | parent: 1:27547f69f254
809 | | user: test
808 | | user: test
810 | | date: Thu Jan 01 00:00:00 1970 +0000
809 | | date: Thu Jan 01 00:00:00 1970 +0000
811 | | summary: B'
810 | | summary: B'
812 | |
811 | |
813 o | changeset: 4:a603bfb5a83e
812 o | changeset: 4:a603bfb5a83e
814 | | phase: secret
813 | | phase: secret
815 | | user: test
814 | | user: test
816 | | date: Thu Jan 01 00:00:00 1970 +0000
815 | | date: Thu Jan 01 00:00:00 1970 +0000
817 | | summary: E
816 | | summary: E
818 | |
817 | |
819 o | changeset: 3:b3325c91a4d9
818 o | changeset: 3:b3325c91a4d9
820 | | phase: secret
819 | | phase: secret
821 | | user: test
820 | | user: test
822 | | date: Thu Jan 01 00:00:00 1970 +0000
821 | | date: Thu Jan 01 00:00:00 1970 +0000
823 | | summary: D
822 | | summary: D
824 | |
823 | |
825 o | changeset: 2:f838bfaca5c7
824 o | changeset: 2:f838bfaca5c7
826 |/ phase: public
825 |/ phase: public
827 | user: test
826 | user: test
828 | date: Thu Jan 01 00:00:00 1970 +0000
827 | date: Thu Jan 01 00:00:00 1970 +0000
829 | summary: C
828 | summary: C
830 |
829 |
831 o changeset: 1:27547f69f254
830 o changeset: 1:27547f69f254
832 | phase: public
831 | phase: public
833 | user: test
832 | user: test
834 | date: Thu Jan 01 00:00:00 1970 +0000
833 | date: Thu Jan 01 00:00:00 1970 +0000
835 | summary: B
834 | summary: B
836 |
835 |
837 o changeset: 0:4a2df7238c3b
836 o changeset: 0:4a2df7238c3b
838 phase: public
837 phase: public
839 user: test
838 user: test
840 date: Thu Jan 01 00:00:00 1970 +0000
839 date: Thu Jan 01 00:00:00 1970 +0000
841 summary: A
840 summary: A
842
841
843
842
844 Install a hook that prevent b3325c91a4d9 to become public
843 Install a hook that prevent b3325c91a4d9 to become public
845
844
846 $ cat >> .hg/hgrc << EOF
845 $ cat >> .hg/hgrc << EOF
847 > [hooks]
846 > [hooks]
848 > pretxnclose-phase.nopublish_D = sh -c "(echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 'public' != \$HG_PHASE ]"
847 > pretxnclose-phase.nopublish_D = sh -c "(echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 'public' != \$HG_PHASE ]"
849 > EOF
848 > EOF
850
849
851 Try various actions. only the draft move should succeed
850 Try various actions. only the draft move should succeed
852
851
853 $ hg phase --public b3325c91a4d9
852 $ hg phase --public b3325c91a4d9
854 transaction abort!
853 transaction abort!
855 rollback completed
854 rollback completed
856 abort: pretxnclose-phase.nopublish_D hook exited with status 1
855 abort: pretxnclose-phase.nopublish_D hook exited with status 1
857 [40]
856 [40]
858 $ hg phase --public a603bfb5a83e
857 $ hg phase --public a603bfb5a83e
859 transaction abort!
858 transaction abort!
860 rollback completed
859 rollback completed
861 abort: pretxnclose-phase.nopublish_D hook exited with status 1
860 abort: pretxnclose-phase.nopublish_D hook exited with status 1
862 [40]
861 [40]
863 $ hg phase --draft 17a481b3bccb
862 $ hg phase --draft 17a481b3bccb
864 test-debug-phase: move rev 3: 2 -> 1
863 test-debug-phase: move rev 3: 2 -> 1
865 test-debug-phase: move rev 4: 2 -> 1
864 test-debug-phase: move rev 4: 2 -> 1
866 test-debug-phase: move rev 7: 2 -> 1
865 test-debug-phase: move rev 7: 2 -> 1
867 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: secret -> draft
866 test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e: secret -> draft
868 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
867 test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde: secret -> draft
869 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> draft
868 test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af: secret -> draft
870 $ hg phase --public 17a481b3bccb
869 $ hg phase --public 17a481b3bccb
871 transaction abort!
870 transaction abort!
872 rollback completed
871 rollback completed
873 abort: pretxnclose-phase.nopublish_D hook exited with status 1
872 abort: pretxnclose-phase.nopublish_D hook exited with status 1
874 [40]
873 [40]
875
874
876 $ cd ..
875 $ cd ..
877
876
878 Test for the "internal" phase
877 Test for the "internal" phase
879 =============================
878 =============================
880
879
881 Check we deny its usage on older repository
880 Check we deny its usage on older repository
882
881
883 $ hg init no-internal-phase --config format.internal-phase=no
882 $ hg init no-internal-phase --config format.internal-phase=no
884 $ cd no-internal-phase
883 $ cd no-internal-phase
885 $ hg debugrequires | grep internal-phase
884 $ hg debugrequires | grep internal-phase
886 [1]
885 [1]
887 $ echo X > X
886 $ echo X > X
888 $ hg add X
887 $ hg add X
889 $ hg status
888 $ hg status
890 A X
889 A X
891 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit" 2>&1 | grep ProgrammingError
890 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit" 2>&1 | grep ProgrammingError
892 ** ProgrammingError: this repository does not support the internal phase
891 ** ProgrammingError: this repository does not support the internal phase
893 raise error.ProgrammingError(msg) (no-pyoxidizer !)
892 raise error.ProgrammingError(msg) (no-pyoxidizer !)
894 *ProgrammingError: this repository does not support the internal phase (glob)
893 *ProgrammingError: this repository does not support the internal phase (glob)
895 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit" 2>&1 | grep ProgrammingError
894 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit" 2>&1 | grep ProgrammingError
896 ** ProgrammingError: this repository does not support the archived phase
895 ** ProgrammingError: this repository does not support the archived phase
897 raise error.ProgrammingError(msg) (no-pyoxidizer !)
896 raise error.ProgrammingError(msg) (no-pyoxidizer !)
898 *ProgrammingError: this repository does not support the archived phase (glob)
897 *ProgrammingError: this repository does not support the archived phase (glob)
899
898
900 $ cd ..
899 $ cd ..
901
900
902 Check it works fine with repository that supports it.
901 Check it works fine with repository that supports it.
903
902
904 $ hg init internal-phase --config format.internal-phase=yes
903 $ hg init internal-phase --config format.internal-phase=yes
905 $ cd internal-phase
904 $ cd internal-phase
906 $ hg debugrequires | grep internal-phase
905 $ hg debugrequires | grep internal-phase
907 internal-phase
906 internal-phase
908 $ mkcommit A
907 $ mkcommit A
909 test-debug-phase: new rev 0: x -> 1
908 test-debug-phase: new rev 0: x -> 1
910 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
909 test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256: -> draft
911
910
912 Commit an internal changesets
911 Commit an internal changesets
913
912
914 $ echo B > B
913 $ echo B > B
915 $ hg add B
914 $ hg add B
916 $ hg status
915 $ hg status
917 A B
916 A B
918 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit"
917 $ hg --config "phases.new-commit=internal" commit -m "my test internal commit"
919 test-debug-phase: new rev 1: x -> 96
918 test-debug-phase: new rev 1: x -> 96
920 test-hook-close-phase: c01c42dffc7f81223397e99652a0703f83e1c5ea: -> internal
919 test-hook-close-phase: c01c42dffc7f81223397e99652a0703f83e1c5ea: -> internal
921
920
922 The changeset is a working parent descendant.
921 The changeset is a working parent descendant.
923 Per the usual visibility rules, it is made visible.
922 Per the usual visibility rules, it is made visible.
924
923
925 $ hg log -G -l 3
924 $ hg log -G -l 3
926 @ changeset: 1:c01c42dffc7f
925 @ changeset: 1:c01c42dffc7f
927 | tag: tip
926 | tag: tip
928 | user: test
927 | user: test
929 | date: Thu Jan 01 00:00:00 1970 +0000
928 | date: Thu Jan 01 00:00:00 1970 +0000
930 | summary: my test internal commit
929 | summary: my test internal commit
931 |
930 |
932 o changeset: 0:4a2df7238c3b
931 o changeset: 0:4a2df7238c3b
933 user: test
932 user: test
934 date: Thu Jan 01 00:00:00 1970 +0000
933 date: Thu Jan 01 00:00:00 1970 +0000
935 summary: A
934 summary: A
936
935
937
936
938 Commit is hidden as expected
937 Commit is hidden as expected
939
938
940 $ hg up 0
939 $ hg up 0
941 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
940 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
942 $ hg log -G
941 $ hg log -G
943 @ changeset: 0:4a2df7238c3b
942 @ changeset: 0:4a2df7238c3b
944 tag: tip
943 tag: tip
945 user: test
944 user: test
946 date: Thu Jan 01 00:00:00 1970 +0000
945 date: Thu Jan 01 00:00:00 1970 +0000
947 summary: A
946 summary: A
948
947
949
948
950 Test for archived phase
949 Test for archived phase
951 -----------------------
950 -----------------------
952
951
953 Commit an archived changesets
952 Commit an archived changesets
954
953
955 $ echo B > B
954 $ echo B > B
956 $ hg add B
955 $ hg add B
957 $ hg status
956 $ hg status
958 A B
957 A B
959 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit"
958 $ hg --config "phases.new-commit=archived" commit -m "my test archived commit"
960 test-debug-phase: new rev 2: x -> 32
959 test-debug-phase: new rev 2: x -> 32
961 test-hook-close-phase: 8df5997c3361518f733d1ae67cd3adb9b0eaf125: -> archived
960 test-hook-close-phase: 8df5997c3361518f733d1ae67cd3adb9b0eaf125: -> archived
962
961
963 The changeset is a working parent descendant.
962 The changeset is a working parent descendant.
964 Per the usual visibility rules, it is made visible.
963 Per the usual visibility rules, it is made visible.
965
964
966 $ hg log -G -l 3
965 $ hg log -G -l 3
967 @ changeset: 2:8df5997c3361
966 @ changeset: 2:8df5997c3361
968 | tag: tip
967 | tag: tip
969 | parent: 0:4a2df7238c3b
968 | parent: 0:4a2df7238c3b
970 | user: test
969 | user: test
971 | date: Thu Jan 01 00:00:00 1970 +0000
970 | date: Thu Jan 01 00:00:00 1970 +0000
972 | summary: my test archived commit
971 | summary: my test archived commit
973 |
972 |
974 o changeset: 0:4a2df7238c3b
973 o changeset: 0:4a2df7238c3b
975 user: test
974 user: test
976 date: Thu Jan 01 00:00:00 1970 +0000
975 date: Thu Jan 01 00:00:00 1970 +0000
977 summary: A
976 summary: A
978
977
979
978
980 Commit is hidden as expected
979 Commit is hidden as expected
981
980
982 $ hg up 0
981 $ hg up 0
983 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
982 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
984 $ hg log -G
983 $ hg log -G
985 @ changeset: 0:4a2df7238c3b
984 @ changeset: 0:4a2df7238c3b
986 tag: tip
985 tag: tip
987 user: test
986 user: test
988 date: Thu Jan 01 00:00:00 1970 +0000
987 date: Thu Jan 01 00:00:00 1970 +0000
989 summary: A
988 summary: A
990
989
991 $ cd ..
990 $ cd ..
992
991
993 Recommitting an exact match of a public commit shouldn't change it to
992 Recommitting an exact match of a public commit shouldn't change it to
994 draft:
993 draft:
995
994
996 $ cd initialrepo
995 $ cd initialrepo
997 $ hg phase -r 2
996 $ hg phase -r 2
998 2: public
997 2: public
999 $ hg up -C 1
998 $ hg up -C 1
1000 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
999 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
1001 $ mkcommit C
1000 $ mkcommit C
1002 warning: commit already existed in the repository!
1001 warning: commit already existed in the repository!
1003 $ hg phase -r 2
1002 $ hg phase -r 2
1004 2: public
1003 2: public
1005
1004
1006 Same, but for secret:
1005 Same, but for secret:
1007
1006
1008 $ hg up 7
1007 $ hg up 7
1009 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1008 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1010 $ mkcommit F -s
1009 $ mkcommit F -s
1011 test-debug-phase: new rev 8: x -> 2
1010 test-debug-phase: new rev 8: x -> 2
1012 test-hook-close-phase: de414268ec5ce2330c590b942fbb5ff0b0ca1a0a: -> secret
1011 test-hook-close-phase: de414268ec5ce2330c590b942fbb5ff0b0ca1a0a: -> secret
1013 $ hg up 7
1012 $ hg up 7
1014 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1013 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1015 $ hg phase
1014 $ hg phase
1016 7: draft
1015 7: draft
1017 $ mkcommit F
1016 $ mkcommit F
1018 test-debug-phase: new rev 8: x -> 2
1017 test-debug-phase: new rev 8: x -> 2
1019 warning: commit already existed in the repository!
1018 warning: commit already existed in the repository!
1020 test-hook-close-phase: de414268ec5ce2330c590b942fbb5ff0b0ca1a0a: -> secret
1019 test-hook-close-phase: de414268ec5ce2330c590b942fbb5ff0b0ca1a0a: -> secret
1021 $ hg phase -r tip
1020 $ hg phase -r tip
1022 8: secret
1021 8: secret
1023
1022
1024 But what about obsoleted changesets?
1023 But what about obsoleted changesets?
1025
1024
1026 $ hg up 4
1025 $ hg up 4
1027 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
1026 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
1028 $ mkcommit H
1027 $ mkcommit H
1029 test-debug-phase: new rev 5: x -> 2
1028 test-debug-phase: new rev 5: x -> 2
1030 warning: commit already existed in the repository!
1029 warning: commit already existed in the repository!
1031 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: -> secret
1030 test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8: -> secret
1032 $ hg phase -r 5
1031 $ hg phase -r 5
1033 5: secret
1032 5: secret
1034 $ hg par
1033 $ hg par
1035 changeset: 5:a030c6be5127
1034 changeset: 5:a030c6be5127
1036 user: test
1035 user: test
1037 date: Thu Jan 01 00:00:00 1970 +0000
1036 date: Thu Jan 01 00:00:00 1970 +0000
1038 obsolete: pruned
1037 obsolete: pruned
1039 summary: H
1038 summary: H
1040
1039
1041 $ hg up tip
1040 $ hg up tip
1042 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1041 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1043 $ cd ..
1042 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now