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