##// END OF EJS Templates
manifest: make uses of _mancache aware of contexts...
Durham Goode -
r29908:bb3281b3 default
parent child Browse files
Show More
@@ -1,554 +1,554 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 revlog,
38 revlog,
39 scmutil,
39 scmutil,
40 util,
40 util,
41 )
41 )
42
42
43 class bundlerevlog(revlog.revlog):
43 class bundlerevlog(revlog.revlog):
44 def __init__(self, opener, indexfile, bundle, linkmapper):
44 def __init__(self, opener, indexfile, bundle, linkmapper):
45 # How it works:
45 # How it works:
46 # To retrieve a revision, we need to know the offset of the revision in
46 # To retrieve a revision, we need to know the offset of the revision in
47 # the bundle (an unbundle object). We store this offset in the index
47 # the bundle (an unbundle object). We store this offset in the index
48 # (start). The base of the delta is stored in the base field.
48 # (start). The base of the delta is stored in the base field.
49 #
49 #
50 # To differentiate a rev in the bundle from a rev in the revlog, we
50 # To differentiate a rev in the bundle from a rev in the revlog, we
51 # check revision against repotiprev.
51 # check revision against repotiprev.
52 opener = scmutil.readonlyvfs(opener)
52 opener = scmutil.readonlyvfs(opener)
53 revlog.revlog.__init__(self, opener, indexfile)
53 revlog.revlog.__init__(self, opener, indexfile)
54 self.bundle = bundle
54 self.bundle = bundle
55 n = len(self)
55 n = len(self)
56 self.repotiprev = n - 1
56 self.repotiprev = n - 1
57 chain = None
57 chain = None
58 self.bundlerevs = set() # used by 'bundle()' revset expression
58 self.bundlerevs = set() # used by 'bundle()' revset expression
59 getchunk = lambda: bundle.deltachunk(chain)
59 getchunk = lambda: bundle.deltachunk(chain)
60 for chunkdata in iter(getchunk, {}):
60 for chunkdata in iter(getchunk, {}):
61 node = chunkdata['node']
61 node = chunkdata['node']
62 p1 = chunkdata['p1']
62 p1 = chunkdata['p1']
63 p2 = chunkdata['p2']
63 p2 = chunkdata['p2']
64 cs = chunkdata['cs']
64 cs = chunkdata['cs']
65 deltabase = chunkdata['deltabase']
65 deltabase = chunkdata['deltabase']
66 delta = chunkdata['delta']
66 delta = chunkdata['delta']
67
67
68 size = len(delta)
68 size = len(delta)
69 start = bundle.tell() - size
69 start = bundle.tell() - size
70
70
71 link = linkmapper(cs)
71 link = linkmapper(cs)
72 if node in self.nodemap:
72 if node in self.nodemap:
73 # this can happen if two branches make the same change
73 # this can happen if two branches make the same change
74 chain = node
74 chain = node
75 self.bundlerevs.add(self.nodemap[node])
75 self.bundlerevs.add(self.nodemap[node])
76 continue
76 continue
77
77
78 for p in (p1, p2):
78 for p in (p1, p2):
79 if p not in self.nodemap:
79 if p not in self.nodemap:
80 raise error.LookupError(p, self.indexfile,
80 raise error.LookupError(p, self.indexfile,
81 _("unknown parent"))
81 _("unknown parent"))
82
82
83 if deltabase not in self.nodemap:
83 if deltabase not in self.nodemap:
84 raise LookupError(deltabase, self.indexfile,
84 raise LookupError(deltabase, self.indexfile,
85 _('unknown delta base'))
85 _('unknown delta base'))
86
86
87 baserev = self.rev(deltabase)
87 baserev = self.rev(deltabase)
88 # start, size, full unc. size, base (unused), link, p1, p2, node
88 # start, size, full unc. size, base (unused), link, p1, p2, node
89 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
89 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
90 self.rev(p1), self.rev(p2), node)
90 self.rev(p1), self.rev(p2), node)
91 self.index.insert(-1, e)
91 self.index.insert(-1, e)
92 self.nodemap[node] = n
92 self.nodemap[node] = n
93 self.bundlerevs.add(n)
93 self.bundlerevs.add(n)
94 chain = node
94 chain = node
95 n += 1
95 n += 1
96
96
97 def _chunk(self, rev):
97 def _chunk(self, rev):
98 # Warning: in case of bundle, the diff is against what we stored as
98 # Warning: in case of bundle, the diff is against what we stored as
99 # delta base, not against rev - 1
99 # delta base, not against rev - 1
100 # XXX: could use some caching
100 # XXX: could use some caching
101 if rev <= self.repotiprev:
101 if rev <= self.repotiprev:
102 return revlog.revlog._chunk(self, rev)
102 return revlog.revlog._chunk(self, rev)
103 self.bundle.seek(self.start(rev))
103 self.bundle.seek(self.start(rev))
104 return self.bundle.read(self.length(rev))
104 return self.bundle.read(self.length(rev))
105
105
106 def revdiff(self, rev1, rev2):
106 def revdiff(self, rev1, rev2):
107 """return or calculate a delta between two revisions"""
107 """return or calculate a delta between two revisions"""
108 if rev1 > self.repotiprev and rev2 > self.repotiprev:
108 if rev1 > self.repotiprev and rev2 > self.repotiprev:
109 # hot path for bundle
109 # hot path for bundle
110 revb = self.index[rev2][3]
110 revb = self.index[rev2][3]
111 if revb == rev1:
111 if revb == rev1:
112 return self._chunk(rev2)
112 return self._chunk(rev2)
113 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
113 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
114 return revlog.revlog.revdiff(self, rev1, rev2)
114 return revlog.revlog.revdiff(self, rev1, rev2)
115
115
116 return mdiff.textdiff(self.revision(self.node(rev1)),
116 return mdiff.textdiff(self.revision(self.node(rev1)),
117 self.revision(self.node(rev2)))
117 self.revision(self.node(rev2)))
118
118
119 def revision(self, nodeorrev):
119 def revision(self, nodeorrev):
120 """return an uncompressed revision of a given node or revision
120 """return an uncompressed revision of a given node or revision
121 number.
121 number.
122 """
122 """
123 if isinstance(nodeorrev, int):
123 if isinstance(nodeorrev, int):
124 rev = nodeorrev
124 rev = nodeorrev
125 node = self.node(rev)
125 node = self.node(rev)
126 else:
126 else:
127 node = nodeorrev
127 node = nodeorrev
128 rev = self.rev(node)
128 rev = self.rev(node)
129
129
130 if node == nullid:
130 if node == nullid:
131 return ""
131 return ""
132
132
133 text = None
133 text = None
134 chain = []
134 chain = []
135 iterrev = rev
135 iterrev = rev
136 # reconstruct the revision if it is from a changegroup
136 # reconstruct the revision if it is from a changegroup
137 while iterrev > self.repotiprev:
137 while iterrev > self.repotiprev:
138 if self._cache and self._cache[1] == iterrev:
138 if self._cache and self._cache[1] == iterrev:
139 text = self._cache[2]
139 text = self._cache[2]
140 break
140 break
141 chain.append(iterrev)
141 chain.append(iterrev)
142 iterrev = self.index[iterrev][3]
142 iterrev = self.index[iterrev][3]
143 if text is None:
143 if text is None:
144 text = self.baserevision(iterrev)
144 text = self.baserevision(iterrev)
145
145
146 while chain:
146 while chain:
147 delta = self._chunk(chain.pop())
147 delta = self._chunk(chain.pop())
148 text = mdiff.patches(text, [delta])
148 text = mdiff.patches(text, [delta])
149
149
150 self._checkhash(text, node, rev)
150 self._checkhash(text, node, rev)
151 self._cache = (node, rev, text)
151 self._cache = (node, rev, text)
152 return text
152 return text
153
153
154 def baserevision(self, nodeorrev):
154 def baserevision(self, nodeorrev):
155 # Revlog subclasses may override 'revision' method to modify format of
155 # Revlog subclasses may override 'revision' method to modify format of
156 # content retrieved from revlog. To use bundlerevlog with such class one
156 # content retrieved from revlog. To use bundlerevlog with such class one
157 # needs to override 'baserevision' and make more specific call here.
157 # needs to override 'baserevision' and make more specific call here.
158 return revlog.revlog.revision(self, nodeorrev)
158 return revlog.revlog.revision(self, nodeorrev)
159
159
160 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
160 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
161 raise NotImplementedError
161 raise NotImplementedError
162 def addgroup(self, revs, linkmapper, transaction):
162 def addgroup(self, revs, linkmapper, transaction):
163 raise NotImplementedError
163 raise NotImplementedError
164 def strip(self, rev, minlink):
164 def strip(self, rev, minlink):
165 raise NotImplementedError
165 raise NotImplementedError
166 def checksize(self):
166 def checksize(self):
167 raise NotImplementedError
167 raise NotImplementedError
168
168
169 class bundlechangelog(bundlerevlog, changelog.changelog):
169 class bundlechangelog(bundlerevlog, changelog.changelog):
170 def __init__(self, opener, bundle):
170 def __init__(self, opener, bundle):
171 changelog.changelog.__init__(self, opener)
171 changelog.changelog.__init__(self, opener)
172 linkmapper = lambda x: x
172 linkmapper = lambda x: x
173 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
173 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
174 linkmapper)
174 linkmapper)
175
175
176 def baserevision(self, nodeorrev):
176 def baserevision(self, nodeorrev):
177 # Although changelog doesn't override 'revision' method, some extensions
177 # Although changelog doesn't override 'revision' method, some extensions
178 # may replace this class with another that does. Same story with
178 # may replace this class with another that does. Same story with
179 # manifest and filelog classes.
179 # manifest and filelog classes.
180
180
181 # This bypasses filtering on changelog.node() and rev() because we need
181 # This bypasses filtering on changelog.node() and rev() because we need
182 # revision text of the bundle base even if it is hidden.
182 # revision text of the bundle base even if it is hidden.
183 oldfilter = self.filteredrevs
183 oldfilter = self.filteredrevs
184 try:
184 try:
185 self.filteredrevs = ()
185 self.filteredrevs = ()
186 return changelog.changelog.revision(self, nodeorrev)
186 return changelog.changelog.revision(self, nodeorrev)
187 finally:
187 finally:
188 self.filteredrevs = oldfilter
188 self.filteredrevs = oldfilter
189
189
190 class bundlemanifest(bundlerevlog, manifest.manifest):
190 class bundlemanifest(bundlerevlog, manifest.manifest):
191 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
191 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
192 manifest.manifest.__init__(self, opener, dir=dir)
192 manifest.manifest.__init__(self, opener, dir=dir)
193 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
193 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
194 linkmapper)
194 linkmapper)
195 if dirlogstarts is None:
195 if dirlogstarts is None:
196 dirlogstarts = {}
196 dirlogstarts = {}
197 if self.bundle.version == "03":
197 if self.bundle.version == "03":
198 dirlogstarts = _getfilestarts(self.bundle)
198 dirlogstarts = _getfilestarts(self.bundle)
199 self._dirlogstarts = dirlogstarts
199 self._dirlogstarts = dirlogstarts
200 self._linkmapper = linkmapper
200 self._linkmapper = linkmapper
201
201
202 def baserevision(self, nodeorrev):
202 def baserevision(self, nodeorrev):
203 node = nodeorrev
203 node = nodeorrev
204 if isinstance(node, int):
204 if isinstance(node, int):
205 node = self.node(node)
205 node = self.node(node)
206
206
207 if node in self._mancache:
207 if node in self.fulltextcache:
208 result = self._mancache[node].text()
208 result = self.fulltextcache[node].tostring()
209 else:
209 else:
210 result = manifest.manifest.revision(self, nodeorrev)
210 result = manifest.manifest.revision(self, nodeorrev)
211 return result
211 return result
212
212
213 def dirlog(self, d):
213 def dirlog(self, d):
214 if d in self._dirlogstarts:
214 if d in self._dirlogstarts:
215 self.bundle.seek(self._dirlogstarts[d])
215 self.bundle.seek(self._dirlogstarts[d])
216 return bundlemanifest(
216 return bundlemanifest(
217 self.opener, self.bundle, self._linkmapper,
217 self.opener, self.bundle, self._linkmapper,
218 self._dirlogstarts, dir=d)
218 self._dirlogstarts, dir=d)
219 return super(bundlemanifest, self).dirlog(d)
219 return super(bundlemanifest, self).dirlog(d)
220
220
221 class bundlefilelog(bundlerevlog, filelog.filelog):
221 class bundlefilelog(bundlerevlog, filelog.filelog):
222 def __init__(self, opener, path, bundle, linkmapper):
222 def __init__(self, opener, path, bundle, linkmapper):
223 filelog.filelog.__init__(self, opener, path)
223 filelog.filelog.__init__(self, opener, path)
224 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
224 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
225 linkmapper)
225 linkmapper)
226
226
227 def baserevision(self, nodeorrev):
227 def baserevision(self, nodeorrev):
228 return filelog.filelog.revision(self, nodeorrev)
228 return filelog.filelog.revision(self, nodeorrev)
229
229
230 class bundlepeer(localrepo.localpeer):
230 class bundlepeer(localrepo.localpeer):
231 def canpush(self):
231 def canpush(self):
232 return False
232 return False
233
233
234 class bundlephasecache(phases.phasecache):
234 class bundlephasecache(phases.phasecache):
235 def __init__(self, *args, **kwargs):
235 def __init__(self, *args, **kwargs):
236 super(bundlephasecache, self).__init__(*args, **kwargs)
236 super(bundlephasecache, self).__init__(*args, **kwargs)
237 if util.safehasattr(self, 'opener'):
237 if util.safehasattr(self, 'opener'):
238 self.opener = scmutil.readonlyvfs(self.opener)
238 self.opener = scmutil.readonlyvfs(self.opener)
239
239
240 def write(self):
240 def write(self):
241 raise NotImplementedError
241 raise NotImplementedError
242
242
243 def _write(self, fp):
243 def _write(self, fp):
244 raise NotImplementedError
244 raise NotImplementedError
245
245
246 def _updateroots(self, phase, newroots, tr):
246 def _updateroots(self, phase, newroots, tr):
247 self.phaseroots[phase] = newroots
247 self.phaseroots[phase] = newroots
248 self.invalidate()
248 self.invalidate()
249 self.dirty = True
249 self.dirty = True
250
250
251 def _getfilestarts(bundle):
251 def _getfilestarts(bundle):
252 bundlefilespos = {}
252 bundlefilespos = {}
253 for chunkdata in iter(bundle.filelogheader, {}):
253 for chunkdata in iter(bundle.filelogheader, {}):
254 fname = chunkdata['filename']
254 fname = chunkdata['filename']
255 bundlefilespos[fname] = bundle.tell()
255 bundlefilespos[fname] = bundle.tell()
256 for chunk in iter(lambda: bundle.deltachunk(None), {}):
256 for chunk in iter(lambda: bundle.deltachunk(None), {}):
257 pass
257 pass
258 return bundlefilespos
258 return bundlefilespos
259
259
260 class bundlerepository(localrepo.localrepository):
260 class bundlerepository(localrepo.localrepository):
261 def __init__(self, ui, path, bundlename):
261 def __init__(self, ui, path, bundlename):
262 def _writetempbundle(read, suffix, header=''):
262 def _writetempbundle(read, suffix, header=''):
263 """Write a temporary file to disk
263 """Write a temporary file to disk
264
264
265 This is closure because we need to make sure this tracked by
265 This is closure because we need to make sure this tracked by
266 self.tempfile for cleanup purposes."""
266 self.tempfile for cleanup purposes."""
267 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
267 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
268 suffix=".hg10un")
268 suffix=".hg10un")
269 self.tempfile = temp
269 self.tempfile = temp
270
270
271 with os.fdopen(fdtemp, 'wb') as fptemp:
271 with os.fdopen(fdtemp, 'wb') as fptemp:
272 fptemp.write(header)
272 fptemp.write(header)
273 while True:
273 while True:
274 chunk = read(2**18)
274 chunk = read(2**18)
275 if not chunk:
275 if not chunk:
276 break
276 break
277 fptemp.write(chunk)
277 fptemp.write(chunk)
278
278
279 return self.vfs.open(self.tempfile, mode="rb")
279 return self.vfs.open(self.tempfile, mode="rb")
280 self._tempparent = None
280 self._tempparent = None
281 try:
281 try:
282 localrepo.localrepository.__init__(self, ui, path)
282 localrepo.localrepository.__init__(self, ui, path)
283 except error.RepoError:
283 except error.RepoError:
284 self._tempparent = tempfile.mkdtemp()
284 self._tempparent = tempfile.mkdtemp()
285 localrepo.instance(ui, self._tempparent, 1)
285 localrepo.instance(ui, self._tempparent, 1)
286 localrepo.localrepository.__init__(self, ui, self._tempparent)
286 localrepo.localrepository.__init__(self, ui, self._tempparent)
287 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
287 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
288
288
289 if path:
289 if path:
290 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
290 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
291 else:
291 else:
292 self._url = 'bundle:' + bundlename
292 self._url = 'bundle:' + bundlename
293
293
294 self.tempfile = None
294 self.tempfile = None
295 f = util.posixfile(bundlename, "rb")
295 f = util.posixfile(bundlename, "rb")
296 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
296 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
297
297
298 if isinstance(self.bundle, bundle2.unbundle20):
298 if isinstance(self.bundle, bundle2.unbundle20):
299 cgstream = None
299 cgstream = None
300 for part in self.bundle.iterparts():
300 for part in self.bundle.iterparts():
301 if part.type == 'changegroup':
301 if part.type == 'changegroup':
302 if cgstream is not None:
302 if cgstream is not None:
303 raise NotImplementedError("can't process "
303 raise NotImplementedError("can't process "
304 "multiple changegroups")
304 "multiple changegroups")
305 cgstream = part
305 cgstream = part
306 version = part.params.get('version', '01')
306 version = part.params.get('version', '01')
307 legalcgvers = changegroup.supportedincomingversions(self)
307 legalcgvers = changegroup.supportedincomingversions(self)
308 if version not in legalcgvers:
308 if version not in legalcgvers:
309 msg = _('Unsupported changegroup version: %s')
309 msg = _('Unsupported changegroup version: %s')
310 raise error.Abort(msg % version)
310 raise error.Abort(msg % version)
311 if self.bundle.compressed():
311 if self.bundle.compressed():
312 cgstream = _writetempbundle(part.read,
312 cgstream = _writetempbundle(part.read,
313 ".cg%sun" % version)
313 ".cg%sun" % version)
314
314
315 if cgstream is None:
315 if cgstream is None:
316 raise error.Abort(_('No changegroups found'))
316 raise error.Abort(_('No changegroups found'))
317 cgstream.seek(0)
317 cgstream.seek(0)
318
318
319 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
319 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
320
320
321 elif self.bundle.compressed():
321 elif self.bundle.compressed():
322 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
322 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
323 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
323 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
324 bundlename,
324 bundlename,
325 self.vfs)
325 self.vfs)
326
326
327 # dict with the mapping 'filename' -> position in the bundle
327 # dict with the mapping 'filename' -> position in the bundle
328 self.bundlefilespos = {}
328 self.bundlefilespos = {}
329
329
330 self.firstnewrev = self.changelog.repotiprev + 1
330 self.firstnewrev = self.changelog.repotiprev + 1
331 phases.retractboundary(self, None, phases.draft,
331 phases.retractboundary(self, None, phases.draft,
332 [ctx.node() for ctx in self[self.firstnewrev:]])
332 [ctx.node() for ctx in self[self.firstnewrev:]])
333
333
334 @localrepo.unfilteredpropertycache
334 @localrepo.unfilteredpropertycache
335 def _phasecache(self):
335 def _phasecache(self):
336 return bundlephasecache(self, self._phasedefaults)
336 return bundlephasecache(self, self._phasedefaults)
337
337
338 @localrepo.unfilteredpropertycache
338 @localrepo.unfilteredpropertycache
339 def changelog(self):
339 def changelog(self):
340 # consume the header if it exists
340 # consume the header if it exists
341 self.bundle.changelogheader()
341 self.bundle.changelogheader()
342 c = bundlechangelog(self.svfs, self.bundle)
342 c = bundlechangelog(self.svfs, self.bundle)
343 self.manstart = self.bundle.tell()
343 self.manstart = self.bundle.tell()
344 return c
344 return c
345
345
346 @localrepo.unfilteredpropertycache
346 @localrepo.unfilteredpropertycache
347 def manifest(self):
347 def manifest(self):
348 self.bundle.seek(self.manstart)
348 self.bundle.seek(self.manstart)
349 # consume the header if it exists
349 # consume the header if it exists
350 self.bundle.manifestheader()
350 self.bundle.manifestheader()
351 linkmapper = self.unfiltered().changelog.rev
351 linkmapper = self.unfiltered().changelog.rev
352 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
352 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
353 self.filestart = self.bundle.tell()
353 self.filestart = self.bundle.tell()
354 return m
354 return m
355
355
356 @localrepo.unfilteredpropertycache
356 @localrepo.unfilteredpropertycache
357 def manstart(self):
357 def manstart(self):
358 self.changelog
358 self.changelog
359 return self.manstart
359 return self.manstart
360
360
361 @localrepo.unfilteredpropertycache
361 @localrepo.unfilteredpropertycache
362 def filestart(self):
362 def filestart(self):
363 self.manifest
363 self.manifest
364 return self.filestart
364 return self.filestart
365
365
366 def url(self):
366 def url(self):
367 return self._url
367 return self._url
368
368
369 def file(self, f):
369 def file(self, f):
370 if not self.bundlefilespos:
370 if not self.bundlefilespos:
371 self.bundle.seek(self.filestart)
371 self.bundle.seek(self.filestart)
372 self.bundlefilespos = _getfilestarts(self.bundle)
372 self.bundlefilespos = _getfilestarts(self.bundle)
373
373
374 if f in self.bundlefilespos:
374 if f in self.bundlefilespos:
375 self.bundle.seek(self.bundlefilespos[f])
375 self.bundle.seek(self.bundlefilespos[f])
376 linkmapper = self.unfiltered().changelog.rev
376 linkmapper = self.unfiltered().changelog.rev
377 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
377 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
378 else:
378 else:
379 return filelog.filelog(self.svfs, f)
379 return filelog.filelog(self.svfs, f)
380
380
381 def close(self):
381 def close(self):
382 """Close assigned bundle file immediately."""
382 """Close assigned bundle file immediately."""
383 self.bundlefile.close()
383 self.bundlefile.close()
384 if self.tempfile is not None:
384 if self.tempfile is not None:
385 self.vfs.unlink(self.tempfile)
385 self.vfs.unlink(self.tempfile)
386 if self._tempparent:
386 if self._tempparent:
387 shutil.rmtree(self._tempparent, True)
387 shutil.rmtree(self._tempparent, True)
388
388
389 def cancopy(self):
389 def cancopy(self):
390 return False
390 return False
391
391
392 def peer(self):
392 def peer(self):
393 return bundlepeer(self)
393 return bundlepeer(self)
394
394
395 def getcwd(self):
395 def getcwd(self):
396 return os.getcwd() # always outside the repo
396 return os.getcwd() # always outside the repo
397
397
398 # Check if parents exist in localrepo before setting
398 # Check if parents exist in localrepo before setting
399 def setparents(self, p1, p2=nullid):
399 def setparents(self, p1, p2=nullid):
400 p1rev = self.changelog.rev(p1)
400 p1rev = self.changelog.rev(p1)
401 p2rev = self.changelog.rev(p2)
401 p2rev = self.changelog.rev(p2)
402 msg = _("setting parent to node %s that only exists in the bundle\n")
402 msg = _("setting parent to node %s that only exists in the bundle\n")
403 if self.changelog.repotiprev < p1rev:
403 if self.changelog.repotiprev < p1rev:
404 self.ui.warn(msg % nodemod.hex(p1))
404 self.ui.warn(msg % nodemod.hex(p1))
405 if self.changelog.repotiprev < p2rev:
405 if self.changelog.repotiprev < p2rev:
406 self.ui.warn(msg % nodemod.hex(p2))
406 self.ui.warn(msg % nodemod.hex(p2))
407 return super(bundlerepository, self).setparents(p1, p2)
407 return super(bundlerepository, self).setparents(p1, p2)
408
408
409 def instance(ui, path, create):
409 def instance(ui, path, create):
410 if create:
410 if create:
411 raise error.Abort(_('cannot create new bundle repository'))
411 raise error.Abort(_('cannot create new bundle repository'))
412 # internal config: bundle.mainreporoot
412 # internal config: bundle.mainreporoot
413 parentpath = ui.config("bundle", "mainreporoot", "")
413 parentpath = ui.config("bundle", "mainreporoot", "")
414 if not parentpath:
414 if not parentpath:
415 # try to find the correct path to the working directory repo
415 # try to find the correct path to the working directory repo
416 parentpath = cmdutil.findrepo(os.getcwd())
416 parentpath = cmdutil.findrepo(os.getcwd())
417 if parentpath is None:
417 if parentpath is None:
418 parentpath = ''
418 parentpath = ''
419 if parentpath:
419 if parentpath:
420 # Try to make the full path relative so we get a nice, short URL.
420 # Try to make the full path relative so we get a nice, short URL.
421 # In particular, we don't want temp dir names in test outputs.
421 # In particular, we don't want temp dir names in test outputs.
422 cwd = os.getcwd()
422 cwd = os.getcwd()
423 if parentpath == cwd:
423 if parentpath == cwd:
424 parentpath = ''
424 parentpath = ''
425 else:
425 else:
426 cwd = pathutil.normasprefix(cwd)
426 cwd = pathutil.normasprefix(cwd)
427 if parentpath.startswith(cwd):
427 if parentpath.startswith(cwd):
428 parentpath = parentpath[len(cwd):]
428 parentpath = parentpath[len(cwd):]
429 u = util.url(path)
429 u = util.url(path)
430 path = u.localpath()
430 path = u.localpath()
431 if u.scheme == 'bundle':
431 if u.scheme == 'bundle':
432 s = path.split("+", 1)
432 s = path.split("+", 1)
433 if len(s) == 1:
433 if len(s) == 1:
434 repopath, bundlename = parentpath, s[0]
434 repopath, bundlename = parentpath, s[0]
435 else:
435 else:
436 repopath, bundlename = s
436 repopath, bundlename = s
437 else:
437 else:
438 repopath, bundlename = parentpath, path
438 repopath, bundlename = parentpath, path
439 return bundlerepository(ui, repopath, bundlename)
439 return bundlerepository(ui, repopath, bundlename)
440
440
441 class bundletransactionmanager(object):
441 class bundletransactionmanager(object):
442 def transaction(self):
442 def transaction(self):
443 return None
443 return None
444
444
445 def close(self):
445 def close(self):
446 raise NotImplementedError
446 raise NotImplementedError
447
447
448 def release(self):
448 def release(self):
449 raise NotImplementedError
449 raise NotImplementedError
450
450
451 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
451 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
452 force=False):
452 force=False):
453 '''obtains a bundle of changes incoming from other
453 '''obtains a bundle of changes incoming from other
454
454
455 "onlyheads" restricts the returned changes to those reachable from the
455 "onlyheads" restricts the returned changes to those reachable from the
456 specified heads.
456 specified heads.
457 "bundlename", if given, stores the bundle to this file path permanently;
457 "bundlename", if given, stores the bundle to this file path permanently;
458 otherwise it's stored to a temp file and gets deleted again when you call
458 otherwise it's stored to a temp file and gets deleted again when you call
459 the returned "cleanupfn".
459 the returned "cleanupfn".
460 "force" indicates whether to proceed on unrelated repos.
460 "force" indicates whether to proceed on unrelated repos.
461
461
462 Returns a tuple (local, csets, cleanupfn):
462 Returns a tuple (local, csets, cleanupfn):
463
463
464 "local" is a local repo from which to obtain the actual incoming
464 "local" is a local repo from which to obtain the actual incoming
465 changesets; it is a bundlerepo for the obtained bundle when the
465 changesets; it is a bundlerepo for the obtained bundle when the
466 original "other" is remote.
466 original "other" is remote.
467 "csets" lists the incoming changeset node ids.
467 "csets" lists the incoming changeset node ids.
468 "cleanupfn" must be called without arguments when you're done processing
468 "cleanupfn" must be called without arguments when you're done processing
469 the changes; it closes both the original "other" and the one returned
469 the changes; it closes both the original "other" and the one returned
470 here.
470 here.
471 '''
471 '''
472 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
472 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
473 force=force)
473 force=force)
474 common, incoming, rheads = tmp
474 common, incoming, rheads = tmp
475 if not incoming:
475 if not incoming:
476 try:
476 try:
477 if bundlename:
477 if bundlename:
478 os.unlink(bundlename)
478 os.unlink(bundlename)
479 except OSError:
479 except OSError:
480 pass
480 pass
481 return repo, [], other.close
481 return repo, [], other.close
482
482
483 commonset = set(common)
483 commonset = set(common)
484 rheads = [x for x in rheads if x not in commonset]
484 rheads = [x for x in rheads if x not in commonset]
485
485
486 bundle = None
486 bundle = None
487 bundlerepo = None
487 bundlerepo = None
488 localrepo = other.local()
488 localrepo = other.local()
489 if bundlename or not localrepo:
489 if bundlename or not localrepo:
490 # create a bundle (uncompressed if other repo is not local)
490 # create a bundle (uncompressed if other repo is not local)
491
491
492 # developer config: devel.legacy.exchange
492 # developer config: devel.legacy.exchange
493 legexc = ui.configlist('devel', 'legacy.exchange')
493 legexc = ui.configlist('devel', 'legacy.exchange')
494 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
494 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
495 canbundle2 = (not forcebundle1
495 canbundle2 = (not forcebundle1
496 and other.capable('getbundle')
496 and other.capable('getbundle')
497 and other.capable('bundle2'))
497 and other.capable('bundle2'))
498 if canbundle2:
498 if canbundle2:
499 kwargs = {}
499 kwargs = {}
500 kwargs['common'] = common
500 kwargs['common'] = common
501 kwargs['heads'] = rheads
501 kwargs['heads'] = rheads
502 kwargs['bundlecaps'] = exchange.caps20to10(repo)
502 kwargs['bundlecaps'] = exchange.caps20to10(repo)
503 kwargs['cg'] = True
503 kwargs['cg'] = True
504 b2 = other.getbundle('incoming', **kwargs)
504 b2 = other.getbundle('incoming', **kwargs)
505 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
505 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
506 bundlename)
506 bundlename)
507 else:
507 else:
508 if other.capable('getbundle'):
508 if other.capable('getbundle'):
509 cg = other.getbundle('incoming', common=common, heads=rheads)
509 cg = other.getbundle('incoming', common=common, heads=rheads)
510 elif onlyheads is None and not other.capable('changegroupsubset'):
510 elif onlyheads is None and not other.capable('changegroupsubset'):
511 # compat with older servers when pulling all remote heads
511 # compat with older servers when pulling all remote heads
512 cg = other.changegroup(incoming, "incoming")
512 cg = other.changegroup(incoming, "incoming")
513 rheads = None
513 rheads = None
514 else:
514 else:
515 cg = other.changegroupsubset(incoming, rheads, 'incoming')
515 cg = other.changegroupsubset(incoming, rheads, 'incoming')
516 if localrepo:
516 if localrepo:
517 bundletype = "HG10BZ"
517 bundletype = "HG10BZ"
518 else:
518 else:
519 bundletype = "HG10UN"
519 bundletype = "HG10UN"
520 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
520 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
521 bundletype)
521 bundletype)
522 # keep written bundle?
522 # keep written bundle?
523 if bundlename:
523 if bundlename:
524 bundle = None
524 bundle = None
525 if not localrepo:
525 if not localrepo:
526 # use the created uncompressed bundlerepo
526 # use the created uncompressed bundlerepo
527 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
527 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
528 fname)
528 fname)
529 # this repo contains local and other now, so filter out local again
529 # this repo contains local and other now, so filter out local again
530 common = repo.heads()
530 common = repo.heads()
531 if localrepo:
531 if localrepo:
532 # Part of common may be remotely filtered
532 # Part of common may be remotely filtered
533 # So use an unfiltered version
533 # So use an unfiltered version
534 # The discovery process probably need cleanup to avoid that
534 # The discovery process probably need cleanup to avoid that
535 localrepo = localrepo.unfiltered()
535 localrepo = localrepo.unfiltered()
536
536
537 csets = localrepo.changelog.findmissing(common, rheads)
537 csets = localrepo.changelog.findmissing(common, rheads)
538
538
539 if bundlerepo:
539 if bundlerepo:
540 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
540 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
541 remotephases = other.listkeys('phases')
541 remotephases = other.listkeys('phases')
542
542
543 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
543 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
544 pullop.trmanager = bundletransactionmanager()
544 pullop.trmanager = bundletransactionmanager()
545 exchange._pullapplyphases(pullop, remotephases)
545 exchange._pullapplyphases(pullop, remotephases)
546
546
547 def cleanup():
547 def cleanup():
548 if bundlerepo:
548 if bundlerepo:
549 bundlerepo.close()
549 bundlerepo.close()
550 if bundle:
550 if bundle:
551 os.unlink(bundle)
551 os.unlink(bundle)
552 other.close()
552 other.close()
553
553
554 return (localrepo, csets, cleanup)
554 return (localrepo, csets, cleanup)
@@ -1,1228 +1,1232 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision 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 import array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class _lazymanifest(dict):
107 class _lazymanifest(dict):
108 """This is the pure implementation of lazymanifest.
108 """This is the pure implementation of lazymanifest.
109
109
110 It has not been optimized *at all* and is not lazy.
110 It has not been optimized *at all* and is not lazy.
111 """
111 """
112
112
113 def __init__(self, data):
113 def __init__(self, data):
114 dict.__init__(self)
114 dict.__init__(self)
115 for f, n, fl in _parse(data):
115 for f, n, fl in _parse(data):
116 self[f] = n, fl
116 self[f] = n, fl
117
117
118 def __setitem__(self, k, v):
118 def __setitem__(self, k, v):
119 node, flag = v
119 node, flag = v
120 assert node is not None
120 assert node is not None
121 if len(node) > 21:
121 if len(node) > 21:
122 node = node[:21] # match c implementation behavior
122 node = node[:21] # match c implementation behavior
123 dict.__setitem__(self, k, (node, flag))
123 dict.__setitem__(self, k, (node, flag))
124
124
125 def __iter__(self):
125 def __iter__(self):
126 return iter(sorted(dict.keys(self)))
126 return iter(sorted(dict.keys(self)))
127
127
128 def iterkeys(self):
128 def iterkeys(self):
129 return iter(sorted(dict.keys(self)))
129 return iter(sorted(dict.keys(self)))
130
130
131 def iterentries(self):
131 def iterentries(self):
132 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
132 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
133
133
134 def copy(self):
134 def copy(self):
135 c = _lazymanifest('')
135 c = _lazymanifest('')
136 c.update(self)
136 c.update(self)
137 return c
137 return c
138
138
139 def diff(self, m2, clean=False):
139 def diff(self, m2, clean=False):
140 '''Finds changes between the current manifest and m2.'''
140 '''Finds changes between the current manifest and m2.'''
141 diff = {}
141 diff = {}
142
142
143 for fn, e1 in self.iteritems():
143 for fn, e1 in self.iteritems():
144 if fn not in m2:
144 if fn not in m2:
145 diff[fn] = e1, (None, '')
145 diff[fn] = e1, (None, '')
146 else:
146 else:
147 e2 = m2[fn]
147 e2 = m2[fn]
148 if e1 != e2:
148 if e1 != e2:
149 diff[fn] = e1, e2
149 diff[fn] = e1, e2
150 elif clean:
150 elif clean:
151 diff[fn] = None
151 diff[fn] = None
152
152
153 for fn, e2 in m2.iteritems():
153 for fn, e2 in m2.iteritems():
154 if fn not in self:
154 if fn not in self:
155 diff[fn] = (None, ''), e2
155 diff[fn] = (None, ''), e2
156
156
157 return diff
157 return diff
158
158
159 def filtercopy(self, filterfn):
159 def filtercopy(self, filterfn):
160 c = _lazymanifest('')
160 c = _lazymanifest('')
161 for f, n, fl in self.iterentries():
161 for f, n, fl in self.iterentries():
162 if filterfn(f):
162 if filterfn(f):
163 c[f] = n, fl
163 c[f] = n, fl
164 return c
164 return c
165
165
166 def text(self):
166 def text(self):
167 """Get the full data of this manifest as a bytestring."""
167 """Get the full data of this manifest as a bytestring."""
168 return _textv1(self.iterentries())
168 return _textv1(self.iterentries())
169
169
170 try:
170 try:
171 _lazymanifest = parsers.lazymanifest
171 _lazymanifest = parsers.lazymanifest
172 except AttributeError:
172 except AttributeError:
173 pass
173 pass
174
174
175 class manifestdict(object):
175 class manifestdict(object):
176 def __init__(self, data=''):
176 def __init__(self, data=''):
177 if data.startswith('\0'):
177 if data.startswith('\0'):
178 #_lazymanifest can not parse v2
178 #_lazymanifest can not parse v2
179 self._lm = _lazymanifest('')
179 self._lm = _lazymanifest('')
180 for f, n, fl in _parsev2(data):
180 for f, n, fl in _parsev2(data):
181 self._lm[f] = n, fl
181 self._lm[f] = n, fl
182 else:
182 else:
183 self._lm = _lazymanifest(data)
183 self._lm = _lazymanifest(data)
184
184
185 def __getitem__(self, key):
185 def __getitem__(self, key):
186 return self._lm[key][0]
186 return self._lm[key][0]
187
187
188 def find(self, key):
188 def find(self, key):
189 return self._lm[key]
189 return self._lm[key]
190
190
191 def __len__(self):
191 def __len__(self):
192 return len(self._lm)
192 return len(self._lm)
193
193
194 def __setitem__(self, key, node):
194 def __setitem__(self, key, node):
195 self._lm[key] = node, self.flags(key, '')
195 self._lm[key] = node, self.flags(key, '')
196
196
197 def __contains__(self, key):
197 def __contains__(self, key):
198 return key in self._lm
198 return key in self._lm
199
199
200 def __delitem__(self, key):
200 def __delitem__(self, key):
201 del self._lm[key]
201 del self._lm[key]
202
202
203 def __iter__(self):
203 def __iter__(self):
204 return self._lm.__iter__()
204 return self._lm.__iter__()
205
205
206 def iterkeys(self):
206 def iterkeys(self):
207 return self._lm.iterkeys()
207 return self._lm.iterkeys()
208
208
209 def keys(self):
209 def keys(self):
210 return list(self.iterkeys())
210 return list(self.iterkeys())
211
211
212 def filesnotin(self, m2):
212 def filesnotin(self, m2):
213 '''Set of files in this manifest that are not in the other'''
213 '''Set of files in this manifest that are not in the other'''
214 diff = self.diff(m2)
214 diff = self.diff(m2)
215 files = set(filepath
215 files = set(filepath
216 for filepath, hashflags in diff.iteritems()
216 for filepath, hashflags in diff.iteritems()
217 if hashflags[1][0] is None)
217 if hashflags[1][0] is None)
218 return files
218 return files
219
219
220 @propertycache
220 @propertycache
221 def _dirs(self):
221 def _dirs(self):
222 return util.dirs(self)
222 return util.dirs(self)
223
223
224 def dirs(self):
224 def dirs(self):
225 return self._dirs
225 return self._dirs
226
226
227 def hasdir(self, dir):
227 def hasdir(self, dir):
228 return dir in self._dirs
228 return dir in self._dirs
229
229
230 def _filesfastpath(self, match):
230 def _filesfastpath(self, match):
231 '''Checks whether we can correctly and quickly iterate over matcher
231 '''Checks whether we can correctly and quickly iterate over matcher
232 files instead of over manifest files.'''
232 files instead of over manifest files.'''
233 files = match.files()
233 files = match.files()
234 return (len(files) < 100 and (match.isexact() or
234 return (len(files) < 100 and (match.isexact() or
235 (match.prefix() and all(fn in self for fn in files))))
235 (match.prefix() and all(fn in self for fn in files))))
236
236
237 def walk(self, match):
237 def walk(self, match):
238 '''Generates matching file names.
238 '''Generates matching file names.
239
239
240 Equivalent to manifest.matches(match).iterkeys(), but without creating
240 Equivalent to manifest.matches(match).iterkeys(), but without creating
241 an entirely new manifest.
241 an entirely new manifest.
242
242
243 It also reports nonexistent files by marking them bad with match.bad().
243 It also reports nonexistent files by marking them bad with match.bad().
244 '''
244 '''
245 if match.always():
245 if match.always():
246 for f in iter(self):
246 for f in iter(self):
247 yield f
247 yield f
248 return
248 return
249
249
250 fset = set(match.files())
250 fset = set(match.files())
251
251
252 # avoid the entire walk if we're only looking for specific files
252 # avoid the entire walk if we're only looking for specific files
253 if self._filesfastpath(match):
253 if self._filesfastpath(match):
254 for fn in sorted(fset):
254 for fn in sorted(fset):
255 yield fn
255 yield fn
256 return
256 return
257
257
258 for fn in self:
258 for fn in self:
259 if fn in fset:
259 if fn in fset:
260 # specified pattern is the exact name
260 # specified pattern is the exact name
261 fset.remove(fn)
261 fset.remove(fn)
262 if match(fn):
262 if match(fn):
263 yield fn
263 yield fn
264
264
265 # for dirstate.walk, files=['.'] means "walk the whole tree".
265 # for dirstate.walk, files=['.'] means "walk the whole tree".
266 # follow that here, too
266 # follow that here, too
267 fset.discard('.')
267 fset.discard('.')
268
268
269 for fn in sorted(fset):
269 for fn in sorted(fset):
270 if not self.hasdir(fn):
270 if not self.hasdir(fn):
271 match.bad(fn, None)
271 match.bad(fn, None)
272
272
273 def matches(self, match):
273 def matches(self, match):
274 '''generate a new manifest filtered by the match argument'''
274 '''generate a new manifest filtered by the match argument'''
275 if match.always():
275 if match.always():
276 return self.copy()
276 return self.copy()
277
277
278 if self._filesfastpath(match):
278 if self._filesfastpath(match):
279 m = manifestdict()
279 m = manifestdict()
280 lm = self._lm
280 lm = self._lm
281 for fn in match.files():
281 for fn in match.files():
282 if fn in lm:
282 if fn in lm:
283 m._lm[fn] = lm[fn]
283 m._lm[fn] = lm[fn]
284 return m
284 return m
285
285
286 m = manifestdict()
286 m = manifestdict()
287 m._lm = self._lm.filtercopy(match)
287 m._lm = self._lm.filtercopy(match)
288 return m
288 return m
289
289
290 def diff(self, m2, clean=False):
290 def diff(self, m2, clean=False):
291 '''Finds changes between the current manifest and m2.
291 '''Finds changes between the current manifest and m2.
292
292
293 Args:
293 Args:
294 m2: the manifest to which this manifest should be compared.
294 m2: the manifest to which this manifest should be compared.
295 clean: if true, include files unchanged between these manifests
295 clean: if true, include files unchanged between these manifests
296 with a None value in the returned dictionary.
296 with a None value in the returned dictionary.
297
297
298 The result is returned as a dict with filename as key and
298 The result is returned as a dict with filename as key and
299 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
299 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
300 nodeid in the current/other manifest and fl1/fl2 is the flag
300 nodeid in the current/other manifest and fl1/fl2 is the flag
301 in the current/other manifest. Where the file does not exist,
301 in the current/other manifest. Where the file does not exist,
302 the nodeid will be None and the flags will be the empty
302 the nodeid will be None and the flags will be the empty
303 string.
303 string.
304 '''
304 '''
305 return self._lm.diff(m2._lm, clean)
305 return self._lm.diff(m2._lm, clean)
306
306
307 def setflag(self, key, flag):
307 def setflag(self, key, flag):
308 self._lm[key] = self[key], flag
308 self._lm[key] = self[key], flag
309
309
310 def get(self, key, default=None):
310 def get(self, key, default=None):
311 try:
311 try:
312 return self._lm[key][0]
312 return self._lm[key][0]
313 except KeyError:
313 except KeyError:
314 return default
314 return default
315
315
316 def flags(self, key, default=''):
316 def flags(self, key, default=''):
317 try:
317 try:
318 return self._lm[key][1]
318 return self._lm[key][1]
319 except KeyError:
319 except KeyError:
320 return default
320 return default
321
321
322 def copy(self):
322 def copy(self):
323 c = manifestdict()
323 c = manifestdict()
324 c._lm = self._lm.copy()
324 c._lm = self._lm.copy()
325 return c
325 return c
326
326
327 def iteritems(self):
327 def iteritems(self):
328 return (x[:2] for x in self._lm.iterentries())
328 return (x[:2] for x in self._lm.iterentries())
329
329
330 def iterentries(self):
330 def iterentries(self):
331 return self._lm.iterentries()
331 return self._lm.iterentries()
332
332
333 def text(self, usemanifestv2=False):
333 def text(self, usemanifestv2=False):
334 if usemanifestv2:
334 if usemanifestv2:
335 return _textv2(self._lm.iterentries())
335 return _textv2(self._lm.iterentries())
336 else:
336 else:
337 # use (probably) native version for v1
337 # use (probably) native version for v1
338 return self._lm.text()
338 return self._lm.text()
339
339
340 def fastdelta(self, base, changes):
340 def fastdelta(self, base, changes):
341 """Given a base manifest text as an array.array and a list of changes
341 """Given a base manifest text as an array.array and a list of changes
342 relative to that text, compute a delta that can be used by revlog.
342 relative to that text, compute a delta that can be used by revlog.
343 """
343 """
344 delta = []
344 delta = []
345 dstart = None
345 dstart = None
346 dend = None
346 dend = None
347 dline = [""]
347 dline = [""]
348 start = 0
348 start = 0
349 # zero copy representation of base as a buffer
349 # zero copy representation of base as a buffer
350 addbuf = util.buffer(base)
350 addbuf = util.buffer(base)
351
351
352 changes = list(changes)
352 changes = list(changes)
353 if len(changes) < 1000:
353 if len(changes) < 1000:
354 # start with a readonly loop that finds the offset of
354 # start with a readonly loop that finds the offset of
355 # each line and creates the deltas
355 # each line and creates the deltas
356 for f, todelete in changes:
356 for f, todelete in changes:
357 # bs will either be the index of the item or the insert point
357 # bs will either be the index of the item or the insert point
358 start, end = _msearch(addbuf, f, start)
358 start, end = _msearch(addbuf, f, start)
359 if not todelete:
359 if not todelete:
360 h, fl = self._lm[f]
360 h, fl = self._lm[f]
361 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
361 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
362 else:
362 else:
363 if start == end:
363 if start == end:
364 # item we want to delete was not found, error out
364 # item we want to delete was not found, error out
365 raise AssertionError(
365 raise AssertionError(
366 _("failed to remove %s from manifest") % f)
366 _("failed to remove %s from manifest") % f)
367 l = ""
367 l = ""
368 if dstart is not None and dstart <= start and dend >= start:
368 if dstart is not None and dstart <= start and dend >= start:
369 if dend < end:
369 if dend < end:
370 dend = end
370 dend = end
371 if l:
371 if l:
372 dline.append(l)
372 dline.append(l)
373 else:
373 else:
374 if dstart is not None:
374 if dstart is not None:
375 delta.append([dstart, dend, "".join(dline)])
375 delta.append([dstart, dend, "".join(dline)])
376 dstart = start
376 dstart = start
377 dend = end
377 dend = end
378 dline = [l]
378 dline = [l]
379
379
380 if dstart is not None:
380 if dstart is not None:
381 delta.append([dstart, dend, "".join(dline)])
381 delta.append([dstart, dend, "".join(dline)])
382 # apply the delta to the base, and get a delta for addrevision
382 # apply the delta to the base, and get a delta for addrevision
383 deltatext, arraytext = _addlistdelta(base, delta)
383 deltatext, arraytext = _addlistdelta(base, delta)
384 else:
384 else:
385 # For large changes, it's much cheaper to just build the text and
385 # For large changes, it's much cheaper to just build the text and
386 # diff it.
386 # diff it.
387 arraytext = array.array('c', self.text())
387 arraytext = array.array('c', self.text())
388 deltatext = mdiff.textdiff(base, arraytext)
388 deltatext = mdiff.textdiff(base, arraytext)
389
389
390 return arraytext, deltatext
390 return arraytext, deltatext
391
391
392 def _msearch(m, s, lo=0, hi=None):
392 def _msearch(m, s, lo=0, hi=None):
393 '''return a tuple (start, end) that says where to find s within m.
393 '''return a tuple (start, end) that says where to find s within m.
394
394
395 If the string is found m[start:end] are the line containing
395 If the string is found m[start:end] are the line containing
396 that string. If start == end the string was not found and
396 that string. If start == end the string was not found and
397 they indicate the proper sorted insertion point.
397 they indicate the proper sorted insertion point.
398
398
399 m should be a buffer or a string
399 m should be a buffer or a string
400 s is a string'''
400 s is a string'''
401 def advance(i, c):
401 def advance(i, c):
402 while i < lenm and m[i] != c:
402 while i < lenm and m[i] != c:
403 i += 1
403 i += 1
404 return i
404 return i
405 if not s:
405 if not s:
406 return (lo, lo)
406 return (lo, lo)
407 lenm = len(m)
407 lenm = len(m)
408 if not hi:
408 if not hi:
409 hi = lenm
409 hi = lenm
410 while lo < hi:
410 while lo < hi:
411 mid = (lo + hi) // 2
411 mid = (lo + hi) // 2
412 start = mid
412 start = mid
413 while start > 0 and m[start - 1] != '\n':
413 while start > 0 and m[start - 1] != '\n':
414 start -= 1
414 start -= 1
415 end = advance(start, '\0')
415 end = advance(start, '\0')
416 if m[start:end] < s:
416 if m[start:end] < s:
417 # we know that after the null there are 40 bytes of sha1
417 # we know that after the null there are 40 bytes of sha1
418 # this translates to the bisect lo = mid + 1
418 # this translates to the bisect lo = mid + 1
419 lo = advance(end + 40, '\n') + 1
419 lo = advance(end + 40, '\n') + 1
420 else:
420 else:
421 # this translates to the bisect hi = mid
421 # this translates to the bisect hi = mid
422 hi = start
422 hi = start
423 end = advance(lo, '\0')
423 end = advance(lo, '\0')
424 found = m[lo:end]
424 found = m[lo:end]
425 if s == found:
425 if s == found:
426 # we know that after the null there are 40 bytes of sha1
426 # we know that after the null there are 40 bytes of sha1
427 end = advance(end + 40, '\n')
427 end = advance(end + 40, '\n')
428 return (lo, end + 1)
428 return (lo, end + 1)
429 else:
429 else:
430 return (lo, lo)
430 return (lo, lo)
431
431
432 def _checkforbidden(l):
432 def _checkforbidden(l):
433 """Check filenames for illegal characters."""
433 """Check filenames for illegal characters."""
434 for f in l:
434 for f in l:
435 if '\n' in f or '\r' in f:
435 if '\n' in f or '\r' in f:
436 raise error.RevlogError(
436 raise error.RevlogError(
437 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
437 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
438
438
439
439
440 # apply the changes collected during the bisect loop to our addlist
440 # apply the changes collected during the bisect loop to our addlist
441 # return a delta suitable for addrevision
441 # return a delta suitable for addrevision
442 def _addlistdelta(addlist, x):
442 def _addlistdelta(addlist, x):
443 # for large addlist arrays, building a new array is cheaper
443 # for large addlist arrays, building a new array is cheaper
444 # than repeatedly modifying the existing one
444 # than repeatedly modifying the existing one
445 currentposition = 0
445 currentposition = 0
446 newaddlist = array.array('c')
446 newaddlist = array.array('c')
447
447
448 for start, end, content in x:
448 for start, end, content in x:
449 newaddlist += addlist[currentposition:start]
449 newaddlist += addlist[currentposition:start]
450 if content:
450 if content:
451 newaddlist += array.array('c', content)
451 newaddlist += array.array('c', content)
452
452
453 currentposition = end
453 currentposition = end
454
454
455 newaddlist += addlist[currentposition:]
455 newaddlist += addlist[currentposition:]
456
456
457 deltatext = "".join(struct.pack(">lll", start, end, len(content))
457 deltatext = "".join(struct.pack(">lll", start, end, len(content))
458 + content for start, end, content in x)
458 + content for start, end, content in x)
459 return deltatext, newaddlist
459 return deltatext, newaddlist
460
460
461 def _splittopdir(f):
461 def _splittopdir(f):
462 if '/' in f:
462 if '/' in f:
463 dir, subpath = f.split('/', 1)
463 dir, subpath = f.split('/', 1)
464 return dir + '/', subpath
464 return dir + '/', subpath
465 else:
465 else:
466 return '', f
466 return '', f
467
467
468 _noop = lambda s: None
468 _noop = lambda s: None
469
469
470 class treemanifest(object):
470 class treemanifest(object):
471 def __init__(self, dir='', text=''):
471 def __init__(self, dir='', text=''):
472 self._dir = dir
472 self._dir = dir
473 self._node = revlog.nullid
473 self._node = revlog.nullid
474 self._loadfunc = _noop
474 self._loadfunc = _noop
475 self._copyfunc = _noop
475 self._copyfunc = _noop
476 self._dirty = False
476 self._dirty = False
477 self._dirs = {}
477 self._dirs = {}
478 # Using _lazymanifest here is a little slower than plain old dicts
478 # Using _lazymanifest here is a little slower than plain old dicts
479 self._files = {}
479 self._files = {}
480 self._flags = {}
480 self._flags = {}
481 if text:
481 if text:
482 def readsubtree(subdir, subm):
482 def readsubtree(subdir, subm):
483 raise AssertionError('treemanifest constructor only accepts '
483 raise AssertionError('treemanifest constructor only accepts '
484 'flat manifests')
484 'flat manifests')
485 self.parse(text, readsubtree)
485 self.parse(text, readsubtree)
486 self._dirty = True # Mark flat manifest dirty after parsing
486 self._dirty = True # Mark flat manifest dirty after parsing
487
487
488 def _subpath(self, path):
488 def _subpath(self, path):
489 return self._dir + path
489 return self._dir + path
490
490
491 def __len__(self):
491 def __len__(self):
492 self._load()
492 self._load()
493 size = len(self._files)
493 size = len(self._files)
494 for m in self._dirs.values():
494 for m in self._dirs.values():
495 size += m.__len__()
495 size += m.__len__()
496 return size
496 return size
497
497
498 def _isempty(self):
498 def _isempty(self):
499 self._load() # for consistency; already loaded by all callers
499 self._load() # for consistency; already loaded by all callers
500 return (not self._files and (not self._dirs or
500 return (not self._files and (not self._dirs or
501 all(m._isempty() for m in self._dirs.values())))
501 all(m._isempty() for m in self._dirs.values())))
502
502
503 def __repr__(self):
503 def __repr__(self):
504 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
504 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
505 (self._dir, revlog.hex(self._node),
505 (self._dir, revlog.hex(self._node),
506 bool(self._loadfunc is _noop),
506 bool(self._loadfunc is _noop),
507 self._dirty, id(self)))
507 self._dirty, id(self)))
508
508
509 def dir(self):
509 def dir(self):
510 '''The directory that this tree manifest represents, including a
510 '''The directory that this tree manifest represents, including a
511 trailing '/'. Empty string for the repo root directory.'''
511 trailing '/'. Empty string for the repo root directory.'''
512 return self._dir
512 return self._dir
513
513
514 def node(self):
514 def node(self):
515 '''This node of this instance. nullid for unsaved instances. Should
515 '''This node of this instance. nullid for unsaved instances. Should
516 be updated when the instance is read or written from a revlog.
516 be updated when the instance is read or written from a revlog.
517 '''
517 '''
518 assert not self._dirty
518 assert not self._dirty
519 return self._node
519 return self._node
520
520
521 def setnode(self, node):
521 def setnode(self, node):
522 self._node = node
522 self._node = node
523 self._dirty = False
523 self._dirty = False
524
524
525 def iterentries(self):
525 def iterentries(self):
526 self._load()
526 self._load()
527 for p, n in sorted(self._dirs.items() + self._files.items()):
527 for p, n in sorted(self._dirs.items() + self._files.items()):
528 if p in self._files:
528 if p in self._files:
529 yield self._subpath(p), n, self._flags.get(p, '')
529 yield self._subpath(p), n, self._flags.get(p, '')
530 else:
530 else:
531 for x in n.iterentries():
531 for x in n.iterentries():
532 yield x
532 yield x
533
533
534 def iteritems(self):
534 def iteritems(self):
535 self._load()
535 self._load()
536 for p, n in sorted(self._dirs.items() + self._files.items()):
536 for p, n in sorted(self._dirs.items() + self._files.items()):
537 if p in self._files:
537 if p in self._files:
538 yield self._subpath(p), n
538 yield self._subpath(p), n
539 else:
539 else:
540 for f, sn in n.iteritems():
540 for f, sn in n.iteritems():
541 yield f, sn
541 yield f, sn
542
542
543 def iterkeys(self):
543 def iterkeys(self):
544 self._load()
544 self._load()
545 for p in sorted(self._dirs.keys() + self._files.keys()):
545 for p in sorted(self._dirs.keys() + self._files.keys()):
546 if p in self._files:
546 if p in self._files:
547 yield self._subpath(p)
547 yield self._subpath(p)
548 else:
548 else:
549 for f in self._dirs[p].iterkeys():
549 for f in self._dirs[p].iterkeys():
550 yield f
550 yield f
551
551
552 def keys(self):
552 def keys(self):
553 return list(self.iterkeys())
553 return list(self.iterkeys())
554
554
555 def __iter__(self):
555 def __iter__(self):
556 return self.iterkeys()
556 return self.iterkeys()
557
557
558 def __contains__(self, f):
558 def __contains__(self, f):
559 if f is None:
559 if f is None:
560 return False
560 return False
561 self._load()
561 self._load()
562 dir, subpath = _splittopdir(f)
562 dir, subpath = _splittopdir(f)
563 if dir:
563 if dir:
564 if dir not in self._dirs:
564 if dir not in self._dirs:
565 return False
565 return False
566 return self._dirs[dir].__contains__(subpath)
566 return self._dirs[dir].__contains__(subpath)
567 else:
567 else:
568 return f in self._files
568 return f in self._files
569
569
570 def get(self, f, default=None):
570 def get(self, f, default=None):
571 self._load()
571 self._load()
572 dir, subpath = _splittopdir(f)
572 dir, subpath = _splittopdir(f)
573 if dir:
573 if dir:
574 if dir not in self._dirs:
574 if dir not in self._dirs:
575 return default
575 return default
576 return self._dirs[dir].get(subpath, default)
576 return self._dirs[dir].get(subpath, default)
577 else:
577 else:
578 return self._files.get(f, default)
578 return self._files.get(f, default)
579
579
580 def __getitem__(self, f):
580 def __getitem__(self, f):
581 self._load()
581 self._load()
582 dir, subpath = _splittopdir(f)
582 dir, subpath = _splittopdir(f)
583 if dir:
583 if dir:
584 return self._dirs[dir].__getitem__(subpath)
584 return self._dirs[dir].__getitem__(subpath)
585 else:
585 else:
586 return self._files[f]
586 return self._files[f]
587
587
588 def flags(self, f):
588 def flags(self, f):
589 self._load()
589 self._load()
590 dir, subpath = _splittopdir(f)
590 dir, subpath = _splittopdir(f)
591 if dir:
591 if dir:
592 if dir not in self._dirs:
592 if dir not in self._dirs:
593 return ''
593 return ''
594 return self._dirs[dir].flags(subpath)
594 return self._dirs[dir].flags(subpath)
595 else:
595 else:
596 if f in self._dirs:
596 if f in self._dirs:
597 return ''
597 return ''
598 return self._flags.get(f, '')
598 return self._flags.get(f, '')
599
599
600 def find(self, f):
600 def find(self, f):
601 self._load()
601 self._load()
602 dir, subpath = _splittopdir(f)
602 dir, subpath = _splittopdir(f)
603 if dir:
603 if dir:
604 return self._dirs[dir].find(subpath)
604 return self._dirs[dir].find(subpath)
605 else:
605 else:
606 return self._files[f], self._flags.get(f, '')
606 return self._files[f], self._flags.get(f, '')
607
607
608 def __delitem__(self, f):
608 def __delitem__(self, f):
609 self._load()
609 self._load()
610 dir, subpath = _splittopdir(f)
610 dir, subpath = _splittopdir(f)
611 if dir:
611 if dir:
612 self._dirs[dir].__delitem__(subpath)
612 self._dirs[dir].__delitem__(subpath)
613 # If the directory is now empty, remove it
613 # If the directory is now empty, remove it
614 if self._dirs[dir]._isempty():
614 if self._dirs[dir]._isempty():
615 del self._dirs[dir]
615 del self._dirs[dir]
616 else:
616 else:
617 del self._files[f]
617 del self._files[f]
618 if f in self._flags:
618 if f in self._flags:
619 del self._flags[f]
619 del self._flags[f]
620 self._dirty = True
620 self._dirty = True
621
621
622 def __setitem__(self, f, n):
622 def __setitem__(self, f, n):
623 assert n is not None
623 assert n is not None
624 self._load()
624 self._load()
625 dir, subpath = _splittopdir(f)
625 dir, subpath = _splittopdir(f)
626 if dir:
626 if dir:
627 if dir not in self._dirs:
627 if dir not in self._dirs:
628 self._dirs[dir] = treemanifest(self._subpath(dir))
628 self._dirs[dir] = treemanifest(self._subpath(dir))
629 self._dirs[dir].__setitem__(subpath, n)
629 self._dirs[dir].__setitem__(subpath, n)
630 else:
630 else:
631 self._files[f] = n[:21] # to match manifestdict's behavior
631 self._files[f] = n[:21] # to match manifestdict's behavior
632 self._dirty = True
632 self._dirty = True
633
633
634 def _load(self):
634 def _load(self):
635 if self._loadfunc is not _noop:
635 if self._loadfunc is not _noop:
636 lf, self._loadfunc = self._loadfunc, _noop
636 lf, self._loadfunc = self._loadfunc, _noop
637 lf(self)
637 lf(self)
638 elif self._copyfunc is not _noop:
638 elif self._copyfunc is not _noop:
639 cf, self._copyfunc = self._copyfunc, _noop
639 cf, self._copyfunc = self._copyfunc, _noop
640 cf(self)
640 cf(self)
641
641
642 def setflag(self, f, flags):
642 def setflag(self, f, flags):
643 """Set the flags (symlink, executable) for path f."""
643 """Set the flags (symlink, executable) for path f."""
644 self._load()
644 self._load()
645 dir, subpath = _splittopdir(f)
645 dir, subpath = _splittopdir(f)
646 if dir:
646 if dir:
647 if dir not in self._dirs:
647 if dir not in self._dirs:
648 self._dirs[dir] = treemanifest(self._subpath(dir))
648 self._dirs[dir] = treemanifest(self._subpath(dir))
649 self._dirs[dir].setflag(subpath, flags)
649 self._dirs[dir].setflag(subpath, flags)
650 else:
650 else:
651 self._flags[f] = flags
651 self._flags[f] = flags
652 self._dirty = True
652 self._dirty = True
653
653
654 def copy(self):
654 def copy(self):
655 copy = treemanifest(self._dir)
655 copy = treemanifest(self._dir)
656 copy._node = self._node
656 copy._node = self._node
657 copy._dirty = self._dirty
657 copy._dirty = self._dirty
658 if self._copyfunc is _noop:
658 if self._copyfunc is _noop:
659 def _copyfunc(s):
659 def _copyfunc(s):
660 self._load()
660 self._load()
661 for d in self._dirs:
661 for d in self._dirs:
662 s._dirs[d] = self._dirs[d].copy()
662 s._dirs[d] = self._dirs[d].copy()
663 s._files = dict.copy(self._files)
663 s._files = dict.copy(self._files)
664 s._flags = dict.copy(self._flags)
664 s._flags = dict.copy(self._flags)
665 if self._loadfunc is _noop:
665 if self._loadfunc is _noop:
666 _copyfunc(copy)
666 _copyfunc(copy)
667 else:
667 else:
668 copy._copyfunc = _copyfunc
668 copy._copyfunc = _copyfunc
669 else:
669 else:
670 copy._copyfunc = self._copyfunc
670 copy._copyfunc = self._copyfunc
671 return copy
671 return copy
672
672
673 def filesnotin(self, m2):
673 def filesnotin(self, m2):
674 '''Set of files in this manifest that are not in the other'''
674 '''Set of files in this manifest that are not in the other'''
675 files = set()
675 files = set()
676 def _filesnotin(t1, t2):
676 def _filesnotin(t1, t2):
677 if t1._node == t2._node and not t1._dirty and not t2._dirty:
677 if t1._node == t2._node and not t1._dirty and not t2._dirty:
678 return
678 return
679 t1._load()
679 t1._load()
680 t2._load()
680 t2._load()
681 for d, m1 in t1._dirs.iteritems():
681 for d, m1 in t1._dirs.iteritems():
682 if d in t2._dirs:
682 if d in t2._dirs:
683 m2 = t2._dirs[d]
683 m2 = t2._dirs[d]
684 _filesnotin(m1, m2)
684 _filesnotin(m1, m2)
685 else:
685 else:
686 files.update(m1.iterkeys())
686 files.update(m1.iterkeys())
687
687
688 for fn in t1._files.iterkeys():
688 for fn in t1._files.iterkeys():
689 if fn not in t2._files:
689 if fn not in t2._files:
690 files.add(t1._subpath(fn))
690 files.add(t1._subpath(fn))
691
691
692 _filesnotin(self, m2)
692 _filesnotin(self, m2)
693 return files
693 return files
694
694
695 @propertycache
695 @propertycache
696 def _alldirs(self):
696 def _alldirs(self):
697 return util.dirs(self)
697 return util.dirs(self)
698
698
699 def dirs(self):
699 def dirs(self):
700 return self._alldirs
700 return self._alldirs
701
701
702 def hasdir(self, dir):
702 def hasdir(self, dir):
703 self._load()
703 self._load()
704 topdir, subdir = _splittopdir(dir)
704 topdir, subdir = _splittopdir(dir)
705 if topdir:
705 if topdir:
706 if topdir in self._dirs:
706 if topdir in self._dirs:
707 return self._dirs[topdir].hasdir(subdir)
707 return self._dirs[topdir].hasdir(subdir)
708 return False
708 return False
709 return (dir + '/') in self._dirs
709 return (dir + '/') in self._dirs
710
710
711 def walk(self, match):
711 def walk(self, match):
712 '''Generates matching file names.
712 '''Generates matching file names.
713
713
714 Equivalent to manifest.matches(match).iterkeys(), but without creating
714 Equivalent to manifest.matches(match).iterkeys(), but without creating
715 an entirely new manifest.
715 an entirely new manifest.
716
716
717 It also reports nonexistent files by marking them bad with match.bad().
717 It also reports nonexistent files by marking them bad with match.bad().
718 '''
718 '''
719 if match.always():
719 if match.always():
720 for f in iter(self):
720 for f in iter(self):
721 yield f
721 yield f
722 return
722 return
723
723
724 fset = set(match.files())
724 fset = set(match.files())
725
725
726 for fn in self._walk(match):
726 for fn in self._walk(match):
727 if fn in fset:
727 if fn in fset:
728 # specified pattern is the exact name
728 # specified pattern is the exact name
729 fset.remove(fn)
729 fset.remove(fn)
730 yield fn
730 yield fn
731
731
732 # for dirstate.walk, files=['.'] means "walk the whole tree".
732 # for dirstate.walk, files=['.'] means "walk the whole tree".
733 # follow that here, too
733 # follow that here, too
734 fset.discard('.')
734 fset.discard('.')
735
735
736 for fn in sorted(fset):
736 for fn in sorted(fset):
737 if not self.hasdir(fn):
737 if not self.hasdir(fn):
738 match.bad(fn, None)
738 match.bad(fn, None)
739
739
740 def _walk(self, match):
740 def _walk(self, match):
741 '''Recursively generates matching file names for walk().'''
741 '''Recursively generates matching file names for walk().'''
742 if not match.visitdir(self._dir[:-1] or '.'):
742 if not match.visitdir(self._dir[:-1] or '.'):
743 return
743 return
744
744
745 # yield this dir's files and walk its submanifests
745 # yield this dir's files and walk its submanifests
746 self._load()
746 self._load()
747 for p in sorted(self._dirs.keys() + self._files.keys()):
747 for p in sorted(self._dirs.keys() + self._files.keys()):
748 if p in self._files:
748 if p in self._files:
749 fullp = self._subpath(p)
749 fullp = self._subpath(p)
750 if match(fullp):
750 if match(fullp):
751 yield fullp
751 yield fullp
752 else:
752 else:
753 for f in self._dirs[p]._walk(match):
753 for f in self._dirs[p]._walk(match):
754 yield f
754 yield f
755
755
756 def matches(self, match):
756 def matches(self, match):
757 '''generate a new manifest filtered by the match argument'''
757 '''generate a new manifest filtered by the match argument'''
758 if match.always():
758 if match.always():
759 return self.copy()
759 return self.copy()
760
760
761 return self._matches(match)
761 return self._matches(match)
762
762
763 def _matches(self, match):
763 def _matches(self, match):
764 '''recursively generate a new manifest filtered by the match argument.
764 '''recursively generate a new manifest filtered by the match argument.
765 '''
765 '''
766
766
767 visit = match.visitdir(self._dir[:-1] or '.')
767 visit = match.visitdir(self._dir[:-1] or '.')
768 if visit == 'all':
768 if visit == 'all':
769 return self.copy()
769 return self.copy()
770 ret = treemanifest(self._dir)
770 ret = treemanifest(self._dir)
771 if not visit:
771 if not visit:
772 return ret
772 return ret
773
773
774 self._load()
774 self._load()
775 for fn in self._files:
775 for fn in self._files:
776 fullp = self._subpath(fn)
776 fullp = self._subpath(fn)
777 if not match(fullp):
777 if not match(fullp):
778 continue
778 continue
779 ret._files[fn] = self._files[fn]
779 ret._files[fn] = self._files[fn]
780 if fn in self._flags:
780 if fn in self._flags:
781 ret._flags[fn] = self._flags[fn]
781 ret._flags[fn] = self._flags[fn]
782
782
783 for dir, subm in self._dirs.iteritems():
783 for dir, subm in self._dirs.iteritems():
784 m = subm._matches(match)
784 m = subm._matches(match)
785 if not m._isempty():
785 if not m._isempty():
786 ret._dirs[dir] = m
786 ret._dirs[dir] = m
787
787
788 if not ret._isempty():
788 if not ret._isempty():
789 ret._dirty = True
789 ret._dirty = True
790 return ret
790 return ret
791
791
792 def diff(self, m2, clean=False):
792 def diff(self, m2, clean=False):
793 '''Finds changes between the current manifest and m2.
793 '''Finds changes between the current manifest and m2.
794
794
795 Args:
795 Args:
796 m2: the manifest to which this manifest should be compared.
796 m2: the manifest to which this manifest should be compared.
797 clean: if true, include files unchanged between these manifests
797 clean: if true, include files unchanged between these manifests
798 with a None value in the returned dictionary.
798 with a None value in the returned dictionary.
799
799
800 The result is returned as a dict with filename as key and
800 The result is returned as a dict with filename as key and
801 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
801 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
802 nodeid in the current/other manifest and fl1/fl2 is the flag
802 nodeid in the current/other manifest and fl1/fl2 is the flag
803 in the current/other manifest. Where the file does not exist,
803 in the current/other manifest. Where the file does not exist,
804 the nodeid will be None and the flags will be the empty
804 the nodeid will be None and the flags will be the empty
805 string.
805 string.
806 '''
806 '''
807 result = {}
807 result = {}
808 emptytree = treemanifest()
808 emptytree = treemanifest()
809 def _diff(t1, t2):
809 def _diff(t1, t2):
810 if t1._node == t2._node and not t1._dirty and not t2._dirty:
810 if t1._node == t2._node and not t1._dirty and not t2._dirty:
811 return
811 return
812 t1._load()
812 t1._load()
813 t2._load()
813 t2._load()
814 for d, m1 in t1._dirs.iteritems():
814 for d, m1 in t1._dirs.iteritems():
815 m2 = t2._dirs.get(d, emptytree)
815 m2 = t2._dirs.get(d, emptytree)
816 _diff(m1, m2)
816 _diff(m1, m2)
817
817
818 for d, m2 in t2._dirs.iteritems():
818 for d, m2 in t2._dirs.iteritems():
819 if d not in t1._dirs:
819 if d not in t1._dirs:
820 _diff(emptytree, m2)
820 _diff(emptytree, m2)
821
821
822 for fn, n1 in t1._files.iteritems():
822 for fn, n1 in t1._files.iteritems():
823 fl1 = t1._flags.get(fn, '')
823 fl1 = t1._flags.get(fn, '')
824 n2 = t2._files.get(fn, None)
824 n2 = t2._files.get(fn, None)
825 fl2 = t2._flags.get(fn, '')
825 fl2 = t2._flags.get(fn, '')
826 if n1 != n2 or fl1 != fl2:
826 if n1 != n2 or fl1 != fl2:
827 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
827 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
828 elif clean:
828 elif clean:
829 result[t1._subpath(fn)] = None
829 result[t1._subpath(fn)] = None
830
830
831 for fn, n2 in t2._files.iteritems():
831 for fn, n2 in t2._files.iteritems():
832 if fn not in t1._files:
832 if fn not in t1._files:
833 fl2 = t2._flags.get(fn, '')
833 fl2 = t2._flags.get(fn, '')
834 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
834 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
835
835
836 _diff(self, m2)
836 _diff(self, m2)
837 return result
837 return result
838
838
839 def unmodifiedsince(self, m2):
839 def unmodifiedsince(self, m2):
840 return not self._dirty and not m2._dirty and self._node == m2._node
840 return not self._dirty and not m2._dirty and self._node == m2._node
841
841
842 def parse(self, text, readsubtree):
842 def parse(self, text, readsubtree):
843 for f, n, fl in _parse(text):
843 for f, n, fl in _parse(text):
844 if fl == 't':
844 if fl == 't':
845 f = f + '/'
845 f = f + '/'
846 self._dirs[f] = readsubtree(self._subpath(f), n)
846 self._dirs[f] = readsubtree(self._subpath(f), n)
847 elif '/' in f:
847 elif '/' in f:
848 # This is a flat manifest, so use __setitem__ and setflag rather
848 # This is a flat manifest, so use __setitem__ and setflag rather
849 # than assigning directly to _files and _flags, so we can
849 # than assigning directly to _files and _flags, so we can
850 # assign a path in a subdirectory, and to mark dirty (compared
850 # assign a path in a subdirectory, and to mark dirty (compared
851 # to nullid).
851 # to nullid).
852 self[f] = n
852 self[f] = n
853 if fl:
853 if fl:
854 self.setflag(f, fl)
854 self.setflag(f, fl)
855 else:
855 else:
856 # Assigning to _files and _flags avoids marking as dirty,
856 # Assigning to _files and _flags avoids marking as dirty,
857 # and should be a little faster.
857 # and should be a little faster.
858 self._files[f] = n
858 self._files[f] = n
859 if fl:
859 if fl:
860 self._flags[f] = fl
860 self._flags[f] = fl
861
861
862 def text(self, usemanifestv2=False):
862 def text(self, usemanifestv2=False):
863 """Get the full data of this manifest as a bytestring."""
863 """Get the full data of this manifest as a bytestring."""
864 self._load()
864 self._load()
865 return _text(self.iterentries(), usemanifestv2)
865 return _text(self.iterentries(), usemanifestv2)
866
866
867 def dirtext(self, usemanifestv2=False):
867 def dirtext(self, usemanifestv2=False):
868 """Get the full data of this directory as a bytestring. Make sure that
868 """Get the full data of this directory as a bytestring. Make sure that
869 any submanifests have been written first, so their nodeids are correct.
869 any submanifests have been written first, so their nodeids are correct.
870 """
870 """
871 self._load()
871 self._load()
872 flags = self.flags
872 flags = self.flags
873 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
873 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
874 files = [(f, self._files[f], flags(f)) for f in self._files]
874 files = [(f, self._files[f], flags(f)) for f in self._files]
875 return _text(sorted(dirs + files), usemanifestv2)
875 return _text(sorted(dirs + files), usemanifestv2)
876
876
877 def read(self, gettext, readsubtree):
877 def read(self, gettext, readsubtree):
878 def _load_for_read(s):
878 def _load_for_read(s):
879 s.parse(gettext(), readsubtree)
879 s.parse(gettext(), readsubtree)
880 s._dirty = False
880 s._dirty = False
881 self._loadfunc = _load_for_read
881 self._loadfunc = _load_for_read
882
882
883 def writesubtrees(self, m1, m2, writesubtree):
883 def writesubtrees(self, m1, m2, writesubtree):
884 self._load() # for consistency; should never have any effect here
884 self._load() # for consistency; should never have any effect here
885 m1._load()
885 m1._load()
886 m2._load()
886 m2._load()
887 emptytree = treemanifest()
887 emptytree = treemanifest()
888 for d, subm in self._dirs.iteritems():
888 for d, subm in self._dirs.iteritems():
889 subp1 = m1._dirs.get(d, emptytree)._node
889 subp1 = m1._dirs.get(d, emptytree)._node
890 subp2 = m2._dirs.get(d, emptytree)._node
890 subp2 = m2._dirs.get(d, emptytree)._node
891 if subp1 == revlog.nullid:
891 if subp1 == revlog.nullid:
892 subp1, subp2 = subp2, subp1
892 subp1, subp2 = subp2, subp1
893 writesubtree(subm, subp1, subp2)
893 writesubtree(subm, subp1, subp2)
894
894
895 class manifestrevlog(revlog.revlog):
895 class manifestrevlog(revlog.revlog):
896 '''A revlog that stores manifest texts. This is responsible for caching the
896 '''A revlog that stores manifest texts. This is responsible for caching the
897 full-text manifest contents.
897 full-text manifest contents.
898 '''
898 '''
899 def __init__(self, opener, indexfile):
899 def __init__(self, opener, indexfile):
900 super(manifestrevlog, self).__init__(opener, indexfile)
900 super(manifestrevlog, self).__init__(opener, indexfile)
901
901
902 # During normal operations, we expect to deal with not more than four
902 # During normal operations, we expect to deal with not more than four
903 # revs at a time (such as during commit --amend). When rebasing large
903 # revs at a time (such as during commit --amend). When rebasing large
904 # stacks of commits, the number can go up, hence the config knob below.
904 # stacks of commits, the number can go up, hence the config knob below.
905 cachesize = 4
905 cachesize = 4
906 opts = getattr(opener, 'options', None)
906 opts = getattr(opener, 'options', None)
907 if opts is not None:
907 if opts is not None:
908 cachesize = opts.get('manifestcachesize', cachesize)
908 cachesize = opts.get('manifestcachesize', cachesize)
909 self._fulltextcache = util.lrucachedict(cachesize)
909 self._fulltextcache = util.lrucachedict(cachesize)
910
910
911 @property
911 @property
912 def fulltextcache(self):
912 def fulltextcache(self):
913 return self._fulltextcache
913 return self._fulltextcache
914
914
915 def clearcaches(self):
915 def clearcaches(self):
916 super(manifestrevlog, self).clearcaches()
916 super(manifestrevlog, self).clearcaches()
917 self._fulltextcache.clear()
917 self._fulltextcache.clear()
918
918
919 class manifestlog(object):
919 class manifestlog(object):
920 """A collection class representing the collection of manifest snapshots
920 """A collection class representing the collection of manifest snapshots
921 referenced by commits in the repository.
921 referenced by commits in the repository.
922
922
923 In this situation, 'manifest' refers to the abstract concept of a snapshot
923 In this situation, 'manifest' refers to the abstract concept of a snapshot
924 of the list of files in the given commit. Consumers of the output of this
924 of the list of files in the given commit. Consumers of the output of this
925 class do not care about the implementation details of the actual manifests
925 class do not care about the implementation details of the actual manifests
926 they receive (i.e. tree or flat or lazily loaded, etc)."""
926 they receive (i.e. tree or flat or lazily loaded, etc)."""
927 def __init__(self, opener, repo):
927 def __init__(self, opener, repo):
928 self._repo = repo
928 self._repo = repo
929
929
930 # We'll separate this into it's own cache once oldmanifest is no longer
930 # We'll separate this into it's own cache once oldmanifest is no longer
931 # used
931 # used
932 self._mancache = repo.manifest._mancache
932 self._mancache = repo.manifest._mancache
933
933
934 @property
934 @property
935 def _revlog(self):
935 def _revlog(self):
936 return self._repo.manifest
936 return self._repo.manifest
937
937
938 @property
938 @property
939 def _oldmanifest(self):
939 def _oldmanifest(self):
940 # _revlog is the same as _oldmanifest right now, but we eventually want
940 # _revlog is the same as _oldmanifest right now, but we eventually want
941 # to delete _oldmanifest while still allowing manifestlog to access the
941 # to delete _oldmanifest while still allowing manifestlog to access the
942 # revlog specific apis.
942 # revlog specific apis.
943 return self._repo.manifest
943 return self._repo.manifest
944
944
945 def __getitem__(self, node):
945 def __getitem__(self, node):
946 """Retrieves the manifest instance for the given node. Throws a KeyError
946 """Retrieves the manifest instance for the given node. Throws a KeyError
947 if not found.
947 if not found.
948 """
948 """
949 if node in self._mancache:
949 if node in self._mancache:
950 cachemf = self._mancache[node]
950 cachemf = self._mancache[node]
951 # The old manifest may put non-ctx manifests in the cache, so skip
951 # The old manifest may put non-ctx manifests in the cache, so skip
952 # those since they don't implement the full api.
952 # those since they don't implement the full api.
953 if (isinstance(cachemf, manifestctx) or
953 if (isinstance(cachemf, manifestctx) or
954 isinstance(cachemf, treemanifestctx)):
954 isinstance(cachemf, treemanifestctx)):
955 return cachemf
955 return cachemf
956
956
957 if self._oldmanifest._treeinmem:
957 if self._oldmanifest._treeinmem:
958 m = treemanifestctx(self._revlog, '', node)
958 m = treemanifestctx(self._revlog, '', node)
959 else:
959 else:
960 m = manifestctx(self._revlog, node)
960 m = manifestctx(self._revlog, node)
961 if node != revlog.nullid:
961 if node != revlog.nullid:
962 self._mancache[node] = m
962 self._mancache[node] = m
963 return m
963 return m
964
964
965 class manifestctx(manifestdict):
965 class manifestctx(manifestdict):
966 """A class representing a single revision of a manifest, including its
966 """A class representing a single revision of a manifest, including its
967 contents, its parent revs, and its linkrev.
967 contents, its parent revs, and its linkrev.
968 """
968 """
969 def __init__(self, revlog, node):
969 def __init__(self, revlog, node):
970 self._revlog = revlog
970 self._revlog = revlog
971
971
972 self._node = node
972 self._node = node
973
973
974 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
974 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
975 # but let's add it later when something needs it and we can load it
975 # but let's add it later when something needs it and we can load it
976 # lazily.
976 # lazily.
977 #self.p1, self.p2 = revlog.parents(node)
977 #self.p1, self.p2 = revlog.parents(node)
978 #rev = revlog.rev(node)
978 #rev = revlog.rev(node)
979 #self.linkrev = revlog.linkrev(rev)
979 #self.linkrev = revlog.linkrev(rev)
980
980
981 # This should eventually be made lazy loaded, so consumers can access
981 # This should eventually be made lazy loaded, so consumers can access
982 # the node/p1/linkrev data without having to parse the whole manifest.
982 # the node/p1/linkrev data without having to parse the whole manifest.
983 data = revlog.revision(node)
983 data = revlog.revision(node)
984 arraytext = array.array('c', data)
984 arraytext = array.array('c', data)
985 revlog._fulltextcache[node] = arraytext
985 revlog._fulltextcache[node] = arraytext
986 super(manifestctx, self).__init__(data)
986 super(manifestctx, self).__init__(data)
987
987
988 def node(self):
988 def node(self):
989 return self._node
989 return self._node
990
990
991 class treemanifestctx(treemanifest):
991 class treemanifestctx(treemanifest):
992 def __init__(self, revlog, dir, node):
992 def __init__(self, revlog, dir, node):
993 revlog = revlog.dirlog(dir)
993 revlog = revlog.dirlog(dir)
994 self._revlog = revlog
994 self._revlog = revlog
995 self._dir = dir
995 self._dir = dir
996
996
997 self._node = node
997 self._node = node
998
998
999 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
999 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1000 # we can instantiate treemanifestctx objects for directories we don't
1000 # we can instantiate treemanifestctx objects for directories we don't
1001 # have on disk.
1001 # have on disk.
1002 #self.p1, self.p2 = revlog.parents(node)
1002 #self.p1, self.p2 = revlog.parents(node)
1003 #rev = revlog.rev(node)
1003 #rev = revlog.rev(node)
1004 #self.linkrev = revlog.linkrev(rev)
1004 #self.linkrev = revlog.linkrev(rev)
1005
1005
1006 if revlog._treeondisk:
1006 if revlog._treeondisk:
1007 super(treemanifestctx, self).__init__(dir=dir)
1007 super(treemanifestctx, self).__init__(dir=dir)
1008 def gettext():
1008 def gettext():
1009 return revlog.revision(node)
1009 return revlog.revision(node)
1010 def readsubtree(dir, subm):
1010 def readsubtree(dir, subm):
1011 return revlog.dirlog(dir).read(subm)
1011 return revlog.dirlog(dir).read(subm)
1012 self.read(gettext, readsubtree)
1012 self.read(gettext, readsubtree)
1013 self.setnode(node)
1013 self.setnode(node)
1014 else:
1014 else:
1015 text = revlog.revision(node)
1015 text = revlog.revision(node)
1016 arraytext = array.array('c', text)
1016 arraytext = array.array('c', text)
1017 revlog.fulltextcache[node] = arraytext
1017 revlog.fulltextcache[node] = arraytext
1018 super(treemanifestctx, self).__init__(dir=dir, text=text)
1018 super(treemanifestctx, self).__init__(dir=dir, text=text)
1019
1019
1020 def node(self):
1020 def node(self):
1021 return self._node
1021 return self._node
1022
1022
1023 class manifest(manifestrevlog):
1023 class manifest(manifestrevlog):
1024 def __init__(self, opener, dir='', dirlogcache=None):
1024 def __init__(self, opener, dir='', dirlogcache=None):
1025 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1025 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1026 manifest.manifest only. External users should create a root manifest
1026 manifest.manifest only. External users should create a root manifest
1027 log with manifest.manifest(opener) and call dirlog() on it.
1027 log with manifest.manifest(opener) and call dirlog() on it.
1028 '''
1028 '''
1029 # During normal operations, we expect to deal with not more than four
1029 # During normal operations, we expect to deal with not more than four
1030 # revs at a time (such as during commit --amend). When rebasing large
1030 # revs at a time (such as during commit --amend). When rebasing large
1031 # stacks of commits, the number can go up, hence the config knob below.
1031 # stacks of commits, the number can go up, hence the config knob below.
1032 cachesize = 4
1032 cachesize = 4
1033 usetreemanifest = False
1033 usetreemanifest = False
1034 usemanifestv2 = False
1034 usemanifestv2 = False
1035 opts = getattr(opener, 'options', None)
1035 opts = getattr(opener, 'options', None)
1036 if opts is not None:
1036 if opts is not None:
1037 cachesize = opts.get('manifestcachesize', cachesize)
1037 cachesize = opts.get('manifestcachesize', cachesize)
1038 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1038 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1039 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1039 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1040 self._mancache = util.lrucachedict(cachesize)
1040 self._mancache = util.lrucachedict(cachesize)
1041 self._treeinmem = usetreemanifest
1041 self._treeinmem = usetreemanifest
1042 self._treeondisk = usetreemanifest
1042 self._treeondisk = usetreemanifest
1043 self._usemanifestv2 = usemanifestv2
1043 self._usemanifestv2 = usemanifestv2
1044 indexfile = "00manifest.i"
1044 indexfile = "00manifest.i"
1045 if dir:
1045 if dir:
1046 assert self._treeondisk, 'opts is %r' % opts
1046 assert self._treeondisk, 'opts is %r' % opts
1047 if not dir.endswith('/'):
1047 if not dir.endswith('/'):
1048 dir = dir + '/'
1048 dir = dir + '/'
1049 indexfile = "meta/" + dir + "00manifest.i"
1049 indexfile = "meta/" + dir + "00manifest.i"
1050 super(manifest, self).__init__(opener, indexfile)
1050 super(manifest, self).__init__(opener, indexfile)
1051 self._dir = dir
1051 self._dir = dir
1052 # The dirlogcache is kept on the root manifest log
1052 # The dirlogcache is kept on the root manifest log
1053 if dir:
1053 if dir:
1054 self._dirlogcache = dirlogcache
1054 self._dirlogcache = dirlogcache
1055 else:
1055 else:
1056 self._dirlogcache = {'': self}
1056 self._dirlogcache = {'': self}
1057
1057
1058 def _newmanifest(self, data=''):
1058 def _newmanifest(self, data=''):
1059 if self._treeinmem:
1059 if self._treeinmem:
1060 return treemanifest(self._dir, data)
1060 return treemanifest(self._dir, data)
1061 return manifestdict(data)
1061 return manifestdict(data)
1062
1062
1063 def dirlog(self, dir):
1063 def dirlog(self, dir):
1064 if dir:
1064 if dir:
1065 assert self._treeondisk
1065 assert self._treeondisk
1066 if dir not in self._dirlogcache:
1066 if dir not in self._dirlogcache:
1067 self._dirlogcache[dir] = manifest(self.opener, dir,
1067 self._dirlogcache[dir] = manifest(self.opener, dir,
1068 self._dirlogcache)
1068 self._dirlogcache)
1069 return self._dirlogcache[dir]
1069 return self._dirlogcache[dir]
1070
1070
1071 def _slowreaddelta(self, node):
1071 def _slowreaddelta(self, node):
1072 r0 = self.deltaparent(self.rev(node))
1072 r0 = self.deltaparent(self.rev(node))
1073 m0 = self.read(self.node(r0))
1073 m0 = self.read(self.node(r0))
1074 m1 = self.read(node)
1074 m1 = self.read(node)
1075 md = self._newmanifest()
1075 md = self._newmanifest()
1076 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1076 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1077 if n1:
1077 if n1:
1078 md[f] = n1
1078 md[f] = n1
1079 if fl1:
1079 if fl1:
1080 md.setflag(f, fl1)
1080 md.setflag(f, fl1)
1081 return md
1081 return md
1082
1082
1083 def readdelta(self, node):
1083 def readdelta(self, node):
1084 if self._usemanifestv2 or self._treeondisk:
1084 if self._usemanifestv2 or self._treeondisk:
1085 return self._slowreaddelta(node)
1085 return self._slowreaddelta(node)
1086 r = self.rev(node)
1086 r = self.rev(node)
1087 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1087 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1088 return self._newmanifest(d)
1088 return self._newmanifest(d)
1089
1089
1090 def readshallowdelta(self, node):
1090 def readshallowdelta(self, node):
1091 '''For flat manifests, this is the same as readdelta(). For
1091 '''For flat manifests, this is the same as readdelta(). For
1092 treemanifests, this will read the delta for this revlog's directory,
1092 treemanifests, this will read the delta for this revlog's directory,
1093 without recursively reading subdirectory manifests. Instead, any
1093 without recursively reading subdirectory manifests. Instead, any
1094 subdirectory entry will be reported as it appears in the manifests, i.e.
1094 subdirectory entry will be reported as it appears in the manifests, i.e.
1095 the subdirectory will be reported among files and distinguished only by
1095 the subdirectory will be reported among files and distinguished only by
1096 its 't' flag.'''
1096 its 't' flag.'''
1097 if not self._treeondisk:
1097 if not self._treeondisk:
1098 return self.readdelta(node)
1098 return self.readdelta(node)
1099 if self._usemanifestv2:
1099 if self._usemanifestv2:
1100 raise error.Abort(
1100 raise error.Abort(
1101 _("readshallowdelta() not implemented for manifestv2"))
1101 _("readshallowdelta() not implemented for manifestv2"))
1102 r = self.rev(node)
1102 r = self.rev(node)
1103 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1103 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
1104 return manifestdict(d)
1104 return manifestdict(d)
1105
1105
1106 def readfast(self, node):
1106 def readfast(self, node):
1107 '''use the faster of readdelta or read
1107 '''use the faster of readdelta or read
1108
1108
1109 This will return a manifest which is either only the files
1109 This will return a manifest which is either only the files
1110 added/modified relative to p1, or all files in the
1110 added/modified relative to p1, or all files in the
1111 manifest. Which one is returned depends on the codepath used
1111 manifest. Which one is returned depends on the codepath used
1112 to retrieve the data.
1112 to retrieve the data.
1113 '''
1113 '''
1114 r = self.rev(node)
1114 r = self.rev(node)
1115 deltaparent = self.deltaparent(r)
1115 deltaparent = self.deltaparent(r)
1116 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1116 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1117 return self.readdelta(node)
1117 return self.readdelta(node)
1118 return self.read(node)
1118 return self.read(node)
1119
1119
1120 def readshallowfast(self, node):
1120 def readshallowfast(self, node):
1121 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1121 '''like readfast(), but calls readshallowdelta() instead of readdelta()
1122 '''
1122 '''
1123 r = self.rev(node)
1123 r = self.rev(node)
1124 deltaparent = self.deltaparent(r)
1124 deltaparent = self.deltaparent(r)
1125 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1125 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
1126 return self.readshallowdelta(node)
1126 return self.readshallowdelta(node)
1127 return self.readshallow(node)
1127 return self.readshallow(node)
1128
1128
1129 def read(self, node):
1129 def read(self, node):
1130 if node == revlog.nullid:
1130 if node == revlog.nullid:
1131 return self._newmanifest() # don't upset local cache
1131 return self._newmanifest() # don't upset local cache
1132 if node in self._mancache:
1132 if node in self._mancache:
1133 return self._mancache[node]
1133 cached = self._mancache[node]
1134 if (isinstance(cached, manifestctx) or
1135 isinstance(cached, treemanifestctx)):
1136 cached = cached.read()
1137 return cached
1134 if self._treeondisk:
1138 if self._treeondisk:
1135 def gettext():
1139 def gettext():
1136 return self.revision(node)
1140 return self.revision(node)
1137 def readsubtree(dir, subm):
1141 def readsubtree(dir, subm):
1138 return self.dirlog(dir).read(subm)
1142 return self.dirlog(dir).read(subm)
1139 m = self._newmanifest()
1143 m = self._newmanifest()
1140 m.read(gettext, readsubtree)
1144 m.read(gettext, readsubtree)
1141 m.setnode(node)
1145 m.setnode(node)
1142 arraytext = None
1146 arraytext = None
1143 else:
1147 else:
1144 text = self.revision(node)
1148 text = self.revision(node)
1145 m = self._newmanifest(text)
1149 m = self._newmanifest(text)
1146 arraytext = array.array('c', text)
1150 arraytext = array.array('c', text)
1147 self._mancache[node] = m
1151 self._mancache[node] = m
1148 self.fulltextcache[node] = arraytext
1152 self.fulltextcache[node] = arraytext
1149 return m
1153 return m
1150
1154
1151 def readshallow(self, node):
1155 def readshallow(self, node):
1152 '''Reads the manifest in this directory. When using flat manifests,
1156 '''Reads the manifest in this directory. When using flat manifests,
1153 this manifest will generally have files in subdirectories in it. Does
1157 this manifest will generally have files in subdirectories in it. Does
1154 not cache the manifest as the callers generally do not read the same
1158 not cache the manifest as the callers generally do not read the same
1155 version twice.'''
1159 version twice.'''
1156 return manifestdict(self.revision(node))
1160 return manifestdict(self.revision(node))
1157
1161
1158 def find(self, node, f):
1162 def find(self, node, f):
1159 '''look up entry for a single file efficiently.
1163 '''look up entry for a single file efficiently.
1160 return (node, flags) pair if found, (None, None) if not.'''
1164 return (node, flags) pair if found, (None, None) if not.'''
1161 m = self.read(node)
1165 m = self.read(node)
1162 try:
1166 try:
1163 return m.find(f)
1167 return m.find(f)
1164 except KeyError:
1168 except KeyError:
1165 return None, None
1169 return None, None
1166
1170
1167 def add(self, m, transaction, link, p1, p2, added, removed):
1171 def add(self, m, transaction, link, p1, p2, added, removed):
1168 if (p1 in self.fulltextcache and not self._treeinmem
1172 if (p1 in self.fulltextcache and not self._treeinmem
1169 and not self._usemanifestv2):
1173 and not self._usemanifestv2):
1170 # If our first parent is in the manifest cache, we can
1174 # If our first parent is in the manifest cache, we can
1171 # compute a delta here using properties we know about the
1175 # compute a delta here using properties we know about the
1172 # manifest up-front, which may save time later for the
1176 # manifest up-front, which may save time later for the
1173 # revlog layer.
1177 # revlog layer.
1174
1178
1175 _checkforbidden(added)
1179 _checkforbidden(added)
1176 # combine the changed lists into one sorted iterator
1180 # combine the changed lists into one sorted iterator
1177 work = heapq.merge([(x, False) for x in added],
1181 work = heapq.merge([(x, False) for x in added],
1178 [(x, True) for x in removed])
1182 [(x, True) for x in removed])
1179
1183
1180 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1184 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1181 cachedelta = self.rev(p1), deltatext
1185 cachedelta = self.rev(p1), deltatext
1182 text = util.buffer(arraytext)
1186 text = util.buffer(arraytext)
1183 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1187 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1184 else:
1188 else:
1185 # The first parent manifest isn't already loaded, so we'll
1189 # The first parent manifest isn't already loaded, so we'll
1186 # just encode a fulltext of the manifest and pass that
1190 # just encode a fulltext of the manifest and pass that
1187 # through to the revlog layer, and let it handle the delta
1191 # through to the revlog layer, and let it handle the delta
1188 # process.
1192 # process.
1189 if self._treeondisk:
1193 if self._treeondisk:
1190 m1 = self.read(p1)
1194 m1 = self.read(p1)
1191 m2 = self.read(p2)
1195 m2 = self.read(p2)
1192 n = self._addtree(m, transaction, link, m1, m2)
1196 n = self._addtree(m, transaction, link, m1, m2)
1193 arraytext = None
1197 arraytext = None
1194 else:
1198 else:
1195 text = m.text(self._usemanifestv2)
1199 text = m.text(self._usemanifestv2)
1196 n = self.addrevision(text, transaction, link, p1, p2)
1200 n = self.addrevision(text, transaction, link, p1, p2)
1197 arraytext = array.array('c', text)
1201 arraytext = array.array('c', text)
1198
1202
1199 self._mancache[n] = m
1203 self._mancache[n] = m
1200 self.fulltextcache[n] = arraytext
1204 self.fulltextcache[n] = arraytext
1201
1205
1202 return n
1206 return n
1203
1207
1204 def _addtree(self, m, transaction, link, m1, m2):
1208 def _addtree(self, m, transaction, link, m1, m2):
1205 # If the manifest is unchanged compared to one parent,
1209 # If the manifest is unchanged compared to one parent,
1206 # don't write a new revision
1210 # don't write a new revision
1207 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1211 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1208 return m.node()
1212 return m.node()
1209 def writesubtree(subm, subp1, subp2):
1213 def writesubtree(subm, subp1, subp2):
1210 sublog = self.dirlog(subm.dir())
1214 sublog = self.dirlog(subm.dir())
1211 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1215 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1212 m.writesubtrees(m1, m2, writesubtree)
1216 m.writesubtrees(m1, m2, writesubtree)
1213 text = m.dirtext(self._usemanifestv2)
1217 text = m.dirtext(self._usemanifestv2)
1214 # Double-check whether contents are unchanged to one parent
1218 # Double-check whether contents are unchanged to one parent
1215 if text == m1.dirtext(self._usemanifestv2):
1219 if text == m1.dirtext(self._usemanifestv2):
1216 n = m1.node()
1220 n = m1.node()
1217 elif text == m2.dirtext(self._usemanifestv2):
1221 elif text == m2.dirtext(self._usemanifestv2):
1218 n = m2.node()
1222 n = m2.node()
1219 else:
1223 else:
1220 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1224 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1221 # Save nodeid so parent manifest can calculate its nodeid
1225 # Save nodeid so parent manifest can calculate its nodeid
1222 m.setnode(n)
1226 m.setnode(n)
1223 return n
1227 return n
1224
1228
1225 def clearcaches(self):
1229 def clearcaches(self):
1226 super(manifest, self).clearcaches()
1230 super(manifest, self).clearcaches()
1227 self._mancache.clear()
1231 self._mancache.clear()
1228 self._dirlogcache = {'': self}
1232 self._dirlogcache = {'': self}
General Comments 0
You need to be logged in to leave comments. Login now