mercurial/hg.py
changeset 94 7daef883134f
parent 90 ab9ebff09dcd
child 95 589f507bb259
equal deleted inserted replaced
93:0b0efe409d79 94:7daef883134f
    27 
    27 
    28     def read(self, node):
    28     def read(self, node):
    29         return self.revision(node)
    29         return self.revision(node)
    30     def add(self, text, transaction, link, p1=None, p2=None):
    30     def add(self, text, transaction, link, p1=None, p2=None):
    31         return self.addrevision(text, transaction, link, p1, p2)
    31         return self.addrevision(text, transaction, link, p1, p2)
    32 
       
    33     def resolvedag(self, old, new, transaction, link):
       
    34         """resolve unmerged heads in our DAG"""
       
    35         if old == new: return None
       
    36         a = self.ancestor(old, new)
       
    37         if old == a: return None
       
    38         return self.merge3(old, new, a, transaction, link)
       
    39 
       
    40     def merge3(self, my, other, base, transaction, link):
       
    41         """perform a 3-way merge and append the result"""
       
    42         def temp(prefix, node):
       
    43             (fd, name) = tempfile.mkstemp(prefix)
       
    44             f = os.fdopen(fd, "w")
       
    45             f.write(self.revision(node))
       
    46             f.close()
       
    47             return name
       
    48 
       
    49         a = temp("local", my)
       
    50         b = temp("remote", other)
       
    51         c = temp("parent", base)
       
    52 
       
    53         cmd = os.environ["HGMERGE"]
       
    54         r = os.system("%s %s %s %s" % (cmd, a, b, c))
       
    55         if r:
       
    56             raise "Merge failed, implement rollback!"
       
    57 
       
    58         t = open(a).read()
       
    59         os.unlink(a)
       
    60         os.unlink(b)
       
    61         os.unlink(c)
       
    62         return self.addrevision(t, transaction, link, my, other)
       
    63 
       
    64     def merge(self, other, transaction, linkseq, link):
       
    65         """perform a merge and resolve resulting heads"""
       
    66         (o, n) = self.mergedag(other, transaction, linkseq)
       
    67         return self.resolvedag(o, n, transaction, link)
       
    68 
    32 
    69     def annotate(self, node):
    33     def annotate(self, node):
    70         revs = []
    34         revs = []
    71         while node != nullid:
    35         while node != nullid:
    72             revs.append(node)
    36             revs.append(node)
   158         list.sort()
   122         list.sort()
   159         l = [hex(manifest), user, date] + list + ["", desc]
   123         l = [hex(manifest), user, date] + list + ["", desc]
   160         text = "\n".join(l)
   124         text = "\n".join(l)
   161         return self.addrevision(text, transaction, self.count(), p1, p2)
   125         return self.addrevision(text, transaction, self.count(), p1, p2)
   162 
   126 
   163     def merge3(self, my, other, base):
       
   164         pass
       
   165 
       
   166 class dircache:
   127 class dircache:
   167     def __init__(self, opener, ui):
   128     def __init__(self, opener, ui):
   168         self.opener = opener
   129         self.opener = opener
   169         self.dirty = 0
   130         self.dirty = 0
   170         self.ui = ui
   131         self.ui = ui
   332     def file(self, f):
   293     def file(self, f):
   333         return filelog(self.opener, f)
   294         return filelog(self.opener, f)
   334 
   295 
   335     def transaction(self):
   296     def transaction(self):
   336         return transaction(self.opener, self.join("journal"))
   297         return transaction(self.opener, self.join("journal"))
   337 
       
   338     def merge(self, other):
       
   339         tr = self.transaction()
       
   340         changed = {}
       
   341         new = {}
       
   342         seqrev = self.changelog.count()
       
   343         # some magic to allow fiddling in nested scope
       
   344         nextrev = [seqrev]
       
   345 
       
   346         # helpers for back-linking file revisions to local changeset
       
   347         # revisions so we can immediately get to changeset from annotate
       
   348         def accumulate(text):
       
   349             # track which files are added in which changeset and the
       
   350             # corresponding _local_ changeset revision
       
   351             files = self.changelog.extract(text)[3]
       
   352             for f in files:
       
   353                 changed.setdefault(f, []).append(nextrev[0])
       
   354             nextrev[0] += 1
       
   355 
       
   356         def seq(start):
       
   357             while 1:
       
   358                 yield start
       
   359                 start += 1
       
   360 
       
   361         def lseq(l):
       
   362             for r in l:
       
   363                 yield r
       
   364 
       
   365         # begin the import/merge of changesets
       
   366         self.ui.status("merging new changesets\n")
       
   367         (co, cn) = self.changelog.mergedag(other.changelog, tr,
       
   368                                            seq(seqrev), accumulate)
       
   369         resolverev = self.changelog.count()
       
   370 
       
   371         # is there anything to do?
       
   372         if co == cn:
       
   373             tr.close()
       
   374             return
       
   375         
       
   376         # do we need to resolve?
       
   377         simple = (co == self.changelog.ancestor(co, cn))
       
   378 
       
   379         # merge all files changed by the changesets,
       
   380         # keeping track of the new tips
       
   381         changelist = changed.keys()
       
   382         changelist.sort()
       
   383         for f in changelist:
       
   384             sys.stdout.write(".")
       
   385             sys.stdout.flush()
       
   386             r = self.file(f)
       
   387             node = r.merge(other.file(f), tr, lseq(changed[f]), resolverev)
       
   388             if node:
       
   389                 new[f] = node
       
   390         sys.stdout.write("\n")
       
   391 
       
   392         # begin the merge of the manifest
       
   393         self.ui.status("merging manifests\n")
       
   394         (mm, mo) = self.manifest.mergedag(other.manifest, tr, seq(seqrev))
       
   395 
       
   396         # For simple merges, we don't need to resolve manifests or changesets
       
   397         if simple:
       
   398             tr.close()
       
   399             return
       
   400 
       
   401         ma = self.manifest.ancestor(mm, mo)
       
   402 
       
   403         # resolve the manifest to point to all the merged files
       
   404         self.ui.status("resolving manifests\n")
       
   405         omap = self.manifest.read(mo) # other
       
   406         amap = self.manifest.read(ma) # ancestor
       
   407         mmap = self.manifest.read(mm) # mine
       
   408         nmap = {}
       
   409 
       
   410         for f, mid in mmap.iteritems():
       
   411             if f in omap:
       
   412                 if mid != omap[f]: 
       
   413                     nmap[f] = new.get(f, mid) # use merged version
       
   414                 else:
       
   415                     nmap[f] = new.get(f, mid) # they're the same
       
   416                 del omap[f]
       
   417             elif f in amap:
       
   418                 if mid != amap[f]: 
       
   419                     pass # we should prompt here
       
   420                 else:
       
   421                     pass # other deleted it
       
   422             else:
       
   423                 nmap[f] = new.get(f, mid) # we created it
       
   424                 
       
   425         del mmap
       
   426 
       
   427         for f, oid in omap.iteritems():
       
   428             if f in amap:
       
   429                 if oid != amap[f]:
       
   430                     pass # this is the nasty case, we should prompt
       
   431                 else:
       
   432                     pass # probably safe
       
   433             else:
       
   434                 nmap[f] = new.get(f, oid) # remote created it
       
   435 
       
   436         del omap
       
   437         del amap
       
   438 
       
   439         node = self.manifest.add(nmap, tr, resolverev, mm, mo)
       
   440 
       
   441         # Now all files and manifests are merged, we add the changed files
       
   442         # and manifest id to the changelog
       
   443         self.ui.status("committing merge changeset\n")
       
   444         new = new.keys()
       
   445         new.sort()
       
   446         if co == cn: cn = -1
       
   447 
       
   448         edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
       
   449         edittext = self.ui.edit(edittext)
       
   450         n = self.changelog.add(node, new, edittext, tr, co, cn)
       
   451 
       
   452         tr.close()
       
   453 
   298 
   454     def commit(self, parent, update = None, text = ""):
   299     def commit(self, parent, update = None, text = ""):
   455         tr = self.transaction()
   300         tr = self.transaction()
   456         
   301         
   457         try:
   302         try:
   638         return r
   483         return r
   639 
   484 
   640     def newer(self, nodes):
   485     def newer(self, nodes):
   641         m = {}
   486         m = {}
   642         nl = []
   487         nl = []
       
   488         pm = {}
   643         cl = self.changelog
   489         cl = self.changelog
   644         t = l = cl.count()
   490         t = l = cl.count()
       
   491 
       
   492         # find the lowest numbered node
   645         for n in nodes:
   493         for n in nodes:
   646             l = min(l, cl.rev(n))
   494             l = min(l, cl.rev(n))
   647             for p in cl.parents(n):
   495             m[n] = 1
   648                 m[p] = 1
       
   649 
   496 
   650         for i in xrange(l, t):
   497         for i in xrange(l, t):
   651             n = cl.node(i)
   498             n = cl.node(i)
       
   499             if n in m: # explicitly listed
       
   500                 pm[n] = 1
       
   501                 nl.append(n)
       
   502                 continue
   652             for p in cl.parents(n):
   503             for p in cl.parents(n):
   653                 if p in m and n not in m:
   504                 if p in pm: # parent listed
   654                     m[n] = 1
   505                     pm[n] = 1
   655                     nl.append(n)
   506                     nl.append(n)
       
   507                     break
   656 
   508 
   657         return nl
   509         return nl
   658 
   510 
   659     def getchangegroup(self, remote):
   511     def getchangegroup(self, remote):
   660         tip = remote.branches([])[0]
   512         tip = remote.branches([])[0]
   674             if n == nullid: break
   526             if n == nullid: break
   675             if n[1] and n[1] in m: # do we know the base?
   527             if n[1] and n[1] in m: # do we know the base?
   676                 self.ui.debug("found incomplete branch %s\n" % short(n[1]))
   528                 self.ui.debug("found incomplete branch %s\n" % short(n[1]))
   677                 search.append(n) # schedule branch range for scanning
   529                 search.append(n) # schedule branch range for scanning
   678             else:
   530             else:
       
   531                 if n[2] in m and n[3] in m:
       
   532                     if n[1] not in fetch:
       
   533                         self.ui.debug("found new changeset %s\n" %
       
   534                                       short(n[1]))
       
   535                         fetch.append(n[1]) # earliest unknown
       
   536                         continue
   679                 for b in remote.branches([n[2], n[3]]):
   537                 for b in remote.branches([n[2], n[3]]):
   680                     if b[0] in m:
   538                     if b[0] not in m:
   681                         if n[1] not in fetch:
       
   682                             self.ui.debug("found new changeset %s\n" %
       
   683                                           short(n[1]))
       
   684                             fetch.append(n[1]) # earliest unknown
       
   685                     else:
       
   686                         unknown.append(b)
   539                         unknown.append(b)
   687   
   540   
   688         while search:
   541         while search:
   689             n = search.pop(0)
   542             n = search.pop(0)
   690             l = remote.between([(n[0], n[1])])[0]
   543             l = remote.between([(n[0], n[1])])[0]
   705 
   558 
   706         for f in fetch:
   559         for f in fetch:
   707             if f in m:
   560             if f in m:
   708                 raise "already have", short(f[:4])
   561                 raise "already have", short(f[:4])
   709 
   562 
   710         self.ui.note("merging new changesets starting at " +
   563         self.ui.note("adding new changesets starting at " +
   711                      " ".join([short(f) for f in fetch]) + "\n")
   564                      " ".join([short(f) for f in fetch]) + "\n")
   712 
   565 
   713         return remote.changegroup(fetch)
   566         return remote.changegroup(fetch)
   714     
   567     
   715     def changegroup(self, basenodes):
   568     def changegroup(self, basenodes):
   765             return source.read(l - 4 + add)
   618             return source.read(l - 4 + add)
   766 
   619 
   767         tr = self.transaction()
   620         tr = self.transaction()
   768         simple = True
   621         simple = True
   769 
   622 
   770         self.ui.status("merging changesets\n")
   623         self.ui.status("adding changesets\n")
   771         # pull off the changeset group
   624         # pull off the changeset group
       
   625         def report(x):
       
   626             self.ui.debug("add changeset %s\n" % short(x))
       
   627             return self.changelog.count()
       
   628             
   772         csg = getchunk()
   629         csg = getchunk()
   773         co = self.changelog.tip()
   630         co = self.changelog.tip()
   774         cn = self.changelog.addgroup(csg, lambda x: self.changelog.count(), tr)
   631         cn = self.changelog.addgroup(csg, report, tr)
   775 
   632 
   776         self.ui.status("merging manifests\n")
   633         self.ui.status("adding manifests\n")
   777         # pull off the manifest group
   634         # pull off the manifest group
   778         mfg = getchunk()
   635         mfg = getchunk()
   779         mm = self.manifest.tip()
   636         mm = self.manifest.tip()
   780         mo = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr)
   637         mo = self.manifest.addgroup(mfg, lambda x: self.changelog.rev(x), tr)
   781 
   638 
   783         if self.changelog.ancestor(co, cn) != co:
   640         if self.changelog.ancestor(co, cn) != co:
   784             simple = False
   641             simple = False
   785             resolverev = self.changelog.count()
   642             resolverev = self.changelog.count()
   786 
   643 
   787         # process the files
   644         # process the files
   788         self.ui.status("merging files\n")
   645         self.ui.status("adding files\n")
   789         new = {}
   646         new = {}
   790         while 1:
   647         while 1:
   791             f = getchunk(4)
   648             f = getchunk(4)
   792             if not f: break
   649             if not f: break
   793             fg = getchunk()
   650             fg = getchunk()
   794 
   651             self.ui.debug("adding %s revisions\n" % f)
   795             fl = self.file(f)
   652             fl = self.file(f)
   796             o = fl.tip()
   653             o = fl.tip()
   797             n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr)
   654             n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr)
   798             if not simple:
   655             if not simple:
   799                 nn = fl.resolvedag(o, n, tr, resolverev)
   656                 if o == n: continue
   800                 if nn:
   657                 # this file has changed between branches, so it must be
   801                     self.ui.note("merged %s\n", f)
   658                 # represented in the merge changeset
   802                     new[f] = nn
   659                 new[f] = self.merge3(fl, f, o, n, tr, resolverev)
   803 
   660 
   804         # For simple merges, we don't need to resolve manifests or changesets
   661         # For simple merges, we don't need to resolve manifests or changesets
   805         if simple:
   662         if simple:
   806             self.ui.debug("simple merge, skipping resolve\n")
   663             self.ui.debug("simple merge, skipping resolve\n")
   807             tr.close()
   664             tr.close()
   819 
   676 
   820         for f, mid in mmap.iteritems():
   677         for f, mid in mmap.iteritems():
   821             if f in omap:
   678             if f in omap:
   822                 if mid != omap[f]:
   679                 if mid != omap[f]:
   823                     self.ui.debug("%s versions differ\n" % f)
   680                     self.ui.debug("%s versions differ\n" % f)
   824                     if f in new: self.ui.note("%s updated in resolve\n" % f)
   681                     if f in new: self.ui.debug("%s updated in resolve\n" % f)
   825                     nmap[f] = new.get(f, mid) # use merged version
   682                     # use merged version or local version
       
   683                     nmap[f] = new.get(f, mid)
   826                 else:
   684                 else:
   827                     nmap[f] = mid # keep ours
   685                     nmap[f] = mid # keep ours
   828                 del omap[f]
   686                 del omap[f]
   829             elif f in amap:
   687             elif f in amap:
   830                 if mid != amap[f]:
   688                 if mid != amap[f]: