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