##// END OF EJS Templates
narrow: move manifestlog overrides to core...
Martin von Zweigbergk -
r37392:ac42e39b default
parent child Browse files
Show More
@@ -1,81 +1,75 b''
1 # narrowrepo.py - repository which supports narrow revlogs, lazy loading
1 # narrowrepo.py - repository which supports narrow revlogs, lazy loading
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from mercurial import (
10 from mercurial import (
11 bundlerepo,
11 bundlerepo,
12 changegroup,
12 changegroup,
13 hg,
13 hg,
14 localrepo,
14 localrepo,
15 narrowspec,
15 narrowspec,
16 scmutil,
16 scmutil,
17 )
17 )
18
18
19 from . import (
19 from . import (
20 narrowrevlog,
20 narrowrevlog,
21 )
21 )
22
22
23 def wrappostshare(orig, sourcerepo, destrepo, **kwargs):
23 def wrappostshare(orig, sourcerepo, destrepo, **kwargs):
24 orig(sourcerepo, destrepo, **kwargs)
24 orig(sourcerepo, destrepo, **kwargs)
25 if changegroup.NARROW_REQUIREMENT in sourcerepo.requirements:
25 if changegroup.NARROW_REQUIREMENT in sourcerepo.requirements:
26 with destrepo.wlock():
26 with destrepo.wlock():
27 with destrepo.vfs('shared', 'a') as fp:
27 with destrepo.vfs('shared', 'a') as fp:
28 fp.write(narrowspec.FILENAME + '\n')
28 fp.write(narrowspec.FILENAME + '\n')
29
29
30 def unsharenarrowspec(orig, ui, repo, repopath):
30 def unsharenarrowspec(orig, ui, repo, repopath):
31 if (changegroup.NARROW_REQUIREMENT in repo.requirements
31 if (changegroup.NARROW_REQUIREMENT in repo.requirements
32 and repo.path == repopath and repo.shared()):
32 and repo.path == repopath and repo.shared()):
33 srcrepo = hg.sharedreposource(repo)
33 srcrepo = hg.sharedreposource(repo)
34 with srcrepo.vfs(narrowspec.FILENAME) as f:
34 with srcrepo.vfs(narrowspec.FILENAME) as f:
35 spec = f.read()
35 spec = f.read()
36 with repo.vfs(narrowspec.FILENAME, 'w') as f:
36 with repo.vfs(narrowspec.FILENAME, 'w') as f:
37 f.write(spec)
37 f.write(spec)
38 return orig(ui, repo, repopath)
38 return orig(ui, repo, repopath)
39
39
40 def wraprepo(repo):
40 def wraprepo(repo):
41 """Enables narrow clone functionality on a single local repository."""
41 """Enables narrow clone functionality on a single local repository."""
42
42
43 cacheprop = localrepo.storecache
43 cacheprop = localrepo.storecache
44 if isinstance(repo, bundlerepo.bundlerepository):
44 if isinstance(repo, bundlerepo.bundlerepository):
45 # We have to use a different caching property decorator for
45 # We have to use a different caching property decorator for
46 # bundlerepo because storecache blows up in strange ways on a
46 # bundlerepo because storecache blows up in strange ways on a
47 # bundlerepo. Fortunately, there's no risk of data changing in
47 # bundlerepo. Fortunately, there's no risk of data changing in
48 # a bundlerepo.
48 # a bundlerepo.
49 cacheprop = lambda name: localrepo.unfilteredpropertycache
49 cacheprop = lambda name: localrepo.unfilteredpropertycache
50
50
51 class narrowrepository(repo.__class__):
51 class narrowrepository(repo.__class__):
52
52
53 @cacheprop('00manifest.i')
54 def manifestlog(self):
55 mfl = super(narrowrepository, self).manifestlog
56 narrowrevlog.makenarrowmanifestlog(mfl, self)
57 return mfl
58
59 def file(self, f):
53 def file(self, f):
60 fl = super(narrowrepository, self).file(f)
54 fl = super(narrowrepository, self).file(f)
61 narrowrevlog.makenarrowfilelog(fl, self.narrowmatch())
55 narrowrevlog.makenarrowfilelog(fl, self.narrowmatch())
62 return fl
56 return fl
63
57
64 # I'm not sure this is the right place to do this filter.
58 # I'm not sure this is the right place to do this filter.
65 # context._manifestmatches() would probably be better, or perhaps
59 # context._manifestmatches() would probably be better, or perhaps
66 # move it to a later place, in case some of the callers do want to know
60 # move it to a later place, in case some of the callers do want to know
67 # which directories changed. This seems to work for now, though.
61 # which directories changed. This seems to work for now, though.
68 def status(self, *args, **kwargs):
62 def status(self, *args, **kwargs):
69 s = super(narrowrepository, self).status(*args, **kwargs)
63 s = super(narrowrepository, self).status(*args, **kwargs)
70 narrowmatch = self.narrowmatch()
64 narrowmatch = self.narrowmatch()
71 modified = list(filter(narrowmatch, s.modified))
65 modified = list(filter(narrowmatch, s.modified))
72 added = list(filter(narrowmatch, s.added))
66 added = list(filter(narrowmatch, s.added))
73 removed = list(filter(narrowmatch, s.removed))
67 removed = list(filter(narrowmatch, s.removed))
74 deleted = list(filter(narrowmatch, s.deleted))
68 deleted = list(filter(narrowmatch, s.deleted))
75 unknown = list(filter(narrowmatch, s.unknown))
69 unknown = list(filter(narrowmatch, s.unknown))
76 ignored = list(filter(narrowmatch, s.ignored))
70 ignored = list(filter(narrowmatch, s.ignored))
77 clean = list(filter(narrowmatch, s.clean))
71 clean = list(filter(narrowmatch, s.clean))
78 return scmutil.status(modified, added, removed, deleted, unknown,
72 return scmutil.status(modified, added, removed, deleted, unknown,
79 ignored, clean)
73 ignored, clean)
80
74
81 repo.__class__ = narrowrepository
75 repo.__class__ = narrowrepository
@@ -1,89 +1,80 b''
1 # narrowrevlog.py - revlog storing irrelevant nodes as "ellipsis" nodes
1 # narrowrevlog.py - revlog storing irrelevant nodes as "ellipsis" nodes
2 #
2 #
3 # Copyright 2017 Google, Inc.
3 # Copyright 2017 Google, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from mercurial import (
10 from mercurial import (
11 manifest,
12 revlog,
11 revlog,
13 util,
12 util,
14 )
13 )
15
14
16 def readtransform(self, text):
15 def readtransform(self, text):
17 return text, False
16 return text, False
18
17
19 def writetransform(self, text):
18 def writetransform(self, text):
20 return text, False
19 return text, False
21
20
22 def rawtransform(self, text):
21 def rawtransform(self, text):
23 return False
22 return False
24
23
25 revlog.addflagprocessor(revlog.REVIDX_ELLIPSIS,
24 revlog.addflagprocessor(revlog.REVIDX_ELLIPSIS,
26 (readtransform, writetransform, rawtransform))
25 (readtransform, writetransform, rawtransform))
27
26
28 def setup():
27 def setup():
29 # We just wanted to add the flag processor, which is done at module
28 # We just wanted to add the flag processor, which is done at module
30 # load time.
29 # load time.
31 pass
30 pass
32
31
33 def makenarrowmanifestlog(mfl, repo):
34 class narrowmanifestlog(mfl.__class__):
35 def get(self, dir, node, verify=True):
36 if not repo.narrowmatch().visitdir(dir[:-1] or '.'):
37 return manifest.excludeddirmanifestctx(dir, node)
38 return super(narrowmanifestlog, self).get(dir, node, verify=verify)
39 mfl.__class__ = narrowmanifestlog
40
41 def makenarrowfilelog(fl, narrowmatch):
32 def makenarrowfilelog(fl, narrowmatch):
42 class narrowfilelog(fl.__class__):
33 class narrowfilelog(fl.__class__):
43 def renamed(self, node):
34 def renamed(self, node):
44 # Renames that come from outside the narrowspec are
35 # Renames that come from outside the narrowspec are
45 # problematic at least for git-diffs, because we lack the
36 # problematic at least for git-diffs, because we lack the
46 # base text for the rename. This logic was introduced in
37 # base text for the rename. This logic was introduced in
47 # 3cd72b1 of narrowhg (authored by martinvonz, reviewed by
38 # 3cd72b1 of narrowhg (authored by martinvonz, reviewed by
48 # adgar), but that revision doesn't have any additional
39 # adgar), but that revision doesn't have any additional
49 # commentary on what problems we can encounter.
40 # commentary on what problems we can encounter.
50 m = super(narrowfilelog, self).renamed(node)
41 m = super(narrowfilelog, self).renamed(node)
51 if m and not narrowmatch(m[0]):
42 if m and not narrowmatch(m[0]):
52 return None
43 return None
53 return m
44 return m
54
45
55 def size(self, rev):
46 def size(self, rev):
56 # We take advantage of the fact that remotefilelog
47 # We take advantage of the fact that remotefilelog
57 # lacks a node() method to just skip the
48 # lacks a node() method to just skip the
58 # rename-checking logic when on remotefilelog. This
49 # rename-checking logic when on remotefilelog. This
59 # might be incorrect on other non-revlog-based storage
50 # might be incorrect on other non-revlog-based storage
60 # engines, but for now this seems to be fine.
51 # engines, but for now this seems to be fine.
61 #
52 #
62 # TODO: when remotefilelog is in core, improve this to
53 # TODO: when remotefilelog is in core, improve this to
63 # explicitly look for remotefilelog instead of cheating
54 # explicitly look for remotefilelog instead of cheating
64 # with a hasattr check.
55 # with a hasattr check.
65 if util.safehasattr(self, 'node'):
56 if util.safehasattr(self, 'node'):
66 node = self.node(rev)
57 node = self.node(rev)
67 # Because renamed() is overridden above to
58 # Because renamed() is overridden above to
68 # sometimes return None even if there is metadata
59 # sometimes return None even if there is metadata
69 # in the revlog, size can be incorrect for
60 # in the revlog, size can be incorrect for
70 # copies/renames, so we need to make sure we call
61 # copies/renames, so we need to make sure we call
71 # the super class's implementation of renamed()
62 # the super class's implementation of renamed()
72 # for the purpose of size calculation.
63 # for the purpose of size calculation.
73 if super(narrowfilelog, self).renamed(node):
64 if super(narrowfilelog, self).renamed(node):
74 return len(self.read(node))
65 return len(self.read(node))
75 return super(narrowfilelog, self).size(rev)
66 return super(narrowfilelog, self).size(rev)
76
67
77 def cmp(self, node, text):
68 def cmp(self, node, text):
78 different = super(narrowfilelog, self).cmp(node, text)
69 different = super(narrowfilelog, self).cmp(node, text)
79 if different:
70 if different:
80 # Similar to size() above, if the file was copied from
71 # Similar to size() above, if the file was copied from
81 # a file outside the narrowspec, the super class's
72 # a file outside the narrowspec, the super class's
82 # would have returned True because we tricked it into
73 # would have returned True because we tricked it into
83 # thinking that the file was not renamed.
74 # thinking that the file was not renamed.
84 if super(narrowfilelog, self).renamed(node):
75 if super(narrowfilelog, self).renamed(node):
85 t2 = self.read(node)
76 t2 = self.read(node)
86 return t2 != text
77 return t2 != text
87 return different
78 return different
88
79
89 fl.__class__ = narrowfilelog
80 fl.__class__ = narrowfilelog
@@ -1,1653 +1,1656 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 heapq
10 import heapq
11 import itertools
11 import itertools
12 import struct
12 import struct
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 bin,
16 bin,
17 hex,
17 hex,
18 )
18 )
19 from . import (
19 from . import (
20 error,
20 error,
21 mdiff,
21 mdiff,
22 policy,
22 policy,
23 revlog,
23 revlog,
24 util,
24 util,
25 )
25 )
26
26
27 parsers = policy.importmod(r'parsers')
27 parsers = policy.importmod(r'parsers')
28 propertycache = util.propertycache
28 propertycache = util.propertycache
29
29
30 def _parse(data):
30 def _parse(data):
31 # This method does a little bit of excessive-looking
31 # This method does a little bit of excessive-looking
32 # precondition checking. This is so that the behavior of this
32 # precondition checking. This is so that the behavior of this
33 # class exactly matches its C counterpart to try and help
33 # class exactly matches its C counterpart to try and help
34 # prevent surprise breakage for anyone that develops against
34 # prevent surprise breakage for anyone that develops against
35 # the pure version.
35 # the pure version.
36 if data and data[-1:] != '\n':
36 if data and data[-1:] != '\n':
37 raise ValueError('Manifest did not end in a newline.')
37 raise ValueError('Manifest did not end in a newline.')
38 prev = None
38 prev = None
39 for l in data.splitlines():
39 for l in data.splitlines():
40 if prev is not None and prev > l:
40 if prev is not None and prev > l:
41 raise ValueError('Manifest lines not in sorted order.')
41 raise ValueError('Manifest lines not in sorted order.')
42 prev = l
42 prev = l
43 f, n = l.split('\0')
43 f, n = l.split('\0')
44 if len(n) > 40:
44 if len(n) > 40:
45 yield f, bin(n[:40]), n[40:]
45 yield f, bin(n[:40]), n[40:]
46 else:
46 else:
47 yield f, bin(n), ''
47 yield f, bin(n), ''
48
48
49 def _text(it):
49 def _text(it):
50 files = []
50 files = []
51 lines = []
51 lines = []
52 _hex = revlog.hex
52 _hex = revlog.hex
53 for f, n, fl in it:
53 for f, n, fl in it:
54 files.append(f)
54 files.append(f)
55 # if this is changed to support newlines in filenames,
55 # if this is changed to support newlines in filenames,
56 # be sure to check the templates/ dir again (especially *-raw.tmpl)
56 # be sure to check the templates/ dir again (especially *-raw.tmpl)
57 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
57 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
58
58
59 _checkforbidden(files)
59 _checkforbidden(files)
60 return ''.join(lines)
60 return ''.join(lines)
61
61
62 class lazymanifestiter(object):
62 class lazymanifestiter(object):
63 def __init__(self, lm):
63 def __init__(self, lm):
64 self.pos = 0
64 self.pos = 0
65 self.lm = lm
65 self.lm = lm
66
66
67 def __iter__(self):
67 def __iter__(self):
68 return self
68 return self
69
69
70 def next(self):
70 def next(self):
71 try:
71 try:
72 data, pos = self.lm._get(self.pos)
72 data, pos = self.lm._get(self.pos)
73 except IndexError:
73 except IndexError:
74 raise StopIteration
74 raise StopIteration
75 if pos == -1:
75 if pos == -1:
76 self.pos += 1
76 self.pos += 1
77 return data[0]
77 return data[0]
78 self.pos += 1
78 self.pos += 1
79 zeropos = data.find('\x00', pos)
79 zeropos = data.find('\x00', pos)
80 return data[pos:zeropos]
80 return data[pos:zeropos]
81
81
82 __next__ = next
82 __next__ = next
83
83
84 class lazymanifestiterentries(object):
84 class lazymanifestiterentries(object):
85 def __init__(self, lm):
85 def __init__(self, lm):
86 self.lm = lm
86 self.lm = lm
87 self.pos = 0
87 self.pos = 0
88
88
89 def __iter__(self):
89 def __iter__(self):
90 return self
90 return self
91
91
92 def next(self):
92 def next(self):
93 try:
93 try:
94 data, pos = self.lm._get(self.pos)
94 data, pos = self.lm._get(self.pos)
95 except IndexError:
95 except IndexError:
96 raise StopIteration
96 raise StopIteration
97 if pos == -1:
97 if pos == -1:
98 self.pos += 1
98 self.pos += 1
99 return data
99 return data
100 zeropos = data.find('\x00', pos)
100 zeropos = data.find('\x00', pos)
101 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
101 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
102 zeropos + 1, 40)
102 zeropos + 1, 40)
103 flags = self.lm._getflags(data, self.pos, zeropos)
103 flags = self.lm._getflags(data, self.pos, zeropos)
104 self.pos += 1
104 self.pos += 1
105 return (data[pos:zeropos], hashval, flags)
105 return (data[pos:zeropos], hashval, flags)
106
106
107 __next__ = next
107 __next__ = next
108
108
109 def unhexlify(data, extra, pos, length):
109 def unhexlify(data, extra, pos, length):
110 s = bin(data[pos:pos + length])
110 s = bin(data[pos:pos + length])
111 if extra:
111 if extra:
112 s += chr(extra & 0xff)
112 s += chr(extra & 0xff)
113 return s
113 return s
114
114
115 def _cmp(a, b):
115 def _cmp(a, b):
116 return (a > b) - (a < b)
116 return (a > b) - (a < b)
117
117
118 class _lazymanifest(object):
118 class _lazymanifest(object):
119 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
119 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
120 if positions is None:
120 if positions is None:
121 self.positions = self.findlines(data)
121 self.positions = self.findlines(data)
122 self.extrainfo = [0] * len(self.positions)
122 self.extrainfo = [0] * len(self.positions)
123 self.data = data
123 self.data = data
124 self.extradata = []
124 self.extradata = []
125 else:
125 else:
126 self.positions = positions[:]
126 self.positions = positions[:]
127 self.extrainfo = extrainfo[:]
127 self.extrainfo = extrainfo[:]
128 self.extradata = extradata[:]
128 self.extradata = extradata[:]
129 self.data = data
129 self.data = data
130
130
131 def findlines(self, data):
131 def findlines(self, data):
132 if not data:
132 if not data:
133 return []
133 return []
134 pos = data.find("\n")
134 pos = data.find("\n")
135 if pos == -1 or data[-1:] != '\n':
135 if pos == -1 or data[-1:] != '\n':
136 raise ValueError("Manifest did not end in a newline.")
136 raise ValueError("Manifest did not end in a newline.")
137 positions = [0]
137 positions = [0]
138 prev = data[:data.find('\x00')]
138 prev = data[:data.find('\x00')]
139 while pos < len(data) - 1 and pos != -1:
139 while pos < len(data) - 1 and pos != -1:
140 positions.append(pos + 1)
140 positions.append(pos + 1)
141 nexts = data[pos + 1:data.find('\x00', pos + 1)]
141 nexts = data[pos + 1:data.find('\x00', pos + 1)]
142 if nexts < prev:
142 if nexts < prev:
143 raise ValueError("Manifest lines not in sorted order.")
143 raise ValueError("Manifest lines not in sorted order.")
144 prev = nexts
144 prev = nexts
145 pos = data.find("\n", pos + 1)
145 pos = data.find("\n", pos + 1)
146 return positions
146 return positions
147
147
148 def _get(self, index):
148 def _get(self, index):
149 # get the position encoded in pos:
149 # get the position encoded in pos:
150 # positive number is an index in 'data'
150 # positive number is an index in 'data'
151 # negative number is in extrapieces
151 # negative number is in extrapieces
152 pos = self.positions[index]
152 pos = self.positions[index]
153 if pos >= 0:
153 if pos >= 0:
154 return self.data, pos
154 return self.data, pos
155 return self.extradata[-pos - 1], -1
155 return self.extradata[-pos - 1], -1
156
156
157 def _getkey(self, pos):
157 def _getkey(self, pos):
158 if pos >= 0:
158 if pos >= 0:
159 return self.data[pos:self.data.find('\x00', pos + 1)]
159 return self.data[pos:self.data.find('\x00', pos + 1)]
160 return self.extradata[-pos - 1][0]
160 return self.extradata[-pos - 1][0]
161
161
162 def bsearch(self, key):
162 def bsearch(self, key):
163 first = 0
163 first = 0
164 last = len(self.positions) - 1
164 last = len(self.positions) - 1
165
165
166 while first <= last:
166 while first <= last:
167 midpoint = (first + last)//2
167 midpoint = (first + last)//2
168 nextpos = self.positions[midpoint]
168 nextpos = self.positions[midpoint]
169 candidate = self._getkey(nextpos)
169 candidate = self._getkey(nextpos)
170 r = _cmp(key, candidate)
170 r = _cmp(key, candidate)
171 if r == 0:
171 if r == 0:
172 return midpoint
172 return midpoint
173 else:
173 else:
174 if r < 0:
174 if r < 0:
175 last = midpoint - 1
175 last = midpoint - 1
176 else:
176 else:
177 first = midpoint + 1
177 first = midpoint + 1
178 return -1
178 return -1
179
179
180 def bsearch2(self, key):
180 def bsearch2(self, key):
181 # same as the above, but will always return the position
181 # same as the above, but will always return the position
182 # done for performance reasons
182 # done for performance reasons
183 first = 0
183 first = 0
184 last = len(self.positions) - 1
184 last = len(self.positions) - 1
185
185
186 while first <= last:
186 while first <= last:
187 midpoint = (first + last)//2
187 midpoint = (first + last)//2
188 nextpos = self.positions[midpoint]
188 nextpos = self.positions[midpoint]
189 candidate = self._getkey(nextpos)
189 candidate = self._getkey(nextpos)
190 r = _cmp(key, candidate)
190 r = _cmp(key, candidate)
191 if r == 0:
191 if r == 0:
192 return (midpoint, True)
192 return (midpoint, True)
193 else:
193 else:
194 if r < 0:
194 if r < 0:
195 last = midpoint - 1
195 last = midpoint - 1
196 else:
196 else:
197 first = midpoint + 1
197 first = midpoint + 1
198 return (first, False)
198 return (first, False)
199
199
200 def __contains__(self, key):
200 def __contains__(self, key):
201 return self.bsearch(key) != -1
201 return self.bsearch(key) != -1
202
202
203 def _getflags(self, data, needle, pos):
203 def _getflags(self, data, needle, pos):
204 start = pos + 41
204 start = pos + 41
205 end = data.find("\n", start)
205 end = data.find("\n", start)
206 if end == -1:
206 if end == -1:
207 end = len(data) - 1
207 end = len(data) - 1
208 if start == end:
208 if start == end:
209 return ''
209 return ''
210 return self.data[start:end]
210 return self.data[start:end]
211
211
212 def __getitem__(self, key):
212 def __getitem__(self, key):
213 if not isinstance(key, bytes):
213 if not isinstance(key, bytes):
214 raise TypeError("getitem: manifest keys must be a bytes.")
214 raise TypeError("getitem: manifest keys must be a bytes.")
215 needle = self.bsearch(key)
215 needle = self.bsearch(key)
216 if needle == -1:
216 if needle == -1:
217 raise KeyError
217 raise KeyError
218 data, pos = self._get(needle)
218 data, pos = self._get(needle)
219 if pos == -1:
219 if pos == -1:
220 return (data[1], data[2])
220 return (data[1], data[2])
221 zeropos = data.find('\x00', pos)
221 zeropos = data.find('\x00', pos)
222 assert 0 <= needle <= len(self.positions)
222 assert 0 <= needle <= len(self.positions)
223 assert len(self.extrainfo) == len(self.positions)
223 assert len(self.extrainfo) == len(self.positions)
224 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
224 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
225 flags = self._getflags(data, needle, zeropos)
225 flags = self._getflags(data, needle, zeropos)
226 return (hashval, flags)
226 return (hashval, flags)
227
227
228 def __delitem__(self, key):
228 def __delitem__(self, key):
229 needle, found = self.bsearch2(key)
229 needle, found = self.bsearch2(key)
230 if not found:
230 if not found:
231 raise KeyError
231 raise KeyError
232 cur = self.positions[needle]
232 cur = self.positions[needle]
233 self.positions = self.positions[:needle] + self.positions[needle + 1:]
233 self.positions = self.positions[:needle] + self.positions[needle + 1:]
234 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
234 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
235 if cur >= 0:
235 if cur >= 0:
236 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
236 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
237
237
238 def __setitem__(self, key, value):
238 def __setitem__(self, key, value):
239 if not isinstance(key, bytes):
239 if not isinstance(key, bytes):
240 raise TypeError("setitem: manifest keys must be a byte string.")
240 raise TypeError("setitem: manifest keys must be a byte string.")
241 if not isinstance(value, tuple) or len(value) != 2:
241 if not isinstance(value, tuple) or len(value) != 2:
242 raise TypeError("Manifest values must be a tuple of (node, flags).")
242 raise TypeError("Manifest values must be a tuple of (node, flags).")
243 hashval = value[0]
243 hashval = value[0]
244 if not isinstance(hashval, bytes) or not 20 <= len(hashval) <= 22:
244 if not isinstance(hashval, bytes) or not 20 <= len(hashval) <= 22:
245 raise TypeError("node must be a 20-byte byte string")
245 raise TypeError("node must be a 20-byte byte string")
246 flags = value[1]
246 flags = value[1]
247 if len(hashval) == 22:
247 if len(hashval) == 22:
248 hashval = hashval[:-1]
248 hashval = hashval[:-1]
249 if not isinstance(flags, bytes) or len(flags) > 1:
249 if not isinstance(flags, bytes) or len(flags) > 1:
250 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
250 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
251 needle, found = self.bsearch2(key)
251 needle, found = self.bsearch2(key)
252 if found:
252 if found:
253 # put the item
253 # put the item
254 pos = self.positions[needle]
254 pos = self.positions[needle]
255 if pos < 0:
255 if pos < 0:
256 self.extradata[-pos - 1] = (key, hashval, value[1])
256 self.extradata[-pos - 1] = (key, hashval, value[1])
257 else:
257 else:
258 # just don't bother
258 # just don't bother
259 self.extradata.append((key, hashval, value[1]))
259 self.extradata.append((key, hashval, value[1]))
260 self.positions[needle] = -len(self.extradata)
260 self.positions[needle] = -len(self.extradata)
261 else:
261 else:
262 # not found, put it in with extra positions
262 # not found, put it in with extra positions
263 self.extradata.append((key, hashval, value[1]))
263 self.extradata.append((key, hashval, value[1]))
264 self.positions = (self.positions[:needle] + [-len(self.extradata)]
264 self.positions = (self.positions[:needle] + [-len(self.extradata)]
265 + self.positions[needle:])
265 + self.positions[needle:])
266 self.extrainfo = (self.extrainfo[:needle] + [0] +
266 self.extrainfo = (self.extrainfo[:needle] + [0] +
267 self.extrainfo[needle:])
267 self.extrainfo[needle:])
268
268
269 def copy(self):
269 def copy(self):
270 # XXX call _compact like in C?
270 # XXX call _compact like in C?
271 return _lazymanifest(self.data, self.positions, self.extrainfo,
271 return _lazymanifest(self.data, self.positions, self.extrainfo,
272 self.extradata)
272 self.extradata)
273
273
274 def _compact(self):
274 def _compact(self):
275 # hopefully not called TOO often
275 # hopefully not called TOO often
276 if len(self.extradata) == 0:
276 if len(self.extradata) == 0:
277 return
277 return
278 l = []
278 l = []
279 last_cut = 0
279 last_cut = 0
280 i = 0
280 i = 0
281 offset = 0
281 offset = 0
282 self.extrainfo = [0] * len(self.positions)
282 self.extrainfo = [0] * len(self.positions)
283 while i < len(self.positions):
283 while i < len(self.positions):
284 if self.positions[i] >= 0:
284 if self.positions[i] >= 0:
285 cur = self.positions[i]
285 cur = self.positions[i]
286 last_cut = cur
286 last_cut = cur
287 while True:
287 while True:
288 self.positions[i] = offset
288 self.positions[i] = offset
289 i += 1
289 i += 1
290 if i == len(self.positions) or self.positions[i] < 0:
290 if i == len(self.positions) or self.positions[i] < 0:
291 break
291 break
292 offset += self.positions[i] - cur
292 offset += self.positions[i] - cur
293 cur = self.positions[i]
293 cur = self.positions[i]
294 end_cut = self.data.find('\n', cur)
294 end_cut = self.data.find('\n', cur)
295 if end_cut != -1:
295 if end_cut != -1:
296 end_cut += 1
296 end_cut += 1
297 offset += end_cut - cur
297 offset += end_cut - cur
298 l.append(self.data[last_cut:end_cut])
298 l.append(self.data[last_cut:end_cut])
299 else:
299 else:
300 while i < len(self.positions) and self.positions[i] < 0:
300 while i < len(self.positions) and self.positions[i] < 0:
301 cur = self.positions[i]
301 cur = self.positions[i]
302 t = self.extradata[-cur - 1]
302 t = self.extradata[-cur - 1]
303 l.append(self._pack(t))
303 l.append(self._pack(t))
304 self.positions[i] = offset
304 self.positions[i] = offset
305 if len(t[1]) > 20:
305 if len(t[1]) > 20:
306 self.extrainfo[i] = ord(t[1][21])
306 self.extrainfo[i] = ord(t[1][21])
307 offset += len(l[-1])
307 offset += len(l[-1])
308 i += 1
308 i += 1
309 self.data = ''.join(l)
309 self.data = ''.join(l)
310 self.extradata = []
310 self.extradata = []
311
311
312 def _pack(self, d):
312 def _pack(self, d):
313 return d[0] + '\x00' + hex(d[1][:20]) + d[2] + '\n'
313 return d[0] + '\x00' + hex(d[1][:20]) + d[2] + '\n'
314
314
315 def text(self):
315 def text(self):
316 self._compact()
316 self._compact()
317 return self.data
317 return self.data
318
318
319 def diff(self, m2, clean=False):
319 def diff(self, m2, clean=False):
320 '''Finds changes between the current manifest and m2.'''
320 '''Finds changes between the current manifest and m2.'''
321 # XXX think whether efficiency matters here
321 # XXX think whether efficiency matters here
322 diff = {}
322 diff = {}
323
323
324 for fn, e1, flags in self.iterentries():
324 for fn, e1, flags in self.iterentries():
325 if fn not in m2:
325 if fn not in m2:
326 diff[fn] = (e1, flags), (None, '')
326 diff[fn] = (e1, flags), (None, '')
327 else:
327 else:
328 e2 = m2[fn]
328 e2 = m2[fn]
329 if (e1, flags) != e2:
329 if (e1, flags) != e2:
330 diff[fn] = (e1, flags), e2
330 diff[fn] = (e1, flags), e2
331 elif clean:
331 elif clean:
332 diff[fn] = None
332 diff[fn] = None
333
333
334 for fn, e2, flags in m2.iterentries():
334 for fn, e2, flags in m2.iterentries():
335 if fn not in self:
335 if fn not in self:
336 diff[fn] = (None, ''), (e2, flags)
336 diff[fn] = (None, ''), (e2, flags)
337
337
338 return diff
338 return diff
339
339
340 def iterentries(self):
340 def iterentries(self):
341 return lazymanifestiterentries(self)
341 return lazymanifestiterentries(self)
342
342
343 def iterkeys(self):
343 def iterkeys(self):
344 return lazymanifestiter(self)
344 return lazymanifestiter(self)
345
345
346 def __iter__(self):
346 def __iter__(self):
347 return lazymanifestiter(self)
347 return lazymanifestiter(self)
348
348
349 def __len__(self):
349 def __len__(self):
350 return len(self.positions)
350 return len(self.positions)
351
351
352 def filtercopy(self, filterfn):
352 def filtercopy(self, filterfn):
353 # XXX should be optimized
353 # XXX should be optimized
354 c = _lazymanifest('')
354 c = _lazymanifest('')
355 for f, n, fl in self.iterentries():
355 for f, n, fl in self.iterentries():
356 if filterfn(f):
356 if filterfn(f):
357 c[f] = n, fl
357 c[f] = n, fl
358 return c
358 return c
359
359
360 try:
360 try:
361 _lazymanifest = parsers.lazymanifest
361 _lazymanifest = parsers.lazymanifest
362 except AttributeError:
362 except AttributeError:
363 pass
363 pass
364
364
365 class manifestdict(object):
365 class manifestdict(object):
366 def __init__(self, data=''):
366 def __init__(self, data=''):
367 self._lm = _lazymanifest(data)
367 self._lm = _lazymanifest(data)
368
368
369 def __getitem__(self, key):
369 def __getitem__(self, key):
370 return self._lm[key][0]
370 return self._lm[key][0]
371
371
372 def find(self, key):
372 def find(self, key):
373 return self._lm[key]
373 return self._lm[key]
374
374
375 def __len__(self):
375 def __len__(self):
376 return len(self._lm)
376 return len(self._lm)
377
377
378 def __nonzero__(self):
378 def __nonzero__(self):
379 # nonzero is covered by the __len__ function, but implementing it here
379 # nonzero is covered by the __len__ function, but implementing it here
380 # makes it easier for extensions to override.
380 # makes it easier for extensions to override.
381 return len(self._lm) != 0
381 return len(self._lm) != 0
382
382
383 __bool__ = __nonzero__
383 __bool__ = __nonzero__
384
384
385 def __setitem__(self, key, node):
385 def __setitem__(self, key, node):
386 self._lm[key] = node, self.flags(key, '')
386 self._lm[key] = node, self.flags(key, '')
387
387
388 def __contains__(self, key):
388 def __contains__(self, key):
389 if key is None:
389 if key is None:
390 return False
390 return False
391 return key in self._lm
391 return key in self._lm
392
392
393 def __delitem__(self, key):
393 def __delitem__(self, key):
394 del self._lm[key]
394 del self._lm[key]
395
395
396 def __iter__(self):
396 def __iter__(self):
397 return self._lm.__iter__()
397 return self._lm.__iter__()
398
398
399 def iterkeys(self):
399 def iterkeys(self):
400 return self._lm.iterkeys()
400 return self._lm.iterkeys()
401
401
402 def keys(self):
402 def keys(self):
403 return list(self.iterkeys())
403 return list(self.iterkeys())
404
404
405 def filesnotin(self, m2, match=None):
405 def filesnotin(self, m2, match=None):
406 '''Set of files in this manifest that are not in the other'''
406 '''Set of files in this manifest that are not in the other'''
407 if match:
407 if match:
408 m1 = self.matches(match)
408 m1 = self.matches(match)
409 m2 = m2.matches(match)
409 m2 = m2.matches(match)
410 return m1.filesnotin(m2)
410 return m1.filesnotin(m2)
411 diff = self.diff(m2)
411 diff = self.diff(m2)
412 files = set(filepath
412 files = set(filepath
413 for filepath, hashflags in diff.iteritems()
413 for filepath, hashflags in diff.iteritems()
414 if hashflags[1][0] is None)
414 if hashflags[1][0] is None)
415 return files
415 return files
416
416
417 @propertycache
417 @propertycache
418 def _dirs(self):
418 def _dirs(self):
419 return util.dirs(self)
419 return util.dirs(self)
420
420
421 def dirs(self):
421 def dirs(self):
422 return self._dirs
422 return self._dirs
423
423
424 def hasdir(self, dir):
424 def hasdir(self, dir):
425 return dir in self._dirs
425 return dir in self._dirs
426
426
427 def _filesfastpath(self, match):
427 def _filesfastpath(self, match):
428 '''Checks whether we can correctly and quickly iterate over matcher
428 '''Checks whether we can correctly and quickly iterate over matcher
429 files instead of over manifest files.'''
429 files instead of over manifest files.'''
430 files = match.files()
430 files = match.files()
431 return (len(files) < 100 and (match.isexact() or
431 return (len(files) < 100 and (match.isexact() or
432 (match.prefix() and all(fn in self for fn in files))))
432 (match.prefix() and all(fn in self for fn in files))))
433
433
434 def walk(self, match):
434 def walk(self, match):
435 '''Generates matching file names.
435 '''Generates matching file names.
436
436
437 Equivalent to manifest.matches(match).iterkeys(), but without creating
437 Equivalent to manifest.matches(match).iterkeys(), but without creating
438 an entirely new manifest.
438 an entirely new manifest.
439
439
440 It also reports nonexistent files by marking them bad with match.bad().
440 It also reports nonexistent files by marking them bad with match.bad().
441 '''
441 '''
442 if match.always():
442 if match.always():
443 for f in iter(self):
443 for f in iter(self):
444 yield f
444 yield f
445 return
445 return
446
446
447 fset = set(match.files())
447 fset = set(match.files())
448
448
449 # avoid the entire walk if we're only looking for specific files
449 # avoid the entire walk if we're only looking for specific files
450 if self._filesfastpath(match):
450 if self._filesfastpath(match):
451 for fn in sorted(fset):
451 for fn in sorted(fset):
452 yield fn
452 yield fn
453 return
453 return
454
454
455 for fn in self:
455 for fn in self:
456 if fn in fset:
456 if fn in fset:
457 # specified pattern is the exact name
457 # specified pattern is the exact name
458 fset.remove(fn)
458 fset.remove(fn)
459 if match(fn):
459 if match(fn):
460 yield fn
460 yield fn
461
461
462 # for dirstate.walk, files=['.'] means "walk the whole tree".
462 # for dirstate.walk, files=['.'] means "walk the whole tree".
463 # follow that here, too
463 # follow that here, too
464 fset.discard('.')
464 fset.discard('.')
465
465
466 for fn in sorted(fset):
466 for fn in sorted(fset):
467 if not self.hasdir(fn):
467 if not self.hasdir(fn):
468 match.bad(fn, None)
468 match.bad(fn, None)
469
469
470 def matches(self, match):
470 def matches(self, match):
471 '''generate a new manifest filtered by the match argument'''
471 '''generate a new manifest filtered by the match argument'''
472 if match.always():
472 if match.always():
473 return self.copy()
473 return self.copy()
474
474
475 if self._filesfastpath(match):
475 if self._filesfastpath(match):
476 m = manifestdict()
476 m = manifestdict()
477 lm = self._lm
477 lm = self._lm
478 for fn in match.files():
478 for fn in match.files():
479 if fn in lm:
479 if fn in lm:
480 m._lm[fn] = lm[fn]
480 m._lm[fn] = lm[fn]
481 return m
481 return m
482
482
483 m = manifestdict()
483 m = manifestdict()
484 m._lm = self._lm.filtercopy(match)
484 m._lm = self._lm.filtercopy(match)
485 return m
485 return m
486
486
487 def diff(self, m2, match=None, clean=False):
487 def diff(self, m2, match=None, clean=False):
488 '''Finds changes between the current manifest and m2.
488 '''Finds changes between the current manifest and m2.
489
489
490 Args:
490 Args:
491 m2: the manifest to which this manifest should be compared.
491 m2: the manifest to which this manifest should be compared.
492 clean: if true, include files unchanged between these manifests
492 clean: if true, include files unchanged between these manifests
493 with a None value in the returned dictionary.
493 with a None value in the returned dictionary.
494
494
495 The result is returned as a dict with filename as key and
495 The result is returned as a dict with filename as key and
496 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
496 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
497 nodeid in the current/other manifest and fl1/fl2 is the flag
497 nodeid in the current/other manifest and fl1/fl2 is the flag
498 in the current/other manifest. Where the file does not exist,
498 in the current/other manifest. Where the file does not exist,
499 the nodeid will be None and the flags will be the empty
499 the nodeid will be None and the flags will be the empty
500 string.
500 string.
501 '''
501 '''
502 if match:
502 if match:
503 m1 = self.matches(match)
503 m1 = self.matches(match)
504 m2 = m2.matches(match)
504 m2 = m2.matches(match)
505 return m1.diff(m2, clean=clean)
505 return m1.diff(m2, clean=clean)
506 return self._lm.diff(m2._lm, clean)
506 return self._lm.diff(m2._lm, clean)
507
507
508 def setflag(self, key, flag):
508 def setflag(self, key, flag):
509 self._lm[key] = self[key], flag
509 self._lm[key] = self[key], flag
510
510
511 def get(self, key, default=None):
511 def get(self, key, default=None):
512 try:
512 try:
513 return self._lm[key][0]
513 return self._lm[key][0]
514 except KeyError:
514 except KeyError:
515 return default
515 return default
516
516
517 def flags(self, key, default=''):
517 def flags(self, key, default=''):
518 try:
518 try:
519 return self._lm[key][1]
519 return self._lm[key][1]
520 except KeyError:
520 except KeyError:
521 return default
521 return default
522
522
523 def copy(self):
523 def copy(self):
524 c = manifestdict()
524 c = manifestdict()
525 c._lm = self._lm.copy()
525 c._lm = self._lm.copy()
526 return c
526 return c
527
527
528 def items(self):
528 def items(self):
529 return (x[:2] for x in self._lm.iterentries())
529 return (x[:2] for x in self._lm.iterentries())
530
530
531 iteritems = items
531 iteritems = items
532
532
533 def iterentries(self):
533 def iterentries(self):
534 return self._lm.iterentries()
534 return self._lm.iterentries()
535
535
536 def text(self):
536 def text(self):
537 # most likely uses native version
537 # most likely uses native version
538 return self._lm.text()
538 return self._lm.text()
539
539
540 def fastdelta(self, base, changes):
540 def fastdelta(self, base, changes):
541 """Given a base manifest text as a bytearray and a list of changes
541 """Given a base manifest text as a bytearray and a list of changes
542 relative to that text, compute a delta that can be used by revlog.
542 relative to that text, compute a delta that can be used by revlog.
543 """
543 """
544 delta = []
544 delta = []
545 dstart = None
545 dstart = None
546 dend = None
546 dend = None
547 dline = [""]
547 dline = [""]
548 start = 0
548 start = 0
549 # zero copy representation of base as a buffer
549 # zero copy representation of base as a buffer
550 addbuf = util.buffer(base)
550 addbuf = util.buffer(base)
551
551
552 changes = list(changes)
552 changes = list(changes)
553 if len(changes) < 1000:
553 if len(changes) < 1000:
554 # start with a readonly loop that finds the offset of
554 # start with a readonly loop that finds the offset of
555 # each line and creates the deltas
555 # each line and creates the deltas
556 for f, todelete in changes:
556 for f, todelete in changes:
557 # bs will either be the index of the item or the insert point
557 # bs will either be the index of the item or the insert point
558 start, end = _msearch(addbuf, f, start)
558 start, end = _msearch(addbuf, f, start)
559 if not todelete:
559 if not todelete:
560 h, fl = self._lm[f]
560 h, fl = self._lm[f]
561 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
561 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
562 else:
562 else:
563 if start == end:
563 if start == end:
564 # item we want to delete was not found, error out
564 # item we want to delete was not found, error out
565 raise AssertionError(
565 raise AssertionError(
566 _("failed to remove %s from manifest") % f)
566 _("failed to remove %s from manifest") % f)
567 l = ""
567 l = ""
568 if dstart is not None and dstart <= start and dend >= start:
568 if dstart is not None and dstart <= start and dend >= start:
569 if dend < end:
569 if dend < end:
570 dend = end
570 dend = end
571 if l:
571 if l:
572 dline.append(l)
572 dline.append(l)
573 else:
573 else:
574 if dstart is not None:
574 if dstart is not None:
575 delta.append([dstart, dend, "".join(dline)])
575 delta.append([dstart, dend, "".join(dline)])
576 dstart = start
576 dstart = start
577 dend = end
577 dend = end
578 dline = [l]
578 dline = [l]
579
579
580 if dstart is not None:
580 if dstart is not None:
581 delta.append([dstart, dend, "".join(dline)])
581 delta.append([dstart, dend, "".join(dline)])
582 # apply the delta to the base, and get a delta for addrevision
582 # apply the delta to the base, and get a delta for addrevision
583 deltatext, arraytext = _addlistdelta(base, delta)
583 deltatext, arraytext = _addlistdelta(base, delta)
584 else:
584 else:
585 # For large changes, it's much cheaper to just build the text and
585 # For large changes, it's much cheaper to just build the text and
586 # diff it.
586 # diff it.
587 arraytext = bytearray(self.text())
587 arraytext = bytearray(self.text())
588 deltatext = mdiff.textdiff(
588 deltatext = mdiff.textdiff(
589 util.buffer(base), util.buffer(arraytext))
589 util.buffer(base), util.buffer(arraytext))
590
590
591 return arraytext, deltatext
591 return arraytext, deltatext
592
592
593 def _msearch(m, s, lo=0, hi=None):
593 def _msearch(m, s, lo=0, hi=None):
594 '''return a tuple (start, end) that says where to find s within m.
594 '''return a tuple (start, end) that says where to find s within m.
595
595
596 If the string is found m[start:end] are the line containing
596 If the string is found m[start:end] are the line containing
597 that string. If start == end the string was not found and
597 that string. If start == end the string was not found and
598 they indicate the proper sorted insertion point.
598 they indicate the proper sorted insertion point.
599
599
600 m should be a buffer, a memoryview or a byte string.
600 m should be a buffer, a memoryview or a byte string.
601 s is a byte string'''
601 s is a byte string'''
602 def advance(i, c):
602 def advance(i, c):
603 while i < lenm and m[i:i + 1] != c:
603 while i < lenm and m[i:i + 1] != c:
604 i += 1
604 i += 1
605 return i
605 return i
606 if not s:
606 if not s:
607 return (lo, lo)
607 return (lo, lo)
608 lenm = len(m)
608 lenm = len(m)
609 if not hi:
609 if not hi:
610 hi = lenm
610 hi = lenm
611 while lo < hi:
611 while lo < hi:
612 mid = (lo + hi) // 2
612 mid = (lo + hi) // 2
613 start = mid
613 start = mid
614 while start > 0 and m[start - 1:start] != '\n':
614 while start > 0 and m[start - 1:start] != '\n':
615 start -= 1
615 start -= 1
616 end = advance(start, '\0')
616 end = advance(start, '\0')
617 if bytes(m[start:end]) < s:
617 if bytes(m[start:end]) < s:
618 # we know that after the null there are 40 bytes of sha1
618 # we know that after the null there are 40 bytes of sha1
619 # this translates to the bisect lo = mid + 1
619 # this translates to the bisect lo = mid + 1
620 lo = advance(end + 40, '\n') + 1
620 lo = advance(end + 40, '\n') + 1
621 else:
621 else:
622 # this translates to the bisect hi = mid
622 # this translates to the bisect hi = mid
623 hi = start
623 hi = start
624 end = advance(lo, '\0')
624 end = advance(lo, '\0')
625 found = m[lo:end]
625 found = m[lo:end]
626 if s == found:
626 if s == found:
627 # we know that after the null there are 40 bytes of sha1
627 # we know that after the null there are 40 bytes of sha1
628 end = advance(end + 40, '\n')
628 end = advance(end + 40, '\n')
629 return (lo, end + 1)
629 return (lo, end + 1)
630 else:
630 else:
631 return (lo, lo)
631 return (lo, lo)
632
632
633 def _checkforbidden(l):
633 def _checkforbidden(l):
634 """Check filenames for illegal characters."""
634 """Check filenames for illegal characters."""
635 for f in l:
635 for f in l:
636 if '\n' in f or '\r' in f:
636 if '\n' in f or '\r' in f:
637 raise error.RevlogError(
637 raise error.RevlogError(
638 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
638 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
639
639
640
640
641 # apply the changes collected during the bisect loop to our addlist
641 # apply the changes collected during the bisect loop to our addlist
642 # return a delta suitable for addrevision
642 # return a delta suitable for addrevision
643 def _addlistdelta(addlist, x):
643 def _addlistdelta(addlist, x):
644 # for large addlist arrays, building a new array is cheaper
644 # for large addlist arrays, building a new array is cheaper
645 # than repeatedly modifying the existing one
645 # than repeatedly modifying the existing one
646 currentposition = 0
646 currentposition = 0
647 newaddlist = bytearray()
647 newaddlist = bytearray()
648
648
649 for start, end, content in x:
649 for start, end, content in x:
650 newaddlist += addlist[currentposition:start]
650 newaddlist += addlist[currentposition:start]
651 if content:
651 if content:
652 newaddlist += bytearray(content)
652 newaddlist += bytearray(content)
653
653
654 currentposition = end
654 currentposition = end
655
655
656 newaddlist += addlist[currentposition:]
656 newaddlist += addlist[currentposition:]
657
657
658 deltatext = "".join(struct.pack(">lll", start, end, len(content))
658 deltatext = "".join(struct.pack(">lll", start, end, len(content))
659 + content for start, end, content in x)
659 + content for start, end, content in x)
660 return deltatext, newaddlist
660 return deltatext, newaddlist
661
661
662 def _splittopdir(f):
662 def _splittopdir(f):
663 if '/' in f:
663 if '/' in f:
664 dir, subpath = f.split('/', 1)
664 dir, subpath = f.split('/', 1)
665 return dir + '/', subpath
665 return dir + '/', subpath
666 else:
666 else:
667 return '', f
667 return '', f
668
668
669 _noop = lambda s: None
669 _noop = lambda s: None
670
670
671 class treemanifest(object):
671 class treemanifest(object):
672 def __init__(self, dir='', text=''):
672 def __init__(self, dir='', text=''):
673 self._dir = dir
673 self._dir = dir
674 self._node = revlog.nullid
674 self._node = revlog.nullid
675 self._loadfunc = _noop
675 self._loadfunc = _noop
676 self._copyfunc = _noop
676 self._copyfunc = _noop
677 self._dirty = False
677 self._dirty = False
678 self._dirs = {}
678 self._dirs = {}
679 # Using _lazymanifest here is a little slower than plain old dicts
679 # Using _lazymanifest here is a little slower than plain old dicts
680 self._files = {}
680 self._files = {}
681 self._flags = {}
681 self._flags = {}
682 if text:
682 if text:
683 def readsubtree(subdir, subm):
683 def readsubtree(subdir, subm):
684 raise AssertionError('treemanifest constructor only accepts '
684 raise AssertionError('treemanifest constructor only accepts '
685 'flat manifests')
685 'flat manifests')
686 self.parse(text, readsubtree)
686 self.parse(text, readsubtree)
687 self._dirty = True # Mark flat manifest dirty after parsing
687 self._dirty = True # Mark flat manifest dirty after parsing
688
688
689 def _subpath(self, path):
689 def _subpath(self, path):
690 return self._dir + path
690 return self._dir + path
691
691
692 def __len__(self):
692 def __len__(self):
693 self._load()
693 self._load()
694 size = len(self._files)
694 size = len(self._files)
695 for m in self._dirs.values():
695 for m in self._dirs.values():
696 size += m.__len__()
696 size += m.__len__()
697 return size
697 return size
698
698
699 def __nonzero__(self):
699 def __nonzero__(self):
700 # Faster than "__len() != 0" since it avoids loading sub-manifests
700 # Faster than "__len() != 0" since it avoids loading sub-manifests
701 return not self._isempty()
701 return not self._isempty()
702
702
703 __bool__ = __nonzero__
703 __bool__ = __nonzero__
704
704
705 def _isempty(self):
705 def _isempty(self):
706 self._load() # for consistency; already loaded by all callers
706 self._load() # for consistency; already loaded by all callers
707 return (not self._files and (not self._dirs or
707 return (not self._files and (not self._dirs or
708 all(m._isempty() for m in self._dirs.values())))
708 all(m._isempty() for m in self._dirs.values())))
709
709
710 def __repr__(self):
710 def __repr__(self):
711 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
711 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
712 (self._dir, revlog.hex(self._node),
712 (self._dir, revlog.hex(self._node),
713 bool(self._loadfunc is _noop),
713 bool(self._loadfunc is _noop),
714 self._dirty, id(self)))
714 self._dirty, id(self)))
715
715
716 def dir(self):
716 def dir(self):
717 '''The directory that this tree manifest represents, including a
717 '''The directory that this tree manifest represents, including a
718 trailing '/'. Empty string for the repo root directory.'''
718 trailing '/'. Empty string for the repo root directory.'''
719 return self._dir
719 return self._dir
720
720
721 def node(self):
721 def node(self):
722 '''This node of this instance. nullid for unsaved instances. Should
722 '''This node of this instance. nullid for unsaved instances. Should
723 be updated when the instance is read or written from a revlog.
723 be updated when the instance is read or written from a revlog.
724 '''
724 '''
725 assert not self._dirty
725 assert not self._dirty
726 return self._node
726 return self._node
727
727
728 def setnode(self, node):
728 def setnode(self, node):
729 self._node = node
729 self._node = node
730 self._dirty = False
730 self._dirty = False
731
731
732 def iterentries(self):
732 def iterentries(self):
733 self._load()
733 self._load()
734 for p, n in sorted(itertools.chain(self._dirs.items(),
734 for p, n in sorted(itertools.chain(self._dirs.items(),
735 self._files.items())):
735 self._files.items())):
736 if p in self._files:
736 if p in self._files:
737 yield self._subpath(p), n, self._flags.get(p, '')
737 yield self._subpath(p), n, self._flags.get(p, '')
738 else:
738 else:
739 for x in n.iterentries():
739 for x in n.iterentries():
740 yield x
740 yield x
741
741
742 def items(self):
742 def items(self):
743 self._load()
743 self._load()
744 for p, n in sorted(itertools.chain(self._dirs.items(),
744 for p, n in sorted(itertools.chain(self._dirs.items(),
745 self._files.items())):
745 self._files.items())):
746 if p in self._files:
746 if p in self._files:
747 yield self._subpath(p), n
747 yield self._subpath(p), n
748 else:
748 else:
749 for f, sn in n.iteritems():
749 for f, sn in n.iteritems():
750 yield f, sn
750 yield f, sn
751
751
752 iteritems = items
752 iteritems = items
753
753
754 def iterkeys(self):
754 def iterkeys(self):
755 self._load()
755 self._load()
756 for p in sorted(itertools.chain(self._dirs, self._files)):
756 for p in sorted(itertools.chain(self._dirs, self._files)):
757 if p in self._files:
757 if p in self._files:
758 yield self._subpath(p)
758 yield self._subpath(p)
759 else:
759 else:
760 for f in self._dirs[p]:
760 for f in self._dirs[p]:
761 yield f
761 yield f
762
762
763 def keys(self):
763 def keys(self):
764 return list(self.iterkeys())
764 return list(self.iterkeys())
765
765
766 def __iter__(self):
766 def __iter__(self):
767 return self.iterkeys()
767 return self.iterkeys()
768
768
769 def __contains__(self, f):
769 def __contains__(self, f):
770 if f is None:
770 if f is None:
771 return False
771 return False
772 self._load()
772 self._load()
773 dir, subpath = _splittopdir(f)
773 dir, subpath = _splittopdir(f)
774 if dir:
774 if dir:
775 if dir not in self._dirs:
775 if dir not in self._dirs:
776 return False
776 return False
777 return self._dirs[dir].__contains__(subpath)
777 return self._dirs[dir].__contains__(subpath)
778 else:
778 else:
779 return f in self._files
779 return f in self._files
780
780
781 def get(self, f, default=None):
781 def get(self, f, default=None):
782 self._load()
782 self._load()
783 dir, subpath = _splittopdir(f)
783 dir, subpath = _splittopdir(f)
784 if dir:
784 if dir:
785 if dir not in self._dirs:
785 if dir not in self._dirs:
786 return default
786 return default
787 return self._dirs[dir].get(subpath, default)
787 return self._dirs[dir].get(subpath, default)
788 else:
788 else:
789 return self._files.get(f, default)
789 return self._files.get(f, default)
790
790
791 def __getitem__(self, f):
791 def __getitem__(self, f):
792 self._load()
792 self._load()
793 dir, subpath = _splittopdir(f)
793 dir, subpath = _splittopdir(f)
794 if dir:
794 if dir:
795 return self._dirs[dir].__getitem__(subpath)
795 return self._dirs[dir].__getitem__(subpath)
796 else:
796 else:
797 return self._files[f]
797 return self._files[f]
798
798
799 def flags(self, f):
799 def flags(self, f):
800 self._load()
800 self._load()
801 dir, subpath = _splittopdir(f)
801 dir, subpath = _splittopdir(f)
802 if dir:
802 if dir:
803 if dir not in self._dirs:
803 if dir not in self._dirs:
804 return ''
804 return ''
805 return self._dirs[dir].flags(subpath)
805 return self._dirs[dir].flags(subpath)
806 else:
806 else:
807 if f in self._dirs:
807 if f in self._dirs:
808 return ''
808 return ''
809 return self._flags.get(f, '')
809 return self._flags.get(f, '')
810
810
811 def find(self, f):
811 def find(self, f):
812 self._load()
812 self._load()
813 dir, subpath = _splittopdir(f)
813 dir, subpath = _splittopdir(f)
814 if dir:
814 if dir:
815 return self._dirs[dir].find(subpath)
815 return self._dirs[dir].find(subpath)
816 else:
816 else:
817 return self._files[f], self._flags.get(f, '')
817 return self._files[f], self._flags.get(f, '')
818
818
819 def __delitem__(self, f):
819 def __delitem__(self, f):
820 self._load()
820 self._load()
821 dir, subpath = _splittopdir(f)
821 dir, subpath = _splittopdir(f)
822 if dir:
822 if dir:
823 self._dirs[dir].__delitem__(subpath)
823 self._dirs[dir].__delitem__(subpath)
824 # If the directory is now empty, remove it
824 # If the directory is now empty, remove it
825 if self._dirs[dir]._isempty():
825 if self._dirs[dir]._isempty():
826 del self._dirs[dir]
826 del self._dirs[dir]
827 else:
827 else:
828 del self._files[f]
828 del self._files[f]
829 if f in self._flags:
829 if f in self._flags:
830 del self._flags[f]
830 del self._flags[f]
831 self._dirty = True
831 self._dirty = True
832
832
833 def __setitem__(self, f, n):
833 def __setitem__(self, f, n):
834 assert n is not None
834 assert n is not None
835 self._load()
835 self._load()
836 dir, subpath = _splittopdir(f)
836 dir, subpath = _splittopdir(f)
837 if dir:
837 if dir:
838 if dir not in self._dirs:
838 if dir not in self._dirs:
839 self._dirs[dir] = treemanifest(self._subpath(dir))
839 self._dirs[dir] = treemanifest(self._subpath(dir))
840 self._dirs[dir].__setitem__(subpath, n)
840 self._dirs[dir].__setitem__(subpath, n)
841 else:
841 else:
842 self._files[f] = n[:21] # to match manifestdict's behavior
842 self._files[f] = n[:21] # to match manifestdict's behavior
843 self._dirty = True
843 self._dirty = True
844
844
845 def _load(self):
845 def _load(self):
846 if self._loadfunc is not _noop:
846 if self._loadfunc is not _noop:
847 lf, self._loadfunc = self._loadfunc, _noop
847 lf, self._loadfunc = self._loadfunc, _noop
848 lf(self)
848 lf(self)
849 elif self._copyfunc is not _noop:
849 elif self._copyfunc is not _noop:
850 cf, self._copyfunc = self._copyfunc, _noop
850 cf, self._copyfunc = self._copyfunc, _noop
851 cf(self)
851 cf(self)
852
852
853 def setflag(self, f, flags):
853 def setflag(self, f, flags):
854 """Set the flags (symlink, executable) for path f."""
854 """Set the flags (symlink, executable) for path f."""
855 self._load()
855 self._load()
856 dir, subpath = _splittopdir(f)
856 dir, subpath = _splittopdir(f)
857 if dir:
857 if dir:
858 if dir not in self._dirs:
858 if dir not in self._dirs:
859 self._dirs[dir] = treemanifest(self._subpath(dir))
859 self._dirs[dir] = treemanifest(self._subpath(dir))
860 self._dirs[dir].setflag(subpath, flags)
860 self._dirs[dir].setflag(subpath, flags)
861 else:
861 else:
862 self._flags[f] = flags
862 self._flags[f] = flags
863 self._dirty = True
863 self._dirty = True
864
864
865 def copy(self):
865 def copy(self):
866 copy = treemanifest(self._dir)
866 copy = treemanifest(self._dir)
867 copy._node = self._node
867 copy._node = self._node
868 copy._dirty = self._dirty
868 copy._dirty = self._dirty
869 if self._copyfunc is _noop:
869 if self._copyfunc is _noop:
870 def _copyfunc(s):
870 def _copyfunc(s):
871 self._load()
871 self._load()
872 for d in self._dirs:
872 for d in self._dirs:
873 s._dirs[d] = self._dirs[d].copy()
873 s._dirs[d] = self._dirs[d].copy()
874 s._files = dict.copy(self._files)
874 s._files = dict.copy(self._files)
875 s._flags = dict.copy(self._flags)
875 s._flags = dict.copy(self._flags)
876 if self._loadfunc is _noop:
876 if self._loadfunc is _noop:
877 _copyfunc(copy)
877 _copyfunc(copy)
878 else:
878 else:
879 copy._copyfunc = _copyfunc
879 copy._copyfunc = _copyfunc
880 else:
880 else:
881 copy._copyfunc = self._copyfunc
881 copy._copyfunc = self._copyfunc
882 return copy
882 return copy
883
883
884 def filesnotin(self, m2, match=None):
884 def filesnotin(self, m2, match=None):
885 '''Set of files in this manifest that are not in the other'''
885 '''Set of files in this manifest that are not in the other'''
886 if match:
886 if match:
887 m1 = self.matches(match)
887 m1 = self.matches(match)
888 m2 = m2.matches(match)
888 m2 = m2.matches(match)
889 return m1.filesnotin(m2)
889 return m1.filesnotin(m2)
890
890
891 files = set()
891 files = set()
892 def _filesnotin(t1, t2):
892 def _filesnotin(t1, t2):
893 if t1._node == t2._node and not t1._dirty and not t2._dirty:
893 if t1._node == t2._node and not t1._dirty and not t2._dirty:
894 return
894 return
895 t1._load()
895 t1._load()
896 t2._load()
896 t2._load()
897 for d, m1 in t1._dirs.iteritems():
897 for d, m1 in t1._dirs.iteritems():
898 if d in t2._dirs:
898 if d in t2._dirs:
899 m2 = t2._dirs[d]
899 m2 = t2._dirs[d]
900 _filesnotin(m1, m2)
900 _filesnotin(m1, m2)
901 else:
901 else:
902 files.update(m1.iterkeys())
902 files.update(m1.iterkeys())
903
903
904 for fn in t1._files:
904 for fn in t1._files:
905 if fn not in t2._files:
905 if fn not in t2._files:
906 files.add(t1._subpath(fn))
906 files.add(t1._subpath(fn))
907
907
908 _filesnotin(self, m2)
908 _filesnotin(self, m2)
909 return files
909 return files
910
910
911 @propertycache
911 @propertycache
912 def _alldirs(self):
912 def _alldirs(self):
913 return util.dirs(self)
913 return util.dirs(self)
914
914
915 def dirs(self):
915 def dirs(self):
916 return self._alldirs
916 return self._alldirs
917
917
918 def hasdir(self, dir):
918 def hasdir(self, dir):
919 self._load()
919 self._load()
920 topdir, subdir = _splittopdir(dir)
920 topdir, subdir = _splittopdir(dir)
921 if topdir:
921 if topdir:
922 if topdir in self._dirs:
922 if topdir in self._dirs:
923 return self._dirs[topdir].hasdir(subdir)
923 return self._dirs[topdir].hasdir(subdir)
924 return False
924 return False
925 return (dir + '/') in self._dirs
925 return (dir + '/') in self._dirs
926
926
927 def walk(self, match):
927 def walk(self, match):
928 '''Generates matching file names.
928 '''Generates matching file names.
929
929
930 Equivalent to manifest.matches(match).iterkeys(), but without creating
930 Equivalent to manifest.matches(match).iterkeys(), but without creating
931 an entirely new manifest.
931 an entirely new manifest.
932
932
933 It also reports nonexistent files by marking them bad with match.bad().
933 It also reports nonexistent files by marking them bad with match.bad().
934 '''
934 '''
935 if match.always():
935 if match.always():
936 for f in iter(self):
936 for f in iter(self):
937 yield f
937 yield f
938 return
938 return
939
939
940 fset = set(match.files())
940 fset = set(match.files())
941
941
942 for fn in self._walk(match):
942 for fn in self._walk(match):
943 if fn in fset:
943 if fn in fset:
944 # specified pattern is the exact name
944 # specified pattern is the exact name
945 fset.remove(fn)
945 fset.remove(fn)
946 yield fn
946 yield fn
947
947
948 # for dirstate.walk, files=['.'] means "walk the whole tree".
948 # for dirstate.walk, files=['.'] means "walk the whole tree".
949 # follow that here, too
949 # follow that here, too
950 fset.discard('.')
950 fset.discard('.')
951
951
952 for fn in sorted(fset):
952 for fn in sorted(fset):
953 if not self.hasdir(fn):
953 if not self.hasdir(fn):
954 match.bad(fn, None)
954 match.bad(fn, None)
955
955
956 def _walk(self, match):
956 def _walk(self, match):
957 '''Recursively generates matching file names for walk().'''
957 '''Recursively generates matching file names for walk().'''
958 if not match.visitdir(self._dir[:-1] or '.'):
958 if not match.visitdir(self._dir[:-1] or '.'):
959 return
959 return
960
960
961 # yield this dir's files and walk its submanifests
961 # yield this dir's files and walk its submanifests
962 self._load()
962 self._load()
963 for p in sorted(list(self._dirs) + list(self._files)):
963 for p in sorted(list(self._dirs) + list(self._files)):
964 if p in self._files:
964 if p in self._files:
965 fullp = self._subpath(p)
965 fullp = self._subpath(p)
966 if match(fullp):
966 if match(fullp):
967 yield fullp
967 yield fullp
968 else:
968 else:
969 for f in self._dirs[p]._walk(match):
969 for f in self._dirs[p]._walk(match):
970 yield f
970 yield f
971
971
972 def matches(self, match):
972 def matches(self, match):
973 '''generate a new manifest filtered by the match argument'''
973 '''generate a new manifest filtered by the match argument'''
974 if match.always():
974 if match.always():
975 return self.copy()
975 return self.copy()
976
976
977 return self._matches(match)
977 return self._matches(match)
978
978
979 def _matches(self, match):
979 def _matches(self, match):
980 '''recursively generate a new manifest filtered by the match argument.
980 '''recursively generate a new manifest filtered by the match argument.
981 '''
981 '''
982
982
983 visit = match.visitdir(self._dir[:-1] or '.')
983 visit = match.visitdir(self._dir[:-1] or '.')
984 if visit == 'all':
984 if visit == 'all':
985 return self.copy()
985 return self.copy()
986 ret = treemanifest(self._dir)
986 ret = treemanifest(self._dir)
987 if not visit:
987 if not visit:
988 return ret
988 return ret
989
989
990 self._load()
990 self._load()
991 for fn in self._files:
991 for fn in self._files:
992 fullp = self._subpath(fn)
992 fullp = self._subpath(fn)
993 if not match(fullp):
993 if not match(fullp):
994 continue
994 continue
995 ret._files[fn] = self._files[fn]
995 ret._files[fn] = self._files[fn]
996 if fn in self._flags:
996 if fn in self._flags:
997 ret._flags[fn] = self._flags[fn]
997 ret._flags[fn] = self._flags[fn]
998
998
999 for dir, subm in self._dirs.iteritems():
999 for dir, subm in self._dirs.iteritems():
1000 m = subm._matches(match)
1000 m = subm._matches(match)
1001 if not m._isempty():
1001 if not m._isempty():
1002 ret._dirs[dir] = m
1002 ret._dirs[dir] = m
1003
1003
1004 if not ret._isempty():
1004 if not ret._isempty():
1005 ret._dirty = True
1005 ret._dirty = True
1006 return ret
1006 return ret
1007
1007
1008 def diff(self, m2, match=None, clean=False):
1008 def diff(self, m2, match=None, clean=False):
1009 '''Finds changes between the current manifest and m2.
1009 '''Finds changes between the current manifest and m2.
1010
1010
1011 Args:
1011 Args:
1012 m2: the manifest to which this manifest should be compared.
1012 m2: the manifest to which this manifest should be compared.
1013 clean: if true, include files unchanged between these manifests
1013 clean: if true, include files unchanged between these manifests
1014 with a None value in the returned dictionary.
1014 with a None value in the returned dictionary.
1015
1015
1016 The result is returned as a dict with filename as key and
1016 The result is returned as a dict with filename as key and
1017 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1017 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1018 nodeid in the current/other manifest and fl1/fl2 is the flag
1018 nodeid in the current/other manifest and fl1/fl2 is the flag
1019 in the current/other manifest. Where the file does not exist,
1019 in the current/other manifest. Where the file does not exist,
1020 the nodeid will be None and the flags will be the empty
1020 the nodeid will be None and the flags will be the empty
1021 string.
1021 string.
1022 '''
1022 '''
1023 if match:
1023 if match:
1024 m1 = self.matches(match)
1024 m1 = self.matches(match)
1025 m2 = m2.matches(match)
1025 m2 = m2.matches(match)
1026 return m1.diff(m2, clean=clean)
1026 return m1.diff(m2, clean=clean)
1027 result = {}
1027 result = {}
1028 emptytree = treemanifest()
1028 emptytree = treemanifest()
1029 def _diff(t1, t2):
1029 def _diff(t1, t2):
1030 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1030 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1031 return
1031 return
1032 t1._load()
1032 t1._load()
1033 t2._load()
1033 t2._load()
1034 for d, m1 in t1._dirs.iteritems():
1034 for d, m1 in t1._dirs.iteritems():
1035 m2 = t2._dirs.get(d, emptytree)
1035 m2 = t2._dirs.get(d, emptytree)
1036 _diff(m1, m2)
1036 _diff(m1, m2)
1037
1037
1038 for d, m2 in t2._dirs.iteritems():
1038 for d, m2 in t2._dirs.iteritems():
1039 if d not in t1._dirs:
1039 if d not in t1._dirs:
1040 _diff(emptytree, m2)
1040 _diff(emptytree, m2)
1041
1041
1042 for fn, n1 in t1._files.iteritems():
1042 for fn, n1 in t1._files.iteritems():
1043 fl1 = t1._flags.get(fn, '')
1043 fl1 = t1._flags.get(fn, '')
1044 n2 = t2._files.get(fn, None)
1044 n2 = t2._files.get(fn, None)
1045 fl2 = t2._flags.get(fn, '')
1045 fl2 = t2._flags.get(fn, '')
1046 if n1 != n2 or fl1 != fl2:
1046 if n1 != n2 or fl1 != fl2:
1047 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1047 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1048 elif clean:
1048 elif clean:
1049 result[t1._subpath(fn)] = None
1049 result[t1._subpath(fn)] = None
1050
1050
1051 for fn, n2 in t2._files.iteritems():
1051 for fn, n2 in t2._files.iteritems():
1052 if fn not in t1._files:
1052 if fn not in t1._files:
1053 fl2 = t2._flags.get(fn, '')
1053 fl2 = t2._flags.get(fn, '')
1054 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1054 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1055
1055
1056 _diff(self, m2)
1056 _diff(self, m2)
1057 return result
1057 return result
1058
1058
1059 def unmodifiedsince(self, m2):
1059 def unmodifiedsince(self, m2):
1060 return not self._dirty and not m2._dirty and self._node == m2._node
1060 return not self._dirty and not m2._dirty and self._node == m2._node
1061
1061
1062 def parse(self, text, readsubtree):
1062 def parse(self, text, readsubtree):
1063 for f, n, fl in _parse(text):
1063 for f, n, fl in _parse(text):
1064 if fl == 't':
1064 if fl == 't':
1065 f = f + '/'
1065 f = f + '/'
1066 self._dirs[f] = readsubtree(self._subpath(f), n)
1066 self._dirs[f] = readsubtree(self._subpath(f), n)
1067 elif '/' in f:
1067 elif '/' in f:
1068 # This is a flat manifest, so use __setitem__ and setflag rather
1068 # This is a flat manifest, so use __setitem__ and setflag rather
1069 # than assigning directly to _files and _flags, so we can
1069 # than assigning directly to _files and _flags, so we can
1070 # assign a path in a subdirectory, and to mark dirty (compared
1070 # assign a path in a subdirectory, and to mark dirty (compared
1071 # to nullid).
1071 # to nullid).
1072 self[f] = n
1072 self[f] = n
1073 if fl:
1073 if fl:
1074 self.setflag(f, fl)
1074 self.setflag(f, fl)
1075 else:
1075 else:
1076 # Assigning to _files and _flags avoids marking as dirty,
1076 # Assigning to _files and _flags avoids marking as dirty,
1077 # and should be a little faster.
1077 # and should be a little faster.
1078 self._files[f] = n
1078 self._files[f] = n
1079 if fl:
1079 if fl:
1080 self._flags[f] = fl
1080 self._flags[f] = fl
1081
1081
1082 def text(self):
1082 def text(self):
1083 """Get the full data of this manifest as a bytestring."""
1083 """Get the full data of this manifest as a bytestring."""
1084 self._load()
1084 self._load()
1085 return _text(self.iterentries())
1085 return _text(self.iterentries())
1086
1086
1087 def dirtext(self):
1087 def dirtext(self):
1088 """Get the full data of this directory as a bytestring. Make sure that
1088 """Get the full data of this directory as a bytestring. Make sure that
1089 any submanifests have been written first, so their nodeids are correct.
1089 any submanifests have been written first, so their nodeids are correct.
1090 """
1090 """
1091 self._load()
1091 self._load()
1092 flags = self.flags
1092 flags = self.flags
1093 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1093 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1094 files = [(f, self._files[f], flags(f)) for f in self._files]
1094 files = [(f, self._files[f], flags(f)) for f in self._files]
1095 return _text(sorted(dirs + files))
1095 return _text(sorted(dirs + files))
1096
1096
1097 def read(self, gettext, readsubtree):
1097 def read(self, gettext, readsubtree):
1098 def _load_for_read(s):
1098 def _load_for_read(s):
1099 s.parse(gettext(), readsubtree)
1099 s.parse(gettext(), readsubtree)
1100 s._dirty = False
1100 s._dirty = False
1101 self._loadfunc = _load_for_read
1101 self._loadfunc = _load_for_read
1102
1102
1103 def writesubtrees(self, m1, m2, writesubtree):
1103 def writesubtrees(self, m1, m2, writesubtree):
1104 self._load() # for consistency; should never have any effect here
1104 self._load() # for consistency; should never have any effect here
1105 m1._load()
1105 m1._load()
1106 m2._load()
1106 m2._load()
1107 emptytree = treemanifest()
1107 emptytree = treemanifest()
1108 for d, subm in self._dirs.iteritems():
1108 for d, subm in self._dirs.iteritems():
1109 subp1 = m1._dirs.get(d, emptytree)._node
1109 subp1 = m1._dirs.get(d, emptytree)._node
1110 subp2 = m2._dirs.get(d, emptytree)._node
1110 subp2 = m2._dirs.get(d, emptytree)._node
1111 if subp1 == revlog.nullid:
1111 if subp1 == revlog.nullid:
1112 subp1, subp2 = subp2, subp1
1112 subp1, subp2 = subp2, subp1
1113 writesubtree(subm, subp1, subp2)
1113 writesubtree(subm, subp1, subp2)
1114
1114
1115 def walksubtrees(self, matcher=None):
1115 def walksubtrees(self, matcher=None):
1116 """Returns an iterator of the subtrees of this manifest, including this
1116 """Returns an iterator of the subtrees of this manifest, including this
1117 manifest itself.
1117 manifest itself.
1118
1118
1119 If `matcher` is provided, it only returns subtrees that match.
1119 If `matcher` is provided, it only returns subtrees that match.
1120 """
1120 """
1121 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1121 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1122 return
1122 return
1123 if not matcher or matcher(self._dir[:-1]):
1123 if not matcher or matcher(self._dir[:-1]):
1124 yield self
1124 yield self
1125
1125
1126 self._load()
1126 self._load()
1127 for d, subm in self._dirs.iteritems():
1127 for d, subm in self._dirs.iteritems():
1128 for subtree in subm.walksubtrees(matcher=matcher):
1128 for subtree in subm.walksubtrees(matcher=matcher):
1129 yield subtree
1129 yield subtree
1130
1130
1131 class manifestrevlog(revlog.revlog):
1131 class manifestrevlog(revlog.revlog):
1132 '''A revlog that stores manifest texts. This is responsible for caching the
1132 '''A revlog that stores manifest texts. This is responsible for caching the
1133 full-text manifest contents.
1133 full-text manifest contents.
1134 '''
1134 '''
1135 def __init__(self, opener, dir='', dirlogcache=None, indexfile=None,
1135 def __init__(self, opener, dir='', dirlogcache=None, indexfile=None,
1136 treemanifest=False):
1136 treemanifest=False):
1137 """Constructs a new manifest revlog
1137 """Constructs a new manifest revlog
1138
1138
1139 `indexfile` - used by extensions to have two manifests at once, like
1139 `indexfile` - used by extensions to have two manifests at once, like
1140 when transitioning between flatmanifeset and treemanifests.
1140 when transitioning between flatmanifeset and treemanifests.
1141
1141
1142 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1142 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1143 options can also be used to make this a tree manifest revlog. The opener
1143 options can also be used to make this a tree manifest revlog. The opener
1144 option takes precedence, so if it is set to True, we ignore whatever
1144 option takes precedence, so if it is set to True, we ignore whatever
1145 value is passed in to the constructor.
1145 value is passed in to the constructor.
1146 """
1146 """
1147 # During normal operations, we expect to deal with not more than four
1147 # During normal operations, we expect to deal with not more than four
1148 # revs at a time (such as during commit --amend). When rebasing large
1148 # revs at a time (such as during commit --amend). When rebasing large
1149 # stacks of commits, the number can go up, hence the config knob below.
1149 # stacks of commits, the number can go up, hence the config knob below.
1150 cachesize = 4
1150 cachesize = 4
1151 optiontreemanifest = False
1151 optiontreemanifest = False
1152 opts = getattr(opener, 'options', None)
1152 opts = getattr(opener, 'options', None)
1153 if opts is not None:
1153 if opts is not None:
1154 cachesize = opts.get('manifestcachesize', cachesize)
1154 cachesize = opts.get('manifestcachesize', cachesize)
1155 optiontreemanifest = opts.get('treemanifest', False)
1155 optiontreemanifest = opts.get('treemanifest', False)
1156
1156
1157 self._treeondisk = optiontreemanifest or treemanifest
1157 self._treeondisk = optiontreemanifest or treemanifest
1158
1158
1159 self._fulltextcache = util.lrucachedict(cachesize)
1159 self._fulltextcache = util.lrucachedict(cachesize)
1160
1160
1161 if dir:
1161 if dir:
1162 assert self._treeondisk, 'opts is %r' % opts
1162 assert self._treeondisk, 'opts is %r' % opts
1163
1163
1164 if indexfile is None:
1164 if indexfile is None:
1165 indexfile = '00manifest.i'
1165 indexfile = '00manifest.i'
1166 if dir:
1166 if dir:
1167 indexfile = "meta/" + dir + indexfile
1167 indexfile = "meta/" + dir + indexfile
1168
1168
1169 self._dir = dir
1169 self._dir = dir
1170 # The dirlogcache is kept on the root manifest log
1170 # The dirlogcache is kept on the root manifest log
1171 if dir:
1171 if dir:
1172 self._dirlogcache = dirlogcache
1172 self._dirlogcache = dirlogcache
1173 else:
1173 else:
1174 self._dirlogcache = {'': self}
1174 self._dirlogcache = {'': self}
1175
1175
1176 super(manifestrevlog, self).__init__(opener, indexfile,
1176 super(manifestrevlog, self).__init__(opener, indexfile,
1177 # only root indexfile is cached
1177 # only root indexfile is cached
1178 checkambig=not bool(dir),
1178 checkambig=not bool(dir),
1179 mmaplargeindex=True)
1179 mmaplargeindex=True)
1180
1180
1181 @property
1181 @property
1182 def fulltextcache(self):
1182 def fulltextcache(self):
1183 return self._fulltextcache
1183 return self._fulltextcache
1184
1184
1185 def clearcaches(self):
1185 def clearcaches(self):
1186 super(manifestrevlog, self).clearcaches()
1186 super(manifestrevlog, self).clearcaches()
1187 self._fulltextcache.clear()
1187 self._fulltextcache.clear()
1188 self._dirlogcache = {'': self}
1188 self._dirlogcache = {'': self}
1189
1189
1190 def dirlog(self, d):
1190 def dirlog(self, d):
1191 if d:
1191 if d:
1192 assert self._treeondisk
1192 assert self._treeondisk
1193 if d not in self._dirlogcache:
1193 if d not in self._dirlogcache:
1194 mfrevlog = manifestrevlog(self.opener, d,
1194 mfrevlog = manifestrevlog(self.opener, d,
1195 self._dirlogcache,
1195 self._dirlogcache,
1196 treemanifest=self._treeondisk)
1196 treemanifest=self._treeondisk)
1197 self._dirlogcache[d] = mfrevlog
1197 self._dirlogcache[d] = mfrevlog
1198 return self._dirlogcache[d]
1198 return self._dirlogcache[d]
1199
1199
1200 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1200 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1201 if p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta'):
1201 if p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta'):
1202 # If our first parent is in the manifest cache, we can
1202 # If our first parent is in the manifest cache, we can
1203 # compute a delta here using properties we know about the
1203 # compute a delta here using properties we know about the
1204 # manifest up-front, which may save time later for the
1204 # manifest up-front, which may save time later for the
1205 # revlog layer.
1205 # revlog layer.
1206
1206
1207 _checkforbidden(added)
1207 _checkforbidden(added)
1208 # combine the changed lists into one sorted iterator
1208 # combine the changed lists into one sorted iterator
1209 work = heapq.merge([(x, False) for x in added],
1209 work = heapq.merge([(x, False) for x in added],
1210 [(x, True) for x in removed])
1210 [(x, True) for x in removed])
1211
1211
1212 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1212 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1213 cachedelta = self.rev(p1), deltatext
1213 cachedelta = self.rev(p1), deltatext
1214 text = util.buffer(arraytext)
1214 text = util.buffer(arraytext)
1215 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1215 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1216 else:
1216 else:
1217 # The first parent manifest isn't already loaded, so we'll
1217 # The first parent manifest isn't already loaded, so we'll
1218 # just encode a fulltext of the manifest and pass that
1218 # just encode a fulltext of the manifest and pass that
1219 # through to the revlog layer, and let it handle the delta
1219 # through to the revlog layer, and let it handle the delta
1220 # process.
1220 # process.
1221 if self._treeondisk:
1221 if self._treeondisk:
1222 assert readtree, "readtree must be set for treemanifest writes"
1222 assert readtree, "readtree must be set for treemanifest writes"
1223 m1 = readtree(self._dir, p1)
1223 m1 = readtree(self._dir, p1)
1224 m2 = readtree(self._dir, p2)
1224 m2 = readtree(self._dir, p2)
1225 n = self._addtree(m, transaction, link, m1, m2, readtree)
1225 n = self._addtree(m, transaction, link, m1, m2, readtree)
1226 arraytext = None
1226 arraytext = None
1227 else:
1227 else:
1228 text = m.text()
1228 text = m.text()
1229 n = self.addrevision(text, transaction, link, p1, p2)
1229 n = self.addrevision(text, transaction, link, p1, p2)
1230 arraytext = bytearray(text)
1230 arraytext = bytearray(text)
1231
1231
1232 if arraytext is not None:
1232 if arraytext is not None:
1233 self.fulltextcache[n] = arraytext
1233 self.fulltextcache[n] = arraytext
1234
1234
1235 return n
1235 return n
1236
1236
1237 def _addtree(self, m, transaction, link, m1, m2, readtree):
1237 def _addtree(self, m, transaction, link, m1, m2, readtree):
1238 # If the manifest is unchanged compared to one parent,
1238 # If the manifest is unchanged compared to one parent,
1239 # don't write a new revision
1239 # don't write a new revision
1240 if self._dir != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(m2)):
1240 if self._dir != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(m2)):
1241 return m.node()
1241 return m.node()
1242 def writesubtree(subm, subp1, subp2):
1242 def writesubtree(subm, subp1, subp2):
1243 sublog = self.dirlog(subm.dir())
1243 sublog = self.dirlog(subm.dir())
1244 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1244 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1245 readtree=readtree)
1245 readtree=readtree)
1246 m.writesubtrees(m1, m2, writesubtree)
1246 m.writesubtrees(m1, m2, writesubtree)
1247 text = m.dirtext()
1247 text = m.dirtext()
1248 n = None
1248 n = None
1249 if self._dir != '':
1249 if self._dir != '':
1250 # Double-check whether contents are unchanged to one parent
1250 # Double-check whether contents are unchanged to one parent
1251 if text == m1.dirtext():
1251 if text == m1.dirtext():
1252 n = m1.node()
1252 n = m1.node()
1253 elif text == m2.dirtext():
1253 elif text == m2.dirtext():
1254 n = m2.node()
1254 n = m2.node()
1255
1255
1256 if not n:
1256 if not n:
1257 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1257 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1258
1258
1259 # Save nodeid so parent manifest can calculate its nodeid
1259 # Save nodeid so parent manifest can calculate its nodeid
1260 m.setnode(n)
1260 m.setnode(n)
1261 return n
1261 return n
1262
1262
1263 class manifestlog(object):
1263 class manifestlog(object):
1264 """A collection class representing the collection of manifest snapshots
1264 """A collection class representing the collection of manifest snapshots
1265 referenced by commits in the repository.
1265 referenced by commits in the repository.
1266
1266
1267 In this situation, 'manifest' refers to the abstract concept of a snapshot
1267 In this situation, 'manifest' refers to the abstract concept of a snapshot
1268 of the list of files in the given commit. Consumers of the output of this
1268 of the list of files in the given commit. Consumers of the output of this
1269 class do not care about the implementation details of the actual manifests
1269 class do not care about the implementation details of the actual manifests
1270 they receive (i.e. tree or flat or lazily loaded, etc)."""
1270 they receive (i.e. tree or flat or lazily loaded, etc)."""
1271 def __init__(self, opener, repo):
1271 def __init__(self, opener, repo):
1272 usetreemanifest = False
1272 usetreemanifest = False
1273 cachesize = 4
1273 cachesize = 4
1274
1274
1275 opts = getattr(opener, 'options', None)
1275 opts = getattr(opener, 'options', None)
1276 if opts is not None:
1276 if opts is not None:
1277 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1277 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1278 cachesize = opts.get('manifestcachesize', cachesize)
1278 cachesize = opts.get('manifestcachesize', cachesize)
1279 self._treeinmem = usetreemanifest
1279 self._treeinmem = usetreemanifest
1280
1280
1281 self._revlog = repo._constructmanifest()
1281 self._revlog = repo._constructmanifest()
1282 self._narrowmatch = repo.narrowmatch()
1282 self._narrowmatch = repo.narrowmatch()
1283
1283
1284 # A cache of the manifestctx or treemanifestctx for each directory
1284 # A cache of the manifestctx or treemanifestctx for each directory
1285 self._dirmancache = {}
1285 self._dirmancache = {}
1286 self._dirmancache[''] = util.lrucachedict(cachesize)
1286 self._dirmancache[''] = util.lrucachedict(cachesize)
1287
1287
1288 self.cachesize = cachesize
1288 self.cachesize = cachesize
1289
1289
1290 def __getitem__(self, node):
1290 def __getitem__(self, node):
1291 """Retrieves the manifest instance for the given node. Throws a
1291 """Retrieves the manifest instance for the given node. Throws a
1292 LookupError if not found.
1292 LookupError if not found.
1293 """
1293 """
1294 return self.get('', node)
1294 return self.get('', node)
1295
1295
1296 def get(self, dir, node, verify=True):
1296 def get(self, dir, node, verify=True):
1297 """Retrieves the manifest instance for the given node. Throws a
1297 """Retrieves the manifest instance for the given node. Throws a
1298 LookupError if not found.
1298 LookupError if not found.
1299
1299
1300 `verify` - if True an exception will be thrown if the node is not in
1300 `verify` - if True an exception will be thrown if the node is not in
1301 the revlog
1301 the revlog
1302 """
1302 """
1303 if node in self._dirmancache.get(dir, ()):
1303 if node in self._dirmancache.get(dir, ()):
1304 return self._dirmancache[dir][node]
1304 return self._dirmancache[dir][node]
1305
1305
1306 if not self._narrowmatch.always():
1307 if not self._narrowmatch.visitdir(dir[:-1] or '.'):
1308 return excludeddirmanifestctx(dir, node)
1306 if dir:
1309 if dir:
1307 if self._revlog._treeondisk:
1310 if self._revlog._treeondisk:
1308 if verify:
1311 if verify:
1309 dirlog = self._revlog.dirlog(dir)
1312 dirlog = self._revlog.dirlog(dir)
1310 if node not in dirlog.nodemap:
1313 if node not in dirlog.nodemap:
1311 raise LookupError(node, dirlog.indexfile,
1314 raise LookupError(node, dirlog.indexfile,
1312 _('no node'))
1315 _('no node'))
1313 m = treemanifestctx(self, dir, node)
1316 m = treemanifestctx(self, dir, node)
1314 else:
1317 else:
1315 raise error.Abort(
1318 raise error.Abort(
1316 _("cannot ask for manifest directory '%s' in a flat "
1319 _("cannot ask for manifest directory '%s' in a flat "
1317 "manifest") % dir)
1320 "manifest") % dir)
1318 else:
1321 else:
1319 if verify:
1322 if verify:
1320 if node not in self._revlog.nodemap:
1323 if node not in self._revlog.nodemap:
1321 raise LookupError(node, self._revlog.indexfile,
1324 raise LookupError(node, self._revlog.indexfile,
1322 _('no node'))
1325 _('no node'))
1323 if self._treeinmem:
1326 if self._treeinmem:
1324 m = treemanifestctx(self, '', node)
1327 m = treemanifestctx(self, '', node)
1325 else:
1328 else:
1326 m = manifestctx(self, node)
1329 m = manifestctx(self, node)
1327
1330
1328 if node != revlog.nullid:
1331 if node != revlog.nullid:
1329 mancache = self._dirmancache.get(dir)
1332 mancache = self._dirmancache.get(dir)
1330 if not mancache:
1333 if not mancache:
1331 mancache = util.lrucachedict(self.cachesize)
1334 mancache = util.lrucachedict(self.cachesize)
1332 self._dirmancache[dir] = mancache
1335 self._dirmancache[dir] = mancache
1333 mancache[node] = m
1336 mancache[node] = m
1334 return m
1337 return m
1335
1338
1336 def clearcaches(self):
1339 def clearcaches(self):
1337 self._dirmancache.clear()
1340 self._dirmancache.clear()
1338 self._revlog.clearcaches()
1341 self._revlog.clearcaches()
1339
1342
1340 class memmanifestctx(object):
1343 class memmanifestctx(object):
1341 def __init__(self, manifestlog):
1344 def __init__(self, manifestlog):
1342 self._manifestlog = manifestlog
1345 self._manifestlog = manifestlog
1343 self._manifestdict = manifestdict()
1346 self._manifestdict = manifestdict()
1344
1347
1345 def _revlog(self):
1348 def _revlog(self):
1346 return self._manifestlog._revlog
1349 return self._manifestlog._revlog
1347
1350
1348 def new(self):
1351 def new(self):
1349 return memmanifestctx(self._manifestlog)
1352 return memmanifestctx(self._manifestlog)
1350
1353
1351 def copy(self):
1354 def copy(self):
1352 memmf = memmanifestctx(self._manifestlog)
1355 memmf = memmanifestctx(self._manifestlog)
1353 memmf._manifestdict = self.read().copy()
1356 memmf._manifestdict = self.read().copy()
1354 return memmf
1357 return memmf
1355
1358
1356 def read(self):
1359 def read(self):
1357 return self._manifestdict
1360 return self._manifestdict
1358
1361
1359 def write(self, transaction, link, p1, p2, added, removed):
1362 def write(self, transaction, link, p1, p2, added, removed):
1360 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1363 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1361 added, removed)
1364 added, removed)
1362
1365
1363 class manifestctx(object):
1366 class manifestctx(object):
1364 """A class representing a single revision of a manifest, including its
1367 """A class representing a single revision of a manifest, including its
1365 contents, its parent revs, and its linkrev.
1368 contents, its parent revs, and its linkrev.
1366 """
1369 """
1367 def __init__(self, manifestlog, node):
1370 def __init__(self, manifestlog, node):
1368 self._manifestlog = manifestlog
1371 self._manifestlog = manifestlog
1369 self._data = None
1372 self._data = None
1370
1373
1371 self._node = node
1374 self._node = node
1372
1375
1373 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1376 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1374 # but let's add it later when something needs it and we can load it
1377 # but let's add it later when something needs it and we can load it
1375 # lazily.
1378 # lazily.
1376 #self.p1, self.p2 = revlog.parents(node)
1379 #self.p1, self.p2 = revlog.parents(node)
1377 #rev = revlog.rev(node)
1380 #rev = revlog.rev(node)
1378 #self.linkrev = revlog.linkrev(rev)
1381 #self.linkrev = revlog.linkrev(rev)
1379
1382
1380 def _revlog(self):
1383 def _revlog(self):
1381 return self._manifestlog._revlog
1384 return self._manifestlog._revlog
1382
1385
1383 def node(self):
1386 def node(self):
1384 return self._node
1387 return self._node
1385
1388
1386 def new(self):
1389 def new(self):
1387 return memmanifestctx(self._manifestlog)
1390 return memmanifestctx(self._manifestlog)
1388
1391
1389 def copy(self):
1392 def copy(self):
1390 memmf = memmanifestctx(self._manifestlog)
1393 memmf = memmanifestctx(self._manifestlog)
1391 memmf._manifestdict = self.read().copy()
1394 memmf._manifestdict = self.read().copy()
1392 return memmf
1395 return memmf
1393
1396
1394 @propertycache
1397 @propertycache
1395 def parents(self):
1398 def parents(self):
1396 return self._revlog().parents(self._node)
1399 return self._revlog().parents(self._node)
1397
1400
1398 def read(self):
1401 def read(self):
1399 if self._data is None:
1402 if self._data is None:
1400 if self._node == revlog.nullid:
1403 if self._node == revlog.nullid:
1401 self._data = manifestdict()
1404 self._data = manifestdict()
1402 else:
1405 else:
1403 rl = self._revlog()
1406 rl = self._revlog()
1404 text = rl.revision(self._node)
1407 text = rl.revision(self._node)
1405 arraytext = bytearray(text)
1408 arraytext = bytearray(text)
1406 rl._fulltextcache[self._node] = arraytext
1409 rl._fulltextcache[self._node] = arraytext
1407 self._data = manifestdict(text)
1410 self._data = manifestdict(text)
1408 return self._data
1411 return self._data
1409
1412
1410 def readfast(self, shallow=False):
1413 def readfast(self, shallow=False):
1411 '''Calls either readdelta or read, based on which would be less work.
1414 '''Calls either readdelta or read, based on which would be less work.
1412 readdelta is called if the delta is against the p1, and therefore can be
1415 readdelta is called if the delta is against the p1, and therefore can be
1413 read quickly.
1416 read quickly.
1414
1417
1415 If `shallow` is True, nothing changes since this is a flat manifest.
1418 If `shallow` is True, nothing changes since this is a flat manifest.
1416 '''
1419 '''
1417 rl = self._revlog()
1420 rl = self._revlog()
1418 r = rl.rev(self._node)
1421 r = rl.rev(self._node)
1419 deltaparent = rl.deltaparent(r)
1422 deltaparent = rl.deltaparent(r)
1420 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1423 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1421 return self.readdelta()
1424 return self.readdelta()
1422 return self.read()
1425 return self.read()
1423
1426
1424 def readdelta(self, shallow=False):
1427 def readdelta(self, shallow=False):
1425 '''Returns a manifest containing just the entries that are present
1428 '''Returns a manifest containing just the entries that are present
1426 in this manifest, but not in its p1 manifest. This is efficient to read
1429 in this manifest, but not in its p1 manifest. This is efficient to read
1427 if the revlog delta is already p1.
1430 if the revlog delta is already p1.
1428
1431
1429 Changing the value of `shallow` has no effect on flat manifests.
1432 Changing the value of `shallow` has no effect on flat manifests.
1430 '''
1433 '''
1431 revlog = self._revlog()
1434 revlog = self._revlog()
1432 r = revlog.rev(self._node)
1435 r = revlog.rev(self._node)
1433 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1436 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1434 return manifestdict(d)
1437 return manifestdict(d)
1435
1438
1436 def find(self, key):
1439 def find(self, key):
1437 return self.read().find(key)
1440 return self.read().find(key)
1438
1441
1439 class memtreemanifestctx(object):
1442 class memtreemanifestctx(object):
1440 def __init__(self, manifestlog, dir=''):
1443 def __init__(self, manifestlog, dir=''):
1441 self._manifestlog = manifestlog
1444 self._manifestlog = manifestlog
1442 self._dir = dir
1445 self._dir = dir
1443 self._treemanifest = treemanifest()
1446 self._treemanifest = treemanifest()
1444
1447
1445 def _revlog(self):
1448 def _revlog(self):
1446 return self._manifestlog._revlog
1449 return self._manifestlog._revlog
1447
1450
1448 def new(self, dir=''):
1451 def new(self, dir=''):
1449 return memtreemanifestctx(self._manifestlog, dir=dir)
1452 return memtreemanifestctx(self._manifestlog, dir=dir)
1450
1453
1451 def copy(self):
1454 def copy(self):
1452 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1455 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1453 memmf._treemanifest = self._treemanifest.copy()
1456 memmf._treemanifest = self._treemanifest.copy()
1454 return memmf
1457 return memmf
1455
1458
1456 def read(self):
1459 def read(self):
1457 return self._treemanifest
1460 return self._treemanifest
1458
1461
1459 def write(self, transaction, link, p1, p2, added, removed):
1462 def write(self, transaction, link, p1, p2, added, removed):
1460 def readtree(dir, node):
1463 def readtree(dir, node):
1461 return self._manifestlog.get(dir, node).read()
1464 return self._manifestlog.get(dir, node).read()
1462 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1465 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1463 added, removed, readtree=readtree)
1466 added, removed, readtree=readtree)
1464
1467
1465 class treemanifestctx(object):
1468 class treemanifestctx(object):
1466 def __init__(self, manifestlog, dir, node):
1469 def __init__(self, manifestlog, dir, node):
1467 self._manifestlog = manifestlog
1470 self._manifestlog = manifestlog
1468 self._dir = dir
1471 self._dir = dir
1469 self._data = None
1472 self._data = None
1470
1473
1471 self._node = node
1474 self._node = node
1472
1475
1473 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1476 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1474 # we can instantiate treemanifestctx objects for directories we don't
1477 # we can instantiate treemanifestctx objects for directories we don't
1475 # have on disk.
1478 # have on disk.
1476 #self.p1, self.p2 = revlog.parents(node)
1479 #self.p1, self.p2 = revlog.parents(node)
1477 #rev = revlog.rev(node)
1480 #rev = revlog.rev(node)
1478 #self.linkrev = revlog.linkrev(rev)
1481 #self.linkrev = revlog.linkrev(rev)
1479
1482
1480 def _revlog(self):
1483 def _revlog(self):
1481 narrowmatch = self._manifestlog._narrowmatch
1484 narrowmatch = self._manifestlog._narrowmatch
1482 if not narrowmatch.always():
1485 if not narrowmatch.always():
1483 if not narrowmatch.visitdir(self._dir[:-1] or '.'):
1486 if not narrowmatch.visitdir(self._dir[:-1] or '.'):
1484 return excludedmanifestrevlog(self._dir)
1487 return excludedmanifestrevlog(self._dir)
1485 return self._manifestlog._revlog.dirlog(self._dir)
1488 return self._manifestlog._revlog.dirlog(self._dir)
1486
1489
1487 def read(self):
1490 def read(self):
1488 if self._data is None:
1491 if self._data is None:
1489 rl = self._revlog()
1492 rl = self._revlog()
1490 if self._node == revlog.nullid:
1493 if self._node == revlog.nullid:
1491 self._data = treemanifest()
1494 self._data = treemanifest()
1492 elif rl._treeondisk:
1495 elif rl._treeondisk:
1493 m = treemanifest(dir=self._dir)
1496 m = treemanifest(dir=self._dir)
1494 def gettext():
1497 def gettext():
1495 return rl.revision(self._node)
1498 return rl.revision(self._node)
1496 def readsubtree(dir, subm):
1499 def readsubtree(dir, subm):
1497 # Set verify to False since we need to be able to create
1500 # Set verify to False since we need to be able to create
1498 # subtrees for trees that don't exist on disk.
1501 # subtrees for trees that don't exist on disk.
1499 return self._manifestlog.get(dir, subm, verify=False).read()
1502 return self._manifestlog.get(dir, subm, verify=False).read()
1500 m.read(gettext, readsubtree)
1503 m.read(gettext, readsubtree)
1501 m.setnode(self._node)
1504 m.setnode(self._node)
1502 self._data = m
1505 self._data = m
1503 else:
1506 else:
1504 text = rl.revision(self._node)
1507 text = rl.revision(self._node)
1505 arraytext = bytearray(text)
1508 arraytext = bytearray(text)
1506 rl.fulltextcache[self._node] = arraytext
1509 rl.fulltextcache[self._node] = arraytext
1507 self._data = treemanifest(dir=self._dir, text=text)
1510 self._data = treemanifest(dir=self._dir, text=text)
1508
1511
1509 return self._data
1512 return self._data
1510
1513
1511 def node(self):
1514 def node(self):
1512 return self._node
1515 return self._node
1513
1516
1514 def new(self, dir=''):
1517 def new(self, dir=''):
1515 return memtreemanifestctx(self._manifestlog, dir=dir)
1518 return memtreemanifestctx(self._manifestlog, dir=dir)
1516
1519
1517 def copy(self):
1520 def copy(self):
1518 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1521 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1519 memmf._treemanifest = self.read().copy()
1522 memmf._treemanifest = self.read().copy()
1520 return memmf
1523 return memmf
1521
1524
1522 @propertycache
1525 @propertycache
1523 def parents(self):
1526 def parents(self):
1524 return self._revlog().parents(self._node)
1527 return self._revlog().parents(self._node)
1525
1528
1526 def readdelta(self, shallow=False):
1529 def readdelta(self, shallow=False):
1527 '''Returns a manifest containing just the entries that are present
1530 '''Returns a manifest containing just the entries that are present
1528 in this manifest, but not in its p1 manifest. This is efficient to read
1531 in this manifest, but not in its p1 manifest. This is efficient to read
1529 if the revlog delta is already p1.
1532 if the revlog delta is already p1.
1530
1533
1531 If `shallow` is True, this will read the delta for this directory,
1534 If `shallow` is True, this will read the delta for this directory,
1532 without recursively reading subdirectory manifests. Instead, any
1535 without recursively reading subdirectory manifests. Instead, any
1533 subdirectory entry will be reported as it appears in the manifest, i.e.
1536 subdirectory entry will be reported as it appears in the manifest, i.e.
1534 the subdirectory will be reported among files and distinguished only by
1537 the subdirectory will be reported among files and distinguished only by
1535 its 't' flag.
1538 its 't' flag.
1536 '''
1539 '''
1537 revlog = self._revlog()
1540 revlog = self._revlog()
1538 if shallow:
1541 if shallow:
1539 r = revlog.rev(self._node)
1542 r = revlog.rev(self._node)
1540 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1543 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1541 return manifestdict(d)
1544 return manifestdict(d)
1542 else:
1545 else:
1543 # Need to perform a slow delta
1546 # Need to perform a slow delta
1544 r0 = revlog.deltaparent(revlog.rev(self._node))
1547 r0 = revlog.deltaparent(revlog.rev(self._node))
1545 m0 = self._manifestlog.get(self._dir, revlog.node(r0)).read()
1548 m0 = self._manifestlog.get(self._dir, revlog.node(r0)).read()
1546 m1 = self.read()
1549 m1 = self.read()
1547 md = treemanifest(dir=self._dir)
1550 md = treemanifest(dir=self._dir)
1548 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1551 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1549 if n1:
1552 if n1:
1550 md[f] = n1
1553 md[f] = n1
1551 if fl1:
1554 if fl1:
1552 md.setflag(f, fl1)
1555 md.setflag(f, fl1)
1553 return md
1556 return md
1554
1557
1555 def readfast(self, shallow=False):
1558 def readfast(self, shallow=False):
1556 '''Calls either readdelta or read, based on which would be less work.
1559 '''Calls either readdelta or read, based on which would be less work.
1557 readdelta is called if the delta is against the p1, and therefore can be
1560 readdelta is called if the delta is against the p1, and therefore can be
1558 read quickly.
1561 read quickly.
1559
1562
1560 If `shallow` is True, it only returns the entries from this manifest,
1563 If `shallow` is True, it only returns the entries from this manifest,
1561 and not any submanifests.
1564 and not any submanifests.
1562 '''
1565 '''
1563 rl = self._revlog()
1566 rl = self._revlog()
1564 r = rl.rev(self._node)
1567 r = rl.rev(self._node)
1565 deltaparent = rl.deltaparent(r)
1568 deltaparent = rl.deltaparent(r)
1566 if (deltaparent != revlog.nullrev and
1569 if (deltaparent != revlog.nullrev and
1567 deltaparent in rl.parentrevs(r)):
1570 deltaparent in rl.parentrevs(r)):
1568 return self.readdelta(shallow=shallow)
1571 return self.readdelta(shallow=shallow)
1569
1572
1570 if shallow:
1573 if shallow:
1571 return manifestdict(rl.revision(self._node))
1574 return manifestdict(rl.revision(self._node))
1572 else:
1575 else:
1573 return self.read()
1576 return self.read()
1574
1577
1575 def find(self, key):
1578 def find(self, key):
1576 return self.read().find(key)
1579 return self.read().find(key)
1577
1580
1578 class excludeddir(treemanifest):
1581 class excludeddir(treemanifest):
1579 """Stand-in for a directory that is excluded from the repository.
1582 """Stand-in for a directory that is excluded from the repository.
1580
1583
1581 With narrowing active on a repository that uses treemanifests,
1584 With narrowing active on a repository that uses treemanifests,
1582 some of the directory revlogs will be excluded from the resulting
1585 some of the directory revlogs will be excluded from the resulting
1583 clone. This is a huge storage win for clients, but means we need
1586 clone. This is a huge storage win for clients, but means we need
1584 some sort of pseudo-manifest to surface to internals so we can
1587 some sort of pseudo-manifest to surface to internals so we can
1585 detect a merge conflict outside the narrowspec. That's what this
1588 detect a merge conflict outside the narrowspec. That's what this
1586 class is: it stands in for a directory whose node is known, but
1589 class is: it stands in for a directory whose node is known, but
1587 whose contents are unknown.
1590 whose contents are unknown.
1588 """
1591 """
1589 def __init__(self, dir, node):
1592 def __init__(self, dir, node):
1590 super(excludeddir, self).__init__(dir)
1593 super(excludeddir, self).__init__(dir)
1591 self._node = node
1594 self._node = node
1592 # Add an empty file, which will be included by iterators and such,
1595 # Add an empty file, which will be included by iterators and such,
1593 # appearing as the directory itself (i.e. something like "dir/")
1596 # appearing as the directory itself (i.e. something like "dir/")
1594 self._files[''] = node
1597 self._files[''] = node
1595 self._flags[''] = 't'
1598 self._flags[''] = 't'
1596
1599
1597 # Manifests outside the narrowspec should never be modified, so avoid
1600 # Manifests outside the narrowspec should never be modified, so avoid
1598 # copying. This makes a noticeable difference when there are very many
1601 # copying. This makes a noticeable difference when there are very many
1599 # directories outside the narrowspec. Also, it makes sense for the copy to
1602 # directories outside the narrowspec. Also, it makes sense for the copy to
1600 # be of the same type as the original, which would not happen with the
1603 # be of the same type as the original, which would not happen with the
1601 # super type's copy().
1604 # super type's copy().
1602 def copy(self):
1605 def copy(self):
1603 return self
1606 return self
1604
1607
1605 class excludeddirmanifestctx(treemanifestctx):
1608 class excludeddirmanifestctx(treemanifestctx):
1606 """context wrapper for excludeddir - see that docstring for rationale"""
1609 """context wrapper for excludeddir - see that docstring for rationale"""
1607 def __init__(self, dir, node):
1610 def __init__(self, dir, node):
1608 self._dir = dir
1611 self._dir = dir
1609 self._node = node
1612 self._node = node
1610
1613
1611 def read(self):
1614 def read(self):
1612 return excludeddir(self._dir, self._node)
1615 return excludeddir(self._dir, self._node)
1613
1616
1614 def write(self, *args):
1617 def write(self, *args):
1615 raise error.ProgrammingError(
1618 raise error.ProgrammingError(
1616 'attempt to write manifest from excluded dir %s' % self._dir)
1619 'attempt to write manifest from excluded dir %s' % self._dir)
1617
1620
1618 class excludedmanifestrevlog(manifestrevlog):
1621 class excludedmanifestrevlog(manifestrevlog):
1619 """Stand-in for excluded treemanifest revlogs.
1622 """Stand-in for excluded treemanifest revlogs.
1620
1623
1621 When narrowing is active on a treemanifest repository, we'll have
1624 When narrowing is active on a treemanifest repository, we'll have
1622 references to directories we can't see due to the revlog being
1625 references to directories we can't see due to the revlog being
1623 skipped. This class exists to conform to the manifestrevlog
1626 skipped. This class exists to conform to the manifestrevlog
1624 interface for those directories and proactively prevent writes to
1627 interface for those directories and proactively prevent writes to
1625 outside the narrowspec.
1628 outside the narrowspec.
1626 """
1629 """
1627
1630
1628 def __init__(self, dir):
1631 def __init__(self, dir):
1629 self._dir = dir
1632 self._dir = dir
1630
1633
1631 def __len__(self):
1634 def __len__(self):
1632 raise error.ProgrammingError(
1635 raise error.ProgrammingError(
1633 'attempt to get length of excluded dir %s' % self._dir)
1636 'attempt to get length of excluded dir %s' % self._dir)
1634
1637
1635 def rev(self, node):
1638 def rev(self, node):
1636 raise error.ProgrammingError(
1639 raise error.ProgrammingError(
1637 'attempt to get rev from excluded dir %s' % self._dir)
1640 'attempt to get rev from excluded dir %s' % self._dir)
1638
1641
1639 def linkrev(self, node):
1642 def linkrev(self, node):
1640 raise error.ProgrammingError(
1643 raise error.ProgrammingError(
1641 'attempt to get linkrev from excluded dir %s' % self._dir)
1644 'attempt to get linkrev from excluded dir %s' % self._dir)
1642
1645
1643 def node(self, rev):
1646 def node(self, rev):
1644 raise error.ProgrammingError(
1647 raise error.ProgrammingError(
1645 'attempt to get node from excluded dir %s' % self._dir)
1648 'attempt to get node from excluded dir %s' % self._dir)
1646
1649
1647 def add(self, *args, **kwargs):
1650 def add(self, *args, **kwargs):
1648 # We should never write entries in dirlogs outside the narrow clone.
1651 # We should never write entries in dirlogs outside the narrow clone.
1649 # However, the method still gets called from writesubtree() in
1652 # However, the method still gets called from writesubtree() in
1650 # _addtree(), so we need to handle it. We should possibly make that
1653 # _addtree(), so we need to handle it. We should possibly make that
1651 # avoid calling add() with a clean manifest (_dirty is always False
1654 # avoid calling add() with a clean manifest (_dirty is always False
1652 # in excludeddir instances).
1655 # in excludeddir instances).
1653 pass
1656 pass
General Comments 0
You need to be logged in to leave comments. Login now