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