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]: