##// END OF EJS Templates
cmdutil: enhance "getcommiteditor()" for specific usages in MQ...
FUJIWARA Katsunori -
r21419:27278548 default
parent child Browse files
Show More
@@ -1,2479 +1,2501 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile
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 lock as lockmod
16 import lock as lockmod
17
17
18 def parsealiases(cmd):
18 def parsealiases(cmd):
19 return cmd.lstrip("^").split("|")
19 return cmd.lstrip("^").split("|")
20
20
21 def findpossible(cmd, table, strict=False):
21 def findpossible(cmd, table, strict=False):
22 """
22 """
23 Return cmd -> (aliases, command table entry)
23 Return cmd -> (aliases, command table entry)
24 for each matching command.
24 for each matching command.
25 Return debug commands (or their aliases) only if no normal command matches.
25 Return debug commands (or their aliases) only if no normal command matches.
26 """
26 """
27 choice = {}
27 choice = {}
28 debugchoice = {}
28 debugchoice = {}
29
29
30 if cmd in table:
30 if cmd in table:
31 # short-circuit exact matches, "log" alias beats "^log|history"
31 # short-circuit exact matches, "log" alias beats "^log|history"
32 keys = [cmd]
32 keys = [cmd]
33 else:
33 else:
34 keys = table.keys()
34 keys = table.keys()
35
35
36 for e in keys:
36 for e in keys:
37 aliases = parsealiases(e)
37 aliases = parsealiases(e)
38 found = None
38 found = None
39 if cmd in aliases:
39 if cmd in aliases:
40 found = cmd
40 found = cmd
41 elif not strict:
41 elif not strict:
42 for a in aliases:
42 for a in aliases:
43 if a.startswith(cmd):
43 if a.startswith(cmd):
44 found = a
44 found = a
45 break
45 break
46 if found is not None:
46 if found is not None:
47 if aliases[0].startswith("debug") or found.startswith("debug"):
47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 debugchoice[found] = (aliases, table[e])
48 debugchoice[found] = (aliases, table[e])
49 else:
49 else:
50 choice[found] = (aliases, table[e])
50 choice[found] = (aliases, table[e])
51
51
52 if not choice and debugchoice:
52 if not choice and debugchoice:
53 choice = debugchoice
53 choice = debugchoice
54
54
55 return choice
55 return choice
56
56
57 def findcmd(cmd, table, strict=True):
57 def findcmd(cmd, table, strict=True):
58 """Return (aliases, command table entry) for command string."""
58 """Return (aliases, command table entry) for command string."""
59 choice = findpossible(cmd, table, strict)
59 choice = findpossible(cmd, table, strict)
60
60
61 if cmd in choice:
61 if cmd in choice:
62 return choice[cmd]
62 return choice[cmd]
63
63
64 if len(choice) > 1:
64 if len(choice) > 1:
65 clist = choice.keys()
65 clist = choice.keys()
66 clist.sort()
66 clist.sort()
67 raise error.AmbiguousCommand(cmd, clist)
67 raise error.AmbiguousCommand(cmd, clist)
68
68
69 if choice:
69 if choice:
70 return choice.values()[0]
70 return choice.values()[0]
71
71
72 raise error.UnknownCommand(cmd)
72 raise error.UnknownCommand(cmd)
73
73
74 def findrepo(p):
74 def findrepo(p):
75 while not os.path.isdir(os.path.join(p, ".hg")):
75 while not os.path.isdir(os.path.join(p, ".hg")):
76 oldp, p = p, os.path.dirname(p)
76 oldp, p = p, os.path.dirname(p)
77 if p == oldp:
77 if p == oldp:
78 return None
78 return None
79
79
80 return p
80 return p
81
81
82 def bailifchanged(repo):
82 def bailifchanged(repo):
83 if repo.dirstate.p2() != nullid:
83 if repo.dirstate.p2() != nullid:
84 raise util.Abort(_('outstanding uncommitted merge'))
84 raise util.Abort(_('outstanding uncommitted merge'))
85 modified, added, removed, deleted = repo.status()[:4]
85 modified, added, removed, deleted = repo.status()[:4]
86 if modified or added or removed or deleted:
86 if modified or added or removed or deleted:
87 raise util.Abort(_('uncommitted changes'))
87 raise util.Abort(_('uncommitted changes'))
88 ctx = repo[None]
88 ctx = repo[None]
89 for s in sorted(ctx.substate):
89 for s in sorted(ctx.substate):
90 if ctx.sub(s).dirty():
90 if ctx.sub(s).dirty():
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92
92
93 def logmessage(ui, opts):
93 def logmessage(ui, opts):
94 """ get the log message according to -m and -l option """
94 """ get the log message according to -m and -l option """
95 message = opts.get('message')
95 message = opts.get('message')
96 logfile = opts.get('logfile')
96 logfile = opts.get('logfile')
97
97
98 if message and logfile:
98 if message and logfile:
99 raise util.Abort(_('options --message and --logfile are mutually '
99 raise util.Abort(_('options --message and --logfile are mutually '
100 'exclusive'))
100 'exclusive'))
101 if not message and logfile:
101 if not message and logfile:
102 try:
102 try:
103 if logfile == '-':
103 if logfile == '-':
104 message = ui.fin.read()
104 message = ui.fin.read()
105 else:
105 else:
106 message = '\n'.join(util.readfile(logfile).splitlines())
106 message = '\n'.join(util.readfile(logfile).splitlines())
107 except IOError, inst:
107 except IOError, inst:
108 raise util.Abort(_("can't read commit message '%s': %s") %
108 raise util.Abort(_("can't read commit message '%s': %s") %
109 (logfile, inst.strerror))
109 (logfile, inst.strerror))
110 return message
110 return message
111
111
112 def getcommiteditor(edit=False, **opts):
112 def getcommiteditor(edit=False, finishdesc=None, extramsg=None, **opts):
113 """get appropriate commit message editor according to '--edit' option"""
113 """get appropriate commit message editor according to '--edit' option
114 if edit:
114
115 return commitforceeditor
115 'finishdesc' is a function to be called with edited commit message
116 (= 'description' of the new changeset) just after editing, but
117 before checking empty-ness. It should return actual text to be
118 stored into history. This allows to change description before
119 storing.
120
121 'extramsg' is a extra message to be shown in the editor instead of
122 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
123 is automatically added.
124
125 'getcommiteditor' returns 'commitforceeditor' regardless of
126 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
127 they are specific for usage in MQ.
128 """
129 if edit or finishdesc or extramsg:
130 return lambda r, c, s: commitforceeditor(r, c, s,
131 finishdesc=finishdesc,
132 extramsg=extramsg)
116 else:
133 else:
117 return commiteditor
134 return commiteditor
118
135
119 def loglimit(opts):
136 def loglimit(opts):
120 """get the log limit according to option -l/--limit"""
137 """get the log limit according to option -l/--limit"""
121 limit = opts.get('limit')
138 limit = opts.get('limit')
122 if limit:
139 if limit:
123 try:
140 try:
124 limit = int(limit)
141 limit = int(limit)
125 except ValueError:
142 except ValueError:
126 raise util.Abort(_('limit must be a positive integer'))
143 raise util.Abort(_('limit must be a positive integer'))
127 if limit <= 0:
144 if limit <= 0:
128 raise util.Abort(_('limit must be positive'))
145 raise util.Abort(_('limit must be positive'))
129 else:
146 else:
130 limit = None
147 limit = None
131 return limit
148 return limit
132
149
133 def makefilename(repo, pat, node, desc=None,
150 def makefilename(repo, pat, node, desc=None,
134 total=None, seqno=None, revwidth=None, pathname=None):
151 total=None, seqno=None, revwidth=None, pathname=None):
135 node_expander = {
152 node_expander = {
136 'H': lambda: hex(node),
153 'H': lambda: hex(node),
137 'R': lambda: str(repo.changelog.rev(node)),
154 'R': lambda: str(repo.changelog.rev(node)),
138 'h': lambda: short(node),
155 'h': lambda: short(node),
139 'm': lambda: re.sub('[^\w]', '_', str(desc))
156 'm': lambda: re.sub('[^\w]', '_', str(desc))
140 }
157 }
141 expander = {
158 expander = {
142 '%': lambda: '%',
159 '%': lambda: '%',
143 'b': lambda: os.path.basename(repo.root),
160 'b': lambda: os.path.basename(repo.root),
144 }
161 }
145
162
146 try:
163 try:
147 if node:
164 if node:
148 expander.update(node_expander)
165 expander.update(node_expander)
149 if node:
166 if node:
150 expander['r'] = (lambda:
167 expander['r'] = (lambda:
151 str(repo.changelog.rev(node)).zfill(revwidth or 0))
168 str(repo.changelog.rev(node)).zfill(revwidth or 0))
152 if total is not None:
169 if total is not None:
153 expander['N'] = lambda: str(total)
170 expander['N'] = lambda: str(total)
154 if seqno is not None:
171 if seqno is not None:
155 expander['n'] = lambda: str(seqno)
172 expander['n'] = lambda: str(seqno)
156 if total is not None and seqno is not None:
173 if total is not None and seqno is not None:
157 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
174 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
158 if pathname is not None:
175 if pathname is not None:
159 expander['s'] = lambda: os.path.basename(pathname)
176 expander['s'] = lambda: os.path.basename(pathname)
160 expander['d'] = lambda: os.path.dirname(pathname) or '.'
177 expander['d'] = lambda: os.path.dirname(pathname) or '.'
161 expander['p'] = lambda: pathname
178 expander['p'] = lambda: pathname
162
179
163 newname = []
180 newname = []
164 patlen = len(pat)
181 patlen = len(pat)
165 i = 0
182 i = 0
166 while i < patlen:
183 while i < patlen:
167 c = pat[i]
184 c = pat[i]
168 if c == '%':
185 if c == '%':
169 i += 1
186 i += 1
170 c = pat[i]
187 c = pat[i]
171 c = expander[c]()
188 c = expander[c]()
172 newname.append(c)
189 newname.append(c)
173 i += 1
190 i += 1
174 return ''.join(newname)
191 return ''.join(newname)
175 except KeyError, inst:
192 except KeyError, inst:
176 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
193 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
177 inst.args[0])
194 inst.args[0])
178
195
179 def makefileobj(repo, pat, node=None, desc=None, total=None,
196 def makefileobj(repo, pat, node=None, desc=None, total=None,
180 seqno=None, revwidth=None, mode='wb', modemap=None,
197 seqno=None, revwidth=None, mode='wb', modemap=None,
181 pathname=None):
198 pathname=None):
182
199
183 writable = mode not in ('r', 'rb')
200 writable = mode not in ('r', 'rb')
184
201
185 if not pat or pat == '-':
202 if not pat or pat == '-':
186 fp = writable and repo.ui.fout or repo.ui.fin
203 fp = writable and repo.ui.fout or repo.ui.fin
187 if util.safehasattr(fp, 'fileno'):
204 if util.safehasattr(fp, 'fileno'):
188 return os.fdopen(os.dup(fp.fileno()), mode)
205 return os.fdopen(os.dup(fp.fileno()), mode)
189 else:
206 else:
190 # if this fp can't be duped properly, return
207 # if this fp can't be duped properly, return
191 # a dummy object that can be closed
208 # a dummy object that can be closed
192 class wrappedfileobj(object):
209 class wrappedfileobj(object):
193 noop = lambda x: None
210 noop = lambda x: None
194 def __init__(self, f):
211 def __init__(self, f):
195 self.f = f
212 self.f = f
196 def __getattr__(self, attr):
213 def __getattr__(self, attr):
197 if attr == 'close':
214 if attr == 'close':
198 return self.noop
215 return self.noop
199 else:
216 else:
200 return getattr(self.f, attr)
217 return getattr(self.f, attr)
201
218
202 return wrappedfileobj(fp)
219 return wrappedfileobj(fp)
203 if util.safehasattr(pat, 'write') and writable:
220 if util.safehasattr(pat, 'write') and writable:
204 return pat
221 return pat
205 if util.safehasattr(pat, 'read') and 'r' in mode:
222 if util.safehasattr(pat, 'read') and 'r' in mode:
206 return pat
223 return pat
207 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
224 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
208 if modemap is not None:
225 if modemap is not None:
209 mode = modemap.get(fn, mode)
226 mode = modemap.get(fn, mode)
210 if mode == 'wb':
227 if mode == 'wb':
211 modemap[fn] = 'ab'
228 modemap[fn] = 'ab'
212 return open(fn, mode)
229 return open(fn, mode)
213
230
214 def openrevlog(repo, cmd, file_, opts):
231 def openrevlog(repo, cmd, file_, opts):
215 """opens the changelog, manifest, a filelog or a given revlog"""
232 """opens the changelog, manifest, a filelog or a given revlog"""
216 cl = opts['changelog']
233 cl = opts['changelog']
217 mf = opts['manifest']
234 mf = opts['manifest']
218 msg = None
235 msg = None
219 if cl and mf:
236 if cl and mf:
220 msg = _('cannot specify --changelog and --manifest at the same time')
237 msg = _('cannot specify --changelog and --manifest at the same time')
221 elif cl or mf:
238 elif cl or mf:
222 if file_:
239 if file_:
223 msg = _('cannot specify filename with --changelog or --manifest')
240 msg = _('cannot specify filename with --changelog or --manifest')
224 elif not repo:
241 elif not repo:
225 msg = _('cannot specify --changelog or --manifest '
242 msg = _('cannot specify --changelog or --manifest '
226 'without a repository')
243 'without a repository')
227 if msg:
244 if msg:
228 raise util.Abort(msg)
245 raise util.Abort(msg)
229
246
230 r = None
247 r = None
231 if repo:
248 if repo:
232 if cl:
249 if cl:
233 r = repo.unfiltered().changelog
250 r = repo.unfiltered().changelog
234 elif mf:
251 elif mf:
235 r = repo.manifest
252 r = repo.manifest
236 elif file_:
253 elif file_:
237 filelog = repo.file(file_)
254 filelog = repo.file(file_)
238 if len(filelog):
255 if len(filelog):
239 r = filelog
256 r = filelog
240 if not r:
257 if not r:
241 if not file_:
258 if not file_:
242 raise error.CommandError(cmd, _('invalid arguments'))
259 raise error.CommandError(cmd, _('invalid arguments'))
243 if not os.path.isfile(file_):
260 if not os.path.isfile(file_):
244 raise util.Abort(_("revlog '%s' not found") % file_)
261 raise util.Abort(_("revlog '%s' not found") % file_)
245 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
262 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
246 file_[:-2] + ".i")
263 file_[:-2] + ".i")
247 return r
264 return r
248
265
249 def copy(ui, repo, pats, opts, rename=False):
266 def copy(ui, repo, pats, opts, rename=False):
250 # called with the repo lock held
267 # called with the repo lock held
251 #
268 #
252 # hgsep => pathname that uses "/" to separate directories
269 # hgsep => pathname that uses "/" to separate directories
253 # ossep => pathname that uses os.sep to separate directories
270 # ossep => pathname that uses os.sep to separate directories
254 cwd = repo.getcwd()
271 cwd = repo.getcwd()
255 targets = {}
272 targets = {}
256 after = opts.get("after")
273 after = opts.get("after")
257 dryrun = opts.get("dry_run")
274 dryrun = opts.get("dry_run")
258 wctx = repo[None]
275 wctx = repo[None]
259
276
260 def walkpat(pat):
277 def walkpat(pat):
261 srcs = []
278 srcs = []
262 badstates = after and '?' or '?r'
279 badstates = after and '?' or '?r'
263 m = scmutil.match(repo[None], [pat], opts, globbed=True)
280 m = scmutil.match(repo[None], [pat], opts, globbed=True)
264 for abs in repo.walk(m):
281 for abs in repo.walk(m):
265 state = repo.dirstate[abs]
282 state = repo.dirstate[abs]
266 rel = m.rel(abs)
283 rel = m.rel(abs)
267 exact = m.exact(abs)
284 exact = m.exact(abs)
268 if state in badstates:
285 if state in badstates:
269 if exact and state == '?':
286 if exact and state == '?':
270 ui.warn(_('%s: not copying - file is not managed\n') % rel)
287 ui.warn(_('%s: not copying - file is not managed\n') % rel)
271 if exact and state == 'r':
288 if exact and state == 'r':
272 ui.warn(_('%s: not copying - file has been marked for'
289 ui.warn(_('%s: not copying - file has been marked for'
273 ' remove\n') % rel)
290 ' remove\n') % rel)
274 continue
291 continue
275 # abs: hgsep
292 # abs: hgsep
276 # rel: ossep
293 # rel: ossep
277 srcs.append((abs, rel, exact))
294 srcs.append((abs, rel, exact))
278 return srcs
295 return srcs
279
296
280 # abssrc: hgsep
297 # abssrc: hgsep
281 # relsrc: ossep
298 # relsrc: ossep
282 # otarget: ossep
299 # otarget: ossep
283 def copyfile(abssrc, relsrc, otarget, exact):
300 def copyfile(abssrc, relsrc, otarget, exact):
284 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
301 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
285 if '/' in abstarget:
302 if '/' in abstarget:
286 # We cannot normalize abstarget itself, this would prevent
303 # We cannot normalize abstarget itself, this would prevent
287 # case only renames, like a => A.
304 # case only renames, like a => A.
288 abspath, absname = abstarget.rsplit('/', 1)
305 abspath, absname = abstarget.rsplit('/', 1)
289 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
306 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
290 reltarget = repo.pathto(abstarget, cwd)
307 reltarget = repo.pathto(abstarget, cwd)
291 target = repo.wjoin(abstarget)
308 target = repo.wjoin(abstarget)
292 src = repo.wjoin(abssrc)
309 src = repo.wjoin(abssrc)
293 state = repo.dirstate[abstarget]
310 state = repo.dirstate[abstarget]
294
311
295 scmutil.checkportable(ui, abstarget)
312 scmutil.checkportable(ui, abstarget)
296
313
297 # check for collisions
314 # check for collisions
298 prevsrc = targets.get(abstarget)
315 prevsrc = targets.get(abstarget)
299 if prevsrc is not None:
316 if prevsrc is not None:
300 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
317 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
301 (reltarget, repo.pathto(abssrc, cwd),
318 (reltarget, repo.pathto(abssrc, cwd),
302 repo.pathto(prevsrc, cwd)))
319 repo.pathto(prevsrc, cwd)))
303 return
320 return
304
321
305 # check for overwrites
322 # check for overwrites
306 exists = os.path.lexists(target)
323 exists = os.path.lexists(target)
307 samefile = False
324 samefile = False
308 if exists and abssrc != abstarget:
325 if exists and abssrc != abstarget:
309 if (repo.dirstate.normalize(abssrc) ==
326 if (repo.dirstate.normalize(abssrc) ==
310 repo.dirstate.normalize(abstarget)):
327 repo.dirstate.normalize(abstarget)):
311 if not rename:
328 if not rename:
312 ui.warn(_("%s: can't copy - same file\n") % reltarget)
329 ui.warn(_("%s: can't copy - same file\n") % reltarget)
313 return
330 return
314 exists = False
331 exists = False
315 samefile = True
332 samefile = True
316
333
317 if not after and exists or after and state in 'mn':
334 if not after and exists or after and state in 'mn':
318 if not opts['force']:
335 if not opts['force']:
319 ui.warn(_('%s: not overwriting - file exists\n') %
336 ui.warn(_('%s: not overwriting - file exists\n') %
320 reltarget)
337 reltarget)
321 return
338 return
322
339
323 if after:
340 if after:
324 if not exists:
341 if not exists:
325 if rename:
342 if rename:
326 ui.warn(_('%s: not recording move - %s does not exist\n') %
343 ui.warn(_('%s: not recording move - %s does not exist\n') %
327 (relsrc, reltarget))
344 (relsrc, reltarget))
328 else:
345 else:
329 ui.warn(_('%s: not recording copy - %s does not exist\n') %
346 ui.warn(_('%s: not recording copy - %s does not exist\n') %
330 (relsrc, reltarget))
347 (relsrc, reltarget))
331 return
348 return
332 elif not dryrun:
349 elif not dryrun:
333 try:
350 try:
334 if exists:
351 if exists:
335 os.unlink(target)
352 os.unlink(target)
336 targetdir = os.path.dirname(target) or '.'
353 targetdir = os.path.dirname(target) or '.'
337 if not os.path.isdir(targetdir):
354 if not os.path.isdir(targetdir):
338 os.makedirs(targetdir)
355 os.makedirs(targetdir)
339 if samefile:
356 if samefile:
340 tmp = target + "~hgrename"
357 tmp = target + "~hgrename"
341 os.rename(src, tmp)
358 os.rename(src, tmp)
342 os.rename(tmp, target)
359 os.rename(tmp, target)
343 else:
360 else:
344 util.copyfile(src, target)
361 util.copyfile(src, target)
345 srcexists = True
362 srcexists = True
346 except IOError, inst:
363 except IOError, inst:
347 if inst.errno == errno.ENOENT:
364 if inst.errno == errno.ENOENT:
348 ui.warn(_('%s: deleted in working copy\n') % relsrc)
365 ui.warn(_('%s: deleted in working copy\n') % relsrc)
349 srcexists = False
366 srcexists = False
350 else:
367 else:
351 ui.warn(_('%s: cannot copy - %s\n') %
368 ui.warn(_('%s: cannot copy - %s\n') %
352 (relsrc, inst.strerror))
369 (relsrc, inst.strerror))
353 return True # report a failure
370 return True # report a failure
354
371
355 if ui.verbose or not exact:
372 if ui.verbose or not exact:
356 if rename:
373 if rename:
357 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
374 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
358 else:
375 else:
359 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
376 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
360
377
361 targets[abstarget] = abssrc
378 targets[abstarget] = abssrc
362
379
363 # fix up dirstate
380 # fix up dirstate
364 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
381 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
365 dryrun=dryrun, cwd=cwd)
382 dryrun=dryrun, cwd=cwd)
366 if rename and not dryrun:
383 if rename and not dryrun:
367 if not after and srcexists and not samefile:
384 if not after and srcexists and not samefile:
368 util.unlinkpath(repo.wjoin(abssrc))
385 util.unlinkpath(repo.wjoin(abssrc))
369 wctx.forget([abssrc])
386 wctx.forget([abssrc])
370
387
371 # pat: ossep
388 # pat: ossep
372 # dest ossep
389 # dest ossep
373 # srcs: list of (hgsep, hgsep, ossep, bool)
390 # srcs: list of (hgsep, hgsep, ossep, bool)
374 # return: function that takes hgsep and returns ossep
391 # return: function that takes hgsep and returns ossep
375 def targetpathfn(pat, dest, srcs):
392 def targetpathfn(pat, dest, srcs):
376 if os.path.isdir(pat):
393 if os.path.isdir(pat):
377 abspfx = pathutil.canonpath(repo.root, cwd, pat)
394 abspfx = pathutil.canonpath(repo.root, cwd, pat)
378 abspfx = util.localpath(abspfx)
395 abspfx = util.localpath(abspfx)
379 if destdirexists:
396 if destdirexists:
380 striplen = len(os.path.split(abspfx)[0])
397 striplen = len(os.path.split(abspfx)[0])
381 else:
398 else:
382 striplen = len(abspfx)
399 striplen = len(abspfx)
383 if striplen:
400 if striplen:
384 striplen += len(os.sep)
401 striplen += len(os.sep)
385 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
402 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
386 elif destdirexists:
403 elif destdirexists:
387 res = lambda p: os.path.join(dest,
404 res = lambda p: os.path.join(dest,
388 os.path.basename(util.localpath(p)))
405 os.path.basename(util.localpath(p)))
389 else:
406 else:
390 res = lambda p: dest
407 res = lambda p: dest
391 return res
408 return res
392
409
393 # pat: ossep
410 # pat: ossep
394 # dest ossep
411 # dest ossep
395 # srcs: list of (hgsep, hgsep, ossep, bool)
412 # srcs: list of (hgsep, hgsep, ossep, bool)
396 # return: function that takes hgsep and returns ossep
413 # return: function that takes hgsep and returns ossep
397 def targetpathafterfn(pat, dest, srcs):
414 def targetpathafterfn(pat, dest, srcs):
398 if matchmod.patkind(pat):
415 if matchmod.patkind(pat):
399 # a mercurial pattern
416 # a mercurial pattern
400 res = lambda p: os.path.join(dest,
417 res = lambda p: os.path.join(dest,
401 os.path.basename(util.localpath(p)))
418 os.path.basename(util.localpath(p)))
402 else:
419 else:
403 abspfx = pathutil.canonpath(repo.root, cwd, pat)
420 abspfx = pathutil.canonpath(repo.root, cwd, pat)
404 if len(abspfx) < len(srcs[0][0]):
421 if len(abspfx) < len(srcs[0][0]):
405 # A directory. Either the target path contains the last
422 # A directory. Either the target path contains the last
406 # component of the source path or it does not.
423 # component of the source path or it does not.
407 def evalpath(striplen):
424 def evalpath(striplen):
408 score = 0
425 score = 0
409 for s in srcs:
426 for s in srcs:
410 t = os.path.join(dest, util.localpath(s[0])[striplen:])
427 t = os.path.join(dest, util.localpath(s[0])[striplen:])
411 if os.path.lexists(t):
428 if os.path.lexists(t):
412 score += 1
429 score += 1
413 return score
430 return score
414
431
415 abspfx = util.localpath(abspfx)
432 abspfx = util.localpath(abspfx)
416 striplen = len(abspfx)
433 striplen = len(abspfx)
417 if striplen:
434 if striplen:
418 striplen += len(os.sep)
435 striplen += len(os.sep)
419 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
436 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
420 score = evalpath(striplen)
437 score = evalpath(striplen)
421 striplen1 = len(os.path.split(abspfx)[0])
438 striplen1 = len(os.path.split(abspfx)[0])
422 if striplen1:
439 if striplen1:
423 striplen1 += len(os.sep)
440 striplen1 += len(os.sep)
424 if evalpath(striplen1) > score:
441 if evalpath(striplen1) > score:
425 striplen = striplen1
442 striplen = striplen1
426 res = lambda p: os.path.join(dest,
443 res = lambda p: os.path.join(dest,
427 util.localpath(p)[striplen:])
444 util.localpath(p)[striplen:])
428 else:
445 else:
429 # a file
446 # a file
430 if destdirexists:
447 if destdirexists:
431 res = lambda p: os.path.join(dest,
448 res = lambda p: os.path.join(dest,
432 os.path.basename(util.localpath(p)))
449 os.path.basename(util.localpath(p)))
433 else:
450 else:
434 res = lambda p: dest
451 res = lambda p: dest
435 return res
452 return res
436
453
437
454
438 pats = scmutil.expandpats(pats)
455 pats = scmutil.expandpats(pats)
439 if not pats:
456 if not pats:
440 raise util.Abort(_('no source or destination specified'))
457 raise util.Abort(_('no source or destination specified'))
441 if len(pats) == 1:
458 if len(pats) == 1:
442 raise util.Abort(_('no destination specified'))
459 raise util.Abort(_('no destination specified'))
443 dest = pats.pop()
460 dest = pats.pop()
444 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
461 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
445 if not destdirexists:
462 if not destdirexists:
446 if len(pats) > 1 or matchmod.patkind(pats[0]):
463 if len(pats) > 1 or matchmod.patkind(pats[0]):
447 raise util.Abort(_('with multiple sources, destination must be an '
464 raise util.Abort(_('with multiple sources, destination must be an '
448 'existing directory'))
465 'existing directory'))
449 if util.endswithsep(dest):
466 if util.endswithsep(dest):
450 raise util.Abort(_('destination %s is not a directory') % dest)
467 raise util.Abort(_('destination %s is not a directory') % dest)
451
468
452 tfn = targetpathfn
469 tfn = targetpathfn
453 if after:
470 if after:
454 tfn = targetpathafterfn
471 tfn = targetpathafterfn
455 copylist = []
472 copylist = []
456 for pat in pats:
473 for pat in pats:
457 srcs = walkpat(pat)
474 srcs = walkpat(pat)
458 if not srcs:
475 if not srcs:
459 continue
476 continue
460 copylist.append((tfn(pat, dest, srcs), srcs))
477 copylist.append((tfn(pat, dest, srcs), srcs))
461 if not copylist:
478 if not copylist:
462 raise util.Abort(_('no files to copy'))
479 raise util.Abort(_('no files to copy'))
463
480
464 errors = 0
481 errors = 0
465 for targetpath, srcs in copylist:
482 for targetpath, srcs in copylist:
466 for abssrc, relsrc, exact in srcs:
483 for abssrc, relsrc, exact in srcs:
467 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
484 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
468 errors += 1
485 errors += 1
469
486
470 if errors:
487 if errors:
471 ui.warn(_('(consider using --after)\n'))
488 ui.warn(_('(consider using --after)\n'))
472
489
473 return errors != 0
490 return errors != 0
474
491
475 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
492 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
476 runargs=None, appendpid=False):
493 runargs=None, appendpid=False):
477 '''Run a command as a service.'''
494 '''Run a command as a service.'''
478
495
479 def writepid(pid):
496 def writepid(pid):
480 if opts['pid_file']:
497 if opts['pid_file']:
481 mode = appendpid and 'a' or 'w'
498 mode = appendpid and 'a' or 'w'
482 fp = open(opts['pid_file'], mode)
499 fp = open(opts['pid_file'], mode)
483 fp.write(str(pid) + '\n')
500 fp.write(str(pid) + '\n')
484 fp.close()
501 fp.close()
485
502
486 if opts['daemon'] and not opts['daemon_pipefds']:
503 if opts['daemon'] and not opts['daemon_pipefds']:
487 # Signal child process startup with file removal
504 # Signal child process startup with file removal
488 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
505 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
489 os.close(lockfd)
506 os.close(lockfd)
490 try:
507 try:
491 if not runargs:
508 if not runargs:
492 runargs = util.hgcmd() + sys.argv[1:]
509 runargs = util.hgcmd() + sys.argv[1:]
493 runargs.append('--daemon-pipefds=%s' % lockpath)
510 runargs.append('--daemon-pipefds=%s' % lockpath)
494 # Don't pass --cwd to the child process, because we've already
511 # Don't pass --cwd to the child process, because we've already
495 # changed directory.
512 # changed directory.
496 for i in xrange(1, len(runargs)):
513 for i in xrange(1, len(runargs)):
497 if runargs[i].startswith('--cwd='):
514 if runargs[i].startswith('--cwd='):
498 del runargs[i]
515 del runargs[i]
499 break
516 break
500 elif runargs[i].startswith('--cwd'):
517 elif runargs[i].startswith('--cwd'):
501 del runargs[i:i + 2]
518 del runargs[i:i + 2]
502 break
519 break
503 def condfn():
520 def condfn():
504 return not os.path.exists(lockpath)
521 return not os.path.exists(lockpath)
505 pid = util.rundetached(runargs, condfn)
522 pid = util.rundetached(runargs, condfn)
506 if pid < 0:
523 if pid < 0:
507 raise util.Abort(_('child process failed to start'))
524 raise util.Abort(_('child process failed to start'))
508 writepid(pid)
525 writepid(pid)
509 finally:
526 finally:
510 try:
527 try:
511 os.unlink(lockpath)
528 os.unlink(lockpath)
512 except OSError, e:
529 except OSError, e:
513 if e.errno != errno.ENOENT:
530 if e.errno != errno.ENOENT:
514 raise
531 raise
515 if parentfn:
532 if parentfn:
516 return parentfn(pid)
533 return parentfn(pid)
517 else:
534 else:
518 return
535 return
519
536
520 if initfn:
537 if initfn:
521 initfn()
538 initfn()
522
539
523 if not opts['daemon']:
540 if not opts['daemon']:
524 writepid(os.getpid())
541 writepid(os.getpid())
525
542
526 if opts['daemon_pipefds']:
543 if opts['daemon_pipefds']:
527 lockpath = opts['daemon_pipefds']
544 lockpath = opts['daemon_pipefds']
528 try:
545 try:
529 os.setsid()
546 os.setsid()
530 except AttributeError:
547 except AttributeError:
531 pass
548 pass
532 os.unlink(lockpath)
549 os.unlink(lockpath)
533 util.hidewindow()
550 util.hidewindow()
534 sys.stdout.flush()
551 sys.stdout.flush()
535 sys.stderr.flush()
552 sys.stderr.flush()
536
553
537 nullfd = os.open(os.devnull, os.O_RDWR)
554 nullfd = os.open(os.devnull, os.O_RDWR)
538 logfilefd = nullfd
555 logfilefd = nullfd
539 if logfile:
556 if logfile:
540 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
557 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
541 os.dup2(nullfd, 0)
558 os.dup2(nullfd, 0)
542 os.dup2(logfilefd, 1)
559 os.dup2(logfilefd, 1)
543 os.dup2(logfilefd, 2)
560 os.dup2(logfilefd, 2)
544 if nullfd not in (0, 1, 2):
561 if nullfd not in (0, 1, 2):
545 os.close(nullfd)
562 os.close(nullfd)
546 if logfile and logfilefd not in (0, 1, 2):
563 if logfile and logfilefd not in (0, 1, 2):
547 os.close(logfilefd)
564 os.close(logfilefd)
548
565
549 if runfn:
566 if runfn:
550 return runfn()
567 return runfn()
551
568
552 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
569 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
553 """Utility function used by commands.import to import a single patch
570 """Utility function used by commands.import to import a single patch
554
571
555 This function is explicitly defined here to help the evolve extension to
572 This function is explicitly defined here to help the evolve extension to
556 wrap this part of the import logic.
573 wrap this part of the import logic.
557
574
558 The API is currently a bit ugly because it a simple code translation from
575 The API is currently a bit ugly because it a simple code translation from
559 the import command. Feel free to make it better.
576 the import command. Feel free to make it better.
560
577
561 :hunk: a patch (as a binary string)
578 :hunk: a patch (as a binary string)
562 :parents: nodes that will be parent of the created commit
579 :parents: nodes that will be parent of the created commit
563 :opts: the full dict of option passed to the import command
580 :opts: the full dict of option passed to the import command
564 :msgs: list to save commit message to.
581 :msgs: list to save commit message to.
565 (used in case we need to save it when failing)
582 (used in case we need to save it when failing)
566 :updatefunc: a function that update a repo to a given node
583 :updatefunc: a function that update a repo to a given node
567 updatefunc(<repo>, <node>)
584 updatefunc(<repo>, <node>)
568 """
585 """
569 tmpname, message, user, date, branch, nodeid, p1, p2 = \
586 tmpname, message, user, date, branch, nodeid, p1, p2 = \
570 patch.extract(ui, hunk)
587 patch.extract(ui, hunk)
571
588
572 editor = getcommiteditor(**opts)
589 editor = getcommiteditor(**opts)
573 update = not opts.get('bypass')
590 update = not opts.get('bypass')
574 strip = opts["strip"]
591 strip = opts["strip"]
575 sim = float(opts.get('similarity') or 0)
592 sim = float(opts.get('similarity') or 0)
576 if not tmpname:
593 if not tmpname:
577 return (None, None)
594 return (None, None)
578 msg = _('applied to working directory')
595 msg = _('applied to working directory')
579
596
580 try:
597 try:
581 cmdline_message = logmessage(ui, opts)
598 cmdline_message = logmessage(ui, opts)
582 if cmdline_message:
599 if cmdline_message:
583 # pickup the cmdline msg
600 # pickup the cmdline msg
584 message = cmdline_message
601 message = cmdline_message
585 elif message:
602 elif message:
586 # pickup the patch msg
603 # pickup the patch msg
587 message = message.strip()
604 message = message.strip()
588 else:
605 else:
589 # launch the editor
606 # launch the editor
590 message = None
607 message = None
591 ui.debug('message:\n%s\n' % message)
608 ui.debug('message:\n%s\n' % message)
592
609
593 if len(parents) == 1:
610 if len(parents) == 1:
594 parents.append(repo[nullid])
611 parents.append(repo[nullid])
595 if opts.get('exact'):
612 if opts.get('exact'):
596 if not nodeid or not p1:
613 if not nodeid or not p1:
597 raise util.Abort(_('not a Mercurial patch'))
614 raise util.Abort(_('not a Mercurial patch'))
598 p1 = repo[p1]
615 p1 = repo[p1]
599 p2 = repo[p2 or nullid]
616 p2 = repo[p2 or nullid]
600 elif p2:
617 elif p2:
601 try:
618 try:
602 p1 = repo[p1]
619 p1 = repo[p1]
603 p2 = repo[p2]
620 p2 = repo[p2]
604 # Without any options, consider p2 only if the
621 # Without any options, consider p2 only if the
605 # patch is being applied on top of the recorded
622 # patch is being applied on top of the recorded
606 # first parent.
623 # first parent.
607 if p1 != parents[0]:
624 if p1 != parents[0]:
608 p1 = parents[0]
625 p1 = parents[0]
609 p2 = repo[nullid]
626 p2 = repo[nullid]
610 except error.RepoError:
627 except error.RepoError:
611 p1, p2 = parents
628 p1, p2 = parents
612 else:
629 else:
613 p1, p2 = parents
630 p1, p2 = parents
614
631
615 n = None
632 n = None
616 if update:
633 if update:
617 if p1 != parents[0]:
634 if p1 != parents[0]:
618 updatefunc(repo, p1.node())
635 updatefunc(repo, p1.node())
619 if p2 != parents[1]:
636 if p2 != parents[1]:
620 repo.setparents(p1.node(), p2.node())
637 repo.setparents(p1.node(), p2.node())
621
638
622 if opts.get('exact') or opts.get('import_branch'):
639 if opts.get('exact') or opts.get('import_branch'):
623 repo.dirstate.setbranch(branch or 'default')
640 repo.dirstate.setbranch(branch or 'default')
624
641
625 files = set()
642 files = set()
626 patch.patch(ui, repo, tmpname, strip=strip, files=files,
643 patch.patch(ui, repo, tmpname, strip=strip, files=files,
627 eolmode=None, similarity=sim / 100.0)
644 eolmode=None, similarity=sim / 100.0)
628 files = list(files)
645 files = list(files)
629 if opts.get('no_commit'):
646 if opts.get('no_commit'):
630 if message:
647 if message:
631 msgs.append(message)
648 msgs.append(message)
632 else:
649 else:
633 if opts.get('exact') or p2:
650 if opts.get('exact') or p2:
634 # If you got here, you either use --force and know what
651 # If you got here, you either use --force and know what
635 # you are doing or used --exact or a merge patch while
652 # you are doing or used --exact or a merge patch while
636 # being updated to its first parent.
653 # being updated to its first parent.
637 m = None
654 m = None
638 else:
655 else:
639 m = scmutil.matchfiles(repo, files or [])
656 m = scmutil.matchfiles(repo, files or [])
640 n = repo.commit(message, opts.get('user') or user,
657 n = repo.commit(message, opts.get('user') or user,
641 opts.get('date') or date, match=m,
658 opts.get('date') or date, match=m,
642 editor=editor)
659 editor=editor)
643 else:
660 else:
644 if opts.get('exact') or opts.get('import_branch'):
661 if opts.get('exact') or opts.get('import_branch'):
645 branch = branch or 'default'
662 branch = branch or 'default'
646 else:
663 else:
647 branch = p1.branch()
664 branch = p1.branch()
648 store = patch.filestore()
665 store = patch.filestore()
649 try:
666 try:
650 files = set()
667 files = set()
651 try:
668 try:
652 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
669 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
653 files, eolmode=None)
670 files, eolmode=None)
654 except patch.PatchError, e:
671 except patch.PatchError, e:
655 raise util.Abort(str(e))
672 raise util.Abort(str(e))
656 memctx = context.makememctx(repo, (p1.node(), p2.node()),
673 memctx = context.makememctx(repo, (p1.node(), p2.node()),
657 message,
674 message,
658 opts.get('user') or user,
675 opts.get('user') or user,
659 opts.get('date') or date,
676 opts.get('date') or date,
660 branch, files, store,
677 branch, files, store,
661 editor=getcommiteditor())
678 editor=getcommiteditor())
662 n = memctx.commit()
679 n = memctx.commit()
663 finally:
680 finally:
664 store.close()
681 store.close()
665 if opts.get('exact') and hex(n) != nodeid:
682 if opts.get('exact') and hex(n) != nodeid:
666 raise util.Abort(_('patch is damaged or loses information'))
683 raise util.Abort(_('patch is damaged or loses information'))
667 if n:
684 if n:
668 # i18n: refers to a short changeset id
685 # i18n: refers to a short changeset id
669 msg = _('created %s') % short(n)
686 msg = _('created %s') % short(n)
670 return (msg, n)
687 return (msg, n)
671 finally:
688 finally:
672 os.unlink(tmpname)
689 os.unlink(tmpname)
673
690
674 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
691 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
675 opts=None):
692 opts=None):
676 '''export changesets as hg patches.'''
693 '''export changesets as hg patches.'''
677
694
678 total = len(revs)
695 total = len(revs)
679 revwidth = max([len(str(rev)) for rev in revs])
696 revwidth = max([len(str(rev)) for rev in revs])
680 filemode = {}
697 filemode = {}
681
698
682 def single(rev, seqno, fp):
699 def single(rev, seqno, fp):
683 ctx = repo[rev]
700 ctx = repo[rev]
684 node = ctx.node()
701 node = ctx.node()
685 parents = [p.node() for p in ctx.parents() if p]
702 parents = [p.node() for p in ctx.parents() if p]
686 branch = ctx.branch()
703 branch = ctx.branch()
687 if switch_parent:
704 if switch_parent:
688 parents.reverse()
705 parents.reverse()
689 prev = (parents and parents[0]) or nullid
706 prev = (parents and parents[0]) or nullid
690
707
691 shouldclose = False
708 shouldclose = False
692 if not fp and len(template) > 0:
709 if not fp and len(template) > 0:
693 desc_lines = ctx.description().rstrip().split('\n')
710 desc_lines = ctx.description().rstrip().split('\n')
694 desc = desc_lines[0] #Commit always has a first line.
711 desc = desc_lines[0] #Commit always has a first line.
695 fp = makefileobj(repo, template, node, desc=desc, total=total,
712 fp = makefileobj(repo, template, node, desc=desc, total=total,
696 seqno=seqno, revwidth=revwidth, mode='wb',
713 seqno=seqno, revwidth=revwidth, mode='wb',
697 modemap=filemode)
714 modemap=filemode)
698 if fp != template:
715 if fp != template:
699 shouldclose = True
716 shouldclose = True
700 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
717 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
701 repo.ui.note("%s\n" % fp.name)
718 repo.ui.note("%s\n" % fp.name)
702
719
703 if not fp:
720 if not fp:
704 write = repo.ui.write
721 write = repo.ui.write
705 else:
722 else:
706 def write(s, **kw):
723 def write(s, **kw):
707 fp.write(s)
724 fp.write(s)
708
725
709
726
710 write("# HG changeset patch\n")
727 write("# HG changeset patch\n")
711 write("# User %s\n" % ctx.user())
728 write("# User %s\n" % ctx.user())
712 write("# Date %d %d\n" % ctx.date())
729 write("# Date %d %d\n" % ctx.date())
713 write("# %s\n" % util.datestr(ctx.date()))
730 write("# %s\n" % util.datestr(ctx.date()))
714 if branch and branch != 'default':
731 if branch and branch != 'default':
715 write("# Branch %s\n" % branch)
732 write("# Branch %s\n" % branch)
716 write("# Node ID %s\n" % hex(node))
733 write("# Node ID %s\n" % hex(node))
717 write("# Parent %s\n" % hex(prev))
734 write("# Parent %s\n" % hex(prev))
718 if len(parents) > 1:
735 if len(parents) > 1:
719 write("# Parent %s\n" % hex(parents[1]))
736 write("# Parent %s\n" % hex(parents[1]))
720 write(ctx.description().rstrip())
737 write(ctx.description().rstrip())
721 write("\n\n")
738 write("\n\n")
722
739
723 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
740 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
724 write(chunk, label=label)
741 write(chunk, label=label)
725
742
726 if shouldclose:
743 if shouldclose:
727 fp.close()
744 fp.close()
728
745
729 for seqno, rev in enumerate(revs):
746 for seqno, rev in enumerate(revs):
730 single(rev, seqno + 1, fp)
747 single(rev, seqno + 1, fp)
731
748
732 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
749 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
733 changes=None, stat=False, fp=None, prefix='',
750 changes=None, stat=False, fp=None, prefix='',
734 listsubrepos=False):
751 listsubrepos=False):
735 '''show diff or diffstat.'''
752 '''show diff or diffstat.'''
736 if fp is None:
753 if fp is None:
737 write = ui.write
754 write = ui.write
738 else:
755 else:
739 def write(s, **kw):
756 def write(s, **kw):
740 fp.write(s)
757 fp.write(s)
741
758
742 if stat:
759 if stat:
743 diffopts = diffopts.copy(context=0)
760 diffopts = diffopts.copy(context=0)
744 width = 80
761 width = 80
745 if not ui.plain():
762 if not ui.plain():
746 width = ui.termwidth()
763 width = ui.termwidth()
747 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
764 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
748 prefix=prefix)
765 prefix=prefix)
749 for chunk, label in patch.diffstatui(util.iterlines(chunks),
766 for chunk, label in patch.diffstatui(util.iterlines(chunks),
750 width=width,
767 width=width,
751 git=diffopts.git):
768 git=diffopts.git):
752 write(chunk, label=label)
769 write(chunk, label=label)
753 else:
770 else:
754 for chunk, label in patch.diffui(repo, node1, node2, match,
771 for chunk, label in patch.diffui(repo, node1, node2, match,
755 changes, diffopts, prefix=prefix):
772 changes, diffopts, prefix=prefix):
756 write(chunk, label=label)
773 write(chunk, label=label)
757
774
758 if listsubrepos:
775 if listsubrepos:
759 ctx1 = repo[node1]
776 ctx1 = repo[node1]
760 ctx2 = repo[node2]
777 ctx2 = repo[node2]
761 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
778 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
762 tempnode2 = node2
779 tempnode2 = node2
763 try:
780 try:
764 if node2 is not None:
781 if node2 is not None:
765 tempnode2 = ctx2.substate[subpath][1]
782 tempnode2 = ctx2.substate[subpath][1]
766 except KeyError:
783 except KeyError:
767 # A subrepo that existed in node1 was deleted between node1 and
784 # A subrepo that existed in node1 was deleted between node1 and
768 # node2 (inclusive). Thus, ctx2's substate won't contain that
785 # node2 (inclusive). Thus, ctx2's substate won't contain that
769 # subpath. The best we can do is to ignore it.
786 # subpath. The best we can do is to ignore it.
770 tempnode2 = None
787 tempnode2 = None
771 submatch = matchmod.narrowmatcher(subpath, match)
788 submatch = matchmod.narrowmatcher(subpath, match)
772 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
789 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
773 stat=stat, fp=fp, prefix=prefix)
790 stat=stat, fp=fp, prefix=prefix)
774
791
775 class changeset_printer(object):
792 class changeset_printer(object):
776 '''show changeset information when templating not requested.'''
793 '''show changeset information when templating not requested.'''
777
794
778 def __init__(self, ui, repo, patch, diffopts, buffered):
795 def __init__(self, ui, repo, patch, diffopts, buffered):
779 self.ui = ui
796 self.ui = ui
780 self.repo = repo
797 self.repo = repo
781 self.buffered = buffered
798 self.buffered = buffered
782 self.patch = patch
799 self.patch = patch
783 self.diffopts = diffopts
800 self.diffopts = diffopts
784 self.header = {}
801 self.header = {}
785 self.hunk = {}
802 self.hunk = {}
786 self.lastheader = None
803 self.lastheader = None
787 self.footer = None
804 self.footer = None
788
805
789 def flush(self, rev):
806 def flush(self, rev):
790 if rev in self.header:
807 if rev in self.header:
791 h = self.header[rev]
808 h = self.header[rev]
792 if h != self.lastheader:
809 if h != self.lastheader:
793 self.lastheader = h
810 self.lastheader = h
794 self.ui.write(h)
811 self.ui.write(h)
795 del self.header[rev]
812 del self.header[rev]
796 if rev in self.hunk:
813 if rev in self.hunk:
797 self.ui.write(self.hunk[rev])
814 self.ui.write(self.hunk[rev])
798 del self.hunk[rev]
815 del self.hunk[rev]
799 return 1
816 return 1
800 return 0
817 return 0
801
818
802 def close(self):
819 def close(self):
803 if self.footer:
820 if self.footer:
804 self.ui.write(self.footer)
821 self.ui.write(self.footer)
805
822
806 def show(self, ctx, copies=None, matchfn=None, **props):
823 def show(self, ctx, copies=None, matchfn=None, **props):
807 if self.buffered:
824 if self.buffered:
808 self.ui.pushbuffer()
825 self.ui.pushbuffer()
809 self._show(ctx, copies, matchfn, props)
826 self._show(ctx, copies, matchfn, props)
810 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
827 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
811 else:
828 else:
812 self._show(ctx, copies, matchfn, props)
829 self._show(ctx, copies, matchfn, props)
813
830
814 def _show(self, ctx, copies, matchfn, props):
831 def _show(self, ctx, copies, matchfn, props):
815 '''show a single changeset or file revision'''
832 '''show a single changeset or file revision'''
816 changenode = ctx.node()
833 changenode = ctx.node()
817 rev = ctx.rev()
834 rev = ctx.rev()
818
835
819 if self.ui.quiet:
836 if self.ui.quiet:
820 self.ui.write("%d:%s\n" % (rev, short(changenode)),
837 self.ui.write("%d:%s\n" % (rev, short(changenode)),
821 label='log.node')
838 label='log.node')
822 return
839 return
823
840
824 log = self.repo.changelog
841 log = self.repo.changelog
825 date = util.datestr(ctx.date())
842 date = util.datestr(ctx.date())
826
843
827 hexfunc = self.ui.debugflag and hex or short
844 hexfunc = self.ui.debugflag and hex or short
828
845
829 parents = [(p, hexfunc(log.node(p)))
846 parents = [(p, hexfunc(log.node(p)))
830 for p in self._meaningful_parentrevs(log, rev)]
847 for p in self._meaningful_parentrevs(log, rev)]
831
848
832 # i18n: column positioning for "hg log"
849 # i18n: column positioning for "hg log"
833 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
850 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
834 label='log.changeset changeset.%s' % ctx.phasestr())
851 label='log.changeset changeset.%s' % ctx.phasestr())
835
852
836 branch = ctx.branch()
853 branch = ctx.branch()
837 # don't show the default branch name
854 # don't show the default branch name
838 if branch != 'default':
855 if branch != 'default':
839 # i18n: column positioning for "hg log"
856 # i18n: column positioning for "hg log"
840 self.ui.write(_("branch: %s\n") % branch,
857 self.ui.write(_("branch: %s\n") % branch,
841 label='log.branch')
858 label='log.branch')
842 for bookmark in self.repo.nodebookmarks(changenode):
859 for bookmark in self.repo.nodebookmarks(changenode):
843 # i18n: column positioning for "hg log"
860 # i18n: column positioning for "hg log"
844 self.ui.write(_("bookmark: %s\n") % bookmark,
861 self.ui.write(_("bookmark: %s\n") % bookmark,
845 label='log.bookmark')
862 label='log.bookmark')
846 for tag in self.repo.nodetags(changenode):
863 for tag in self.repo.nodetags(changenode):
847 # i18n: column positioning for "hg log"
864 # i18n: column positioning for "hg log"
848 self.ui.write(_("tag: %s\n") % tag,
865 self.ui.write(_("tag: %s\n") % tag,
849 label='log.tag')
866 label='log.tag')
850 if self.ui.debugflag and ctx.phase():
867 if self.ui.debugflag and ctx.phase():
851 # i18n: column positioning for "hg log"
868 # i18n: column positioning for "hg log"
852 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
869 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
853 label='log.phase')
870 label='log.phase')
854 for parent in parents:
871 for parent in parents:
855 # i18n: column positioning for "hg log"
872 # i18n: column positioning for "hg log"
856 self.ui.write(_("parent: %d:%s\n") % parent,
873 self.ui.write(_("parent: %d:%s\n") % parent,
857 label='log.parent changeset.%s' % ctx.phasestr())
874 label='log.parent changeset.%s' % ctx.phasestr())
858
875
859 if self.ui.debugflag:
876 if self.ui.debugflag:
860 mnode = ctx.manifestnode()
877 mnode = ctx.manifestnode()
861 # i18n: column positioning for "hg log"
878 # i18n: column positioning for "hg log"
862 self.ui.write(_("manifest: %d:%s\n") %
879 self.ui.write(_("manifest: %d:%s\n") %
863 (self.repo.manifest.rev(mnode), hex(mnode)),
880 (self.repo.manifest.rev(mnode), hex(mnode)),
864 label='ui.debug log.manifest')
881 label='ui.debug log.manifest')
865 # i18n: column positioning for "hg log"
882 # i18n: column positioning for "hg log"
866 self.ui.write(_("user: %s\n") % ctx.user(),
883 self.ui.write(_("user: %s\n") % ctx.user(),
867 label='log.user')
884 label='log.user')
868 # i18n: column positioning for "hg log"
885 # i18n: column positioning for "hg log"
869 self.ui.write(_("date: %s\n") % date,
886 self.ui.write(_("date: %s\n") % date,
870 label='log.date')
887 label='log.date')
871
888
872 if self.ui.debugflag:
889 if self.ui.debugflag:
873 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
890 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
874 for key, value in zip([# i18n: column positioning for "hg log"
891 for key, value in zip([# i18n: column positioning for "hg log"
875 _("files:"),
892 _("files:"),
876 # i18n: column positioning for "hg log"
893 # i18n: column positioning for "hg log"
877 _("files+:"),
894 _("files+:"),
878 # i18n: column positioning for "hg log"
895 # i18n: column positioning for "hg log"
879 _("files-:")], files):
896 _("files-:")], files):
880 if value:
897 if value:
881 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
898 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
882 label='ui.debug log.files')
899 label='ui.debug log.files')
883 elif ctx.files() and self.ui.verbose:
900 elif ctx.files() and self.ui.verbose:
884 # i18n: column positioning for "hg log"
901 # i18n: column positioning for "hg log"
885 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
902 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
886 label='ui.note log.files')
903 label='ui.note log.files')
887 if copies and self.ui.verbose:
904 if copies and self.ui.verbose:
888 copies = ['%s (%s)' % c for c in copies]
905 copies = ['%s (%s)' % c for c in copies]
889 # i18n: column positioning for "hg log"
906 # i18n: column positioning for "hg log"
890 self.ui.write(_("copies: %s\n") % ' '.join(copies),
907 self.ui.write(_("copies: %s\n") % ' '.join(copies),
891 label='ui.note log.copies')
908 label='ui.note log.copies')
892
909
893 extra = ctx.extra()
910 extra = ctx.extra()
894 if extra and self.ui.debugflag:
911 if extra and self.ui.debugflag:
895 for key, value in sorted(extra.items()):
912 for key, value in sorted(extra.items()):
896 # i18n: column positioning for "hg log"
913 # i18n: column positioning for "hg log"
897 self.ui.write(_("extra: %s=%s\n")
914 self.ui.write(_("extra: %s=%s\n")
898 % (key, value.encode('string_escape')),
915 % (key, value.encode('string_escape')),
899 label='ui.debug log.extra')
916 label='ui.debug log.extra')
900
917
901 description = ctx.description().strip()
918 description = ctx.description().strip()
902 if description:
919 if description:
903 if self.ui.verbose:
920 if self.ui.verbose:
904 self.ui.write(_("description:\n"),
921 self.ui.write(_("description:\n"),
905 label='ui.note log.description')
922 label='ui.note log.description')
906 self.ui.write(description,
923 self.ui.write(description,
907 label='ui.note log.description')
924 label='ui.note log.description')
908 self.ui.write("\n\n")
925 self.ui.write("\n\n")
909 else:
926 else:
910 # i18n: column positioning for "hg log"
927 # i18n: column positioning for "hg log"
911 self.ui.write(_("summary: %s\n") %
928 self.ui.write(_("summary: %s\n") %
912 description.splitlines()[0],
929 description.splitlines()[0],
913 label='log.summary')
930 label='log.summary')
914 self.ui.write("\n")
931 self.ui.write("\n")
915
932
916 self.showpatch(changenode, matchfn)
933 self.showpatch(changenode, matchfn)
917
934
918 def showpatch(self, node, matchfn):
935 def showpatch(self, node, matchfn):
919 if not matchfn:
936 if not matchfn:
920 matchfn = self.patch
937 matchfn = self.patch
921 if matchfn:
938 if matchfn:
922 stat = self.diffopts.get('stat')
939 stat = self.diffopts.get('stat')
923 diff = self.diffopts.get('patch')
940 diff = self.diffopts.get('patch')
924 diffopts = patch.diffopts(self.ui, self.diffopts)
941 diffopts = patch.diffopts(self.ui, self.diffopts)
925 prev = self.repo.changelog.parents(node)[0]
942 prev = self.repo.changelog.parents(node)[0]
926 if stat:
943 if stat:
927 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
944 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
928 match=matchfn, stat=True)
945 match=matchfn, stat=True)
929 if diff:
946 if diff:
930 if stat:
947 if stat:
931 self.ui.write("\n")
948 self.ui.write("\n")
932 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
949 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
933 match=matchfn, stat=False)
950 match=matchfn, stat=False)
934 self.ui.write("\n")
951 self.ui.write("\n")
935
952
936 def _meaningful_parentrevs(self, log, rev):
953 def _meaningful_parentrevs(self, log, rev):
937 """Return list of meaningful (or all if debug) parentrevs for rev.
954 """Return list of meaningful (or all if debug) parentrevs for rev.
938
955
939 For merges (two non-nullrev revisions) both parents are meaningful.
956 For merges (two non-nullrev revisions) both parents are meaningful.
940 Otherwise the first parent revision is considered meaningful if it
957 Otherwise the first parent revision is considered meaningful if it
941 is not the preceding revision.
958 is not the preceding revision.
942 """
959 """
943 parents = log.parentrevs(rev)
960 parents = log.parentrevs(rev)
944 if not self.ui.debugflag and parents[1] == nullrev:
961 if not self.ui.debugflag and parents[1] == nullrev:
945 if parents[0] >= rev - 1:
962 if parents[0] >= rev - 1:
946 parents = []
963 parents = []
947 else:
964 else:
948 parents = [parents[0]]
965 parents = [parents[0]]
949 return parents
966 return parents
950
967
951
968
952 class changeset_templater(changeset_printer):
969 class changeset_templater(changeset_printer):
953 '''format changeset information.'''
970 '''format changeset information.'''
954
971
955 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
972 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
956 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
973 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
957 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
974 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
958 defaulttempl = {
975 defaulttempl = {
959 'parent': '{rev}:{node|formatnode} ',
976 'parent': '{rev}:{node|formatnode} ',
960 'manifest': '{rev}:{node|formatnode}',
977 'manifest': '{rev}:{node|formatnode}',
961 'file_copy': '{name} ({source})',
978 'file_copy': '{name} ({source})',
962 'extra': '{key}={value|stringescape}'
979 'extra': '{key}={value|stringescape}'
963 }
980 }
964 # filecopy is preserved for compatibility reasons
981 # filecopy is preserved for compatibility reasons
965 defaulttempl['filecopy'] = defaulttempl['file_copy']
982 defaulttempl['filecopy'] = defaulttempl['file_copy']
966 self.t = templater.templater(mapfile, {'formatnode': formatnode},
983 self.t = templater.templater(mapfile, {'formatnode': formatnode},
967 cache=defaulttempl)
984 cache=defaulttempl)
968 if tmpl:
985 if tmpl:
969 self.t.cache['changeset'] = tmpl
986 self.t.cache['changeset'] = tmpl
970
987
971 self.cache = {}
988 self.cache = {}
972
989
973 def _meaningful_parentrevs(self, ctx):
990 def _meaningful_parentrevs(self, ctx):
974 """Return list of meaningful (or all if debug) parentrevs for rev.
991 """Return list of meaningful (or all if debug) parentrevs for rev.
975 """
992 """
976 parents = ctx.parents()
993 parents = ctx.parents()
977 if len(parents) > 1:
994 if len(parents) > 1:
978 return parents
995 return parents
979 if self.ui.debugflag:
996 if self.ui.debugflag:
980 return [parents[0], self.repo['null']]
997 return [parents[0], self.repo['null']]
981 if parents[0].rev() >= ctx.rev() - 1:
998 if parents[0].rev() >= ctx.rev() - 1:
982 return []
999 return []
983 return parents
1000 return parents
984
1001
985 def _show(self, ctx, copies, matchfn, props):
1002 def _show(self, ctx, copies, matchfn, props):
986 '''show a single changeset or file revision'''
1003 '''show a single changeset or file revision'''
987
1004
988 showlist = templatekw.showlist
1005 showlist = templatekw.showlist
989
1006
990 # showparents() behaviour depends on ui trace level which
1007 # showparents() behaviour depends on ui trace level which
991 # causes unexpected behaviours at templating level and makes
1008 # causes unexpected behaviours at templating level and makes
992 # it harder to extract it in a standalone function. Its
1009 # it harder to extract it in a standalone function. Its
993 # behaviour cannot be changed so leave it here for now.
1010 # behaviour cannot be changed so leave it here for now.
994 def showparents(**args):
1011 def showparents(**args):
995 ctx = args['ctx']
1012 ctx = args['ctx']
996 parents = [[('rev', p.rev()), ('node', p.hex())]
1013 parents = [[('rev', p.rev()), ('node', p.hex())]
997 for p in self._meaningful_parentrevs(ctx)]
1014 for p in self._meaningful_parentrevs(ctx)]
998 return showlist('parent', parents, **args)
1015 return showlist('parent', parents, **args)
999
1016
1000 props = props.copy()
1017 props = props.copy()
1001 props.update(templatekw.keywords)
1018 props.update(templatekw.keywords)
1002 props['parents'] = showparents
1019 props['parents'] = showparents
1003 props['templ'] = self.t
1020 props['templ'] = self.t
1004 props['ctx'] = ctx
1021 props['ctx'] = ctx
1005 props['repo'] = self.repo
1022 props['repo'] = self.repo
1006 props['revcache'] = {'copies': copies}
1023 props['revcache'] = {'copies': copies}
1007 props['cache'] = self.cache
1024 props['cache'] = self.cache
1008
1025
1009 # find correct templates for current mode
1026 # find correct templates for current mode
1010
1027
1011 tmplmodes = [
1028 tmplmodes = [
1012 (True, None),
1029 (True, None),
1013 (self.ui.verbose, 'verbose'),
1030 (self.ui.verbose, 'verbose'),
1014 (self.ui.quiet, 'quiet'),
1031 (self.ui.quiet, 'quiet'),
1015 (self.ui.debugflag, 'debug'),
1032 (self.ui.debugflag, 'debug'),
1016 ]
1033 ]
1017
1034
1018 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1035 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1019 for mode, postfix in tmplmodes:
1036 for mode, postfix in tmplmodes:
1020 for type in types:
1037 for type in types:
1021 cur = postfix and ('%s_%s' % (type, postfix)) or type
1038 cur = postfix and ('%s_%s' % (type, postfix)) or type
1022 if mode and cur in self.t:
1039 if mode and cur in self.t:
1023 types[type] = cur
1040 types[type] = cur
1024
1041
1025 try:
1042 try:
1026
1043
1027 # write header
1044 # write header
1028 if types['header']:
1045 if types['header']:
1029 h = templater.stringify(self.t(types['header'], **props))
1046 h = templater.stringify(self.t(types['header'], **props))
1030 if self.buffered:
1047 if self.buffered:
1031 self.header[ctx.rev()] = h
1048 self.header[ctx.rev()] = h
1032 else:
1049 else:
1033 if self.lastheader != h:
1050 if self.lastheader != h:
1034 self.lastheader = h
1051 self.lastheader = h
1035 self.ui.write(h)
1052 self.ui.write(h)
1036
1053
1037 # write changeset metadata, then patch if requested
1054 # write changeset metadata, then patch if requested
1038 key = types['changeset']
1055 key = types['changeset']
1039 self.ui.write(templater.stringify(self.t(key, **props)))
1056 self.ui.write(templater.stringify(self.t(key, **props)))
1040 self.showpatch(ctx.node(), matchfn)
1057 self.showpatch(ctx.node(), matchfn)
1041
1058
1042 if types['footer']:
1059 if types['footer']:
1043 if not self.footer:
1060 if not self.footer:
1044 self.footer = templater.stringify(self.t(types['footer'],
1061 self.footer = templater.stringify(self.t(types['footer'],
1045 **props))
1062 **props))
1046
1063
1047 except KeyError, inst:
1064 except KeyError, inst:
1048 msg = _("%s: no key named '%s'")
1065 msg = _("%s: no key named '%s'")
1049 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1066 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1050 except SyntaxError, inst:
1067 except SyntaxError, inst:
1051 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1068 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1052
1069
1053 def gettemplate(ui, tmpl, style):
1070 def gettemplate(ui, tmpl, style):
1054 """
1071 """
1055 Find the template matching the given template spec or style.
1072 Find the template matching the given template spec or style.
1056 """
1073 """
1057
1074
1058 # ui settings
1075 # ui settings
1059 if not tmpl and not style:
1076 if not tmpl and not style:
1060 tmpl = ui.config('ui', 'logtemplate')
1077 tmpl = ui.config('ui', 'logtemplate')
1061 if tmpl:
1078 if tmpl:
1062 try:
1079 try:
1063 tmpl = templater.parsestring(tmpl)
1080 tmpl = templater.parsestring(tmpl)
1064 except SyntaxError:
1081 except SyntaxError:
1065 tmpl = templater.parsestring(tmpl, quoted=False)
1082 tmpl = templater.parsestring(tmpl, quoted=False)
1066 return tmpl, None
1083 return tmpl, None
1067 else:
1084 else:
1068 style = util.expandpath(ui.config('ui', 'style', ''))
1085 style = util.expandpath(ui.config('ui', 'style', ''))
1069
1086
1070 if style:
1087 if style:
1071 mapfile = style
1088 mapfile = style
1072 if not os.path.split(mapfile)[0]:
1089 if not os.path.split(mapfile)[0]:
1073 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1090 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1074 or templater.templatepath(mapfile))
1091 or templater.templatepath(mapfile))
1075 if mapname:
1092 if mapname:
1076 mapfile = mapname
1093 mapfile = mapname
1077 return None, mapfile
1094 return None, mapfile
1078
1095
1079 if not tmpl:
1096 if not tmpl:
1080 return None, None
1097 return None, None
1081
1098
1082 # looks like a literal template?
1099 # looks like a literal template?
1083 if '{' in tmpl:
1100 if '{' in tmpl:
1084 return tmpl, None
1101 return tmpl, None
1085
1102
1086 # perhaps a stock style?
1103 # perhaps a stock style?
1087 if not os.path.split(tmpl)[0]:
1104 if not os.path.split(tmpl)[0]:
1088 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1105 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1089 or templater.templatepath(tmpl))
1106 or templater.templatepath(tmpl))
1090 if mapname and os.path.isfile(mapname):
1107 if mapname and os.path.isfile(mapname):
1091 return None, mapname
1108 return None, mapname
1092
1109
1093 # perhaps it's a reference to [templates]
1110 # perhaps it's a reference to [templates]
1094 t = ui.config('templates', tmpl)
1111 t = ui.config('templates', tmpl)
1095 if t:
1112 if t:
1096 try:
1113 try:
1097 tmpl = templater.parsestring(t)
1114 tmpl = templater.parsestring(t)
1098 except SyntaxError:
1115 except SyntaxError:
1099 tmpl = templater.parsestring(t, quoted=False)
1116 tmpl = templater.parsestring(t, quoted=False)
1100 return tmpl, None
1117 return tmpl, None
1101
1118
1102 # perhaps it's a path to a map or a template
1119 # perhaps it's a path to a map or a template
1103 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1120 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1104 # is it a mapfile for a style?
1121 # is it a mapfile for a style?
1105 if os.path.basename(tmpl).startswith("map-"):
1122 if os.path.basename(tmpl).startswith("map-"):
1106 return None, os.path.realpath(tmpl)
1123 return None, os.path.realpath(tmpl)
1107 tmpl = open(tmpl).read()
1124 tmpl = open(tmpl).read()
1108 return tmpl, None
1125 return tmpl, None
1109
1126
1110 # constant string?
1127 # constant string?
1111 return tmpl, None
1128 return tmpl, None
1112
1129
1113 def show_changeset(ui, repo, opts, buffered=False):
1130 def show_changeset(ui, repo, opts, buffered=False):
1114 """show one changeset using template or regular display.
1131 """show one changeset using template or regular display.
1115
1132
1116 Display format will be the first non-empty hit of:
1133 Display format will be the first non-empty hit of:
1117 1. option 'template'
1134 1. option 'template'
1118 2. option 'style'
1135 2. option 'style'
1119 3. [ui] setting 'logtemplate'
1136 3. [ui] setting 'logtemplate'
1120 4. [ui] setting 'style'
1137 4. [ui] setting 'style'
1121 If all of these values are either the unset or the empty string,
1138 If all of these values are either the unset or the empty string,
1122 regular display via changeset_printer() is done.
1139 regular display via changeset_printer() is done.
1123 """
1140 """
1124 # options
1141 # options
1125 patch = None
1142 patch = None
1126 if opts.get('patch') or opts.get('stat'):
1143 if opts.get('patch') or opts.get('stat'):
1127 patch = scmutil.matchall(repo)
1144 patch = scmutil.matchall(repo)
1128
1145
1129 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1146 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1130
1147
1131 if not tmpl and not mapfile:
1148 if not tmpl and not mapfile:
1132 return changeset_printer(ui, repo, patch, opts, buffered)
1149 return changeset_printer(ui, repo, patch, opts, buffered)
1133
1150
1134 try:
1151 try:
1135 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1152 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1136 except SyntaxError, inst:
1153 except SyntaxError, inst:
1137 raise util.Abort(inst.args[0])
1154 raise util.Abort(inst.args[0])
1138 return t
1155 return t
1139
1156
1140 def showmarker(ui, marker):
1157 def showmarker(ui, marker):
1141 """utility function to display obsolescence marker in a readable way
1158 """utility function to display obsolescence marker in a readable way
1142
1159
1143 To be used by debug function."""
1160 To be used by debug function."""
1144 ui.write(hex(marker.precnode()))
1161 ui.write(hex(marker.precnode()))
1145 for repl in marker.succnodes():
1162 for repl in marker.succnodes():
1146 ui.write(' ')
1163 ui.write(' ')
1147 ui.write(hex(repl))
1164 ui.write(hex(repl))
1148 ui.write(' %X ' % marker._data[2])
1165 ui.write(' %X ' % marker._data[2])
1149 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1166 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1150 sorted(marker.metadata().items()))))
1167 sorted(marker.metadata().items()))))
1151 ui.write('\n')
1168 ui.write('\n')
1152
1169
1153 def finddate(ui, repo, date):
1170 def finddate(ui, repo, date):
1154 """Find the tipmost changeset that matches the given date spec"""
1171 """Find the tipmost changeset that matches the given date spec"""
1155
1172
1156 df = util.matchdate(date)
1173 df = util.matchdate(date)
1157 m = scmutil.matchall(repo)
1174 m = scmutil.matchall(repo)
1158 results = {}
1175 results = {}
1159
1176
1160 def prep(ctx, fns):
1177 def prep(ctx, fns):
1161 d = ctx.date()
1178 d = ctx.date()
1162 if df(d[0]):
1179 if df(d[0]):
1163 results[ctx.rev()] = d
1180 results[ctx.rev()] = d
1164
1181
1165 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1182 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1166 rev = ctx.rev()
1183 rev = ctx.rev()
1167 if rev in results:
1184 if rev in results:
1168 ui.status(_("found revision %s from %s\n") %
1185 ui.status(_("found revision %s from %s\n") %
1169 (rev, util.datestr(results[rev])))
1186 (rev, util.datestr(results[rev])))
1170 return str(rev)
1187 return str(rev)
1171
1188
1172 raise util.Abort(_("revision matching date not found"))
1189 raise util.Abort(_("revision matching date not found"))
1173
1190
1174 def increasingwindows(windowsize=8, sizelimit=512):
1191 def increasingwindows(windowsize=8, sizelimit=512):
1175 while True:
1192 while True:
1176 yield windowsize
1193 yield windowsize
1177 if windowsize < sizelimit:
1194 if windowsize < sizelimit:
1178 windowsize *= 2
1195 windowsize *= 2
1179
1196
1180 class FileWalkError(Exception):
1197 class FileWalkError(Exception):
1181 pass
1198 pass
1182
1199
1183 def walkfilerevs(repo, match, follow, revs, fncache):
1200 def walkfilerevs(repo, match, follow, revs, fncache):
1184 '''Walks the file history for the matched files.
1201 '''Walks the file history for the matched files.
1185
1202
1186 Returns the changeset revs that are involved in the file history.
1203 Returns the changeset revs that are involved in the file history.
1187
1204
1188 Throws FileWalkError if the file history can't be walked using
1205 Throws FileWalkError if the file history can't be walked using
1189 filelogs alone.
1206 filelogs alone.
1190 '''
1207 '''
1191 wanted = set()
1208 wanted = set()
1192 copies = []
1209 copies = []
1193 minrev, maxrev = min(revs), max(revs)
1210 minrev, maxrev = min(revs), max(revs)
1194 def filerevgen(filelog, last):
1211 def filerevgen(filelog, last):
1195 """
1212 """
1196 Only files, no patterns. Check the history of each file.
1213 Only files, no patterns. Check the history of each file.
1197
1214
1198 Examines filelog entries within minrev, maxrev linkrev range
1215 Examines filelog entries within minrev, maxrev linkrev range
1199 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1216 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1200 tuples in backwards order
1217 tuples in backwards order
1201 """
1218 """
1202 cl_count = len(repo)
1219 cl_count = len(repo)
1203 revs = []
1220 revs = []
1204 for j in xrange(0, last + 1):
1221 for j in xrange(0, last + 1):
1205 linkrev = filelog.linkrev(j)
1222 linkrev = filelog.linkrev(j)
1206 if linkrev < minrev:
1223 if linkrev < minrev:
1207 continue
1224 continue
1208 # only yield rev for which we have the changelog, it can
1225 # only yield rev for which we have the changelog, it can
1209 # happen while doing "hg log" during a pull or commit
1226 # happen while doing "hg log" during a pull or commit
1210 if linkrev >= cl_count:
1227 if linkrev >= cl_count:
1211 break
1228 break
1212
1229
1213 parentlinkrevs = []
1230 parentlinkrevs = []
1214 for p in filelog.parentrevs(j):
1231 for p in filelog.parentrevs(j):
1215 if p != nullrev:
1232 if p != nullrev:
1216 parentlinkrevs.append(filelog.linkrev(p))
1233 parentlinkrevs.append(filelog.linkrev(p))
1217 n = filelog.node(j)
1234 n = filelog.node(j)
1218 revs.append((linkrev, parentlinkrevs,
1235 revs.append((linkrev, parentlinkrevs,
1219 follow and filelog.renamed(n)))
1236 follow and filelog.renamed(n)))
1220
1237
1221 return reversed(revs)
1238 return reversed(revs)
1222 def iterfiles():
1239 def iterfiles():
1223 pctx = repo['.']
1240 pctx = repo['.']
1224 for filename in match.files():
1241 for filename in match.files():
1225 if follow:
1242 if follow:
1226 if filename not in pctx:
1243 if filename not in pctx:
1227 raise util.Abort(_('cannot follow file not in parent '
1244 raise util.Abort(_('cannot follow file not in parent '
1228 'revision: "%s"') % filename)
1245 'revision: "%s"') % filename)
1229 yield filename, pctx[filename].filenode()
1246 yield filename, pctx[filename].filenode()
1230 else:
1247 else:
1231 yield filename, None
1248 yield filename, None
1232 for filename_node in copies:
1249 for filename_node in copies:
1233 yield filename_node
1250 yield filename_node
1234
1251
1235 for file_, node in iterfiles():
1252 for file_, node in iterfiles():
1236 filelog = repo.file(file_)
1253 filelog = repo.file(file_)
1237 if not len(filelog):
1254 if not len(filelog):
1238 if node is None:
1255 if node is None:
1239 # A zero count may be a directory or deleted file, so
1256 # A zero count may be a directory or deleted file, so
1240 # try to find matching entries on the slow path.
1257 # try to find matching entries on the slow path.
1241 if follow:
1258 if follow:
1242 raise util.Abort(
1259 raise util.Abort(
1243 _('cannot follow nonexistent file: "%s"') % file_)
1260 _('cannot follow nonexistent file: "%s"') % file_)
1244 raise FileWalkError("Cannot walk via filelog")
1261 raise FileWalkError("Cannot walk via filelog")
1245 else:
1262 else:
1246 continue
1263 continue
1247
1264
1248 if node is None:
1265 if node is None:
1249 last = len(filelog) - 1
1266 last = len(filelog) - 1
1250 else:
1267 else:
1251 last = filelog.rev(node)
1268 last = filelog.rev(node)
1252
1269
1253
1270
1254 # keep track of all ancestors of the file
1271 # keep track of all ancestors of the file
1255 ancestors = set([filelog.linkrev(last)])
1272 ancestors = set([filelog.linkrev(last)])
1256
1273
1257 # iterate from latest to oldest revision
1274 # iterate from latest to oldest revision
1258 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1275 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1259 if not follow:
1276 if not follow:
1260 if rev > maxrev:
1277 if rev > maxrev:
1261 continue
1278 continue
1262 else:
1279 else:
1263 # Note that last might not be the first interesting
1280 # Note that last might not be the first interesting
1264 # rev to us:
1281 # rev to us:
1265 # if the file has been changed after maxrev, we'll
1282 # if the file has been changed after maxrev, we'll
1266 # have linkrev(last) > maxrev, and we still need
1283 # have linkrev(last) > maxrev, and we still need
1267 # to explore the file graph
1284 # to explore the file graph
1268 if rev not in ancestors:
1285 if rev not in ancestors:
1269 continue
1286 continue
1270 # XXX insert 1327 fix here
1287 # XXX insert 1327 fix here
1271 if flparentlinkrevs:
1288 if flparentlinkrevs:
1272 ancestors.update(flparentlinkrevs)
1289 ancestors.update(flparentlinkrevs)
1273
1290
1274 fncache.setdefault(rev, []).append(file_)
1291 fncache.setdefault(rev, []).append(file_)
1275 wanted.add(rev)
1292 wanted.add(rev)
1276 if copied:
1293 if copied:
1277 copies.append(copied)
1294 copies.append(copied)
1278
1295
1279 return wanted
1296 return wanted
1280
1297
1281 def walkchangerevs(repo, match, opts, prepare):
1298 def walkchangerevs(repo, match, opts, prepare):
1282 '''Iterate over files and the revs in which they changed.
1299 '''Iterate over files and the revs in which they changed.
1283
1300
1284 Callers most commonly need to iterate backwards over the history
1301 Callers most commonly need to iterate backwards over the history
1285 in which they are interested. Doing so has awful (quadratic-looking)
1302 in which they are interested. Doing so has awful (quadratic-looking)
1286 performance, so we use iterators in a "windowed" way.
1303 performance, so we use iterators in a "windowed" way.
1287
1304
1288 We walk a window of revisions in the desired order. Within the
1305 We walk a window of revisions in the desired order. Within the
1289 window, we first walk forwards to gather data, then in the desired
1306 window, we first walk forwards to gather data, then in the desired
1290 order (usually backwards) to display it.
1307 order (usually backwards) to display it.
1291
1308
1292 This function returns an iterator yielding contexts. Before
1309 This function returns an iterator yielding contexts. Before
1293 yielding each context, the iterator will first call the prepare
1310 yielding each context, the iterator will first call the prepare
1294 function on each context in the window in forward order.'''
1311 function on each context in the window in forward order.'''
1295
1312
1296 follow = opts.get('follow') or opts.get('follow_first')
1313 follow = opts.get('follow') or opts.get('follow_first')
1297
1314
1298 if opts.get('rev'):
1315 if opts.get('rev'):
1299 revs = scmutil.revrange(repo, opts.get('rev'))
1316 revs = scmutil.revrange(repo, opts.get('rev'))
1300 elif follow:
1317 elif follow:
1301 revs = repo.revs('reverse(:.)')
1318 revs = repo.revs('reverse(:.)')
1302 else:
1319 else:
1303 revs = revset.spanset(repo)
1320 revs = revset.spanset(repo)
1304 revs.reverse()
1321 revs.reverse()
1305 if not revs:
1322 if not revs:
1306 return []
1323 return []
1307 wanted = set()
1324 wanted = set()
1308 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1325 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1309 fncache = {}
1326 fncache = {}
1310 change = repo.changectx
1327 change = repo.changectx
1311
1328
1312 # First step is to fill wanted, the set of revisions that we want to yield.
1329 # First step is to fill wanted, the set of revisions that we want to yield.
1313 # When it does not induce extra cost, we also fill fncache for revisions in
1330 # When it does not induce extra cost, we also fill fncache for revisions in
1314 # wanted: a cache of filenames that were changed (ctx.files()) and that
1331 # wanted: a cache of filenames that were changed (ctx.files()) and that
1315 # match the file filtering conditions.
1332 # match the file filtering conditions.
1316
1333
1317 if not slowpath and not match.files():
1334 if not slowpath and not match.files():
1318 # No files, no patterns. Display all revs.
1335 # No files, no patterns. Display all revs.
1319 wanted = revs
1336 wanted = revs
1320
1337
1321 if not slowpath and match.files():
1338 if not slowpath and match.files():
1322 # We only have to read through the filelog to find wanted revisions
1339 # We only have to read through the filelog to find wanted revisions
1323
1340
1324 try:
1341 try:
1325 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1342 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1326 except FileWalkError:
1343 except FileWalkError:
1327 slowpath = True
1344 slowpath = True
1328
1345
1329 # We decided to fall back to the slowpath because at least one
1346 # We decided to fall back to the slowpath because at least one
1330 # of the paths was not a file. Check to see if at least one of them
1347 # of the paths was not a file. Check to see if at least one of them
1331 # existed in history, otherwise simply return
1348 # existed in history, otherwise simply return
1332 for path in match.files():
1349 for path in match.files():
1333 if path == '.' or path in repo.store:
1350 if path == '.' or path in repo.store:
1334 break
1351 break
1335 else:
1352 else:
1336 return []
1353 return []
1337
1354
1338 if slowpath:
1355 if slowpath:
1339 # We have to read the changelog to match filenames against
1356 # We have to read the changelog to match filenames against
1340 # changed files
1357 # changed files
1341
1358
1342 if follow:
1359 if follow:
1343 raise util.Abort(_('can only follow copies/renames for explicit '
1360 raise util.Abort(_('can only follow copies/renames for explicit '
1344 'filenames'))
1361 'filenames'))
1345
1362
1346 # The slow path checks files modified in every changeset.
1363 # The slow path checks files modified in every changeset.
1347 # This is really slow on large repos, so compute the set lazily.
1364 # This is really slow on large repos, so compute the set lazily.
1348 class lazywantedset(object):
1365 class lazywantedset(object):
1349 def __init__(self):
1366 def __init__(self):
1350 self.set = set()
1367 self.set = set()
1351 self.revs = set(revs)
1368 self.revs = set(revs)
1352
1369
1353 # No need to worry about locality here because it will be accessed
1370 # No need to worry about locality here because it will be accessed
1354 # in the same order as the increasing window below.
1371 # in the same order as the increasing window below.
1355 def __contains__(self, value):
1372 def __contains__(self, value):
1356 if value in self.set:
1373 if value in self.set:
1357 return True
1374 return True
1358 elif not value in self.revs:
1375 elif not value in self.revs:
1359 return False
1376 return False
1360 else:
1377 else:
1361 self.revs.discard(value)
1378 self.revs.discard(value)
1362 ctx = change(value)
1379 ctx = change(value)
1363 matches = filter(match, ctx.files())
1380 matches = filter(match, ctx.files())
1364 if matches:
1381 if matches:
1365 fncache[value] = matches
1382 fncache[value] = matches
1366 self.set.add(value)
1383 self.set.add(value)
1367 return True
1384 return True
1368 return False
1385 return False
1369
1386
1370 def discard(self, value):
1387 def discard(self, value):
1371 self.revs.discard(value)
1388 self.revs.discard(value)
1372 self.set.discard(value)
1389 self.set.discard(value)
1373
1390
1374 wanted = lazywantedset()
1391 wanted = lazywantedset()
1375
1392
1376 class followfilter(object):
1393 class followfilter(object):
1377 def __init__(self, onlyfirst=False):
1394 def __init__(self, onlyfirst=False):
1378 self.startrev = nullrev
1395 self.startrev = nullrev
1379 self.roots = set()
1396 self.roots = set()
1380 self.onlyfirst = onlyfirst
1397 self.onlyfirst = onlyfirst
1381
1398
1382 def match(self, rev):
1399 def match(self, rev):
1383 def realparents(rev):
1400 def realparents(rev):
1384 if self.onlyfirst:
1401 if self.onlyfirst:
1385 return repo.changelog.parentrevs(rev)[0:1]
1402 return repo.changelog.parentrevs(rev)[0:1]
1386 else:
1403 else:
1387 return filter(lambda x: x != nullrev,
1404 return filter(lambda x: x != nullrev,
1388 repo.changelog.parentrevs(rev))
1405 repo.changelog.parentrevs(rev))
1389
1406
1390 if self.startrev == nullrev:
1407 if self.startrev == nullrev:
1391 self.startrev = rev
1408 self.startrev = rev
1392 return True
1409 return True
1393
1410
1394 if rev > self.startrev:
1411 if rev > self.startrev:
1395 # forward: all descendants
1412 # forward: all descendants
1396 if not self.roots:
1413 if not self.roots:
1397 self.roots.add(self.startrev)
1414 self.roots.add(self.startrev)
1398 for parent in realparents(rev):
1415 for parent in realparents(rev):
1399 if parent in self.roots:
1416 if parent in self.roots:
1400 self.roots.add(rev)
1417 self.roots.add(rev)
1401 return True
1418 return True
1402 else:
1419 else:
1403 # backwards: all parents
1420 # backwards: all parents
1404 if not self.roots:
1421 if not self.roots:
1405 self.roots.update(realparents(self.startrev))
1422 self.roots.update(realparents(self.startrev))
1406 if rev in self.roots:
1423 if rev in self.roots:
1407 self.roots.remove(rev)
1424 self.roots.remove(rev)
1408 self.roots.update(realparents(rev))
1425 self.roots.update(realparents(rev))
1409 return True
1426 return True
1410
1427
1411 return False
1428 return False
1412
1429
1413 # it might be worthwhile to do this in the iterator if the rev range
1430 # it might be worthwhile to do this in the iterator if the rev range
1414 # is descending and the prune args are all within that range
1431 # is descending and the prune args are all within that range
1415 for rev in opts.get('prune', ()):
1432 for rev in opts.get('prune', ()):
1416 rev = repo[rev].rev()
1433 rev = repo[rev].rev()
1417 ff = followfilter()
1434 ff = followfilter()
1418 stop = min(revs[0], revs[-1])
1435 stop = min(revs[0], revs[-1])
1419 for x in xrange(rev, stop - 1, -1):
1436 for x in xrange(rev, stop - 1, -1):
1420 if ff.match(x):
1437 if ff.match(x):
1421 wanted = wanted - [x]
1438 wanted = wanted - [x]
1422
1439
1423 # Now that wanted is correctly initialized, we can iterate over the
1440 # Now that wanted is correctly initialized, we can iterate over the
1424 # revision range, yielding only revisions in wanted.
1441 # revision range, yielding only revisions in wanted.
1425 def iterate():
1442 def iterate():
1426 if follow and not match.files():
1443 if follow and not match.files():
1427 ff = followfilter(onlyfirst=opts.get('follow_first'))
1444 ff = followfilter(onlyfirst=opts.get('follow_first'))
1428 def want(rev):
1445 def want(rev):
1429 return ff.match(rev) and rev in wanted
1446 return ff.match(rev) and rev in wanted
1430 else:
1447 else:
1431 def want(rev):
1448 def want(rev):
1432 return rev in wanted
1449 return rev in wanted
1433
1450
1434 it = iter(revs)
1451 it = iter(revs)
1435 stopiteration = False
1452 stopiteration = False
1436 for windowsize in increasingwindows():
1453 for windowsize in increasingwindows():
1437 nrevs = []
1454 nrevs = []
1438 for i in xrange(windowsize):
1455 for i in xrange(windowsize):
1439 try:
1456 try:
1440 rev = it.next()
1457 rev = it.next()
1441 if want(rev):
1458 if want(rev):
1442 nrevs.append(rev)
1459 nrevs.append(rev)
1443 except (StopIteration):
1460 except (StopIteration):
1444 stopiteration = True
1461 stopiteration = True
1445 break
1462 break
1446 for rev in sorted(nrevs):
1463 for rev in sorted(nrevs):
1447 fns = fncache.get(rev)
1464 fns = fncache.get(rev)
1448 ctx = change(rev)
1465 ctx = change(rev)
1449 if not fns:
1466 if not fns:
1450 def fns_generator():
1467 def fns_generator():
1451 for f in ctx.files():
1468 for f in ctx.files():
1452 if match(f):
1469 if match(f):
1453 yield f
1470 yield f
1454 fns = fns_generator()
1471 fns = fns_generator()
1455 prepare(ctx, fns)
1472 prepare(ctx, fns)
1456 for rev in nrevs:
1473 for rev in nrevs:
1457 yield change(rev)
1474 yield change(rev)
1458
1475
1459 if stopiteration:
1476 if stopiteration:
1460 break
1477 break
1461
1478
1462 return iterate()
1479 return iterate()
1463
1480
1464 def _makelogfilematcher(repo, pats, followfirst):
1481 def _makelogfilematcher(repo, pats, followfirst):
1465 # When displaying a revision with --patch --follow FILE, we have
1482 # When displaying a revision with --patch --follow FILE, we have
1466 # to know which file of the revision must be diffed. With
1483 # to know which file of the revision must be diffed. With
1467 # --follow, we want the names of the ancestors of FILE in the
1484 # --follow, we want the names of the ancestors of FILE in the
1468 # revision, stored in "fcache". "fcache" is populated by
1485 # revision, stored in "fcache". "fcache" is populated by
1469 # reproducing the graph traversal already done by --follow revset
1486 # reproducing the graph traversal already done by --follow revset
1470 # and relating linkrevs to file names (which is not "correct" but
1487 # and relating linkrevs to file names (which is not "correct" but
1471 # good enough).
1488 # good enough).
1472 fcache = {}
1489 fcache = {}
1473 fcacheready = [False]
1490 fcacheready = [False]
1474 pctx = repo['.']
1491 pctx = repo['.']
1475 wctx = repo[None]
1492 wctx = repo[None]
1476
1493
1477 def populate():
1494 def populate():
1478 for fn in pats:
1495 for fn in pats:
1479 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1496 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1480 for c in i:
1497 for c in i:
1481 fcache.setdefault(c.linkrev(), set()).add(c.path())
1498 fcache.setdefault(c.linkrev(), set()).add(c.path())
1482
1499
1483 def filematcher(rev):
1500 def filematcher(rev):
1484 if not fcacheready[0]:
1501 if not fcacheready[0]:
1485 # Lazy initialization
1502 # Lazy initialization
1486 fcacheready[0] = True
1503 fcacheready[0] = True
1487 populate()
1504 populate()
1488 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1505 return scmutil.match(wctx, fcache.get(rev, []), default='path')
1489
1506
1490 return filematcher
1507 return filematcher
1491
1508
1492 def _makelogrevset(repo, pats, opts, revs):
1509 def _makelogrevset(repo, pats, opts, revs):
1493 """Return (expr, filematcher) where expr is a revset string built
1510 """Return (expr, filematcher) where expr is a revset string built
1494 from log options and file patterns or None. If --stat or --patch
1511 from log options and file patterns or None. If --stat or --patch
1495 are not passed filematcher is None. Otherwise it is a callable
1512 are not passed filematcher is None. Otherwise it is a callable
1496 taking a revision number and returning a match objects filtering
1513 taking a revision number and returning a match objects filtering
1497 the files to be detailed when displaying the revision.
1514 the files to be detailed when displaying the revision.
1498 """
1515 """
1499 opt2revset = {
1516 opt2revset = {
1500 'no_merges': ('not merge()', None),
1517 'no_merges': ('not merge()', None),
1501 'only_merges': ('merge()', None),
1518 'only_merges': ('merge()', None),
1502 '_ancestors': ('ancestors(%(val)s)', None),
1519 '_ancestors': ('ancestors(%(val)s)', None),
1503 '_fancestors': ('_firstancestors(%(val)s)', None),
1520 '_fancestors': ('_firstancestors(%(val)s)', None),
1504 '_descendants': ('descendants(%(val)s)', None),
1521 '_descendants': ('descendants(%(val)s)', None),
1505 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1522 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1506 '_matchfiles': ('_matchfiles(%(val)s)', None),
1523 '_matchfiles': ('_matchfiles(%(val)s)', None),
1507 'date': ('date(%(val)r)', None),
1524 'date': ('date(%(val)r)', None),
1508 'branch': ('branch(%(val)r)', ' or '),
1525 'branch': ('branch(%(val)r)', ' or '),
1509 '_patslog': ('filelog(%(val)r)', ' or '),
1526 '_patslog': ('filelog(%(val)r)', ' or '),
1510 '_patsfollow': ('follow(%(val)r)', ' or '),
1527 '_patsfollow': ('follow(%(val)r)', ' or '),
1511 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1528 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1512 'keyword': ('keyword(%(val)r)', ' or '),
1529 'keyword': ('keyword(%(val)r)', ' or '),
1513 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1530 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1514 'user': ('user(%(val)r)', ' or '),
1531 'user': ('user(%(val)r)', ' or '),
1515 }
1532 }
1516
1533
1517 opts = dict(opts)
1534 opts = dict(opts)
1518 # follow or not follow?
1535 # follow or not follow?
1519 follow = opts.get('follow') or opts.get('follow_first')
1536 follow = opts.get('follow') or opts.get('follow_first')
1520 followfirst = opts.get('follow_first') and 1 or 0
1537 followfirst = opts.get('follow_first') and 1 or 0
1521 # --follow with FILE behaviour depends on revs...
1538 # --follow with FILE behaviour depends on revs...
1522 it = iter(revs)
1539 it = iter(revs)
1523 startrev = it.next()
1540 startrev = it.next()
1524 try:
1541 try:
1525 followdescendants = startrev < it.next()
1542 followdescendants = startrev < it.next()
1526 except (StopIteration):
1543 except (StopIteration):
1527 followdescendants = False
1544 followdescendants = False
1528
1545
1529 # branch and only_branch are really aliases and must be handled at
1546 # branch and only_branch are really aliases and must be handled at
1530 # the same time
1547 # the same time
1531 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1548 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1532 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1549 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1533 # pats/include/exclude are passed to match.match() directly in
1550 # pats/include/exclude are passed to match.match() directly in
1534 # _matchfiles() revset but walkchangerevs() builds its matcher with
1551 # _matchfiles() revset but walkchangerevs() builds its matcher with
1535 # scmutil.match(). The difference is input pats are globbed on
1552 # scmutil.match(). The difference is input pats are globbed on
1536 # platforms without shell expansion (windows).
1553 # platforms without shell expansion (windows).
1537 pctx = repo[None]
1554 pctx = repo[None]
1538 match, pats = scmutil.matchandpats(pctx, pats, opts)
1555 match, pats = scmutil.matchandpats(pctx, pats, opts)
1539 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1556 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1540 if not slowpath:
1557 if not slowpath:
1541 for f in match.files():
1558 for f in match.files():
1542 if follow and f not in pctx:
1559 if follow and f not in pctx:
1543 raise util.Abort(_('cannot follow file not in parent '
1560 raise util.Abort(_('cannot follow file not in parent '
1544 'revision: "%s"') % f)
1561 'revision: "%s"') % f)
1545 filelog = repo.file(f)
1562 filelog = repo.file(f)
1546 if not filelog:
1563 if not filelog:
1547 # A zero count may be a directory or deleted file, so
1564 # A zero count may be a directory or deleted file, so
1548 # try to find matching entries on the slow path.
1565 # try to find matching entries on the slow path.
1549 if follow:
1566 if follow:
1550 raise util.Abort(
1567 raise util.Abort(
1551 _('cannot follow nonexistent file: "%s"') % f)
1568 _('cannot follow nonexistent file: "%s"') % f)
1552 slowpath = True
1569 slowpath = True
1553
1570
1554 # We decided to fall back to the slowpath because at least one
1571 # We decided to fall back to the slowpath because at least one
1555 # of the paths was not a file. Check to see if at least one of them
1572 # of the paths was not a file. Check to see if at least one of them
1556 # existed in history - in that case, we'll continue down the
1573 # existed in history - in that case, we'll continue down the
1557 # slowpath; otherwise, we can turn off the slowpath
1574 # slowpath; otherwise, we can turn off the slowpath
1558 if slowpath:
1575 if slowpath:
1559 for path in match.files():
1576 for path in match.files():
1560 if path == '.' or path in repo.store:
1577 if path == '.' or path in repo.store:
1561 break
1578 break
1562 else:
1579 else:
1563 slowpath = False
1580 slowpath = False
1564
1581
1565 if slowpath:
1582 if slowpath:
1566 # See walkchangerevs() slow path.
1583 # See walkchangerevs() slow path.
1567 #
1584 #
1568 if follow:
1585 if follow:
1569 raise util.Abort(_('can only follow copies/renames for explicit '
1586 raise util.Abort(_('can only follow copies/renames for explicit '
1570 'filenames'))
1587 'filenames'))
1571 # pats/include/exclude cannot be represented as separate
1588 # pats/include/exclude cannot be represented as separate
1572 # revset expressions as their filtering logic applies at file
1589 # revset expressions as their filtering logic applies at file
1573 # level. For instance "-I a -X a" matches a revision touching
1590 # level. For instance "-I a -X a" matches a revision touching
1574 # "a" and "b" while "file(a) and not file(b)" does
1591 # "a" and "b" while "file(a) and not file(b)" does
1575 # not. Besides, filesets are evaluated against the working
1592 # not. Besides, filesets are evaluated against the working
1576 # directory.
1593 # directory.
1577 matchargs = ['r:', 'd:relpath']
1594 matchargs = ['r:', 'd:relpath']
1578 for p in pats:
1595 for p in pats:
1579 matchargs.append('p:' + p)
1596 matchargs.append('p:' + p)
1580 for p in opts.get('include', []):
1597 for p in opts.get('include', []):
1581 matchargs.append('i:' + p)
1598 matchargs.append('i:' + p)
1582 for p in opts.get('exclude', []):
1599 for p in opts.get('exclude', []):
1583 matchargs.append('x:' + p)
1600 matchargs.append('x:' + p)
1584 matchargs = ','.join(('%r' % p) for p in matchargs)
1601 matchargs = ','.join(('%r' % p) for p in matchargs)
1585 opts['_matchfiles'] = matchargs
1602 opts['_matchfiles'] = matchargs
1586 else:
1603 else:
1587 if follow:
1604 if follow:
1588 fpats = ('_patsfollow', '_patsfollowfirst')
1605 fpats = ('_patsfollow', '_patsfollowfirst')
1589 fnopats = (('_ancestors', '_fancestors'),
1606 fnopats = (('_ancestors', '_fancestors'),
1590 ('_descendants', '_fdescendants'))
1607 ('_descendants', '_fdescendants'))
1591 if pats:
1608 if pats:
1592 # follow() revset interprets its file argument as a
1609 # follow() revset interprets its file argument as a
1593 # manifest entry, so use match.files(), not pats.
1610 # manifest entry, so use match.files(), not pats.
1594 opts[fpats[followfirst]] = list(match.files())
1611 opts[fpats[followfirst]] = list(match.files())
1595 else:
1612 else:
1596 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1613 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1597 else:
1614 else:
1598 opts['_patslog'] = list(pats)
1615 opts['_patslog'] = list(pats)
1599
1616
1600 filematcher = None
1617 filematcher = None
1601 if opts.get('patch') or opts.get('stat'):
1618 if opts.get('patch') or opts.get('stat'):
1602 if follow:
1619 if follow:
1603 filematcher = _makelogfilematcher(repo, pats, followfirst)
1620 filematcher = _makelogfilematcher(repo, pats, followfirst)
1604 else:
1621 else:
1605 filematcher = lambda rev: match
1622 filematcher = lambda rev: match
1606
1623
1607 expr = []
1624 expr = []
1608 for op, val in opts.iteritems():
1625 for op, val in opts.iteritems():
1609 if not val:
1626 if not val:
1610 continue
1627 continue
1611 if op not in opt2revset:
1628 if op not in opt2revset:
1612 continue
1629 continue
1613 revop, andor = opt2revset[op]
1630 revop, andor = opt2revset[op]
1614 if '%(val)' not in revop:
1631 if '%(val)' not in revop:
1615 expr.append(revop)
1632 expr.append(revop)
1616 else:
1633 else:
1617 if not isinstance(val, list):
1634 if not isinstance(val, list):
1618 e = revop % {'val': val}
1635 e = revop % {'val': val}
1619 else:
1636 else:
1620 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1637 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1621 expr.append(e)
1638 expr.append(e)
1622
1639
1623 if expr:
1640 if expr:
1624 expr = '(' + ' and '.join(expr) + ')'
1641 expr = '(' + ' and '.join(expr) + ')'
1625 else:
1642 else:
1626 expr = None
1643 expr = None
1627 return expr, filematcher
1644 return expr, filematcher
1628
1645
1629 def getgraphlogrevs(repo, pats, opts):
1646 def getgraphlogrevs(repo, pats, opts):
1630 """Return (revs, expr, filematcher) where revs is an iterable of
1647 """Return (revs, expr, filematcher) where revs is an iterable of
1631 revision numbers, expr is a revset string built from log options
1648 revision numbers, expr is a revset string built from log options
1632 and file patterns or None, and used to filter 'revs'. If --stat or
1649 and file patterns or None, and used to filter 'revs'. If --stat or
1633 --patch are not passed filematcher is None. Otherwise it is a
1650 --patch are not passed filematcher is None. Otherwise it is a
1634 callable taking a revision number and returning a match objects
1651 callable taking a revision number and returning a match objects
1635 filtering the files to be detailed when displaying the revision.
1652 filtering the files to be detailed when displaying the revision.
1636 """
1653 """
1637 if not len(repo):
1654 if not len(repo):
1638 return [], None, None
1655 return [], None, None
1639 limit = loglimit(opts)
1656 limit = loglimit(opts)
1640 # Default --rev value depends on --follow but --follow behaviour
1657 # Default --rev value depends on --follow but --follow behaviour
1641 # depends on revisions resolved from --rev...
1658 # depends on revisions resolved from --rev...
1642 follow = opts.get('follow') or opts.get('follow_first')
1659 follow = opts.get('follow') or opts.get('follow_first')
1643 possiblyunsorted = False # whether revs might need sorting
1660 possiblyunsorted = False # whether revs might need sorting
1644 if opts.get('rev'):
1661 if opts.get('rev'):
1645 revs = scmutil.revrange(repo, opts['rev'])
1662 revs = scmutil.revrange(repo, opts['rev'])
1646 # Don't sort here because _makelogrevset might depend on the
1663 # Don't sort here because _makelogrevset might depend on the
1647 # order of revs
1664 # order of revs
1648 possiblyunsorted = True
1665 possiblyunsorted = True
1649 else:
1666 else:
1650 if follow and len(repo) > 0:
1667 if follow and len(repo) > 0:
1651 revs = repo.revs('reverse(:.)')
1668 revs = repo.revs('reverse(:.)')
1652 else:
1669 else:
1653 revs = revset.spanset(repo)
1670 revs = revset.spanset(repo)
1654 revs.reverse()
1671 revs.reverse()
1655 if not revs:
1672 if not revs:
1656 return revset.baseset(), None, None
1673 return revset.baseset(), None, None
1657 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1674 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1658 if possiblyunsorted:
1675 if possiblyunsorted:
1659 revs.sort(reverse=True)
1676 revs.sort(reverse=True)
1660 if expr:
1677 if expr:
1661 # Revset matchers often operate faster on revisions in changelog
1678 # Revset matchers often operate faster on revisions in changelog
1662 # order, because most filters deal with the changelog.
1679 # order, because most filters deal with the changelog.
1663 revs.reverse()
1680 revs.reverse()
1664 matcher = revset.match(repo.ui, expr)
1681 matcher = revset.match(repo.ui, expr)
1665 # Revset matches can reorder revisions. "A or B" typically returns
1682 # Revset matches can reorder revisions. "A or B" typically returns
1666 # returns the revision matching A then the revision matching B. Sort
1683 # returns the revision matching A then the revision matching B. Sort
1667 # again to fix that.
1684 # again to fix that.
1668 revs = matcher(repo, revs)
1685 revs = matcher(repo, revs)
1669 revs.sort(reverse=True)
1686 revs.sort(reverse=True)
1670 if limit is not None:
1687 if limit is not None:
1671 limitedrevs = revset.baseset()
1688 limitedrevs = revset.baseset()
1672 for idx, rev in enumerate(revs):
1689 for idx, rev in enumerate(revs):
1673 if idx >= limit:
1690 if idx >= limit:
1674 break
1691 break
1675 limitedrevs.append(rev)
1692 limitedrevs.append(rev)
1676 revs = limitedrevs
1693 revs = limitedrevs
1677
1694
1678 return revs, expr, filematcher
1695 return revs, expr, filematcher
1679
1696
1680 def getlogrevs(repo, pats, opts):
1697 def getlogrevs(repo, pats, opts):
1681 """Return (revs, expr, filematcher) where revs is an iterable of
1698 """Return (revs, expr, filematcher) where revs is an iterable of
1682 revision numbers, expr is a revset string built from log options
1699 revision numbers, expr is a revset string built from log options
1683 and file patterns or None, and used to filter 'revs'. If --stat or
1700 and file patterns or None, and used to filter 'revs'. If --stat or
1684 --patch are not passed filematcher is None. Otherwise it is a
1701 --patch are not passed filematcher is None. Otherwise it is a
1685 callable taking a revision number and returning a match objects
1702 callable taking a revision number and returning a match objects
1686 filtering the files to be detailed when displaying the revision.
1703 filtering the files to be detailed when displaying the revision.
1687 """
1704 """
1688 limit = loglimit(opts)
1705 limit = loglimit(opts)
1689 # Default --rev value depends on --follow but --follow behaviour
1706 # Default --rev value depends on --follow but --follow behaviour
1690 # depends on revisions resolved from --rev...
1707 # depends on revisions resolved from --rev...
1691 follow = opts.get('follow') or opts.get('follow_first')
1708 follow = opts.get('follow') or opts.get('follow_first')
1692 if opts.get('rev'):
1709 if opts.get('rev'):
1693 revs = scmutil.revrange(repo, opts['rev'])
1710 revs = scmutil.revrange(repo, opts['rev'])
1694 elif follow:
1711 elif follow:
1695 revs = revset.baseset(repo.revs('reverse(:.)'))
1712 revs = revset.baseset(repo.revs('reverse(:.)'))
1696 else:
1713 else:
1697 revs = revset.spanset(repo)
1714 revs = revset.spanset(repo)
1698 revs.reverse()
1715 revs.reverse()
1699 if not revs:
1716 if not revs:
1700 return revset.baseset([]), None, None
1717 return revset.baseset([]), None, None
1701 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1718 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1702 if expr:
1719 if expr:
1703 # Revset matchers often operate faster on revisions in changelog
1720 # Revset matchers often operate faster on revisions in changelog
1704 # order, because most filters deal with the changelog.
1721 # order, because most filters deal with the changelog.
1705 if not opts.get('rev'):
1722 if not opts.get('rev'):
1706 revs.reverse()
1723 revs.reverse()
1707 matcher = revset.match(repo.ui, expr)
1724 matcher = revset.match(repo.ui, expr)
1708 # Revset matches can reorder revisions. "A or B" typically returns
1725 # Revset matches can reorder revisions. "A or B" typically returns
1709 # returns the revision matching A then the revision matching B. Sort
1726 # returns the revision matching A then the revision matching B. Sort
1710 # again to fix that.
1727 # again to fix that.
1711 revs = matcher(repo, revs)
1728 revs = matcher(repo, revs)
1712 if not opts.get('rev'):
1729 if not opts.get('rev'):
1713 revs.sort(reverse=True)
1730 revs.sort(reverse=True)
1714 if limit is not None:
1731 if limit is not None:
1715 count = 0
1732 count = 0
1716 limitedrevs = revset.baseset([])
1733 limitedrevs = revset.baseset([])
1717 it = iter(revs)
1734 it = iter(revs)
1718 while count < limit:
1735 while count < limit:
1719 try:
1736 try:
1720 limitedrevs.append(it.next())
1737 limitedrevs.append(it.next())
1721 except (StopIteration):
1738 except (StopIteration):
1722 break
1739 break
1723 count += 1
1740 count += 1
1724 revs = limitedrevs
1741 revs = limitedrevs
1725
1742
1726 return revs, expr, filematcher
1743 return revs, expr, filematcher
1727
1744
1728 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1745 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1729 filematcher=None):
1746 filematcher=None):
1730 seen, state = [], graphmod.asciistate()
1747 seen, state = [], graphmod.asciistate()
1731 for rev, type, ctx, parents in dag:
1748 for rev, type, ctx, parents in dag:
1732 char = 'o'
1749 char = 'o'
1733 if ctx.node() in showparents:
1750 if ctx.node() in showparents:
1734 char = '@'
1751 char = '@'
1735 elif ctx.obsolete():
1752 elif ctx.obsolete():
1736 char = 'x'
1753 char = 'x'
1737 copies = None
1754 copies = None
1738 if getrenamed and ctx.rev():
1755 if getrenamed and ctx.rev():
1739 copies = []
1756 copies = []
1740 for fn in ctx.files():
1757 for fn in ctx.files():
1741 rename = getrenamed(fn, ctx.rev())
1758 rename = getrenamed(fn, ctx.rev())
1742 if rename:
1759 if rename:
1743 copies.append((fn, rename[0]))
1760 copies.append((fn, rename[0]))
1744 revmatchfn = None
1761 revmatchfn = None
1745 if filematcher is not None:
1762 if filematcher is not None:
1746 revmatchfn = filematcher(ctx.rev())
1763 revmatchfn = filematcher(ctx.rev())
1747 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1764 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1748 lines = displayer.hunk.pop(rev).split('\n')
1765 lines = displayer.hunk.pop(rev).split('\n')
1749 if not lines[-1]:
1766 if not lines[-1]:
1750 del lines[-1]
1767 del lines[-1]
1751 displayer.flush(rev)
1768 displayer.flush(rev)
1752 edges = edgefn(type, char, lines, seen, rev, parents)
1769 edges = edgefn(type, char, lines, seen, rev, parents)
1753 for type, char, lines, coldata in edges:
1770 for type, char, lines, coldata in edges:
1754 graphmod.ascii(ui, state, type, char, lines, coldata)
1771 graphmod.ascii(ui, state, type, char, lines, coldata)
1755 displayer.close()
1772 displayer.close()
1756
1773
1757 def graphlog(ui, repo, *pats, **opts):
1774 def graphlog(ui, repo, *pats, **opts):
1758 # Parameters are identical to log command ones
1775 # Parameters are identical to log command ones
1759 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1776 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1760 revdag = graphmod.dagwalker(repo, revs)
1777 revdag = graphmod.dagwalker(repo, revs)
1761
1778
1762 getrenamed = None
1779 getrenamed = None
1763 if opts.get('copies'):
1780 if opts.get('copies'):
1764 endrev = None
1781 endrev = None
1765 if opts.get('rev'):
1782 if opts.get('rev'):
1766 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1783 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1767 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1784 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1768 displayer = show_changeset(ui, repo, opts, buffered=True)
1785 displayer = show_changeset(ui, repo, opts, buffered=True)
1769 showparents = [ctx.node() for ctx in repo[None].parents()]
1786 showparents = [ctx.node() for ctx in repo[None].parents()]
1770 displaygraph(ui, revdag, displayer, showparents,
1787 displaygraph(ui, revdag, displayer, showparents,
1771 graphmod.asciiedges, getrenamed, filematcher)
1788 graphmod.asciiedges, getrenamed, filematcher)
1772
1789
1773 def checkunsupportedgraphflags(pats, opts):
1790 def checkunsupportedgraphflags(pats, opts):
1774 for op in ["newest_first"]:
1791 for op in ["newest_first"]:
1775 if op in opts and opts[op]:
1792 if op in opts and opts[op]:
1776 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1793 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1777 % op.replace("_", "-"))
1794 % op.replace("_", "-"))
1778
1795
1779 def graphrevs(repo, nodes, opts):
1796 def graphrevs(repo, nodes, opts):
1780 limit = loglimit(opts)
1797 limit = loglimit(opts)
1781 nodes.reverse()
1798 nodes.reverse()
1782 if limit is not None:
1799 if limit is not None:
1783 nodes = nodes[:limit]
1800 nodes = nodes[:limit]
1784 return graphmod.nodes(repo, nodes)
1801 return graphmod.nodes(repo, nodes)
1785
1802
1786 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1803 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1787 join = lambda f: os.path.join(prefix, f)
1804 join = lambda f: os.path.join(prefix, f)
1788 bad = []
1805 bad = []
1789 oldbad = match.bad
1806 oldbad = match.bad
1790 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1807 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1791 names = []
1808 names = []
1792 wctx = repo[None]
1809 wctx = repo[None]
1793 cca = None
1810 cca = None
1794 abort, warn = scmutil.checkportabilityalert(ui)
1811 abort, warn = scmutil.checkportabilityalert(ui)
1795 if abort or warn:
1812 if abort or warn:
1796 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1813 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1797 for f in repo.walk(match):
1814 for f in repo.walk(match):
1798 exact = match.exact(f)
1815 exact = match.exact(f)
1799 if exact or not explicitonly and f not in repo.dirstate:
1816 if exact or not explicitonly and f not in repo.dirstate:
1800 if cca:
1817 if cca:
1801 cca(f)
1818 cca(f)
1802 names.append(f)
1819 names.append(f)
1803 if ui.verbose or not exact:
1820 if ui.verbose or not exact:
1804 ui.status(_('adding %s\n') % match.rel(join(f)))
1821 ui.status(_('adding %s\n') % match.rel(join(f)))
1805
1822
1806 for subpath in sorted(wctx.substate):
1823 for subpath in sorted(wctx.substate):
1807 sub = wctx.sub(subpath)
1824 sub = wctx.sub(subpath)
1808 try:
1825 try:
1809 submatch = matchmod.narrowmatcher(subpath, match)
1826 submatch = matchmod.narrowmatcher(subpath, match)
1810 if listsubrepos:
1827 if listsubrepos:
1811 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1828 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1812 False))
1829 False))
1813 else:
1830 else:
1814 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1831 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1815 True))
1832 True))
1816 except error.LookupError:
1833 except error.LookupError:
1817 ui.status(_("skipping missing subrepository: %s\n")
1834 ui.status(_("skipping missing subrepository: %s\n")
1818 % join(subpath))
1835 % join(subpath))
1819
1836
1820 if not dryrun:
1837 if not dryrun:
1821 rejected = wctx.add(names, prefix)
1838 rejected = wctx.add(names, prefix)
1822 bad.extend(f for f in rejected if f in match.files())
1839 bad.extend(f for f in rejected if f in match.files())
1823 return bad
1840 return bad
1824
1841
1825 def forget(ui, repo, match, prefix, explicitonly):
1842 def forget(ui, repo, match, prefix, explicitonly):
1826 join = lambda f: os.path.join(prefix, f)
1843 join = lambda f: os.path.join(prefix, f)
1827 bad = []
1844 bad = []
1828 oldbad = match.bad
1845 oldbad = match.bad
1829 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1846 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1830 wctx = repo[None]
1847 wctx = repo[None]
1831 forgot = []
1848 forgot = []
1832 s = repo.status(match=match, clean=True)
1849 s = repo.status(match=match, clean=True)
1833 forget = sorted(s[0] + s[1] + s[3] + s[6])
1850 forget = sorted(s[0] + s[1] + s[3] + s[6])
1834 if explicitonly:
1851 if explicitonly:
1835 forget = [f for f in forget if match.exact(f)]
1852 forget = [f for f in forget if match.exact(f)]
1836
1853
1837 for subpath in sorted(wctx.substate):
1854 for subpath in sorted(wctx.substate):
1838 sub = wctx.sub(subpath)
1855 sub = wctx.sub(subpath)
1839 try:
1856 try:
1840 submatch = matchmod.narrowmatcher(subpath, match)
1857 submatch = matchmod.narrowmatcher(subpath, match)
1841 subbad, subforgot = sub.forget(ui, submatch, prefix)
1858 subbad, subforgot = sub.forget(ui, submatch, prefix)
1842 bad.extend([subpath + '/' + f for f in subbad])
1859 bad.extend([subpath + '/' + f for f in subbad])
1843 forgot.extend([subpath + '/' + f for f in subforgot])
1860 forgot.extend([subpath + '/' + f for f in subforgot])
1844 except error.LookupError:
1861 except error.LookupError:
1845 ui.status(_("skipping missing subrepository: %s\n")
1862 ui.status(_("skipping missing subrepository: %s\n")
1846 % join(subpath))
1863 % join(subpath))
1847
1864
1848 if not explicitonly:
1865 if not explicitonly:
1849 for f in match.files():
1866 for f in match.files():
1850 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1867 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1851 if f not in forgot:
1868 if f not in forgot:
1852 if os.path.exists(match.rel(join(f))):
1869 if os.path.exists(match.rel(join(f))):
1853 ui.warn(_('not removing %s: '
1870 ui.warn(_('not removing %s: '
1854 'file is already untracked\n')
1871 'file is already untracked\n')
1855 % match.rel(join(f)))
1872 % match.rel(join(f)))
1856 bad.append(f)
1873 bad.append(f)
1857
1874
1858 for f in forget:
1875 for f in forget:
1859 if ui.verbose or not match.exact(f):
1876 if ui.verbose or not match.exact(f):
1860 ui.status(_('removing %s\n') % match.rel(join(f)))
1877 ui.status(_('removing %s\n') % match.rel(join(f)))
1861
1878
1862 rejected = wctx.forget(forget, prefix)
1879 rejected = wctx.forget(forget, prefix)
1863 bad.extend(f for f in rejected if f in match.files())
1880 bad.extend(f for f in rejected if f in match.files())
1864 forgot.extend(forget)
1881 forgot.extend(forget)
1865 return bad, forgot
1882 return bad, forgot
1866
1883
1867 def cat(ui, repo, ctx, matcher, prefix, **opts):
1884 def cat(ui, repo, ctx, matcher, prefix, **opts):
1868 err = 1
1885 err = 1
1869
1886
1870 def write(path):
1887 def write(path):
1871 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1888 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1872 pathname=os.path.join(prefix, path))
1889 pathname=os.path.join(prefix, path))
1873 data = ctx[path].data()
1890 data = ctx[path].data()
1874 if opts.get('decode'):
1891 if opts.get('decode'):
1875 data = repo.wwritedata(path, data)
1892 data = repo.wwritedata(path, data)
1876 fp.write(data)
1893 fp.write(data)
1877 fp.close()
1894 fp.close()
1878
1895
1879 # Automation often uses hg cat on single files, so special case it
1896 # Automation often uses hg cat on single files, so special case it
1880 # for performance to avoid the cost of parsing the manifest.
1897 # for performance to avoid the cost of parsing the manifest.
1881 if len(matcher.files()) == 1 and not matcher.anypats():
1898 if len(matcher.files()) == 1 and not matcher.anypats():
1882 file = matcher.files()[0]
1899 file = matcher.files()[0]
1883 mf = repo.manifest
1900 mf = repo.manifest
1884 mfnode = ctx._changeset[0]
1901 mfnode = ctx._changeset[0]
1885 if mf.find(mfnode, file)[0]:
1902 if mf.find(mfnode, file)[0]:
1886 write(file)
1903 write(file)
1887 return 0
1904 return 0
1888
1905
1889 # Don't warn about "missing" files that are really in subrepos
1906 # Don't warn about "missing" files that are really in subrepos
1890 bad = matcher.bad
1907 bad = matcher.bad
1891
1908
1892 def badfn(path, msg):
1909 def badfn(path, msg):
1893 for subpath in ctx.substate:
1910 for subpath in ctx.substate:
1894 if path.startswith(subpath):
1911 if path.startswith(subpath):
1895 return
1912 return
1896 bad(path, msg)
1913 bad(path, msg)
1897
1914
1898 matcher.bad = badfn
1915 matcher.bad = badfn
1899
1916
1900 for abs in ctx.walk(matcher):
1917 for abs in ctx.walk(matcher):
1901 write(abs)
1918 write(abs)
1902 err = 0
1919 err = 0
1903
1920
1904 matcher.bad = bad
1921 matcher.bad = bad
1905
1922
1906 for subpath in sorted(ctx.substate):
1923 for subpath in sorted(ctx.substate):
1907 sub = ctx.sub(subpath)
1924 sub = ctx.sub(subpath)
1908 try:
1925 try:
1909 submatch = matchmod.narrowmatcher(subpath, matcher)
1926 submatch = matchmod.narrowmatcher(subpath, matcher)
1910
1927
1911 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1928 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1912 **opts):
1929 **opts):
1913 err = 0
1930 err = 0
1914 except error.RepoLookupError:
1931 except error.RepoLookupError:
1915 ui.status(_("skipping missing subrepository: %s\n")
1932 ui.status(_("skipping missing subrepository: %s\n")
1916 % os.path.join(prefix, subpath))
1933 % os.path.join(prefix, subpath))
1917
1934
1918 return err
1935 return err
1919
1936
1920 def duplicatecopies(repo, rev, fromrev):
1937 def duplicatecopies(repo, rev, fromrev):
1921 '''reproduce copies from fromrev to rev in the dirstate'''
1938 '''reproduce copies from fromrev to rev in the dirstate'''
1922 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1939 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1923 # copies.pathcopies returns backward renames, so dst might not
1940 # copies.pathcopies returns backward renames, so dst might not
1924 # actually be in the dirstate
1941 # actually be in the dirstate
1925 if repo.dirstate[dst] in "nma":
1942 if repo.dirstate[dst] in "nma":
1926 repo.dirstate.copy(src, dst)
1943 repo.dirstate.copy(src, dst)
1927
1944
1928 def commit(ui, repo, commitfunc, pats, opts):
1945 def commit(ui, repo, commitfunc, pats, opts):
1929 '''commit the specified files or all outstanding changes'''
1946 '''commit the specified files or all outstanding changes'''
1930 date = opts.get('date')
1947 date = opts.get('date')
1931 if date:
1948 if date:
1932 opts['date'] = util.parsedate(date)
1949 opts['date'] = util.parsedate(date)
1933 message = logmessage(ui, opts)
1950 message = logmessage(ui, opts)
1934
1951
1935 # extract addremove carefully -- this function can be called from a command
1952 # extract addremove carefully -- this function can be called from a command
1936 # that doesn't support addremove
1953 # that doesn't support addremove
1937 if opts.get('addremove'):
1954 if opts.get('addremove'):
1938 scmutil.addremove(repo, pats, opts)
1955 scmutil.addremove(repo, pats, opts)
1939
1956
1940 return commitfunc(ui, repo, message,
1957 return commitfunc(ui, repo, message,
1941 scmutil.match(repo[None], pats, opts), opts)
1958 scmutil.match(repo[None], pats, opts), opts)
1942
1959
1943 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1960 def amend(ui, repo, commitfunc, old, extra, pats, opts):
1944 ui.note(_('amending changeset %s\n') % old)
1961 ui.note(_('amending changeset %s\n') % old)
1945 base = old.p1()
1962 base = old.p1()
1946
1963
1947 wlock = lock = newid = None
1964 wlock = lock = newid = None
1948 try:
1965 try:
1949 wlock = repo.wlock()
1966 wlock = repo.wlock()
1950 lock = repo.lock()
1967 lock = repo.lock()
1951 tr = repo.transaction('amend')
1968 tr = repo.transaction('amend')
1952 try:
1969 try:
1953 # See if we got a message from -m or -l, if not, open the editor
1970 # See if we got a message from -m or -l, if not, open the editor
1954 # with the message of the changeset to amend
1971 # with the message of the changeset to amend
1955 message = logmessage(ui, opts)
1972 message = logmessage(ui, opts)
1956 # ensure logfile does not conflict with later enforcement of the
1973 # ensure logfile does not conflict with later enforcement of the
1957 # message. potential logfile content has been processed by
1974 # message. potential logfile content has been processed by
1958 # `logmessage` anyway.
1975 # `logmessage` anyway.
1959 opts.pop('logfile')
1976 opts.pop('logfile')
1960 # First, do a regular commit to record all changes in the working
1977 # First, do a regular commit to record all changes in the working
1961 # directory (if there are any)
1978 # directory (if there are any)
1962 ui.callhooks = False
1979 ui.callhooks = False
1963 currentbookmark = repo._bookmarkcurrent
1980 currentbookmark = repo._bookmarkcurrent
1964 try:
1981 try:
1965 repo._bookmarkcurrent = None
1982 repo._bookmarkcurrent = None
1966 opts['message'] = 'temporary amend commit for %s' % old
1983 opts['message'] = 'temporary amend commit for %s' % old
1967 node = commit(ui, repo, commitfunc, pats, opts)
1984 node = commit(ui, repo, commitfunc, pats, opts)
1968 finally:
1985 finally:
1969 repo._bookmarkcurrent = currentbookmark
1986 repo._bookmarkcurrent = currentbookmark
1970 ui.callhooks = True
1987 ui.callhooks = True
1971 ctx = repo[node]
1988 ctx = repo[node]
1972
1989
1973 # Participating changesets:
1990 # Participating changesets:
1974 #
1991 #
1975 # node/ctx o - new (intermediate) commit that contains changes
1992 # node/ctx o - new (intermediate) commit that contains changes
1976 # | from working dir to go into amending commit
1993 # | from working dir to go into amending commit
1977 # | (or a workingctx if there were no changes)
1994 # | (or a workingctx if there were no changes)
1978 # |
1995 # |
1979 # old o - changeset to amend
1996 # old o - changeset to amend
1980 # |
1997 # |
1981 # base o - parent of amending changeset
1998 # base o - parent of amending changeset
1982
1999
1983 # Update extra dict from amended commit (e.g. to preserve graft
2000 # Update extra dict from amended commit (e.g. to preserve graft
1984 # source)
2001 # source)
1985 extra.update(old.extra())
2002 extra.update(old.extra())
1986
2003
1987 # Also update it from the intermediate commit or from the wctx
2004 # Also update it from the intermediate commit or from the wctx
1988 extra.update(ctx.extra())
2005 extra.update(ctx.extra())
1989
2006
1990 if len(old.parents()) > 1:
2007 if len(old.parents()) > 1:
1991 # ctx.files() isn't reliable for merges, so fall back to the
2008 # ctx.files() isn't reliable for merges, so fall back to the
1992 # slower repo.status() method
2009 # slower repo.status() method
1993 files = set([fn for st in repo.status(base, old)[:3]
2010 files = set([fn for st in repo.status(base, old)[:3]
1994 for fn in st])
2011 for fn in st])
1995 else:
2012 else:
1996 files = set(old.files())
2013 files = set(old.files())
1997
2014
1998 # Second, we use either the commit we just did, or if there were no
2015 # Second, we use either the commit we just did, or if there were no
1999 # changes the parent of the working directory as the version of the
2016 # changes the parent of the working directory as the version of the
2000 # files in the final amend commit
2017 # files in the final amend commit
2001 if node:
2018 if node:
2002 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2019 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2003
2020
2004 user = ctx.user()
2021 user = ctx.user()
2005 date = ctx.date()
2022 date = ctx.date()
2006 # Recompute copies (avoid recording a -> b -> a)
2023 # Recompute copies (avoid recording a -> b -> a)
2007 copied = copies.pathcopies(base, ctx)
2024 copied = copies.pathcopies(base, ctx)
2008
2025
2009 # Prune files which were reverted by the updates: if old
2026 # Prune files which were reverted by the updates: if old
2010 # introduced file X and our intermediate commit, node,
2027 # introduced file X and our intermediate commit, node,
2011 # renamed that file, then those two files are the same and
2028 # renamed that file, then those two files are the same and
2012 # we can discard X from our list of files. Likewise if X
2029 # we can discard X from our list of files. Likewise if X
2013 # was deleted, it's no longer relevant
2030 # was deleted, it's no longer relevant
2014 files.update(ctx.files())
2031 files.update(ctx.files())
2015
2032
2016 def samefile(f):
2033 def samefile(f):
2017 if f in ctx.manifest():
2034 if f in ctx.manifest():
2018 a = ctx.filectx(f)
2035 a = ctx.filectx(f)
2019 if f in base.manifest():
2036 if f in base.manifest():
2020 b = base.filectx(f)
2037 b = base.filectx(f)
2021 return (not a.cmp(b)
2038 return (not a.cmp(b)
2022 and a.flags() == b.flags())
2039 and a.flags() == b.flags())
2023 else:
2040 else:
2024 return False
2041 return False
2025 else:
2042 else:
2026 return f not in base.manifest()
2043 return f not in base.manifest()
2027 files = [f for f in files if not samefile(f)]
2044 files = [f for f in files if not samefile(f)]
2028
2045
2029 def filectxfn(repo, ctx_, path):
2046 def filectxfn(repo, ctx_, path):
2030 try:
2047 try:
2031 fctx = ctx[path]
2048 fctx = ctx[path]
2032 flags = fctx.flags()
2049 flags = fctx.flags()
2033 mctx = context.memfilectx(fctx.path(), fctx.data(),
2050 mctx = context.memfilectx(fctx.path(), fctx.data(),
2034 islink='l' in flags,
2051 islink='l' in flags,
2035 isexec='x' in flags,
2052 isexec='x' in flags,
2036 copied=copied.get(path))
2053 copied=copied.get(path))
2037 return mctx
2054 return mctx
2038 except KeyError:
2055 except KeyError:
2039 raise IOError
2056 raise IOError
2040 else:
2057 else:
2041 ui.note(_('copying changeset %s to %s\n') % (old, base))
2058 ui.note(_('copying changeset %s to %s\n') % (old, base))
2042
2059
2043 # Use version of files as in the old cset
2060 # Use version of files as in the old cset
2044 def filectxfn(repo, ctx_, path):
2061 def filectxfn(repo, ctx_, path):
2045 try:
2062 try:
2046 return old.filectx(path)
2063 return old.filectx(path)
2047 except KeyError:
2064 except KeyError:
2048 raise IOError
2065 raise IOError
2049
2066
2050 user = opts.get('user') or old.user()
2067 user = opts.get('user') or old.user()
2051 date = opts.get('date') or old.date()
2068 date = opts.get('date') or old.date()
2052 editor = getcommiteditor(**opts)
2069 editor = getcommiteditor(**opts)
2053 if not message:
2070 if not message:
2054 editor = getcommiteditor(edit=True)
2071 editor = getcommiteditor(edit=True)
2055 message = old.description()
2072 message = old.description()
2056
2073
2057 pureextra = extra.copy()
2074 pureextra = extra.copy()
2058 extra['amend_source'] = old.hex()
2075 extra['amend_source'] = old.hex()
2059
2076
2060 new = context.memctx(repo,
2077 new = context.memctx(repo,
2061 parents=[base.node(), old.p2().node()],
2078 parents=[base.node(), old.p2().node()],
2062 text=message,
2079 text=message,
2063 files=files,
2080 files=files,
2064 filectxfn=filectxfn,
2081 filectxfn=filectxfn,
2065 user=user,
2082 user=user,
2066 date=date,
2083 date=date,
2067 extra=extra,
2084 extra=extra,
2068 editor=editor)
2085 editor=editor)
2069
2086
2070 newdesc = changelog.stripdesc(new.description())
2087 newdesc = changelog.stripdesc(new.description())
2071 if ((not node)
2088 if ((not node)
2072 and newdesc == old.description()
2089 and newdesc == old.description()
2073 and user == old.user()
2090 and user == old.user()
2074 and date == old.date()
2091 and date == old.date()
2075 and pureextra == old.extra()):
2092 and pureextra == old.extra()):
2076 # nothing changed. continuing here would create a new node
2093 # nothing changed. continuing here would create a new node
2077 # anyway because of the amend_source noise.
2094 # anyway because of the amend_source noise.
2078 #
2095 #
2079 # This not what we expect from amend.
2096 # This not what we expect from amend.
2080 return old.node()
2097 return old.node()
2081
2098
2082 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2099 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2083 try:
2100 try:
2084 if opts.get('secret'):
2101 if opts.get('secret'):
2085 commitphase = 'secret'
2102 commitphase = 'secret'
2086 else:
2103 else:
2087 commitphase = old.phase()
2104 commitphase = old.phase()
2088 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2105 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2089 newid = repo.commitctx(new)
2106 newid = repo.commitctx(new)
2090 finally:
2107 finally:
2091 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2108 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2092 if newid != old.node():
2109 if newid != old.node():
2093 # Reroute the working copy parent to the new changeset
2110 # Reroute the working copy parent to the new changeset
2094 repo.setparents(newid, nullid)
2111 repo.setparents(newid, nullid)
2095
2112
2096 # Move bookmarks from old parent to amend commit
2113 # Move bookmarks from old parent to amend commit
2097 bms = repo.nodebookmarks(old.node())
2114 bms = repo.nodebookmarks(old.node())
2098 if bms:
2115 if bms:
2099 marks = repo._bookmarks
2116 marks = repo._bookmarks
2100 for bm in bms:
2117 for bm in bms:
2101 marks[bm] = newid
2118 marks[bm] = newid
2102 marks.write()
2119 marks.write()
2103 #commit the whole amend process
2120 #commit the whole amend process
2104 if obsolete._enabled and newid != old.node():
2121 if obsolete._enabled and newid != old.node():
2105 # mark the new changeset as successor of the rewritten one
2122 # mark the new changeset as successor of the rewritten one
2106 new = repo[newid]
2123 new = repo[newid]
2107 obs = [(old, (new,))]
2124 obs = [(old, (new,))]
2108 if node:
2125 if node:
2109 obs.append((ctx, ()))
2126 obs.append((ctx, ()))
2110
2127
2111 obsolete.createmarkers(repo, obs)
2128 obsolete.createmarkers(repo, obs)
2112 tr.close()
2129 tr.close()
2113 finally:
2130 finally:
2114 tr.release()
2131 tr.release()
2115 if (not obsolete._enabled) and newid != old.node():
2132 if (not obsolete._enabled) and newid != old.node():
2116 # Strip the intermediate commit (if there was one) and the amended
2133 # Strip the intermediate commit (if there was one) and the amended
2117 # commit
2134 # commit
2118 if node:
2135 if node:
2119 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2136 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2120 ui.note(_('stripping amended changeset %s\n') % old)
2137 ui.note(_('stripping amended changeset %s\n') % old)
2121 repair.strip(ui, repo, old.node(), topic='amend-backup')
2138 repair.strip(ui, repo, old.node(), topic='amend-backup')
2122 finally:
2139 finally:
2123 if newid is None:
2140 if newid is None:
2124 repo.dirstate.invalidate()
2141 repo.dirstate.invalidate()
2125 lockmod.release(lock, wlock)
2142 lockmod.release(lock, wlock)
2126 return newid
2143 return newid
2127
2144
2128 def commiteditor(repo, ctx, subs):
2145 def commiteditor(repo, ctx, subs):
2129 if ctx.description():
2146 if ctx.description():
2130 return ctx.description()
2147 return ctx.description()
2131 return commitforceeditor(repo, ctx, subs)
2148 return commitforceeditor(repo, ctx, subs)
2132
2149
2133 def commitforceeditor(repo, ctx, subs):
2150 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None):
2134 edittext = []
2151 edittext = []
2135 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2152 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2136 if ctx.description():
2153 if ctx.description():
2137 edittext.append(ctx.description())
2154 edittext.append(ctx.description())
2138 edittext.append("")
2155 edittext.append("")
2139 edittext.append("") # Empty line between message and comments.
2156 edittext.append("") # Empty line between message and comments.
2140 edittext.append(_("HG: Enter commit message."
2157 edittext.append(_("HG: Enter commit message."
2141 " Lines beginning with 'HG:' are removed."))
2158 " Lines beginning with 'HG:' are removed."))
2142 edittext.append(_("HG: Leave message empty to abort commit."))
2159 if extramsg:
2160 edittext.append("HG: %s" % extramsg)
2161 else:
2162 edittext.append(_("HG: Leave message empty to abort commit."))
2143 edittext.append("HG: --")
2163 edittext.append("HG: --")
2144 edittext.append(_("HG: user: %s") % ctx.user())
2164 edittext.append(_("HG: user: %s") % ctx.user())
2145 if ctx.p2():
2165 if ctx.p2():
2146 edittext.append(_("HG: branch merge"))
2166 edittext.append(_("HG: branch merge"))
2147 if ctx.branch():
2167 if ctx.branch():
2148 edittext.append(_("HG: branch '%s'") % ctx.branch())
2168 edittext.append(_("HG: branch '%s'") % ctx.branch())
2149 if bookmarks.iscurrent(repo):
2169 if bookmarks.iscurrent(repo):
2150 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2170 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2151 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2171 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2152 edittext.extend([_("HG: added %s") % f for f in added])
2172 edittext.extend([_("HG: added %s") % f for f in added])
2153 edittext.extend([_("HG: changed %s") % f for f in modified])
2173 edittext.extend([_("HG: changed %s") % f for f in modified])
2154 edittext.extend([_("HG: removed %s") % f for f in removed])
2174 edittext.extend([_("HG: removed %s") % f for f in removed])
2155 if not added and not modified and not removed:
2175 if not added and not modified and not removed:
2156 edittext.append(_("HG: no files changed"))
2176 edittext.append(_("HG: no files changed"))
2157 edittext.append("")
2177 edittext.append("")
2158 # run editor in the repository root
2178 # run editor in the repository root
2159 olddir = os.getcwd()
2179 olddir = os.getcwd()
2160 os.chdir(repo.root)
2180 os.chdir(repo.root)
2161 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2181 text = repo.ui.edit("\n".join(edittext), ctx.user(), ctx.extra())
2162 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2182 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2163 os.chdir(olddir)
2183 os.chdir(olddir)
2164
2184
2185 if finishdesc:
2186 text = finishdesc(text)
2165 if not text.strip():
2187 if not text.strip():
2166 raise util.Abort(_("empty commit message"))
2188 raise util.Abort(_("empty commit message"))
2167
2189
2168 return text
2190 return text
2169
2191
2170 def commitstatus(repo, node, branch, bheads=None, opts={}):
2192 def commitstatus(repo, node, branch, bheads=None, opts={}):
2171 ctx = repo[node]
2193 ctx = repo[node]
2172 parents = ctx.parents()
2194 parents = ctx.parents()
2173
2195
2174 if (not opts.get('amend') and bheads and node not in bheads and not
2196 if (not opts.get('amend') and bheads and node not in bheads and not
2175 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2197 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2176 repo.ui.status(_('created new head\n'))
2198 repo.ui.status(_('created new head\n'))
2177 # The message is not printed for initial roots. For the other
2199 # The message is not printed for initial roots. For the other
2178 # changesets, it is printed in the following situations:
2200 # changesets, it is printed in the following situations:
2179 #
2201 #
2180 # Par column: for the 2 parents with ...
2202 # Par column: for the 2 parents with ...
2181 # N: null or no parent
2203 # N: null or no parent
2182 # B: parent is on another named branch
2204 # B: parent is on another named branch
2183 # C: parent is a regular non head changeset
2205 # C: parent is a regular non head changeset
2184 # H: parent was a branch head of the current branch
2206 # H: parent was a branch head of the current branch
2185 # Msg column: whether we print "created new head" message
2207 # Msg column: whether we print "created new head" message
2186 # In the following, it is assumed that there already exists some
2208 # In the following, it is assumed that there already exists some
2187 # initial branch heads of the current branch, otherwise nothing is
2209 # initial branch heads of the current branch, otherwise nothing is
2188 # printed anyway.
2210 # printed anyway.
2189 #
2211 #
2190 # Par Msg Comment
2212 # Par Msg Comment
2191 # N N y additional topo root
2213 # N N y additional topo root
2192 #
2214 #
2193 # B N y additional branch root
2215 # B N y additional branch root
2194 # C N y additional topo head
2216 # C N y additional topo head
2195 # H N n usual case
2217 # H N n usual case
2196 #
2218 #
2197 # B B y weird additional branch root
2219 # B B y weird additional branch root
2198 # C B y branch merge
2220 # C B y branch merge
2199 # H B n merge with named branch
2221 # H B n merge with named branch
2200 #
2222 #
2201 # C C y additional head from merge
2223 # C C y additional head from merge
2202 # C H n merge with a head
2224 # C H n merge with a head
2203 #
2225 #
2204 # H H n head merge: head count decreases
2226 # H H n head merge: head count decreases
2205
2227
2206 if not opts.get('close_branch'):
2228 if not opts.get('close_branch'):
2207 for r in parents:
2229 for r in parents:
2208 if r.closesbranch() and r.branch() == branch:
2230 if r.closesbranch() and r.branch() == branch:
2209 repo.ui.status(_('reopening closed branch head %d\n') % r)
2231 repo.ui.status(_('reopening closed branch head %d\n') % r)
2210
2232
2211 if repo.ui.debugflag:
2233 if repo.ui.debugflag:
2212 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2234 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2213 elif repo.ui.verbose:
2235 elif repo.ui.verbose:
2214 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2236 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2215
2237
2216 def revert(ui, repo, ctx, parents, *pats, **opts):
2238 def revert(ui, repo, ctx, parents, *pats, **opts):
2217 parent, p2 = parents
2239 parent, p2 = parents
2218 node = ctx.node()
2240 node = ctx.node()
2219
2241
2220 mf = ctx.manifest()
2242 mf = ctx.manifest()
2221 if node == parent:
2243 if node == parent:
2222 pmf = mf
2244 pmf = mf
2223 else:
2245 else:
2224 pmf = None
2246 pmf = None
2225
2247
2226 # need all matching names in dirstate and manifest of target rev,
2248 # need all matching names in dirstate and manifest of target rev,
2227 # so have to walk both. do not print errors if files exist in one
2249 # so have to walk both. do not print errors if files exist in one
2228 # but not other.
2250 # but not other.
2229
2251
2230 names = {}
2252 names = {}
2231
2253
2232 wlock = repo.wlock()
2254 wlock = repo.wlock()
2233 try:
2255 try:
2234 # walk dirstate.
2256 # walk dirstate.
2235
2257
2236 m = scmutil.match(repo[None], pats, opts)
2258 m = scmutil.match(repo[None], pats, opts)
2237 m.bad = lambda x, y: False
2259 m.bad = lambda x, y: False
2238 for abs in repo.walk(m):
2260 for abs in repo.walk(m):
2239 names[abs] = m.rel(abs), m.exact(abs)
2261 names[abs] = m.rel(abs), m.exact(abs)
2240
2262
2241 # walk target manifest.
2263 # walk target manifest.
2242
2264
2243 def badfn(path, msg):
2265 def badfn(path, msg):
2244 if path in names:
2266 if path in names:
2245 return
2267 return
2246 if path in ctx.substate:
2268 if path in ctx.substate:
2247 return
2269 return
2248 path_ = path + '/'
2270 path_ = path + '/'
2249 for f in names:
2271 for f in names:
2250 if f.startswith(path_):
2272 if f.startswith(path_):
2251 return
2273 return
2252 ui.warn("%s: %s\n" % (m.rel(path), msg))
2274 ui.warn("%s: %s\n" % (m.rel(path), msg))
2253
2275
2254 m = scmutil.match(ctx, pats, opts)
2276 m = scmutil.match(ctx, pats, opts)
2255 m.bad = badfn
2277 m.bad = badfn
2256 for abs in ctx.walk(m):
2278 for abs in ctx.walk(m):
2257 if abs not in names:
2279 if abs not in names:
2258 names[abs] = m.rel(abs), m.exact(abs)
2280 names[abs] = m.rel(abs), m.exact(abs)
2259
2281
2260 # get the list of subrepos that must be reverted
2282 # get the list of subrepos that must be reverted
2261 targetsubs = sorted(s for s in ctx.substate if m(s))
2283 targetsubs = sorted(s for s in ctx.substate if m(s))
2262 m = scmutil.matchfiles(repo, names)
2284 m = scmutil.matchfiles(repo, names)
2263 changes = repo.status(match=m)[:4]
2285 changes = repo.status(match=m)[:4]
2264 modified, added, removed, deleted = map(set, changes)
2286 modified, added, removed, deleted = map(set, changes)
2265
2287
2266 # if f is a rename, also revert the source
2288 # if f is a rename, also revert the source
2267 cwd = repo.getcwd()
2289 cwd = repo.getcwd()
2268 for f in added:
2290 for f in added:
2269 src = repo.dirstate.copied(f)
2291 src = repo.dirstate.copied(f)
2270 if src and src not in names and repo.dirstate[src] == 'r':
2292 if src and src not in names and repo.dirstate[src] == 'r':
2271 removed.add(src)
2293 removed.add(src)
2272 names[src] = (repo.pathto(src, cwd), True)
2294 names[src] = (repo.pathto(src, cwd), True)
2273
2295
2274 def removeforget(abs):
2296 def removeforget(abs):
2275 if repo.dirstate[abs] == 'a':
2297 if repo.dirstate[abs] == 'a':
2276 return _('forgetting %s\n')
2298 return _('forgetting %s\n')
2277 return _('removing %s\n')
2299 return _('removing %s\n')
2278
2300
2279 revert = ([], _('reverting %s\n'))
2301 revert = ([], _('reverting %s\n'))
2280 add = ([], _('adding %s\n'))
2302 add = ([], _('adding %s\n'))
2281 remove = ([], removeforget)
2303 remove = ([], removeforget)
2282 undelete = ([], _('undeleting %s\n'))
2304 undelete = ([], _('undeleting %s\n'))
2283
2305
2284 disptable = (
2306 disptable = (
2285 # dispatch table:
2307 # dispatch table:
2286 # file state
2308 # file state
2287 # action if in target manifest
2309 # action if in target manifest
2288 # action if not in target manifest
2310 # action if not in target manifest
2289 # make backup if in target manifest
2311 # make backup if in target manifest
2290 # make backup if not in target manifest
2312 # make backup if not in target manifest
2291 (modified, revert, remove, True, True),
2313 (modified, revert, remove, True, True),
2292 (added, revert, remove, True, False),
2314 (added, revert, remove, True, False),
2293 (removed, undelete, None, True, False),
2315 (removed, undelete, None, True, False),
2294 (deleted, revert, remove, False, False),
2316 (deleted, revert, remove, False, False),
2295 )
2317 )
2296
2318
2297 for abs, (rel, exact) in sorted(names.items()):
2319 for abs, (rel, exact) in sorted(names.items()):
2298 mfentry = mf.get(abs)
2320 mfentry = mf.get(abs)
2299 target = repo.wjoin(abs)
2321 target = repo.wjoin(abs)
2300 def handle(xlist, dobackup):
2322 def handle(xlist, dobackup):
2301 xlist[0].append(abs)
2323 xlist[0].append(abs)
2302 if (dobackup and not opts.get('no_backup') and
2324 if (dobackup and not opts.get('no_backup') and
2303 os.path.lexists(target) and
2325 os.path.lexists(target) and
2304 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2326 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2305 bakname = "%s.orig" % rel
2327 bakname = "%s.orig" % rel
2306 ui.note(_('saving current version of %s as %s\n') %
2328 ui.note(_('saving current version of %s as %s\n') %
2307 (rel, bakname))
2329 (rel, bakname))
2308 if not opts.get('dry_run'):
2330 if not opts.get('dry_run'):
2309 util.rename(target, bakname)
2331 util.rename(target, bakname)
2310 if ui.verbose or not exact:
2332 if ui.verbose or not exact:
2311 msg = xlist[1]
2333 msg = xlist[1]
2312 if not isinstance(msg, basestring):
2334 if not isinstance(msg, basestring):
2313 msg = msg(abs)
2335 msg = msg(abs)
2314 ui.status(msg % rel)
2336 ui.status(msg % rel)
2315 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2337 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2316 if abs not in table:
2338 if abs not in table:
2317 continue
2339 continue
2318 # file has changed in dirstate
2340 # file has changed in dirstate
2319 if mfentry:
2341 if mfentry:
2320 handle(hitlist, backuphit)
2342 handle(hitlist, backuphit)
2321 elif misslist is not None:
2343 elif misslist is not None:
2322 handle(misslist, backupmiss)
2344 handle(misslist, backupmiss)
2323 break
2345 break
2324 else:
2346 else:
2325 if abs not in repo.dirstate:
2347 if abs not in repo.dirstate:
2326 if mfentry:
2348 if mfentry:
2327 handle(add, True)
2349 handle(add, True)
2328 elif exact:
2350 elif exact:
2329 ui.warn(_('file not managed: %s\n') % rel)
2351 ui.warn(_('file not managed: %s\n') % rel)
2330 continue
2352 continue
2331 # file has not changed in dirstate
2353 # file has not changed in dirstate
2332 if node == parent:
2354 if node == parent:
2333 if exact:
2355 if exact:
2334 ui.warn(_('no changes needed to %s\n') % rel)
2356 ui.warn(_('no changes needed to %s\n') % rel)
2335 continue
2357 continue
2336 if pmf is None:
2358 if pmf is None:
2337 # only need parent manifest in this unlikely case,
2359 # only need parent manifest in this unlikely case,
2338 # so do not read by default
2360 # so do not read by default
2339 pmf = repo[parent].manifest()
2361 pmf = repo[parent].manifest()
2340 if abs in pmf and mfentry:
2362 if abs in pmf and mfentry:
2341 # if version of file is same in parent and target
2363 # if version of file is same in parent and target
2342 # manifests, do nothing
2364 # manifests, do nothing
2343 if (pmf[abs] != mfentry or
2365 if (pmf[abs] != mfentry or
2344 pmf.flags(abs) != mf.flags(abs)):
2366 pmf.flags(abs) != mf.flags(abs)):
2345 handle(revert, False)
2367 handle(revert, False)
2346 else:
2368 else:
2347 handle(remove, False)
2369 handle(remove, False)
2348 if not opts.get('dry_run'):
2370 if not opts.get('dry_run'):
2349 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2371 _performrevert(repo, parents, ctx, revert, add, remove, undelete)
2350
2372
2351 if targetsubs:
2373 if targetsubs:
2352 # Revert the subrepos on the revert list
2374 # Revert the subrepos on the revert list
2353 for sub in targetsubs:
2375 for sub in targetsubs:
2354 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2376 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2355 finally:
2377 finally:
2356 wlock.release()
2378 wlock.release()
2357
2379
2358 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2380 def _performrevert(repo, parents, ctx, revert, add, remove, undelete):
2359 """function that actually perform all the action computed for revert
2381 """function that actually perform all the action computed for revert
2360
2382
2361 This is an independent function to let extension to plug in and react to
2383 This is an independent function to let extension to plug in and react to
2362 the imminent revert.
2384 the imminent revert.
2363
2385
2364 Make sure you have the working directory locked when calling this function.
2386 Make sure you have the working directory locked when calling this function.
2365 """
2387 """
2366 parent, p2 = parents
2388 parent, p2 = parents
2367 node = ctx.node()
2389 node = ctx.node()
2368 def checkout(f):
2390 def checkout(f):
2369 fc = ctx[f]
2391 fc = ctx[f]
2370 repo.wwrite(f, fc.data(), fc.flags())
2392 repo.wwrite(f, fc.data(), fc.flags())
2371
2393
2372 audit_path = pathutil.pathauditor(repo.root)
2394 audit_path = pathutil.pathauditor(repo.root)
2373 for f in remove[0]:
2395 for f in remove[0]:
2374 if repo.dirstate[f] == 'a':
2396 if repo.dirstate[f] == 'a':
2375 repo.dirstate.drop(f)
2397 repo.dirstate.drop(f)
2376 continue
2398 continue
2377 audit_path(f)
2399 audit_path(f)
2378 try:
2400 try:
2379 util.unlinkpath(repo.wjoin(f))
2401 util.unlinkpath(repo.wjoin(f))
2380 except OSError:
2402 except OSError:
2381 pass
2403 pass
2382 repo.dirstate.remove(f)
2404 repo.dirstate.remove(f)
2383
2405
2384 normal = None
2406 normal = None
2385 if node == parent:
2407 if node == parent:
2386 # We're reverting to our parent. If possible, we'd like status
2408 # We're reverting to our parent. If possible, we'd like status
2387 # to report the file as clean. We have to use normallookup for
2409 # to report the file as clean. We have to use normallookup for
2388 # merges to avoid losing information about merged/dirty files.
2410 # merges to avoid losing information about merged/dirty files.
2389 if p2 != nullid:
2411 if p2 != nullid:
2390 normal = repo.dirstate.normallookup
2412 normal = repo.dirstate.normallookup
2391 else:
2413 else:
2392 normal = repo.dirstate.normal
2414 normal = repo.dirstate.normal
2393 for f in revert[0]:
2415 for f in revert[0]:
2394 checkout(f)
2416 checkout(f)
2395 if normal:
2417 if normal:
2396 normal(f)
2418 normal(f)
2397
2419
2398 for f in add[0]:
2420 for f in add[0]:
2399 checkout(f)
2421 checkout(f)
2400 repo.dirstate.add(f)
2422 repo.dirstate.add(f)
2401
2423
2402 normal = repo.dirstate.normallookup
2424 normal = repo.dirstate.normallookup
2403 if node == parent and p2 == nullid:
2425 if node == parent and p2 == nullid:
2404 normal = repo.dirstate.normal
2426 normal = repo.dirstate.normal
2405 for f in undelete[0]:
2427 for f in undelete[0]:
2406 checkout(f)
2428 checkout(f)
2407 normal(f)
2429 normal(f)
2408
2430
2409 copied = copies.pathcopies(repo[parent], ctx)
2431 copied = copies.pathcopies(repo[parent], ctx)
2410
2432
2411 for f in add[0] + undelete[0] + revert[0]:
2433 for f in add[0] + undelete[0] + revert[0]:
2412 if f in copied:
2434 if f in copied:
2413 repo.dirstate.copy(copied[f], f)
2435 repo.dirstate.copy(copied[f], f)
2414
2436
2415 def command(table):
2437 def command(table):
2416 '''returns a function object bound to table which can be used as
2438 '''returns a function object bound to table which can be used as
2417 a decorator for populating table as a command table'''
2439 a decorator for populating table as a command table'''
2418
2440
2419 def cmd(name, options=(), synopsis=None):
2441 def cmd(name, options=(), synopsis=None):
2420 def decorator(func):
2442 def decorator(func):
2421 if synopsis:
2443 if synopsis:
2422 table[name] = func, list(options), synopsis
2444 table[name] = func, list(options), synopsis
2423 else:
2445 else:
2424 table[name] = func, list(options)
2446 table[name] = func, list(options)
2425 return func
2447 return func
2426 return decorator
2448 return decorator
2427
2449
2428 return cmd
2450 return cmd
2429
2451
2430 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2452 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2431 # commands.outgoing. "missing" is "missing" of the result of
2453 # commands.outgoing. "missing" is "missing" of the result of
2432 # "findcommonoutgoing()"
2454 # "findcommonoutgoing()"
2433 outgoinghooks = util.hooks()
2455 outgoinghooks = util.hooks()
2434
2456
2435 # a list of (ui, repo) functions called by commands.summary
2457 # a list of (ui, repo) functions called by commands.summary
2436 summaryhooks = util.hooks()
2458 summaryhooks = util.hooks()
2437
2459
2438 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2460 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2439 #
2461 #
2440 # functions should return tuple of booleans below, if 'changes' is None:
2462 # functions should return tuple of booleans below, if 'changes' is None:
2441 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2463 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2442 #
2464 #
2443 # otherwise, 'changes' is a tuple of tuples below:
2465 # otherwise, 'changes' is a tuple of tuples below:
2444 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2466 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2445 # - (desturl, destbranch, destpeer, outgoing)
2467 # - (desturl, destbranch, destpeer, outgoing)
2446 summaryremotehooks = util.hooks()
2468 summaryremotehooks = util.hooks()
2447
2469
2448 # A list of state files kept by multistep operations like graft.
2470 # A list of state files kept by multistep operations like graft.
2449 # Since graft cannot be aborted, it is considered 'clearable' by update.
2471 # Since graft cannot be aborted, it is considered 'clearable' by update.
2450 # note: bisect is intentionally excluded
2472 # note: bisect is intentionally excluded
2451 # (state file, clearable, allowcommit, error, hint)
2473 # (state file, clearable, allowcommit, error, hint)
2452 unfinishedstates = [
2474 unfinishedstates = [
2453 ('graftstate', True, False, _('graft in progress'),
2475 ('graftstate', True, False, _('graft in progress'),
2454 _("use 'hg graft --continue' or 'hg update' to abort")),
2476 _("use 'hg graft --continue' or 'hg update' to abort")),
2455 ('updatestate', True, False, _('last update was interrupted'),
2477 ('updatestate', True, False, _('last update was interrupted'),
2456 _("use 'hg update' to get a consistent checkout"))
2478 _("use 'hg update' to get a consistent checkout"))
2457 ]
2479 ]
2458
2480
2459 def checkunfinished(repo, commit=False):
2481 def checkunfinished(repo, commit=False):
2460 '''Look for an unfinished multistep operation, like graft, and abort
2482 '''Look for an unfinished multistep operation, like graft, and abort
2461 if found. It's probably good to check this right before
2483 if found. It's probably good to check this right before
2462 bailifchanged().
2484 bailifchanged().
2463 '''
2485 '''
2464 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2486 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2465 if commit and allowcommit:
2487 if commit and allowcommit:
2466 continue
2488 continue
2467 if repo.vfs.exists(f):
2489 if repo.vfs.exists(f):
2468 raise util.Abort(msg, hint=hint)
2490 raise util.Abort(msg, hint=hint)
2469
2491
2470 def clearunfinished(repo):
2492 def clearunfinished(repo):
2471 '''Check for unfinished operations (as above), and clear the ones
2493 '''Check for unfinished operations (as above), and clear the ones
2472 that are clearable.
2494 that are clearable.
2473 '''
2495 '''
2474 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2496 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2475 if not clearable and repo.vfs.exists(f):
2497 if not clearable and repo.vfs.exists(f):
2476 raise util.Abort(msg, hint=hint)
2498 raise util.Abort(msg, hint=hint)
2477 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2499 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2478 if clearable and repo.vfs.exists(f):
2500 if clearable and repo.vfs.exists(f):
2479 util.unlink(repo.join(f))
2501 util.unlink(repo.join(f))
General Comments 0
You need to be logged in to leave comments. Login now