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