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