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