##// END OF EJS Templates
manifest: rearrange add() method and add comments for clarity...
Augie Fackler -
r22788:160efd22 default
parent child Browse files
Show More
@@ -1,231 +1,237
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 i18n import _
8 from i18n import _
9 import mdiff, parsers, error, revlog, util, dicthelpers
9 import mdiff, parsers, error, revlog, util, dicthelpers
10 import array, struct
10 import array, struct
11
11
12 class manifestdict(dict):
12 class manifestdict(dict):
13 def __init__(self, mapping=None, flags=None):
13 def __init__(self, mapping=None, flags=None):
14 if mapping is None:
14 if mapping is None:
15 mapping = {}
15 mapping = {}
16 if flags is None:
16 if flags is None:
17 flags = {}
17 flags = {}
18 dict.__init__(self, mapping)
18 dict.__init__(self, mapping)
19 self._flags = flags
19 self._flags = flags
20 def flags(self, f):
20 def flags(self, f):
21 return self._flags.get(f, "")
21 return self._flags.get(f, "")
22 def withflags(self):
22 def withflags(self):
23 return set(self._flags.keys())
23 return set(self._flags.keys())
24 def set(self, f, flags):
24 def set(self, f, flags):
25 self._flags[f] = flags
25 self._flags[f] = flags
26 def copy(self):
26 def copy(self):
27 return manifestdict(self, dict.copy(self._flags))
27 return manifestdict(self, dict.copy(self._flags))
28 def intersectfiles(self, files):
28 def intersectfiles(self, files):
29 '''make a new manifestdict with the intersection of self with files
29 '''make a new manifestdict with the intersection of self with files
30
30
31 The algorithm assumes that files is much smaller than self.'''
31 The algorithm assumes that files is much smaller than self.'''
32 ret = manifestdict()
32 ret = manifestdict()
33 for fn in files:
33 for fn in files:
34 if fn in self:
34 if fn in self:
35 ret[fn] = self[fn]
35 ret[fn] = self[fn]
36 flags = self._flags.get(fn, None)
36 flags = self._flags.get(fn, None)
37 if flags:
37 if flags:
38 ret._flags[fn] = flags
38 ret._flags[fn] = flags
39 return ret
39 return ret
40 def flagsdiff(self, d2):
40 def flagsdiff(self, d2):
41 return dicthelpers.diff(self._flags, d2._flags, "")
41 return dicthelpers.diff(self._flags, d2._flags, "")
42
42
43
43
44 def _checkforbidden(l):
44 def _checkforbidden(l):
45 """Check filenames for illegal characters."""
45 """Check filenames for illegal characters."""
46 for f in l:
46 for f in l:
47 if '\n' in f or '\r' in f:
47 if '\n' in f or '\r' in f:
48 raise error.RevlogError(
48 raise error.RevlogError(
49 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
49 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
50
50
51
51
52 # apply the changes collected during the bisect loop to our addlist
52 # apply the changes collected during the bisect loop to our addlist
53 # return a delta suitable for addrevision
53 # return a delta suitable for addrevision
54 def _addlistdelta(addlist, x):
54 def _addlistdelta(addlist, x):
55 # for large addlist arrays, building a new array is cheaper
55 # for large addlist arrays, building a new array is cheaper
56 # than repeatedly modifying the existing one
56 # than repeatedly modifying the existing one
57 currentposition = 0
57 currentposition = 0
58 newaddlist = array.array('c')
58 newaddlist = array.array('c')
59
59
60 for start, end, content in x:
60 for start, end, content in x:
61 newaddlist += addlist[currentposition:start]
61 newaddlist += addlist[currentposition:start]
62 if content:
62 if content:
63 newaddlist += array.array('c', content)
63 newaddlist += array.array('c', content)
64
64
65 currentposition = end
65 currentposition = end
66
66
67 newaddlist += addlist[currentposition:]
67 newaddlist += addlist[currentposition:]
68
68
69 deltatext = "".join(struct.pack(">lll", start, end, len(content))
69 deltatext = "".join(struct.pack(">lll", start, end, len(content))
70 + content for start, end, content in x)
70 + content for start, end, content in x)
71 return deltatext, newaddlist
71 return deltatext, newaddlist
72
72
73 def _parse(lines):
73 def _parse(lines):
74 mfdict = manifestdict()
74 mfdict = manifestdict()
75 parsers.parse_manifest(mfdict, mfdict._flags, lines)
75 parsers.parse_manifest(mfdict, mfdict._flags, lines)
76 return mfdict
76 return mfdict
77
77
78 class manifest(revlog.revlog):
78 class manifest(revlog.revlog):
79 def __init__(self, opener):
79 def __init__(self, opener):
80 # we expect to deal with not more than four revs at a time,
80 # we expect to deal with not more than four revs at a time,
81 # during a commit --amend
81 # during a commit --amend
82 self._mancache = util.lrucachedict(4)
82 self._mancache = util.lrucachedict(4)
83 revlog.revlog.__init__(self, opener, "00manifest.i")
83 revlog.revlog.__init__(self, opener, "00manifest.i")
84
84
85 def readdelta(self, node):
85 def readdelta(self, node):
86 r = self.rev(node)
86 r = self.rev(node)
87 return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
87 return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
88
88
89 def readfast(self, node):
89 def readfast(self, node):
90 '''use the faster of readdelta or read'''
90 '''use the faster of readdelta or read'''
91 r = self.rev(node)
91 r = self.rev(node)
92 deltaparent = self.deltaparent(r)
92 deltaparent = self.deltaparent(r)
93 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
93 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
94 return self.readdelta(node)
94 return self.readdelta(node)
95 return self.read(node)
95 return self.read(node)
96
96
97 def read(self, node):
97 def read(self, node):
98 if node == revlog.nullid:
98 if node == revlog.nullid:
99 return manifestdict() # don't upset local cache
99 return manifestdict() # don't upset local cache
100 if node in self._mancache:
100 if node in self._mancache:
101 return self._mancache[node][0]
101 return self._mancache[node][0]
102 text = self.revision(node)
102 text = self.revision(node)
103 arraytext = array.array('c', text)
103 arraytext = array.array('c', text)
104 mapping = _parse(text)
104 mapping = _parse(text)
105 self._mancache[node] = (mapping, arraytext)
105 self._mancache[node] = (mapping, arraytext)
106 return mapping
106 return mapping
107
107
108 def _search(self, m, s, lo=0, hi=None):
108 def _search(self, m, s, lo=0, hi=None):
109 '''return a tuple (start, end) that says where to find s within m.
109 '''return a tuple (start, end) that says where to find s within m.
110
110
111 If the string is found m[start:end] are the line containing
111 If the string is found m[start:end] are the line containing
112 that string. If start == end the string was not found and
112 that string. If start == end the string was not found and
113 they indicate the proper sorted insertion point.
113 they indicate the proper sorted insertion point.
114
114
115 m should be a buffer or a string
115 m should be a buffer or a string
116 s is a string'''
116 s is a string'''
117 def advance(i, c):
117 def advance(i, c):
118 while i < lenm and m[i] != c:
118 while i < lenm and m[i] != c:
119 i += 1
119 i += 1
120 return i
120 return i
121 if not s:
121 if not s:
122 return (lo, lo)
122 return (lo, lo)
123 lenm = len(m)
123 lenm = len(m)
124 if not hi:
124 if not hi:
125 hi = lenm
125 hi = lenm
126 while lo < hi:
126 while lo < hi:
127 mid = (lo + hi) // 2
127 mid = (lo + hi) // 2
128 start = mid
128 start = mid
129 while start > 0 and m[start - 1] != '\n':
129 while start > 0 and m[start - 1] != '\n':
130 start -= 1
130 start -= 1
131 end = advance(start, '\0')
131 end = advance(start, '\0')
132 if m[start:end] < s:
132 if m[start:end] < s:
133 # we know that after the null there are 40 bytes of sha1
133 # we know that after the null there are 40 bytes of sha1
134 # this translates to the bisect lo = mid + 1
134 # this translates to the bisect lo = mid + 1
135 lo = advance(end + 40, '\n') + 1
135 lo = advance(end + 40, '\n') + 1
136 else:
136 else:
137 # this translates to the bisect hi = mid
137 # this translates to the bisect hi = mid
138 hi = start
138 hi = start
139 end = advance(lo, '\0')
139 end = advance(lo, '\0')
140 found = m[lo:end]
140 found = m[lo:end]
141 if s == found:
141 if s == found:
142 # we know that after the null there are 40 bytes of sha1
142 # we know that after the null there are 40 bytes of sha1
143 end = advance(end + 40, '\n')
143 end = advance(end + 40, '\n')
144 return (lo, end + 1)
144 return (lo, end + 1)
145 else:
145 else:
146 return (lo, lo)
146 return (lo, lo)
147
147
148 def find(self, node, f):
148 def find(self, node, f):
149 '''look up entry for a single file efficiently.
149 '''look up entry for a single file efficiently.
150 return (node, flags) pair if found, (None, None) if not.'''
150 return (node, flags) pair if found, (None, None) if not.'''
151 if node in self._mancache:
151 if node in self._mancache:
152 mapping = self._mancache[node][0]
152 mapping = self._mancache[node][0]
153 return mapping.get(f), mapping.flags(f)
153 return mapping.get(f), mapping.flags(f)
154 text = self.revision(node)
154 text = self.revision(node)
155 start, end = self._search(text, f)
155 start, end = self._search(text, f)
156 if start == end:
156 if start == end:
157 return None, None
157 return None, None
158 l = text[start:end]
158 l = text[start:end]
159 f, n = l.split('\0')
159 f, n = l.split('\0')
160 return revlog.bin(n[:40]), n[40:-1]
160 return revlog.bin(n[:40]), n[40:-1]
161
161
162 def add(self, map, transaction, link, p1, p2, added, removed):
162 def add(self, map, transaction, link, p1, p2, added, removed):
163 # if we're using the cache, make sure it is valid and
163 if p1 in self._mancache:
164 # parented by the same node we're diffing against
164 # If our first parent is in the manifest cache, we can
165 if not (p1 and (p1 in self._mancache)):
165 # compute a delta here using properties we know about the
166 files = sorted(map)
166 # manifest up-front, which may save time later for the
167 _checkforbidden(files)
167 # revlog layer.
168
169 # if this is changed to support newlines in filenames,
170 # be sure to check the templates/ dir again (especially *-raw.tmpl)
171 hex, flags = revlog.hex, map.flags
172 text = ''.join("%s\0%s%s\n" % (f, hex(map[f]), flags(f))
173 for f in files)
174 arraytext = array.array('c', text)
175 cachedelta = None
176 else:
177 addlist = self._mancache[p1][1]
168 addlist = self._mancache[p1][1]
178
169
179 _checkforbidden(added)
170 _checkforbidden(added)
180 # combine the changed lists into one list for sorting
171 # combine the changed lists into one list for sorting
181 work = [(x, False) for x in added]
172 work = [(x, False) for x in added]
182 work.extend((x, True) for x in removed)
173 work.extend((x, True) for x in removed)
183 # this could use heapq.merge() (from Python 2.6+) or equivalent
174 # this could use heapq.merge() (from Python 2.6+) or equivalent
184 # since the lists are already sorted
175 # since the lists are already sorted
185 work.sort()
176 work.sort()
186
177
187 delta = []
178 delta = []
188 dstart = None
179 dstart = None
189 dend = None
180 dend = None
190 dline = [""]
181 dline = [""]
191 start = 0
182 start = 0
192 # zero copy representation of addlist as a buffer
183 # zero copy representation of addlist as a buffer
193 addbuf = util.buffer(addlist)
184 addbuf = util.buffer(addlist)
194
185
195 # start with a readonly loop that finds the offset of
186 # start with a readonly loop that finds the offset of
196 # each line and creates the deltas
187 # each line and creates the deltas
197 for f, todelete in work:
188 for f, todelete in work:
198 # bs will either be the index of the item or the insert point
189 # bs will either be the index of the item or the insert point
199 start, end = self._search(addbuf, f, start)
190 start, end = self._search(addbuf, f, start)
200 if not todelete:
191 if not todelete:
201 l = "%s\0%s%s\n" % (f, revlog.hex(map[f]), map.flags(f))
192 l = "%s\0%s%s\n" % (f, revlog.hex(map[f]), map.flags(f))
202 else:
193 else:
203 if start == end:
194 if start == end:
204 # item we want to delete was not found, error out
195 # item we want to delete was not found, error out
205 raise AssertionError(
196 raise AssertionError(
206 _("failed to remove %s from manifest") % f)
197 _("failed to remove %s from manifest") % f)
207 l = ""
198 l = ""
208 if dstart is not None and dstart <= start and dend >= start:
199 if dstart is not None and dstart <= start and dend >= start:
209 if dend < end:
200 if dend < end:
210 dend = end
201 dend = end
211 if l:
202 if l:
212 dline.append(l)
203 dline.append(l)
213 else:
204 else:
214 if dstart is not None:
205 if dstart is not None:
215 delta.append([dstart, dend, "".join(dline)])
206 delta.append([dstart, dend, "".join(dline)])
216 dstart = start
207 dstart = start
217 dend = end
208 dend = end
218 dline = [l]
209 dline = [l]
219
210
220 if dstart is not None:
211 if dstart is not None:
221 delta.append([dstart, dend, "".join(dline)])
212 delta.append([dstart, dend, "".join(dline)])
222 # apply the delta to the addlist, and get a delta for addrevision
213 # apply the delta to the addlist, and get a delta for addrevision
223 deltatext, addlist = _addlistdelta(addlist, delta)
214 deltatext, addlist = _addlistdelta(addlist, delta)
224 cachedelta = (self.rev(p1), deltatext)
215 cachedelta = (self.rev(p1), deltatext)
225 arraytext = addlist
216 arraytext = addlist
226 text = util.buffer(arraytext)
217 text = util.buffer(arraytext)
218 else:
219 # The first parent manifest isn't already loaded, so we'll
220 # just encode a fulltext of the manifest and pass that
221 # through to the revlog layer, and let it handle the delta
222 # process.
223 files = sorted(map)
224 _checkforbidden(files)
225
226 # if this is changed to support newlines in filenames,
227 # be sure to check the templates/ dir again (especially *-raw.tmpl)
228 hex, flags = revlog.hex, map.flags
229 text = ''.join("%s\0%s%s\n" % (f, hex(map[f]), flags(f))
230 for f in files)
231 arraytext = array.array('c', text)
232 cachedelta = None
227
233
228 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
234 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
229 self._mancache[n] = (map, arraytext)
235 self._mancache[n] = (map, arraytext)
230
236
231 return n
237 return n
General Comments 0
You need to be logged in to leave comments. Login now