##// END OF EJS Templates
templatekw: move defaulttmpl constant from changeset_templater...
Yuya Nishihara -
r31171:1ec89cf0 default
parent child Browse files
Show More
@@ -1,3475 +1,3467
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 bin,
17 bin,
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23
23
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 changelog,
26 changelog,
27 copies,
27 copies,
28 crecord as crecordmod,
28 crecord as crecordmod,
29 encoding,
29 encoding,
30 error,
30 error,
31 formatter,
31 formatter,
32 graphmod,
32 graphmod,
33 lock as lockmod,
33 lock as lockmod,
34 match as matchmod,
34 match as matchmod,
35 obsolete,
35 obsolete,
36 patch,
36 patch,
37 pathutil,
37 pathutil,
38 phases,
38 phases,
39 pycompat,
39 pycompat,
40 repair,
40 repair,
41 revlog,
41 revlog,
42 revset,
42 revset,
43 scmutil,
43 scmutil,
44 smartset,
44 smartset,
45 templatekw,
45 templatekw,
46 templater,
46 templater,
47 util,
47 util,
48 )
48 )
49 stringio = util.stringio
49 stringio = util.stringio
50
50
51 # special string such that everything below this line will be ingored in the
51 # special string such that everything below this line will be ingored in the
52 # editor text
52 # editor text
53 _linebelow = "^HG: ------------------------ >8 ------------------------$"
53 _linebelow = "^HG: ------------------------ >8 ------------------------$"
54
54
55 def ishunk(x):
55 def ishunk(x):
56 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
56 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
57 return isinstance(x, hunkclasses)
57 return isinstance(x, hunkclasses)
58
58
59 def newandmodified(chunks, originalchunks):
59 def newandmodified(chunks, originalchunks):
60 newlyaddedandmodifiedfiles = set()
60 newlyaddedandmodifiedfiles = set()
61 for chunk in chunks:
61 for chunk in chunks:
62 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
62 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
63 originalchunks:
63 originalchunks:
64 newlyaddedandmodifiedfiles.add(chunk.header.filename())
64 newlyaddedandmodifiedfiles.add(chunk.header.filename())
65 return newlyaddedandmodifiedfiles
65 return newlyaddedandmodifiedfiles
66
66
67 def parsealiases(cmd):
67 def parsealiases(cmd):
68 return cmd.lstrip("^").split("|")
68 return cmd.lstrip("^").split("|")
69
69
70 def setupwrapcolorwrite(ui):
70 def setupwrapcolorwrite(ui):
71 # wrap ui.write so diff output can be labeled/colorized
71 # wrap ui.write so diff output can be labeled/colorized
72 def wrapwrite(orig, *args, **kw):
72 def wrapwrite(orig, *args, **kw):
73 label = kw.pop('label', '')
73 label = kw.pop('label', '')
74 for chunk, l in patch.difflabel(lambda: args):
74 for chunk, l in patch.difflabel(lambda: args):
75 orig(chunk, label=label + l)
75 orig(chunk, label=label + l)
76
76
77 oldwrite = ui.write
77 oldwrite = ui.write
78 def wrap(*args, **kwargs):
78 def wrap(*args, **kwargs):
79 return wrapwrite(oldwrite, *args, **kwargs)
79 return wrapwrite(oldwrite, *args, **kwargs)
80 setattr(ui, 'write', wrap)
80 setattr(ui, 'write', wrap)
81 return oldwrite
81 return oldwrite
82
82
83 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
83 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
84 if usecurses:
84 if usecurses:
85 if testfile:
85 if testfile:
86 recordfn = crecordmod.testdecorator(testfile,
86 recordfn = crecordmod.testdecorator(testfile,
87 crecordmod.testchunkselector)
87 crecordmod.testchunkselector)
88 else:
88 else:
89 recordfn = crecordmod.chunkselector
89 recordfn = crecordmod.chunkselector
90
90
91 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
91 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
92
92
93 else:
93 else:
94 return patch.filterpatch(ui, originalhunks, operation)
94 return patch.filterpatch(ui, originalhunks, operation)
95
95
96 def recordfilter(ui, originalhunks, operation=None):
96 def recordfilter(ui, originalhunks, operation=None):
97 """ Prompts the user to filter the originalhunks and return a list of
97 """ Prompts the user to filter the originalhunks and return a list of
98 selected hunks.
98 selected hunks.
99 *operation* is used for to build ui messages to indicate the user what
99 *operation* is used for to build ui messages to indicate the user what
100 kind of filtering they are doing: reverting, committing, shelving, etc.
100 kind of filtering they are doing: reverting, committing, shelving, etc.
101 (see patch.filterpatch).
101 (see patch.filterpatch).
102 """
102 """
103 usecurses = crecordmod.checkcurses(ui)
103 usecurses = crecordmod.checkcurses(ui)
104 testfile = ui.config('experimental', 'crecordtest', None)
104 testfile = ui.config('experimental', 'crecordtest', None)
105 oldwrite = setupwrapcolorwrite(ui)
105 oldwrite = setupwrapcolorwrite(ui)
106 try:
106 try:
107 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
107 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
108 testfile, operation)
108 testfile, operation)
109 finally:
109 finally:
110 ui.write = oldwrite
110 ui.write = oldwrite
111 return newchunks, newopts
111 return newchunks, newopts
112
112
113 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
113 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
114 filterfn, *pats, **opts):
114 filterfn, *pats, **opts):
115 from . import merge as mergemod
115 from . import merge as mergemod
116 if not ui.interactive():
116 if not ui.interactive():
117 if cmdsuggest:
117 if cmdsuggest:
118 msg = _('running non-interactively, use %s instead') % cmdsuggest
118 msg = _('running non-interactively, use %s instead') % cmdsuggest
119 else:
119 else:
120 msg = _('running non-interactively')
120 msg = _('running non-interactively')
121 raise error.Abort(msg)
121 raise error.Abort(msg)
122
122
123 # make sure username is set before going interactive
123 # make sure username is set before going interactive
124 if not opts.get('user'):
124 if not opts.get('user'):
125 ui.username() # raise exception, username not provided
125 ui.username() # raise exception, username not provided
126
126
127 def recordfunc(ui, repo, message, match, opts):
127 def recordfunc(ui, repo, message, match, opts):
128 """This is generic record driver.
128 """This is generic record driver.
129
129
130 Its job is to interactively filter local changes, and
130 Its job is to interactively filter local changes, and
131 accordingly prepare working directory into a state in which the
131 accordingly prepare working directory into a state in which the
132 job can be delegated to a non-interactive commit command such as
132 job can be delegated to a non-interactive commit command such as
133 'commit' or 'qrefresh'.
133 'commit' or 'qrefresh'.
134
134
135 After the actual job is done by non-interactive command, the
135 After the actual job is done by non-interactive command, the
136 working directory is restored to its original state.
136 working directory is restored to its original state.
137
137
138 In the end we'll record interesting changes, and everything else
138 In the end we'll record interesting changes, and everything else
139 will be left in place, so the user can continue working.
139 will be left in place, so the user can continue working.
140 """
140 """
141
141
142 checkunfinished(repo, commit=True)
142 checkunfinished(repo, commit=True)
143 wctx = repo[None]
143 wctx = repo[None]
144 merge = len(wctx.parents()) > 1
144 merge = len(wctx.parents()) > 1
145 if merge:
145 if merge:
146 raise error.Abort(_('cannot partially commit a merge '
146 raise error.Abort(_('cannot partially commit a merge '
147 '(use "hg commit" instead)'))
147 '(use "hg commit" instead)'))
148
148
149 def fail(f, msg):
149 def fail(f, msg):
150 raise error.Abort('%s: %s' % (f, msg))
150 raise error.Abort('%s: %s' % (f, msg))
151
151
152 force = opts.get('force')
152 force = opts.get('force')
153 if not force:
153 if not force:
154 vdirs = []
154 vdirs = []
155 match.explicitdir = vdirs.append
155 match.explicitdir = vdirs.append
156 match.bad = fail
156 match.bad = fail
157
157
158 status = repo.status(match=match)
158 status = repo.status(match=match)
159 if not force:
159 if not force:
160 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
160 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
161 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
161 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
162 diffopts.nodates = True
162 diffopts.nodates = True
163 diffopts.git = True
163 diffopts.git = True
164 diffopts.showfunc = True
164 diffopts.showfunc = True
165 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
165 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
166 originalchunks = patch.parsepatch(originaldiff)
166 originalchunks = patch.parsepatch(originaldiff)
167
167
168 # 1. filter patch, since we are intending to apply subset of it
168 # 1. filter patch, since we are intending to apply subset of it
169 try:
169 try:
170 chunks, newopts = filterfn(ui, originalchunks)
170 chunks, newopts = filterfn(ui, originalchunks)
171 except patch.PatchError as err:
171 except patch.PatchError as err:
172 raise error.Abort(_('error parsing patch: %s') % err)
172 raise error.Abort(_('error parsing patch: %s') % err)
173 opts.update(newopts)
173 opts.update(newopts)
174
174
175 # We need to keep a backup of files that have been newly added and
175 # We need to keep a backup of files that have been newly added and
176 # modified during the recording process because there is a previous
176 # modified during the recording process because there is a previous
177 # version without the edit in the workdir
177 # version without the edit in the workdir
178 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
178 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
179 contenders = set()
179 contenders = set()
180 for h in chunks:
180 for h in chunks:
181 try:
181 try:
182 contenders.update(set(h.files()))
182 contenders.update(set(h.files()))
183 except AttributeError:
183 except AttributeError:
184 pass
184 pass
185
185
186 changed = status.modified + status.added + status.removed
186 changed = status.modified + status.added + status.removed
187 newfiles = [f for f in changed if f in contenders]
187 newfiles = [f for f in changed if f in contenders]
188 if not newfiles:
188 if not newfiles:
189 ui.status(_('no changes to record\n'))
189 ui.status(_('no changes to record\n'))
190 return 0
190 return 0
191
191
192 modified = set(status.modified)
192 modified = set(status.modified)
193
193
194 # 2. backup changed files, so we can restore them in the end
194 # 2. backup changed files, so we can restore them in the end
195
195
196 if backupall:
196 if backupall:
197 tobackup = changed
197 tobackup = changed
198 else:
198 else:
199 tobackup = [f for f in newfiles if f in modified or f in \
199 tobackup = [f for f in newfiles if f in modified or f in \
200 newlyaddedandmodifiedfiles]
200 newlyaddedandmodifiedfiles]
201 backups = {}
201 backups = {}
202 if tobackup:
202 if tobackup:
203 backupdir = repo.join('record-backups')
203 backupdir = repo.join('record-backups')
204 try:
204 try:
205 os.mkdir(backupdir)
205 os.mkdir(backupdir)
206 except OSError as err:
206 except OSError as err:
207 if err.errno != errno.EEXIST:
207 if err.errno != errno.EEXIST:
208 raise
208 raise
209 try:
209 try:
210 # backup continues
210 # backup continues
211 for f in tobackup:
211 for f in tobackup:
212 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
212 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
213 dir=backupdir)
213 dir=backupdir)
214 os.close(fd)
214 os.close(fd)
215 ui.debug('backup %r as %r\n' % (f, tmpname))
215 ui.debug('backup %r as %r\n' % (f, tmpname))
216 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
216 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
217 backups[f] = tmpname
217 backups[f] = tmpname
218
218
219 fp = stringio()
219 fp = stringio()
220 for c in chunks:
220 for c in chunks:
221 fname = c.filename()
221 fname = c.filename()
222 if fname in backups:
222 if fname in backups:
223 c.write(fp)
223 c.write(fp)
224 dopatch = fp.tell()
224 dopatch = fp.tell()
225 fp.seek(0)
225 fp.seek(0)
226
226
227 # 2.5 optionally review / modify patch in text editor
227 # 2.5 optionally review / modify patch in text editor
228 if opts.get('review', False):
228 if opts.get('review', False):
229 patchtext = (crecordmod.diffhelptext
229 patchtext = (crecordmod.diffhelptext
230 + crecordmod.patchhelptext
230 + crecordmod.patchhelptext
231 + fp.read())
231 + fp.read())
232 reviewedpatch = ui.edit(patchtext, "",
232 reviewedpatch = ui.edit(patchtext, "",
233 extra={"suffix": ".diff"},
233 extra={"suffix": ".diff"},
234 repopath=repo.path)
234 repopath=repo.path)
235 fp.truncate(0)
235 fp.truncate(0)
236 fp.write(reviewedpatch)
236 fp.write(reviewedpatch)
237 fp.seek(0)
237 fp.seek(0)
238
238
239 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
239 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
240 # 3a. apply filtered patch to clean repo (clean)
240 # 3a. apply filtered patch to clean repo (clean)
241 if backups:
241 if backups:
242 # Equivalent to hg.revert
242 # Equivalent to hg.revert
243 m = scmutil.matchfiles(repo, backups.keys())
243 m = scmutil.matchfiles(repo, backups.keys())
244 mergemod.update(repo, repo.dirstate.p1(),
244 mergemod.update(repo, repo.dirstate.p1(),
245 False, True, matcher=m)
245 False, True, matcher=m)
246
246
247 # 3b. (apply)
247 # 3b. (apply)
248 if dopatch:
248 if dopatch:
249 try:
249 try:
250 ui.debug('applying patch\n')
250 ui.debug('applying patch\n')
251 ui.debug(fp.getvalue())
251 ui.debug(fp.getvalue())
252 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
252 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
253 except patch.PatchError as err:
253 except patch.PatchError as err:
254 raise error.Abort(str(err))
254 raise error.Abort(str(err))
255 del fp
255 del fp
256
256
257 # 4. We prepared working directory according to filtered
257 # 4. We prepared working directory according to filtered
258 # patch. Now is the time to delegate the job to
258 # patch. Now is the time to delegate the job to
259 # commit/qrefresh or the like!
259 # commit/qrefresh or the like!
260
260
261 # Make all of the pathnames absolute.
261 # Make all of the pathnames absolute.
262 newfiles = [repo.wjoin(nf) for nf in newfiles]
262 newfiles = [repo.wjoin(nf) for nf in newfiles]
263 return commitfunc(ui, repo, *newfiles, **opts)
263 return commitfunc(ui, repo, *newfiles, **opts)
264 finally:
264 finally:
265 # 5. finally restore backed-up files
265 # 5. finally restore backed-up files
266 try:
266 try:
267 dirstate = repo.dirstate
267 dirstate = repo.dirstate
268 for realname, tmpname in backups.iteritems():
268 for realname, tmpname in backups.iteritems():
269 ui.debug('restoring %r to %r\n' % (tmpname, realname))
269 ui.debug('restoring %r to %r\n' % (tmpname, realname))
270
270
271 if dirstate[realname] == 'n':
271 if dirstate[realname] == 'n':
272 # without normallookup, restoring timestamp
272 # without normallookup, restoring timestamp
273 # may cause partially committed files
273 # may cause partially committed files
274 # to be treated as unmodified
274 # to be treated as unmodified
275 dirstate.normallookup(realname)
275 dirstate.normallookup(realname)
276
276
277 # copystat=True here and above are a hack to trick any
277 # copystat=True here and above are a hack to trick any
278 # editors that have f open that we haven't modified them.
278 # editors that have f open that we haven't modified them.
279 #
279 #
280 # Also note that this racy as an editor could notice the
280 # Also note that this racy as an editor could notice the
281 # file's mtime before we've finished writing it.
281 # file's mtime before we've finished writing it.
282 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
282 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
283 os.unlink(tmpname)
283 os.unlink(tmpname)
284 if tobackup:
284 if tobackup:
285 os.rmdir(backupdir)
285 os.rmdir(backupdir)
286 except OSError:
286 except OSError:
287 pass
287 pass
288
288
289 def recordinwlock(ui, repo, message, match, opts):
289 def recordinwlock(ui, repo, message, match, opts):
290 with repo.wlock():
290 with repo.wlock():
291 return recordfunc(ui, repo, message, match, opts)
291 return recordfunc(ui, repo, message, match, opts)
292
292
293 return commit(ui, repo, recordinwlock, pats, opts)
293 return commit(ui, repo, recordinwlock, pats, opts)
294
294
295 def findpossible(cmd, table, strict=False):
295 def findpossible(cmd, table, strict=False):
296 """
296 """
297 Return cmd -> (aliases, command table entry)
297 Return cmd -> (aliases, command table entry)
298 for each matching command.
298 for each matching command.
299 Return debug commands (or their aliases) only if no normal command matches.
299 Return debug commands (or their aliases) only if no normal command matches.
300 """
300 """
301 choice = {}
301 choice = {}
302 debugchoice = {}
302 debugchoice = {}
303
303
304 if cmd in table:
304 if cmd in table:
305 # short-circuit exact matches, "log" alias beats "^log|history"
305 # short-circuit exact matches, "log" alias beats "^log|history"
306 keys = [cmd]
306 keys = [cmd]
307 else:
307 else:
308 keys = table.keys()
308 keys = table.keys()
309
309
310 allcmds = []
310 allcmds = []
311 for e in keys:
311 for e in keys:
312 aliases = parsealiases(e)
312 aliases = parsealiases(e)
313 allcmds.extend(aliases)
313 allcmds.extend(aliases)
314 found = None
314 found = None
315 if cmd in aliases:
315 if cmd in aliases:
316 found = cmd
316 found = cmd
317 elif not strict:
317 elif not strict:
318 for a in aliases:
318 for a in aliases:
319 if a.startswith(cmd):
319 if a.startswith(cmd):
320 found = a
320 found = a
321 break
321 break
322 if found is not None:
322 if found is not None:
323 if aliases[0].startswith("debug") or found.startswith("debug"):
323 if aliases[0].startswith("debug") or found.startswith("debug"):
324 debugchoice[found] = (aliases, table[e])
324 debugchoice[found] = (aliases, table[e])
325 else:
325 else:
326 choice[found] = (aliases, table[e])
326 choice[found] = (aliases, table[e])
327
327
328 if not choice and debugchoice:
328 if not choice and debugchoice:
329 choice = debugchoice
329 choice = debugchoice
330
330
331 return choice, allcmds
331 return choice, allcmds
332
332
333 def findcmd(cmd, table, strict=True):
333 def findcmd(cmd, table, strict=True):
334 """Return (aliases, command table entry) for command string."""
334 """Return (aliases, command table entry) for command string."""
335 choice, allcmds = findpossible(cmd, table, strict)
335 choice, allcmds = findpossible(cmd, table, strict)
336
336
337 if cmd in choice:
337 if cmd in choice:
338 return choice[cmd]
338 return choice[cmd]
339
339
340 if len(choice) > 1:
340 if len(choice) > 1:
341 clist = choice.keys()
341 clist = choice.keys()
342 clist.sort()
342 clist.sort()
343 raise error.AmbiguousCommand(cmd, clist)
343 raise error.AmbiguousCommand(cmd, clist)
344
344
345 if choice:
345 if choice:
346 return choice.values()[0]
346 return choice.values()[0]
347
347
348 raise error.UnknownCommand(cmd, allcmds)
348 raise error.UnknownCommand(cmd, allcmds)
349
349
350 def findrepo(p):
350 def findrepo(p):
351 while not os.path.isdir(os.path.join(p, ".hg")):
351 while not os.path.isdir(os.path.join(p, ".hg")):
352 oldp, p = p, os.path.dirname(p)
352 oldp, p = p, os.path.dirname(p)
353 if p == oldp:
353 if p == oldp:
354 return None
354 return None
355
355
356 return p
356 return p
357
357
358 def bailifchanged(repo, merge=True, hint=None):
358 def bailifchanged(repo, merge=True, hint=None):
359 """ enforce the precondition that working directory must be clean.
359 """ enforce the precondition that working directory must be clean.
360
360
361 'merge' can be set to false if a pending uncommitted merge should be
361 'merge' can be set to false if a pending uncommitted merge should be
362 ignored (such as when 'update --check' runs).
362 ignored (such as when 'update --check' runs).
363
363
364 'hint' is the usual hint given to Abort exception.
364 'hint' is the usual hint given to Abort exception.
365 """
365 """
366
366
367 if merge and repo.dirstate.p2() != nullid:
367 if merge and repo.dirstate.p2() != nullid:
368 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
368 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
369 modified, added, removed, deleted = repo.status()[:4]
369 modified, added, removed, deleted = repo.status()[:4]
370 if modified or added or removed or deleted:
370 if modified or added or removed or deleted:
371 raise error.Abort(_('uncommitted changes'), hint=hint)
371 raise error.Abort(_('uncommitted changes'), hint=hint)
372 ctx = repo[None]
372 ctx = repo[None]
373 for s in sorted(ctx.substate):
373 for s in sorted(ctx.substate):
374 ctx.sub(s).bailifchanged(hint=hint)
374 ctx.sub(s).bailifchanged(hint=hint)
375
375
376 def logmessage(ui, opts):
376 def logmessage(ui, opts):
377 """ get the log message according to -m and -l option """
377 """ get the log message according to -m and -l option """
378 message = opts.get('message')
378 message = opts.get('message')
379 logfile = opts.get('logfile')
379 logfile = opts.get('logfile')
380
380
381 if message and logfile:
381 if message and logfile:
382 raise error.Abort(_('options --message and --logfile are mutually '
382 raise error.Abort(_('options --message and --logfile are mutually '
383 'exclusive'))
383 'exclusive'))
384 if not message and logfile:
384 if not message and logfile:
385 try:
385 try:
386 if logfile == '-':
386 if logfile == '-':
387 message = ui.fin.read()
387 message = ui.fin.read()
388 else:
388 else:
389 message = '\n'.join(util.readfile(logfile).splitlines())
389 message = '\n'.join(util.readfile(logfile).splitlines())
390 except IOError as inst:
390 except IOError as inst:
391 raise error.Abort(_("can't read commit message '%s': %s") %
391 raise error.Abort(_("can't read commit message '%s': %s") %
392 (logfile, inst.strerror))
392 (logfile, inst.strerror))
393 return message
393 return message
394
394
395 def mergeeditform(ctxorbool, baseformname):
395 def mergeeditform(ctxorbool, baseformname):
396 """return appropriate editform name (referencing a committemplate)
396 """return appropriate editform name (referencing a committemplate)
397
397
398 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
398 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
399 merging is committed.
399 merging is committed.
400
400
401 This returns baseformname with '.merge' appended if it is a merge,
401 This returns baseformname with '.merge' appended if it is a merge,
402 otherwise '.normal' is appended.
402 otherwise '.normal' is appended.
403 """
403 """
404 if isinstance(ctxorbool, bool):
404 if isinstance(ctxorbool, bool):
405 if ctxorbool:
405 if ctxorbool:
406 return baseformname + ".merge"
406 return baseformname + ".merge"
407 elif 1 < len(ctxorbool.parents()):
407 elif 1 < len(ctxorbool.parents()):
408 return baseformname + ".merge"
408 return baseformname + ".merge"
409
409
410 return baseformname + ".normal"
410 return baseformname + ".normal"
411
411
412 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
412 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
413 editform='', **opts):
413 editform='', **opts):
414 """get appropriate commit message editor according to '--edit' option
414 """get appropriate commit message editor according to '--edit' option
415
415
416 'finishdesc' is a function to be called with edited commit message
416 'finishdesc' is a function to be called with edited commit message
417 (= 'description' of the new changeset) just after editing, but
417 (= 'description' of the new changeset) just after editing, but
418 before checking empty-ness. It should return actual text to be
418 before checking empty-ness. It should return actual text to be
419 stored into history. This allows to change description before
419 stored into history. This allows to change description before
420 storing.
420 storing.
421
421
422 'extramsg' is a extra message to be shown in the editor instead of
422 'extramsg' is a extra message to be shown in the editor instead of
423 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
423 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
424 is automatically added.
424 is automatically added.
425
425
426 'editform' is a dot-separated list of names, to distinguish
426 'editform' is a dot-separated list of names, to distinguish
427 the purpose of commit text editing.
427 the purpose of commit text editing.
428
428
429 'getcommiteditor' returns 'commitforceeditor' regardless of
429 'getcommiteditor' returns 'commitforceeditor' regardless of
430 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
430 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
431 they are specific for usage in MQ.
431 they are specific for usage in MQ.
432 """
432 """
433 if edit or finishdesc or extramsg:
433 if edit or finishdesc or extramsg:
434 return lambda r, c, s: commitforceeditor(r, c, s,
434 return lambda r, c, s: commitforceeditor(r, c, s,
435 finishdesc=finishdesc,
435 finishdesc=finishdesc,
436 extramsg=extramsg,
436 extramsg=extramsg,
437 editform=editform)
437 editform=editform)
438 elif editform:
438 elif editform:
439 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
439 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
440 else:
440 else:
441 return commiteditor
441 return commiteditor
442
442
443 def loglimit(opts):
443 def loglimit(opts):
444 """get the log limit according to option -l/--limit"""
444 """get the log limit according to option -l/--limit"""
445 limit = opts.get('limit')
445 limit = opts.get('limit')
446 if limit:
446 if limit:
447 try:
447 try:
448 limit = int(limit)
448 limit = int(limit)
449 except ValueError:
449 except ValueError:
450 raise error.Abort(_('limit must be a positive integer'))
450 raise error.Abort(_('limit must be a positive integer'))
451 if limit <= 0:
451 if limit <= 0:
452 raise error.Abort(_('limit must be positive'))
452 raise error.Abort(_('limit must be positive'))
453 else:
453 else:
454 limit = None
454 limit = None
455 return limit
455 return limit
456
456
457 def makefilename(repo, pat, node, desc=None,
457 def makefilename(repo, pat, node, desc=None,
458 total=None, seqno=None, revwidth=None, pathname=None):
458 total=None, seqno=None, revwidth=None, pathname=None):
459 node_expander = {
459 node_expander = {
460 'H': lambda: hex(node),
460 'H': lambda: hex(node),
461 'R': lambda: str(repo.changelog.rev(node)),
461 'R': lambda: str(repo.changelog.rev(node)),
462 'h': lambda: short(node),
462 'h': lambda: short(node),
463 'm': lambda: re.sub('[^\w]', '_', str(desc))
463 'm': lambda: re.sub('[^\w]', '_', str(desc))
464 }
464 }
465 expander = {
465 expander = {
466 '%': lambda: '%',
466 '%': lambda: '%',
467 'b': lambda: os.path.basename(repo.root),
467 'b': lambda: os.path.basename(repo.root),
468 }
468 }
469
469
470 try:
470 try:
471 if node:
471 if node:
472 expander.update(node_expander)
472 expander.update(node_expander)
473 if node:
473 if node:
474 expander['r'] = (lambda:
474 expander['r'] = (lambda:
475 str(repo.changelog.rev(node)).zfill(revwidth or 0))
475 str(repo.changelog.rev(node)).zfill(revwidth or 0))
476 if total is not None:
476 if total is not None:
477 expander['N'] = lambda: str(total)
477 expander['N'] = lambda: str(total)
478 if seqno is not None:
478 if seqno is not None:
479 expander['n'] = lambda: str(seqno)
479 expander['n'] = lambda: str(seqno)
480 if total is not None and seqno is not None:
480 if total is not None and seqno is not None:
481 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
481 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
482 if pathname is not None:
482 if pathname is not None:
483 expander['s'] = lambda: os.path.basename(pathname)
483 expander['s'] = lambda: os.path.basename(pathname)
484 expander['d'] = lambda: os.path.dirname(pathname) or '.'
484 expander['d'] = lambda: os.path.dirname(pathname) or '.'
485 expander['p'] = lambda: pathname
485 expander['p'] = lambda: pathname
486
486
487 newname = []
487 newname = []
488 patlen = len(pat)
488 patlen = len(pat)
489 i = 0
489 i = 0
490 while i < patlen:
490 while i < patlen:
491 c = pat[i]
491 c = pat[i]
492 if c == '%':
492 if c == '%':
493 i += 1
493 i += 1
494 c = pat[i]
494 c = pat[i]
495 c = expander[c]()
495 c = expander[c]()
496 newname.append(c)
496 newname.append(c)
497 i += 1
497 i += 1
498 return ''.join(newname)
498 return ''.join(newname)
499 except KeyError as inst:
499 except KeyError as inst:
500 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
500 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
501 inst.args[0])
501 inst.args[0])
502
502
503 class _unclosablefile(object):
503 class _unclosablefile(object):
504 def __init__(self, fp):
504 def __init__(self, fp):
505 self._fp = fp
505 self._fp = fp
506
506
507 def close(self):
507 def close(self):
508 pass
508 pass
509
509
510 def __iter__(self):
510 def __iter__(self):
511 return iter(self._fp)
511 return iter(self._fp)
512
512
513 def __getattr__(self, attr):
513 def __getattr__(self, attr):
514 return getattr(self._fp, attr)
514 return getattr(self._fp, attr)
515
515
516 def __enter__(self):
516 def __enter__(self):
517 return self
517 return self
518
518
519 def __exit__(self, exc_type, exc_value, exc_tb):
519 def __exit__(self, exc_type, exc_value, exc_tb):
520 pass
520 pass
521
521
522 def makefileobj(repo, pat, node=None, desc=None, total=None,
522 def makefileobj(repo, pat, node=None, desc=None, total=None,
523 seqno=None, revwidth=None, mode='wb', modemap=None,
523 seqno=None, revwidth=None, mode='wb', modemap=None,
524 pathname=None):
524 pathname=None):
525
525
526 writable = mode not in ('r', 'rb')
526 writable = mode not in ('r', 'rb')
527
527
528 if not pat or pat == '-':
528 if not pat or pat == '-':
529 if writable:
529 if writable:
530 fp = repo.ui.fout
530 fp = repo.ui.fout
531 else:
531 else:
532 fp = repo.ui.fin
532 fp = repo.ui.fin
533 return _unclosablefile(fp)
533 return _unclosablefile(fp)
534 if util.safehasattr(pat, 'write') and writable:
534 if util.safehasattr(pat, 'write') and writable:
535 return pat
535 return pat
536 if util.safehasattr(pat, 'read') and 'r' in mode:
536 if util.safehasattr(pat, 'read') and 'r' in mode:
537 return pat
537 return pat
538 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
538 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
539 if modemap is not None:
539 if modemap is not None:
540 mode = modemap.get(fn, mode)
540 mode = modemap.get(fn, mode)
541 if mode == 'wb':
541 if mode == 'wb':
542 modemap[fn] = 'ab'
542 modemap[fn] = 'ab'
543 return open(fn, mode)
543 return open(fn, mode)
544
544
545 def openrevlog(repo, cmd, file_, opts):
545 def openrevlog(repo, cmd, file_, opts):
546 """opens the changelog, manifest, a filelog or a given revlog"""
546 """opens the changelog, manifest, a filelog or a given revlog"""
547 cl = opts['changelog']
547 cl = opts['changelog']
548 mf = opts['manifest']
548 mf = opts['manifest']
549 dir = opts['dir']
549 dir = opts['dir']
550 msg = None
550 msg = None
551 if cl and mf:
551 if cl and mf:
552 msg = _('cannot specify --changelog and --manifest at the same time')
552 msg = _('cannot specify --changelog and --manifest at the same time')
553 elif cl and dir:
553 elif cl and dir:
554 msg = _('cannot specify --changelog and --dir at the same time')
554 msg = _('cannot specify --changelog and --dir at the same time')
555 elif cl or mf or dir:
555 elif cl or mf or dir:
556 if file_:
556 if file_:
557 msg = _('cannot specify filename with --changelog or --manifest')
557 msg = _('cannot specify filename with --changelog or --manifest')
558 elif not repo:
558 elif not repo:
559 msg = _('cannot specify --changelog or --manifest or --dir '
559 msg = _('cannot specify --changelog or --manifest or --dir '
560 'without a repository')
560 'without a repository')
561 if msg:
561 if msg:
562 raise error.Abort(msg)
562 raise error.Abort(msg)
563
563
564 r = None
564 r = None
565 if repo:
565 if repo:
566 if cl:
566 if cl:
567 r = repo.unfiltered().changelog
567 r = repo.unfiltered().changelog
568 elif dir:
568 elif dir:
569 if 'treemanifest' not in repo.requirements:
569 if 'treemanifest' not in repo.requirements:
570 raise error.Abort(_("--dir can only be used on repos with "
570 raise error.Abort(_("--dir can only be used on repos with "
571 "treemanifest enabled"))
571 "treemanifest enabled"))
572 dirlog = repo.manifestlog._revlog.dirlog(dir)
572 dirlog = repo.manifestlog._revlog.dirlog(dir)
573 if len(dirlog):
573 if len(dirlog):
574 r = dirlog
574 r = dirlog
575 elif mf:
575 elif mf:
576 r = repo.manifestlog._revlog
576 r = repo.manifestlog._revlog
577 elif file_:
577 elif file_:
578 filelog = repo.file(file_)
578 filelog = repo.file(file_)
579 if len(filelog):
579 if len(filelog):
580 r = filelog
580 r = filelog
581 if not r:
581 if not r:
582 if not file_:
582 if not file_:
583 raise error.CommandError(cmd, _('invalid arguments'))
583 raise error.CommandError(cmd, _('invalid arguments'))
584 if not os.path.isfile(file_):
584 if not os.path.isfile(file_):
585 raise error.Abort(_("revlog '%s' not found") % file_)
585 raise error.Abort(_("revlog '%s' not found") % file_)
586 r = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False),
586 r = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False),
587 file_[:-2] + ".i")
587 file_[:-2] + ".i")
588 return r
588 return r
589
589
590 def copy(ui, repo, pats, opts, rename=False):
590 def copy(ui, repo, pats, opts, rename=False):
591 # called with the repo lock held
591 # called with the repo lock held
592 #
592 #
593 # hgsep => pathname that uses "/" to separate directories
593 # hgsep => pathname that uses "/" to separate directories
594 # ossep => pathname that uses os.sep to separate directories
594 # ossep => pathname that uses os.sep to separate directories
595 cwd = repo.getcwd()
595 cwd = repo.getcwd()
596 targets = {}
596 targets = {}
597 after = opts.get("after")
597 after = opts.get("after")
598 dryrun = opts.get("dry_run")
598 dryrun = opts.get("dry_run")
599 wctx = repo[None]
599 wctx = repo[None]
600
600
601 def walkpat(pat):
601 def walkpat(pat):
602 srcs = []
602 srcs = []
603 if after:
603 if after:
604 badstates = '?'
604 badstates = '?'
605 else:
605 else:
606 badstates = '?r'
606 badstates = '?r'
607 m = scmutil.match(repo[None], [pat], opts, globbed=True)
607 m = scmutil.match(repo[None], [pat], opts, globbed=True)
608 for abs in repo.walk(m):
608 for abs in repo.walk(m):
609 state = repo.dirstate[abs]
609 state = repo.dirstate[abs]
610 rel = m.rel(abs)
610 rel = m.rel(abs)
611 exact = m.exact(abs)
611 exact = m.exact(abs)
612 if state in badstates:
612 if state in badstates:
613 if exact and state == '?':
613 if exact and state == '?':
614 ui.warn(_('%s: not copying - file is not managed\n') % rel)
614 ui.warn(_('%s: not copying - file is not managed\n') % rel)
615 if exact and state == 'r':
615 if exact and state == 'r':
616 ui.warn(_('%s: not copying - file has been marked for'
616 ui.warn(_('%s: not copying - file has been marked for'
617 ' remove\n') % rel)
617 ' remove\n') % rel)
618 continue
618 continue
619 # abs: hgsep
619 # abs: hgsep
620 # rel: ossep
620 # rel: ossep
621 srcs.append((abs, rel, exact))
621 srcs.append((abs, rel, exact))
622 return srcs
622 return srcs
623
623
624 # abssrc: hgsep
624 # abssrc: hgsep
625 # relsrc: ossep
625 # relsrc: ossep
626 # otarget: ossep
626 # otarget: ossep
627 def copyfile(abssrc, relsrc, otarget, exact):
627 def copyfile(abssrc, relsrc, otarget, exact):
628 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
628 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
629 if '/' in abstarget:
629 if '/' in abstarget:
630 # We cannot normalize abstarget itself, this would prevent
630 # We cannot normalize abstarget itself, this would prevent
631 # case only renames, like a => A.
631 # case only renames, like a => A.
632 abspath, absname = abstarget.rsplit('/', 1)
632 abspath, absname = abstarget.rsplit('/', 1)
633 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
633 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
634 reltarget = repo.pathto(abstarget, cwd)
634 reltarget = repo.pathto(abstarget, cwd)
635 target = repo.wjoin(abstarget)
635 target = repo.wjoin(abstarget)
636 src = repo.wjoin(abssrc)
636 src = repo.wjoin(abssrc)
637 state = repo.dirstate[abstarget]
637 state = repo.dirstate[abstarget]
638
638
639 scmutil.checkportable(ui, abstarget)
639 scmutil.checkportable(ui, abstarget)
640
640
641 # check for collisions
641 # check for collisions
642 prevsrc = targets.get(abstarget)
642 prevsrc = targets.get(abstarget)
643 if prevsrc is not None:
643 if prevsrc is not None:
644 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
644 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
645 (reltarget, repo.pathto(abssrc, cwd),
645 (reltarget, repo.pathto(abssrc, cwd),
646 repo.pathto(prevsrc, cwd)))
646 repo.pathto(prevsrc, cwd)))
647 return
647 return
648
648
649 # check for overwrites
649 # check for overwrites
650 exists = os.path.lexists(target)
650 exists = os.path.lexists(target)
651 samefile = False
651 samefile = False
652 if exists and abssrc != abstarget:
652 if exists and abssrc != abstarget:
653 if (repo.dirstate.normalize(abssrc) ==
653 if (repo.dirstate.normalize(abssrc) ==
654 repo.dirstate.normalize(abstarget)):
654 repo.dirstate.normalize(abstarget)):
655 if not rename:
655 if not rename:
656 ui.warn(_("%s: can't copy - same file\n") % reltarget)
656 ui.warn(_("%s: can't copy - same file\n") % reltarget)
657 return
657 return
658 exists = False
658 exists = False
659 samefile = True
659 samefile = True
660
660
661 if not after and exists or after and state in 'mn':
661 if not after and exists or after and state in 'mn':
662 if not opts['force']:
662 if not opts['force']:
663 if state in 'mn':
663 if state in 'mn':
664 msg = _('%s: not overwriting - file already committed\n')
664 msg = _('%s: not overwriting - file already committed\n')
665 if after:
665 if after:
666 flags = '--after --force'
666 flags = '--after --force'
667 else:
667 else:
668 flags = '--force'
668 flags = '--force'
669 if rename:
669 if rename:
670 hint = _('(hg rename %s to replace the file by '
670 hint = _('(hg rename %s to replace the file by '
671 'recording a rename)\n') % flags
671 'recording a rename)\n') % flags
672 else:
672 else:
673 hint = _('(hg copy %s to replace the file by '
673 hint = _('(hg copy %s to replace the file by '
674 'recording a copy)\n') % flags
674 'recording a copy)\n') % flags
675 else:
675 else:
676 msg = _('%s: not overwriting - file exists\n')
676 msg = _('%s: not overwriting - file exists\n')
677 if rename:
677 if rename:
678 hint = _('(hg rename --after to record the rename)\n')
678 hint = _('(hg rename --after to record the rename)\n')
679 else:
679 else:
680 hint = _('(hg copy --after to record the copy)\n')
680 hint = _('(hg copy --after to record the copy)\n')
681 ui.warn(msg % reltarget)
681 ui.warn(msg % reltarget)
682 ui.warn(hint)
682 ui.warn(hint)
683 return
683 return
684
684
685 if after:
685 if after:
686 if not exists:
686 if not exists:
687 if rename:
687 if rename:
688 ui.warn(_('%s: not recording move - %s does not exist\n') %
688 ui.warn(_('%s: not recording move - %s does not exist\n') %
689 (relsrc, reltarget))
689 (relsrc, reltarget))
690 else:
690 else:
691 ui.warn(_('%s: not recording copy - %s does not exist\n') %
691 ui.warn(_('%s: not recording copy - %s does not exist\n') %
692 (relsrc, reltarget))
692 (relsrc, reltarget))
693 return
693 return
694 elif not dryrun:
694 elif not dryrun:
695 try:
695 try:
696 if exists:
696 if exists:
697 os.unlink(target)
697 os.unlink(target)
698 targetdir = os.path.dirname(target) or '.'
698 targetdir = os.path.dirname(target) or '.'
699 if not os.path.isdir(targetdir):
699 if not os.path.isdir(targetdir):
700 os.makedirs(targetdir)
700 os.makedirs(targetdir)
701 if samefile:
701 if samefile:
702 tmp = target + "~hgrename"
702 tmp = target + "~hgrename"
703 os.rename(src, tmp)
703 os.rename(src, tmp)
704 os.rename(tmp, target)
704 os.rename(tmp, target)
705 else:
705 else:
706 util.copyfile(src, target)
706 util.copyfile(src, target)
707 srcexists = True
707 srcexists = True
708 except IOError as inst:
708 except IOError as inst:
709 if inst.errno == errno.ENOENT:
709 if inst.errno == errno.ENOENT:
710 ui.warn(_('%s: deleted in working directory\n') % relsrc)
710 ui.warn(_('%s: deleted in working directory\n') % relsrc)
711 srcexists = False
711 srcexists = False
712 else:
712 else:
713 ui.warn(_('%s: cannot copy - %s\n') %
713 ui.warn(_('%s: cannot copy - %s\n') %
714 (relsrc, inst.strerror))
714 (relsrc, inst.strerror))
715 return True # report a failure
715 return True # report a failure
716
716
717 if ui.verbose or not exact:
717 if ui.verbose or not exact:
718 if rename:
718 if rename:
719 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
719 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
720 else:
720 else:
721 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
721 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
722
722
723 targets[abstarget] = abssrc
723 targets[abstarget] = abssrc
724
724
725 # fix up dirstate
725 # fix up dirstate
726 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
726 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
727 dryrun=dryrun, cwd=cwd)
727 dryrun=dryrun, cwd=cwd)
728 if rename and not dryrun:
728 if rename and not dryrun:
729 if not after and srcexists and not samefile:
729 if not after and srcexists and not samefile:
730 util.unlinkpath(repo.wjoin(abssrc))
730 util.unlinkpath(repo.wjoin(abssrc))
731 wctx.forget([abssrc])
731 wctx.forget([abssrc])
732
732
733 # pat: ossep
733 # pat: ossep
734 # dest ossep
734 # dest ossep
735 # srcs: list of (hgsep, hgsep, ossep, bool)
735 # srcs: list of (hgsep, hgsep, ossep, bool)
736 # return: function that takes hgsep and returns ossep
736 # return: function that takes hgsep and returns ossep
737 def targetpathfn(pat, dest, srcs):
737 def targetpathfn(pat, dest, srcs):
738 if os.path.isdir(pat):
738 if os.path.isdir(pat):
739 abspfx = pathutil.canonpath(repo.root, cwd, pat)
739 abspfx = pathutil.canonpath(repo.root, cwd, pat)
740 abspfx = util.localpath(abspfx)
740 abspfx = util.localpath(abspfx)
741 if destdirexists:
741 if destdirexists:
742 striplen = len(os.path.split(abspfx)[0])
742 striplen = len(os.path.split(abspfx)[0])
743 else:
743 else:
744 striplen = len(abspfx)
744 striplen = len(abspfx)
745 if striplen:
745 if striplen:
746 striplen += len(pycompat.ossep)
746 striplen += len(pycompat.ossep)
747 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
747 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
748 elif destdirexists:
748 elif destdirexists:
749 res = lambda p: os.path.join(dest,
749 res = lambda p: os.path.join(dest,
750 os.path.basename(util.localpath(p)))
750 os.path.basename(util.localpath(p)))
751 else:
751 else:
752 res = lambda p: dest
752 res = lambda p: dest
753 return res
753 return res
754
754
755 # pat: ossep
755 # pat: ossep
756 # dest ossep
756 # dest ossep
757 # srcs: list of (hgsep, hgsep, ossep, bool)
757 # srcs: list of (hgsep, hgsep, ossep, bool)
758 # return: function that takes hgsep and returns ossep
758 # return: function that takes hgsep and returns ossep
759 def targetpathafterfn(pat, dest, srcs):
759 def targetpathafterfn(pat, dest, srcs):
760 if matchmod.patkind(pat):
760 if matchmod.patkind(pat):
761 # a mercurial pattern
761 # a mercurial pattern
762 res = lambda p: os.path.join(dest,
762 res = lambda p: os.path.join(dest,
763 os.path.basename(util.localpath(p)))
763 os.path.basename(util.localpath(p)))
764 else:
764 else:
765 abspfx = pathutil.canonpath(repo.root, cwd, pat)
765 abspfx = pathutil.canonpath(repo.root, cwd, pat)
766 if len(abspfx) < len(srcs[0][0]):
766 if len(abspfx) < len(srcs[0][0]):
767 # A directory. Either the target path contains the last
767 # A directory. Either the target path contains the last
768 # component of the source path or it does not.
768 # component of the source path or it does not.
769 def evalpath(striplen):
769 def evalpath(striplen):
770 score = 0
770 score = 0
771 for s in srcs:
771 for s in srcs:
772 t = os.path.join(dest, util.localpath(s[0])[striplen:])
772 t = os.path.join(dest, util.localpath(s[0])[striplen:])
773 if os.path.lexists(t):
773 if os.path.lexists(t):
774 score += 1
774 score += 1
775 return score
775 return score
776
776
777 abspfx = util.localpath(abspfx)
777 abspfx = util.localpath(abspfx)
778 striplen = len(abspfx)
778 striplen = len(abspfx)
779 if striplen:
779 if striplen:
780 striplen += len(pycompat.ossep)
780 striplen += len(pycompat.ossep)
781 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
781 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
782 score = evalpath(striplen)
782 score = evalpath(striplen)
783 striplen1 = len(os.path.split(abspfx)[0])
783 striplen1 = len(os.path.split(abspfx)[0])
784 if striplen1:
784 if striplen1:
785 striplen1 += len(pycompat.ossep)
785 striplen1 += len(pycompat.ossep)
786 if evalpath(striplen1) > score:
786 if evalpath(striplen1) > score:
787 striplen = striplen1
787 striplen = striplen1
788 res = lambda p: os.path.join(dest,
788 res = lambda p: os.path.join(dest,
789 util.localpath(p)[striplen:])
789 util.localpath(p)[striplen:])
790 else:
790 else:
791 # a file
791 # a file
792 if destdirexists:
792 if destdirexists:
793 res = lambda p: os.path.join(dest,
793 res = lambda p: os.path.join(dest,
794 os.path.basename(util.localpath(p)))
794 os.path.basename(util.localpath(p)))
795 else:
795 else:
796 res = lambda p: dest
796 res = lambda p: dest
797 return res
797 return res
798
798
799 pats = scmutil.expandpats(pats)
799 pats = scmutil.expandpats(pats)
800 if not pats:
800 if not pats:
801 raise error.Abort(_('no source or destination specified'))
801 raise error.Abort(_('no source or destination specified'))
802 if len(pats) == 1:
802 if len(pats) == 1:
803 raise error.Abort(_('no destination specified'))
803 raise error.Abort(_('no destination specified'))
804 dest = pats.pop()
804 dest = pats.pop()
805 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
805 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
806 if not destdirexists:
806 if not destdirexists:
807 if len(pats) > 1 or matchmod.patkind(pats[0]):
807 if len(pats) > 1 or matchmod.patkind(pats[0]):
808 raise error.Abort(_('with multiple sources, destination must be an '
808 raise error.Abort(_('with multiple sources, destination must be an '
809 'existing directory'))
809 'existing directory'))
810 if util.endswithsep(dest):
810 if util.endswithsep(dest):
811 raise error.Abort(_('destination %s is not a directory') % dest)
811 raise error.Abort(_('destination %s is not a directory') % dest)
812
812
813 tfn = targetpathfn
813 tfn = targetpathfn
814 if after:
814 if after:
815 tfn = targetpathafterfn
815 tfn = targetpathafterfn
816 copylist = []
816 copylist = []
817 for pat in pats:
817 for pat in pats:
818 srcs = walkpat(pat)
818 srcs = walkpat(pat)
819 if not srcs:
819 if not srcs:
820 continue
820 continue
821 copylist.append((tfn(pat, dest, srcs), srcs))
821 copylist.append((tfn(pat, dest, srcs), srcs))
822 if not copylist:
822 if not copylist:
823 raise error.Abort(_('no files to copy'))
823 raise error.Abort(_('no files to copy'))
824
824
825 errors = 0
825 errors = 0
826 for targetpath, srcs in copylist:
826 for targetpath, srcs in copylist:
827 for abssrc, relsrc, exact in srcs:
827 for abssrc, relsrc, exact in srcs:
828 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
828 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
829 errors += 1
829 errors += 1
830
830
831 if errors:
831 if errors:
832 ui.warn(_('(consider using --after)\n'))
832 ui.warn(_('(consider using --after)\n'))
833
833
834 return errors != 0
834 return errors != 0
835
835
836 ## facility to let extension process additional data into an import patch
836 ## facility to let extension process additional data into an import patch
837 # list of identifier to be executed in order
837 # list of identifier to be executed in order
838 extrapreimport = [] # run before commit
838 extrapreimport = [] # run before commit
839 extrapostimport = [] # run after commit
839 extrapostimport = [] # run after commit
840 # mapping from identifier to actual import function
840 # mapping from identifier to actual import function
841 #
841 #
842 # 'preimport' are run before the commit is made and are provided the following
842 # 'preimport' are run before the commit is made and are provided the following
843 # arguments:
843 # arguments:
844 # - repo: the localrepository instance,
844 # - repo: the localrepository instance,
845 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
845 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
846 # - extra: the future extra dictionary of the changeset, please mutate it,
846 # - extra: the future extra dictionary of the changeset, please mutate it,
847 # - opts: the import options.
847 # - opts: the import options.
848 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
848 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
849 # mutation of in memory commit and more. Feel free to rework the code to get
849 # mutation of in memory commit and more. Feel free to rework the code to get
850 # there.
850 # there.
851 extrapreimportmap = {}
851 extrapreimportmap = {}
852 # 'postimport' are run after the commit is made and are provided the following
852 # 'postimport' are run after the commit is made and are provided the following
853 # argument:
853 # argument:
854 # - ctx: the changectx created by import.
854 # - ctx: the changectx created by import.
855 extrapostimportmap = {}
855 extrapostimportmap = {}
856
856
857 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
857 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
858 """Utility function used by commands.import to import a single patch
858 """Utility function used by commands.import to import a single patch
859
859
860 This function is explicitly defined here to help the evolve extension to
860 This function is explicitly defined here to help the evolve extension to
861 wrap this part of the import logic.
861 wrap this part of the import logic.
862
862
863 The API is currently a bit ugly because it a simple code translation from
863 The API is currently a bit ugly because it a simple code translation from
864 the import command. Feel free to make it better.
864 the import command. Feel free to make it better.
865
865
866 :hunk: a patch (as a binary string)
866 :hunk: a patch (as a binary string)
867 :parents: nodes that will be parent of the created commit
867 :parents: nodes that will be parent of the created commit
868 :opts: the full dict of option passed to the import command
868 :opts: the full dict of option passed to the import command
869 :msgs: list to save commit message to.
869 :msgs: list to save commit message to.
870 (used in case we need to save it when failing)
870 (used in case we need to save it when failing)
871 :updatefunc: a function that update a repo to a given node
871 :updatefunc: a function that update a repo to a given node
872 updatefunc(<repo>, <node>)
872 updatefunc(<repo>, <node>)
873 """
873 """
874 # avoid cycle context -> subrepo -> cmdutil
874 # avoid cycle context -> subrepo -> cmdutil
875 from . import context
875 from . import context
876 extractdata = patch.extract(ui, hunk)
876 extractdata = patch.extract(ui, hunk)
877 tmpname = extractdata.get('filename')
877 tmpname = extractdata.get('filename')
878 message = extractdata.get('message')
878 message = extractdata.get('message')
879 user = opts.get('user') or extractdata.get('user')
879 user = opts.get('user') or extractdata.get('user')
880 date = opts.get('date') or extractdata.get('date')
880 date = opts.get('date') or extractdata.get('date')
881 branch = extractdata.get('branch')
881 branch = extractdata.get('branch')
882 nodeid = extractdata.get('nodeid')
882 nodeid = extractdata.get('nodeid')
883 p1 = extractdata.get('p1')
883 p1 = extractdata.get('p1')
884 p2 = extractdata.get('p2')
884 p2 = extractdata.get('p2')
885
885
886 nocommit = opts.get('no_commit')
886 nocommit = opts.get('no_commit')
887 importbranch = opts.get('import_branch')
887 importbranch = opts.get('import_branch')
888 update = not opts.get('bypass')
888 update = not opts.get('bypass')
889 strip = opts["strip"]
889 strip = opts["strip"]
890 prefix = opts["prefix"]
890 prefix = opts["prefix"]
891 sim = float(opts.get('similarity') or 0)
891 sim = float(opts.get('similarity') or 0)
892 if not tmpname:
892 if not tmpname:
893 return (None, None, False)
893 return (None, None, False)
894
894
895 rejects = False
895 rejects = False
896
896
897 try:
897 try:
898 cmdline_message = logmessage(ui, opts)
898 cmdline_message = logmessage(ui, opts)
899 if cmdline_message:
899 if cmdline_message:
900 # pickup the cmdline msg
900 # pickup the cmdline msg
901 message = cmdline_message
901 message = cmdline_message
902 elif message:
902 elif message:
903 # pickup the patch msg
903 # pickup the patch msg
904 message = message.strip()
904 message = message.strip()
905 else:
905 else:
906 # launch the editor
906 # launch the editor
907 message = None
907 message = None
908 ui.debug('message:\n%s\n' % message)
908 ui.debug('message:\n%s\n' % message)
909
909
910 if len(parents) == 1:
910 if len(parents) == 1:
911 parents.append(repo[nullid])
911 parents.append(repo[nullid])
912 if opts.get('exact'):
912 if opts.get('exact'):
913 if not nodeid or not p1:
913 if not nodeid or not p1:
914 raise error.Abort(_('not a Mercurial patch'))
914 raise error.Abort(_('not a Mercurial patch'))
915 p1 = repo[p1]
915 p1 = repo[p1]
916 p2 = repo[p2 or nullid]
916 p2 = repo[p2 or nullid]
917 elif p2:
917 elif p2:
918 try:
918 try:
919 p1 = repo[p1]
919 p1 = repo[p1]
920 p2 = repo[p2]
920 p2 = repo[p2]
921 # Without any options, consider p2 only if the
921 # Without any options, consider p2 only if the
922 # patch is being applied on top of the recorded
922 # patch is being applied on top of the recorded
923 # first parent.
923 # first parent.
924 if p1 != parents[0]:
924 if p1 != parents[0]:
925 p1 = parents[0]
925 p1 = parents[0]
926 p2 = repo[nullid]
926 p2 = repo[nullid]
927 except error.RepoError:
927 except error.RepoError:
928 p1, p2 = parents
928 p1, p2 = parents
929 if p2.node() == nullid:
929 if p2.node() == nullid:
930 ui.warn(_("warning: import the patch as a normal revision\n"
930 ui.warn(_("warning: import the patch as a normal revision\n"
931 "(use --exact to import the patch as a merge)\n"))
931 "(use --exact to import the patch as a merge)\n"))
932 else:
932 else:
933 p1, p2 = parents
933 p1, p2 = parents
934
934
935 n = None
935 n = None
936 if update:
936 if update:
937 if p1 != parents[0]:
937 if p1 != parents[0]:
938 updatefunc(repo, p1.node())
938 updatefunc(repo, p1.node())
939 if p2 != parents[1]:
939 if p2 != parents[1]:
940 repo.setparents(p1.node(), p2.node())
940 repo.setparents(p1.node(), p2.node())
941
941
942 if opts.get('exact') or importbranch:
942 if opts.get('exact') or importbranch:
943 repo.dirstate.setbranch(branch or 'default')
943 repo.dirstate.setbranch(branch or 'default')
944
944
945 partial = opts.get('partial', False)
945 partial = opts.get('partial', False)
946 files = set()
946 files = set()
947 try:
947 try:
948 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
948 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
949 files=files, eolmode=None, similarity=sim / 100.0)
949 files=files, eolmode=None, similarity=sim / 100.0)
950 except patch.PatchError as e:
950 except patch.PatchError as e:
951 if not partial:
951 if not partial:
952 raise error.Abort(str(e))
952 raise error.Abort(str(e))
953 if partial:
953 if partial:
954 rejects = True
954 rejects = True
955
955
956 files = list(files)
956 files = list(files)
957 if nocommit:
957 if nocommit:
958 if message:
958 if message:
959 msgs.append(message)
959 msgs.append(message)
960 else:
960 else:
961 if opts.get('exact') or p2:
961 if opts.get('exact') or p2:
962 # If you got here, you either use --force and know what
962 # If you got here, you either use --force and know what
963 # you are doing or used --exact or a merge patch while
963 # you are doing or used --exact or a merge patch while
964 # being updated to its first parent.
964 # being updated to its first parent.
965 m = None
965 m = None
966 else:
966 else:
967 m = scmutil.matchfiles(repo, files or [])
967 m = scmutil.matchfiles(repo, files or [])
968 editform = mergeeditform(repo[None], 'import.normal')
968 editform = mergeeditform(repo[None], 'import.normal')
969 if opts.get('exact'):
969 if opts.get('exact'):
970 editor = None
970 editor = None
971 else:
971 else:
972 editor = getcommiteditor(editform=editform, **opts)
972 editor = getcommiteditor(editform=editform, **opts)
973 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
973 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
974 extra = {}
974 extra = {}
975 for idfunc in extrapreimport:
975 for idfunc in extrapreimport:
976 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
976 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
977 try:
977 try:
978 if partial:
978 if partial:
979 repo.ui.setconfig('ui', 'allowemptycommit', True)
979 repo.ui.setconfig('ui', 'allowemptycommit', True)
980 n = repo.commit(message, user,
980 n = repo.commit(message, user,
981 date, match=m,
981 date, match=m,
982 editor=editor, extra=extra)
982 editor=editor, extra=extra)
983 for idfunc in extrapostimport:
983 for idfunc in extrapostimport:
984 extrapostimportmap[idfunc](repo[n])
984 extrapostimportmap[idfunc](repo[n])
985 finally:
985 finally:
986 repo.ui.restoreconfig(allowemptyback)
986 repo.ui.restoreconfig(allowemptyback)
987 else:
987 else:
988 if opts.get('exact') or importbranch:
988 if opts.get('exact') or importbranch:
989 branch = branch or 'default'
989 branch = branch or 'default'
990 else:
990 else:
991 branch = p1.branch()
991 branch = p1.branch()
992 store = patch.filestore()
992 store = patch.filestore()
993 try:
993 try:
994 files = set()
994 files = set()
995 try:
995 try:
996 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
996 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
997 files, eolmode=None)
997 files, eolmode=None)
998 except patch.PatchError as e:
998 except patch.PatchError as e:
999 raise error.Abort(str(e))
999 raise error.Abort(str(e))
1000 if opts.get('exact'):
1000 if opts.get('exact'):
1001 editor = None
1001 editor = None
1002 else:
1002 else:
1003 editor = getcommiteditor(editform='import.bypass')
1003 editor = getcommiteditor(editform='import.bypass')
1004 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1004 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1005 message,
1005 message,
1006 user,
1006 user,
1007 date,
1007 date,
1008 branch, files, store,
1008 branch, files, store,
1009 editor=editor)
1009 editor=editor)
1010 n = memctx.commit()
1010 n = memctx.commit()
1011 finally:
1011 finally:
1012 store.close()
1012 store.close()
1013 if opts.get('exact') and nocommit:
1013 if opts.get('exact') and nocommit:
1014 # --exact with --no-commit is still useful in that it does merge
1014 # --exact with --no-commit is still useful in that it does merge
1015 # and branch bits
1015 # and branch bits
1016 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1016 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1017 elif opts.get('exact') and hex(n) != nodeid:
1017 elif opts.get('exact') and hex(n) != nodeid:
1018 raise error.Abort(_('patch is damaged or loses information'))
1018 raise error.Abort(_('patch is damaged or loses information'))
1019 msg = _('applied to working directory')
1019 msg = _('applied to working directory')
1020 if n:
1020 if n:
1021 # i18n: refers to a short changeset id
1021 # i18n: refers to a short changeset id
1022 msg = _('created %s') % short(n)
1022 msg = _('created %s') % short(n)
1023 return (msg, n, rejects)
1023 return (msg, n, rejects)
1024 finally:
1024 finally:
1025 os.unlink(tmpname)
1025 os.unlink(tmpname)
1026
1026
1027 # facility to let extensions include additional data in an exported patch
1027 # facility to let extensions include additional data in an exported patch
1028 # list of identifiers to be executed in order
1028 # list of identifiers to be executed in order
1029 extraexport = []
1029 extraexport = []
1030 # mapping from identifier to actual export function
1030 # mapping from identifier to actual export function
1031 # function as to return a string to be added to the header or None
1031 # function as to return a string to be added to the header or None
1032 # it is given two arguments (sequencenumber, changectx)
1032 # it is given two arguments (sequencenumber, changectx)
1033 extraexportmap = {}
1033 extraexportmap = {}
1034
1034
1035 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1035 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1036 opts=None, match=None):
1036 opts=None, match=None):
1037 '''export changesets as hg patches.'''
1037 '''export changesets as hg patches.'''
1038
1038
1039 total = len(revs)
1039 total = len(revs)
1040 revwidth = max([len(str(rev)) for rev in revs])
1040 revwidth = max([len(str(rev)) for rev in revs])
1041 filemode = {}
1041 filemode = {}
1042
1042
1043 def single(rev, seqno, fp):
1043 def single(rev, seqno, fp):
1044 ctx = repo[rev]
1044 ctx = repo[rev]
1045 node = ctx.node()
1045 node = ctx.node()
1046 parents = [p.node() for p in ctx.parents() if p]
1046 parents = [p.node() for p in ctx.parents() if p]
1047 branch = ctx.branch()
1047 branch = ctx.branch()
1048 if switch_parent:
1048 if switch_parent:
1049 parents.reverse()
1049 parents.reverse()
1050
1050
1051 if parents:
1051 if parents:
1052 prev = parents[0]
1052 prev = parents[0]
1053 else:
1053 else:
1054 prev = nullid
1054 prev = nullid
1055
1055
1056 shouldclose = False
1056 shouldclose = False
1057 if not fp and len(template) > 0:
1057 if not fp and len(template) > 0:
1058 desc_lines = ctx.description().rstrip().split('\n')
1058 desc_lines = ctx.description().rstrip().split('\n')
1059 desc = desc_lines[0] #Commit always has a first line.
1059 desc = desc_lines[0] #Commit always has a first line.
1060 fp = makefileobj(repo, template, node, desc=desc, total=total,
1060 fp = makefileobj(repo, template, node, desc=desc, total=total,
1061 seqno=seqno, revwidth=revwidth, mode='wb',
1061 seqno=seqno, revwidth=revwidth, mode='wb',
1062 modemap=filemode)
1062 modemap=filemode)
1063 shouldclose = True
1063 shouldclose = True
1064 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1064 if fp and not getattr(fp, 'name', '<unnamed>').startswith('<'):
1065 repo.ui.note("%s\n" % fp.name)
1065 repo.ui.note("%s\n" % fp.name)
1066
1066
1067 if not fp:
1067 if not fp:
1068 write = repo.ui.write
1068 write = repo.ui.write
1069 else:
1069 else:
1070 def write(s, **kw):
1070 def write(s, **kw):
1071 fp.write(s)
1071 fp.write(s)
1072
1072
1073 write("# HG changeset patch\n")
1073 write("# HG changeset patch\n")
1074 write("# User %s\n" % ctx.user())
1074 write("# User %s\n" % ctx.user())
1075 write("# Date %d %d\n" % ctx.date())
1075 write("# Date %d %d\n" % ctx.date())
1076 write("# %s\n" % util.datestr(ctx.date()))
1076 write("# %s\n" % util.datestr(ctx.date()))
1077 if branch and branch != 'default':
1077 if branch and branch != 'default':
1078 write("# Branch %s\n" % branch)
1078 write("# Branch %s\n" % branch)
1079 write("# Node ID %s\n" % hex(node))
1079 write("# Node ID %s\n" % hex(node))
1080 write("# Parent %s\n" % hex(prev))
1080 write("# Parent %s\n" % hex(prev))
1081 if len(parents) > 1:
1081 if len(parents) > 1:
1082 write("# Parent %s\n" % hex(parents[1]))
1082 write("# Parent %s\n" % hex(parents[1]))
1083
1083
1084 for headerid in extraexport:
1084 for headerid in extraexport:
1085 header = extraexportmap[headerid](seqno, ctx)
1085 header = extraexportmap[headerid](seqno, ctx)
1086 if header is not None:
1086 if header is not None:
1087 write('# %s\n' % header)
1087 write('# %s\n' % header)
1088 write(ctx.description().rstrip())
1088 write(ctx.description().rstrip())
1089 write("\n\n")
1089 write("\n\n")
1090
1090
1091 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1091 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1092 write(chunk, label=label)
1092 write(chunk, label=label)
1093
1093
1094 if shouldclose:
1094 if shouldclose:
1095 fp.close()
1095 fp.close()
1096
1096
1097 for seqno, rev in enumerate(revs):
1097 for seqno, rev in enumerate(revs):
1098 single(rev, seqno + 1, fp)
1098 single(rev, seqno + 1, fp)
1099
1099
1100 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1100 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1101 changes=None, stat=False, fp=None, prefix='',
1101 changes=None, stat=False, fp=None, prefix='',
1102 root='', listsubrepos=False):
1102 root='', listsubrepos=False):
1103 '''show diff or diffstat.'''
1103 '''show diff or diffstat.'''
1104 if fp is None:
1104 if fp is None:
1105 write = ui.write
1105 write = ui.write
1106 else:
1106 else:
1107 def write(s, **kw):
1107 def write(s, **kw):
1108 fp.write(s)
1108 fp.write(s)
1109
1109
1110 if root:
1110 if root:
1111 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1111 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1112 else:
1112 else:
1113 relroot = ''
1113 relroot = ''
1114 if relroot != '':
1114 if relroot != '':
1115 # XXX relative roots currently don't work if the root is within a
1115 # XXX relative roots currently don't work if the root is within a
1116 # subrepo
1116 # subrepo
1117 uirelroot = match.uipath(relroot)
1117 uirelroot = match.uipath(relroot)
1118 relroot += '/'
1118 relroot += '/'
1119 for matchroot in match.files():
1119 for matchroot in match.files():
1120 if not matchroot.startswith(relroot):
1120 if not matchroot.startswith(relroot):
1121 ui.warn(_('warning: %s not inside relative root %s\n') % (
1121 ui.warn(_('warning: %s not inside relative root %s\n') % (
1122 match.uipath(matchroot), uirelroot))
1122 match.uipath(matchroot), uirelroot))
1123
1123
1124 if stat:
1124 if stat:
1125 diffopts = diffopts.copy(context=0)
1125 diffopts = diffopts.copy(context=0)
1126 width = 80
1126 width = 80
1127 if not ui.plain():
1127 if not ui.plain():
1128 width = ui.termwidth()
1128 width = ui.termwidth()
1129 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1129 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1130 prefix=prefix, relroot=relroot)
1130 prefix=prefix, relroot=relroot)
1131 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1131 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1132 width=width):
1132 width=width):
1133 write(chunk, label=label)
1133 write(chunk, label=label)
1134 else:
1134 else:
1135 for chunk, label in patch.diffui(repo, node1, node2, match,
1135 for chunk, label in patch.diffui(repo, node1, node2, match,
1136 changes, diffopts, prefix=prefix,
1136 changes, diffopts, prefix=prefix,
1137 relroot=relroot):
1137 relroot=relroot):
1138 write(chunk, label=label)
1138 write(chunk, label=label)
1139
1139
1140 if listsubrepos:
1140 if listsubrepos:
1141 ctx1 = repo[node1]
1141 ctx1 = repo[node1]
1142 ctx2 = repo[node2]
1142 ctx2 = repo[node2]
1143 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1143 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1144 tempnode2 = node2
1144 tempnode2 = node2
1145 try:
1145 try:
1146 if node2 is not None:
1146 if node2 is not None:
1147 tempnode2 = ctx2.substate[subpath][1]
1147 tempnode2 = ctx2.substate[subpath][1]
1148 except KeyError:
1148 except KeyError:
1149 # A subrepo that existed in node1 was deleted between node1 and
1149 # A subrepo that existed in node1 was deleted between node1 and
1150 # node2 (inclusive). Thus, ctx2's substate won't contain that
1150 # node2 (inclusive). Thus, ctx2's substate won't contain that
1151 # subpath. The best we can do is to ignore it.
1151 # subpath. The best we can do is to ignore it.
1152 tempnode2 = None
1152 tempnode2 = None
1153 submatch = matchmod.subdirmatcher(subpath, match)
1153 submatch = matchmod.subdirmatcher(subpath, match)
1154 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1154 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1155 stat=stat, fp=fp, prefix=prefix)
1155 stat=stat, fp=fp, prefix=prefix)
1156
1156
1157 def _changesetlabels(ctx):
1157 def _changesetlabels(ctx):
1158 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1158 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1159 if ctx.troubled():
1159 if ctx.troubled():
1160 labels.append('changeset.troubled')
1160 labels.append('changeset.troubled')
1161 for trouble in ctx.troubles():
1161 for trouble in ctx.troubles():
1162 labels.append('trouble.%s' % trouble)
1162 labels.append('trouble.%s' % trouble)
1163 return ' '.join(labels)
1163 return ' '.join(labels)
1164
1164
1165 class changeset_printer(object):
1165 class changeset_printer(object):
1166 '''show changeset information when templating not requested.'''
1166 '''show changeset information when templating not requested.'''
1167
1167
1168 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1168 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1169 self.ui = ui
1169 self.ui = ui
1170 self.repo = repo
1170 self.repo = repo
1171 self.buffered = buffered
1171 self.buffered = buffered
1172 self.matchfn = matchfn
1172 self.matchfn = matchfn
1173 self.diffopts = diffopts
1173 self.diffopts = diffopts
1174 self.header = {}
1174 self.header = {}
1175 self.hunk = {}
1175 self.hunk = {}
1176 self.lastheader = None
1176 self.lastheader = None
1177 self.footer = None
1177 self.footer = None
1178
1178
1179 def flush(self, ctx):
1179 def flush(self, ctx):
1180 rev = ctx.rev()
1180 rev = ctx.rev()
1181 if rev in self.header:
1181 if rev in self.header:
1182 h = self.header[rev]
1182 h = self.header[rev]
1183 if h != self.lastheader:
1183 if h != self.lastheader:
1184 self.lastheader = h
1184 self.lastheader = h
1185 self.ui.write(h)
1185 self.ui.write(h)
1186 del self.header[rev]
1186 del self.header[rev]
1187 if rev in self.hunk:
1187 if rev in self.hunk:
1188 self.ui.write(self.hunk[rev])
1188 self.ui.write(self.hunk[rev])
1189 del self.hunk[rev]
1189 del self.hunk[rev]
1190 return 1
1190 return 1
1191 return 0
1191 return 0
1192
1192
1193 def close(self):
1193 def close(self):
1194 if self.footer:
1194 if self.footer:
1195 self.ui.write(self.footer)
1195 self.ui.write(self.footer)
1196
1196
1197 def show(self, ctx, copies=None, matchfn=None, **props):
1197 def show(self, ctx, copies=None, matchfn=None, **props):
1198 if self.buffered:
1198 if self.buffered:
1199 self.ui.pushbuffer(labeled=True)
1199 self.ui.pushbuffer(labeled=True)
1200 self._show(ctx, copies, matchfn, props)
1200 self._show(ctx, copies, matchfn, props)
1201 self.hunk[ctx.rev()] = self.ui.popbuffer()
1201 self.hunk[ctx.rev()] = self.ui.popbuffer()
1202 else:
1202 else:
1203 self._show(ctx, copies, matchfn, props)
1203 self._show(ctx, copies, matchfn, props)
1204
1204
1205 def _show(self, ctx, copies, matchfn, props):
1205 def _show(self, ctx, copies, matchfn, props):
1206 '''show a single changeset or file revision'''
1206 '''show a single changeset or file revision'''
1207 changenode = ctx.node()
1207 changenode = ctx.node()
1208 rev = ctx.rev()
1208 rev = ctx.rev()
1209 if self.ui.debugflag:
1209 if self.ui.debugflag:
1210 hexfunc = hex
1210 hexfunc = hex
1211 else:
1211 else:
1212 hexfunc = short
1212 hexfunc = short
1213 # as of now, wctx.node() and wctx.rev() return None, but we want to
1213 # as of now, wctx.node() and wctx.rev() return None, but we want to
1214 # show the same values as {node} and {rev} templatekw
1214 # show the same values as {node} and {rev} templatekw
1215 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1215 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1216
1216
1217 if self.ui.quiet:
1217 if self.ui.quiet:
1218 self.ui.write("%d:%s\n" % revnode, label='log.node')
1218 self.ui.write("%d:%s\n" % revnode, label='log.node')
1219 return
1219 return
1220
1220
1221 date = util.datestr(ctx.date())
1221 date = util.datestr(ctx.date())
1222
1222
1223 # i18n: column positioning for "hg log"
1223 # i18n: column positioning for "hg log"
1224 self.ui.write(_("changeset: %d:%s\n") % revnode,
1224 self.ui.write(_("changeset: %d:%s\n") % revnode,
1225 label=_changesetlabels(ctx))
1225 label=_changesetlabels(ctx))
1226
1226
1227 # branches are shown first before any other names due to backwards
1227 # branches are shown first before any other names due to backwards
1228 # compatibility
1228 # compatibility
1229 branch = ctx.branch()
1229 branch = ctx.branch()
1230 # don't show the default branch name
1230 # don't show the default branch name
1231 if branch != 'default':
1231 if branch != 'default':
1232 # i18n: column positioning for "hg log"
1232 # i18n: column positioning for "hg log"
1233 self.ui.write(_("branch: %s\n") % branch,
1233 self.ui.write(_("branch: %s\n") % branch,
1234 label='log.branch')
1234 label='log.branch')
1235
1235
1236 for nsname, ns in self.repo.names.iteritems():
1236 for nsname, ns in self.repo.names.iteritems():
1237 # branches has special logic already handled above, so here we just
1237 # branches has special logic already handled above, so here we just
1238 # skip it
1238 # skip it
1239 if nsname == 'branches':
1239 if nsname == 'branches':
1240 continue
1240 continue
1241 # we will use the templatename as the color name since those two
1241 # we will use the templatename as the color name since those two
1242 # should be the same
1242 # should be the same
1243 for name in ns.names(self.repo, changenode):
1243 for name in ns.names(self.repo, changenode):
1244 self.ui.write(ns.logfmt % name,
1244 self.ui.write(ns.logfmt % name,
1245 label='log.%s' % ns.colorname)
1245 label='log.%s' % ns.colorname)
1246 if self.ui.debugflag:
1246 if self.ui.debugflag:
1247 # i18n: column positioning for "hg log"
1247 # i18n: column positioning for "hg log"
1248 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1248 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1249 label='log.phase')
1249 label='log.phase')
1250 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1250 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1251 label = 'log.parent changeset.%s' % pctx.phasestr()
1251 label = 'log.parent changeset.%s' % pctx.phasestr()
1252 # i18n: column positioning for "hg log"
1252 # i18n: column positioning for "hg log"
1253 self.ui.write(_("parent: %d:%s\n")
1253 self.ui.write(_("parent: %d:%s\n")
1254 % (pctx.rev(), hexfunc(pctx.node())),
1254 % (pctx.rev(), hexfunc(pctx.node())),
1255 label=label)
1255 label=label)
1256
1256
1257 if self.ui.debugflag and rev is not None:
1257 if self.ui.debugflag and rev is not None:
1258 mnode = ctx.manifestnode()
1258 mnode = ctx.manifestnode()
1259 # i18n: column positioning for "hg log"
1259 # i18n: column positioning for "hg log"
1260 self.ui.write(_("manifest: %d:%s\n") %
1260 self.ui.write(_("manifest: %d:%s\n") %
1261 (self.repo.manifestlog._revlog.rev(mnode),
1261 (self.repo.manifestlog._revlog.rev(mnode),
1262 hex(mnode)),
1262 hex(mnode)),
1263 label='ui.debug log.manifest')
1263 label='ui.debug log.manifest')
1264 # i18n: column positioning for "hg log"
1264 # i18n: column positioning for "hg log"
1265 self.ui.write(_("user: %s\n") % ctx.user(),
1265 self.ui.write(_("user: %s\n") % ctx.user(),
1266 label='log.user')
1266 label='log.user')
1267 # i18n: column positioning for "hg log"
1267 # i18n: column positioning for "hg log"
1268 self.ui.write(_("date: %s\n") % date,
1268 self.ui.write(_("date: %s\n") % date,
1269 label='log.date')
1269 label='log.date')
1270
1270
1271 if ctx.troubled():
1271 if ctx.troubled():
1272 # i18n: column positioning for "hg log"
1272 # i18n: column positioning for "hg log"
1273 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1273 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1274 label='log.trouble')
1274 label='log.trouble')
1275
1275
1276 if self.ui.debugflag:
1276 if self.ui.debugflag:
1277 files = ctx.p1().status(ctx)[:3]
1277 files = ctx.p1().status(ctx)[:3]
1278 for key, value in zip([# i18n: column positioning for "hg log"
1278 for key, value in zip([# i18n: column positioning for "hg log"
1279 _("files:"),
1279 _("files:"),
1280 # i18n: column positioning for "hg log"
1280 # i18n: column positioning for "hg log"
1281 _("files+:"),
1281 _("files+:"),
1282 # i18n: column positioning for "hg log"
1282 # i18n: column positioning for "hg log"
1283 _("files-:")], files):
1283 _("files-:")], files):
1284 if value:
1284 if value:
1285 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1285 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1286 label='ui.debug log.files')
1286 label='ui.debug log.files')
1287 elif ctx.files() and self.ui.verbose:
1287 elif ctx.files() and self.ui.verbose:
1288 # i18n: column positioning for "hg log"
1288 # i18n: column positioning for "hg log"
1289 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1289 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1290 label='ui.note log.files')
1290 label='ui.note log.files')
1291 if copies and self.ui.verbose:
1291 if copies and self.ui.verbose:
1292 copies = ['%s (%s)' % c for c in copies]
1292 copies = ['%s (%s)' % c for c in copies]
1293 # i18n: column positioning for "hg log"
1293 # i18n: column positioning for "hg log"
1294 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1294 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1295 label='ui.note log.copies')
1295 label='ui.note log.copies')
1296
1296
1297 extra = ctx.extra()
1297 extra = ctx.extra()
1298 if extra and self.ui.debugflag:
1298 if extra and self.ui.debugflag:
1299 for key, value in sorted(extra.items()):
1299 for key, value in sorted(extra.items()):
1300 # i18n: column positioning for "hg log"
1300 # i18n: column positioning for "hg log"
1301 self.ui.write(_("extra: %s=%s\n")
1301 self.ui.write(_("extra: %s=%s\n")
1302 % (key, value.encode('string_escape')),
1302 % (key, value.encode('string_escape')),
1303 label='ui.debug log.extra')
1303 label='ui.debug log.extra')
1304
1304
1305 description = ctx.description().strip()
1305 description = ctx.description().strip()
1306 if description:
1306 if description:
1307 if self.ui.verbose:
1307 if self.ui.verbose:
1308 self.ui.write(_("description:\n"),
1308 self.ui.write(_("description:\n"),
1309 label='ui.note log.description')
1309 label='ui.note log.description')
1310 self.ui.write(description,
1310 self.ui.write(description,
1311 label='ui.note log.description')
1311 label='ui.note log.description')
1312 self.ui.write("\n\n")
1312 self.ui.write("\n\n")
1313 else:
1313 else:
1314 # i18n: column positioning for "hg log"
1314 # i18n: column positioning for "hg log"
1315 self.ui.write(_("summary: %s\n") %
1315 self.ui.write(_("summary: %s\n") %
1316 description.splitlines()[0],
1316 description.splitlines()[0],
1317 label='log.summary')
1317 label='log.summary')
1318 self.ui.write("\n")
1318 self.ui.write("\n")
1319
1319
1320 self.showpatch(ctx, matchfn)
1320 self.showpatch(ctx, matchfn)
1321
1321
1322 def showpatch(self, ctx, matchfn):
1322 def showpatch(self, ctx, matchfn):
1323 if not matchfn:
1323 if not matchfn:
1324 matchfn = self.matchfn
1324 matchfn = self.matchfn
1325 if matchfn:
1325 if matchfn:
1326 stat = self.diffopts.get('stat')
1326 stat = self.diffopts.get('stat')
1327 diff = self.diffopts.get('patch')
1327 diff = self.diffopts.get('patch')
1328 diffopts = patch.diffallopts(self.ui, self.diffopts)
1328 diffopts = patch.diffallopts(self.ui, self.diffopts)
1329 node = ctx.node()
1329 node = ctx.node()
1330 prev = ctx.p1().node()
1330 prev = ctx.p1().node()
1331 if stat:
1331 if stat:
1332 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1332 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1333 match=matchfn, stat=True)
1333 match=matchfn, stat=True)
1334 if diff:
1334 if diff:
1335 if stat:
1335 if stat:
1336 self.ui.write("\n")
1336 self.ui.write("\n")
1337 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1337 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1338 match=matchfn, stat=False)
1338 match=matchfn, stat=False)
1339 self.ui.write("\n")
1339 self.ui.write("\n")
1340
1340
1341 class jsonchangeset(changeset_printer):
1341 class jsonchangeset(changeset_printer):
1342 '''format changeset information.'''
1342 '''format changeset information.'''
1343
1343
1344 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1344 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1345 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1345 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1346 self.cache = {}
1346 self.cache = {}
1347 self._first = True
1347 self._first = True
1348
1348
1349 def close(self):
1349 def close(self):
1350 if not self._first:
1350 if not self._first:
1351 self.ui.write("\n]\n")
1351 self.ui.write("\n]\n")
1352 else:
1352 else:
1353 self.ui.write("[]\n")
1353 self.ui.write("[]\n")
1354
1354
1355 def _show(self, ctx, copies, matchfn, props):
1355 def _show(self, ctx, copies, matchfn, props):
1356 '''show a single changeset or file revision'''
1356 '''show a single changeset or file revision'''
1357 rev = ctx.rev()
1357 rev = ctx.rev()
1358 if rev is None:
1358 if rev is None:
1359 jrev = jnode = 'null'
1359 jrev = jnode = 'null'
1360 else:
1360 else:
1361 jrev = str(rev)
1361 jrev = str(rev)
1362 jnode = '"%s"' % hex(ctx.node())
1362 jnode = '"%s"' % hex(ctx.node())
1363 j = encoding.jsonescape
1363 j = encoding.jsonescape
1364
1364
1365 if self._first:
1365 if self._first:
1366 self.ui.write("[\n {")
1366 self.ui.write("[\n {")
1367 self._first = False
1367 self._first = False
1368 else:
1368 else:
1369 self.ui.write(",\n {")
1369 self.ui.write(",\n {")
1370
1370
1371 if self.ui.quiet:
1371 if self.ui.quiet:
1372 self.ui.write(('\n "rev": %s') % jrev)
1372 self.ui.write(('\n "rev": %s') % jrev)
1373 self.ui.write((',\n "node": %s') % jnode)
1373 self.ui.write((',\n "node": %s') % jnode)
1374 self.ui.write('\n }')
1374 self.ui.write('\n }')
1375 return
1375 return
1376
1376
1377 self.ui.write(('\n "rev": %s') % jrev)
1377 self.ui.write(('\n "rev": %s') % jrev)
1378 self.ui.write((',\n "node": %s') % jnode)
1378 self.ui.write((',\n "node": %s') % jnode)
1379 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1379 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1380 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1380 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1381 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1381 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1382 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1382 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1383 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1383 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1384
1384
1385 self.ui.write((',\n "bookmarks": [%s]') %
1385 self.ui.write((',\n "bookmarks": [%s]') %
1386 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1386 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1387 self.ui.write((',\n "tags": [%s]') %
1387 self.ui.write((',\n "tags": [%s]') %
1388 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1388 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1389 self.ui.write((',\n "parents": [%s]') %
1389 self.ui.write((',\n "parents": [%s]') %
1390 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1390 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1391
1391
1392 if self.ui.debugflag:
1392 if self.ui.debugflag:
1393 if rev is None:
1393 if rev is None:
1394 jmanifestnode = 'null'
1394 jmanifestnode = 'null'
1395 else:
1395 else:
1396 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1396 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1397 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1397 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1398
1398
1399 self.ui.write((',\n "extra": {%s}') %
1399 self.ui.write((',\n "extra": {%s}') %
1400 ", ".join('"%s": "%s"' % (j(k), j(v))
1400 ", ".join('"%s": "%s"' % (j(k), j(v))
1401 for k, v in ctx.extra().items()))
1401 for k, v in ctx.extra().items()))
1402
1402
1403 files = ctx.p1().status(ctx)
1403 files = ctx.p1().status(ctx)
1404 self.ui.write((',\n "modified": [%s]') %
1404 self.ui.write((',\n "modified": [%s]') %
1405 ", ".join('"%s"' % j(f) for f in files[0]))
1405 ", ".join('"%s"' % j(f) for f in files[0]))
1406 self.ui.write((',\n "added": [%s]') %
1406 self.ui.write((',\n "added": [%s]') %
1407 ", ".join('"%s"' % j(f) for f in files[1]))
1407 ", ".join('"%s"' % j(f) for f in files[1]))
1408 self.ui.write((',\n "removed": [%s]') %
1408 self.ui.write((',\n "removed": [%s]') %
1409 ", ".join('"%s"' % j(f) for f in files[2]))
1409 ", ".join('"%s"' % j(f) for f in files[2]))
1410
1410
1411 elif self.ui.verbose:
1411 elif self.ui.verbose:
1412 self.ui.write((',\n "files": [%s]') %
1412 self.ui.write((',\n "files": [%s]') %
1413 ", ".join('"%s"' % j(f) for f in ctx.files()))
1413 ", ".join('"%s"' % j(f) for f in ctx.files()))
1414
1414
1415 if copies:
1415 if copies:
1416 self.ui.write((',\n "copies": {%s}') %
1416 self.ui.write((',\n "copies": {%s}') %
1417 ", ".join('"%s": "%s"' % (j(k), j(v))
1417 ", ".join('"%s": "%s"' % (j(k), j(v))
1418 for k, v in copies))
1418 for k, v in copies))
1419
1419
1420 matchfn = self.matchfn
1420 matchfn = self.matchfn
1421 if matchfn:
1421 if matchfn:
1422 stat = self.diffopts.get('stat')
1422 stat = self.diffopts.get('stat')
1423 diff = self.diffopts.get('patch')
1423 diff = self.diffopts.get('patch')
1424 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1424 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1425 node, prev = ctx.node(), ctx.p1().node()
1425 node, prev = ctx.node(), ctx.p1().node()
1426 if stat:
1426 if stat:
1427 self.ui.pushbuffer()
1427 self.ui.pushbuffer()
1428 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1428 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1429 match=matchfn, stat=True)
1429 match=matchfn, stat=True)
1430 self.ui.write((',\n "diffstat": "%s"')
1430 self.ui.write((',\n "diffstat": "%s"')
1431 % j(self.ui.popbuffer()))
1431 % j(self.ui.popbuffer()))
1432 if diff:
1432 if diff:
1433 self.ui.pushbuffer()
1433 self.ui.pushbuffer()
1434 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1434 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1435 match=matchfn, stat=False)
1435 match=matchfn, stat=False)
1436 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1436 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1437
1437
1438 self.ui.write("\n }")
1438 self.ui.write("\n }")
1439
1439
1440 class changeset_templater(changeset_printer):
1440 class changeset_templater(changeset_printer):
1441 '''format changeset information.'''
1441 '''format changeset information.'''
1442
1442
1443 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1443 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1444 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1444 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1445 defaulttempl = {
1446 'parent': '{rev}:{node|formatnode} ',
1447 'manifest': '{rev}:{node|formatnode}',
1448 'file_copy': '{name} ({source})',
1449 'envvar': '{key}={value}',
1450 'extra': '{key}={value|stringescape}'
1451 }
1452 # filecopy is preserved for compatibility reasons
1453 defaulttempl['filecopy'] = defaulttempl['file_copy']
1454 assert not (tmpl and mapfile)
1445 assert not (tmpl and mapfile)
1446 defaulttempl = templatekw.defaulttempl
1455 if mapfile:
1447 if mapfile:
1456 self.t = templater.templater.frommapfile(mapfile,
1448 self.t = templater.templater.frommapfile(mapfile,
1457 cache=defaulttempl)
1449 cache=defaulttempl)
1458 else:
1450 else:
1459 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1451 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1460 cache=defaulttempl)
1452 cache=defaulttempl)
1461
1453
1462 self.cache = {}
1454 self.cache = {}
1463
1455
1464 # find correct templates for current mode
1456 # find correct templates for current mode
1465 tmplmodes = [
1457 tmplmodes = [
1466 (True, None),
1458 (True, None),
1467 (self.ui.verbose, 'verbose'),
1459 (self.ui.verbose, 'verbose'),
1468 (self.ui.quiet, 'quiet'),
1460 (self.ui.quiet, 'quiet'),
1469 (self.ui.debugflag, 'debug'),
1461 (self.ui.debugflag, 'debug'),
1470 ]
1462 ]
1471
1463
1472 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1464 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1473 'docheader': '', 'docfooter': ''}
1465 'docheader': '', 'docfooter': ''}
1474 for mode, postfix in tmplmodes:
1466 for mode, postfix in tmplmodes:
1475 for t in self._parts:
1467 for t in self._parts:
1476 cur = t
1468 cur = t
1477 if postfix:
1469 if postfix:
1478 cur += "_" + postfix
1470 cur += "_" + postfix
1479 if mode and cur in self.t:
1471 if mode and cur in self.t:
1480 self._parts[t] = cur
1472 self._parts[t] = cur
1481
1473
1482 if self._parts['docheader']:
1474 if self._parts['docheader']:
1483 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1475 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1484
1476
1485 def close(self):
1477 def close(self):
1486 if self._parts['docfooter']:
1478 if self._parts['docfooter']:
1487 if not self.footer:
1479 if not self.footer:
1488 self.footer = ""
1480 self.footer = ""
1489 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1481 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1490 return super(changeset_templater, self).close()
1482 return super(changeset_templater, self).close()
1491
1483
1492 def _show(self, ctx, copies, matchfn, props):
1484 def _show(self, ctx, copies, matchfn, props):
1493 '''show a single changeset or file revision'''
1485 '''show a single changeset or file revision'''
1494 props = props.copy()
1486 props = props.copy()
1495 props.update(templatekw.keywords)
1487 props.update(templatekw.keywords)
1496 props['templ'] = self.t
1488 props['templ'] = self.t
1497 props['ctx'] = ctx
1489 props['ctx'] = ctx
1498 props['repo'] = self.repo
1490 props['repo'] = self.repo
1499 props['ui'] = self.repo.ui
1491 props['ui'] = self.repo.ui
1500 props['revcache'] = {'copies': copies}
1492 props['revcache'] = {'copies': copies}
1501 props['cache'] = self.cache
1493 props['cache'] = self.cache
1502
1494
1503 # write header
1495 # write header
1504 if self._parts['header']:
1496 if self._parts['header']:
1505 h = templater.stringify(self.t(self._parts['header'], **props))
1497 h = templater.stringify(self.t(self._parts['header'], **props))
1506 if self.buffered:
1498 if self.buffered:
1507 self.header[ctx.rev()] = h
1499 self.header[ctx.rev()] = h
1508 else:
1500 else:
1509 if self.lastheader != h:
1501 if self.lastheader != h:
1510 self.lastheader = h
1502 self.lastheader = h
1511 self.ui.write(h)
1503 self.ui.write(h)
1512
1504
1513 # write changeset metadata, then patch if requested
1505 # write changeset metadata, then patch if requested
1514 key = self._parts['changeset']
1506 key = self._parts['changeset']
1515 self.ui.write(templater.stringify(self.t(key, **props)))
1507 self.ui.write(templater.stringify(self.t(key, **props)))
1516 self.showpatch(ctx, matchfn)
1508 self.showpatch(ctx, matchfn)
1517
1509
1518 if self._parts['footer']:
1510 if self._parts['footer']:
1519 if not self.footer:
1511 if not self.footer:
1520 self.footer = templater.stringify(
1512 self.footer = templater.stringify(
1521 self.t(self._parts['footer'], **props))
1513 self.t(self._parts['footer'], **props))
1522
1514
1523 def gettemplate(ui, tmpl, style):
1515 def gettemplate(ui, tmpl, style):
1524 """
1516 """
1525 Find the template matching the given template spec or style.
1517 Find the template matching the given template spec or style.
1526 """
1518 """
1527
1519
1528 # ui settings
1520 # ui settings
1529 if not tmpl and not style: # template are stronger than style
1521 if not tmpl and not style: # template are stronger than style
1530 tmpl = ui.config('ui', 'logtemplate')
1522 tmpl = ui.config('ui', 'logtemplate')
1531 if tmpl:
1523 if tmpl:
1532 return templater.unquotestring(tmpl), None
1524 return templater.unquotestring(tmpl), None
1533 else:
1525 else:
1534 style = util.expandpath(ui.config('ui', 'style', ''))
1526 style = util.expandpath(ui.config('ui', 'style', ''))
1535
1527
1536 if not tmpl and style:
1528 if not tmpl and style:
1537 mapfile = style
1529 mapfile = style
1538 if not os.path.split(mapfile)[0]:
1530 if not os.path.split(mapfile)[0]:
1539 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1531 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1540 or templater.templatepath(mapfile))
1532 or templater.templatepath(mapfile))
1541 if mapname:
1533 if mapname:
1542 mapfile = mapname
1534 mapfile = mapname
1543 return None, mapfile
1535 return None, mapfile
1544
1536
1545 if not tmpl:
1537 if not tmpl:
1546 return None, None
1538 return None, None
1547
1539
1548 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1540 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1549
1541
1550 def show_changeset(ui, repo, opts, buffered=False):
1542 def show_changeset(ui, repo, opts, buffered=False):
1551 """show one changeset using template or regular display.
1543 """show one changeset using template or regular display.
1552
1544
1553 Display format will be the first non-empty hit of:
1545 Display format will be the first non-empty hit of:
1554 1. option 'template'
1546 1. option 'template'
1555 2. option 'style'
1547 2. option 'style'
1556 3. [ui] setting 'logtemplate'
1548 3. [ui] setting 'logtemplate'
1557 4. [ui] setting 'style'
1549 4. [ui] setting 'style'
1558 If all of these values are either the unset or the empty string,
1550 If all of these values are either the unset or the empty string,
1559 regular display via changeset_printer() is done.
1551 regular display via changeset_printer() is done.
1560 """
1552 """
1561 # options
1553 # options
1562 matchfn = None
1554 matchfn = None
1563 if opts.get('patch') or opts.get('stat'):
1555 if opts.get('patch') or opts.get('stat'):
1564 matchfn = scmutil.matchall(repo)
1556 matchfn = scmutil.matchall(repo)
1565
1557
1566 if opts.get('template') == 'json':
1558 if opts.get('template') == 'json':
1567 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1559 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1568
1560
1569 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1561 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1570
1562
1571 if not tmpl and not mapfile:
1563 if not tmpl and not mapfile:
1572 return changeset_printer(ui, repo, matchfn, opts, buffered)
1564 return changeset_printer(ui, repo, matchfn, opts, buffered)
1573
1565
1574 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1566 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1575
1567
1576 def showmarker(fm, marker, index=None):
1568 def showmarker(fm, marker, index=None):
1577 """utility function to display obsolescence marker in a readable way
1569 """utility function to display obsolescence marker in a readable way
1578
1570
1579 To be used by debug function."""
1571 To be used by debug function."""
1580 if index is not None:
1572 if index is not None:
1581 fm.write('index', '%i ', index)
1573 fm.write('index', '%i ', index)
1582 fm.write('precnode', '%s ', hex(marker.precnode()))
1574 fm.write('precnode', '%s ', hex(marker.precnode()))
1583 succs = marker.succnodes()
1575 succs = marker.succnodes()
1584 fm.condwrite(succs, 'succnodes', '%s ',
1576 fm.condwrite(succs, 'succnodes', '%s ',
1585 fm.formatlist(map(hex, succs), name='node'))
1577 fm.formatlist(map(hex, succs), name='node'))
1586 fm.write('flag', '%X ', marker.flags())
1578 fm.write('flag', '%X ', marker.flags())
1587 parents = marker.parentnodes()
1579 parents = marker.parentnodes()
1588 if parents is not None:
1580 if parents is not None:
1589 fm.write('parentnodes', '{%s} ',
1581 fm.write('parentnodes', '{%s} ',
1590 fm.formatlist(map(hex, parents), name='node', sep=', '))
1582 fm.formatlist(map(hex, parents), name='node', sep=', '))
1591 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1583 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1592 meta = marker.metadata().copy()
1584 meta = marker.metadata().copy()
1593 meta.pop('date', None)
1585 meta.pop('date', None)
1594 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1586 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1595 fm.plain('\n')
1587 fm.plain('\n')
1596
1588
1597 def finddate(ui, repo, date):
1589 def finddate(ui, repo, date):
1598 """Find the tipmost changeset that matches the given date spec"""
1590 """Find the tipmost changeset that matches the given date spec"""
1599
1591
1600 df = util.matchdate(date)
1592 df = util.matchdate(date)
1601 m = scmutil.matchall(repo)
1593 m = scmutil.matchall(repo)
1602 results = {}
1594 results = {}
1603
1595
1604 def prep(ctx, fns):
1596 def prep(ctx, fns):
1605 d = ctx.date()
1597 d = ctx.date()
1606 if df(d[0]):
1598 if df(d[0]):
1607 results[ctx.rev()] = d
1599 results[ctx.rev()] = d
1608
1600
1609 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1601 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1610 rev = ctx.rev()
1602 rev = ctx.rev()
1611 if rev in results:
1603 if rev in results:
1612 ui.status(_("found revision %s from %s\n") %
1604 ui.status(_("found revision %s from %s\n") %
1613 (rev, util.datestr(results[rev])))
1605 (rev, util.datestr(results[rev])))
1614 return str(rev)
1606 return str(rev)
1615
1607
1616 raise error.Abort(_("revision matching date not found"))
1608 raise error.Abort(_("revision matching date not found"))
1617
1609
1618 def increasingwindows(windowsize=8, sizelimit=512):
1610 def increasingwindows(windowsize=8, sizelimit=512):
1619 while True:
1611 while True:
1620 yield windowsize
1612 yield windowsize
1621 if windowsize < sizelimit:
1613 if windowsize < sizelimit:
1622 windowsize *= 2
1614 windowsize *= 2
1623
1615
1624 class FileWalkError(Exception):
1616 class FileWalkError(Exception):
1625 pass
1617 pass
1626
1618
1627 def walkfilerevs(repo, match, follow, revs, fncache):
1619 def walkfilerevs(repo, match, follow, revs, fncache):
1628 '''Walks the file history for the matched files.
1620 '''Walks the file history for the matched files.
1629
1621
1630 Returns the changeset revs that are involved in the file history.
1622 Returns the changeset revs that are involved in the file history.
1631
1623
1632 Throws FileWalkError if the file history can't be walked using
1624 Throws FileWalkError if the file history can't be walked using
1633 filelogs alone.
1625 filelogs alone.
1634 '''
1626 '''
1635 wanted = set()
1627 wanted = set()
1636 copies = []
1628 copies = []
1637 minrev, maxrev = min(revs), max(revs)
1629 minrev, maxrev = min(revs), max(revs)
1638 def filerevgen(filelog, last):
1630 def filerevgen(filelog, last):
1639 """
1631 """
1640 Only files, no patterns. Check the history of each file.
1632 Only files, no patterns. Check the history of each file.
1641
1633
1642 Examines filelog entries within minrev, maxrev linkrev range
1634 Examines filelog entries within minrev, maxrev linkrev range
1643 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1635 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1644 tuples in backwards order
1636 tuples in backwards order
1645 """
1637 """
1646 cl_count = len(repo)
1638 cl_count = len(repo)
1647 revs = []
1639 revs = []
1648 for j in xrange(0, last + 1):
1640 for j in xrange(0, last + 1):
1649 linkrev = filelog.linkrev(j)
1641 linkrev = filelog.linkrev(j)
1650 if linkrev < minrev:
1642 if linkrev < minrev:
1651 continue
1643 continue
1652 # only yield rev for which we have the changelog, it can
1644 # only yield rev for which we have the changelog, it can
1653 # happen while doing "hg log" during a pull or commit
1645 # happen while doing "hg log" during a pull or commit
1654 if linkrev >= cl_count:
1646 if linkrev >= cl_count:
1655 break
1647 break
1656
1648
1657 parentlinkrevs = []
1649 parentlinkrevs = []
1658 for p in filelog.parentrevs(j):
1650 for p in filelog.parentrevs(j):
1659 if p != nullrev:
1651 if p != nullrev:
1660 parentlinkrevs.append(filelog.linkrev(p))
1652 parentlinkrevs.append(filelog.linkrev(p))
1661 n = filelog.node(j)
1653 n = filelog.node(j)
1662 revs.append((linkrev, parentlinkrevs,
1654 revs.append((linkrev, parentlinkrevs,
1663 follow and filelog.renamed(n)))
1655 follow and filelog.renamed(n)))
1664
1656
1665 return reversed(revs)
1657 return reversed(revs)
1666 def iterfiles():
1658 def iterfiles():
1667 pctx = repo['.']
1659 pctx = repo['.']
1668 for filename in match.files():
1660 for filename in match.files():
1669 if follow:
1661 if follow:
1670 if filename not in pctx:
1662 if filename not in pctx:
1671 raise error.Abort(_('cannot follow file not in parent '
1663 raise error.Abort(_('cannot follow file not in parent '
1672 'revision: "%s"') % filename)
1664 'revision: "%s"') % filename)
1673 yield filename, pctx[filename].filenode()
1665 yield filename, pctx[filename].filenode()
1674 else:
1666 else:
1675 yield filename, None
1667 yield filename, None
1676 for filename_node in copies:
1668 for filename_node in copies:
1677 yield filename_node
1669 yield filename_node
1678
1670
1679 for file_, node in iterfiles():
1671 for file_, node in iterfiles():
1680 filelog = repo.file(file_)
1672 filelog = repo.file(file_)
1681 if not len(filelog):
1673 if not len(filelog):
1682 if node is None:
1674 if node is None:
1683 # A zero count may be a directory or deleted file, so
1675 # A zero count may be a directory or deleted file, so
1684 # try to find matching entries on the slow path.
1676 # try to find matching entries on the slow path.
1685 if follow:
1677 if follow:
1686 raise error.Abort(
1678 raise error.Abort(
1687 _('cannot follow nonexistent file: "%s"') % file_)
1679 _('cannot follow nonexistent file: "%s"') % file_)
1688 raise FileWalkError("Cannot walk via filelog")
1680 raise FileWalkError("Cannot walk via filelog")
1689 else:
1681 else:
1690 continue
1682 continue
1691
1683
1692 if node is None:
1684 if node is None:
1693 last = len(filelog) - 1
1685 last = len(filelog) - 1
1694 else:
1686 else:
1695 last = filelog.rev(node)
1687 last = filelog.rev(node)
1696
1688
1697 # keep track of all ancestors of the file
1689 # keep track of all ancestors of the file
1698 ancestors = set([filelog.linkrev(last)])
1690 ancestors = set([filelog.linkrev(last)])
1699
1691
1700 # iterate from latest to oldest revision
1692 # iterate from latest to oldest revision
1701 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1693 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1702 if not follow:
1694 if not follow:
1703 if rev > maxrev:
1695 if rev > maxrev:
1704 continue
1696 continue
1705 else:
1697 else:
1706 # Note that last might not be the first interesting
1698 # Note that last might not be the first interesting
1707 # rev to us:
1699 # rev to us:
1708 # if the file has been changed after maxrev, we'll
1700 # if the file has been changed after maxrev, we'll
1709 # have linkrev(last) > maxrev, and we still need
1701 # have linkrev(last) > maxrev, and we still need
1710 # to explore the file graph
1702 # to explore the file graph
1711 if rev not in ancestors:
1703 if rev not in ancestors:
1712 continue
1704 continue
1713 # XXX insert 1327 fix here
1705 # XXX insert 1327 fix here
1714 if flparentlinkrevs:
1706 if flparentlinkrevs:
1715 ancestors.update(flparentlinkrevs)
1707 ancestors.update(flparentlinkrevs)
1716
1708
1717 fncache.setdefault(rev, []).append(file_)
1709 fncache.setdefault(rev, []).append(file_)
1718 wanted.add(rev)
1710 wanted.add(rev)
1719 if copied:
1711 if copied:
1720 copies.append(copied)
1712 copies.append(copied)
1721
1713
1722 return wanted
1714 return wanted
1723
1715
1724 class _followfilter(object):
1716 class _followfilter(object):
1725 def __init__(self, repo, onlyfirst=False):
1717 def __init__(self, repo, onlyfirst=False):
1726 self.repo = repo
1718 self.repo = repo
1727 self.startrev = nullrev
1719 self.startrev = nullrev
1728 self.roots = set()
1720 self.roots = set()
1729 self.onlyfirst = onlyfirst
1721 self.onlyfirst = onlyfirst
1730
1722
1731 def match(self, rev):
1723 def match(self, rev):
1732 def realparents(rev):
1724 def realparents(rev):
1733 if self.onlyfirst:
1725 if self.onlyfirst:
1734 return self.repo.changelog.parentrevs(rev)[0:1]
1726 return self.repo.changelog.parentrevs(rev)[0:1]
1735 else:
1727 else:
1736 return filter(lambda x: x != nullrev,
1728 return filter(lambda x: x != nullrev,
1737 self.repo.changelog.parentrevs(rev))
1729 self.repo.changelog.parentrevs(rev))
1738
1730
1739 if self.startrev == nullrev:
1731 if self.startrev == nullrev:
1740 self.startrev = rev
1732 self.startrev = rev
1741 return True
1733 return True
1742
1734
1743 if rev > self.startrev:
1735 if rev > self.startrev:
1744 # forward: all descendants
1736 # forward: all descendants
1745 if not self.roots:
1737 if not self.roots:
1746 self.roots.add(self.startrev)
1738 self.roots.add(self.startrev)
1747 for parent in realparents(rev):
1739 for parent in realparents(rev):
1748 if parent in self.roots:
1740 if parent in self.roots:
1749 self.roots.add(rev)
1741 self.roots.add(rev)
1750 return True
1742 return True
1751 else:
1743 else:
1752 # backwards: all parents
1744 # backwards: all parents
1753 if not self.roots:
1745 if not self.roots:
1754 self.roots.update(realparents(self.startrev))
1746 self.roots.update(realparents(self.startrev))
1755 if rev in self.roots:
1747 if rev in self.roots:
1756 self.roots.remove(rev)
1748 self.roots.remove(rev)
1757 self.roots.update(realparents(rev))
1749 self.roots.update(realparents(rev))
1758 return True
1750 return True
1759
1751
1760 return False
1752 return False
1761
1753
1762 def walkchangerevs(repo, match, opts, prepare):
1754 def walkchangerevs(repo, match, opts, prepare):
1763 '''Iterate over files and the revs in which they changed.
1755 '''Iterate over files and the revs in which they changed.
1764
1756
1765 Callers most commonly need to iterate backwards over the history
1757 Callers most commonly need to iterate backwards over the history
1766 in which they are interested. Doing so has awful (quadratic-looking)
1758 in which they are interested. Doing so has awful (quadratic-looking)
1767 performance, so we use iterators in a "windowed" way.
1759 performance, so we use iterators in a "windowed" way.
1768
1760
1769 We walk a window of revisions in the desired order. Within the
1761 We walk a window of revisions in the desired order. Within the
1770 window, we first walk forwards to gather data, then in the desired
1762 window, we first walk forwards to gather data, then in the desired
1771 order (usually backwards) to display it.
1763 order (usually backwards) to display it.
1772
1764
1773 This function returns an iterator yielding contexts. Before
1765 This function returns an iterator yielding contexts. Before
1774 yielding each context, the iterator will first call the prepare
1766 yielding each context, the iterator will first call the prepare
1775 function on each context in the window in forward order.'''
1767 function on each context in the window in forward order.'''
1776
1768
1777 follow = opts.get('follow') or opts.get('follow_first')
1769 follow = opts.get('follow') or opts.get('follow_first')
1778 revs = _logrevs(repo, opts)
1770 revs = _logrevs(repo, opts)
1779 if not revs:
1771 if not revs:
1780 return []
1772 return []
1781 wanted = set()
1773 wanted = set()
1782 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1774 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1783 opts.get('removed'))
1775 opts.get('removed'))
1784 fncache = {}
1776 fncache = {}
1785 change = repo.changectx
1777 change = repo.changectx
1786
1778
1787 # First step is to fill wanted, the set of revisions that we want to yield.
1779 # First step is to fill wanted, the set of revisions that we want to yield.
1788 # When it does not induce extra cost, we also fill fncache for revisions in
1780 # When it does not induce extra cost, we also fill fncache for revisions in
1789 # wanted: a cache of filenames that were changed (ctx.files()) and that
1781 # wanted: a cache of filenames that were changed (ctx.files()) and that
1790 # match the file filtering conditions.
1782 # match the file filtering conditions.
1791
1783
1792 if match.always():
1784 if match.always():
1793 # No files, no patterns. Display all revs.
1785 # No files, no patterns. Display all revs.
1794 wanted = revs
1786 wanted = revs
1795 elif not slowpath:
1787 elif not slowpath:
1796 # We only have to read through the filelog to find wanted revisions
1788 # We only have to read through the filelog to find wanted revisions
1797
1789
1798 try:
1790 try:
1799 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1791 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1800 except FileWalkError:
1792 except FileWalkError:
1801 slowpath = True
1793 slowpath = True
1802
1794
1803 # We decided to fall back to the slowpath because at least one
1795 # We decided to fall back to the slowpath because at least one
1804 # of the paths was not a file. Check to see if at least one of them
1796 # of the paths was not a file. Check to see if at least one of them
1805 # existed in history, otherwise simply return
1797 # existed in history, otherwise simply return
1806 for path in match.files():
1798 for path in match.files():
1807 if path == '.' or path in repo.store:
1799 if path == '.' or path in repo.store:
1808 break
1800 break
1809 else:
1801 else:
1810 return []
1802 return []
1811
1803
1812 if slowpath:
1804 if slowpath:
1813 # We have to read the changelog to match filenames against
1805 # We have to read the changelog to match filenames against
1814 # changed files
1806 # changed files
1815
1807
1816 if follow:
1808 if follow:
1817 raise error.Abort(_('can only follow copies/renames for explicit '
1809 raise error.Abort(_('can only follow copies/renames for explicit '
1818 'filenames'))
1810 'filenames'))
1819
1811
1820 # The slow path checks files modified in every changeset.
1812 # The slow path checks files modified in every changeset.
1821 # This is really slow on large repos, so compute the set lazily.
1813 # This is really slow on large repos, so compute the set lazily.
1822 class lazywantedset(object):
1814 class lazywantedset(object):
1823 def __init__(self):
1815 def __init__(self):
1824 self.set = set()
1816 self.set = set()
1825 self.revs = set(revs)
1817 self.revs = set(revs)
1826
1818
1827 # No need to worry about locality here because it will be accessed
1819 # No need to worry about locality here because it will be accessed
1828 # in the same order as the increasing window below.
1820 # in the same order as the increasing window below.
1829 def __contains__(self, value):
1821 def __contains__(self, value):
1830 if value in self.set:
1822 if value in self.set:
1831 return True
1823 return True
1832 elif not value in self.revs:
1824 elif not value in self.revs:
1833 return False
1825 return False
1834 else:
1826 else:
1835 self.revs.discard(value)
1827 self.revs.discard(value)
1836 ctx = change(value)
1828 ctx = change(value)
1837 matches = filter(match, ctx.files())
1829 matches = filter(match, ctx.files())
1838 if matches:
1830 if matches:
1839 fncache[value] = matches
1831 fncache[value] = matches
1840 self.set.add(value)
1832 self.set.add(value)
1841 return True
1833 return True
1842 return False
1834 return False
1843
1835
1844 def discard(self, value):
1836 def discard(self, value):
1845 self.revs.discard(value)
1837 self.revs.discard(value)
1846 self.set.discard(value)
1838 self.set.discard(value)
1847
1839
1848 wanted = lazywantedset()
1840 wanted = lazywantedset()
1849
1841
1850 # it might be worthwhile to do this in the iterator if the rev range
1842 # it might be worthwhile to do this in the iterator if the rev range
1851 # is descending and the prune args are all within that range
1843 # is descending and the prune args are all within that range
1852 for rev in opts.get('prune', ()):
1844 for rev in opts.get('prune', ()):
1853 rev = repo[rev].rev()
1845 rev = repo[rev].rev()
1854 ff = _followfilter(repo)
1846 ff = _followfilter(repo)
1855 stop = min(revs[0], revs[-1])
1847 stop = min(revs[0], revs[-1])
1856 for x in xrange(rev, stop - 1, -1):
1848 for x in xrange(rev, stop - 1, -1):
1857 if ff.match(x):
1849 if ff.match(x):
1858 wanted = wanted - [x]
1850 wanted = wanted - [x]
1859
1851
1860 # Now that wanted is correctly initialized, we can iterate over the
1852 # Now that wanted is correctly initialized, we can iterate over the
1861 # revision range, yielding only revisions in wanted.
1853 # revision range, yielding only revisions in wanted.
1862 def iterate():
1854 def iterate():
1863 if follow and match.always():
1855 if follow and match.always():
1864 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1856 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1865 def want(rev):
1857 def want(rev):
1866 return ff.match(rev) and rev in wanted
1858 return ff.match(rev) and rev in wanted
1867 else:
1859 else:
1868 def want(rev):
1860 def want(rev):
1869 return rev in wanted
1861 return rev in wanted
1870
1862
1871 it = iter(revs)
1863 it = iter(revs)
1872 stopiteration = False
1864 stopiteration = False
1873 for windowsize in increasingwindows():
1865 for windowsize in increasingwindows():
1874 nrevs = []
1866 nrevs = []
1875 for i in xrange(windowsize):
1867 for i in xrange(windowsize):
1876 rev = next(it, None)
1868 rev = next(it, None)
1877 if rev is None:
1869 if rev is None:
1878 stopiteration = True
1870 stopiteration = True
1879 break
1871 break
1880 elif want(rev):
1872 elif want(rev):
1881 nrevs.append(rev)
1873 nrevs.append(rev)
1882 for rev in sorted(nrevs):
1874 for rev in sorted(nrevs):
1883 fns = fncache.get(rev)
1875 fns = fncache.get(rev)
1884 ctx = change(rev)
1876 ctx = change(rev)
1885 if not fns:
1877 if not fns:
1886 def fns_generator():
1878 def fns_generator():
1887 for f in ctx.files():
1879 for f in ctx.files():
1888 if match(f):
1880 if match(f):
1889 yield f
1881 yield f
1890 fns = fns_generator()
1882 fns = fns_generator()
1891 prepare(ctx, fns)
1883 prepare(ctx, fns)
1892 for rev in nrevs:
1884 for rev in nrevs:
1893 yield change(rev)
1885 yield change(rev)
1894
1886
1895 if stopiteration:
1887 if stopiteration:
1896 break
1888 break
1897
1889
1898 return iterate()
1890 return iterate()
1899
1891
1900 def _makefollowlogfilematcher(repo, files, followfirst):
1892 def _makefollowlogfilematcher(repo, files, followfirst):
1901 # When displaying a revision with --patch --follow FILE, we have
1893 # When displaying a revision with --patch --follow FILE, we have
1902 # to know which file of the revision must be diffed. With
1894 # to know which file of the revision must be diffed. With
1903 # --follow, we want the names of the ancestors of FILE in the
1895 # --follow, we want the names of the ancestors of FILE in the
1904 # revision, stored in "fcache". "fcache" is populated by
1896 # revision, stored in "fcache". "fcache" is populated by
1905 # reproducing the graph traversal already done by --follow revset
1897 # reproducing the graph traversal already done by --follow revset
1906 # and relating revs to file names (which is not "correct" but
1898 # and relating revs to file names (which is not "correct" but
1907 # good enough).
1899 # good enough).
1908 fcache = {}
1900 fcache = {}
1909 fcacheready = [False]
1901 fcacheready = [False]
1910 pctx = repo['.']
1902 pctx = repo['.']
1911
1903
1912 def populate():
1904 def populate():
1913 for fn in files:
1905 for fn in files:
1914 fctx = pctx[fn]
1906 fctx = pctx[fn]
1915 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1907 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1916 for c in fctx.ancestors(followfirst=followfirst):
1908 for c in fctx.ancestors(followfirst=followfirst):
1917 fcache.setdefault(c.rev(), set()).add(c.path())
1909 fcache.setdefault(c.rev(), set()).add(c.path())
1918
1910
1919 def filematcher(rev):
1911 def filematcher(rev):
1920 if not fcacheready[0]:
1912 if not fcacheready[0]:
1921 # Lazy initialization
1913 # Lazy initialization
1922 fcacheready[0] = True
1914 fcacheready[0] = True
1923 populate()
1915 populate()
1924 return scmutil.matchfiles(repo, fcache.get(rev, []))
1916 return scmutil.matchfiles(repo, fcache.get(rev, []))
1925
1917
1926 return filematcher
1918 return filematcher
1927
1919
1928 def _makenofollowlogfilematcher(repo, pats, opts):
1920 def _makenofollowlogfilematcher(repo, pats, opts):
1929 '''hook for extensions to override the filematcher for non-follow cases'''
1921 '''hook for extensions to override the filematcher for non-follow cases'''
1930 return None
1922 return None
1931
1923
1932 def _makelogrevset(repo, pats, opts, revs):
1924 def _makelogrevset(repo, pats, opts, revs):
1933 """Return (expr, filematcher) where expr is a revset string built
1925 """Return (expr, filematcher) where expr is a revset string built
1934 from log options and file patterns or None. If --stat or --patch
1926 from log options and file patterns or None. If --stat or --patch
1935 are not passed filematcher is None. Otherwise it is a callable
1927 are not passed filematcher is None. Otherwise it is a callable
1936 taking a revision number and returning a match objects filtering
1928 taking a revision number and returning a match objects filtering
1937 the files to be detailed when displaying the revision.
1929 the files to be detailed when displaying the revision.
1938 """
1930 """
1939 opt2revset = {
1931 opt2revset = {
1940 'no_merges': ('not merge()', None),
1932 'no_merges': ('not merge()', None),
1941 'only_merges': ('merge()', None),
1933 'only_merges': ('merge()', None),
1942 '_ancestors': ('ancestors(%(val)s)', None),
1934 '_ancestors': ('ancestors(%(val)s)', None),
1943 '_fancestors': ('_firstancestors(%(val)s)', None),
1935 '_fancestors': ('_firstancestors(%(val)s)', None),
1944 '_descendants': ('descendants(%(val)s)', None),
1936 '_descendants': ('descendants(%(val)s)', None),
1945 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1937 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1946 '_matchfiles': ('_matchfiles(%(val)s)', None),
1938 '_matchfiles': ('_matchfiles(%(val)s)', None),
1947 'date': ('date(%(val)r)', None),
1939 'date': ('date(%(val)r)', None),
1948 'branch': ('branch(%(val)r)', ' or '),
1940 'branch': ('branch(%(val)r)', ' or '),
1949 '_patslog': ('filelog(%(val)r)', ' or '),
1941 '_patslog': ('filelog(%(val)r)', ' or '),
1950 '_patsfollow': ('follow(%(val)r)', ' or '),
1942 '_patsfollow': ('follow(%(val)r)', ' or '),
1951 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1943 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1952 'keyword': ('keyword(%(val)r)', ' or '),
1944 'keyword': ('keyword(%(val)r)', ' or '),
1953 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1945 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1954 'user': ('user(%(val)r)', ' or '),
1946 'user': ('user(%(val)r)', ' or '),
1955 }
1947 }
1956
1948
1957 opts = dict(opts)
1949 opts = dict(opts)
1958 # follow or not follow?
1950 # follow or not follow?
1959 follow = opts.get('follow') or opts.get('follow_first')
1951 follow = opts.get('follow') or opts.get('follow_first')
1960 if opts.get('follow_first'):
1952 if opts.get('follow_first'):
1961 followfirst = 1
1953 followfirst = 1
1962 else:
1954 else:
1963 followfirst = 0
1955 followfirst = 0
1964 # --follow with FILE behavior depends on revs...
1956 # --follow with FILE behavior depends on revs...
1965 it = iter(revs)
1957 it = iter(revs)
1966 startrev = next(it)
1958 startrev = next(it)
1967 followdescendants = startrev < next(it, startrev)
1959 followdescendants = startrev < next(it, startrev)
1968
1960
1969 # branch and only_branch are really aliases and must be handled at
1961 # branch and only_branch are really aliases and must be handled at
1970 # the same time
1962 # the same time
1971 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1963 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1972 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1964 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1973 # pats/include/exclude are passed to match.match() directly in
1965 # pats/include/exclude are passed to match.match() directly in
1974 # _matchfiles() revset but walkchangerevs() builds its matcher with
1966 # _matchfiles() revset but walkchangerevs() builds its matcher with
1975 # scmutil.match(). The difference is input pats are globbed on
1967 # scmutil.match(). The difference is input pats are globbed on
1976 # platforms without shell expansion (windows).
1968 # platforms without shell expansion (windows).
1977 wctx = repo[None]
1969 wctx = repo[None]
1978 match, pats = scmutil.matchandpats(wctx, pats, opts)
1970 match, pats = scmutil.matchandpats(wctx, pats, opts)
1979 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1971 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1980 opts.get('removed'))
1972 opts.get('removed'))
1981 if not slowpath:
1973 if not slowpath:
1982 for f in match.files():
1974 for f in match.files():
1983 if follow and f not in wctx:
1975 if follow and f not in wctx:
1984 # If the file exists, it may be a directory, so let it
1976 # If the file exists, it may be a directory, so let it
1985 # take the slow path.
1977 # take the slow path.
1986 if os.path.exists(repo.wjoin(f)):
1978 if os.path.exists(repo.wjoin(f)):
1987 slowpath = True
1979 slowpath = True
1988 continue
1980 continue
1989 else:
1981 else:
1990 raise error.Abort(_('cannot follow file not in parent '
1982 raise error.Abort(_('cannot follow file not in parent '
1991 'revision: "%s"') % f)
1983 'revision: "%s"') % f)
1992 filelog = repo.file(f)
1984 filelog = repo.file(f)
1993 if not filelog:
1985 if not filelog:
1994 # A zero count may be a directory or deleted file, so
1986 # A zero count may be a directory or deleted file, so
1995 # try to find matching entries on the slow path.
1987 # try to find matching entries on the slow path.
1996 if follow:
1988 if follow:
1997 raise error.Abort(
1989 raise error.Abort(
1998 _('cannot follow nonexistent file: "%s"') % f)
1990 _('cannot follow nonexistent file: "%s"') % f)
1999 slowpath = True
1991 slowpath = True
2000
1992
2001 # We decided to fall back to the slowpath because at least one
1993 # We decided to fall back to the slowpath because at least one
2002 # of the paths was not a file. Check to see if at least one of them
1994 # of the paths was not a file. Check to see if at least one of them
2003 # existed in history - in that case, we'll continue down the
1995 # existed in history - in that case, we'll continue down the
2004 # slowpath; otherwise, we can turn off the slowpath
1996 # slowpath; otherwise, we can turn off the slowpath
2005 if slowpath:
1997 if slowpath:
2006 for path in match.files():
1998 for path in match.files():
2007 if path == '.' or path in repo.store:
1999 if path == '.' or path in repo.store:
2008 break
2000 break
2009 else:
2001 else:
2010 slowpath = False
2002 slowpath = False
2011
2003
2012 fpats = ('_patsfollow', '_patsfollowfirst')
2004 fpats = ('_patsfollow', '_patsfollowfirst')
2013 fnopats = (('_ancestors', '_fancestors'),
2005 fnopats = (('_ancestors', '_fancestors'),
2014 ('_descendants', '_fdescendants'))
2006 ('_descendants', '_fdescendants'))
2015 if slowpath:
2007 if slowpath:
2016 # See walkchangerevs() slow path.
2008 # See walkchangerevs() slow path.
2017 #
2009 #
2018 # pats/include/exclude cannot be represented as separate
2010 # pats/include/exclude cannot be represented as separate
2019 # revset expressions as their filtering logic applies at file
2011 # revset expressions as their filtering logic applies at file
2020 # level. For instance "-I a -X a" matches a revision touching
2012 # level. For instance "-I a -X a" matches a revision touching
2021 # "a" and "b" while "file(a) and not file(b)" does
2013 # "a" and "b" while "file(a) and not file(b)" does
2022 # not. Besides, filesets are evaluated against the working
2014 # not. Besides, filesets are evaluated against the working
2023 # directory.
2015 # directory.
2024 matchargs = ['r:', 'd:relpath']
2016 matchargs = ['r:', 'd:relpath']
2025 for p in pats:
2017 for p in pats:
2026 matchargs.append('p:' + p)
2018 matchargs.append('p:' + p)
2027 for p in opts.get('include', []):
2019 for p in opts.get('include', []):
2028 matchargs.append('i:' + p)
2020 matchargs.append('i:' + p)
2029 for p in opts.get('exclude', []):
2021 for p in opts.get('exclude', []):
2030 matchargs.append('x:' + p)
2022 matchargs.append('x:' + p)
2031 matchargs = ','.join(('%r' % p) for p in matchargs)
2023 matchargs = ','.join(('%r' % p) for p in matchargs)
2032 opts['_matchfiles'] = matchargs
2024 opts['_matchfiles'] = matchargs
2033 if follow:
2025 if follow:
2034 opts[fnopats[0][followfirst]] = '.'
2026 opts[fnopats[0][followfirst]] = '.'
2035 else:
2027 else:
2036 if follow:
2028 if follow:
2037 if pats:
2029 if pats:
2038 # follow() revset interprets its file argument as a
2030 # follow() revset interprets its file argument as a
2039 # manifest entry, so use match.files(), not pats.
2031 # manifest entry, so use match.files(), not pats.
2040 opts[fpats[followfirst]] = list(match.files())
2032 opts[fpats[followfirst]] = list(match.files())
2041 else:
2033 else:
2042 op = fnopats[followdescendants][followfirst]
2034 op = fnopats[followdescendants][followfirst]
2043 opts[op] = 'rev(%d)' % startrev
2035 opts[op] = 'rev(%d)' % startrev
2044 else:
2036 else:
2045 opts['_patslog'] = list(pats)
2037 opts['_patslog'] = list(pats)
2046
2038
2047 filematcher = None
2039 filematcher = None
2048 if opts.get('patch') or opts.get('stat'):
2040 if opts.get('patch') or opts.get('stat'):
2049 # When following files, track renames via a special matcher.
2041 # When following files, track renames via a special matcher.
2050 # If we're forced to take the slowpath it means we're following
2042 # If we're forced to take the slowpath it means we're following
2051 # at least one pattern/directory, so don't bother with rename tracking.
2043 # at least one pattern/directory, so don't bother with rename tracking.
2052 if follow and not match.always() and not slowpath:
2044 if follow and not match.always() and not slowpath:
2053 # _makefollowlogfilematcher expects its files argument to be
2045 # _makefollowlogfilematcher expects its files argument to be
2054 # relative to the repo root, so use match.files(), not pats.
2046 # relative to the repo root, so use match.files(), not pats.
2055 filematcher = _makefollowlogfilematcher(repo, match.files(),
2047 filematcher = _makefollowlogfilematcher(repo, match.files(),
2056 followfirst)
2048 followfirst)
2057 else:
2049 else:
2058 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2050 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2059 if filematcher is None:
2051 if filematcher is None:
2060 filematcher = lambda rev: match
2052 filematcher = lambda rev: match
2061
2053
2062 expr = []
2054 expr = []
2063 for op, val in sorted(opts.iteritems()):
2055 for op, val in sorted(opts.iteritems()):
2064 if not val:
2056 if not val:
2065 continue
2057 continue
2066 if op not in opt2revset:
2058 if op not in opt2revset:
2067 continue
2059 continue
2068 revop, andor = opt2revset[op]
2060 revop, andor = opt2revset[op]
2069 if '%(val)' not in revop:
2061 if '%(val)' not in revop:
2070 expr.append(revop)
2062 expr.append(revop)
2071 else:
2063 else:
2072 if not isinstance(val, list):
2064 if not isinstance(val, list):
2073 e = revop % {'val': val}
2065 e = revop % {'val': val}
2074 else:
2066 else:
2075 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2067 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2076 expr.append(e)
2068 expr.append(e)
2077
2069
2078 if expr:
2070 if expr:
2079 expr = '(' + ' and '.join(expr) + ')'
2071 expr = '(' + ' and '.join(expr) + ')'
2080 else:
2072 else:
2081 expr = None
2073 expr = None
2082 return expr, filematcher
2074 return expr, filematcher
2083
2075
2084 def _logrevs(repo, opts):
2076 def _logrevs(repo, opts):
2085 # Default --rev value depends on --follow but --follow behavior
2077 # Default --rev value depends on --follow but --follow behavior
2086 # depends on revisions resolved from --rev...
2078 # depends on revisions resolved from --rev...
2087 follow = opts.get('follow') or opts.get('follow_first')
2079 follow = opts.get('follow') or opts.get('follow_first')
2088 if opts.get('rev'):
2080 if opts.get('rev'):
2089 revs = scmutil.revrange(repo, opts['rev'])
2081 revs = scmutil.revrange(repo, opts['rev'])
2090 elif follow and repo.dirstate.p1() == nullid:
2082 elif follow and repo.dirstate.p1() == nullid:
2091 revs = smartset.baseset()
2083 revs = smartset.baseset()
2092 elif follow:
2084 elif follow:
2093 revs = repo.revs('reverse(:.)')
2085 revs = repo.revs('reverse(:.)')
2094 else:
2086 else:
2095 revs = smartset.spanset(repo)
2087 revs = smartset.spanset(repo)
2096 revs.reverse()
2088 revs.reverse()
2097 return revs
2089 return revs
2098
2090
2099 def getgraphlogrevs(repo, pats, opts):
2091 def getgraphlogrevs(repo, pats, opts):
2100 """Return (revs, expr, filematcher) where revs is an iterable of
2092 """Return (revs, expr, filematcher) where revs is an iterable of
2101 revision numbers, expr is a revset string built from log options
2093 revision numbers, expr is a revset string built from log options
2102 and file patterns or None, and used to filter 'revs'. If --stat or
2094 and file patterns or None, and used to filter 'revs'. If --stat or
2103 --patch are not passed filematcher is None. Otherwise it is a
2095 --patch are not passed filematcher is None. Otherwise it is a
2104 callable taking a revision number and returning a match objects
2096 callable taking a revision number and returning a match objects
2105 filtering the files to be detailed when displaying the revision.
2097 filtering the files to be detailed when displaying the revision.
2106 """
2098 """
2107 limit = loglimit(opts)
2099 limit = loglimit(opts)
2108 revs = _logrevs(repo, opts)
2100 revs = _logrevs(repo, opts)
2109 if not revs:
2101 if not revs:
2110 return smartset.baseset(), None, None
2102 return smartset.baseset(), None, None
2111 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2103 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2112 if opts.get('rev'):
2104 if opts.get('rev'):
2113 # User-specified revs might be unsorted, but don't sort before
2105 # User-specified revs might be unsorted, but don't sort before
2114 # _makelogrevset because it might depend on the order of revs
2106 # _makelogrevset because it might depend on the order of revs
2115 if not (revs.isdescending() or revs.istopo()):
2107 if not (revs.isdescending() or revs.istopo()):
2116 revs.sort(reverse=True)
2108 revs.sort(reverse=True)
2117 if expr:
2109 if expr:
2118 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2110 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2119 revs = matcher(repo, revs)
2111 revs = matcher(repo, revs)
2120 if limit is not None:
2112 if limit is not None:
2121 limitedrevs = []
2113 limitedrevs = []
2122 for idx, rev in enumerate(revs):
2114 for idx, rev in enumerate(revs):
2123 if idx >= limit:
2115 if idx >= limit:
2124 break
2116 break
2125 limitedrevs.append(rev)
2117 limitedrevs.append(rev)
2126 revs = smartset.baseset(limitedrevs)
2118 revs = smartset.baseset(limitedrevs)
2127
2119
2128 return revs, expr, filematcher
2120 return revs, expr, filematcher
2129
2121
2130 def getlogrevs(repo, pats, opts):
2122 def getlogrevs(repo, pats, opts):
2131 """Return (revs, expr, filematcher) where revs is an iterable of
2123 """Return (revs, expr, filematcher) where revs is an iterable of
2132 revision numbers, expr is a revset string built from log options
2124 revision numbers, expr is a revset string built from log options
2133 and file patterns or None, and used to filter 'revs'. If --stat or
2125 and file patterns or None, and used to filter 'revs'. If --stat or
2134 --patch are not passed filematcher is None. Otherwise it is a
2126 --patch are not passed filematcher is None. Otherwise it is a
2135 callable taking a revision number and returning a match objects
2127 callable taking a revision number and returning a match objects
2136 filtering the files to be detailed when displaying the revision.
2128 filtering the files to be detailed when displaying the revision.
2137 """
2129 """
2138 limit = loglimit(opts)
2130 limit = loglimit(opts)
2139 revs = _logrevs(repo, opts)
2131 revs = _logrevs(repo, opts)
2140 if not revs:
2132 if not revs:
2141 return smartset.baseset([]), None, None
2133 return smartset.baseset([]), None, None
2142 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2134 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2143 if expr:
2135 if expr:
2144 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2136 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2145 revs = matcher(repo, revs)
2137 revs = matcher(repo, revs)
2146 if limit is not None:
2138 if limit is not None:
2147 limitedrevs = []
2139 limitedrevs = []
2148 for idx, r in enumerate(revs):
2140 for idx, r in enumerate(revs):
2149 if limit <= idx:
2141 if limit <= idx:
2150 break
2142 break
2151 limitedrevs.append(r)
2143 limitedrevs.append(r)
2152 revs = smartset.baseset(limitedrevs)
2144 revs = smartset.baseset(limitedrevs)
2153
2145
2154 return revs, expr, filematcher
2146 return revs, expr, filematcher
2155
2147
2156 def _graphnodeformatter(ui, displayer):
2148 def _graphnodeformatter(ui, displayer):
2157 spec = ui.config('ui', 'graphnodetemplate')
2149 spec = ui.config('ui', 'graphnodetemplate')
2158 if not spec:
2150 if not spec:
2159 return templatekw.showgraphnode # fast path for "{graphnode}"
2151 return templatekw.showgraphnode # fast path for "{graphnode}"
2160
2152
2161 templ = formatter.gettemplater(ui, 'graphnode', spec)
2153 templ = formatter.gettemplater(ui, 'graphnode', spec)
2162 cache = {}
2154 cache = {}
2163 if isinstance(displayer, changeset_templater):
2155 if isinstance(displayer, changeset_templater):
2164 cache = displayer.cache # reuse cache of slow templates
2156 cache = displayer.cache # reuse cache of slow templates
2165 props = templatekw.keywords.copy()
2157 props = templatekw.keywords.copy()
2166 props['templ'] = templ
2158 props['templ'] = templ
2167 props['cache'] = cache
2159 props['cache'] = cache
2168 def formatnode(repo, ctx):
2160 def formatnode(repo, ctx):
2169 props['ctx'] = ctx
2161 props['ctx'] = ctx
2170 props['repo'] = repo
2162 props['repo'] = repo
2171 props['ui'] = repo.ui
2163 props['ui'] = repo.ui
2172 props['revcache'] = {}
2164 props['revcache'] = {}
2173 return templater.stringify(templ('graphnode', **props))
2165 return templater.stringify(templ('graphnode', **props))
2174 return formatnode
2166 return formatnode
2175
2167
2176 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2168 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2177 filematcher=None):
2169 filematcher=None):
2178 formatnode = _graphnodeformatter(ui, displayer)
2170 formatnode = _graphnodeformatter(ui, displayer)
2179 state = graphmod.asciistate()
2171 state = graphmod.asciistate()
2180 styles = state['styles']
2172 styles = state['styles']
2181
2173
2182 # only set graph styling if HGPLAIN is not set.
2174 # only set graph styling if HGPLAIN is not set.
2183 if ui.plain('graph'):
2175 if ui.plain('graph'):
2184 # set all edge styles to |, the default pre-3.8 behaviour
2176 # set all edge styles to |, the default pre-3.8 behaviour
2185 styles.update(dict.fromkeys(styles, '|'))
2177 styles.update(dict.fromkeys(styles, '|'))
2186 else:
2178 else:
2187 edgetypes = {
2179 edgetypes = {
2188 'parent': graphmod.PARENT,
2180 'parent': graphmod.PARENT,
2189 'grandparent': graphmod.GRANDPARENT,
2181 'grandparent': graphmod.GRANDPARENT,
2190 'missing': graphmod.MISSINGPARENT
2182 'missing': graphmod.MISSINGPARENT
2191 }
2183 }
2192 for name, key in edgetypes.items():
2184 for name, key in edgetypes.items():
2193 # experimental config: experimental.graphstyle.*
2185 # experimental config: experimental.graphstyle.*
2194 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2186 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2195 styles[key])
2187 styles[key])
2196 if not styles[key]:
2188 if not styles[key]:
2197 styles[key] = None
2189 styles[key] = None
2198
2190
2199 # experimental config: experimental.graphshorten
2191 # experimental config: experimental.graphshorten
2200 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2192 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2201
2193
2202 for rev, type, ctx, parents in dag:
2194 for rev, type, ctx, parents in dag:
2203 char = formatnode(repo, ctx)
2195 char = formatnode(repo, ctx)
2204 copies = None
2196 copies = None
2205 if getrenamed and ctx.rev():
2197 if getrenamed and ctx.rev():
2206 copies = []
2198 copies = []
2207 for fn in ctx.files():
2199 for fn in ctx.files():
2208 rename = getrenamed(fn, ctx.rev())
2200 rename = getrenamed(fn, ctx.rev())
2209 if rename:
2201 if rename:
2210 copies.append((fn, rename[0]))
2202 copies.append((fn, rename[0]))
2211 revmatchfn = None
2203 revmatchfn = None
2212 if filematcher is not None:
2204 if filematcher is not None:
2213 revmatchfn = filematcher(ctx.rev())
2205 revmatchfn = filematcher(ctx.rev())
2214 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2206 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2215 lines = displayer.hunk.pop(rev).split('\n')
2207 lines = displayer.hunk.pop(rev).split('\n')
2216 if not lines[-1]:
2208 if not lines[-1]:
2217 del lines[-1]
2209 del lines[-1]
2218 displayer.flush(ctx)
2210 displayer.flush(ctx)
2219 edges = edgefn(type, char, lines, state, rev, parents)
2211 edges = edgefn(type, char, lines, state, rev, parents)
2220 for type, char, lines, coldata in edges:
2212 for type, char, lines, coldata in edges:
2221 graphmod.ascii(ui, state, type, char, lines, coldata)
2213 graphmod.ascii(ui, state, type, char, lines, coldata)
2222 displayer.close()
2214 displayer.close()
2223
2215
2224 def graphlog(ui, repo, *pats, **opts):
2216 def graphlog(ui, repo, *pats, **opts):
2225 # Parameters are identical to log command ones
2217 # Parameters are identical to log command ones
2226 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2218 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2227 revdag = graphmod.dagwalker(repo, revs)
2219 revdag = graphmod.dagwalker(repo, revs)
2228
2220
2229 getrenamed = None
2221 getrenamed = None
2230 if opts.get('copies'):
2222 if opts.get('copies'):
2231 endrev = None
2223 endrev = None
2232 if opts.get('rev'):
2224 if opts.get('rev'):
2233 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2225 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2234 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2226 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2235
2227
2236 ui.pager('log')
2228 ui.pager('log')
2237 displayer = show_changeset(ui, repo, opts, buffered=True)
2229 displayer = show_changeset(ui, repo, opts, buffered=True)
2238 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2230 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2239 filematcher)
2231 filematcher)
2240
2232
2241 def checkunsupportedgraphflags(pats, opts):
2233 def checkunsupportedgraphflags(pats, opts):
2242 for op in ["newest_first"]:
2234 for op in ["newest_first"]:
2243 if op in opts and opts[op]:
2235 if op in opts and opts[op]:
2244 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2236 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2245 % op.replace("_", "-"))
2237 % op.replace("_", "-"))
2246
2238
2247 def graphrevs(repo, nodes, opts):
2239 def graphrevs(repo, nodes, opts):
2248 limit = loglimit(opts)
2240 limit = loglimit(opts)
2249 nodes.reverse()
2241 nodes.reverse()
2250 if limit is not None:
2242 if limit is not None:
2251 nodes = nodes[:limit]
2243 nodes = nodes[:limit]
2252 return graphmod.nodes(repo, nodes)
2244 return graphmod.nodes(repo, nodes)
2253
2245
2254 def add(ui, repo, match, prefix, explicitonly, **opts):
2246 def add(ui, repo, match, prefix, explicitonly, **opts):
2255 join = lambda f: os.path.join(prefix, f)
2247 join = lambda f: os.path.join(prefix, f)
2256 bad = []
2248 bad = []
2257
2249
2258 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2250 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2259 names = []
2251 names = []
2260 wctx = repo[None]
2252 wctx = repo[None]
2261 cca = None
2253 cca = None
2262 abort, warn = scmutil.checkportabilityalert(ui)
2254 abort, warn = scmutil.checkportabilityalert(ui)
2263 if abort or warn:
2255 if abort or warn:
2264 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2256 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2265
2257
2266 badmatch = matchmod.badmatch(match, badfn)
2258 badmatch = matchmod.badmatch(match, badfn)
2267 dirstate = repo.dirstate
2259 dirstate = repo.dirstate
2268 # We don't want to just call wctx.walk here, since it would return a lot of
2260 # We don't want to just call wctx.walk here, since it would return a lot of
2269 # clean files, which we aren't interested in and takes time.
2261 # clean files, which we aren't interested in and takes time.
2270 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2262 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2271 True, False, full=False)):
2263 True, False, full=False)):
2272 exact = match.exact(f)
2264 exact = match.exact(f)
2273 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2265 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2274 if cca:
2266 if cca:
2275 cca(f)
2267 cca(f)
2276 names.append(f)
2268 names.append(f)
2277 if ui.verbose or not exact:
2269 if ui.verbose or not exact:
2278 ui.status(_('adding %s\n') % match.rel(f))
2270 ui.status(_('adding %s\n') % match.rel(f))
2279
2271
2280 for subpath in sorted(wctx.substate):
2272 for subpath in sorted(wctx.substate):
2281 sub = wctx.sub(subpath)
2273 sub = wctx.sub(subpath)
2282 try:
2274 try:
2283 submatch = matchmod.subdirmatcher(subpath, match)
2275 submatch = matchmod.subdirmatcher(subpath, match)
2284 if opts.get('subrepos'):
2276 if opts.get('subrepos'):
2285 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2277 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2286 else:
2278 else:
2287 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2279 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2288 except error.LookupError:
2280 except error.LookupError:
2289 ui.status(_("skipping missing subrepository: %s\n")
2281 ui.status(_("skipping missing subrepository: %s\n")
2290 % join(subpath))
2282 % join(subpath))
2291
2283
2292 if not opts.get('dry_run'):
2284 if not opts.get('dry_run'):
2293 rejected = wctx.add(names, prefix)
2285 rejected = wctx.add(names, prefix)
2294 bad.extend(f for f in rejected if f in match.files())
2286 bad.extend(f for f in rejected if f in match.files())
2295 return bad
2287 return bad
2296
2288
2297 def forget(ui, repo, match, prefix, explicitonly):
2289 def forget(ui, repo, match, prefix, explicitonly):
2298 join = lambda f: os.path.join(prefix, f)
2290 join = lambda f: os.path.join(prefix, f)
2299 bad = []
2291 bad = []
2300 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2292 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2301 wctx = repo[None]
2293 wctx = repo[None]
2302 forgot = []
2294 forgot = []
2303
2295
2304 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2296 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2305 forget = sorted(s[0] + s[1] + s[3] + s[6])
2297 forget = sorted(s[0] + s[1] + s[3] + s[6])
2306 if explicitonly:
2298 if explicitonly:
2307 forget = [f for f in forget if match.exact(f)]
2299 forget = [f for f in forget if match.exact(f)]
2308
2300
2309 for subpath in sorted(wctx.substate):
2301 for subpath in sorted(wctx.substate):
2310 sub = wctx.sub(subpath)
2302 sub = wctx.sub(subpath)
2311 try:
2303 try:
2312 submatch = matchmod.subdirmatcher(subpath, match)
2304 submatch = matchmod.subdirmatcher(subpath, match)
2313 subbad, subforgot = sub.forget(submatch, prefix)
2305 subbad, subforgot = sub.forget(submatch, prefix)
2314 bad.extend([subpath + '/' + f for f in subbad])
2306 bad.extend([subpath + '/' + f for f in subbad])
2315 forgot.extend([subpath + '/' + f for f in subforgot])
2307 forgot.extend([subpath + '/' + f for f in subforgot])
2316 except error.LookupError:
2308 except error.LookupError:
2317 ui.status(_("skipping missing subrepository: %s\n")
2309 ui.status(_("skipping missing subrepository: %s\n")
2318 % join(subpath))
2310 % join(subpath))
2319
2311
2320 if not explicitonly:
2312 if not explicitonly:
2321 for f in match.files():
2313 for f in match.files():
2322 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2314 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2323 if f not in forgot:
2315 if f not in forgot:
2324 if repo.wvfs.exists(f):
2316 if repo.wvfs.exists(f):
2325 # Don't complain if the exact case match wasn't given.
2317 # Don't complain if the exact case match wasn't given.
2326 # But don't do this until after checking 'forgot', so
2318 # But don't do this until after checking 'forgot', so
2327 # that subrepo files aren't normalized, and this op is
2319 # that subrepo files aren't normalized, and this op is
2328 # purely from data cached by the status walk above.
2320 # purely from data cached by the status walk above.
2329 if repo.dirstate.normalize(f) in repo.dirstate:
2321 if repo.dirstate.normalize(f) in repo.dirstate:
2330 continue
2322 continue
2331 ui.warn(_('not removing %s: '
2323 ui.warn(_('not removing %s: '
2332 'file is already untracked\n')
2324 'file is already untracked\n')
2333 % match.rel(f))
2325 % match.rel(f))
2334 bad.append(f)
2326 bad.append(f)
2335
2327
2336 for f in forget:
2328 for f in forget:
2337 if ui.verbose or not match.exact(f):
2329 if ui.verbose or not match.exact(f):
2338 ui.status(_('removing %s\n') % match.rel(f))
2330 ui.status(_('removing %s\n') % match.rel(f))
2339
2331
2340 rejected = wctx.forget(forget, prefix)
2332 rejected = wctx.forget(forget, prefix)
2341 bad.extend(f for f in rejected if f in match.files())
2333 bad.extend(f for f in rejected if f in match.files())
2342 forgot.extend(f for f in forget if f not in rejected)
2334 forgot.extend(f for f in forget if f not in rejected)
2343 return bad, forgot
2335 return bad, forgot
2344
2336
2345 def files(ui, ctx, m, fm, fmt, subrepos):
2337 def files(ui, ctx, m, fm, fmt, subrepos):
2346 rev = ctx.rev()
2338 rev = ctx.rev()
2347 ret = 1
2339 ret = 1
2348 ds = ctx.repo().dirstate
2340 ds = ctx.repo().dirstate
2349
2341
2350 for f in ctx.matches(m):
2342 for f in ctx.matches(m):
2351 if rev is None and ds[f] == 'r':
2343 if rev is None and ds[f] == 'r':
2352 continue
2344 continue
2353 fm.startitem()
2345 fm.startitem()
2354 if ui.verbose:
2346 if ui.verbose:
2355 fc = ctx[f]
2347 fc = ctx[f]
2356 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2348 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2357 fm.data(abspath=f)
2349 fm.data(abspath=f)
2358 fm.write('path', fmt, m.rel(f))
2350 fm.write('path', fmt, m.rel(f))
2359 ret = 0
2351 ret = 0
2360
2352
2361 for subpath in sorted(ctx.substate):
2353 for subpath in sorted(ctx.substate):
2362 submatch = matchmod.subdirmatcher(subpath, m)
2354 submatch = matchmod.subdirmatcher(subpath, m)
2363 if (subrepos or m.exact(subpath) or any(submatch.files())):
2355 if (subrepos or m.exact(subpath) or any(submatch.files())):
2364 sub = ctx.sub(subpath)
2356 sub = ctx.sub(subpath)
2365 try:
2357 try:
2366 recurse = m.exact(subpath) or subrepos
2358 recurse = m.exact(subpath) or subrepos
2367 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2359 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2368 ret = 0
2360 ret = 0
2369 except error.LookupError:
2361 except error.LookupError:
2370 ui.status(_("skipping missing subrepository: %s\n")
2362 ui.status(_("skipping missing subrepository: %s\n")
2371 % m.abs(subpath))
2363 % m.abs(subpath))
2372
2364
2373 return ret
2365 return ret
2374
2366
2375 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2367 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2376 join = lambda f: os.path.join(prefix, f)
2368 join = lambda f: os.path.join(prefix, f)
2377 ret = 0
2369 ret = 0
2378 s = repo.status(match=m, clean=True)
2370 s = repo.status(match=m, clean=True)
2379 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2371 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2380
2372
2381 wctx = repo[None]
2373 wctx = repo[None]
2382
2374
2383 if warnings is None:
2375 if warnings is None:
2384 warnings = []
2376 warnings = []
2385 warn = True
2377 warn = True
2386 else:
2378 else:
2387 warn = False
2379 warn = False
2388
2380
2389 subs = sorted(wctx.substate)
2381 subs = sorted(wctx.substate)
2390 total = len(subs)
2382 total = len(subs)
2391 count = 0
2383 count = 0
2392 for subpath in subs:
2384 for subpath in subs:
2393 count += 1
2385 count += 1
2394 submatch = matchmod.subdirmatcher(subpath, m)
2386 submatch = matchmod.subdirmatcher(subpath, m)
2395 if subrepos or m.exact(subpath) or any(submatch.files()):
2387 if subrepos or m.exact(subpath) or any(submatch.files()):
2396 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2388 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2397 sub = wctx.sub(subpath)
2389 sub = wctx.sub(subpath)
2398 try:
2390 try:
2399 if sub.removefiles(submatch, prefix, after, force, subrepos,
2391 if sub.removefiles(submatch, prefix, after, force, subrepos,
2400 warnings):
2392 warnings):
2401 ret = 1
2393 ret = 1
2402 except error.LookupError:
2394 except error.LookupError:
2403 warnings.append(_("skipping missing subrepository: %s\n")
2395 warnings.append(_("skipping missing subrepository: %s\n")
2404 % join(subpath))
2396 % join(subpath))
2405 ui.progress(_('searching'), None)
2397 ui.progress(_('searching'), None)
2406
2398
2407 # warn about failure to delete explicit files/dirs
2399 # warn about failure to delete explicit files/dirs
2408 deleteddirs = util.dirs(deleted)
2400 deleteddirs = util.dirs(deleted)
2409 files = m.files()
2401 files = m.files()
2410 total = len(files)
2402 total = len(files)
2411 count = 0
2403 count = 0
2412 for f in files:
2404 for f in files:
2413 def insubrepo():
2405 def insubrepo():
2414 for subpath in wctx.substate:
2406 for subpath in wctx.substate:
2415 if f.startswith(subpath + '/'):
2407 if f.startswith(subpath + '/'):
2416 return True
2408 return True
2417 return False
2409 return False
2418
2410
2419 count += 1
2411 count += 1
2420 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2412 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2421 isdir = f in deleteddirs or wctx.hasdir(f)
2413 isdir = f in deleteddirs or wctx.hasdir(f)
2422 if (f in repo.dirstate or isdir or f == '.'
2414 if (f in repo.dirstate or isdir or f == '.'
2423 or insubrepo() or f in subs):
2415 or insubrepo() or f in subs):
2424 continue
2416 continue
2425
2417
2426 if repo.wvfs.exists(f):
2418 if repo.wvfs.exists(f):
2427 if repo.wvfs.isdir(f):
2419 if repo.wvfs.isdir(f):
2428 warnings.append(_('not removing %s: no tracked files\n')
2420 warnings.append(_('not removing %s: no tracked files\n')
2429 % m.rel(f))
2421 % m.rel(f))
2430 else:
2422 else:
2431 warnings.append(_('not removing %s: file is untracked\n')
2423 warnings.append(_('not removing %s: file is untracked\n')
2432 % m.rel(f))
2424 % m.rel(f))
2433 # missing files will generate a warning elsewhere
2425 # missing files will generate a warning elsewhere
2434 ret = 1
2426 ret = 1
2435 ui.progress(_('deleting'), None)
2427 ui.progress(_('deleting'), None)
2436
2428
2437 if force:
2429 if force:
2438 list = modified + deleted + clean + added
2430 list = modified + deleted + clean + added
2439 elif after:
2431 elif after:
2440 list = deleted
2432 list = deleted
2441 remaining = modified + added + clean
2433 remaining = modified + added + clean
2442 total = len(remaining)
2434 total = len(remaining)
2443 count = 0
2435 count = 0
2444 for f in remaining:
2436 for f in remaining:
2445 count += 1
2437 count += 1
2446 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2438 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2447 warnings.append(_('not removing %s: file still exists\n')
2439 warnings.append(_('not removing %s: file still exists\n')
2448 % m.rel(f))
2440 % m.rel(f))
2449 ret = 1
2441 ret = 1
2450 ui.progress(_('skipping'), None)
2442 ui.progress(_('skipping'), None)
2451 else:
2443 else:
2452 list = deleted + clean
2444 list = deleted + clean
2453 total = len(modified) + len(added)
2445 total = len(modified) + len(added)
2454 count = 0
2446 count = 0
2455 for f in modified:
2447 for f in modified:
2456 count += 1
2448 count += 1
2457 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2449 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2458 warnings.append(_('not removing %s: file is modified (use -f'
2450 warnings.append(_('not removing %s: file is modified (use -f'
2459 ' to force removal)\n') % m.rel(f))
2451 ' to force removal)\n') % m.rel(f))
2460 ret = 1
2452 ret = 1
2461 for f in added:
2453 for f in added:
2462 count += 1
2454 count += 1
2463 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2455 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2464 warnings.append(_("not removing %s: file has been marked for add"
2456 warnings.append(_("not removing %s: file has been marked for add"
2465 " (use 'hg forget' to undo add)\n") % m.rel(f))
2457 " (use 'hg forget' to undo add)\n") % m.rel(f))
2466 ret = 1
2458 ret = 1
2467 ui.progress(_('skipping'), None)
2459 ui.progress(_('skipping'), None)
2468
2460
2469 list = sorted(list)
2461 list = sorted(list)
2470 total = len(list)
2462 total = len(list)
2471 count = 0
2463 count = 0
2472 for f in list:
2464 for f in list:
2473 count += 1
2465 count += 1
2474 if ui.verbose or not m.exact(f):
2466 if ui.verbose or not m.exact(f):
2475 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2467 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2476 ui.status(_('removing %s\n') % m.rel(f))
2468 ui.status(_('removing %s\n') % m.rel(f))
2477 ui.progress(_('deleting'), None)
2469 ui.progress(_('deleting'), None)
2478
2470
2479 with repo.wlock():
2471 with repo.wlock():
2480 if not after:
2472 if not after:
2481 for f in list:
2473 for f in list:
2482 if f in added:
2474 if f in added:
2483 continue # we never unlink added files on remove
2475 continue # we never unlink added files on remove
2484 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2476 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2485 repo[None].forget(list)
2477 repo[None].forget(list)
2486
2478
2487 if warn:
2479 if warn:
2488 for warning in warnings:
2480 for warning in warnings:
2489 ui.warn(warning)
2481 ui.warn(warning)
2490
2482
2491 return ret
2483 return ret
2492
2484
2493 def cat(ui, repo, ctx, matcher, prefix, **opts):
2485 def cat(ui, repo, ctx, matcher, prefix, **opts):
2494 err = 1
2486 err = 1
2495
2487
2496 def write(path):
2488 def write(path):
2497 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2489 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2498 pathname=os.path.join(prefix, path))
2490 pathname=os.path.join(prefix, path))
2499 data = ctx[path].data()
2491 data = ctx[path].data()
2500 if opts.get('decode'):
2492 if opts.get('decode'):
2501 data = repo.wwritedata(path, data)
2493 data = repo.wwritedata(path, data)
2502 fp.write(data)
2494 fp.write(data)
2503 fp.close()
2495 fp.close()
2504
2496
2505 # Automation often uses hg cat on single files, so special case it
2497 # Automation often uses hg cat on single files, so special case it
2506 # for performance to avoid the cost of parsing the manifest.
2498 # for performance to avoid the cost of parsing the manifest.
2507 if len(matcher.files()) == 1 and not matcher.anypats():
2499 if len(matcher.files()) == 1 and not matcher.anypats():
2508 file = matcher.files()[0]
2500 file = matcher.files()[0]
2509 mfl = repo.manifestlog
2501 mfl = repo.manifestlog
2510 mfnode = ctx.manifestnode()
2502 mfnode = ctx.manifestnode()
2511 try:
2503 try:
2512 if mfnode and mfl[mfnode].find(file)[0]:
2504 if mfnode and mfl[mfnode].find(file)[0]:
2513 write(file)
2505 write(file)
2514 return 0
2506 return 0
2515 except KeyError:
2507 except KeyError:
2516 pass
2508 pass
2517
2509
2518 for abs in ctx.walk(matcher):
2510 for abs in ctx.walk(matcher):
2519 write(abs)
2511 write(abs)
2520 err = 0
2512 err = 0
2521
2513
2522 for subpath in sorted(ctx.substate):
2514 for subpath in sorted(ctx.substate):
2523 sub = ctx.sub(subpath)
2515 sub = ctx.sub(subpath)
2524 try:
2516 try:
2525 submatch = matchmod.subdirmatcher(subpath, matcher)
2517 submatch = matchmod.subdirmatcher(subpath, matcher)
2526
2518
2527 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2519 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2528 **opts):
2520 **opts):
2529 err = 0
2521 err = 0
2530 except error.RepoLookupError:
2522 except error.RepoLookupError:
2531 ui.status(_("skipping missing subrepository: %s\n")
2523 ui.status(_("skipping missing subrepository: %s\n")
2532 % os.path.join(prefix, subpath))
2524 % os.path.join(prefix, subpath))
2533
2525
2534 return err
2526 return err
2535
2527
2536 def commit(ui, repo, commitfunc, pats, opts):
2528 def commit(ui, repo, commitfunc, pats, opts):
2537 '''commit the specified files or all outstanding changes'''
2529 '''commit the specified files or all outstanding changes'''
2538 date = opts.get('date')
2530 date = opts.get('date')
2539 if date:
2531 if date:
2540 opts['date'] = util.parsedate(date)
2532 opts['date'] = util.parsedate(date)
2541 message = logmessage(ui, opts)
2533 message = logmessage(ui, opts)
2542 matcher = scmutil.match(repo[None], pats, opts)
2534 matcher = scmutil.match(repo[None], pats, opts)
2543
2535
2544 # extract addremove carefully -- this function can be called from a command
2536 # extract addremove carefully -- this function can be called from a command
2545 # that doesn't support addremove
2537 # that doesn't support addremove
2546 if opts.get('addremove'):
2538 if opts.get('addremove'):
2547 if scmutil.addremove(repo, matcher, "", opts) != 0:
2539 if scmutil.addremove(repo, matcher, "", opts) != 0:
2548 raise error.Abort(
2540 raise error.Abort(
2549 _("failed to mark all new/missing files as added/removed"))
2541 _("failed to mark all new/missing files as added/removed"))
2550
2542
2551 return commitfunc(ui, repo, message, matcher, opts)
2543 return commitfunc(ui, repo, message, matcher, opts)
2552
2544
2553 def samefile(f, ctx1, ctx2):
2545 def samefile(f, ctx1, ctx2):
2554 if f in ctx1.manifest():
2546 if f in ctx1.manifest():
2555 a = ctx1.filectx(f)
2547 a = ctx1.filectx(f)
2556 if f in ctx2.manifest():
2548 if f in ctx2.manifest():
2557 b = ctx2.filectx(f)
2549 b = ctx2.filectx(f)
2558 return (not a.cmp(b)
2550 return (not a.cmp(b)
2559 and a.flags() == b.flags())
2551 and a.flags() == b.flags())
2560 else:
2552 else:
2561 return False
2553 return False
2562 else:
2554 else:
2563 return f not in ctx2.manifest()
2555 return f not in ctx2.manifest()
2564
2556
2565 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2557 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2566 # avoid cycle context -> subrepo -> cmdutil
2558 # avoid cycle context -> subrepo -> cmdutil
2567 from . import context
2559 from . import context
2568
2560
2569 # amend will reuse the existing user if not specified, but the obsolete
2561 # amend will reuse the existing user if not specified, but the obsolete
2570 # marker creation requires that the current user's name is specified.
2562 # marker creation requires that the current user's name is specified.
2571 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2563 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2572 ui.username() # raise exception if username not set
2564 ui.username() # raise exception if username not set
2573
2565
2574 ui.note(_('amending changeset %s\n') % old)
2566 ui.note(_('amending changeset %s\n') % old)
2575 base = old.p1()
2567 base = old.p1()
2576 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2568 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2577
2569
2578 wlock = lock = newid = None
2570 wlock = lock = newid = None
2579 try:
2571 try:
2580 wlock = repo.wlock()
2572 wlock = repo.wlock()
2581 lock = repo.lock()
2573 lock = repo.lock()
2582 with repo.transaction('amend') as tr:
2574 with repo.transaction('amend') as tr:
2583 # See if we got a message from -m or -l, if not, open the editor
2575 # See if we got a message from -m or -l, if not, open the editor
2584 # with the message of the changeset to amend
2576 # with the message of the changeset to amend
2585 message = logmessage(ui, opts)
2577 message = logmessage(ui, opts)
2586 # ensure logfile does not conflict with later enforcement of the
2578 # ensure logfile does not conflict with later enforcement of the
2587 # message. potential logfile content has been processed by
2579 # message. potential logfile content has been processed by
2588 # `logmessage` anyway.
2580 # `logmessage` anyway.
2589 opts.pop('logfile')
2581 opts.pop('logfile')
2590 # First, do a regular commit to record all changes in the working
2582 # First, do a regular commit to record all changes in the working
2591 # directory (if there are any)
2583 # directory (if there are any)
2592 ui.callhooks = False
2584 ui.callhooks = False
2593 activebookmark = repo._bookmarks.active
2585 activebookmark = repo._bookmarks.active
2594 try:
2586 try:
2595 repo._bookmarks.active = None
2587 repo._bookmarks.active = None
2596 opts['message'] = 'temporary amend commit for %s' % old
2588 opts['message'] = 'temporary amend commit for %s' % old
2597 node = commit(ui, repo, commitfunc, pats, opts)
2589 node = commit(ui, repo, commitfunc, pats, opts)
2598 finally:
2590 finally:
2599 repo._bookmarks.active = activebookmark
2591 repo._bookmarks.active = activebookmark
2600 repo._bookmarks.recordchange(tr)
2592 repo._bookmarks.recordchange(tr)
2601 ui.callhooks = True
2593 ui.callhooks = True
2602 ctx = repo[node]
2594 ctx = repo[node]
2603
2595
2604 # Participating changesets:
2596 # Participating changesets:
2605 #
2597 #
2606 # node/ctx o - new (intermediate) commit that contains changes
2598 # node/ctx o - new (intermediate) commit that contains changes
2607 # | from working dir to go into amending commit
2599 # | from working dir to go into amending commit
2608 # | (or a workingctx if there were no changes)
2600 # | (or a workingctx if there were no changes)
2609 # |
2601 # |
2610 # old o - changeset to amend
2602 # old o - changeset to amend
2611 # |
2603 # |
2612 # base o - parent of amending changeset
2604 # base o - parent of amending changeset
2613
2605
2614 # Update extra dict from amended commit (e.g. to preserve graft
2606 # Update extra dict from amended commit (e.g. to preserve graft
2615 # source)
2607 # source)
2616 extra.update(old.extra())
2608 extra.update(old.extra())
2617
2609
2618 # Also update it from the intermediate commit or from the wctx
2610 # Also update it from the intermediate commit or from the wctx
2619 extra.update(ctx.extra())
2611 extra.update(ctx.extra())
2620
2612
2621 if len(old.parents()) > 1:
2613 if len(old.parents()) > 1:
2622 # ctx.files() isn't reliable for merges, so fall back to the
2614 # ctx.files() isn't reliable for merges, so fall back to the
2623 # slower repo.status() method
2615 # slower repo.status() method
2624 files = set([fn for st in repo.status(base, old)[:3]
2616 files = set([fn for st in repo.status(base, old)[:3]
2625 for fn in st])
2617 for fn in st])
2626 else:
2618 else:
2627 files = set(old.files())
2619 files = set(old.files())
2628
2620
2629 # Second, we use either the commit we just did, or if there were no
2621 # Second, we use either the commit we just did, or if there were no
2630 # changes the parent of the working directory as the version of the
2622 # changes the parent of the working directory as the version of the
2631 # files in the final amend commit
2623 # files in the final amend commit
2632 if node:
2624 if node:
2633 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2625 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2634
2626
2635 user = ctx.user()
2627 user = ctx.user()
2636 date = ctx.date()
2628 date = ctx.date()
2637 # Recompute copies (avoid recording a -> b -> a)
2629 # Recompute copies (avoid recording a -> b -> a)
2638 copied = copies.pathcopies(base, ctx)
2630 copied = copies.pathcopies(base, ctx)
2639 if old.p2:
2631 if old.p2:
2640 copied.update(copies.pathcopies(old.p2(), ctx))
2632 copied.update(copies.pathcopies(old.p2(), ctx))
2641
2633
2642 # Prune files which were reverted by the updates: if old
2634 # Prune files which were reverted by the updates: if old
2643 # introduced file X and our intermediate commit, node,
2635 # introduced file X and our intermediate commit, node,
2644 # renamed that file, then those two files are the same and
2636 # renamed that file, then those two files are the same and
2645 # we can discard X from our list of files. Likewise if X
2637 # we can discard X from our list of files. Likewise if X
2646 # was deleted, it's no longer relevant
2638 # was deleted, it's no longer relevant
2647 files.update(ctx.files())
2639 files.update(ctx.files())
2648 files = [f for f in files if not samefile(f, ctx, base)]
2640 files = [f for f in files if not samefile(f, ctx, base)]
2649
2641
2650 def filectxfn(repo, ctx_, path):
2642 def filectxfn(repo, ctx_, path):
2651 try:
2643 try:
2652 fctx = ctx[path]
2644 fctx = ctx[path]
2653 flags = fctx.flags()
2645 flags = fctx.flags()
2654 mctx = context.memfilectx(repo,
2646 mctx = context.memfilectx(repo,
2655 fctx.path(), fctx.data(),
2647 fctx.path(), fctx.data(),
2656 islink='l' in flags,
2648 islink='l' in flags,
2657 isexec='x' in flags,
2649 isexec='x' in flags,
2658 copied=copied.get(path))
2650 copied=copied.get(path))
2659 return mctx
2651 return mctx
2660 except KeyError:
2652 except KeyError:
2661 return None
2653 return None
2662 else:
2654 else:
2663 ui.note(_('copying changeset %s to %s\n') % (old, base))
2655 ui.note(_('copying changeset %s to %s\n') % (old, base))
2664
2656
2665 # Use version of files as in the old cset
2657 # Use version of files as in the old cset
2666 def filectxfn(repo, ctx_, path):
2658 def filectxfn(repo, ctx_, path):
2667 try:
2659 try:
2668 return old.filectx(path)
2660 return old.filectx(path)
2669 except KeyError:
2661 except KeyError:
2670 return None
2662 return None
2671
2663
2672 user = opts.get('user') or old.user()
2664 user = opts.get('user') or old.user()
2673 date = opts.get('date') or old.date()
2665 date = opts.get('date') or old.date()
2674 editform = mergeeditform(old, 'commit.amend')
2666 editform = mergeeditform(old, 'commit.amend')
2675 editor = getcommiteditor(editform=editform, **opts)
2667 editor = getcommiteditor(editform=editform, **opts)
2676 if not message:
2668 if not message:
2677 editor = getcommiteditor(edit=True, editform=editform)
2669 editor = getcommiteditor(edit=True, editform=editform)
2678 message = old.description()
2670 message = old.description()
2679
2671
2680 pureextra = extra.copy()
2672 pureextra = extra.copy()
2681 extra['amend_source'] = old.hex()
2673 extra['amend_source'] = old.hex()
2682
2674
2683 new = context.memctx(repo,
2675 new = context.memctx(repo,
2684 parents=[base.node(), old.p2().node()],
2676 parents=[base.node(), old.p2().node()],
2685 text=message,
2677 text=message,
2686 files=files,
2678 files=files,
2687 filectxfn=filectxfn,
2679 filectxfn=filectxfn,
2688 user=user,
2680 user=user,
2689 date=date,
2681 date=date,
2690 extra=extra,
2682 extra=extra,
2691 editor=editor)
2683 editor=editor)
2692
2684
2693 newdesc = changelog.stripdesc(new.description())
2685 newdesc = changelog.stripdesc(new.description())
2694 if ((not node)
2686 if ((not node)
2695 and newdesc == old.description()
2687 and newdesc == old.description()
2696 and user == old.user()
2688 and user == old.user()
2697 and date == old.date()
2689 and date == old.date()
2698 and pureextra == old.extra()):
2690 and pureextra == old.extra()):
2699 # nothing changed. continuing here would create a new node
2691 # nothing changed. continuing here would create a new node
2700 # anyway because of the amend_source noise.
2692 # anyway because of the amend_source noise.
2701 #
2693 #
2702 # This not what we expect from amend.
2694 # This not what we expect from amend.
2703 return old.node()
2695 return old.node()
2704
2696
2705 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2697 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2706 try:
2698 try:
2707 if opts.get('secret'):
2699 if opts.get('secret'):
2708 commitphase = 'secret'
2700 commitphase = 'secret'
2709 else:
2701 else:
2710 commitphase = old.phase()
2702 commitphase = old.phase()
2711 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2703 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2712 newid = repo.commitctx(new)
2704 newid = repo.commitctx(new)
2713 finally:
2705 finally:
2714 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2706 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2715 if newid != old.node():
2707 if newid != old.node():
2716 # Reroute the working copy parent to the new changeset
2708 # Reroute the working copy parent to the new changeset
2717 repo.setparents(newid, nullid)
2709 repo.setparents(newid, nullid)
2718
2710
2719 # Move bookmarks from old parent to amend commit
2711 # Move bookmarks from old parent to amend commit
2720 bms = repo.nodebookmarks(old.node())
2712 bms = repo.nodebookmarks(old.node())
2721 if bms:
2713 if bms:
2722 marks = repo._bookmarks
2714 marks = repo._bookmarks
2723 for bm in bms:
2715 for bm in bms:
2724 ui.debug('moving bookmarks %r from %s to %s\n' %
2716 ui.debug('moving bookmarks %r from %s to %s\n' %
2725 (marks, old.hex(), hex(newid)))
2717 (marks, old.hex(), hex(newid)))
2726 marks[bm] = newid
2718 marks[bm] = newid
2727 marks.recordchange(tr)
2719 marks.recordchange(tr)
2728 #commit the whole amend process
2720 #commit the whole amend process
2729 if createmarkers:
2721 if createmarkers:
2730 # mark the new changeset as successor of the rewritten one
2722 # mark the new changeset as successor of the rewritten one
2731 new = repo[newid]
2723 new = repo[newid]
2732 obs = [(old, (new,))]
2724 obs = [(old, (new,))]
2733 if node:
2725 if node:
2734 obs.append((ctx, ()))
2726 obs.append((ctx, ()))
2735
2727
2736 obsolete.createmarkers(repo, obs)
2728 obsolete.createmarkers(repo, obs)
2737 if not createmarkers and newid != old.node():
2729 if not createmarkers and newid != old.node():
2738 # Strip the intermediate commit (if there was one) and the amended
2730 # Strip the intermediate commit (if there was one) and the amended
2739 # commit
2731 # commit
2740 if node:
2732 if node:
2741 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2733 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2742 ui.note(_('stripping amended changeset %s\n') % old)
2734 ui.note(_('stripping amended changeset %s\n') % old)
2743 repair.strip(ui, repo, old.node(), topic='amend-backup')
2735 repair.strip(ui, repo, old.node(), topic='amend-backup')
2744 finally:
2736 finally:
2745 lockmod.release(lock, wlock)
2737 lockmod.release(lock, wlock)
2746 return newid
2738 return newid
2747
2739
2748 def commiteditor(repo, ctx, subs, editform=''):
2740 def commiteditor(repo, ctx, subs, editform=''):
2749 if ctx.description():
2741 if ctx.description():
2750 return ctx.description()
2742 return ctx.description()
2751 return commitforceeditor(repo, ctx, subs, editform=editform,
2743 return commitforceeditor(repo, ctx, subs, editform=editform,
2752 unchangedmessagedetection=True)
2744 unchangedmessagedetection=True)
2753
2745
2754 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2746 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2755 editform='', unchangedmessagedetection=False):
2747 editform='', unchangedmessagedetection=False):
2756 if not extramsg:
2748 if not extramsg:
2757 extramsg = _("Leave message empty to abort commit.")
2749 extramsg = _("Leave message empty to abort commit.")
2758
2750
2759 forms = [e for e in editform.split('.') if e]
2751 forms = [e for e in editform.split('.') if e]
2760 forms.insert(0, 'changeset')
2752 forms.insert(0, 'changeset')
2761 templatetext = None
2753 templatetext = None
2762 while forms:
2754 while forms:
2763 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2755 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2764 if tmpl:
2756 if tmpl:
2765 templatetext = committext = buildcommittemplate(
2757 templatetext = committext = buildcommittemplate(
2766 repo, ctx, subs, extramsg, tmpl)
2758 repo, ctx, subs, extramsg, tmpl)
2767 break
2759 break
2768 forms.pop()
2760 forms.pop()
2769 else:
2761 else:
2770 committext = buildcommittext(repo, ctx, subs, extramsg)
2762 committext = buildcommittext(repo, ctx, subs, extramsg)
2771
2763
2772 # run editor in the repository root
2764 # run editor in the repository root
2773 olddir = pycompat.getcwd()
2765 olddir = pycompat.getcwd()
2774 os.chdir(repo.root)
2766 os.chdir(repo.root)
2775
2767
2776 # make in-memory changes visible to external process
2768 # make in-memory changes visible to external process
2777 tr = repo.currenttransaction()
2769 tr = repo.currenttransaction()
2778 repo.dirstate.write(tr)
2770 repo.dirstate.write(tr)
2779 pending = tr and tr.writepending() and repo.root
2771 pending = tr and tr.writepending() and repo.root
2780
2772
2781 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2773 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2782 editform=editform, pending=pending,
2774 editform=editform, pending=pending,
2783 repopath=repo.path)
2775 repopath=repo.path)
2784 text = editortext
2776 text = editortext
2785
2777
2786 # strip away anything below this special string (used for editors that want
2778 # strip away anything below this special string (used for editors that want
2787 # to display the diff)
2779 # to display the diff)
2788 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2780 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2789 if stripbelow:
2781 if stripbelow:
2790 text = text[:stripbelow.start()]
2782 text = text[:stripbelow.start()]
2791
2783
2792 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2784 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2793 os.chdir(olddir)
2785 os.chdir(olddir)
2794
2786
2795 if finishdesc:
2787 if finishdesc:
2796 text = finishdesc(text)
2788 text = finishdesc(text)
2797 if not text.strip():
2789 if not text.strip():
2798 raise error.Abort(_("empty commit message"))
2790 raise error.Abort(_("empty commit message"))
2799 if unchangedmessagedetection and editortext == templatetext:
2791 if unchangedmessagedetection and editortext == templatetext:
2800 raise error.Abort(_("commit message unchanged"))
2792 raise error.Abort(_("commit message unchanged"))
2801
2793
2802 return text
2794 return text
2803
2795
2804 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2796 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2805 ui = repo.ui
2797 ui = repo.ui
2806 tmpl, mapfile = gettemplate(ui, tmpl, None)
2798 tmpl, mapfile = gettemplate(ui, tmpl, None)
2807
2799
2808 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2800 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2809
2801
2810 for k, v in repo.ui.configitems('committemplate'):
2802 for k, v in repo.ui.configitems('committemplate'):
2811 if k != 'changeset':
2803 if k != 'changeset':
2812 t.t.cache[k] = v
2804 t.t.cache[k] = v
2813
2805
2814 if not extramsg:
2806 if not extramsg:
2815 extramsg = '' # ensure that extramsg is string
2807 extramsg = '' # ensure that extramsg is string
2816
2808
2817 ui.pushbuffer()
2809 ui.pushbuffer()
2818 t.show(ctx, extramsg=extramsg)
2810 t.show(ctx, extramsg=extramsg)
2819 return ui.popbuffer()
2811 return ui.popbuffer()
2820
2812
2821 def hgprefix(msg):
2813 def hgprefix(msg):
2822 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2814 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2823
2815
2824 def buildcommittext(repo, ctx, subs, extramsg):
2816 def buildcommittext(repo, ctx, subs, extramsg):
2825 edittext = []
2817 edittext = []
2826 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2818 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2827 if ctx.description():
2819 if ctx.description():
2828 edittext.append(ctx.description())
2820 edittext.append(ctx.description())
2829 edittext.append("")
2821 edittext.append("")
2830 edittext.append("") # Empty line between message and comments.
2822 edittext.append("") # Empty line between message and comments.
2831 edittext.append(hgprefix(_("Enter commit message."
2823 edittext.append(hgprefix(_("Enter commit message."
2832 " Lines beginning with 'HG:' are removed.")))
2824 " Lines beginning with 'HG:' are removed.")))
2833 edittext.append(hgprefix(extramsg))
2825 edittext.append(hgprefix(extramsg))
2834 edittext.append("HG: --")
2826 edittext.append("HG: --")
2835 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2827 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2836 if ctx.p2():
2828 if ctx.p2():
2837 edittext.append(hgprefix(_("branch merge")))
2829 edittext.append(hgprefix(_("branch merge")))
2838 if ctx.branch():
2830 if ctx.branch():
2839 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2831 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2840 if bookmarks.isactivewdirparent(repo):
2832 if bookmarks.isactivewdirparent(repo):
2841 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2833 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2842 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2834 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2843 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2835 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2844 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2836 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2845 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2837 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2846 if not added and not modified and not removed:
2838 if not added and not modified and not removed:
2847 edittext.append(hgprefix(_("no files changed")))
2839 edittext.append(hgprefix(_("no files changed")))
2848 edittext.append("")
2840 edittext.append("")
2849
2841
2850 return "\n".join(edittext)
2842 return "\n".join(edittext)
2851
2843
2852 def commitstatus(repo, node, branch, bheads=None, opts=None):
2844 def commitstatus(repo, node, branch, bheads=None, opts=None):
2853 if opts is None:
2845 if opts is None:
2854 opts = {}
2846 opts = {}
2855 ctx = repo[node]
2847 ctx = repo[node]
2856 parents = ctx.parents()
2848 parents = ctx.parents()
2857
2849
2858 if (not opts.get('amend') and bheads and node not in bheads and not
2850 if (not opts.get('amend') and bheads and node not in bheads and not
2859 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2851 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2860 repo.ui.status(_('created new head\n'))
2852 repo.ui.status(_('created new head\n'))
2861 # The message is not printed for initial roots. For the other
2853 # The message is not printed for initial roots. For the other
2862 # changesets, it is printed in the following situations:
2854 # changesets, it is printed in the following situations:
2863 #
2855 #
2864 # Par column: for the 2 parents with ...
2856 # Par column: for the 2 parents with ...
2865 # N: null or no parent
2857 # N: null or no parent
2866 # B: parent is on another named branch
2858 # B: parent is on another named branch
2867 # C: parent is a regular non head changeset
2859 # C: parent is a regular non head changeset
2868 # H: parent was a branch head of the current branch
2860 # H: parent was a branch head of the current branch
2869 # Msg column: whether we print "created new head" message
2861 # Msg column: whether we print "created new head" message
2870 # In the following, it is assumed that there already exists some
2862 # In the following, it is assumed that there already exists some
2871 # initial branch heads of the current branch, otherwise nothing is
2863 # initial branch heads of the current branch, otherwise nothing is
2872 # printed anyway.
2864 # printed anyway.
2873 #
2865 #
2874 # Par Msg Comment
2866 # Par Msg Comment
2875 # N N y additional topo root
2867 # N N y additional topo root
2876 #
2868 #
2877 # B N y additional branch root
2869 # B N y additional branch root
2878 # C N y additional topo head
2870 # C N y additional topo head
2879 # H N n usual case
2871 # H N n usual case
2880 #
2872 #
2881 # B B y weird additional branch root
2873 # B B y weird additional branch root
2882 # C B y branch merge
2874 # C B y branch merge
2883 # H B n merge with named branch
2875 # H B n merge with named branch
2884 #
2876 #
2885 # C C y additional head from merge
2877 # C C y additional head from merge
2886 # C H n merge with a head
2878 # C H n merge with a head
2887 #
2879 #
2888 # H H n head merge: head count decreases
2880 # H H n head merge: head count decreases
2889
2881
2890 if not opts.get('close_branch'):
2882 if not opts.get('close_branch'):
2891 for r in parents:
2883 for r in parents:
2892 if r.closesbranch() and r.branch() == branch:
2884 if r.closesbranch() and r.branch() == branch:
2893 repo.ui.status(_('reopening closed branch head %d\n') % r)
2885 repo.ui.status(_('reopening closed branch head %d\n') % r)
2894
2886
2895 if repo.ui.debugflag:
2887 if repo.ui.debugflag:
2896 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2888 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2897 elif repo.ui.verbose:
2889 elif repo.ui.verbose:
2898 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2890 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2899
2891
2900 def postcommitstatus(repo, pats, opts):
2892 def postcommitstatus(repo, pats, opts):
2901 return repo.status(match=scmutil.match(repo[None], pats, opts))
2893 return repo.status(match=scmutil.match(repo[None], pats, opts))
2902
2894
2903 def revert(ui, repo, ctx, parents, *pats, **opts):
2895 def revert(ui, repo, ctx, parents, *pats, **opts):
2904 parent, p2 = parents
2896 parent, p2 = parents
2905 node = ctx.node()
2897 node = ctx.node()
2906
2898
2907 mf = ctx.manifest()
2899 mf = ctx.manifest()
2908 if node == p2:
2900 if node == p2:
2909 parent = p2
2901 parent = p2
2910
2902
2911 # need all matching names in dirstate and manifest of target rev,
2903 # need all matching names in dirstate and manifest of target rev,
2912 # so have to walk both. do not print errors if files exist in one
2904 # so have to walk both. do not print errors if files exist in one
2913 # but not other. in both cases, filesets should be evaluated against
2905 # but not other. in both cases, filesets should be evaluated against
2914 # workingctx to get consistent result (issue4497). this means 'set:**'
2906 # workingctx to get consistent result (issue4497). this means 'set:**'
2915 # cannot be used to select missing files from target rev.
2907 # cannot be used to select missing files from target rev.
2916
2908
2917 # `names` is a mapping for all elements in working copy and target revision
2909 # `names` is a mapping for all elements in working copy and target revision
2918 # The mapping is in the form:
2910 # The mapping is in the form:
2919 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2911 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2920 names = {}
2912 names = {}
2921
2913
2922 with repo.wlock():
2914 with repo.wlock():
2923 ## filling of the `names` mapping
2915 ## filling of the `names` mapping
2924 # walk dirstate to fill `names`
2916 # walk dirstate to fill `names`
2925
2917
2926 interactive = opts.get('interactive', False)
2918 interactive = opts.get('interactive', False)
2927 wctx = repo[None]
2919 wctx = repo[None]
2928 m = scmutil.match(wctx, pats, opts)
2920 m = scmutil.match(wctx, pats, opts)
2929
2921
2930 # we'll need this later
2922 # we'll need this later
2931 targetsubs = sorted(s for s in wctx.substate if m(s))
2923 targetsubs = sorted(s for s in wctx.substate if m(s))
2932
2924
2933 if not m.always():
2925 if not m.always():
2934 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2926 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2935 names[abs] = m.rel(abs), m.exact(abs)
2927 names[abs] = m.rel(abs), m.exact(abs)
2936
2928
2937 # walk target manifest to fill `names`
2929 # walk target manifest to fill `names`
2938
2930
2939 def badfn(path, msg):
2931 def badfn(path, msg):
2940 if path in names:
2932 if path in names:
2941 return
2933 return
2942 if path in ctx.substate:
2934 if path in ctx.substate:
2943 return
2935 return
2944 path_ = path + '/'
2936 path_ = path + '/'
2945 for f in names:
2937 for f in names:
2946 if f.startswith(path_):
2938 if f.startswith(path_):
2947 return
2939 return
2948 ui.warn("%s: %s\n" % (m.rel(path), msg))
2940 ui.warn("%s: %s\n" % (m.rel(path), msg))
2949
2941
2950 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2942 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2951 if abs not in names:
2943 if abs not in names:
2952 names[abs] = m.rel(abs), m.exact(abs)
2944 names[abs] = m.rel(abs), m.exact(abs)
2953
2945
2954 # Find status of all file in `names`.
2946 # Find status of all file in `names`.
2955 m = scmutil.matchfiles(repo, names)
2947 m = scmutil.matchfiles(repo, names)
2956
2948
2957 changes = repo.status(node1=node, match=m,
2949 changes = repo.status(node1=node, match=m,
2958 unknown=True, ignored=True, clean=True)
2950 unknown=True, ignored=True, clean=True)
2959 else:
2951 else:
2960 changes = repo.status(node1=node, match=m)
2952 changes = repo.status(node1=node, match=m)
2961 for kind in changes:
2953 for kind in changes:
2962 for abs in kind:
2954 for abs in kind:
2963 names[abs] = m.rel(abs), m.exact(abs)
2955 names[abs] = m.rel(abs), m.exact(abs)
2964
2956
2965 m = scmutil.matchfiles(repo, names)
2957 m = scmutil.matchfiles(repo, names)
2966
2958
2967 modified = set(changes.modified)
2959 modified = set(changes.modified)
2968 added = set(changes.added)
2960 added = set(changes.added)
2969 removed = set(changes.removed)
2961 removed = set(changes.removed)
2970 _deleted = set(changes.deleted)
2962 _deleted = set(changes.deleted)
2971 unknown = set(changes.unknown)
2963 unknown = set(changes.unknown)
2972 unknown.update(changes.ignored)
2964 unknown.update(changes.ignored)
2973 clean = set(changes.clean)
2965 clean = set(changes.clean)
2974 modadded = set()
2966 modadded = set()
2975
2967
2976 # We need to account for the state of the file in the dirstate,
2968 # We need to account for the state of the file in the dirstate,
2977 # even when we revert against something else than parent. This will
2969 # even when we revert against something else than parent. This will
2978 # slightly alter the behavior of revert (doing back up or not, delete
2970 # slightly alter the behavior of revert (doing back up or not, delete
2979 # or just forget etc).
2971 # or just forget etc).
2980 if parent == node:
2972 if parent == node:
2981 dsmodified = modified
2973 dsmodified = modified
2982 dsadded = added
2974 dsadded = added
2983 dsremoved = removed
2975 dsremoved = removed
2984 # store all local modifications, useful later for rename detection
2976 # store all local modifications, useful later for rename detection
2985 localchanges = dsmodified | dsadded
2977 localchanges = dsmodified | dsadded
2986 modified, added, removed = set(), set(), set()
2978 modified, added, removed = set(), set(), set()
2987 else:
2979 else:
2988 changes = repo.status(node1=parent, match=m)
2980 changes = repo.status(node1=parent, match=m)
2989 dsmodified = set(changes.modified)
2981 dsmodified = set(changes.modified)
2990 dsadded = set(changes.added)
2982 dsadded = set(changes.added)
2991 dsremoved = set(changes.removed)
2983 dsremoved = set(changes.removed)
2992 # store all local modifications, useful later for rename detection
2984 # store all local modifications, useful later for rename detection
2993 localchanges = dsmodified | dsadded
2985 localchanges = dsmodified | dsadded
2994
2986
2995 # only take into account for removes between wc and target
2987 # only take into account for removes between wc and target
2996 clean |= dsremoved - removed
2988 clean |= dsremoved - removed
2997 dsremoved &= removed
2989 dsremoved &= removed
2998 # distinct between dirstate remove and other
2990 # distinct between dirstate remove and other
2999 removed -= dsremoved
2991 removed -= dsremoved
3000
2992
3001 modadded = added & dsmodified
2993 modadded = added & dsmodified
3002 added -= modadded
2994 added -= modadded
3003
2995
3004 # tell newly modified apart.
2996 # tell newly modified apart.
3005 dsmodified &= modified
2997 dsmodified &= modified
3006 dsmodified |= modified & dsadded # dirstate added may need backup
2998 dsmodified |= modified & dsadded # dirstate added may need backup
3007 modified -= dsmodified
2999 modified -= dsmodified
3008
3000
3009 # We need to wait for some post-processing to update this set
3001 # We need to wait for some post-processing to update this set
3010 # before making the distinction. The dirstate will be used for
3002 # before making the distinction. The dirstate will be used for
3011 # that purpose.
3003 # that purpose.
3012 dsadded = added
3004 dsadded = added
3013
3005
3014 # in case of merge, files that are actually added can be reported as
3006 # in case of merge, files that are actually added can be reported as
3015 # modified, we need to post process the result
3007 # modified, we need to post process the result
3016 if p2 != nullid:
3008 if p2 != nullid:
3017 mergeadd = set(dsmodified)
3009 mergeadd = set(dsmodified)
3018 for path in dsmodified:
3010 for path in dsmodified:
3019 if path in mf:
3011 if path in mf:
3020 mergeadd.remove(path)
3012 mergeadd.remove(path)
3021 dsadded |= mergeadd
3013 dsadded |= mergeadd
3022 dsmodified -= mergeadd
3014 dsmodified -= mergeadd
3023
3015
3024 # if f is a rename, update `names` to also revert the source
3016 # if f is a rename, update `names` to also revert the source
3025 cwd = repo.getcwd()
3017 cwd = repo.getcwd()
3026 for f in localchanges:
3018 for f in localchanges:
3027 src = repo.dirstate.copied(f)
3019 src = repo.dirstate.copied(f)
3028 # XXX should we check for rename down to target node?
3020 # XXX should we check for rename down to target node?
3029 if src and src not in names and repo.dirstate[src] == 'r':
3021 if src and src not in names and repo.dirstate[src] == 'r':
3030 dsremoved.add(src)
3022 dsremoved.add(src)
3031 names[src] = (repo.pathto(src, cwd), True)
3023 names[src] = (repo.pathto(src, cwd), True)
3032
3024
3033 # determine the exact nature of the deleted changesets
3025 # determine the exact nature of the deleted changesets
3034 deladded = set(_deleted)
3026 deladded = set(_deleted)
3035 for path in _deleted:
3027 for path in _deleted:
3036 if path in mf:
3028 if path in mf:
3037 deladded.remove(path)
3029 deladded.remove(path)
3038 deleted = _deleted - deladded
3030 deleted = _deleted - deladded
3039
3031
3040 # distinguish between file to forget and the other
3032 # distinguish between file to forget and the other
3041 added = set()
3033 added = set()
3042 for abs in dsadded:
3034 for abs in dsadded:
3043 if repo.dirstate[abs] != 'a':
3035 if repo.dirstate[abs] != 'a':
3044 added.add(abs)
3036 added.add(abs)
3045 dsadded -= added
3037 dsadded -= added
3046
3038
3047 for abs in deladded:
3039 for abs in deladded:
3048 if repo.dirstate[abs] == 'a':
3040 if repo.dirstate[abs] == 'a':
3049 dsadded.add(abs)
3041 dsadded.add(abs)
3050 deladded -= dsadded
3042 deladded -= dsadded
3051
3043
3052 # For files marked as removed, we check if an unknown file is present at
3044 # For files marked as removed, we check if an unknown file is present at
3053 # the same path. If a such file exists it may need to be backed up.
3045 # the same path. If a such file exists it may need to be backed up.
3054 # Making the distinction at this stage helps have simpler backup
3046 # Making the distinction at this stage helps have simpler backup
3055 # logic.
3047 # logic.
3056 removunk = set()
3048 removunk = set()
3057 for abs in removed:
3049 for abs in removed:
3058 target = repo.wjoin(abs)
3050 target = repo.wjoin(abs)
3059 if os.path.lexists(target):
3051 if os.path.lexists(target):
3060 removunk.add(abs)
3052 removunk.add(abs)
3061 removed -= removunk
3053 removed -= removunk
3062
3054
3063 dsremovunk = set()
3055 dsremovunk = set()
3064 for abs in dsremoved:
3056 for abs in dsremoved:
3065 target = repo.wjoin(abs)
3057 target = repo.wjoin(abs)
3066 if os.path.lexists(target):
3058 if os.path.lexists(target):
3067 dsremovunk.add(abs)
3059 dsremovunk.add(abs)
3068 dsremoved -= dsremovunk
3060 dsremoved -= dsremovunk
3069
3061
3070 # action to be actually performed by revert
3062 # action to be actually performed by revert
3071 # (<list of file>, message>) tuple
3063 # (<list of file>, message>) tuple
3072 actions = {'revert': ([], _('reverting %s\n')),
3064 actions = {'revert': ([], _('reverting %s\n')),
3073 'add': ([], _('adding %s\n')),
3065 'add': ([], _('adding %s\n')),
3074 'remove': ([], _('removing %s\n')),
3066 'remove': ([], _('removing %s\n')),
3075 'drop': ([], _('removing %s\n')),
3067 'drop': ([], _('removing %s\n')),
3076 'forget': ([], _('forgetting %s\n')),
3068 'forget': ([], _('forgetting %s\n')),
3077 'undelete': ([], _('undeleting %s\n')),
3069 'undelete': ([], _('undeleting %s\n')),
3078 'noop': (None, _('no changes needed to %s\n')),
3070 'noop': (None, _('no changes needed to %s\n')),
3079 'unknown': (None, _('file not managed: %s\n')),
3071 'unknown': (None, _('file not managed: %s\n')),
3080 }
3072 }
3081
3073
3082 # "constant" that convey the backup strategy.
3074 # "constant" that convey the backup strategy.
3083 # All set to `discard` if `no-backup` is set do avoid checking
3075 # All set to `discard` if `no-backup` is set do avoid checking
3084 # no_backup lower in the code.
3076 # no_backup lower in the code.
3085 # These values are ordered for comparison purposes
3077 # These values are ordered for comparison purposes
3086 backupinteractive = 3 # do backup if interactively modified
3078 backupinteractive = 3 # do backup if interactively modified
3087 backup = 2 # unconditionally do backup
3079 backup = 2 # unconditionally do backup
3088 check = 1 # check if the existing file differs from target
3080 check = 1 # check if the existing file differs from target
3089 discard = 0 # never do backup
3081 discard = 0 # never do backup
3090 if opts.get('no_backup'):
3082 if opts.get('no_backup'):
3091 backupinteractive = backup = check = discard
3083 backupinteractive = backup = check = discard
3092 if interactive:
3084 if interactive:
3093 dsmodifiedbackup = backupinteractive
3085 dsmodifiedbackup = backupinteractive
3094 else:
3086 else:
3095 dsmodifiedbackup = backup
3087 dsmodifiedbackup = backup
3096 tobackup = set()
3088 tobackup = set()
3097
3089
3098 backupanddel = actions['remove']
3090 backupanddel = actions['remove']
3099 if not opts.get('no_backup'):
3091 if not opts.get('no_backup'):
3100 backupanddel = actions['drop']
3092 backupanddel = actions['drop']
3101
3093
3102 disptable = (
3094 disptable = (
3103 # dispatch table:
3095 # dispatch table:
3104 # file state
3096 # file state
3105 # action
3097 # action
3106 # make backup
3098 # make backup
3107
3099
3108 ## Sets that results that will change file on disk
3100 ## Sets that results that will change file on disk
3109 # Modified compared to target, no local change
3101 # Modified compared to target, no local change
3110 (modified, actions['revert'], discard),
3102 (modified, actions['revert'], discard),
3111 # Modified compared to target, but local file is deleted
3103 # Modified compared to target, but local file is deleted
3112 (deleted, actions['revert'], discard),
3104 (deleted, actions['revert'], discard),
3113 # Modified compared to target, local change
3105 # Modified compared to target, local change
3114 (dsmodified, actions['revert'], dsmodifiedbackup),
3106 (dsmodified, actions['revert'], dsmodifiedbackup),
3115 # Added since target
3107 # Added since target
3116 (added, actions['remove'], discard),
3108 (added, actions['remove'], discard),
3117 # Added in working directory
3109 # Added in working directory
3118 (dsadded, actions['forget'], discard),
3110 (dsadded, actions['forget'], discard),
3119 # Added since target, have local modification
3111 # Added since target, have local modification
3120 (modadded, backupanddel, backup),
3112 (modadded, backupanddel, backup),
3121 # Added since target but file is missing in working directory
3113 # Added since target but file is missing in working directory
3122 (deladded, actions['drop'], discard),
3114 (deladded, actions['drop'], discard),
3123 # Removed since target, before working copy parent
3115 # Removed since target, before working copy parent
3124 (removed, actions['add'], discard),
3116 (removed, actions['add'], discard),
3125 # Same as `removed` but an unknown file exists at the same path
3117 # Same as `removed` but an unknown file exists at the same path
3126 (removunk, actions['add'], check),
3118 (removunk, actions['add'], check),
3127 # Removed since targe, marked as such in working copy parent
3119 # Removed since targe, marked as such in working copy parent
3128 (dsremoved, actions['undelete'], discard),
3120 (dsremoved, actions['undelete'], discard),
3129 # Same as `dsremoved` but an unknown file exists at the same path
3121 # Same as `dsremoved` but an unknown file exists at the same path
3130 (dsremovunk, actions['undelete'], check),
3122 (dsremovunk, actions['undelete'], check),
3131 ## the following sets does not result in any file changes
3123 ## the following sets does not result in any file changes
3132 # File with no modification
3124 # File with no modification
3133 (clean, actions['noop'], discard),
3125 (clean, actions['noop'], discard),
3134 # Existing file, not tracked anywhere
3126 # Existing file, not tracked anywhere
3135 (unknown, actions['unknown'], discard),
3127 (unknown, actions['unknown'], discard),
3136 )
3128 )
3137
3129
3138 for abs, (rel, exact) in sorted(names.items()):
3130 for abs, (rel, exact) in sorted(names.items()):
3139 # target file to be touch on disk (relative to cwd)
3131 # target file to be touch on disk (relative to cwd)
3140 target = repo.wjoin(abs)
3132 target = repo.wjoin(abs)
3141 # search the entry in the dispatch table.
3133 # search the entry in the dispatch table.
3142 # if the file is in any of these sets, it was touched in the working
3134 # if the file is in any of these sets, it was touched in the working
3143 # directory parent and we are sure it needs to be reverted.
3135 # directory parent and we are sure it needs to be reverted.
3144 for table, (xlist, msg), dobackup in disptable:
3136 for table, (xlist, msg), dobackup in disptable:
3145 if abs not in table:
3137 if abs not in table:
3146 continue
3138 continue
3147 if xlist is not None:
3139 if xlist is not None:
3148 xlist.append(abs)
3140 xlist.append(abs)
3149 if dobackup:
3141 if dobackup:
3150 # If in interactive mode, don't automatically create
3142 # If in interactive mode, don't automatically create
3151 # .orig files (issue4793)
3143 # .orig files (issue4793)
3152 if dobackup == backupinteractive:
3144 if dobackup == backupinteractive:
3153 tobackup.add(abs)
3145 tobackup.add(abs)
3154 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3146 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3155 bakname = scmutil.origpath(ui, repo, rel)
3147 bakname = scmutil.origpath(ui, repo, rel)
3156 ui.note(_('saving current version of %s as %s\n') %
3148 ui.note(_('saving current version of %s as %s\n') %
3157 (rel, bakname))
3149 (rel, bakname))
3158 if not opts.get('dry_run'):
3150 if not opts.get('dry_run'):
3159 if interactive:
3151 if interactive:
3160 util.copyfile(target, bakname)
3152 util.copyfile(target, bakname)
3161 else:
3153 else:
3162 util.rename(target, bakname)
3154 util.rename(target, bakname)
3163 if ui.verbose or not exact:
3155 if ui.verbose or not exact:
3164 if not isinstance(msg, basestring):
3156 if not isinstance(msg, basestring):
3165 msg = msg(abs)
3157 msg = msg(abs)
3166 ui.status(msg % rel)
3158 ui.status(msg % rel)
3167 elif exact:
3159 elif exact:
3168 ui.warn(msg % rel)
3160 ui.warn(msg % rel)
3169 break
3161 break
3170
3162
3171 if not opts.get('dry_run'):
3163 if not opts.get('dry_run'):
3172 needdata = ('revert', 'add', 'undelete')
3164 needdata = ('revert', 'add', 'undelete')
3173 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3165 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3174 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3166 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3175
3167
3176 if targetsubs:
3168 if targetsubs:
3177 # Revert the subrepos on the revert list
3169 # Revert the subrepos on the revert list
3178 for sub in targetsubs:
3170 for sub in targetsubs:
3179 try:
3171 try:
3180 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3172 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3181 except KeyError:
3173 except KeyError:
3182 raise error.Abort("subrepository '%s' does not exist in %s!"
3174 raise error.Abort("subrepository '%s' does not exist in %s!"
3183 % (sub, short(ctx.node())))
3175 % (sub, short(ctx.node())))
3184
3176
3185 def _revertprefetch(repo, ctx, *files):
3177 def _revertprefetch(repo, ctx, *files):
3186 """Let extension changing the storage layer prefetch content"""
3178 """Let extension changing the storage layer prefetch content"""
3187 pass
3179 pass
3188
3180
3189 def _performrevert(repo, parents, ctx, actions, interactive=False,
3181 def _performrevert(repo, parents, ctx, actions, interactive=False,
3190 tobackup=None):
3182 tobackup=None):
3191 """function that actually perform all the actions computed for revert
3183 """function that actually perform all the actions computed for revert
3192
3184
3193 This is an independent function to let extension to plug in and react to
3185 This is an independent function to let extension to plug in and react to
3194 the imminent revert.
3186 the imminent revert.
3195
3187
3196 Make sure you have the working directory locked when calling this function.
3188 Make sure you have the working directory locked when calling this function.
3197 """
3189 """
3198 parent, p2 = parents
3190 parent, p2 = parents
3199 node = ctx.node()
3191 node = ctx.node()
3200 excluded_files = []
3192 excluded_files = []
3201 matcher_opts = {"exclude": excluded_files}
3193 matcher_opts = {"exclude": excluded_files}
3202
3194
3203 def checkout(f):
3195 def checkout(f):
3204 fc = ctx[f]
3196 fc = ctx[f]
3205 repo.wwrite(f, fc.data(), fc.flags())
3197 repo.wwrite(f, fc.data(), fc.flags())
3206
3198
3207 def doremove(f):
3199 def doremove(f):
3208 try:
3200 try:
3209 util.unlinkpath(repo.wjoin(f))
3201 util.unlinkpath(repo.wjoin(f))
3210 except OSError:
3202 except OSError:
3211 pass
3203 pass
3212 repo.dirstate.remove(f)
3204 repo.dirstate.remove(f)
3213
3205
3214 audit_path = pathutil.pathauditor(repo.root)
3206 audit_path = pathutil.pathauditor(repo.root)
3215 for f in actions['forget'][0]:
3207 for f in actions['forget'][0]:
3216 if interactive:
3208 if interactive:
3217 choice = repo.ui.promptchoice(
3209 choice = repo.ui.promptchoice(
3218 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3210 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3219 if choice == 0:
3211 if choice == 0:
3220 repo.dirstate.drop(f)
3212 repo.dirstate.drop(f)
3221 else:
3213 else:
3222 excluded_files.append(repo.wjoin(f))
3214 excluded_files.append(repo.wjoin(f))
3223 else:
3215 else:
3224 repo.dirstate.drop(f)
3216 repo.dirstate.drop(f)
3225 for f in actions['remove'][0]:
3217 for f in actions['remove'][0]:
3226 audit_path(f)
3218 audit_path(f)
3227 if interactive:
3219 if interactive:
3228 choice = repo.ui.promptchoice(
3220 choice = repo.ui.promptchoice(
3229 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3221 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3230 if choice == 0:
3222 if choice == 0:
3231 doremove(f)
3223 doremove(f)
3232 else:
3224 else:
3233 excluded_files.append(repo.wjoin(f))
3225 excluded_files.append(repo.wjoin(f))
3234 else:
3226 else:
3235 doremove(f)
3227 doremove(f)
3236 for f in actions['drop'][0]:
3228 for f in actions['drop'][0]:
3237 audit_path(f)
3229 audit_path(f)
3238 repo.dirstate.remove(f)
3230 repo.dirstate.remove(f)
3239
3231
3240 normal = None
3232 normal = None
3241 if node == parent:
3233 if node == parent:
3242 # We're reverting to our parent. If possible, we'd like status
3234 # We're reverting to our parent. If possible, we'd like status
3243 # to report the file as clean. We have to use normallookup for
3235 # to report the file as clean. We have to use normallookup for
3244 # merges to avoid losing information about merged/dirty files.
3236 # merges to avoid losing information about merged/dirty files.
3245 if p2 != nullid:
3237 if p2 != nullid:
3246 normal = repo.dirstate.normallookup
3238 normal = repo.dirstate.normallookup
3247 else:
3239 else:
3248 normal = repo.dirstate.normal
3240 normal = repo.dirstate.normal
3249
3241
3250 newlyaddedandmodifiedfiles = set()
3242 newlyaddedandmodifiedfiles = set()
3251 if interactive:
3243 if interactive:
3252 # Prompt the user for changes to revert
3244 # Prompt the user for changes to revert
3253 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3245 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3254 m = scmutil.match(ctx, torevert, matcher_opts)
3246 m = scmutil.match(ctx, torevert, matcher_opts)
3255 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3247 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3256 diffopts.nodates = True
3248 diffopts.nodates = True
3257 diffopts.git = True
3249 diffopts.git = True
3258 reversehunks = repo.ui.configbool('experimental',
3250 reversehunks = repo.ui.configbool('experimental',
3259 'revertalternateinteractivemode',
3251 'revertalternateinteractivemode',
3260 True)
3252 True)
3261 if reversehunks:
3253 if reversehunks:
3262 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3254 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3263 else:
3255 else:
3264 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3256 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3265 originalchunks = patch.parsepatch(diff)
3257 originalchunks = patch.parsepatch(diff)
3266 operation = 'discard' if node == parent else 'revert'
3258 operation = 'discard' if node == parent else 'revert'
3267
3259
3268 try:
3260 try:
3269
3261
3270 chunks, opts = recordfilter(repo.ui, originalchunks,
3262 chunks, opts = recordfilter(repo.ui, originalchunks,
3271 operation=operation)
3263 operation=operation)
3272 if reversehunks:
3264 if reversehunks:
3273 chunks = patch.reversehunks(chunks)
3265 chunks = patch.reversehunks(chunks)
3274
3266
3275 except patch.PatchError as err:
3267 except patch.PatchError as err:
3276 raise error.Abort(_('error parsing patch: %s') % err)
3268 raise error.Abort(_('error parsing patch: %s') % err)
3277
3269
3278 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3270 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3279 if tobackup is None:
3271 if tobackup is None:
3280 tobackup = set()
3272 tobackup = set()
3281 # Apply changes
3273 # Apply changes
3282 fp = stringio()
3274 fp = stringio()
3283 for c in chunks:
3275 for c in chunks:
3284 # Create a backup file only if this hunk should be backed up
3276 # Create a backup file only if this hunk should be backed up
3285 if ishunk(c) and c.header.filename() in tobackup:
3277 if ishunk(c) and c.header.filename() in tobackup:
3286 abs = c.header.filename()
3278 abs = c.header.filename()
3287 target = repo.wjoin(abs)
3279 target = repo.wjoin(abs)
3288 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3280 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3289 util.copyfile(target, bakname)
3281 util.copyfile(target, bakname)
3290 tobackup.remove(abs)
3282 tobackup.remove(abs)
3291 c.write(fp)
3283 c.write(fp)
3292 dopatch = fp.tell()
3284 dopatch = fp.tell()
3293 fp.seek(0)
3285 fp.seek(0)
3294 if dopatch:
3286 if dopatch:
3295 try:
3287 try:
3296 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3288 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3297 except patch.PatchError as err:
3289 except patch.PatchError as err:
3298 raise error.Abort(str(err))
3290 raise error.Abort(str(err))
3299 del fp
3291 del fp
3300 else:
3292 else:
3301 for f in actions['revert'][0]:
3293 for f in actions['revert'][0]:
3302 checkout(f)
3294 checkout(f)
3303 if normal:
3295 if normal:
3304 normal(f)
3296 normal(f)
3305
3297
3306 for f in actions['add'][0]:
3298 for f in actions['add'][0]:
3307 # Don't checkout modified files, they are already created by the diff
3299 # Don't checkout modified files, they are already created by the diff
3308 if f not in newlyaddedandmodifiedfiles:
3300 if f not in newlyaddedandmodifiedfiles:
3309 checkout(f)
3301 checkout(f)
3310 repo.dirstate.add(f)
3302 repo.dirstate.add(f)
3311
3303
3312 normal = repo.dirstate.normallookup
3304 normal = repo.dirstate.normallookup
3313 if node == parent and p2 == nullid:
3305 if node == parent and p2 == nullid:
3314 normal = repo.dirstate.normal
3306 normal = repo.dirstate.normal
3315 for f in actions['undelete'][0]:
3307 for f in actions['undelete'][0]:
3316 checkout(f)
3308 checkout(f)
3317 normal(f)
3309 normal(f)
3318
3310
3319 copied = copies.pathcopies(repo[parent], ctx)
3311 copied = copies.pathcopies(repo[parent], ctx)
3320
3312
3321 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3313 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3322 if f in copied:
3314 if f in copied:
3323 repo.dirstate.copy(copied[f], f)
3315 repo.dirstate.copy(copied[f], f)
3324
3316
3325 def command(table):
3317 def command(table):
3326 """Returns a function object to be used as a decorator for making commands.
3318 """Returns a function object to be used as a decorator for making commands.
3327
3319
3328 This function receives a command table as its argument. The table should
3320 This function receives a command table as its argument. The table should
3329 be a dict.
3321 be a dict.
3330
3322
3331 The returned function can be used as a decorator for adding commands
3323 The returned function can be used as a decorator for adding commands
3332 to that command table. This function accepts multiple arguments to define
3324 to that command table. This function accepts multiple arguments to define
3333 a command.
3325 a command.
3334
3326
3335 The first argument is the command name.
3327 The first argument is the command name.
3336
3328
3337 The options argument is an iterable of tuples defining command arguments.
3329 The options argument is an iterable of tuples defining command arguments.
3338 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3330 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3339
3331
3340 The synopsis argument defines a short, one line summary of how to use the
3332 The synopsis argument defines a short, one line summary of how to use the
3341 command. This shows up in the help output.
3333 command. This shows up in the help output.
3342
3334
3343 The norepo argument defines whether the command does not require a
3335 The norepo argument defines whether the command does not require a
3344 local repository. Most commands operate against a repository, thus the
3336 local repository. Most commands operate against a repository, thus the
3345 default is False.
3337 default is False.
3346
3338
3347 The optionalrepo argument defines whether the command optionally requires
3339 The optionalrepo argument defines whether the command optionally requires
3348 a local repository.
3340 a local repository.
3349
3341
3350 The inferrepo argument defines whether to try to find a repository from the
3342 The inferrepo argument defines whether to try to find a repository from the
3351 command line arguments. If True, arguments will be examined for potential
3343 command line arguments. If True, arguments will be examined for potential
3352 repository locations. See ``findrepo()``. If a repository is found, it
3344 repository locations. See ``findrepo()``. If a repository is found, it
3353 will be used.
3345 will be used.
3354 """
3346 """
3355 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3347 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3356 inferrepo=False):
3348 inferrepo=False):
3357 def decorator(func):
3349 def decorator(func):
3358 func.norepo = norepo
3350 func.norepo = norepo
3359 func.optionalrepo = optionalrepo
3351 func.optionalrepo = optionalrepo
3360 func.inferrepo = inferrepo
3352 func.inferrepo = inferrepo
3361 if synopsis:
3353 if synopsis:
3362 table[name] = func, list(options), synopsis
3354 table[name] = func, list(options), synopsis
3363 else:
3355 else:
3364 table[name] = func, list(options)
3356 table[name] = func, list(options)
3365 return func
3357 return func
3366 return decorator
3358 return decorator
3367
3359
3368 return cmd
3360 return cmd
3369
3361
3370 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3362 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3371 # commands.outgoing. "missing" is "missing" of the result of
3363 # commands.outgoing. "missing" is "missing" of the result of
3372 # "findcommonoutgoing()"
3364 # "findcommonoutgoing()"
3373 outgoinghooks = util.hooks()
3365 outgoinghooks = util.hooks()
3374
3366
3375 # a list of (ui, repo) functions called by commands.summary
3367 # a list of (ui, repo) functions called by commands.summary
3376 summaryhooks = util.hooks()
3368 summaryhooks = util.hooks()
3377
3369
3378 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3370 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3379 #
3371 #
3380 # functions should return tuple of booleans below, if 'changes' is None:
3372 # functions should return tuple of booleans below, if 'changes' is None:
3381 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3373 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3382 #
3374 #
3383 # otherwise, 'changes' is a tuple of tuples below:
3375 # otherwise, 'changes' is a tuple of tuples below:
3384 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3376 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3385 # - (desturl, destbranch, destpeer, outgoing)
3377 # - (desturl, destbranch, destpeer, outgoing)
3386 summaryremotehooks = util.hooks()
3378 summaryremotehooks = util.hooks()
3387
3379
3388 # A list of state files kept by multistep operations like graft.
3380 # A list of state files kept by multistep operations like graft.
3389 # Since graft cannot be aborted, it is considered 'clearable' by update.
3381 # Since graft cannot be aborted, it is considered 'clearable' by update.
3390 # note: bisect is intentionally excluded
3382 # note: bisect is intentionally excluded
3391 # (state file, clearable, allowcommit, error, hint)
3383 # (state file, clearable, allowcommit, error, hint)
3392 unfinishedstates = [
3384 unfinishedstates = [
3393 ('graftstate', True, False, _('graft in progress'),
3385 ('graftstate', True, False, _('graft in progress'),
3394 _("use 'hg graft --continue' or 'hg update' to abort")),
3386 _("use 'hg graft --continue' or 'hg update' to abort")),
3395 ('updatestate', True, False, _('last update was interrupted'),
3387 ('updatestate', True, False, _('last update was interrupted'),
3396 _("use 'hg update' to get a consistent checkout"))
3388 _("use 'hg update' to get a consistent checkout"))
3397 ]
3389 ]
3398
3390
3399 def checkunfinished(repo, commit=False):
3391 def checkunfinished(repo, commit=False):
3400 '''Look for an unfinished multistep operation, like graft, and abort
3392 '''Look for an unfinished multistep operation, like graft, and abort
3401 if found. It's probably good to check this right before
3393 if found. It's probably good to check this right before
3402 bailifchanged().
3394 bailifchanged().
3403 '''
3395 '''
3404 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3396 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3405 if commit and allowcommit:
3397 if commit and allowcommit:
3406 continue
3398 continue
3407 if repo.vfs.exists(f):
3399 if repo.vfs.exists(f):
3408 raise error.Abort(msg, hint=hint)
3400 raise error.Abort(msg, hint=hint)
3409
3401
3410 def clearunfinished(repo):
3402 def clearunfinished(repo):
3411 '''Check for unfinished operations (as above), and clear the ones
3403 '''Check for unfinished operations (as above), and clear the ones
3412 that are clearable.
3404 that are clearable.
3413 '''
3405 '''
3414 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3406 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3415 if not clearable and repo.vfs.exists(f):
3407 if not clearable and repo.vfs.exists(f):
3416 raise error.Abort(msg, hint=hint)
3408 raise error.Abort(msg, hint=hint)
3417 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3409 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3418 if clearable and repo.vfs.exists(f):
3410 if clearable and repo.vfs.exists(f):
3419 util.unlink(repo.join(f))
3411 util.unlink(repo.join(f))
3420
3412
3421 afterresolvedstates = [
3413 afterresolvedstates = [
3422 ('graftstate',
3414 ('graftstate',
3423 _('hg graft --continue')),
3415 _('hg graft --continue')),
3424 ]
3416 ]
3425
3417
3426 def howtocontinue(repo):
3418 def howtocontinue(repo):
3427 '''Check for an unfinished operation and return the command to finish
3419 '''Check for an unfinished operation and return the command to finish
3428 it.
3420 it.
3429
3421
3430 afterresolvedstates tuples define a .hg/{file} and the corresponding
3422 afterresolvedstates tuples define a .hg/{file} and the corresponding
3431 command needed to finish it.
3423 command needed to finish it.
3432
3424
3433 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3425 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3434 a boolean.
3426 a boolean.
3435 '''
3427 '''
3436 contmsg = _("continue: %s")
3428 contmsg = _("continue: %s")
3437 for f, msg in afterresolvedstates:
3429 for f, msg in afterresolvedstates:
3438 if repo.vfs.exists(f):
3430 if repo.vfs.exists(f):
3439 return contmsg % msg, True
3431 return contmsg % msg, True
3440 workingctx = repo[None]
3432 workingctx = repo[None]
3441 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3433 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3442 for s in workingctx.substate)
3434 for s in workingctx.substate)
3443 if dirty:
3435 if dirty:
3444 return contmsg % _("hg commit"), False
3436 return contmsg % _("hg commit"), False
3445 return None, None
3437 return None, None
3446
3438
3447 def checkafterresolved(repo):
3439 def checkafterresolved(repo):
3448 '''Inform the user about the next action after completing hg resolve
3440 '''Inform the user about the next action after completing hg resolve
3449
3441
3450 If there's a matching afterresolvedstates, howtocontinue will yield
3442 If there's a matching afterresolvedstates, howtocontinue will yield
3451 repo.ui.warn as the reporter.
3443 repo.ui.warn as the reporter.
3452
3444
3453 Otherwise, it will yield repo.ui.note.
3445 Otherwise, it will yield repo.ui.note.
3454 '''
3446 '''
3455 msg, warning = howtocontinue(repo)
3447 msg, warning = howtocontinue(repo)
3456 if msg is not None:
3448 if msg is not None:
3457 if warning:
3449 if warning:
3458 repo.ui.warn("%s\n" % msg)
3450 repo.ui.warn("%s\n" % msg)
3459 else:
3451 else:
3460 repo.ui.note("%s\n" % msg)
3452 repo.ui.note("%s\n" % msg)
3461
3453
3462 def wrongtooltocontinue(repo, task):
3454 def wrongtooltocontinue(repo, task):
3463 '''Raise an abort suggesting how to properly continue if there is an
3455 '''Raise an abort suggesting how to properly continue if there is an
3464 active task.
3456 active task.
3465
3457
3466 Uses howtocontinue() to find the active task.
3458 Uses howtocontinue() to find the active task.
3467
3459
3468 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3460 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3469 a hint.
3461 a hint.
3470 '''
3462 '''
3471 after = howtocontinue(repo)
3463 after = howtocontinue(repo)
3472 hint = None
3464 hint = None
3473 if after[1]:
3465 if after[1]:
3474 hint = after[0]
3466 hint = after[0]
3475 raise error.Abort(_('no %s in progress') % task, hint=hint)
3467 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,619 +1,630
1 # templatekw.py - common changeset template keywords
1 # templatekw.py - common changeset template keywords
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .node import hex, nullid
10 from .node import hex, nullid
11 from . import (
11 from . import (
12 encoding,
12 encoding,
13 error,
13 error,
14 hbisect,
14 hbisect,
15 patch,
15 patch,
16 registrar,
16 registrar,
17 scmutil,
17 scmutil,
18 util,
18 util,
19 )
19 )
20
20
21 # This helper class allows us to handle both:
21 # This helper class allows us to handle both:
22 # "{files}" (legacy command-line-specific list hack) and
22 # "{files}" (legacy command-line-specific list hack) and
23 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
23 # "{files % '{file}\n'}" (hgweb-style with inlining and function support)
24 # and to access raw values:
24 # and to access raw values:
25 # "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
25 # "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
26 # "{get(extras, key)}"
26 # "{get(extras, key)}"
27
27
28 class _hybrid(object):
28 class _hybrid(object):
29 def __init__(self, gen, values, makemap, joinfmt):
29 def __init__(self, gen, values, makemap, joinfmt):
30 self.gen = gen
30 self.gen = gen
31 self.values = values
31 self.values = values
32 self._makemap = makemap
32 self._makemap = makemap
33 self.joinfmt = joinfmt
33 self.joinfmt = joinfmt
34 def __iter__(self):
34 def __iter__(self):
35 return self.gen
35 return self.gen
36 def itermaps(self):
36 def itermaps(self):
37 makemap = self._makemap
37 makemap = self._makemap
38 for x in self.values:
38 for x in self.values:
39 yield makemap(x)
39 yield makemap(x)
40 def __contains__(self, x):
40 def __contains__(self, x):
41 return x in self.values
41 return x in self.values
42 def __len__(self):
42 def __len__(self):
43 return len(self.values)
43 return len(self.values)
44 def __getattr__(self, name):
44 def __getattr__(self, name):
45 if name != 'get':
45 if name != 'get':
46 raise AttributeError(name)
46 raise AttributeError(name)
47 return getattr(self.values, name)
47 return getattr(self.values, name)
48
48
49 def showlist(name, values, plural=None, element=None, separator=' ', **args):
49 def showlist(name, values, plural=None, element=None, separator=' ', **args):
50 if not element:
50 if not element:
51 element = name
51 element = name
52 f = _showlist(name, values, plural, separator, **args)
52 f = _showlist(name, values, plural, separator, **args)
53 return _hybrid(f, values, lambda x: {element: x}, lambda d: d[element])
53 return _hybrid(f, values, lambda x: {element: x}, lambda d: d[element])
54
54
55 def _showlist(name, values, plural=None, separator=' ', **args):
55 def _showlist(name, values, plural=None, separator=' ', **args):
56 '''expand set of values.
56 '''expand set of values.
57 name is name of key in template map.
57 name is name of key in template map.
58 values is list of strings or dicts.
58 values is list of strings or dicts.
59 plural is plural of name, if not simply name + 's'.
59 plural is plural of name, if not simply name + 's'.
60 separator is used to join values as a string
60 separator is used to join values as a string
61
61
62 expansion works like this, given name 'foo'.
62 expansion works like this, given name 'foo'.
63
63
64 if values is empty, expand 'no_foos'.
64 if values is empty, expand 'no_foos'.
65
65
66 if 'foo' not in template map, return values as a string,
66 if 'foo' not in template map, return values as a string,
67 joined by 'separator'.
67 joined by 'separator'.
68
68
69 expand 'start_foos'.
69 expand 'start_foos'.
70
70
71 for each value, expand 'foo'. if 'last_foo' in template
71 for each value, expand 'foo'. if 'last_foo' in template
72 map, expand it instead of 'foo' for last key.
72 map, expand it instead of 'foo' for last key.
73
73
74 expand 'end_foos'.
74 expand 'end_foos'.
75 '''
75 '''
76 templ = args['templ']
76 templ = args['templ']
77 if plural:
77 if plural:
78 names = plural
78 names = plural
79 else: names = name + 's'
79 else: names = name + 's'
80 if not values:
80 if not values:
81 noname = 'no_' + names
81 noname = 'no_' + names
82 if noname in templ:
82 if noname in templ:
83 yield templ(noname, **args)
83 yield templ(noname, **args)
84 return
84 return
85 if name not in templ:
85 if name not in templ:
86 if isinstance(values[0], str):
86 if isinstance(values[0], str):
87 yield separator.join(values)
87 yield separator.join(values)
88 else:
88 else:
89 for v in values:
89 for v in values:
90 yield dict(v, **args)
90 yield dict(v, **args)
91 return
91 return
92 startname = 'start_' + names
92 startname = 'start_' + names
93 if startname in templ:
93 if startname in templ:
94 yield templ(startname, **args)
94 yield templ(startname, **args)
95 vargs = args.copy()
95 vargs = args.copy()
96 def one(v, tag=name):
96 def one(v, tag=name):
97 try:
97 try:
98 vargs.update(v)
98 vargs.update(v)
99 except (AttributeError, ValueError):
99 except (AttributeError, ValueError):
100 try:
100 try:
101 for a, b in v:
101 for a, b in v:
102 vargs[a] = b
102 vargs[a] = b
103 except ValueError:
103 except ValueError:
104 vargs[name] = v
104 vargs[name] = v
105 return templ(tag, **vargs)
105 return templ(tag, **vargs)
106 lastname = 'last_' + name
106 lastname = 'last_' + name
107 if lastname in templ:
107 if lastname in templ:
108 last = values.pop()
108 last = values.pop()
109 else:
109 else:
110 last = None
110 last = None
111 for v in values:
111 for v in values:
112 yield one(v)
112 yield one(v)
113 if last is not None:
113 if last is not None:
114 yield one(last, tag=lastname)
114 yield one(last, tag=lastname)
115 endname = 'end_' + names
115 endname = 'end_' + names
116 if endname in templ:
116 if endname in templ:
117 yield templ(endname, **args)
117 yield templ(endname, **args)
118
118
119 def _formatrevnode(ctx):
119 def _formatrevnode(ctx):
120 """Format changeset as '{rev}:{node|formatnode}', which is the default
120 """Format changeset as '{rev}:{node|formatnode}', which is the default
121 template provided by cmdutil.changeset_templater"""
121 template provided by cmdutil.changeset_templater"""
122 repo = ctx.repo()
122 repo = ctx.repo()
123 if repo.ui.debugflag:
123 if repo.ui.debugflag:
124 hexnode = ctx.hex()
124 hexnode = ctx.hex()
125 else:
125 else:
126 hexnode = ctx.hex()[:12]
126 hexnode = ctx.hex()[:12]
127 return '%d:%s' % (scmutil.intrev(ctx.rev()), hexnode)
127 return '%d:%s' % (scmutil.intrev(ctx.rev()), hexnode)
128
128
129 def getfiles(repo, ctx, revcache):
129 def getfiles(repo, ctx, revcache):
130 if 'files' not in revcache:
130 if 'files' not in revcache:
131 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
131 revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
132 return revcache['files']
132 return revcache['files']
133
133
134 def getlatesttags(repo, ctx, cache, pattern=None):
134 def getlatesttags(repo, ctx, cache, pattern=None):
135 '''return date, distance and name for the latest tag of rev'''
135 '''return date, distance and name for the latest tag of rev'''
136
136
137 cachename = 'latesttags'
137 cachename = 'latesttags'
138 if pattern is not None:
138 if pattern is not None:
139 cachename += '-' + pattern
139 cachename += '-' + pattern
140 match = util.stringmatcher(pattern)[2]
140 match = util.stringmatcher(pattern)[2]
141 else:
141 else:
142 match = util.always
142 match = util.always
143
143
144 if cachename not in cache:
144 if cachename not in cache:
145 # Cache mapping from rev to a tuple with tag date, tag
145 # Cache mapping from rev to a tuple with tag date, tag
146 # distance and tag name
146 # distance and tag name
147 cache[cachename] = {-1: (0, 0, ['null'])}
147 cache[cachename] = {-1: (0, 0, ['null'])}
148 latesttags = cache[cachename]
148 latesttags = cache[cachename]
149
149
150 rev = ctx.rev()
150 rev = ctx.rev()
151 todo = [rev]
151 todo = [rev]
152 while todo:
152 while todo:
153 rev = todo.pop()
153 rev = todo.pop()
154 if rev in latesttags:
154 if rev in latesttags:
155 continue
155 continue
156 ctx = repo[rev]
156 ctx = repo[rev]
157 tags = [t for t in ctx.tags()
157 tags = [t for t in ctx.tags()
158 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
158 if (repo.tagtype(t) and repo.tagtype(t) != 'local'
159 and match(t))]
159 and match(t))]
160 if tags:
160 if tags:
161 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
161 latesttags[rev] = ctx.date()[0], 0, [t for t in sorted(tags)]
162 continue
162 continue
163 try:
163 try:
164 # The tuples are laid out so the right one can be found by
164 # The tuples are laid out so the right one can be found by
165 # comparison.
165 # comparison.
166 pdate, pdist, ptag = max(
166 pdate, pdist, ptag = max(
167 latesttags[p.rev()] for p in ctx.parents())
167 latesttags[p.rev()] for p in ctx.parents())
168 except KeyError:
168 except KeyError:
169 # Cache miss - recurse
169 # Cache miss - recurse
170 todo.append(rev)
170 todo.append(rev)
171 todo.extend(p.rev() for p in ctx.parents())
171 todo.extend(p.rev() for p in ctx.parents())
172 continue
172 continue
173 latesttags[rev] = pdate, pdist + 1, ptag
173 latesttags[rev] = pdate, pdist + 1, ptag
174 return latesttags[rev]
174 return latesttags[rev]
175
175
176 def getrenamedfn(repo, endrev=None):
176 def getrenamedfn(repo, endrev=None):
177 rcache = {}
177 rcache = {}
178 if endrev is None:
178 if endrev is None:
179 endrev = len(repo)
179 endrev = len(repo)
180
180
181 def getrenamed(fn, rev):
181 def getrenamed(fn, rev):
182 '''looks up all renames for a file (up to endrev) the first
182 '''looks up all renames for a file (up to endrev) the first
183 time the file is given. It indexes on the changerev and only
183 time the file is given. It indexes on the changerev and only
184 parses the manifest if linkrev != changerev.
184 parses the manifest if linkrev != changerev.
185 Returns rename info for fn at changerev rev.'''
185 Returns rename info for fn at changerev rev.'''
186 if fn not in rcache:
186 if fn not in rcache:
187 rcache[fn] = {}
187 rcache[fn] = {}
188 fl = repo.file(fn)
188 fl = repo.file(fn)
189 for i in fl:
189 for i in fl:
190 lr = fl.linkrev(i)
190 lr = fl.linkrev(i)
191 renamed = fl.renamed(fl.node(i))
191 renamed = fl.renamed(fl.node(i))
192 rcache[fn][lr] = renamed
192 rcache[fn][lr] = renamed
193 if lr >= endrev:
193 if lr >= endrev:
194 break
194 break
195 if rev in rcache[fn]:
195 if rev in rcache[fn]:
196 return rcache[fn][rev]
196 return rcache[fn][rev]
197
197
198 # If linkrev != rev (i.e. rev not found in rcache) fallback to
198 # If linkrev != rev (i.e. rev not found in rcache) fallback to
199 # filectx logic.
199 # filectx logic.
200 try:
200 try:
201 return repo[rev][fn].renamed()
201 return repo[rev][fn].renamed()
202 except error.LookupError:
202 except error.LookupError:
203 return None
203 return None
204
204
205 return getrenamed
205 return getrenamed
206
206
207 # default templates internally used for rendering of lists
208 defaulttempl = {
209 'parent': '{rev}:{node|formatnode} ',
210 'manifest': '{rev}:{node|formatnode}',
211 'file_copy': '{name} ({source})',
212 'envvar': '{key}={value}',
213 'extra': '{key}={value|stringescape}'
214 }
215 # filecopy is preserved for compatibility reasons
216 defaulttempl['filecopy'] = defaulttempl['file_copy']
217
207 # keywords are callables like:
218 # keywords are callables like:
208 # fn(repo, ctx, templ, cache, revcache, **args)
219 # fn(repo, ctx, templ, cache, revcache, **args)
209 # with:
220 # with:
210 # repo - current repository instance
221 # repo - current repository instance
211 # ctx - the changectx being displayed
222 # ctx - the changectx being displayed
212 # templ - the templater instance
223 # templ - the templater instance
213 # cache - a cache dictionary for the whole templater run
224 # cache - a cache dictionary for the whole templater run
214 # revcache - a cache dictionary for the current revision
225 # revcache - a cache dictionary for the current revision
215 keywords = {}
226 keywords = {}
216
227
217 templatekeyword = registrar.templatekeyword(keywords)
228 templatekeyword = registrar.templatekeyword(keywords)
218
229
219 @templatekeyword('author')
230 @templatekeyword('author')
220 def showauthor(repo, ctx, templ, **args):
231 def showauthor(repo, ctx, templ, **args):
221 """String. The unmodified author of the changeset."""
232 """String. The unmodified author of the changeset."""
222 return ctx.user()
233 return ctx.user()
223
234
224 @templatekeyword('bisect')
235 @templatekeyword('bisect')
225 def showbisect(repo, ctx, templ, **args):
236 def showbisect(repo, ctx, templ, **args):
226 """String. The changeset bisection status."""
237 """String. The changeset bisection status."""
227 return hbisect.label(repo, ctx.node())
238 return hbisect.label(repo, ctx.node())
228
239
229 @templatekeyword('branch')
240 @templatekeyword('branch')
230 def showbranch(**args):
241 def showbranch(**args):
231 """String. The name of the branch on which the changeset was
242 """String. The name of the branch on which the changeset was
232 committed.
243 committed.
233 """
244 """
234 return args['ctx'].branch()
245 return args['ctx'].branch()
235
246
236 @templatekeyword('branches')
247 @templatekeyword('branches')
237 def showbranches(**args):
248 def showbranches(**args):
238 """List of strings. The name of the branch on which the
249 """List of strings. The name of the branch on which the
239 changeset was committed. Will be empty if the branch name was
250 changeset was committed. Will be empty if the branch name was
240 default. (DEPRECATED)
251 default. (DEPRECATED)
241 """
252 """
242 branch = args['ctx'].branch()
253 branch = args['ctx'].branch()
243 if branch != 'default':
254 if branch != 'default':
244 return showlist('branch', [branch], plural='branches', **args)
255 return showlist('branch', [branch], plural='branches', **args)
245 return showlist('branch', [], plural='branches', **args)
256 return showlist('branch', [], plural='branches', **args)
246
257
247 @templatekeyword('bookmarks')
258 @templatekeyword('bookmarks')
248 def showbookmarks(**args):
259 def showbookmarks(**args):
249 """List of strings. Any bookmarks associated with the
260 """List of strings. Any bookmarks associated with the
250 changeset. Also sets 'active', the name of the active bookmark.
261 changeset. Also sets 'active', the name of the active bookmark.
251 """
262 """
252 repo = args['ctx']._repo
263 repo = args['ctx']._repo
253 bookmarks = args['ctx'].bookmarks()
264 bookmarks = args['ctx'].bookmarks()
254 active = repo._activebookmark
265 active = repo._activebookmark
255 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
266 makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
256 f = _showlist('bookmark', bookmarks, **args)
267 f = _showlist('bookmark', bookmarks, **args)
257 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
268 return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
258
269
259 @templatekeyword('children')
270 @templatekeyword('children')
260 def showchildren(**args):
271 def showchildren(**args):
261 """List of strings. The children of the changeset."""
272 """List of strings. The children of the changeset."""
262 ctx = args['ctx']
273 ctx = args['ctx']
263 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
274 childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()]
264 return showlist('children', childrevs, element='child', **args)
275 return showlist('children', childrevs, element='child', **args)
265
276
266 # Deprecated, but kept alive for help generation a purpose.
277 # Deprecated, but kept alive for help generation a purpose.
267 @templatekeyword('currentbookmark')
278 @templatekeyword('currentbookmark')
268 def showcurrentbookmark(**args):
279 def showcurrentbookmark(**args):
269 """String. The active bookmark, if it is
280 """String. The active bookmark, if it is
270 associated with the changeset (DEPRECATED)"""
281 associated with the changeset (DEPRECATED)"""
271 return showactivebookmark(**args)
282 return showactivebookmark(**args)
272
283
273 @templatekeyword('activebookmark')
284 @templatekeyword('activebookmark')
274 def showactivebookmark(**args):
285 def showactivebookmark(**args):
275 """String. The active bookmark, if it is
286 """String. The active bookmark, if it is
276 associated with the changeset"""
287 associated with the changeset"""
277 active = args['repo']._activebookmark
288 active = args['repo']._activebookmark
278 if active and active in args['ctx'].bookmarks():
289 if active and active in args['ctx'].bookmarks():
279 return active
290 return active
280 return ''
291 return ''
281
292
282 @templatekeyword('date')
293 @templatekeyword('date')
283 def showdate(repo, ctx, templ, **args):
294 def showdate(repo, ctx, templ, **args):
284 """Date information. The date when the changeset was committed."""
295 """Date information. The date when the changeset was committed."""
285 return ctx.date()
296 return ctx.date()
286
297
287 @templatekeyword('desc')
298 @templatekeyword('desc')
288 def showdescription(repo, ctx, templ, **args):
299 def showdescription(repo, ctx, templ, **args):
289 """String. The text of the changeset description."""
300 """String. The text of the changeset description."""
290 s = ctx.description()
301 s = ctx.description()
291 if isinstance(s, encoding.localstr):
302 if isinstance(s, encoding.localstr):
292 # try hard to preserve utf-8 bytes
303 # try hard to preserve utf-8 bytes
293 return encoding.tolocal(encoding.fromlocal(s).strip())
304 return encoding.tolocal(encoding.fromlocal(s).strip())
294 else:
305 else:
295 return s.strip()
306 return s.strip()
296
307
297 @templatekeyword('diffstat')
308 @templatekeyword('diffstat')
298 def showdiffstat(repo, ctx, templ, **args):
309 def showdiffstat(repo, ctx, templ, **args):
299 """String. Statistics of changes with the following format:
310 """String. Statistics of changes with the following format:
300 "modified files: +added/-removed lines"
311 "modified files: +added/-removed lines"
301 """
312 """
302 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
313 stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
303 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
314 maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
304 return '%s: +%s/-%s' % (len(stats), adds, removes)
315 return '%s: +%s/-%s' % (len(stats), adds, removes)
305
316
306 @templatekeyword('envvars')
317 @templatekeyword('envvars')
307 def showenvvars(repo, **args):
318 def showenvvars(repo, **args):
308 """A dictionary of environment variables. (EXPERIMENTAL)"""
319 """A dictionary of environment variables. (EXPERIMENTAL)"""
309
320
310 env = repo.ui.exportableenviron()
321 env = repo.ui.exportableenviron()
311 env = util.sortdict((k, env[k]) for k in sorted(env))
322 env = util.sortdict((k, env[k]) for k in sorted(env))
312 makemap = lambda k: {'key': k, 'value': env[k]}
323 makemap = lambda k: {'key': k, 'value': env[k]}
313 c = [makemap(k) for k in env]
324 c = [makemap(k) for k in env]
314 f = _showlist('envvar', c, plural='envvars', **args)
325 f = _showlist('envvar', c, plural='envvars', **args)
315 return _hybrid(f, env, makemap,
326 return _hybrid(f, env, makemap,
316 lambda x: '%s=%s' % (x['key'], x['value']))
327 lambda x: '%s=%s' % (x['key'], x['value']))
317
328
318 @templatekeyword('extras')
329 @templatekeyword('extras')
319 def showextras(**args):
330 def showextras(**args):
320 """List of dicts with key, value entries of the 'extras'
331 """List of dicts with key, value entries of the 'extras'
321 field of this changeset."""
332 field of this changeset."""
322 extras = args['ctx'].extra()
333 extras = args['ctx'].extra()
323 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
334 extras = util.sortdict((k, extras[k]) for k in sorted(extras))
324 makemap = lambda k: {'key': k, 'value': extras[k]}
335 makemap = lambda k: {'key': k, 'value': extras[k]}
325 c = [makemap(k) for k in extras]
336 c = [makemap(k) for k in extras]
326 f = _showlist('extra', c, plural='extras', **args)
337 f = _showlist('extra', c, plural='extras', **args)
327 return _hybrid(f, extras, makemap,
338 return _hybrid(f, extras, makemap,
328 lambda x: '%s=%s' % (x['key'], x['value']))
339 lambda x: '%s=%s' % (x['key'], x['value']))
329
340
330 @templatekeyword('file_adds')
341 @templatekeyword('file_adds')
331 def showfileadds(**args):
342 def showfileadds(**args):
332 """List of strings. Files added by this changeset."""
343 """List of strings. Files added by this changeset."""
333 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
344 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
334 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
345 return showlist('file_add', getfiles(repo, ctx, revcache)[1],
335 element='file', **args)
346 element='file', **args)
336
347
337 @templatekeyword('file_copies')
348 @templatekeyword('file_copies')
338 def showfilecopies(**args):
349 def showfilecopies(**args):
339 """List of strings. Files copied in this changeset with
350 """List of strings. Files copied in this changeset with
340 their sources.
351 their sources.
341 """
352 """
342 cache, ctx = args['cache'], args['ctx']
353 cache, ctx = args['cache'], args['ctx']
343 copies = args['revcache'].get('copies')
354 copies = args['revcache'].get('copies')
344 if copies is None:
355 if copies is None:
345 if 'getrenamed' not in cache:
356 if 'getrenamed' not in cache:
346 cache['getrenamed'] = getrenamedfn(args['repo'])
357 cache['getrenamed'] = getrenamedfn(args['repo'])
347 copies = []
358 copies = []
348 getrenamed = cache['getrenamed']
359 getrenamed = cache['getrenamed']
349 for fn in ctx.files():
360 for fn in ctx.files():
350 rename = getrenamed(fn, ctx.rev())
361 rename = getrenamed(fn, ctx.rev())
351 if rename:
362 if rename:
352 copies.append((fn, rename[0]))
363 copies.append((fn, rename[0]))
353
364
354 copies = util.sortdict(copies)
365 copies = util.sortdict(copies)
355 makemap = lambda k: {'name': k, 'source': copies[k]}
366 makemap = lambda k: {'name': k, 'source': copies[k]}
356 c = [makemap(k) for k in copies]
367 c = [makemap(k) for k in copies]
357 f = _showlist('file_copy', c, plural='file_copies', **args)
368 f = _showlist('file_copy', c, plural='file_copies', **args)
358 return _hybrid(f, copies, makemap,
369 return _hybrid(f, copies, makemap,
359 lambda x: '%s (%s)' % (x['name'], x['source']))
370 lambda x: '%s (%s)' % (x['name'], x['source']))
360
371
361 # showfilecopiesswitch() displays file copies only if copy records are
372 # showfilecopiesswitch() displays file copies only if copy records are
362 # provided before calling the templater, usually with a --copies
373 # provided before calling the templater, usually with a --copies
363 # command line switch.
374 # command line switch.
364 @templatekeyword('file_copies_switch')
375 @templatekeyword('file_copies_switch')
365 def showfilecopiesswitch(**args):
376 def showfilecopiesswitch(**args):
366 """List of strings. Like "file_copies" but displayed
377 """List of strings. Like "file_copies" but displayed
367 only if the --copied switch is set.
378 only if the --copied switch is set.
368 """
379 """
369 copies = args['revcache'].get('copies') or []
380 copies = args['revcache'].get('copies') or []
370 copies = util.sortdict(copies)
381 copies = util.sortdict(copies)
371 makemap = lambda k: {'name': k, 'source': copies[k]}
382 makemap = lambda k: {'name': k, 'source': copies[k]}
372 c = [makemap(k) for k in copies]
383 c = [makemap(k) for k in copies]
373 f = _showlist('file_copy', c, plural='file_copies', **args)
384 f = _showlist('file_copy', c, plural='file_copies', **args)
374 return _hybrid(f, copies, makemap,
385 return _hybrid(f, copies, makemap,
375 lambda x: '%s (%s)' % (x['name'], x['source']))
386 lambda x: '%s (%s)' % (x['name'], x['source']))
376
387
377 @templatekeyword('file_dels')
388 @templatekeyword('file_dels')
378 def showfiledels(**args):
389 def showfiledels(**args):
379 """List of strings. Files removed by this changeset."""
390 """List of strings. Files removed by this changeset."""
380 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
391 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
381 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
392 return showlist('file_del', getfiles(repo, ctx, revcache)[2],
382 element='file', **args)
393 element='file', **args)
383
394
384 @templatekeyword('file_mods')
395 @templatekeyword('file_mods')
385 def showfilemods(**args):
396 def showfilemods(**args):
386 """List of strings. Files modified by this changeset."""
397 """List of strings. Files modified by this changeset."""
387 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
398 repo, ctx, revcache = args['repo'], args['ctx'], args['revcache']
388 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
399 return showlist('file_mod', getfiles(repo, ctx, revcache)[0],
389 element='file', **args)
400 element='file', **args)
390
401
391 @templatekeyword('files')
402 @templatekeyword('files')
392 def showfiles(**args):
403 def showfiles(**args):
393 """List of strings. All files modified, added, or removed by this
404 """List of strings. All files modified, added, or removed by this
394 changeset.
405 changeset.
395 """
406 """
396 return showlist('file', args['ctx'].files(), **args)
407 return showlist('file', args['ctx'].files(), **args)
397
408
398 @templatekeyword('graphnode')
409 @templatekeyword('graphnode')
399 def showgraphnode(repo, ctx, **args):
410 def showgraphnode(repo, ctx, **args):
400 """String. The character representing the changeset node in
411 """String. The character representing the changeset node in
401 an ASCII revision graph"""
412 an ASCII revision graph"""
402 wpnodes = repo.dirstate.parents()
413 wpnodes = repo.dirstate.parents()
403 if wpnodes[1] == nullid:
414 if wpnodes[1] == nullid:
404 wpnodes = wpnodes[:1]
415 wpnodes = wpnodes[:1]
405 if ctx.node() in wpnodes:
416 if ctx.node() in wpnodes:
406 return '@'
417 return '@'
407 elif ctx.obsolete():
418 elif ctx.obsolete():
408 return 'x'
419 return 'x'
409 elif ctx.closesbranch():
420 elif ctx.closesbranch():
410 return '_'
421 return '_'
411 else:
422 else:
412 return 'o'
423 return 'o'
413
424
414 @templatekeyword('latesttag')
425 @templatekeyword('latesttag')
415 def showlatesttag(**args):
426 def showlatesttag(**args):
416 """List of strings. The global tags on the most recent globally
427 """List of strings. The global tags on the most recent globally
417 tagged ancestor of this changeset.
428 tagged ancestor of this changeset.
418 """
429 """
419 return showlatesttags(None, **args)
430 return showlatesttags(None, **args)
420
431
421 def showlatesttags(pattern, **args):
432 def showlatesttags(pattern, **args):
422 """helper method for the latesttag keyword and function"""
433 """helper method for the latesttag keyword and function"""
423 repo, ctx = args['repo'], args['ctx']
434 repo, ctx = args['repo'], args['ctx']
424 cache = args['cache']
435 cache = args['cache']
425 latesttags = getlatesttags(repo, ctx, cache, pattern)
436 latesttags = getlatesttags(repo, ctx, cache, pattern)
426
437
427 # latesttag[0] is an implementation detail for sorting csets on different
438 # latesttag[0] is an implementation detail for sorting csets on different
428 # branches in a stable manner- it is the date the tagged cset was created,
439 # branches in a stable manner- it is the date the tagged cset was created,
429 # not the date the tag was created. Therefore it isn't made visible here.
440 # not the date the tag was created. Therefore it isn't made visible here.
430 makemap = lambda v: {
441 makemap = lambda v: {
431 'changes': _showchangessincetag,
442 'changes': _showchangessincetag,
432 'distance': latesttags[1],
443 'distance': latesttags[1],
433 'latesttag': v, # BC with {latesttag % '{latesttag}'}
444 'latesttag': v, # BC with {latesttag % '{latesttag}'}
434 'tag': v
445 'tag': v
435 }
446 }
436
447
437 tags = latesttags[2]
448 tags = latesttags[2]
438 f = _showlist('latesttag', tags, separator=':', **args)
449 f = _showlist('latesttag', tags, separator=':', **args)
439 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
450 return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
440
451
441 @templatekeyword('latesttagdistance')
452 @templatekeyword('latesttagdistance')
442 def showlatesttagdistance(repo, ctx, templ, cache, **args):
453 def showlatesttagdistance(repo, ctx, templ, cache, **args):
443 """Integer. Longest path to the latest tag."""
454 """Integer. Longest path to the latest tag."""
444 return getlatesttags(repo, ctx, cache)[1]
455 return getlatesttags(repo, ctx, cache)[1]
445
456
446 @templatekeyword('changessincelatesttag')
457 @templatekeyword('changessincelatesttag')
447 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
458 def showchangessincelatesttag(repo, ctx, templ, cache, **args):
448 """Integer. All ancestors not in the latest tag."""
459 """Integer. All ancestors not in the latest tag."""
449 latesttag = getlatesttags(repo, ctx, cache)[2][0]
460 latesttag = getlatesttags(repo, ctx, cache)[2][0]
450
461
451 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
462 return _showchangessincetag(repo, ctx, tag=latesttag, **args)
452
463
453 def _showchangessincetag(repo, ctx, **args):
464 def _showchangessincetag(repo, ctx, **args):
454 offset = 0
465 offset = 0
455 revs = [ctx.rev()]
466 revs = [ctx.rev()]
456 tag = args['tag']
467 tag = args['tag']
457
468
458 # The only() revset doesn't currently support wdir()
469 # The only() revset doesn't currently support wdir()
459 if ctx.rev() is None:
470 if ctx.rev() is None:
460 offset = 1
471 offset = 1
461 revs = [p.rev() for p in ctx.parents()]
472 revs = [p.rev() for p in ctx.parents()]
462
473
463 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
474 return len(repo.revs('only(%ld, %s)', revs, tag)) + offset
464
475
465 @templatekeyword('manifest')
476 @templatekeyword('manifest')
466 def showmanifest(**args):
477 def showmanifest(**args):
467 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
478 repo, ctx, templ = args['repo'], args['ctx'], args['templ']
468 mnode = ctx.manifestnode()
479 mnode = ctx.manifestnode()
469 if mnode is None:
480 if mnode is None:
470 # just avoid crash, we might want to use the 'ff...' hash in future
481 # just avoid crash, we might want to use the 'ff...' hash in future
471 return
482 return
472 args = args.copy()
483 args = args.copy()
473 args.update({'rev': repo.manifestlog._revlog.rev(mnode),
484 args.update({'rev': repo.manifestlog._revlog.rev(mnode),
474 'node': hex(mnode)})
485 'node': hex(mnode)})
475 return templ('manifest', **args)
486 return templ('manifest', **args)
476
487
477 def shownames(namespace, **args):
488 def shownames(namespace, **args):
478 """helper method to generate a template keyword for a namespace"""
489 """helper method to generate a template keyword for a namespace"""
479 ctx = args['ctx']
490 ctx = args['ctx']
480 repo = ctx.repo()
491 repo = ctx.repo()
481 ns = repo.names[namespace]
492 ns = repo.names[namespace]
482 names = ns.names(repo, ctx.node())
493 names = ns.names(repo, ctx.node())
483 return showlist(ns.templatename, names, plural=namespace, **args)
494 return showlist(ns.templatename, names, plural=namespace, **args)
484
495
485 @templatekeyword('namespaces')
496 @templatekeyword('namespaces')
486 def shownamespaces(**args):
497 def shownamespaces(**args):
487 """Dict of lists. Names attached to this changeset per
498 """Dict of lists. Names attached to this changeset per
488 namespace."""
499 namespace."""
489 ctx = args['ctx']
500 ctx = args['ctx']
490 repo = ctx.repo()
501 repo = ctx.repo()
491 namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()),
502 namespaces = util.sortdict((k, showlist('name', ns.names(repo, ctx.node()),
492 **args))
503 **args))
493 for k, ns in repo.names.iteritems())
504 for k, ns in repo.names.iteritems())
494 f = _showlist('namespace', list(namespaces), **args)
505 f = _showlist('namespace', list(namespaces), **args)
495 return _hybrid(f, namespaces,
506 return _hybrid(f, namespaces,
496 lambda k: {'namespace': k, 'names': namespaces[k]},
507 lambda k: {'namespace': k, 'names': namespaces[k]},
497 lambda x: x['namespace'])
508 lambda x: x['namespace'])
498
509
499 @templatekeyword('node')
510 @templatekeyword('node')
500 def shownode(repo, ctx, templ, **args):
511 def shownode(repo, ctx, templ, **args):
501 """String. The changeset identification hash, as a 40 hexadecimal
512 """String. The changeset identification hash, as a 40 hexadecimal
502 digit string.
513 digit string.
503 """
514 """
504 return ctx.hex()
515 return ctx.hex()
505
516
506 @templatekeyword('p1rev')
517 @templatekeyword('p1rev')
507 def showp1rev(repo, ctx, templ, **args):
518 def showp1rev(repo, ctx, templ, **args):
508 """Integer. The repository-local revision number of the changeset's
519 """Integer. The repository-local revision number of the changeset's
509 first parent, or -1 if the changeset has no parents."""
520 first parent, or -1 if the changeset has no parents."""
510 return ctx.p1().rev()
521 return ctx.p1().rev()
511
522
512 @templatekeyword('p2rev')
523 @templatekeyword('p2rev')
513 def showp2rev(repo, ctx, templ, **args):
524 def showp2rev(repo, ctx, templ, **args):
514 """Integer. The repository-local revision number of the changeset's
525 """Integer. The repository-local revision number of the changeset's
515 second parent, or -1 if the changeset has no second parent."""
526 second parent, or -1 if the changeset has no second parent."""
516 return ctx.p2().rev()
527 return ctx.p2().rev()
517
528
518 @templatekeyword('p1node')
529 @templatekeyword('p1node')
519 def showp1node(repo, ctx, templ, **args):
530 def showp1node(repo, ctx, templ, **args):
520 """String. The identification hash of the changeset's first parent,
531 """String. The identification hash of the changeset's first parent,
521 as a 40 digit hexadecimal string. If the changeset has no parents, all
532 as a 40 digit hexadecimal string. If the changeset has no parents, all
522 digits are 0."""
533 digits are 0."""
523 return ctx.p1().hex()
534 return ctx.p1().hex()
524
535
525 @templatekeyword('p2node')
536 @templatekeyword('p2node')
526 def showp2node(repo, ctx, templ, **args):
537 def showp2node(repo, ctx, templ, **args):
527 """String. The identification hash of the changeset's second
538 """String. The identification hash of the changeset's second
528 parent, as a 40 digit hexadecimal string. If the changeset has no second
539 parent, as a 40 digit hexadecimal string. If the changeset has no second
529 parent, all digits are 0."""
540 parent, all digits are 0."""
530 return ctx.p2().hex()
541 return ctx.p2().hex()
531
542
532 @templatekeyword('parents')
543 @templatekeyword('parents')
533 def showparents(**args):
544 def showparents(**args):
534 """List of strings. The parents of the changeset in "rev:node"
545 """List of strings. The parents of the changeset in "rev:node"
535 format. If the changeset has only one "natural" parent (the predecessor
546 format. If the changeset has only one "natural" parent (the predecessor
536 revision) nothing is shown."""
547 revision) nothing is shown."""
537 repo = args['repo']
548 repo = args['repo']
538 ctx = args['ctx']
549 ctx = args['ctx']
539 pctxs = scmutil.meaningfulparents(repo, ctx)
550 pctxs = scmutil.meaningfulparents(repo, ctx)
540 prevs = [str(p.rev()) for p in pctxs] # ifcontains() needs a list of str
551 prevs = [str(p.rev()) for p in pctxs] # ifcontains() needs a list of str
541 parents = [[('rev', p.rev()),
552 parents = [[('rev', p.rev()),
542 ('node', p.hex()),
553 ('node', p.hex()),
543 ('phase', p.phasestr())]
554 ('phase', p.phasestr())]
544 for p in pctxs]
555 for p in pctxs]
545 f = _showlist('parent', parents, **args)
556 f = _showlist('parent', parents, **args)
546 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
557 return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
547 lambda d: _formatrevnode(d['ctx']))
558 lambda d: _formatrevnode(d['ctx']))
548
559
549 @templatekeyword('phase')
560 @templatekeyword('phase')
550 def showphase(repo, ctx, templ, **args):
561 def showphase(repo, ctx, templ, **args):
551 """String. The changeset phase name."""
562 """String. The changeset phase name."""
552 return ctx.phasestr()
563 return ctx.phasestr()
553
564
554 @templatekeyword('phaseidx')
565 @templatekeyword('phaseidx')
555 def showphaseidx(repo, ctx, templ, **args):
566 def showphaseidx(repo, ctx, templ, **args):
556 """Integer. The changeset phase index."""
567 """Integer. The changeset phase index."""
557 return ctx.phase()
568 return ctx.phase()
558
569
559 @templatekeyword('rev')
570 @templatekeyword('rev')
560 def showrev(repo, ctx, templ, **args):
571 def showrev(repo, ctx, templ, **args):
561 """Integer. The repository-local changeset revision number."""
572 """Integer. The repository-local changeset revision number."""
562 return scmutil.intrev(ctx.rev())
573 return scmutil.intrev(ctx.rev())
563
574
564 def showrevslist(name, revs, **args):
575 def showrevslist(name, revs, **args):
565 """helper to generate a list of revisions in which a mapped template will
576 """helper to generate a list of revisions in which a mapped template will
566 be evaluated"""
577 be evaluated"""
567 repo = args['ctx'].repo()
578 repo = args['ctx'].repo()
568 revs = [str(r) for r in revs] # ifcontains() needs a list of str
579 revs = [str(r) for r in revs] # ifcontains() needs a list of str
569 f = _showlist(name, revs, **args)
580 f = _showlist(name, revs, **args)
570 return _hybrid(f, revs,
581 return _hybrid(f, revs,
571 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
582 lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
572 lambda d: d[name])
583 lambda d: d[name])
573
584
574 @templatekeyword('subrepos')
585 @templatekeyword('subrepos')
575 def showsubrepos(**args):
586 def showsubrepos(**args):
576 """List of strings. Updated subrepositories in the changeset."""
587 """List of strings. Updated subrepositories in the changeset."""
577 ctx = args['ctx']
588 ctx = args['ctx']
578 substate = ctx.substate
589 substate = ctx.substate
579 if not substate:
590 if not substate:
580 return showlist('subrepo', [], **args)
591 return showlist('subrepo', [], **args)
581 psubstate = ctx.parents()[0].substate or {}
592 psubstate = ctx.parents()[0].substate or {}
582 subrepos = []
593 subrepos = []
583 for sub in substate:
594 for sub in substate:
584 if sub not in psubstate or substate[sub] != psubstate[sub]:
595 if sub not in psubstate or substate[sub] != psubstate[sub]:
585 subrepos.append(sub) # modified or newly added in ctx
596 subrepos.append(sub) # modified or newly added in ctx
586 for sub in psubstate:
597 for sub in psubstate:
587 if sub not in substate:
598 if sub not in substate:
588 subrepos.append(sub) # removed in ctx
599 subrepos.append(sub) # removed in ctx
589 return showlist('subrepo', sorted(subrepos), **args)
600 return showlist('subrepo', sorted(subrepos), **args)
590
601
591 # don't remove "showtags" definition, even though namespaces will put
602 # don't remove "showtags" definition, even though namespaces will put
592 # a helper function for "tags" keyword into "keywords" map automatically,
603 # a helper function for "tags" keyword into "keywords" map automatically,
593 # because online help text is built without namespaces initialization
604 # because online help text is built without namespaces initialization
594 @templatekeyword('tags')
605 @templatekeyword('tags')
595 def showtags(**args):
606 def showtags(**args):
596 """List of strings. Any tags associated with the changeset."""
607 """List of strings. Any tags associated with the changeset."""
597 return shownames('tags', **args)
608 return shownames('tags', **args)
598
609
599 def loadkeyword(ui, extname, registrarobj):
610 def loadkeyword(ui, extname, registrarobj):
600 """Load template keyword from specified registrarobj
611 """Load template keyword from specified registrarobj
601 """
612 """
602 for name, func in registrarobj._table.iteritems():
613 for name, func in registrarobj._table.iteritems():
603 keywords[name] = func
614 keywords[name] = func
604
615
605 @templatekeyword('termwidth')
616 @templatekeyword('termwidth')
606 def termwidth(repo, ctx, templ, **args):
617 def termwidth(repo, ctx, templ, **args):
607 """Integer. The width of the current terminal."""
618 """Integer. The width of the current terminal."""
608 return repo.ui.termwidth()
619 return repo.ui.termwidth()
609
620
610 @templatekeyword('troubles')
621 @templatekeyword('troubles')
611 def showtroubles(**args):
622 def showtroubles(**args):
612 """List of strings. Evolution troubles affecting the changeset.
623 """List of strings. Evolution troubles affecting the changeset.
613
624
614 (EXPERIMENTAL)
625 (EXPERIMENTAL)
615 """
626 """
616 return showlist('trouble', args['ctx'].troubles(), **args)
627 return showlist('trouble', args['ctx'].troubles(), **args)
617
628
618 # tell hggettext to extract docstrings from these functions:
629 # tell hggettext to extract docstrings from these functions:
619 i18nfunctions = keywords.values()
630 i18nfunctions = keywords.values()
General Comments 0
You need to be logged in to leave comments. Login now