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