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