##// END OF EJS Templates
manifest: add optional recording of clean entries to diff...
Augie Fackler -
r23756:829f640b default
parent child Browse files
Show More
@@ -1,287 +1,297
1 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import mdiff, parsers, error, revlog, util
10 10 import array, struct
11 11
12 12 class manifestdict(dict):
13 13 def __init__(self, mapping=None, flags=None):
14 14 if mapping is None:
15 15 mapping = {}
16 16 if flags is None:
17 17 flags = {}
18 18 dict.__init__(self, mapping)
19 19 self._flags = flags
20 20 def __setitem__(self, k, v):
21 21 assert v is not None
22 22 dict.__setitem__(self, k, v)
23 23 def flags(self, f):
24 24 return self._flags.get(f, "")
25 25 def withflags(self):
26 26 return set(self._flags.keys())
27 27 def setflag(self, f, flags):
28 28 """Set the flags (symlink, executable) for path f."""
29 29 self._flags[f] = flags
30 30 def copy(self):
31 31 return manifestdict(self, dict.copy(self._flags))
32 32 def intersectfiles(self, files):
33 33 '''make a new manifestdict with the intersection of self with files
34 34
35 35 The algorithm assumes that files is much smaller than self.'''
36 36 ret = manifestdict()
37 37 for fn in files:
38 38 if fn in self:
39 39 ret[fn] = self[fn]
40 40 flags = self._flags.get(fn, None)
41 41 if flags:
42 42 ret._flags[fn] = flags
43 43 return ret
44 44
45 45 def matches(self, match):
46 46 '''generate a new manifest filtered by the match argument'''
47 47 if match.always():
48 48 return self.copy()
49 49
50 50 files = match.files()
51 51 if (match.matchfn == match.exact or
52 52 (not match.anypats() and util.all(fn in self for fn in files))):
53 53 return self.intersectfiles(files)
54 54
55 55 mf = self.copy()
56 56 for fn in mf.keys():
57 57 if not match(fn):
58 58 del mf[fn]
59 59 return mf
60 60
61 def diff(self, m2):
62 '''Finds changes between the current manifest and m2. The result is
63 returned as a dict with filename as key and values of the form
64 ((n1,fl1),(n2,fl2)), where n1/n2 is the nodeid in the current/other
65 manifest and fl1/fl2 is the flag in the current/other manifest. Where
66 the file does not exist, the nodeid will be None and the flags will be
67 the empty string.'''
61 def diff(self, m2, clean=False):
62 '''Finds changes between the current manifest and m2.
63
64 Args:
65 m2: the manifest to which this manifest should be compared.
66 clean: if true, include files unchanged between these manifests
67 with a None value in the returned dictionary.
68
69 The result is returned as a dict with filename as key and
70 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
71 nodeid in the current/other manifest and fl1/fl2 is the flag
72 in the current/other manifest. Where the file does not exist,
73 the nodeid will be None and the flags will be the empty
74 string.
75 '''
68 76 diff = {}
69 77
70 78 for fn, n1 in self.iteritems():
71 79 fl1 = self._flags.get(fn, '')
72 80 n2 = m2.get(fn, None)
73 81 fl2 = m2._flags.get(fn, '')
74 82 if n2 is None:
75 83 fl2 = ''
76 84 if n1 != n2 or fl1 != fl2:
77 85 diff[fn] = ((n1, fl1), (n2, fl2))
86 elif clean:
87 diff[fn] = None
78 88
79 89 for fn, n2 in m2.iteritems():
80 90 if fn not in self:
81 91 fl2 = m2._flags.get(fn, '')
82 92 diff[fn] = ((None, ''), (n2, fl2))
83 93
84 94 return diff
85 95
86 96 def text(self):
87 97 """Get the full data of this manifest as a bytestring."""
88 98 fl = sorted(self)
89 99 _checkforbidden(fl)
90 100
91 101 hex, flags = revlog.hex, self.flags
92 102 # if this is changed to support newlines in filenames,
93 103 # be sure to check the templates/ dir again (especially *-raw.tmpl)
94 104 return ''.join("%s\0%s%s\n" % (f, hex(self[f]), flags(f)) for f in fl)
95 105
96 106 def fastdelta(self, base, changes):
97 107 """Given a base manifest text as an array.array and a list of changes
98 108 relative to that text, compute a delta that can be used by revlog.
99 109 """
100 110 delta = []
101 111 dstart = None
102 112 dend = None
103 113 dline = [""]
104 114 start = 0
105 115 # zero copy representation of base as a buffer
106 116 addbuf = util.buffer(base)
107 117
108 118 # start with a readonly loop that finds the offset of
109 119 # each line and creates the deltas
110 120 for f, todelete in changes:
111 121 # bs will either be the index of the item or the insert point
112 122 start, end = _msearch(addbuf, f, start)
113 123 if not todelete:
114 124 l = "%s\0%s%s\n" % (f, revlog.hex(self[f]), self.flags(f))
115 125 else:
116 126 if start == end:
117 127 # item we want to delete was not found, error out
118 128 raise AssertionError(
119 129 _("failed to remove %s from manifest") % f)
120 130 l = ""
121 131 if dstart is not None and dstart <= start and dend >= start:
122 132 if dend < end:
123 133 dend = end
124 134 if l:
125 135 dline.append(l)
126 136 else:
127 137 if dstart is not None:
128 138 delta.append([dstart, dend, "".join(dline)])
129 139 dstart = start
130 140 dend = end
131 141 dline = [l]
132 142
133 143 if dstart is not None:
134 144 delta.append([dstart, dend, "".join(dline)])
135 145 # apply the delta to the base, and get a delta for addrevision
136 146 deltatext, arraytext = _addlistdelta(base, delta)
137 147 return arraytext, deltatext
138 148
139 149 def _msearch(m, s, lo=0, hi=None):
140 150 '''return a tuple (start, end) that says where to find s within m.
141 151
142 152 If the string is found m[start:end] are the line containing
143 153 that string. If start == end the string was not found and
144 154 they indicate the proper sorted insertion point.
145 155
146 156 m should be a buffer or a string
147 157 s is a string'''
148 158 def advance(i, c):
149 159 while i < lenm and m[i] != c:
150 160 i += 1
151 161 return i
152 162 if not s:
153 163 return (lo, lo)
154 164 lenm = len(m)
155 165 if not hi:
156 166 hi = lenm
157 167 while lo < hi:
158 168 mid = (lo + hi) // 2
159 169 start = mid
160 170 while start > 0 and m[start - 1] != '\n':
161 171 start -= 1
162 172 end = advance(start, '\0')
163 173 if m[start:end] < s:
164 174 # we know that after the null there are 40 bytes of sha1
165 175 # this translates to the bisect lo = mid + 1
166 176 lo = advance(end + 40, '\n') + 1
167 177 else:
168 178 # this translates to the bisect hi = mid
169 179 hi = start
170 180 end = advance(lo, '\0')
171 181 found = m[lo:end]
172 182 if s == found:
173 183 # we know that after the null there are 40 bytes of sha1
174 184 end = advance(end + 40, '\n')
175 185 return (lo, end + 1)
176 186 else:
177 187 return (lo, lo)
178 188
179 189 def _checkforbidden(l):
180 190 """Check filenames for illegal characters."""
181 191 for f in l:
182 192 if '\n' in f or '\r' in f:
183 193 raise error.RevlogError(
184 194 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
185 195
186 196
187 197 # apply the changes collected during the bisect loop to our addlist
188 198 # return a delta suitable for addrevision
189 199 def _addlistdelta(addlist, x):
190 200 # for large addlist arrays, building a new array is cheaper
191 201 # than repeatedly modifying the existing one
192 202 currentposition = 0
193 203 newaddlist = array.array('c')
194 204
195 205 for start, end, content in x:
196 206 newaddlist += addlist[currentposition:start]
197 207 if content:
198 208 newaddlist += array.array('c', content)
199 209
200 210 currentposition = end
201 211
202 212 newaddlist += addlist[currentposition:]
203 213
204 214 deltatext = "".join(struct.pack(">lll", start, end, len(content))
205 215 + content for start, end, content in x)
206 216 return deltatext, newaddlist
207 217
208 218 def _parse(lines):
209 219 mfdict = manifestdict()
210 220 parsers.parse_manifest(mfdict, mfdict._flags, lines)
211 221 return mfdict
212 222
213 223 class manifest(revlog.revlog):
214 224 def __init__(self, opener):
215 225 # we expect to deal with not more than four revs at a time,
216 226 # during a commit --amend
217 227 self._mancache = util.lrucachedict(4)
218 228 revlog.revlog.__init__(self, opener, "00manifest.i")
219 229
220 230 def readdelta(self, node):
221 231 r = self.rev(node)
222 232 return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
223 233
224 234 def readfast(self, node):
225 235 '''use the faster of readdelta or read'''
226 236 r = self.rev(node)
227 237 deltaparent = self.deltaparent(r)
228 238 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
229 239 return self.readdelta(node)
230 240 return self.read(node)
231 241
232 242 def read(self, node):
233 243 if node == revlog.nullid:
234 244 return manifestdict() # don't upset local cache
235 245 if node in self._mancache:
236 246 return self._mancache[node][0]
237 247 text = self.revision(node)
238 248 arraytext = array.array('c', text)
239 249 mapping = _parse(text)
240 250 self._mancache[node] = (mapping, arraytext)
241 251 return mapping
242 252
243 253 def find(self, node, f):
244 254 '''look up entry for a single file efficiently.
245 255 return (node, flags) pair if found, (None, None) if not.'''
246 256 if node in self._mancache:
247 257 mapping = self._mancache[node][0]
248 258 return mapping.get(f), mapping.flags(f)
249 259 text = self.revision(node)
250 260 start, end = _msearch(text, f)
251 261 if start == end:
252 262 return None, None
253 263 l = text[start:end]
254 264 f, n = l.split('\0')
255 265 return revlog.bin(n[:40]), n[40:-1]
256 266
257 267 def add(self, map, transaction, link, p1, p2, added, removed):
258 268 if p1 in self._mancache:
259 269 # If our first parent is in the manifest cache, we can
260 270 # compute a delta here using properties we know about the
261 271 # manifest up-front, which may save time later for the
262 272 # revlog layer.
263 273
264 274 _checkforbidden(added)
265 275 # combine the changed lists into one list for sorting
266 276 work = [(x, False) for x in added]
267 277 work.extend((x, True) for x in removed)
268 278 # this could use heapq.merge() (from Python 2.6+) or equivalent
269 279 # since the lists are already sorted
270 280 work.sort()
271 281
272 282 arraytext, deltatext = map.fastdelta(self._mancache[p1][1], work)
273 283 cachedelta = self.rev(p1), deltatext
274 284 text = util.buffer(arraytext)
275 285 else:
276 286 # The first parent manifest isn't already loaded, so we'll
277 287 # just encode a fulltext of the manifest and pass that
278 288 # through to the revlog layer, and let it handle the delta
279 289 # process.
280 290 text = map.text()
281 291 arraytext = array.array('c', text)
282 292 cachedelta = None
283 293
284 294 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
285 295 self._mancache[n] = (map, arraytext)
286 296
287 297 return n
General Comments 0
You need to be logged in to leave comments. Login now