##// END OF EJS Templates
unionrepo: read-only operations on a union of two localrepos...
Mads Kiilerich -
r18944:a9c443b3 default
parent child Browse files
Show More
@@ -0,0 +1,208 b''
1 # unionrepo.py - repository class for viewing union of repository changesets
2 #
3 # Derived from bundlerepo.py
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
6 #
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
9
10 """Repository class for "in-memory pull" of one local repository to another,
11 allowing operations like diff and log with revsets.
12 """
13
14 from node import nullid
15 from i18n import _
16 import os
17 import util, mdiff, cmdutil, scmutil
18 import localrepo, changelog, manifest, filelog, revlog
19
20 class unionrevlog(revlog.revlog):
21 def __init__(self, opener, indexfile, revlog2, linkmapper):
22 # How it works:
23 # To retrieve a revision, we just need to know the node id so we can
24 # look it up in revlog2.
25 #
26 # To differentiate a rev in the second revlog from a rev in the revlog,
27 # we check revision against repotiprev.
28 opener = scmutil.readonlyvfs(opener)
29 revlog.revlog.__init__(self, opener, indexfile)
30 self.revlog2 = revlog2
31
32 n = len(self)
33 self.repotiprev = n - 1
34 self.bundlerevs = set() # used by 'bundle()' revset expression
35 for rev2 in self.revlog2:
36 rev = self.revlog2.index[rev2]
37 # rev numbers - in revlog2, very different from self.rev
38 _start, _csize, _rsize, _base, linkrev, p1rev, p2rev, node = rev
39
40 if linkmapper is None: # link is to same revlog
41 assert linkrev == rev2 # we never link back
42 link = n
43 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
44 link = linkmapper(linkrev)
45
46 if node in self.nodemap:
47 # this happens for the common revlog revisions
48 self.bundlerevs.add(self.nodemap[node])
49 continue
50
51 p1node = self.revlog2.node(p1rev)
52 p2node = self.revlog2.node(p2rev)
53
54 e = (None, None, None, None,
55 link, self.rev(p1node), self.rev(p2node), node)
56 self.index.insert(-1, e)
57 self.nodemap[node] = n
58 self.bundlerevs.add(n)
59 n += 1
60
61 def _chunk(self, rev):
62 if rev <= self.repotiprev:
63 return revlog.revlog._chunk(self, rev)
64 return self.revlog2._chunk(self.node(rev))
65
66 def revdiff(self, rev1, rev2):
67 """return or calculate a delta between two revisions"""
68 if rev1 > self.repotiprev and rev2 > self.repotiprev:
69 return self.revlog2.revdiff(
70 self.revlog2.rev(self.node(rev1)),
71 self.revlog2.rev(self.node(rev2)))
72 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
73 return revlog.revlog.revdiff(self, rev1, rev2)
74
75 return mdiff.textdiff(self.revision(self.node(rev1)),
76 self.revision(self.node(rev2)))
77
78 def revision(self, nodeorrev):
79 """return an uncompressed revision of a given node or revision
80 number.
81 """
82 if isinstance(nodeorrev, int):
83 rev = nodeorrev
84 node = self.node(rev)
85 else:
86 node = nodeorrev
87 rev = self.rev(node)
88
89 if node == nullid:
90 return ""
91
92 if rev > self.repotiprev:
93 text = self.revlog2.revision(node)
94 self._cache = (node, rev, text)
95 else:
96 text = revlog.revlog.revision(self, rev)
97 # already cached
98 return text
99
100 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
101 raise NotImplementedError
102 def addgroup(self, revs, linkmapper, transaction):
103 raise NotImplementedError
104 def strip(self, rev, minlink):
105 raise NotImplementedError
106 def checksize(self):
107 raise NotImplementedError
108
109 class unionchangelog(unionrevlog, changelog.changelog):
110 def __init__(self, opener, opener2):
111 changelog.changelog.__init__(self, opener)
112 linkmapper = None
113 changelog2 = changelog.changelog(opener2)
114 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
115 linkmapper)
116
117 class unionmanifest(unionrevlog, manifest.manifest):
118 def __init__(self, opener, opener2, linkmapper):
119 manifest.manifest.__init__(self, opener)
120 manifest2 = manifest.manifest(opener2)
121 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
122 linkmapper)
123
124 class unionfilelog(unionrevlog, filelog.filelog):
125 def __init__(self, opener, path, opener2, linkmapper, repo):
126 filelog.filelog.__init__(self, opener, path)
127 filelog2 = filelog.filelog(opener2, path)
128 unionrevlog.__init__(self, opener, self.indexfile, filelog2,
129 linkmapper)
130 self._repo = repo
131
132 def _file(self, f):
133 self._repo.file(f)
134
135 class unionpeer(localrepo.localpeer):
136 def canpush(self):
137 return False
138
139 class unionrepository(localrepo.localrepository):
140 def __init__(self, ui, path, path2):
141 localrepo.localrepository.__init__(self, ui, path)
142 self.ui.setconfig('phases', 'publish', False)
143
144 self._url = 'union:%s+%s' % (util.expandpath(path),
145 util.expandpath(path2))
146 self.repo2 = localrepo.localrepository(ui, path2)
147
148 @localrepo.unfilteredpropertycache
149 def changelog(self):
150 return unionchangelog(self.sopener, self.repo2.sopener)
151
152 def _clrev(self, rev2):
153 """map from repo2 changelog rev to temporary rev in self.changelog"""
154 node = self.repo2.changelog.node(rev2)
155 return self.changelog.rev(node)
156
157 @localrepo.unfilteredpropertycache
158 def manifest(self):
159 return unionmanifest(self.sopener, self.repo2.sopener,
160 self._clrev)
161
162 def url(self):
163 return self._url
164
165 def file(self, f):
166 return unionfilelog(self.sopener, f, self.repo2.sopener,
167 self._clrev, self)
168
169 def close(self):
170 self.repo2.close()
171
172 def cancopy(self):
173 return False
174
175 def peer(self):
176 return unionpeer(self)
177
178 def getcwd(self):
179 return os.getcwd() # always outside the repo
180
181 def instance(ui, path, create):
182 if create:
183 raise util.Abort(_('cannot create new union repository'))
184 parentpath = ui.config("bundle", "mainreporoot", "")
185 if not parentpath:
186 # try to find the correct path to the working directory repo
187 parentpath = cmdutil.findrepo(os.getcwd())
188 if parentpath is None:
189 parentpath = ''
190 if parentpath:
191 # Try to make the full path relative so we get a nice, short URL.
192 # In particular, we don't want temp dir names in test outputs.
193 cwd = os.getcwd()
194 if parentpath == cwd:
195 parentpath = ''
196 else:
197 cwd = os.path.join(cwd,'')
198 if parentpath.startswith(cwd):
199 parentpath = parentpath[len(cwd):]
200 if path.startswith('union:'):
201 s = path.split(":", 1)[1].split("+", 1)
202 if len(s) == 1:
203 repopath, repopath2 = parentpath, s[0]
204 else:
205 repopath, repopath2 = s
206 else:
207 repopath, repopath2 = parentpath, path
208 return unionrepository(ui, repopath, repopath2)
@@ -0,0 +1,148 b''
1 Test unionrepo functionality
2
3 Create one repository
4
5 $ hg init repo1
6 $ cd repo1
7 $ touch repo1-0
8 $ echo repo1-0 > f
9 $ hg ci -Aqmrepo1-0
10 $ touch repo1-1
11 $ echo repo1-1 >> f
12 $ hg ci -Aqmrepo1-1
13 $ touch repo1-2
14 $ echo repo1-2 >> f
15 $ hg ci -Aqmrepo1-2
16 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
17 2:68c0685446a3 repo1-2
18 1:8a58db72e69d repo1-1
19 0:f093fec0529b repo1-0
20 $ tip1=`hg id -q`
21 $ cd ..
22
23 - and a clone with a not-completely-trivial history
24
25 $ hg clone -q repo1 --rev 0 repo2
26 $ cd repo2
27 $ touch repo2-1
28 $ sed '1irepo2-1 at top' f > f.tmp
29 $ mv f.tmp f
30 $ hg ci -Aqmrepo2-1
31 $ touch repo2-2
32 $ hg pull -q ../repo1 -r 1
33 $ hg merge -q
34 $ hg ci -Aqmrepo2-2-merge
35 $ touch repo2-3
36 $ echo repo2-3 >> f
37 $ hg ci -mrepo2-3
38 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
39 4:2f0d178c469c repo2-3
40 3:9e6fb3e0b9da repo2-2-merge
41 2:8a58db72e69d repo1-1
42 1:c337dba826e7 repo2-1
43 0:f093fec0529b repo1-0
44 $ cd ..
45
46 revisions from repo2 appear as appended / pulled to repo1
47
48 $ hg -R union:repo1+repo2 log --template '{rev}:{node|short} {desc|firstline}\n'
49 5:2f0d178c469c repo2-3
50 4:9e6fb3e0b9da repo2-2-merge
51 3:c337dba826e7 repo2-1
52 2:68c0685446a3 repo1-2
53 1:8a58db72e69d repo1-1
54 0:f093fec0529b repo1-0
55
56 manifest can be retrieved for revisions in both repos
57
58 $ hg -R union:repo1+repo2 mani -r $tip1
59 f
60 repo1-0
61 repo1-1
62 repo1-2
63 $ hg -R union:repo1+repo2 mani -r 4
64 f
65 repo1-0
66 repo1-1
67 repo2-1
68 repo2-2
69
70 files can be retrieved form both repos
71
72 $ hg -R repo1 cat repo1/f -r2
73 repo1-0
74 repo1-1
75 repo1-2
76
77 $ hg -R union:repo1+repo2 cat -r$tip1 repo1/f
78 repo1-0
79 repo1-1
80 repo1-2
81
82 $ hg -R union:repo1+repo2 cat -r4 $TESTTMP/repo1/f
83 repo2-1 at top
84 repo1-0
85 repo1-1
86
87 files can be compared across repos
88
89 $ hg -R union:repo1+repo2 diff -r$tip1 -rtip
90 diff -r 68c0685446a3 -r 2f0d178c469c f
91 --- a/f Thu Jan 01 00:00:00 1970 +0000
92 +++ b/f Thu Jan 01 00:00:00 1970 +0000
93 @@ -1,3 +1,4 @@
94 +repo2-1 at top
95 repo1-0
96 repo1-1
97 -repo1-2
98 +repo2-3
99
100 heads from both repos are found correctly
101
102 $ hg -R union:repo1+repo2 heads --template '{rev}:{node|short} {desc|firstline}\n'
103 5:2f0d178c469c repo2-3
104 2:68c0685446a3 repo1-2
105
106 revsets works across repos
107
108 $ hg -R union:repo1+repo2 id -r "ancestor($tip1, 5)"
109 8a58db72e69d
110
111 annotate works - an indication that linkrevs works
112
113 $ hg --cwd repo1 -R union:../repo2 annotate $TESTTMP/repo1/f -r tip
114 3: repo2-1 at top
115 0: repo1-0
116 1: repo1-1
117 5: repo2-3
118
119 union repos can be cloned ... and clones works correctly
120
121 $ hg clone -U union:repo1+repo2 repo3
122 requesting all changes
123 adding changesets
124 adding manifests
125 adding file changes
126 added 6 changesets with 11 changes to 6 files (+1 heads)
127
128 $ hg -R repo3 paths
129 default = union:repo1+repo2
130
131 $ hg -R repo3 verify
132 checking changesets
133 checking manifests
134 crosschecking files in changesets and manifests
135 checking files
136 6 files, 6 changesets, 11 total revisions
137
138 $ hg -R repo3 heads --template '{rev}:{node|short} {desc|firstline}\n'
139 5:2f0d178c469c repo2-3
140 2:68c0685446a3 repo1-2
141
142 $ hg -R repo3 log --template '{rev}:{node|short} {desc|firstline}\n'
143 5:2f0d178c469c repo2-3
144 4:9e6fb3e0b9da repo2-2-merge
145 3:c337dba826e7 repo2-1
146 2:68c0685446a3 repo1-2
147 1:8a58db72e69d repo1-1
148 0:f093fec0529b repo1-0
@@ -9,8 +9,8 b''
9 from i18n import _
9 from i18n import _
10 from lock import release
10 from lock import release
11 from node import hex, nullid
11 from node import hex, nullid
12 import localrepo, bundlerepo, httppeer, sshpeer, statichttprepo, bookmarks
12 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
13 import lock, util, extensions, error, node, scmutil, phases, url
13 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
14 import cmdutil, discovery
14 import cmdutil, discovery
15 import merge as mergemod
15 import merge as mergemod
16 import verify as verifymod
16 import verify as verifymod
@@ -64,6 +64,7 b' def parseurl(path, branches=None):'
64
64
65 schemes = {
65 schemes = {
66 'bundle': bundlerepo,
66 'bundle': bundlerepo,
67 'union': unionrepo,
67 'file': _local,
68 'file': _local,
68 'http': httppeer,
69 'http': httppeer,
69 'https': httppeer,
70 'https': httppeer,
General Comments 0
You need to be logged in to leave comments. Login now