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