##// END OF EJS Templates
Make changeset_printer respect ui diffopts
Benoit Boissinot -
r4714:a7412937 default
parent child Browse files
Show More
@@ -1,1236 +1,1237
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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex
10 import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex
11 import mdiff, bdiff, util, templater, patch, commands, hg, lock, time
11 import mdiff, bdiff, util, templater, patch, commands, hg, lock, time
12 import fancyopts, revlog, version, extensions, hook
12 import fancyopts, revlog, version, extensions, hook
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 class UnknownCommand(Exception):
16 class UnknownCommand(Exception):
17 """Exception raised if command is not in the command table."""
17 """Exception raised if command is not in the command table."""
18 class AmbiguousCommand(Exception):
18 class AmbiguousCommand(Exception):
19 """Exception raised if command shortcut matches more than one command."""
19 """Exception raised if command shortcut matches more than one command."""
20 class ParseError(Exception):
20 class ParseError(Exception):
21 """Exception raised on errors in parsing the command line."""
21 """Exception raised on errors in parsing the command line."""
22
22
23 def runcatch(ui, args, argv0=None):
23 def runcatch(ui, args, argv0=None):
24 def catchterm(*args):
24 def catchterm(*args):
25 raise util.SignalInterrupt
25 raise util.SignalInterrupt
26
26
27 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
27 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
28 num = getattr(signal, name, None)
28 num = getattr(signal, name, None)
29 if num: signal.signal(num, catchterm)
29 if num: signal.signal(num, catchterm)
30
30
31 try:
31 try:
32 try:
32 try:
33 # enter the debugger before command execution
33 # enter the debugger before command execution
34 if '--debugger' in args:
34 if '--debugger' in args:
35 pdb.set_trace()
35 pdb.set_trace()
36 try:
36 try:
37 return dispatch(ui, args, argv0=argv0)
37 return dispatch(ui, args, argv0=argv0)
38 finally:
38 finally:
39 ui.flush()
39 ui.flush()
40 except:
40 except:
41 # enter the debugger when we hit an exception
41 # enter the debugger when we hit an exception
42 if '--debugger' in args:
42 if '--debugger' in args:
43 pdb.post_mortem(sys.exc_info()[2])
43 pdb.post_mortem(sys.exc_info()[2])
44 ui.print_exc()
44 ui.print_exc()
45 raise
45 raise
46
46
47 except ParseError, inst:
47 except ParseError, inst:
48 if inst.args[0]:
48 if inst.args[0]:
49 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
49 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
50 commands.help_(ui, inst.args[0])
50 commands.help_(ui, inst.args[0])
51 else:
51 else:
52 ui.warn(_("hg: %s\n") % inst.args[1])
52 ui.warn(_("hg: %s\n") % inst.args[1])
53 commands.help_(ui, 'shortlist')
53 commands.help_(ui, 'shortlist')
54 except AmbiguousCommand, inst:
54 except AmbiguousCommand, inst:
55 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
55 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
56 (inst.args[0], " ".join(inst.args[1])))
56 (inst.args[0], " ".join(inst.args[1])))
57 except UnknownCommand, inst:
57 except UnknownCommand, inst:
58 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
58 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
59 commands.help_(ui, 'shortlist')
59 commands.help_(ui, 'shortlist')
60 except hg.RepoError, inst:
60 except hg.RepoError, inst:
61 ui.warn(_("abort: %s!\n") % inst)
61 ui.warn(_("abort: %s!\n") % inst)
62 except lock.LockHeld, inst:
62 except lock.LockHeld, inst:
63 if inst.errno == errno.ETIMEDOUT:
63 if inst.errno == errno.ETIMEDOUT:
64 reason = _('timed out waiting for lock held by %s') % inst.locker
64 reason = _('timed out waiting for lock held by %s') % inst.locker
65 else:
65 else:
66 reason = _('lock held by %s') % inst.locker
66 reason = _('lock held by %s') % inst.locker
67 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
67 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
68 except lock.LockUnavailable, inst:
68 except lock.LockUnavailable, inst:
69 ui.warn(_("abort: could not lock %s: %s\n") %
69 ui.warn(_("abort: could not lock %s: %s\n") %
70 (inst.desc or inst.filename, inst.strerror))
70 (inst.desc or inst.filename, inst.strerror))
71 except revlog.RevlogError, inst:
71 except revlog.RevlogError, inst:
72 ui.warn(_("abort: %s!\n") % inst)
72 ui.warn(_("abort: %s!\n") % inst)
73 except util.SignalInterrupt:
73 except util.SignalInterrupt:
74 ui.warn(_("killed!\n"))
74 ui.warn(_("killed!\n"))
75 except KeyboardInterrupt:
75 except KeyboardInterrupt:
76 try:
76 try:
77 ui.warn(_("interrupted!\n"))
77 ui.warn(_("interrupted!\n"))
78 except IOError, inst:
78 except IOError, inst:
79 if inst.errno == errno.EPIPE:
79 if inst.errno == errno.EPIPE:
80 if ui.debugflag:
80 if ui.debugflag:
81 ui.warn(_("\nbroken pipe\n"))
81 ui.warn(_("\nbroken pipe\n"))
82 else:
82 else:
83 raise
83 raise
84 except socket.error, inst:
84 except socket.error, inst:
85 ui.warn(_("abort: %s\n") % inst[1])
85 ui.warn(_("abort: %s\n") % inst[1])
86 except IOError, inst:
86 except IOError, inst:
87 if hasattr(inst, "code"):
87 if hasattr(inst, "code"):
88 ui.warn(_("abort: %s\n") % inst)
88 ui.warn(_("abort: %s\n") % inst)
89 elif hasattr(inst, "reason"):
89 elif hasattr(inst, "reason"):
90 try: # usually it is in the form (errno, strerror)
90 try: # usually it is in the form (errno, strerror)
91 reason = inst.reason.args[1]
91 reason = inst.reason.args[1]
92 except: # it might be anything, for example a string
92 except: # it might be anything, for example a string
93 reason = inst.reason
93 reason = inst.reason
94 ui.warn(_("abort: error: %s\n") % reason)
94 ui.warn(_("abort: error: %s\n") % reason)
95 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
95 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
96 if ui.debugflag:
96 if ui.debugflag:
97 ui.warn(_("broken pipe\n"))
97 ui.warn(_("broken pipe\n"))
98 elif getattr(inst, "strerror", None):
98 elif getattr(inst, "strerror", None):
99 if getattr(inst, "filename", None):
99 if getattr(inst, "filename", None):
100 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
100 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
101 else:
101 else:
102 ui.warn(_("abort: %s\n") % inst.strerror)
102 ui.warn(_("abort: %s\n") % inst.strerror)
103 else:
103 else:
104 raise
104 raise
105 except OSError, inst:
105 except OSError, inst:
106 if getattr(inst, "filename", None):
106 if getattr(inst, "filename", None):
107 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
107 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
108 else:
108 else:
109 ui.warn(_("abort: %s\n") % inst.strerror)
109 ui.warn(_("abort: %s\n") % inst.strerror)
110 except util.UnexpectedOutput, inst:
110 except util.UnexpectedOutput, inst:
111 ui.warn(_("abort: %s") % inst[0])
111 ui.warn(_("abort: %s") % inst[0])
112 if not isinstance(inst[1], basestring):
112 if not isinstance(inst[1], basestring):
113 ui.warn(" %r\n" % (inst[1],))
113 ui.warn(" %r\n" % (inst[1],))
114 elif not inst[1]:
114 elif not inst[1]:
115 ui.warn(_(" empty string\n"))
115 ui.warn(_(" empty string\n"))
116 else:
116 else:
117 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
117 ui.warn("\n%r\n" % util.ellipsis(inst[1]))
118 except ImportError, inst:
118 except ImportError, inst:
119 m = str(inst).split()[-1]
119 m = str(inst).split()[-1]
120 ui.warn(_("abort: could not import module %s!\n" % m))
120 ui.warn(_("abort: could not import module %s!\n" % m))
121 if m in "mpatch bdiff".split():
121 if m in "mpatch bdiff".split():
122 ui.warn(_("(did you forget to compile extensions?)\n"))
122 ui.warn(_("(did you forget to compile extensions?)\n"))
123 elif m in "zlib".split():
123 elif m in "zlib".split():
124 ui.warn(_("(is your Python install correct?)\n"))
124 ui.warn(_("(is your Python install correct?)\n"))
125
125
126 except util.Abort, inst:
126 except util.Abort, inst:
127 ui.warn(_("abort: %s\n") % inst)
127 ui.warn(_("abort: %s\n") % inst)
128 except SystemExit, inst:
128 except SystemExit, inst:
129 # Commands shouldn't sys.exit directly, but give a return code.
129 # Commands shouldn't sys.exit directly, but give a return code.
130 # Just in case catch this and and pass exit code to caller.
130 # Just in case catch this and and pass exit code to caller.
131 return inst.code
131 return inst.code
132 except:
132 except:
133 ui.warn(_("** unknown exception encountered, details follow\n"))
133 ui.warn(_("** unknown exception encountered, details follow\n"))
134 ui.warn(_("** report bug details to "
134 ui.warn(_("** report bug details to "
135 "http://www.selenic.com/mercurial/bts\n"))
135 "http://www.selenic.com/mercurial/bts\n"))
136 ui.warn(_("** or mercurial@selenic.com\n"))
136 ui.warn(_("** or mercurial@selenic.com\n"))
137 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
137 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
138 % version.get_version())
138 % version.get_version())
139 raise
139 raise
140
140
141 return -1
141 return -1
142
142
143 def findpossible(ui, cmd):
143 def findpossible(ui, cmd):
144 """
144 """
145 Return cmd -> (aliases, command table entry)
145 Return cmd -> (aliases, command table entry)
146 for each matching command.
146 for each matching command.
147 Return debug commands (or their aliases) only if no normal command matches.
147 Return debug commands (or their aliases) only if no normal command matches.
148 """
148 """
149 choice = {}
149 choice = {}
150 debugchoice = {}
150 debugchoice = {}
151 for e in commands.table.keys():
151 for e in commands.table.keys():
152 aliases = e.lstrip("^").split("|")
152 aliases = e.lstrip("^").split("|")
153 found = None
153 found = None
154 if cmd in aliases:
154 if cmd in aliases:
155 found = cmd
155 found = cmd
156 elif not ui.config("ui", "strict"):
156 elif not ui.config("ui", "strict"):
157 for a in aliases:
157 for a in aliases:
158 if a.startswith(cmd):
158 if a.startswith(cmd):
159 found = a
159 found = a
160 break
160 break
161 if found is not None:
161 if found is not None:
162 if aliases[0].startswith("debug") or found.startswith("debug"):
162 if aliases[0].startswith("debug") or found.startswith("debug"):
163 debugchoice[found] = (aliases, commands.table[e])
163 debugchoice[found] = (aliases, commands.table[e])
164 else:
164 else:
165 choice[found] = (aliases, commands.table[e])
165 choice[found] = (aliases, commands.table[e])
166
166
167 if not choice and debugchoice:
167 if not choice and debugchoice:
168 choice = debugchoice
168 choice = debugchoice
169
169
170 return choice
170 return choice
171
171
172 def findcmd(ui, cmd):
172 def findcmd(ui, cmd):
173 """Return (aliases, command table entry) for command string."""
173 """Return (aliases, command table entry) for command string."""
174 choice = findpossible(ui, cmd)
174 choice = findpossible(ui, cmd)
175
175
176 if choice.has_key(cmd):
176 if choice.has_key(cmd):
177 return choice[cmd]
177 return choice[cmd]
178
178
179 if len(choice) > 1:
179 if len(choice) > 1:
180 clist = choice.keys()
180 clist = choice.keys()
181 clist.sort()
181 clist.sort()
182 raise AmbiguousCommand(cmd, clist)
182 raise AmbiguousCommand(cmd, clist)
183
183
184 if choice:
184 if choice:
185 return choice.values()[0]
185 return choice.values()[0]
186
186
187 raise UnknownCommand(cmd)
187 raise UnknownCommand(cmd)
188
188
189 def findrepo():
189 def findrepo():
190 p = os.getcwd()
190 p = os.getcwd()
191 while not os.path.isdir(os.path.join(p, ".hg")):
191 while not os.path.isdir(os.path.join(p, ".hg")):
192 oldp, p = p, os.path.dirname(p)
192 oldp, p = p, os.path.dirname(p)
193 if p == oldp:
193 if p == oldp:
194 return None
194 return None
195
195
196 return p
196 return p
197
197
198 def parse(ui, args):
198 def parse(ui, args):
199 options = {}
199 options = {}
200 cmdoptions = {}
200 cmdoptions = {}
201
201
202 try:
202 try:
203 args = fancyopts.fancyopts(args, commands.globalopts, options)
203 args = fancyopts.fancyopts(args, commands.globalopts, options)
204 except fancyopts.getopt.GetoptError, inst:
204 except fancyopts.getopt.GetoptError, inst:
205 raise ParseError(None, inst)
205 raise ParseError(None, inst)
206
206
207 if args:
207 if args:
208 cmd, args = args[0], args[1:]
208 cmd, args = args[0], args[1:]
209 aliases, i = findcmd(ui, cmd)
209 aliases, i = findcmd(ui, cmd)
210 cmd = aliases[0]
210 cmd = aliases[0]
211 defaults = ui.config("defaults", cmd)
211 defaults = ui.config("defaults", cmd)
212 if defaults:
212 if defaults:
213 args = shlex.split(defaults) + args
213 args = shlex.split(defaults) + args
214 c = list(i[1])
214 c = list(i[1])
215 else:
215 else:
216 cmd = None
216 cmd = None
217 c = []
217 c = []
218
218
219 # combine global options into local
219 # combine global options into local
220 for o in commands.globalopts:
220 for o in commands.globalopts:
221 c.append((o[0], o[1], options[o[1]], o[3]))
221 c.append((o[0], o[1], options[o[1]], o[3]))
222
222
223 try:
223 try:
224 args = fancyopts.fancyopts(args, c, cmdoptions)
224 args = fancyopts.fancyopts(args, c, cmdoptions)
225 except fancyopts.getopt.GetoptError, inst:
225 except fancyopts.getopt.GetoptError, inst:
226 raise ParseError(cmd, inst)
226 raise ParseError(cmd, inst)
227
227
228 # separate global options back out
228 # separate global options back out
229 for o in commands.globalopts:
229 for o in commands.globalopts:
230 n = o[1]
230 n = o[1]
231 options[n] = cmdoptions[n]
231 options[n] = cmdoptions[n]
232 del cmdoptions[n]
232 del cmdoptions[n]
233
233
234 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
234 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
235
235
236 def parseconfig(config):
236 def parseconfig(config):
237 """parse the --config options from the command line"""
237 """parse the --config options from the command line"""
238 parsed = []
238 parsed = []
239 for cfg in config:
239 for cfg in config:
240 try:
240 try:
241 name, value = cfg.split('=', 1)
241 name, value = cfg.split('=', 1)
242 section, name = name.split('.', 1)
242 section, name = name.split('.', 1)
243 if not section or not name:
243 if not section or not name:
244 raise IndexError
244 raise IndexError
245 parsed.append((section, name, value))
245 parsed.append((section, name, value))
246 except (IndexError, ValueError):
246 except (IndexError, ValueError):
247 raise util.Abort(_('malformed --config option: %s') % cfg)
247 raise util.Abort(_('malformed --config option: %s') % cfg)
248 return parsed
248 return parsed
249
249
250 def earlygetopt(aliases, args):
250 def earlygetopt(aliases, args):
251 if "--" in args:
251 if "--" in args:
252 args = args[:args.index("--")]
252 args = args[:args.index("--")]
253 for opt in aliases:
253 for opt in aliases:
254 if opt in args:
254 if opt in args:
255 return args[args.index(opt) + 1]
255 return args[args.index(opt) + 1]
256 return None
256 return None
257
257
258 def dispatch(ui, args, argv0=None):
258 def dispatch(ui, args, argv0=None):
259 # remember how to call 'hg' before changing the working dir
259 # remember how to call 'hg' before changing the working dir
260 util.set_hgexecutable(argv0)
260 util.set_hgexecutable(argv0)
261
261
262 # check for cwd first
262 # check for cwd first
263 cwd = earlygetopt(['--cwd'], args)
263 cwd = earlygetopt(['--cwd'], args)
264 if cwd:
264 if cwd:
265 os.chdir(cwd)
265 os.chdir(cwd)
266
266
267 # read the local repository .hgrc into a local ui object
267 # read the local repository .hgrc into a local ui object
268 path = findrepo() or ""
268 path = findrepo() or ""
269 if not path:
269 if not path:
270 lui = ui
270 lui = ui
271 if path:
271 if path:
272 try:
272 try:
273 lui = commands.ui.ui(parentui=ui)
273 lui = commands.ui.ui(parentui=ui)
274 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
274 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
275 except IOError:
275 except IOError:
276 pass
276 pass
277
277
278 # now we can expand paths, even ones in .hg/hgrc
278 # now we can expand paths, even ones in .hg/hgrc
279 rpath = earlygetopt(["-R", "--repository", "--repo"], args)
279 rpath = earlygetopt(["-R", "--repository", "--repo"], args)
280 if rpath:
280 if rpath:
281 path = lui.expandpath(rpath)
281 path = lui.expandpath(rpath)
282 lui = commands.ui.ui(parentui=ui)
282 lui = commands.ui.ui(parentui=ui)
283 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
283 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
284
284
285 extensions.loadall(lui)
285 extensions.loadall(lui)
286 # check for fallback encoding
286 # check for fallback encoding
287 fallback = lui.config('ui', 'fallbackencoding')
287 fallback = lui.config('ui', 'fallbackencoding')
288 if fallback:
288 if fallback:
289 util._fallbackencoding = fallback
289 util._fallbackencoding = fallback
290
290
291 fullargs = args
291 fullargs = args
292 cmd, func, args, options, cmdoptions = parse(ui, args)
292 cmd, func, args, options, cmdoptions = parse(ui, args)
293
293
294 if options["encoding"]:
294 if options["encoding"]:
295 util._encoding = options["encoding"]
295 util._encoding = options["encoding"]
296 if options["encodingmode"]:
296 if options["encodingmode"]:
297 util._encodingmode = options["encodingmode"]
297 util._encodingmode = options["encodingmode"]
298 if options["time"]:
298 if options["time"]:
299 def get_times():
299 def get_times():
300 t = os.times()
300 t = os.times()
301 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
301 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
302 t = (t[0], t[1], t[2], t[3], time.clock())
302 t = (t[0], t[1], t[2], t[3], time.clock())
303 return t
303 return t
304 s = get_times()
304 s = get_times()
305 def print_time():
305 def print_time():
306 t = get_times()
306 t = get_times()
307 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
307 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
308 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
308 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
309 atexit.register(print_time)
309 atexit.register(print_time)
310
310
311 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
311 ui.updateopts(options["verbose"], options["debug"], options["quiet"],
312 not options["noninteractive"], options["traceback"],
312 not options["noninteractive"], options["traceback"],
313 parseconfig(options["config"]))
313 parseconfig(options["config"]))
314
314
315 if options['help']:
315 if options['help']:
316 return commands.help_(ui, cmd, options['version'])
316 return commands.help_(ui, cmd, options['version'])
317 elif options['version']:
317 elif options['version']:
318 return commands.version_(ui)
318 return commands.version_(ui)
319 elif not cmd:
319 elif not cmd:
320 return commands.help_(ui, 'shortlist')
320 return commands.help_(ui, 'shortlist')
321
321
322 repo = None
322 repo = None
323 if cmd not in commands.norepo.split():
323 if cmd not in commands.norepo.split():
324 try:
324 try:
325 repo = hg.repository(ui, path=path)
325 repo = hg.repository(ui, path=path)
326 ui = repo.ui
326 ui = repo.ui
327 if not repo.local():
327 if not repo.local():
328 raise util.Abort(_("repository '%s' is not local") % path)
328 raise util.Abort(_("repository '%s' is not local") % path)
329 except hg.RepoError:
329 except hg.RepoError:
330 if cmd not in commands.optionalrepo.split():
330 if cmd not in commands.optionalrepo.split():
331 if not path:
331 if not path:
332 raise hg.RepoError(_("There is no Mercurial repository here"
332 raise hg.RepoError(_("There is no Mercurial repository here"
333 " (.hg not found)"))
333 " (.hg not found)"))
334 raise
334 raise
335 d = lambda: func(ui, repo, *args, **cmdoptions)
335 d = lambda: func(ui, repo, *args, **cmdoptions)
336 else:
336 else:
337 d = lambda: func(ui, *args, **cmdoptions)
337 d = lambda: func(ui, *args, **cmdoptions)
338
338
339 # run pre-hook, and abort if it fails
339 # run pre-hook, and abort if it fails
340 ret = hook.hook(ui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
340 ret = hook.hook(ui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
341 if ret:
341 if ret:
342 return ret
342 return ret
343 ret = runcommand(ui, options, cmd, d)
343 ret = runcommand(ui, options, cmd, d)
344 # run post-hook, passing command result
344 # run post-hook, passing command result
345 hook.hook(ui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
345 hook.hook(ui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
346 result = ret)
346 result = ret)
347 return ret
347 return ret
348
348
349 def runcommand(ui, options, cmd, cmdfunc):
349 def runcommand(ui, options, cmd, cmdfunc):
350 def checkargs():
350 def checkargs():
351 try:
351 try:
352 return cmdfunc()
352 return cmdfunc()
353 except TypeError, inst:
353 except TypeError, inst:
354 # was this an argument error?
354 # was this an argument error?
355 tb = traceback.extract_tb(sys.exc_info()[2])
355 tb = traceback.extract_tb(sys.exc_info()[2])
356 if len(tb) != 2: # no
356 if len(tb) != 2: # no
357 raise
357 raise
358 raise ParseError(cmd, _("invalid arguments"))
358 raise ParseError(cmd, _("invalid arguments"))
359
359
360 if options['profile']:
360 if options['profile']:
361 import hotshot, hotshot.stats
361 import hotshot, hotshot.stats
362 prof = hotshot.Profile("hg.prof")
362 prof = hotshot.Profile("hg.prof")
363 try:
363 try:
364 try:
364 try:
365 return prof.runcall(checkargs)
365 return prof.runcall(checkargs)
366 except:
366 except:
367 try:
367 try:
368 ui.warn(_('exception raised - generating '
368 ui.warn(_('exception raised - generating '
369 'profile anyway\n'))
369 'profile anyway\n'))
370 except:
370 except:
371 pass
371 pass
372 raise
372 raise
373 finally:
373 finally:
374 prof.close()
374 prof.close()
375 stats = hotshot.stats.load("hg.prof")
375 stats = hotshot.stats.load("hg.prof")
376 stats.strip_dirs()
376 stats.strip_dirs()
377 stats.sort_stats('time', 'calls')
377 stats.sort_stats('time', 'calls')
378 stats.print_stats(40)
378 stats.print_stats(40)
379 elif options['lsprof']:
379 elif options['lsprof']:
380 try:
380 try:
381 from mercurial import lsprof
381 from mercurial import lsprof
382 except ImportError:
382 except ImportError:
383 raise util.Abort(_(
383 raise util.Abort(_(
384 'lsprof not available - install from '
384 'lsprof not available - install from '
385 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
385 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
386 p = lsprof.Profiler()
386 p = lsprof.Profiler()
387 p.enable(subcalls=True)
387 p.enable(subcalls=True)
388 try:
388 try:
389 return checkargs()
389 return checkargs()
390 finally:
390 finally:
391 p.disable()
391 p.disable()
392 stats = lsprof.Stats(p.getstats())
392 stats = lsprof.Stats(p.getstats())
393 stats.sort()
393 stats.sort()
394 stats.pprint(top=10, file=sys.stderr, climit=5)
394 stats.pprint(top=10, file=sys.stderr, climit=5)
395 else:
395 else:
396 return checkargs()
396 return checkargs()
397
397
398 def bail_if_changed(repo):
398 def bail_if_changed(repo):
399 modified, added, removed, deleted = repo.status()[:4]
399 modified, added, removed, deleted = repo.status()[:4]
400 if modified or added or removed or deleted:
400 if modified or added or removed or deleted:
401 raise util.Abort(_("outstanding uncommitted changes"))
401 raise util.Abort(_("outstanding uncommitted changes"))
402
402
403 def logmessage(opts):
403 def logmessage(opts):
404 """ get the log message according to -m and -l option """
404 """ get the log message according to -m and -l option """
405 message = opts['message']
405 message = opts['message']
406 logfile = opts['logfile']
406 logfile = opts['logfile']
407
407
408 if message and logfile:
408 if message and logfile:
409 raise util.Abort(_('options --message and --logfile are mutually '
409 raise util.Abort(_('options --message and --logfile are mutually '
410 'exclusive'))
410 'exclusive'))
411 if not message and logfile:
411 if not message and logfile:
412 try:
412 try:
413 if logfile == '-':
413 if logfile == '-':
414 message = sys.stdin.read()
414 message = sys.stdin.read()
415 else:
415 else:
416 message = open(logfile).read()
416 message = open(logfile).read()
417 except IOError, inst:
417 except IOError, inst:
418 raise util.Abort(_("can't read commit message '%s': %s") %
418 raise util.Abort(_("can't read commit message '%s': %s") %
419 (logfile, inst.strerror))
419 (logfile, inst.strerror))
420 return message
420 return message
421
421
422 def setremoteconfig(ui, opts):
422 def setremoteconfig(ui, opts):
423 "copy remote options to ui tree"
423 "copy remote options to ui tree"
424 if opts.get('ssh'):
424 if opts.get('ssh'):
425 ui.setconfig("ui", "ssh", opts['ssh'])
425 ui.setconfig("ui", "ssh", opts['ssh'])
426 if opts.get('remotecmd'):
426 if opts.get('remotecmd'):
427 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
427 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
428
428
429 def parseurl(url, revs):
429 def parseurl(url, revs):
430 '''parse url#branch, returning url, branch + revs'''
430 '''parse url#branch, returning url, branch + revs'''
431
431
432 if '#' not in url:
432 if '#' not in url:
433 return url, (revs or None)
433 return url, (revs or None)
434
434
435 url, rev = url.split('#', 1)
435 url, rev = url.split('#', 1)
436 return url, revs + [rev]
436 return url, revs + [rev]
437
437
438 def revpair(repo, revs):
438 def revpair(repo, revs):
439 '''return pair of nodes, given list of revisions. second item can
439 '''return pair of nodes, given list of revisions. second item can
440 be None, meaning use working dir.'''
440 be None, meaning use working dir.'''
441
441
442 def revfix(repo, val, defval):
442 def revfix(repo, val, defval):
443 if not val and val != 0 and defval is not None:
443 if not val and val != 0 and defval is not None:
444 val = defval
444 val = defval
445 return repo.lookup(val)
445 return repo.lookup(val)
446
446
447 if not revs:
447 if not revs:
448 return repo.dirstate.parents()[0], None
448 return repo.dirstate.parents()[0], None
449 end = None
449 end = None
450 if len(revs) == 1:
450 if len(revs) == 1:
451 if revrangesep in revs[0]:
451 if revrangesep in revs[0]:
452 start, end = revs[0].split(revrangesep, 1)
452 start, end = revs[0].split(revrangesep, 1)
453 start = revfix(repo, start, 0)
453 start = revfix(repo, start, 0)
454 end = revfix(repo, end, repo.changelog.count() - 1)
454 end = revfix(repo, end, repo.changelog.count() - 1)
455 else:
455 else:
456 start = revfix(repo, revs[0], None)
456 start = revfix(repo, revs[0], None)
457 elif len(revs) == 2:
457 elif len(revs) == 2:
458 if revrangesep in revs[0] or revrangesep in revs[1]:
458 if revrangesep in revs[0] or revrangesep in revs[1]:
459 raise util.Abort(_('too many revisions specified'))
459 raise util.Abort(_('too many revisions specified'))
460 start = revfix(repo, revs[0], None)
460 start = revfix(repo, revs[0], None)
461 end = revfix(repo, revs[1], None)
461 end = revfix(repo, revs[1], None)
462 else:
462 else:
463 raise util.Abort(_('too many revisions specified'))
463 raise util.Abort(_('too many revisions specified'))
464 return start, end
464 return start, end
465
465
466 def revrange(repo, revs):
466 def revrange(repo, revs):
467 """Yield revision as strings from a list of revision specifications."""
467 """Yield revision as strings from a list of revision specifications."""
468
468
469 def revfix(repo, val, defval):
469 def revfix(repo, val, defval):
470 if not val and val != 0 and defval is not None:
470 if not val and val != 0 and defval is not None:
471 return defval
471 return defval
472 return repo.changelog.rev(repo.lookup(val))
472 return repo.changelog.rev(repo.lookup(val))
473
473
474 seen, l = {}, []
474 seen, l = {}, []
475 for spec in revs:
475 for spec in revs:
476 if revrangesep in spec:
476 if revrangesep in spec:
477 start, end = spec.split(revrangesep, 1)
477 start, end = spec.split(revrangesep, 1)
478 start = revfix(repo, start, 0)
478 start = revfix(repo, start, 0)
479 end = revfix(repo, end, repo.changelog.count() - 1)
479 end = revfix(repo, end, repo.changelog.count() - 1)
480 step = start > end and -1 or 1
480 step = start > end and -1 or 1
481 for rev in xrange(start, end+step, step):
481 for rev in xrange(start, end+step, step):
482 if rev in seen:
482 if rev in seen:
483 continue
483 continue
484 seen[rev] = 1
484 seen[rev] = 1
485 l.append(rev)
485 l.append(rev)
486 else:
486 else:
487 rev = revfix(repo, spec, None)
487 rev = revfix(repo, spec, None)
488 if rev in seen:
488 if rev in seen:
489 continue
489 continue
490 seen[rev] = 1
490 seen[rev] = 1
491 l.append(rev)
491 l.append(rev)
492
492
493 return l
493 return l
494
494
495 def make_filename(repo, pat, node,
495 def make_filename(repo, pat, node,
496 total=None, seqno=None, revwidth=None, pathname=None):
496 total=None, seqno=None, revwidth=None, pathname=None):
497 node_expander = {
497 node_expander = {
498 'H': lambda: hex(node),
498 'H': lambda: hex(node),
499 'R': lambda: str(repo.changelog.rev(node)),
499 'R': lambda: str(repo.changelog.rev(node)),
500 'h': lambda: short(node),
500 'h': lambda: short(node),
501 }
501 }
502 expander = {
502 expander = {
503 '%': lambda: '%',
503 '%': lambda: '%',
504 'b': lambda: os.path.basename(repo.root),
504 'b': lambda: os.path.basename(repo.root),
505 }
505 }
506
506
507 try:
507 try:
508 if node:
508 if node:
509 expander.update(node_expander)
509 expander.update(node_expander)
510 if node and revwidth is not None:
510 if node and revwidth is not None:
511 expander['r'] = (lambda:
511 expander['r'] = (lambda:
512 str(repo.changelog.rev(node)).zfill(revwidth))
512 str(repo.changelog.rev(node)).zfill(revwidth))
513 if total is not None:
513 if total is not None:
514 expander['N'] = lambda: str(total)
514 expander['N'] = lambda: str(total)
515 if seqno is not None:
515 if seqno is not None:
516 expander['n'] = lambda: str(seqno)
516 expander['n'] = lambda: str(seqno)
517 if total is not None and seqno is not None:
517 if total is not None and seqno is not None:
518 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
518 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
519 if pathname is not None:
519 if pathname is not None:
520 expander['s'] = lambda: os.path.basename(pathname)
520 expander['s'] = lambda: os.path.basename(pathname)
521 expander['d'] = lambda: os.path.dirname(pathname) or '.'
521 expander['d'] = lambda: os.path.dirname(pathname) or '.'
522 expander['p'] = lambda: pathname
522 expander['p'] = lambda: pathname
523
523
524 newname = []
524 newname = []
525 patlen = len(pat)
525 patlen = len(pat)
526 i = 0
526 i = 0
527 while i < patlen:
527 while i < patlen:
528 c = pat[i]
528 c = pat[i]
529 if c == '%':
529 if c == '%':
530 i += 1
530 i += 1
531 c = pat[i]
531 c = pat[i]
532 c = expander[c]()
532 c = expander[c]()
533 newname.append(c)
533 newname.append(c)
534 i += 1
534 i += 1
535 return ''.join(newname)
535 return ''.join(newname)
536 except KeyError, inst:
536 except KeyError, inst:
537 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
537 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
538 inst.args[0])
538 inst.args[0])
539
539
540 def make_file(repo, pat, node=None,
540 def make_file(repo, pat, node=None,
541 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
541 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
542 if not pat or pat == '-':
542 if not pat or pat == '-':
543 return 'w' in mode and sys.stdout or sys.stdin
543 return 'w' in mode and sys.stdout or sys.stdin
544 if hasattr(pat, 'write') and 'w' in mode:
544 if hasattr(pat, 'write') and 'w' in mode:
545 return pat
545 return pat
546 if hasattr(pat, 'read') and 'r' in mode:
546 if hasattr(pat, 'read') and 'r' in mode:
547 return pat
547 return pat
548 return open(make_filename(repo, pat, node, total, seqno, revwidth,
548 return open(make_filename(repo, pat, node, total, seqno, revwidth,
549 pathname),
549 pathname),
550 mode)
550 mode)
551
551
552 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
552 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
553 cwd = repo.getcwd()
553 cwd = repo.getcwd()
554 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
554 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
555 opts.get('exclude'), globbed=globbed,
555 opts.get('exclude'), globbed=globbed,
556 default=default)
556 default=default)
557
557
558 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
558 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
559 default=None):
559 default=None):
560 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
560 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
561 default=default)
561 default=default)
562 exact = dict.fromkeys(files)
562 exact = dict.fromkeys(files)
563 cwd = repo.getcwd()
563 cwd = repo.getcwd()
564 for src, fn in repo.walk(node=node, files=files, match=matchfn,
564 for src, fn in repo.walk(node=node, files=files, match=matchfn,
565 badmatch=badmatch):
565 badmatch=badmatch):
566 yield src, fn, repo.pathto(fn, cwd), fn in exact
566 yield src, fn, repo.pathto(fn, cwd), fn in exact
567
567
568 def findrenames(repo, added=None, removed=None, threshold=0.5):
568 def findrenames(repo, added=None, removed=None, threshold=0.5):
569 '''find renamed files -- yields (before, after, score) tuples'''
569 '''find renamed files -- yields (before, after, score) tuples'''
570 if added is None or removed is None:
570 if added is None or removed is None:
571 added, removed = repo.status()[1:3]
571 added, removed = repo.status()[1:3]
572 ctx = repo.changectx()
572 ctx = repo.changectx()
573 for a in added:
573 for a in added:
574 aa = repo.wread(a)
574 aa = repo.wread(a)
575 bestname, bestscore = None, threshold
575 bestname, bestscore = None, threshold
576 for r in removed:
576 for r in removed:
577 rr = ctx.filectx(r).data()
577 rr = ctx.filectx(r).data()
578
578
579 # bdiff.blocks() returns blocks of matching lines
579 # bdiff.blocks() returns blocks of matching lines
580 # count the number of bytes in each
580 # count the number of bytes in each
581 equal = 0
581 equal = 0
582 alines = mdiff.splitnewlines(aa)
582 alines = mdiff.splitnewlines(aa)
583 matches = bdiff.blocks(aa, rr)
583 matches = bdiff.blocks(aa, rr)
584 for x1,x2,y1,y2 in matches:
584 for x1,x2,y1,y2 in matches:
585 for line in alines[x1:x2]:
585 for line in alines[x1:x2]:
586 equal += len(line)
586 equal += len(line)
587
587
588 lengths = len(aa) + len(rr)
588 lengths = len(aa) + len(rr)
589 if lengths:
589 if lengths:
590 myscore = equal*2.0 / lengths
590 myscore = equal*2.0 / lengths
591 if myscore >= bestscore:
591 if myscore >= bestscore:
592 bestname, bestscore = r, myscore
592 bestname, bestscore = r, myscore
593 if bestname:
593 if bestname:
594 yield bestname, a, bestscore
594 yield bestname, a, bestscore
595
595
596 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
596 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
597 similarity=None):
597 similarity=None):
598 if dry_run is None:
598 if dry_run is None:
599 dry_run = opts.get('dry_run')
599 dry_run = opts.get('dry_run')
600 if similarity is None:
600 if similarity is None:
601 similarity = float(opts.get('similarity') or 0)
601 similarity = float(opts.get('similarity') or 0)
602 add, remove = [], []
602 add, remove = [], []
603 mapping = {}
603 mapping = {}
604 for src, abs, rel, exact in walk(repo, pats, opts):
604 for src, abs, rel, exact in walk(repo, pats, opts):
605 target = repo.wjoin(abs)
605 target = repo.wjoin(abs)
606 if src == 'f' and repo.dirstate.state(abs) == '?':
606 if src == 'f' and repo.dirstate.state(abs) == '?':
607 add.append(abs)
607 add.append(abs)
608 mapping[abs] = rel, exact
608 mapping[abs] = rel, exact
609 if repo.ui.verbose or not exact:
609 if repo.ui.verbose or not exact:
610 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
610 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
611 if repo.dirstate.state(abs) != 'r' and not util.lexists(target):
611 if repo.dirstate.state(abs) != 'r' and not util.lexists(target):
612 remove.append(abs)
612 remove.append(abs)
613 mapping[abs] = rel, exact
613 mapping[abs] = rel, exact
614 if repo.ui.verbose or not exact:
614 if repo.ui.verbose or not exact:
615 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
615 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
616 if not dry_run:
616 if not dry_run:
617 repo.add(add, wlock=wlock)
617 repo.add(add, wlock=wlock)
618 repo.remove(remove, wlock=wlock)
618 repo.remove(remove, wlock=wlock)
619 if similarity > 0:
619 if similarity > 0:
620 for old, new, score in findrenames(repo, add, remove, similarity):
620 for old, new, score in findrenames(repo, add, remove, similarity):
621 oldrel, oldexact = mapping[old]
621 oldrel, oldexact = mapping[old]
622 newrel, newexact = mapping[new]
622 newrel, newexact = mapping[new]
623 if repo.ui.verbose or not oldexact or not newexact:
623 if repo.ui.verbose or not oldexact or not newexact:
624 repo.ui.status(_('recording removal of %s as rename to %s '
624 repo.ui.status(_('recording removal of %s as rename to %s '
625 '(%d%% similar)\n') %
625 '(%d%% similar)\n') %
626 (oldrel, newrel, score * 100))
626 (oldrel, newrel, score * 100))
627 if not dry_run:
627 if not dry_run:
628 repo.copy(old, new, wlock=wlock)
628 repo.copy(old, new, wlock=wlock)
629
629
630 def service(opts, parentfn=None, initfn=None, runfn=None):
630 def service(opts, parentfn=None, initfn=None, runfn=None):
631 '''Run a command as a service.'''
631 '''Run a command as a service.'''
632
632
633 if opts['daemon'] and not opts['daemon_pipefds']:
633 if opts['daemon'] and not opts['daemon_pipefds']:
634 rfd, wfd = os.pipe()
634 rfd, wfd = os.pipe()
635 args = sys.argv[:]
635 args = sys.argv[:]
636 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
636 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
637 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
637 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
638 args[0], args)
638 args[0], args)
639 os.close(wfd)
639 os.close(wfd)
640 os.read(rfd, 1)
640 os.read(rfd, 1)
641 if parentfn:
641 if parentfn:
642 return parentfn(pid)
642 return parentfn(pid)
643 else:
643 else:
644 os._exit(0)
644 os._exit(0)
645
645
646 if initfn:
646 if initfn:
647 initfn()
647 initfn()
648
648
649 if opts['pid_file']:
649 if opts['pid_file']:
650 fp = open(opts['pid_file'], 'w')
650 fp = open(opts['pid_file'], 'w')
651 fp.write(str(os.getpid()) + '\n')
651 fp.write(str(os.getpid()) + '\n')
652 fp.close()
652 fp.close()
653
653
654 if opts['daemon_pipefds']:
654 if opts['daemon_pipefds']:
655 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
655 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
656 os.close(rfd)
656 os.close(rfd)
657 try:
657 try:
658 os.setsid()
658 os.setsid()
659 except AttributeError:
659 except AttributeError:
660 pass
660 pass
661 os.write(wfd, 'y')
661 os.write(wfd, 'y')
662 os.close(wfd)
662 os.close(wfd)
663 sys.stdout.flush()
663 sys.stdout.flush()
664 sys.stderr.flush()
664 sys.stderr.flush()
665 fd = os.open(util.nulldev, os.O_RDWR)
665 fd = os.open(util.nulldev, os.O_RDWR)
666 if fd != 0: os.dup2(fd, 0)
666 if fd != 0: os.dup2(fd, 0)
667 if fd != 1: os.dup2(fd, 1)
667 if fd != 1: os.dup2(fd, 1)
668 if fd != 2: os.dup2(fd, 2)
668 if fd != 2: os.dup2(fd, 2)
669 if fd not in (0, 1, 2): os.close(fd)
669 if fd not in (0, 1, 2): os.close(fd)
670
670
671 if runfn:
671 if runfn:
672 return runfn()
672 return runfn()
673
673
674 class changeset_printer(object):
674 class changeset_printer(object):
675 '''show changeset information when templating not requested.'''
675 '''show changeset information when templating not requested.'''
676
676
677 def __init__(self, ui, repo, patch, buffered):
677 def __init__(self, ui, repo, patch, buffered):
678 self.ui = ui
678 self.ui = ui
679 self.repo = repo
679 self.repo = repo
680 self.buffered = buffered
680 self.buffered = buffered
681 self.patch = patch
681 self.patch = patch
682 self.header = {}
682 self.header = {}
683 self.hunk = {}
683 self.hunk = {}
684 self.lastheader = None
684 self.lastheader = None
685
685
686 def flush(self, rev):
686 def flush(self, rev):
687 if rev in self.header:
687 if rev in self.header:
688 h = self.header[rev]
688 h = self.header[rev]
689 if h != self.lastheader:
689 if h != self.lastheader:
690 self.lastheader = h
690 self.lastheader = h
691 self.ui.write(h)
691 self.ui.write(h)
692 del self.header[rev]
692 del self.header[rev]
693 if rev in self.hunk:
693 if rev in self.hunk:
694 self.ui.write(self.hunk[rev])
694 self.ui.write(self.hunk[rev])
695 del self.hunk[rev]
695 del self.hunk[rev]
696 return 1
696 return 1
697 return 0
697 return 0
698
698
699 def show(self, rev=0, changenode=None, copies=(), **props):
699 def show(self, rev=0, changenode=None, copies=(), **props):
700 if self.buffered:
700 if self.buffered:
701 self.ui.pushbuffer()
701 self.ui.pushbuffer()
702 self._show(rev, changenode, copies, props)
702 self._show(rev, changenode, copies, props)
703 self.hunk[rev] = self.ui.popbuffer()
703 self.hunk[rev] = self.ui.popbuffer()
704 else:
704 else:
705 self._show(rev, changenode, copies, props)
705 self._show(rev, changenode, copies, props)
706
706
707 def _show(self, rev, changenode, copies, props):
707 def _show(self, rev, changenode, copies, props):
708 '''show a single changeset or file revision'''
708 '''show a single changeset or file revision'''
709 log = self.repo.changelog
709 log = self.repo.changelog
710 if changenode is None:
710 if changenode is None:
711 changenode = log.node(rev)
711 changenode = log.node(rev)
712 elif not rev:
712 elif not rev:
713 rev = log.rev(changenode)
713 rev = log.rev(changenode)
714
714
715 if self.ui.quiet:
715 if self.ui.quiet:
716 self.ui.write("%d:%s\n" % (rev, short(changenode)))
716 self.ui.write("%d:%s\n" % (rev, short(changenode)))
717 return
717 return
718
718
719 changes = log.read(changenode)
719 changes = log.read(changenode)
720 date = util.datestr(changes[2])
720 date = util.datestr(changes[2])
721 extra = changes[5]
721 extra = changes[5]
722 branch = extra.get("branch")
722 branch = extra.get("branch")
723
723
724 hexfunc = self.ui.debugflag and hex or short
724 hexfunc = self.ui.debugflag and hex or short
725
725
726 parents = log.parentrevs(rev)
726 parents = log.parentrevs(rev)
727 if not self.ui.debugflag:
727 if not self.ui.debugflag:
728 if parents[1] == nullrev:
728 if parents[1] == nullrev:
729 if parents[0] >= rev - 1:
729 if parents[0] >= rev - 1:
730 parents = []
730 parents = []
731 else:
731 else:
732 parents = [parents[0]]
732 parents = [parents[0]]
733 parents = [(p, hexfunc(log.node(p))) for p in parents]
733 parents = [(p, hexfunc(log.node(p))) for p in parents]
734
734
735 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
735 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
736
736
737 # don't show the default branch name
737 # don't show the default branch name
738 if branch != 'default':
738 if branch != 'default':
739 branch = util.tolocal(branch)
739 branch = util.tolocal(branch)
740 self.ui.write(_("branch: %s\n") % branch)
740 self.ui.write(_("branch: %s\n") % branch)
741 for tag in self.repo.nodetags(changenode):
741 for tag in self.repo.nodetags(changenode):
742 self.ui.write(_("tag: %s\n") % tag)
742 self.ui.write(_("tag: %s\n") % tag)
743 for parent in parents:
743 for parent in parents:
744 self.ui.write(_("parent: %d:%s\n") % parent)
744 self.ui.write(_("parent: %d:%s\n") % parent)
745
745
746 if self.ui.debugflag:
746 if self.ui.debugflag:
747 self.ui.write(_("manifest: %d:%s\n") %
747 self.ui.write(_("manifest: %d:%s\n") %
748 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
748 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
749 self.ui.write(_("user: %s\n") % changes[1])
749 self.ui.write(_("user: %s\n") % changes[1])
750 self.ui.write(_("date: %s\n") % date)
750 self.ui.write(_("date: %s\n") % date)
751
751
752 if self.ui.debugflag:
752 if self.ui.debugflag:
753 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
753 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
754 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
754 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
755 files):
755 files):
756 if value:
756 if value:
757 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
757 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
758 elif changes[3] and self.ui.verbose:
758 elif changes[3] and self.ui.verbose:
759 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
759 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
760 if copies and self.ui.verbose:
760 if copies and self.ui.verbose:
761 copies = ['%s (%s)' % c for c in copies]
761 copies = ['%s (%s)' % c for c in copies]
762 self.ui.write(_("copies: %s\n") % ' '.join(copies))
762 self.ui.write(_("copies: %s\n") % ' '.join(copies))
763
763
764 if extra and self.ui.debugflag:
764 if extra and self.ui.debugflag:
765 extraitems = extra.items()
765 extraitems = extra.items()
766 extraitems.sort()
766 extraitems.sort()
767 for key, value in extraitems:
767 for key, value in extraitems:
768 self.ui.write(_("extra: %s=%s\n")
768 self.ui.write(_("extra: %s=%s\n")
769 % (key, value.encode('string_escape')))
769 % (key, value.encode('string_escape')))
770
770
771 description = changes[4].strip()
771 description = changes[4].strip()
772 if description:
772 if description:
773 if self.ui.verbose:
773 if self.ui.verbose:
774 self.ui.write(_("description:\n"))
774 self.ui.write(_("description:\n"))
775 self.ui.write(description)
775 self.ui.write(description)
776 self.ui.write("\n\n")
776 self.ui.write("\n\n")
777 else:
777 else:
778 self.ui.write(_("summary: %s\n") %
778 self.ui.write(_("summary: %s\n") %
779 description.splitlines()[0])
779 description.splitlines()[0])
780 self.ui.write("\n")
780 self.ui.write("\n")
781
781
782 self.showpatch(changenode)
782 self.showpatch(changenode)
783
783
784 def showpatch(self, node):
784 def showpatch(self, node):
785 if self.patch:
785 if self.patch:
786 prev = self.repo.changelog.parents(node)[0]
786 prev = self.repo.changelog.parents(node)[0]
787 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui)
787 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui,
788 opts=patch.diffopts(self.ui))
788 self.ui.write("\n")
789 self.ui.write("\n")
789
790
790 class changeset_templater(changeset_printer):
791 class changeset_templater(changeset_printer):
791 '''format changeset information.'''
792 '''format changeset information.'''
792
793
793 def __init__(self, ui, repo, patch, mapfile, buffered):
794 def __init__(self, ui, repo, patch, mapfile, buffered):
794 changeset_printer.__init__(self, ui, repo, patch, buffered)
795 changeset_printer.__init__(self, ui, repo, patch, buffered)
795 filters = templater.common_filters.copy()
796 filters = templater.common_filters.copy()
796 filters['formatnode'] = (ui.debugflag and (lambda x: x)
797 filters['formatnode'] = (ui.debugflag and (lambda x: x)
797 or (lambda x: x[:12]))
798 or (lambda x: x[:12]))
798 self.t = templater.templater(mapfile, filters,
799 self.t = templater.templater(mapfile, filters,
799 cache={
800 cache={
800 'parent': '{rev}:{node|formatnode} ',
801 'parent': '{rev}:{node|formatnode} ',
801 'manifest': '{rev}:{node|formatnode}',
802 'manifest': '{rev}:{node|formatnode}',
802 'filecopy': '{name} ({source})'})
803 'filecopy': '{name} ({source})'})
803
804
804 def use_template(self, t):
805 def use_template(self, t):
805 '''set template string to use'''
806 '''set template string to use'''
806 self.t.cache['changeset'] = t
807 self.t.cache['changeset'] = t
807
808
808 def _show(self, rev, changenode, copies, props):
809 def _show(self, rev, changenode, copies, props):
809 '''show a single changeset or file revision'''
810 '''show a single changeset or file revision'''
810 log = self.repo.changelog
811 log = self.repo.changelog
811 if changenode is None:
812 if changenode is None:
812 changenode = log.node(rev)
813 changenode = log.node(rev)
813 elif not rev:
814 elif not rev:
814 rev = log.rev(changenode)
815 rev = log.rev(changenode)
815
816
816 changes = log.read(changenode)
817 changes = log.read(changenode)
817
818
818 def showlist(name, values, plural=None, **args):
819 def showlist(name, values, plural=None, **args):
819 '''expand set of values.
820 '''expand set of values.
820 name is name of key in template map.
821 name is name of key in template map.
821 values is list of strings or dicts.
822 values is list of strings or dicts.
822 plural is plural of name, if not simply name + 's'.
823 plural is plural of name, if not simply name + 's'.
823
824
824 expansion works like this, given name 'foo'.
825 expansion works like this, given name 'foo'.
825
826
826 if values is empty, expand 'no_foos'.
827 if values is empty, expand 'no_foos'.
827
828
828 if 'foo' not in template map, return values as a string,
829 if 'foo' not in template map, return values as a string,
829 joined by space.
830 joined by space.
830
831
831 expand 'start_foos'.
832 expand 'start_foos'.
832
833
833 for each value, expand 'foo'. if 'last_foo' in template
834 for each value, expand 'foo'. if 'last_foo' in template
834 map, expand it instead of 'foo' for last key.
835 map, expand it instead of 'foo' for last key.
835
836
836 expand 'end_foos'.
837 expand 'end_foos'.
837 '''
838 '''
838 if plural: names = plural
839 if plural: names = plural
839 else: names = name + 's'
840 else: names = name + 's'
840 if not values:
841 if not values:
841 noname = 'no_' + names
842 noname = 'no_' + names
842 if noname in self.t:
843 if noname in self.t:
843 yield self.t(noname, **args)
844 yield self.t(noname, **args)
844 return
845 return
845 if name not in self.t:
846 if name not in self.t:
846 if isinstance(values[0], str):
847 if isinstance(values[0], str):
847 yield ' '.join(values)
848 yield ' '.join(values)
848 else:
849 else:
849 for v in values:
850 for v in values:
850 yield dict(v, **args)
851 yield dict(v, **args)
851 return
852 return
852 startname = 'start_' + names
853 startname = 'start_' + names
853 if startname in self.t:
854 if startname in self.t:
854 yield self.t(startname, **args)
855 yield self.t(startname, **args)
855 vargs = args.copy()
856 vargs = args.copy()
856 def one(v, tag=name):
857 def one(v, tag=name):
857 try:
858 try:
858 vargs.update(v)
859 vargs.update(v)
859 except (AttributeError, ValueError):
860 except (AttributeError, ValueError):
860 try:
861 try:
861 for a, b in v:
862 for a, b in v:
862 vargs[a] = b
863 vargs[a] = b
863 except ValueError:
864 except ValueError:
864 vargs[name] = v
865 vargs[name] = v
865 return self.t(tag, **vargs)
866 return self.t(tag, **vargs)
866 lastname = 'last_' + name
867 lastname = 'last_' + name
867 if lastname in self.t:
868 if lastname in self.t:
868 last = values.pop()
869 last = values.pop()
869 else:
870 else:
870 last = None
871 last = None
871 for v in values:
872 for v in values:
872 yield one(v)
873 yield one(v)
873 if last is not None:
874 if last is not None:
874 yield one(last, tag=lastname)
875 yield one(last, tag=lastname)
875 endname = 'end_' + names
876 endname = 'end_' + names
876 if endname in self.t:
877 if endname in self.t:
877 yield self.t(endname, **args)
878 yield self.t(endname, **args)
878
879
879 def showbranches(**args):
880 def showbranches(**args):
880 branch = changes[5].get("branch")
881 branch = changes[5].get("branch")
881 if branch != 'default':
882 if branch != 'default':
882 branch = util.tolocal(branch)
883 branch = util.tolocal(branch)
883 return showlist('branch', [branch], plural='branches', **args)
884 return showlist('branch', [branch], plural='branches', **args)
884
885
885 def showparents(**args):
886 def showparents(**args):
886 parents = [[('rev', log.rev(p)), ('node', hex(p))]
887 parents = [[('rev', log.rev(p)), ('node', hex(p))]
887 for p in log.parents(changenode)
888 for p in log.parents(changenode)
888 if self.ui.debugflag or p != nullid]
889 if self.ui.debugflag or p != nullid]
889 if (not self.ui.debugflag and len(parents) == 1 and
890 if (not self.ui.debugflag and len(parents) == 1 and
890 parents[0][0][1] == rev - 1):
891 parents[0][0][1] == rev - 1):
891 return
892 return
892 return showlist('parent', parents, **args)
893 return showlist('parent', parents, **args)
893
894
894 def showtags(**args):
895 def showtags(**args):
895 return showlist('tag', self.repo.nodetags(changenode), **args)
896 return showlist('tag', self.repo.nodetags(changenode), **args)
896
897
897 def showextras(**args):
898 def showextras(**args):
898 extras = changes[5].items()
899 extras = changes[5].items()
899 extras.sort()
900 extras.sort()
900 for key, value in extras:
901 for key, value in extras:
901 args = args.copy()
902 args = args.copy()
902 args.update(dict(key=key, value=value))
903 args.update(dict(key=key, value=value))
903 yield self.t('extra', **args)
904 yield self.t('extra', **args)
904
905
905 def showcopies(**args):
906 def showcopies(**args):
906 c = [{'name': x[0], 'source': x[1]} for x in copies]
907 c = [{'name': x[0], 'source': x[1]} for x in copies]
907 return showlist('file_copy', c, plural='file_copies', **args)
908 return showlist('file_copy', c, plural='file_copies', **args)
908
909
909 if self.ui.debugflag:
910 if self.ui.debugflag:
910 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
911 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
911 def showfiles(**args):
912 def showfiles(**args):
912 return showlist('file', files[0], **args)
913 return showlist('file', files[0], **args)
913 def showadds(**args):
914 def showadds(**args):
914 return showlist('file_add', files[1], **args)
915 return showlist('file_add', files[1], **args)
915 def showdels(**args):
916 def showdels(**args):
916 return showlist('file_del', files[2], **args)
917 return showlist('file_del', files[2], **args)
917 def showmanifest(**args):
918 def showmanifest(**args):
918 args = args.copy()
919 args = args.copy()
919 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
920 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
920 node=hex(changes[0])))
921 node=hex(changes[0])))
921 return self.t('manifest', **args)
922 return self.t('manifest', **args)
922 else:
923 else:
923 def showfiles(**args):
924 def showfiles(**args):
924 return showlist('file', changes[3], **args)
925 return showlist('file', changes[3], **args)
925 showadds = ''
926 showadds = ''
926 showdels = ''
927 showdels = ''
927 showmanifest = ''
928 showmanifest = ''
928
929
929 defprops = {
930 defprops = {
930 'author': changes[1],
931 'author': changes[1],
931 'branches': showbranches,
932 'branches': showbranches,
932 'date': changes[2],
933 'date': changes[2],
933 'desc': changes[4],
934 'desc': changes[4],
934 'file_adds': showadds,
935 'file_adds': showadds,
935 'file_dels': showdels,
936 'file_dels': showdels,
936 'files': showfiles,
937 'files': showfiles,
937 'file_copies': showcopies,
938 'file_copies': showcopies,
938 'manifest': showmanifest,
939 'manifest': showmanifest,
939 'node': hex(changenode),
940 'node': hex(changenode),
940 'parents': showparents,
941 'parents': showparents,
941 'rev': rev,
942 'rev': rev,
942 'tags': showtags,
943 'tags': showtags,
943 'extras': showextras,
944 'extras': showextras,
944 }
945 }
945 props = props.copy()
946 props = props.copy()
946 props.update(defprops)
947 props.update(defprops)
947
948
948 try:
949 try:
949 if self.ui.debugflag and 'header_debug' in self.t:
950 if self.ui.debugflag and 'header_debug' in self.t:
950 key = 'header_debug'
951 key = 'header_debug'
951 elif self.ui.quiet and 'header_quiet' in self.t:
952 elif self.ui.quiet and 'header_quiet' in self.t:
952 key = 'header_quiet'
953 key = 'header_quiet'
953 elif self.ui.verbose and 'header_verbose' in self.t:
954 elif self.ui.verbose and 'header_verbose' in self.t:
954 key = 'header_verbose'
955 key = 'header_verbose'
955 elif 'header' in self.t:
956 elif 'header' in self.t:
956 key = 'header'
957 key = 'header'
957 else:
958 else:
958 key = ''
959 key = ''
959 if key:
960 if key:
960 h = templater.stringify(self.t(key, **props))
961 h = templater.stringify(self.t(key, **props))
961 if self.buffered:
962 if self.buffered:
962 self.header[rev] = h
963 self.header[rev] = h
963 else:
964 else:
964 self.ui.write(h)
965 self.ui.write(h)
965 if self.ui.debugflag and 'changeset_debug' in self.t:
966 if self.ui.debugflag and 'changeset_debug' in self.t:
966 key = 'changeset_debug'
967 key = 'changeset_debug'
967 elif self.ui.quiet and 'changeset_quiet' in self.t:
968 elif self.ui.quiet and 'changeset_quiet' in self.t:
968 key = 'changeset_quiet'
969 key = 'changeset_quiet'
969 elif self.ui.verbose and 'changeset_verbose' in self.t:
970 elif self.ui.verbose and 'changeset_verbose' in self.t:
970 key = 'changeset_verbose'
971 key = 'changeset_verbose'
971 else:
972 else:
972 key = 'changeset'
973 key = 'changeset'
973 self.ui.write(templater.stringify(self.t(key, **props)))
974 self.ui.write(templater.stringify(self.t(key, **props)))
974 self.showpatch(changenode)
975 self.showpatch(changenode)
975 except KeyError, inst:
976 except KeyError, inst:
976 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
977 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
977 inst.args[0]))
978 inst.args[0]))
978 except SyntaxError, inst:
979 except SyntaxError, inst:
979 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
980 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
980
981
981 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
982 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
982 """show one changeset using template or regular display.
983 """show one changeset using template or regular display.
983
984
984 Display format will be the first non-empty hit of:
985 Display format will be the first non-empty hit of:
985 1. option 'template'
986 1. option 'template'
986 2. option 'style'
987 2. option 'style'
987 3. [ui] setting 'logtemplate'
988 3. [ui] setting 'logtemplate'
988 4. [ui] setting 'style'
989 4. [ui] setting 'style'
989 If all of these values are either the unset or the empty string,
990 If all of these values are either the unset or the empty string,
990 regular display via changeset_printer() is done.
991 regular display via changeset_printer() is done.
991 """
992 """
992 # options
993 # options
993 patch = False
994 patch = False
994 if opts.get('patch'):
995 if opts.get('patch'):
995 patch = matchfn or util.always
996 patch = matchfn or util.always
996
997
997 tmpl = opts.get('template')
998 tmpl = opts.get('template')
998 mapfile = None
999 mapfile = None
999 if tmpl:
1000 if tmpl:
1000 tmpl = templater.parsestring(tmpl, quoted=False)
1001 tmpl = templater.parsestring(tmpl, quoted=False)
1001 else:
1002 else:
1002 mapfile = opts.get('style')
1003 mapfile = opts.get('style')
1003 # ui settings
1004 # ui settings
1004 if not mapfile:
1005 if not mapfile:
1005 tmpl = ui.config('ui', 'logtemplate')
1006 tmpl = ui.config('ui', 'logtemplate')
1006 if tmpl:
1007 if tmpl:
1007 tmpl = templater.parsestring(tmpl)
1008 tmpl = templater.parsestring(tmpl)
1008 else:
1009 else:
1009 mapfile = ui.config('ui', 'style')
1010 mapfile = ui.config('ui', 'style')
1010
1011
1011 if tmpl or mapfile:
1012 if tmpl or mapfile:
1012 if mapfile:
1013 if mapfile:
1013 if not os.path.split(mapfile)[0]:
1014 if not os.path.split(mapfile)[0]:
1014 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1015 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1015 or templater.templatepath(mapfile))
1016 or templater.templatepath(mapfile))
1016 if mapname: mapfile = mapname
1017 if mapname: mapfile = mapname
1017 try:
1018 try:
1018 t = changeset_templater(ui, repo, patch, mapfile, buffered)
1019 t = changeset_templater(ui, repo, patch, mapfile, buffered)
1019 except SyntaxError, inst:
1020 except SyntaxError, inst:
1020 raise util.Abort(inst.args[0])
1021 raise util.Abort(inst.args[0])
1021 if tmpl: t.use_template(tmpl)
1022 if tmpl: t.use_template(tmpl)
1022 return t
1023 return t
1023 return changeset_printer(ui, repo, patch, buffered)
1024 return changeset_printer(ui, repo, patch, buffered)
1024
1025
1025 def finddate(ui, repo, date):
1026 def finddate(ui, repo, date):
1026 """Find the tipmost changeset that matches the given date spec"""
1027 """Find the tipmost changeset that matches the given date spec"""
1027 df = util.matchdate(date + " to " + date)
1028 df = util.matchdate(date + " to " + date)
1028 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1029 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
1029 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
1030 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
1030 results = {}
1031 results = {}
1031 for st, rev, fns in changeiter:
1032 for st, rev, fns in changeiter:
1032 if st == 'add':
1033 if st == 'add':
1033 d = get(rev)[2]
1034 d = get(rev)[2]
1034 if df(d[0]):
1035 if df(d[0]):
1035 results[rev] = d
1036 results[rev] = d
1036 elif st == 'iter':
1037 elif st == 'iter':
1037 if rev in results:
1038 if rev in results:
1038 ui.status("Found revision %s from %s\n" %
1039 ui.status("Found revision %s from %s\n" %
1039 (rev, util.datestr(results[rev])))
1040 (rev, util.datestr(results[rev])))
1040 return str(rev)
1041 return str(rev)
1041
1042
1042 raise util.Abort(_("revision matching date not found"))
1043 raise util.Abort(_("revision matching date not found"))
1043
1044
1044 def walkchangerevs(ui, repo, pats, change, opts):
1045 def walkchangerevs(ui, repo, pats, change, opts):
1045 '''Iterate over files and the revs they changed in.
1046 '''Iterate over files and the revs they changed in.
1046
1047
1047 Callers most commonly need to iterate backwards over the history
1048 Callers most commonly need to iterate backwards over the history
1048 it is interested in. Doing so has awful (quadratic-looking)
1049 it is interested in. Doing so has awful (quadratic-looking)
1049 performance, so we use iterators in a "windowed" way.
1050 performance, so we use iterators in a "windowed" way.
1050
1051
1051 We walk a window of revisions in the desired order. Within the
1052 We walk a window of revisions in the desired order. Within the
1052 window, we first walk forwards to gather data, then in the desired
1053 window, we first walk forwards to gather data, then in the desired
1053 order (usually backwards) to display it.
1054 order (usually backwards) to display it.
1054
1055
1055 This function returns an (iterator, matchfn) tuple. The iterator
1056 This function returns an (iterator, matchfn) tuple. The iterator
1056 yields 3-tuples. They will be of one of the following forms:
1057 yields 3-tuples. They will be of one of the following forms:
1057
1058
1058 "window", incrementing, lastrev: stepping through a window,
1059 "window", incrementing, lastrev: stepping through a window,
1059 positive if walking forwards through revs, last rev in the
1060 positive if walking forwards through revs, last rev in the
1060 sequence iterated over - use to reset state for the current window
1061 sequence iterated over - use to reset state for the current window
1061
1062
1062 "add", rev, fns: out-of-order traversal of the given file names
1063 "add", rev, fns: out-of-order traversal of the given file names
1063 fns, which changed during revision rev - use to gather data for
1064 fns, which changed during revision rev - use to gather data for
1064 possible display
1065 possible display
1065
1066
1066 "iter", rev, None: in-order traversal of the revs earlier iterated
1067 "iter", rev, None: in-order traversal of the revs earlier iterated
1067 over with "add" - use to display data'''
1068 over with "add" - use to display data'''
1068
1069
1069 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1070 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1070 if start < end:
1071 if start < end:
1071 while start < end:
1072 while start < end:
1072 yield start, min(windowsize, end-start)
1073 yield start, min(windowsize, end-start)
1073 start += windowsize
1074 start += windowsize
1074 if windowsize < sizelimit:
1075 if windowsize < sizelimit:
1075 windowsize *= 2
1076 windowsize *= 2
1076 else:
1077 else:
1077 while start > end:
1078 while start > end:
1078 yield start, min(windowsize, start-end-1)
1079 yield start, min(windowsize, start-end-1)
1079 start -= windowsize
1080 start -= windowsize
1080 if windowsize < sizelimit:
1081 if windowsize < sizelimit:
1081 windowsize *= 2
1082 windowsize *= 2
1082
1083
1083 files, matchfn, anypats = matchpats(repo, pats, opts)
1084 files, matchfn, anypats = matchpats(repo, pats, opts)
1084 follow = opts.get('follow') or opts.get('follow_first')
1085 follow = opts.get('follow') or opts.get('follow_first')
1085
1086
1086 if repo.changelog.count() == 0:
1087 if repo.changelog.count() == 0:
1087 return [], matchfn
1088 return [], matchfn
1088
1089
1089 if follow:
1090 if follow:
1090 defrange = '%s:0' % repo.changectx().rev()
1091 defrange = '%s:0' % repo.changectx().rev()
1091 else:
1092 else:
1092 defrange = 'tip:0'
1093 defrange = 'tip:0'
1093 revs = revrange(repo, opts['rev'] or [defrange])
1094 revs = revrange(repo, opts['rev'] or [defrange])
1094 wanted = {}
1095 wanted = {}
1095 slowpath = anypats or opts.get('removed')
1096 slowpath = anypats or opts.get('removed')
1096 fncache = {}
1097 fncache = {}
1097
1098
1098 if not slowpath and not files:
1099 if not slowpath and not files:
1099 # No files, no patterns. Display all revs.
1100 # No files, no patterns. Display all revs.
1100 wanted = dict.fromkeys(revs)
1101 wanted = dict.fromkeys(revs)
1101 copies = []
1102 copies = []
1102 if not slowpath:
1103 if not slowpath:
1103 # Only files, no patterns. Check the history of each file.
1104 # Only files, no patterns. Check the history of each file.
1104 def filerevgen(filelog, node):
1105 def filerevgen(filelog, node):
1105 cl_count = repo.changelog.count()
1106 cl_count = repo.changelog.count()
1106 if node is None:
1107 if node is None:
1107 last = filelog.count() - 1
1108 last = filelog.count() - 1
1108 else:
1109 else:
1109 last = filelog.rev(node)
1110 last = filelog.rev(node)
1110 for i, window in increasing_windows(last, nullrev):
1111 for i, window in increasing_windows(last, nullrev):
1111 revs = []
1112 revs = []
1112 for j in xrange(i - window, i + 1):
1113 for j in xrange(i - window, i + 1):
1113 n = filelog.node(j)
1114 n = filelog.node(j)
1114 revs.append((filelog.linkrev(n),
1115 revs.append((filelog.linkrev(n),
1115 follow and filelog.renamed(n)))
1116 follow and filelog.renamed(n)))
1116 revs.reverse()
1117 revs.reverse()
1117 for rev in revs:
1118 for rev in revs:
1118 # only yield rev for which we have the changelog, it can
1119 # only yield rev for which we have the changelog, it can
1119 # happen while doing "hg log" during a pull or commit
1120 # happen while doing "hg log" during a pull or commit
1120 if rev[0] < cl_count:
1121 if rev[0] < cl_count:
1121 yield rev
1122 yield rev
1122 def iterfiles():
1123 def iterfiles():
1123 for filename in files:
1124 for filename in files:
1124 yield filename, None
1125 yield filename, None
1125 for filename_node in copies:
1126 for filename_node in copies:
1126 yield filename_node
1127 yield filename_node
1127 minrev, maxrev = min(revs), max(revs)
1128 minrev, maxrev = min(revs), max(revs)
1128 for file_, node in iterfiles():
1129 for file_, node in iterfiles():
1129 filelog = repo.file(file_)
1130 filelog = repo.file(file_)
1130 # A zero count may be a directory or deleted file, so
1131 # A zero count may be a directory or deleted file, so
1131 # try to find matching entries on the slow path.
1132 # try to find matching entries on the slow path.
1132 if filelog.count() == 0:
1133 if filelog.count() == 0:
1133 slowpath = True
1134 slowpath = True
1134 break
1135 break
1135 for rev, copied in filerevgen(filelog, node):
1136 for rev, copied in filerevgen(filelog, node):
1136 if rev <= maxrev:
1137 if rev <= maxrev:
1137 if rev < minrev:
1138 if rev < minrev:
1138 break
1139 break
1139 fncache.setdefault(rev, [])
1140 fncache.setdefault(rev, [])
1140 fncache[rev].append(file_)
1141 fncache[rev].append(file_)
1141 wanted[rev] = 1
1142 wanted[rev] = 1
1142 if follow and copied:
1143 if follow and copied:
1143 copies.append(copied)
1144 copies.append(copied)
1144 if slowpath:
1145 if slowpath:
1145 if follow:
1146 if follow:
1146 raise util.Abort(_('can only follow copies/renames for explicit '
1147 raise util.Abort(_('can only follow copies/renames for explicit '
1147 'file names'))
1148 'file names'))
1148
1149
1149 # The slow path checks files modified in every changeset.
1150 # The slow path checks files modified in every changeset.
1150 def changerevgen():
1151 def changerevgen():
1151 for i, window in increasing_windows(repo.changelog.count()-1,
1152 for i, window in increasing_windows(repo.changelog.count()-1,
1152 nullrev):
1153 nullrev):
1153 for j in xrange(i - window, i + 1):
1154 for j in xrange(i - window, i + 1):
1154 yield j, change(j)[3]
1155 yield j, change(j)[3]
1155
1156
1156 for rev, changefiles in changerevgen():
1157 for rev, changefiles in changerevgen():
1157 matches = filter(matchfn, changefiles)
1158 matches = filter(matchfn, changefiles)
1158 if matches:
1159 if matches:
1159 fncache[rev] = matches
1160 fncache[rev] = matches
1160 wanted[rev] = 1
1161 wanted[rev] = 1
1161
1162
1162 class followfilter:
1163 class followfilter:
1163 def __init__(self, onlyfirst=False):
1164 def __init__(self, onlyfirst=False):
1164 self.startrev = nullrev
1165 self.startrev = nullrev
1165 self.roots = []
1166 self.roots = []
1166 self.onlyfirst = onlyfirst
1167 self.onlyfirst = onlyfirst
1167
1168
1168 def match(self, rev):
1169 def match(self, rev):
1169 def realparents(rev):
1170 def realparents(rev):
1170 if self.onlyfirst:
1171 if self.onlyfirst:
1171 return repo.changelog.parentrevs(rev)[0:1]
1172 return repo.changelog.parentrevs(rev)[0:1]
1172 else:
1173 else:
1173 return filter(lambda x: x != nullrev,
1174 return filter(lambda x: x != nullrev,
1174 repo.changelog.parentrevs(rev))
1175 repo.changelog.parentrevs(rev))
1175
1176
1176 if self.startrev == nullrev:
1177 if self.startrev == nullrev:
1177 self.startrev = rev
1178 self.startrev = rev
1178 return True
1179 return True
1179
1180
1180 if rev > self.startrev:
1181 if rev > self.startrev:
1181 # forward: all descendants
1182 # forward: all descendants
1182 if not self.roots:
1183 if not self.roots:
1183 self.roots.append(self.startrev)
1184 self.roots.append(self.startrev)
1184 for parent in realparents(rev):
1185 for parent in realparents(rev):
1185 if parent in self.roots:
1186 if parent in self.roots:
1186 self.roots.append(rev)
1187 self.roots.append(rev)
1187 return True
1188 return True
1188 else:
1189 else:
1189 # backwards: all parents
1190 # backwards: all parents
1190 if not self.roots:
1191 if not self.roots:
1191 self.roots.extend(realparents(self.startrev))
1192 self.roots.extend(realparents(self.startrev))
1192 if rev in self.roots:
1193 if rev in self.roots:
1193 self.roots.remove(rev)
1194 self.roots.remove(rev)
1194 self.roots.extend(realparents(rev))
1195 self.roots.extend(realparents(rev))
1195 return True
1196 return True
1196
1197
1197 return False
1198 return False
1198
1199
1199 # it might be worthwhile to do this in the iterator if the rev range
1200 # it might be worthwhile to do this in the iterator if the rev range
1200 # is descending and the prune args are all within that range
1201 # is descending and the prune args are all within that range
1201 for rev in opts.get('prune', ()):
1202 for rev in opts.get('prune', ()):
1202 rev = repo.changelog.rev(repo.lookup(rev))
1203 rev = repo.changelog.rev(repo.lookup(rev))
1203 ff = followfilter()
1204 ff = followfilter()
1204 stop = min(revs[0], revs[-1])
1205 stop = min(revs[0], revs[-1])
1205 for x in xrange(rev, stop-1, -1):
1206 for x in xrange(rev, stop-1, -1):
1206 if ff.match(x) and x in wanted:
1207 if ff.match(x) and x in wanted:
1207 del wanted[x]
1208 del wanted[x]
1208
1209
1209 def iterate():
1210 def iterate():
1210 if follow and not files:
1211 if follow and not files:
1211 ff = followfilter(onlyfirst=opts.get('follow_first'))
1212 ff = followfilter(onlyfirst=opts.get('follow_first'))
1212 def want(rev):
1213 def want(rev):
1213 if ff.match(rev) and rev in wanted:
1214 if ff.match(rev) and rev in wanted:
1214 return True
1215 return True
1215 return False
1216 return False
1216 else:
1217 else:
1217 def want(rev):
1218 def want(rev):
1218 return rev in wanted
1219 return rev in wanted
1219
1220
1220 for i, window in increasing_windows(0, len(revs)):
1221 for i, window in increasing_windows(0, len(revs)):
1221 yield 'window', revs[0] < revs[-1], revs[-1]
1222 yield 'window', revs[0] < revs[-1], revs[-1]
1222 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1223 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1223 srevs = list(nrevs)
1224 srevs = list(nrevs)
1224 srevs.sort()
1225 srevs.sort()
1225 for rev in srevs:
1226 for rev in srevs:
1226 fns = fncache.get(rev)
1227 fns = fncache.get(rev)
1227 if not fns:
1228 if not fns:
1228 def fns_generator():
1229 def fns_generator():
1229 for f in change(rev)[3]:
1230 for f in change(rev)[3]:
1230 if matchfn(f):
1231 if matchfn(f):
1231 yield f
1232 yield f
1232 fns = fns_generator()
1233 fns = fns_generator()
1233 yield 'add', rev, fns
1234 yield 'add', rev, fns
1234 for rev in nrevs:
1235 for rev in nrevs:
1235 yield 'iter', rev, None
1236 yield 'iter', rev, None
1236 return iterate(), matchfn
1237 return iterate(), matchfn
General Comments 0
You need to be logged in to leave comments. Login now