diff --git a/hgext/shelve.py b/hgext/shelve.py --- a/hgext/shelve.py +++ b/hgext/shelve.py @@ -561,7 +561,8 @@ def unshelve(ui, repo, *shelved, **opts) ui.quiet = True fp = shelvedfile(repo, basename, 'hg').opener() gen = changegroup.readbundle(fp, fp.name) - repo.addchangegroup(gen, 'unshelve', 'bundle:' + fp.name) + changegroup.addchangegroup(repo, gen, 'unshelve', + 'bundle:' + fp.name) nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)] phases.retractboundary(repo, phases.secret, nodes) finally: diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py --- a/mercurial/changegroup.py +++ b/mercurial/changegroup.py @@ -5,11 +5,12 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +import weakref from i18n import _ -from node import nullrev, nullid, hex +from node import nullrev, nullid, hex, short import mdiff, util, dagutil import struct, os, bz2, zlib, tempfile -import discovery, error +import discovery, error, phases, branchmap _BUNDLE10_DELTA_HEADER = "20s20s20s20s" @@ -554,3 +555,184 @@ def addchangegroupfiles(repo, source, re (f, hex(n))) return revisions, files + +def addchangegroup(repo, source, srctype, url, emptyok=False): + """Add the changegroup returned by source.read() to this repo. + srctype is a string like 'push', 'pull', or 'unbundle'. url is + the URL of the repo where this changegroup is coming from. + + Return an integer summarizing the change to this repo: + - nothing changed or no source: 0 + - more heads than before: 1+added heads (2..n) + - fewer heads than before: -1-removed heads (-2..-n) + - number of heads stays the same: 1 + """ + repo = repo.unfiltered() + def csmap(x): + repo.ui.debug("add changeset %s\n" % short(x)) + return len(cl) + + def revmap(x): + return cl.rev(x) + + if not source: + return 0 + + repo.hook('prechangegroup', throw=True, source=srctype, url=url) + + changesets = files = revisions = 0 + efiles = set() + + # write changelog data to temp files so concurrent readers will not see + # inconsistent view + cl = repo.changelog + cl.delayupdate() + oldheads = cl.heads() + + tr = repo.transaction("\n".join([srctype, util.hidepassword(url)])) + try: + trp = weakref.proxy(tr) + # pull off the changeset group + repo.ui.status(_("adding changesets\n")) + clstart = len(cl) + class prog(object): + step = _('changesets') + count = 1 + ui = repo.ui + total = None + def __call__(repo): + repo.ui.progress(repo.step, repo.count, unit=_('chunks'), + total=repo.total) + repo.count += 1 + pr = prog() + source.callback = pr + + source.changelogheader() + srccontent = cl.addgroup(source, csmap, trp) + if not (srccontent or emptyok): + raise util.Abort(_("received changelog group is empty")) + clend = len(cl) + changesets = clend - clstart + for c in xrange(clstart, clend): + efiles.update(repo[c].files()) + efiles = len(efiles) + repo.ui.progress(_('changesets'), None) + + # pull off the manifest group + repo.ui.status(_("adding manifests\n")) + pr.step = _('manifests') + pr.count = 1 + pr.total = changesets # manifests <= changesets + # no need to check for empty manifest group here: + # if the result of the merge of 1 and 2 is the same in 3 and 4, + # no new manifest will be created and the manifest group will + # be empty during the pull + source.manifestheader() + repo.manifest.addgroup(source, revmap, trp) + repo.ui.progress(_('manifests'), None) + + needfiles = {} + if repo.ui.configbool('server', 'validate', default=False): + # validate incoming csets have their manifests + for cset in xrange(clstart, clend): + mfest = repo.changelog.read(repo.changelog.node(cset))[0] + mfest = repo.manifest.readdelta(mfest) + # store file nodes we must see + for f, n in mfest.iteritems(): + needfiles.setdefault(f, set()).add(n) + + # process the files + repo.ui.status(_("adding file changes\n")) + pr.step = _('files') + pr.count = 1 + pr.total = efiles + source.callback = None + + newrevs, newfiles = addchangegroupfiles(repo, source, revmap, trp, pr, + needfiles) + revisions += newrevs + files += newfiles + + dh = 0 + if oldheads: + heads = cl.heads() + dh = len(heads) - len(oldheads) + for h in heads: + if h not in oldheads and repo[h].closesbranch(): + dh -= 1 + htext = "" + if dh: + htext = _(" (%+d heads)") % dh + + repo.ui.status(_("added %d changesets" + " with %d changes to %d files%s\n") + % (changesets, revisions, files, htext)) + repo.invalidatevolatilesets() + + if changesets > 0: + p = lambda: cl.writepending() and repo.root or "" + repo.hook('pretxnchangegroup', throw=True, + node=hex(cl.node(clstart)), source=srctype, + url=url, pending=p) + + added = [cl.node(r) for r in xrange(clstart, clend)] + publishing = repo.ui.configbool('phases', 'publish', True) + if srctype == 'push': + # Old servers can not push the boundary themselves. + # New servers won't push the boundary if changeset already + # exists locally as secret + # + # We should not use added here but the list of all change in + # the bundle + if publishing: + phases.advanceboundary(repo, phases.public, srccontent) + else: + phases.advanceboundary(repo, phases.draft, srccontent) + phases.retractboundary(repo, phases.draft, added) + elif srctype != 'strip': + # publishing only alter behavior during push + # + # strip should not touch boundary at all + phases.retractboundary(repo, phases.draft, added) + + # make changelog see real files again + cl.finalize(trp) + + tr.close() + + if changesets > 0: + if srctype != 'strip': + # During strip, branchcache is invalid but coming call to + # `destroyed` will repair it. + # In other case we can safely update cache on disk. + branchmap.updatecache(repo.filtered('served')) + def runhooks(): + # These hooks run when the lock releases, not when the + # transaction closes. So it's possible for the changelog + # to have changed since we last saw it. + if clstart >= len(repo): + return + + # forcefully update the on-disk branch cache + repo.ui.debug("updating the branch cache\n") + repo.hook("changegroup", node=hex(cl.node(clstart)), + source=srctype, url=url) + + for n in added: + repo.hook("incoming", node=hex(n), source=srctype, + url=url) + + newheads = [h for h in repo.heads() if h not in oldheads] + repo.ui.log("incoming", + "%s incoming changes - new heads: %s\n", + len(added), + ', '.join([hex(c[:6]) for c in newheads])) + repo._afterlock(runhooks) + + finally: + tr.release() + # never return 0 here: + if dh < 0: + return dh - 1 + else: + return dh + 1 diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -5780,7 +5780,8 @@ def unbundle(ui, repo, fname1, *fnames, for fname in fnames: f = hg.openpath(ui, fname) gen = changegroup.readbundle(f, fname) - modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname) + modheads = changegroup.addchangegroup(repo, gen, 'unbundle', + 'bundle:' + fname) finally: lock.release() bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch()) diff --git a/mercurial/exchange.py b/mercurial/exchange.py --- a/mercurial/exchange.py +++ b/mercurial/exchange.py @@ -508,7 +508,7 @@ def _pullchangeset(pullop): "changegroupsubset.")) else: cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull') - pullop.cgresult = pullop.repo.addchangegroup(cg, 'pull', + pullop.cgresult = changegroup.addchangegroup(pullop.repo, cg, 'pull', pullop.remote.url()) def _pullphase(pullop): diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -114,7 +114,7 @@ class localpeer(peer.peerrepository): return self._repo.lock() def addchangegroup(self, cg, source, url): - return self._repo.addchangegroup(cg, source, url) + return changegroup.addchangegroup(self._repo, cg, source, url) def pushkey(self, namespace, key, old, new): return self._repo.pushkey(namespace, key, old, new) @@ -1683,192 +1683,6 @@ class localrepository(object): def push(self, remote, force=False, revs=None, newbranch=False): return exchange.push(self, remote, force, revs, newbranch) - @unfilteredmethod - def addchangegroup(self, source, srctype, url, emptyok=False): - """Add the changegroup returned by source.read() to this repo. - srctype is a string like 'push', 'pull', or 'unbundle'. url is - the URL of the repo where this changegroup is coming from. - - Return an integer summarizing the change to this repo: - - nothing changed or no source: 0 - - more heads than before: 1+added heads (2..n) - - fewer heads than before: -1-removed heads (-2..-n) - - number of heads stays the same: 1 - """ - def csmap(x): - self.ui.debug("add changeset %s\n" % short(x)) - return len(cl) - - def revmap(x): - return cl.rev(x) - - if not source: - return 0 - - self.hook('prechangegroup', throw=True, source=srctype, url=url) - - changesets = files = revisions = 0 - efiles = set() - - # write changelog data to temp files so concurrent readers will not see - # inconsistent view - cl = self.changelog - cl.delayupdate() - oldheads = cl.heads() - - tr = self.transaction("\n".join([srctype, util.hidepassword(url)])) - try: - trp = weakref.proxy(tr) - # pull off the changeset group - self.ui.status(_("adding changesets\n")) - clstart = len(cl) - class prog(object): - step = _('changesets') - count = 1 - ui = self.ui - total = None - def __call__(self): - self.ui.progress(self.step, self.count, unit=_('chunks'), - total=self.total) - self.count += 1 - pr = prog() - source.callback = pr - - source.changelogheader() - srccontent = cl.addgroup(source, csmap, trp) - if not (srccontent or emptyok): - raise util.Abort(_("received changelog group is empty")) - clend = len(cl) - changesets = clend - clstart - for c in xrange(clstart, clend): - efiles.update(self[c].files()) - efiles = len(efiles) - self.ui.progress(_('changesets'), None) - - # pull off the manifest group - self.ui.status(_("adding manifests\n")) - pr.step = _('manifests') - pr.count = 1 - pr.total = changesets # manifests <= changesets - # no need to check for empty manifest group here: - # if the result of the merge of 1 and 2 is the same in 3 and 4, - # no new manifest will be created and the manifest group will - # be empty during the pull - source.manifestheader() - self.manifest.addgroup(source, revmap, trp) - self.ui.progress(_('manifests'), None) - - needfiles = {} - if self.ui.configbool('server', 'validate', default=False): - # validate incoming csets have their manifests - for cset in xrange(clstart, clend): - mfest = self.changelog.read(self.changelog.node(cset))[0] - mfest = self.manifest.readdelta(mfest) - # store file nodes we must see - for f, n in mfest.iteritems(): - needfiles.setdefault(f, set()).add(n) - - # process the files - self.ui.status(_("adding file changes\n")) - pr.step = _('files') - pr.count = 1 - pr.total = efiles - source.callback = None - - newrevs, newfiles = changegroup.addchangegroupfiles(self, - source, - revmap, - trp, - pr, - needfiles) - revisions += newrevs - files += newfiles - - dh = 0 - if oldheads: - heads = cl.heads() - dh = len(heads) - len(oldheads) - for h in heads: - if h not in oldheads and self[h].closesbranch(): - dh -= 1 - htext = "" - if dh: - htext = _(" (%+d heads)") % dh - - self.ui.status(_("added %d changesets" - " with %d changes to %d files%s\n") - % (changesets, revisions, files, htext)) - self.invalidatevolatilesets() - - if changesets > 0: - p = lambda: cl.writepending() and self.root or "" - self.hook('pretxnchangegroup', throw=True, - node=hex(cl.node(clstart)), source=srctype, - url=url, pending=p) - - added = [cl.node(r) for r in xrange(clstart, clend)] - publishing = self.ui.configbool('phases', 'publish', True) - if srctype == 'push': - # Old servers can not push the boundary themselves. - # New servers won't push the boundary if changeset already - # exists locally as secret - # - # We should not use added here but the list of all change in - # the bundle - if publishing: - phases.advanceboundary(self, phases.public, srccontent) - else: - phases.advanceboundary(self, phases.draft, srccontent) - phases.retractboundary(self, phases.draft, added) - elif srctype != 'strip': - # publishing only alter behavior during push - # - # strip should not touch boundary at all - phases.retractboundary(self, phases.draft, added) - - # make changelog see real files again - cl.finalize(trp) - - tr.close() - - if changesets > 0: - if srctype != 'strip': - # During strip, branchcache is invalid but coming call to - # `destroyed` will repair it. - # In other case we can safely update cache on disk. - branchmap.updatecache(self.filtered('served')) - def runhooks(): - # These hooks run when the lock releases, not when the - # transaction closes. So it's possible for the changelog - # to have changed since we last saw it. - if clstart >= len(self): - return - - # forcefully update the on-disk branch cache - self.ui.debug("updating the branch cache\n") - self.hook("changegroup", node=hex(cl.node(clstart)), - source=srctype, url=url) - - for n in added: - self.hook("incoming", node=hex(n), source=srctype, - url=url) - - newheads = [h for h in self.heads() if h not in oldheads] - self.ui.log("incoming", - "%s incoming changes - new heads: %s\n", - len(added), - ', '.join([hex(c[:6]) for c in newheads])) - self._afterlock(runhooks) - - finally: - tr.release() - # never return 0 here: - if dh < 0: - return dh - 1 - else: - return dh + 1 - - def stream_in(self, remote, requirements): lock = self.lock() try: diff --git a/mercurial/repair.py b/mercurial/repair.py --- a/mercurial/repair.py +++ b/mercurial/repair.py @@ -148,7 +148,8 @@ def strip(ui, repo, nodelist, backup="al if not repo.ui.verbose: # silence internal shuffling chatter repo.ui.pushbuffer() - repo.addchangegroup(gen, 'strip', 'bundle:' + chgrpfile, True) + changegroup.addchangegroup(repo, gen, 'strip', + 'bundle:' + chgrpfile, True) if not repo.ui.verbose: repo.ui.popbuffer() f.close() diff --git a/mercurial/sshserver.py b/mercurial/sshserver.py --- a/mercurial/sshserver.py +++ b/mercurial/sshserver.py @@ -143,7 +143,7 @@ class sshserver(wireproto.abstractserver self.sendresponse("") cg = changegroup.unbundle10(self.fin, "UN") - r = self.repo.addchangegroup(cg, 'serve', self._client()) + r = changegroup.addchangegroup(self.repo, cg, 'serve', self._client()) self.lock.release() return str(r) diff --git a/mercurial/wireproto.py b/mercurial/wireproto.py --- a/mercurial/wireproto.py +++ b/mercurial/wireproto.py @@ -786,7 +786,8 @@ def unbundle(repo, proto, heads): gen = changegroupmod.readbundle(fp, None) try: - r = repo.addchangegroup(gen, 'serve', proto._client()) + r = changegroupmod.addchangegroup(repo, gen, 'serve', + proto._client()) except util.Abort, inst: sys.stderr.write("abort: %s\n" % inst) finally: