##// END OF EJS Templates
checkheads: upgrade the obsolescence postprocessing logic (issue4354)...
Pierre-Yves David -
r32009:c6cb21dd default
parent child Browse files
Show More
@@ -0,0 +1,72 b''
1 ====================================
2 Testing head checking code: Case B-1
3 ====================================
4
5 Mercurial checks for the introduction of new heads on push. Evolution comes
6 into play to detect if existing branches on the server are being replaced by
7 some of the new one we push.
8
9 This case is part of a series of tests checking this behavior.
10
11 Category B: simple case involving pruned changesets
12 TestCase 1: single pruned changeset
13
14 .. old-state:
15 ..
16 .. * 1 changeset branch
17 ..
18 .. new-state:
19 ..
20 .. * old branch is pruned
21 .. * 1 new unrelated branch
22 ..
23 .. expected-result:
24 ..
25 .. * push allowed
26 ..
27 .. graph-summary:
28 ..
29 .. β—” B
30 .. |
31 .. A βŠ— |
32 .. |/
33 .. ●
34
35 $ . $TESTDIR/testlib/push-checkheads-util.sh
36
37 Test setup
38 ----------
39
40 $ mkdir B1
41 $ cd B1
42 $ setuprepos
43 creating basic server and client repo
44 updating to branch default
45 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 $ cd client
47 $ hg up 0
48 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
49 $ mkcommit B0
50 created new head
51 $ hg debugobsolete --record-parents `getid "desc(A0)"`
52 $ hg log -G --hidden
53 @ 74ff5441d343 (draft): B0
54 |
55 | x 8aaa48160adc (draft): A0
56 |/
57 o 1e4be0697311 (public): root
58
59
60 Actual testing
61 --------------
62
63 $ hg push
64 pushing to $TESTTMP/B1/server (glob)
65 searching for changes
66 adding changesets
67 adding manifests
68 adding file changes
69 added 1 changesets with 1 changes to 1 files (+1 heads)
70 1 new obsolescence markers
71
72 $ cd ../..
@@ -1,450 +1,519 b''
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 __future__ import absolute_import
9 9
10 import functools
11
10 12 from .i18n import _
11 13 from .node import (
14 hex,
12 15 nullid,
13 16 short,
14 17 )
15 18
16 19 from . import (
17 20 bookmarks,
18 21 branchmap,
19 22 error,
20 obsolete,
21 23 phases,
22 24 setdiscovery,
23 25 treediscovery,
24 26 util,
25 27 )
26 28
27 29 def findcommonincoming(repo, remote, heads=None, force=False):
28 30 """Return a tuple (common, anyincoming, heads) used to identify the common
29 31 subset of nodes between repo and remote.
30 32
31 33 "common" is a list of (at least) the heads of the common subset.
32 34 "anyincoming" is testable as a boolean indicating if any nodes are missing
33 35 locally. If remote does not support getbundle, this actually is a list of
34 36 roots of the nodes that would be incoming, to be supplied to
35 37 changegroupsubset. No code except for pull should be relying on this fact
36 38 any longer.
37 39 "heads" is either the supplied heads, or else the remote's heads.
38 40
39 41 If you pass heads and they are all known locally, the response lists just
40 42 these heads in "common" and in "heads".
41 43
42 44 Please use findcommonoutgoing to compute the set of outgoing nodes to give
43 45 extensions a good hook into outgoing.
44 46 """
45 47
46 48 if not remote.capable('getbundle'):
47 49 return treediscovery.findcommonincoming(repo, remote, heads, force)
48 50
49 51 if heads:
50 52 allknown = True
51 53 knownnode = repo.changelog.hasnode # no nodemap until it is filtered
52 54 for h in heads:
53 55 if not knownnode(h):
54 56 allknown = False
55 57 break
56 58 if allknown:
57 59 return (heads, False, heads)
58 60
59 61 res = setdiscovery.findcommonheads(repo.ui, repo, remote,
60 62 abortwhenunrelated=not force)
61 63 common, anyinc, srvheads = res
62 64 return (list(common), anyinc, heads or list(srvheads))
63 65
64 66 class outgoing(object):
65 67 '''Represents the set of nodes present in a local repo but not in a
66 68 (possibly) remote one.
67 69
68 70 Members:
69 71
70 72 missing is a list of all nodes present in local but not in remote.
71 73 common is a list of all nodes shared between the two repos.
72 74 excluded is the list of missing changeset that shouldn't be sent remotely.
73 75 missingheads is the list of heads of missing.
74 76 commonheads is the list of heads of common.
75 77
76 78 The sets are computed on demand from the heads, unless provided upfront
77 79 by discovery.'''
78 80
79 81 def __init__(self, repo, commonheads=None, missingheads=None,
80 82 missingroots=None):
81 83 # at least one of them must not be set
82 84 assert None in (commonheads, missingroots)
83 85 cl = repo.changelog
84 86 if missingheads is None:
85 87 missingheads = cl.heads()
86 88 if missingroots:
87 89 discbases = []
88 90 for n in missingroots:
89 91 discbases.extend([p for p in cl.parents(n) if p != nullid])
90 92 # TODO remove call to nodesbetween.
91 93 # TODO populate attributes on outgoing instance instead of setting
92 94 # discbases.
93 95 csets, roots, heads = cl.nodesbetween(missingroots, missingheads)
94 96 included = set(csets)
95 97 missingheads = heads
96 98 commonheads = [n for n in discbases if n not in included]
97 99 elif not commonheads:
98 100 commonheads = [nullid]
99 101 self.commonheads = commonheads
100 102 self.missingheads = missingheads
101 103 self._revlog = cl
102 104 self._common = None
103 105 self._missing = None
104 106 self.excluded = []
105 107
106 108 def _computecommonmissing(self):
107 109 sets = self._revlog.findcommonmissing(self.commonheads,
108 110 self.missingheads)
109 111 self._common, self._missing = sets
110 112
111 113 @util.propertycache
112 114 def common(self):
113 115 if self._common is None:
114 116 self._computecommonmissing()
115 117 return self._common
116 118
117 119 @util.propertycache
118 120 def missing(self):
119 121 if self._missing is None:
120 122 self._computecommonmissing()
121 123 return self._missing
122 124
123 125 def findcommonoutgoing(repo, other, onlyheads=None, force=False,
124 126 commoninc=None, portable=False):
125 127 '''Return an outgoing instance to identify the nodes present in repo but
126 128 not in other.
127 129
128 130 If onlyheads is given, only nodes ancestral to nodes in onlyheads
129 131 (inclusive) are included. If you already know the local repo's heads,
130 132 passing them in onlyheads is faster than letting them be recomputed here.
131 133
132 134 If commoninc is given, it must be the result of a prior call to
133 135 findcommonincoming(repo, other, force) to avoid recomputing it here.
134 136
135 137 If portable is given, compute more conservative common and missingheads,
136 138 to make bundles created from the instance more portable.'''
137 139 # declare an empty outgoing object to be filled later
138 140 og = outgoing(repo, None, None)
139 141
140 142 # get common set if not provided
141 143 if commoninc is None:
142 144 commoninc = findcommonincoming(repo, other, force=force)
143 145 og.commonheads, _any, _hds = commoninc
144 146
145 147 # compute outgoing
146 148 mayexclude = (repo._phasecache.phaseroots[phases.secret] or repo.obsstore)
147 149 if not mayexclude:
148 150 og.missingheads = onlyheads or repo.heads()
149 151 elif onlyheads is None:
150 152 # use visible heads as it should be cached
151 153 og.missingheads = repo.filtered("served").heads()
152 154 og.excluded = [ctx.node() for ctx in repo.set('secret() or extinct()')]
153 155 else:
154 156 # compute common, missing and exclude secret stuff
155 157 sets = repo.changelog.findcommonmissing(og.commonheads, onlyheads)
156 158 og._common, allmissing = sets
157 159 og._missing = missing = []
158 160 og.excluded = excluded = []
159 161 for node in allmissing:
160 162 ctx = repo[node]
161 163 if ctx.phase() >= phases.secret or ctx.extinct():
162 164 excluded.append(node)
163 165 else:
164 166 missing.append(node)
165 167 if len(missing) == len(allmissing):
166 168 missingheads = onlyheads
167 169 else: # update missing heads
168 170 missingheads = phases.newheads(repo, onlyheads, excluded)
169 171 og.missingheads = missingheads
170 172 if portable:
171 173 # recompute common and missingheads as if -r<rev> had been given for
172 174 # each head of missing, and --base <rev> for each head of the proper
173 175 # ancestors of missing
174 176 og._computecommonmissing()
175 177 cl = repo.changelog
176 178 missingrevs = set(cl.rev(n) for n in og._missing)
177 179 og._common = set(cl.ancestors(missingrevs)) - missingrevs
178 180 commonheads = set(og.commonheads)
179 181 og.missingheads = [h for h in og.missingheads if h not in commonheads]
180 182
181 183 return og
182 184
183 185 def _headssummary(repo, remote, outgoing):
184 186 """compute a summary of branch and heads status before and after push
185 187
186 188 return {'branch': ([remoteheads], [newheads], [unsyncedheads])} mapping
187 189
188 190 - branch: the branch name
189 191 - remoteheads: the list of remote heads known locally
190 192 None if the branch is new
191 193 - newheads: the new remote heads (known locally) with outgoing pushed
192 194 - unsyncedheads: the list of remote heads unknown locally.
193 195 """
194 196 cl = repo.changelog
195 197 headssum = {}
196 198 # A. Create set of branches involved in the push.
197 199 branches = set(repo[n].branch() for n in outgoing.missing)
198 200 remotemap = remote.branchmap()
199 201 newbranches = branches - set(remotemap)
200 202 branches.difference_update(newbranches)
201 203
202 204 # A. register remote heads
203 205 remotebranches = set()
204 206 for branch, heads in remote.branchmap().iteritems():
205 207 remotebranches.add(branch)
206 208 known = []
207 209 unsynced = []
208 210 knownnode = cl.hasnode # do not use nodemap until it is filtered
209 211 for h in heads:
210 212 if knownnode(h):
211 213 known.append(h)
212 214 else:
213 215 unsynced.append(h)
214 216 headssum[branch] = (known, list(known), unsynced)
215 217 # B. add new branch data
216 218 missingctx = list(repo[n] for n in outgoing.missing)
217 219 touchedbranches = set()
218 220 for ctx in missingctx:
219 221 branch = ctx.branch()
220 222 touchedbranches.add(branch)
221 223 if branch not in headssum:
222 224 headssum[branch] = (None, [], [])
223 225
224 226 # C drop data about untouched branches:
225 227 for branch in remotebranches - touchedbranches:
226 228 del headssum[branch]
227 229
228 230 # D. Update newmap with outgoing changes.
229 231 # This will possibly add new heads and remove existing ones.
230 232 newmap = branchmap.branchcache((branch, heads[1])
231 233 for branch, heads in headssum.iteritems()
232 234 if heads[0] is not None)
233 235 newmap.update(repo, (ctx.rev() for ctx in missingctx))
234 236 for branch, newheads in newmap.iteritems():
235 237 headssum[branch][1][:] = newheads
236 238 return headssum
237 239
238 240 def _oldheadssummary(repo, remoteheads, outgoing, inc=False):
239 241 """Compute branchmapsummary for repo without branchmap support"""
240 242
241 243 # 1-4b. old servers: Check for new topological heads.
242 244 # Construct {old,new}map with branch = None (topological branch).
243 245 # (code based on update)
244 246 knownnode = repo.changelog.hasnode # no nodemap until it is filtered
245 247 oldheads = set(h for h in remoteheads if knownnode(h))
246 248 # all nodes in outgoing.missing are children of either:
247 249 # - an element of oldheads
248 250 # - another element of outgoing.missing
249 251 # - nullrev
250 252 # This explains why the new head are very simple to compute.
251 253 r = repo.set('heads(%ln + %ln)', oldheads, outgoing.missing)
252 254 newheads = list(c.node() for c in r)
253 255 # set some unsynced head to issue the "unsynced changes" warning
254 256 if inc:
255 257 unsynced = set([None])
256 258 else:
257 259 unsynced = set()
258 260 return {None: (oldheads, newheads, unsynced)}
259 261
260 262 def _nowarnheads(pushop):
261 263 # Compute newly pushed bookmarks. We don't warn about bookmarked heads.
262 264 repo = pushop.repo.unfiltered()
263 265 remote = pushop.remote
264 266 localbookmarks = repo._bookmarks
265 267 remotebookmarks = remote.listkeys('bookmarks')
266 268 bookmarkedheads = set()
267 269
268 270 # internal config: bookmarks.pushing
269 271 newbookmarks = [localbookmarks.expandname(b)
270 272 for b in pushop.ui.configlist('bookmarks', 'pushing')]
271 273
272 274 for bm in localbookmarks:
273 275 rnode = remotebookmarks.get(bm)
274 276 if rnode and rnode in repo:
275 277 lctx, rctx = repo[bm], repo[rnode]
276 278 if bookmarks.validdest(repo, rctx, lctx):
277 279 bookmarkedheads.add(lctx.node())
278 280 else:
279 281 if bm in newbookmarks and bm not in remotebookmarks:
280 282 bookmarkedheads.add(repo[bm].node())
281 283
282 284 return bookmarkedheads
283 285
284 286 def checkheads(pushop):
285 287 """Check that a push won't add any outgoing head
286 288
287 289 raise Abort error and display ui message as needed.
288 290 """
289 291
290 292 repo = pushop.repo.unfiltered()
291 293 remote = pushop.remote
292 294 outgoing = pushop.outgoing
293 295 remoteheads = pushop.remoteheads
294 296 newbranch = pushop.newbranch
295 297 inc = bool(pushop.incoming)
296 298
297 299 # Check for each named branch if we're creating new remote heads.
298 300 # To be a remote head after push, node must be either:
299 301 # - unknown locally
300 302 # - a local outgoing head descended from update
301 303 # - a remote head that's known locally and not
302 304 # ancestral to an outgoing head
303 305 if remoteheads == [nullid]:
304 306 # remote is empty, nothing to check.
305 307 return
306 308
307 309 if remote.capable('branchmap'):
308 310 headssum = _headssummary(repo, remote, outgoing)
309 311 else:
310 312 headssum = _oldheadssummary(repo, remoteheads, outgoing, inc)
311 313 newbranches = [branch for branch, heads in headssum.iteritems()
312 314 if heads[0] is None]
313 315 # 1. Check for new branches on the remote.
314 316 if newbranches and not newbranch: # new branch requires --new-branch
315 317 branchnames = ', '.join(sorted(newbranches))
316 318 raise error.Abort(_("push creates new remote branches: %s!")
317 319 % branchnames,
318 320 hint=_("use 'hg push --new-branch' to create"
319 321 " new remote branches"))
320 322
321 323 # 2. Find heads that we need not warn about
322 324 nowarnheads = _nowarnheads(pushop)
323 325
324 326 # 3. Check for new heads.
325 327 # If there are more heads after the push than before, a suitable
326 328 # error message, depending on unsynced status, is displayed.
327 329 errormsg = None
328 330 # If there is no obsstore, allfuturecommon won't be used, so no
329 331 # need to compute it.
330 332 if repo.obsstore:
331 333 allmissing = set(outgoing.missing)
332 334 cctx = repo.set('%ld', outgoing.common)
333 335 allfuturecommon = set(c.node() for c in cctx)
334 336 allfuturecommon.update(allmissing)
335 337 for branch, heads in sorted(headssum.iteritems()):
336 338 remoteheads, newheads, unsyncedheads = heads
337 339 candidate_newhs = set(newheads)
338 340 # add unsynced data
339 341 if remoteheads is None:
340 342 oldhs = set()
341 343 else:
342 344 oldhs = set(remoteheads)
343 345 oldhs.update(unsyncedheads)
344 346 candidate_newhs.update(unsyncedheads)
345 347 dhs = None # delta heads, the new heads on branch
346 348 if not repo.obsstore:
347 349 discardedheads = set()
348 350 newhs = candidate_newhs
349 351 else:
350 352 newhs, discardedheads = _postprocessobsolete(pushop,
351 353 allfuturecommon,
352 354 candidate_newhs)
353 355 unsynced = sorted(h for h in unsyncedheads if h not in discardedheads)
354 356 if unsynced:
355 357 if None in unsynced:
356 358 # old remote, no heads data
357 359 heads = None
358 360 elif len(unsynced) <= 4 or repo.ui.verbose:
359 361 heads = ' '.join(short(h) for h in unsynced)
360 362 else:
361 363 heads = (' '.join(short(h) for h in unsynced[:4]) +
362 364 ' ' + _("and %s others") % (len(unsynced) - 4))
363 365 if heads is None:
364 366 repo.ui.status(_("remote has heads that are "
365 367 "not known locally\n"))
366 368 elif branch is None:
367 369 repo.ui.status(_("remote has heads that are "
368 370 "not known locally: %s\n") % heads)
369 371 else:
370 372 repo.ui.status(_("remote has heads on branch '%s' that are "
371 373 "not known locally: %s\n") % (branch, heads))
372 374 if remoteheads is None:
373 375 if len(newhs) > 1:
374 376 dhs = list(newhs)
375 377 if errormsg is None:
376 378 errormsg = (_("push creates new branch '%s' "
377 379 "with multiple heads") % (branch))
378 380 hint = _("merge or"
379 381 " see 'hg help push' for details about"
380 382 " pushing new heads")
381 383 elif len(newhs) > len(oldhs):
382 384 # remove bookmarked or existing remote heads from the new heads list
383 385 dhs = sorted(newhs - nowarnheads - oldhs)
384 386 if dhs:
385 387 if errormsg is None:
386 388 if branch not in ('default', None):
387 389 errormsg = _("push creates new remote head %s "
388 390 "on branch '%s'!") % (short(dhs[0]), branch)
389 391 elif repo[dhs[0]].bookmarks():
390 392 errormsg = _("push creates new remote head %s "
391 393 "with bookmark '%s'!") % (
392 394 short(dhs[0]), repo[dhs[0]].bookmarks()[0])
393 395 else:
394 396 errormsg = _("push creates new remote head %s!"
395 397 ) % short(dhs[0])
396 398 if unsyncedheads:
397 399 hint = _("pull and merge or"
398 400 " see 'hg help push' for details about"
399 401 " pushing new heads")
400 402 else:
401 403 hint = _("merge or"
402 404 " see 'hg help push' for details about"
403 405 " pushing new heads")
404 406 if branch is None:
405 407 repo.ui.note(_("new remote heads:\n"))
406 408 else:
407 409 repo.ui.note(_("new remote heads on branch '%s':\n") % branch)
408 410 for h in dhs:
409 411 repo.ui.note((" %s\n") % short(h))
410 412 if errormsg:
411 413 raise error.Abort(errormsg, hint=hint)
412 414
413 415 def _postprocessobsolete(pushop, futurecommon, candidate_newhs):
414 416 """post process the list of new heads with obsolescence information
415 417
416 Exists as a subfunction to contain the complexity and allow extensions to
418 Exists as a sub-function to contain the complexity and allow extensions to
417 419 experiment with smarter logic.
420
418 421 Returns (newheads, discarded_heads) tuple
419 422 """
420 # remove future heads which are actually obsoleted by another
421 # pushed element:
423 # known issue
422 424 #
423 # XXX as above, There are several cases this code does not handle
424 # XXX properly
425 #
426 # (1) if <nh> is public, it won't be affected by obsolete marker
427 # and a new is created
425 # * We "silently" skip processing on all changeset unknown locally
428 426 #
429 # (2) if the new heads have ancestors which are not obsolete and
430 # not ancestors of any other heads we will have a new head too.
431 #
432 # These two cases will be easy to handle for known changeset but
433 # much more tricky for unsynced changes.
434 #
435 # In addition, this code is confused by prune as it only looks for
436 # successors of the heads (none if pruned) leading to issue4354
427 # * if <nh> is public on the remote, it won't be affected by obsolete
428 # marker and a new is created
429
430 # define various utilities and containers
437 431 repo = pushop.repo
438 newhs = set()
439 discarded = set()
440 for nh in candidate_newhs:
441 if nh in repo and repo[nh].phase() <= phases.public:
432 unfi = repo.unfiltered()
433 tonode = unfi.changelog.node
434 public = phases.public
435 getphase = unfi._phasecache.phase
436 ispublic = (lambda r: getphase(unfi, r) == public)
437 hasoutmarker = functools.partial(pushingmarkerfor, unfi.obsstore,
438 futurecommon)
439 successorsmarkers = unfi.obsstore.successors
440 newhs = set() # final set of new heads
441 discarded = set() # new head of fully replaced branch
442
443 localcandidate = set() # candidate heads known locally
444 unknownheads = set() # candidate heads unknown locally
445 for h in candidate_newhs:
446 if h in unfi:
447 localcandidate.add(h)
448 else:
449 if successorsmarkers.get(h) is not None:
450 msg = ('checkheads: remote head unknown locally has'
451 ' local marker: %s\n')
452 repo.ui.debug(msg % hex(h))
453 unknownheads.add(h)
454
455 # fast path the simple case
456 if len(localcandidate) == 1:
457 return unknownheads | set(candidate_newhs), set()
458
459 # actually process branch replacement
460 while localcandidate:
461 nh = localcandidate.pop()
462 # run this check early to skip the evaluation of the whole branch
463 if (nh in futurecommon
464 or unfi[nh].phase() <= public):
465 newhs.add(nh)
466 continue
467
468 # Get all revs/nodes on the branch exclusive to this head
469 # (already filtered heads are "ignored"))
470 branchrevs = unfi.revs('only(%n, (%ln+%ln))',
471 nh, localcandidate, newhs)
472 branchnodes = [tonode(r) for r in branchrevs]
473
474 # The branch won't be hidden on the remote if
475 # * any part of it is public,
476 # * any part of it is considered part of the result by previous logic,
477 # * if we have no markers to push to obsolete it.
478 if (any(ispublic(r) for r in branchrevs)
479 or any(n in futurecommon for n in branchnodes)
480 or any(not hasoutmarker(n) for n in branchnodes)):
442 481 newhs.add(nh)
443 482 else:
444 for suc in obsolete.allsuccessors(repo.obsstore, [nh]):
445 if suc != nh and suc in futurecommon:
446 discarded.add(nh)
447 break
448 else:
449 newhs.add(nh)
483 # note: there is a corner case if there is a merge in the branch.
484 # we might end up with -more- heads. However, these heads are not
485 # "added" by the push, but more by the "removal" on the remote so I
486 # think is a okay to ignore them,
487 discarded.add(nh)
488 newhs |= unknownheads
450 489 return newhs, discarded
490
491 def pushingmarkerfor(obsstore, pushset, node):
492 """true if some markers are to be pushed for node
493
494 We cannot just look in to the pushed obsmarkers from the pushop because
495 discovery might have filtered relevant markers. In addition listing all
496 markers relevant to all changesets in the pushed set would be too expensive
497 (O(len(repo)))
498
499 (note: There are cache opportunity in this function. but it would requires
500 a two dimensional stack.)
501 """
502 successorsmarkers = obsstore.successors
503 stack = [node]
504 seen = set(stack)
505 while stack:
506 current = stack.pop()
507 if current in pushset:
508 return True
509 markers = successorsmarkers.get(current, ())
510 # markers fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
511 for m in markers:
512 nexts = m[1] # successors
513 if not nexts: # this is a prune marker
514 nexts = m[5] # parents
515 for n in nexts:
516 if n not in seen:
517 seen.add(n)
518 stack.append(n)
519 return False
@@ -1,265 +1,283 b''
1 1 Check that obsolete properly strip heads
2 2 $ cat >> $HGRCPATH << EOF
3 3 > [phases]
4 4 > # public changeset are not obsolete
5 5 > publish=false
6 6 > [ui]
7 7 > logtemplate='{node|short} ({phase}) {desc|firstline}\n'
8 8 > [experimental]
9 9 > evolution=createmarkers
10 10 > EOF
11 11 $ mkcommit() {
12 12 > echo "$1" > "$1"
13 13 > hg add "$1"
14 14 > hg ci -m "add $1"
15 15 > }
16 16 $ getid() {
17 17 > hg id --debug -ir "desc('$1')"
18 18 > }
19 19
20 20
21 21 $ hg init remote
22 22 $ cd remote
23 23 $ mkcommit base
24 24 $ hg phase --public .
25 25 $ cd ..
26 26 $ cp -R remote base
27 27 $ hg clone remote local
28 28 updating to branch default
29 29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30 $ cd local
31 31
32 32 New head replaces old head
33 33 ==========================
34 34
35 35 setup
36 36 (we add the 1 flags to prevent bumped error during the test)
37 37
38 38 $ mkcommit old
39 39 $ hg push
40 40 pushing to $TESTTMP/remote (glob)
41 41 searching for changes
42 42 adding changesets
43 43 adding manifests
44 44 adding file changes
45 45 added 1 changesets with 1 changes to 1 files
46 46 $ hg up -q '.^'
47 47 $ mkcommit new
48 48 created new head
49 49 $ hg debugobsolete --flags 1 `getid old` `getid new`
50 50 $ hg log -G --hidden
51 51 @ 71e3228bffe1 (draft) add new
52 52 |
53 53 | x c70b08862e08 (draft) add old
54 54 |/
55 55 o b4952fcf48cf (public) add base
56 56
57 57 $ cp -R ../remote ../backup1
58 58
59 59 old exists remotely as draft. It is obsoleted by new that we now push.
60 60 Push should not warn about creating new head
61 61
62 62 $ hg push
63 63 pushing to $TESTTMP/remote (glob)
64 64 searching for changes
65 65 adding changesets
66 66 adding manifests
67 67 adding file changes
68 68 added 1 changesets with 1 changes to 1 files (+1 heads)
69 69
70 70 old head is now public (public local version)
71 71 =============================================
72 72
73 73 setup
74 74
75 75 $ rm -fr ../remote
76 76 $ cp -R ../backup1 ../remote
77 77 $ hg -R ../remote phase --public c70b08862e08
78 78 $ hg pull -v
79 79 pulling from $TESTTMP/remote (glob)
80 80 searching for changes
81 81 no changes found
82 82 $ hg log -G --hidden
83 83 @ 71e3228bffe1 (draft) add new
84 84 |
85 85 | o c70b08862e08 (public) add old
86 86 |/
87 87 o b4952fcf48cf (public) add base
88 88
89 89
90 90 Abort: old will still be an head because it's public.
91 91
92 92 $ hg push
93 93 pushing to $TESTTMP/remote (glob)
94 94 searching for changes
95 95 abort: push creates new remote head 71e3228bffe1!
96 96 (merge or see 'hg help push' for details about pushing new heads)
97 97 [255]
98 98
99 99 old head is now public (public remote version)
100 100 ==============================================
101 101
102 102 TODO: Not implemented yet.
103 103
104 104 # setup
105 105 #
106 106 # $ rm -fr ../remote
107 107 # $ cp -R ../backup1 ../remote
108 108 # $ hg -R ../remote phase --public c70b08862e08
109 109 # $ hg phase --draft --force c70b08862e08
110 110 # $ hg log -G --hidden
111 111 # @ 71e3228bffe1 (draft) add new
112 112 # |
113 113 # | x c70b08862e08 (draft) add old
114 114 # |/
115 115 # o b4952fcf48cf (public) add base
116 116 #
117 117 #
118 118 #
119 119 # Abort: old will still be an head because it's public.
120 120 #
121 121 # $ hg push
122 122 # pushing to $TESTTMP/remote
123 123 # searching for changes
124 124 # abort: push creates new remote head 71e3228bffe1!
125 125 # (merge or see 'hg help push' for details about pushing new heads)
126 126 # [255]
127 127
128 128 old head is obsolete but replacement is not pushed
129 129 ==================================================
130 130
131 131 setup
132 132
133 133 $ rm -fr ../remote
134 134 $ cp -R ../backup1 ../remote
135 135 $ hg phase --draft --force '(0::) - 0'
136 136 $ hg up -q '.^'
137 137 $ mkcommit other
138 138 created new head
139 139 $ hg log -G --hidden
140 140 @ d7d41ccbd4de (draft) add other
141 141 |
142 142 | o 71e3228bffe1 (draft) add new
143 143 |/
144 144 | x c70b08862e08 (draft) add old
145 145 |/
146 146 o b4952fcf48cf (public) add base
147 147
148 148
149 149 old exists remotely as draft. It is obsoleted by new but we don't push new.
150 150 Push should abort on new head
151 151
152 152 $ hg push -r 'desc("other")'
153 153 pushing to $TESTTMP/remote (glob)
154 154 searching for changes
155 155 abort: push creates new remote head d7d41ccbd4de!
156 156 (merge or see 'hg help push' for details about pushing new heads)
157 157 [255]
158 158
159 159
160 160
161 161 Both precursors and successors are already know remotely. Descendant adds heads
162 162 ===============================================================================
163 163
164 164 setup. (The obsolete marker is known locally only
165 165
166 166 $ cd ..
167 167 $ rm -rf local
168 168 $ hg clone remote local
169 169 updating to branch default
170 170 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 171 $ cd local
172 172 $ mkcommit old
173 173 old already tracked!
174 174 nothing changed
175 175 [1]
176 176 $ hg up -q '.^'
177 177 $ mkcommit new
178 178 created new head
179 179 $ hg push -f
180 180 pushing to $TESTTMP/remote (glob)
181 181 searching for changes
182 182 adding changesets
183 183 adding manifests
184 184 adding file changes
185 185 added 1 changesets with 1 changes to 1 files (+1 heads)
186 186 $ mkcommit desc1
187 187 $ hg up -q '.^'
188 188 $ mkcommit desc2
189 189 created new head
190 190 $ hg debugobsolete `getid old` `getid new`
191 191 $ hg log -G --hidden
192 192 @ 5fe37041cc2b (draft) add desc2
193 193 |
194 194 | o a3ef1d111c5f (draft) add desc1
195 195 |/
196 196 o 71e3228bffe1 (draft) add new
197 197 |
198 198 | x c70b08862e08 (draft) add old
199 199 |/
200 200 o b4952fcf48cf (public) add base
201 201
202 202 $ hg log -G --hidden -R ../remote
203 203 o 71e3228bffe1 (draft) add new
204 204 |
205 205 | o c70b08862e08 (draft) add old
206 206 |/
207 207 @ b4952fcf48cf (public) add base
208 208
209 209 $ cp -R ../remote ../backup2
210 210
211 211 Push should not warn about adding new heads. We create one, but we'll delete
212 212 one anyway.
213 213
214 214 $ hg push
215 215 pushing to $TESTTMP/remote (glob)
216 216 searching for changes
217 217 adding changesets
218 218 adding manifests
219 219 adding file changes
220 220 added 2 changesets with 2 changes to 2 files (+1 heads)
221 221
222 222
223 223 Remote head is unknown but obsoleted by a local changeset
224 224 =========================================================
225 225
226 226 setup
227 227
228 228 $ rm -fr ../remote
229 229 $ cp -R ../backup1 ../remote
230 230 $ cd ..
231 231 $ rm -rf local
232 232 $ hg clone remote local -r 0
233 233 adding changesets
234 234 adding manifests
235 235 adding file changes
236 236 added 1 changesets with 1 changes to 1 files
237 237 updating to branch default
238 238 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
239 239 $ cd local
240 240 $ mkcommit new
241 241 $ hg -R ../remote id --debug -r tip
242 242 c70b08862e0838ea6d7c59c85da2f1ed6c8d67da tip
243 243 $ hg id --debug -r tip
244 244 71e3228bffe1886550777233d6c97bb5a6b2a650 tip
245 245 $ hg debugobsolete c70b08862e0838ea6d7c59c85da2f1ed6c8d67da 71e3228bffe1886550777233d6c97bb5a6b2a650
246 246 $ hg log -G --hidden
247 247 @ 71e3228bffe1 (draft) add new
248 248 |
249 249 o b4952fcf48cf (public) add base
250 250
251 251 $ hg log -G --hidden -R ../remote
252 252 o c70b08862e08 (draft) add old
253 253 |
254 254 @ b4952fcf48cf (public) add base
255 255
256 256
257 Push should not complain about new heads.
257 We do not have enought data to take the right decision, we should fail
258
259 $ hg push
260 pushing to $TESTTMP/remote (glob)
261 searching for changes
262 remote has heads on branch 'default' that are not known locally: c70b08862e08
263 abort: push creates new remote head 71e3228bffe1!
264 (pull and merge or see 'hg help push' for details about pushing new heads)
265 [255]
258 266
259 $ hg push --traceback
267 Pulling the missing data makes it work
268
269 $ hg pull
270 pulling from $TESTTMP/remote (glob)
271 searching for changes
272 adding changesets
273 adding manifests
274 adding file changes
275 added 1 changesets with 1 changes to 1 files (+1 heads)
276 (run 'hg heads' to see heads)
277 $ hg push
260 278 pushing to $TESTTMP/remote (glob)
261 279 searching for changes
262 280 adding changesets
263 281 adding manifests
264 282 adding file changes
265 283 added 1 changesets with 1 changes to 1 files (+1 heads)
General Comments 0
You need to be logged in to leave comments. Login now