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