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