##// END OF EJS Templates
verify: check directory manifests...
Martin von Zweigbergk -
r28203:7297e9e1 default
parent child Browse files
Show More
@@ -1,1052 +1,1072 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class _lazymanifest(dict):
107 class _lazymanifest(dict):
108 """This is the pure implementation of lazymanifest.
108 """This is the pure implementation of lazymanifest.
109
109
110 It has not been optimized *at all* and is not lazy.
110 It has not been optimized *at all* and is not lazy.
111 """
111 """
112
112
113 def __init__(self, data):
113 def __init__(self, data):
114 dict.__init__(self)
114 dict.__init__(self)
115 for f, n, fl in _parse(data):
115 for f, n, fl in _parse(data):
116 self[f] = n, fl
116 self[f] = n, fl
117
117
118 def __setitem__(self, k, v):
118 def __setitem__(self, k, v):
119 node, flag = v
119 node, flag = v
120 assert node is not None
120 assert node is not None
121 if len(node) > 21:
121 if len(node) > 21:
122 node = node[:21] # match c implementation behavior
122 node = node[:21] # match c implementation behavior
123 dict.__setitem__(self, k, (node, flag))
123 dict.__setitem__(self, k, (node, flag))
124
124
125 def __iter__(self):
125 def __iter__(self):
126 return iter(sorted(dict.keys(self)))
126 return iter(sorted(dict.keys(self)))
127
127
128 def iterkeys(self):
128 def iterkeys(self):
129 return iter(sorted(dict.keys(self)))
129 return iter(sorted(dict.keys(self)))
130
130
131 def iterentries(self):
131 def iterentries(self):
132 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
132 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
133
133
134 def copy(self):
134 def copy(self):
135 c = _lazymanifest('')
135 c = _lazymanifest('')
136 c.update(self)
136 c.update(self)
137 return c
137 return c
138
138
139 def diff(self, m2, clean=False):
139 def diff(self, m2, clean=False):
140 '''Finds changes between the current manifest and m2.'''
140 '''Finds changes between the current manifest and m2.'''
141 diff = {}
141 diff = {}
142
142
143 for fn, e1 in self.iteritems():
143 for fn, e1 in self.iteritems():
144 if fn not in m2:
144 if fn not in m2:
145 diff[fn] = e1, (None, '')
145 diff[fn] = e1, (None, '')
146 else:
146 else:
147 e2 = m2[fn]
147 e2 = m2[fn]
148 if e1 != e2:
148 if e1 != e2:
149 diff[fn] = e1, e2
149 diff[fn] = e1, e2
150 elif clean:
150 elif clean:
151 diff[fn] = None
151 diff[fn] = None
152
152
153 for fn, e2 in m2.iteritems():
153 for fn, e2 in m2.iteritems():
154 if fn not in self:
154 if fn not in self:
155 diff[fn] = (None, ''), e2
155 diff[fn] = (None, ''), e2
156
156
157 return diff
157 return diff
158
158
159 def filtercopy(self, filterfn):
159 def filtercopy(self, filterfn):
160 c = _lazymanifest('')
160 c = _lazymanifest('')
161 for f, n, fl in self.iterentries():
161 for f, n, fl in self.iterentries():
162 if filterfn(f):
162 if filterfn(f):
163 c[f] = n, fl
163 c[f] = n, fl
164 return c
164 return c
165
165
166 def text(self):
166 def text(self):
167 """Get the full data of this manifest as a bytestring."""
167 """Get the full data of this manifest as a bytestring."""
168 return _textv1(self.iterentries())
168 return _textv1(self.iterentries())
169
169
170 try:
170 try:
171 _lazymanifest = parsers.lazymanifest
171 _lazymanifest = parsers.lazymanifest
172 except AttributeError:
172 except AttributeError:
173 pass
173 pass
174
174
175 class manifestdict(object):
175 class manifestdict(object):
176 def __init__(self, data=''):
176 def __init__(self, data=''):
177 if data.startswith('\0'):
177 if data.startswith('\0'):
178 #_lazymanifest can not parse v2
178 #_lazymanifest can not parse v2
179 self._lm = _lazymanifest('')
179 self._lm = _lazymanifest('')
180 for f, n, fl in _parsev2(data):
180 for f, n, fl in _parsev2(data):
181 self._lm[f] = n, fl
181 self._lm[f] = n, fl
182 else:
182 else:
183 self._lm = _lazymanifest(data)
183 self._lm = _lazymanifest(data)
184
184
185 def __getitem__(self, key):
185 def __getitem__(self, key):
186 return self._lm[key][0]
186 return self._lm[key][0]
187
187
188 def find(self, key):
188 def find(self, key):
189 return self._lm[key]
189 return self._lm[key]
190
190
191 def __len__(self):
191 def __len__(self):
192 return len(self._lm)
192 return len(self._lm)
193
193
194 def __setitem__(self, key, node):
194 def __setitem__(self, key, node):
195 self._lm[key] = node, self.flags(key, '')
195 self._lm[key] = node, self.flags(key, '')
196
196
197 def __contains__(self, key):
197 def __contains__(self, key):
198 return key in self._lm
198 return key in self._lm
199
199
200 def __delitem__(self, key):
200 def __delitem__(self, key):
201 del self._lm[key]
201 del self._lm[key]
202
202
203 def __iter__(self):
203 def __iter__(self):
204 return self._lm.__iter__()
204 return self._lm.__iter__()
205
205
206 def iterkeys(self):
206 def iterkeys(self):
207 return self._lm.iterkeys()
207 return self._lm.iterkeys()
208
208
209 def keys(self):
209 def keys(self):
210 return list(self.iterkeys())
210 return list(self.iterkeys())
211
211
212 def filesnotin(self, m2):
212 def filesnotin(self, m2):
213 '''Set of files in this manifest that are not in the other'''
213 '''Set of files in this manifest that are not in the other'''
214 files = set(self)
214 files = set(self)
215 files.difference_update(m2)
215 files.difference_update(m2)
216 return files
216 return files
217
217
218 @propertycache
218 @propertycache
219 def _dirs(self):
219 def _dirs(self):
220 return util.dirs(self)
220 return util.dirs(self)
221
221
222 def dirs(self):
222 def dirs(self):
223 return self._dirs
223 return self._dirs
224
224
225 def hasdir(self, dir):
225 def hasdir(self, dir):
226 return dir in self._dirs
226 return dir in self._dirs
227
227
228 def _filesfastpath(self, match):
228 def _filesfastpath(self, match):
229 '''Checks whether we can correctly and quickly iterate over matcher
229 '''Checks whether we can correctly and quickly iterate over matcher
230 files instead of over manifest files.'''
230 files instead of over manifest files.'''
231 files = match.files()
231 files = match.files()
232 return (len(files) < 100 and (match.isexact() or
232 return (len(files) < 100 and (match.isexact() or
233 (match.prefix() and all(fn in self for fn in files))))
233 (match.prefix() and all(fn in self for fn in files))))
234
234
235 def walk(self, match):
235 def walk(self, match):
236 '''Generates matching file names.
236 '''Generates matching file names.
237
237
238 Equivalent to manifest.matches(match).iterkeys(), but without creating
238 Equivalent to manifest.matches(match).iterkeys(), but without creating
239 an entirely new manifest.
239 an entirely new manifest.
240
240
241 It also reports nonexistent files by marking them bad with match.bad().
241 It also reports nonexistent files by marking them bad with match.bad().
242 '''
242 '''
243 if match.always():
243 if match.always():
244 for f in iter(self):
244 for f in iter(self):
245 yield f
245 yield f
246 return
246 return
247
247
248 fset = set(match.files())
248 fset = set(match.files())
249
249
250 # avoid the entire walk if we're only looking for specific files
250 # avoid the entire walk if we're only looking for specific files
251 if self._filesfastpath(match):
251 if self._filesfastpath(match):
252 for fn in sorted(fset):
252 for fn in sorted(fset):
253 yield fn
253 yield fn
254 return
254 return
255
255
256 for fn in self:
256 for fn in self:
257 if fn in fset:
257 if fn in fset:
258 # specified pattern is the exact name
258 # specified pattern is the exact name
259 fset.remove(fn)
259 fset.remove(fn)
260 if match(fn):
260 if match(fn):
261 yield fn
261 yield fn
262
262
263 # for dirstate.walk, files=['.'] means "walk the whole tree".
263 # for dirstate.walk, files=['.'] means "walk the whole tree".
264 # follow that here, too
264 # follow that here, too
265 fset.discard('.')
265 fset.discard('.')
266
266
267 for fn in sorted(fset):
267 for fn in sorted(fset):
268 if not self.hasdir(fn):
268 if not self.hasdir(fn):
269 match.bad(fn, None)
269 match.bad(fn, None)
270
270
271 def matches(self, match):
271 def matches(self, match):
272 '''generate a new manifest filtered by the match argument'''
272 '''generate a new manifest filtered by the match argument'''
273 if match.always():
273 if match.always():
274 return self.copy()
274 return self.copy()
275
275
276 if self._filesfastpath(match):
276 if self._filesfastpath(match):
277 m = manifestdict()
277 m = manifestdict()
278 lm = self._lm
278 lm = self._lm
279 for fn in match.files():
279 for fn in match.files():
280 if fn in lm:
280 if fn in lm:
281 m._lm[fn] = lm[fn]
281 m._lm[fn] = lm[fn]
282 return m
282 return m
283
283
284 m = manifestdict()
284 m = manifestdict()
285 m._lm = self._lm.filtercopy(match)
285 m._lm = self._lm.filtercopy(match)
286 return m
286 return m
287
287
288 def diff(self, m2, clean=False):
288 def diff(self, m2, clean=False):
289 '''Finds changes between the current manifest and m2.
289 '''Finds changes between the current manifest and m2.
290
290
291 Args:
291 Args:
292 m2: the manifest to which this manifest should be compared.
292 m2: the manifest to which this manifest should be compared.
293 clean: if true, include files unchanged between these manifests
293 clean: if true, include files unchanged between these manifests
294 with a None value in the returned dictionary.
294 with a None value in the returned dictionary.
295
295
296 The result is returned as a dict with filename as key and
296 The result is returned as a dict with filename as key and
297 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
297 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
298 nodeid in the current/other manifest and fl1/fl2 is the flag
298 nodeid in the current/other manifest and fl1/fl2 is the flag
299 in the current/other manifest. Where the file does not exist,
299 in the current/other manifest. Where the file does not exist,
300 the nodeid will be None and the flags will be the empty
300 the nodeid will be None and the flags will be the empty
301 string.
301 string.
302 '''
302 '''
303 return self._lm.diff(m2._lm, clean)
303 return self._lm.diff(m2._lm, clean)
304
304
305 def setflag(self, key, flag):
305 def setflag(self, key, flag):
306 self._lm[key] = self[key], flag
306 self._lm[key] = self[key], flag
307
307
308 def get(self, key, default=None):
308 def get(self, key, default=None):
309 try:
309 try:
310 return self._lm[key][0]
310 return self._lm[key][0]
311 except KeyError:
311 except KeyError:
312 return default
312 return default
313
313
314 def flags(self, key, default=''):
314 def flags(self, key, default=''):
315 try:
315 try:
316 return self._lm[key][1]
316 return self._lm[key][1]
317 except KeyError:
317 except KeyError:
318 return default
318 return default
319
319
320 def copy(self):
320 def copy(self):
321 c = manifestdict()
321 c = manifestdict()
322 c._lm = self._lm.copy()
322 c._lm = self._lm.copy()
323 return c
323 return c
324
324
325 def iteritems(self):
325 def iteritems(self):
326 return (x[:2] for x in self._lm.iterentries())
326 return (x[:2] for x in self._lm.iterentries())
327
327
328 def iterentries(self):
329 return self._lm.iterentries()
330
328 def text(self, usemanifestv2=False):
331 def text(self, usemanifestv2=False):
329 if usemanifestv2:
332 if usemanifestv2:
330 return _textv2(self._lm.iterentries())
333 return _textv2(self._lm.iterentries())
331 else:
334 else:
332 # use (probably) native version for v1
335 # use (probably) native version for v1
333 return self._lm.text()
336 return self._lm.text()
334
337
335 def fastdelta(self, base, changes):
338 def fastdelta(self, base, changes):
336 """Given a base manifest text as an array.array and a list of changes
339 """Given a base manifest text as an array.array and a list of changes
337 relative to that text, compute a delta that can be used by revlog.
340 relative to that text, compute a delta that can be used by revlog.
338 """
341 """
339 delta = []
342 delta = []
340 dstart = None
343 dstart = None
341 dend = None
344 dend = None
342 dline = [""]
345 dline = [""]
343 start = 0
346 start = 0
344 # zero copy representation of base as a buffer
347 # zero copy representation of base as a buffer
345 addbuf = util.buffer(base)
348 addbuf = util.buffer(base)
346
349
347 changes = list(changes)
350 changes = list(changes)
348 if len(changes) < 1000:
351 if len(changes) < 1000:
349 # start with a readonly loop that finds the offset of
352 # start with a readonly loop that finds the offset of
350 # each line and creates the deltas
353 # each line and creates the deltas
351 for f, todelete in changes:
354 for f, todelete in changes:
352 # bs will either be the index of the item or the insert point
355 # bs will either be the index of the item or the insert point
353 start, end = _msearch(addbuf, f, start)
356 start, end = _msearch(addbuf, f, start)
354 if not todelete:
357 if not todelete:
355 h, fl = self._lm[f]
358 h, fl = self._lm[f]
356 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
359 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
357 else:
360 else:
358 if start == end:
361 if start == end:
359 # item we want to delete was not found, error out
362 # item we want to delete was not found, error out
360 raise AssertionError(
363 raise AssertionError(
361 _("failed to remove %s from manifest") % f)
364 _("failed to remove %s from manifest") % f)
362 l = ""
365 l = ""
363 if dstart is not None and dstart <= start and dend >= start:
366 if dstart is not None and dstart <= start and dend >= start:
364 if dend < end:
367 if dend < end:
365 dend = end
368 dend = end
366 if l:
369 if l:
367 dline.append(l)
370 dline.append(l)
368 else:
371 else:
369 if dstart is not None:
372 if dstart is not None:
370 delta.append([dstart, dend, "".join(dline)])
373 delta.append([dstart, dend, "".join(dline)])
371 dstart = start
374 dstart = start
372 dend = end
375 dend = end
373 dline = [l]
376 dline = [l]
374
377
375 if dstart is not None:
378 if dstart is not None:
376 delta.append([dstart, dend, "".join(dline)])
379 delta.append([dstart, dend, "".join(dline)])
377 # apply the delta to the base, and get a delta for addrevision
380 # apply the delta to the base, and get a delta for addrevision
378 deltatext, arraytext = _addlistdelta(base, delta)
381 deltatext, arraytext = _addlistdelta(base, delta)
379 else:
382 else:
380 # For large changes, it's much cheaper to just build the text and
383 # For large changes, it's much cheaper to just build the text and
381 # diff it.
384 # diff it.
382 arraytext = array.array('c', self.text())
385 arraytext = array.array('c', self.text())
383 deltatext = mdiff.textdiff(base, arraytext)
386 deltatext = mdiff.textdiff(base, arraytext)
384
387
385 return arraytext, deltatext
388 return arraytext, deltatext
386
389
387 def _msearch(m, s, lo=0, hi=None):
390 def _msearch(m, s, lo=0, hi=None):
388 '''return a tuple (start, end) that says where to find s within m.
391 '''return a tuple (start, end) that says where to find s within m.
389
392
390 If the string is found m[start:end] are the line containing
393 If the string is found m[start:end] are the line containing
391 that string. If start == end the string was not found and
394 that string. If start == end the string was not found and
392 they indicate the proper sorted insertion point.
395 they indicate the proper sorted insertion point.
393
396
394 m should be a buffer or a string
397 m should be a buffer or a string
395 s is a string'''
398 s is a string'''
396 def advance(i, c):
399 def advance(i, c):
397 while i < lenm and m[i] != c:
400 while i < lenm and m[i] != c:
398 i += 1
401 i += 1
399 return i
402 return i
400 if not s:
403 if not s:
401 return (lo, lo)
404 return (lo, lo)
402 lenm = len(m)
405 lenm = len(m)
403 if not hi:
406 if not hi:
404 hi = lenm
407 hi = lenm
405 while lo < hi:
408 while lo < hi:
406 mid = (lo + hi) // 2
409 mid = (lo + hi) // 2
407 start = mid
410 start = mid
408 while start > 0 and m[start - 1] != '\n':
411 while start > 0 and m[start - 1] != '\n':
409 start -= 1
412 start -= 1
410 end = advance(start, '\0')
413 end = advance(start, '\0')
411 if m[start:end] < s:
414 if m[start:end] < s:
412 # we know that after the null there are 40 bytes of sha1
415 # we know that after the null there are 40 bytes of sha1
413 # this translates to the bisect lo = mid + 1
416 # this translates to the bisect lo = mid + 1
414 lo = advance(end + 40, '\n') + 1
417 lo = advance(end + 40, '\n') + 1
415 else:
418 else:
416 # this translates to the bisect hi = mid
419 # this translates to the bisect hi = mid
417 hi = start
420 hi = start
418 end = advance(lo, '\0')
421 end = advance(lo, '\0')
419 found = m[lo:end]
422 found = m[lo:end]
420 if s == found:
423 if s == found:
421 # we know that after the null there are 40 bytes of sha1
424 # we know that after the null there are 40 bytes of sha1
422 end = advance(end + 40, '\n')
425 end = advance(end + 40, '\n')
423 return (lo, end + 1)
426 return (lo, end + 1)
424 else:
427 else:
425 return (lo, lo)
428 return (lo, lo)
426
429
427 def _checkforbidden(l):
430 def _checkforbidden(l):
428 """Check filenames for illegal characters."""
431 """Check filenames for illegal characters."""
429 for f in l:
432 for f in l:
430 if '\n' in f or '\r' in f:
433 if '\n' in f or '\r' in f:
431 raise error.RevlogError(
434 raise error.RevlogError(
432 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
435 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
433
436
434
437
435 # apply the changes collected during the bisect loop to our addlist
438 # apply the changes collected during the bisect loop to our addlist
436 # return a delta suitable for addrevision
439 # return a delta suitable for addrevision
437 def _addlistdelta(addlist, x):
440 def _addlistdelta(addlist, x):
438 # for large addlist arrays, building a new array is cheaper
441 # for large addlist arrays, building a new array is cheaper
439 # than repeatedly modifying the existing one
442 # than repeatedly modifying the existing one
440 currentposition = 0
443 currentposition = 0
441 newaddlist = array.array('c')
444 newaddlist = array.array('c')
442
445
443 for start, end, content in x:
446 for start, end, content in x:
444 newaddlist += addlist[currentposition:start]
447 newaddlist += addlist[currentposition:start]
445 if content:
448 if content:
446 newaddlist += array.array('c', content)
449 newaddlist += array.array('c', content)
447
450
448 currentposition = end
451 currentposition = end
449
452
450 newaddlist += addlist[currentposition:]
453 newaddlist += addlist[currentposition:]
451
454
452 deltatext = "".join(struct.pack(">lll", start, end, len(content))
455 deltatext = "".join(struct.pack(">lll", start, end, len(content))
453 + content for start, end, content in x)
456 + content for start, end, content in x)
454 return deltatext, newaddlist
457 return deltatext, newaddlist
455
458
456 def _splittopdir(f):
459 def _splittopdir(f):
457 if '/' in f:
460 if '/' in f:
458 dir, subpath = f.split('/', 1)
461 dir, subpath = f.split('/', 1)
459 return dir + '/', subpath
462 return dir + '/', subpath
460 else:
463 else:
461 return '', f
464 return '', f
462
465
463 _noop = lambda s: None
466 _noop = lambda s: None
464
467
465 class treemanifest(object):
468 class treemanifest(object):
466 def __init__(self, dir='', text=''):
469 def __init__(self, dir='', text=''):
467 self._dir = dir
470 self._dir = dir
468 self._node = revlog.nullid
471 self._node = revlog.nullid
469 self._loadfunc = _noop
472 self._loadfunc = _noop
470 self._copyfunc = _noop
473 self._copyfunc = _noop
471 self._dirty = False
474 self._dirty = False
472 self._dirs = {}
475 self._dirs = {}
473 # Using _lazymanifest here is a little slower than plain old dicts
476 # Using _lazymanifest here is a little slower than plain old dicts
474 self._files = {}
477 self._files = {}
475 self._flags = {}
478 self._flags = {}
476 if text:
479 if text:
477 def readsubtree(subdir, subm):
480 def readsubtree(subdir, subm):
478 raise AssertionError('treemanifest constructor only accepts '
481 raise AssertionError('treemanifest constructor only accepts '
479 'flat manifests')
482 'flat manifests')
480 self.parse(text, readsubtree)
483 self.parse(text, readsubtree)
481 self._dirty = True # Mark flat manifest dirty after parsing
484 self._dirty = True # Mark flat manifest dirty after parsing
482
485
483 def _subpath(self, path):
486 def _subpath(self, path):
484 return self._dir + path
487 return self._dir + path
485
488
486 def __len__(self):
489 def __len__(self):
487 self._load()
490 self._load()
488 size = len(self._files)
491 size = len(self._files)
489 for m in self._dirs.values():
492 for m in self._dirs.values():
490 size += m.__len__()
493 size += m.__len__()
491 return size
494 return size
492
495
493 def _isempty(self):
496 def _isempty(self):
494 self._load() # for consistency; already loaded by all callers
497 self._load() # for consistency; already loaded by all callers
495 return (not self._files and (not self._dirs or
498 return (not self._files and (not self._dirs or
496 all(m._isempty() for m in self._dirs.values())))
499 all(m._isempty() for m in self._dirs.values())))
497
500
498 def __repr__(self):
501 def __repr__(self):
499 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
502 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
500 (self._dir, revlog.hex(self._node),
503 (self._dir, revlog.hex(self._node),
501 bool(self._loadfunc is _noop),
504 bool(self._loadfunc is _noop),
502 self._dirty, id(self)))
505 self._dirty, id(self)))
503
506
504 def dir(self):
507 def dir(self):
505 '''The directory that this tree manifest represents, including a
508 '''The directory that this tree manifest represents, including a
506 trailing '/'. Empty string for the repo root directory.'''
509 trailing '/'. Empty string for the repo root directory.'''
507 return self._dir
510 return self._dir
508
511
509 def node(self):
512 def node(self):
510 '''This node of this instance. nullid for unsaved instances. Should
513 '''This node of this instance. nullid for unsaved instances. Should
511 be updated when the instance is read or written from a revlog.
514 be updated when the instance is read or written from a revlog.
512 '''
515 '''
513 assert not self._dirty
516 assert not self._dirty
514 return self._node
517 return self._node
515
518
516 def setnode(self, node):
519 def setnode(self, node):
517 self._node = node
520 self._node = node
518 self._dirty = False
521 self._dirty = False
519
522
520 def iteritems(self):
523 def iteritems(self):
521 self._load()
524 self._load()
522 for p, n in sorted(self._dirs.items() + self._files.items()):
525 for p, n in sorted(self._dirs.items() + self._files.items()):
523 if p in self._files:
526 if p in self._files:
524 yield self._subpath(p), n
527 yield self._subpath(p), n
525 else:
528 else:
526 for f, sn in n.iteritems():
529 for f, sn in n.iteritems():
527 yield f, sn
530 yield f, sn
528
531
529 def iterkeys(self):
532 def iterkeys(self):
530 self._load()
533 self._load()
531 for p in sorted(self._dirs.keys() + self._files.keys()):
534 for p in sorted(self._dirs.keys() + self._files.keys()):
532 if p in self._files:
535 if p in self._files:
533 yield self._subpath(p)
536 yield self._subpath(p)
534 else:
537 else:
535 for f in self._dirs[p].iterkeys():
538 for f in self._dirs[p].iterkeys():
536 yield f
539 yield f
537
540
538 def keys(self):
541 def keys(self):
539 return list(self.iterkeys())
542 return list(self.iterkeys())
540
543
541 def __iter__(self):
544 def __iter__(self):
542 return self.iterkeys()
545 return self.iterkeys()
543
546
544 def __contains__(self, f):
547 def __contains__(self, f):
545 if f is None:
548 if f is None:
546 return False
549 return False
547 self._load()
550 self._load()
548 dir, subpath = _splittopdir(f)
551 dir, subpath = _splittopdir(f)
549 if dir:
552 if dir:
550 if dir not in self._dirs:
553 if dir not in self._dirs:
551 return False
554 return False
552 return self._dirs[dir].__contains__(subpath)
555 return self._dirs[dir].__contains__(subpath)
553 else:
556 else:
554 return f in self._files
557 return f in self._files
555
558
556 def get(self, f, default=None):
559 def get(self, f, default=None):
557 self._load()
560 self._load()
558 dir, subpath = _splittopdir(f)
561 dir, subpath = _splittopdir(f)
559 if dir:
562 if dir:
560 if dir not in self._dirs:
563 if dir not in self._dirs:
561 return default
564 return default
562 return self._dirs[dir].get(subpath, default)
565 return self._dirs[dir].get(subpath, default)
563 else:
566 else:
564 return self._files.get(f, default)
567 return self._files.get(f, default)
565
568
566 def __getitem__(self, f):
569 def __getitem__(self, f):
567 self._load()
570 self._load()
568 dir, subpath = _splittopdir(f)
571 dir, subpath = _splittopdir(f)
569 if dir:
572 if dir:
570 return self._dirs[dir].__getitem__(subpath)
573 return self._dirs[dir].__getitem__(subpath)
571 else:
574 else:
572 return self._files[f]
575 return self._files[f]
573
576
574 def flags(self, f):
577 def flags(self, f):
575 self._load()
578 self._load()
576 dir, subpath = _splittopdir(f)
579 dir, subpath = _splittopdir(f)
577 if dir:
580 if dir:
578 if dir not in self._dirs:
581 if dir not in self._dirs:
579 return ''
582 return ''
580 return self._dirs[dir].flags(subpath)
583 return self._dirs[dir].flags(subpath)
581 else:
584 else:
582 if f in self._dirs:
585 if f in self._dirs:
583 return ''
586 return ''
584 return self._flags.get(f, '')
587 return self._flags.get(f, '')
585
588
586 def find(self, f):
589 def find(self, f):
587 self._load()
590 self._load()
588 dir, subpath = _splittopdir(f)
591 dir, subpath = _splittopdir(f)
589 if dir:
592 if dir:
590 return self._dirs[dir].find(subpath)
593 return self._dirs[dir].find(subpath)
591 else:
594 else:
592 return self._files[f], self._flags.get(f, '')
595 return self._files[f], self._flags.get(f, '')
593
596
594 def __delitem__(self, f):
597 def __delitem__(self, f):
595 self._load()
598 self._load()
596 dir, subpath = _splittopdir(f)
599 dir, subpath = _splittopdir(f)
597 if dir:
600 if dir:
598 self._dirs[dir].__delitem__(subpath)
601 self._dirs[dir].__delitem__(subpath)
599 # If the directory is now empty, remove it
602 # If the directory is now empty, remove it
600 if self._dirs[dir]._isempty():
603 if self._dirs[dir]._isempty():
601 del self._dirs[dir]
604 del self._dirs[dir]
602 else:
605 else:
603 del self._files[f]
606 del self._files[f]
604 if f in self._flags:
607 if f in self._flags:
605 del self._flags[f]
608 del self._flags[f]
606 self._dirty = True
609 self._dirty = True
607
610
608 def __setitem__(self, f, n):
611 def __setitem__(self, f, n):
609 assert n is not None
612 assert n is not None
610 self._load()
613 self._load()
611 dir, subpath = _splittopdir(f)
614 dir, subpath = _splittopdir(f)
612 if dir:
615 if dir:
613 if dir not in self._dirs:
616 if dir not in self._dirs:
614 self._dirs[dir] = treemanifest(self._subpath(dir))
617 self._dirs[dir] = treemanifest(self._subpath(dir))
615 self._dirs[dir].__setitem__(subpath, n)
618 self._dirs[dir].__setitem__(subpath, n)
616 else:
619 else:
617 self._files[f] = n[:21] # to match manifestdict's behavior
620 self._files[f] = n[:21] # to match manifestdict's behavior
618 self._dirty = True
621 self._dirty = True
619
622
620 def _load(self):
623 def _load(self):
621 if self._loadfunc is not _noop:
624 if self._loadfunc is not _noop:
622 lf, self._loadfunc = self._loadfunc, _noop
625 lf, self._loadfunc = self._loadfunc, _noop
623 lf(self)
626 lf(self)
624 elif self._copyfunc is not _noop:
627 elif self._copyfunc is not _noop:
625 cf, self._copyfunc = self._copyfunc, _noop
628 cf, self._copyfunc = self._copyfunc, _noop
626 cf(self)
629 cf(self)
627
630
628 def setflag(self, f, flags):
631 def setflag(self, f, flags):
629 """Set the flags (symlink, executable) for path f."""
632 """Set the flags (symlink, executable) for path f."""
630 assert 't' not in flags
633 assert 't' not in flags
631 self._load()
634 self._load()
632 dir, subpath = _splittopdir(f)
635 dir, subpath = _splittopdir(f)
633 if dir:
636 if dir:
634 if dir not in self._dirs:
637 if dir not in self._dirs:
635 self._dirs[dir] = treemanifest(self._subpath(dir))
638 self._dirs[dir] = treemanifest(self._subpath(dir))
636 self._dirs[dir].setflag(subpath, flags)
639 self._dirs[dir].setflag(subpath, flags)
637 else:
640 else:
638 self._flags[f] = flags
641 self._flags[f] = flags
639 self._dirty = True
642 self._dirty = True
640
643
641 def copy(self):
644 def copy(self):
642 copy = treemanifest(self._dir)
645 copy = treemanifest(self._dir)
643 copy._node = self._node
646 copy._node = self._node
644 copy._dirty = self._dirty
647 copy._dirty = self._dirty
645 if self._copyfunc is _noop:
648 if self._copyfunc is _noop:
646 def _copyfunc(s):
649 def _copyfunc(s):
647 self._load()
650 self._load()
648 for d in self._dirs:
651 for d in self._dirs:
649 s._dirs[d] = self._dirs[d].copy()
652 s._dirs[d] = self._dirs[d].copy()
650 s._files = dict.copy(self._files)
653 s._files = dict.copy(self._files)
651 s._flags = dict.copy(self._flags)
654 s._flags = dict.copy(self._flags)
652 if self._loadfunc is _noop:
655 if self._loadfunc is _noop:
653 _copyfunc(copy)
656 _copyfunc(copy)
654 else:
657 else:
655 copy._copyfunc = _copyfunc
658 copy._copyfunc = _copyfunc
656 else:
659 else:
657 copy._copyfunc = self._copyfunc
660 copy._copyfunc = self._copyfunc
658 return copy
661 return copy
659
662
660 def filesnotin(self, m2):
663 def filesnotin(self, m2):
661 '''Set of files in this manifest that are not in the other'''
664 '''Set of files in this manifest that are not in the other'''
662 files = set()
665 files = set()
663 def _filesnotin(t1, t2):
666 def _filesnotin(t1, t2):
664 if t1._node == t2._node and not t1._dirty and not t2._dirty:
667 if t1._node == t2._node and not t1._dirty and not t2._dirty:
665 return
668 return
666 t1._load()
669 t1._load()
667 t2._load()
670 t2._load()
668 for d, m1 in t1._dirs.iteritems():
671 for d, m1 in t1._dirs.iteritems():
669 if d in t2._dirs:
672 if d in t2._dirs:
670 m2 = t2._dirs[d]
673 m2 = t2._dirs[d]
671 _filesnotin(m1, m2)
674 _filesnotin(m1, m2)
672 else:
675 else:
673 files.update(m1.iterkeys())
676 files.update(m1.iterkeys())
674
677
675 for fn in t1._files.iterkeys():
678 for fn in t1._files.iterkeys():
676 if fn not in t2._files:
679 if fn not in t2._files:
677 files.add(t1._subpath(fn))
680 files.add(t1._subpath(fn))
678
681
679 _filesnotin(self, m2)
682 _filesnotin(self, m2)
680 return files
683 return files
681
684
682 @propertycache
685 @propertycache
683 def _alldirs(self):
686 def _alldirs(self):
684 return util.dirs(self)
687 return util.dirs(self)
685
688
686 def dirs(self):
689 def dirs(self):
687 return self._alldirs
690 return self._alldirs
688
691
689 def hasdir(self, dir):
692 def hasdir(self, dir):
690 self._load()
693 self._load()
691 topdir, subdir = _splittopdir(dir)
694 topdir, subdir = _splittopdir(dir)
692 if topdir:
695 if topdir:
693 if topdir in self._dirs:
696 if topdir in self._dirs:
694 return self._dirs[topdir].hasdir(subdir)
697 return self._dirs[topdir].hasdir(subdir)
695 return False
698 return False
696 return (dir + '/') in self._dirs
699 return (dir + '/') in self._dirs
697
700
698 def walk(self, match):
701 def walk(self, match):
699 '''Generates matching file names.
702 '''Generates matching file names.
700
703
701 Equivalent to manifest.matches(match).iterkeys(), but without creating
704 Equivalent to manifest.matches(match).iterkeys(), but without creating
702 an entirely new manifest.
705 an entirely new manifest.
703
706
704 It also reports nonexistent files by marking them bad with match.bad().
707 It also reports nonexistent files by marking them bad with match.bad().
705 '''
708 '''
706 if match.always():
709 if match.always():
707 for f in iter(self):
710 for f in iter(self):
708 yield f
711 yield f
709 return
712 return
710
713
711 fset = set(match.files())
714 fset = set(match.files())
712
715
713 for fn in self._walk(match):
716 for fn in self._walk(match):
714 if fn in fset:
717 if fn in fset:
715 # specified pattern is the exact name
718 # specified pattern is the exact name
716 fset.remove(fn)
719 fset.remove(fn)
717 yield fn
720 yield fn
718
721
719 # for dirstate.walk, files=['.'] means "walk the whole tree".
722 # for dirstate.walk, files=['.'] means "walk the whole tree".
720 # follow that here, too
723 # follow that here, too
721 fset.discard('.')
724 fset.discard('.')
722
725
723 for fn in sorted(fset):
726 for fn in sorted(fset):
724 if not self.hasdir(fn):
727 if not self.hasdir(fn):
725 match.bad(fn, None)
728 match.bad(fn, None)
726
729
727 def _walk(self, match):
730 def _walk(self, match):
728 '''Recursively generates matching file names for walk().'''
731 '''Recursively generates matching file names for walk().'''
729 if not match.visitdir(self._dir[:-1] or '.'):
732 if not match.visitdir(self._dir[:-1] or '.'):
730 return
733 return
731
734
732 # yield this dir's files and walk its submanifests
735 # yield this dir's files and walk its submanifests
733 self._load()
736 self._load()
734 for p in sorted(self._dirs.keys() + self._files.keys()):
737 for p in sorted(self._dirs.keys() + self._files.keys()):
735 if p in self._files:
738 if p in self._files:
736 fullp = self._subpath(p)
739 fullp = self._subpath(p)
737 if match(fullp):
740 if match(fullp):
738 yield fullp
741 yield fullp
739 else:
742 else:
740 for f in self._dirs[p]._walk(match):
743 for f in self._dirs[p]._walk(match):
741 yield f
744 yield f
742
745
743 def matches(self, match):
746 def matches(self, match):
744 '''generate a new manifest filtered by the match argument'''
747 '''generate a new manifest filtered by the match argument'''
745 if match.always():
748 if match.always():
746 return self.copy()
749 return self.copy()
747
750
748 return self._matches(match)
751 return self._matches(match)
749
752
750 def _matches(self, match):
753 def _matches(self, match):
751 '''recursively generate a new manifest filtered by the match argument.
754 '''recursively generate a new manifest filtered by the match argument.
752 '''
755 '''
753
756
754 visit = match.visitdir(self._dir[:-1] or '.')
757 visit = match.visitdir(self._dir[:-1] or '.')
755 if visit == 'all':
758 if visit == 'all':
756 return self.copy()
759 return self.copy()
757 ret = treemanifest(self._dir)
760 ret = treemanifest(self._dir)
758 if not visit:
761 if not visit:
759 return ret
762 return ret
760
763
761 self._load()
764 self._load()
762 for fn in self._files:
765 for fn in self._files:
763 fullp = self._subpath(fn)
766 fullp = self._subpath(fn)
764 if not match(fullp):
767 if not match(fullp):
765 continue
768 continue
766 ret._files[fn] = self._files[fn]
769 ret._files[fn] = self._files[fn]
767 if fn in self._flags:
770 if fn in self._flags:
768 ret._flags[fn] = self._flags[fn]
771 ret._flags[fn] = self._flags[fn]
769
772
770 for dir, subm in self._dirs.iteritems():
773 for dir, subm in self._dirs.iteritems():
771 m = subm._matches(match)
774 m = subm._matches(match)
772 if not m._isempty():
775 if not m._isempty():
773 ret._dirs[dir] = m
776 ret._dirs[dir] = m
774
777
775 if not ret._isempty():
778 if not ret._isempty():
776 ret._dirty = True
779 ret._dirty = True
777 return ret
780 return ret
778
781
779 def diff(self, m2, clean=False):
782 def diff(self, m2, clean=False):
780 '''Finds changes between the current manifest and m2.
783 '''Finds changes between the current manifest and m2.
781
784
782 Args:
785 Args:
783 m2: the manifest to which this manifest should be compared.
786 m2: the manifest to which this manifest should be compared.
784 clean: if true, include files unchanged between these manifests
787 clean: if true, include files unchanged between these manifests
785 with a None value in the returned dictionary.
788 with a None value in the returned dictionary.
786
789
787 The result is returned as a dict with filename as key and
790 The result is returned as a dict with filename as key and
788 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
791 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
789 nodeid in the current/other manifest and fl1/fl2 is the flag
792 nodeid in the current/other manifest and fl1/fl2 is the flag
790 in the current/other manifest. Where the file does not exist,
793 in the current/other manifest. Where the file does not exist,
791 the nodeid will be None and the flags will be the empty
794 the nodeid will be None and the flags will be the empty
792 string.
795 string.
793 '''
796 '''
794 result = {}
797 result = {}
795 emptytree = treemanifest()
798 emptytree = treemanifest()
796 def _diff(t1, t2):
799 def _diff(t1, t2):
797 if t1._node == t2._node and not t1._dirty and not t2._dirty:
800 if t1._node == t2._node and not t1._dirty and not t2._dirty:
798 return
801 return
799 t1._load()
802 t1._load()
800 t2._load()
803 t2._load()
801 for d, m1 in t1._dirs.iteritems():
804 for d, m1 in t1._dirs.iteritems():
802 m2 = t2._dirs.get(d, emptytree)
805 m2 = t2._dirs.get(d, emptytree)
803 _diff(m1, m2)
806 _diff(m1, m2)
804
807
805 for d, m2 in t2._dirs.iteritems():
808 for d, m2 in t2._dirs.iteritems():
806 if d not in t1._dirs:
809 if d not in t1._dirs:
807 _diff(emptytree, m2)
810 _diff(emptytree, m2)
808
811
809 for fn, n1 in t1._files.iteritems():
812 for fn, n1 in t1._files.iteritems():
810 fl1 = t1._flags.get(fn, '')
813 fl1 = t1._flags.get(fn, '')
811 n2 = t2._files.get(fn, None)
814 n2 = t2._files.get(fn, None)
812 fl2 = t2._flags.get(fn, '')
815 fl2 = t2._flags.get(fn, '')
813 if n1 != n2 or fl1 != fl2:
816 if n1 != n2 or fl1 != fl2:
814 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
817 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
815 elif clean:
818 elif clean:
816 result[t1._subpath(fn)] = None
819 result[t1._subpath(fn)] = None
817
820
818 for fn, n2 in t2._files.iteritems():
821 for fn, n2 in t2._files.iteritems():
819 if fn not in t1._files:
822 if fn not in t1._files:
820 fl2 = t2._flags.get(fn, '')
823 fl2 = t2._flags.get(fn, '')
821 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
824 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
822
825
823 _diff(self, m2)
826 _diff(self, m2)
824 return result
827 return result
825
828
826 def unmodifiedsince(self, m2):
829 def unmodifiedsince(self, m2):
827 return not self._dirty and not m2._dirty and self._node == m2._node
830 return not self._dirty and not m2._dirty and self._node == m2._node
828
831
829 def parse(self, text, readsubtree):
832 def parse(self, text, readsubtree):
830 for f, n, fl in _parse(text):
833 for f, n, fl in _parse(text):
831 if fl == 't':
834 if fl == 't':
832 f = f + '/'
835 f = f + '/'
833 self._dirs[f] = readsubtree(self._subpath(f), n)
836 self._dirs[f] = readsubtree(self._subpath(f), n)
834 elif '/' in f:
837 elif '/' in f:
835 # This is a flat manifest, so use __setitem__ and setflag rather
838 # This is a flat manifest, so use __setitem__ and setflag rather
836 # than assigning directly to _files and _flags, so we can
839 # than assigning directly to _files and _flags, so we can
837 # assign a path in a subdirectory, and to mark dirty (compared
840 # assign a path in a subdirectory, and to mark dirty (compared
838 # to nullid).
841 # to nullid).
839 self[f] = n
842 self[f] = n
840 if fl:
843 if fl:
841 self.setflag(f, fl)
844 self.setflag(f, fl)
842 else:
845 else:
843 # Assigning to _files and _flags avoids marking as dirty,
846 # Assigning to _files and _flags avoids marking as dirty,
844 # and should be a little faster.
847 # and should be a little faster.
845 self._files[f] = n
848 self._files[f] = n
846 if fl:
849 if fl:
847 self._flags[f] = fl
850 self._flags[f] = fl
848
851
849 def text(self, usemanifestv2=False):
852 def text(self, usemanifestv2=False):
850 """Get the full data of this manifest as a bytestring."""
853 """Get the full data of this manifest as a bytestring."""
851 self._load()
854 self._load()
852 flags = self.flags
855 flags = self.flags
853 return _text(((f, self[f], flags(f)) for f in self.keys()),
856 return _text(((f, self[f], flags(f)) for f in self.keys()),
854 usemanifestv2)
857 usemanifestv2)
855
858
856 def dirtext(self, usemanifestv2=False):
859 def dirtext(self, usemanifestv2=False):
857 """Get the full data of this directory as a bytestring. Make sure that
860 """Get the full data of this directory as a bytestring. Make sure that
858 any submanifests have been written first, so their nodeids are correct.
861 any submanifests have been written first, so their nodeids are correct.
859 """
862 """
860 self._load()
863 self._load()
861 flags = self.flags
864 flags = self.flags
862 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
865 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
863 files = [(f, self._files[f], flags(f)) for f in self._files]
866 files = [(f, self._files[f], flags(f)) for f in self._files]
864 return _text(sorted(dirs + files), usemanifestv2)
867 return _text(sorted(dirs + files), usemanifestv2)
865
868
866 def read(self, gettext, readsubtree):
869 def read(self, gettext, readsubtree):
867 def _load_for_read(s):
870 def _load_for_read(s):
868 s.parse(gettext(), readsubtree)
871 s.parse(gettext(), readsubtree)
869 s._dirty = False
872 s._dirty = False
870 self._loadfunc = _load_for_read
873 self._loadfunc = _load_for_read
871
874
872 def writesubtrees(self, m1, m2, writesubtree):
875 def writesubtrees(self, m1, m2, writesubtree):
873 self._load() # for consistency; should never have any effect here
876 self._load() # for consistency; should never have any effect here
874 emptytree = treemanifest()
877 emptytree = treemanifest()
875 for d, subm in self._dirs.iteritems():
878 for d, subm in self._dirs.iteritems():
876 subp1 = m1._dirs.get(d, emptytree)._node
879 subp1 = m1._dirs.get(d, emptytree)._node
877 subp2 = m2._dirs.get(d, emptytree)._node
880 subp2 = m2._dirs.get(d, emptytree)._node
878 if subp1 == revlog.nullid:
881 if subp1 == revlog.nullid:
879 subp1, subp2 = subp2, subp1
882 subp1, subp2 = subp2, subp1
880 writesubtree(subm, subp1, subp2)
883 writesubtree(subm, subp1, subp2)
881
884
882 class manifest(revlog.revlog):
885 class manifest(revlog.revlog):
883 def __init__(self, opener, dir='', dirlogcache=None):
886 def __init__(self, opener, dir='', dirlogcache=None):
884 '''The 'dir' and 'dirlogcache' arguments are for internal use by
887 '''The 'dir' and 'dirlogcache' arguments are for internal use by
885 manifest.manifest only. External users should create a root manifest
888 manifest.manifest only. External users should create a root manifest
886 log with manifest.manifest(opener) and call dirlog() on it.
889 log with manifest.manifest(opener) and call dirlog() on it.
887 '''
890 '''
888 # During normal operations, we expect to deal with not more than four
891 # During normal operations, we expect to deal with not more than four
889 # revs at a time (such as during commit --amend). When rebasing large
892 # revs at a time (such as during commit --amend). When rebasing large
890 # stacks of commits, the number can go up, hence the config knob below.
893 # stacks of commits, the number can go up, hence the config knob below.
891 cachesize = 4
894 cachesize = 4
892 usetreemanifest = False
895 usetreemanifest = False
893 usemanifestv2 = False
896 usemanifestv2 = False
894 opts = getattr(opener, 'options', None)
897 opts = getattr(opener, 'options', None)
895 if opts is not None:
898 if opts is not None:
896 cachesize = opts.get('manifestcachesize', cachesize)
899 cachesize = opts.get('manifestcachesize', cachesize)
897 usetreemanifest = opts.get('treemanifest', usetreemanifest)
900 usetreemanifest = opts.get('treemanifest', usetreemanifest)
898 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
901 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
899 self._mancache = util.lrucachedict(cachesize)
902 self._mancache = util.lrucachedict(cachesize)
900 self._treeinmem = usetreemanifest
903 self._treeinmem = usetreemanifest
901 self._treeondisk = usetreemanifest
904 self._treeondisk = usetreemanifest
902 self._usemanifestv2 = usemanifestv2
905 self._usemanifestv2 = usemanifestv2
903 indexfile = "00manifest.i"
906 indexfile = "00manifest.i"
904 if dir:
907 if dir:
905 assert self._treeondisk
908 assert self._treeondisk
906 if not dir.endswith('/'):
909 if not dir.endswith('/'):
907 dir = dir + '/'
910 dir = dir + '/'
908 indexfile = "meta/" + dir + "00manifest.i"
911 indexfile = "meta/" + dir + "00manifest.i"
909 revlog.revlog.__init__(self, opener, indexfile)
912 revlog.revlog.__init__(self, opener, indexfile)
910 self._dir = dir
913 self._dir = dir
911 # The dirlogcache is kept on the root manifest log
914 # The dirlogcache is kept on the root manifest log
912 if dir:
915 if dir:
913 self._dirlogcache = dirlogcache
916 self._dirlogcache = dirlogcache
914 else:
917 else:
915 self._dirlogcache = {'': self}
918 self._dirlogcache = {'': self}
916
919
917 def _newmanifest(self, data=''):
920 def _newmanifest(self, data=''):
918 if self._treeinmem:
921 if self._treeinmem:
919 return treemanifest(self._dir, data)
922 return treemanifest(self._dir, data)
920 return manifestdict(data)
923 return manifestdict(data)
921
924
922 def dirlog(self, dir):
925 def dirlog(self, dir):
923 assert self._treeondisk
926 if dir:
927 assert self._treeondisk
924 if dir not in self._dirlogcache:
928 if dir not in self._dirlogcache:
925 self._dirlogcache[dir] = manifest(self.opener, dir,
929 self._dirlogcache[dir] = manifest(self.opener, dir,
926 self._dirlogcache)
930 self._dirlogcache)
927 return self._dirlogcache[dir]
931 return self._dirlogcache[dir]
928
932
929 def _slowreaddelta(self, node):
933 def _slowreaddelta(self, node):
930 r0 = self.deltaparent(self.rev(node))
934 r0 = self.deltaparent(self.rev(node))
931 m0 = self.read(self.node(r0))
935 m0 = self.read(self.node(r0))
932 m1 = self.read(node)
936 m1 = self.read(node)
933 md = self._newmanifest()
937 md = self._newmanifest()
934 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
938 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
935 if n1:
939 if n1:
936 md[f] = n1
940 md[f] = n1
937 if fl1:
941 if fl1:
938 md.setflag(f, fl1)
942 md.setflag(f, fl1)
939 return md
943 return md
940
944
941 def readdelta(self, node):
945 def readdelta(self, node):
942 if self._usemanifestv2 or self._treeondisk:
946 if self._usemanifestv2 or self._treeondisk:
943 return self._slowreaddelta(node)
947 return self._slowreaddelta(node)
944 r = self.rev(node)
948 r = self.rev(node)
945 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
949 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
946 return self._newmanifest(d)
950 return self._newmanifest(d)
947
951
952 def readshallowdelta(self, node):
953 '''For flat manifests, this is the same as readdelta(). For
954 treemanifests, this will read the delta for this revlog's directory,
955 without recursively reading subdirectory manifests. Instead, any
956 subdirectory entry will be reported as it appears in the manifests, i.e.
957 the subdirectory will be reported among files and distinguished only by
958 its 't' flag.'''
959 if not self._treeondisk:
960 return self.readdelta(node)
961 if self._usemanifestv2:
962 raise error.Abort(
963 "readshallowdelta() not implemented for manifestv2")
964 r = self.rev(node)
965 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
966 return manifestdict(d)
967
948 def readfast(self, node):
968 def readfast(self, node):
949 '''use the faster of readdelta or read
969 '''use the faster of readdelta or read
950
970
951 This will return a manifest which is either only the files
971 This will return a manifest which is either only the files
952 added/modified relative to p1, or all files in the
972 added/modified relative to p1, or all files in the
953 manifest. Which one is returned depends on the codepath used
973 manifest. Which one is returned depends on the codepath used
954 to retrieve the data.
974 to retrieve the data.
955 '''
975 '''
956 r = self.rev(node)
976 r = self.rev(node)
957 deltaparent = self.deltaparent(r)
977 deltaparent = self.deltaparent(r)
958 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
978 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
959 return self.readdelta(node)
979 return self.readdelta(node)
960 return self.read(node)
980 return self.read(node)
961
981
962 def read(self, node):
982 def read(self, node):
963 if node == revlog.nullid:
983 if node == revlog.nullid:
964 return self._newmanifest() # don't upset local cache
984 return self._newmanifest() # don't upset local cache
965 if node in self._mancache:
985 if node in self._mancache:
966 return self._mancache[node][0]
986 return self._mancache[node][0]
967 if self._treeondisk:
987 if self._treeondisk:
968 def gettext():
988 def gettext():
969 return self.revision(node)
989 return self.revision(node)
970 def readsubtree(dir, subm):
990 def readsubtree(dir, subm):
971 return self.dirlog(dir).read(subm)
991 return self.dirlog(dir).read(subm)
972 m = self._newmanifest()
992 m = self._newmanifest()
973 m.read(gettext, readsubtree)
993 m.read(gettext, readsubtree)
974 m.setnode(node)
994 m.setnode(node)
975 arraytext = None
995 arraytext = None
976 else:
996 else:
977 text = self.revision(node)
997 text = self.revision(node)
978 m = self._newmanifest(text)
998 m = self._newmanifest(text)
979 arraytext = array.array('c', text)
999 arraytext = array.array('c', text)
980 self._mancache[node] = (m, arraytext)
1000 self._mancache[node] = (m, arraytext)
981 return m
1001 return m
982
1002
983 def find(self, node, f):
1003 def find(self, node, f):
984 '''look up entry for a single file efficiently.
1004 '''look up entry for a single file efficiently.
985 return (node, flags) pair if found, (None, None) if not.'''
1005 return (node, flags) pair if found, (None, None) if not.'''
986 m = self.read(node)
1006 m = self.read(node)
987 try:
1007 try:
988 return m.find(f)
1008 return m.find(f)
989 except KeyError:
1009 except KeyError:
990 return None, None
1010 return None, None
991
1011
992 def add(self, m, transaction, link, p1, p2, added, removed):
1012 def add(self, m, transaction, link, p1, p2, added, removed):
993 if (p1 in self._mancache and not self._treeinmem
1013 if (p1 in self._mancache and not self._treeinmem
994 and not self._usemanifestv2):
1014 and not self._usemanifestv2):
995 # If our first parent is in the manifest cache, we can
1015 # If our first parent is in the manifest cache, we can
996 # compute a delta here using properties we know about the
1016 # compute a delta here using properties we know about the
997 # manifest up-front, which may save time later for the
1017 # manifest up-front, which may save time later for the
998 # revlog layer.
1018 # revlog layer.
999
1019
1000 _checkforbidden(added)
1020 _checkforbidden(added)
1001 # combine the changed lists into one sorted iterator
1021 # combine the changed lists into one sorted iterator
1002 work = heapq.merge([(x, False) for x in added],
1022 work = heapq.merge([(x, False) for x in added],
1003 [(x, True) for x in removed])
1023 [(x, True) for x in removed])
1004
1024
1005 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
1025 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
1006 cachedelta = self.rev(p1), deltatext
1026 cachedelta = self.rev(p1), deltatext
1007 text = util.buffer(arraytext)
1027 text = util.buffer(arraytext)
1008 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1028 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1009 else:
1029 else:
1010 # The first parent manifest isn't already loaded, so we'll
1030 # The first parent manifest isn't already loaded, so we'll
1011 # just encode a fulltext of the manifest and pass that
1031 # just encode a fulltext of the manifest and pass that
1012 # through to the revlog layer, and let it handle the delta
1032 # through to the revlog layer, and let it handle the delta
1013 # process.
1033 # process.
1014 if self._treeondisk:
1034 if self._treeondisk:
1015 m1 = self.read(p1)
1035 m1 = self.read(p1)
1016 m2 = self.read(p2)
1036 m2 = self.read(p2)
1017 n = self._addtree(m, transaction, link, m1, m2)
1037 n = self._addtree(m, transaction, link, m1, m2)
1018 arraytext = None
1038 arraytext = None
1019 else:
1039 else:
1020 text = m.text(self._usemanifestv2)
1040 text = m.text(self._usemanifestv2)
1021 n = self.addrevision(text, transaction, link, p1, p2)
1041 n = self.addrevision(text, transaction, link, p1, p2)
1022 arraytext = array.array('c', text)
1042 arraytext = array.array('c', text)
1023
1043
1024 self._mancache[n] = (m, arraytext)
1044 self._mancache[n] = (m, arraytext)
1025
1045
1026 return n
1046 return n
1027
1047
1028 def _addtree(self, m, transaction, link, m1, m2):
1048 def _addtree(self, m, transaction, link, m1, m2):
1029 # If the manifest is unchanged compared to one parent,
1049 # If the manifest is unchanged compared to one parent,
1030 # don't write a new revision
1050 # don't write a new revision
1031 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1051 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1032 return m.node()
1052 return m.node()
1033 def writesubtree(subm, subp1, subp2):
1053 def writesubtree(subm, subp1, subp2):
1034 sublog = self.dirlog(subm.dir())
1054 sublog = self.dirlog(subm.dir())
1035 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1055 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1036 m.writesubtrees(m1, m2, writesubtree)
1056 m.writesubtrees(m1, m2, writesubtree)
1037 text = m.dirtext(self._usemanifestv2)
1057 text = m.dirtext(self._usemanifestv2)
1038 # Double-check whether contents are unchanged to one parent
1058 # Double-check whether contents are unchanged to one parent
1039 if text == m1.dirtext(self._usemanifestv2):
1059 if text == m1.dirtext(self._usemanifestv2):
1040 n = m1.node()
1060 n = m1.node()
1041 elif text == m2.dirtext(self._usemanifestv2):
1061 elif text == m2.dirtext(self._usemanifestv2):
1042 n = m2.node()
1062 n = m2.node()
1043 else:
1063 else:
1044 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1064 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1045 # Save nodeid so parent manifest can calculate its nodeid
1065 # Save nodeid so parent manifest can calculate its nodeid
1046 m.setnode(n)
1066 m.setnode(n)
1047 return n
1067 return n
1048
1068
1049 def clearcaches(self):
1069 def clearcaches(self):
1050 super(manifest, self).clearcaches()
1070 super(manifest, self).clearcaches()
1051 self._mancache.clear()
1071 self._mancache.clear()
1052 self._dirlogcache = {'': self}
1072 self._dirlogcache = {'': self}
@@ -1,381 +1,408 b''
1 # verify.py - repository integrity checking for Mercurial
1 # verify.py - repository integrity checking for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 nullid,
14 nullid,
15 short,
15 short,
16 )
16 )
17
17
18 from . import (
18 from . import (
19 error,
19 error,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 def verify(repo):
24 def verify(repo):
25 with repo.lock():
25 with repo.lock():
26 return verifier(repo).verify()
26 return verifier(repo).verify()
27
27
28 def _normpath(f):
28 def _normpath(f):
29 # under hg < 2.4, convert didn't sanitize paths properly, so a
29 # under hg < 2.4, convert didn't sanitize paths properly, so a
30 # converted repo may contain repeated slashes
30 # converted repo may contain repeated slashes
31 while '//' in f:
31 while '//' in f:
32 f = f.replace('//', '/')
32 f = f.replace('//', '/')
33 return f
33 return f
34
34
35 def _validpath(repo, path):
35 def _validpath(repo, path):
36 """Returns False if a path should NOT be treated as part of a repo.
36 """Returns False if a path should NOT be treated as part of a repo.
37
37
38 For all in-core cases, this returns True, as we have no way for a
38 For all in-core cases, this returns True, as we have no way for a
39 path to be mentioned in the history but not actually be
39 path to be mentioned in the history but not actually be
40 relevant. For narrow clones, this is important because many
40 relevant. For narrow clones, this is important because many
41 filelogs will be missing, and changelog entries may mention
41 filelogs will be missing, and changelog entries may mention
42 modified files that are outside the narrow scope.
42 modified files that are outside the narrow scope.
43 """
43 """
44 return True
44 return True
45
45
46 class verifier(object):
46 class verifier(object):
47 def __init__(self, repo):
47 def __init__(self, repo):
48 self.repo = repo.unfiltered()
48 self.repo = repo.unfiltered()
49 self.ui = repo.ui
49 self.ui = repo.ui
50 self.badrevs = set()
50 self.badrevs = set()
51 self.errors = 0
51 self.errors = 0
52 self.warnings = 0
52 self.warnings = 0
53 self.havecl = len(repo.changelog) > 0
53 self.havecl = len(repo.changelog) > 0
54 self.havemf = len(repo.manifest) > 0
54 self.havemf = len(repo.manifest) > 0
55 self.revlogv1 = repo.changelog.version != revlog.REVLOGV0
55 self.revlogv1 = repo.changelog.version != revlog.REVLOGV0
56 self.lrugetctx = util.lrucachefunc(repo.changectx)
56 self.lrugetctx = util.lrucachefunc(repo.changectx)
57 self.refersmf = False
57 self.refersmf = False
58 self.fncachewarned = False
58 self.fncachewarned = False
59
59
60 def warn(self, msg):
60 def warn(self, msg):
61 self.ui.warn(msg + "\n")
61 self.ui.warn(msg + "\n")
62 self.warnings += 1
62 self.warnings += 1
63
63
64 def err(self, linkrev, msg, filename=None):
64 def err(self, linkrev, msg, filename=None):
65 if linkrev is not None:
65 if linkrev is not None:
66 self.badrevs.add(linkrev)
66 self.badrevs.add(linkrev)
67 else:
67 else:
68 linkrev = '?'
68 linkrev = '?'
69 msg = "%s: %s" % (linkrev, msg)
69 msg = "%s: %s" % (linkrev, msg)
70 if filename:
70 if filename:
71 msg = "%s@%s" % (filename, msg)
71 msg = "%s@%s" % (filename, msg)
72 self.ui.warn(" " + msg + "\n")
72 self.ui.warn(" " + msg + "\n")
73 self.errors += 1
73 self.errors += 1
74
74
75 def exc(self, linkrev, msg, inst, filename=None):
75 def exc(self, linkrev, msg, inst, filename=None):
76 if not str(inst):
76 if not str(inst):
77 inst = repr(inst)
77 inst = repr(inst)
78 self.err(linkrev, "%s: %s" % (msg, inst), filename)
78 self.err(linkrev, "%s: %s" % (msg, inst), filename)
79
79
80 def checklog(self, obj, name, linkrev):
80 def checklog(self, obj, name, linkrev):
81 if not len(obj) and (self.havecl or self.havemf):
81 if not len(obj) and (self.havecl or self.havemf):
82 self.err(linkrev, _("empty or missing %s") % name)
82 self.err(linkrev, _("empty or missing %s") % name)
83 return
83 return
84
84
85 d = obj.checksize()
85 d = obj.checksize()
86 if d[0]:
86 if d[0]:
87 self.err(None, _("data length off by %d bytes") % d[0], name)
87 self.err(None, _("data length off by %d bytes") % d[0], name)
88 if d[1]:
88 if d[1]:
89 self.err(None, _("index contains %d extra bytes") % d[1], name)
89 self.err(None, _("index contains %d extra bytes") % d[1], name)
90
90
91 if obj.version != revlog.REVLOGV0:
91 if obj.version != revlog.REVLOGV0:
92 if not self.revlogv1:
92 if not self.revlogv1:
93 self.warn(_("warning: `%s' uses revlog format 1") % name)
93 self.warn(_("warning: `%s' uses revlog format 1") % name)
94 elif self.revlogv1:
94 elif self.revlogv1:
95 self.warn(_("warning: `%s' uses revlog format 0") % name)
95 self.warn(_("warning: `%s' uses revlog format 0") % name)
96
96
97 def checkentry(self, obj, i, node, seen, linkrevs, f):
97 def checkentry(self, obj, i, node, seen, linkrevs, f):
98 lr = obj.linkrev(obj.rev(node))
98 lr = obj.linkrev(obj.rev(node))
99 if lr < 0 or (self.havecl and lr not in linkrevs):
99 if lr < 0 or (self.havecl and lr not in linkrevs):
100 if lr < 0 or lr >= len(self.repo.changelog):
100 if lr < 0 or lr >= len(self.repo.changelog):
101 msg = _("rev %d points to nonexistent changeset %d")
101 msg = _("rev %d points to nonexistent changeset %d")
102 else:
102 else:
103 msg = _("rev %d points to unexpected changeset %d")
103 msg = _("rev %d points to unexpected changeset %d")
104 self.err(None, msg % (i, lr), f)
104 self.err(None, msg % (i, lr), f)
105 if linkrevs:
105 if linkrevs:
106 if f and len(linkrevs) > 1:
106 if f and len(linkrevs) > 1:
107 try:
107 try:
108 # attempt to filter down to real linkrevs
108 # attempt to filter down to real linkrevs
109 linkrevs = [l for l in linkrevs
109 linkrevs = [l for l in linkrevs
110 if self.lrugetctx(l)[f].filenode() == node]
110 if self.lrugetctx(l)[f].filenode() == node]
111 except Exception:
111 except Exception:
112 pass
112 pass
113 self.warn(_(" (expected %s)") % " ".join(map(str, linkrevs)))
113 self.warn(_(" (expected %s)") % " ".join(map(str, linkrevs)))
114 lr = None # can't be trusted
114 lr = None # can't be trusted
115
115
116 try:
116 try:
117 p1, p2 = obj.parents(node)
117 p1, p2 = obj.parents(node)
118 if p1 not in seen and p1 != nullid:
118 if p1 not in seen and p1 != nullid:
119 self.err(lr, _("unknown parent 1 %s of %s") %
119 self.err(lr, _("unknown parent 1 %s of %s") %
120 (short(p1), short(node)), f)
120 (short(p1), short(node)), f)
121 if p2 not in seen and p2 != nullid:
121 if p2 not in seen and p2 != nullid:
122 self.err(lr, _("unknown parent 2 %s of %s") %
122 self.err(lr, _("unknown parent 2 %s of %s") %
123 (short(p2), short(node)), f)
123 (short(p2), short(node)), f)
124 except Exception as inst:
124 except Exception as inst:
125 self.exc(lr, _("checking parents of %s") % short(node), inst, f)
125 self.exc(lr, _("checking parents of %s") % short(node), inst, f)
126
126
127 if node in seen:
127 if node in seen:
128 self.err(lr, _("duplicate revision %d (%d)") % (i, seen[node]), f)
128 self.err(lr, _("duplicate revision %d (%d)") % (i, seen[node]), f)
129 seen[node] = i
129 seen[node] = i
130 return lr
130 return lr
131
131
132 def verify(self):
132 def verify(self):
133 repo = self.repo
133 repo = self.repo
134
134
135 ui = repo.ui
135 ui = repo.ui
136
136
137 if not repo.url().startswith('file:'):
137 if not repo.url().startswith('file:'):
138 raise error.Abort(_("cannot verify bundle or remote repos"))
138 raise error.Abort(_("cannot verify bundle or remote repos"))
139
139
140 if os.path.exists(repo.sjoin("journal")):
140 if os.path.exists(repo.sjoin("journal")):
141 ui.warn(_("abandoned transaction found - run hg recover\n"))
141 ui.warn(_("abandoned transaction found - run hg recover\n"))
142
142
143 if ui.verbose or not self.revlogv1:
143 if ui.verbose or not self.revlogv1:
144 ui.status(_("repository uses revlog format %d\n") %
144 ui.status(_("repository uses revlog format %d\n") %
145 (self.revlogv1 and 1 or 0))
145 (self.revlogv1 and 1 or 0))
146
146
147 mflinkrevs, filelinkrevs = self._verifychangelog()
147 mflinkrevs, filelinkrevs = self._verifychangelog()
148
148
149 filenodes = self._verifymanifest(mflinkrevs)
149 filenodes = self._verifymanifest(mflinkrevs)
150 del mflinkrevs
150 del mflinkrevs
151
151
152 self._crosscheckfiles(filelinkrevs, filenodes)
152 self._crosscheckfiles(filelinkrevs, filenodes)
153
153
154 totalfiles, filerevisions = self._verifyfiles(filenodes, filelinkrevs)
154 totalfiles, filerevisions = self._verifyfiles(filenodes, filelinkrevs)
155
155
156 ui.status(_("%d files, %d changesets, %d total revisions\n") %
156 ui.status(_("%d files, %d changesets, %d total revisions\n") %
157 (totalfiles, len(repo.changelog), filerevisions))
157 (totalfiles, len(repo.changelog), filerevisions))
158 if self.warnings:
158 if self.warnings:
159 ui.warn(_("%d warnings encountered!\n") % self.warnings)
159 ui.warn(_("%d warnings encountered!\n") % self.warnings)
160 if self.fncachewarned:
160 if self.fncachewarned:
161 ui.warn(_('hint: run "hg debugrebuildfncache" to recover from '
161 ui.warn(_('hint: run "hg debugrebuildfncache" to recover from '
162 'corrupt fncache\n'))
162 'corrupt fncache\n'))
163 if self.errors:
163 if self.errors:
164 ui.warn(_("%d integrity errors encountered!\n") % self.errors)
164 ui.warn(_("%d integrity errors encountered!\n") % self.errors)
165 if self.badrevs:
165 if self.badrevs:
166 ui.warn(_("(first damaged changeset appears to be %d)\n")
166 ui.warn(_("(first damaged changeset appears to be %d)\n")
167 % min(self.badrevs))
167 % min(self.badrevs))
168 return 1
168 return 1
169
169
170 def _verifychangelog(self):
170 def _verifychangelog(self):
171 ui = self.ui
171 ui = self.ui
172 repo = self.repo
172 repo = self.repo
173 cl = repo.changelog
173 cl = repo.changelog
174
174
175 ui.status(_("checking changesets\n"))
175 ui.status(_("checking changesets\n"))
176 mflinkrevs = {}
176 mflinkrevs = {}
177 filelinkrevs = {}
177 filelinkrevs = {}
178 seen = {}
178 seen = {}
179 self.checklog(cl, "changelog", 0)
179 self.checklog(cl, "changelog", 0)
180 total = len(repo)
180 total = len(repo)
181 for i in repo:
181 for i in repo:
182 ui.progress(_('checking'), i, total=total, unit=_('changesets'))
182 ui.progress(_('checking'), i, total=total, unit=_('changesets'))
183 n = cl.node(i)
183 n = cl.node(i)
184 self.checkentry(cl, i, n, seen, [i], "changelog")
184 self.checkentry(cl, i, n, seen, [i], "changelog")
185
185
186 try:
186 try:
187 changes = cl.read(n)
187 changes = cl.read(n)
188 if changes[0] != nullid:
188 if changes[0] != nullid:
189 mflinkrevs.setdefault(changes[0], []).append(i)
189 mflinkrevs.setdefault(changes[0], []).append(i)
190 self.refersmf = True
190 self.refersmf = True
191 for f in changes[3]:
191 for f in changes[3]:
192 if _validpath(repo, f):
192 if _validpath(repo, f):
193 filelinkrevs.setdefault(_normpath(f), []).append(i)
193 filelinkrevs.setdefault(_normpath(f), []).append(i)
194 except Exception as inst:
194 except Exception as inst:
195 self.refersmf = True
195 self.refersmf = True
196 self.exc(i, _("unpacking changeset %s") % short(n), inst)
196 self.exc(i, _("unpacking changeset %s") % short(n), inst)
197 ui.progress(_('checking'), None)
197 ui.progress(_('checking'), None)
198 return mflinkrevs, filelinkrevs
198 return mflinkrevs, filelinkrevs
199
199
200 def _verifymanifest(self, mflinkrevs):
200 def _verifymanifest(self, mflinkrevs, dir=""):
201 repo = self.repo
201 repo = self.repo
202 ui = self.ui
202 ui = self.ui
203 mf = self.repo.manifest
203 mf = self.repo.manifest.dirlog(dir)
204
204
205 ui.status(_("checking manifests\n"))
205 if not dir:
206 self.ui.status(_("checking manifests\n"))
207
206 filenodes = {}
208 filenodes = {}
209 subdirnodes = {}
207 seen = {}
210 seen = {}
208 label = "manifest"
211 label = "manifest"
212 if dir:
213 label = dir
209 if self.refersmf:
214 if self.refersmf:
210 # Do not check manifest if there are only changelog entries with
215 # Do not check manifest if there are only changelog entries with
211 # null manifests.
216 # null manifests.
212 self.checklog(mf, label, 0)
217 self.checklog(mf, label, 0)
213 total = len(mf)
218 total = len(mf)
214 for i in mf:
219 for i in mf:
215 ui.progress(_('checking'), i, total=total, unit=_('manifests'))
220 if not dir:
221 ui.progress(_('checking'), i, total=total, unit=_('manifests'))
216 n = mf.node(i)
222 n = mf.node(i)
217 lr = self.checkentry(mf, i, n, seen, mflinkrevs.get(n, []), label)
223 lr = self.checkentry(mf, i, n, seen, mflinkrevs.get(n, []), label)
218 if n in mflinkrevs:
224 if n in mflinkrevs:
219 del mflinkrevs[n]
225 del mflinkrevs[n]
226 elif dir:
227 self.err(lr, _("%s not in parent-directory manifest") %
228 short(n), label)
220 else:
229 else:
221 self.err(lr, _("%s not in changesets") % short(n), label)
230 self.err(lr, _("%s not in changesets") % short(n), label)
222
231
223 try:
232 try:
224 for f, fn in mf.readdelta(n).iteritems():
233 for f, fn, fl in mf.readshallowdelta(n).iterentries():
225 if not f:
234 if not f:
226 self.err(lr, _("file without name in manifest"))
235 self.err(lr, _("entry without name in manifest"))
227 elif f != "/dev/null": # ignore this in very old repos
236 elif f == "/dev/null": # ignore this in very old repos
228 if _validpath(repo, f):
237 continue
229 filenodes.setdefault(
238 fullpath = dir + _normpath(f)
230 _normpath(f), {}).setdefault(fn, lr)
239 if not _validpath(repo, fullpath):
240 continue
241 if fl == 't':
242 subdirnodes.setdefault(fullpath + '/', {}).setdefault(
243 fn, []).append(lr)
244 else:
245 filenodes.setdefault(fullpath, {}).setdefault(fn, lr)
231 except Exception as inst:
246 except Exception as inst:
232 self.exc(lr, _("reading delta %s") % short(n), inst, label)
247 self.exc(lr, _("reading delta %s") % short(n), inst, label)
233 ui.progress(_('checking'), None)
248 if not dir:
249 ui.progress(_('checking'), None)
234
250
235 if self.havemf:
251 if self.havemf:
236 for c, m in sorted([(c, m) for m in mflinkrevs
252 for c, m in sorted([(c, m) for m in mflinkrevs
237 for c in mflinkrevs[m]]):
253 for c in mflinkrevs[m]]):
238 self.err(c, _("changeset refers to unknown revision %s") %
254 if dir:
239 short(m), label)
255 self.err(c, _("parent-directory manifest refers to unknown "
256 "revision %s") % short(m), label)
257 else:
258 self.err(c, _("changeset refers to unknown revision %s") %
259 short(m), label)
260
261 if not dir and subdirnodes:
262 self.ui.status(_("checking directory manifests\n"))
263 for subdir, linkrevs in subdirnodes.iteritems():
264 subdirfilenodes = self._verifymanifest(linkrevs, subdir)
265 for f, onefilenodes in subdirfilenodes.iteritems():
266 filenodes.setdefault(f, {}).update(onefilenodes)
240
267
241 return filenodes
268 return filenodes
242
269
243 def _crosscheckfiles(self, filelinkrevs, filenodes):
270 def _crosscheckfiles(self, filelinkrevs, filenodes):
244 repo = self.repo
271 repo = self.repo
245 ui = self.ui
272 ui = self.ui
246 ui.status(_("crosschecking files in changesets and manifests\n"))
273 ui.status(_("crosschecking files in changesets and manifests\n"))
247
274
248 total = len(filelinkrevs) + len(filenodes)
275 total = len(filelinkrevs) + len(filenodes)
249 count = 0
276 count = 0
250 if self.havemf:
277 if self.havemf:
251 for f in sorted(filelinkrevs):
278 for f in sorted(filelinkrevs):
252 count += 1
279 count += 1
253 ui.progress(_('crosschecking'), count, total=total)
280 ui.progress(_('crosschecking'), count, total=total)
254 if f not in filenodes:
281 if f not in filenodes:
255 lr = filelinkrevs[f][0]
282 lr = filelinkrevs[f][0]
256 self.err(lr, _("in changeset but not in manifest"), f)
283 self.err(lr, _("in changeset but not in manifest"), f)
257
284
258 if self.havecl:
285 if self.havecl:
259 for f in sorted(filenodes):
286 for f in sorted(filenodes):
260 count += 1
287 count += 1
261 ui.progress(_('crosschecking'), count, total=total)
288 ui.progress(_('crosschecking'), count, total=total)
262 if f not in filelinkrevs:
289 if f not in filelinkrevs:
263 try:
290 try:
264 fl = repo.file(f)
291 fl = repo.file(f)
265 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
292 lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]])
266 except Exception:
293 except Exception:
267 lr = None
294 lr = None
268 self.err(lr, _("in manifest but not in changeset"), f)
295 self.err(lr, _("in manifest but not in changeset"), f)
269
296
270 ui.progress(_('crosschecking'), None)
297 ui.progress(_('crosschecking'), None)
271
298
272 def _verifyfiles(self, filenodes, filelinkrevs):
299 def _verifyfiles(self, filenodes, filelinkrevs):
273 repo = self.repo
300 repo = self.repo
274 ui = self.ui
301 ui = self.ui
275 lrugetctx = self.lrugetctx
302 lrugetctx = self.lrugetctx
276 revlogv1 = self.revlogv1
303 revlogv1 = self.revlogv1
277 havemf = self.havemf
304 havemf = self.havemf
278 ui.status(_("checking files\n"))
305 ui.status(_("checking files\n"))
279
306
280 storefiles = set()
307 storefiles = set()
281 for f, f2, size in repo.store.datafiles():
308 for f, f2, size in repo.store.datafiles():
282 if not f:
309 if not f:
283 self.err(None, _("cannot decode filename '%s'") % f2)
310 self.err(None, _("cannot decode filename '%s'") % f2)
284 elif (size > 0 or not revlogv1) and f.startswith('data/'):
311 elif (size > 0 or not revlogv1) and f.startswith('data/'):
285 storefiles.add(_normpath(f))
312 storefiles.add(_normpath(f))
286
313
287 files = sorted(set(filenodes) | set(filelinkrevs))
314 files = sorted(set(filenodes) | set(filelinkrevs))
288 total = len(files)
315 total = len(files)
289 revisions = 0
316 revisions = 0
290 for i, f in enumerate(files):
317 for i, f in enumerate(files):
291 ui.progress(_('checking'), i, item=f, total=total)
318 ui.progress(_('checking'), i, item=f, total=total)
292 try:
319 try:
293 linkrevs = filelinkrevs[f]
320 linkrevs = filelinkrevs[f]
294 except KeyError:
321 except KeyError:
295 # in manifest but not in changelog
322 # in manifest but not in changelog
296 linkrevs = []
323 linkrevs = []
297
324
298 if linkrevs:
325 if linkrevs:
299 lr = linkrevs[0]
326 lr = linkrevs[0]
300 else:
327 else:
301 lr = None
328 lr = None
302
329
303 try:
330 try:
304 fl = repo.file(f)
331 fl = repo.file(f)
305 except error.RevlogError as e:
332 except error.RevlogError as e:
306 self.err(lr, _("broken revlog! (%s)") % e, f)
333 self.err(lr, _("broken revlog! (%s)") % e, f)
307 continue
334 continue
308
335
309 for ff in fl.files():
336 for ff in fl.files():
310 try:
337 try:
311 storefiles.remove(ff)
338 storefiles.remove(ff)
312 except KeyError:
339 except KeyError:
313 self.warn(_(" warning: revlog '%s' not in fncache!") % ff)
340 self.warn(_(" warning: revlog '%s' not in fncache!") % ff)
314 self.fncachewarned = True
341 self.fncachewarned = True
315
342
316 self.checklog(fl, f, lr)
343 self.checklog(fl, f, lr)
317 seen = {}
344 seen = {}
318 rp = None
345 rp = None
319 for i in fl:
346 for i in fl:
320 revisions += 1
347 revisions += 1
321 n = fl.node(i)
348 n = fl.node(i)
322 lr = self.checkentry(fl, i, n, seen, linkrevs, f)
349 lr = self.checkentry(fl, i, n, seen, linkrevs, f)
323 if f in filenodes:
350 if f in filenodes:
324 if havemf and n not in filenodes[f]:
351 if havemf and n not in filenodes[f]:
325 self.err(lr, _("%s not in manifests") % (short(n)), f)
352 self.err(lr, _("%s not in manifests") % (short(n)), f)
326 else:
353 else:
327 del filenodes[f][n]
354 del filenodes[f][n]
328
355
329 # verify contents
356 # verify contents
330 try:
357 try:
331 l = len(fl.read(n))
358 l = len(fl.read(n))
332 rp = fl.renamed(n)
359 rp = fl.renamed(n)
333 if l != fl.size(i):
360 if l != fl.size(i):
334 if len(fl.revision(n)) != fl.size(i):
361 if len(fl.revision(n)) != fl.size(i):
335 self.err(lr, _("unpacked size is %s, %s expected") %
362 self.err(lr, _("unpacked size is %s, %s expected") %
336 (l, fl.size(i)), f)
363 (l, fl.size(i)), f)
337 except error.CensoredNodeError:
364 except error.CensoredNodeError:
338 # experimental config: censor.policy
365 # experimental config: censor.policy
339 if ui.config("censor", "policy", "abort") == "abort":
366 if ui.config("censor", "policy", "abort") == "abort":
340 self.err(lr, _("censored file data"), f)
367 self.err(lr, _("censored file data"), f)
341 except Exception as inst:
368 except Exception as inst:
342 self.exc(lr, _("unpacking %s") % short(n), inst, f)
369 self.exc(lr, _("unpacking %s") % short(n), inst, f)
343
370
344 # check renames
371 # check renames
345 try:
372 try:
346 if rp:
373 if rp:
347 if lr is not None and ui.verbose:
374 if lr is not None and ui.verbose:
348 ctx = lrugetctx(lr)
375 ctx = lrugetctx(lr)
349 found = False
376 found = False
350 for pctx in ctx.parents():
377 for pctx in ctx.parents():
351 if rp[0] in pctx:
378 if rp[0] in pctx:
352 found = True
379 found = True
353 break
380 break
354 if not found:
381 if not found:
355 self.warn(_("warning: copy source of '%s' not"
382 self.warn(_("warning: copy source of '%s' not"
356 " in parents of %s") % (f, ctx))
383 " in parents of %s") % (f, ctx))
357 fl2 = repo.file(rp[0])
384 fl2 = repo.file(rp[0])
358 if not len(fl2):
385 if not len(fl2):
359 self.err(lr, _("empty or missing copy source "
386 self.err(lr, _("empty or missing copy source "
360 "revlog %s:%s") % (rp[0], short(rp[1])), f)
387 "revlog %s:%s") % (rp[0], short(rp[1])), f)
361 elif rp[1] == nullid:
388 elif rp[1] == nullid:
362 ui.note(_("warning: %s@%s: copy source"
389 ui.note(_("warning: %s@%s: copy source"
363 " revision is nullid %s:%s\n")
390 " revision is nullid %s:%s\n")
364 % (f, lr, rp[0], short(rp[1])))
391 % (f, lr, rp[0], short(rp[1])))
365 else:
392 else:
366 fl2.rev(rp[1])
393 fl2.rev(rp[1])
367 except Exception as inst:
394 except Exception as inst:
368 self.exc(lr, _("checking rename of %s") % short(n), inst, f)
395 self.exc(lr, _("checking rename of %s") % short(n), inst, f)
369
396
370 # cross-check
397 # cross-check
371 if f in filenodes:
398 if f in filenodes:
372 fns = [(lr, n) for n, lr in filenodes[f].iteritems()]
399 fns = [(lr, n) for n, lr in filenodes[f].iteritems()]
373 for lr, node in sorted(fns):
400 for lr, node in sorted(fns):
374 self.err(lr, _("manifest refers to unknown revision %s") %
401 self.err(lr, _("manifest refers to unknown revision %s") %
375 short(node), f)
402 short(node), f)
376 ui.progress(_('checking'), None)
403 ui.progress(_('checking'), None)
377
404
378 for f in storefiles:
405 for f in storefiles:
379 self.warn(_("warning: orphan revlog '%s'") % f)
406 self.warn(_("warning: orphan revlog '%s'") % f)
380
407
381 return len(files), revisions
408 return len(files), revisions
@@ -1,666 +1,724 b''
1 #require killdaemons
1 #require killdaemons
2
2
3 $ cat << EOF >> $HGRCPATH
3 $ cat << EOF >> $HGRCPATH
4 > [format]
4 > [format]
5 > usegeneraldelta=yes
5 > usegeneraldelta=yes
6 > [ui]
6 > [ui]
7 > ssh=python "$TESTDIR/dummyssh"
7 > ssh=python "$TESTDIR/dummyssh"
8 > EOF
8 > EOF
9
9
10 Set up repo
10 Set up repo
11
11
12 $ hg --config experimental.treemanifest=True init repo
12 $ hg --config experimental.treemanifest=True init repo
13 $ cd repo
13 $ cd repo
14
14
15 Requirements get set on init
15 Requirements get set on init
16
16
17 $ grep treemanifest .hg/requires
17 $ grep treemanifest .hg/requires
18 treemanifest
18 treemanifest
19
19
20 Without directories, looks like any other repo
20 Without directories, looks like any other repo
21
21
22 $ echo 0 > a
22 $ echo 0 > a
23 $ echo 0 > b
23 $ echo 0 > b
24 $ hg ci -Aqm initial
24 $ hg ci -Aqm initial
25 $ hg debugdata -m 0
25 $ hg debugdata -m 0
26 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
26 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
27 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
27 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
28
28
29 Submanifest is stored in separate revlog
29 Submanifest is stored in separate revlog
30
30
31 $ mkdir dir1
31 $ mkdir dir1
32 $ echo 1 > dir1/a
32 $ echo 1 > dir1/a
33 $ echo 1 > dir1/b
33 $ echo 1 > dir1/b
34 $ echo 1 > e
34 $ echo 1 > e
35 $ hg ci -Aqm 'add dir1'
35 $ hg ci -Aqm 'add dir1'
36 $ hg debugdata -m 1
36 $ hg debugdata -m 1
37 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
37 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
38 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
38 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
39 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44et (esc)
39 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44et (esc)
40 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
40 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
41 $ hg debugdata --dir dir1 0
41 $ hg debugdata --dir dir1 0
42 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
42 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
43 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
43 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
44
44
45 Can add nested directories
45 Can add nested directories
46
46
47 $ mkdir dir1/dir1
47 $ mkdir dir1/dir1
48 $ echo 2 > dir1/dir1/a
48 $ echo 2 > dir1/dir1/a
49 $ echo 2 > dir1/dir1/b
49 $ echo 2 > dir1/dir1/b
50 $ mkdir dir1/dir2
50 $ mkdir dir1/dir2
51 $ echo 2 > dir1/dir2/a
51 $ echo 2 > dir1/dir2/a
52 $ echo 2 > dir1/dir2/b
52 $ echo 2 > dir1/dir2/b
53 $ hg ci -Aqm 'add dir1/dir1'
53 $ hg ci -Aqm 'add dir1/dir1'
54 $ hg files -r .
54 $ hg files -r .
55 a
55 a
56 b
56 b
57 dir1/a (glob)
57 dir1/a (glob)
58 dir1/b (glob)
58 dir1/b (glob)
59 dir1/dir1/a (glob)
59 dir1/dir1/a (glob)
60 dir1/dir1/b (glob)
60 dir1/dir1/b (glob)
61 dir1/dir2/a (glob)
61 dir1/dir2/a (glob)
62 dir1/dir2/b (glob)
62 dir1/dir2/b (glob)
63 e
63 e
64
64
65 Revision is not created for unchanged directory
65 Revision is not created for unchanged directory
66
66
67 $ mkdir dir2
67 $ mkdir dir2
68 $ echo 3 > dir2/a
68 $ echo 3 > dir2/a
69 $ hg add dir2
69 $ hg add dir2
70 adding dir2/a (glob)
70 adding dir2/a (glob)
71 $ hg debugindex --dir dir1 > before
71 $ hg debugindex --dir dir1 > before
72 $ hg ci -qm 'add dir2'
72 $ hg ci -qm 'add dir2'
73 $ hg debugindex --dir dir1 > after
73 $ hg debugindex --dir dir1 > after
74 $ diff before after
74 $ diff before after
75 $ rm before after
75 $ rm before after
76
76
77 Removing directory does not create an revlog entry
77 Removing directory does not create an revlog entry
78
78
79 $ hg rm dir1/dir1
79 $ hg rm dir1/dir1
80 removing dir1/dir1/a (glob)
80 removing dir1/dir1/a (glob)
81 removing dir1/dir1/b (glob)
81 removing dir1/dir1/b (glob)
82 $ hg debugindex --dir dir1/dir1 > before
82 $ hg debugindex --dir dir1/dir1 > before
83 $ hg ci -qm 'remove dir1/dir1'
83 $ hg ci -qm 'remove dir1/dir1'
84 $ hg debugindex --dir dir1/dir1 > after
84 $ hg debugindex --dir dir1/dir1 > after
85 $ diff before after
85 $ diff before after
86 $ rm before after
86 $ rm before after
87
87
88 Check that hg files (calls treemanifest.walk()) works
88 Check that hg files (calls treemanifest.walk()) works
89 without loading all directory revlogs
89 without loading all directory revlogs
90
90
91 $ hg co 'desc("add dir2")'
91 $ hg co 'desc("add dir2")'
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
93 $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
94 $ hg files -r . dir1
94 $ hg files -r . dir1
95 dir1/a (glob)
95 dir1/a (glob)
96 dir1/b (glob)
96 dir1/b (glob)
97 dir1/dir1/a (glob)
97 dir1/dir1/a (glob)
98 dir1/dir1/b (glob)
98 dir1/dir1/b (glob)
99 dir1/dir2/a (glob)
99 dir1/dir2/a (glob)
100 dir1/dir2/b (glob)
100 dir1/dir2/b (glob)
101
101
102 Check that status between revisions works (calls treemanifest.matches())
102 Check that status between revisions works (calls treemanifest.matches())
103 without loading all directory revlogs
103 without loading all directory revlogs
104
104
105 $ hg status --rev 'desc("add dir1")' --rev . dir1
105 $ hg status --rev 'desc("add dir1")' --rev . dir1
106 A dir1/dir1/a
106 A dir1/dir1/a
107 A dir1/dir1/b
107 A dir1/dir1/b
108 A dir1/dir2/a
108 A dir1/dir2/a
109 A dir1/dir2/b
109 A dir1/dir2/b
110 $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
110 $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
111
111
112 Merge creates 2-parent revision of directory revlog
112 Merge creates 2-parent revision of directory revlog
113
113
114 $ echo 5 > dir1/a
114 $ echo 5 > dir1/a
115 $ hg ci -Aqm 'modify dir1/a'
115 $ hg ci -Aqm 'modify dir1/a'
116 $ hg co '.^'
116 $ hg co '.^'
117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 $ echo 6 > dir1/b
118 $ echo 6 > dir1/b
119 $ hg ci -Aqm 'modify dir1/b'
119 $ hg ci -Aqm 'modify dir1/b'
120 $ hg merge 'desc("modify dir1/a")'
120 $ hg merge 'desc("modify dir1/a")'
121 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
121 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 (branch merge, don't forget to commit)
122 (branch merge, don't forget to commit)
123 $ hg ci -m 'conflict-free merge involving dir1/'
123 $ hg ci -m 'conflict-free merge involving dir1/'
124 $ cat dir1/a
124 $ cat dir1/a
125 5
125 5
126 $ cat dir1/b
126 $ cat dir1/b
127 6
127 6
128 $ hg debugindex --dir dir1
128 $ hg debugindex --dir dir1
129 rev offset length delta linkrev nodeid p1 p2
129 rev offset length delta linkrev nodeid p1 p2
130 0 0 54 -1 1 8b3ffd73f901 000000000000 000000000000
130 0 0 54 -1 1 8b3ffd73f901 000000000000 000000000000
131 1 54 68 0 2 68e9d057c5a8 8b3ffd73f901 000000000000
131 1 54 68 0 2 68e9d057c5a8 8b3ffd73f901 000000000000
132 2 122 12 1 4 4698198d2624 68e9d057c5a8 000000000000
132 2 122 12 1 4 4698198d2624 68e9d057c5a8 000000000000
133 3 134 55 1 5 44844058ccce 68e9d057c5a8 000000000000
133 3 134 55 1 5 44844058ccce 68e9d057c5a8 000000000000
134 4 189 55 1 6 bf3d9b744927 68e9d057c5a8 000000000000
134 4 189 55 1 6 bf3d9b744927 68e9d057c5a8 000000000000
135 5 244 55 4 7 dde7c0af2a03 bf3d9b744927 44844058ccce
135 5 244 55 4 7 dde7c0af2a03 bf3d9b744927 44844058ccce
136
136
137 Merge keeping directory from parent 1 does not create revlog entry. (Note that
137 Merge keeping directory from parent 1 does not create revlog entry. (Note that
138 dir1's manifest does change, but only because dir1/a's filelog changes.)
138 dir1's manifest does change, but only because dir1/a's filelog changes.)
139
139
140 $ hg co 'desc("add dir2")'
140 $ hg co 'desc("add dir2")'
141 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 $ echo 8 > dir2/a
142 $ echo 8 > dir2/a
143 $ hg ci -m 'modify dir2/a'
143 $ hg ci -m 'modify dir2/a'
144 created new head
144 created new head
145
145
146 $ hg debugindex --dir dir2 > before
146 $ hg debugindex --dir dir2 > before
147 $ hg merge 'desc("modify dir1/a")'
147 $ hg merge 'desc("modify dir1/a")'
148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 (branch merge, don't forget to commit)
149 (branch merge, don't forget to commit)
150 $ hg revert -r 'desc("modify dir2/a")' .
150 $ hg revert -r 'desc("modify dir2/a")' .
151 reverting dir1/a (glob)
151 reverting dir1/a (glob)
152 $ hg ci -m 'merge, keeping parent 1'
152 $ hg ci -m 'merge, keeping parent 1'
153 $ hg debugindex --dir dir2 > after
153 $ hg debugindex --dir dir2 > after
154 $ diff before after
154 $ diff before after
155 $ rm before after
155 $ rm before after
156
156
157 Merge keeping directory from parent 2 does not create revlog entry. (Note that
157 Merge keeping directory from parent 2 does not create revlog entry. (Note that
158 dir2's manifest does change, but only because dir2/a's filelog changes.)
158 dir2's manifest does change, but only because dir2/a's filelog changes.)
159
159
160 $ hg co 'desc("modify dir2/a")'
160 $ hg co 'desc("modify dir2/a")'
161 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
161 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
162 $ hg debugindex --dir dir1 > before
162 $ hg debugindex --dir dir1 > before
163 $ hg merge 'desc("modify dir1/a")'
163 $ hg merge 'desc("modify dir1/a")'
164 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
165 (branch merge, don't forget to commit)
165 (branch merge, don't forget to commit)
166 $ hg revert -r 'desc("modify dir1/a")' .
166 $ hg revert -r 'desc("modify dir1/a")' .
167 reverting dir2/a (glob)
167 reverting dir2/a (glob)
168 $ hg ci -m 'merge, keeping parent 2'
168 $ hg ci -m 'merge, keeping parent 2'
169 created new head
169 created new head
170 $ hg debugindex --dir dir1 > after
170 $ hg debugindex --dir dir1 > after
171 $ diff before after
171 $ diff before after
172 $ rm before after
172 $ rm before after
173
173
174 Create flat source repo for tests with mixed flat/tree manifests
174 Create flat source repo for tests with mixed flat/tree manifests
175
175
176 $ cd ..
176 $ cd ..
177 $ hg init repo-flat
177 $ hg init repo-flat
178 $ cd repo-flat
178 $ cd repo-flat
179
179
180 Create a few commits with flat manifest
180 Create a few commits with flat manifest
181
181
182 $ echo 0 > a
182 $ echo 0 > a
183 $ echo 0 > b
183 $ echo 0 > b
184 $ echo 0 > e
184 $ echo 0 > e
185 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
185 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
186 > do
186 > do
187 > mkdir $d
187 > mkdir $d
188 > echo 0 > $d/a
188 > echo 0 > $d/a
189 > echo 0 > $d/b
189 > echo 0 > $d/b
190 > done
190 > done
191 $ hg ci -Aqm initial
191 $ hg ci -Aqm initial
192
192
193 $ echo 1 > a
193 $ echo 1 > a
194 $ echo 1 > dir1/a
194 $ echo 1 > dir1/a
195 $ echo 1 > dir1/dir1/a
195 $ echo 1 > dir1/dir1/a
196 $ hg ci -Aqm 'modify on branch 1'
196 $ hg ci -Aqm 'modify on branch 1'
197
197
198 $ hg co 0
198 $ hg co 0
199 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
199 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
200 $ echo 2 > b
200 $ echo 2 > b
201 $ echo 2 > dir1/b
201 $ echo 2 > dir1/b
202 $ echo 2 > dir1/dir1/b
202 $ echo 2 > dir1/dir1/b
203 $ hg ci -Aqm 'modify on branch 2'
203 $ hg ci -Aqm 'modify on branch 2'
204
204
205 $ hg merge 1
205 $ hg merge 1
206 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
206 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 (branch merge, don't forget to commit)
207 (branch merge, don't forget to commit)
208 $ hg ci -m 'merge of flat manifests to new flat manifest'
208 $ hg ci -m 'merge of flat manifests to new flat manifest'
209
209
210 $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
210 $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
211 $ cat hg.pid >> $DAEMON_PIDS
211 $ cat hg.pid >> $DAEMON_PIDS
212
212
213 Create clone with tree manifests enabled
213 Create clone with tree manifests enabled
214
214
215 $ cd ..
215 $ cd ..
216 $ hg clone --config experimental.treemanifest=1 \
216 $ hg clone --config experimental.treemanifest=1 \
217 > http://localhost:$HGPORT repo-mixed -r 1
217 > http://localhost:$HGPORT repo-mixed -r 1
218 adding changesets
218 adding changesets
219 adding manifests
219 adding manifests
220 adding file changes
220 adding file changes
221 added 2 changesets with 14 changes to 11 files
221 added 2 changesets with 14 changes to 11 files
222 updating to branch default
222 updating to branch default
223 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 $ cd repo-mixed
224 $ cd repo-mixed
225 $ test -d .hg/store/meta
225 $ test -d .hg/store/meta
226 [1]
226 [1]
227 $ grep treemanifest .hg/requires
227 $ grep treemanifest .hg/requires
228 treemanifest
228 treemanifest
229
229
230 Should be possible to push updates from flat to tree manifest repo
230 Should be possible to push updates from flat to tree manifest repo
231
231
232 $ hg -R ../repo-flat push ssh://user@dummy/repo-mixed
232 $ hg -R ../repo-flat push ssh://user@dummy/repo-mixed
233 pushing to ssh://user@dummy/repo-mixed
233 pushing to ssh://user@dummy/repo-mixed
234 searching for changes
234 searching for changes
235 remote: adding changesets
235 remote: adding changesets
236 remote: adding manifests
236 remote: adding manifests
237 remote: adding file changes
237 remote: adding file changes
238 remote: added 2 changesets with 3 changes to 3 files
238 remote: added 2 changesets with 3 changes to 3 files
239
239
240 Commit should store revlog per directory
240 Commit should store revlog per directory
241
241
242 $ hg co 1
242 $ hg co 1
243 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
243 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 $ echo 3 > a
244 $ echo 3 > a
245 $ echo 3 > dir1/a
245 $ echo 3 > dir1/a
246 $ echo 3 > dir1/dir1/a
246 $ echo 3 > dir1/dir1/a
247 $ hg ci -m 'first tree'
247 $ hg ci -m 'first tree'
248 created new head
248 created new head
249 $ find .hg/store/meta | sort
249 $ find .hg/store/meta | sort
250 .hg/store/meta
250 .hg/store/meta
251 .hg/store/meta/dir1
251 .hg/store/meta/dir1
252 .hg/store/meta/dir1/00manifest.i
252 .hg/store/meta/dir1/00manifest.i
253 .hg/store/meta/dir1/dir1
253 .hg/store/meta/dir1/dir1
254 .hg/store/meta/dir1/dir1/00manifest.i
254 .hg/store/meta/dir1/dir1/00manifest.i
255 .hg/store/meta/dir1/dir2
255 .hg/store/meta/dir1/dir2
256 .hg/store/meta/dir1/dir2/00manifest.i
256 .hg/store/meta/dir1/dir2/00manifest.i
257 .hg/store/meta/dir2
257 .hg/store/meta/dir2
258 .hg/store/meta/dir2/00manifest.i
258 .hg/store/meta/dir2/00manifest.i
259
259
260 Merge of two trees
260 Merge of two trees
261
261
262 $ hg co 2
262 $ hg co 2
263 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
263 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
264 $ hg merge 1
264 $ hg merge 1
265 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
265 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 (branch merge, don't forget to commit)
266 (branch merge, don't forget to commit)
267 $ hg ci -m 'merge of flat manifests to new tree manifest'
267 $ hg ci -m 'merge of flat manifests to new tree manifest'
268 created new head
268 created new head
269 $ hg diff -r 3
269 $ hg diff -r 3
270
270
271 Parent of tree root manifest should be flat manifest, and two for merge
271 Parent of tree root manifest should be flat manifest, and two for merge
272
272
273 $ hg debugindex -m
273 $ hg debugindex -m
274 rev offset length delta linkrev nodeid p1 p2
274 rev offset length delta linkrev nodeid p1 p2
275 0 0 80 -1 0 40536115ed9e 000000000000 000000000000
275 0 0 80 -1 0 40536115ed9e 000000000000 000000000000
276 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
276 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
277 2 163 89 0 2 5d9b9da231a2 40536115ed9e 000000000000
277 2 163 89 0 2 5d9b9da231a2 40536115ed9e 000000000000
278 3 252 83 2 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
278 3 252 83 2 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
279 4 335 124 1 4 51e32a8c60ee f3376063c255 000000000000
279 4 335 124 1 4 51e32a8c60ee f3376063c255 000000000000
280 5 459 126 2 5 cc5baa78b230 5d9b9da231a2 f3376063c255
280 5 459 126 2 5 cc5baa78b230 5d9b9da231a2 f3376063c255
281
281
282
282
283 Status across flat/tree boundary should work
283 Status across flat/tree boundary should work
284
284
285 $ hg status --rev '.^' --rev .
285 $ hg status --rev '.^' --rev .
286 M a
286 M a
287 M dir1/a
287 M dir1/a
288 M dir1/dir1/a
288 M dir1/dir1/a
289
289
290
290
291 Turning off treemanifest config has no effect
291 Turning off treemanifest config has no effect
292
292
293 $ hg debugindex --dir dir1
293 $ hg debugindex --dir dir1
294 rev offset length delta linkrev nodeid p1 p2
294 rev offset length delta linkrev nodeid p1 p2
295 0 0 127 -1 4 064927a0648a 000000000000 000000000000
295 0 0 127 -1 4 064927a0648a 000000000000 000000000000
296 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
296 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
297 $ echo 2 > dir1/a
297 $ echo 2 > dir1/a
298 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
298 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
299 $ hg debugindex --dir dir1
299 $ hg debugindex --dir dir1
300 rev offset length delta linkrev nodeid p1 p2
300 rev offset length delta linkrev nodeid p1 p2
301 0 0 127 -1 4 064927a0648a 000000000000 000000000000
301 0 0 127 -1 4 064927a0648a 000000000000 000000000000
302 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
302 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
303 2 238 55 1 6 5b16163a30c6 25ecb8cb8618 000000000000
303 2 238 55 1 6 5b16163a30c6 25ecb8cb8618 000000000000
304
304
305 Stripping and recovering changes should work
305 Stripping and recovering changes should work
306
306
307 $ hg st --change tip
307 $ hg st --change tip
308 M dir1/a
308 M dir1/a
309 $ hg --config extensions.strip= strip tip
309 $ hg --config extensions.strip= strip tip
310 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
310 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
311 saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/51cfd7b1e13b-78a2f3ed-backup.hg (glob)
311 saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/51cfd7b1e13b-78a2f3ed-backup.hg (glob)
312 $ hg unbundle -q .hg/strip-backup/*
312 $ hg unbundle -q .hg/strip-backup/*
313 $ hg st --change tip
313 $ hg st --change tip
314 M dir1/a
314 M dir1/a
315
315
316 Shelving and unshelving should work
316 Shelving and unshelving should work
317
317
318 $ echo foo >> dir1/a
318 $ echo foo >> dir1/a
319 $ hg --config extensions.shelve= shelve
319 $ hg --config extensions.shelve= shelve
320 shelved as default
320 shelved as default
321 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
321 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
322 $ hg --config extensions.shelve= unshelve
322 $ hg --config extensions.shelve= unshelve
323 unshelving change 'default'
323 unshelving change 'default'
324 $ hg diff --nodates
324 $ hg diff --nodates
325 diff -r 708a273da119 dir1/a
325 diff -r 708a273da119 dir1/a
326 --- a/dir1/a
326 --- a/dir1/a
327 +++ b/dir1/a
327 +++ b/dir1/a
328 @@ -1,1 +1,2 @@
328 @@ -1,1 +1,2 @@
329 1
329 1
330 +foo
330 +foo
331
331
332 Pushing from treemanifest repo to an empty repo makes that a treemanifest repo
332 Pushing from treemanifest repo to an empty repo makes that a treemanifest repo
333
333
334 $ cd ..
334 $ cd ..
335 $ hg init empty-repo
335 $ hg init empty-repo
336 $ cat << EOF >> empty-repo/.hg/hgrc
336 $ cat << EOF >> empty-repo/.hg/hgrc
337 > [experimental]
337 > [experimental]
338 > changegroup3=yes
338 > changegroup3=yes
339 > EOF
339 > EOF
340 $ grep treemanifest empty-repo/.hg/requires
340 $ grep treemanifest empty-repo/.hg/requires
341 [1]
341 [1]
342 $ hg push -R repo -r 0 empty-repo
342 $ hg push -R repo -r 0 empty-repo
343 pushing to empty-repo
343 pushing to empty-repo
344 searching for changes
344 searching for changes
345 adding changesets
345 adding changesets
346 adding manifests
346 adding manifests
347 adding file changes
347 adding file changes
348 added 1 changesets with 2 changes to 2 files
348 added 1 changesets with 2 changes to 2 files
349 $ grep treemanifest empty-repo/.hg/requires
349 $ grep treemanifest empty-repo/.hg/requires
350 treemanifest
350 treemanifest
351
351
352 Pushing to an empty repo works
352 Pushing to an empty repo works
353
353
354 $ hg --config experimental.treemanifest=1 init clone
354 $ hg --config experimental.treemanifest=1 init clone
355 $ grep treemanifest clone/.hg/requires
355 $ grep treemanifest clone/.hg/requires
356 treemanifest
356 treemanifest
357 $ hg push -R repo clone
357 $ hg push -R repo clone
358 pushing to clone
358 pushing to clone
359 searching for changes
359 searching for changes
360 adding changesets
360 adding changesets
361 adding manifests
361 adding manifests
362 adding file changes
362 adding file changes
363 added 11 changesets with 15 changes to 10 files (+3 heads)
363 added 11 changesets with 15 changes to 10 files (+3 heads)
364 $ grep treemanifest clone/.hg/requires
364 $ grep treemanifest clone/.hg/requires
365 treemanifest
365 treemanifest
366
366
367 Create deeper repo with tree manifests.
367 Create deeper repo with tree manifests.
368
368
369 $ hg --config experimental.treemanifest=True init deeprepo
369 $ hg --config experimental.treemanifest=True init deeprepo
370 $ cd deeprepo
370 $ cd deeprepo
371
371
372 $ mkdir .A
372 $ mkdir .A
373 $ mkdir b
373 $ mkdir b
374 $ mkdir b/bar
374 $ mkdir b/bar
375 $ mkdir b/bar/orange
375 $ mkdir b/bar/orange
376 $ mkdir b/bar/orange/fly
376 $ mkdir b/bar/orange/fly
377 $ mkdir b/foo
377 $ mkdir b/foo
378 $ mkdir b/foo/apple
378 $ mkdir b/foo/apple
379 $ mkdir b/foo/apple/bees
379 $ mkdir b/foo/apple/bees
380
380
381 $ touch .A/one.txt
381 $ touch .A/one.txt
382 $ touch .A/two.txt
382 $ touch .A/two.txt
383 $ touch b/bar/fruits.txt
383 $ touch b/bar/fruits.txt
384 $ touch b/bar/orange/fly/gnat.py
384 $ touch b/bar/orange/fly/gnat.py
385 $ touch b/bar/orange/fly/housefly.txt
385 $ touch b/bar/orange/fly/housefly.txt
386 $ touch b/foo/apple/bees/flower.py
386 $ touch b/foo/apple/bees/flower.py
387 $ touch c.txt
387 $ touch c.txt
388 $ touch d.py
388 $ touch d.py
389
389
390 $ hg ci -Aqm 'initial'
390 $ hg ci -Aqm 'initial'
391
391
392 We'll see that visitdir works by removing some treemanifest revlogs and running
392 We'll see that visitdir works by removing some treemanifest revlogs and running
393 the files command with various parameters.
393 the files command with various parameters.
394
394
395 Test files from the root.
395 Test files from the root.
396
396
397 $ hg files -r .
397 $ hg files -r .
398 .A/one.txt (glob)
398 .A/one.txt (glob)
399 .A/two.txt (glob)
399 .A/two.txt (glob)
400 b/bar/fruits.txt (glob)
400 b/bar/fruits.txt (glob)
401 b/bar/orange/fly/gnat.py (glob)
401 b/bar/orange/fly/gnat.py (glob)
402 b/bar/orange/fly/housefly.txt (glob)
402 b/bar/orange/fly/housefly.txt (glob)
403 b/foo/apple/bees/flower.py (glob)
403 b/foo/apple/bees/flower.py (glob)
404 c.txt
404 c.txt
405 d.py
405 d.py
406
406
407 Excludes with a glob should not exclude everything from the glob's root
407 Excludes with a glob should not exclude everything from the glob's root
408
408
409 $ hg files -r . -X 'b/fo?' b
409 $ hg files -r . -X 'b/fo?' b
410 b/bar/fruits.txt (glob)
410 b/bar/fruits.txt (glob)
411 b/bar/orange/fly/gnat.py (glob)
411 b/bar/orange/fly/gnat.py (glob)
412 b/bar/orange/fly/housefly.txt (glob)
412 b/bar/orange/fly/housefly.txt (glob)
413 $ cp -r .hg/store .hg/store-copy
413 $ cp -r .hg/store .hg/store-copy
414
414
415 Test files for a subdirectory.
415 Test files for a subdirectory.
416
416
417 $ rm -r .hg/store/meta/~2e_a
417 $ rm -r .hg/store/meta/~2e_a
418 $ hg files -r . b
418 $ hg files -r . b
419 b/bar/fruits.txt (glob)
419 b/bar/fruits.txt (glob)
420 b/bar/orange/fly/gnat.py (glob)
420 b/bar/orange/fly/gnat.py (glob)
421 b/bar/orange/fly/housefly.txt (glob)
421 b/bar/orange/fly/housefly.txt (glob)
422 b/foo/apple/bees/flower.py (glob)
422 b/foo/apple/bees/flower.py (glob)
423 $ cp -r .hg/store-copy/* .hg/store
423 $ cp -r .hg/store-copy/* .hg/store
424
424
425 Test files with just includes and excludes.
425 Test files with just includes and excludes.
426
426
427 $ rm -r .hg/store/meta/~2e_a
427 $ rm -r .hg/store/meta/~2e_a
428 $ rm -r .hg/store/meta/b/bar/orange/fly
428 $ rm -r .hg/store/meta/b/bar/orange/fly
429 $ rm -r .hg/store/meta/b/foo/apple/bees
429 $ rm -r .hg/store/meta/b/foo/apple/bees
430 $ hg files -r . -I path:b/bar -X path:b/bar/orange/fly -I path:b/foo -X path:b/foo/apple/bees
430 $ hg files -r . -I path:b/bar -X path:b/bar/orange/fly -I path:b/foo -X path:b/foo/apple/bees
431 b/bar/fruits.txt (glob)
431 b/bar/fruits.txt (glob)
432 $ cp -r .hg/store-copy/* .hg/store
432 $ cp -r .hg/store-copy/* .hg/store
433
433
434 Test files for a subdirectory, excluding a directory within it.
434 Test files for a subdirectory, excluding a directory within it.
435
435
436 $ rm -r .hg/store/meta/~2e_a
436 $ rm -r .hg/store/meta/~2e_a
437 $ rm -r .hg/store/meta/b/foo
437 $ rm -r .hg/store/meta/b/foo
438 $ hg files -r . -X path:b/foo b
438 $ hg files -r . -X path:b/foo b
439 b/bar/fruits.txt (glob)
439 b/bar/fruits.txt (glob)
440 b/bar/orange/fly/gnat.py (glob)
440 b/bar/orange/fly/gnat.py (glob)
441 b/bar/orange/fly/housefly.txt (glob)
441 b/bar/orange/fly/housefly.txt (glob)
442 $ cp -r .hg/store-copy/* .hg/store
442 $ cp -r .hg/store-copy/* .hg/store
443
443
444 Test files for a sub directory, including only a directory within it, and
444 Test files for a sub directory, including only a directory within it, and
445 including an unrelated directory.
445 including an unrelated directory.
446
446
447 $ rm -r .hg/store/meta/~2e_a
447 $ rm -r .hg/store/meta/~2e_a
448 $ rm -r .hg/store/meta/b/foo
448 $ rm -r .hg/store/meta/b/foo
449 $ hg files -r . -I path:b/bar/orange -I path:a b
449 $ hg files -r . -I path:b/bar/orange -I path:a b
450 b/bar/orange/fly/gnat.py (glob)
450 b/bar/orange/fly/gnat.py (glob)
451 b/bar/orange/fly/housefly.txt (glob)
451 b/bar/orange/fly/housefly.txt (glob)
452 $ cp -r .hg/store-copy/* .hg/store
452 $ cp -r .hg/store-copy/* .hg/store
453
453
454 Test files for a pattern, including a directory, and excluding a directory
454 Test files for a pattern, including a directory, and excluding a directory
455 within that.
455 within that.
456
456
457 $ rm -r .hg/store/meta/~2e_a
457 $ rm -r .hg/store/meta/~2e_a
458 $ rm -r .hg/store/meta/b/foo
458 $ rm -r .hg/store/meta/b/foo
459 $ rm -r .hg/store/meta/b/bar/orange
459 $ rm -r .hg/store/meta/b/bar/orange
460 $ hg files -r . glob:**.txt -I path:b/bar -X path:b/bar/orange
460 $ hg files -r . glob:**.txt -I path:b/bar -X path:b/bar/orange
461 b/bar/fruits.txt (glob)
461 b/bar/fruits.txt (glob)
462 $ cp -r .hg/store-copy/* .hg/store
462 $ cp -r .hg/store-copy/* .hg/store
463
463
464 Add some more changes to the deep repo
464 Add some more changes to the deep repo
465 $ echo narf >> b/bar/fruits.txt
465 $ echo narf >> b/bar/fruits.txt
466 $ hg ci -m narf
466 $ hg ci -m narf
467 $ echo troz >> b/bar/orange/fly/gnat.py
467 $ echo troz >> b/bar/orange/fly/gnat.py
468 $ hg ci -m troz
468 $ hg ci -m troz
469
469
470 Verify works
470 Verify works
471 $ hg verify
471 $ hg verify
472 checking changesets
472 checking changesets
473 checking manifests
473 checking manifests
474 checking directory manifests
474 crosschecking files in changesets and manifests
475 crosschecking files in changesets and manifests
475 checking files
476 checking files
476 8 files, 3 changesets, 10 total revisions
477 8 files, 3 changesets, 10 total revisions
477
478
478 Dirlogs are included in fncache
479 Dirlogs are included in fncache
479 $ grep meta/.A/00manifest.i .hg/store/fncache
480 $ grep meta/.A/00manifest.i .hg/store/fncache
480 meta/.A/00manifest.i
481 meta/.A/00manifest.i
481
482
482 Rebuilt fncache includes dirlogs
483 Rebuilt fncache includes dirlogs
483 $ rm .hg/store/fncache
484 $ rm .hg/store/fncache
484 $ hg debugrebuildfncache
485 $ hg debugrebuildfncache
485 adding data/.A/one.txt.i
486 adding data/.A/one.txt.i
486 adding data/.A/two.txt.i
487 adding data/.A/two.txt.i
487 adding data/b/bar/fruits.txt.i
488 adding data/b/bar/fruits.txt.i
488 adding data/b/bar/orange/fly/gnat.py.i
489 adding data/b/bar/orange/fly/gnat.py.i
489 adding data/b/bar/orange/fly/housefly.txt.i
490 adding data/b/bar/orange/fly/housefly.txt.i
490 adding data/b/foo/apple/bees/flower.py.i
491 adding data/b/foo/apple/bees/flower.py.i
491 adding data/c.txt.i
492 adding data/c.txt.i
492 adding data/d.py.i
493 adding data/d.py.i
493 adding meta/.A/00manifest.i
494 adding meta/.A/00manifest.i
494 adding meta/b/00manifest.i
495 adding meta/b/00manifest.i
495 adding meta/b/bar/00manifest.i
496 adding meta/b/bar/00manifest.i
496 adding meta/b/bar/orange/00manifest.i
497 adding meta/b/bar/orange/00manifest.i
497 adding meta/b/bar/orange/fly/00manifest.i
498 adding meta/b/bar/orange/fly/00manifest.i
498 adding meta/b/foo/00manifest.i
499 adding meta/b/foo/00manifest.i
499 adding meta/b/foo/apple/00manifest.i
500 adding meta/b/foo/apple/00manifest.i
500 adding meta/b/foo/apple/bees/00manifest.i
501 adding meta/b/foo/apple/bees/00manifest.i
501 16 items added, 0 removed from fncache
502 16 items added, 0 removed from fncache
502
503
503 Finish first server
504 Finish first server
504 $ killdaemons.py
505 $ killdaemons.py
505
506
507 Back up the recently added revlogs
508 $ cp -r .hg/store .hg/store-newcopy
509
510 Verify reports missing dirlog
511 $ rm .hg/store/meta/b/00manifest.*
512 $ hg verify
513 checking changesets
514 checking manifests
515 checking directory manifests
516 0: empty or missing b/
517 b/@0: parent-directory manifest refers to unknown revision 67688a370455
518 b/@1: parent-directory manifest refers to unknown revision f38e85d334c5
519 b/@2: parent-directory manifest refers to unknown revision 99c9792fd4b0
520 crosschecking files in changesets and manifests
521 b/bar/fruits.txt@0: in changeset but not in manifest
522 b/bar/orange/fly/gnat.py@0: in changeset but not in manifest
523 b/bar/orange/fly/housefly.txt@0: in changeset but not in manifest
524 b/foo/apple/bees/flower.py@0: in changeset but not in manifest
525 checking files
526 8 files, 3 changesets, 10 total revisions
527 8 integrity errors encountered!
528 (first damaged changeset appears to be 0)
529 [1]
530 $ cp -rT .hg/store-newcopy .hg/store
531
532 Verify reports missing dirlog entry
533 $ mv -f .hg/store-copy/meta/b/00manifest.* .hg/store/meta/b/
534 $ hg verify
535 checking changesets
536 checking manifests
537 checking directory manifests
538 b/@1: parent-directory manifest refers to unknown revision f38e85d334c5
539 b/@2: parent-directory manifest refers to unknown revision 99c9792fd4b0
540 b/bar/@?: rev 1 points to unexpected changeset 1
541 b/bar/@?: 5e03c4ee5e4a not in parent-directory manifest
542 b/bar/@?: rev 2 points to unexpected changeset 2
543 b/bar/@?: 1b16940d66d6 not in parent-directory manifest
544 b/bar/orange/@?: rev 1 points to unexpected changeset 2
545 (expected None)
546 b/bar/orange/fly/@?: rev 1 points to unexpected changeset 2
547 (expected None)
548 crosschecking files in changesets and manifests
549 checking files
550 8 files, 3 changesets, 10 total revisions
551 2 warnings encountered!
552 8 integrity errors encountered!
553 (first damaged changeset appears to be 1)
554 [1]
555 $ cp -rT .hg/store-newcopy .hg/store
556
506 Test cloning a treemanifest repo over http.
557 Test cloning a treemanifest repo over http.
507 $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
558 $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
508 $ cat hg.pid >> $DAEMON_PIDS
559 $ cat hg.pid >> $DAEMON_PIDS
509 $ cd ..
560 $ cd ..
510 We can clone even with the knob turned off and we'll get a treemanifest repo.
561 We can clone even with the knob turned off and we'll get a treemanifest repo.
511 $ hg clone --config experimental.treemanifest=False \
562 $ hg clone --config experimental.treemanifest=False \
512 > --config experimental.changegroup3=True \
563 > --config experimental.changegroup3=True \
513 > http://localhost:$HGPORT deepclone
564 > http://localhost:$HGPORT deepclone
514 requesting all changes
565 requesting all changes
515 adding changesets
566 adding changesets
516 adding manifests
567 adding manifests
517 adding file changes
568 adding file changes
518 added 3 changesets with 10 changes to 8 files
569 added 3 changesets with 10 changes to 8 files
519 updating to branch default
570 updating to branch default
520 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
571 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
521 No server errors.
572 No server errors.
522 $ cat deeprepo/errors.log
573 $ cat deeprepo/errors.log
523 requires got updated to include treemanifest
574 requires got updated to include treemanifest
524 $ cat deepclone/.hg/requires | grep treemanifest
575 $ cat deepclone/.hg/requires | grep treemanifest
525 treemanifest
576 treemanifest
526 Tree manifest revlogs exist.
577 Tree manifest revlogs exist.
527 $ find deepclone/.hg/store/meta | sort
578 $ find deepclone/.hg/store/meta | sort
528 deepclone/.hg/store/meta
579 deepclone/.hg/store/meta
529 deepclone/.hg/store/meta/b
580 deepclone/.hg/store/meta/b
530 deepclone/.hg/store/meta/b/00manifest.i
581 deepclone/.hg/store/meta/b/00manifest.i
531 deepclone/.hg/store/meta/b/bar
582 deepclone/.hg/store/meta/b/bar
532 deepclone/.hg/store/meta/b/bar/00manifest.i
583 deepclone/.hg/store/meta/b/bar/00manifest.i
533 deepclone/.hg/store/meta/b/bar/orange
584 deepclone/.hg/store/meta/b/bar/orange
534 deepclone/.hg/store/meta/b/bar/orange/00manifest.i
585 deepclone/.hg/store/meta/b/bar/orange/00manifest.i
535 deepclone/.hg/store/meta/b/bar/orange/fly
586 deepclone/.hg/store/meta/b/bar/orange/fly
536 deepclone/.hg/store/meta/b/bar/orange/fly/00manifest.i
587 deepclone/.hg/store/meta/b/bar/orange/fly/00manifest.i
537 deepclone/.hg/store/meta/b/foo
588 deepclone/.hg/store/meta/b/foo
538 deepclone/.hg/store/meta/b/foo/00manifest.i
589 deepclone/.hg/store/meta/b/foo/00manifest.i
539 deepclone/.hg/store/meta/b/foo/apple
590 deepclone/.hg/store/meta/b/foo/apple
540 deepclone/.hg/store/meta/b/foo/apple/00manifest.i
591 deepclone/.hg/store/meta/b/foo/apple/00manifest.i
541 deepclone/.hg/store/meta/b/foo/apple/bees
592 deepclone/.hg/store/meta/b/foo/apple/bees
542 deepclone/.hg/store/meta/b/foo/apple/bees/00manifest.i
593 deepclone/.hg/store/meta/b/foo/apple/bees/00manifest.i
543 deepclone/.hg/store/meta/~2e_a
594 deepclone/.hg/store/meta/~2e_a
544 deepclone/.hg/store/meta/~2e_a/00manifest.i
595 deepclone/.hg/store/meta/~2e_a/00manifest.i
545 Verify passes.
596 Verify passes.
546 $ cd deepclone
597 $ cd deepclone
547 $ hg verify
598 $ hg verify
548 checking changesets
599 checking changesets
549 checking manifests
600 checking manifests
601 checking directory manifests
550 crosschecking files in changesets and manifests
602 crosschecking files in changesets and manifests
551 checking files
603 checking files
552 8 files, 3 changesets, 10 total revisions
604 8 files, 3 changesets, 10 total revisions
553 $ cd ..
605 $ cd ..
554
606
555 Create clones using old repo formats to use in later tests
607 Create clones using old repo formats to use in later tests
556 $ hg clone --config format.usestore=False \
608 $ hg clone --config format.usestore=False \
557 > --config experimental.changegroup3=True \
609 > --config experimental.changegroup3=True \
558 > http://localhost:$HGPORT deeprepo-basicstore
610 > http://localhost:$HGPORT deeprepo-basicstore
559 requesting all changes
611 requesting all changes
560 adding changesets
612 adding changesets
561 adding manifests
613 adding manifests
562 adding file changes
614 adding file changes
563 added 3 changesets with 10 changes to 8 files
615 added 3 changesets with 10 changes to 8 files
564 updating to branch default
616 updating to branch default
565 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
617 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
566 $ cd deeprepo-basicstore
618 $ cd deeprepo-basicstore
567 $ grep store .hg/requires
619 $ grep store .hg/requires
568 [1]
620 [1]
569 $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --errorlog=errors.log
621 $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --errorlog=errors.log
570 $ cat hg.pid >> $DAEMON_PIDS
622 $ cat hg.pid >> $DAEMON_PIDS
571 $ cd ..
623 $ cd ..
572 $ hg clone --config format.usefncache=False \
624 $ hg clone --config format.usefncache=False \
573 > --config experimental.changegroup3=True \
625 > --config experimental.changegroup3=True \
574 > http://localhost:$HGPORT deeprepo-encodedstore
626 > http://localhost:$HGPORT deeprepo-encodedstore
575 requesting all changes
627 requesting all changes
576 adding changesets
628 adding changesets
577 adding manifests
629 adding manifests
578 adding file changes
630 adding file changes
579 added 3 changesets with 10 changes to 8 files
631 added 3 changesets with 10 changes to 8 files
580 updating to branch default
632 updating to branch default
581 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
633 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
582 $ cd deeprepo-encodedstore
634 $ cd deeprepo-encodedstore
583 $ grep fncache .hg/requires
635 $ grep fncache .hg/requires
584 [1]
636 [1]
585 $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --errorlog=errors.log
637 $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --errorlog=errors.log
586 $ cat hg.pid >> $DAEMON_PIDS
638 $ cat hg.pid >> $DAEMON_PIDS
587 $ cd ..
639 $ cd ..
588
640
589 Local clone with basicstore
641 Local clone with basicstore
590 $ hg clone -U deeprepo-basicstore local-clone-basicstore
642 $ hg clone -U deeprepo-basicstore local-clone-basicstore
591 $ hg -R local-clone-basicstore verify
643 $ hg -R local-clone-basicstore verify
592 checking changesets
644 checking changesets
593 checking manifests
645 checking manifests
646 checking directory manifests
594 crosschecking files in changesets and manifests
647 crosschecking files in changesets and manifests
595 checking files
648 checking files
596 8 files, 3 changesets, 10 total revisions
649 8 files, 3 changesets, 10 total revisions
597
650
598 Local clone with encodedstore
651 Local clone with encodedstore
599 $ hg clone -U deeprepo-encodedstore local-clone-encodedstore
652 $ hg clone -U deeprepo-encodedstore local-clone-encodedstore
600 $ hg -R local-clone-encodedstore verify
653 $ hg -R local-clone-encodedstore verify
601 checking changesets
654 checking changesets
602 checking manifests
655 checking manifests
656 checking directory manifests
603 crosschecking files in changesets and manifests
657 crosschecking files in changesets and manifests
604 checking files
658 checking files
605 8 files, 3 changesets, 10 total revisions
659 8 files, 3 changesets, 10 total revisions
606
660
607 Local clone with fncachestore
661 Local clone with fncachestore
608 $ hg clone -U deeprepo local-clone-fncachestore
662 $ hg clone -U deeprepo local-clone-fncachestore
609 $ hg -R local-clone-fncachestore verify
663 $ hg -R local-clone-fncachestore verify
610 checking changesets
664 checking changesets
611 checking manifests
665 checking manifests
666 checking directory manifests
612 crosschecking files in changesets and manifests
667 crosschecking files in changesets and manifests
613 checking files
668 checking files
614 8 files, 3 changesets, 10 total revisions
669 8 files, 3 changesets, 10 total revisions
615
670
616 Stream clone with basicstore
671 Stream clone with basicstore
617 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
672 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
618 > http://localhost:$HGPORT1 stream-clone-basicstore
673 > http://localhost:$HGPORT1 stream-clone-basicstore
619 streaming all changes
674 streaming all changes
620 18 files to transfer, * of data (glob)
675 18 files to transfer, * of data (glob)
621 transferred * in * seconds (*) (glob)
676 transferred * in * seconds (*) (glob)
622 searching for changes
677 searching for changes
623 no changes found
678 no changes found
624 $ hg -R stream-clone-basicstore verify
679 $ hg -R stream-clone-basicstore verify
625 checking changesets
680 checking changesets
626 checking manifests
681 checking manifests
682 checking directory manifests
627 crosschecking files in changesets and manifests
683 crosschecking files in changesets and manifests
628 checking files
684 checking files
629 8 files, 3 changesets, 10 total revisions
685 8 files, 3 changesets, 10 total revisions
630
686
631 Stream clone with encodedstore
687 Stream clone with encodedstore
632 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
688 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
633 > http://localhost:$HGPORT2 stream-clone-encodedstore
689 > http://localhost:$HGPORT2 stream-clone-encodedstore
634 streaming all changes
690 streaming all changes
635 18 files to transfer, * of data (glob)
691 18 files to transfer, * of data (glob)
636 transferred * in * seconds (*) (glob)
692 transferred * in * seconds (*) (glob)
637 searching for changes
693 searching for changes
638 no changes found
694 no changes found
639 $ hg -R stream-clone-encodedstore verify
695 $ hg -R stream-clone-encodedstore verify
640 checking changesets
696 checking changesets
641 checking manifests
697 checking manifests
698 checking directory manifests
642 crosschecking files in changesets and manifests
699 crosschecking files in changesets and manifests
643 checking files
700 checking files
644 8 files, 3 changesets, 10 total revisions
701 8 files, 3 changesets, 10 total revisions
645
702
646 Stream clone with fncachestore
703 Stream clone with fncachestore
647 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
704 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
648 > http://localhost:$HGPORT stream-clone-fncachestore
705 > http://localhost:$HGPORT stream-clone-fncachestore
649 streaming all changes
706 streaming all changes
650 18 files to transfer, * of data (glob)
707 18 files to transfer, * of data (glob)
651 transferred * in * seconds (*) (glob)
708 transferred * in * seconds (*) (glob)
652 searching for changes
709 searching for changes
653 no changes found
710 no changes found
654 $ hg -R stream-clone-fncachestore verify
711 $ hg -R stream-clone-fncachestore verify
655 checking changesets
712 checking changesets
656 checking manifests
713 checking manifests
714 checking directory manifests
657 crosschecking files in changesets and manifests
715 crosschecking files in changesets and manifests
658 checking files
716 checking files
659 8 files, 3 changesets, 10 total revisions
717 8 files, 3 changesets, 10 total revisions
660
718
661 Packed bundle
719 Packed bundle
662 $ hg -R deeprepo debugcreatestreamclonebundle repo-packed.hg
720 $ hg -R deeprepo debugcreatestreamclonebundle repo-packed.hg
663 writing 3349 bytes for 18 files
721 writing 3349 bytes for 18 files
664 bundle requirements: generaldelta, revlogv1, treemanifest
722 bundle requirements: generaldelta, revlogv1, treemanifest
665 $ hg debugbundle --spec repo-packed.hg
723 $ hg debugbundle --spec repo-packed.hg
666 none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Ctreemanifest
724 none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Ctreemanifest
General Comments 0
You need to be logged in to leave comments. Login now