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