##// END OF EJS Templates
bundlerepo: try to find containing repo on creation (issue1812)
Matt Mackall -
r16042:4b7aa1c8 stable
parent child Browse files
Show More
@@ -1,363 +1,368
1 1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 2 #
3 3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """Repository class for viewing uncompressed bundles.
9 9
10 10 This provides a read-only repository interface to bundles as if they
11 11 were part of the actual repository.
12 12 """
13 13
14 14 from node import nullid
15 15 from i18n import _
16 16 import os, tempfile, shutil
17 import changegroup, util, mdiff, discovery
17 import changegroup, util, mdiff, discovery, cmdutil
18 18 import localrepo, changelog, manifest, filelog, revlog, error
19 19
20 20 class bundlerevlog(revlog.revlog):
21 21 def __init__(self, opener, indexfile, bundle, linkmapper):
22 22 # How it works:
23 23 # to retrieve a revision, we need to know the offset of
24 24 # the revision in the bundle (an unbundle object).
25 25 #
26 26 # We store this offset in the index (start), to differentiate a
27 27 # rev in the bundle and from a rev in the revlog, we check
28 28 # len(index[r]). If the tuple is bigger than 7, it is a bundle
29 29 # (it is bigger since we store the node to which the delta is)
30 30 #
31 31 revlog.revlog.__init__(self, opener, indexfile)
32 32 self.bundle = bundle
33 33 self.basemap = {}
34 34 n = len(self)
35 35 chain = None
36 36 while True:
37 37 chunkdata = bundle.deltachunk(chain)
38 38 if not chunkdata:
39 39 break
40 40 node = chunkdata['node']
41 41 p1 = chunkdata['p1']
42 42 p2 = chunkdata['p2']
43 43 cs = chunkdata['cs']
44 44 deltabase = chunkdata['deltabase']
45 45 delta = chunkdata['delta']
46 46
47 47 size = len(delta)
48 48 start = bundle.tell() - size
49 49
50 50 link = linkmapper(cs)
51 51 if node in self.nodemap:
52 52 # this can happen if two branches make the same change
53 53 chain = node
54 54 continue
55 55
56 56 for p in (p1, p2):
57 57 if not p in self.nodemap:
58 58 raise error.LookupError(p, self.indexfile,
59 59 _("unknown parent"))
60 60 # start, size, full unc. size, base (unused), link, p1, p2, node
61 61 e = (revlog.offset_type(start, 0), size, -1, -1, link,
62 62 self.rev(p1), self.rev(p2), node)
63 63 self.basemap[n] = deltabase
64 64 self.index.insert(-1, e)
65 65 self.nodemap[node] = n
66 66 chain = node
67 67 n += 1
68 68
69 69 def inbundle(self, rev):
70 70 """is rev from the bundle"""
71 71 if rev < 0:
72 72 return False
73 73 return rev in self.basemap
74 74 def bundlebase(self, rev):
75 75 return self.basemap[rev]
76 76 def _chunk(self, rev):
77 77 # Warning: in case of bundle, the diff is against bundlebase,
78 78 # not against rev - 1
79 79 # XXX: could use some caching
80 80 if not self.inbundle(rev):
81 81 return revlog.revlog._chunk(self, rev)
82 82 self.bundle.seek(self.start(rev))
83 83 return self.bundle.read(self.length(rev))
84 84
85 85 def revdiff(self, rev1, rev2):
86 86 """return or calculate a delta between two revisions"""
87 87 if self.inbundle(rev1) and self.inbundle(rev2):
88 88 # hot path for bundle
89 89 revb = self.rev(self.bundlebase(rev2))
90 90 if revb == rev1:
91 91 return self._chunk(rev2)
92 92 elif not self.inbundle(rev1) and not self.inbundle(rev2):
93 93 return revlog.revlog.revdiff(self, rev1, rev2)
94 94
95 95 return mdiff.textdiff(self.revision(self.node(rev1)),
96 96 self.revision(self.node(rev2)))
97 97
98 98 def revision(self, node):
99 99 """return an uncompressed revision of a given"""
100 100 if node == nullid:
101 101 return ""
102 102
103 103 text = None
104 104 chain = []
105 105 iter_node = node
106 106 rev = self.rev(iter_node)
107 107 # reconstruct the revision if it is from a changegroup
108 108 while self.inbundle(rev):
109 109 if self._cache and self._cache[0] == iter_node:
110 110 text = self._cache[2]
111 111 break
112 112 chain.append(rev)
113 113 iter_node = self.bundlebase(rev)
114 114 rev = self.rev(iter_node)
115 115 if text is None:
116 116 text = revlog.revlog.revision(self, iter_node)
117 117
118 118 while chain:
119 119 delta = self._chunk(chain.pop())
120 120 text = mdiff.patches(text, [delta])
121 121
122 122 p1, p2 = self.parents(node)
123 123 if node != revlog.hash(text, p1, p2):
124 124 raise error.RevlogError(_("integrity check failed on %s:%d")
125 125 % (self.datafile, self.rev(node)))
126 126
127 127 self._cache = (node, self.rev(node), text)
128 128 return text
129 129
130 130 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
131 131 raise NotImplementedError
132 132 def addgroup(self, revs, linkmapper, transaction):
133 133 raise NotImplementedError
134 134 def strip(self, rev, minlink):
135 135 raise NotImplementedError
136 136 def checksize(self):
137 137 raise NotImplementedError
138 138
139 139 class bundlechangelog(bundlerevlog, changelog.changelog):
140 140 def __init__(self, opener, bundle):
141 141 changelog.changelog.__init__(self, opener)
142 142 linkmapper = lambda x: x
143 143 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
144 144 linkmapper)
145 145
146 146 class bundlemanifest(bundlerevlog, manifest.manifest):
147 147 def __init__(self, opener, bundle, linkmapper):
148 148 manifest.manifest.__init__(self, opener)
149 149 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
150 150 linkmapper)
151 151
152 152 class bundlefilelog(bundlerevlog, filelog.filelog):
153 153 def __init__(self, opener, path, bundle, linkmapper, repo):
154 154 filelog.filelog.__init__(self, opener, path)
155 155 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
156 156 linkmapper)
157 157 self._repo = repo
158 158
159 159 def _file(self, f):
160 160 self._repo.file(f)
161 161
162 162 class bundlerepository(localrepo.localrepository):
163 163 def __init__(self, ui, path, bundlename):
164 164 self._tempparent = None
165 165 try:
166 166 localrepo.localrepository.__init__(self, ui, path)
167 167 except error.RepoError:
168 168 self._tempparent = tempfile.mkdtemp()
169 169 localrepo.instance(ui, self._tempparent, 1)
170 170 localrepo.localrepository.__init__(self, ui, self._tempparent)
171 171
172 172 if path:
173 173 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
174 174 else:
175 175 self._url = 'bundle:' + bundlename
176 176
177 177 self.tempfile = None
178 178 f = util.posixfile(bundlename, "rb")
179 179 self.bundle = changegroup.readbundle(f, bundlename)
180 180 if self.bundle.compressed():
181 181 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
182 182 suffix=".hg10un", dir=self.path)
183 183 self.tempfile = temp
184 184 fptemp = os.fdopen(fdtemp, 'wb')
185 185
186 186 try:
187 187 fptemp.write("HG10UN")
188 188 while True:
189 189 chunk = self.bundle.read(2**18)
190 190 if not chunk:
191 191 break
192 192 fptemp.write(chunk)
193 193 finally:
194 194 fptemp.close()
195 195
196 196 f = util.posixfile(self.tempfile, "rb")
197 197 self.bundle = changegroup.readbundle(f, bundlename)
198 198
199 199 # dict with the mapping 'filename' -> position in the bundle
200 200 self.bundlefilespos = {}
201 201
202 202 @util.propertycache
203 203 def changelog(self):
204 204 # consume the header if it exists
205 205 self.bundle.changelogheader()
206 206 c = bundlechangelog(self.sopener, self.bundle)
207 207 self.manstart = self.bundle.tell()
208 208 return c
209 209
210 210 @util.propertycache
211 211 def manifest(self):
212 212 self.bundle.seek(self.manstart)
213 213 # consume the header if it exists
214 214 self.bundle.manifestheader()
215 215 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
216 216 self.filestart = self.bundle.tell()
217 217 return m
218 218
219 219 @util.propertycache
220 220 def manstart(self):
221 221 self.changelog
222 222 return self.manstart
223 223
224 224 @util.propertycache
225 225 def filestart(self):
226 226 self.manifest
227 227 return self.filestart
228 228
229 229 def url(self):
230 230 return self._url
231 231
232 232 def file(self, f):
233 233 if not self.bundlefilespos:
234 234 self.bundle.seek(self.filestart)
235 235 while True:
236 236 chunkdata = self.bundle.filelogheader()
237 237 if not chunkdata:
238 238 break
239 239 fname = chunkdata['filename']
240 240 self.bundlefilespos[fname] = self.bundle.tell()
241 241 while True:
242 242 c = self.bundle.deltachunk(None)
243 243 if not c:
244 244 break
245 245
246 246 if f[0] == '/':
247 247 f = f[1:]
248 248 if f in self.bundlefilespos:
249 249 self.bundle.seek(self.bundlefilespos[f])
250 250 return bundlefilelog(self.sopener, f, self.bundle,
251 251 self.changelog.rev, self)
252 252 else:
253 253 return filelog.filelog(self.sopener, f)
254 254
255 255 def close(self):
256 256 """Close assigned bundle file immediately."""
257 257 self.bundle.close()
258 258 if self.tempfile is not None:
259 259 os.unlink(self.tempfile)
260 260 if self._tempparent:
261 261 shutil.rmtree(self._tempparent, True)
262 262
263 263 def cancopy(self):
264 264 return False
265 265
266 266 def getcwd(self):
267 267 return os.getcwd() # always outside the repo
268 268
269 269 def _writebranchcache(self, branches, tip, tiprev):
270 270 # don't overwrite the disk cache with bundle-augmented data
271 271 pass
272 272
273 273 def instance(ui, path, create):
274 274 if create:
275 275 raise util.Abort(_('cannot create new bundle repository'))
276 276 parentpath = ui.config("bundle", "mainreporoot", "")
277 if not parentpath:
278 # try to find the correct path to the working directory repo
279 parentpath = cmdutil.findrepo(os.getcwd())
280 if parentpath is None:
281 parentpath = ''
277 282 if parentpath:
278 283 # Try to make the full path relative so we get a nice, short URL.
279 284 # In particular, we don't want temp dir names in test outputs.
280 285 cwd = os.getcwd()
281 286 if parentpath == cwd:
282 287 parentpath = ''
283 288 else:
284 289 cwd = os.path.join(cwd,'')
285 290 if parentpath.startswith(cwd):
286 291 parentpath = parentpath[len(cwd):]
287 292 u = util.url(path)
288 293 path = u.localpath()
289 294 if u.scheme == 'bundle':
290 295 s = path.split("+", 1)
291 296 if len(s) == 1:
292 297 repopath, bundlename = parentpath, s[0]
293 298 else:
294 299 repopath, bundlename = s
295 300 else:
296 301 repopath, bundlename = parentpath, path
297 302 return bundlerepository(ui, repopath, bundlename)
298 303
299 304 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
300 305 force=False):
301 306 '''obtains a bundle of changes incoming from other
302 307
303 308 "onlyheads" restricts the returned changes to those reachable from the
304 309 specified heads.
305 310 "bundlename", if given, stores the bundle to this file path permanently;
306 311 otherwise it's stored to a temp file and gets deleted again when you call
307 312 the returned "cleanupfn".
308 313 "force" indicates whether to proceed on unrelated repos.
309 314
310 315 Returns a tuple (local, csets, cleanupfn):
311 316
312 317 "local" is a local repo from which to obtain the actual incoming changesets; it
313 318 is a bundlerepo for the obtained bundle when the original "other" is remote.
314 319 "csets" lists the incoming changeset node ids.
315 320 "cleanupfn" must be called without arguments when you're done processing the
316 321 changes; it closes both the original "other" and the one returned here.
317 322 '''
318 323 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads, force=force)
319 324 common, incoming, rheads = tmp
320 325 if not incoming:
321 326 try:
322 327 if bundlename:
323 328 os.unlink(bundlename)
324 329 except OSError:
325 330 pass
326 331 return other, [], other.close
327 332
328 333 bundle = None
329 334 bundlerepo = None
330 335 localrepo = other
331 336 if bundlename or not other.local():
332 337 # create a bundle (uncompressed if other repo is not local)
333 338
334 339 if other.capable('getbundle'):
335 340 cg = other.getbundle('incoming', common=common, heads=rheads)
336 341 elif onlyheads is None and not other.capable('changegroupsubset'):
337 342 # compat with older servers when pulling all remote heads
338 343 cg = other.changegroup(incoming, "incoming")
339 344 rheads = None
340 345 else:
341 346 cg = other.changegroupsubset(incoming, rheads, 'incoming')
342 347 bundletype = other.local() and "HG10BZ" or "HG10UN"
343 348 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
344 349 # keep written bundle?
345 350 if bundlename:
346 351 bundle = None
347 352 if not other.local():
348 353 # use the created uncompressed bundlerepo
349 354 localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
350 355 # this repo contains local and other now, so filter out local again
351 356 common = repo.heads()
352 357
353 358 csets = localrepo.changelog.findmissing(common, rheads)
354 359
355 360 def cleanup():
356 361 if bundlerepo:
357 362 bundlerepo.close()
358 363 if bundle:
359 364 os.unlink(bundle)
360 365 other.close()
361 366
362 367 return (localrepo, csets, cleanup)
363 368
General Comments 0
You need to be logged in to leave comments. Login now