##// END OF EJS Templates
rebase: turn rebaseskipobsolete on by default...
Kostia Balytskyi -
r28429:a4788168 default
parent child Browse files
Show More
@@ -1,1327 +1,1328 b''
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 19 from mercurial import copies, destutil, repoview, registrar, revset
20 20 from mercurial.commands import templateopts
21 21 from mercurial.node import nullrev, nullid, hex, short
22 22 from mercurial.lock import release
23 23 from mercurial.i18n import _
24 24 import os, errno
25 25
26 26 # The following constants are used throughout the rebase module. The ordering of
27 27 # their values must be maintained.
28 28
29 29 # Indicates that a revision needs to be rebased
30 30 revtodo = -1
31 31 nullmerge = -2
32 32 revignored = -3
33 33 # successor in rebase destination
34 34 revprecursor = -4
35 35 # plain prune (no successor)
36 36 revpruned = -5
37 37 revskipped = (revignored, revprecursor, revpruned)
38 38
39 39 cmdtable = {}
40 40 command = cmdutil.command(cmdtable)
41 41 # Note for extension authors: ONLY specify testedwith = 'internal' for
42 42 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
43 43 # be specifying the version(s) of Mercurial they are tested with, or
44 44 # leave the attribute unspecified.
45 45 testedwith = 'internal'
46 46
47 47 def _nothingtorebase():
48 48 return 1
49 49
50 50 def _savegraft(ctx, extra):
51 51 s = ctx.extra().get('source', None)
52 52 if s is not None:
53 53 extra['source'] = s
54 54 s = ctx.extra().get('intermediate-source', None)
55 55 if s is not None:
56 56 extra['intermediate-source'] = s
57 57
58 58 def _savebranch(ctx, extra):
59 59 extra['branch'] = ctx.branch()
60 60
61 61 def _makeextrafn(copiers):
62 62 """make an extrafn out of the given copy-functions.
63 63
64 64 A copy function takes a context and an extra dict, and mutates the
65 65 extra dict as needed based on the given context.
66 66 """
67 67 def extrafn(ctx, extra):
68 68 for c in copiers:
69 69 c(ctx, extra)
70 70 return extrafn
71 71
72 72 def _destrebase(repo, sourceset):
73 73 """small wrapper around destmerge to pass the right extra args
74 74
75 75 Please wrap destutil.destmerge instead."""
76 76 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
77 77 onheadcheck=False)
78 78
79 79 revsetpredicate = registrar.revsetpredicate()
80 80
81 81 @revsetpredicate('_destrebase')
82 82 def _revsetdestrebase(repo, subset, x):
83 83 # ``_rebasedefaultdest()``
84 84
85 85 # default destination for rebase.
86 86 # # XXX: Currently private because I expect the signature to change.
87 87 # # XXX: - bailing out in case of ambiguity vs returning all data.
88 88 # i18n: "_rebasedefaultdest" is a keyword
89 89 sourceset = None
90 90 if x is not None:
91 91 sourceset = revset.getset(repo, revset.fullreposet(repo), x)
92 92 return subset & revset.baseset([_destrebase(repo, sourceset)])
93 93
94 94 @command('rebase',
95 95 [('s', 'source', '',
96 96 _('rebase the specified changeset and descendants'), _('REV')),
97 97 ('b', 'base', '',
98 98 _('rebase everything from branching point of specified changeset'),
99 99 _('REV')),
100 100 ('r', 'rev', [],
101 101 _('rebase these revisions'),
102 102 _('REV')),
103 103 ('d', 'dest', '',
104 104 _('rebase onto the specified changeset'), _('REV')),
105 105 ('', 'collapse', False, _('collapse the rebased changesets')),
106 106 ('m', 'message', '',
107 107 _('use text as collapse commit message'), _('TEXT')),
108 108 ('e', 'edit', False, _('invoke editor on commit messages')),
109 109 ('l', 'logfile', '',
110 110 _('read collapse commit message from file'), _('FILE')),
111 111 ('k', 'keep', False, _('keep original changesets')),
112 112 ('', 'keepbranches', False, _('keep original branch names')),
113 113 ('D', 'detach', False, _('(DEPRECATED)')),
114 114 ('i', 'interactive', False, _('(DEPRECATED)')),
115 115 ('t', 'tool', '', _('specify merge tool')),
116 116 ('c', 'continue', False, _('continue an interrupted rebase')),
117 117 ('a', 'abort', False, _('abort an interrupted rebase'))] +
118 118 templateopts,
119 119 _('[-s REV | -b REV] [-d REV] [OPTION]'))
120 120 def rebase(ui, repo, **opts):
121 121 """move changeset (and descendants) to a different branch
122 122
123 123 Rebase uses repeated merging to graft changesets from one part of
124 124 history (the source) onto another (the destination). This can be
125 125 useful for linearizing *local* changes relative to a master
126 126 development tree.
127 127
128 128 Published commits cannot be rebased (see :hg:`help phases`).
129 129 To copy commits, see :hg:`help graft`.
130 130
131 131 If you don't specify a destination changeset (``-d/--dest``), rebase
132 132 will use the same logic as :hg:`merge` to pick a destination. if
133 133 the current branch contains exactly one other head, the other head
134 134 is merged with by default. Otherwise, an explicit revision with
135 135 which to merge with must be provided. (destination changeset is not
136 136 modified by rebasing, but new changesets are added as its
137 137 descendants.)
138 138
139 139 Here are the ways to select changesets:
140 140
141 141 1. Explicitly select them using ``--rev``.
142 142
143 143 2. Use ``--source`` to select a root changeset and include all of its
144 144 descendants.
145 145
146 146 3. Use ``--base`` to select a changeset; rebase will find ancestors
147 147 and their descendants which are not also ancestors of the destination.
148 148
149 149 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
150 150 rebase will use ``--base .`` as above.
151 151
152 152 Rebase will destroy original changesets unless you use ``--keep``.
153 153 It will also move your bookmarks (even if you do).
154 154
155 155 Some changesets may be dropped if they do not contribute changes
156 156 (e.g. merges from the destination branch).
157 157
158 158 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
159 159 a named branch with two heads. You will need to explicitly specify source
160 160 and/or destination.
161 161
162 162 If you need to use a tool to automate merge/conflict decisions, you
163 163 can specify one with ``--tool``, see :hg:`help merge-tools`.
164 164 As a caveat: the tool will not be used to mediate when a file was
165 165 deleted, there is no hook presently available for this.
166 166
167 167 If a rebase is interrupted to manually resolve a conflict, it can be
168 168 continued with --continue/-c or aborted with --abort/-a.
169 169
170 170 .. container:: verbose
171 171
172 172 Examples:
173 173
174 174 - move "local changes" (current commit back to branching point)
175 175 to the current branch tip after a pull::
176 176
177 177 hg rebase
178 178
179 179 - move a single changeset to the stable branch::
180 180
181 181 hg rebase -r 5f493448 -d stable
182 182
183 183 - splice a commit and all its descendants onto another part of history::
184 184
185 185 hg rebase --source c0c3 --dest 4cf9
186 186
187 187 - rebase everything on a branch marked by a bookmark onto the
188 188 default branch::
189 189
190 190 hg rebase --base myfeature --dest default
191 191
192 192 - collapse a sequence of changes into a single commit::
193 193
194 194 hg rebase --collapse -r 1520:1525 -d .
195 195
196 196 - move a named branch while preserving its name::
197 197
198 198 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
199 199
200 200 Returns 0 on success, 1 if nothing to rebase or there are
201 201 unresolved conflicts.
202 202
203 203 """
204 204 originalwd = target = None
205 205 activebookmark = None
206 206 external = nullrev
207 207 # Mapping between the old revision id and either what is the new rebased
208 208 # revision or what needs to be done with the old revision. The state dict
209 209 # will be what contains most of the rebase progress state.
210 210 state = {}
211 211 skipped = set()
212 212 targetancestors = set()
213 213
214 214
215 215 lock = wlock = None
216 216 try:
217 217 wlock = repo.wlock()
218 218 lock = repo.lock()
219 219
220 220 # Validate input and define rebasing points
221 221 destf = opts.get('dest', None)
222 222 srcf = opts.get('source', None)
223 223 basef = opts.get('base', None)
224 224 revf = opts.get('rev', [])
225 225 contf = opts.get('continue')
226 226 abortf = opts.get('abort')
227 227 collapsef = opts.get('collapse', False)
228 228 collapsemsg = cmdutil.logmessage(ui, opts)
229 229 date = opts.get('date', None)
230 230 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
231 231 extrafns = [_savegraft]
232 232 if e:
233 233 extrafns = [e]
234 234 keepf = opts.get('keep', False)
235 235 keepbranchesf = opts.get('keepbranches', False)
236 236 # keepopen is not meant for use on the command line, but by
237 237 # other extensions
238 238 keepopen = opts.get('keepopen', False)
239 239
240 240 if opts.get('interactive'):
241 241 try:
242 242 if extensions.find('histedit'):
243 243 enablehistedit = ''
244 244 except KeyError:
245 245 enablehistedit = " --config extensions.histedit="
246 246 help = "hg%s help -e histedit" % enablehistedit
247 247 msg = _("interactive history editing is supported by the "
248 248 "'histedit' extension (see \"%s\")") % help
249 249 raise error.Abort(msg)
250 250
251 251 if collapsemsg and not collapsef:
252 252 raise error.Abort(
253 253 _('message can only be specified with collapse'))
254 254
255 255 if contf or abortf:
256 256 if contf and abortf:
257 257 raise error.Abort(_('cannot use both abort and continue'))
258 258 if collapsef:
259 259 raise error.Abort(
260 260 _('cannot use collapse with continue or abort'))
261 261 if srcf or basef or destf:
262 262 raise error.Abort(
263 263 _('abort and continue do not allow specifying revisions'))
264 264 if abortf and opts.get('tool', False):
265 265 ui.warn(_('tool option will be ignored\n'))
266 266
267 267 try:
268 268 (originalwd, target, state, skipped, collapsef, keepf,
269 269 keepbranchesf, external, activebookmark) = restorestatus(repo)
270 270 collapsemsg = restorecollapsemsg(repo)
271 271 except error.RepoLookupError:
272 272 if abortf:
273 273 clearstatus(repo)
274 274 clearcollapsemsg(repo)
275 275 repo.ui.warn(_('rebase aborted (no revision is removed,'
276 276 ' only broken state is cleared)\n'))
277 277 return 0
278 278 else:
279 279 msg = _('cannot continue inconsistent rebase')
280 280 hint = _('use "hg rebase --abort" to clear broken state')
281 281 raise error.Abort(msg, hint=hint)
282 282 if abortf:
283 283 return abort(repo, originalwd, target, state,
284 284 activebookmark=activebookmark)
285 285 else:
286 286 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf)
287 287 if dest is None:
288 288 return _nothingtorebase()
289 289
290 290 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
291 291 if (not (keepf or allowunstable)
292 292 and repo.revs('first(children(%ld) - %ld)',
293 293 rebaseset, rebaseset)):
294 294 raise error.Abort(
295 295 _("can't remove original changesets with"
296 296 " unrebased descendants"),
297 297 hint=_('use --keep to keep original changesets'))
298 298
299 299 obsoletenotrebased = {}
300 if ui.configbool('experimental', 'rebaseskipobsolete'):
300 if ui.configbool('experimental', 'rebaseskipobsolete',
301 default=True):
301 302 rebasesetrevs = set(rebaseset)
302 303 rebaseobsrevs = _filterobsoleterevs(repo, rebasesetrevs)
303 304 obsoletenotrebased = _computeobsoletenotrebased(repo,
304 305 rebaseobsrevs,
305 306 dest)
306 307 rebaseobsskipped = set(obsoletenotrebased)
307 308
308 309 # Obsolete node with successors not in dest leads to divergence
309 310 divergenceok = ui.configbool('experimental',
310 311 'allowdivergence')
311 312 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
312 313
313 314 if divergencebasecandidates and not divergenceok:
314 315 divhashes = (str(repo[r])
315 316 for r in divergencebasecandidates)
316 317 msg = _("this rebase will cause "
317 318 "divergences from: %s")
318 319 h = _("to force the rebase please set "
319 320 "experimental.allowdivergence=True")
320 321 raise error.Abort(msg % (",".join(divhashes),), hint=h)
321 322
322 323 # - plain prune (no successor) changesets are rebased
323 324 # - split changesets are not rebased if at least one of the
324 325 # changeset resulting from the split is an ancestor of dest
325 326 rebaseset = rebasesetrevs - rebaseobsskipped
326 327 if rebasesetrevs and not rebaseset:
327 328 msg = _('all requested changesets have equivalents '
328 329 'or were marked as obsolete')
329 330 hint = _('to force the rebase, set the config '
330 331 'experimental.rebaseskipobsolete to False')
331 332 raise error.Abort(msg, hint=hint)
332 333
333 334 result = buildstate(repo, dest, rebaseset, collapsef,
334 335 obsoletenotrebased)
335 336
336 337 if not result:
337 338 # Empty state built, nothing to rebase
338 339 ui.status(_('nothing to rebase\n'))
339 340 return _nothingtorebase()
340 341
341 342 root = min(rebaseset)
342 343 if not keepf and not repo[root].mutable():
343 344 raise error.Abort(_("can't rebase public changeset %s")
344 345 % repo[root],
345 346 hint=_('see "hg help phases" for details'))
346 347
347 348 originalwd, target, state = result
348 349 if collapsef:
349 350 targetancestors = repo.changelog.ancestors([target],
350 351 inclusive=True)
351 352 external = externalparent(repo, state, targetancestors)
352 353
353 354 if dest.closesbranch() and not keepbranchesf:
354 355 ui.status(_('reopening closed branch head %s\n') % dest)
355 356
356 357 if keepbranchesf:
357 358 # insert _savebranch at the start of extrafns so if
358 359 # there's a user-provided extrafn it can clobber branch if
359 360 # desired
360 361 extrafns.insert(0, _savebranch)
361 362 if collapsef:
362 363 branches = set()
363 364 for rev in state:
364 365 branches.add(repo[rev].branch())
365 366 if len(branches) > 1:
366 367 raise error.Abort(_('cannot collapse multiple named '
367 368 'branches'))
368 369
369 370 # Rebase
370 371 if not targetancestors:
371 372 targetancestors = repo.changelog.ancestors([target], inclusive=True)
372 373
373 374 # Keep track of the current bookmarks in order to reset them later
374 375 currentbookmarks = repo._bookmarks.copy()
375 376 activebookmark = activebookmark or repo._activebookmark
376 377 if activebookmark:
377 378 bookmarks.deactivate(repo)
378 379
379 380 extrafn = _makeextrafn(extrafns)
380 381
381 382 sortedstate = sorted(state)
382 383 total = len(sortedstate)
383 384 pos = 0
384 385 for rev in sortedstate:
385 386 ctx = repo[rev]
386 387 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
387 388 ctx.description().split('\n', 1)[0])
388 389 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
389 390 if names:
390 391 desc += ' (%s)' % ' '.join(names)
391 392 pos += 1
392 393 if state[rev] == revtodo:
393 394 ui.status(_('rebasing %s\n') % desc)
394 395 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
395 396 _('changesets'), total)
396 397 p1, p2, base = defineparents(repo, rev, target, state,
397 398 targetancestors)
398 399 storestatus(repo, originalwd, target, state, collapsef, keepf,
399 400 keepbranchesf, external, activebookmark)
400 401 storecollapsemsg(repo, collapsemsg)
401 402 if len(repo[None].parents()) == 2:
402 403 repo.ui.debug('resuming interrupted rebase\n')
403 404 else:
404 405 try:
405 406 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
406 407 'rebase')
407 408 stats = rebasenode(repo, rev, p1, base, state,
408 409 collapsef, target)
409 410 if stats and stats[3] > 0:
410 411 raise error.InterventionRequired(
411 412 _('unresolved conflicts (see hg '
412 413 'resolve, then hg rebase --continue)'))
413 414 finally:
414 415 ui.setconfig('ui', 'forcemerge', '', 'rebase')
415 416 if not collapsef:
416 417 merging = p2 != nullrev
417 418 editform = cmdutil.mergeeditform(merging, 'rebase')
418 419 editor = cmdutil.getcommiteditor(editform=editform, **opts)
419 420 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
420 421 editor=editor,
421 422 keepbranches=keepbranchesf,
422 423 date=date)
423 424 else:
424 425 # Skip commit if we are collapsing
425 426 repo.dirstate.beginparentchange()
426 427 repo.setparents(repo[p1].node())
427 428 repo.dirstate.endparentchange()
428 429 newnode = None
429 430 # Update the state
430 431 if newnode is not None:
431 432 state[rev] = repo[newnode].rev()
432 433 ui.debug('rebased as %s\n' % short(newnode))
433 434 else:
434 435 if not collapsef:
435 436 ui.warn(_('note: rebase of %d:%s created no changes '
436 437 'to commit\n') % (rev, ctx))
437 438 skipped.add(rev)
438 439 state[rev] = p1
439 440 ui.debug('next revision set to %s\n' % p1)
440 441 elif state[rev] == nullmerge:
441 442 ui.debug('ignoring null merge rebase of %s\n' % rev)
442 443 elif state[rev] == revignored:
443 444 ui.status(_('not rebasing ignored %s\n') % desc)
444 445 elif state[rev] == revprecursor:
445 446 targetctx = repo[obsoletenotrebased[rev]]
446 447 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
447 448 targetctx.description().split('\n', 1)[0])
448 449 msg = _('note: not rebasing %s, already in destination as %s\n')
449 450 ui.status(msg % (desc, desctarget))
450 451 elif state[rev] == revpruned:
451 452 msg = _('note: not rebasing %s, it has no successor\n')
452 453 ui.status(msg % desc)
453 454 else:
454 455 ui.status(_('already rebased %s as %s\n') %
455 456 (desc, repo[state[rev]]))
456 457
457 458 ui.progress(_('rebasing'), None)
458 459 ui.note(_('rebase merging completed\n'))
459 460
460 461 if collapsef and not keepopen:
461 462 p1, p2, _base = defineparents(repo, min(state), target,
462 463 state, targetancestors)
463 464 editopt = opts.get('edit')
464 465 editform = 'rebase.collapse'
465 466 if collapsemsg:
466 467 commitmsg = collapsemsg
467 468 else:
468 469 commitmsg = 'Collapsed revision'
469 470 for rebased in state:
470 471 if rebased not in skipped and state[rebased] > nullmerge:
471 472 commitmsg += '\n* %s' % repo[rebased].description()
472 473 editopt = True
473 474 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
474 475 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
475 476 extrafn=extrafn, editor=editor,
476 477 keepbranches=keepbranchesf,
477 478 date=date)
478 479 if newnode is None:
479 480 newrev = target
480 481 else:
481 482 newrev = repo[newnode].rev()
482 483 for oldrev in state.iterkeys():
483 484 if state[oldrev] > nullmerge:
484 485 state[oldrev] = newrev
485 486
486 487 if 'qtip' in repo.tags():
487 488 updatemq(repo, state, skipped, **opts)
488 489
489 490 if currentbookmarks:
490 491 # Nodeids are needed to reset bookmarks
491 492 nstate = {}
492 493 for k, v in state.iteritems():
493 494 if v > nullmerge:
494 495 nstate[repo[k].node()] = repo[v].node()
495 496 # XXX this is the same as dest.node() for the non-continue path --
496 497 # this should probably be cleaned up
497 498 targetnode = repo[target].node()
498 499
499 500 # restore original working directory
500 501 # (we do this before stripping)
501 502 newwd = state.get(originalwd, originalwd)
502 503 if newwd < 0:
503 504 # original directory is a parent of rebase set root or ignored
504 505 newwd = originalwd
505 506 if newwd not in [c.rev() for c in repo[None].parents()]:
506 507 ui.note(_("update back to initial working directory parent\n"))
507 508 hg.updaterepo(repo, newwd, False)
508 509
509 510 if not keepf:
510 511 collapsedas = None
511 512 if collapsef:
512 513 collapsedas = newnode
513 514 clearrebased(ui, repo, state, skipped, collapsedas)
514 515
515 516 with repo.transaction('bookmark') as tr:
516 517 if currentbookmarks:
517 518 updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr)
518 519 if activebookmark not in repo._bookmarks:
519 520 # active bookmark was divergent one and has been deleted
520 521 activebookmark = None
521 522 clearstatus(repo)
522 523 clearcollapsemsg(repo)
523 524
524 525 ui.note(_("rebase completed\n"))
525 526 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
526 527 if skipped:
527 528 ui.note(_("%d revisions have been skipped\n") % len(skipped))
528 529
529 530 if (activebookmark and
530 531 repo['.'].node() == repo._bookmarks[activebookmark]):
531 532 bookmarks.activate(repo, activebookmark)
532 533
533 534 finally:
534 535 release(lock, wlock)
535 536
536 537 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[]):
537 538 """use revisions argument to define destination and rebase set
538 539 """
539 540 if srcf and basef:
540 541 raise error.Abort(_('cannot specify both a source and a base'))
541 542 if revf and basef:
542 543 raise error.Abort(_('cannot specify both a revision and a base'))
543 544 if revf and srcf:
544 545 raise error.Abort(_('cannot specify both a revision and a source'))
545 546
546 547 cmdutil.checkunfinished(repo)
547 548 cmdutil.bailifchanged(repo)
548 549
549 550 if destf:
550 551 dest = scmutil.revsingle(repo, destf)
551 552
552 553 if revf:
553 554 rebaseset = scmutil.revrange(repo, revf)
554 555 if not rebaseset:
555 556 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
556 557 return None, None
557 558 elif srcf:
558 559 src = scmutil.revrange(repo, [srcf])
559 560 if not src:
560 561 ui.status(_('empty "source" revision set - nothing to rebase\n'))
561 562 return None, None
562 563 rebaseset = repo.revs('(%ld)::', src)
563 564 assert rebaseset
564 565 else:
565 566 base = scmutil.revrange(repo, [basef or '.'])
566 567 if not base:
567 568 ui.status(_('empty "base" revision set - '
568 569 "can't compute rebase set\n"))
569 570 return None, None
570 571 if not destf:
571 572 dest = repo[_destrebase(repo, base)]
572 573 destf = str(dest)
573 574
574 575 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
575 576 if commonanc is not None:
576 577 rebaseset = repo.revs('(%d::(%ld) - %d)::',
577 578 commonanc, base, commonanc)
578 579 else:
579 580 rebaseset = []
580 581
581 582 if not rebaseset:
582 583 # transform to list because smartsets are not comparable to
583 584 # lists. This should be improved to honor laziness of
584 585 # smartset.
585 586 if list(base) == [dest.rev()]:
586 587 if basef:
587 588 ui.status(_('nothing to rebase - %s is both "base"'
588 589 ' and destination\n') % dest)
589 590 else:
590 591 ui.status(_('nothing to rebase - working directory '
591 592 'parent is also destination\n'))
592 593 elif not repo.revs('%ld - ::%d', base, dest):
593 594 if basef:
594 595 ui.status(_('nothing to rebase - "base" %s is '
595 596 'already an ancestor of destination '
596 597 '%s\n') %
597 598 ('+'.join(str(repo[r]) for r in base),
598 599 dest))
599 600 else:
600 601 ui.status(_('nothing to rebase - working '
601 602 'directory parent is already an '
602 603 'ancestor of destination %s\n') % dest)
603 604 else: # can it happen?
604 605 ui.status(_('nothing to rebase from %s to %s\n') %
605 606 ('+'.join(str(repo[r]) for r in base), dest))
606 607 return None, None
607 608
608 609 if not destf:
609 610 dest = repo[_destrebase(repo, rebaseset)]
610 611 destf = str(dest)
611 612
612 613 return dest, rebaseset
613 614
614 615 def externalparent(repo, state, targetancestors):
615 616 """Return the revision that should be used as the second parent
616 617 when the revisions in state is collapsed on top of targetancestors.
617 618 Abort if there is more than one parent.
618 619 """
619 620 parents = set()
620 621 source = min(state)
621 622 for rev in state:
622 623 if rev == source:
623 624 continue
624 625 for p in repo[rev].parents():
625 626 if (p.rev() not in state
626 627 and p.rev() not in targetancestors):
627 628 parents.add(p.rev())
628 629 if not parents:
629 630 return nullrev
630 631 if len(parents) == 1:
631 632 return parents.pop()
632 633 raise error.Abort(_('unable to collapse on top of %s, there is more '
633 634 'than one external parent: %s') %
634 635 (max(targetancestors),
635 636 ', '.join(str(p) for p in sorted(parents))))
636 637
637 638 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
638 639 keepbranches=False, date=None):
639 640 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
640 641 but also store useful information in extra.
641 642 Return node of committed revision.'''
642 643 dsguard = cmdutil.dirstateguard(repo, 'rebase')
643 644 try:
644 645 repo.setparents(repo[p1].node(), repo[p2].node())
645 646 ctx = repo[rev]
646 647 if commitmsg is None:
647 648 commitmsg = ctx.description()
648 649 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
649 650 extra = {'rebase_source': ctx.hex()}
650 651 if extrafn:
651 652 extrafn(ctx, extra)
652 653
653 654 backup = repo.ui.backupconfig('phases', 'new-commit')
654 655 try:
655 656 targetphase = max(ctx.phase(), phases.draft)
656 657 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
657 658 if keepbranch:
658 659 repo.ui.setconfig('ui', 'allowemptycommit', True)
659 660 # Commit might fail if unresolved files exist
660 661 if date is None:
661 662 date = ctx.date()
662 663 newnode = repo.commit(text=commitmsg, user=ctx.user(),
663 664 date=date, extra=extra, editor=editor)
664 665 finally:
665 666 repo.ui.restoreconfig(backup)
666 667
667 668 repo.dirstate.setbranch(repo[newnode].branch())
668 669 dsguard.close()
669 670 return newnode
670 671 finally:
671 672 release(dsguard)
672 673
673 674 def rebasenode(repo, rev, p1, base, state, collapse, target):
674 675 'Rebase a single revision rev on top of p1 using base as merge ancestor'
675 676 # Merge phase
676 677 # Update to target and merge it with local
677 678 if repo['.'].rev() != p1:
678 679 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
679 680 merge.update(repo, p1, False, True)
680 681 else:
681 682 repo.ui.debug(" already in target\n")
682 683 repo.dirstate.write(repo.currenttransaction())
683 684 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
684 685 if base is not None:
685 686 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
686 687 # When collapsing in-place, the parent is the common ancestor, we
687 688 # have to allow merging with it.
688 689 stats = merge.update(repo, rev, True, True, base, collapse,
689 690 labels=['dest', 'source'])
690 691 if collapse:
691 692 copies.duplicatecopies(repo, rev, target)
692 693 else:
693 694 # If we're not using --collapse, we need to
694 695 # duplicate copies between the revision we're
695 696 # rebasing and its first parent, but *not*
696 697 # duplicate any copies that have already been
697 698 # performed in the destination.
698 699 p1rev = repo[rev].p1().rev()
699 700 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
700 701 return stats
701 702
702 703 def nearestrebased(repo, rev, state):
703 704 """return the nearest ancestors of rev in the rebase result"""
704 705 rebased = [r for r in state if state[r] > nullmerge]
705 706 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
706 707 if candidates:
707 708 return state[candidates.first()]
708 709 else:
709 710 return None
710 711
711 712 def defineparents(repo, rev, target, state, targetancestors):
712 713 'Return the new parent relationship of the revision that will be rebased'
713 714 parents = repo[rev].parents()
714 715 p1 = p2 = nullrev
715 716
716 717 p1n = parents[0].rev()
717 718 if p1n in targetancestors:
718 719 p1 = target
719 720 elif p1n in state:
720 721 if state[p1n] == nullmerge:
721 722 p1 = target
722 723 elif state[p1n] in revskipped:
723 724 p1 = nearestrebased(repo, p1n, state)
724 725 if p1 is None:
725 726 p1 = target
726 727 else:
727 728 p1 = state[p1n]
728 729 else: # p1n external
729 730 p1 = target
730 731 p2 = p1n
731 732
732 733 if len(parents) == 2 and parents[1].rev() not in targetancestors:
733 734 p2n = parents[1].rev()
734 735 # interesting second parent
735 736 if p2n in state:
736 737 if p1 == target: # p1n in targetancestors or external
737 738 p1 = state[p2n]
738 739 elif state[p2n] in revskipped:
739 740 p2 = nearestrebased(repo, p2n, state)
740 741 if p2 is None:
741 742 # no ancestors rebased yet, detach
742 743 p2 = target
743 744 else:
744 745 p2 = state[p2n]
745 746 else: # p2n external
746 747 if p2 != nullrev: # p1n external too => rev is a merged revision
747 748 raise error.Abort(_('cannot use revision %d as base, result '
748 749 'would have 3 parents') % rev)
749 750 p2 = p2n
750 751 repo.ui.debug(" future parents are %d and %d\n" %
751 752 (repo[p1].rev(), repo[p2].rev()))
752 753
753 754 if not any(p.rev() in state for p in parents):
754 755 # Case (1) root changeset of a non-detaching rebase set.
755 756 # Let the merge mechanism find the base itself.
756 757 base = None
757 758 elif not repo[rev].p2():
758 759 # Case (2) detaching the node with a single parent, use this parent
759 760 base = repo[rev].p1().rev()
760 761 else:
761 762 # Assuming there is a p1, this is the case where there also is a p2.
762 763 # We are thus rebasing a merge and need to pick the right merge base.
763 764 #
764 765 # Imagine we have:
765 766 # - M: current rebase revision in this step
766 767 # - A: one parent of M
767 768 # - B: other parent of M
768 769 # - D: destination of this merge step (p1 var)
769 770 #
770 771 # Consider the case where D is a descendant of A or B and the other is
771 772 # 'outside'. In this case, the right merge base is the D ancestor.
772 773 #
773 774 # An informal proof, assuming A is 'outside' and B is the D ancestor:
774 775 #
775 776 # If we pick B as the base, the merge involves:
776 777 # - changes from B to M (actual changeset payload)
777 778 # - changes from B to D (induced by rebase) as D is a rebased
778 779 # version of B)
779 780 # Which exactly represent the rebase operation.
780 781 #
781 782 # If we pick A as the base, the merge involves:
782 783 # - changes from A to M (actual changeset payload)
783 784 # - changes from A to D (with include changes between unrelated A and B
784 785 # plus changes induced by rebase)
785 786 # Which does not represent anything sensible and creates a lot of
786 787 # conflicts. A is thus not the right choice - B is.
787 788 #
788 789 # Note: The base found in this 'proof' is only correct in the specified
789 790 # case. This base does not make sense if is not D a descendant of A or B
790 791 # or if the other is not parent 'outside' (especially not if the other
791 792 # parent has been rebased). The current implementation does not
792 793 # make it feasible to consider different cases separately. In these
793 794 # other cases we currently just leave it to the user to correctly
794 795 # resolve an impossible merge using a wrong ancestor.
795 796 for p in repo[rev].parents():
796 797 if state.get(p.rev()) == p1:
797 798 base = p.rev()
798 799 break
799 800 else: # fallback when base not found
800 801 base = None
801 802
802 803 # Raise because this function is called wrong (see issue 4106)
803 804 raise AssertionError('no base found to rebase on '
804 805 '(defineparents called wrong)')
805 806 return p1, p2, base
806 807
807 808 def isagitpatch(repo, patchname):
808 809 'Return true if the given patch is in git format'
809 810 mqpatch = os.path.join(repo.mq.path, patchname)
810 811 for line in patch.linereader(file(mqpatch, 'rb')):
811 812 if line.startswith('diff --git'):
812 813 return True
813 814 return False
814 815
815 816 def updatemq(repo, state, skipped, **opts):
816 817 'Update rebased mq patches - finalize and then import them'
817 818 mqrebase = {}
818 819 mq = repo.mq
819 820 original_series = mq.fullseries[:]
820 821 skippedpatches = set()
821 822
822 823 for p in mq.applied:
823 824 rev = repo[p.node].rev()
824 825 if rev in state:
825 826 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
826 827 (rev, p.name))
827 828 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
828 829 else:
829 830 # Applied but not rebased, not sure this should happen
830 831 skippedpatches.add(p.name)
831 832
832 833 if mqrebase:
833 834 mq.finish(repo, mqrebase.keys())
834 835
835 836 # We must start import from the newest revision
836 837 for rev in sorted(mqrebase, reverse=True):
837 838 if rev not in skipped:
838 839 name, isgit = mqrebase[rev]
839 840 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
840 841 (name, state[rev], repo[state[rev]]))
841 842 mq.qimport(repo, (), patchname=name, git=isgit,
842 843 rev=[str(state[rev])])
843 844 else:
844 845 # Rebased and skipped
845 846 skippedpatches.add(mqrebase[rev][0])
846 847
847 848 # Patches were either applied and rebased and imported in
848 849 # order, applied and removed or unapplied. Discard the removed
849 850 # ones while preserving the original series order and guards.
850 851 newseries = [s for s in original_series
851 852 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
852 853 mq.fullseries[:] = newseries
853 854 mq.seriesdirty = True
854 855 mq.savedirty()
855 856
856 857 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
857 858 'Move bookmarks to their correct changesets, and delete divergent ones'
858 859 marks = repo._bookmarks
859 860 for k, v in originalbookmarks.iteritems():
860 861 if v in nstate:
861 862 # update the bookmarks for revs that have moved
862 863 marks[k] = nstate[v]
863 864 bookmarks.deletedivergent(repo, [targetnode], k)
864 865 marks.recordchange(tr)
865 866
866 867 def storecollapsemsg(repo, collapsemsg):
867 868 'Store the collapse message to allow recovery'
868 869 collapsemsg = collapsemsg or ''
869 870 f = repo.vfs("last-message.txt", "w")
870 871 f.write("%s\n" % collapsemsg)
871 872 f.close()
872 873
873 874 def clearcollapsemsg(repo):
874 875 'Remove collapse message file'
875 876 util.unlinkpath(repo.join("last-message.txt"), ignoremissing=True)
876 877
877 878 def restorecollapsemsg(repo):
878 879 'Restore previously stored collapse message'
879 880 try:
880 881 f = repo.vfs("last-message.txt")
881 882 collapsemsg = f.readline().strip()
882 883 f.close()
883 884 except IOError as err:
884 885 if err.errno != errno.ENOENT:
885 886 raise
886 887 raise error.Abort(_('no rebase in progress'))
887 888 return collapsemsg
888 889
889 890 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
890 891 external, activebookmark):
891 892 'Store the current status to allow recovery'
892 893 f = repo.vfs("rebasestate", "w")
893 894 f.write(repo[originalwd].hex() + '\n')
894 895 f.write(repo[target].hex() + '\n')
895 896 f.write(repo[external].hex() + '\n')
896 897 f.write('%d\n' % int(collapse))
897 898 f.write('%d\n' % int(keep))
898 899 f.write('%d\n' % int(keepbranches))
899 900 f.write('%s\n' % (activebookmark or ''))
900 901 for d, v in state.iteritems():
901 902 oldrev = repo[d].hex()
902 903 if v >= 0:
903 904 newrev = repo[v].hex()
904 905 elif v == revtodo:
905 906 # To maintain format compatibility, we have to use nullid.
906 907 # Please do remove this special case when upgrading the format.
907 908 newrev = hex(nullid)
908 909 else:
909 910 newrev = v
910 911 f.write("%s:%s\n" % (oldrev, newrev))
911 912 f.close()
912 913 repo.ui.debug('rebase status stored\n')
913 914
914 915 def clearstatus(repo):
915 916 'Remove the status files'
916 917 _clearrebasesetvisibiliy(repo)
917 918 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
918 919
919 920 def restorestatus(repo):
920 921 'Restore a previously stored status'
921 922 keepbranches = None
922 923 target = None
923 924 collapse = False
924 925 external = nullrev
925 926 activebookmark = None
926 927 state = {}
927 928
928 929 try:
929 930 f = repo.vfs("rebasestate")
930 931 for i, l in enumerate(f.read().splitlines()):
931 932 if i == 0:
932 933 originalwd = repo[l].rev()
933 934 elif i == 1:
934 935 target = repo[l].rev()
935 936 elif i == 2:
936 937 external = repo[l].rev()
937 938 elif i == 3:
938 939 collapse = bool(int(l))
939 940 elif i == 4:
940 941 keep = bool(int(l))
941 942 elif i == 5:
942 943 keepbranches = bool(int(l))
943 944 elif i == 6 and not (len(l) == 81 and ':' in l):
944 945 # line 6 is a recent addition, so for backwards compatibility
945 946 # check that the line doesn't look like the oldrev:newrev lines
946 947 activebookmark = l
947 948 else:
948 949 oldrev, newrev = l.split(':')
949 950 if newrev in (str(nullmerge), str(revignored),
950 951 str(revprecursor), str(revpruned)):
951 952 state[repo[oldrev].rev()] = int(newrev)
952 953 elif newrev == nullid:
953 954 state[repo[oldrev].rev()] = revtodo
954 955 # Legacy compat special case
955 956 else:
956 957 state[repo[oldrev].rev()] = repo[newrev].rev()
957 958
958 959 except IOError as err:
959 960 if err.errno != errno.ENOENT:
960 961 raise
961 962 cmdutil.wrongtooltocontinue(repo, _('rebase'))
962 963
963 964 if keepbranches is None:
964 965 raise error.Abort(_('.hg/rebasestate is incomplete'))
965 966
966 967 skipped = set()
967 968 # recompute the set of skipped revs
968 969 if not collapse:
969 970 seen = set([target])
970 971 for old, new in sorted(state.items()):
971 972 if new != revtodo and new in seen:
972 973 skipped.add(old)
973 974 seen.add(new)
974 975 repo.ui.debug('computed skipped revs: %s\n' %
975 976 (' '.join(str(r) for r in sorted(skipped)) or None))
976 977 repo.ui.debug('rebase status resumed\n')
977 978 _setrebasesetvisibility(repo, state.keys())
978 979 return (originalwd, target, state, skipped,
979 980 collapse, keep, keepbranches, external, activebookmark)
980 981
981 982 def needupdate(repo, state):
982 983 '''check whether we should `update --clean` away from a merge, or if
983 984 somehow the working dir got forcibly updated, e.g. by older hg'''
984 985 parents = [p.rev() for p in repo[None].parents()]
985 986
986 987 # Are we in a merge state at all?
987 988 if len(parents) < 2:
988 989 return False
989 990
990 991 # We should be standing on the first as-of-yet unrebased commit.
991 992 firstunrebased = min([old for old, new in state.iteritems()
992 993 if new == nullrev])
993 994 if firstunrebased in parents:
994 995 return True
995 996
996 997 return False
997 998
998 999 def abort(repo, originalwd, target, state, activebookmark=None):
999 1000 '''Restore the repository to its original state. Additional args:
1000 1001
1001 1002 activebookmark: the name of the bookmark that should be active after the
1002 1003 restore'''
1003 1004
1004 1005 try:
1005 1006 # If the first commits in the rebased set get skipped during the rebase,
1006 1007 # their values within the state mapping will be the target rev id. The
1007 1008 # dstates list must must not contain the target rev (issue4896)
1008 1009 dstates = [s for s in state.values() if s >= 0 and s != target]
1009 1010 immutable = [d for d in dstates if not repo[d].mutable()]
1010 1011 cleanup = True
1011 1012 if immutable:
1012 1013 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1013 1014 % ', '.join(str(repo[r]) for r in immutable),
1014 1015 hint=_('see "hg help phases" for details'))
1015 1016 cleanup = False
1016 1017
1017 1018 descendants = set()
1018 1019 if dstates:
1019 1020 descendants = set(repo.changelog.descendants(dstates))
1020 1021 if descendants - set(dstates):
1021 1022 repo.ui.warn(_("warning: new changesets detected on target branch, "
1022 1023 "can't strip\n"))
1023 1024 cleanup = False
1024 1025
1025 1026 if cleanup:
1026 1027 shouldupdate = False
1027 1028 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1028 1029 if rebased:
1029 1030 strippoints = [
1030 1031 c.node() for c in repo.set('roots(%ld)', rebased)]
1031 1032 shouldupdate = len([
1032 1033 c.node() for c in repo.set('. & (%ld)', rebased)]) > 0
1033 1034
1034 1035 # Update away from the rebase if necessary
1035 1036 if shouldupdate or needupdate(repo, state):
1036 1037 merge.update(repo, originalwd, False, True)
1037 1038
1038 1039 # Strip from the first rebased revision
1039 1040 if rebased:
1040 1041 # no backup of rebased cset versions needed
1041 1042 repair.strip(repo.ui, repo, strippoints)
1042 1043
1043 1044 if activebookmark and activebookmark in repo._bookmarks:
1044 1045 bookmarks.activate(repo, activebookmark)
1045 1046
1046 1047 finally:
1047 1048 clearstatus(repo)
1048 1049 clearcollapsemsg(repo)
1049 1050 repo.ui.warn(_('rebase aborted\n'))
1050 1051 return 0
1051 1052
1052 1053 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1053 1054 '''Define which revisions are going to be rebased and where
1054 1055
1055 1056 repo: repo
1056 1057 dest: context
1057 1058 rebaseset: set of rev
1058 1059 '''
1059 1060 _setrebasesetvisibility(repo, rebaseset)
1060 1061
1061 1062 # This check isn't strictly necessary, since mq detects commits over an
1062 1063 # applied patch. But it prevents messing up the working directory when
1063 1064 # a partially completed rebase is blocked by mq.
1064 1065 if 'qtip' in repo.tags() and (dest.node() in
1065 1066 [s.node for s in repo.mq.applied]):
1066 1067 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1067 1068
1068 1069 roots = list(repo.set('roots(%ld)', rebaseset))
1069 1070 if not roots:
1070 1071 raise error.Abort(_('no matching revisions'))
1071 1072 roots.sort()
1072 1073 state = {}
1073 1074 detachset = set()
1074 1075 for root in roots:
1075 1076 commonbase = root.ancestor(dest)
1076 1077 if commonbase == root:
1077 1078 raise error.Abort(_('source is ancestor of destination'))
1078 1079 if commonbase == dest:
1079 1080 samebranch = root.branch() == dest.branch()
1080 1081 if not collapse and samebranch and root in dest.children():
1081 1082 repo.ui.debug('source is a child of destination\n')
1082 1083 return None
1083 1084
1084 1085 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
1085 1086 state.update(dict.fromkeys(rebaseset, revtodo))
1086 1087 # Rebase tries to turn <dest> into a parent of <root> while
1087 1088 # preserving the number of parents of rebased changesets:
1088 1089 #
1089 1090 # - A changeset with a single parent will always be rebased as a
1090 1091 # changeset with a single parent.
1091 1092 #
1092 1093 # - A merge will be rebased as merge unless its parents are both
1093 1094 # ancestors of <dest> or are themselves in the rebased set and
1094 1095 # pruned while rebased.
1095 1096 #
1096 1097 # If one parent of <root> is an ancestor of <dest>, the rebased
1097 1098 # version of this parent will be <dest>. This is always true with
1098 1099 # --base option.
1099 1100 #
1100 1101 # Otherwise, we need to *replace* the original parents with
1101 1102 # <dest>. This "detaches" the rebased set from its former location
1102 1103 # and rebases it onto <dest>. Changes introduced by ancestors of
1103 1104 # <root> not common with <dest> (the detachset, marked as
1104 1105 # nullmerge) are "removed" from the rebased changesets.
1105 1106 #
1106 1107 # - If <root> has a single parent, set it to <dest>.
1107 1108 #
1108 1109 # - If <root> is a merge, we cannot decide which parent to
1109 1110 # replace, the rebase operation is not clearly defined.
1110 1111 #
1111 1112 # The table below sums up this behavior:
1112 1113 #
1113 1114 # +------------------+----------------------+-------------------------+
1114 1115 # | | one parent | merge |
1115 1116 # +------------------+----------------------+-------------------------+
1116 1117 # | parent in | new parent is <dest> | parents in ::<dest> are |
1117 1118 # | ::<dest> | | remapped to <dest> |
1118 1119 # +------------------+----------------------+-------------------------+
1119 1120 # | unrelated source | new parent is <dest> | ambiguous, abort |
1120 1121 # +------------------+----------------------+-------------------------+
1121 1122 #
1122 1123 # The actual abort is handled by `defineparents`
1123 1124 if len(root.parents()) <= 1:
1124 1125 # ancestors of <root> not ancestors of <dest>
1125 1126 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1126 1127 [root.rev()]))
1127 1128 for r in detachset:
1128 1129 if r not in state:
1129 1130 state[r] = nullmerge
1130 1131 if len(roots) > 1:
1131 1132 # If we have multiple roots, we may have "hole" in the rebase set.
1132 1133 # Rebase roots that descend from those "hole" should not be detached as
1133 1134 # other root are. We use the special `revignored` to inform rebase that
1134 1135 # the revision should be ignored but that `defineparents` should search
1135 1136 # a rebase destination that make sense regarding rebased topology.
1136 1137 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1137 1138 for ignored in set(rebasedomain) - set(rebaseset):
1138 1139 state[ignored] = revignored
1139 1140 for r in obsoletenotrebased:
1140 1141 if obsoletenotrebased[r] is None:
1141 1142 state[r] = revpruned
1142 1143 else:
1143 1144 state[r] = revprecursor
1144 1145 return repo['.'].rev(), dest.rev(), state
1145 1146
1146 1147 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1147 1148 """dispose of rebased revision at the end of the rebase
1148 1149
1149 1150 If `collapsedas` is not None, the rebase was a collapse whose result if the
1150 1151 `collapsedas` node."""
1151 1152 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1152 1153 markers = []
1153 1154 for rev, newrev in sorted(state.items()):
1154 1155 if newrev >= 0:
1155 1156 if rev in skipped:
1156 1157 succs = ()
1157 1158 elif collapsedas is not None:
1158 1159 succs = (repo[collapsedas],)
1159 1160 else:
1160 1161 succs = (repo[newrev],)
1161 1162 markers.append((repo[rev], succs))
1162 1163 if markers:
1163 1164 obsolete.createmarkers(repo, markers)
1164 1165 else:
1165 1166 rebased = [rev for rev in state if state[rev] > nullmerge]
1166 1167 if rebased:
1167 1168 stripped = []
1168 1169 for root in repo.set('roots(%ld)', rebased):
1169 1170 if set(repo.changelog.descendants([root.rev()])) - set(state):
1170 1171 ui.warn(_("warning: new changesets detected "
1171 1172 "on source branch, not stripping\n"))
1172 1173 else:
1173 1174 stripped.append(root.node())
1174 1175 if stripped:
1175 1176 # backup the old csets by default
1176 1177 repair.strip(ui, repo, stripped, "all")
1177 1178
1178 1179
1179 1180 def pullrebase(orig, ui, repo, *args, **opts):
1180 1181 'Call rebase after pull if the latter has been invoked with --rebase'
1181 1182 ret = None
1182 1183 if opts.get('rebase'):
1183 1184 wlock = lock = None
1184 1185 try:
1185 1186 wlock = repo.wlock()
1186 1187 lock = repo.lock()
1187 1188 if opts.get('update'):
1188 1189 del opts['update']
1189 1190 ui.debug('--update and --rebase are not compatible, ignoring '
1190 1191 'the update flag\n')
1191 1192
1192 1193 revsprepull = len(repo)
1193 1194 origpostincoming = commands.postincoming
1194 1195 def _dummy(*args, **kwargs):
1195 1196 pass
1196 1197 commands.postincoming = _dummy
1197 1198 try:
1198 1199 ret = orig(ui, repo, *args, **opts)
1199 1200 finally:
1200 1201 commands.postincoming = origpostincoming
1201 1202 revspostpull = len(repo)
1202 1203 if revspostpull > revsprepull:
1203 1204 # --rev option from pull conflict with rebase own --rev
1204 1205 # dropping it
1205 1206 if 'rev' in opts:
1206 1207 del opts['rev']
1207 1208 # positional argument from pull conflicts with rebase's own
1208 1209 # --source.
1209 1210 if 'source' in opts:
1210 1211 del opts['source']
1211 1212 try:
1212 1213 rebase(ui, repo, **opts)
1213 1214 except error.NoMergeDestAbort:
1214 1215 # we can maybe update instead
1215 1216 rev, _a, _b = destutil.destupdate(repo)
1216 1217 if rev == repo['.'].rev():
1217 1218 ui.status(_('nothing to rebase\n'))
1218 1219 else:
1219 1220 ui.status(_('nothing to rebase - updating instead\n'))
1220 1221 # not passing argument to get the bare update behavior
1221 1222 # with warning and trumpets
1222 1223 commands.update(ui, repo)
1223 1224 finally:
1224 1225 release(lock, wlock)
1225 1226 else:
1226 1227 if opts.get('tool'):
1227 1228 raise error.Abort(_('--tool can only be used with --rebase'))
1228 1229 ret = orig(ui, repo, *args, **opts)
1229 1230
1230 1231 return ret
1231 1232
1232 1233 def _setrebasesetvisibility(repo, revs):
1233 1234 """store the currently rebased set on the repo object
1234 1235
1235 1236 This is used by another function to prevent rebased revision to because
1236 1237 hidden (see issue4505)"""
1237 1238 repo = repo.unfiltered()
1238 1239 revs = set(revs)
1239 1240 repo._rebaseset = revs
1240 1241 # invalidate cache if visibility changes
1241 1242 hiddens = repo.filteredrevcache.get('visible', set())
1242 1243 if revs & hiddens:
1243 1244 repo.invalidatevolatilesets()
1244 1245
1245 1246 def _clearrebasesetvisibiliy(repo):
1246 1247 """remove rebaseset data from the repo"""
1247 1248 repo = repo.unfiltered()
1248 1249 if '_rebaseset' in vars(repo):
1249 1250 del repo._rebaseset
1250 1251
1251 1252 def _rebasedvisible(orig, repo):
1252 1253 """ensure rebased revs stay visible (see issue4505)"""
1253 1254 blockers = orig(repo)
1254 1255 blockers.update(getattr(repo, '_rebaseset', ()))
1255 1256 return blockers
1256 1257
1257 1258 def _filterobsoleterevs(repo, revs):
1258 1259 """returns a set of the obsolete revisions in revs"""
1259 1260 return set(r for r in revs if repo[r].obsolete())
1260 1261
1261 1262 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1262 1263 """return a mapping obsolete => successor for all obsolete nodes to be
1263 1264 rebased that have a successors in the destination
1264 1265
1265 1266 obsolete => None entries in the mapping indicate nodes with no succesor"""
1266 1267 obsoletenotrebased = {}
1267 1268
1268 1269 # Build a mapping successor => obsolete nodes for the obsolete
1269 1270 # nodes to be rebased
1270 1271 allsuccessors = {}
1271 1272 cl = repo.changelog
1272 1273 for r in rebaseobsrevs:
1273 1274 node = cl.node(r)
1274 1275 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1275 1276 try:
1276 1277 allsuccessors[cl.rev(s)] = cl.rev(node)
1277 1278 except LookupError:
1278 1279 pass
1279 1280
1280 1281 if allsuccessors:
1281 1282 # Look for successors of obsolete nodes to be rebased among
1282 1283 # the ancestors of dest
1283 1284 ancs = cl.ancestors([repo[dest].rev()],
1284 1285 stoprev=min(allsuccessors),
1285 1286 inclusive=True)
1286 1287 for s in allsuccessors:
1287 1288 if s in ancs:
1288 1289 obsoletenotrebased[allsuccessors[s]] = s
1289 1290 elif (s == allsuccessors[s] and
1290 1291 allsuccessors.values().count(s) == 1):
1291 1292 # plain prune
1292 1293 obsoletenotrebased[s] = None
1293 1294
1294 1295 return obsoletenotrebased
1295 1296
1296 1297 def summaryhook(ui, repo):
1297 1298 if not os.path.exists(repo.join('rebasestate')):
1298 1299 return
1299 1300 try:
1300 1301 state = restorestatus(repo)[2]
1301 1302 except error.RepoLookupError:
1302 1303 # i18n: column positioning for "hg summary"
1303 1304 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1304 1305 ui.write(msg)
1305 1306 return
1306 1307 numrebased = len([i for i in state.itervalues() if i >= 0])
1307 1308 # i18n: column positioning for "hg summary"
1308 1309 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1309 1310 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1310 1311 ui.label(_('%d remaining'), 'rebase.remaining') %
1311 1312 (len(state) - numrebased)))
1312 1313
1313 1314 def uisetup(ui):
1314 1315 #Replace pull with a decorator to provide --rebase option
1315 1316 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1316 1317 entry[1].append(('', 'rebase', None,
1317 1318 _("rebase working directory to branch head")))
1318 1319 entry[1].append(('t', 'tool', '',
1319 1320 _("specify merge tool for rebase")))
1320 1321 cmdutil.summaryhooks.add('rebase', summaryhook)
1321 1322 cmdutil.unfinishedstates.append(
1322 1323 ['rebasestate', False, False, _('rebase in progress'),
1323 1324 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1324 1325 cmdutil.afterresolvedstates.append(
1325 1326 ['rebasestate', _('hg rebase --continue')])
1326 1327 # ensure rebased rev are not hidden
1327 1328 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
@@ -1,807 +1,807 b''
1 1 ==========================
2 2 Test rebase with obsolete
3 3 ==========================
4 4
5 5 Enable obsolete
6 6
7 7 $ cat >> $HGRCPATH << EOF
8 8 > [ui]
9 9 > logtemplate= {rev}:{node|short} {desc|firstline}
10 10 > [experimental]
11 11 > evolution=createmarkers,allowunstable
12 12 > [phases]
13 13 > publish=False
14 14 > [extensions]
15 15 > rebase=
16 16 > EOF
17 17
18 18 Setup rebase canonical repo
19 19
20 20 $ hg init base
21 21 $ cd base
22 22 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
23 23 adding changesets
24 24 adding manifests
25 25 adding file changes
26 26 added 8 changesets with 7 changes to 7 files (+2 heads)
27 27 (run 'hg heads' to see heads, 'hg merge' to merge)
28 28 $ hg up tip
29 29 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30 $ hg log -G
31 31 @ 7:02de42196ebe H
32 32 |
33 33 | o 6:eea13746799a G
34 34 |/|
35 35 o | 5:24b6387c8c8c F
36 36 | |
37 37 | o 4:9520eea781bc E
38 38 |/
39 39 | o 3:32af7686d403 D
40 40 | |
41 41 | o 2:5fddd98957c8 C
42 42 | |
43 43 | o 1:42ccdea3bb16 B
44 44 |/
45 45 o 0:cd010b8cd998 A
46 46
47 47 $ cd ..
48 48
49 49 simple rebase
50 50 ---------------------------------
51 51
52 52 $ hg clone base simple
53 53 updating to branch default
54 54 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 55 $ cd simple
56 56 $ hg up 32af7686d403
57 57 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
58 58 $ hg rebase -d eea13746799a
59 59 rebasing 1:42ccdea3bb16 "B"
60 60 rebasing 2:5fddd98957c8 "C"
61 61 rebasing 3:32af7686d403 "D"
62 62 $ hg log -G
63 63 @ 10:8eeb3c33ad33 D
64 64 |
65 65 o 9:2327fea05063 C
66 66 |
67 67 o 8:e4e5be0395b2 B
68 68 |
69 69 | o 7:02de42196ebe H
70 70 | |
71 71 o | 6:eea13746799a G
72 72 |\|
73 73 | o 5:24b6387c8c8c F
74 74 | |
75 75 o | 4:9520eea781bc E
76 76 |/
77 77 o 0:cd010b8cd998 A
78 78
79 79 $ hg log --hidden -G
80 80 @ 10:8eeb3c33ad33 D
81 81 |
82 82 o 9:2327fea05063 C
83 83 |
84 84 o 8:e4e5be0395b2 B
85 85 |
86 86 | o 7:02de42196ebe H
87 87 | |
88 88 o | 6:eea13746799a G
89 89 |\|
90 90 | o 5:24b6387c8c8c F
91 91 | |
92 92 o | 4:9520eea781bc E
93 93 |/
94 94 | x 3:32af7686d403 D
95 95 | |
96 96 | x 2:5fddd98957c8 C
97 97 | |
98 98 | x 1:42ccdea3bb16 B
99 99 |/
100 100 o 0:cd010b8cd998 A
101 101
102 102 $ hg debugobsolete
103 103 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (*) {'user': 'test'} (glob)
104 104 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (*) {'user': 'test'} (glob)
105 105 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (*) {'user': 'test'} (glob)
106 106
107 107
108 108 $ cd ..
109 109
110 110 empty changeset
111 111 ---------------------------------
112 112
113 113 $ hg clone base empty
114 114 updating to branch default
115 115 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 116 $ cd empty
117 117 $ hg up eea13746799a
118 118 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
119 119
120 120 We make a copy of both the first changeset in the rebased and some other in the
121 121 set.
122 122
123 123 $ hg graft 42ccdea3bb16 32af7686d403
124 124 grafting 1:42ccdea3bb16 "B"
125 125 grafting 3:32af7686d403 "D"
126 126 $ hg rebase -s 42ccdea3bb16 -d .
127 127 rebasing 1:42ccdea3bb16 "B"
128 128 note: rebase of 1:42ccdea3bb16 created no changes to commit
129 129 rebasing 2:5fddd98957c8 "C"
130 130 rebasing 3:32af7686d403 "D"
131 131 note: rebase of 3:32af7686d403 created no changes to commit
132 132 $ hg log -G
133 133 o 10:5ae4c968c6ac C
134 134 |
135 135 @ 9:08483444fef9 D
136 136 |
137 137 o 8:8877864f1edb B
138 138 |
139 139 | o 7:02de42196ebe H
140 140 | |
141 141 o | 6:eea13746799a G
142 142 |\|
143 143 | o 5:24b6387c8c8c F
144 144 | |
145 145 o | 4:9520eea781bc E
146 146 |/
147 147 o 0:cd010b8cd998 A
148 148
149 149 $ hg log --hidden -G
150 150 o 10:5ae4c968c6ac C
151 151 |
152 152 @ 9:08483444fef9 D
153 153 |
154 154 o 8:8877864f1edb B
155 155 |
156 156 | o 7:02de42196ebe H
157 157 | |
158 158 o | 6:eea13746799a G
159 159 |\|
160 160 | o 5:24b6387c8c8c F
161 161 | |
162 162 o | 4:9520eea781bc E
163 163 |/
164 164 | x 3:32af7686d403 D
165 165 | |
166 166 | x 2:5fddd98957c8 C
167 167 | |
168 168 | x 1:42ccdea3bb16 B
169 169 |/
170 170 o 0:cd010b8cd998 A
171 171
172 172 $ hg debugobsolete
173 173 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
174 174 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
175 175 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
176 176
177 177
178 178 More complex case were part of the rebase set were already rebased
179 179
180 180 $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
181 181 rebasing 9:08483444fef9 "D"
182 182 $ hg debugobsolete
183 183 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
184 184 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
185 185 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
186 186 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
187 187 $ hg log -G
188 188 @ 11:4596109a6a43 D
189 189 |
190 190 | o 10:5ae4c968c6ac C
191 191 | |
192 192 | x 9:08483444fef9 D
193 193 | |
194 194 | o 8:8877864f1edb B
195 195 | |
196 196 o | 7:02de42196ebe H
197 197 | |
198 198 | o 6:eea13746799a G
199 199 |/|
200 200 o | 5:24b6387c8c8c F
201 201 | |
202 202 | o 4:9520eea781bc E
203 203 |/
204 204 o 0:cd010b8cd998 A
205 205
206 206 $ hg rebase --source 'desc(B)' --dest 'tip' --config experimental.rebaseskipobsolete=True
207 207 rebasing 8:8877864f1edb "B"
208 208 note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D"
209 209 rebasing 10:5ae4c968c6ac "C"
210 210 $ hg debugobsolete
211 211 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
212 212 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
213 213 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
214 214 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
215 215 8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
216 216 5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
217 217 $ hg log --rev 'divergent()'
218 218 $ hg log -G
219 219 o 13:98f6af4ee953 C
220 220 |
221 221 o 12:462a34d07e59 B
222 222 |
223 223 @ 11:4596109a6a43 D
224 224 |
225 225 o 7:02de42196ebe H
226 226 |
227 227 | o 6:eea13746799a G
228 228 |/|
229 229 o | 5:24b6387c8c8c F
230 230 | |
231 231 | o 4:9520eea781bc E
232 232 |/
233 233 o 0:cd010b8cd998 A
234 234
235 235 $ hg log --style default --debug -r 4596109a6a4328c398bde3a4a3b6737cfade3003
236 236 changeset: 11:4596109a6a4328c398bde3a4a3b6737cfade3003
237 237 phase: draft
238 238 parent: 7:02de42196ebee42ef284b6780a87cdc96e8eaab6
239 239 parent: -1:0000000000000000000000000000000000000000
240 240 manifest: 11:a91006e3a02f1edf631f7018e6e5684cf27dd905
241 241 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
242 242 date: Sat Apr 30 15:24:48 2011 +0200
243 243 files+: D
244 244 extra: branch=default
245 245 extra: rebase_source=08483444fef91d6224f6655ee586a65d263ad34c
246 246 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
247 247 description:
248 248 D
249 249
250 250
251 251 $ hg up -qr 'desc(G)'
252 252 $ hg graft 4596109a6a4328c398bde3a4a3b6737cfade3003
253 253 grafting 11:4596109a6a43 "D"
254 254 $ hg up -qr 'desc(E)'
255 255 $ hg rebase -s tip -d .
256 256 rebasing 14:9e36056a46e3 "D" (tip)
257 257 $ hg log --style default --debug -r tip
258 258 changeset: 15:627d4614809036ba22b9e7cb31638ddc06ab99ab
259 259 tag: tip
260 260 phase: draft
261 261 parent: 4:9520eea781bcca16c1e15acc0ba14335a0e8e5ba
262 262 parent: -1:0000000000000000000000000000000000000000
263 263 manifest: 15:648e8ede73ae3e497d093d3a4c8fcc2daa864f42
264 264 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
265 265 date: Sat Apr 30 15:24:48 2011 +0200
266 266 files+: D
267 267 extra: branch=default
268 268 extra: intermediate-source=4596109a6a4328c398bde3a4a3b6737cfade3003
269 269 extra: rebase_source=9e36056a46e37c9776168c7375734eebc70e294f
270 270 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
271 271 description:
272 272 D
273 273
274 274
275 275 $ cd ..
276 276
277 277 collapse rebase
278 278 ---------------------------------
279 279
280 280 $ hg clone base collapse
281 281 updating to branch default
282 282 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
283 283 $ cd collapse
284 284 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
285 285 rebasing 1:42ccdea3bb16 "B"
286 286 rebasing 2:5fddd98957c8 "C"
287 287 rebasing 3:32af7686d403 "D"
288 288 $ hg log -G
289 289 o 8:4dc2197e807b Collapsed revision
290 290 |
291 291 | @ 7:02de42196ebe H
292 292 | |
293 293 o | 6:eea13746799a G
294 294 |\|
295 295 | o 5:24b6387c8c8c F
296 296 | |
297 297 o | 4:9520eea781bc E
298 298 |/
299 299 o 0:cd010b8cd998 A
300 300
301 301 $ hg log --hidden -G
302 302 o 8:4dc2197e807b Collapsed revision
303 303 |
304 304 | @ 7:02de42196ebe H
305 305 | |
306 306 o | 6:eea13746799a G
307 307 |\|
308 308 | o 5:24b6387c8c8c F
309 309 | |
310 310 o | 4:9520eea781bc E
311 311 |/
312 312 | x 3:32af7686d403 D
313 313 | |
314 314 | x 2:5fddd98957c8 C
315 315 | |
316 316 | x 1:42ccdea3bb16 B
317 317 |/
318 318 o 0:cd010b8cd998 A
319 319
320 320 $ hg id --debug -r tip
321 321 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
322 322 $ hg debugobsolete
323 323 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
324 324 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
325 325 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
326 326
327 327 $ cd ..
328 328
329 329 Rebase set has hidden descendants
330 330 ---------------------------------
331 331
332 332 We rebase a changeset which has a hidden changeset. The hidden changeset must
333 333 not be rebased.
334 334
335 335 $ hg clone base hidden
336 336 updating to branch default
337 337 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
338 338 $ cd hidden
339 339 $ hg rebase -s 5fddd98957c8 -d eea13746799a
340 340 rebasing 2:5fddd98957c8 "C"
341 341 rebasing 3:32af7686d403 "D"
342 342 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
343 343 rebasing 1:42ccdea3bb16 "B"
344 344 $ hg log -G
345 345 o 10:7c6027df6a99 B
346 346 |
347 347 | o 9:cf44d2f5a9f4 D
348 348 | |
349 349 | o 8:e273c5e7d2d2 C
350 350 | |
351 351 @ | 7:02de42196ebe H
352 352 | |
353 353 | o 6:eea13746799a G
354 354 |/|
355 355 o | 5:24b6387c8c8c F
356 356 | |
357 357 | o 4:9520eea781bc E
358 358 |/
359 359 o 0:cd010b8cd998 A
360 360
361 361 $ hg log --hidden -G
362 362 o 10:7c6027df6a99 B
363 363 |
364 364 | o 9:cf44d2f5a9f4 D
365 365 | |
366 366 | o 8:e273c5e7d2d2 C
367 367 | |
368 368 @ | 7:02de42196ebe H
369 369 | |
370 370 | o 6:eea13746799a G
371 371 |/|
372 372 o | 5:24b6387c8c8c F
373 373 | |
374 374 | o 4:9520eea781bc E
375 375 |/
376 376 | x 3:32af7686d403 D
377 377 | |
378 378 | x 2:5fddd98957c8 C
379 379 | |
380 380 | x 1:42ccdea3bb16 B
381 381 |/
382 382 o 0:cd010b8cd998 A
383 383
384 384 $ hg debugobsolete
385 385 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (*) {'user': 'test'} (glob)
386 386 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (*) {'user': 'test'} (glob)
387 387 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (*) {'user': 'test'} (glob)
388 388
389 389 Test that rewriting leaving instability behind is allowed
390 390 ---------------------------------------------------------------------
391 391
392 392 $ hg log -r 'children(8)'
393 393 9:cf44d2f5a9f4 D (no-eol)
394 394 $ hg rebase -r 8
395 395 rebasing 8:e273c5e7d2d2 "C"
396 396 $ hg log -G
397 397 o 11:0d8f238b634c C
398 398 |
399 399 o 10:7c6027df6a99 B
400 400 |
401 401 | o 9:cf44d2f5a9f4 D
402 402 | |
403 403 | x 8:e273c5e7d2d2 C
404 404 | |
405 405 @ | 7:02de42196ebe H
406 406 | |
407 407 | o 6:eea13746799a G
408 408 |/|
409 409 o | 5:24b6387c8c8c F
410 410 | |
411 411 | o 4:9520eea781bc E
412 412 |/
413 413 o 0:cd010b8cd998 A
414 414
415 415
416 416
417 417 Test multiple root handling
418 418 ------------------------------------
419 419
420 420 $ hg rebase --dest 4 --rev '7+11+9'
421 421 rebasing 7:02de42196ebe "H"
422 422 rebasing 9:cf44d2f5a9f4 "D"
423 423 not rebasing ignored 10:7c6027df6a99 "B"
424 424 rebasing 11:0d8f238b634c "C" (tip)
425 425 $ hg log -G
426 426 o 14:1e8370e38cca C
427 427 |
428 428 | o 13:102b4c1d889b D
429 429 | |
430 430 @ | 12:bfe264faf697 H
431 431 |/
432 432 | o 10:7c6027df6a99 B
433 433 | |
434 434 | x 7:02de42196ebe H
435 435 | |
436 436 +---o 6:eea13746799a G
437 437 | |/
438 438 | o 5:24b6387c8c8c F
439 439 | |
440 440 o | 4:9520eea781bc E
441 441 |/
442 442 o 0:cd010b8cd998 A
443 443
444 444 $ cd ..
445 445
446 446 test on rebase dropping a merge
447 447
448 448 (setup)
449 449
450 450 $ hg init dropmerge
451 451 $ cd dropmerge
452 452 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
453 453 adding changesets
454 454 adding manifests
455 455 adding file changes
456 456 added 8 changesets with 7 changes to 7 files (+2 heads)
457 457 (run 'hg heads' to see heads, 'hg merge' to merge)
458 458 $ hg up 3
459 459 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
460 460 $ hg merge 7
461 461 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
462 462 (branch merge, don't forget to commit)
463 463 $ hg ci -m 'M'
464 464 $ echo I > I
465 465 $ hg add I
466 466 $ hg ci -m I
467 467 $ hg log -G
468 468 @ 9:4bde274eefcf I
469 469 |
470 470 o 8:53a6a128b2b7 M
471 471 |\
472 472 | o 7:02de42196ebe H
473 473 | |
474 474 | | o 6:eea13746799a G
475 475 | |/|
476 476 | o | 5:24b6387c8c8c F
477 477 | | |
478 478 | | o 4:9520eea781bc E
479 479 | |/
480 480 o | 3:32af7686d403 D
481 481 | |
482 482 o | 2:5fddd98957c8 C
483 483 | |
484 484 o | 1:42ccdea3bb16 B
485 485 |/
486 486 o 0:cd010b8cd998 A
487 487
488 488 (actual test)
489 489
490 490 $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
491 491 rebasing 3:32af7686d403 "D"
492 492 rebasing 7:02de42196ebe "H"
493 493 not rebasing ignored 8:53a6a128b2b7 "M"
494 494 rebasing 9:4bde274eefcf "I" (tip)
495 495 $ hg log -G
496 496 @ 12:acd174b7ab39 I
497 497 |
498 498 o 11:6c11a6218c97 H
499 499 |
500 500 | o 10:b5313c85b22e D
501 501 |/
502 502 | o 8:53a6a128b2b7 M
503 503 | |\
504 504 | | x 7:02de42196ebe H
505 505 | | |
506 506 o---+ 6:eea13746799a G
507 507 | | |
508 508 | | o 5:24b6387c8c8c F
509 509 | | |
510 510 o---+ 4:9520eea781bc E
511 511 / /
512 512 x | 3:32af7686d403 D
513 513 | |
514 514 o | 2:5fddd98957c8 C
515 515 | |
516 516 o | 1:42ccdea3bb16 B
517 517 |/
518 518 o 0:cd010b8cd998 A
519 519
520 520
521 521 Test hidden changesets in the rebase set (issue4504)
522 522
523 523 $ hg up --hidden 9
524 524 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
525 525 $ echo J > J
526 526 $ hg add J
527 527 $ hg commit -m J
528 528 $ hg debugobsolete `hg log --rev . -T '{node}'`
529 529
530 $ hg rebase --rev .~1::. --dest 'max(desc(D))' --traceback
530 $ hg rebase --rev .~1::. --dest 'max(desc(D))' --traceback --config experimental.rebaseskipobsolete=off
531 531 rebasing 9:4bde274eefcf "I"
532 532 rebasing 13:06edfc82198f "J" (tip)
533 533 $ hg log -G
534 534 @ 15:5ae8a643467b J
535 535 |
536 536 o 14:9ad579b4a5de I
537 537 |
538 538 | o 12:acd174b7ab39 I
539 539 | |
540 540 | o 11:6c11a6218c97 H
541 541 | |
542 542 o | 10:b5313c85b22e D
543 543 |/
544 544 | o 8:53a6a128b2b7 M
545 545 | |\
546 546 | | x 7:02de42196ebe H
547 547 | | |
548 548 o---+ 6:eea13746799a G
549 549 | | |
550 550 | | o 5:24b6387c8c8c F
551 551 | | |
552 552 o---+ 4:9520eea781bc E
553 553 / /
554 554 x | 3:32af7686d403 D
555 555 | |
556 556 o | 2:5fddd98957c8 C
557 557 | |
558 558 o | 1:42ccdea3bb16 B
559 559 |/
560 560 o 0:cd010b8cd998 A
561 561
562 562 $ hg up 14 -C
563 563 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
564 564 $ echo "K" > K
565 565 $ hg add K
566 566 $ hg commit --amend -m "K"
567 567 $ echo "L" > L
568 568 $ hg add L
569 569 $ hg commit -m "L"
570 570 $ hg up '.^'
571 571 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
572 572 $ echo "M" > M
573 573 $ hg add M
574 574 $ hg commit --amend -m "M"
575 575 $ hg log -G
576 576 @ 20:bfaedf8eb73b M
577 577 |
578 578 | o 18:97219452e4bd L
579 579 | |
580 580 | x 17:fc37a630c901 K
581 581 |/
582 582 | o 15:5ae8a643467b J
583 583 | |
584 584 | x 14:9ad579b4a5de I
585 585 |/
586 586 | o 12:acd174b7ab39 I
587 587 | |
588 588 | o 11:6c11a6218c97 H
589 589 | |
590 590 o | 10:b5313c85b22e D
591 591 |/
592 592 | o 8:53a6a128b2b7 M
593 593 | |\
594 594 | | x 7:02de42196ebe H
595 595 | | |
596 596 o---+ 6:eea13746799a G
597 597 | | |
598 598 | | o 5:24b6387c8c8c F
599 599 | | |
600 600 o---+ 4:9520eea781bc E
601 601 / /
602 602 x | 3:32af7686d403 D
603 603 | |
604 604 o | 2:5fddd98957c8 C
605 605 | |
606 606 o | 1:42ccdea3bb16 B
607 607 |/
608 608 o 0:cd010b8cd998 A
609 609
610 610 $ hg rebase -s 14 -d 18 --config experimental.rebaseskipobsolete=True
611 611 note: not rebasing 14:9ad579b4a5de "I", already in destination as 17:fc37a630c901 "K"
612 612 rebasing 15:5ae8a643467b "J"
613 613
614 614 $ cd ..
615 615
616 616 Skip obsolete changeset even with multiple hops
617 617 -----------------------------------------------
618 618
619 619 setup
620 620
621 621 $ hg init obsskip
622 622 $ cd obsskip
623 623 $ cat << EOF >> .hg/hgrc
624 624 > [experimental]
625 625 > rebaseskipobsolete = True
626 626 > [extensions]
627 627 > strip =
628 628 > EOF
629 629 $ echo A > A
630 630 $ hg add A
631 631 $ hg commit -m A
632 632 $ echo B > B
633 633 $ hg add B
634 634 $ hg commit -m B0
635 635 $ hg commit --amend -m B1
636 636 $ hg commit --amend -m B2
637 637 $ hg up --hidden 'desc(B0)'
638 638 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
639 639 $ echo C > C
640 640 $ hg add C
641 641 $ hg commit -m C
642 642
643 643 Rebase finds its way in a chain of marker
644 644
645 645 $ hg rebase -d 'desc(B2)'
646 646 note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 3:261e70097290 "B2"
647 647 rebasing 4:212cb178bcbb "C" (tip)
648 648
649 649 Even when the chain include missing node
650 650
651 651 $ hg up --hidden 'desc(B0)'
652 652 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
653 653 $ echo D > D
654 654 $ hg add D
655 655 $ hg commit -m D
656 656 $ hg --hidden strip -r 'desc(B1)'
657 657 saved backup bundle to $TESTTMP/obsskip/.hg/strip-backup/86f6414ccda7-b1c452ee-backup.hg (glob)
658 658
659 659 $ hg rebase -d 'desc(B2)'
660 660 note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 2:261e70097290 "B2"
661 661 rebasing 5:1a79b7535141 "D" (tip)
662 662 $ hg up 4
663 663 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
664 664 $ echo "O" > O
665 665 $ hg add O
666 666 $ hg commit -m O
667 667 $ echo "P" > P
668 668 $ hg add P
669 669 $ hg commit -m P
670 670 $ hg log -G
671 671 @ 8:8d47583e023f P
672 672 |
673 673 o 7:360bbaa7d3ce O
674 674 |
675 675 | o 6:9c48361117de D
676 676 | |
677 677 o | 4:ff2c4d47b71d C
678 678 |/
679 679 o 2:261e70097290 B2
680 680 |
681 681 o 0:4a2df7238c3b A
682 682
683 683 $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=all
684 684 $ hg rebase -d 6 -r "4::"
685 685 rebasing 4:ff2c4d47b71d "C"
686 686 note: not rebasing 7:360bbaa7d3ce "O", it has no successor
687 687 rebasing 8:8d47583e023f "P" (tip)
688 688
689 689 If all the changeset to be rebased are obsolete and present in the destination, we
690 690 should display a friendly error message
691 691
692 692 $ hg log -G
693 693 @ 10:121d9e3bc4c6 P
694 694 |
695 695 o 9:4be60e099a77 C
696 696 |
697 697 o 6:9c48361117de D
698 698 |
699 699 o 2:261e70097290 B2
700 700 |
701 701 o 0:4a2df7238c3b A
702 702
703 703
704 704 $ hg up 9
705 705 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
706 706 $ echo "non-relevant change" > nonrelevant
707 707 $ hg add nonrelevant
708 708 $ hg commit -m nonrelevant
709 709 created new head
710 710 $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=all
711 711 $ hg rebase -r . -d 10
712 712 abort: all requested changesets have equivalents or were marked as obsolete
713 713 (to force the rebase, set the config experimental.rebaseskipobsolete to False)
714 714 [255]
715 715
716 716 If a rebase is going to create divergence, it should abort
717 717
718 718 $ hg log -G
719 719 @ 11:f44da1f4954c nonrelevant
720 720 |
721 721 | o 10:121d9e3bc4c6 P
722 722 |/
723 723 o 9:4be60e099a77 C
724 724 |
725 725 o 6:9c48361117de D
726 726 |
727 727 o 2:261e70097290 B2
728 728 |
729 729 o 0:4a2df7238c3b A
730 730
731 731
732 732 $ hg up 9
733 733 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
734 734 $ echo "john" > doe
735 735 $ hg add doe
736 736 $ hg commit -m "john doe"
737 737 created new head
738 738 $ hg up 10
739 739 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
740 740 $ echo "foo" > bar
741 741 $ hg add bar
742 742 $ hg commit --amend -m "10'"
743 743 $ hg up 10 --hidden
744 744 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
745 745 $ echo "bar" > foo
746 746 $ hg add foo
747 747 $ hg commit -m "bar foo"
748 748 $ hg log -G
749 749 @ 15:73568ab6879d bar foo
750 750 |
751 751 | o 14:77d874d096a2 10'
752 752 | |
753 753 | | o 12:3eb461388009 john doe
754 754 | |/
755 755 x | 10:121d9e3bc4c6 P
756 756 |/
757 757 o 9:4be60e099a77 C
758 758 |
759 759 o 6:9c48361117de D
760 760 |
761 761 o 2:261e70097290 B2
762 762 |
763 763 o 0:4a2df7238c3b A
764 764
765 765 $ hg summary
766 766 parent: 15:73568ab6879d tip
767 767 bar foo
768 768 branch: default
769 769 commit: (clean)
770 770 update: 2 new changesets, 3 branch heads (merge)
771 771 phases: 8 draft
772 772 unstable: 1 changesets
773 773 $ hg rebase -s 10 -d 12
774 774 abort: this rebase will cause divergences from: 121d9e3bc4c6
775 775 (to force the rebase please set experimental.allowdivergence=True)
776 776 [255]
777 777 $ hg log -G
778 778 @ 15:73568ab6879d bar foo
779 779 |
780 780 | o 14:77d874d096a2 10'
781 781 | |
782 782 | | o 12:3eb461388009 john doe
783 783 | |/
784 784 x | 10:121d9e3bc4c6 P
785 785 |/
786 786 o 9:4be60e099a77 C
787 787 |
788 788 o 6:9c48361117de D
789 789 |
790 790 o 2:261e70097290 B2
791 791 |
792 792 o 0:4a2df7238c3b A
793 793
794 794 With experimental.allowdivergence=True, rebase can create divergence
795 795
796 796 $ hg rebase -s 10 -d 12 --config experimental.allowdivergence=True
797 797 rebasing 10:121d9e3bc4c6 "P"
798 798 rebasing 15:73568ab6879d "bar foo" (tip)
799 799 $ hg summary
800 800 parent: 17:61bd55f69bc4 tip
801 801 bar foo
802 802 branch: default
803 803 commit: (clean)
804 804 update: 1 new changesets, 2 branch heads (merge)
805 805 phases: 8 draft
806 806 divergent: 2 changesets
807 807
General Comments 0
You need to be logged in to leave comments. Login now