##// END OF EJS Templates
clfilter: add impactable filter...
Pierre-Yves David -
r18246:58ca19ed default
parent child Browse files
Show More
@@ -1,140 +1,165
1 1 # repoview.py - Filtered view of a localrepo object
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import copy
10 10 import phases
11 11 import util
12 12
13 13
14 14 def computehidden(repo):
15 15 """compute the set of hidden revision to filter
16 16
17 17 During most operation hidden should be filtered."""
18 18 assert not repo.changelog.filteredrevs
19 19 if repo.obsstore:
20 20 return frozenset(repo.revs('hidden()'))
21 21 return frozenset()
22 22
23 23 def computeunserved(repo):
24 24 """compute the set of revision that should be filtered when used a server
25 25
26 26 Secret and hidden changeset should not pretend to be here."""
27 27 assert not repo.changelog.filteredrevs
28 28 # fast path in simple case to avoid impact of non optimised code
29 29 if phases.hassecret(repo) or repo.obsstore:
30 30 return frozenset(repo.revs('hidden() + secret()'))
31 31 return frozenset()
32 32
33 33 def computemutable(repo):
34 34 """compute the set of revision that should be filtered when used a server
35 35
36 36 Secret and hidden changeset should not pretend to be here."""
37 37 assert not repo.changelog.filteredrevs
38 38 # fast check to avoid revset call on huge repo
39 39 if util.any(repo._phasecache.phaseroots[1:]):
40 40 return frozenset(repo.revs('draft() + secret()'))
41 41 return frozenset()
42 42
43 def computeimpactable(repo):
44 """Everything impactable by mutable revision
45
46 The mutable filter still have some chance to get invalidated. This will
47 happen when:
48
49 - you garbage collect hidden changeset,
50 - public phase is moved backward,
51 - something is changed in the filtering (this could be fixed)
52
53 This filter out any mutable changeset and any public changeset that may be
54 impacted by something happening to a mutable revision.
55
56 This is achieved by filtered everything with a revision number egal or
57 higher than the first mutable changeset is filtered."""
58 assert not repo.changelog.filteredrevs
59 cl = repo.changelog
60 firstmutable = len(cl)
61 for roots in repo._phasecache.phaseroots[1:]:
62 if roots:
63 firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
64 return frozenset(xrange(firstmutable, len(cl)))
65
43 66 # function to compute filtered set
44 67 filtertable = {'hidden': computehidden,
45 68 'unserved': computeunserved,
46 'mutable': computemutable}
69 'mutable': computemutable,
70 'impactable': computeimpactable}
47 71 ### Nearest subset relation
48 72 # Nearest subset of filter X is a filter Y so that:
49 73 # * Y is included in X,
50 74 # * X - Y is as small as possible.
51 75 # This create and ordering used for branchmap purpose.
52 76 # the ordering may be partial
53 77 subsettable = {None: 'hidden',
54 78 'hidden': 'unserved',
55 'unserved': 'mutable'}
79 'unserved': 'mutable',
80 'mutable': 'impactable'}
56 81
57 82 def filteredrevs(repo, filtername):
58 83 """returns set of filtered revision for this filter name"""
59 84 if filtername not in repo.filteredrevcache:
60 85 func = filtertable[filtername]
61 86 repo.filteredrevcache[filtername] = func(repo.unfiltered())
62 87 return repo.filteredrevcache[filtername]
63 88
64 89 class repoview(object):
65 90 """Provide a read/write view of a repo through a filtered changelog
66 91
67 92 This object is used to access a filtered version of a repository without
68 93 altering the original repository object itself. We can not alter the
69 94 original object for two main reasons:
70 95 - It prevents the use of a repo with multiple filters at the same time. In
71 96 particular when multiple threads are involved.
72 97 - It makes scope of the filtering harder to control.
73 98
74 99 This object behaves very closely to the original repository. All attribute
75 100 operations are done on the original repository:
76 101 - An access to `repoview.someattr` actually returns `repo.someattr`,
77 102 - A write to `repoview.someattr` actually sets value of `repo.someattr`,
78 103 - A deletion of `repoview.someattr` actually drops `someattr`
79 104 from `repo.__dict__`.
80 105
81 106 The only exception is the `changelog` property. It is overridden to return
82 107 a (surface) copy of `repo.changelog` with some revisions filtered. The
83 108 `filtername` attribute of the view control the revisions that need to be
84 109 filtered. (the fact the changelog is copied is an implementation detail).
85 110
86 111 Unlike attributes, this object intercepts all method calls. This means that
87 112 all methods are run on the `repoview` object with the filtered `changelog`
88 113 property. For this purpose the simple `repoview` class must be mixed with
89 114 the actual class of the repository. This ensures that the resulting
90 115 `repoview` object have the very same methods than the repo object. This
91 116 leads to the property below.
92 117
93 118 repoview.method() --> repo.__class__.method(repoview)
94 119
95 120 The inheritance has to be done dynamically because `repo` can be of any
96 121 subclasses of `localrepo`. Eg: `bundlerepo` or `httprepo`.
97 122 """
98 123
99 124 def __init__(self, repo, filtername):
100 125 object.__setattr__(self, '_unfilteredrepo', repo)
101 126 object.__setattr__(self, 'filtername', filtername)
102 127
103 128 # not a cacheproperty on purpose we shall implement a proper cache later
104 129 @property
105 130 def changelog(self):
106 131 """return a filtered version of the changeset
107 132
108 133 this changelog must not be used for writing"""
109 134 # some cache may be implemented later
110 135 cl = copy.copy(self._unfilteredrepo.changelog)
111 136 cl.filteredrevs = filteredrevs(self._unfilteredrepo, self.filtername)
112 137 return cl
113 138
114 139 def unfiltered(self):
115 140 """Return an unfiltered version of a repo"""
116 141 return self._unfilteredrepo
117 142
118 143 def filtered(self, name):
119 144 """Return a filtered version of a repository"""
120 145 if name == self.filtername:
121 146 return self
122 147 return self.unfiltered().filtered(name)
123 148
124 149 # everything access are forwarded to the proxied repo
125 150 def __getattr__(self, attr):
126 151 return getattr(self._unfilteredrepo, attr)
127 152
128 153 def __setattr__(self, attr, value):
129 154 return setattr(self._unfilteredrepo, attr, value)
130 155
131 156 def __delattr__(self, attr):
132 157 return delattr(self._unfilteredrepo, attr)
133 158
134 159 # The `requirement` attribut is initialiazed during __init__. But
135 160 # __getattr__ won't be called as it also exists on the class. We need
136 161 # explicit forwarding to main repo here
137 162 @property
138 163 def requirements(self):
139 164 return self._unfilteredrepo.requirements
140 165
@@ -1,152 +1,152
1 1 test that new files created in .hg inherit the permissions from .hg/store
2 2
3 3
4 4 $ "$TESTDIR/hghave" unix-permissions || exit 80
5 5
6 6 $ mkdir dir
7 7
8 8 just in case somebody has a strange $TMPDIR
9 9
10 10 $ chmod g-s dir
11 11 $ cd dir
12 12
13 13 $ cat >printmodes.py <<EOF
14 14 > import os, sys
15 15 >
16 16 > allnames = []
17 17 > isdir = {}
18 18 > for root, dirs, files in os.walk(sys.argv[1]):
19 19 > for d in dirs:
20 20 > name = os.path.join(root, d)
21 21 > isdir[name] = 1
22 22 > allnames.append(name)
23 23 > for f in files:
24 24 > name = os.path.join(root, f)
25 25 > allnames.append(name)
26 26 > allnames.sort()
27 27 > for name in allnames:
28 28 > suffix = name in isdir and '/' or ''
29 29 > print '%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix)
30 30 > EOF
31 31
32 32 $ cat >mode.py <<EOF
33 33 > import sys
34 34 > import os
35 35 > print '%05o' % os.lstat(sys.argv[1]).st_mode
36 36 > EOF
37 37
38 38 $ umask 077
39 39
40 40 $ hg init repo
41 41 $ cd repo
42 42
43 43 $ chmod 0770 .hg/store
44 44
45 45 before commit
46 46 store can be written by the group, other files cannot
47 47 store is setgid
48 48
49 49 $ python ../printmodes.py .
50 50 00700 ./.hg/
51 51 00600 ./.hg/00changelog.i
52 52 00600 ./.hg/requires
53 53 00770 ./.hg/store/
54 54
55 55 $ mkdir dir
56 56 $ touch foo dir/bar
57 57 $ hg ci -qAm 'add files'
58 58
59 59 after commit
60 60 working dir files can only be written by the owner
61 61 files created in .hg can be written by the group
62 62 (in particular, store/**, dirstate, branch cache file, undo files)
63 63 new directories are setgid
64 64
65 65 $ python ../printmodes.py .
66 66 00700 ./.hg/
67 67 00600 ./.hg/00changelog.i
68 68 00770 ./.hg/cache/
69 69 00660 ./.hg/cache/branchheads-unserved
70 70 00660 ./.hg/dirstate
71 71 00660 ./.hg/last-message.txt
72 72 00600 ./.hg/requires
73 73 00770 ./.hg/store/
74 74 00660 ./.hg/store/00changelog.i
75 75 00660 ./.hg/store/00manifest.i
76 76 00770 ./.hg/store/data/
77 77 00770 ./.hg/store/data/dir/
78 78 00660 ./.hg/store/data/dir/bar.i
79 79 00660 ./.hg/store/data/foo.i
80 80 00660 ./.hg/store/fncache
81 81 00660 ./.hg/store/phaseroots
82 82 00660 ./.hg/store/undo
83 83 00660 ./.hg/store/undo.phaseroots
84 84 00660 ./.hg/undo.bookmarks
85 85 00660 ./.hg/undo.branch
86 86 00660 ./.hg/undo.desc
87 87 00660 ./.hg/undo.dirstate
88 88 00700 ./dir/
89 89 00600 ./dir/bar
90 90 00600 ./foo
91 91
92 92 $ umask 007
93 93 $ hg init ../push
94 94
95 95 before push
96 96 group can write everything
97 97
98 98 $ python ../printmodes.py ../push
99 99 00770 ../push/.hg/
100 100 00660 ../push/.hg/00changelog.i
101 101 00660 ../push/.hg/requires
102 102 00770 ../push/.hg/store/
103 103
104 104 $ umask 077
105 105 $ hg -q push ../push
106 106
107 107 after push
108 108 group can still write everything
109 109
110 110 $ python ../printmodes.py ../push
111 111 00770 ../push/.hg/
112 112 00660 ../push/.hg/00changelog.i
113 113 00770 ../push/.hg/cache/
114 00660 ../push/.hg/cache/branchheads-mutable
114 00660 ../push/.hg/cache/branchheads-impactable
115 115 00660 ../push/.hg/requires
116 116 00770 ../push/.hg/store/
117 117 00660 ../push/.hg/store/00changelog.i
118 118 00660 ../push/.hg/store/00manifest.i
119 119 00770 ../push/.hg/store/data/
120 120 00770 ../push/.hg/store/data/dir/
121 121 00660 ../push/.hg/store/data/dir/bar.i
122 122 00660 ../push/.hg/store/data/foo.i
123 123 00660 ../push/.hg/store/fncache
124 124 00660 ../push/.hg/store/phaseroots
125 125 00660 ../push/.hg/store/undo
126 126 00660 ../push/.hg/store/undo.phaseroots
127 127 00660 ../push/.hg/undo.bookmarks
128 128 00660 ../push/.hg/undo.branch
129 129 00660 ../push/.hg/undo.desc
130 130 00660 ../push/.hg/undo.dirstate
131 131
132 132
133 133 Test that we don't lose the setgid bit when we call chmod.
134 134 Not all systems support setgid directories (e.g. HFS+), so
135 135 just check that directories have the same mode.
136 136
137 137 $ cd ..
138 138 $ hg init setgid
139 139 $ cd setgid
140 140 $ chmod g+rwx .hg/store
141 141 $ chmod g+s .hg/store 2> /dev/null || true
142 142 $ mkdir dir
143 143 $ touch dir/file
144 144 $ hg ci -qAm 'add dir/file'
145 145 $ storemode=`python ../mode.py .hg/store`
146 146 $ dirmode=`python ../mode.py .hg/store/data/dir`
147 147 $ if [ "$storemode" != "$dirmode" ]; then
148 148 > echo "$storemode != $dirmode"
149 149 > fi
150 150 $ cd ..
151 151
152 152 $ cd .. # g-s dir
@@ -1,345 +1,345
1 1 $ branchcache=.hg/cache/branchheads
2 2
3 3 $ listbranchcaches() {
4 4 > for f in .hg/cache/branchheads*;
5 5 > do echo === $f ===;
6 6 > cat $f;
7 7 > done;
8 8 > }
9 9 $ purgebranchcaches() {
10 10 > rm .hg/cache/branchheads*
11 11 > }
12 12
13 13 $ hg init t
14 14 $ cd t
15 15
16 16 $ hg branches
17 17 $ echo foo > a
18 18 $ hg add a
19 19 $ hg ci -m "initial"
20 20 $ hg branch foo
21 21 marked working directory as branch foo
22 22 (branches are permanent and global, did you want a bookmark?)
23 23 $ hg branch
24 24 foo
25 25 $ hg ci -m "add branch name"
26 26 $ hg branch bar
27 27 marked working directory as branch bar
28 28 (branches are permanent and global, did you want a bookmark?)
29 29 $ hg ci -m "change branch name"
30 30
31 31 Branch shadowing:
32 32
33 33 $ hg branch default
34 34 abort: a branch of the same name already exists
35 35 (use 'hg update' to switch to it)
36 36 [255]
37 37
38 38 $ hg branch -f default
39 39 marked working directory as branch default
40 40 (branches are permanent and global, did you want a bookmark?)
41 41
42 42 $ hg ci -m "clear branch name"
43 43 created new head
44 44
45 45 There should be only one default branch head
46 46
47 47 $ hg heads .
48 48 changeset: 3:1c28f494dae6
49 49 tag: tip
50 50 user: test
51 51 date: Thu Jan 01 00:00:00 1970 +0000
52 52 summary: clear branch name
53 53
54 54
55 55 $ hg co foo
56 56 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
57 57 $ hg branch
58 58 foo
59 59 $ echo bleah > a
60 60 $ hg ci -m "modify a branch"
61 61
62 62 $ hg merge default
63 63 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
64 64 (branch merge, don't forget to commit)
65 65
66 66 $ hg branch
67 67 foo
68 68 $ hg ci -m "merge"
69 69
70 70 $ hg log
71 71 changeset: 5:530046499edf
72 72 branch: foo
73 73 tag: tip
74 74 parent: 4:adf1a74a7f7b
75 75 parent: 3:1c28f494dae6
76 76 user: test
77 77 date: Thu Jan 01 00:00:00 1970 +0000
78 78 summary: merge
79 79
80 80 changeset: 4:adf1a74a7f7b
81 81 branch: foo
82 82 parent: 1:6c0e42da283a
83 83 user: test
84 84 date: Thu Jan 01 00:00:00 1970 +0000
85 85 summary: modify a branch
86 86
87 87 changeset: 3:1c28f494dae6
88 88 user: test
89 89 date: Thu Jan 01 00:00:00 1970 +0000
90 90 summary: clear branch name
91 91
92 92 changeset: 2:c21617b13b22
93 93 branch: bar
94 94 user: test
95 95 date: Thu Jan 01 00:00:00 1970 +0000
96 96 summary: change branch name
97 97
98 98 changeset: 1:6c0e42da283a
99 99 branch: foo
100 100 user: test
101 101 date: Thu Jan 01 00:00:00 1970 +0000
102 102 summary: add branch name
103 103
104 104 changeset: 0:db01e8ea3388
105 105 user: test
106 106 date: Thu Jan 01 00:00:00 1970 +0000
107 107 summary: initial
108 108
109 109 $ hg branches
110 110 foo 5:530046499edf
111 111 default 3:1c28f494dae6 (inactive)
112 112 bar 2:c21617b13b22 (inactive)
113 113
114 114 $ hg branches -q
115 115 foo
116 116 default
117 117 bar
118 118
119 119 Test for invalid branch cache:
120 120
121 121 $ hg rollback
122 122 repository tip rolled back to revision 4 (undo commit)
123 123 working directory now based on revisions 4 and 3
124 124
125 125 $ cp ${branchcache}-unserved .hg/bc-invalid
126 126
127 127 $ hg log -r foo
128 128 changeset: 4:adf1a74a7f7b
129 129 branch: foo
130 130 tag: tip
131 131 parent: 1:6c0e42da283a
132 132 user: test
133 133 date: Thu Jan 01 00:00:00 1970 +0000
134 134 summary: modify a branch
135 135
136 136 $ cp .hg/bc-invalid $branchcache
137 137
138 138 $ hg --debug log -r foo
139 139 invalid branchheads cache: tip differs
140 140 changeset: 4:adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6
141 141 branch: foo
142 142 tag: tip
143 143 phase: draft
144 144 parent: 1:6c0e42da283a56b5edc5b4fadb491365ec7f5fa8
145 145 parent: -1:0000000000000000000000000000000000000000
146 146 manifest: 1:8c342a37dfba0b3d3ce073562a00d8a813c54ffe
147 147 user: test
148 148 date: Thu Jan 01 00:00:00 1970 +0000
149 149 files: a
150 150 extra: branch=foo
151 151 description:
152 152 modify a branch
153 153
154 154
155 155 $ purgebranchcaches
156 156 $ echo corrupted > $branchcache
157 157
158 158 $ hg log -qr foo
159 159 4:adf1a74a7f7b
160 160
161 161 $ listbranchcaches
162 162 === .hg/cache/branchheads ===
163 163 corrupted
164 164 === .hg/cache/branchheads-unserved ===
165 165 adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 4
166 166 1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
167 167 adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
168 168 c21617b13b220988e7a2e26290fbe4325ffa7139 bar
169 169
170 170 Push should update the branch cache:
171 171
172 172 $ hg init ../target
173 173
174 174 Pushing just rev 0:
175 175
176 176 $ hg push -qr 0 ../target
177 177
178 178 $ (cd ../target/; listbranchcaches)
179 === .hg/cache/branchheads-mutable ===
179 === .hg/cache/branchheads-impactable ===
180 180 db01e8ea3388fd3c7c94e1436ea2bd6a53d581c5 0
181 181 db01e8ea3388fd3c7c94e1436ea2bd6a53d581c5 default
182 182
183 183 Pushing everything:
184 184
185 185 $ hg push -qf ../target
186 186
187 187 $ (cd ../target/; listbranchcaches)
188 === .hg/cache/branchheads-mutable ===
188 === .hg/cache/branchheads-impactable ===
189 189 adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 4
190 190 1c28f494dae69a2f8fc815059d257eccf3fcfe75 default
191 191 adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo
192 192 c21617b13b220988e7a2e26290fbe4325ffa7139 bar
193 193
194 194 Update with no arguments: tipmost revision of the current branch:
195 195
196 196 $ hg up -q -C 0
197 197 $ hg up -q
198 198 $ hg id
199 199 1c28f494dae6
200 200
201 201 $ hg up -q 1
202 202 $ hg up -q
203 203 $ hg id
204 204 adf1a74a7f7b (foo) tip
205 205
206 206 $ hg branch foobar
207 207 marked working directory as branch foobar
208 208 (branches are permanent and global, did you want a bookmark?)
209 209
210 210 $ hg up
211 211 abort: branch foobar not found
212 212 [255]
213 213
214 214 Fastforward merge:
215 215
216 216 $ hg branch ff
217 217 marked working directory as branch ff
218 218 (branches are permanent and global, did you want a bookmark?)
219 219
220 220 $ echo ff > ff
221 221 $ hg ci -Am'fast forward'
222 222 adding ff
223 223
224 224 $ hg up foo
225 225 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
226 226
227 227 $ hg merge ff
228 228 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
229 229 (branch merge, don't forget to commit)
230 230
231 231 $ hg branch
232 232 foo
233 233 $ hg commit -m'Merge ff into foo'
234 234 $ hg parents
235 235 changeset: 6:185ffbfefa30
236 236 branch: foo
237 237 tag: tip
238 238 parent: 4:adf1a74a7f7b
239 239 parent: 5:1a3c27dc5e11
240 240 user: test
241 241 date: Thu Jan 01 00:00:00 1970 +0000
242 242 summary: Merge ff into foo
243 243
244 244 $ hg manifest
245 245 a
246 246 ff
247 247
248 248
249 249 Test merging, add 3 default heads and one test head:
250 250
251 251 $ cd ..
252 252 $ hg init merges
253 253 $ cd merges
254 254 $ echo a > a
255 255 $ hg ci -Ama
256 256 adding a
257 257
258 258 $ echo b > b
259 259 $ hg ci -Amb
260 260 adding b
261 261
262 262 $ hg up 0
263 263 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
264 264 $ echo c > c
265 265 $ hg ci -Amc
266 266 adding c
267 267 created new head
268 268
269 269 $ hg up 0
270 270 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
271 271 $ echo d > d
272 272 $ hg ci -Amd
273 273 adding d
274 274 created new head
275 275
276 276 $ hg up 0
277 277 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
278 278 $ hg branch test
279 279 marked working directory as branch test
280 280 (branches are permanent and global, did you want a bookmark?)
281 281 $ echo e >> e
282 282 $ hg ci -Ame
283 283 adding e
284 284
285 285 $ hg log
286 286 changeset: 4:3a1e01ed1df4
287 287 branch: test
288 288 tag: tip
289 289 parent: 0:cb9a9f314b8b
290 290 user: test
291 291 date: Thu Jan 01 00:00:00 1970 +0000
292 292 summary: e
293 293
294 294 changeset: 3:980f7dc84c29
295 295 parent: 0:cb9a9f314b8b
296 296 user: test
297 297 date: Thu Jan 01 00:00:00 1970 +0000
298 298 summary: d
299 299
300 300 changeset: 2:d36c0562f908
301 301 parent: 0:cb9a9f314b8b
302 302 user: test
303 303 date: Thu Jan 01 00:00:00 1970 +0000
304 304 summary: c
305 305
306 306 changeset: 1:d2ae7f538514
307 307 user: test
308 308 date: Thu Jan 01 00:00:00 1970 +0000
309 309 summary: b
310 310
311 311 changeset: 0:cb9a9f314b8b
312 312 user: test
313 313 date: Thu Jan 01 00:00:00 1970 +0000
314 314 summary: a
315 315
316 316 Implicit merge with test branch as parent:
317 317
318 318 $ hg merge
319 319 abort: branch 'test' has one head - please merge with an explicit rev
320 320 (run 'hg heads' to see all heads)
321 321 [255]
322 322 $ hg up -C default
323 323 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
324 324
325 325 Implicit merge with default branch as parent:
326 326
327 327 $ hg merge
328 328 abort: branch 'default' has 3 heads - please merge with an explicit rev
329 329 (run 'hg heads .' to see heads)
330 330 [255]
331 331
332 332 3 branch heads, explicit merge required:
333 333
334 334 $ hg merge 2
335 335 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 336 (branch merge, don't forget to commit)
337 337 $ hg ci -m merge
338 338
339 339 2 branch heads, implicit merge works:
340 340
341 341 $ hg merge
342 342 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
343 343 (branch merge, don't forget to commit)
344 344
345 345 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now