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