##// END OF EJS Templates
filelog: add file function to open other filelogs
Sune Foldager -
r14287:7c231754 default
parent child Browse files
Show More
@@ -1,355 +1,359 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
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 1:
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 def __init__(self, opener, path, bundle, linkmapper):
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 self._repo = repo
158
159 def _file(self, f):
160 self._repo.file(f)
157 161
158 162 class bundlerepository(localrepo.localrepository):
159 163 def __init__(self, ui, path, bundlename):
160 164 self._tempparent = None
161 165 try:
162 166 localrepo.localrepository.__init__(self, ui, path)
163 167 except error.RepoError:
164 168 self._tempparent = tempfile.mkdtemp()
165 169 localrepo.instance(ui, self._tempparent, 1)
166 170 localrepo.localrepository.__init__(self, ui, self._tempparent)
167 171
168 172 if path:
169 173 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
170 174 else:
171 175 self._url = 'bundle:' + bundlename
172 176
173 177 self.tempfile = None
174 178 f = util.posixfile(bundlename, "rb")
175 179 self.bundle = changegroup.readbundle(f, bundlename)
176 180 if self.bundle.compressed():
177 181 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
178 182 suffix=".hg10un", dir=self.path)
179 183 self.tempfile = temp
180 184 fptemp = os.fdopen(fdtemp, 'wb')
181 185
182 186 try:
183 187 fptemp.write("HG10UN")
184 188 while 1:
185 189 chunk = self.bundle.read(2**18)
186 190 if not chunk:
187 191 break
188 192 fptemp.write(chunk)
189 193 finally:
190 194 fptemp.close()
191 195
192 196 f = util.posixfile(self.tempfile, "rb")
193 197 self.bundle = changegroup.readbundle(f, bundlename)
194 198
195 199 # dict with the mapping 'filename' -> position in the bundle
196 200 self.bundlefilespos = {}
197 201
198 202 @util.propertycache
199 203 def changelog(self):
200 204 # consume the header if it exists
201 205 self.bundle.changelogheader()
202 206 c = bundlechangelog(self.sopener, self.bundle)
203 207 self.manstart = self.bundle.tell()
204 208 return c
205 209
206 210 @util.propertycache
207 211 def manifest(self):
208 212 self.bundle.seek(self.manstart)
209 213 # consume the header if it exists
210 214 self.bundle.manifestheader()
211 215 m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev)
212 216 self.filestart = self.bundle.tell()
213 217 return m
214 218
215 219 @util.propertycache
216 220 def manstart(self):
217 221 self.changelog
218 222 return self.manstart
219 223
220 224 @util.propertycache
221 225 def filestart(self):
222 226 self.manifest
223 227 return self.filestart
224 228
225 229 def url(self):
226 230 return self._url
227 231
228 232 def file(self, f):
229 233 if not self.bundlefilespos:
230 234 self.bundle.seek(self.filestart)
231 235 while 1:
232 236 chunkdata = self.bundle.filelogheader()
233 237 if not chunkdata:
234 238 break
235 239 fname = chunkdata['filename']
236 240 self.bundlefilespos[fname] = self.bundle.tell()
237 241 while 1:
238 242 c = self.bundle.deltachunk(None)
239 243 if not c:
240 244 break
241 245
242 246 if f[0] == '/':
243 247 f = f[1:]
244 248 if f in self.bundlefilespos:
245 249 self.bundle.seek(self.bundlefilespos[f])
246 250 return bundlefilelog(self.sopener, f, self.bundle,
247 self.changelog.rev)
251 self.changelog.rev, self)
248 252 else:
249 253 return filelog.filelog(self.sopener, f)
250 254
251 255 def close(self):
252 256 """Close assigned bundle file immediately."""
253 257 self.bundle.close()
254 258 if self.tempfile is not None:
255 259 os.unlink(self.tempfile)
256 260 if self._tempparent:
257 261 shutil.rmtree(self._tempparent, True)
258 262
259 263 def cancopy(self):
260 264 return False
261 265
262 266 def getcwd(self):
263 267 return os.getcwd() # always outside the repo
264 268
265 269 def instance(ui, path, create):
266 270 if create:
267 271 raise util.Abort(_('cannot create new bundle repository'))
268 272 parentpath = ui.config("bundle", "mainreporoot", "")
269 273 if parentpath:
270 274 # Try to make the full path relative so we get a nice, short URL.
271 275 # In particular, we don't want temp dir names in test outputs.
272 276 cwd = os.getcwd()
273 277 if parentpath == cwd:
274 278 parentpath = ''
275 279 else:
276 280 cwd = os.path.join(cwd,'')
277 281 if parentpath.startswith(cwd):
278 282 parentpath = parentpath[len(cwd):]
279 283 u = util.url(path)
280 284 path = u.localpath()
281 285 if u.scheme == 'bundle':
282 286 s = path.split("+", 1)
283 287 if len(s) == 1:
284 288 repopath, bundlename = parentpath, s[0]
285 289 else:
286 290 repopath, bundlename = s
287 291 else:
288 292 repopath, bundlename = parentpath, path
289 293 return bundlerepository(ui, repopath, bundlename)
290 294
291 295 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
292 296 force=False):
293 297 '''obtains a bundle of changes incoming from other
294 298
295 299 "onlyheads" restricts the returned changes to those reachable from the
296 300 specified heads.
297 301 "bundlename", if given, stores the bundle to this file path permanently;
298 302 otherwise it's stored to a temp file and gets deleted again when you call
299 303 the returned "cleanupfn".
300 304 "force" indicates whether to proceed on unrelated repos.
301 305
302 306 Returns a tuple (local, csets, cleanupfn):
303 307
304 308 "local" is a local repo from which to obtain the actual incoming changesets; it
305 309 is a bundlerepo for the obtained bundle when the original "other" is remote.
306 310 "csets" lists the incoming changeset node ids.
307 311 "cleanupfn" must be called without arguments when you're done processing the
308 312 changes; it closes both the original "other" and the one returned here.
309 313 '''
310 314 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads, force=force)
311 315 common, incoming, rheads = tmp
312 316 if not incoming:
313 317 try:
314 318 os.unlink(bundlename)
315 319 except OSError:
316 320 pass
317 321 return other, [], other.close
318 322
319 323 bundle = None
320 324 bundlerepo = None
321 325 localrepo = other
322 326 if bundlename or not other.local():
323 327 # create a bundle (uncompressed if other repo is not local)
324 328
325 329 if onlyheads is None and other.capable('changegroupsubset'):
326 330 onlyheads = rheads
327 331
328 332 if other.capable('getbundle'):
329 333 cg = other.getbundle('incoming', common=common, heads=onlyheads)
330 334 elif onlyheads is None:
331 335 cg = other.changegroup(incoming, "incoming")
332 336 else:
333 337 cg = other.changegroupsubset(incoming, onlyheads, 'incoming')
334 338 bundletype = other.local() and "HG10BZ" or "HG10UN"
335 339 fname = bundle = changegroup.writebundle(cg, bundlename, bundletype)
336 340 # keep written bundle?
337 341 if bundlename:
338 342 bundle = None
339 343 if not other.local():
340 344 # use the created uncompressed bundlerepo
341 345 localrepo = bundlerepo = bundlerepository(ui, repo.root, fname)
342 346 # this repo contains local and other now, so filter out local again
343 347 common = repo.heads()
344 348
345 349 csets = localrepo.changelog.findmissing(common, onlyheads)
346 350
347 351 def cleanup():
348 352 if bundlerepo:
349 353 bundlerepo.close()
350 354 if bundle:
351 355 os.unlink(bundle)
352 356 other.close()
353 357
354 358 return (localrepo, csets, cleanup)
355 359
@@ -1,89 +1,92 b''
1 1 # filelog.py - file history class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 import revlog
9 9 import re
10 10
11 11 _mdre = re.compile('\1\n')
12 12 def _parsemeta(text):
13 13 """return (metadatadict, keylist, metadatasize)"""
14 14 # text can be buffer, so we can't use .startswith or .index
15 15 if text[:2] != '\1\n':
16 16 return None, None, None
17 17 s = _mdre.search(text, 2).start()
18 18 mtext = text[2:s]
19 19 meta = {}
20 20 keys = []
21 21 for l in mtext.splitlines():
22 22 k, v = l.split(": ", 1)
23 23 meta[k] = v
24 24 keys.append(k)
25 25 return meta, keys, (s + 2)
26 26
27 27 def _packmeta(meta, keys=None):
28 28 if not keys:
29 29 keys = sorted(meta.iterkeys())
30 30 return "".join("%s: %s\n" % (k, meta[k]) for k in keys)
31 31
32 32 class filelog(revlog.revlog):
33 33 def __init__(self, opener, path):
34 34 revlog.revlog.__init__(self, opener,
35 35 "/".join(("data", path + ".i")))
36 36
37 37 def read(self, node):
38 38 t = self.revision(node)
39 39 if not t.startswith('\1\n'):
40 40 return t
41 41 s = t.index('\1\n', 2)
42 42 return t[s + 2:]
43 43
44 44 def add(self, text, meta, transaction, link, p1=None, p2=None):
45 45 if meta or text.startswith('\1\n'):
46 46 text = "\1\n%s\1\n%s" % (_packmeta(meta), text)
47 47 return self.addrevision(text, transaction, link, p1, p2)
48 48
49 49 def renamed(self, node):
50 50 if self.parents(node)[0] != revlog.nullid:
51 51 return False
52 52 t = self.revision(node)
53 53 m = _parsemeta(t)[0]
54 54 if m and "copy" in m:
55 55 return (m["copy"], revlog.bin(m["copyrev"]))
56 56 return False
57 57
58 58 def size(self, rev):
59 59 """return the size of a given revision"""
60 60
61 61 # for revisions with renames, we have to go the slow way
62 62 node = self.node(rev)
63 63 if self.renamed(node):
64 64 return len(self.read(node))
65 65
66 66 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
67 67 return revlog.revlog.size(self, rev)
68 68
69 69 def cmp(self, node, text):
70 70 """compare text with a given file revision
71 71
72 72 returns True if text is different than what is stored.
73 73 """
74 74
75 75 t = text
76 76 if text.startswith('\1\n'):
77 77 t = '\1\n\1\n' + text
78 78
79 79 samehashes = not revlog.revlog.cmp(self, node, t)
80 80 if samehashes:
81 81 return False
82 82
83 83 # renaming a file produces a different hash, even if the data
84 84 # remains unchanged. Check if it's the case (slow):
85 85 if self.renamed(node):
86 86 t2 = self.read(node)
87 87 return t2 != text
88 88
89 89 return True
90
91 def _file(self, f):
92 return filelog(self.opener, f)
General Comments 0
You need to be logged in to leave comments. Login now