mercurial/changegroup.py
changeset 26695 1121fced5b20
parent 26694 c2e6e3cc7cb4
child 26703 2b16ffcd3c4e
equal deleted inserted replaced
26694:c2e6e3cc7cb4 26695:1121fced5b20
   264                 while pos < len(chunk):
   264                 while pos < len(chunk):
   265                     next = pos + 2**20
   265                     next = pos + 2**20
   266                     yield chunk[pos:next]
   266                     yield chunk[pos:next]
   267                     pos = next
   267                     pos = next
   268             yield closechunk()
   268             yield closechunk()
       
   269 
       
   270     def apply(self, repo, srctype, url, emptyok=False,
       
   271               targetphase=phases.draft, expectedtotal=None):
       
   272         """Add the changegroup returned by source.read() to this repo.
       
   273         srctype is a string like 'push', 'pull', or 'unbundle'.  url is
       
   274         the URL of the repo where this changegroup is coming from.
       
   275 
       
   276         Return an integer summarizing the change to this repo:
       
   277         - nothing changed or no source: 0
       
   278         - more heads than before: 1+added heads (2..n)
       
   279         - fewer heads than before: -1-removed heads (-2..-n)
       
   280         - number of heads stays the same: 1
       
   281         """
       
   282         repo = repo.unfiltered()
       
   283         def csmap(x):
       
   284             repo.ui.debug("add changeset %s\n" % short(x))
       
   285             return len(cl)
       
   286 
       
   287         def revmap(x):
       
   288             return cl.rev(x)
       
   289 
       
   290         changesets = files = revisions = 0
       
   291 
       
   292         tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
       
   293         # The transaction could have been created before and already
       
   294         # carries source information. In this case we use the top
       
   295         # level data. We overwrite the argument because we need to use
       
   296         # the top level value (if they exist) in this function.
       
   297         srctype = tr.hookargs.setdefault('source', srctype)
       
   298         url = tr.hookargs.setdefault('url', url)
       
   299 
       
   300         # write changelog data to temp files so concurrent readers will not see
       
   301         # inconsistent view
       
   302         cl = repo.changelog
       
   303         cl.delayupdate(tr)
       
   304         oldheads = cl.heads()
       
   305         try:
       
   306             repo.hook('prechangegroup', throw=True, **tr.hookargs)
       
   307 
       
   308             trp = weakref.proxy(tr)
       
   309             # pull off the changeset group
       
   310             repo.ui.status(_("adding changesets\n"))
       
   311             clstart = len(cl)
       
   312             class prog(object):
       
   313                 def __init__(self, step, total):
       
   314                     self._step = step
       
   315                     self._total = total
       
   316                     self._count = 1
       
   317                 def __call__(self):
       
   318                     repo.ui.progress(self._step, self._count, unit=_('chunks'),
       
   319                                      total=self._total)
       
   320                     self._count += 1
       
   321             self.callback = prog(_('changesets'), expectedtotal)
       
   322 
       
   323             efiles = set()
       
   324             def onchangelog(cl, node):
       
   325                 efiles.update(cl.read(node)[3])
       
   326 
       
   327             self.changelogheader()
       
   328             srccontent = cl.addgroup(self, csmap, trp,
       
   329                                      addrevisioncb=onchangelog)
       
   330             efiles = len(efiles)
       
   331 
       
   332             if not (srccontent or emptyok):
       
   333                 raise error.Abort(_("received changelog group is empty"))
       
   334             clend = len(cl)
       
   335             changesets = clend - clstart
       
   336             repo.ui.progress(_('changesets'), None)
       
   337 
       
   338             # pull off the manifest group
       
   339             repo.ui.status(_("adding manifests\n"))
       
   340             # manifests <= changesets
       
   341             self.callback = prog(_('manifests'), changesets)
       
   342             # no need to check for empty manifest group here:
       
   343             # if the result of the merge of 1 and 2 is the same in 3 and 4,
       
   344             # no new manifest will be created and the manifest group will
       
   345             # be empty during the pull
       
   346             self.manifestheader()
       
   347             repo.manifest.addgroup(self, revmap, trp)
       
   348             repo.ui.progress(_('manifests'), None)
       
   349 
       
   350             needfiles = {}
       
   351             if repo.ui.configbool('server', 'validate', default=False):
       
   352                 # validate incoming csets have their manifests
       
   353                 for cset in xrange(clstart, clend):
       
   354                     mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
       
   355                     mfest = repo.manifest.readdelta(mfnode)
       
   356                     # store file nodes we must see
       
   357                     for f, n in mfest.iteritems():
       
   358                         needfiles.setdefault(f, set()).add(n)
       
   359 
       
   360             # process the files
       
   361             repo.ui.status(_("adding file changes\n"))
       
   362             self.callback = None
       
   363             pr = prog(_('files'), efiles)
       
   364             newrevs, newfiles = addchangegroupfiles(repo, self, revmap, trp, pr,
       
   365                                                     needfiles)
       
   366             revisions += newrevs
       
   367             files += newfiles
       
   368 
       
   369             dh = 0
       
   370             if oldheads:
       
   371                 heads = cl.heads()
       
   372                 dh = len(heads) - len(oldheads)
       
   373                 for h in heads:
       
   374                     if h not in oldheads and repo[h].closesbranch():
       
   375                         dh -= 1
       
   376             htext = ""
       
   377             if dh:
       
   378                 htext = _(" (%+d heads)") % dh
       
   379 
       
   380             repo.ui.status(_("added %d changesets"
       
   381                              " with %d changes to %d files%s\n")
       
   382                              % (changesets, revisions, files, htext))
       
   383             repo.invalidatevolatilesets()
       
   384 
       
   385             if changesets > 0:
       
   386                 p = lambda: tr.writepending() and repo.root or ""
       
   387                 if 'node' not in tr.hookargs:
       
   388                     tr.hookargs['node'] = hex(cl.node(clstart))
       
   389                     hookargs = dict(tr.hookargs)
       
   390                 else:
       
   391                     hookargs = dict(tr.hookargs)
       
   392                     hookargs['node'] = hex(cl.node(clstart))
       
   393                 repo.hook('pretxnchangegroup', throw=True, pending=p,
       
   394                           **hookargs)
       
   395 
       
   396             added = [cl.node(r) for r in xrange(clstart, clend)]
       
   397             publishing = repo.publishing()
       
   398             if srctype in ('push', 'serve'):
       
   399                 # Old servers can not push the boundary themselves.
       
   400                 # New servers won't push the boundary if changeset already
       
   401                 # exists locally as secret
       
   402                 #
       
   403                 # We should not use added here but the list of all change in
       
   404                 # the bundle
       
   405                 if publishing:
       
   406                     phases.advanceboundary(repo, tr, phases.public, srccontent)
       
   407                 else:
       
   408                     # Those changesets have been pushed from the outside, their
       
   409                     # phases are going to be pushed alongside. Therefor
       
   410                     # `targetphase` is ignored.
       
   411                     phases.advanceboundary(repo, tr, phases.draft, srccontent)
       
   412                     phases.retractboundary(repo, tr, phases.draft, added)
       
   413             elif srctype != 'strip':
       
   414                 # publishing only alter behavior during push
       
   415                 #
       
   416                 # strip should not touch boundary at all
       
   417                 phases.retractboundary(repo, tr, targetphase, added)
       
   418 
       
   419             if changesets > 0:
       
   420                 if srctype != 'strip':
       
   421                     # During strip, branchcache is invalid but coming call to
       
   422                     # `destroyed` will repair it.
       
   423                     # In other case we can safely update cache on disk.
       
   424                     branchmap.updatecache(repo.filtered('served'))
       
   425 
       
   426                 def runhooks():
       
   427                     # These hooks run when the lock releases, not when the
       
   428                     # transaction closes. So it's possible for the changelog
       
   429                     # to have changed since we last saw it.
       
   430                     if clstart >= len(repo):
       
   431                         return
       
   432 
       
   433                     # forcefully update the on-disk branch cache
       
   434                     repo.ui.debug("updating the branch cache\n")
       
   435                     repo.hook("changegroup", **hookargs)
       
   436 
       
   437                     for n in added:
       
   438                         args = hookargs.copy()
       
   439                         args['node'] = hex(n)
       
   440                         repo.hook("incoming", **args)
       
   441 
       
   442                     newheads = [h for h in repo.heads() if h not in oldheads]
       
   443                     repo.ui.log("incoming",
       
   444                                 "%s incoming changes - new heads: %s\n",
       
   445                                 len(added),
       
   446                                 ', '.join([hex(c[:6]) for c in newheads]))
       
   447 
       
   448                 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
       
   449                                 lambda tr: repo._afterlock(runhooks))
       
   450 
       
   451             tr.close()
       
   452 
       
   453         finally:
       
   454             tr.release()
       
   455             repo.ui.flush()
       
   456         # never return 0 here:
       
   457         if dh < 0:
       
   458             return dh - 1
       
   459         else:
       
   460             return dh + 1
   269 
   461 
   270 class cg2unpacker(cg1unpacker):
   462 class cg2unpacker(cg1unpacker):
   271     deltaheader = _CHANGEGROUPV2_DELTA_HEADER
   463     deltaheader = _CHANGEGROUPV2_DELTA_HEADER
   272     deltaheadersize = struct.calcsize(deltaheader)
   464     deltaheadersize = struct.calcsize(deltaheader)
   273     version = '02'
   465     version = '02'
   718 
   910 
   719     return revisions, files
   911     return revisions, files
   720 
   912 
   721 def addchangegroup(repo, source, srctype, url, emptyok=False,
   913 def addchangegroup(repo, source, srctype, url, emptyok=False,
   722                    targetphase=phases.draft, expectedtotal=None):
   914                    targetphase=phases.draft, expectedtotal=None):
   723     """Add the changegroup returned by source.read() to this repo.
   915     """Legacy forwarding method to cg?unpacker.apply() to be removed soon."""
   724     srctype is a string like 'push', 'pull', or 'unbundle'.  url is
       
   725     the URL of the repo where this changegroup is coming from.
       
   726 
       
   727     Return an integer summarizing the change to this repo:
       
   728     - nothing changed or no source: 0
       
   729     - more heads than before: 1+added heads (2..n)
       
   730     - fewer heads than before: -1-removed heads (-2..-n)
       
   731     - number of heads stays the same: 1
       
   732     """
       
   733     if not source:
   916     if not source:
   734         return 0
   917         return 0
   735 
   918 
   736     repo = repo.unfiltered()
   919     return source.apply(repo, srctype, url, emptyok=emptyok,
   737     def csmap(x):
   920                         targetphase=targetphase, expectedtotal=expectedtotal)
   738         repo.ui.debug("add changeset %s\n" % short(x))
       
   739         return len(cl)
       
   740 
       
   741     def revmap(x):
       
   742         return cl.rev(x)
       
   743 
       
   744     changesets = files = revisions = 0
       
   745 
       
   746     tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
       
   747     # The transaction could have been created before and already carries source
       
   748     # information. In this case we use the top level data. We overwrite the
       
   749     # argument because we need to use the top level value (if they exist) in
       
   750     # this function.
       
   751     srctype = tr.hookargs.setdefault('source', srctype)
       
   752     url = tr.hookargs.setdefault('url', url)
       
   753 
       
   754     # write changelog data to temp files so concurrent readers will not see
       
   755     # inconsistent view
       
   756     cl = repo.changelog
       
   757     cl.delayupdate(tr)
       
   758     oldheads = cl.heads()
       
   759     try:
       
   760         repo.hook('prechangegroup', throw=True, **tr.hookargs)
       
   761 
       
   762         trp = weakref.proxy(tr)
       
   763         # pull off the changeset group
       
   764         repo.ui.status(_("adding changesets\n"))
       
   765         clstart = len(cl)
       
   766         class prog(object):
       
   767             def __init__(self, step, total):
       
   768                 self._step = step
       
   769                 self._total = total
       
   770                 self._count = 1
       
   771             def __call__(self):
       
   772                 repo.ui.progress(self._step, self._count, unit=_('chunks'),
       
   773                                  total=self._total)
       
   774                 self._count += 1
       
   775         source.callback = prog(_('changesets'), expectedtotal)
       
   776 
       
   777         efiles = set()
       
   778         def onchangelog(cl, node):
       
   779             efiles.update(cl.read(node)[3])
       
   780 
       
   781         source.changelogheader()
       
   782         srccontent = cl.addgroup(source, csmap, trp,
       
   783                                  addrevisioncb=onchangelog)
       
   784         efiles = len(efiles)
       
   785 
       
   786         if not (srccontent or emptyok):
       
   787             raise error.Abort(_("received changelog group is empty"))
       
   788         clend = len(cl)
       
   789         changesets = clend - clstart
       
   790         repo.ui.progress(_('changesets'), None)
       
   791 
       
   792         # pull off the manifest group
       
   793         repo.ui.status(_("adding manifests\n"))
       
   794         # manifests <= changesets
       
   795         source.callback = prog(_('manifests'), changesets)
       
   796         # no need to check for empty manifest group here:
       
   797         # if the result of the merge of 1 and 2 is the same in 3 and 4,
       
   798         # no new manifest will be created and the manifest group will
       
   799         # be empty during the pull
       
   800         source.manifestheader()
       
   801         repo.manifest.addgroup(source, revmap, trp)
       
   802         repo.ui.progress(_('manifests'), None)
       
   803 
       
   804         needfiles = {}
       
   805         if repo.ui.configbool('server', 'validate', default=False):
       
   806             # validate incoming csets have their manifests
       
   807             for cset in xrange(clstart, clend):
       
   808                 mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
       
   809                 mfest = repo.manifest.readdelta(mfnode)
       
   810                 # store file nodes we must see
       
   811                 for f, n in mfest.iteritems():
       
   812                     needfiles.setdefault(f, set()).add(n)
       
   813 
       
   814         # process the files
       
   815         repo.ui.status(_("adding file changes\n"))
       
   816         source.callback = None
       
   817         pr = prog(_('files'), efiles)
       
   818         newrevs, newfiles = addchangegroupfiles(repo, source, revmap, trp, pr,
       
   819                                                 needfiles)
       
   820         revisions += newrevs
       
   821         files += newfiles
       
   822 
       
   823         dh = 0
       
   824         if oldheads:
       
   825             heads = cl.heads()
       
   826             dh = len(heads) - len(oldheads)
       
   827             for h in heads:
       
   828                 if h not in oldheads and repo[h].closesbranch():
       
   829                     dh -= 1
       
   830         htext = ""
       
   831         if dh:
       
   832             htext = _(" (%+d heads)") % dh
       
   833 
       
   834         repo.ui.status(_("added %d changesets"
       
   835                          " with %d changes to %d files%s\n")
       
   836                          % (changesets, revisions, files, htext))
       
   837         repo.invalidatevolatilesets()
       
   838 
       
   839         if changesets > 0:
       
   840             p = lambda: tr.writepending() and repo.root or ""
       
   841             if 'node' not in tr.hookargs:
       
   842                 tr.hookargs['node'] = hex(cl.node(clstart))
       
   843                 hookargs = dict(tr.hookargs)
       
   844             else:
       
   845                 hookargs = dict(tr.hookargs)
       
   846                 hookargs['node'] = hex(cl.node(clstart))
       
   847             repo.hook('pretxnchangegroup', throw=True, pending=p, **hookargs)
       
   848 
       
   849         added = [cl.node(r) for r in xrange(clstart, clend)]
       
   850         publishing = repo.publishing()
       
   851         if srctype in ('push', 'serve'):
       
   852             # Old servers can not push the boundary themselves.
       
   853             # New servers won't push the boundary if changeset already
       
   854             # exists locally as secret
       
   855             #
       
   856             # We should not use added here but the list of all change in
       
   857             # the bundle
       
   858             if publishing:
       
   859                 phases.advanceboundary(repo, tr, phases.public, srccontent)
       
   860             else:
       
   861                 # Those changesets have been pushed from the outside, their
       
   862                 # phases are going to be pushed alongside. Therefor
       
   863                 # `targetphase` is ignored.
       
   864                 phases.advanceboundary(repo, tr, phases.draft, srccontent)
       
   865                 phases.retractboundary(repo, tr, phases.draft, added)
       
   866         elif srctype != 'strip':
       
   867             # publishing only alter behavior during push
       
   868             #
       
   869             # strip should not touch boundary at all
       
   870             phases.retractboundary(repo, tr, targetphase, added)
       
   871 
       
   872         if changesets > 0:
       
   873             if srctype != 'strip':
       
   874                 # During strip, branchcache is invalid but coming call to
       
   875                 # `destroyed` will repair it.
       
   876                 # In other case we can safely update cache on disk.
       
   877                 branchmap.updatecache(repo.filtered('served'))
       
   878 
       
   879             def runhooks():
       
   880                 # These hooks run when the lock releases, not when the
       
   881                 # transaction closes. So it's possible for the changelog
       
   882                 # to have changed since we last saw it.
       
   883                 if clstart >= len(repo):
       
   884                     return
       
   885 
       
   886                 # forcefully update the on-disk branch cache
       
   887                 repo.ui.debug("updating the branch cache\n")
       
   888                 repo.hook("changegroup", **hookargs)
       
   889 
       
   890                 for n in added:
       
   891                     args = hookargs.copy()
       
   892                     args['node'] = hex(n)
       
   893                     repo.hook("incoming", **args)
       
   894 
       
   895                 newheads = [h for h in repo.heads() if h not in oldheads]
       
   896                 repo.ui.log("incoming",
       
   897                             "%s incoming changes - new heads: %s\n",
       
   898                             len(added),
       
   899                             ', '.join([hex(c[:6]) for c in newheads]))
       
   900 
       
   901             tr.addpostclose('changegroup-runhooks-%020i' % clstart,
       
   902                             lambda tr: repo._afterlock(runhooks))
       
   903 
       
   904         tr.close()
       
   905 
       
   906     finally:
       
   907         tr.release()
       
   908         repo.ui.flush()
       
   909     # never return 0 here:
       
   910     if dh < 0:
       
   911         return dh - 1
       
   912     else:
       
   913         return dh + 1