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