##// END OF EJS Templates
treemanifest: lazily load manifests...
Martin von Zweigbergk -
r25222:0de132d5 default
parent child Browse files
Show More
@@ -1,975 +1,1022
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 import os
11 import os
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 def _parsev1(data):
15 def _parsev1(data):
16 # This method does a little bit of excessive-looking
16 # This method does a little bit of excessive-looking
17 # precondition checking. This is so that the behavior of this
17 # precondition checking. This is so that the behavior of this
18 # class exactly matches its C counterpart to try and help
18 # class exactly matches its C counterpart to try and help
19 # prevent surprise breakage for anyone that develops against
19 # prevent surprise breakage for anyone that develops against
20 # the pure version.
20 # the pure version.
21 if data and data[-1] != '\n':
21 if data and data[-1] != '\n':
22 raise ValueError('Manifest did not end in a newline.')
22 raise ValueError('Manifest did not end in a newline.')
23 prev = None
23 prev = None
24 for l in data.splitlines():
24 for l in data.splitlines():
25 if prev is not None and prev > l:
25 if prev is not None and prev > l:
26 raise ValueError('Manifest lines not in sorted order.')
26 raise ValueError('Manifest lines not in sorted order.')
27 prev = l
27 prev = l
28 f, n = l.split('\0')
28 f, n = l.split('\0')
29 if len(n) > 40:
29 if len(n) > 40:
30 yield f, revlog.bin(n[:40]), n[40:]
30 yield f, revlog.bin(n[:40]), n[40:]
31 else:
31 else:
32 yield f, revlog.bin(n), ''
32 yield f, revlog.bin(n), ''
33
33
34 def _parsev2(data):
34 def _parsev2(data):
35 metadataend = data.find('\n')
35 metadataend = data.find('\n')
36 # Just ignore metadata for now
36 # Just ignore metadata for now
37 pos = metadataend + 1
37 pos = metadataend + 1
38 prevf = ''
38 prevf = ''
39 while pos < len(data):
39 while pos < len(data):
40 end = data.find('\n', pos + 1) # +1 to skip stem length byte
40 end = data.find('\n', pos + 1) # +1 to skip stem length byte
41 if end == -1:
41 if end == -1:
42 raise ValueError('Manifest ended with incomplete file entry.')
42 raise ValueError('Manifest ended with incomplete file entry.')
43 stemlen = ord(data[pos])
43 stemlen = ord(data[pos])
44 items = data[pos + 1:end].split('\0')
44 items = data[pos + 1:end].split('\0')
45 f = prevf[:stemlen] + items[0]
45 f = prevf[:stemlen] + items[0]
46 if prevf > f:
46 if prevf > f:
47 raise ValueError('Manifest entries not in sorted order.')
47 raise ValueError('Manifest entries not in sorted order.')
48 fl = items[1]
48 fl = items[1]
49 # Just ignore metadata (items[2:] for now)
49 # Just ignore metadata (items[2:] for now)
50 n = data[end + 1:end + 21]
50 n = data[end + 1:end + 21]
51 yield f, n, fl
51 yield f, n, fl
52 pos = end + 22
52 pos = end + 22
53 prevf = f
53 prevf = f
54
54
55 def _parse(data):
55 def _parse(data):
56 """Generates (path, node, flags) tuples from a manifest text"""
56 """Generates (path, node, flags) tuples from a manifest text"""
57 if data.startswith('\0'):
57 if data.startswith('\0'):
58 return iter(_parsev2(data))
58 return iter(_parsev2(data))
59 else:
59 else:
60 return iter(_parsev1(data))
60 return iter(_parsev1(data))
61
61
62 def _text(it, usemanifestv2):
62 def _text(it, usemanifestv2):
63 """Given an iterator over (path, node, flags) tuples, returns a manifest
63 """Given an iterator over (path, node, flags) tuples, returns a manifest
64 text"""
64 text"""
65 if usemanifestv2:
65 if usemanifestv2:
66 return _textv2(it)
66 return _textv2(it)
67 else:
67 else:
68 return _textv1(it)
68 return _textv1(it)
69
69
70 def _textv1(it):
70 def _textv1(it):
71 files = []
71 files = []
72 lines = []
72 lines = []
73 _hex = revlog.hex
73 _hex = revlog.hex
74 for f, n, fl in it:
74 for f, n, fl in it:
75 files.append(f)
75 files.append(f)
76 # if this is changed to support newlines in filenames,
76 # if this is changed to support newlines in filenames,
77 # be sure to check the templates/ dir again (especially *-raw.tmpl)
77 # be sure to check the templates/ dir again (especially *-raw.tmpl)
78 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
78 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
79
79
80 _checkforbidden(files)
80 _checkforbidden(files)
81 return ''.join(lines)
81 return ''.join(lines)
82
82
83 def _textv2(it):
83 def _textv2(it):
84 files = []
84 files = []
85 lines = ['\0\n']
85 lines = ['\0\n']
86 prevf = ''
86 prevf = ''
87 for f, n, fl in it:
87 for f, n, fl in it:
88 files.append(f)
88 files.append(f)
89 stem = os.path.commonprefix([prevf, f])
89 stem = os.path.commonprefix([prevf, f])
90 stemlen = min(len(stem), 255)
90 stemlen = min(len(stem), 255)
91 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
91 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
92 prevf = f
92 prevf = f
93 _checkforbidden(files)
93 _checkforbidden(files)
94 return ''.join(lines)
94 return ''.join(lines)
95
95
96 class _lazymanifest(dict):
96 class _lazymanifest(dict):
97 """This is the pure implementation of lazymanifest.
97 """This is the pure implementation of lazymanifest.
98
98
99 It has not been optimized *at all* and is not lazy.
99 It has not been optimized *at all* and is not lazy.
100 """
100 """
101
101
102 def __init__(self, data):
102 def __init__(self, data):
103 dict.__init__(self)
103 dict.__init__(self)
104 for f, n, fl in _parse(data):
104 for f, n, fl in _parse(data):
105 self[f] = n, fl
105 self[f] = n, fl
106
106
107 def __setitem__(self, k, v):
107 def __setitem__(self, k, v):
108 node, flag = v
108 node, flag = v
109 assert node is not None
109 assert node is not None
110 if len(node) > 21:
110 if len(node) > 21:
111 node = node[:21] # match c implementation behavior
111 node = node[:21] # match c implementation behavior
112 dict.__setitem__(self, k, (node, flag))
112 dict.__setitem__(self, k, (node, flag))
113
113
114 def __iter__(self):
114 def __iter__(self):
115 return iter(sorted(dict.keys(self)))
115 return iter(sorted(dict.keys(self)))
116
116
117 def iterkeys(self):
117 def iterkeys(self):
118 return iter(sorted(dict.keys(self)))
118 return iter(sorted(dict.keys(self)))
119
119
120 def iterentries(self):
120 def iterentries(self):
121 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
121 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
122
122
123 def copy(self):
123 def copy(self):
124 c = _lazymanifest('')
124 c = _lazymanifest('')
125 c.update(self)
125 c.update(self)
126 return c
126 return c
127
127
128 def diff(self, m2, clean=False):
128 def diff(self, m2, clean=False):
129 '''Finds changes between the current manifest and m2.'''
129 '''Finds changes between the current manifest and m2.'''
130 diff = {}
130 diff = {}
131
131
132 for fn, e1 in self.iteritems():
132 for fn, e1 in self.iteritems():
133 if fn not in m2:
133 if fn not in m2:
134 diff[fn] = e1, (None, '')
134 diff[fn] = e1, (None, '')
135 else:
135 else:
136 e2 = m2[fn]
136 e2 = m2[fn]
137 if e1 != e2:
137 if e1 != e2:
138 diff[fn] = e1, e2
138 diff[fn] = e1, e2
139 elif clean:
139 elif clean:
140 diff[fn] = None
140 diff[fn] = None
141
141
142 for fn, e2 in m2.iteritems():
142 for fn, e2 in m2.iteritems():
143 if fn not in self:
143 if fn not in self:
144 diff[fn] = (None, ''), e2
144 diff[fn] = (None, ''), e2
145
145
146 return diff
146 return diff
147
147
148 def filtercopy(self, filterfn):
148 def filtercopy(self, filterfn):
149 c = _lazymanifest('')
149 c = _lazymanifest('')
150 for f, n, fl in self.iterentries():
150 for f, n, fl in self.iterentries():
151 if filterfn(f):
151 if filterfn(f):
152 c[f] = n, fl
152 c[f] = n, fl
153 return c
153 return c
154
154
155 def text(self):
155 def text(self):
156 """Get the full data of this manifest as a bytestring."""
156 """Get the full data of this manifest as a bytestring."""
157 return _textv1(self.iterentries())
157 return _textv1(self.iterentries())
158
158
159 try:
159 try:
160 _lazymanifest = parsers.lazymanifest
160 _lazymanifest = parsers.lazymanifest
161 except AttributeError:
161 except AttributeError:
162 pass
162 pass
163
163
164 class manifestdict(object):
164 class manifestdict(object):
165 def __init__(self, data=''):
165 def __init__(self, data=''):
166 if data.startswith('\0'):
166 if data.startswith('\0'):
167 #_lazymanifest can not parse v2
167 #_lazymanifest can not parse v2
168 self._lm = _lazymanifest('')
168 self._lm = _lazymanifest('')
169 for f, n, fl in _parsev2(data):
169 for f, n, fl in _parsev2(data):
170 self._lm[f] = n, fl
170 self._lm[f] = n, fl
171 else:
171 else:
172 self._lm = _lazymanifest(data)
172 self._lm = _lazymanifest(data)
173
173
174 def __getitem__(self, key):
174 def __getitem__(self, key):
175 return self._lm[key][0]
175 return self._lm[key][0]
176
176
177 def find(self, key):
177 def find(self, key):
178 return self._lm[key]
178 return self._lm[key]
179
179
180 def __len__(self):
180 def __len__(self):
181 return len(self._lm)
181 return len(self._lm)
182
182
183 def __setitem__(self, key, node):
183 def __setitem__(self, key, node):
184 self._lm[key] = node, self.flags(key, '')
184 self._lm[key] = node, self.flags(key, '')
185
185
186 def __contains__(self, key):
186 def __contains__(self, key):
187 return key in self._lm
187 return key in self._lm
188
188
189 def __delitem__(self, key):
189 def __delitem__(self, key):
190 del self._lm[key]
190 del self._lm[key]
191
191
192 def __iter__(self):
192 def __iter__(self):
193 return self._lm.__iter__()
193 return self._lm.__iter__()
194
194
195 def iterkeys(self):
195 def iterkeys(self):
196 return self._lm.iterkeys()
196 return self._lm.iterkeys()
197
197
198 def keys(self):
198 def keys(self):
199 return list(self.iterkeys())
199 return list(self.iterkeys())
200
200
201 def filesnotin(self, m2):
201 def filesnotin(self, m2):
202 '''Set of files in this manifest that are not in the other'''
202 '''Set of files in this manifest that are not in the other'''
203 files = set(self)
203 files = set(self)
204 files.difference_update(m2)
204 files.difference_update(m2)
205 return files
205 return files
206
206
207 @propertycache
207 @propertycache
208 def _dirs(self):
208 def _dirs(self):
209 return util.dirs(self)
209 return util.dirs(self)
210
210
211 def dirs(self):
211 def dirs(self):
212 return self._dirs
212 return self._dirs
213
213
214 def hasdir(self, dir):
214 def hasdir(self, dir):
215 return dir in self._dirs
215 return dir in self._dirs
216
216
217 def _filesfastpath(self, match):
217 def _filesfastpath(self, match):
218 '''Checks whether we can correctly and quickly iterate over matcher
218 '''Checks whether we can correctly and quickly iterate over matcher
219 files instead of over manifest files.'''
219 files instead of over manifest files.'''
220 files = match.files()
220 files = match.files()
221 return (len(files) < 100 and (match.isexact() or
221 return (len(files) < 100 and (match.isexact() or
222 (not match.anypats() and all(fn in self for fn in files))))
222 (not match.anypats() and all(fn in self for fn in files))))
223
223
224 def walk(self, match):
224 def walk(self, match):
225 '''Generates matching file names.
225 '''Generates matching file names.
226
226
227 Equivalent to manifest.matches(match).iterkeys(), but without creating
227 Equivalent to manifest.matches(match).iterkeys(), but without creating
228 an entirely new manifest.
228 an entirely new manifest.
229
229
230 It also reports nonexistent files by marking them bad with match.bad().
230 It also reports nonexistent files by marking them bad with match.bad().
231 '''
231 '''
232 if match.always():
232 if match.always():
233 for f in iter(self):
233 for f in iter(self):
234 yield f
234 yield f
235 return
235 return
236
236
237 fset = set(match.files())
237 fset = set(match.files())
238
238
239 # avoid the entire walk if we're only looking for specific files
239 # avoid the entire walk if we're only looking for specific files
240 if self._filesfastpath(match):
240 if self._filesfastpath(match):
241 for fn in sorted(fset):
241 for fn in sorted(fset):
242 yield fn
242 yield fn
243 return
243 return
244
244
245 for fn in self:
245 for fn in self:
246 if fn in fset:
246 if fn in fset:
247 # specified pattern is the exact name
247 # specified pattern is the exact name
248 fset.remove(fn)
248 fset.remove(fn)
249 if match(fn):
249 if match(fn):
250 yield fn
250 yield fn
251
251
252 # for dirstate.walk, files=['.'] means "walk the whole tree".
252 # for dirstate.walk, files=['.'] means "walk the whole tree".
253 # follow that here, too
253 # follow that here, too
254 fset.discard('.')
254 fset.discard('.')
255
255
256 for fn in sorted(fset):
256 for fn in sorted(fset):
257 if not self.hasdir(fn):
257 if not self.hasdir(fn):
258 match.bad(fn, None)
258 match.bad(fn, None)
259
259
260 def matches(self, match):
260 def matches(self, match):
261 '''generate a new manifest filtered by the match argument'''
261 '''generate a new manifest filtered by the match argument'''
262 if match.always():
262 if match.always():
263 return self.copy()
263 return self.copy()
264
264
265 if self._filesfastpath(match):
265 if self._filesfastpath(match):
266 m = manifestdict()
266 m = manifestdict()
267 lm = self._lm
267 lm = self._lm
268 for fn in match.files():
268 for fn in match.files():
269 if fn in lm:
269 if fn in lm:
270 m._lm[fn] = lm[fn]
270 m._lm[fn] = lm[fn]
271 return m
271 return m
272
272
273 m = manifestdict()
273 m = manifestdict()
274 m._lm = self._lm.filtercopy(match)
274 m._lm = self._lm.filtercopy(match)
275 return m
275 return m
276
276
277 def diff(self, m2, clean=False):
277 def diff(self, m2, clean=False):
278 '''Finds changes between the current manifest and m2.
278 '''Finds changes between the current manifest and m2.
279
279
280 Args:
280 Args:
281 m2: the manifest to which this manifest should be compared.
281 m2: the manifest to which this manifest should be compared.
282 clean: if true, include files unchanged between these manifests
282 clean: if true, include files unchanged between these manifests
283 with a None value in the returned dictionary.
283 with a None value in the returned dictionary.
284
284
285 The result is returned as a dict with filename as key and
285 The result is returned as a dict with filename as key and
286 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
286 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
287 nodeid in the current/other manifest and fl1/fl2 is the flag
287 nodeid in the current/other manifest and fl1/fl2 is the flag
288 in the current/other manifest. Where the file does not exist,
288 in the current/other manifest. Where the file does not exist,
289 the nodeid will be None and the flags will be the empty
289 the nodeid will be None and the flags will be the empty
290 string.
290 string.
291 '''
291 '''
292 return self._lm.diff(m2._lm, clean)
292 return self._lm.diff(m2._lm, clean)
293
293
294 def setflag(self, key, flag):
294 def setflag(self, key, flag):
295 self._lm[key] = self[key], flag
295 self._lm[key] = self[key], flag
296
296
297 def get(self, key, default=None):
297 def get(self, key, default=None):
298 try:
298 try:
299 return self._lm[key][0]
299 return self._lm[key][0]
300 except KeyError:
300 except KeyError:
301 return default
301 return default
302
302
303 def flags(self, key, default=''):
303 def flags(self, key, default=''):
304 try:
304 try:
305 return self._lm[key][1]
305 return self._lm[key][1]
306 except KeyError:
306 except KeyError:
307 return default
307 return default
308
308
309 def copy(self):
309 def copy(self):
310 c = manifestdict()
310 c = manifestdict()
311 c._lm = self._lm.copy()
311 c._lm = self._lm.copy()
312 return c
312 return c
313
313
314 def iteritems(self):
314 def iteritems(self):
315 return (x[:2] for x in self._lm.iterentries())
315 return (x[:2] for x in self._lm.iterentries())
316
316
317 def text(self, usemanifestv2=False):
317 def text(self, usemanifestv2=False):
318 if usemanifestv2:
318 if usemanifestv2:
319 return _textv2(self._lm.iterentries())
319 return _textv2(self._lm.iterentries())
320 else:
320 else:
321 # use (probably) native version for v1
321 # use (probably) native version for v1
322 return self._lm.text()
322 return self._lm.text()
323
323
324 def fastdelta(self, base, changes):
324 def fastdelta(self, base, changes):
325 """Given a base manifest text as an array.array and a list of changes
325 """Given a base manifest text as an array.array and a list of changes
326 relative to that text, compute a delta that can be used by revlog.
326 relative to that text, compute a delta that can be used by revlog.
327 """
327 """
328 delta = []
328 delta = []
329 dstart = None
329 dstart = None
330 dend = None
330 dend = None
331 dline = [""]
331 dline = [""]
332 start = 0
332 start = 0
333 # zero copy representation of base as a buffer
333 # zero copy representation of base as a buffer
334 addbuf = util.buffer(base)
334 addbuf = util.buffer(base)
335
335
336 # start with a readonly loop that finds the offset of
336 # start with a readonly loop that finds the offset of
337 # each line and creates the deltas
337 # each line and creates the deltas
338 for f, todelete in changes:
338 for f, todelete in changes:
339 # bs will either be the index of the item or the insert point
339 # bs will either be the index of the item or the insert point
340 start, end = _msearch(addbuf, f, start)
340 start, end = _msearch(addbuf, f, start)
341 if not todelete:
341 if not todelete:
342 h, fl = self._lm[f]
342 h, fl = self._lm[f]
343 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
343 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
344 else:
344 else:
345 if start == end:
345 if start == end:
346 # item we want to delete was not found, error out
346 # item we want to delete was not found, error out
347 raise AssertionError(
347 raise AssertionError(
348 _("failed to remove %s from manifest") % f)
348 _("failed to remove %s from manifest") % f)
349 l = ""
349 l = ""
350 if dstart is not None and dstart <= start and dend >= start:
350 if dstart is not None and dstart <= start and dend >= start:
351 if dend < end:
351 if dend < end:
352 dend = end
352 dend = end
353 if l:
353 if l:
354 dline.append(l)
354 dline.append(l)
355 else:
355 else:
356 if dstart is not None:
356 if dstart is not None:
357 delta.append([dstart, dend, "".join(dline)])
357 delta.append([dstart, dend, "".join(dline)])
358 dstart = start
358 dstart = start
359 dend = end
359 dend = end
360 dline = [l]
360 dline = [l]
361
361
362 if dstart is not None:
362 if dstart is not None:
363 delta.append([dstart, dend, "".join(dline)])
363 delta.append([dstart, dend, "".join(dline)])
364 # apply the delta to the base, and get a delta for addrevision
364 # apply the delta to the base, and get a delta for addrevision
365 deltatext, arraytext = _addlistdelta(base, delta)
365 deltatext, arraytext = _addlistdelta(base, delta)
366 return arraytext, deltatext
366 return arraytext, deltatext
367
367
368 def _msearch(m, s, lo=0, hi=None):
368 def _msearch(m, s, lo=0, hi=None):
369 '''return a tuple (start, end) that says where to find s within m.
369 '''return a tuple (start, end) that says where to find s within m.
370
370
371 If the string is found m[start:end] are the line containing
371 If the string is found m[start:end] are the line containing
372 that string. If start == end the string was not found and
372 that string. If start == end the string was not found and
373 they indicate the proper sorted insertion point.
373 they indicate the proper sorted insertion point.
374
374
375 m should be a buffer or a string
375 m should be a buffer or a string
376 s is a string'''
376 s is a string'''
377 def advance(i, c):
377 def advance(i, c):
378 while i < lenm and m[i] != c:
378 while i < lenm and m[i] != c:
379 i += 1
379 i += 1
380 return i
380 return i
381 if not s:
381 if not s:
382 return (lo, lo)
382 return (lo, lo)
383 lenm = len(m)
383 lenm = len(m)
384 if not hi:
384 if not hi:
385 hi = lenm
385 hi = lenm
386 while lo < hi:
386 while lo < hi:
387 mid = (lo + hi) // 2
387 mid = (lo + hi) // 2
388 start = mid
388 start = mid
389 while start > 0 and m[start - 1] != '\n':
389 while start > 0 and m[start - 1] != '\n':
390 start -= 1
390 start -= 1
391 end = advance(start, '\0')
391 end = advance(start, '\0')
392 if m[start:end] < s:
392 if m[start:end] < s:
393 # we know that after the null there are 40 bytes of sha1
393 # we know that after the null there are 40 bytes of sha1
394 # this translates to the bisect lo = mid + 1
394 # this translates to the bisect lo = mid + 1
395 lo = advance(end + 40, '\n') + 1
395 lo = advance(end + 40, '\n') + 1
396 else:
396 else:
397 # this translates to the bisect hi = mid
397 # this translates to the bisect hi = mid
398 hi = start
398 hi = start
399 end = advance(lo, '\0')
399 end = advance(lo, '\0')
400 found = m[lo:end]
400 found = m[lo:end]
401 if s == found:
401 if s == found:
402 # we know that after the null there are 40 bytes of sha1
402 # we know that after the null there are 40 bytes of sha1
403 end = advance(end + 40, '\n')
403 end = advance(end + 40, '\n')
404 return (lo, end + 1)
404 return (lo, end + 1)
405 else:
405 else:
406 return (lo, lo)
406 return (lo, lo)
407
407
408 def _checkforbidden(l):
408 def _checkforbidden(l):
409 """Check filenames for illegal characters."""
409 """Check filenames for illegal characters."""
410 for f in l:
410 for f in l:
411 if '\n' in f or '\r' in f:
411 if '\n' in f or '\r' in f:
412 raise error.RevlogError(
412 raise error.RevlogError(
413 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
413 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
414
414
415
415
416 # apply the changes collected during the bisect loop to our addlist
416 # apply the changes collected during the bisect loop to our addlist
417 # return a delta suitable for addrevision
417 # return a delta suitable for addrevision
418 def _addlistdelta(addlist, x):
418 def _addlistdelta(addlist, x):
419 # for large addlist arrays, building a new array is cheaper
419 # for large addlist arrays, building a new array is cheaper
420 # than repeatedly modifying the existing one
420 # than repeatedly modifying the existing one
421 currentposition = 0
421 currentposition = 0
422 newaddlist = array.array('c')
422 newaddlist = array.array('c')
423
423
424 for start, end, content in x:
424 for start, end, content in x:
425 newaddlist += addlist[currentposition:start]
425 newaddlist += addlist[currentposition:start]
426 if content:
426 if content:
427 newaddlist += array.array('c', content)
427 newaddlist += array.array('c', content)
428
428
429 currentposition = end
429 currentposition = end
430
430
431 newaddlist += addlist[currentposition:]
431 newaddlist += addlist[currentposition:]
432
432
433 deltatext = "".join(struct.pack(">lll", start, end, len(content))
433 deltatext = "".join(struct.pack(">lll", start, end, len(content))
434 + content for start, end, content in x)
434 + content for start, end, content in x)
435 return deltatext, newaddlist
435 return deltatext, newaddlist
436
436
437 def _splittopdir(f):
437 def _splittopdir(f):
438 if '/' in f:
438 if '/' in f:
439 dir, subpath = f.split('/', 1)
439 dir, subpath = f.split('/', 1)
440 return dir + '/', subpath
440 return dir + '/', subpath
441 else:
441 else:
442 return '', f
442 return '', f
443
443
444 _noop = lambda: None
445
444 class treemanifest(object):
446 class treemanifest(object):
445 def __init__(self, dir='', text=''):
447 def __init__(self, dir='', text=''):
446 self._dir = dir
448 self._dir = dir
447 self._node = revlog.nullid
449 self._node = revlog.nullid
450 self._load = _noop
448 self._dirty = False
451 self._dirty = False
449 self._dirs = {}
452 self._dirs = {}
450 # Using _lazymanifest here is a little slower than plain old dicts
453 # Using _lazymanifest here is a little slower than plain old dicts
451 self._files = {}
454 self._files = {}
452 self._flags = {}
455 self._flags = {}
453 if text:
456 if text:
454 def readsubtree(subdir, subm):
457 def readsubtree(subdir, subm):
455 raise AssertionError('treemanifest constructor only accepts '
458 raise AssertionError('treemanifest constructor only accepts '
456 'flat manifests')
459 'flat manifests')
457 self.parse(text, readsubtree)
460 self.parse(text, readsubtree)
458 self._dirty = True # Mark flat manifest dirty after parsing
461 self._dirty = True # Mark flat manifest dirty after parsing
459
462
460 def _subpath(self, path):
463 def _subpath(self, path):
461 return self._dir + path
464 return self._dir + path
462
465
463 def __len__(self):
466 def __len__(self):
467 self._load()
464 size = len(self._files)
468 size = len(self._files)
465 for m in self._dirs.values():
469 for m in self._dirs.values():
466 size += m.__len__()
470 size += m.__len__()
467 return size
471 return size
468
472
469 def _isempty(self):
473 def _isempty(self):
474 self._load() # for consistency; already loaded by all callers
470 return (not self._files and (not self._dirs or
475 return (not self._files and (not self._dirs or
471 all(m._isempty() for m in self._dirs.values())))
476 all(m._isempty() for m in self._dirs.values())))
472
477
473 def __str__(self):
478 def __str__(self):
474 return ('<treemanifest dir=%s, node=%s, dirty=%s>' %
479 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s>' %
475 (self._dir, revlog.hex(self._node), self._dirty))
480 (self._dir, revlog.hex(self._node),
481 bool(self._load is _noop),
482 self._dirty))
476
483
477 def dir(self):
484 def dir(self):
478 '''The directory that this tree manifest represents, including a
485 '''The directory that this tree manifest represents, including a
479 trailing '/'. Empty string for the repo root directory.'''
486 trailing '/'. Empty string for the repo root directory.'''
480 return self._dir
487 return self._dir
481
488
482 def node(self):
489 def node(self):
483 '''This node of this instance. nullid for unsaved instances. Should
490 '''This node of this instance. nullid for unsaved instances. Should
484 be updated when the instance is read or written from a revlog.
491 be updated when the instance is read or written from a revlog.
485 '''
492 '''
486 assert not self._dirty
493 assert not self._dirty
487 return self._node
494 return self._node
488
495
489 def setnode(self, node):
496 def setnode(self, node):
490 self._node = node
497 self._node = node
491 self._dirty = False
498 self._dirty = False
492
499
493 def iteritems(self):
500 def iteritems(self):
501 self._load()
494 for p, n in sorted(self._dirs.items() + self._files.items()):
502 for p, n in sorted(self._dirs.items() + self._files.items()):
495 if p in self._files:
503 if p in self._files:
496 yield self._subpath(p), n
504 yield self._subpath(p), n
497 else:
505 else:
498 for f, sn in n.iteritems():
506 for f, sn in n.iteritems():
499 yield f, sn
507 yield f, sn
500
508
501 def iterkeys(self):
509 def iterkeys(self):
510 self._load()
502 for p in sorted(self._dirs.keys() + self._files.keys()):
511 for p in sorted(self._dirs.keys() + self._files.keys()):
503 if p in self._files:
512 if p in self._files:
504 yield self._subpath(p)
513 yield self._subpath(p)
505 else:
514 else:
506 for f in self._dirs[p].iterkeys():
515 for f in self._dirs[p].iterkeys():
507 yield f
516 yield f
508
517
509 def keys(self):
518 def keys(self):
510 return list(self.iterkeys())
519 return list(self.iterkeys())
511
520
512 def __iter__(self):
521 def __iter__(self):
513 return self.iterkeys()
522 return self.iterkeys()
514
523
515 def __contains__(self, f):
524 def __contains__(self, f):
516 if f is None:
525 if f is None:
517 return False
526 return False
527 self._load()
518 dir, subpath = _splittopdir(f)
528 dir, subpath = _splittopdir(f)
519 if dir:
529 if dir:
520 if dir not in self._dirs:
530 if dir not in self._dirs:
521 return False
531 return False
522 return self._dirs[dir].__contains__(subpath)
532 return self._dirs[dir].__contains__(subpath)
523 else:
533 else:
524 return f in self._files
534 return f in self._files
525
535
526 def get(self, f, default=None):
536 def get(self, f, default=None):
537 self._load()
527 dir, subpath = _splittopdir(f)
538 dir, subpath = _splittopdir(f)
528 if dir:
539 if dir:
529 if dir not in self._dirs:
540 if dir not in self._dirs:
530 return default
541 return default
531 return self._dirs[dir].get(subpath, default)
542 return self._dirs[dir].get(subpath, default)
532 else:
543 else:
533 return self._files.get(f, default)
544 return self._files.get(f, default)
534
545
535 def __getitem__(self, f):
546 def __getitem__(self, f):
547 self._load()
536 dir, subpath = _splittopdir(f)
548 dir, subpath = _splittopdir(f)
537 if dir:
549 if dir:
538 return self._dirs[dir].__getitem__(subpath)
550 return self._dirs[dir].__getitem__(subpath)
539 else:
551 else:
540 return self._files[f]
552 return self._files[f]
541
553
542 def flags(self, f):
554 def flags(self, f):
555 self._load()
543 dir, subpath = _splittopdir(f)
556 dir, subpath = _splittopdir(f)
544 if dir:
557 if dir:
545 if dir not in self._dirs:
558 if dir not in self._dirs:
546 return ''
559 return ''
547 return self._dirs[dir].flags(subpath)
560 return self._dirs[dir].flags(subpath)
548 else:
561 else:
549 if f in self._dirs:
562 if f in self._dirs:
550 return ''
563 return ''
551 return self._flags.get(f, '')
564 return self._flags.get(f, '')
552
565
553 def find(self, f):
566 def find(self, f):
567 self._load()
554 dir, subpath = _splittopdir(f)
568 dir, subpath = _splittopdir(f)
555 if dir:
569 if dir:
556 return self._dirs[dir].find(subpath)
570 return self._dirs[dir].find(subpath)
557 else:
571 else:
558 return self._files[f], self._flags.get(f, '')
572 return self._files[f], self._flags.get(f, '')
559
573
560 def __delitem__(self, f):
574 def __delitem__(self, f):
575 self._load()
561 dir, subpath = _splittopdir(f)
576 dir, subpath = _splittopdir(f)
562 if dir:
577 if dir:
563 self._dirs[dir].__delitem__(subpath)
578 self._dirs[dir].__delitem__(subpath)
564 # If the directory is now empty, remove it
579 # If the directory is now empty, remove it
565 if self._dirs[dir]._isempty():
580 if self._dirs[dir]._isempty():
566 del self._dirs[dir]
581 del self._dirs[dir]
567 else:
582 else:
568 del self._files[f]
583 del self._files[f]
569 if f in self._flags:
584 if f in self._flags:
570 del self._flags[f]
585 del self._flags[f]
571 self._dirty = True
586 self._dirty = True
572
587
573 def __setitem__(self, f, n):
588 def __setitem__(self, f, n):
574 assert n is not None
589 assert n is not None
590 self._load()
575 dir, subpath = _splittopdir(f)
591 dir, subpath = _splittopdir(f)
576 if dir:
592 if dir:
577 if dir not in self._dirs:
593 if dir not in self._dirs:
578 self._dirs[dir] = treemanifest(self._subpath(dir))
594 self._dirs[dir] = treemanifest(self._subpath(dir))
579 self._dirs[dir].__setitem__(subpath, n)
595 self._dirs[dir].__setitem__(subpath, n)
580 else:
596 else:
581 self._files[f] = n[:21] # to match manifestdict's behavior
597 self._files[f] = n[:21] # to match manifestdict's behavior
582 self._dirty = True
598 self._dirty = True
583
599
584 def setflag(self, f, flags):
600 def setflag(self, f, flags):
585 """Set the flags (symlink, executable) for path f."""
601 """Set the flags (symlink, executable) for path f."""
586 assert 'd' not in flags
602 assert 'd' not in flags
603 self._load()
587 dir, subpath = _splittopdir(f)
604 dir, subpath = _splittopdir(f)
588 if dir:
605 if dir:
589 if dir not in self._dirs:
606 if dir not in self._dirs:
590 self._dirs[dir] = treemanifest(self._subpath(dir))
607 self._dirs[dir] = treemanifest(self._subpath(dir))
591 self._dirs[dir].setflag(subpath, flags)
608 self._dirs[dir].setflag(subpath, flags)
592 else:
609 else:
593 self._flags[f] = flags
610 self._flags[f] = flags
594 self._dirty = True
611 self._dirty = True
595
612
596 def copy(self):
613 def copy(self):
597 copy = treemanifest(self._dir)
614 copy = treemanifest(self._dir)
598 copy._node = self._node
615 copy._node = self._node
599 copy._dirty = self._dirty
616 copy._dirty = self._dirty
617 def _load():
618 self._load()
600 for d in self._dirs:
619 for d in self._dirs:
601 copy._dirs[d] = self._dirs[d].copy()
620 copy._dirs[d] = self._dirs[d].copy()
602 copy._files = dict.copy(self._files)
621 copy._files = dict.copy(self._files)
603 copy._flags = dict.copy(self._flags)
622 copy._flags = dict.copy(self._flags)
623 copy._load = _noop
624 copy._load = _load
625 if self._load == _noop:
626 # Chaining _load if it's _noop is functionally correct, but the
627 # chain may end up excessively long (stack overflow), and
628 # will prevent garbage collection of 'self'.
629 copy._load()
604 return copy
630 return copy
605
631
606 def filesnotin(self, m2):
632 def filesnotin(self, m2):
607 '''Set of files in this manifest that are not in the other'''
633 '''Set of files in this manifest that are not in the other'''
608 files = set()
634 files = set()
609 def _filesnotin(t1, t2):
635 def _filesnotin(t1, t2):
610 if t1._node == t2._node and not t1._dirty and not t2._dirty:
636 if t1._node == t2._node and not t1._dirty and not t2._dirty:
611 return
637 return
638 t1._load()
639 t2._load()
612 for d, m1 in t1._dirs.iteritems():
640 for d, m1 in t1._dirs.iteritems():
613 if d in t2._dirs:
641 if d in t2._dirs:
614 m2 = t2._dirs[d]
642 m2 = t2._dirs[d]
615 _filesnotin(m1, m2)
643 _filesnotin(m1, m2)
616 else:
644 else:
617 files.update(m1.iterkeys())
645 files.update(m1.iterkeys())
618
646
619 for fn in t1._files.iterkeys():
647 for fn in t1._files.iterkeys():
620 if fn not in t2._files:
648 if fn not in t2._files:
621 files.add(t1._subpath(fn))
649 files.add(t1._subpath(fn))
622
650
623 _filesnotin(self, m2)
651 _filesnotin(self, m2)
624 return files
652 return files
625
653
626 @propertycache
654 @propertycache
627 def _alldirs(self):
655 def _alldirs(self):
628 return util.dirs(self)
656 return util.dirs(self)
629
657
630 def dirs(self):
658 def dirs(self):
631 return self._alldirs
659 return self._alldirs
632
660
633 def hasdir(self, dir):
661 def hasdir(self, dir):
662 self._load()
634 topdir, subdir = _splittopdir(dir)
663 topdir, subdir = _splittopdir(dir)
635 if topdir:
664 if topdir:
636 if topdir in self._dirs:
665 if topdir in self._dirs:
637 return self._dirs[topdir].hasdir(subdir)
666 return self._dirs[topdir].hasdir(subdir)
638 return False
667 return False
639 return (dir + '/') in self._dirs
668 return (dir + '/') in self._dirs
640
669
641 def walk(self, match):
670 def walk(self, match):
642 '''Generates matching file names.
671 '''Generates matching file names.
643
672
644 Equivalent to manifest.matches(match).iterkeys(), but without creating
673 Equivalent to manifest.matches(match).iterkeys(), but without creating
645 an entirely new manifest.
674 an entirely new manifest.
646
675
647 It also reports nonexistent files by marking them bad with match.bad().
676 It also reports nonexistent files by marking them bad with match.bad().
648 '''
677 '''
649 if match.always():
678 if match.always():
650 for f in iter(self):
679 for f in iter(self):
651 yield f
680 yield f
652 return
681 return
653
682
654 fset = set(match.files())
683 fset = set(match.files())
655
684
656 for fn in self._walk(match):
685 for fn in self._walk(match):
657 if fn in fset:
686 if fn in fset:
658 # specified pattern is the exact name
687 # specified pattern is the exact name
659 fset.remove(fn)
688 fset.remove(fn)
660 yield fn
689 yield fn
661
690
662 # for dirstate.walk, files=['.'] means "walk the whole tree".
691 # for dirstate.walk, files=['.'] means "walk the whole tree".
663 # follow that here, too
692 # follow that here, too
664 fset.discard('.')
693 fset.discard('.')
665
694
666 for fn in sorted(fset):
695 for fn in sorted(fset):
667 if not self.hasdir(fn):
696 if not self.hasdir(fn):
668 match.bad(fn, None)
697 match.bad(fn, None)
669
698
670 def _walk(self, match):
699 def _walk(self, match):
671 '''Recursively generates matching file names for walk().'''
700 '''Recursively generates matching file names for walk().'''
672 if not match.visitdir(self._dir[:-1] or '.'):
701 if not match.visitdir(self._dir[:-1] or '.'):
673 return
702 return
674
703
675 # yield this dir's files and walk its submanifests
704 # yield this dir's files and walk its submanifests
705 self._load()
676 for p in sorted(self._dirs.keys() + self._files.keys()):
706 for p in sorted(self._dirs.keys() + self._files.keys()):
677 if p in self._files:
707 if p in self._files:
678 fullp = self._subpath(p)
708 fullp = self._subpath(p)
679 if match(fullp):
709 if match(fullp):
680 yield fullp
710 yield fullp
681 else:
711 else:
682 for f in self._dirs[p]._walk(match):
712 for f in self._dirs[p]._walk(match):
683 yield f
713 yield f
684
714
685 def matches(self, match):
715 def matches(self, match):
686 '''generate a new manifest filtered by the match argument'''
716 '''generate a new manifest filtered by the match argument'''
687 if match.always():
717 if match.always():
688 return self.copy()
718 return self.copy()
689
719
690 return self._matches(match)
720 return self._matches(match)
691
721
692 def _matches(self, match):
722 def _matches(self, match):
693 '''recursively generate a new manifest filtered by the match argument.
723 '''recursively generate a new manifest filtered by the match argument.
694 '''
724 '''
695 ret = treemanifest(self._dir)
725 ret = treemanifest(self._dir)
696
726
697 if not match.visitdir(self._dir[:-1] or '.'):
727 if not match.visitdir(self._dir[:-1] or '.'):
698 return ret
728 return ret
699
729
730 self._load()
700 for fn in self._files:
731 for fn in self._files:
701 fullp = self._subpath(fn)
732 fullp = self._subpath(fn)
702 if not match(fullp):
733 if not match(fullp):
703 continue
734 continue
704 ret._files[fn] = self._files[fn]
735 ret._files[fn] = self._files[fn]
705 if fn in self._flags:
736 if fn in self._flags:
706 ret._flags[fn] = self._flags[fn]
737 ret._flags[fn] = self._flags[fn]
707
738
708 for dir, subm in self._dirs.iteritems():
739 for dir, subm in self._dirs.iteritems():
709 m = subm._matches(match)
740 m = subm._matches(match)
710 if not m._isempty():
741 if not m._isempty():
711 ret._dirs[dir] = m
742 ret._dirs[dir] = m
712
743
713 if not ret._isempty():
744 if not ret._isempty():
714 ret._dirty = True
745 ret._dirty = True
715 return ret
746 return ret
716
747
717 def diff(self, m2, clean=False):
748 def diff(self, m2, clean=False):
718 '''Finds changes between the current manifest and m2.
749 '''Finds changes between the current manifest and m2.
719
750
720 Args:
751 Args:
721 m2: the manifest to which this manifest should be compared.
752 m2: the manifest to which this manifest should be compared.
722 clean: if true, include files unchanged between these manifests
753 clean: if true, include files unchanged between these manifests
723 with a None value in the returned dictionary.
754 with a None value in the returned dictionary.
724
755
725 The result is returned as a dict with filename as key and
756 The result is returned as a dict with filename as key and
726 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
757 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
727 nodeid in the current/other manifest and fl1/fl2 is the flag
758 nodeid in the current/other manifest and fl1/fl2 is the flag
728 in the current/other manifest. Where the file does not exist,
759 in the current/other manifest. Where the file does not exist,
729 the nodeid will be None and the flags will be the empty
760 the nodeid will be None and the flags will be the empty
730 string.
761 string.
731 '''
762 '''
732 result = {}
763 result = {}
733 emptytree = treemanifest()
764 emptytree = treemanifest()
734 def _diff(t1, t2):
765 def _diff(t1, t2):
735 if t1._node == t2._node and not t1._dirty and not t2._dirty:
766 if t1._node == t2._node and not t1._dirty and not t2._dirty:
736 return
767 return
768 t1._load()
769 t2._load()
737 for d, m1 in t1._dirs.iteritems():
770 for d, m1 in t1._dirs.iteritems():
738 m2 = t2._dirs.get(d, emptytree)
771 m2 = t2._dirs.get(d, emptytree)
739 _diff(m1, m2)
772 _diff(m1, m2)
740
773
741 for d, m2 in t2._dirs.iteritems():
774 for d, m2 in t2._dirs.iteritems():
742 if d not in t1._dirs:
775 if d not in t1._dirs:
743 _diff(emptytree, m2)
776 _diff(emptytree, m2)
744
777
745 for fn, n1 in t1._files.iteritems():
778 for fn, n1 in t1._files.iteritems():
746 fl1 = t1._flags.get(fn, '')
779 fl1 = t1._flags.get(fn, '')
747 n2 = t2._files.get(fn, None)
780 n2 = t2._files.get(fn, None)
748 fl2 = t2._flags.get(fn, '')
781 fl2 = t2._flags.get(fn, '')
749 if n1 != n2 or fl1 != fl2:
782 if n1 != n2 or fl1 != fl2:
750 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
783 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
751 elif clean:
784 elif clean:
752 result[t1._subpath(fn)] = None
785 result[t1._subpath(fn)] = None
753
786
754 for fn, n2 in t2._files.iteritems():
787 for fn, n2 in t2._files.iteritems():
755 if fn not in t1._files:
788 if fn not in t1._files:
756 fl2 = t2._flags.get(fn, '')
789 fl2 = t2._flags.get(fn, '')
757 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
790 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
758
791
759 _diff(self, m2)
792 _diff(self, m2)
760 return result
793 return result
761
794
762 def unmodifiedsince(self, m2):
795 def unmodifiedsince(self, m2):
763 return not self._dirty and not m2._dirty and self._node == m2._node
796 return not self._dirty and not m2._dirty and self._node == m2._node
764
797
765 def parse(self, text, readsubtree):
798 def parse(self, text, readsubtree):
766 for f, n, fl in _parse(text):
799 for f, n, fl in _parse(text):
767 if fl == 'd':
800 if fl == 'd':
768 f = f + '/'
801 f = f + '/'
769 self._dirs[f] = readsubtree(self._subpath(f), n)
802 self._dirs[f] = readsubtree(self._subpath(f), n)
770 elif '/' in f:
803 elif '/' in f:
771 # This is a flat manifest, so use __setitem__ and setflag rather
804 # This is a flat manifest, so use __setitem__ and setflag rather
772 # than assigning directly to _files and _flags, so we can
805 # than assigning directly to _files and _flags, so we can
773 # assign a path in a subdirectory, and to mark dirty (compared
806 # assign a path in a subdirectory, and to mark dirty (compared
774 # to nullid).
807 # to nullid).
775 self[f] = n
808 self[f] = n
776 if fl:
809 if fl:
777 self.setflag(f, fl)
810 self.setflag(f, fl)
778 else:
811 else:
779 # Assigning to _files and _flags avoids marking as dirty,
812 # Assigning to _files and _flags avoids marking as dirty,
780 # and should be a little faster.
813 # and should be a little faster.
781 self._files[f] = n
814 self._files[f] = n
782 if fl:
815 if fl:
783 self._flags[f] = fl
816 self._flags[f] = fl
784
817
785 def text(self, usemanifestv2=False):
818 def text(self, usemanifestv2=False):
786 """Get the full data of this manifest as a bytestring."""
819 """Get the full data of this manifest as a bytestring."""
820 self._load()
787 flags = self.flags
821 flags = self.flags
788 return _text(((f, self[f], flags(f)) for f in self.keys()),
822 return _text(((f, self[f], flags(f)) for f in self.keys()),
789 usemanifestv2)
823 usemanifestv2)
790
824
791 def dirtext(self, usemanifestv2=False):
825 def dirtext(self, usemanifestv2=False):
792 """Get the full data of this directory as a bytestring. Make sure that
826 """Get the full data of this directory as a bytestring. Make sure that
793 any submanifests have been written first, so their nodeids are correct.
827 any submanifests have been written first, so their nodeids are correct.
794 """
828 """
829 self._load()
795 flags = self.flags
830 flags = self.flags
796 dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs]
831 dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs]
797 files = [(f, self._files[f], flags(f)) for f in self._files]
832 files = [(f, self._files[f], flags(f)) for f in self._files]
798 return _text(sorted(dirs + files), usemanifestv2)
833 return _text(sorted(dirs + files), usemanifestv2)
799
834
835 def read(self, gettext, readsubtree):
836 def _load():
837 # Mark as loaded already here, so __setitem__ and setflag() don't
838 # cause infinite loops when they try to load.
839 self._load = _noop
840 self.parse(gettext(), readsubtree)
841 self._dirty = False
842 self._load = _load
843
800 def writesubtrees(self, m1, m2, writesubtree):
844 def writesubtrees(self, m1, m2, writesubtree):
845 self._load() # for consistency; should never have any effect here
801 emptytree = treemanifest()
846 emptytree = treemanifest()
802 for d, subm in self._dirs.iteritems():
847 for d, subm in self._dirs.iteritems():
803 subp1 = m1._dirs.get(d, emptytree)._node
848 subp1 = m1._dirs.get(d, emptytree)._node
804 subp2 = m2._dirs.get(d, emptytree)._node
849 subp2 = m2._dirs.get(d, emptytree)._node
805 if subp1 == revlog.nullid:
850 if subp1 == revlog.nullid:
806 subp1, subp2 = subp2, subp1
851 subp1, subp2 = subp2, subp1
807 writesubtree(subm, subp1, subp2)
852 writesubtree(subm, subp1, subp2)
808
853
809 class manifest(revlog.revlog):
854 class manifest(revlog.revlog):
810 def __init__(self, opener, dir='', dirlogcache=None):
855 def __init__(self, opener, dir='', dirlogcache=None):
811 '''The 'dir' and 'dirlogcache' arguments are for internal use by
856 '''The 'dir' and 'dirlogcache' arguments are for internal use by
812 manifest.manifest only. External users should create a root manifest
857 manifest.manifest only. External users should create a root manifest
813 log with manifest.manifest(opener) and call dirlog() on it.
858 log with manifest.manifest(opener) and call dirlog() on it.
814 '''
859 '''
815 # During normal operations, we expect to deal with not more than four
860 # During normal operations, we expect to deal with not more than four
816 # revs at a time (such as during commit --amend). When rebasing large
861 # revs at a time (such as during commit --amend). When rebasing large
817 # stacks of commits, the number can go up, hence the config knob below.
862 # stacks of commits, the number can go up, hence the config knob below.
818 cachesize = 4
863 cachesize = 4
819 usetreemanifest = False
864 usetreemanifest = False
820 usemanifestv2 = False
865 usemanifestv2 = False
821 opts = getattr(opener, 'options', None)
866 opts = getattr(opener, 'options', None)
822 if opts is not None:
867 if opts is not None:
823 cachesize = opts.get('manifestcachesize', cachesize)
868 cachesize = opts.get('manifestcachesize', cachesize)
824 usetreemanifest = opts.get('treemanifest', usetreemanifest)
869 usetreemanifest = opts.get('treemanifest', usetreemanifest)
825 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
870 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
826 self._mancache = util.lrucachedict(cachesize)
871 self._mancache = util.lrucachedict(cachesize)
827 self._treeinmem = usetreemanifest
872 self._treeinmem = usetreemanifest
828 self._treeondisk = usetreemanifest
873 self._treeondisk = usetreemanifest
829 self._usemanifestv2 = usemanifestv2
874 self._usemanifestv2 = usemanifestv2
830 indexfile = "00manifest.i"
875 indexfile = "00manifest.i"
831 if dir:
876 if dir:
832 assert self._treeondisk
877 assert self._treeondisk
833 if not dir.endswith('/'):
878 if not dir.endswith('/'):
834 dir = dir + '/'
879 dir = dir + '/'
835 indexfile = "meta/" + dir + "00manifest.i"
880 indexfile = "meta/" + dir + "00manifest.i"
836 revlog.revlog.__init__(self, opener, indexfile)
881 revlog.revlog.__init__(self, opener, indexfile)
837 self._dir = dir
882 self._dir = dir
838 # The dirlogcache is kept on the root manifest log
883 # The dirlogcache is kept on the root manifest log
839 if dir:
884 if dir:
840 self._dirlogcache = dirlogcache
885 self._dirlogcache = dirlogcache
841 else:
886 else:
842 self._dirlogcache = {'': self}
887 self._dirlogcache = {'': self}
843
888
844 def _newmanifest(self, data=''):
889 def _newmanifest(self, data=''):
845 if self._treeinmem:
890 if self._treeinmem:
846 return treemanifest(self._dir, data)
891 return treemanifest(self._dir, data)
847 return manifestdict(data)
892 return manifestdict(data)
848
893
849 def dirlog(self, dir):
894 def dirlog(self, dir):
850 assert self._treeondisk
895 assert self._treeondisk
851 if dir not in self._dirlogcache:
896 if dir not in self._dirlogcache:
852 self._dirlogcache[dir] = manifest(self.opener, dir,
897 self._dirlogcache[dir] = manifest(self.opener, dir,
853 self._dirlogcache)
898 self._dirlogcache)
854 return self._dirlogcache[dir]
899 return self._dirlogcache[dir]
855
900
856 def _slowreaddelta(self, node):
901 def _slowreaddelta(self, node):
857 r0 = self.deltaparent(self.rev(node))
902 r0 = self.deltaparent(self.rev(node))
858 m0 = self.read(self.node(r0))
903 m0 = self.read(self.node(r0))
859 m1 = self.read(node)
904 m1 = self.read(node)
860 md = self._newmanifest()
905 md = self._newmanifest()
861 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
906 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
862 if n1:
907 if n1:
863 md[f] = n1
908 md[f] = n1
864 if fl1:
909 if fl1:
865 md.setflag(f, fl1)
910 md.setflag(f, fl1)
866 return md
911 return md
867
912
868 def readdelta(self, node):
913 def readdelta(self, node):
869 if self._usemanifestv2 or self._treeondisk:
914 if self._usemanifestv2 or self._treeondisk:
870 return self._slowreaddelta(node)
915 return self._slowreaddelta(node)
871 r = self.rev(node)
916 r = self.rev(node)
872 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
917 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
873 return self._newmanifest(d)
918 return self._newmanifest(d)
874
919
875 def readfast(self, node):
920 def readfast(self, node):
876 '''use the faster of readdelta or read
921 '''use the faster of readdelta or read
877
922
878 This will return a manifest which is either only the files
923 This will return a manifest which is either only the files
879 added/modified relative to p1, or all files in the
924 added/modified relative to p1, or all files in the
880 manifest. Which one is returned depends on the codepath used
925 manifest. Which one is returned depends on the codepath used
881 to retrieve the data.
926 to retrieve the data.
882 '''
927 '''
883 r = self.rev(node)
928 r = self.rev(node)
884 deltaparent = self.deltaparent(r)
929 deltaparent = self.deltaparent(r)
885 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
930 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
886 return self.readdelta(node)
931 return self.readdelta(node)
887 return self.read(node)
932 return self.read(node)
888
933
889 def read(self, node):
934 def read(self, node):
890 if node == revlog.nullid:
935 if node == revlog.nullid:
891 return self._newmanifest() # don't upset local cache
936 return self._newmanifest() # don't upset local cache
892 if node in self._mancache:
937 if node in self._mancache:
893 return self._mancache[node][0]
938 return self._mancache[node][0]
894 text = self.revision(node)
895 if self._treeondisk:
939 if self._treeondisk:
940 def gettext():
941 return self.revision(node)
896 def readsubtree(dir, subm):
942 def readsubtree(dir, subm):
897 return self.dirlog(dir).read(subm)
943 return self.dirlog(dir).read(subm)
898 m = self._newmanifest()
944 m = self._newmanifest()
899 m.parse(text, readsubtree)
945 m.read(gettext, readsubtree)
900 m.setnode(node)
946 m.setnode(node)
901 arraytext = None
947 arraytext = None
902 else:
948 else:
949 text = self.revision(node)
903 m = self._newmanifest(text)
950 m = self._newmanifest(text)
904 arraytext = array.array('c', text)
951 arraytext = array.array('c', text)
905 self._mancache[node] = (m, arraytext)
952 self._mancache[node] = (m, arraytext)
906 return m
953 return m
907
954
908 def find(self, node, f):
955 def find(self, node, f):
909 '''look up entry for a single file efficiently.
956 '''look up entry for a single file efficiently.
910 return (node, flags) pair if found, (None, None) if not.'''
957 return (node, flags) pair if found, (None, None) if not.'''
911 m = self.read(node)
958 m = self.read(node)
912 try:
959 try:
913 return m.find(f)
960 return m.find(f)
914 except KeyError:
961 except KeyError:
915 return None, None
962 return None, None
916
963
917 def add(self, m, transaction, link, p1, p2, added, removed):
964 def add(self, m, transaction, link, p1, p2, added, removed):
918 if (p1 in self._mancache and not self._treeinmem
965 if (p1 in self._mancache and not self._treeinmem
919 and not self._usemanifestv2):
966 and not self._usemanifestv2):
920 # If our first parent is in the manifest cache, we can
967 # If our first parent is in the manifest cache, we can
921 # compute a delta here using properties we know about the
968 # compute a delta here using properties we know about the
922 # manifest up-front, which may save time later for the
969 # manifest up-front, which may save time later for the
923 # revlog layer.
970 # revlog layer.
924
971
925 _checkforbidden(added)
972 _checkforbidden(added)
926 # combine the changed lists into one list for sorting
973 # combine the changed lists into one list for sorting
927 work = [(x, False) for x in added]
974 work = [(x, False) for x in added]
928 work.extend((x, True) for x in removed)
975 work.extend((x, True) for x in removed)
929 # this could use heapq.merge() (from Python 2.6+) or equivalent
976 # this could use heapq.merge() (from Python 2.6+) or equivalent
930 # since the lists are already sorted
977 # since the lists are already sorted
931 work.sort()
978 work.sort()
932
979
933 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
980 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
934 cachedelta = self.rev(p1), deltatext
981 cachedelta = self.rev(p1), deltatext
935 text = util.buffer(arraytext)
982 text = util.buffer(arraytext)
936 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
983 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
937 else:
984 else:
938 # The first parent manifest isn't already loaded, so we'll
985 # The first parent manifest isn't already loaded, so we'll
939 # just encode a fulltext of the manifest and pass that
986 # just encode a fulltext of the manifest and pass that
940 # through to the revlog layer, and let it handle the delta
987 # through to the revlog layer, and let it handle the delta
941 # process.
988 # process.
942 if self._treeondisk:
989 if self._treeondisk:
943 m1 = self.read(p1)
990 m1 = self.read(p1)
944 m2 = self.read(p2)
991 m2 = self.read(p2)
945 n = self._addtree(m, transaction, link, m1, m2)
992 n = self._addtree(m, transaction, link, m1, m2)
946 arraytext = None
993 arraytext = None
947 else:
994 else:
948 text = m.text(self._usemanifestv2)
995 text = m.text(self._usemanifestv2)
949 n = self.addrevision(text, transaction, link, p1, p2)
996 n = self.addrevision(text, transaction, link, p1, p2)
950 arraytext = array.array('c', text)
997 arraytext = array.array('c', text)
951
998
952 self._mancache[n] = (m, arraytext)
999 self._mancache[n] = (m, arraytext)
953
1000
954 return n
1001 return n
955
1002
956 def _addtree(self, m, transaction, link, m1, m2):
1003 def _addtree(self, m, transaction, link, m1, m2):
957 # If the manifest is unchanged compared to one parent,
1004 # If the manifest is unchanged compared to one parent,
958 # don't write a new revision
1005 # don't write a new revision
959 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1006 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
960 return m.node()
1007 return m.node()
961 def writesubtree(subm, subp1, subp2):
1008 def writesubtree(subm, subp1, subp2):
962 sublog = self.dirlog(subm.dir())
1009 sublog = self.dirlog(subm.dir())
963 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1010 sublog.add(subm, transaction, link, subp1, subp2, None, None)
964 m.writesubtrees(m1, m2, writesubtree)
1011 m.writesubtrees(m1, m2, writesubtree)
965 text = m.dirtext(self._usemanifestv2)
1012 text = m.dirtext(self._usemanifestv2)
966 # Double-check whether contents are unchanged to one parent
1013 # Double-check whether contents are unchanged to one parent
967 if text == m1.dirtext(self._usemanifestv2):
1014 if text == m1.dirtext(self._usemanifestv2):
968 n = m1.node()
1015 n = m1.node()
969 elif text == m2.dirtext(self._usemanifestv2):
1016 elif text == m2.dirtext(self._usemanifestv2):
970 n = m2.node()
1017 n = m2.node()
971 else:
1018 else:
972 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1019 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
973 # Save nodeid so parent manifest can calculate its nodeid
1020 # Save nodeid so parent manifest can calculate its nodeid
974 m.setnode(n)
1021 m.setnode(n)
975 return n
1022 return n
@@ -1,278 +1,282
1
1
2 Set up repo
2 Set up repo
3
3
4 $ hg --config experimental.treemanifest=True init repo
4 $ hg --config experimental.treemanifest=True init repo
5 $ cd repo
5 $ cd repo
6
6
7 Requirements get set on init
7 Requirements get set on init
8
8
9 $ grep treemanifest .hg/requires
9 $ grep treemanifest .hg/requires
10 treemanifest
10 treemanifest
11
11
12 Without directories, looks like any other repo
12 Without directories, looks like any other repo
13
13
14 $ echo 0 > a
14 $ echo 0 > a
15 $ echo 0 > b
15 $ echo 0 > b
16 $ hg ci -Aqm initial
16 $ hg ci -Aqm initial
17 $ hg debugdata -m 0
17 $ hg debugdata -m 0
18 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
18 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
19 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
19 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
20
20
21 Submanifest is stored in separate revlog
21 Submanifest is stored in separate revlog
22
22
23 $ mkdir dir1
23 $ mkdir dir1
24 $ echo 1 > dir1/a
24 $ echo 1 > dir1/a
25 $ echo 1 > dir1/b
25 $ echo 1 > dir1/b
26 $ echo 1 > e
26 $ echo 1 > e
27 $ hg ci -Aqm 'add dir1'
27 $ hg ci -Aqm 'add dir1'
28 $ hg debugdata -m 1
28 $ hg debugdata -m 1
29 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
29 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
30 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
30 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
31 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc)
31 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc)
32 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
32 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
33 $ hg debugdata --dir dir1 0
33 $ hg debugdata --dir dir1 0
34 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
34 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
35 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
35 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
36
36
37 Can add nested directories
37 Can add nested directories
38
38
39 $ mkdir dir1/dir1
39 $ mkdir dir1/dir1
40 $ echo 2 > dir1/dir1/a
40 $ echo 2 > dir1/dir1/a
41 $ echo 2 > dir1/dir1/b
41 $ echo 2 > dir1/dir1/b
42 $ mkdir dir1/dir2
42 $ mkdir dir1/dir2
43 $ echo 2 > dir1/dir2/a
43 $ echo 2 > dir1/dir2/a
44 $ echo 2 > dir1/dir2/b
44 $ echo 2 > dir1/dir2/b
45 $ hg ci -Aqm 'add dir1/dir1'
45 $ hg ci -Aqm 'add dir1/dir1'
46 $ hg files -r .
46 $ hg files -r .
47 a
47 a
48 b
48 b
49 dir1/a (glob)
49 dir1/a (glob)
50 dir1/b (glob)
50 dir1/b (glob)
51 dir1/dir1/a (glob)
51 dir1/dir1/a (glob)
52 dir1/dir1/b (glob)
52 dir1/dir1/b (glob)
53 dir1/dir2/a (glob)
53 dir1/dir2/a (glob)
54 dir1/dir2/b (glob)
54 dir1/dir2/b (glob)
55 e
55 e
56
56
57 Revision is not created for unchanged directory
57 Revision is not created for unchanged directory
58
58
59 $ mkdir dir2
59 $ mkdir dir2
60 $ echo 3 > dir2/a
60 $ echo 3 > dir2/a
61 $ hg add dir2
61 $ hg add dir2
62 adding dir2/a (glob)
62 adding dir2/a (glob)
63 $ hg debugindex --dir dir1 > before
63 $ hg debugindex --dir dir1 > before
64 $ hg ci -qm 'add dir2'
64 $ hg ci -qm 'add dir2'
65 $ hg debugindex --dir dir1 > after
65 $ hg debugindex --dir dir1 > after
66 $ diff before after
66 $ diff before after
67 $ rm before after
67 $ rm before after
68
68
69 Removing directory does not create an revlog entry
69 Removing directory does not create an revlog entry
70
70
71 $ hg rm dir1/dir1
71 $ hg rm dir1/dir1
72 removing dir1/dir1/a (glob)
72 removing dir1/dir1/a (glob)
73 removing dir1/dir1/b (glob)
73 removing dir1/dir1/b (glob)
74 $ hg debugindex --dir dir1/dir1 > before
74 $ hg debugindex --dir dir1/dir1 > before
75 $ hg ci -qm 'remove dir1/dir1'
75 $ hg ci -qm 'remove dir1/dir1'
76 $ hg debugindex --dir dir1/dir1 > after
76 $ hg debugindex --dir dir1/dir1 > after
77 $ diff before after
77 $ diff before after
78 $ rm before after
78 $ rm before after
79
79
80 Check that hg files (calls treemanifest.walk()) works
80 Check that hg files (calls treemanifest.walk()) works
81 without loading all directory revlogs
81
82
82 $ hg co 'desc("add dir2")'
83 $ hg co 'desc("add dir2")'
83 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
84 $ hg files -r . dir1
86 $ hg files -r . dir1
85 dir1/a (glob)
87 dir1/a (glob)
86 dir1/b (glob)
88 dir1/b (glob)
87 dir1/dir1/a (glob)
89 dir1/dir1/a (glob)
88 dir1/dir1/b (glob)
90 dir1/dir1/b (glob)
89 dir1/dir2/a (glob)
91 dir1/dir2/a (glob)
90 dir1/dir2/b (glob)
92 dir1/dir2/b (glob)
91
93
92 Check that status between revisions works (calls treemanifest.matches())
94 Check that status between revisions works (calls treemanifest.matches())
95 without loading all directory revlogs
93
96
94 $ hg status --rev 'desc("add dir1")' --rev . dir1
97 $ hg status --rev 'desc("add dir1")' --rev . dir1
95 A dir1/dir1/a
98 A dir1/dir1/a
96 A dir1/dir1/b
99 A dir1/dir1/b
97 A dir1/dir2/a
100 A dir1/dir2/a
98 A dir1/dir2/b
101 A dir1/dir2/b
102 $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
99
103
100 Merge creates 2-parent revision of directory revlog
104 Merge creates 2-parent revision of directory revlog
101
105
102 $ echo 5 > dir1/a
106 $ echo 5 > dir1/a
103 $ hg ci -Aqm 'modify dir1/a'
107 $ hg ci -Aqm 'modify dir1/a'
104 $ hg co '.^'
108 $ hg co '.^'
105 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 $ echo 6 > dir1/b
110 $ echo 6 > dir1/b
107 $ hg ci -Aqm 'modify dir1/b'
111 $ hg ci -Aqm 'modify dir1/b'
108 $ hg merge 'desc("modify dir1/a")'
112 $ hg merge 'desc("modify dir1/a")'
109 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 (branch merge, don't forget to commit)
114 (branch merge, don't forget to commit)
111 $ hg ci -m 'conflict-free merge involving dir1/'
115 $ hg ci -m 'conflict-free merge involving dir1/'
112 $ cat dir1/a
116 $ cat dir1/a
113 5
117 5
114 $ cat dir1/b
118 $ cat dir1/b
115 6
119 6
116 $ hg debugindex --dir dir1
120 $ hg debugindex --dir dir1
117 rev offset length base linkrev nodeid p1 p2
121 rev offset length base linkrev nodeid p1 p2
118 0 0 54 0 1 8b3ffd73f901 000000000000 000000000000
122 0 0 54 0 1 8b3ffd73f901 000000000000 000000000000
119 1 54 68 0 2 b66d046c644f 8b3ffd73f901 000000000000
123 1 54 68 0 2 b66d046c644f 8b3ffd73f901 000000000000
120 2 122 12 0 4 b87265673c8a b66d046c644f 000000000000
124 2 122 12 0 4 b87265673c8a b66d046c644f 000000000000
121 3 134 95 0 5 aa5d3adcec72 b66d046c644f 000000000000
125 3 134 95 0 5 aa5d3adcec72 b66d046c644f 000000000000
122 4 229 81 0 6 e29b066b91ad b66d046c644f 000000000000
126 4 229 81 0 6 e29b066b91ad b66d046c644f 000000000000
123 5 310 107 5 7 a120ce2b83f5 e29b066b91ad aa5d3adcec72
127 5 310 107 5 7 a120ce2b83f5 e29b066b91ad aa5d3adcec72
124
128
125 Merge keeping directory from parent 1 does not create revlog entry. (Note that
129 Merge keeping directory from parent 1 does not create revlog entry. (Note that
126 dir1's manifest does change, but only because dir1/a's filelog changes.)
130 dir1's manifest does change, but only because dir1/a's filelog changes.)
127
131
128 $ hg co 'desc("add dir2")'
132 $ hg co 'desc("add dir2")'
129 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
130 $ echo 8 > dir2/a
134 $ echo 8 > dir2/a
131 $ hg ci -m 'modify dir2/a'
135 $ hg ci -m 'modify dir2/a'
132 created new head
136 created new head
133
137
134 $ hg debugindex --dir dir2 > before
138 $ hg debugindex --dir dir2 > before
135 $ hg merge 'desc("modify dir1/a")'
139 $ hg merge 'desc("modify dir1/a")'
136 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 (branch merge, don't forget to commit)
141 (branch merge, don't forget to commit)
138 $ hg revert -r 'desc("modify dir2/a")' .
142 $ hg revert -r 'desc("modify dir2/a")' .
139 reverting dir1/a (glob)
143 reverting dir1/a (glob)
140 $ hg ci -m 'merge, keeping parent 1'
144 $ hg ci -m 'merge, keeping parent 1'
141 $ hg debugindex --dir dir2 > after
145 $ hg debugindex --dir dir2 > after
142 $ diff before after
146 $ diff before after
143 $ rm before after
147 $ rm before after
144
148
145 Merge keeping directory from parent 2 does not create revlog entry. (Note that
149 Merge keeping directory from parent 2 does not create revlog entry. (Note that
146 dir2's manifest does change, but only because dir2/a's filelog changes.)
150 dir2's manifest does change, but only because dir2/a's filelog changes.)
147
151
148 $ hg co 'desc("modify dir2/a")'
152 $ hg co 'desc("modify dir2/a")'
149 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 $ hg debugindex --dir dir1 > before
154 $ hg debugindex --dir dir1 > before
151 $ hg merge 'desc("modify dir1/a")'
155 $ hg merge 'desc("modify dir1/a")'
152 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 (branch merge, don't forget to commit)
157 (branch merge, don't forget to commit)
154 $ hg revert -r 'desc("modify dir1/a")' .
158 $ hg revert -r 'desc("modify dir1/a")' .
155 reverting dir2/a (glob)
159 reverting dir2/a (glob)
156 $ hg ci -m 'merge, keeping parent 2'
160 $ hg ci -m 'merge, keeping parent 2'
157 created new head
161 created new head
158 $ hg debugindex --dir dir1 > after
162 $ hg debugindex --dir dir1 > after
159 $ diff before after
163 $ diff before after
160 $ rm before after
164 $ rm before after
161
165
162 Create flat source repo for tests with mixed flat/tree manifests
166 Create flat source repo for tests with mixed flat/tree manifests
163
167
164 $ cd ..
168 $ cd ..
165 $ hg init repo-flat
169 $ hg init repo-flat
166 $ cd repo-flat
170 $ cd repo-flat
167
171
168 Create a few commits with flat manifest
172 Create a few commits with flat manifest
169
173
170 $ echo 0 > a
174 $ echo 0 > a
171 $ echo 0 > b
175 $ echo 0 > b
172 $ echo 0 > e
176 $ echo 0 > e
173 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
177 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
174 > do
178 > do
175 > mkdir $d
179 > mkdir $d
176 > echo 0 > $d/a
180 > echo 0 > $d/a
177 > echo 0 > $d/b
181 > echo 0 > $d/b
178 > done
182 > done
179 $ hg ci -Aqm initial
183 $ hg ci -Aqm initial
180
184
181 $ echo 1 > a
185 $ echo 1 > a
182 $ echo 1 > dir1/a
186 $ echo 1 > dir1/a
183 $ echo 1 > dir1/dir1/a
187 $ echo 1 > dir1/dir1/a
184 $ hg ci -Aqm 'modify on branch 1'
188 $ hg ci -Aqm 'modify on branch 1'
185
189
186 $ hg co 0
190 $ hg co 0
187 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
188 $ echo 2 > b
192 $ echo 2 > b
189 $ echo 2 > dir1/b
193 $ echo 2 > dir1/b
190 $ echo 2 > dir1/dir1/b
194 $ echo 2 > dir1/dir1/b
191 $ hg ci -Aqm 'modify on branch 2'
195 $ hg ci -Aqm 'modify on branch 2'
192
196
193 $ hg merge 1
197 $ hg merge 1
194 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
198 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 (branch merge, don't forget to commit)
199 (branch merge, don't forget to commit)
196 $ hg ci -m 'merge of flat manifests to new flat manifest'
200 $ hg ci -m 'merge of flat manifests to new flat manifest'
197
201
198 Create clone with tree manifests enabled
202 Create clone with tree manifests enabled
199
203
200 $ cd ..
204 $ cd ..
201 $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed
205 $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed
202 requesting all changes
206 requesting all changes
203 adding changesets
207 adding changesets
204 adding manifests
208 adding manifests
205 adding file changes
209 adding file changes
206 added 4 changesets with 17 changes to 11 files
210 added 4 changesets with 17 changes to 11 files
207 updating to branch default
211 updating to branch default
208 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
212 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
209 $ cd repo-mixed
213 $ cd repo-mixed
210 $ test -f .hg/store/meta
214 $ test -f .hg/store/meta
211 [1]
215 [1]
212 $ grep treemanifest .hg/requires
216 $ grep treemanifest .hg/requires
213 treemanifest
217 treemanifest
214
218
215 Commit should store revlog per directory
219 Commit should store revlog per directory
216
220
217 $ hg co 1
221 $ hg co 1
218 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
219 $ echo 3 > a
223 $ echo 3 > a
220 $ echo 3 > dir1/a
224 $ echo 3 > dir1/a
221 $ echo 3 > dir1/dir1/a
225 $ echo 3 > dir1/dir1/a
222 $ hg ci -m 'first tree'
226 $ hg ci -m 'first tree'
223 created new head
227 created new head
224 $ find .hg/store/meta | sort
228 $ find .hg/store/meta | sort
225 .hg/store/meta
229 .hg/store/meta
226 .hg/store/meta/dir1
230 .hg/store/meta/dir1
227 .hg/store/meta/dir1/00manifest.i
231 .hg/store/meta/dir1/00manifest.i
228 .hg/store/meta/dir1/dir1
232 .hg/store/meta/dir1/dir1
229 .hg/store/meta/dir1/dir1/00manifest.i
233 .hg/store/meta/dir1/dir1/00manifest.i
230 .hg/store/meta/dir1/dir2
234 .hg/store/meta/dir1/dir2
231 .hg/store/meta/dir1/dir2/00manifest.i
235 .hg/store/meta/dir1/dir2/00manifest.i
232 .hg/store/meta/dir2
236 .hg/store/meta/dir2
233 .hg/store/meta/dir2/00manifest.i
237 .hg/store/meta/dir2/00manifest.i
234
238
235 Merge of two trees
239 Merge of two trees
236
240
237 $ hg co 2
241 $ hg co 2
238 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
242 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
239 $ hg merge 1
243 $ hg merge 1
240 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
241 (branch merge, don't forget to commit)
245 (branch merge, don't forget to commit)
242 $ hg ci -m 'merge of flat manifests to new tree manifest'
246 $ hg ci -m 'merge of flat manifests to new tree manifest'
243 created new head
247 created new head
244 $ hg diff -r 3
248 $ hg diff -r 3
245
249
246 Parent of tree root manifest should be flat manifest, and two for merge
250 Parent of tree root manifest should be flat manifest, and two for merge
247
251
248 $ hg debugindex -m
252 $ hg debugindex -m
249 rev offset length base linkrev nodeid p1 p2
253 rev offset length base linkrev nodeid p1 p2
250 0 0 80 0 0 40536115ed9e 000000000000 000000000000
254 0 0 80 0 0 40536115ed9e 000000000000 000000000000
251 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
255 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
252 2 163 103 0 2 5d9b9da231a2 40536115ed9e 000000000000
256 2 163 103 0 2 5d9b9da231a2 40536115ed9e 000000000000
253 3 266 83 0 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
257 3 266 83 0 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
254 4 349 132 4 4 c05a51345f86 f3376063c255 000000000000
258 4 349 132 4 4 c05a51345f86 f3376063c255 000000000000
255 5 481 110 4 5 82594b1f557d 5d9b9da231a2 f3376063c255
259 5 481 110 4 5 82594b1f557d 5d9b9da231a2 f3376063c255
256
260
257
261
258 Status across flat/tree boundary should work
262 Status across flat/tree boundary should work
259
263
260 $ hg status --rev '.^' --rev .
264 $ hg status --rev '.^' --rev .
261 M a
265 M a
262 M dir1/a
266 M dir1/a
263 M dir1/dir1/a
267 M dir1/dir1/a
264
268
265
269
266 Turning off treemanifest config has no effect
270 Turning off treemanifest config has no effect
267
271
268 $ hg debugindex .hg/store/meta/dir1/00manifest.i
272 $ hg debugindex .hg/store/meta/dir1/00manifest.i
269 rev offset length base linkrev nodeid p1 p2
273 rev offset length base linkrev nodeid p1 p2
270 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
274 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
271 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
275 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
272 $ echo 2 > dir1/a
276 $ echo 2 > dir1/a
273 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
277 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
274 $ hg debugindex .hg/store/meta/dir1/00manifest.i
278 $ hg debugindex .hg/store/meta/dir1/00manifest.i
275 rev offset length base linkrev nodeid p1 p2
279 rev offset length base linkrev nodeid p1 p2
276 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
280 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
277 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
281 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
278 2 234 55 0 6 3cb2d87b4250 23d12a1f6e0e 000000000000
282 2 234 55 0 6 3cb2d87b4250 23d12a1f6e0e 000000000000
General Comments 0
You need to be logged in to leave comments. Login now