Mercurial > public > mercurial-scm > hg
comparison mercurial/unionrepo.py @ 18944:a9c443b3b240
unionrepo: read-only operations on a union of two localrepos
unionrepo is just like bundlerepo without bundles.
The implementation is very similar to bundlerepo, but I don't see any obvious
way to generalize it.
Some most obvious use cases for this would be log and diff across local repos,
as a kind of preview of pulls, for instance:
$ hg -R union:repo1+repo2 heads
$ hg -R union:repo1+repo2 log -r REPO1REV -r REPO2REV
$ hg -R union:repo1+repo2 log -r '::REPO1REV-::REPO2REV'
$ hg -R union:repo1+repo2 log -r 'ancestor(REPO1REV,REPO2REV)'
$ hg -R union:repo1+repo2 diff -r REPO1REV -r REPO2REV
This is going to be used in RhodeCode, and Bitbucket already uses something
similar. Having a core implementation would be beneficial.
author | Mads Kiilerich <madski@unity3d.com> |
---|---|
date | Fri, 18 Jan 2013 15:54:09 +0100 |
parents | |
children | bb67f630b335 |
comparison
equal
deleted
inserted
replaced
18943:27e8dfc2c338 | 18944:a9c443b3b240 |
---|---|
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) |