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