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