##// END OF EJS Templates
filelog: wrap revlog instead of inheriting it (API)...
Gregory Szorc -
r37515:1541e1a8 default
parent child Browse files
Show More
@@ -1,598 +1,598 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 __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import os
16 import os
17 import shutil
17 import shutil
18 import tempfile
18 import tempfile
19
19
20 from .i18n import _
20 from .i18n import _
21 from .node import nullid
21 from .node import nullid
22
22
23 from . import (
23 from . import (
24 bundle2,
24 bundle2,
25 changegroup,
25 changegroup,
26 changelog,
26 changelog,
27 cmdutil,
27 cmdutil,
28 discovery,
28 discovery,
29 error,
29 error,
30 exchange,
30 exchange,
31 filelog,
31 filelog,
32 localrepo,
32 localrepo,
33 manifest,
33 manifest,
34 mdiff,
34 mdiff,
35 node as nodemod,
35 node as nodemod,
36 pathutil,
36 pathutil,
37 phases,
37 phases,
38 pycompat,
38 pycompat,
39 revlog,
39 revlog,
40 util,
40 util,
41 vfs as vfsmod,
41 vfs as vfsmod,
42 )
42 )
43
43
44 class bundlerevlog(revlog.revlog):
44 class bundlerevlog(revlog.revlog):
45 def __init__(self, opener, indexfile, cgunpacker, linkmapper):
45 def __init__(self, opener, indexfile, cgunpacker, linkmapper):
46 # How it works:
46 # How it works:
47 # To retrieve a revision, we need to know the offset of the revision in
47 # To retrieve a revision, we need to know the offset of the revision in
48 # the bundle (an unbundle object). We store this offset in the index
48 # the bundle (an unbundle object). We store this offset in the index
49 # (start). The base of the delta is stored in the base field.
49 # (start). The base of the delta is stored in the base field.
50 #
50 #
51 # To differentiate a rev in the bundle from a rev in the revlog, we
51 # To differentiate a rev in the bundle from a rev in the revlog, we
52 # check revision against repotiprev.
52 # check revision against repotiprev.
53 opener = vfsmod.readonlyvfs(opener)
53 opener = vfsmod.readonlyvfs(opener)
54 revlog.revlog.__init__(self, opener, indexfile)
54 revlog.revlog.__init__(self, opener, indexfile)
55 self.bundle = cgunpacker
55 self.bundle = cgunpacker
56 n = len(self)
56 n = len(self)
57 self.repotiprev = n - 1
57 self.repotiprev = n - 1
58 self.bundlerevs = set() # used by 'bundle()' revset expression
58 self.bundlerevs = set() # used by 'bundle()' revset expression
59 for deltadata in cgunpacker.deltaiter():
59 for deltadata in cgunpacker.deltaiter():
60 node, p1, p2, cs, deltabase, delta, flags = deltadata
60 node, p1, p2, cs, deltabase, delta, flags = deltadata
61
61
62 size = len(delta)
62 size = len(delta)
63 start = cgunpacker.tell() - size
63 start = cgunpacker.tell() - size
64
64
65 link = linkmapper(cs)
65 link = linkmapper(cs)
66 if node in self.nodemap:
66 if node in self.nodemap:
67 # this can happen if two branches make the same change
67 # this can happen if two branches make the same change
68 self.bundlerevs.add(self.nodemap[node])
68 self.bundlerevs.add(self.nodemap[node])
69 continue
69 continue
70
70
71 for p in (p1, p2):
71 for p in (p1, p2):
72 if p not in self.nodemap:
72 if p not in self.nodemap:
73 raise error.LookupError(p, self.indexfile,
73 raise error.LookupError(p, self.indexfile,
74 _("unknown parent"))
74 _("unknown parent"))
75
75
76 if deltabase not in self.nodemap:
76 if deltabase not in self.nodemap:
77 raise LookupError(deltabase, self.indexfile,
77 raise LookupError(deltabase, self.indexfile,
78 _('unknown delta base'))
78 _('unknown delta base'))
79
79
80 baserev = self.rev(deltabase)
80 baserev = self.rev(deltabase)
81 # start, size, full unc. size, base (unused), link, p1, p2, node
81 # start, size, full unc. size, base (unused), link, p1, p2, node
82 e = (revlog.offset_type(start, flags), size, -1, baserev, link,
82 e = (revlog.offset_type(start, flags), size, -1, baserev, link,
83 self.rev(p1), self.rev(p2), node)
83 self.rev(p1), self.rev(p2), node)
84 self.index.insert(-1, e)
84 self.index.insert(-1, e)
85 self.nodemap[node] = n
85 self.nodemap[node] = n
86 self.bundlerevs.add(n)
86 self.bundlerevs.add(n)
87 n += 1
87 n += 1
88
88
89 def _chunk(self, rev, df=None):
89 def _chunk(self, rev, df=None):
90 # Warning: in case of bundle, the diff is against what we stored as
90 # Warning: in case of bundle, the diff is against what we stored as
91 # delta base, not against rev - 1
91 # delta base, not against rev - 1
92 # XXX: could use some caching
92 # XXX: could use some caching
93 if rev <= self.repotiprev:
93 if rev <= self.repotiprev:
94 return revlog.revlog._chunk(self, rev)
94 return revlog.revlog._chunk(self, rev)
95 self.bundle.seek(self.start(rev))
95 self.bundle.seek(self.start(rev))
96 return self.bundle.read(self.length(rev))
96 return self.bundle.read(self.length(rev))
97
97
98 def revdiff(self, rev1, rev2):
98 def revdiff(self, rev1, rev2):
99 """return or calculate a delta between two revisions"""
99 """return or calculate a delta between two revisions"""
100 if rev1 > self.repotiprev and rev2 > self.repotiprev:
100 if rev1 > self.repotiprev and rev2 > self.repotiprev:
101 # hot path for bundle
101 # hot path for bundle
102 revb = self.index[rev2][3]
102 revb = self.index[rev2][3]
103 if revb == rev1:
103 if revb == rev1:
104 return self._chunk(rev2)
104 return self._chunk(rev2)
105 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
105 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
106 return revlog.revlog.revdiff(self, rev1, rev2)
106 return revlog.revlog.revdiff(self, rev1, rev2)
107
107
108 return mdiff.textdiff(self.revision(rev1, raw=True),
108 return mdiff.textdiff(self.revision(rev1, raw=True),
109 self.revision(rev2, raw=True))
109 self.revision(rev2, raw=True))
110
110
111 def revision(self, nodeorrev, _df=None, raw=False):
111 def revision(self, nodeorrev, _df=None, raw=False):
112 """return an uncompressed revision of a given node or revision
112 """return an uncompressed revision of a given node or revision
113 number.
113 number.
114 """
114 """
115 if isinstance(nodeorrev, int):
115 if isinstance(nodeorrev, int):
116 rev = nodeorrev
116 rev = nodeorrev
117 node = self.node(rev)
117 node = self.node(rev)
118 else:
118 else:
119 node = nodeorrev
119 node = nodeorrev
120 rev = self.rev(node)
120 rev = self.rev(node)
121
121
122 if node == nullid:
122 if node == nullid:
123 return ""
123 return ""
124
124
125 rawtext = None
125 rawtext = None
126 chain = []
126 chain = []
127 iterrev = rev
127 iterrev = rev
128 # reconstruct the revision if it is from a changegroup
128 # reconstruct the revision if it is from a changegroup
129 while iterrev > self.repotiprev:
129 while iterrev > self.repotiprev:
130 if self._cache and self._cache[1] == iterrev:
130 if self._cache and self._cache[1] == iterrev:
131 rawtext = self._cache[2]
131 rawtext = self._cache[2]
132 break
132 break
133 chain.append(iterrev)
133 chain.append(iterrev)
134 iterrev = self.index[iterrev][3]
134 iterrev = self.index[iterrev][3]
135 if rawtext is None:
135 if rawtext is None:
136 rawtext = self.baserevision(iterrev)
136 rawtext = self.baserevision(iterrev)
137
137
138 while chain:
138 while chain:
139 delta = self._chunk(chain.pop())
139 delta = self._chunk(chain.pop())
140 rawtext = mdiff.patches(rawtext, [delta])
140 rawtext = mdiff.patches(rawtext, [delta])
141
141
142 text, validatehash = self._processflags(rawtext, self.flags(rev),
142 text, validatehash = self._processflags(rawtext, self.flags(rev),
143 'read', raw=raw)
143 'read', raw=raw)
144 if validatehash:
144 if validatehash:
145 self.checkhash(text, node, rev=rev)
145 self.checkhash(text, node, rev=rev)
146 self._cache = (node, rev, rawtext)
146 self._cache = (node, rev, rawtext)
147 return text
147 return text
148
148
149 def baserevision(self, nodeorrev):
149 def baserevision(self, nodeorrev):
150 # Revlog subclasses may override 'revision' method to modify format of
150 # Revlog subclasses may override 'revision' method to modify format of
151 # content retrieved from revlog. To use bundlerevlog with such class one
151 # content retrieved from revlog. To use bundlerevlog with such class one
152 # needs to override 'baserevision' and make more specific call here.
152 # needs to override 'baserevision' and make more specific call here.
153 return revlog.revlog.revision(self, nodeorrev, raw=True)
153 return revlog.revlog.revision(self, nodeorrev, raw=True)
154
154
155 def addrevision(self, *args, **kwargs):
155 def addrevision(self, *args, **kwargs):
156 raise NotImplementedError
156 raise NotImplementedError
157
157
158 def addgroup(self, *args, **kwargs):
158 def addgroup(self, *args, **kwargs):
159 raise NotImplementedError
159 raise NotImplementedError
160
160
161 def strip(self, *args, **kwargs):
161 def strip(self, *args, **kwargs):
162 raise NotImplementedError
162 raise NotImplementedError
163
163
164 def checksize(self):
164 def checksize(self):
165 raise NotImplementedError
165 raise NotImplementedError
166
166
167 class bundlechangelog(bundlerevlog, changelog.changelog):
167 class bundlechangelog(bundlerevlog, changelog.changelog):
168 def __init__(self, opener, cgunpacker):
168 def __init__(self, opener, cgunpacker):
169 changelog.changelog.__init__(self, opener)
169 changelog.changelog.__init__(self, opener)
170 linkmapper = lambda x: x
170 linkmapper = lambda x: x
171 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
171 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
172 linkmapper)
172 linkmapper)
173
173
174 def baserevision(self, nodeorrev):
174 def baserevision(self, nodeorrev):
175 # Although changelog doesn't override 'revision' method, some extensions
175 # Although changelog doesn't override 'revision' method, some extensions
176 # may replace this class with another that does. Same story with
176 # may replace this class with another that does. Same story with
177 # manifest and filelog classes.
177 # manifest and filelog classes.
178
178
179 # This bypasses filtering on changelog.node() and rev() because we need
179 # This bypasses filtering on changelog.node() and rev() because we need
180 # revision text of the bundle base even if it is hidden.
180 # revision text of the bundle base even if it is hidden.
181 oldfilter = self.filteredrevs
181 oldfilter = self.filteredrevs
182 try:
182 try:
183 self.filteredrevs = ()
183 self.filteredrevs = ()
184 return changelog.changelog.revision(self, nodeorrev, raw=True)
184 return changelog.changelog.revision(self, nodeorrev, raw=True)
185 finally:
185 finally:
186 self.filteredrevs = oldfilter
186 self.filteredrevs = oldfilter
187
187
188 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
188 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
189 def __init__(self, opener, cgunpacker, linkmapper, dirlogstarts=None,
189 def __init__(self, opener, cgunpacker, linkmapper, dirlogstarts=None,
190 dir=''):
190 dir=''):
191 manifest.manifestrevlog.__init__(self, opener, dir=dir)
191 manifest.manifestrevlog.__init__(self, opener, dir=dir)
192 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
192 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
193 linkmapper)
193 linkmapper)
194 if dirlogstarts is None:
194 if dirlogstarts is None:
195 dirlogstarts = {}
195 dirlogstarts = {}
196 if self.bundle.version == "03":
196 if self.bundle.version == "03":
197 dirlogstarts = _getfilestarts(self.bundle)
197 dirlogstarts = _getfilestarts(self.bundle)
198 self._dirlogstarts = dirlogstarts
198 self._dirlogstarts = dirlogstarts
199 self._linkmapper = linkmapper
199 self._linkmapper = linkmapper
200
200
201 def baserevision(self, nodeorrev):
201 def baserevision(self, nodeorrev):
202 node = nodeorrev
202 node = nodeorrev
203 if isinstance(node, int):
203 if isinstance(node, int):
204 node = self.node(node)
204 node = self.node(node)
205
205
206 if node in self.fulltextcache:
206 if node in self.fulltextcache:
207 result = '%s' % self.fulltextcache[node]
207 result = '%s' % self.fulltextcache[node]
208 else:
208 else:
209 result = manifest.manifestrevlog.revision(self, nodeorrev, raw=True)
209 result = manifest.manifestrevlog.revision(self, nodeorrev, raw=True)
210 return result
210 return result
211
211
212 def dirlog(self, d):
212 def dirlog(self, d):
213 if d in self._dirlogstarts:
213 if d in self._dirlogstarts:
214 self.bundle.seek(self._dirlogstarts[d])
214 self.bundle.seek(self._dirlogstarts[d])
215 return bundlemanifest(
215 return bundlemanifest(
216 self.opener, self.bundle, self._linkmapper,
216 self.opener, self.bundle, self._linkmapper,
217 self._dirlogstarts, dir=d)
217 self._dirlogstarts, dir=d)
218 return super(bundlemanifest, self).dirlog(d)
218 return super(bundlemanifest, self).dirlog(d)
219
219
220 class bundlefilelog(bundlerevlog, filelog.filelog):
220 class bundlefilelog(filelog.filelog):
221 def __init__(self, opener, path, cgunpacker, linkmapper):
221 def __init__(self, opener, path, cgunpacker, linkmapper):
222 filelog.filelog.__init__(self, opener, path)
222 filelog.filelog.__init__(self, opener, path)
223 bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
223 self._revlog = bundlerevlog(opener, self.indexfile,
224 linkmapper)
224 cgunpacker, linkmapper)
225
225
226 def baserevision(self, nodeorrev):
226 def baserevision(self, nodeorrev):
227 return filelog.filelog.revision(self, nodeorrev, raw=True)
227 return filelog.filelog.revision(self, nodeorrev, raw=True)
228
228
229 class bundlepeer(localrepo.localpeer):
229 class bundlepeer(localrepo.localpeer):
230 def canpush(self):
230 def canpush(self):
231 return False
231 return False
232
232
233 class bundlephasecache(phases.phasecache):
233 class bundlephasecache(phases.phasecache):
234 def __init__(self, *args, **kwargs):
234 def __init__(self, *args, **kwargs):
235 super(bundlephasecache, self).__init__(*args, **kwargs)
235 super(bundlephasecache, self).__init__(*args, **kwargs)
236 if util.safehasattr(self, 'opener'):
236 if util.safehasattr(self, 'opener'):
237 self.opener = vfsmod.readonlyvfs(self.opener)
237 self.opener = vfsmod.readonlyvfs(self.opener)
238
238
239 def write(self):
239 def write(self):
240 raise NotImplementedError
240 raise NotImplementedError
241
241
242 def _write(self, fp):
242 def _write(self, fp):
243 raise NotImplementedError
243 raise NotImplementedError
244
244
245 def _updateroots(self, phase, newroots, tr):
245 def _updateroots(self, phase, newroots, tr):
246 self.phaseroots[phase] = newroots
246 self.phaseroots[phase] = newroots
247 self.invalidate()
247 self.invalidate()
248 self.dirty = True
248 self.dirty = True
249
249
250 def _getfilestarts(cgunpacker):
250 def _getfilestarts(cgunpacker):
251 filespos = {}
251 filespos = {}
252 for chunkdata in iter(cgunpacker.filelogheader, {}):
252 for chunkdata in iter(cgunpacker.filelogheader, {}):
253 fname = chunkdata['filename']
253 fname = chunkdata['filename']
254 filespos[fname] = cgunpacker.tell()
254 filespos[fname] = cgunpacker.tell()
255 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
255 for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
256 pass
256 pass
257 return filespos
257 return filespos
258
258
259 class bundlerepository(localrepo.localrepository):
259 class bundlerepository(localrepo.localrepository):
260 """A repository instance that is a union of a local repo and a bundle.
260 """A repository instance that is a union of a local repo and a bundle.
261
261
262 Instances represent a read-only repository composed of a local repository
262 Instances represent a read-only repository composed of a local repository
263 with the contents of a bundle file applied. The repository instance is
263 with the contents of a bundle file applied. The repository instance is
264 conceptually similar to the state of a repository after an
264 conceptually similar to the state of a repository after an
265 ``hg unbundle`` operation. However, the contents of the bundle are never
265 ``hg unbundle`` operation. However, the contents of the bundle are never
266 applied to the actual base repository.
266 applied to the actual base repository.
267 """
267 """
268 def __init__(self, ui, repopath, bundlepath):
268 def __init__(self, ui, repopath, bundlepath):
269 self._tempparent = None
269 self._tempparent = None
270 try:
270 try:
271 localrepo.localrepository.__init__(self, ui, repopath)
271 localrepo.localrepository.__init__(self, ui, repopath)
272 except error.RepoError:
272 except error.RepoError:
273 self._tempparent = tempfile.mkdtemp()
273 self._tempparent = tempfile.mkdtemp()
274 localrepo.instance(ui, self._tempparent, 1)
274 localrepo.instance(ui, self._tempparent, 1)
275 localrepo.localrepository.__init__(self, ui, self._tempparent)
275 localrepo.localrepository.__init__(self, ui, self._tempparent)
276 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
276 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
277
277
278 if repopath:
278 if repopath:
279 self._url = 'bundle:' + util.expandpath(repopath) + '+' + bundlepath
279 self._url = 'bundle:' + util.expandpath(repopath) + '+' + bundlepath
280 else:
280 else:
281 self._url = 'bundle:' + bundlepath
281 self._url = 'bundle:' + bundlepath
282
282
283 self.tempfile = None
283 self.tempfile = None
284 f = util.posixfile(bundlepath, "rb")
284 f = util.posixfile(bundlepath, "rb")
285 bundle = exchange.readbundle(ui, f, bundlepath)
285 bundle = exchange.readbundle(ui, f, bundlepath)
286
286
287 if isinstance(bundle, bundle2.unbundle20):
287 if isinstance(bundle, bundle2.unbundle20):
288 self._bundlefile = bundle
288 self._bundlefile = bundle
289 self._cgunpacker = None
289 self._cgunpacker = None
290
290
291 cgpart = None
291 cgpart = None
292 for part in bundle.iterparts(seekable=True):
292 for part in bundle.iterparts(seekable=True):
293 if part.type == 'changegroup':
293 if part.type == 'changegroup':
294 if cgpart:
294 if cgpart:
295 raise NotImplementedError("can't process "
295 raise NotImplementedError("can't process "
296 "multiple changegroups")
296 "multiple changegroups")
297 cgpart = part
297 cgpart = part
298
298
299 self._handlebundle2part(bundle, part)
299 self._handlebundle2part(bundle, part)
300
300
301 if not cgpart:
301 if not cgpart:
302 raise error.Abort(_("No changegroups found"))
302 raise error.Abort(_("No changegroups found"))
303
303
304 # This is required to placate a later consumer, which expects
304 # This is required to placate a later consumer, which expects
305 # the payload offset to be at the beginning of the changegroup.
305 # the payload offset to be at the beginning of the changegroup.
306 # We need to do this after the iterparts() generator advances
306 # We need to do this after the iterparts() generator advances
307 # because iterparts() will seek to end of payload after the
307 # because iterparts() will seek to end of payload after the
308 # generator returns control to iterparts().
308 # generator returns control to iterparts().
309 cgpart.seek(0, os.SEEK_SET)
309 cgpart.seek(0, os.SEEK_SET)
310
310
311 elif isinstance(bundle, changegroup.cg1unpacker):
311 elif isinstance(bundle, changegroup.cg1unpacker):
312 if bundle.compressed():
312 if bundle.compressed():
313 f = self._writetempbundle(bundle.read, '.hg10un',
313 f = self._writetempbundle(bundle.read, '.hg10un',
314 header='HG10UN')
314 header='HG10UN')
315 bundle = exchange.readbundle(ui, f, bundlepath, self.vfs)
315 bundle = exchange.readbundle(ui, f, bundlepath, self.vfs)
316
316
317 self._bundlefile = bundle
317 self._bundlefile = bundle
318 self._cgunpacker = bundle
318 self._cgunpacker = bundle
319 else:
319 else:
320 raise error.Abort(_('bundle type %s cannot be read') %
320 raise error.Abort(_('bundle type %s cannot be read') %
321 type(bundle))
321 type(bundle))
322
322
323 # dict with the mapping 'filename' -> position in the changegroup.
323 # dict with the mapping 'filename' -> position in the changegroup.
324 self._cgfilespos = {}
324 self._cgfilespos = {}
325
325
326 self.firstnewrev = self.changelog.repotiprev + 1
326 self.firstnewrev = self.changelog.repotiprev + 1
327 phases.retractboundary(self, None, phases.draft,
327 phases.retractboundary(self, None, phases.draft,
328 [ctx.node() for ctx in self[self.firstnewrev:]])
328 [ctx.node() for ctx in self[self.firstnewrev:]])
329
329
330 def _handlebundle2part(self, bundle, part):
330 def _handlebundle2part(self, bundle, part):
331 if part.type != 'changegroup':
331 if part.type != 'changegroup':
332 return
332 return
333
333
334 cgstream = part
334 cgstream = part
335 version = part.params.get('version', '01')
335 version = part.params.get('version', '01')
336 legalcgvers = changegroup.supportedincomingversions(self)
336 legalcgvers = changegroup.supportedincomingversions(self)
337 if version not in legalcgvers:
337 if version not in legalcgvers:
338 msg = _('Unsupported changegroup version: %s')
338 msg = _('Unsupported changegroup version: %s')
339 raise error.Abort(msg % version)
339 raise error.Abort(msg % version)
340 if bundle.compressed():
340 if bundle.compressed():
341 cgstream = self._writetempbundle(part.read, '.cg%sun' % version)
341 cgstream = self._writetempbundle(part.read, '.cg%sun' % version)
342
342
343 self._cgunpacker = changegroup.getunbundler(version, cgstream, 'UN')
343 self._cgunpacker = changegroup.getunbundler(version, cgstream, 'UN')
344
344
345 def _writetempbundle(self, readfn, suffix, header=''):
345 def _writetempbundle(self, readfn, suffix, header=''):
346 """Write a temporary file to disk
346 """Write a temporary file to disk
347 """
347 """
348 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
348 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
349 suffix=suffix)
349 suffix=suffix)
350 self.tempfile = temp
350 self.tempfile = temp
351
351
352 with os.fdopen(fdtemp, r'wb') as fptemp:
352 with os.fdopen(fdtemp, r'wb') as fptemp:
353 fptemp.write(header)
353 fptemp.write(header)
354 while True:
354 while True:
355 chunk = readfn(2**18)
355 chunk = readfn(2**18)
356 if not chunk:
356 if not chunk:
357 break
357 break
358 fptemp.write(chunk)
358 fptemp.write(chunk)
359
359
360 return self.vfs.open(self.tempfile, mode="rb")
360 return self.vfs.open(self.tempfile, mode="rb")
361
361
362 @localrepo.unfilteredpropertycache
362 @localrepo.unfilteredpropertycache
363 def _phasecache(self):
363 def _phasecache(self):
364 return bundlephasecache(self, self._phasedefaults)
364 return bundlephasecache(self, self._phasedefaults)
365
365
366 @localrepo.unfilteredpropertycache
366 @localrepo.unfilteredpropertycache
367 def changelog(self):
367 def changelog(self):
368 # consume the header if it exists
368 # consume the header if it exists
369 self._cgunpacker.changelogheader()
369 self._cgunpacker.changelogheader()
370 c = bundlechangelog(self.svfs, self._cgunpacker)
370 c = bundlechangelog(self.svfs, self._cgunpacker)
371 self.manstart = self._cgunpacker.tell()
371 self.manstart = self._cgunpacker.tell()
372 return c
372 return c
373
373
374 def _constructmanifest(self):
374 def _constructmanifest(self):
375 self._cgunpacker.seek(self.manstart)
375 self._cgunpacker.seek(self.manstart)
376 # consume the header if it exists
376 # consume the header if it exists
377 self._cgunpacker.manifestheader()
377 self._cgunpacker.manifestheader()
378 linkmapper = self.unfiltered().changelog.rev
378 linkmapper = self.unfiltered().changelog.rev
379 m = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
379 m = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
380 self.filestart = self._cgunpacker.tell()
380 self.filestart = self._cgunpacker.tell()
381 return m
381 return m
382
382
383 def _consumemanifest(self):
383 def _consumemanifest(self):
384 """Consumes the manifest portion of the bundle, setting filestart so the
384 """Consumes the manifest portion of the bundle, setting filestart so the
385 file portion can be read."""
385 file portion can be read."""
386 self._cgunpacker.seek(self.manstart)
386 self._cgunpacker.seek(self.manstart)
387 self._cgunpacker.manifestheader()
387 self._cgunpacker.manifestheader()
388 for delta in self._cgunpacker.deltaiter():
388 for delta in self._cgunpacker.deltaiter():
389 pass
389 pass
390 self.filestart = self._cgunpacker.tell()
390 self.filestart = self._cgunpacker.tell()
391
391
392 @localrepo.unfilteredpropertycache
392 @localrepo.unfilteredpropertycache
393 def manstart(self):
393 def manstart(self):
394 self.changelog
394 self.changelog
395 return self.manstart
395 return self.manstart
396
396
397 @localrepo.unfilteredpropertycache
397 @localrepo.unfilteredpropertycache
398 def filestart(self):
398 def filestart(self):
399 self.manifestlog
399 self.manifestlog
400
400
401 # If filestart was not set by self.manifestlog, that means the
401 # If filestart was not set by self.manifestlog, that means the
402 # manifestlog implementation did not consume the manifests from the
402 # manifestlog implementation did not consume the manifests from the
403 # changegroup (ex: it might be consuming trees from a separate bundle2
403 # changegroup (ex: it might be consuming trees from a separate bundle2
404 # part instead). So we need to manually consume it.
404 # part instead). So we need to manually consume it.
405 if r'filestart' not in self.__dict__:
405 if r'filestart' not in self.__dict__:
406 self._consumemanifest()
406 self._consumemanifest()
407
407
408 return self.filestart
408 return self.filestart
409
409
410 def url(self):
410 def url(self):
411 return self._url
411 return self._url
412
412
413 def file(self, f):
413 def file(self, f):
414 if not self._cgfilespos:
414 if not self._cgfilespos:
415 self._cgunpacker.seek(self.filestart)
415 self._cgunpacker.seek(self.filestart)
416 self._cgfilespos = _getfilestarts(self._cgunpacker)
416 self._cgfilespos = _getfilestarts(self._cgunpacker)
417
417
418 if f in self._cgfilespos:
418 if f in self._cgfilespos:
419 self._cgunpacker.seek(self._cgfilespos[f])
419 self._cgunpacker.seek(self._cgfilespos[f])
420 linkmapper = self.unfiltered().changelog.rev
420 linkmapper = self.unfiltered().changelog.rev
421 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
421 return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
422 else:
422 else:
423 return super(bundlerepository, self).file(f)
423 return super(bundlerepository, self).file(f)
424
424
425 def close(self):
425 def close(self):
426 """Close assigned bundle file immediately."""
426 """Close assigned bundle file immediately."""
427 self._bundlefile.close()
427 self._bundlefile.close()
428 if self.tempfile is not None:
428 if self.tempfile is not None:
429 self.vfs.unlink(self.tempfile)
429 self.vfs.unlink(self.tempfile)
430 if self._tempparent:
430 if self._tempparent:
431 shutil.rmtree(self._tempparent, True)
431 shutil.rmtree(self._tempparent, True)
432
432
433 def cancopy(self):
433 def cancopy(self):
434 return False
434 return False
435
435
436 def peer(self):
436 def peer(self):
437 return bundlepeer(self)
437 return bundlepeer(self)
438
438
439 def getcwd(self):
439 def getcwd(self):
440 return pycompat.getcwd() # always outside the repo
440 return pycompat.getcwd() # always outside the repo
441
441
442 # Check if parents exist in localrepo before setting
442 # Check if parents exist in localrepo before setting
443 def setparents(self, p1, p2=nullid):
443 def setparents(self, p1, p2=nullid):
444 p1rev = self.changelog.rev(p1)
444 p1rev = self.changelog.rev(p1)
445 p2rev = self.changelog.rev(p2)
445 p2rev = self.changelog.rev(p2)
446 msg = _("setting parent to node %s that only exists in the bundle\n")
446 msg = _("setting parent to node %s that only exists in the bundle\n")
447 if self.changelog.repotiprev < p1rev:
447 if self.changelog.repotiprev < p1rev:
448 self.ui.warn(msg % nodemod.hex(p1))
448 self.ui.warn(msg % nodemod.hex(p1))
449 if self.changelog.repotiprev < p2rev:
449 if self.changelog.repotiprev < p2rev:
450 self.ui.warn(msg % nodemod.hex(p2))
450 self.ui.warn(msg % nodemod.hex(p2))
451 return super(bundlerepository, self).setparents(p1, p2)
451 return super(bundlerepository, self).setparents(p1, p2)
452
452
453 def instance(ui, path, create):
453 def instance(ui, path, create):
454 if create:
454 if create:
455 raise error.Abort(_('cannot create new bundle repository'))
455 raise error.Abort(_('cannot create new bundle repository'))
456 # internal config: bundle.mainreporoot
456 # internal config: bundle.mainreporoot
457 parentpath = ui.config("bundle", "mainreporoot")
457 parentpath = ui.config("bundle", "mainreporoot")
458 if not parentpath:
458 if not parentpath:
459 # try to find the correct path to the working directory repo
459 # try to find the correct path to the working directory repo
460 parentpath = cmdutil.findrepo(pycompat.getcwd())
460 parentpath = cmdutil.findrepo(pycompat.getcwd())
461 if parentpath is None:
461 if parentpath is None:
462 parentpath = ''
462 parentpath = ''
463 if parentpath:
463 if parentpath:
464 # Try to make the full path relative so we get a nice, short URL.
464 # Try to make the full path relative so we get a nice, short URL.
465 # In particular, we don't want temp dir names in test outputs.
465 # In particular, we don't want temp dir names in test outputs.
466 cwd = pycompat.getcwd()
466 cwd = pycompat.getcwd()
467 if parentpath == cwd:
467 if parentpath == cwd:
468 parentpath = ''
468 parentpath = ''
469 else:
469 else:
470 cwd = pathutil.normasprefix(cwd)
470 cwd = pathutil.normasprefix(cwd)
471 if parentpath.startswith(cwd):
471 if parentpath.startswith(cwd):
472 parentpath = parentpath[len(cwd):]
472 parentpath = parentpath[len(cwd):]
473 u = util.url(path)
473 u = util.url(path)
474 path = u.localpath()
474 path = u.localpath()
475 if u.scheme == 'bundle':
475 if u.scheme == 'bundle':
476 s = path.split("+", 1)
476 s = path.split("+", 1)
477 if len(s) == 1:
477 if len(s) == 1:
478 repopath, bundlename = parentpath, s[0]
478 repopath, bundlename = parentpath, s[0]
479 else:
479 else:
480 repopath, bundlename = s
480 repopath, bundlename = s
481 else:
481 else:
482 repopath, bundlename = parentpath, path
482 repopath, bundlename = parentpath, path
483 return bundlerepository(ui, repopath, bundlename)
483 return bundlerepository(ui, repopath, bundlename)
484
484
485 class bundletransactionmanager(object):
485 class bundletransactionmanager(object):
486 def transaction(self):
486 def transaction(self):
487 return None
487 return None
488
488
489 def close(self):
489 def close(self):
490 raise NotImplementedError
490 raise NotImplementedError
491
491
492 def release(self):
492 def release(self):
493 raise NotImplementedError
493 raise NotImplementedError
494
494
495 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
495 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
496 force=False):
496 force=False):
497 '''obtains a bundle of changes incoming from other
497 '''obtains a bundle of changes incoming from other
498
498
499 "onlyheads" restricts the returned changes to those reachable from the
499 "onlyheads" restricts the returned changes to those reachable from the
500 specified heads.
500 specified heads.
501 "bundlename", if given, stores the bundle to this file path permanently;
501 "bundlename", if given, stores the bundle to this file path permanently;
502 otherwise it's stored to a temp file and gets deleted again when you call
502 otherwise it's stored to a temp file and gets deleted again when you call
503 the returned "cleanupfn".
503 the returned "cleanupfn".
504 "force" indicates whether to proceed on unrelated repos.
504 "force" indicates whether to proceed on unrelated repos.
505
505
506 Returns a tuple (local, csets, cleanupfn):
506 Returns a tuple (local, csets, cleanupfn):
507
507
508 "local" is a local repo from which to obtain the actual incoming
508 "local" is a local repo from which to obtain the actual incoming
509 changesets; it is a bundlerepo for the obtained bundle when the
509 changesets; it is a bundlerepo for the obtained bundle when the
510 original "other" is remote.
510 original "other" is remote.
511 "csets" lists the incoming changeset node ids.
511 "csets" lists the incoming changeset node ids.
512 "cleanupfn" must be called without arguments when you're done processing
512 "cleanupfn" must be called without arguments when you're done processing
513 the changes; it closes both the original "other" and the one returned
513 the changes; it closes both the original "other" and the one returned
514 here.
514 here.
515 '''
515 '''
516 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
516 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
517 force=force)
517 force=force)
518 common, incoming, rheads = tmp
518 common, incoming, rheads = tmp
519 if not incoming:
519 if not incoming:
520 try:
520 try:
521 if bundlename:
521 if bundlename:
522 os.unlink(bundlename)
522 os.unlink(bundlename)
523 except OSError:
523 except OSError:
524 pass
524 pass
525 return repo, [], other.close
525 return repo, [], other.close
526
526
527 commonset = set(common)
527 commonset = set(common)
528 rheads = [x for x in rheads if x not in commonset]
528 rheads = [x for x in rheads if x not in commonset]
529
529
530 bundle = None
530 bundle = None
531 bundlerepo = None
531 bundlerepo = None
532 localrepo = other.local()
532 localrepo = other.local()
533 if bundlename or not localrepo:
533 if bundlename or not localrepo:
534 # create a bundle (uncompressed if other repo is not local)
534 # create a bundle (uncompressed if other repo is not local)
535
535
536 # developer config: devel.legacy.exchange
536 # developer config: devel.legacy.exchange
537 legexc = ui.configlist('devel', 'legacy.exchange')
537 legexc = ui.configlist('devel', 'legacy.exchange')
538 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
538 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
539 canbundle2 = (not forcebundle1
539 canbundle2 = (not forcebundle1
540 and other.capable('getbundle')
540 and other.capable('getbundle')
541 and other.capable('bundle2'))
541 and other.capable('bundle2'))
542 if canbundle2:
542 if canbundle2:
543 kwargs = {}
543 kwargs = {}
544 kwargs[r'common'] = common
544 kwargs[r'common'] = common
545 kwargs[r'heads'] = rheads
545 kwargs[r'heads'] = rheads
546 kwargs[r'bundlecaps'] = exchange.caps20to10(repo, role='client')
546 kwargs[r'bundlecaps'] = exchange.caps20to10(repo, role='client')
547 kwargs[r'cg'] = True
547 kwargs[r'cg'] = True
548 b2 = other.getbundle('incoming', **kwargs)
548 b2 = other.getbundle('incoming', **kwargs)
549 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
549 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
550 bundlename)
550 bundlename)
551 else:
551 else:
552 if other.capable('getbundle'):
552 if other.capable('getbundle'):
553 cg = other.getbundle('incoming', common=common, heads=rheads)
553 cg = other.getbundle('incoming', common=common, heads=rheads)
554 elif onlyheads is None and not other.capable('changegroupsubset'):
554 elif onlyheads is None and not other.capable('changegroupsubset'):
555 # compat with older servers when pulling all remote heads
555 # compat with older servers when pulling all remote heads
556 cg = other.changegroup(incoming, "incoming")
556 cg = other.changegroup(incoming, "incoming")
557 rheads = None
557 rheads = None
558 else:
558 else:
559 cg = other.changegroupsubset(incoming, rheads, 'incoming')
559 cg = other.changegroupsubset(incoming, rheads, 'incoming')
560 if localrepo:
560 if localrepo:
561 bundletype = "HG10BZ"
561 bundletype = "HG10BZ"
562 else:
562 else:
563 bundletype = "HG10UN"
563 bundletype = "HG10UN"
564 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
564 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
565 bundletype)
565 bundletype)
566 # keep written bundle?
566 # keep written bundle?
567 if bundlename:
567 if bundlename:
568 bundle = None
568 bundle = None
569 if not localrepo:
569 if not localrepo:
570 # use the created uncompressed bundlerepo
570 # use the created uncompressed bundlerepo
571 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
571 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
572 fname)
572 fname)
573 # this repo contains local and other now, so filter out local again
573 # this repo contains local and other now, so filter out local again
574 common = repo.heads()
574 common = repo.heads()
575 if localrepo:
575 if localrepo:
576 # Part of common may be remotely filtered
576 # Part of common may be remotely filtered
577 # So use an unfiltered version
577 # So use an unfiltered version
578 # The discovery process probably need cleanup to avoid that
578 # The discovery process probably need cleanup to avoid that
579 localrepo = localrepo.unfiltered()
579 localrepo = localrepo.unfiltered()
580
580
581 csets = localrepo.changelog.findmissing(common, rheads)
581 csets = localrepo.changelog.findmissing(common, rheads)
582
582
583 if bundlerepo:
583 if bundlerepo:
584 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
584 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
585 remotephases = other.listkeys('phases')
585 remotephases = other.listkeys('phases')
586
586
587 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
587 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
588 pullop.trmanager = bundletransactionmanager()
588 pullop.trmanager = bundletransactionmanager()
589 exchange._pullapplyphases(pullop, remotephases)
589 exchange._pullapplyphases(pullop, remotephases)
590
590
591 def cleanup():
591 def cleanup():
592 if bundlerepo:
592 if bundlerepo:
593 bundlerepo.close()
593 bundlerepo.close()
594 if bundle:
594 if bundle:
595 os.unlink(bundle)
595 os.unlink(bundle)
596 other.close()
596 other.close()
597
597
598 return (localrepo, csets, cleanup)
598 return (localrepo, csets, cleanup)
@@ -1,85 +1,267 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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .thirdparty.zope import (
10 from .thirdparty.zope import (
11 interface as zi,
11 interface as zi,
12 )
12 )
13 from . import (
13 from . import (
14 error,
14 repository,
15 repository,
15 revlog,
16 revlog,
16 )
17 )
17
18
18 @zi.implementer(repository.ifilestorage)
19 @zi.implementer(repository.ifilestorage)
19 class filelog(revlog.revlog):
20 class filelog(object):
20 def __init__(self, opener, path):
21 def __init__(self, opener, path):
21 super(filelog, self).__init__(opener,
22 self._revlog = revlog.revlog(opener,
22 "/".join(("data", path + ".i")),
23 '/'.join(('data', path + '.i')),
23 censorable=True)
24 censorable=True)
24 # full name of the user visible file, relative to the repository root
25 # full name of the user visible file, relative to the repository root
25 self.filename = path
26 self.filename = path
27 self.index = self._revlog.index
28 self.version = self._revlog.version
29 self.storedeltachains = self._revlog.storedeltachains
30 self._generaldelta = self._revlog._generaldelta
31
32 def __len__(self):
33 return len(self._revlog)
34
35 def __iter__(self):
36 return self._revlog.__iter__()
37
38 def revs(self, start=0, stop=None):
39 return self._revlog.revs(start=start, stop=stop)
40
41 def parents(self, node):
42 return self._revlog.parents(node)
43
44 def parentrevs(self, rev):
45 return self._revlog.parentrevs(rev)
46
47 def rev(self, node):
48 return self._revlog.rev(node)
49
50 def node(self, rev):
51 return self._revlog.node(rev)
52
53 def lookup(self, node):
54 return self._revlog.lookup(node)
55
56 def linkrev(self, rev):
57 return self._revlog.linkrev(rev)
58
59 def flags(self, rev):
60 return self._revlog.flags(rev)
61
62 def commonancestorsheads(self, node1, node2):
63 return self._revlog.commonancestorsheads(node1, node2)
64
65 def descendants(self, revs):
66 return self._revlog.descendants(revs)
67
68 def headrevs(self):
69 return self._revlog.headrevs()
70
71 def heads(self, start=None, stop=None):
72 return self._revlog.heads(start, stop)
73
74 def children(self, node):
75 return self._revlog.children(node)
76
77 def deltaparent(self, rev):
78 return self._revlog.deltaparent(rev)
79
80 def candelta(self, baserev, rev):
81 return self._revlog.candelta(baserev, rev)
82
83 def iscensored(self, rev):
84 return self._revlog.iscensored(rev)
85
86 def rawsize(self, rev):
87 return self._revlog.rawsize(rev)
88
89 def checkhash(self, text, node, p1=None, p2=None, rev=None):
90 return self._revlog.checkhash(text, node, p1=p1, p2=p2, rev=rev)
91
92 def revision(self, node, _df=None, raw=False):
93 return self._revlog.revision(node, _df=_df, raw=raw)
94
95 def revdiff(self, rev1, rev2):
96 return self._revlog.revdiff(rev1, rev2)
97
98 def addrevision(self, revisiondata, transaction, linkrev, p1, p2,
99 node=None, flags=revlog.REVIDX_DEFAULT_FLAGS,
100 cachedelta=None):
101 return self._revlog.addrevision(revisiondata, transaction, linkrev,
102 p1, p2, node=node, flags=flags,
103 cachedelta=cachedelta)
104
105 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
106 return self._revlog.addgroup(deltas, linkmapper, transaction,
107 addrevisioncb=addrevisioncb)
108
109 def getstrippoint(self, minlink):
110 return self._revlog.getstrippoint(minlink)
111
112 def strip(self, minlink, transaction):
113 return self._revlog.strip(minlink, transaction)
114
115 def files(self):
116 return self._revlog.files()
117
118 def checksize(self):
119 return self._revlog.checksize()
26
120
27 def read(self, node):
121 def read(self, node):
28 t = self.revision(node)
122 t = self.revision(node)
29 if not t.startswith('\1\n'):
123 if not t.startswith('\1\n'):
30 return t
124 return t
31 s = t.index('\1\n', 2)
125 s = t.index('\1\n', 2)
32 return t[s + 2:]
126 return t[s + 2:]
33
127
34 def add(self, text, meta, transaction, link, p1=None, p2=None):
128 def add(self, text, meta, transaction, link, p1=None, p2=None):
35 if meta or text.startswith('\1\n'):
129 if meta or text.startswith('\1\n'):
36 text = revlog.packmeta(meta, text)
130 text = revlog.packmeta(meta, text)
37 return self.addrevision(text, transaction, link, p1, p2)
131 return self.addrevision(text, transaction, link, p1, p2)
38
132
39 def renamed(self, node):
133 def renamed(self, node):
40 if self.parents(node)[0] != revlog.nullid:
134 if self.parents(node)[0] != revlog.nullid:
41 return False
135 return False
42 t = self.revision(node)
136 t = self.revision(node)
43 m = revlog.parsemeta(t)[0]
137 m = revlog.parsemeta(t)[0]
44 if m and "copy" in m:
138 if m and "copy" in m:
45 return (m["copy"], revlog.bin(m["copyrev"]))
139 return (m["copy"], revlog.bin(m["copyrev"]))
46 return False
140 return False
47
141
48 def size(self, rev):
142 def size(self, rev):
49 """return the size of a given revision"""
143 """return the size of a given revision"""
50
144
51 # for revisions with renames, we have to go the slow way
145 # for revisions with renames, we have to go the slow way
52 node = self.node(rev)
146 node = self.node(rev)
53 if self.renamed(node):
147 if self.renamed(node):
54 return len(self.read(node))
148 return len(self.read(node))
55 if self.iscensored(rev):
149 if self.iscensored(rev):
56 return 0
150 return 0
57
151
58 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
152 # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
59 return super(filelog, self).size(rev)
153 return self._revlog.size(rev)
60
154
61 def cmp(self, node, text):
155 def cmp(self, node, text):
62 """compare text with a given file revision
156 """compare text with a given file revision
63
157
64 returns True if text is different than what is stored.
158 returns True if text is different than what is stored.
65 """
159 """
66
160
67 t = text
161 t = text
68 if text.startswith('\1\n'):
162 if text.startswith('\1\n'):
69 t = '\1\n\1\n' + text
163 t = '\1\n\1\n' + text
70
164
71 samehashes = not super(filelog, self).cmp(node, t)
165 samehashes = not self._revlog.cmp(node, t)
72 if samehashes:
166 if samehashes:
73 return False
167 return False
74
168
75 # censored files compare against the empty file
169 # censored files compare against the empty file
76 if self.iscensored(self.rev(node)):
170 if self.iscensored(self.rev(node)):
77 return text != ''
171 return text != ''
78
172
79 # renaming a file produces a different hash, even if the data
173 # renaming a file produces a different hash, even if the data
80 # remains unchanged. Check if it's the case (slow):
174 # remains unchanged. Check if it's the case (slow):
81 if self.renamed(node):
175 if self.renamed(node):
82 t2 = self.read(node)
176 t2 = self.read(node)
83 return t2 != text
177 return t2 != text
84
178
85 return True
179 return True
180
181 @property
182 def filename(self):
183 return self._revlog.filename
184
185 @filename.setter
186 def filename(self, value):
187 self._revlog.filename = value
188
189 # TODO these aren't part of the interface and aren't internal methods.
190 # Callers should be fixed to not use them.
191 @property
192 def indexfile(self):
193 return self._revlog.indexfile
194
195 @indexfile.setter
196 def indexfile(self, value):
197 self._revlog.indexfile = value
198
199 @property
200 def datafile(self):
201 return self._revlog.datafile
202
203 @property
204 def opener(self):
205 return self._revlog.opener
206
207 @property
208 def _lazydeltabase(self):
209 return self._revlog._lazydeltabase
210
211 @_lazydeltabase.setter
212 def _lazydeltabase(self, value):
213 self._revlog._lazydeltabase = value
214
215 @property
216 def _aggressivemergedeltas(self):
217 return self._revlog._aggressivemergedeltas
218
219 @_aggressivemergedeltas.setter
220 def _aggressivemergedeltas(self, value):
221 self._revlog._aggressivemergedeltas = value
222
223 @property
224 def _inline(self):
225 return self._revlog._inline
226
227 @property
228 def _withsparseread(self):
229 return getattr(self._revlog, '_withsparseread', False)
230
231 @property
232 def _srmingapsize(self):
233 return self._revlog._srmingapsize
234
235 @property
236 def _srdensitythreshold(self):
237 return self._revlog._srdensitythreshold
238
239 def _deltachain(self, rev, stoprev=None):
240 return self._revlog._deltachain(rev, stoprev)
241
242 def chainbase(self, rev):
243 return self._revlog.chainbase(rev)
244
245 def chainlen(self, rev):
246 return self._revlog.chainlen(rev)
247
248 def clone(self, tr, destrevlog, **kwargs):
249 if not isinstance(destrevlog, filelog):
250 raise error.ProgrammingError('expected filelog to clone()')
251
252 return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
253
254 def start(self, rev):
255 return self._revlog.start(rev)
256
257 def end(self, rev):
258 return self._revlog.end(rev)
259
260 def length(self, rev):
261 return self._revlog.length(rev)
262
263 def compress(self, data):
264 return self._revlog.compress(data)
265
266 def _addrevision(self, *args, **kwargs):
267 return self._revlog._addrevision(*args, **kwargs)
@@ -1,259 +1,261 b''
1 # unionrepo.py - repository class for viewing union of repository changesets
1 # unionrepo.py - repository class for viewing union of repository changesets
2 #
2 #
3 # Derived from bundlerepo.py
3 # Derived from bundlerepo.py
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
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.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Repository class for "in-memory pull" of one local repository to another,
10 """Repository class for "in-memory pull" of one local repository to another,
11 allowing operations like diff and log with revsets.
11 allowing operations like diff and log with revsets.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18
18
19 from . import (
19 from . import (
20 changelog,
20 changelog,
21 cmdutil,
21 cmdutil,
22 error,
22 error,
23 filelog,
23 filelog,
24 localrepo,
24 localrepo,
25 manifest,
25 manifest,
26 mdiff,
26 mdiff,
27 pathutil,
27 pathutil,
28 pycompat,
28 pycompat,
29 revlog,
29 revlog,
30 util,
30 util,
31 vfs as vfsmod,
31 vfs as vfsmod,
32 )
32 )
33
33
34 class unionrevlog(revlog.revlog):
34 class unionrevlog(revlog.revlog):
35 def __init__(self, opener, indexfile, revlog2, linkmapper):
35 def __init__(self, opener, indexfile, revlog2, linkmapper):
36 # How it works:
36 # How it works:
37 # To retrieve a revision, we just need to know the node id so we can
37 # To retrieve a revision, we just need to know the node id so we can
38 # look it up in revlog2.
38 # look it up in revlog2.
39 #
39 #
40 # To differentiate a rev in the second revlog from a rev in the revlog,
40 # To differentiate a rev in the second revlog from a rev in the revlog,
41 # we check revision against repotiprev.
41 # we check revision against repotiprev.
42 opener = vfsmod.readonlyvfs(opener)
42 opener = vfsmod.readonlyvfs(opener)
43 revlog.revlog.__init__(self, opener, indexfile)
43 revlog.revlog.__init__(self, opener, indexfile)
44 self.revlog2 = revlog2
44 self.revlog2 = revlog2
45
45
46 n = len(self)
46 n = len(self)
47 self.repotiprev = n - 1
47 self.repotiprev = n - 1
48 self.bundlerevs = set() # used by 'bundle()' revset expression
48 self.bundlerevs = set() # used by 'bundle()' revset expression
49 for rev2 in self.revlog2:
49 for rev2 in self.revlog2:
50 rev = self.revlog2.index[rev2]
50 rev = self.revlog2.index[rev2]
51 # rev numbers - in revlog2, very different from self.rev
51 # rev numbers - in revlog2, very different from self.rev
52 _start, _csize, _rsize, base, linkrev, p1rev, p2rev, node = rev
52 _start, _csize, _rsize, base, linkrev, p1rev, p2rev, node = rev
53 flags = _start & 0xFFFF
53 flags = _start & 0xFFFF
54
54
55 if linkmapper is None: # link is to same revlog
55 if linkmapper is None: # link is to same revlog
56 assert linkrev == rev2 # we never link back
56 assert linkrev == rev2 # we never link back
57 link = n
57 link = n
58 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
58 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
59 link = linkmapper(linkrev)
59 link = linkmapper(linkrev)
60
60
61 if linkmapper is not None: # link is to same revlog
61 if linkmapper is not None: # link is to same revlog
62 base = linkmapper(base)
62 base = linkmapper(base)
63
63
64 if node in self.nodemap:
64 if node in self.nodemap:
65 # this happens for the common revlog revisions
65 # this happens for the common revlog revisions
66 self.bundlerevs.add(self.nodemap[node])
66 self.bundlerevs.add(self.nodemap[node])
67 continue
67 continue
68
68
69 p1node = self.revlog2.node(p1rev)
69 p1node = self.revlog2.node(p1rev)
70 p2node = self.revlog2.node(p2rev)
70 p2node = self.revlog2.node(p2rev)
71
71
72 e = (flags, None, None, base,
72 e = (flags, None, None, base,
73 link, self.rev(p1node), self.rev(p2node), node)
73 link, self.rev(p1node), self.rev(p2node), node)
74 self.index.insert(-1, e)
74 self.index.insert(-1, e)
75 self.nodemap[node] = n
75 self.nodemap[node] = n
76 self.bundlerevs.add(n)
76 self.bundlerevs.add(n)
77 n += 1
77 n += 1
78
78
79 def _chunk(self, rev):
79 def _chunk(self, rev):
80 if rev <= self.repotiprev:
80 if rev <= self.repotiprev:
81 return revlog.revlog._chunk(self, rev)
81 return revlog.revlog._chunk(self, rev)
82 return self.revlog2._chunk(self.node(rev))
82 return self.revlog2._chunk(self.node(rev))
83
83
84 def revdiff(self, rev1, rev2):
84 def revdiff(self, rev1, rev2):
85 """return or calculate a delta between two revisions"""
85 """return or calculate a delta between two revisions"""
86 if rev1 > self.repotiprev and rev2 > self.repotiprev:
86 if rev1 > self.repotiprev and rev2 > self.repotiprev:
87 return self.revlog2.revdiff(
87 return self.revlog2.revdiff(
88 self.revlog2.rev(self.node(rev1)),
88 self.revlog2.rev(self.node(rev1)),
89 self.revlog2.rev(self.node(rev2)))
89 self.revlog2.rev(self.node(rev2)))
90 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
90 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
91 return self.baserevdiff(rev1, rev2)
91 return self.baserevdiff(rev1, rev2)
92
92
93 return mdiff.textdiff(self.revision(rev1), self.revision(rev2))
93 return mdiff.textdiff(self.revision(rev1), self.revision(rev2))
94
94
95 def revision(self, nodeorrev, raw=False):
95 def revision(self, nodeorrev, _df=None, raw=False):
96 """return an uncompressed revision of a given node or revision
96 """return an uncompressed revision of a given node or revision
97 number.
97 number.
98 """
98 """
99 if isinstance(nodeorrev, int):
99 if isinstance(nodeorrev, int):
100 rev = nodeorrev
100 rev = nodeorrev
101 node = self.node(rev)
101 node = self.node(rev)
102 else:
102 else:
103 node = nodeorrev
103 node = nodeorrev
104 rev = self.rev(node)
104 rev = self.rev(node)
105
105
106 if node == nullid:
106 if node == nullid:
107 return ""
107 return ""
108
108
109 if rev > self.repotiprev:
109 if rev > self.repotiprev:
110 text = self.revlog2.revision(node)
110 text = self.revlog2.revision(node)
111 self._cache = (node, rev, text)
111 self._cache = (node, rev, text)
112 else:
112 else:
113 text = self.baserevision(rev)
113 text = self.baserevision(rev)
114 # already cached
114 # already cached
115 return text
115 return text
116
116
117 def baserevision(self, nodeorrev):
117 def baserevision(self, nodeorrev):
118 # Revlog subclasses may override 'revision' method to modify format of
118 # Revlog subclasses may override 'revision' method to modify format of
119 # content retrieved from revlog. To use unionrevlog with such class one
119 # content retrieved from revlog. To use unionrevlog with such class one
120 # needs to override 'baserevision' and make more specific call here.
120 # needs to override 'baserevision' and make more specific call here.
121 return revlog.revlog.revision(self, nodeorrev)
121 return revlog.revlog.revision(self, nodeorrev)
122
122
123 def baserevdiff(self, rev1, rev2):
123 def baserevdiff(self, rev1, rev2):
124 # Exists for the same purpose as baserevision.
124 # Exists for the same purpose as baserevision.
125 return revlog.revlog.revdiff(self, rev1, rev2)
125 return revlog.revlog.revdiff(self, rev1, rev2)
126
126
127 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
127 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
128 raise NotImplementedError
128 raise NotImplementedError
129 def addgroup(self, deltas, transaction, addrevisioncb=None):
129 def addgroup(self, deltas, transaction, addrevisioncb=None):
130 raise NotImplementedError
130 raise NotImplementedError
131 def strip(self, rev, minlink):
131 def strip(self, rev, minlink):
132 raise NotImplementedError
132 raise NotImplementedError
133 def checksize(self):
133 def checksize(self):
134 raise NotImplementedError
134 raise NotImplementedError
135
135
136 class unionchangelog(unionrevlog, changelog.changelog):
136 class unionchangelog(unionrevlog, changelog.changelog):
137 def __init__(self, opener, opener2):
137 def __init__(self, opener, opener2):
138 changelog.changelog.__init__(self, opener)
138 changelog.changelog.__init__(self, opener)
139 linkmapper = None
139 linkmapper = None
140 changelog2 = changelog.changelog(opener2)
140 changelog2 = changelog.changelog(opener2)
141 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
141 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
142 linkmapper)
142 linkmapper)
143
143
144 def baserevision(self, nodeorrev):
144 def baserevision(self, nodeorrev):
145 # Although changelog doesn't override 'revision' method, some extensions
145 # Although changelog doesn't override 'revision' method, some extensions
146 # may replace this class with another that does. Same story with
146 # may replace this class with another that does. Same story with
147 # manifest and filelog classes.
147 # manifest and filelog classes.
148 return changelog.changelog.revision(self, nodeorrev)
148 return changelog.changelog.revision(self, nodeorrev)
149
149
150 def baserevdiff(self, rev1, rev2):
150 def baserevdiff(self, rev1, rev2):
151 return changelog.changelog.revdiff(self, rev1, rev2)
151 return changelog.changelog.revdiff(self, rev1, rev2)
152
152
153 class unionmanifest(unionrevlog, manifest.manifestrevlog):
153 class unionmanifest(unionrevlog, manifest.manifestrevlog):
154 def __init__(self, opener, opener2, linkmapper):
154 def __init__(self, opener, opener2, linkmapper):
155 manifest.manifestrevlog.__init__(self, opener)
155 manifest.manifestrevlog.__init__(self, opener)
156 manifest2 = manifest.manifestrevlog(opener2)
156 manifest2 = manifest.manifestrevlog(opener2)
157 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
157 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
158 linkmapper)
158 linkmapper)
159
159
160 def baserevision(self, nodeorrev):
160 def baserevision(self, nodeorrev):
161 return manifest.manifestrevlog.revision(self, nodeorrev)
161 return manifest.manifestrevlog.revision(self, nodeorrev)
162
162
163 def baserevdiff(self, rev1, rev2):
163 def baserevdiff(self, rev1, rev2):
164 return manifest.manifestrevlog.revdiff(self, rev1, rev2)
164 return manifest.manifestrevlog.revdiff(self, rev1, rev2)
165
165
166 class unionfilelog(unionrevlog, filelog.filelog):
166 class unionfilelog(filelog.filelog):
167 def __init__(self, opener, path, opener2, linkmapper, repo):
167 def __init__(self, opener, path, opener2, linkmapper, repo):
168 filelog.filelog.__init__(self, opener, path)
168 filelog.filelog.__init__(self, opener, path)
169 filelog2 = filelog.filelog(opener2, path)
169 filelog2 = filelog.filelog(opener2, path)
170 unionrevlog.__init__(self, opener, self.indexfile, filelog2,
170 self._revlog = unionrevlog(opener, self.indexfile,
171 linkmapper)
171 filelog2._revlog, linkmapper)
172 self._repo = repo
172 self._repo = repo
173 self.repotiprev = self._revlog.repotiprev
174 self.revlog2 = self._revlog.revlog2
173
175
174 def baserevision(self, nodeorrev):
176 def baserevision(self, nodeorrev):
175 return filelog.filelog.revision(self, nodeorrev)
177 return filelog.filelog.revision(self, nodeorrev)
176
178
177 def baserevdiff(self, rev1, rev2):
179 def baserevdiff(self, rev1, rev2):
178 return filelog.filelog.revdiff(self, rev1, rev2)
180 return filelog.filelog.revdiff(self, rev1, rev2)
179
181
180 def iscensored(self, rev):
182 def iscensored(self, rev):
181 """Check if a revision is censored."""
183 """Check if a revision is censored."""
182 if rev <= self.repotiprev:
184 if rev <= self.repotiprev:
183 return filelog.filelog.iscensored(self, rev)
185 return filelog.filelog.iscensored(self, rev)
184 node = self.node(rev)
186 node = self.node(rev)
185 return self.revlog2.iscensored(self.revlog2.rev(node))
187 return self.revlog2.iscensored(self.revlog2.rev(node))
186
188
187 class unionpeer(localrepo.localpeer):
189 class unionpeer(localrepo.localpeer):
188 def canpush(self):
190 def canpush(self):
189 return False
191 return False
190
192
191 class unionrepository(localrepo.localrepository):
193 class unionrepository(localrepo.localrepository):
192 def __init__(self, ui, path, path2):
194 def __init__(self, ui, path, path2):
193 localrepo.localrepository.__init__(self, ui, path)
195 localrepo.localrepository.__init__(self, ui, path)
194 self.ui.setconfig('phases', 'publish', False, 'unionrepo')
196 self.ui.setconfig('phases', 'publish', False, 'unionrepo')
195
197
196 self._url = 'union:%s+%s' % (util.expandpath(path),
198 self._url = 'union:%s+%s' % (util.expandpath(path),
197 util.expandpath(path2))
199 util.expandpath(path2))
198 self.repo2 = localrepo.localrepository(ui, path2)
200 self.repo2 = localrepo.localrepository(ui, path2)
199
201
200 @localrepo.unfilteredpropertycache
202 @localrepo.unfilteredpropertycache
201 def changelog(self):
203 def changelog(self):
202 return unionchangelog(self.svfs, self.repo2.svfs)
204 return unionchangelog(self.svfs, self.repo2.svfs)
203
205
204 def _clrev(self, rev2):
206 def _clrev(self, rev2):
205 """map from repo2 changelog rev to temporary rev in self.changelog"""
207 """map from repo2 changelog rev to temporary rev in self.changelog"""
206 node = self.repo2.changelog.node(rev2)
208 node = self.repo2.changelog.node(rev2)
207 return self.changelog.rev(node)
209 return self.changelog.rev(node)
208
210
209 def _constructmanifest(self):
211 def _constructmanifest(self):
210 return unionmanifest(self.svfs, self.repo2.svfs,
212 return unionmanifest(self.svfs, self.repo2.svfs,
211 self.unfiltered()._clrev)
213 self.unfiltered()._clrev)
212
214
213 def url(self):
215 def url(self):
214 return self._url
216 return self._url
215
217
216 def file(self, f):
218 def file(self, f):
217 return unionfilelog(self.svfs, f, self.repo2.svfs,
219 return unionfilelog(self.svfs, f, self.repo2.svfs,
218 self.unfiltered()._clrev, self)
220 self.unfiltered()._clrev, self)
219
221
220 def close(self):
222 def close(self):
221 self.repo2.close()
223 self.repo2.close()
222
224
223 def cancopy(self):
225 def cancopy(self):
224 return False
226 return False
225
227
226 def peer(self):
228 def peer(self):
227 return unionpeer(self)
229 return unionpeer(self)
228
230
229 def getcwd(self):
231 def getcwd(self):
230 return pycompat.getcwd() # always outside the repo
232 return pycompat.getcwd() # always outside the repo
231
233
232 def instance(ui, path, create):
234 def instance(ui, path, create):
233 if create:
235 if create:
234 raise error.Abort(_('cannot create new union repository'))
236 raise error.Abort(_('cannot create new union repository'))
235 parentpath = ui.config("bundle", "mainreporoot")
237 parentpath = ui.config("bundle", "mainreporoot")
236 if not parentpath:
238 if not parentpath:
237 # try to find the correct path to the working directory repo
239 # try to find the correct path to the working directory repo
238 parentpath = cmdutil.findrepo(pycompat.getcwd())
240 parentpath = cmdutil.findrepo(pycompat.getcwd())
239 if parentpath is None:
241 if parentpath is None:
240 parentpath = ''
242 parentpath = ''
241 if parentpath:
243 if parentpath:
242 # Try to make the full path relative so we get a nice, short URL.
244 # Try to make the full path relative so we get a nice, short URL.
243 # In particular, we don't want temp dir names in test outputs.
245 # In particular, we don't want temp dir names in test outputs.
244 cwd = pycompat.getcwd()
246 cwd = pycompat.getcwd()
245 if parentpath == cwd:
247 if parentpath == cwd:
246 parentpath = ''
248 parentpath = ''
247 else:
249 else:
248 cwd = pathutil.normasprefix(cwd)
250 cwd = pathutil.normasprefix(cwd)
249 if parentpath.startswith(cwd):
251 if parentpath.startswith(cwd):
250 parentpath = parentpath[len(cwd):]
252 parentpath = parentpath[len(cwd):]
251 if path.startswith('union:'):
253 if path.startswith('union:'):
252 s = path.split(":", 1)[1].split("+", 1)
254 s = path.split(":", 1)[1].split("+", 1)
253 if len(s) == 1:
255 if len(s) == 1:
254 repopath, repopath2 = parentpath, s[0]
256 repopath, repopath2 = parentpath, s[0]
255 else:
257 else:
256 repopath, repopath2 = s
258 repopath, repopath2 = s
257 else:
259 else:
258 repopath, repopath2 = parentpath, path
260 repopath, repopath2 = parentpath, path
259 return unionrepository(ui, repopath, repopath2)
261 return unionrepository(ui, repopath, repopath2)
General Comments 0
You need to be logged in to leave comments. Login now