856 % (files, changesets, revisions)) |
856 % (files, changesets, revisions)) |
857 |
857 |
858 tr.close() |
858 tr.close() |
859 return |
859 return |
860 |
860 |
861 def merge(self, generator): |
861 def resolve(self, node): |
862 changesets = files = revisions = 0 |
862 pl = self.dirstate.parents() |
863 |
863 if pl[1] != nullid: |
864 self.lock() |
864 self.ui.warn("last merge not committed") |
865 class genread: |
865 return |
866 def __init__(self, generator): |
866 |
867 self.g = generator |
867 p1, p2 = pl[0], node |
868 self.buf = "" |
868 m1n = self.changelog.read(p1)[0] |
869 def read(self, l): |
869 m2n = self.changelog.read(p2)[0] |
870 while l > len(self.buf): |
870 man = self.manifest.ancestor(m1n, m2n) |
871 try: |
871 m1 = self.manifest.read(m1n) |
872 self.buf += self.g.next() |
872 m2 = self.manifest.read(m2n) |
873 except StopIteration: |
873 ma = self.manifest.read(man) |
874 break |
874 |
875 d, self.buf = self.buf[:l], self.buf[l:] |
875 (c, a, d, u) = self.diffdir(self.root) |
876 return d |
876 |
877 |
877 # resolve the manifest to determine which files |
878 if not generator: return |
878 # we care about merging |
879 source = genread(generator) |
879 self.ui.status("resolving manifests\n") |
880 |
880 self.ui.debug(" ancestor %s local %s remote %s\n" % |
881 def getchunk(): |
881 (short(man), short(m1n), short(m2n))) |
882 d = source.read(4) |
882 |
883 if not d: return "" |
883 merge = {} |
884 l = struct.unpack(">l", d)[0] |
884 get = {} |
885 if l <= 4: return "" |
885 remove = [] |
886 return source.read(l - 4) |
886 |
887 |
887 # construct a working dir manifest |
888 def getgroup(): |
888 mw = m1.copy() |
889 while 1: |
889 for f in a + c: |
890 c = getchunk() |
890 mw[f] = nullid |
891 if not c: break |
891 for f in d: |
892 yield c |
892 del mw[f] |
893 |
893 |
894 tr = self.transaction() |
894 for f, n in mw.iteritems(): |
895 simple = True |
895 if f in m2: |
896 need = {} |
896 if n != m2[f]: |
897 |
897 self.ui.debug(" %s versions differ, do resolve\n" % f) |
898 self.ui.status("adding changesets\n") |
898 merge[f] = (m1.get(f, nullid), m2[f]) |
899 # pull off the changeset group |
899 del m2[f] |
900 def report(x): |
900 elif f in ma: |
901 self.ui.debug("add changeset %s\n" % short(x)) |
901 if n != ma[f]: |
902 return self.changelog.count() |
902 r = self.ui.prompt( |
903 |
903 (" local changed %s which remote deleted\n" % f) + |
904 co = self.changelog.tip() |
904 "(k)eep or (d)elete?", "[kd]", "k") |
905 cn = self.changelog.addgroup(getgroup(), report, tr) |
905 if r == "d": |
906 |
906 remove.append(f) |
907 changesets = self.changelog.rev(cn) - self.changelog.rev(co) |
|
908 |
|
909 self.ui.status("adding manifests\n") |
|
910 # pull off the manifest group |
|
911 mm = self.manifest.tip() |
|
912 mo = self.manifest.addgroup(getgroup(), |
|
913 lambda x: self.changelog.rev(x), tr) |
|
914 |
|
915 # do we need a resolve? |
|
916 if self.changelog.ancestor(co, cn) != co: |
|
917 simple = False |
|
918 resolverev = self.changelog.count() |
|
919 |
|
920 # resolve the manifest to determine which files |
|
921 # we care about merging |
|
922 self.ui.status("resolving manifests\n") |
|
923 ma = self.manifest.ancestor(mm, mo) |
|
924 omap = self.manifest.read(mo) # other |
|
925 amap = self.manifest.read(ma) # ancestor |
|
926 mmap = self.manifest.read(mm) # mine |
|
927 nmap = {} |
|
928 |
|
929 self.ui.debug(" ancestor %s local %s remote %s\n" % |
|
930 (short(ma), short(mm), short(mo))) |
|
931 |
|
932 for f, mid in mmap.iteritems(): |
|
933 if f in omap: |
|
934 if mid != omap[f]: |
|
935 self.ui.debug(" %s versions differ, do resolve\n" % f) |
|
936 need[f] = mid # use merged version or local version |
|
937 else: |
|
938 nmap[f] = mid # keep ours |
|
939 del omap[f] |
|
940 elif f in amap: |
|
941 if mid != amap[f]: |
|
942 r = self.ui.prompt( |
|
943 (" local changed %s which remote deleted\n" % f) + |
|
944 "(k)eep or (d)elete?", "[kd]", "k") |
|
945 if r == "k": nmap[f] = mid |
|
946 else: |
|
947 self.ui.debug("other deleted %s\n" % f) |
|
948 pass # other deleted it |
|
949 else: |
907 else: |
950 self.ui.debug("local created %s\n" %f) |
908 self.ui.debug("other deleted %s\n" % f) |
951 nmap[f] = mid # we created it |
909 pass # other deleted it |
952 |
910 else: |
953 del mmap |
911 self.ui.debug("local created %s\n" %f) |
954 |
912 |
955 for f, oid in omap.iteritems(): |
913 for f, n in m2.iteritems(): |
956 if f in amap: |
914 if f in ma: |
957 if oid != amap[f]: |
915 if n != ma[f]: |
958 r = self.ui.prompt( |
916 r = self.ui.prompt( |
959 ("remote changed %s which local deleted\n" % f) + |
917 ("remote changed %s which local deleted\n" % f) + |
960 "(k)eep or (d)elete?", "[kd]", "k") |
918 "(k)eep or (d)elete?", "[kd]", "k") |
961 if r == "k": nmap[f] = oid |
919 if r == "d": remove.append(f) |
962 else: |
|
963 pass # probably safe |
|
964 else: |
920 else: |
965 self.ui.debug("remote created %s, do resolve\n" % f) |
921 pass # probably safe |
966 need[f] = oid |
922 else: |
967 |
923 self.ui.debug("remote created %s, do resolve\n" % f) |
968 del omap |
924 get[f] = n |
969 del amap |
925 |
970 |
926 del mw, m1, m2, ma |
971 new = need.keys() |
927 |
972 new.sort() |
928 self.dirstate.setparents(p1, p2) |
973 |
929 |
974 # process the files |
930 # get the files we don't need to change |
975 self.ui.status("adding files\n") |
931 files = get.keys() |
976 while 1: |
932 files.sort() |
977 f = getchunk() |
933 for f in files: |
978 if not f: break |
934 if f[0] == "/": continue |
979 self.ui.debug("adding %s revisions\n" % f) |
935 self.ui.note(f, "\n") |
980 fl = self.file(f) |
936 t = self.file(f).revision(get[f]) |
981 o = fl.tip() |
937 try: |
982 n = fl.addgroup(getgroup(), lambda x: self.changelog.rev(x), tr) |
938 file(f, "w").write(t) |
983 revisions += fl.rev(n) - fl.rev(o) |
939 except IOError: |
984 files += 1 |
940 os.makedirs(os.path.dirname(f)) |
985 if f in need: |
941 file(f, "w").write(t) |
986 del need[f] |
942 |
987 # manifest resolve determined we need to merge the tips |
943 # we have to remember what files we needed to get/change |
988 nmap[f] = self.merge3(fl, f, o, n, tr, resolverev) |
944 # because any file that's different from either one of its |
989 |
945 # parents must be in the changeset |
990 if need: |
946 self.dirstate.update(files, 'm') |
991 # we need to do trivial merges on local files |
947 |
992 for f in new: |
948 # merge the tricky bits |
993 if f not in need: continue |
949 files = merge.keys() |
994 fl = self.file(f) |
950 files.sort() |
995 nmap[f] = self.merge3(fl, f, need[f], fl.tip(), tr, resolverev) |
951 for f in files: |
996 revisions += 1 |
952 m, o = merge[f] |
997 |
953 self.merge3(f, m, o) |
998 # For simple merges, we don't need to resolve manifests or changesets |
954 |
999 if simple: |
955 # same here |
1000 self.ui.debug("simple merge, skipping resolve\n") |
956 self.dirstate.update(files, 'm') |
1001 self.ui.status(("modified %d files, added %d changesets" + |
957 |
1002 " and %d new revisions\n") |
958 for f in remove: |
1003 % (files, changesets, revisions)) |
959 self.ui.note("removing %s\n" % f) |
1004 tr.close() |
960 #os.unlink(f) |
1005 return |
961 self.dirstate.update(remove, 'r') |
1006 |
962 |
1007 node = self.manifest.add(nmap, tr, resolverev, mm, mo) |
963 def merge3(self, fn, my, other): |
1008 revisions += 1 |
964 """perform a 3-way merge in the working directory""" |
1009 |
|
1010 # Now all files and manifests are merged, we add the changed files |
|
1011 # and manifest id to the changelog |
|
1012 self.ui.status("committing merge changeset\n") |
|
1013 if co == cn: cn = -1 |
|
1014 |
|
1015 edittext = "\nHG: merge resolve\n" + \ |
|
1016 "HG: manifest hash %s\n" % hex(node) + \ |
|
1017 "".join(["HG: changed %s\n" % f for f in new]) |
|
1018 edittext = self.ui.edit(edittext) |
|
1019 n = self.changelog.add(node, new, edittext, tr, co, cn) |
|
1020 revisions += 1 |
|
1021 |
|
1022 self.ui.status("added %d changesets, %d files, and %d new revisions\n" |
|
1023 % (changesets, files, revisions)) |
|
1024 |
|
1025 tr.close() |
|
1026 |
|
1027 def merge3(self, fl, fn, my, other, transaction, link): |
|
1028 """perform a 3-way merge and append the result""" |
|
1029 |
965 |
1030 def temp(prefix, node): |
966 def temp(prefix, node): |
1031 pre = "%s~%s." % (os.path.basename(fn), prefix) |
967 pre = "%s~%s." % (os.path.basename(fn), prefix) |
1032 (fd, name) = tempfile.mkstemp("", pre) |
968 (fd, name) = tempfile.mkstemp("", pre) |
1033 f = os.fdopen(fd, "w") |
969 f = os.fdopen(fd, "w") |
1034 f.write(fl.revision(node)) |
970 f.write(fl.revision(node)) |
1035 f.close() |
971 f.close() |
1036 return name |
972 return name |
1037 |
973 |
|
974 fl = self.file(fn) |
1038 base = fl.ancestor(my, other) |
975 base = fl.ancestor(my, other) |
|
976 a = fn |
|
977 b = temp("other", other) |
|
978 c = temp("base", base) |
|
979 |
1039 self.ui.note("resolving %s\n" % fn) |
980 self.ui.note("resolving %s\n" % fn) |
1040 self.ui.debug("local %s remote %s ancestor %s\n" % |
981 self.ui.debug("file %s: other %s ancestor %s\n" % |
1041 (short(my), short(other), short(base))) |
982 (fn, short(other), short(base))) |
1042 |
983 |
1043 if my == base: |
984 cmd = os.environ["HGMERGE"] |
1044 text = fl.revision(other) |
985 r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn)) |
1045 else: |
986 if r: |
1046 a = temp("local", my) |
987 self.ui.warn("merging %s failed!\n" % f) |
1047 b = temp("remote", other) |
988 |
1048 c = temp("parent", base) |
989 os.unlink(b) |
1049 |
990 os.unlink(c) |
1050 cmd = os.environ["HGMERGE"] |
|
1051 self.ui.debug("invoking merge with %s\n" % cmd) |
|
1052 r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn)) |
|
1053 if r: |
|
1054 raise "Merge failed!" |
|
1055 |
|
1056 text = open(a).read() |
|
1057 os.unlink(a) |
|
1058 os.unlink(b) |
|
1059 os.unlink(c) |
|
1060 |
|
1061 return fl.add(text, transaction, link, my, other) |
|
1062 |
991 |
1063 class remoterepository: |
992 class remoterepository: |
1064 def __init__(self, ui, path): |
993 def __init__(self, ui, path): |
1065 self.url = path |
994 self.url = path |
1066 self.ui = ui |
995 self.ui = ui |