##// END OF EJS Templates
hg resolve: merge a given node into the working directory...
mpm@selenic.com -
r232:fc4a6e5b default
parent child Browse files
Show More
@@ -153,6 +153,14 b' def parents(ui, repo, node = None):'
153 153 if n != hg.nullid:
154 154 ui.write("%d:%s\n" % (repo.changelog.rev(n), hg.hex(n)))
155 155
156 def resolve(ui, repo, node = None):
157 '''merge a given node or the current tip into the working dir'''
158 if not node:
159 node = repo.changelog.tip()
160 else:
161 node = repo.lookup(node)
162 repo.resolve(node)
163
156 164 def status(ui, repo):
157 165 '''show changed files in the working directory
158 166
@@ -184,6 +192,7 b' table = {'
184 192 ('c', 'changeset', None, 'show changeset')],
185 193 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
186 194 "parents": (parents, [], 'hg parents [node]'),
195 "resolve": (resolve, [], 'hg resolve [node]'),
187 196 "status": (status, [], 'hg status'),
188 197 "undo": (undo, [], 'hg undo'),
189 198 }
@@ -858,174 +858,110 b' class localrepository:'
858 858 tr.close()
859 859 return
860 860
861 def merge(self, generator):
862 changesets = files = revisions = 0
863
864 self.lock()
865 class genread:
866 def __init__(self, generator):
867 self.g = generator
868 self.buf = ""
869 def read(self, l):
870 while l > len(self.buf):
871 try:
872 self.buf += self.g.next()
873 except StopIteration:
874 break
875 d, self.buf = self.buf[:l], self.buf[l:]
876 return d
877
878 if not generator: return
879 source = genread(generator)
880
881 def getchunk():
882 d = source.read(4)
883 if not d: return ""
884 l = struct.unpack(">l", d)[0]
885 if l <= 4: return ""
886 return source.read(l - 4)
887
888 def getgroup():
889 while 1:
890 c = getchunk()
891 if not c: break
892 yield c
893
894 tr = self.transaction()
895 simple = True
896 need = {}
897
898 self.ui.status("adding changesets\n")
899 # pull off the changeset group
900 def report(x):
901 self.ui.debug("add changeset %s\n" % short(x))
902 return self.changelog.count()
903
904 co = self.changelog.tip()
905 cn = self.changelog.addgroup(getgroup(), report, tr)
906
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:
950 self.ui.debug("local created %s\n" %f)
951 nmap[f] = mid # we created it
952
953 del mmap
954
955 for f, oid in omap.iteritems():
956 if f in amap:
957 if oid != amap[f]:
958 r = self.ui.prompt(
959 ("remote changed %s which local deleted\n" % f) +
960 "(k)eep or (d)elete?", "[kd]", "k")
961 if r == "k": nmap[f] = oid
962 else:
963 pass # probably safe
964 else:
965 self.ui.debug("remote created %s, do resolve\n" % f)
966 need[f] = oid
967
968 del omap
969 del amap
970
971 new = need.keys()
972 new.sort()
973
974 # process the files
975 self.ui.status("adding files\n")
976 while 1:
977 f = getchunk()
978 if not f: break
979 self.ui.debug("adding %s revisions\n" % f)
980 fl = self.file(f)
981 o = fl.tip()
982 n = fl.addgroup(getgroup(), lambda x: self.changelog.rev(x), tr)
983 revisions += fl.rev(n) - fl.rev(o)
984 files += 1
985 if f in need:
986 del need[f]
987 # manifest resolve determined we need to merge the tips
988 nmap[f] = self.merge3(fl, f, o, n, tr, resolverev)
989
990 if need:
991 # we need to do trivial merges on local files
992 for f in new:
993 if f not in need: continue
994 fl = self.file(f)
995 nmap[f] = self.merge3(fl, f, need[f], fl.tip(), tr, resolverev)
996 revisions += 1
997
998 # For simple merges, we don't need to resolve manifests or changesets
999 if simple:
1000 self.ui.debug("simple merge, skipping resolve\n")
1001 self.ui.status(("modified %d files, added %d changesets" +
1002 " and %d new revisions\n")
1003 % (files, changesets, revisions))
1004 tr.close()
861 def resolve(self, node):
862 pl = self.dirstate.parents()
863 if pl[1] != nullid:
864 self.ui.warn("last merge not committed")
1005 865 return
1006 866
1007 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
1008 revisions += 1
867 p1, p2 = pl[0], node
868 m1n = self.changelog.read(p1)[0]
869 m2n = self.changelog.read(p2)[0]
870 man = self.manifest.ancestor(m1n, m2n)
871 m1 = self.manifest.read(m1n)
872 m2 = self.manifest.read(m2n)
873 ma = self.manifest.read(man)
874
875 (c, a, d, u) = self.diffdir(self.root)
876
877 # resolve the manifest to determine which files
878 # we care about merging
879 self.ui.status("resolving manifests\n")
880 self.ui.debug(" ancestor %s local %s remote %s\n" %
881 (short(man), short(m1n), short(m2n)))
882
883 merge = {}
884 get = {}
885 remove = []
1009 886
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
887 # construct a working dir manifest
888 mw = m1.copy()
889 for f in a + c:
890 mw[f] = nullid
891 for f in d:
892 del mw[f]
893
894 for f, n in mw.iteritems():
895 if f in m2:
896 if n != m2[f]:
897 self.ui.debug(" %s versions differ, do resolve\n" % f)
898 merge[f] = (m1.get(f, nullid), m2[f])
899 del m2[f]
900 elif f in ma:
901 if n != ma[f]:
902 r = self.ui.prompt(
903 (" local changed %s which remote deleted\n" % f) +
904 "(k)eep or (d)elete?", "[kd]", "k")
905 if r == "d":
906 remove.append(f)
907 else:
908 self.ui.debug("other deleted %s\n" % f)
909 pass # other deleted it
910 else:
911 self.ui.debug("local created %s\n" %f)
1014 912
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
913 for f, n in m2.iteritems():
914 if f in ma:
915 if n != ma[f]:
916 r = self.ui.prompt(
917 ("remote changed %s which local deleted\n" % f) +
918 "(k)eep or (d)elete?", "[kd]", "k")
919 if r == "d": remove.append(f)
920 else:
921 pass # probably safe
922 else:
923 self.ui.debug("remote created %s, do resolve\n" % f)
924 get[f] = n
925
926 del mw, m1, m2, ma
927
928 self.dirstate.setparents(p1, p2)
1021 929
1022 self.ui.status("added %d changesets, %d files, and %d new revisions\n"
1023 % (changesets, files, revisions))
930 # get the files we don't need to change
931 files = get.keys()
932 files.sort()
933 for f in files:
934 if f[0] == "/": continue
935 self.ui.note(f, "\n")
936 t = self.file(f).revision(get[f])
937 try:
938 file(f, "w").write(t)
939 except IOError:
940 os.makedirs(os.path.dirname(f))
941 file(f, "w").write(t)
942
943 # we have to remember what files we needed to get/change
944 # because any file that's different from either one of its
945 # parents must be in the changeset
946 self.dirstate.update(files, 'm')
1024 947
1025 tr.close()
948 # merge the tricky bits
949 files = merge.keys()
950 files.sort()
951 for f in files:
952 m, o = merge[f]
953 self.merge3(f, m, o)
1026 954
1027 def merge3(self, fl, fn, my, other, transaction, link):
1028 """perform a 3-way merge and append the result"""
955 # same here
956 self.dirstate.update(files, 'm')
957
958 for f in remove:
959 self.ui.note("removing %s\n" % f)
960 #os.unlink(f)
961 self.dirstate.update(remove, 'r')
962
963 def merge3(self, fn, my, other):
964 """perform a 3-way merge in the working directory"""
1029 965
1030 966 def temp(prefix, node):
1031 967 pre = "%s~%s." % (os.path.basename(fn), prefix)
@@ -1035,30 +971,23 b' class localrepository:'
1035 971 f.close()
1036 972 return name
1037 973
974 fl = self.file(fn)
1038 975 base = fl.ancestor(my, other)
1039 self.ui.note("resolving %s\n" % fn)
1040 self.ui.debug("local %s remote %s ancestor %s\n" %
1041 (short(my), short(other), short(base)))
1042
1043 if my == base:
1044 text = fl.revision(other)
1045 else:
1046 a = temp("local", my)
1047 b = temp("remote", other)
1048 c = temp("parent", base)
976 a = fn
977 b = temp("other", other)
978 c = temp("base", base)
1049 979
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!"
980 self.ui.note("resolving %s\n" % fn)
981 self.ui.debug("file %s: other %s ancestor %s\n" %
982 (fn, short(other), short(base)))
1055 983
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)
984 cmd = os.environ["HGMERGE"]
985 r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn))
986 if r:
987 self.ui.warn("merging %s failed!\n" % f)
988
989 os.unlink(b)
990 os.unlink(c)
1062 991
1063 992 class remoterepository:
1064 993 def __init__(self, ui, path):
General Comments 0
You need to be logged in to leave comments. Login now