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