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