##// END OF EJS Templates
logcmdutil: drop redundant "log" from function names (API)...
Yuya Nishihara -
r35905:572f36e9 default
parent child Browse files
Show More
@@ -1,1495 +1,1493 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import copy
12 import copy
13 import os
13 import os
14
14
15 from mercurial.i18n import _
15 from mercurial.i18n import _
16
16
17 from mercurial import (
17 from mercurial import (
18 archival,
18 archival,
19 cmdutil,
19 cmdutil,
20 error,
20 error,
21 hg,
21 hg,
22 logcmdutil,
22 logcmdutil,
23 match as matchmod,
23 match as matchmod,
24 pathutil,
24 pathutil,
25 pycompat,
25 pycompat,
26 registrar,
26 registrar,
27 scmutil,
27 scmutil,
28 smartset,
28 smartset,
29 util,
29 util,
30 )
30 )
31
31
32 from . import (
32 from . import (
33 lfcommands,
33 lfcommands,
34 lfutil,
34 lfutil,
35 storefactory,
35 storefactory,
36 )
36 )
37
37
38 # -- Utility functions: commonly/repeatedly needed functionality ---------------
38 # -- Utility functions: commonly/repeatedly needed functionality ---------------
39
39
40 def composelargefilematcher(match, manifest):
40 def composelargefilematcher(match, manifest):
41 '''create a matcher that matches only the largefiles in the original
41 '''create a matcher that matches only the largefiles in the original
42 matcher'''
42 matcher'''
43 m = copy.copy(match)
43 m = copy.copy(match)
44 lfile = lambda f: lfutil.standin(f) in manifest
44 lfile = lambda f: lfutil.standin(f) in manifest
45 m._files = filter(lfile, m._files)
45 m._files = filter(lfile, m._files)
46 m._fileset = set(m._files)
46 m._fileset = set(m._files)
47 m.always = lambda: False
47 m.always = lambda: False
48 origmatchfn = m.matchfn
48 origmatchfn = m.matchfn
49 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
49 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
50 return m
50 return m
51
51
52 def composenormalfilematcher(match, manifest, exclude=None):
52 def composenormalfilematcher(match, manifest, exclude=None):
53 excluded = set()
53 excluded = set()
54 if exclude is not None:
54 if exclude is not None:
55 excluded.update(exclude)
55 excluded.update(exclude)
56
56
57 m = copy.copy(match)
57 m = copy.copy(match)
58 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
58 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
59 manifest or f in excluded)
59 manifest or f in excluded)
60 m._files = filter(notlfile, m._files)
60 m._files = filter(notlfile, m._files)
61 m._fileset = set(m._files)
61 m._fileset = set(m._files)
62 m.always = lambda: False
62 m.always = lambda: False
63 origmatchfn = m.matchfn
63 origmatchfn = m.matchfn
64 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
64 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
65 return m
65 return m
66
66
67 def installnormalfilesmatchfn(manifest):
67 def installnormalfilesmatchfn(manifest):
68 '''installmatchfn with a matchfn that ignores all largefiles'''
68 '''installmatchfn with a matchfn that ignores all largefiles'''
69 def overridematch(ctx, pats=(), opts=None, globbed=False,
69 def overridematch(ctx, pats=(), opts=None, globbed=False,
70 default='relpath', badfn=None):
70 default='relpath', badfn=None):
71 if opts is None:
71 if opts is None:
72 opts = {}
72 opts = {}
73 match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn)
73 match = oldmatch(ctx, pats, opts, globbed, default, badfn=badfn)
74 return composenormalfilematcher(match, manifest)
74 return composenormalfilematcher(match, manifest)
75 oldmatch = installmatchfn(overridematch)
75 oldmatch = installmatchfn(overridematch)
76
76
77 def installmatchfn(f):
77 def installmatchfn(f):
78 '''monkey patch the scmutil module with a custom match function.
78 '''monkey patch the scmutil module with a custom match function.
79 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
79 Warning: it is monkey patching the _module_ on runtime! Not thread safe!'''
80 oldmatch = scmutil.match
80 oldmatch = scmutil.match
81 setattr(f, 'oldmatch', oldmatch)
81 setattr(f, 'oldmatch', oldmatch)
82 scmutil.match = f
82 scmutil.match = f
83 return oldmatch
83 return oldmatch
84
84
85 def restorematchfn():
85 def restorematchfn():
86 '''restores scmutil.match to what it was before installmatchfn
86 '''restores scmutil.match to what it was before installmatchfn
87 was called. no-op if scmutil.match is its original function.
87 was called. no-op if scmutil.match is its original function.
88
88
89 Note that n calls to installmatchfn will require n calls to
89 Note that n calls to installmatchfn will require n calls to
90 restore the original matchfn.'''
90 restore the original matchfn.'''
91 scmutil.match = getattr(scmutil.match, 'oldmatch')
91 scmutil.match = getattr(scmutil.match, 'oldmatch')
92
92
93 def installmatchandpatsfn(f):
93 def installmatchandpatsfn(f):
94 oldmatchandpats = scmutil.matchandpats
94 oldmatchandpats = scmutil.matchandpats
95 setattr(f, 'oldmatchandpats', oldmatchandpats)
95 setattr(f, 'oldmatchandpats', oldmatchandpats)
96 scmutil.matchandpats = f
96 scmutil.matchandpats = f
97 return oldmatchandpats
97 return oldmatchandpats
98
98
99 def restorematchandpatsfn():
99 def restorematchandpatsfn():
100 '''restores scmutil.matchandpats to what it was before
100 '''restores scmutil.matchandpats to what it was before
101 installmatchandpatsfn was called. No-op if scmutil.matchandpats
101 installmatchandpatsfn was called. No-op if scmutil.matchandpats
102 is its original function.
102 is its original function.
103
103
104 Note that n calls to installmatchandpatsfn will require n calls
104 Note that n calls to installmatchandpatsfn will require n calls
105 to restore the original matchfn.'''
105 to restore the original matchfn.'''
106 scmutil.matchandpats = getattr(scmutil.matchandpats, 'oldmatchandpats',
106 scmutil.matchandpats = getattr(scmutil.matchandpats, 'oldmatchandpats',
107 scmutil.matchandpats)
107 scmutil.matchandpats)
108
108
109 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
109 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
110 large = opts.get(r'large')
110 large = opts.get(r'large')
111 lfsize = lfutil.getminsize(
111 lfsize = lfutil.getminsize(
112 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
112 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
113
113
114 lfmatcher = None
114 lfmatcher = None
115 if lfutil.islfilesrepo(repo):
115 if lfutil.islfilesrepo(repo):
116 lfpats = ui.configlist(lfutil.longname, 'patterns')
116 lfpats = ui.configlist(lfutil.longname, 'patterns')
117 if lfpats:
117 if lfpats:
118 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
118 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
119
119
120 lfnames = []
120 lfnames = []
121 m = matcher
121 m = matcher
122
122
123 wctx = repo[None]
123 wctx = repo[None]
124 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
124 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
125 exact = m.exact(f)
125 exact = m.exact(f)
126 lfile = lfutil.standin(f) in wctx
126 lfile = lfutil.standin(f) in wctx
127 nfile = f in wctx
127 nfile = f in wctx
128 exists = lfile or nfile
128 exists = lfile or nfile
129
129
130 # addremove in core gets fancy with the name, add doesn't
130 # addremove in core gets fancy with the name, add doesn't
131 if isaddremove:
131 if isaddremove:
132 name = m.uipath(f)
132 name = m.uipath(f)
133 else:
133 else:
134 name = m.rel(f)
134 name = m.rel(f)
135
135
136 # Don't warn the user when they attempt to add a normal tracked file.
136 # Don't warn the user when they attempt to add a normal tracked file.
137 # The normal add code will do that for us.
137 # The normal add code will do that for us.
138 if exact and exists:
138 if exact and exists:
139 if lfile:
139 if lfile:
140 ui.warn(_('%s already a largefile\n') % name)
140 ui.warn(_('%s already a largefile\n') % name)
141 continue
141 continue
142
142
143 if (exact or not exists) and not lfutil.isstandin(f):
143 if (exact or not exists) and not lfutil.isstandin(f):
144 # In case the file was removed previously, but not committed
144 # In case the file was removed previously, but not committed
145 # (issue3507)
145 # (issue3507)
146 if not repo.wvfs.exists(f):
146 if not repo.wvfs.exists(f):
147 continue
147 continue
148
148
149 abovemin = (lfsize and
149 abovemin = (lfsize and
150 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
150 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
151 if large or abovemin or (lfmatcher and lfmatcher(f)):
151 if large or abovemin or (lfmatcher and lfmatcher(f)):
152 lfnames.append(f)
152 lfnames.append(f)
153 if ui.verbose or not exact:
153 if ui.verbose or not exact:
154 ui.status(_('adding %s as a largefile\n') % name)
154 ui.status(_('adding %s as a largefile\n') % name)
155
155
156 bad = []
156 bad = []
157
157
158 # Need to lock, otherwise there could be a race condition between
158 # Need to lock, otherwise there could be a race condition between
159 # when standins are created and added to the repo.
159 # when standins are created and added to the repo.
160 with repo.wlock():
160 with repo.wlock():
161 if not opts.get(r'dry_run'):
161 if not opts.get(r'dry_run'):
162 standins = []
162 standins = []
163 lfdirstate = lfutil.openlfdirstate(ui, repo)
163 lfdirstate = lfutil.openlfdirstate(ui, repo)
164 for f in lfnames:
164 for f in lfnames:
165 standinname = lfutil.standin(f)
165 standinname = lfutil.standin(f)
166 lfutil.writestandin(repo, standinname, hash='',
166 lfutil.writestandin(repo, standinname, hash='',
167 executable=lfutil.getexecutable(repo.wjoin(f)))
167 executable=lfutil.getexecutable(repo.wjoin(f)))
168 standins.append(standinname)
168 standins.append(standinname)
169 if lfdirstate[f] == 'r':
169 if lfdirstate[f] == 'r':
170 lfdirstate.normallookup(f)
170 lfdirstate.normallookup(f)
171 else:
171 else:
172 lfdirstate.add(f)
172 lfdirstate.add(f)
173 lfdirstate.write()
173 lfdirstate.write()
174 bad += [lfutil.splitstandin(f)
174 bad += [lfutil.splitstandin(f)
175 for f in repo[None].add(standins)
175 for f in repo[None].add(standins)
176 if f in m.files()]
176 if f in m.files()]
177
177
178 added = [f for f in lfnames if f not in bad]
178 added = [f for f in lfnames if f not in bad]
179 return added, bad
179 return added, bad
180
180
181 def removelargefiles(ui, repo, isaddremove, matcher, **opts):
181 def removelargefiles(ui, repo, isaddremove, matcher, **opts):
182 after = opts.get(r'after')
182 after = opts.get(r'after')
183 m = composelargefilematcher(matcher, repo[None].manifest())
183 m = composelargefilematcher(matcher, repo[None].manifest())
184 try:
184 try:
185 repo.lfstatus = True
185 repo.lfstatus = True
186 s = repo.status(match=m, clean=not isaddremove)
186 s = repo.status(match=m, clean=not isaddremove)
187 finally:
187 finally:
188 repo.lfstatus = False
188 repo.lfstatus = False
189 manifest = repo[None].manifest()
189 manifest = repo[None].manifest()
190 modified, added, deleted, clean = [[f for f in list
190 modified, added, deleted, clean = [[f for f in list
191 if lfutil.standin(f) in manifest]
191 if lfutil.standin(f) in manifest]
192 for list in (s.modified, s.added,
192 for list in (s.modified, s.added,
193 s.deleted, s.clean)]
193 s.deleted, s.clean)]
194
194
195 def warn(files, msg):
195 def warn(files, msg):
196 for f in files:
196 for f in files:
197 ui.warn(msg % m.rel(f))
197 ui.warn(msg % m.rel(f))
198 return int(len(files) > 0)
198 return int(len(files) > 0)
199
199
200 result = 0
200 result = 0
201
201
202 if after:
202 if after:
203 remove = deleted
203 remove = deleted
204 result = warn(modified + added + clean,
204 result = warn(modified + added + clean,
205 _('not removing %s: file still exists\n'))
205 _('not removing %s: file still exists\n'))
206 else:
206 else:
207 remove = deleted + clean
207 remove = deleted + clean
208 result = warn(modified, _('not removing %s: file is modified (use -f'
208 result = warn(modified, _('not removing %s: file is modified (use -f'
209 ' to force removal)\n'))
209 ' to force removal)\n'))
210 result = warn(added, _('not removing %s: file has been marked for add'
210 result = warn(added, _('not removing %s: file has been marked for add'
211 ' (use forget to undo)\n')) or result
211 ' (use forget to undo)\n')) or result
212
212
213 # Need to lock because standin files are deleted then removed from the
213 # Need to lock because standin files are deleted then removed from the
214 # repository and we could race in-between.
214 # repository and we could race in-between.
215 with repo.wlock():
215 with repo.wlock():
216 lfdirstate = lfutil.openlfdirstate(ui, repo)
216 lfdirstate = lfutil.openlfdirstate(ui, repo)
217 for f in sorted(remove):
217 for f in sorted(remove):
218 if ui.verbose or not m.exact(f):
218 if ui.verbose or not m.exact(f):
219 # addremove in core gets fancy with the name, remove doesn't
219 # addremove in core gets fancy with the name, remove doesn't
220 if isaddremove:
220 if isaddremove:
221 name = m.uipath(f)
221 name = m.uipath(f)
222 else:
222 else:
223 name = m.rel(f)
223 name = m.rel(f)
224 ui.status(_('removing %s\n') % name)
224 ui.status(_('removing %s\n') % name)
225
225
226 if not opts.get(r'dry_run'):
226 if not opts.get(r'dry_run'):
227 if not after:
227 if not after:
228 repo.wvfs.unlinkpath(f, ignoremissing=True)
228 repo.wvfs.unlinkpath(f, ignoremissing=True)
229
229
230 if opts.get(r'dry_run'):
230 if opts.get(r'dry_run'):
231 return result
231 return result
232
232
233 remove = [lfutil.standin(f) for f in remove]
233 remove = [lfutil.standin(f) for f in remove]
234 # If this is being called by addremove, let the original addremove
234 # If this is being called by addremove, let the original addremove
235 # function handle this.
235 # function handle this.
236 if not isaddremove:
236 if not isaddremove:
237 for f in remove:
237 for f in remove:
238 repo.wvfs.unlinkpath(f, ignoremissing=True)
238 repo.wvfs.unlinkpath(f, ignoremissing=True)
239 repo[None].forget(remove)
239 repo[None].forget(remove)
240
240
241 for f in remove:
241 for f in remove:
242 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
242 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
243 False)
243 False)
244
244
245 lfdirstate.write()
245 lfdirstate.write()
246
246
247 return result
247 return result
248
248
249 # For overriding mercurial.hgweb.webcommands so that largefiles will
249 # For overriding mercurial.hgweb.webcommands so that largefiles will
250 # appear at their right place in the manifests.
250 # appear at their right place in the manifests.
251 def decodepath(orig, path):
251 def decodepath(orig, path):
252 return lfutil.splitstandin(path) or path
252 return lfutil.splitstandin(path) or path
253
253
254 # -- Wrappers: modify existing commands --------------------------------
254 # -- Wrappers: modify existing commands --------------------------------
255
255
256 def overrideadd(orig, ui, repo, *pats, **opts):
256 def overrideadd(orig, ui, repo, *pats, **opts):
257 if opts.get(r'normal') and opts.get(r'large'):
257 if opts.get(r'normal') and opts.get(r'large'):
258 raise error.Abort(_('--normal cannot be used with --large'))
258 raise error.Abort(_('--normal cannot be used with --large'))
259 return orig(ui, repo, *pats, **opts)
259 return orig(ui, repo, *pats, **opts)
260
260
261 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
261 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
262 # The --normal flag short circuits this override
262 # The --normal flag short circuits this override
263 if opts.get(r'normal'):
263 if opts.get(r'normal'):
264 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
264 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
265
265
266 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
266 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
267 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
267 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
268 ladded)
268 ladded)
269 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
269 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
270
270
271 bad.extend(f for f in lbad)
271 bad.extend(f for f in lbad)
272 return bad
272 return bad
273
273
274 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos):
274 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos):
275 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
275 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
276 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos)
276 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos)
277 return removelargefiles(ui, repo, False, matcher, after=after,
277 return removelargefiles(ui, repo, False, matcher, after=after,
278 force=force) or result
278 force=force) or result
279
279
280 def overridestatusfn(orig, repo, rev2, **opts):
280 def overridestatusfn(orig, repo, rev2, **opts):
281 try:
281 try:
282 repo._repo.lfstatus = True
282 repo._repo.lfstatus = True
283 return orig(repo, rev2, **opts)
283 return orig(repo, rev2, **opts)
284 finally:
284 finally:
285 repo._repo.lfstatus = False
285 repo._repo.lfstatus = False
286
286
287 def overridestatus(orig, ui, repo, *pats, **opts):
287 def overridestatus(orig, ui, repo, *pats, **opts):
288 try:
288 try:
289 repo.lfstatus = True
289 repo.lfstatus = True
290 return orig(ui, repo, *pats, **opts)
290 return orig(ui, repo, *pats, **opts)
291 finally:
291 finally:
292 repo.lfstatus = False
292 repo.lfstatus = False
293
293
294 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
294 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
295 try:
295 try:
296 repo._repo.lfstatus = True
296 repo._repo.lfstatus = True
297 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
297 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
298 finally:
298 finally:
299 repo._repo.lfstatus = False
299 repo._repo.lfstatus = False
300
300
301 def overridelog(orig, ui, repo, *pats, **opts):
301 def overridelog(orig, ui, repo, *pats, **opts):
302 def overridematchandpats(ctx, pats=(), opts=None, globbed=False,
302 def overridematchandpats(ctx, pats=(), opts=None, globbed=False,
303 default='relpath', badfn=None):
303 default='relpath', badfn=None):
304 """Matcher that merges root directory with .hglf, suitable for log.
304 """Matcher that merges root directory with .hglf, suitable for log.
305 It is still possible to match .hglf directly.
305 It is still possible to match .hglf directly.
306 For any listed files run log on the standin too.
306 For any listed files run log on the standin too.
307 matchfn tries both the given filename and with .hglf stripped.
307 matchfn tries both the given filename and with .hglf stripped.
308 """
308 """
309 if opts is None:
309 if opts is None:
310 opts = {}
310 opts = {}
311 matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default,
311 matchandpats = oldmatchandpats(ctx, pats, opts, globbed, default,
312 badfn=badfn)
312 badfn=badfn)
313 m, p = copy.copy(matchandpats)
313 m, p = copy.copy(matchandpats)
314
314
315 if m.always():
315 if m.always():
316 # We want to match everything anyway, so there's no benefit trying
316 # We want to match everything anyway, so there's no benefit trying
317 # to add standins.
317 # to add standins.
318 return matchandpats
318 return matchandpats
319
319
320 pats = set(p)
320 pats = set(p)
321
321
322 def fixpats(pat, tostandin=lfutil.standin):
322 def fixpats(pat, tostandin=lfutil.standin):
323 if pat.startswith('set:'):
323 if pat.startswith('set:'):
324 return pat
324 return pat
325
325
326 kindpat = matchmod._patsplit(pat, None)
326 kindpat = matchmod._patsplit(pat, None)
327
327
328 if kindpat[0] is not None:
328 if kindpat[0] is not None:
329 return kindpat[0] + ':' + tostandin(kindpat[1])
329 return kindpat[0] + ':' + tostandin(kindpat[1])
330 return tostandin(kindpat[1])
330 return tostandin(kindpat[1])
331
331
332 if m._cwd:
332 if m._cwd:
333 hglf = lfutil.shortname
333 hglf = lfutil.shortname
334 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
334 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
335
335
336 def tostandin(f):
336 def tostandin(f):
337 # The file may already be a standin, so truncate the back
337 # The file may already be a standin, so truncate the back
338 # prefix and test before mangling it. This avoids turning
338 # prefix and test before mangling it. This avoids turning
339 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
339 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
340 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
340 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
341 return f
341 return f
342
342
343 # An absolute path is from outside the repo, so truncate the
343 # An absolute path is from outside the repo, so truncate the
344 # path to the root before building the standin. Otherwise cwd
344 # path to the root before building the standin. Otherwise cwd
345 # is somewhere in the repo, relative to root, and needs to be
345 # is somewhere in the repo, relative to root, and needs to be
346 # prepended before building the standin.
346 # prepended before building the standin.
347 if os.path.isabs(m._cwd):
347 if os.path.isabs(m._cwd):
348 f = f[len(back):]
348 f = f[len(back):]
349 else:
349 else:
350 f = m._cwd + '/' + f
350 f = m._cwd + '/' + f
351 return back + lfutil.standin(f)
351 return back + lfutil.standin(f)
352 else:
352 else:
353 def tostandin(f):
353 def tostandin(f):
354 if lfutil.isstandin(f):
354 if lfutil.isstandin(f):
355 return f
355 return f
356 return lfutil.standin(f)
356 return lfutil.standin(f)
357 pats.update(fixpats(f, tostandin) for f in p)
357 pats.update(fixpats(f, tostandin) for f in p)
358
358
359 for i in range(0, len(m._files)):
359 for i in range(0, len(m._files)):
360 # Don't add '.hglf' to m.files, since that is already covered by '.'
360 # Don't add '.hglf' to m.files, since that is already covered by '.'
361 if m._files[i] == '.':
361 if m._files[i] == '.':
362 continue
362 continue
363 standin = lfutil.standin(m._files[i])
363 standin = lfutil.standin(m._files[i])
364 # If the "standin" is a directory, append instead of replace to
364 # If the "standin" is a directory, append instead of replace to
365 # support naming a directory on the command line with only
365 # support naming a directory on the command line with only
366 # largefiles. The original directory is kept to support normal
366 # largefiles. The original directory is kept to support normal
367 # files.
367 # files.
368 if standin in ctx:
368 if standin in ctx:
369 m._files[i] = standin
369 m._files[i] = standin
370 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
370 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
371 m._files.append(standin)
371 m._files.append(standin)
372
372
373 m._fileset = set(m._files)
373 m._fileset = set(m._files)
374 m.always = lambda: False
374 m.always = lambda: False
375 origmatchfn = m.matchfn
375 origmatchfn = m.matchfn
376 def lfmatchfn(f):
376 def lfmatchfn(f):
377 lf = lfutil.splitstandin(f)
377 lf = lfutil.splitstandin(f)
378 if lf is not None and origmatchfn(lf):
378 if lf is not None and origmatchfn(lf):
379 return True
379 return True
380 r = origmatchfn(f)
380 r = origmatchfn(f)
381 return r
381 return r
382 m.matchfn = lfmatchfn
382 m.matchfn = lfmatchfn
383
383
384 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
384 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
385 return m, pats
385 return m, pats
386
386
387 # For hg log --patch, the match object is used in two different senses:
387 # For hg log --patch, the match object is used in two different senses:
388 # (1) to determine what revisions should be printed out, and
388 # (1) to determine what revisions should be printed out, and
389 # (2) to determine what files to print out diffs for.
389 # (2) to determine what files to print out diffs for.
390 # The magic matchandpats override should be used for case (1) but not for
390 # The magic matchandpats override should be used for case (1) but not for
391 # case (2).
391 # case (2).
392 def overridemakelogfilematcher(repo, pats, opts, badfn=None):
392 def overridemakefilematcher(repo, pats, opts, badfn=None):
393 wctx = repo[None]
393 wctx = repo[None]
394 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
394 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
395 return lambda rev: match
395 return lambda rev: match
396
396
397 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
397 oldmatchandpats = installmatchandpatsfn(overridematchandpats)
398 oldmakelogfilematcher = logcmdutil._makenofollowlogfilematcher
398 oldmakefilematcher = logcmdutil._makenofollowfilematcher
399 setattr(logcmdutil, '_makenofollowlogfilematcher',
399 setattr(logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
400 overridemakelogfilematcher)
401
400
402 try:
401 try:
403 return orig(ui, repo, *pats, **opts)
402 return orig(ui, repo, *pats, **opts)
404 finally:
403 finally:
405 restorematchandpatsfn()
404 restorematchandpatsfn()
406 setattr(logcmdutil, '_makenofollowlogfilematcher',
405 setattr(logcmdutil, '_makenofollowfilematcher', oldmakefilematcher)
407 oldmakelogfilematcher)
408
406
409 def overrideverify(orig, ui, repo, *pats, **opts):
407 def overrideverify(orig, ui, repo, *pats, **opts):
410 large = opts.pop(r'large', False)
408 large = opts.pop(r'large', False)
411 all = opts.pop(r'lfa', False)
409 all = opts.pop(r'lfa', False)
412 contents = opts.pop(r'lfc', False)
410 contents = opts.pop(r'lfc', False)
413
411
414 result = orig(ui, repo, *pats, **opts)
412 result = orig(ui, repo, *pats, **opts)
415 if large or all or contents:
413 if large or all or contents:
416 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
414 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
417 return result
415 return result
418
416
419 def overridedebugstate(orig, ui, repo, *pats, **opts):
417 def overridedebugstate(orig, ui, repo, *pats, **opts):
420 large = opts.pop(r'large', False)
418 large = opts.pop(r'large', False)
421 if large:
419 if large:
422 class fakerepo(object):
420 class fakerepo(object):
423 dirstate = lfutil.openlfdirstate(ui, repo)
421 dirstate = lfutil.openlfdirstate(ui, repo)
424 orig(ui, fakerepo, *pats, **opts)
422 orig(ui, fakerepo, *pats, **opts)
425 else:
423 else:
426 orig(ui, repo, *pats, **opts)
424 orig(ui, repo, *pats, **opts)
427
425
428 # Before starting the manifest merge, merge.updates will call
426 # Before starting the manifest merge, merge.updates will call
429 # _checkunknownfile to check if there are any files in the merged-in
427 # _checkunknownfile to check if there are any files in the merged-in
430 # changeset that collide with unknown files in the working copy.
428 # changeset that collide with unknown files in the working copy.
431 #
429 #
432 # The largefiles are seen as unknown, so this prevents us from merging
430 # The largefiles are seen as unknown, so this prevents us from merging
433 # in a file 'foo' if we already have a largefile with the same name.
431 # in a file 'foo' if we already have a largefile with the same name.
434 #
432 #
435 # The overridden function filters the unknown files by removing any
433 # The overridden function filters the unknown files by removing any
436 # largefiles. This makes the merge proceed and we can then handle this
434 # largefiles. This makes the merge proceed and we can then handle this
437 # case further in the overridden calculateupdates function below.
435 # case further in the overridden calculateupdates function below.
438 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
436 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
439 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
437 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
440 return False
438 return False
441 return origfn(repo, wctx, mctx, f, f2)
439 return origfn(repo, wctx, mctx, f, f2)
442
440
443 # The manifest merge handles conflicts on the manifest level. We want
441 # The manifest merge handles conflicts on the manifest level. We want
444 # to handle changes in largefile-ness of files at this level too.
442 # to handle changes in largefile-ness of files at this level too.
445 #
443 #
446 # The strategy is to run the original calculateupdates and then process
444 # The strategy is to run the original calculateupdates and then process
447 # the action list it outputs. There are two cases we need to deal with:
445 # the action list it outputs. There are two cases we need to deal with:
448 #
446 #
449 # 1. Normal file in p1, largefile in p2. Here the largefile is
447 # 1. Normal file in p1, largefile in p2. Here the largefile is
450 # detected via its standin file, which will enter the working copy
448 # detected via its standin file, which will enter the working copy
451 # with a "get" action. It is not "merge" since the standin is all
449 # with a "get" action. It is not "merge" since the standin is all
452 # Mercurial is concerned with at this level -- the link to the
450 # Mercurial is concerned with at this level -- the link to the
453 # existing normal file is not relevant here.
451 # existing normal file is not relevant here.
454 #
452 #
455 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
453 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
456 # since the largefile will be present in the working copy and
454 # since the largefile will be present in the working copy and
457 # different from the normal file in p2. Mercurial therefore
455 # different from the normal file in p2. Mercurial therefore
458 # triggers a merge action.
456 # triggers a merge action.
459 #
457 #
460 # In both cases, we prompt the user and emit new actions to either
458 # In both cases, we prompt the user and emit new actions to either
461 # remove the standin (if the normal file was kept) or to remove the
459 # remove the standin (if the normal file was kept) or to remove the
462 # normal file and get the standin (if the largefile was kept). The
460 # normal file and get the standin (if the largefile was kept). The
463 # default prompt answer is to use the largefile version since it was
461 # default prompt answer is to use the largefile version since it was
464 # presumably changed on purpose.
462 # presumably changed on purpose.
465 #
463 #
466 # Finally, the merge.applyupdates function will then take care of
464 # Finally, the merge.applyupdates function will then take care of
467 # writing the files into the working copy and lfcommands.updatelfiles
465 # writing the files into the working copy and lfcommands.updatelfiles
468 # will update the largefiles.
466 # will update the largefiles.
469 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
467 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
470 acceptremote, *args, **kwargs):
468 acceptremote, *args, **kwargs):
471 overwrite = force and not branchmerge
469 overwrite = force and not branchmerge
472 actions, diverge, renamedelete = origfn(
470 actions, diverge, renamedelete = origfn(
473 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
471 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
474
472
475 if overwrite:
473 if overwrite:
476 return actions, diverge, renamedelete
474 return actions, diverge, renamedelete
477
475
478 # Convert to dictionary with filename as key and action as value.
476 # Convert to dictionary with filename as key and action as value.
479 lfiles = set()
477 lfiles = set()
480 for f in actions:
478 for f in actions:
481 splitstandin = lfutil.splitstandin(f)
479 splitstandin = lfutil.splitstandin(f)
482 if splitstandin in p1:
480 if splitstandin in p1:
483 lfiles.add(splitstandin)
481 lfiles.add(splitstandin)
484 elif lfutil.standin(f) in p1:
482 elif lfutil.standin(f) in p1:
485 lfiles.add(f)
483 lfiles.add(f)
486
484
487 for lfile in sorted(lfiles):
485 for lfile in sorted(lfiles):
488 standin = lfutil.standin(lfile)
486 standin = lfutil.standin(lfile)
489 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
487 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
490 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
488 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
491 if sm in ('g', 'dc') and lm != 'r':
489 if sm in ('g', 'dc') and lm != 'r':
492 if sm == 'dc':
490 if sm == 'dc':
493 f1, f2, fa, move, anc = sargs
491 f1, f2, fa, move, anc = sargs
494 sargs = (p2[f2].flags(), False)
492 sargs = (p2[f2].flags(), False)
495 # Case 1: normal file in the working copy, largefile in
493 # Case 1: normal file in the working copy, largefile in
496 # the second parent
494 # the second parent
497 usermsg = _('remote turned local normal file %s into a largefile\n'
495 usermsg = _('remote turned local normal file %s into a largefile\n'
498 'use (l)argefile or keep (n)ormal file?'
496 'use (l)argefile or keep (n)ormal file?'
499 '$$ &Largefile $$ &Normal file') % lfile
497 '$$ &Largefile $$ &Normal file') % lfile
500 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
498 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
501 actions[lfile] = ('r', None, 'replaced by standin')
499 actions[lfile] = ('r', None, 'replaced by standin')
502 actions[standin] = ('g', sargs, 'replaces standin')
500 actions[standin] = ('g', sargs, 'replaces standin')
503 else: # keep local normal file
501 else: # keep local normal file
504 actions[lfile] = ('k', None, 'replaces standin')
502 actions[lfile] = ('k', None, 'replaces standin')
505 if branchmerge:
503 if branchmerge:
506 actions[standin] = ('k', None, 'replaced by non-standin')
504 actions[standin] = ('k', None, 'replaced by non-standin')
507 else:
505 else:
508 actions[standin] = ('r', None, 'replaced by non-standin')
506 actions[standin] = ('r', None, 'replaced by non-standin')
509 elif lm in ('g', 'dc') and sm != 'r':
507 elif lm in ('g', 'dc') and sm != 'r':
510 if lm == 'dc':
508 if lm == 'dc':
511 f1, f2, fa, move, anc = largs
509 f1, f2, fa, move, anc = largs
512 largs = (p2[f2].flags(), False)
510 largs = (p2[f2].flags(), False)
513 # Case 2: largefile in the working copy, normal file in
511 # Case 2: largefile in the working copy, normal file in
514 # the second parent
512 # the second parent
515 usermsg = _('remote turned local largefile %s into a normal file\n'
513 usermsg = _('remote turned local largefile %s into a normal file\n'
516 'keep (l)argefile or use (n)ormal file?'
514 'keep (l)argefile or use (n)ormal file?'
517 '$$ &Largefile $$ &Normal file') % lfile
515 '$$ &Largefile $$ &Normal file') % lfile
518 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
516 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
519 if branchmerge:
517 if branchmerge:
520 # largefile can be restored from standin safely
518 # largefile can be restored from standin safely
521 actions[lfile] = ('k', None, 'replaced by standin')
519 actions[lfile] = ('k', None, 'replaced by standin')
522 actions[standin] = ('k', None, 'replaces standin')
520 actions[standin] = ('k', None, 'replaces standin')
523 else:
521 else:
524 # "lfile" should be marked as "removed" without
522 # "lfile" should be marked as "removed" without
525 # removal of itself
523 # removal of itself
526 actions[lfile] = ('lfmr', None,
524 actions[lfile] = ('lfmr', None,
527 'forget non-standin largefile')
525 'forget non-standin largefile')
528
526
529 # linear-merge should treat this largefile as 're-added'
527 # linear-merge should treat this largefile as 're-added'
530 actions[standin] = ('a', None, 'keep standin')
528 actions[standin] = ('a', None, 'keep standin')
531 else: # pick remote normal file
529 else: # pick remote normal file
532 actions[lfile] = ('g', largs, 'replaces standin')
530 actions[lfile] = ('g', largs, 'replaces standin')
533 actions[standin] = ('r', None, 'replaced by non-standin')
531 actions[standin] = ('r', None, 'replaced by non-standin')
534
532
535 return actions, diverge, renamedelete
533 return actions, diverge, renamedelete
536
534
537 def mergerecordupdates(orig, repo, actions, branchmerge):
535 def mergerecordupdates(orig, repo, actions, branchmerge):
538 if 'lfmr' in actions:
536 if 'lfmr' in actions:
539 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
537 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
540 for lfile, args, msg in actions['lfmr']:
538 for lfile, args, msg in actions['lfmr']:
541 # this should be executed before 'orig', to execute 'remove'
539 # this should be executed before 'orig', to execute 'remove'
542 # before all other actions
540 # before all other actions
543 repo.dirstate.remove(lfile)
541 repo.dirstate.remove(lfile)
544 # make sure lfile doesn't get synclfdirstate'd as normal
542 # make sure lfile doesn't get synclfdirstate'd as normal
545 lfdirstate.add(lfile)
543 lfdirstate.add(lfile)
546 lfdirstate.write()
544 lfdirstate.write()
547
545
548 return orig(repo, actions, branchmerge)
546 return orig(repo, actions, branchmerge)
549
547
550 # Override filemerge to prompt the user about how they wish to merge
548 # Override filemerge to prompt the user about how they wish to merge
551 # largefiles. This will handle identical edits without prompting the user.
549 # largefiles. This will handle identical edits without prompting the user.
552 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
550 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
553 labels=None):
551 labels=None):
554 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
552 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
555 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
553 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
556 labels=labels)
554 labels=labels)
557
555
558 ahash = lfutil.readasstandin(fca).lower()
556 ahash = lfutil.readasstandin(fca).lower()
559 dhash = lfutil.readasstandin(fcd).lower()
557 dhash = lfutil.readasstandin(fcd).lower()
560 ohash = lfutil.readasstandin(fco).lower()
558 ohash = lfutil.readasstandin(fco).lower()
561 if (ohash != ahash and
559 if (ohash != ahash and
562 ohash != dhash and
560 ohash != dhash and
563 (dhash == ahash or
561 (dhash == ahash or
564 repo.ui.promptchoice(
562 repo.ui.promptchoice(
565 _('largefile %s has a merge conflict\nancestor was %s\n'
563 _('largefile %s has a merge conflict\nancestor was %s\n'
566 'keep (l)ocal %s or\ntake (o)ther %s?'
564 'keep (l)ocal %s or\ntake (o)ther %s?'
567 '$$ &Local $$ &Other') %
565 '$$ &Local $$ &Other') %
568 (lfutil.splitstandin(orig), ahash, dhash, ohash),
566 (lfutil.splitstandin(orig), ahash, dhash, ohash),
569 0) == 1)):
567 0) == 1)):
570 repo.wwrite(fcd.path(), fco.data(), fco.flags())
568 repo.wwrite(fcd.path(), fco.data(), fco.flags())
571 return True, 0, False
569 return True, 0, False
572
570
573 def copiespathcopies(orig, ctx1, ctx2, match=None):
571 def copiespathcopies(orig, ctx1, ctx2, match=None):
574 copies = orig(ctx1, ctx2, match=match)
572 copies = orig(ctx1, ctx2, match=match)
575 updated = {}
573 updated = {}
576
574
577 for k, v in copies.iteritems():
575 for k, v in copies.iteritems():
578 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
576 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
579
577
580 return updated
578 return updated
581
579
582 # Copy first changes the matchers to match standins instead of
580 # Copy first changes the matchers to match standins instead of
583 # largefiles. Then it overrides util.copyfile in that function it
581 # largefiles. Then it overrides util.copyfile in that function it
584 # checks if the destination largefile already exists. It also keeps a
582 # checks if the destination largefile already exists. It also keeps a
585 # list of copied files so that the largefiles can be copied and the
583 # list of copied files so that the largefiles can be copied and the
586 # dirstate updated.
584 # dirstate updated.
587 def overridecopy(orig, ui, repo, pats, opts, rename=False):
585 def overridecopy(orig, ui, repo, pats, opts, rename=False):
588 # doesn't remove largefile on rename
586 # doesn't remove largefile on rename
589 if len(pats) < 2:
587 if len(pats) < 2:
590 # this isn't legal, let the original function deal with it
588 # this isn't legal, let the original function deal with it
591 return orig(ui, repo, pats, opts, rename)
589 return orig(ui, repo, pats, opts, rename)
592
590
593 # This could copy both lfiles and normal files in one command,
591 # This could copy both lfiles and normal files in one command,
594 # but we don't want to do that. First replace their matcher to
592 # but we don't want to do that. First replace their matcher to
595 # only match normal files and run it, then replace it to just
593 # only match normal files and run it, then replace it to just
596 # match largefiles and run it again.
594 # match largefiles and run it again.
597 nonormalfiles = False
595 nonormalfiles = False
598 nolfiles = False
596 nolfiles = False
599 installnormalfilesmatchfn(repo[None].manifest())
597 installnormalfilesmatchfn(repo[None].manifest())
600 try:
598 try:
601 result = orig(ui, repo, pats, opts, rename)
599 result = orig(ui, repo, pats, opts, rename)
602 except error.Abort as e:
600 except error.Abort as e:
603 if str(e) != _('no files to copy'):
601 if str(e) != _('no files to copy'):
604 raise e
602 raise e
605 else:
603 else:
606 nonormalfiles = True
604 nonormalfiles = True
607 result = 0
605 result = 0
608 finally:
606 finally:
609 restorematchfn()
607 restorematchfn()
610
608
611 # The first rename can cause our current working directory to be removed.
609 # The first rename can cause our current working directory to be removed.
612 # In that case there is nothing left to copy/rename so just quit.
610 # In that case there is nothing left to copy/rename so just quit.
613 try:
611 try:
614 repo.getcwd()
612 repo.getcwd()
615 except OSError:
613 except OSError:
616 return result
614 return result
617
615
618 def makestandin(relpath):
616 def makestandin(relpath):
619 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
617 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
620 return repo.wvfs.join(lfutil.standin(path))
618 return repo.wvfs.join(lfutil.standin(path))
621
619
622 fullpats = scmutil.expandpats(pats)
620 fullpats = scmutil.expandpats(pats)
623 dest = fullpats[-1]
621 dest = fullpats[-1]
624
622
625 if os.path.isdir(dest):
623 if os.path.isdir(dest):
626 if not os.path.isdir(makestandin(dest)):
624 if not os.path.isdir(makestandin(dest)):
627 os.makedirs(makestandin(dest))
625 os.makedirs(makestandin(dest))
628
626
629 try:
627 try:
630 # When we call orig below it creates the standins but we don't add
628 # When we call orig below it creates the standins but we don't add
631 # them to the dir state until later so lock during that time.
629 # them to the dir state until later so lock during that time.
632 wlock = repo.wlock()
630 wlock = repo.wlock()
633
631
634 manifest = repo[None].manifest()
632 manifest = repo[None].manifest()
635 def overridematch(ctx, pats=(), opts=None, globbed=False,
633 def overridematch(ctx, pats=(), opts=None, globbed=False,
636 default='relpath', badfn=None):
634 default='relpath', badfn=None):
637 if opts is None:
635 if opts is None:
638 opts = {}
636 opts = {}
639 newpats = []
637 newpats = []
640 # The patterns were previously mangled to add the standin
638 # The patterns were previously mangled to add the standin
641 # directory; we need to remove that now
639 # directory; we need to remove that now
642 for pat in pats:
640 for pat in pats:
643 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
641 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
644 newpats.append(pat.replace(lfutil.shortname, ''))
642 newpats.append(pat.replace(lfutil.shortname, ''))
645 else:
643 else:
646 newpats.append(pat)
644 newpats.append(pat)
647 match = oldmatch(ctx, newpats, opts, globbed, default, badfn=badfn)
645 match = oldmatch(ctx, newpats, opts, globbed, default, badfn=badfn)
648 m = copy.copy(match)
646 m = copy.copy(match)
649 lfile = lambda f: lfutil.standin(f) in manifest
647 lfile = lambda f: lfutil.standin(f) in manifest
650 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
648 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
651 m._fileset = set(m._files)
649 m._fileset = set(m._files)
652 origmatchfn = m.matchfn
650 origmatchfn = m.matchfn
653 def matchfn(f):
651 def matchfn(f):
654 lfile = lfutil.splitstandin(f)
652 lfile = lfutil.splitstandin(f)
655 return (lfile is not None and
653 return (lfile is not None and
656 (f in manifest) and
654 (f in manifest) and
657 origmatchfn(lfile) or
655 origmatchfn(lfile) or
658 None)
656 None)
659 m.matchfn = matchfn
657 m.matchfn = matchfn
660 return m
658 return m
661 oldmatch = installmatchfn(overridematch)
659 oldmatch = installmatchfn(overridematch)
662 listpats = []
660 listpats = []
663 for pat in pats:
661 for pat in pats:
664 if matchmod.patkind(pat) is not None:
662 if matchmod.patkind(pat) is not None:
665 listpats.append(pat)
663 listpats.append(pat)
666 else:
664 else:
667 listpats.append(makestandin(pat))
665 listpats.append(makestandin(pat))
668
666
669 try:
667 try:
670 origcopyfile = util.copyfile
668 origcopyfile = util.copyfile
671 copiedfiles = []
669 copiedfiles = []
672 def overridecopyfile(src, dest):
670 def overridecopyfile(src, dest):
673 if (lfutil.shortname in src and
671 if (lfutil.shortname in src and
674 dest.startswith(repo.wjoin(lfutil.shortname))):
672 dest.startswith(repo.wjoin(lfutil.shortname))):
675 destlfile = dest.replace(lfutil.shortname, '')
673 destlfile = dest.replace(lfutil.shortname, '')
676 if not opts['force'] and os.path.exists(destlfile):
674 if not opts['force'] and os.path.exists(destlfile):
677 raise IOError('',
675 raise IOError('',
678 _('destination largefile already exists'))
676 _('destination largefile already exists'))
679 copiedfiles.append((src, dest))
677 copiedfiles.append((src, dest))
680 origcopyfile(src, dest)
678 origcopyfile(src, dest)
681
679
682 util.copyfile = overridecopyfile
680 util.copyfile = overridecopyfile
683 result += orig(ui, repo, listpats, opts, rename)
681 result += orig(ui, repo, listpats, opts, rename)
684 finally:
682 finally:
685 util.copyfile = origcopyfile
683 util.copyfile = origcopyfile
686
684
687 lfdirstate = lfutil.openlfdirstate(ui, repo)
685 lfdirstate = lfutil.openlfdirstate(ui, repo)
688 for (src, dest) in copiedfiles:
686 for (src, dest) in copiedfiles:
689 if (lfutil.shortname in src and
687 if (lfutil.shortname in src and
690 dest.startswith(repo.wjoin(lfutil.shortname))):
688 dest.startswith(repo.wjoin(lfutil.shortname))):
691 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
689 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
692 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
690 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
693 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
691 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
694 if not os.path.isdir(destlfiledir):
692 if not os.path.isdir(destlfiledir):
695 os.makedirs(destlfiledir)
693 os.makedirs(destlfiledir)
696 if rename:
694 if rename:
697 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
695 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
698
696
699 # The file is gone, but this deletes any empty parent
697 # The file is gone, but this deletes any empty parent
700 # directories as a side-effect.
698 # directories as a side-effect.
701 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
699 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
702 lfdirstate.remove(srclfile)
700 lfdirstate.remove(srclfile)
703 else:
701 else:
704 util.copyfile(repo.wjoin(srclfile),
702 util.copyfile(repo.wjoin(srclfile),
705 repo.wjoin(destlfile))
703 repo.wjoin(destlfile))
706
704
707 lfdirstate.add(destlfile)
705 lfdirstate.add(destlfile)
708 lfdirstate.write()
706 lfdirstate.write()
709 except error.Abort as e:
707 except error.Abort as e:
710 if str(e) != _('no files to copy'):
708 if str(e) != _('no files to copy'):
711 raise e
709 raise e
712 else:
710 else:
713 nolfiles = True
711 nolfiles = True
714 finally:
712 finally:
715 restorematchfn()
713 restorematchfn()
716 wlock.release()
714 wlock.release()
717
715
718 if nolfiles and nonormalfiles:
716 if nolfiles and nonormalfiles:
719 raise error.Abort(_('no files to copy'))
717 raise error.Abort(_('no files to copy'))
720
718
721 return result
719 return result
722
720
723 # When the user calls revert, we have to be careful to not revert any
721 # When the user calls revert, we have to be careful to not revert any
724 # changes to other largefiles accidentally. This means we have to keep
722 # changes to other largefiles accidentally. This means we have to keep
725 # track of the largefiles that are being reverted so we only pull down
723 # track of the largefiles that are being reverted so we only pull down
726 # the necessary largefiles.
724 # the necessary largefiles.
727 #
725 #
728 # Standins are only updated (to match the hash of largefiles) before
726 # Standins are only updated (to match the hash of largefiles) before
729 # commits. Update the standins then run the original revert, changing
727 # commits. Update the standins then run the original revert, changing
730 # the matcher to hit standins instead of largefiles. Based on the
728 # the matcher to hit standins instead of largefiles. Based on the
731 # resulting standins update the largefiles.
729 # resulting standins update the largefiles.
732 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
730 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
733 # Because we put the standins in a bad state (by updating them)
731 # Because we put the standins in a bad state (by updating them)
734 # and then return them to a correct state we need to lock to
732 # and then return them to a correct state we need to lock to
735 # prevent others from changing them in their incorrect state.
733 # prevent others from changing them in their incorrect state.
736 with repo.wlock():
734 with repo.wlock():
737 lfdirstate = lfutil.openlfdirstate(ui, repo)
735 lfdirstate = lfutil.openlfdirstate(ui, repo)
738 s = lfutil.lfdirstatestatus(lfdirstate, repo)
736 s = lfutil.lfdirstatestatus(lfdirstate, repo)
739 lfdirstate.write()
737 lfdirstate.write()
740 for lfile in s.modified:
738 for lfile in s.modified:
741 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
739 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
742 for lfile in s.deleted:
740 for lfile in s.deleted:
743 fstandin = lfutil.standin(lfile)
741 fstandin = lfutil.standin(lfile)
744 if (repo.wvfs.exists(fstandin)):
742 if (repo.wvfs.exists(fstandin)):
745 repo.wvfs.unlink(fstandin)
743 repo.wvfs.unlink(fstandin)
746
744
747 oldstandins = lfutil.getstandinsstate(repo)
745 oldstandins = lfutil.getstandinsstate(repo)
748
746
749 def overridematch(mctx, pats=(), opts=None, globbed=False,
747 def overridematch(mctx, pats=(), opts=None, globbed=False,
750 default='relpath', badfn=None):
748 default='relpath', badfn=None):
751 if opts is None:
749 if opts is None:
752 opts = {}
750 opts = {}
753 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
751 match = oldmatch(mctx, pats, opts, globbed, default, badfn=badfn)
754 m = copy.copy(match)
752 m = copy.copy(match)
755
753
756 # revert supports recursing into subrepos, and though largefiles
754 # revert supports recursing into subrepos, and though largefiles
757 # currently doesn't work correctly in that case, this match is
755 # currently doesn't work correctly in that case, this match is
758 # called, so the lfdirstate above may not be the correct one for
756 # called, so the lfdirstate above may not be the correct one for
759 # this invocation of match.
757 # this invocation of match.
760 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
758 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
761 False)
759 False)
762
760
763 wctx = repo[None]
761 wctx = repo[None]
764 matchfiles = []
762 matchfiles = []
765 for f in m._files:
763 for f in m._files:
766 standin = lfutil.standin(f)
764 standin = lfutil.standin(f)
767 if standin in ctx or standin in mctx:
765 if standin in ctx or standin in mctx:
768 matchfiles.append(standin)
766 matchfiles.append(standin)
769 elif standin in wctx or lfdirstate[f] == 'r':
767 elif standin in wctx or lfdirstate[f] == 'r':
770 continue
768 continue
771 else:
769 else:
772 matchfiles.append(f)
770 matchfiles.append(f)
773 m._files = matchfiles
771 m._files = matchfiles
774 m._fileset = set(m._files)
772 m._fileset = set(m._files)
775 origmatchfn = m.matchfn
773 origmatchfn = m.matchfn
776 def matchfn(f):
774 def matchfn(f):
777 lfile = lfutil.splitstandin(f)
775 lfile = lfutil.splitstandin(f)
778 if lfile is not None:
776 if lfile is not None:
779 return (origmatchfn(lfile) and
777 return (origmatchfn(lfile) and
780 (f in ctx or f in mctx))
778 (f in ctx or f in mctx))
781 return origmatchfn(f)
779 return origmatchfn(f)
782 m.matchfn = matchfn
780 m.matchfn = matchfn
783 return m
781 return m
784 oldmatch = installmatchfn(overridematch)
782 oldmatch = installmatchfn(overridematch)
785 try:
783 try:
786 orig(ui, repo, ctx, parents, *pats, **opts)
784 orig(ui, repo, ctx, parents, *pats, **opts)
787 finally:
785 finally:
788 restorematchfn()
786 restorematchfn()
789
787
790 newstandins = lfutil.getstandinsstate(repo)
788 newstandins = lfutil.getstandinsstate(repo)
791 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
789 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
792 # lfdirstate should be 'normallookup'-ed for updated files,
790 # lfdirstate should be 'normallookup'-ed for updated files,
793 # because reverting doesn't touch dirstate for 'normal' files
791 # because reverting doesn't touch dirstate for 'normal' files
794 # when target revision is explicitly specified: in such case,
792 # when target revision is explicitly specified: in such case,
795 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
793 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
796 # of target (standin) file.
794 # of target (standin) file.
797 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
795 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
798 normallookup=True)
796 normallookup=True)
799
797
800 # after pulling changesets, we need to take some extra care to get
798 # after pulling changesets, we need to take some extra care to get
801 # largefiles updated remotely
799 # largefiles updated remotely
802 def overridepull(orig, ui, repo, source=None, **opts):
800 def overridepull(orig, ui, repo, source=None, **opts):
803 revsprepull = len(repo)
801 revsprepull = len(repo)
804 if not source:
802 if not source:
805 source = 'default'
803 source = 'default'
806 repo.lfpullsource = source
804 repo.lfpullsource = source
807 result = orig(ui, repo, source, **opts)
805 result = orig(ui, repo, source, **opts)
808 revspostpull = len(repo)
806 revspostpull = len(repo)
809 lfrevs = opts.get(r'lfrev', [])
807 lfrevs = opts.get(r'lfrev', [])
810 if opts.get(r'all_largefiles'):
808 if opts.get(r'all_largefiles'):
811 lfrevs.append('pulled()')
809 lfrevs.append('pulled()')
812 if lfrevs and revspostpull > revsprepull:
810 if lfrevs and revspostpull > revsprepull:
813 numcached = 0
811 numcached = 0
814 repo.firstpulled = revsprepull # for pulled() revset expression
812 repo.firstpulled = revsprepull # for pulled() revset expression
815 try:
813 try:
816 for rev in scmutil.revrange(repo, lfrevs):
814 for rev in scmutil.revrange(repo, lfrevs):
817 ui.note(_('pulling largefiles for revision %s\n') % rev)
815 ui.note(_('pulling largefiles for revision %s\n') % rev)
818 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
816 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
819 numcached += len(cached)
817 numcached += len(cached)
820 finally:
818 finally:
821 del repo.firstpulled
819 del repo.firstpulled
822 ui.status(_("%d largefiles cached\n") % numcached)
820 ui.status(_("%d largefiles cached\n") % numcached)
823 return result
821 return result
824
822
825 def overridepush(orig, ui, repo, *args, **kwargs):
823 def overridepush(orig, ui, repo, *args, **kwargs):
826 """Override push command and store --lfrev parameters in opargs"""
824 """Override push command and store --lfrev parameters in opargs"""
827 lfrevs = kwargs.pop(r'lfrev', None)
825 lfrevs = kwargs.pop(r'lfrev', None)
828 if lfrevs:
826 if lfrevs:
829 opargs = kwargs.setdefault('opargs', {})
827 opargs = kwargs.setdefault('opargs', {})
830 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
828 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
831 return orig(ui, repo, *args, **kwargs)
829 return orig(ui, repo, *args, **kwargs)
832
830
833 def exchangepushoperation(orig, *args, **kwargs):
831 def exchangepushoperation(orig, *args, **kwargs):
834 """Override pushoperation constructor and store lfrevs parameter"""
832 """Override pushoperation constructor and store lfrevs parameter"""
835 lfrevs = kwargs.pop(r'lfrevs', None)
833 lfrevs = kwargs.pop(r'lfrevs', None)
836 pushop = orig(*args, **kwargs)
834 pushop = orig(*args, **kwargs)
837 pushop.lfrevs = lfrevs
835 pushop.lfrevs = lfrevs
838 return pushop
836 return pushop
839
837
840 revsetpredicate = registrar.revsetpredicate()
838 revsetpredicate = registrar.revsetpredicate()
841
839
842 @revsetpredicate('pulled()')
840 @revsetpredicate('pulled()')
843 def pulledrevsetsymbol(repo, subset, x):
841 def pulledrevsetsymbol(repo, subset, x):
844 """Changesets that just has been pulled.
842 """Changesets that just has been pulled.
845
843
846 Only available with largefiles from pull --lfrev expressions.
844 Only available with largefiles from pull --lfrev expressions.
847
845
848 .. container:: verbose
846 .. container:: verbose
849
847
850 Some examples:
848 Some examples:
851
849
852 - pull largefiles for all new changesets::
850 - pull largefiles for all new changesets::
853
851
854 hg pull -lfrev "pulled()"
852 hg pull -lfrev "pulled()"
855
853
856 - pull largefiles for all new branch heads::
854 - pull largefiles for all new branch heads::
857
855
858 hg pull -lfrev "head(pulled()) and not closed()"
856 hg pull -lfrev "head(pulled()) and not closed()"
859
857
860 """
858 """
861
859
862 try:
860 try:
863 firstpulled = repo.firstpulled
861 firstpulled = repo.firstpulled
864 except AttributeError:
862 except AttributeError:
865 raise error.Abort(_("pulled() only available in --lfrev"))
863 raise error.Abort(_("pulled() only available in --lfrev"))
866 return smartset.baseset([r for r in subset if r >= firstpulled])
864 return smartset.baseset([r for r in subset if r >= firstpulled])
867
865
868 def overrideclone(orig, ui, source, dest=None, **opts):
866 def overrideclone(orig, ui, source, dest=None, **opts):
869 d = dest
867 d = dest
870 if d is None:
868 if d is None:
871 d = hg.defaultdest(source)
869 d = hg.defaultdest(source)
872 if opts.get(r'all_largefiles') and not hg.islocal(d):
870 if opts.get(r'all_largefiles') and not hg.islocal(d):
873 raise error.Abort(_(
871 raise error.Abort(_(
874 '--all-largefiles is incompatible with non-local destination %s') %
872 '--all-largefiles is incompatible with non-local destination %s') %
875 d)
873 d)
876
874
877 return orig(ui, source, dest, **opts)
875 return orig(ui, source, dest, **opts)
878
876
879 def hgclone(orig, ui, opts, *args, **kwargs):
877 def hgclone(orig, ui, opts, *args, **kwargs):
880 result = orig(ui, opts, *args, **kwargs)
878 result = orig(ui, opts, *args, **kwargs)
881
879
882 if result is not None:
880 if result is not None:
883 sourcerepo, destrepo = result
881 sourcerepo, destrepo = result
884 repo = destrepo.local()
882 repo = destrepo.local()
885
883
886 # When cloning to a remote repo (like through SSH), no repo is available
884 # When cloning to a remote repo (like through SSH), no repo is available
887 # from the peer. Therefore the largefiles can't be downloaded and the
885 # from the peer. Therefore the largefiles can't be downloaded and the
888 # hgrc can't be updated.
886 # hgrc can't be updated.
889 if not repo:
887 if not repo:
890 return result
888 return result
891
889
892 # If largefiles is required for this repo, permanently enable it locally
890 # If largefiles is required for this repo, permanently enable it locally
893 if 'largefiles' in repo.requirements:
891 if 'largefiles' in repo.requirements:
894 repo.vfs.append('hgrc',
892 repo.vfs.append('hgrc',
895 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
893 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
896
894
897 # Caching is implicitly limited to 'rev' option, since the dest repo was
895 # Caching is implicitly limited to 'rev' option, since the dest repo was
898 # truncated at that point. The user may expect a download count with
896 # truncated at that point. The user may expect a download count with
899 # this option, so attempt whether or not this is a largefile repo.
897 # this option, so attempt whether or not this is a largefile repo.
900 if opts.get(r'all_largefiles'):
898 if opts.get(r'all_largefiles'):
901 success, missing = lfcommands.downloadlfiles(ui, repo, None)
899 success, missing = lfcommands.downloadlfiles(ui, repo, None)
902
900
903 if missing != 0:
901 if missing != 0:
904 return None
902 return None
905
903
906 return result
904 return result
907
905
908 def hgpostshare(orig, sourcerepo, destrepo, bookmarks=True, defaultpath=None):
906 def hgpostshare(orig, sourcerepo, destrepo, bookmarks=True, defaultpath=None):
909 orig(sourcerepo, destrepo, bookmarks, defaultpath)
907 orig(sourcerepo, destrepo, bookmarks, defaultpath)
910
908
911 # If largefiles is required for this repo, permanently enable it locally
909 # If largefiles is required for this repo, permanently enable it locally
912 if 'largefiles' in destrepo.requirements:
910 if 'largefiles' in destrepo.requirements:
913 destrepo.vfs.append('hgrc',
911 destrepo.vfs.append('hgrc',
914 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
912 util.tonativeeol('\n[extensions]\nlargefiles=\n'))
915
913
916 def overriderebase(orig, ui, repo, **opts):
914 def overriderebase(orig, ui, repo, **opts):
917 if not util.safehasattr(repo, '_largefilesenabled'):
915 if not util.safehasattr(repo, '_largefilesenabled'):
918 return orig(ui, repo, **opts)
916 return orig(ui, repo, **opts)
919
917
920 resuming = opts.get(r'continue')
918 resuming = opts.get(r'continue')
921 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
919 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
922 repo._lfstatuswriters.append(lambda *msg, **opts: None)
920 repo._lfstatuswriters.append(lambda *msg, **opts: None)
923 try:
921 try:
924 return orig(ui, repo, **opts)
922 return orig(ui, repo, **opts)
925 finally:
923 finally:
926 repo._lfstatuswriters.pop()
924 repo._lfstatuswriters.pop()
927 repo._lfcommithooks.pop()
925 repo._lfcommithooks.pop()
928
926
929 def overridearchivecmd(orig, ui, repo, dest, **opts):
927 def overridearchivecmd(orig, ui, repo, dest, **opts):
930 repo.unfiltered().lfstatus = True
928 repo.unfiltered().lfstatus = True
931
929
932 try:
930 try:
933 return orig(ui, repo.unfiltered(), dest, **opts)
931 return orig(ui, repo.unfiltered(), dest, **opts)
934 finally:
932 finally:
935 repo.unfiltered().lfstatus = False
933 repo.unfiltered().lfstatus = False
936
934
937 def hgwebarchive(orig, web, req, tmpl):
935 def hgwebarchive(orig, web, req, tmpl):
938 web.repo.lfstatus = True
936 web.repo.lfstatus = True
939
937
940 try:
938 try:
941 return orig(web, req, tmpl)
939 return orig(web, req, tmpl)
942 finally:
940 finally:
943 web.repo.lfstatus = False
941 web.repo.lfstatus = False
944
942
945 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
943 def overridearchive(orig, repo, dest, node, kind, decode=True, matchfn=None,
946 prefix='', mtime=None, subrepos=None):
944 prefix='', mtime=None, subrepos=None):
947 # For some reason setting repo.lfstatus in hgwebarchive only changes the
945 # For some reason setting repo.lfstatus in hgwebarchive only changes the
948 # unfiltered repo's attr, so check that as well.
946 # unfiltered repo's attr, so check that as well.
949 if not repo.lfstatus and not repo.unfiltered().lfstatus:
947 if not repo.lfstatus and not repo.unfiltered().lfstatus:
950 return orig(repo, dest, node, kind, decode, matchfn, prefix, mtime,
948 return orig(repo, dest, node, kind, decode, matchfn, prefix, mtime,
951 subrepos)
949 subrepos)
952
950
953 # No need to lock because we are only reading history and
951 # No need to lock because we are only reading history and
954 # largefile caches, neither of which are modified.
952 # largefile caches, neither of which are modified.
955 if node is not None:
953 if node is not None:
956 lfcommands.cachelfiles(repo.ui, repo, node)
954 lfcommands.cachelfiles(repo.ui, repo, node)
957
955
958 if kind not in archival.archivers:
956 if kind not in archival.archivers:
959 raise error.Abort(_("unknown archive type '%s'") % kind)
957 raise error.Abort(_("unknown archive type '%s'") % kind)
960
958
961 ctx = repo[node]
959 ctx = repo[node]
962
960
963 if kind == 'files':
961 if kind == 'files':
964 if prefix:
962 if prefix:
965 raise error.Abort(
963 raise error.Abort(
966 _('cannot give prefix when archiving to files'))
964 _('cannot give prefix when archiving to files'))
967 else:
965 else:
968 prefix = archival.tidyprefix(dest, kind, prefix)
966 prefix = archival.tidyprefix(dest, kind, prefix)
969
967
970 def write(name, mode, islink, getdata):
968 def write(name, mode, islink, getdata):
971 if matchfn and not matchfn(name):
969 if matchfn and not matchfn(name):
972 return
970 return
973 data = getdata()
971 data = getdata()
974 if decode:
972 if decode:
975 data = repo.wwritedata(name, data)
973 data = repo.wwritedata(name, data)
976 archiver.addfile(prefix + name, mode, islink, data)
974 archiver.addfile(prefix + name, mode, islink, data)
977
975
978 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
976 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
979
977
980 if repo.ui.configbool("ui", "archivemeta"):
978 if repo.ui.configbool("ui", "archivemeta"):
981 write('.hg_archival.txt', 0o644, False,
979 write('.hg_archival.txt', 0o644, False,
982 lambda: archival.buildmetadata(ctx))
980 lambda: archival.buildmetadata(ctx))
983
981
984 for f in ctx:
982 for f in ctx:
985 ff = ctx.flags(f)
983 ff = ctx.flags(f)
986 getdata = ctx[f].data
984 getdata = ctx[f].data
987 lfile = lfutil.splitstandin(f)
985 lfile = lfutil.splitstandin(f)
988 if lfile is not None:
986 if lfile is not None:
989 if node is not None:
987 if node is not None:
990 path = lfutil.findfile(repo, getdata().strip())
988 path = lfutil.findfile(repo, getdata().strip())
991
989
992 if path is None:
990 if path is None:
993 raise error.Abort(
991 raise error.Abort(
994 _('largefile %s not found in repo store or system cache')
992 _('largefile %s not found in repo store or system cache')
995 % lfile)
993 % lfile)
996 else:
994 else:
997 path = lfile
995 path = lfile
998
996
999 f = lfile
997 f = lfile
1000
998
1001 getdata = lambda: util.readfile(path)
999 getdata = lambda: util.readfile(path)
1002 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1000 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1003
1001
1004 if subrepos:
1002 if subrepos:
1005 for subpath in sorted(ctx.substate):
1003 for subpath in sorted(ctx.substate):
1006 sub = ctx.workingsub(subpath)
1004 sub = ctx.workingsub(subpath)
1007 submatch = matchmod.subdirmatcher(subpath, matchfn)
1005 submatch = matchmod.subdirmatcher(subpath, matchfn)
1008 sub._repo.lfstatus = True
1006 sub._repo.lfstatus = True
1009 sub.archive(archiver, prefix, submatch)
1007 sub.archive(archiver, prefix, submatch)
1010
1008
1011 archiver.done()
1009 archiver.done()
1012
1010
1013 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1011 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1014 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1012 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1015 if not lfenabled or not repo._repo.lfstatus:
1013 if not lfenabled or not repo._repo.lfstatus:
1016 return orig(repo, archiver, prefix, match, decode)
1014 return orig(repo, archiver, prefix, match, decode)
1017
1015
1018 repo._get(repo._state + ('hg',))
1016 repo._get(repo._state + ('hg',))
1019 rev = repo._state[1]
1017 rev = repo._state[1]
1020 ctx = repo._repo[rev]
1018 ctx = repo._repo[rev]
1021
1019
1022 if ctx.node() is not None:
1020 if ctx.node() is not None:
1023 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1021 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1024
1022
1025 def write(name, mode, islink, getdata):
1023 def write(name, mode, islink, getdata):
1026 # At this point, the standin has been replaced with the largefile name,
1024 # At this point, the standin has been replaced with the largefile name,
1027 # so the normal matcher works here without the lfutil variants.
1025 # so the normal matcher works here without the lfutil variants.
1028 if match and not match(f):
1026 if match and not match(f):
1029 return
1027 return
1030 data = getdata()
1028 data = getdata()
1031 if decode:
1029 if decode:
1032 data = repo._repo.wwritedata(name, data)
1030 data = repo._repo.wwritedata(name, data)
1033
1031
1034 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1032 archiver.addfile(prefix + repo._path + '/' + name, mode, islink, data)
1035
1033
1036 for f in ctx:
1034 for f in ctx:
1037 ff = ctx.flags(f)
1035 ff = ctx.flags(f)
1038 getdata = ctx[f].data
1036 getdata = ctx[f].data
1039 lfile = lfutil.splitstandin(f)
1037 lfile = lfutil.splitstandin(f)
1040 if lfile is not None:
1038 if lfile is not None:
1041 if ctx.node() is not None:
1039 if ctx.node() is not None:
1042 path = lfutil.findfile(repo._repo, getdata().strip())
1040 path = lfutil.findfile(repo._repo, getdata().strip())
1043
1041
1044 if path is None:
1042 if path is None:
1045 raise error.Abort(
1043 raise error.Abort(
1046 _('largefile %s not found in repo store or system cache')
1044 _('largefile %s not found in repo store or system cache')
1047 % lfile)
1045 % lfile)
1048 else:
1046 else:
1049 path = lfile
1047 path = lfile
1050
1048
1051 f = lfile
1049 f = lfile
1052
1050
1053 getdata = lambda: util.readfile(os.path.join(prefix, path))
1051 getdata = lambda: util.readfile(os.path.join(prefix, path))
1054
1052
1055 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1053 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1056
1054
1057 for subpath in sorted(ctx.substate):
1055 for subpath in sorted(ctx.substate):
1058 sub = ctx.workingsub(subpath)
1056 sub = ctx.workingsub(subpath)
1059 submatch = matchmod.subdirmatcher(subpath, match)
1057 submatch = matchmod.subdirmatcher(subpath, match)
1060 sub._repo.lfstatus = True
1058 sub._repo.lfstatus = True
1061 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1059 sub.archive(archiver, prefix + repo._path + '/', submatch, decode)
1062
1060
1063 # If a largefile is modified, the change is not reflected in its
1061 # If a largefile is modified, the change is not reflected in its
1064 # standin until a commit. cmdutil.bailifchanged() raises an exception
1062 # standin until a commit. cmdutil.bailifchanged() raises an exception
1065 # if the repo has uncommitted changes. Wrap it to also check if
1063 # if the repo has uncommitted changes. Wrap it to also check if
1066 # largefiles were changed. This is used by bisect, backout and fetch.
1064 # largefiles were changed. This is used by bisect, backout and fetch.
1067 def overridebailifchanged(orig, repo, *args, **kwargs):
1065 def overridebailifchanged(orig, repo, *args, **kwargs):
1068 orig(repo, *args, **kwargs)
1066 orig(repo, *args, **kwargs)
1069 repo.lfstatus = True
1067 repo.lfstatus = True
1070 s = repo.status()
1068 s = repo.status()
1071 repo.lfstatus = False
1069 repo.lfstatus = False
1072 if s.modified or s.added or s.removed or s.deleted:
1070 if s.modified or s.added or s.removed or s.deleted:
1073 raise error.Abort(_('uncommitted changes'))
1071 raise error.Abort(_('uncommitted changes'))
1074
1072
1075 def postcommitstatus(orig, repo, *args, **kwargs):
1073 def postcommitstatus(orig, repo, *args, **kwargs):
1076 repo.lfstatus = True
1074 repo.lfstatus = True
1077 try:
1075 try:
1078 return orig(repo, *args, **kwargs)
1076 return orig(repo, *args, **kwargs)
1079 finally:
1077 finally:
1080 repo.lfstatus = False
1078 repo.lfstatus = False
1081
1079
1082 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly):
1080 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly):
1083 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1081 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1084 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly)
1082 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly)
1085 m = composelargefilematcher(match, repo[None].manifest())
1083 m = composelargefilematcher(match, repo[None].manifest())
1086
1084
1087 try:
1085 try:
1088 repo.lfstatus = True
1086 repo.lfstatus = True
1089 s = repo.status(match=m, clean=True)
1087 s = repo.status(match=m, clean=True)
1090 finally:
1088 finally:
1091 repo.lfstatus = False
1089 repo.lfstatus = False
1092 manifest = repo[None].manifest()
1090 manifest = repo[None].manifest()
1093 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1091 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1094 forget = [f for f in forget if lfutil.standin(f) in manifest]
1092 forget = [f for f in forget if lfutil.standin(f) in manifest]
1095
1093
1096 for f in forget:
1094 for f in forget:
1097 fstandin = lfutil.standin(f)
1095 fstandin = lfutil.standin(f)
1098 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1096 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1099 ui.warn(_('not removing %s: file is already untracked\n')
1097 ui.warn(_('not removing %s: file is already untracked\n')
1100 % m.rel(f))
1098 % m.rel(f))
1101 bad.append(f)
1099 bad.append(f)
1102
1100
1103 for f in forget:
1101 for f in forget:
1104 if ui.verbose or not m.exact(f):
1102 if ui.verbose or not m.exact(f):
1105 ui.status(_('removing %s\n') % m.rel(f))
1103 ui.status(_('removing %s\n') % m.rel(f))
1106
1104
1107 # Need to lock because standin files are deleted then removed from the
1105 # Need to lock because standin files are deleted then removed from the
1108 # repository and we could race in-between.
1106 # repository and we could race in-between.
1109 with repo.wlock():
1107 with repo.wlock():
1110 lfdirstate = lfutil.openlfdirstate(ui, repo)
1108 lfdirstate = lfutil.openlfdirstate(ui, repo)
1111 for f in forget:
1109 for f in forget:
1112 if lfdirstate[f] == 'a':
1110 if lfdirstate[f] == 'a':
1113 lfdirstate.drop(f)
1111 lfdirstate.drop(f)
1114 else:
1112 else:
1115 lfdirstate.remove(f)
1113 lfdirstate.remove(f)
1116 lfdirstate.write()
1114 lfdirstate.write()
1117 standins = [lfutil.standin(f) for f in forget]
1115 standins = [lfutil.standin(f) for f in forget]
1118 for f in standins:
1116 for f in standins:
1119 repo.wvfs.unlinkpath(f, ignoremissing=True)
1117 repo.wvfs.unlinkpath(f, ignoremissing=True)
1120 rejected = repo[None].forget(standins)
1118 rejected = repo[None].forget(standins)
1121
1119
1122 bad.extend(f for f in rejected if f in m.files())
1120 bad.extend(f for f in rejected if f in m.files())
1123 forgot.extend(f for f in forget if f not in rejected)
1121 forgot.extend(f for f in forget if f not in rejected)
1124 return bad, forgot
1122 return bad, forgot
1125
1123
1126 def _getoutgoings(repo, other, missing, addfunc):
1124 def _getoutgoings(repo, other, missing, addfunc):
1127 """get pairs of filename and largefile hash in outgoing revisions
1125 """get pairs of filename and largefile hash in outgoing revisions
1128 in 'missing'.
1126 in 'missing'.
1129
1127
1130 largefiles already existing on 'other' repository are ignored.
1128 largefiles already existing on 'other' repository are ignored.
1131
1129
1132 'addfunc' is invoked with each unique pairs of filename and
1130 'addfunc' is invoked with each unique pairs of filename and
1133 largefile hash value.
1131 largefile hash value.
1134 """
1132 """
1135 knowns = set()
1133 knowns = set()
1136 lfhashes = set()
1134 lfhashes = set()
1137 def dedup(fn, lfhash):
1135 def dedup(fn, lfhash):
1138 k = (fn, lfhash)
1136 k = (fn, lfhash)
1139 if k not in knowns:
1137 if k not in knowns:
1140 knowns.add(k)
1138 knowns.add(k)
1141 lfhashes.add(lfhash)
1139 lfhashes.add(lfhash)
1142 lfutil.getlfilestoupload(repo, missing, dedup)
1140 lfutil.getlfilestoupload(repo, missing, dedup)
1143 if lfhashes:
1141 if lfhashes:
1144 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1142 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1145 for fn, lfhash in knowns:
1143 for fn, lfhash in knowns:
1146 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1144 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1147 addfunc(fn, lfhash)
1145 addfunc(fn, lfhash)
1148
1146
1149 def outgoinghook(ui, repo, other, opts, missing):
1147 def outgoinghook(ui, repo, other, opts, missing):
1150 if opts.pop('large', None):
1148 if opts.pop('large', None):
1151 lfhashes = set()
1149 lfhashes = set()
1152 if ui.debugflag:
1150 if ui.debugflag:
1153 toupload = {}
1151 toupload = {}
1154 def addfunc(fn, lfhash):
1152 def addfunc(fn, lfhash):
1155 if fn not in toupload:
1153 if fn not in toupload:
1156 toupload[fn] = []
1154 toupload[fn] = []
1157 toupload[fn].append(lfhash)
1155 toupload[fn].append(lfhash)
1158 lfhashes.add(lfhash)
1156 lfhashes.add(lfhash)
1159 def showhashes(fn):
1157 def showhashes(fn):
1160 for lfhash in sorted(toupload[fn]):
1158 for lfhash in sorted(toupload[fn]):
1161 ui.debug(' %s\n' % (lfhash))
1159 ui.debug(' %s\n' % (lfhash))
1162 else:
1160 else:
1163 toupload = set()
1161 toupload = set()
1164 def addfunc(fn, lfhash):
1162 def addfunc(fn, lfhash):
1165 toupload.add(fn)
1163 toupload.add(fn)
1166 lfhashes.add(lfhash)
1164 lfhashes.add(lfhash)
1167 def showhashes(fn):
1165 def showhashes(fn):
1168 pass
1166 pass
1169 _getoutgoings(repo, other, missing, addfunc)
1167 _getoutgoings(repo, other, missing, addfunc)
1170
1168
1171 if not toupload:
1169 if not toupload:
1172 ui.status(_('largefiles: no files to upload\n'))
1170 ui.status(_('largefiles: no files to upload\n'))
1173 else:
1171 else:
1174 ui.status(_('largefiles to upload (%d entities):\n')
1172 ui.status(_('largefiles to upload (%d entities):\n')
1175 % (len(lfhashes)))
1173 % (len(lfhashes)))
1176 for file in sorted(toupload):
1174 for file in sorted(toupload):
1177 ui.status(lfutil.splitstandin(file) + '\n')
1175 ui.status(lfutil.splitstandin(file) + '\n')
1178 showhashes(file)
1176 showhashes(file)
1179 ui.status('\n')
1177 ui.status('\n')
1180
1178
1181 def summaryremotehook(ui, repo, opts, changes):
1179 def summaryremotehook(ui, repo, opts, changes):
1182 largeopt = opts.get('large', False)
1180 largeopt = opts.get('large', False)
1183 if changes is None:
1181 if changes is None:
1184 if largeopt:
1182 if largeopt:
1185 return (False, True) # only outgoing check is needed
1183 return (False, True) # only outgoing check is needed
1186 else:
1184 else:
1187 return (False, False)
1185 return (False, False)
1188 elif largeopt:
1186 elif largeopt:
1189 url, branch, peer, outgoing = changes[1]
1187 url, branch, peer, outgoing = changes[1]
1190 if peer is None:
1188 if peer is None:
1191 # i18n: column positioning for "hg summary"
1189 # i18n: column positioning for "hg summary"
1192 ui.status(_('largefiles: (no remote repo)\n'))
1190 ui.status(_('largefiles: (no remote repo)\n'))
1193 return
1191 return
1194
1192
1195 toupload = set()
1193 toupload = set()
1196 lfhashes = set()
1194 lfhashes = set()
1197 def addfunc(fn, lfhash):
1195 def addfunc(fn, lfhash):
1198 toupload.add(fn)
1196 toupload.add(fn)
1199 lfhashes.add(lfhash)
1197 lfhashes.add(lfhash)
1200 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1198 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1201
1199
1202 if not toupload:
1200 if not toupload:
1203 # i18n: column positioning for "hg summary"
1201 # i18n: column positioning for "hg summary"
1204 ui.status(_('largefiles: (no files to upload)\n'))
1202 ui.status(_('largefiles: (no files to upload)\n'))
1205 else:
1203 else:
1206 # i18n: column positioning for "hg summary"
1204 # i18n: column positioning for "hg summary"
1207 ui.status(_('largefiles: %d entities for %d files to upload\n')
1205 ui.status(_('largefiles: %d entities for %d files to upload\n')
1208 % (len(lfhashes), len(toupload)))
1206 % (len(lfhashes), len(toupload)))
1209
1207
1210 def overridesummary(orig, ui, repo, *pats, **opts):
1208 def overridesummary(orig, ui, repo, *pats, **opts):
1211 try:
1209 try:
1212 repo.lfstatus = True
1210 repo.lfstatus = True
1213 orig(ui, repo, *pats, **opts)
1211 orig(ui, repo, *pats, **opts)
1214 finally:
1212 finally:
1215 repo.lfstatus = False
1213 repo.lfstatus = False
1216
1214
1217 def scmutiladdremove(orig, repo, matcher, prefix, opts=None, dry_run=None,
1215 def scmutiladdremove(orig, repo, matcher, prefix, opts=None, dry_run=None,
1218 similarity=None):
1216 similarity=None):
1219 if opts is None:
1217 if opts is None:
1220 opts = {}
1218 opts = {}
1221 if not lfutil.islfilesrepo(repo):
1219 if not lfutil.islfilesrepo(repo):
1222 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1220 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1223 # Get the list of missing largefiles so we can remove them
1221 # Get the list of missing largefiles so we can remove them
1224 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1222 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1225 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1223 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1226 subrepos=[], ignored=False, clean=False,
1224 subrepos=[], ignored=False, clean=False,
1227 unknown=False)
1225 unknown=False)
1228
1226
1229 # Call into the normal remove code, but the removing of the standin, we want
1227 # Call into the normal remove code, but the removing of the standin, we want
1230 # to have handled by original addremove. Monkey patching here makes sure
1228 # to have handled by original addremove. Monkey patching here makes sure
1231 # we don't remove the standin in the largefiles code, preventing a very
1229 # we don't remove the standin in the largefiles code, preventing a very
1232 # confused state later.
1230 # confused state later.
1233 if s.deleted:
1231 if s.deleted:
1234 m = copy.copy(matcher)
1232 m = copy.copy(matcher)
1235
1233
1236 # The m._files and m._map attributes are not changed to the deleted list
1234 # The m._files and m._map attributes are not changed to the deleted list
1237 # because that affects the m.exact() test, which in turn governs whether
1235 # because that affects the m.exact() test, which in turn governs whether
1238 # or not the file name is printed, and how. Simply limit the original
1236 # or not the file name is printed, and how. Simply limit the original
1239 # matches to those in the deleted status list.
1237 # matches to those in the deleted status list.
1240 matchfn = m.matchfn
1238 matchfn = m.matchfn
1241 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1239 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1242
1240
1243 removelargefiles(repo.ui, repo, True, m, **opts)
1241 removelargefiles(repo.ui, repo, True, m, **opts)
1244 # Call into the normal add code, and any files that *should* be added as
1242 # Call into the normal add code, and any files that *should* be added as
1245 # largefiles will be
1243 # largefiles will be
1246 added, bad = addlargefiles(repo.ui, repo, True, matcher, **opts)
1244 added, bad = addlargefiles(repo.ui, repo, True, matcher, **opts)
1247 # Now that we've handled largefiles, hand off to the original addremove
1245 # Now that we've handled largefiles, hand off to the original addremove
1248 # function to take care of the rest. Make sure it doesn't do anything with
1246 # function to take care of the rest. Make sure it doesn't do anything with
1249 # largefiles by passing a matcher that will ignore them.
1247 # largefiles by passing a matcher that will ignore them.
1250 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1248 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1251 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1249 return orig(repo, matcher, prefix, opts, dry_run, similarity)
1252
1250
1253 # Calling purge with --all will cause the largefiles to be deleted.
1251 # Calling purge with --all will cause the largefiles to be deleted.
1254 # Override repo.status to prevent this from happening.
1252 # Override repo.status to prevent this from happening.
1255 def overridepurge(orig, ui, repo, *dirs, **opts):
1253 def overridepurge(orig, ui, repo, *dirs, **opts):
1256 # XXX Monkey patching a repoview will not work. The assigned attribute will
1254 # XXX Monkey patching a repoview will not work. The assigned attribute will
1257 # be set on the unfiltered repo, but we will only lookup attributes in the
1255 # be set on the unfiltered repo, but we will only lookup attributes in the
1258 # unfiltered repo if the lookup in the repoview object itself fails. As the
1256 # unfiltered repo if the lookup in the repoview object itself fails. As the
1259 # monkey patched method exists on the repoview class the lookup will not
1257 # monkey patched method exists on the repoview class the lookup will not
1260 # fail. As a result, the original version will shadow the monkey patched
1258 # fail. As a result, the original version will shadow the monkey patched
1261 # one, defeating the monkey patch.
1259 # one, defeating the monkey patch.
1262 #
1260 #
1263 # As a work around we use an unfiltered repo here. We should do something
1261 # As a work around we use an unfiltered repo here. We should do something
1264 # cleaner instead.
1262 # cleaner instead.
1265 repo = repo.unfiltered()
1263 repo = repo.unfiltered()
1266 oldstatus = repo.status
1264 oldstatus = repo.status
1267 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1265 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1268 clean=False, unknown=False, listsubrepos=False):
1266 clean=False, unknown=False, listsubrepos=False):
1269 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1267 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1270 listsubrepos)
1268 listsubrepos)
1271 lfdirstate = lfutil.openlfdirstate(ui, repo)
1269 lfdirstate = lfutil.openlfdirstate(ui, repo)
1272 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1270 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1273 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1271 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1274 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1272 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1275 unknown, ignored, r.clean)
1273 unknown, ignored, r.clean)
1276 repo.status = overridestatus
1274 repo.status = overridestatus
1277 orig(ui, repo, *dirs, **opts)
1275 orig(ui, repo, *dirs, **opts)
1278 repo.status = oldstatus
1276 repo.status = oldstatus
1279
1277
1280 def overriderollback(orig, ui, repo, **opts):
1278 def overriderollback(orig, ui, repo, **opts):
1281 with repo.wlock():
1279 with repo.wlock():
1282 before = repo.dirstate.parents()
1280 before = repo.dirstate.parents()
1283 orphans = set(f for f in repo.dirstate
1281 orphans = set(f for f in repo.dirstate
1284 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1282 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1285 result = orig(ui, repo, **opts)
1283 result = orig(ui, repo, **opts)
1286 after = repo.dirstate.parents()
1284 after = repo.dirstate.parents()
1287 if before == after:
1285 if before == after:
1288 return result # no need to restore standins
1286 return result # no need to restore standins
1289
1287
1290 pctx = repo['.']
1288 pctx = repo['.']
1291 for f in repo.dirstate:
1289 for f in repo.dirstate:
1292 if lfutil.isstandin(f):
1290 if lfutil.isstandin(f):
1293 orphans.discard(f)
1291 orphans.discard(f)
1294 if repo.dirstate[f] == 'r':
1292 if repo.dirstate[f] == 'r':
1295 repo.wvfs.unlinkpath(f, ignoremissing=True)
1293 repo.wvfs.unlinkpath(f, ignoremissing=True)
1296 elif f in pctx:
1294 elif f in pctx:
1297 fctx = pctx[f]
1295 fctx = pctx[f]
1298 repo.wwrite(f, fctx.data(), fctx.flags())
1296 repo.wwrite(f, fctx.data(), fctx.flags())
1299 else:
1297 else:
1300 # content of standin is not so important in 'a',
1298 # content of standin is not so important in 'a',
1301 # 'm' or 'n' (coming from the 2nd parent) cases
1299 # 'm' or 'n' (coming from the 2nd parent) cases
1302 lfutil.writestandin(repo, f, '', False)
1300 lfutil.writestandin(repo, f, '', False)
1303 for standin in orphans:
1301 for standin in orphans:
1304 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1302 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1305
1303
1306 lfdirstate = lfutil.openlfdirstate(ui, repo)
1304 lfdirstate = lfutil.openlfdirstate(ui, repo)
1307 orphans = set(lfdirstate)
1305 orphans = set(lfdirstate)
1308 lfiles = lfutil.listlfiles(repo)
1306 lfiles = lfutil.listlfiles(repo)
1309 for file in lfiles:
1307 for file in lfiles:
1310 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1308 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1311 orphans.discard(file)
1309 orphans.discard(file)
1312 for lfile in orphans:
1310 for lfile in orphans:
1313 lfdirstate.drop(lfile)
1311 lfdirstate.drop(lfile)
1314 lfdirstate.write()
1312 lfdirstate.write()
1315 return result
1313 return result
1316
1314
1317 def overridetransplant(orig, ui, repo, *revs, **opts):
1315 def overridetransplant(orig, ui, repo, *revs, **opts):
1318 resuming = opts.get(r'continue')
1316 resuming = opts.get(r'continue')
1319 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1317 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1320 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1318 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1321 try:
1319 try:
1322 result = orig(ui, repo, *revs, **opts)
1320 result = orig(ui, repo, *revs, **opts)
1323 finally:
1321 finally:
1324 repo._lfstatuswriters.pop()
1322 repo._lfstatuswriters.pop()
1325 repo._lfcommithooks.pop()
1323 repo._lfcommithooks.pop()
1326 return result
1324 return result
1327
1325
1328 def overridecat(orig, ui, repo, file1, *pats, **opts):
1326 def overridecat(orig, ui, repo, file1, *pats, **opts):
1329 opts = pycompat.byteskwargs(opts)
1327 opts = pycompat.byteskwargs(opts)
1330 ctx = scmutil.revsingle(repo, opts.get('rev'))
1328 ctx = scmutil.revsingle(repo, opts.get('rev'))
1331 err = 1
1329 err = 1
1332 notbad = set()
1330 notbad = set()
1333 m = scmutil.match(ctx, (file1,) + pats, opts)
1331 m = scmutil.match(ctx, (file1,) + pats, opts)
1334 origmatchfn = m.matchfn
1332 origmatchfn = m.matchfn
1335 def lfmatchfn(f):
1333 def lfmatchfn(f):
1336 if origmatchfn(f):
1334 if origmatchfn(f):
1337 return True
1335 return True
1338 lf = lfutil.splitstandin(f)
1336 lf = lfutil.splitstandin(f)
1339 if lf is None:
1337 if lf is None:
1340 return False
1338 return False
1341 notbad.add(lf)
1339 notbad.add(lf)
1342 return origmatchfn(lf)
1340 return origmatchfn(lf)
1343 m.matchfn = lfmatchfn
1341 m.matchfn = lfmatchfn
1344 origbadfn = m.bad
1342 origbadfn = m.bad
1345 def lfbadfn(f, msg):
1343 def lfbadfn(f, msg):
1346 if not f in notbad:
1344 if not f in notbad:
1347 origbadfn(f, msg)
1345 origbadfn(f, msg)
1348 m.bad = lfbadfn
1346 m.bad = lfbadfn
1349
1347
1350 origvisitdirfn = m.visitdir
1348 origvisitdirfn = m.visitdir
1351 def lfvisitdirfn(dir):
1349 def lfvisitdirfn(dir):
1352 if dir == lfutil.shortname:
1350 if dir == lfutil.shortname:
1353 return True
1351 return True
1354 ret = origvisitdirfn(dir)
1352 ret = origvisitdirfn(dir)
1355 if ret:
1353 if ret:
1356 return ret
1354 return ret
1357 lf = lfutil.splitstandin(dir)
1355 lf = lfutil.splitstandin(dir)
1358 if lf is None:
1356 if lf is None:
1359 return False
1357 return False
1360 return origvisitdirfn(lf)
1358 return origvisitdirfn(lf)
1361 m.visitdir = lfvisitdirfn
1359 m.visitdir = lfvisitdirfn
1362
1360
1363 for f in ctx.walk(m):
1361 for f in ctx.walk(m):
1364 with cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1362 with cmdutil.makefileobj(repo, opts.get('output'), ctx.node(),
1365 pathname=f) as fp:
1363 pathname=f) as fp:
1366 lf = lfutil.splitstandin(f)
1364 lf = lfutil.splitstandin(f)
1367 if lf is None or origmatchfn(f):
1365 if lf is None or origmatchfn(f):
1368 # duplicating unreachable code from commands.cat
1366 # duplicating unreachable code from commands.cat
1369 data = ctx[f].data()
1367 data = ctx[f].data()
1370 if opts.get('decode'):
1368 if opts.get('decode'):
1371 data = repo.wwritedata(f, data)
1369 data = repo.wwritedata(f, data)
1372 fp.write(data)
1370 fp.write(data)
1373 else:
1371 else:
1374 hash = lfutil.readasstandin(ctx[f])
1372 hash = lfutil.readasstandin(ctx[f])
1375 if not lfutil.inusercache(repo.ui, hash):
1373 if not lfutil.inusercache(repo.ui, hash):
1376 store = storefactory.openstore(repo)
1374 store = storefactory.openstore(repo)
1377 success, missing = store.get([(lf, hash)])
1375 success, missing = store.get([(lf, hash)])
1378 if len(success) != 1:
1376 if len(success) != 1:
1379 raise error.Abort(
1377 raise error.Abort(
1380 _('largefile %s is not in cache and could not be '
1378 _('largefile %s is not in cache and could not be '
1381 'downloaded') % lf)
1379 'downloaded') % lf)
1382 path = lfutil.usercachepath(repo.ui, hash)
1380 path = lfutil.usercachepath(repo.ui, hash)
1383 with open(path, "rb") as fpin:
1381 with open(path, "rb") as fpin:
1384 for chunk in util.filechunkiter(fpin):
1382 for chunk in util.filechunkiter(fpin):
1385 fp.write(chunk)
1383 fp.write(chunk)
1386 err = 0
1384 err = 0
1387 return err
1385 return err
1388
1386
1389 def mergeupdate(orig, repo, node, branchmerge, force,
1387 def mergeupdate(orig, repo, node, branchmerge, force,
1390 *args, **kwargs):
1388 *args, **kwargs):
1391 matcher = kwargs.get(r'matcher', None)
1389 matcher = kwargs.get(r'matcher', None)
1392 # note if this is a partial update
1390 # note if this is a partial update
1393 partial = matcher and not matcher.always()
1391 partial = matcher and not matcher.always()
1394 with repo.wlock():
1392 with repo.wlock():
1395 # branch | | |
1393 # branch | | |
1396 # merge | force | partial | action
1394 # merge | force | partial | action
1397 # -------+-------+---------+--------------
1395 # -------+-------+---------+--------------
1398 # x | x | x | linear-merge
1396 # x | x | x | linear-merge
1399 # o | x | x | branch-merge
1397 # o | x | x | branch-merge
1400 # x | o | x | overwrite (as clean update)
1398 # x | o | x | overwrite (as clean update)
1401 # o | o | x | force-branch-merge (*1)
1399 # o | o | x | force-branch-merge (*1)
1402 # x | x | o | (*)
1400 # x | x | o | (*)
1403 # o | x | o | (*)
1401 # o | x | o | (*)
1404 # x | o | o | overwrite (as revert)
1402 # x | o | o | overwrite (as revert)
1405 # o | o | o | (*)
1403 # o | o | o | (*)
1406 #
1404 #
1407 # (*) don't care
1405 # (*) don't care
1408 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1406 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1409
1407
1410 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1408 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1411 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1409 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1412 repo.getcwd()),
1410 repo.getcwd()),
1413 subrepos=[], ignored=False,
1411 subrepos=[], ignored=False,
1414 clean=True, unknown=False)
1412 clean=True, unknown=False)
1415 oldclean = set(s.clean)
1413 oldclean = set(s.clean)
1416 pctx = repo['.']
1414 pctx = repo['.']
1417 dctx = repo[node]
1415 dctx = repo[node]
1418 for lfile in unsure + s.modified:
1416 for lfile in unsure + s.modified:
1419 lfileabs = repo.wvfs.join(lfile)
1417 lfileabs = repo.wvfs.join(lfile)
1420 if not repo.wvfs.exists(lfileabs):
1418 if not repo.wvfs.exists(lfileabs):
1421 continue
1419 continue
1422 lfhash = lfutil.hashfile(lfileabs)
1420 lfhash = lfutil.hashfile(lfileabs)
1423 standin = lfutil.standin(lfile)
1421 standin = lfutil.standin(lfile)
1424 lfutil.writestandin(repo, standin, lfhash,
1422 lfutil.writestandin(repo, standin, lfhash,
1425 lfutil.getexecutable(lfileabs))
1423 lfutil.getexecutable(lfileabs))
1426 if (standin in pctx and
1424 if (standin in pctx and
1427 lfhash == lfutil.readasstandin(pctx[standin])):
1425 lfhash == lfutil.readasstandin(pctx[standin])):
1428 oldclean.add(lfile)
1426 oldclean.add(lfile)
1429 for lfile in s.added:
1427 for lfile in s.added:
1430 fstandin = lfutil.standin(lfile)
1428 fstandin = lfutil.standin(lfile)
1431 if fstandin not in dctx:
1429 if fstandin not in dctx:
1432 # in this case, content of standin file is meaningless
1430 # in this case, content of standin file is meaningless
1433 # (in dctx, lfile is unknown, or normal file)
1431 # (in dctx, lfile is unknown, or normal file)
1434 continue
1432 continue
1435 lfutil.updatestandin(repo, lfile, fstandin)
1433 lfutil.updatestandin(repo, lfile, fstandin)
1436 # mark all clean largefiles as dirty, just in case the update gets
1434 # mark all clean largefiles as dirty, just in case the update gets
1437 # interrupted before largefiles and lfdirstate are synchronized
1435 # interrupted before largefiles and lfdirstate are synchronized
1438 for lfile in oldclean:
1436 for lfile in oldclean:
1439 lfdirstate.normallookup(lfile)
1437 lfdirstate.normallookup(lfile)
1440 lfdirstate.write()
1438 lfdirstate.write()
1441
1439
1442 oldstandins = lfutil.getstandinsstate(repo)
1440 oldstandins = lfutil.getstandinsstate(repo)
1443 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1441 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1444 # good candidate for in-memory merge (large files, custom dirstate,
1442 # good candidate for in-memory merge (large files, custom dirstate,
1445 # matcher usage).
1443 # matcher usage).
1446 kwargs[r'wc'] = repo[None]
1444 kwargs[r'wc'] = repo[None]
1447 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1445 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1448
1446
1449 newstandins = lfutil.getstandinsstate(repo)
1447 newstandins = lfutil.getstandinsstate(repo)
1450 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1448 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1451
1449
1452 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1450 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1453 # all the ones that didn't change as clean
1451 # all the ones that didn't change as clean
1454 for lfile in oldclean.difference(filelist):
1452 for lfile in oldclean.difference(filelist):
1455 lfdirstate.normal(lfile)
1453 lfdirstate.normal(lfile)
1456 lfdirstate.write()
1454 lfdirstate.write()
1457
1455
1458 if branchmerge or force or partial:
1456 if branchmerge or force or partial:
1459 filelist.extend(s.deleted + s.removed)
1457 filelist.extend(s.deleted + s.removed)
1460
1458
1461 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1459 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1462 normallookup=partial)
1460 normallookup=partial)
1463
1461
1464 return result
1462 return result
1465
1463
1466 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1464 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1467 result = orig(repo, files, *args, **kwargs)
1465 result = orig(repo, files, *args, **kwargs)
1468
1466
1469 filelist = []
1467 filelist = []
1470 for f in files:
1468 for f in files:
1471 lf = lfutil.splitstandin(f)
1469 lf = lfutil.splitstandin(f)
1472 if lf is not None:
1470 if lf is not None:
1473 filelist.append(lf)
1471 filelist.append(lf)
1474 if filelist:
1472 if filelist:
1475 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1473 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1476 printmessage=False, normallookup=True)
1474 printmessage=False, normallookup=True)
1477
1475
1478 return result
1476 return result
1479
1477
1480 def upgraderequirements(orig, repo):
1478 def upgraderequirements(orig, repo):
1481 reqs = orig(repo)
1479 reqs = orig(repo)
1482 if 'largefiles' in repo.requirements:
1480 if 'largefiles' in repo.requirements:
1483 reqs.add('largefiles')
1481 reqs.add('largefiles')
1484 return reqs
1482 return reqs
1485
1483
1486 _lfscheme = 'largefile://'
1484 _lfscheme = 'largefile://'
1487 def openlargefile(orig, ui, url_, data=None):
1485 def openlargefile(orig, ui, url_, data=None):
1488 if url_.startswith(_lfscheme):
1486 if url_.startswith(_lfscheme):
1489 if data:
1487 if data:
1490 msg = "cannot use data on a 'largefile://' url"
1488 msg = "cannot use data on a 'largefile://' url"
1491 raise error.ProgrammingError(msg)
1489 raise error.ProgrammingError(msg)
1492 lfid = url_[len(_lfscheme):]
1490 lfid = url_[len(_lfscheme):]
1493 return storefactory.getlfile(ui, lfid)
1491 return storefactory.getlfile(ui, lfid)
1494 else:
1492 else:
1495 return orig(ui, url_, data=data)
1493 return orig(ui, url_, data=data)
@@ -1,339 +1,339 b''
1 # sparse.py - allow sparse checkouts of the working directory
1 # sparse.py - allow sparse checkouts of the working directory
2 #
2 #
3 # Copyright 2014 Facebook, Inc.
3 # Copyright 2014 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """allow sparse checkouts of the working directory (EXPERIMENTAL)
8 """allow sparse checkouts of the working directory (EXPERIMENTAL)
9
9
10 (This extension is not yet protected by backwards compatibility
10 (This extension is not yet protected by backwards compatibility
11 guarantees. Any aspect may break in future releases until this
11 guarantees. Any aspect may break in future releases until this
12 notice is removed.)
12 notice is removed.)
13
13
14 This extension allows the working directory to only consist of a
14 This extension allows the working directory to only consist of a
15 subset of files for the revision. This allows specific files or
15 subset of files for the revision. This allows specific files or
16 directories to be explicitly included or excluded. Many repository
16 directories to be explicitly included or excluded. Many repository
17 operations have performance proportional to the number of files in
17 operations have performance proportional to the number of files in
18 the working directory. So only realizing a subset of files in the
18 the working directory. So only realizing a subset of files in the
19 working directory can improve performance.
19 working directory can improve performance.
20
20
21 Sparse Config Files
21 Sparse Config Files
22 -------------------
22 -------------------
23
23
24 The set of files that are part of a sparse checkout are defined by
24 The set of files that are part of a sparse checkout are defined by
25 a sparse config file. The file defines 3 things: includes (files to
25 a sparse config file. The file defines 3 things: includes (files to
26 include in the sparse checkout), excludes (files to exclude from the
26 include in the sparse checkout), excludes (files to exclude from the
27 sparse checkout), and profiles (links to other config files).
27 sparse checkout), and profiles (links to other config files).
28
28
29 The file format is newline delimited. Empty lines and lines beginning
29 The file format is newline delimited. Empty lines and lines beginning
30 with ``#`` are ignored.
30 with ``#`` are ignored.
31
31
32 Lines beginning with ``%include `` denote another sparse config file
32 Lines beginning with ``%include `` denote another sparse config file
33 to include. e.g. ``%include tests.sparse``. The filename is relative
33 to include. e.g. ``%include tests.sparse``. The filename is relative
34 to the repository root.
34 to the repository root.
35
35
36 The special lines ``[include]`` and ``[exclude]`` denote the section
36 The special lines ``[include]`` and ``[exclude]`` denote the section
37 for includes and excludes that follow, respectively. It is illegal to
37 for includes and excludes that follow, respectively. It is illegal to
38 have ``[include]`` after ``[exclude]``.
38 have ``[include]`` after ``[exclude]``.
39
39
40 Non-special lines resemble file patterns to be added to either includes
40 Non-special lines resemble file patterns to be added to either includes
41 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
41 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
42 Patterns are interpreted as ``glob:`` by default and match against the
42 Patterns are interpreted as ``glob:`` by default and match against the
43 root of the repository.
43 root of the repository.
44
44
45 Exclusion patterns take precedence over inclusion patterns. So even
45 Exclusion patterns take precedence over inclusion patterns. So even
46 if a file is explicitly included, an ``[exclude]`` entry can remove it.
46 if a file is explicitly included, an ``[exclude]`` entry can remove it.
47
47
48 For example, say you have a repository with 3 directories, ``frontend/``,
48 For example, say you have a repository with 3 directories, ``frontend/``,
49 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
49 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
50 to different projects and it is uncommon for someone working on one
50 to different projects and it is uncommon for someone working on one
51 to need the files for the other. But ``tools/`` contains files shared
51 to need the files for the other. But ``tools/`` contains files shared
52 between both projects. Your sparse config files may resemble::
52 between both projects. Your sparse config files may resemble::
53
53
54 # frontend.sparse
54 # frontend.sparse
55 frontend/**
55 frontend/**
56 tools/**
56 tools/**
57
57
58 # backend.sparse
58 # backend.sparse
59 backend/**
59 backend/**
60 tools/**
60 tools/**
61
61
62 Say the backend grows in size. Or there's a directory with thousands
62 Say the backend grows in size. Or there's a directory with thousands
63 of files you wish to exclude. You can modify the profile to exclude
63 of files you wish to exclude. You can modify the profile to exclude
64 certain files::
64 certain files::
65
65
66 [include]
66 [include]
67 backend/**
67 backend/**
68 tools/**
68 tools/**
69
69
70 [exclude]
70 [exclude]
71 tools/tests/**
71 tools/tests/**
72 """
72 """
73
73
74 from __future__ import absolute_import
74 from __future__ import absolute_import
75
75
76 from mercurial.i18n import _
76 from mercurial.i18n import _
77 from mercurial import (
77 from mercurial import (
78 commands,
78 commands,
79 dirstate,
79 dirstate,
80 error,
80 error,
81 extensions,
81 extensions,
82 hg,
82 hg,
83 logcmdutil,
83 logcmdutil,
84 match as matchmod,
84 match as matchmod,
85 pycompat,
85 pycompat,
86 registrar,
86 registrar,
87 sparse,
87 sparse,
88 util,
88 util,
89 )
89 )
90
90
91 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
91 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
92 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
92 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
93 # be specifying the version(s) of Mercurial they are tested with, or
93 # be specifying the version(s) of Mercurial they are tested with, or
94 # leave the attribute unspecified.
94 # leave the attribute unspecified.
95 testedwith = 'ships-with-hg-core'
95 testedwith = 'ships-with-hg-core'
96
96
97 cmdtable = {}
97 cmdtable = {}
98 command = registrar.command(cmdtable)
98 command = registrar.command(cmdtable)
99
99
100 def extsetup(ui):
100 def extsetup(ui):
101 sparse.enabled = True
101 sparse.enabled = True
102
102
103 _setupclone(ui)
103 _setupclone(ui)
104 _setuplog(ui)
104 _setuplog(ui)
105 _setupadd(ui)
105 _setupadd(ui)
106 _setupdirstate(ui)
106 _setupdirstate(ui)
107
107
108 def replacefilecache(cls, propname, replacement):
108 def replacefilecache(cls, propname, replacement):
109 """Replace a filecache property with a new class. This allows changing the
109 """Replace a filecache property with a new class. This allows changing the
110 cache invalidation condition."""
110 cache invalidation condition."""
111 origcls = cls
111 origcls = cls
112 assert callable(replacement)
112 assert callable(replacement)
113 while cls is not object:
113 while cls is not object:
114 if propname in cls.__dict__:
114 if propname in cls.__dict__:
115 orig = cls.__dict__[propname]
115 orig = cls.__dict__[propname]
116 setattr(cls, propname, replacement(orig))
116 setattr(cls, propname, replacement(orig))
117 break
117 break
118 cls = cls.__bases__[0]
118 cls = cls.__bases__[0]
119
119
120 if cls is object:
120 if cls is object:
121 raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
121 raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
122 propname))
122 propname))
123
123
124 def _setuplog(ui):
124 def _setuplog(ui):
125 entry = commands.table['^log|history']
125 entry = commands.table['^log|history']
126 entry[1].append(('', 'sparse', None,
126 entry[1].append(('', 'sparse', None,
127 "limit to changesets affecting the sparse checkout"))
127 "limit to changesets affecting the sparse checkout"))
128
128
129 def _logrevs(orig, repo, opts):
129 def _initialrevs(orig, repo, opts):
130 revs = orig(repo, opts)
130 revs = orig(repo, opts)
131 if opts.get('sparse'):
131 if opts.get('sparse'):
132 sparsematch = sparse.matcher(repo)
132 sparsematch = sparse.matcher(repo)
133 def ctxmatch(rev):
133 def ctxmatch(rev):
134 ctx = repo[rev]
134 ctx = repo[rev]
135 return any(f for f in ctx.files() if sparsematch(f))
135 return any(f for f in ctx.files() if sparsematch(f))
136 revs = revs.filter(ctxmatch)
136 revs = revs.filter(ctxmatch)
137 return revs
137 return revs
138 extensions.wrapfunction(logcmdutil, '_logrevs', _logrevs)
138 extensions.wrapfunction(logcmdutil, '_initialrevs', _initialrevs)
139
139
140 def _clonesparsecmd(orig, ui, repo, *args, **opts):
140 def _clonesparsecmd(orig, ui, repo, *args, **opts):
141 include_pat = opts.get('include')
141 include_pat = opts.get('include')
142 exclude_pat = opts.get('exclude')
142 exclude_pat = opts.get('exclude')
143 enableprofile_pat = opts.get('enable_profile')
143 enableprofile_pat = opts.get('enable_profile')
144 include = exclude = enableprofile = False
144 include = exclude = enableprofile = False
145 if include_pat:
145 if include_pat:
146 pat = include_pat
146 pat = include_pat
147 include = True
147 include = True
148 if exclude_pat:
148 if exclude_pat:
149 pat = exclude_pat
149 pat = exclude_pat
150 exclude = True
150 exclude = True
151 if enableprofile_pat:
151 if enableprofile_pat:
152 pat = enableprofile_pat
152 pat = enableprofile_pat
153 enableprofile = True
153 enableprofile = True
154 if sum([include, exclude, enableprofile]) > 1:
154 if sum([include, exclude, enableprofile]) > 1:
155 raise error.Abort(_("too many flags specified."))
155 raise error.Abort(_("too many flags specified."))
156 if include or exclude or enableprofile:
156 if include or exclude or enableprofile:
157 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
157 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
158 sparse.updateconfig(self.unfiltered(), pat, {}, include=include,
158 sparse.updateconfig(self.unfiltered(), pat, {}, include=include,
159 exclude=exclude, enableprofile=enableprofile,
159 exclude=exclude, enableprofile=enableprofile,
160 usereporootpaths=True)
160 usereporootpaths=True)
161 return orig(self, node, overwrite, *args, **kwargs)
161 return orig(self, node, overwrite, *args, **kwargs)
162 extensions.wrapfunction(hg, 'updaterepo', clonesparse)
162 extensions.wrapfunction(hg, 'updaterepo', clonesparse)
163 return orig(ui, repo, *args, **opts)
163 return orig(ui, repo, *args, **opts)
164
164
165 def _setupclone(ui):
165 def _setupclone(ui):
166 entry = commands.table['^clone']
166 entry = commands.table['^clone']
167 entry[1].append(('', 'enable-profile', [],
167 entry[1].append(('', 'enable-profile', [],
168 'enable a sparse profile'))
168 'enable a sparse profile'))
169 entry[1].append(('', 'include', [],
169 entry[1].append(('', 'include', [],
170 'include sparse pattern'))
170 'include sparse pattern'))
171 entry[1].append(('', 'exclude', [],
171 entry[1].append(('', 'exclude', [],
172 'exclude sparse pattern'))
172 'exclude sparse pattern'))
173 extensions.wrapcommand(commands.table, 'clone', _clonesparsecmd)
173 extensions.wrapcommand(commands.table, 'clone', _clonesparsecmd)
174
174
175 def _setupadd(ui):
175 def _setupadd(ui):
176 entry = commands.table['^add']
176 entry = commands.table['^add']
177 entry[1].append(('s', 'sparse', None,
177 entry[1].append(('s', 'sparse', None,
178 'also include directories of added files in sparse config'))
178 'also include directories of added files in sparse config'))
179
179
180 def _add(orig, ui, repo, *pats, **opts):
180 def _add(orig, ui, repo, *pats, **opts):
181 if opts.get('sparse'):
181 if opts.get('sparse'):
182 dirs = set()
182 dirs = set()
183 for pat in pats:
183 for pat in pats:
184 dirname, basename = util.split(pat)
184 dirname, basename = util.split(pat)
185 dirs.add(dirname)
185 dirs.add(dirname)
186 sparse.updateconfig(repo, list(dirs), opts, include=True)
186 sparse.updateconfig(repo, list(dirs), opts, include=True)
187 return orig(ui, repo, *pats, **opts)
187 return orig(ui, repo, *pats, **opts)
188
188
189 extensions.wrapcommand(commands.table, 'add', _add)
189 extensions.wrapcommand(commands.table, 'add', _add)
190
190
191 def _setupdirstate(ui):
191 def _setupdirstate(ui):
192 """Modify the dirstate to prevent stat'ing excluded files,
192 """Modify the dirstate to prevent stat'ing excluded files,
193 and to prevent modifications to files outside the checkout.
193 and to prevent modifications to files outside the checkout.
194 """
194 """
195
195
196 def walk(orig, self, match, subrepos, unknown, ignored, full=True):
196 def walk(orig, self, match, subrepos, unknown, ignored, full=True):
197 match = matchmod.intersectmatchers(match, self._sparsematcher)
197 match = matchmod.intersectmatchers(match, self._sparsematcher)
198 return orig(self, match, subrepos, unknown, ignored, full)
198 return orig(self, match, subrepos, unknown, ignored, full)
199
199
200 extensions.wrapfunction(dirstate.dirstate, 'walk', walk)
200 extensions.wrapfunction(dirstate.dirstate, 'walk', walk)
201
201
202 # dirstate.rebuild should not add non-matching files
202 # dirstate.rebuild should not add non-matching files
203 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
203 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
204 matcher = self._sparsematcher
204 matcher = self._sparsematcher
205 if not matcher.always():
205 if not matcher.always():
206 allfiles = allfiles.matches(matcher)
206 allfiles = allfiles.matches(matcher)
207 if changedfiles:
207 if changedfiles:
208 changedfiles = [f for f in changedfiles if matcher(f)]
208 changedfiles = [f for f in changedfiles if matcher(f)]
209
209
210 if changedfiles is not None:
210 if changedfiles is not None:
211 # In _rebuild, these files will be deleted from the dirstate
211 # In _rebuild, these files will be deleted from the dirstate
212 # when they are not found to be in allfiles
212 # when they are not found to be in allfiles
213 dirstatefilestoremove = set(f for f in self if not matcher(f))
213 dirstatefilestoremove = set(f for f in self if not matcher(f))
214 changedfiles = dirstatefilestoremove.union(changedfiles)
214 changedfiles = dirstatefilestoremove.union(changedfiles)
215
215
216 return orig(self, parent, allfiles, changedfiles)
216 return orig(self, parent, allfiles, changedfiles)
217 extensions.wrapfunction(dirstate.dirstate, 'rebuild', _rebuild)
217 extensions.wrapfunction(dirstate.dirstate, 'rebuild', _rebuild)
218
218
219 # Prevent adding files that are outside the sparse checkout
219 # Prevent adding files that are outside the sparse checkout
220 editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
220 editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
221 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
221 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
222 '`hg add -s <file>` to include file directory while adding')
222 '`hg add -s <file>` to include file directory while adding')
223 for func in editfuncs:
223 for func in editfuncs:
224 def _wrapper(orig, self, *args):
224 def _wrapper(orig, self, *args):
225 sparsematch = self._sparsematcher
225 sparsematch = self._sparsematcher
226 if not sparsematch.always():
226 if not sparsematch.always():
227 for f in args:
227 for f in args:
228 if (f is not None and not sparsematch(f) and
228 if (f is not None and not sparsematch(f) and
229 f not in self):
229 f not in self):
230 raise error.Abort(_("cannot add '%s' - it is outside "
230 raise error.Abort(_("cannot add '%s' - it is outside "
231 "the sparse checkout") % f,
231 "the sparse checkout") % f,
232 hint=hint)
232 hint=hint)
233 return orig(self, *args)
233 return orig(self, *args)
234 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
234 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
235
235
236 @command('^debugsparse', [
236 @command('^debugsparse', [
237 ('I', 'include', False, _('include files in the sparse checkout')),
237 ('I', 'include', False, _('include files in the sparse checkout')),
238 ('X', 'exclude', False, _('exclude files in the sparse checkout')),
238 ('X', 'exclude', False, _('exclude files in the sparse checkout')),
239 ('d', 'delete', False, _('delete an include/exclude rule')),
239 ('d', 'delete', False, _('delete an include/exclude rule')),
240 ('f', 'force', False, _('allow changing rules even with pending changes')),
240 ('f', 'force', False, _('allow changing rules even with pending changes')),
241 ('', 'enable-profile', False, _('enables the specified profile')),
241 ('', 'enable-profile', False, _('enables the specified profile')),
242 ('', 'disable-profile', False, _('disables the specified profile')),
242 ('', 'disable-profile', False, _('disables the specified profile')),
243 ('', 'import-rules', False, _('imports rules from a file')),
243 ('', 'import-rules', False, _('imports rules from a file')),
244 ('', 'clear-rules', False, _('clears local include/exclude rules')),
244 ('', 'clear-rules', False, _('clears local include/exclude rules')),
245 ('', 'refresh', False, _('updates the working after sparseness changes')),
245 ('', 'refresh', False, _('updates the working after sparseness changes')),
246 ('', 'reset', False, _('makes the repo full again')),
246 ('', 'reset', False, _('makes the repo full again')),
247 ] + commands.templateopts,
247 ] + commands.templateopts,
248 _('[--OPTION] PATTERN...'))
248 _('[--OPTION] PATTERN...'))
249 def debugsparse(ui, repo, *pats, **opts):
249 def debugsparse(ui, repo, *pats, **opts):
250 """make the current checkout sparse, or edit the existing checkout
250 """make the current checkout sparse, or edit the existing checkout
251
251
252 The sparse command is used to make the current checkout sparse.
252 The sparse command is used to make the current checkout sparse.
253 This means files that don't meet the sparse condition will not be
253 This means files that don't meet the sparse condition will not be
254 written to disk, or show up in any working copy operations. It does
254 written to disk, or show up in any working copy operations. It does
255 not affect files in history in any way.
255 not affect files in history in any way.
256
256
257 Passing no arguments prints the currently applied sparse rules.
257 Passing no arguments prints the currently applied sparse rules.
258
258
259 --include and --exclude are used to add and remove files from the sparse
259 --include and --exclude are used to add and remove files from the sparse
260 checkout. The effects of adding an include or exclude rule are applied
260 checkout. The effects of adding an include or exclude rule are applied
261 immediately. If applying the new rule would cause a file with pending
261 immediately. If applying the new rule would cause a file with pending
262 changes to be added or removed, the command will fail. Pass --force to
262 changes to be added or removed, the command will fail. Pass --force to
263 force a rule change even with pending changes (the changes on disk will
263 force a rule change even with pending changes (the changes on disk will
264 be preserved).
264 be preserved).
265
265
266 --delete removes an existing include/exclude rule. The effects are
266 --delete removes an existing include/exclude rule. The effects are
267 immediate.
267 immediate.
268
268
269 --refresh refreshes the files on disk based on the sparse rules. This is
269 --refresh refreshes the files on disk based on the sparse rules. This is
270 only necessary if .hg/sparse was changed by hand.
270 only necessary if .hg/sparse was changed by hand.
271
271
272 --enable-profile and --disable-profile accept a path to a .hgsparse file.
272 --enable-profile and --disable-profile accept a path to a .hgsparse file.
273 This allows defining sparse checkouts and tracking them inside the
273 This allows defining sparse checkouts and tracking them inside the
274 repository. This is useful for defining commonly used sparse checkouts for
274 repository. This is useful for defining commonly used sparse checkouts for
275 many people to use. As the profile definition changes over time, the sparse
275 many people to use. As the profile definition changes over time, the sparse
276 checkout will automatically be updated appropriately, depending on which
276 checkout will automatically be updated appropriately, depending on which
277 changeset is checked out. Changes to .hgsparse are not applied until they
277 changeset is checked out. Changes to .hgsparse are not applied until they
278 have been committed.
278 have been committed.
279
279
280 --import-rules accepts a path to a file containing rules in the .hgsparse
280 --import-rules accepts a path to a file containing rules in the .hgsparse
281 format, allowing you to add --include, --exclude and --enable-profile rules
281 format, allowing you to add --include, --exclude and --enable-profile rules
282 in bulk. Like the --include, --exclude and --enable-profile switches, the
282 in bulk. Like the --include, --exclude and --enable-profile switches, the
283 changes are applied immediately.
283 changes are applied immediately.
284
284
285 --clear-rules removes all local include and exclude rules, while leaving
285 --clear-rules removes all local include and exclude rules, while leaving
286 any enabled profiles in place.
286 any enabled profiles in place.
287
287
288 Returns 0 if editing the sparse checkout succeeds.
288 Returns 0 if editing the sparse checkout succeeds.
289 """
289 """
290 opts = pycompat.byteskwargs(opts)
290 opts = pycompat.byteskwargs(opts)
291 include = opts.get('include')
291 include = opts.get('include')
292 exclude = opts.get('exclude')
292 exclude = opts.get('exclude')
293 force = opts.get('force')
293 force = opts.get('force')
294 enableprofile = opts.get('enable_profile')
294 enableprofile = opts.get('enable_profile')
295 disableprofile = opts.get('disable_profile')
295 disableprofile = opts.get('disable_profile')
296 importrules = opts.get('import_rules')
296 importrules = opts.get('import_rules')
297 clearrules = opts.get('clear_rules')
297 clearrules = opts.get('clear_rules')
298 delete = opts.get('delete')
298 delete = opts.get('delete')
299 refresh = opts.get('refresh')
299 refresh = opts.get('refresh')
300 reset = opts.get('reset')
300 reset = opts.get('reset')
301 count = sum([include, exclude, enableprofile, disableprofile, delete,
301 count = sum([include, exclude, enableprofile, disableprofile, delete,
302 importrules, refresh, clearrules, reset])
302 importrules, refresh, clearrules, reset])
303 if count > 1:
303 if count > 1:
304 raise error.Abort(_("too many flags specified"))
304 raise error.Abort(_("too many flags specified"))
305
305
306 if count == 0:
306 if count == 0:
307 if repo.vfs.exists('sparse'):
307 if repo.vfs.exists('sparse'):
308 ui.status(repo.vfs.read("sparse") + "\n")
308 ui.status(repo.vfs.read("sparse") + "\n")
309 temporaryincludes = sparse.readtemporaryincludes(repo)
309 temporaryincludes = sparse.readtemporaryincludes(repo)
310 if temporaryincludes:
310 if temporaryincludes:
311 ui.status(_("Temporarily Included Files (for merge/rebase):\n"))
311 ui.status(_("Temporarily Included Files (for merge/rebase):\n"))
312 ui.status(("\n".join(temporaryincludes) + "\n"))
312 ui.status(("\n".join(temporaryincludes) + "\n"))
313 else:
313 else:
314 ui.status(_('repo is not sparse\n'))
314 ui.status(_('repo is not sparse\n'))
315 return
315 return
316
316
317 if include or exclude or delete or reset or enableprofile or disableprofile:
317 if include or exclude or delete or reset or enableprofile or disableprofile:
318 sparse.updateconfig(repo, pats, opts, include=include, exclude=exclude,
318 sparse.updateconfig(repo, pats, opts, include=include, exclude=exclude,
319 reset=reset, delete=delete,
319 reset=reset, delete=delete,
320 enableprofile=enableprofile,
320 enableprofile=enableprofile,
321 disableprofile=disableprofile, force=force)
321 disableprofile=disableprofile, force=force)
322
322
323 if importrules:
323 if importrules:
324 sparse.importfromfiles(repo, opts, pats, force=force)
324 sparse.importfromfiles(repo, opts, pats, force=force)
325
325
326 if clearrules:
326 if clearrules:
327 sparse.clearrules(repo, force=force)
327 sparse.clearrules(repo, force=force)
328
328
329 if refresh:
329 if refresh:
330 try:
330 try:
331 wlock = repo.wlock()
331 wlock = repo.wlock()
332 fcounts = map(
332 fcounts = map(
333 len,
333 len,
334 sparse.refreshwdir(repo, repo.status(), sparse.matcher(repo),
334 sparse.refreshwdir(repo, repo.status(), sparse.matcher(repo),
335 force=force))
335 force=force))
336 sparse.printchanges(ui, opts, added=fcounts[0], dropped=fcounts[1],
336 sparse.printchanges(ui, opts, added=fcounts[0], dropped=fcounts[1],
337 conflicting=fcounts[2])
337 conflicting=fcounts[2])
338 finally:
338 finally:
339 wlock.release()
339 wlock.release()
@@ -1,3155 +1,3155 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import tempfile
13 import tempfile
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22
22
23 from . import (
23 from . import (
24 bookmarks,
24 bookmarks,
25 changelog,
25 changelog,
26 copies,
26 copies,
27 crecord as crecordmod,
27 crecord as crecordmod,
28 dirstateguard,
28 dirstateguard,
29 encoding,
29 encoding,
30 error,
30 error,
31 formatter,
31 formatter,
32 logcmdutil,
32 logcmdutil,
33 match as matchmod,
33 match as matchmod,
34 obsolete,
34 obsolete,
35 patch,
35 patch,
36 pathutil,
36 pathutil,
37 pycompat,
37 pycompat,
38 registrar,
38 registrar,
39 revlog,
39 revlog,
40 rewriteutil,
40 rewriteutil,
41 scmutil,
41 scmutil,
42 smartset,
42 smartset,
43 templater,
43 templater,
44 util,
44 util,
45 vfs as vfsmod,
45 vfs as vfsmod,
46 )
46 )
47 stringio = util.stringio
47 stringio = util.stringio
48
48
49 loglimit = logcmdutil.loglimit
49 loglimit = logcmdutil.getlimit
50 diffordiffstat = logcmdutil.diffordiffstat
50 diffordiffstat = logcmdutil.diffordiffstat
51 _changesetlabels = logcmdutil.changesetlabels
51 _changesetlabels = logcmdutil.changesetlabels
52 changeset_printer = logcmdutil.changesetprinter
52 changeset_printer = logcmdutil.changesetprinter
53 jsonchangeset = logcmdutil.jsonchangeset
53 jsonchangeset = logcmdutil.jsonchangeset
54 changeset_templater = logcmdutil.changesettemplater
54 changeset_templater = logcmdutil.changesettemplater
55 logtemplatespec = logcmdutil.logtemplatespec
55 logtemplatespec = logcmdutil.templatespec
56 makelogtemplater = logcmdutil.makelogtemplater
56 makelogtemplater = logcmdutil.maketemplater
57 show_changeset = logcmdutil.changesetdisplayer
57 show_changeset = logcmdutil.changesetdisplayer
58 getlogrevs = logcmdutil.getlogrevs
58 getlogrevs = logcmdutil.getrevs
59 getloglinerangerevs = logcmdutil.getloglinerangerevs
59 getloglinerangerevs = logcmdutil.getlinerangerevs
60 displaygraph = logcmdutil.displaygraph
60 displaygraph = logcmdutil.displaygraph
61 graphlog = logcmdutil.graphlog
61 graphlog = logcmdutil.graphlog
62 checkunsupportedgraphflags = logcmdutil.checkunsupportedgraphflags
62 checkunsupportedgraphflags = logcmdutil.checkunsupportedgraphflags
63 graphrevs = logcmdutil.graphrevs
63 graphrevs = logcmdutil.graphrevs
64
64
65 # templates of common command options
65 # templates of common command options
66
66
67 dryrunopts = [
67 dryrunopts = [
68 ('n', 'dry-run', None,
68 ('n', 'dry-run', None,
69 _('do not perform actions, just print output')),
69 _('do not perform actions, just print output')),
70 ]
70 ]
71
71
72 remoteopts = [
72 remoteopts = [
73 ('e', 'ssh', '',
73 ('e', 'ssh', '',
74 _('specify ssh command to use'), _('CMD')),
74 _('specify ssh command to use'), _('CMD')),
75 ('', 'remotecmd', '',
75 ('', 'remotecmd', '',
76 _('specify hg command to run on the remote side'), _('CMD')),
76 _('specify hg command to run on the remote side'), _('CMD')),
77 ('', 'insecure', None,
77 ('', 'insecure', None,
78 _('do not verify server certificate (ignoring web.cacerts config)')),
78 _('do not verify server certificate (ignoring web.cacerts config)')),
79 ]
79 ]
80
80
81 walkopts = [
81 walkopts = [
82 ('I', 'include', [],
82 ('I', 'include', [],
83 _('include names matching the given patterns'), _('PATTERN')),
83 _('include names matching the given patterns'), _('PATTERN')),
84 ('X', 'exclude', [],
84 ('X', 'exclude', [],
85 _('exclude names matching the given patterns'), _('PATTERN')),
85 _('exclude names matching the given patterns'), _('PATTERN')),
86 ]
86 ]
87
87
88 commitopts = [
88 commitopts = [
89 ('m', 'message', '',
89 ('m', 'message', '',
90 _('use text as commit message'), _('TEXT')),
90 _('use text as commit message'), _('TEXT')),
91 ('l', 'logfile', '',
91 ('l', 'logfile', '',
92 _('read commit message from file'), _('FILE')),
92 _('read commit message from file'), _('FILE')),
93 ]
93 ]
94
94
95 commitopts2 = [
95 commitopts2 = [
96 ('d', 'date', '',
96 ('d', 'date', '',
97 _('record the specified date as commit date'), _('DATE')),
97 _('record the specified date as commit date'), _('DATE')),
98 ('u', 'user', '',
98 ('u', 'user', '',
99 _('record the specified user as committer'), _('USER')),
99 _('record the specified user as committer'), _('USER')),
100 ]
100 ]
101
101
102 # hidden for now
102 # hidden for now
103 formatteropts = [
103 formatteropts = [
104 ('T', 'template', '',
104 ('T', 'template', '',
105 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
105 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
106 ]
106 ]
107
107
108 templateopts = [
108 templateopts = [
109 ('', 'style', '',
109 ('', 'style', '',
110 _('display using template map file (DEPRECATED)'), _('STYLE')),
110 _('display using template map file (DEPRECATED)'), _('STYLE')),
111 ('T', 'template', '',
111 ('T', 'template', '',
112 _('display with template'), _('TEMPLATE')),
112 _('display with template'), _('TEMPLATE')),
113 ]
113 ]
114
114
115 logopts = [
115 logopts = [
116 ('p', 'patch', None, _('show patch')),
116 ('p', 'patch', None, _('show patch')),
117 ('g', 'git', None, _('use git extended diff format')),
117 ('g', 'git', None, _('use git extended diff format')),
118 ('l', 'limit', '',
118 ('l', 'limit', '',
119 _('limit number of changes displayed'), _('NUM')),
119 _('limit number of changes displayed'), _('NUM')),
120 ('M', 'no-merges', None, _('do not show merges')),
120 ('M', 'no-merges', None, _('do not show merges')),
121 ('', 'stat', None, _('output diffstat-style summary of changes')),
121 ('', 'stat', None, _('output diffstat-style summary of changes')),
122 ('G', 'graph', None, _("show the revision DAG")),
122 ('G', 'graph', None, _("show the revision DAG")),
123 ] + templateopts
123 ] + templateopts
124
124
125 diffopts = [
125 diffopts = [
126 ('a', 'text', None, _('treat all files as text')),
126 ('a', 'text', None, _('treat all files as text')),
127 ('g', 'git', None, _('use git extended diff format')),
127 ('g', 'git', None, _('use git extended diff format')),
128 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
128 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
129 ('', 'nodates', None, _('omit dates from diff headers'))
129 ('', 'nodates', None, _('omit dates from diff headers'))
130 ]
130 ]
131
131
132 diffwsopts = [
132 diffwsopts = [
133 ('w', 'ignore-all-space', None,
133 ('w', 'ignore-all-space', None,
134 _('ignore white space when comparing lines')),
134 _('ignore white space when comparing lines')),
135 ('b', 'ignore-space-change', None,
135 ('b', 'ignore-space-change', None,
136 _('ignore changes in the amount of white space')),
136 _('ignore changes in the amount of white space')),
137 ('B', 'ignore-blank-lines', None,
137 ('B', 'ignore-blank-lines', None,
138 _('ignore changes whose lines are all blank')),
138 _('ignore changes whose lines are all blank')),
139 ('Z', 'ignore-space-at-eol', None,
139 ('Z', 'ignore-space-at-eol', None,
140 _('ignore changes in whitespace at EOL')),
140 _('ignore changes in whitespace at EOL')),
141 ]
141 ]
142
142
143 diffopts2 = [
143 diffopts2 = [
144 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
144 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
145 ('p', 'show-function', None, _('show which function each change is in')),
145 ('p', 'show-function', None, _('show which function each change is in')),
146 ('', 'reverse', None, _('produce a diff that undoes the changes')),
146 ('', 'reverse', None, _('produce a diff that undoes the changes')),
147 ] + diffwsopts + [
147 ] + diffwsopts + [
148 ('U', 'unified', '',
148 ('U', 'unified', '',
149 _('number of lines of context to show'), _('NUM')),
149 _('number of lines of context to show'), _('NUM')),
150 ('', 'stat', None, _('output diffstat-style summary of changes')),
150 ('', 'stat', None, _('output diffstat-style summary of changes')),
151 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
151 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
152 ]
152 ]
153
153
154 mergetoolopts = [
154 mergetoolopts = [
155 ('t', 'tool', '', _('specify merge tool')),
155 ('t', 'tool', '', _('specify merge tool')),
156 ]
156 ]
157
157
158 similarityopts = [
158 similarityopts = [
159 ('s', 'similarity', '',
159 ('s', 'similarity', '',
160 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
160 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
161 ]
161 ]
162
162
163 subrepoopts = [
163 subrepoopts = [
164 ('S', 'subrepos', None,
164 ('S', 'subrepos', None,
165 _('recurse into subrepositories'))
165 _('recurse into subrepositories'))
166 ]
166 ]
167
167
168 debugrevlogopts = [
168 debugrevlogopts = [
169 ('c', 'changelog', False, _('open changelog')),
169 ('c', 'changelog', False, _('open changelog')),
170 ('m', 'manifest', False, _('open manifest')),
170 ('m', 'manifest', False, _('open manifest')),
171 ('', 'dir', '', _('open directory manifest')),
171 ('', 'dir', '', _('open directory manifest')),
172 ]
172 ]
173
173
174 # special string such that everything below this line will be ingored in the
174 # special string such that everything below this line will be ingored in the
175 # editor text
175 # editor text
176 _linebelow = "^HG: ------------------------ >8 ------------------------$"
176 _linebelow = "^HG: ------------------------ >8 ------------------------$"
177
177
178 def ishunk(x):
178 def ishunk(x):
179 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
179 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
180 return isinstance(x, hunkclasses)
180 return isinstance(x, hunkclasses)
181
181
182 def newandmodified(chunks, originalchunks):
182 def newandmodified(chunks, originalchunks):
183 newlyaddedandmodifiedfiles = set()
183 newlyaddedandmodifiedfiles = set()
184 for chunk in chunks:
184 for chunk in chunks:
185 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
185 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
186 originalchunks:
186 originalchunks:
187 newlyaddedandmodifiedfiles.add(chunk.header.filename())
187 newlyaddedandmodifiedfiles.add(chunk.header.filename())
188 return newlyaddedandmodifiedfiles
188 return newlyaddedandmodifiedfiles
189
189
190 def parsealiases(cmd):
190 def parsealiases(cmd):
191 return cmd.lstrip("^").split("|")
191 return cmd.lstrip("^").split("|")
192
192
193 def setupwrapcolorwrite(ui):
193 def setupwrapcolorwrite(ui):
194 # wrap ui.write so diff output can be labeled/colorized
194 # wrap ui.write so diff output can be labeled/colorized
195 def wrapwrite(orig, *args, **kw):
195 def wrapwrite(orig, *args, **kw):
196 label = kw.pop(r'label', '')
196 label = kw.pop(r'label', '')
197 for chunk, l in patch.difflabel(lambda: args):
197 for chunk, l in patch.difflabel(lambda: args):
198 orig(chunk, label=label + l)
198 orig(chunk, label=label + l)
199
199
200 oldwrite = ui.write
200 oldwrite = ui.write
201 def wrap(*args, **kwargs):
201 def wrap(*args, **kwargs):
202 return wrapwrite(oldwrite, *args, **kwargs)
202 return wrapwrite(oldwrite, *args, **kwargs)
203 setattr(ui, 'write', wrap)
203 setattr(ui, 'write', wrap)
204 return oldwrite
204 return oldwrite
205
205
206 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
206 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
207 if usecurses:
207 if usecurses:
208 if testfile:
208 if testfile:
209 recordfn = crecordmod.testdecorator(testfile,
209 recordfn = crecordmod.testdecorator(testfile,
210 crecordmod.testchunkselector)
210 crecordmod.testchunkselector)
211 else:
211 else:
212 recordfn = crecordmod.chunkselector
212 recordfn = crecordmod.chunkselector
213
213
214 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
214 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
215
215
216 else:
216 else:
217 return patch.filterpatch(ui, originalhunks, operation)
217 return patch.filterpatch(ui, originalhunks, operation)
218
218
219 def recordfilter(ui, originalhunks, operation=None):
219 def recordfilter(ui, originalhunks, operation=None):
220 """ Prompts the user to filter the originalhunks and return a list of
220 """ Prompts the user to filter the originalhunks and return a list of
221 selected hunks.
221 selected hunks.
222 *operation* is used for to build ui messages to indicate the user what
222 *operation* is used for to build ui messages to indicate the user what
223 kind of filtering they are doing: reverting, committing, shelving, etc.
223 kind of filtering they are doing: reverting, committing, shelving, etc.
224 (see patch.filterpatch).
224 (see patch.filterpatch).
225 """
225 """
226 usecurses = crecordmod.checkcurses(ui)
226 usecurses = crecordmod.checkcurses(ui)
227 testfile = ui.config('experimental', 'crecordtest')
227 testfile = ui.config('experimental', 'crecordtest')
228 oldwrite = setupwrapcolorwrite(ui)
228 oldwrite = setupwrapcolorwrite(ui)
229 try:
229 try:
230 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
230 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
231 testfile, operation)
231 testfile, operation)
232 finally:
232 finally:
233 ui.write = oldwrite
233 ui.write = oldwrite
234 return newchunks, newopts
234 return newchunks, newopts
235
235
236 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
236 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
237 filterfn, *pats, **opts):
237 filterfn, *pats, **opts):
238 from . import merge as mergemod
238 from . import merge as mergemod
239 opts = pycompat.byteskwargs(opts)
239 opts = pycompat.byteskwargs(opts)
240 if not ui.interactive():
240 if not ui.interactive():
241 if cmdsuggest:
241 if cmdsuggest:
242 msg = _('running non-interactively, use %s instead') % cmdsuggest
242 msg = _('running non-interactively, use %s instead') % cmdsuggest
243 else:
243 else:
244 msg = _('running non-interactively')
244 msg = _('running non-interactively')
245 raise error.Abort(msg)
245 raise error.Abort(msg)
246
246
247 # make sure username is set before going interactive
247 # make sure username is set before going interactive
248 if not opts.get('user'):
248 if not opts.get('user'):
249 ui.username() # raise exception, username not provided
249 ui.username() # raise exception, username not provided
250
250
251 def recordfunc(ui, repo, message, match, opts):
251 def recordfunc(ui, repo, message, match, opts):
252 """This is generic record driver.
252 """This is generic record driver.
253
253
254 Its job is to interactively filter local changes, and
254 Its job is to interactively filter local changes, and
255 accordingly prepare working directory into a state in which the
255 accordingly prepare working directory into a state in which the
256 job can be delegated to a non-interactive commit command such as
256 job can be delegated to a non-interactive commit command such as
257 'commit' or 'qrefresh'.
257 'commit' or 'qrefresh'.
258
258
259 After the actual job is done by non-interactive command, the
259 After the actual job is done by non-interactive command, the
260 working directory is restored to its original state.
260 working directory is restored to its original state.
261
261
262 In the end we'll record interesting changes, and everything else
262 In the end we'll record interesting changes, and everything else
263 will be left in place, so the user can continue working.
263 will be left in place, so the user can continue working.
264 """
264 """
265
265
266 checkunfinished(repo, commit=True)
266 checkunfinished(repo, commit=True)
267 wctx = repo[None]
267 wctx = repo[None]
268 merge = len(wctx.parents()) > 1
268 merge = len(wctx.parents()) > 1
269 if merge:
269 if merge:
270 raise error.Abort(_('cannot partially commit a merge '
270 raise error.Abort(_('cannot partially commit a merge '
271 '(use "hg commit" instead)'))
271 '(use "hg commit" instead)'))
272
272
273 def fail(f, msg):
273 def fail(f, msg):
274 raise error.Abort('%s: %s' % (f, msg))
274 raise error.Abort('%s: %s' % (f, msg))
275
275
276 force = opts.get('force')
276 force = opts.get('force')
277 if not force:
277 if not force:
278 vdirs = []
278 vdirs = []
279 match.explicitdir = vdirs.append
279 match.explicitdir = vdirs.append
280 match.bad = fail
280 match.bad = fail
281
281
282 status = repo.status(match=match)
282 status = repo.status(match=match)
283 if not force:
283 if not force:
284 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
284 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
285 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
285 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
286 diffopts.nodates = True
286 diffopts.nodates = True
287 diffopts.git = True
287 diffopts.git = True
288 diffopts.showfunc = True
288 diffopts.showfunc = True
289 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
289 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
290 originalchunks = patch.parsepatch(originaldiff)
290 originalchunks = patch.parsepatch(originaldiff)
291
291
292 # 1. filter patch, since we are intending to apply subset of it
292 # 1. filter patch, since we are intending to apply subset of it
293 try:
293 try:
294 chunks, newopts = filterfn(ui, originalchunks)
294 chunks, newopts = filterfn(ui, originalchunks)
295 except error.PatchError as err:
295 except error.PatchError as err:
296 raise error.Abort(_('error parsing patch: %s') % err)
296 raise error.Abort(_('error parsing patch: %s') % err)
297 opts.update(newopts)
297 opts.update(newopts)
298
298
299 # We need to keep a backup of files that have been newly added and
299 # We need to keep a backup of files that have been newly added and
300 # modified during the recording process because there is a previous
300 # modified during the recording process because there is a previous
301 # version without the edit in the workdir
301 # version without the edit in the workdir
302 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
302 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
303 contenders = set()
303 contenders = set()
304 for h in chunks:
304 for h in chunks:
305 try:
305 try:
306 contenders.update(set(h.files()))
306 contenders.update(set(h.files()))
307 except AttributeError:
307 except AttributeError:
308 pass
308 pass
309
309
310 changed = status.modified + status.added + status.removed
310 changed = status.modified + status.added + status.removed
311 newfiles = [f for f in changed if f in contenders]
311 newfiles = [f for f in changed if f in contenders]
312 if not newfiles:
312 if not newfiles:
313 ui.status(_('no changes to record\n'))
313 ui.status(_('no changes to record\n'))
314 return 0
314 return 0
315
315
316 modified = set(status.modified)
316 modified = set(status.modified)
317
317
318 # 2. backup changed files, so we can restore them in the end
318 # 2. backup changed files, so we can restore them in the end
319
319
320 if backupall:
320 if backupall:
321 tobackup = changed
321 tobackup = changed
322 else:
322 else:
323 tobackup = [f for f in newfiles if f in modified or f in \
323 tobackup = [f for f in newfiles if f in modified or f in \
324 newlyaddedandmodifiedfiles]
324 newlyaddedandmodifiedfiles]
325 backups = {}
325 backups = {}
326 if tobackup:
326 if tobackup:
327 backupdir = repo.vfs.join('record-backups')
327 backupdir = repo.vfs.join('record-backups')
328 try:
328 try:
329 os.mkdir(backupdir)
329 os.mkdir(backupdir)
330 except OSError as err:
330 except OSError as err:
331 if err.errno != errno.EEXIST:
331 if err.errno != errno.EEXIST:
332 raise
332 raise
333 try:
333 try:
334 # backup continues
334 # backup continues
335 for f in tobackup:
335 for f in tobackup:
336 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
336 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
337 dir=backupdir)
337 dir=backupdir)
338 os.close(fd)
338 os.close(fd)
339 ui.debug('backup %r as %r\n' % (f, tmpname))
339 ui.debug('backup %r as %r\n' % (f, tmpname))
340 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
340 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
341 backups[f] = tmpname
341 backups[f] = tmpname
342
342
343 fp = stringio()
343 fp = stringio()
344 for c in chunks:
344 for c in chunks:
345 fname = c.filename()
345 fname = c.filename()
346 if fname in backups:
346 if fname in backups:
347 c.write(fp)
347 c.write(fp)
348 dopatch = fp.tell()
348 dopatch = fp.tell()
349 fp.seek(0)
349 fp.seek(0)
350
350
351 # 2.5 optionally review / modify patch in text editor
351 # 2.5 optionally review / modify patch in text editor
352 if opts.get('review', False):
352 if opts.get('review', False):
353 patchtext = (crecordmod.diffhelptext
353 patchtext = (crecordmod.diffhelptext
354 + crecordmod.patchhelptext
354 + crecordmod.patchhelptext
355 + fp.read())
355 + fp.read())
356 reviewedpatch = ui.edit(patchtext, "",
356 reviewedpatch = ui.edit(patchtext, "",
357 action="diff",
357 action="diff",
358 repopath=repo.path)
358 repopath=repo.path)
359 fp.truncate(0)
359 fp.truncate(0)
360 fp.write(reviewedpatch)
360 fp.write(reviewedpatch)
361 fp.seek(0)
361 fp.seek(0)
362
362
363 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
363 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
364 # 3a. apply filtered patch to clean repo (clean)
364 # 3a. apply filtered patch to clean repo (clean)
365 if backups:
365 if backups:
366 # Equivalent to hg.revert
366 # Equivalent to hg.revert
367 m = scmutil.matchfiles(repo, backups.keys())
367 m = scmutil.matchfiles(repo, backups.keys())
368 mergemod.update(repo, repo.dirstate.p1(),
368 mergemod.update(repo, repo.dirstate.p1(),
369 False, True, matcher=m)
369 False, True, matcher=m)
370
370
371 # 3b. (apply)
371 # 3b. (apply)
372 if dopatch:
372 if dopatch:
373 try:
373 try:
374 ui.debug('applying patch\n')
374 ui.debug('applying patch\n')
375 ui.debug(fp.getvalue())
375 ui.debug(fp.getvalue())
376 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
376 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
377 except error.PatchError as err:
377 except error.PatchError as err:
378 raise error.Abort(str(err))
378 raise error.Abort(str(err))
379 del fp
379 del fp
380
380
381 # 4. We prepared working directory according to filtered
381 # 4. We prepared working directory according to filtered
382 # patch. Now is the time to delegate the job to
382 # patch. Now is the time to delegate the job to
383 # commit/qrefresh or the like!
383 # commit/qrefresh or the like!
384
384
385 # Make all of the pathnames absolute.
385 # Make all of the pathnames absolute.
386 newfiles = [repo.wjoin(nf) for nf in newfiles]
386 newfiles = [repo.wjoin(nf) for nf in newfiles]
387 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
387 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
388 finally:
388 finally:
389 # 5. finally restore backed-up files
389 # 5. finally restore backed-up files
390 try:
390 try:
391 dirstate = repo.dirstate
391 dirstate = repo.dirstate
392 for realname, tmpname in backups.iteritems():
392 for realname, tmpname in backups.iteritems():
393 ui.debug('restoring %r to %r\n' % (tmpname, realname))
393 ui.debug('restoring %r to %r\n' % (tmpname, realname))
394
394
395 if dirstate[realname] == 'n':
395 if dirstate[realname] == 'n':
396 # without normallookup, restoring timestamp
396 # without normallookup, restoring timestamp
397 # may cause partially committed files
397 # may cause partially committed files
398 # to be treated as unmodified
398 # to be treated as unmodified
399 dirstate.normallookup(realname)
399 dirstate.normallookup(realname)
400
400
401 # copystat=True here and above are a hack to trick any
401 # copystat=True here and above are a hack to trick any
402 # editors that have f open that we haven't modified them.
402 # editors that have f open that we haven't modified them.
403 #
403 #
404 # Also note that this racy as an editor could notice the
404 # Also note that this racy as an editor could notice the
405 # file's mtime before we've finished writing it.
405 # file's mtime before we've finished writing it.
406 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
406 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
407 os.unlink(tmpname)
407 os.unlink(tmpname)
408 if tobackup:
408 if tobackup:
409 os.rmdir(backupdir)
409 os.rmdir(backupdir)
410 except OSError:
410 except OSError:
411 pass
411 pass
412
412
413 def recordinwlock(ui, repo, message, match, opts):
413 def recordinwlock(ui, repo, message, match, opts):
414 with repo.wlock():
414 with repo.wlock():
415 return recordfunc(ui, repo, message, match, opts)
415 return recordfunc(ui, repo, message, match, opts)
416
416
417 return commit(ui, repo, recordinwlock, pats, opts)
417 return commit(ui, repo, recordinwlock, pats, opts)
418
418
419 class dirnode(object):
419 class dirnode(object):
420 """
420 """
421 Represent a directory in user working copy with information required for
421 Represent a directory in user working copy with information required for
422 the purpose of tersing its status.
422 the purpose of tersing its status.
423
423
424 path is the path to the directory
424 path is the path to the directory
425
425
426 statuses is a set of statuses of all files in this directory (this includes
426 statuses is a set of statuses of all files in this directory (this includes
427 all the files in all the subdirectories too)
427 all the files in all the subdirectories too)
428
428
429 files is a list of files which are direct child of this directory
429 files is a list of files which are direct child of this directory
430
430
431 subdirs is a dictionary of sub-directory name as the key and it's own
431 subdirs is a dictionary of sub-directory name as the key and it's own
432 dirnode object as the value
432 dirnode object as the value
433 """
433 """
434
434
435 def __init__(self, dirpath):
435 def __init__(self, dirpath):
436 self.path = dirpath
436 self.path = dirpath
437 self.statuses = set([])
437 self.statuses = set([])
438 self.files = []
438 self.files = []
439 self.subdirs = {}
439 self.subdirs = {}
440
440
441 def _addfileindir(self, filename, status):
441 def _addfileindir(self, filename, status):
442 """Add a file in this directory as a direct child."""
442 """Add a file in this directory as a direct child."""
443 self.files.append((filename, status))
443 self.files.append((filename, status))
444
444
445 def addfile(self, filename, status):
445 def addfile(self, filename, status):
446 """
446 """
447 Add a file to this directory or to its direct parent directory.
447 Add a file to this directory or to its direct parent directory.
448
448
449 If the file is not direct child of this directory, we traverse to the
449 If the file is not direct child of this directory, we traverse to the
450 directory of which this file is a direct child of and add the file
450 directory of which this file is a direct child of and add the file
451 there.
451 there.
452 """
452 """
453
453
454 # the filename contains a path separator, it means it's not the direct
454 # the filename contains a path separator, it means it's not the direct
455 # child of this directory
455 # child of this directory
456 if '/' in filename:
456 if '/' in filename:
457 subdir, filep = filename.split('/', 1)
457 subdir, filep = filename.split('/', 1)
458
458
459 # does the dirnode object for subdir exists
459 # does the dirnode object for subdir exists
460 if subdir not in self.subdirs:
460 if subdir not in self.subdirs:
461 subdirpath = os.path.join(self.path, subdir)
461 subdirpath = os.path.join(self.path, subdir)
462 self.subdirs[subdir] = dirnode(subdirpath)
462 self.subdirs[subdir] = dirnode(subdirpath)
463
463
464 # try adding the file in subdir
464 # try adding the file in subdir
465 self.subdirs[subdir].addfile(filep, status)
465 self.subdirs[subdir].addfile(filep, status)
466
466
467 else:
467 else:
468 self._addfileindir(filename, status)
468 self._addfileindir(filename, status)
469
469
470 if status not in self.statuses:
470 if status not in self.statuses:
471 self.statuses.add(status)
471 self.statuses.add(status)
472
472
473 def iterfilepaths(self):
473 def iterfilepaths(self):
474 """Yield (status, path) for files directly under this directory."""
474 """Yield (status, path) for files directly under this directory."""
475 for f, st in self.files:
475 for f, st in self.files:
476 yield st, os.path.join(self.path, f)
476 yield st, os.path.join(self.path, f)
477
477
478 def tersewalk(self, terseargs):
478 def tersewalk(self, terseargs):
479 """
479 """
480 Yield (status, path) obtained by processing the status of this
480 Yield (status, path) obtained by processing the status of this
481 dirnode.
481 dirnode.
482
482
483 terseargs is the string of arguments passed by the user with `--terse`
483 terseargs is the string of arguments passed by the user with `--terse`
484 flag.
484 flag.
485
485
486 Following are the cases which can happen:
486 Following are the cases which can happen:
487
487
488 1) All the files in the directory (including all the files in its
488 1) All the files in the directory (including all the files in its
489 subdirectories) share the same status and the user has asked us to terse
489 subdirectories) share the same status and the user has asked us to terse
490 that status. -> yield (status, dirpath)
490 that status. -> yield (status, dirpath)
491
491
492 2) Otherwise, we do following:
492 2) Otherwise, we do following:
493
493
494 a) Yield (status, filepath) for all the files which are in this
494 a) Yield (status, filepath) for all the files which are in this
495 directory (only the ones in this directory, not the subdirs)
495 directory (only the ones in this directory, not the subdirs)
496
496
497 b) Recurse the function on all the subdirectories of this
497 b) Recurse the function on all the subdirectories of this
498 directory
498 directory
499 """
499 """
500
500
501 if len(self.statuses) == 1:
501 if len(self.statuses) == 1:
502 onlyst = self.statuses.pop()
502 onlyst = self.statuses.pop()
503
503
504 # Making sure we terse only when the status abbreviation is
504 # Making sure we terse only when the status abbreviation is
505 # passed as terse argument
505 # passed as terse argument
506 if onlyst in terseargs:
506 if onlyst in terseargs:
507 yield onlyst, self.path + pycompat.ossep
507 yield onlyst, self.path + pycompat.ossep
508 return
508 return
509
509
510 # add the files to status list
510 # add the files to status list
511 for st, fpath in self.iterfilepaths():
511 for st, fpath in self.iterfilepaths():
512 yield st, fpath
512 yield st, fpath
513
513
514 #recurse on the subdirs
514 #recurse on the subdirs
515 for dirobj in self.subdirs.values():
515 for dirobj in self.subdirs.values():
516 for st, fpath in dirobj.tersewalk(terseargs):
516 for st, fpath in dirobj.tersewalk(terseargs):
517 yield st, fpath
517 yield st, fpath
518
518
519 def tersedir(statuslist, terseargs):
519 def tersedir(statuslist, terseargs):
520 """
520 """
521 Terse the status if all the files in a directory shares the same status.
521 Terse the status if all the files in a directory shares the same status.
522
522
523 statuslist is scmutil.status() object which contains a list of files for
523 statuslist is scmutil.status() object which contains a list of files for
524 each status.
524 each status.
525 terseargs is string which is passed by the user as the argument to `--terse`
525 terseargs is string which is passed by the user as the argument to `--terse`
526 flag.
526 flag.
527
527
528 The function makes a tree of objects of dirnode class, and at each node it
528 The function makes a tree of objects of dirnode class, and at each node it
529 stores the information required to know whether we can terse a certain
529 stores the information required to know whether we can terse a certain
530 directory or not.
530 directory or not.
531 """
531 """
532 # the order matters here as that is used to produce final list
532 # the order matters here as that is used to produce final list
533 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
533 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
534
534
535 # checking the argument validity
535 # checking the argument validity
536 for s in pycompat.bytestr(terseargs):
536 for s in pycompat.bytestr(terseargs):
537 if s not in allst:
537 if s not in allst:
538 raise error.Abort(_("'%s' not recognized") % s)
538 raise error.Abort(_("'%s' not recognized") % s)
539
539
540 # creating a dirnode object for the root of the repo
540 # creating a dirnode object for the root of the repo
541 rootobj = dirnode('')
541 rootobj = dirnode('')
542 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
542 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
543 'ignored', 'removed')
543 'ignored', 'removed')
544
544
545 tersedict = {}
545 tersedict = {}
546 for attrname in pstatus:
546 for attrname in pstatus:
547 statuschar = attrname[0:1]
547 statuschar = attrname[0:1]
548 for f in getattr(statuslist, attrname):
548 for f in getattr(statuslist, attrname):
549 rootobj.addfile(f, statuschar)
549 rootobj.addfile(f, statuschar)
550 tersedict[statuschar] = []
550 tersedict[statuschar] = []
551
551
552 # we won't be tersing the root dir, so add files in it
552 # we won't be tersing the root dir, so add files in it
553 for st, fpath in rootobj.iterfilepaths():
553 for st, fpath in rootobj.iterfilepaths():
554 tersedict[st].append(fpath)
554 tersedict[st].append(fpath)
555
555
556 # process each sub-directory and build tersedict
556 # process each sub-directory and build tersedict
557 for subdir in rootobj.subdirs.values():
557 for subdir in rootobj.subdirs.values():
558 for st, f in subdir.tersewalk(terseargs):
558 for st, f in subdir.tersewalk(terseargs):
559 tersedict[st].append(f)
559 tersedict[st].append(f)
560
560
561 tersedlist = []
561 tersedlist = []
562 for st in allst:
562 for st in allst:
563 tersedict[st].sort()
563 tersedict[st].sort()
564 tersedlist.append(tersedict[st])
564 tersedlist.append(tersedict[st])
565
565
566 return tersedlist
566 return tersedlist
567
567
568 def _commentlines(raw):
568 def _commentlines(raw):
569 '''Surround lineswith a comment char and a new line'''
569 '''Surround lineswith a comment char and a new line'''
570 lines = raw.splitlines()
570 lines = raw.splitlines()
571 commentedlines = ['# %s' % line for line in lines]
571 commentedlines = ['# %s' % line for line in lines]
572 return '\n'.join(commentedlines) + '\n'
572 return '\n'.join(commentedlines) + '\n'
573
573
574 def _conflictsmsg(repo):
574 def _conflictsmsg(repo):
575 # avoid merge cycle
575 # avoid merge cycle
576 from . import merge as mergemod
576 from . import merge as mergemod
577 mergestate = mergemod.mergestate.read(repo)
577 mergestate = mergemod.mergestate.read(repo)
578 if not mergestate.active():
578 if not mergestate.active():
579 return
579 return
580
580
581 m = scmutil.match(repo[None])
581 m = scmutil.match(repo[None])
582 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
582 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
583 if unresolvedlist:
583 if unresolvedlist:
584 mergeliststr = '\n'.join(
584 mergeliststr = '\n'.join(
585 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
585 [' %s' % util.pathto(repo.root, pycompat.getcwd(), path)
586 for path in unresolvedlist])
586 for path in unresolvedlist])
587 msg = _('''Unresolved merge conflicts:
587 msg = _('''Unresolved merge conflicts:
588
588
589 %s
589 %s
590
590
591 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
591 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
592 else:
592 else:
593 msg = _('No unresolved merge conflicts.')
593 msg = _('No unresolved merge conflicts.')
594
594
595 return _commentlines(msg)
595 return _commentlines(msg)
596
596
597 def _helpmessage(continuecmd, abortcmd):
597 def _helpmessage(continuecmd, abortcmd):
598 msg = _('To continue: %s\n'
598 msg = _('To continue: %s\n'
599 'To abort: %s') % (continuecmd, abortcmd)
599 'To abort: %s') % (continuecmd, abortcmd)
600 return _commentlines(msg)
600 return _commentlines(msg)
601
601
602 def _rebasemsg():
602 def _rebasemsg():
603 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
603 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
604
604
605 def _histeditmsg():
605 def _histeditmsg():
606 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
606 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
607
607
608 def _unshelvemsg():
608 def _unshelvemsg():
609 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
609 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
610
610
611 def _updatecleanmsg(dest=None):
611 def _updatecleanmsg(dest=None):
612 warning = _('warning: this will discard uncommitted changes')
612 warning = _('warning: this will discard uncommitted changes')
613 return 'hg update --clean %s (%s)' % (dest or '.', warning)
613 return 'hg update --clean %s (%s)' % (dest or '.', warning)
614
614
615 def _graftmsg():
615 def _graftmsg():
616 # tweakdefaults requires `update` to have a rev hence the `.`
616 # tweakdefaults requires `update` to have a rev hence the `.`
617 return _helpmessage('hg graft --continue', _updatecleanmsg())
617 return _helpmessage('hg graft --continue', _updatecleanmsg())
618
618
619 def _mergemsg():
619 def _mergemsg():
620 # tweakdefaults requires `update` to have a rev hence the `.`
620 # tweakdefaults requires `update` to have a rev hence the `.`
621 return _helpmessage('hg commit', _updatecleanmsg())
621 return _helpmessage('hg commit', _updatecleanmsg())
622
622
623 def _bisectmsg():
623 def _bisectmsg():
624 msg = _('To mark the changeset good: hg bisect --good\n'
624 msg = _('To mark the changeset good: hg bisect --good\n'
625 'To mark the changeset bad: hg bisect --bad\n'
625 'To mark the changeset bad: hg bisect --bad\n'
626 'To abort: hg bisect --reset\n')
626 'To abort: hg bisect --reset\n')
627 return _commentlines(msg)
627 return _commentlines(msg)
628
628
629 def fileexistspredicate(filename):
629 def fileexistspredicate(filename):
630 return lambda repo: repo.vfs.exists(filename)
630 return lambda repo: repo.vfs.exists(filename)
631
631
632 def _mergepredicate(repo):
632 def _mergepredicate(repo):
633 return len(repo[None].parents()) > 1
633 return len(repo[None].parents()) > 1
634
634
635 STATES = (
635 STATES = (
636 # (state, predicate to detect states, helpful message function)
636 # (state, predicate to detect states, helpful message function)
637 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
637 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
638 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
638 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
639 ('graft', fileexistspredicate('graftstate'), _graftmsg),
639 ('graft', fileexistspredicate('graftstate'), _graftmsg),
640 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
640 ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
641 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
641 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
642 # The merge state is part of a list that will be iterated over.
642 # The merge state is part of a list that will be iterated over.
643 # They need to be last because some of the other unfinished states may also
643 # They need to be last because some of the other unfinished states may also
644 # be in a merge or update state (eg. rebase, histedit, graft, etc).
644 # be in a merge or update state (eg. rebase, histedit, graft, etc).
645 # We want those to have priority.
645 # We want those to have priority.
646 ('merge', _mergepredicate, _mergemsg),
646 ('merge', _mergepredicate, _mergemsg),
647 )
647 )
648
648
649 def _getrepostate(repo):
649 def _getrepostate(repo):
650 # experimental config: commands.status.skipstates
650 # experimental config: commands.status.skipstates
651 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
651 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
652 for state, statedetectionpredicate, msgfn in STATES:
652 for state, statedetectionpredicate, msgfn in STATES:
653 if state in skip:
653 if state in skip:
654 continue
654 continue
655 if statedetectionpredicate(repo):
655 if statedetectionpredicate(repo):
656 return (state, statedetectionpredicate, msgfn)
656 return (state, statedetectionpredicate, msgfn)
657
657
658 def morestatus(repo, fm):
658 def morestatus(repo, fm):
659 statetuple = _getrepostate(repo)
659 statetuple = _getrepostate(repo)
660 label = 'status.morestatus'
660 label = 'status.morestatus'
661 if statetuple:
661 if statetuple:
662 fm.startitem()
662 fm.startitem()
663 state, statedetectionpredicate, helpfulmsg = statetuple
663 state, statedetectionpredicate, helpfulmsg = statetuple
664 statemsg = _('The repository is in an unfinished *%s* state.') % state
664 statemsg = _('The repository is in an unfinished *%s* state.') % state
665 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
665 fm.write('statemsg', '%s\n', _commentlines(statemsg), label=label)
666 conmsg = _conflictsmsg(repo)
666 conmsg = _conflictsmsg(repo)
667 if conmsg:
667 if conmsg:
668 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
668 fm.write('conflictsmsg', '%s\n', conmsg, label=label)
669 if helpfulmsg:
669 if helpfulmsg:
670 helpmsg = helpfulmsg()
670 helpmsg = helpfulmsg()
671 fm.write('helpmsg', '%s\n', helpmsg, label=label)
671 fm.write('helpmsg', '%s\n', helpmsg, label=label)
672
672
673 def findpossible(cmd, table, strict=False):
673 def findpossible(cmd, table, strict=False):
674 """
674 """
675 Return cmd -> (aliases, command table entry)
675 Return cmd -> (aliases, command table entry)
676 for each matching command.
676 for each matching command.
677 Return debug commands (or their aliases) only if no normal command matches.
677 Return debug commands (or their aliases) only if no normal command matches.
678 """
678 """
679 choice = {}
679 choice = {}
680 debugchoice = {}
680 debugchoice = {}
681
681
682 if cmd in table:
682 if cmd in table:
683 # short-circuit exact matches, "log" alias beats "^log|history"
683 # short-circuit exact matches, "log" alias beats "^log|history"
684 keys = [cmd]
684 keys = [cmd]
685 else:
685 else:
686 keys = table.keys()
686 keys = table.keys()
687
687
688 allcmds = []
688 allcmds = []
689 for e in keys:
689 for e in keys:
690 aliases = parsealiases(e)
690 aliases = parsealiases(e)
691 allcmds.extend(aliases)
691 allcmds.extend(aliases)
692 found = None
692 found = None
693 if cmd in aliases:
693 if cmd in aliases:
694 found = cmd
694 found = cmd
695 elif not strict:
695 elif not strict:
696 for a in aliases:
696 for a in aliases:
697 if a.startswith(cmd):
697 if a.startswith(cmd):
698 found = a
698 found = a
699 break
699 break
700 if found is not None:
700 if found is not None:
701 if aliases[0].startswith("debug") or found.startswith("debug"):
701 if aliases[0].startswith("debug") or found.startswith("debug"):
702 debugchoice[found] = (aliases, table[e])
702 debugchoice[found] = (aliases, table[e])
703 else:
703 else:
704 choice[found] = (aliases, table[e])
704 choice[found] = (aliases, table[e])
705
705
706 if not choice and debugchoice:
706 if not choice and debugchoice:
707 choice = debugchoice
707 choice = debugchoice
708
708
709 return choice, allcmds
709 return choice, allcmds
710
710
711 def findcmd(cmd, table, strict=True):
711 def findcmd(cmd, table, strict=True):
712 """Return (aliases, command table entry) for command string."""
712 """Return (aliases, command table entry) for command string."""
713 choice, allcmds = findpossible(cmd, table, strict)
713 choice, allcmds = findpossible(cmd, table, strict)
714
714
715 if cmd in choice:
715 if cmd in choice:
716 return choice[cmd]
716 return choice[cmd]
717
717
718 if len(choice) > 1:
718 if len(choice) > 1:
719 clist = sorted(choice)
719 clist = sorted(choice)
720 raise error.AmbiguousCommand(cmd, clist)
720 raise error.AmbiguousCommand(cmd, clist)
721
721
722 if choice:
722 if choice:
723 return list(choice.values())[0]
723 return list(choice.values())[0]
724
724
725 raise error.UnknownCommand(cmd, allcmds)
725 raise error.UnknownCommand(cmd, allcmds)
726
726
727 def changebranch(ui, repo, revs, label):
727 def changebranch(ui, repo, revs, label):
728 """ Change the branch name of given revs to label """
728 """ Change the branch name of given revs to label """
729
729
730 with repo.wlock(), repo.lock(), repo.transaction('branches'):
730 with repo.wlock(), repo.lock(), repo.transaction('branches'):
731 # abort in case of uncommitted merge or dirty wdir
731 # abort in case of uncommitted merge or dirty wdir
732 bailifchanged(repo)
732 bailifchanged(repo)
733 revs = scmutil.revrange(repo, revs)
733 revs = scmutil.revrange(repo, revs)
734 if not revs:
734 if not revs:
735 raise error.Abort("empty revision set")
735 raise error.Abort("empty revision set")
736 roots = repo.revs('roots(%ld)', revs)
736 roots = repo.revs('roots(%ld)', revs)
737 if len(roots) > 1:
737 if len(roots) > 1:
738 raise error.Abort(_("cannot change branch of non-linear revisions"))
738 raise error.Abort(_("cannot change branch of non-linear revisions"))
739 rewriteutil.precheck(repo, revs, 'change branch of')
739 rewriteutil.precheck(repo, revs, 'change branch of')
740
740
741 root = repo[roots.first()]
741 root = repo[roots.first()]
742 if not root.p1().branch() == label and label in repo.branchmap():
742 if not root.p1().branch() == label and label in repo.branchmap():
743 raise error.Abort(_("a branch of the same name already exists"))
743 raise error.Abort(_("a branch of the same name already exists"))
744
744
745 if repo.revs('merge() and %ld', revs):
745 if repo.revs('merge() and %ld', revs):
746 raise error.Abort(_("cannot change branch of a merge commit"))
746 raise error.Abort(_("cannot change branch of a merge commit"))
747 if repo.revs('obsolete() and %ld', revs):
747 if repo.revs('obsolete() and %ld', revs):
748 raise error.Abort(_("cannot change branch of a obsolete changeset"))
748 raise error.Abort(_("cannot change branch of a obsolete changeset"))
749
749
750 # make sure only topological heads
750 # make sure only topological heads
751 if repo.revs('heads(%ld) - head()', revs):
751 if repo.revs('heads(%ld) - head()', revs):
752 raise error.Abort(_("cannot change branch in middle of a stack"))
752 raise error.Abort(_("cannot change branch in middle of a stack"))
753
753
754 replacements = {}
754 replacements = {}
755 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
755 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
756 # mercurial.subrepo -> mercurial.cmdutil
756 # mercurial.subrepo -> mercurial.cmdutil
757 from . import context
757 from . import context
758 for rev in revs:
758 for rev in revs:
759 ctx = repo[rev]
759 ctx = repo[rev]
760 oldbranch = ctx.branch()
760 oldbranch = ctx.branch()
761 # check if ctx has same branch
761 # check if ctx has same branch
762 if oldbranch == label:
762 if oldbranch == label:
763 continue
763 continue
764
764
765 def filectxfn(repo, newctx, path):
765 def filectxfn(repo, newctx, path):
766 try:
766 try:
767 return ctx[path]
767 return ctx[path]
768 except error.ManifestLookupError:
768 except error.ManifestLookupError:
769 return None
769 return None
770
770
771 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
771 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
772 % (hex(ctx.node()), oldbranch, label))
772 % (hex(ctx.node()), oldbranch, label))
773 extra = ctx.extra()
773 extra = ctx.extra()
774 extra['branch_change'] = hex(ctx.node())
774 extra['branch_change'] = hex(ctx.node())
775 # While changing branch of set of linear commits, make sure that
775 # While changing branch of set of linear commits, make sure that
776 # we base our commits on new parent rather than old parent which
776 # we base our commits on new parent rather than old parent which
777 # was obsoleted while changing the branch
777 # was obsoleted while changing the branch
778 p1 = ctx.p1().node()
778 p1 = ctx.p1().node()
779 p2 = ctx.p2().node()
779 p2 = ctx.p2().node()
780 if p1 in replacements:
780 if p1 in replacements:
781 p1 = replacements[p1][0]
781 p1 = replacements[p1][0]
782 if p2 in replacements:
782 if p2 in replacements:
783 p2 = replacements[p2][0]
783 p2 = replacements[p2][0]
784
784
785 mc = context.memctx(repo, (p1, p2),
785 mc = context.memctx(repo, (p1, p2),
786 ctx.description(),
786 ctx.description(),
787 ctx.files(),
787 ctx.files(),
788 filectxfn,
788 filectxfn,
789 user=ctx.user(),
789 user=ctx.user(),
790 date=ctx.date(),
790 date=ctx.date(),
791 extra=extra,
791 extra=extra,
792 branch=label)
792 branch=label)
793
793
794 commitphase = ctx.phase()
794 commitphase = ctx.phase()
795 overrides = {('phases', 'new-commit'): commitphase}
795 overrides = {('phases', 'new-commit'): commitphase}
796 with repo.ui.configoverride(overrides, 'branch-change'):
796 with repo.ui.configoverride(overrides, 'branch-change'):
797 newnode = repo.commitctx(mc)
797 newnode = repo.commitctx(mc)
798
798
799 replacements[ctx.node()] = (newnode,)
799 replacements[ctx.node()] = (newnode,)
800 ui.debug('new node id is %s\n' % hex(newnode))
800 ui.debug('new node id is %s\n' % hex(newnode))
801
801
802 # create obsmarkers and move bookmarks
802 # create obsmarkers and move bookmarks
803 scmutil.cleanupnodes(repo, replacements, 'branch-change')
803 scmutil.cleanupnodes(repo, replacements, 'branch-change')
804
804
805 # move the working copy too
805 # move the working copy too
806 wctx = repo[None]
806 wctx = repo[None]
807 # in-progress merge is a bit too complex for now.
807 # in-progress merge is a bit too complex for now.
808 if len(wctx.parents()) == 1:
808 if len(wctx.parents()) == 1:
809 newid = replacements.get(wctx.p1().node())
809 newid = replacements.get(wctx.p1().node())
810 if newid is not None:
810 if newid is not None:
811 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
811 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
812 # mercurial.cmdutil
812 # mercurial.cmdutil
813 from . import hg
813 from . import hg
814 hg.update(repo, newid[0], quietempty=True)
814 hg.update(repo, newid[0], quietempty=True)
815
815
816 ui.status(_("changed branch on %d changesets\n") % len(replacements))
816 ui.status(_("changed branch on %d changesets\n") % len(replacements))
817
817
818 def findrepo(p):
818 def findrepo(p):
819 while not os.path.isdir(os.path.join(p, ".hg")):
819 while not os.path.isdir(os.path.join(p, ".hg")):
820 oldp, p = p, os.path.dirname(p)
820 oldp, p = p, os.path.dirname(p)
821 if p == oldp:
821 if p == oldp:
822 return None
822 return None
823
823
824 return p
824 return p
825
825
826 def bailifchanged(repo, merge=True, hint=None):
826 def bailifchanged(repo, merge=True, hint=None):
827 """ enforce the precondition that working directory must be clean.
827 """ enforce the precondition that working directory must be clean.
828
828
829 'merge' can be set to false if a pending uncommitted merge should be
829 'merge' can be set to false if a pending uncommitted merge should be
830 ignored (such as when 'update --check' runs).
830 ignored (such as when 'update --check' runs).
831
831
832 'hint' is the usual hint given to Abort exception.
832 'hint' is the usual hint given to Abort exception.
833 """
833 """
834
834
835 if merge and repo.dirstate.p2() != nullid:
835 if merge and repo.dirstate.p2() != nullid:
836 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
836 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
837 modified, added, removed, deleted = repo.status()[:4]
837 modified, added, removed, deleted = repo.status()[:4]
838 if modified or added or removed or deleted:
838 if modified or added or removed or deleted:
839 raise error.Abort(_('uncommitted changes'), hint=hint)
839 raise error.Abort(_('uncommitted changes'), hint=hint)
840 ctx = repo[None]
840 ctx = repo[None]
841 for s in sorted(ctx.substate):
841 for s in sorted(ctx.substate):
842 ctx.sub(s).bailifchanged(hint=hint)
842 ctx.sub(s).bailifchanged(hint=hint)
843
843
844 def logmessage(ui, opts):
844 def logmessage(ui, opts):
845 """ get the log message according to -m and -l option """
845 """ get the log message according to -m and -l option """
846 message = opts.get('message')
846 message = opts.get('message')
847 logfile = opts.get('logfile')
847 logfile = opts.get('logfile')
848
848
849 if message and logfile:
849 if message and logfile:
850 raise error.Abort(_('options --message and --logfile are mutually '
850 raise error.Abort(_('options --message and --logfile are mutually '
851 'exclusive'))
851 'exclusive'))
852 if not message and logfile:
852 if not message and logfile:
853 try:
853 try:
854 if isstdiofilename(logfile):
854 if isstdiofilename(logfile):
855 message = ui.fin.read()
855 message = ui.fin.read()
856 else:
856 else:
857 message = '\n'.join(util.readfile(logfile).splitlines())
857 message = '\n'.join(util.readfile(logfile).splitlines())
858 except IOError as inst:
858 except IOError as inst:
859 raise error.Abort(_("can't read commit message '%s': %s") %
859 raise error.Abort(_("can't read commit message '%s': %s") %
860 (logfile, encoding.strtolocal(inst.strerror)))
860 (logfile, encoding.strtolocal(inst.strerror)))
861 return message
861 return message
862
862
863 def mergeeditform(ctxorbool, baseformname):
863 def mergeeditform(ctxorbool, baseformname):
864 """return appropriate editform name (referencing a committemplate)
864 """return appropriate editform name (referencing a committemplate)
865
865
866 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
866 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
867 merging is committed.
867 merging is committed.
868
868
869 This returns baseformname with '.merge' appended if it is a merge,
869 This returns baseformname with '.merge' appended if it is a merge,
870 otherwise '.normal' is appended.
870 otherwise '.normal' is appended.
871 """
871 """
872 if isinstance(ctxorbool, bool):
872 if isinstance(ctxorbool, bool):
873 if ctxorbool:
873 if ctxorbool:
874 return baseformname + ".merge"
874 return baseformname + ".merge"
875 elif 1 < len(ctxorbool.parents()):
875 elif 1 < len(ctxorbool.parents()):
876 return baseformname + ".merge"
876 return baseformname + ".merge"
877
877
878 return baseformname + ".normal"
878 return baseformname + ".normal"
879
879
880 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
880 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
881 editform='', **opts):
881 editform='', **opts):
882 """get appropriate commit message editor according to '--edit' option
882 """get appropriate commit message editor according to '--edit' option
883
883
884 'finishdesc' is a function to be called with edited commit message
884 'finishdesc' is a function to be called with edited commit message
885 (= 'description' of the new changeset) just after editing, but
885 (= 'description' of the new changeset) just after editing, but
886 before checking empty-ness. It should return actual text to be
886 before checking empty-ness. It should return actual text to be
887 stored into history. This allows to change description before
887 stored into history. This allows to change description before
888 storing.
888 storing.
889
889
890 'extramsg' is a extra message to be shown in the editor instead of
890 'extramsg' is a extra message to be shown in the editor instead of
891 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
891 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
892 is automatically added.
892 is automatically added.
893
893
894 'editform' is a dot-separated list of names, to distinguish
894 'editform' is a dot-separated list of names, to distinguish
895 the purpose of commit text editing.
895 the purpose of commit text editing.
896
896
897 'getcommiteditor' returns 'commitforceeditor' regardless of
897 'getcommiteditor' returns 'commitforceeditor' regardless of
898 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
898 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
899 they are specific for usage in MQ.
899 they are specific for usage in MQ.
900 """
900 """
901 if edit or finishdesc or extramsg:
901 if edit or finishdesc or extramsg:
902 return lambda r, c, s: commitforceeditor(r, c, s,
902 return lambda r, c, s: commitforceeditor(r, c, s,
903 finishdesc=finishdesc,
903 finishdesc=finishdesc,
904 extramsg=extramsg,
904 extramsg=extramsg,
905 editform=editform)
905 editform=editform)
906 elif editform:
906 elif editform:
907 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
907 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
908 else:
908 else:
909 return commiteditor
909 return commiteditor
910
910
911 def makefilename(repo, pat, node, desc=None,
911 def makefilename(repo, pat, node, desc=None,
912 total=None, seqno=None, revwidth=None, pathname=None):
912 total=None, seqno=None, revwidth=None, pathname=None):
913 node_expander = {
913 node_expander = {
914 'H': lambda: hex(node),
914 'H': lambda: hex(node),
915 'R': lambda: '%d' % repo.changelog.rev(node),
915 'R': lambda: '%d' % repo.changelog.rev(node),
916 'h': lambda: short(node),
916 'h': lambda: short(node),
917 'm': lambda: re.sub('[^\w]', '_', desc or '')
917 'm': lambda: re.sub('[^\w]', '_', desc or '')
918 }
918 }
919 expander = {
919 expander = {
920 '%': lambda: '%',
920 '%': lambda: '%',
921 'b': lambda: os.path.basename(repo.root),
921 'b': lambda: os.path.basename(repo.root),
922 }
922 }
923
923
924 try:
924 try:
925 if node:
925 if node:
926 expander.update(node_expander)
926 expander.update(node_expander)
927 if node:
927 if node:
928 expander['r'] = (lambda:
928 expander['r'] = (lambda:
929 ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0))
929 ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0))
930 if total is not None:
930 if total is not None:
931 expander['N'] = lambda: '%d' % total
931 expander['N'] = lambda: '%d' % total
932 if seqno is not None:
932 if seqno is not None:
933 expander['n'] = lambda: '%d' % seqno
933 expander['n'] = lambda: '%d' % seqno
934 if total is not None and seqno is not None:
934 if total is not None and seqno is not None:
935 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
935 expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
936 if pathname is not None:
936 if pathname is not None:
937 expander['s'] = lambda: os.path.basename(pathname)
937 expander['s'] = lambda: os.path.basename(pathname)
938 expander['d'] = lambda: os.path.dirname(pathname) or '.'
938 expander['d'] = lambda: os.path.dirname(pathname) or '.'
939 expander['p'] = lambda: pathname
939 expander['p'] = lambda: pathname
940
940
941 newname = []
941 newname = []
942 patlen = len(pat)
942 patlen = len(pat)
943 i = 0
943 i = 0
944 while i < patlen:
944 while i < patlen:
945 c = pat[i:i + 1]
945 c = pat[i:i + 1]
946 if c == '%':
946 if c == '%':
947 i += 1
947 i += 1
948 c = pat[i:i + 1]
948 c = pat[i:i + 1]
949 c = expander[c]()
949 c = expander[c]()
950 newname.append(c)
950 newname.append(c)
951 i += 1
951 i += 1
952 return ''.join(newname)
952 return ''.join(newname)
953 except KeyError as inst:
953 except KeyError as inst:
954 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
954 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
955 inst.args[0])
955 inst.args[0])
956
956
957 def isstdiofilename(pat):
957 def isstdiofilename(pat):
958 """True if the given pat looks like a filename denoting stdin/stdout"""
958 """True if the given pat looks like a filename denoting stdin/stdout"""
959 return not pat or pat == '-'
959 return not pat or pat == '-'
960
960
961 class _unclosablefile(object):
961 class _unclosablefile(object):
962 def __init__(self, fp):
962 def __init__(self, fp):
963 self._fp = fp
963 self._fp = fp
964
964
965 def close(self):
965 def close(self):
966 pass
966 pass
967
967
968 def __iter__(self):
968 def __iter__(self):
969 return iter(self._fp)
969 return iter(self._fp)
970
970
971 def __getattr__(self, attr):
971 def __getattr__(self, attr):
972 return getattr(self._fp, attr)
972 return getattr(self._fp, attr)
973
973
974 def __enter__(self):
974 def __enter__(self):
975 return self
975 return self
976
976
977 def __exit__(self, exc_type, exc_value, exc_tb):
977 def __exit__(self, exc_type, exc_value, exc_tb):
978 pass
978 pass
979
979
980 def makefileobj(repo, pat, node=None, desc=None, total=None,
980 def makefileobj(repo, pat, node=None, desc=None, total=None,
981 seqno=None, revwidth=None, mode='wb', modemap=None,
981 seqno=None, revwidth=None, mode='wb', modemap=None,
982 pathname=None):
982 pathname=None):
983
983
984 writable = mode not in ('r', 'rb')
984 writable = mode not in ('r', 'rb')
985
985
986 if isstdiofilename(pat):
986 if isstdiofilename(pat):
987 if writable:
987 if writable:
988 fp = repo.ui.fout
988 fp = repo.ui.fout
989 else:
989 else:
990 fp = repo.ui.fin
990 fp = repo.ui.fin
991 return _unclosablefile(fp)
991 return _unclosablefile(fp)
992 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
992 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
993 if modemap is not None:
993 if modemap is not None:
994 mode = modemap.get(fn, mode)
994 mode = modemap.get(fn, mode)
995 if mode == 'wb':
995 if mode == 'wb':
996 modemap[fn] = 'ab'
996 modemap[fn] = 'ab'
997 return open(fn, mode)
997 return open(fn, mode)
998
998
999 def openrevlog(repo, cmd, file_, opts):
999 def openrevlog(repo, cmd, file_, opts):
1000 """opens the changelog, manifest, a filelog or a given revlog"""
1000 """opens the changelog, manifest, a filelog or a given revlog"""
1001 cl = opts['changelog']
1001 cl = opts['changelog']
1002 mf = opts['manifest']
1002 mf = opts['manifest']
1003 dir = opts['dir']
1003 dir = opts['dir']
1004 msg = None
1004 msg = None
1005 if cl and mf:
1005 if cl and mf:
1006 msg = _('cannot specify --changelog and --manifest at the same time')
1006 msg = _('cannot specify --changelog and --manifest at the same time')
1007 elif cl and dir:
1007 elif cl and dir:
1008 msg = _('cannot specify --changelog and --dir at the same time')
1008 msg = _('cannot specify --changelog and --dir at the same time')
1009 elif cl or mf or dir:
1009 elif cl or mf or dir:
1010 if file_:
1010 if file_:
1011 msg = _('cannot specify filename with --changelog or --manifest')
1011 msg = _('cannot specify filename with --changelog or --manifest')
1012 elif not repo:
1012 elif not repo:
1013 msg = _('cannot specify --changelog or --manifest or --dir '
1013 msg = _('cannot specify --changelog or --manifest or --dir '
1014 'without a repository')
1014 'without a repository')
1015 if msg:
1015 if msg:
1016 raise error.Abort(msg)
1016 raise error.Abort(msg)
1017
1017
1018 r = None
1018 r = None
1019 if repo:
1019 if repo:
1020 if cl:
1020 if cl:
1021 r = repo.unfiltered().changelog
1021 r = repo.unfiltered().changelog
1022 elif dir:
1022 elif dir:
1023 if 'treemanifest' not in repo.requirements:
1023 if 'treemanifest' not in repo.requirements:
1024 raise error.Abort(_("--dir can only be used on repos with "
1024 raise error.Abort(_("--dir can only be used on repos with "
1025 "treemanifest enabled"))
1025 "treemanifest enabled"))
1026 dirlog = repo.manifestlog._revlog.dirlog(dir)
1026 dirlog = repo.manifestlog._revlog.dirlog(dir)
1027 if len(dirlog):
1027 if len(dirlog):
1028 r = dirlog
1028 r = dirlog
1029 elif mf:
1029 elif mf:
1030 r = repo.manifestlog._revlog
1030 r = repo.manifestlog._revlog
1031 elif file_:
1031 elif file_:
1032 filelog = repo.file(file_)
1032 filelog = repo.file(file_)
1033 if len(filelog):
1033 if len(filelog):
1034 r = filelog
1034 r = filelog
1035 if not r:
1035 if not r:
1036 if not file_:
1036 if not file_:
1037 raise error.CommandError(cmd, _('invalid arguments'))
1037 raise error.CommandError(cmd, _('invalid arguments'))
1038 if not os.path.isfile(file_):
1038 if not os.path.isfile(file_):
1039 raise error.Abort(_("revlog '%s' not found") % file_)
1039 raise error.Abort(_("revlog '%s' not found") % file_)
1040 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1040 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
1041 file_[:-2] + ".i")
1041 file_[:-2] + ".i")
1042 return r
1042 return r
1043
1043
1044 def copy(ui, repo, pats, opts, rename=False):
1044 def copy(ui, repo, pats, opts, rename=False):
1045 # called with the repo lock held
1045 # called with the repo lock held
1046 #
1046 #
1047 # hgsep => pathname that uses "/" to separate directories
1047 # hgsep => pathname that uses "/" to separate directories
1048 # ossep => pathname that uses os.sep to separate directories
1048 # ossep => pathname that uses os.sep to separate directories
1049 cwd = repo.getcwd()
1049 cwd = repo.getcwd()
1050 targets = {}
1050 targets = {}
1051 after = opts.get("after")
1051 after = opts.get("after")
1052 dryrun = opts.get("dry_run")
1052 dryrun = opts.get("dry_run")
1053 wctx = repo[None]
1053 wctx = repo[None]
1054
1054
1055 def walkpat(pat):
1055 def walkpat(pat):
1056 srcs = []
1056 srcs = []
1057 if after:
1057 if after:
1058 badstates = '?'
1058 badstates = '?'
1059 else:
1059 else:
1060 badstates = '?r'
1060 badstates = '?r'
1061 m = scmutil.match(wctx, [pat], opts, globbed=True)
1061 m = scmutil.match(wctx, [pat], opts, globbed=True)
1062 for abs in wctx.walk(m):
1062 for abs in wctx.walk(m):
1063 state = repo.dirstate[abs]
1063 state = repo.dirstate[abs]
1064 rel = m.rel(abs)
1064 rel = m.rel(abs)
1065 exact = m.exact(abs)
1065 exact = m.exact(abs)
1066 if state in badstates:
1066 if state in badstates:
1067 if exact and state == '?':
1067 if exact and state == '?':
1068 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1068 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1069 if exact and state == 'r':
1069 if exact and state == 'r':
1070 ui.warn(_('%s: not copying - file has been marked for'
1070 ui.warn(_('%s: not copying - file has been marked for'
1071 ' remove\n') % rel)
1071 ' remove\n') % rel)
1072 continue
1072 continue
1073 # abs: hgsep
1073 # abs: hgsep
1074 # rel: ossep
1074 # rel: ossep
1075 srcs.append((abs, rel, exact))
1075 srcs.append((abs, rel, exact))
1076 return srcs
1076 return srcs
1077
1077
1078 # abssrc: hgsep
1078 # abssrc: hgsep
1079 # relsrc: ossep
1079 # relsrc: ossep
1080 # otarget: ossep
1080 # otarget: ossep
1081 def copyfile(abssrc, relsrc, otarget, exact):
1081 def copyfile(abssrc, relsrc, otarget, exact):
1082 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1082 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1083 if '/' in abstarget:
1083 if '/' in abstarget:
1084 # We cannot normalize abstarget itself, this would prevent
1084 # We cannot normalize abstarget itself, this would prevent
1085 # case only renames, like a => A.
1085 # case only renames, like a => A.
1086 abspath, absname = abstarget.rsplit('/', 1)
1086 abspath, absname = abstarget.rsplit('/', 1)
1087 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1087 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1088 reltarget = repo.pathto(abstarget, cwd)
1088 reltarget = repo.pathto(abstarget, cwd)
1089 target = repo.wjoin(abstarget)
1089 target = repo.wjoin(abstarget)
1090 src = repo.wjoin(abssrc)
1090 src = repo.wjoin(abssrc)
1091 state = repo.dirstate[abstarget]
1091 state = repo.dirstate[abstarget]
1092
1092
1093 scmutil.checkportable(ui, abstarget)
1093 scmutil.checkportable(ui, abstarget)
1094
1094
1095 # check for collisions
1095 # check for collisions
1096 prevsrc = targets.get(abstarget)
1096 prevsrc = targets.get(abstarget)
1097 if prevsrc is not None:
1097 if prevsrc is not None:
1098 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1098 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1099 (reltarget, repo.pathto(abssrc, cwd),
1099 (reltarget, repo.pathto(abssrc, cwd),
1100 repo.pathto(prevsrc, cwd)))
1100 repo.pathto(prevsrc, cwd)))
1101 return
1101 return
1102
1102
1103 # check for overwrites
1103 # check for overwrites
1104 exists = os.path.lexists(target)
1104 exists = os.path.lexists(target)
1105 samefile = False
1105 samefile = False
1106 if exists and abssrc != abstarget:
1106 if exists and abssrc != abstarget:
1107 if (repo.dirstate.normalize(abssrc) ==
1107 if (repo.dirstate.normalize(abssrc) ==
1108 repo.dirstate.normalize(abstarget)):
1108 repo.dirstate.normalize(abstarget)):
1109 if not rename:
1109 if not rename:
1110 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1110 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1111 return
1111 return
1112 exists = False
1112 exists = False
1113 samefile = True
1113 samefile = True
1114
1114
1115 if not after and exists or after and state in 'mn':
1115 if not after and exists or after and state in 'mn':
1116 if not opts['force']:
1116 if not opts['force']:
1117 if state in 'mn':
1117 if state in 'mn':
1118 msg = _('%s: not overwriting - file already committed\n')
1118 msg = _('%s: not overwriting - file already committed\n')
1119 if after:
1119 if after:
1120 flags = '--after --force'
1120 flags = '--after --force'
1121 else:
1121 else:
1122 flags = '--force'
1122 flags = '--force'
1123 if rename:
1123 if rename:
1124 hint = _('(hg rename %s to replace the file by '
1124 hint = _('(hg rename %s to replace the file by '
1125 'recording a rename)\n') % flags
1125 'recording a rename)\n') % flags
1126 else:
1126 else:
1127 hint = _('(hg copy %s to replace the file by '
1127 hint = _('(hg copy %s to replace the file by '
1128 'recording a copy)\n') % flags
1128 'recording a copy)\n') % flags
1129 else:
1129 else:
1130 msg = _('%s: not overwriting - file exists\n')
1130 msg = _('%s: not overwriting - file exists\n')
1131 if rename:
1131 if rename:
1132 hint = _('(hg rename --after to record the rename)\n')
1132 hint = _('(hg rename --after to record the rename)\n')
1133 else:
1133 else:
1134 hint = _('(hg copy --after to record the copy)\n')
1134 hint = _('(hg copy --after to record the copy)\n')
1135 ui.warn(msg % reltarget)
1135 ui.warn(msg % reltarget)
1136 ui.warn(hint)
1136 ui.warn(hint)
1137 return
1137 return
1138
1138
1139 if after:
1139 if after:
1140 if not exists:
1140 if not exists:
1141 if rename:
1141 if rename:
1142 ui.warn(_('%s: not recording move - %s does not exist\n') %
1142 ui.warn(_('%s: not recording move - %s does not exist\n') %
1143 (relsrc, reltarget))
1143 (relsrc, reltarget))
1144 else:
1144 else:
1145 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1145 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1146 (relsrc, reltarget))
1146 (relsrc, reltarget))
1147 return
1147 return
1148 elif not dryrun:
1148 elif not dryrun:
1149 try:
1149 try:
1150 if exists:
1150 if exists:
1151 os.unlink(target)
1151 os.unlink(target)
1152 targetdir = os.path.dirname(target) or '.'
1152 targetdir = os.path.dirname(target) or '.'
1153 if not os.path.isdir(targetdir):
1153 if not os.path.isdir(targetdir):
1154 os.makedirs(targetdir)
1154 os.makedirs(targetdir)
1155 if samefile:
1155 if samefile:
1156 tmp = target + "~hgrename"
1156 tmp = target + "~hgrename"
1157 os.rename(src, tmp)
1157 os.rename(src, tmp)
1158 os.rename(tmp, target)
1158 os.rename(tmp, target)
1159 else:
1159 else:
1160 util.copyfile(src, target)
1160 util.copyfile(src, target)
1161 srcexists = True
1161 srcexists = True
1162 except IOError as inst:
1162 except IOError as inst:
1163 if inst.errno == errno.ENOENT:
1163 if inst.errno == errno.ENOENT:
1164 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1164 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1165 srcexists = False
1165 srcexists = False
1166 else:
1166 else:
1167 ui.warn(_('%s: cannot copy - %s\n') %
1167 ui.warn(_('%s: cannot copy - %s\n') %
1168 (relsrc, encoding.strtolocal(inst.strerror)))
1168 (relsrc, encoding.strtolocal(inst.strerror)))
1169 return True # report a failure
1169 return True # report a failure
1170
1170
1171 if ui.verbose or not exact:
1171 if ui.verbose or not exact:
1172 if rename:
1172 if rename:
1173 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1173 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1174 else:
1174 else:
1175 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1175 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1176
1176
1177 targets[abstarget] = abssrc
1177 targets[abstarget] = abssrc
1178
1178
1179 # fix up dirstate
1179 # fix up dirstate
1180 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1180 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1181 dryrun=dryrun, cwd=cwd)
1181 dryrun=dryrun, cwd=cwd)
1182 if rename and not dryrun:
1182 if rename and not dryrun:
1183 if not after and srcexists and not samefile:
1183 if not after and srcexists and not samefile:
1184 repo.wvfs.unlinkpath(abssrc)
1184 repo.wvfs.unlinkpath(abssrc)
1185 wctx.forget([abssrc])
1185 wctx.forget([abssrc])
1186
1186
1187 # pat: ossep
1187 # pat: ossep
1188 # dest ossep
1188 # dest ossep
1189 # srcs: list of (hgsep, hgsep, ossep, bool)
1189 # srcs: list of (hgsep, hgsep, ossep, bool)
1190 # return: function that takes hgsep and returns ossep
1190 # return: function that takes hgsep and returns ossep
1191 def targetpathfn(pat, dest, srcs):
1191 def targetpathfn(pat, dest, srcs):
1192 if os.path.isdir(pat):
1192 if os.path.isdir(pat):
1193 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1193 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1194 abspfx = util.localpath(abspfx)
1194 abspfx = util.localpath(abspfx)
1195 if destdirexists:
1195 if destdirexists:
1196 striplen = len(os.path.split(abspfx)[0])
1196 striplen = len(os.path.split(abspfx)[0])
1197 else:
1197 else:
1198 striplen = len(abspfx)
1198 striplen = len(abspfx)
1199 if striplen:
1199 if striplen:
1200 striplen += len(pycompat.ossep)
1200 striplen += len(pycompat.ossep)
1201 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1201 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1202 elif destdirexists:
1202 elif destdirexists:
1203 res = lambda p: os.path.join(dest,
1203 res = lambda p: os.path.join(dest,
1204 os.path.basename(util.localpath(p)))
1204 os.path.basename(util.localpath(p)))
1205 else:
1205 else:
1206 res = lambda p: dest
1206 res = lambda p: dest
1207 return res
1207 return res
1208
1208
1209 # pat: ossep
1209 # pat: ossep
1210 # dest ossep
1210 # dest ossep
1211 # srcs: list of (hgsep, hgsep, ossep, bool)
1211 # srcs: list of (hgsep, hgsep, ossep, bool)
1212 # return: function that takes hgsep and returns ossep
1212 # return: function that takes hgsep and returns ossep
1213 def targetpathafterfn(pat, dest, srcs):
1213 def targetpathafterfn(pat, dest, srcs):
1214 if matchmod.patkind(pat):
1214 if matchmod.patkind(pat):
1215 # a mercurial pattern
1215 # a mercurial pattern
1216 res = lambda p: os.path.join(dest,
1216 res = lambda p: os.path.join(dest,
1217 os.path.basename(util.localpath(p)))
1217 os.path.basename(util.localpath(p)))
1218 else:
1218 else:
1219 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1219 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1220 if len(abspfx) < len(srcs[0][0]):
1220 if len(abspfx) < len(srcs[0][0]):
1221 # A directory. Either the target path contains the last
1221 # A directory. Either the target path contains the last
1222 # component of the source path or it does not.
1222 # component of the source path or it does not.
1223 def evalpath(striplen):
1223 def evalpath(striplen):
1224 score = 0
1224 score = 0
1225 for s in srcs:
1225 for s in srcs:
1226 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1226 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1227 if os.path.lexists(t):
1227 if os.path.lexists(t):
1228 score += 1
1228 score += 1
1229 return score
1229 return score
1230
1230
1231 abspfx = util.localpath(abspfx)
1231 abspfx = util.localpath(abspfx)
1232 striplen = len(abspfx)
1232 striplen = len(abspfx)
1233 if striplen:
1233 if striplen:
1234 striplen += len(pycompat.ossep)
1234 striplen += len(pycompat.ossep)
1235 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1235 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1236 score = evalpath(striplen)
1236 score = evalpath(striplen)
1237 striplen1 = len(os.path.split(abspfx)[0])
1237 striplen1 = len(os.path.split(abspfx)[0])
1238 if striplen1:
1238 if striplen1:
1239 striplen1 += len(pycompat.ossep)
1239 striplen1 += len(pycompat.ossep)
1240 if evalpath(striplen1) > score:
1240 if evalpath(striplen1) > score:
1241 striplen = striplen1
1241 striplen = striplen1
1242 res = lambda p: os.path.join(dest,
1242 res = lambda p: os.path.join(dest,
1243 util.localpath(p)[striplen:])
1243 util.localpath(p)[striplen:])
1244 else:
1244 else:
1245 # a file
1245 # a file
1246 if destdirexists:
1246 if destdirexists:
1247 res = lambda p: os.path.join(dest,
1247 res = lambda p: os.path.join(dest,
1248 os.path.basename(util.localpath(p)))
1248 os.path.basename(util.localpath(p)))
1249 else:
1249 else:
1250 res = lambda p: dest
1250 res = lambda p: dest
1251 return res
1251 return res
1252
1252
1253 pats = scmutil.expandpats(pats)
1253 pats = scmutil.expandpats(pats)
1254 if not pats:
1254 if not pats:
1255 raise error.Abort(_('no source or destination specified'))
1255 raise error.Abort(_('no source or destination specified'))
1256 if len(pats) == 1:
1256 if len(pats) == 1:
1257 raise error.Abort(_('no destination specified'))
1257 raise error.Abort(_('no destination specified'))
1258 dest = pats.pop()
1258 dest = pats.pop()
1259 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1259 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1260 if not destdirexists:
1260 if not destdirexists:
1261 if len(pats) > 1 or matchmod.patkind(pats[0]):
1261 if len(pats) > 1 or matchmod.patkind(pats[0]):
1262 raise error.Abort(_('with multiple sources, destination must be an '
1262 raise error.Abort(_('with multiple sources, destination must be an '
1263 'existing directory'))
1263 'existing directory'))
1264 if util.endswithsep(dest):
1264 if util.endswithsep(dest):
1265 raise error.Abort(_('destination %s is not a directory') % dest)
1265 raise error.Abort(_('destination %s is not a directory') % dest)
1266
1266
1267 tfn = targetpathfn
1267 tfn = targetpathfn
1268 if after:
1268 if after:
1269 tfn = targetpathafterfn
1269 tfn = targetpathafterfn
1270 copylist = []
1270 copylist = []
1271 for pat in pats:
1271 for pat in pats:
1272 srcs = walkpat(pat)
1272 srcs = walkpat(pat)
1273 if not srcs:
1273 if not srcs:
1274 continue
1274 continue
1275 copylist.append((tfn(pat, dest, srcs), srcs))
1275 copylist.append((tfn(pat, dest, srcs), srcs))
1276 if not copylist:
1276 if not copylist:
1277 raise error.Abort(_('no files to copy'))
1277 raise error.Abort(_('no files to copy'))
1278
1278
1279 errors = 0
1279 errors = 0
1280 for targetpath, srcs in copylist:
1280 for targetpath, srcs in copylist:
1281 for abssrc, relsrc, exact in srcs:
1281 for abssrc, relsrc, exact in srcs:
1282 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1282 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1283 errors += 1
1283 errors += 1
1284
1284
1285 if errors:
1285 if errors:
1286 ui.warn(_('(consider using --after)\n'))
1286 ui.warn(_('(consider using --after)\n'))
1287
1287
1288 return errors != 0
1288 return errors != 0
1289
1289
1290 ## facility to let extension process additional data into an import patch
1290 ## facility to let extension process additional data into an import patch
1291 # list of identifier to be executed in order
1291 # list of identifier to be executed in order
1292 extrapreimport = [] # run before commit
1292 extrapreimport = [] # run before commit
1293 extrapostimport = [] # run after commit
1293 extrapostimport = [] # run after commit
1294 # mapping from identifier to actual import function
1294 # mapping from identifier to actual import function
1295 #
1295 #
1296 # 'preimport' are run before the commit is made and are provided the following
1296 # 'preimport' are run before the commit is made and are provided the following
1297 # arguments:
1297 # arguments:
1298 # - repo: the localrepository instance,
1298 # - repo: the localrepository instance,
1299 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1299 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1300 # - extra: the future extra dictionary of the changeset, please mutate it,
1300 # - extra: the future extra dictionary of the changeset, please mutate it,
1301 # - opts: the import options.
1301 # - opts: the import options.
1302 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1302 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1303 # mutation of in memory commit and more. Feel free to rework the code to get
1303 # mutation of in memory commit and more. Feel free to rework the code to get
1304 # there.
1304 # there.
1305 extrapreimportmap = {}
1305 extrapreimportmap = {}
1306 # 'postimport' are run after the commit is made and are provided the following
1306 # 'postimport' are run after the commit is made and are provided the following
1307 # argument:
1307 # argument:
1308 # - ctx: the changectx created by import.
1308 # - ctx: the changectx created by import.
1309 extrapostimportmap = {}
1309 extrapostimportmap = {}
1310
1310
1311 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1311 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
1312 """Utility function used by commands.import to import a single patch
1312 """Utility function used by commands.import to import a single patch
1313
1313
1314 This function is explicitly defined here to help the evolve extension to
1314 This function is explicitly defined here to help the evolve extension to
1315 wrap this part of the import logic.
1315 wrap this part of the import logic.
1316
1316
1317 The API is currently a bit ugly because it a simple code translation from
1317 The API is currently a bit ugly because it a simple code translation from
1318 the import command. Feel free to make it better.
1318 the import command. Feel free to make it better.
1319
1319
1320 :hunk: a patch (as a binary string)
1320 :hunk: a patch (as a binary string)
1321 :parents: nodes that will be parent of the created commit
1321 :parents: nodes that will be parent of the created commit
1322 :opts: the full dict of option passed to the import command
1322 :opts: the full dict of option passed to the import command
1323 :msgs: list to save commit message to.
1323 :msgs: list to save commit message to.
1324 (used in case we need to save it when failing)
1324 (used in case we need to save it when failing)
1325 :updatefunc: a function that update a repo to a given node
1325 :updatefunc: a function that update a repo to a given node
1326 updatefunc(<repo>, <node>)
1326 updatefunc(<repo>, <node>)
1327 """
1327 """
1328 # avoid cycle context -> subrepo -> cmdutil
1328 # avoid cycle context -> subrepo -> cmdutil
1329 from . import context
1329 from . import context
1330 extractdata = patch.extract(ui, hunk)
1330 extractdata = patch.extract(ui, hunk)
1331 tmpname = extractdata.get('filename')
1331 tmpname = extractdata.get('filename')
1332 message = extractdata.get('message')
1332 message = extractdata.get('message')
1333 user = opts.get('user') or extractdata.get('user')
1333 user = opts.get('user') or extractdata.get('user')
1334 date = opts.get('date') or extractdata.get('date')
1334 date = opts.get('date') or extractdata.get('date')
1335 branch = extractdata.get('branch')
1335 branch = extractdata.get('branch')
1336 nodeid = extractdata.get('nodeid')
1336 nodeid = extractdata.get('nodeid')
1337 p1 = extractdata.get('p1')
1337 p1 = extractdata.get('p1')
1338 p2 = extractdata.get('p2')
1338 p2 = extractdata.get('p2')
1339
1339
1340 nocommit = opts.get('no_commit')
1340 nocommit = opts.get('no_commit')
1341 importbranch = opts.get('import_branch')
1341 importbranch = opts.get('import_branch')
1342 update = not opts.get('bypass')
1342 update = not opts.get('bypass')
1343 strip = opts["strip"]
1343 strip = opts["strip"]
1344 prefix = opts["prefix"]
1344 prefix = opts["prefix"]
1345 sim = float(opts.get('similarity') or 0)
1345 sim = float(opts.get('similarity') or 0)
1346 if not tmpname:
1346 if not tmpname:
1347 return (None, None, False)
1347 return (None, None, False)
1348
1348
1349 rejects = False
1349 rejects = False
1350
1350
1351 try:
1351 try:
1352 cmdline_message = logmessage(ui, opts)
1352 cmdline_message = logmessage(ui, opts)
1353 if cmdline_message:
1353 if cmdline_message:
1354 # pickup the cmdline msg
1354 # pickup the cmdline msg
1355 message = cmdline_message
1355 message = cmdline_message
1356 elif message:
1356 elif message:
1357 # pickup the patch msg
1357 # pickup the patch msg
1358 message = message.strip()
1358 message = message.strip()
1359 else:
1359 else:
1360 # launch the editor
1360 # launch the editor
1361 message = None
1361 message = None
1362 ui.debug('message:\n%s\n' % message)
1362 ui.debug('message:\n%s\n' % message)
1363
1363
1364 if len(parents) == 1:
1364 if len(parents) == 1:
1365 parents.append(repo[nullid])
1365 parents.append(repo[nullid])
1366 if opts.get('exact'):
1366 if opts.get('exact'):
1367 if not nodeid or not p1:
1367 if not nodeid or not p1:
1368 raise error.Abort(_('not a Mercurial patch'))
1368 raise error.Abort(_('not a Mercurial patch'))
1369 p1 = repo[p1]
1369 p1 = repo[p1]
1370 p2 = repo[p2 or nullid]
1370 p2 = repo[p2 or nullid]
1371 elif p2:
1371 elif p2:
1372 try:
1372 try:
1373 p1 = repo[p1]
1373 p1 = repo[p1]
1374 p2 = repo[p2]
1374 p2 = repo[p2]
1375 # Without any options, consider p2 only if the
1375 # Without any options, consider p2 only if the
1376 # patch is being applied on top of the recorded
1376 # patch is being applied on top of the recorded
1377 # first parent.
1377 # first parent.
1378 if p1 != parents[0]:
1378 if p1 != parents[0]:
1379 p1 = parents[0]
1379 p1 = parents[0]
1380 p2 = repo[nullid]
1380 p2 = repo[nullid]
1381 except error.RepoError:
1381 except error.RepoError:
1382 p1, p2 = parents
1382 p1, p2 = parents
1383 if p2.node() == nullid:
1383 if p2.node() == nullid:
1384 ui.warn(_("warning: import the patch as a normal revision\n"
1384 ui.warn(_("warning: import the patch as a normal revision\n"
1385 "(use --exact to import the patch as a merge)\n"))
1385 "(use --exact to import the patch as a merge)\n"))
1386 else:
1386 else:
1387 p1, p2 = parents
1387 p1, p2 = parents
1388
1388
1389 n = None
1389 n = None
1390 if update:
1390 if update:
1391 if p1 != parents[0]:
1391 if p1 != parents[0]:
1392 updatefunc(repo, p1.node())
1392 updatefunc(repo, p1.node())
1393 if p2 != parents[1]:
1393 if p2 != parents[1]:
1394 repo.setparents(p1.node(), p2.node())
1394 repo.setparents(p1.node(), p2.node())
1395
1395
1396 if opts.get('exact') or importbranch:
1396 if opts.get('exact') or importbranch:
1397 repo.dirstate.setbranch(branch or 'default')
1397 repo.dirstate.setbranch(branch or 'default')
1398
1398
1399 partial = opts.get('partial', False)
1399 partial = opts.get('partial', False)
1400 files = set()
1400 files = set()
1401 try:
1401 try:
1402 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1402 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1403 files=files, eolmode=None, similarity=sim / 100.0)
1403 files=files, eolmode=None, similarity=sim / 100.0)
1404 except error.PatchError as e:
1404 except error.PatchError as e:
1405 if not partial:
1405 if not partial:
1406 raise error.Abort(str(e))
1406 raise error.Abort(str(e))
1407 if partial:
1407 if partial:
1408 rejects = True
1408 rejects = True
1409
1409
1410 files = list(files)
1410 files = list(files)
1411 if nocommit:
1411 if nocommit:
1412 if message:
1412 if message:
1413 msgs.append(message)
1413 msgs.append(message)
1414 else:
1414 else:
1415 if opts.get('exact') or p2:
1415 if opts.get('exact') or p2:
1416 # If you got here, you either use --force and know what
1416 # If you got here, you either use --force and know what
1417 # you are doing or used --exact or a merge patch while
1417 # you are doing or used --exact or a merge patch while
1418 # being updated to its first parent.
1418 # being updated to its first parent.
1419 m = None
1419 m = None
1420 else:
1420 else:
1421 m = scmutil.matchfiles(repo, files or [])
1421 m = scmutil.matchfiles(repo, files or [])
1422 editform = mergeeditform(repo[None], 'import.normal')
1422 editform = mergeeditform(repo[None], 'import.normal')
1423 if opts.get('exact'):
1423 if opts.get('exact'):
1424 editor = None
1424 editor = None
1425 else:
1425 else:
1426 editor = getcommiteditor(editform=editform,
1426 editor = getcommiteditor(editform=editform,
1427 **pycompat.strkwargs(opts))
1427 **pycompat.strkwargs(opts))
1428 extra = {}
1428 extra = {}
1429 for idfunc in extrapreimport:
1429 for idfunc in extrapreimport:
1430 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1430 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1431 overrides = {}
1431 overrides = {}
1432 if partial:
1432 if partial:
1433 overrides[('ui', 'allowemptycommit')] = True
1433 overrides[('ui', 'allowemptycommit')] = True
1434 with repo.ui.configoverride(overrides, 'import'):
1434 with repo.ui.configoverride(overrides, 'import'):
1435 n = repo.commit(message, user,
1435 n = repo.commit(message, user,
1436 date, match=m,
1436 date, match=m,
1437 editor=editor, extra=extra)
1437 editor=editor, extra=extra)
1438 for idfunc in extrapostimport:
1438 for idfunc in extrapostimport:
1439 extrapostimportmap[idfunc](repo[n])
1439 extrapostimportmap[idfunc](repo[n])
1440 else:
1440 else:
1441 if opts.get('exact') or importbranch:
1441 if opts.get('exact') or importbranch:
1442 branch = branch or 'default'
1442 branch = branch or 'default'
1443 else:
1443 else:
1444 branch = p1.branch()
1444 branch = p1.branch()
1445 store = patch.filestore()
1445 store = patch.filestore()
1446 try:
1446 try:
1447 files = set()
1447 files = set()
1448 try:
1448 try:
1449 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1449 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1450 files, eolmode=None)
1450 files, eolmode=None)
1451 except error.PatchError as e:
1451 except error.PatchError as e:
1452 raise error.Abort(str(e))
1452 raise error.Abort(str(e))
1453 if opts.get('exact'):
1453 if opts.get('exact'):
1454 editor = None
1454 editor = None
1455 else:
1455 else:
1456 editor = getcommiteditor(editform='import.bypass')
1456 editor = getcommiteditor(editform='import.bypass')
1457 memctx = context.memctx(repo, (p1.node(), p2.node()),
1457 memctx = context.memctx(repo, (p1.node(), p2.node()),
1458 message,
1458 message,
1459 files=files,
1459 files=files,
1460 filectxfn=store,
1460 filectxfn=store,
1461 user=user,
1461 user=user,
1462 date=date,
1462 date=date,
1463 branch=branch,
1463 branch=branch,
1464 editor=editor)
1464 editor=editor)
1465 n = memctx.commit()
1465 n = memctx.commit()
1466 finally:
1466 finally:
1467 store.close()
1467 store.close()
1468 if opts.get('exact') and nocommit:
1468 if opts.get('exact') and nocommit:
1469 # --exact with --no-commit is still useful in that it does merge
1469 # --exact with --no-commit is still useful in that it does merge
1470 # and branch bits
1470 # and branch bits
1471 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1471 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1472 elif opts.get('exact') and hex(n) != nodeid:
1472 elif opts.get('exact') and hex(n) != nodeid:
1473 raise error.Abort(_('patch is damaged or loses information'))
1473 raise error.Abort(_('patch is damaged or loses information'))
1474 msg = _('applied to working directory')
1474 msg = _('applied to working directory')
1475 if n:
1475 if n:
1476 # i18n: refers to a short changeset id
1476 # i18n: refers to a short changeset id
1477 msg = _('created %s') % short(n)
1477 msg = _('created %s') % short(n)
1478 return (msg, n, rejects)
1478 return (msg, n, rejects)
1479 finally:
1479 finally:
1480 os.unlink(tmpname)
1480 os.unlink(tmpname)
1481
1481
1482 # facility to let extensions include additional data in an exported patch
1482 # facility to let extensions include additional data in an exported patch
1483 # list of identifiers to be executed in order
1483 # list of identifiers to be executed in order
1484 extraexport = []
1484 extraexport = []
1485 # mapping from identifier to actual export function
1485 # mapping from identifier to actual export function
1486 # function as to return a string to be added to the header or None
1486 # function as to return a string to be added to the header or None
1487 # it is given two arguments (sequencenumber, changectx)
1487 # it is given two arguments (sequencenumber, changectx)
1488 extraexportmap = {}
1488 extraexportmap = {}
1489
1489
1490 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1490 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1491 node = scmutil.binnode(ctx)
1491 node = scmutil.binnode(ctx)
1492 parents = [p.node() for p in ctx.parents() if p]
1492 parents = [p.node() for p in ctx.parents() if p]
1493 branch = ctx.branch()
1493 branch = ctx.branch()
1494 if switch_parent:
1494 if switch_parent:
1495 parents.reverse()
1495 parents.reverse()
1496
1496
1497 if parents:
1497 if parents:
1498 prev = parents[0]
1498 prev = parents[0]
1499 else:
1499 else:
1500 prev = nullid
1500 prev = nullid
1501
1501
1502 write("# HG changeset patch\n")
1502 write("# HG changeset patch\n")
1503 write("# User %s\n" % ctx.user())
1503 write("# User %s\n" % ctx.user())
1504 write("# Date %d %d\n" % ctx.date())
1504 write("# Date %d %d\n" % ctx.date())
1505 write("# %s\n" % util.datestr(ctx.date()))
1505 write("# %s\n" % util.datestr(ctx.date()))
1506 if branch and branch != 'default':
1506 if branch and branch != 'default':
1507 write("# Branch %s\n" % branch)
1507 write("# Branch %s\n" % branch)
1508 write("# Node ID %s\n" % hex(node))
1508 write("# Node ID %s\n" % hex(node))
1509 write("# Parent %s\n" % hex(prev))
1509 write("# Parent %s\n" % hex(prev))
1510 if len(parents) > 1:
1510 if len(parents) > 1:
1511 write("# Parent %s\n" % hex(parents[1]))
1511 write("# Parent %s\n" % hex(parents[1]))
1512
1512
1513 for headerid in extraexport:
1513 for headerid in extraexport:
1514 header = extraexportmap[headerid](seqno, ctx)
1514 header = extraexportmap[headerid](seqno, ctx)
1515 if header is not None:
1515 if header is not None:
1516 write('# %s\n' % header)
1516 write('# %s\n' % header)
1517 write(ctx.description().rstrip())
1517 write(ctx.description().rstrip())
1518 write("\n\n")
1518 write("\n\n")
1519
1519
1520 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1520 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1521 write(chunk, label=label)
1521 write(chunk, label=label)
1522
1522
1523 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1523 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1524 opts=None, match=None):
1524 opts=None, match=None):
1525 '''export changesets as hg patches
1525 '''export changesets as hg patches
1526
1526
1527 Args:
1527 Args:
1528 repo: The repository from which we're exporting revisions.
1528 repo: The repository from which we're exporting revisions.
1529 revs: A list of revisions to export as revision numbers.
1529 revs: A list of revisions to export as revision numbers.
1530 fntemplate: An optional string to use for generating patch file names.
1530 fntemplate: An optional string to use for generating patch file names.
1531 fp: An optional file-like object to which patches should be written.
1531 fp: An optional file-like object to which patches should be written.
1532 switch_parent: If True, show diffs against second parent when not nullid.
1532 switch_parent: If True, show diffs against second parent when not nullid.
1533 Default is false, which always shows diff against p1.
1533 Default is false, which always shows diff against p1.
1534 opts: diff options to use for generating the patch.
1534 opts: diff options to use for generating the patch.
1535 match: If specified, only export changes to files matching this matcher.
1535 match: If specified, only export changes to files matching this matcher.
1536
1536
1537 Returns:
1537 Returns:
1538 Nothing.
1538 Nothing.
1539
1539
1540 Side Effect:
1540 Side Effect:
1541 "HG Changeset Patch" data is emitted to one of the following
1541 "HG Changeset Patch" data is emitted to one of the following
1542 destinations:
1542 destinations:
1543 fp is specified: All revs are written to the specified
1543 fp is specified: All revs are written to the specified
1544 file-like object.
1544 file-like object.
1545 fntemplate specified: Each rev is written to a unique file named using
1545 fntemplate specified: Each rev is written to a unique file named using
1546 the given template.
1546 the given template.
1547 Neither fp nor template specified: All revs written to repo.ui.write()
1547 Neither fp nor template specified: All revs written to repo.ui.write()
1548 '''
1548 '''
1549
1549
1550 total = len(revs)
1550 total = len(revs)
1551 revwidth = max(len(str(rev)) for rev in revs)
1551 revwidth = max(len(str(rev)) for rev in revs)
1552 filemode = {}
1552 filemode = {}
1553
1553
1554 write = None
1554 write = None
1555 dest = '<unnamed>'
1555 dest = '<unnamed>'
1556 if fp:
1556 if fp:
1557 dest = getattr(fp, 'name', dest)
1557 dest = getattr(fp, 'name', dest)
1558 def write(s, **kw):
1558 def write(s, **kw):
1559 fp.write(s)
1559 fp.write(s)
1560 elif not fntemplate:
1560 elif not fntemplate:
1561 write = repo.ui.write
1561 write = repo.ui.write
1562
1562
1563 for seqno, rev in enumerate(revs, 1):
1563 for seqno, rev in enumerate(revs, 1):
1564 ctx = repo[rev]
1564 ctx = repo[rev]
1565 fo = None
1565 fo = None
1566 if not fp and fntemplate:
1566 if not fp and fntemplate:
1567 desc_lines = ctx.description().rstrip().split('\n')
1567 desc_lines = ctx.description().rstrip().split('\n')
1568 desc = desc_lines[0] #Commit always has a first line.
1568 desc = desc_lines[0] #Commit always has a first line.
1569 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1569 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1570 total=total, seqno=seqno, revwidth=revwidth,
1570 total=total, seqno=seqno, revwidth=revwidth,
1571 mode='wb', modemap=filemode)
1571 mode='wb', modemap=filemode)
1572 dest = fo.name
1572 dest = fo.name
1573 def write(s, **kw):
1573 def write(s, **kw):
1574 fo.write(s)
1574 fo.write(s)
1575 if not dest.startswith('<'):
1575 if not dest.startswith('<'):
1576 repo.ui.note("%s\n" % dest)
1576 repo.ui.note("%s\n" % dest)
1577 _exportsingle(
1577 _exportsingle(
1578 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1578 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1579 if fo is not None:
1579 if fo is not None:
1580 fo.close()
1580 fo.close()
1581
1581
1582 class _regrettablereprbytes(bytes):
1582 class _regrettablereprbytes(bytes):
1583 """Bytes subclass that makes the repr the same on Python 3 as Python 2.
1583 """Bytes subclass that makes the repr the same on Python 3 as Python 2.
1584
1584
1585 This is a huge hack.
1585 This is a huge hack.
1586 """
1586 """
1587 def __repr__(self):
1587 def __repr__(self):
1588 return repr(pycompat.sysstr(self))
1588 return repr(pycompat.sysstr(self))
1589
1589
1590 def _maybebytestr(v):
1590 def _maybebytestr(v):
1591 if pycompat.ispy3 and isinstance(v, bytes):
1591 if pycompat.ispy3 and isinstance(v, bytes):
1592 return _regrettablereprbytes(v)
1592 return _regrettablereprbytes(v)
1593 return v
1593 return v
1594
1594
1595 def showmarker(fm, marker, index=None):
1595 def showmarker(fm, marker, index=None):
1596 """utility function to display obsolescence marker in a readable way
1596 """utility function to display obsolescence marker in a readable way
1597
1597
1598 To be used by debug function."""
1598 To be used by debug function."""
1599 if index is not None:
1599 if index is not None:
1600 fm.write('index', '%i ', index)
1600 fm.write('index', '%i ', index)
1601 fm.write('prednode', '%s ', hex(marker.prednode()))
1601 fm.write('prednode', '%s ', hex(marker.prednode()))
1602 succs = marker.succnodes()
1602 succs = marker.succnodes()
1603 fm.condwrite(succs, 'succnodes', '%s ',
1603 fm.condwrite(succs, 'succnodes', '%s ',
1604 fm.formatlist(map(hex, succs), name='node'))
1604 fm.formatlist(map(hex, succs), name='node'))
1605 fm.write('flag', '%X ', marker.flags())
1605 fm.write('flag', '%X ', marker.flags())
1606 parents = marker.parentnodes()
1606 parents = marker.parentnodes()
1607 if parents is not None:
1607 if parents is not None:
1608 fm.write('parentnodes', '{%s} ',
1608 fm.write('parentnodes', '{%s} ',
1609 fm.formatlist(map(hex, parents), name='node', sep=', '))
1609 fm.formatlist(map(hex, parents), name='node', sep=', '))
1610 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1610 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1611 meta = marker.metadata().copy()
1611 meta = marker.metadata().copy()
1612 meta.pop('date', None)
1612 meta.pop('date', None)
1613 smeta = {_maybebytestr(k): _maybebytestr(v) for k, v in meta.iteritems()}
1613 smeta = {_maybebytestr(k): _maybebytestr(v) for k, v in meta.iteritems()}
1614 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1614 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1615 fm.plain('\n')
1615 fm.plain('\n')
1616
1616
1617 def finddate(ui, repo, date):
1617 def finddate(ui, repo, date):
1618 """Find the tipmost changeset that matches the given date spec"""
1618 """Find the tipmost changeset that matches the given date spec"""
1619
1619
1620 df = util.matchdate(date)
1620 df = util.matchdate(date)
1621 m = scmutil.matchall(repo)
1621 m = scmutil.matchall(repo)
1622 results = {}
1622 results = {}
1623
1623
1624 def prep(ctx, fns):
1624 def prep(ctx, fns):
1625 d = ctx.date()
1625 d = ctx.date()
1626 if df(d[0]):
1626 if df(d[0]):
1627 results[ctx.rev()] = d
1627 results[ctx.rev()] = d
1628
1628
1629 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1629 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1630 rev = ctx.rev()
1630 rev = ctx.rev()
1631 if rev in results:
1631 if rev in results:
1632 ui.status(_("found revision %s from %s\n") %
1632 ui.status(_("found revision %s from %s\n") %
1633 (rev, util.datestr(results[rev])))
1633 (rev, util.datestr(results[rev])))
1634 return '%d' % rev
1634 return '%d' % rev
1635
1635
1636 raise error.Abort(_("revision matching date not found"))
1636 raise error.Abort(_("revision matching date not found"))
1637
1637
1638 def increasingwindows(windowsize=8, sizelimit=512):
1638 def increasingwindows(windowsize=8, sizelimit=512):
1639 while True:
1639 while True:
1640 yield windowsize
1640 yield windowsize
1641 if windowsize < sizelimit:
1641 if windowsize < sizelimit:
1642 windowsize *= 2
1642 windowsize *= 2
1643
1643
1644 def _walkrevs(repo, opts):
1644 def _walkrevs(repo, opts):
1645 # Default --rev value depends on --follow but --follow behavior
1645 # Default --rev value depends on --follow but --follow behavior
1646 # depends on revisions resolved from --rev...
1646 # depends on revisions resolved from --rev...
1647 follow = opts.get('follow') or opts.get('follow_first')
1647 follow = opts.get('follow') or opts.get('follow_first')
1648 if opts.get('rev'):
1648 if opts.get('rev'):
1649 revs = scmutil.revrange(repo, opts['rev'])
1649 revs = scmutil.revrange(repo, opts['rev'])
1650 elif follow and repo.dirstate.p1() == nullid:
1650 elif follow and repo.dirstate.p1() == nullid:
1651 revs = smartset.baseset()
1651 revs = smartset.baseset()
1652 elif follow:
1652 elif follow:
1653 revs = repo.revs('reverse(:.)')
1653 revs = repo.revs('reverse(:.)')
1654 else:
1654 else:
1655 revs = smartset.spanset(repo)
1655 revs = smartset.spanset(repo)
1656 revs.reverse()
1656 revs.reverse()
1657 return revs
1657 return revs
1658
1658
1659 class FileWalkError(Exception):
1659 class FileWalkError(Exception):
1660 pass
1660 pass
1661
1661
1662 def walkfilerevs(repo, match, follow, revs, fncache):
1662 def walkfilerevs(repo, match, follow, revs, fncache):
1663 '''Walks the file history for the matched files.
1663 '''Walks the file history for the matched files.
1664
1664
1665 Returns the changeset revs that are involved in the file history.
1665 Returns the changeset revs that are involved in the file history.
1666
1666
1667 Throws FileWalkError if the file history can't be walked using
1667 Throws FileWalkError if the file history can't be walked using
1668 filelogs alone.
1668 filelogs alone.
1669 '''
1669 '''
1670 wanted = set()
1670 wanted = set()
1671 copies = []
1671 copies = []
1672 minrev, maxrev = min(revs), max(revs)
1672 minrev, maxrev = min(revs), max(revs)
1673 def filerevgen(filelog, last):
1673 def filerevgen(filelog, last):
1674 """
1674 """
1675 Only files, no patterns. Check the history of each file.
1675 Only files, no patterns. Check the history of each file.
1676
1676
1677 Examines filelog entries within minrev, maxrev linkrev range
1677 Examines filelog entries within minrev, maxrev linkrev range
1678 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1678 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1679 tuples in backwards order
1679 tuples in backwards order
1680 """
1680 """
1681 cl_count = len(repo)
1681 cl_count = len(repo)
1682 revs = []
1682 revs = []
1683 for j in xrange(0, last + 1):
1683 for j in xrange(0, last + 1):
1684 linkrev = filelog.linkrev(j)
1684 linkrev = filelog.linkrev(j)
1685 if linkrev < minrev:
1685 if linkrev < minrev:
1686 continue
1686 continue
1687 # only yield rev for which we have the changelog, it can
1687 # only yield rev for which we have the changelog, it can
1688 # happen while doing "hg log" during a pull or commit
1688 # happen while doing "hg log" during a pull or commit
1689 if linkrev >= cl_count:
1689 if linkrev >= cl_count:
1690 break
1690 break
1691
1691
1692 parentlinkrevs = []
1692 parentlinkrevs = []
1693 for p in filelog.parentrevs(j):
1693 for p in filelog.parentrevs(j):
1694 if p != nullrev:
1694 if p != nullrev:
1695 parentlinkrevs.append(filelog.linkrev(p))
1695 parentlinkrevs.append(filelog.linkrev(p))
1696 n = filelog.node(j)
1696 n = filelog.node(j)
1697 revs.append((linkrev, parentlinkrevs,
1697 revs.append((linkrev, parentlinkrevs,
1698 follow and filelog.renamed(n)))
1698 follow and filelog.renamed(n)))
1699
1699
1700 return reversed(revs)
1700 return reversed(revs)
1701 def iterfiles():
1701 def iterfiles():
1702 pctx = repo['.']
1702 pctx = repo['.']
1703 for filename in match.files():
1703 for filename in match.files():
1704 if follow:
1704 if follow:
1705 if filename not in pctx:
1705 if filename not in pctx:
1706 raise error.Abort(_('cannot follow file not in parent '
1706 raise error.Abort(_('cannot follow file not in parent '
1707 'revision: "%s"') % filename)
1707 'revision: "%s"') % filename)
1708 yield filename, pctx[filename].filenode()
1708 yield filename, pctx[filename].filenode()
1709 else:
1709 else:
1710 yield filename, None
1710 yield filename, None
1711 for filename_node in copies:
1711 for filename_node in copies:
1712 yield filename_node
1712 yield filename_node
1713
1713
1714 for file_, node in iterfiles():
1714 for file_, node in iterfiles():
1715 filelog = repo.file(file_)
1715 filelog = repo.file(file_)
1716 if not len(filelog):
1716 if not len(filelog):
1717 if node is None:
1717 if node is None:
1718 # A zero count may be a directory or deleted file, so
1718 # A zero count may be a directory or deleted file, so
1719 # try to find matching entries on the slow path.
1719 # try to find matching entries on the slow path.
1720 if follow:
1720 if follow:
1721 raise error.Abort(
1721 raise error.Abort(
1722 _('cannot follow nonexistent file: "%s"') % file_)
1722 _('cannot follow nonexistent file: "%s"') % file_)
1723 raise FileWalkError("Cannot walk via filelog")
1723 raise FileWalkError("Cannot walk via filelog")
1724 else:
1724 else:
1725 continue
1725 continue
1726
1726
1727 if node is None:
1727 if node is None:
1728 last = len(filelog) - 1
1728 last = len(filelog) - 1
1729 else:
1729 else:
1730 last = filelog.rev(node)
1730 last = filelog.rev(node)
1731
1731
1732 # keep track of all ancestors of the file
1732 # keep track of all ancestors of the file
1733 ancestors = {filelog.linkrev(last)}
1733 ancestors = {filelog.linkrev(last)}
1734
1734
1735 # iterate from latest to oldest revision
1735 # iterate from latest to oldest revision
1736 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1736 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1737 if not follow:
1737 if not follow:
1738 if rev > maxrev:
1738 if rev > maxrev:
1739 continue
1739 continue
1740 else:
1740 else:
1741 # Note that last might not be the first interesting
1741 # Note that last might not be the first interesting
1742 # rev to us:
1742 # rev to us:
1743 # if the file has been changed after maxrev, we'll
1743 # if the file has been changed after maxrev, we'll
1744 # have linkrev(last) > maxrev, and we still need
1744 # have linkrev(last) > maxrev, and we still need
1745 # to explore the file graph
1745 # to explore the file graph
1746 if rev not in ancestors:
1746 if rev not in ancestors:
1747 continue
1747 continue
1748 # XXX insert 1327 fix here
1748 # XXX insert 1327 fix here
1749 if flparentlinkrevs:
1749 if flparentlinkrevs:
1750 ancestors.update(flparentlinkrevs)
1750 ancestors.update(flparentlinkrevs)
1751
1751
1752 fncache.setdefault(rev, []).append(file_)
1752 fncache.setdefault(rev, []).append(file_)
1753 wanted.add(rev)
1753 wanted.add(rev)
1754 if copied:
1754 if copied:
1755 copies.append(copied)
1755 copies.append(copied)
1756
1756
1757 return wanted
1757 return wanted
1758
1758
1759 class _followfilter(object):
1759 class _followfilter(object):
1760 def __init__(self, repo, onlyfirst=False):
1760 def __init__(self, repo, onlyfirst=False):
1761 self.repo = repo
1761 self.repo = repo
1762 self.startrev = nullrev
1762 self.startrev = nullrev
1763 self.roots = set()
1763 self.roots = set()
1764 self.onlyfirst = onlyfirst
1764 self.onlyfirst = onlyfirst
1765
1765
1766 def match(self, rev):
1766 def match(self, rev):
1767 def realparents(rev):
1767 def realparents(rev):
1768 if self.onlyfirst:
1768 if self.onlyfirst:
1769 return self.repo.changelog.parentrevs(rev)[0:1]
1769 return self.repo.changelog.parentrevs(rev)[0:1]
1770 else:
1770 else:
1771 return filter(lambda x: x != nullrev,
1771 return filter(lambda x: x != nullrev,
1772 self.repo.changelog.parentrevs(rev))
1772 self.repo.changelog.parentrevs(rev))
1773
1773
1774 if self.startrev == nullrev:
1774 if self.startrev == nullrev:
1775 self.startrev = rev
1775 self.startrev = rev
1776 return True
1776 return True
1777
1777
1778 if rev > self.startrev:
1778 if rev > self.startrev:
1779 # forward: all descendants
1779 # forward: all descendants
1780 if not self.roots:
1780 if not self.roots:
1781 self.roots.add(self.startrev)
1781 self.roots.add(self.startrev)
1782 for parent in realparents(rev):
1782 for parent in realparents(rev):
1783 if parent in self.roots:
1783 if parent in self.roots:
1784 self.roots.add(rev)
1784 self.roots.add(rev)
1785 return True
1785 return True
1786 else:
1786 else:
1787 # backwards: all parents
1787 # backwards: all parents
1788 if not self.roots:
1788 if not self.roots:
1789 self.roots.update(realparents(self.startrev))
1789 self.roots.update(realparents(self.startrev))
1790 if rev in self.roots:
1790 if rev in self.roots:
1791 self.roots.remove(rev)
1791 self.roots.remove(rev)
1792 self.roots.update(realparents(rev))
1792 self.roots.update(realparents(rev))
1793 return True
1793 return True
1794
1794
1795 return False
1795 return False
1796
1796
1797 def walkchangerevs(repo, match, opts, prepare):
1797 def walkchangerevs(repo, match, opts, prepare):
1798 '''Iterate over files and the revs in which they changed.
1798 '''Iterate over files and the revs in which they changed.
1799
1799
1800 Callers most commonly need to iterate backwards over the history
1800 Callers most commonly need to iterate backwards over the history
1801 in which they are interested. Doing so has awful (quadratic-looking)
1801 in which they are interested. Doing so has awful (quadratic-looking)
1802 performance, so we use iterators in a "windowed" way.
1802 performance, so we use iterators in a "windowed" way.
1803
1803
1804 We walk a window of revisions in the desired order. Within the
1804 We walk a window of revisions in the desired order. Within the
1805 window, we first walk forwards to gather data, then in the desired
1805 window, we first walk forwards to gather data, then in the desired
1806 order (usually backwards) to display it.
1806 order (usually backwards) to display it.
1807
1807
1808 This function returns an iterator yielding contexts. Before
1808 This function returns an iterator yielding contexts. Before
1809 yielding each context, the iterator will first call the prepare
1809 yielding each context, the iterator will first call the prepare
1810 function on each context in the window in forward order.'''
1810 function on each context in the window in forward order.'''
1811
1811
1812 follow = opts.get('follow') or opts.get('follow_first')
1812 follow = opts.get('follow') or opts.get('follow_first')
1813 revs = _walkrevs(repo, opts)
1813 revs = _walkrevs(repo, opts)
1814 if not revs:
1814 if not revs:
1815 return []
1815 return []
1816 wanted = set()
1816 wanted = set()
1817 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1817 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1818 fncache = {}
1818 fncache = {}
1819 change = repo.changectx
1819 change = repo.changectx
1820
1820
1821 # First step is to fill wanted, the set of revisions that we want to yield.
1821 # First step is to fill wanted, the set of revisions that we want to yield.
1822 # When it does not induce extra cost, we also fill fncache for revisions in
1822 # When it does not induce extra cost, we also fill fncache for revisions in
1823 # wanted: a cache of filenames that were changed (ctx.files()) and that
1823 # wanted: a cache of filenames that were changed (ctx.files()) and that
1824 # match the file filtering conditions.
1824 # match the file filtering conditions.
1825
1825
1826 if match.always():
1826 if match.always():
1827 # No files, no patterns. Display all revs.
1827 # No files, no patterns. Display all revs.
1828 wanted = revs
1828 wanted = revs
1829 elif not slowpath:
1829 elif not slowpath:
1830 # We only have to read through the filelog to find wanted revisions
1830 # We only have to read through the filelog to find wanted revisions
1831
1831
1832 try:
1832 try:
1833 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1833 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1834 except FileWalkError:
1834 except FileWalkError:
1835 slowpath = True
1835 slowpath = True
1836
1836
1837 # We decided to fall back to the slowpath because at least one
1837 # We decided to fall back to the slowpath because at least one
1838 # of the paths was not a file. Check to see if at least one of them
1838 # of the paths was not a file. Check to see if at least one of them
1839 # existed in history, otherwise simply return
1839 # existed in history, otherwise simply return
1840 for path in match.files():
1840 for path in match.files():
1841 if path == '.' or path in repo.store:
1841 if path == '.' or path in repo.store:
1842 break
1842 break
1843 else:
1843 else:
1844 return []
1844 return []
1845
1845
1846 if slowpath:
1846 if slowpath:
1847 # We have to read the changelog to match filenames against
1847 # We have to read the changelog to match filenames against
1848 # changed files
1848 # changed files
1849
1849
1850 if follow:
1850 if follow:
1851 raise error.Abort(_('can only follow copies/renames for explicit '
1851 raise error.Abort(_('can only follow copies/renames for explicit '
1852 'filenames'))
1852 'filenames'))
1853
1853
1854 # The slow path checks files modified in every changeset.
1854 # The slow path checks files modified in every changeset.
1855 # This is really slow on large repos, so compute the set lazily.
1855 # This is really slow on large repos, so compute the set lazily.
1856 class lazywantedset(object):
1856 class lazywantedset(object):
1857 def __init__(self):
1857 def __init__(self):
1858 self.set = set()
1858 self.set = set()
1859 self.revs = set(revs)
1859 self.revs = set(revs)
1860
1860
1861 # No need to worry about locality here because it will be accessed
1861 # No need to worry about locality here because it will be accessed
1862 # in the same order as the increasing window below.
1862 # in the same order as the increasing window below.
1863 def __contains__(self, value):
1863 def __contains__(self, value):
1864 if value in self.set:
1864 if value in self.set:
1865 return True
1865 return True
1866 elif not value in self.revs:
1866 elif not value in self.revs:
1867 return False
1867 return False
1868 else:
1868 else:
1869 self.revs.discard(value)
1869 self.revs.discard(value)
1870 ctx = change(value)
1870 ctx = change(value)
1871 matches = filter(match, ctx.files())
1871 matches = filter(match, ctx.files())
1872 if matches:
1872 if matches:
1873 fncache[value] = matches
1873 fncache[value] = matches
1874 self.set.add(value)
1874 self.set.add(value)
1875 return True
1875 return True
1876 return False
1876 return False
1877
1877
1878 def discard(self, value):
1878 def discard(self, value):
1879 self.revs.discard(value)
1879 self.revs.discard(value)
1880 self.set.discard(value)
1880 self.set.discard(value)
1881
1881
1882 wanted = lazywantedset()
1882 wanted = lazywantedset()
1883
1883
1884 # it might be worthwhile to do this in the iterator if the rev range
1884 # it might be worthwhile to do this in the iterator if the rev range
1885 # is descending and the prune args are all within that range
1885 # is descending and the prune args are all within that range
1886 for rev in opts.get('prune', ()):
1886 for rev in opts.get('prune', ()):
1887 rev = repo[rev].rev()
1887 rev = repo[rev].rev()
1888 ff = _followfilter(repo)
1888 ff = _followfilter(repo)
1889 stop = min(revs[0], revs[-1])
1889 stop = min(revs[0], revs[-1])
1890 for x in xrange(rev, stop - 1, -1):
1890 for x in xrange(rev, stop - 1, -1):
1891 if ff.match(x):
1891 if ff.match(x):
1892 wanted = wanted - [x]
1892 wanted = wanted - [x]
1893
1893
1894 # Now that wanted is correctly initialized, we can iterate over the
1894 # Now that wanted is correctly initialized, we can iterate over the
1895 # revision range, yielding only revisions in wanted.
1895 # revision range, yielding only revisions in wanted.
1896 def iterate():
1896 def iterate():
1897 if follow and match.always():
1897 if follow and match.always():
1898 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1898 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1899 def want(rev):
1899 def want(rev):
1900 return ff.match(rev) and rev in wanted
1900 return ff.match(rev) and rev in wanted
1901 else:
1901 else:
1902 def want(rev):
1902 def want(rev):
1903 return rev in wanted
1903 return rev in wanted
1904
1904
1905 it = iter(revs)
1905 it = iter(revs)
1906 stopiteration = False
1906 stopiteration = False
1907 for windowsize in increasingwindows():
1907 for windowsize in increasingwindows():
1908 nrevs = []
1908 nrevs = []
1909 for i in xrange(windowsize):
1909 for i in xrange(windowsize):
1910 rev = next(it, None)
1910 rev = next(it, None)
1911 if rev is None:
1911 if rev is None:
1912 stopiteration = True
1912 stopiteration = True
1913 break
1913 break
1914 elif want(rev):
1914 elif want(rev):
1915 nrevs.append(rev)
1915 nrevs.append(rev)
1916 for rev in sorted(nrevs):
1916 for rev in sorted(nrevs):
1917 fns = fncache.get(rev)
1917 fns = fncache.get(rev)
1918 ctx = change(rev)
1918 ctx = change(rev)
1919 if not fns:
1919 if not fns:
1920 def fns_generator():
1920 def fns_generator():
1921 for f in ctx.files():
1921 for f in ctx.files():
1922 if match(f):
1922 if match(f):
1923 yield f
1923 yield f
1924 fns = fns_generator()
1924 fns = fns_generator()
1925 prepare(ctx, fns)
1925 prepare(ctx, fns)
1926 for rev in nrevs:
1926 for rev in nrevs:
1927 yield change(rev)
1927 yield change(rev)
1928
1928
1929 if stopiteration:
1929 if stopiteration:
1930 break
1930 break
1931
1931
1932 return iterate()
1932 return iterate()
1933
1933
1934 def add(ui, repo, match, prefix, explicitonly, **opts):
1934 def add(ui, repo, match, prefix, explicitonly, **opts):
1935 join = lambda f: os.path.join(prefix, f)
1935 join = lambda f: os.path.join(prefix, f)
1936 bad = []
1936 bad = []
1937
1937
1938 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1938 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1939 names = []
1939 names = []
1940 wctx = repo[None]
1940 wctx = repo[None]
1941 cca = None
1941 cca = None
1942 abort, warn = scmutil.checkportabilityalert(ui)
1942 abort, warn = scmutil.checkportabilityalert(ui)
1943 if abort or warn:
1943 if abort or warn:
1944 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1944 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1945
1945
1946 badmatch = matchmod.badmatch(match, badfn)
1946 badmatch = matchmod.badmatch(match, badfn)
1947 dirstate = repo.dirstate
1947 dirstate = repo.dirstate
1948 # We don't want to just call wctx.walk here, since it would return a lot of
1948 # We don't want to just call wctx.walk here, since it would return a lot of
1949 # clean files, which we aren't interested in and takes time.
1949 # clean files, which we aren't interested in and takes time.
1950 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1950 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
1951 unknown=True, ignored=False, full=False)):
1951 unknown=True, ignored=False, full=False)):
1952 exact = match.exact(f)
1952 exact = match.exact(f)
1953 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1953 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1954 if cca:
1954 if cca:
1955 cca(f)
1955 cca(f)
1956 names.append(f)
1956 names.append(f)
1957 if ui.verbose or not exact:
1957 if ui.verbose or not exact:
1958 ui.status(_('adding %s\n') % match.rel(f))
1958 ui.status(_('adding %s\n') % match.rel(f))
1959
1959
1960 for subpath in sorted(wctx.substate):
1960 for subpath in sorted(wctx.substate):
1961 sub = wctx.sub(subpath)
1961 sub = wctx.sub(subpath)
1962 try:
1962 try:
1963 submatch = matchmod.subdirmatcher(subpath, match)
1963 submatch = matchmod.subdirmatcher(subpath, match)
1964 if opts.get(r'subrepos'):
1964 if opts.get(r'subrepos'):
1965 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1965 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1966 else:
1966 else:
1967 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1967 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1968 except error.LookupError:
1968 except error.LookupError:
1969 ui.status(_("skipping missing subrepository: %s\n")
1969 ui.status(_("skipping missing subrepository: %s\n")
1970 % join(subpath))
1970 % join(subpath))
1971
1971
1972 if not opts.get(r'dry_run'):
1972 if not opts.get(r'dry_run'):
1973 rejected = wctx.add(names, prefix)
1973 rejected = wctx.add(names, prefix)
1974 bad.extend(f for f in rejected if f in match.files())
1974 bad.extend(f for f in rejected if f in match.files())
1975 return bad
1975 return bad
1976
1976
1977 def addwebdirpath(repo, serverpath, webconf):
1977 def addwebdirpath(repo, serverpath, webconf):
1978 webconf[serverpath] = repo.root
1978 webconf[serverpath] = repo.root
1979 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
1979 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
1980
1980
1981 for r in repo.revs('filelog("path:.hgsub")'):
1981 for r in repo.revs('filelog("path:.hgsub")'):
1982 ctx = repo[r]
1982 ctx = repo[r]
1983 for subpath in ctx.substate:
1983 for subpath in ctx.substate:
1984 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
1984 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
1985
1985
1986 def forget(ui, repo, match, prefix, explicitonly):
1986 def forget(ui, repo, match, prefix, explicitonly):
1987 join = lambda f: os.path.join(prefix, f)
1987 join = lambda f: os.path.join(prefix, f)
1988 bad = []
1988 bad = []
1989 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1989 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
1990 wctx = repo[None]
1990 wctx = repo[None]
1991 forgot = []
1991 forgot = []
1992
1992
1993 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
1993 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
1994 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1994 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1995 if explicitonly:
1995 if explicitonly:
1996 forget = [f for f in forget if match.exact(f)]
1996 forget = [f for f in forget if match.exact(f)]
1997
1997
1998 for subpath in sorted(wctx.substate):
1998 for subpath in sorted(wctx.substate):
1999 sub = wctx.sub(subpath)
1999 sub = wctx.sub(subpath)
2000 try:
2000 try:
2001 submatch = matchmod.subdirmatcher(subpath, match)
2001 submatch = matchmod.subdirmatcher(subpath, match)
2002 subbad, subforgot = sub.forget(submatch, prefix)
2002 subbad, subforgot = sub.forget(submatch, prefix)
2003 bad.extend([subpath + '/' + f for f in subbad])
2003 bad.extend([subpath + '/' + f for f in subbad])
2004 forgot.extend([subpath + '/' + f for f in subforgot])
2004 forgot.extend([subpath + '/' + f for f in subforgot])
2005 except error.LookupError:
2005 except error.LookupError:
2006 ui.status(_("skipping missing subrepository: %s\n")
2006 ui.status(_("skipping missing subrepository: %s\n")
2007 % join(subpath))
2007 % join(subpath))
2008
2008
2009 if not explicitonly:
2009 if not explicitonly:
2010 for f in match.files():
2010 for f in match.files():
2011 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2011 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2012 if f not in forgot:
2012 if f not in forgot:
2013 if repo.wvfs.exists(f):
2013 if repo.wvfs.exists(f):
2014 # Don't complain if the exact case match wasn't given.
2014 # Don't complain if the exact case match wasn't given.
2015 # But don't do this until after checking 'forgot', so
2015 # But don't do this until after checking 'forgot', so
2016 # that subrepo files aren't normalized, and this op is
2016 # that subrepo files aren't normalized, and this op is
2017 # purely from data cached by the status walk above.
2017 # purely from data cached by the status walk above.
2018 if repo.dirstate.normalize(f) in repo.dirstate:
2018 if repo.dirstate.normalize(f) in repo.dirstate:
2019 continue
2019 continue
2020 ui.warn(_('not removing %s: '
2020 ui.warn(_('not removing %s: '
2021 'file is already untracked\n')
2021 'file is already untracked\n')
2022 % match.rel(f))
2022 % match.rel(f))
2023 bad.append(f)
2023 bad.append(f)
2024
2024
2025 for f in forget:
2025 for f in forget:
2026 if ui.verbose or not match.exact(f):
2026 if ui.verbose or not match.exact(f):
2027 ui.status(_('removing %s\n') % match.rel(f))
2027 ui.status(_('removing %s\n') % match.rel(f))
2028
2028
2029 rejected = wctx.forget(forget, prefix)
2029 rejected = wctx.forget(forget, prefix)
2030 bad.extend(f for f in rejected if f in match.files())
2030 bad.extend(f for f in rejected if f in match.files())
2031 forgot.extend(f for f in forget if f not in rejected)
2031 forgot.extend(f for f in forget if f not in rejected)
2032 return bad, forgot
2032 return bad, forgot
2033
2033
2034 def files(ui, ctx, m, fm, fmt, subrepos):
2034 def files(ui, ctx, m, fm, fmt, subrepos):
2035 rev = ctx.rev()
2035 rev = ctx.rev()
2036 ret = 1
2036 ret = 1
2037 ds = ctx.repo().dirstate
2037 ds = ctx.repo().dirstate
2038
2038
2039 for f in ctx.matches(m):
2039 for f in ctx.matches(m):
2040 if rev is None and ds[f] == 'r':
2040 if rev is None and ds[f] == 'r':
2041 continue
2041 continue
2042 fm.startitem()
2042 fm.startitem()
2043 if ui.verbose:
2043 if ui.verbose:
2044 fc = ctx[f]
2044 fc = ctx[f]
2045 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2045 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2046 fm.data(abspath=f)
2046 fm.data(abspath=f)
2047 fm.write('path', fmt, m.rel(f))
2047 fm.write('path', fmt, m.rel(f))
2048 ret = 0
2048 ret = 0
2049
2049
2050 for subpath in sorted(ctx.substate):
2050 for subpath in sorted(ctx.substate):
2051 submatch = matchmod.subdirmatcher(subpath, m)
2051 submatch = matchmod.subdirmatcher(subpath, m)
2052 if (subrepos or m.exact(subpath) or any(submatch.files())):
2052 if (subrepos or m.exact(subpath) or any(submatch.files())):
2053 sub = ctx.sub(subpath)
2053 sub = ctx.sub(subpath)
2054 try:
2054 try:
2055 recurse = m.exact(subpath) or subrepos
2055 recurse = m.exact(subpath) or subrepos
2056 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2056 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2057 ret = 0
2057 ret = 0
2058 except error.LookupError:
2058 except error.LookupError:
2059 ui.status(_("skipping missing subrepository: %s\n")
2059 ui.status(_("skipping missing subrepository: %s\n")
2060 % m.abs(subpath))
2060 % m.abs(subpath))
2061
2061
2062 return ret
2062 return ret
2063
2063
2064 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2064 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2065 join = lambda f: os.path.join(prefix, f)
2065 join = lambda f: os.path.join(prefix, f)
2066 ret = 0
2066 ret = 0
2067 s = repo.status(match=m, clean=True)
2067 s = repo.status(match=m, clean=True)
2068 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2068 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2069
2069
2070 wctx = repo[None]
2070 wctx = repo[None]
2071
2071
2072 if warnings is None:
2072 if warnings is None:
2073 warnings = []
2073 warnings = []
2074 warn = True
2074 warn = True
2075 else:
2075 else:
2076 warn = False
2076 warn = False
2077
2077
2078 subs = sorted(wctx.substate)
2078 subs = sorted(wctx.substate)
2079 total = len(subs)
2079 total = len(subs)
2080 count = 0
2080 count = 0
2081 for subpath in subs:
2081 for subpath in subs:
2082 count += 1
2082 count += 1
2083 submatch = matchmod.subdirmatcher(subpath, m)
2083 submatch = matchmod.subdirmatcher(subpath, m)
2084 if subrepos or m.exact(subpath) or any(submatch.files()):
2084 if subrepos or m.exact(subpath) or any(submatch.files()):
2085 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2085 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2086 sub = wctx.sub(subpath)
2086 sub = wctx.sub(subpath)
2087 try:
2087 try:
2088 if sub.removefiles(submatch, prefix, after, force, subrepos,
2088 if sub.removefiles(submatch, prefix, after, force, subrepos,
2089 warnings):
2089 warnings):
2090 ret = 1
2090 ret = 1
2091 except error.LookupError:
2091 except error.LookupError:
2092 warnings.append(_("skipping missing subrepository: %s\n")
2092 warnings.append(_("skipping missing subrepository: %s\n")
2093 % join(subpath))
2093 % join(subpath))
2094 ui.progress(_('searching'), None)
2094 ui.progress(_('searching'), None)
2095
2095
2096 # warn about failure to delete explicit files/dirs
2096 # warn about failure to delete explicit files/dirs
2097 deleteddirs = util.dirs(deleted)
2097 deleteddirs = util.dirs(deleted)
2098 files = m.files()
2098 files = m.files()
2099 total = len(files)
2099 total = len(files)
2100 count = 0
2100 count = 0
2101 for f in files:
2101 for f in files:
2102 def insubrepo():
2102 def insubrepo():
2103 for subpath in wctx.substate:
2103 for subpath in wctx.substate:
2104 if f.startswith(subpath + '/'):
2104 if f.startswith(subpath + '/'):
2105 return True
2105 return True
2106 return False
2106 return False
2107
2107
2108 count += 1
2108 count += 1
2109 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2109 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2110 isdir = f in deleteddirs or wctx.hasdir(f)
2110 isdir = f in deleteddirs or wctx.hasdir(f)
2111 if (f in repo.dirstate or isdir or f == '.'
2111 if (f in repo.dirstate or isdir or f == '.'
2112 or insubrepo() or f in subs):
2112 or insubrepo() or f in subs):
2113 continue
2113 continue
2114
2114
2115 if repo.wvfs.exists(f):
2115 if repo.wvfs.exists(f):
2116 if repo.wvfs.isdir(f):
2116 if repo.wvfs.isdir(f):
2117 warnings.append(_('not removing %s: no tracked files\n')
2117 warnings.append(_('not removing %s: no tracked files\n')
2118 % m.rel(f))
2118 % m.rel(f))
2119 else:
2119 else:
2120 warnings.append(_('not removing %s: file is untracked\n')
2120 warnings.append(_('not removing %s: file is untracked\n')
2121 % m.rel(f))
2121 % m.rel(f))
2122 # missing files will generate a warning elsewhere
2122 # missing files will generate a warning elsewhere
2123 ret = 1
2123 ret = 1
2124 ui.progress(_('deleting'), None)
2124 ui.progress(_('deleting'), None)
2125
2125
2126 if force:
2126 if force:
2127 list = modified + deleted + clean + added
2127 list = modified + deleted + clean + added
2128 elif after:
2128 elif after:
2129 list = deleted
2129 list = deleted
2130 remaining = modified + added + clean
2130 remaining = modified + added + clean
2131 total = len(remaining)
2131 total = len(remaining)
2132 count = 0
2132 count = 0
2133 for f in remaining:
2133 for f in remaining:
2134 count += 1
2134 count += 1
2135 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2135 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2136 if ui.verbose or (f in files):
2136 if ui.verbose or (f in files):
2137 warnings.append(_('not removing %s: file still exists\n')
2137 warnings.append(_('not removing %s: file still exists\n')
2138 % m.rel(f))
2138 % m.rel(f))
2139 ret = 1
2139 ret = 1
2140 ui.progress(_('skipping'), None)
2140 ui.progress(_('skipping'), None)
2141 else:
2141 else:
2142 list = deleted + clean
2142 list = deleted + clean
2143 total = len(modified) + len(added)
2143 total = len(modified) + len(added)
2144 count = 0
2144 count = 0
2145 for f in modified:
2145 for f in modified:
2146 count += 1
2146 count += 1
2147 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2147 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2148 warnings.append(_('not removing %s: file is modified (use -f'
2148 warnings.append(_('not removing %s: file is modified (use -f'
2149 ' to force removal)\n') % m.rel(f))
2149 ' to force removal)\n') % m.rel(f))
2150 ret = 1
2150 ret = 1
2151 for f in added:
2151 for f in added:
2152 count += 1
2152 count += 1
2153 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2153 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2154 warnings.append(_("not removing %s: file has been marked for add"
2154 warnings.append(_("not removing %s: file has been marked for add"
2155 " (use 'hg forget' to undo add)\n") % m.rel(f))
2155 " (use 'hg forget' to undo add)\n") % m.rel(f))
2156 ret = 1
2156 ret = 1
2157 ui.progress(_('skipping'), None)
2157 ui.progress(_('skipping'), None)
2158
2158
2159 list = sorted(list)
2159 list = sorted(list)
2160 total = len(list)
2160 total = len(list)
2161 count = 0
2161 count = 0
2162 for f in list:
2162 for f in list:
2163 count += 1
2163 count += 1
2164 if ui.verbose or not m.exact(f):
2164 if ui.verbose or not m.exact(f):
2165 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2165 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2166 ui.status(_('removing %s\n') % m.rel(f))
2166 ui.status(_('removing %s\n') % m.rel(f))
2167 ui.progress(_('deleting'), None)
2167 ui.progress(_('deleting'), None)
2168
2168
2169 with repo.wlock():
2169 with repo.wlock():
2170 if not after:
2170 if not after:
2171 for f in list:
2171 for f in list:
2172 if f in added:
2172 if f in added:
2173 continue # we never unlink added files on remove
2173 continue # we never unlink added files on remove
2174 repo.wvfs.unlinkpath(f, ignoremissing=True)
2174 repo.wvfs.unlinkpath(f, ignoremissing=True)
2175 repo[None].forget(list)
2175 repo[None].forget(list)
2176
2176
2177 if warn:
2177 if warn:
2178 for warning in warnings:
2178 for warning in warnings:
2179 ui.warn(warning)
2179 ui.warn(warning)
2180
2180
2181 return ret
2181 return ret
2182
2182
2183 def _updatecatformatter(fm, ctx, matcher, path, decode):
2183 def _updatecatformatter(fm, ctx, matcher, path, decode):
2184 """Hook for adding data to the formatter used by ``hg cat``.
2184 """Hook for adding data to the formatter used by ``hg cat``.
2185
2185
2186 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2186 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2187 this method first."""
2187 this method first."""
2188 data = ctx[path].data()
2188 data = ctx[path].data()
2189 if decode:
2189 if decode:
2190 data = ctx.repo().wwritedata(path, data)
2190 data = ctx.repo().wwritedata(path, data)
2191 fm.startitem()
2191 fm.startitem()
2192 fm.write('data', '%s', data)
2192 fm.write('data', '%s', data)
2193 fm.data(abspath=path, path=matcher.rel(path))
2193 fm.data(abspath=path, path=matcher.rel(path))
2194
2194
2195 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2195 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2196 err = 1
2196 err = 1
2197 opts = pycompat.byteskwargs(opts)
2197 opts = pycompat.byteskwargs(opts)
2198
2198
2199 def write(path):
2199 def write(path):
2200 filename = None
2200 filename = None
2201 if fntemplate:
2201 if fntemplate:
2202 filename = makefilename(repo, fntemplate, ctx.node(),
2202 filename = makefilename(repo, fntemplate, ctx.node(),
2203 pathname=os.path.join(prefix, path))
2203 pathname=os.path.join(prefix, path))
2204 # attempt to create the directory if it does not already exist
2204 # attempt to create the directory if it does not already exist
2205 try:
2205 try:
2206 os.makedirs(os.path.dirname(filename))
2206 os.makedirs(os.path.dirname(filename))
2207 except OSError:
2207 except OSError:
2208 pass
2208 pass
2209 with formatter.maybereopen(basefm, filename, opts) as fm:
2209 with formatter.maybereopen(basefm, filename, opts) as fm:
2210 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2210 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2211
2211
2212 # Automation often uses hg cat on single files, so special case it
2212 # Automation often uses hg cat on single files, so special case it
2213 # for performance to avoid the cost of parsing the manifest.
2213 # for performance to avoid the cost of parsing the manifest.
2214 if len(matcher.files()) == 1 and not matcher.anypats():
2214 if len(matcher.files()) == 1 and not matcher.anypats():
2215 file = matcher.files()[0]
2215 file = matcher.files()[0]
2216 mfl = repo.manifestlog
2216 mfl = repo.manifestlog
2217 mfnode = ctx.manifestnode()
2217 mfnode = ctx.manifestnode()
2218 try:
2218 try:
2219 if mfnode and mfl[mfnode].find(file)[0]:
2219 if mfnode and mfl[mfnode].find(file)[0]:
2220 write(file)
2220 write(file)
2221 return 0
2221 return 0
2222 except KeyError:
2222 except KeyError:
2223 pass
2223 pass
2224
2224
2225 for abs in ctx.walk(matcher):
2225 for abs in ctx.walk(matcher):
2226 write(abs)
2226 write(abs)
2227 err = 0
2227 err = 0
2228
2228
2229 for subpath in sorted(ctx.substate):
2229 for subpath in sorted(ctx.substate):
2230 sub = ctx.sub(subpath)
2230 sub = ctx.sub(subpath)
2231 try:
2231 try:
2232 submatch = matchmod.subdirmatcher(subpath, matcher)
2232 submatch = matchmod.subdirmatcher(subpath, matcher)
2233
2233
2234 if not sub.cat(submatch, basefm, fntemplate,
2234 if not sub.cat(submatch, basefm, fntemplate,
2235 os.path.join(prefix, sub._path),
2235 os.path.join(prefix, sub._path),
2236 **pycompat.strkwargs(opts)):
2236 **pycompat.strkwargs(opts)):
2237 err = 0
2237 err = 0
2238 except error.RepoLookupError:
2238 except error.RepoLookupError:
2239 ui.status(_("skipping missing subrepository: %s\n")
2239 ui.status(_("skipping missing subrepository: %s\n")
2240 % os.path.join(prefix, subpath))
2240 % os.path.join(prefix, subpath))
2241
2241
2242 return err
2242 return err
2243
2243
2244 def commit(ui, repo, commitfunc, pats, opts):
2244 def commit(ui, repo, commitfunc, pats, opts):
2245 '''commit the specified files or all outstanding changes'''
2245 '''commit the specified files or all outstanding changes'''
2246 date = opts.get('date')
2246 date = opts.get('date')
2247 if date:
2247 if date:
2248 opts['date'] = util.parsedate(date)
2248 opts['date'] = util.parsedate(date)
2249 message = logmessage(ui, opts)
2249 message = logmessage(ui, opts)
2250 matcher = scmutil.match(repo[None], pats, opts)
2250 matcher = scmutil.match(repo[None], pats, opts)
2251
2251
2252 dsguard = None
2252 dsguard = None
2253 # extract addremove carefully -- this function can be called from a command
2253 # extract addremove carefully -- this function can be called from a command
2254 # that doesn't support addremove
2254 # that doesn't support addremove
2255 if opts.get('addremove'):
2255 if opts.get('addremove'):
2256 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2256 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2257 with dsguard or util.nullcontextmanager():
2257 with dsguard or util.nullcontextmanager():
2258 if dsguard:
2258 if dsguard:
2259 if scmutil.addremove(repo, matcher, "", opts) != 0:
2259 if scmutil.addremove(repo, matcher, "", opts) != 0:
2260 raise error.Abort(
2260 raise error.Abort(
2261 _("failed to mark all new/missing files as added/removed"))
2261 _("failed to mark all new/missing files as added/removed"))
2262
2262
2263 return commitfunc(ui, repo, message, matcher, opts)
2263 return commitfunc(ui, repo, message, matcher, opts)
2264
2264
2265 def samefile(f, ctx1, ctx2):
2265 def samefile(f, ctx1, ctx2):
2266 if f in ctx1.manifest():
2266 if f in ctx1.manifest():
2267 a = ctx1.filectx(f)
2267 a = ctx1.filectx(f)
2268 if f in ctx2.manifest():
2268 if f in ctx2.manifest():
2269 b = ctx2.filectx(f)
2269 b = ctx2.filectx(f)
2270 return (not a.cmp(b)
2270 return (not a.cmp(b)
2271 and a.flags() == b.flags())
2271 and a.flags() == b.flags())
2272 else:
2272 else:
2273 return False
2273 return False
2274 else:
2274 else:
2275 return f not in ctx2.manifest()
2275 return f not in ctx2.manifest()
2276
2276
2277 def amend(ui, repo, old, extra, pats, opts):
2277 def amend(ui, repo, old, extra, pats, opts):
2278 # avoid cycle context -> subrepo -> cmdutil
2278 # avoid cycle context -> subrepo -> cmdutil
2279 from . import context
2279 from . import context
2280
2280
2281 # amend will reuse the existing user if not specified, but the obsolete
2281 # amend will reuse the existing user if not specified, but the obsolete
2282 # marker creation requires that the current user's name is specified.
2282 # marker creation requires that the current user's name is specified.
2283 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2283 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2284 ui.username() # raise exception if username not set
2284 ui.username() # raise exception if username not set
2285
2285
2286 ui.note(_('amending changeset %s\n') % old)
2286 ui.note(_('amending changeset %s\n') % old)
2287 base = old.p1()
2287 base = old.p1()
2288
2288
2289 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2289 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2290 # Participating changesets:
2290 # Participating changesets:
2291 #
2291 #
2292 # wctx o - workingctx that contains changes from working copy
2292 # wctx o - workingctx that contains changes from working copy
2293 # | to go into amending commit
2293 # | to go into amending commit
2294 # |
2294 # |
2295 # old o - changeset to amend
2295 # old o - changeset to amend
2296 # |
2296 # |
2297 # base o - first parent of the changeset to amend
2297 # base o - first parent of the changeset to amend
2298 wctx = repo[None]
2298 wctx = repo[None]
2299
2299
2300 # Copy to avoid mutating input
2300 # Copy to avoid mutating input
2301 extra = extra.copy()
2301 extra = extra.copy()
2302 # Update extra dict from amended commit (e.g. to preserve graft
2302 # Update extra dict from amended commit (e.g. to preserve graft
2303 # source)
2303 # source)
2304 extra.update(old.extra())
2304 extra.update(old.extra())
2305
2305
2306 # Also update it from the from the wctx
2306 # Also update it from the from the wctx
2307 extra.update(wctx.extra())
2307 extra.update(wctx.extra())
2308
2308
2309 user = opts.get('user') or old.user()
2309 user = opts.get('user') or old.user()
2310 date = opts.get('date') or old.date()
2310 date = opts.get('date') or old.date()
2311
2311
2312 # Parse the date to allow comparison between date and old.date()
2312 # Parse the date to allow comparison between date and old.date()
2313 date = util.parsedate(date)
2313 date = util.parsedate(date)
2314
2314
2315 if len(old.parents()) > 1:
2315 if len(old.parents()) > 1:
2316 # ctx.files() isn't reliable for merges, so fall back to the
2316 # ctx.files() isn't reliable for merges, so fall back to the
2317 # slower repo.status() method
2317 # slower repo.status() method
2318 files = set([fn for st in repo.status(base, old)[:3]
2318 files = set([fn for st in repo.status(base, old)[:3]
2319 for fn in st])
2319 for fn in st])
2320 else:
2320 else:
2321 files = set(old.files())
2321 files = set(old.files())
2322
2322
2323 # add/remove the files to the working copy if the "addremove" option
2323 # add/remove the files to the working copy if the "addremove" option
2324 # was specified.
2324 # was specified.
2325 matcher = scmutil.match(wctx, pats, opts)
2325 matcher = scmutil.match(wctx, pats, opts)
2326 if (opts.get('addremove')
2326 if (opts.get('addremove')
2327 and scmutil.addremove(repo, matcher, "", opts)):
2327 and scmutil.addremove(repo, matcher, "", opts)):
2328 raise error.Abort(
2328 raise error.Abort(
2329 _("failed to mark all new/missing files as added/removed"))
2329 _("failed to mark all new/missing files as added/removed"))
2330
2330
2331 # Check subrepos. This depends on in-place wctx._status update in
2331 # Check subrepos. This depends on in-place wctx._status update in
2332 # subrepo.precommit(). To minimize the risk of this hack, we do
2332 # subrepo.precommit(). To minimize the risk of this hack, we do
2333 # nothing if .hgsub does not exist.
2333 # nothing if .hgsub does not exist.
2334 if '.hgsub' in wctx or '.hgsub' in old:
2334 if '.hgsub' in wctx or '.hgsub' in old:
2335 from . import subrepo # avoid cycle: cmdutil -> subrepo -> cmdutil
2335 from . import subrepo # avoid cycle: cmdutil -> subrepo -> cmdutil
2336 subs, commitsubs, newsubstate = subrepo.precommit(
2336 subs, commitsubs, newsubstate = subrepo.precommit(
2337 ui, wctx, wctx._status, matcher)
2337 ui, wctx, wctx._status, matcher)
2338 # amend should abort if commitsubrepos is enabled
2338 # amend should abort if commitsubrepos is enabled
2339 assert not commitsubs
2339 assert not commitsubs
2340 if subs:
2340 if subs:
2341 subrepo.writestate(repo, newsubstate)
2341 subrepo.writestate(repo, newsubstate)
2342
2342
2343 filestoamend = set(f for f in wctx.files() if matcher(f))
2343 filestoamend = set(f for f in wctx.files() if matcher(f))
2344
2344
2345 changes = (len(filestoamend) > 0)
2345 changes = (len(filestoamend) > 0)
2346 if changes:
2346 if changes:
2347 # Recompute copies (avoid recording a -> b -> a)
2347 # Recompute copies (avoid recording a -> b -> a)
2348 copied = copies.pathcopies(base, wctx, matcher)
2348 copied = copies.pathcopies(base, wctx, matcher)
2349 if old.p2:
2349 if old.p2:
2350 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2350 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2351
2351
2352 # Prune files which were reverted by the updates: if old
2352 # Prune files which were reverted by the updates: if old
2353 # introduced file X and the file was renamed in the working
2353 # introduced file X and the file was renamed in the working
2354 # copy, then those two files are the same and
2354 # copy, then those two files are the same and
2355 # we can discard X from our list of files. Likewise if X
2355 # we can discard X from our list of files. Likewise if X
2356 # was removed, it's no longer relevant. If X is missing (aka
2356 # was removed, it's no longer relevant. If X is missing (aka
2357 # deleted), old X must be preserved.
2357 # deleted), old X must be preserved.
2358 files.update(filestoamend)
2358 files.update(filestoamend)
2359 files = [f for f in files if (not samefile(f, wctx, base)
2359 files = [f for f in files if (not samefile(f, wctx, base)
2360 or f in wctx.deleted())]
2360 or f in wctx.deleted())]
2361
2361
2362 def filectxfn(repo, ctx_, path):
2362 def filectxfn(repo, ctx_, path):
2363 try:
2363 try:
2364 # If the file being considered is not amongst the files
2364 # If the file being considered is not amongst the files
2365 # to be amended, we should return the file context from the
2365 # to be amended, we should return the file context from the
2366 # old changeset. This avoids issues when only some files in
2366 # old changeset. This avoids issues when only some files in
2367 # the working copy are being amended but there are also
2367 # the working copy are being amended but there are also
2368 # changes to other files from the old changeset.
2368 # changes to other files from the old changeset.
2369 if path not in filestoamend:
2369 if path not in filestoamend:
2370 return old.filectx(path)
2370 return old.filectx(path)
2371
2371
2372 # Return None for removed files.
2372 # Return None for removed files.
2373 if path in wctx.removed():
2373 if path in wctx.removed():
2374 return None
2374 return None
2375
2375
2376 fctx = wctx[path]
2376 fctx = wctx[path]
2377 flags = fctx.flags()
2377 flags = fctx.flags()
2378 mctx = context.memfilectx(repo, ctx_,
2378 mctx = context.memfilectx(repo, ctx_,
2379 fctx.path(), fctx.data(),
2379 fctx.path(), fctx.data(),
2380 islink='l' in flags,
2380 islink='l' in flags,
2381 isexec='x' in flags,
2381 isexec='x' in flags,
2382 copied=copied.get(path))
2382 copied=copied.get(path))
2383 return mctx
2383 return mctx
2384 except KeyError:
2384 except KeyError:
2385 return None
2385 return None
2386 else:
2386 else:
2387 ui.note(_('copying changeset %s to %s\n') % (old, base))
2387 ui.note(_('copying changeset %s to %s\n') % (old, base))
2388
2388
2389 # Use version of files as in the old cset
2389 # Use version of files as in the old cset
2390 def filectxfn(repo, ctx_, path):
2390 def filectxfn(repo, ctx_, path):
2391 try:
2391 try:
2392 return old.filectx(path)
2392 return old.filectx(path)
2393 except KeyError:
2393 except KeyError:
2394 return None
2394 return None
2395
2395
2396 # See if we got a message from -m or -l, if not, open the editor with
2396 # See if we got a message from -m or -l, if not, open the editor with
2397 # the message of the changeset to amend.
2397 # the message of the changeset to amend.
2398 message = logmessage(ui, opts)
2398 message = logmessage(ui, opts)
2399
2399
2400 editform = mergeeditform(old, 'commit.amend')
2400 editform = mergeeditform(old, 'commit.amend')
2401 editor = getcommiteditor(editform=editform,
2401 editor = getcommiteditor(editform=editform,
2402 **pycompat.strkwargs(opts))
2402 **pycompat.strkwargs(opts))
2403
2403
2404 if not message:
2404 if not message:
2405 editor = getcommiteditor(edit=True, editform=editform)
2405 editor = getcommiteditor(edit=True, editform=editform)
2406 message = old.description()
2406 message = old.description()
2407
2407
2408 pureextra = extra.copy()
2408 pureextra = extra.copy()
2409 extra['amend_source'] = old.hex()
2409 extra['amend_source'] = old.hex()
2410
2410
2411 new = context.memctx(repo,
2411 new = context.memctx(repo,
2412 parents=[base.node(), old.p2().node()],
2412 parents=[base.node(), old.p2().node()],
2413 text=message,
2413 text=message,
2414 files=files,
2414 files=files,
2415 filectxfn=filectxfn,
2415 filectxfn=filectxfn,
2416 user=user,
2416 user=user,
2417 date=date,
2417 date=date,
2418 extra=extra,
2418 extra=extra,
2419 editor=editor)
2419 editor=editor)
2420
2420
2421 newdesc = changelog.stripdesc(new.description())
2421 newdesc = changelog.stripdesc(new.description())
2422 if ((not changes)
2422 if ((not changes)
2423 and newdesc == old.description()
2423 and newdesc == old.description()
2424 and user == old.user()
2424 and user == old.user()
2425 and date == old.date()
2425 and date == old.date()
2426 and pureextra == old.extra()):
2426 and pureextra == old.extra()):
2427 # nothing changed. continuing here would create a new node
2427 # nothing changed. continuing here would create a new node
2428 # anyway because of the amend_source noise.
2428 # anyway because of the amend_source noise.
2429 #
2429 #
2430 # This not what we expect from amend.
2430 # This not what we expect from amend.
2431 return old.node()
2431 return old.node()
2432
2432
2433 if opts.get('secret'):
2433 if opts.get('secret'):
2434 commitphase = 'secret'
2434 commitphase = 'secret'
2435 else:
2435 else:
2436 commitphase = old.phase()
2436 commitphase = old.phase()
2437 overrides = {('phases', 'new-commit'): commitphase}
2437 overrides = {('phases', 'new-commit'): commitphase}
2438 with ui.configoverride(overrides, 'amend'):
2438 with ui.configoverride(overrides, 'amend'):
2439 newid = repo.commitctx(new)
2439 newid = repo.commitctx(new)
2440
2440
2441 # Reroute the working copy parent to the new changeset
2441 # Reroute the working copy parent to the new changeset
2442 repo.setparents(newid, nullid)
2442 repo.setparents(newid, nullid)
2443 mapping = {old.node(): (newid,)}
2443 mapping = {old.node(): (newid,)}
2444 obsmetadata = None
2444 obsmetadata = None
2445 if opts.get('note'):
2445 if opts.get('note'):
2446 obsmetadata = {'note': opts['note']}
2446 obsmetadata = {'note': opts['note']}
2447 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2447 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
2448
2448
2449 # Fixing the dirstate because localrepo.commitctx does not update
2449 # Fixing the dirstate because localrepo.commitctx does not update
2450 # it. This is rather convenient because we did not need to update
2450 # it. This is rather convenient because we did not need to update
2451 # the dirstate for all the files in the new commit which commitctx
2451 # the dirstate for all the files in the new commit which commitctx
2452 # could have done if it updated the dirstate. Now, we can
2452 # could have done if it updated the dirstate. Now, we can
2453 # selectively update the dirstate only for the amended files.
2453 # selectively update the dirstate only for the amended files.
2454 dirstate = repo.dirstate
2454 dirstate = repo.dirstate
2455
2455
2456 # Update the state of the files which were added and
2456 # Update the state of the files which were added and
2457 # and modified in the amend to "normal" in the dirstate.
2457 # and modified in the amend to "normal" in the dirstate.
2458 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2458 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2459 for f in normalfiles:
2459 for f in normalfiles:
2460 dirstate.normal(f)
2460 dirstate.normal(f)
2461
2461
2462 # Update the state of files which were removed in the amend
2462 # Update the state of files which were removed in the amend
2463 # to "removed" in the dirstate.
2463 # to "removed" in the dirstate.
2464 removedfiles = set(wctx.removed()) & filestoamend
2464 removedfiles = set(wctx.removed()) & filestoamend
2465 for f in removedfiles:
2465 for f in removedfiles:
2466 dirstate.drop(f)
2466 dirstate.drop(f)
2467
2467
2468 return newid
2468 return newid
2469
2469
2470 def commiteditor(repo, ctx, subs, editform=''):
2470 def commiteditor(repo, ctx, subs, editform=''):
2471 if ctx.description():
2471 if ctx.description():
2472 return ctx.description()
2472 return ctx.description()
2473 return commitforceeditor(repo, ctx, subs, editform=editform,
2473 return commitforceeditor(repo, ctx, subs, editform=editform,
2474 unchangedmessagedetection=True)
2474 unchangedmessagedetection=True)
2475
2475
2476 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2476 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2477 editform='', unchangedmessagedetection=False):
2477 editform='', unchangedmessagedetection=False):
2478 if not extramsg:
2478 if not extramsg:
2479 extramsg = _("Leave message empty to abort commit.")
2479 extramsg = _("Leave message empty to abort commit.")
2480
2480
2481 forms = [e for e in editform.split('.') if e]
2481 forms = [e for e in editform.split('.') if e]
2482 forms.insert(0, 'changeset')
2482 forms.insert(0, 'changeset')
2483 templatetext = None
2483 templatetext = None
2484 while forms:
2484 while forms:
2485 ref = '.'.join(forms)
2485 ref = '.'.join(forms)
2486 if repo.ui.config('committemplate', ref):
2486 if repo.ui.config('committemplate', ref):
2487 templatetext = committext = buildcommittemplate(
2487 templatetext = committext = buildcommittemplate(
2488 repo, ctx, subs, extramsg, ref)
2488 repo, ctx, subs, extramsg, ref)
2489 break
2489 break
2490 forms.pop()
2490 forms.pop()
2491 else:
2491 else:
2492 committext = buildcommittext(repo, ctx, subs, extramsg)
2492 committext = buildcommittext(repo, ctx, subs, extramsg)
2493
2493
2494 # run editor in the repository root
2494 # run editor in the repository root
2495 olddir = pycompat.getcwd()
2495 olddir = pycompat.getcwd()
2496 os.chdir(repo.root)
2496 os.chdir(repo.root)
2497
2497
2498 # make in-memory changes visible to external process
2498 # make in-memory changes visible to external process
2499 tr = repo.currenttransaction()
2499 tr = repo.currenttransaction()
2500 repo.dirstate.write(tr)
2500 repo.dirstate.write(tr)
2501 pending = tr and tr.writepending() and repo.root
2501 pending = tr and tr.writepending() and repo.root
2502
2502
2503 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2503 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2504 editform=editform, pending=pending,
2504 editform=editform, pending=pending,
2505 repopath=repo.path, action='commit')
2505 repopath=repo.path, action='commit')
2506 text = editortext
2506 text = editortext
2507
2507
2508 # strip away anything below this special string (used for editors that want
2508 # strip away anything below this special string (used for editors that want
2509 # to display the diff)
2509 # to display the diff)
2510 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2510 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2511 if stripbelow:
2511 if stripbelow:
2512 text = text[:stripbelow.start()]
2512 text = text[:stripbelow.start()]
2513
2513
2514 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2514 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2515 os.chdir(olddir)
2515 os.chdir(olddir)
2516
2516
2517 if finishdesc:
2517 if finishdesc:
2518 text = finishdesc(text)
2518 text = finishdesc(text)
2519 if not text.strip():
2519 if not text.strip():
2520 raise error.Abort(_("empty commit message"))
2520 raise error.Abort(_("empty commit message"))
2521 if unchangedmessagedetection and editortext == templatetext:
2521 if unchangedmessagedetection and editortext == templatetext:
2522 raise error.Abort(_("commit message unchanged"))
2522 raise error.Abort(_("commit message unchanged"))
2523
2523
2524 return text
2524 return text
2525
2525
2526 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2526 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2527 ui = repo.ui
2527 ui = repo.ui
2528 spec = formatter.templatespec(ref, None, None)
2528 spec = formatter.templatespec(ref, None, None)
2529 t = changeset_templater(ui, repo, spec, None, {}, False)
2529 t = changeset_templater(ui, repo, spec, None, {}, False)
2530 t.t.cache.update((k, templater.unquotestring(v))
2530 t.t.cache.update((k, templater.unquotestring(v))
2531 for k, v in repo.ui.configitems('committemplate'))
2531 for k, v in repo.ui.configitems('committemplate'))
2532
2532
2533 if not extramsg:
2533 if not extramsg:
2534 extramsg = '' # ensure that extramsg is string
2534 extramsg = '' # ensure that extramsg is string
2535
2535
2536 ui.pushbuffer()
2536 ui.pushbuffer()
2537 t.show(ctx, extramsg=extramsg)
2537 t.show(ctx, extramsg=extramsg)
2538 return ui.popbuffer()
2538 return ui.popbuffer()
2539
2539
2540 def hgprefix(msg):
2540 def hgprefix(msg):
2541 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2541 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2542
2542
2543 def buildcommittext(repo, ctx, subs, extramsg):
2543 def buildcommittext(repo, ctx, subs, extramsg):
2544 edittext = []
2544 edittext = []
2545 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2545 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2546 if ctx.description():
2546 if ctx.description():
2547 edittext.append(ctx.description())
2547 edittext.append(ctx.description())
2548 edittext.append("")
2548 edittext.append("")
2549 edittext.append("") # Empty line between message and comments.
2549 edittext.append("") # Empty line between message and comments.
2550 edittext.append(hgprefix(_("Enter commit message."
2550 edittext.append(hgprefix(_("Enter commit message."
2551 " Lines beginning with 'HG:' are removed.")))
2551 " Lines beginning with 'HG:' are removed.")))
2552 edittext.append(hgprefix(extramsg))
2552 edittext.append(hgprefix(extramsg))
2553 edittext.append("HG: --")
2553 edittext.append("HG: --")
2554 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2554 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2555 if ctx.p2():
2555 if ctx.p2():
2556 edittext.append(hgprefix(_("branch merge")))
2556 edittext.append(hgprefix(_("branch merge")))
2557 if ctx.branch():
2557 if ctx.branch():
2558 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2558 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2559 if bookmarks.isactivewdirparent(repo):
2559 if bookmarks.isactivewdirparent(repo):
2560 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2560 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2561 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2561 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2562 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2562 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2563 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2563 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2564 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2564 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2565 if not added and not modified and not removed:
2565 if not added and not modified and not removed:
2566 edittext.append(hgprefix(_("no files changed")))
2566 edittext.append(hgprefix(_("no files changed")))
2567 edittext.append("")
2567 edittext.append("")
2568
2568
2569 return "\n".join(edittext)
2569 return "\n".join(edittext)
2570
2570
2571 def commitstatus(repo, node, branch, bheads=None, opts=None):
2571 def commitstatus(repo, node, branch, bheads=None, opts=None):
2572 if opts is None:
2572 if opts is None:
2573 opts = {}
2573 opts = {}
2574 ctx = repo[node]
2574 ctx = repo[node]
2575 parents = ctx.parents()
2575 parents = ctx.parents()
2576
2576
2577 if (not opts.get('amend') and bheads and node not in bheads and not
2577 if (not opts.get('amend') and bheads and node not in bheads and not
2578 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2578 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2579 repo.ui.status(_('created new head\n'))
2579 repo.ui.status(_('created new head\n'))
2580 # The message is not printed for initial roots. For the other
2580 # The message is not printed for initial roots. For the other
2581 # changesets, it is printed in the following situations:
2581 # changesets, it is printed in the following situations:
2582 #
2582 #
2583 # Par column: for the 2 parents with ...
2583 # Par column: for the 2 parents with ...
2584 # N: null or no parent
2584 # N: null or no parent
2585 # B: parent is on another named branch
2585 # B: parent is on another named branch
2586 # C: parent is a regular non head changeset
2586 # C: parent is a regular non head changeset
2587 # H: parent was a branch head of the current branch
2587 # H: parent was a branch head of the current branch
2588 # Msg column: whether we print "created new head" message
2588 # Msg column: whether we print "created new head" message
2589 # In the following, it is assumed that there already exists some
2589 # In the following, it is assumed that there already exists some
2590 # initial branch heads of the current branch, otherwise nothing is
2590 # initial branch heads of the current branch, otherwise nothing is
2591 # printed anyway.
2591 # printed anyway.
2592 #
2592 #
2593 # Par Msg Comment
2593 # Par Msg Comment
2594 # N N y additional topo root
2594 # N N y additional topo root
2595 #
2595 #
2596 # B N y additional branch root
2596 # B N y additional branch root
2597 # C N y additional topo head
2597 # C N y additional topo head
2598 # H N n usual case
2598 # H N n usual case
2599 #
2599 #
2600 # B B y weird additional branch root
2600 # B B y weird additional branch root
2601 # C B y branch merge
2601 # C B y branch merge
2602 # H B n merge with named branch
2602 # H B n merge with named branch
2603 #
2603 #
2604 # C C y additional head from merge
2604 # C C y additional head from merge
2605 # C H n merge with a head
2605 # C H n merge with a head
2606 #
2606 #
2607 # H H n head merge: head count decreases
2607 # H H n head merge: head count decreases
2608
2608
2609 if not opts.get('close_branch'):
2609 if not opts.get('close_branch'):
2610 for r in parents:
2610 for r in parents:
2611 if r.closesbranch() and r.branch() == branch:
2611 if r.closesbranch() and r.branch() == branch:
2612 repo.ui.status(_('reopening closed branch head %d\n') % r)
2612 repo.ui.status(_('reopening closed branch head %d\n') % r)
2613
2613
2614 if repo.ui.debugflag:
2614 if repo.ui.debugflag:
2615 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2615 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2616 elif repo.ui.verbose:
2616 elif repo.ui.verbose:
2617 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2617 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2618
2618
2619 def postcommitstatus(repo, pats, opts):
2619 def postcommitstatus(repo, pats, opts):
2620 return repo.status(match=scmutil.match(repo[None], pats, opts))
2620 return repo.status(match=scmutil.match(repo[None], pats, opts))
2621
2621
2622 def revert(ui, repo, ctx, parents, *pats, **opts):
2622 def revert(ui, repo, ctx, parents, *pats, **opts):
2623 opts = pycompat.byteskwargs(opts)
2623 opts = pycompat.byteskwargs(opts)
2624 parent, p2 = parents
2624 parent, p2 = parents
2625 node = ctx.node()
2625 node = ctx.node()
2626
2626
2627 mf = ctx.manifest()
2627 mf = ctx.manifest()
2628 if node == p2:
2628 if node == p2:
2629 parent = p2
2629 parent = p2
2630
2630
2631 # need all matching names in dirstate and manifest of target rev,
2631 # need all matching names in dirstate and manifest of target rev,
2632 # so have to walk both. do not print errors if files exist in one
2632 # so have to walk both. do not print errors if files exist in one
2633 # but not other. in both cases, filesets should be evaluated against
2633 # but not other. in both cases, filesets should be evaluated against
2634 # workingctx to get consistent result (issue4497). this means 'set:**'
2634 # workingctx to get consistent result (issue4497). this means 'set:**'
2635 # cannot be used to select missing files from target rev.
2635 # cannot be used to select missing files from target rev.
2636
2636
2637 # `names` is a mapping for all elements in working copy and target revision
2637 # `names` is a mapping for all elements in working copy and target revision
2638 # The mapping is in the form:
2638 # The mapping is in the form:
2639 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2639 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2640 names = {}
2640 names = {}
2641
2641
2642 with repo.wlock():
2642 with repo.wlock():
2643 ## filling of the `names` mapping
2643 ## filling of the `names` mapping
2644 # walk dirstate to fill `names`
2644 # walk dirstate to fill `names`
2645
2645
2646 interactive = opts.get('interactive', False)
2646 interactive = opts.get('interactive', False)
2647 wctx = repo[None]
2647 wctx = repo[None]
2648 m = scmutil.match(wctx, pats, opts)
2648 m = scmutil.match(wctx, pats, opts)
2649
2649
2650 # we'll need this later
2650 # we'll need this later
2651 targetsubs = sorted(s for s in wctx.substate if m(s))
2651 targetsubs = sorted(s for s in wctx.substate if m(s))
2652
2652
2653 if not m.always():
2653 if not m.always():
2654 matcher = matchmod.badmatch(m, lambda x, y: False)
2654 matcher = matchmod.badmatch(m, lambda x, y: False)
2655 for abs in wctx.walk(matcher):
2655 for abs in wctx.walk(matcher):
2656 names[abs] = m.rel(abs), m.exact(abs)
2656 names[abs] = m.rel(abs), m.exact(abs)
2657
2657
2658 # walk target manifest to fill `names`
2658 # walk target manifest to fill `names`
2659
2659
2660 def badfn(path, msg):
2660 def badfn(path, msg):
2661 if path in names:
2661 if path in names:
2662 return
2662 return
2663 if path in ctx.substate:
2663 if path in ctx.substate:
2664 return
2664 return
2665 path_ = path + '/'
2665 path_ = path + '/'
2666 for f in names:
2666 for f in names:
2667 if f.startswith(path_):
2667 if f.startswith(path_):
2668 return
2668 return
2669 ui.warn("%s: %s\n" % (m.rel(path), msg))
2669 ui.warn("%s: %s\n" % (m.rel(path), msg))
2670
2670
2671 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2671 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2672 if abs not in names:
2672 if abs not in names:
2673 names[abs] = m.rel(abs), m.exact(abs)
2673 names[abs] = m.rel(abs), m.exact(abs)
2674
2674
2675 # Find status of all file in `names`.
2675 # Find status of all file in `names`.
2676 m = scmutil.matchfiles(repo, names)
2676 m = scmutil.matchfiles(repo, names)
2677
2677
2678 changes = repo.status(node1=node, match=m,
2678 changes = repo.status(node1=node, match=m,
2679 unknown=True, ignored=True, clean=True)
2679 unknown=True, ignored=True, clean=True)
2680 else:
2680 else:
2681 changes = repo.status(node1=node, match=m)
2681 changes = repo.status(node1=node, match=m)
2682 for kind in changes:
2682 for kind in changes:
2683 for abs in kind:
2683 for abs in kind:
2684 names[abs] = m.rel(abs), m.exact(abs)
2684 names[abs] = m.rel(abs), m.exact(abs)
2685
2685
2686 m = scmutil.matchfiles(repo, names)
2686 m = scmutil.matchfiles(repo, names)
2687
2687
2688 modified = set(changes.modified)
2688 modified = set(changes.modified)
2689 added = set(changes.added)
2689 added = set(changes.added)
2690 removed = set(changes.removed)
2690 removed = set(changes.removed)
2691 _deleted = set(changes.deleted)
2691 _deleted = set(changes.deleted)
2692 unknown = set(changes.unknown)
2692 unknown = set(changes.unknown)
2693 unknown.update(changes.ignored)
2693 unknown.update(changes.ignored)
2694 clean = set(changes.clean)
2694 clean = set(changes.clean)
2695 modadded = set()
2695 modadded = set()
2696
2696
2697 # We need to account for the state of the file in the dirstate,
2697 # We need to account for the state of the file in the dirstate,
2698 # even when we revert against something else than parent. This will
2698 # even when we revert against something else than parent. This will
2699 # slightly alter the behavior of revert (doing back up or not, delete
2699 # slightly alter the behavior of revert (doing back up or not, delete
2700 # or just forget etc).
2700 # or just forget etc).
2701 if parent == node:
2701 if parent == node:
2702 dsmodified = modified
2702 dsmodified = modified
2703 dsadded = added
2703 dsadded = added
2704 dsremoved = removed
2704 dsremoved = removed
2705 # store all local modifications, useful later for rename detection
2705 # store all local modifications, useful later for rename detection
2706 localchanges = dsmodified | dsadded
2706 localchanges = dsmodified | dsadded
2707 modified, added, removed = set(), set(), set()
2707 modified, added, removed = set(), set(), set()
2708 else:
2708 else:
2709 changes = repo.status(node1=parent, match=m)
2709 changes = repo.status(node1=parent, match=m)
2710 dsmodified = set(changes.modified)
2710 dsmodified = set(changes.modified)
2711 dsadded = set(changes.added)
2711 dsadded = set(changes.added)
2712 dsremoved = set(changes.removed)
2712 dsremoved = set(changes.removed)
2713 # store all local modifications, useful later for rename detection
2713 # store all local modifications, useful later for rename detection
2714 localchanges = dsmodified | dsadded
2714 localchanges = dsmodified | dsadded
2715
2715
2716 # only take into account for removes between wc and target
2716 # only take into account for removes between wc and target
2717 clean |= dsremoved - removed
2717 clean |= dsremoved - removed
2718 dsremoved &= removed
2718 dsremoved &= removed
2719 # distinct between dirstate remove and other
2719 # distinct between dirstate remove and other
2720 removed -= dsremoved
2720 removed -= dsremoved
2721
2721
2722 modadded = added & dsmodified
2722 modadded = added & dsmodified
2723 added -= modadded
2723 added -= modadded
2724
2724
2725 # tell newly modified apart.
2725 # tell newly modified apart.
2726 dsmodified &= modified
2726 dsmodified &= modified
2727 dsmodified |= modified & dsadded # dirstate added may need backup
2727 dsmodified |= modified & dsadded # dirstate added may need backup
2728 modified -= dsmodified
2728 modified -= dsmodified
2729
2729
2730 # We need to wait for some post-processing to update this set
2730 # We need to wait for some post-processing to update this set
2731 # before making the distinction. The dirstate will be used for
2731 # before making the distinction. The dirstate will be used for
2732 # that purpose.
2732 # that purpose.
2733 dsadded = added
2733 dsadded = added
2734
2734
2735 # in case of merge, files that are actually added can be reported as
2735 # in case of merge, files that are actually added can be reported as
2736 # modified, we need to post process the result
2736 # modified, we need to post process the result
2737 if p2 != nullid:
2737 if p2 != nullid:
2738 mergeadd = set(dsmodified)
2738 mergeadd = set(dsmodified)
2739 for path in dsmodified:
2739 for path in dsmodified:
2740 if path in mf:
2740 if path in mf:
2741 mergeadd.remove(path)
2741 mergeadd.remove(path)
2742 dsadded |= mergeadd
2742 dsadded |= mergeadd
2743 dsmodified -= mergeadd
2743 dsmodified -= mergeadd
2744
2744
2745 # if f is a rename, update `names` to also revert the source
2745 # if f is a rename, update `names` to also revert the source
2746 cwd = repo.getcwd()
2746 cwd = repo.getcwd()
2747 for f in localchanges:
2747 for f in localchanges:
2748 src = repo.dirstate.copied(f)
2748 src = repo.dirstate.copied(f)
2749 # XXX should we check for rename down to target node?
2749 # XXX should we check for rename down to target node?
2750 if src and src not in names and repo.dirstate[src] == 'r':
2750 if src and src not in names and repo.dirstate[src] == 'r':
2751 dsremoved.add(src)
2751 dsremoved.add(src)
2752 names[src] = (repo.pathto(src, cwd), True)
2752 names[src] = (repo.pathto(src, cwd), True)
2753
2753
2754 # determine the exact nature of the deleted changesets
2754 # determine the exact nature of the deleted changesets
2755 deladded = set(_deleted)
2755 deladded = set(_deleted)
2756 for path in _deleted:
2756 for path in _deleted:
2757 if path in mf:
2757 if path in mf:
2758 deladded.remove(path)
2758 deladded.remove(path)
2759 deleted = _deleted - deladded
2759 deleted = _deleted - deladded
2760
2760
2761 # distinguish between file to forget and the other
2761 # distinguish between file to forget and the other
2762 added = set()
2762 added = set()
2763 for abs in dsadded:
2763 for abs in dsadded:
2764 if repo.dirstate[abs] != 'a':
2764 if repo.dirstate[abs] != 'a':
2765 added.add(abs)
2765 added.add(abs)
2766 dsadded -= added
2766 dsadded -= added
2767
2767
2768 for abs in deladded:
2768 for abs in deladded:
2769 if repo.dirstate[abs] == 'a':
2769 if repo.dirstate[abs] == 'a':
2770 dsadded.add(abs)
2770 dsadded.add(abs)
2771 deladded -= dsadded
2771 deladded -= dsadded
2772
2772
2773 # For files marked as removed, we check if an unknown file is present at
2773 # For files marked as removed, we check if an unknown file is present at
2774 # the same path. If a such file exists it may need to be backed up.
2774 # the same path. If a such file exists it may need to be backed up.
2775 # Making the distinction at this stage helps have simpler backup
2775 # Making the distinction at this stage helps have simpler backup
2776 # logic.
2776 # logic.
2777 removunk = set()
2777 removunk = set()
2778 for abs in removed:
2778 for abs in removed:
2779 target = repo.wjoin(abs)
2779 target = repo.wjoin(abs)
2780 if os.path.lexists(target):
2780 if os.path.lexists(target):
2781 removunk.add(abs)
2781 removunk.add(abs)
2782 removed -= removunk
2782 removed -= removunk
2783
2783
2784 dsremovunk = set()
2784 dsremovunk = set()
2785 for abs in dsremoved:
2785 for abs in dsremoved:
2786 target = repo.wjoin(abs)
2786 target = repo.wjoin(abs)
2787 if os.path.lexists(target):
2787 if os.path.lexists(target):
2788 dsremovunk.add(abs)
2788 dsremovunk.add(abs)
2789 dsremoved -= dsremovunk
2789 dsremoved -= dsremovunk
2790
2790
2791 # action to be actually performed by revert
2791 # action to be actually performed by revert
2792 # (<list of file>, message>) tuple
2792 # (<list of file>, message>) tuple
2793 actions = {'revert': ([], _('reverting %s\n')),
2793 actions = {'revert': ([], _('reverting %s\n')),
2794 'add': ([], _('adding %s\n')),
2794 'add': ([], _('adding %s\n')),
2795 'remove': ([], _('removing %s\n')),
2795 'remove': ([], _('removing %s\n')),
2796 'drop': ([], _('removing %s\n')),
2796 'drop': ([], _('removing %s\n')),
2797 'forget': ([], _('forgetting %s\n')),
2797 'forget': ([], _('forgetting %s\n')),
2798 'undelete': ([], _('undeleting %s\n')),
2798 'undelete': ([], _('undeleting %s\n')),
2799 'noop': (None, _('no changes needed to %s\n')),
2799 'noop': (None, _('no changes needed to %s\n')),
2800 'unknown': (None, _('file not managed: %s\n')),
2800 'unknown': (None, _('file not managed: %s\n')),
2801 }
2801 }
2802
2802
2803 # "constant" that convey the backup strategy.
2803 # "constant" that convey the backup strategy.
2804 # All set to `discard` if `no-backup` is set do avoid checking
2804 # All set to `discard` if `no-backup` is set do avoid checking
2805 # no_backup lower in the code.
2805 # no_backup lower in the code.
2806 # These values are ordered for comparison purposes
2806 # These values are ordered for comparison purposes
2807 backupinteractive = 3 # do backup if interactively modified
2807 backupinteractive = 3 # do backup if interactively modified
2808 backup = 2 # unconditionally do backup
2808 backup = 2 # unconditionally do backup
2809 check = 1 # check if the existing file differs from target
2809 check = 1 # check if the existing file differs from target
2810 discard = 0 # never do backup
2810 discard = 0 # never do backup
2811 if opts.get('no_backup'):
2811 if opts.get('no_backup'):
2812 backupinteractive = backup = check = discard
2812 backupinteractive = backup = check = discard
2813 if interactive:
2813 if interactive:
2814 dsmodifiedbackup = backupinteractive
2814 dsmodifiedbackup = backupinteractive
2815 else:
2815 else:
2816 dsmodifiedbackup = backup
2816 dsmodifiedbackup = backup
2817 tobackup = set()
2817 tobackup = set()
2818
2818
2819 backupanddel = actions['remove']
2819 backupanddel = actions['remove']
2820 if not opts.get('no_backup'):
2820 if not opts.get('no_backup'):
2821 backupanddel = actions['drop']
2821 backupanddel = actions['drop']
2822
2822
2823 disptable = (
2823 disptable = (
2824 # dispatch table:
2824 # dispatch table:
2825 # file state
2825 # file state
2826 # action
2826 # action
2827 # make backup
2827 # make backup
2828
2828
2829 ## Sets that results that will change file on disk
2829 ## Sets that results that will change file on disk
2830 # Modified compared to target, no local change
2830 # Modified compared to target, no local change
2831 (modified, actions['revert'], discard),
2831 (modified, actions['revert'], discard),
2832 # Modified compared to target, but local file is deleted
2832 # Modified compared to target, but local file is deleted
2833 (deleted, actions['revert'], discard),
2833 (deleted, actions['revert'], discard),
2834 # Modified compared to target, local change
2834 # Modified compared to target, local change
2835 (dsmodified, actions['revert'], dsmodifiedbackup),
2835 (dsmodified, actions['revert'], dsmodifiedbackup),
2836 # Added since target
2836 # Added since target
2837 (added, actions['remove'], discard),
2837 (added, actions['remove'], discard),
2838 # Added in working directory
2838 # Added in working directory
2839 (dsadded, actions['forget'], discard),
2839 (dsadded, actions['forget'], discard),
2840 # Added since target, have local modification
2840 # Added since target, have local modification
2841 (modadded, backupanddel, backup),
2841 (modadded, backupanddel, backup),
2842 # Added since target but file is missing in working directory
2842 # Added since target but file is missing in working directory
2843 (deladded, actions['drop'], discard),
2843 (deladded, actions['drop'], discard),
2844 # Removed since target, before working copy parent
2844 # Removed since target, before working copy parent
2845 (removed, actions['add'], discard),
2845 (removed, actions['add'], discard),
2846 # Same as `removed` but an unknown file exists at the same path
2846 # Same as `removed` but an unknown file exists at the same path
2847 (removunk, actions['add'], check),
2847 (removunk, actions['add'], check),
2848 # Removed since targe, marked as such in working copy parent
2848 # Removed since targe, marked as such in working copy parent
2849 (dsremoved, actions['undelete'], discard),
2849 (dsremoved, actions['undelete'], discard),
2850 # Same as `dsremoved` but an unknown file exists at the same path
2850 # Same as `dsremoved` but an unknown file exists at the same path
2851 (dsremovunk, actions['undelete'], check),
2851 (dsremovunk, actions['undelete'], check),
2852 ## the following sets does not result in any file changes
2852 ## the following sets does not result in any file changes
2853 # File with no modification
2853 # File with no modification
2854 (clean, actions['noop'], discard),
2854 (clean, actions['noop'], discard),
2855 # Existing file, not tracked anywhere
2855 # Existing file, not tracked anywhere
2856 (unknown, actions['unknown'], discard),
2856 (unknown, actions['unknown'], discard),
2857 )
2857 )
2858
2858
2859 for abs, (rel, exact) in sorted(names.items()):
2859 for abs, (rel, exact) in sorted(names.items()):
2860 # target file to be touch on disk (relative to cwd)
2860 # target file to be touch on disk (relative to cwd)
2861 target = repo.wjoin(abs)
2861 target = repo.wjoin(abs)
2862 # search the entry in the dispatch table.
2862 # search the entry in the dispatch table.
2863 # if the file is in any of these sets, it was touched in the working
2863 # if the file is in any of these sets, it was touched in the working
2864 # directory parent and we are sure it needs to be reverted.
2864 # directory parent and we are sure it needs to be reverted.
2865 for table, (xlist, msg), dobackup in disptable:
2865 for table, (xlist, msg), dobackup in disptable:
2866 if abs not in table:
2866 if abs not in table:
2867 continue
2867 continue
2868 if xlist is not None:
2868 if xlist is not None:
2869 xlist.append(abs)
2869 xlist.append(abs)
2870 if dobackup:
2870 if dobackup:
2871 # If in interactive mode, don't automatically create
2871 # If in interactive mode, don't automatically create
2872 # .orig files (issue4793)
2872 # .orig files (issue4793)
2873 if dobackup == backupinteractive:
2873 if dobackup == backupinteractive:
2874 tobackup.add(abs)
2874 tobackup.add(abs)
2875 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2875 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
2876 bakname = scmutil.origpath(ui, repo, rel)
2876 bakname = scmutil.origpath(ui, repo, rel)
2877 ui.note(_('saving current version of %s as %s\n') %
2877 ui.note(_('saving current version of %s as %s\n') %
2878 (rel, bakname))
2878 (rel, bakname))
2879 if not opts.get('dry_run'):
2879 if not opts.get('dry_run'):
2880 if interactive:
2880 if interactive:
2881 util.copyfile(target, bakname)
2881 util.copyfile(target, bakname)
2882 else:
2882 else:
2883 util.rename(target, bakname)
2883 util.rename(target, bakname)
2884 if ui.verbose or not exact:
2884 if ui.verbose or not exact:
2885 if not isinstance(msg, bytes):
2885 if not isinstance(msg, bytes):
2886 msg = msg(abs)
2886 msg = msg(abs)
2887 ui.status(msg % rel)
2887 ui.status(msg % rel)
2888 elif exact:
2888 elif exact:
2889 ui.warn(msg % rel)
2889 ui.warn(msg % rel)
2890 break
2890 break
2891
2891
2892 if not opts.get('dry_run'):
2892 if not opts.get('dry_run'):
2893 needdata = ('revert', 'add', 'undelete')
2893 needdata = ('revert', 'add', 'undelete')
2894 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2894 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2895 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2895 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
2896
2896
2897 if targetsubs:
2897 if targetsubs:
2898 # Revert the subrepos on the revert list
2898 # Revert the subrepos on the revert list
2899 for sub in targetsubs:
2899 for sub in targetsubs:
2900 try:
2900 try:
2901 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2901 wctx.sub(sub).revert(ctx.substate[sub], *pats,
2902 **pycompat.strkwargs(opts))
2902 **pycompat.strkwargs(opts))
2903 except KeyError:
2903 except KeyError:
2904 raise error.Abort("subrepository '%s' does not exist in %s!"
2904 raise error.Abort("subrepository '%s' does not exist in %s!"
2905 % (sub, short(ctx.node())))
2905 % (sub, short(ctx.node())))
2906
2906
2907 def _revertprefetch(repo, ctx, *files):
2907 def _revertprefetch(repo, ctx, *files):
2908 """Let extension changing the storage layer prefetch content"""
2908 """Let extension changing the storage layer prefetch content"""
2909
2909
2910 def _performrevert(repo, parents, ctx, actions, interactive=False,
2910 def _performrevert(repo, parents, ctx, actions, interactive=False,
2911 tobackup=None):
2911 tobackup=None):
2912 """function that actually perform all the actions computed for revert
2912 """function that actually perform all the actions computed for revert
2913
2913
2914 This is an independent function to let extension to plug in and react to
2914 This is an independent function to let extension to plug in and react to
2915 the imminent revert.
2915 the imminent revert.
2916
2916
2917 Make sure you have the working directory locked when calling this function.
2917 Make sure you have the working directory locked when calling this function.
2918 """
2918 """
2919 parent, p2 = parents
2919 parent, p2 = parents
2920 node = ctx.node()
2920 node = ctx.node()
2921 excluded_files = []
2921 excluded_files = []
2922 matcher_opts = {"exclude": excluded_files}
2922 matcher_opts = {"exclude": excluded_files}
2923
2923
2924 def checkout(f):
2924 def checkout(f):
2925 fc = ctx[f]
2925 fc = ctx[f]
2926 repo.wwrite(f, fc.data(), fc.flags())
2926 repo.wwrite(f, fc.data(), fc.flags())
2927
2927
2928 def doremove(f):
2928 def doremove(f):
2929 try:
2929 try:
2930 repo.wvfs.unlinkpath(f)
2930 repo.wvfs.unlinkpath(f)
2931 except OSError:
2931 except OSError:
2932 pass
2932 pass
2933 repo.dirstate.remove(f)
2933 repo.dirstate.remove(f)
2934
2934
2935 audit_path = pathutil.pathauditor(repo.root, cached=True)
2935 audit_path = pathutil.pathauditor(repo.root, cached=True)
2936 for f in actions['forget'][0]:
2936 for f in actions['forget'][0]:
2937 if interactive:
2937 if interactive:
2938 choice = repo.ui.promptchoice(
2938 choice = repo.ui.promptchoice(
2939 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2939 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
2940 if choice == 0:
2940 if choice == 0:
2941 repo.dirstate.drop(f)
2941 repo.dirstate.drop(f)
2942 else:
2942 else:
2943 excluded_files.append(repo.wjoin(f))
2943 excluded_files.append(repo.wjoin(f))
2944 else:
2944 else:
2945 repo.dirstate.drop(f)
2945 repo.dirstate.drop(f)
2946 for f in actions['remove'][0]:
2946 for f in actions['remove'][0]:
2947 audit_path(f)
2947 audit_path(f)
2948 if interactive:
2948 if interactive:
2949 choice = repo.ui.promptchoice(
2949 choice = repo.ui.promptchoice(
2950 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
2950 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
2951 if choice == 0:
2951 if choice == 0:
2952 doremove(f)
2952 doremove(f)
2953 else:
2953 else:
2954 excluded_files.append(repo.wjoin(f))
2954 excluded_files.append(repo.wjoin(f))
2955 else:
2955 else:
2956 doremove(f)
2956 doremove(f)
2957 for f in actions['drop'][0]:
2957 for f in actions['drop'][0]:
2958 audit_path(f)
2958 audit_path(f)
2959 repo.dirstate.remove(f)
2959 repo.dirstate.remove(f)
2960
2960
2961 normal = None
2961 normal = None
2962 if node == parent:
2962 if node == parent:
2963 # We're reverting to our parent. If possible, we'd like status
2963 # We're reverting to our parent. If possible, we'd like status
2964 # to report the file as clean. We have to use normallookup for
2964 # to report the file as clean. We have to use normallookup for
2965 # merges to avoid losing information about merged/dirty files.
2965 # merges to avoid losing information about merged/dirty files.
2966 if p2 != nullid:
2966 if p2 != nullid:
2967 normal = repo.dirstate.normallookup
2967 normal = repo.dirstate.normallookup
2968 else:
2968 else:
2969 normal = repo.dirstate.normal
2969 normal = repo.dirstate.normal
2970
2970
2971 newlyaddedandmodifiedfiles = set()
2971 newlyaddedandmodifiedfiles = set()
2972 if interactive:
2972 if interactive:
2973 # Prompt the user for changes to revert
2973 # Prompt the user for changes to revert
2974 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
2974 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
2975 m = scmutil.match(ctx, torevert, matcher_opts)
2975 m = scmutil.match(ctx, torevert, matcher_opts)
2976 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
2976 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
2977 diffopts.nodates = True
2977 diffopts.nodates = True
2978 diffopts.git = True
2978 diffopts.git = True
2979 operation = 'discard'
2979 operation = 'discard'
2980 reversehunks = True
2980 reversehunks = True
2981 if node != parent:
2981 if node != parent:
2982 operation = 'apply'
2982 operation = 'apply'
2983 reversehunks = False
2983 reversehunks = False
2984 if reversehunks:
2984 if reversehunks:
2985 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
2985 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
2986 else:
2986 else:
2987 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
2987 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
2988 originalchunks = patch.parsepatch(diff)
2988 originalchunks = patch.parsepatch(diff)
2989
2989
2990 try:
2990 try:
2991
2991
2992 chunks, opts = recordfilter(repo.ui, originalchunks,
2992 chunks, opts = recordfilter(repo.ui, originalchunks,
2993 operation=operation)
2993 operation=operation)
2994 if reversehunks:
2994 if reversehunks:
2995 chunks = patch.reversehunks(chunks)
2995 chunks = patch.reversehunks(chunks)
2996
2996
2997 except error.PatchError as err:
2997 except error.PatchError as err:
2998 raise error.Abort(_('error parsing patch: %s') % err)
2998 raise error.Abort(_('error parsing patch: %s') % err)
2999
2999
3000 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3000 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3001 if tobackup is None:
3001 if tobackup is None:
3002 tobackup = set()
3002 tobackup = set()
3003 # Apply changes
3003 # Apply changes
3004 fp = stringio()
3004 fp = stringio()
3005 for c in chunks:
3005 for c in chunks:
3006 # Create a backup file only if this hunk should be backed up
3006 # Create a backup file only if this hunk should be backed up
3007 if ishunk(c) and c.header.filename() in tobackup:
3007 if ishunk(c) and c.header.filename() in tobackup:
3008 abs = c.header.filename()
3008 abs = c.header.filename()
3009 target = repo.wjoin(abs)
3009 target = repo.wjoin(abs)
3010 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3010 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3011 util.copyfile(target, bakname)
3011 util.copyfile(target, bakname)
3012 tobackup.remove(abs)
3012 tobackup.remove(abs)
3013 c.write(fp)
3013 c.write(fp)
3014 dopatch = fp.tell()
3014 dopatch = fp.tell()
3015 fp.seek(0)
3015 fp.seek(0)
3016 if dopatch:
3016 if dopatch:
3017 try:
3017 try:
3018 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3018 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3019 except error.PatchError as err:
3019 except error.PatchError as err:
3020 raise error.Abort(str(err))
3020 raise error.Abort(str(err))
3021 del fp
3021 del fp
3022 else:
3022 else:
3023 for f in actions['revert'][0]:
3023 for f in actions['revert'][0]:
3024 checkout(f)
3024 checkout(f)
3025 if normal:
3025 if normal:
3026 normal(f)
3026 normal(f)
3027
3027
3028 for f in actions['add'][0]:
3028 for f in actions['add'][0]:
3029 # Don't checkout modified files, they are already created by the diff
3029 # Don't checkout modified files, they are already created by the diff
3030 if f not in newlyaddedandmodifiedfiles:
3030 if f not in newlyaddedandmodifiedfiles:
3031 checkout(f)
3031 checkout(f)
3032 repo.dirstate.add(f)
3032 repo.dirstate.add(f)
3033
3033
3034 normal = repo.dirstate.normallookup
3034 normal = repo.dirstate.normallookup
3035 if node == parent and p2 == nullid:
3035 if node == parent and p2 == nullid:
3036 normal = repo.dirstate.normal
3036 normal = repo.dirstate.normal
3037 for f in actions['undelete'][0]:
3037 for f in actions['undelete'][0]:
3038 checkout(f)
3038 checkout(f)
3039 normal(f)
3039 normal(f)
3040
3040
3041 copied = copies.pathcopies(repo[parent], ctx)
3041 copied = copies.pathcopies(repo[parent], ctx)
3042
3042
3043 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3043 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3044 if f in copied:
3044 if f in copied:
3045 repo.dirstate.copy(copied[f], f)
3045 repo.dirstate.copy(copied[f], f)
3046
3046
3047 class command(registrar.command):
3047 class command(registrar.command):
3048 """deprecated: used registrar.command instead"""
3048 """deprecated: used registrar.command instead"""
3049 def _doregister(self, func, name, *args, **kwargs):
3049 def _doregister(self, func, name, *args, **kwargs):
3050 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3050 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3051 return super(command, self)._doregister(func, name, *args, **kwargs)
3051 return super(command, self)._doregister(func, name, *args, **kwargs)
3052
3052
3053 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3053 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3054 # commands.outgoing. "missing" is "missing" of the result of
3054 # commands.outgoing. "missing" is "missing" of the result of
3055 # "findcommonoutgoing()"
3055 # "findcommonoutgoing()"
3056 outgoinghooks = util.hooks()
3056 outgoinghooks = util.hooks()
3057
3057
3058 # a list of (ui, repo) functions called by commands.summary
3058 # a list of (ui, repo) functions called by commands.summary
3059 summaryhooks = util.hooks()
3059 summaryhooks = util.hooks()
3060
3060
3061 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3061 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3062 #
3062 #
3063 # functions should return tuple of booleans below, if 'changes' is None:
3063 # functions should return tuple of booleans below, if 'changes' is None:
3064 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3064 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3065 #
3065 #
3066 # otherwise, 'changes' is a tuple of tuples below:
3066 # otherwise, 'changes' is a tuple of tuples below:
3067 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3067 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3068 # - (desturl, destbranch, destpeer, outgoing)
3068 # - (desturl, destbranch, destpeer, outgoing)
3069 summaryremotehooks = util.hooks()
3069 summaryremotehooks = util.hooks()
3070
3070
3071 # A list of state files kept by multistep operations like graft.
3071 # A list of state files kept by multistep operations like graft.
3072 # Since graft cannot be aborted, it is considered 'clearable' by update.
3072 # Since graft cannot be aborted, it is considered 'clearable' by update.
3073 # note: bisect is intentionally excluded
3073 # note: bisect is intentionally excluded
3074 # (state file, clearable, allowcommit, error, hint)
3074 # (state file, clearable, allowcommit, error, hint)
3075 unfinishedstates = [
3075 unfinishedstates = [
3076 ('graftstate', True, False, _('graft in progress'),
3076 ('graftstate', True, False, _('graft in progress'),
3077 _("use 'hg graft --continue' or 'hg update' to abort")),
3077 _("use 'hg graft --continue' or 'hg update' to abort")),
3078 ('updatestate', True, False, _('last update was interrupted'),
3078 ('updatestate', True, False, _('last update was interrupted'),
3079 _("use 'hg update' to get a consistent checkout"))
3079 _("use 'hg update' to get a consistent checkout"))
3080 ]
3080 ]
3081
3081
3082 def checkunfinished(repo, commit=False):
3082 def checkunfinished(repo, commit=False):
3083 '''Look for an unfinished multistep operation, like graft, and abort
3083 '''Look for an unfinished multistep operation, like graft, and abort
3084 if found. It's probably good to check this right before
3084 if found. It's probably good to check this right before
3085 bailifchanged().
3085 bailifchanged().
3086 '''
3086 '''
3087 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3087 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3088 if commit and allowcommit:
3088 if commit and allowcommit:
3089 continue
3089 continue
3090 if repo.vfs.exists(f):
3090 if repo.vfs.exists(f):
3091 raise error.Abort(msg, hint=hint)
3091 raise error.Abort(msg, hint=hint)
3092
3092
3093 def clearunfinished(repo):
3093 def clearunfinished(repo):
3094 '''Check for unfinished operations (as above), and clear the ones
3094 '''Check for unfinished operations (as above), and clear the ones
3095 that are clearable.
3095 that are clearable.
3096 '''
3096 '''
3097 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3097 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3098 if not clearable and repo.vfs.exists(f):
3098 if not clearable and repo.vfs.exists(f):
3099 raise error.Abort(msg, hint=hint)
3099 raise error.Abort(msg, hint=hint)
3100 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3100 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3101 if clearable and repo.vfs.exists(f):
3101 if clearable and repo.vfs.exists(f):
3102 util.unlink(repo.vfs.join(f))
3102 util.unlink(repo.vfs.join(f))
3103
3103
3104 afterresolvedstates = [
3104 afterresolvedstates = [
3105 ('graftstate',
3105 ('graftstate',
3106 _('hg graft --continue')),
3106 _('hg graft --continue')),
3107 ]
3107 ]
3108
3108
3109 def howtocontinue(repo):
3109 def howtocontinue(repo):
3110 '''Check for an unfinished operation and return the command to finish
3110 '''Check for an unfinished operation and return the command to finish
3111 it.
3111 it.
3112
3112
3113 afterresolvedstates tuples define a .hg/{file} and the corresponding
3113 afterresolvedstates tuples define a .hg/{file} and the corresponding
3114 command needed to finish it.
3114 command needed to finish it.
3115
3115
3116 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3116 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3117 a boolean.
3117 a boolean.
3118 '''
3118 '''
3119 contmsg = _("continue: %s")
3119 contmsg = _("continue: %s")
3120 for f, msg in afterresolvedstates:
3120 for f, msg in afterresolvedstates:
3121 if repo.vfs.exists(f):
3121 if repo.vfs.exists(f):
3122 return contmsg % msg, True
3122 return contmsg % msg, True
3123 if repo[None].dirty(missing=True, merge=False, branch=False):
3123 if repo[None].dirty(missing=True, merge=False, branch=False):
3124 return contmsg % _("hg commit"), False
3124 return contmsg % _("hg commit"), False
3125 return None, None
3125 return None, None
3126
3126
3127 def checkafterresolved(repo):
3127 def checkafterresolved(repo):
3128 '''Inform the user about the next action after completing hg resolve
3128 '''Inform the user about the next action after completing hg resolve
3129
3129
3130 If there's a matching afterresolvedstates, howtocontinue will yield
3130 If there's a matching afterresolvedstates, howtocontinue will yield
3131 repo.ui.warn as the reporter.
3131 repo.ui.warn as the reporter.
3132
3132
3133 Otherwise, it will yield repo.ui.note.
3133 Otherwise, it will yield repo.ui.note.
3134 '''
3134 '''
3135 msg, warning = howtocontinue(repo)
3135 msg, warning = howtocontinue(repo)
3136 if msg is not None:
3136 if msg is not None:
3137 if warning:
3137 if warning:
3138 repo.ui.warn("%s\n" % msg)
3138 repo.ui.warn("%s\n" % msg)
3139 else:
3139 else:
3140 repo.ui.note("%s\n" % msg)
3140 repo.ui.note("%s\n" % msg)
3141
3141
3142 def wrongtooltocontinue(repo, task):
3142 def wrongtooltocontinue(repo, task):
3143 '''Raise an abort suggesting how to properly continue if there is an
3143 '''Raise an abort suggesting how to properly continue if there is an
3144 active task.
3144 active task.
3145
3145
3146 Uses howtocontinue() to find the active task.
3146 Uses howtocontinue() to find the active task.
3147
3147
3148 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3148 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3149 a hint.
3149 a hint.
3150 '''
3150 '''
3151 after = howtocontinue(repo)
3151 after = howtocontinue(repo)
3152 hint = None
3152 hint = None
3153 if after[1]:
3153 if after[1]:
3154 hint = after[0]
3154 hint = after[0]
3155 raise error.Abort(_('no %s in progress') % task, hint=hint)
3155 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,933 +1,933 b''
1 # logcmdutil.py - utility for log-like commands
1 # logcmdutil.py - utility for log-like commands
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import itertools
10 import itertools
11 import os
11 import os
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 hex,
15 hex,
16 nullid,
16 nullid,
17 )
17 )
18
18
19 from . import (
19 from . import (
20 dagop,
20 dagop,
21 encoding,
21 encoding,
22 error,
22 error,
23 formatter,
23 formatter,
24 graphmod,
24 graphmod,
25 match as matchmod,
25 match as matchmod,
26 mdiff,
26 mdiff,
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
38
39 def loglimit(opts):
39 def getlimit(opts):
40 """get the log limit according to option -l/--limit"""
40 """get the log limit according to option -l/--limit"""
41 limit = opts.get('limit')
41 limit = opts.get('limit')
42 if limit:
42 if limit:
43 try:
43 try:
44 limit = int(limit)
44 limit = int(limit)
45 except ValueError:
45 except ValueError:
46 raise error.Abort(_('limit must be a positive integer'))
46 raise error.Abort(_('limit must be a positive integer'))
47 if limit <= 0:
47 if limit <= 0:
48 raise error.Abort(_('limit must be positive'))
48 raise error.Abort(_('limit must be positive'))
49 else:
49 else:
50 limit = None
50 limit = None
51 return limit
51 return limit
52
52
53 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
53 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
54 changes=None, stat=False, fp=None, prefix='',
54 changes=None, stat=False, fp=None, prefix='',
55 root='', listsubrepos=False, hunksfilterfn=None):
55 root='', listsubrepos=False, hunksfilterfn=None):
56 '''show diff or diffstat.'''
56 '''show diff or diffstat.'''
57 if fp is None:
57 if fp is None:
58 write = ui.write
58 write = ui.write
59 else:
59 else:
60 def write(s, **kw):
60 def write(s, **kw):
61 fp.write(s)
61 fp.write(s)
62
62
63 if root:
63 if root:
64 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
64 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
65 else:
65 else:
66 relroot = ''
66 relroot = ''
67 if relroot != '':
67 if relroot != '':
68 # XXX relative roots currently don't work if the root is within a
68 # XXX relative roots currently don't work if the root is within a
69 # subrepo
69 # subrepo
70 uirelroot = match.uipath(relroot)
70 uirelroot = match.uipath(relroot)
71 relroot += '/'
71 relroot += '/'
72 for matchroot in match.files():
72 for matchroot in match.files():
73 if not matchroot.startswith(relroot):
73 if not matchroot.startswith(relroot):
74 ui.warn(_('warning: %s not inside relative root %s\n') % (
74 ui.warn(_('warning: %s not inside relative root %s\n') % (
75 match.uipath(matchroot), uirelroot))
75 match.uipath(matchroot), uirelroot))
76
76
77 if stat:
77 if stat:
78 diffopts = diffopts.copy(context=0, noprefix=False)
78 diffopts = diffopts.copy(context=0, noprefix=False)
79 width = 80
79 width = 80
80 if not ui.plain():
80 if not ui.plain():
81 width = ui.termwidth()
81 width = ui.termwidth()
82 chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
82 chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
83 prefix=prefix, relroot=relroot,
83 prefix=prefix, relroot=relroot,
84 hunksfilterfn=hunksfilterfn)
84 hunksfilterfn=hunksfilterfn)
85 for chunk, label in patch.diffstatui(util.iterlines(chunks),
85 for chunk, label in patch.diffstatui(util.iterlines(chunks),
86 width=width):
86 width=width):
87 write(chunk, label=label)
87 write(chunk, label=label)
88 else:
88 else:
89 for chunk, label in patch.diffui(repo, node1, node2, match,
89 for chunk, label in patch.diffui(repo, node1, node2, match,
90 changes, opts=diffopts, prefix=prefix,
90 changes, opts=diffopts, prefix=prefix,
91 relroot=relroot,
91 relroot=relroot,
92 hunksfilterfn=hunksfilterfn):
92 hunksfilterfn=hunksfilterfn):
93 write(chunk, label=label)
93 write(chunk, label=label)
94
94
95 if listsubrepos:
95 if listsubrepos:
96 ctx1 = repo[node1]
96 ctx1 = repo[node1]
97 ctx2 = repo[node2]
97 ctx2 = repo[node2]
98 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
98 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
99 tempnode2 = node2
99 tempnode2 = node2
100 try:
100 try:
101 if node2 is not None:
101 if node2 is not None:
102 tempnode2 = ctx2.substate[subpath][1]
102 tempnode2 = ctx2.substate[subpath][1]
103 except KeyError:
103 except KeyError:
104 # A subrepo that existed in node1 was deleted between node1 and
104 # A subrepo that existed in node1 was deleted between node1 and
105 # node2 (inclusive). Thus, ctx2's substate won't contain that
105 # node2 (inclusive). Thus, ctx2's substate won't contain that
106 # subpath. The best we can do is to ignore it.
106 # subpath. The best we can do is to ignore it.
107 tempnode2 = None
107 tempnode2 = None
108 submatch = matchmod.subdirmatcher(subpath, match)
108 submatch = matchmod.subdirmatcher(subpath, match)
109 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
109 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
110 stat=stat, fp=fp, prefix=prefix)
110 stat=stat, fp=fp, prefix=prefix)
111
111
112 def changesetlabels(ctx):
112 def changesetlabels(ctx):
113 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
113 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
114 if ctx.obsolete():
114 if ctx.obsolete():
115 labels.append('changeset.obsolete')
115 labels.append('changeset.obsolete')
116 if ctx.isunstable():
116 if ctx.isunstable():
117 labels.append('changeset.unstable')
117 labels.append('changeset.unstable')
118 for instability in ctx.instabilities():
118 for instability in ctx.instabilities():
119 labels.append('instability.%s' % instability)
119 labels.append('instability.%s' % instability)
120 return ' '.join(labels)
120 return ' '.join(labels)
121
121
122 class changesetprinter(object):
122 class changesetprinter(object):
123 '''show changeset information when templating not requested.'''
123 '''show changeset information when templating not requested.'''
124
124
125 def __init__(self, ui, repo, matchfn, diffopts, buffered):
125 def __init__(self, ui, repo, matchfn, diffopts, buffered):
126 self.ui = ui
126 self.ui = ui
127 self.repo = repo
127 self.repo = repo
128 self.buffered = buffered
128 self.buffered = buffered
129 self.matchfn = matchfn
129 self.matchfn = matchfn
130 self.diffopts = diffopts
130 self.diffopts = diffopts
131 self.header = {}
131 self.header = {}
132 self.hunk = {}
132 self.hunk = {}
133 self.lastheader = None
133 self.lastheader = None
134 self.footer = None
134 self.footer = None
135 self._columns = templatekw.getlogcolumns()
135 self._columns = templatekw.getlogcolumns()
136
136
137 def flush(self, ctx):
137 def flush(self, ctx):
138 rev = ctx.rev()
138 rev = ctx.rev()
139 if rev in self.header:
139 if rev in self.header:
140 h = self.header[rev]
140 h = self.header[rev]
141 if h != self.lastheader:
141 if h != self.lastheader:
142 self.lastheader = h
142 self.lastheader = h
143 self.ui.write(h)
143 self.ui.write(h)
144 del self.header[rev]
144 del self.header[rev]
145 if rev in self.hunk:
145 if rev in self.hunk:
146 self.ui.write(self.hunk[rev])
146 self.ui.write(self.hunk[rev])
147 del self.hunk[rev]
147 del self.hunk[rev]
148
148
149 def close(self):
149 def close(self):
150 if self.footer:
150 if self.footer:
151 self.ui.write(self.footer)
151 self.ui.write(self.footer)
152
152
153 def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
153 def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
154 **props):
154 **props):
155 props = pycompat.byteskwargs(props)
155 props = pycompat.byteskwargs(props)
156 if self.buffered:
156 if self.buffered:
157 self.ui.pushbuffer(labeled=True)
157 self.ui.pushbuffer(labeled=True)
158 self._show(ctx, copies, matchfn, hunksfilterfn, props)
158 self._show(ctx, copies, matchfn, hunksfilterfn, props)
159 self.hunk[ctx.rev()] = self.ui.popbuffer()
159 self.hunk[ctx.rev()] = self.ui.popbuffer()
160 else:
160 else:
161 self._show(ctx, copies, matchfn, hunksfilterfn, props)
161 self._show(ctx, copies, matchfn, hunksfilterfn, props)
162
162
163 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
163 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
164 '''show a single changeset or file revision'''
164 '''show a single changeset or file revision'''
165 changenode = ctx.node()
165 changenode = ctx.node()
166 rev = ctx.rev()
166 rev = ctx.rev()
167
167
168 if self.ui.quiet:
168 if self.ui.quiet:
169 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
169 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
170 label='log.node')
170 label='log.node')
171 return
171 return
172
172
173 columns = self._columns
173 columns = self._columns
174 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
174 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
175 label=changesetlabels(ctx))
175 label=changesetlabels(ctx))
176
176
177 # branches are shown first before any other names due to backwards
177 # branches are shown first before any other names due to backwards
178 # compatibility
178 # compatibility
179 branch = ctx.branch()
179 branch = ctx.branch()
180 # don't show the default branch name
180 # don't show the default branch name
181 if branch != 'default':
181 if branch != 'default':
182 self.ui.write(columns['branch'] % branch, label='log.branch')
182 self.ui.write(columns['branch'] % branch, label='log.branch')
183
183
184 for nsname, ns in self.repo.names.iteritems():
184 for nsname, ns in self.repo.names.iteritems():
185 # branches has special logic already handled above, so here we just
185 # branches has special logic already handled above, so here we just
186 # skip it
186 # skip it
187 if nsname == 'branches':
187 if nsname == 'branches':
188 continue
188 continue
189 # we will use the templatename as the color name since those two
189 # we will use the templatename as the color name since those two
190 # should be the same
190 # should be the same
191 for name in ns.names(self.repo, changenode):
191 for name in ns.names(self.repo, changenode):
192 self.ui.write(ns.logfmt % name,
192 self.ui.write(ns.logfmt % name,
193 label='log.%s' % ns.colorname)
193 label='log.%s' % ns.colorname)
194 if self.ui.debugflag:
194 if self.ui.debugflag:
195 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
195 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
196 for pctx in scmutil.meaningfulparents(self.repo, ctx):
196 for pctx in scmutil.meaningfulparents(self.repo, ctx):
197 label = 'log.parent changeset.%s' % pctx.phasestr()
197 label = 'log.parent changeset.%s' % pctx.phasestr()
198 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
198 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
199 label=label)
199 label=label)
200
200
201 if self.ui.debugflag and rev is not None:
201 if self.ui.debugflag and rev is not None:
202 mnode = ctx.manifestnode()
202 mnode = ctx.manifestnode()
203 mrev = self.repo.manifestlog._revlog.rev(mnode)
203 mrev = self.repo.manifestlog._revlog.rev(mnode)
204 self.ui.write(columns['manifest']
204 self.ui.write(columns['manifest']
205 % scmutil.formatrevnode(self.ui, mrev, mnode),
205 % scmutil.formatrevnode(self.ui, mrev, mnode),
206 label='ui.debug log.manifest')
206 label='ui.debug log.manifest')
207 self.ui.write(columns['user'] % ctx.user(), label='log.user')
207 self.ui.write(columns['user'] % ctx.user(), label='log.user')
208 self.ui.write(columns['date'] % util.datestr(ctx.date()),
208 self.ui.write(columns['date'] % util.datestr(ctx.date()),
209 label='log.date')
209 label='log.date')
210
210
211 if ctx.isunstable():
211 if ctx.isunstable():
212 instabilities = ctx.instabilities()
212 instabilities = ctx.instabilities()
213 self.ui.write(columns['instability'] % ', '.join(instabilities),
213 self.ui.write(columns['instability'] % ', '.join(instabilities),
214 label='log.instability')
214 label='log.instability')
215
215
216 elif ctx.obsolete():
216 elif ctx.obsolete():
217 self._showobsfate(ctx)
217 self._showobsfate(ctx)
218
218
219 self._exthook(ctx)
219 self._exthook(ctx)
220
220
221 if self.ui.debugflag:
221 if self.ui.debugflag:
222 files = ctx.p1().status(ctx)[:3]
222 files = ctx.p1().status(ctx)[:3]
223 for key, value in zip(['files', 'files+', 'files-'], files):
223 for key, value in zip(['files', 'files+', 'files-'], files):
224 if value:
224 if value:
225 self.ui.write(columns[key] % " ".join(value),
225 self.ui.write(columns[key] % " ".join(value),
226 label='ui.debug log.files')
226 label='ui.debug log.files')
227 elif ctx.files() and self.ui.verbose:
227 elif ctx.files() and self.ui.verbose:
228 self.ui.write(columns['files'] % " ".join(ctx.files()),
228 self.ui.write(columns['files'] % " ".join(ctx.files()),
229 label='ui.note log.files')
229 label='ui.note log.files')
230 if copies and self.ui.verbose:
230 if copies and self.ui.verbose:
231 copies = ['%s (%s)' % c for c in copies]
231 copies = ['%s (%s)' % c for c in copies]
232 self.ui.write(columns['copies'] % ' '.join(copies),
232 self.ui.write(columns['copies'] % ' '.join(copies),
233 label='ui.note log.copies')
233 label='ui.note log.copies')
234
234
235 extra = ctx.extra()
235 extra = ctx.extra()
236 if extra and self.ui.debugflag:
236 if extra and self.ui.debugflag:
237 for key, value in sorted(extra.items()):
237 for key, value in sorted(extra.items()):
238 self.ui.write(columns['extra'] % (key, util.escapestr(value)),
238 self.ui.write(columns['extra'] % (key, util.escapestr(value)),
239 label='ui.debug log.extra')
239 label='ui.debug log.extra')
240
240
241 description = ctx.description().strip()
241 description = ctx.description().strip()
242 if description:
242 if description:
243 if self.ui.verbose:
243 if self.ui.verbose:
244 self.ui.write(_("description:\n"),
244 self.ui.write(_("description:\n"),
245 label='ui.note log.description')
245 label='ui.note log.description')
246 self.ui.write(description,
246 self.ui.write(description,
247 label='ui.note log.description')
247 label='ui.note log.description')
248 self.ui.write("\n\n")
248 self.ui.write("\n\n")
249 else:
249 else:
250 self.ui.write(columns['summary'] % description.splitlines()[0],
250 self.ui.write(columns['summary'] % description.splitlines()[0],
251 label='log.summary')
251 label='log.summary')
252 self.ui.write("\n")
252 self.ui.write("\n")
253
253
254 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
254 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
255
255
256 def _showobsfate(self, ctx):
256 def _showobsfate(self, ctx):
257 obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
257 obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
258
258
259 if obsfate:
259 if obsfate:
260 for obsfateline in obsfate:
260 for obsfateline in obsfate:
261 self.ui.write(self._columns['obsolete'] % obsfateline,
261 self.ui.write(self._columns['obsolete'] % obsfateline,
262 label='log.obsfate')
262 label='log.obsfate')
263
263
264 def _exthook(self, ctx):
264 def _exthook(self, ctx):
265 '''empty method used by extension as a hook point
265 '''empty method used by extension as a hook point
266 '''
266 '''
267
267
268 def showpatch(self, ctx, matchfn, hunksfilterfn=None):
268 def showpatch(self, ctx, matchfn, hunksfilterfn=None):
269 if not matchfn:
269 if not matchfn:
270 matchfn = self.matchfn
270 matchfn = self.matchfn
271 if matchfn:
271 if matchfn:
272 stat = self.diffopts.get('stat')
272 stat = self.diffopts.get('stat')
273 diff = self.diffopts.get('patch')
273 diff = self.diffopts.get('patch')
274 diffopts = patch.diffallopts(self.ui, self.diffopts)
274 diffopts = patch.diffallopts(self.ui, self.diffopts)
275 node = ctx.node()
275 node = ctx.node()
276 prev = ctx.p1().node()
276 prev = ctx.p1().node()
277 if stat:
277 if stat:
278 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
278 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
279 match=matchfn, stat=True,
279 match=matchfn, stat=True,
280 hunksfilterfn=hunksfilterfn)
280 hunksfilterfn=hunksfilterfn)
281 if diff:
281 if diff:
282 if stat:
282 if stat:
283 self.ui.write("\n")
283 self.ui.write("\n")
284 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
284 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
285 match=matchfn, stat=False,
285 match=matchfn, stat=False,
286 hunksfilterfn=hunksfilterfn)
286 hunksfilterfn=hunksfilterfn)
287 if stat or diff:
287 if stat or diff:
288 self.ui.write("\n")
288 self.ui.write("\n")
289
289
290 class jsonchangeset(changesetprinter):
290 class jsonchangeset(changesetprinter):
291 '''format changeset information.'''
291 '''format changeset information.'''
292
292
293 def __init__(self, ui, repo, matchfn, diffopts, buffered):
293 def __init__(self, ui, repo, matchfn, diffopts, buffered):
294 changesetprinter.__init__(self, ui, repo, matchfn, diffopts, buffered)
294 changesetprinter.__init__(self, ui, repo, matchfn, diffopts, buffered)
295 self.cache = {}
295 self.cache = {}
296 self._first = True
296 self._first = True
297
297
298 def close(self):
298 def close(self):
299 if not self._first:
299 if not self._first:
300 self.ui.write("\n]\n")
300 self.ui.write("\n]\n")
301 else:
301 else:
302 self.ui.write("[]\n")
302 self.ui.write("[]\n")
303
303
304 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
304 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
305 '''show a single changeset or file revision'''
305 '''show a single changeset or file revision'''
306 rev = ctx.rev()
306 rev = ctx.rev()
307 if rev is None:
307 if rev is None:
308 jrev = jnode = 'null'
308 jrev = jnode = 'null'
309 else:
309 else:
310 jrev = '%d' % rev
310 jrev = '%d' % rev
311 jnode = '"%s"' % hex(ctx.node())
311 jnode = '"%s"' % hex(ctx.node())
312 j = encoding.jsonescape
312 j = encoding.jsonescape
313
313
314 if self._first:
314 if self._first:
315 self.ui.write("[\n {")
315 self.ui.write("[\n {")
316 self._first = False
316 self._first = False
317 else:
317 else:
318 self.ui.write(",\n {")
318 self.ui.write(",\n {")
319
319
320 if self.ui.quiet:
320 if self.ui.quiet:
321 self.ui.write(('\n "rev": %s') % jrev)
321 self.ui.write(('\n "rev": %s') % jrev)
322 self.ui.write((',\n "node": %s') % jnode)
322 self.ui.write((',\n "node": %s') % jnode)
323 self.ui.write('\n }')
323 self.ui.write('\n }')
324 return
324 return
325
325
326 self.ui.write(('\n "rev": %s') % jrev)
326 self.ui.write(('\n "rev": %s') % jrev)
327 self.ui.write((',\n "node": %s') % jnode)
327 self.ui.write((',\n "node": %s') % jnode)
328 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
328 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
329 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
329 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
330 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
330 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
331 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
331 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
332 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
332 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
333
333
334 self.ui.write((',\n "bookmarks": [%s]') %
334 self.ui.write((',\n "bookmarks": [%s]') %
335 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
335 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
336 self.ui.write((',\n "tags": [%s]') %
336 self.ui.write((',\n "tags": [%s]') %
337 ", ".join('"%s"' % j(t) for t in ctx.tags()))
337 ", ".join('"%s"' % j(t) for t in ctx.tags()))
338 self.ui.write((',\n "parents": [%s]') %
338 self.ui.write((',\n "parents": [%s]') %
339 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
339 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
340
340
341 if self.ui.debugflag:
341 if self.ui.debugflag:
342 if rev is None:
342 if rev is None:
343 jmanifestnode = 'null'
343 jmanifestnode = 'null'
344 else:
344 else:
345 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
345 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
346 self.ui.write((',\n "manifest": %s') % jmanifestnode)
346 self.ui.write((',\n "manifest": %s') % jmanifestnode)
347
347
348 self.ui.write((',\n "extra": {%s}') %
348 self.ui.write((',\n "extra": {%s}') %
349 ", ".join('"%s": "%s"' % (j(k), j(v))
349 ", ".join('"%s": "%s"' % (j(k), j(v))
350 for k, v in ctx.extra().items()))
350 for k, v in ctx.extra().items()))
351
351
352 files = ctx.p1().status(ctx)
352 files = ctx.p1().status(ctx)
353 self.ui.write((',\n "modified": [%s]') %
353 self.ui.write((',\n "modified": [%s]') %
354 ", ".join('"%s"' % j(f) for f in files[0]))
354 ", ".join('"%s"' % j(f) for f in files[0]))
355 self.ui.write((',\n "added": [%s]') %
355 self.ui.write((',\n "added": [%s]') %
356 ", ".join('"%s"' % j(f) for f in files[1]))
356 ", ".join('"%s"' % j(f) for f in files[1]))
357 self.ui.write((',\n "removed": [%s]') %
357 self.ui.write((',\n "removed": [%s]') %
358 ", ".join('"%s"' % j(f) for f in files[2]))
358 ", ".join('"%s"' % j(f) for f in files[2]))
359
359
360 elif self.ui.verbose:
360 elif self.ui.verbose:
361 self.ui.write((',\n "files": [%s]') %
361 self.ui.write((',\n "files": [%s]') %
362 ", ".join('"%s"' % j(f) for f in ctx.files()))
362 ", ".join('"%s"' % j(f) for f in ctx.files()))
363
363
364 if copies:
364 if copies:
365 self.ui.write((',\n "copies": {%s}') %
365 self.ui.write((',\n "copies": {%s}') %
366 ", ".join('"%s": "%s"' % (j(k), j(v))
366 ", ".join('"%s": "%s"' % (j(k), j(v))
367 for k, v in copies))
367 for k, v in copies))
368
368
369 matchfn = self.matchfn
369 matchfn = self.matchfn
370 if matchfn:
370 if matchfn:
371 stat = self.diffopts.get('stat')
371 stat = self.diffopts.get('stat')
372 diff = self.diffopts.get('patch')
372 diff = self.diffopts.get('patch')
373 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
373 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
374 node, prev = ctx.node(), ctx.p1().node()
374 node, prev = ctx.node(), ctx.p1().node()
375 if stat:
375 if stat:
376 self.ui.pushbuffer()
376 self.ui.pushbuffer()
377 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
377 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
378 match=matchfn, stat=True)
378 match=matchfn, stat=True)
379 self.ui.write((',\n "diffstat": "%s"')
379 self.ui.write((',\n "diffstat": "%s"')
380 % j(self.ui.popbuffer()))
380 % j(self.ui.popbuffer()))
381 if diff:
381 if diff:
382 self.ui.pushbuffer()
382 self.ui.pushbuffer()
383 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
383 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
384 match=matchfn, stat=False)
384 match=matchfn, stat=False)
385 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
385 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
386
386
387 self.ui.write("\n }")
387 self.ui.write("\n }")
388
388
389 class changesettemplater(changesetprinter):
389 class changesettemplater(changesetprinter):
390 '''format changeset information.
390 '''format changeset information.
391
391
392 Note: there are a variety of convenience functions to build a
392 Note: there are a variety of convenience functions to build a
393 changesettemplater for common cases. See functions such as:
393 changesettemplater for common cases. See functions such as:
394 makelogtemplater, changesetdisplayer, buildcommittemplate, or other
394 maketemplater, changesetdisplayer, buildcommittemplate, or other
395 functions that use changesest_templater.
395 functions that use changesest_templater.
396 '''
396 '''
397
397
398 # Arguments before "buffered" used to be positional. Consider not
398 # Arguments before "buffered" used to be positional. Consider not
399 # adding/removing arguments before "buffered" to not break callers.
399 # adding/removing arguments before "buffered" to not break callers.
400 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
400 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
401 buffered=False):
401 buffered=False):
402 diffopts = diffopts or {}
402 diffopts = diffopts or {}
403
403
404 changesetprinter.__init__(self, ui, repo, matchfn, diffopts, buffered)
404 changesetprinter.__init__(self, ui, repo, matchfn, diffopts, buffered)
405 tres = formatter.templateresources(ui, repo)
405 tres = formatter.templateresources(ui, repo)
406 self.t = formatter.loadtemplater(ui, tmplspec,
406 self.t = formatter.loadtemplater(ui, tmplspec,
407 defaults=templatekw.keywords,
407 defaults=templatekw.keywords,
408 resources=tres,
408 resources=tres,
409 cache=templatekw.defaulttempl)
409 cache=templatekw.defaulttempl)
410 self._counter = itertools.count()
410 self._counter = itertools.count()
411 self.cache = tres['cache'] # shared with _graphnodeformatter()
411 self.cache = tres['cache'] # shared with _graphnodeformatter()
412
412
413 self._tref = tmplspec.ref
413 self._tref = tmplspec.ref
414 self._parts = {'header': '', 'footer': '',
414 self._parts = {'header': '', 'footer': '',
415 tmplspec.ref: tmplspec.ref,
415 tmplspec.ref: tmplspec.ref,
416 'docheader': '', 'docfooter': '',
416 'docheader': '', 'docfooter': '',
417 'separator': ''}
417 'separator': ''}
418 if tmplspec.mapfile:
418 if tmplspec.mapfile:
419 # find correct templates for current mode, for backward
419 # find correct templates for current mode, for backward
420 # compatibility with 'log -v/-q/--debug' using a mapfile
420 # compatibility with 'log -v/-q/--debug' using a mapfile
421 tmplmodes = [
421 tmplmodes = [
422 (True, ''),
422 (True, ''),
423 (self.ui.verbose, '_verbose'),
423 (self.ui.verbose, '_verbose'),
424 (self.ui.quiet, '_quiet'),
424 (self.ui.quiet, '_quiet'),
425 (self.ui.debugflag, '_debug'),
425 (self.ui.debugflag, '_debug'),
426 ]
426 ]
427 for mode, postfix in tmplmodes:
427 for mode, postfix in tmplmodes:
428 for t in self._parts:
428 for t in self._parts:
429 cur = t + postfix
429 cur = t + postfix
430 if mode and cur in self.t:
430 if mode and cur in self.t:
431 self._parts[t] = cur
431 self._parts[t] = cur
432 else:
432 else:
433 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
433 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
434 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
434 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
435 self._parts.update(m)
435 self._parts.update(m)
436
436
437 if self._parts['docheader']:
437 if self._parts['docheader']:
438 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
438 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
439
439
440 def close(self):
440 def close(self):
441 if self._parts['docfooter']:
441 if self._parts['docfooter']:
442 if not self.footer:
442 if not self.footer:
443 self.footer = ""
443 self.footer = ""
444 self.footer += templater.stringify(self.t(self._parts['docfooter']))
444 self.footer += templater.stringify(self.t(self._parts['docfooter']))
445 return super(changesettemplater, self).close()
445 return super(changesettemplater, self).close()
446
446
447 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
447 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
448 '''show a single changeset or file revision'''
448 '''show a single changeset or file revision'''
449 props = props.copy()
449 props = props.copy()
450 props['ctx'] = ctx
450 props['ctx'] = ctx
451 props['index'] = index = next(self._counter)
451 props['index'] = index = next(self._counter)
452 props['revcache'] = {'copies': copies}
452 props['revcache'] = {'copies': copies}
453 props = pycompat.strkwargs(props)
453 props = pycompat.strkwargs(props)
454
454
455 # write separator, which wouldn't work well with the header part below
455 # write separator, which wouldn't work well with the header part below
456 # since there's inherently a conflict between header (across items) and
456 # since there's inherently a conflict between header (across items) and
457 # separator (per item)
457 # separator (per item)
458 if self._parts['separator'] and index > 0:
458 if self._parts['separator'] and index > 0:
459 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
459 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
460
460
461 # write header
461 # write header
462 if self._parts['header']:
462 if self._parts['header']:
463 h = templater.stringify(self.t(self._parts['header'], **props))
463 h = templater.stringify(self.t(self._parts['header'], **props))
464 if self.buffered:
464 if self.buffered:
465 self.header[ctx.rev()] = h
465 self.header[ctx.rev()] = h
466 else:
466 else:
467 if self.lastheader != h:
467 if self.lastheader != h:
468 self.lastheader = h
468 self.lastheader = h
469 self.ui.write(h)
469 self.ui.write(h)
470
470
471 # write changeset metadata, then patch if requested
471 # write changeset metadata, then patch if requested
472 key = self._parts[self._tref]
472 key = self._parts[self._tref]
473 self.ui.write(templater.stringify(self.t(key, **props)))
473 self.ui.write(templater.stringify(self.t(key, **props)))
474 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
474 self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
475
475
476 if self._parts['footer']:
476 if self._parts['footer']:
477 if not self.footer:
477 if not self.footer:
478 self.footer = templater.stringify(
478 self.footer = templater.stringify(
479 self.t(self._parts['footer'], **props))
479 self.t(self._parts['footer'], **props))
480
480
481 def logtemplatespec(tmpl, mapfile):
481 def templatespec(tmpl, mapfile):
482 if mapfile:
482 if mapfile:
483 return formatter.templatespec('changeset', tmpl, mapfile)
483 return formatter.templatespec('changeset', tmpl, mapfile)
484 else:
484 else:
485 return formatter.templatespec('', tmpl, None)
485 return formatter.templatespec('', tmpl, None)
486
486
487 def _lookuplogtemplate(ui, tmpl, style):
487 def _lookuptemplate(ui, tmpl, style):
488 """Find the template matching the given template spec or style
488 """Find the template matching the given template spec or style
489
489
490 See formatter.lookuptemplate() for details.
490 See formatter.lookuptemplate() for details.
491 """
491 """
492
492
493 # ui settings
493 # ui settings
494 if not tmpl and not style: # template are stronger than style
494 if not tmpl and not style: # template are stronger than style
495 tmpl = ui.config('ui', 'logtemplate')
495 tmpl = ui.config('ui', 'logtemplate')
496 if tmpl:
496 if tmpl:
497 return logtemplatespec(templater.unquotestring(tmpl), None)
497 return templatespec(templater.unquotestring(tmpl), None)
498 else:
498 else:
499 style = util.expandpath(ui.config('ui', 'style'))
499 style = util.expandpath(ui.config('ui', 'style'))
500
500
501 if not tmpl and style:
501 if not tmpl and style:
502 mapfile = style
502 mapfile = style
503 if not os.path.split(mapfile)[0]:
503 if not os.path.split(mapfile)[0]:
504 mapname = (templater.templatepath('map-cmdline.' + mapfile)
504 mapname = (templater.templatepath('map-cmdline.' + mapfile)
505 or templater.templatepath(mapfile))
505 or templater.templatepath(mapfile))
506 if mapname:
506 if mapname:
507 mapfile = mapname
507 mapfile = mapname
508 return logtemplatespec(None, mapfile)
508 return templatespec(None, mapfile)
509
509
510 if not tmpl:
510 if not tmpl:
511 return logtemplatespec(None, None)
511 return templatespec(None, None)
512
512
513 return formatter.lookuptemplate(ui, 'changeset', tmpl)
513 return formatter.lookuptemplate(ui, 'changeset', tmpl)
514
514
515 def makelogtemplater(ui, repo, tmpl, buffered=False):
515 def maketemplater(ui, repo, tmpl, buffered=False):
516 """Create a changesettemplater from a literal template 'tmpl'
516 """Create a changesettemplater from a literal template 'tmpl'
517 byte-string."""
517 byte-string."""
518 spec = logtemplatespec(tmpl, None)
518 spec = templatespec(tmpl, None)
519 return changesettemplater(ui, repo, spec, buffered=buffered)
519 return changesettemplater(ui, repo, spec, buffered=buffered)
520
520
521 def changesetdisplayer(ui, repo, opts, buffered=False):
521 def changesetdisplayer(ui, repo, opts, buffered=False):
522 """show one changeset using template or regular display.
522 """show one changeset using template or regular display.
523
523
524 Display format will be the first non-empty hit of:
524 Display format will be the first non-empty hit of:
525 1. option 'template'
525 1. option 'template'
526 2. option 'style'
526 2. option 'style'
527 3. [ui] setting 'logtemplate'
527 3. [ui] setting 'logtemplate'
528 4. [ui] setting 'style'
528 4. [ui] setting 'style'
529 If all of these values are either the unset or the empty string,
529 If all of these values are either the unset or the empty string,
530 regular display via changesetprinter() is done.
530 regular display via changesetprinter() is done.
531 """
531 """
532 # options
532 # options
533 match = None
533 match = None
534 if opts.get('patch') or opts.get('stat'):
534 if opts.get('patch') or opts.get('stat'):
535 match = scmutil.matchall(repo)
535 match = scmutil.matchall(repo)
536
536
537 if opts.get('template') == 'json':
537 if opts.get('template') == 'json':
538 return jsonchangeset(ui, repo, match, opts, buffered)
538 return jsonchangeset(ui, repo, match, opts, buffered)
539
539
540 spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
540 spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
541
541
542 if not spec.ref and not spec.tmpl and not spec.mapfile:
542 if not spec.ref and not spec.tmpl and not spec.mapfile:
543 return changesetprinter(ui, repo, match, opts, buffered)
543 return changesetprinter(ui, repo, match, opts, buffered)
544
544
545 return changesettemplater(ui, repo, spec, match, opts, buffered)
545 return changesettemplater(ui, repo, spec, match, opts, buffered)
546
546
547 def _makelogmatcher(repo, revs, pats, opts):
547 def _makematcher(repo, revs, pats, opts):
548 """Build matcher and expanded patterns from log options
548 """Build matcher and expanded patterns from log options
549
549
550 If --follow, revs are the revisions to follow from.
550 If --follow, revs are the revisions to follow from.
551
551
552 Returns (match, pats, slowpath) where
552 Returns (match, pats, slowpath) where
553 - match: a matcher built from the given pats and -I/-X opts
553 - match: a matcher built from the given pats and -I/-X opts
554 - pats: patterns used (globs are expanded on Windows)
554 - pats: patterns used (globs are expanded on Windows)
555 - slowpath: True if patterns aren't as simple as scanning filelogs
555 - slowpath: True if patterns aren't as simple as scanning filelogs
556 """
556 """
557 # pats/include/exclude are passed to match.match() directly in
557 # pats/include/exclude are passed to match.match() directly in
558 # _matchfiles() revset but walkchangerevs() builds its matcher with
558 # _matchfiles() revset but walkchangerevs() builds its matcher with
559 # scmutil.match(). The difference is input pats are globbed on
559 # scmutil.match(). The difference is input pats are globbed on
560 # platforms without shell expansion (windows).
560 # platforms without shell expansion (windows).
561 wctx = repo[None]
561 wctx = repo[None]
562 match, pats = scmutil.matchandpats(wctx, pats, opts)
562 match, pats = scmutil.matchandpats(wctx, pats, opts)
563 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
563 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
564 if not slowpath:
564 if not slowpath:
565 follow = opts.get('follow') or opts.get('follow_first')
565 follow = opts.get('follow') or opts.get('follow_first')
566 startctxs = []
566 startctxs = []
567 if follow and opts.get('rev'):
567 if follow and opts.get('rev'):
568 startctxs = [repo[r] for r in revs]
568 startctxs = [repo[r] for r in revs]
569 for f in match.files():
569 for f in match.files():
570 if follow and startctxs:
570 if follow and startctxs:
571 # No idea if the path was a directory at that revision, so
571 # No idea if the path was a directory at that revision, so
572 # take the slow path.
572 # take the slow path.
573 if any(f not in c for c in startctxs):
573 if any(f not in c for c in startctxs):
574 slowpath = True
574 slowpath = True
575 continue
575 continue
576 elif follow and f not in wctx:
576 elif follow and f not in wctx:
577 # If the file exists, it may be a directory, so let it
577 # If the file exists, it may be a directory, so let it
578 # take the slow path.
578 # take the slow path.
579 if os.path.exists(repo.wjoin(f)):
579 if os.path.exists(repo.wjoin(f)):
580 slowpath = True
580 slowpath = True
581 continue
581 continue
582 else:
582 else:
583 raise error.Abort(_('cannot follow file not in parent '
583 raise error.Abort(_('cannot follow file not in parent '
584 'revision: "%s"') % f)
584 'revision: "%s"') % f)
585 filelog = repo.file(f)
585 filelog = repo.file(f)
586 if not filelog:
586 if not filelog:
587 # A zero count may be a directory or deleted file, so
587 # A zero count may be a directory or deleted file, so
588 # try to find matching entries on the slow path.
588 # try to find matching entries on the slow path.
589 if follow:
589 if follow:
590 raise error.Abort(
590 raise error.Abort(
591 _('cannot follow nonexistent file: "%s"') % f)
591 _('cannot follow nonexistent file: "%s"') % f)
592 slowpath = True
592 slowpath = True
593
593
594 # We decided to fall back to the slowpath because at least one
594 # We decided to fall back to the slowpath because at least one
595 # of the paths was not a file. Check to see if at least one of them
595 # of the paths was not a file. Check to see if at least one of them
596 # existed in history - in that case, we'll continue down the
596 # existed in history - in that case, we'll continue down the
597 # slowpath; otherwise, we can turn off the slowpath
597 # slowpath; otherwise, we can turn off the slowpath
598 if slowpath:
598 if slowpath:
599 for path in match.files():
599 for path in match.files():
600 if path == '.' or path in repo.store:
600 if path == '.' or path in repo.store:
601 break
601 break
602 else:
602 else:
603 slowpath = False
603 slowpath = False
604
604
605 return match, pats, slowpath
605 return match, pats, slowpath
606
606
607 def _fileancestors(repo, revs, match, followfirst):
607 def _fileancestors(repo, revs, match, followfirst):
608 fctxs = []
608 fctxs = []
609 for r in revs:
609 for r in revs:
610 ctx = repo[r]
610 ctx = repo[r]
611 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
611 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
612
612
613 # When displaying a revision with --patch --follow FILE, we have
613 # When displaying a revision with --patch --follow FILE, we have
614 # to know which file of the revision must be diffed. With
614 # to know which file of the revision must be diffed. With
615 # --follow, we want the names of the ancestors of FILE in the
615 # --follow, we want the names of the ancestors of FILE in the
616 # revision, stored in "fcache". "fcache" is populated as a side effect
616 # revision, stored in "fcache". "fcache" is populated as a side effect
617 # of the graph traversal.
617 # of the graph traversal.
618 fcache = {}
618 fcache = {}
619 def filematcher(rev):
619 def filematcher(rev):
620 return scmutil.matchfiles(repo, fcache.get(rev, []))
620 return scmutil.matchfiles(repo, fcache.get(rev, []))
621
621
622 def revgen():
622 def revgen():
623 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
623 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
624 fcache[rev] = [c.path() for c in cs]
624 fcache[rev] = [c.path() for c in cs]
625 yield rev
625 yield rev
626 return smartset.generatorset(revgen(), iterasc=False), filematcher
626 return smartset.generatorset(revgen(), iterasc=False), filematcher
627
627
628 def _makenofollowlogfilematcher(repo, pats, opts):
628 def _makenofollowfilematcher(repo, pats, opts):
629 '''hook for extensions to override the filematcher for non-follow cases'''
629 '''hook for extensions to override the filematcher for non-follow cases'''
630 return None
630 return None
631
631
632 _opt2logrevset = {
632 _opt2logrevset = {
633 'no_merges': ('not merge()', None),
633 'no_merges': ('not merge()', None),
634 'only_merges': ('merge()', None),
634 'only_merges': ('merge()', None),
635 '_matchfiles': (None, '_matchfiles(%ps)'),
635 '_matchfiles': (None, '_matchfiles(%ps)'),
636 'date': ('date(%s)', None),
636 'date': ('date(%s)', None),
637 'branch': ('branch(%s)', '%lr'),
637 'branch': ('branch(%s)', '%lr'),
638 '_patslog': ('filelog(%s)', '%lr'),
638 '_patslog': ('filelog(%s)', '%lr'),
639 'keyword': ('keyword(%s)', '%lr'),
639 'keyword': ('keyword(%s)', '%lr'),
640 'prune': ('ancestors(%s)', 'not %lr'),
640 'prune': ('ancestors(%s)', 'not %lr'),
641 'user': ('user(%s)', '%lr'),
641 'user': ('user(%s)', '%lr'),
642 }
642 }
643
643
644 def _makelogrevset(repo, match, pats, slowpath, opts):
644 def _makerevset(repo, match, pats, slowpath, opts):
645 """Return a revset string built from log options and file patterns"""
645 """Return a revset string built from log options and file patterns"""
646 opts = dict(opts)
646 opts = dict(opts)
647 # follow or not follow?
647 # follow or not follow?
648 follow = opts.get('follow') or opts.get('follow_first')
648 follow = opts.get('follow') or opts.get('follow_first')
649
649
650 # branch and only_branch are really aliases and must be handled at
650 # branch and only_branch are really aliases and must be handled at
651 # the same time
651 # the same time
652 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
652 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
653 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
653 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
654
654
655 if slowpath:
655 if slowpath:
656 # See walkchangerevs() slow path.
656 # See walkchangerevs() slow path.
657 #
657 #
658 # pats/include/exclude cannot be represented as separate
658 # pats/include/exclude cannot be represented as separate
659 # revset expressions as their filtering logic applies at file
659 # revset expressions as their filtering logic applies at file
660 # level. For instance "-I a -X b" matches a revision touching
660 # level. For instance "-I a -X b" matches a revision touching
661 # "a" and "b" while "file(a) and not file(b)" does
661 # "a" and "b" while "file(a) and not file(b)" does
662 # not. Besides, filesets are evaluated against the working
662 # not. Besides, filesets are evaluated against the working
663 # directory.
663 # directory.
664 matchargs = ['r:', 'd:relpath']
664 matchargs = ['r:', 'd:relpath']
665 for p in pats:
665 for p in pats:
666 matchargs.append('p:' + p)
666 matchargs.append('p:' + p)
667 for p in opts.get('include', []):
667 for p in opts.get('include', []):
668 matchargs.append('i:' + p)
668 matchargs.append('i:' + p)
669 for p in opts.get('exclude', []):
669 for p in opts.get('exclude', []):
670 matchargs.append('x:' + p)
670 matchargs.append('x:' + p)
671 opts['_matchfiles'] = matchargs
671 opts['_matchfiles'] = matchargs
672 elif not follow:
672 elif not follow:
673 opts['_patslog'] = list(pats)
673 opts['_patslog'] = list(pats)
674
674
675 expr = []
675 expr = []
676 for op, val in sorted(opts.iteritems()):
676 for op, val in sorted(opts.iteritems()):
677 if not val:
677 if not val:
678 continue
678 continue
679 if op not in _opt2logrevset:
679 if op not in _opt2logrevset:
680 continue
680 continue
681 revop, listop = _opt2logrevset[op]
681 revop, listop = _opt2logrevset[op]
682 if revop and '%' not in revop:
682 if revop and '%' not in revop:
683 expr.append(revop)
683 expr.append(revop)
684 elif not listop:
684 elif not listop:
685 expr.append(revsetlang.formatspec(revop, val))
685 expr.append(revsetlang.formatspec(revop, val))
686 else:
686 else:
687 if revop:
687 if revop:
688 val = [revsetlang.formatspec(revop, v) for v in val]
688 val = [revsetlang.formatspec(revop, v) for v in val]
689 expr.append(revsetlang.formatspec(listop, val))
689 expr.append(revsetlang.formatspec(listop, val))
690
690
691 if expr:
691 if expr:
692 expr = '(' + ' and '.join(expr) + ')'
692 expr = '(' + ' and '.join(expr) + ')'
693 else:
693 else:
694 expr = None
694 expr = None
695 return expr
695 return expr
696
696
697 def _logrevs(repo, opts):
697 def _initialrevs(repo, opts):
698 """Return the initial set of revisions to be filtered or followed"""
698 """Return the initial set of revisions to be filtered or followed"""
699 follow = opts.get('follow') or opts.get('follow_first')
699 follow = opts.get('follow') or opts.get('follow_first')
700 if opts.get('rev'):
700 if opts.get('rev'):
701 revs = scmutil.revrange(repo, opts['rev'])
701 revs = scmutil.revrange(repo, opts['rev'])
702 elif follow and repo.dirstate.p1() == nullid:
702 elif follow and repo.dirstate.p1() == nullid:
703 revs = smartset.baseset()
703 revs = smartset.baseset()
704 elif follow:
704 elif follow:
705 revs = repo.revs('.')
705 revs = repo.revs('.')
706 else:
706 else:
707 revs = smartset.spanset(repo)
707 revs = smartset.spanset(repo)
708 revs.reverse()
708 revs.reverse()
709 return revs
709 return revs
710
710
711 def getlogrevs(repo, pats, opts):
711 def getrevs(repo, pats, opts):
712 """Return (revs, filematcher) where revs is a smartset
712 """Return (revs, filematcher) where revs is a smartset
713
713
714 filematcher is a callable taking a revision number and returning a match
714 filematcher is a callable taking a revision number and returning a match
715 objects filtering the files to be detailed when displaying the revision.
715 objects filtering the files to be detailed when displaying the revision.
716 """
716 """
717 follow = opts.get('follow') or opts.get('follow_first')
717 follow = opts.get('follow') or opts.get('follow_first')
718 followfirst = opts.get('follow_first')
718 followfirst = opts.get('follow_first')
719 limit = loglimit(opts)
719 limit = getlimit(opts)
720 revs = _logrevs(repo, opts)
720 revs = _initialrevs(repo, opts)
721 if not revs:
721 if not revs:
722 return smartset.baseset(), None
722 return smartset.baseset(), None
723 match, pats, slowpath = _makelogmatcher(repo, revs, pats, opts)
723 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
724 filematcher = None
724 filematcher = None
725 if follow:
725 if follow:
726 if slowpath or match.always():
726 if slowpath or match.always():
727 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
727 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
728 else:
728 else:
729 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
729 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
730 revs.reverse()
730 revs.reverse()
731 if filematcher is None:
731 if filematcher is None:
732 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
732 filematcher = _makenofollowfilematcher(repo, pats, opts)
733 if filematcher is None:
733 if filematcher is None:
734 def filematcher(rev):
734 def filematcher(rev):
735 return match
735 return match
736
736
737 expr = _makelogrevset(repo, match, pats, slowpath, opts)
737 expr = _makerevset(repo, match, pats, slowpath, opts)
738 if opts.get('graph') and opts.get('rev'):
738 if opts.get('graph') and opts.get('rev'):
739 # User-specified revs might be unsorted, but don't sort before
739 # User-specified revs might be unsorted, but don't sort before
740 # _makelogrevset because it might depend on the order of revs
740 # _makerevset because it might depend on the order of revs
741 if not (revs.isdescending() or revs.istopo()):
741 if not (revs.isdescending() or revs.istopo()):
742 revs.sort(reverse=True)
742 revs.sort(reverse=True)
743 if expr:
743 if expr:
744 matcher = revset.match(None, expr)
744 matcher = revset.match(None, expr)
745 revs = matcher(repo, revs)
745 revs = matcher(repo, revs)
746 if limit is not None:
746 if limit is not None:
747 revs = revs.slice(0, limit)
747 revs = revs.slice(0, limit)
748 return revs, filematcher
748 return revs, filematcher
749
749
750 def _parselinerangelogopt(repo, opts):
750 def _parselinerangeopt(repo, opts):
751 """Parse --line-range log option and return a list of tuples (filename,
751 """Parse --line-range log option and return a list of tuples (filename,
752 (fromline, toline)).
752 (fromline, toline)).
753 """
753 """
754 linerangebyfname = []
754 linerangebyfname = []
755 for pat in opts.get('line_range', []):
755 for pat in opts.get('line_range', []):
756 try:
756 try:
757 pat, linerange = pat.rsplit(',', 1)
757 pat, linerange = pat.rsplit(',', 1)
758 except ValueError:
758 except ValueError:
759 raise error.Abort(_('malformatted line-range pattern %s') % pat)
759 raise error.Abort(_('malformatted line-range pattern %s') % pat)
760 try:
760 try:
761 fromline, toline = map(int, linerange.split(':'))
761 fromline, toline = map(int, linerange.split(':'))
762 except ValueError:
762 except ValueError:
763 raise error.Abort(_("invalid line range for %s") % pat)
763 raise error.Abort(_("invalid line range for %s") % pat)
764 msg = _("line range pattern '%s' must match exactly one file") % pat
764 msg = _("line range pattern '%s' must match exactly one file") % pat
765 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
765 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
766 linerangebyfname.append(
766 linerangebyfname.append(
767 (fname, util.processlinerange(fromline, toline)))
767 (fname, util.processlinerange(fromline, toline)))
768 return linerangebyfname
768 return linerangebyfname
769
769
770 def getloglinerangerevs(repo, userrevs, opts):
770 def getlinerangerevs(repo, userrevs, opts):
771 """Return (revs, filematcher, hunksfilter).
771 """Return (revs, filematcher, hunksfilter).
772
772
773 "revs" are revisions obtained by processing "line-range" log options and
773 "revs" are revisions obtained by processing "line-range" log options and
774 walking block ancestors of each specified file/line-range.
774 walking block ancestors of each specified file/line-range.
775
775
776 "filematcher(rev) -> match" is a factory function returning a match object
776 "filematcher(rev) -> match" is a factory function returning a match object
777 for a given revision for file patterns specified in --line-range option.
777 for a given revision for file patterns specified in --line-range option.
778 If neither --stat nor --patch options are passed, "filematcher" is None.
778 If neither --stat nor --patch options are passed, "filematcher" is None.
779
779
780 "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
780 "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
781 returning a hunks filtering function.
781 returning a hunks filtering function.
782 If neither --stat nor --patch options are passed, "filterhunks" is None.
782 If neither --stat nor --patch options are passed, "filterhunks" is None.
783 """
783 """
784 wctx = repo[None]
784 wctx = repo[None]
785
785
786 # Two-levels map of "rev -> file ctx -> [line range]".
786 # Two-levels map of "rev -> file ctx -> [line range]".
787 linerangesbyrev = {}
787 linerangesbyrev = {}
788 for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
788 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
789 if fname not in wctx:
789 if fname not in wctx:
790 raise error.Abort(_('cannot follow file not in parent '
790 raise error.Abort(_('cannot follow file not in parent '
791 'revision: "%s"') % fname)
791 'revision: "%s"') % fname)
792 fctx = wctx.filectx(fname)
792 fctx = wctx.filectx(fname)
793 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
793 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
794 rev = fctx.introrev()
794 rev = fctx.introrev()
795 if rev not in userrevs:
795 if rev not in userrevs:
796 continue
796 continue
797 linerangesbyrev.setdefault(
797 linerangesbyrev.setdefault(
798 rev, {}).setdefault(
798 rev, {}).setdefault(
799 fctx.path(), []).append(linerange)
799 fctx.path(), []).append(linerange)
800
800
801 filematcher = None
801 filematcher = None
802 hunksfilter = None
802 hunksfilter = None
803 if opts.get('patch') or opts.get('stat'):
803 if opts.get('patch') or opts.get('stat'):
804
804
805 def nofilterhunksfn(fctx, hunks):
805 def nofilterhunksfn(fctx, hunks):
806 return hunks
806 return hunks
807
807
808 def hunksfilter(rev):
808 def hunksfilter(rev):
809 fctxlineranges = linerangesbyrev.get(rev)
809 fctxlineranges = linerangesbyrev.get(rev)
810 if fctxlineranges is None:
810 if fctxlineranges is None:
811 return nofilterhunksfn
811 return nofilterhunksfn
812
812
813 def filterfn(fctx, hunks):
813 def filterfn(fctx, hunks):
814 lineranges = fctxlineranges.get(fctx.path())
814 lineranges = fctxlineranges.get(fctx.path())
815 if lineranges is not None:
815 if lineranges is not None:
816 for hr, lines in hunks:
816 for hr, lines in hunks:
817 if hr is None: # binary
817 if hr is None: # binary
818 yield hr, lines
818 yield hr, lines
819 continue
819 continue
820 if any(mdiff.hunkinrange(hr[2:], lr)
820 if any(mdiff.hunkinrange(hr[2:], lr)
821 for lr in lineranges):
821 for lr in lineranges):
822 yield hr, lines
822 yield hr, lines
823 else:
823 else:
824 for hunk in hunks:
824 for hunk in hunks:
825 yield hunk
825 yield hunk
826
826
827 return filterfn
827 return filterfn
828
828
829 def filematcher(rev):
829 def filematcher(rev):
830 files = list(linerangesbyrev.get(rev, []))
830 files = list(linerangesbyrev.get(rev, []))
831 return scmutil.matchfiles(repo, files)
831 return scmutil.matchfiles(repo, files)
832
832
833 revs = sorted(linerangesbyrev, reverse=True)
833 revs = sorted(linerangesbyrev, reverse=True)
834
834
835 return revs, filematcher, hunksfilter
835 return revs, filematcher, hunksfilter
836
836
837 def _graphnodeformatter(ui, displayer):
837 def _graphnodeformatter(ui, displayer):
838 spec = ui.config('ui', 'graphnodetemplate')
838 spec = ui.config('ui', 'graphnodetemplate')
839 if not spec:
839 if not spec:
840 return templatekw.showgraphnode # fast path for "{graphnode}"
840 return templatekw.showgraphnode # fast path for "{graphnode}"
841
841
842 spec = templater.unquotestring(spec)
842 spec = templater.unquotestring(spec)
843 tres = formatter.templateresources(ui)
843 tres = formatter.templateresources(ui)
844 if isinstance(displayer, changesettemplater):
844 if isinstance(displayer, changesettemplater):
845 tres['cache'] = displayer.cache # reuse cache of slow templates
845 tres['cache'] = displayer.cache # reuse cache of slow templates
846 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
846 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
847 resources=tres)
847 resources=tres)
848 def formatnode(repo, ctx):
848 def formatnode(repo, ctx):
849 props = {'ctx': ctx, 'repo': repo, 'revcache': {}}
849 props = {'ctx': ctx, 'repo': repo, 'revcache': {}}
850 return templ.render(props)
850 return templ.render(props)
851 return formatnode
851 return formatnode
852
852
853 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
853 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
854 filematcher=None, props=None):
854 filematcher=None, props=None):
855 props = props or {}
855 props = props or {}
856 formatnode = _graphnodeformatter(ui, displayer)
856 formatnode = _graphnodeformatter(ui, displayer)
857 state = graphmod.asciistate()
857 state = graphmod.asciistate()
858 styles = state['styles']
858 styles = state['styles']
859
859
860 # only set graph styling if HGPLAIN is not set.
860 # only set graph styling if HGPLAIN is not set.
861 if ui.plain('graph'):
861 if ui.plain('graph'):
862 # set all edge styles to |, the default pre-3.8 behaviour
862 # set all edge styles to |, the default pre-3.8 behaviour
863 styles.update(dict.fromkeys(styles, '|'))
863 styles.update(dict.fromkeys(styles, '|'))
864 else:
864 else:
865 edgetypes = {
865 edgetypes = {
866 'parent': graphmod.PARENT,
866 'parent': graphmod.PARENT,
867 'grandparent': graphmod.GRANDPARENT,
867 'grandparent': graphmod.GRANDPARENT,
868 'missing': graphmod.MISSINGPARENT
868 'missing': graphmod.MISSINGPARENT
869 }
869 }
870 for name, key in edgetypes.items():
870 for name, key in edgetypes.items():
871 # experimental config: experimental.graphstyle.*
871 # experimental config: experimental.graphstyle.*
872 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
872 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
873 styles[key])
873 styles[key])
874 if not styles[key]:
874 if not styles[key]:
875 styles[key] = None
875 styles[key] = None
876
876
877 # experimental config: experimental.graphshorten
877 # experimental config: experimental.graphshorten
878 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
878 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
879
879
880 for rev, type, ctx, parents in dag:
880 for rev, type, ctx, parents in dag:
881 char = formatnode(repo, ctx)
881 char = formatnode(repo, ctx)
882 copies = None
882 copies = None
883 if getrenamed and ctx.rev():
883 if getrenamed and ctx.rev():
884 copies = []
884 copies = []
885 for fn in ctx.files():
885 for fn in ctx.files():
886 rename = getrenamed(fn, ctx.rev())
886 rename = getrenamed(fn, ctx.rev())
887 if rename:
887 if rename:
888 copies.append((fn, rename[0]))
888 copies.append((fn, rename[0]))
889 revmatchfn = None
889 revmatchfn = None
890 if filematcher is not None:
890 if filematcher is not None:
891 revmatchfn = filematcher(ctx.rev())
891 revmatchfn = filematcher(ctx.rev())
892 edges = edgefn(type, char, state, rev, parents)
892 edges = edgefn(type, char, state, rev, parents)
893 firstedge = next(edges)
893 firstedge = next(edges)
894 width = firstedge[2]
894 width = firstedge[2]
895 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
895 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
896 _graphwidth=width, **pycompat.strkwargs(props))
896 _graphwidth=width, **pycompat.strkwargs(props))
897 lines = displayer.hunk.pop(rev).split('\n')
897 lines = displayer.hunk.pop(rev).split('\n')
898 if not lines[-1]:
898 if not lines[-1]:
899 del lines[-1]
899 del lines[-1]
900 displayer.flush(ctx)
900 displayer.flush(ctx)
901 for type, char, width, coldata in itertools.chain([firstedge], edges):
901 for type, char, width, coldata in itertools.chain([firstedge], edges):
902 graphmod.ascii(ui, state, type, char, lines, coldata)
902 graphmod.ascii(ui, state, type, char, lines, coldata)
903 lines = []
903 lines = []
904 displayer.close()
904 displayer.close()
905
905
906 def graphlog(ui, repo, revs, filematcher, opts):
906 def graphlog(ui, repo, revs, filematcher, opts):
907 # Parameters are identical to log command ones
907 # Parameters are identical to log command ones
908 revdag = graphmod.dagwalker(repo, revs)
908 revdag = graphmod.dagwalker(repo, revs)
909
909
910 getrenamed = None
910 getrenamed = None
911 if opts.get('copies'):
911 if opts.get('copies'):
912 endrev = None
912 endrev = None
913 if opts.get('rev'):
913 if opts.get('rev'):
914 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
914 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
915 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
915 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
916
916
917 ui.pager('log')
917 ui.pager('log')
918 displayer = changesetdisplayer(ui, repo, opts, buffered=True)
918 displayer = changesetdisplayer(ui, repo, opts, buffered=True)
919 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
919 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
920 filematcher)
920 filematcher)
921
921
922 def checkunsupportedgraphflags(pats, opts):
922 def checkunsupportedgraphflags(pats, opts):
923 for op in ["newest_first"]:
923 for op in ["newest_first"]:
924 if op in opts and opts[op]:
924 if op in opts and opts[op]:
925 raise error.Abort(_("-G/--graph option is incompatible with --%s")
925 raise error.Abort(_("-G/--graph option is incompatible with --%s")
926 % op.replace("_", "-"))
926 % op.replace("_", "-"))
927
927
928 def graphrevs(repo, nodes, opts):
928 def graphrevs(repo, nodes, opts):
929 limit = loglimit(opts)
929 limit = getlimit(opts)
930 nodes.reverse()
930 nodes.reverse()
931 if limit is not None:
931 if limit is not None:
932 nodes = nodes[:limit]
932 nodes = nodes[:limit]
933 return graphmod.nodes(repo, nodes)
933 return graphmod.nodes(repo, nodes)
@@ -1,3487 +1,3487 b''
1 @ (34) head
1 @ (34) head
2 |
2 |
3 | o (33) head
3 | o (33) head
4 | |
4 | |
5 o | (32) expand
5 o | (32) expand
6 |\ \
6 |\ \
7 | o \ (31) expand
7 | o \ (31) expand
8 | |\ \
8 | |\ \
9 | | o \ (30) expand
9 | | o \ (30) expand
10 | | |\ \
10 | | |\ \
11 | | | o | (29) regular commit
11 | | | o | (29) regular commit
12 | | | | |
12 | | | | |
13 | | o | | (28) merge zero known
13 | | o | | (28) merge zero known
14 | | |\ \ \
14 | | |\ \ \
15 o | | | | | (27) collapse
15 o | | | | | (27) collapse
16 |/ / / / /
16 |/ / / / /
17 | | o---+ (26) merge one known; far right
17 | | o---+ (26) merge one known; far right
18 | | | | |
18 | | | | |
19 +---o | | (25) merge one known; far left
19 +---o | | (25) merge one known; far left
20 | | | | |
20 | | | | |
21 | | o | | (24) merge one known; immediate right
21 | | o | | (24) merge one known; immediate right
22 | | |\| |
22 | | |\| |
23 | | o | | (23) merge one known; immediate left
23 | | o | | (23) merge one known; immediate left
24 | |/| | |
24 | |/| | |
25 +---o---+ (22) merge two known; one far left, one far right
25 +---o---+ (22) merge two known; one far left, one far right
26 | | / /
26 | | / /
27 o | | | (21) expand
27 o | | | (21) expand
28 |\ \ \ \
28 |\ \ \ \
29 | o---+-+ (20) merge two known; two far right
29 | o---+-+ (20) merge two known; two far right
30 | / / /
30 | / / /
31 o | | | (19) expand
31 o | | | (19) expand
32 |\ \ \ \
32 |\ \ \ \
33 +---+---o (18) merge two known; two far left
33 +---+---o (18) merge two known; two far left
34 | | | |
34 | | | |
35 | o | | (17) expand
35 | o | | (17) expand
36 | |\ \ \
36 | |\ \ \
37 | | o---+ (16) merge two known; one immediate right, one near right
37 | | o---+ (16) merge two known; one immediate right, one near right
38 | | |/ /
38 | | |/ /
39 o | | | (15) expand
39 o | | | (15) expand
40 |\ \ \ \
40 |\ \ \ \
41 | o-----+ (14) merge two known; one immediate right, one far right
41 | o-----+ (14) merge two known; one immediate right, one far right
42 | |/ / /
42 | |/ / /
43 o | | | (13) expand
43 o | | | (13) expand
44 |\ \ \ \
44 |\ \ \ \
45 +---o | | (12) merge two known; one immediate right, one far left
45 +---o | | (12) merge two known; one immediate right, one far left
46 | | |/ /
46 | | |/ /
47 | o | | (11) expand
47 | o | | (11) expand
48 | |\ \ \
48 | |\ \ \
49 | | o---+ (10) merge two known; one immediate left, one near right
49 | | o---+ (10) merge two known; one immediate left, one near right
50 | |/ / /
50 | |/ / /
51 o | | | (9) expand
51 o | | | (9) expand
52 |\ \ \ \
52 |\ \ \ \
53 | o-----+ (8) merge two known; one immediate left, one far right
53 | o-----+ (8) merge two known; one immediate left, one far right
54 |/ / / /
54 |/ / / /
55 o | | | (7) expand
55 o | | | (7) expand
56 |\ \ \ \
56 |\ \ \ \
57 +---o | | (6) merge two known; one immediate left, one far left
57 +---o | | (6) merge two known; one immediate left, one far left
58 | |/ / /
58 | |/ / /
59 | o | | (5) expand
59 | o | | (5) expand
60 | |\ \ \
60 | |\ \ \
61 | | o | | (4) merge two known; one immediate left, one immediate right
61 | | o | | (4) merge two known; one immediate left, one immediate right
62 | |/|/ /
62 | |/|/ /
63 | o / / (3) collapse
63 | o / / (3) collapse
64 |/ / /
64 |/ / /
65 o / / (2) collapse
65 o / / (2) collapse
66 |/ /
66 |/ /
67 o / (1) collapse
67 o / (1) collapse
68 |/
68 |/
69 o (0) root
69 o (0) root
70
70
71
71
72 $ commit()
72 $ commit()
73 > {
73 > {
74 > rev=$1
74 > rev=$1
75 > msg=$2
75 > msg=$2
76 > shift 2
76 > shift 2
77 > if [ "$#" -gt 0 ]; then
77 > if [ "$#" -gt 0 ]; then
78 > hg debugsetparents "$@"
78 > hg debugsetparents "$@"
79 > fi
79 > fi
80 > echo $rev > a
80 > echo $rev > a
81 > hg commit -Aqd "$rev 0" -m "($rev) $msg"
81 > hg commit -Aqd "$rev 0" -m "($rev) $msg"
82 > }
82 > }
83
83
84 $ cat > printrevset.py <<EOF
84 $ cat > printrevset.py <<EOF
85 > from __future__ import absolute_import
85 > from __future__ import absolute_import
86 > from mercurial import (
86 > from mercurial import (
87 > cmdutil,
87 > cmdutil,
88 > commands,
88 > commands,
89 > extensions,
89 > extensions,
90 > logcmdutil,
90 > logcmdutil,
91 > revsetlang,
91 > revsetlang,
92 > smartset,
92 > smartset,
93 > )
93 > )
94 >
94 >
95 > def logrevset(repo, pats, opts):
95 > def logrevset(repo, pats, opts):
96 > revs = logcmdutil._logrevs(repo, opts)
96 > revs = logcmdutil._initialrevs(repo, opts)
97 > if not revs:
97 > if not revs:
98 > return None
98 > return None
99 > match, pats, slowpath = logcmdutil._makelogmatcher(repo, revs, pats, opts)
99 > match, pats, slowpath = logcmdutil._makematcher(repo, revs, pats, opts)
100 > return logcmdutil._makelogrevset(repo, match, pats, slowpath, opts)
100 > return logcmdutil._makerevset(repo, match, pats, slowpath, opts)
101 >
101 >
102 > def uisetup(ui):
102 > def uisetup(ui):
103 > def printrevset(orig, repo, pats, opts):
103 > def printrevset(orig, repo, pats, opts):
104 > revs, filematcher = orig(repo, pats, opts)
104 > revs, filematcher = orig(repo, pats, opts)
105 > if opts.get('print_revset'):
105 > if opts.get('print_revset'):
106 > expr = logrevset(repo, pats, opts)
106 > expr = logrevset(repo, pats, opts)
107 > if expr:
107 > if expr:
108 > tree = revsetlang.parse(expr)
108 > tree = revsetlang.parse(expr)
109 > tree = revsetlang.analyze(tree)
109 > tree = revsetlang.analyze(tree)
110 > else:
110 > else:
111 > tree = []
111 > tree = []
112 > ui = repo.ui
112 > ui = repo.ui
113 > ui.write('%r\n' % (opts.get('rev', []),))
113 > ui.write('%r\n' % (opts.get('rev', []),))
114 > ui.write(revsetlang.prettyformat(tree) + '\n')
114 > ui.write(revsetlang.prettyformat(tree) + '\n')
115 > ui.write(smartset.prettyformat(revs) + '\n')
115 > ui.write(smartset.prettyformat(revs) + '\n')
116 > revs = smartset.baseset() # display no revisions
116 > revs = smartset.baseset() # display no revisions
117 > return revs, filematcher
117 > return revs, filematcher
118 > extensions.wrapfunction(cmdutil, 'getlogrevs', printrevset)
118 > extensions.wrapfunction(cmdutil, 'getlogrevs', printrevset)
119 > aliases, entry = cmdutil.findcmd('log', commands.table)
119 > aliases, entry = cmdutil.findcmd('log', commands.table)
120 > entry[1].append(('', 'print-revset', False,
120 > entry[1].append(('', 'print-revset', False,
121 > 'print generated revset and exit (DEPRECATED)'))
121 > 'print generated revset and exit (DEPRECATED)'))
122 > EOF
122 > EOF
123
123
124 $ echo "[extensions]" >> $HGRCPATH
124 $ echo "[extensions]" >> $HGRCPATH
125 $ echo "printrevset=`pwd`/printrevset.py" >> $HGRCPATH
125 $ echo "printrevset=`pwd`/printrevset.py" >> $HGRCPATH
126
126
127 $ hg init repo
127 $ hg init repo
128 $ cd repo
128 $ cd repo
129
129
130 Empty repo:
130 Empty repo:
131
131
132 $ hg log -G
132 $ hg log -G
133
133
134
134
135 Building DAG:
135 Building DAG:
136
136
137 $ commit 0 "root"
137 $ commit 0 "root"
138 $ commit 1 "collapse" 0
138 $ commit 1 "collapse" 0
139 $ commit 2 "collapse" 1
139 $ commit 2 "collapse" 1
140 $ commit 3 "collapse" 2
140 $ commit 3 "collapse" 2
141 $ commit 4 "merge two known; one immediate left, one immediate right" 1 3
141 $ commit 4 "merge two known; one immediate left, one immediate right" 1 3
142 $ commit 5 "expand" 3 4
142 $ commit 5 "expand" 3 4
143 $ commit 6 "merge two known; one immediate left, one far left" 2 5
143 $ commit 6 "merge two known; one immediate left, one far left" 2 5
144 $ commit 7 "expand" 2 5
144 $ commit 7 "expand" 2 5
145 $ commit 8 "merge two known; one immediate left, one far right" 0 7
145 $ commit 8 "merge two known; one immediate left, one far right" 0 7
146 $ commit 9 "expand" 7 8
146 $ commit 9 "expand" 7 8
147 $ commit 10 "merge two known; one immediate left, one near right" 0 6
147 $ commit 10 "merge two known; one immediate left, one near right" 0 6
148 $ commit 11 "expand" 6 10
148 $ commit 11 "expand" 6 10
149 $ commit 12 "merge two known; one immediate right, one far left" 1 9
149 $ commit 12 "merge two known; one immediate right, one far left" 1 9
150 $ commit 13 "expand" 9 11
150 $ commit 13 "expand" 9 11
151 $ commit 14 "merge two known; one immediate right, one far right" 0 12
151 $ commit 14 "merge two known; one immediate right, one far right" 0 12
152 $ commit 15 "expand" 13 14
152 $ commit 15 "expand" 13 14
153 $ commit 16 "merge two known; one immediate right, one near right" 0 1
153 $ commit 16 "merge two known; one immediate right, one near right" 0 1
154 $ commit 17 "expand" 12 16
154 $ commit 17 "expand" 12 16
155 $ commit 18 "merge two known; two far left" 1 15
155 $ commit 18 "merge two known; two far left" 1 15
156 $ commit 19 "expand" 15 17
156 $ commit 19 "expand" 15 17
157 $ commit 20 "merge two known; two far right" 0 18
157 $ commit 20 "merge two known; two far right" 0 18
158 $ commit 21 "expand" 19 20
158 $ commit 21 "expand" 19 20
159 $ commit 22 "merge two known; one far left, one far right" 18 21
159 $ commit 22 "merge two known; one far left, one far right" 18 21
160 $ commit 23 "merge one known; immediate left" 1 22
160 $ commit 23 "merge one known; immediate left" 1 22
161 $ commit 24 "merge one known; immediate right" 0 23
161 $ commit 24 "merge one known; immediate right" 0 23
162 $ commit 25 "merge one known; far left" 21 24
162 $ commit 25 "merge one known; far left" 21 24
163 $ commit 26 "merge one known; far right" 18 25
163 $ commit 26 "merge one known; far right" 18 25
164 $ commit 27 "collapse" 21
164 $ commit 27 "collapse" 21
165 $ commit 28 "merge zero known" 1 26
165 $ commit 28 "merge zero known" 1 26
166 $ commit 29 "regular commit" 0
166 $ commit 29 "regular commit" 0
167 $ commit 30 "expand" 28 29
167 $ commit 30 "expand" 28 29
168 $ commit 31 "expand" 21 30
168 $ commit 31 "expand" 21 30
169 $ commit 32 "expand" 27 31
169 $ commit 32 "expand" 27 31
170 $ commit 33 "head" 18
170 $ commit 33 "head" 18
171 $ commit 34 "head" 32
171 $ commit 34 "head" 32
172
172
173
173
174 $ hg log -G -q
174 $ hg log -G -q
175 @ 34:fea3ac5810e0
175 @ 34:fea3ac5810e0
176 |
176 |
177 | o 33:68608f5145f9
177 | o 33:68608f5145f9
178 | |
178 | |
179 o | 32:d06dffa21a31
179 o | 32:d06dffa21a31
180 |\ \
180 |\ \
181 | o \ 31:621d83e11f67
181 | o \ 31:621d83e11f67
182 | |\ \
182 | |\ \
183 | | o \ 30:6e11cd4b648f
183 | | o \ 30:6e11cd4b648f
184 | | |\ \
184 | | |\ \
185 | | | o | 29:cd9bb2be7593
185 | | | o | 29:cd9bb2be7593
186 | | | | |
186 | | | | |
187 | | o | | 28:44ecd0b9ae99
187 | | o | | 28:44ecd0b9ae99
188 | | |\ \ \
188 | | |\ \ \
189 o | | | | | 27:886ed638191b
189 o | | | | | 27:886ed638191b
190 |/ / / / /
190 |/ / / / /
191 | | o---+ 26:7f25b6c2f0b9
191 | | o---+ 26:7f25b6c2f0b9
192 | | | | |
192 | | | | |
193 +---o | | 25:91da8ed57247
193 +---o | | 25:91da8ed57247
194 | | | | |
194 | | | | |
195 | | o | | 24:a9c19a3d96b7
195 | | o | | 24:a9c19a3d96b7
196 | | |\| |
196 | | |\| |
197 | | o | | 23:a01cddf0766d
197 | | o | | 23:a01cddf0766d
198 | |/| | |
198 | |/| | |
199 +---o---+ 22:e0d9cccacb5d
199 +---o---+ 22:e0d9cccacb5d
200 | | / /
200 | | / /
201 o | | | 21:d42a756af44d
201 o | | | 21:d42a756af44d
202 |\ \ \ \
202 |\ \ \ \
203 | o---+-+ 20:d30ed6450e32
203 | o---+-+ 20:d30ed6450e32
204 | / / /
204 | / / /
205 o | | | 19:31ddc2c1573b
205 o | | | 19:31ddc2c1573b
206 |\ \ \ \
206 |\ \ \ \
207 +---+---o 18:1aa84d96232a
207 +---+---o 18:1aa84d96232a
208 | | | |
208 | | | |
209 | o | | 17:44765d7c06e0
209 | o | | 17:44765d7c06e0
210 | |\ \ \
210 | |\ \ \
211 | | o---+ 16:3677d192927d
211 | | o---+ 16:3677d192927d
212 | | |/ /
212 | | |/ /
213 o | | | 15:1dda3f72782d
213 o | | | 15:1dda3f72782d
214 |\ \ \ \
214 |\ \ \ \
215 | o-----+ 14:8eac370358ef
215 | o-----+ 14:8eac370358ef
216 | |/ / /
216 | |/ / /
217 o | | | 13:22d8966a97e3
217 o | | | 13:22d8966a97e3
218 |\ \ \ \
218 |\ \ \ \
219 +---o | | 12:86b91144a6e9
219 +---o | | 12:86b91144a6e9
220 | | |/ /
220 | | |/ /
221 | o | | 11:832d76e6bdf2
221 | o | | 11:832d76e6bdf2
222 | |\ \ \
222 | |\ \ \
223 | | o---+ 10:74c64d036d72
223 | | o---+ 10:74c64d036d72
224 | |/ / /
224 | |/ / /
225 o | | | 9:7010c0af0a35
225 o | | | 9:7010c0af0a35
226 |\ \ \ \
226 |\ \ \ \
227 | o-----+ 8:7a0b11f71937
227 | o-----+ 8:7a0b11f71937
228 |/ / / /
228 |/ / / /
229 o | | | 7:b632bb1b1224
229 o | | | 7:b632bb1b1224
230 |\ \ \ \
230 |\ \ \ \
231 +---o | | 6:b105a072e251
231 +---o | | 6:b105a072e251
232 | |/ / /
232 | |/ / /
233 | o | | 5:4409d547b708
233 | o | | 5:4409d547b708
234 | |\ \ \
234 | |\ \ \
235 | | o | | 4:26a8bac39d9f
235 | | o | | 4:26a8bac39d9f
236 | |/|/ /
236 | |/|/ /
237 | o / / 3:27eef8ed80b4
237 | o / / 3:27eef8ed80b4
238 |/ / /
238 |/ / /
239 o / / 2:3d9a33b8d1e1
239 o / / 2:3d9a33b8d1e1
240 |/ /
240 |/ /
241 o / 1:6db2ef61d156
241 o / 1:6db2ef61d156
242 |/
242 |/
243 o 0:e6eb3150255d
243 o 0:e6eb3150255d
244
244
245
245
246 $ hg log -G
246 $ hg log -G
247 @ changeset: 34:fea3ac5810e0
247 @ changeset: 34:fea3ac5810e0
248 | tag: tip
248 | tag: tip
249 | parent: 32:d06dffa21a31
249 | parent: 32:d06dffa21a31
250 | user: test
250 | user: test
251 | date: Thu Jan 01 00:00:34 1970 +0000
251 | date: Thu Jan 01 00:00:34 1970 +0000
252 | summary: (34) head
252 | summary: (34) head
253 |
253 |
254 | o changeset: 33:68608f5145f9
254 | o changeset: 33:68608f5145f9
255 | | parent: 18:1aa84d96232a
255 | | parent: 18:1aa84d96232a
256 | | user: test
256 | | user: test
257 | | date: Thu Jan 01 00:00:33 1970 +0000
257 | | date: Thu Jan 01 00:00:33 1970 +0000
258 | | summary: (33) head
258 | | summary: (33) head
259 | |
259 | |
260 o | changeset: 32:d06dffa21a31
260 o | changeset: 32:d06dffa21a31
261 |\ \ parent: 27:886ed638191b
261 |\ \ parent: 27:886ed638191b
262 | | | parent: 31:621d83e11f67
262 | | | parent: 31:621d83e11f67
263 | | | user: test
263 | | | user: test
264 | | | date: Thu Jan 01 00:00:32 1970 +0000
264 | | | date: Thu Jan 01 00:00:32 1970 +0000
265 | | | summary: (32) expand
265 | | | summary: (32) expand
266 | | |
266 | | |
267 | o | changeset: 31:621d83e11f67
267 | o | changeset: 31:621d83e11f67
268 | |\ \ parent: 21:d42a756af44d
268 | |\ \ parent: 21:d42a756af44d
269 | | | | parent: 30:6e11cd4b648f
269 | | | | parent: 30:6e11cd4b648f
270 | | | | user: test
270 | | | | user: test
271 | | | | date: Thu Jan 01 00:00:31 1970 +0000
271 | | | | date: Thu Jan 01 00:00:31 1970 +0000
272 | | | | summary: (31) expand
272 | | | | summary: (31) expand
273 | | | |
273 | | | |
274 | | o | changeset: 30:6e11cd4b648f
274 | | o | changeset: 30:6e11cd4b648f
275 | | |\ \ parent: 28:44ecd0b9ae99
275 | | |\ \ parent: 28:44ecd0b9ae99
276 | | | | | parent: 29:cd9bb2be7593
276 | | | | | parent: 29:cd9bb2be7593
277 | | | | | user: test
277 | | | | | user: test
278 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
278 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
279 | | | | | summary: (30) expand
279 | | | | | summary: (30) expand
280 | | | | |
280 | | | | |
281 | | | o | changeset: 29:cd9bb2be7593
281 | | | o | changeset: 29:cd9bb2be7593
282 | | | | | parent: 0:e6eb3150255d
282 | | | | | parent: 0:e6eb3150255d
283 | | | | | user: test
283 | | | | | user: test
284 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
284 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
285 | | | | | summary: (29) regular commit
285 | | | | | summary: (29) regular commit
286 | | | | |
286 | | | | |
287 | | o | | changeset: 28:44ecd0b9ae99
287 | | o | | changeset: 28:44ecd0b9ae99
288 | | |\ \ \ parent: 1:6db2ef61d156
288 | | |\ \ \ parent: 1:6db2ef61d156
289 | | | | | | parent: 26:7f25b6c2f0b9
289 | | | | | | parent: 26:7f25b6c2f0b9
290 | | | | | | user: test
290 | | | | | | user: test
291 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
291 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
292 | | | | | | summary: (28) merge zero known
292 | | | | | | summary: (28) merge zero known
293 | | | | | |
293 | | | | | |
294 o | | | | | changeset: 27:886ed638191b
294 o | | | | | changeset: 27:886ed638191b
295 |/ / / / / parent: 21:d42a756af44d
295 |/ / / / / parent: 21:d42a756af44d
296 | | | | | user: test
296 | | | | | user: test
297 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
297 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
298 | | | | | summary: (27) collapse
298 | | | | | summary: (27) collapse
299 | | | | |
299 | | | | |
300 | | o---+ changeset: 26:7f25b6c2f0b9
300 | | o---+ changeset: 26:7f25b6c2f0b9
301 | | | | | parent: 18:1aa84d96232a
301 | | | | | parent: 18:1aa84d96232a
302 | | | | | parent: 25:91da8ed57247
302 | | | | | parent: 25:91da8ed57247
303 | | | | | user: test
303 | | | | | user: test
304 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
304 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
305 | | | | | summary: (26) merge one known; far right
305 | | | | | summary: (26) merge one known; far right
306 | | | | |
306 | | | | |
307 +---o | | changeset: 25:91da8ed57247
307 +---o | | changeset: 25:91da8ed57247
308 | | | | | parent: 21:d42a756af44d
308 | | | | | parent: 21:d42a756af44d
309 | | | | | parent: 24:a9c19a3d96b7
309 | | | | | parent: 24:a9c19a3d96b7
310 | | | | | user: test
310 | | | | | user: test
311 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
311 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
312 | | | | | summary: (25) merge one known; far left
312 | | | | | summary: (25) merge one known; far left
313 | | | | |
313 | | | | |
314 | | o | | changeset: 24:a9c19a3d96b7
314 | | o | | changeset: 24:a9c19a3d96b7
315 | | |\| | parent: 0:e6eb3150255d
315 | | |\| | parent: 0:e6eb3150255d
316 | | | | | parent: 23:a01cddf0766d
316 | | | | | parent: 23:a01cddf0766d
317 | | | | | user: test
317 | | | | | user: test
318 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
318 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
319 | | | | | summary: (24) merge one known; immediate right
319 | | | | | summary: (24) merge one known; immediate right
320 | | | | |
320 | | | | |
321 | | o | | changeset: 23:a01cddf0766d
321 | | o | | changeset: 23:a01cddf0766d
322 | |/| | | parent: 1:6db2ef61d156
322 | |/| | | parent: 1:6db2ef61d156
323 | | | | | parent: 22:e0d9cccacb5d
323 | | | | | parent: 22:e0d9cccacb5d
324 | | | | | user: test
324 | | | | | user: test
325 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
325 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
326 | | | | | summary: (23) merge one known; immediate left
326 | | | | | summary: (23) merge one known; immediate left
327 | | | | |
327 | | | | |
328 +---o---+ changeset: 22:e0d9cccacb5d
328 +---o---+ changeset: 22:e0d9cccacb5d
329 | | | | parent: 18:1aa84d96232a
329 | | | | parent: 18:1aa84d96232a
330 | | / / parent: 21:d42a756af44d
330 | | / / parent: 21:d42a756af44d
331 | | | | user: test
331 | | | | user: test
332 | | | | date: Thu Jan 01 00:00:22 1970 +0000
332 | | | | date: Thu Jan 01 00:00:22 1970 +0000
333 | | | | summary: (22) merge two known; one far left, one far right
333 | | | | summary: (22) merge two known; one far left, one far right
334 | | | |
334 | | | |
335 o | | | changeset: 21:d42a756af44d
335 o | | | changeset: 21:d42a756af44d
336 |\ \ \ \ parent: 19:31ddc2c1573b
336 |\ \ \ \ parent: 19:31ddc2c1573b
337 | | | | | parent: 20:d30ed6450e32
337 | | | | | parent: 20:d30ed6450e32
338 | | | | | user: test
338 | | | | | user: test
339 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
339 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
340 | | | | | summary: (21) expand
340 | | | | | summary: (21) expand
341 | | | | |
341 | | | | |
342 | o---+-+ changeset: 20:d30ed6450e32
342 | o---+-+ changeset: 20:d30ed6450e32
343 | | | | parent: 0:e6eb3150255d
343 | | | | parent: 0:e6eb3150255d
344 | / / / parent: 18:1aa84d96232a
344 | / / / parent: 18:1aa84d96232a
345 | | | | user: test
345 | | | | user: test
346 | | | | date: Thu Jan 01 00:00:20 1970 +0000
346 | | | | date: Thu Jan 01 00:00:20 1970 +0000
347 | | | | summary: (20) merge two known; two far right
347 | | | | summary: (20) merge two known; two far right
348 | | | |
348 | | | |
349 o | | | changeset: 19:31ddc2c1573b
349 o | | | changeset: 19:31ddc2c1573b
350 |\ \ \ \ parent: 15:1dda3f72782d
350 |\ \ \ \ parent: 15:1dda3f72782d
351 | | | | | parent: 17:44765d7c06e0
351 | | | | | parent: 17:44765d7c06e0
352 | | | | | user: test
352 | | | | | user: test
353 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
353 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
354 | | | | | summary: (19) expand
354 | | | | | summary: (19) expand
355 | | | | |
355 | | | | |
356 +---+---o changeset: 18:1aa84d96232a
356 +---+---o changeset: 18:1aa84d96232a
357 | | | | parent: 1:6db2ef61d156
357 | | | | parent: 1:6db2ef61d156
358 | | | | parent: 15:1dda3f72782d
358 | | | | parent: 15:1dda3f72782d
359 | | | | user: test
359 | | | | user: test
360 | | | | date: Thu Jan 01 00:00:18 1970 +0000
360 | | | | date: Thu Jan 01 00:00:18 1970 +0000
361 | | | | summary: (18) merge two known; two far left
361 | | | | summary: (18) merge two known; two far left
362 | | | |
362 | | | |
363 | o | | changeset: 17:44765d7c06e0
363 | o | | changeset: 17:44765d7c06e0
364 | |\ \ \ parent: 12:86b91144a6e9
364 | |\ \ \ parent: 12:86b91144a6e9
365 | | | | | parent: 16:3677d192927d
365 | | | | | parent: 16:3677d192927d
366 | | | | | user: test
366 | | | | | user: test
367 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
367 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
368 | | | | | summary: (17) expand
368 | | | | | summary: (17) expand
369 | | | | |
369 | | | | |
370 | | o---+ changeset: 16:3677d192927d
370 | | o---+ changeset: 16:3677d192927d
371 | | | | | parent: 0:e6eb3150255d
371 | | | | | parent: 0:e6eb3150255d
372 | | |/ / parent: 1:6db2ef61d156
372 | | |/ / parent: 1:6db2ef61d156
373 | | | | user: test
373 | | | | user: test
374 | | | | date: Thu Jan 01 00:00:16 1970 +0000
374 | | | | date: Thu Jan 01 00:00:16 1970 +0000
375 | | | | summary: (16) merge two known; one immediate right, one near right
375 | | | | summary: (16) merge two known; one immediate right, one near right
376 | | | |
376 | | | |
377 o | | | changeset: 15:1dda3f72782d
377 o | | | changeset: 15:1dda3f72782d
378 |\ \ \ \ parent: 13:22d8966a97e3
378 |\ \ \ \ parent: 13:22d8966a97e3
379 | | | | | parent: 14:8eac370358ef
379 | | | | | parent: 14:8eac370358ef
380 | | | | | user: test
380 | | | | | user: test
381 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
381 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
382 | | | | | summary: (15) expand
382 | | | | | summary: (15) expand
383 | | | | |
383 | | | | |
384 | o-----+ changeset: 14:8eac370358ef
384 | o-----+ changeset: 14:8eac370358ef
385 | | | | | parent: 0:e6eb3150255d
385 | | | | | parent: 0:e6eb3150255d
386 | |/ / / parent: 12:86b91144a6e9
386 | |/ / / parent: 12:86b91144a6e9
387 | | | | user: test
387 | | | | user: test
388 | | | | date: Thu Jan 01 00:00:14 1970 +0000
388 | | | | date: Thu Jan 01 00:00:14 1970 +0000
389 | | | | summary: (14) merge two known; one immediate right, one far right
389 | | | | summary: (14) merge two known; one immediate right, one far right
390 | | | |
390 | | | |
391 o | | | changeset: 13:22d8966a97e3
391 o | | | changeset: 13:22d8966a97e3
392 |\ \ \ \ parent: 9:7010c0af0a35
392 |\ \ \ \ parent: 9:7010c0af0a35
393 | | | | | parent: 11:832d76e6bdf2
393 | | | | | parent: 11:832d76e6bdf2
394 | | | | | user: test
394 | | | | | user: test
395 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
395 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
396 | | | | | summary: (13) expand
396 | | | | | summary: (13) expand
397 | | | | |
397 | | | | |
398 +---o | | changeset: 12:86b91144a6e9
398 +---o | | changeset: 12:86b91144a6e9
399 | | |/ / parent: 1:6db2ef61d156
399 | | |/ / parent: 1:6db2ef61d156
400 | | | | parent: 9:7010c0af0a35
400 | | | | parent: 9:7010c0af0a35
401 | | | | user: test
401 | | | | user: test
402 | | | | date: Thu Jan 01 00:00:12 1970 +0000
402 | | | | date: Thu Jan 01 00:00:12 1970 +0000
403 | | | | summary: (12) merge two known; one immediate right, one far left
403 | | | | summary: (12) merge two known; one immediate right, one far left
404 | | | |
404 | | | |
405 | o | | changeset: 11:832d76e6bdf2
405 | o | | changeset: 11:832d76e6bdf2
406 | |\ \ \ parent: 6:b105a072e251
406 | |\ \ \ parent: 6:b105a072e251
407 | | | | | parent: 10:74c64d036d72
407 | | | | | parent: 10:74c64d036d72
408 | | | | | user: test
408 | | | | | user: test
409 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
409 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
410 | | | | | summary: (11) expand
410 | | | | | summary: (11) expand
411 | | | | |
411 | | | | |
412 | | o---+ changeset: 10:74c64d036d72
412 | | o---+ changeset: 10:74c64d036d72
413 | | | | | parent: 0:e6eb3150255d
413 | | | | | parent: 0:e6eb3150255d
414 | |/ / / parent: 6:b105a072e251
414 | |/ / / parent: 6:b105a072e251
415 | | | | user: test
415 | | | | user: test
416 | | | | date: Thu Jan 01 00:00:10 1970 +0000
416 | | | | date: Thu Jan 01 00:00:10 1970 +0000
417 | | | | summary: (10) merge two known; one immediate left, one near right
417 | | | | summary: (10) merge two known; one immediate left, one near right
418 | | | |
418 | | | |
419 o | | | changeset: 9:7010c0af0a35
419 o | | | changeset: 9:7010c0af0a35
420 |\ \ \ \ parent: 7:b632bb1b1224
420 |\ \ \ \ parent: 7:b632bb1b1224
421 | | | | | parent: 8:7a0b11f71937
421 | | | | | parent: 8:7a0b11f71937
422 | | | | | user: test
422 | | | | | user: test
423 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
423 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
424 | | | | | summary: (9) expand
424 | | | | | summary: (9) expand
425 | | | | |
425 | | | | |
426 | o-----+ changeset: 8:7a0b11f71937
426 | o-----+ changeset: 8:7a0b11f71937
427 | | | | | parent: 0:e6eb3150255d
427 | | | | | parent: 0:e6eb3150255d
428 |/ / / / parent: 7:b632bb1b1224
428 |/ / / / parent: 7:b632bb1b1224
429 | | | | user: test
429 | | | | user: test
430 | | | | date: Thu Jan 01 00:00:08 1970 +0000
430 | | | | date: Thu Jan 01 00:00:08 1970 +0000
431 | | | | summary: (8) merge two known; one immediate left, one far right
431 | | | | summary: (8) merge two known; one immediate left, one far right
432 | | | |
432 | | | |
433 o | | | changeset: 7:b632bb1b1224
433 o | | | changeset: 7:b632bb1b1224
434 |\ \ \ \ parent: 2:3d9a33b8d1e1
434 |\ \ \ \ parent: 2:3d9a33b8d1e1
435 | | | | | parent: 5:4409d547b708
435 | | | | | parent: 5:4409d547b708
436 | | | | | user: test
436 | | | | | user: test
437 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
437 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
438 | | | | | summary: (7) expand
438 | | | | | summary: (7) expand
439 | | | | |
439 | | | | |
440 +---o | | changeset: 6:b105a072e251
440 +---o | | changeset: 6:b105a072e251
441 | |/ / / parent: 2:3d9a33b8d1e1
441 | |/ / / parent: 2:3d9a33b8d1e1
442 | | | | parent: 5:4409d547b708
442 | | | | parent: 5:4409d547b708
443 | | | | user: test
443 | | | | user: test
444 | | | | date: Thu Jan 01 00:00:06 1970 +0000
444 | | | | date: Thu Jan 01 00:00:06 1970 +0000
445 | | | | summary: (6) merge two known; one immediate left, one far left
445 | | | | summary: (6) merge two known; one immediate left, one far left
446 | | | |
446 | | | |
447 | o | | changeset: 5:4409d547b708
447 | o | | changeset: 5:4409d547b708
448 | |\ \ \ parent: 3:27eef8ed80b4
448 | |\ \ \ parent: 3:27eef8ed80b4
449 | | | | | parent: 4:26a8bac39d9f
449 | | | | | parent: 4:26a8bac39d9f
450 | | | | | user: test
450 | | | | | user: test
451 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
451 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
452 | | | | | summary: (5) expand
452 | | | | | summary: (5) expand
453 | | | | |
453 | | | | |
454 | | o | | changeset: 4:26a8bac39d9f
454 | | o | | changeset: 4:26a8bac39d9f
455 | |/|/ / parent: 1:6db2ef61d156
455 | |/|/ / parent: 1:6db2ef61d156
456 | | | | parent: 3:27eef8ed80b4
456 | | | | parent: 3:27eef8ed80b4
457 | | | | user: test
457 | | | | user: test
458 | | | | date: Thu Jan 01 00:00:04 1970 +0000
458 | | | | date: Thu Jan 01 00:00:04 1970 +0000
459 | | | | summary: (4) merge two known; one immediate left, one immediate right
459 | | | | summary: (4) merge two known; one immediate left, one immediate right
460 | | | |
460 | | | |
461 | o | | changeset: 3:27eef8ed80b4
461 | o | | changeset: 3:27eef8ed80b4
462 |/ / / user: test
462 |/ / / user: test
463 | | | date: Thu Jan 01 00:00:03 1970 +0000
463 | | | date: Thu Jan 01 00:00:03 1970 +0000
464 | | | summary: (3) collapse
464 | | | summary: (3) collapse
465 | | |
465 | | |
466 o | | changeset: 2:3d9a33b8d1e1
466 o | | changeset: 2:3d9a33b8d1e1
467 |/ / user: test
467 |/ / user: test
468 | | date: Thu Jan 01 00:00:02 1970 +0000
468 | | date: Thu Jan 01 00:00:02 1970 +0000
469 | | summary: (2) collapse
469 | | summary: (2) collapse
470 | |
470 | |
471 o | changeset: 1:6db2ef61d156
471 o | changeset: 1:6db2ef61d156
472 |/ user: test
472 |/ user: test
473 | date: Thu Jan 01 00:00:01 1970 +0000
473 | date: Thu Jan 01 00:00:01 1970 +0000
474 | summary: (1) collapse
474 | summary: (1) collapse
475 |
475 |
476 o changeset: 0:e6eb3150255d
476 o changeset: 0:e6eb3150255d
477 user: test
477 user: test
478 date: Thu Jan 01 00:00:00 1970 +0000
478 date: Thu Jan 01 00:00:00 1970 +0000
479 summary: (0) root
479 summary: (0) root
480
480
481
481
482 File glog:
482 File glog:
483 $ hg log -G a
483 $ hg log -G a
484 @ changeset: 34:fea3ac5810e0
484 @ changeset: 34:fea3ac5810e0
485 | tag: tip
485 | tag: tip
486 | parent: 32:d06dffa21a31
486 | parent: 32:d06dffa21a31
487 | user: test
487 | user: test
488 | date: Thu Jan 01 00:00:34 1970 +0000
488 | date: Thu Jan 01 00:00:34 1970 +0000
489 | summary: (34) head
489 | summary: (34) head
490 |
490 |
491 | o changeset: 33:68608f5145f9
491 | o changeset: 33:68608f5145f9
492 | | parent: 18:1aa84d96232a
492 | | parent: 18:1aa84d96232a
493 | | user: test
493 | | user: test
494 | | date: Thu Jan 01 00:00:33 1970 +0000
494 | | date: Thu Jan 01 00:00:33 1970 +0000
495 | | summary: (33) head
495 | | summary: (33) head
496 | |
496 | |
497 o | changeset: 32:d06dffa21a31
497 o | changeset: 32:d06dffa21a31
498 |\ \ parent: 27:886ed638191b
498 |\ \ parent: 27:886ed638191b
499 | | | parent: 31:621d83e11f67
499 | | | parent: 31:621d83e11f67
500 | | | user: test
500 | | | user: test
501 | | | date: Thu Jan 01 00:00:32 1970 +0000
501 | | | date: Thu Jan 01 00:00:32 1970 +0000
502 | | | summary: (32) expand
502 | | | summary: (32) expand
503 | | |
503 | | |
504 | o | changeset: 31:621d83e11f67
504 | o | changeset: 31:621d83e11f67
505 | |\ \ parent: 21:d42a756af44d
505 | |\ \ parent: 21:d42a756af44d
506 | | | | parent: 30:6e11cd4b648f
506 | | | | parent: 30:6e11cd4b648f
507 | | | | user: test
507 | | | | user: test
508 | | | | date: Thu Jan 01 00:00:31 1970 +0000
508 | | | | date: Thu Jan 01 00:00:31 1970 +0000
509 | | | | summary: (31) expand
509 | | | | summary: (31) expand
510 | | | |
510 | | | |
511 | | o | changeset: 30:6e11cd4b648f
511 | | o | changeset: 30:6e11cd4b648f
512 | | |\ \ parent: 28:44ecd0b9ae99
512 | | |\ \ parent: 28:44ecd0b9ae99
513 | | | | | parent: 29:cd9bb2be7593
513 | | | | | parent: 29:cd9bb2be7593
514 | | | | | user: test
514 | | | | | user: test
515 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
515 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
516 | | | | | summary: (30) expand
516 | | | | | summary: (30) expand
517 | | | | |
517 | | | | |
518 | | | o | changeset: 29:cd9bb2be7593
518 | | | o | changeset: 29:cd9bb2be7593
519 | | | | | parent: 0:e6eb3150255d
519 | | | | | parent: 0:e6eb3150255d
520 | | | | | user: test
520 | | | | | user: test
521 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
521 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
522 | | | | | summary: (29) regular commit
522 | | | | | summary: (29) regular commit
523 | | | | |
523 | | | | |
524 | | o | | changeset: 28:44ecd0b9ae99
524 | | o | | changeset: 28:44ecd0b9ae99
525 | | |\ \ \ parent: 1:6db2ef61d156
525 | | |\ \ \ parent: 1:6db2ef61d156
526 | | | | | | parent: 26:7f25b6c2f0b9
526 | | | | | | parent: 26:7f25b6c2f0b9
527 | | | | | | user: test
527 | | | | | | user: test
528 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
528 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
529 | | | | | | summary: (28) merge zero known
529 | | | | | | summary: (28) merge zero known
530 | | | | | |
530 | | | | | |
531 o | | | | | changeset: 27:886ed638191b
531 o | | | | | changeset: 27:886ed638191b
532 |/ / / / / parent: 21:d42a756af44d
532 |/ / / / / parent: 21:d42a756af44d
533 | | | | | user: test
533 | | | | | user: test
534 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
534 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
535 | | | | | summary: (27) collapse
535 | | | | | summary: (27) collapse
536 | | | | |
536 | | | | |
537 | | o---+ changeset: 26:7f25b6c2f0b9
537 | | o---+ changeset: 26:7f25b6c2f0b9
538 | | | | | parent: 18:1aa84d96232a
538 | | | | | parent: 18:1aa84d96232a
539 | | | | | parent: 25:91da8ed57247
539 | | | | | parent: 25:91da8ed57247
540 | | | | | user: test
540 | | | | | user: test
541 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
541 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
542 | | | | | summary: (26) merge one known; far right
542 | | | | | summary: (26) merge one known; far right
543 | | | | |
543 | | | | |
544 +---o | | changeset: 25:91da8ed57247
544 +---o | | changeset: 25:91da8ed57247
545 | | | | | parent: 21:d42a756af44d
545 | | | | | parent: 21:d42a756af44d
546 | | | | | parent: 24:a9c19a3d96b7
546 | | | | | parent: 24:a9c19a3d96b7
547 | | | | | user: test
547 | | | | | user: test
548 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
548 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
549 | | | | | summary: (25) merge one known; far left
549 | | | | | summary: (25) merge one known; far left
550 | | | | |
550 | | | | |
551 | | o | | changeset: 24:a9c19a3d96b7
551 | | o | | changeset: 24:a9c19a3d96b7
552 | | |\| | parent: 0:e6eb3150255d
552 | | |\| | parent: 0:e6eb3150255d
553 | | | | | parent: 23:a01cddf0766d
553 | | | | | parent: 23:a01cddf0766d
554 | | | | | user: test
554 | | | | | user: test
555 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
555 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
556 | | | | | summary: (24) merge one known; immediate right
556 | | | | | summary: (24) merge one known; immediate right
557 | | | | |
557 | | | | |
558 | | o | | changeset: 23:a01cddf0766d
558 | | o | | changeset: 23:a01cddf0766d
559 | |/| | | parent: 1:6db2ef61d156
559 | |/| | | parent: 1:6db2ef61d156
560 | | | | | parent: 22:e0d9cccacb5d
560 | | | | | parent: 22:e0d9cccacb5d
561 | | | | | user: test
561 | | | | | user: test
562 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
562 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
563 | | | | | summary: (23) merge one known; immediate left
563 | | | | | summary: (23) merge one known; immediate left
564 | | | | |
564 | | | | |
565 +---o---+ changeset: 22:e0d9cccacb5d
565 +---o---+ changeset: 22:e0d9cccacb5d
566 | | | | parent: 18:1aa84d96232a
566 | | | | parent: 18:1aa84d96232a
567 | | / / parent: 21:d42a756af44d
567 | | / / parent: 21:d42a756af44d
568 | | | | user: test
568 | | | | user: test
569 | | | | date: Thu Jan 01 00:00:22 1970 +0000
569 | | | | date: Thu Jan 01 00:00:22 1970 +0000
570 | | | | summary: (22) merge two known; one far left, one far right
570 | | | | summary: (22) merge two known; one far left, one far right
571 | | | |
571 | | | |
572 o | | | changeset: 21:d42a756af44d
572 o | | | changeset: 21:d42a756af44d
573 |\ \ \ \ parent: 19:31ddc2c1573b
573 |\ \ \ \ parent: 19:31ddc2c1573b
574 | | | | | parent: 20:d30ed6450e32
574 | | | | | parent: 20:d30ed6450e32
575 | | | | | user: test
575 | | | | | user: test
576 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
576 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
577 | | | | | summary: (21) expand
577 | | | | | summary: (21) expand
578 | | | | |
578 | | | | |
579 | o---+-+ changeset: 20:d30ed6450e32
579 | o---+-+ changeset: 20:d30ed6450e32
580 | | | | parent: 0:e6eb3150255d
580 | | | | parent: 0:e6eb3150255d
581 | / / / parent: 18:1aa84d96232a
581 | / / / parent: 18:1aa84d96232a
582 | | | | user: test
582 | | | | user: test
583 | | | | date: Thu Jan 01 00:00:20 1970 +0000
583 | | | | date: Thu Jan 01 00:00:20 1970 +0000
584 | | | | summary: (20) merge two known; two far right
584 | | | | summary: (20) merge two known; two far right
585 | | | |
585 | | | |
586 o | | | changeset: 19:31ddc2c1573b
586 o | | | changeset: 19:31ddc2c1573b
587 |\ \ \ \ parent: 15:1dda3f72782d
587 |\ \ \ \ parent: 15:1dda3f72782d
588 | | | | | parent: 17:44765d7c06e0
588 | | | | | parent: 17:44765d7c06e0
589 | | | | | user: test
589 | | | | | user: test
590 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
590 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
591 | | | | | summary: (19) expand
591 | | | | | summary: (19) expand
592 | | | | |
592 | | | | |
593 +---+---o changeset: 18:1aa84d96232a
593 +---+---o changeset: 18:1aa84d96232a
594 | | | | parent: 1:6db2ef61d156
594 | | | | parent: 1:6db2ef61d156
595 | | | | parent: 15:1dda3f72782d
595 | | | | parent: 15:1dda3f72782d
596 | | | | user: test
596 | | | | user: test
597 | | | | date: Thu Jan 01 00:00:18 1970 +0000
597 | | | | date: Thu Jan 01 00:00:18 1970 +0000
598 | | | | summary: (18) merge two known; two far left
598 | | | | summary: (18) merge two known; two far left
599 | | | |
599 | | | |
600 | o | | changeset: 17:44765d7c06e0
600 | o | | changeset: 17:44765d7c06e0
601 | |\ \ \ parent: 12:86b91144a6e9
601 | |\ \ \ parent: 12:86b91144a6e9
602 | | | | | parent: 16:3677d192927d
602 | | | | | parent: 16:3677d192927d
603 | | | | | user: test
603 | | | | | user: test
604 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
604 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
605 | | | | | summary: (17) expand
605 | | | | | summary: (17) expand
606 | | | | |
606 | | | | |
607 | | o---+ changeset: 16:3677d192927d
607 | | o---+ changeset: 16:3677d192927d
608 | | | | | parent: 0:e6eb3150255d
608 | | | | | parent: 0:e6eb3150255d
609 | | |/ / parent: 1:6db2ef61d156
609 | | |/ / parent: 1:6db2ef61d156
610 | | | | user: test
610 | | | | user: test
611 | | | | date: Thu Jan 01 00:00:16 1970 +0000
611 | | | | date: Thu Jan 01 00:00:16 1970 +0000
612 | | | | summary: (16) merge two known; one immediate right, one near right
612 | | | | summary: (16) merge two known; one immediate right, one near right
613 | | | |
613 | | | |
614 o | | | changeset: 15:1dda3f72782d
614 o | | | changeset: 15:1dda3f72782d
615 |\ \ \ \ parent: 13:22d8966a97e3
615 |\ \ \ \ parent: 13:22d8966a97e3
616 | | | | | parent: 14:8eac370358ef
616 | | | | | parent: 14:8eac370358ef
617 | | | | | user: test
617 | | | | | user: test
618 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
618 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
619 | | | | | summary: (15) expand
619 | | | | | summary: (15) expand
620 | | | | |
620 | | | | |
621 | o-----+ changeset: 14:8eac370358ef
621 | o-----+ changeset: 14:8eac370358ef
622 | | | | | parent: 0:e6eb3150255d
622 | | | | | parent: 0:e6eb3150255d
623 | |/ / / parent: 12:86b91144a6e9
623 | |/ / / parent: 12:86b91144a6e9
624 | | | | user: test
624 | | | | user: test
625 | | | | date: Thu Jan 01 00:00:14 1970 +0000
625 | | | | date: Thu Jan 01 00:00:14 1970 +0000
626 | | | | summary: (14) merge two known; one immediate right, one far right
626 | | | | summary: (14) merge two known; one immediate right, one far right
627 | | | |
627 | | | |
628 o | | | changeset: 13:22d8966a97e3
628 o | | | changeset: 13:22d8966a97e3
629 |\ \ \ \ parent: 9:7010c0af0a35
629 |\ \ \ \ parent: 9:7010c0af0a35
630 | | | | | parent: 11:832d76e6bdf2
630 | | | | | parent: 11:832d76e6bdf2
631 | | | | | user: test
631 | | | | | user: test
632 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
632 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
633 | | | | | summary: (13) expand
633 | | | | | summary: (13) expand
634 | | | | |
634 | | | | |
635 +---o | | changeset: 12:86b91144a6e9
635 +---o | | changeset: 12:86b91144a6e9
636 | | |/ / parent: 1:6db2ef61d156
636 | | |/ / parent: 1:6db2ef61d156
637 | | | | parent: 9:7010c0af0a35
637 | | | | parent: 9:7010c0af0a35
638 | | | | user: test
638 | | | | user: test
639 | | | | date: Thu Jan 01 00:00:12 1970 +0000
639 | | | | date: Thu Jan 01 00:00:12 1970 +0000
640 | | | | summary: (12) merge two known; one immediate right, one far left
640 | | | | summary: (12) merge two known; one immediate right, one far left
641 | | | |
641 | | | |
642 | o | | changeset: 11:832d76e6bdf2
642 | o | | changeset: 11:832d76e6bdf2
643 | |\ \ \ parent: 6:b105a072e251
643 | |\ \ \ parent: 6:b105a072e251
644 | | | | | parent: 10:74c64d036d72
644 | | | | | parent: 10:74c64d036d72
645 | | | | | user: test
645 | | | | | user: test
646 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
646 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
647 | | | | | summary: (11) expand
647 | | | | | summary: (11) expand
648 | | | | |
648 | | | | |
649 | | o---+ changeset: 10:74c64d036d72
649 | | o---+ changeset: 10:74c64d036d72
650 | | | | | parent: 0:e6eb3150255d
650 | | | | | parent: 0:e6eb3150255d
651 | |/ / / parent: 6:b105a072e251
651 | |/ / / parent: 6:b105a072e251
652 | | | | user: test
652 | | | | user: test
653 | | | | date: Thu Jan 01 00:00:10 1970 +0000
653 | | | | date: Thu Jan 01 00:00:10 1970 +0000
654 | | | | summary: (10) merge two known; one immediate left, one near right
654 | | | | summary: (10) merge two known; one immediate left, one near right
655 | | | |
655 | | | |
656 o | | | changeset: 9:7010c0af0a35
656 o | | | changeset: 9:7010c0af0a35
657 |\ \ \ \ parent: 7:b632bb1b1224
657 |\ \ \ \ parent: 7:b632bb1b1224
658 | | | | | parent: 8:7a0b11f71937
658 | | | | | parent: 8:7a0b11f71937
659 | | | | | user: test
659 | | | | | user: test
660 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
660 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
661 | | | | | summary: (9) expand
661 | | | | | summary: (9) expand
662 | | | | |
662 | | | | |
663 | o-----+ changeset: 8:7a0b11f71937
663 | o-----+ changeset: 8:7a0b11f71937
664 | | | | | parent: 0:e6eb3150255d
664 | | | | | parent: 0:e6eb3150255d
665 |/ / / / parent: 7:b632bb1b1224
665 |/ / / / parent: 7:b632bb1b1224
666 | | | | user: test
666 | | | | user: test
667 | | | | date: Thu Jan 01 00:00:08 1970 +0000
667 | | | | date: Thu Jan 01 00:00:08 1970 +0000
668 | | | | summary: (8) merge two known; one immediate left, one far right
668 | | | | summary: (8) merge two known; one immediate left, one far right
669 | | | |
669 | | | |
670 o | | | changeset: 7:b632bb1b1224
670 o | | | changeset: 7:b632bb1b1224
671 |\ \ \ \ parent: 2:3d9a33b8d1e1
671 |\ \ \ \ parent: 2:3d9a33b8d1e1
672 | | | | | parent: 5:4409d547b708
672 | | | | | parent: 5:4409d547b708
673 | | | | | user: test
673 | | | | | user: test
674 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
674 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
675 | | | | | summary: (7) expand
675 | | | | | summary: (7) expand
676 | | | | |
676 | | | | |
677 +---o | | changeset: 6:b105a072e251
677 +---o | | changeset: 6:b105a072e251
678 | |/ / / parent: 2:3d9a33b8d1e1
678 | |/ / / parent: 2:3d9a33b8d1e1
679 | | | | parent: 5:4409d547b708
679 | | | | parent: 5:4409d547b708
680 | | | | user: test
680 | | | | user: test
681 | | | | date: Thu Jan 01 00:00:06 1970 +0000
681 | | | | date: Thu Jan 01 00:00:06 1970 +0000
682 | | | | summary: (6) merge two known; one immediate left, one far left
682 | | | | summary: (6) merge two known; one immediate left, one far left
683 | | | |
683 | | | |
684 | o | | changeset: 5:4409d547b708
684 | o | | changeset: 5:4409d547b708
685 | |\ \ \ parent: 3:27eef8ed80b4
685 | |\ \ \ parent: 3:27eef8ed80b4
686 | | | | | parent: 4:26a8bac39d9f
686 | | | | | parent: 4:26a8bac39d9f
687 | | | | | user: test
687 | | | | | user: test
688 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
688 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
689 | | | | | summary: (5) expand
689 | | | | | summary: (5) expand
690 | | | | |
690 | | | | |
691 | | o | | changeset: 4:26a8bac39d9f
691 | | o | | changeset: 4:26a8bac39d9f
692 | |/|/ / parent: 1:6db2ef61d156
692 | |/|/ / parent: 1:6db2ef61d156
693 | | | | parent: 3:27eef8ed80b4
693 | | | | parent: 3:27eef8ed80b4
694 | | | | user: test
694 | | | | user: test
695 | | | | date: Thu Jan 01 00:00:04 1970 +0000
695 | | | | date: Thu Jan 01 00:00:04 1970 +0000
696 | | | | summary: (4) merge two known; one immediate left, one immediate right
696 | | | | summary: (4) merge two known; one immediate left, one immediate right
697 | | | |
697 | | | |
698 | o | | changeset: 3:27eef8ed80b4
698 | o | | changeset: 3:27eef8ed80b4
699 |/ / / user: test
699 |/ / / user: test
700 | | | date: Thu Jan 01 00:00:03 1970 +0000
700 | | | date: Thu Jan 01 00:00:03 1970 +0000
701 | | | summary: (3) collapse
701 | | | summary: (3) collapse
702 | | |
702 | | |
703 o | | changeset: 2:3d9a33b8d1e1
703 o | | changeset: 2:3d9a33b8d1e1
704 |/ / user: test
704 |/ / user: test
705 | | date: Thu Jan 01 00:00:02 1970 +0000
705 | | date: Thu Jan 01 00:00:02 1970 +0000
706 | | summary: (2) collapse
706 | | summary: (2) collapse
707 | |
707 | |
708 o | changeset: 1:6db2ef61d156
708 o | changeset: 1:6db2ef61d156
709 |/ user: test
709 |/ user: test
710 | date: Thu Jan 01 00:00:01 1970 +0000
710 | date: Thu Jan 01 00:00:01 1970 +0000
711 | summary: (1) collapse
711 | summary: (1) collapse
712 |
712 |
713 o changeset: 0:e6eb3150255d
713 o changeset: 0:e6eb3150255d
714 user: test
714 user: test
715 date: Thu Jan 01 00:00:00 1970 +0000
715 date: Thu Jan 01 00:00:00 1970 +0000
716 summary: (0) root
716 summary: (0) root
717
717
718
718
719 File glog per revset:
719 File glog per revset:
720
720
721 $ hg log -G -r 'file("a")'
721 $ hg log -G -r 'file("a")'
722 @ changeset: 34:fea3ac5810e0
722 @ changeset: 34:fea3ac5810e0
723 | tag: tip
723 | tag: tip
724 | parent: 32:d06dffa21a31
724 | parent: 32:d06dffa21a31
725 | user: test
725 | user: test
726 | date: Thu Jan 01 00:00:34 1970 +0000
726 | date: Thu Jan 01 00:00:34 1970 +0000
727 | summary: (34) head
727 | summary: (34) head
728 |
728 |
729 | o changeset: 33:68608f5145f9
729 | o changeset: 33:68608f5145f9
730 | | parent: 18:1aa84d96232a
730 | | parent: 18:1aa84d96232a
731 | | user: test
731 | | user: test
732 | | date: Thu Jan 01 00:00:33 1970 +0000
732 | | date: Thu Jan 01 00:00:33 1970 +0000
733 | | summary: (33) head
733 | | summary: (33) head
734 | |
734 | |
735 o | changeset: 32:d06dffa21a31
735 o | changeset: 32:d06dffa21a31
736 |\ \ parent: 27:886ed638191b
736 |\ \ parent: 27:886ed638191b
737 | | | parent: 31:621d83e11f67
737 | | | parent: 31:621d83e11f67
738 | | | user: test
738 | | | user: test
739 | | | date: Thu Jan 01 00:00:32 1970 +0000
739 | | | date: Thu Jan 01 00:00:32 1970 +0000
740 | | | summary: (32) expand
740 | | | summary: (32) expand
741 | | |
741 | | |
742 | o | changeset: 31:621d83e11f67
742 | o | changeset: 31:621d83e11f67
743 | |\ \ parent: 21:d42a756af44d
743 | |\ \ parent: 21:d42a756af44d
744 | | | | parent: 30:6e11cd4b648f
744 | | | | parent: 30:6e11cd4b648f
745 | | | | user: test
745 | | | | user: test
746 | | | | date: Thu Jan 01 00:00:31 1970 +0000
746 | | | | date: Thu Jan 01 00:00:31 1970 +0000
747 | | | | summary: (31) expand
747 | | | | summary: (31) expand
748 | | | |
748 | | | |
749 | | o | changeset: 30:6e11cd4b648f
749 | | o | changeset: 30:6e11cd4b648f
750 | | |\ \ parent: 28:44ecd0b9ae99
750 | | |\ \ parent: 28:44ecd0b9ae99
751 | | | | | parent: 29:cd9bb2be7593
751 | | | | | parent: 29:cd9bb2be7593
752 | | | | | user: test
752 | | | | | user: test
753 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
753 | | | | | date: Thu Jan 01 00:00:30 1970 +0000
754 | | | | | summary: (30) expand
754 | | | | | summary: (30) expand
755 | | | | |
755 | | | | |
756 | | | o | changeset: 29:cd9bb2be7593
756 | | | o | changeset: 29:cd9bb2be7593
757 | | | | | parent: 0:e6eb3150255d
757 | | | | | parent: 0:e6eb3150255d
758 | | | | | user: test
758 | | | | | user: test
759 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
759 | | | | | date: Thu Jan 01 00:00:29 1970 +0000
760 | | | | | summary: (29) regular commit
760 | | | | | summary: (29) regular commit
761 | | | | |
761 | | | | |
762 | | o | | changeset: 28:44ecd0b9ae99
762 | | o | | changeset: 28:44ecd0b9ae99
763 | | |\ \ \ parent: 1:6db2ef61d156
763 | | |\ \ \ parent: 1:6db2ef61d156
764 | | | | | | parent: 26:7f25b6c2f0b9
764 | | | | | | parent: 26:7f25b6c2f0b9
765 | | | | | | user: test
765 | | | | | | user: test
766 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
766 | | | | | | date: Thu Jan 01 00:00:28 1970 +0000
767 | | | | | | summary: (28) merge zero known
767 | | | | | | summary: (28) merge zero known
768 | | | | | |
768 | | | | | |
769 o | | | | | changeset: 27:886ed638191b
769 o | | | | | changeset: 27:886ed638191b
770 |/ / / / / parent: 21:d42a756af44d
770 |/ / / / / parent: 21:d42a756af44d
771 | | | | | user: test
771 | | | | | user: test
772 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
772 | | | | | date: Thu Jan 01 00:00:27 1970 +0000
773 | | | | | summary: (27) collapse
773 | | | | | summary: (27) collapse
774 | | | | |
774 | | | | |
775 | | o---+ changeset: 26:7f25b6c2f0b9
775 | | o---+ changeset: 26:7f25b6c2f0b9
776 | | | | | parent: 18:1aa84d96232a
776 | | | | | parent: 18:1aa84d96232a
777 | | | | | parent: 25:91da8ed57247
777 | | | | | parent: 25:91da8ed57247
778 | | | | | user: test
778 | | | | | user: test
779 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
779 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
780 | | | | | summary: (26) merge one known; far right
780 | | | | | summary: (26) merge one known; far right
781 | | | | |
781 | | | | |
782 +---o | | changeset: 25:91da8ed57247
782 +---o | | changeset: 25:91da8ed57247
783 | | | | | parent: 21:d42a756af44d
783 | | | | | parent: 21:d42a756af44d
784 | | | | | parent: 24:a9c19a3d96b7
784 | | | | | parent: 24:a9c19a3d96b7
785 | | | | | user: test
785 | | | | | user: test
786 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
786 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
787 | | | | | summary: (25) merge one known; far left
787 | | | | | summary: (25) merge one known; far left
788 | | | | |
788 | | | | |
789 | | o | | changeset: 24:a9c19a3d96b7
789 | | o | | changeset: 24:a9c19a3d96b7
790 | | |\| | parent: 0:e6eb3150255d
790 | | |\| | parent: 0:e6eb3150255d
791 | | | | | parent: 23:a01cddf0766d
791 | | | | | parent: 23:a01cddf0766d
792 | | | | | user: test
792 | | | | | user: test
793 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
793 | | | | | date: Thu Jan 01 00:00:24 1970 +0000
794 | | | | | summary: (24) merge one known; immediate right
794 | | | | | summary: (24) merge one known; immediate right
795 | | | | |
795 | | | | |
796 | | o | | changeset: 23:a01cddf0766d
796 | | o | | changeset: 23:a01cddf0766d
797 | |/| | | parent: 1:6db2ef61d156
797 | |/| | | parent: 1:6db2ef61d156
798 | | | | | parent: 22:e0d9cccacb5d
798 | | | | | parent: 22:e0d9cccacb5d
799 | | | | | user: test
799 | | | | | user: test
800 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
800 | | | | | date: Thu Jan 01 00:00:23 1970 +0000
801 | | | | | summary: (23) merge one known; immediate left
801 | | | | | summary: (23) merge one known; immediate left
802 | | | | |
802 | | | | |
803 +---o---+ changeset: 22:e0d9cccacb5d
803 +---o---+ changeset: 22:e0d9cccacb5d
804 | | | | parent: 18:1aa84d96232a
804 | | | | parent: 18:1aa84d96232a
805 | | / / parent: 21:d42a756af44d
805 | | / / parent: 21:d42a756af44d
806 | | | | user: test
806 | | | | user: test
807 | | | | date: Thu Jan 01 00:00:22 1970 +0000
807 | | | | date: Thu Jan 01 00:00:22 1970 +0000
808 | | | | summary: (22) merge two known; one far left, one far right
808 | | | | summary: (22) merge two known; one far left, one far right
809 | | | |
809 | | | |
810 o | | | changeset: 21:d42a756af44d
810 o | | | changeset: 21:d42a756af44d
811 |\ \ \ \ parent: 19:31ddc2c1573b
811 |\ \ \ \ parent: 19:31ddc2c1573b
812 | | | | | parent: 20:d30ed6450e32
812 | | | | | parent: 20:d30ed6450e32
813 | | | | | user: test
813 | | | | | user: test
814 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
814 | | | | | date: Thu Jan 01 00:00:21 1970 +0000
815 | | | | | summary: (21) expand
815 | | | | | summary: (21) expand
816 | | | | |
816 | | | | |
817 | o---+-+ changeset: 20:d30ed6450e32
817 | o---+-+ changeset: 20:d30ed6450e32
818 | | | | parent: 0:e6eb3150255d
818 | | | | parent: 0:e6eb3150255d
819 | / / / parent: 18:1aa84d96232a
819 | / / / parent: 18:1aa84d96232a
820 | | | | user: test
820 | | | | user: test
821 | | | | date: Thu Jan 01 00:00:20 1970 +0000
821 | | | | date: Thu Jan 01 00:00:20 1970 +0000
822 | | | | summary: (20) merge two known; two far right
822 | | | | summary: (20) merge two known; two far right
823 | | | |
823 | | | |
824 o | | | changeset: 19:31ddc2c1573b
824 o | | | changeset: 19:31ddc2c1573b
825 |\ \ \ \ parent: 15:1dda3f72782d
825 |\ \ \ \ parent: 15:1dda3f72782d
826 | | | | | parent: 17:44765d7c06e0
826 | | | | | parent: 17:44765d7c06e0
827 | | | | | user: test
827 | | | | | user: test
828 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
828 | | | | | date: Thu Jan 01 00:00:19 1970 +0000
829 | | | | | summary: (19) expand
829 | | | | | summary: (19) expand
830 | | | | |
830 | | | | |
831 +---+---o changeset: 18:1aa84d96232a
831 +---+---o changeset: 18:1aa84d96232a
832 | | | | parent: 1:6db2ef61d156
832 | | | | parent: 1:6db2ef61d156
833 | | | | parent: 15:1dda3f72782d
833 | | | | parent: 15:1dda3f72782d
834 | | | | user: test
834 | | | | user: test
835 | | | | date: Thu Jan 01 00:00:18 1970 +0000
835 | | | | date: Thu Jan 01 00:00:18 1970 +0000
836 | | | | summary: (18) merge two known; two far left
836 | | | | summary: (18) merge two known; two far left
837 | | | |
837 | | | |
838 | o | | changeset: 17:44765d7c06e0
838 | o | | changeset: 17:44765d7c06e0
839 | |\ \ \ parent: 12:86b91144a6e9
839 | |\ \ \ parent: 12:86b91144a6e9
840 | | | | | parent: 16:3677d192927d
840 | | | | | parent: 16:3677d192927d
841 | | | | | user: test
841 | | | | | user: test
842 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
842 | | | | | date: Thu Jan 01 00:00:17 1970 +0000
843 | | | | | summary: (17) expand
843 | | | | | summary: (17) expand
844 | | | | |
844 | | | | |
845 | | o---+ changeset: 16:3677d192927d
845 | | o---+ changeset: 16:3677d192927d
846 | | | | | parent: 0:e6eb3150255d
846 | | | | | parent: 0:e6eb3150255d
847 | | |/ / parent: 1:6db2ef61d156
847 | | |/ / parent: 1:6db2ef61d156
848 | | | | user: test
848 | | | | user: test
849 | | | | date: Thu Jan 01 00:00:16 1970 +0000
849 | | | | date: Thu Jan 01 00:00:16 1970 +0000
850 | | | | summary: (16) merge two known; one immediate right, one near right
850 | | | | summary: (16) merge two known; one immediate right, one near right
851 | | | |
851 | | | |
852 o | | | changeset: 15:1dda3f72782d
852 o | | | changeset: 15:1dda3f72782d
853 |\ \ \ \ parent: 13:22d8966a97e3
853 |\ \ \ \ parent: 13:22d8966a97e3
854 | | | | | parent: 14:8eac370358ef
854 | | | | | parent: 14:8eac370358ef
855 | | | | | user: test
855 | | | | | user: test
856 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
856 | | | | | date: Thu Jan 01 00:00:15 1970 +0000
857 | | | | | summary: (15) expand
857 | | | | | summary: (15) expand
858 | | | | |
858 | | | | |
859 | o-----+ changeset: 14:8eac370358ef
859 | o-----+ changeset: 14:8eac370358ef
860 | | | | | parent: 0:e6eb3150255d
860 | | | | | parent: 0:e6eb3150255d
861 | |/ / / parent: 12:86b91144a6e9
861 | |/ / / parent: 12:86b91144a6e9
862 | | | | user: test
862 | | | | user: test
863 | | | | date: Thu Jan 01 00:00:14 1970 +0000
863 | | | | date: Thu Jan 01 00:00:14 1970 +0000
864 | | | | summary: (14) merge two known; one immediate right, one far right
864 | | | | summary: (14) merge two known; one immediate right, one far right
865 | | | |
865 | | | |
866 o | | | changeset: 13:22d8966a97e3
866 o | | | changeset: 13:22d8966a97e3
867 |\ \ \ \ parent: 9:7010c0af0a35
867 |\ \ \ \ parent: 9:7010c0af0a35
868 | | | | | parent: 11:832d76e6bdf2
868 | | | | | parent: 11:832d76e6bdf2
869 | | | | | user: test
869 | | | | | user: test
870 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
870 | | | | | date: Thu Jan 01 00:00:13 1970 +0000
871 | | | | | summary: (13) expand
871 | | | | | summary: (13) expand
872 | | | | |
872 | | | | |
873 +---o | | changeset: 12:86b91144a6e9
873 +---o | | changeset: 12:86b91144a6e9
874 | | |/ / parent: 1:6db2ef61d156
874 | | |/ / parent: 1:6db2ef61d156
875 | | | | parent: 9:7010c0af0a35
875 | | | | parent: 9:7010c0af0a35
876 | | | | user: test
876 | | | | user: test
877 | | | | date: Thu Jan 01 00:00:12 1970 +0000
877 | | | | date: Thu Jan 01 00:00:12 1970 +0000
878 | | | | summary: (12) merge two known; one immediate right, one far left
878 | | | | summary: (12) merge two known; one immediate right, one far left
879 | | | |
879 | | | |
880 | o | | changeset: 11:832d76e6bdf2
880 | o | | changeset: 11:832d76e6bdf2
881 | |\ \ \ parent: 6:b105a072e251
881 | |\ \ \ parent: 6:b105a072e251
882 | | | | | parent: 10:74c64d036d72
882 | | | | | parent: 10:74c64d036d72
883 | | | | | user: test
883 | | | | | user: test
884 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
884 | | | | | date: Thu Jan 01 00:00:11 1970 +0000
885 | | | | | summary: (11) expand
885 | | | | | summary: (11) expand
886 | | | | |
886 | | | | |
887 | | o---+ changeset: 10:74c64d036d72
887 | | o---+ changeset: 10:74c64d036d72
888 | | | | | parent: 0:e6eb3150255d
888 | | | | | parent: 0:e6eb3150255d
889 | |/ / / parent: 6:b105a072e251
889 | |/ / / parent: 6:b105a072e251
890 | | | | user: test
890 | | | | user: test
891 | | | | date: Thu Jan 01 00:00:10 1970 +0000
891 | | | | date: Thu Jan 01 00:00:10 1970 +0000
892 | | | | summary: (10) merge two known; one immediate left, one near right
892 | | | | summary: (10) merge two known; one immediate left, one near right
893 | | | |
893 | | | |
894 o | | | changeset: 9:7010c0af0a35
894 o | | | changeset: 9:7010c0af0a35
895 |\ \ \ \ parent: 7:b632bb1b1224
895 |\ \ \ \ parent: 7:b632bb1b1224
896 | | | | | parent: 8:7a0b11f71937
896 | | | | | parent: 8:7a0b11f71937
897 | | | | | user: test
897 | | | | | user: test
898 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
898 | | | | | date: Thu Jan 01 00:00:09 1970 +0000
899 | | | | | summary: (9) expand
899 | | | | | summary: (9) expand
900 | | | | |
900 | | | | |
901 | o-----+ changeset: 8:7a0b11f71937
901 | o-----+ changeset: 8:7a0b11f71937
902 | | | | | parent: 0:e6eb3150255d
902 | | | | | parent: 0:e6eb3150255d
903 |/ / / / parent: 7:b632bb1b1224
903 |/ / / / parent: 7:b632bb1b1224
904 | | | | user: test
904 | | | | user: test
905 | | | | date: Thu Jan 01 00:00:08 1970 +0000
905 | | | | date: Thu Jan 01 00:00:08 1970 +0000
906 | | | | summary: (8) merge two known; one immediate left, one far right
906 | | | | summary: (8) merge two known; one immediate left, one far right
907 | | | |
907 | | | |
908 o | | | changeset: 7:b632bb1b1224
908 o | | | changeset: 7:b632bb1b1224
909 |\ \ \ \ parent: 2:3d9a33b8d1e1
909 |\ \ \ \ parent: 2:3d9a33b8d1e1
910 | | | | | parent: 5:4409d547b708
910 | | | | | parent: 5:4409d547b708
911 | | | | | user: test
911 | | | | | user: test
912 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
912 | | | | | date: Thu Jan 01 00:00:07 1970 +0000
913 | | | | | summary: (7) expand
913 | | | | | summary: (7) expand
914 | | | | |
914 | | | | |
915 +---o | | changeset: 6:b105a072e251
915 +---o | | changeset: 6:b105a072e251
916 | |/ / / parent: 2:3d9a33b8d1e1
916 | |/ / / parent: 2:3d9a33b8d1e1
917 | | | | parent: 5:4409d547b708
917 | | | | parent: 5:4409d547b708
918 | | | | user: test
918 | | | | user: test
919 | | | | date: Thu Jan 01 00:00:06 1970 +0000
919 | | | | date: Thu Jan 01 00:00:06 1970 +0000
920 | | | | summary: (6) merge two known; one immediate left, one far left
920 | | | | summary: (6) merge two known; one immediate left, one far left
921 | | | |
921 | | | |
922 | o | | changeset: 5:4409d547b708
922 | o | | changeset: 5:4409d547b708
923 | |\ \ \ parent: 3:27eef8ed80b4
923 | |\ \ \ parent: 3:27eef8ed80b4
924 | | | | | parent: 4:26a8bac39d9f
924 | | | | | parent: 4:26a8bac39d9f
925 | | | | | user: test
925 | | | | | user: test
926 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
926 | | | | | date: Thu Jan 01 00:00:05 1970 +0000
927 | | | | | summary: (5) expand
927 | | | | | summary: (5) expand
928 | | | | |
928 | | | | |
929 | | o | | changeset: 4:26a8bac39d9f
929 | | o | | changeset: 4:26a8bac39d9f
930 | |/|/ / parent: 1:6db2ef61d156
930 | |/|/ / parent: 1:6db2ef61d156
931 | | | | parent: 3:27eef8ed80b4
931 | | | | parent: 3:27eef8ed80b4
932 | | | | user: test
932 | | | | user: test
933 | | | | date: Thu Jan 01 00:00:04 1970 +0000
933 | | | | date: Thu Jan 01 00:00:04 1970 +0000
934 | | | | summary: (4) merge two known; one immediate left, one immediate right
934 | | | | summary: (4) merge two known; one immediate left, one immediate right
935 | | | |
935 | | | |
936 | o | | changeset: 3:27eef8ed80b4
936 | o | | changeset: 3:27eef8ed80b4
937 |/ / / user: test
937 |/ / / user: test
938 | | | date: Thu Jan 01 00:00:03 1970 +0000
938 | | | date: Thu Jan 01 00:00:03 1970 +0000
939 | | | summary: (3) collapse
939 | | | summary: (3) collapse
940 | | |
940 | | |
941 o | | changeset: 2:3d9a33b8d1e1
941 o | | changeset: 2:3d9a33b8d1e1
942 |/ / user: test
942 |/ / user: test
943 | | date: Thu Jan 01 00:00:02 1970 +0000
943 | | date: Thu Jan 01 00:00:02 1970 +0000
944 | | summary: (2) collapse
944 | | summary: (2) collapse
945 | |
945 | |
946 o | changeset: 1:6db2ef61d156
946 o | changeset: 1:6db2ef61d156
947 |/ user: test
947 |/ user: test
948 | date: Thu Jan 01 00:00:01 1970 +0000
948 | date: Thu Jan 01 00:00:01 1970 +0000
949 | summary: (1) collapse
949 | summary: (1) collapse
950 |
950 |
951 o changeset: 0:e6eb3150255d
951 o changeset: 0:e6eb3150255d
952 user: test
952 user: test
953 date: Thu Jan 01 00:00:00 1970 +0000
953 date: Thu Jan 01 00:00:00 1970 +0000
954 summary: (0) root
954 summary: (0) root
955
955
956
956
957
957
958 File glog per revset (only merges):
958 File glog per revset (only merges):
959
959
960 $ hg log -G -r 'file("a")' -m
960 $ hg log -G -r 'file("a")' -m
961 o changeset: 32:d06dffa21a31
961 o changeset: 32:d06dffa21a31
962 |\ parent: 27:886ed638191b
962 |\ parent: 27:886ed638191b
963 | : parent: 31:621d83e11f67
963 | : parent: 31:621d83e11f67
964 | : user: test
964 | : user: test
965 | : date: Thu Jan 01 00:00:32 1970 +0000
965 | : date: Thu Jan 01 00:00:32 1970 +0000
966 | : summary: (32) expand
966 | : summary: (32) expand
967 | :
967 | :
968 o : changeset: 31:621d83e11f67
968 o : changeset: 31:621d83e11f67
969 |\: parent: 21:d42a756af44d
969 |\: parent: 21:d42a756af44d
970 | : parent: 30:6e11cd4b648f
970 | : parent: 30:6e11cd4b648f
971 | : user: test
971 | : user: test
972 | : date: Thu Jan 01 00:00:31 1970 +0000
972 | : date: Thu Jan 01 00:00:31 1970 +0000
973 | : summary: (31) expand
973 | : summary: (31) expand
974 | :
974 | :
975 o : changeset: 30:6e11cd4b648f
975 o : changeset: 30:6e11cd4b648f
976 |\ \ parent: 28:44ecd0b9ae99
976 |\ \ parent: 28:44ecd0b9ae99
977 | ~ : parent: 29:cd9bb2be7593
977 | ~ : parent: 29:cd9bb2be7593
978 | : user: test
978 | : user: test
979 | : date: Thu Jan 01 00:00:30 1970 +0000
979 | : date: Thu Jan 01 00:00:30 1970 +0000
980 | : summary: (30) expand
980 | : summary: (30) expand
981 | /
981 | /
982 o : changeset: 28:44ecd0b9ae99
982 o : changeset: 28:44ecd0b9ae99
983 |\ \ parent: 1:6db2ef61d156
983 |\ \ parent: 1:6db2ef61d156
984 | ~ : parent: 26:7f25b6c2f0b9
984 | ~ : parent: 26:7f25b6c2f0b9
985 | : user: test
985 | : user: test
986 | : date: Thu Jan 01 00:00:28 1970 +0000
986 | : date: Thu Jan 01 00:00:28 1970 +0000
987 | : summary: (28) merge zero known
987 | : summary: (28) merge zero known
988 | /
988 | /
989 o : changeset: 26:7f25b6c2f0b9
989 o : changeset: 26:7f25b6c2f0b9
990 |\ \ parent: 18:1aa84d96232a
990 |\ \ parent: 18:1aa84d96232a
991 | | : parent: 25:91da8ed57247
991 | | : parent: 25:91da8ed57247
992 | | : user: test
992 | | : user: test
993 | | : date: Thu Jan 01 00:00:26 1970 +0000
993 | | : date: Thu Jan 01 00:00:26 1970 +0000
994 | | : summary: (26) merge one known; far right
994 | | : summary: (26) merge one known; far right
995 | | :
995 | | :
996 | o : changeset: 25:91da8ed57247
996 | o : changeset: 25:91da8ed57247
997 | |\: parent: 21:d42a756af44d
997 | |\: parent: 21:d42a756af44d
998 | | : parent: 24:a9c19a3d96b7
998 | | : parent: 24:a9c19a3d96b7
999 | | : user: test
999 | | : user: test
1000 | | : date: Thu Jan 01 00:00:25 1970 +0000
1000 | | : date: Thu Jan 01 00:00:25 1970 +0000
1001 | | : summary: (25) merge one known; far left
1001 | | : summary: (25) merge one known; far left
1002 | | :
1002 | | :
1003 | o : changeset: 24:a9c19a3d96b7
1003 | o : changeset: 24:a9c19a3d96b7
1004 | |\ \ parent: 0:e6eb3150255d
1004 | |\ \ parent: 0:e6eb3150255d
1005 | | ~ : parent: 23:a01cddf0766d
1005 | | ~ : parent: 23:a01cddf0766d
1006 | | : user: test
1006 | | : user: test
1007 | | : date: Thu Jan 01 00:00:24 1970 +0000
1007 | | : date: Thu Jan 01 00:00:24 1970 +0000
1008 | | : summary: (24) merge one known; immediate right
1008 | | : summary: (24) merge one known; immediate right
1009 | | /
1009 | | /
1010 | o : changeset: 23:a01cddf0766d
1010 | o : changeset: 23:a01cddf0766d
1011 | |\ \ parent: 1:6db2ef61d156
1011 | |\ \ parent: 1:6db2ef61d156
1012 | | ~ : parent: 22:e0d9cccacb5d
1012 | | ~ : parent: 22:e0d9cccacb5d
1013 | | : user: test
1013 | | : user: test
1014 | | : date: Thu Jan 01 00:00:23 1970 +0000
1014 | | : date: Thu Jan 01 00:00:23 1970 +0000
1015 | | : summary: (23) merge one known; immediate left
1015 | | : summary: (23) merge one known; immediate left
1016 | | /
1016 | | /
1017 | o : changeset: 22:e0d9cccacb5d
1017 | o : changeset: 22:e0d9cccacb5d
1018 |/:/ parent: 18:1aa84d96232a
1018 |/:/ parent: 18:1aa84d96232a
1019 | : parent: 21:d42a756af44d
1019 | : parent: 21:d42a756af44d
1020 | : user: test
1020 | : user: test
1021 | : date: Thu Jan 01 00:00:22 1970 +0000
1021 | : date: Thu Jan 01 00:00:22 1970 +0000
1022 | : summary: (22) merge two known; one far left, one far right
1022 | : summary: (22) merge two known; one far left, one far right
1023 | :
1023 | :
1024 | o changeset: 21:d42a756af44d
1024 | o changeset: 21:d42a756af44d
1025 | |\ parent: 19:31ddc2c1573b
1025 | |\ parent: 19:31ddc2c1573b
1026 | | | parent: 20:d30ed6450e32
1026 | | | parent: 20:d30ed6450e32
1027 | | | user: test
1027 | | | user: test
1028 | | | date: Thu Jan 01 00:00:21 1970 +0000
1028 | | | date: Thu Jan 01 00:00:21 1970 +0000
1029 | | | summary: (21) expand
1029 | | | summary: (21) expand
1030 | | |
1030 | | |
1031 +---o changeset: 20:d30ed6450e32
1031 +---o changeset: 20:d30ed6450e32
1032 | | | parent: 0:e6eb3150255d
1032 | | | parent: 0:e6eb3150255d
1033 | | ~ parent: 18:1aa84d96232a
1033 | | ~ parent: 18:1aa84d96232a
1034 | | user: test
1034 | | user: test
1035 | | date: Thu Jan 01 00:00:20 1970 +0000
1035 | | date: Thu Jan 01 00:00:20 1970 +0000
1036 | | summary: (20) merge two known; two far right
1036 | | summary: (20) merge two known; two far right
1037 | |
1037 | |
1038 | o changeset: 19:31ddc2c1573b
1038 | o changeset: 19:31ddc2c1573b
1039 | |\ parent: 15:1dda3f72782d
1039 | |\ parent: 15:1dda3f72782d
1040 | | | parent: 17:44765d7c06e0
1040 | | | parent: 17:44765d7c06e0
1041 | | | user: test
1041 | | | user: test
1042 | | | date: Thu Jan 01 00:00:19 1970 +0000
1042 | | | date: Thu Jan 01 00:00:19 1970 +0000
1043 | | | summary: (19) expand
1043 | | | summary: (19) expand
1044 | | |
1044 | | |
1045 o | | changeset: 18:1aa84d96232a
1045 o | | changeset: 18:1aa84d96232a
1046 |\| | parent: 1:6db2ef61d156
1046 |\| | parent: 1:6db2ef61d156
1047 ~ | | parent: 15:1dda3f72782d
1047 ~ | | parent: 15:1dda3f72782d
1048 | | user: test
1048 | | user: test
1049 | | date: Thu Jan 01 00:00:18 1970 +0000
1049 | | date: Thu Jan 01 00:00:18 1970 +0000
1050 | | summary: (18) merge two known; two far left
1050 | | summary: (18) merge two known; two far left
1051 / /
1051 / /
1052 | o changeset: 17:44765d7c06e0
1052 | o changeset: 17:44765d7c06e0
1053 | |\ parent: 12:86b91144a6e9
1053 | |\ parent: 12:86b91144a6e9
1054 | | | parent: 16:3677d192927d
1054 | | | parent: 16:3677d192927d
1055 | | | user: test
1055 | | | user: test
1056 | | | date: Thu Jan 01 00:00:17 1970 +0000
1056 | | | date: Thu Jan 01 00:00:17 1970 +0000
1057 | | | summary: (17) expand
1057 | | | summary: (17) expand
1058 | | |
1058 | | |
1059 | | o changeset: 16:3677d192927d
1059 | | o changeset: 16:3677d192927d
1060 | | |\ parent: 0:e6eb3150255d
1060 | | |\ parent: 0:e6eb3150255d
1061 | | ~ ~ parent: 1:6db2ef61d156
1061 | | ~ ~ parent: 1:6db2ef61d156
1062 | | user: test
1062 | | user: test
1063 | | date: Thu Jan 01 00:00:16 1970 +0000
1063 | | date: Thu Jan 01 00:00:16 1970 +0000
1064 | | summary: (16) merge two known; one immediate right, one near right
1064 | | summary: (16) merge two known; one immediate right, one near right
1065 | |
1065 | |
1066 o | changeset: 15:1dda3f72782d
1066 o | changeset: 15:1dda3f72782d
1067 |\ \ parent: 13:22d8966a97e3
1067 |\ \ parent: 13:22d8966a97e3
1068 | | | parent: 14:8eac370358ef
1068 | | | parent: 14:8eac370358ef
1069 | | | user: test
1069 | | | user: test
1070 | | | date: Thu Jan 01 00:00:15 1970 +0000
1070 | | | date: Thu Jan 01 00:00:15 1970 +0000
1071 | | | summary: (15) expand
1071 | | | summary: (15) expand
1072 | | |
1072 | | |
1073 | o | changeset: 14:8eac370358ef
1073 | o | changeset: 14:8eac370358ef
1074 | |\| parent: 0:e6eb3150255d
1074 | |\| parent: 0:e6eb3150255d
1075 | ~ | parent: 12:86b91144a6e9
1075 | ~ | parent: 12:86b91144a6e9
1076 | | user: test
1076 | | user: test
1077 | | date: Thu Jan 01 00:00:14 1970 +0000
1077 | | date: Thu Jan 01 00:00:14 1970 +0000
1078 | | summary: (14) merge two known; one immediate right, one far right
1078 | | summary: (14) merge two known; one immediate right, one far right
1079 | /
1079 | /
1080 o | changeset: 13:22d8966a97e3
1080 o | changeset: 13:22d8966a97e3
1081 |\ \ parent: 9:7010c0af0a35
1081 |\ \ parent: 9:7010c0af0a35
1082 | | | parent: 11:832d76e6bdf2
1082 | | | parent: 11:832d76e6bdf2
1083 | | | user: test
1083 | | | user: test
1084 | | | date: Thu Jan 01 00:00:13 1970 +0000
1084 | | | date: Thu Jan 01 00:00:13 1970 +0000
1085 | | | summary: (13) expand
1085 | | | summary: (13) expand
1086 | | |
1086 | | |
1087 +---o changeset: 12:86b91144a6e9
1087 +---o changeset: 12:86b91144a6e9
1088 | | | parent: 1:6db2ef61d156
1088 | | | parent: 1:6db2ef61d156
1089 | | ~ parent: 9:7010c0af0a35
1089 | | ~ parent: 9:7010c0af0a35
1090 | | user: test
1090 | | user: test
1091 | | date: Thu Jan 01 00:00:12 1970 +0000
1091 | | date: Thu Jan 01 00:00:12 1970 +0000
1092 | | summary: (12) merge two known; one immediate right, one far left
1092 | | summary: (12) merge two known; one immediate right, one far left
1093 | |
1093 | |
1094 | o changeset: 11:832d76e6bdf2
1094 | o changeset: 11:832d76e6bdf2
1095 | |\ parent: 6:b105a072e251
1095 | |\ parent: 6:b105a072e251
1096 | | | parent: 10:74c64d036d72
1096 | | | parent: 10:74c64d036d72
1097 | | | user: test
1097 | | | user: test
1098 | | | date: Thu Jan 01 00:00:11 1970 +0000
1098 | | | date: Thu Jan 01 00:00:11 1970 +0000
1099 | | | summary: (11) expand
1099 | | | summary: (11) expand
1100 | | |
1100 | | |
1101 | | o changeset: 10:74c64d036d72
1101 | | o changeset: 10:74c64d036d72
1102 | |/| parent: 0:e6eb3150255d
1102 | |/| parent: 0:e6eb3150255d
1103 | | ~ parent: 6:b105a072e251
1103 | | ~ parent: 6:b105a072e251
1104 | | user: test
1104 | | user: test
1105 | | date: Thu Jan 01 00:00:10 1970 +0000
1105 | | date: Thu Jan 01 00:00:10 1970 +0000
1106 | | summary: (10) merge two known; one immediate left, one near right
1106 | | summary: (10) merge two known; one immediate left, one near right
1107 | |
1107 | |
1108 o | changeset: 9:7010c0af0a35
1108 o | changeset: 9:7010c0af0a35
1109 |\ \ parent: 7:b632bb1b1224
1109 |\ \ parent: 7:b632bb1b1224
1110 | | | parent: 8:7a0b11f71937
1110 | | | parent: 8:7a0b11f71937
1111 | | | user: test
1111 | | | user: test
1112 | | | date: Thu Jan 01 00:00:09 1970 +0000
1112 | | | date: Thu Jan 01 00:00:09 1970 +0000
1113 | | | summary: (9) expand
1113 | | | summary: (9) expand
1114 | | |
1114 | | |
1115 | o | changeset: 8:7a0b11f71937
1115 | o | changeset: 8:7a0b11f71937
1116 |/| | parent: 0:e6eb3150255d
1116 |/| | parent: 0:e6eb3150255d
1117 | ~ | parent: 7:b632bb1b1224
1117 | ~ | parent: 7:b632bb1b1224
1118 | | user: test
1118 | | user: test
1119 | | date: Thu Jan 01 00:00:08 1970 +0000
1119 | | date: Thu Jan 01 00:00:08 1970 +0000
1120 | | summary: (8) merge two known; one immediate left, one far right
1120 | | summary: (8) merge two known; one immediate left, one far right
1121 | /
1121 | /
1122 o | changeset: 7:b632bb1b1224
1122 o | changeset: 7:b632bb1b1224
1123 |\ \ parent: 2:3d9a33b8d1e1
1123 |\ \ parent: 2:3d9a33b8d1e1
1124 | ~ | parent: 5:4409d547b708
1124 | ~ | parent: 5:4409d547b708
1125 | | user: test
1125 | | user: test
1126 | | date: Thu Jan 01 00:00:07 1970 +0000
1126 | | date: Thu Jan 01 00:00:07 1970 +0000
1127 | | summary: (7) expand
1127 | | summary: (7) expand
1128 | /
1128 | /
1129 | o changeset: 6:b105a072e251
1129 | o changeset: 6:b105a072e251
1130 |/| parent: 2:3d9a33b8d1e1
1130 |/| parent: 2:3d9a33b8d1e1
1131 | ~ parent: 5:4409d547b708
1131 | ~ parent: 5:4409d547b708
1132 | user: test
1132 | user: test
1133 | date: Thu Jan 01 00:00:06 1970 +0000
1133 | date: Thu Jan 01 00:00:06 1970 +0000
1134 | summary: (6) merge two known; one immediate left, one far left
1134 | summary: (6) merge two known; one immediate left, one far left
1135 |
1135 |
1136 o changeset: 5:4409d547b708
1136 o changeset: 5:4409d547b708
1137 |\ parent: 3:27eef8ed80b4
1137 |\ parent: 3:27eef8ed80b4
1138 | ~ parent: 4:26a8bac39d9f
1138 | ~ parent: 4:26a8bac39d9f
1139 | user: test
1139 | user: test
1140 | date: Thu Jan 01 00:00:05 1970 +0000
1140 | date: Thu Jan 01 00:00:05 1970 +0000
1141 | summary: (5) expand
1141 | summary: (5) expand
1142 |
1142 |
1143 o changeset: 4:26a8bac39d9f
1143 o changeset: 4:26a8bac39d9f
1144 |\ parent: 1:6db2ef61d156
1144 |\ parent: 1:6db2ef61d156
1145 ~ ~ parent: 3:27eef8ed80b4
1145 ~ ~ parent: 3:27eef8ed80b4
1146 user: test
1146 user: test
1147 date: Thu Jan 01 00:00:04 1970 +0000
1147 date: Thu Jan 01 00:00:04 1970 +0000
1148 summary: (4) merge two known; one immediate left, one immediate right
1148 summary: (4) merge two known; one immediate left, one immediate right
1149
1149
1150
1150
1151
1151
1152 Empty revision range - display nothing:
1152 Empty revision range - display nothing:
1153 $ hg log -G -r 1..0
1153 $ hg log -G -r 1..0
1154
1154
1155 $ cd ..
1155 $ cd ..
1156
1156
1157 #if no-outer-repo
1157 #if no-outer-repo
1158
1158
1159 From outer space:
1159 From outer space:
1160 $ hg log -G -l1 repo
1160 $ hg log -G -l1 repo
1161 @ changeset: 34:fea3ac5810e0
1161 @ changeset: 34:fea3ac5810e0
1162 | tag: tip
1162 | tag: tip
1163 ~ parent: 32:d06dffa21a31
1163 ~ parent: 32:d06dffa21a31
1164 user: test
1164 user: test
1165 date: Thu Jan 01 00:00:34 1970 +0000
1165 date: Thu Jan 01 00:00:34 1970 +0000
1166 summary: (34) head
1166 summary: (34) head
1167
1167
1168 $ hg log -G -l1 repo/a
1168 $ hg log -G -l1 repo/a
1169 @ changeset: 34:fea3ac5810e0
1169 @ changeset: 34:fea3ac5810e0
1170 | tag: tip
1170 | tag: tip
1171 ~ parent: 32:d06dffa21a31
1171 ~ parent: 32:d06dffa21a31
1172 user: test
1172 user: test
1173 date: Thu Jan 01 00:00:34 1970 +0000
1173 date: Thu Jan 01 00:00:34 1970 +0000
1174 summary: (34) head
1174 summary: (34) head
1175
1175
1176 $ hg log -G -l1 repo/missing
1176 $ hg log -G -l1 repo/missing
1177
1177
1178 #endif
1178 #endif
1179
1179
1180 File log with revs != cset revs:
1180 File log with revs != cset revs:
1181 $ hg init flog
1181 $ hg init flog
1182 $ cd flog
1182 $ cd flog
1183 $ echo one >one
1183 $ echo one >one
1184 $ hg add one
1184 $ hg add one
1185 $ hg commit -mone
1185 $ hg commit -mone
1186 $ echo two >two
1186 $ echo two >two
1187 $ hg add two
1187 $ hg add two
1188 $ hg commit -mtwo
1188 $ hg commit -mtwo
1189 $ echo more >two
1189 $ echo more >two
1190 $ hg commit -mmore
1190 $ hg commit -mmore
1191 $ hg log -G two
1191 $ hg log -G two
1192 @ changeset: 2:12c28321755b
1192 @ changeset: 2:12c28321755b
1193 | tag: tip
1193 | tag: tip
1194 | user: test
1194 | user: test
1195 | date: Thu Jan 01 00:00:00 1970 +0000
1195 | date: Thu Jan 01 00:00:00 1970 +0000
1196 | summary: more
1196 | summary: more
1197 |
1197 |
1198 o changeset: 1:5ac72c0599bf
1198 o changeset: 1:5ac72c0599bf
1199 | user: test
1199 | user: test
1200 ~ date: Thu Jan 01 00:00:00 1970 +0000
1200 ~ date: Thu Jan 01 00:00:00 1970 +0000
1201 summary: two
1201 summary: two
1202
1202
1203
1203
1204 Issue1896: File log with explicit style
1204 Issue1896: File log with explicit style
1205 $ hg log -G --style=default one
1205 $ hg log -G --style=default one
1206 o changeset: 0:3d578b4a1f53
1206 o changeset: 0:3d578b4a1f53
1207 user: test
1207 user: test
1208 date: Thu Jan 01 00:00:00 1970 +0000
1208 date: Thu Jan 01 00:00:00 1970 +0000
1209 summary: one
1209 summary: one
1210
1210
1211 Issue2395: glog --style header and footer
1211 Issue2395: glog --style header and footer
1212 $ hg log -G --style=xml one
1212 $ hg log -G --style=xml one
1213 <?xml version="1.0"?>
1213 <?xml version="1.0"?>
1214 <log>
1214 <log>
1215 o <logentry revision="0" node="3d578b4a1f537d5fcf7301bfa9c0b97adfaa6fb1">
1215 o <logentry revision="0" node="3d578b4a1f537d5fcf7301bfa9c0b97adfaa6fb1">
1216 <author email="test">test</author>
1216 <author email="test">test</author>
1217 <date>1970-01-01T00:00:00+00:00</date>
1217 <date>1970-01-01T00:00:00+00:00</date>
1218 <msg xml:space="preserve">one</msg>
1218 <msg xml:space="preserve">one</msg>
1219 </logentry>
1219 </logentry>
1220 </log>
1220 </log>
1221
1221
1222 $ cd ..
1222 $ cd ..
1223
1223
1224 Incoming and outgoing:
1224 Incoming and outgoing:
1225
1225
1226 $ hg clone -U -r31 repo repo2
1226 $ hg clone -U -r31 repo repo2
1227 adding changesets
1227 adding changesets
1228 adding manifests
1228 adding manifests
1229 adding file changes
1229 adding file changes
1230 added 31 changesets with 31 changes to 1 files
1230 added 31 changesets with 31 changes to 1 files
1231 new changesets e6eb3150255d:621d83e11f67
1231 new changesets e6eb3150255d:621d83e11f67
1232 $ cd repo2
1232 $ cd repo2
1233
1233
1234 $ hg incoming --graph ../repo
1234 $ hg incoming --graph ../repo
1235 comparing with ../repo
1235 comparing with ../repo
1236 searching for changes
1236 searching for changes
1237 o changeset: 34:fea3ac5810e0
1237 o changeset: 34:fea3ac5810e0
1238 | tag: tip
1238 | tag: tip
1239 | parent: 32:d06dffa21a31
1239 | parent: 32:d06dffa21a31
1240 | user: test
1240 | user: test
1241 | date: Thu Jan 01 00:00:34 1970 +0000
1241 | date: Thu Jan 01 00:00:34 1970 +0000
1242 | summary: (34) head
1242 | summary: (34) head
1243 |
1243 |
1244 | o changeset: 33:68608f5145f9
1244 | o changeset: 33:68608f5145f9
1245 | parent: 18:1aa84d96232a
1245 | parent: 18:1aa84d96232a
1246 | user: test
1246 | user: test
1247 | date: Thu Jan 01 00:00:33 1970 +0000
1247 | date: Thu Jan 01 00:00:33 1970 +0000
1248 | summary: (33) head
1248 | summary: (33) head
1249 |
1249 |
1250 o changeset: 32:d06dffa21a31
1250 o changeset: 32:d06dffa21a31
1251 | parent: 27:886ed638191b
1251 | parent: 27:886ed638191b
1252 | parent: 31:621d83e11f67
1252 | parent: 31:621d83e11f67
1253 | user: test
1253 | user: test
1254 | date: Thu Jan 01 00:00:32 1970 +0000
1254 | date: Thu Jan 01 00:00:32 1970 +0000
1255 | summary: (32) expand
1255 | summary: (32) expand
1256 |
1256 |
1257 o changeset: 27:886ed638191b
1257 o changeset: 27:886ed638191b
1258 parent: 21:d42a756af44d
1258 parent: 21:d42a756af44d
1259 user: test
1259 user: test
1260 date: Thu Jan 01 00:00:27 1970 +0000
1260 date: Thu Jan 01 00:00:27 1970 +0000
1261 summary: (27) collapse
1261 summary: (27) collapse
1262
1262
1263 $ cd ..
1263 $ cd ..
1264
1264
1265 $ hg -R repo outgoing --graph repo2
1265 $ hg -R repo outgoing --graph repo2
1266 comparing with repo2
1266 comparing with repo2
1267 searching for changes
1267 searching for changes
1268 @ changeset: 34:fea3ac5810e0
1268 @ changeset: 34:fea3ac5810e0
1269 | tag: tip
1269 | tag: tip
1270 | parent: 32:d06dffa21a31
1270 | parent: 32:d06dffa21a31
1271 | user: test
1271 | user: test
1272 | date: Thu Jan 01 00:00:34 1970 +0000
1272 | date: Thu Jan 01 00:00:34 1970 +0000
1273 | summary: (34) head
1273 | summary: (34) head
1274 |
1274 |
1275 | o changeset: 33:68608f5145f9
1275 | o changeset: 33:68608f5145f9
1276 | parent: 18:1aa84d96232a
1276 | parent: 18:1aa84d96232a
1277 | user: test
1277 | user: test
1278 | date: Thu Jan 01 00:00:33 1970 +0000
1278 | date: Thu Jan 01 00:00:33 1970 +0000
1279 | summary: (33) head
1279 | summary: (33) head
1280 |
1280 |
1281 o changeset: 32:d06dffa21a31
1281 o changeset: 32:d06dffa21a31
1282 | parent: 27:886ed638191b
1282 | parent: 27:886ed638191b
1283 | parent: 31:621d83e11f67
1283 | parent: 31:621d83e11f67
1284 | user: test
1284 | user: test
1285 | date: Thu Jan 01 00:00:32 1970 +0000
1285 | date: Thu Jan 01 00:00:32 1970 +0000
1286 | summary: (32) expand
1286 | summary: (32) expand
1287 |
1287 |
1288 o changeset: 27:886ed638191b
1288 o changeset: 27:886ed638191b
1289 parent: 21:d42a756af44d
1289 parent: 21:d42a756af44d
1290 user: test
1290 user: test
1291 date: Thu Jan 01 00:00:27 1970 +0000
1291 date: Thu Jan 01 00:00:27 1970 +0000
1292 summary: (27) collapse
1292 summary: (27) collapse
1293
1293
1294
1294
1295 File + limit with revs != cset revs:
1295 File + limit with revs != cset revs:
1296 $ cd repo
1296 $ cd repo
1297 $ touch b
1297 $ touch b
1298 $ hg ci -Aqm0
1298 $ hg ci -Aqm0
1299 $ hg log -G -l2 a
1299 $ hg log -G -l2 a
1300 o changeset: 34:fea3ac5810e0
1300 o changeset: 34:fea3ac5810e0
1301 | parent: 32:d06dffa21a31
1301 | parent: 32:d06dffa21a31
1302 ~ user: test
1302 ~ user: test
1303 date: Thu Jan 01 00:00:34 1970 +0000
1303 date: Thu Jan 01 00:00:34 1970 +0000
1304 summary: (34) head
1304 summary: (34) head
1305
1305
1306 o changeset: 33:68608f5145f9
1306 o changeset: 33:68608f5145f9
1307 | parent: 18:1aa84d96232a
1307 | parent: 18:1aa84d96232a
1308 ~ user: test
1308 ~ user: test
1309 date: Thu Jan 01 00:00:33 1970 +0000
1309 date: Thu Jan 01 00:00:33 1970 +0000
1310 summary: (33) head
1310 summary: (33) head
1311
1311
1312
1312
1313 File + limit + -ra:b, (b - a) < limit:
1313 File + limit + -ra:b, (b - a) < limit:
1314 $ hg log -G -l3000 -r32:tip a
1314 $ hg log -G -l3000 -r32:tip a
1315 o changeset: 34:fea3ac5810e0
1315 o changeset: 34:fea3ac5810e0
1316 | parent: 32:d06dffa21a31
1316 | parent: 32:d06dffa21a31
1317 | user: test
1317 | user: test
1318 | date: Thu Jan 01 00:00:34 1970 +0000
1318 | date: Thu Jan 01 00:00:34 1970 +0000
1319 | summary: (34) head
1319 | summary: (34) head
1320 |
1320 |
1321 | o changeset: 33:68608f5145f9
1321 | o changeset: 33:68608f5145f9
1322 | | parent: 18:1aa84d96232a
1322 | | parent: 18:1aa84d96232a
1323 | ~ user: test
1323 | ~ user: test
1324 | date: Thu Jan 01 00:00:33 1970 +0000
1324 | date: Thu Jan 01 00:00:33 1970 +0000
1325 | summary: (33) head
1325 | summary: (33) head
1326 |
1326 |
1327 o changeset: 32:d06dffa21a31
1327 o changeset: 32:d06dffa21a31
1328 |\ parent: 27:886ed638191b
1328 |\ parent: 27:886ed638191b
1329 ~ ~ parent: 31:621d83e11f67
1329 ~ ~ parent: 31:621d83e11f67
1330 user: test
1330 user: test
1331 date: Thu Jan 01 00:00:32 1970 +0000
1331 date: Thu Jan 01 00:00:32 1970 +0000
1332 summary: (32) expand
1332 summary: (32) expand
1333
1333
1334
1334
1335 Point out a common and an uncommon unshown parent
1335 Point out a common and an uncommon unshown parent
1336
1336
1337 $ hg log -G -r 'rev(8) or rev(9)'
1337 $ hg log -G -r 'rev(8) or rev(9)'
1338 o changeset: 9:7010c0af0a35
1338 o changeset: 9:7010c0af0a35
1339 |\ parent: 7:b632bb1b1224
1339 |\ parent: 7:b632bb1b1224
1340 | ~ parent: 8:7a0b11f71937
1340 | ~ parent: 8:7a0b11f71937
1341 | user: test
1341 | user: test
1342 | date: Thu Jan 01 00:00:09 1970 +0000
1342 | date: Thu Jan 01 00:00:09 1970 +0000
1343 | summary: (9) expand
1343 | summary: (9) expand
1344 |
1344 |
1345 o changeset: 8:7a0b11f71937
1345 o changeset: 8:7a0b11f71937
1346 |\ parent: 0:e6eb3150255d
1346 |\ parent: 0:e6eb3150255d
1347 ~ ~ parent: 7:b632bb1b1224
1347 ~ ~ parent: 7:b632bb1b1224
1348 user: test
1348 user: test
1349 date: Thu Jan 01 00:00:08 1970 +0000
1349 date: Thu Jan 01 00:00:08 1970 +0000
1350 summary: (8) merge two known; one immediate left, one far right
1350 summary: (8) merge two known; one immediate left, one far right
1351
1351
1352
1352
1353 File + limit + -ra:b, b < tip:
1353 File + limit + -ra:b, b < tip:
1354
1354
1355 $ hg log -G -l1 -r32:34 a
1355 $ hg log -G -l1 -r32:34 a
1356 o changeset: 34:fea3ac5810e0
1356 o changeset: 34:fea3ac5810e0
1357 | parent: 32:d06dffa21a31
1357 | parent: 32:d06dffa21a31
1358 ~ user: test
1358 ~ user: test
1359 date: Thu Jan 01 00:00:34 1970 +0000
1359 date: Thu Jan 01 00:00:34 1970 +0000
1360 summary: (34) head
1360 summary: (34) head
1361
1361
1362
1362
1363 file(File) + limit + -ra:b, b < tip:
1363 file(File) + limit + -ra:b, b < tip:
1364
1364
1365 $ hg log -G -l1 -r32:34 -r 'file("a")'
1365 $ hg log -G -l1 -r32:34 -r 'file("a")'
1366 o changeset: 34:fea3ac5810e0
1366 o changeset: 34:fea3ac5810e0
1367 | parent: 32:d06dffa21a31
1367 | parent: 32:d06dffa21a31
1368 ~ user: test
1368 ~ user: test
1369 date: Thu Jan 01 00:00:34 1970 +0000
1369 date: Thu Jan 01 00:00:34 1970 +0000
1370 summary: (34) head
1370 summary: (34) head
1371
1371
1372
1372
1373 limit(file(File) and a::b), b < tip:
1373 limit(file(File) and a::b), b < tip:
1374
1374
1375 $ hg log -G -r 'limit(file("a") and 32::34, 1)'
1375 $ hg log -G -r 'limit(file("a") and 32::34, 1)'
1376 o changeset: 32:d06dffa21a31
1376 o changeset: 32:d06dffa21a31
1377 |\ parent: 27:886ed638191b
1377 |\ parent: 27:886ed638191b
1378 ~ ~ parent: 31:621d83e11f67
1378 ~ ~ parent: 31:621d83e11f67
1379 user: test
1379 user: test
1380 date: Thu Jan 01 00:00:32 1970 +0000
1380 date: Thu Jan 01 00:00:32 1970 +0000
1381 summary: (32) expand
1381 summary: (32) expand
1382
1382
1383
1383
1384 File + limit + -ra:b, b < tip:
1384 File + limit + -ra:b, b < tip:
1385
1385
1386 $ hg log -G -r 'limit(file("a") and 34::32, 1)'
1386 $ hg log -G -r 'limit(file("a") and 34::32, 1)'
1387
1387
1388 File + limit + -ra:b, b < tip, (b - a) < limit:
1388 File + limit + -ra:b, b < tip, (b - a) < limit:
1389
1389
1390 $ hg log -G -l10 -r33:34 a
1390 $ hg log -G -l10 -r33:34 a
1391 o changeset: 34:fea3ac5810e0
1391 o changeset: 34:fea3ac5810e0
1392 | parent: 32:d06dffa21a31
1392 | parent: 32:d06dffa21a31
1393 ~ user: test
1393 ~ user: test
1394 date: Thu Jan 01 00:00:34 1970 +0000
1394 date: Thu Jan 01 00:00:34 1970 +0000
1395 summary: (34) head
1395 summary: (34) head
1396
1396
1397 o changeset: 33:68608f5145f9
1397 o changeset: 33:68608f5145f9
1398 | parent: 18:1aa84d96232a
1398 | parent: 18:1aa84d96232a
1399 ~ user: test
1399 ~ user: test
1400 date: Thu Jan 01 00:00:33 1970 +0000
1400 date: Thu Jan 01 00:00:33 1970 +0000
1401 summary: (33) head
1401 summary: (33) head
1402
1402
1403
1403
1404 Do not crash or produce strange graphs if history is buggy
1404 Do not crash or produce strange graphs if history is buggy
1405
1405
1406 $ hg branch branch
1406 $ hg branch branch
1407 marked working directory as branch branch
1407 marked working directory as branch branch
1408 (branches are permanent and global, did you want a bookmark?)
1408 (branches are permanent and global, did you want a bookmark?)
1409 $ commit 36 "buggy merge: identical parents" 35 35
1409 $ commit 36 "buggy merge: identical parents" 35 35
1410 $ hg log -G -l5
1410 $ hg log -G -l5
1411 @ changeset: 36:08a19a744424
1411 @ changeset: 36:08a19a744424
1412 | branch: branch
1412 | branch: branch
1413 | tag: tip
1413 | tag: tip
1414 | parent: 35:9159c3644c5e
1414 | parent: 35:9159c3644c5e
1415 | parent: 35:9159c3644c5e
1415 | parent: 35:9159c3644c5e
1416 | user: test
1416 | user: test
1417 | date: Thu Jan 01 00:00:36 1970 +0000
1417 | date: Thu Jan 01 00:00:36 1970 +0000
1418 | summary: (36) buggy merge: identical parents
1418 | summary: (36) buggy merge: identical parents
1419 |
1419 |
1420 o changeset: 35:9159c3644c5e
1420 o changeset: 35:9159c3644c5e
1421 | user: test
1421 | user: test
1422 | date: Thu Jan 01 00:00:00 1970 +0000
1422 | date: Thu Jan 01 00:00:00 1970 +0000
1423 | summary: 0
1423 | summary: 0
1424 |
1424 |
1425 o changeset: 34:fea3ac5810e0
1425 o changeset: 34:fea3ac5810e0
1426 | parent: 32:d06dffa21a31
1426 | parent: 32:d06dffa21a31
1427 | user: test
1427 | user: test
1428 | date: Thu Jan 01 00:00:34 1970 +0000
1428 | date: Thu Jan 01 00:00:34 1970 +0000
1429 | summary: (34) head
1429 | summary: (34) head
1430 |
1430 |
1431 | o changeset: 33:68608f5145f9
1431 | o changeset: 33:68608f5145f9
1432 | | parent: 18:1aa84d96232a
1432 | | parent: 18:1aa84d96232a
1433 | ~ user: test
1433 | ~ user: test
1434 | date: Thu Jan 01 00:00:33 1970 +0000
1434 | date: Thu Jan 01 00:00:33 1970 +0000
1435 | summary: (33) head
1435 | summary: (33) head
1436 |
1436 |
1437 o changeset: 32:d06dffa21a31
1437 o changeset: 32:d06dffa21a31
1438 |\ parent: 27:886ed638191b
1438 |\ parent: 27:886ed638191b
1439 ~ ~ parent: 31:621d83e11f67
1439 ~ ~ parent: 31:621d83e11f67
1440 user: test
1440 user: test
1441 date: Thu Jan 01 00:00:32 1970 +0000
1441 date: Thu Jan 01 00:00:32 1970 +0000
1442 summary: (32) expand
1442 summary: (32) expand
1443
1443
1444
1444
1445 Test log -G options
1445 Test log -G options
1446
1446
1447 $ testlog() {
1447 $ testlog() {
1448 > hg log -G --print-revset "$@"
1448 > hg log -G --print-revset "$@"
1449 > hg log --template 'nodetag {rev}\n' "$@" | grep nodetag \
1449 > hg log --template 'nodetag {rev}\n' "$@" | grep nodetag \
1450 > | sed 's/.*nodetag/nodetag/' > log.nodes
1450 > | sed 's/.*nodetag/nodetag/' > log.nodes
1451 > hg log -G --template 'nodetag {rev}\n' "$@" | grep nodetag \
1451 > hg log -G --template 'nodetag {rev}\n' "$@" | grep nodetag \
1452 > | sed 's/.*nodetag/nodetag/' > glog.nodes
1452 > | sed 's/.*nodetag/nodetag/' > glog.nodes
1453 > (cmp log.nodes glog.nodes || diff -u log.nodes glog.nodes) \
1453 > (cmp log.nodes glog.nodes || diff -u log.nodes glog.nodes) \
1454 > | grep '^[-+@ ]' || :
1454 > | grep '^[-+@ ]' || :
1455 > }
1455 > }
1456
1456
1457 glog always reorders nodes which explains the difference with log
1457 glog always reorders nodes which explains the difference with log
1458
1458
1459 $ testlog -r 27 -r 25 -r 21 -r 34 -r 32 -r 31
1459 $ testlog -r 27 -r 25 -r 21 -r 34 -r 32 -r 31
1460 ['27', '25', '21', '34', '32', '31']
1460 ['27', '25', '21', '34', '32', '31']
1461 []
1461 []
1462 <baseset- [21, 25, 27, 31, 32, 34]>
1462 <baseset- [21, 25, 27, 31, 32, 34]>
1463 --- log.nodes * (glob)
1463 --- log.nodes * (glob)
1464 +++ glog.nodes * (glob)
1464 +++ glog.nodes * (glob)
1465 @@ -1,6 +1,6 @@
1465 @@ -1,6 +1,6 @@
1466 -nodetag 27
1466 -nodetag 27
1467 -nodetag 25
1467 -nodetag 25
1468 -nodetag 21
1468 -nodetag 21
1469 nodetag 34
1469 nodetag 34
1470 nodetag 32
1470 nodetag 32
1471 nodetag 31
1471 nodetag 31
1472 +nodetag 27
1472 +nodetag 27
1473 +nodetag 25
1473 +nodetag 25
1474 +nodetag 21
1474 +nodetag 21
1475 $ testlog -u test -u not-a-user
1475 $ testlog -u test -u not-a-user
1476 []
1476 []
1477 (or
1477 (or
1478 (list
1478 (list
1479 (func
1479 (func
1480 (symbol 'user')
1480 (symbol 'user')
1481 (string 'test'))
1481 (string 'test'))
1482 (func
1482 (func
1483 (symbol 'user')
1483 (symbol 'user')
1484 (string 'not-a-user'))))
1484 (string 'not-a-user'))))
1485 <filteredset
1485 <filteredset
1486 <spanset- 0:37>,
1486 <spanset- 0:37>,
1487 <addset
1487 <addset
1488 <filteredset
1488 <filteredset
1489 <fullreposet+ 0:37>,
1489 <fullreposet+ 0:37>,
1490 <user 'test'>>,
1490 <user 'test'>>,
1491 <filteredset
1491 <filteredset
1492 <fullreposet+ 0:37>,
1492 <fullreposet+ 0:37>,
1493 <user 'not-a-user'>>>>
1493 <user 'not-a-user'>>>>
1494 $ testlog -b not-a-branch
1494 $ testlog -b not-a-branch
1495 abort: unknown revision 'not-a-branch'!
1495 abort: unknown revision 'not-a-branch'!
1496 abort: unknown revision 'not-a-branch'!
1496 abort: unknown revision 'not-a-branch'!
1497 abort: unknown revision 'not-a-branch'!
1497 abort: unknown revision 'not-a-branch'!
1498 $ testlog -b 35 -b 36 --only-branch branch
1498 $ testlog -b 35 -b 36 --only-branch branch
1499 []
1499 []
1500 (or
1500 (or
1501 (list
1501 (list
1502 (func
1502 (func
1503 (symbol 'branch')
1503 (symbol 'branch')
1504 (string 'default'))
1504 (string 'default'))
1505 (or
1505 (or
1506 (list
1506 (list
1507 (func
1507 (func
1508 (symbol 'branch')
1508 (symbol 'branch')
1509 (string 'branch'))
1509 (string 'branch'))
1510 (func
1510 (func
1511 (symbol 'branch')
1511 (symbol 'branch')
1512 (string 'branch'))))))
1512 (string 'branch'))))))
1513 <filteredset
1513 <filteredset
1514 <spanset- 0:37>,
1514 <spanset- 0:37>,
1515 <addset
1515 <addset
1516 <filteredset
1516 <filteredset
1517 <fullreposet+ 0:37>,
1517 <fullreposet+ 0:37>,
1518 <branch 'default'>>,
1518 <branch 'default'>>,
1519 <addset
1519 <addset
1520 <filteredset
1520 <filteredset
1521 <fullreposet+ 0:37>,
1521 <fullreposet+ 0:37>,
1522 <branch 'branch'>>,
1522 <branch 'branch'>>,
1523 <filteredset
1523 <filteredset
1524 <fullreposet+ 0:37>,
1524 <fullreposet+ 0:37>,
1525 <branch 'branch'>>>>>
1525 <branch 'branch'>>>>>
1526 $ testlog -k expand -k merge
1526 $ testlog -k expand -k merge
1527 []
1527 []
1528 (or
1528 (or
1529 (list
1529 (list
1530 (func
1530 (func
1531 (symbol 'keyword')
1531 (symbol 'keyword')
1532 (string 'expand'))
1532 (string 'expand'))
1533 (func
1533 (func
1534 (symbol 'keyword')
1534 (symbol 'keyword')
1535 (string 'merge'))))
1535 (string 'merge'))))
1536 <filteredset
1536 <filteredset
1537 <spanset- 0:37>,
1537 <spanset- 0:37>,
1538 <addset
1538 <addset
1539 <filteredset
1539 <filteredset
1540 <fullreposet+ 0:37>,
1540 <fullreposet+ 0:37>,
1541 <keyword 'expand'>>,
1541 <keyword 'expand'>>,
1542 <filteredset
1542 <filteredset
1543 <fullreposet+ 0:37>,
1543 <fullreposet+ 0:37>,
1544 <keyword 'merge'>>>>
1544 <keyword 'merge'>>>>
1545 $ testlog --only-merges
1545 $ testlog --only-merges
1546 []
1546 []
1547 (func
1547 (func
1548 (symbol 'merge')
1548 (symbol 'merge')
1549 None)
1549 None)
1550 <filteredset
1550 <filteredset
1551 <spanset- 0:37>,
1551 <spanset- 0:37>,
1552 <merge>>
1552 <merge>>
1553 $ testlog --no-merges
1553 $ testlog --no-merges
1554 []
1554 []
1555 (not
1555 (not
1556 (func
1556 (func
1557 (symbol 'merge')
1557 (symbol 'merge')
1558 None))
1558 None))
1559 <filteredset
1559 <filteredset
1560 <spanset- 0:37>,
1560 <spanset- 0:37>,
1561 <not
1561 <not
1562 <filteredset
1562 <filteredset
1563 <spanset- 0:37>,
1563 <spanset- 0:37>,
1564 <merge>>>>
1564 <merge>>>>
1565 $ testlog --date '2 0 to 4 0'
1565 $ testlog --date '2 0 to 4 0'
1566 []
1566 []
1567 (func
1567 (func
1568 (symbol 'date')
1568 (symbol 'date')
1569 (string '2 0 to 4 0'))
1569 (string '2 0 to 4 0'))
1570 <filteredset
1570 <filteredset
1571 <spanset- 0:37>,
1571 <spanset- 0:37>,
1572 <date '2 0 to 4 0'>>
1572 <date '2 0 to 4 0'>>
1573 $ hg log -G -d 'brace ) in a date'
1573 $ hg log -G -d 'brace ) in a date'
1574 hg: parse error: invalid date: 'brace ) in a date'
1574 hg: parse error: invalid date: 'brace ) in a date'
1575 [255]
1575 [255]
1576 $ testlog --prune 31 --prune 32
1576 $ testlog --prune 31 --prune 32
1577 []
1577 []
1578 (not
1578 (not
1579 (or
1579 (or
1580 (list
1580 (list
1581 (func
1581 (func
1582 (symbol 'ancestors')
1582 (symbol 'ancestors')
1583 (string '31'))
1583 (string '31'))
1584 (func
1584 (func
1585 (symbol 'ancestors')
1585 (symbol 'ancestors')
1586 (string '32')))))
1586 (string '32')))))
1587 <filteredset
1587 <filteredset
1588 <spanset- 0:37>,
1588 <spanset- 0:37>,
1589 <not
1589 <not
1590 <addset
1590 <addset
1591 <filteredset
1591 <filteredset
1592 <spanset- 0:37>,
1592 <spanset- 0:37>,
1593 <generatorsetdesc+>>,
1593 <generatorsetdesc+>>,
1594 <filteredset
1594 <filteredset
1595 <spanset- 0:37>,
1595 <spanset- 0:37>,
1596 <generatorsetdesc+>>>>>
1596 <generatorsetdesc+>>>>>
1597
1597
1598 Dedicated repo for --follow and paths filtering. The g is crafted to
1598 Dedicated repo for --follow and paths filtering. The g is crafted to
1599 have 2 filelog topological heads in a linear changeset graph.
1599 have 2 filelog topological heads in a linear changeset graph.
1600
1600
1601 $ cd ..
1601 $ cd ..
1602 $ hg init follow
1602 $ hg init follow
1603 $ cd follow
1603 $ cd follow
1604 $ testlog --follow
1604 $ testlog --follow
1605 []
1605 []
1606 []
1606 []
1607 <baseset []>
1607 <baseset []>
1608 $ testlog -rnull
1608 $ testlog -rnull
1609 ['null']
1609 ['null']
1610 []
1610 []
1611 <baseset [-1]>
1611 <baseset [-1]>
1612 $ echo a > a
1612 $ echo a > a
1613 $ echo aa > aa
1613 $ echo aa > aa
1614 $ echo f > f
1614 $ echo f > f
1615 $ hg ci -Am "add a" a aa f
1615 $ hg ci -Am "add a" a aa f
1616 $ hg cp a b
1616 $ hg cp a b
1617 $ hg cp f g
1617 $ hg cp f g
1618 $ hg ci -m "copy a b"
1618 $ hg ci -m "copy a b"
1619 $ mkdir dir
1619 $ mkdir dir
1620 $ hg mv b dir
1620 $ hg mv b dir
1621 $ echo g >> g
1621 $ echo g >> g
1622 $ echo f >> f
1622 $ echo f >> f
1623 $ hg ci -m "mv b dir/b"
1623 $ hg ci -m "mv b dir/b"
1624 $ hg mv a b
1624 $ hg mv a b
1625 $ hg cp -f f g
1625 $ hg cp -f f g
1626 $ echo a > d
1626 $ echo a > d
1627 $ hg add d
1627 $ hg add d
1628 $ hg ci -m "mv a b; add d"
1628 $ hg ci -m "mv a b; add d"
1629 $ hg mv dir/b e
1629 $ hg mv dir/b e
1630 $ hg ci -m "mv dir/b e"
1630 $ hg ci -m "mv dir/b e"
1631 $ hg log -G --template '({rev}) {desc|firstline}\n'
1631 $ hg log -G --template '({rev}) {desc|firstline}\n'
1632 @ (4) mv dir/b e
1632 @ (4) mv dir/b e
1633 |
1633 |
1634 o (3) mv a b; add d
1634 o (3) mv a b; add d
1635 |
1635 |
1636 o (2) mv b dir/b
1636 o (2) mv b dir/b
1637 |
1637 |
1638 o (1) copy a b
1638 o (1) copy a b
1639 |
1639 |
1640 o (0) add a
1640 o (0) add a
1641
1641
1642
1642
1643 $ testlog a
1643 $ testlog a
1644 []
1644 []
1645 (func
1645 (func
1646 (symbol 'filelog')
1646 (symbol 'filelog')
1647 (string 'a'))
1647 (string 'a'))
1648 <filteredset
1648 <filteredset
1649 <spanset- 0:5>, set([0])>
1649 <spanset- 0:5>, set([0])>
1650 $ testlog a b
1650 $ testlog a b
1651 []
1651 []
1652 (or
1652 (or
1653 (list
1653 (list
1654 (func
1654 (func
1655 (symbol 'filelog')
1655 (symbol 'filelog')
1656 (string 'a'))
1656 (string 'a'))
1657 (func
1657 (func
1658 (symbol 'filelog')
1658 (symbol 'filelog')
1659 (string 'b'))))
1659 (string 'b'))))
1660 <filteredset
1660 <filteredset
1661 <spanset- 0:5>,
1661 <spanset- 0:5>,
1662 <addset
1662 <addset
1663 <baseset+ [0]>,
1663 <baseset+ [0]>,
1664 <baseset+ [1]>>>
1664 <baseset+ [1]>>>
1665
1665
1666 Test falling back to slow path for non-existing files
1666 Test falling back to slow path for non-existing files
1667
1667
1668 $ testlog a c
1668 $ testlog a c
1669 []
1669 []
1670 (func
1670 (func
1671 (symbol '_matchfiles')
1671 (symbol '_matchfiles')
1672 (list
1672 (list
1673 (string 'r:')
1673 (string 'r:')
1674 (string 'd:relpath')
1674 (string 'd:relpath')
1675 (string 'p:a')
1675 (string 'p:a')
1676 (string 'p:c')))
1676 (string 'p:c')))
1677 <filteredset
1677 <filteredset
1678 <spanset- 0:5>,
1678 <spanset- 0:5>,
1679 <matchfiles patterns=['a', 'c'], include=[] exclude=[], default='relpath', rev=2147483647>>
1679 <matchfiles patterns=['a', 'c'], include=[] exclude=[], default='relpath', rev=2147483647>>
1680
1680
1681 Test multiple --include/--exclude/paths
1681 Test multiple --include/--exclude/paths
1682
1682
1683 $ testlog --include a --include e --exclude b --exclude e a e
1683 $ testlog --include a --include e --exclude b --exclude e a e
1684 []
1684 []
1685 (func
1685 (func
1686 (symbol '_matchfiles')
1686 (symbol '_matchfiles')
1687 (list
1687 (list
1688 (string 'r:')
1688 (string 'r:')
1689 (string 'd:relpath')
1689 (string 'd:relpath')
1690 (string 'p:a')
1690 (string 'p:a')
1691 (string 'p:e')
1691 (string 'p:e')
1692 (string 'i:a')
1692 (string 'i:a')
1693 (string 'i:e')
1693 (string 'i:e')
1694 (string 'x:b')
1694 (string 'x:b')
1695 (string 'x:e')))
1695 (string 'x:e')))
1696 <filteredset
1696 <filteredset
1697 <spanset- 0:5>,
1697 <spanset- 0:5>,
1698 <matchfiles patterns=['a', 'e'], include=['a', 'e'] exclude=['b', 'e'], default='relpath', rev=2147483647>>
1698 <matchfiles patterns=['a', 'e'], include=['a', 'e'] exclude=['b', 'e'], default='relpath', rev=2147483647>>
1699
1699
1700 Test glob expansion of pats
1700 Test glob expansion of pats
1701
1701
1702 $ expandglobs=`$PYTHON -c "import mercurial.util; \
1702 $ expandglobs=`$PYTHON -c "import mercurial.util; \
1703 > print(mercurial.util.expandglobs and 'true' or 'false')"`
1703 > print(mercurial.util.expandglobs and 'true' or 'false')"`
1704 $ if [ $expandglobs = "true" ]; then
1704 $ if [ $expandglobs = "true" ]; then
1705 > testlog 'a*';
1705 > testlog 'a*';
1706 > else
1706 > else
1707 > testlog a*;
1707 > testlog a*;
1708 > fi;
1708 > fi;
1709 []
1709 []
1710 (func
1710 (func
1711 (symbol 'filelog')
1711 (symbol 'filelog')
1712 (string 'aa'))
1712 (string 'aa'))
1713 <filteredset
1713 <filteredset
1714 <spanset- 0:5>, set([0])>
1714 <spanset- 0:5>, set([0])>
1715
1715
1716 Test --follow on a non-existent directory
1716 Test --follow on a non-existent directory
1717
1717
1718 $ testlog -f dir
1718 $ testlog -f dir
1719 abort: cannot follow file not in parent revision: "dir"
1719 abort: cannot follow file not in parent revision: "dir"
1720 abort: cannot follow file not in parent revision: "dir"
1720 abort: cannot follow file not in parent revision: "dir"
1721 abort: cannot follow file not in parent revision: "dir"
1721 abort: cannot follow file not in parent revision: "dir"
1722
1722
1723 Test --follow on a directory
1723 Test --follow on a directory
1724
1724
1725 $ hg up -q '.^'
1725 $ hg up -q '.^'
1726 $ testlog -f dir
1726 $ testlog -f dir
1727 []
1727 []
1728 (func
1728 (func
1729 (symbol '_matchfiles')
1729 (symbol '_matchfiles')
1730 (list
1730 (list
1731 (string 'r:')
1731 (string 'r:')
1732 (string 'd:relpath')
1732 (string 'd:relpath')
1733 (string 'p:dir')))
1733 (string 'p:dir')))
1734 <filteredset
1734 <filteredset
1735 <generatorsetdesc->,
1735 <generatorsetdesc->,
1736 <matchfiles patterns=['dir'], include=[] exclude=[], default='relpath', rev=2147483647>>
1736 <matchfiles patterns=['dir'], include=[] exclude=[], default='relpath', rev=2147483647>>
1737 $ hg up -q tip
1737 $ hg up -q tip
1738
1738
1739 Test --follow on file not in parent revision
1739 Test --follow on file not in parent revision
1740
1740
1741 $ testlog -f a
1741 $ testlog -f a
1742 abort: cannot follow file not in parent revision: "a"
1742 abort: cannot follow file not in parent revision: "a"
1743 abort: cannot follow file not in parent revision: "a"
1743 abort: cannot follow file not in parent revision: "a"
1744 abort: cannot follow file not in parent revision: "a"
1744 abort: cannot follow file not in parent revision: "a"
1745
1745
1746 Test --follow and patterns
1746 Test --follow and patterns
1747
1747
1748 $ testlog -f 'glob:*'
1748 $ testlog -f 'glob:*'
1749 []
1749 []
1750 (func
1750 (func
1751 (symbol '_matchfiles')
1751 (symbol '_matchfiles')
1752 (list
1752 (list
1753 (string 'r:')
1753 (string 'r:')
1754 (string 'd:relpath')
1754 (string 'd:relpath')
1755 (string 'p:glob:*')))
1755 (string 'p:glob:*')))
1756 <filteredset
1756 <filteredset
1757 <generatorsetdesc->,
1757 <generatorsetdesc->,
1758 <matchfiles patterns=['glob:*'], include=[] exclude=[], default='relpath', rev=2147483647>>
1758 <matchfiles patterns=['glob:*'], include=[] exclude=[], default='relpath', rev=2147483647>>
1759
1759
1760 Test --follow on a single rename
1760 Test --follow on a single rename
1761
1761
1762 $ hg up -q 2
1762 $ hg up -q 2
1763 $ testlog -f a
1763 $ testlog -f a
1764 []
1764 []
1765 []
1765 []
1766 <generatorsetdesc->
1766 <generatorsetdesc->
1767
1767
1768 Test --follow and multiple renames
1768 Test --follow and multiple renames
1769
1769
1770 $ hg up -q tip
1770 $ hg up -q tip
1771 $ testlog -f e
1771 $ testlog -f e
1772 []
1772 []
1773 []
1773 []
1774 <generatorsetdesc->
1774 <generatorsetdesc->
1775
1775
1776 Test --follow and multiple filelog heads
1776 Test --follow and multiple filelog heads
1777
1777
1778 $ hg up -q 2
1778 $ hg up -q 2
1779 $ testlog -f g
1779 $ testlog -f g
1780 []
1780 []
1781 []
1781 []
1782 <generatorsetdesc->
1782 <generatorsetdesc->
1783 $ cat log.nodes
1783 $ cat log.nodes
1784 nodetag 2
1784 nodetag 2
1785 nodetag 1
1785 nodetag 1
1786 nodetag 0
1786 nodetag 0
1787 $ hg up -q tip
1787 $ hg up -q tip
1788 $ testlog -f g
1788 $ testlog -f g
1789 []
1789 []
1790 []
1790 []
1791 <generatorsetdesc->
1791 <generatorsetdesc->
1792 $ cat log.nodes
1792 $ cat log.nodes
1793 nodetag 3
1793 nodetag 3
1794 nodetag 2
1794 nodetag 2
1795 nodetag 0
1795 nodetag 0
1796
1796
1797 Test --follow and multiple files
1797 Test --follow and multiple files
1798
1798
1799 $ testlog -f g e
1799 $ testlog -f g e
1800 []
1800 []
1801 []
1801 []
1802 <generatorsetdesc->
1802 <generatorsetdesc->
1803 $ cat log.nodes
1803 $ cat log.nodes
1804 nodetag 4
1804 nodetag 4
1805 nodetag 3
1805 nodetag 3
1806 nodetag 2
1806 nodetag 2
1807 nodetag 1
1807 nodetag 1
1808 nodetag 0
1808 nodetag 0
1809
1809
1810 Test --follow null parent
1810 Test --follow null parent
1811
1811
1812 $ hg up -q null
1812 $ hg up -q null
1813 $ testlog -f
1813 $ testlog -f
1814 []
1814 []
1815 []
1815 []
1816 <baseset []>
1816 <baseset []>
1817
1817
1818 Test --follow-first
1818 Test --follow-first
1819
1819
1820 $ hg up -q 3
1820 $ hg up -q 3
1821 $ echo ee > e
1821 $ echo ee > e
1822 $ hg ci -Am "add another e" e
1822 $ hg ci -Am "add another e" e
1823 created new head
1823 created new head
1824 $ hg merge --tool internal:other 4
1824 $ hg merge --tool internal:other 4
1825 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
1825 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
1826 (branch merge, don't forget to commit)
1826 (branch merge, don't forget to commit)
1827 $ echo merge > e
1827 $ echo merge > e
1828 $ hg ci -m "merge 5 and 4"
1828 $ hg ci -m "merge 5 and 4"
1829 $ testlog --follow-first
1829 $ testlog --follow-first
1830 []
1830 []
1831 []
1831 []
1832 <generatorsetdesc->
1832 <generatorsetdesc->
1833
1833
1834 Cannot compare with log --follow-first FILE as it never worked
1834 Cannot compare with log --follow-first FILE as it never worked
1835
1835
1836 $ hg log -G --print-revset --follow-first e
1836 $ hg log -G --print-revset --follow-first e
1837 []
1837 []
1838 []
1838 []
1839 <generatorsetdesc->
1839 <generatorsetdesc->
1840 $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
1840 $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
1841 @ 6 merge 5 and 4
1841 @ 6 merge 5 and 4
1842 |\
1842 |\
1843 | ~
1843 | ~
1844 o 5 add another e
1844 o 5 add another e
1845 |
1845 |
1846 ~
1846 ~
1847
1847
1848 Test --copies
1848 Test --copies
1849
1849
1850 $ hg log -G --copies --template "{rev} {desc|firstline} \
1850 $ hg log -G --copies --template "{rev} {desc|firstline} \
1851 > copies: {file_copies_switch}\n"
1851 > copies: {file_copies_switch}\n"
1852 @ 6 merge 5 and 4 copies:
1852 @ 6 merge 5 and 4 copies:
1853 |\
1853 |\
1854 | o 5 add another e copies:
1854 | o 5 add another e copies:
1855 | |
1855 | |
1856 o | 4 mv dir/b e copies: e (dir/b)
1856 o | 4 mv dir/b e copies: e (dir/b)
1857 |/
1857 |/
1858 o 3 mv a b; add d copies: b (a)g (f)
1858 o 3 mv a b; add d copies: b (a)g (f)
1859 |
1859 |
1860 o 2 mv b dir/b copies: dir/b (b)
1860 o 2 mv b dir/b copies: dir/b (b)
1861 |
1861 |
1862 o 1 copy a b copies: b (a)g (f)
1862 o 1 copy a b copies: b (a)g (f)
1863 |
1863 |
1864 o 0 add a copies:
1864 o 0 add a copies:
1865
1865
1866 Test "set:..." and parent revision
1866 Test "set:..." and parent revision
1867
1867
1868 $ hg up -q 4
1868 $ hg up -q 4
1869 $ testlog "set:copied()"
1869 $ testlog "set:copied()"
1870 []
1870 []
1871 (func
1871 (func
1872 (symbol '_matchfiles')
1872 (symbol '_matchfiles')
1873 (list
1873 (list
1874 (string 'r:')
1874 (string 'r:')
1875 (string 'd:relpath')
1875 (string 'd:relpath')
1876 (string 'p:set:copied()')))
1876 (string 'p:set:copied()')))
1877 <filteredset
1877 <filteredset
1878 <spanset- 0:7>,
1878 <spanset- 0:7>,
1879 <matchfiles patterns=['set:copied()'], include=[] exclude=[], default='relpath', rev=2147483647>>
1879 <matchfiles patterns=['set:copied()'], include=[] exclude=[], default='relpath', rev=2147483647>>
1880 $ testlog --include "set:copied()"
1880 $ testlog --include "set:copied()"
1881 []
1881 []
1882 (func
1882 (func
1883 (symbol '_matchfiles')
1883 (symbol '_matchfiles')
1884 (list
1884 (list
1885 (string 'r:')
1885 (string 'r:')
1886 (string 'd:relpath')
1886 (string 'd:relpath')
1887 (string 'i:set:copied()')))
1887 (string 'i:set:copied()')))
1888 <filteredset
1888 <filteredset
1889 <spanset- 0:7>,
1889 <spanset- 0:7>,
1890 <matchfiles patterns=[], include=['set:copied()'] exclude=[], default='relpath', rev=2147483647>>
1890 <matchfiles patterns=[], include=['set:copied()'] exclude=[], default='relpath', rev=2147483647>>
1891 $ testlog -r "sort(file('set:copied()'), -rev)"
1891 $ testlog -r "sort(file('set:copied()'), -rev)"
1892 ["sort(file('set:copied()'), -rev)"]
1892 ["sort(file('set:copied()'), -rev)"]
1893 []
1893 []
1894 <filteredset
1894 <filteredset
1895 <fullreposet- 0:7>,
1895 <fullreposet- 0:7>,
1896 <matchfiles patterns=['set:copied()'], include=[] exclude=[], default='glob', rev=None>>
1896 <matchfiles patterns=['set:copied()'], include=[] exclude=[], default='glob', rev=None>>
1897
1897
1898 Test --removed
1898 Test --removed
1899
1899
1900 $ testlog --removed
1900 $ testlog --removed
1901 []
1901 []
1902 []
1902 []
1903 <spanset- 0:7>
1903 <spanset- 0:7>
1904 $ testlog --removed a
1904 $ testlog --removed a
1905 []
1905 []
1906 (func
1906 (func
1907 (symbol '_matchfiles')
1907 (symbol '_matchfiles')
1908 (list
1908 (list
1909 (string 'r:')
1909 (string 'r:')
1910 (string 'd:relpath')
1910 (string 'd:relpath')
1911 (string 'p:a')))
1911 (string 'p:a')))
1912 <filteredset
1912 <filteredset
1913 <spanset- 0:7>,
1913 <spanset- 0:7>,
1914 <matchfiles patterns=['a'], include=[] exclude=[], default='relpath', rev=2147483647>>
1914 <matchfiles patterns=['a'], include=[] exclude=[], default='relpath', rev=2147483647>>
1915 $ testlog --removed --follow a
1915 $ testlog --removed --follow a
1916 []
1916 []
1917 (func
1917 (func
1918 (symbol '_matchfiles')
1918 (symbol '_matchfiles')
1919 (list
1919 (list
1920 (string 'r:')
1920 (string 'r:')
1921 (string 'd:relpath')
1921 (string 'd:relpath')
1922 (string 'p:a')))
1922 (string 'p:a')))
1923 <filteredset
1923 <filteredset
1924 <generatorsetdesc->,
1924 <generatorsetdesc->,
1925 <matchfiles patterns=['a'], include=[] exclude=[], default='relpath', rev=2147483647>>
1925 <matchfiles patterns=['a'], include=[] exclude=[], default='relpath', rev=2147483647>>
1926
1926
1927 Test --patch and --stat with --follow and --follow-first
1927 Test --patch and --stat with --follow and --follow-first
1928
1928
1929 $ hg up -q 3
1929 $ hg up -q 3
1930 $ hg log -G --git --patch b
1930 $ hg log -G --git --patch b
1931 o changeset: 1:216d4c92cf98
1931 o changeset: 1:216d4c92cf98
1932 | user: test
1932 | user: test
1933 ~ date: Thu Jan 01 00:00:00 1970 +0000
1933 ~ date: Thu Jan 01 00:00:00 1970 +0000
1934 summary: copy a b
1934 summary: copy a b
1935
1935
1936 diff --git a/a b/b
1936 diff --git a/a b/b
1937 copy from a
1937 copy from a
1938 copy to b
1938 copy to b
1939
1939
1940
1940
1941 $ hg log -G --git --stat b
1941 $ hg log -G --git --stat b
1942 o changeset: 1:216d4c92cf98
1942 o changeset: 1:216d4c92cf98
1943 | user: test
1943 | user: test
1944 ~ date: Thu Jan 01 00:00:00 1970 +0000
1944 ~ date: Thu Jan 01 00:00:00 1970 +0000
1945 summary: copy a b
1945 summary: copy a b
1946
1946
1947 b | 0
1947 b | 0
1948 1 files changed, 0 insertions(+), 0 deletions(-)
1948 1 files changed, 0 insertions(+), 0 deletions(-)
1949
1949
1950
1950
1951 $ hg log -G --git --patch --follow b
1951 $ hg log -G --git --patch --follow b
1952 o changeset: 1:216d4c92cf98
1952 o changeset: 1:216d4c92cf98
1953 | user: test
1953 | user: test
1954 | date: Thu Jan 01 00:00:00 1970 +0000
1954 | date: Thu Jan 01 00:00:00 1970 +0000
1955 | summary: copy a b
1955 | summary: copy a b
1956 |
1956 |
1957 | diff --git a/a b/b
1957 | diff --git a/a b/b
1958 | copy from a
1958 | copy from a
1959 | copy to b
1959 | copy to b
1960 |
1960 |
1961 o changeset: 0:f8035bb17114
1961 o changeset: 0:f8035bb17114
1962 user: test
1962 user: test
1963 date: Thu Jan 01 00:00:00 1970 +0000
1963 date: Thu Jan 01 00:00:00 1970 +0000
1964 summary: add a
1964 summary: add a
1965
1965
1966 diff --git a/a b/a
1966 diff --git a/a b/a
1967 new file mode 100644
1967 new file mode 100644
1968 --- /dev/null
1968 --- /dev/null
1969 +++ b/a
1969 +++ b/a
1970 @@ -0,0 +1,1 @@
1970 @@ -0,0 +1,1 @@
1971 +a
1971 +a
1972
1972
1973
1973
1974 $ hg log -G --git --stat --follow b
1974 $ hg log -G --git --stat --follow b
1975 o changeset: 1:216d4c92cf98
1975 o changeset: 1:216d4c92cf98
1976 | user: test
1976 | user: test
1977 | date: Thu Jan 01 00:00:00 1970 +0000
1977 | date: Thu Jan 01 00:00:00 1970 +0000
1978 | summary: copy a b
1978 | summary: copy a b
1979 |
1979 |
1980 | b | 0
1980 | b | 0
1981 | 1 files changed, 0 insertions(+), 0 deletions(-)
1981 | 1 files changed, 0 insertions(+), 0 deletions(-)
1982 |
1982 |
1983 o changeset: 0:f8035bb17114
1983 o changeset: 0:f8035bb17114
1984 user: test
1984 user: test
1985 date: Thu Jan 01 00:00:00 1970 +0000
1985 date: Thu Jan 01 00:00:00 1970 +0000
1986 summary: add a
1986 summary: add a
1987
1987
1988 a | 1 +
1988 a | 1 +
1989 1 files changed, 1 insertions(+), 0 deletions(-)
1989 1 files changed, 1 insertions(+), 0 deletions(-)
1990
1990
1991
1991
1992 $ hg up -q 6
1992 $ hg up -q 6
1993 $ hg log -G --git --patch --follow-first e
1993 $ hg log -G --git --patch --follow-first e
1994 @ changeset: 6:fc281d8ff18d
1994 @ changeset: 6:fc281d8ff18d
1995 |\ tag: tip
1995 |\ tag: tip
1996 | ~ parent: 5:99b31f1c2782
1996 | ~ parent: 5:99b31f1c2782
1997 | parent: 4:17d952250a9d
1997 | parent: 4:17d952250a9d
1998 | user: test
1998 | user: test
1999 | date: Thu Jan 01 00:00:00 1970 +0000
1999 | date: Thu Jan 01 00:00:00 1970 +0000
2000 | summary: merge 5 and 4
2000 | summary: merge 5 and 4
2001 |
2001 |
2002 | diff --git a/e b/e
2002 | diff --git a/e b/e
2003 | --- a/e
2003 | --- a/e
2004 | +++ b/e
2004 | +++ b/e
2005 | @@ -1,1 +1,1 @@
2005 | @@ -1,1 +1,1 @@
2006 | -ee
2006 | -ee
2007 | +merge
2007 | +merge
2008 |
2008 |
2009 o changeset: 5:99b31f1c2782
2009 o changeset: 5:99b31f1c2782
2010 | parent: 3:5918b8d165d1
2010 | parent: 3:5918b8d165d1
2011 ~ user: test
2011 ~ user: test
2012 date: Thu Jan 01 00:00:00 1970 +0000
2012 date: Thu Jan 01 00:00:00 1970 +0000
2013 summary: add another e
2013 summary: add another e
2014
2014
2015 diff --git a/e b/e
2015 diff --git a/e b/e
2016 new file mode 100644
2016 new file mode 100644
2017 --- /dev/null
2017 --- /dev/null
2018 +++ b/e
2018 +++ b/e
2019 @@ -0,0 +1,1 @@
2019 @@ -0,0 +1,1 @@
2020 +ee
2020 +ee
2021
2021
2022
2022
2023 Test old-style --rev
2023 Test old-style --rev
2024
2024
2025 $ hg tag 'foo-bar'
2025 $ hg tag 'foo-bar'
2026 $ testlog -r 'foo-bar'
2026 $ testlog -r 'foo-bar'
2027 ['foo-bar']
2027 ['foo-bar']
2028 []
2028 []
2029 <baseset [6]>
2029 <baseset [6]>
2030
2030
2031 Test --follow and forward --rev
2031 Test --follow and forward --rev
2032
2032
2033 $ hg up -q 6
2033 $ hg up -q 6
2034 $ echo g > g
2034 $ echo g > g
2035 $ hg ci -Am 'add g' g
2035 $ hg ci -Am 'add g' g
2036 created new head
2036 created new head
2037 $ hg up -q 2
2037 $ hg up -q 2
2038 $ hg log -G --template "{rev} {desc|firstline}\n"
2038 $ hg log -G --template "{rev} {desc|firstline}\n"
2039 o 8 add g
2039 o 8 add g
2040 |
2040 |
2041 | o 7 Added tag foo-bar for changeset fc281d8ff18d
2041 | o 7 Added tag foo-bar for changeset fc281d8ff18d
2042 |/
2042 |/
2043 o 6 merge 5 and 4
2043 o 6 merge 5 and 4
2044 |\
2044 |\
2045 | o 5 add another e
2045 | o 5 add another e
2046 | |
2046 | |
2047 o | 4 mv dir/b e
2047 o | 4 mv dir/b e
2048 |/
2048 |/
2049 o 3 mv a b; add d
2049 o 3 mv a b; add d
2050 |
2050 |
2051 @ 2 mv b dir/b
2051 @ 2 mv b dir/b
2052 |
2052 |
2053 o 1 copy a b
2053 o 1 copy a b
2054 |
2054 |
2055 o 0 add a
2055 o 0 add a
2056
2056
2057 $ hg archive -r 7 archive
2057 $ hg archive -r 7 archive
2058 $ grep changessincelatesttag archive/.hg_archival.txt
2058 $ grep changessincelatesttag archive/.hg_archival.txt
2059 changessincelatesttag: 1
2059 changessincelatesttag: 1
2060 $ rm -r archive
2060 $ rm -r archive
2061
2061
2062 changessincelatesttag with no prior tag
2062 changessincelatesttag with no prior tag
2063 $ hg archive -r 4 archive
2063 $ hg archive -r 4 archive
2064 $ grep changessincelatesttag archive/.hg_archival.txt
2064 $ grep changessincelatesttag archive/.hg_archival.txt
2065 changessincelatesttag: 5
2065 changessincelatesttag: 5
2066
2066
2067 $ hg export 'all()'
2067 $ hg export 'all()'
2068 # HG changeset patch
2068 # HG changeset patch
2069 # User test
2069 # User test
2070 # Date 0 0
2070 # Date 0 0
2071 # Thu Jan 01 00:00:00 1970 +0000
2071 # Thu Jan 01 00:00:00 1970 +0000
2072 # Node ID f8035bb17114da16215af3436ec5222428ace8ee
2072 # Node ID f8035bb17114da16215af3436ec5222428ace8ee
2073 # Parent 0000000000000000000000000000000000000000
2073 # Parent 0000000000000000000000000000000000000000
2074 add a
2074 add a
2075
2075
2076 diff -r 000000000000 -r f8035bb17114 a
2076 diff -r 000000000000 -r f8035bb17114 a
2077 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2077 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2078 +++ b/a Thu Jan 01 00:00:00 1970 +0000
2078 +++ b/a Thu Jan 01 00:00:00 1970 +0000
2079 @@ -0,0 +1,1 @@
2079 @@ -0,0 +1,1 @@
2080 +a
2080 +a
2081 diff -r 000000000000 -r f8035bb17114 aa
2081 diff -r 000000000000 -r f8035bb17114 aa
2082 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2082 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2083 +++ b/aa Thu Jan 01 00:00:00 1970 +0000
2083 +++ b/aa Thu Jan 01 00:00:00 1970 +0000
2084 @@ -0,0 +1,1 @@
2084 @@ -0,0 +1,1 @@
2085 +aa
2085 +aa
2086 diff -r 000000000000 -r f8035bb17114 f
2086 diff -r 000000000000 -r f8035bb17114 f
2087 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2087 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2088 +++ b/f Thu Jan 01 00:00:00 1970 +0000
2088 +++ b/f Thu Jan 01 00:00:00 1970 +0000
2089 @@ -0,0 +1,1 @@
2089 @@ -0,0 +1,1 @@
2090 +f
2090 +f
2091 # HG changeset patch
2091 # HG changeset patch
2092 # User test
2092 # User test
2093 # Date 0 0
2093 # Date 0 0
2094 # Thu Jan 01 00:00:00 1970 +0000
2094 # Thu Jan 01 00:00:00 1970 +0000
2095 # Node ID 216d4c92cf98ff2b4641d508b76b529f3d424c92
2095 # Node ID 216d4c92cf98ff2b4641d508b76b529f3d424c92
2096 # Parent f8035bb17114da16215af3436ec5222428ace8ee
2096 # Parent f8035bb17114da16215af3436ec5222428ace8ee
2097 copy a b
2097 copy a b
2098
2098
2099 diff -r f8035bb17114 -r 216d4c92cf98 b
2099 diff -r f8035bb17114 -r 216d4c92cf98 b
2100 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2100 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2101 +++ b/b Thu Jan 01 00:00:00 1970 +0000
2101 +++ b/b Thu Jan 01 00:00:00 1970 +0000
2102 @@ -0,0 +1,1 @@
2102 @@ -0,0 +1,1 @@
2103 +a
2103 +a
2104 diff -r f8035bb17114 -r 216d4c92cf98 g
2104 diff -r f8035bb17114 -r 216d4c92cf98 g
2105 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2105 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2106 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2106 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2107 @@ -0,0 +1,1 @@
2107 @@ -0,0 +1,1 @@
2108 +f
2108 +f
2109 # HG changeset patch
2109 # HG changeset patch
2110 # User test
2110 # User test
2111 # Date 0 0
2111 # Date 0 0
2112 # Thu Jan 01 00:00:00 1970 +0000
2112 # Thu Jan 01 00:00:00 1970 +0000
2113 # Node ID bb573313a9e8349099b6ea2b2fb1fc7f424446f3
2113 # Node ID bb573313a9e8349099b6ea2b2fb1fc7f424446f3
2114 # Parent 216d4c92cf98ff2b4641d508b76b529f3d424c92
2114 # Parent 216d4c92cf98ff2b4641d508b76b529f3d424c92
2115 mv b dir/b
2115 mv b dir/b
2116
2116
2117 diff -r 216d4c92cf98 -r bb573313a9e8 b
2117 diff -r 216d4c92cf98 -r bb573313a9e8 b
2118 --- a/b Thu Jan 01 00:00:00 1970 +0000
2118 --- a/b Thu Jan 01 00:00:00 1970 +0000
2119 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2119 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2120 @@ -1,1 +0,0 @@
2120 @@ -1,1 +0,0 @@
2121 -a
2121 -a
2122 diff -r 216d4c92cf98 -r bb573313a9e8 dir/b
2122 diff -r 216d4c92cf98 -r bb573313a9e8 dir/b
2123 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2123 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2124 +++ b/dir/b Thu Jan 01 00:00:00 1970 +0000
2124 +++ b/dir/b Thu Jan 01 00:00:00 1970 +0000
2125 @@ -0,0 +1,1 @@
2125 @@ -0,0 +1,1 @@
2126 +a
2126 +a
2127 diff -r 216d4c92cf98 -r bb573313a9e8 f
2127 diff -r 216d4c92cf98 -r bb573313a9e8 f
2128 --- a/f Thu Jan 01 00:00:00 1970 +0000
2128 --- a/f Thu Jan 01 00:00:00 1970 +0000
2129 +++ b/f Thu Jan 01 00:00:00 1970 +0000
2129 +++ b/f Thu Jan 01 00:00:00 1970 +0000
2130 @@ -1,1 +1,2 @@
2130 @@ -1,1 +1,2 @@
2131 f
2131 f
2132 +f
2132 +f
2133 diff -r 216d4c92cf98 -r bb573313a9e8 g
2133 diff -r 216d4c92cf98 -r bb573313a9e8 g
2134 --- a/g Thu Jan 01 00:00:00 1970 +0000
2134 --- a/g Thu Jan 01 00:00:00 1970 +0000
2135 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2135 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2136 @@ -1,1 +1,2 @@
2136 @@ -1,1 +1,2 @@
2137 f
2137 f
2138 +g
2138 +g
2139 # HG changeset patch
2139 # HG changeset patch
2140 # User test
2140 # User test
2141 # Date 0 0
2141 # Date 0 0
2142 # Thu Jan 01 00:00:00 1970 +0000
2142 # Thu Jan 01 00:00:00 1970 +0000
2143 # Node ID 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2143 # Node ID 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2144 # Parent bb573313a9e8349099b6ea2b2fb1fc7f424446f3
2144 # Parent bb573313a9e8349099b6ea2b2fb1fc7f424446f3
2145 mv a b; add d
2145 mv a b; add d
2146
2146
2147 diff -r bb573313a9e8 -r 5918b8d165d1 a
2147 diff -r bb573313a9e8 -r 5918b8d165d1 a
2148 --- a/a Thu Jan 01 00:00:00 1970 +0000
2148 --- a/a Thu Jan 01 00:00:00 1970 +0000
2149 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2149 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2150 @@ -1,1 +0,0 @@
2150 @@ -1,1 +0,0 @@
2151 -a
2151 -a
2152 diff -r bb573313a9e8 -r 5918b8d165d1 b
2152 diff -r bb573313a9e8 -r 5918b8d165d1 b
2153 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2153 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2154 +++ b/b Thu Jan 01 00:00:00 1970 +0000
2154 +++ b/b Thu Jan 01 00:00:00 1970 +0000
2155 @@ -0,0 +1,1 @@
2155 @@ -0,0 +1,1 @@
2156 +a
2156 +a
2157 diff -r bb573313a9e8 -r 5918b8d165d1 d
2157 diff -r bb573313a9e8 -r 5918b8d165d1 d
2158 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2158 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2159 +++ b/d Thu Jan 01 00:00:00 1970 +0000
2159 +++ b/d Thu Jan 01 00:00:00 1970 +0000
2160 @@ -0,0 +1,1 @@
2160 @@ -0,0 +1,1 @@
2161 +a
2161 +a
2162 diff -r bb573313a9e8 -r 5918b8d165d1 g
2162 diff -r bb573313a9e8 -r 5918b8d165d1 g
2163 --- a/g Thu Jan 01 00:00:00 1970 +0000
2163 --- a/g Thu Jan 01 00:00:00 1970 +0000
2164 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2164 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2165 @@ -1,2 +1,2 @@
2165 @@ -1,2 +1,2 @@
2166 f
2166 f
2167 -g
2167 -g
2168 +f
2168 +f
2169 # HG changeset patch
2169 # HG changeset patch
2170 # User test
2170 # User test
2171 # Date 0 0
2171 # Date 0 0
2172 # Thu Jan 01 00:00:00 1970 +0000
2172 # Thu Jan 01 00:00:00 1970 +0000
2173 # Node ID 17d952250a9d03cc3dc77b199ab60e959b9b0260
2173 # Node ID 17d952250a9d03cc3dc77b199ab60e959b9b0260
2174 # Parent 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2174 # Parent 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2175 mv dir/b e
2175 mv dir/b e
2176
2176
2177 diff -r 5918b8d165d1 -r 17d952250a9d dir/b
2177 diff -r 5918b8d165d1 -r 17d952250a9d dir/b
2178 --- a/dir/b Thu Jan 01 00:00:00 1970 +0000
2178 --- a/dir/b Thu Jan 01 00:00:00 1970 +0000
2179 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2179 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2180 @@ -1,1 +0,0 @@
2180 @@ -1,1 +0,0 @@
2181 -a
2181 -a
2182 diff -r 5918b8d165d1 -r 17d952250a9d e
2182 diff -r 5918b8d165d1 -r 17d952250a9d e
2183 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2183 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2184 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2184 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2185 @@ -0,0 +1,1 @@
2185 @@ -0,0 +1,1 @@
2186 +a
2186 +a
2187 # HG changeset patch
2187 # HG changeset patch
2188 # User test
2188 # User test
2189 # Date 0 0
2189 # Date 0 0
2190 # Thu Jan 01 00:00:00 1970 +0000
2190 # Thu Jan 01 00:00:00 1970 +0000
2191 # Node ID 99b31f1c2782e2deb1723cef08930f70fc84b37b
2191 # Node ID 99b31f1c2782e2deb1723cef08930f70fc84b37b
2192 # Parent 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2192 # Parent 5918b8d165d1364e78a66d02e66caa0133c5d1ed
2193 add another e
2193 add another e
2194
2194
2195 diff -r 5918b8d165d1 -r 99b31f1c2782 e
2195 diff -r 5918b8d165d1 -r 99b31f1c2782 e
2196 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2196 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2197 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2197 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2198 @@ -0,0 +1,1 @@
2198 @@ -0,0 +1,1 @@
2199 +ee
2199 +ee
2200 # HG changeset patch
2200 # HG changeset patch
2201 # User test
2201 # User test
2202 # Date 0 0
2202 # Date 0 0
2203 # Thu Jan 01 00:00:00 1970 +0000
2203 # Thu Jan 01 00:00:00 1970 +0000
2204 # Node ID fc281d8ff18d999ad6497b3d27390bcd695dcc73
2204 # Node ID fc281d8ff18d999ad6497b3d27390bcd695dcc73
2205 # Parent 99b31f1c2782e2deb1723cef08930f70fc84b37b
2205 # Parent 99b31f1c2782e2deb1723cef08930f70fc84b37b
2206 # Parent 17d952250a9d03cc3dc77b199ab60e959b9b0260
2206 # Parent 17d952250a9d03cc3dc77b199ab60e959b9b0260
2207 merge 5 and 4
2207 merge 5 and 4
2208
2208
2209 diff -r 99b31f1c2782 -r fc281d8ff18d dir/b
2209 diff -r 99b31f1c2782 -r fc281d8ff18d dir/b
2210 --- a/dir/b Thu Jan 01 00:00:00 1970 +0000
2210 --- a/dir/b Thu Jan 01 00:00:00 1970 +0000
2211 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2211 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2212 @@ -1,1 +0,0 @@
2212 @@ -1,1 +0,0 @@
2213 -a
2213 -a
2214 diff -r 99b31f1c2782 -r fc281d8ff18d e
2214 diff -r 99b31f1c2782 -r fc281d8ff18d e
2215 --- a/e Thu Jan 01 00:00:00 1970 +0000
2215 --- a/e Thu Jan 01 00:00:00 1970 +0000
2216 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2216 +++ b/e Thu Jan 01 00:00:00 1970 +0000
2217 @@ -1,1 +1,1 @@
2217 @@ -1,1 +1,1 @@
2218 -ee
2218 -ee
2219 +merge
2219 +merge
2220 # HG changeset patch
2220 # HG changeset patch
2221 # User test
2221 # User test
2222 # Date 0 0
2222 # Date 0 0
2223 # Thu Jan 01 00:00:00 1970 +0000
2223 # Thu Jan 01 00:00:00 1970 +0000
2224 # Node ID 02dbb8e276b8ab7abfd07cab50c901647e75c2dd
2224 # Node ID 02dbb8e276b8ab7abfd07cab50c901647e75c2dd
2225 # Parent fc281d8ff18d999ad6497b3d27390bcd695dcc73
2225 # Parent fc281d8ff18d999ad6497b3d27390bcd695dcc73
2226 Added tag foo-bar for changeset fc281d8ff18d
2226 Added tag foo-bar for changeset fc281d8ff18d
2227
2227
2228 diff -r fc281d8ff18d -r 02dbb8e276b8 .hgtags
2228 diff -r fc281d8ff18d -r 02dbb8e276b8 .hgtags
2229 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2229 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2230 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
2230 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
2231 @@ -0,0 +1,1 @@
2231 @@ -0,0 +1,1 @@
2232 +fc281d8ff18d999ad6497b3d27390bcd695dcc73 foo-bar
2232 +fc281d8ff18d999ad6497b3d27390bcd695dcc73 foo-bar
2233 # HG changeset patch
2233 # HG changeset patch
2234 # User test
2234 # User test
2235 # Date 0 0
2235 # Date 0 0
2236 # Thu Jan 01 00:00:00 1970 +0000
2236 # Thu Jan 01 00:00:00 1970 +0000
2237 # Node ID 24c2e826ddebf80f9dcd60b856bdb8e6715c5449
2237 # Node ID 24c2e826ddebf80f9dcd60b856bdb8e6715c5449
2238 # Parent fc281d8ff18d999ad6497b3d27390bcd695dcc73
2238 # Parent fc281d8ff18d999ad6497b3d27390bcd695dcc73
2239 add g
2239 add g
2240
2240
2241 diff -r fc281d8ff18d -r 24c2e826ddeb g
2241 diff -r fc281d8ff18d -r 24c2e826ddeb g
2242 --- a/g Thu Jan 01 00:00:00 1970 +0000
2242 --- a/g Thu Jan 01 00:00:00 1970 +0000
2243 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2243 +++ b/g Thu Jan 01 00:00:00 1970 +0000
2244 @@ -1,2 +1,1 @@
2244 @@ -1,2 +1,1 @@
2245 -f
2245 -f
2246 -f
2246 -f
2247 +g
2247 +g
2248 $ testlog --follow -r6 -r8 -r5 -r7 -r4
2248 $ testlog --follow -r6 -r8 -r5 -r7 -r4
2249 ['6', '8', '5', '7', '4']
2249 ['6', '8', '5', '7', '4']
2250 []
2250 []
2251 <generatorsetdesc->
2251 <generatorsetdesc->
2252
2252
2253 Test --follow-first and forward --rev
2253 Test --follow-first and forward --rev
2254
2254
2255 $ testlog --follow-first -r6 -r8 -r5 -r7 -r4
2255 $ testlog --follow-first -r6 -r8 -r5 -r7 -r4
2256 ['6', '8', '5', '7', '4']
2256 ['6', '8', '5', '7', '4']
2257 []
2257 []
2258 <generatorsetdesc->
2258 <generatorsetdesc->
2259
2259
2260 Test --follow and backward --rev
2260 Test --follow and backward --rev
2261
2261
2262 $ testlog --follow -r6 -r5 -r7 -r8 -r4
2262 $ testlog --follow -r6 -r5 -r7 -r8 -r4
2263 ['6', '5', '7', '8', '4']
2263 ['6', '5', '7', '8', '4']
2264 []
2264 []
2265 <generatorsetdesc->
2265 <generatorsetdesc->
2266
2266
2267 Test --follow-first and backward --rev
2267 Test --follow-first and backward --rev
2268
2268
2269 $ testlog --follow-first -r6 -r5 -r7 -r8 -r4
2269 $ testlog --follow-first -r6 -r5 -r7 -r8 -r4
2270 ['6', '5', '7', '8', '4']
2270 ['6', '5', '7', '8', '4']
2271 []
2271 []
2272 <generatorsetdesc->
2272 <generatorsetdesc->
2273
2273
2274 Test --follow with --rev of graphlog extension
2274 Test --follow with --rev of graphlog extension
2275
2275
2276 $ hg --config extensions.graphlog= glog -qfr1
2276 $ hg --config extensions.graphlog= glog -qfr1
2277 o 1:216d4c92cf98
2277 o 1:216d4c92cf98
2278 |
2278 |
2279 o 0:f8035bb17114
2279 o 0:f8035bb17114
2280
2280
2281
2281
2282 Test subdir
2282 Test subdir
2283
2283
2284 $ hg up -q 3
2284 $ hg up -q 3
2285 $ cd dir
2285 $ cd dir
2286 $ testlog .
2286 $ testlog .
2287 []
2287 []
2288 (func
2288 (func
2289 (symbol '_matchfiles')
2289 (symbol '_matchfiles')
2290 (list
2290 (list
2291 (string 'r:')
2291 (string 'r:')
2292 (string 'd:relpath')
2292 (string 'd:relpath')
2293 (string 'p:.')))
2293 (string 'p:.')))
2294 <filteredset
2294 <filteredset
2295 <spanset- 0:9>,
2295 <spanset- 0:9>,
2296 <matchfiles patterns=['.'], include=[] exclude=[], default='relpath', rev=2147483647>>
2296 <matchfiles patterns=['.'], include=[] exclude=[], default='relpath', rev=2147483647>>
2297 $ testlog ../b
2297 $ testlog ../b
2298 []
2298 []
2299 (func
2299 (func
2300 (symbol 'filelog')
2300 (symbol 'filelog')
2301 (string '../b'))
2301 (string '../b'))
2302 <filteredset
2302 <filteredset
2303 <spanset- 0:9>, set([1])>
2303 <spanset- 0:9>, set([1])>
2304 $ testlog -f ../b
2304 $ testlog -f ../b
2305 []
2305 []
2306 []
2306 []
2307 <generatorsetdesc->
2307 <generatorsetdesc->
2308 $ cd ..
2308 $ cd ..
2309
2309
2310 Test --hidden
2310 Test --hidden
2311 (enable obsolete)
2311 (enable obsolete)
2312
2312
2313 $ cat >> $HGRCPATH << EOF
2313 $ cat >> $HGRCPATH << EOF
2314 > [experimental]
2314 > [experimental]
2315 > evolution.createmarkers=True
2315 > evolution.createmarkers=True
2316 > EOF
2316 > EOF
2317
2317
2318 $ hg debugobsolete `hg id --debug -i -r 8`
2318 $ hg debugobsolete `hg id --debug -i -r 8`
2319 obsoleted 1 changesets
2319 obsoleted 1 changesets
2320 $ testlog
2320 $ testlog
2321 []
2321 []
2322 []
2322 []
2323 <spanset- 0:9>
2323 <spanset- 0:9>
2324 $ testlog --hidden
2324 $ testlog --hidden
2325 []
2325 []
2326 []
2326 []
2327 <spanset- 0:9>
2327 <spanset- 0:9>
2328 $ hg log -G --template '{rev} {desc}\n'
2328 $ hg log -G --template '{rev} {desc}\n'
2329 o 7 Added tag foo-bar for changeset fc281d8ff18d
2329 o 7 Added tag foo-bar for changeset fc281d8ff18d
2330 |
2330 |
2331 o 6 merge 5 and 4
2331 o 6 merge 5 and 4
2332 |\
2332 |\
2333 | o 5 add another e
2333 | o 5 add another e
2334 | |
2334 | |
2335 o | 4 mv dir/b e
2335 o | 4 mv dir/b e
2336 |/
2336 |/
2337 @ 3 mv a b; add d
2337 @ 3 mv a b; add d
2338 |
2338 |
2339 o 2 mv b dir/b
2339 o 2 mv b dir/b
2340 |
2340 |
2341 o 1 copy a b
2341 o 1 copy a b
2342 |
2342 |
2343 o 0 add a
2343 o 0 add a
2344
2344
2345
2345
2346 A template without trailing newline should do something sane
2346 A template without trailing newline should do something sane
2347
2347
2348 $ hg log -G -r ::2 --template '{rev} {desc}'
2348 $ hg log -G -r ::2 --template '{rev} {desc}'
2349 o 2 mv b dir/b
2349 o 2 mv b dir/b
2350 |
2350 |
2351 o 1 copy a b
2351 o 1 copy a b
2352 |
2352 |
2353 o 0 add a
2353 o 0 add a
2354
2354
2355
2355
2356 Extra newlines must be preserved
2356 Extra newlines must be preserved
2357
2357
2358 $ hg log -G -r ::2 --template '\n{rev} {desc}\n\n'
2358 $ hg log -G -r ::2 --template '\n{rev} {desc}\n\n'
2359 o
2359 o
2360 | 2 mv b dir/b
2360 | 2 mv b dir/b
2361 |
2361 |
2362 o
2362 o
2363 | 1 copy a b
2363 | 1 copy a b
2364 |
2364 |
2365 o
2365 o
2366 0 add a
2366 0 add a
2367
2367
2368
2368
2369 The almost-empty template should do something sane too ...
2369 The almost-empty template should do something sane too ...
2370
2370
2371 $ hg log -G -r ::2 --template '\n'
2371 $ hg log -G -r ::2 --template '\n'
2372 o
2372 o
2373 |
2373 |
2374 o
2374 o
2375 |
2375 |
2376 o
2376 o
2377
2377
2378
2378
2379 issue3772
2379 issue3772
2380
2380
2381 $ hg log -G -r :null
2381 $ hg log -G -r :null
2382 o changeset: 0:f8035bb17114
2382 o changeset: 0:f8035bb17114
2383 | user: test
2383 | user: test
2384 | date: Thu Jan 01 00:00:00 1970 +0000
2384 | date: Thu Jan 01 00:00:00 1970 +0000
2385 | summary: add a
2385 | summary: add a
2386 |
2386 |
2387 o changeset: -1:000000000000
2387 o changeset: -1:000000000000
2388 user:
2388 user:
2389 date: Thu Jan 01 00:00:00 1970 +0000
2389 date: Thu Jan 01 00:00:00 1970 +0000
2390
2390
2391 $ hg log -G -r null:null
2391 $ hg log -G -r null:null
2392 o changeset: -1:000000000000
2392 o changeset: -1:000000000000
2393 user:
2393 user:
2394 date: Thu Jan 01 00:00:00 1970 +0000
2394 date: Thu Jan 01 00:00:00 1970 +0000
2395
2395
2396
2396
2397 should not draw line down to null due to the magic of fullreposet
2397 should not draw line down to null due to the magic of fullreposet
2398
2398
2399 $ hg log -G -r 'all()' | tail -6
2399 $ hg log -G -r 'all()' | tail -6
2400 |
2400 |
2401 o changeset: 0:f8035bb17114
2401 o changeset: 0:f8035bb17114
2402 user: test
2402 user: test
2403 date: Thu Jan 01 00:00:00 1970 +0000
2403 date: Thu Jan 01 00:00:00 1970 +0000
2404 summary: add a
2404 summary: add a
2405
2405
2406
2406
2407 $ hg log -G -r 'branch(default)' | tail -6
2407 $ hg log -G -r 'branch(default)' | tail -6
2408 |
2408 |
2409 o changeset: 0:f8035bb17114
2409 o changeset: 0:f8035bb17114
2410 user: test
2410 user: test
2411 date: Thu Jan 01 00:00:00 1970 +0000
2411 date: Thu Jan 01 00:00:00 1970 +0000
2412 summary: add a
2412 summary: add a
2413
2413
2414
2414
2415 working-directory revision
2415 working-directory revision
2416
2416
2417 $ hg log -G -qr '. + wdir()'
2417 $ hg log -G -qr '. + wdir()'
2418 o 2147483647:ffffffffffff
2418 o 2147483647:ffffffffffff
2419 |
2419 |
2420 @ 3:5918b8d165d1
2420 @ 3:5918b8d165d1
2421 |
2421 |
2422 ~
2422 ~
2423
2423
2424 node template with changeset_printer:
2424 node template with changeset_printer:
2425
2425
2426 $ hg log -Gqr 5:7 --config ui.graphnodetemplate='"{rev}"'
2426 $ hg log -Gqr 5:7 --config ui.graphnodetemplate='"{rev}"'
2427 7 7:02dbb8e276b8
2427 7 7:02dbb8e276b8
2428 |
2428 |
2429 6 6:fc281d8ff18d
2429 6 6:fc281d8ff18d
2430 |\
2430 |\
2431 | ~
2431 | ~
2432 5 5:99b31f1c2782
2432 5 5:99b31f1c2782
2433 |
2433 |
2434 ~
2434 ~
2435
2435
2436 node template with changeset_templater (shared cache variable):
2436 node template with changeset_templater (shared cache variable):
2437
2437
2438 $ hg log -Gr 5:7 -T '{latesttag % "{rev} {tag}+{distance}"}\n' \
2438 $ hg log -Gr 5:7 -T '{latesttag % "{rev} {tag}+{distance}"}\n' \
2439 > --config ui.graphnodetemplate='{ifeq(latesttagdistance, 0, "#", graphnode)}'
2439 > --config ui.graphnodetemplate='{ifeq(latesttagdistance, 0, "#", graphnode)}'
2440 o 7 foo-bar+1
2440 o 7 foo-bar+1
2441 |
2441 |
2442 # 6 foo-bar+0
2442 # 6 foo-bar+0
2443 |\
2443 |\
2444 | ~
2444 | ~
2445 o 5 null+5
2445 o 5 null+5
2446 |
2446 |
2447 ~
2447 ~
2448
2448
2449 label() should just work in node template:
2449 label() should just work in node template:
2450
2450
2451 $ hg log -Gqr 7 --config extensions.color= --color=debug \
2451 $ hg log -Gqr 7 --config extensions.color= --color=debug \
2452 > --config ui.graphnodetemplate='{label("branch.{branch}", rev)}'
2452 > --config ui.graphnodetemplate='{label("branch.{branch}", rev)}'
2453 [branch.default|7] [log.node|7:02dbb8e276b8]
2453 [branch.default|7] [log.node|7:02dbb8e276b8]
2454 |
2454 |
2455 ~
2455 ~
2456
2456
2457 $ cd ..
2457 $ cd ..
2458
2458
2459 change graph edge styling
2459 change graph edge styling
2460
2460
2461 $ cd repo
2461 $ cd repo
2462 $ cat << EOF >> $HGRCPATH
2462 $ cat << EOF >> $HGRCPATH
2463 > [experimental]
2463 > [experimental]
2464 > graphstyle.parent = |
2464 > graphstyle.parent = |
2465 > graphstyle.grandparent = :
2465 > graphstyle.grandparent = :
2466 > graphstyle.missing =
2466 > graphstyle.missing =
2467 > EOF
2467 > EOF
2468 $ hg log -G -r 'file("a")' -m
2468 $ hg log -G -r 'file("a")' -m
2469 @ changeset: 36:08a19a744424
2469 @ changeset: 36:08a19a744424
2470 : branch: branch
2470 : branch: branch
2471 : tag: tip
2471 : tag: tip
2472 : parent: 35:9159c3644c5e
2472 : parent: 35:9159c3644c5e
2473 : parent: 35:9159c3644c5e
2473 : parent: 35:9159c3644c5e
2474 : user: test
2474 : user: test
2475 : date: Thu Jan 01 00:00:36 1970 +0000
2475 : date: Thu Jan 01 00:00:36 1970 +0000
2476 : summary: (36) buggy merge: identical parents
2476 : summary: (36) buggy merge: identical parents
2477 :
2477 :
2478 o changeset: 32:d06dffa21a31
2478 o changeset: 32:d06dffa21a31
2479 |\ parent: 27:886ed638191b
2479 |\ parent: 27:886ed638191b
2480 | : parent: 31:621d83e11f67
2480 | : parent: 31:621d83e11f67
2481 | : user: test
2481 | : user: test
2482 | : date: Thu Jan 01 00:00:32 1970 +0000
2482 | : date: Thu Jan 01 00:00:32 1970 +0000
2483 | : summary: (32) expand
2483 | : summary: (32) expand
2484 | :
2484 | :
2485 o : changeset: 31:621d83e11f67
2485 o : changeset: 31:621d83e11f67
2486 |\: parent: 21:d42a756af44d
2486 |\: parent: 21:d42a756af44d
2487 | : parent: 30:6e11cd4b648f
2487 | : parent: 30:6e11cd4b648f
2488 | : user: test
2488 | : user: test
2489 | : date: Thu Jan 01 00:00:31 1970 +0000
2489 | : date: Thu Jan 01 00:00:31 1970 +0000
2490 | : summary: (31) expand
2490 | : summary: (31) expand
2491 | :
2491 | :
2492 o : changeset: 30:6e11cd4b648f
2492 o : changeset: 30:6e11cd4b648f
2493 |\ \ parent: 28:44ecd0b9ae99
2493 |\ \ parent: 28:44ecd0b9ae99
2494 | ~ : parent: 29:cd9bb2be7593
2494 | ~ : parent: 29:cd9bb2be7593
2495 | : user: test
2495 | : user: test
2496 | : date: Thu Jan 01 00:00:30 1970 +0000
2496 | : date: Thu Jan 01 00:00:30 1970 +0000
2497 | : summary: (30) expand
2497 | : summary: (30) expand
2498 | /
2498 | /
2499 o : changeset: 28:44ecd0b9ae99
2499 o : changeset: 28:44ecd0b9ae99
2500 |\ \ parent: 1:6db2ef61d156
2500 |\ \ parent: 1:6db2ef61d156
2501 | ~ : parent: 26:7f25b6c2f0b9
2501 | ~ : parent: 26:7f25b6c2f0b9
2502 | : user: test
2502 | : user: test
2503 | : date: Thu Jan 01 00:00:28 1970 +0000
2503 | : date: Thu Jan 01 00:00:28 1970 +0000
2504 | : summary: (28) merge zero known
2504 | : summary: (28) merge zero known
2505 | /
2505 | /
2506 o : changeset: 26:7f25b6c2f0b9
2506 o : changeset: 26:7f25b6c2f0b9
2507 |\ \ parent: 18:1aa84d96232a
2507 |\ \ parent: 18:1aa84d96232a
2508 | | : parent: 25:91da8ed57247
2508 | | : parent: 25:91da8ed57247
2509 | | : user: test
2509 | | : user: test
2510 | | : date: Thu Jan 01 00:00:26 1970 +0000
2510 | | : date: Thu Jan 01 00:00:26 1970 +0000
2511 | | : summary: (26) merge one known; far right
2511 | | : summary: (26) merge one known; far right
2512 | | :
2512 | | :
2513 | o : changeset: 25:91da8ed57247
2513 | o : changeset: 25:91da8ed57247
2514 | |\: parent: 21:d42a756af44d
2514 | |\: parent: 21:d42a756af44d
2515 | | : parent: 24:a9c19a3d96b7
2515 | | : parent: 24:a9c19a3d96b7
2516 | | : user: test
2516 | | : user: test
2517 | | : date: Thu Jan 01 00:00:25 1970 +0000
2517 | | : date: Thu Jan 01 00:00:25 1970 +0000
2518 | | : summary: (25) merge one known; far left
2518 | | : summary: (25) merge one known; far left
2519 | | :
2519 | | :
2520 | o : changeset: 24:a9c19a3d96b7
2520 | o : changeset: 24:a9c19a3d96b7
2521 | |\ \ parent: 0:e6eb3150255d
2521 | |\ \ parent: 0:e6eb3150255d
2522 | | ~ : parent: 23:a01cddf0766d
2522 | | ~ : parent: 23:a01cddf0766d
2523 | | : user: test
2523 | | : user: test
2524 | | : date: Thu Jan 01 00:00:24 1970 +0000
2524 | | : date: Thu Jan 01 00:00:24 1970 +0000
2525 | | : summary: (24) merge one known; immediate right
2525 | | : summary: (24) merge one known; immediate right
2526 | | /
2526 | | /
2527 | o : changeset: 23:a01cddf0766d
2527 | o : changeset: 23:a01cddf0766d
2528 | |\ \ parent: 1:6db2ef61d156
2528 | |\ \ parent: 1:6db2ef61d156
2529 | | ~ : parent: 22:e0d9cccacb5d
2529 | | ~ : parent: 22:e0d9cccacb5d
2530 | | : user: test
2530 | | : user: test
2531 | | : date: Thu Jan 01 00:00:23 1970 +0000
2531 | | : date: Thu Jan 01 00:00:23 1970 +0000
2532 | | : summary: (23) merge one known; immediate left
2532 | | : summary: (23) merge one known; immediate left
2533 | | /
2533 | | /
2534 | o : changeset: 22:e0d9cccacb5d
2534 | o : changeset: 22:e0d9cccacb5d
2535 |/:/ parent: 18:1aa84d96232a
2535 |/:/ parent: 18:1aa84d96232a
2536 | : parent: 21:d42a756af44d
2536 | : parent: 21:d42a756af44d
2537 | : user: test
2537 | : user: test
2538 | : date: Thu Jan 01 00:00:22 1970 +0000
2538 | : date: Thu Jan 01 00:00:22 1970 +0000
2539 | : summary: (22) merge two known; one far left, one far right
2539 | : summary: (22) merge two known; one far left, one far right
2540 | :
2540 | :
2541 | o changeset: 21:d42a756af44d
2541 | o changeset: 21:d42a756af44d
2542 | |\ parent: 19:31ddc2c1573b
2542 | |\ parent: 19:31ddc2c1573b
2543 | | | parent: 20:d30ed6450e32
2543 | | | parent: 20:d30ed6450e32
2544 | | | user: test
2544 | | | user: test
2545 | | | date: Thu Jan 01 00:00:21 1970 +0000
2545 | | | date: Thu Jan 01 00:00:21 1970 +0000
2546 | | | summary: (21) expand
2546 | | | summary: (21) expand
2547 | | |
2547 | | |
2548 +---o changeset: 20:d30ed6450e32
2548 +---o changeset: 20:d30ed6450e32
2549 | | | parent: 0:e6eb3150255d
2549 | | | parent: 0:e6eb3150255d
2550 | | ~ parent: 18:1aa84d96232a
2550 | | ~ parent: 18:1aa84d96232a
2551 | | user: test
2551 | | user: test
2552 | | date: Thu Jan 01 00:00:20 1970 +0000
2552 | | date: Thu Jan 01 00:00:20 1970 +0000
2553 | | summary: (20) merge two known; two far right
2553 | | summary: (20) merge two known; two far right
2554 | |
2554 | |
2555 | o changeset: 19:31ddc2c1573b
2555 | o changeset: 19:31ddc2c1573b
2556 | |\ parent: 15:1dda3f72782d
2556 | |\ parent: 15:1dda3f72782d
2557 | | | parent: 17:44765d7c06e0
2557 | | | parent: 17:44765d7c06e0
2558 | | | user: test
2558 | | | user: test
2559 | | | date: Thu Jan 01 00:00:19 1970 +0000
2559 | | | date: Thu Jan 01 00:00:19 1970 +0000
2560 | | | summary: (19) expand
2560 | | | summary: (19) expand
2561 | | |
2561 | | |
2562 o | | changeset: 18:1aa84d96232a
2562 o | | changeset: 18:1aa84d96232a
2563 |\| | parent: 1:6db2ef61d156
2563 |\| | parent: 1:6db2ef61d156
2564 ~ | | parent: 15:1dda3f72782d
2564 ~ | | parent: 15:1dda3f72782d
2565 | | user: test
2565 | | user: test
2566 | | date: Thu Jan 01 00:00:18 1970 +0000
2566 | | date: Thu Jan 01 00:00:18 1970 +0000
2567 | | summary: (18) merge two known; two far left
2567 | | summary: (18) merge two known; two far left
2568 / /
2568 / /
2569 | o changeset: 17:44765d7c06e0
2569 | o changeset: 17:44765d7c06e0
2570 | |\ parent: 12:86b91144a6e9
2570 | |\ parent: 12:86b91144a6e9
2571 | | | parent: 16:3677d192927d
2571 | | | parent: 16:3677d192927d
2572 | | | user: test
2572 | | | user: test
2573 | | | date: Thu Jan 01 00:00:17 1970 +0000
2573 | | | date: Thu Jan 01 00:00:17 1970 +0000
2574 | | | summary: (17) expand
2574 | | | summary: (17) expand
2575 | | |
2575 | | |
2576 | | o changeset: 16:3677d192927d
2576 | | o changeset: 16:3677d192927d
2577 | | |\ parent: 0:e6eb3150255d
2577 | | |\ parent: 0:e6eb3150255d
2578 | | ~ ~ parent: 1:6db2ef61d156
2578 | | ~ ~ parent: 1:6db2ef61d156
2579 | | user: test
2579 | | user: test
2580 | | date: Thu Jan 01 00:00:16 1970 +0000
2580 | | date: Thu Jan 01 00:00:16 1970 +0000
2581 | | summary: (16) merge two known; one immediate right, one near right
2581 | | summary: (16) merge two known; one immediate right, one near right
2582 | |
2582 | |
2583 o | changeset: 15:1dda3f72782d
2583 o | changeset: 15:1dda3f72782d
2584 |\ \ parent: 13:22d8966a97e3
2584 |\ \ parent: 13:22d8966a97e3
2585 | | | parent: 14:8eac370358ef
2585 | | | parent: 14:8eac370358ef
2586 | | | user: test
2586 | | | user: test
2587 | | | date: Thu Jan 01 00:00:15 1970 +0000
2587 | | | date: Thu Jan 01 00:00:15 1970 +0000
2588 | | | summary: (15) expand
2588 | | | summary: (15) expand
2589 | | |
2589 | | |
2590 | o | changeset: 14:8eac370358ef
2590 | o | changeset: 14:8eac370358ef
2591 | |\| parent: 0:e6eb3150255d
2591 | |\| parent: 0:e6eb3150255d
2592 | ~ | parent: 12:86b91144a6e9
2592 | ~ | parent: 12:86b91144a6e9
2593 | | user: test
2593 | | user: test
2594 | | date: Thu Jan 01 00:00:14 1970 +0000
2594 | | date: Thu Jan 01 00:00:14 1970 +0000
2595 | | summary: (14) merge two known; one immediate right, one far right
2595 | | summary: (14) merge two known; one immediate right, one far right
2596 | /
2596 | /
2597 o | changeset: 13:22d8966a97e3
2597 o | changeset: 13:22d8966a97e3
2598 |\ \ parent: 9:7010c0af0a35
2598 |\ \ parent: 9:7010c0af0a35
2599 | | | parent: 11:832d76e6bdf2
2599 | | | parent: 11:832d76e6bdf2
2600 | | | user: test
2600 | | | user: test
2601 | | | date: Thu Jan 01 00:00:13 1970 +0000
2601 | | | date: Thu Jan 01 00:00:13 1970 +0000
2602 | | | summary: (13) expand
2602 | | | summary: (13) expand
2603 | | |
2603 | | |
2604 +---o changeset: 12:86b91144a6e9
2604 +---o changeset: 12:86b91144a6e9
2605 | | | parent: 1:6db2ef61d156
2605 | | | parent: 1:6db2ef61d156
2606 | | ~ parent: 9:7010c0af0a35
2606 | | ~ parent: 9:7010c0af0a35
2607 | | user: test
2607 | | user: test
2608 | | date: Thu Jan 01 00:00:12 1970 +0000
2608 | | date: Thu Jan 01 00:00:12 1970 +0000
2609 | | summary: (12) merge two known; one immediate right, one far left
2609 | | summary: (12) merge two known; one immediate right, one far left
2610 | |
2610 | |
2611 | o changeset: 11:832d76e6bdf2
2611 | o changeset: 11:832d76e6bdf2
2612 | |\ parent: 6:b105a072e251
2612 | |\ parent: 6:b105a072e251
2613 | | | parent: 10:74c64d036d72
2613 | | | parent: 10:74c64d036d72
2614 | | | user: test
2614 | | | user: test
2615 | | | date: Thu Jan 01 00:00:11 1970 +0000
2615 | | | date: Thu Jan 01 00:00:11 1970 +0000
2616 | | | summary: (11) expand
2616 | | | summary: (11) expand
2617 | | |
2617 | | |
2618 | | o changeset: 10:74c64d036d72
2618 | | o changeset: 10:74c64d036d72
2619 | |/| parent: 0:e6eb3150255d
2619 | |/| parent: 0:e6eb3150255d
2620 | | ~ parent: 6:b105a072e251
2620 | | ~ parent: 6:b105a072e251
2621 | | user: test
2621 | | user: test
2622 | | date: Thu Jan 01 00:00:10 1970 +0000
2622 | | date: Thu Jan 01 00:00:10 1970 +0000
2623 | | summary: (10) merge two known; one immediate left, one near right
2623 | | summary: (10) merge two known; one immediate left, one near right
2624 | |
2624 | |
2625 o | changeset: 9:7010c0af0a35
2625 o | changeset: 9:7010c0af0a35
2626 |\ \ parent: 7:b632bb1b1224
2626 |\ \ parent: 7:b632bb1b1224
2627 | | | parent: 8:7a0b11f71937
2627 | | | parent: 8:7a0b11f71937
2628 | | | user: test
2628 | | | user: test
2629 | | | date: Thu Jan 01 00:00:09 1970 +0000
2629 | | | date: Thu Jan 01 00:00:09 1970 +0000
2630 | | | summary: (9) expand
2630 | | | summary: (9) expand
2631 | | |
2631 | | |
2632 | o | changeset: 8:7a0b11f71937
2632 | o | changeset: 8:7a0b11f71937
2633 |/| | parent: 0:e6eb3150255d
2633 |/| | parent: 0:e6eb3150255d
2634 | ~ | parent: 7:b632bb1b1224
2634 | ~ | parent: 7:b632bb1b1224
2635 | | user: test
2635 | | user: test
2636 | | date: Thu Jan 01 00:00:08 1970 +0000
2636 | | date: Thu Jan 01 00:00:08 1970 +0000
2637 | | summary: (8) merge two known; one immediate left, one far right
2637 | | summary: (8) merge two known; one immediate left, one far right
2638 | /
2638 | /
2639 o | changeset: 7:b632bb1b1224
2639 o | changeset: 7:b632bb1b1224
2640 |\ \ parent: 2:3d9a33b8d1e1
2640 |\ \ parent: 2:3d9a33b8d1e1
2641 | ~ | parent: 5:4409d547b708
2641 | ~ | parent: 5:4409d547b708
2642 | | user: test
2642 | | user: test
2643 | | date: Thu Jan 01 00:00:07 1970 +0000
2643 | | date: Thu Jan 01 00:00:07 1970 +0000
2644 | | summary: (7) expand
2644 | | summary: (7) expand
2645 | /
2645 | /
2646 | o changeset: 6:b105a072e251
2646 | o changeset: 6:b105a072e251
2647 |/| parent: 2:3d9a33b8d1e1
2647 |/| parent: 2:3d9a33b8d1e1
2648 | ~ parent: 5:4409d547b708
2648 | ~ parent: 5:4409d547b708
2649 | user: test
2649 | user: test
2650 | date: Thu Jan 01 00:00:06 1970 +0000
2650 | date: Thu Jan 01 00:00:06 1970 +0000
2651 | summary: (6) merge two known; one immediate left, one far left
2651 | summary: (6) merge two known; one immediate left, one far left
2652 |
2652 |
2653 o changeset: 5:4409d547b708
2653 o changeset: 5:4409d547b708
2654 |\ parent: 3:27eef8ed80b4
2654 |\ parent: 3:27eef8ed80b4
2655 | ~ parent: 4:26a8bac39d9f
2655 | ~ parent: 4:26a8bac39d9f
2656 | user: test
2656 | user: test
2657 | date: Thu Jan 01 00:00:05 1970 +0000
2657 | date: Thu Jan 01 00:00:05 1970 +0000
2658 | summary: (5) expand
2658 | summary: (5) expand
2659 |
2659 |
2660 o changeset: 4:26a8bac39d9f
2660 o changeset: 4:26a8bac39d9f
2661 |\ parent: 1:6db2ef61d156
2661 |\ parent: 1:6db2ef61d156
2662 ~ ~ parent: 3:27eef8ed80b4
2662 ~ ~ parent: 3:27eef8ed80b4
2663 user: test
2663 user: test
2664 date: Thu Jan 01 00:00:04 1970 +0000
2664 date: Thu Jan 01 00:00:04 1970 +0000
2665 summary: (4) merge two known; one immediate left, one immediate right
2665 summary: (4) merge two known; one immediate left, one immediate right
2666
2666
2667
2667
2668 Setting HGPLAIN ignores graphmod styling:
2668 Setting HGPLAIN ignores graphmod styling:
2669
2669
2670 $ HGPLAIN=1 hg log -G -r 'file("a")' -m
2670 $ HGPLAIN=1 hg log -G -r 'file("a")' -m
2671 @ changeset: 36:08a19a744424
2671 @ changeset: 36:08a19a744424
2672 | branch: branch
2672 | branch: branch
2673 | tag: tip
2673 | tag: tip
2674 | parent: 35:9159c3644c5e
2674 | parent: 35:9159c3644c5e
2675 | parent: 35:9159c3644c5e
2675 | parent: 35:9159c3644c5e
2676 | user: test
2676 | user: test
2677 | date: Thu Jan 01 00:00:36 1970 +0000
2677 | date: Thu Jan 01 00:00:36 1970 +0000
2678 | summary: (36) buggy merge: identical parents
2678 | summary: (36) buggy merge: identical parents
2679 |
2679 |
2680 o changeset: 32:d06dffa21a31
2680 o changeset: 32:d06dffa21a31
2681 |\ parent: 27:886ed638191b
2681 |\ parent: 27:886ed638191b
2682 | | parent: 31:621d83e11f67
2682 | | parent: 31:621d83e11f67
2683 | | user: test
2683 | | user: test
2684 | | date: Thu Jan 01 00:00:32 1970 +0000
2684 | | date: Thu Jan 01 00:00:32 1970 +0000
2685 | | summary: (32) expand
2685 | | summary: (32) expand
2686 | |
2686 | |
2687 o | changeset: 31:621d83e11f67
2687 o | changeset: 31:621d83e11f67
2688 |\| parent: 21:d42a756af44d
2688 |\| parent: 21:d42a756af44d
2689 | | parent: 30:6e11cd4b648f
2689 | | parent: 30:6e11cd4b648f
2690 | | user: test
2690 | | user: test
2691 | | date: Thu Jan 01 00:00:31 1970 +0000
2691 | | date: Thu Jan 01 00:00:31 1970 +0000
2692 | | summary: (31) expand
2692 | | summary: (31) expand
2693 | |
2693 | |
2694 o | changeset: 30:6e11cd4b648f
2694 o | changeset: 30:6e11cd4b648f
2695 |\ \ parent: 28:44ecd0b9ae99
2695 |\ \ parent: 28:44ecd0b9ae99
2696 | | | parent: 29:cd9bb2be7593
2696 | | | parent: 29:cd9bb2be7593
2697 | | | user: test
2697 | | | user: test
2698 | | | date: Thu Jan 01 00:00:30 1970 +0000
2698 | | | date: Thu Jan 01 00:00:30 1970 +0000
2699 | | | summary: (30) expand
2699 | | | summary: (30) expand
2700 | | |
2700 | | |
2701 o | | changeset: 28:44ecd0b9ae99
2701 o | | changeset: 28:44ecd0b9ae99
2702 |\ \ \ parent: 1:6db2ef61d156
2702 |\ \ \ parent: 1:6db2ef61d156
2703 | | | | parent: 26:7f25b6c2f0b9
2703 | | | | parent: 26:7f25b6c2f0b9
2704 | | | | user: test
2704 | | | | user: test
2705 | | | | date: Thu Jan 01 00:00:28 1970 +0000
2705 | | | | date: Thu Jan 01 00:00:28 1970 +0000
2706 | | | | summary: (28) merge zero known
2706 | | | | summary: (28) merge zero known
2707 | | | |
2707 | | | |
2708 o | | | changeset: 26:7f25b6c2f0b9
2708 o | | | changeset: 26:7f25b6c2f0b9
2709 |\ \ \ \ parent: 18:1aa84d96232a
2709 |\ \ \ \ parent: 18:1aa84d96232a
2710 | | | | | parent: 25:91da8ed57247
2710 | | | | | parent: 25:91da8ed57247
2711 | | | | | user: test
2711 | | | | | user: test
2712 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
2712 | | | | | date: Thu Jan 01 00:00:26 1970 +0000
2713 | | | | | summary: (26) merge one known; far right
2713 | | | | | summary: (26) merge one known; far right
2714 | | | | |
2714 | | | | |
2715 | o-----+ changeset: 25:91da8ed57247
2715 | o-----+ changeset: 25:91da8ed57247
2716 | | | | | parent: 21:d42a756af44d
2716 | | | | | parent: 21:d42a756af44d
2717 | | | | | parent: 24:a9c19a3d96b7
2717 | | | | | parent: 24:a9c19a3d96b7
2718 | | | | | user: test
2718 | | | | | user: test
2719 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
2719 | | | | | date: Thu Jan 01 00:00:25 1970 +0000
2720 | | | | | summary: (25) merge one known; far left
2720 | | | | | summary: (25) merge one known; far left
2721 | | | | |
2721 | | | | |
2722 | o | | | changeset: 24:a9c19a3d96b7
2722 | o | | | changeset: 24:a9c19a3d96b7
2723 | |\ \ \ \ parent: 0:e6eb3150255d
2723 | |\ \ \ \ parent: 0:e6eb3150255d
2724 | | | | | | parent: 23:a01cddf0766d
2724 | | | | | | parent: 23:a01cddf0766d
2725 | | | | | | user: test
2725 | | | | | | user: test
2726 | | | | | | date: Thu Jan 01 00:00:24 1970 +0000
2726 | | | | | | date: Thu Jan 01 00:00:24 1970 +0000
2727 | | | | | | summary: (24) merge one known; immediate right
2727 | | | | | | summary: (24) merge one known; immediate right
2728 | | | | | |
2728 | | | | | |
2729 | o---+ | | changeset: 23:a01cddf0766d
2729 | o---+ | | changeset: 23:a01cddf0766d
2730 | | | | | | parent: 1:6db2ef61d156
2730 | | | | | | parent: 1:6db2ef61d156
2731 | | | | | | parent: 22:e0d9cccacb5d
2731 | | | | | | parent: 22:e0d9cccacb5d
2732 | | | | | | user: test
2732 | | | | | | user: test
2733 | | | | | | date: Thu Jan 01 00:00:23 1970 +0000
2733 | | | | | | date: Thu Jan 01 00:00:23 1970 +0000
2734 | | | | | | summary: (23) merge one known; immediate left
2734 | | | | | | summary: (23) merge one known; immediate left
2735 | | | | | |
2735 | | | | | |
2736 | o-------+ changeset: 22:e0d9cccacb5d
2736 | o-------+ changeset: 22:e0d9cccacb5d
2737 | | | | | | parent: 18:1aa84d96232a
2737 | | | | | | parent: 18:1aa84d96232a
2738 |/ / / / / parent: 21:d42a756af44d
2738 |/ / / / / parent: 21:d42a756af44d
2739 | | | | | user: test
2739 | | | | | user: test
2740 | | | | | date: Thu Jan 01 00:00:22 1970 +0000
2740 | | | | | date: Thu Jan 01 00:00:22 1970 +0000
2741 | | | | | summary: (22) merge two known; one far left, one far right
2741 | | | | | summary: (22) merge two known; one far left, one far right
2742 | | | | |
2742 | | | | |
2743 | | | | o changeset: 21:d42a756af44d
2743 | | | | o changeset: 21:d42a756af44d
2744 | | | | |\ parent: 19:31ddc2c1573b
2744 | | | | |\ parent: 19:31ddc2c1573b
2745 | | | | | | parent: 20:d30ed6450e32
2745 | | | | | | parent: 20:d30ed6450e32
2746 | | | | | | user: test
2746 | | | | | | user: test
2747 | | | | | | date: Thu Jan 01 00:00:21 1970 +0000
2747 | | | | | | date: Thu Jan 01 00:00:21 1970 +0000
2748 | | | | | | summary: (21) expand
2748 | | | | | | summary: (21) expand
2749 | | | | | |
2749 | | | | | |
2750 +-+-------o changeset: 20:d30ed6450e32
2750 +-+-------o changeset: 20:d30ed6450e32
2751 | | | | | parent: 0:e6eb3150255d
2751 | | | | | parent: 0:e6eb3150255d
2752 | | | | | parent: 18:1aa84d96232a
2752 | | | | | parent: 18:1aa84d96232a
2753 | | | | | user: test
2753 | | | | | user: test
2754 | | | | | date: Thu Jan 01 00:00:20 1970 +0000
2754 | | | | | date: Thu Jan 01 00:00:20 1970 +0000
2755 | | | | | summary: (20) merge two known; two far right
2755 | | | | | summary: (20) merge two known; two far right
2756 | | | | |
2756 | | | | |
2757 | | | | o changeset: 19:31ddc2c1573b
2757 | | | | o changeset: 19:31ddc2c1573b
2758 | | | | |\ parent: 15:1dda3f72782d
2758 | | | | |\ parent: 15:1dda3f72782d
2759 | | | | | | parent: 17:44765d7c06e0
2759 | | | | | | parent: 17:44765d7c06e0
2760 | | | | | | user: test
2760 | | | | | | user: test
2761 | | | | | | date: Thu Jan 01 00:00:19 1970 +0000
2761 | | | | | | date: Thu Jan 01 00:00:19 1970 +0000
2762 | | | | | | summary: (19) expand
2762 | | | | | | summary: (19) expand
2763 | | | | | |
2763 | | | | | |
2764 o---+---+ | changeset: 18:1aa84d96232a
2764 o---+---+ | changeset: 18:1aa84d96232a
2765 | | | | | parent: 1:6db2ef61d156
2765 | | | | | parent: 1:6db2ef61d156
2766 / / / / / parent: 15:1dda3f72782d
2766 / / / / / parent: 15:1dda3f72782d
2767 | | | | | user: test
2767 | | | | | user: test
2768 | | | | | date: Thu Jan 01 00:00:18 1970 +0000
2768 | | | | | date: Thu Jan 01 00:00:18 1970 +0000
2769 | | | | | summary: (18) merge two known; two far left
2769 | | | | | summary: (18) merge two known; two far left
2770 | | | | |
2770 | | | | |
2771 | | | | o changeset: 17:44765d7c06e0
2771 | | | | o changeset: 17:44765d7c06e0
2772 | | | | |\ parent: 12:86b91144a6e9
2772 | | | | |\ parent: 12:86b91144a6e9
2773 | | | | | | parent: 16:3677d192927d
2773 | | | | | | parent: 16:3677d192927d
2774 | | | | | | user: test
2774 | | | | | | user: test
2775 | | | | | | date: Thu Jan 01 00:00:17 1970 +0000
2775 | | | | | | date: Thu Jan 01 00:00:17 1970 +0000
2776 | | | | | | summary: (17) expand
2776 | | | | | | summary: (17) expand
2777 | | | | | |
2777 | | | | | |
2778 +-+-------o changeset: 16:3677d192927d
2778 +-+-------o changeset: 16:3677d192927d
2779 | | | | | parent: 0:e6eb3150255d
2779 | | | | | parent: 0:e6eb3150255d
2780 | | | | | parent: 1:6db2ef61d156
2780 | | | | | parent: 1:6db2ef61d156
2781 | | | | | user: test
2781 | | | | | user: test
2782 | | | | | date: Thu Jan 01 00:00:16 1970 +0000
2782 | | | | | date: Thu Jan 01 00:00:16 1970 +0000
2783 | | | | | summary: (16) merge two known; one immediate right, one near right
2783 | | | | | summary: (16) merge two known; one immediate right, one near right
2784 | | | | |
2784 | | | | |
2785 | | | o | changeset: 15:1dda3f72782d
2785 | | | o | changeset: 15:1dda3f72782d
2786 | | | |\ \ parent: 13:22d8966a97e3
2786 | | | |\ \ parent: 13:22d8966a97e3
2787 | | | | | | parent: 14:8eac370358ef
2787 | | | | | | parent: 14:8eac370358ef
2788 | | | | | | user: test
2788 | | | | | | user: test
2789 | | | | | | date: Thu Jan 01 00:00:15 1970 +0000
2789 | | | | | | date: Thu Jan 01 00:00:15 1970 +0000
2790 | | | | | | summary: (15) expand
2790 | | | | | | summary: (15) expand
2791 | | | | | |
2791 | | | | | |
2792 +-------o | changeset: 14:8eac370358ef
2792 +-------o | changeset: 14:8eac370358ef
2793 | | | | |/ parent: 0:e6eb3150255d
2793 | | | | |/ parent: 0:e6eb3150255d
2794 | | | | | parent: 12:86b91144a6e9
2794 | | | | | parent: 12:86b91144a6e9
2795 | | | | | user: test
2795 | | | | | user: test
2796 | | | | | date: Thu Jan 01 00:00:14 1970 +0000
2796 | | | | | date: Thu Jan 01 00:00:14 1970 +0000
2797 | | | | | summary: (14) merge two known; one immediate right, one far right
2797 | | | | | summary: (14) merge two known; one immediate right, one far right
2798 | | | | |
2798 | | | | |
2799 | | | o | changeset: 13:22d8966a97e3
2799 | | | o | changeset: 13:22d8966a97e3
2800 | | | |\ \ parent: 9:7010c0af0a35
2800 | | | |\ \ parent: 9:7010c0af0a35
2801 | | | | | | parent: 11:832d76e6bdf2
2801 | | | | | | parent: 11:832d76e6bdf2
2802 | | | | | | user: test
2802 | | | | | | user: test
2803 | | | | | | date: Thu Jan 01 00:00:13 1970 +0000
2803 | | | | | | date: Thu Jan 01 00:00:13 1970 +0000
2804 | | | | | | summary: (13) expand
2804 | | | | | | summary: (13) expand
2805 | | | | | |
2805 | | | | | |
2806 | +---+---o changeset: 12:86b91144a6e9
2806 | +---+---o changeset: 12:86b91144a6e9
2807 | | | | | parent: 1:6db2ef61d156
2807 | | | | | parent: 1:6db2ef61d156
2808 | | | | | parent: 9:7010c0af0a35
2808 | | | | | parent: 9:7010c0af0a35
2809 | | | | | user: test
2809 | | | | | user: test
2810 | | | | | date: Thu Jan 01 00:00:12 1970 +0000
2810 | | | | | date: Thu Jan 01 00:00:12 1970 +0000
2811 | | | | | summary: (12) merge two known; one immediate right, one far left
2811 | | | | | summary: (12) merge two known; one immediate right, one far left
2812 | | | | |
2812 | | | | |
2813 | | | | o changeset: 11:832d76e6bdf2
2813 | | | | o changeset: 11:832d76e6bdf2
2814 | | | | |\ parent: 6:b105a072e251
2814 | | | | |\ parent: 6:b105a072e251
2815 | | | | | | parent: 10:74c64d036d72
2815 | | | | | | parent: 10:74c64d036d72
2816 | | | | | | user: test
2816 | | | | | | user: test
2817 | | | | | | date: Thu Jan 01 00:00:11 1970 +0000
2817 | | | | | | date: Thu Jan 01 00:00:11 1970 +0000
2818 | | | | | | summary: (11) expand
2818 | | | | | | summary: (11) expand
2819 | | | | | |
2819 | | | | | |
2820 +---------o changeset: 10:74c64d036d72
2820 +---------o changeset: 10:74c64d036d72
2821 | | | | |/ parent: 0:e6eb3150255d
2821 | | | | |/ parent: 0:e6eb3150255d
2822 | | | | | parent: 6:b105a072e251
2822 | | | | | parent: 6:b105a072e251
2823 | | | | | user: test
2823 | | | | | user: test
2824 | | | | | date: Thu Jan 01 00:00:10 1970 +0000
2824 | | | | | date: Thu Jan 01 00:00:10 1970 +0000
2825 | | | | | summary: (10) merge two known; one immediate left, one near right
2825 | | | | | summary: (10) merge two known; one immediate left, one near right
2826 | | | | |
2826 | | | | |
2827 | | | o | changeset: 9:7010c0af0a35
2827 | | | o | changeset: 9:7010c0af0a35
2828 | | | |\ \ parent: 7:b632bb1b1224
2828 | | | |\ \ parent: 7:b632bb1b1224
2829 | | | | | | parent: 8:7a0b11f71937
2829 | | | | | | parent: 8:7a0b11f71937
2830 | | | | | | user: test
2830 | | | | | | user: test
2831 | | | | | | date: Thu Jan 01 00:00:09 1970 +0000
2831 | | | | | | date: Thu Jan 01 00:00:09 1970 +0000
2832 | | | | | | summary: (9) expand
2832 | | | | | | summary: (9) expand
2833 | | | | | |
2833 | | | | | |
2834 +-------o | changeset: 8:7a0b11f71937
2834 +-------o | changeset: 8:7a0b11f71937
2835 | | | |/ / parent: 0:e6eb3150255d
2835 | | | |/ / parent: 0:e6eb3150255d
2836 | | | | | parent: 7:b632bb1b1224
2836 | | | | | parent: 7:b632bb1b1224
2837 | | | | | user: test
2837 | | | | | user: test
2838 | | | | | date: Thu Jan 01 00:00:08 1970 +0000
2838 | | | | | date: Thu Jan 01 00:00:08 1970 +0000
2839 | | | | | summary: (8) merge two known; one immediate left, one far right
2839 | | | | | summary: (8) merge two known; one immediate left, one far right
2840 | | | | |
2840 | | | | |
2841 | | | o | changeset: 7:b632bb1b1224
2841 | | | o | changeset: 7:b632bb1b1224
2842 | | | |\ \ parent: 2:3d9a33b8d1e1
2842 | | | |\ \ parent: 2:3d9a33b8d1e1
2843 | | | | | | parent: 5:4409d547b708
2843 | | | | | | parent: 5:4409d547b708
2844 | | | | | | user: test
2844 | | | | | | user: test
2845 | | | | | | date: Thu Jan 01 00:00:07 1970 +0000
2845 | | | | | | date: Thu Jan 01 00:00:07 1970 +0000
2846 | | | | | | summary: (7) expand
2846 | | | | | | summary: (7) expand
2847 | | | | | |
2847 | | | | | |
2848 | | | +---o changeset: 6:b105a072e251
2848 | | | +---o changeset: 6:b105a072e251
2849 | | | | |/ parent: 2:3d9a33b8d1e1
2849 | | | | |/ parent: 2:3d9a33b8d1e1
2850 | | | | | parent: 5:4409d547b708
2850 | | | | | parent: 5:4409d547b708
2851 | | | | | user: test
2851 | | | | | user: test
2852 | | | | | date: Thu Jan 01 00:00:06 1970 +0000
2852 | | | | | date: Thu Jan 01 00:00:06 1970 +0000
2853 | | | | | summary: (6) merge two known; one immediate left, one far left
2853 | | | | | summary: (6) merge two known; one immediate left, one far left
2854 | | | | |
2854 | | | | |
2855 | | | o | changeset: 5:4409d547b708
2855 | | | o | changeset: 5:4409d547b708
2856 | | | |\ \ parent: 3:27eef8ed80b4
2856 | | | |\ \ parent: 3:27eef8ed80b4
2857 | | | | | | parent: 4:26a8bac39d9f
2857 | | | | | | parent: 4:26a8bac39d9f
2858 | | | | | | user: test
2858 | | | | | | user: test
2859 | | | | | | date: Thu Jan 01 00:00:05 1970 +0000
2859 | | | | | | date: Thu Jan 01 00:00:05 1970 +0000
2860 | | | | | | summary: (5) expand
2860 | | | | | | summary: (5) expand
2861 | | | | | |
2861 | | | | | |
2862 | +---o | | changeset: 4:26a8bac39d9f
2862 | +---o | | changeset: 4:26a8bac39d9f
2863 | | | |/ / parent: 1:6db2ef61d156
2863 | | | |/ / parent: 1:6db2ef61d156
2864 | | | | | parent: 3:27eef8ed80b4
2864 | | | | | parent: 3:27eef8ed80b4
2865 | | | | | user: test
2865 | | | | | user: test
2866 | | | | | date: Thu Jan 01 00:00:04 1970 +0000
2866 | | | | | date: Thu Jan 01 00:00:04 1970 +0000
2867 | | | | | summary: (4) merge two known; one immediate left, one immediate right
2867 | | | | | summary: (4) merge two known; one immediate left, one immediate right
2868 | | | | |
2868 | | | | |
2869
2869
2870 .. unless HGPLAINEXCEPT=graph is set:
2870 .. unless HGPLAINEXCEPT=graph is set:
2871
2871
2872 $ HGPLAIN=1 HGPLAINEXCEPT=graph hg log -G -r 'file("a")' -m
2872 $ HGPLAIN=1 HGPLAINEXCEPT=graph hg log -G -r 'file("a")' -m
2873 @ changeset: 36:08a19a744424
2873 @ changeset: 36:08a19a744424
2874 : branch: branch
2874 : branch: branch
2875 : tag: tip
2875 : tag: tip
2876 : parent: 35:9159c3644c5e
2876 : parent: 35:9159c3644c5e
2877 : parent: 35:9159c3644c5e
2877 : parent: 35:9159c3644c5e
2878 : user: test
2878 : user: test
2879 : date: Thu Jan 01 00:00:36 1970 +0000
2879 : date: Thu Jan 01 00:00:36 1970 +0000
2880 : summary: (36) buggy merge: identical parents
2880 : summary: (36) buggy merge: identical parents
2881 :
2881 :
2882 o changeset: 32:d06dffa21a31
2882 o changeset: 32:d06dffa21a31
2883 |\ parent: 27:886ed638191b
2883 |\ parent: 27:886ed638191b
2884 | : parent: 31:621d83e11f67
2884 | : parent: 31:621d83e11f67
2885 | : user: test
2885 | : user: test
2886 | : date: Thu Jan 01 00:00:32 1970 +0000
2886 | : date: Thu Jan 01 00:00:32 1970 +0000
2887 | : summary: (32) expand
2887 | : summary: (32) expand
2888 | :
2888 | :
2889 o : changeset: 31:621d83e11f67
2889 o : changeset: 31:621d83e11f67
2890 |\: parent: 21:d42a756af44d
2890 |\: parent: 21:d42a756af44d
2891 | : parent: 30:6e11cd4b648f
2891 | : parent: 30:6e11cd4b648f
2892 | : user: test
2892 | : user: test
2893 | : date: Thu Jan 01 00:00:31 1970 +0000
2893 | : date: Thu Jan 01 00:00:31 1970 +0000
2894 | : summary: (31) expand
2894 | : summary: (31) expand
2895 | :
2895 | :
2896 o : changeset: 30:6e11cd4b648f
2896 o : changeset: 30:6e11cd4b648f
2897 |\ \ parent: 28:44ecd0b9ae99
2897 |\ \ parent: 28:44ecd0b9ae99
2898 | ~ : parent: 29:cd9bb2be7593
2898 | ~ : parent: 29:cd9bb2be7593
2899 | : user: test
2899 | : user: test
2900 | : date: Thu Jan 01 00:00:30 1970 +0000
2900 | : date: Thu Jan 01 00:00:30 1970 +0000
2901 | : summary: (30) expand
2901 | : summary: (30) expand
2902 | /
2902 | /
2903 o : changeset: 28:44ecd0b9ae99
2903 o : changeset: 28:44ecd0b9ae99
2904 |\ \ parent: 1:6db2ef61d156
2904 |\ \ parent: 1:6db2ef61d156
2905 | ~ : parent: 26:7f25b6c2f0b9
2905 | ~ : parent: 26:7f25b6c2f0b9
2906 | : user: test
2906 | : user: test
2907 | : date: Thu Jan 01 00:00:28 1970 +0000
2907 | : date: Thu Jan 01 00:00:28 1970 +0000
2908 | : summary: (28) merge zero known
2908 | : summary: (28) merge zero known
2909 | /
2909 | /
2910 o : changeset: 26:7f25b6c2f0b9
2910 o : changeset: 26:7f25b6c2f0b9
2911 |\ \ parent: 18:1aa84d96232a
2911 |\ \ parent: 18:1aa84d96232a
2912 | | : parent: 25:91da8ed57247
2912 | | : parent: 25:91da8ed57247
2913 | | : user: test
2913 | | : user: test
2914 | | : date: Thu Jan 01 00:00:26 1970 +0000
2914 | | : date: Thu Jan 01 00:00:26 1970 +0000
2915 | | : summary: (26) merge one known; far right
2915 | | : summary: (26) merge one known; far right
2916 | | :
2916 | | :
2917 | o : changeset: 25:91da8ed57247
2917 | o : changeset: 25:91da8ed57247
2918 | |\: parent: 21:d42a756af44d
2918 | |\: parent: 21:d42a756af44d
2919 | | : parent: 24:a9c19a3d96b7
2919 | | : parent: 24:a9c19a3d96b7
2920 | | : user: test
2920 | | : user: test
2921 | | : date: Thu Jan 01 00:00:25 1970 +0000
2921 | | : date: Thu Jan 01 00:00:25 1970 +0000
2922 | | : summary: (25) merge one known; far left
2922 | | : summary: (25) merge one known; far left
2923 | | :
2923 | | :
2924 | o : changeset: 24:a9c19a3d96b7
2924 | o : changeset: 24:a9c19a3d96b7
2925 | |\ \ parent: 0:e6eb3150255d
2925 | |\ \ parent: 0:e6eb3150255d
2926 | | ~ : parent: 23:a01cddf0766d
2926 | | ~ : parent: 23:a01cddf0766d
2927 | | : user: test
2927 | | : user: test
2928 | | : date: Thu Jan 01 00:00:24 1970 +0000
2928 | | : date: Thu Jan 01 00:00:24 1970 +0000
2929 | | : summary: (24) merge one known; immediate right
2929 | | : summary: (24) merge one known; immediate right
2930 | | /
2930 | | /
2931 | o : changeset: 23:a01cddf0766d
2931 | o : changeset: 23:a01cddf0766d
2932 | |\ \ parent: 1:6db2ef61d156
2932 | |\ \ parent: 1:6db2ef61d156
2933 | | ~ : parent: 22:e0d9cccacb5d
2933 | | ~ : parent: 22:e0d9cccacb5d
2934 | | : user: test
2934 | | : user: test
2935 | | : date: Thu Jan 01 00:00:23 1970 +0000
2935 | | : date: Thu Jan 01 00:00:23 1970 +0000
2936 | | : summary: (23) merge one known; immediate left
2936 | | : summary: (23) merge one known; immediate left
2937 | | /
2937 | | /
2938 | o : changeset: 22:e0d9cccacb5d
2938 | o : changeset: 22:e0d9cccacb5d
2939 |/:/ parent: 18:1aa84d96232a
2939 |/:/ parent: 18:1aa84d96232a
2940 | : parent: 21:d42a756af44d
2940 | : parent: 21:d42a756af44d
2941 | : user: test
2941 | : user: test
2942 | : date: Thu Jan 01 00:00:22 1970 +0000
2942 | : date: Thu Jan 01 00:00:22 1970 +0000
2943 | : summary: (22) merge two known; one far left, one far right
2943 | : summary: (22) merge two known; one far left, one far right
2944 | :
2944 | :
2945 | o changeset: 21:d42a756af44d
2945 | o changeset: 21:d42a756af44d
2946 | |\ parent: 19:31ddc2c1573b
2946 | |\ parent: 19:31ddc2c1573b
2947 | | | parent: 20:d30ed6450e32
2947 | | | parent: 20:d30ed6450e32
2948 | | | user: test
2948 | | | user: test
2949 | | | date: Thu Jan 01 00:00:21 1970 +0000
2949 | | | date: Thu Jan 01 00:00:21 1970 +0000
2950 | | | summary: (21) expand
2950 | | | summary: (21) expand
2951 | | |
2951 | | |
2952 +---o changeset: 20:d30ed6450e32
2952 +---o changeset: 20:d30ed6450e32
2953 | | | parent: 0:e6eb3150255d
2953 | | | parent: 0:e6eb3150255d
2954 | | ~ parent: 18:1aa84d96232a
2954 | | ~ parent: 18:1aa84d96232a
2955 | | user: test
2955 | | user: test
2956 | | date: Thu Jan 01 00:00:20 1970 +0000
2956 | | date: Thu Jan 01 00:00:20 1970 +0000
2957 | | summary: (20) merge two known; two far right
2957 | | summary: (20) merge two known; two far right
2958 | |
2958 | |
2959 | o changeset: 19:31ddc2c1573b
2959 | o changeset: 19:31ddc2c1573b
2960 | |\ parent: 15:1dda3f72782d
2960 | |\ parent: 15:1dda3f72782d
2961 | | | parent: 17:44765d7c06e0
2961 | | | parent: 17:44765d7c06e0
2962 | | | user: test
2962 | | | user: test
2963 | | | date: Thu Jan 01 00:00:19 1970 +0000
2963 | | | date: Thu Jan 01 00:00:19 1970 +0000
2964 | | | summary: (19) expand
2964 | | | summary: (19) expand
2965 | | |
2965 | | |
2966 o | | changeset: 18:1aa84d96232a
2966 o | | changeset: 18:1aa84d96232a
2967 |\| | parent: 1:6db2ef61d156
2967 |\| | parent: 1:6db2ef61d156
2968 ~ | | parent: 15:1dda3f72782d
2968 ~ | | parent: 15:1dda3f72782d
2969 | | user: test
2969 | | user: test
2970 | | date: Thu Jan 01 00:00:18 1970 +0000
2970 | | date: Thu Jan 01 00:00:18 1970 +0000
2971 | | summary: (18) merge two known; two far left
2971 | | summary: (18) merge two known; two far left
2972 / /
2972 / /
2973 | o changeset: 17:44765d7c06e0
2973 | o changeset: 17:44765d7c06e0
2974 | |\ parent: 12:86b91144a6e9
2974 | |\ parent: 12:86b91144a6e9
2975 | | | parent: 16:3677d192927d
2975 | | | parent: 16:3677d192927d
2976 | | | user: test
2976 | | | user: test
2977 | | | date: Thu Jan 01 00:00:17 1970 +0000
2977 | | | date: Thu Jan 01 00:00:17 1970 +0000
2978 | | | summary: (17) expand
2978 | | | summary: (17) expand
2979 | | |
2979 | | |
2980 | | o changeset: 16:3677d192927d
2980 | | o changeset: 16:3677d192927d
2981 | | |\ parent: 0:e6eb3150255d
2981 | | |\ parent: 0:e6eb3150255d
2982 | | ~ ~ parent: 1:6db2ef61d156
2982 | | ~ ~ parent: 1:6db2ef61d156
2983 | | user: test
2983 | | user: test
2984 | | date: Thu Jan 01 00:00:16 1970 +0000
2984 | | date: Thu Jan 01 00:00:16 1970 +0000
2985 | | summary: (16) merge two known; one immediate right, one near right
2985 | | summary: (16) merge two known; one immediate right, one near right
2986 | |
2986 | |
2987 o | changeset: 15:1dda3f72782d
2987 o | changeset: 15:1dda3f72782d
2988 |\ \ parent: 13:22d8966a97e3
2988 |\ \ parent: 13:22d8966a97e3
2989 | | | parent: 14:8eac370358ef
2989 | | | parent: 14:8eac370358ef
2990 | | | user: test
2990 | | | user: test
2991 | | | date: Thu Jan 01 00:00:15 1970 +0000
2991 | | | date: Thu Jan 01 00:00:15 1970 +0000
2992 | | | summary: (15) expand
2992 | | | summary: (15) expand
2993 | | |
2993 | | |
2994 | o | changeset: 14:8eac370358ef
2994 | o | changeset: 14:8eac370358ef
2995 | |\| parent: 0:e6eb3150255d
2995 | |\| parent: 0:e6eb3150255d
2996 | ~ | parent: 12:86b91144a6e9
2996 | ~ | parent: 12:86b91144a6e9
2997 | | user: test
2997 | | user: test
2998 | | date: Thu Jan 01 00:00:14 1970 +0000
2998 | | date: Thu Jan 01 00:00:14 1970 +0000
2999 | | summary: (14) merge two known; one immediate right, one far right
2999 | | summary: (14) merge two known; one immediate right, one far right
3000 | /
3000 | /
3001 o | changeset: 13:22d8966a97e3
3001 o | changeset: 13:22d8966a97e3
3002 |\ \ parent: 9:7010c0af0a35
3002 |\ \ parent: 9:7010c0af0a35
3003 | | | parent: 11:832d76e6bdf2
3003 | | | parent: 11:832d76e6bdf2
3004 | | | user: test
3004 | | | user: test
3005 | | | date: Thu Jan 01 00:00:13 1970 +0000
3005 | | | date: Thu Jan 01 00:00:13 1970 +0000
3006 | | | summary: (13) expand
3006 | | | summary: (13) expand
3007 | | |
3007 | | |
3008 +---o changeset: 12:86b91144a6e9
3008 +---o changeset: 12:86b91144a6e9
3009 | | | parent: 1:6db2ef61d156
3009 | | | parent: 1:6db2ef61d156
3010 | | ~ parent: 9:7010c0af0a35
3010 | | ~ parent: 9:7010c0af0a35
3011 | | user: test
3011 | | user: test
3012 | | date: Thu Jan 01 00:00:12 1970 +0000
3012 | | date: Thu Jan 01 00:00:12 1970 +0000
3013 | | summary: (12) merge two known; one immediate right, one far left
3013 | | summary: (12) merge two known; one immediate right, one far left
3014 | |
3014 | |
3015 | o changeset: 11:832d76e6bdf2
3015 | o changeset: 11:832d76e6bdf2
3016 | |\ parent: 6:b105a072e251
3016 | |\ parent: 6:b105a072e251
3017 | | | parent: 10:74c64d036d72
3017 | | | parent: 10:74c64d036d72
3018 | | | user: test
3018 | | | user: test
3019 | | | date: Thu Jan 01 00:00:11 1970 +0000
3019 | | | date: Thu Jan 01 00:00:11 1970 +0000
3020 | | | summary: (11) expand
3020 | | | summary: (11) expand
3021 | | |
3021 | | |
3022 | | o changeset: 10:74c64d036d72
3022 | | o changeset: 10:74c64d036d72
3023 | |/| parent: 0:e6eb3150255d
3023 | |/| parent: 0:e6eb3150255d
3024 | | ~ parent: 6:b105a072e251
3024 | | ~ parent: 6:b105a072e251
3025 | | user: test
3025 | | user: test
3026 | | date: Thu Jan 01 00:00:10 1970 +0000
3026 | | date: Thu Jan 01 00:00:10 1970 +0000
3027 | | summary: (10) merge two known; one immediate left, one near right
3027 | | summary: (10) merge two known; one immediate left, one near right
3028 | |
3028 | |
3029 o | changeset: 9:7010c0af0a35
3029 o | changeset: 9:7010c0af0a35
3030 |\ \ parent: 7:b632bb1b1224
3030 |\ \ parent: 7:b632bb1b1224
3031 | | | parent: 8:7a0b11f71937
3031 | | | parent: 8:7a0b11f71937
3032 | | | user: test
3032 | | | user: test
3033 | | | date: Thu Jan 01 00:00:09 1970 +0000
3033 | | | date: Thu Jan 01 00:00:09 1970 +0000
3034 | | | summary: (9) expand
3034 | | | summary: (9) expand
3035 | | |
3035 | | |
3036 | o | changeset: 8:7a0b11f71937
3036 | o | changeset: 8:7a0b11f71937
3037 |/| | parent: 0:e6eb3150255d
3037 |/| | parent: 0:e6eb3150255d
3038 | ~ | parent: 7:b632bb1b1224
3038 | ~ | parent: 7:b632bb1b1224
3039 | | user: test
3039 | | user: test
3040 | | date: Thu Jan 01 00:00:08 1970 +0000
3040 | | date: Thu Jan 01 00:00:08 1970 +0000
3041 | | summary: (8) merge two known; one immediate left, one far right
3041 | | summary: (8) merge two known; one immediate left, one far right
3042 | /
3042 | /
3043 o | changeset: 7:b632bb1b1224
3043 o | changeset: 7:b632bb1b1224
3044 |\ \ parent: 2:3d9a33b8d1e1
3044 |\ \ parent: 2:3d9a33b8d1e1
3045 | ~ | parent: 5:4409d547b708
3045 | ~ | parent: 5:4409d547b708
3046 | | user: test
3046 | | user: test
3047 | | date: Thu Jan 01 00:00:07 1970 +0000
3047 | | date: Thu Jan 01 00:00:07 1970 +0000
3048 | | summary: (7) expand
3048 | | summary: (7) expand
3049 | /
3049 | /
3050 | o changeset: 6:b105a072e251
3050 | o changeset: 6:b105a072e251
3051 |/| parent: 2:3d9a33b8d1e1
3051 |/| parent: 2:3d9a33b8d1e1
3052 | ~ parent: 5:4409d547b708
3052 | ~ parent: 5:4409d547b708
3053 | user: test
3053 | user: test
3054 | date: Thu Jan 01 00:00:06 1970 +0000
3054 | date: Thu Jan 01 00:00:06 1970 +0000
3055 | summary: (6) merge two known; one immediate left, one far left
3055 | summary: (6) merge two known; one immediate left, one far left
3056 |
3056 |
3057 o changeset: 5:4409d547b708
3057 o changeset: 5:4409d547b708
3058 |\ parent: 3:27eef8ed80b4
3058 |\ parent: 3:27eef8ed80b4
3059 | ~ parent: 4:26a8bac39d9f
3059 | ~ parent: 4:26a8bac39d9f
3060 | user: test
3060 | user: test
3061 | date: Thu Jan 01 00:00:05 1970 +0000
3061 | date: Thu Jan 01 00:00:05 1970 +0000
3062 | summary: (5) expand
3062 | summary: (5) expand
3063 |
3063 |
3064 o changeset: 4:26a8bac39d9f
3064 o changeset: 4:26a8bac39d9f
3065 |\ parent: 1:6db2ef61d156
3065 |\ parent: 1:6db2ef61d156
3066 ~ ~ parent: 3:27eef8ed80b4
3066 ~ ~ parent: 3:27eef8ed80b4
3067 user: test
3067 user: test
3068 date: Thu Jan 01 00:00:04 1970 +0000
3068 date: Thu Jan 01 00:00:04 1970 +0000
3069 summary: (4) merge two known; one immediate left, one immediate right
3069 summary: (4) merge two known; one immediate left, one immediate right
3070
3070
3071 Draw only part of a grandparent line differently with "<N><char>"; only the
3071 Draw only part of a grandparent line differently with "<N><char>"; only the
3072 last N lines (for positive N) or everything but the first N lines (for
3072 last N lines (for positive N) or everything but the first N lines (for
3073 negative N) along the current node use the style, the rest of the edge uses
3073 negative N) along the current node use the style, the rest of the edge uses
3074 the parent edge styling.
3074 the parent edge styling.
3075
3075
3076 Last 3 lines:
3076 Last 3 lines:
3077
3077
3078 $ cat << EOF >> $HGRCPATH
3078 $ cat << EOF >> $HGRCPATH
3079 > [experimental]
3079 > [experimental]
3080 > graphstyle.parent = !
3080 > graphstyle.parent = !
3081 > graphstyle.grandparent = 3.
3081 > graphstyle.grandparent = 3.
3082 > graphstyle.missing =
3082 > graphstyle.missing =
3083 > EOF
3083 > EOF
3084 $ hg log -G -r '36:18 & file("a")' -m
3084 $ hg log -G -r '36:18 & file("a")' -m
3085 @ changeset: 36:08a19a744424
3085 @ changeset: 36:08a19a744424
3086 ! branch: branch
3086 ! branch: branch
3087 ! tag: tip
3087 ! tag: tip
3088 ! parent: 35:9159c3644c5e
3088 ! parent: 35:9159c3644c5e
3089 ! parent: 35:9159c3644c5e
3089 ! parent: 35:9159c3644c5e
3090 ! user: test
3090 ! user: test
3091 . date: Thu Jan 01 00:00:36 1970 +0000
3091 . date: Thu Jan 01 00:00:36 1970 +0000
3092 . summary: (36) buggy merge: identical parents
3092 . summary: (36) buggy merge: identical parents
3093 .
3093 .
3094 o changeset: 32:d06dffa21a31
3094 o changeset: 32:d06dffa21a31
3095 !\ parent: 27:886ed638191b
3095 !\ parent: 27:886ed638191b
3096 ! ! parent: 31:621d83e11f67
3096 ! ! parent: 31:621d83e11f67
3097 ! ! user: test
3097 ! ! user: test
3098 ! . date: Thu Jan 01 00:00:32 1970 +0000
3098 ! . date: Thu Jan 01 00:00:32 1970 +0000
3099 ! . summary: (32) expand
3099 ! . summary: (32) expand
3100 ! .
3100 ! .
3101 o ! changeset: 31:621d83e11f67
3101 o ! changeset: 31:621d83e11f67
3102 !\! parent: 21:d42a756af44d
3102 !\! parent: 21:d42a756af44d
3103 ! ! parent: 30:6e11cd4b648f
3103 ! ! parent: 30:6e11cd4b648f
3104 ! ! user: test
3104 ! ! user: test
3105 ! ! date: Thu Jan 01 00:00:31 1970 +0000
3105 ! ! date: Thu Jan 01 00:00:31 1970 +0000
3106 ! ! summary: (31) expand
3106 ! ! summary: (31) expand
3107 ! !
3107 ! !
3108 o ! changeset: 30:6e11cd4b648f
3108 o ! changeset: 30:6e11cd4b648f
3109 !\ \ parent: 28:44ecd0b9ae99
3109 !\ \ parent: 28:44ecd0b9ae99
3110 ! ~ ! parent: 29:cd9bb2be7593
3110 ! ~ ! parent: 29:cd9bb2be7593
3111 ! ! user: test
3111 ! ! user: test
3112 ! ! date: Thu Jan 01 00:00:30 1970 +0000
3112 ! ! date: Thu Jan 01 00:00:30 1970 +0000
3113 ! ! summary: (30) expand
3113 ! ! summary: (30) expand
3114 ! /
3114 ! /
3115 o ! changeset: 28:44ecd0b9ae99
3115 o ! changeset: 28:44ecd0b9ae99
3116 !\ \ parent: 1:6db2ef61d156
3116 !\ \ parent: 1:6db2ef61d156
3117 ! ~ ! parent: 26:7f25b6c2f0b9
3117 ! ~ ! parent: 26:7f25b6c2f0b9
3118 ! ! user: test
3118 ! ! user: test
3119 ! ! date: Thu Jan 01 00:00:28 1970 +0000
3119 ! ! date: Thu Jan 01 00:00:28 1970 +0000
3120 ! ! summary: (28) merge zero known
3120 ! ! summary: (28) merge zero known
3121 ! /
3121 ! /
3122 o ! changeset: 26:7f25b6c2f0b9
3122 o ! changeset: 26:7f25b6c2f0b9
3123 !\ \ parent: 18:1aa84d96232a
3123 !\ \ parent: 18:1aa84d96232a
3124 ! ! ! parent: 25:91da8ed57247
3124 ! ! ! parent: 25:91da8ed57247
3125 ! ! ! user: test
3125 ! ! ! user: test
3126 ! ! ! date: Thu Jan 01 00:00:26 1970 +0000
3126 ! ! ! date: Thu Jan 01 00:00:26 1970 +0000
3127 ! ! ! summary: (26) merge one known; far right
3127 ! ! ! summary: (26) merge one known; far right
3128 ! ! !
3128 ! ! !
3129 ! o ! changeset: 25:91da8ed57247
3129 ! o ! changeset: 25:91da8ed57247
3130 ! !\! parent: 21:d42a756af44d
3130 ! !\! parent: 21:d42a756af44d
3131 ! ! ! parent: 24:a9c19a3d96b7
3131 ! ! ! parent: 24:a9c19a3d96b7
3132 ! ! ! user: test
3132 ! ! ! user: test
3133 ! ! ! date: Thu Jan 01 00:00:25 1970 +0000
3133 ! ! ! date: Thu Jan 01 00:00:25 1970 +0000
3134 ! ! ! summary: (25) merge one known; far left
3134 ! ! ! summary: (25) merge one known; far left
3135 ! ! !
3135 ! ! !
3136 ! o ! changeset: 24:a9c19a3d96b7
3136 ! o ! changeset: 24:a9c19a3d96b7
3137 ! !\ \ parent: 0:e6eb3150255d
3137 ! !\ \ parent: 0:e6eb3150255d
3138 ! ! ~ ! parent: 23:a01cddf0766d
3138 ! ! ~ ! parent: 23:a01cddf0766d
3139 ! ! ! user: test
3139 ! ! ! user: test
3140 ! ! ! date: Thu Jan 01 00:00:24 1970 +0000
3140 ! ! ! date: Thu Jan 01 00:00:24 1970 +0000
3141 ! ! ! summary: (24) merge one known; immediate right
3141 ! ! ! summary: (24) merge one known; immediate right
3142 ! ! /
3142 ! ! /
3143 ! o ! changeset: 23:a01cddf0766d
3143 ! o ! changeset: 23:a01cddf0766d
3144 ! !\ \ parent: 1:6db2ef61d156
3144 ! !\ \ parent: 1:6db2ef61d156
3145 ! ! ~ ! parent: 22:e0d9cccacb5d
3145 ! ! ~ ! parent: 22:e0d9cccacb5d
3146 ! ! ! user: test
3146 ! ! ! user: test
3147 ! ! ! date: Thu Jan 01 00:00:23 1970 +0000
3147 ! ! ! date: Thu Jan 01 00:00:23 1970 +0000
3148 ! ! ! summary: (23) merge one known; immediate left
3148 ! ! ! summary: (23) merge one known; immediate left
3149 ! ! /
3149 ! ! /
3150 ! o ! changeset: 22:e0d9cccacb5d
3150 ! o ! changeset: 22:e0d9cccacb5d
3151 !/!/ parent: 18:1aa84d96232a
3151 !/!/ parent: 18:1aa84d96232a
3152 ! ! parent: 21:d42a756af44d
3152 ! ! parent: 21:d42a756af44d
3153 ! ! user: test
3153 ! ! user: test
3154 ! ! date: Thu Jan 01 00:00:22 1970 +0000
3154 ! ! date: Thu Jan 01 00:00:22 1970 +0000
3155 ! ! summary: (22) merge two known; one far left, one far right
3155 ! ! summary: (22) merge two known; one far left, one far right
3156 ! !
3156 ! !
3157 ! o changeset: 21:d42a756af44d
3157 ! o changeset: 21:d42a756af44d
3158 ! !\ parent: 19:31ddc2c1573b
3158 ! !\ parent: 19:31ddc2c1573b
3159 ! ! ! parent: 20:d30ed6450e32
3159 ! ! ! parent: 20:d30ed6450e32
3160 ! ! ! user: test
3160 ! ! ! user: test
3161 ! ! ! date: Thu Jan 01 00:00:21 1970 +0000
3161 ! ! ! date: Thu Jan 01 00:00:21 1970 +0000
3162 ! ! ! summary: (21) expand
3162 ! ! ! summary: (21) expand
3163 ! ! !
3163 ! ! !
3164 +---o changeset: 20:d30ed6450e32
3164 +---o changeset: 20:d30ed6450e32
3165 ! ! | parent: 0:e6eb3150255d
3165 ! ! | parent: 0:e6eb3150255d
3166 ! ! ~ parent: 18:1aa84d96232a
3166 ! ! ~ parent: 18:1aa84d96232a
3167 ! ! user: test
3167 ! ! user: test
3168 ! ! date: Thu Jan 01 00:00:20 1970 +0000
3168 ! ! date: Thu Jan 01 00:00:20 1970 +0000
3169 ! ! summary: (20) merge two known; two far right
3169 ! ! summary: (20) merge two known; two far right
3170 ! !
3170 ! !
3171 ! o changeset: 19:31ddc2c1573b
3171 ! o changeset: 19:31ddc2c1573b
3172 ! |\ parent: 15:1dda3f72782d
3172 ! |\ parent: 15:1dda3f72782d
3173 ! ~ ~ parent: 17:44765d7c06e0
3173 ! ~ ~ parent: 17:44765d7c06e0
3174 ! user: test
3174 ! user: test
3175 ! date: Thu Jan 01 00:00:19 1970 +0000
3175 ! date: Thu Jan 01 00:00:19 1970 +0000
3176 ! summary: (19) expand
3176 ! summary: (19) expand
3177 !
3177 !
3178 o changeset: 18:1aa84d96232a
3178 o changeset: 18:1aa84d96232a
3179 |\ parent: 1:6db2ef61d156
3179 |\ parent: 1:6db2ef61d156
3180 ~ ~ parent: 15:1dda3f72782d
3180 ~ ~ parent: 15:1dda3f72782d
3181 user: test
3181 user: test
3182 date: Thu Jan 01 00:00:18 1970 +0000
3182 date: Thu Jan 01 00:00:18 1970 +0000
3183 summary: (18) merge two known; two far left
3183 summary: (18) merge two known; two far left
3184
3184
3185 All but the first 3 lines:
3185 All but the first 3 lines:
3186
3186
3187 $ cat << EOF >> $HGRCPATH
3187 $ cat << EOF >> $HGRCPATH
3188 > [experimental]
3188 > [experimental]
3189 > graphstyle.parent = !
3189 > graphstyle.parent = !
3190 > graphstyle.grandparent = -3.
3190 > graphstyle.grandparent = -3.
3191 > graphstyle.missing =
3191 > graphstyle.missing =
3192 > EOF
3192 > EOF
3193 $ hg log -G -r '36:18 & file("a")' -m
3193 $ hg log -G -r '36:18 & file("a")' -m
3194 @ changeset: 36:08a19a744424
3194 @ changeset: 36:08a19a744424
3195 ! branch: branch
3195 ! branch: branch
3196 ! tag: tip
3196 ! tag: tip
3197 . parent: 35:9159c3644c5e
3197 . parent: 35:9159c3644c5e
3198 . parent: 35:9159c3644c5e
3198 . parent: 35:9159c3644c5e
3199 . user: test
3199 . user: test
3200 . date: Thu Jan 01 00:00:36 1970 +0000
3200 . date: Thu Jan 01 00:00:36 1970 +0000
3201 . summary: (36) buggy merge: identical parents
3201 . summary: (36) buggy merge: identical parents
3202 .
3202 .
3203 o changeset: 32:d06dffa21a31
3203 o changeset: 32:d06dffa21a31
3204 !\ parent: 27:886ed638191b
3204 !\ parent: 27:886ed638191b
3205 ! ! parent: 31:621d83e11f67
3205 ! ! parent: 31:621d83e11f67
3206 ! . user: test
3206 ! . user: test
3207 ! . date: Thu Jan 01 00:00:32 1970 +0000
3207 ! . date: Thu Jan 01 00:00:32 1970 +0000
3208 ! . summary: (32) expand
3208 ! . summary: (32) expand
3209 ! .
3209 ! .
3210 o ! changeset: 31:621d83e11f67
3210 o ! changeset: 31:621d83e11f67
3211 !\! parent: 21:d42a756af44d
3211 !\! parent: 21:d42a756af44d
3212 ! ! parent: 30:6e11cd4b648f
3212 ! ! parent: 30:6e11cd4b648f
3213 ! ! user: test
3213 ! ! user: test
3214 ! ! date: Thu Jan 01 00:00:31 1970 +0000
3214 ! ! date: Thu Jan 01 00:00:31 1970 +0000
3215 ! ! summary: (31) expand
3215 ! ! summary: (31) expand
3216 ! !
3216 ! !
3217 o ! changeset: 30:6e11cd4b648f
3217 o ! changeset: 30:6e11cd4b648f
3218 !\ \ parent: 28:44ecd0b9ae99
3218 !\ \ parent: 28:44ecd0b9ae99
3219 ! ~ ! parent: 29:cd9bb2be7593
3219 ! ~ ! parent: 29:cd9bb2be7593
3220 ! ! user: test
3220 ! ! user: test
3221 ! ! date: Thu Jan 01 00:00:30 1970 +0000
3221 ! ! date: Thu Jan 01 00:00:30 1970 +0000
3222 ! ! summary: (30) expand
3222 ! ! summary: (30) expand
3223 ! /
3223 ! /
3224 o ! changeset: 28:44ecd0b9ae99
3224 o ! changeset: 28:44ecd0b9ae99
3225 !\ \ parent: 1:6db2ef61d156
3225 !\ \ parent: 1:6db2ef61d156
3226 ! ~ ! parent: 26:7f25b6c2f0b9
3226 ! ~ ! parent: 26:7f25b6c2f0b9
3227 ! ! user: test
3227 ! ! user: test
3228 ! ! date: Thu Jan 01 00:00:28 1970 +0000
3228 ! ! date: Thu Jan 01 00:00:28 1970 +0000
3229 ! ! summary: (28) merge zero known
3229 ! ! summary: (28) merge zero known
3230 ! /
3230 ! /
3231 o ! changeset: 26:7f25b6c2f0b9
3231 o ! changeset: 26:7f25b6c2f0b9
3232 !\ \ parent: 18:1aa84d96232a
3232 !\ \ parent: 18:1aa84d96232a
3233 ! ! ! parent: 25:91da8ed57247
3233 ! ! ! parent: 25:91da8ed57247
3234 ! ! ! user: test
3234 ! ! ! user: test
3235 ! ! ! date: Thu Jan 01 00:00:26 1970 +0000
3235 ! ! ! date: Thu Jan 01 00:00:26 1970 +0000
3236 ! ! ! summary: (26) merge one known; far right
3236 ! ! ! summary: (26) merge one known; far right
3237 ! ! !
3237 ! ! !
3238 ! o ! changeset: 25:91da8ed57247
3238 ! o ! changeset: 25:91da8ed57247
3239 ! !\! parent: 21:d42a756af44d
3239 ! !\! parent: 21:d42a756af44d
3240 ! ! ! parent: 24:a9c19a3d96b7
3240 ! ! ! parent: 24:a9c19a3d96b7
3241 ! ! ! user: test
3241 ! ! ! user: test
3242 ! ! ! date: Thu Jan 01 00:00:25 1970 +0000
3242 ! ! ! date: Thu Jan 01 00:00:25 1970 +0000
3243 ! ! ! summary: (25) merge one known; far left
3243 ! ! ! summary: (25) merge one known; far left
3244 ! ! !
3244 ! ! !
3245 ! o ! changeset: 24:a9c19a3d96b7
3245 ! o ! changeset: 24:a9c19a3d96b7
3246 ! !\ \ parent: 0:e6eb3150255d
3246 ! !\ \ parent: 0:e6eb3150255d
3247 ! ! ~ ! parent: 23:a01cddf0766d
3247 ! ! ~ ! parent: 23:a01cddf0766d
3248 ! ! ! user: test
3248 ! ! ! user: test
3249 ! ! ! date: Thu Jan 01 00:00:24 1970 +0000
3249 ! ! ! date: Thu Jan 01 00:00:24 1970 +0000
3250 ! ! ! summary: (24) merge one known; immediate right
3250 ! ! ! summary: (24) merge one known; immediate right
3251 ! ! /
3251 ! ! /
3252 ! o ! changeset: 23:a01cddf0766d
3252 ! o ! changeset: 23:a01cddf0766d
3253 ! !\ \ parent: 1:6db2ef61d156
3253 ! !\ \ parent: 1:6db2ef61d156
3254 ! ! ~ ! parent: 22:e0d9cccacb5d
3254 ! ! ~ ! parent: 22:e0d9cccacb5d
3255 ! ! ! user: test
3255 ! ! ! user: test
3256 ! ! ! date: Thu Jan 01 00:00:23 1970 +0000
3256 ! ! ! date: Thu Jan 01 00:00:23 1970 +0000
3257 ! ! ! summary: (23) merge one known; immediate left
3257 ! ! ! summary: (23) merge one known; immediate left
3258 ! ! /
3258 ! ! /
3259 ! o ! changeset: 22:e0d9cccacb5d
3259 ! o ! changeset: 22:e0d9cccacb5d
3260 !/!/ parent: 18:1aa84d96232a
3260 !/!/ parent: 18:1aa84d96232a
3261 ! ! parent: 21:d42a756af44d
3261 ! ! parent: 21:d42a756af44d
3262 ! ! user: test
3262 ! ! user: test
3263 ! ! date: Thu Jan 01 00:00:22 1970 +0000
3263 ! ! date: Thu Jan 01 00:00:22 1970 +0000
3264 ! ! summary: (22) merge two known; one far left, one far right
3264 ! ! summary: (22) merge two known; one far left, one far right
3265 ! !
3265 ! !
3266 ! o changeset: 21:d42a756af44d
3266 ! o changeset: 21:d42a756af44d
3267 ! !\ parent: 19:31ddc2c1573b
3267 ! !\ parent: 19:31ddc2c1573b
3268 ! ! ! parent: 20:d30ed6450e32
3268 ! ! ! parent: 20:d30ed6450e32
3269 ! ! ! user: test
3269 ! ! ! user: test
3270 ! ! ! date: Thu Jan 01 00:00:21 1970 +0000
3270 ! ! ! date: Thu Jan 01 00:00:21 1970 +0000
3271 ! ! ! summary: (21) expand
3271 ! ! ! summary: (21) expand
3272 ! ! !
3272 ! ! !
3273 +---o changeset: 20:d30ed6450e32
3273 +---o changeset: 20:d30ed6450e32
3274 ! ! | parent: 0:e6eb3150255d
3274 ! ! | parent: 0:e6eb3150255d
3275 ! ! ~ parent: 18:1aa84d96232a
3275 ! ! ~ parent: 18:1aa84d96232a
3276 ! ! user: test
3276 ! ! user: test
3277 ! ! date: Thu Jan 01 00:00:20 1970 +0000
3277 ! ! date: Thu Jan 01 00:00:20 1970 +0000
3278 ! ! summary: (20) merge two known; two far right
3278 ! ! summary: (20) merge two known; two far right
3279 ! !
3279 ! !
3280 ! o changeset: 19:31ddc2c1573b
3280 ! o changeset: 19:31ddc2c1573b
3281 ! |\ parent: 15:1dda3f72782d
3281 ! |\ parent: 15:1dda3f72782d
3282 ! ~ ~ parent: 17:44765d7c06e0
3282 ! ~ ~ parent: 17:44765d7c06e0
3283 ! user: test
3283 ! user: test
3284 ! date: Thu Jan 01 00:00:19 1970 +0000
3284 ! date: Thu Jan 01 00:00:19 1970 +0000
3285 ! summary: (19) expand
3285 ! summary: (19) expand
3286 !
3286 !
3287 o changeset: 18:1aa84d96232a
3287 o changeset: 18:1aa84d96232a
3288 |\ parent: 1:6db2ef61d156
3288 |\ parent: 1:6db2ef61d156
3289 ~ ~ parent: 15:1dda3f72782d
3289 ~ ~ parent: 15:1dda3f72782d
3290 user: test
3290 user: test
3291 date: Thu Jan 01 00:00:18 1970 +0000
3291 date: Thu Jan 01 00:00:18 1970 +0000
3292 summary: (18) merge two known; two far left
3292 summary: (18) merge two known; two far left
3293
3293
3294 $ cd ..
3294 $ cd ..
3295
3295
3296 Change graph shorten, test better with graphstyle.missing not none
3296 Change graph shorten, test better with graphstyle.missing not none
3297
3297
3298 $ cd repo
3298 $ cd repo
3299 $ cat << EOF >> $HGRCPATH
3299 $ cat << EOF >> $HGRCPATH
3300 > [experimental]
3300 > [experimental]
3301 > graphstyle.parent = |
3301 > graphstyle.parent = |
3302 > graphstyle.grandparent = :
3302 > graphstyle.grandparent = :
3303 > graphstyle.missing = '
3303 > graphstyle.missing = '
3304 > graphshorten = true
3304 > graphshorten = true
3305 > EOF
3305 > EOF
3306 $ hg log -G -r 'file("a")' -m -T '{rev} {desc}'
3306 $ hg log -G -r 'file("a")' -m -T '{rev} {desc}'
3307 @ 36 (36) buggy merge: identical parents
3307 @ 36 (36) buggy merge: identical parents
3308 o 32 (32) expand
3308 o 32 (32) expand
3309 |\
3309 |\
3310 o : 31 (31) expand
3310 o : 31 (31) expand
3311 |\:
3311 |\:
3312 o : 30 (30) expand
3312 o : 30 (30) expand
3313 |\ \
3313 |\ \
3314 o \ \ 28 (28) merge zero known
3314 o \ \ 28 (28) merge zero known
3315 |\ \ \
3315 |\ \ \
3316 o \ \ \ 26 (26) merge one known; far right
3316 o \ \ \ 26 (26) merge one known; far right
3317 |\ \ \ \
3317 |\ \ \ \
3318 | o-----+ 25 (25) merge one known; far left
3318 | o-----+ 25 (25) merge one known; far left
3319 | o ' ' : 24 (24) merge one known; immediate right
3319 | o ' ' : 24 (24) merge one known; immediate right
3320 | |\ \ \ \
3320 | |\ \ \ \
3321 | o---+ ' : 23 (23) merge one known; immediate left
3321 | o---+ ' : 23 (23) merge one known; immediate left
3322 | o-------+ 22 (22) merge two known; one far left, one far right
3322 | o-------+ 22 (22) merge two known; one far left, one far right
3323 |/ / / / /
3323 |/ / / / /
3324 | ' ' ' o 21 (21) expand
3324 | ' ' ' o 21 (21) expand
3325 | ' ' ' |\
3325 | ' ' ' |\
3326 +-+-------o 20 (20) merge two known; two far right
3326 +-+-------o 20 (20) merge two known; two far right
3327 | ' ' ' o 19 (19) expand
3327 | ' ' ' o 19 (19) expand
3328 | ' ' ' |\
3328 | ' ' ' |\
3329 o---+---+ | 18 (18) merge two known; two far left
3329 o---+---+ | 18 (18) merge two known; two far left
3330 / / / / /
3330 / / / / /
3331 ' ' ' | o 17 (17) expand
3331 ' ' ' | o 17 (17) expand
3332 ' ' ' | |\
3332 ' ' ' | |\
3333 +-+-------o 16 (16) merge two known; one immediate right, one near right
3333 +-+-------o 16 (16) merge two known; one immediate right, one near right
3334 ' ' ' o | 15 (15) expand
3334 ' ' ' o | 15 (15) expand
3335 ' ' ' |\ \
3335 ' ' ' |\ \
3336 +-------o | 14 (14) merge two known; one immediate right, one far right
3336 +-------o | 14 (14) merge two known; one immediate right, one far right
3337 ' ' ' | |/
3337 ' ' ' | |/
3338 ' ' ' o | 13 (13) expand
3338 ' ' ' o | 13 (13) expand
3339 ' ' ' |\ \
3339 ' ' ' |\ \
3340 ' +---+---o 12 (12) merge two known; one immediate right, one far left
3340 ' +---+---o 12 (12) merge two known; one immediate right, one far left
3341 ' ' ' | o 11 (11) expand
3341 ' ' ' | o 11 (11) expand
3342 ' ' ' | |\
3342 ' ' ' | |\
3343 +---------o 10 (10) merge two known; one immediate left, one near right
3343 +---------o 10 (10) merge two known; one immediate left, one near right
3344 ' ' ' | |/
3344 ' ' ' | |/
3345 ' ' ' o | 9 (9) expand
3345 ' ' ' o | 9 (9) expand
3346 ' ' ' |\ \
3346 ' ' ' |\ \
3347 +-------o | 8 (8) merge two known; one immediate left, one far right
3347 +-------o | 8 (8) merge two known; one immediate left, one far right
3348 ' ' ' |/ /
3348 ' ' ' |/ /
3349 ' ' ' o | 7 (7) expand
3349 ' ' ' o | 7 (7) expand
3350 ' ' ' |\ \
3350 ' ' ' |\ \
3351 ' ' ' +---o 6 (6) merge two known; one immediate left, one far left
3351 ' ' ' +---o 6 (6) merge two known; one immediate left, one far left
3352 ' ' ' | '/
3352 ' ' ' | '/
3353 ' ' ' o ' 5 (5) expand
3353 ' ' ' o ' 5 (5) expand
3354 ' ' ' |\ \
3354 ' ' ' |\ \
3355 ' +---o ' ' 4 (4) merge two known; one immediate left, one immediate right
3355 ' +---o ' ' 4 (4) merge two known; one immediate left, one immediate right
3356 ' ' ' '/ /
3356 ' ' ' '/ /
3357
3357
3358 behavior with newlines
3358 behavior with newlines
3359
3359
3360 $ hg log -G -r ::2 -T '{rev} {desc}'
3360 $ hg log -G -r ::2 -T '{rev} {desc}'
3361 o 2 (2) collapse
3361 o 2 (2) collapse
3362 o 1 (1) collapse
3362 o 1 (1) collapse
3363 o 0 (0) root
3363 o 0 (0) root
3364
3364
3365 $ hg log -G -r ::2 -T '{rev} {desc}\n'
3365 $ hg log -G -r ::2 -T '{rev} {desc}\n'
3366 o 2 (2) collapse
3366 o 2 (2) collapse
3367 o 1 (1) collapse
3367 o 1 (1) collapse
3368 o 0 (0) root
3368 o 0 (0) root
3369
3369
3370 $ hg log -G -r ::2 -T '{rev} {desc}\n\n'
3370 $ hg log -G -r ::2 -T '{rev} {desc}\n\n'
3371 o 2 (2) collapse
3371 o 2 (2) collapse
3372 |
3372 |
3373 o 1 (1) collapse
3373 o 1 (1) collapse
3374 |
3374 |
3375 o 0 (0) root
3375 o 0 (0) root
3376
3376
3377
3377
3378 $ hg log -G -r ::2 -T '\n{rev} {desc}'
3378 $ hg log -G -r ::2 -T '\n{rev} {desc}'
3379 o
3379 o
3380 | 2 (2) collapse
3380 | 2 (2) collapse
3381 o
3381 o
3382 | 1 (1) collapse
3382 | 1 (1) collapse
3383 o
3383 o
3384 0 (0) root
3384 0 (0) root
3385
3385
3386 $ hg log -G -r ::2 -T '{rev} {desc}\n\n\n'
3386 $ hg log -G -r ::2 -T '{rev} {desc}\n\n\n'
3387 o 2 (2) collapse
3387 o 2 (2) collapse
3388 |
3388 |
3389 |
3389 |
3390 o 1 (1) collapse
3390 o 1 (1) collapse
3391 |
3391 |
3392 |
3392 |
3393 o 0 (0) root
3393 o 0 (0) root
3394
3394
3395
3395
3396 $ cd ..
3396 $ cd ..
3397
3397
3398 When inserting extra line nodes to handle more than 2 parents, ensure that
3398 When inserting extra line nodes to handle more than 2 parents, ensure that
3399 the right node styles are used (issue5174):
3399 the right node styles are used (issue5174):
3400
3400
3401 $ hg init repo-issue5174
3401 $ hg init repo-issue5174
3402 $ cd repo-issue5174
3402 $ cd repo-issue5174
3403 $ echo a > f0
3403 $ echo a > f0
3404 $ hg ci -Aqm 0
3404 $ hg ci -Aqm 0
3405 $ echo a > f1
3405 $ echo a > f1
3406 $ hg ci -Aqm 1
3406 $ hg ci -Aqm 1
3407 $ echo a > f2
3407 $ echo a > f2
3408 $ hg ci -Aqm 2
3408 $ hg ci -Aqm 2
3409 $ hg co ".^"
3409 $ hg co ".^"
3410 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
3410 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
3411 $ echo a > f3
3411 $ echo a > f3
3412 $ hg ci -Aqm 3
3412 $ hg ci -Aqm 3
3413 $ hg co ".^^"
3413 $ hg co ".^^"
3414 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
3414 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
3415 $ echo a > f4
3415 $ echo a > f4
3416 $ hg ci -Aqm 4
3416 $ hg ci -Aqm 4
3417 $ hg merge -r 2
3417 $ hg merge -r 2
3418 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
3418 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
3419 (branch merge, don't forget to commit)
3419 (branch merge, don't forget to commit)
3420 $ hg ci -qm 5
3420 $ hg ci -qm 5
3421 $ hg merge -r 3
3421 $ hg merge -r 3
3422 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3422 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3423 (branch merge, don't forget to commit)
3423 (branch merge, don't forget to commit)
3424 $ hg ci -qm 6
3424 $ hg ci -qm 6
3425 $ hg log -G -r '0 | 1 | 2 | 6'
3425 $ hg log -G -r '0 | 1 | 2 | 6'
3426 @ changeset: 6:851fe89689ad
3426 @ changeset: 6:851fe89689ad
3427 :\ tag: tip
3427 :\ tag: tip
3428 : : parent: 5:4f1e3cf15f5d
3428 : : parent: 5:4f1e3cf15f5d
3429 : : parent: 3:b74ba7084d2d
3429 : : parent: 3:b74ba7084d2d
3430 : : user: test
3430 : : user: test
3431 : : date: Thu Jan 01 00:00:00 1970 +0000
3431 : : date: Thu Jan 01 00:00:00 1970 +0000
3432 : : summary: 6
3432 : : summary: 6
3433 : :
3433 : :
3434 : \
3434 : \
3435 : :\
3435 : :\
3436 : o : changeset: 2:3e6599df4cce
3436 : o : changeset: 2:3e6599df4cce
3437 : :/ user: test
3437 : :/ user: test
3438 : : date: Thu Jan 01 00:00:00 1970 +0000
3438 : : date: Thu Jan 01 00:00:00 1970 +0000
3439 : : summary: 2
3439 : : summary: 2
3440 : :
3440 : :
3441 : o changeset: 1:bd9a55143933
3441 : o changeset: 1:bd9a55143933
3442 :/ user: test
3442 :/ user: test
3443 : date: Thu Jan 01 00:00:00 1970 +0000
3443 : date: Thu Jan 01 00:00:00 1970 +0000
3444 : summary: 1
3444 : summary: 1
3445 :
3445 :
3446 o changeset: 0:870a5edc339c
3446 o changeset: 0:870a5edc339c
3447 user: test
3447 user: test
3448 date: Thu Jan 01 00:00:00 1970 +0000
3448 date: Thu Jan 01 00:00:00 1970 +0000
3449 summary: 0
3449 summary: 0
3450
3450
3451
3451
3452 $ cd ..
3452 $ cd ..
3453
3453
3454 Multiple roots (issue5440):
3454 Multiple roots (issue5440):
3455
3455
3456 $ hg init multiroots
3456 $ hg init multiroots
3457 $ cd multiroots
3457 $ cd multiroots
3458 $ cat <<EOF > .hg/hgrc
3458 $ cat <<EOF > .hg/hgrc
3459 > [ui]
3459 > [ui]
3460 > logtemplate = '{rev} {desc}\n\n'
3460 > logtemplate = '{rev} {desc}\n\n'
3461 > EOF
3461 > EOF
3462
3462
3463 $ touch foo
3463 $ touch foo
3464 $ hg ci -Aqm foo
3464 $ hg ci -Aqm foo
3465 $ hg co -q null
3465 $ hg co -q null
3466 $ touch bar
3466 $ touch bar
3467 $ hg ci -Aqm bar
3467 $ hg ci -Aqm bar
3468
3468
3469 $ hg log -Gr null:
3469 $ hg log -Gr null:
3470 @ 1 bar
3470 @ 1 bar
3471 |
3471 |
3472 | o 0 foo
3472 | o 0 foo
3473 |/
3473 |/
3474 o -1
3474 o -1
3475
3475
3476 $ hg log -Gr null+0
3476 $ hg log -Gr null+0
3477 o 0 foo
3477 o 0 foo
3478 |
3478 |
3479 o -1
3479 o -1
3480
3480
3481 $ hg log -Gr null+1
3481 $ hg log -Gr null+1
3482 @ 1 bar
3482 @ 1 bar
3483 |
3483 |
3484 o -1
3484 o -1
3485
3485
3486
3486
3487 $ cd ..
3487 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now