mercurial/hg.py
changeset 232 fc4a6e5b5812
parent 231 15e7c6cee929
child 237 4f802588cdfb
child 237 4f802588cdfb
child 240 737c66b68290
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