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