Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/hg.py @ 94:7daef883134f
Refactor merge code
Delete old code
Fix calculation of newer nodes on server
Fix branch recursion on client
Fix manifest merge problems
Add more debugging and note messages to merge
author | mpm@selenic.com |
---|---|
date | Wed, 18 May 2005 16:29:39 -0800 |
parents | ab9ebff09dcd |
children | 589f507bb259 |
comparison
equal
deleted
inserted
replaced
93:0b0efe409d79 | 94:7daef883134f |
---|---|
27 | 27 |
28 def read(self, node): | 28 def read(self, node): |
29 return self.revision(node) | 29 return self.revision(node) |
30 def add(self, text, transaction, link, p1=None, p2=None): | 30 def add(self, text, transaction, link, p1=None, p2=None): |
31 return self.addrevision(text, transaction, link, p1, p2) | 31 return self.addrevision(text, transaction, link, p1, p2) |
32 | |
33 def resolvedag(self, old, new, transaction, link): | |
34 """resolve unmerged heads in our DAG""" | |
35 if old == new: return None | |
36 a = self.ancestor(old, new) | |
37 if old == a: return None | |
38 return self.merge3(old, new, a, transaction, link) | |
39 | |
40 def merge3(self, my, other, base, transaction, link): | |
41 """perform a 3-way merge and append the result""" | |
42 def temp(prefix, node): | |
43 (fd, name) = tempfile.mkstemp(prefix) | |
44 f = os.fdopen(fd, "w") | |
45 f.write(self.revision(node)) | |
46 f.close() | |
47 return name | |
48 | |
49 a = temp("local", my) | |
50 b = temp("remote", other) | |
51 c = temp("parent", base) | |
52 | |
53 cmd = os.environ["HGMERGE"] | |
54 r = os.system("%s %s %s %s" % (cmd, a, b, c)) | |
55 if r: | |
56 raise "Merge failed, implement rollback!" | |
57 | |
58 t = open(a).read() | |
59 os.unlink(a) | |
60 os.unlink(b) | |
61 os.unlink(c) | |
62 return self.addrevision(t, transaction, link, my, other) | |
63 | |
64 def merge(self, other, transaction, linkseq, link): | |
65 """perform a merge and resolve resulting heads""" | |
66 (o, n) = self.mergedag(other, transaction, linkseq) | |
67 return self.resolvedag(o, n, transaction, link) | |
68 | 32 |
69 def annotate(self, node): | 33 def annotate(self, node): |
70 revs = [] | 34 revs = [] |
71 while node != nullid: | 35 while node != nullid: |
72 revs.append(node) | 36 revs.append(node) |
158 list.sort() | 122 list.sort() |
159 l = [hex(manifest), user, date] + list + ["", desc] | 123 l = [hex(manifest), user, date] + list + ["", desc] |
160 text = "\n".join(l) | 124 text = "\n".join(l) |
161 return self.addrevision(text, transaction, self.count(), p1, p2) | 125 return self.addrevision(text, transaction, self.count(), p1, p2) |
162 | 126 |
163 def merge3(self, my, other, base): | |
164 pass | |
165 | |
166 class dircache: | 127 class dircache: |
167 def __init__(self, opener, ui): | 128 def __init__(self, opener, ui): |
168 self.opener = opener | 129 self.opener = opener |
169 self.dirty = 0 | 130 self.dirty = 0 |
170 self.ui = ui | 131 self.ui = ui |
332 def file(self, f): | 293 def file(self, f): |
333 return filelog(self.opener, f) | 294 return filelog(self.opener, f) |
334 | 295 |
335 def transaction(self): | 296 def transaction(self): |
336 return transaction(self.opener, self.join("journal")) | 297 return transaction(self.opener, self.join("journal")) |
337 | |
338 def merge(self, other): | |
339 tr = self.transaction() | |
340 changed = {} | |
341 new = {} | |
342 seqrev = self.changelog.count() | |
343 # some magic to allow fiddling in nested scope | |
344 nextrev = [seqrev] | |
345 | |
346 # helpers for back-linking file revisions to local changeset | |
347 # revisions so we can immediately get to changeset from annotate | |
348 def accumulate(text): | |
349 # track which files are added in which changeset and the | |
350 # corresponding _local_ changeset revision | |
351 files = self.changelog.extract(text)[3] | |
352 for f in files: | |
353 changed.setdefault(f, []).append(nextrev[0]) | |
354 nextrev[0] += 1 | |
355 | |
356 def seq(start): | |
357 while 1: | |
358 yield start | |
359 start += 1 | |
360 | |
361 def lseq(l): | |
362 for r in l: | |
363 yield r | |
364 | |
365 # begin the import/merge of changesets | |
366 self.ui.status("merging new changesets\n") | |
367 (co, cn) = self.changelog.mergedag(other.changelog, tr, | |
368 seq(seqrev), accumulate) | |
369 resolverev = self.changelog.count() | |
370 | |
371 # is there anything to do? | |
372 if co == cn: | |
373 tr.close() | |
374 return | |
375 | |
376 # do we need to resolve? | |
377 simple = (co == self.changelog.ancestor(co, cn)) | |
378 | |
379 # merge all files changed by the changesets, | |
380 # keeping track of the new tips | |
381 changelist = changed.keys() | |
382 changelist.sort() | |
383 for f in changelist: | |
384 sys.stdout.write(".") | |
385 sys.stdout.flush() | |
386 r = self.file(f) | |
387 node = r.merge(other.file(f), tr, lseq(changed[f]), resolverev) | |
388 if node: | |
389 new[f] = node | |
390 sys.stdout.write("\n") | |
391 | |
392 # begin the merge of the manifest | |
393 self.ui.status("merging manifests\n") | |
394 (mm, mo) = self.manifest.mergedag(other.manifest, tr, seq(seqrev)) | |
395 | |
396 # For simple merges, we don't need to resolve manifests or changesets | |
397 if simple: | |
398 tr.close() | |
399 return | |
400 | |
401 ma = self.manifest.ancestor(mm, mo) | |
402 | |
403 # resolve the manifest to point to all the merged files | |
404 self.ui.status("resolving manifests\n") | |
405 omap = self.manifest.read(mo) # other | |
406 amap = self.manifest.read(ma) # ancestor | |
407 mmap = self.manifest.read(mm) # mine | |
408 nmap = {} | |
409 | |
410 for f, mid in mmap.iteritems(): | |
411 if f in omap: | |
412 if mid != omap[f]: | |
413 nmap[f] = new.get(f, mid) # use merged version | |
414 else: | |
415 nmap[f] = new.get(f, mid) # they're the same | |
416 del omap[f] | |
417 elif f in amap: | |
418 if mid != amap[f]: | |
419 pass # we should prompt here | |
420 else: | |
421 pass # other deleted it | |
422 else: | |
423 nmap[f] = new.get(f, mid) # we created it | |
424 | |
425 del mmap | |
426 | |
427 for f, oid in omap.iteritems(): | |
428 if f in amap: | |
429 if oid != amap[f]: | |
430 pass # this is the nasty case, we should prompt | |
431 else: | |
432 pass # probably safe | |
433 else: | |
434 nmap[f] = new.get(f, oid) # remote created it | |
435 | |
436 del omap | |
437 del amap | |
438 | |
439 node = self.manifest.add(nmap, tr, resolverev, mm, mo) | |
440 | |
441 # Now all files and manifests are merged, we add the changed files | |
442 # and manifest id to the changelog | |
443 self.ui.status("committing merge changeset\n") | |
444 new = new.keys() | |
445 new.sort() | |
446 if co == cn: cn = -1 | |
447 | |
448 edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new]) | |
449 edittext = self.ui.edit(edittext) | |
450 n = self.changelog.add(node, new, edittext, tr, co, cn) | |
451 | |
452 tr.close() | |
453 | 298 |
454 def commit(self, parent, update = None, text = ""): | 299 def commit(self, parent, update = None, text = ""): |
455 tr = self.transaction() | 300 tr = self.transaction() |
456 | 301 |
457 try: | 302 try: |
638 return r | 483 return r |
639 | 484 |
640 def newer(self, nodes): | 485 def newer(self, nodes): |
641 m = {} | 486 m = {} |
642 nl = [] | 487 nl = [] |
488 pm = {} | |
643 cl = self.changelog | 489 cl = self.changelog |
644 t = l = cl.count() | 490 t = l = cl.count() |
491 | |
492 # find the lowest numbered node | |
645 for n in nodes: | 493 for n in nodes: |
646 l = min(l, cl.rev(n)) | 494 l = min(l, cl.rev(n)) |
647 for p in cl.parents(n): | 495 m[n] = 1 |
648 m[p] = 1 | |
649 | 496 |
650 for i in xrange(l, t): | 497 for i in xrange(l, t): |
651 n = cl.node(i) | 498 n = cl.node(i) |
499 if n in m: # explicitly listed | |
500 pm[n] = 1 | |
501 nl.append(n) | |
502 continue | |
652 for p in cl.parents(n): | 503 for p in cl.parents(n): |
653 if p in m and n not in m: | 504 if p in pm: # parent listed |
654 m[n] = 1 | 505 pm[n] = 1 |
655 nl.append(n) | 506 nl.append(n) |
507 break | |
656 | 508 |
657 return nl | 509 return nl |
658 | 510 |
659 def getchangegroup(self, remote): | 511 def getchangegroup(self, remote): |
660 tip = remote.branches([])[0] | 512 tip = remote.branches([])[0] |
674 if n == nullid: break | 526 if n == nullid: break |
675 if n[1] and n[1] in m: # do we know the base? | 527 if n[1] and n[1] in m: # do we know the base? |
676 self.ui.debug("found incomplete branch %s\n" % short(n[1])) | 528 self.ui.debug("found incomplete branch %s\n" % short(n[1])) |
677 search.append(n) # schedule branch range for scanning | 529 search.append(n) # schedule branch range for scanning |
678 else: | 530 else: |
531 if n[2] in m and n[3] in m: | |
532 if n[1] not in fetch: | |
533 self.ui.debug("found new changeset %s\n" % | |
534 short(n[1])) | |
535 fetch.append(n[1]) # earliest unknown | |
536 continue | |
679 for b in remote.branches([n[2], n[3]]): | 537 for b in remote.branches([n[2], n[3]]): |
680 if b[0] in m: | 538 if b[0] not in m: |
681 if n[1] not in fetch: | |
682 self.ui.debug("found new changeset %s\n" % | |
683 short(n[1])) | |
684 fetch.append(n[1]) # earliest unknown | |
685 else: | |
686 unknown.append(b) | 539 unknown.append(b) |
687 | 540 |
688 while search: | 541 while search: |
689 n = search.pop(0) | 542 n = search.pop(0) |
690 l = remote.between([(n[0], n[1])])[0] | 543 l = remote.between([(n[0], n[1])])[0] |
705 | 558 |
706 for f in fetch: | 559 for f in fetch: |
707 if f in m: | 560 if f in m: |
708 raise "already have", short(f[:4]) | 561 raise "already have", short(f[:4]) |
709 | 562 |
710 self.ui.note("merging new changesets starting at " + | 563 self.ui.note("adding new changesets starting at " + |
711 " ".join([short(f) for f in fetch]) + "\n") | 564 " ".join([short(f) for f in fetch]) + "\n") |
712 | 565 |
713 return remote.changegroup(fetch) | 566 return remote.changegroup(fetch) |
714 | 567 |
715 def changegroup(self, basenodes): | 568 def changegroup(self, basenodes): |
765 return source.read(l - 4 + add) | 618 return source.read(l - 4 + add) |
766 | 619 |
767 tr = self.transaction() | 620 tr = self.transaction() |
768 simple = True | 621 simple = True |
769 | 622 |
770 self.ui.status("merging changesets\n") | 623 self.ui.status("adding changesets\n") |
771 # pull off the changeset group | 624 # pull off the changeset group |
625 def report(x): | |
626 self.ui.debug("add changeset %s\n" % short(x)) | |
627 return self.changelog.count() | |
628 | |
772 csg = getchunk() | 629 csg = getchunk() |
773 co = self.changelog.tip() | 630 co = self.changelog.tip() |
774 cn = self.changelog.addgroup(csg, lambda x: self.changelog.count(), tr) | 631 cn = self.changelog.addgroup(csg, report, tr) |
775 | 632 |
776 self.ui.status("merging manifests\n") | 633 self.ui.status("adding manifests\n") |
777 # pull off the manifest group | 634 # pull off the manifest group |
778 mfg = getchunk() | 635 mfg = getchunk() |
779 mm = self.manifest.tip() | 636 mm = self.manifest.tip() |
780 mo = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr) | 637 mo = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr) |
781 | 638 |
783 if self.changelog.ancestor(co, cn) != co: | 640 if self.changelog.ancestor(co, cn) != co: |
784 simple = False | 641 simple = False |
785 resolverev = self.changelog.count() | 642 resolverev = self.changelog.count() |
786 | 643 |
787 # process the files | 644 # process the files |
788 self.ui.status("merging files\n") | 645 self.ui.status("adding files\n") |
789 new = {} | 646 new = {} |
790 while 1: | 647 while 1: |
791 f = getchunk(4) | 648 f = getchunk(4) |
792 if not f: break | 649 if not f: break |
793 fg = getchunk() | 650 fg = getchunk() |
794 | 651 self.ui.debug("adding %s revisions\n" % f) |
795 fl = self.file(f) | 652 fl = self.file(f) |
796 o = fl.tip() | 653 o = fl.tip() |
797 n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr) | 654 n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr) |
798 if not simple: | 655 if not simple: |
799 nn = fl.resolvedag(o, n, tr, resolverev) | 656 if o == n: continue |
800 if nn: | 657 # this file has changed between branches, so it must be |
801 self.ui.note("merged %s\n", f) | 658 # represented in the merge changeset |
802 new[f] = nn | 659 new[f] = self.merge3(fl, f, o, n, tr, resolverev) |
803 | 660 |
804 # For simple merges, we don't need to resolve manifests or changesets | 661 # For simple merges, we don't need to resolve manifests or changesets |
805 if simple: | 662 if simple: |
806 self.ui.debug("simple merge, skipping resolve\n") | 663 self.ui.debug("simple merge, skipping resolve\n") |
807 tr.close() | 664 tr.close() |
819 | 676 |
820 for f, mid in mmap.iteritems(): | 677 for f, mid in mmap.iteritems(): |
821 if f in omap: | 678 if f in omap: |
822 if mid != omap[f]: | 679 if mid != omap[f]: |
823 self.ui.debug("%s versions differ\n" % f) | 680 self.ui.debug("%s versions differ\n" % f) |
824 if f in new: self.ui.note("%s updated in resolve\n" % f) | 681 if f in new: self.ui.debug("%s updated in resolve\n" % f) |
825 nmap[f] = new.get(f, mid) # use merged version | 682 # use merged version or local version |
683 nmap[f] = new.get(f, mid) | |
826 else: | 684 else: |
827 nmap[f] = mid # keep ours | 685 nmap[f] = mid # keep ours |
828 del omap[f] | 686 del omap[f] |
829 elif f in amap: | 687 elif f in amap: |
830 if mid != amap[f]: | 688 if mid != amap[f]: |