comparison mercurial/hg.py @ 232:fc4a6e5b5812

hg resolve: merge a given node into the working directory -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 hg resolve: merge a given node into the working directory This is the first pass at working directory-based merges. Doing a resolve adds a second parent to the working directory state for the next commit. manifest hash: 827b19995dd2d7686286da3b62c7d5fe3e0bc48c -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.0 (GNU/Linux) iD8DBQFCoMSHywK+sNU5EO8RAnOkAJsHH9jviMJcQJ4JurFuSlrbIwKqRACdHgNC kkfoAxX2E5jkuOeSJ1Hjalk= =bdrT -----END PGP SIGNATURE-----
author mpm@selenic.com
date Fri, 03 Jun 2005 12:58:47 -0800
parents 15e7c6cee929
children 4f802588cdfb 4f802588cdfb 737c66b68290
comparison
equal deleted inserted replaced
231:15e7c6cee929 232:fc4a6e5b5812
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