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