##// END OF EJS Templates
phases: write default phase data as soon as possible....
Pierre-Yves David -
r16025:6697498b stable
parent child Browse files
Show More
@@ -1,299 +1,300 b''
1 """ Mercurial phases support code
1 """ Mercurial phases support code
2
2
3 ---
3 ---
4
4
5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 Logilab SA <contact@logilab.fr>
6 Logilab SA <contact@logilab.fr>
7 Augie Fackler <durin42@gmail.com>
7 Augie Fackler <durin42@gmail.com>
8
8
9 This software may be used and distributed according to the terms of the
9 This software may be used and distributed according to the terms of the
10 GNU General Public License version 2 or any later version.
10 GNU General Public License version 2 or any later version.
11
11
12 ---
12 ---
13
13
14 This module implements most phase logic in mercurial.
14 This module implements most phase logic in mercurial.
15
15
16
16
17 Basic Concept
17 Basic Concept
18 =============
18 =============
19
19
20 A 'changeset phases' is an indicator that tells us how a changeset is
20 A 'changeset phases' is an indicator that tells us how a changeset is
21 manipulated and communicated. The details of each phase is described below,
21 manipulated and communicated. The details of each phase is described below,
22 here we describe the properties they have in common.
22 here we describe the properties they have in common.
23
23
24 Like bookmarks, phases are not stored in history and thus are not permanent and
24 Like bookmarks, phases are not stored in history and thus are not permanent and
25 leave no audit trail.
25 leave no audit trail.
26
26
27 First, no changeset can be in two phases at once. Phases are ordered, so they
27 First, no changeset can be in two phases at once. Phases are ordered, so they
28 can be considered from lowest to highest. The default, lowest phase is 'public'
28 can be considered from lowest to highest. The default, lowest phase is 'public'
29 - this is the normal phase of existing changesets. A child changeset can not be
29 - this is the normal phase of existing changesets. A child changeset can not be
30 in a lower phase than its parents.
30 in a lower phase than its parents.
31
31
32 These phases share a hierarchy of traits:
32 These phases share a hierarchy of traits:
33
33
34 immutable shared
34 immutable shared
35 public: X X
35 public: X X
36 draft: X
36 draft: X
37 secret:
37 secret:
38
38
39 local commits are draft by default
39 local commits are draft by default
40
40
41 Phase movement and exchange
41 Phase movement and exchange
42 ============================
42 ============================
43
43
44 Phase data are exchanged by pushkey on pull and push. Some server have a
44 Phase data are exchanged by pushkey on pull and push. Some server have a
45 publish option set, we call them publishing server. Pushing to such server make
45 publish option set, we call them publishing server. Pushing to such server make
46 draft changeset publish.
46 draft changeset publish.
47
47
48 A small list of fact/rules define the exchange of phase:
48 A small list of fact/rules define the exchange of phase:
49
49
50 * old client never changes server states
50 * old client never changes server states
51 * pull never changes server states
51 * pull never changes server states
52 * publish and old server csets are seen as public by client
52 * publish and old server csets are seen as public by client
53
53
54 * Any secret changeset seens in another repository is lowered to at least draft
54 * Any secret changeset seens in another repository is lowered to at least draft
55
55
56
56
57 Here is the final table summing up the 49 possible usecase of phase exchange:
57 Here is the final table summing up the 49 possible usecase of phase exchange:
58
58
59 server
59 server
60 old publish non-publish
60 old publish non-publish
61 N X N D P N D P
61 N X N D P N D P
62 old client
62 old client
63 pull
63 pull
64 N - X/X - X/D X/P - X/D X/P
64 N - X/X - X/D X/P - X/D X/P
65 X - X/X - X/D X/P - X/D X/P
65 X - X/X - X/D X/P - X/D X/P
66 push
66 push
67 X X/X X/X X/P X/P X/P X/D X/D X/P
67 X X/X X/X X/P X/P X/P X/D X/D X/P
68 new client
68 new client
69 pull
69 pull
70 N - P/X - P/D P/P - D/D P/P
70 N - P/X - P/D P/P - D/D P/P
71 D - P/X - P/D P/P - D/D P/P
71 D - P/X - P/D P/P - D/D P/P
72 P - P/X - P/D P/P - P/D P/P
72 P - P/X - P/D P/P - P/D P/P
73 push
73 push
74 D P/X P/X P/P P/P P/P D/D D/D P/P
74 D P/X P/X P/P P/P P/P D/D D/D P/P
75 P P/X P/X P/P P/P P/P P/P P/P P/P
75 P P/X P/X P/P P/P P/P P/P P/P P/P
76
76
77 Legend:
77 Legend:
78
78
79 A/B = final state on client / state on server
79 A/B = final state on client / state on server
80
80
81 * N = new/not present,
81 * N = new/not present,
82 * P = public,
82 * P = public,
83 * D = draft,
83 * D = draft,
84 * X = not tracked (ie: the old client or server has no internal way of
84 * X = not tracked (ie: the old client or server has no internal way of
85 recording the phase.)
85 recording the phase.)
86
86
87 passive = only pushes
87 passive = only pushes
88
88
89
89
90 A cell here can be read like this:
90 A cell here can be read like this:
91
91
92 "When a new client pushes a draft changeset (D) to a publishing server
92 "When a new client pushes a draft changeset (D) to a publishing server
93 where it's not present (N), it's marked public on both sides (P/P)."
93 where it's not present (N), it's marked public on both sides (P/P)."
94
94
95 Note: old client behave as publish server with Draft only content
95 Note: old client behave as publish server with Draft only content
96 - other people see it as public
96 - other people see it as public
97 - content is pushed as draft
97 - content is pushed as draft
98
98
99 """
99 """
100
100
101 import errno
101 import errno
102 from node import nullid, bin, hex, short
102 from node import nullid, bin, hex, short
103 from i18n import _
103 from i18n import _
104
104
105 allphases = public, draft, secret = range(3)
105 allphases = public, draft, secret = range(3)
106 trackedphases = allphases[1:]
106 trackedphases = allphases[1:]
107 phasenames = ['public', 'draft', 'secret']
107 phasenames = ['public', 'draft', 'secret']
108
108
109 def readroots(repo):
109 def readroots(repo):
110 """Read phase roots from disk"""
110 """Read phase roots from disk"""
111 roots = [set() for i in allphases]
111 roots = [set() for i in allphases]
112 try:
112 try:
113 f = repo.sopener('phaseroots')
113 f = repo.sopener('phaseroots')
114 try:
114 try:
115 for line in f:
115 for line in f:
116 phase, nh = line.strip().split()
116 phase, nh = line.strip().split()
117 roots[int(phase)].add(bin(nh))
117 roots[int(phase)].add(bin(nh))
118 finally:
118 finally:
119 f.close()
119 f.close()
120 except IOError, inst:
120 except IOError, inst:
121 if inst.errno != errno.ENOENT:
121 if inst.errno != errno.ENOENT:
122 raise
122 raise
123 for f in repo._phasedefaults:
123 for f in repo._phasedefaults:
124 roots = f(repo, roots)
124 roots = f(repo, roots)
125 repo._dirtyphases = True
125 return roots
126 return roots
126
127
127 def writeroots(repo):
128 def writeroots(repo):
128 """Write phase roots from disk"""
129 """Write phase roots from disk"""
129 f = repo.sopener('phaseroots', 'w', atomictemp=True)
130 f = repo.sopener('phaseroots', 'w', atomictemp=True)
130 try:
131 try:
131 for phase, roots in enumerate(repo._phaseroots):
132 for phase, roots in enumerate(repo._phaseroots):
132 for h in roots:
133 for h in roots:
133 f.write('%i %s\n' % (phase, hex(h)))
134 f.write('%i %s\n' % (phase, hex(h)))
134 repo._dirtyphases = False
135 repo._dirtyphases = False
135 finally:
136 finally:
136 f.close()
137 f.close()
137
138
138 def filterunknown(repo, phaseroots=None):
139 def filterunknown(repo, phaseroots=None):
139 """remove unknown nodes from the phase boundary
140 """remove unknown nodes from the phase boundary
140
141
141 no data is lost as unknown node only old data for their descentants
142 no data is lost as unknown node only old data for their descentants
142 """
143 """
143 if phaseroots is None:
144 if phaseroots is None:
144 phaseroots = repo._phaseroots
145 phaseroots = repo._phaseroots
145 nodemap = repo.changelog.nodemap # to filter unknown nodes
146 nodemap = repo.changelog.nodemap # to filter unknown nodes
146 for phase, nodes in enumerate(phaseroots):
147 for phase, nodes in enumerate(phaseroots):
147 missing = [node for node in nodes if node not in nodemap]
148 missing = [node for node in nodes if node not in nodemap]
148 if missing:
149 if missing:
149 for mnode in missing:
150 for mnode in missing:
150 msg = 'Removing unknown node %(n)s from %(p)i-phase boundary'
151 msg = 'Removing unknown node %(n)s from %(p)i-phase boundary'
151 repo.ui.debug(msg, {'n': short(mnode), 'p': phase})
152 repo.ui.debug(msg, {'n': short(mnode), 'p': phase})
152 nodes.symmetric_difference_update(missing)
153 nodes.symmetric_difference_update(missing)
153 repo._dirtyphases = True
154 repo._dirtyphases = True
154
155
155 def advanceboundary(repo, targetphase, nodes):
156 def advanceboundary(repo, targetphase, nodes):
156 """Add nodes to a phase changing other nodes phases if necessary.
157 """Add nodes to a phase changing other nodes phases if necessary.
157
158
158 This function move boundary *forward* this means that all nodes are set
159 This function move boundary *forward* this means that all nodes are set
159 in the target phase or kept in a *lower* phase.
160 in the target phase or kept in a *lower* phase.
160
161
161 Simplify boundary to contains phase roots only."""
162 Simplify boundary to contains phase roots only."""
162 delroots = [] # set of root deleted by this path
163 delroots = [] # set of root deleted by this path
163 for phase in xrange(targetphase + 1, len(allphases)):
164 for phase in xrange(targetphase + 1, len(allphases)):
164 # filter nodes that are not in a compatible phase already
165 # filter nodes that are not in a compatible phase already
165 # XXX rev phase cache might have been invalidated by a previous loop
166 # XXX rev phase cache might have been invalidated by a previous loop
166 # XXX we need to be smarter here
167 # XXX we need to be smarter here
167 nodes = [n for n in nodes if repo[n].phase() >= phase]
168 nodes = [n for n in nodes if repo[n].phase() >= phase]
168 if not nodes:
169 if not nodes:
169 break # no roots to move anymore
170 break # no roots to move anymore
170 roots = repo._phaseroots[phase]
171 roots = repo._phaseroots[phase]
171 olds = roots.copy()
172 olds = roots.copy()
172 ctxs = list(repo.set('roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
173 ctxs = list(repo.set('roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
173 roots.clear()
174 roots.clear()
174 roots.update(ctx.node() for ctx in ctxs)
175 roots.update(ctx.node() for ctx in ctxs)
175 if olds != roots:
176 if olds != roots:
176 # invalidate cache (we probably could be smarter here
177 # invalidate cache (we probably could be smarter here
177 if '_phaserev' in vars(repo):
178 if '_phaserev' in vars(repo):
178 del repo._phaserev
179 del repo._phaserev
179 repo._dirtyphases = True
180 repo._dirtyphases = True
180 # some roots may need to be declared for lower phases
181 # some roots may need to be declared for lower phases
181 delroots.extend(olds - roots)
182 delroots.extend(olds - roots)
182 # declare deleted root in the target phase
183 # declare deleted root in the target phase
183 if targetphase != 0:
184 if targetphase != 0:
184 retractboundary(repo, targetphase, delroots)
185 retractboundary(repo, targetphase, delroots)
185
186
186
187
187 def retractboundary(repo, targetphase, nodes):
188 def retractboundary(repo, targetphase, nodes):
188 """Set nodes back to a phase changing other nodes phases if necessary.
189 """Set nodes back to a phase changing other nodes phases if necessary.
189
190
190 This function move boundary *backward* this means that all nodes are set
191 This function move boundary *backward* this means that all nodes are set
191 in the target phase or kept in a *higher* phase.
192 in the target phase or kept in a *higher* phase.
192
193
193 Simplify boundary to contains phase roots only."""
194 Simplify boundary to contains phase roots only."""
194 currentroots = repo._phaseroots[targetphase]
195 currentroots = repo._phaseroots[targetphase]
195 newroots = [n for n in nodes if repo[n].phase() < targetphase]
196 newroots = [n for n in nodes if repo[n].phase() < targetphase]
196 if newroots:
197 if newroots:
197 currentroots.update(newroots)
198 currentroots.update(newroots)
198 ctxs = repo.set('roots(%ln::)', currentroots)
199 ctxs = repo.set('roots(%ln::)', currentroots)
199 currentroots.intersection_update(ctx.node() for ctx in ctxs)
200 currentroots.intersection_update(ctx.node() for ctx in ctxs)
200 if '_phaserev' in vars(repo):
201 if '_phaserev' in vars(repo):
201 del repo._phaserev
202 del repo._phaserev
202 repo._dirtyphases = True
203 repo._dirtyphases = True
203
204
204
205
205 def listphases(repo):
206 def listphases(repo):
206 """List phases root for serialisation over pushkey"""
207 """List phases root for serialisation over pushkey"""
207 keys = {}
208 keys = {}
208 value = '%i' % draft
209 value = '%i' % draft
209 for root in repo._phaseroots[draft]:
210 for root in repo._phaseroots[draft]:
210 keys[hex(root)] = value
211 keys[hex(root)] = value
211
212
212 if repo.ui.configbool('phases', 'publish', True):
213 if repo.ui.configbool('phases', 'publish', True):
213 # Add an extra data to let remote know we are a publishing repo.
214 # Add an extra data to let remote know we are a publishing repo.
214 # Publishing repo can't just pretend they are old repo. When pushing to
215 # Publishing repo can't just pretend they are old repo. When pushing to
215 # a publishing repo, the client still need to push phase boundary
216 # a publishing repo, the client still need to push phase boundary
216 #
217 #
217 # Push do not only push changeset. It also push phase data. New
218 # Push do not only push changeset. It also push phase data. New
218 # phase data may apply to common changeset which won't be push (as they
219 # phase data may apply to common changeset which won't be push (as they
219 # are common). Here is a very simple example:
220 # are common). Here is a very simple example:
220 #
221 #
221 # 1) repo A push changeset X as draft to repo B
222 # 1) repo A push changeset X as draft to repo B
222 # 2) repo B make changeset X public
223 # 2) repo B make changeset X public
223 # 3) repo B push to repo A. X is not pushed but the data that X as now
224 # 3) repo B push to repo A. X is not pushed but the data that X as now
224 # public should
225 # public should
225 #
226 #
226 # The server can't handle it on it's own as it has no idea of client
227 # The server can't handle it on it's own as it has no idea of client
227 # phase data.
228 # phase data.
228 keys['publishing'] = 'True'
229 keys['publishing'] = 'True'
229 return keys
230 return keys
230
231
231 def pushphase(repo, nhex, oldphasestr, newphasestr):
232 def pushphase(repo, nhex, oldphasestr, newphasestr):
232 """List phases root for serialisation over pushkey"""
233 """List phases root for serialisation over pushkey"""
233 lock = repo.lock()
234 lock = repo.lock()
234 try:
235 try:
235 currentphase = repo[nhex].phase()
236 currentphase = repo[nhex].phase()
236 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
237 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
237 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
238 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
238 if currentphase == oldphase and newphase < oldphase:
239 if currentphase == oldphase and newphase < oldphase:
239 advanceboundary(repo, newphase, [bin(nhex)])
240 advanceboundary(repo, newphase, [bin(nhex)])
240 return 1
241 return 1
241 else:
242 else:
242 return 0
243 return 0
243 finally:
244 finally:
244 lock.release()
245 lock.release()
245
246
246 def visibleheads(repo):
247 def visibleheads(repo):
247 """return the set of visible head of this repo"""
248 """return the set of visible head of this repo"""
248 # XXX we want a cache on this
249 # XXX we want a cache on this
249 sroots = repo._phaseroots[secret]
250 sroots = repo._phaseroots[secret]
250 if sroots:
251 if sroots:
251 # XXX very slow revset. storing heads or secret "boundary" would help.
252 # XXX very slow revset. storing heads or secret "boundary" would help.
252 revset = repo.set('heads(not (%ln::))', sroots)
253 revset = repo.set('heads(not (%ln::))', sroots)
253
254
254 vheads = [ctx.node() for ctx in revset]
255 vheads = [ctx.node() for ctx in revset]
255 if not vheads:
256 if not vheads:
256 vheads.append(nullid)
257 vheads.append(nullid)
257 else:
258 else:
258 vheads = repo.heads()
259 vheads = repo.heads()
259 return vheads
260 return vheads
260
261
261 def analyzeremotephases(repo, subset, roots):
262 def analyzeremotephases(repo, subset, roots):
262 """Compute phases heads and root in a subset of node from root dict
263 """Compute phases heads and root in a subset of node from root dict
263
264
264 * subset is heads of the subset
265 * subset is heads of the subset
265 * roots is {<nodeid> => phase} mapping. key and value are string.
266 * roots is {<nodeid> => phase} mapping. key and value are string.
266
267
267 Accept unknown element input
268 Accept unknown element input
268 """
269 """
269 # build list from dictionary
270 # build list from dictionary
270 draftroots = []
271 draftroots = []
271 nodemap = repo.changelog.nodemap # to filter unknown nodes
272 nodemap = repo.changelog.nodemap # to filter unknown nodes
272 for nhex, phase in roots.iteritems():
273 for nhex, phase in roots.iteritems():
273 if nhex == 'publishing': # ignore data related to publish option
274 if nhex == 'publishing': # ignore data related to publish option
274 continue
275 continue
275 node = bin(nhex)
276 node = bin(nhex)
276 phase = int(phase)
277 phase = int(phase)
277 if phase == 0:
278 if phase == 0:
278 if node != nullid:
279 if node != nullid:
279 repo.ui.warn(_('ignoring inconsistent public root'
280 repo.ui.warn(_('ignoring inconsistent public root'
280 ' from remote: %s\n') % nhex)
281 ' from remote: %s\n') % nhex)
281 elif phase == 1:
282 elif phase == 1:
282 if node in nodemap:
283 if node in nodemap:
283 draftroots.append(node)
284 draftroots.append(node)
284 else:
285 else:
285 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
286 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
286 % (phase, nhex))
287 % (phase, nhex))
287 # compute heads
288 # compute heads
288 publicheads = newheads(repo, subset, draftroots)
289 publicheads = newheads(repo, subset, draftroots)
289 return publicheads, draftroots
290 return publicheads, draftroots
290
291
291 def newheads(repo, heads, roots):
292 def newheads(repo, heads, roots):
292 """compute new head of a subset minus another
293 """compute new head of a subset minus another
293
294
294 * `heads`: define the first subset
295 * `heads`: define the first subset
295 * `rroots`: define the second we substract to the first"""
296 * `rroots`: define the second we substract to the first"""
296 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
297 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
297 heads, roots, roots, heads)
298 heads, roots, roots, heads)
298 return [c.node() for c in revset]
299 return [c.node() for c in revset]
299
300
@@ -1,148 +1,149 b''
1 test that new files created in .hg inherit the permissions from .hg/store
1 test that new files created in .hg inherit the permissions from .hg/store
2
2
3
3
4 $ "$TESTDIR/hghave" unix-permissions || exit 80
4 $ "$TESTDIR/hghave" unix-permissions || exit 80
5
5
6 $ mkdir dir
6 $ mkdir dir
7
7
8 just in case somebody has a strange $TMPDIR
8 just in case somebody has a strange $TMPDIR
9
9
10 $ chmod g-s dir
10 $ chmod g-s dir
11 $ cd dir
11 $ cd dir
12
12
13 $ cat >printmodes.py <<EOF
13 $ cat >printmodes.py <<EOF
14 > import os, sys
14 > import os, sys
15 >
15 >
16 > allnames = []
16 > allnames = []
17 > isdir = {}
17 > isdir = {}
18 > for root, dirs, files in os.walk(sys.argv[1]):
18 > for root, dirs, files in os.walk(sys.argv[1]):
19 > for d in dirs:
19 > for d in dirs:
20 > name = os.path.join(root, d)
20 > name = os.path.join(root, d)
21 > isdir[name] = 1
21 > isdir[name] = 1
22 > allnames.append(name)
22 > allnames.append(name)
23 > for f in files:
23 > for f in files:
24 > name = os.path.join(root, f)
24 > name = os.path.join(root, f)
25 > allnames.append(name)
25 > allnames.append(name)
26 > allnames.sort()
26 > allnames.sort()
27 > for name in allnames:
27 > for name in allnames:
28 > suffix = name in isdir and '/' or ''
28 > suffix = name in isdir and '/' or ''
29 > print '%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix)
29 > print '%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix)
30 > EOF
30 > EOF
31
31
32 $ cat >mode.py <<EOF
32 $ cat >mode.py <<EOF
33 > import sys
33 > import sys
34 > import os
34 > import os
35 > print '%05o' % os.lstat(sys.argv[1]).st_mode
35 > print '%05o' % os.lstat(sys.argv[1]).st_mode
36 > EOF
36 > EOF
37
37
38 $ umask 077
38 $ umask 077
39
39
40 $ hg init repo
40 $ hg init repo
41 $ cd repo
41 $ cd repo
42
42
43 $ chmod 0770 .hg/store
43 $ chmod 0770 .hg/store
44
44
45 before commit
45 before commit
46 store can be written by the group, other files cannot
46 store can be written by the group, other files cannot
47 store is setgid
47 store is setgid
48
48
49 $ python ../printmodes.py .
49 $ python ../printmodes.py .
50 00700 ./.hg/
50 00700 ./.hg/
51 00600 ./.hg/00changelog.i
51 00600 ./.hg/00changelog.i
52 00600 ./.hg/requires
52 00600 ./.hg/requires
53 00770 ./.hg/store/
53 00770 ./.hg/store/
54
54
55 $ mkdir dir
55 $ mkdir dir
56 $ touch foo dir/bar
56 $ touch foo dir/bar
57 $ hg ci -qAm 'add files'
57 $ hg ci -qAm 'add files'
58
58
59 after commit
59 after commit
60 working dir files can only be written by the owner
60 working dir files can only be written by the owner
61 files created in .hg can be written by the group
61 files created in .hg can be written by the group
62 (in particular, store/**, dirstate, branch cache file, undo files)
62 (in particular, store/**, dirstate, branch cache file, undo files)
63 new directories are setgid
63 new directories are setgid
64
64
65 $ python ../printmodes.py .
65 $ python ../printmodes.py .
66 00700 ./.hg/
66 00700 ./.hg/
67 00600 ./.hg/00changelog.i
67 00600 ./.hg/00changelog.i
68 00770 ./.hg/cache/
68 00770 ./.hg/cache/
69 00660 ./.hg/cache/branchheads
69 00660 ./.hg/cache/branchheads
70 00660 ./.hg/dirstate
70 00660 ./.hg/dirstate
71 00660 ./.hg/last-message.txt
71 00660 ./.hg/last-message.txt
72 00600 ./.hg/requires
72 00600 ./.hg/requires
73 00770 ./.hg/store/
73 00770 ./.hg/store/
74 00660 ./.hg/store/00changelog.i
74 00660 ./.hg/store/00changelog.i
75 00660 ./.hg/store/00manifest.i
75 00660 ./.hg/store/00manifest.i
76 00770 ./.hg/store/data/
76 00770 ./.hg/store/data/
77 00770 ./.hg/store/data/dir/
77 00770 ./.hg/store/data/dir/
78 00660 ./.hg/store/data/dir/bar.i
78 00660 ./.hg/store/data/dir/bar.i
79 00660 ./.hg/store/data/foo.i
79 00660 ./.hg/store/data/foo.i
80 00660 ./.hg/store/fncache
80 00660 ./.hg/store/fncache
81 00660 ./.hg/store/phaseroots
81 00660 ./.hg/store/phaseroots
82 00660 ./.hg/store/undo
82 00660 ./.hg/store/undo
83 00660 ./.hg/store/undo.phaseroots
83 00660 ./.hg/store/undo.phaseroots
84 00660 ./.hg/undo.bookmarks
84 00660 ./.hg/undo.bookmarks
85 00660 ./.hg/undo.branch
85 00660 ./.hg/undo.branch
86 00660 ./.hg/undo.desc
86 00660 ./.hg/undo.desc
87 00660 ./.hg/undo.dirstate
87 00660 ./.hg/undo.dirstate
88 00700 ./dir/
88 00700 ./dir/
89 00600 ./dir/bar
89 00600 ./dir/bar
90 00600 ./foo
90 00600 ./foo
91
91
92 $ umask 007
92 $ umask 007
93 $ hg init ../push
93 $ hg init ../push
94
94
95 before push
95 before push
96 group can write everything
96 group can write everything
97
97
98 $ python ../printmodes.py ../push
98 $ python ../printmodes.py ../push
99 00770 ../push/.hg/
99 00770 ../push/.hg/
100 00660 ../push/.hg/00changelog.i
100 00660 ../push/.hg/00changelog.i
101 00660 ../push/.hg/requires
101 00660 ../push/.hg/requires
102 00770 ../push/.hg/store/
102 00770 ../push/.hg/store/
103
103
104 $ umask 077
104 $ umask 077
105 $ hg -q push ../push
105 $ hg -q push ../push
106
106
107 after push
107 after push
108 group can still write everything
108 group can still write everything
109
109
110 $ python ../printmodes.py ../push
110 $ python ../printmodes.py ../push
111 00770 ../push/.hg/
111 00770 ../push/.hg/
112 00660 ../push/.hg/00changelog.i
112 00660 ../push/.hg/00changelog.i
113 00770 ../push/.hg/cache/
113 00770 ../push/.hg/cache/
114 00660 ../push/.hg/cache/branchheads
114 00660 ../push/.hg/cache/branchheads
115 00660 ../push/.hg/requires
115 00660 ../push/.hg/requires
116 00770 ../push/.hg/store/
116 00770 ../push/.hg/store/
117 00660 ../push/.hg/store/00changelog.i
117 00660 ../push/.hg/store/00changelog.i
118 00660 ../push/.hg/store/00manifest.i
118 00660 ../push/.hg/store/00manifest.i
119 00770 ../push/.hg/store/data/
119 00770 ../push/.hg/store/data/
120 00770 ../push/.hg/store/data/dir/
120 00770 ../push/.hg/store/data/dir/
121 00660 ../push/.hg/store/data/dir/bar.i
121 00660 ../push/.hg/store/data/dir/bar.i
122 00660 ../push/.hg/store/data/foo.i
122 00660 ../push/.hg/store/data/foo.i
123 00660 ../push/.hg/store/fncache
123 00660 ../push/.hg/store/fncache
124 00660 ../push/.hg/store/phaseroots
124 00660 ../push/.hg/store/undo
125 00660 ../push/.hg/store/undo
125 00660 ../push/.hg/store/undo.phaseroots
126 00660 ../push/.hg/store/undo.phaseroots
126 00660 ../push/.hg/undo.bookmarks
127 00660 ../push/.hg/undo.bookmarks
127 00660 ../push/.hg/undo.branch
128 00660 ../push/.hg/undo.branch
128 00660 ../push/.hg/undo.desc
129 00660 ../push/.hg/undo.desc
129 00660 ../push/.hg/undo.dirstate
130 00660 ../push/.hg/undo.dirstate
130
131
131
132
132 Test that we don't lose the setgid bit when we call chmod.
133 Test that we don't lose the setgid bit when we call chmod.
133 Not all systems support setgid directories (e.g. HFS+), so
134 Not all systems support setgid directories (e.g. HFS+), so
134 just check that directories have the same mode.
135 just check that directories have the same mode.
135
136
136 $ cd ..
137 $ cd ..
137 $ hg init setgid
138 $ hg init setgid
138 $ cd setgid
139 $ cd setgid
139 $ chmod g+rwx .hg/store
140 $ chmod g+rwx .hg/store
140 $ chmod g+s .hg/store 2> /dev/null
141 $ chmod g+s .hg/store 2> /dev/null
141 $ mkdir dir
142 $ mkdir dir
142 $ touch dir/file
143 $ touch dir/file
143 $ hg ci -qAm 'add dir/file'
144 $ hg ci -qAm 'add dir/file'
144 $ storemode=`python ../mode.py .hg/store`
145 $ storemode=`python ../mode.py .hg/store`
145 $ dirmode=`python ../mode.py .hg/store/data/dir`
146 $ dirmode=`python ../mode.py .hg/store/data/dir`
146 $ if [ "$storemode" != "$dirmode" ]; then
147 $ if [ "$storemode" != "$dirmode" ]; then
147 > echo "$storemode != $dirmode"
148 > echo "$storemode != $dirmode"
148 $ fi
149 $ fi
General Comments 0
You need to be logged in to leave comments. Login now