##// END OF EJS Templates
branchmap: server should not advertise secret changeset in branchmap (Issue3303)...
Pierre-Yves David -
r16535:39d1f83e stable
parent child Browse files
Show More
@@ -1,238 +1,241
1 1 # discovery.py - protocol changeset discovery functions
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, short
9 9 from i18n import _
10 10 import util, setdiscovery, treediscovery, phases
11 11
12 12 def findcommonincoming(repo, remote, heads=None, force=False):
13 13 """Return a tuple (common, anyincoming, heads) used to identify the common
14 14 subset of nodes between repo and remote.
15 15
16 16 "common" is a list of (at least) the heads of the common subset.
17 17 "anyincoming" is testable as a boolean indicating if any nodes are missing
18 18 locally. If remote does not support getbundle, this actually is a list of
19 19 roots of the nodes that would be incoming, to be supplied to
20 20 changegroupsubset. No code except for pull should be relying on this fact
21 21 any longer.
22 22 "heads" is either the supplied heads, or else the remote's heads.
23 23
24 24 If you pass heads and they are all known locally, the reponse lists justs
25 25 these heads in "common" and in "heads".
26 26
27 27 Please use findcommonoutgoing to compute the set of outgoing nodes to give
28 28 extensions a good hook into outgoing.
29 29 """
30 30
31 31 if not remote.capable('getbundle'):
32 32 return treediscovery.findcommonincoming(repo, remote, heads, force)
33 33
34 34 if heads:
35 35 allknown = True
36 36 nm = repo.changelog.nodemap
37 37 for h in heads:
38 38 if nm.get(h) is None:
39 39 allknown = False
40 40 break
41 41 if allknown:
42 42 return (heads, False, heads)
43 43
44 44 res = setdiscovery.findcommonheads(repo.ui, repo, remote,
45 45 abortwhenunrelated=not force)
46 46 common, anyinc, srvheads = res
47 47 return (list(common), anyinc, heads or list(srvheads))
48 48
49 49 class outgoing(object):
50 50 '''Represents the set of nodes present in a local repo but not in a
51 51 (possibly) remote one.
52 52
53 53 Members:
54 54
55 55 missing is a list of all nodes present in local but not in remote.
56 56 common is a list of all nodes shared between the two repos.
57 57 excluded is the list of missing changeset that shouldn't be sent remotely.
58 58 missingheads is the list of heads of missing.
59 59 commonheads is the list of heads of common.
60 60
61 61 The sets are computed on demand from the heads, unless provided upfront
62 62 by discovery.'''
63 63
64 64 def __init__(self, revlog, commonheads, missingheads):
65 65 self.commonheads = commonheads
66 66 self.missingheads = missingheads
67 67 self._revlog = revlog
68 68 self._common = None
69 69 self._missing = None
70 70 self.excluded = []
71 71
72 72 def _computecommonmissing(self):
73 73 sets = self._revlog.findcommonmissing(self.commonheads,
74 74 self.missingheads)
75 75 self._common, self._missing = sets
76 76
77 77 @util.propertycache
78 78 def common(self):
79 79 if self._common is None:
80 80 self._computecommonmissing()
81 81 return self._common
82 82
83 83 @util.propertycache
84 84 def missing(self):
85 85 if self._missing is None:
86 86 self._computecommonmissing()
87 87 return self._missing
88 88
89 89 def findcommonoutgoing(repo, other, onlyheads=None, force=False, commoninc=None):
90 90 '''Return an outgoing instance to identify the nodes present in repo but
91 91 not in other.
92 92
93 93 If onlyheads is given, only nodes ancestral to nodes in onlyheads (inclusive)
94 94 are included. If you already know the local repo's heads, passing them in
95 95 onlyheads is faster than letting them be recomputed here.
96 96
97 97 If commoninc is given, it must the the result of a prior call to
98 98 findcommonincoming(repo, other, force) to avoid recomputing it here.'''
99 99 # declare an empty outgoing object to be filled later
100 100 og = outgoing(repo.changelog, None, None)
101 101
102 102 # get common set if not provided
103 103 if commoninc is None:
104 104 commoninc = findcommonincoming(repo, other, force=force)
105 105 og.commonheads, _any, _hds = commoninc
106 106
107 107 # compute outgoing
108 108 if not repo._phaseroots[phases.secret]:
109 109 og.missingheads = onlyheads or repo.heads()
110 110 elif onlyheads is None:
111 111 # use visible heads as it should be cached
112 112 og.missingheads = phases.visibleheads(repo)
113 113 og.excluded = [ctx.node() for ctx in repo.set('secret()')]
114 114 else:
115 115 # compute common, missing and exclude secret stuff
116 116 sets = repo.changelog.findcommonmissing(og.commonheads, onlyheads)
117 117 og._common, allmissing = sets
118 118 og._missing = missing = []
119 119 og.excluded = excluded = []
120 120 for node in allmissing:
121 121 if repo[node].phase() >= phases.secret:
122 122 excluded.append(node)
123 123 else:
124 124 missing.append(node)
125 125 if excluded:
126 126 # update missing heads
127 127 missingheads = phases.newheads(repo, onlyheads, excluded)
128 128 else:
129 129 missingheads = onlyheads
130 130 og.missingheads = missingheads
131 131
132 132 return og
133 133
134 134 def checkheads(repo, remote, outgoing, remoteheads, newbranch=False, inc=False):
135 135 """Check that a push won't add any outgoing head
136 136
137 137 raise Abort error and display ui message as needed.
138 138 """
139 139 if remoteheads == [nullid]:
140 140 # remote is empty, nothing to check.
141 141 return
142 142
143 143 cl = repo.changelog
144 144 if remote.capable('branchmap'):
145 145 # Check for each named branch if we're creating new remote heads.
146 146 # To be a remote head after push, node must be either:
147 147 # - unknown locally
148 148 # - a local outgoing head descended from update
149 149 # - a remote head that's known locally and not
150 150 # ancestral to an outgoing head
151 151
152 152 # 1. Create set of branches involved in the push.
153 153 branches = set(repo[n].branch() for n in outgoing.missing)
154 154
155 155 # 2. Check for new branches on the remote.
156 remotemap = remote.branchmap()
156 if remote.local():
157 remotemap = phases.visiblebranchmap(remote)
158 else:
159 remotemap = remote.branchmap()
157 160 newbranches = branches - set(remotemap)
158 161 if newbranches and not newbranch: # new branch requires --new-branch
159 162 branchnames = ', '.join(sorted(newbranches))
160 163 raise util.Abort(_("push creates new remote branches: %s!")
161 164 % branchnames,
162 165 hint=_("use 'hg push --new-branch' to create"
163 166 " new remote branches"))
164 167 branches.difference_update(newbranches)
165 168
166 169 # 3. Construct the initial oldmap and newmap dicts.
167 170 # They contain information about the remote heads before and
168 171 # after the push, respectively.
169 172 # Heads not found locally are not included in either dict,
170 173 # since they won't be affected by the push.
171 174 # unsynced contains all branches with incoming changesets.
172 175 oldmap = {}
173 176 newmap = {}
174 177 unsynced = set()
175 178 for branch in branches:
176 179 remotebrheads = remotemap[branch]
177 180 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
178 181 oldmap[branch] = prunedbrheads
179 182 newmap[branch] = list(prunedbrheads)
180 183 if len(remotebrheads) > len(prunedbrheads):
181 184 unsynced.add(branch)
182 185
183 186 # 4. Update newmap with outgoing changes.
184 187 # This will possibly add new heads and remove existing ones.
185 188 ctxgen = (repo[n] for n in outgoing.missing)
186 189 repo._updatebranchcache(newmap, ctxgen)
187 190
188 191 else:
189 192 # 1-4b. old servers: Check for new topological heads.
190 193 # Construct {old,new}map with branch = None (topological branch).
191 194 # (code based on _updatebranchcache)
192 195 oldheads = set(h for h in remoteheads if h in cl.nodemap)
193 196 newheads = oldheads.union(outgoing.missing)
194 197 if len(newheads) > 1:
195 198 for latest in reversed(outgoing.missing):
196 199 if latest not in newheads:
197 200 continue
198 201 minhrev = min(cl.rev(h) for h in newheads)
199 202 reachable = cl.reachable(latest, cl.node(minhrev))
200 203 reachable.remove(latest)
201 204 newheads.difference_update(reachable)
202 205 branches = set([None])
203 206 newmap = {None: newheads}
204 207 oldmap = {None: oldheads}
205 208 unsynced = inc and branches or set()
206 209
207 210 # 5. Check for new heads.
208 211 # If there are more heads after the push than before, a suitable
209 212 # error message, depending on unsynced status, is displayed.
210 213 error = None
211 214 for branch in branches:
212 215 newhs = set(newmap[branch])
213 216 oldhs = set(oldmap[branch])
214 217 if len(newhs) > len(oldhs):
215 218 dhs = list(newhs - oldhs)
216 219 if error is None:
217 220 if branch not in ('default', None):
218 221 error = _("push creates new remote head %s "
219 222 "on branch '%s'!") % (short(dhs[0]), branch)
220 223 else:
221 224 error = _("push creates new remote head %s!"
222 225 ) % short(dhs[0])
223 226 if branch in unsynced:
224 227 hint = _("you should pull and merge or "
225 228 "use push -f to force")
226 229 else:
227 230 hint = _("did you forget to merge? "
228 231 "use push -f to force")
229 232 if branch is not None:
230 233 repo.ui.note(_("new remote heads on branch '%s'\n") % branch)
231 234 for h in dhs:
232 235 repo.ui.note(_("new remote head %s\n") % short(h))
233 236 if error:
234 237 raise util.Abort(error, hint=hint)
235 238
236 239 # 6. Check for unsynced changes on involved branches.
237 240 if unsynced:
238 241 repo.ui.warn(_("note: unsynced remote changes!\n"))
@@ -1,321 +1,343
1 1 """ Mercurial phases support code
2 2
3 3 ---
4 4
5 5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 6 Logilab SA <contact@logilab.fr>
7 7 Augie Fackler <durin42@gmail.com>
8 8
9 9 This software may be used and distributed according to the terms of the
10 10 GNU General Public License version 2 or any later version.
11 11
12 12 ---
13 13
14 14 This module implements most phase logic in mercurial.
15 15
16 16
17 17 Basic Concept
18 18 =============
19 19
20 20 A 'changeset phases' is an indicator that tells us how a changeset is
21 21 manipulated and communicated. The details of each phase is described below,
22 22 here we describe the properties they have in common.
23 23
24 24 Like bookmarks, phases are not stored in history and thus are not permanent and
25 25 leave no audit trail.
26 26
27 27 First, no changeset can be in two phases at once. Phases are ordered, so they
28 28 can be considered from lowest to highest. The default, lowest phase is 'public'
29 29 - this is the normal phase of existing changesets. A child changeset can not be
30 30 in a lower phase than its parents.
31 31
32 32 These phases share a hierarchy of traits:
33 33
34 34 immutable shared
35 35 public: X X
36 36 draft: X
37 37 secret:
38 38
39 39 local commits are draft by default
40 40
41 41 Phase movement and exchange
42 42 ============================
43 43
44 44 Phase data are exchanged by pushkey on pull and push. Some server have a
45 45 publish option set, we call them publishing server. Pushing to such server make
46 46 draft changeset publish.
47 47
48 48 A small list of fact/rules define the exchange of phase:
49 49
50 50 * old client never changes server states
51 51 * pull never changes server states
52 52 * publish and old server csets are seen as public by client
53 53
54 54 * Any secret changeset seens in another repository is lowered to at least draft
55 55
56 56
57 57 Here is the final table summing up the 49 possible usecase of phase exchange:
58 58
59 59 server
60 60 old publish non-publish
61 61 N X N D P N D P
62 62 old client
63 63 pull
64 64 N - X/X - X/D X/P - X/D X/P
65 65 X - X/X - X/D X/P - X/D X/P
66 66 push
67 67 X X/X X/X X/P X/P X/P X/D X/D X/P
68 68 new client
69 69 pull
70 70 N - P/X - P/D P/P - D/D P/P
71 71 D - P/X - P/D P/P - D/D P/P
72 72 P - P/X - P/D P/P - P/D P/P
73 73 push
74 74 D P/X P/X P/P P/P P/P D/D D/D P/P
75 75 P P/X P/X P/P P/P P/P P/P P/P P/P
76 76
77 77 Legend:
78 78
79 79 A/B = final state on client / state on server
80 80
81 81 * N = new/not present,
82 82 * P = public,
83 83 * D = draft,
84 84 * X = not tracked (ie: the old client or server has no internal way of
85 85 recording the phase.)
86 86
87 87 passive = only pushes
88 88
89 89
90 90 A cell here can be read like this:
91 91
92 92 "When a new client pushes a draft changeset (D) to a publishing server
93 93 where it's not present (N), it's marked public on both sides (P/P)."
94 94
95 95 Note: old client behave as publish server with Draft only content
96 96 - other people see it as public
97 97 - content is pushed as draft
98 98
99 99 """
100 100
101 101 import errno
102 102 from node import nullid, bin, hex, short
103 103 from i18n import _
104 104
105 105 allphases = public, draft, secret = range(3)
106 106 trackedphases = allphases[1:]
107 107 phasenames = ['public', 'draft', 'secret']
108 108
109 109 def readroots(repo):
110 110 """Read phase roots from disk"""
111 111 roots = [set() for i in allphases]
112 112 try:
113 113 f = repo.sopener('phaseroots')
114 114 try:
115 115 for line in f:
116 116 phase, nh = line.strip().split()
117 117 roots[int(phase)].add(bin(nh))
118 118 finally:
119 119 f.close()
120 120 except IOError, inst:
121 121 if inst.errno != errno.ENOENT:
122 122 raise
123 123 for f in repo._phasedefaults:
124 124 roots = f(repo, roots)
125 125 repo._dirtyphases = True
126 126 return roots
127 127
128 128 def writeroots(repo):
129 129 """Write phase roots from disk"""
130 130 f = repo.sopener('phaseroots', 'w', atomictemp=True)
131 131 try:
132 132 for phase, roots in enumerate(repo._phaseroots):
133 133 for h in roots:
134 134 f.write('%i %s\n' % (phase, hex(h)))
135 135 repo._dirtyphases = False
136 136 finally:
137 137 f.close()
138 138
139 139 def filterunknown(repo, phaseroots=None):
140 140 """remove unknown nodes from the phase boundary
141 141
142 142 no data is lost as unknown node only old data for their descentants
143 143 """
144 144 if phaseroots is None:
145 145 phaseroots = repo._phaseroots
146 146 nodemap = repo.changelog.nodemap # to filter unknown nodes
147 147 for phase, nodes in enumerate(phaseroots):
148 148 missing = [node for node in nodes if node not in nodemap]
149 149 if missing:
150 150 for mnode in missing:
151 151 repo.ui.debug(
152 152 'removing unknown node %s from %i-phase boundary\n'
153 153 % (short(mnode), phase))
154 154 nodes.symmetric_difference_update(missing)
155 155 repo._dirtyphases = True
156 156
157 157 def advanceboundary(repo, targetphase, nodes):
158 158 """Add nodes to a phase changing other nodes phases if necessary.
159 159
160 160 This function move boundary *forward* this means that all nodes are set
161 161 in the target phase or kept in a *lower* phase.
162 162
163 163 Simplify boundary to contains phase roots only."""
164 164 delroots = [] # set of root deleted by this path
165 165 for phase in xrange(targetphase + 1, len(allphases)):
166 166 # filter nodes that are not in a compatible phase already
167 167 # XXX rev phase cache might have been invalidated by a previous loop
168 168 # XXX we need to be smarter here
169 169 nodes = [n for n in nodes if repo[n].phase() >= phase]
170 170 if not nodes:
171 171 break # no roots to move anymore
172 172 roots = repo._phaseroots[phase]
173 173 olds = roots.copy()
174 174 ctxs = list(repo.set('roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
175 175 roots.clear()
176 176 roots.update(ctx.node() for ctx in ctxs)
177 177 if olds != roots:
178 178 # invalidate cache (we probably could be smarter here
179 179 if '_phaserev' in vars(repo):
180 180 del repo._phaserev
181 181 repo._dirtyphases = True
182 182 # some roots may need to be declared for lower phases
183 183 delroots.extend(olds - roots)
184 184 # declare deleted root in the target phase
185 185 if targetphase != 0:
186 186 retractboundary(repo, targetphase, delroots)
187 187
188 188
189 189 def retractboundary(repo, targetphase, nodes):
190 190 """Set nodes back to a phase changing other nodes phases if necessary.
191 191
192 192 This function move boundary *backward* this means that all nodes are set
193 193 in the target phase or kept in a *higher* phase.
194 194
195 195 Simplify boundary to contains phase roots only."""
196 196 currentroots = repo._phaseroots[targetphase]
197 197 newroots = [n for n in nodes if repo[n].phase() < targetphase]
198 198 if newroots:
199 199 currentroots.update(newroots)
200 200 ctxs = repo.set('roots(%ln::)', currentroots)
201 201 currentroots.intersection_update(ctx.node() for ctx in ctxs)
202 202 if '_phaserev' in vars(repo):
203 203 del repo._phaserev
204 204 repo._dirtyphases = True
205 205
206 206
207 207 def listphases(repo):
208 208 """List phases root for serialisation over pushkey"""
209 209 keys = {}
210 210 value = '%i' % draft
211 211 for root in repo._phaseroots[draft]:
212 212 keys[hex(root)] = value
213 213
214 214 if repo.ui.configbool('phases', 'publish', True):
215 215 # Add an extra data to let remote know we are a publishing repo.
216 216 # Publishing repo can't just pretend they are old repo. When pushing to
217 217 # a publishing repo, the client still need to push phase boundary
218 218 #
219 219 # Push do not only push changeset. It also push phase data. New
220 220 # phase data may apply to common changeset which won't be push (as they
221 221 # are common). Here is a very simple example:
222 222 #
223 223 # 1) repo A push changeset X as draft to repo B
224 224 # 2) repo B make changeset X public
225 225 # 3) repo B push to repo A. X is not pushed but the data that X as now
226 226 # public should
227 227 #
228 228 # The server can't handle it on it's own as it has no idea of client
229 229 # phase data.
230 230 keys['publishing'] = 'True'
231 231 return keys
232 232
233 233 def pushphase(repo, nhex, oldphasestr, newphasestr):
234 234 """List phases root for serialisation over pushkey"""
235 235 lock = repo.lock()
236 236 try:
237 237 currentphase = repo[nhex].phase()
238 238 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
239 239 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
240 240 if currentphase == oldphase and newphase < oldphase:
241 241 advanceboundary(repo, newphase, [bin(nhex)])
242 242 return 1
243 243 elif currentphase == newphase:
244 244 # raced, but got correct result
245 245 return 1
246 246 else:
247 247 return 0
248 248 finally:
249 249 lock.release()
250 250
251 251 def visibleheads(repo):
252 252 """return the set of visible head of this repo"""
253 253 # XXX we want a cache on this
254 254 sroots = repo._phaseroots[secret]
255 255 if sroots:
256 256 # XXX very slow revset. storing heads or secret "boundary" would help.
257 257 revset = repo.set('heads(not (%ln::))', sroots)
258 258
259 259 vheads = [ctx.node() for ctx in revset]
260 260 if not vheads:
261 261 vheads.append(nullid)
262 262 else:
263 263 vheads = repo.heads()
264 264 return vheads
265 265
266 def visiblebranchmap(repo):
267 """return a branchmap for the visible set"""
268 # XXX Recomputing this data on the fly is very slow. We should build a
269 # XXX cached version while computin the standard branchmap version.
270 sroots = repo._phaseroots[secret]
271 if sroots:
272 vbranchmap = {}
273 for branch, nodes in repo.branchmap().iteritems():
274 # search for secret heads.
275 for n in nodes:
276 if repo[n].phase() >= secret:
277 nodes = None
278 break
279 # if secreat heads where found we must compute them again
280 if nodes is None:
281 s = repo.set('heads(branch(%s) - secret())', branch)
282 nodes = [c.node() for c in s]
283 vbranchmap[branch] = nodes
284 else:
285 vbranchmap = repo.branchmap()
286 return vbranchmap
287
266 288 def analyzeremotephases(repo, subset, roots):
267 289 """Compute phases heads and root in a subset of node from root dict
268 290
269 291 * subset is heads of the subset
270 292 * roots is {<nodeid> => phase} mapping. key and value are string.
271 293
272 294 Accept unknown element input
273 295 """
274 296 # build list from dictionary
275 297 draftroots = []
276 298 nodemap = repo.changelog.nodemap # to filter unknown nodes
277 299 for nhex, phase in roots.iteritems():
278 300 if nhex == 'publishing': # ignore data related to publish option
279 301 continue
280 302 node = bin(nhex)
281 303 phase = int(phase)
282 304 if phase == 0:
283 305 if node != nullid:
284 306 repo.ui.warn(_('ignoring inconsistent public root'
285 307 ' from remote: %s\n') % nhex)
286 308 elif phase == 1:
287 309 if node in nodemap:
288 310 draftroots.append(node)
289 311 else:
290 312 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
291 313 % (phase, nhex))
292 314 # compute heads
293 315 publicheads = newheads(repo, subset, draftroots)
294 316 return publicheads, draftroots
295 317
296 318 def newheads(repo, heads, roots):
297 319 """compute new head of a subset minus another
298 320
299 321 * `heads`: define the first subset
300 322 * `rroots`: define the second we substract to the first"""
301 323 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
302 324 heads, roots, roots, heads)
303 325 return [c.node() for c in revset]
304 326
305 327
306 328 def newcommitphase(ui):
307 329 """helper to get the target phase of new commit
308 330
309 331 Handle all possible values for the phases.new-commit options.
310 332
311 333 """
312 334 v = ui.config('phases', 'new-commit', draft)
313 335 try:
314 336 return phasenames.index(v)
315 337 except ValueError:
316 338 try:
317 339 return int(v)
318 340 except ValueError:
319 341 msg = _("phases.new-commit: not a valid phase name ('%s')")
320 342 raise error.ConfigError(msg % v)
321 343
@@ -1,616 +1,616
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import urllib, tempfile, os, sys
9 9 from i18n import _
10 10 from node import bin, hex
11 11 import changegroup as changegroupmod
12 12 import repo, error, encoding, util, store
13 13 import phases
14 14
15 15 # abstract batching support
16 16
17 17 class future(object):
18 18 '''placeholder for a value to be set later'''
19 19 def set(self, value):
20 20 if util.safehasattr(self, 'value'):
21 21 raise error.RepoError("future is already set")
22 22 self.value = value
23 23
24 24 class batcher(object):
25 25 '''base class for batches of commands submittable in a single request
26 26
27 27 All methods invoked on instances of this class are simply queued and return a
28 28 a future for the result. Once you call submit(), all the queued calls are
29 29 performed and the results set in their respective futures.
30 30 '''
31 31 def __init__(self):
32 32 self.calls = []
33 33 def __getattr__(self, name):
34 34 def call(*args, **opts):
35 35 resref = future()
36 36 self.calls.append((name, args, opts, resref,))
37 37 return resref
38 38 return call
39 39 def submit(self):
40 40 pass
41 41
42 42 class localbatch(batcher):
43 43 '''performs the queued calls directly'''
44 44 def __init__(self, local):
45 45 batcher.__init__(self)
46 46 self.local = local
47 47 def submit(self):
48 48 for name, args, opts, resref in self.calls:
49 49 resref.set(getattr(self.local, name)(*args, **opts))
50 50
51 51 class remotebatch(batcher):
52 52 '''batches the queued calls; uses as few roundtrips as possible'''
53 53 def __init__(self, remote):
54 54 '''remote must support _submitbatch(encbatch) and _submitone(op, encargs)'''
55 55 batcher.__init__(self)
56 56 self.remote = remote
57 57 def submit(self):
58 58 req, rsp = [], []
59 59 for name, args, opts, resref in self.calls:
60 60 mtd = getattr(self.remote, name)
61 61 batchablefn = getattr(mtd, 'batchable', None)
62 62 if batchablefn is not None:
63 63 batchable = batchablefn(mtd.im_self, *args, **opts)
64 64 encargsorres, encresref = batchable.next()
65 65 if encresref:
66 66 req.append((name, encargsorres,))
67 67 rsp.append((batchable, encresref, resref,))
68 68 else:
69 69 resref.set(encargsorres)
70 70 else:
71 71 if req:
72 72 self._submitreq(req, rsp)
73 73 req, rsp = [], []
74 74 resref.set(mtd(*args, **opts))
75 75 if req:
76 76 self._submitreq(req, rsp)
77 77 def _submitreq(self, req, rsp):
78 78 encresults = self.remote._submitbatch(req)
79 79 for encres, r in zip(encresults, rsp):
80 80 batchable, encresref, resref = r
81 81 encresref.set(encres)
82 82 resref.set(batchable.next())
83 83
84 84 def batchable(f):
85 85 '''annotation for batchable methods
86 86
87 87 Such methods must implement a coroutine as follows:
88 88
89 89 @batchable
90 90 def sample(self, one, two=None):
91 91 # Handle locally computable results first:
92 92 if not one:
93 93 yield "a local result", None
94 94 # Build list of encoded arguments suitable for your wire protocol:
95 95 encargs = [('one', encode(one),), ('two', encode(two),)]
96 96 # Create future for injection of encoded result:
97 97 encresref = future()
98 98 # Return encoded arguments and future:
99 99 yield encargs, encresref
100 100 # Assuming the future to be filled with the result from the batched request
101 101 # now. Decode it:
102 102 yield decode(encresref.value)
103 103
104 104 The decorator returns a function which wraps this coroutine as a plain method,
105 105 but adds the original method as an attribute called "batchable", which is
106 106 used by remotebatch to split the call into separate encoding and decoding
107 107 phases.
108 108 '''
109 109 def plain(*args, **opts):
110 110 batchable = f(*args, **opts)
111 111 encargsorres, encresref = batchable.next()
112 112 if not encresref:
113 113 return encargsorres # a local result in this case
114 114 self = args[0]
115 115 encresref.set(self._submitone(f.func_name, encargsorres))
116 116 return batchable.next()
117 117 setattr(plain, 'batchable', f)
118 118 return plain
119 119
120 120 # list of nodes encoding / decoding
121 121
122 122 def decodelist(l, sep=' '):
123 123 if l:
124 124 return map(bin, l.split(sep))
125 125 return []
126 126
127 127 def encodelist(l, sep=' '):
128 128 return sep.join(map(hex, l))
129 129
130 130 # batched call argument encoding
131 131
132 132 def escapearg(plain):
133 133 return (plain
134 134 .replace(':', '::')
135 135 .replace(',', ':,')
136 136 .replace(';', ':;')
137 137 .replace('=', ':='))
138 138
139 139 def unescapearg(escaped):
140 140 return (escaped
141 141 .replace(':=', '=')
142 142 .replace(':;', ';')
143 143 .replace(':,', ',')
144 144 .replace('::', ':'))
145 145
146 146 # client side
147 147
148 148 def todict(**args):
149 149 return args
150 150
151 151 class wirerepository(repo.repository):
152 152
153 153 def batch(self):
154 154 return remotebatch(self)
155 155 def _submitbatch(self, req):
156 156 cmds = []
157 157 for op, argsdict in req:
158 158 args = ','.join('%s=%s' % p for p in argsdict.iteritems())
159 159 cmds.append('%s %s' % (op, args))
160 160 rsp = self._call("batch", cmds=';'.join(cmds))
161 161 return rsp.split(';')
162 162 def _submitone(self, op, args):
163 163 return self._call(op, **args)
164 164
165 165 @batchable
166 166 def lookup(self, key):
167 167 self.requirecap('lookup', _('look up remote revision'))
168 168 f = future()
169 169 yield todict(key=encoding.fromlocal(key)), f
170 170 d = f.value
171 171 success, data = d[:-1].split(" ", 1)
172 172 if int(success):
173 173 yield bin(data)
174 174 self._abort(error.RepoError(data))
175 175
176 176 @batchable
177 177 def heads(self):
178 178 f = future()
179 179 yield {}, f
180 180 d = f.value
181 181 try:
182 182 yield decodelist(d[:-1])
183 183 except ValueError:
184 184 self._abort(error.ResponseError(_("unexpected response:"), d))
185 185
186 186 @batchable
187 187 def known(self, nodes):
188 188 f = future()
189 189 yield todict(nodes=encodelist(nodes)), f
190 190 d = f.value
191 191 try:
192 192 yield [bool(int(f)) for f in d]
193 193 except ValueError:
194 194 self._abort(error.ResponseError(_("unexpected response:"), d))
195 195
196 196 @batchable
197 197 def branchmap(self):
198 198 f = future()
199 199 yield {}, f
200 200 d = f.value
201 201 try:
202 202 branchmap = {}
203 203 for branchpart in d.splitlines():
204 204 branchname, branchheads = branchpart.split(' ', 1)
205 205 branchname = encoding.tolocal(urllib.unquote(branchname))
206 206 branchheads = decodelist(branchheads)
207 207 branchmap[branchname] = branchheads
208 208 yield branchmap
209 209 except TypeError:
210 210 self._abort(error.ResponseError(_("unexpected response:"), d))
211 211
212 212 def branches(self, nodes):
213 213 n = encodelist(nodes)
214 214 d = self._call("branches", nodes=n)
215 215 try:
216 216 br = [tuple(decodelist(b)) for b in d.splitlines()]
217 217 return br
218 218 except ValueError:
219 219 self._abort(error.ResponseError(_("unexpected response:"), d))
220 220
221 221 def between(self, pairs):
222 222 batch = 8 # avoid giant requests
223 223 r = []
224 224 for i in xrange(0, len(pairs), batch):
225 225 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
226 226 d = self._call("between", pairs=n)
227 227 try:
228 228 r.extend(l and decodelist(l) or [] for l in d.splitlines())
229 229 except ValueError:
230 230 self._abort(error.ResponseError(_("unexpected response:"), d))
231 231 return r
232 232
233 233 @batchable
234 234 def pushkey(self, namespace, key, old, new):
235 235 if not self.capable('pushkey'):
236 236 yield False, None
237 237 f = future()
238 238 yield todict(namespace=encoding.fromlocal(namespace),
239 239 key=encoding.fromlocal(key),
240 240 old=encoding.fromlocal(old),
241 241 new=encoding.fromlocal(new)), f
242 242 d = f.value
243 243 d, output = d.split('\n', 1)
244 244 try:
245 245 d = bool(int(d))
246 246 except ValueError:
247 247 raise error.ResponseError(
248 248 _('push failed (unexpected response):'), d)
249 249 for l in output.splitlines(True):
250 250 self.ui.status(_('remote: '), l)
251 251 yield d
252 252
253 253 @batchable
254 254 def listkeys(self, namespace):
255 255 if not self.capable('pushkey'):
256 256 yield {}, None
257 257 f = future()
258 258 yield todict(namespace=encoding.fromlocal(namespace)), f
259 259 d = f.value
260 260 r = {}
261 261 for l in d.splitlines():
262 262 k, v = l.split('\t')
263 263 r[encoding.tolocal(k)] = encoding.tolocal(v)
264 264 yield r
265 265
266 266 def stream_out(self):
267 267 return self._callstream('stream_out')
268 268
269 269 def changegroup(self, nodes, kind):
270 270 n = encodelist(nodes)
271 271 f = self._callstream("changegroup", roots=n)
272 272 return changegroupmod.unbundle10(self._decompress(f), 'UN')
273 273
274 274 def changegroupsubset(self, bases, heads, kind):
275 275 self.requirecap('changegroupsubset', _('look up remote changes'))
276 276 bases = encodelist(bases)
277 277 heads = encodelist(heads)
278 278 f = self._callstream("changegroupsubset",
279 279 bases=bases, heads=heads)
280 280 return changegroupmod.unbundle10(self._decompress(f), 'UN')
281 281
282 282 def getbundle(self, source, heads=None, common=None):
283 283 self.requirecap('getbundle', _('look up remote changes'))
284 284 opts = {}
285 285 if heads is not None:
286 286 opts['heads'] = encodelist(heads)
287 287 if common is not None:
288 288 opts['common'] = encodelist(common)
289 289 f = self._callstream("getbundle", **opts)
290 290 return changegroupmod.unbundle10(self._decompress(f), 'UN')
291 291
292 292 def unbundle(self, cg, heads, source):
293 293 '''Send cg (a readable file-like object representing the
294 294 changegroup to push, typically a chunkbuffer object) to the
295 295 remote server as a bundle. Return an integer indicating the
296 296 result of the push (see localrepository.addchangegroup()).'''
297 297
298 298 if heads != ['force'] and self.capable('unbundlehash'):
299 299 heads = encodelist(['hashed',
300 300 util.sha1(''.join(sorted(heads))).digest()])
301 301 else:
302 302 heads = encodelist(heads)
303 303
304 304 ret, output = self._callpush("unbundle", cg, heads=heads)
305 305 if ret == "":
306 306 raise error.ResponseError(
307 307 _('push failed:'), output)
308 308 try:
309 309 ret = int(ret)
310 310 except ValueError:
311 311 raise error.ResponseError(
312 312 _('push failed (unexpected response):'), ret)
313 313
314 314 for l in output.splitlines(True):
315 315 self.ui.status(_('remote: '), l)
316 316 return ret
317 317
318 318 def debugwireargs(self, one, two, three=None, four=None, five=None):
319 319 # don't pass optional arguments left at their default value
320 320 opts = {}
321 321 if three is not None:
322 322 opts['three'] = three
323 323 if four is not None:
324 324 opts['four'] = four
325 325 return self._call('debugwireargs', one=one, two=two, **opts)
326 326
327 327 # server side
328 328
329 329 class streamres(object):
330 330 def __init__(self, gen):
331 331 self.gen = gen
332 332
333 333 class pushres(object):
334 334 def __init__(self, res):
335 335 self.res = res
336 336
337 337 class pusherr(object):
338 338 def __init__(self, res):
339 339 self.res = res
340 340
341 341 class ooberror(object):
342 342 def __init__(self, message):
343 343 self.message = message
344 344
345 345 def dispatch(repo, proto, command):
346 346 func, spec = commands[command]
347 347 args = proto.getargs(spec)
348 348 return func(repo, proto, *args)
349 349
350 350 def options(cmd, keys, others):
351 351 opts = {}
352 352 for k in keys:
353 353 if k in others:
354 354 opts[k] = others[k]
355 355 del others[k]
356 356 if others:
357 357 sys.stderr.write("abort: %s got unexpected arguments %s\n"
358 358 % (cmd, ",".join(others)))
359 359 return opts
360 360
361 361 def batch(repo, proto, cmds, others):
362 362 res = []
363 363 for pair in cmds.split(';'):
364 364 op, args = pair.split(' ', 1)
365 365 vals = {}
366 366 for a in args.split(','):
367 367 if a:
368 368 n, v = a.split('=')
369 369 vals[n] = unescapearg(v)
370 370 func, spec = commands[op]
371 371 if spec:
372 372 keys = spec.split()
373 373 data = {}
374 374 for k in keys:
375 375 if k == '*':
376 376 star = {}
377 377 for key in vals.keys():
378 378 if key not in keys:
379 379 star[key] = vals[key]
380 380 data['*'] = star
381 381 else:
382 382 data[k] = vals[k]
383 383 result = func(repo, proto, *[data[k] for k in keys])
384 384 else:
385 385 result = func(repo, proto)
386 386 if isinstance(result, ooberror):
387 387 return result
388 388 res.append(escapearg(result))
389 389 return ';'.join(res)
390 390
391 391 def between(repo, proto, pairs):
392 392 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
393 393 r = []
394 394 for b in repo.between(pairs):
395 395 r.append(encodelist(b) + "\n")
396 396 return "".join(r)
397 397
398 398 def branchmap(repo, proto):
399 branchmap = repo.branchmap()
399 branchmap = phases.visiblebranchmap(repo)
400 400 heads = []
401 401 for branch, nodes in branchmap.iteritems():
402 402 branchname = urllib.quote(encoding.fromlocal(branch))
403 403 branchnodes = encodelist(nodes)
404 404 heads.append('%s %s' % (branchname, branchnodes))
405 405 return '\n'.join(heads)
406 406
407 407 def branches(repo, proto, nodes):
408 408 nodes = decodelist(nodes)
409 409 r = []
410 410 for b in repo.branches(nodes):
411 411 r.append(encodelist(b) + "\n")
412 412 return "".join(r)
413 413
414 414 def capabilities(repo, proto):
415 415 caps = ('lookup changegroupsubset branchmap pushkey known getbundle '
416 416 'unbundlehash batch').split()
417 417 if _allowstream(repo.ui):
418 418 if repo.ui.configbool('server', 'preferuncompressed', False):
419 419 caps.append('stream-preferred')
420 420 requiredformats = repo.requirements & repo.supportedformats
421 421 # if our local revlogs are just revlogv1, add 'stream' cap
422 422 if not requiredformats - set(('revlogv1',)):
423 423 caps.append('stream')
424 424 # otherwise, add 'streamreqs' detailing our local revlog format
425 425 else:
426 426 caps.append('streamreqs=%s' % ','.join(requiredformats))
427 427 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
428 428 caps.append('httpheader=1024')
429 429 return ' '.join(caps)
430 430
431 431 def changegroup(repo, proto, roots):
432 432 nodes = decodelist(roots)
433 433 cg = repo.changegroup(nodes, 'serve')
434 434 return streamres(proto.groupchunks(cg))
435 435
436 436 def changegroupsubset(repo, proto, bases, heads):
437 437 bases = decodelist(bases)
438 438 heads = decodelist(heads)
439 439 cg = repo.changegroupsubset(bases, heads, 'serve')
440 440 return streamres(proto.groupchunks(cg))
441 441
442 442 def debugwireargs(repo, proto, one, two, others):
443 443 # only accept optional args from the known set
444 444 opts = options('debugwireargs', ['three', 'four'], others)
445 445 return repo.debugwireargs(one, two, **opts)
446 446
447 447 def getbundle(repo, proto, others):
448 448 opts = options('getbundle', ['heads', 'common'], others)
449 449 for k, v in opts.iteritems():
450 450 opts[k] = decodelist(v)
451 451 cg = repo.getbundle('serve', **opts)
452 452 return streamres(proto.groupchunks(cg))
453 453
454 454 def heads(repo, proto):
455 455 h = phases.visibleheads(repo)
456 456 return encodelist(h) + "\n"
457 457
458 458 def hello(repo, proto):
459 459 '''the hello command returns a set of lines describing various
460 460 interesting things about the server, in an RFC822-like format.
461 461 Currently the only one defined is "capabilities", which
462 462 consists of a line in the form:
463 463
464 464 capabilities: space separated list of tokens
465 465 '''
466 466 return "capabilities: %s\n" % (capabilities(repo, proto))
467 467
468 468 def listkeys(repo, proto, namespace):
469 469 d = repo.listkeys(encoding.tolocal(namespace)).items()
470 470 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
471 471 for k, v in d])
472 472 return t
473 473
474 474 def lookup(repo, proto, key):
475 475 try:
476 476 k = encoding.tolocal(key)
477 477 c = repo[k]
478 478 if c.phase() == phases.secret:
479 479 raise error.RepoLookupError(_("unknown revision '%s'") % k)
480 480 r = c.hex()
481 481 success = 1
482 482 except Exception, inst:
483 483 r = str(inst)
484 484 success = 0
485 485 return "%s %s\n" % (success, r)
486 486
487 487 def known(repo, proto, nodes, others):
488 488 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
489 489
490 490 def pushkey(repo, proto, namespace, key, old, new):
491 491 # compatibility with pre-1.8 clients which were accidentally
492 492 # sending raw binary nodes rather than utf-8-encoded hex
493 493 if len(new) == 20 and new.encode('string-escape') != new:
494 494 # looks like it could be a binary node
495 495 try:
496 496 new.decode('utf-8')
497 497 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
498 498 except UnicodeDecodeError:
499 499 pass # binary, leave unmodified
500 500 else:
501 501 new = encoding.tolocal(new) # normal path
502 502
503 503 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
504 504 encoding.tolocal(old), new)
505 505 return '%s\n' % int(r)
506 506
507 507 def _allowstream(ui):
508 508 return ui.configbool('server', 'uncompressed', True, untrusted=True)
509 509
510 510 def stream(repo, proto):
511 511 '''If the server supports streaming clone, it advertises the "stream"
512 512 capability with a value representing the version and flags of the repo
513 513 it is serving. Client checks to see if it understands the format.
514 514
515 515 The format is simple: the server writes out a line with the amount
516 516 of files, then the total amount of bytes to be transfered (separated
517 517 by a space). Then, for each file, the server first writes the filename
518 518 and filesize (separated by the null character), then the file contents.
519 519 '''
520 520
521 521 if not _allowstream(repo.ui):
522 522 return '1\n'
523 523
524 524 entries = []
525 525 total_bytes = 0
526 526 try:
527 527 # get consistent snapshot of repo, lock during scan
528 528 lock = repo.lock()
529 529 try:
530 530 repo.ui.debug('scanning\n')
531 531 for name, ename, size in repo.store.walk():
532 532 entries.append((name, size))
533 533 total_bytes += size
534 534 finally:
535 535 lock.release()
536 536 except error.LockError:
537 537 return '2\n' # error: 2
538 538
539 539 def streamer(repo, entries, total):
540 540 '''stream out all metadata files in repository.'''
541 541 yield '0\n' # success
542 542 repo.ui.debug('%d files, %d bytes to transfer\n' %
543 543 (len(entries), total_bytes))
544 544 yield '%d %d\n' % (len(entries), total_bytes)
545 545 for name, size in entries:
546 546 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
547 547 # partially encode name over the wire for backwards compat
548 548 yield '%s\0%d\n' % (store.encodedir(name), size)
549 549 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
550 550 yield chunk
551 551
552 552 return streamres(streamer(repo, entries, total_bytes))
553 553
554 554 def unbundle(repo, proto, heads):
555 555 their_heads = decodelist(heads)
556 556
557 557 def check_heads():
558 558 heads = phases.visibleheads(repo)
559 559 heads_hash = util.sha1(''.join(sorted(heads))).digest()
560 560 return (their_heads == ['force'] or their_heads == heads or
561 561 their_heads == ['hashed', heads_hash])
562 562
563 563 proto.redirect()
564 564
565 565 # fail early if possible
566 566 if not check_heads():
567 567 return pusherr('unsynced changes')
568 568
569 569 # write bundle data to temporary file because it can be big
570 570 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
571 571 fp = os.fdopen(fd, 'wb+')
572 572 r = 0
573 573 try:
574 574 proto.getfile(fp)
575 575 lock = repo.lock()
576 576 try:
577 577 if not check_heads():
578 578 # someone else committed/pushed/unbundled while we
579 579 # were transferring data
580 580 return pusherr('unsynced changes')
581 581
582 582 # push can proceed
583 583 fp.seek(0)
584 584 gen = changegroupmod.readbundle(fp, None)
585 585
586 586 try:
587 587 r = repo.addchangegroup(gen, 'serve', proto._client())
588 588 except util.Abort, inst:
589 589 sys.stderr.write("abort: %s\n" % inst)
590 590 finally:
591 591 lock.release()
592 592 return pushres(r)
593 593
594 594 finally:
595 595 fp.close()
596 596 os.unlink(tempname)
597 597
598 598 commands = {
599 599 'batch': (batch, 'cmds *'),
600 600 'between': (between, 'pairs'),
601 601 'branchmap': (branchmap, ''),
602 602 'branches': (branches, 'nodes'),
603 603 'capabilities': (capabilities, ''),
604 604 'changegroup': (changegroup, 'roots'),
605 605 'changegroupsubset': (changegroupsubset, 'bases heads'),
606 606 'debugwireargs': (debugwireargs, 'one two *'),
607 607 'getbundle': (getbundle, '*'),
608 608 'heads': (heads, ''),
609 609 'hello': (hello, ''),
610 610 'known': (known, 'nodes *'),
611 611 'listkeys': (listkeys, 'namespace'),
612 612 'lookup': (lookup, 'key'),
613 613 'pushkey': (pushkey, 'namespace key old new'),
614 614 'stream_out': (stream, ''),
615 615 'unbundle': (unbundle, 'heads'),
616 616 }
@@ -1,1055 +1,1063
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > graphlog=
4 4 > EOF
5 5 $ hgph() { hg log -G --template "{rev} {phase} {desc} - {node|short}\n" $*; }
6 6
7 7 $ mkcommit() {
8 8 > echo "$1" > "$1"
9 9 > hg add "$1"
10 10 > message="$1"
11 11 > shift
12 12 > hg ci -m "$message" $*
13 13 > }
14 14
15 15 $ hg init alpha
16 16 $ cd alpha
17 17 $ mkcommit a-A
18 18 $ mkcommit a-B
19 19 $ mkcommit a-C
20 20 $ mkcommit a-D
21 21 $ hgph
22 22 @ 3 draft a-D - b555f63b6063
23 23 |
24 24 o 2 draft a-C - 54acac6f23ab
25 25 |
26 26 o 1 draft a-B - 548a3d25dbf0
27 27 |
28 28 o 0 draft a-A - 054250a37db4
29 29
30 30
31 31 $ hg init ../beta
32 32 $ hg push -r 1 ../beta
33 33 pushing to ../beta
34 34 searching for changes
35 35 adding changesets
36 36 adding manifests
37 37 adding file changes
38 38 added 2 changesets with 2 changes to 2 files
39 39 $ hgph
40 40 @ 3 draft a-D - b555f63b6063
41 41 |
42 42 o 2 draft a-C - 54acac6f23ab
43 43 |
44 44 o 1 public a-B - 548a3d25dbf0
45 45 |
46 46 o 0 public a-A - 054250a37db4
47 47
48 48
49 49 $ cd ../beta
50 50 $ hgph
51 51 o 1 public a-B - 548a3d25dbf0
52 52 |
53 53 o 0 public a-A - 054250a37db4
54 54
55 55 $ hg up -q
56 56 $ mkcommit b-A
57 57 $ hgph
58 58 @ 2 draft b-A - f54f1bb90ff3
59 59 |
60 60 o 1 public a-B - 548a3d25dbf0
61 61 |
62 62 o 0 public a-A - 054250a37db4
63 63
64 64 $ hg pull ../alpha
65 65 pulling from ../alpha
66 66 searching for changes
67 67 adding changesets
68 68 adding manifests
69 69 adding file changes
70 70 added 2 changesets with 2 changes to 2 files (+1 heads)
71 71 (run 'hg heads' to see heads, 'hg merge' to merge)
72 72 $ hgph
73 73 o 4 public a-D - b555f63b6063
74 74 |
75 75 o 3 public a-C - 54acac6f23ab
76 76 |
77 77 | @ 2 draft b-A - f54f1bb90ff3
78 78 |/
79 79 o 1 public a-B - 548a3d25dbf0
80 80 |
81 81 o 0 public a-A - 054250a37db4
82 82
83 83
84 84 pull did not updated ../alpha state.
85 85 push from alpha to beta should update phase even if nothing is transfered
86 86
87 87 $ cd ../alpha
88 88 $ hgph # not updated by remote pull
89 89 @ 3 draft a-D - b555f63b6063
90 90 |
91 91 o 2 draft a-C - 54acac6f23ab
92 92 |
93 93 o 1 public a-B - 548a3d25dbf0
94 94 |
95 95 o 0 public a-A - 054250a37db4
96 96
97 97 $ hg push ../beta
98 98 pushing to ../beta
99 99 searching for changes
100 100 no changes found
101 101 [1]
102 102 $ hgph
103 103 @ 3 public a-D - b555f63b6063
104 104 |
105 105 o 2 public a-C - 54acac6f23ab
106 106 |
107 107 o 1 public a-B - 548a3d25dbf0
108 108 |
109 109 o 0 public a-A - 054250a37db4
110 110
111 111
112 112 update must update phase of common changeset too
113 113
114 114 $ hg pull ../beta # getting b-A
115 115 pulling from ../beta
116 116 searching for changes
117 117 adding changesets
118 118 adding manifests
119 119 adding file changes
120 120 added 1 changesets with 1 changes to 1 files (+1 heads)
121 121 (run 'hg heads' to see heads, 'hg merge' to merge)
122 122
123 123 $ cd ../beta
124 124 $ hgph # not updated by remote pull
125 125 o 4 public a-D - b555f63b6063
126 126 |
127 127 o 3 public a-C - 54acac6f23ab
128 128 |
129 129 | @ 2 draft b-A - f54f1bb90ff3
130 130 |/
131 131 o 1 public a-B - 548a3d25dbf0
132 132 |
133 133 o 0 public a-A - 054250a37db4
134 134
135 135 $ hg pull ../alpha
136 136 pulling from ../alpha
137 137 searching for changes
138 138 no changes found
139 139 $ hgph
140 140 o 4 public a-D - b555f63b6063
141 141 |
142 142 o 3 public a-C - 54acac6f23ab
143 143 |
144 144 | @ 2 public b-A - f54f1bb90ff3
145 145 |/
146 146 o 1 public a-B - 548a3d25dbf0
147 147 |
148 148 o 0 public a-A - 054250a37db4
149 149
150 150
151 151 Publish configuration option
152 152 ----------------------------
153 153
154 154 Pull
155 155 ````
156 156
157 157 changegroup are added without phase movement
158 158
159 159 $ hg bundle -a ../base.bundle
160 160 5 changesets found
161 161 $ cd ..
162 162 $ hg init mu
163 163 $ cd mu
164 164 $ cat > .hg/hgrc << EOF
165 165 > [phases]
166 166 > publish=0
167 167 > EOF
168 168 $ hg unbundle ../base.bundle
169 169 adding changesets
170 170 adding manifests
171 171 adding file changes
172 172 added 5 changesets with 5 changes to 5 files (+1 heads)
173 173 (run 'hg heads' to see heads, 'hg merge' to merge)
174 174 $ hgph
175 175 o 4 draft a-D - b555f63b6063
176 176 |
177 177 o 3 draft a-C - 54acac6f23ab
178 178 |
179 179 | o 2 draft b-A - f54f1bb90ff3
180 180 |/
181 181 o 1 draft a-B - 548a3d25dbf0
182 182 |
183 183 o 0 draft a-A - 054250a37db4
184 184
185 185 $ cd ..
186 186
187 187 Pulling from publish=False to publish=False does not move boundary.
188 188
189 189 $ hg init nu
190 190 $ cd nu
191 191 $ cat > .hg/hgrc << EOF
192 192 > [phases]
193 193 > publish=0
194 194 > EOF
195 195 $ hg pull ../mu -r 54acac6f23ab
196 196 pulling from ../mu
197 197 adding changesets
198 198 adding manifests
199 199 adding file changes
200 200 added 3 changesets with 3 changes to 3 files
201 201 (run 'hg update' to get a working copy)
202 202 $ hgph
203 203 o 2 draft a-C - 54acac6f23ab
204 204 |
205 205 o 1 draft a-B - 548a3d25dbf0
206 206 |
207 207 o 0 draft a-A - 054250a37db4
208 208
209 209
210 210 Even for common
211 211
212 212 $ hg pull ../mu -r f54f1bb90ff3
213 213 pulling from ../mu
214 214 searching for changes
215 215 adding changesets
216 216 adding manifests
217 217 adding file changes
218 218 added 1 changesets with 1 changes to 1 files (+1 heads)
219 219 (run 'hg heads' to see heads, 'hg merge' to merge)
220 220 $ hgph
221 221 o 3 draft b-A - f54f1bb90ff3
222 222 |
223 223 | o 2 draft a-C - 54acac6f23ab
224 224 |/
225 225 o 1 draft a-B - 548a3d25dbf0
226 226 |
227 227 o 0 draft a-A - 054250a37db4
228 228
229 229
230 230
231 231 Pulling from Publish=True to Publish=False move boundary in common set.
232 232 we are in nu
233 233
234 234 $ hg pull ../alpha -r b555f63b6063
235 235 pulling from ../alpha
236 236 searching for changes
237 237 adding changesets
238 238 adding manifests
239 239 adding file changes
240 240 added 1 changesets with 1 changes to 1 files
241 241 (run 'hg update' to get a working copy)
242 242 $ hgph # f54f1bb90ff3 stay draft, not ancestor of -r
243 243 o 4 public a-D - b555f63b6063
244 244 |
245 245 | o 3 draft b-A - f54f1bb90ff3
246 246 | |
247 247 o | 2 public a-C - 54acac6f23ab
248 248 |/
249 249 o 1 public a-B - 548a3d25dbf0
250 250 |
251 251 o 0 public a-A - 054250a37db4
252 252
253 253
254 254 pulling from Publish=False to publish=False with some public
255 255
256 256 $ hg up -q f54f1bb90ff3
257 257 $ mkcommit n-A
258 258 $ mkcommit n-B
259 259 $ hgph
260 260 @ 6 draft n-B - 145e75495359
261 261 |
262 262 o 5 draft n-A - d6bcb4f74035
263 263 |
264 264 | o 4 public a-D - b555f63b6063
265 265 | |
266 266 o | 3 draft b-A - f54f1bb90ff3
267 267 | |
268 268 | o 2 public a-C - 54acac6f23ab
269 269 |/
270 270 o 1 public a-B - 548a3d25dbf0
271 271 |
272 272 o 0 public a-A - 054250a37db4
273 273
274 274 $ cd ../mu
275 275 $ hg pull ../nu
276 276 pulling from ../nu
277 277 searching for changes
278 278 adding changesets
279 279 adding manifests
280 280 adding file changes
281 281 added 2 changesets with 2 changes to 2 files
282 282 (run 'hg update' to get a working copy)
283 283 $ hgph
284 284 o 6 draft n-B - 145e75495359
285 285 |
286 286 o 5 draft n-A - d6bcb4f74035
287 287 |
288 288 | o 4 public a-D - b555f63b6063
289 289 | |
290 290 | o 3 public a-C - 54acac6f23ab
291 291 | |
292 292 o | 2 draft b-A - f54f1bb90ff3
293 293 |/
294 294 o 1 public a-B - 548a3d25dbf0
295 295 |
296 296 o 0 public a-A - 054250a37db4
297 297
298 298 $ cd ..
299 299
300 300 pulling into publish=True
301 301
302 302 $ cd alpha
303 303 $ hgph
304 304 o 4 public b-A - f54f1bb90ff3
305 305 |
306 306 | @ 3 public a-D - b555f63b6063
307 307 | |
308 308 | o 2 public a-C - 54acac6f23ab
309 309 |/
310 310 o 1 public a-B - 548a3d25dbf0
311 311 |
312 312 o 0 public a-A - 054250a37db4
313 313
314 314 $ hg pull ../mu
315 315 pulling from ../mu
316 316 searching for changes
317 317 adding changesets
318 318 adding manifests
319 319 adding file changes
320 320 added 2 changesets with 2 changes to 2 files
321 321 (run 'hg update' to get a working copy)
322 322 $ hgph
323 323 o 6 draft n-B - 145e75495359
324 324 |
325 325 o 5 draft n-A - d6bcb4f74035
326 326 |
327 327 o 4 public b-A - f54f1bb90ff3
328 328 |
329 329 | @ 3 public a-D - b555f63b6063
330 330 | |
331 331 | o 2 public a-C - 54acac6f23ab
332 332 |/
333 333 o 1 public a-B - 548a3d25dbf0
334 334 |
335 335 o 0 public a-A - 054250a37db4
336 336
337 337 $ cd ..
338 338
339 339 pulling back into original repo
340 340
341 341 $ cd nu
342 342 $ hg pull ../alpha
343 343 pulling from ../alpha
344 344 searching for changes
345 345 no changes found
346 346 $ hgph
347 347 @ 6 public n-B - 145e75495359
348 348 |
349 349 o 5 public n-A - d6bcb4f74035
350 350 |
351 351 | o 4 public a-D - b555f63b6063
352 352 | |
353 353 o | 3 public b-A - f54f1bb90ff3
354 354 | |
355 355 | o 2 public a-C - 54acac6f23ab
356 356 |/
357 357 o 1 public a-B - 548a3d25dbf0
358 358 |
359 359 o 0 public a-A - 054250a37db4
360 360
361 361
362 362 Push
363 363 ````
364 364
365 365 (inserted)
366 366
367 367 Test that phase are pushed even when they are nothing to pus
368 368 (this might be tested later bu are very convenient to not alter too much test)
369 369
370 370 Push back to alpha
371 371
372 372 $ hg push ../alpha # from nu
373 373 pushing to ../alpha
374 374 searching for changes
375 375 no changes found
376 376 [1]
377 377 $ cd ..
378 378 $ cd alpha
379 379 $ hgph
380 380 o 6 public n-B - 145e75495359
381 381 |
382 382 o 5 public n-A - d6bcb4f74035
383 383 |
384 384 o 4 public b-A - f54f1bb90ff3
385 385 |
386 386 | @ 3 public a-D - b555f63b6063
387 387 | |
388 388 | o 2 public a-C - 54acac6f23ab
389 389 |/
390 390 o 1 public a-B - 548a3d25dbf0
391 391 |
392 392 o 0 public a-A - 054250a37db4
393 393
394 394
395 395 (end insertion)
396 396
397 397
398 398 initial setup
399 399
400 400 $ hg glog # of alpha
401 401 o changeset: 6:145e75495359
402 402 | tag: tip
403 403 | user: test
404 404 | date: Thu Jan 01 00:00:00 1970 +0000
405 405 | summary: n-B
406 406 |
407 407 o changeset: 5:d6bcb4f74035
408 408 | user: test
409 409 | date: Thu Jan 01 00:00:00 1970 +0000
410 410 | summary: n-A
411 411 |
412 412 o changeset: 4:f54f1bb90ff3
413 413 | parent: 1:548a3d25dbf0
414 414 | user: test
415 415 | date: Thu Jan 01 00:00:00 1970 +0000
416 416 | summary: b-A
417 417 |
418 418 | @ changeset: 3:b555f63b6063
419 419 | | user: test
420 420 | | date: Thu Jan 01 00:00:00 1970 +0000
421 421 | | summary: a-D
422 422 | |
423 423 | o changeset: 2:54acac6f23ab
424 424 |/ user: test
425 425 | date: Thu Jan 01 00:00:00 1970 +0000
426 426 | summary: a-C
427 427 |
428 428 o changeset: 1:548a3d25dbf0
429 429 | user: test
430 430 | date: Thu Jan 01 00:00:00 1970 +0000
431 431 | summary: a-B
432 432 |
433 433 o changeset: 0:054250a37db4
434 434 user: test
435 435 date: Thu Jan 01 00:00:00 1970 +0000
436 436 summary: a-A
437 437
438 438 $ mkcommit a-E
439 439 $ mkcommit a-F
440 440 $ mkcommit a-G
441 441 $ hg up d6bcb4f74035 -q
442 442 $ mkcommit a-H
443 443 created new head
444 444 $ hgph
445 445 @ 10 draft a-H - 967b449fbc94
446 446 |
447 447 | o 9 draft a-G - 3e27b6f1eee1
448 448 | |
449 449 | o 8 draft a-F - b740e3e5c05d
450 450 | |
451 451 | o 7 draft a-E - e9f537e46dea
452 452 | |
453 453 +---o 6 public n-B - 145e75495359
454 454 | |
455 455 o | 5 public n-A - d6bcb4f74035
456 456 | |
457 457 o | 4 public b-A - f54f1bb90ff3
458 458 | |
459 459 | o 3 public a-D - b555f63b6063
460 460 | |
461 461 | o 2 public a-C - 54acac6f23ab
462 462 |/
463 463 o 1 public a-B - 548a3d25dbf0
464 464 |
465 465 o 0 public a-A - 054250a37db4
466 466
467 467
468 468 Pulling from bundle does not alter phases of changeset not present in the bundle
469 469
470 470 $ hg bundle --base 1 -r 6 -r 3 ../partial-bundle.hg
471 471 5 changesets found
472 472 $ hg pull ../partial-bundle.hg
473 473 pulling from ../partial-bundle.hg
474 474 searching for changes
475 475 no changes found
476 476 $ hgph
477 477 @ 10 draft a-H - 967b449fbc94
478 478 |
479 479 | o 9 draft a-G - 3e27b6f1eee1
480 480 | |
481 481 | o 8 draft a-F - b740e3e5c05d
482 482 | |
483 483 | o 7 draft a-E - e9f537e46dea
484 484 | |
485 485 +---o 6 public n-B - 145e75495359
486 486 | |
487 487 o | 5 public n-A - d6bcb4f74035
488 488 | |
489 489 o | 4 public b-A - f54f1bb90ff3
490 490 | |
491 491 | o 3 public a-D - b555f63b6063
492 492 | |
493 493 | o 2 public a-C - 54acac6f23ab
494 494 |/
495 495 o 1 public a-B - 548a3d25dbf0
496 496 |
497 497 o 0 public a-A - 054250a37db4
498 498
499 499
500 500 Pushing to Publish=False (unknown changeset)
501 501
502 502 $ hg push ../mu -r b740e3e5c05d # a-F
503 503 pushing to ../mu
504 504 searching for changes
505 505 adding changesets
506 506 adding manifests
507 507 adding file changes
508 508 added 2 changesets with 2 changes to 2 files
509 509 $ hgph
510 510 @ 10 draft a-H - 967b449fbc94
511 511 |
512 512 | o 9 draft a-G - 3e27b6f1eee1
513 513 | |
514 514 | o 8 draft a-F - b740e3e5c05d
515 515 | |
516 516 | o 7 draft a-E - e9f537e46dea
517 517 | |
518 518 +---o 6 public n-B - 145e75495359
519 519 | |
520 520 o | 5 public n-A - d6bcb4f74035
521 521 | |
522 522 o | 4 public b-A - f54f1bb90ff3
523 523 | |
524 524 | o 3 public a-D - b555f63b6063
525 525 | |
526 526 | o 2 public a-C - 54acac6f23ab
527 527 |/
528 528 o 1 public a-B - 548a3d25dbf0
529 529 |
530 530 o 0 public a-A - 054250a37db4
531 531
532 532
533 533 $ cd ../mu
534 534 $ hgph # again f54f1bb90ff3, d6bcb4f74035 and 145e75495359 stay draft,
535 535 > # not ancestor of -r
536 536 o 8 draft a-F - b740e3e5c05d
537 537 |
538 538 o 7 draft a-E - e9f537e46dea
539 539 |
540 540 | o 6 draft n-B - 145e75495359
541 541 | |
542 542 | o 5 draft n-A - d6bcb4f74035
543 543 | |
544 544 o | 4 public a-D - b555f63b6063
545 545 | |
546 546 o | 3 public a-C - 54acac6f23ab
547 547 | |
548 548 | o 2 draft b-A - f54f1bb90ff3
549 549 |/
550 550 o 1 public a-B - 548a3d25dbf0
551 551 |
552 552 o 0 public a-A - 054250a37db4
553 553
554 554
555 555 Pushing to Publish=True (unknown changeset)
556 556
557 557 $ hg push ../beta -r b740e3e5c05d
558 558 pushing to ../beta
559 559 searching for changes
560 560 adding changesets
561 561 adding manifests
562 562 adding file changes
563 563 added 2 changesets with 2 changes to 2 files
564 564 $ hgph # again f54f1bb90ff3, d6bcb4f74035 and 145e75495359 stay draft,
565 565 > # not ancestor of -r
566 566 o 8 public a-F - b740e3e5c05d
567 567 |
568 568 o 7 public a-E - e9f537e46dea
569 569 |
570 570 | o 6 draft n-B - 145e75495359
571 571 | |
572 572 | o 5 draft n-A - d6bcb4f74035
573 573 | |
574 574 o | 4 public a-D - b555f63b6063
575 575 | |
576 576 o | 3 public a-C - 54acac6f23ab
577 577 | |
578 578 | o 2 draft b-A - f54f1bb90ff3
579 579 |/
580 580 o 1 public a-B - 548a3d25dbf0
581 581 |
582 582 o 0 public a-A - 054250a37db4
583 583
584 584
585 585 Pushing to Publish=True (common changeset)
586 586
587 587 $ cd ../beta
588 588 $ hg push ../alpha
589 589 pushing to ../alpha
590 590 searching for changes
591 591 no changes found
592 592 [1]
593 593 $ hgph
594 594 o 6 public a-F - b740e3e5c05d
595 595 |
596 596 o 5 public a-E - e9f537e46dea
597 597 |
598 598 o 4 public a-D - b555f63b6063
599 599 |
600 600 o 3 public a-C - 54acac6f23ab
601 601 |
602 602 | @ 2 public b-A - f54f1bb90ff3
603 603 |/
604 604 o 1 public a-B - 548a3d25dbf0
605 605 |
606 606 o 0 public a-A - 054250a37db4
607 607
608 608 $ cd ../alpha
609 609 $ hgph
610 610 @ 10 draft a-H - 967b449fbc94
611 611 |
612 612 | o 9 draft a-G - 3e27b6f1eee1
613 613 | |
614 614 | o 8 public a-F - b740e3e5c05d
615 615 | |
616 616 | o 7 public a-E - e9f537e46dea
617 617 | |
618 618 +---o 6 public n-B - 145e75495359
619 619 | |
620 620 o | 5 public n-A - d6bcb4f74035
621 621 | |
622 622 o | 4 public b-A - f54f1bb90ff3
623 623 | |
624 624 | o 3 public a-D - b555f63b6063
625 625 | |
626 626 | o 2 public a-C - 54acac6f23ab
627 627 |/
628 628 o 1 public a-B - 548a3d25dbf0
629 629 |
630 630 o 0 public a-A - 054250a37db4
631 631
632 632
633 633 Pushing to Publish=False (common changeset that change phase + unknown one)
634 634
635 635 $ hg push ../mu -r 967b449fbc94 -f
636 636 pushing to ../mu
637 637 searching for changes
638 638 adding changesets
639 639 adding manifests
640 640 adding file changes
641 641 added 1 changesets with 1 changes to 1 files (+1 heads)
642 642 $ hgph
643 643 @ 10 draft a-H - 967b449fbc94
644 644 |
645 645 | o 9 draft a-G - 3e27b6f1eee1
646 646 | |
647 647 | o 8 public a-F - b740e3e5c05d
648 648 | |
649 649 | o 7 public a-E - e9f537e46dea
650 650 | |
651 651 +---o 6 public n-B - 145e75495359
652 652 | |
653 653 o | 5 public n-A - d6bcb4f74035
654 654 | |
655 655 o | 4 public b-A - f54f1bb90ff3
656 656 | |
657 657 | o 3 public a-D - b555f63b6063
658 658 | |
659 659 | o 2 public a-C - 54acac6f23ab
660 660 |/
661 661 o 1 public a-B - 548a3d25dbf0
662 662 |
663 663 o 0 public a-A - 054250a37db4
664 664
665 665 $ cd ../mu
666 666 $ hgph # d6bcb4f74035 should have changed phase
667 667 > # 145e75495359 is still draft. not ancestor of -r
668 668 o 9 draft a-H - 967b449fbc94
669 669 |
670 670 | o 8 public a-F - b740e3e5c05d
671 671 | |
672 672 | o 7 public a-E - e9f537e46dea
673 673 | |
674 674 +---o 6 draft n-B - 145e75495359
675 675 | |
676 676 o | 5 public n-A - d6bcb4f74035
677 677 | |
678 678 | o 4 public a-D - b555f63b6063
679 679 | |
680 680 | o 3 public a-C - 54acac6f23ab
681 681 | |
682 682 o | 2 public b-A - f54f1bb90ff3
683 683 |/
684 684 o 1 public a-B - 548a3d25dbf0
685 685 |
686 686 o 0 public a-A - 054250a37db4
687 687
688 688
689 689
690 690 Pushing to Publish=True (common changeset from publish=False)
691 691
692 692 (in mu)
693 693 $ hg push ../alpha
694 694 pushing to ../alpha
695 695 searching for changes
696 696 no changes found
697 697 [1]
698 698 $ hgph
699 699 o 9 public a-H - 967b449fbc94
700 700 |
701 701 | o 8 public a-F - b740e3e5c05d
702 702 | |
703 703 | o 7 public a-E - e9f537e46dea
704 704 | |
705 705 +---o 6 public n-B - 145e75495359
706 706 | |
707 707 o | 5 public n-A - d6bcb4f74035
708 708 | |
709 709 | o 4 public a-D - b555f63b6063
710 710 | |
711 711 | o 3 public a-C - 54acac6f23ab
712 712 | |
713 713 o | 2 public b-A - f54f1bb90ff3
714 714 |/
715 715 o 1 public a-B - 548a3d25dbf0
716 716 |
717 717 o 0 public a-A - 054250a37db4
718 718
719 719 $ hgph -R ../alpha # a-H should have been synced to 0
720 720 @ 10 public a-H - 967b449fbc94
721 721 |
722 722 | o 9 draft a-G - 3e27b6f1eee1
723 723 | |
724 724 | o 8 public a-F - b740e3e5c05d
725 725 | |
726 726 | o 7 public a-E - e9f537e46dea
727 727 | |
728 728 +---o 6 public n-B - 145e75495359
729 729 | |
730 730 o | 5 public n-A - d6bcb4f74035
731 731 | |
732 732 o | 4 public b-A - f54f1bb90ff3
733 733 | |
734 734 | o 3 public a-D - b555f63b6063
735 735 | |
736 736 | o 2 public a-C - 54acac6f23ab
737 737 |/
738 738 o 1 public a-B - 548a3d25dbf0
739 739 |
740 740 o 0 public a-A - 054250a37db4
741 741
742 742
743 743
744 744 Discovery locally secret changeset on a remote repository:
745 745
746 746 - should make it non-secret
747 747
748 748 $ cd ../alpha
749 749 $ mkcommit A-secret --config phases.new-commit=2
750 750 $ hgph
751 751 @ 11 secret A-secret - 435b5d83910c
752 752 |
753 753 o 10 public a-H - 967b449fbc94
754 754 |
755 755 | o 9 draft a-G - 3e27b6f1eee1
756 756 | |
757 757 | o 8 public a-F - b740e3e5c05d
758 758 | |
759 759 | o 7 public a-E - e9f537e46dea
760 760 | |
761 761 +---o 6 public n-B - 145e75495359
762 762 | |
763 763 o | 5 public n-A - d6bcb4f74035
764 764 | |
765 765 o | 4 public b-A - f54f1bb90ff3
766 766 | |
767 767 | o 3 public a-D - b555f63b6063
768 768 | |
769 769 | o 2 public a-C - 54acac6f23ab
770 770 |/
771 771 o 1 public a-B - 548a3d25dbf0
772 772 |
773 773 o 0 public a-A - 054250a37db4
774 774
775 775 $ hg bundle --base 'parents(.)' -r . ../secret-bundle.hg
776 776 1 changesets found
777 777 $ hg -R ../mu unbundle ../secret-bundle.hg
778 778 adding changesets
779 779 adding manifests
780 780 adding file changes
781 781 added 1 changesets with 1 changes to 1 files
782 782 (run 'hg update' to get a working copy)
783 783 $ hgph -R ../mu
784 784 o 10 draft A-secret - 435b5d83910c
785 785 |
786 786 o 9 public a-H - 967b449fbc94
787 787 |
788 788 | o 8 public a-F - b740e3e5c05d
789 789 | |
790 790 | o 7 public a-E - e9f537e46dea
791 791 | |
792 792 +---o 6 public n-B - 145e75495359
793 793 | |
794 794 o | 5 public n-A - d6bcb4f74035
795 795 | |
796 796 | o 4 public a-D - b555f63b6063
797 797 | |
798 798 | o 3 public a-C - 54acac6f23ab
799 799 | |
800 800 o | 2 public b-A - f54f1bb90ff3
801 801 |/
802 802 o 1 public a-B - 548a3d25dbf0
803 803 |
804 804 o 0 public a-A - 054250a37db4
805 805
806 806 $ hg pull ../mu
807 807 pulling from ../mu
808 808 searching for changes
809 809 no changes found
810 810 $ hgph
811 811 @ 11 draft A-secret - 435b5d83910c
812 812 |
813 813 o 10 public a-H - 967b449fbc94
814 814 |
815 815 | o 9 draft a-G - 3e27b6f1eee1
816 816 | |
817 817 | o 8 public a-F - b740e3e5c05d
818 818 | |
819 819 | o 7 public a-E - e9f537e46dea
820 820 | |
821 821 +---o 6 public n-B - 145e75495359
822 822 | |
823 823 o | 5 public n-A - d6bcb4f74035
824 824 | |
825 825 o | 4 public b-A - f54f1bb90ff3
826 826 | |
827 827 | o 3 public a-D - b555f63b6063
828 828 | |
829 829 | o 2 public a-C - 54acac6f23ab
830 830 |/
831 831 o 1 public a-B - 548a3d25dbf0
832 832 |
833 833 o 0 public a-A - 054250a37db4
834 834
835 835
836 pushing a locally public and draft changesets remotly secret should make them appear on the remote side
836 pushing a locally public and draft changesets remotly secret should make them
837 appear on the remote side.
838
837 839
838 840 $ hg -R ../mu phase --secret --force 967b449fbc94
839 841 $ hg push -r 435b5d83910c ../mu
840 842 pushing to ../mu
841 843 searching for changes
844 abort: push creates new remote head 435b5d83910c!
845 (did you forget to merge? use push -f to force)
846 [255]
847 $ hg push -fr 435b5d83910c ../mu # because the push will create new visible head
848 pushing to ../mu
849 searching for changes
842 850 adding changesets
843 851 adding manifests
844 852 adding file changes
845 853 added 0 changesets with 0 changes to 2 files
846 854 $ hgph -R ../mu
847 855 o 10 draft A-secret - 435b5d83910c
848 856 |
849 857 o 9 public a-H - 967b449fbc94
850 858 |
851 859 | o 8 public a-F - b740e3e5c05d
852 860 | |
853 861 | o 7 public a-E - e9f537e46dea
854 862 | |
855 863 +---o 6 public n-B - 145e75495359
856 864 | |
857 865 o | 5 public n-A - d6bcb4f74035
858 866 | |
859 867 | o 4 public a-D - b555f63b6063
860 868 | |
861 869 | o 3 public a-C - 54acac6f23ab
862 870 | |
863 871 o | 2 public b-A - f54f1bb90ff3
864 872 |/
865 873 o 1 public a-B - 548a3d25dbf0
866 874 |
867 875 o 0 public a-A - 054250a37db4
868 876
869 877
870 878 pull new changeset with common draft locally
871 879
872 880 $ hg up -q 967b449fbc94 # create a new root for draft
873 881 $ mkcommit 'alpha-more'
874 882 created new head
875 883 $ hg push -fr . ../mu
876 884 pushing to ../mu
877 885 searching for changes
878 886 adding changesets
879 887 adding manifests
880 888 adding file changes
881 889 added 1 changesets with 1 changes to 1 files (+1 heads)
882 890 $ cd ../mu
883 891 $ hg phase --secret --force 1c5cfd894796
884 892 $ hg up -q 435b5d83910c
885 893 $ mkcommit 'mu-more'
886 894 $ cd ../alpha
887 895 $ hg pull ../mu
888 896 pulling from ../mu
889 897 searching for changes
890 898 adding changesets
891 899 adding manifests
892 900 adding file changes
893 901 added 1 changesets with 1 changes to 1 files
894 902 (run 'hg update' to get a working copy)
895 903 $ hgph
896 904 o 13 draft mu-more - 5237fb433fc8
897 905 |
898 906 | @ 12 draft alpha-more - 1c5cfd894796
899 907 | |
900 908 o | 11 draft A-secret - 435b5d83910c
901 909 |/
902 910 o 10 public a-H - 967b449fbc94
903 911 |
904 912 | o 9 draft a-G - 3e27b6f1eee1
905 913 | |
906 914 | o 8 public a-F - b740e3e5c05d
907 915 | |
908 916 | o 7 public a-E - e9f537e46dea
909 917 | |
910 918 +---o 6 public n-B - 145e75495359
911 919 | |
912 920 o | 5 public n-A - d6bcb4f74035
913 921 | |
914 922 o | 4 public b-A - f54f1bb90ff3
915 923 | |
916 924 | o 3 public a-D - b555f63b6063
917 925 | |
918 926 | o 2 public a-C - 54acac6f23ab
919 927 |/
920 928 o 1 public a-B - 548a3d25dbf0
921 929 |
922 930 o 0 public a-A - 054250a37db4
923 931
924 932
925 933 Test that test are properly ignored on remote event when existing locally
926 934
927 935 $ cd ..
928 936 $ hg clone -qU -r b555f63b6063 -r f54f1bb90ff3 beta gamma
929 937
930 938 # pathological case are
931 939 #
932 940 # * secret remotely
933 941 # * known locally
934 942 # * repo have uncommon changeset
935 943
936 944 $ hg -R beta phase --secret --force f54f1bb90ff3
937 945 $ hg -R gamma phase --draft --force f54f1bb90ff3
938 946
939 947 $ cd gamma
940 948 $ hg pull ../beta
941 949 pulling from ../beta
942 950 searching for changes
943 951 adding changesets
944 952 adding manifests
945 953 adding file changes
946 954 added 2 changesets with 2 changes to 2 files
947 955 (run 'hg update' to get a working copy)
948 956 $ hg phase f54f1bb90ff3
949 957 2: draft
950 958
951 959 same over the wire
952 960
953 961 $ cd ../beta
954 962 $ hg serve -p $HGPORT -d --pid-file=../beta.pid -E ../beta-error.log
955 963 $ cat ../beta.pid >> $DAEMON_PIDS
956 964 $ cd ../gamma
957 965
958 966 $ hg pull http://localhost:$HGPORT/
959 967 pulling from http://localhost:$HGPORT/
960 968 searching for changes
961 969 no changes found
962 970 $ hg phase f54f1bb90ff3
963 971 2: draft
964 972
965 973 check that secret local on both side are not synced to public
966 974
967 975 $ hg push -r b555f63b6063 http://localhost:$HGPORT/
968 976 pushing to http://localhost:$HGPORT/
969 977 searching for changes
970 978 no changes found
971 979 [1]
972 980 $ hg phase f54f1bb90ff3
973 981 2: draft
974 982
975 983 put the changeset in the draft state again
976 984 (first test after this one expect to be able to copy)
977 985
978 986 $ cd ..
979 987
980 988
981 989 Test Clone behavior
982 990
983 991 A. Clone without secret changeset
984 992
985 993 1. cloning non-publishing repository
986 994 (Phase should be preserved)
987 995
988 996 # make sure there is no secret so we can use a copy clone
989 997
990 998 $ hg -R mu phase --draft 'secret()'
991 999
992 1000 $ hg clone -U mu Tau
993 1001 $ hgph -R Tau
994 1002 o 12 draft mu-more - 5237fb433fc8
995 1003 |
996 1004 | o 11 draft alpha-more - 1c5cfd894796
997 1005 | |
998 1006 o | 10 draft A-secret - 435b5d83910c
999 1007 |/
1000 1008 o 9 public a-H - 967b449fbc94
1001 1009 |
1002 1010 | o 8 public a-F - b740e3e5c05d
1003 1011 | |
1004 1012 | o 7 public a-E - e9f537e46dea
1005 1013 | |
1006 1014 +---o 6 public n-B - 145e75495359
1007 1015 | |
1008 1016 o | 5 public n-A - d6bcb4f74035
1009 1017 | |
1010 1018 | o 4 public a-D - b555f63b6063
1011 1019 | |
1012 1020 | o 3 public a-C - 54acac6f23ab
1013 1021 | |
1014 1022 o | 2 public b-A - f54f1bb90ff3
1015 1023 |/
1016 1024 o 1 public a-B - 548a3d25dbf0
1017 1025 |
1018 1026 o 0 public a-A - 054250a37db4
1019 1027
1020 1028
1021 1029 2. cloning publishing repository
1022 1030
1023 1031 (everything should be public)
1024 1032
1025 1033 $ hg clone -U alpha Upsilon
1026 1034 $ hgph -R Upsilon
1027 1035 o 13 public mu-more - 5237fb433fc8
1028 1036 |
1029 1037 | o 12 public alpha-more - 1c5cfd894796
1030 1038 | |
1031 1039 o | 11 public A-secret - 435b5d83910c
1032 1040 |/
1033 1041 o 10 public a-H - 967b449fbc94
1034 1042 |
1035 1043 | o 9 public a-G - 3e27b6f1eee1
1036 1044 | |
1037 1045 | o 8 public a-F - b740e3e5c05d
1038 1046 | |
1039 1047 | o 7 public a-E - e9f537e46dea
1040 1048 | |
1041 1049 +---o 6 public n-B - 145e75495359
1042 1050 | |
1043 1051 o | 5 public n-A - d6bcb4f74035
1044 1052 | |
1045 1053 o | 4 public b-A - f54f1bb90ff3
1046 1054 | |
1047 1055 | o 3 public a-D - b555f63b6063
1048 1056 | |
1049 1057 | o 2 public a-C - 54acac6f23ab
1050 1058 |/
1051 1059 o 1 public a-B - 548a3d25dbf0
1052 1060 |
1053 1061 o 0 public a-A - 054250a37db4
1054 1062
1055 1063
@@ -1,435 +1,468
1 1 $ hglog() { hg log --template "{rev} {phaseidx} {desc}\n" $*; }
2 2 $ mkcommit() {
3 3 > echo "$1" > "$1"
4 4 > hg add "$1"
5 5 > message="$1"
6 6 > shift
7 7 > hg ci -m "$message" $*
8 8 > }
9 9
10 10 $ hg init initialrepo
11 11 $ cd initialrepo
12 12 $ mkcommit A
13 13
14 14 New commit are draft by default
15 15
16 16 $ hglog
17 17 0 1 A
18 18
19 19 Following commit are draft too
20 20
21 21 $ mkcommit B
22 22
23 23 $ hglog
24 24 1 1 B
25 25 0 1 A
26 26
27 27 Draft commit are properly created over public one:
28 28
29 29 $ hg phase --public .
30 30 $ hglog
31 31 1 0 B
32 32 0 0 A
33 33
34 34 $ mkcommit C
35 35 $ mkcommit D
36 36
37 37 $ hglog
38 38 3 1 D
39 39 2 1 C
40 40 1 0 B
41 41 0 0 A
42 42
43 43 Test creating changeset as secret
44 44
45 45 $ mkcommit E --config phases.new-commit='secret'
46 46 $ hglog
47 47 4 2 E
48 48 3 1 D
49 49 2 1 C
50 50 1 0 B
51 51 0 0 A
52 52
53 53 Test the secret property is inherited
54 54
55 55 $ mkcommit H
56 56 $ hglog
57 57 5 2 H
58 58 4 2 E
59 59 3 1 D
60 60 2 1 C
61 61 1 0 B
62 62 0 0 A
63 63
64 64 Even on merge
65 65
66 66 $ hg up -q 1
67 67 $ mkcommit "B'"
68 68 created new head
69 69 $ hglog
70 70 6 1 B'
71 71 5 2 H
72 72 4 2 E
73 73 3 1 D
74 74 2 1 C
75 75 1 0 B
76 76 0 0 A
77 77 $ hg merge 4 # E
78 78 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
79 79 (branch merge, don't forget to commit)
80 80 $ hg ci -m "merge B' and E"
81 81 $ hglog
82 82 7 2 merge B' and E
83 83 6 1 B'
84 84 5 2 H
85 85 4 2 E
86 86 3 1 D
87 87 2 1 C
88 88 1 0 B
89 89 0 0 A
90 90
91 91 Test secret changeset are not pushed
92 92
93 93 $ hg init ../push-dest
94 94 $ cat > ../push-dest/.hg/hgrc << EOF
95 95 > [phases]
96 96 > publish=False
97 97 > EOF
98 98 $ hg outgoing ../push-dest --template='{rev} {phase} {desc|firstline}\n'
99 99 comparing with ../push-dest
100 100 searching for changes
101 101 0 public A
102 102 1 public B
103 103 2 draft C
104 104 3 draft D
105 105 6 draft B'
106 106 $ hg outgoing -r default ../push-dest --template='{rev} {phase} {desc|firstline}\n'
107 107 comparing with ../push-dest
108 108 searching for changes
109 109 0 public A
110 110 1 public B
111 111 2 draft C
112 112 3 draft D
113 113 6 draft B'
114 114
115 115 $ hg push ../push-dest -f # force because we push multiple heads
116 116 pushing to ../push-dest
117 117 searching for changes
118 118 adding changesets
119 119 adding manifests
120 120 adding file changes
121 121 added 5 changesets with 5 changes to 5 files (+1 heads)
122 122 $ hglog
123 123 7 2 merge B' and E
124 124 6 1 B'
125 125 5 2 H
126 126 4 2 E
127 127 3 1 D
128 128 2 1 C
129 129 1 0 B
130 130 0 0 A
131 131 $ cd ../push-dest
132 132 $ hglog
133 133 4 1 B'
134 134 3 1 D
135 135 2 1 C
136 136 1 0 B
137 137 0 0 A
138
139 (Issue3303)
140 Check that remote secret changeset are ignore when checking creation of remote heads
141
142 We add a secret head into the push destination. This secreat head shadow a
143 visible shared between the initial repo and the push destination.
144
145 $ hg up -q 4 # B'
146 $ mkcommit Z --config phases.new-commit=secret
147 $ hg phase .
148 5: secret
149
150 # We now try to push a new public changeset that descend from the common public
151 # head shadowed by the remote secret head.
152
153 $ cd ../initialrepo
154 $ hg up -q 6 #B'
155 $ mkcommit I
156 created new head
157 $ hg push ../push-dest
158 pushing to ../push-dest
159 searching for changes
160 adding changesets
161 adding manifests
162 adding file changes
163 added 1 changesets with 1 changes to 1 files (+1 heads)
164
165 :note: The "(+1 heads)" is wrong as we do not had any visible head
166
167
168 Restore condition prior extra insertion.
169 $ hg -q --config extensions.mq= strip .
170 $ hg up -q 7
138 171 $ cd ..
139 172
140 173 Test secret changeset are not pull
141 174
142 175 $ hg init pull-dest
143 176 $ cd pull-dest
144 177 $ hg pull ../initialrepo
145 178 pulling from ../initialrepo
146 179 requesting all changes
147 180 adding changesets
148 181 adding manifests
149 182 adding file changes
150 183 added 5 changesets with 5 changes to 5 files (+1 heads)
151 184 (run 'hg heads' to see heads, 'hg merge' to merge)
152 185 $ hglog
153 186 4 0 B'
154 187 3 0 D
155 188 2 0 C
156 189 1 0 B
157 190 0 0 A
158 191 $ cd ..
159 192
160 193 But secret can still be bundled explicitly
161 194
162 195 $ cd initialrepo
163 196 $ hg bundle --base '4^' -r 'children(4)' ../secret-bundle.hg
164 197 4 changesets found
165 198 $ cd ..
166 199
167 200 Test secret changeset are not cloned
168 201 (during local clone)
169 202
170 203 $ hg clone -qU initialrepo clone-dest
171 204 $ hglog -R clone-dest
172 205 4 0 B'
173 206 3 0 D
174 207 2 0 C
175 208 1 0 B
176 209 0 0 A
177 210
178 211 Test revset
179 212
180 213 $ cd initialrepo
181 214 $ hglog -r 'public()'
182 215 0 0 A
183 216 1 0 B
184 217 $ hglog -r 'draft()'
185 218 2 1 C
186 219 3 1 D
187 220 6 1 B'
188 221 $ hglog -r 'secret()'
189 222 4 2 E
190 223 5 2 H
191 224 7 2 merge B' and E
192 225
193 226 test that phase are displayed in log at debug level
194 227
195 228 $ hg log --debug
196 229 changeset: 7:17a481b3bccb796c0521ae97903d81c52bfee4af
197 230 tag: tip
198 231 phase: secret
199 232 parent: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
200 233 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
201 234 manifest: 7:5e724ffacba267b2ab726c91fc8b650710deaaa8
202 235 user: test
203 236 date: Thu Jan 01 00:00:00 1970 +0000
204 237 files+: C D E
205 238 extra: branch=default
206 239 description:
207 240 merge B' and E
208 241
209 242
210 243 changeset: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
211 244 phase: draft
212 245 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
213 246 parent: -1:0000000000000000000000000000000000000000
214 247 manifest: 6:ab8bfef2392903058bf4ebb9e7746e8d7026b27a
215 248 user: test
216 249 date: Thu Jan 01 00:00:00 1970 +0000
217 250 files+: B'
218 251 extra: branch=default
219 252 description:
220 253 B'
221 254
222 255
223 256 changeset: 5:a030c6be5127abc010fcbff1851536552e6951a8
224 257 phase: secret
225 258 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
226 259 parent: -1:0000000000000000000000000000000000000000
227 260 manifest: 5:5c710aa854874fe3d5fa7192e77bdb314cc08b5a
228 261 user: test
229 262 date: Thu Jan 01 00:00:00 1970 +0000
230 263 files+: H
231 264 extra: branch=default
232 265 description:
233 266 H
234 267
235 268
236 269 changeset: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
237 270 phase: secret
238 271 parent: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
239 272 parent: -1:0000000000000000000000000000000000000000
240 273 manifest: 4:7173fd1c27119750b959e3a0f47ed78abe75d6dc
241 274 user: test
242 275 date: Thu Jan 01 00:00:00 1970 +0000
243 276 files+: E
244 277 extra: branch=default
245 278 description:
246 279 E
247 280
248 281
249 282 changeset: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
250 283 phase: draft
251 284 parent: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
252 285 parent: -1:0000000000000000000000000000000000000000
253 286 manifest: 3:6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c
254 287 user: test
255 288 date: Thu Jan 01 00:00:00 1970 +0000
256 289 files+: D
257 290 extra: branch=default
258 291 description:
259 292 D
260 293
261 294
262 295 changeset: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
263 296 phase: draft
264 297 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
265 298 parent: -1:0000000000000000000000000000000000000000
266 299 manifest: 2:66a5a01817fdf5239c273802b5b7618d051c89e4
267 300 user: test
268 301 date: Thu Jan 01 00:00:00 1970 +0000
269 302 files+: C
270 303 extra: branch=default
271 304 description:
272 305 C
273 306
274 307
275 308 changeset: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
276 309 parent: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
277 310 parent: -1:0000000000000000000000000000000000000000
278 311 manifest: 1:cb5cbbc1bfbf24cc34b9e8c16914e9caa2d2a7fd
279 312 user: test
280 313 date: Thu Jan 01 00:00:00 1970 +0000
281 314 files+: B
282 315 extra: branch=default
283 316 description:
284 317 B
285 318
286 319
287 320 changeset: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
288 321 parent: -1:0000000000000000000000000000000000000000
289 322 parent: -1:0000000000000000000000000000000000000000
290 323 manifest: 0:007d8c9d88841325f5c6b06371b35b4e8a2b1a83
291 324 user: test
292 325 date: Thu Jan 01 00:00:00 1970 +0000
293 326 files+: A
294 327 extra: branch=default
295 328 description:
296 329 A
297 330
298 331
299 332
300 333 Test phase command
301 334 ===================
302 335
303 336 initial picture
304 337
305 338 $ cat >> $HGRCPATH << EOF
306 339 > [extensions]
307 340 > hgext.graphlog=
308 341 > EOF
309 342 $ hg log -G --template "{rev} {phase} {desc}\n"
310 343 @ 7 secret merge B' and E
311 344 |\
312 345 | o 6 draft B'
313 346 | |
314 347 +---o 5 secret H
315 348 | |
316 349 o | 4 secret E
317 350 | |
318 351 o | 3 draft D
319 352 | |
320 353 o | 2 draft C
321 354 |/
322 355 o 1 public B
323 356 |
324 357 o 0 public A
325 358
326 359
327 360 display changesets phase
328 361
329 362 (mixing -r and plain rev specification)
330 363
331 364 $ hg phase 1::4 -r 7
332 365 1: public
333 366 2: draft
334 367 3: draft
335 368 4: secret
336 369 7: secret
337 370
338 371
339 372 move changeset forward
340 373
341 374 (with -r option)
342 375
343 376 $ hg phase --public -r 2
344 377 $ hg log -G --template "{rev} {phase} {desc}\n"
345 378 @ 7 secret merge B' and E
346 379 |\
347 380 | o 6 draft B'
348 381 | |
349 382 +---o 5 secret H
350 383 | |
351 384 o | 4 secret E
352 385 | |
353 386 o | 3 draft D
354 387 | |
355 388 o | 2 public C
356 389 |/
357 390 o 1 public B
358 391 |
359 392 o 0 public A
360 393
361 394
362 395 move changeset backward
363 396
364 397 (without -r option)
365 398
366 399 $ hg phase --draft --force 2
367 400 $ hg log -G --template "{rev} {phase} {desc}\n"
368 401 @ 7 secret merge B' and E
369 402 |\
370 403 | o 6 draft B'
371 404 | |
372 405 +---o 5 secret H
373 406 | |
374 407 o | 4 secret E
375 408 | |
376 409 o | 3 draft D
377 410 | |
378 411 o | 2 draft C
379 412 |/
380 413 o 1 public B
381 414 |
382 415 o 0 public A
383 416
384 417
385 418 move changeset forward and backward
386 419
387 420 $ hg phase --draft --force 1::4
388 421 $ hg log -G --template "{rev} {phase} {desc}\n"
389 422 @ 7 secret merge B' and E
390 423 |\
391 424 | o 6 draft B'
392 425 | |
393 426 +---o 5 secret H
394 427 | |
395 428 o | 4 draft E
396 429 | |
397 430 o | 3 draft D
398 431 | |
399 432 o | 2 draft C
400 433 |/
401 434 o 1 draft B
402 435 |
403 436 o 0 public A
404 437
405 438 test partial failure
406 439
407 440 $ hg phase --public 7
408 441 $ hg phase --draft '5 or 7'
409 442 cannot move 1 changesets to a more permissive phase, use --force
410 443 phase changed for 1 changesets
411 444 [1]
412 445 $ hg log -G --template "{rev} {phase} {desc}\n"
413 446 @ 7 public merge B' and E
414 447 |\
415 448 | o 6 public B'
416 449 | |
417 450 +---o 5 draft H
418 451 | |
419 452 o | 4 public E
420 453 | |
421 454 o | 3 public D
422 455 | |
423 456 o | 2 public C
424 457 |/
425 458 o 1 public B
426 459 |
427 460 o 0 public A
428 461
429 462
430 463 test complete failure
431 464
432 465 $ hg phase --draft 7
433 466 cannot move 1 changesets to a more permissive phase, use --force
434 467 no phases changed
435 468 [1]
General Comments 0
You need to be logged in to leave comments. Login now