##// END OF EJS Templates
Work on walk code.
Bryan O'Sullivan -
r723:9e0f3ba4 default
parent child Browse files
Show More
@@ -1,1355 +1,1355 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 demandload(globals(), "os re sys signal shutil")
10 10 demandload(globals(), "fancyopts ui hg util")
11 11 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
12 12 demandload(globals(), "errno socket version struct")
13 13
14 14 class UnknownCommand(Exception):
15 15 """Exception raised if command is not in the command table."""
16 16
17 17 def filterfiles(filters, files):
18 18 l = [x for x in files if x in filters]
19 19
20 20 for t in filters:
21 21 if t and t[-1] != "/":
22 22 t += "/"
23 23 l += [x for x in files if x.startswith(t)]
24 24 return l
25 25
26 26 def relfilter(repo, files):
27 27 cwd = repo.getcwd()
28 28 if cwd:
29 29 return filterfiles([util.pconvert(cwd)], files)
30 30 return files
31 31
32 32 def relpath(repo, args):
33 33 cwd = repo.getcwd()
34 34 if cwd:
35 35 return [util.pconvert(os.path.normpath(os.path.join(cwd, x)))
36 36 for x in args]
37 37 return args
38 38
39 39 revrangesep = ':'
40 40
41 41 def revrange(ui, repo, revs, revlog=None):
42 42 if revlog is None:
43 43 revlog = repo.changelog
44 44 revcount = revlog.count()
45 45 def fix(val, defval):
46 46 if not val:
47 47 return defval
48 48 try:
49 49 num = int(val)
50 50 if str(num) != val:
51 51 raise ValueError
52 52 if num < 0:
53 53 num += revcount
54 54 if not (0 <= num < revcount):
55 55 raise ValueError
56 56 except ValueError:
57 57 try:
58 58 num = repo.changelog.rev(repo.lookup(val))
59 59 except KeyError:
60 60 try:
61 61 num = revlog.rev(revlog.lookup(val))
62 62 except KeyError:
63 63 ui.warn('abort: invalid revision identifier %s\n' % val)
64 64 sys.exit(1)
65 65 return num
66 66 for spec in revs:
67 67 if spec.find(revrangesep) >= 0:
68 68 start, end = spec.split(revrangesep, 1)
69 69 start = fix(start, 0)
70 70 end = fix(end, revcount - 1)
71 71 if end > start:
72 72 end += 1
73 73 step = 1
74 74 else:
75 75 end -= 1
76 76 step = -1
77 77 for rev in xrange(start, end, step):
78 78 yield str(rev)
79 79 else:
80 80 yield spec
81 81
82 82 def make_filename(repo, r, pat, node=None,
83 83 total=None, seqno=None, revwidth=None):
84 84 node_expander = {
85 85 'H': lambda: hg.hex(node),
86 86 'R': lambda: str(r.rev(node)),
87 87 'h': lambda: hg.short(node),
88 88 }
89 89 expander = {
90 90 '%': lambda: '%',
91 91 'b': lambda: os.path.basename(repo.root),
92 92 }
93 93
94 94 if node:
95 95 expander.update(node_expander)
96 96 if node and revwidth is not None:
97 97 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
98 98 if total is not None:
99 99 expander['N'] = lambda: str(total)
100 100 if seqno is not None:
101 101 expander['n'] = lambda: str(seqno)
102 102 if total is not None and seqno is not None:
103 103 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
104 104
105 105 newname = []
106 106 patlen = len(pat)
107 107 i = 0
108 108 while i < patlen:
109 109 c = pat[i]
110 110 if c == '%':
111 111 i += 1
112 112 c = pat[i]
113 113 c = expander[c]()
114 114 newname.append(c)
115 115 i += 1
116 116 return ''.join(newname)
117 117
118 118 def dodiff(fp, ui, repo, files=None, node1=None, node2=None):
119 119 def date(c):
120 120 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
121 121
122 122 (c, a, d, u) = repo.changes(node1, node2, files)
123 123 if files:
124 124 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
125 125
126 126 if not c and not a and not d:
127 127 return
128 128
129 129 if node2:
130 130 change = repo.changelog.read(node2)
131 131 mmap2 = repo.manifest.read(change[0])
132 132 date2 = date(change)
133 133 def read(f):
134 134 return repo.file(f).read(mmap2[f])
135 135 else:
136 136 date2 = time.asctime()
137 137 if not node1:
138 138 node1 = repo.dirstate.parents()[0]
139 139 def read(f):
140 140 return repo.wfile(f).read()
141 141
142 142 if ui.quiet:
143 143 r = None
144 144 else:
145 145 hexfunc = ui.verbose and hg.hex or hg.short
146 146 r = [hexfunc(node) for node in [node1, node2] if node]
147 147
148 148 change = repo.changelog.read(node1)
149 149 mmap = repo.manifest.read(change[0])
150 150 date1 = date(change)
151 151
152 152 for f in c:
153 153 to = None
154 154 if f in mmap:
155 155 to = repo.file(f).read(mmap[f])
156 156 tn = read(f)
157 157 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
158 158 for f in a:
159 159 to = None
160 160 tn = read(f)
161 161 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
162 162 for f in d:
163 163 to = repo.file(f).read(mmap[f])
164 164 tn = None
165 165 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r))
166 166
167 167 def show_changeset(ui, repo, rev=0, changenode=None, filelog=None):
168 168 """show a single changeset or file revision"""
169 169 changelog = repo.changelog
170 170 if filelog:
171 171 log = filelog
172 172 filerev = rev
173 173 node = filenode = filelog.node(filerev)
174 174 changerev = filelog.linkrev(filenode)
175 175 changenode = changenode or changelog.node(changerev)
176 176 else:
177 177 log = changelog
178 178 changerev = rev
179 179 if changenode is None:
180 180 changenode = changelog.node(changerev)
181 181 elif not changerev:
182 182 rev = changerev = changelog.rev(changenode)
183 183 node = changenode
184 184
185 185 if ui.quiet:
186 186 ui.write("%d:%s\n" % (rev, hg.hex(node)))
187 187 return
188 188
189 189 changes = changelog.read(changenode)
190 190
191 191 parents = [(log.rev(parent), hg.hex(parent))
192 192 for parent in log.parents(node)
193 193 if ui.debugflag or parent != hg.nullid]
194 194 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
195 195 parents = []
196 196
197 197 ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode)))
198 198 for tag in repo.nodetags(changenode):
199 199 ui.status("tag: %s\n" % tag)
200 200 for parent in parents:
201 201 ui.write("parent: %d:%s\n" % parent)
202 202 if filelog:
203 203 ui.debug("file rev: %d:%s\n" % (filerev, hg.hex(filenode)))
204 204 ui.note("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
205 205 hg.hex(changes[0])))
206 206 ui.status("user: %s\n" % changes[1])
207 207 ui.status("date: %s\n" % time.asctime(
208 208 time.localtime(float(changes[2].split(' ')[0]))))
209 209 if ui.debugflag:
210 210 files = repo.changes(changelog.parents(changenode)[0], changenode)
211 211 for key, value in zip(["files:", "files+:", "files-:"], files):
212 212 if value:
213 213 ui.note("%-12s %s\n" % (key, " ".join(value)))
214 214 else:
215 215 ui.note("files: %s\n" % " ".join(changes[3]))
216 216 description = changes[4].strip()
217 217 if description:
218 218 if ui.verbose:
219 219 ui.status("description:\n")
220 220 ui.status(description)
221 221 ui.status("\n\n")
222 222 else:
223 223 ui.status("summary: %s\n" % description.splitlines()[0])
224 224 ui.status("\n")
225 225
226 226 def show_version(ui):
227 227 """output version and copyright information"""
228 228 ui.write("Mercurial version %s\n" % version.get_version())
229 229 ui.status(
230 230 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
231 231 "This is free software; see the source for copying conditions. "
232 232 "There is NO\nwarranty; "
233 233 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
234 234 )
235 235
236 236 def help_(ui, cmd=None):
237 237 """show help for a given command or all commands"""
238 238 if cmd:
239 239 try:
240 240 i = find(cmd)
241 241 ui.write("%s\n\n" % i[2])
242 242
243 243 if i[1]:
244 244 for s, l, d, c in i[1]:
245 245 opt = ' '
246 246 if s:
247 247 opt = opt + '-' + s + ' '
248 248 if l:
249 249 opt = opt + '--' + l + ' '
250 250 if d:
251 251 opt = opt + '(' + str(d) + ')'
252 252 ui.write(opt, "\n")
253 253 if c:
254 254 ui.write(' %s\n' % c)
255 255 ui.write("\n")
256 256
257 257 ui.write(i[0].__doc__, "\n")
258 258 except UnknownCommand:
259 259 ui.warn("hg: unknown command %s\n" % cmd)
260 260 sys.exit(0)
261 261 else:
262 262 if ui.verbose:
263 263 show_version(ui)
264 264 ui.write('\n')
265 265 if ui.verbose:
266 266 ui.write('hg commands:\n\n')
267 267 else:
268 268 ui.write('basic hg commands (use "hg help -v" for more):\n\n')
269 269
270 270 h = {}
271 271 for c, e in table.items():
272 272 f = c.split("|")[0]
273 273 if not ui.verbose and not f.startswith("^"):
274 274 continue
275 275 if not ui.debugflag and f.startswith("debug"):
276 276 continue
277 277 f = f.lstrip("^")
278 278 d = ""
279 279 if e[0].__doc__:
280 280 d = e[0].__doc__.splitlines(0)[0].rstrip()
281 281 h[f] = d
282 282
283 283 fns = h.keys()
284 284 fns.sort()
285 285 m = max(map(len, fns))
286 286 for f in fns:
287 287 ui.write(' %-*s %s\n' % (m, f, h[f]))
288 288
289 289 # Commands start here, listed alphabetically
290 290
291 291 def add(ui, repo, file1, *files):
292 292 '''add the specified files on the next commit'''
293 293 repo.add(relpath(repo, (file1,) + files))
294 294
295 295 def addremove(ui, repo, *files):
296 296 """add all new files, delete all missing files"""
297 297 if files:
298 298 files = relpath(repo, files)
299 299 d = []
300 300 u = []
301 301 for f in files:
302 302 p = repo.wjoin(f)
303 303 s = repo.dirstate.state(f)
304 304 isfile = os.path.isfile(p)
305 305 if s != 'r' and not isfile:
306 306 d.append(f)
307 307 elif s not in 'nmai' and isfile:
308 308 u.append(f)
309 309 else:
310 (c, a, d, u) = repo.changes(None, None)
310 (c, a, d, u) = repo.changes()
311 311 repo.add(u)
312 312 repo.remove(d)
313 313
314 314 def annotate(u, repo, file1, *files, **ops):
315 315 """show changeset information per file line"""
316 316 def getnode(rev):
317 317 return hg.short(repo.changelog.node(rev))
318 318
319 319 def getname(rev):
320 320 try:
321 321 return bcache[rev]
322 322 except KeyError:
323 323 cl = repo.changelog.read(repo.changelog.node(rev))
324 324 name = cl[1]
325 325 f = name.find('@')
326 326 if f >= 0:
327 327 name = name[:f]
328 328 f = name.find('<')
329 329 if f >= 0:
330 330 name = name[f+1:]
331 331 bcache[rev] = name
332 332 return name
333 333
334 334 bcache = {}
335 335 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
336 336 if not ops['user'] and not ops['changeset']:
337 337 ops['number'] = 1
338 338
339 339 node = repo.dirstate.parents()[0]
340 340 if ops['revision']:
341 341 node = repo.changelog.lookup(ops['revision'])
342 342 change = repo.changelog.read(node)
343 343 mmap = repo.manifest.read(change[0])
344 344 for f in relpath(repo, (file1,) + files):
345 345 lines = repo.file(f).annotate(mmap[f])
346 346 pieces = []
347 347
348 348 for o, f in opmap:
349 349 if ops[o]:
350 350 l = [f(n) for n, dummy in lines]
351 351 m = max(map(len, l))
352 352 pieces.append(["%*s" % (m, x) for x in l])
353 353
354 354 for p, l in zip(zip(*pieces), lines):
355 355 u.write(" ".join(p) + ": " + l[1])
356 356
357 357 def cat(ui, repo, file1, rev=None, **opts):
358 358 """output the latest or given revision of a file"""
359 359 r = repo.file(relpath(repo, [file1])[0])
360 360 if rev:
361 361 n = r.lookup(rev)
362 362 else:
363 363 n = r.tip()
364 364 if opts['output'] and opts['output'] != '-':
365 365 try:
366 366 outname = make_filename(repo, r, opts['output'], node=n)
367 367 fp = open(outname, 'wb')
368 368 except KeyError, inst:
369 369 ui.warn("error: invlaid format spec '%%%s' in output file name\n" %
370 370 inst.args[0])
371 371 sys.exit(1);
372 372 else:
373 373 fp = sys.stdout
374 374 fp.write(r.read(n))
375 375
376 376 def clone(ui, source, dest=None, **opts):
377 377 """make a copy of an existing repository"""
378 378 if dest is None:
379 379 dest = os.path.basename(os.path.normpath(source))
380 380
381 381 if os.path.exists(dest):
382 382 ui.warn("abort: destination '%s' already exists\n" % dest)
383 383 return 1
384 384
385 385 class Dircleanup:
386 386 def __init__(self, dir_):
387 387 self.rmtree = shutil.rmtree
388 388 self.dir_ = dir_
389 389 os.mkdir(dir_)
390 390 def close(self):
391 391 self.dir_ = None
392 392 def __del__(self):
393 393 if self.dir_:
394 394 self.rmtree(self.dir_, True)
395 395
396 396 d = Dircleanup(dest)
397 397 abspath = source
398 398 source = ui.expandpath(source)
399 399 other = hg.repository(ui, source)
400 400
401 401 if other.dev() != -1:
402 402 abspath = os.path.abspath(source)
403 403 copyfile = (os.stat(dest).st_dev == other.dev()
404 404 and getattr(os, 'link', None) or shutil.copy2)
405 405 if copyfile is not shutil.copy2:
406 406 ui.note("cloning by hardlink\n")
407 407 util.copytree(os.path.join(source, ".hg"), os.path.join(dest, ".hg"),
408 408 copyfile)
409 409 try:
410 410 os.unlink(os.path.join(dest, ".hg", "dirstate"))
411 411 except IOError:
412 412 pass
413 413
414 414 repo = hg.repository(ui, dest)
415 415
416 416 else:
417 417 repo = hg.repository(ui, dest, create=1)
418 418 repo.pull(other)
419 419
420 420 f = repo.opener("hgrc", "w")
421 421 f.write("[paths]\n")
422 422 f.write("default = %s\n" % abspath)
423 423
424 424 if not opts['noupdate']:
425 425 update(ui, repo)
426 426
427 427 d.close()
428 428
429 429 def commit(ui, repo, *files, **opts):
430 430 """commit the specified files or all outstanding changes"""
431 431 text = opts['text']
432 432 logfile = opts['logfile']
433 433 if not text and logfile:
434 434 try:
435 435 text = open(logfile).read()
436 436 except IOError, why:
437 437 ui.warn("Can't read commit text %s: %s\n" % (logfile, why))
438 438
439 439 if opts['addremove']:
440 440 addremove(ui, repo, *files)
441 441 repo.commit(relpath(repo, files), text, opts['user'], opts['date'])
442 442
443 443 def copy(ui, repo, source, dest):
444 444 """mark a file as copied or renamed for the next commit"""
445 445 return repo.copy(*relpath(repo, (source, dest)))
446 446
447 447 def debugcheckstate(ui, repo):
448 448 """validate the correctness of the current dirstate"""
449 449 parent1, parent2 = repo.dirstate.parents()
450 450 repo.dirstate.read()
451 451 dc = repo.dirstate.map
452 452 keys = dc.keys()
453 453 keys.sort()
454 454 m1n = repo.changelog.read(parent1)[0]
455 455 m2n = repo.changelog.read(parent2)[0]
456 456 m1 = repo.manifest.read(m1n)
457 457 m2 = repo.manifest.read(m2n)
458 458 errors = 0
459 459 for f in dc:
460 460 state = repo.dirstate.state(f)
461 461 if state in "nr" and f not in m1:
462 462 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
463 463 errors += 1
464 464 if state in "a" and f in m1:
465 465 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
466 466 errors += 1
467 467 if state in "m" and f not in m1 and f not in m2:
468 468 ui.warn("%s in state %s, but not in either manifest\n" %
469 469 (f, state))
470 470 errors += 1
471 471 for f in m1:
472 472 state = repo.dirstate.state(f)
473 473 if state not in "nrm":
474 474 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
475 475 errors += 1
476 476 if errors:
477 477 ui.warn(".hg/dirstate inconsistent with current parent's manifest\n")
478 478 sys.exit(1)
479 479
480 480 def debugstate(ui, repo):
481 481 """show the contents of the current dirstate"""
482 482 repo.dirstate.read()
483 483 dc = repo.dirstate.map
484 484 keys = dc.keys()
485 485 keys.sort()
486 486 for file_ in keys:
487 487 ui.write("%c %s\n" % (dc[file_][0], file_))
488 488
489 489 def debugindex(ui, file_):
490 490 """dump the contents of an index file"""
491 491 r = hg.revlog(hg.opener(""), file_, "")
492 492 ui.write(" rev offset length base linkrev" +
493 493 " p1 p2 nodeid\n")
494 494 for i in range(r.count()):
495 495 e = r.index[i]
496 496 ui.write("% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s..\n" % (
497 497 i, e[0], e[1], e[2], e[3],
498 498 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5])))
499 499
500 500 def debugindexdot(ui, file_):
501 501 """dump an index DAG as a .dot file"""
502 502 r = hg.revlog(hg.opener(""), file_, "")
503 503 ui.write("digraph G {\n")
504 504 for i in range(r.count()):
505 505 e = r.index[i]
506 506 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
507 507 if e[5] != hg.nullid:
508 508 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
509 509 ui.write("}\n")
510 510
511 511 def diff(ui, repo, *files, **opts):
512 512 """diff working directory (or selected files)"""
513 513 revs = []
514 514 if opts['rev']:
515 515 revs = map(lambda x: repo.lookup(x), opts['rev'])
516 516
517 517 if len(revs) > 2:
518 518 ui.warn("too many revisions to diff\n")
519 519 sys.exit(1)
520 520
521 521 if files:
522 522 files = relpath(repo, files)
523 523 else:
524 524 files = relpath(repo, [""])
525 525
526 526 dodiff(sys.stdout, ui, repo, files, *revs)
527 527
528 528 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
529 529 node = repo.lookup(changeset)
530 530 prev, other = repo.changelog.parents(node)
531 531 change = repo.changelog.read(node)
532 532
533 533 if opts['output'] and opts['output'] != '-':
534 534 try:
535 535 outname = make_filename(repo, repo.changelog, opts['output'],
536 536 node=node, total=total, seqno=seqno,
537 537 revwidth=revwidth)
538 538 fp = open(outname, 'wb')
539 539 except KeyError, inst:
540 540 ui.warn("error: invalid format spec '%%%s' in output file name\n" %
541 541 inst.args[0])
542 542 sys.exit(1)
543 543 else:
544 544 fp = sys.stdout
545 545
546 546 fp.write("# HG changeset patch\n")
547 547 fp.write("# User %s\n" % change[1])
548 548 fp.write("# Node ID %s\n" % hg.hex(node))
549 549 fp.write("# Parent %s\n" % hg.hex(prev))
550 550 if other != hg.nullid:
551 551 fp.write("# Parent %s\n" % hg.hex(other))
552 552 fp.write(change[4].rstrip())
553 553 fp.write("\n\n")
554 554
555 555 dodiff(fp, ui, repo, None, prev, node)
556 556
557 557 def export(ui, repo, *changesets, **opts):
558 558 """dump the header and diffs for one or more changesets"""
559 559 if not changesets:
560 560 ui.warn("error: export requires at least one changeset\n")
561 561 sys.exit(1)
562 562 seqno = 0
563 563 revs = list(revrange(ui, repo, changesets))
564 564 total = len(revs)
565 565 revwidth = max(len(revs[0]), len(revs[-1]))
566 566 for cset in revs:
567 567 seqno += 1
568 568 doexport(ui, repo, cset, seqno, total, revwidth, opts)
569 569
570 570 def forget(ui, repo, file1, *files):
571 571 """don't add the specified files on the next commit"""
572 572 repo.forget(relpath(repo, (file1,) + files))
573 573
574 574 def heads(ui, repo):
575 575 """show current repository heads"""
576 576 for n in repo.changelog.heads():
577 577 show_changeset(ui, repo, changenode=n)
578 578
579 579 def identify(ui, repo):
580 580 """print information about the working copy"""
581 581 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
582 582 if not parents:
583 583 ui.write("unknown\n")
584 584 return
585 585
586 586 hexfunc = ui.verbose and hg.hex or hg.short
587 (c, a, d, u) = repo.changes(None, None)
587 (c, a, d, u) = repo.changes()
588 588 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
589 589 (c or a or d) and "+" or "")]
590 590
591 591 if not ui.quiet:
592 592 # multiple tags for a single parent separated by '/'
593 593 parenttags = ['/'.join(tags)
594 594 for tags in map(repo.nodetags, parents) if tags]
595 595 # tags for multiple parents separated by ' + '
596 596 output.append(' + '.join(parenttags))
597 597
598 598 ui.write("%s\n" % ' '.join(output))
599 599
600 600 def import_(ui, repo, patch1, *patches, **opts):
601 601 """import an ordered set of patches"""
602 602 try:
603 603 import psyco
604 604 psyco.full()
605 605 except ImportError:
606 606 pass
607 607
608 608 patches = (patch1,) + patches
609 609
610 610 d = opts["base"]
611 611 strip = opts["strip"]
612 612
613 613 for patch in patches:
614 614 ui.status("applying %s\n" % patch)
615 615 pf = os.path.join(d, patch)
616 616
617 617 text = []
618 618 user = None
619 619 hgpatch = False
620 620 for line in file(pf):
621 621 line = line.rstrip()
622 622 if line.startswith("--- ") or line.startswith("diff -r"):
623 623 break
624 624 elif hgpatch:
625 625 # parse values when importing the result of an hg export
626 626 if line.startswith("# User "):
627 627 user = line[7:]
628 628 ui.debug('User: %s\n' % user)
629 629 elif not line.startswith("# ") and line:
630 630 text.append(line)
631 631 hgpatch = False
632 632 elif line == '# HG changeset patch':
633 633 hgpatch = True
634 634 else:
635 635 text.append(line)
636 636
637 637 # make sure text isn't empty
638 638 if not text:
639 639 text = "imported patch %s\n" % patch
640 640 else:
641 641 text = "%s\n" % '\n'.join(text)
642 642 ui.debug('text:\n%s\n' % text)
643 643
644 644 f = os.popen("patch -p%d < %s" % (strip, pf))
645 645 files = []
646 646 for l in f.read().splitlines():
647 647 l.rstrip('\r\n');
648 648 ui.status("%s\n" % l)
649 649 if l.startswith('patching file '):
650 650 pf = l[14:]
651 651 if pf not in files:
652 652 files.append(pf)
653 653 patcherr = f.close()
654 654 if patcherr:
655 655 sys.stderr.write("patch failed")
656 656 sys.exit(1)
657 657
658 658 if len(files) > 0:
659 659 addremove(ui, repo, *files)
660 660 repo.commit(files, text, user)
661 661
662 662 def init(ui, source=None):
663 663 """create a new repository in the current directory"""
664 664
665 665 if source:
666 666 ui.warn("no longer supported: use \"hg clone\" instead\n")
667 667 sys.exit(1)
668 668 hg.repository(ui, ".", create=1)
669 669
670 670 def locate(ui, repo, *pats, **opts):
671 671 """locate files matching specific patterns"""
672 672 if [p for p in pats if os.sep in p]:
673 673 ui.warn("error: patterns may not contain '%s'\n" % os.sep)
674 674 ui.warn("use '-i <dir>' instead\n")
675 675 sys.exit(1)
676 676 def compile(pats, head='^', tail=os.sep, on_empty=True):
677 677 if not pats:
678 678 class c:
679 679 def match(self, x):
680 680 return on_empty
681 681 return c()
682 682 fnpats = [fnmatch.translate(os.path.normpath(os.path.normcase(p)))[:-1]
683 683 for p in pats]
684 684 regexp = r'%s(?:%s)%s' % (head, '|'.join(fnpats), tail)
685 685 return re.compile(regexp)
686 686 exclude = compile(opts['exclude'], on_empty=False)
687 687 include = compile(opts['include'])
688 688 pat = compile(pats, head='', tail='$')
689 689 end = opts['print0'] and '\0' or '\n'
690 690 if opts['rev']:
691 691 node = repo.manifest.lookup(opts['rev'])
692 692 else:
693 693 node = repo.manifest.tip()
694 694 manifest = repo.manifest.read(node)
695 695 cwd = repo.getcwd()
696 696 cwd_plus = cwd and (cwd + os.sep)
697 697 found = []
698 698 for f in manifest:
699 699 f = os.path.normcase(f)
700 700 if exclude.match(f) or not(include.match(f) and
701 701 f.startswith(cwd_plus) and
702 702 pat.match(os.path.basename(f))):
703 703 continue
704 704 if opts['fullpath']:
705 705 f = os.path.join(repo.root, f)
706 706 elif cwd:
707 707 f = f[len(cwd_plus):]
708 708 found.append(f)
709 709 found.sort()
710 710 for f in found:
711 711 ui.write(f, end)
712 712
713 713 def log(ui, repo, f=None, **opts):
714 714 """show the revision history of the repository or a single file"""
715 715 if f:
716 716 files = relpath(repo, [f])
717 717 filelog = repo.file(files[0])
718 718 log = filelog
719 719 lookup = filelog.lookup
720 720 else:
721 721 files = None
722 722 filelog = None
723 723 log = repo.changelog
724 724 lookup = repo.lookup
725 725 revlist = []
726 726 revs = [log.rev(lookup(rev)) for rev in opts['rev']]
727 727 while revs:
728 728 if len(revs) == 1:
729 729 revlist.append(revs.pop(0))
730 730 else:
731 731 a = revs.pop(0)
732 732 b = revs.pop(0)
733 733 off = a > b and -1 or 1
734 734 revlist.extend(range(a, b + off, off))
735 735
736 736 for i in revlist or range(log.count() - 1, -1, -1):
737 737 show_changeset(ui, repo, filelog=filelog, rev=i)
738 738 if opts['patch']:
739 739 if filelog:
740 740 filenode = filelog.node(i)
741 741 i = filelog.linkrev(filenode)
742 742 changenode = repo.changelog.node(i)
743 743 prev, other = repo.changelog.parents(changenode)
744 744 dodiff(sys.stdout, ui, repo, files, prev, changenode)
745 745 ui.write("\n\n")
746 746
747 747 def manifest(ui, repo, rev=None):
748 748 """output the latest or given revision of the project manifest"""
749 749 if rev:
750 750 try:
751 751 # assume all revision numbers are for changesets
752 752 n = repo.lookup(rev)
753 753 change = repo.changelog.read(n)
754 754 n = change[0]
755 755 except hg.RepoError:
756 756 n = repo.manifest.lookup(rev)
757 757 else:
758 758 n = repo.manifest.tip()
759 759 m = repo.manifest.read(n)
760 760 mf = repo.manifest.readflags(n)
761 761 files = m.keys()
762 762 files.sort()
763 763
764 764 for f in files:
765 765 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
766 766
767 767 def parents(ui, repo, node=None):
768 768 '''show the parents of the current working dir'''
769 769 if node:
770 770 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
771 771 else:
772 772 p = repo.dirstate.parents()
773 773
774 774 for n in p:
775 775 if n != hg.nullid:
776 776 show_changeset(ui, repo, changenode=n)
777 777
778 778 def pull(ui, repo, source="default", **opts):
779 779 """pull changes from the specified source"""
780 780 source = ui.expandpath(source)
781 781 ui.status('pulling from %s\n' % (source))
782 782
783 783 other = hg.repository(ui, source)
784 784 r = repo.pull(other)
785 785 if not r:
786 786 if opts['update']:
787 787 return update(ui, repo)
788 788 else:
789 789 ui.status("(run 'hg update' to get a working copy)\n")
790 790
791 791 return r
792 792
793 793 def push(ui, repo, dest="default-push"):
794 794 """push changes to the specified destination"""
795 795 dest = ui.expandpath(dest)
796 796 ui.status('pushing to %s\n' % (dest))
797 797
798 798 other = hg.repository(ui, dest)
799 799 r = repo.push(other)
800 800 return r
801 801
802 802 def rawcommit(ui, repo, *flist, **rc):
803 803 "raw commit interface"
804 804
805 805 text = rc['text']
806 806 if not text and rc['logfile']:
807 807 try:
808 808 text = open(rc['logfile']).read()
809 809 except IOError:
810 810 pass
811 811 if not text and not rc['logfile']:
812 812 ui.warn("abort: missing commit text\n")
813 813 return 1
814 814
815 815 files = relpath(repo, list(flist))
816 816 if rc['files']:
817 817 files += open(rc['files']).read().splitlines()
818 818
819 819 rc['parent'] = map(repo.lookup, rc['parent'])
820 820
821 821 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
822 822
823 823 def recover(ui, repo):
824 824 """roll back an interrupted transaction"""
825 825 repo.recover()
826 826
827 827 def remove(ui, repo, file1, *files):
828 828 """remove the specified files on the next commit"""
829 829 repo.remove(relpath(repo, (file1,) + files))
830 830
831 831 def revert(ui, repo, *names, **opts):
832 832 """revert modified files or dirs back to their unmodified states"""
833 833 node = opts['rev'] and repo.lookup(opts['rev']) or \
834 834 repo.dirstate.parents()[0]
835 835 root = os.path.realpath(repo.root)
836 836
837 837 def trimpath(p):
838 838 p = os.path.realpath(p)
839 839 if p.startswith(root):
840 840 rest = p[len(root):]
841 841 if not rest:
842 842 return rest
843 843 if p.startswith(os.sep):
844 844 return rest[1:]
845 845 return p
846 846
847 847 relnames = map(trimpath, names or [os.getcwd()])
848 848 chosen = {}
849 849
850 850 def choose(name):
851 851 def body(name):
852 852 for r in relnames:
853 853 if not name.startswith(r):
854 854 continue
855 855 rest = name[len(r):]
856 856 if not rest:
857 857 return r, True
858 858 depth = rest.count(os.sep)
859 859 if not r:
860 860 if depth == 0 or not opts['nonrecursive']:
861 861 return r, True
862 862 elif rest[0] == os.sep:
863 863 if depth == 1 or not opts['nonrecursive']:
864 864 return r, True
865 865 return None, False
866 866 relname, ret = body(name)
867 867 if ret:
868 868 chosen[relname] = 1
869 869 return ret
870 870
871 871 r = repo.update(node, False, True, choose, False)
872 872 for n in relnames:
873 873 if n not in chosen:
874 874 ui.warn('error: no matches for %s\n' % n)
875 875 r = 1
876 876 sys.stdout.flush()
877 877 return r
878 878
879 879 def root(ui, repo):
880 880 """print the root (top) of the current working dir"""
881 881 ui.write(repo.root + "\n")
882 882
883 883 def serve(ui, repo, **opts):
884 884 """export the repository via HTTP"""
885 885
886 886 if opts["stdio"]:
887 887 fin, fout = sys.stdin, sys.stdout
888 888 sys.stdout = sys.stderr
889 889
890 890 def getarg():
891 891 argline = fin.readline()[:-1]
892 892 arg, l = argline.split()
893 893 val = fin.read(int(l))
894 894 return arg, val
895 895 def respond(v):
896 896 fout.write("%d\n" % len(v))
897 897 fout.write(v)
898 898 fout.flush()
899 899
900 900 lock = None
901 901
902 902 while 1:
903 903 cmd = fin.readline()[:-1]
904 904 if cmd == '':
905 905 return
906 906 if cmd == "heads":
907 907 h = repo.heads()
908 908 respond(" ".join(map(hg.hex, h)) + "\n")
909 909 if cmd == "lock":
910 910 lock = repo.lock()
911 911 respond("")
912 912 if cmd == "unlock":
913 913 if lock:
914 914 lock.release()
915 915 lock = None
916 916 respond("")
917 917 elif cmd == "branches":
918 918 arg, nodes = getarg()
919 919 nodes = map(hg.bin, nodes.split(" "))
920 920 r = []
921 921 for b in repo.branches(nodes):
922 922 r.append(" ".join(map(hg.hex, b)) + "\n")
923 923 respond("".join(r))
924 924 elif cmd == "between":
925 925 arg, pairs = getarg()
926 926 pairs = [map(hg.bin, p.split("-")) for p in pairs.split(" ")]
927 927 r = []
928 928 for b in repo.between(pairs):
929 929 r.append(" ".join(map(hg.hex, b)) + "\n")
930 930 respond("".join(r))
931 931 elif cmd == "changegroup":
932 932 nodes = []
933 933 arg, roots = getarg()
934 934 nodes = map(hg.bin, roots.split(" "))
935 935
936 936 cg = repo.changegroup(nodes)
937 937 while 1:
938 938 d = cg.read(4096)
939 939 if not d:
940 940 break
941 941 fout.write(d)
942 942
943 943 fout.flush()
944 944
945 945 elif cmd == "addchangegroup":
946 946 if not lock:
947 947 respond("not locked")
948 948 continue
949 949 respond("")
950 950
951 951 r = repo.addchangegroup(fin)
952 952 respond("")
953 953
954 954 def openlog(opt, default):
955 955 if opts[opt] and opts[opt] != '-':
956 956 return open(opts[opt], 'w')
957 957 else:
958 958 return default
959 959
960 960 httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
961 961 opts["address"], opts["port"],
962 962 openlog('accesslog', sys.stdout),
963 963 openlog('errorlog', sys.stderr))
964 964 if ui.verbose:
965 965 addr, port = httpd.socket.getsockname()
966 966 if addr == '0.0.0.0':
967 967 addr = socket.gethostname()
968 968 else:
969 969 try:
970 970 addr = socket.gethostbyaddr(addr)[0]
971 971 except socket.error:
972 972 pass
973 973 if port != 80:
974 974 ui.status('listening at http://%s:%d/\n' % (addr, port))
975 975 else:
976 976 ui.status('listening at http://%s/\n' % addr)
977 977 httpd.serve_forever()
978 978
979 979 def status(ui, repo):
980 980 '''show changed files in the working directory
981 981
982 982 C = changed
983 983 A = added
984 984 R = removed
985 985 ? = not tracked'''
986 986
987 (c, a, d, u) = repo.changes(None, None)
987 (c, a, d, u) = repo.changes()
988 988 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
989 989
990 990 for f in c:
991 991 ui.write("C ", f, "\n")
992 992 for f in a:
993 993 ui.write("A ", f, "\n")
994 994 for f in d:
995 995 ui.write("R ", f, "\n")
996 996 for f in u:
997 997 ui.write("? ", f, "\n")
998 998
999 999 def tag(ui, repo, name, rev=None, **opts):
1000 1000 """add a tag for the current tip or a given revision"""
1001 1001
1002 1002 if name == "tip":
1003 1003 ui.warn("abort: 'tip' is a reserved name!\n")
1004 1004 return -1
1005 1005 if rev:
1006 1006 r = hg.hex(repo.lookup(rev))
1007 1007 else:
1008 1008 r = hg.hex(repo.changelog.tip())
1009 1009
1010 1010 if name.find(revrangesep) >= 0:
1011 1011 ui.warn("abort: '%s' cannot be used in a tag name\n" % revrangesep)
1012 1012 return -1
1013 1013
1014 1014 if opts['local']:
1015 1015 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1016 1016 return
1017 1017
1018 (c, a, d, u) = repo.changes(None, None)
1018 (c, a, d, u) = repo.changes()
1019 1019 for x in (c, a, d, u):
1020 1020 if ".hgtags" in x:
1021 1021 ui.warn("abort: working copy of .hgtags is changed!\n")
1022 1022 ui.status("(please commit .hgtags manually)\n")
1023 1023 return -1
1024 1024
1025 1025 add = not os.path.exists(repo.wjoin(".hgtags"))
1026 1026 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1027 1027 if add:
1028 1028 repo.add([".hgtags"])
1029 1029
1030 1030 if not opts['text']:
1031 1031 opts['text'] = "Added tag %s for changeset %s" % (name, r)
1032 1032
1033 1033 repo.commit([".hgtags"], opts['text'], opts['user'], opts['date'])
1034 1034
1035 1035 def tags(ui, repo):
1036 1036 """list repository tags"""
1037 1037
1038 1038 l = repo.tagslist()
1039 1039 l.reverse()
1040 1040 for t, n in l:
1041 1041 try:
1042 1042 r = "%5d:%s" % (repo.changelog.rev(n), hg.hex(n))
1043 1043 except KeyError:
1044 1044 r = " ?:?"
1045 1045 ui.write("%-30s %s\n" % (t, r))
1046 1046
1047 1047 def tip(ui, repo):
1048 1048 """show the tip revision"""
1049 1049 n = repo.changelog.tip()
1050 1050 show_changeset(ui, repo, changenode=n)
1051 1051
1052 1052 def undo(ui, repo):
1053 1053 """undo the last commit or pull
1054 1054
1055 1055 Roll back the last pull or commit transaction on the
1056 1056 repository, restoring the project to its earlier state.
1057 1057
1058 1058 This command should be used with care. There is only one level of
1059 1059 undo and there is no redo.
1060 1060
1061 1061 This command is not intended for use on public repositories. Once
1062 1062 a change is visible for pull by other users, undoing it locally is
1063 1063 ineffective.
1064 1064 """
1065 1065 repo.undo()
1066 1066
1067 1067 def update(ui, repo, node=None, merge=False, clean=False):
1068 1068 '''update or merge working directory
1069 1069
1070 1070 If there are no outstanding changes in the working directory and
1071 1071 there is a linear relationship between the current version and the
1072 1072 requested version, the result is the requested version.
1073 1073
1074 1074 Otherwise the result is a merge between the contents of the
1075 1075 current working directory and the requested version. Files that
1076 1076 changed between either parent are marked as changed for the next
1077 1077 commit and a commit must be performed before any further updates
1078 1078 are allowed.
1079 1079 '''
1080 1080 node = node and repo.lookup(node) or repo.changelog.tip()
1081 1081 return repo.update(node, allow=merge, force=clean)
1082 1082
1083 1083 def verify(ui, repo):
1084 1084 """verify the integrity of the repository"""
1085 1085 return repo.verify()
1086 1086
1087 1087 # Command options and aliases are listed here, alphabetically
1088 1088
1089 1089 table = {
1090 1090 "^add": (add, [], "hg add [files]"),
1091 1091 "addremove": (addremove, [], "hg addremove [files]"),
1092 1092 "^annotate":
1093 1093 (annotate,
1094 1094 [('r', 'revision', '', 'revision'),
1095 1095 ('u', 'user', None, 'show user'),
1096 1096 ('n', 'number', None, 'show revision number'),
1097 1097 ('c', 'changeset', None, 'show changeset')],
1098 1098 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
1099 1099 "cat":
1100 1100 (cat,
1101 1101 [('o', 'output', "", 'output to file')],
1102 1102 'hg cat [-o outfile] <file> [rev]'),
1103 1103 "^clone":
1104 1104 (clone,
1105 1105 [('U', 'noupdate', None, 'skip update after cloning')],
1106 1106 'hg clone [options] <source> [dest]'),
1107 1107 "^commit|ci":
1108 1108 (commit,
1109 1109 [('t', 'text', "", 'commit text'),
1110 1110 ('A', 'addremove', None, 'run add/remove during commit'),
1111 1111 ('l', 'logfile', "", 'commit text file'),
1112 1112 ('d', 'date', "", 'date code'),
1113 1113 ('u', 'user', "", 'user')],
1114 1114 'hg commit [files]'),
1115 1115 "copy": (copy, [], 'hg copy <source> <dest>'),
1116 1116 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1117 1117 "debugstate": (debugstate, [], 'debugstate'),
1118 1118 "debugindex": (debugindex, [], 'debugindex <file>'),
1119 1119 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
1120 1120 "^diff":
1121 1121 (diff,
1122 1122 [('r', 'rev', [], 'revision')],
1123 1123 'hg diff [-r A] [-r B] [files]'),
1124 1124 "^export":
1125 1125 (export,
1126 1126 [('o', 'output', "", 'output to file')],
1127 1127 "hg export [-o file] <changeset> ..."),
1128 1128 "forget": (forget, [], "hg forget [files]"),
1129 1129 "heads": (heads, [], 'hg heads'),
1130 1130 "help": (help_, [], 'hg help [command]'),
1131 1131 "identify|id": (identify, [], 'hg identify'),
1132 1132 "import|patch":
1133 1133 (import_,
1134 1134 [('p', 'strip', 1, 'path strip'),
1135 1135 ('b', 'base', "", 'base path')],
1136 1136 "hg import [options] <patches>"),
1137 1137 "^init": (init, [], 'hg init'),
1138 1138 "locate":
1139 1139 (locate,
1140 1140 [('0', 'print0', None, 'end records with NUL'),
1141 1141 ('f', 'fullpath', None, 'print complete paths'),
1142 1142 ('i', 'include', [], 'include path in search'),
1143 1143 ('r', 'rev', '', 'revision'),
1144 1144 ('x', 'exclude', [], 'exclude path from search')],
1145 1145 'hg locate [options] [files]'),
1146 1146 "^log|history":
1147 1147 (log,
1148 1148 [('r', 'rev', [], 'revision'),
1149 1149 ('p', 'patch', None, 'show patch')],
1150 1150 'hg log [-r A] [-r B] [-p] [file]'),
1151 1151 "manifest": (manifest, [], 'hg manifest [rev]'),
1152 1152 "parents": (parents, [], 'hg parents [node]'),
1153 1153 "^pull":
1154 1154 (pull,
1155 1155 [('u', 'update', None, 'update working directory')],
1156 1156 'hg pull [options] [source]'),
1157 1157 "^push": (push, [], 'hg push <destination>'),
1158 1158 "rawcommit":
1159 1159 (rawcommit,
1160 1160 [('p', 'parent', [], 'parent'),
1161 1161 ('d', 'date', "", 'date code'),
1162 1162 ('u', 'user', "", 'user'),
1163 1163 ('F', 'files', "", 'file list'),
1164 1164 ('t', 'text', "", 'commit text'),
1165 1165 ('l', 'logfile', "", 'commit text file')],
1166 1166 'hg rawcommit [options] [files]'),
1167 1167 "recover": (recover, [], "hg recover"),
1168 1168 "^remove|rm": (remove, [], "hg remove [files]"),
1169 1169 "^revert":
1170 1170 (revert,
1171 1171 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1172 1172 ("r", "rev", "", "revision")],
1173 1173 "hg revert [files|dirs]"),
1174 1174 "root": (root, [], "hg root"),
1175 1175 "^serve":
1176 1176 (serve,
1177 1177 [('A', 'accesslog', '', 'access log file'),
1178 1178 ('E', 'errorlog', '', 'error log file'),
1179 1179 ('p', 'port', 8000, 'listen port'),
1180 1180 ('a', 'address', '', 'interface address'),
1181 1181 ('n', 'name', os.getcwd(), 'repository name'),
1182 1182 ('', 'stdio', None, 'for remote clients'),
1183 1183 ('t', 'templates', "", 'template map')],
1184 1184 "hg serve [options]"),
1185 1185 "^status": (status, [], 'hg status'),
1186 1186 "tag":
1187 1187 (tag,
1188 1188 [('l', 'local', None, 'make the tag local'),
1189 1189 ('t', 'text', "", 'commit text'),
1190 1190 ('d', 'date', "", 'date code'),
1191 1191 ('u', 'user', "", 'user')],
1192 1192 'hg tag [options] <name> [rev]'),
1193 1193 "tags": (tags, [], 'hg tags'),
1194 1194 "tip": (tip, [], 'hg tip'),
1195 1195 "undo": (undo, [], 'hg undo'),
1196 1196 "^update|up|checkout|co":
1197 1197 (update,
1198 1198 [('m', 'merge', None, 'allow merging of conflicts'),
1199 1199 ('C', 'clean', None, 'overwrite locally modified files')],
1200 1200 'hg update [options] [node]'),
1201 1201 "verify": (verify, [], 'hg verify'),
1202 1202 "version": (show_version, [], 'hg version'),
1203 1203 }
1204 1204
1205 1205 globalopts = [('v', 'verbose', None, 'verbose'),
1206 1206 ('', 'debug', None, 'debug'),
1207 1207 ('q', 'quiet', None, 'quiet'),
1208 1208 ('', 'profile', None, 'profile'),
1209 1209 ('R', 'repository', "", 'repository root directory'),
1210 1210 ('', 'traceback', None, 'print traceback on exception'),
1211 1211 ('y', 'noninteractive', None, 'run non-interactively'),
1212 1212 ('', 'version', None, 'output version information and exit'),
1213 1213 ]
1214 1214
1215 1215 norepo = "clone init version help debugindex debugindexdot"
1216 1216
1217 1217 def find(cmd):
1218 1218 for e in table.keys():
1219 1219 if re.match("(%s)$" % e, cmd):
1220 1220 return table[e]
1221 1221
1222 1222 raise UnknownCommand(cmd)
1223 1223
1224 1224 class SignalInterrupt(Exception):
1225 1225 """Exception raised on SIGTERM and SIGHUP."""
1226 1226
1227 1227 def catchterm(*args):
1228 1228 raise SignalInterrupt
1229 1229
1230 1230 def run():
1231 1231 sys.exit(dispatch(sys.argv[1:]))
1232 1232
1233 1233 class ParseError(Exception):
1234 1234 """Exception raised on errors in parsing the command line."""
1235 1235
1236 1236 def parse(args):
1237 1237 options = {}
1238 1238 cmdoptions = {}
1239 1239
1240 1240 try:
1241 1241 args = fancyopts.fancyopts(args, globalopts, options)
1242 1242 except fancyopts.getopt.GetoptError, inst:
1243 1243 raise ParseError(None, inst)
1244 1244
1245 1245 if options["version"]:
1246 1246 return ("version", show_version, [], options, cmdoptions)
1247 1247 elif not args:
1248 1248 return ("help", help_, [], options, cmdoptions)
1249 1249 else:
1250 1250 cmd, args = args[0], args[1:]
1251 1251
1252 1252 i = find(cmd)
1253 1253
1254 1254 # combine global options into local
1255 1255 c = list(i[1])
1256 1256 for o in globalopts:
1257 1257 c.append((o[0], o[1], options[o[1]], o[3]))
1258 1258
1259 1259 try:
1260 1260 args = fancyopts.fancyopts(args, c, cmdoptions)
1261 1261 except fancyopts.getopt.GetoptError, inst:
1262 1262 raise ParseError(cmd, inst)
1263 1263
1264 1264 # separate global options back out
1265 1265 for o in globalopts:
1266 1266 n = o[1]
1267 1267 options[n] = cmdoptions[n]
1268 1268 del cmdoptions[n]
1269 1269
1270 1270 return (cmd, i[0], args, options, cmdoptions)
1271 1271
1272 1272 def dispatch(args):
1273 1273 signal.signal(signal.SIGTERM, catchterm)
1274 1274 try:
1275 1275 signal.signal(signal.SIGHUP, catchterm)
1276 1276 except AttributeError:
1277 1277 pass
1278 1278
1279 1279 try:
1280 1280 cmd, func, args, options, cmdoptions = parse(args)
1281 1281 except ParseError, inst:
1282 1282 u = ui.ui()
1283 1283 if inst.args[0]:
1284 1284 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
1285 1285 help_(u, inst.args[0])
1286 1286 else:
1287 1287 u.warn("hg: %s\n" % inst.args[1])
1288 1288 help_(u)
1289 1289 sys.exit(-1)
1290 1290 except UnknownCommand, inst:
1291 1291 u = ui.ui()
1292 1292 u.warn("hg: unknown command '%s'\n" % inst.args[0])
1293 1293 help_(u)
1294 1294 sys.exit(1)
1295 1295
1296 1296 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
1297 1297 not options["noninteractive"])
1298 1298
1299 1299 try:
1300 1300 try:
1301 1301 if cmd not in norepo.split():
1302 1302 path = options["repository"] or ""
1303 1303 repo = hg.repository(ui=u, path=path)
1304 1304 d = lambda: func(u, repo, *args, **cmdoptions)
1305 1305 else:
1306 1306 d = lambda: func(u, *args, **cmdoptions)
1307 1307
1308 1308 if options['profile']:
1309 1309 import hotshot, hotshot.stats
1310 1310 prof = hotshot.Profile("hg.prof")
1311 1311 r = prof.runcall(d)
1312 1312 prof.close()
1313 1313 stats = hotshot.stats.load("hg.prof")
1314 1314 stats.strip_dirs()
1315 1315 stats.sort_stats('time', 'calls')
1316 1316 stats.print_stats(40)
1317 1317 return r
1318 1318 else:
1319 1319 return d()
1320 1320 except:
1321 1321 if options['traceback']:
1322 1322 traceback.print_exc()
1323 1323 raise
1324 1324 except util.CommandError, inst:
1325 1325 u.warn("abort: %s\n" % inst.args)
1326 1326 except hg.RepoError, inst:
1327 1327 u.warn("abort: ", inst, "!\n")
1328 1328 except SignalInterrupt:
1329 1329 u.warn("killed!\n")
1330 1330 except KeyboardInterrupt:
1331 1331 u.warn("interrupted!\n")
1332 1332 except IOError, inst:
1333 1333 if hasattr(inst, "code"):
1334 1334 u.warn("abort: %s\n" % inst)
1335 1335 elif hasattr(inst, "reason"):
1336 1336 u.warn("abort: error %d: %s\n" % (inst.reason[0], inst.reason[1]))
1337 1337 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
1338 1338 if u.debugflag: u.warn("broken pipe\n")
1339 1339 else:
1340 1340 raise
1341 1341 except OSError, inst:
1342 1342 if hasattr(inst, "filename"):
1343 1343 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
1344 1344 else:
1345 1345 u.warn("abort: %s\n" % inst.strerror)
1346 1346 except TypeError, inst:
1347 1347 # was this an argument error?
1348 1348 tb = traceback.extract_tb(sys.exc_info()[2])
1349 1349 if len(tb) > 2: # no
1350 1350 raise
1351 1351 u.debug(inst, "\n")
1352 1352 u.warn("%s: invalid arguments\n" % cmd)
1353 1353 help_(u, cmd)
1354 1354
1355 1355 sys.exit(-1)
@@ -1,1887 +1,1925 b''
1 1 # hg.py - repository classes 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 import sys, struct, os
9 9 import util
10 10 from revlog import *
11 11 from demandload import *
12 12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
13 13 demandload(globals(), "tempfile httprangereader bdiff")
14 14 demandload(globals(), "bisect select")
15 15
16 def always(fn):
17 return True
18
16 19 class filelog(revlog):
17 20 def __init__(self, opener, path):
18 21 revlog.__init__(self, opener,
19 22 os.path.join("data", path + ".i"),
20 23 os.path.join("data", path + ".d"))
21 24
22 25 def read(self, node):
23 26 t = self.revision(node)
24 27 if not t.startswith('\1\n'):
25 28 return t
26 29 s = t.find('\1\n', 2)
27 30 return t[s+2:]
28 31
29 32 def readmeta(self, node):
30 33 t = self.revision(node)
31 34 if not t.startswith('\1\n'):
32 35 return t
33 36 s = t.find('\1\n', 2)
34 37 mt = t[2:s]
35 38 for l in mt.splitlines():
36 39 k, v = l.split(": ", 1)
37 40 m[k] = v
38 41 return m
39 42
40 43 def add(self, text, meta, transaction, link, p1=None, p2=None):
41 44 if meta or text.startswith('\1\n'):
42 45 mt = ""
43 46 if meta:
44 47 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
45 48 text = "\1\n" + "".join(mt) + "\1\n" + text
46 49 return self.addrevision(text, transaction, link, p1, p2)
47 50
48 51 def annotate(self, node):
49 52
50 53 def decorate(text, rev):
51 54 return ([rev] * len(text.splitlines()), text)
52 55
53 56 def pair(parent, child):
54 57 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
55 58 child[0][b1:b2] = parent[0][a1:a2]
56 59 return child
57 60
58 61 # find all ancestors
59 62 needed = {node:1}
60 63 visit = [node]
61 64 while visit:
62 65 n = visit.pop(0)
63 66 for p in self.parents(n):
64 67 if p not in needed:
65 68 needed[p] = 1
66 69 visit.append(p)
67 70 else:
68 71 # count how many times we'll use this
69 72 needed[p] += 1
70 73
71 74 # sort by revision which is a topological order
72 75 visit = [ (self.rev(n), n) for n in needed.keys() ]
73 76 visit.sort()
74 77 hist = {}
75 78
76 79 for r,n in visit:
77 80 curr = decorate(self.read(n), self.linkrev(n))
78 81 for p in self.parents(n):
79 82 if p != nullid:
80 83 curr = pair(hist[p], curr)
81 84 # trim the history of unneeded revs
82 85 needed[p] -= 1
83 86 if not needed[p]:
84 87 del hist[p]
85 88 hist[n] = curr
86 89
87 90 return zip(hist[n][0], hist[n][1].splitlines(1))
88 91
89 92 class manifest(revlog):
90 93 def __init__(self, opener):
91 94 self.mapcache = None
92 95 self.listcache = None
93 96 self.addlist = None
94 97 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
95 98
96 99 def read(self, node):
97 100 if node == nullid: return {} # don't upset local cache
98 101 if self.mapcache and self.mapcache[0] == node:
99 102 return self.mapcache[1]
100 103 text = self.revision(node)
101 104 map = {}
102 105 flag = {}
103 106 self.listcache = (text, text.splitlines(1))
104 107 for l in self.listcache[1]:
105 108 (f, n) = l.split('\0')
106 109 map[f] = bin(n[:40])
107 110 flag[f] = (n[40:-1] == "x")
108 111 self.mapcache = (node, map, flag)
109 112 return map
110 113
111 114 def readflags(self, node):
112 115 if node == nullid: return {} # don't upset local cache
113 116 if not self.mapcache or self.mapcache[0] != node:
114 117 self.read(node)
115 118 return self.mapcache[2]
116 119
117 120 def diff(self, a, b):
118 121 # this is sneaky, as we're not actually using a and b
119 122 if self.listcache and self.addlist and self.listcache[0] == a:
120 123 d = mdiff.diff(self.listcache[1], self.addlist, 1)
121 124 if mdiff.patch(a, d) != b:
122 125 sys.stderr.write("*** sortdiff failed, falling back ***\n")
123 126 return mdiff.textdiff(a, b)
124 127 return d
125 128 else:
126 129 return mdiff.textdiff(a, b)
127 130
128 131 def add(self, map, flags, transaction, link, p1=None, p2=None,changed=None):
129 132 # directly generate the mdiff delta from the data collected during
130 133 # the bisect loop below
131 134 def gendelta(delta):
132 135 i = 0
133 136 result = []
134 137 while i < len(delta):
135 138 start = delta[i][2]
136 139 end = delta[i][3]
137 140 l = delta[i][4]
138 141 if l == None:
139 142 l = ""
140 143 while i < len(delta) - 1 and start <= delta[i+1][2] and end >= delta[i+1][2]:
141 144 if delta[i+1][3] > end:
142 145 end = delta[i+1][3]
143 146 if delta[i+1][4]:
144 147 l += delta[i+1][4]
145 148 i += 1
146 149 result.append(struct.pack(">lll", start, end, len(l)) + l)
147 150 i += 1
148 151 return result
149 152
150 153 # apply the changes collected during the bisect loop to our addlist
151 154 def addlistdelta(addlist, delta):
152 155 # apply the deltas to the addlist. start from the bottom up
153 156 # so changes to the offsets don't mess things up.
154 157 i = len(delta)
155 158 while i > 0:
156 159 i -= 1
157 160 start = delta[i][0]
158 161 end = delta[i][1]
159 162 if delta[i][4]:
160 163 addlist[start:end] = [delta[i][4]]
161 164 else:
162 165 del addlist[start:end]
163 166 return addlist
164 167
165 168 # calculate the byte offset of the start of each line in the
166 169 # manifest
167 170 def calcoffsets(addlist):
168 171 offsets = [0] * (len(addlist) + 1)
169 172 offset = 0
170 173 i = 0
171 174 while i < len(addlist):
172 175 offsets[i] = offset
173 176 offset += len(addlist[i])
174 177 i += 1
175 178 offsets[i] = offset
176 179 return offsets
177 180
178 181 # if we're using the listcache, make sure it is valid and
179 182 # parented by the same node we're diffing against
180 183 if not changed or not self.listcache or not p1 or self.mapcache[0] != p1:
181 184 files = map.keys()
182 185 files.sort()
183 186
184 187 self.addlist = ["%s\000%s%s\n" %
185 188 (f, hex(map[f]), flags[f] and "x" or '')
186 189 for f in files]
187 190 cachedelta = None
188 191 else:
189 192 addlist = self.listcache[1]
190 193
191 194 # find the starting offset for each line in the add list
192 195 offsets = calcoffsets(addlist)
193 196
194 197 # combine the changed lists into one list for sorting
195 198 work = [[x, 0] for x in changed[0]]
196 199 work[len(work):] = [[x, 1] for x in changed[1]]
197 200 work.sort()
198 201
199 202 delta = []
200 203 bs = 0
201 204
202 205 for w in work:
203 206 f = w[0]
204 207 # bs will either be the index of the item or the insertion point
205 208 bs = bisect.bisect(addlist, f, bs)
206 209 if bs < len(addlist):
207 210 fn = addlist[bs][:addlist[bs].index('\0')]
208 211 else:
209 212 fn = None
210 213 if w[1] == 0:
211 214 l = "%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '')
212 215 else:
213 216 l = None
214 217 start = bs
215 218 if fn != f:
216 219 # item not found, insert a new one
217 220 end = bs
218 221 if w[1] == 1:
219 222 sys.stderr.write("failed to remove %s from manifest" % f)
220 223 sys.exit(1)
221 224 else:
222 225 # item is found, replace/delete the existing line
223 226 end = bs + 1
224 227 delta.append([start, end, offsets[start], offsets[end], l])
225 228
226 229 self.addlist = addlistdelta(addlist, delta)
227 230 if self.mapcache[0] == self.tip():
228 231 cachedelta = "".join(gendelta(delta))
229 232 else:
230 233 cachedelta = None
231 234
232 235 text = "".join(self.addlist)
233 236 if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
234 237 sys.stderr.write("manifest delta failure")
235 238 sys.exit(1)
236 239 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
237 240 self.mapcache = (n, map, flags)
238 241 self.listcache = (text, self.addlist)
239 242 self.addlist = None
240 243
241 244 return n
242 245
243 246 class changelog(revlog):
244 247 def __init__(self, opener):
245 248 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
246 249
247 250 def extract(self, text):
248 251 if not text:
249 252 return (nullid, "", "0", [], "")
250 253 last = text.index("\n\n")
251 254 desc = text[last + 2:]
252 255 l = text[:last].splitlines()
253 256 manifest = bin(l[0])
254 257 user = l[1]
255 258 date = l[2]
256 259 files = l[3:]
257 260 return (manifest, user, date, files, desc)
258 261
259 262 def read(self, node):
260 263 return self.extract(self.revision(node))
261 264
262 265 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
263 266 user=None, date=None):
264 267 date = date or "%d %d" % (time.time(), time.timezone)
265 268 list.sort()
266 269 l = [hex(manifest), user, date] + list + ["", desc]
267 270 text = "\n".join(l)
268 271 return self.addrevision(text, transaction, self.count(), p1, p2)
269 272
270 273 class dirstate:
271 274 def __init__(self, opener, ui, root):
272 275 self.opener = opener
273 276 self.root = root
274 277 self.dirty = 0
275 278 self.ui = ui
276 279 self.map = None
277 280 self.pl = None
278 281 self.copies = {}
282 self.ignorefunc = None
283
284 def wjoin(self, f):
285 return os.path.join(self.root, f)
286
287 def ignore(self, f):
288 if not self.ignorefunc:
289 bigpat = []
290 try:
291 l = file(self.wjoin(".hgignore"))
292 for pat in l:
293 if pat != "\n":
294 p = util.pconvert(pat[:-1])
295 try:
296 r = re.compile(p)
297 except:
298 self.ui.warn("ignoring invalid ignore"
299 + " regular expression '%s'\n" % p)
300 else:
301 bigpat.append(util.pconvert(pat[:-1]))
302 except IOError: pass
303
304 s = "(?:%s)" % (")|(?:".join(bigpat))
305 r = re.compile(s)
306 self.ignorefunc = r.search
307
308 return self.ignorefunc(f)
279 309
280 310 def __del__(self):
281 311 if self.dirty:
282 312 self.write()
283 313
284 314 def __getitem__(self, key):
285 315 try:
286 316 return self.map[key]
287 317 except TypeError:
288 318 self.read()
289 319 return self[key]
290 320
291 321 def __contains__(self, key):
292 322 if not self.map: self.read()
293 323 return key in self.map
294 324
295 325 def parents(self):
296 326 if not self.pl:
297 327 self.read()
298 328 return self.pl
299 329
330 def markdirty(self):
331 if not self.dirty:
332 self.dirty = 1
333
300 334 def setparents(self, p1, p2 = nullid):
301 self.dirty = 1
335 self.markdirty()
302 336 self.pl = p1, p2
303 337
304 338 def state(self, key):
305 339 try:
306 340 return self[key][0]
307 341 except KeyError:
308 342 return "?"
309 343
310 344 def read(self):
311 345 if self.map is not None: return self.map
312 346
313 347 self.map = {}
314 348 self.pl = [nullid, nullid]
315 349 try:
316 350 st = self.opener("dirstate").read()
317 351 if not st: return
318 352 except: return
319 353
320 354 self.pl = [st[:20], st[20: 40]]
321 355
322 356 pos = 40
323 357 while pos < len(st):
324 358 e = struct.unpack(">cllll", st[pos:pos+17])
325 359 l = e[4]
326 360 pos += 17
327 361 f = st[pos:pos + l]
328 362 if '\0' in f:
329 363 f, c = f.split('\0')
330 364 self.copies[f] = c
331 365 self.map[f] = e[:4]
332 366 pos += l
333 367
334 368 def copy(self, source, dest):
335 369 self.read()
336 self.dirty = 1
370 self.markdirty()
337 371 self.copies[dest] = source
338 372
339 373 def copied(self, file):
340 374 return self.copies.get(file, None)
341 375
342 376 def update(self, files, state):
343 377 ''' current states:
344 378 n normal
345 379 m needs merging
346 380 r marked for removal
347 381 a marked for addition'''
348 382
349 383 if not files: return
350 384 self.read()
351 self.dirty = 1
385 self.markdirty()
352 386 for f in files:
353 387 if state == "r":
354 388 self.map[f] = ('r', 0, 0, 0)
355 389 else:
356 390 s = os.stat(os.path.join(self.root, f))
357 391 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
358 392
359 393 def forget(self, files):
360 394 if not files: return
361 395 self.read()
362 self.dirty = 1
396 self.markdirty()
363 397 for f in files:
364 398 try:
365 399 del self.map[f]
366 400 except KeyError:
367 401 self.ui.warn("not in dirstate: %s!\n" % f)
368 402 pass
369 403
370 404 def clear(self):
371 405 self.map = {}
372 self.dirty = 1
406 self.markdirty()
373 407
374 408 def write(self):
375 409 st = self.opener("dirstate", "w")
376 410 st.write("".join(self.pl))
377 411 for f, e in self.map.items():
378 412 c = self.copied(f)
379 413 if c:
380 414 f = f + "\0" + c
381 415 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
382 416 st.write(e + f)
383 417 self.dirty = 0
384 418
385 def changes(self, files, ignore):
419 def walk(self, files = None, match = always):
386 420 self.read()
387 421 dc = self.map.copy()
388 lookup, changed, added, unknown = [], [], [], []
389
390 # compare all files by default
422 # walk all files by default
391 423 if not files: files = [self.root]
392
393 # recursive generator of all files listed
394 def walk(files):
424 def traverse():
395 425 for f in util.unique(files):
396 426 f = os.path.join(self.root, f)
397 427 if os.path.isdir(f):
398 428 for dir, subdirs, fl in os.walk(f):
399 429 d = dir[len(self.root) + 1:]
430 if d == '.hg':
431 subdirs[:] = []
432 continue
400 433 for sd in subdirs:
401 if ignore(os.path.join(d, sd +'/')):
434 ds = os.path.join(d, sd +'/')
435 if self.ignore(ds) or not match(ds):
402 436 subdirs.remove(sd)
403 437 for fn in fl:
404 438 fn = util.pconvert(os.path.join(d, fn))
405 439 yield fn
406 440 else:
407 441 yield f[len(self.root) + 1:]
408 442
409 443 for k in dc.keys():
410 444 yield k
411 445
412 for fn in util.unique(walk(files)):
446 # yield only files that match: all in dirstate, others only if
447 # not in .hgignore
448
449 for fn in util.unique(traverse()):
450 if fn in dc:
451 del dc[fn]
452 elif self.ignore(fn):
453 continue
454 if match(fn):
455 yield fn
456
457 def changes(self, files = None, match = always):
458 self.read()
459 dc = self.map.copy()
460 lookup, changed, added, unknown = [], [], [], []
461
462 for fn in self.walk(files, match):
413 463 try: s = os.stat(os.path.join(self.root, fn))
414 464 except: continue
415 465
416 466 if fn in dc:
417 467 c = dc[fn]
418 468 del dc[fn]
419 469
420 470 if c[0] == 'm':
421 471 changed.append(fn)
422 472 elif c[0] == 'a':
423 473 added.append(fn)
424 474 elif c[0] == 'r':
425 475 unknown.append(fn)
426 476 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
427 477 changed.append(fn)
428 478 elif c[1] != s.st_mode or c[3] != s.st_mtime:
429 479 lookup.append(fn)
430 480 else:
431 if not ignore(fn): unknown.append(fn)
481 if match(fn): unknown.append(fn)
432 482
433 483 return (lookup, changed, added, dc.keys(), unknown)
434 484
435 485 # used to avoid circular references so destructors work
436 486 def opener(base):
437 487 p = base
438 488 def o(path, mode="r"):
439 489 if p.startswith("http://"):
440 490 f = os.path.join(p, urllib.quote(path))
441 491 return httprangereader.httprangereader(f)
442 492
443 493 f = os.path.join(p, path)
444 494
445 495 mode += "b" # for that other OS
446 496
447 497 if mode[0] != "r":
448 498 try:
449 499 s = os.stat(f)
450 500 except OSError:
451 501 d = os.path.dirname(f)
452 502 if not os.path.isdir(d):
453 503 os.makedirs(d)
454 504 else:
455 505 if s.st_nlink > 1:
456 506 file(f + ".tmp", "wb").write(file(f, "rb").read())
457 507 util.rename(f+".tmp", f)
458 508
459 509 return file(f, mode)
460 510
461 511 return o
462 512
463 513 class RepoError(Exception): pass
464 514
465 515 class localrepository:
466 516 def __init__(self, ui, path=None, create=0):
467 517 self.remote = 0
468 518 if path and path.startswith("http://"):
469 519 self.remote = 1
470 520 self.path = path
471 521 else:
472 522 if not path:
473 523 p = os.getcwd()
474 524 while not os.path.isdir(os.path.join(p, ".hg")):
475 525 oldp = p
476 526 p = os.path.dirname(p)
477 527 if p == oldp: raise RepoError("no repo found")
478 528 path = p
479 529 self.path = os.path.join(path, ".hg")
480 530
481 531 if not create and not os.path.isdir(self.path):
482 532 raise RepoError("repository %s not found" % self.path)
483 533
484 534 self.root = path
485 535 self.ui = ui
486 536
487 537 if create:
488 538 os.mkdir(self.path)
489 539 os.mkdir(self.join("data"))
490 540
491 541 self.opener = opener(self.path)
492 542 self.wopener = opener(self.root)
493 543 self.manifest = manifest(self.opener)
494 544 self.changelog = changelog(self.opener)
495 self.ignorefunc = None
496 545 self.tagscache = None
497 546 self.nodetagscache = None
498 547
499 548 if not self.remote:
500 549 self.dirstate = dirstate(self.opener, ui, self.root)
501 550 try:
502 551 self.ui.readconfig(self.opener("hgrc"))
503 552 except IOError: pass
504 553
505 def ignore(self, f):
506 if not self.ignorefunc:
507 bigpat = ["^.hg/$"]
508 try:
509 l = file(self.wjoin(".hgignore"))
510 for pat in l:
511 if pat != "\n":
512 p = util.pconvert(pat[:-1])
513 try:
514 r = re.compile(p)
515 except:
516 self.ui.warn("ignoring invalid ignore"
517 + " regular expression '%s'\n" % p)
518 else:
519 bigpat.append(util.pconvert(pat[:-1]))
520 except IOError: pass
521
522 s = "(?:%s)" % (")|(?:".join(bigpat))
523 r = re.compile(s)
524 self.ignorefunc = r.search
525
526 return self.ignorefunc(f)
527
528 554 def hook(self, name, **args):
529 555 s = self.ui.config("hooks", name)
530 556 if s:
531 557 self.ui.note("running hook %s: %s\n" % (name, s))
532 558 old = {}
533 559 for k, v in args.items():
534 560 k = k.upper()
535 561 old[k] = os.environ.get(k, None)
536 562 os.environ[k] = v
537 563
538 564 r = os.system(s)
539 565
540 566 for k, v in old.items():
541 567 if v != None:
542 568 os.environ[k] = v
543 569 else:
544 570 del os.environ[k]
545 571
546 572 if r:
547 573 self.ui.warn("abort: %s hook failed with status %d!\n" %
548 574 (name, r))
549 575 return False
550 576 return True
551 577
552 578 def tags(self):
553 579 '''return a mapping of tag to node'''
554 580 if not self.tagscache:
555 581 self.tagscache = {}
556 582 def addtag(self, k, n):
557 583 try:
558 584 bin_n = bin(n)
559 585 except TypeError:
560 586 bin_n = ''
561 587 self.tagscache[k.strip()] = bin_n
562 588
563 589 try:
564 590 # read each head of the tags file, ending with the tip
565 591 # and add each tag found to the map, with "newer" ones
566 592 # taking precedence
567 593 fl = self.file(".hgtags")
568 594 h = fl.heads()
569 595 h.reverse()
570 596 for r in h:
571 597 for l in fl.revision(r).splitlines():
572 598 if l:
573 599 n, k = l.split(" ", 1)
574 600 addtag(self, k, n)
575 601 except KeyError:
576 602 pass
577 603
578 604 try:
579 605 f = self.opener("localtags")
580 606 for l in f:
581 607 n, k = l.split(" ", 1)
582 608 addtag(self, k, n)
583 609 except IOError:
584 610 pass
585 611
586 612 self.tagscache['tip'] = self.changelog.tip()
587 613
588 614 return self.tagscache
589 615
590 616 def tagslist(self):
591 617 '''return a list of tags ordered by revision'''
592 618 l = []
593 619 for t, n in self.tags().items():
594 620 try:
595 621 r = self.changelog.rev(n)
596 622 except:
597 623 r = -2 # sort to the beginning of the list if unknown
598 624 l.append((r,t,n))
599 625 l.sort()
600 626 return [(t,n) for r,t,n in l]
601 627
602 628 def nodetags(self, node):
603 629 '''return the tags associated with a node'''
604 630 if not self.nodetagscache:
605 631 self.nodetagscache = {}
606 632 for t,n in self.tags().items():
607 633 self.nodetagscache.setdefault(n,[]).append(t)
608 634 return self.nodetagscache.get(node, [])
609 635
610 636 def lookup(self, key):
611 637 try:
612 638 return self.tags()[key]
613 639 except KeyError:
614 640 try:
615 641 return self.changelog.lookup(key)
616 642 except:
617 643 raise RepoError("unknown revision '%s'" % key)
618 644
619 645 def dev(self):
620 646 if self.remote: return -1
621 647 return os.stat(self.path).st_dev
622 648
623 649 def join(self, f):
624 650 return os.path.join(self.path, f)
625 651
626 652 def wjoin(self, f):
627 653 return os.path.join(self.root, f)
628 654
629 655 def file(self, f):
630 656 if f[0] == '/': f = f[1:]
631 657 return filelog(self.opener, f)
632 658
633 659 def getcwd(self):
634 660 cwd = os.getcwd()
635 661 if cwd == self.root: return ''
636 662 return cwd[len(self.root) + 1:]
637 663
638 664 def wfile(self, f, mode='r'):
639 665 return self.wopener(f, mode)
640 666
641 667 def transaction(self):
642 668 # save dirstate for undo
643 669 try:
644 670 ds = self.opener("dirstate").read()
645 671 except IOError:
646 672 ds = ""
647 673 self.opener("undo.dirstate", "w").write(ds)
648 674
649 675 return transaction.transaction(self.ui.warn,
650 676 self.opener, self.join("journal"),
651 677 self.join("undo"))
652 678
653 679 def recover(self):
654 680 lock = self.lock()
655 681 if os.path.exists(self.join("journal")):
656 682 self.ui.status("rolling back interrupted transaction\n")
657 683 return transaction.rollback(self.opener, self.join("journal"))
658 684 else:
659 685 self.ui.warn("no interrupted transaction available\n")
660 686
661 687 def undo(self):
662 688 lock = self.lock()
663 689 if os.path.exists(self.join("undo")):
664 690 self.ui.status("rolling back last transaction\n")
665 691 transaction.rollback(self.opener, self.join("undo"))
666 692 self.dirstate = None
667 693 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
668 694 self.dirstate = dirstate(self.opener, self.ui, self.root)
669 695 else:
670 696 self.ui.warn("no undo information available\n")
671 697
672 698 def lock(self, wait = 1):
673 699 try:
674 700 return lock.lock(self.join("lock"), 0)
675 701 except lock.LockHeld, inst:
676 702 if wait:
677 703 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
678 704 return lock.lock(self.join("lock"), wait)
679 705 raise inst
680 706
681 707 def rawcommit(self, files, text, user, date, p1=None, p2=None):
682 708 orig_parent = self.dirstate.parents()[0] or nullid
683 709 p1 = p1 or self.dirstate.parents()[0] or nullid
684 710 p2 = p2 or self.dirstate.parents()[1] or nullid
685 711 c1 = self.changelog.read(p1)
686 712 c2 = self.changelog.read(p2)
687 713 m1 = self.manifest.read(c1[0])
688 714 mf1 = self.manifest.readflags(c1[0])
689 715 m2 = self.manifest.read(c2[0])
690 716
691 717 if orig_parent == p1:
692 718 update_dirstate = 1
693 719 else:
694 720 update_dirstate = 0
695 721
696 722 tr = self.transaction()
697 723 mm = m1.copy()
698 724 mfm = mf1.copy()
699 725 linkrev = self.changelog.count()
700 726 for f in files:
701 727 try:
702 728 t = self.wfile(f).read()
703 729 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
704 730 r = self.file(f)
705 731 mfm[f] = tm
706 732 mm[f] = r.add(t, {}, tr, linkrev,
707 733 m1.get(f, nullid), m2.get(f, nullid))
708 734 if update_dirstate:
709 735 self.dirstate.update([f], "n")
710 736 except IOError:
711 737 try:
712 738 del mm[f]
713 739 del mfm[f]
714 740 if update_dirstate:
715 741 self.dirstate.forget([f])
716 742 except:
717 743 # deleted from p2?
718 744 pass
719 745
720 746 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
721 747 user = user or self.ui.username()
722 748 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
723 749 tr.close()
724 750 if update_dirstate:
725 751 self.dirstate.setparents(n, nullid)
726 752
727 753 def commit(self, files = None, text = "", user = None, date = None):
728 754 commit = []
729 755 remove = []
730 756 if files:
731 757 for f in files:
732 758 s = self.dirstate.state(f)
733 759 if s in 'nmai':
734 760 commit.append(f)
735 761 elif s == 'r':
736 762 remove.append(f)
737 763 else:
738 764 self.ui.warn("%s not tracked!\n" % f)
739 765 else:
740 (c, a, d, u) = self.changes(None, None)
766 (c, a, d, u) = self.changes()
741 767 commit = c + a
742 768 remove = d
743 769
744 770 if not commit and not remove:
745 771 self.ui.status("nothing changed\n")
746 772 return
747 773
748 774 if not self.hook("precommit"):
749 775 return 1
750 776
751 777 p1, p2 = self.dirstate.parents()
752 778 c1 = self.changelog.read(p1)
753 779 c2 = self.changelog.read(p2)
754 780 m1 = self.manifest.read(c1[0])
755 781 mf1 = self.manifest.readflags(c1[0])
756 782 m2 = self.manifest.read(c2[0])
757 783 lock = self.lock()
758 784 tr = self.transaction()
759 785
760 786 # check in files
761 787 new = {}
762 788 linkrev = self.changelog.count()
763 789 commit.sort()
764 790 for f in commit:
765 791 self.ui.note(f + "\n")
766 792 try:
767 793 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
768 794 t = self.wfile(f).read()
769 795 except IOError:
770 796 self.ui.warn("trouble committing %s!\n" % f)
771 797 raise
772 798
773 799 meta = {}
774 800 cp = self.dirstate.copied(f)
775 801 if cp:
776 802 meta["copy"] = cp
777 803 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
778 804 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
779 805
780 806 r = self.file(f)
781 807 fp1 = m1.get(f, nullid)
782 808 fp2 = m2.get(f, nullid)
783 809 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
784 810
785 811 # update manifest
786 812 m1.update(new)
787 813 for f in remove:
788 814 if f in m1:
789 815 del m1[f]
790 816 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0], (new,remove))
791 817
792 818 # add changeset
793 819 new = new.keys()
794 820 new.sort()
795 821
796 822 if not text:
797 823 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
798 824 edittext += "".join(["HG: changed %s\n" % f for f in new])
799 825 edittext += "".join(["HG: removed %s\n" % f for f in remove])
800 826 edittext = self.ui.edit(edittext)
801 827 if not edittext.rstrip():
802 828 return 1
803 829 text = edittext
804 830
805 831 user = user or self.ui.username()
806 832 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
807 833
808 834 tr.close()
809 835
810 836 self.dirstate.setparents(n)
811 837 self.dirstate.update(new, "n")
812 838 self.dirstate.forget(remove)
813 839
814 840 if not self.hook("commit", node=hex(n)):
815 841 return 1
816 842
817 def changes(self, node1, node2, files=None):
843 def walk(self, rev = None, files = [], match = always):
844 if rev is None: fns = self.dirstate.walk(files, match)
845 else: fns = filter(match, self.manifest.read(rev))
846 for fn in fns: yield fn
847
848 def changes(self, node1 = None, node2 = None, files = [], match = always):
818 849 mf2, u = None, []
819 850
820 851 def fcmp(fn, mf):
821 852 t1 = self.wfile(fn).read()
822 853 t2 = self.file(fn).revision(mf[fn])
823 854 return cmp(t1, t2)
824 855
856 def mfmatches(node):
857 mf = dict(self.manifest.read(node))
858 for fn in mf.keys():
859 if not match(fn):
860 del mf[fn]
861 return mf
862
825 863 # are we comparing the working directory?
826 864 if not node2:
827 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
865 l, c, a, d, u = self.dirstate.changes(files, match)
828 866
829 867 # are we comparing working dir against its parent?
830 868 if not node1:
831 869 if l:
832 870 # do a full compare of any files that might have changed
833 871 change = self.changelog.read(self.dirstate.parents()[0])
834 mf2 = self.manifest.read(change[0])
872 mf2 = mfmatches(change[0])
835 873 for f in l:
836 874 if fcmp(f, mf2):
837 875 c.append(f)
838 876
839 877 for l in c, a, d, u:
840 878 l.sort()
841 879
842 880 return (c, a, d, u)
843 881
844 882 # are we comparing working dir against non-tip?
845 883 # generate a pseudo-manifest for the working dir
846 884 if not node2:
847 885 if not mf2:
848 886 change = self.changelog.read(self.dirstate.parents()[0])
849 mf2 = self.manifest.read(change[0]).copy()
887 mf2 = mfmatches(change[0])
850 888 for f in a + c + l:
851 889 mf2[f] = ""
852 890 for f in d:
853 891 if f in mf2: del mf2[f]
854 892 else:
855 893 change = self.changelog.read(node2)
856 mf2 = self.manifest.read(change[0])
894 mf2 = mfmatches(change[0])
857 895
858 896 # flush lists from dirstate before comparing manifests
859 897 c, a = [], []
860 898
861 899 change = self.changelog.read(node1)
862 mf1 = self.manifest.read(change[0]).copy()
900 mf1 = mfmatches(change[0])
863 901
864 902 for fn in mf2:
865 903 if mf1.has_key(fn):
866 904 if mf1[fn] != mf2[fn]:
867 905 if mf2[fn] != "" or fcmp(fn, mf1):
868 906 c.append(fn)
869 907 del mf1[fn]
870 908 else:
871 909 a.append(fn)
872 910
873 911 d = mf1.keys()
874 912
875 913 for l in c, a, d, u:
876 914 l.sort()
877 915
878 916 return (c, a, d, u)
879 917
880 918 def add(self, list):
881 919 for f in list:
882 920 p = self.wjoin(f)
883 921 if not os.path.exists(p):
884 922 self.ui.warn("%s does not exist!\n" % f)
885 923 elif not os.path.isfile(p):
886 924 self.ui.warn("%s not added: mercurial only supports files currently\n" % f)
887 925 elif self.dirstate.state(f) == 'n':
888 926 self.ui.warn("%s already tracked!\n" % f)
889 927 else:
890 928 self.dirstate.update([f], "a")
891 929
892 930 def forget(self, list):
893 931 for f in list:
894 932 if self.dirstate.state(f) not in 'ai':
895 933 self.ui.warn("%s not added!\n" % f)
896 934 else:
897 935 self.dirstate.forget([f])
898 936
899 937 def remove(self, list):
900 938 for f in list:
901 939 p = self.wjoin(f)
902 940 if os.path.exists(p):
903 941 self.ui.warn("%s still exists!\n" % f)
904 942 elif self.dirstate.state(f) == 'a':
905 943 self.ui.warn("%s never committed!\n" % f)
906 944 self.dirstate.forget([f])
907 945 elif f not in self.dirstate:
908 946 self.ui.warn("%s not tracked!\n" % f)
909 947 else:
910 948 self.dirstate.update([f], "r")
911 949
912 950 def copy(self, source, dest):
913 951 p = self.wjoin(dest)
914 952 if not os.path.exists(dest):
915 953 self.ui.warn("%s does not exist!\n" % dest)
916 954 elif not os.path.isfile(dest):
917 955 self.ui.warn("copy failed: %s is not a file\n" % dest)
918 956 else:
919 957 if self.dirstate.state(dest) == '?':
920 958 self.dirstate.update([dest], "a")
921 959 self.dirstate.copy(source, dest)
922 960
923 961 def heads(self):
924 962 return self.changelog.heads()
925 963
926 964 def branches(self, nodes):
927 965 if not nodes: nodes = [self.changelog.tip()]
928 966 b = []
929 967 for n in nodes:
930 968 t = n
931 969 while n:
932 970 p = self.changelog.parents(n)
933 971 if p[1] != nullid or p[0] == nullid:
934 972 b.append((t, n, p[0], p[1]))
935 973 break
936 974 n = p[0]
937 975 return b
938 976
939 977 def between(self, pairs):
940 978 r = []
941 979
942 980 for top, bottom in pairs:
943 981 n, l, i = top, [], 0
944 982 f = 1
945 983
946 984 while n != bottom:
947 985 p = self.changelog.parents(n)[0]
948 986 if i == f:
949 987 l.append(n)
950 988 f = f * 2
951 989 n = p
952 990 i += 1
953 991
954 992 r.append(l)
955 993
956 994 return r
957 995
958 996 def newer(self, nodes):
959 997 m = {}
960 998 nl = []
961 999 pm = {}
962 1000 cl = self.changelog
963 1001 t = l = cl.count()
964 1002
965 1003 # find the lowest numbered node
966 1004 for n in nodes:
967 1005 l = min(l, cl.rev(n))
968 1006 m[n] = 1
969 1007
970 1008 for i in xrange(l, t):
971 1009 n = cl.node(i)
972 1010 if n in m: # explicitly listed
973 1011 pm[n] = 1
974 1012 nl.append(n)
975 1013 continue
976 1014 for p in cl.parents(n):
977 1015 if p in pm: # parent listed
978 1016 pm[n] = 1
979 1017 nl.append(n)
980 1018 break
981 1019
982 1020 return nl
983 1021
984 1022 def findincoming(self, remote, base={}):
985 1023 m = self.changelog.nodemap
986 1024 search = []
987 1025 fetch = []
988 1026 seen = {}
989 1027 seenbranch = {}
990 1028
991 1029 # assume we're closer to the tip than the root
992 1030 # and start by examining the heads
993 1031 self.ui.status("searching for changes\n")
994 1032 heads = remote.heads()
995 1033 unknown = []
996 1034 for h in heads:
997 1035 if h not in m:
998 1036 unknown.append(h)
999 1037 else:
1000 1038 base[h] = 1
1001 1039
1002 1040 if not unknown:
1003 1041 return None
1004 1042
1005 1043 rep = {}
1006 1044 reqcnt = 0
1007 1045
1008 1046 # search through remote branches
1009 1047 # a 'branch' here is a linear segment of history, with four parts:
1010 1048 # head, root, first parent, second parent
1011 1049 # (a branch always has two parents (or none) by definition)
1012 1050 unknown = remote.branches(unknown)
1013 1051 while unknown:
1014 1052 r = []
1015 1053 while unknown:
1016 1054 n = unknown.pop(0)
1017 1055 if n[0] in seen:
1018 1056 continue
1019 1057
1020 1058 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
1021 1059 if n[0] == nullid:
1022 1060 break
1023 1061 if n in seenbranch:
1024 1062 self.ui.debug("branch already found\n")
1025 1063 continue
1026 1064 if n[1] and n[1] in m: # do we know the base?
1027 1065 self.ui.debug("found incomplete branch %s:%s\n"
1028 1066 % (short(n[0]), short(n[1])))
1029 1067 search.append(n) # schedule branch range for scanning
1030 1068 seenbranch[n] = 1
1031 1069 else:
1032 1070 if n[1] not in seen and n[1] not in fetch:
1033 1071 if n[2] in m and n[3] in m:
1034 1072 self.ui.debug("found new changeset %s\n" %
1035 1073 short(n[1]))
1036 1074 fetch.append(n[1]) # earliest unknown
1037 1075 base[n[2]] = 1 # latest known
1038 1076 continue
1039 1077
1040 1078 for a in n[2:4]:
1041 1079 if a not in rep:
1042 1080 r.append(a)
1043 1081 rep[a] = 1
1044 1082
1045 1083 seen[n[0]] = 1
1046 1084
1047 1085 if r:
1048 1086 reqcnt += 1
1049 1087 self.ui.debug("request %d: %s\n" %
1050 1088 (reqcnt, " ".join(map(short, r))))
1051 1089 for p in range(0, len(r), 10):
1052 1090 for b in remote.branches(r[p:p+10]):
1053 1091 self.ui.debug("received %s:%s\n" %
1054 1092 (short(b[0]), short(b[1])))
1055 1093 if b[0] not in m and b[0] not in seen:
1056 1094 unknown.append(b)
1057 1095
1058 1096 # do binary search on the branches we found
1059 1097 while search:
1060 1098 n = search.pop(0)
1061 1099 reqcnt += 1
1062 1100 l = remote.between([(n[0], n[1])])[0]
1063 1101 l.append(n[1])
1064 1102 p = n[0]
1065 1103 f = 1
1066 1104 for i in l:
1067 1105 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
1068 1106 if i in m:
1069 1107 if f <= 2:
1070 1108 self.ui.debug("found new branch changeset %s\n" %
1071 1109 short(p))
1072 1110 fetch.append(p)
1073 1111 base[i] = 1
1074 1112 else:
1075 1113 self.ui.debug("narrowed branch search to %s:%s\n"
1076 1114 % (short(p), short(i)))
1077 1115 search.append((p, i))
1078 1116 break
1079 1117 p, f = i, f * 2
1080 1118
1081 1119 # sanity check our fetch list
1082 1120 for f in fetch:
1083 1121 if f in m:
1084 1122 raise RepoError("already have changeset " + short(f[:4]))
1085 1123
1086 1124 if base.keys() == [nullid]:
1087 1125 self.ui.warn("warning: pulling from an unrelated repository!\n")
1088 1126
1089 1127 self.ui.note("adding new changesets starting at " +
1090 1128 " ".join([short(f) for f in fetch]) + "\n")
1091 1129
1092 1130 self.ui.debug("%d total queries\n" % reqcnt)
1093 1131
1094 1132 return fetch
1095 1133
1096 1134 def findoutgoing(self, remote):
1097 1135 base = {}
1098 1136 self.findincoming(remote, base)
1099 1137 remain = dict.fromkeys(self.changelog.nodemap)
1100 1138
1101 1139 # prune everything remote has from the tree
1102 1140 del remain[nullid]
1103 1141 remove = base.keys()
1104 1142 while remove:
1105 1143 n = remove.pop(0)
1106 1144 if n in remain:
1107 1145 del remain[n]
1108 1146 for p in self.changelog.parents(n):
1109 1147 remove.append(p)
1110 1148
1111 1149 # find every node whose parents have been pruned
1112 1150 subset = []
1113 1151 for n in remain:
1114 1152 p1, p2 = self.changelog.parents(n)
1115 1153 if p1 not in remain and p2 not in remain:
1116 1154 subset.append(n)
1117 1155
1118 1156 # this is the set of all roots we have to push
1119 1157 return subset
1120 1158
1121 1159 def pull(self, remote):
1122 1160 lock = self.lock()
1123 1161
1124 1162 # if we have an empty repo, fetch everything
1125 1163 if self.changelog.tip() == nullid:
1126 1164 self.ui.status("requesting all changes\n")
1127 1165 fetch = [nullid]
1128 1166 else:
1129 1167 fetch = self.findincoming(remote)
1130 1168
1131 1169 if not fetch:
1132 1170 self.ui.status("no changes found\n")
1133 1171 return 1
1134 1172
1135 1173 cg = remote.changegroup(fetch)
1136 1174 return self.addchangegroup(cg)
1137 1175
1138 1176 def push(self, remote):
1139 1177 lock = remote.lock()
1140 1178 update = self.findoutgoing(remote)
1141 1179 if not update:
1142 1180 self.ui.status("no changes found\n")
1143 1181 return 1
1144 1182
1145 1183 cg = self.changegroup(update)
1146 1184 return remote.addchangegroup(cg)
1147 1185
1148 1186 def changegroup(self, basenodes):
1149 1187 class genread:
1150 1188 def __init__(self, generator):
1151 1189 self.g = generator
1152 1190 self.buf = ""
1153 1191 def read(self, l):
1154 1192 while l > len(self.buf):
1155 1193 try:
1156 1194 self.buf += self.g.next()
1157 1195 except StopIteration:
1158 1196 break
1159 1197 d, self.buf = self.buf[:l], self.buf[l:]
1160 1198 return d
1161 1199
1162 1200 def gengroup():
1163 1201 nodes = self.newer(basenodes)
1164 1202
1165 1203 # construct the link map
1166 1204 linkmap = {}
1167 1205 for n in nodes:
1168 1206 linkmap[self.changelog.rev(n)] = n
1169 1207
1170 1208 # construct a list of all changed files
1171 1209 changed = {}
1172 1210 for n in nodes:
1173 1211 c = self.changelog.read(n)
1174 1212 for f in c[3]:
1175 1213 changed[f] = 1
1176 1214 changed = changed.keys()
1177 1215 changed.sort()
1178 1216
1179 1217 # the changegroup is changesets + manifests + all file revs
1180 1218 revs = [ self.changelog.rev(n) for n in nodes ]
1181 1219
1182 1220 for y in self.changelog.group(linkmap): yield y
1183 1221 for y in self.manifest.group(linkmap): yield y
1184 1222 for f in changed:
1185 1223 yield struct.pack(">l", len(f) + 4) + f
1186 1224 g = self.file(f).group(linkmap)
1187 1225 for y in g:
1188 1226 yield y
1189 1227
1190 1228 yield struct.pack(">l", 0)
1191 1229
1192 1230 return genread(gengroup())
1193 1231
1194 1232 def addchangegroup(self, source):
1195 1233
1196 1234 def getchunk():
1197 1235 d = source.read(4)
1198 1236 if not d: return ""
1199 1237 l = struct.unpack(">l", d)[0]
1200 1238 if l <= 4: return ""
1201 1239 return source.read(l - 4)
1202 1240
1203 1241 def getgroup():
1204 1242 while 1:
1205 1243 c = getchunk()
1206 1244 if not c: break
1207 1245 yield c
1208 1246
1209 1247 def csmap(x):
1210 1248 self.ui.debug("add changeset %s\n" % short(x))
1211 1249 return self.changelog.count()
1212 1250
1213 1251 def revmap(x):
1214 1252 return self.changelog.rev(x)
1215 1253
1216 1254 if not source: return
1217 1255 changesets = files = revisions = 0
1218 1256
1219 1257 tr = self.transaction()
1220 1258
1221 1259 # pull off the changeset group
1222 1260 self.ui.status("adding changesets\n")
1223 1261 co = self.changelog.tip()
1224 1262 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1225 1263 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1226 1264
1227 1265 # pull off the manifest group
1228 1266 self.ui.status("adding manifests\n")
1229 1267 mm = self.manifest.tip()
1230 1268 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1231 1269
1232 1270 # process the files
1233 1271 self.ui.status("adding file revisions\n")
1234 1272 while 1:
1235 1273 f = getchunk()
1236 1274 if not f: break
1237 1275 self.ui.debug("adding %s revisions\n" % f)
1238 1276 fl = self.file(f)
1239 1277 o = fl.count()
1240 1278 n = fl.addgroup(getgroup(), revmap, tr)
1241 1279 revisions += fl.count() - o
1242 1280 files += 1
1243 1281
1244 1282 self.ui.status(("modified %d files, added %d changesets" +
1245 1283 " and %d new revisions\n")
1246 1284 % (files, changesets, revisions))
1247 1285
1248 1286 tr.close()
1249 1287 return
1250 1288
1251 1289 def update(self, node, allow=False, force=False, choose=None,
1252 1290 moddirstate=True):
1253 1291 pl = self.dirstate.parents()
1254 1292 if not force and pl[1] != nullid:
1255 1293 self.ui.warn("aborting: outstanding uncommitted merges\n")
1256 1294 return 1
1257 1295
1258 1296 p1, p2 = pl[0], node
1259 1297 pa = self.changelog.ancestor(p1, p2)
1260 1298 m1n = self.changelog.read(p1)[0]
1261 1299 m2n = self.changelog.read(p2)[0]
1262 1300 man = self.manifest.ancestor(m1n, m2n)
1263 1301 m1 = self.manifest.read(m1n)
1264 1302 mf1 = self.manifest.readflags(m1n)
1265 1303 m2 = self.manifest.read(m2n)
1266 1304 mf2 = self.manifest.readflags(m2n)
1267 1305 ma = self.manifest.read(man)
1268 1306 mfa = self.manifest.readflags(man)
1269 1307
1270 (c, a, d, u) = self.changes(None, None)
1308 (c, a, d, u) = self.changes()
1271 1309
1272 1310 # is this a jump, or a merge? i.e. is there a linear path
1273 1311 # from p1 to p2?
1274 1312 linear_path = (pa == p1 or pa == p2)
1275 1313
1276 1314 # resolve the manifest to determine which files
1277 1315 # we care about merging
1278 1316 self.ui.note("resolving manifests\n")
1279 1317 self.ui.debug(" force %s allow %s moddirstate %s linear %s\n" %
1280 1318 (force, allow, moddirstate, linear_path))
1281 1319 self.ui.debug(" ancestor %s local %s remote %s\n" %
1282 1320 (short(man), short(m1n), short(m2n)))
1283 1321
1284 1322 merge = {}
1285 1323 get = {}
1286 1324 remove = []
1287 1325 mark = {}
1288 1326
1289 1327 # construct a working dir manifest
1290 1328 mw = m1.copy()
1291 1329 mfw = mf1.copy()
1292 1330 umap = dict.fromkeys(u)
1293 1331
1294 1332 for f in a + c + u:
1295 1333 mw[f] = ""
1296 1334 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1297 1335
1298 1336 for f in d:
1299 1337 if f in mw: del mw[f]
1300 1338
1301 1339 # If we're jumping between revisions (as opposed to merging),
1302 1340 # and if neither the working directory nor the target rev has
1303 1341 # the file, then we need to remove it from the dirstate, to
1304 1342 # prevent the dirstate from listing the file when it is no
1305 1343 # longer in the manifest.
1306 1344 if moddirstate and linear_path and f not in m2:
1307 1345 self.dirstate.forget((f,))
1308 1346
1309 1347 # Compare manifests
1310 1348 for f, n in mw.iteritems():
1311 1349 if choose and not choose(f): continue
1312 1350 if f in m2:
1313 1351 s = 0
1314 1352
1315 1353 # is the wfile new since m1, and match m2?
1316 1354 if f not in m1:
1317 1355 t1 = self.wfile(f).read()
1318 1356 t2 = self.file(f).revision(m2[f])
1319 1357 if cmp(t1, t2) == 0:
1320 1358 mark[f] = 1
1321 1359 n = m2[f]
1322 1360 del t1, t2
1323 1361
1324 1362 # are files different?
1325 1363 if n != m2[f]:
1326 1364 a = ma.get(f, nullid)
1327 1365 # are both different from the ancestor?
1328 1366 if n != a and m2[f] != a:
1329 1367 self.ui.debug(" %s versions differ, resolve\n" % f)
1330 1368 # merge executable bits
1331 1369 # "if we changed or they changed, change in merge"
1332 1370 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1333 1371 mode = ((a^b) | (a^c)) ^ a
1334 1372 merge[f] = (m1.get(f, nullid), m2[f], mode)
1335 1373 s = 1
1336 1374 # are we clobbering?
1337 1375 # is remote's version newer?
1338 1376 # or are we going back in time?
1339 1377 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1340 1378 self.ui.debug(" remote %s is newer, get\n" % f)
1341 1379 get[f] = m2[f]
1342 1380 s = 1
1343 1381 else:
1344 1382 mark[f] = 1
1345 1383 elif f in umap:
1346 1384 # this unknown file is the same as the checkout
1347 1385 get[f] = m2[f]
1348 1386
1349 1387 if not s and mfw[f] != mf2[f]:
1350 1388 if force:
1351 1389 self.ui.debug(" updating permissions for %s\n" % f)
1352 1390 util.set_exec(self.wjoin(f), mf2[f])
1353 1391 else:
1354 1392 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1355 1393 mode = ((a^b) | (a^c)) ^ a
1356 1394 if mode != b:
1357 1395 self.ui.debug(" updating permissions for %s\n" % f)
1358 1396 util.set_exec(self.wjoin(f), mode)
1359 1397 mark[f] = 1
1360 1398 del m2[f]
1361 1399 elif f in ma:
1362 1400 if n != ma[f]:
1363 1401 r = "d"
1364 1402 if not force and (linear_path or allow):
1365 1403 r = self.ui.prompt(
1366 1404 (" local changed %s which remote deleted\n" % f) +
1367 1405 "(k)eep or (d)elete?", "[kd]", "k")
1368 1406 if r == "d":
1369 1407 remove.append(f)
1370 1408 else:
1371 1409 self.ui.debug("other deleted %s\n" % f)
1372 1410 remove.append(f) # other deleted it
1373 1411 else:
1374 1412 if n == m1.get(f, nullid): # same as parent
1375 1413 if p2 == pa: # going backwards?
1376 1414 self.ui.debug("remote deleted %s\n" % f)
1377 1415 remove.append(f)
1378 1416 else:
1379 1417 self.ui.debug("local created %s, keeping\n" % f)
1380 1418 else:
1381 1419 self.ui.debug("working dir created %s, keeping\n" % f)
1382 1420
1383 1421 for f, n in m2.iteritems():
1384 1422 if choose and not choose(f): continue
1385 1423 if f[0] == "/": continue
1386 1424 if f in ma and n != ma[f]:
1387 1425 r = "k"
1388 1426 if not force and (linear_path or allow):
1389 1427 r = self.ui.prompt(
1390 1428 ("remote changed %s which local deleted\n" % f) +
1391 1429 "(k)eep or (d)elete?", "[kd]", "k")
1392 1430 if r == "k": get[f] = n
1393 1431 elif f not in ma:
1394 1432 self.ui.debug("remote created %s\n" % f)
1395 1433 get[f] = n
1396 1434 else:
1397 1435 if force or p2 == pa: # going backwards?
1398 1436 self.ui.debug("local deleted %s, recreating\n" % f)
1399 1437 get[f] = n
1400 1438 else:
1401 1439 self.ui.debug("local deleted %s\n" % f)
1402 1440
1403 1441 del mw, m1, m2, ma
1404 1442
1405 1443 if force:
1406 1444 for f in merge:
1407 1445 get[f] = merge[f][1]
1408 1446 merge = {}
1409 1447
1410 1448 if linear_path or force:
1411 1449 # we don't need to do any magic, just jump to the new rev
1412 1450 mode = 'n'
1413 1451 p1, p2 = p2, nullid
1414 1452 else:
1415 1453 if not allow:
1416 1454 self.ui.status("this update spans a branch" +
1417 1455 " affecting the following files:\n")
1418 1456 fl = merge.keys() + get.keys()
1419 1457 fl.sort()
1420 1458 for f in fl:
1421 1459 cf = ""
1422 1460 if f in merge: cf = " (resolve)"
1423 1461 self.ui.status(" %s%s\n" % (f, cf))
1424 1462 self.ui.warn("aborting update spanning branches!\n")
1425 1463 self.ui.status("(use update -m to perform a branch merge)\n")
1426 1464 return 1
1427 1465 # we have to remember what files we needed to get/change
1428 1466 # because any file that's different from either one of its
1429 1467 # parents must be in the changeset
1430 1468 mode = 'm'
1431 1469 if moddirstate:
1432 1470 self.dirstate.update(mark.keys(), "m")
1433 1471
1434 1472 if moddirstate:
1435 1473 self.dirstate.setparents(p1, p2)
1436 1474
1437 1475 # get the files we don't need to change
1438 1476 files = get.keys()
1439 1477 files.sort()
1440 1478 for f in files:
1441 1479 if f[0] == "/": continue
1442 1480 self.ui.note("getting %s\n" % f)
1443 1481 t = self.file(f).read(get[f])
1444 1482 try:
1445 1483 self.wfile(f, "w").write(t)
1446 1484 except IOError:
1447 1485 os.makedirs(os.path.dirname(self.wjoin(f)))
1448 1486 self.wfile(f, "w").write(t)
1449 1487 util.set_exec(self.wjoin(f), mf2[f])
1450 1488 if moddirstate:
1451 1489 self.dirstate.update([f], mode)
1452 1490
1453 1491 # merge the tricky bits
1454 1492 files = merge.keys()
1455 1493 files.sort()
1456 1494 for f in files:
1457 1495 self.ui.status("merging %s\n" % f)
1458 1496 m, o, flag = merge[f]
1459 1497 self.merge3(f, m, o)
1460 1498 util.set_exec(self.wjoin(f), flag)
1461 1499 if moddirstate:
1462 1500 self.dirstate.update([f], 'm')
1463 1501
1464 1502 remove.sort()
1465 1503 for f in remove:
1466 1504 self.ui.note("removing %s\n" % f)
1467 1505 try:
1468 1506 os.unlink(f)
1469 1507 except OSError, inst:
1470 1508 self.ui.warn("update failed to remove %s: %s!\n" % (f, inst))
1471 1509 # try removing directories that might now be empty
1472 1510 try: os.removedirs(os.path.dirname(f))
1473 1511 except: pass
1474 1512 if moddirstate:
1475 1513 if mode == 'n':
1476 1514 self.dirstate.forget(remove)
1477 1515 else:
1478 1516 self.dirstate.update(remove, 'r')
1479 1517
1480 1518 def merge3(self, fn, my, other):
1481 1519 """perform a 3-way merge in the working directory"""
1482 1520
1483 1521 def temp(prefix, node):
1484 1522 pre = "%s~%s." % (os.path.basename(fn), prefix)
1485 1523 (fd, name) = tempfile.mkstemp("", pre)
1486 1524 f = os.fdopen(fd, "wb")
1487 1525 f.write(fl.revision(node))
1488 1526 f.close()
1489 1527 return name
1490 1528
1491 1529 fl = self.file(fn)
1492 1530 base = fl.ancestor(my, other)
1493 1531 a = self.wjoin(fn)
1494 1532 b = temp("base", base)
1495 1533 c = temp("other", other)
1496 1534
1497 1535 self.ui.note("resolving %s\n" % fn)
1498 1536 self.ui.debug("file %s: other %s ancestor %s\n" %
1499 1537 (fn, short(other), short(base)))
1500 1538
1501 1539 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1502 1540 or "hgmerge")
1503 1541 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1504 1542 if r:
1505 1543 self.ui.warn("merging %s failed!\n" % fn)
1506 1544
1507 1545 os.unlink(b)
1508 1546 os.unlink(c)
1509 1547
1510 1548 def verify(self):
1511 1549 filelinkrevs = {}
1512 1550 filenodes = {}
1513 1551 changesets = revisions = files = 0
1514 1552 errors = 0
1515 1553
1516 1554 seen = {}
1517 1555 self.ui.status("checking changesets\n")
1518 1556 for i in range(self.changelog.count()):
1519 1557 changesets += 1
1520 1558 n = self.changelog.node(i)
1521 1559 if n in seen:
1522 1560 self.ui.warn("duplicate changeset at revision %d\n" % i)
1523 1561 errors += 1
1524 1562 seen[n] = 1
1525 1563
1526 1564 for p in self.changelog.parents(n):
1527 1565 if p not in self.changelog.nodemap:
1528 1566 self.ui.warn("changeset %s has unknown parent %s\n" %
1529 1567 (short(n), short(p)))
1530 1568 errors += 1
1531 1569 try:
1532 1570 changes = self.changelog.read(n)
1533 1571 except Exception, inst:
1534 1572 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1535 1573 errors += 1
1536 1574
1537 1575 for f in changes[3]:
1538 1576 filelinkrevs.setdefault(f, []).append(i)
1539 1577
1540 1578 seen = {}
1541 1579 self.ui.status("checking manifests\n")
1542 1580 for i in range(self.manifest.count()):
1543 1581 n = self.manifest.node(i)
1544 1582 if n in seen:
1545 1583 self.ui.warn("duplicate manifest at revision %d\n" % i)
1546 1584 errors += 1
1547 1585 seen[n] = 1
1548 1586
1549 1587 for p in self.manifest.parents(n):
1550 1588 if p not in self.manifest.nodemap:
1551 1589 self.ui.warn("manifest %s has unknown parent %s\n" %
1552 1590 (short(n), short(p)))
1553 1591 errors += 1
1554 1592
1555 1593 try:
1556 1594 delta = mdiff.patchtext(self.manifest.delta(n))
1557 1595 except KeyboardInterrupt:
1558 1596 self.ui.warn("aborted")
1559 1597 sys.exit(0)
1560 1598 except Exception, inst:
1561 1599 self.ui.warn("unpacking manifest %s: %s\n"
1562 1600 % (short(n), inst))
1563 1601 errors += 1
1564 1602
1565 1603 ff = [ l.split('\0') for l in delta.splitlines() ]
1566 1604 for f, fn in ff:
1567 1605 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1568 1606
1569 1607 self.ui.status("crosschecking files in changesets and manifests\n")
1570 1608 for f in filenodes:
1571 1609 if f not in filelinkrevs:
1572 1610 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1573 1611 errors += 1
1574 1612
1575 1613 for f in filelinkrevs:
1576 1614 if f not in filenodes:
1577 1615 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1578 1616 errors += 1
1579 1617
1580 1618 self.ui.status("checking files\n")
1581 1619 ff = filenodes.keys()
1582 1620 ff.sort()
1583 1621 for f in ff:
1584 1622 if f == "/dev/null": continue
1585 1623 files += 1
1586 1624 fl = self.file(f)
1587 1625 nodes = { nullid: 1 }
1588 1626 seen = {}
1589 1627 for i in range(fl.count()):
1590 1628 revisions += 1
1591 1629 n = fl.node(i)
1592 1630
1593 1631 if n in seen:
1594 1632 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1595 1633 errors += 1
1596 1634
1597 1635 if n not in filenodes[f]:
1598 1636 self.ui.warn("%s: %d:%s not in manifests\n"
1599 1637 % (f, i, short(n)))
1600 1638 errors += 1
1601 1639 else:
1602 1640 del filenodes[f][n]
1603 1641
1604 1642 flr = fl.linkrev(n)
1605 1643 if flr not in filelinkrevs[f]:
1606 1644 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1607 1645 % (f, short(n), fl.linkrev(n)))
1608 1646 errors += 1
1609 1647 else:
1610 1648 filelinkrevs[f].remove(flr)
1611 1649
1612 1650 # verify contents
1613 1651 try:
1614 1652 t = fl.read(n)
1615 1653 except Exception, inst:
1616 1654 self.ui.warn("unpacking file %s %s: %s\n"
1617 1655 % (f, short(n), inst))
1618 1656 errors += 1
1619 1657
1620 1658 # verify parents
1621 1659 (p1, p2) = fl.parents(n)
1622 1660 if p1 not in nodes:
1623 1661 self.ui.warn("file %s:%s unknown parent 1 %s" %
1624 1662 (f, short(n), short(p1)))
1625 1663 errors += 1
1626 1664 if p2 not in nodes:
1627 1665 self.ui.warn("file %s:%s unknown parent 2 %s" %
1628 1666 (f, short(n), short(p1)))
1629 1667 errors += 1
1630 1668 nodes[n] = 1
1631 1669
1632 1670 # cross-check
1633 1671 for node in filenodes[f]:
1634 1672 self.ui.warn("node %s in manifests not in %s\n"
1635 1673 % (hex(n), f))
1636 1674 errors += 1
1637 1675
1638 1676 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1639 1677 (files, changesets, revisions))
1640 1678
1641 1679 if errors:
1642 1680 self.ui.warn("%d integrity errors encountered!\n" % errors)
1643 1681 return 1
1644 1682
1645 1683 class httprepository:
1646 1684 def __init__(self, ui, path):
1647 1685 self.url = path
1648 1686 self.ui = ui
1649 1687 no_list = [ "localhost", "127.0.0.1" ]
1650 1688 host = ui.config("http_proxy", "host")
1651 1689 if host is None:
1652 1690 host = os.environ.get("http_proxy")
1653 1691 if host and host.startswith('http://'):
1654 1692 host = host[7:]
1655 1693 user = ui.config("http_proxy", "user")
1656 1694 passwd = ui.config("http_proxy", "passwd")
1657 1695 no = ui.config("http_proxy", "no")
1658 1696 if no is None:
1659 1697 no = os.environ.get("no_proxy")
1660 1698 if no:
1661 1699 no_list = no_list + no.split(",")
1662 1700
1663 1701 no_proxy = 0
1664 1702 for h in no_list:
1665 1703 if (path.startswith("http://" + h + "/") or
1666 1704 path.startswith("http://" + h + ":") or
1667 1705 path == "http://" + h):
1668 1706 no_proxy = 1
1669 1707
1670 1708 # Note: urllib2 takes proxy values from the environment and those will
1671 1709 # take precedence
1672 1710 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1673 1711 if os.environ.has_key(env):
1674 1712 del os.environ[env]
1675 1713
1676 1714 proxy_handler = urllib2.BaseHandler()
1677 1715 if host and not no_proxy:
1678 1716 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1679 1717
1680 1718 authinfo = None
1681 1719 if user and passwd:
1682 1720 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1683 1721 passmgr.add_password(None, host, user, passwd)
1684 1722 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1685 1723
1686 1724 opener = urllib2.build_opener(proxy_handler, authinfo)
1687 1725 urllib2.install_opener(opener)
1688 1726
1689 1727 def dev(self):
1690 1728 return -1
1691 1729
1692 1730 def do_cmd(self, cmd, **args):
1693 1731 self.ui.debug("sending %s command\n" % cmd)
1694 1732 q = {"cmd": cmd}
1695 1733 q.update(args)
1696 1734 qs = urllib.urlencode(q)
1697 1735 cu = "%s?%s" % (self.url, qs)
1698 1736 return urllib2.urlopen(cu)
1699 1737
1700 1738 def heads(self):
1701 1739 d = self.do_cmd("heads").read()
1702 1740 try:
1703 1741 return map(bin, d[:-1].split(" "))
1704 1742 except:
1705 1743 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1706 1744 raise
1707 1745
1708 1746 def branches(self, nodes):
1709 1747 n = " ".join(map(hex, nodes))
1710 1748 d = self.do_cmd("branches", nodes=n).read()
1711 1749 try:
1712 1750 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1713 1751 return br
1714 1752 except:
1715 1753 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1716 1754 raise
1717 1755
1718 1756 def between(self, pairs):
1719 1757 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1720 1758 d = self.do_cmd("between", pairs=n).read()
1721 1759 try:
1722 1760 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1723 1761 return p
1724 1762 except:
1725 1763 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1726 1764 raise
1727 1765
1728 1766 def changegroup(self, nodes):
1729 1767 n = " ".join(map(hex, nodes))
1730 1768 f = self.do_cmd("changegroup", roots=n)
1731 1769 bytes = 0
1732 1770
1733 1771 class zread:
1734 1772 def __init__(self, f):
1735 1773 self.zd = zlib.decompressobj()
1736 1774 self.f = f
1737 1775 self.buf = ""
1738 1776 def read(self, l):
1739 1777 while l > len(self.buf):
1740 1778 r = f.read(4096)
1741 1779 if r:
1742 1780 self.buf += self.zd.decompress(r)
1743 1781 else:
1744 1782 self.buf += self.zd.flush()
1745 1783 break
1746 1784 d, self.buf = self.buf[:l], self.buf[l:]
1747 1785 return d
1748 1786
1749 1787 return zread(f)
1750 1788
1751 1789 class remotelock:
1752 1790 def __init__(self, repo):
1753 1791 self.repo = repo
1754 1792 def release(self):
1755 1793 self.repo.unlock()
1756 1794 self.repo = None
1757 1795 def __del__(self):
1758 1796 if self.repo:
1759 1797 self.release()
1760 1798
1761 1799 class sshrepository:
1762 1800 def __init__(self, ui, path):
1763 1801 self.url = path
1764 1802 self.ui = ui
1765 1803
1766 1804 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
1767 1805 if not m:
1768 1806 raise RepoError("couldn't parse destination %s\n" % path)
1769 1807
1770 1808 self.user = m.group(2)
1771 1809 self.host = m.group(3)
1772 1810 self.port = m.group(5)
1773 1811 self.path = m.group(7)
1774 1812
1775 1813 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1776 1814 args = self.port and ("%s -p %s") % (args, self.port) or args
1777 1815 path = self.path or ""
1778 1816
1779 1817 cmd = "ssh %s 'hg -R %s serve --stdio'"
1780 1818 cmd = cmd % (args, path)
1781 1819
1782 1820 self.pipeo, self.pipei, self.pipee = os.popen3(cmd)
1783 1821
1784 1822 def readerr(self):
1785 1823 while 1:
1786 1824 r,w,x = select.select([self.pipee], [], [], 0)
1787 1825 if not r: break
1788 1826 l = self.pipee.readline()
1789 1827 if not l: break
1790 1828 self.ui.status("remote: ", l)
1791 1829
1792 1830 def __del__(self):
1793 1831 self.pipeo.close()
1794 1832 self.pipei.close()
1795 1833 for l in self.pipee:
1796 1834 self.ui.status("remote: ", l)
1797 1835 self.pipee.close()
1798 1836
1799 1837 def dev(self):
1800 1838 return -1
1801 1839
1802 1840 def do_cmd(self, cmd, **args):
1803 1841 self.ui.debug("sending %s command\n" % cmd)
1804 1842 self.pipeo.write("%s\n" % cmd)
1805 1843 for k, v in args.items():
1806 1844 self.pipeo.write("%s %d\n" % (k, len(v)))
1807 1845 self.pipeo.write(v)
1808 1846 self.pipeo.flush()
1809 1847
1810 1848 return self.pipei
1811 1849
1812 1850 def call(self, cmd, **args):
1813 1851 r = self.do_cmd(cmd, **args)
1814 1852 l = r.readline()
1815 1853 self.readerr()
1816 1854 try:
1817 1855 l = int(l)
1818 1856 except:
1819 1857 raise RepoError("unexpected response '%s'" % l)
1820 1858 return r.read(l)
1821 1859
1822 1860 def lock(self):
1823 1861 self.call("lock")
1824 1862 return remotelock(self)
1825 1863
1826 1864 def unlock(self):
1827 1865 self.call("unlock")
1828 1866
1829 1867 def heads(self):
1830 1868 d = self.call("heads")
1831 1869 try:
1832 1870 return map(bin, d[:-1].split(" "))
1833 1871 except:
1834 1872 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1835 1873
1836 1874 def branches(self, nodes):
1837 1875 n = " ".join(map(hex, nodes))
1838 1876 d = self.call("branches", nodes=n)
1839 1877 try:
1840 1878 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1841 1879 return br
1842 1880 except:
1843 1881 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1844 1882
1845 1883 def between(self, pairs):
1846 1884 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1847 1885 d = self.call("between", pairs=n)
1848 1886 try:
1849 1887 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1850 1888 return p
1851 1889 except:
1852 1890 raise RepoError("unexpected response '%s'" % (d[:400] + "..."))
1853 1891
1854 1892 def changegroup(self, nodes):
1855 1893 n = " ".join(map(hex, nodes))
1856 1894 f = self.do_cmd("changegroup", roots=n)
1857 1895 return self.pipei
1858 1896
1859 1897 def addchangegroup(self, cg):
1860 1898 d = self.call("addchangegroup")
1861 1899 if d:
1862 1900 raise RepoError("push refused: %s", d)
1863 1901
1864 1902 while 1:
1865 1903 d = cg.read(4096)
1866 1904 if not d: break
1867 1905 self.pipeo.write(d)
1868 1906 self.readerr()
1869 1907
1870 1908 self.pipeo.flush()
1871 1909
1872 1910 self.readerr()
1873 1911 l = int(self.pipei.readline())
1874 1912 return self.pipei.read(l) != ""
1875 1913
1876 1914 def repository(ui, path=None, create=0):
1877 1915 if path:
1878 1916 if path.startswith("http://"):
1879 1917 return httprepository(ui, path)
1880 1918 if path.startswith("hg://"):
1881 1919 return httprepository(ui, path.replace("hg://", "http://"))
1882 1920 if path.startswith("old-http://"):
1883 1921 return localrepository(ui, path.replace("old-http://", "http://"))
1884 1922 if path.startswith("ssh://"):
1885 1923 return sshrepository(ui, path)
1886 1924
1887 1925 return localrepository(ui, path, create)
General Comments 0
You need to be logged in to leave comments. Login now