##// END OF EJS Templates
rebase: preserve metadata from grafts of changes (issue4001)
Augie Fackler -
r19861:a69a77a8 default
parent child Browse files
Show More
@@ -1,859 +1,864 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 http://mercurial.selenic.com/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.commands import templateopts
20 20 from mercurial.node import nullrev
21 21 from mercurial.lock import release
22 22 from mercurial.i18n import _
23 23 import os, errno
24 24
25 25 nullmerge = -2
26 26 revignored = -3
27 27
28 28 cmdtable = {}
29 29 command = cmdutil.command(cmdtable)
30 30 testedwith = 'internal'
31 31
32 def _savegraft(ctx, extra):
33 s = ctx.extra().get('source', None)
34 if s is not None:
35 extra['source'] = s
36
32 37 def _savebranch(ctx, extra):
33 38 extra['branch'] = ctx.branch()
34 39
35 40 def _makeextrafn(copiers):
36 41 """make an extrafn out of the given copy-functions.
37 42
38 43 A copy function takes a context and an extra dict, and mutates the
39 44 extra dict as needed based on the given context.
40 45 """
41 46 def extrafn(ctx, extra):
42 47 for c in copiers:
43 48 c(ctx, extra)
44 49 return extrafn
45 50
46 51 @command('rebase',
47 52 [('s', 'source', '',
48 53 _('rebase from the specified changeset'), _('REV')),
49 54 ('b', 'base', '',
50 55 _('rebase from the base of the specified changeset '
51 56 '(up to greatest common ancestor of base and dest)'),
52 57 _('REV')),
53 58 ('r', 'rev', [],
54 59 _('rebase these revisions'),
55 60 _('REV')),
56 61 ('d', 'dest', '',
57 62 _('rebase onto the specified changeset'), _('REV')),
58 63 ('', 'collapse', False, _('collapse the rebased changesets')),
59 64 ('m', 'message', '',
60 65 _('use text as collapse commit message'), _('TEXT')),
61 66 ('e', 'edit', False, _('invoke editor on commit messages')),
62 67 ('l', 'logfile', '',
63 68 _('read collapse commit message from file'), _('FILE')),
64 69 ('', 'keep', False, _('keep original changesets')),
65 70 ('', 'keepbranches', False, _('keep original branch names')),
66 71 ('D', 'detach', False, _('(DEPRECATED)')),
67 72 ('t', 'tool', '', _('specify merge tool')),
68 73 ('c', 'continue', False, _('continue an interrupted rebase')),
69 74 ('a', 'abort', False, _('abort an interrupted rebase'))] +
70 75 templateopts,
71 76 _('[-s REV | -b REV] [-d REV] [OPTION]'))
72 77 def rebase(ui, repo, **opts):
73 78 """move changeset (and descendants) to a different branch
74 79
75 80 Rebase uses repeated merging to graft changesets from one part of
76 81 history (the source) onto another (the destination). This can be
77 82 useful for linearizing *local* changes relative to a master
78 83 development tree.
79 84
80 85 You should not rebase changesets that have already been shared
81 86 with others. Doing so will force everybody else to perform the
82 87 same rebase or they will end up with duplicated changesets after
83 88 pulling in your rebased changesets.
84 89
85 90 In its default configuration, Mercurial will prevent you from
86 91 rebasing published changes. See :hg:`help phases` for details.
87 92
88 93 If you don't specify a destination changeset (``-d/--dest``),
89 94 rebase uses the current branch tip as the destination. (The
90 95 destination changeset is not modified by rebasing, but new
91 96 changesets are added as its descendants.)
92 97
93 98 You can specify which changesets to rebase in two ways: as a
94 99 "source" changeset or as a "base" changeset. Both are shorthand
95 100 for a topologically related set of changesets (the "source
96 101 branch"). If you specify source (``-s/--source``), rebase will
97 102 rebase that changeset and all of its descendants onto dest. If you
98 103 specify base (``-b/--base``), rebase will select ancestors of base
99 104 back to but not including the common ancestor with dest. Thus,
100 105 ``-b`` is less precise but more convenient than ``-s``: you can
101 106 specify any changeset in the source branch, and rebase will select
102 107 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
103 108 uses the parent of the working directory as the base.
104 109
105 110 For advanced usage, a third way is available through the ``--rev``
106 111 option. It allows you to specify an arbitrary set of changesets to
107 112 rebase. Descendants of revs you specify with this option are not
108 113 automatically included in the rebase.
109 114
110 115 By default, rebase recreates the changesets in the source branch
111 116 as descendants of dest and then destroys the originals. Use
112 117 ``--keep`` to preserve the original source changesets. Some
113 118 changesets in the source branch (e.g. merges from the destination
114 119 branch) may be dropped if they no longer contribute any change.
115 120
116 121 One result of the rules for selecting the destination changeset
117 122 and source branch is that, unlike ``merge``, rebase will do
118 123 nothing if you are at the branch tip of a named branch
119 124 with two heads. You need to explicitly specify source and/or
120 125 destination (or ``update`` to the other head, if it's the head of
121 126 the intended source branch).
122 127
123 128 If a rebase is interrupted to manually resolve a merge, it can be
124 129 continued with --continue/-c or aborted with --abort/-a.
125 130
126 131 Returns 0 on success, 1 if nothing to rebase.
127 132 """
128 133 originalwd = target = None
129 134 activebookmark = None
130 135 external = nullrev
131 136 state = {}
132 137 skipped = set()
133 138 targetancestors = set()
134 139
135 140 editor = None
136 141 if opts.get('edit'):
137 142 editor = cmdutil.commitforceeditor
138 143
139 144 lock = wlock = None
140 145 try:
141 146 wlock = repo.wlock()
142 147 lock = repo.lock()
143 148
144 149 # Validate input and define rebasing points
145 150 destf = opts.get('dest', None)
146 151 srcf = opts.get('source', None)
147 152 basef = opts.get('base', None)
148 153 revf = opts.get('rev', [])
149 154 contf = opts.get('continue')
150 155 abortf = opts.get('abort')
151 156 collapsef = opts.get('collapse', False)
152 157 collapsemsg = cmdutil.logmessage(ui, opts)
153 158 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
154 extrafns = []
159 extrafns = [_savegraft]
155 160 if e:
156 161 extrafns = [e]
157 162 keepf = opts.get('keep', False)
158 163 keepbranchesf = opts.get('keepbranches', False)
159 164 # keepopen is not meant for use on the command line, but by
160 165 # other extensions
161 166 keepopen = opts.get('keepopen', False)
162 167
163 168 if collapsemsg and not collapsef:
164 169 raise util.Abort(
165 170 _('message can only be specified with collapse'))
166 171
167 172 if contf or abortf:
168 173 if contf and abortf:
169 174 raise util.Abort(_('cannot use both abort and continue'))
170 175 if collapsef:
171 176 raise util.Abort(
172 177 _('cannot use collapse with continue or abort'))
173 178 if srcf or basef or destf:
174 179 raise util.Abort(
175 180 _('abort and continue do not allow specifying revisions'))
176 181 if opts.get('tool', False):
177 182 ui.warn(_('tool option will be ignored\n'))
178 183
179 184 try:
180 185 (originalwd, target, state, skipped, collapsef, keepf,
181 186 keepbranchesf, external, activebookmark) = restorestatus(repo)
182 187 except error.RepoLookupError:
183 188 if abortf:
184 189 clearstatus(repo)
185 190 repo.ui.warn(_('rebase aborted (no revision is removed,'
186 191 ' only broken state is cleared)\n'))
187 192 return 0
188 193 else:
189 194 msg = _('cannot continue inconsistent rebase')
190 195 hint = _('use "hg rebase --abort" to clear borken state')
191 196 raise util.Abort(msg, hint=hint)
192 197 if abortf:
193 198 return abort(repo, originalwd, target, state)
194 199 else:
195 200 if srcf and basef:
196 201 raise util.Abort(_('cannot specify both a '
197 202 'source and a base'))
198 203 if revf and basef:
199 204 raise util.Abort(_('cannot specify both a '
200 205 'revision and a base'))
201 206 if revf and srcf:
202 207 raise util.Abort(_('cannot specify both a '
203 208 'revision and a source'))
204 209
205 210 cmdutil.checkunfinished(repo)
206 211 cmdutil.bailifchanged(repo)
207 212
208 213 if not destf:
209 214 # Destination defaults to the latest revision in the
210 215 # current branch
211 216 branch = repo[None].branch()
212 217 dest = repo[branch]
213 218 else:
214 219 dest = scmutil.revsingle(repo, destf)
215 220
216 221 if revf:
217 222 rebaseset = scmutil.revrange(repo, revf)
218 223 elif srcf:
219 224 src = scmutil.revrange(repo, [srcf])
220 225 rebaseset = repo.revs('(%ld)::', src)
221 226 else:
222 227 base = scmutil.revrange(repo, [basef or '.'])
223 228 rebaseset = repo.revs(
224 229 '(children(ancestor(%ld, %d)) and ::(%ld))::',
225 230 base, dest, base)
226 231 if rebaseset:
227 232 root = min(rebaseset)
228 233 else:
229 234 root = None
230 235
231 236 if not rebaseset:
232 237 repo.ui.debug('base is ancestor of destination\n')
233 238 result = None
234 239 elif (not (keepf or obsolete._enabled)
235 240 and repo.revs('first(children(%ld) - %ld)',
236 241 rebaseset, rebaseset)):
237 242 raise util.Abort(
238 243 _("can't remove original changesets with"
239 244 " unrebased descendants"),
240 245 hint=_('use --keep to keep original changesets'))
241 246 else:
242 247 result = buildstate(repo, dest, rebaseset, collapsef)
243 248
244 249 if not result:
245 250 # Empty state built, nothing to rebase
246 251 ui.status(_('nothing to rebase\n'))
247 252 return 1
248 253 elif not keepf and not repo[root].mutable():
249 254 raise util.Abort(_("can't rebase immutable changeset %s")
250 255 % repo[root],
251 256 hint=_('see hg help phases for details'))
252 257 else:
253 258 originalwd, target, state = result
254 259 if collapsef:
255 260 targetancestors = repo.changelog.ancestors([target],
256 261 inclusive=True)
257 262 external = checkexternal(repo, state, targetancestors)
258 263
259 264 if keepbranchesf:
260 265 # insert _savebranch at the start of extrafns so if
261 266 # there's a user-provided extrafn it can clobber branch if
262 267 # desired
263 268 extrafns.insert(0, _savebranch)
264 269 if collapsef:
265 270 branches = set()
266 271 for rev in state:
267 272 branches.add(repo[rev].branch())
268 273 if len(branches) > 1:
269 274 raise util.Abort(_('cannot collapse multiple named '
270 275 'branches'))
271 276
272 277
273 278 # Rebase
274 279 if not targetancestors:
275 280 targetancestors = repo.changelog.ancestors([target], inclusive=True)
276 281
277 282 # Keep track of the current bookmarks in order to reset them later
278 283 currentbookmarks = repo._bookmarks.copy()
279 284 activebookmark = activebookmark or repo._bookmarkcurrent
280 285 if activebookmark:
281 286 bookmarks.unsetcurrent(repo)
282 287
283 288 extrafn = _makeextrafn(extrafns)
284 289
285 290 sortedstate = sorted(state)
286 291 total = len(sortedstate)
287 292 pos = 0
288 293 for rev in sortedstate:
289 294 pos += 1
290 295 if state[rev] == -1:
291 296 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
292 297 _('changesets'), total)
293 298 p1, p2 = defineparents(repo, rev, target, state,
294 299 targetancestors)
295 300 storestatus(repo, originalwd, target, state, collapsef, keepf,
296 301 keepbranchesf, external, activebookmark)
297 302 if len(repo.parents()) == 2:
298 303 repo.ui.debug('resuming interrupted rebase\n')
299 304 else:
300 305 try:
301 306 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
302 307 stats = rebasenode(repo, rev, p1, state, collapsef)
303 308 if stats and stats[3] > 0:
304 309 raise error.InterventionRequired(
305 310 _('unresolved conflicts (see hg '
306 311 'resolve, then hg rebase --continue)'))
307 312 finally:
308 313 ui.setconfig('ui', 'forcemerge', '')
309 314 cmdutil.duplicatecopies(repo, rev, target)
310 315 if not collapsef:
311 316 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
312 317 editor=editor)
313 318 else:
314 319 # Skip commit if we are collapsing
315 320 repo.setparents(repo[p1].node())
316 321 newrev = None
317 322 # Update the state
318 323 if newrev is not None:
319 324 state[rev] = repo[newrev].rev()
320 325 else:
321 326 if not collapsef:
322 327 ui.note(_('no changes, revision %d skipped\n') % rev)
323 328 ui.debug('next revision set to %s\n' % p1)
324 329 skipped.add(rev)
325 330 state[rev] = p1
326 331
327 332 ui.progress(_('rebasing'), None)
328 333 ui.note(_('rebase merging completed\n'))
329 334
330 335 if collapsef and not keepopen:
331 336 p1, p2 = defineparents(repo, min(state), target,
332 337 state, targetancestors)
333 338 if collapsemsg:
334 339 commitmsg = collapsemsg
335 340 else:
336 341 commitmsg = 'Collapsed revision'
337 342 for rebased in state:
338 343 if rebased not in skipped and state[rebased] > nullmerge:
339 344 commitmsg += '\n* %s' % repo[rebased].description()
340 345 commitmsg = ui.edit(commitmsg, repo.ui.username())
341 346 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
342 347 extrafn=extrafn, editor=editor)
343 348
344 349 if 'qtip' in repo.tags():
345 350 updatemq(repo, state, skipped, **opts)
346 351
347 352 if currentbookmarks:
348 353 # Nodeids are needed to reset bookmarks
349 354 nstate = {}
350 355 for k, v in state.iteritems():
351 356 if v > nullmerge:
352 357 nstate[repo[k].node()] = repo[v].node()
353 358 # XXX this is the same as dest.node() for the non-continue path --
354 359 # this should probably be cleaned up
355 360 targetnode = repo[target].node()
356 361
357 362 if not keepf:
358 363 collapsedas = None
359 364 if collapsef:
360 365 collapsedas = newrev
361 366 clearrebased(ui, repo, state, skipped, collapsedas)
362 367
363 368 if currentbookmarks:
364 369 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
365 370
366 371 clearstatus(repo)
367 372 ui.note(_("rebase completed\n"))
368 373 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
369 374 if skipped:
370 375 ui.note(_("%d revisions have been skipped\n") % len(skipped))
371 376
372 377 if (activebookmark and
373 378 repo['tip'].node() == repo._bookmarks[activebookmark]):
374 379 bookmarks.setcurrent(repo, activebookmark)
375 380
376 381 finally:
377 382 release(lock, wlock)
378 383
379 384 def checkexternal(repo, state, targetancestors):
380 385 """Check whether one or more external revisions need to be taken in
381 386 consideration. In the latter case, abort.
382 387 """
383 388 external = nullrev
384 389 source = min(state)
385 390 for rev in state:
386 391 if rev == source:
387 392 continue
388 393 # Check externals and fail if there are more than one
389 394 for p in repo[rev].parents():
390 395 if (p.rev() not in state
391 396 and p.rev() not in targetancestors):
392 397 if external != nullrev:
393 398 raise util.Abort(_('unable to collapse, there is more '
394 399 'than one external parent'))
395 400 external = p.rev()
396 401 return external
397 402
398 403 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
399 404 'Commit the changes and store useful information in extra'
400 405 try:
401 406 repo.setparents(repo[p1].node(), repo[p2].node())
402 407 ctx = repo[rev]
403 408 if commitmsg is None:
404 409 commitmsg = ctx.description()
405 410 extra = {'rebase_source': ctx.hex()}
406 411 if extrafn:
407 412 extrafn(ctx, extra)
408 413 # Commit might fail if unresolved files exist
409 414 newrev = repo.commit(text=commitmsg, user=ctx.user(),
410 415 date=ctx.date(), extra=extra, editor=editor)
411 416 repo.dirstate.setbranch(repo[newrev].branch())
412 417 targetphase = max(ctx.phase(), phases.draft)
413 418 # retractboundary doesn't overwrite upper phase inherited from parent
414 419 newnode = repo[newrev].node()
415 420 if newnode:
416 421 phases.retractboundary(repo, targetphase, [newnode])
417 422 return newrev
418 423 except util.Abort:
419 424 # Invalidate the previous setparents
420 425 repo.dirstate.invalidate()
421 426 raise
422 427
423 428 def rebasenode(repo, rev, p1, state, collapse):
424 429 'Rebase a single revision'
425 430 # Merge phase
426 431 # Update to target and merge it with local
427 432 if repo['.'].rev() != repo[p1].rev():
428 433 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
429 434 merge.update(repo, p1, False, True, False)
430 435 else:
431 436 repo.ui.debug(" already in target\n")
432 437 repo.dirstate.write()
433 438 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
434 439 base = None
435 440 if repo[rev].rev() != repo[min(state)].rev():
436 441 base = repo[rev].p1().node()
437 442 # When collapsing in-place, the parent is the common ancestor, we
438 443 # have to allow merging with it.
439 444 return merge.update(repo, rev, True, True, False, base, collapse)
440 445
441 446 def nearestrebased(repo, rev, state):
442 447 """return the nearest ancestors of rev in the rebase result"""
443 448 rebased = [r for r in state if state[r] > nullmerge]
444 449 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
445 450 if candidates:
446 451 return state[candidates[0]]
447 452 else:
448 453 return None
449 454
450 455 def defineparents(repo, rev, target, state, targetancestors):
451 456 'Return the new parent relationship of the revision that will be rebased'
452 457 parents = repo[rev].parents()
453 458 p1 = p2 = nullrev
454 459
455 460 P1n = parents[0].rev()
456 461 if P1n in targetancestors:
457 462 p1 = target
458 463 elif P1n in state:
459 464 if state[P1n] == nullmerge:
460 465 p1 = target
461 466 elif state[P1n] == revignored:
462 467 p1 = nearestrebased(repo, P1n, state)
463 468 if p1 is None:
464 469 p1 = target
465 470 else:
466 471 p1 = state[P1n]
467 472 else: # P1n external
468 473 p1 = target
469 474 p2 = P1n
470 475
471 476 if len(parents) == 2 and parents[1].rev() not in targetancestors:
472 477 P2n = parents[1].rev()
473 478 # interesting second parent
474 479 if P2n in state:
475 480 if p1 == target: # P1n in targetancestors or external
476 481 p1 = state[P2n]
477 482 elif state[P2n] == revignored:
478 483 p2 = nearestrebased(repo, P2n, state)
479 484 if p2 is None:
480 485 # no ancestors rebased yet, detach
481 486 p2 = target
482 487 else:
483 488 p2 = state[P2n]
484 489 else: # P2n external
485 490 if p2 != nullrev: # P1n external too => rev is a merged revision
486 491 raise util.Abort(_('cannot use revision %d as base, result '
487 492 'would have 3 parents') % rev)
488 493 p2 = P2n
489 494 repo.ui.debug(" future parents are %d and %d\n" %
490 495 (repo[p1].rev(), repo[p2].rev()))
491 496 return p1, p2
492 497
493 498 def isagitpatch(repo, patchname):
494 499 'Return true if the given patch is in git format'
495 500 mqpatch = os.path.join(repo.mq.path, patchname)
496 501 for line in patch.linereader(file(mqpatch, 'rb')):
497 502 if line.startswith('diff --git'):
498 503 return True
499 504 return False
500 505
501 506 def updatemq(repo, state, skipped, **opts):
502 507 'Update rebased mq patches - finalize and then import them'
503 508 mqrebase = {}
504 509 mq = repo.mq
505 510 original_series = mq.fullseries[:]
506 511 skippedpatches = set()
507 512
508 513 for p in mq.applied:
509 514 rev = repo[p.node].rev()
510 515 if rev in state:
511 516 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
512 517 (rev, p.name))
513 518 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
514 519 else:
515 520 # Applied but not rebased, not sure this should happen
516 521 skippedpatches.add(p.name)
517 522
518 523 if mqrebase:
519 524 mq.finish(repo, mqrebase.keys())
520 525
521 526 # We must start import from the newest revision
522 527 for rev in sorted(mqrebase, reverse=True):
523 528 if rev not in skipped:
524 529 name, isgit = mqrebase[rev]
525 530 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
526 531 mq.qimport(repo, (), patchname=name, git=isgit,
527 532 rev=[str(state[rev])])
528 533 else:
529 534 # Rebased and skipped
530 535 skippedpatches.add(mqrebase[rev][0])
531 536
532 537 # Patches were either applied and rebased and imported in
533 538 # order, applied and removed or unapplied. Discard the removed
534 539 # ones while preserving the original series order and guards.
535 540 newseries = [s for s in original_series
536 541 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
537 542 mq.fullseries[:] = newseries
538 543 mq.seriesdirty = True
539 544 mq.savedirty()
540 545
541 546 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
542 547 'Move bookmarks to their correct changesets, and delete divergent ones'
543 548 marks = repo._bookmarks
544 549 for k, v in originalbookmarks.iteritems():
545 550 if v in nstate:
546 551 # update the bookmarks for revs that have moved
547 552 marks[k] = nstate[v]
548 553 bookmarks.deletedivergent(repo, [targetnode], k)
549 554
550 555 marks.write()
551 556
552 557 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
553 558 external, activebookmark):
554 559 'Store the current status to allow recovery'
555 560 f = repo.opener("rebasestate", "w")
556 561 f.write(repo[originalwd].hex() + '\n')
557 562 f.write(repo[target].hex() + '\n')
558 563 f.write(repo[external].hex() + '\n')
559 564 f.write('%d\n' % int(collapse))
560 565 f.write('%d\n' % int(keep))
561 566 f.write('%d\n' % int(keepbranches))
562 567 f.write('%s\n' % (activebookmark or ''))
563 568 for d, v in state.iteritems():
564 569 oldrev = repo[d].hex()
565 570 if v > nullmerge:
566 571 newrev = repo[v].hex()
567 572 else:
568 573 newrev = v
569 574 f.write("%s:%s\n" % (oldrev, newrev))
570 575 f.close()
571 576 repo.ui.debug('rebase status stored\n')
572 577
573 578 def clearstatus(repo):
574 579 'Remove the status files'
575 580 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
576 581
577 582 def restorestatus(repo):
578 583 'Restore a previously stored status'
579 584 try:
580 585 target = None
581 586 collapse = False
582 587 external = nullrev
583 588 activebookmark = None
584 589 state = {}
585 590 f = repo.opener("rebasestate")
586 591 for i, l in enumerate(f.read().splitlines()):
587 592 if i == 0:
588 593 originalwd = repo[l].rev()
589 594 elif i == 1:
590 595 target = repo[l].rev()
591 596 elif i == 2:
592 597 external = repo[l].rev()
593 598 elif i == 3:
594 599 collapse = bool(int(l))
595 600 elif i == 4:
596 601 keep = bool(int(l))
597 602 elif i == 5:
598 603 keepbranches = bool(int(l))
599 604 elif i == 6 and not (len(l) == 81 and ':' in l):
600 605 # line 6 is a recent addition, so for backwards compatibility
601 606 # check that the line doesn't look like the oldrev:newrev lines
602 607 activebookmark = l
603 608 else:
604 609 oldrev, newrev = l.split(':')
605 610 if newrev in (str(nullmerge), str(revignored)):
606 611 state[repo[oldrev].rev()] = int(newrev)
607 612 else:
608 613 state[repo[oldrev].rev()] = repo[newrev].rev()
609 614 skipped = set()
610 615 # recompute the set of skipped revs
611 616 if not collapse:
612 617 seen = set([target])
613 618 for old, new in sorted(state.items()):
614 619 if new != nullrev and new in seen:
615 620 skipped.add(old)
616 621 seen.add(new)
617 622 repo.ui.debug('computed skipped revs: %s\n' % skipped)
618 623 repo.ui.debug('rebase status resumed\n')
619 624 return (originalwd, target, state, skipped,
620 625 collapse, keep, keepbranches, external, activebookmark)
621 626 except IOError, err:
622 627 if err.errno != errno.ENOENT:
623 628 raise
624 629 raise util.Abort(_('no rebase in progress'))
625 630
626 631 def inrebase(repo, originalwd, state):
627 632 '''check whether the workdir is in an interrupted rebase'''
628 633 parents = [p.rev() for p in repo.parents()]
629 634 if originalwd in parents:
630 635 return True
631 636
632 637 for newrev in state.itervalues():
633 638 if newrev in parents:
634 639 return True
635 640
636 641 return False
637 642
638 643 def abort(repo, originalwd, target, state):
639 644 'Restore the repository to its original state'
640 645 dstates = [s for s in state.values() if s != nullrev]
641 646 immutable = [d for d in dstates if not repo[d].mutable()]
642 647 cleanup = True
643 648 if immutable:
644 649 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
645 650 % ', '.join(str(repo[r]) for r in immutable),
646 651 hint=_('see hg help phases for details'))
647 652 cleanup = False
648 653
649 654 descendants = set()
650 655 if dstates:
651 656 descendants = set(repo.changelog.descendants(dstates))
652 657 if descendants - set(dstates):
653 658 repo.ui.warn(_("warning: new changesets detected on target branch, "
654 659 "can't strip\n"))
655 660 cleanup = False
656 661
657 662 if cleanup:
658 663 # Update away from the rebase if necessary
659 664 if inrebase(repo, originalwd, state):
660 665 merge.update(repo, repo[originalwd].rev(), False, True, False)
661 666
662 667 # Strip from the first rebased revision
663 668 rebased = filter(lambda x: x > -1 and x != target, state.values())
664 669 if rebased:
665 670 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
666 671 # no backup of rebased cset versions needed
667 672 repair.strip(repo.ui, repo, strippoints)
668 673
669 674 clearstatus(repo)
670 675 repo.ui.warn(_('rebase aborted\n'))
671 676 return 0
672 677
673 678 def buildstate(repo, dest, rebaseset, collapse):
674 679 '''Define which revisions are going to be rebased and where
675 680
676 681 repo: repo
677 682 dest: context
678 683 rebaseset: set of rev
679 684 '''
680 685
681 686 # This check isn't strictly necessary, since mq detects commits over an
682 687 # applied patch. But it prevents messing up the working directory when
683 688 # a partially completed rebase is blocked by mq.
684 689 if 'qtip' in repo.tags() and (dest.node() in
685 690 [s.node for s in repo.mq.applied]):
686 691 raise util.Abort(_('cannot rebase onto an applied mq patch'))
687 692
688 693 roots = list(repo.set('roots(%ld)', rebaseset))
689 694 if not roots:
690 695 raise util.Abort(_('no matching revisions'))
691 696 roots.sort()
692 697 state = {}
693 698 detachset = set()
694 699 for root in roots:
695 700 commonbase = root.ancestor(dest)
696 701 if commonbase == root:
697 702 raise util.Abort(_('source is ancestor of destination'))
698 703 if commonbase == dest:
699 704 samebranch = root.branch() == dest.branch()
700 705 if not collapse and samebranch and root in dest.children():
701 706 repo.ui.debug('source is a child of destination\n')
702 707 return None
703 708
704 709 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
705 710 state.update(dict.fromkeys(rebaseset, nullrev))
706 711 # Rebase tries to turn <dest> into a parent of <root> while
707 712 # preserving the number of parents of rebased changesets:
708 713 #
709 714 # - A changeset with a single parent will always be rebased as a
710 715 # changeset with a single parent.
711 716 #
712 717 # - A merge will be rebased as merge unless its parents are both
713 718 # ancestors of <dest> or are themselves in the rebased set and
714 719 # pruned while rebased.
715 720 #
716 721 # If one parent of <root> is an ancestor of <dest>, the rebased
717 722 # version of this parent will be <dest>. This is always true with
718 723 # --base option.
719 724 #
720 725 # Otherwise, we need to *replace* the original parents with
721 726 # <dest>. This "detaches" the rebased set from its former location
722 727 # and rebases it onto <dest>. Changes introduced by ancestors of
723 728 # <root> not common with <dest> (the detachset, marked as
724 729 # nullmerge) are "removed" from the rebased changesets.
725 730 #
726 731 # - If <root> has a single parent, set it to <dest>.
727 732 #
728 733 # - If <root> is a merge, we cannot decide which parent to
729 734 # replace, the rebase operation is not clearly defined.
730 735 #
731 736 # The table below sums up this behavior:
732 737 #
733 738 # +------------------+----------------------+-------------------------+
734 739 # | | one parent | merge |
735 740 # +------------------+----------------------+-------------------------+
736 741 # | parent in | new parent is <dest> | parents in ::<dest> are |
737 742 # | ::<dest> | | remapped to <dest> |
738 743 # +------------------+----------------------+-------------------------+
739 744 # | unrelated source | new parent is <dest> | ambiguous, abort |
740 745 # +------------------+----------------------+-------------------------+
741 746 #
742 747 # The actual abort is handled by `defineparents`
743 748 if len(root.parents()) <= 1:
744 749 # ancestors of <root> not ancestors of <dest>
745 750 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
746 751 [root.rev()]))
747 752 for r in detachset:
748 753 if r not in state:
749 754 state[r] = nullmerge
750 755 if len(roots) > 1:
751 756 # If we have multiple roots, we may have "hole" in the rebase set.
752 757 # Rebase roots that descend from those "hole" should not be detached as
753 758 # other root are. We use the special `revignored` to inform rebase that
754 759 # the revision should be ignored but that `defineparents` should search
755 760 # a rebase destination that make sense regarding rebased topology.
756 761 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
757 762 for ignored in set(rebasedomain) - set(rebaseset):
758 763 state[ignored] = revignored
759 764 return repo['.'].rev(), dest.rev(), state
760 765
761 766 def clearrebased(ui, repo, state, skipped, collapsedas=None):
762 767 """dispose of rebased revision at the end of the rebase
763 768
764 769 If `collapsedas` is not None, the rebase was a collapse whose result if the
765 770 `collapsedas` node."""
766 771 if obsolete._enabled:
767 772 markers = []
768 773 for rev, newrev in sorted(state.items()):
769 774 if newrev >= 0:
770 775 if rev in skipped:
771 776 succs = ()
772 777 elif collapsedas is not None:
773 778 succs = (repo[collapsedas],)
774 779 else:
775 780 succs = (repo[newrev],)
776 781 markers.append((repo[rev], succs))
777 782 if markers:
778 783 obsolete.createmarkers(repo, markers)
779 784 else:
780 785 rebased = [rev for rev in state if state[rev] > nullmerge]
781 786 if rebased:
782 787 stripped = []
783 788 for root in repo.set('roots(%ld)', rebased):
784 789 if set(repo.changelog.descendants([root.rev()])) - set(state):
785 790 ui.warn(_("warning: new changesets detected "
786 791 "on source branch, not stripping\n"))
787 792 else:
788 793 stripped.append(root.node())
789 794 if stripped:
790 795 # backup the old csets by default
791 796 repair.strip(ui, repo, stripped, "all")
792 797
793 798
794 799 def pullrebase(orig, ui, repo, *args, **opts):
795 800 'Call rebase after pull if the latter has been invoked with --rebase'
796 801 if opts.get('rebase'):
797 802 if opts.get('update'):
798 803 del opts['update']
799 804 ui.debug('--update and --rebase are not compatible, ignoring '
800 805 'the update flag\n')
801 806
802 807 movemarkfrom = repo['.'].node()
803 808 revsprepull = len(repo)
804 809 origpostincoming = commands.postincoming
805 810 def _dummy(*args, **kwargs):
806 811 pass
807 812 commands.postincoming = _dummy
808 813 try:
809 814 orig(ui, repo, *args, **opts)
810 815 finally:
811 816 commands.postincoming = origpostincoming
812 817 revspostpull = len(repo)
813 818 if revspostpull > revsprepull:
814 819 # --rev option from pull conflict with rebase own --rev
815 820 # dropping it
816 821 if 'rev' in opts:
817 822 del opts['rev']
818 823 rebase(ui, repo, **opts)
819 824 branch = repo[None].branch()
820 825 dest = repo[branch].rev()
821 826 if dest != repo['.'].rev():
822 827 # there was nothing to rebase we force an update
823 828 hg.update(repo, dest)
824 829 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
825 830 ui.status(_("updating bookmark %s\n")
826 831 % repo._bookmarkcurrent)
827 832 else:
828 833 if opts.get('tool'):
829 834 raise util.Abort(_('--tool can only be used with --rebase'))
830 835 orig(ui, repo, *args, **opts)
831 836
832 837 def summaryhook(ui, repo):
833 838 if not os.path.exists(repo.join('rebasestate')):
834 839 return
835 840 try:
836 841 state = restorestatus(repo)[2]
837 842 except error.RepoLookupError:
838 843 # i18n: column positioning for "hg summary"
839 844 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
840 845 ui.write(msg)
841 846 return
842 847 numrebased = len([i for i in state.itervalues() if i != -1])
843 848 # i18n: column positioning for "hg summary"
844 849 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
845 850 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
846 851 ui.label(_('%d remaining'), 'rebase.remaining') %
847 852 (len(state) - numrebased)))
848 853
849 854 def uisetup(ui):
850 855 'Replace pull with a decorator to provide --rebase option'
851 856 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
852 857 entry[1].append(('', 'rebase', None,
853 858 _("rebase working directory to branch head")))
854 859 entry[1].append(('t', 'tool', '',
855 860 _("specify merge tool for rebase")))
856 861 cmdutil.summaryhooks.add('rebase', summaryhook)
857 862 cmdutil.unfinishedstates.append(
858 863 ['rebasestate', False, False, _('rebase in progress'),
859 864 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,458 +1,473 b''
1 1 ==========================
2 2 Test rebase with obsolete
3 3 ==========================
4 4
5 5 Enable obsolete
6 6
7 7 $ cat > ${TESTTMP}/obs.py << EOF
8 8 > import mercurial.obsolete
9 9 > mercurial.obsolete._enabled = True
10 10 > EOF
11 11 $ cat >> $HGRCPATH << EOF
12 12 > [ui]
13 13 > logtemplate= {rev}:{node|short} {desc|firstline}
14 14 > [phases]
15 15 > publish=False
16 16 > [extensions]'
17 17 > rebase=
18 18 >
19 19 > obs=${TESTTMP}/obs.py
20 20 > EOF
21 21
22 22 Setup rebase canonical repo
23 23
24 24 $ hg init base
25 25 $ cd base
26 26 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
27 27 adding changesets
28 28 adding manifests
29 29 adding file changes
30 30 added 8 changesets with 7 changes to 7 files (+2 heads)
31 31 (run 'hg heads' to see heads, 'hg merge' to merge)
32 32 $ hg up tip
33 33 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 34 $ hg log -G
35 35 @ 7:02de42196ebe H
36 36 |
37 37 | o 6:eea13746799a G
38 38 |/|
39 39 o | 5:24b6387c8c8c F
40 40 | |
41 41 | o 4:9520eea781bc E
42 42 |/
43 43 | o 3:32af7686d403 D
44 44 | |
45 45 | o 2:5fddd98957c8 C
46 46 | |
47 47 | o 1:42ccdea3bb16 B
48 48 |/
49 49 o 0:cd010b8cd998 A
50 50
51 51 $ cd ..
52 52
53 53 simple rebase
54 54 ---------------------------------
55 55
56 56 $ hg clone base simple
57 57 updating to branch default
58 58 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 59 $ cd simple
60 60 $ hg up 32af7686d403
61 61 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
62 62 $ hg rebase -d eea13746799a
63 63 $ hg log -G
64 64 @ 10:8eeb3c33ad33 D
65 65 |
66 66 o 9:2327fea05063 C
67 67 |
68 68 o 8:e4e5be0395b2 B
69 69 |
70 70 | o 7:02de42196ebe H
71 71 | |
72 72 o | 6:eea13746799a G
73 73 |\|
74 74 | o 5:24b6387c8c8c F
75 75 | |
76 76 o | 4:9520eea781bc E
77 77 |/
78 78 o 0:cd010b8cd998 A
79 79
80 80 $ hg log --hidden -G
81 81 @ 10:8eeb3c33ad33 D
82 82 |
83 83 o 9:2327fea05063 C
84 84 |
85 85 o 8:e4e5be0395b2 B
86 86 |
87 87 | o 7:02de42196ebe H
88 88 | |
89 89 o | 6:eea13746799a G
90 90 |\|
91 91 | o 5:24b6387c8c8c F
92 92 | |
93 93 o | 4:9520eea781bc E
94 94 |/
95 95 | x 3:32af7686d403 D
96 96 | |
97 97 | x 2:5fddd98957c8 C
98 98 | |
99 99 | x 1:42ccdea3bb16 B
100 100 |/
101 101 o 0:cd010b8cd998 A
102 102
103 103 $ hg debugobsolete
104 104 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 {'date': '*', 'user': 'test'} (glob)
105 105 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 {'date': '*', 'user': 'test'} (glob)
106 106 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 {'date': '*', 'user': 'test'} (glob)
107 107
108 108
109 109 $ cd ..
110 110
111 111 empty changeset
112 112 ---------------------------------
113 113
114 114 $ hg clone base empty
115 115 updating to branch default
116 116 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117 $ cd empty
118 118 $ hg up eea13746799a
119 119 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
120 120
121 121 We make a copy of both the first changeset in the rebased and some other in the
122 122 set.
123 123
124 124 $ hg graft 42ccdea3bb16 32af7686d403
125 125 grafting revision 1
126 126 grafting revision 3
127 127 $ hg rebase -s 42ccdea3bb16 -d .
128 128 $ hg log -G
129 129 @ 10:5ae4c968c6ac C
130 130 |
131 131 o 9:08483444fef9 D
132 132 |
133 133 o 8:8877864f1edb B
134 134 |
135 135 | o 7:02de42196ebe H
136 136 | |
137 137 o | 6:eea13746799a G
138 138 |\|
139 139 | o 5:24b6387c8c8c F
140 140 | |
141 141 o | 4:9520eea781bc E
142 142 |/
143 143 o 0:cd010b8cd998 A
144 144
145 145 $ hg log --hidden -G
146 146 @ 10:5ae4c968c6ac C
147 147 |
148 148 o 9:08483444fef9 D
149 149 |
150 150 o 8:8877864f1edb B
151 151 |
152 152 | o 7:02de42196ebe H
153 153 | |
154 154 o | 6:eea13746799a G
155 155 |\|
156 156 | o 5:24b6387c8c8c F
157 157 | |
158 158 o | 4:9520eea781bc E
159 159 |/
160 160 | x 3:32af7686d403 D
161 161 | |
162 162 | x 2:5fddd98957c8 C
163 163 | |
164 164 | x 1:42ccdea3bb16 B
165 165 |/
166 166 o 0:cd010b8cd998 A
167 167
168 168 $ hg debugobsolete
169 169 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '*', 'user': 'test'} (glob)
170 170 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
171 171 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '*', 'user': 'test'} (glob)
172 172
173 173
174 174 More complex case were part of the rebase set were already rebased
175 175
176 176 $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
177 177 $ hg debugobsolete
178 178 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '*', 'user': 'test'} (glob)
179 179 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
180 180 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '*', 'user': 'test'} (glob)
181 08483444fef91d6224f6655ee586a65d263ad34c cbc07f26687521cecfc9a141bf5ecfc0fd2b8531 0 {'date': '* *', 'user': 'test'} (glob)
181 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 {'date': '* *', 'user': 'test'} (glob)
182 182 $ hg log -G
183 @ 11:cbc07f266875 D
183 @ 11:4596109a6a43 D
184 184 |
185 185 | o 10:5ae4c968c6ac C
186 186 | |
187 187 | x 9:08483444fef9 D
188 188 | |
189 189 | o 8:8877864f1edb B
190 190 | |
191 191 o | 7:02de42196ebe H
192 192 | |
193 193 | o 6:eea13746799a G
194 194 |/|
195 195 o | 5:24b6387c8c8c F
196 196 | |
197 197 | o 4:9520eea781bc E
198 198 |/
199 199 o 0:cd010b8cd998 A
200 200
201 201 $ hg rebase --source 'desc(B)' --dest 'tip'
202 202 $ hg debugobsolete
203 203 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '* *', 'user': 'test'} (glob)
204 204 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '* *', 'user': 'test'} (glob)
205 205 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '* *', 'user': 'test'} (glob)
206 08483444fef91d6224f6655ee586a65d263ad34c cbc07f26687521cecfc9a141bf5ecfc0fd2b8531 0 {'date': '* *', 'user': 'test'} (glob)
207 8877864f1edb05d0e07dc4ba77b67a80a7b86672 b1861c79d66ec3aa1b607ac3c9fb819e38b12238 0 {'date': '* *', 'user': 'test'} (glob)
206 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 {'date': '* *', 'user': 'test'} (glob)
207 8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 {'date': '* *', 'user': 'test'} (glob)
208 208 08483444fef91d6224f6655ee586a65d263ad34c 0 {'date': '* *', 'user': 'test'} (glob)
209 5ae4c968c6aca831df823664e706c9d4aa34473d dd4be135457a404ce5541de427ae1d98a28f4acd 0 {'date': '* *', 'user': 'test'} (glob)
209 5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 {'date': '* *', 'user': 'test'} (glob)
210 210 $ hg log --rev 'divergent()'
211 211 $ hg log -G
212 @ 13:dd4be135457a C
212 @ 13:98f6af4ee953 C
213 213 |
214 o 12:b1861c79d66e B
214 o 12:462a34d07e59 B
215 215 |
216 o 11:cbc07f266875 D
216 o 11:4596109a6a43 D
217 217 |
218 218 o 7:02de42196ebe H
219 219 |
220 220 | o 6:eea13746799a G
221 221 |/|
222 222 o | 5:24b6387c8c8c F
223 223 | |
224 224 | o 4:9520eea781bc E
225 225 |/
226 226 o 0:cd010b8cd998 A
227 227
228
228 $ hg log --style default --debug -r 4596109a6a4328c398bde3a4a3b6737cfade3003
229 changeset: 11:4596109a6a4328c398bde3a4a3b6737cfade3003
230 phase: draft
231 parent: 7:02de42196ebee42ef284b6780a87cdc96e8eaab6
232 parent: -1:0000000000000000000000000000000000000000
233 manifest: 11:a91006e3a02f1edf631f7018e6e5684cf27dd905
234 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
235 date: Sat Apr 30 15:24:48 2011 +0200
236 files+: D
237 extra: branch=default
238 extra: rebase_source=08483444fef91d6224f6655ee586a65d263ad34c
239 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
240 description:
241 D
242
243
229 244 $ cd ..
230 245
231 246 collapse rebase
232 247 ---------------------------------
233 248
234 249 $ hg clone base collapse
235 250 updating to branch default
236 251 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
237 252 $ cd collapse
238 253 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
239 254 $ hg log -G
240 255 @ 8:4dc2197e807b Collapsed revision
241 256 |
242 257 | o 7:02de42196ebe H
243 258 | |
244 259 o | 6:eea13746799a G
245 260 |\|
246 261 | o 5:24b6387c8c8c F
247 262 | |
248 263 o | 4:9520eea781bc E
249 264 |/
250 265 o 0:cd010b8cd998 A
251 266
252 267 $ hg log --hidden -G
253 268 @ 8:4dc2197e807b Collapsed revision
254 269 |
255 270 | o 7:02de42196ebe H
256 271 | |
257 272 o | 6:eea13746799a G
258 273 |\|
259 274 | o 5:24b6387c8c8c F
260 275 | |
261 276 o | 4:9520eea781bc E
262 277 |/
263 278 | x 3:32af7686d403 D
264 279 | |
265 280 | x 2:5fddd98957c8 C
266 281 | |
267 282 | x 1:42ccdea3bb16 B
268 283 |/
269 284 o 0:cd010b8cd998 A
270 285
271 286 $ hg id --debug
272 287 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
273 288 $ hg debugobsolete
274 289 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
275 290 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
276 291 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
277 292
278 293 $ cd ..
279 294
280 295 Rebase set has hidden descendants
281 296 ---------------------------------
282 297
283 298 We rebase a changeset which has a hidden changeset. The hidden changeset must
284 299 not be rebased.
285 300
286 301 $ hg clone base hidden
287 302 updating to branch default
288 303 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
289 304 $ cd hidden
290 305 $ hg rebase -s 5fddd98957c8 -d eea13746799a
291 306 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
292 307 $ hg log -G
293 308 @ 10:7c6027df6a99 B
294 309 |
295 310 | o 9:cf44d2f5a9f4 D
296 311 | |
297 312 | o 8:e273c5e7d2d2 C
298 313 | |
299 314 o | 7:02de42196ebe H
300 315 | |
301 316 | o 6:eea13746799a G
302 317 |/|
303 318 o | 5:24b6387c8c8c F
304 319 | |
305 320 | o 4:9520eea781bc E
306 321 |/
307 322 o 0:cd010b8cd998 A
308 323
309 324 $ hg log --hidden -G
310 325 @ 10:7c6027df6a99 B
311 326 |
312 327 | o 9:cf44d2f5a9f4 D
313 328 | |
314 329 | o 8:e273c5e7d2d2 C
315 330 | |
316 331 o | 7:02de42196ebe H
317 332 | |
318 333 | o 6:eea13746799a G
319 334 |/|
320 335 o | 5:24b6387c8c8c F
321 336 | |
322 337 | o 4:9520eea781bc E
323 338 |/
324 339 | x 3:32af7686d403 D
325 340 | |
326 341 | x 2:5fddd98957c8 C
327 342 | |
328 343 | x 1:42ccdea3bb16 B
329 344 |/
330 345 o 0:cd010b8cd998 A
331 346
332 347 $ hg debugobsolete
333 348 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 {'date': '*', 'user': 'test'} (glob)
334 349 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 {'date': '*', 'user': 'test'} (glob)
335 350 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 {'date': '*', 'user': 'test'} (glob)
336 351
337 352 Test that rewriting leaving instability behind is allowed
338 353 ---------------------------------------------------------------------
339 354
340 355 $ hg log -r 'children(8)'
341 356 9:cf44d2f5a9f4 D (no-eol)
342 357 $ hg rebase -r 8
343 358 $ hg log -G
344 359 @ 11:0d8f238b634c C
345 360 |
346 361 o 10:7c6027df6a99 B
347 362 |
348 363 | o 9:cf44d2f5a9f4 D
349 364 | |
350 365 | x 8:e273c5e7d2d2 C
351 366 | |
352 367 o | 7:02de42196ebe H
353 368 | |
354 369 | o 6:eea13746799a G
355 370 |/|
356 371 o | 5:24b6387c8c8c F
357 372 | |
358 373 | o 4:9520eea781bc E
359 374 |/
360 375 o 0:cd010b8cd998 A
361 376
362 377
363 378
364 379 Test multiple root handling
365 380 ------------------------------------
366 381
367 382 $ hg rebase --dest 4 --rev '7+11+9'
368 383 $ hg log -G
369 384 @ 14:1e8370e38cca C
370 385 |
371 386 | o 13:102b4c1d889b D
372 387 | |
373 388 o | 12:bfe264faf697 H
374 389 |/
375 390 | o 10:7c6027df6a99 B
376 391 | |
377 392 | x 7:02de42196ebe H
378 393 | |
379 394 +---o 6:eea13746799a G
380 395 | |/
381 396 | o 5:24b6387c8c8c F
382 397 | |
383 398 o | 4:9520eea781bc E
384 399 |/
385 400 o 0:cd010b8cd998 A
386 401
387 402 $ cd ..
388 403
389 404 test on rebase dropping a merge
390 405
391 406 (setup)
392 407
393 408 $ hg init dropmerge
394 409 $ cd dropmerge
395 410 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
396 411 adding changesets
397 412 adding manifests
398 413 adding file changes
399 414 added 8 changesets with 7 changes to 7 files (+2 heads)
400 415 (run 'hg heads' to see heads, 'hg merge' to merge)
401 416 $ hg up 3
402 417 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
403 418 $ hg merge 7
404 419 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
405 420 (branch merge, don't forget to commit)
406 421 $ hg ci -m 'M'
407 422 $ echo I > I
408 423 $ hg add I
409 424 $ hg ci -m I
410 425 $ hg log -G
411 426 @ 9:4bde274eefcf I
412 427 |
413 428 o 8:53a6a128b2b7 M
414 429 |\
415 430 | o 7:02de42196ebe H
416 431 | |
417 432 | | o 6:eea13746799a G
418 433 | |/|
419 434 | o | 5:24b6387c8c8c F
420 435 | | |
421 436 | | o 4:9520eea781bc E
422 437 | |/
423 438 o | 3:32af7686d403 D
424 439 | |
425 440 o | 2:5fddd98957c8 C
426 441 | |
427 442 o | 1:42ccdea3bb16 B
428 443 |/
429 444 o 0:cd010b8cd998 A
430 445
431 446 (actual test)
432 447
433 448 $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
434 449 $ hg log -G
435 450 @ 12:acd174b7ab39 I
436 451 |
437 452 o 11:6c11a6218c97 H
438 453 |
439 454 | o 10:b5313c85b22e D
440 455 |/
441 456 | o 8:53a6a128b2b7 M
442 457 | |\
443 458 | | x 7:02de42196ebe H
444 459 | | |
445 460 o---+ 6:eea13746799a G
446 461 | | |
447 462 | | o 5:24b6387c8c8c F
448 463 | | |
449 464 o---+ 4:9520eea781bc E
450 465 / /
451 466 x | 3:32af7686d403 D
452 467 | |
453 468 o | 2:5fddd98957c8 C
454 469 | |
455 470 o | 1:42ccdea3bb16 B
456 471 |/
457 472 o 0:cd010b8cd998 A
458 473
General Comments 0
You need to be logged in to leave comments. Login now