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