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