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