##// END OF EJS Templates
treemanifest: store submanifest revlog per directory...
Martin von Zweigbergk -
r25091:b5052fc7 default
parent child Browse files
Show More
@@ -0,0 +1,278 b''
1
2 Set up repo
3
4 $ hg --config experimental.treemanifest=True init repo
5 $ cd repo
6
7 Requirements get set on init
8
9 $ grep treemanifest .hg/requires
10 treemanifest
11
12 Without directories, looks like any other repo
13
14 $ echo 0 > a
15 $ echo 0 > b
16 $ hg ci -Aqm initial
17 $ hg debugdata -m 0
18 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
19 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
20
21 Submanifest is stored in separate revlog
22
23 $ mkdir dir1
24 $ echo 1 > dir1/a
25 $ echo 1 > dir1/b
26 $ echo 1 > e
27 $ hg ci -Aqm 'add dir1'
28 $ hg debugdata -m 1
29 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
30 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
31 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc)
32 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
33 $ hg debugdata .hg/store/meta/dir1/00manifest.i 0
34 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
35 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
36
37 Can add nested directories
38
39 $ mkdir dir1/dir1
40 $ echo 2 > dir1/dir1/a
41 $ echo 2 > dir1/dir1/b
42 $ mkdir dir1/dir2
43 $ echo 2 > dir1/dir2/a
44 $ echo 2 > dir1/dir2/b
45 $ hg ci -Aqm 'add dir1/dir1'
46 $ hg files -r .
47 a
48 b
49 dir1/a
50 dir1/b
51 dir1/dir1/a
52 dir1/dir1/b
53 dir1/dir2/a
54 dir1/dir2/b
55 e
56
57 Revision is not created for unchanged directory
58
59 $ mkdir dir2
60 $ echo 3 > dir2/a
61 $ hg add dir2
62 adding dir2/a
63 $ hg debugindex .hg/store/meta/dir1/00manifest.i > before
64 $ hg ci -qm 'add dir2'
65 $ hg debugindex .hg/store/meta/dir1/00manifest.i > after
66 $ diff before after
67 $ rm before after
68
69 Removing directory does not create an revlog entry
70
71 $ hg rm dir1/dir1
72 removing dir1/dir1/a
73 removing dir1/dir1/b
74 $ hg debugindex .hg/store/meta/dir1/dir1/00manifest.i > before
75 $ hg ci -qm 'remove dir1/dir1'
76 $ hg debugindex .hg/store/meta/dir1/dir1/00manifest.i > after
77 $ diff before after
78 $ rm before after
79
80 Check that hg files (calls treemanifest.walk()) works
81
82 $ hg co 'desc("add dir2")'
83 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 $ hg files -r . dir1
85 dir1/a
86 dir1/b
87 dir1/dir1/a
88 dir1/dir1/b
89 dir1/dir2/a
90 dir1/dir2/b
91
92 Check that status between revisions works (calls treemanifest.matches())
93
94 $ hg status --rev 'desc("add dir1")' --rev . dir1
95 A dir1/dir1/a
96 A dir1/dir1/b
97 A dir1/dir2/a
98 A dir1/dir2/b
99
100 Merge creates 2-parent revision of directory revlog
101
102 $ echo 5 > dir1/a
103 $ hg ci -Aqm 'modify dir1/a'
104 $ hg co '.^'
105 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
106 $ echo 6 > dir1/b
107 $ hg ci -Aqm 'modify dir1/b'
108 $ hg merge 'desc("modify dir1/a")'
109 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 (branch merge, don't forget to commit)
111 $ hg ci -m 'conflict-free merge involving dir1/'
112 $ cat dir1/a
113 5
114 $ cat dir1/b
115 6
116 $ hg debugindex .hg/store/meta/dir1/00manifest.i
117 rev offset length base linkrev nodeid p1 p2
118 0 0 54 0 1 8b3ffd73f901 000000000000 000000000000
119 1 54 68 0 2 b66d046c644f 8b3ffd73f901 000000000000
120 2 122 12 0 4 b87265673c8a b66d046c644f 000000000000
121 3 134 95 0 5 aa5d3adcec72 b66d046c644f 000000000000
122 4 229 81 0 6 e29b066b91ad b66d046c644f 000000000000
123 5 310 107 5 7 a120ce2b83f5 e29b066b91ad aa5d3adcec72
124
125 Merge keeping directory from parent 1 does not create revlog entry. (Note that
126 dir1's manifest does change, but only because dir1/a's filelog changes.)
127
128 $ hg co 'desc("add dir2")'
129 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
130 $ echo 8 > dir2/a
131 $ hg ci -m 'modify dir2/a'
132 created new head
133
134 $ hg debugindex .hg/store/meta/dir2/00manifest.i > before
135 $ hg merge 'desc("modify dir1/a")'
136 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 (branch merge, don't forget to commit)
138 $ hg revert -r 'desc("modify dir2/a")' .
139 reverting dir1/a (glob)
140 $ hg ci -m 'merge, keeping parent 1'
141 $ hg debugindex .hg/store/meta/dir2/00manifest.i > after
142 $ diff before after
143 $ rm before after
144
145 Merge keeping directory from parent 2 does not create revlog entry. (Note that
146 dir2's manifest does change, but only because dir2/a's filelog changes.)
147
148 $ hg co 'desc("modify dir2/a")'
149 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 $ hg debugindex .hg/store/meta/dir1/00manifest.i > before
151 $ hg merge 'desc("modify dir1/a")'
152 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 (branch merge, don't forget to commit)
154 $ hg revert -r 'desc("modify dir1/a")' .
155 reverting dir2/a (glob)
156 $ hg ci -m 'merge, keeping parent 2'
157 created new head
158 $ hg debugindex .hg/store/meta/dir1/00manifest.i > after
159 $ diff before after
160 $ rm before after
161
162 Create flat source repo for tests with mixed flat/tree manifests
163
164 $ cd ..
165 $ hg init repo-flat
166 $ cd repo-flat
167
168 Create a few commits with flat manifest
169
170 $ echo 0 > a
171 $ echo 0 > b
172 $ echo 0 > e
173 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
174 > do
175 > mkdir $d
176 > echo 0 > $d/a
177 > echo 0 > $d/b
178 > done
179 $ hg ci -Aqm initial
180
181 $ echo 1 > a
182 $ echo 1 > dir1/a
183 $ echo 1 > dir1/dir1/a
184 $ hg ci -Aqm 'modify on branch 1'
185
186 $ hg co 0
187 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
188 $ echo 2 > b
189 $ echo 2 > dir1/b
190 $ echo 2 > dir1/dir1/b
191 $ hg ci -Aqm 'modify on branch 2'
192
193 $ hg merge 1
194 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 (branch merge, don't forget to commit)
196 $ hg ci -m 'merge of flat manifests to new flat manifest'
197
198 Create clone with tree manifests enabled
199
200 $ cd ..
201 $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed
202 requesting all changes
203 adding changesets
204 adding manifests
205 adding file changes
206 added 4 changesets with 17 changes to 11 files
207 updating to branch default
208 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
209 $ cd repo-mixed
210 $ test -f .hg/store/meta
211 [1]
212 $ grep treemanifest .hg/requires
213 treemanifest
214
215 Commit should store revlog per directory
216
217 $ hg co 1
218 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
219 $ echo 3 > a
220 $ echo 3 > dir1/a
221 $ echo 3 > dir1/dir1/a
222 $ hg ci -m 'first tree'
223 created new head
224 $ find .hg/store/meta | sort
225 .hg/store/meta
226 .hg/store/meta/dir1
227 .hg/store/meta/dir1/00manifest.i
228 .hg/store/meta/dir1/dir1
229 .hg/store/meta/dir1/dir1/00manifest.i
230 .hg/store/meta/dir1/dir2
231 .hg/store/meta/dir1/dir2/00manifest.i
232 .hg/store/meta/dir2
233 .hg/store/meta/dir2/00manifest.i
234
235 Merge of two trees
236
237 $ hg co 2
238 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
239 $ hg merge 1
240 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
241 (branch merge, don't forget to commit)
242 $ hg ci -m 'merge of flat manifests to new tree manifest'
243 created new head
244 $ hg diff -r 3
245
246 Parent of tree root manifest should be flat manifest, and two for merge
247
248 $ hg debugindex -m
249 rev offset length base linkrev nodeid p1 p2
250 0 0 80 0 0 40536115ed9e 000000000000 000000000000
251 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
252 2 163 103 0 2 5d9b9da231a2 40536115ed9e 000000000000
253 3 266 83 0 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
254 4 349 132 4 4 c05a51345f86 f3376063c255 000000000000
255 5 481 110 4 5 82594b1f557d 5d9b9da231a2 f3376063c255
256
257
258 Status across flat/tree boundary should work
259
260 $ hg status --rev '.^' --rev .
261 M a
262 M dir1/a
263 M dir1/dir1/a
264
265
266 Turning off treemanifest config has no effect
267
268 $ hg debugindex .hg/store/meta/dir1/00manifest.i
269 rev offset length base linkrev nodeid p1 p2
270 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
271 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
272 $ echo 2 > dir1/a
273 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
274 $ hg debugindex .hg/store/meta/dir1/00manifest.i
275 rev offset length base linkrev nodeid p1 p2
276 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
277 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
278 2 234 55 0 6 3cb2d87b4250 23d12a1f6e0e 000000000000
@@ -444,11 +444,15 b' def _splittopdir(f):'
444 class treemanifest(object):
444 class treemanifest(object):
445 def __init__(self, dir='', text=''):
445 def __init__(self, dir='', text=''):
446 self._dir = dir
446 self._dir = dir
447 self._node = revlog.nullid
447 self._dirs = {}
448 self._dirs = {}
448 # Using _lazymanifest here is a little slower than plain old dicts
449 # Using _lazymanifest here is a little slower than plain old dicts
449 self._files = {}
450 self._files = {}
450 self._flags = {}
451 self._flags = {}
451 self.parse(text)
452 def readsubtree(subdir, subm):
453 raise AssertionError('treemanifest constructor only accepts '
454 'flat manifests')
455 self.parse(text, readsubtree)
452
456
453 def _subpath(self, path):
457 def _subpath(self, path):
454 return self._dir + path
458 return self._dir + path
@@ -464,7 +468,22 b' class treemanifest(object):'
464 util.all(m._isempty() for m in self._dirs.values())))
468 util.all(m._isempty() for m in self._dirs.values())))
465
469
466 def __str__(self):
470 def __str__(self):
467 return '<treemanifest dir=%s>' % self._dir
471 return ('<treemanifest dir=%s, node=%s>' %
472 (self._dir, revlog.hex(self._node)))
473
474 def dir(self):
475 '''The directory that this tree manifest represents, including a
476 trailing '/'. Empty string for the repo root directory.'''
477 return self._dir
478
479 def node(self):
480 '''This node of this instance. nullid for unsaved instances. Should
481 be updated when the instance is read or written from a revlog.
482 '''
483 return self._node
484
485 def setnode(self, node):
486 self._node = node
468
487
469 def iteritems(self):
488 def iteritems(self):
470 for p, n in sorted(self._dirs.items() + self._files.items()):
489 for p, n in sorted(self._dirs.items() + self._files.items()):
@@ -557,6 +576,7 b' class treemanifest(object):'
557
576
558 def setflag(self, f, flags):
577 def setflag(self, f, flags):
559 """Set the flags (symlink, executable) for path f."""
578 """Set the flags (symlink, executable) for path f."""
579 assert 'd' not in flags
560 dir, subpath = _splittopdir(f)
580 dir, subpath = _splittopdir(f)
561 if dir:
581 if dir:
562 if dir not in self._dirs:
582 if dir not in self._dirs:
@@ -567,6 +587,7 b' class treemanifest(object):'
567
587
568 def copy(self):
588 def copy(self):
569 copy = treemanifest(self._dir)
589 copy = treemanifest(self._dir)
590 copy._node = self._node
570 for d in self._dirs:
591 for d in self._dirs:
571 copy._dirs[d] = self._dirs[d].copy()
592 copy._dirs[d] = self._dirs[d].copy()
572 copy._files = dict.copy(self._files)
593 copy._files = dict.copy(self._files)
@@ -737,11 +758,18 b' class treemanifest(object):'
737 _diff(self, m2)
758 _diff(self, m2)
738 return result
759 return result
739
760
740 def parse(self, text):
761 def parse(self, text, readsubtree):
741 for f, n, fl in _parse(text):
762 for f, n, fl in _parse(text):
742 self[f] = n
763 if fl == 'd':
743 if fl:
764 f = f + '/'
744 self.setflag(f, fl)
765 self._dirs[f] = readsubtree(self._subpath(f), n)
766 else:
767 # Use __setitem__ and setflag rather than assigning directly
768 # to _files and _flags, thereby letting us parse flat manifests
769 # as well as tree manifests.
770 self[f] = n
771 if fl:
772 self.setflag(f, fl)
745
773
746 def text(self, usemanifestv2=False):
774 def text(self, usemanifestv2=False):
747 """Get the full data of this manifest as a bytestring."""
775 """Get the full data of this manifest as a bytestring."""
@@ -749,8 +777,26 b' class treemanifest(object):'
749 return _text(((f, self[f], flags(f)) for f in self.keys()),
777 return _text(((f, self[f], flags(f)) for f in self.keys()),
750 usemanifestv2)
778 usemanifestv2)
751
779
780 def dirtext(self, usemanifestv2=False):
781 """Get the full data of this directory as a bytestring. Make sure that
782 any submanifests have been written first, so their nodeids are correct.
783 """
784 flags = self.flags
785 dirs = [(d[:-1], self._dirs[d]._node, 'd') for d in self._dirs]
786 files = [(f, self._files[f], flags(f)) for f in self._files]
787 return _text(sorted(dirs + files), usemanifestv2)
788
789 def writesubtrees(self, m1, m2, writesubtree):
790 emptytree = treemanifest()
791 for d, subm in self._dirs.iteritems():
792 subp1 = m1._dirs.get(d, emptytree)._node
793 subp2 = m2._dirs.get(d, emptytree)._node
794 if subp1 == revlog.nullid:
795 subp1, subp2 = subp2, subp1
796 writesubtree(subm, subp1, subp2)
797
752 class manifest(revlog.revlog):
798 class manifest(revlog.revlog):
753 def __init__(self, opener):
799 def __init__(self, opener, dir=''):
754 # During normal operations, we expect to deal with not more than four
800 # During normal operations, we expect to deal with not more than four
755 # revs at a time (such as during commit --amend). When rebasing large
801 # revs at a time (such as during commit --amend). When rebasing large
756 # stacks of commits, the number can go up, hence the config knob below.
802 # stacks of commits, the number can go up, hence the config knob below.
@@ -763,14 +809,19 b' class manifest(revlog.revlog):'
763 usetreemanifest = opts.get('treemanifest', usetreemanifest)
809 usetreemanifest = opts.get('treemanifest', usetreemanifest)
764 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
810 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
765 self._mancache = util.lrucachedict(cachesize)
811 self._mancache = util.lrucachedict(cachesize)
766 revlog.revlog.__init__(self, opener, "00manifest.i")
767 self._treeinmem = usetreemanifest
812 self._treeinmem = usetreemanifest
768 self._treeondisk = usetreemanifest
813 self._treeondisk = usetreemanifest
769 self._usemanifestv2 = usemanifestv2
814 self._usemanifestv2 = usemanifestv2
815 indexfile = "00manifest.i"
816 if dir:
817 assert self._treeondisk
818 indexfile = "meta/" + dir + "00manifest.i"
819 revlog.revlog.__init__(self, opener, indexfile)
820 self._dir = dir
770
821
771 def _newmanifest(self, data=''):
822 def _newmanifest(self, data=''):
772 if self._treeinmem:
823 if self._treeinmem:
773 return treemanifest('', data)
824 return treemanifest(self._dir, data)
774 return manifestdict(data)
825 return manifestdict(data)
775
826
776 def _slowreaddelta(self, node):
827 def _slowreaddelta(self, node):
@@ -812,8 +863,17 b' class manifest(revlog.revlog):'
812 if node in self._mancache:
863 if node in self._mancache:
813 return self._mancache[node][0]
864 return self._mancache[node][0]
814 text = self.revision(node)
865 text = self.revision(node)
815 arraytext = array.array('c', text)
866 if self._treeondisk:
816 m = self._newmanifest(text)
867 def readsubtree(dir, subm):
868 sublog = manifest(self.opener, dir)
869 return sublog.read(subm)
870 m = self._newmanifest()
871 m.parse(text, readsubtree)
872 m.setnode(node)
873 arraytext = None
874 else:
875 m = self._newmanifest(text)
876 arraytext = array.array('c', text)
817 self._mancache[node] = (m, arraytext)
877 self._mancache[node] = (m, arraytext)
818 return m
878 return m
819
879
@@ -851,10 +911,34 b' class manifest(revlog.revlog):'
851 # just encode a fulltext of the manifest and pass that
911 # just encode a fulltext of the manifest and pass that
852 # through to the revlog layer, and let it handle the delta
912 # through to the revlog layer, and let it handle the delta
853 # process.
913 # process.
854 text = m.text(self._usemanifestv2)
914 if self._treeondisk:
855 arraytext = array.array('c', text)
915 m1 = self.read(p1)
856 n = self.addrevision(text, transaction, link, p1, p2)
916 m2 = self.read(p2)
917 n = self._addtree(m, transaction, link, m1, m2)
918 arraytext = None
919 else:
920 text = m.text(self._usemanifestv2)
921 n = self.addrevision(text, transaction, link, p1, p2)
922 arraytext = array.array('c', text)
857
923
858 self._mancache[n] = (m, arraytext)
924 self._mancache[n] = (m, arraytext)
859
925
860 return n
926 return n
927
928 def _addtree(self, m, transaction, link, m1, m2):
929 def writesubtree(subm, subp1, subp2):
930 sublog = manifest(self.opener, subm.dir())
931 sublog.add(subm, transaction, link, subp1, subp2, None, None)
932 m.writesubtrees(m1, m2, writesubtree)
933 text = m.dirtext(self._usemanifestv2)
934 # If the manifest is unchanged compared to one parent,
935 # don't write a new revision
936 if text == m1.dirtext(self._usemanifestv2):
937 n = m1.node()
938 elif text == m2.dirtext(self._usemanifestv2):
939 n = m2.node()
940 else:
941 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
942 # Save nodeid so parent manifest can calculate its nodeid
943 m.setnode(n)
944 return n
@@ -187,7 +187,7 b' def _auxencode(path, dotencode):'
187
187
188 def _hashencode(path, dotencode):
188 def _hashencode(path, dotencode):
189 digest = _sha(path).hexdigest()
189 digest = _sha(path).hexdigest()
190 le = lowerencode(path[5:]).split('/') # skips prefix 'data/'
190 le = lowerencode(path[5:]).split('/') # skips prefix 'data/' or 'meta/'
191 parts = _auxencode(le, dotencode)
191 parts = _auxencode(le, dotencode)
192 basename = parts[-1]
192 basename = parts[-1]
193 _root, ext = os.path.splitext(basename)
193 _root, ext = os.path.splitext(basename)
General Comments 0
You need to be logged in to leave comments. Login now