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