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