Mercurial > public > mercurial-scm > hg
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 |