##// END OF EJS Templates
unionrepo: dynamically create repository type from base repository...
Gregory Szorc -
r39641:23f2299e default
parent child Browse files
Show More
@@ -1,263 +1,284
1 # unionrepo.py - repository class for viewing union of repository changesets
1 # unionrepo.py - repository class for viewing union of repository changesets
2 #
2 #
3 # Derived from bundlerepo.py
3 # Derived from bundlerepo.py
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
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.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Repository class for "in-memory pull" of one local repository to another,
10 """Repository class for "in-memory pull" of one local repository to another,
11 allowing operations like diff and log with revsets.
11 allowing operations like diff and log with revsets.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18
18
19 from . import (
19 from . import (
20 changelog,
20 changelog,
21 cmdutil,
21 cmdutil,
22 error,
22 error,
23 filelog,
23 filelog,
24 localrepo,
24 localrepo,
25 manifest,
25 manifest,
26 mdiff,
26 mdiff,
27 pathutil,
27 pathutil,
28 pycompat,
28 pycompat,
29 revlog,
29 revlog,
30 util,
30 util,
31 vfs as vfsmod,
31 vfs as vfsmod,
32 )
32 )
33
33
34 class unionrevlog(revlog.revlog):
34 class unionrevlog(revlog.revlog):
35 def __init__(self, opener, indexfile, revlog2, linkmapper):
35 def __init__(self, opener, indexfile, revlog2, linkmapper):
36 # How it works:
36 # How it works:
37 # To retrieve a revision, we just need to know the node id so we can
37 # To retrieve a revision, we just need to know the node id so we can
38 # look it up in revlog2.
38 # look it up in revlog2.
39 #
39 #
40 # To differentiate a rev in the second revlog from a rev in the revlog,
40 # To differentiate a rev in the second revlog from a rev in the revlog,
41 # we check revision against repotiprev.
41 # we check revision against repotiprev.
42 opener = vfsmod.readonlyvfs(opener)
42 opener = vfsmod.readonlyvfs(opener)
43 revlog.revlog.__init__(self, opener, indexfile)
43 revlog.revlog.__init__(self, opener, indexfile)
44 self.revlog2 = revlog2
44 self.revlog2 = revlog2
45
45
46 n = len(self)
46 n = len(self)
47 self.repotiprev = n - 1
47 self.repotiprev = n - 1
48 self.bundlerevs = set() # used by 'bundle()' revset expression
48 self.bundlerevs = set() # used by 'bundle()' revset expression
49 for rev2 in self.revlog2:
49 for rev2 in self.revlog2:
50 rev = self.revlog2.index[rev2]
50 rev = self.revlog2.index[rev2]
51 # rev numbers - in revlog2, very different from self.rev
51 # rev numbers - in revlog2, very different from self.rev
52 _start, _csize, rsize, base, linkrev, p1rev, p2rev, node = rev
52 _start, _csize, rsize, base, linkrev, p1rev, p2rev, node = rev
53 flags = _start & 0xFFFF
53 flags = _start & 0xFFFF
54
54
55 if linkmapper is None: # link is to same revlog
55 if linkmapper is None: # link is to same revlog
56 assert linkrev == rev2 # we never link back
56 assert linkrev == rev2 # we never link back
57 link = n
57 link = n
58 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
58 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
59 link = linkmapper(linkrev)
59 link = linkmapper(linkrev)
60
60
61 if linkmapper is not None: # link is to same revlog
61 if linkmapper is not None: # link is to same revlog
62 base = linkmapper(base)
62 base = linkmapper(base)
63
63
64 if node in self.nodemap:
64 if node in self.nodemap:
65 # this happens for the common revlog revisions
65 # this happens for the common revlog revisions
66 self.bundlerevs.add(self.nodemap[node])
66 self.bundlerevs.add(self.nodemap[node])
67 continue
67 continue
68
68
69 p1node = self.revlog2.node(p1rev)
69 p1node = self.revlog2.node(p1rev)
70 p2node = self.revlog2.node(p2rev)
70 p2node = self.revlog2.node(p2rev)
71
71
72 # TODO: it's probably wrong to set compressed length to None, but
72 # TODO: it's probably wrong to set compressed length to None, but
73 # I have no idea if csize is valid in the base revlog context.
73 # I have no idea if csize is valid in the base revlog context.
74 e = (flags, None, rsize, base,
74 e = (flags, None, rsize, base,
75 link, self.rev(p1node), self.rev(p2node), node)
75 link, self.rev(p1node), self.rev(p2node), node)
76 self.index.append(e)
76 self.index.append(e)
77 self.nodemap[node] = n
77 self.nodemap[node] = n
78 self.bundlerevs.add(n)
78 self.bundlerevs.add(n)
79 n += 1
79 n += 1
80
80
81 def _chunk(self, rev):
81 def _chunk(self, rev):
82 if rev <= self.repotiprev:
82 if rev <= self.repotiprev:
83 return revlog.revlog._chunk(self, rev)
83 return revlog.revlog._chunk(self, rev)
84 return self.revlog2._chunk(self.node(rev))
84 return self.revlog2._chunk(self.node(rev))
85
85
86 def revdiff(self, rev1, rev2):
86 def revdiff(self, rev1, rev2):
87 """return or calculate a delta between two revisions"""
87 """return or calculate a delta between two revisions"""
88 if rev1 > self.repotiprev and rev2 > self.repotiprev:
88 if rev1 > self.repotiprev and rev2 > self.repotiprev:
89 return self.revlog2.revdiff(
89 return self.revlog2.revdiff(
90 self.revlog2.rev(self.node(rev1)),
90 self.revlog2.rev(self.node(rev1)),
91 self.revlog2.rev(self.node(rev2)))
91 self.revlog2.rev(self.node(rev2)))
92 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
92 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
93 return self.baserevdiff(rev1, rev2)
93 return self.baserevdiff(rev1, rev2)
94
94
95 return mdiff.textdiff(self.revision(rev1), self.revision(rev2))
95 return mdiff.textdiff(self.revision(rev1), self.revision(rev2))
96
96
97 def revision(self, nodeorrev, _df=None, raw=False):
97 def revision(self, nodeorrev, _df=None, raw=False):
98 """return an uncompressed revision of a given node or revision
98 """return an uncompressed revision of a given node or revision
99 number.
99 number.
100 """
100 """
101 if isinstance(nodeorrev, int):
101 if isinstance(nodeorrev, int):
102 rev = nodeorrev
102 rev = nodeorrev
103 node = self.node(rev)
103 node = self.node(rev)
104 else:
104 else:
105 node = nodeorrev
105 node = nodeorrev
106 rev = self.rev(node)
106 rev = self.rev(node)
107
107
108 if node == nullid:
108 if node == nullid:
109 return ""
109 return ""
110
110
111 if rev > self.repotiprev:
111 if rev > self.repotiprev:
112 text = self.revlog2.revision(node)
112 text = self.revlog2.revision(node)
113 self._cache = (node, rev, text)
113 self._cache = (node, rev, text)
114 else:
114 else:
115 text = self.baserevision(rev)
115 text = self.baserevision(rev)
116 # already cached
116 # already cached
117 return text
117 return text
118
118
119 def baserevision(self, nodeorrev):
119 def baserevision(self, nodeorrev):
120 # Revlog subclasses may override 'revision' method to modify format of
120 # Revlog subclasses may override 'revision' method to modify format of
121 # content retrieved from revlog. To use unionrevlog with such class one
121 # content retrieved from revlog. To use unionrevlog with such class one
122 # needs to override 'baserevision' and make more specific call here.
122 # needs to override 'baserevision' and make more specific call here.
123 return revlog.revlog.revision(self, nodeorrev)
123 return revlog.revlog.revision(self, nodeorrev)
124
124
125 def baserevdiff(self, rev1, rev2):
125 def baserevdiff(self, rev1, rev2):
126 # Exists for the same purpose as baserevision.
126 # Exists for the same purpose as baserevision.
127 return revlog.revlog.revdiff(self, rev1, rev2)
127 return revlog.revlog.revdiff(self, rev1, rev2)
128
128
129 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
129 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
130 raise NotImplementedError
130 raise NotImplementedError
131 def addgroup(self, deltas, transaction, addrevisioncb=None):
131 def addgroup(self, deltas, transaction, addrevisioncb=None):
132 raise NotImplementedError
132 raise NotImplementedError
133 def strip(self, rev, minlink):
133 def strip(self, rev, minlink):
134 raise NotImplementedError
134 raise NotImplementedError
135 def checksize(self):
135 def checksize(self):
136 raise NotImplementedError
136 raise NotImplementedError
137
137
138 class unionchangelog(unionrevlog, changelog.changelog):
138 class unionchangelog(unionrevlog, changelog.changelog):
139 def __init__(self, opener, opener2):
139 def __init__(self, opener, opener2):
140 changelog.changelog.__init__(self, opener)
140 changelog.changelog.__init__(self, opener)
141 linkmapper = None
141 linkmapper = None
142 changelog2 = changelog.changelog(opener2)
142 changelog2 = changelog.changelog(opener2)
143 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
143 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
144 linkmapper)
144 linkmapper)
145
145
146 def baserevision(self, nodeorrev):
146 def baserevision(self, nodeorrev):
147 # Although changelog doesn't override 'revision' method, some extensions
147 # Although changelog doesn't override 'revision' method, some extensions
148 # may replace this class with another that does. Same story with
148 # may replace this class with another that does. Same story with
149 # manifest and filelog classes.
149 # manifest and filelog classes.
150 return changelog.changelog.revision(self, nodeorrev)
150 return changelog.changelog.revision(self, nodeorrev)
151
151
152 def baserevdiff(self, rev1, rev2):
152 def baserevdiff(self, rev1, rev2):
153 return changelog.changelog.revdiff(self, rev1, rev2)
153 return changelog.changelog.revdiff(self, rev1, rev2)
154
154
155 class unionmanifest(unionrevlog, manifest.manifestrevlog):
155 class unionmanifest(unionrevlog, manifest.manifestrevlog):
156 def __init__(self, opener, opener2, linkmapper):
156 def __init__(self, opener, opener2, linkmapper):
157 manifest.manifestrevlog.__init__(self, opener)
157 manifest.manifestrevlog.__init__(self, opener)
158 manifest2 = manifest.manifestrevlog(opener2)
158 manifest2 = manifest.manifestrevlog(opener2)
159 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
159 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
160 linkmapper)
160 linkmapper)
161
161
162 def baserevision(self, nodeorrev):
162 def baserevision(self, nodeorrev):
163 return manifest.manifestrevlog.revision(self, nodeorrev)
163 return manifest.manifestrevlog.revision(self, nodeorrev)
164
164
165 def baserevdiff(self, rev1, rev2):
165 def baserevdiff(self, rev1, rev2):
166 return manifest.manifestrevlog.revdiff(self, rev1, rev2)
166 return manifest.manifestrevlog.revdiff(self, rev1, rev2)
167
167
168 class unionfilelog(filelog.filelog):
168 class unionfilelog(filelog.filelog):
169 def __init__(self, opener, path, opener2, linkmapper, repo):
169 def __init__(self, opener, path, opener2, linkmapper, repo):
170 filelog.filelog.__init__(self, opener, path)
170 filelog.filelog.__init__(self, opener, path)
171 filelog2 = filelog.filelog(opener2, path)
171 filelog2 = filelog.filelog(opener2, path)
172 self._revlog = unionrevlog(opener, self.indexfile,
172 self._revlog = unionrevlog(opener, self.indexfile,
173 filelog2._revlog, linkmapper)
173 filelog2._revlog, linkmapper)
174 self._repo = repo
174 self._repo = repo
175 self.repotiprev = self._revlog.repotiprev
175 self.repotiprev = self._revlog.repotiprev
176 self.revlog2 = self._revlog.revlog2
176 self.revlog2 = self._revlog.revlog2
177
177
178 def baserevision(self, nodeorrev):
178 def baserevision(self, nodeorrev):
179 return filelog.filelog.revision(self, nodeorrev)
179 return filelog.filelog.revision(self, nodeorrev)
180
180
181 def baserevdiff(self, rev1, rev2):
181 def baserevdiff(self, rev1, rev2):
182 return filelog.filelog.revdiff(self, rev1, rev2)
182 return filelog.filelog.revdiff(self, rev1, rev2)
183
183
184 def iscensored(self, rev):
184 def iscensored(self, rev):
185 """Check if a revision is censored."""
185 """Check if a revision is censored."""
186 if rev <= self.repotiprev:
186 if rev <= self.repotiprev:
187 return filelog.filelog.iscensored(self, rev)
187 return filelog.filelog.iscensored(self, rev)
188 node = self.node(rev)
188 node = self.node(rev)
189 return self.revlog2.iscensored(self.revlog2.rev(node))
189 return self.revlog2.iscensored(self.revlog2.rev(node))
190
190
191 class unionpeer(localrepo.localpeer):
191 class unionpeer(localrepo.localpeer):
192 def canpush(self):
192 def canpush(self):
193 return False
193 return False
194
194
195 class unionrepository(localrepo.localrepository):
195 class unionrepository(object):
196 def __init__(self, ui, path, path2):
196 """Represents the union of data in 2 repositories.
197 localrepo.localrepository.__init__(self, ui, path)
197
198 Instances are not usable if constructed directly. Use ``instance()``
199 or ``makeunionrepository()`` to create a usable instance.
200 """
201 def __init__(self, repo2, url):
202 self.repo2 = repo2
203 self._url = url
204
198 self.ui.setconfig('phases', 'publish', False, 'unionrepo')
205 self.ui.setconfig('phases', 'publish', False, 'unionrepo')
199
206
200 self._url = 'union:%s+%s' % (util.expandpath(path),
201 util.expandpath(path2))
202 self.repo2 = localrepo.localrepository(ui, path2)
203
204 @localrepo.unfilteredpropertycache
207 @localrepo.unfilteredpropertycache
205 def changelog(self):
208 def changelog(self):
206 return unionchangelog(self.svfs, self.repo2.svfs)
209 return unionchangelog(self.svfs, self.repo2.svfs)
207
210
208 def _clrev(self, rev2):
211 def _clrev(self, rev2):
209 """map from repo2 changelog rev to temporary rev in self.changelog"""
212 """map from repo2 changelog rev to temporary rev in self.changelog"""
210 node = self.repo2.changelog.node(rev2)
213 node = self.repo2.changelog.node(rev2)
211 return self.changelog.rev(node)
214 return self.changelog.rev(node)
212
215
213 def _constructmanifest(self):
216 def _constructmanifest(self):
214 return unionmanifest(self.svfs, self.repo2.svfs,
217 return unionmanifest(self.svfs, self.repo2.svfs,
215 self.unfiltered()._clrev)
218 self.unfiltered()._clrev)
216
219
217 def url(self):
220 def url(self):
218 return self._url
221 return self._url
219
222
220 def file(self, f):
223 def file(self, f):
221 return unionfilelog(self.svfs, f, self.repo2.svfs,
224 return unionfilelog(self.svfs, f, self.repo2.svfs,
222 self.unfiltered()._clrev, self)
225 self.unfiltered()._clrev, self)
223
226
224 def close(self):
227 def close(self):
225 self.repo2.close()
228 self.repo2.close()
226
229
227 def cancopy(self):
230 def cancopy(self):
228 return False
231 return False
229
232
230 def peer(self):
233 def peer(self):
231 return unionpeer(self)
234 return unionpeer(self)
232
235
233 def getcwd(self):
236 def getcwd(self):
234 return pycompat.getcwd() # always outside the repo
237 return pycompat.getcwd() # always outside the repo
235
238
236 def instance(ui, path, create, intents=None, createopts=None):
239 def instance(ui, path, create, intents=None, createopts=None):
237 if create:
240 if create:
238 raise error.Abort(_('cannot create new union repository'))
241 raise error.Abort(_('cannot create new union repository'))
239 parentpath = ui.config("bundle", "mainreporoot")
242 parentpath = ui.config("bundle", "mainreporoot")
240 if not parentpath:
243 if not parentpath:
241 # try to find the correct path to the working directory repo
244 # try to find the correct path to the working directory repo
242 parentpath = cmdutil.findrepo(pycompat.getcwd())
245 parentpath = cmdutil.findrepo(pycompat.getcwd())
243 if parentpath is None:
246 if parentpath is None:
244 parentpath = ''
247 parentpath = ''
245 if parentpath:
248 if parentpath:
246 # Try to make the full path relative so we get a nice, short URL.
249 # Try to make the full path relative so we get a nice, short URL.
247 # In particular, we don't want temp dir names in test outputs.
250 # In particular, we don't want temp dir names in test outputs.
248 cwd = pycompat.getcwd()
251 cwd = pycompat.getcwd()
249 if parentpath == cwd:
252 if parentpath == cwd:
250 parentpath = ''
253 parentpath = ''
251 else:
254 else:
252 cwd = pathutil.normasprefix(cwd)
255 cwd = pathutil.normasprefix(cwd)
253 if parentpath.startswith(cwd):
256 if parentpath.startswith(cwd):
254 parentpath = parentpath[len(cwd):]
257 parentpath = parentpath[len(cwd):]
255 if path.startswith('union:'):
258 if path.startswith('union:'):
256 s = path.split(":", 1)[1].split("+", 1)
259 s = path.split(":", 1)[1].split("+", 1)
257 if len(s) == 1:
260 if len(s) == 1:
258 repopath, repopath2 = parentpath, s[0]
261 repopath, repopath2 = parentpath, s[0]
259 else:
262 else:
260 repopath, repopath2 = s
263 repopath, repopath2 = s
261 else:
264 else:
262 repopath, repopath2 = parentpath, path
265 repopath, repopath2 = parentpath, path
263 return unionrepository(ui, repopath, repopath2)
266
267 return makeunionrepository(ui, repopath, repopath2)
268
269 def makeunionrepository(ui, repopath1, repopath2):
270 """Make a union repository object from 2 local repo paths."""
271 repo1 = localrepo.instance(ui, repopath1, create=False)
272 repo2 = localrepo.instance(ui, repopath2, create=False)
273
274 url = 'union:%s+%s' % (util.expandpath(repopath1),
275 util.expandpath(repopath2))
276
277 class derivedunionrepository(unionrepository, repo1.__class__):
278 pass
279
280 repo = repo1
281 repo.__class__ = derivedunionrepository
282 unionrepository.__init__(repo1, repo2, url)
283
284 return repo
General Comments 0
You need to be logged in to leave comments. Login now