##// END OF EJS Templates
Smarter handling of revlog key errors...
mpm@selenic.com -
r1214:34706a83 default
parent child Browse files
Show More
@@ -1,2005 +1,2007 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 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 demandload import demandload
9 9 from node import *
10 10 demandload(globals(), "os re sys signal shutil imp")
11 11 demandload(globals(), "fancyopts ui hg util lock revlog")
12 12 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
13 13 demandload(globals(), "errno socket version struct atexit sets")
14 14
15 15 class UnknownCommand(Exception):
16 16 """Exception raised if command is not in the command table."""
17 17
18 18 def filterfiles(filters, files):
19 19 l = [x for x in files if x in filters]
20 20
21 21 for t in filters:
22 22 if t and t[-1] != "/":
23 23 t += "/"
24 24 l += [x for x in files if x.startswith(t)]
25 25 return l
26 26
27 27 def relpath(repo, args):
28 28 cwd = repo.getcwd()
29 29 if cwd:
30 30 return [util.normpath(os.path.join(cwd, x)) for x in args]
31 31 return args
32 32
33 33 def matchpats(repo, cwd, pats=[], opts={}, head=''):
34 34 return util.matcher(repo.root, cwd, pats or ['.'], opts.get('include'),
35 35 opts.get('exclude'), head)
36 36
37 37 def makewalk(repo, pats, opts, head=''):
38 38 cwd = repo.getcwd()
39 39 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
40 40 exact = dict(zip(files, files))
41 41 def walk():
42 42 for src, fn in repo.walk(files=files, match=matchfn):
43 43 yield src, fn, util.pathto(cwd, fn), fn in exact
44 44 return files, matchfn, walk()
45 45
46 46 def walk(repo, pats, opts, head=''):
47 47 files, matchfn, results = makewalk(repo, pats, opts, head)
48 48 for r in results:
49 49 yield r
50 50
51 51 def walkchangerevs(ui, repo, cwd, pats, opts):
52 52 '''Iterate over files and the revs they changed in.
53 53
54 54 Callers most commonly need to iterate backwards over the history
55 55 it is interested in. Doing so has awful (quadratic-looking)
56 56 performance, so we use iterators in a "windowed" way.
57 57
58 58 We walk a window of revisions in the desired order. Within the
59 59 window, we first walk forwards to gather data, then in the desired
60 60 order (usually backwards) to display it.
61 61
62 62 This function returns an (iterator, getchange) pair. The
63 63 getchange function returns the changelog entry for a numeric
64 64 revision. The iterator yields 3-tuples. They will be of one of
65 65 the following forms:
66 66
67 67 "window", incrementing, lastrev: stepping through a window,
68 68 positive if walking forwards through revs, last rev in the
69 69 sequence iterated over - use to reset state for the current window
70 70
71 71 "add", rev, fns: out-of-order traversal of the given file names
72 72 fns, which changed during revision rev - use to gather data for
73 73 possible display
74 74
75 75 "iter", rev, None: in-order traversal of the revs earlier iterated
76 76 over with "add" - use to display data'''
77 77 cwd = repo.getcwd()
78 78 if not pats and cwd:
79 79 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
80 80 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
81 81 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
82 82 pats, opts)
83 83 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
84 84 wanted = {}
85 85 slowpath = anypats
86 86 window = 300
87 87 fncache = {}
88 88
89 89 chcache = {}
90 90 def getchange(rev):
91 91 ch = chcache.get(rev)
92 92 if ch is None:
93 93 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
94 94 return ch
95 95
96 96 if not slowpath and not files:
97 97 # No files, no patterns. Display all revs.
98 98 wanted = dict(zip(revs, revs))
99 99 if not slowpath:
100 100 # Only files, no patterns. Check the history of each file.
101 101 def filerevgen(filelog):
102 102 for i in xrange(filelog.count() - 1, -1, -window):
103 103 revs = []
104 104 for j in xrange(max(0, i - window), i + 1):
105 105 revs.append(filelog.linkrev(filelog.node(j)))
106 106 revs.reverse()
107 107 for rev in revs:
108 108 yield rev
109 109
110 110 minrev, maxrev = min(revs), max(revs)
111 111 for file in files:
112 112 filelog = repo.file(file)
113 113 # A zero count may be a directory or deleted file, so
114 114 # try to find matching entries on the slow path.
115 115 if filelog.count() == 0:
116 116 slowpath = True
117 117 break
118 118 for rev in filerevgen(filelog):
119 119 if rev <= maxrev:
120 120 if rev < minrev:
121 121 break
122 122 fncache.setdefault(rev, [])
123 123 fncache[rev].append(file)
124 124 wanted[rev] = 1
125 125 if slowpath:
126 126 # The slow path checks files modified in every changeset.
127 127 def changerevgen():
128 128 for i in xrange(repo.changelog.count() - 1, -1, -window):
129 129 for j in xrange(max(0, i - window), i + 1):
130 130 yield j, getchange(j)[3]
131 131
132 132 for rev, changefiles in changerevgen():
133 133 matches = filter(matchfn, changefiles)
134 134 if matches:
135 135 fncache[rev] = matches
136 136 wanted[rev] = 1
137 137
138 138 def iterate():
139 139 for i in xrange(0, len(revs), window):
140 140 yield 'window', revs[0] < revs[-1], revs[-1]
141 141 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
142 142 if rev in wanted]
143 143 srevs = list(nrevs)
144 144 srevs.sort()
145 145 for rev in srevs:
146 146 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
147 147 yield 'add', rev, fns
148 148 for rev in nrevs:
149 149 yield 'iter', rev, None
150 150 return iterate(), getchange
151 151
152 152 revrangesep = ':'
153 153
154 154 def revrange(ui, repo, revs, revlog=None):
155 155 """Yield revision as strings from a list of revision specifications."""
156 156 if revlog is None:
157 157 revlog = repo.changelog
158 158 revcount = revlog.count()
159 159 def fix(val, defval):
160 160 if not val:
161 161 return defval
162 162 try:
163 163 num = int(val)
164 164 if str(num) != val:
165 165 raise ValueError
166 166 if num < 0:
167 167 num += revcount
168 168 if not (0 <= num < revcount):
169 169 raise ValueError
170 170 except ValueError:
171 171 try:
172 172 num = repo.changelog.rev(repo.lookup(val))
173 173 except KeyError:
174 174 try:
175 175 num = revlog.rev(revlog.lookup(val))
176 176 except KeyError:
177 177 raise util.Abort('invalid revision identifier %s', val)
178 178 return num
179 179 seen = {}
180 180 for spec in revs:
181 181 if spec.find(revrangesep) >= 0:
182 182 start, end = spec.split(revrangesep, 1)
183 183 start = fix(start, 0)
184 184 end = fix(end, revcount - 1)
185 185 step = start > end and -1 or 1
186 186 for rev in xrange(start, end+step, step):
187 187 if rev in seen: continue
188 188 seen[rev] = 1
189 189 yield str(rev)
190 190 else:
191 191 rev = fix(spec, None)
192 192 if rev in seen: continue
193 193 seen[rev] = 1
194 194 yield str(rev)
195 195
196 196 def make_filename(repo, r, pat, node=None,
197 197 total=None, seqno=None, revwidth=None):
198 198 node_expander = {
199 199 'H': lambda: hex(node),
200 200 'R': lambda: str(r.rev(node)),
201 201 'h': lambda: short(node),
202 202 }
203 203 expander = {
204 204 '%': lambda: '%',
205 205 'b': lambda: os.path.basename(repo.root),
206 206 }
207 207
208 208 try:
209 209 if node:
210 210 expander.update(node_expander)
211 211 if node and revwidth is not None:
212 212 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
213 213 if total is not None:
214 214 expander['N'] = lambda: str(total)
215 215 if seqno is not None:
216 216 expander['n'] = lambda: str(seqno)
217 217 if total is not None and seqno is not None:
218 218 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
219 219
220 220 newname = []
221 221 patlen = len(pat)
222 222 i = 0
223 223 while i < patlen:
224 224 c = pat[i]
225 225 if c == '%':
226 226 i += 1
227 227 c = pat[i]
228 228 c = expander[c]()
229 229 newname.append(c)
230 230 i += 1
231 231 return ''.join(newname)
232 232 except KeyError, inst:
233 233 raise util.Abort("invalid format spec '%%%s' in output file name",
234 234 inst.args[0])
235 235
236 236 def make_file(repo, r, pat, node=None,
237 237 total=None, seqno=None, revwidth=None, mode='wb'):
238 238 if not pat or pat == '-':
239 239 return 'w' in mode and sys.stdout or sys.stdin
240 240 if hasattr(pat, 'write') and 'w' in mode:
241 241 return pat
242 242 if hasattr(pat, 'read') and 'r' in mode:
243 243 return pat
244 244 return open(make_filename(repo, r, pat, node, total, seqno, revwidth),
245 245 mode)
246 246
247 247 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
248 248 changes=None, text=False):
249 249 def date(c):
250 250 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
251 251
252 252 if not changes:
253 253 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
254 254 else:
255 255 (c, a, d, u) = changes
256 256 if files:
257 257 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
258 258
259 259 if not c and not a and not d:
260 260 return
261 261
262 262 if node2:
263 263 change = repo.changelog.read(node2)
264 264 mmap2 = repo.manifest.read(change[0])
265 265 date2 = date(change)
266 266 def read(f):
267 267 return repo.file(f).read(mmap2[f])
268 268 else:
269 269 date2 = time.asctime()
270 270 if not node1:
271 271 node1 = repo.dirstate.parents()[0]
272 272 def read(f):
273 273 return repo.wfile(f).read()
274 274
275 275 if ui.quiet:
276 276 r = None
277 277 else:
278 278 hexfunc = ui.verbose and hex or short
279 279 r = [hexfunc(node) for node in [node1, node2] if node]
280 280
281 281 change = repo.changelog.read(node1)
282 282 mmap = repo.manifest.read(change[0])
283 283 date1 = date(change)
284 284
285 285 for f in c:
286 286 to = None
287 287 if f in mmap:
288 288 to = repo.file(f).read(mmap[f])
289 289 tn = read(f)
290 290 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
291 291 for f in a:
292 292 to = None
293 293 tn = read(f)
294 294 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
295 295 for f in d:
296 296 to = repo.file(f).read(mmap[f])
297 297 tn = None
298 298 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
299 299
300 300 def trimuser(ui, name, rev, revcache):
301 301 """trim the name of the user who committed a change"""
302 302 user = revcache.get(rev)
303 303 if user is None:
304 304 user = revcache[rev] = ui.shortuser(name)
305 305 return user
306 306
307 307 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
308 308 """show a single changeset or file revision"""
309 309 log = repo.changelog
310 310 if changenode is None:
311 311 changenode = log.node(rev)
312 312 elif not rev:
313 313 rev = log.rev(changenode)
314 314
315 315 if ui.quiet:
316 316 ui.write("%d:%s\n" % (rev, short(changenode)))
317 317 return
318 318
319 319 changes = log.read(changenode)
320 320
321 321 t, tz = changes[2].split(' ')
322 322 # a conversion tool was sticking non-integer offsets into repos
323 323 try:
324 324 tz = int(tz)
325 325 except ValueError:
326 326 tz = 0
327 327 date = time.asctime(time.localtime(float(t))) + " %+05d" % (int(tz)/-36)
328 328
329 329 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
330 330 for p in log.parents(changenode)
331 331 if ui.debugflag or p != nullid]
332 332 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
333 333 parents = []
334 334
335 335 if ui.verbose:
336 336 ui.write("changeset: %d:%s\n" % (rev, hex(changenode)))
337 337 else:
338 338 ui.write("changeset: %d:%s\n" % (rev, short(changenode)))
339 339
340 340 for tag in repo.nodetags(changenode):
341 341 ui.status("tag: %s\n" % tag)
342 342 for parent in parents:
343 343 ui.write("parent: %d:%s\n" % parent)
344 344
345 345 if brinfo and changenode in brinfo:
346 346 br = brinfo[changenode]
347 347 ui.write("branch: %s\n" % " ".join(br))
348 348
349 349 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
350 350 hex(changes[0])))
351 351 ui.status("user: %s\n" % changes[1])
352 352 ui.status("date: %s\n" % date)
353 353
354 354 if ui.debugflag:
355 355 files = repo.changes(log.parents(changenode)[0], changenode)
356 356 for key, value in zip(["files:", "files+:", "files-:"], files):
357 357 if value:
358 358 ui.note("%-12s %s\n" % (key, " ".join(value)))
359 359 else:
360 360 ui.note("files: %s\n" % " ".join(changes[3]))
361 361
362 362 description = changes[4].strip()
363 363 if description:
364 364 if ui.verbose:
365 365 ui.status("description:\n")
366 366 ui.status(description)
367 367 ui.status("\n\n")
368 368 else:
369 369 ui.status("summary: %s\n" % description.splitlines()[0])
370 370 ui.status("\n")
371 371
372 372 def show_version(ui):
373 373 """output version and copyright information"""
374 374 ui.write("Mercurial Distributed SCM (version %s)\n"
375 375 % version.get_version())
376 376 ui.status(
377 377 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
378 378 "This is free software; see the source for copying conditions. "
379 379 "There is NO\nwarranty; "
380 380 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
381 381 )
382 382
383 383 def help_(ui, cmd=None, with_version=False):
384 384 """show help for a given command or all commands"""
385 385 option_lists = []
386 386 if cmd and cmd != 'shortlist':
387 387 if with_version:
388 388 show_version(ui)
389 389 ui.write('\n')
390 390 key, i = find(cmd)
391 391 # synopsis
392 392 ui.write("%s\n\n" % i[2])
393 393
394 394 # description
395 395 doc = i[0].__doc__
396 396 if ui.quiet:
397 397 doc = doc.splitlines(0)[0]
398 398 ui.write("%s\n" % doc.rstrip())
399 399
400 400 if not ui.quiet:
401 401 # aliases
402 402 aliases = ', '.join(key.split('|')[1:])
403 403 if aliases:
404 404 ui.write("\naliases: %s\n" % aliases)
405 405
406 406 # options
407 407 if i[1]:
408 408 option_lists.append(("options", i[1]))
409 409
410 410 else:
411 411 # program name
412 412 if ui.verbose or with_version:
413 413 show_version(ui)
414 414 else:
415 415 ui.status("Mercurial Distributed SCM\n")
416 416 ui.status('\n')
417 417
418 418 # list of commands
419 419 if cmd == "shortlist":
420 420 ui.status('basic commands (use "hg help" '
421 421 'for the full list or option "-v" for details):\n\n')
422 422 elif ui.verbose:
423 423 ui.status('list of commands:\n\n')
424 424 else:
425 425 ui.status('list of commands (use "hg help -v" '
426 426 'to show aliases and global options):\n\n')
427 427
428 428 h = {}
429 429 cmds = {}
430 430 for c, e in table.items():
431 431 f = c.split("|")[0]
432 432 if cmd == "shortlist" and not f.startswith("^"):
433 433 continue
434 434 f = f.lstrip("^")
435 435 if not ui.debugflag and f.startswith("debug"):
436 436 continue
437 437 d = ""
438 438 if e[0].__doc__:
439 439 d = e[0].__doc__.splitlines(0)[0].rstrip()
440 440 h[f] = d
441 441 cmds[f]=c.lstrip("^")
442 442
443 443 fns = h.keys()
444 444 fns.sort()
445 445 m = max(map(len, fns))
446 446 for f in fns:
447 447 if ui.verbose:
448 448 commands = cmds[f].replace("|",", ")
449 449 ui.write(" %s:\n %s\n"%(commands,h[f]))
450 450 else:
451 451 ui.write(' %-*s %s\n' % (m, f, h[f]))
452 452
453 453 # global options
454 454 if ui.verbose:
455 455 option_lists.append(("global options", globalopts))
456 456
457 457 # list all option lists
458 458 opt_output = []
459 459 for title, options in option_lists:
460 460 opt_output.append(("\n%s:\n" % title, None))
461 461 for shortopt, longopt, default, desc in options:
462 462 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
463 463 longopt and " --%s" % longopt),
464 464 "%s%s" % (desc,
465 465 default and " (default: %s)" % default
466 466 or "")))
467 467
468 468 if opt_output:
469 469 opts_len = max([len(line[0]) for line in opt_output if line[1]])
470 470 for first, second in opt_output:
471 471 if second:
472 472 ui.write(" %-*s %s\n" % (opts_len, first, second))
473 473 else:
474 474 ui.write("%s\n" % first)
475 475
476 476 # Commands start here, listed alphabetically
477 477
478 478 def add(ui, repo, *pats, **opts):
479 479 '''add the specified files on the next commit'''
480 480 names = []
481 481 for src, abs, rel, exact in walk(repo, pats, opts):
482 482 if exact:
483 483 names.append(abs)
484 484 elif repo.dirstate.state(abs) == '?':
485 485 ui.status('adding %s\n' % rel)
486 486 names.append(abs)
487 487 repo.add(names)
488 488
489 489 def addremove(ui, repo, *pats, **opts):
490 490 """add all new files, delete all missing files"""
491 491 add, remove = [], []
492 492 for src, abs, rel, exact in walk(repo, pats, opts):
493 493 if src == 'f' and repo.dirstate.state(abs) == '?':
494 494 add.append(abs)
495 495 if not exact:
496 496 ui.status('adding ', rel, '\n')
497 497 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
498 498 remove.append(abs)
499 499 if not exact:
500 500 ui.status('removing ', rel, '\n')
501 501 repo.add(add)
502 502 repo.remove(remove)
503 503
504 504 def annotate(ui, repo, *pats, **opts):
505 505 """show changeset information per file line"""
506 506 def getnode(rev):
507 507 return short(repo.changelog.node(rev))
508 508
509 509 ucache = {}
510 510 def getname(rev):
511 511 cl = repo.changelog.read(repo.changelog.node(rev))
512 512 return trimuser(ui, cl[1], rev, ucache)
513 513
514 514 if not pats:
515 515 raise util.Abort('at least one file name or pattern required')
516 516
517 517 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
518 518 if not opts['user'] and not opts['changeset']:
519 519 opts['number'] = 1
520 520
521 521 if opts['rev']:
522 522 node = repo.changelog.lookup(opts['rev'])
523 523 else:
524 524 node = repo.dirstate.parents()[0]
525 525 change = repo.changelog.read(node)
526 526 mmap = repo.manifest.read(change[0])
527 527
528 528 for src, abs, rel, exact in walk(repo, pats, opts):
529 529 if abs not in mmap:
530 530 ui.warn("warning: %s is not in the repository!\n" % rel)
531 531 continue
532 532
533 533 f = repo.file(abs)
534 534 if not opts['text'] and util.binary(f.read(mmap[abs])):
535 535 ui.write("%s: binary file\n" % rel)
536 536 continue
537 537
538 538 lines = f.annotate(mmap[abs])
539 539 pieces = []
540 540
541 541 for o, f in opmap:
542 542 if opts[o]:
543 543 l = [f(n) for n, dummy in lines]
544 544 if l:
545 545 m = max(map(len, l))
546 546 pieces.append(["%*s" % (m, x) for x in l])
547 547
548 548 if pieces:
549 549 for p, l in zip(zip(*pieces), lines):
550 550 ui.write("%s: %s" % (" ".join(p), l[1]))
551 551
552 552 def cat(ui, repo, file1, rev=None, **opts):
553 553 """output the latest or given revision of a file"""
554 554 r = repo.file(relpath(repo, [file1])[0])
555 555 if rev:
556 556 try:
557 557 # assume all revision numbers are for changesets
558 558 n = repo.lookup(rev)
559 559 change = repo.changelog.read(n)
560 560 m = repo.manifest.read(change[0])
561 561 n = m[relpath(repo, [file1])[0]]
562 562 except hg.RepoError, KeyError:
563 563 n = r.lookup(rev)
564 564 else:
565 565 n = r.tip()
566 566 fp = make_file(repo, r, opts['output'], node=n)
567 567 fp.write(r.read(n))
568 568
569 569 def clone(ui, source, dest=None, **opts):
570 570 """make a copy of an existing repository"""
571 571 if dest is None:
572 572 dest = os.path.basename(os.path.normpath(source))
573 573
574 574 if os.path.exists(dest):
575 575 ui.warn("abort: destination '%s' already exists\n" % dest)
576 576 return 1
577 577
578 578 dest = os.path.realpath(dest)
579 579
580 580 class Dircleanup:
581 581 def __init__(self, dir_):
582 582 self.rmtree = shutil.rmtree
583 583 self.dir_ = dir_
584 584 os.mkdir(dir_)
585 585 def close(self):
586 586 self.dir_ = None
587 587 def __del__(self):
588 588 if self.dir_:
589 589 self.rmtree(self.dir_, True)
590 590
591 591 if opts['ssh']:
592 592 ui.setconfig("ui", "ssh", opts['ssh'])
593 593 if opts['remotecmd']:
594 594 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
595 595
596 596 d = Dircleanup(dest)
597 597 source = ui.expandpath(source)
598 598 abspath = source
599 599 other = hg.repository(ui, source)
600 600
601 601 if other.dev() != -1:
602 602 abspath = os.path.abspath(source)
603 603 copyfile = (os.stat(dest).st_dev == other.dev()
604 604 and getattr(os, 'link', None) or shutil.copy2)
605 605 if copyfile is not shutil.copy2:
606 606 ui.note("cloning by hardlink\n")
607 607
608 608 # we use a lock here because if we race with commit, we can
609 609 # end up with extra data in the cloned revlogs that's not
610 610 # pointed to by changesets, thus causing verify to fail
611 611 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
612 612
613 613 # and here to avoid premature writing to the target
614 614 os.mkdir(os.path.join(dest, ".hg"))
615 615 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
616 616
617 617 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
618 618 for f in files.split():
619 619 src = os.path.join(source, ".hg", f)
620 620 dst = os.path.join(dest, ".hg", f)
621 621 util.copyfiles(src, dst, copyfile)
622 622
623 623 repo = hg.repository(ui, dest)
624 624
625 625 else:
626 626 repo = hg.repository(ui, dest, create=1)
627 627 repo.pull(other)
628 628
629 629 f = repo.opener("hgrc", "w")
630 630 f.write("[paths]\n")
631 631 f.write("default = %s\n" % abspath)
632 632
633 633 if not opts['noupdate']:
634 634 update(ui, repo)
635 635
636 636 d.close()
637 637
638 638 def commit(ui, repo, *pats, **opts):
639 639 """commit the specified files or all outstanding changes"""
640 640 if opts['text']:
641 641 ui.warn("Warning: -t and --text is deprecated,"
642 642 " please use -m or --message instead.\n")
643 643 message = opts['message'] or opts['text']
644 644 logfile = opts['logfile']
645 645 if not message and logfile:
646 646 try:
647 647 if logfile == '-':
648 648 message = sys.stdin.read()
649 649 else:
650 650 message = open(logfile).read()
651 651 except IOError, why:
652 652 ui.warn("Can't read commit message %s: %s\n" % (logfile, why))
653 653
654 654 if opts['addremove']:
655 655 addremove(ui, repo, *pats, **opts)
656 656 cwd = repo.getcwd()
657 657 if not pats and cwd:
658 658 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
659 659 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
660 660 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
661 661 pats, opts)
662 662 if pats:
663 663 c, a, d, u = repo.changes(files=fns, match=match)
664 664 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
665 665 else:
666 666 files = []
667 667 try:
668 668 repo.commit(files, message, opts['user'], opts['date'], match)
669 669 except ValueError, inst:
670 670 raise util.Abort(str(inst))
671 671
672 672 def copy(ui, repo, source, dest):
673 673 """mark a file as copied or renamed for the next commit"""
674 674 return repo.copy(*relpath(repo, (source, dest)))
675 675
676 676 def debugcheckstate(ui, repo):
677 677 """validate the correctness of the current dirstate"""
678 678 parent1, parent2 = repo.dirstate.parents()
679 679 repo.dirstate.read()
680 680 dc = repo.dirstate.map
681 681 keys = dc.keys()
682 682 keys.sort()
683 683 m1n = repo.changelog.read(parent1)[0]
684 684 m2n = repo.changelog.read(parent2)[0]
685 685 m1 = repo.manifest.read(m1n)
686 686 m2 = repo.manifest.read(m2n)
687 687 errors = 0
688 688 for f in dc:
689 689 state = repo.dirstate.state(f)
690 690 if state in "nr" and f not in m1:
691 691 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
692 692 errors += 1
693 693 if state in "a" and f in m1:
694 694 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
695 695 errors += 1
696 696 if state in "m" and f not in m1 and f not in m2:
697 697 ui.warn("%s in state %s, but not in either manifest\n" %
698 698 (f, state))
699 699 errors += 1
700 700 for f in m1:
701 701 state = repo.dirstate.state(f)
702 702 if state not in "nrm":
703 703 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
704 704 errors += 1
705 705 if errors:
706 706 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
707 707
708 708 def debugconfig(ui):
709 709 """show combined config settings from all hgrc files"""
710 710 try:
711 711 repo = hg.repository(ui)
712 712 except hg.RepoError:
713 713 pass
714 714 for section, name, value in ui.walkconfig():
715 715 ui.write('%s.%s=%s\n' % (section, name, value))
716 716
717 717 def debugstate(ui, repo):
718 718 """show the contents of the current dirstate"""
719 719 repo.dirstate.read()
720 720 dc = repo.dirstate.map
721 721 keys = dc.keys()
722 722 keys.sort()
723 723 for file_ in keys:
724 724 ui.write("%c %3o %10d %s %s\n"
725 725 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
726 726 time.strftime("%x %X",
727 727 time.localtime(dc[file_][3])), file_))
728 728 for f in repo.dirstate.copies:
729 729 ui.write("copy: %s -> %s\n" % (repo.dirstate.copies[f], f))
730 730
731 731 def debugdata(ui, file_, rev):
732 732 """dump the contents of an data file revision"""
733 733 r = revlog.revlog(file, file_[:-2] + ".i", file_)
734 734 ui.write(r.revision(r.lookup(rev)))
735 735
736 736 def debugindex(ui, file_):
737 737 """dump the contents of an index file"""
738 738 r = revlog.revlog(file, file_, "")
739 739 ui.write(" rev offset length base linkrev" +
740 740 " nodeid p1 p2\n")
741 741 for i in range(r.count()):
742 742 e = r.index[i]
743 743 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
744 744 i, e[0], e[1], e[2], e[3],
745 745 short(e[6]), short(e[4]), short(e[5])))
746 746
747 747 def debugindexdot(ui, file_):
748 748 """dump an index DAG as a .dot file"""
749 749 r = revlog.revlog(file, file_, "")
750 750 ui.write("digraph G {\n")
751 751 for i in range(r.count()):
752 752 e = r.index[i]
753 753 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
754 754 if e[5] != nullid:
755 755 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
756 756 ui.write("}\n")
757 757
758 758 def debugrename(ui, repo, file, rev=None):
759 759 """dump rename information"""
760 760 r = repo.file(relpath(repo, [file])[0])
761 761 if rev:
762 762 try:
763 763 # assume all revision numbers are for changesets
764 764 n = repo.lookup(rev)
765 765 change = repo.changelog.read(n)
766 766 m = repo.manifest.read(change[0])
767 767 n = m[relpath(repo, [file])[0]]
768 768 except hg.RepoError, KeyError:
769 769 n = r.lookup(rev)
770 770 else:
771 771 n = r.tip()
772 772 m = r.renamed(n)
773 773 if m:
774 774 ui.write("renamed from %s:%s\n" % (m[0], hex(m[1])))
775 775 else:
776 776 ui.write("not renamed\n")
777 777
778 778 def debugwalk(ui, repo, *pats, **opts):
779 779 """show how files match on given patterns"""
780 780 items = list(walk(repo, pats, opts))
781 781 if not items:
782 782 return
783 783 fmt = '%%s %%-%ds %%-%ds %%s\n' % (
784 784 max([len(abs) for (src, abs, rel, exact) in items]),
785 785 max([len(rel) for (src, abs, rel, exact) in items]))
786 786 for src, abs, rel, exact in items:
787 787 ui.write(fmt % (src, abs, rel, exact and 'exact' or ''))
788 788
789 789 def diff(ui, repo, *pats, **opts):
790 790 """diff working directory (or selected files)"""
791 791 node1, node2 = None, None
792 792 revs = [repo.lookup(x) for x in opts['rev']]
793 793
794 794 if len(revs) > 0:
795 795 node1 = revs[0]
796 796 if len(revs) > 1:
797 797 node2 = revs[1]
798 798 if len(revs) > 2:
799 799 raise util.Abort("too many revisions to diff")
800 800
801 801 files = []
802 802 match = util.always
803 803 if pats:
804 804 roots, match, results = makewalk(repo, pats, opts)
805 805 for src, abs, rel, exact in results:
806 806 files.append(abs)
807 807
808 808 dodiff(sys.stdout, ui, repo, node1, node2, files, match=match,
809 809 text=opts['text'])
810 810
811 811 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
812 812 node = repo.lookup(changeset)
813 813 prev, other = repo.changelog.parents(node)
814 814 change = repo.changelog.read(node)
815 815
816 816 fp = make_file(repo, repo.changelog, opts['output'],
817 817 node=node, total=total, seqno=seqno,
818 818 revwidth=revwidth)
819 819 if fp != sys.stdout:
820 820 ui.note("%s\n" % fp.name)
821 821
822 822 fp.write("# HG changeset patch\n")
823 823 fp.write("# User %s\n" % change[1])
824 824 fp.write("# Node ID %s\n" % hex(node))
825 825 fp.write("# Parent %s\n" % hex(prev))
826 826 if other != nullid:
827 827 fp.write("# Parent %s\n" % hex(other))
828 828 fp.write(change[4].rstrip())
829 829 fp.write("\n\n")
830 830
831 831 dodiff(fp, ui, repo, prev, node, text=opts['text'])
832 832 if fp != sys.stdout:
833 833 fp.close()
834 834
835 835 def export(ui, repo, *changesets, **opts):
836 836 """dump the header and diffs for one or more changesets"""
837 837 if not changesets:
838 838 raise util.Abort("export requires at least one changeset")
839 839 seqno = 0
840 840 revs = list(revrange(ui, repo, changesets))
841 841 total = len(revs)
842 842 revwidth = max(map(len, revs))
843 843 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
844 844 for cset in revs:
845 845 seqno += 1
846 846 doexport(ui, repo, cset, seqno, total, revwidth, opts)
847 847
848 848 def forget(ui, repo, *pats, **opts):
849 849 """don't add the specified files on the next commit"""
850 850 forget = []
851 851 for src, abs, rel, exact in walk(repo, pats, opts):
852 852 if repo.dirstate.state(abs) == 'a':
853 853 forget.append(abs)
854 854 if not exact:
855 855 ui.status('forgetting ', rel, '\n')
856 856 repo.forget(forget)
857 857
858 858 def grep(ui, repo, pattern, *pats, **opts):
859 859 """search for a pattern in specified files and revisions"""
860 860 reflags = 0
861 861 if opts['ignore_case']:
862 862 reflags |= re.I
863 863 regexp = re.compile(pattern, reflags)
864 864 sep, eol = ':', '\n'
865 865 if opts['print0']:
866 866 sep = eol = '\0'
867 867
868 868 fcache = {}
869 869 def getfile(fn):
870 870 if fn not in fcache:
871 871 fcache[fn] = repo.file(fn)
872 872 return fcache[fn]
873 873
874 874 def matchlines(body):
875 875 begin = 0
876 876 linenum = 0
877 877 while True:
878 878 match = regexp.search(body, begin)
879 879 if not match:
880 880 break
881 881 mstart, mend = match.span()
882 882 linenum += body.count('\n', begin, mstart) + 1
883 883 lstart = body.rfind('\n', begin, mstart) + 1 or begin
884 884 lend = body.find('\n', mend)
885 885 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
886 886 begin = lend + 1
887 887
888 888 class linestate:
889 889 def __init__(self, line, linenum, colstart, colend):
890 890 self.line = line
891 891 self.linenum = linenum
892 892 self.colstart = colstart
893 893 self.colend = colend
894 894 def __eq__(self, other):
895 895 return self.line == other.line
896 896 def __hash__(self):
897 897 return hash(self.line)
898 898
899 899 matches = {}
900 900 def grepbody(fn, rev, body):
901 901 matches[rev].setdefault(fn, {})
902 902 m = matches[rev][fn]
903 903 for lnum, cstart, cend, line in matchlines(body):
904 904 s = linestate(line, lnum, cstart, cend)
905 905 m[s] = s
906 906
907 907 prev = {}
908 908 ucache = {}
909 909 def display(fn, rev, states, prevstates):
910 910 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
911 911 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
912 912 counts = {'-': 0, '+': 0}
913 913 filerevmatches = {}
914 914 for l in diff:
915 915 if incrementing or not opts['all']:
916 916 change = ((l in prevstates) and '-') or '+'
917 917 r = rev
918 918 else:
919 919 change = ((l in states) and '-') or '+'
920 920 r = prev[fn]
921 921 cols = [fn, str(rev)]
922 922 if opts['line_number']: cols.append(str(l.linenum))
923 923 if opts['all']: cols.append(change)
924 924 if opts['user']: cols.append(trimuser(ui, getchange(rev)[1], rev,
925 925 ucache))
926 926 if opts['files_with_matches']:
927 927 c = (fn, rev)
928 928 if c in filerevmatches: continue
929 929 filerevmatches[c] = 1
930 930 else:
931 931 cols.append(l.line)
932 932 ui.write(sep.join(cols), eol)
933 933 counts[change] += 1
934 934 return counts['+'], counts['-']
935 935
936 936 fstate = {}
937 937 skip = {}
938 938 changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts)
939 939 count = 0
940 940 for st, rev, fns in changeiter:
941 941 if st == 'window':
942 942 incrementing = rev
943 943 matches.clear()
944 944 elif st == 'add':
945 945 change = repo.changelog.read(repo.lookup(str(rev)))
946 946 mf = repo.manifest.read(change[0])
947 947 matches[rev] = {}
948 948 for fn in fns:
949 949 if fn in skip: continue
950 950 fstate.setdefault(fn, {})
951 951 try:
952 952 grepbody(fn, rev, getfile(fn).read(mf[fn]))
953 953 except KeyError:
954 954 pass
955 955 elif st == 'iter':
956 956 states = matches[rev].items()
957 957 states.sort()
958 958 for fn, m in states:
959 959 if fn in skip: continue
960 960 if incrementing or not opts['all'] or fstate[fn]:
961 961 pos, neg = display(fn, rev, m, fstate[fn])
962 962 count += pos + neg
963 963 if pos and not opts['all']:
964 964 skip[fn] = True
965 965 fstate[fn] = m
966 966 prev[fn] = rev
967 967
968 968 if not incrementing:
969 969 fstate = fstate.items()
970 970 fstate.sort()
971 971 for fn, state in fstate:
972 972 if fn in skip: continue
973 973 display(fn, rev, {}, state)
974 974 return (count == 0 and 1) or 0
975 975
976 976 def heads(ui, repo, **opts):
977 977 """show current repository heads"""
978 978 heads = repo.changelog.heads()
979 979 br = None
980 980 if opts['branches']:
981 981 br = repo.branchlookup(heads)
982 982 for n in repo.changelog.heads():
983 983 show_changeset(ui, repo, changenode=n, brinfo=br)
984 984
985 985 def identify(ui, repo):
986 986 """print information about the working copy"""
987 987 parents = [p for p in repo.dirstate.parents() if p != nullid]
988 988 if not parents:
989 989 ui.write("unknown\n")
990 990 return
991 991
992 992 hexfunc = ui.verbose and hex or short
993 993 (c, a, d, u) = repo.changes()
994 994 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
995 995 (c or a or d) and "+" or "")]
996 996
997 997 if not ui.quiet:
998 998 # multiple tags for a single parent separated by '/'
999 999 parenttags = ['/'.join(tags)
1000 1000 for tags in map(repo.nodetags, parents) if tags]
1001 1001 # tags for multiple parents separated by ' + '
1002 1002 if parenttags:
1003 1003 output.append(' + '.join(parenttags))
1004 1004
1005 1005 ui.write("%s\n" % ' '.join(output))
1006 1006
1007 1007 def import_(ui, repo, patch1, *patches, **opts):
1008 1008 """import an ordered set of patches"""
1009 1009 patches = (patch1,) + patches
1010 1010
1011 1011 if not opts['force']:
1012 1012 (c, a, d, u) = repo.changes()
1013 1013 if c or a or d:
1014 1014 ui.warn("abort: outstanding uncommitted changes!\n")
1015 1015 return 1
1016 1016
1017 1017 d = opts["base"]
1018 1018 strip = opts["strip"]
1019 1019
1020 1020 mailre = re.compile(r'(?:From |[\w-]+:)')
1021 1021 diffre = re.compile(r'(?:diff -|--- .*\s+\w+ \w+ +\d+ \d+:\d+:\d+ \d+)')
1022 1022
1023 1023 for patch in patches:
1024 1024 ui.status("applying %s\n" % patch)
1025 1025 pf = os.path.join(d, patch)
1026 1026
1027 1027 message = []
1028 1028 user = None
1029 1029 hgpatch = False
1030 1030 for line in file(pf):
1031 1031 line = line.rstrip()
1032 1032 if not message and mailre.match(line) and not opts['force']:
1033 1033 if len(line) > 35: line = line[:32] + '...'
1034 1034 raise util.Abort('first line looks like a '
1035 1035 'mail header: ' + line)
1036 1036 if diffre.match(line):
1037 1037 break
1038 1038 elif hgpatch:
1039 1039 # parse values when importing the result of an hg export
1040 1040 if line.startswith("# User "):
1041 1041 user = line[7:]
1042 1042 ui.debug('User: %s\n' % user)
1043 1043 elif not line.startswith("# ") and line:
1044 1044 message.append(line)
1045 1045 hgpatch = False
1046 1046 elif line == '# HG changeset patch':
1047 1047 hgpatch = True
1048 1048 message = [] # We may have collected garbage
1049 1049 else:
1050 1050 message.append(line)
1051 1051
1052 1052 # make sure message isn't empty
1053 1053 if not message:
1054 1054 message = "imported patch %s\n" % patch
1055 1055 else:
1056 1056 message = "%s\n" % '\n'.join(message)
1057 1057 ui.debug('message:\n%s\n' % message)
1058 1058
1059 1059 f = os.popen("patch -p%d < '%s'" % (strip, pf))
1060 1060 files = []
1061 1061 for l in f.read().splitlines():
1062 1062 l.rstrip('\r\n');
1063 1063 ui.status("%s\n" % l)
1064 1064 if l.startswith('patching file '):
1065 1065 pf = l[14:]
1066 1066 if pf not in files:
1067 1067 files.append(pf)
1068 1068 patcherr = f.close()
1069 1069 if patcherr:
1070 1070 raise util.Abort("patch failed")
1071 1071
1072 1072 if len(files) > 0:
1073 1073 addremove(ui, repo, *files)
1074 1074 repo.commit(files, message, user)
1075 1075
1076 1076 def incoming(ui, repo, source="default", **opts):
1077 1077 """show new changesets found in source"""
1078 1078 source = ui.expandpath(source)
1079 1079 other = hg.repository(ui, source)
1080 1080 if not other.local():
1081 1081 ui.warn("abort: incoming doesn't work for remote"
1082 1082 + " repositories yet, sorry!\n")
1083 1083 return 1
1084 1084 o = repo.findincoming(other)
1085 1085 if not o:
1086 1086 return
1087 1087 o = other.newer(o)
1088 1088 o.reverse()
1089 1089 for n in o:
1090 1090 show_changeset(ui, other, changenode=n)
1091 1091 if opts['patch']:
1092 1092 prev = other.changelog.parents(n)[0]
1093 1093 dodiff(ui, ui, other, prev, n)
1094 1094 ui.write("\n")
1095 1095
1096 1096 def init(ui, dest="."):
1097 1097 """create a new repository in the given directory"""
1098 1098 if not os.path.exists(dest):
1099 1099 os.mkdir(dest)
1100 1100 hg.repository(ui, dest, create=1)
1101 1101
1102 1102 def locate(ui, repo, *pats, **opts):
1103 1103 """locate files matching specific patterns"""
1104 1104 end = opts['print0'] and '\0' or '\n'
1105 1105
1106 1106 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1107 1107 if repo.dirstate.state(abs) == '?':
1108 1108 continue
1109 1109 if opts['fullpath']:
1110 1110 ui.write(os.path.join(repo.root, abs), end)
1111 1111 else:
1112 1112 ui.write(rel, end)
1113 1113
1114 1114 def log(ui, repo, *pats, **opts):
1115 1115 """show revision history of entire repository or files"""
1116 1116 class dui:
1117 1117 # Implement and delegate some ui protocol. Save hunks of
1118 1118 # output for later display in the desired order.
1119 1119 def __init__(self, ui):
1120 1120 self.ui = ui
1121 1121 self.hunk = {}
1122 1122 def bump(self, rev):
1123 1123 self.rev = rev
1124 1124 self.hunk[rev] = []
1125 1125 def note(self, *args):
1126 1126 if self.verbose:
1127 1127 self.write(*args)
1128 1128 def status(self, *args):
1129 1129 if not self.quiet:
1130 1130 self.write(*args)
1131 1131 def write(self, *args):
1132 1132 self.hunk[self.rev].append(args)
1133 1133 def __getattr__(self, key):
1134 1134 return getattr(self.ui, key)
1135 1135 cwd = repo.getcwd()
1136 1136 if not pats and cwd:
1137 1137 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1138 1138 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1139 1139 changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '',
1140 1140 pats, opts)
1141 1141 for st, rev, fns in changeiter:
1142 1142 if st == 'window':
1143 1143 du = dui(ui)
1144 1144 elif st == 'add':
1145 1145 du.bump(rev)
1146 1146 show_changeset(du, repo, rev)
1147 1147 if opts['patch']:
1148 1148 changenode = repo.changelog.node(rev)
1149 1149 prev, other = repo.changelog.parents(changenode)
1150 1150 dodiff(du, du, repo, prev, changenode, fns)
1151 1151 du.write("\n\n")
1152 1152 elif st == 'iter':
1153 1153 for args in du.hunk[rev]:
1154 1154 ui.write(*args)
1155 1155
1156 1156 def manifest(ui, repo, rev=None):
1157 1157 """output the latest or given revision of the project manifest"""
1158 1158 if rev:
1159 1159 try:
1160 1160 # assume all revision numbers are for changesets
1161 1161 n = repo.lookup(rev)
1162 1162 change = repo.changelog.read(n)
1163 1163 n = change[0]
1164 1164 except hg.RepoError:
1165 1165 n = repo.manifest.lookup(rev)
1166 1166 else:
1167 1167 n = repo.manifest.tip()
1168 1168 m = repo.manifest.read(n)
1169 1169 mf = repo.manifest.readflags(n)
1170 1170 files = m.keys()
1171 1171 files.sort()
1172 1172
1173 1173 for f in files:
1174 1174 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1175 1175
1176 1176 def outgoing(ui, repo, dest="default-push", **opts):
1177 1177 """show changesets not found in destination"""
1178 1178 dest = ui.expandpath(dest)
1179 1179 other = hg.repository(ui, dest)
1180 1180 o = repo.findoutgoing(other)
1181 1181 o = repo.newer(o)
1182 1182 o.reverse()
1183 1183 for n in o:
1184 1184 show_changeset(ui, repo, changenode=n)
1185 1185 if opts['patch']:
1186 1186 prev = repo.changelog.parents(n)[0]
1187 1187 dodiff(ui, ui, repo, prev, n)
1188 1188 ui.write("\n")
1189 1189
1190 1190 def parents(ui, repo, rev=None):
1191 1191 """show the parents of the working dir or revision"""
1192 1192 if rev:
1193 1193 p = repo.changelog.parents(repo.lookup(rev))
1194 1194 else:
1195 1195 p = repo.dirstate.parents()
1196 1196
1197 1197 for n in p:
1198 1198 if n != nullid:
1199 1199 show_changeset(ui, repo, changenode=n)
1200 1200
1201 1201 def paths(ui, search=None):
1202 1202 """show definition of symbolic path names"""
1203 1203 try:
1204 1204 repo = hg.repository(ui=ui)
1205 1205 except hg.RepoError:
1206 1206 pass
1207 1207
1208 1208 if search:
1209 1209 for name, path in ui.configitems("paths"):
1210 1210 if name == search:
1211 1211 ui.write("%s\n" % path)
1212 1212 return
1213 1213 ui.warn("not found!\n")
1214 1214 return 1
1215 1215 else:
1216 1216 for name, path in ui.configitems("paths"):
1217 1217 ui.write("%s = %s\n" % (name, path))
1218 1218
1219 1219 def pull(ui, repo, source="default", **opts):
1220 1220 """pull changes from the specified source"""
1221 1221 source = ui.expandpath(source)
1222 1222 ui.status('pulling from %s\n' % (source))
1223 1223
1224 1224 if opts['ssh']:
1225 1225 ui.setconfig("ui", "ssh", opts['ssh'])
1226 1226 if opts['remotecmd']:
1227 1227 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1228 1228
1229 1229 other = hg.repository(ui, source)
1230 1230 r = repo.pull(other)
1231 1231 if not r:
1232 1232 if opts['update']:
1233 1233 return update(ui, repo)
1234 1234 else:
1235 1235 ui.status("(run 'hg update' to get a working copy)\n")
1236 1236
1237 1237 return r
1238 1238
1239 1239 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1240 1240 """push changes to the specified destination"""
1241 1241 dest = ui.expandpath(dest)
1242 1242 ui.status('pushing to %s\n' % (dest))
1243 1243
1244 1244 if ssh:
1245 1245 ui.setconfig("ui", "ssh", ssh)
1246 1246 if remotecmd:
1247 1247 ui.setconfig("ui", "remotecmd", remotecmd)
1248 1248
1249 1249 other = hg.repository(ui, dest)
1250 1250 r = repo.push(other, force)
1251 1251 return r
1252 1252
1253 1253 def rawcommit(ui, repo, *flist, **rc):
1254 1254 "raw commit interface"
1255 1255 if rc['text']:
1256 1256 ui.warn("Warning: -t and --text is deprecated,"
1257 1257 " please use -m or --message instead.\n")
1258 1258 message = rc['message'] or rc['text']
1259 1259 if not message and rc['logfile']:
1260 1260 try:
1261 1261 message = open(rc['logfile']).read()
1262 1262 except IOError:
1263 1263 pass
1264 1264 if not message and not rc['logfile']:
1265 1265 ui.warn("abort: missing commit message\n")
1266 1266 return 1
1267 1267
1268 1268 files = relpath(repo, list(flist))
1269 1269 if rc['files']:
1270 1270 files += open(rc['files']).read().splitlines()
1271 1271
1272 1272 rc['parent'] = map(repo.lookup, rc['parent'])
1273 1273
1274 1274 try:
1275 1275 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1276 1276 except ValueError, inst:
1277 1277 raise util.Abort(str(inst))
1278 1278
1279 1279 def recover(ui, repo):
1280 1280 """roll back an interrupted transaction"""
1281 1281 repo.recover()
1282 1282
1283 1283 def remove(ui, repo, pat, *pats, **opts):
1284 1284 """remove the specified files on the next commit"""
1285 1285 names = []
1286 1286 def okaytoremove(abs, rel, exact):
1287 1287 c, a, d, u = repo.changes(files = [abs])
1288 1288 reason = None
1289 1289 if c: reason = 'is modified'
1290 1290 elif a: reason = 'has been marked for add'
1291 1291 elif u: reason = 'not managed'
1292 1292 if reason and exact:
1293 1293 ui.warn('not removing %s: file %s\n' % (rel, reason))
1294 1294 else:
1295 1295 return True
1296 1296 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1297 1297 if okaytoremove(abs, rel, exact):
1298 1298 if not exact: ui.status('removing %s\n' % rel)
1299 1299 names.append(abs)
1300 1300 repo.remove(names)
1301 1301
1302 1302 def revert(ui, repo, *names, **opts):
1303 1303 """revert modified files or dirs back to their unmodified states"""
1304 1304 node = opts['rev'] and repo.lookup(opts['rev']) or \
1305 1305 repo.dirstate.parents()[0]
1306 1306 root = os.path.realpath(repo.root)
1307 1307
1308 1308 def trimpath(p):
1309 1309 p = os.path.realpath(p)
1310 1310 if p.startswith(root):
1311 1311 rest = p[len(root):]
1312 1312 if not rest:
1313 1313 return rest
1314 1314 if p.startswith(os.sep):
1315 1315 return rest[1:]
1316 1316 return p
1317 1317
1318 1318 relnames = map(trimpath, names or [os.getcwd()])
1319 1319 chosen = {}
1320 1320
1321 1321 def choose(name):
1322 1322 def body(name):
1323 1323 for r in relnames:
1324 1324 if not name.startswith(r):
1325 1325 continue
1326 1326 rest = name[len(r):]
1327 1327 if not rest:
1328 1328 return r, True
1329 1329 depth = rest.count(os.sep)
1330 1330 if not r:
1331 1331 if depth == 0 or not opts['nonrecursive']:
1332 1332 return r, True
1333 1333 elif rest[0] == os.sep:
1334 1334 if depth == 1 or not opts['nonrecursive']:
1335 1335 return r, True
1336 1336 return None, False
1337 1337 relname, ret = body(name)
1338 1338 if ret:
1339 1339 chosen[relname] = 1
1340 1340 return ret
1341 1341
1342 1342 r = repo.update(node, False, True, choose, False)
1343 1343 for n in relnames:
1344 1344 if n not in chosen:
1345 1345 ui.warn('error: no matches for %s\n' % n)
1346 1346 r = 1
1347 1347 sys.stdout.flush()
1348 1348 return r
1349 1349
1350 1350 def root(ui, repo):
1351 1351 """print the root (top) of the current working dir"""
1352 1352 ui.write(repo.root + "\n")
1353 1353
1354 1354 def serve(ui, repo, **opts):
1355 1355 """export the repository via HTTP"""
1356 1356
1357 1357 if opts["stdio"]:
1358 1358 fin, fout = sys.stdin, sys.stdout
1359 1359 sys.stdout = sys.stderr
1360 1360
1361 1361 def getarg():
1362 1362 argline = fin.readline()[:-1]
1363 1363 arg, l = argline.split()
1364 1364 val = fin.read(int(l))
1365 1365 return arg, val
1366 1366 def respond(v):
1367 1367 fout.write("%d\n" % len(v))
1368 1368 fout.write(v)
1369 1369 fout.flush()
1370 1370
1371 1371 lock = None
1372 1372
1373 1373 while 1:
1374 1374 cmd = fin.readline()[:-1]
1375 1375 if cmd == '':
1376 1376 return
1377 1377 if cmd == "heads":
1378 1378 h = repo.heads()
1379 1379 respond(" ".join(map(hex, h)) + "\n")
1380 1380 if cmd == "lock":
1381 1381 lock = repo.lock()
1382 1382 respond("")
1383 1383 if cmd == "unlock":
1384 1384 if lock:
1385 1385 lock.release()
1386 1386 lock = None
1387 1387 respond("")
1388 1388 elif cmd == "branches":
1389 1389 arg, nodes = getarg()
1390 1390 nodes = map(bin, nodes.split(" "))
1391 1391 r = []
1392 1392 for b in repo.branches(nodes):
1393 1393 r.append(" ".join(map(hex, b)) + "\n")
1394 1394 respond("".join(r))
1395 1395 elif cmd == "between":
1396 1396 arg, pairs = getarg()
1397 1397 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1398 1398 r = []
1399 1399 for b in repo.between(pairs):
1400 1400 r.append(" ".join(map(hex, b)) + "\n")
1401 1401 respond("".join(r))
1402 1402 elif cmd == "changegroup":
1403 1403 nodes = []
1404 1404 arg, roots = getarg()
1405 1405 nodes = map(bin, roots.split(" "))
1406 1406
1407 1407 cg = repo.changegroup(nodes)
1408 1408 while 1:
1409 1409 d = cg.read(4096)
1410 1410 if not d:
1411 1411 break
1412 1412 fout.write(d)
1413 1413
1414 1414 fout.flush()
1415 1415
1416 1416 elif cmd == "addchangegroup":
1417 1417 if not lock:
1418 1418 respond("not locked")
1419 1419 continue
1420 1420 respond("")
1421 1421
1422 1422 r = repo.addchangegroup(fin)
1423 1423 respond("")
1424 1424
1425 1425 optlist = "name templates style address port ipv6 accesslog errorlog"
1426 1426 for o in optlist.split():
1427 1427 if opts[o]:
1428 1428 ui.setconfig("web", o, opts[o])
1429 1429
1430 1430 try:
1431 1431 httpd = hgweb.create_server(repo)
1432 1432 except socket.error, inst:
1433 1433 raise util.Abort('cannot start server: ' + inst.args[1])
1434 1434
1435 1435 if ui.verbose:
1436 1436 addr, port = httpd.socket.getsockname()
1437 1437 if addr == '0.0.0.0':
1438 1438 addr = socket.gethostname()
1439 1439 else:
1440 1440 try:
1441 1441 addr = socket.gethostbyaddr(addr)[0]
1442 1442 except socket.error:
1443 1443 pass
1444 1444 if port != 80:
1445 1445 ui.status('listening at http://%s:%d/\n' % (addr, port))
1446 1446 else:
1447 1447 ui.status('listening at http://%s/\n' % addr)
1448 1448 httpd.serve_forever()
1449 1449
1450 1450 def status(ui, repo, *pats, **opts):
1451 1451 '''show changed files in the working directory
1452 1452
1453 1453 M = modified
1454 1454 A = added
1455 1455 R = removed
1456 1456 ? = not tracked
1457 1457 '''
1458 1458
1459 1459 cwd = repo.getcwd()
1460 1460 files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
1461 1461 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1462 1462 for n in repo.changes(files=files, match=matchfn)]
1463 1463
1464 1464 changetypes = [('modified', 'M', c),
1465 1465 ('added', 'A', a),
1466 1466 ('removed', 'R', d),
1467 1467 ('unknown', '?', u)]
1468 1468
1469 1469 end = opts['print0'] and '\0' or '\n'
1470 1470
1471 1471 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1472 1472 or changetypes):
1473 1473 if opts['no_status']:
1474 1474 format = "%%s%s" % end
1475 1475 else:
1476 1476 format = "%s %%s%s" % (char, end);
1477 1477
1478 1478 for f in changes:
1479 1479 ui.write(format % f)
1480 1480
1481 1481 def tag(ui, repo, name, rev=None, **opts):
1482 1482 """add a tag for the current tip or a given revision"""
1483 1483 if opts['text']:
1484 1484 ui.warn("Warning: -t and --text is deprecated,"
1485 1485 " please use -m or --message instead.\n")
1486 1486 if name == "tip":
1487 1487 ui.warn("abort: 'tip' is a reserved name!\n")
1488 1488 return -1
1489 1489 if rev:
1490 1490 r = hex(repo.lookup(rev))
1491 1491 else:
1492 1492 r = hex(repo.changelog.tip())
1493 1493
1494 1494 if name.find(revrangesep) >= 0:
1495 1495 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
1496 1496 return -1
1497 1497
1498 1498 if opts['local']:
1499 1499 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1500 1500 return
1501 1501
1502 1502 (c, a, d, u) = repo.changes()
1503 1503 for x in (c, a, d, u):
1504 1504 if ".hgtags" in x:
1505 1505 ui.warn("abort: working copy of .hgtags is changed!\n")
1506 1506 ui.status("(please commit .hgtags manually)\n")
1507 1507 return -1
1508 1508
1509 1509 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1510 1510 if repo.dirstate.state(".hgtags") == '?':
1511 1511 repo.add([".hgtags"])
1512 1512
1513 1513 message = (opts['message'] or opts['text'] or
1514 1514 "Added tag %s for changeset %s" % (name, r))
1515 1515 try:
1516 1516 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1517 1517 except ValueError, inst:
1518 1518 raise util.Abort(str(inst))
1519 1519
1520 1520 def tags(ui, repo):
1521 1521 """list repository tags"""
1522 1522
1523 1523 l = repo.tagslist()
1524 1524 l.reverse()
1525 1525 for t, n in l:
1526 1526 try:
1527 1527 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
1528 1528 except KeyError:
1529 1529 r = " ?:?"
1530 1530 ui.write("%-30s %s\n" % (t, r))
1531 1531
1532 1532 def tip(ui, repo):
1533 1533 """show the tip revision"""
1534 1534 n = repo.changelog.tip()
1535 1535 show_changeset(ui, repo, changenode=n)
1536 1536
1537 1537 def undo(ui, repo):
1538 1538 """undo the last commit or pull
1539 1539
1540 1540 Roll back the last pull or commit transaction on the
1541 1541 repository, restoring the project to its earlier state.
1542 1542
1543 1543 This command should be used with care. There is only one level of
1544 1544 undo and there is no redo.
1545 1545
1546 1546 This command is not intended for use on public repositories. Once
1547 1547 a change is visible for pull by other users, undoing it locally is
1548 1548 ineffective.
1549 1549 """
1550 1550 repo.undo()
1551 1551
1552 1552 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1553 1553 '''update or merge working directory
1554 1554
1555 1555 If there are no outstanding changes in the working directory and
1556 1556 there is a linear relationship between the current version and the
1557 1557 requested version, the result is the requested version.
1558 1558
1559 1559 Otherwise the result is a merge between the contents of the
1560 1560 current working directory and the requested version. Files that
1561 1561 changed between either parent are marked as changed for the next
1562 1562 commit and a commit must be performed before any further updates
1563 1563 are allowed.
1564 1564 '''
1565 1565 if branch:
1566 1566 br = repo.branchlookup(branch=branch)
1567 1567 found = []
1568 1568 for x in br:
1569 1569 if branch in br[x]:
1570 1570 found.append(x)
1571 1571 if len(found) > 1:
1572 1572 ui.warn("Found multiple heads for %s\n" % branch)
1573 1573 for x in found:
1574 1574 show_changeset(ui, repo, changenode=x, brinfo=br)
1575 1575 return 1
1576 1576 if len(found) == 1:
1577 1577 node = found[0]
1578 1578 ui.warn("Using head %s for branch %s\n" % (short(node), branch))
1579 1579 else:
1580 1580 ui.warn("branch %s not found\n" % (branch))
1581 1581 return 1
1582 1582 else:
1583 1583 node = node and repo.lookup(node) or repo.changelog.tip()
1584 1584 return repo.update(node, allow=merge, force=clean)
1585 1585
1586 1586 def verify(ui, repo):
1587 1587 """verify the integrity of the repository"""
1588 1588 return repo.verify()
1589 1589
1590 1590 # Command options and aliases are listed here, alphabetically
1591 1591
1592 1592 table = {
1593 1593 "^add":
1594 1594 (add,
1595 1595 [('I', 'include', [], 'include path in search'),
1596 1596 ('X', 'exclude', [], 'exclude path from search')],
1597 1597 "hg add [OPTION]... [FILE]..."),
1598 1598 "addremove":
1599 1599 (addremove,
1600 1600 [('I', 'include', [], 'include path in search'),
1601 1601 ('X', 'exclude', [], 'exclude path from search')],
1602 1602 "hg addremove [OPTION]... [FILE]..."),
1603 1603 "^annotate":
1604 1604 (annotate,
1605 1605 [('r', 'rev', '', 'revision'),
1606 1606 ('a', 'text', None, 'treat all files as text'),
1607 1607 ('u', 'user', None, 'show user'),
1608 1608 ('n', 'number', None, 'show revision number'),
1609 1609 ('c', 'changeset', None, 'show changeset'),
1610 1610 ('I', 'include', [], 'include path in search'),
1611 1611 ('X', 'exclude', [], 'exclude path from search')],
1612 1612 'hg annotate [OPTION]... FILE...'),
1613 1613 "cat":
1614 1614 (cat,
1615 1615 [('o', 'output', "", 'output to file')],
1616 1616 'hg cat [-o OUTFILE] FILE [REV]'),
1617 1617 "^clone":
1618 1618 (clone,
1619 1619 [('U', 'noupdate', None, 'skip update after cloning'),
1620 1620 ('e', 'ssh', "", 'ssh command'),
1621 1621 ('', 'remotecmd', "", 'remote hg command')],
1622 1622 'hg clone [OPTION]... SOURCE [DEST]'),
1623 1623 "^commit|ci":
1624 1624 (commit,
1625 1625 [('A', 'addremove', None, 'run add/remove during commit'),
1626 1626 ('I', 'include', [], 'include path in search'),
1627 1627 ('X', 'exclude', [], 'exclude path from search'),
1628 1628 ('m', 'message', "", 'commit message'),
1629 1629 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1630 1630 ('l', 'logfile', "", 'commit message file'),
1631 1631 ('d', 'date', "", 'date code'),
1632 1632 ('u', 'user', "", 'user')],
1633 1633 'hg commit [OPTION]... [FILE]...'),
1634 1634 "copy": (copy, [], 'hg copy SOURCE DEST'),
1635 1635 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1636 1636 "debugconfig": (debugconfig, [], 'debugconfig'),
1637 1637 "debugstate": (debugstate, [], 'debugstate'),
1638 1638 "debugdata": (debugdata, [], 'debugdata FILE REV'),
1639 1639 "debugindex": (debugindex, [], 'debugindex FILE'),
1640 1640 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1641 1641 "debugrename": (debugrename, [], 'debugrename FILE [REV]'),
1642 1642 "debugwalk":
1643 1643 (debugwalk,
1644 1644 [('I', 'include', [], 'include path in search'),
1645 1645 ('X', 'exclude', [], 'exclude path from search')],
1646 1646 'debugwalk [OPTION]... [FILE]...'),
1647 1647 "^diff":
1648 1648 (diff,
1649 1649 [('r', 'rev', [], 'revision'),
1650 1650 ('a', 'text', None, 'treat all files as text'),
1651 1651 ('I', 'include', [], 'include path in search'),
1652 1652 ('X', 'exclude', [], 'exclude path from search')],
1653 1653 'hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...'),
1654 1654 "^export":
1655 1655 (export,
1656 1656 [('o', 'output', "", 'output to file'),
1657 1657 ('a', 'text', None, 'treat all files as text')],
1658 1658 "hg export [-a] [-o OUTFILE] REV..."),
1659 1659 "forget":
1660 1660 (forget,
1661 1661 [('I', 'include', [], 'include path in search'),
1662 1662 ('X', 'exclude', [], 'exclude path from search')],
1663 1663 "hg forget [OPTION]... FILE..."),
1664 1664 "grep":
1665 1665 (grep,
1666 1666 [('0', 'print0', None, 'end fields with NUL'),
1667 1667 ('I', 'include', [], 'include path in search'),
1668 1668 ('X', 'exclude', [], 'include path in search'),
1669 1669 ('', 'all', None, 'print all revisions with matches'),
1670 1670 ('i', 'ignore-case', None, 'ignore case when matching'),
1671 1671 ('l', 'files-with-matches', None, 'print names of files and revs with matches'),
1672 1672 ('n', 'line-number', None, 'print line numbers'),
1673 1673 ('r', 'rev', [], 'search in revision rev'),
1674 1674 ('u', 'user', None, 'print user who made change')],
1675 1675 "hg grep [OPTION]... PATTERN [FILE]..."),
1676 1676 "heads":
1677 1677 (heads,
1678 1678 [('b', 'branches', None, 'find branch info')],
1679 1679 'hg heads [-b]'),
1680 1680 "help": (help_, [], 'hg help [COMMAND]'),
1681 1681 "identify|id": (identify, [], 'hg identify'),
1682 1682 "import|patch":
1683 1683 (import_,
1684 1684 [('p', 'strip', 1, 'path strip'),
1685 1685 ('f', 'force', None, 'skip check for outstanding changes'),
1686 1686 ('b', 'base', "", 'base path')],
1687 1687 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
1688 1688 "incoming|in": (incoming,
1689 1689 [('p', 'patch', None, 'show patch')],
1690 1690 'hg incoming [-p] [SOURCE]'),
1691 1691 "^init": (init, [], 'hg init [DEST]'),
1692 1692 "locate":
1693 1693 (locate,
1694 1694 [('r', 'rev', '', 'revision'),
1695 1695 ('0', 'print0', None, 'end filenames with NUL'),
1696 1696 ('f', 'fullpath', None, 'print complete paths'),
1697 1697 ('I', 'include', [], 'include path in search'),
1698 1698 ('X', 'exclude', [], 'exclude path from search')],
1699 1699 'hg locate [OPTION]... [PATTERN]...'),
1700 1700 "^log|history":
1701 1701 (log,
1702 1702 [('I', 'include', [], 'include path in search'),
1703 1703 ('X', 'exclude', [], 'exclude path from search'),
1704 1704 ('r', 'rev', [], 'revision'),
1705 1705 ('p', 'patch', None, 'show patch')],
1706 1706 'hg log [-I] [-X] [-r REV]... [-p] [FILE]'),
1707 1707 "manifest": (manifest, [], 'hg manifest [REV]'),
1708 1708 "outgoing|out": (outgoing,
1709 1709 [('p', 'patch', None, 'show patch')],
1710 1710 'hg outgoing [-p] [DEST]'),
1711 1711 "parents": (parents, [], 'hg parents [REV]'),
1712 1712 "paths": (paths, [], 'hg paths [NAME]'),
1713 1713 "^pull":
1714 1714 (pull,
1715 1715 [('u', 'update', None, 'update working directory'),
1716 1716 ('e', 'ssh', "", 'ssh command'),
1717 1717 ('', 'remotecmd', "", 'remote hg command')],
1718 1718 'hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]'),
1719 1719 "^push":
1720 1720 (push,
1721 1721 [('f', 'force', None, 'force push'),
1722 1722 ('e', 'ssh', "", 'ssh command'),
1723 1723 ('', 'remotecmd', "", 'remote hg command')],
1724 1724 'hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]'),
1725 1725 "rawcommit":
1726 1726 (rawcommit,
1727 1727 [('p', 'parent', [], 'parent'),
1728 1728 ('d', 'date', "", 'date code'),
1729 1729 ('u', 'user', "", 'user'),
1730 1730 ('F', 'files', "", 'file list'),
1731 1731 ('m', 'message', "", 'commit message'),
1732 1732 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1733 1733 ('l', 'logfile', "", 'commit message file')],
1734 1734 'hg rawcommit [OPTION]... [FILE]...'),
1735 1735 "recover": (recover, [], "hg recover"),
1736 1736 "^remove|rm": (remove,
1737 1737 [('I', 'include', [], 'include path in search'),
1738 1738 ('X', 'exclude', [], 'exclude path from search')],
1739 1739 "hg remove [OPTION]... FILE..."),
1740 1740 "^revert":
1741 1741 (revert,
1742 1742 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1743 1743 ("r", "rev", "", "revision")],
1744 1744 "hg revert [-n] [-r REV] [NAME]..."),
1745 1745 "root": (root, [], "hg root"),
1746 1746 "^serve":
1747 1747 (serve,
1748 1748 [('A', 'accesslog', '', 'access log file'),
1749 1749 ('E', 'errorlog', '', 'error log file'),
1750 1750 ('p', 'port', 0, 'listen port'),
1751 1751 ('a', 'address', '', 'interface address'),
1752 1752 ('n', 'name', "", 'repository name'),
1753 1753 ('', 'stdio', None, 'for remote clients'),
1754 1754 ('t', 'templates', "", 'template directory'),
1755 1755 ('', 'style', "", 'template style'),
1756 1756 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1757 1757 "hg serve [OPTION]..."),
1758 1758 "^status":
1759 1759 (status,
1760 1760 [('m', 'modified', None, 'show only modified files'),
1761 1761 ('a', 'added', None, 'show only added files'),
1762 1762 ('r', 'removed', None, 'show only removed files'),
1763 1763 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1764 1764 ('n', 'no-status', None, 'hide status prefix'),
1765 1765 ('0', 'print0', None, 'end filenames with NUL'),
1766 1766 ('I', 'include', [], 'include path in search'),
1767 1767 ('X', 'exclude', [], 'exclude path from search')],
1768 1768 "hg status [OPTION]... [FILE]..."),
1769 1769 "tag":
1770 1770 (tag,
1771 1771 [('l', 'local', None, 'make the tag local'),
1772 1772 ('m', 'message', "", 'commit message'),
1773 1773 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1774 1774 ('d', 'date', "", 'date code'),
1775 1775 ('u', 'user', "", 'user')],
1776 1776 'hg tag [OPTION]... NAME [REV]'),
1777 1777 "tags": (tags, [], 'hg tags'),
1778 1778 "tip": (tip, [], 'hg tip'),
1779 1779 "undo": (undo, [], 'hg undo'),
1780 1780 "^update|up|checkout|co":
1781 1781 (update,
1782 1782 [('b', 'branch', "", 'checkout the head of a specific branch'),
1783 1783 ('m', 'merge', None, 'allow merging of conflicts'),
1784 1784 ('C', 'clean', None, 'overwrite locally modified files')],
1785 1785 'hg update [-b TAG] [-m] [-C] [REV]'),
1786 1786 "verify": (verify, [], 'hg verify'),
1787 1787 "version": (show_version, [], 'hg version'),
1788 1788 }
1789 1789
1790 1790 globalopts = [
1791 1791 ('R', 'repository', "", 'repository root directory'),
1792 1792 ('', 'cwd', '', 'change working directory'),
1793 1793 ('y', 'noninteractive', None, 'run non-interactively'),
1794 1794 ('q', 'quiet', None, 'quiet mode'),
1795 1795 ('v', 'verbose', None, 'verbose mode'),
1796 1796 ('', 'debug', None, 'debug mode'),
1797 1797 ('', 'traceback', None, 'print traceback on exception'),
1798 1798 ('', 'time', None, 'time how long the command takes'),
1799 1799 ('', 'profile', None, 'profile'),
1800 1800 ('', 'version', None, 'output version information and exit'),
1801 1801 ('h', 'help', None, 'display help and exit'),
1802 1802 ]
1803 1803
1804 1804 norepo = ("clone init version help debugconfig debugdata"
1805 1805 " debugindex debugindexdot paths")
1806 1806
1807 1807 def find(cmd):
1808 1808 for e in table.keys():
1809 1809 if re.match("(%s)$" % e, cmd):
1810 1810 return e, table[e]
1811 1811
1812 1812 raise UnknownCommand(cmd)
1813 1813
1814 1814 class SignalInterrupt(Exception):
1815 1815 """Exception raised on SIGTERM and SIGHUP."""
1816 1816
1817 1817 def catchterm(*args):
1818 1818 raise SignalInterrupt
1819 1819
1820 1820 def run():
1821 1821 sys.exit(dispatch(sys.argv[1:]))
1822 1822
1823 1823 class ParseError(Exception):
1824 1824 """Exception raised on errors in parsing the command line."""
1825 1825
1826 1826 def parse(args):
1827 1827 options = {}
1828 1828 cmdoptions = {}
1829 1829
1830 1830 try:
1831 1831 args = fancyopts.fancyopts(args, globalopts, options)
1832 1832 except fancyopts.getopt.GetoptError, inst:
1833 1833 raise ParseError(None, inst)
1834 1834
1835 1835 if args:
1836 1836 cmd, args = args[0], args[1:]
1837 1837 i = find(cmd)[1]
1838 1838 c = list(i[1])
1839 1839 else:
1840 1840 cmd = None
1841 1841 c = []
1842 1842
1843 1843 # combine global options into local
1844 1844 for o in globalopts:
1845 1845 c.append((o[0], o[1], options[o[1]], o[3]))
1846 1846
1847 1847 try:
1848 1848 args = fancyopts.fancyopts(args, c, cmdoptions)
1849 1849 except fancyopts.getopt.GetoptError, inst:
1850 1850 raise ParseError(cmd, inst)
1851 1851
1852 1852 # separate global options back out
1853 1853 for o in globalopts:
1854 1854 n = o[1]
1855 1855 options[n] = cmdoptions[n]
1856 1856 del cmdoptions[n]
1857 1857
1858 1858 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
1859 1859
1860 1860 def dispatch(args):
1861 1861 signal.signal(signal.SIGTERM, catchterm)
1862 1862 try:
1863 1863 signal.signal(signal.SIGHUP, catchterm)
1864 1864 except AttributeError:
1865 1865 pass
1866 1866
1867 1867 u = ui.ui()
1868 1868 external = []
1869 1869 for x in u.extensions():
1870 1870 if x[1]:
1871 1871 mod = imp.load_source(x[0], x[1])
1872 1872 else:
1873 1873 def importh(name):
1874 1874 mod = __import__(name)
1875 1875 components = name.split('.')
1876 1876 for comp in components[1:]:
1877 1877 mod = getattr(mod, comp)
1878 1878 return mod
1879 1879 mod = importh(x[0])
1880 1880 external.append(mod)
1881 1881 for x in external:
1882 1882 for t in x.cmdtable:
1883 1883 if t in table:
1884 1884 u.warn("module %s override %s\n" % (x.__name__, t))
1885 1885 table.update(x.cmdtable)
1886 1886
1887 1887 try:
1888 1888 cmd, func, args, options, cmdoptions = parse(args)
1889 1889 except ParseError, inst:
1890 1890 if inst.args[0]:
1891 1891 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1892 1892 help_(u, inst.args[0])
1893 1893 else:
1894 1894 u.warn("hg: %s\n" % inst.args[1])
1895 1895 help_(u, 'shortlist')
1896 1896 sys.exit(-1)
1897 1897 except UnknownCommand, inst:
1898 1898 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1899 1899 help_(u, 'shortlist')
1900 1900 sys.exit(1)
1901 1901
1902 1902 if options["time"]:
1903 1903 def get_times():
1904 1904 t = os.times()
1905 1905 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
1906 1906 t = (t[0], t[1], t[2], t[3], time.clock())
1907 1907 return t
1908 1908 s = get_times()
1909 1909 def print_time():
1910 1910 t = get_times()
1911 1911 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
1912 1912 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
1913 1913 atexit.register(print_time)
1914 1914
1915 1915 u.updateopts(options["verbose"], options["debug"], options["quiet"],
1916 1916 not options["noninteractive"])
1917 1917
1918 1918 try:
1919 1919 try:
1920 1920 if options['help']:
1921 1921 help_(u, cmd, options['version'])
1922 1922 sys.exit(0)
1923 1923 elif options['version']:
1924 1924 show_version(u)
1925 1925 sys.exit(0)
1926 1926 elif not cmd:
1927 1927 help_(u, 'shortlist')
1928 1928 sys.exit(0)
1929 1929
1930 1930 if options['cwd']:
1931 1931 try:
1932 1932 os.chdir(options['cwd'])
1933 1933 except OSError, inst:
1934 1934 u.warn('abort: %s: %s\n' % (options['cwd'], inst.strerror))
1935 1935 sys.exit(1)
1936 1936
1937 1937 if cmd not in norepo.split():
1938 1938 path = options["repository"] or ""
1939 1939 repo = hg.repository(ui=u, path=path)
1940 1940 for x in external:
1941 1941 x.reposetup(u, repo)
1942 1942 d = lambda: func(u, repo, *args, **cmdoptions)
1943 1943 else:
1944 1944 d = lambda: func(u, *args, **cmdoptions)
1945 1945
1946 1946 if options['profile']:
1947 1947 import hotshot, hotshot.stats
1948 1948 prof = hotshot.Profile("hg.prof")
1949 1949 r = prof.runcall(d)
1950 1950 prof.close()
1951 1951 stats = hotshot.stats.load("hg.prof")
1952 1952 stats.strip_dirs()
1953 1953 stats.sort_stats('time', 'calls')
1954 1954 stats.print_stats(40)
1955 1955 return r
1956 1956 else:
1957 1957 return d()
1958 1958 except:
1959 1959 if options['traceback']:
1960 1960 traceback.print_exc()
1961 1961 raise
1962 1962 except hg.RepoError, inst:
1963 1963 u.warn("abort: ", inst, "!\n")
1964 except revlog.RevlogError, inst:
1965 u.warn("abort: ", inst, "!\n")
1964 1966 except SignalInterrupt:
1965 1967 u.warn("killed!\n")
1966 1968 except KeyboardInterrupt:
1967 1969 try:
1968 1970 u.warn("interrupted!\n")
1969 1971 except IOError, inst:
1970 1972 if inst.errno == errno.EPIPE:
1971 1973 if u.debugflag:
1972 1974 u.warn("\nbroken pipe\n")
1973 1975 else:
1974 1976 raise
1975 1977 except IOError, inst:
1976 1978 if hasattr(inst, "code"):
1977 1979 u.warn("abort: %s\n" % inst)
1978 1980 elif hasattr(inst, "reason"):
1979 1981 u.warn("abort: error: %s\n" % inst.reason[1])
1980 1982 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1981 1983 if u.debugflag:
1982 1984 u.warn("broken pipe\n")
1983 1985 else:
1984 1986 raise
1985 1987 except OSError, inst:
1986 1988 if hasattr(inst, "filename"):
1987 1989 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1988 1990 else:
1989 1991 u.warn("abort: %s\n" % inst.strerror)
1990 1992 except util.Abort, inst:
1991 1993 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
1992 1994 sys.exit(1)
1993 1995 except TypeError, inst:
1994 1996 # was this an argument error?
1995 1997 tb = traceback.extract_tb(sys.exc_info()[2])
1996 1998 if len(tb) > 2: # no
1997 1999 raise
1998 2000 u.debug(inst, "\n")
1999 2001 u.warn("%s: invalid arguments\n" % cmd)
2000 2002 help_(u, cmd)
2001 2003 except UnknownCommand, inst:
2002 2004 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2003 2005 help_(u, 'shortlist')
2004 2006
2005 2007 sys.exit(-1)
@@ -1,650 +1,651 b''
1 1 """
2 2 revlog.py - storage back-end for mercurial
3 3
4 4 This provides efficient delta storage with O(1) retrieve and append
5 5 and O(changes) merge between branches
6 6
7 7 Copyright 2005 Matt Mackall <mpm@selenic.com>
8 8
9 9 This software may be used and distributed according to the terms
10 10 of the GNU General Public License, incorporated herein by reference.
11 11 """
12 12
13 13 import zlib, struct, sha, binascii, heapq
14 14 import mdiff
15 15 from node import *
16 16
17 17 def hash(text, p1, p2):
18 18 """generate a hash from the given text and its parent hashes
19 19
20 20 This hash combines both the current file contents and its history
21 21 in a manner that makes it easy to distinguish nodes with the same
22 22 content in the revision graph.
23 23 """
24 24 l = [p1, p2]
25 25 l.sort()
26 26 s = sha.new(l[0])
27 27 s.update(l[1])
28 28 s.update(text)
29 29 return s.digest()
30 30
31 31 def compress(text):
32 32 """ generate a possibly-compressed representation of text """
33 33 if not text: return text
34 34 if len(text) < 44:
35 35 if text[0] == '\0': return text
36 36 return 'u' + text
37 37 bin = zlib.compress(text)
38 38 if len(bin) > len(text):
39 39 if text[0] == '\0': return text
40 40 return 'u' + text
41 41 return bin
42 42
43 43 def decompress(bin):
44 44 """ decompress the given input """
45 45 if not bin: return bin
46 46 t = bin[0]
47 47 if t == '\0': return bin
48 48 if t == 'x': return zlib.decompress(bin)
49 49 if t == 'u': return bin[1:]
50 50 raise RevlogError("unknown compression type %s" % t)
51 51
52 52 indexformat = ">4l20s20s20s"
53 53
54 54 class lazyparser:
55 55 """
56 56 this class avoids the need to parse the entirety of large indices
57 57
58 58 By default we parse and load 1000 entries at a time.
59 59
60 60 If no position is specified, we load the whole index, and replace
61 61 the lazy objects in revlog with the underlying objects for
62 62 efficiency in cases where we look at most of the nodes.
63 63 """
64 64 def __init__(self, data, revlog):
65 65 self.data = data
66 66 self.s = struct.calcsize(indexformat)
67 67 self.l = len(data)/self.s
68 68 self.index = [None] * self.l
69 69 self.map = {nullid: -1}
70 70 self.all = 0
71 71 self.revlog = revlog
72 72
73 73 def load(self, pos=None):
74 74 if self.all: return
75 75 if pos is not None:
76 76 block = pos / 1000
77 77 i = block * 1000
78 78 end = min(self.l, i + 1000)
79 79 else:
80 80 self.all = 1
81 81 i = 0
82 82 end = self.l
83 83 self.revlog.index = self.index
84 84 self.revlog.nodemap = self.map
85 85
86 86 while i < end:
87 87 d = self.data[i * self.s: (i + 1) * self.s]
88 88 e = struct.unpack(indexformat, d)
89 89 self.index[i] = e
90 90 self.map[e[6]] = i
91 91 i += 1
92 92
93 93 class lazyindex:
94 94 """a lazy version of the index array"""
95 95 def __init__(self, parser):
96 96 self.p = parser
97 97 def __len__(self):
98 98 return len(self.p.index)
99 99 def load(self, pos):
100 100 self.p.load(pos)
101 101 return self.p.index[pos]
102 102 def __getitem__(self, pos):
103 103 return self.p.index[pos] or self.load(pos)
104 104 def append(self, e):
105 105 self.p.index.append(e)
106 106
107 107 class lazymap:
108 108 """a lazy version of the node map"""
109 109 def __init__(self, parser):
110 110 self.p = parser
111 111 def load(self, key):
112 112 if self.p.all: return
113 113 n = self.p.data.find(key)
114 if n < 0: raise KeyError("node " + hex(key))
114 if n < 0:
115 raise KeyError(key)
115 116 pos = n / self.p.s
116 117 self.p.load(pos)
117 118 def __contains__(self, key):
118 119 self.p.load()
119 120 return key in self.p.map
120 121 def __iter__(self):
121 122 yield nullid
122 123 for i in xrange(self.p.l):
123 124 try:
124 125 yield self.p.index[i][6]
125 126 except:
126 127 self.p.load(i)
127 128 yield self.p.index[i][6]
128 129 def __getitem__(self, key):
129 130 try:
130 131 return self.p.map[key]
131 132 except KeyError:
132 133 try:
133 134 self.load(key)
134 135 return self.p.map[key]
135 136 except KeyError:
136 137 raise KeyError("node " + hex(key))
137 138 def __setitem__(self, key, val):
138 139 self.p.map[key] = val
139 140
140 141 class RevlogError(Exception): pass
141 142
142 143 class revlog:
143 144 """
144 145 the underlying revision storage object
145 146
146 147 A revlog consists of two parts, an index and the revision data.
147 148
148 149 The index is a file with a fixed record size containing
149 150 information on each revision, includings its nodeid (hash), the
150 151 nodeids of its parents, the position and offset of its data within
151 152 the data file, and the revision it's based on. Finally, each entry
152 153 contains a linkrev entry that can serve as a pointer to external
153 154 data.
154 155
155 156 The revision data itself is a linear collection of data chunks.
156 157 Each chunk represents a revision and is usually represented as a
157 158 delta against the previous chunk. To bound lookup time, runs of
158 159 deltas are limited to about 2 times the length of the original
159 160 version data. This makes retrieval of a version proportional to
160 161 its size, or O(1) relative to the number of revisions.
161 162
162 163 Both pieces of the revlog are written to in an append-only
163 164 fashion, which means we never need to rewrite a file to insert or
164 165 remove data, and can use some simple techniques to avoid the need
165 166 for locking while reading.
166 167 """
167 168 def __init__(self, opener, indexfile, datafile):
168 169 """
169 170 create a revlog object
170 171
171 172 opener is a function that abstracts the file opening operation
172 173 and can be used to implement COW semantics or the like.
173 174 """
174 175 self.indexfile = indexfile
175 176 self.datafile = datafile
176 177 self.opener = opener
177 178 self.cache = None
178 179
179 180 try:
180 181 i = self.opener(self.indexfile).read()
181 182 except IOError:
182 183 i = ""
183 184
184 185 if len(i) > 10000:
185 186 # big index, let's parse it on demand
186 187 parser = lazyparser(i, self)
187 188 self.index = lazyindex(parser)
188 189 self.nodemap = lazymap(parser)
189 190 else:
190 191 s = struct.calcsize(indexformat)
191 192 l = len(i) / s
192 193 self.index = [None] * l
193 194 m = [None] * l
194 195
195 196 n = 0
196 197 for f in xrange(0, len(i), s):
197 198 # offset, size, base, linkrev, p1, p2, nodeid
198 199 e = struct.unpack(indexformat, i[f:f + s])
199 200 m[n] = (e[6], n)
200 201 self.index[n] = e
201 202 n += 1
202 203
203 204 self.nodemap = dict(m)
204 205 self.nodemap[nullid] = -1
205 206
206 207 def tip(self): return self.node(len(self.index) - 1)
207 208 def count(self): return len(self.index)
208 209 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
209 210 def rev(self, node):
210 211 try:
211 212 return self.nodemap[node]
212 213 except KeyError:
213 raise KeyError('%s: no node %s' % (self.indexfile, hex(node)))
214 raise RevlogError('%s: no node %s' % (self.indexfile, hex(node)))
214 215 def linkrev(self, node): return self.index[self.rev(node)][3]
215 216 def parents(self, node):
216 217 if node == nullid: return (nullid, nullid)
217 218 return self.index[self.rev(node)][4:6]
218 219
219 220 def start(self, rev): return self.index[rev][0]
220 221 def length(self, rev): return self.index[rev][1]
221 222 def end(self, rev): return self.start(rev) + self.length(rev)
222 223 def base(self, rev): return self.index[rev][2]
223 224
224 225 def reachable(self, rev, stop=None):
225 226 reachable = {}
226 227 visit = [rev]
227 228 reachable[rev] = 1
228 229 if stop:
229 230 stopn = self.rev(stop)
230 231 else:
231 232 stopn = 0
232 233 while visit:
233 234 n = visit.pop(0)
234 235 if n == stop:
235 236 continue
236 237 if n == nullid:
237 238 continue
238 239 for p in self.parents(n):
239 240 if self.rev(p) < stopn:
240 241 continue
241 242 if p not in reachable:
242 243 reachable[p] = 1
243 244 visit.append(p)
244 245 return reachable
245 246
246 247 def heads(self, stop=None):
247 248 """return the list of all nodes that have no children"""
248 249 p = {}
249 250 h = []
250 251 stoprev = 0
251 252 if stop and stop in self.nodemap:
252 253 stoprev = self.rev(stop)
253 254
254 255 for r in range(self.count() - 1, -1, -1):
255 256 n = self.node(r)
256 257 if n not in p:
257 258 h.append(n)
258 259 if n == stop:
259 260 break
260 261 if r < stoprev:
261 262 break
262 263 for pn in self.parents(n):
263 264 p[pn] = 1
264 265 return h
265 266
266 267 def children(self, node):
267 268 """find the children of a given node"""
268 269 c = []
269 270 p = self.rev(node)
270 271 for r in range(p + 1, self.count()):
271 272 n = self.node(r)
272 273 for pn in self.parents(n):
273 274 if pn == node:
274 275 c.append(n)
275 276 continue
276 277 elif pn == nullid:
277 278 continue
278 279 return c
279 280
280 281 def lookup(self, id):
281 282 """locate a node based on revision number or subset of hex nodeid"""
282 283 try:
283 284 rev = int(id)
284 285 if str(rev) != id: raise ValueError
285 286 if rev < 0: rev = self.count() + rev
286 287 if rev < 0 or rev >= self.count(): raise ValueError
287 288 return self.node(rev)
288 289 except (ValueError, OverflowError):
289 290 c = []
290 291 for n in self.nodemap:
291 292 if hex(n).startswith(id):
292 293 c.append(n)
293 if len(c) > 1: raise KeyError("Ambiguous identifier")
294 if len(c) < 1: raise KeyError("No match found")
294 if len(c) > 1: raise RevlogError("Ambiguous identifier")
295 if len(c) < 1: raise RevlogError("No match found")
295 296 return c[0]
296 297
297 298 return None
298 299
299 300 def diff(self, a, b):
300 301 """return a delta between two revisions"""
301 302 return mdiff.textdiff(a, b)
302 303
303 304 def patches(self, t, pl):
304 305 """apply a list of patches to a string"""
305 306 return mdiff.patches(t, pl)
306 307
307 308 def delta(self, node):
308 309 """return or calculate a delta between a node and its predecessor"""
309 310 r = self.rev(node)
310 311 b = self.base(r)
311 312 if r == b:
312 313 return self.diff(self.revision(self.node(r - 1)),
313 314 self.revision(node))
314 315 else:
315 316 f = self.opener(self.datafile)
316 317 f.seek(self.start(r))
317 318 data = f.read(self.length(r))
318 319 return decompress(data)
319 320
320 321 def revision(self, node):
321 322 """return an uncompressed revision of a given"""
322 323 if node == nullid: return ""
323 324 if self.cache and self.cache[0] == node: return self.cache[2]
324 325
325 326 # look up what we need to read
326 327 text = None
327 328 rev = self.rev(node)
328 329 start, length, base, link, p1, p2, node = self.index[rev]
329 330 end = start + length
330 331 if base != rev: start = self.start(base)
331 332
332 333 # do we have useful data cached?
333 334 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
334 335 base = self.cache[1]
335 336 start = self.start(base + 1)
336 337 text = self.cache[2]
337 338 last = 0
338 339
339 340 f = self.opener(self.datafile)
340 341 f.seek(start)
341 342 data = f.read(end - start)
342 343
343 344 if text is None:
344 345 last = self.length(base)
345 346 text = decompress(data[:last])
346 347
347 348 bins = []
348 349 for r in xrange(base + 1, rev + 1):
349 350 s = self.length(r)
350 351 bins.append(decompress(data[last:last + s]))
351 352 last = last + s
352 353
353 354 text = mdiff.patches(text, bins)
354 355
355 356 if node != hash(text, p1, p2):
356 raise IOError("integrity check failed on %s:%d"
357 raise RevlogError("integrity check failed on %s:%d"
357 358 % (self.datafile, rev))
358 359
359 360 self.cache = (node, rev, text)
360 361 return text
361 362
362 363 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
363 364 """add a revision to the log
364 365
365 366 text - the revision data to add
366 367 transaction - the transaction object used for rollback
367 368 link - the linkrev data to add
368 369 p1, p2 - the parent nodeids of the revision
369 370 d - an optional precomputed delta
370 371 """
371 372 if text is None: text = ""
372 373 if p1 is None: p1 = self.tip()
373 374 if p2 is None: p2 = nullid
374 375
375 376 node = hash(text, p1, p2)
376 377
377 378 if node in self.nodemap:
378 379 return node
379 380
380 381 n = self.count()
381 382 t = n - 1
382 383
383 384 if n:
384 385 base = self.base(t)
385 386 start = self.start(base)
386 387 end = self.end(t)
387 388 if not d:
388 389 prev = self.revision(self.tip())
389 390 d = self.diff(prev, text)
390 391 data = compress(d)
391 392 dist = end - start + len(data)
392 393
393 394 # full versions are inserted when the needed deltas
394 395 # become comparable to the uncompressed text
395 396 if not n or dist > len(text) * 2:
396 397 data = compress(text)
397 398 base = n
398 399 else:
399 400 base = self.base(t)
400 401
401 402 offset = 0
402 403 if t >= 0:
403 404 offset = self.end(t)
404 405
405 406 e = (offset, len(data), base, link, p1, p2, node)
406 407
407 408 self.index.append(e)
408 409 self.nodemap[node] = n
409 410 entry = struct.pack(indexformat, *e)
410 411
411 412 transaction.add(self.datafile, e[0])
412 413 self.opener(self.datafile, "a").write(data)
413 414 transaction.add(self.indexfile, n * len(entry))
414 415 self.opener(self.indexfile, "a").write(entry)
415 416
416 417 self.cache = (node, n, text)
417 418 return node
418 419
419 420 def ancestor(self, a, b):
420 421 """calculate the least common ancestor of nodes a and b"""
421 422 # calculate the distance of every node from root
422 423 dist = {nullid: 0}
423 424 for i in xrange(self.count()):
424 425 n = self.node(i)
425 426 p1, p2 = self.parents(n)
426 427 dist[n] = max(dist[p1], dist[p2]) + 1
427 428
428 429 # traverse ancestors in order of decreasing distance from root
429 430 def ancestors(node):
430 431 # we store negative distances because heap returns smallest member
431 432 h = [(-dist[node], node)]
432 433 seen = {}
433 434 earliest = self.count()
434 435 while h:
435 436 d, n = heapq.heappop(h)
436 437 if n not in seen:
437 438 seen[n] = 1
438 439 r = self.rev(n)
439 440 yield (-d, r, n)
440 441 for p in self.parents(n):
441 442 heapq.heappush(h, (-dist[p], p))
442 443
443 444 x = ancestors(a)
444 445 y = ancestors(b)
445 446 lx = x.next()
446 447 ly = y.next()
447 448
448 449 # increment each ancestor list until it is closer to root than
449 450 # the other, or they match
450 451 while 1:
451 452 if lx == ly:
452 453 return lx[2]
453 454 elif lx < ly:
454 455 ly = y.next()
455 456 elif lx > ly:
456 457 lx = x.next()
457 458
458 459 def group(self, linkmap):
459 460 """calculate a delta group
460 461
461 462 Given a list of changeset revs, return a set of deltas and
462 463 metadata corresponding to nodes. the first delta is
463 464 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
464 465 have this parent as it has all history before these
465 466 changesets. parent is parent[0]
466 467 """
467 468 revs = []
468 469 needed = {}
469 470
470 471 # find file nodes/revs that match changeset revs
471 472 for i in xrange(0, self.count()):
472 473 if self.index[i][3] in linkmap:
473 474 revs.append(i)
474 475 needed[i] = 1
475 476
476 477 # if we don't have any revisions touched by these changesets, bail
477 478 if not revs:
478 479 yield struct.pack(">l", 0)
479 480 return
480 481
481 482 # add the parent of the first rev
482 483 p = self.parents(self.node(revs[0]))[0]
483 484 revs.insert(0, self.rev(p))
484 485
485 486 # for each delta that isn't contiguous in the log, we need to
486 487 # reconstruct the base, reconstruct the result, and then
487 488 # calculate the delta. We also need to do this where we've
488 489 # stored a full version and not a delta
489 490 for i in xrange(0, len(revs) - 1):
490 491 a, b = revs[i], revs[i + 1]
491 492 if a + 1 != b or self.base(b) == b:
492 493 for j in xrange(self.base(a), a + 1):
493 494 needed[j] = 1
494 495 for j in xrange(self.base(b), b + 1):
495 496 needed[j] = 1
496 497
497 498 # calculate spans to retrieve from datafile
498 499 needed = needed.keys()
499 500 needed.sort()
500 501 spans = []
501 502 oo = -1
502 503 ol = 0
503 504 for n in needed:
504 505 if n < 0: continue
505 506 o = self.start(n)
506 507 l = self.length(n)
507 508 if oo + ol == o: # can we merge with the previous?
508 509 nl = spans[-1][2]
509 510 nl.append((n, l))
510 511 ol += l
511 512 spans[-1] = (oo, ol, nl)
512 513 else:
513 514 oo = o
514 515 ol = l
515 516 spans.append((oo, ol, [(n, l)]))
516 517
517 518 # read spans in, divide up chunks
518 519 chunks = {}
519 520 for span in spans:
520 521 # we reopen the file for each span to make http happy for now
521 522 f = self.opener(self.datafile)
522 523 f.seek(span[0])
523 524 data = f.read(span[1])
524 525
525 526 # divide up the span
526 527 pos = 0
527 528 for r, l in span[2]:
528 529 chunks[r] = decompress(data[pos: pos + l])
529 530 pos += l
530 531
531 532 # helper to reconstruct intermediate versions
532 533 def construct(text, base, rev):
533 534 bins = [chunks[r] for r in xrange(base + 1, rev + 1)]
534 535 return mdiff.patches(text, bins)
535 536
536 537 # build deltas
537 538 deltas = []
538 539 for d in xrange(0, len(revs) - 1):
539 540 a, b = revs[d], revs[d + 1]
540 541 n = self.node(b)
541 542
542 543 # do we need to construct a new delta?
543 544 if a + 1 != b or self.base(b) == b:
544 545 if a >= 0:
545 546 base = self.base(a)
546 547 ta = chunks[self.base(a)]
547 548 ta = construct(ta, base, a)
548 549 else:
549 550 ta = ""
550 551
551 552 base = self.base(b)
552 553 if a > base:
553 554 base = a
554 555 tb = ta
555 556 else:
556 557 tb = chunks[self.base(b)]
557 558 tb = construct(tb, base, b)
558 559 d = self.diff(ta, tb)
559 560 else:
560 561 d = chunks[b]
561 562
562 563 p = self.parents(n)
563 564 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)]
564 565 l = struct.pack(">l", len(meta) + len(d) + 4)
565 566 yield l
566 567 yield meta
567 568 yield d
568 569
569 570 yield struct.pack(">l", 0)
570 571
571 572 def addgroup(self, revs, linkmapper, transaction, unique=0):
572 573 """
573 574 add a delta group
574 575
575 576 given a set of deltas, add them to the revision log. the
576 577 first delta is against its parent, which should be in our
577 578 log, the rest are against the previous delta.
578 579 """
579 580
580 581 #track the base of the current delta log
581 582 r = self.count()
582 583 t = r - 1
583 584 node = nullid
584 585
585 586 base = prev = -1
586 587 start = end = measure = 0
587 588 if r:
588 589 start = self.start(self.base(t))
589 590 end = self.end(t)
590 591 measure = self.length(self.base(t))
591 592 base = self.base(t)
592 593 prev = self.tip()
593 594
594 595 transaction.add(self.datafile, end)
595 596 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
596 597 dfh = self.opener(self.datafile, "a")
597 598 ifh = self.opener(self.indexfile, "a")
598 599
599 600 # loop through our set of deltas
600 601 chain = None
601 602 for chunk in revs:
602 603 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
603 604 link = linkmapper(cs)
604 605 if node in self.nodemap:
605 606 # this can happen if two branches make the same change
606 607 if unique:
607 608 raise RevlogError("already have %s" % hex(node[:4]))
608 609 chain = node
609 610 continue
610 611 delta = chunk[80:]
611 612
612 613 if not chain:
613 614 # retrieve the parent revision of the delta chain
614 615 chain = p1
615 616 if not chain in self.nodemap:
616 617 raise RevlogError("unknown base %s" % short(chain[:4]))
617 618
618 619 # full versions are inserted when the needed deltas become
619 620 # comparable to the uncompressed text or when the previous
620 621 # version is not the one we have a delta against. We use
621 622 # the size of the previous full rev as a proxy for the
622 623 # current size.
623 624
624 625 if chain == prev:
625 626 cdelta = compress(delta)
626 627
627 628 if chain != prev or (end - start + len(cdelta)) > measure * 2:
628 629 # flush our writes here so we can read it in revision
629 630 dfh.flush()
630 631 ifh.flush()
631 632 text = self.revision(chain)
632 633 text = self.patches(text, [delta])
633 634 chk = self.addrevision(text, transaction, link, p1, p2)
634 635 if chk != node:
635 636 raise RevlogError("consistency error adding group")
636 637 measure = len(text)
637 638 else:
638 639 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
639 640 self.index.append(e)
640 641 self.nodemap[node] = r
641 642 dfh.write(cdelta)
642 643 ifh.write(struct.pack(indexformat, *e))
643 644
644 645 t, r, chain, prev = r, r + 1, node, node
645 646 start = self.start(self.base(t))
646 647 end = self.end(t)
647 648
648 649 dfh.close()
649 650 ifh.close()
650 651 return node
General Comments 0
You need to be logged in to leave comments. Login now