##// END OF EJS Templates
changeset_printer: replace _meaningful_parentrevs() by changeset_templater's...
Yuya Nishihara -
r24484:ca62ae36 default
parent child Browse files
Show More
@@ -1,3257 +1,3243
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 = [self.repo[p]
1119 for p in self._meaningful_parentrevs(log, rev)]
1120
1121 # i18n: column positioning for "hg log"
1118 # i18n: column positioning for "hg log"
1122 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
1119 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
1123 label='log.changeset changeset.%s' % ctx.phasestr())
1120 label='log.changeset changeset.%s' % ctx.phasestr())
1124
1121
1125 # branches are shown first before any other names due to backwards
1122 # branches are shown first before any other names due to backwards
1126 # compatibility
1123 # compatibility
1127 branch = ctx.branch()
1124 branch = ctx.branch()
1128 # don't show the default branch name
1125 # don't show the default branch name
1129 if branch != 'default':
1126 if branch != 'default':
1130 # i18n: column positioning for "hg log"
1127 # i18n: column positioning for "hg log"
1131 self.ui.write(_("branch: %s\n") % branch,
1128 self.ui.write(_("branch: %s\n") % branch,
1132 label='log.branch')
1129 label='log.branch')
1133
1130
1134 for name, ns in self.repo.names.iteritems():
1131 for name, ns in self.repo.names.iteritems():
1135 # branches has special logic already handled above, so here we just
1132 # branches has special logic already handled above, so here we just
1136 # skip it
1133 # skip it
1137 if name == 'branches':
1134 if name == 'branches':
1138 continue
1135 continue
1139 # we will use the templatename as the color name since those two
1136 # we will use the templatename as the color name since those two
1140 # should be the same
1137 # should be the same
1141 for name in ns.names(self.repo, changenode):
1138 for name in ns.names(self.repo, changenode):
1142 self.ui.write(ns.logfmt % name,
1139 self.ui.write(ns.logfmt % name,
1143 label='log.%s' % ns.colorname)
1140 label='log.%s' % ns.colorname)
1144 if self.ui.debugflag:
1141 if self.ui.debugflag:
1145 # i18n: column positioning for "hg log"
1142 # i18n: column positioning for "hg log"
1146 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
1143 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
1147 label='log.phase')
1144 label='log.phase')
1148 for pctx in parents:
1145 for pctx in self._meaningful_parentrevs(ctx):
1149 label = 'log.parent changeset.%s' % pctx.phasestr()
1146 label = 'log.parent changeset.%s' % pctx.phasestr()
1150 # i18n: column positioning for "hg log"
1147 # i18n: column positioning for "hg log"
1151 self.ui.write(_("parent: %d:%s\n")
1148 self.ui.write(_("parent: %d:%s\n")
1152 % (pctx.rev(), hexfunc(pctx.node())),
1149 % (pctx.rev(), hexfunc(pctx.node())),
1153 label=label)
1150 label=label)
1154
1151
1155 if self.ui.debugflag:
1152 if self.ui.debugflag:
1156 mnode = ctx.manifestnode()
1153 mnode = ctx.manifestnode()
1157 # i18n: column positioning for "hg log"
1154 # i18n: column positioning for "hg log"
1158 self.ui.write(_("manifest: %d:%s\n") %
1155 self.ui.write(_("manifest: %d:%s\n") %
1159 (self.repo.manifest.rev(mnode), hex(mnode)),
1156 (self.repo.manifest.rev(mnode), hex(mnode)),
1160 label='ui.debug log.manifest')
1157 label='ui.debug log.manifest')
1161 # i18n: column positioning for "hg log"
1158 # i18n: column positioning for "hg log"
1162 self.ui.write(_("user: %s\n") % ctx.user(),
1159 self.ui.write(_("user: %s\n") % ctx.user(),
1163 label='log.user')
1160 label='log.user')
1164 # i18n: column positioning for "hg log"
1161 # i18n: column positioning for "hg log"
1165 self.ui.write(_("date: %s\n") % date,
1162 self.ui.write(_("date: %s\n") % date,
1166 label='log.date')
1163 label='log.date')
1167
1164
1168 if self.ui.debugflag:
1165 if self.ui.debugflag:
1169 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
1166 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
1170 for key, value in zip([# i18n: column positioning for "hg log"
1167 for key, value in zip([# i18n: column positioning for "hg log"
1171 _("files:"),
1168 _("files:"),
1172 # i18n: column positioning for "hg log"
1169 # i18n: column positioning for "hg log"
1173 _("files+:"),
1170 _("files+:"),
1174 # i18n: column positioning for "hg log"
1171 # i18n: column positioning for "hg log"
1175 _("files-:")], files):
1172 _("files-:")], files):
1176 if value:
1173 if value:
1177 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1174 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1178 label='ui.debug log.files')
1175 label='ui.debug log.files')
1179 elif ctx.files() and self.ui.verbose:
1176 elif ctx.files() and self.ui.verbose:
1180 # i18n: column positioning for "hg log"
1177 # i18n: column positioning for "hg log"
1181 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1178 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1182 label='ui.note log.files')
1179 label='ui.note log.files')
1183 if copies and self.ui.verbose:
1180 if copies and self.ui.verbose:
1184 copies = ['%s (%s)' % c for c in copies]
1181 copies = ['%s (%s)' % c for c in copies]
1185 # i18n: column positioning for "hg log"
1182 # i18n: column positioning for "hg log"
1186 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1183 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1187 label='ui.note log.copies')
1184 label='ui.note log.copies')
1188
1185
1189 extra = ctx.extra()
1186 extra = ctx.extra()
1190 if extra and self.ui.debugflag:
1187 if extra and self.ui.debugflag:
1191 for key, value in sorted(extra.items()):
1188 for key, value in sorted(extra.items()):
1192 # i18n: column positioning for "hg log"
1189 # i18n: column positioning for "hg log"
1193 self.ui.write(_("extra: %s=%s\n")
1190 self.ui.write(_("extra: %s=%s\n")
1194 % (key, value.encode('string_escape')),
1191 % (key, value.encode('string_escape')),
1195 label='ui.debug log.extra')
1192 label='ui.debug log.extra')
1196
1193
1197 description = ctx.description().strip()
1194 description = ctx.description().strip()
1198 if description:
1195 if description:
1199 if self.ui.verbose:
1196 if self.ui.verbose:
1200 self.ui.write(_("description:\n"),
1197 self.ui.write(_("description:\n"),
1201 label='ui.note log.description')
1198 label='ui.note log.description')
1202 self.ui.write(description,
1199 self.ui.write(description,
1203 label='ui.note log.description')
1200 label='ui.note log.description')
1204 self.ui.write("\n\n")
1201 self.ui.write("\n\n")
1205 else:
1202 else:
1206 # i18n: column positioning for "hg log"
1203 # i18n: column positioning for "hg log"
1207 self.ui.write(_("summary: %s\n") %
1204 self.ui.write(_("summary: %s\n") %
1208 description.splitlines()[0],
1205 description.splitlines()[0],
1209 label='log.summary')
1206 label='log.summary')
1210 self.ui.write("\n")
1207 self.ui.write("\n")
1211
1208
1212 self.showpatch(changenode, matchfn)
1209 self.showpatch(changenode, matchfn)
1213
1210
1214 def showpatch(self, node, matchfn):
1211 def showpatch(self, node, matchfn):
1215 if not matchfn:
1212 if not matchfn:
1216 matchfn = self.matchfn
1213 matchfn = self.matchfn
1217 if matchfn:
1214 if matchfn:
1218 stat = self.diffopts.get('stat')
1215 stat = self.diffopts.get('stat')
1219 diff = self.diffopts.get('patch')
1216 diff = self.diffopts.get('patch')
1220 diffopts = patch.diffallopts(self.ui, self.diffopts)
1217 diffopts = patch.diffallopts(self.ui, self.diffopts)
1221 prev = self.repo.changelog.parents(node)[0]
1218 prev = self.repo.changelog.parents(node)[0]
1222 if stat:
1219 if stat:
1223 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1220 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1224 match=matchfn, stat=True)
1221 match=matchfn, stat=True)
1225 if diff:
1222 if diff:
1226 if stat:
1223 if stat:
1227 self.ui.write("\n")
1224 self.ui.write("\n")
1228 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1225 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1229 match=matchfn, stat=False)
1226 match=matchfn, stat=False)
1230 self.ui.write("\n")
1227 self.ui.write("\n")
1231
1228
1232 def _meaningful_parentrevs(self, log, rev):
1229 def _meaningful_parentrevs(self, ctx):
1233 """Return list of meaningful (or all if debug) parentrevs for rev.
1230 """Return list of meaningful (or all if debug) parentrevs for rev.
1234
1231
1235 For merges (two non-nullrev revisions) both parents are meaningful.
1232 For merges (two non-nullrev revisions) both parents are meaningful.
1236 Otherwise the first parent revision is considered meaningful if it
1233 Otherwise the first parent revision is considered meaningful if it
1237 is not the preceding revision.
1234 is not the preceding revision.
1238 """
1235 """
1239 parents = log.parentrevs(rev)
1236 parents = ctx.parents()
1240 if not self.ui.debugflag and parents[1] == nullrev:
1237 if len(parents) > 1:
1241 if parents[0] >= rev - 1:
1238 return parents
1242 parents = []
1239 if self.ui.debugflag:
1243 else:
1240 return [parents[0], self.repo['null']]
1244 parents = [parents[0]]
1241 if parents[0].rev() >= ctx.rev() - 1:
1242 return []
1245 return parents
1243 return parents
1246
1244
1247 class jsonchangeset(changeset_printer):
1245 class jsonchangeset(changeset_printer):
1248 '''format changeset information.'''
1246 '''format changeset information.'''
1249
1247
1250 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1248 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1251 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1249 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1252 self.cache = {}
1250 self.cache = {}
1253 self._first = True
1251 self._first = True
1254
1252
1255 def close(self):
1253 def close(self):
1256 if not self._first:
1254 if not self._first:
1257 self.ui.write("\n]\n")
1255 self.ui.write("\n]\n")
1258 else:
1256 else:
1259 self.ui.write("[]\n")
1257 self.ui.write("[]\n")
1260
1258
1261 def _show(self, ctx, copies, matchfn, props):
1259 def _show(self, ctx, copies, matchfn, props):
1262 '''show a single changeset or file revision'''
1260 '''show a single changeset or file revision'''
1263 hexnode = hex(ctx.node())
1261 hexnode = hex(ctx.node())
1264 rev = ctx.rev()
1262 rev = ctx.rev()
1265 j = encoding.jsonescape
1263 j = encoding.jsonescape
1266
1264
1267 if self._first:
1265 if self._first:
1268 self.ui.write("[\n {")
1266 self.ui.write("[\n {")
1269 self._first = False
1267 self._first = False
1270 else:
1268 else:
1271 self.ui.write(",\n {")
1269 self.ui.write(",\n {")
1272
1270
1273 if self.ui.quiet:
1271 if self.ui.quiet:
1274 self.ui.write('\n "rev": %d' % rev)
1272 self.ui.write('\n "rev": %d' % rev)
1275 self.ui.write(',\n "node": "%s"' % hexnode)
1273 self.ui.write(',\n "node": "%s"' % hexnode)
1276 self.ui.write('\n }')
1274 self.ui.write('\n }')
1277 return
1275 return
1278
1276
1279 self.ui.write('\n "rev": %d' % rev)
1277 self.ui.write('\n "rev": %d' % rev)
1280 self.ui.write(',\n "node": "%s"' % hexnode)
1278 self.ui.write(',\n "node": "%s"' % hexnode)
1281 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1279 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1282 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1280 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1283 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1281 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1284 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1282 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1285 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1283 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1286
1284
1287 self.ui.write(',\n "bookmarks": [%s]' %
1285 self.ui.write(',\n "bookmarks": [%s]' %
1288 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1286 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1289 self.ui.write(',\n "tags": [%s]' %
1287 self.ui.write(',\n "tags": [%s]' %
1290 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1288 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1291 self.ui.write(',\n "parents": [%s]' %
1289 self.ui.write(',\n "parents": [%s]' %
1292 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1290 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1293
1291
1294 if self.ui.debugflag:
1292 if self.ui.debugflag:
1295 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1293 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1296
1294
1297 self.ui.write(',\n "extra": {%s}' %
1295 self.ui.write(',\n "extra": {%s}' %
1298 ", ".join('"%s": "%s"' % (j(k), j(v))
1296 ", ".join('"%s": "%s"' % (j(k), j(v))
1299 for k, v in ctx.extra().items()))
1297 for k, v in ctx.extra().items()))
1300
1298
1301 files = ctx.p1().status(ctx)
1299 files = ctx.p1().status(ctx)
1302 self.ui.write(',\n "modified": [%s]' %
1300 self.ui.write(',\n "modified": [%s]' %
1303 ", ".join('"%s"' % j(f) for f in files[0]))
1301 ", ".join('"%s"' % j(f) for f in files[0]))
1304 self.ui.write(',\n "added": [%s]' %
1302 self.ui.write(',\n "added": [%s]' %
1305 ", ".join('"%s"' % j(f) for f in files[1]))
1303 ", ".join('"%s"' % j(f) for f in files[1]))
1306 self.ui.write(',\n "removed": [%s]' %
1304 self.ui.write(',\n "removed": [%s]' %
1307 ", ".join('"%s"' % j(f) for f in files[2]))
1305 ", ".join('"%s"' % j(f) for f in files[2]))
1308
1306
1309 elif self.ui.verbose:
1307 elif self.ui.verbose:
1310 self.ui.write(',\n "files": [%s]' %
1308 self.ui.write(',\n "files": [%s]' %
1311 ", ".join('"%s"' % j(f) for f in ctx.files()))
1309 ", ".join('"%s"' % j(f) for f in ctx.files()))
1312
1310
1313 if copies:
1311 if copies:
1314 self.ui.write(',\n "copies": {%s}' %
1312 self.ui.write(',\n "copies": {%s}' %
1315 ", ".join('"%s": "%s"' % (j(k), j(v))
1313 ", ".join('"%s": "%s"' % (j(k), j(v))
1316 for k, v in copies))
1314 for k, v in copies))
1317
1315
1318 matchfn = self.matchfn
1316 matchfn = self.matchfn
1319 if matchfn:
1317 if matchfn:
1320 stat = self.diffopts.get('stat')
1318 stat = self.diffopts.get('stat')
1321 diff = self.diffopts.get('patch')
1319 diff = self.diffopts.get('patch')
1322 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1320 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1323 node, prev = ctx.node(), ctx.p1().node()
1321 node, prev = ctx.node(), ctx.p1().node()
1324 if stat:
1322 if stat:
1325 self.ui.pushbuffer()
1323 self.ui.pushbuffer()
1326 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1324 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1327 match=matchfn, stat=True)
1325 match=matchfn, stat=True)
1328 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1326 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1329 if diff:
1327 if diff:
1330 self.ui.pushbuffer()
1328 self.ui.pushbuffer()
1331 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1329 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1332 match=matchfn, stat=False)
1330 match=matchfn, stat=False)
1333 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1331 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1334
1332
1335 self.ui.write("\n }")
1333 self.ui.write("\n }")
1336
1334
1337 class changeset_templater(changeset_printer):
1335 class changeset_templater(changeset_printer):
1338 '''format changeset information.'''
1336 '''format changeset information.'''
1339
1337
1340 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1338 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1341 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1339 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1342 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1340 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1343 defaulttempl = {
1341 defaulttempl = {
1344 'parent': '{rev}:{node|formatnode} ',
1342 'parent': '{rev}:{node|formatnode} ',
1345 'manifest': '{rev}:{node|formatnode}',
1343 'manifest': '{rev}:{node|formatnode}',
1346 'file_copy': '{name} ({source})',
1344 'file_copy': '{name} ({source})',
1347 'extra': '{key}={value|stringescape}'
1345 'extra': '{key}={value|stringescape}'
1348 }
1346 }
1349 # filecopy is preserved for compatibility reasons
1347 # filecopy is preserved for compatibility reasons
1350 defaulttempl['filecopy'] = defaulttempl['file_copy']
1348 defaulttempl['filecopy'] = defaulttempl['file_copy']
1351 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1349 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1352 cache=defaulttempl)
1350 cache=defaulttempl)
1353 if tmpl:
1351 if tmpl:
1354 self.t.cache['changeset'] = tmpl
1352 self.t.cache['changeset'] = tmpl
1355
1353
1356 self.cache = {}
1354 self.cache = {}
1357
1355
1358 def _meaningful_parentrevs(self, ctx):
1359 """Return list of meaningful (or all if debug) parentrevs for rev.
1360 """
1361 parents = ctx.parents()
1362 if len(parents) > 1:
1363 return parents
1364 if self.ui.debugflag:
1365 return [parents[0], self.repo['null']]
1366 if parents[0].rev() >= ctx.rev() - 1:
1367 return []
1368 return parents
1369
1370 def _show(self, ctx, copies, matchfn, props):
1356 def _show(self, ctx, copies, matchfn, props):
1371 '''show a single changeset or file revision'''
1357 '''show a single changeset or file revision'''
1372
1358
1373 showlist = templatekw.showlist
1359 showlist = templatekw.showlist
1374
1360
1375 # showparents() behaviour depends on ui trace level which
1361 # showparents() behaviour depends on ui trace level which
1376 # causes unexpected behaviours at templating level and makes
1362 # causes unexpected behaviours at templating level and makes
1377 # it harder to extract it in a standalone function. Its
1363 # it harder to extract it in a standalone function. Its
1378 # behaviour cannot be changed so leave it here for now.
1364 # behaviour cannot be changed so leave it here for now.
1379 def showparents(**args):
1365 def showparents(**args):
1380 ctx = args['ctx']
1366 ctx = args['ctx']
1381 parents = [[('rev', p.rev()),
1367 parents = [[('rev', p.rev()),
1382 ('node', p.hex()),
1368 ('node', p.hex()),
1383 ('phase', p.phasestr())]
1369 ('phase', p.phasestr())]
1384 for p in self._meaningful_parentrevs(ctx)]
1370 for p in self._meaningful_parentrevs(ctx)]
1385 return showlist('parent', parents, **args)
1371 return showlist('parent', parents, **args)
1386
1372
1387 props = props.copy()
1373 props = props.copy()
1388 props.update(templatekw.keywords)
1374 props.update(templatekw.keywords)
1389 props['parents'] = showparents
1375 props['parents'] = showparents
1390 props['templ'] = self.t
1376 props['templ'] = self.t
1391 props['ctx'] = ctx
1377 props['ctx'] = ctx
1392 props['repo'] = self.repo
1378 props['repo'] = self.repo
1393 props['revcache'] = {'copies': copies}
1379 props['revcache'] = {'copies': copies}
1394 props['cache'] = self.cache
1380 props['cache'] = self.cache
1395
1381
1396 # find correct templates for current mode
1382 # find correct templates for current mode
1397
1383
1398 tmplmodes = [
1384 tmplmodes = [
1399 (True, None),
1385 (True, None),
1400 (self.ui.verbose, 'verbose'),
1386 (self.ui.verbose, 'verbose'),
1401 (self.ui.quiet, 'quiet'),
1387 (self.ui.quiet, 'quiet'),
1402 (self.ui.debugflag, 'debug'),
1388 (self.ui.debugflag, 'debug'),
1403 ]
1389 ]
1404
1390
1405 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1391 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1406 for mode, postfix in tmplmodes:
1392 for mode, postfix in tmplmodes:
1407 for type in types:
1393 for type in types:
1408 cur = postfix and ('%s_%s' % (type, postfix)) or type
1394 cur = postfix and ('%s_%s' % (type, postfix)) or type
1409 if mode and cur in self.t:
1395 if mode and cur in self.t:
1410 types[type] = cur
1396 types[type] = cur
1411
1397
1412 try:
1398 try:
1413
1399
1414 # write header
1400 # write header
1415 if types['header']:
1401 if types['header']:
1416 h = templater.stringify(self.t(types['header'], **props))
1402 h = templater.stringify(self.t(types['header'], **props))
1417 if self.buffered:
1403 if self.buffered:
1418 self.header[ctx.rev()] = h
1404 self.header[ctx.rev()] = h
1419 else:
1405 else:
1420 if self.lastheader != h:
1406 if self.lastheader != h:
1421 self.lastheader = h
1407 self.lastheader = h
1422 self.ui.write(h)
1408 self.ui.write(h)
1423
1409
1424 # write changeset metadata, then patch if requested
1410 # write changeset metadata, then patch if requested
1425 key = types['changeset']
1411 key = types['changeset']
1426 self.ui.write(templater.stringify(self.t(key, **props)))
1412 self.ui.write(templater.stringify(self.t(key, **props)))
1427 self.showpatch(ctx.node(), matchfn)
1413 self.showpatch(ctx.node(), matchfn)
1428
1414
1429 if types['footer']:
1415 if types['footer']:
1430 if not self.footer:
1416 if not self.footer:
1431 self.footer = templater.stringify(self.t(types['footer'],
1417 self.footer = templater.stringify(self.t(types['footer'],
1432 **props))
1418 **props))
1433
1419
1434 except KeyError, inst:
1420 except KeyError, inst:
1435 msg = _("%s: no key named '%s'")
1421 msg = _("%s: no key named '%s'")
1436 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1422 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1437 except SyntaxError, inst:
1423 except SyntaxError, inst:
1438 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1424 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1439
1425
1440 def gettemplate(ui, tmpl, style):
1426 def gettemplate(ui, tmpl, style):
1441 """
1427 """
1442 Find the template matching the given template spec or style.
1428 Find the template matching the given template spec or style.
1443 """
1429 """
1444
1430
1445 # ui settings
1431 # ui settings
1446 if not tmpl and not style: # template are stronger than style
1432 if not tmpl and not style: # template are stronger than style
1447 tmpl = ui.config('ui', 'logtemplate')
1433 tmpl = ui.config('ui', 'logtemplate')
1448 if tmpl:
1434 if tmpl:
1449 try:
1435 try:
1450 tmpl = templater.parsestring(tmpl)
1436 tmpl = templater.parsestring(tmpl)
1451 except SyntaxError:
1437 except SyntaxError:
1452 tmpl = templater.parsestring(tmpl, quoted=False)
1438 tmpl = templater.parsestring(tmpl, quoted=False)
1453 return tmpl, None
1439 return tmpl, None
1454 else:
1440 else:
1455 style = util.expandpath(ui.config('ui', 'style', ''))
1441 style = util.expandpath(ui.config('ui', 'style', ''))
1456
1442
1457 if not tmpl and style:
1443 if not tmpl and style:
1458 mapfile = style
1444 mapfile = style
1459 if not os.path.split(mapfile)[0]:
1445 if not os.path.split(mapfile)[0]:
1460 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1446 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1461 or templater.templatepath(mapfile))
1447 or templater.templatepath(mapfile))
1462 if mapname:
1448 if mapname:
1463 mapfile = mapname
1449 mapfile = mapname
1464 return None, mapfile
1450 return None, mapfile
1465
1451
1466 if not tmpl:
1452 if not tmpl:
1467 return None, None
1453 return None, None
1468
1454
1469 # looks like a literal template?
1455 # looks like a literal template?
1470 if '{' in tmpl:
1456 if '{' in tmpl:
1471 return tmpl, None
1457 return tmpl, None
1472
1458
1473 # perhaps a stock style?
1459 # perhaps a stock style?
1474 if not os.path.split(tmpl)[0]:
1460 if not os.path.split(tmpl)[0]:
1475 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1461 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1476 or templater.templatepath(tmpl))
1462 or templater.templatepath(tmpl))
1477 if mapname and os.path.isfile(mapname):
1463 if mapname and os.path.isfile(mapname):
1478 return None, mapname
1464 return None, mapname
1479
1465
1480 # perhaps it's a reference to [templates]
1466 # perhaps it's a reference to [templates]
1481 t = ui.config('templates', tmpl)
1467 t = ui.config('templates', tmpl)
1482 if t:
1468 if t:
1483 try:
1469 try:
1484 tmpl = templater.parsestring(t)
1470 tmpl = templater.parsestring(t)
1485 except SyntaxError:
1471 except SyntaxError:
1486 tmpl = templater.parsestring(t, quoted=False)
1472 tmpl = templater.parsestring(t, quoted=False)
1487 return tmpl, None
1473 return tmpl, None
1488
1474
1489 if tmpl == 'list':
1475 if tmpl == 'list':
1490 ui.write(_("available styles: %s\n") % templater.stylelist())
1476 ui.write(_("available styles: %s\n") % templater.stylelist())
1491 raise util.Abort(_("specify a template"))
1477 raise util.Abort(_("specify a template"))
1492
1478
1493 # perhaps it's a path to a map or a template
1479 # perhaps it's a path to a map or a template
1494 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1480 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1495 # is it a mapfile for a style?
1481 # is it a mapfile for a style?
1496 if os.path.basename(tmpl).startswith("map-"):
1482 if os.path.basename(tmpl).startswith("map-"):
1497 return None, os.path.realpath(tmpl)
1483 return None, os.path.realpath(tmpl)
1498 tmpl = open(tmpl).read()
1484 tmpl = open(tmpl).read()
1499 return tmpl, None
1485 return tmpl, None
1500
1486
1501 # constant string?
1487 # constant string?
1502 return tmpl, None
1488 return tmpl, None
1503
1489
1504 def show_changeset(ui, repo, opts, buffered=False):
1490 def show_changeset(ui, repo, opts, buffered=False):
1505 """show one changeset using template or regular display.
1491 """show one changeset using template or regular display.
1506
1492
1507 Display format will be the first non-empty hit of:
1493 Display format will be the first non-empty hit of:
1508 1. option 'template'
1494 1. option 'template'
1509 2. option 'style'
1495 2. option 'style'
1510 3. [ui] setting 'logtemplate'
1496 3. [ui] setting 'logtemplate'
1511 4. [ui] setting 'style'
1497 4. [ui] setting 'style'
1512 If all of these values are either the unset or the empty string,
1498 If all of these values are either the unset or the empty string,
1513 regular display via changeset_printer() is done.
1499 regular display via changeset_printer() is done.
1514 """
1500 """
1515 # options
1501 # options
1516 matchfn = None
1502 matchfn = None
1517 if opts.get('patch') or opts.get('stat'):
1503 if opts.get('patch') or opts.get('stat'):
1518 matchfn = scmutil.matchall(repo)
1504 matchfn = scmutil.matchall(repo)
1519
1505
1520 if opts.get('template') == 'json':
1506 if opts.get('template') == 'json':
1521 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1507 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1522
1508
1523 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1509 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1524
1510
1525 if not tmpl and not mapfile:
1511 if not tmpl and not mapfile:
1526 return changeset_printer(ui, repo, matchfn, opts, buffered)
1512 return changeset_printer(ui, repo, matchfn, opts, buffered)
1527
1513
1528 try:
1514 try:
1529 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1515 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1530 buffered)
1516 buffered)
1531 except SyntaxError, inst:
1517 except SyntaxError, inst:
1532 raise util.Abort(inst.args[0])
1518 raise util.Abort(inst.args[0])
1533 return t
1519 return t
1534
1520
1535 def showmarker(ui, marker):
1521 def showmarker(ui, marker):
1536 """utility function to display obsolescence marker in a readable way
1522 """utility function to display obsolescence marker in a readable way
1537
1523
1538 To be used by debug function."""
1524 To be used by debug function."""
1539 ui.write(hex(marker.precnode()))
1525 ui.write(hex(marker.precnode()))
1540 for repl in marker.succnodes():
1526 for repl in marker.succnodes():
1541 ui.write(' ')
1527 ui.write(' ')
1542 ui.write(hex(repl))
1528 ui.write(hex(repl))
1543 ui.write(' %X ' % marker.flags())
1529 ui.write(' %X ' % marker.flags())
1544 parents = marker.parentnodes()
1530 parents = marker.parentnodes()
1545 if parents is not None:
1531 if parents is not None:
1546 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1532 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1547 ui.write('(%s) ' % util.datestr(marker.date()))
1533 ui.write('(%s) ' % util.datestr(marker.date()))
1548 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1534 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1549 sorted(marker.metadata().items())
1535 sorted(marker.metadata().items())
1550 if t[0] != 'date')))
1536 if t[0] != 'date')))
1551 ui.write('\n')
1537 ui.write('\n')
1552
1538
1553 def finddate(ui, repo, date):
1539 def finddate(ui, repo, date):
1554 """Find the tipmost changeset that matches the given date spec"""
1540 """Find the tipmost changeset that matches the given date spec"""
1555
1541
1556 df = util.matchdate(date)
1542 df = util.matchdate(date)
1557 m = scmutil.matchall(repo)
1543 m = scmutil.matchall(repo)
1558 results = {}
1544 results = {}
1559
1545
1560 def prep(ctx, fns):
1546 def prep(ctx, fns):
1561 d = ctx.date()
1547 d = ctx.date()
1562 if df(d[0]):
1548 if df(d[0]):
1563 results[ctx.rev()] = d
1549 results[ctx.rev()] = d
1564
1550
1565 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1551 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1566 rev = ctx.rev()
1552 rev = ctx.rev()
1567 if rev in results:
1553 if rev in results:
1568 ui.status(_("found revision %s from %s\n") %
1554 ui.status(_("found revision %s from %s\n") %
1569 (rev, util.datestr(results[rev])))
1555 (rev, util.datestr(results[rev])))
1570 return str(rev)
1556 return str(rev)
1571
1557
1572 raise util.Abort(_("revision matching date not found"))
1558 raise util.Abort(_("revision matching date not found"))
1573
1559
1574 def increasingwindows(windowsize=8, sizelimit=512):
1560 def increasingwindows(windowsize=8, sizelimit=512):
1575 while True:
1561 while True:
1576 yield windowsize
1562 yield windowsize
1577 if windowsize < sizelimit:
1563 if windowsize < sizelimit:
1578 windowsize *= 2
1564 windowsize *= 2
1579
1565
1580 class FileWalkError(Exception):
1566 class FileWalkError(Exception):
1581 pass
1567 pass
1582
1568
1583 def walkfilerevs(repo, match, follow, revs, fncache):
1569 def walkfilerevs(repo, match, follow, revs, fncache):
1584 '''Walks the file history for the matched files.
1570 '''Walks the file history for the matched files.
1585
1571
1586 Returns the changeset revs that are involved in the file history.
1572 Returns the changeset revs that are involved in the file history.
1587
1573
1588 Throws FileWalkError if the file history can't be walked using
1574 Throws FileWalkError if the file history can't be walked using
1589 filelogs alone.
1575 filelogs alone.
1590 '''
1576 '''
1591 wanted = set()
1577 wanted = set()
1592 copies = []
1578 copies = []
1593 minrev, maxrev = min(revs), max(revs)
1579 minrev, maxrev = min(revs), max(revs)
1594 def filerevgen(filelog, last):
1580 def filerevgen(filelog, last):
1595 """
1581 """
1596 Only files, no patterns. Check the history of each file.
1582 Only files, no patterns. Check the history of each file.
1597
1583
1598 Examines filelog entries within minrev, maxrev linkrev range
1584 Examines filelog entries within minrev, maxrev linkrev range
1599 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1585 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1600 tuples in backwards order
1586 tuples in backwards order
1601 """
1587 """
1602 cl_count = len(repo)
1588 cl_count = len(repo)
1603 revs = []
1589 revs = []
1604 for j in xrange(0, last + 1):
1590 for j in xrange(0, last + 1):
1605 linkrev = filelog.linkrev(j)
1591 linkrev = filelog.linkrev(j)
1606 if linkrev < minrev:
1592 if linkrev < minrev:
1607 continue
1593 continue
1608 # only yield rev for which we have the changelog, it can
1594 # only yield rev for which we have the changelog, it can
1609 # happen while doing "hg log" during a pull or commit
1595 # happen while doing "hg log" during a pull or commit
1610 if linkrev >= cl_count:
1596 if linkrev >= cl_count:
1611 break
1597 break
1612
1598
1613 parentlinkrevs = []
1599 parentlinkrevs = []
1614 for p in filelog.parentrevs(j):
1600 for p in filelog.parentrevs(j):
1615 if p != nullrev:
1601 if p != nullrev:
1616 parentlinkrevs.append(filelog.linkrev(p))
1602 parentlinkrevs.append(filelog.linkrev(p))
1617 n = filelog.node(j)
1603 n = filelog.node(j)
1618 revs.append((linkrev, parentlinkrevs,
1604 revs.append((linkrev, parentlinkrevs,
1619 follow and filelog.renamed(n)))
1605 follow and filelog.renamed(n)))
1620
1606
1621 return reversed(revs)
1607 return reversed(revs)
1622 def iterfiles():
1608 def iterfiles():
1623 pctx = repo['.']
1609 pctx = repo['.']
1624 for filename in match.files():
1610 for filename in match.files():
1625 if follow:
1611 if follow:
1626 if filename not in pctx:
1612 if filename not in pctx:
1627 raise util.Abort(_('cannot follow file not in parent '
1613 raise util.Abort(_('cannot follow file not in parent '
1628 'revision: "%s"') % filename)
1614 'revision: "%s"') % filename)
1629 yield filename, pctx[filename].filenode()
1615 yield filename, pctx[filename].filenode()
1630 else:
1616 else:
1631 yield filename, None
1617 yield filename, None
1632 for filename_node in copies:
1618 for filename_node in copies:
1633 yield filename_node
1619 yield filename_node
1634
1620
1635 for file_, node in iterfiles():
1621 for file_, node in iterfiles():
1636 filelog = repo.file(file_)
1622 filelog = repo.file(file_)
1637 if not len(filelog):
1623 if not len(filelog):
1638 if node is None:
1624 if node is None:
1639 # A zero count may be a directory or deleted file, so
1625 # A zero count may be a directory or deleted file, so
1640 # try to find matching entries on the slow path.
1626 # try to find matching entries on the slow path.
1641 if follow:
1627 if follow:
1642 raise util.Abort(
1628 raise util.Abort(
1643 _('cannot follow nonexistent file: "%s"') % file_)
1629 _('cannot follow nonexistent file: "%s"') % file_)
1644 raise FileWalkError("Cannot walk via filelog")
1630 raise FileWalkError("Cannot walk via filelog")
1645 else:
1631 else:
1646 continue
1632 continue
1647
1633
1648 if node is None:
1634 if node is None:
1649 last = len(filelog) - 1
1635 last = len(filelog) - 1
1650 else:
1636 else:
1651 last = filelog.rev(node)
1637 last = filelog.rev(node)
1652
1638
1653 # keep track of all ancestors of the file
1639 # keep track of all ancestors of the file
1654 ancestors = set([filelog.linkrev(last)])
1640 ancestors = set([filelog.linkrev(last)])
1655
1641
1656 # iterate from latest to oldest revision
1642 # iterate from latest to oldest revision
1657 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1643 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1658 if not follow:
1644 if not follow:
1659 if rev > maxrev:
1645 if rev > maxrev:
1660 continue
1646 continue
1661 else:
1647 else:
1662 # Note that last might not be the first interesting
1648 # Note that last might not be the first interesting
1663 # rev to us:
1649 # rev to us:
1664 # if the file has been changed after maxrev, we'll
1650 # if the file has been changed after maxrev, we'll
1665 # have linkrev(last) > maxrev, and we still need
1651 # have linkrev(last) > maxrev, and we still need
1666 # to explore the file graph
1652 # to explore the file graph
1667 if rev not in ancestors:
1653 if rev not in ancestors:
1668 continue
1654 continue
1669 # XXX insert 1327 fix here
1655 # XXX insert 1327 fix here
1670 if flparentlinkrevs:
1656 if flparentlinkrevs:
1671 ancestors.update(flparentlinkrevs)
1657 ancestors.update(flparentlinkrevs)
1672
1658
1673 fncache.setdefault(rev, []).append(file_)
1659 fncache.setdefault(rev, []).append(file_)
1674 wanted.add(rev)
1660 wanted.add(rev)
1675 if copied:
1661 if copied:
1676 copies.append(copied)
1662 copies.append(copied)
1677
1663
1678 return wanted
1664 return wanted
1679
1665
1680 class _followfilter(object):
1666 class _followfilter(object):
1681 def __init__(self, repo, onlyfirst=False):
1667 def __init__(self, repo, onlyfirst=False):
1682 self.repo = repo
1668 self.repo = repo
1683 self.startrev = nullrev
1669 self.startrev = nullrev
1684 self.roots = set()
1670 self.roots = set()
1685 self.onlyfirst = onlyfirst
1671 self.onlyfirst = onlyfirst
1686
1672
1687 def match(self, rev):
1673 def match(self, rev):
1688 def realparents(rev):
1674 def realparents(rev):
1689 if self.onlyfirst:
1675 if self.onlyfirst:
1690 return self.repo.changelog.parentrevs(rev)[0:1]
1676 return self.repo.changelog.parentrevs(rev)[0:1]
1691 else:
1677 else:
1692 return filter(lambda x: x != nullrev,
1678 return filter(lambda x: x != nullrev,
1693 self.repo.changelog.parentrevs(rev))
1679 self.repo.changelog.parentrevs(rev))
1694
1680
1695 if self.startrev == nullrev:
1681 if self.startrev == nullrev:
1696 self.startrev = rev
1682 self.startrev = rev
1697 return True
1683 return True
1698
1684
1699 if rev > self.startrev:
1685 if rev > self.startrev:
1700 # forward: all descendants
1686 # forward: all descendants
1701 if not self.roots:
1687 if not self.roots:
1702 self.roots.add(self.startrev)
1688 self.roots.add(self.startrev)
1703 for parent in realparents(rev):
1689 for parent in realparents(rev):
1704 if parent in self.roots:
1690 if parent in self.roots:
1705 self.roots.add(rev)
1691 self.roots.add(rev)
1706 return True
1692 return True
1707 else:
1693 else:
1708 # backwards: all parents
1694 # backwards: all parents
1709 if not self.roots:
1695 if not self.roots:
1710 self.roots.update(realparents(self.startrev))
1696 self.roots.update(realparents(self.startrev))
1711 if rev in self.roots:
1697 if rev in self.roots:
1712 self.roots.remove(rev)
1698 self.roots.remove(rev)
1713 self.roots.update(realparents(rev))
1699 self.roots.update(realparents(rev))
1714 return True
1700 return True
1715
1701
1716 return False
1702 return False
1717
1703
1718 def walkchangerevs(repo, match, opts, prepare):
1704 def walkchangerevs(repo, match, opts, prepare):
1719 '''Iterate over files and the revs in which they changed.
1705 '''Iterate over files and the revs in which they changed.
1720
1706
1721 Callers most commonly need to iterate backwards over the history
1707 Callers most commonly need to iterate backwards over the history
1722 in which they are interested. Doing so has awful (quadratic-looking)
1708 in which they are interested. Doing so has awful (quadratic-looking)
1723 performance, so we use iterators in a "windowed" way.
1709 performance, so we use iterators in a "windowed" way.
1724
1710
1725 We walk a window of revisions in the desired order. Within the
1711 We walk a window of revisions in the desired order. Within the
1726 window, we first walk forwards to gather data, then in the desired
1712 window, we first walk forwards to gather data, then in the desired
1727 order (usually backwards) to display it.
1713 order (usually backwards) to display it.
1728
1714
1729 This function returns an iterator yielding contexts. Before
1715 This function returns an iterator yielding contexts. Before
1730 yielding each context, the iterator will first call the prepare
1716 yielding each context, the iterator will first call the prepare
1731 function on each context in the window in forward order.'''
1717 function on each context in the window in forward order.'''
1732
1718
1733 follow = opts.get('follow') or opts.get('follow_first')
1719 follow = opts.get('follow') or opts.get('follow_first')
1734 revs = _logrevs(repo, opts)
1720 revs = _logrevs(repo, opts)
1735 if not revs:
1721 if not revs:
1736 return []
1722 return []
1737 wanted = set()
1723 wanted = set()
1738 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1724 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1739 fncache = {}
1725 fncache = {}
1740 change = repo.changectx
1726 change = repo.changectx
1741
1727
1742 # First step is to fill wanted, the set of revisions that we want to yield.
1728 # First step is to fill wanted, the set of revisions that we want to yield.
1743 # When it does not induce extra cost, we also fill fncache for revisions in
1729 # When it does not induce extra cost, we also fill fncache for revisions in
1744 # wanted: a cache of filenames that were changed (ctx.files()) and that
1730 # wanted: a cache of filenames that were changed (ctx.files()) and that
1745 # match the file filtering conditions.
1731 # match the file filtering conditions.
1746
1732
1747 if match.always():
1733 if match.always():
1748 # No files, no patterns. Display all revs.
1734 # No files, no patterns. Display all revs.
1749 wanted = revs
1735 wanted = revs
1750
1736
1751 if not slowpath and match.files():
1737 if not slowpath and match.files():
1752 # We only have to read through the filelog to find wanted revisions
1738 # We only have to read through the filelog to find wanted revisions
1753
1739
1754 try:
1740 try:
1755 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1741 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1756 except FileWalkError:
1742 except FileWalkError:
1757 slowpath = True
1743 slowpath = True
1758
1744
1759 # We decided to fall back to the slowpath because at least one
1745 # We decided to fall back to the slowpath because at least one
1760 # of the paths was not a file. Check to see if at least one of them
1746 # of the paths was not a file. Check to see if at least one of them
1761 # existed in history, otherwise simply return
1747 # existed in history, otherwise simply return
1762 for path in match.files():
1748 for path in match.files():
1763 if path == '.' or path in repo.store:
1749 if path == '.' or path in repo.store:
1764 break
1750 break
1765 else:
1751 else:
1766 return []
1752 return []
1767
1753
1768 if slowpath:
1754 if slowpath:
1769 # We have to read the changelog to match filenames against
1755 # We have to read the changelog to match filenames against
1770 # changed files
1756 # changed files
1771
1757
1772 if follow:
1758 if follow:
1773 raise util.Abort(_('can only follow copies/renames for explicit '
1759 raise util.Abort(_('can only follow copies/renames for explicit '
1774 'filenames'))
1760 'filenames'))
1775
1761
1776 # The slow path checks files modified in every changeset.
1762 # The slow path checks files modified in every changeset.
1777 # This is really slow on large repos, so compute the set lazily.
1763 # This is really slow on large repos, so compute the set lazily.
1778 class lazywantedset(object):
1764 class lazywantedset(object):
1779 def __init__(self):
1765 def __init__(self):
1780 self.set = set()
1766 self.set = set()
1781 self.revs = set(revs)
1767 self.revs = set(revs)
1782
1768
1783 # No need to worry about locality here because it will be accessed
1769 # No need to worry about locality here because it will be accessed
1784 # in the same order as the increasing window below.
1770 # in the same order as the increasing window below.
1785 def __contains__(self, value):
1771 def __contains__(self, value):
1786 if value in self.set:
1772 if value in self.set:
1787 return True
1773 return True
1788 elif not value in self.revs:
1774 elif not value in self.revs:
1789 return False
1775 return False
1790 else:
1776 else:
1791 self.revs.discard(value)
1777 self.revs.discard(value)
1792 ctx = change(value)
1778 ctx = change(value)
1793 matches = filter(match, ctx.files())
1779 matches = filter(match, ctx.files())
1794 if matches:
1780 if matches:
1795 fncache[value] = matches
1781 fncache[value] = matches
1796 self.set.add(value)
1782 self.set.add(value)
1797 return True
1783 return True
1798 return False
1784 return False
1799
1785
1800 def discard(self, value):
1786 def discard(self, value):
1801 self.revs.discard(value)
1787 self.revs.discard(value)
1802 self.set.discard(value)
1788 self.set.discard(value)
1803
1789
1804 wanted = lazywantedset()
1790 wanted = lazywantedset()
1805
1791
1806 # it might be worthwhile to do this in the iterator if the rev range
1792 # it might be worthwhile to do this in the iterator if the rev range
1807 # is descending and the prune args are all within that range
1793 # is descending and the prune args are all within that range
1808 for rev in opts.get('prune', ()):
1794 for rev in opts.get('prune', ()):
1809 rev = repo[rev].rev()
1795 rev = repo[rev].rev()
1810 ff = _followfilter(repo)
1796 ff = _followfilter(repo)
1811 stop = min(revs[0], revs[-1])
1797 stop = min(revs[0], revs[-1])
1812 for x in xrange(rev, stop - 1, -1):
1798 for x in xrange(rev, stop - 1, -1):
1813 if ff.match(x):
1799 if ff.match(x):
1814 wanted = wanted - [x]
1800 wanted = wanted - [x]
1815
1801
1816 # Now that wanted is correctly initialized, we can iterate over the
1802 # Now that wanted is correctly initialized, we can iterate over the
1817 # revision range, yielding only revisions in wanted.
1803 # revision range, yielding only revisions in wanted.
1818 def iterate():
1804 def iterate():
1819 if follow and not match.files():
1805 if follow and not match.files():
1820 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1806 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1821 def want(rev):
1807 def want(rev):
1822 return ff.match(rev) and rev in wanted
1808 return ff.match(rev) and rev in wanted
1823 else:
1809 else:
1824 def want(rev):
1810 def want(rev):
1825 return rev in wanted
1811 return rev in wanted
1826
1812
1827 it = iter(revs)
1813 it = iter(revs)
1828 stopiteration = False
1814 stopiteration = False
1829 for windowsize in increasingwindows():
1815 for windowsize in increasingwindows():
1830 nrevs = []
1816 nrevs = []
1831 for i in xrange(windowsize):
1817 for i in xrange(windowsize):
1832 try:
1818 try:
1833 rev = it.next()
1819 rev = it.next()
1834 if want(rev):
1820 if want(rev):
1835 nrevs.append(rev)
1821 nrevs.append(rev)
1836 except (StopIteration):
1822 except (StopIteration):
1837 stopiteration = True
1823 stopiteration = True
1838 break
1824 break
1839 for rev in sorted(nrevs):
1825 for rev in sorted(nrevs):
1840 fns = fncache.get(rev)
1826 fns = fncache.get(rev)
1841 ctx = change(rev)
1827 ctx = change(rev)
1842 if not fns:
1828 if not fns:
1843 def fns_generator():
1829 def fns_generator():
1844 for f in ctx.files():
1830 for f in ctx.files():
1845 if match(f):
1831 if match(f):
1846 yield f
1832 yield f
1847 fns = fns_generator()
1833 fns = fns_generator()
1848 prepare(ctx, fns)
1834 prepare(ctx, fns)
1849 for rev in nrevs:
1835 for rev in nrevs:
1850 yield change(rev)
1836 yield change(rev)
1851
1837
1852 if stopiteration:
1838 if stopiteration:
1853 break
1839 break
1854
1840
1855 return iterate()
1841 return iterate()
1856
1842
1857 def _makefollowlogfilematcher(repo, files, followfirst):
1843 def _makefollowlogfilematcher(repo, files, followfirst):
1858 # When displaying a revision with --patch --follow FILE, we have
1844 # When displaying a revision with --patch --follow FILE, we have
1859 # to know which file of the revision must be diffed. With
1845 # to know which file of the revision must be diffed. With
1860 # --follow, we want the names of the ancestors of FILE in the
1846 # --follow, we want the names of the ancestors of FILE in the
1861 # revision, stored in "fcache". "fcache" is populated by
1847 # revision, stored in "fcache". "fcache" is populated by
1862 # reproducing the graph traversal already done by --follow revset
1848 # reproducing the graph traversal already done by --follow revset
1863 # and relating linkrevs to file names (which is not "correct" but
1849 # and relating linkrevs to file names (which is not "correct" but
1864 # good enough).
1850 # good enough).
1865 fcache = {}
1851 fcache = {}
1866 fcacheready = [False]
1852 fcacheready = [False]
1867 pctx = repo['.']
1853 pctx = repo['.']
1868
1854
1869 def populate():
1855 def populate():
1870 for fn in files:
1856 for fn in files:
1871 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1857 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1872 for c in i:
1858 for c in i:
1873 fcache.setdefault(c.linkrev(), set()).add(c.path())
1859 fcache.setdefault(c.linkrev(), set()).add(c.path())
1874
1860
1875 def filematcher(rev):
1861 def filematcher(rev):
1876 if not fcacheready[0]:
1862 if not fcacheready[0]:
1877 # Lazy initialization
1863 # Lazy initialization
1878 fcacheready[0] = True
1864 fcacheready[0] = True
1879 populate()
1865 populate()
1880 return scmutil.matchfiles(repo, fcache.get(rev, []))
1866 return scmutil.matchfiles(repo, fcache.get(rev, []))
1881
1867
1882 return filematcher
1868 return filematcher
1883
1869
1884 def _makenofollowlogfilematcher(repo, pats, opts):
1870 def _makenofollowlogfilematcher(repo, pats, opts):
1885 '''hook for extensions to override the filematcher for non-follow cases'''
1871 '''hook for extensions to override the filematcher for non-follow cases'''
1886 return None
1872 return None
1887
1873
1888 def _makelogrevset(repo, pats, opts, revs):
1874 def _makelogrevset(repo, pats, opts, revs):
1889 """Return (expr, filematcher) where expr is a revset string built
1875 """Return (expr, filematcher) where expr is a revset string built
1890 from log options and file patterns or None. If --stat or --patch
1876 from log options and file patterns or None. If --stat or --patch
1891 are not passed filematcher is None. Otherwise it is a callable
1877 are not passed filematcher is None. Otherwise it is a callable
1892 taking a revision number and returning a match objects filtering
1878 taking a revision number and returning a match objects filtering
1893 the files to be detailed when displaying the revision.
1879 the files to be detailed when displaying the revision.
1894 """
1880 """
1895 opt2revset = {
1881 opt2revset = {
1896 'no_merges': ('not merge()', None),
1882 'no_merges': ('not merge()', None),
1897 'only_merges': ('merge()', None),
1883 'only_merges': ('merge()', None),
1898 '_ancestors': ('ancestors(%(val)s)', None),
1884 '_ancestors': ('ancestors(%(val)s)', None),
1899 '_fancestors': ('_firstancestors(%(val)s)', None),
1885 '_fancestors': ('_firstancestors(%(val)s)', None),
1900 '_descendants': ('descendants(%(val)s)', None),
1886 '_descendants': ('descendants(%(val)s)', None),
1901 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1887 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1902 '_matchfiles': ('_matchfiles(%(val)s)', None),
1888 '_matchfiles': ('_matchfiles(%(val)s)', None),
1903 'date': ('date(%(val)r)', None),
1889 'date': ('date(%(val)r)', None),
1904 'branch': ('branch(%(val)r)', ' or '),
1890 'branch': ('branch(%(val)r)', ' or '),
1905 '_patslog': ('filelog(%(val)r)', ' or '),
1891 '_patslog': ('filelog(%(val)r)', ' or '),
1906 '_patsfollow': ('follow(%(val)r)', ' or '),
1892 '_patsfollow': ('follow(%(val)r)', ' or '),
1907 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1893 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1908 'keyword': ('keyword(%(val)r)', ' or '),
1894 'keyword': ('keyword(%(val)r)', ' or '),
1909 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1895 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1910 'user': ('user(%(val)r)', ' or '),
1896 'user': ('user(%(val)r)', ' or '),
1911 }
1897 }
1912
1898
1913 opts = dict(opts)
1899 opts = dict(opts)
1914 # follow or not follow?
1900 # follow or not follow?
1915 follow = opts.get('follow') or opts.get('follow_first')
1901 follow = opts.get('follow') or opts.get('follow_first')
1916 if opts.get('follow_first'):
1902 if opts.get('follow_first'):
1917 followfirst = 1
1903 followfirst = 1
1918 else:
1904 else:
1919 followfirst = 0
1905 followfirst = 0
1920 # --follow with FILE behaviour depends on revs...
1906 # --follow with FILE behaviour depends on revs...
1921 it = iter(revs)
1907 it = iter(revs)
1922 startrev = it.next()
1908 startrev = it.next()
1923 try:
1909 try:
1924 followdescendants = startrev < it.next()
1910 followdescendants = startrev < it.next()
1925 except (StopIteration):
1911 except (StopIteration):
1926 followdescendants = False
1912 followdescendants = False
1927
1913
1928 # branch and only_branch are really aliases and must be handled at
1914 # branch and only_branch are really aliases and must be handled at
1929 # the same time
1915 # the same time
1930 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1916 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1931 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1917 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1932 # pats/include/exclude are passed to match.match() directly in
1918 # pats/include/exclude are passed to match.match() directly in
1933 # _matchfiles() revset but walkchangerevs() builds its matcher with
1919 # _matchfiles() revset but walkchangerevs() builds its matcher with
1934 # scmutil.match(). The difference is input pats are globbed on
1920 # scmutil.match(). The difference is input pats are globbed on
1935 # platforms without shell expansion (windows).
1921 # platforms without shell expansion (windows).
1936 pctx = repo[None]
1922 pctx = repo[None]
1937 match, pats = scmutil.matchandpats(pctx, pats, opts)
1923 match, pats = scmutil.matchandpats(pctx, pats, opts)
1938 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1924 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1939 if not slowpath:
1925 if not slowpath:
1940 for f in match.files():
1926 for f in match.files():
1941 if follow and f not in pctx:
1927 if follow and f not in pctx:
1942 # If the file exists, it may be a directory, so let it
1928 # If the file exists, it may be a directory, so let it
1943 # take the slow path.
1929 # take the slow path.
1944 if os.path.exists(repo.wjoin(f)):
1930 if os.path.exists(repo.wjoin(f)):
1945 slowpath = True
1931 slowpath = True
1946 continue
1932 continue
1947 else:
1933 else:
1948 raise util.Abort(_('cannot follow file not in parent '
1934 raise util.Abort(_('cannot follow file not in parent '
1949 'revision: "%s"') % f)
1935 'revision: "%s"') % f)
1950 filelog = repo.file(f)
1936 filelog = repo.file(f)
1951 if not filelog:
1937 if not filelog:
1952 # A zero count may be a directory or deleted file, so
1938 # A zero count may be a directory or deleted file, so
1953 # try to find matching entries on the slow path.
1939 # try to find matching entries on the slow path.
1954 if follow:
1940 if follow:
1955 raise util.Abort(
1941 raise util.Abort(
1956 _('cannot follow nonexistent file: "%s"') % f)
1942 _('cannot follow nonexistent file: "%s"') % f)
1957 slowpath = True
1943 slowpath = True
1958
1944
1959 # We decided to fall back to the slowpath because at least one
1945 # We decided to fall back to the slowpath because at least one
1960 # of the paths was not a file. Check to see if at least one of them
1946 # of the paths was not a file. Check to see if at least one of them
1961 # existed in history - in that case, we'll continue down the
1947 # existed in history - in that case, we'll continue down the
1962 # slowpath; otherwise, we can turn off the slowpath
1948 # slowpath; otherwise, we can turn off the slowpath
1963 if slowpath:
1949 if slowpath:
1964 for path in match.files():
1950 for path in match.files():
1965 if path == '.' or path in repo.store:
1951 if path == '.' or path in repo.store:
1966 break
1952 break
1967 else:
1953 else:
1968 slowpath = False
1954 slowpath = False
1969
1955
1970 fpats = ('_patsfollow', '_patsfollowfirst')
1956 fpats = ('_patsfollow', '_patsfollowfirst')
1971 fnopats = (('_ancestors', '_fancestors'),
1957 fnopats = (('_ancestors', '_fancestors'),
1972 ('_descendants', '_fdescendants'))
1958 ('_descendants', '_fdescendants'))
1973 if slowpath:
1959 if slowpath:
1974 # See walkchangerevs() slow path.
1960 # See walkchangerevs() slow path.
1975 #
1961 #
1976 # pats/include/exclude cannot be represented as separate
1962 # pats/include/exclude cannot be represented as separate
1977 # revset expressions as their filtering logic applies at file
1963 # revset expressions as their filtering logic applies at file
1978 # level. For instance "-I a -X a" matches a revision touching
1964 # level. For instance "-I a -X a" matches a revision touching
1979 # "a" and "b" while "file(a) and not file(b)" does
1965 # "a" and "b" while "file(a) and not file(b)" does
1980 # not. Besides, filesets are evaluated against the working
1966 # not. Besides, filesets are evaluated against the working
1981 # directory.
1967 # directory.
1982 matchargs = ['r:', 'd:relpath']
1968 matchargs = ['r:', 'd:relpath']
1983 for p in pats:
1969 for p in pats:
1984 matchargs.append('p:' + p)
1970 matchargs.append('p:' + p)
1985 for p in opts.get('include', []):
1971 for p in opts.get('include', []):
1986 matchargs.append('i:' + p)
1972 matchargs.append('i:' + p)
1987 for p in opts.get('exclude', []):
1973 for p in opts.get('exclude', []):
1988 matchargs.append('x:' + p)
1974 matchargs.append('x:' + p)
1989 matchargs = ','.join(('%r' % p) for p in matchargs)
1975 matchargs = ','.join(('%r' % p) for p in matchargs)
1990 opts['_matchfiles'] = matchargs
1976 opts['_matchfiles'] = matchargs
1991 if follow:
1977 if follow:
1992 opts[fnopats[0][followfirst]] = '.'
1978 opts[fnopats[0][followfirst]] = '.'
1993 else:
1979 else:
1994 if follow:
1980 if follow:
1995 if pats:
1981 if pats:
1996 # follow() revset interprets its file argument as a
1982 # follow() revset interprets its file argument as a
1997 # manifest entry, so use match.files(), not pats.
1983 # manifest entry, so use match.files(), not pats.
1998 opts[fpats[followfirst]] = list(match.files())
1984 opts[fpats[followfirst]] = list(match.files())
1999 else:
1985 else:
2000 op = fnopats[followdescendants][followfirst]
1986 op = fnopats[followdescendants][followfirst]
2001 opts[op] = 'rev(%d)' % startrev
1987 opts[op] = 'rev(%d)' % startrev
2002 else:
1988 else:
2003 opts['_patslog'] = list(pats)
1989 opts['_patslog'] = list(pats)
2004
1990
2005 filematcher = None
1991 filematcher = None
2006 if opts.get('patch') or opts.get('stat'):
1992 if opts.get('patch') or opts.get('stat'):
2007 # When following files, track renames via a special matcher.
1993 # When following files, track renames via a special matcher.
2008 # If we're forced to take the slowpath it means we're following
1994 # If we're forced to take the slowpath it means we're following
2009 # at least one pattern/directory, so don't bother with rename tracking.
1995 # at least one pattern/directory, so don't bother with rename tracking.
2010 if follow and not match.always() and not slowpath:
1996 if follow and not match.always() and not slowpath:
2011 # _makefollowlogfilematcher expects its files argument to be
1997 # _makefollowlogfilematcher expects its files argument to be
2012 # relative to the repo root, so use match.files(), not pats.
1998 # relative to the repo root, so use match.files(), not pats.
2013 filematcher = _makefollowlogfilematcher(repo, match.files(),
1999 filematcher = _makefollowlogfilematcher(repo, match.files(),
2014 followfirst)
2000 followfirst)
2015 else:
2001 else:
2016 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2002 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2017 if filematcher is None:
2003 if filematcher is None:
2018 filematcher = lambda rev: match
2004 filematcher = lambda rev: match
2019
2005
2020 expr = []
2006 expr = []
2021 for op, val in sorted(opts.iteritems()):
2007 for op, val in sorted(opts.iteritems()):
2022 if not val:
2008 if not val:
2023 continue
2009 continue
2024 if op not in opt2revset:
2010 if op not in opt2revset:
2025 continue
2011 continue
2026 revop, andor = opt2revset[op]
2012 revop, andor = opt2revset[op]
2027 if '%(val)' not in revop:
2013 if '%(val)' not in revop:
2028 expr.append(revop)
2014 expr.append(revop)
2029 else:
2015 else:
2030 if not isinstance(val, list):
2016 if not isinstance(val, list):
2031 e = revop % {'val': val}
2017 e = revop % {'val': val}
2032 else:
2018 else:
2033 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2019 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2034 expr.append(e)
2020 expr.append(e)
2035
2021
2036 if expr:
2022 if expr:
2037 expr = '(' + ' and '.join(expr) + ')'
2023 expr = '(' + ' and '.join(expr) + ')'
2038 else:
2024 else:
2039 expr = None
2025 expr = None
2040 return expr, filematcher
2026 return expr, filematcher
2041
2027
2042 def _logrevs(repo, opts):
2028 def _logrevs(repo, opts):
2043 # Default --rev value depends on --follow but --follow behaviour
2029 # Default --rev value depends on --follow but --follow behaviour
2044 # depends on revisions resolved from --rev...
2030 # depends on revisions resolved from --rev...
2045 follow = opts.get('follow') or opts.get('follow_first')
2031 follow = opts.get('follow') or opts.get('follow_first')
2046 if opts.get('rev'):
2032 if opts.get('rev'):
2047 revs = scmutil.revrange(repo, opts['rev'])
2033 revs = scmutil.revrange(repo, opts['rev'])
2048 elif follow and repo.dirstate.p1() == nullid:
2034 elif follow and repo.dirstate.p1() == nullid:
2049 revs = revset.baseset()
2035 revs = revset.baseset()
2050 elif follow:
2036 elif follow:
2051 revs = repo.revs('reverse(:.)')
2037 revs = repo.revs('reverse(:.)')
2052 else:
2038 else:
2053 revs = revset.spanset(repo)
2039 revs = revset.spanset(repo)
2054 revs.reverse()
2040 revs.reverse()
2055 return revs
2041 return revs
2056
2042
2057 def getgraphlogrevs(repo, pats, opts):
2043 def getgraphlogrevs(repo, pats, opts):
2058 """Return (revs, expr, filematcher) where revs is an iterable of
2044 """Return (revs, expr, filematcher) where revs is an iterable of
2059 revision numbers, expr is a revset string built from log options
2045 revision numbers, expr is a revset string built from log options
2060 and file patterns or None, and used to filter 'revs'. If --stat or
2046 and file patterns or None, and used to filter 'revs'. If --stat or
2061 --patch are not passed filematcher is None. Otherwise it is a
2047 --patch are not passed filematcher is None. Otherwise it is a
2062 callable taking a revision number and returning a match objects
2048 callable taking a revision number and returning a match objects
2063 filtering the files to be detailed when displaying the revision.
2049 filtering the files to be detailed when displaying the revision.
2064 """
2050 """
2065 limit = loglimit(opts)
2051 limit = loglimit(opts)
2066 revs = _logrevs(repo, opts)
2052 revs = _logrevs(repo, opts)
2067 if not revs:
2053 if not revs:
2068 return revset.baseset(), None, None
2054 return revset.baseset(), None, None
2069 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2055 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2070 if opts.get('rev'):
2056 if opts.get('rev'):
2071 # User-specified revs might be unsorted, but don't sort before
2057 # User-specified revs might be unsorted, but don't sort before
2072 # _makelogrevset because it might depend on the order of revs
2058 # _makelogrevset because it might depend on the order of revs
2073 revs.sort(reverse=True)
2059 revs.sort(reverse=True)
2074 if expr:
2060 if expr:
2075 # Revset matchers often operate faster on revisions in changelog
2061 # Revset matchers often operate faster on revisions in changelog
2076 # order, because most filters deal with the changelog.
2062 # order, because most filters deal with the changelog.
2077 revs.reverse()
2063 revs.reverse()
2078 matcher = revset.match(repo.ui, expr)
2064 matcher = revset.match(repo.ui, expr)
2079 # Revset matches can reorder revisions. "A or B" typically returns
2065 # Revset matches can reorder revisions. "A or B" typically returns
2080 # returns the revision matching A then the revision matching B. Sort
2066 # returns the revision matching A then the revision matching B. Sort
2081 # again to fix that.
2067 # again to fix that.
2082 revs = matcher(repo, revs)
2068 revs = matcher(repo, revs)
2083 revs.sort(reverse=True)
2069 revs.sort(reverse=True)
2084 if limit is not None:
2070 if limit is not None:
2085 limitedrevs = []
2071 limitedrevs = []
2086 for idx, rev in enumerate(revs):
2072 for idx, rev in enumerate(revs):
2087 if idx >= limit:
2073 if idx >= limit:
2088 break
2074 break
2089 limitedrevs.append(rev)
2075 limitedrevs.append(rev)
2090 revs = revset.baseset(limitedrevs)
2076 revs = revset.baseset(limitedrevs)
2091
2077
2092 return revs, expr, filematcher
2078 return revs, expr, filematcher
2093
2079
2094 def getlogrevs(repo, pats, opts):
2080 def getlogrevs(repo, pats, opts):
2095 """Return (revs, expr, filematcher) where revs is an iterable of
2081 """Return (revs, expr, filematcher) where revs is an iterable of
2096 revision numbers, expr is a revset string built from log options
2082 revision numbers, expr is a revset string built from log options
2097 and file patterns or None, and used to filter 'revs'. If --stat or
2083 and file patterns or None, and used to filter 'revs'. If --stat or
2098 --patch are not passed filematcher is None. Otherwise it is a
2084 --patch are not passed filematcher is None. Otherwise it is a
2099 callable taking a revision number and returning a match objects
2085 callable taking a revision number and returning a match objects
2100 filtering the files to be detailed when displaying the revision.
2086 filtering the files to be detailed when displaying the revision.
2101 """
2087 """
2102 limit = loglimit(opts)
2088 limit = loglimit(opts)
2103 revs = _logrevs(repo, opts)
2089 revs = _logrevs(repo, opts)
2104 if not revs:
2090 if not revs:
2105 return revset.baseset([]), None, None
2091 return revset.baseset([]), None, None
2106 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2092 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2107 if expr:
2093 if expr:
2108 # Revset matchers often operate faster on revisions in changelog
2094 # Revset matchers often operate faster on revisions in changelog
2109 # order, because most filters deal with the changelog.
2095 # order, because most filters deal with the changelog.
2110 if not opts.get('rev'):
2096 if not opts.get('rev'):
2111 revs.reverse()
2097 revs.reverse()
2112 matcher = revset.match(repo.ui, expr)
2098 matcher = revset.match(repo.ui, expr)
2113 # Revset matches can reorder revisions. "A or B" typically returns
2099 # Revset matches can reorder revisions. "A or B" typically returns
2114 # returns the revision matching A then the revision matching B. Sort
2100 # returns the revision matching A then the revision matching B. Sort
2115 # again to fix that.
2101 # again to fix that.
2116 revs = matcher(repo, revs)
2102 revs = matcher(repo, revs)
2117 if not opts.get('rev'):
2103 if not opts.get('rev'):
2118 revs.sort(reverse=True)
2104 revs.sort(reverse=True)
2119 if limit is not None:
2105 if limit is not None:
2120 count = 0
2106 count = 0
2121 limitedrevs = []
2107 limitedrevs = []
2122 it = iter(revs)
2108 it = iter(revs)
2123 while count < limit:
2109 while count < limit:
2124 try:
2110 try:
2125 limitedrevs.append(it.next())
2111 limitedrevs.append(it.next())
2126 except (StopIteration):
2112 except (StopIteration):
2127 break
2113 break
2128 count += 1
2114 count += 1
2129 revs = revset.baseset(limitedrevs)
2115 revs = revset.baseset(limitedrevs)
2130
2116
2131 return revs, expr, filematcher
2117 return revs, expr, filematcher
2132
2118
2133 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2119 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2134 filematcher=None):
2120 filematcher=None):
2135 seen, state = [], graphmod.asciistate()
2121 seen, state = [], graphmod.asciistate()
2136 for rev, type, ctx, parents in dag:
2122 for rev, type, ctx, parents in dag:
2137 char = 'o'
2123 char = 'o'
2138 if ctx.node() in showparents:
2124 if ctx.node() in showparents:
2139 char = '@'
2125 char = '@'
2140 elif ctx.obsolete():
2126 elif ctx.obsolete():
2141 char = 'x'
2127 char = 'x'
2142 elif ctx.closesbranch():
2128 elif ctx.closesbranch():
2143 char = '_'
2129 char = '_'
2144 copies = None
2130 copies = None
2145 if getrenamed and ctx.rev():
2131 if getrenamed and ctx.rev():
2146 copies = []
2132 copies = []
2147 for fn in ctx.files():
2133 for fn in ctx.files():
2148 rename = getrenamed(fn, ctx.rev())
2134 rename = getrenamed(fn, ctx.rev())
2149 if rename:
2135 if rename:
2150 copies.append((fn, rename[0]))
2136 copies.append((fn, rename[0]))
2151 revmatchfn = None
2137 revmatchfn = None
2152 if filematcher is not None:
2138 if filematcher is not None:
2153 revmatchfn = filematcher(ctx.rev())
2139 revmatchfn = filematcher(ctx.rev())
2154 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2140 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2155 lines = displayer.hunk.pop(rev).split('\n')
2141 lines = displayer.hunk.pop(rev).split('\n')
2156 if not lines[-1]:
2142 if not lines[-1]:
2157 del lines[-1]
2143 del lines[-1]
2158 displayer.flush(rev)
2144 displayer.flush(rev)
2159 edges = edgefn(type, char, lines, seen, rev, parents)
2145 edges = edgefn(type, char, lines, seen, rev, parents)
2160 for type, char, lines, coldata in edges:
2146 for type, char, lines, coldata in edges:
2161 graphmod.ascii(ui, state, type, char, lines, coldata)
2147 graphmod.ascii(ui, state, type, char, lines, coldata)
2162 displayer.close()
2148 displayer.close()
2163
2149
2164 def graphlog(ui, repo, *pats, **opts):
2150 def graphlog(ui, repo, *pats, **opts):
2165 # Parameters are identical to log command ones
2151 # Parameters are identical to log command ones
2166 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2152 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2167 revdag = graphmod.dagwalker(repo, revs)
2153 revdag = graphmod.dagwalker(repo, revs)
2168
2154
2169 getrenamed = None
2155 getrenamed = None
2170 if opts.get('copies'):
2156 if opts.get('copies'):
2171 endrev = None
2157 endrev = None
2172 if opts.get('rev'):
2158 if opts.get('rev'):
2173 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2159 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2174 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2160 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2175 displayer = show_changeset(ui, repo, opts, buffered=True)
2161 displayer = show_changeset(ui, repo, opts, buffered=True)
2176 showparents = [ctx.node() for ctx in repo[None].parents()]
2162 showparents = [ctx.node() for ctx in repo[None].parents()]
2177 displaygraph(ui, revdag, displayer, showparents,
2163 displaygraph(ui, revdag, displayer, showparents,
2178 graphmod.asciiedges, getrenamed, filematcher)
2164 graphmod.asciiedges, getrenamed, filematcher)
2179
2165
2180 def checkunsupportedgraphflags(pats, opts):
2166 def checkunsupportedgraphflags(pats, opts):
2181 for op in ["newest_first"]:
2167 for op in ["newest_first"]:
2182 if op in opts and opts[op]:
2168 if op in opts and opts[op]:
2183 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2169 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2184 % op.replace("_", "-"))
2170 % op.replace("_", "-"))
2185
2171
2186 def graphrevs(repo, nodes, opts):
2172 def graphrevs(repo, nodes, opts):
2187 limit = loglimit(opts)
2173 limit = loglimit(opts)
2188 nodes.reverse()
2174 nodes.reverse()
2189 if limit is not None:
2175 if limit is not None:
2190 nodes = nodes[:limit]
2176 nodes = nodes[:limit]
2191 return graphmod.nodes(repo, nodes)
2177 return graphmod.nodes(repo, nodes)
2192
2178
2193 def add(ui, repo, match, prefix, explicitonly, **opts):
2179 def add(ui, repo, match, prefix, explicitonly, **opts):
2194 join = lambda f: os.path.join(prefix, f)
2180 join = lambda f: os.path.join(prefix, f)
2195 bad = []
2181 bad = []
2196 oldbad = match.bad
2182 oldbad = match.bad
2197 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2183 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2198 names = []
2184 names = []
2199 wctx = repo[None]
2185 wctx = repo[None]
2200 cca = None
2186 cca = None
2201 abort, warn = scmutil.checkportabilityalert(ui)
2187 abort, warn = scmutil.checkportabilityalert(ui)
2202 if abort or warn:
2188 if abort or warn:
2203 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2189 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2204 for f in wctx.walk(match):
2190 for f in wctx.walk(match):
2205 exact = match.exact(f)
2191 exact = match.exact(f)
2206 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2192 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2207 if cca:
2193 if cca:
2208 cca(f)
2194 cca(f)
2209 names.append(f)
2195 names.append(f)
2210 if ui.verbose or not exact:
2196 if ui.verbose or not exact:
2211 ui.status(_('adding %s\n') % match.rel(f))
2197 ui.status(_('adding %s\n') % match.rel(f))
2212
2198
2213 for subpath in sorted(wctx.substate):
2199 for subpath in sorted(wctx.substate):
2214 sub = wctx.sub(subpath)
2200 sub = wctx.sub(subpath)
2215 try:
2201 try:
2216 submatch = matchmod.narrowmatcher(subpath, match)
2202 submatch = matchmod.narrowmatcher(subpath, match)
2217 if opts.get('subrepos'):
2203 if opts.get('subrepos'):
2218 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2204 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2219 else:
2205 else:
2220 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2206 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2221 except error.LookupError:
2207 except error.LookupError:
2222 ui.status(_("skipping missing subrepository: %s\n")
2208 ui.status(_("skipping missing subrepository: %s\n")
2223 % join(subpath))
2209 % join(subpath))
2224
2210
2225 if not opts.get('dry_run'):
2211 if not opts.get('dry_run'):
2226 rejected = wctx.add(names, prefix)
2212 rejected = wctx.add(names, prefix)
2227 bad.extend(f for f in rejected if f in match.files())
2213 bad.extend(f for f in rejected if f in match.files())
2228 return bad
2214 return bad
2229
2215
2230 def forget(ui, repo, match, prefix, explicitonly):
2216 def forget(ui, repo, match, prefix, explicitonly):
2231 join = lambda f: os.path.join(prefix, f)
2217 join = lambda f: os.path.join(prefix, f)
2232 bad = []
2218 bad = []
2233 oldbad = match.bad
2219 oldbad = match.bad
2234 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2220 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2235 wctx = repo[None]
2221 wctx = repo[None]
2236 forgot = []
2222 forgot = []
2237 s = repo.status(match=match, clean=True)
2223 s = repo.status(match=match, clean=True)
2238 forget = sorted(s[0] + s[1] + s[3] + s[6])
2224 forget = sorted(s[0] + s[1] + s[3] + s[6])
2239 if explicitonly:
2225 if explicitonly:
2240 forget = [f for f in forget if match.exact(f)]
2226 forget = [f for f in forget if match.exact(f)]
2241
2227
2242 for subpath in sorted(wctx.substate):
2228 for subpath in sorted(wctx.substate):
2243 sub = wctx.sub(subpath)
2229 sub = wctx.sub(subpath)
2244 try:
2230 try:
2245 submatch = matchmod.narrowmatcher(subpath, match)
2231 submatch = matchmod.narrowmatcher(subpath, match)
2246 subbad, subforgot = sub.forget(submatch, prefix)
2232 subbad, subforgot = sub.forget(submatch, prefix)
2247 bad.extend([subpath + '/' + f for f in subbad])
2233 bad.extend([subpath + '/' + f for f in subbad])
2248 forgot.extend([subpath + '/' + f for f in subforgot])
2234 forgot.extend([subpath + '/' + f for f in subforgot])
2249 except error.LookupError:
2235 except error.LookupError:
2250 ui.status(_("skipping missing subrepository: %s\n")
2236 ui.status(_("skipping missing subrepository: %s\n")
2251 % join(subpath))
2237 % join(subpath))
2252
2238
2253 if not explicitonly:
2239 if not explicitonly:
2254 for f in match.files():
2240 for f in match.files():
2255 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2241 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2256 if f not in forgot:
2242 if f not in forgot:
2257 if repo.wvfs.exists(f):
2243 if repo.wvfs.exists(f):
2258 ui.warn(_('not removing %s: '
2244 ui.warn(_('not removing %s: '
2259 'file is already untracked\n')
2245 'file is already untracked\n')
2260 % match.rel(f))
2246 % match.rel(f))
2261 bad.append(f)
2247 bad.append(f)
2262
2248
2263 for f in forget:
2249 for f in forget:
2264 if ui.verbose or not match.exact(f):
2250 if ui.verbose or not match.exact(f):
2265 ui.status(_('removing %s\n') % match.rel(f))
2251 ui.status(_('removing %s\n') % match.rel(f))
2266
2252
2267 rejected = wctx.forget(forget, prefix)
2253 rejected = wctx.forget(forget, prefix)
2268 bad.extend(f for f in rejected if f in match.files())
2254 bad.extend(f for f in rejected if f in match.files())
2269 forgot.extend(f for f in forget if f not in rejected)
2255 forgot.extend(f for f in forget if f not in rejected)
2270 return bad, forgot
2256 return bad, forgot
2271
2257
2272 def files(ui, ctx, m, fm, fmt, subrepos):
2258 def files(ui, ctx, m, fm, fmt, subrepos):
2273 rev = ctx.rev()
2259 rev = ctx.rev()
2274 ret = 1
2260 ret = 1
2275 ds = ctx.repo().dirstate
2261 ds = ctx.repo().dirstate
2276
2262
2277 for f in ctx.matches(m):
2263 for f in ctx.matches(m):
2278 if rev is None and ds[f] == 'r':
2264 if rev is None and ds[f] == 'r':
2279 continue
2265 continue
2280 fm.startitem()
2266 fm.startitem()
2281 if ui.verbose:
2267 if ui.verbose:
2282 fc = ctx[f]
2268 fc = ctx[f]
2283 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2269 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2284 fm.data(abspath=f)
2270 fm.data(abspath=f)
2285 fm.write('path', fmt, m.rel(f))
2271 fm.write('path', fmt, m.rel(f))
2286 ret = 0
2272 ret = 0
2287
2273
2288 if subrepos:
2274 if subrepos:
2289 for subpath in sorted(ctx.substate):
2275 for subpath in sorted(ctx.substate):
2290 sub = ctx.sub(subpath)
2276 sub = ctx.sub(subpath)
2291 try:
2277 try:
2292 submatch = matchmod.narrowmatcher(subpath, m)
2278 submatch = matchmod.narrowmatcher(subpath, m)
2293 if sub.printfiles(ui, submatch, fm, fmt) == 0:
2279 if sub.printfiles(ui, submatch, fm, fmt) == 0:
2294 ret = 0
2280 ret = 0
2295 except error.LookupError:
2281 except error.LookupError:
2296 ui.status(_("skipping missing subrepository: %s\n")
2282 ui.status(_("skipping missing subrepository: %s\n")
2297 % m.abs(subpath))
2283 % m.abs(subpath))
2298
2284
2299 return ret
2285 return ret
2300
2286
2301 def remove(ui, repo, m, prefix, after, force, subrepos):
2287 def remove(ui, repo, m, prefix, after, force, subrepos):
2302 join = lambda f: os.path.join(prefix, f)
2288 join = lambda f: os.path.join(prefix, f)
2303 ret = 0
2289 ret = 0
2304 s = repo.status(match=m, clean=True)
2290 s = repo.status(match=m, clean=True)
2305 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2291 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2306
2292
2307 wctx = repo[None]
2293 wctx = repo[None]
2308
2294
2309 for subpath in sorted(wctx.substate):
2295 for subpath in sorted(wctx.substate):
2310 def matchessubrepo(matcher, subpath):
2296 def matchessubrepo(matcher, subpath):
2311 if matcher.exact(subpath):
2297 if matcher.exact(subpath):
2312 return True
2298 return True
2313 for f in matcher.files():
2299 for f in matcher.files():
2314 if f.startswith(subpath):
2300 if f.startswith(subpath):
2315 return True
2301 return True
2316 return False
2302 return False
2317
2303
2318 if subrepos or matchessubrepo(m, subpath):
2304 if subrepos or matchessubrepo(m, subpath):
2319 sub = wctx.sub(subpath)
2305 sub = wctx.sub(subpath)
2320 try:
2306 try:
2321 submatch = matchmod.narrowmatcher(subpath, m)
2307 submatch = matchmod.narrowmatcher(subpath, m)
2322 if sub.removefiles(submatch, prefix, after, force, subrepos):
2308 if sub.removefiles(submatch, prefix, after, force, subrepos):
2323 ret = 1
2309 ret = 1
2324 except error.LookupError:
2310 except error.LookupError:
2325 ui.status(_("skipping missing subrepository: %s\n")
2311 ui.status(_("skipping missing subrepository: %s\n")
2326 % join(subpath))
2312 % join(subpath))
2327
2313
2328 # warn about failure to delete explicit files/dirs
2314 # warn about failure to delete explicit files/dirs
2329 deleteddirs = scmutil.dirs(deleted)
2315 deleteddirs = scmutil.dirs(deleted)
2330 for f in m.files():
2316 for f in m.files():
2331 def insubrepo():
2317 def insubrepo():
2332 for subpath in wctx.substate:
2318 for subpath in wctx.substate:
2333 if f.startswith(subpath):
2319 if f.startswith(subpath):
2334 return True
2320 return True
2335 return False
2321 return False
2336
2322
2337 isdir = f in deleteddirs or f in wctx.dirs()
2323 isdir = f in deleteddirs or f in wctx.dirs()
2338 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2324 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2339 continue
2325 continue
2340
2326
2341 if repo.wvfs.exists(f):
2327 if repo.wvfs.exists(f):
2342 if repo.wvfs.isdir(f):
2328 if repo.wvfs.isdir(f):
2343 ui.warn(_('not removing %s: no tracked files\n')
2329 ui.warn(_('not removing %s: no tracked files\n')
2344 % m.rel(f))
2330 % m.rel(f))
2345 else:
2331 else:
2346 ui.warn(_('not removing %s: file is untracked\n')
2332 ui.warn(_('not removing %s: file is untracked\n')
2347 % m.rel(f))
2333 % m.rel(f))
2348 # missing files will generate a warning elsewhere
2334 # missing files will generate a warning elsewhere
2349 ret = 1
2335 ret = 1
2350
2336
2351 if force:
2337 if force:
2352 list = modified + deleted + clean + added
2338 list = modified + deleted + clean + added
2353 elif after:
2339 elif after:
2354 list = deleted
2340 list = deleted
2355 for f in modified + added + clean:
2341 for f in modified + added + clean:
2356 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2342 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2357 ret = 1
2343 ret = 1
2358 else:
2344 else:
2359 list = deleted + clean
2345 list = deleted + clean
2360 for f in modified:
2346 for f in modified:
2361 ui.warn(_('not removing %s: file is modified (use -f'
2347 ui.warn(_('not removing %s: file is modified (use -f'
2362 ' to force removal)\n') % m.rel(f))
2348 ' to force removal)\n') % m.rel(f))
2363 ret = 1
2349 ret = 1
2364 for f in added:
2350 for f in added:
2365 ui.warn(_('not removing %s: file has been marked for add'
2351 ui.warn(_('not removing %s: file has been marked for add'
2366 ' (use forget to undo)\n') % m.rel(f))
2352 ' (use forget to undo)\n') % m.rel(f))
2367 ret = 1
2353 ret = 1
2368
2354
2369 for f in sorted(list):
2355 for f in sorted(list):
2370 if ui.verbose or not m.exact(f):
2356 if ui.verbose or not m.exact(f):
2371 ui.status(_('removing %s\n') % m.rel(f))
2357 ui.status(_('removing %s\n') % m.rel(f))
2372
2358
2373 wlock = repo.wlock()
2359 wlock = repo.wlock()
2374 try:
2360 try:
2375 if not after:
2361 if not after:
2376 for f in list:
2362 for f in list:
2377 if f in added:
2363 if f in added:
2378 continue # we never unlink added files on remove
2364 continue # we never unlink added files on remove
2379 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2365 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2380 repo[None].forget(list)
2366 repo[None].forget(list)
2381 finally:
2367 finally:
2382 wlock.release()
2368 wlock.release()
2383
2369
2384 return ret
2370 return ret
2385
2371
2386 def cat(ui, repo, ctx, matcher, prefix, **opts):
2372 def cat(ui, repo, ctx, matcher, prefix, **opts):
2387 err = 1
2373 err = 1
2388
2374
2389 def write(path):
2375 def write(path):
2390 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2376 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2391 pathname=os.path.join(prefix, path))
2377 pathname=os.path.join(prefix, path))
2392 data = ctx[path].data()
2378 data = ctx[path].data()
2393 if opts.get('decode'):
2379 if opts.get('decode'):
2394 data = repo.wwritedata(path, data)
2380 data = repo.wwritedata(path, data)
2395 fp.write(data)
2381 fp.write(data)
2396 fp.close()
2382 fp.close()
2397
2383
2398 # Automation often uses hg cat on single files, so special case it
2384 # Automation often uses hg cat on single files, so special case it
2399 # for performance to avoid the cost of parsing the manifest.
2385 # for performance to avoid the cost of parsing the manifest.
2400 if len(matcher.files()) == 1 and not matcher.anypats():
2386 if len(matcher.files()) == 1 and not matcher.anypats():
2401 file = matcher.files()[0]
2387 file = matcher.files()[0]
2402 mf = repo.manifest
2388 mf = repo.manifest
2403 mfnode = ctx._changeset[0]
2389 mfnode = ctx._changeset[0]
2404 if mf.find(mfnode, file)[0]:
2390 if mf.find(mfnode, file)[0]:
2405 write(file)
2391 write(file)
2406 return 0
2392 return 0
2407
2393
2408 # Don't warn about "missing" files that are really in subrepos
2394 # Don't warn about "missing" files that are really in subrepos
2409 bad = matcher.bad
2395 bad = matcher.bad
2410
2396
2411 def badfn(path, msg):
2397 def badfn(path, msg):
2412 for subpath in ctx.substate:
2398 for subpath in ctx.substate:
2413 if path.startswith(subpath):
2399 if path.startswith(subpath):
2414 return
2400 return
2415 bad(path, msg)
2401 bad(path, msg)
2416
2402
2417 matcher.bad = badfn
2403 matcher.bad = badfn
2418
2404
2419 for abs in ctx.walk(matcher):
2405 for abs in ctx.walk(matcher):
2420 write(abs)
2406 write(abs)
2421 err = 0
2407 err = 0
2422
2408
2423 matcher.bad = bad
2409 matcher.bad = bad
2424
2410
2425 for subpath in sorted(ctx.substate):
2411 for subpath in sorted(ctx.substate):
2426 sub = ctx.sub(subpath)
2412 sub = ctx.sub(subpath)
2427 try:
2413 try:
2428 submatch = matchmod.narrowmatcher(subpath, matcher)
2414 submatch = matchmod.narrowmatcher(subpath, matcher)
2429
2415
2430 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2416 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2431 **opts):
2417 **opts):
2432 err = 0
2418 err = 0
2433 except error.RepoLookupError:
2419 except error.RepoLookupError:
2434 ui.status(_("skipping missing subrepository: %s\n")
2420 ui.status(_("skipping missing subrepository: %s\n")
2435 % os.path.join(prefix, subpath))
2421 % os.path.join(prefix, subpath))
2436
2422
2437 return err
2423 return err
2438
2424
2439 def commit(ui, repo, commitfunc, pats, opts):
2425 def commit(ui, repo, commitfunc, pats, opts):
2440 '''commit the specified files or all outstanding changes'''
2426 '''commit the specified files or all outstanding changes'''
2441 date = opts.get('date')
2427 date = opts.get('date')
2442 if date:
2428 if date:
2443 opts['date'] = util.parsedate(date)
2429 opts['date'] = util.parsedate(date)
2444 message = logmessage(ui, opts)
2430 message = logmessage(ui, opts)
2445 matcher = scmutil.match(repo[None], pats, opts)
2431 matcher = scmutil.match(repo[None], pats, opts)
2446
2432
2447 # extract addremove carefully -- this function can be called from a command
2433 # extract addremove carefully -- this function can be called from a command
2448 # that doesn't support addremove
2434 # that doesn't support addremove
2449 if opts.get('addremove'):
2435 if opts.get('addremove'):
2450 if scmutil.addremove(repo, matcher, "", opts) != 0:
2436 if scmutil.addremove(repo, matcher, "", opts) != 0:
2451 raise util.Abort(
2437 raise util.Abort(
2452 _("failed to mark all new/missing files as added/removed"))
2438 _("failed to mark all new/missing files as added/removed"))
2453
2439
2454 return commitfunc(ui, repo, message, matcher, opts)
2440 return commitfunc(ui, repo, message, matcher, opts)
2455
2441
2456 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2442 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2457 # amend will reuse the existing user if not specified, but the obsolete
2443 # amend will reuse the existing user if not specified, but the obsolete
2458 # marker creation requires that the current user's name is specified.
2444 # marker creation requires that the current user's name is specified.
2459 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2445 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2460 ui.username() # raise exception if username not set
2446 ui.username() # raise exception if username not set
2461
2447
2462 ui.note(_('amending changeset %s\n') % old)
2448 ui.note(_('amending changeset %s\n') % old)
2463 base = old.p1()
2449 base = old.p1()
2464
2450
2465 wlock = lock = newid = None
2451 wlock = lock = newid = None
2466 try:
2452 try:
2467 wlock = repo.wlock()
2453 wlock = repo.wlock()
2468 lock = repo.lock()
2454 lock = repo.lock()
2469 tr = repo.transaction('amend')
2455 tr = repo.transaction('amend')
2470 try:
2456 try:
2471 # See if we got a message from -m or -l, if not, open the editor
2457 # See if we got a message from -m or -l, if not, open the editor
2472 # with the message of the changeset to amend
2458 # with the message of the changeset to amend
2473 message = logmessage(ui, opts)
2459 message = logmessage(ui, opts)
2474 # ensure logfile does not conflict with later enforcement of the
2460 # ensure logfile does not conflict with later enforcement of the
2475 # message. potential logfile content has been processed by
2461 # message. potential logfile content has been processed by
2476 # `logmessage` anyway.
2462 # `logmessage` anyway.
2477 opts.pop('logfile')
2463 opts.pop('logfile')
2478 # First, do a regular commit to record all changes in the working
2464 # First, do a regular commit to record all changes in the working
2479 # directory (if there are any)
2465 # directory (if there are any)
2480 ui.callhooks = False
2466 ui.callhooks = False
2481 currentbookmark = repo._bookmarkcurrent
2467 currentbookmark = repo._bookmarkcurrent
2482 try:
2468 try:
2483 repo._bookmarkcurrent = None
2469 repo._bookmarkcurrent = None
2484 opts['message'] = 'temporary amend commit for %s' % old
2470 opts['message'] = 'temporary amend commit for %s' % old
2485 node = commit(ui, repo, commitfunc, pats, opts)
2471 node = commit(ui, repo, commitfunc, pats, opts)
2486 finally:
2472 finally:
2487 repo._bookmarkcurrent = currentbookmark
2473 repo._bookmarkcurrent = currentbookmark
2488 ui.callhooks = True
2474 ui.callhooks = True
2489 ctx = repo[node]
2475 ctx = repo[node]
2490
2476
2491 # Participating changesets:
2477 # Participating changesets:
2492 #
2478 #
2493 # node/ctx o - new (intermediate) commit that contains changes
2479 # node/ctx o - new (intermediate) commit that contains changes
2494 # | from working dir to go into amending commit
2480 # | from working dir to go into amending commit
2495 # | (or a workingctx if there were no changes)
2481 # | (or a workingctx if there were no changes)
2496 # |
2482 # |
2497 # old o - changeset to amend
2483 # old o - changeset to amend
2498 # |
2484 # |
2499 # base o - parent of amending changeset
2485 # base o - parent of amending changeset
2500
2486
2501 # Update extra dict from amended commit (e.g. to preserve graft
2487 # Update extra dict from amended commit (e.g. to preserve graft
2502 # source)
2488 # source)
2503 extra.update(old.extra())
2489 extra.update(old.extra())
2504
2490
2505 # Also update it from the intermediate commit or from the wctx
2491 # Also update it from the intermediate commit or from the wctx
2506 extra.update(ctx.extra())
2492 extra.update(ctx.extra())
2507
2493
2508 if len(old.parents()) > 1:
2494 if len(old.parents()) > 1:
2509 # ctx.files() isn't reliable for merges, so fall back to the
2495 # ctx.files() isn't reliable for merges, so fall back to the
2510 # slower repo.status() method
2496 # slower repo.status() method
2511 files = set([fn for st in repo.status(base, old)[:3]
2497 files = set([fn for st in repo.status(base, old)[:3]
2512 for fn in st])
2498 for fn in st])
2513 else:
2499 else:
2514 files = set(old.files())
2500 files = set(old.files())
2515
2501
2516 # Second, we use either the commit we just did, or if there were no
2502 # Second, we use either the commit we just did, or if there were no
2517 # changes the parent of the working directory as the version of the
2503 # changes the parent of the working directory as the version of the
2518 # files in the final amend commit
2504 # files in the final amend commit
2519 if node:
2505 if node:
2520 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2506 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2521
2507
2522 user = ctx.user()
2508 user = ctx.user()
2523 date = ctx.date()
2509 date = ctx.date()
2524 # Recompute copies (avoid recording a -> b -> a)
2510 # Recompute copies (avoid recording a -> b -> a)
2525 copied = copies.pathcopies(base, ctx)
2511 copied = copies.pathcopies(base, ctx)
2526 if old.p2:
2512 if old.p2:
2527 copied.update(copies.pathcopies(old.p2(), ctx))
2513 copied.update(copies.pathcopies(old.p2(), ctx))
2528
2514
2529 # Prune files which were reverted by the updates: if old
2515 # Prune files which were reverted by the updates: if old
2530 # introduced file X and our intermediate commit, node,
2516 # introduced file X and our intermediate commit, node,
2531 # renamed that file, then those two files are the same and
2517 # renamed that file, then those two files are the same and
2532 # we can discard X from our list of files. Likewise if X
2518 # we can discard X from our list of files. Likewise if X
2533 # was deleted, it's no longer relevant
2519 # was deleted, it's no longer relevant
2534 files.update(ctx.files())
2520 files.update(ctx.files())
2535
2521
2536 def samefile(f):
2522 def samefile(f):
2537 if f in ctx.manifest():
2523 if f in ctx.manifest():
2538 a = ctx.filectx(f)
2524 a = ctx.filectx(f)
2539 if f in base.manifest():
2525 if f in base.manifest():
2540 b = base.filectx(f)
2526 b = base.filectx(f)
2541 return (not a.cmp(b)
2527 return (not a.cmp(b)
2542 and a.flags() == b.flags())
2528 and a.flags() == b.flags())
2543 else:
2529 else:
2544 return False
2530 return False
2545 else:
2531 else:
2546 return f not in base.manifest()
2532 return f not in base.manifest()
2547 files = [f for f in files if not samefile(f)]
2533 files = [f for f in files if not samefile(f)]
2548
2534
2549 def filectxfn(repo, ctx_, path):
2535 def filectxfn(repo, ctx_, path):
2550 try:
2536 try:
2551 fctx = ctx[path]
2537 fctx = ctx[path]
2552 flags = fctx.flags()
2538 flags = fctx.flags()
2553 mctx = context.memfilectx(repo,
2539 mctx = context.memfilectx(repo,
2554 fctx.path(), fctx.data(),
2540 fctx.path(), fctx.data(),
2555 islink='l' in flags,
2541 islink='l' in flags,
2556 isexec='x' in flags,
2542 isexec='x' in flags,
2557 copied=copied.get(path))
2543 copied=copied.get(path))
2558 return mctx
2544 return mctx
2559 except KeyError:
2545 except KeyError:
2560 return None
2546 return None
2561 else:
2547 else:
2562 ui.note(_('copying changeset %s to %s\n') % (old, base))
2548 ui.note(_('copying changeset %s to %s\n') % (old, base))
2563
2549
2564 # Use version of files as in the old cset
2550 # Use version of files as in the old cset
2565 def filectxfn(repo, ctx_, path):
2551 def filectxfn(repo, ctx_, path):
2566 try:
2552 try:
2567 return old.filectx(path)
2553 return old.filectx(path)
2568 except KeyError:
2554 except KeyError:
2569 return None
2555 return None
2570
2556
2571 user = opts.get('user') or old.user()
2557 user = opts.get('user') or old.user()
2572 date = opts.get('date') or old.date()
2558 date = opts.get('date') or old.date()
2573 editform = mergeeditform(old, 'commit.amend')
2559 editform = mergeeditform(old, 'commit.amend')
2574 editor = getcommiteditor(editform=editform, **opts)
2560 editor = getcommiteditor(editform=editform, **opts)
2575 if not message:
2561 if not message:
2576 editor = getcommiteditor(edit=True, editform=editform)
2562 editor = getcommiteditor(edit=True, editform=editform)
2577 message = old.description()
2563 message = old.description()
2578
2564
2579 pureextra = extra.copy()
2565 pureextra = extra.copy()
2580 extra['amend_source'] = old.hex()
2566 extra['amend_source'] = old.hex()
2581
2567
2582 new = context.memctx(repo,
2568 new = context.memctx(repo,
2583 parents=[base.node(), old.p2().node()],
2569 parents=[base.node(), old.p2().node()],
2584 text=message,
2570 text=message,
2585 files=files,
2571 files=files,
2586 filectxfn=filectxfn,
2572 filectxfn=filectxfn,
2587 user=user,
2573 user=user,
2588 date=date,
2574 date=date,
2589 extra=extra,
2575 extra=extra,
2590 editor=editor)
2576 editor=editor)
2591
2577
2592 newdesc = changelog.stripdesc(new.description())
2578 newdesc = changelog.stripdesc(new.description())
2593 if ((not node)
2579 if ((not node)
2594 and newdesc == old.description()
2580 and newdesc == old.description()
2595 and user == old.user()
2581 and user == old.user()
2596 and date == old.date()
2582 and date == old.date()
2597 and pureextra == old.extra()):
2583 and pureextra == old.extra()):
2598 # nothing changed. continuing here would create a new node
2584 # nothing changed. continuing here would create a new node
2599 # anyway because of the amend_source noise.
2585 # anyway because of the amend_source noise.
2600 #
2586 #
2601 # This not what we expect from amend.
2587 # This not what we expect from amend.
2602 return old.node()
2588 return old.node()
2603
2589
2604 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2590 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2605 try:
2591 try:
2606 if opts.get('secret'):
2592 if opts.get('secret'):
2607 commitphase = 'secret'
2593 commitphase = 'secret'
2608 else:
2594 else:
2609 commitphase = old.phase()
2595 commitphase = old.phase()
2610 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2596 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2611 newid = repo.commitctx(new)
2597 newid = repo.commitctx(new)
2612 finally:
2598 finally:
2613 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2599 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2614 if newid != old.node():
2600 if newid != old.node():
2615 # Reroute the working copy parent to the new changeset
2601 # Reroute the working copy parent to the new changeset
2616 repo.setparents(newid, nullid)
2602 repo.setparents(newid, nullid)
2617
2603
2618 # Move bookmarks from old parent to amend commit
2604 # Move bookmarks from old parent to amend commit
2619 bms = repo.nodebookmarks(old.node())
2605 bms = repo.nodebookmarks(old.node())
2620 if bms:
2606 if bms:
2621 marks = repo._bookmarks
2607 marks = repo._bookmarks
2622 for bm in bms:
2608 for bm in bms:
2623 marks[bm] = newid
2609 marks[bm] = newid
2624 marks.write()
2610 marks.write()
2625 #commit the whole amend process
2611 #commit the whole amend process
2626 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2612 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2627 if createmarkers and newid != old.node():
2613 if createmarkers and newid != old.node():
2628 # mark the new changeset as successor of the rewritten one
2614 # mark the new changeset as successor of the rewritten one
2629 new = repo[newid]
2615 new = repo[newid]
2630 obs = [(old, (new,))]
2616 obs = [(old, (new,))]
2631 if node:
2617 if node:
2632 obs.append((ctx, ()))
2618 obs.append((ctx, ()))
2633
2619
2634 obsolete.createmarkers(repo, obs)
2620 obsolete.createmarkers(repo, obs)
2635 tr.close()
2621 tr.close()
2636 finally:
2622 finally:
2637 tr.release()
2623 tr.release()
2638 if not createmarkers and newid != old.node():
2624 if not createmarkers and newid != old.node():
2639 # Strip the intermediate commit (if there was one) and the amended
2625 # Strip the intermediate commit (if there was one) and the amended
2640 # commit
2626 # commit
2641 if node:
2627 if node:
2642 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2628 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2643 ui.note(_('stripping amended changeset %s\n') % old)
2629 ui.note(_('stripping amended changeset %s\n') % old)
2644 repair.strip(ui, repo, old.node(), topic='amend-backup')
2630 repair.strip(ui, repo, old.node(), topic='amend-backup')
2645 finally:
2631 finally:
2646 if newid is None:
2632 if newid is None:
2647 repo.dirstate.invalidate()
2633 repo.dirstate.invalidate()
2648 lockmod.release(lock, wlock)
2634 lockmod.release(lock, wlock)
2649 return newid
2635 return newid
2650
2636
2651 def commiteditor(repo, ctx, subs, editform=''):
2637 def commiteditor(repo, ctx, subs, editform=''):
2652 if ctx.description():
2638 if ctx.description():
2653 return ctx.description()
2639 return ctx.description()
2654 return commitforceeditor(repo, ctx, subs, editform=editform)
2640 return commitforceeditor(repo, ctx, subs, editform=editform)
2655
2641
2656 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2642 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2657 editform=''):
2643 editform=''):
2658 if not extramsg:
2644 if not extramsg:
2659 extramsg = _("Leave message empty to abort commit.")
2645 extramsg = _("Leave message empty to abort commit.")
2660
2646
2661 forms = [e for e in editform.split('.') if e]
2647 forms = [e for e in editform.split('.') if e]
2662 forms.insert(0, 'changeset')
2648 forms.insert(0, 'changeset')
2663 while forms:
2649 while forms:
2664 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2650 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2665 if tmpl:
2651 if tmpl:
2666 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2652 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2667 break
2653 break
2668 forms.pop()
2654 forms.pop()
2669 else:
2655 else:
2670 committext = buildcommittext(repo, ctx, subs, extramsg)
2656 committext = buildcommittext(repo, ctx, subs, extramsg)
2671
2657
2672 # run editor in the repository root
2658 # run editor in the repository root
2673 olddir = os.getcwd()
2659 olddir = os.getcwd()
2674 os.chdir(repo.root)
2660 os.chdir(repo.root)
2675 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2661 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2676 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2662 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2677 os.chdir(olddir)
2663 os.chdir(olddir)
2678
2664
2679 if finishdesc:
2665 if finishdesc:
2680 text = finishdesc(text)
2666 text = finishdesc(text)
2681 if not text.strip():
2667 if not text.strip():
2682 raise util.Abort(_("empty commit message"))
2668 raise util.Abort(_("empty commit message"))
2683
2669
2684 return text
2670 return text
2685
2671
2686 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2672 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2687 ui = repo.ui
2673 ui = repo.ui
2688 tmpl, mapfile = gettemplate(ui, tmpl, None)
2674 tmpl, mapfile = gettemplate(ui, tmpl, None)
2689
2675
2690 try:
2676 try:
2691 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2677 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2692 except SyntaxError, inst:
2678 except SyntaxError, inst:
2693 raise util.Abort(inst.args[0])
2679 raise util.Abort(inst.args[0])
2694
2680
2695 for k, v in repo.ui.configitems('committemplate'):
2681 for k, v in repo.ui.configitems('committemplate'):
2696 if k != 'changeset':
2682 if k != 'changeset':
2697 t.t.cache[k] = v
2683 t.t.cache[k] = v
2698
2684
2699 if not extramsg:
2685 if not extramsg:
2700 extramsg = '' # ensure that extramsg is string
2686 extramsg = '' # ensure that extramsg is string
2701
2687
2702 ui.pushbuffer()
2688 ui.pushbuffer()
2703 t.show(ctx, extramsg=extramsg)
2689 t.show(ctx, extramsg=extramsg)
2704 return ui.popbuffer()
2690 return ui.popbuffer()
2705
2691
2706 def buildcommittext(repo, ctx, subs, extramsg):
2692 def buildcommittext(repo, ctx, subs, extramsg):
2707 edittext = []
2693 edittext = []
2708 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2694 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2709 if ctx.description():
2695 if ctx.description():
2710 edittext.append(ctx.description())
2696 edittext.append(ctx.description())
2711 edittext.append("")
2697 edittext.append("")
2712 edittext.append("") # Empty line between message and comments.
2698 edittext.append("") # Empty line between message and comments.
2713 edittext.append(_("HG: Enter commit message."
2699 edittext.append(_("HG: Enter commit message."
2714 " Lines beginning with 'HG:' are removed."))
2700 " Lines beginning with 'HG:' are removed."))
2715 edittext.append("HG: %s" % extramsg)
2701 edittext.append("HG: %s" % extramsg)
2716 edittext.append("HG: --")
2702 edittext.append("HG: --")
2717 edittext.append(_("HG: user: %s") % ctx.user())
2703 edittext.append(_("HG: user: %s") % ctx.user())
2718 if ctx.p2():
2704 if ctx.p2():
2719 edittext.append(_("HG: branch merge"))
2705 edittext.append(_("HG: branch merge"))
2720 if ctx.branch():
2706 if ctx.branch():
2721 edittext.append(_("HG: branch '%s'") % ctx.branch())
2707 edittext.append(_("HG: branch '%s'") % ctx.branch())
2722 if bookmarks.iscurrent(repo):
2708 if bookmarks.iscurrent(repo):
2723 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2709 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2724 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2710 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2725 edittext.extend([_("HG: added %s") % f for f in added])
2711 edittext.extend([_("HG: added %s") % f for f in added])
2726 edittext.extend([_("HG: changed %s") % f for f in modified])
2712 edittext.extend([_("HG: changed %s") % f for f in modified])
2727 edittext.extend([_("HG: removed %s") % f for f in removed])
2713 edittext.extend([_("HG: removed %s") % f for f in removed])
2728 if not added and not modified and not removed:
2714 if not added and not modified and not removed:
2729 edittext.append(_("HG: no files changed"))
2715 edittext.append(_("HG: no files changed"))
2730 edittext.append("")
2716 edittext.append("")
2731
2717
2732 return "\n".join(edittext)
2718 return "\n".join(edittext)
2733
2719
2734 def commitstatus(repo, node, branch, bheads=None, opts={}):
2720 def commitstatus(repo, node, branch, bheads=None, opts={}):
2735 ctx = repo[node]
2721 ctx = repo[node]
2736 parents = ctx.parents()
2722 parents = ctx.parents()
2737
2723
2738 if (not opts.get('amend') and bheads and node not in bheads and not
2724 if (not opts.get('amend') and bheads and node not in bheads and not
2739 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2725 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2740 repo.ui.status(_('created new head\n'))
2726 repo.ui.status(_('created new head\n'))
2741 # The message is not printed for initial roots. For the other
2727 # The message is not printed for initial roots. For the other
2742 # changesets, it is printed in the following situations:
2728 # changesets, it is printed in the following situations:
2743 #
2729 #
2744 # Par column: for the 2 parents with ...
2730 # Par column: for the 2 parents with ...
2745 # N: null or no parent
2731 # N: null or no parent
2746 # B: parent is on another named branch
2732 # B: parent is on another named branch
2747 # C: parent is a regular non head changeset
2733 # C: parent is a regular non head changeset
2748 # H: parent was a branch head of the current branch
2734 # H: parent was a branch head of the current branch
2749 # Msg column: whether we print "created new head" message
2735 # Msg column: whether we print "created new head" message
2750 # In the following, it is assumed that there already exists some
2736 # In the following, it is assumed that there already exists some
2751 # initial branch heads of the current branch, otherwise nothing is
2737 # initial branch heads of the current branch, otherwise nothing is
2752 # printed anyway.
2738 # printed anyway.
2753 #
2739 #
2754 # Par Msg Comment
2740 # Par Msg Comment
2755 # N N y additional topo root
2741 # N N y additional topo root
2756 #
2742 #
2757 # B N y additional branch root
2743 # B N y additional branch root
2758 # C N y additional topo head
2744 # C N y additional topo head
2759 # H N n usual case
2745 # H N n usual case
2760 #
2746 #
2761 # B B y weird additional branch root
2747 # B B y weird additional branch root
2762 # C B y branch merge
2748 # C B y branch merge
2763 # H B n merge with named branch
2749 # H B n merge with named branch
2764 #
2750 #
2765 # C C y additional head from merge
2751 # C C y additional head from merge
2766 # C H n merge with a head
2752 # C H n merge with a head
2767 #
2753 #
2768 # H H n head merge: head count decreases
2754 # H H n head merge: head count decreases
2769
2755
2770 if not opts.get('close_branch'):
2756 if not opts.get('close_branch'):
2771 for r in parents:
2757 for r in parents:
2772 if r.closesbranch() and r.branch() == branch:
2758 if r.closesbranch() and r.branch() == branch:
2773 repo.ui.status(_('reopening closed branch head %d\n') % r)
2759 repo.ui.status(_('reopening closed branch head %d\n') % r)
2774
2760
2775 if repo.ui.debugflag:
2761 if repo.ui.debugflag:
2776 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2762 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2777 elif repo.ui.verbose:
2763 elif repo.ui.verbose:
2778 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2764 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2779
2765
2780 def revert(ui, repo, ctx, parents, *pats, **opts):
2766 def revert(ui, repo, ctx, parents, *pats, **opts):
2781 parent, p2 = parents
2767 parent, p2 = parents
2782 node = ctx.node()
2768 node = ctx.node()
2783
2769
2784 mf = ctx.manifest()
2770 mf = ctx.manifest()
2785 if node == p2:
2771 if node == p2:
2786 parent = p2
2772 parent = p2
2787 if node == parent:
2773 if node == parent:
2788 pmf = mf
2774 pmf = mf
2789 else:
2775 else:
2790 pmf = None
2776 pmf = None
2791
2777
2792 # need all matching names in dirstate and manifest of target rev,
2778 # need all matching names in dirstate and manifest of target rev,
2793 # so have to walk both. do not print errors if files exist in one
2779 # so have to walk both. do not print errors if files exist in one
2794 # but not other. in both cases, filesets should be evaluated against
2780 # but not other. in both cases, filesets should be evaluated against
2795 # workingctx to get consistent result (issue4497). this means 'set:**'
2781 # workingctx to get consistent result (issue4497). this means 'set:**'
2796 # cannot be used to select missing files from target rev.
2782 # cannot be used to select missing files from target rev.
2797
2783
2798 # `names` is a mapping for all elements in working copy and target revision
2784 # `names` is a mapping for all elements in working copy and target revision
2799 # The mapping is in the form:
2785 # The mapping is in the form:
2800 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2786 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2801 names = {}
2787 names = {}
2802
2788
2803 wlock = repo.wlock()
2789 wlock = repo.wlock()
2804 try:
2790 try:
2805 ## filling of the `names` mapping
2791 ## filling of the `names` mapping
2806 # walk dirstate to fill `names`
2792 # walk dirstate to fill `names`
2807
2793
2808 interactive = opts.get('interactive', False)
2794 interactive = opts.get('interactive', False)
2809 wctx = repo[None]
2795 wctx = repo[None]
2810 m = scmutil.match(wctx, pats, opts)
2796 m = scmutil.match(wctx, pats, opts)
2811
2797
2812 # we'll need this later
2798 # we'll need this later
2813 targetsubs = sorted(s for s in wctx.substate if m(s))
2799 targetsubs = sorted(s for s in wctx.substate if m(s))
2814
2800
2815 if not m.always():
2801 if not m.always():
2816 m.bad = lambda x, y: False
2802 m.bad = lambda x, y: False
2817 for abs in repo.walk(m):
2803 for abs in repo.walk(m):
2818 names[abs] = m.rel(abs), m.exact(abs)
2804 names[abs] = m.rel(abs), m.exact(abs)
2819
2805
2820 # walk target manifest to fill `names`
2806 # walk target manifest to fill `names`
2821
2807
2822 def badfn(path, msg):
2808 def badfn(path, msg):
2823 if path in names:
2809 if path in names:
2824 return
2810 return
2825 if path in ctx.substate:
2811 if path in ctx.substate:
2826 return
2812 return
2827 path_ = path + '/'
2813 path_ = path + '/'
2828 for f in names:
2814 for f in names:
2829 if f.startswith(path_):
2815 if f.startswith(path_):
2830 return
2816 return
2831 ui.warn("%s: %s\n" % (m.rel(path), msg))
2817 ui.warn("%s: %s\n" % (m.rel(path), msg))
2832
2818
2833 m.bad = badfn
2819 m.bad = badfn
2834 for abs in ctx.walk(m):
2820 for abs in ctx.walk(m):
2835 if abs not in names:
2821 if abs not in names:
2836 names[abs] = m.rel(abs), m.exact(abs)
2822 names[abs] = m.rel(abs), m.exact(abs)
2837
2823
2838 # Find status of all file in `names`.
2824 # Find status of all file in `names`.
2839 m = scmutil.matchfiles(repo, names)
2825 m = scmutil.matchfiles(repo, names)
2840
2826
2841 changes = repo.status(node1=node, match=m,
2827 changes = repo.status(node1=node, match=m,
2842 unknown=True, ignored=True, clean=True)
2828 unknown=True, ignored=True, clean=True)
2843 else:
2829 else:
2844 changes = repo.status(node1=node, match=m)
2830 changes = repo.status(node1=node, match=m)
2845 for kind in changes:
2831 for kind in changes:
2846 for abs in kind:
2832 for abs in kind:
2847 names[abs] = m.rel(abs), m.exact(abs)
2833 names[abs] = m.rel(abs), m.exact(abs)
2848
2834
2849 m = scmutil.matchfiles(repo, names)
2835 m = scmutil.matchfiles(repo, names)
2850
2836
2851 modified = set(changes.modified)
2837 modified = set(changes.modified)
2852 added = set(changes.added)
2838 added = set(changes.added)
2853 removed = set(changes.removed)
2839 removed = set(changes.removed)
2854 _deleted = set(changes.deleted)
2840 _deleted = set(changes.deleted)
2855 unknown = set(changes.unknown)
2841 unknown = set(changes.unknown)
2856 unknown.update(changes.ignored)
2842 unknown.update(changes.ignored)
2857 clean = set(changes.clean)
2843 clean = set(changes.clean)
2858 modadded = set()
2844 modadded = set()
2859
2845
2860 # split between files known in target manifest and the others
2846 # split between files known in target manifest and the others
2861 smf = set(mf)
2847 smf = set(mf)
2862
2848
2863 # determine the exact nature of the deleted changesets
2849 # determine the exact nature of the deleted changesets
2864 deladded = _deleted - smf
2850 deladded = _deleted - smf
2865 deleted = _deleted - deladded
2851 deleted = _deleted - deladded
2866
2852
2867 # We need to account for the state of the file in the dirstate,
2853 # We need to account for the state of the file in the dirstate,
2868 # even when we revert against something else than parent. This will
2854 # even when we revert against something else than parent. This will
2869 # slightly alter the behavior of revert (doing back up or not, delete
2855 # slightly alter the behavior of revert (doing back up or not, delete
2870 # or just forget etc).
2856 # or just forget etc).
2871 if parent == node:
2857 if parent == node:
2872 dsmodified = modified
2858 dsmodified = modified
2873 dsadded = added
2859 dsadded = added
2874 dsremoved = removed
2860 dsremoved = removed
2875 # store all local modifications, useful later for rename detection
2861 # store all local modifications, useful later for rename detection
2876 localchanges = dsmodified | dsadded
2862 localchanges = dsmodified | dsadded
2877 modified, added, removed = set(), set(), set()
2863 modified, added, removed = set(), set(), set()
2878 else:
2864 else:
2879 changes = repo.status(node1=parent, match=m)
2865 changes = repo.status(node1=parent, match=m)
2880 dsmodified = set(changes.modified)
2866 dsmodified = set(changes.modified)
2881 dsadded = set(changes.added)
2867 dsadded = set(changes.added)
2882 dsremoved = set(changes.removed)
2868 dsremoved = set(changes.removed)
2883 # store all local modifications, useful later for rename detection
2869 # store all local modifications, useful later for rename detection
2884 localchanges = dsmodified | dsadded
2870 localchanges = dsmodified | dsadded
2885
2871
2886 # only take into account for removes between wc and target
2872 # only take into account for removes between wc and target
2887 clean |= dsremoved - removed
2873 clean |= dsremoved - removed
2888 dsremoved &= removed
2874 dsremoved &= removed
2889 # distinct between dirstate remove and other
2875 # distinct between dirstate remove and other
2890 removed -= dsremoved
2876 removed -= dsremoved
2891
2877
2892 modadded = added & dsmodified
2878 modadded = added & dsmodified
2893 added -= modadded
2879 added -= modadded
2894
2880
2895 # tell newly modified apart.
2881 # tell newly modified apart.
2896 dsmodified &= modified
2882 dsmodified &= modified
2897 dsmodified |= modified & dsadded # dirstate added may needs backup
2883 dsmodified |= modified & dsadded # dirstate added may needs backup
2898 modified -= dsmodified
2884 modified -= dsmodified
2899
2885
2900 # We need to wait for some post-processing to update this set
2886 # We need to wait for some post-processing to update this set
2901 # before making the distinction. The dirstate will be used for
2887 # before making the distinction. The dirstate will be used for
2902 # that purpose.
2888 # that purpose.
2903 dsadded = added
2889 dsadded = added
2904
2890
2905 # in case of merge, files that are actually added can be reported as
2891 # in case of merge, files that are actually added can be reported as
2906 # modified, we need to post process the result
2892 # modified, we need to post process the result
2907 if p2 != nullid:
2893 if p2 != nullid:
2908 if pmf is None:
2894 if pmf is None:
2909 # only need parent manifest in the merge case,
2895 # only need parent manifest in the merge case,
2910 # so do not read by default
2896 # so do not read by default
2911 pmf = repo[parent].manifest()
2897 pmf = repo[parent].manifest()
2912 mergeadd = dsmodified - set(pmf)
2898 mergeadd = dsmodified - set(pmf)
2913 dsadded |= mergeadd
2899 dsadded |= mergeadd
2914 dsmodified -= mergeadd
2900 dsmodified -= mergeadd
2915
2901
2916 # if f is a rename, update `names` to also revert the source
2902 # if f is a rename, update `names` to also revert the source
2917 cwd = repo.getcwd()
2903 cwd = repo.getcwd()
2918 for f in localchanges:
2904 for f in localchanges:
2919 src = repo.dirstate.copied(f)
2905 src = repo.dirstate.copied(f)
2920 # XXX should we check for rename down to target node?
2906 # XXX should we check for rename down to target node?
2921 if src and src not in names and repo.dirstate[src] == 'r':
2907 if src and src not in names and repo.dirstate[src] == 'r':
2922 dsremoved.add(src)
2908 dsremoved.add(src)
2923 names[src] = (repo.pathto(src, cwd), True)
2909 names[src] = (repo.pathto(src, cwd), True)
2924
2910
2925 # distinguish between file to forget and the other
2911 # distinguish between file to forget and the other
2926 added = set()
2912 added = set()
2927 for abs in dsadded:
2913 for abs in dsadded:
2928 if repo.dirstate[abs] != 'a':
2914 if repo.dirstate[abs] != 'a':
2929 added.add(abs)
2915 added.add(abs)
2930 dsadded -= added
2916 dsadded -= added
2931
2917
2932 for abs in deladded:
2918 for abs in deladded:
2933 if repo.dirstate[abs] == 'a':
2919 if repo.dirstate[abs] == 'a':
2934 dsadded.add(abs)
2920 dsadded.add(abs)
2935 deladded -= dsadded
2921 deladded -= dsadded
2936
2922
2937 # For files marked as removed, we check if an unknown file is present at
2923 # For files marked as removed, we check if an unknown file is present at
2938 # the same path. If a such file exists it may need to be backed up.
2924 # the same path. If a such file exists it may need to be backed up.
2939 # Making the distinction at this stage helps have simpler backup
2925 # Making the distinction at this stage helps have simpler backup
2940 # logic.
2926 # logic.
2941 removunk = set()
2927 removunk = set()
2942 for abs in removed:
2928 for abs in removed:
2943 target = repo.wjoin(abs)
2929 target = repo.wjoin(abs)
2944 if os.path.lexists(target):
2930 if os.path.lexists(target):
2945 removunk.add(abs)
2931 removunk.add(abs)
2946 removed -= removunk
2932 removed -= removunk
2947
2933
2948 dsremovunk = set()
2934 dsremovunk = set()
2949 for abs in dsremoved:
2935 for abs in dsremoved:
2950 target = repo.wjoin(abs)
2936 target = repo.wjoin(abs)
2951 if os.path.lexists(target):
2937 if os.path.lexists(target):
2952 dsremovunk.add(abs)
2938 dsremovunk.add(abs)
2953 dsremoved -= dsremovunk
2939 dsremoved -= dsremovunk
2954
2940
2955 # action to be actually performed by revert
2941 # action to be actually performed by revert
2956 # (<list of file>, message>) tuple
2942 # (<list of file>, message>) tuple
2957 actions = {'revert': ([], _('reverting %s\n')),
2943 actions = {'revert': ([], _('reverting %s\n')),
2958 'add': ([], _('adding %s\n')),
2944 'add': ([], _('adding %s\n')),
2959 'remove': ([], _('removing %s\n')),
2945 'remove': ([], _('removing %s\n')),
2960 'drop': ([], _('removing %s\n')),
2946 'drop': ([], _('removing %s\n')),
2961 'forget': ([], _('forgetting %s\n')),
2947 'forget': ([], _('forgetting %s\n')),
2962 'undelete': ([], _('undeleting %s\n')),
2948 'undelete': ([], _('undeleting %s\n')),
2963 'noop': (None, _('no changes needed to %s\n')),
2949 'noop': (None, _('no changes needed to %s\n')),
2964 'unknown': (None, _('file not managed: %s\n')),
2950 'unknown': (None, _('file not managed: %s\n')),
2965 }
2951 }
2966
2952
2967 # "constant" that convey the backup strategy.
2953 # "constant" that convey the backup strategy.
2968 # All set to `discard` if `no-backup` is set do avoid checking
2954 # All set to `discard` if `no-backup` is set do avoid checking
2969 # no_backup lower in the code.
2955 # no_backup lower in the code.
2970 # These values are ordered for comparison purposes
2956 # These values are ordered for comparison purposes
2971 backup = 2 # unconditionally do backup
2957 backup = 2 # unconditionally do backup
2972 check = 1 # check if the existing file differs from target
2958 check = 1 # check if the existing file differs from target
2973 discard = 0 # never do backup
2959 discard = 0 # never do backup
2974 if opts.get('no_backup'):
2960 if opts.get('no_backup'):
2975 backup = check = discard
2961 backup = check = discard
2976
2962
2977 backupanddel = actions['remove']
2963 backupanddel = actions['remove']
2978 if not opts.get('no_backup'):
2964 if not opts.get('no_backup'):
2979 backupanddel = actions['drop']
2965 backupanddel = actions['drop']
2980
2966
2981 disptable = (
2967 disptable = (
2982 # dispatch table:
2968 # dispatch table:
2983 # file state
2969 # file state
2984 # action
2970 # action
2985 # make backup
2971 # make backup
2986
2972
2987 ## Sets that results that will change file on disk
2973 ## Sets that results that will change file on disk
2988 # Modified compared to target, no local change
2974 # Modified compared to target, no local change
2989 (modified, actions['revert'], discard),
2975 (modified, actions['revert'], discard),
2990 # Modified compared to target, but local file is deleted
2976 # Modified compared to target, but local file is deleted
2991 (deleted, actions['revert'], discard),
2977 (deleted, actions['revert'], discard),
2992 # Modified compared to target, local change
2978 # Modified compared to target, local change
2993 (dsmodified, actions['revert'], backup),
2979 (dsmodified, actions['revert'], backup),
2994 # Added since target
2980 # Added since target
2995 (added, actions['remove'], discard),
2981 (added, actions['remove'], discard),
2996 # Added in working directory
2982 # Added in working directory
2997 (dsadded, actions['forget'], discard),
2983 (dsadded, actions['forget'], discard),
2998 # Added since target, have local modification
2984 # Added since target, have local modification
2999 (modadded, backupanddel, backup),
2985 (modadded, backupanddel, backup),
3000 # Added since target but file is missing in working directory
2986 # Added since target but file is missing in working directory
3001 (deladded, actions['drop'], discard),
2987 (deladded, actions['drop'], discard),
3002 # Removed since target, before working copy parent
2988 # Removed since target, before working copy parent
3003 (removed, actions['add'], discard),
2989 (removed, actions['add'], discard),
3004 # Same as `removed` but an unknown file exists at the same path
2990 # Same as `removed` but an unknown file exists at the same path
3005 (removunk, actions['add'], check),
2991 (removunk, actions['add'], check),
3006 # Removed since targe, marked as such in working copy parent
2992 # Removed since targe, marked as such in working copy parent
3007 (dsremoved, actions['undelete'], discard),
2993 (dsremoved, actions['undelete'], discard),
3008 # Same as `dsremoved` but an unknown file exists at the same path
2994 # Same as `dsremoved` but an unknown file exists at the same path
3009 (dsremovunk, actions['undelete'], check),
2995 (dsremovunk, actions['undelete'], check),
3010 ## the following sets does not result in any file changes
2996 ## the following sets does not result in any file changes
3011 # File with no modification
2997 # File with no modification
3012 (clean, actions['noop'], discard),
2998 (clean, actions['noop'], discard),
3013 # Existing file, not tracked anywhere
2999 # Existing file, not tracked anywhere
3014 (unknown, actions['unknown'], discard),
3000 (unknown, actions['unknown'], discard),
3015 )
3001 )
3016
3002
3017 for abs, (rel, exact) in sorted(names.items()):
3003 for abs, (rel, exact) in sorted(names.items()):
3018 # target file to be touch on disk (relative to cwd)
3004 # target file to be touch on disk (relative to cwd)
3019 target = repo.wjoin(abs)
3005 target = repo.wjoin(abs)
3020 # search the entry in the dispatch table.
3006 # search the entry in the dispatch table.
3021 # if the file is in any of these sets, it was touched in the working
3007 # if the file is in any of these sets, it was touched in the working
3022 # directory parent and we are sure it needs to be reverted.
3008 # directory parent and we are sure it needs to be reverted.
3023 for table, (xlist, msg), dobackup in disptable:
3009 for table, (xlist, msg), dobackup in disptable:
3024 if abs not in table:
3010 if abs not in table:
3025 continue
3011 continue
3026 if xlist is not None:
3012 if xlist is not None:
3027 xlist.append(abs)
3013 xlist.append(abs)
3028 if dobackup and (backup <= dobackup
3014 if dobackup and (backup <= dobackup
3029 or wctx[abs].cmp(ctx[abs])):
3015 or wctx[abs].cmp(ctx[abs])):
3030 bakname = "%s.orig" % rel
3016 bakname = "%s.orig" % rel
3031 ui.note(_('saving current version of %s as %s\n') %
3017 ui.note(_('saving current version of %s as %s\n') %
3032 (rel, bakname))
3018 (rel, bakname))
3033 if not opts.get('dry_run'):
3019 if not opts.get('dry_run'):
3034 if interactive:
3020 if interactive:
3035 util.copyfile(target, bakname)
3021 util.copyfile(target, bakname)
3036 else:
3022 else:
3037 util.rename(target, bakname)
3023 util.rename(target, bakname)
3038 if ui.verbose or not exact:
3024 if ui.verbose or not exact:
3039 if not isinstance(msg, basestring):
3025 if not isinstance(msg, basestring):
3040 msg = msg(abs)
3026 msg = msg(abs)
3041 ui.status(msg % rel)
3027 ui.status(msg % rel)
3042 elif exact:
3028 elif exact:
3043 ui.warn(msg % rel)
3029 ui.warn(msg % rel)
3044 break
3030 break
3045
3031
3046 if not opts.get('dry_run'):
3032 if not opts.get('dry_run'):
3047 needdata = ('revert', 'add', 'undelete')
3033 needdata = ('revert', 'add', 'undelete')
3048 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3034 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3049 _performrevert(repo, parents, ctx, actions, interactive)
3035 _performrevert(repo, parents, ctx, actions, interactive)
3050
3036
3051 if targetsubs:
3037 if targetsubs:
3052 # Revert the subrepos on the revert list
3038 # Revert the subrepos on the revert list
3053 for sub in targetsubs:
3039 for sub in targetsubs:
3054 try:
3040 try:
3055 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3041 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3056 except KeyError:
3042 except KeyError:
3057 raise util.Abort("subrepository '%s' does not exist in %s!"
3043 raise util.Abort("subrepository '%s' does not exist in %s!"
3058 % (sub, short(ctx.node())))
3044 % (sub, short(ctx.node())))
3059 finally:
3045 finally:
3060 wlock.release()
3046 wlock.release()
3061
3047
3062 def _revertprefetch(repo, ctx, *files):
3048 def _revertprefetch(repo, ctx, *files):
3063 """Let extension changing the storage layer prefetch content"""
3049 """Let extension changing the storage layer prefetch content"""
3064 pass
3050 pass
3065
3051
3066 def _performrevert(repo, parents, ctx, actions, interactive=False):
3052 def _performrevert(repo, parents, ctx, actions, interactive=False):
3067 """function that actually perform all the actions computed for revert
3053 """function that actually perform all the actions computed for revert
3068
3054
3069 This is an independent function to let extension to plug in and react to
3055 This is an independent function to let extension to plug in and react to
3070 the imminent revert.
3056 the imminent revert.
3071
3057
3072 Make sure you have the working directory locked when calling this function.
3058 Make sure you have the working directory locked when calling this function.
3073 """
3059 """
3074 parent, p2 = parents
3060 parent, p2 = parents
3075 node = ctx.node()
3061 node = ctx.node()
3076 def checkout(f):
3062 def checkout(f):
3077 fc = ctx[f]
3063 fc = ctx[f]
3078 repo.wwrite(f, fc.data(), fc.flags())
3064 repo.wwrite(f, fc.data(), fc.flags())
3079
3065
3080 audit_path = pathutil.pathauditor(repo.root)
3066 audit_path = pathutil.pathauditor(repo.root)
3081 for f in actions['forget'][0]:
3067 for f in actions['forget'][0]:
3082 repo.dirstate.drop(f)
3068 repo.dirstate.drop(f)
3083 for f in actions['remove'][0]:
3069 for f in actions['remove'][0]:
3084 audit_path(f)
3070 audit_path(f)
3085 util.unlinkpath(repo.wjoin(f))
3071 util.unlinkpath(repo.wjoin(f))
3086 repo.dirstate.remove(f)
3072 repo.dirstate.remove(f)
3087 for f in actions['drop'][0]:
3073 for f in actions['drop'][0]:
3088 audit_path(f)
3074 audit_path(f)
3089 repo.dirstate.remove(f)
3075 repo.dirstate.remove(f)
3090
3076
3091 normal = None
3077 normal = None
3092 if node == parent:
3078 if node == parent:
3093 # We're reverting to our parent. If possible, we'd like status
3079 # We're reverting to our parent. If possible, we'd like status
3094 # to report the file as clean. We have to use normallookup for
3080 # to report the file as clean. We have to use normallookup for
3095 # merges to avoid losing information about merged/dirty files.
3081 # merges to avoid losing information about merged/dirty files.
3096 if p2 != nullid:
3082 if p2 != nullid:
3097 normal = repo.dirstate.normallookup
3083 normal = repo.dirstate.normallookup
3098 else:
3084 else:
3099 normal = repo.dirstate.normal
3085 normal = repo.dirstate.normal
3100
3086
3101 if interactive:
3087 if interactive:
3102 # Prompt the user for changes to revert
3088 # Prompt the user for changes to revert
3103 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3089 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3104 m = scmutil.match(ctx, torevert, {})
3090 m = scmutil.match(ctx, torevert, {})
3105 diff = patch.diff(repo, None, ctx.node(), m)
3091 diff = patch.diff(repo, None, ctx.node(), m)
3106 originalchunks = patch.parsepatch(diff)
3092 originalchunks = patch.parsepatch(diff)
3107 try:
3093 try:
3108 chunks = recordfilter(repo.ui, originalchunks)
3094 chunks = recordfilter(repo.ui, originalchunks)
3109 except patch.PatchError, err:
3095 except patch.PatchError, err:
3110 raise util.Abort(_('error parsing patch: %s') % err)
3096 raise util.Abort(_('error parsing patch: %s') % err)
3111
3097
3112 # Apply changes
3098 # Apply changes
3113 fp = cStringIO.StringIO()
3099 fp = cStringIO.StringIO()
3114 for c in chunks:
3100 for c in chunks:
3115 c.write(fp)
3101 c.write(fp)
3116 dopatch = fp.tell()
3102 dopatch = fp.tell()
3117 fp.seek(0)
3103 fp.seek(0)
3118 if dopatch:
3104 if dopatch:
3119 try:
3105 try:
3120 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3106 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3121 except patch.PatchError, err:
3107 except patch.PatchError, err:
3122 raise util.Abort(str(err))
3108 raise util.Abort(str(err))
3123 del fp
3109 del fp
3124
3110
3125 for f in actions['revert'][0]:
3111 for f in actions['revert'][0]:
3126 if normal:
3112 if normal:
3127 normal(f)
3113 normal(f)
3128
3114
3129 else:
3115 else:
3130 for f in actions['revert'][0]:
3116 for f in actions['revert'][0]:
3131 checkout(f)
3117 checkout(f)
3132 if normal:
3118 if normal:
3133 normal(f)
3119 normal(f)
3134
3120
3135 for f in actions['add'][0]:
3121 for f in actions['add'][0]:
3136 checkout(f)
3122 checkout(f)
3137 repo.dirstate.add(f)
3123 repo.dirstate.add(f)
3138
3124
3139 normal = repo.dirstate.normallookup
3125 normal = repo.dirstate.normallookup
3140 if node == parent and p2 == nullid:
3126 if node == parent and p2 == nullid:
3141 normal = repo.dirstate.normal
3127 normal = repo.dirstate.normal
3142 for f in actions['undelete'][0]:
3128 for f in actions['undelete'][0]:
3143 checkout(f)
3129 checkout(f)
3144 normal(f)
3130 normal(f)
3145
3131
3146 copied = copies.pathcopies(repo[parent], ctx)
3132 copied = copies.pathcopies(repo[parent], ctx)
3147
3133
3148 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3134 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3149 if f in copied:
3135 if f in copied:
3150 repo.dirstate.copy(copied[f], f)
3136 repo.dirstate.copy(copied[f], f)
3151
3137
3152 def command(table):
3138 def command(table):
3153 """Returns a function object to be used as a decorator for making commands.
3139 """Returns a function object to be used as a decorator for making commands.
3154
3140
3155 This function receives a command table as its argument. The table should
3141 This function receives a command table as its argument. The table should
3156 be a dict.
3142 be a dict.
3157
3143
3158 The returned function can be used as a decorator for adding commands
3144 The returned function can be used as a decorator for adding commands
3159 to that command table. This function accepts multiple arguments to define
3145 to that command table. This function accepts multiple arguments to define
3160 a command.
3146 a command.
3161
3147
3162 The first argument is the command name.
3148 The first argument is the command name.
3163
3149
3164 The options argument is an iterable of tuples defining command arguments.
3150 The options argument is an iterable of tuples defining command arguments.
3165 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3151 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3166
3152
3167 The synopsis argument defines a short, one line summary of how to use the
3153 The synopsis argument defines a short, one line summary of how to use the
3168 command. This shows up in the help output.
3154 command. This shows up in the help output.
3169
3155
3170 The norepo argument defines whether the command does not require a
3156 The norepo argument defines whether the command does not require a
3171 local repository. Most commands operate against a repository, thus the
3157 local repository. Most commands operate against a repository, thus the
3172 default is False.
3158 default is False.
3173
3159
3174 The optionalrepo argument defines whether the command optionally requires
3160 The optionalrepo argument defines whether the command optionally requires
3175 a local repository.
3161 a local repository.
3176
3162
3177 The inferrepo argument defines whether to try to find a repository from the
3163 The inferrepo argument defines whether to try to find a repository from the
3178 command line arguments. If True, arguments will be examined for potential
3164 command line arguments. If True, arguments will be examined for potential
3179 repository locations. See ``findrepo()``. If a repository is found, it
3165 repository locations. See ``findrepo()``. If a repository is found, it
3180 will be used.
3166 will be used.
3181 """
3167 """
3182 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3168 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3183 inferrepo=False):
3169 inferrepo=False):
3184 def decorator(func):
3170 def decorator(func):
3185 if synopsis:
3171 if synopsis:
3186 table[name] = func, list(options), synopsis
3172 table[name] = func, list(options), synopsis
3187 else:
3173 else:
3188 table[name] = func, list(options)
3174 table[name] = func, list(options)
3189
3175
3190 if norepo:
3176 if norepo:
3191 # Avoid import cycle.
3177 # Avoid import cycle.
3192 import commands
3178 import commands
3193 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3179 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3194
3180
3195 if optionalrepo:
3181 if optionalrepo:
3196 import commands
3182 import commands
3197 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3183 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3198
3184
3199 if inferrepo:
3185 if inferrepo:
3200 import commands
3186 import commands
3201 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3187 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3202
3188
3203 return func
3189 return func
3204 return decorator
3190 return decorator
3205
3191
3206 return cmd
3192 return cmd
3207
3193
3208 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3194 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3209 # commands.outgoing. "missing" is "missing" of the result of
3195 # commands.outgoing. "missing" is "missing" of the result of
3210 # "findcommonoutgoing()"
3196 # "findcommonoutgoing()"
3211 outgoinghooks = util.hooks()
3197 outgoinghooks = util.hooks()
3212
3198
3213 # a list of (ui, repo) functions called by commands.summary
3199 # a list of (ui, repo) functions called by commands.summary
3214 summaryhooks = util.hooks()
3200 summaryhooks = util.hooks()
3215
3201
3216 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3202 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3217 #
3203 #
3218 # functions should return tuple of booleans below, if 'changes' is None:
3204 # functions should return tuple of booleans below, if 'changes' is None:
3219 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3205 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3220 #
3206 #
3221 # otherwise, 'changes' is a tuple of tuples below:
3207 # otherwise, 'changes' is a tuple of tuples below:
3222 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3208 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3223 # - (desturl, destbranch, destpeer, outgoing)
3209 # - (desturl, destbranch, destpeer, outgoing)
3224 summaryremotehooks = util.hooks()
3210 summaryremotehooks = util.hooks()
3225
3211
3226 # A list of state files kept by multistep operations like graft.
3212 # A list of state files kept by multistep operations like graft.
3227 # Since graft cannot be aborted, it is considered 'clearable' by update.
3213 # Since graft cannot be aborted, it is considered 'clearable' by update.
3228 # note: bisect is intentionally excluded
3214 # note: bisect is intentionally excluded
3229 # (state file, clearable, allowcommit, error, hint)
3215 # (state file, clearable, allowcommit, error, hint)
3230 unfinishedstates = [
3216 unfinishedstates = [
3231 ('graftstate', True, False, _('graft in progress'),
3217 ('graftstate', True, False, _('graft in progress'),
3232 _("use 'hg graft --continue' or 'hg update' to abort")),
3218 _("use 'hg graft --continue' or 'hg update' to abort")),
3233 ('updatestate', True, False, _('last update was interrupted'),
3219 ('updatestate', True, False, _('last update was interrupted'),
3234 _("use 'hg update' to get a consistent checkout"))
3220 _("use 'hg update' to get a consistent checkout"))
3235 ]
3221 ]
3236
3222
3237 def checkunfinished(repo, commit=False):
3223 def checkunfinished(repo, commit=False):
3238 '''Look for an unfinished multistep operation, like graft, and abort
3224 '''Look for an unfinished multistep operation, like graft, and abort
3239 if found. It's probably good to check this right before
3225 if found. It's probably good to check this right before
3240 bailifchanged().
3226 bailifchanged().
3241 '''
3227 '''
3242 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3228 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3243 if commit and allowcommit:
3229 if commit and allowcommit:
3244 continue
3230 continue
3245 if repo.vfs.exists(f):
3231 if repo.vfs.exists(f):
3246 raise util.Abort(msg, hint=hint)
3232 raise util.Abort(msg, hint=hint)
3247
3233
3248 def clearunfinished(repo):
3234 def clearunfinished(repo):
3249 '''Check for unfinished operations (as above), and clear the ones
3235 '''Check for unfinished operations (as above), and clear the ones
3250 that are clearable.
3236 that are clearable.
3251 '''
3237 '''
3252 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3238 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3253 if not clearable and repo.vfs.exists(f):
3239 if not clearable and repo.vfs.exists(f):
3254 raise util.Abort(msg, hint=hint)
3240 raise util.Abort(msg, hint=hint)
3255 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3241 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3256 if clearable and repo.vfs.exists(f):
3242 if clearable and repo.vfs.exists(f):
3257 util.unlink(repo.join(f))
3243 util.unlink(repo.join(f))
General Comments 0
You need to be logged in to leave comments. Login now