##// END OF EJS Templates
rebase: fix crash when rebase aborts while rebasing obsolete revisions...
Laurent Charignon -
r28686:b212e01f default
parent child Browse files
Show More
@@ -1,1343 +1,1356
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
286 obsoletenotrebased = {}
287 if ui.configbool('experimental', 'rebaseskipobsolete',
288 default=True):
289 rebaseobsrevs = set([r for r, status in state.items()
290 if status == revprecursor])
291 rebasesetrevs = set(state.keys())
292 obsoletenotrebased = _computeobsoletenotrebased(repo,
293 rebaseobsrevs,
294 target)
295 rebaseobsskipped = set(obsoletenotrebased)
296 _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs,
297 rebaseobsskipped)
285 298 else:
286 299 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf)
287 300 if dest is None:
288 301 return _nothingtorebase()
289 302
290 303 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
291 304 if (not (keepf or allowunstable)
292 305 and repo.revs('first(children(%ld) - %ld)',
293 306 rebaseset, rebaseset)):
294 307 raise error.Abort(
295 308 _("can't remove original changesets with"
296 309 " unrebased descendants"),
297 310 hint=_('use --keep to keep original changesets'))
298 311
299 312 obsoletenotrebased = {}
300 313 if ui.configbool('experimental', 'rebaseskipobsolete',
301 314 default=True):
302 315 rebasesetrevs = set(rebaseset)
303 316 rebaseobsrevs = _filterobsoleterevs(repo, rebasesetrevs)
304 317 obsoletenotrebased = _computeobsoletenotrebased(repo,
305 318 rebaseobsrevs,
306 319 dest)
307 320 rebaseobsskipped = set(obsoletenotrebased)
308 321 _checkobsrebase(repo, ui, rebaseobsrevs,
309 322 rebasesetrevs,
310 323 rebaseobsskipped)
311 324
312 325 result = buildstate(repo, dest, rebaseset, collapsef,
313 326 obsoletenotrebased)
314 327
315 328 if not result:
316 329 # Empty state built, nothing to rebase
317 330 ui.status(_('nothing to rebase\n'))
318 331 return _nothingtorebase()
319 332
320 333 root = min(rebaseset)
321 334 if not keepf and not repo[root].mutable():
322 335 raise error.Abort(_("can't rebase public changeset %s")
323 336 % repo[root],
324 337 hint=_('see "hg help phases" for details'))
325 338
326 339 originalwd, target, state = result
327 340 if collapsef:
328 341 targetancestors = repo.changelog.ancestors([target],
329 342 inclusive=True)
330 343 external = externalparent(repo, state, targetancestors)
331 344
332 345 if dest.closesbranch() and not keepbranchesf:
333 346 ui.status(_('reopening closed branch head %s\n') % dest)
334 347
335 348 if keepbranchesf:
336 349 # insert _savebranch at the start of extrafns so if
337 350 # there's a user-provided extrafn it can clobber branch if
338 351 # desired
339 352 extrafns.insert(0, _savebranch)
340 353 if collapsef:
341 354 branches = set()
342 355 for rev in state:
343 356 branches.add(repo[rev].branch())
344 357 if len(branches) > 1:
345 358 raise error.Abort(_('cannot collapse multiple named '
346 359 'branches'))
347 360
348 361 # Rebase
349 362 if not targetancestors:
350 363 targetancestors = repo.changelog.ancestors([target], inclusive=True)
351 364
352 365 # Keep track of the current bookmarks in order to reset them later
353 366 currentbookmarks = repo._bookmarks.copy()
354 367 activebookmark = activebookmark or repo._activebookmark
355 368 if activebookmark:
356 369 bookmarks.deactivate(repo)
357 370
358 371 extrafn = _makeextrafn(extrafns)
359 372
360 373 sortedstate = sorted(state)
361 374 total = len(sortedstate)
362 375 pos = 0
363 376 for rev in sortedstate:
364 377 ctx = repo[rev]
365 378 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
366 379 ctx.description().split('\n', 1)[0])
367 380 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
368 381 if names:
369 382 desc += ' (%s)' % ' '.join(names)
370 383 pos += 1
371 384 if state[rev] == revtodo:
372 385 ui.status(_('rebasing %s\n') % desc)
373 386 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
374 387 _('changesets'), total)
375 388 p1, p2, base = defineparents(repo, rev, target, state,
376 389 targetancestors)
377 390 storestatus(repo, originalwd, target, state, collapsef, keepf,
378 391 keepbranchesf, external, activebookmark)
379 392 storecollapsemsg(repo, collapsemsg)
380 393 if len(repo[None].parents()) == 2:
381 394 repo.ui.debug('resuming interrupted rebase\n')
382 395 else:
383 396 try:
384 397 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
385 398 'rebase')
386 399 stats = rebasenode(repo, rev, p1, base, state,
387 400 collapsef, target)
388 401 if stats and stats[3] > 0:
389 402 raise error.InterventionRequired(
390 403 _('unresolved conflicts (see hg '
391 404 'resolve, then hg rebase --continue)'))
392 405 finally:
393 406 ui.setconfig('ui', 'forcemerge', '', 'rebase')
394 407 if not collapsef:
395 408 merging = p2 != nullrev
396 409 editform = cmdutil.mergeeditform(merging, 'rebase')
397 410 editor = cmdutil.getcommiteditor(editform=editform, **opts)
398 411 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
399 412 editor=editor,
400 413 keepbranches=keepbranchesf,
401 414 date=date)
402 415 else:
403 416 # Skip commit if we are collapsing
404 417 repo.dirstate.beginparentchange()
405 418 repo.setparents(repo[p1].node())
406 419 repo.dirstate.endparentchange()
407 420 newnode = None
408 421 # Update the state
409 422 if newnode is not None:
410 423 state[rev] = repo[newnode].rev()
411 424 ui.debug('rebased as %s\n' % short(newnode))
412 425 else:
413 426 if not collapsef:
414 427 ui.warn(_('note: rebase of %d:%s created no changes '
415 428 'to commit\n') % (rev, ctx))
416 429 skipped.add(rev)
417 430 state[rev] = p1
418 431 ui.debug('next revision set to %s\n' % p1)
419 432 elif state[rev] == nullmerge:
420 433 ui.debug('ignoring null merge rebase of %s\n' % rev)
421 434 elif state[rev] == revignored:
422 435 ui.status(_('not rebasing ignored %s\n') % desc)
423 436 elif state[rev] == revprecursor:
424 437 targetctx = repo[obsoletenotrebased[rev]]
425 438 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
426 439 targetctx.description().split('\n', 1)[0])
427 440 msg = _('note: not rebasing %s, already in destination as %s\n')
428 441 ui.status(msg % (desc, desctarget))
429 442 elif state[rev] == revpruned:
430 443 msg = _('note: not rebasing %s, it has no successor\n')
431 444 ui.status(msg % desc)
432 445 else:
433 446 ui.status(_('already rebased %s as %s\n') %
434 447 (desc, repo[state[rev]]))
435 448
436 449 ui.progress(_('rebasing'), None)
437 450 ui.note(_('rebase merging completed\n'))
438 451
439 452 if collapsef and not keepopen:
440 453 p1, p2, _base = defineparents(repo, min(state), target,
441 454 state, targetancestors)
442 455 editopt = opts.get('edit')
443 456 editform = 'rebase.collapse'
444 457 if collapsemsg:
445 458 commitmsg = collapsemsg
446 459 else:
447 460 commitmsg = 'Collapsed revision'
448 461 for rebased in state:
449 462 if rebased not in skipped and state[rebased] > nullmerge:
450 463 commitmsg += '\n* %s' % repo[rebased].description()
451 464 editopt = True
452 465 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
453 466 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
454 467 extrafn=extrafn, editor=editor,
455 468 keepbranches=keepbranchesf,
456 469 date=date)
457 470 if newnode is None:
458 471 newrev = target
459 472 else:
460 473 newrev = repo[newnode].rev()
461 474 for oldrev in state.iterkeys():
462 475 if state[oldrev] > nullmerge:
463 476 state[oldrev] = newrev
464 477
465 478 if 'qtip' in repo.tags():
466 479 updatemq(repo, state, skipped, **opts)
467 480
468 481 if currentbookmarks:
469 482 # Nodeids are needed to reset bookmarks
470 483 nstate = {}
471 484 for k, v in state.iteritems():
472 485 if v > nullmerge:
473 486 nstate[repo[k].node()] = repo[v].node()
474 487 # XXX this is the same as dest.node() for the non-continue path --
475 488 # this should probably be cleaned up
476 489 targetnode = repo[target].node()
477 490
478 491 # restore original working directory
479 492 # (we do this before stripping)
480 493 newwd = state.get(originalwd, originalwd)
481 494 if newwd < 0:
482 495 # original directory is a parent of rebase set root or ignored
483 496 newwd = originalwd
484 497 if newwd not in [c.rev() for c in repo[None].parents()]:
485 498 ui.note(_("update back to initial working directory parent\n"))
486 499 hg.updaterepo(repo, newwd, False)
487 500
488 501 if not keepf:
489 502 collapsedas = None
490 503 if collapsef:
491 504 collapsedas = newnode
492 505 clearrebased(ui, repo, state, skipped, collapsedas)
493 506
494 507 with repo.transaction('bookmark') as tr:
495 508 if currentbookmarks:
496 509 updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr)
497 510 if activebookmark not in repo._bookmarks:
498 511 # active bookmark was divergent one and has been deleted
499 512 activebookmark = None
500 513 clearstatus(repo)
501 514 clearcollapsemsg(repo)
502 515
503 516 ui.note(_("rebase completed\n"))
504 517 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
505 518 if skipped:
506 519 ui.note(_("%d revisions have been skipped\n") % len(skipped))
507 520
508 521 if (activebookmark and
509 522 repo['.'].node() == repo._bookmarks[activebookmark]):
510 523 bookmarks.activate(repo, activebookmark)
511 524
512 525 finally:
513 526 release(lock, wlock)
514 527
515 528 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[]):
516 529 """use revisions argument to define destination and rebase set
517 530 """
518 531 if srcf and basef:
519 532 raise error.Abort(_('cannot specify both a source and a base'))
520 533 if revf and basef:
521 534 raise error.Abort(_('cannot specify both a revision and a base'))
522 535 if revf and srcf:
523 536 raise error.Abort(_('cannot specify both a revision and a source'))
524 537
525 538 cmdutil.checkunfinished(repo)
526 539 cmdutil.bailifchanged(repo)
527 540
528 541 if destf:
529 542 dest = scmutil.revsingle(repo, destf)
530 543
531 544 if revf:
532 545 rebaseset = scmutil.revrange(repo, revf)
533 546 if not rebaseset:
534 547 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
535 548 return None, None
536 549 elif srcf:
537 550 src = scmutil.revrange(repo, [srcf])
538 551 if not src:
539 552 ui.status(_('empty "source" revision set - nothing to rebase\n'))
540 553 return None, None
541 554 rebaseset = repo.revs('(%ld)::', src)
542 555 assert rebaseset
543 556 else:
544 557 base = scmutil.revrange(repo, [basef or '.'])
545 558 if not base:
546 559 ui.status(_('empty "base" revision set - '
547 560 "can't compute rebase set\n"))
548 561 return None, None
549 562 if not destf:
550 563 dest = repo[_destrebase(repo, base)]
551 564 destf = str(dest)
552 565
553 566 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
554 567 if commonanc is not None:
555 568 rebaseset = repo.revs('(%d::(%ld) - %d)::',
556 569 commonanc, base, commonanc)
557 570 else:
558 571 rebaseset = []
559 572
560 573 if not rebaseset:
561 574 # transform to list because smartsets are not comparable to
562 575 # lists. This should be improved to honor laziness of
563 576 # smartset.
564 577 if list(base) == [dest.rev()]:
565 578 if basef:
566 579 ui.status(_('nothing to rebase - %s is both "base"'
567 580 ' and destination\n') % dest)
568 581 else:
569 582 ui.status(_('nothing to rebase - working directory '
570 583 'parent is also destination\n'))
571 584 elif not repo.revs('%ld - ::%d', base, dest):
572 585 if basef:
573 586 ui.status(_('nothing to rebase - "base" %s is '
574 587 'already an ancestor of destination '
575 588 '%s\n') %
576 589 ('+'.join(str(repo[r]) for r in base),
577 590 dest))
578 591 else:
579 592 ui.status(_('nothing to rebase - working '
580 593 'directory parent is already an '
581 594 'ancestor of destination %s\n') % dest)
582 595 else: # can it happen?
583 596 ui.status(_('nothing to rebase from %s to %s\n') %
584 597 ('+'.join(str(repo[r]) for r in base), dest))
585 598 return None, None
586 599
587 600 if not destf:
588 601 dest = repo[_destrebase(repo, rebaseset)]
589 602 destf = str(dest)
590 603
591 604 return dest, rebaseset
592 605
593 606 def externalparent(repo, state, targetancestors):
594 607 """Return the revision that should be used as the second parent
595 608 when the revisions in state is collapsed on top of targetancestors.
596 609 Abort if there is more than one parent.
597 610 """
598 611 parents = set()
599 612 source = min(state)
600 613 for rev in state:
601 614 if rev == source:
602 615 continue
603 616 for p in repo[rev].parents():
604 617 if (p.rev() not in state
605 618 and p.rev() not in targetancestors):
606 619 parents.add(p.rev())
607 620 if not parents:
608 621 return nullrev
609 622 if len(parents) == 1:
610 623 return parents.pop()
611 624 raise error.Abort(_('unable to collapse on top of %s, there is more '
612 625 'than one external parent: %s') %
613 626 (max(targetancestors),
614 627 ', '.join(str(p) for p in sorted(parents))))
615 628
616 629 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
617 630 keepbranches=False, date=None):
618 631 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
619 632 but also store useful information in extra.
620 633 Return node of committed revision.'''
621 634 dsguard = cmdutil.dirstateguard(repo, 'rebase')
622 635 try:
623 636 repo.setparents(repo[p1].node(), repo[p2].node())
624 637 ctx = repo[rev]
625 638 if commitmsg is None:
626 639 commitmsg = ctx.description()
627 640 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
628 641 extra = {'rebase_source': ctx.hex()}
629 642 if extrafn:
630 643 extrafn(ctx, extra)
631 644
632 645 backup = repo.ui.backupconfig('phases', 'new-commit')
633 646 try:
634 647 targetphase = max(ctx.phase(), phases.draft)
635 648 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
636 649 if keepbranch:
637 650 repo.ui.setconfig('ui', 'allowemptycommit', True)
638 651 # Commit might fail if unresolved files exist
639 652 if date is None:
640 653 date = ctx.date()
641 654 newnode = repo.commit(text=commitmsg, user=ctx.user(),
642 655 date=date, extra=extra, editor=editor)
643 656 finally:
644 657 repo.ui.restoreconfig(backup)
645 658
646 659 repo.dirstate.setbranch(repo[newnode].branch())
647 660 dsguard.close()
648 661 return newnode
649 662 finally:
650 663 release(dsguard)
651 664
652 665 def rebasenode(repo, rev, p1, base, state, collapse, target):
653 666 'Rebase a single revision rev on top of p1 using base as merge ancestor'
654 667 # Merge phase
655 668 # Update to target and merge it with local
656 669 if repo['.'].rev() != p1:
657 670 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
658 671 merge.update(repo, p1, False, True)
659 672 else:
660 673 repo.ui.debug(" already in target\n")
661 674 repo.dirstate.write(repo.currenttransaction())
662 675 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
663 676 if base is not None:
664 677 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
665 678 # When collapsing in-place, the parent is the common ancestor, we
666 679 # have to allow merging with it.
667 680 stats = merge.update(repo, rev, True, True, base, collapse,
668 681 labels=['dest', 'source'])
669 682 if collapse:
670 683 copies.duplicatecopies(repo, rev, target)
671 684 else:
672 685 # If we're not using --collapse, we need to
673 686 # duplicate copies between the revision we're
674 687 # rebasing and its first parent, but *not*
675 688 # duplicate any copies that have already been
676 689 # performed in the destination.
677 690 p1rev = repo[rev].p1().rev()
678 691 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
679 692 return stats
680 693
681 694 def nearestrebased(repo, rev, state):
682 695 """return the nearest ancestors of rev in the rebase result"""
683 696 rebased = [r for r in state if state[r] > nullmerge]
684 697 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
685 698 if candidates:
686 699 return state[candidates.first()]
687 700 else:
688 701 return None
689 702
690 703 def _checkobsrebase(repo, ui,
691 704 rebaseobsrevs,
692 705 rebasesetrevs,
693 706 rebaseobsskipped):
694 707 """
695 708 Abort if rebase will create divergence or rebase is noop because of markers
696 709
697 710 `rebaseobsrevs`: set of obsolete revision in source
698 711 `rebasesetrevs`: set of revisions to be rebased from source
699 712 `rebaseobsskipped`: set of revisions from source skipped because they have
700 713 successors in destination
701 714 """
702 715 # Obsolete node with successors not in dest leads to divergence
703 716 divergenceok = ui.configbool('experimental',
704 717 'allowdivergence')
705 718 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
706 719
707 720 if divergencebasecandidates and not divergenceok:
708 721 divhashes = (str(repo[r])
709 722 for r in divergencebasecandidates)
710 723 msg = _("this rebase will cause "
711 724 "divergences from: %s")
712 725 h = _("to force the rebase please set "
713 726 "experimental.allowdivergence=True")
714 727 raise error.Abort(msg % (",".join(divhashes),), hint=h)
715 728
716 729 # - plain prune (no successor) changesets are rebased
717 730 # - split changesets are not rebased if at least one of the
718 731 # changeset resulting from the split is an ancestor of dest
719 732 rebaseset = rebasesetrevs - rebaseobsskipped
720 733 if rebasesetrevs and not rebaseset:
721 734 msg = _('all requested changesets have equivalents '
722 735 'or were marked as obsolete')
723 736 hint = _('to force the rebase, set the config '
724 737 'experimental.rebaseskipobsolete to False')
725 738 raise error.Abort(msg, hint=hint)
726 739
727 740 def defineparents(repo, rev, target, state, targetancestors):
728 741 'Return the new parent relationship of the revision that will be rebased'
729 742 parents = repo[rev].parents()
730 743 p1 = p2 = nullrev
731 744
732 745 p1n = parents[0].rev()
733 746 if p1n in targetancestors:
734 747 p1 = target
735 748 elif p1n in state:
736 749 if state[p1n] == nullmerge:
737 750 p1 = target
738 751 elif state[p1n] in revskipped:
739 752 p1 = nearestrebased(repo, p1n, state)
740 753 if p1 is None:
741 754 p1 = target
742 755 else:
743 756 p1 = state[p1n]
744 757 else: # p1n external
745 758 p1 = target
746 759 p2 = p1n
747 760
748 761 if len(parents) == 2 and parents[1].rev() not in targetancestors:
749 762 p2n = parents[1].rev()
750 763 # interesting second parent
751 764 if p2n in state:
752 765 if p1 == target: # p1n in targetancestors or external
753 766 p1 = state[p2n]
754 767 elif state[p2n] in revskipped:
755 768 p2 = nearestrebased(repo, p2n, state)
756 769 if p2 is None:
757 770 # no ancestors rebased yet, detach
758 771 p2 = target
759 772 else:
760 773 p2 = state[p2n]
761 774 else: # p2n external
762 775 if p2 != nullrev: # p1n external too => rev is a merged revision
763 776 raise error.Abort(_('cannot use revision %d as base, result '
764 777 'would have 3 parents') % rev)
765 778 p2 = p2n
766 779 repo.ui.debug(" future parents are %d and %d\n" %
767 780 (repo[p1].rev(), repo[p2].rev()))
768 781
769 782 if not any(p.rev() in state for p in parents):
770 783 # Case (1) root changeset of a non-detaching rebase set.
771 784 # Let the merge mechanism find the base itself.
772 785 base = None
773 786 elif not repo[rev].p2():
774 787 # Case (2) detaching the node with a single parent, use this parent
775 788 base = repo[rev].p1().rev()
776 789 else:
777 790 # Assuming there is a p1, this is the case where there also is a p2.
778 791 # We are thus rebasing a merge and need to pick the right merge base.
779 792 #
780 793 # Imagine we have:
781 794 # - M: current rebase revision in this step
782 795 # - A: one parent of M
783 796 # - B: other parent of M
784 797 # - D: destination of this merge step (p1 var)
785 798 #
786 799 # Consider the case where D is a descendant of A or B and the other is
787 800 # 'outside'. In this case, the right merge base is the D ancestor.
788 801 #
789 802 # An informal proof, assuming A is 'outside' and B is the D ancestor:
790 803 #
791 804 # If we pick B as the base, the merge involves:
792 805 # - changes from B to M (actual changeset payload)
793 806 # - changes from B to D (induced by rebase) as D is a rebased
794 807 # version of B)
795 808 # Which exactly represent the rebase operation.
796 809 #
797 810 # If we pick A as the base, the merge involves:
798 811 # - changes from A to M (actual changeset payload)
799 812 # - changes from A to D (with include changes between unrelated A and B
800 813 # plus changes induced by rebase)
801 814 # Which does not represent anything sensible and creates a lot of
802 815 # conflicts. A is thus not the right choice - B is.
803 816 #
804 817 # Note: The base found in this 'proof' is only correct in the specified
805 818 # case. This base does not make sense if is not D a descendant of A or B
806 819 # or if the other is not parent 'outside' (especially not if the other
807 820 # parent has been rebased). The current implementation does not
808 821 # make it feasible to consider different cases separately. In these
809 822 # other cases we currently just leave it to the user to correctly
810 823 # resolve an impossible merge using a wrong ancestor.
811 824 for p in repo[rev].parents():
812 825 if state.get(p.rev()) == p1:
813 826 base = p.rev()
814 827 break
815 828 else: # fallback when base not found
816 829 base = None
817 830
818 831 # Raise because this function is called wrong (see issue 4106)
819 832 raise AssertionError('no base found to rebase on '
820 833 '(defineparents called wrong)')
821 834 return p1, p2, base
822 835
823 836 def isagitpatch(repo, patchname):
824 837 'Return true if the given patch is in git format'
825 838 mqpatch = os.path.join(repo.mq.path, patchname)
826 839 for line in patch.linereader(file(mqpatch, 'rb')):
827 840 if line.startswith('diff --git'):
828 841 return True
829 842 return False
830 843
831 844 def updatemq(repo, state, skipped, **opts):
832 845 'Update rebased mq patches - finalize and then import them'
833 846 mqrebase = {}
834 847 mq = repo.mq
835 848 original_series = mq.fullseries[:]
836 849 skippedpatches = set()
837 850
838 851 for p in mq.applied:
839 852 rev = repo[p.node].rev()
840 853 if rev in state:
841 854 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
842 855 (rev, p.name))
843 856 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
844 857 else:
845 858 # Applied but not rebased, not sure this should happen
846 859 skippedpatches.add(p.name)
847 860
848 861 if mqrebase:
849 862 mq.finish(repo, mqrebase.keys())
850 863
851 864 # We must start import from the newest revision
852 865 for rev in sorted(mqrebase, reverse=True):
853 866 if rev not in skipped:
854 867 name, isgit = mqrebase[rev]
855 868 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
856 869 (name, state[rev], repo[state[rev]]))
857 870 mq.qimport(repo, (), patchname=name, git=isgit,
858 871 rev=[str(state[rev])])
859 872 else:
860 873 # Rebased and skipped
861 874 skippedpatches.add(mqrebase[rev][0])
862 875
863 876 # Patches were either applied and rebased and imported in
864 877 # order, applied and removed or unapplied. Discard the removed
865 878 # ones while preserving the original series order and guards.
866 879 newseries = [s for s in original_series
867 880 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
868 881 mq.fullseries[:] = newseries
869 882 mq.seriesdirty = True
870 883 mq.savedirty()
871 884
872 885 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
873 886 'Move bookmarks to their correct changesets, and delete divergent ones'
874 887 marks = repo._bookmarks
875 888 for k, v in originalbookmarks.iteritems():
876 889 if v in nstate:
877 890 # update the bookmarks for revs that have moved
878 891 marks[k] = nstate[v]
879 892 bookmarks.deletedivergent(repo, [targetnode], k)
880 893 marks.recordchange(tr)
881 894
882 895 def storecollapsemsg(repo, collapsemsg):
883 896 'Store the collapse message to allow recovery'
884 897 collapsemsg = collapsemsg or ''
885 898 f = repo.vfs("last-message.txt", "w")
886 899 f.write("%s\n" % collapsemsg)
887 900 f.close()
888 901
889 902 def clearcollapsemsg(repo):
890 903 'Remove collapse message file'
891 904 util.unlinkpath(repo.join("last-message.txt"), ignoremissing=True)
892 905
893 906 def restorecollapsemsg(repo):
894 907 'Restore previously stored collapse message'
895 908 try:
896 909 f = repo.vfs("last-message.txt")
897 910 collapsemsg = f.readline().strip()
898 911 f.close()
899 912 except IOError as err:
900 913 if err.errno != errno.ENOENT:
901 914 raise
902 915 raise error.Abort(_('no rebase in progress'))
903 916 return collapsemsg
904 917
905 918 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
906 919 external, activebookmark):
907 920 'Store the current status to allow recovery'
908 921 f = repo.vfs("rebasestate", "w")
909 922 f.write(repo[originalwd].hex() + '\n')
910 923 f.write(repo[target].hex() + '\n')
911 924 f.write(repo[external].hex() + '\n')
912 925 f.write('%d\n' % int(collapse))
913 926 f.write('%d\n' % int(keep))
914 927 f.write('%d\n' % int(keepbranches))
915 928 f.write('%s\n' % (activebookmark or ''))
916 929 for d, v in state.iteritems():
917 930 oldrev = repo[d].hex()
918 931 if v >= 0:
919 932 newrev = repo[v].hex()
920 933 elif v == revtodo:
921 934 # To maintain format compatibility, we have to use nullid.
922 935 # Please do remove this special case when upgrading the format.
923 936 newrev = hex(nullid)
924 937 else:
925 938 newrev = v
926 939 f.write("%s:%s\n" % (oldrev, newrev))
927 940 f.close()
928 941 repo.ui.debug('rebase status stored\n')
929 942
930 943 def clearstatus(repo):
931 944 'Remove the status files'
932 945 _clearrebasesetvisibiliy(repo)
933 946 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
934 947
935 948 def restorestatus(repo):
936 949 'Restore a previously stored status'
937 950 keepbranches = None
938 951 target = None
939 952 collapse = False
940 953 external = nullrev
941 954 activebookmark = None
942 955 state = {}
943 956
944 957 try:
945 958 f = repo.vfs("rebasestate")
946 959 for i, l in enumerate(f.read().splitlines()):
947 960 if i == 0:
948 961 originalwd = repo[l].rev()
949 962 elif i == 1:
950 963 target = repo[l].rev()
951 964 elif i == 2:
952 965 external = repo[l].rev()
953 966 elif i == 3:
954 967 collapse = bool(int(l))
955 968 elif i == 4:
956 969 keep = bool(int(l))
957 970 elif i == 5:
958 971 keepbranches = bool(int(l))
959 972 elif i == 6 and not (len(l) == 81 and ':' in l):
960 973 # line 6 is a recent addition, so for backwards compatibility
961 974 # check that the line doesn't look like the oldrev:newrev lines
962 975 activebookmark = l
963 976 else:
964 977 oldrev, newrev = l.split(':')
965 978 if newrev in (str(nullmerge), str(revignored),
966 979 str(revprecursor), str(revpruned)):
967 980 state[repo[oldrev].rev()] = int(newrev)
968 981 elif newrev == nullid:
969 982 state[repo[oldrev].rev()] = revtodo
970 983 # Legacy compat special case
971 984 else:
972 985 state[repo[oldrev].rev()] = repo[newrev].rev()
973 986
974 987 except IOError as err:
975 988 if err.errno != errno.ENOENT:
976 989 raise
977 990 cmdutil.wrongtooltocontinue(repo, _('rebase'))
978 991
979 992 if keepbranches is None:
980 993 raise error.Abort(_('.hg/rebasestate is incomplete'))
981 994
982 995 skipped = set()
983 996 # recompute the set of skipped revs
984 997 if not collapse:
985 998 seen = set([target])
986 999 for old, new in sorted(state.items()):
987 1000 if new != revtodo and new in seen:
988 1001 skipped.add(old)
989 1002 seen.add(new)
990 1003 repo.ui.debug('computed skipped revs: %s\n' %
991 1004 (' '.join(str(r) for r in sorted(skipped)) or None))
992 1005 repo.ui.debug('rebase status resumed\n')
993 1006 _setrebasesetvisibility(repo, state.keys())
994 1007 return (originalwd, target, state, skipped,
995 1008 collapse, keep, keepbranches, external, activebookmark)
996 1009
997 1010 def needupdate(repo, state):
998 1011 '''check whether we should `update --clean` away from a merge, or if
999 1012 somehow the working dir got forcibly updated, e.g. by older hg'''
1000 1013 parents = [p.rev() for p in repo[None].parents()]
1001 1014
1002 1015 # Are we in a merge state at all?
1003 1016 if len(parents) < 2:
1004 1017 return False
1005 1018
1006 1019 # We should be standing on the first as-of-yet unrebased commit.
1007 1020 firstunrebased = min([old for old, new in state.iteritems()
1008 1021 if new == nullrev])
1009 1022 if firstunrebased in parents:
1010 1023 return True
1011 1024
1012 1025 return False
1013 1026
1014 1027 def abort(repo, originalwd, target, state, activebookmark=None):
1015 1028 '''Restore the repository to its original state. Additional args:
1016 1029
1017 1030 activebookmark: the name of the bookmark that should be active after the
1018 1031 restore'''
1019 1032
1020 1033 try:
1021 1034 # If the first commits in the rebased set get skipped during the rebase,
1022 1035 # their values within the state mapping will be the target rev id. The
1023 1036 # dstates list must must not contain the target rev (issue4896)
1024 1037 dstates = [s for s in state.values() if s >= 0 and s != target]
1025 1038 immutable = [d for d in dstates if not repo[d].mutable()]
1026 1039 cleanup = True
1027 1040 if immutable:
1028 1041 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1029 1042 % ', '.join(str(repo[r]) for r in immutable),
1030 1043 hint=_('see "hg help phases" for details'))
1031 1044 cleanup = False
1032 1045
1033 1046 descendants = set()
1034 1047 if dstates:
1035 1048 descendants = set(repo.changelog.descendants(dstates))
1036 1049 if descendants - set(dstates):
1037 1050 repo.ui.warn(_("warning: new changesets detected on target branch, "
1038 1051 "can't strip\n"))
1039 1052 cleanup = False
1040 1053
1041 1054 if cleanup:
1042 1055 shouldupdate = False
1043 1056 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1044 1057 if rebased:
1045 1058 strippoints = [
1046 1059 c.node() for c in repo.set('roots(%ld)', rebased)]
1047 1060 shouldupdate = len([
1048 1061 c.node() for c in repo.set('. & (%ld)', rebased)]) > 0
1049 1062
1050 1063 # Update away from the rebase if necessary
1051 1064 if shouldupdate or needupdate(repo, state):
1052 1065 merge.update(repo, originalwd, False, True)
1053 1066
1054 1067 # Strip from the first rebased revision
1055 1068 if rebased:
1056 1069 # no backup of rebased cset versions needed
1057 1070 repair.strip(repo.ui, repo, strippoints)
1058 1071
1059 1072 if activebookmark and activebookmark in repo._bookmarks:
1060 1073 bookmarks.activate(repo, activebookmark)
1061 1074
1062 1075 finally:
1063 1076 clearstatus(repo)
1064 1077 clearcollapsemsg(repo)
1065 1078 repo.ui.warn(_('rebase aborted\n'))
1066 1079 return 0
1067 1080
1068 1081 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1069 1082 '''Define which revisions are going to be rebased and where
1070 1083
1071 1084 repo: repo
1072 1085 dest: context
1073 1086 rebaseset: set of rev
1074 1087 '''
1075 1088 _setrebasesetvisibility(repo, rebaseset)
1076 1089
1077 1090 # This check isn't strictly necessary, since mq detects commits over an
1078 1091 # applied patch. But it prevents messing up the working directory when
1079 1092 # a partially completed rebase is blocked by mq.
1080 1093 if 'qtip' in repo.tags() and (dest.node() in
1081 1094 [s.node for s in repo.mq.applied]):
1082 1095 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1083 1096
1084 1097 roots = list(repo.set('roots(%ld)', rebaseset))
1085 1098 if not roots:
1086 1099 raise error.Abort(_('no matching revisions'))
1087 1100 roots.sort()
1088 1101 state = {}
1089 1102 detachset = set()
1090 1103 for root in roots:
1091 1104 commonbase = root.ancestor(dest)
1092 1105 if commonbase == root:
1093 1106 raise error.Abort(_('source is ancestor of destination'))
1094 1107 if commonbase == dest:
1095 1108 samebranch = root.branch() == dest.branch()
1096 1109 if not collapse and samebranch and root in dest.children():
1097 1110 repo.ui.debug('source is a child of destination\n')
1098 1111 return None
1099 1112
1100 1113 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
1101 1114 state.update(dict.fromkeys(rebaseset, revtodo))
1102 1115 # Rebase tries to turn <dest> into a parent of <root> while
1103 1116 # preserving the number of parents of rebased changesets:
1104 1117 #
1105 1118 # - A changeset with a single parent will always be rebased as a
1106 1119 # changeset with a single parent.
1107 1120 #
1108 1121 # - A merge will be rebased as merge unless its parents are both
1109 1122 # ancestors of <dest> or are themselves in the rebased set and
1110 1123 # pruned while rebased.
1111 1124 #
1112 1125 # If one parent of <root> is an ancestor of <dest>, the rebased
1113 1126 # version of this parent will be <dest>. This is always true with
1114 1127 # --base option.
1115 1128 #
1116 1129 # Otherwise, we need to *replace* the original parents with
1117 1130 # <dest>. This "detaches" the rebased set from its former location
1118 1131 # and rebases it onto <dest>. Changes introduced by ancestors of
1119 1132 # <root> not common with <dest> (the detachset, marked as
1120 1133 # nullmerge) are "removed" from the rebased changesets.
1121 1134 #
1122 1135 # - If <root> has a single parent, set it to <dest>.
1123 1136 #
1124 1137 # - If <root> is a merge, we cannot decide which parent to
1125 1138 # replace, the rebase operation is not clearly defined.
1126 1139 #
1127 1140 # The table below sums up this behavior:
1128 1141 #
1129 1142 # +------------------+----------------------+-------------------------+
1130 1143 # | | one parent | merge |
1131 1144 # +------------------+----------------------+-------------------------+
1132 1145 # | parent in | new parent is <dest> | parents in ::<dest> are |
1133 1146 # | ::<dest> | | remapped to <dest> |
1134 1147 # +------------------+----------------------+-------------------------+
1135 1148 # | unrelated source | new parent is <dest> | ambiguous, abort |
1136 1149 # +------------------+----------------------+-------------------------+
1137 1150 #
1138 1151 # The actual abort is handled by `defineparents`
1139 1152 if len(root.parents()) <= 1:
1140 1153 # ancestors of <root> not ancestors of <dest>
1141 1154 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1142 1155 [root.rev()]))
1143 1156 for r in detachset:
1144 1157 if r not in state:
1145 1158 state[r] = nullmerge
1146 1159 if len(roots) > 1:
1147 1160 # If we have multiple roots, we may have "hole" in the rebase set.
1148 1161 # Rebase roots that descend from those "hole" should not be detached as
1149 1162 # other root are. We use the special `revignored` to inform rebase that
1150 1163 # the revision should be ignored but that `defineparents` should search
1151 1164 # a rebase destination that make sense regarding rebased topology.
1152 1165 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1153 1166 for ignored in set(rebasedomain) - set(rebaseset):
1154 1167 state[ignored] = revignored
1155 1168 for r in obsoletenotrebased:
1156 1169 if obsoletenotrebased[r] is None:
1157 1170 state[r] = revpruned
1158 1171 else:
1159 1172 state[r] = revprecursor
1160 1173 return repo['.'].rev(), dest.rev(), state
1161 1174
1162 1175 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1163 1176 """dispose of rebased revision at the end of the rebase
1164 1177
1165 1178 If `collapsedas` is not None, the rebase was a collapse whose result if the
1166 1179 `collapsedas` node."""
1167 1180 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1168 1181 markers = []
1169 1182 for rev, newrev in sorted(state.items()):
1170 1183 if newrev >= 0:
1171 1184 if rev in skipped:
1172 1185 succs = ()
1173 1186 elif collapsedas is not None:
1174 1187 succs = (repo[collapsedas],)
1175 1188 else:
1176 1189 succs = (repo[newrev],)
1177 1190 markers.append((repo[rev], succs))
1178 1191 if markers:
1179 1192 obsolete.createmarkers(repo, markers)
1180 1193 else:
1181 1194 rebased = [rev for rev in state if state[rev] > nullmerge]
1182 1195 if rebased:
1183 1196 stripped = []
1184 1197 for root in repo.set('roots(%ld)', rebased):
1185 1198 if set(repo.changelog.descendants([root.rev()])) - set(state):
1186 1199 ui.warn(_("warning: new changesets detected "
1187 1200 "on source branch, not stripping\n"))
1188 1201 else:
1189 1202 stripped.append(root.node())
1190 1203 if stripped:
1191 1204 # backup the old csets by default
1192 1205 repair.strip(ui, repo, stripped, "all")
1193 1206
1194 1207
1195 1208 def pullrebase(orig, ui, repo, *args, **opts):
1196 1209 'Call rebase after pull if the latter has been invoked with --rebase'
1197 1210 ret = None
1198 1211 if opts.get('rebase'):
1199 1212 wlock = lock = None
1200 1213 try:
1201 1214 wlock = repo.wlock()
1202 1215 lock = repo.lock()
1203 1216 if opts.get('update'):
1204 1217 del opts['update']
1205 1218 ui.debug('--update and --rebase are not compatible, ignoring '
1206 1219 'the update flag\n')
1207 1220
1208 1221 revsprepull = len(repo)
1209 1222 origpostincoming = commands.postincoming
1210 1223 def _dummy(*args, **kwargs):
1211 1224 pass
1212 1225 commands.postincoming = _dummy
1213 1226 try:
1214 1227 ret = orig(ui, repo, *args, **opts)
1215 1228 finally:
1216 1229 commands.postincoming = origpostincoming
1217 1230 revspostpull = len(repo)
1218 1231 if revspostpull > revsprepull:
1219 1232 # --rev option from pull conflict with rebase own --rev
1220 1233 # dropping it
1221 1234 if 'rev' in opts:
1222 1235 del opts['rev']
1223 1236 # positional argument from pull conflicts with rebase's own
1224 1237 # --source.
1225 1238 if 'source' in opts:
1226 1239 del opts['source']
1227 1240 try:
1228 1241 rebase(ui, repo, **opts)
1229 1242 except error.NoMergeDestAbort:
1230 1243 # we can maybe update instead
1231 1244 rev, _a, _b = destutil.destupdate(repo)
1232 1245 if rev == repo['.'].rev():
1233 1246 ui.status(_('nothing to rebase\n'))
1234 1247 else:
1235 1248 ui.status(_('nothing to rebase - updating instead\n'))
1236 1249 # not passing argument to get the bare update behavior
1237 1250 # with warning and trumpets
1238 1251 commands.update(ui, repo)
1239 1252 finally:
1240 1253 release(lock, wlock)
1241 1254 else:
1242 1255 if opts.get('tool'):
1243 1256 raise error.Abort(_('--tool can only be used with --rebase'))
1244 1257 ret = orig(ui, repo, *args, **opts)
1245 1258
1246 1259 return ret
1247 1260
1248 1261 def _setrebasesetvisibility(repo, revs):
1249 1262 """store the currently rebased set on the repo object
1250 1263
1251 1264 This is used by another function to prevent rebased revision to because
1252 1265 hidden (see issue4505)"""
1253 1266 repo = repo.unfiltered()
1254 1267 revs = set(revs)
1255 1268 repo._rebaseset = revs
1256 1269 # invalidate cache if visibility changes
1257 1270 hiddens = repo.filteredrevcache.get('visible', set())
1258 1271 if revs & hiddens:
1259 1272 repo.invalidatevolatilesets()
1260 1273
1261 1274 def _clearrebasesetvisibiliy(repo):
1262 1275 """remove rebaseset data from the repo"""
1263 1276 repo = repo.unfiltered()
1264 1277 if '_rebaseset' in vars(repo):
1265 1278 del repo._rebaseset
1266 1279
1267 1280 def _rebasedvisible(orig, repo):
1268 1281 """ensure rebased revs stay visible (see issue4505)"""
1269 1282 blockers = orig(repo)
1270 1283 blockers.update(getattr(repo, '_rebaseset', ()))
1271 1284 return blockers
1272 1285
1273 1286 def _filterobsoleterevs(repo, revs):
1274 1287 """returns a set of the obsolete revisions in revs"""
1275 1288 return set(r for r in revs if repo[r].obsolete())
1276 1289
1277 1290 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1278 1291 """return a mapping obsolete => successor for all obsolete nodes to be
1279 1292 rebased that have a successors in the destination
1280 1293
1281 1294 obsolete => None entries in the mapping indicate nodes with no succesor"""
1282 1295 obsoletenotrebased = {}
1283 1296
1284 1297 # Build a mapping successor => obsolete nodes for the obsolete
1285 1298 # nodes to be rebased
1286 1299 allsuccessors = {}
1287 1300 cl = repo.changelog
1288 1301 for r in rebaseobsrevs:
1289 1302 node = cl.node(r)
1290 1303 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1291 1304 try:
1292 1305 allsuccessors[cl.rev(s)] = cl.rev(node)
1293 1306 except LookupError:
1294 1307 pass
1295 1308
1296 1309 if allsuccessors:
1297 1310 # Look for successors of obsolete nodes to be rebased among
1298 1311 # the ancestors of dest
1299 1312 ancs = cl.ancestors([repo[dest].rev()],
1300 1313 stoprev=min(allsuccessors),
1301 1314 inclusive=True)
1302 1315 for s in allsuccessors:
1303 1316 if s in ancs:
1304 1317 obsoletenotrebased[allsuccessors[s]] = s
1305 1318 elif (s == allsuccessors[s] and
1306 1319 allsuccessors.values().count(s) == 1):
1307 1320 # plain prune
1308 1321 obsoletenotrebased[s] = None
1309 1322
1310 1323 return obsoletenotrebased
1311 1324
1312 1325 def summaryhook(ui, repo):
1313 1326 if not os.path.exists(repo.join('rebasestate')):
1314 1327 return
1315 1328 try:
1316 1329 state = restorestatus(repo)[2]
1317 1330 except error.RepoLookupError:
1318 1331 # i18n: column positioning for "hg summary"
1319 1332 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1320 1333 ui.write(msg)
1321 1334 return
1322 1335 numrebased = len([i for i in state.itervalues() if i >= 0])
1323 1336 # i18n: column positioning for "hg summary"
1324 1337 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1325 1338 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1326 1339 ui.label(_('%d remaining'), 'rebase.remaining') %
1327 1340 (len(state) - numrebased)))
1328 1341
1329 1342 def uisetup(ui):
1330 1343 #Replace pull with a decorator to provide --rebase option
1331 1344 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1332 1345 entry[1].append(('', 'rebase', None,
1333 1346 _("rebase working directory to branch head")))
1334 1347 entry[1].append(('t', 'tool', '',
1335 1348 _("specify merge tool for rebase")))
1336 1349 cmdutil.summaryhooks.add('rebase', summaryhook)
1337 1350 cmdutil.unfinishedstates.append(
1338 1351 ['rebasestate', False, False, _('rebase in progress'),
1339 1352 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1340 1353 cmdutil.afterresolvedstates.append(
1341 1354 ['rebasestate', _('hg rebase --continue')])
1342 1355 # ensure rebased rev are not hidden
1343 1356 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
@@ -1,807 +1,865
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 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
808 rebase --continue + skipped rev because their successors are in destination
809 we make a change in trunk and work on conflicting changes to make rebase abort.
810
811 $ hg log -G -r 17::
812 @ 17:61bd55f69bc4 bar foo
813 |
814 ~
815
816 Create the two changes in trunk
817 $ printf "a" > willconflict
818 $ hg add willconflict
819 $ hg commit -m "willconflict first version"
820
821 $ printf "dummy" > C
822 $ hg commit -m "dummy change successor"
823
824 Create the changes that we will rebase
825 $ hg update -C 17 -q
826 $ printf "b" > willconflict
827 $ hg add willconflict
828 $ hg commit -m "willconflict second version"
829 created new head
830 $ printf "dummy" > K
831 $ hg add K
832 $ hg commit -m "dummy change"
833 $ printf "dummy" > L
834 $ hg add L
835 $ hg commit -m "dummy change"
836 $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 19 -T '{node}'` --config experimental.evolution=all
837
838 $ hg log -G -r 17::
839 @ 22:7bdc8a87673d dummy change
840 |
841 x 21:8b31da3c4919 dummy change
842 |
843 o 20:b82fb57ea638 willconflict second version
844 |
845 | o 19:601db7a18f51 dummy change successor
846 | |
847 | o 18:357ddf1602d5 willconflict first version
848 |/
849 o 17:61bd55f69bc4 bar foo
850 |
851 ~
852 $ hg rebase -r ".^^ + .^ + ." -d 19
853 rebasing 20:b82fb57ea638 "willconflict second version"
854 merging willconflict
855 warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark')
856 unresolved conflicts (see hg resolve, then hg rebase --continue)
857 [1]
858
859 $ hg resolve --mark willconflict
860 (no more unresolved files)
861 continue: hg rebase --continue
862 $ hg rebase --continue
863 rebasing 20:b82fb57ea638 "willconflict second version"
864 note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
865 rebasing 22:7bdc8a87673d "dummy change" (tip)
General Comments 0
You need to be logged in to leave comments. Login now