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