##// END OF EJS Templates
revert: pass operation to crecord...
Jun Wu -
r30534:c01033fb default
parent child Browse files
Show More
@@ -1,3450 +1,3450 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)
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(os.sep)
734 striplen += len(os.sep)
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(os.sep)
768 striplen += len(os.sep)
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(os.sep)
773 striplen1 += len(os.sep)
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 class changeset_printer(object):
1145 class changeset_printer(object):
1146 '''show changeset information when templating not requested.'''
1146 '''show changeset information when templating not requested.'''
1147
1147
1148 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1148 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1149 self.ui = ui
1149 self.ui = ui
1150 self.repo = repo
1150 self.repo = repo
1151 self.buffered = buffered
1151 self.buffered = buffered
1152 self.matchfn = matchfn
1152 self.matchfn = matchfn
1153 self.diffopts = diffopts
1153 self.diffopts = diffopts
1154 self.header = {}
1154 self.header = {}
1155 self.hunk = {}
1155 self.hunk = {}
1156 self.lastheader = None
1156 self.lastheader = None
1157 self.footer = None
1157 self.footer = None
1158
1158
1159 def flush(self, ctx):
1159 def flush(self, ctx):
1160 rev = ctx.rev()
1160 rev = ctx.rev()
1161 if rev in self.header:
1161 if rev in self.header:
1162 h = self.header[rev]
1162 h = self.header[rev]
1163 if h != self.lastheader:
1163 if h != self.lastheader:
1164 self.lastheader = h
1164 self.lastheader = h
1165 self.ui.write(h)
1165 self.ui.write(h)
1166 del self.header[rev]
1166 del self.header[rev]
1167 if rev in self.hunk:
1167 if rev in self.hunk:
1168 self.ui.write(self.hunk[rev])
1168 self.ui.write(self.hunk[rev])
1169 del self.hunk[rev]
1169 del self.hunk[rev]
1170 return 1
1170 return 1
1171 return 0
1171 return 0
1172
1172
1173 def close(self):
1173 def close(self):
1174 if self.footer:
1174 if self.footer:
1175 self.ui.write(self.footer)
1175 self.ui.write(self.footer)
1176
1176
1177 def show(self, ctx, copies=None, matchfn=None, **props):
1177 def show(self, ctx, copies=None, matchfn=None, **props):
1178 if self.buffered:
1178 if self.buffered:
1179 self.ui.pushbuffer(labeled=True)
1179 self.ui.pushbuffer(labeled=True)
1180 self._show(ctx, copies, matchfn, props)
1180 self._show(ctx, copies, matchfn, props)
1181 self.hunk[ctx.rev()] = self.ui.popbuffer()
1181 self.hunk[ctx.rev()] = self.ui.popbuffer()
1182 else:
1182 else:
1183 self._show(ctx, copies, matchfn, props)
1183 self._show(ctx, copies, matchfn, props)
1184
1184
1185 def _show(self, ctx, copies, matchfn, props):
1185 def _show(self, ctx, copies, matchfn, props):
1186 '''show a single changeset or file revision'''
1186 '''show a single changeset or file revision'''
1187 changenode = ctx.node()
1187 changenode = ctx.node()
1188 rev = ctx.rev()
1188 rev = ctx.rev()
1189 if self.ui.debugflag:
1189 if self.ui.debugflag:
1190 hexfunc = hex
1190 hexfunc = hex
1191 else:
1191 else:
1192 hexfunc = short
1192 hexfunc = short
1193 # as of now, wctx.node() and wctx.rev() return None, but we want to
1193 # as of now, wctx.node() and wctx.rev() return None, but we want to
1194 # show the same values as {node} and {rev} templatekw
1194 # show the same values as {node} and {rev} templatekw
1195 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1195 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1196
1196
1197 if self.ui.quiet:
1197 if self.ui.quiet:
1198 self.ui.write("%d:%s\n" % revnode, label='log.node')
1198 self.ui.write("%d:%s\n" % revnode, label='log.node')
1199 return
1199 return
1200
1200
1201 date = util.datestr(ctx.date())
1201 date = util.datestr(ctx.date())
1202
1202
1203 # i18n: column positioning for "hg log"
1203 # i18n: column positioning for "hg log"
1204 self.ui.write(_("changeset: %d:%s\n") % revnode,
1204 self.ui.write(_("changeset: %d:%s\n") % revnode,
1205 label='log.changeset changeset.%s' % ctx.phasestr())
1205 label='log.changeset changeset.%s' % ctx.phasestr())
1206
1206
1207 # branches are shown first before any other names due to backwards
1207 # branches are shown first before any other names due to backwards
1208 # compatibility
1208 # compatibility
1209 branch = ctx.branch()
1209 branch = ctx.branch()
1210 # don't show the default branch name
1210 # don't show the default branch name
1211 if branch != 'default':
1211 if branch != 'default':
1212 # i18n: column positioning for "hg log"
1212 # i18n: column positioning for "hg log"
1213 self.ui.write(_("branch: %s\n") % branch,
1213 self.ui.write(_("branch: %s\n") % branch,
1214 label='log.branch')
1214 label='log.branch')
1215
1215
1216 for nsname, ns in self.repo.names.iteritems():
1216 for nsname, ns in self.repo.names.iteritems():
1217 # branches has special logic already handled above, so here we just
1217 # branches has special logic already handled above, so here we just
1218 # skip it
1218 # skip it
1219 if nsname == 'branches':
1219 if nsname == 'branches':
1220 continue
1220 continue
1221 # we will use the templatename as the color name since those two
1221 # we will use the templatename as the color name since those two
1222 # should be the same
1222 # should be the same
1223 for name in ns.names(self.repo, changenode):
1223 for name in ns.names(self.repo, changenode):
1224 self.ui.write(ns.logfmt % name,
1224 self.ui.write(ns.logfmt % name,
1225 label='log.%s' % ns.colorname)
1225 label='log.%s' % ns.colorname)
1226 if self.ui.debugflag:
1226 if self.ui.debugflag:
1227 # i18n: column positioning for "hg log"
1227 # i18n: column positioning for "hg log"
1228 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1228 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1229 label='log.phase')
1229 label='log.phase')
1230 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1230 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1231 label = 'log.parent changeset.%s' % pctx.phasestr()
1231 label = 'log.parent changeset.%s' % pctx.phasestr()
1232 # i18n: column positioning for "hg log"
1232 # i18n: column positioning for "hg log"
1233 self.ui.write(_("parent: %d:%s\n")
1233 self.ui.write(_("parent: %d:%s\n")
1234 % (pctx.rev(), hexfunc(pctx.node())),
1234 % (pctx.rev(), hexfunc(pctx.node())),
1235 label=label)
1235 label=label)
1236
1236
1237 if self.ui.debugflag and rev is not None:
1237 if self.ui.debugflag and rev is not None:
1238 mnode = ctx.manifestnode()
1238 mnode = ctx.manifestnode()
1239 # i18n: column positioning for "hg log"
1239 # i18n: column positioning for "hg log"
1240 self.ui.write(_("manifest: %d:%s\n") %
1240 self.ui.write(_("manifest: %d:%s\n") %
1241 (self.repo.manifestlog._revlog.rev(mnode),
1241 (self.repo.manifestlog._revlog.rev(mnode),
1242 hex(mnode)),
1242 hex(mnode)),
1243 label='ui.debug log.manifest')
1243 label='ui.debug log.manifest')
1244 # i18n: column positioning for "hg log"
1244 # i18n: column positioning for "hg log"
1245 self.ui.write(_("user: %s\n") % ctx.user(),
1245 self.ui.write(_("user: %s\n") % ctx.user(),
1246 label='log.user')
1246 label='log.user')
1247 # i18n: column positioning for "hg log"
1247 # i18n: column positioning for "hg log"
1248 self.ui.write(_("date: %s\n") % date,
1248 self.ui.write(_("date: %s\n") % date,
1249 label='log.date')
1249 label='log.date')
1250
1250
1251 if self.ui.debugflag:
1251 if self.ui.debugflag:
1252 files = ctx.p1().status(ctx)[:3]
1252 files = ctx.p1().status(ctx)[:3]
1253 for key, value in zip([# i18n: column positioning for "hg log"
1253 for key, value in zip([# i18n: column positioning for "hg log"
1254 _("files:"),
1254 _("files:"),
1255 # i18n: column positioning for "hg log"
1255 # i18n: column positioning for "hg log"
1256 _("files+:"),
1256 _("files+:"),
1257 # i18n: column positioning for "hg log"
1257 # i18n: column positioning for "hg log"
1258 _("files-:")], files):
1258 _("files-:")], files):
1259 if value:
1259 if value:
1260 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1260 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1261 label='ui.debug log.files')
1261 label='ui.debug log.files')
1262 elif ctx.files() and self.ui.verbose:
1262 elif ctx.files() and self.ui.verbose:
1263 # i18n: column positioning for "hg log"
1263 # i18n: column positioning for "hg log"
1264 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1264 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1265 label='ui.note log.files')
1265 label='ui.note log.files')
1266 if copies and self.ui.verbose:
1266 if copies and self.ui.verbose:
1267 copies = ['%s (%s)' % c for c in copies]
1267 copies = ['%s (%s)' % c for c in copies]
1268 # i18n: column positioning for "hg log"
1268 # i18n: column positioning for "hg log"
1269 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1269 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1270 label='ui.note log.copies')
1270 label='ui.note log.copies')
1271
1271
1272 extra = ctx.extra()
1272 extra = ctx.extra()
1273 if extra and self.ui.debugflag:
1273 if extra and self.ui.debugflag:
1274 for key, value in sorted(extra.items()):
1274 for key, value in sorted(extra.items()):
1275 # i18n: column positioning for "hg log"
1275 # i18n: column positioning for "hg log"
1276 self.ui.write(_("extra: %s=%s\n")
1276 self.ui.write(_("extra: %s=%s\n")
1277 % (key, value.encode('string_escape')),
1277 % (key, value.encode('string_escape')),
1278 label='ui.debug log.extra')
1278 label='ui.debug log.extra')
1279
1279
1280 description = ctx.description().strip()
1280 description = ctx.description().strip()
1281 if description:
1281 if description:
1282 if self.ui.verbose:
1282 if self.ui.verbose:
1283 self.ui.write(_("description:\n"),
1283 self.ui.write(_("description:\n"),
1284 label='ui.note log.description')
1284 label='ui.note log.description')
1285 self.ui.write(description,
1285 self.ui.write(description,
1286 label='ui.note log.description')
1286 label='ui.note log.description')
1287 self.ui.write("\n\n")
1287 self.ui.write("\n\n")
1288 else:
1288 else:
1289 # i18n: column positioning for "hg log"
1289 # i18n: column positioning for "hg log"
1290 self.ui.write(_("summary: %s\n") %
1290 self.ui.write(_("summary: %s\n") %
1291 description.splitlines()[0],
1291 description.splitlines()[0],
1292 label='log.summary')
1292 label='log.summary')
1293 self.ui.write("\n")
1293 self.ui.write("\n")
1294
1294
1295 self.showpatch(ctx, matchfn)
1295 self.showpatch(ctx, matchfn)
1296
1296
1297 def showpatch(self, ctx, matchfn):
1297 def showpatch(self, ctx, matchfn):
1298 if not matchfn:
1298 if not matchfn:
1299 matchfn = self.matchfn
1299 matchfn = self.matchfn
1300 if matchfn:
1300 if matchfn:
1301 stat = self.diffopts.get('stat')
1301 stat = self.diffopts.get('stat')
1302 diff = self.diffopts.get('patch')
1302 diff = self.diffopts.get('patch')
1303 diffopts = patch.diffallopts(self.ui, self.diffopts)
1303 diffopts = patch.diffallopts(self.ui, self.diffopts)
1304 node = ctx.node()
1304 node = ctx.node()
1305 prev = ctx.p1().node()
1305 prev = ctx.p1().node()
1306 if stat:
1306 if stat:
1307 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1307 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1308 match=matchfn, stat=True)
1308 match=matchfn, stat=True)
1309 if diff:
1309 if diff:
1310 if stat:
1310 if stat:
1311 self.ui.write("\n")
1311 self.ui.write("\n")
1312 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1312 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1313 match=matchfn, stat=False)
1313 match=matchfn, stat=False)
1314 self.ui.write("\n")
1314 self.ui.write("\n")
1315
1315
1316 class jsonchangeset(changeset_printer):
1316 class jsonchangeset(changeset_printer):
1317 '''format changeset information.'''
1317 '''format changeset information.'''
1318
1318
1319 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1319 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1320 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1320 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1321 self.cache = {}
1321 self.cache = {}
1322 self._first = True
1322 self._first = True
1323
1323
1324 def close(self):
1324 def close(self):
1325 if not self._first:
1325 if not self._first:
1326 self.ui.write("\n]\n")
1326 self.ui.write("\n]\n")
1327 else:
1327 else:
1328 self.ui.write("[]\n")
1328 self.ui.write("[]\n")
1329
1329
1330 def _show(self, ctx, copies, matchfn, props):
1330 def _show(self, ctx, copies, matchfn, props):
1331 '''show a single changeset or file revision'''
1331 '''show a single changeset or file revision'''
1332 rev = ctx.rev()
1332 rev = ctx.rev()
1333 if rev is None:
1333 if rev is None:
1334 jrev = jnode = 'null'
1334 jrev = jnode = 'null'
1335 else:
1335 else:
1336 jrev = str(rev)
1336 jrev = str(rev)
1337 jnode = '"%s"' % hex(ctx.node())
1337 jnode = '"%s"' % hex(ctx.node())
1338 j = encoding.jsonescape
1338 j = encoding.jsonescape
1339
1339
1340 if self._first:
1340 if self._first:
1341 self.ui.write("[\n {")
1341 self.ui.write("[\n {")
1342 self._first = False
1342 self._first = False
1343 else:
1343 else:
1344 self.ui.write(",\n {")
1344 self.ui.write(",\n {")
1345
1345
1346 if self.ui.quiet:
1346 if self.ui.quiet:
1347 self.ui.write(('\n "rev": %s') % jrev)
1347 self.ui.write(('\n "rev": %s') % jrev)
1348 self.ui.write((',\n "node": %s') % jnode)
1348 self.ui.write((',\n "node": %s') % jnode)
1349 self.ui.write('\n }')
1349 self.ui.write('\n }')
1350 return
1350 return
1351
1351
1352 self.ui.write(('\n "rev": %s') % jrev)
1352 self.ui.write(('\n "rev": %s') % jrev)
1353 self.ui.write((',\n "node": %s') % jnode)
1353 self.ui.write((',\n "node": %s') % jnode)
1354 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1354 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1355 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1355 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1356 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1356 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1357 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1357 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1358 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1358 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1359
1359
1360 self.ui.write((',\n "bookmarks": [%s]') %
1360 self.ui.write((',\n "bookmarks": [%s]') %
1361 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1361 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1362 self.ui.write((',\n "tags": [%s]') %
1362 self.ui.write((',\n "tags": [%s]') %
1363 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1363 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1364 self.ui.write((',\n "parents": [%s]') %
1364 self.ui.write((',\n "parents": [%s]') %
1365 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1365 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1366
1366
1367 if self.ui.debugflag:
1367 if self.ui.debugflag:
1368 if rev is None:
1368 if rev is None:
1369 jmanifestnode = 'null'
1369 jmanifestnode = 'null'
1370 else:
1370 else:
1371 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1371 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1372 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1372 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1373
1373
1374 self.ui.write((',\n "extra": {%s}') %
1374 self.ui.write((',\n "extra": {%s}') %
1375 ", ".join('"%s": "%s"' % (j(k), j(v))
1375 ", ".join('"%s": "%s"' % (j(k), j(v))
1376 for k, v in ctx.extra().items()))
1376 for k, v in ctx.extra().items()))
1377
1377
1378 files = ctx.p1().status(ctx)
1378 files = ctx.p1().status(ctx)
1379 self.ui.write((',\n "modified": [%s]') %
1379 self.ui.write((',\n "modified": [%s]') %
1380 ", ".join('"%s"' % j(f) for f in files[0]))
1380 ", ".join('"%s"' % j(f) for f in files[0]))
1381 self.ui.write((',\n "added": [%s]') %
1381 self.ui.write((',\n "added": [%s]') %
1382 ", ".join('"%s"' % j(f) for f in files[1]))
1382 ", ".join('"%s"' % j(f) for f in files[1]))
1383 self.ui.write((',\n "removed": [%s]') %
1383 self.ui.write((',\n "removed": [%s]') %
1384 ", ".join('"%s"' % j(f) for f in files[2]))
1384 ", ".join('"%s"' % j(f) for f in files[2]))
1385
1385
1386 elif self.ui.verbose:
1386 elif self.ui.verbose:
1387 self.ui.write((',\n "files": [%s]') %
1387 self.ui.write((',\n "files": [%s]') %
1388 ", ".join('"%s"' % j(f) for f in ctx.files()))
1388 ", ".join('"%s"' % j(f) for f in ctx.files()))
1389
1389
1390 if copies:
1390 if copies:
1391 self.ui.write((',\n "copies": {%s}') %
1391 self.ui.write((',\n "copies": {%s}') %
1392 ", ".join('"%s": "%s"' % (j(k), j(v))
1392 ", ".join('"%s": "%s"' % (j(k), j(v))
1393 for k, v in copies))
1393 for k, v in copies))
1394
1394
1395 matchfn = self.matchfn
1395 matchfn = self.matchfn
1396 if matchfn:
1396 if matchfn:
1397 stat = self.diffopts.get('stat')
1397 stat = self.diffopts.get('stat')
1398 diff = self.diffopts.get('patch')
1398 diff = self.diffopts.get('patch')
1399 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1399 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1400 node, prev = ctx.node(), ctx.p1().node()
1400 node, prev = ctx.node(), ctx.p1().node()
1401 if stat:
1401 if stat:
1402 self.ui.pushbuffer()
1402 self.ui.pushbuffer()
1403 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1403 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1404 match=matchfn, stat=True)
1404 match=matchfn, stat=True)
1405 self.ui.write((',\n "diffstat": "%s"')
1405 self.ui.write((',\n "diffstat": "%s"')
1406 % j(self.ui.popbuffer()))
1406 % j(self.ui.popbuffer()))
1407 if diff:
1407 if diff:
1408 self.ui.pushbuffer()
1408 self.ui.pushbuffer()
1409 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1409 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1410 match=matchfn, stat=False)
1410 match=matchfn, stat=False)
1411 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1411 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1412
1412
1413 self.ui.write("\n }")
1413 self.ui.write("\n }")
1414
1414
1415 class changeset_templater(changeset_printer):
1415 class changeset_templater(changeset_printer):
1416 '''format changeset information.'''
1416 '''format changeset information.'''
1417
1417
1418 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1418 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1419 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1419 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1420 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1420 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1421 filters = {'formatnode': formatnode}
1421 filters = {'formatnode': formatnode}
1422 defaulttempl = {
1422 defaulttempl = {
1423 'parent': '{rev}:{node|formatnode} ',
1423 'parent': '{rev}:{node|formatnode} ',
1424 'manifest': '{rev}:{node|formatnode}',
1424 'manifest': '{rev}:{node|formatnode}',
1425 'file_copy': '{name} ({source})',
1425 'file_copy': '{name} ({source})',
1426 'extra': '{key}={value|stringescape}'
1426 'extra': '{key}={value|stringescape}'
1427 }
1427 }
1428 # filecopy is preserved for compatibility reasons
1428 # filecopy is preserved for compatibility reasons
1429 defaulttempl['filecopy'] = defaulttempl['file_copy']
1429 defaulttempl['filecopy'] = defaulttempl['file_copy']
1430 assert not (tmpl and mapfile)
1430 assert not (tmpl and mapfile)
1431 if mapfile:
1431 if mapfile:
1432 self.t = templater.templater.frommapfile(mapfile, filters=filters,
1432 self.t = templater.templater.frommapfile(mapfile, filters=filters,
1433 cache=defaulttempl)
1433 cache=defaulttempl)
1434 else:
1434 else:
1435 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1435 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1436 filters=filters,
1436 filters=filters,
1437 cache=defaulttempl)
1437 cache=defaulttempl)
1438
1438
1439 self.cache = {}
1439 self.cache = {}
1440
1440
1441 # find correct templates for current mode
1441 # find correct templates for current mode
1442 tmplmodes = [
1442 tmplmodes = [
1443 (True, None),
1443 (True, None),
1444 (self.ui.verbose, 'verbose'),
1444 (self.ui.verbose, 'verbose'),
1445 (self.ui.quiet, 'quiet'),
1445 (self.ui.quiet, 'quiet'),
1446 (self.ui.debugflag, 'debug'),
1446 (self.ui.debugflag, 'debug'),
1447 ]
1447 ]
1448
1448
1449 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1449 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1450 'docheader': '', 'docfooter': ''}
1450 'docheader': '', 'docfooter': ''}
1451 for mode, postfix in tmplmodes:
1451 for mode, postfix in tmplmodes:
1452 for t in self._parts:
1452 for t in self._parts:
1453 cur = t
1453 cur = t
1454 if postfix:
1454 if postfix:
1455 cur += "_" + postfix
1455 cur += "_" + postfix
1456 if mode and cur in self.t:
1456 if mode and cur in self.t:
1457 self._parts[t] = cur
1457 self._parts[t] = cur
1458
1458
1459 if self._parts['docheader']:
1459 if self._parts['docheader']:
1460 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1460 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1461
1461
1462 def close(self):
1462 def close(self):
1463 if self._parts['docfooter']:
1463 if self._parts['docfooter']:
1464 if not self.footer:
1464 if not self.footer:
1465 self.footer = ""
1465 self.footer = ""
1466 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1466 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1467 return super(changeset_templater, self).close()
1467 return super(changeset_templater, self).close()
1468
1468
1469 def _show(self, ctx, copies, matchfn, props):
1469 def _show(self, ctx, copies, matchfn, props):
1470 '''show a single changeset or file revision'''
1470 '''show a single changeset or file revision'''
1471 props = props.copy()
1471 props = props.copy()
1472 props.update(templatekw.keywords)
1472 props.update(templatekw.keywords)
1473 props['templ'] = self.t
1473 props['templ'] = self.t
1474 props['ctx'] = ctx
1474 props['ctx'] = ctx
1475 props['repo'] = self.repo
1475 props['repo'] = self.repo
1476 props['ui'] = self.repo.ui
1476 props['ui'] = self.repo.ui
1477 props['revcache'] = {'copies': copies}
1477 props['revcache'] = {'copies': copies}
1478 props['cache'] = self.cache
1478 props['cache'] = self.cache
1479
1479
1480 # write header
1480 # write header
1481 if self._parts['header']:
1481 if self._parts['header']:
1482 h = templater.stringify(self.t(self._parts['header'], **props))
1482 h = templater.stringify(self.t(self._parts['header'], **props))
1483 if self.buffered:
1483 if self.buffered:
1484 self.header[ctx.rev()] = h
1484 self.header[ctx.rev()] = h
1485 else:
1485 else:
1486 if self.lastheader != h:
1486 if self.lastheader != h:
1487 self.lastheader = h
1487 self.lastheader = h
1488 self.ui.write(h)
1488 self.ui.write(h)
1489
1489
1490 # write changeset metadata, then patch if requested
1490 # write changeset metadata, then patch if requested
1491 key = self._parts['changeset']
1491 key = self._parts['changeset']
1492 self.ui.write(templater.stringify(self.t(key, **props)))
1492 self.ui.write(templater.stringify(self.t(key, **props)))
1493 self.showpatch(ctx, matchfn)
1493 self.showpatch(ctx, matchfn)
1494
1494
1495 if self._parts['footer']:
1495 if self._parts['footer']:
1496 if not self.footer:
1496 if not self.footer:
1497 self.footer = templater.stringify(
1497 self.footer = templater.stringify(
1498 self.t(self._parts['footer'], **props))
1498 self.t(self._parts['footer'], **props))
1499
1499
1500 def gettemplate(ui, tmpl, style):
1500 def gettemplate(ui, tmpl, style):
1501 """
1501 """
1502 Find the template matching the given template spec or style.
1502 Find the template matching the given template spec or style.
1503 """
1503 """
1504
1504
1505 # ui settings
1505 # ui settings
1506 if not tmpl and not style: # template are stronger than style
1506 if not tmpl and not style: # template are stronger than style
1507 tmpl = ui.config('ui', 'logtemplate')
1507 tmpl = ui.config('ui', 'logtemplate')
1508 if tmpl:
1508 if tmpl:
1509 return templater.unquotestring(tmpl), None
1509 return templater.unquotestring(tmpl), None
1510 else:
1510 else:
1511 style = util.expandpath(ui.config('ui', 'style', ''))
1511 style = util.expandpath(ui.config('ui', 'style', ''))
1512
1512
1513 if not tmpl and style:
1513 if not tmpl and style:
1514 mapfile = style
1514 mapfile = style
1515 if not os.path.split(mapfile)[0]:
1515 if not os.path.split(mapfile)[0]:
1516 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1516 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1517 or templater.templatepath(mapfile))
1517 or templater.templatepath(mapfile))
1518 if mapname:
1518 if mapname:
1519 mapfile = mapname
1519 mapfile = mapname
1520 return None, mapfile
1520 return None, mapfile
1521
1521
1522 if not tmpl:
1522 if not tmpl:
1523 return None, None
1523 return None, None
1524
1524
1525 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1525 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1526
1526
1527 def show_changeset(ui, repo, opts, buffered=False):
1527 def show_changeset(ui, repo, opts, buffered=False):
1528 """show one changeset using template or regular display.
1528 """show one changeset using template or regular display.
1529
1529
1530 Display format will be the first non-empty hit of:
1530 Display format will be the first non-empty hit of:
1531 1. option 'template'
1531 1. option 'template'
1532 2. option 'style'
1532 2. option 'style'
1533 3. [ui] setting 'logtemplate'
1533 3. [ui] setting 'logtemplate'
1534 4. [ui] setting 'style'
1534 4. [ui] setting 'style'
1535 If all of these values are either the unset or the empty string,
1535 If all of these values are either the unset or the empty string,
1536 regular display via changeset_printer() is done.
1536 regular display via changeset_printer() is done.
1537 """
1537 """
1538 # options
1538 # options
1539 matchfn = None
1539 matchfn = None
1540 if opts.get('patch') or opts.get('stat'):
1540 if opts.get('patch') or opts.get('stat'):
1541 matchfn = scmutil.matchall(repo)
1541 matchfn = scmutil.matchall(repo)
1542
1542
1543 if opts.get('template') == 'json':
1543 if opts.get('template') == 'json':
1544 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1544 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1545
1545
1546 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1546 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1547
1547
1548 if not tmpl and not mapfile:
1548 if not tmpl and not mapfile:
1549 return changeset_printer(ui, repo, matchfn, opts, buffered)
1549 return changeset_printer(ui, repo, matchfn, opts, buffered)
1550
1550
1551 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1551 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1552
1552
1553 def showmarker(fm, marker, index=None):
1553 def showmarker(fm, marker, index=None):
1554 """utility function to display obsolescence marker in a readable way
1554 """utility function to display obsolescence marker in a readable way
1555
1555
1556 To be used by debug function."""
1556 To be used by debug function."""
1557 if index is not None:
1557 if index is not None:
1558 fm.write('index', '%i ', index)
1558 fm.write('index', '%i ', index)
1559 fm.write('precnode', '%s ', hex(marker.precnode()))
1559 fm.write('precnode', '%s ', hex(marker.precnode()))
1560 succs = marker.succnodes()
1560 succs = marker.succnodes()
1561 fm.condwrite(succs, 'succnodes', '%s ',
1561 fm.condwrite(succs, 'succnodes', '%s ',
1562 fm.formatlist(map(hex, succs), name='node'))
1562 fm.formatlist(map(hex, succs), name='node'))
1563 fm.write('flag', '%X ', marker.flags())
1563 fm.write('flag', '%X ', marker.flags())
1564 parents = marker.parentnodes()
1564 parents = marker.parentnodes()
1565 if parents is not None:
1565 if parents is not None:
1566 fm.write('parentnodes', '{%s} ',
1566 fm.write('parentnodes', '{%s} ',
1567 fm.formatlist(map(hex, parents), name='node', sep=', '))
1567 fm.formatlist(map(hex, parents), name='node', sep=', '))
1568 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1568 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1569 meta = marker.metadata().copy()
1569 meta = marker.metadata().copy()
1570 meta.pop('date', None)
1570 meta.pop('date', None)
1571 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1571 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1572 fm.plain('\n')
1572 fm.plain('\n')
1573
1573
1574 def finddate(ui, repo, date):
1574 def finddate(ui, repo, date):
1575 """Find the tipmost changeset that matches the given date spec"""
1575 """Find the tipmost changeset that matches the given date spec"""
1576
1576
1577 df = util.matchdate(date)
1577 df = util.matchdate(date)
1578 m = scmutil.matchall(repo)
1578 m = scmutil.matchall(repo)
1579 results = {}
1579 results = {}
1580
1580
1581 def prep(ctx, fns):
1581 def prep(ctx, fns):
1582 d = ctx.date()
1582 d = ctx.date()
1583 if df(d[0]):
1583 if df(d[0]):
1584 results[ctx.rev()] = d
1584 results[ctx.rev()] = d
1585
1585
1586 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1586 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1587 rev = ctx.rev()
1587 rev = ctx.rev()
1588 if rev in results:
1588 if rev in results:
1589 ui.status(_("found revision %s from %s\n") %
1589 ui.status(_("found revision %s from %s\n") %
1590 (rev, util.datestr(results[rev])))
1590 (rev, util.datestr(results[rev])))
1591 return str(rev)
1591 return str(rev)
1592
1592
1593 raise error.Abort(_("revision matching date not found"))
1593 raise error.Abort(_("revision matching date not found"))
1594
1594
1595 def increasingwindows(windowsize=8, sizelimit=512):
1595 def increasingwindows(windowsize=8, sizelimit=512):
1596 while True:
1596 while True:
1597 yield windowsize
1597 yield windowsize
1598 if windowsize < sizelimit:
1598 if windowsize < sizelimit:
1599 windowsize *= 2
1599 windowsize *= 2
1600
1600
1601 class FileWalkError(Exception):
1601 class FileWalkError(Exception):
1602 pass
1602 pass
1603
1603
1604 def walkfilerevs(repo, match, follow, revs, fncache):
1604 def walkfilerevs(repo, match, follow, revs, fncache):
1605 '''Walks the file history for the matched files.
1605 '''Walks the file history for the matched files.
1606
1606
1607 Returns the changeset revs that are involved in the file history.
1607 Returns the changeset revs that are involved in the file history.
1608
1608
1609 Throws FileWalkError if the file history can't be walked using
1609 Throws FileWalkError if the file history can't be walked using
1610 filelogs alone.
1610 filelogs alone.
1611 '''
1611 '''
1612 wanted = set()
1612 wanted = set()
1613 copies = []
1613 copies = []
1614 minrev, maxrev = min(revs), max(revs)
1614 minrev, maxrev = min(revs), max(revs)
1615 def filerevgen(filelog, last):
1615 def filerevgen(filelog, last):
1616 """
1616 """
1617 Only files, no patterns. Check the history of each file.
1617 Only files, no patterns. Check the history of each file.
1618
1618
1619 Examines filelog entries within minrev, maxrev linkrev range
1619 Examines filelog entries within minrev, maxrev linkrev range
1620 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1620 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1621 tuples in backwards order
1621 tuples in backwards order
1622 """
1622 """
1623 cl_count = len(repo)
1623 cl_count = len(repo)
1624 revs = []
1624 revs = []
1625 for j in xrange(0, last + 1):
1625 for j in xrange(0, last + 1):
1626 linkrev = filelog.linkrev(j)
1626 linkrev = filelog.linkrev(j)
1627 if linkrev < minrev:
1627 if linkrev < minrev:
1628 continue
1628 continue
1629 # only yield rev for which we have the changelog, it can
1629 # only yield rev for which we have the changelog, it can
1630 # happen while doing "hg log" during a pull or commit
1630 # happen while doing "hg log" during a pull or commit
1631 if linkrev >= cl_count:
1631 if linkrev >= cl_count:
1632 break
1632 break
1633
1633
1634 parentlinkrevs = []
1634 parentlinkrevs = []
1635 for p in filelog.parentrevs(j):
1635 for p in filelog.parentrevs(j):
1636 if p != nullrev:
1636 if p != nullrev:
1637 parentlinkrevs.append(filelog.linkrev(p))
1637 parentlinkrevs.append(filelog.linkrev(p))
1638 n = filelog.node(j)
1638 n = filelog.node(j)
1639 revs.append((linkrev, parentlinkrevs,
1639 revs.append((linkrev, parentlinkrevs,
1640 follow and filelog.renamed(n)))
1640 follow and filelog.renamed(n)))
1641
1641
1642 return reversed(revs)
1642 return reversed(revs)
1643 def iterfiles():
1643 def iterfiles():
1644 pctx = repo['.']
1644 pctx = repo['.']
1645 for filename in match.files():
1645 for filename in match.files():
1646 if follow:
1646 if follow:
1647 if filename not in pctx:
1647 if filename not in pctx:
1648 raise error.Abort(_('cannot follow file not in parent '
1648 raise error.Abort(_('cannot follow file not in parent '
1649 'revision: "%s"') % filename)
1649 'revision: "%s"') % filename)
1650 yield filename, pctx[filename].filenode()
1650 yield filename, pctx[filename].filenode()
1651 else:
1651 else:
1652 yield filename, None
1652 yield filename, None
1653 for filename_node in copies:
1653 for filename_node in copies:
1654 yield filename_node
1654 yield filename_node
1655
1655
1656 for file_, node in iterfiles():
1656 for file_, node in iterfiles():
1657 filelog = repo.file(file_)
1657 filelog = repo.file(file_)
1658 if not len(filelog):
1658 if not len(filelog):
1659 if node is None:
1659 if node is None:
1660 # A zero count may be a directory or deleted file, so
1660 # A zero count may be a directory or deleted file, so
1661 # try to find matching entries on the slow path.
1661 # try to find matching entries on the slow path.
1662 if follow:
1662 if follow:
1663 raise error.Abort(
1663 raise error.Abort(
1664 _('cannot follow nonexistent file: "%s"') % file_)
1664 _('cannot follow nonexistent file: "%s"') % file_)
1665 raise FileWalkError("Cannot walk via filelog")
1665 raise FileWalkError("Cannot walk via filelog")
1666 else:
1666 else:
1667 continue
1667 continue
1668
1668
1669 if node is None:
1669 if node is None:
1670 last = len(filelog) - 1
1670 last = len(filelog) - 1
1671 else:
1671 else:
1672 last = filelog.rev(node)
1672 last = filelog.rev(node)
1673
1673
1674 # keep track of all ancestors of the file
1674 # keep track of all ancestors of the file
1675 ancestors = set([filelog.linkrev(last)])
1675 ancestors = set([filelog.linkrev(last)])
1676
1676
1677 # iterate from latest to oldest revision
1677 # iterate from latest to oldest revision
1678 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1678 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1679 if not follow:
1679 if not follow:
1680 if rev > maxrev:
1680 if rev > maxrev:
1681 continue
1681 continue
1682 else:
1682 else:
1683 # Note that last might not be the first interesting
1683 # Note that last might not be the first interesting
1684 # rev to us:
1684 # rev to us:
1685 # if the file has been changed after maxrev, we'll
1685 # if the file has been changed after maxrev, we'll
1686 # have linkrev(last) > maxrev, and we still need
1686 # have linkrev(last) > maxrev, and we still need
1687 # to explore the file graph
1687 # to explore the file graph
1688 if rev not in ancestors:
1688 if rev not in ancestors:
1689 continue
1689 continue
1690 # XXX insert 1327 fix here
1690 # XXX insert 1327 fix here
1691 if flparentlinkrevs:
1691 if flparentlinkrevs:
1692 ancestors.update(flparentlinkrevs)
1692 ancestors.update(flparentlinkrevs)
1693
1693
1694 fncache.setdefault(rev, []).append(file_)
1694 fncache.setdefault(rev, []).append(file_)
1695 wanted.add(rev)
1695 wanted.add(rev)
1696 if copied:
1696 if copied:
1697 copies.append(copied)
1697 copies.append(copied)
1698
1698
1699 return wanted
1699 return wanted
1700
1700
1701 class _followfilter(object):
1701 class _followfilter(object):
1702 def __init__(self, repo, onlyfirst=False):
1702 def __init__(self, repo, onlyfirst=False):
1703 self.repo = repo
1703 self.repo = repo
1704 self.startrev = nullrev
1704 self.startrev = nullrev
1705 self.roots = set()
1705 self.roots = set()
1706 self.onlyfirst = onlyfirst
1706 self.onlyfirst = onlyfirst
1707
1707
1708 def match(self, rev):
1708 def match(self, rev):
1709 def realparents(rev):
1709 def realparents(rev):
1710 if self.onlyfirst:
1710 if self.onlyfirst:
1711 return self.repo.changelog.parentrevs(rev)[0:1]
1711 return self.repo.changelog.parentrevs(rev)[0:1]
1712 else:
1712 else:
1713 return filter(lambda x: x != nullrev,
1713 return filter(lambda x: x != nullrev,
1714 self.repo.changelog.parentrevs(rev))
1714 self.repo.changelog.parentrevs(rev))
1715
1715
1716 if self.startrev == nullrev:
1716 if self.startrev == nullrev:
1717 self.startrev = rev
1717 self.startrev = rev
1718 return True
1718 return True
1719
1719
1720 if rev > self.startrev:
1720 if rev > self.startrev:
1721 # forward: all descendants
1721 # forward: all descendants
1722 if not self.roots:
1722 if not self.roots:
1723 self.roots.add(self.startrev)
1723 self.roots.add(self.startrev)
1724 for parent in realparents(rev):
1724 for parent in realparents(rev):
1725 if parent in self.roots:
1725 if parent in self.roots:
1726 self.roots.add(rev)
1726 self.roots.add(rev)
1727 return True
1727 return True
1728 else:
1728 else:
1729 # backwards: all parents
1729 # backwards: all parents
1730 if not self.roots:
1730 if not self.roots:
1731 self.roots.update(realparents(self.startrev))
1731 self.roots.update(realparents(self.startrev))
1732 if rev in self.roots:
1732 if rev in self.roots:
1733 self.roots.remove(rev)
1733 self.roots.remove(rev)
1734 self.roots.update(realparents(rev))
1734 self.roots.update(realparents(rev))
1735 return True
1735 return True
1736
1736
1737 return False
1737 return False
1738
1738
1739 def walkchangerevs(repo, match, opts, prepare):
1739 def walkchangerevs(repo, match, opts, prepare):
1740 '''Iterate over files and the revs in which they changed.
1740 '''Iterate over files and the revs in which they changed.
1741
1741
1742 Callers most commonly need to iterate backwards over the history
1742 Callers most commonly need to iterate backwards over the history
1743 in which they are interested. Doing so has awful (quadratic-looking)
1743 in which they are interested. Doing so has awful (quadratic-looking)
1744 performance, so we use iterators in a "windowed" way.
1744 performance, so we use iterators in a "windowed" way.
1745
1745
1746 We walk a window of revisions in the desired order. Within the
1746 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
1747 window, we first walk forwards to gather data, then in the desired
1748 order (usually backwards) to display it.
1748 order (usually backwards) to display it.
1749
1749
1750 This function returns an iterator yielding contexts. Before
1750 This function returns an iterator yielding contexts. Before
1751 yielding each context, the iterator will first call the prepare
1751 yielding each context, the iterator will first call the prepare
1752 function on each context in the window in forward order.'''
1752 function on each context in the window in forward order.'''
1753
1753
1754 follow = opts.get('follow') or opts.get('follow_first')
1754 follow = opts.get('follow') or opts.get('follow_first')
1755 revs = _logrevs(repo, opts)
1755 revs = _logrevs(repo, opts)
1756 if not revs:
1756 if not revs:
1757 return []
1757 return []
1758 wanted = set()
1758 wanted = set()
1759 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1759 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1760 opts.get('removed'))
1760 opts.get('removed'))
1761 fncache = {}
1761 fncache = {}
1762 change = repo.changectx
1762 change = repo.changectx
1763
1763
1764 # First step is to fill wanted, the set of revisions that we want to yield.
1764 # 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
1765 # 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
1766 # wanted: a cache of filenames that were changed (ctx.files()) and that
1767 # match the file filtering conditions.
1767 # match the file filtering conditions.
1768
1768
1769 if match.always():
1769 if match.always():
1770 # No files, no patterns. Display all revs.
1770 # No files, no patterns. Display all revs.
1771 wanted = revs
1771 wanted = revs
1772 elif not slowpath:
1772 elif not slowpath:
1773 # We only have to read through the filelog to find wanted revisions
1773 # We only have to read through the filelog to find wanted revisions
1774
1774
1775 try:
1775 try:
1776 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1776 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1777 except FileWalkError:
1777 except FileWalkError:
1778 slowpath = True
1778 slowpath = True
1779
1779
1780 # We decided to fall back to the slowpath because at least one
1780 # 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
1781 # of the paths was not a file. Check to see if at least one of them
1782 # existed in history, otherwise simply return
1782 # existed in history, otherwise simply return
1783 for path in match.files():
1783 for path in match.files():
1784 if path == '.' or path in repo.store:
1784 if path == '.' or path in repo.store:
1785 break
1785 break
1786 else:
1786 else:
1787 return []
1787 return []
1788
1788
1789 if slowpath:
1789 if slowpath:
1790 # We have to read the changelog to match filenames against
1790 # We have to read the changelog to match filenames against
1791 # changed files
1791 # changed files
1792
1792
1793 if follow:
1793 if follow:
1794 raise error.Abort(_('can only follow copies/renames for explicit '
1794 raise error.Abort(_('can only follow copies/renames for explicit '
1795 'filenames'))
1795 'filenames'))
1796
1796
1797 # The slow path checks files modified in every changeset.
1797 # The slow path checks files modified in every changeset.
1798 # This is really slow on large repos, so compute the set lazily.
1798 # This is really slow on large repos, so compute the set lazily.
1799 class lazywantedset(object):
1799 class lazywantedset(object):
1800 def __init__(self):
1800 def __init__(self):
1801 self.set = set()
1801 self.set = set()
1802 self.revs = set(revs)
1802 self.revs = set(revs)
1803
1803
1804 # No need to worry about locality here because it will be accessed
1804 # No need to worry about locality here because it will be accessed
1805 # in the same order as the increasing window below.
1805 # in the same order as the increasing window below.
1806 def __contains__(self, value):
1806 def __contains__(self, value):
1807 if value in self.set:
1807 if value in self.set:
1808 return True
1808 return True
1809 elif not value in self.revs:
1809 elif not value in self.revs:
1810 return False
1810 return False
1811 else:
1811 else:
1812 self.revs.discard(value)
1812 self.revs.discard(value)
1813 ctx = change(value)
1813 ctx = change(value)
1814 matches = filter(match, ctx.files())
1814 matches = filter(match, ctx.files())
1815 if matches:
1815 if matches:
1816 fncache[value] = matches
1816 fncache[value] = matches
1817 self.set.add(value)
1817 self.set.add(value)
1818 return True
1818 return True
1819 return False
1819 return False
1820
1820
1821 def discard(self, value):
1821 def discard(self, value):
1822 self.revs.discard(value)
1822 self.revs.discard(value)
1823 self.set.discard(value)
1823 self.set.discard(value)
1824
1824
1825 wanted = lazywantedset()
1825 wanted = lazywantedset()
1826
1826
1827 # it might be worthwhile to do this in the iterator if the rev range
1827 # 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
1828 # is descending and the prune args are all within that range
1829 for rev in opts.get('prune', ()):
1829 for rev in opts.get('prune', ()):
1830 rev = repo[rev].rev()
1830 rev = repo[rev].rev()
1831 ff = _followfilter(repo)
1831 ff = _followfilter(repo)
1832 stop = min(revs[0], revs[-1])
1832 stop = min(revs[0], revs[-1])
1833 for x in xrange(rev, stop - 1, -1):
1833 for x in xrange(rev, stop - 1, -1):
1834 if ff.match(x):
1834 if ff.match(x):
1835 wanted = wanted - [x]
1835 wanted = wanted - [x]
1836
1836
1837 # Now that wanted is correctly initialized, we can iterate over the
1837 # Now that wanted is correctly initialized, we can iterate over the
1838 # revision range, yielding only revisions in wanted.
1838 # revision range, yielding only revisions in wanted.
1839 def iterate():
1839 def iterate():
1840 if follow and match.always():
1840 if follow and match.always():
1841 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1841 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1842 def want(rev):
1842 def want(rev):
1843 return ff.match(rev) and rev in wanted
1843 return ff.match(rev) and rev in wanted
1844 else:
1844 else:
1845 def want(rev):
1845 def want(rev):
1846 return rev in wanted
1846 return rev in wanted
1847
1847
1848 it = iter(revs)
1848 it = iter(revs)
1849 stopiteration = False
1849 stopiteration = False
1850 for windowsize in increasingwindows():
1850 for windowsize in increasingwindows():
1851 nrevs = []
1851 nrevs = []
1852 for i in xrange(windowsize):
1852 for i in xrange(windowsize):
1853 rev = next(it, None)
1853 rev = next(it, None)
1854 if rev is None:
1854 if rev is None:
1855 stopiteration = True
1855 stopiteration = True
1856 break
1856 break
1857 elif want(rev):
1857 elif want(rev):
1858 nrevs.append(rev)
1858 nrevs.append(rev)
1859 for rev in sorted(nrevs):
1859 for rev in sorted(nrevs):
1860 fns = fncache.get(rev)
1860 fns = fncache.get(rev)
1861 ctx = change(rev)
1861 ctx = change(rev)
1862 if not fns:
1862 if not fns:
1863 def fns_generator():
1863 def fns_generator():
1864 for f in ctx.files():
1864 for f in ctx.files():
1865 if match(f):
1865 if match(f):
1866 yield f
1866 yield f
1867 fns = fns_generator()
1867 fns = fns_generator()
1868 prepare(ctx, fns)
1868 prepare(ctx, fns)
1869 for rev in nrevs:
1869 for rev in nrevs:
1870 yield change(rev)
1870 yield change(rev)
1871
1871
1872 if stopiteration:
1872 if stopiteration:
1873 break
1873 break
1874
1874
1875 return iterate()
1875 return iterate()
1876
1876
1877 def _makefollowlogfilematcher(repo, files, followfirst):
1877 def _makefollowlogfilematcher(repo, files, followfirst):
1878 # When displaying a revision with --patch --follow FILE, we have
1878 # When displaying a revision with --patch --follow FILE, we have
1879 # to know which file of the revision must be diffed. With
1879 # to know which file of the revision must be diffed. With
1880 # --follow, we want the names of the ancestors of FILE in the
1880 # --follow, we want the names of the ancestors of FILE in the
1881 # revision, stored in "fcache". "fcache" is populated by
1881 # revision, stored in "fcache". "fcache" is populated by
1882 # reproducing the graph traversal already done by --follow revset
1882 # reproducing the graph traversal already done by --follow revset
1883 # and relating revs to file names (which is not "correct" but
1883 # and relating revs to file names (which is not "correct" but
1884 # good enough).
1884 # good enough).
1885 fcache = {}
1885 fcache = {}
1886 fcacheready = [False]
1886 fcacheready = [False]
1887 pctx = repo['.']
1887 pctx = repo['.']
1888
1888
1889 def populate():
1889 def populate():
1890 for fn in files:
1890 for fn in files:
1891 fctx = pctx[fn]
1891 fctx = pctx[fn]
1892 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1892 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
1893 for c in fctx.ancestors(followfirst=followfirst):
1893 for c in fctx.ancestors(followfirst=followfirst):
1894 fcache.setdefault(c.rev(), set()).add(c.path())
1894 fcache.setdefault(c.rev(), set()).add(c.path())
1895
1895
1896 def filematcher(rev):
1896 def filematcher(rev):
1897 if not fcacheready[0]:
1897 if not fcacheready[0]:
1898 # Lazy initialization
1898 # Lazy initialization
1899 fcacheready[0] = True
1899 fcacheready[0] = True
1900 populate()
1900 populate()
1901 return scmutil.matchfiles(repo, fcache.get(rev, []))
1901 return scmutil.matchfiles(repo, fcache.get(rev, []))
1902
1902
1903 return filematcher
1903 return filematcher
1904
1904
1905 def _makenofollowlogfilematcher(repo, pats, opts):
1905 def _makenofollowlogfilematcher(repo, pats, opts):
1906 '''hook for extensions to override the filematcher for non-follow cases'''
1906 '''hook for extensions to override the filematcher for non-follow cases'''
1907 return None
1907 return None
1908
1908
1909 def _makelogrevset(repo, pats, opts, revs):
1909 def _makelogrevset(repo, pats, opts, revs):
1910 """Return (expr, filematcher) where expr is a revset string built
1910 """Return (expr, filematcher) where expr is a revset string built
1911 from log options and file patterns or None. If --stat or --patch
1911 from log options and file patterns or None. If --stat or --patch
1912 are not passed filematcher is None. Otherwise it is a callable
1912 are not passed filematcher is None. Otherwise it is a callable
1913 taking a revision number and returning a match objects filtering
1913 taking a revision number and returning a match objects filtering
1914 the files to be detailed when displaying the revision.
1914 the files to be detailed when displaying the revision.
1915 """
1915 """
1916 opt2revset = {
1916 opt2revset = {
1917 'no_merges': ('not merge()', None),
1917 'no_merges': ('not merge()', None),
1918 'only_merges': ('merge()', None),
1918 'only_merges': ('merge()', None),
1919 '_ancestors': ('ancestors(%(val)s)', None),
1919 '_ancestors': ('ancestors(%(val)s)', None),
1920 '_fancestors': ('_firstancestors(%(val)s)', None),
1920 '_fancestors': ('_firstancestors(%(val)s)', None),
1921 '_descendants': ('descendants(%(val)s)', None),
1921 '_descendants': ('descendants(%(val)s)', None),
1922 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1922 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1923 '_matchfiles': ('_matchfiles(%(val)s)', None),
1923 '_matchfiles': ('_matchfiles(%(val)s)', None),
1924 'date': ('date(%(val)r)', None),
1924 'date': ('date(%(val)r)', None),
1925 'branch': ('branch(%(val)r)', ' or '),
1925 'branch': ('branch(%(val)r)', ' or '),
1926 '_patslog': ('filelog(%(val)r)', ' or '),
1926 '_patslog': ('filelog(%(val)r)', ' or '),
1927 '_patsfollow': ('follow(%(val)r)', ' or '),
1927 '_patsfollow': ('follow(%(val)r)', ' or '),
1928 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1928 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1929 'keyword': ('keyword(%(val)r)', ' or '),
1929 'keyword': ('keyword(%(val)r)', ' or '),
1930 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1930 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1931 'user': ('user(%(val)r)', ' or '),
1931 'user': ('user(%(val)r)', ' or '),
1932 }
1932 }
1933
1933
1934 opts = dict(opts)
1934 opts = dict(opts)
1935 # follow or not follow?
1935 # follow or not follow?
1936 follow = opts.get('follow') or opts.get('follow_first')
1936 follow = opts.get('follow') or opts.get('follow_first')
1937 if opts.get('follow_first'):
1937 if opts.get('follow_first'):
1938 followfirst = 1
1938 followfirst = 1
1939 else:
1939 else:
1940 followfirst = 0
1940 followfirst = 0
1941 # --follow with FILE behavior depends on revs...
1941 # --follow with FILE behavior depends on revs...
1942 it = iter(revs)
1942 it = iter(revs)
1943 startrev = next(it)
1943 startrev = next(it)
1944 followdescendants = startrev < next(it, startrev)
1944 followdescendants = startrev < next(it, startrev)
1945
1945
1946 # branch and only_branch are really aliases and must be handled at
1946 # branch and only_branch are really aliases and must be handled at
1947 # the same time
1947 # the same time
1948 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1948 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1949 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1949 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1950 # pats/include/exclude are passed to match.match() directly in
1950 # pats/include/exclude are passed to match.match() directly in
1951 # _matchfiles() revset but walkchangerevs() builds its matcher with
1951 # _matchfiles() revset but walkchangerevs() builds its matcher with
1952 # scmutil.match(). The difference is input pats are globbed on
1952 # scmutil.match(). The difference is input pats are globbed on
1953 # platforms without shell expansion (windows).
1953 # platforms without shell expansion (windows).
1954 wctx = repo[None]
1954 wctx = repo[None]
1955 match, pats = scmutil.matchandpats(wctx, pats, opts)
1955 match, pats = scmutil.matchandpats(wctx, pats, opts)
1956 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1956 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1957 opts.get('removed'))
1957 opts.get('removed'))
1958 if not slowpath:
1958 if not slowpath:
1959 for f in match.files():
1959 for f in match.files():
1960 if follow and f not in wctx:
1960 if follow and f not in wctx:
1961 # If the file exists, it may be a directory, so let it
1961 # If the file exists, it may be a directory, so let it
1962 # take the slow path.
1962 # take the slow path.
1963 if os.path.exists(repo.wjoin(f)):
1963 if os.path.exists(repo.wjoin(f)):
1964 slowpath = True
1964 slowpath = True
1965 continue
1965 continue
1966 else:
1966 else:
1967 raise error.Abort(_('cannot follow file not in parent '
1967 raise error.Abort(_('cannot follow file not in parent '
1968 'revision: "%s"') % f)
1968 'revision: "%s"') % f)
1969 filelog = repo.file(f)
1969 filelog = repo.file(f)
1970 if not filelog:
1970 if not filelog:
1971 # A zero count may be a directory or deleted file, so
1971 # A zero count may be a directory or deleted file, so
1972 # try to find matching entries on the slow path.
1972 # try to find matching entries on the slow path.
1973 if follow:
1973 if follow:
1974 raise error.Abort(
1974 raise error.Abort(
1975 _('cannot follow nonexistent file: "%s"') % f)
1975 _('cannot follow nonexistent file: "%s"') % f)
1976 slowpath = True
1976 slowpath = True
1977
1977
1978 # We decided to fall back to the slowpath because at least one
1978 # 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
1979 # 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
1980 # existed in history - in that case, we'll continue down the
1981 # slowpath; otherwise, we can turn off the slowpath
1981 # slowpath; otherwise, we can turn off the slowpath
1982 if slowpath:
1982 if slowpath:
1983 for path in match.files():
1983 for path in match.files():
1984 if path == '.' or path in repo.store:
1984 if path == '.' or path in repo.store:
1985 break
1985 break
1986 else:
1986 else:
1987 slowpath = False
1987 slowpath = False
1988
1988
1989 fpats = ('_patsfollow', '_patsfollowfirst')
1989 fpats = ('_patsfollow', '_patsfollowfirst')
1990 fnopats = (('_ancestors', '_fancestors'),
1990 fnopats = (('_ancestors', '_fancestors'),
1991 ('_descendants', '_fdescendants'))
1991 ('_descendants', '_fdescendants'))
1992 if slowpath:
1992 if slowpath:
1993 # See walkchangerevs() slow path.
1993 # See walkchangerevs() slow path.
1994 #
1994 #
1995 # pats/include/exclude cannot be represented as separate
1995 # pats/include/exclude cannot be represented as separate
1996 # revset expressions as their filtering logic applies at file
1996 # revset expressions as their filtering logic applies at file
1997 # level. For instance "-I a -X a" matches a revision touching
1997 # level. For instance "-I a -X a" matches a revision touching
1998 # "a" and "b" while "file(a) and not file(b)" does
1998 # "a" and "b" while "file(a) and not file(b)" does
1999 # not. Besides, filesets are evaluated against the working
1999 # not. Besides, filesets are evaluated against the working
2000 # directory.
2000 # directory.
2001 matchargs = ['r:', 'd:relpath']
2001 matchargs = ['r:', 'd:relpath']
2002 for p in pats:
2002 for p in pats:
2003 matchargs.append('p:' + p)
2003 matchargs.append('p:' + p)
2004 for p in opts.get('include', []):
2004 for p in opts.get('include', []):
2005 matchargs.append('i:' + p)
2005 matchargs.append('i:' + p)
2006 for p in opts.get('exclude', []):
2006 for p in opts.get('exclude', []):
2007 matchargs.append('x:' + p)
2007 matchargs.append('x:' + p)
2008 matchargs = ','.join(('%r' % p) for p in matchargs)
2008 matchargs = ','.join(('%r' % p) for p in matchargs)
2009 opts['_matchfiles'] = matchargs
2009 opts['_matchfiles'] = matchargs
2010 if follow:
2010 if follow:
2011 opts[fnopats[0][followfirst]] = '.'
2011 opts[fnopats[0][followfirst]] = '.'
2012 else:
2012 else:
2013 if follow:
2013 if follow:
2014 if pats:
2014 if pats:
2015 # follow() revset interprets its file argument as a
2015 # follow() revset interprets its file argument as a
2016 # manifest entry, so use match.files(), not pats.
2016 # manifest entry, so use match.files(), not pats.
2017 opts[fpats[followfirst]] = list(match.files())
2017 opts[fpats[followfirst]] = list(match.files())
2018 else:
2018 else:
2019 op = fnopats[followdescendants][followfirst]
2019 op = fnopats[followdescendants][followfirst]
2020 opts[op] = 'rev(%d)' % startrev
2020 opts[op] = 'rev(%d)' % startrev
2021 else:
2021 else:
2022 opts['_patslog'] = list(pats)
2022 opts['_patslog'] = list(pats)
2023
2023
2024 filematcher = None
2024 filematcher = None
2025 if opts.get('patch') or opts.get('stat'):
2025 if opts.get('patch') or opts.get('stat'):
2026 # When following files, track renames via a special matcher.
2026 # When following files, track renames via a special matcher.
2027 # If we're forced to take the slowpath it means we're following
2027 # 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.
2028 # at least one pattern/directory, so don't bother with rename tracking.
2029 if follow and not match.always() and not slowpath:
2029 if follow and not match.always() and not slowpath:
2030 # _makefollowlogfilematcher expects its files argument to be
2030 # _makefollowlogfilematcher expects its files argument to be
2031 # relative to the repo root, so use match.files(), not pats.
2031 # relative to the repo root, so use match.files(), not pats.
2032 filematcher = _makefollowlogfilematcher(repo, match.files(),
2032 filematcher = _makefollowlogfilematcher(repo, match.files(),
2033 followfirst)
2033 followfirst)
2034 else:
2034 else:
2035 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2035 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2036 if filematcher is None:
2036 if filematcher is None:
2037 filematcher = lambda rev: match
2037 filematcher = lambda rev: match
2038
2038
2039 expr = []
2039 expr = []
2040 for op, val in sorted(opts.iteritems()):
2040 for op, val in sorted(opts.iteritems()):
2041 if not val:
2041 if not val:
2042 continue
2042 continue
2043 if op not in opt2revset:
2043 if op not in opt2revset:
2044 continue
2044 continue
2045 revop, andor = opt2revset[op]
2045 revop, andor = opt2revset[op]
2046 if '%(val)' not in revop:
2046 if '%(val)' not in revop:
2047 expr.append(revop)
2047 expr.append(revop)
2048 else:
2048 else:
2049 if not isinstance(val, list):
2049 if not isinstance(val, list):
2050 e = revop % {'val': val}
2050 e = revop % {'val': val}
2051 else:
2051 else:
2052 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2052 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2053 expr.append(e)
2053 expr.append(e)
2054
2054
2055 if expr:
2055 if expr:
2056 expr = '(' + ' and '.join(expr) + ')'
2056 expr = '(' + ' and '.join(expr) + ')'
2057 else:
2057 else:
2058 expr = None
2058 expr = None
2059 return expr, filematcher
2059 return expr, filematcher
2060
2060
2061 def _logrevs(repo, opts):
2061 def _logrevs(repo, opts):
2062 # Default --rev value depends on --follow but --follow behavior
2062 # Default --rev value depends on --follow but --follow behavior
2063 # depends on revisions resolved from --rev...
2063 # depends on revisions resolved from --rev...
2064 follow = opts.get('follow') or opts.get('follow_first')
2064 follow = opts.get('follow') or opts.get('follow_first')
2065 if opts.get('rev'):
2065 if opts.get('rev'):
2066 revs = scmutil.revrange(repo, opts['rev'])
2066 revs = scmutil.revrange(repo, opts['rev'])
2067 elif follow and repo.dirstate.p1() == nullid:
2067 elif follow and repo.dirstate.p1() == nullid:
2068 revs = revset.baseset()
2068 revs = revset.baseset()
2069 elif follow:
2069 elif follow:
2070 revs = repo.revs('reverse(:.)')
2070 revs = repo.revs('reverse(:.)')
2071 else:
2071 else:
2072 revs = revset.spanset(repo)
2072 revs = revset.spanset(repo)
2073 revs.reverse()
2073 revs.reverse()
2074 return revs
2074 return revs
2075
2075
2076 def getgraphlogrevs(repo, pats, opts):
2076 def getgraphlogrevs(repo, pats, opts):
2077 """Return (revs, expr, filematcher) where revs is an iterable of
2077 """Return (revs, expr, filematcher) where revs is an iterable of
2078 revision numbers, expr is a revset string built from log options
2078 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
2079 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
2080 --patch are not passed filematcher is None. Otherwise it is a
2081 callable taking a revision number and returning a match objects
2081 callable taking a revision number and returning a match objects
2082 filtering the files to be detailed when displaying the revision.
2082 filtering the files to be detailed when displaying the revision.
2083 """
2083 """
2084 limit = loglimit(opts)
2084 limit = loglimit(opts)
2085 revs = _logrevs(repo, opts)
2085 revs = _logrevs(repo, opts)
2086 if not revs:
2086 if not revs:
2087 return revset.baseset(), None, None
2087 return revset.baseset(), None, None
2088 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2088 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2089 if opts.get('rev'):
2089 if opts.get('rev'):
2090 # User-specified revs might be unsorted, but don't sort before
2090 # User-specified revs might be unsorted, but don't sort before
2091 # _makelogrevset because it might depend on the order of revs
2091 # _makelogrevset because it might depend on the order of revs
2092 if not (revs.isdescending() or revs.istopo()):
2092 if not (revs.isdescending() or revs.istopo()):
2093 revs.sort(reverse=True)
2093 revs.sort(reverse=True)
2094 if expr:
2094 if expr:
2095 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2095 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2096 revs = matcher(repo, revs)
2096 revs = matcher(repo, revs)
2097 if limit is not None:
2097 if limit is not None:
2098 limitedrevs = []
2098 limitedrevs = []
2099 for idx, rev in enumerate(revs):
2099 for idx, rev in enumerate(revs):
2100 if idx >= limit:
2100 if idx >= limit:
2101 break
2101 break
2102 limitedrevs.append(rev)
2102 limitedrevs.append(rev)
2103 revs = revset.baseset(limitedrevs)
2103 revs = revset.baseset(limitedrevs)
2104
2104
2105 return revs, expr, filematcher
2105 return revs, expr, filematcher
2106
2106
2107 def getlogrevs(repo, pats, opts):
2107 def getlogrevs(repo, pats, opts):
2108 """Return (revs, expr, filematcher) where revs is an iterable of
2108 """Return (revs, expr, filematcher) where revs is an iterable of
2109 revision numbers, expr is a revset string built from log options
2109 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
2110 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
2111 --patch are not passed filematcher is None. Otherwise it is a
2112 callable taking a revision number and returning a match objects
2112 callable taking a revision number and returning a match objects
2113 filtering the files to be detailed when displaying the revision.
2113 filtering the files to be detailed when displaying the revision.
2114 """
2114 """
2115 limit = loglimit(opts)
2115 limit = loglimit(opts)
2116 revs = _logrevs(repo, opts)
2116 revs = _logrevs(repo, opts)
2117 if not revs:
2117 if not revs:
2118 return revset.baseset([]), None, None
2118 return revset.baseset([]), None, None
2119 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2119 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2120 if expr:
2120 if expr:
2121 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2121 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2122 revs = matcher(repo, revs)
2122 revs = matcher(repo, revs)
2123 if limit is not None:
2123 if limit is not None:
2124 limitedrevs = []
2124 limitedrevs = []
2125 for idx, r in enumerate(revs):
2125 for idx, r in enumerate(revs):
2126 if limit <= idx:
2126 if limit <= idx:
2127 break
2127 break
2128 limitedrevs.append(r)
2128 limitedrevs.append(r)
2129 revs = revset.baseset(limitedrevs)
2129 revs = revset.baseset(limitedrevs)
2130
2130
2131 return revs, expr, filematcher
2131 return revs, expr, filematcher
2132
2132
2133 def _graphnodeformatter(ui, displayer):
2133 def _graphnodeformatter(ui, displayer):
2134 spec = ui.config('ui', 'graphnodetemplate')
2134 spec = ui.config('ui', 'graphnodetemplate')
2135 if not spec:
2135 if not spec:
2136 return templatekw.showgraphnode # fast path for "{graphnode}"
2136 return templatekw.showgraphnode # fast path for "{graphnode}"
2137
2137
2138 templ = formatter.gettemplater(ui, 'graphnode', spec)
2138 templ = formatter.gettemplater(ui, 'graphnode', spec)
2139 cache = {}
2139 cache = {}
2140 if isinstance(displayer, changeset_templater):
2140 if isinstance(displayer, changeset_templater):
2141 cache = displayer.cache # reuse cache of slow templates
2141 cache = displayer.cache # reuse cache of slow templates
2142 props = templatekw.keywords.copy()
2142 props = templatekw.keywords.copy()
2143 props['templ'] = templ
2143 props['templ'] = templ
2144 props['cache'] = cache
2144 props['cache'] = cache
2145 def formatnode(repo, ctx):
2145 def formatnode(repo, ctx):
2146 props['ctx'] = ctx
2146 props['ctx'] = ctx
2147 props['repo'] = repo
2147 props['repo'] = repo
2148 props['ui'] = repo.ui
2148 props['ui'] = repo.ui
2149 props['revcache'] = {}
2149 props['revcache'] = {}
2150 return templater.stringify(templ('graphnode', **props))
2150 return templater.stringify(templ('graphnode', **props))
2151 return formatnode
2151 return formatnode
2152
2152
2153 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2153 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2154 filematcher=None):
2154 filematcher=None):
2155 formatnode = _graphnodeformatter(ui, displayer)
2155 formatnode = _graphnodeformatter(ui, displayer)
2156 state = graphmod.asciistate()
2156 state = graphmod.asciistate()
2157 styles = state['styles']
2157 styles = state['styles']
2158
2158
2159 # only set graph styling if HGPLAIN is not set.
2159 # only set graph styling if HGPLAIN is not set.
2160 if ui.plain('graph'):
2160 if ui.plain('graph'):
2161 # set all edge styles to |, the default pre-3.8 behaviour
2161 # set all edge styles to |, the default pre-3.8 behaviour
2162 styles.update(dict.fromkeys(styles, '|'))
2162 styles.update(dict.fromkeys(styles, '|'))
2163 else:
2163 else:
2164 edgetypes = {
2164 edgetypes = {
2165 'parent': graphmod.PARENT,
2165 'parent': graphmod.PARENT,
2166 'grandparent': graphmod.GRANDPARENT,
2166 'grandparent': graphmod.GRANDPARENT,
2167 'missing': graphmod.MISSINGPARENT
2167 'missing': graphmod.MISSINGPARENT
2168 }
2168 }
2169 for name, key in edgetypes.items():
2169 for name, key in edgetypes.items():
2170 # experimental config: experimental.graphstyle.*
2170 # experimental config: experimental.graphstyle.*
2171 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2171 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2172 styles[key])
2172 styles[key])
2173 if not styles[key]:
2173 if not styles[key]:
2174 styles[key] = None
2174 styles[key] = None
2175
2175
2176 # experimental config: experimental.graphshorten
2176 # experimental config: experimental.graphshorten
2177 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2177 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2178
2178
2179 for rev, type, ctx, parents in dag:
2179 for rev, type, ctx, parents in dag:
2180 char = formatnode(repo, ctx)
2180 char = formatnode(repo, ctx)
2181 copies = None
2181 copies = None
2182 if getrenamed and ctx.rev():
2182 if getrenamed and ctx.rev():
2183 copies = []
2183 copies = []
2184 for fn in ctx.files():
2184 for fn in ctx.files():
2185 rename = getrenamed(fn, ctx.rev())
2185 rename = getrenamed(fn, ctx.rev())
2186 if rename:
2186 if rename:
2187 copies.append((fn, rename[0]))
2187 copies.append((fn, rename[0]))
2188 revmatchfn = None
2188 revmatchfn = None
2189 if filematcher is not None:
2189 if filematcher is not None:
2190 revmatchfn = filematcher(ctx.rev())
2190 revmatchfn = filematcher(ctx.rev())
2191 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2191 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2192 lines = displayer.hunk.pop(rev).split('\n')
2192 lines = displayer.hunk.pop(rev).split('\n')
2193 if not lines[-1]:
2193 if not lines[-1]:
2194 del lines[-1]
2194 del lines[-1]
2195 displayer.flush(ctx)
2195 displayer.flush(ctx)
2196 edges = edgefn(type, char, lines, state, rev, parents)
2196 edges = edgefn(type, char, lines, state, rev, parents)
2197 for type, char, lines, coldata in edges:
2197 for type, char, lines, coldata in edges:
2198 graphmod.ascii(ui, state, type, char, lines, coldata)
2198 graphmod.ascii(ui, state, type, char, lines, coldata)
2199 displayer.close()
2199 displayer.close()
2200
2200
2201 def graphlog(ui, repo, *pats, **opts):
2201 def graphlog(ui, repo, *pats, **opts):
2202 # Parameters are identical to log command ones
2202 # Parameters are identical to log command ones
2203 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2203 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2204 revdag = graphmod.dagwalker(repo, revs)
2204 revdag = graphmod.dagwalker(repo, revs)
2205
2205
2206 getrenamed = None
2206 getrenamed = None
2207 if opts.get('copies'):
2207 if opts.get('copies'):
2208 endrev = None
2208 endrev = None
2209 if opts.get('rev'):
2209 if opts.get('rev'):
2210 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2210 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2211 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2211 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2212 displayer = show_changeset(ui, repo, opts, buffered=True)
2212 displayer = show_changeset(ui, repo, opts, buffered=True)
2213 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2213 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2214 filematcher)
2214 filematcher)
2215
2215
2216 def checkunsupportedgraphflags(pats, opts):
2216 def checkunsupportedgraphflags(pats, opts):
2217 for op in ["newest_first"]:
2217 for op in ["newest_first"]:
2218 if op in opts and opts[op]:
2218 if op in opts and opts[op]:
2219 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2219 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2220 % op.replace("_", "-"))
2220 % op.replace("_", "-"))
2221
2221
2222 def graphrevs(repo, nodes, opts):
2222 def graphrevs(repo, nodes, opts):
2223 limit = loglimit(opts)
2223 limit = loglimit(opts)
2224 nodes.reverse()
2224 nodes.reverse()
2225 if limit is not None:
2225 if limit is not None:
2226 nodes = nodes[:limit]
2226 nodes = nodes[:limit]
2227 return graphmod.nodes(repo, nodes)
2227 return graphmod.nodes(repo, nodes)
2228
2228
2229 def add(ui, repo, match, prefix, explicitonly, **opts):
2229 def add(ui, repo, match, prefix, explicitonly, **opts):
2230 join = lambda f: os.path.join(prefix, f)
2230 join = lambda f: os.path.join(prefix, f)
2231 bad = []
2231 bad = []
2232
2232
2233 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2233 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2234 names = []
2234 names = []
2235 wctx = repo[None]
2235 wctx = repo[None]
2236 cca = None
2236 cca = None
2237 abort, warn = scmutil.checkportabilityalert(ui)
2237 abort, warn = scmutil.checkportabilityalert(ui)
2238 if abort or warn:
2238 if abort or warn:
2239 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2239 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2240
2240
2241 badmatch = matchmod.badmatch(match, badfn)
2241 badmatch = matchmod.badmatch(match, badfn)
2242 dirstate = repo.dirstate
2242 dirstate = repo.dirstate
2243 # We don't want to just call wctx.walk here, since it would return a lot of
2243 # 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.
2244 # clean files, which we aren't interested in and takes time.
2245 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2245 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2246 True, False, full=False)):
2246 True, False, full=False)):
2247 exact = match.exact(f)
2247 exact = match.exact(f)
2248 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2248 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2249 if cca:
2249 if cca:
2250 cca(f)
2250 cca(f)
2251 names.append(f)
2251 names.append(f)
2252 if ui.verbose or not exact:
2252 if ui.verbose or not exact:
2253 ui.status(_('adding %s\n') % match.rel(f))
2253 ui.status(_('adding %s\n') % match.rel(f))
2254
2254
2255 for subpath in sorted(wctx.substate):
2255 for subpath in sorted(wctx.substate):
2256 sub = wctx.sub(subpath)
2256 sub = wctx.sub(subpath)
2257 try:
2257 try:
2258 submatch = matchmod.subdirmatcher(subpath, match)
2258 submatch = matchmod.subdirmatcher(subpath, match)
2259 if opts.get('subrepos'):
2259 if opts.get('subrepos'):
2260 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2260 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2261 else:
2261 else:
2262 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2262 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2263 except error.LookupError:
2263 except error.LookupError:
2264 ui.status(_("skipping missing subrepository: %s\n")
2264 ui.status(_("skipping missing subrepository: %s\n")
2265 % join(subpath))
2265 % join(subpath))
2266
2266
2267 if not opts.get('dry_run'):
2267 if not opts.get('dry_run'):
2268 rejected = wctx.add(names, prefix)
2268 rejected = wctx.add(names, prefix)
2269 bad.extend(f for f in rejected if f in match.files())
2269 bad.extend(f for f in rejected if f in match.files())
2270 return bad
2270 return bad
2271
2271
2272 def forget(ui, repo, match, prefix, explicitonly):
2272 def forget(ui, repo, match, prefix, explicitonly):
2273 join = lambda f: os.path.join(prefix, f)
2273 join = lambda f: os.path.join(prefix, f)
2274 bad = []
2274 bad = []
2275 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2275 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2276 wctx = repo[None]
2276 wctx = repo[None]
2277 forgot = []
2277 forgot = []
2278
2278
2279 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2279 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2280 forget = sorted(s[0] + s[1] + s[3] + s[6])
2280 forget = sorted(s[0] + s[1] + s[3] + s[6])
2281 if explicitonly:
2281 if explicitonly:
2282 forget = [f for f in forget if match.exact(f)]
2282 forget = [f for f in forget if match.exact(f)]
2283
2283
2284 for subpath in sorted(wctx.substate):
2284 for subpath in sorted(wctx.substate):
2285 sub = wctx.sub(subpath)
2285 sub = wctx.sub(subpath)
2286 try:
2286 try:
2287 submatch = matchmod.subdirmatcher(subpath, match)
2287 submatch = matchmod.subdirmatcher(subpath, match)
2288 subbad, subforgot = sub.forget(submatch, prefix)
2288 subbad, subforgot = sub.forget(submatch, prefix)
2289 bad.extend([subpath + '/' + f for f in subbad])
2289 bad.extend([subpath + '/' + f for f in subbad])
2290 forgot.extend([subpath + '/' + f for f in subforgot])
2290 forgot.extend([subpath + '/' + f for f in subforgot])
2291 except error.LookupError:
2291 except error.LookupError:
2292 ui.status(_("skipping missing subrepository: %s\n")
2292 ui.status(_("skipping missing subrepository: %s\n")
2293 % join(subpath))
2293 % join(subpath))
2294
2294
2295 if not explicitonly:
2295 if not explicitonly:
2296 for f in match.files():
2296 for f in match.files():
2297 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2297 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2298 if f not in forgot:
2298 if f not in forgot:
2299 if repo.wvfs.exists(f):
2299 if repo.wvfs.exists(f):
2300 # Don't complain if the exact case match wasn't given.
2300 # Don't complain if the exact case match wasn't given.
2301 # But don't do this until after checking 'forgot', so
2301 # But don't do this until after checking 'forgot', so
2302 # that subrepo files aren't normalized, and this op is
2302 # that subrepo files aren't normalized, and this op is
2303 # purely from data cached by the status walk above.
2303 # purely from data cached by the status walk above.
2304 if repo.dirstate.normalize(f) in repo.dirstate:
2304 if repo.dirstate.normalize(f) in repo.dirstate:
2305 continue
2305 continue
2306 ui.warn(_('not removing %s: '
2306 ui.warn(_('not removing %s: '
2307 'file is already untracked\n')
2307 'file is already untracked\n')
2308 % match.rel(f))
2308 % match.rel(f))
2309 bad.append(f)
2309 bad.append(f)
2310
2310
2311 for f in forget:
2311 for f in forget:
2312 if ui.verbose or not match.exact(f):
2312 if ui.verbose or not match.exact(f):
2313 ui.status(_('removing %s\n') % match.rel(f))
2313 ui.status(_('removing %s\n') % match.rel(f))
2314
2314
2315 rejected = wctx.forget(forget, prefix)
2315 rejected = wctx.forget(forget, prefix)
2316 bad.extend(f for f in rejected if f in match.files())
2316 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)
2317 forgot.extend(f for f in forget if f not in rejected)
2318 return bad, forgot
2318 return bad, forgot
2319
2319
2320 def files(ui, ctx, m, fm, fmt, subrepos):
2320 def files(ui, ctx, m, fm, fmt, subrepos):
2321 rev = ctx.rev()
2321 rev = ctx.rev()
2322 ret = 1
2322 ret = 1
2323 ds = ctx.repo().dirstate
2323 ds = ctx.repo().dirstate
2324
2324
2325 for f in ctx.matches(m):
2325 for f in ctx.matches(m):
2326 if rev is None and ds[f] == 'r':
2326 if rev is None and ds[f] == 'r':
2327 continue
2327 continue
2328 fm.startitem()
2328 fm.startitem()
2329 if ui.verbose:
2329 if ui.verbose:
2330 fc = ctx[f]
2330 fc = ctx[f]
2331 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2331 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2332 fm.data(abspath=f)
2332 fm.data(abspath=f)
2333 fm.write('path', fmt, m.rel(f))
2333 fm.write('path', fmt, m.rel(f))
2334 ret = 0
2334 ret = 0
2335
2335
2336 for subpath in sorted(ctx.substate):
2336 for subpath in sorted(ctx.substate):
2337 submatch = matchmod.subdirmatcher(subpath, m)
2337 submatch = matchmod.subdirmatcher(subpath, m)
2338 if (subrepos or m.exact(subpath) or any(submatch.files())):
2338 if (subrepos or m.exact(subpath) or any(submatch.files())):
2339 sub = ctx.sub(subpath)
2339 sub = ctx.sub(subpath)
2340 try:
2340 try:
2341 recurse = m.exact(subpath) or subrepos
2341 recurse = m.exact(subpath) or subrepos
2342 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2342 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2343 ret = 0
2343 ret = 0
2344 except error.LookupError:
2344 except error.LookupError:
2345 ui.status(_("skipping missing subrepository: %s\n")
2345 ui.status(_("skipping missing subrepository: %s\n")
2346 % m.abs(subpath))
2346 % m.abs(subpath))
2347
2347
2348 return ret
2348 return ret
2349
2349
2350 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2350 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2351 join = lambda f: os.path.join(prefix, f)
2351 join = lambda f: os.path.join(prefix, f)
2352 ret = 0
2352 ret = 0
2353 s = repo.status(match=m, clean=True)
2353 s = repo.status(match=m, clean=True)
2354 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2354 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2355
2355
2356 wctx = repo[None]
2356 wctx = repo[None]
2357
2357
2358 if warnings is None:
2358 if warnings is None:
2359 warnings = []
2359 warnings = []
2360 warn = True
2360 warn = True
2361 else:
2361 else:
2362 warn = False
2362 warn = False
2363
2363
2364 subs = sorted(wctx.substate)
2364 subs = sorted(wctx.substate)
2365 total = len(subs)
2365 total = len(subs)
2366 count = 0
2366 count = 0
2367 for subpath in subs:
2367 for subpath in subs:
2368 count += 1
2368 count += 1
2369 submatch = matchmod.subdirmatcher(subpath, m)
2369 submatch = matchmod.subdirmatcher(subpath, m)
2370 if subrepos or m.exact(subpath) or any(submatch.files()):
2370 if subrepos or m.exact(subpath) or any(submatch.files()):
2371 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2371 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2372 sub = wctx.sub(subpath)
2372 sub = wctx.sub(subpath)
2373 try:
2373 try:
2374 if sub.removefiles(submatch, prefix, after, force, subrepos,
2374 if sub.removefiles(submatch, prefix, after, force, subrepos,
2375 warnings):
2375 warnings):
2376 ret = 1
2376 ret = 1
2377 except error.LookupError:
2377 except error.LookupError:
2378 warnings.append(_("skipping missing subrepository: %s\n")
2378 warnings.append(_("skipping missing subrepository: %s\n")
2379 % join(subpath))
2379 % join(subpath))
2380 ui.progress(_('searching'), None)
2380 ui.progress(_('searching'), None)
2381
2381
2382 # warn about failure to delete explicit files/dirs
2382 # warn about failure to delete explicit files/dirs
2383 deleteddirs = util.dirs(deleted)
2383 deleteddirs = util.dirs(deleted)
2384 files = m.files()
2384 files = m.files()
2385 total = len(files)
2385 total = len(files)
2386 count = 0
2386 count = 0
2387 for f in files:
2387 for f in files:
2388 def insubrepo():
2388 def insubrepo():
2389 for subpath in wctx.substate:
2389 for subpath in wctx.substate:
2390 if f.startswith(subpath + '/'):
2390 if f.startswith(subpath + '/'):
2391 return True
2391 return True
2392 return False
2392 return False
2393
2393
2394 count += 1
2394 count += 1
2395 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2395 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2396 isdir = f in deleteddirs or wctx.hasdir(f)
2396 isdir = f in deleteddirs or wctx.hasdir(f)
2397 if (f in repo.dirstate or isdir or f == '.'
2397 if (f in repo.dirstate or isdir or f == '.'
2398 or insubrepo() or f in subs):
2398 or insubrepo() or f in subs):
2399 continue
2399 continue
2400
2400
2401 if repo.wvfs.exists(f):
2401 if repo.wvfs.exists(f):
2402 if repo.wvfs.isdir(f):
2402 if repo.wvfs.isdir(f):
2403 warnings.append(_('not removing %s: no tracked files\n')
2403 warnings.append(_('not removing %s: no tracked files\n')
2404 % m.rel(f))
2404 % m.rel(f))
2405 else:
2405 else:
2406 warnings.append(_('not removing %s: file is untracked\n')
2406 warnings.append(_('not removing %s: file is untracked\n')
2407 % m.rel(f))
2407 % m.rel(f))
2408 # missing files will generate a warning elsewhere
2408 # missing files will generate a warning elsewhere
2409 ret = 1
2409 ret = 1
2410 ui.progress(_('deleting'), None)
2410 ui.progress(_('deleting'), None)
2411
2411
2412 if force:
2412 if force:
2413 list = modified + deleted + clean + added
2413 list = modified + deleted + clean + added
2414 elif after:
2414 elif after:
2415 list = deleted
2415 list = deleted
2416 remaining = modified + added + clean
2416 remaining = modified + added + clean
2417 total = len(remaining)
2417 total = len(remaining)
2418 count = 0
2418 count = 0
2419 for f in remaining:
2419 for f in remaining:
2420 count += 1
2420 count += 1
2421 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2421 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2422 warnings.append(_('not removing %s: file still exists\n')
2422 warnings.append(_('not removing %s: file still exists\n')
2423 % m.rel(f))
2423 % m.rel(f))
2424 ret = 1
2424 ret = 1
2425 ui.progress(_('skipping'), None)
2425 ui.progress(_('skipping'), None)
2426 else:
2426 else:
2427 list = deleted + clean
2427 list = deleted + clean
2428 total = len(modified) + len(added)
2428 total = len(modified) + len(added)
2429 count = 0
2429 count = 0
2430 for f in modified:
2430 for f in modified:
2431 count += 1
2431 count += 1
2432 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2432 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2433 warnings.append(_('not removing %s: file is modified (use -f'
2433 warnings.append(_('not removing %s: file is modified (use -f'
2434 ' to force removal)\n') % m.rel(f))
2434 ' to force removal)\n') % m.rel(f))
2435 ret = 1
2435 ret = 1
2436 for f in added:
2436 for f in added:
2437 count += 1
2437 count += 1
2438 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2438 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2439 warnings.append(_("not removing %s: file has been marked for add"
2439 warnings.append(_("not removing %s: file has been marked for add"
2440 " (use 'hg forget' to undo add)\n") % m.rel(f))
2440 " (use 'hg forget' to undo add)\n") % m.rel(f))
2441 ret = 1
2441 ret = 1
2442 ui.progress(_('skipping'), None)
2442 ui.progress(_('skipping'), None)
2443
2443
2444 list = sorted(list)
2444 list = sorted(list)
2445 total = len(list)
2445 total = len(list)
2446 count = 0
2446 count = 0
2447 for f in list:
2447 for f in list:
2448 count += 1
2448 count += 1
2449 if ui.verbose or not m.exact(f):
2449 if ui.verbose or not m.exact(f):
2450 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2450 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2451 ui.status(_('removing %s\n') % m.rel(f))
2451 ui.status(_('removing %s\n') % m.rel(f))
2452 ui.progress(_('deleting'), None)
2452 ui.progress(_('deleting'), None)
2453
2453
2454 with repo.wlock():
2454 with repo.wlock():
2455 if not after:
2455 if not after:
2456 for f in list:
2456 for f in list:
2457 if f in added:
2457 if f in added:
2458 continue # we never unlink added files on remove
2458 continue # we never unlink added files on remove
2459 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2459 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2460 repo[None].forget(list)
2460 repo[None].forget(list)
2461
2461
2462 if warn:
2462 if warn:
2463 for warning in warnings:
2463 for warning in warnings:
2464 ui.warn(warning)
2464 ui.warn(warning)
2465
2465
2466 return ret
2466 return ret
2467
2467
2468 def cat(ui, repo, ctx, matcher, prefix, **opts):
2468 def cat(ui, repo, ctx, matcher, prefix, **opts):
2469 err = 1
2469 err = 1
2470
2470
2471 def write(path):
2471 def write(path):
2472 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2472 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2473 pathname=os.path.join(prefix, path))
2473 pathname=os.path.join(prefix, path))
2474 data = ctx[path].data()
2474 data = ctx[path].data()
2475 if opts.get('decode'):
2475 if opts.get('decode'):
2476 data = repo.wwritedata(path, data)
2476 data = repo.wwritedata(path, data)
2477 fp.write(data)
2477 fp.write(data)
2478 fp.close()
2478 fp.close()
2479
2479
2480 # Automation often uses hg cat on single files, so special case it
2480 # Automation often uses hg cat on single files, so special case it
2481 # for performance to avoid the cost of parsing the manifest.
2481 # for performance to avoid the cost of parsing the manifest.
2482 if len(matcher.files()) == 1 and not matcher.anypats():
2482 if len(matcher.files()) == 1 and not matcher.anypats():
2483 file = matcher.files()[0]
2483 file = matcher.files()[0]
2484 mfl = repo.manifestlog
2484 mfl = repo.manifestlog
2485 mfnode = ctx.manifestnode()
2485 mfnode = ctx.manifestnode()
2486 try:
2486 try:
2487 if mfnode and mfl[mfnode].find(file)[0]:
2487 if mfnode and mfl[mfnode].find(file)[0]:
2488 write(file)
2488 write(file)
2489 return 0
2489 return 0
2490 except KeyError:
2490 except KeyError:
2491 pass
2491 pass
2492
2492
2493 for abs in ctx.walk(matcher):
2493 for abs in ctx.walk(matcher):
2494 write(abs)
2494 write(abs)
2495 err = 0
2495 err = 0
2496
2496
2497 for subpath in sorted(ctx.substate):
2497 for subpath in sorted(ctx.substate):
2498 sub = ctx.sub(subpath)
2498 sub = ctx.sub(subpath)
2499 try:
2499 try:
2500 submatch = matchmod.subdirmatcher(subpath, matcher)
2500 submatch = matchmod.subdirmatcher(subpath, matcher)
2501
2501
2502 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2502 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2503 **opts):
2503 **opts):
2504 err = 0
2504 err = 0
2505 except error.RepoLookupError:
2505 except error.RepoLookupError:
2506 ui.status(_("skipping missing subrepository: %s\n")
2506 ui.status(_("skipping missing subrepository: %s\n")
2507 % os.path.join(prefix, subpath))
2507 % os.path.join(prefix, subpath))
2508
2508
2509 return err
2509 return err
2510
2510
2511 def commit(ui, repo, commitfunc, pats, opts):
2511 def commit(ui, repo, commitfunc, pats, opts):
2512 '''commit the specified files or all outstanding changes'''
2512 '''commit the specified files or all outstanding changes'''
2513 date = opts.get('date')
2513 date = opts.get('date')
2514 if date:
2514 if date:
2515 opts['date'] = util.parsedate(date)
2515 opts['date'] = util.parsedate(date)
2516 message = logmessage(ui, opts)
2516 message = logmessage(ui, opts)
2517 matcher = scmutil.match(repo[None], pats, opts)
2517 matcher = scmutil.match(repo[None], pats, opts)
2518
2518
2519 # extract addremove carefully -- this function can be called from a command
2519 # extract addremove carefully -- this function can be called from a command
2520 # that doesn't support addremove
2520 # that doesn't support addremove
2521 if opts.get('addremove'):
2521 if opts.get('addremove'):
2522 if scmutil.addremove(repo, matcher, "", opts) != 0:
2522 if scmutil.addremove(repo, matcher, "", opts) != 0:
2523 raise error.Abort(
2523 raise error.Abort(
2524 _("failed to mark all new/missing files as added/removed"))
2524 _("failed to mark all new/missing files as added/removed"))
2525
2525
2526 return commitfunc(ui, repo, message, matcher, opts)
2526 return commitfunc(ui, repo, message, matcher, opts)
2527
2527
2528 def samefile(f, ctx1, ctx2):
2528 def samefile(f, ctx1, ctx2):
2529 if f in ctx1.manifest():
2529 if f in ctx1.manifest():
2530 a = ctx1.filectx(f)
2530 a = ctx1.filectx(f)
2531 if f in ctx2.manifest():
2531 if f in ctx2.manifest():
2532 b = ctx2.filectx(f)
2532 b = ctx2.filectx(f)
2533 return (not a.cmp(b)
2533 return (not a.cmp(b)
2534 and a.flags() == b.flags())
2534 and a.flags() == b.flags())
2535 else:
2535 else:
2536 return False
2536 return False
2537 else:
2537 else:
2538 return f not in ctx2.manifest()
2538 return f not in ctx2.manifest()
2539
2539
2540 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2540 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2541 # avoid cycle context -> subrepo -> cmdutil
2541 # avoid cycle context -> subrepo -> cmdutil
2542 from . import context
2542 from . import context
2543
2543
2544 # amend will reuse the existing user if not specified, but the obsolete
2544 # amend will reuse the existing user if not specified, but the obsolete
2545 # marker creation requires that the current user's name is specified.
2545 # marker creation requires that the current user's name is specified.
2546 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2546 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2547 ui.username() # raise exception if username not set
2547 ui.username() # raise exception if username not set
2548
2548
2549 ui.note(_('amending changeset %s\n') % old)
2549 ui.note(_('amending changeset %s\n') % old)
2550 base = old.p1()
2550 base = old.p1()
2551 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2551 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2552
2552
2553 wlock = lock = newid = None
2553 wlock = lock = newid = None
2554 try:
2554 try:
2555 wlock = repo.wlock()
2555 wlock = repo.wlock()
2556 lock = repo.lock()
2556 lock = repo.lock()
2557 with repo.transaction('amend') as tr:
2557 with repo.transaction('amend') as tr:
2558 # See if we got a message from -m or -l, if not, open the editor
2558 # See if we got a message from -m or -l, if not, open the editor
2559 # with the message of the changeset to amend
2559 # with the message of the changeset to amend
2560 message = logmessage(ui, opts)
2560 message = logmessage(ui, opts)
2561 # ensure logfile does not conflict with later enforcement of the
2561 # ensure logfile does not conflict with later enforcement of the
2562 # message. potential logfile content has been processed by
2562 # message. potential logfile content has been processed by
2563 # `logmessage` anyway.
2563 # `logmessage` anyway.
2564 opts.pop('logfile')
2564 opts.pop('logfile')
2565 # First, do a regular commit to record all changes in the working
2565 # First, do a regular commit to record all changes in the working
2566 # directory (if there are any)
2566 # directory (if there are any)
2567 ui.callhooks = False
2567 ui.callhooks = False
2568 activebookmark = repo._bookmarks.active
2568 activebookmark = repo._bookmarks.active
2569 try:
2569 try:
2570 repo._bookmarks.active = None
2570 repo._bookmarks.active = None
2571 opts['message'] = 'temporary amend commit for %s' % old
2571 opts['message'] = 'temporary amend commit for %s' % old
2572 node = commit(ui, repo, commitfunc, pats, opts)
2572 node = commit(ui, repo, commitfunc, pats, opts)
2573 finally:
2573 finally:
2574 repo._bookmarks.active = activebookmark
2574 repo._bookmarks.active = activebookmark
2575 repo._bookmarks.recordchange(tr)
2575 repo._bookmarks.recordchange(tr)
2576 ui.callhooks = True
2576 ui.callhooks = True
2577 ctx = repo[node]
2577 ctx = repo[node]
2578
2578
2579 # Participating changesets:
2579 # Participating changesets:
2580 #
2580 #
2581 # node/ctx o - new (intermediate) commit that contains changes
2581 # node/ctx o - new (intermediate) commit that contains changes
2582 # | from working dir to go into amending commit
2582 # | from working dir to go into amending commit
2583 # | (or a workingctx if there were no changes)
2583 # | (or a workingctx if there were no changes)
2584 # |
2584 # |
2585 # old o - changeset to amend
2585 # old o - changeset to amend
2586 # |
2586 # |
2587 # base o - parent of amending changeset
2587 # base o - parent of amending changeset
2588
2588
2589 # Update extra dict from amended commit (e.g. to preserve graft
2589 # Update extra dict from amended commit (e.g. to preserve graft
2590 # source)
2590 # source)
2591 extra.update(old.extra())
2591 extra.update(old.extra())
2592
2592
2593 # Also update it from the intermediate commit or from the wctx
2593 # Also update it from the intermediate commit or from the wctx
2594 extra.update(ctx.extra())
2594 extra.update(ctx.extra())
2595
2595
2596 if len(old.parents()) > 1:
2596 if len(old.parents()) > 1:
2597 # ctx.files() isn't reliable for merges, so fall back to the
2597 # ctx.files() isn't reliable for merges, so fall back to the
2598 # slower repo.status() method
2598 # slower repo.status() method
2599 files = set([fn for st in repo.status(base, old)[:3]
2599 files = set([fn for st in repo.status(base, old)[:3]
2600 for fn in st])
2600 for fn in st])
2601 else:
2601 else:
2602 files = set(old.files())
2602 files = set(old.files())
2603
2603
2604 # Second, we use either the commit we just did, or if there were no
2604 # 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
2605 # changes the parent of the working directory as the version of the
2606 # files in the final amend commit
2606 # files in the final amend commit
2607 if node:
2607 if node:
2608 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2608 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2609
2609
2610 user = ctx.user()
2610 user = ctx.user()
2611 date = ctx.date()
2611 date = ctx.date()
2612 # Recompute copies (avoid recording a -> b -> a)
2612 # Recompute copies (avoid recording a -> b -> a)
2613 copied = copies.pathcopies(base, ctx)
2613 copied = copies.pathcopies(base, ctx)
2614 if old.p2:
2614 if old.p2:
2615 copied.update(copies.pathcopies(old.p2(), ctx))
2615 copied.update(copies.pathcopies(old.p2(), ctx))
2616
2616
2617 # Prune files which were reverted by the updates: if old
2617 # Prune files which were reverted by the updates: if old
2618 # introduced file X and our intermediate commit, node,
2618 # introduced file X and our intermediate commit, node,
2619 # renamed that file, then those two files are the same and
2619 # renamed that file, then those two files are the same and
2620 # we can discard X from our list of files. Likewise if X
2620 # we can discard X from our list of files. Likewise if X
2621 # was deleted, it's no longer relevant
2621 # was deleted, it's no longer relevant
2622 files.update(ctx.files())
2622 files.update(ctx.files())
2623 files = [f for f in files if not samefile(f, ctx, base)]
2623 files = [f for f in files if not samefile(f, ctx, base)]
2624
2624
2625 def filectxfn(repo, ctx_, path):
2625 def filectxfn(repo, ctx_, path):
2626 try:
2626 try:
2627 fctx = ctx[path]
2627 fctx = ctx[path]
2628 flags = fctx.flags()
2628 flags = fctx.flags()
2629 mctx = context.memfilectx(repo,
2629 mctx = context.memfilectx(repo,
2630 fctx.path(), fctx.data(),
2630 fctx.path(), fctx.data(),
2631 islink='l' in flags,
2631 islink='l' in flags,
2632 isexec='x' in flags,
2632 isexec='x' in flags,
2633 copied=copied.get(path))
2633 copied=copied.get(path))
2634 return mctx
2634 return mctx
2635 except KeyError:
2635 except KeyError:
2636 return None
2636 return None
2637 else:
2637 else:
2638 ui.note(_('copying changeset %s to %s\n') % (old, base))
2638 ui.note(_('copying changeset %s to %s\n') % (old, base))
2639
2639
2640 # Use version of files as in the old cset
2640 # Use version of files as in the old cset
2641 def filectxfn(repo, ctx_, path):
2641 def filectxfn(repo, ctx_, path):
2642 try:
2642 try:
2643 return old.filectx(path)
2643 return old.filectx(path)
2644 except KeyError:
2644 except KeyError:
2645 return None
2645 return None
2646
2646
2647 user = opts.get('user') or old.user()
2647 user = opts.get('user') or old.user()
2648 date = opts.get('date') or old.date()
2648 date = opts.get('date') or old.date()
2649 editform = mergeeditform(old, 'commit.amend')
2649 editform = mergeeditform(old, 'commit.amend')
2650 editor = getcommiteditor(editform=editform, **opts)
2650 editor = getcommiteditor(editform=editform, **opts)
2651 if not message:
2651 if not message:
2652 editor = getcommiteditor(edit=True, editform=editform)
2652 editor = getcommiteditor(edit=True, editform=editform)
2653 message = old.description()
2653 message = old.description()
2654
2654
2655 pureextra = extra.copy()
2655 pureextra = extra.copy()
2656 extra['amend_source'] = old.hex()
2656 extra['amend_source'] = old.hex()
2657
2657
2658 new = context.memctx(repo,
2658 new = context.memctx(repo,
2659 parents=[base.node(), old.p2().node()],
2659 parents=[base.node(), old.p2().node()],
2660 text=message,
2660 text=message,
2661 files=files,
2661 files=files,
2662 filectxfn=filectxfn,
2662 filectxfn=filectxfn,
2663 user=user,
2663 user=user,
2664 date=date,
2664 date=date,
2665 extra=extra,
2665 extra=extra,
2666 editor=editor)
2666 editor=editor)
2667
2667
2668 newdesc = changelog.stripdesc(new.description())
2668 newdesc = changelog.stripdesc(new.description())
2669 if ((not node)
2669 if ((not node)
2670 and newdesc == old.description()
2670 and newdesc == old.description()
2671 and user == old.user()
2671 and user == old.user()
2672 and date == old.date()
2672 and date == old.date()
2673 and pureextra == old.extra()):
2673 and pureextra == old.extra()):
2674 # nothing changed. continuing here would create a new node
2674 # nothing changed. continuing here would create a new node
2675 # anyway because of the amend_source noise.
2675 # anyway because of the amend_source noise.
2676 #
2676 #
2677 # This not what we expect from amend.
2677 # This not what we expect from amend.
2678 return old.node()
2678 return old.node()
2679
2679
2680 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2680 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2681 try:
2681 try:
2682 if opts.get('secret'):
2682 if opts.get('secret'):
2683 commitphase = 'secret'
2683 commitphase = 'secret'
2684 else:
2684 else:
2685 commitphase = old.phase()
2685 commitphase = old.phase()
2686 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2686 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2687 newid = repo.commitctx(new)
2687 newid = repo.commitctx(new)
2688 finally:
2688 finally:
2689 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2689 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2690 if newid != old.node():
2690 if newid != old.node():
2691 # Reroute the working copy parent to the new changeset
2691 # Reroute the working copy parent to the new changeset
2692 repo.setparents(newid, nullid)
2692 repo.setparents(newid, nullid)
2693
2693
2694 # Move bookmarks from old parent to amend commit
2694 # Move bookmarks from old parent to amend commit
2695 bms = repo.nodebookmarks(old.node())
2695 bms = repo.nodebookmarks(old.node())
2696 if bms:
2696 if bms:
2697 marks = repo._bookmarks
2697 marks = repo._bookmarks
2698 for bm in bms:
2698 for bm in bms:
2699 ui.debug('moving bookmarks %r from %s to %s\n' %
2699 ui.debug('moving bookmarks %r from %s to %s\n' %
2700 (marks, old.hex(), hex(newid)))
2700 (marks, old.hex(), hex(newid)))
2701 marks[bm] = newid
2701 marks[bm] = newid
2702 marks.recordchange(tr)
2702 marks.recordchange(tr)
2703 #commit the whole amend process
2703 #commit the whole amend process
2704 if createmarkers:
2704 if createmarkers:
2705 # mark the new changeset as successor of the rewritten one
2705 # mark the new changeset as successor of the rewritten one
2706 new = repo[newid]
2706 new = repo[newid]
2707 obs = [(old, (new,))]
2707 obs = [(old, (new,))]
2708 if node:
2708 if node:
2709 obs.append((ctx, ()))
2709 obs.append((ctx, ()))
2710
2710
2711 obsolete.createmarkers(repo, obs)
2711 obsolete.createmarkers(repo, obs)
2712 if not createmarkers and newid != old.node():
2712 if not createmarkers and newid != old.node():
2713 # Strip the intermediate commit (if there was one) and the amended
2713 # Strip the intermediate commit (if there was one) and the amended
2714 # commit
2714 # commit
2715 if node:
2715 if node:
2716 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2716 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2717 ui.note(_('stripping amended changeset %s\n') % old)
2717 ui.note(_('stripping amended changeset %s\n') % old)
2718 repair.strip(ui, repo, old.node(), topic='amend-backup')
2718 repair.strip(ui, repo, old.node(), topic='amend-backup')
2719 finally:
2719 finally:
2720 lockmod.release(lock, wlock)
2720 lockmod.release(lock, wlock)
2721 return newid
2721 return newid
2722
2722
2723 def commiteditor(repo, ctx, subs, editform=''):
2723 def commiteditor(repo, ctx, subs, editform=''):
2724 if ctx.description():
2724 if ctx.description():
2725 return ctx.description()
2725 return ctx.description()
2726 return commitforceeditor(repo, ctx, subs, editform=editform,
2726 return commitforceeditor(repo, ctx, subs, editform=editform,
2727 unchangedmessagedetection=True)
2727 unchangedmessagedetection=True)
2728
2728
2729 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2729 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2730 editform='', unchangedmessagedetection=False):
2730 editform='', unchangedmessagedetection=False):
2731 if not extramsg:
2731 if not extramsg:
2732 extramsg = _("Leave message empty to abort commit.")
2732 extramsg = _("Leave message empty to abort commit.")
2733
2733
2734 forms = [e for e in editform.split('.') if e]
2734 forms = [e for e in editform.split('.') if e]
2735 forms.insert(0, 'changeset')
2735 forms.insert(0, 'changeset')
2736 templatetext = None
2736 templatetext = None
2737 while forms:
2737 while forms:
2738 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2738 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2739 if tmpl:
2739 if tmpl:
2740 templatetext = committext = buildcommittemplate(
2740 templatetext = committext = buildcommittemplate(
2741 repo, ctx, subs, extramsg, tmpl)
2741 repo, ctx, subs, extramsg, tmpl)
2742 break
2742 break
2743 forms.pop()
2743 forms.pop()
2744 else:
2744 else:
2745 committext = buildcommittext(repo, ctx, subs, extramsg)
2745 committext = buildcommittext(repo, ctx, subs, extramsg)
2746
2746
2747 # run editor in the repository root
2747 # run editor in the repository root
2748 olddir = pycompat.getcwd()
2748 olddir = pycompat.getcwd()
2749 os.chdir(repo.root)
2749 os.chdir(repo.root)
2750
2750
2751 # make in-memory changes visible to external process
2751 # make in-memory changes visible to external process
2752 tr = repo.currenttransaction()
2752 tr = repo.currenttransaction()
2753 repo.dirstate.write(tr)
2753 repo.dirstate.write(tr)
2754 pending = tr and tr.writepending() and repo.root
2754 pending = tr and tr.writepending() and repo.root
2755
2755
2756 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2756 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2757 editform=editform, pending=pending)
2757 editform=editform, pending=pending)
2758 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2758 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2759 os.chdir(olddir)
2759 os.chdir(olddir)
2760
2760
2761 if finishdesc:
2761 if finishdesc:
2762 text = finishdesc(text)
2762 text = finishdesc(text)
2763 if not text.strip():
2763 if not text.strip():
2764 raise error.Abort(_("empty commit message"))
2764 raise error.Abort(_("empty commit message"))
2765 if unchangedmessagedetection and editortext == templatetext:
2765 if unchangedmessagedetection and editortext == templatetext:
2766 raise error.Abort(_("commit message unchanged"))
2766 raise error.Abort(_("commit message unchanged"))
2767
2767
2768 return text
2768 return text
2769
2769
2770 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2770 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2771 ui = repo.ui
2771 ui = repo.ui
2772 tmpl, mapfile = gettemplate(ui, tmpl, None)
2772 tmpl, mapfile = gettemplate(ui, tmpl, None)
2773
2773
2774 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2774 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2775
2775
2776 for k, v in repo.ui.configitems('committemplate'):
2776 for k, v in repo.ui.configitems('committemplate'):
2777 if k != 'changeset':
2777 if k != 'changeset':
2778 t.t.cache[k] = v
2778 t.t.cache[k] = v
2779
2779
2780 if not extramsg:
2780 if not extramsg:
2781 extramsg = '' # ensure that extramsg is string
2781 extramsg = '' # ensure that extramsg is string
2782
2782
2783 ui.pushbuffer()
2783 ui.pushbuffer()
2784 t.show(ctx, extramsg=extramsg)
2784 t.show(ctx, extramsg=extramsg)
2785 return ui.popbuffer()
2785 return ui.popbuffer()
2786
2786
2787 def hgprefix(msg):
2787 def hgprefix(msg):
2788 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2788 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2789
2789
2790 def buildcommittext(repo, ctx, subs, extramsg):
2790 def buildcommittext(repo, ctx, subs, extramsg):
2791 edittext = []
2791 edittext = []
2792 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2792 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2793 if ctx.description():
2793 if ctx.description():
2794 edittext.append(ctx.description())
2794 edittext.append(ctx.description())
2795 edittext.append("")
2795 edittext.append("")
2796 edittext.append("") # Empty line between message and comments.
2796 edittext.append("") # Empty line between message and comments.
2797 edittext.append(hgprefix(_("Enter commit message."
2797 edittext.append(hgprefix(_("Enter commit message."
2798 " Lines beginning with 'HG:' are removed.")))
2798 " Lines beginning with 'HG:' are removed.")))
2799 edittext.append(hgprefix(extramsg))
2799 edittext.append(hgprefix(extramsg))
2800 edittext.append("HG: --")
2800 edittext.append("HG: --")
2801 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2801 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2802 if ctx.p2():
2802 if ctx.p2():
2803 edittext.append(hgprefix(_("branch merge")))
2803 edittext.append(hgprefix(_("branch merge")))
2804 if ctx.branch():
2804 if ctx.branch():
2805 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2805 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2806 if bookmarks.isactivewdirparent(repo):
2806 if bookmarks.isactivewdirparent(repo):
2807 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2807 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2808 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2808 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2809 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2809 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2810 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2810 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2811 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2811 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2812 if not added and not modified and not removed:
2812 if not added and not modified and not removed:
2813 edittext.append(hgprefix(_("no files changed")))
2813 edittext.append(hgprefix(_("no files changed")))
2814 edittext.append("")
2814 edittext.append("")
2815
2815
2816 return "\n".join(edittext)
2816 return "\n".join(edittext)
2817
2817
2818 def commitstatus(repo, node, branch, bheads=None, opts=None):
2818 def commitstatus(repo, node, branch, bheads=None, opts=None):
2819 if opts is None:
2819 if opts is None:
2820 opts = {}
2820 opts = {}
2821 ctx = repo[node]
2821 ctx = repo[node]
2822 parents = ctx.parents()
2822 parents = ctx.parents()
2823
2823
2824 if (not opts.get('amend') and bheads and node not in bheads and not
2824 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]):
2825 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2826 repo.ui.status(_('created new head\n'))
2826 repo.ui.status(_('created new head\n'))
2827 # The message is not printed for initial roots. For the other
2827 # The message is not printed for initial roots. For the other
2828 # changesets, it is printed in the following situations:
2828 # changesets, it is printed in the following situations:
2829 #
2829 #
2830 # Par column: for the 2 parents with ...
2830 # Par column: for the 2 parents with ...
2831 # N: null or no parent
2831 # N: null or no parent
2832 # B: parent is on another named branch
2832 # B: parent is on another named branch
2833 # C: parent is a regular non head changeset
2833 # C: parent is a regular non head changeset
2834 # H: parent was a branch head of the current branch
2834 # H: parent was a branch head of the current branch
2835 # Msg column: whether we print "created new head" message
2835 # Msg column: whether we print "created new head" message
2836 # In the following, it is assumed that there already exists some
2836 # In the following, it is assumed that there already exists some
2837 # initial branch heads of the current branch, otherwise nothing is
2837 # initial branch heads of the current branch, otherwise nothing is
2838 # printed anyway.
2838 # printed anyway.
2839 #
2839 #
2840 # Par Msg Comment
2840 # Par Msg Comment
2841 # N N y additional topo root
2841 # N N y additional topo root
2842 #
2842 #
2843 # B N y additional branch root
2843 # B N y additional branch root
2844 # C N y additional topo head
2844 # C N y additional topo head
2845 # H N n usual case
2845 # H N n usual case
2846 #
2846 #
2847 # B B y weird additional branch root
2847 # B B y weird additional branch root
2848 # C B y branch merge
2848 # C B y branch merge
2849 # H B n merge with named branch
2849 # H B n merge with named branch
2850 #
2850 #
2851 # C C y additional head from merge
2851 # C C y additional head from merge
2852 # C H n merge with a head
2852 # C H n merge with a head
2853 #
2853 #
2854 # H H n head merge: head count decreases
2854 # H H n head merge: head count decreases
2855
2855
2856 if not opts.get('close_branch'):
2856 if not opts.get('close_branch'):
2857 for r in parents:
2857 for r in parents:
2858 if r.closesbranch() and r.branch() == branch:
2858 if r.closesbranch() and r.branch() == branch:
2859 repo.ui.status(_('reopening closed branch head %d\n') % r)
2859 repo.ui.status(_('reopening closed branch head %d\n') % r)
2860
2860
2861 if repo.ui.debugflag:
2861 if repo.ui.debugflag:
2862 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2862 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2863 elif repo.ui.verbose:
2863 elif repo.ui.verbose:
2864 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2864 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2865
2865
2866 def postcommitstatus(repo, pats, opts):
2866 def postcommitstatus(repo, pats, opts):
2867 return repo.status(match=scmutil.match(repo[None], pats, opts))
2867 return repo.status(match=scmutil.match(repo[None], pats, opts))
2868
2868
2869 def revert(ui, repo, ctx, parents, *pats, **opts):
2869 def revert(ui, repo, ctx, parents, *pats, **opts):
2870 parent, p2 = parents
2870 parent, p2 = parents
2871 node = ctx.node()
2871 node = ctx.node()
2872
2872
2873 mf = ctx.manifest()
2873 mf = ctx.manifest()
2874 if node == p2:
2874 if node == p2:
2875 parent = p2
2875 parent = p2
2876
2876
2877 # need all matching names in dirstate and manifest of target rev,
2877 # 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
2878 # 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
2879 # but not other. in both cases, filesets should be evaluated against
2880 # workingctx to get consistent result (issue4497). this means 'set:**'
2880 # workingctx to get consistent result (issue4497). this means 'set:**'
2881 # cannot be used to select missing files from target rev.
2881 # cannot be used to select missing files from target rev.
2882
2882
2883 # `names` is a mapping for all elements in working copy and target revision
2883 # `names` is a mapping for all elements in working copy and target revision
2884 # The mapping is in the form:
2884 # The mapping is in the form:
2885 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2885 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2886 names = {}
2886 names = {}
2887
2887
2888 with repo.wlock():
2888 with repo.wlock():
2889 ## filling of the `names` mapping
2889 ## filling of the `names` mapping
2890 # walk dirstate to fill `names`
2890 # walk dirstate to fill `names`
2891
2891
2892 interactive = opts.get('interactive', False)
2892 interactive = opts.get('interactive', False)
2893 wctx = repo[None]
2893 wctx = repo[None]
2894 m = scmutil.match(wctx, pats, opts)
2894 m = scmutil.match(wctx, pats, opts)
2895
2895
2896 # we'll need this later
2896 # we'll need this later
2897 targetsubs = sorted(s for s in wctx.substate if m(s))
2897 targetsubs = sorted(s for s in wctx.substate if m(s))
2898
2898
2899 if not m.always():
2899 if not m.always():
2900 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2900 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2901 names[abs] = m.rel(abs), m.exact(abs)
2901 names[abs] = m.rel(abs), m.exact(abs)
2902
2902
2903 # walk target manifest to fill `names`
2903 # walk target manifest to fill `names`
2904
2904
2905 def badfn(path, msg):
2905 def badfn(path, msg):
2906 if path in names:
2906 if path in names:
2907 return
2907 return
2908 if path in ctx.substate:
2908 if path in ctx.substate:
2909 return
2909 return
2910 path_ = path + '/'
2910 path_ = path + '/'
2911 for f in names:
2911 for f in names:
2912 if f.startswith(path_):
2912 if f.startswith(path_):
2913 return
2913 return
2914 ui.warn("%s: %s\n" % (m.rel(path), msg))
2914 ui.warn("%s: %s\n" % (m.rel(path), msg))
2915
2915
2916 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2916 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2917 if abs not in names:
2917 if abs not in names:
2918 names[abs] = m.rel(abs), m.exact(abs)
2918 names[abs] = m.rel(abs), m.exact(abs)
2919
2919
2920 # Find status of all file in `names`.
2920 # Find status of all file in `names`.
2921 m = scmutil.matchfiles(repo, names)
2921 m = scmutil.matchfiles(repo, names)
2922
2922
2923 changes = repo.status(node1=node, match=m,
2923 changes = repo.status(node1=node, match=m,
2924 unknown=True, ignored=True, clean=True)
2924 unknown=True, ignored=True, clean=True)
2925 else:
2925 else:
2926 changes = repo.status(node1=node, match=m)
2926 changes = repo.status(node1=node, match=m)
2927 for kind in changes:
2927 for kind in changes:
2928 for abs in kind:
2928 for abs in kind:
2929 names[abs] = m.rel(abs), m.exact(abs)
2929 names[abs] = m.rel(abs), m.exact(abs)
2930
2930
2931 m = scmutil.matchfiles(repo, names)
2931 m = scmutil.matchfiles(repo, names)
2932
2932
2933 modified = set(changes.modified)
2933 modified = set(changes.modified)
2934 added = set(changes.added)
2934 added = set(changes.added)
2935 removed = set(changes.removed)
2935 removed = set(changes.removed)
2936 _deleted = set(changes.deleted)
2936 _deleted = set(changes.deleted)
2937 unknown = set(changes.unknown)
2937 unknown = set(changes.unknown)
2938 unknown.update(changes.ignored)
2938 unknown.update(changes.ignored)
2939 clean = set(changes.clean)
2939 clean = set(changes.clean)
2940 modadded = set()
2940 modadded = set()
2941
2941
2942 # split between files known in target manifest and the others
2942 # split between files known in target manifest and the others
2943 smf = set(mf)
2943 smf = set(mf)
2944
2944
2945 # determine the exact nature of the deleted changesets
2945 # determine the exact nature of the deleted changesets
2946 deladded = _deleted - smf
2946 deladded = _deleted - smf
2947 deleted = _deleted - deladded
2947 deleted = _deleted - deladded
2948
2948
2949 # We need to account for the state of the file in the dirstate,
2949 # 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
2950 # even when we revert against something else than parent. This will
2951 # slightly alter the behavior of revert (doing back up or not, delete
2951 # slightly alter the behavior of revert (doing back up or not, delete
2952 # or just forget etc).
2952 # or just forget etc).
2953 if parent == node:
2953 if parent == node:
2954 dsmodified = modified
2954 dsmodified = modified
2955 dsadded = added
2955 dsadded = added
2956 dsremoved = removed
2956 dsremoved = removed
2957 # store all local modifications, useful later for rename detection
2957 # store all local modifications, useful later for rename detection
2958 localchanges = dsmodified | dsadded
2958 localchanges = dsmodified | dsadded
2959 modified, added, removed = set(), set(), set()
2959 modified, added, removed = set(), set(), set()
2960 else:
2960 else:
2961 changes = repo.status(node1=parent, match=m)
2961 changes = repo.status(node1=parent, match=m)
2962 dsmodified = set(changes.modified)
2962 dsmodified = set(changes.modified)
2963 dsadded = set(changes.added)
2963 dsadded = set(changes.added)
2964 dsremoved = set(changes.removed)
2964 dsremoved = set(changes.removed)
2965 # store all local modifications, useful later for rename detection
2965 # store all local modifications, useful later for rename detection
2966 localchanges = dsmodified | dsadded
2966 localchanges = dsmodified | dsadded
2967
2967
2968 # only take into account for removes between wc and target
2968 # only take into account for removes between wc and target
2969 clean |= dsremoved - removed
2969 clean |= dsremoved - removed
2970 dsremoved &= removed
2970 dsremoved &= removed
2971 # distinct between dirstate remove and other
2971 # distinct between dirstate remove and other
2972 removed -= dsremoved
2972 removed -= dsremoved
2973
2973
2974 modadded = added & dsmodified
2974 modadded = added & dsmodified
2975 added -= modadded
2975 added -= modadded
2976
2976
2977 # tell newly modified apart.
2977 # tell newly modified apart.
2978 dsmodified &= modified
2978 dsmodified &= modified
2979 dsmodified |= modified & dsadded # dirstate added may need backup
2979 dsmodified |= modified & dsadded # dirstate added may need backup
2980 modified -= dsmodified
2980 modified -= dsmodified
2981
2981
2982 # We need to wait for some post-processing to update this set
2982 # We need to wait for some post-processing to update this set
2983 # before making the distinction. The dirstate will be used for
2983 # before making the distinction. The dirstate will be used for
2984 # that purpose.
2984 # that purpose.
2985 dsadded = added
2985 dsadded = added
2986
2986
2987 # in case of merge, files that are actually added can be reported as
2987 # in case of merge, files that are actually added can be reported as
2988 # modified, we need to post process the result
2988 # modified, we need to post process the result
2989 if p2 != nullid:
2989 if p2 != nullid:
2990 mergeadd = dsmodified - smf
2990 mergeadd = dsmodified - smf
2991 dsadded |= mergeadd
2991 dsadded |= mergeadd
2992 dsmodified -= mergeadd
2992 dsmodified -= mergeadd
2993
2993
2994 # if f is a rename, update `names` to also revert the source
2994 # if f is a rename, update `names` to also revert the source
2995 cwd = repo.getcwd()
2995 cwd = repo.getcwd()
2996 for f in localchanges:
2996 for f in localchanges:
2997 src = repo.dirstate.copied(f)
2997 src = repo.dirstate.copied(f)
2998 # XXX should we check for rename down to target node?
2998 # XXX should we check for rename down to target node?
2999 if src and src not in names and repo.dirstate[src] == 'r':
2999 if src and src not in names and repo.dirstate[src] == 'r':
3000 dsremoved.add(src)
3000 dsremoved.add(src)
3001 names[src] = (repo.pathto(src, cwd), True)
3001 names[src] = (repo.pathto(src, cwd), True)
3002
3002
3003 # distinguish between file to forget and the other
3003 # distinguish between file to forget and the other
3004 added = set()
3004 added = set()
3005 for abs in dsadded:
3005 for abs in dsadded:
3006 if repo.dirstate[abs] != 'a':
3006 if repo.dirstate[abs] != 'a':
3007 added.add(abs)
3007 added.add(abs)
3008 dsadded -= added
3008 dsadded -= added
3009
3009
3010 for abs in deladded:
3010 for abs in deladded:
3011 if repo.dirstate[abs] == 'a':
3011 if repo.dirstate[abs] == 'a':
3012 dsadded.add(abs)
3012 dsadded.add(abs)
3013 deladded -= dsadded
3013 deladded -= dsadded
3014
3014
3015 # For files marked as removed, we check if an unknown file is present at
3015 # 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.
3016 # 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
3017 # Making the distinction at this stage helps have simpler backup
3018 # logic.
3018 # logic.
3019 removunk = set()
3019 removunk = set()
3020 for abs in removed:
3020 for abs in removed:
3021 target = repo.wjoin(abs)
3021 target = repo.wjoin(abs)
3022 if os.path.lexists(target):
3022 if os.path.lexists(target):
3023 removunk.add(abs)
3023 removunk.add(abs)
3024 removed -= removunk
3024 removed -= removunk
3025
3025
3026 dsremovunk = set()
3026 dsremovunk = set()
3027 for abs in dsremoved:
3027 for abs in dsremoved:
3028 target = repo.wjoin(abs)
3028 target = repo.wjoin(abs)
3029 if os.path.lexists(target):
3029 if os.path.lexists(target):
3030 dsremovunk.add(abs)
3030 dsremovunk.add(abs)
3031 dsremoved -= dsremovunk
3031 dsremoved -= dsremovunk
3032
3032
3033 # action to be actually performed by revert
3033 # action to be actually performed by revert
3034 # (<list of file>, message>) tuple
3034 # (<list of file>, message>) tuple
3035 actions = {'revert': ([], _('reverting %s\n')),
3035 actions = {'revert': ([], _('reverting %s\n')),
3036 'add': ([], _('adding %s\n')),
3036 'add': ([], _('adding %s\n')),
3037 'remove': ([], _('removing %s\n')),
3037 'remove': ([], _('removing %s\n')),
3038 'drop': ([], _('removing %s\n')),
3038 'drop': ([], _('removing %s\n')),
3039 'forget': ([], _('forgetting %s\n')),
3039 'forget': ([], _('forgetting %s\n')),
3040 'undelete': ([], _('undeleting %s\n')),
3040 'undelete': ([], _('undeleting %s\n')),
3041 'noop': (None, _('no changes needed to %s\n')),
3041 'noop': (None, _('no changes needed to %s\n')),
3042 'unknown': (None, _('file not managed: %s\n')),
3042 'unknown': (None, _('file not managed: %s\n')),
3043 }
3043 }
3044
3044
3045 # "constant" that convey the backup strategy.
3045 # "constant" that convey the backup strategy.
3046 # All set to `discard` if `no-backup` is set do avoid checking
3046 # All set to `discard` if `no-backup` is set do avoid checking
3047 # no_backup lower in the code.
3047 # no_backup lower in the code.
3048 # These values are ordered for comparison purposes
3048 # These values are ordered for comparison purposes
3049 backupinteractive = 3 # do backup if interactively modified
3049 backupinteractive = 3 # do backup if interactively modified
3050 backup = 2 # unconditionally do backup
3050 backup = 2 # unconditionally do backup
3051 check = 1 # check if the existing file differs from target
3051 check = 1 # check if the existing file differs from target
3052 discard = 0 # never do backup
3052 discard = 0 # never do backup
3053 if opts.get('no_backup'):
3053 if opts.get('no_backup'):
3054 backupinteractive = backup = check = discard
3054 backupinteractive = backup = check = discard
3055 if interactive:
3055 if interactive:
3056 dsmodifiedbackup = backupinteractive
3056 dsmodifiedbackup = backupinteractive
3057 else:
3057 else:
3058 dsmodifiedbackup = backup
3058 dsmodifiedbackup = backup
3059 tobackup = set()
3059 tobackup = set()
3060
3060
3061 backupanddel = actions['remove']
3061 backupanddel = actions['remove']
3062 if not opts.get('no_backup'):
3062 if not opts.get('no_backup'):
3063 backupanddel = actions['drop']
3063 backupanddel = actions['drop']
3064
3064
3065 disptable = (
3065 disptable = (
3066 # dispatch table:
3066 # dispatch table:
3067 # file state
3067 # file state
3068 # action
3068 # action
3069 # make backup
3069 # make backup
3070
3070
3071 ## Sets that results that will change file on disk
3071 ## Sets that results that will change file on disk
3072 # Modified compared to target, no local change
3072 # Modified compared to target, no local change
3073 (modified, actions['revert'], discard),
3073 (modified, actions['revert'], discard),
3074 # Modified compared to target, but local file is deleted
3074 # Modified compared to target, but local file is deleted
3075 (deleted, actions['revert'], discard),
3075 (deleted, actions['revert'], discard),
3076 # Modified compared to target, local change
3076 # Modified compared to target, local change
3077 (dsmodified, actions['revert'], dsmodifiedbackup),
3077 (dsmodified, actions['revert'], dsmodifiedbackup),
3078 # Added since target
3078 # Added since target
3079 (added, actions['remove'], discard),
3079 (added, actions['remove'], discard),
3080 # Added in working directory
3080 # Added in working directory
3081 (dsadded, actions['forget'], discard),
3081 (dsadded, actions['forget'], discard),
3082 # Added since target, have local modification
3082 # Added since target, have local modification
3083 (modadded, backupanddel, backup),
3083 (modadded, backupanddel, backup),
3084 # Added since target but file is missing in working directory
3084 # Added since target but file is missing in working directory
3085 (deladded, actions['drop'], discard),
3085 (deladded, actions['drop'], discard),
3086 # Removed since target, before working copy parent
3086 # Removed since target, before working copy parent
3087 (removed, actions['add'], discard),
3087 (removed, actions['add'], discard),
3088 # Same as `removed` but an unknown file exists at the same path
3088 # Same as `removed` but an unknown file exists at the same path
3089 (removunk, actions['add'], check),
3089 (removunk, actions['add'], check),
3090 # Removed since targe, marked as such in working copy parent
3090 # Removed since targe, marked as such in working copy parent
3091 (dsremoved, actions['undelete'], discard),
3091 (dsremoved, actions['undelete'], discard),
3092 # Same as `dsremoved` but an unknown file exists at the same path
3092 # Same as `dsremoved` but an unknown file exists at the same path
3093 (dsremovunk, actions['undelete'], check),
3093 (dsremovunk, actions['undelete'], check),
3094 ## the following sets does not result in any file changes
3094 ## the following sets does not result in any file changes
3095 # File with no modification
3095 # File with no modification
3096 (clean, actions['noop'], discard),
3096 (clean, actions['noop'], discard),
3097 # Existing file, not tracked anywhere
3097 # Existing file, not tracked anywhere
3098 (unknown, actions['unknown'], discard),
3098 (unknown, actions['unknown'], discard),
3099 )
3099 )
3100
3100
3101 for abs, (rel, exact) in sorted(names.items()):
3101 for abs, (rel, exact) in sorted(names.items()):
3102 # target file to be touch on disk (relative to cwd)
3102 # target file to be touch on disk (relative to cwd)
3103 target = repo.wjoin(abs)
3103 target = repo.wjoin(abs)
3104 # search the entry in the dispatch table.
3104 # search the entry in the dispatch table.
3105 # if the file is in any of these sets, it was touched in the working
3105 # 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.
3106 # directory parent and we are sure it needs to be reverted.
3107 for table, (xlist, msg), dobackup in disptable:
3107 for table, (xlist, msg), dobackup in disptable:
3108 if abs not in table:
3108 if abs not in table:
3109 continue
3109 continue
3110 if xlist is not None:
3110 if xlist is not None:
3111 xlist.append(abs)
3111 xlist.append(abs)
3112 if dobackup:
3112 if dobackup:
3113 # If in interactive mode, don't automatically create
3113 # If in interactive mode, don't automatically create
3114 # .orig files (issue4793)
3114 # .orig files (issue4793)
3115 if dobackup == backupinteractive:
3115 if dobackup == backupinteractive:
3116 tobackup.add(abs)
3116 tobackup.add(abs)
3117 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3117 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3118 bakname = scmutil.origpath(ui, repo, rel)
3118 bakname = scmutil.origpath(ui, repo, rel)
3119 ui.note(_('saving current version of %s as %s\n') %
3119 ui.note(_('saving current version of %s as %s\n') %
3120 (rel, bakname))
3120 (rel, bakname))
3121 if not opts.get('dry_run'):
3121 if not opts.get('dry_run'):
3122 if interactive:
3122 if interactive:
3123 util.copyfile(target, bakname)
3123 util.copyfile(target, bakname)
3124 else:
3124 else:
3125 util.rename(target, bakname)
3125 util.rename(target, bakname)
3126 if ui.verbose or not exact:
3126 if ui.verbose or not exact:
3127 if not isinstance(msg, basestring):
3127 if not isinstance(msg, basestring):
3128 msg = msg(abs)
3128 msg = msg(abs)
3129 ui.status(msg % rel)
3129 ui.status(msg % rel)
3130 elif exact:
3130 elif exact:
3131 ui.warn(msg % rel)
3131 ui.warn(msg % rel)
3132 break
3132 break
3133
3133
3134 if not opts.get('dry_run'):
3134 if not opts.get('dry_run'):
3135 needdata = ('revert', 'add', 'undelete')
3135 needdata = ('revert', 'add', 'undelete')
3136 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3136 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3137 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3137 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3138
3138
3139 if targetsubs:
3139 if targetsubs:
3140 # Revert the subrepos on the revert list
3140 # Revert the subrepos on the revert list
3141 for sub in targetsubs:
3141 for sub in targetsubs:
3142 try:
3142 try:
3143 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3143 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3144 except KeyError:
3144 except KeyError:
3145 raise error.Abort("subrepository '%s' does not exist in %s!"
3145 raise error.Abort("subrepository '%s' does not exist in %s!"
3146 % (sub, short(ctx.node())))
3146 % (sub, short(ctx.node())))
3147
3147
3148 def _revertprefetch(repo, ctx, *files):
3148 def _revertprefetch(repo, ctx, *files):
3149 """Let extension changing the storage layer prefetch content"""
3149 """Let extension changing the storage layer prefetch content"""
3150 pass
3150 pass
3151
3151
3152 def _performrevert(repo, parents, ctx, actions, interactive=False,
3152 def _performrevert(repo, parents, ctx, actions, interactive=False,
3153 tobackup=None):
3153 tobackup=None):
3154 """function that actually perform all the actions computed for revert
3154 """function that actually perform all the actions computed for revert
3155
3155
3156 This is an independent function to let extension to plug in and react to
3156 This is an independent function to let extension to plug in and react to
3157 the imminent revert.
3157 the imminent revert.
3158
3158
3159 Make sure you have the working directory locked when calling this function.
3159 Make sure you have the working directory locked when calling this function.
3160 """
3160 """
3161 parent, p2 = parents
3161 parent, p2 = parents
3162 node = ctx.node()
3162 node = ctx.node()
3163 excluded_files = []
3163 excluded_files = []
3164 matcher_opts = {"exclude": excluded_files}
3164 matcher_opts = {"exclude": excluded_files}
3165
3165
3166 def checkout(f):
3166 def checkout(f):
3167 fc = ctx[f]
3167 fc = ctx[f]
3168 repo.wwrite(f, fc.data(), fc.flags())
3168 repo.wwrite(f, fc.data(), fc.flags())
3169
3169
3170 def doremove(f):
3170 def doremove(f):
3171 try:
3171 try:
3172 util.unlinkpath(repo.wjoin(f))
3172 util.unlinkpath(repo.wjoin(f))
3173 except OSError:
3173 except OSError:
3174 pass
3174 pass
3175 repo.dirstate.remove(f)
3175 repo.dirstate.remove(f)
3176
3176
3177 audit_path = pathutil.pathauditor(repo.root)
3177 audit_path = pathutil.pathauditor(repo.root)
3178 for f in actions['forget'][0]:
3178 for f in actions['forget'][0]:
3179 if interactive:
3179 if interactive:
3180 choice = repo.ui.promptchoice(
3180 choice = repo.ui.promptchoice(
3181 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3181 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3182 if choice == 0:
3182 if choice == 0:
3183 repo.dirstate.drop(f)
3183 repo.dirstate.drop(f)
3184 else:
3184 else:
3185 excluded_files.append(repo.wjoin(f))
3185 excluded_files.append(repo.wjoin(f))
3186 else:
3186 else:
3187 repo.dirstate.drop(f)
3187 repo.dirstate.drop(f)
3188 for f in actions['remove'][0]:
3188 for f in actions['remove'][0]:
3189 audit_path(f)
3189 audit_path(f)
3190 if interactive:
3190 if interactive:
3191 choice = repo.ui.promptchoice(
3191 choice = repo.ui.promptchoice(
3192 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3192 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3193 if choice == 0:
3193 if choice == 0:
3194 doremove(f)
3194 doremove(f)
3195 else:
3195 else:
3196 excluded_files.append(repo.wjoin(f))
3196 excluded_files.append(repo.wjoin(f))
3197 else:
3197 else:
3198 doremove(f)
3198 doremove(f)
3199 for f in actions['drop'][0]:
3199 for f in actions['drop'][0]:
3200 audit_path(f)
3200 audit_path(f)
3201 repo.dirstate.remove(f)
3201 repo.dirstate.remove(f)
3202
3202
3203 normal = None
3203 normal = None
3204 if node == parent:
3204 if node == parent:
3205 # We're reverting to our parent. If possible, we'd like status
3205 # 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
3206 # to report the file as clean. We have to use normallookup for
3207 # merges to avoid losing information about merged/dirty files.
3207 # merges to avoid losing information about merged/dirty files.
3208 if p2 != nullid:
3208 if p2 != nullid:
3209 normal = repo.dirstate.normallookup
3209 normal = repo.dirstate.normallookup
3210 else:
3210 else:
3211 normal = repo.dirstate.normal
3211 normal = repo.dirstate.normal
3212
3212
3213 newlyaddedandmodifiedfiles = set()
3213 newlyaddedandmodifiedfiles = set()
3214 if interactive:
3214 if interactive:
3215 # Prompt the user for changes to revert
3215 # Prompt the user for changes to revert
3216 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3216 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3217 m = scmutil.match(ctx, torevert, matcher_opts)
3217 m = scmutil.match(ctx, torevert, matcher_opts)
3218 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3218 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3219 diffopts.nodates = True
3219 diffopts.nodates = True
3220 diffopts.git = True
3220 diffopts.git = True
3221 reversehunks = repo.ui.configbool('experimental',
3221 reversehunks = repo.ui.configbool('experimental',
3222 'revertalternateinteractivemode',
3222 'revertalternateinteractivemode',
3223 True)
3223 True)
3224 if reversehunks:
3224 if reversehunks:
3225 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3225 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3226 else:
3226 else:
3227 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3227 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3228 originalchunks = patch.parsepatch(diff)
3228 originalchunks = patch.parsepatch(diff)
3229 operation = 'discard' if node == parent else 'revert'
3229 operation = 'discard' if node == parent else 'revert'
3230
3230
3231 try:
3231 try:
3232
3232
3233 chunks, opts = recordfilter(repo.ui, originalchunks,
3233 chunks, opts = recordfilter(repo.ui, originalchunks,
3234 operation=operation)
3234 operation=operation)
3235 if reversehunks:
3235 if reversehunks:
3236 chunks = patch.reversehunks(chunks)
3236 chunks = patch.reversehunks(chunks)
3237
3237
3238 except patch.PatchError as err:
3238 except patch.PatchError as err:
3239 raise error.Abort(_('error parsing patch: %s') % err)
3239 raise error.Abort(_('error parsing patch: %s') % err)
3240
3240
3241 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3241 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3242 if tobackup is None:
3242 if tobackup is None:
3243 tobackup = set()
3243 tobackup = set()
3244 # Apply changes
3244 # Apply changes
3245 fp = stringio()
3245 fp = stringio()
3246 for c in chunks:
3246 for c in chunks:
3247 # Create a backup file only if this hunk should be backed up
3247 # Create a backup file only if this hunk should be backed up
3248 if ishunk(c) and c.header.filename() in tobackup:
3248 if ishunk(c) and c.header.filename() in tobackup:
3249 abs = c.header.filename()
3249 abs = c.header.filename()
3250 target = repo.wjoin(abs)
3250 target = repo.wjoin(abs)
3251 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3251 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3252 util.copyfile(target, bakname)
3252 util.copyfile(target, bakname)
3253 tobackup.remove(abs)
3253 tobackup.remove(abs)
3254 c.write(fp)
3254 c.write(fp)
3255 dopatch = fp.tell()
3255 dopatch = fp.tell()
3256 fp.seek(0)
3256 fp.seek(0)
3257 if dopatch:
3257 if dopatch:
3258 try:
3258 try:
3259 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3259 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3260 except patch.PatchError as err:
3260 except patch.PatchError as err:
3261 raise error.Abort(str(err))
3261 raise error.Abort(str(err))
3262 del fp
3262 del fp
3263 else:
3263 else:
3264 for f in actions['revert'][0]:
3264 for f in actions['revert'][0]:
3265 checkout(f)
3265 checkout(f)
3266 if normal:
3266 if normal:
3267 normal(f)
3267 normal(f)
3268
3268
3269 for f in actions['add'][0]:
3269 for f in actions['add'][0]:
3270 # Don't checkout modified files, they are already created by the diff
3270 # Don't checkout modified files, they are already created by the diff
3271 if f not in newlyaddedandmodifiedfiles:
3271 if f not in newlyaddedandmodifiedfiles:
3272 checkout(f)
3272 checkout(f)
3273 repo.dirstate.add(f)
3273 repo.dirstate.add(f)
3274
3274
3275 normal = repo.dirstate.normallookup
3275 normal = repo.dirstate.normallookup
3276 if node == parent and p2 == nullid:
3276 if node == parent and p2 == nullid:
3277 normal = repo.dirstate.normal
3277 normal = repo.dirstate.normal
3278 for f in actions['undelete'][0]:
3278 for f in actions['undelete'][0]:
3279 checkout(f)
3279 checkout(f)
3280 normal(f)
3280 normal(f)
3281
3281
3282 copied = copies.pathcopies(repo[parent], ctx)
3282 copied = copies.pathcopies(repo[parent], ctx)
3283
3283
3284 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3284 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3285 if f in copied:
3285 if f in copied:
3286 repo.dirstate.copy(copied[f], f)
3286 repo.dirstate.copy(copied[f], f)
3287
3287
3288 def command(table):
3288 def command(table):
3289 """Returns a function object to be used as a decorator for making commands.
3289 """Returns a function object to be used as a decorator for making commands.
3290
3290
3291 This function receives a command table as its argument. The table should
3291 This function receives a command table as its argument. The table should
3292 be a dict.
3292 be a dict.
3293
3293
3294 The returned function can be used as a decorator for adding commands
3294 The returned function can be used as a decorator for adding commands
3295 to that command table. This function accepts multiple arguments to define
3295 to that command table. This function accepts multiple arguments to define
3296 a command.
3296 a command.
3297
3297
3298 The first argument is the command name.
3298 The first argument is the command name.
3299
3299
3300 The options argument is an iterable of tuples defining command arguments.
3300 The options argument is an iterable of tuples defining command arguments.
3301 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3301 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3302
3302
3303 The synopsis argument defines a short, one line summary of how to use the
3303 The synopsis argument defines a short, one line summary of how to use the
3304 command. This shows up in the help output.
3304 command. This shows up in the help output.
3305
3305
3306 The norepo argument defines whether the command does not require a
3306 The norepo argument defines whether the command does not require a
3307 local repository. Most commands operate against a repository, thus the
3307 local repository. Most commands operate against a repository, thus the
3308 default is False.
3308 default is False.
3309
3309
3310 The optionalrepo argument defines whether the command optionally requires
3310 The optionalrepo argument defines whether the command optionally requires
3311 a local repository.
3311 a local repository.
3312
3312
3313 The inferrepo argument defines whether to try to find a repository from the
3313 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
3314 command line arguments. If True, arguments will be examined for potential
3315 repository locations. See ``findrepo()``. If a repository is found, it
3315 repository locations. See ``findrepo()``. If a repository is found, it
3316 will be used.
3316 will be used.
3317 """
3317 """
3318 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3318 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3319 inferrepo=False):
3319 inferrepo=False):
3320 def decorator(func):
3320 def decorator(func):
3321 func.norepo = norepo
3321 func.norepo = norepo
3322 func.optionalrepo = optionalrepo
3322 func.optionalrepo = optionalrepo
3323 func.inferrepo = inferrepo
3323 func.inferrepo = inferrepo
3324 if synopsis:
3324 if synopsis:
3325 table[name] = func, list(options), synopsis
3325 table[name] = func, list(options), synopsis
3326 else:
3326 else:
3327 table[name] = func, list(options)
3327 table[name] = func, list(options)
3328 return func
3328 return func
3329 return decorator
3329 return decorator
3330
3330
3331 return cmd
3331 return cmd
3332
3332
3333 def checkunresolved(ms):
3333 def checkunresolved(ms):
3334 ms._repo.ui.deprecwarn('checkunresolved moved from cmdutil to mergeutil',
3334 ms._repo.ui.deprecwarn('checkunresolved moved from cmdutil to mergeutil',
3335 '4.1')
3335 '4.1')
3336 return mergeutil.checkunresolved(ms)
3336 return mergeutil.checkunresolved(ms)
3337
3337
3338 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3338 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3339 # commands.outgoing. "missing" is "missing" of the result of
3339 # commands.outgoing. "missing" is "missing" of the result of
3340 # "findcommonoutgoing()"
3340 # "findcommonoutgoing()"
3341 outgoinghooks = util.hooks()
3341 outgoinghooks = util.hooks()
3342
3342
3343 # a list of (ui, repo) functions called by commands.summary
3343 # a list of (ui, repo) functions called by commands.summary
3344 summaryhooks = util.hooks()
3344 summaryhooks = util.hooks()
3345
3345
3346 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3346 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3347 #
3347 #
3348 # functions should return tuple of booleans below, if 'changes' is None:
3348 # functions should return tuple of booleans below, if 'changes' is None:
3349 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3349 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3350 #
3350 #
3351 # otherwise, 'changes' is a tuple of tuples below:
3351 # otherwise, 'changes' is a tuple of tuples below:
3352 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3352 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3353 # - (desturl, destbranch, destpeer, outgoing)
3353 # - (desturl, destbranch, destpeer, outgoing)
3354 summaryremotehooks = util.hooks()
3354 summaryremotehooks = util.hooks()
3355
3355
3356 # A list of state files kept by multistep operations like graft.
3356 # A list of state files kept by multistep operations like graft.
3357 # Since graft cannot be aborted, it is considered 'clearable' by update.
3357 # Since graft cannot be aborted, it is considered 'clearable' by update.
3358 # note: bisect is intentionally excluded
3358 # note: bisect is intentionally excluded
3359 # (state file, clearable, allowcommit, error, hint)
3359 # (state file, clearable, allowcommit, error, hint)
3360 unfinishedstates = [
3360 unfinishedstates = [
3361 ('graftstate', True, False, _('graft in progress'),
3361 ('graftstate', True, False, _('graft in progress'),
3362 _("use 'hg graft --continue' or 'hg update' to abort")),
3362 _("use 'hg graft --continue' or 'hg update' to abort")),
3363 ('updatestate', True, False, _('last update was interrupted'),
3363 ('updatestate', True, False, _('last update was interrupted'),
3364 _("use 'hg update' to get a consistent checkout"))
3364 _("use 'hg update' to get a consistent checkout"))
3365 ]
3365 ]
3366
3366
3367 def checkunfinished(repo, commit=False):
3367 def checkunfinished(repo, commit=False):
3368 '''Look for an unfinished multistep operation, like graft, and abort
3368 '''Look for an unfinished multistep operation, like graft, and abort
3369 if found. It's probably good to check this right before
3369 if found. It's probably good to check this right before
3370 bailifchanged().
3370 bailifchanged().
3371 '''
3371 '''
3372 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3372 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3373 if commit and allowcommit:
3373 if commit and allowcommit:
3374 continue
3374 continue
3375 if repo.vfs.exists(f):
3375 if repo.vfs.exists(f):
3376 raise error.Abort(msg, hint=hint)
3376 raise error.Abort(msg, hint=hint)
3377
3377
3378 def clearunfinished(repo):
3378 def clearunfinished(repo):
3379 '''Check for unfinished operations (as above), and clear the ones
3379 '''Check for unfinished operations (as above), and clear the ones
3380 that are clearable.
3380 that are clearable.
3381 '''
3381 '''
3382 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3382 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3383 if not clearable and repo.vfs.exists(f):
3383 if not clearable and repo.vfs.exists(f):
3384 raise error.Abort(msg, hint=hint)
3384 raise error.Abort(msg, hint=hint)
3385 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3385 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3386 if clearable and repo.vfs.exists(f):
3386 if clearable and repo.vfs.exists(f):
3387 util.unlink(repo.join(f))
3387 util.unlink(repo.join(f))
3388
3388
3389 afterresolvedstates = [
3389 afterresolvedstates = [
3390 ('graftstate',
3390 ('graftstate',
3391 _('hg graft --continue')),
3391 _('hg graft --continue')),
3392 ]
3392 ]
3393
3393
3394 def howtocontinue(repo):
3394 def howtocontinue(repo):
3395 '''Check for an unfinished operation and return the command to finish
3395 '''Check for an unfinished operation and return the command to finish
3396 it.
3396 it.
3397
3397
3398 afterresolvedstates tuples define a .hg/{file} and the corresponding
3398 afterresolvedstates tuples define a .hg/{file} and the corresponding
3399 command needed to finish it.
3399 command needed to finish it.
3400
3400
3401 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3401 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3402 a boolean.
3402 a boolean.
3403 '''
3403 '''
3404 contmsg = _("continue: %s")
3404 contmsg = _("continue: %s")
3405 for f, msg in afterresolvedstates:
3405 for f, msg in afterresolvedstates:
3406 if repo.vfs.exists(f):
3406 if repo.vfs.exists(f):
3407 return contmsg % msg, True
3407 return contmsg % msg, True
3408 workingctx = repo[None]
3408 workingctx = repo[None]
3409 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3409 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3410 for s in workingctx.substate)
3410 for s in workingctx.substate)
3411 if dirty:
3411 if dirty:
3412 return contmsg % _("hg commit"), False
3412 return contmsg % _("hg commit"), False
3413 return None, None
3413 return None, None
3414
3414
3415 def checkafterresolved(repo):
3415 def checkafterresolved(repo):
3416 '''Inform the user about the next action after completing hg resolve
3416 '''Inform the user about the next action after completing hg resolve
3417
3417
3418 If there's a matching afterresolvedstates, howtocontinue will yield
3418 If there's a matching afterresolvedstates, howtocontinue will yield
3419 repo.ui.warn as the reporter.
3419 repo.ui.warn as the reporter.
3420
3420
3421 Otherwise, it will yield repo.ui.note.
3421 Otherwise, it will yield repo.ui.note.
3422 '''
3422 '''
3423 msg, warning = howtocontinue(repo)
3423 msg, warning = howtocontinue(repo)
3424 if msg is not None:
3424 if msg is not None:
3425 if warning:
3425 if warning:
3426 repo.ui.warn("%s\n" % msg)
3426 repo.ui.warn("%s\n" % msg)
3427 else:
3427 else:
3428 repo.ui.note("%s\n" % msg)
3428 repo.ui.note("%s\n" % msg)
3429
3429
3430 def wrongtooltocontinue(repo, task):
3430 def wrongtooltocontinue(repo, task):
3431 '''Raise an abort suggesting how to properly continue if there is an
3431 '''Raise an abort suggesting how to properly continue if there is an
3432 active task.
3432 active task.
3433
3433
3434 Uses howtocontinue() to find the active task.
3434 Uses howtocontinue() to find the active task.
3435
3435
3436 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3436 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3437 a hint.
3437 a hint.
3438 '''
3438 '''
3439 after = howtocontinue(repo)
3439 after = howtocontinue(repo)
3440 hint = None
3440 hint = None
3441 if after[1]:
3441 if after[1]:
3442 hint = after[0]
3442 hint = after[0]
3443 raise error.Abort(_('no %s in progress') % task, hint=hint)
3443 raise error.Abort(_('no %s in progress') % task, hint=hint)
3444
3444
3445 class dirstateguard(dirstateguardmod.dirstateguard):
3445 class dirstateguard(dirstateguardmod.dirstateguard):
3446 def __init__(self, repo, name):
3446 def __init__(self, repo, name):
3447 dirstateguardmod.dirstateguard.__init__(self, repo, name)
3447 dirstateguardmod.dirstateguard.__init__(self, repo, name)
3448 repo.ui.deprecwarn(
3448 repo.ui.deprecwarn(
3449 'dirstateguard has moved from cmdutil to dirstateguard',
3449 'dirstateguard has moved from cmdutil to dirstateguard',
3450 '4.1')
3450 '4.1')
General Comments 0
You need to be logged in to leave comments. Login now