[PATCH 1 of 1] unionrepo: read-only operations on a union of two localrepos
Mads Kiilerich
mads at kiilerich.com
Fri Feb 1 08:52:37 CST 2013
# HG changeset patch
# User Mads Kiilerich <madski at unity3d.com>
# Date 1358520849 -3600
# Branch stable
# Node ID 4570a9ddd85c4f74454a6e82291a86c0b352c47f
# Parent 9fbeb61b8ad26eea6ed6983724ac49d7b874892d
unionrepo: read-only operations on a union of two localrepos
- just like bundlerepo without bundles.
Primary use case is revlogs and diffs across local repos, as a kind of preview
of pulls.
diff --git a/mercurial/hg.py b/mercurial/hg.py
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -9,8 +9,8 @@
from i18n import _
from lock import release
from node import hex, nullid
-import localrepo, bundlerepo, httppeer, sshpeer, statichttprepo, bookmarks
-import lock, util, extensions, error, node, scmutil, phases, url
+import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
+import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
import cmdutil, discovery
import merge as mergemod
import verify as verifymod
@@ -64,6 +64,7 @@
schemes = {
'bundle': bundlerepo,
+ 'union': unionrepo,
'file': _local,
'http': httppeer,
'https': httppeer,
diff --git a/mercurial/unionrepo.py b/mercurial/unionrepo.py
new file mode 100644
--- /dev/null
+++ b/mercurial/unionrepo.py
@@ -0,0 +1,183 @@
+# unionrepo.py - repository class for viewing union of repositories
+#
+# Derived from bundlerepo.py
+# Copyright 2006, 2007 Benoit Boissinot <bboissin at gmail.com>
+# Copyright 2013 Unity Technologies, Mads Kiilerich <madski at unity3d.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+"""Repository class for "in-memory pull" of one local repository to another,
+allowing operations like log with revsets.
+"""
+
+from node import nullid
+import util, mdiff, scmutil
+import localrepo, changelog, manifest, filelog, revlog
+
+class unionrevlog(revlog.revlog):
+ def __init__(self, opener, indexfile, revlog2, linkmapper):
+ # How it works:
+ # To retrieve a revision, we just need to know the node id so we can
+ # look it up in revlog2.
+ #
+ # basemap is indexed with revisions coming from the second revlog.
+ #
+ # To differentiate a rev in the second revlog from a rev in the revlog,
+ # we check revision against basemap.
+ opener = scmutil.readonlyvfs(opener)
+ revlog.revlog.__init__(self, opener, indexfile)
+ self.revlog2 = revlog2
+
+ self.basemap = {} # mapping rev that is in revlog2 to ... nothing
+ n = len(self)
+ self.bundlerevs = set() # used by 'bundle()' revset expression
+ for rev2 in self.revlog2:
+ rev = self.revlog2.index[rev2]
+ # rev numbers - in revlog2, very different from self.rev
+ _start, _csize, _rsize, _base, linkrev, p1rev, p2rev, node = rev
+
+ if linkmapper is None: # link is to same revlog
+ assert linkrev == rev2 # we never link back
+ link = n
+ else: # rev must be mapped from repo2 cl to unified cl by linkmapper
+ link = linkmapper(linkrev)
+
+ if node in self.nodemap:
+ # this happens for for the common revlog revisions
+ self.bundlerevs.add(self.nodemap[node])
+ continue
+
+ p1node = self.revlog2.node(p1rev)
+ p2node = self.revlog2.node(p2rev)
+
+ e = (None, None, None, None,
+ link, self.rev(p1node), self.rev(p2node), node)
+ self.basemap[n] = None
+ self.index.insert(-1, e)
+ self.nodemap[node] = n
+ self.bundlerevs.add(n)
+ n += 1
+
+ def _chunk(self, rev):
+ if rev not in self.basemap:
+ return revlog.revlog._chunk(self, rev)
+ return self.revlog2._chunk(self.node(rev))
+
+ def revdiff(self, rev1, rev2):
+ """return or calculate a delta between two revisions"""
+ if rev1 in self.basemap and rev2 in self.basemap:
+ return self.revlog2.revdiff(
+ self.revlog2.rev(self.node(rev1)),
+ self.revlog2.rev(self.node(rev2)))
+ elif rev1 not in self.basemap and rev2 not in self.basemap:
+ return revlog.revlog.revdiff(self, rev1, rev2)
+
+ return mdiff.textdiff(self.revision(self.node(rev1)),
+ self.revision(self.node(rev2)))
+
+ def revision(self, nodeorrev):
+ """return an uncompressed revision of a given node or revision
+ number.
+ """
+ if isinstance(nodeorrev, int):
+ rev = nodeorrev
+ node = self.node(rev)
+ else:
+ node = nodeorrev
+ rev = self.rev(node)
+
+ if node == nullid:
+ return ""
+
+ if rev in self.basemap:
+ text = self.revlog2.revision(node)
+ self._cache = (node, rev, text)
+ else:
+ text = revlog.revlog.revision(self, rev)
+ # already cached
+ return text
+
+ def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
+ raise NotImplementedError
+ def addgroup(self, revs, linkmapper, transaction):
+ raise NotImplementedError
+ def strip(self, rev, minlink):
+ raise NotImplementedError
+ def checksize(self):
+ raise NotImplementedError
+
+class unionchangelog(unionrevlog, changelog.changelog):
+ def __init__(self, opener, opener2):
+ changelog.changelog.__init__(self, opener)
+ linkmapper = None
+ changelog2 = changelog.changelog(opener2)
+ unionrevlog.__init__(self, opener, self.indexfile, changelog2,
+ linkmapper)
+
+class unionmanifest(unionrevlog, manifest.manifest):
+ def __init__(self, opener, opener2, linkmapper):
+ manifest.manifest.__init__(self, opener)
+ manifest2 = manifest.manifest(opener2)
+ unionrevlog.__init__(self, opener, self.indexfile, manifest2,
+ linkmapper)
+
+class unionfilelog(unionrevlog, filelog.filelog):
+ def __init__(self, opener, path, opener2, linkmapper, repo):
+ filelog.filelog.__init__(self, opener, path)
+ filelog2 = filelog.filelog(opener2, path)
+ unionrevlog.__init__(self, opener, self.indexfile, filelog2,
+ linkmapper)
+ self._repo = repo
+
+ def _file(self, f):
+ self._repo.file(f)
+
+class unionpeer(localrepo.localpeer):
+ def canpush(self):
+ return False
+
+class unionrepository(localrepo.localrepository):
+ def __init__(self, ui, path, path2):
+ localrepo.localrepository.__init__(self, ui, path)
+ self.ui.setconfig('phases', 'publish', False)
+
+ self._url = 'union:%s+%s' % (util.expandpath(path),
+ util.expandpath(path2))
+ self.repo2 = localrepo.localrepository(ui, path2)
+
+ @localrepo.unfilteredpropertycache
+ def changelog(self):
+ return unionchangelog(self.sopener, self.repo2.sopener)
+
+ def _clrev(self, rev2):
+ """map from repo2 changelog rev to temporary rev in self.changelog"""
+ node = self.repo2.changelog.node(rev2)
+ return self.changelog.rev(node)
+
+ @localrepo.unfilteredpropertycache
+ def manifest(self):
+ return unionmanifest(self.sopener, self.repo2.sopener,
+ self._clrev)
+
+ def url(self):
+ return self._url
+
+ def file(self, f):
+ return unionfilelog(self.sopener, f, self.repo2.sopener,
+ self._clrev, self)
+
+ def close(self):
+ self.repo2.close()
+
+ def cancopy(self):
+ return False
+
+ def peer(self):
+ return unionpeer(self)
+
+def instance(ui, path, create):
+ u = util.url(path)
+ assert u.scheme == 'union'
+ repopath, repopath2 = u.path.split("+", 1)
+ return unionrepository(ui, repopath, repopath2)
diff --git a/tests/test-bundle-simple.t b/tests/test-bundle-simple.t
new file mode 100644
--- /dev/null
+++ b/tests/test-bundle-simple.t
@@ -0,0 +1,127 @@
+# sed 's,bundle:repo1+repo2[.]hg,union:repo1+repo2,g' test-bundle-simple.t > test-union-simple.t
+
+ $ hg init repo1
+ $ cd repo1
+ $ touch repo1-0
+ $ echo repo1-0 > f
+ $ hg ci -Aqmrepo1-0
+ $ touch repo1-1
+ $ echo repo1-1 >> f
+ $ hg ci -Aqmrepo1-1
+ $ touch repo1-2
+ $ echo repo1-2 >> f
+ $ hg ci -Aqmrepo1-2
+ $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
+ 2:68c0685446a3 repo1-2
+ 1:8a58db72e69d repo1-1
+ 0:f093fec0529b repo1-0
+ $ tip1=`hg id -q`
+ $ cd ..
+
+ $ hg clone -q repo1 --rev 0 repo2
+ $ cd repo2
+ $ touch repo2-1
+ $ sed '1irepo2-1 at top' f > f.tmp
+ $ mv f.tmp f
+ $ hg ci -Aqmrepo2-1
+ $ touch repo2-2
+ $ hg pull -q ../repo1 -r 1
+ $ hg merge -q
+ $ hg ci -Aqmrepo2-2-merge
+ $ touch repo2-3
+ $ echo repo2-3 >> f
+ $ hg ci -mrepo2-3
+ $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
+ 4:2f0d178c469c repo2-3
+ 3:9e6fb3e0b9da repo2-2-merge
+ 2:8a58db72e69d repo1-1
+ 1:c337dba826e7 repo2-1
+ 0:f093fec0529b repo1-0
+ $ cd ..
+
+ $ hg -R repo2 bundle --all repo2.hg
+ 5 changesets found
+
+ $ hg -R bundle:repo1+repo2.hg log --template '{rev}:{node|short} {desc|firstline}\n' --traceback
+ 5:2f0d178c469c repo2-3
+ 4:9e6fb3e0b9da repo2-2-merge
+ 3:c337dba826e7 repo2-1
+ 2:68c0685446a3 repo1-2
+ 1:8a58db72e69d repo1-1
+ 0:f093fec0529b repo1-0
+
+ $ hg -R bundle:repo1+repo2.hg mani -r $tip1 --traceback
+ f
+ repo1-0
+ repo1-1
+ repo1-2
+ $ hg -R bundle:repo1+repo2.hg mani -r 4 --traceback
+ f
+ repo1-0
+ repo1-1
+ repo2-1
+ repo2-2
+
+ $ hg -R repo1 cat repo1/f -r2
+ repo1-0
+ repo1-1
+ repo1-2
+
+ $ hg -R bundle:repo1+repo2.hg cat -r$tip1 repo1/f --traceback
+ repo1-0
+ repo1-1
+ repo1-2
+
+ $ hg -R bundle:repo1+repo2.hg cat -r4 $TESTTMP/repo1/f
+ repo2-1 at top
+ repo1-0
+ repo1-1
+
+ $ hg -R bundle:repo1+repo2.hg diff -r$tip1 -rtip
+ diff -r 68c0685446a3 -r 2f0d178c469c f
+ --- a/f Thu Jan 01 00:00:00 1970 +0000
+ +++ b/f Thu Jan 01 00:00:00 1970 +0000
+ @@ -1,3 +1,4 @@
+ +repo2-1 at top
+ repo1-0
+ repo1-1
+ -repo1-2
+ +repo2-3
+
+ $ hg -R bundle:repo1+repo2.hg heads --template '{rev}:{node|short} {desc|firstline}\n'
+ 5:2f0d178c469c repo2-3
+ 2:68c0685446a3 repo1-2
+ $ hg -R bundle:repo1+repo2.hg id -r "ancestor($tip1, 5)"
+ 8a58db72e69d
+
+ $ hg -R bundle:repo1+repo2.hg annotate $TESTTMP/repo1/f -r tip --traceback
+ 3: repo2-1 at top
+ 0: repo1-0
+ 1: repo1-1
+ 5: repo2-3
+
+ $ hg clone -U bundle:repo1+repo2.hg repo3 --traceback
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 6 changesets with 11 changes to 6 files (+1 heads)
+
+ $ hg -R repo3 verify
+ checking changesets
+ checking manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 6 files, 6 changesets, 11 total revisions
+
+ $ hg -R repo3 heads --template '{rev}:{node|short} {desc|firstline}\n'
+ 5:2f0d178c469c repo2-3
+ 2:68c0685446a3 repo1-2
+
+ $ hg -R repo3 log --template '{rev}:{node|short} {desc|firstline}\n'
+ 5:2f0d178c469c repo2-3
+ 4:9e6fb3e0b9da repo2-2-merge
+ 3:c337dba826e7 repo2-1
+ 2:68c0685446a3 repo1-2
+ 1:8a58db72e69d repo1-1
+ 0:f093fec0529b repo1-0
diff --git a/tests/test-union-simple.t b/tests/test-union-simple.t
new file mode 100644
--- /dev/null
+++ b/tests/test-union-simple.t
@@ -0,0 +1,127 @@
+# sed 's,bundle:repo1+repo2[.]hg,union:repo1+repo2,g' test-bundle-simple.t > test-union-simple.t
+
+ $ hg init repo1
+ $ cd repo1
+ $ touch repo1-0
+ $ echo repo1-0 > f
+ $ hg ci -Aqmrepo1-0
+ $ touch repo1-1
+ $ echo repo1-1 >> f
+ $ hg ci -Aqmrepo1-1
+ $ touch repo1-2
+ $ echo repo1-2 >> f
+ $ hg ci -Aqmrepo1-2
+ $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
+ 2:68c0685446a3 repo1-2
+ 1:8a58db72e69d repo1-1
+ 0:f093fec0529b repo1-0
+ $ tip1=`hg id -q`
+ $ cd ..
+
+ $ hg clone -q repo1 --rev 0 repo2
+ $ cd repo2
+ $ touch repo2-1
+ $ sed '1irepo2-1 at top' f > f.tmp
+ $ mv f.tmp f
+ $ hg ci -Aqmrepo2-1
+ $ touch repo2-2
+ $ hg pull -q ../repo1 -r 1
+ $ hg merge -q
+ $ hg ci -Aqmrepo2-2-merge
+ $ touch repo2-3
+ $ echo repo2-3 >> f
+ $ hg ci -mrepo2-3
+ $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
+ 4:2f0d178c469c repo2-3
+ 3:9e6fb3e0b9da repo2-2-merge
+ 2:8a58db72e69d repo1-1
+ 1:c337dba826e7 repo2-1
+ 0:f093fec0529b repo1-0
+ $ cd ..
+
+ $ hg -R repo2 bundle --all repo2.hg
+ 5 changesets found
+
+ $ hg -R union:repo1+repo2 log --template '{rev}:{node|short} {desc|firstline}\n' --traceback
+ 5:2f0d178c469c repo2-3
+ 4:9e6fb3e0b9da repo2-2-merge
+ 3:c337dba826e7 repo2-1
+ 2:68c0685446a3 repo1-2
+ 1:8a58db72e69d repo1-1
+ 0:f093fec0529b repo1-0
+
+ $ hg -R union:repo1+repo2 mani -r $tip1 --traceback
+ f
+ repo1-0
+ repo1-1
+ repo1-2
+ $ hg -R union:repo1+repo2 mani -r 4 --traceback
+ f
+ repo1-0
+ repo1-1
+ repo2-1
+ repo2-2
+
+ $ hg -R repo1 cat repo1/f -r2
+ repo1-0
+ repo1-1
+ repo1-2
+
+ $ hg -R union:repo1+repo2 cat -r$tip1 repo1/f --traceback
+ repo1-0
+ repo1-1
+ repo1-2
+
+ $ hg -R union:repo1+repo2 cat -r4 $TESTTMP/repo1/f
+ repo2-1 at top
+ repo1-0
+ repo1-1
+
+ $ hg -R union:repo1+repo2 diff -r$tip1 -rtip
+ diff -r 68c0685446a3 -r 2f0d178c469c f
+ --- a/f Thu Jan 01 00:00:00 1970 +0000
+ +++ b/f Thu Jan 01 00:00:00 1970 +0000
+ @@ -1,3 +1,4 @@
+ +repo2-1 at top
+ repo1-0
+ repo1-1
+ -repo1-2
+ +repo2-3
+
+ $ hg -R union:repo1+repo2 heads --template '{rev}:{node|short} {desc|firstline}\n'
+ 5:2f0d178c469c repo2-3
+ 2:68c0685446a3 repo1-2
+ $ hg -R union:repo1+repo2 id -r "ancestor($tip1, 5)"
+ 8a58db72e69d
+
+ $ hg -R union:repo1+repo2 annotate $TESTTMP/repo1/f -r tip --traceback
+ 3: repo2-1 at top
+ 0: repo1-0
+ 1: repo1-1
+ 5: repo2-3
+
+ $ hg clone -U union:repo1+repo2 repo3 --traceback
+ requesting all changes
+ adding changesets
+ adding manifests
+ adding file changes
+ added 6 changesets with 11 changes to 6 files (+1 heads)
+
+ $ hg -R repo3 verify
+ checking changesets
+ checking manifests
+ crosschecking files in changesets and manifests
+ checking files
+ 6 files, 6 changesets, 11 total revisions
+
+ $ hg -R repo3 heads --template '{rev}:{node|short} {desc|firstline}\n'
+ 5:2f0d178c469c repo2-3
+ 2:68c0685446a3 repo1-2
+
+ $ hg -R repo3 log --template '{rev}:{node|short} {desc|firstline}\n'
+ 5:2f0d178c469c repo2-3
+ 4:9e6fb3e0b9da repo2-2-merge
+ 3:c337dba826e7 repo2-1
+ 2:68c0685446a3 repo1-2
+ 1:8a58db72e69d repo1-1
+ 0:f093fec0529b repo1-0
More information about the Mercurial-devel
mailing list