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