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