##// END OF EJS Templates
rebase: support --detach when null is common ancestor
Henrik Stuart -
r13021:6c800e7e stable
parent child Browse files
Show More
@@ -1,577 +1,577 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
18 18 from mercurial import extensions, ancestor, copies, patch
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
27 27 def rebase(ui, repo, **opts):
28 28 """move changeset (and descendants) to a different branch
29 29
30 30 Rebase uses repeated merging to graft changesets from one part of
31 31 history (the source) onto another (the destination). This can be
32 32 useful for linearizing *local* changes relative to a master
33 33 development tree.
34 34
35 35 You should not rebase changesets that have already been shared
36 36 with others. Doing so will force everybody else to perform the
37 37 same rebase or they will end up with duplicated changesets after
38 38 pulling in your rebased changesets.
39 39
40 40 If you don't specify a destination changeset (``-d/--dest``),
41 41 rebase uses the tipmost head of the current named branch as the
42 42 destination. (The destination changeset is not modified by
43 43 rebasing, but new changesets are added as its descendants.)
44 44
45 45 You can specify which changesets to rebase in two ways: as a
46 46 "source" changeset or as a "base" changeset. Both are shorthand
47 47 for a topologically related set of changesets (the "source
48 48 branch"). If you specify source (``-s/--source``), rebase will
49 49 rebase that changeset and all of its descendants onto dest. If you
50 50 specify base (``-b/--base``), rebase will select ancestors of base
51 51 back to but not including the common ancestor with dest. Thus,
52 52 ``-b`` is less precise but more convenient than ``-s``: you can
53 53 specify any changeset in the source branch, and rebase will select
54 54 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
55 55 uses the parent of the working directory as the base.
56 56
57 57 By default, rebase recreates the changesets in the source branch
58 58 as descendants of dest and then destroys the originals. Use
59 59 ``--keep`` to preserve the original source changesets. Some
60 60 changesets in the source branch (e.g. merges from the destination
61 61 branch) may be dropped if they no longer contribute any change.
62 62
63 63 One result of the rules for selecting the destination changeset
64 64 and source branch is that, unlike ``merge``, rebase will do
65 65 nothing if you are at the latest (tipmost) head of a named branch
66 66 with two heads. You need to explicitly specify source and/or
67 67 destination (or ``update`` to the other head, if it's the head of
68 68 the intended source branch).
69 69
70 70 If a rebase is interrupted to manually resolve a merge, it can be
71 71 continued with --continue/-c or aborted with --abort/-a.
72 72
73 73 Returns 0 on success, 1 if nothing to rebase.
74 74 """
75 75 originalwd = target = None
76 76 external = nullrev
77 77 state = {}
78 78 skipped = set()
79 79 targetancestors = set()
80 80
81 81 lock = wlock = None
82 82 try:
83 83 lock = repo.lock()
84 84 wlock = repo.wlock()
85 85
86 86 # Validate input and define rebasing points
87 87 destf = opts.get('dest', None)
88 88 srcf = opts.get('source', None)
89 89 basef = opts.get('base', None)
90 90 contf = opts.get('continue')
91 91 abortf = opts.get('abort')
92 92 collapsef = opts.get('collapse', False)
93 93 extrafn = opts.get('extrafn')
94 94 keepf = opts.get('keep', False)
95 95 keepbranchesf = opts.get('keepbranches', False)
96 96 detachf = opts.get('detach', False)
97 97 # keepopen is not meant for use on the command line, but by
98 98 # other extensions
99 99 keepopen = opts.get('keepopen', False)
100 100
101 101 if contf or abortf:
102 102 if contf and abortf:
103 103 raise util.Abort(_('cannot use both abort and continue'))
104 104 if collapsef:
105 105 raise util.Abort(
106 106 _('cannot use collapse with continue or abort'))
107 107 if detachf:
108 108 raise util.Abort(_('cannot use detach with continue or abort'))
109 109 if srcf or basef or destf:
110 110 raise util.Abort(
111 111 _('abort and continue do not allow specifying revisions'))
112 112
113 113 (originalwd, target, state, skipped, collapsef, keepf,
114 114 keepbranchesf, external) = restorestatus(repo)
115 115 if abortf:
116 116 return abort(repo, originalwd, target, state)
117 117 else:
118 118 if srcf and basef:
119 119 raise util.Abort(_('cannot specify both a '
120 120 'revision and a base'))
121 121 if detachf:
122 122 if not srcf:
123 123 raise util.Abort(
124 124 _('detach requires a revision to be specified'))
125 125 if basef:
126 126 raise util.Abort(_('cannot specify a base with detach'))
127 127
128 128 cmdutil.bail_if_changed(repo)
129 129 result = buildstate(repo, destf, srcf, basef, detachf)
130 130 if not result:
131 131 # Empty state built, nothing to rebase
132 132 ui.status(_('nothing to rebase\n'))
133 133 return 1
134 134 else:
135 135 originalwd, target, state = result
136 136 if collapsef:
137 137 targetancestors = set(repo.changelog.ancestors(target))
138 138 external = checkexternal(repo, state, targetancestors)
139 139
140 140 if keepbranchesf:
141 141 if extrafn:
142 142 raise util.Abort(_('cannot use both keepbranches and extrafn'))
143 143 def extrafn(ctx, extra):
144 144 extra['branch'] = ctx.branch()
145 145
146 146 # Rebase
147 147 if not targetancestors:
148 148 targetancestors = set(repo.changelog.ancestors(target))
149 149 targetancestors.add(target)
150 150
151 151 sortedstate = sorted(state)
152 152 total = len(sortedstate)
153 153 pos = 0
154 154 for rev in sortedstate:
155 155 pos += 1
156 156 if state[rev] == -1:
157 157 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
158 158 _('changesets'), total)
159 159 storestatus(repo, originalwd, target, state, collapsef, keepf,
160 160 keepbranchesf, external)
161 161 p1, p2 = defineparents(repo, rev, target, state,
162 162 targetancestors)
163 163 if len(repo.parents()) == 2:
164 164 repo.ui.debug('resuming interrupted rebase\n')
165 165 else:
166 166 stats = rebasenode(repo, rev, p1, p2, state)
167 167 if stats and stats[3] > 0:
168 168 raise util.Abort(_('unresolved conflicts (see hg '
169 169 'resolve, then hg rebase --continue)'))
170 170 updatedirstate(repo, rev, target, p2)
171 171 if not collapsef:
172 172 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
173 173 else:
174 174 # Skip commit if we are collapsing
175 175 repo.dirstate.setparents(repo[p1].node())
176 176 newrev = None
177 177 # Update the state
178 178 if newrev is not None:
179 179 state[rev] = repo[newrev].rev()
180 180 else:
181 181 if not collapsef:
182 182 ui.note(_('no changes, revision %d skipped\n') % rev)
183 183 ui.debug('next revision set to %s\n' % p1)
184 184 skipped.add(rev)
185 185 state[rev] = p1
186 186
187 187 ui.progress(_('rebasing'), None)
188 188 ui.note(_('rebase merging completed\n'))
189 189
190 190 if collapsef and not keepopen:
191 191 p1, p2 = defineparents(repo, min(state), target,
192 192 state, targetancestors)
193 193 commitmsg = 'Collapsed revision'
194 194 for rebased in state:
195 195 if rebased not in skipped and state[rebased] != nullmerge:
196 196 commitmsg += '\n* %s' % repo[rebased].description()
197 197 commitmsg = ui.edit(commitmsg, repo.ui.username())
198 198 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
199 199 extrafn=extrafn)
200 200
201 201 if 'qtip' in repo.tags():
202 202 updatemq(repo, state, skipped, **opts)
203 203
204 204 if not keepf:
205 205 # Remove no more useful revisions
206 206 rebased = [rev for rev in state if state[rev] != nullmerge]
207 207 if rebased:
208 208 if set(repo.changelog.descendants(min(rebased))) - set(state):
209 209 ui.warn(_("warning: new changesets detected "
210 210 "on source branch, not stripping\n"))
211 211 else:
212 212 # backup the old csets by default
213 213 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
214 214
215 215 clearstatus(repo)
216 216 ui.note(_("rebase completed\n"))
217 217 if os.path.exists(repo.sjoin('undo')):
218 218 util.unlink(repo.sjoin('undo'))
219 219 if skipped:
220 220 ui.note(_("%d revisions have been skipped\n") % len(skipped))
221 221 finally:
222 222 release(lock, wlock)
223 223
224 224 def rebasemerge(repo, rev, first=False):
225 225 'return the correct ancestor'
226 226 oldancestor = ancestor.ancestor
227 227
228 228 def newancestor(a, b, pfunc):
229 229 if b == rev:
230 230 return repo[rev].parents()[0].rev()
231 231 return oldancestor(a, b, pfunc)
232 232
233 233 if not first:
234 234 ancestor.ancestor = newancestor
235 235 else:
236 236 repo.ui.debug("first revision, do not change ancestor\n")
237 237 try:
238 238 stats = merge.update(repo, rev, True, True, False)
239 239 return stats
240 240 finally:
241 241 ancestor.ancestor = oldancestor
242 242
243 243 def checkexternal(repo, state, targetancestors):
244 244 """Check whether one or more external revisions need to be taken in
245 245 consideration. In the latter case, abort.
246 246 """
247 247 external = nullrev
248 248 source = min(state)
249 249 for rev in state:
250 250 if rev == source:
251 251 continue
252 252 # Check externals and fail if there are more than one
253 253 for p in repo[rev].parents():
254 254 if (p.rev() not in state
255 255 and p.rev() not in targetancestors):
256 256 if external != nullrev:
257 257 raise util.Abort(_('unable to collapse, there is more '
258 258 'than one external parent'))
259 259 external = p.rev()
260 260 return external
261 261
262 262 def updatedirstate(repo, rev, p1, p2):
263 263 """Keep track of renamed files in the revision that is going to be rebased
264 264 """
265 265 # Here we simulate the copies and renames in the source changeset
266 266 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
267 267 m1 = repo[rev].manifest()
268 268 m2 = repo[p1].manifest()
269 269 for k, v in cop.iteritems():
270 270 if k in m1:
271 271 if v in m1 or v in m2:
272 272 repo.dirstate.copy(v, k)
273 273 if v in m2 and v not in m1:
274 274 repo.dirstate.remove(v)
275 275
276 276 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
277 277 'Commit the changes and store useful information in extra'
278 278 try:
279 279 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
280 280 ctx = repo[rev]
281 281 if commitmsg is None:
282 282 commitmsg = ctx.description()
283 283 extra = {'rebase_source': ctx.hex()}
284 284 if extrafn:
285 285 extrafn(ctx, extra)
286 286 # Commit might fail if unresolved files exist
287 287 newrev = repo.commit(text=commitmsg, user=ctx.user(),
288 288 date=ctx.date(), extra=extra)
289 289 repo.dirstate.setbranch(repo[newrev].branch())
290 290 return newrev
291 291 except util.Abort:
292 292 # Invalidate the previous setparents
293 293 repo.dirstate.invalidate()
294 294 raise
295 295
296 296 def rebasenode(repo, rev, p1, p2, state):
297 297 'Rebase a single revision'
298 298 # Merge phase
299 299 # Update to target and merge it with local
300 300 if repo['.'].rev() != repo[p1].rev():
301 301 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
302 302 merge.update(repo, p1, False, True, False)
303 303 else:
304 304 repo.ui.debug(" already in target\n")
305 305 repo.dirstate.write()
306 306 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
307 307 first = repo[rev].rev() == repo[min(state)].rev()
308 308 stats = rebasemerge(repo, rev, first)
309 309 return stats
310 310
311 311 def defineparents(repo, rev, target, state, targetancestors):
312 312 'Return the new parent relationship of the revision that will be rebased'
313 313 parents = repo[rev].parents()
314 314 p1 = p2 = nullrev
315 315
316 316 P1n = parents[0].rev()
317 317 if P1n in targetancestors:
318 318 p1 = target
319 319 elif P1n in state:
320 320 if state[P1n] == nullmerge:
321 321 p1 = target
322 322 else:
323 323 p1 = state[P1n]
324 324 else: # P1n external
325 325 p1 = target
326 326 p2 = P1n
327 327
328 328 if len(parents) == 2 and parents[1].rev() not in targetancestors:
329 329 P2n = parents[1].rev()
330 330 # interesting second parent
331 331 if P2n in state:
332 332 if p1 == target: # P1n in targetancestors or external
333 333 p1 = state[P2n]
334 334 else:
335 335 p2 = state[P2n]
336 336 else: # P2n external
337 337 if p2 != nullrev: # P1n external too => rev is a merged revision
338 338 raise util.Abort(_('cannot use revision %d as base, result '
339 339 'would have 3 parents') % rev)
340 340 p2 = P2n
341 341 repo.ui.debug(" future parents are %d and %d\n" %
342 342 (repo[p1].rev(), repo[p2].rev()))
343 343 return p1, p2
344 344
345 345 def isagitpatch(repo, patchname):
346 346 'Return true if the given patch is in git format'
347 347 mqpatch = os.path.join(repo.mq.path, patchname)
348 348 for line in patch.linereader(file(mqpatch, 'rb')):
349 349 if line.startswith('diff --git'):
350 350 return True
351 351 return False
352 352
353 353 def updatemq(repo, state, skipped, **opts):
354 354 'Update rebased mq patches - finalize and then import them'
355 355 mqrebase = {}
356 356 mq = repo.mq
357 357 for p in mq.applied:
358 358 rev = repo[p.node].rev()
359 359 if rev in state:
360 360 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
361 361 (rev, p.name))
362 362 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
363 363
364 364 if mqrebase:
365 365 mq.finish(repo, mqrebase.keys())
366 366
367 367 # We must start import from the newest revision
368 368 for rev in sorted(mqrebase, reverse=True):
369 369 if rev not in skipped:
370 370 name, isgit = mqrebase[rev]
371 371 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
372 372 mq.qimport(repo, (), patchname=name, git=isgit,
373 373 rev=[str(state[rev])])
374 374 mq.save_dirty()
375 375
376 376 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
377 377 external):
378 378 'Store the current status to allow recovery'
379 379 f = repo.opener("rebasestate", "w")
380 380 f.write(repo[originalwd].hex() + '\n')
381 381 f.write(repo[target].hex() + '\n')
382 382 f.write(repo[external].hex() + '\n')
383 383 f.write('%d\n' % int(collapse))
384 384 f.write('%d\n' % int(keep))
385 385 f.write('%d\n' % int(keepbranches))
386 386 for d, v in state.iteritems():
387 387 oldrev = repo[d].hex()
388 388 newrev = repo[v].hex()
389 389 f.write("%s:%s\n" % (oldrev, newrev))
390 390 f.close()
391 391 repo.ui.debug('rebase status stored\n')
392 392
393 393 def clearstatus(repo):
394 394 'Remove the status files'
395 395 if os.path.exists(repo.join("rebasestate")):
396 396 util.unlink(repo.join("rebasestate"))
397 397
398 398 def restorestatus(repo):
399 399 'Restore a previously stored status'
400 400 try:
401 401 target = None
402 402 collapse = False
403 403 external = nullrev
404 404 state = {}
405 405 f = repo.opener("rebasestate")
406 406 for i, l in enumerate(f.read().splitlines()):
407 407 if i == 0:
408 408 originalwd = repo[l].rev()
409 409 elif i == 1:
410 410 target = repo[l].rev()
411 411 elif i == 2:
412 412 external = repo[l].rev()
413 413 elif i == 3:
414 414 collapse = bool(int(l))
415 415 elif i == 4:
416 416 keep = bool(int(l))
417 417 elif i == 5:
418 418 keepbranches = bool(int(l))
419 419 else:
420 420 oldrev, newrev = l.split(':')
421 421 state[repo[oldrev].rev()] = repo[newrev].rev()
422 422 skipped = set()
423 423 # recompute the set of skipped revs
424 424 if not collapse:
425 425 seen = set([target])
426 426 for old, new in sorted(state.items()):
427 427 if new != nullrev and new in seen:
428 428 skipped.add(old)
429 429 seen.add(new)
430 430 repo.ui.debug('computed skipped revs: %s\n' % skipped)
431 431 repo.ui.debug('rebase status resumed\n')
432 432 return (originalwd, target, state, skipped,
433 433 collapse, keep, keepbranches, external)
434 434 except IOError, err:
435 435 if err.errno != errno.ENOENT:
436 436 raise
437 437 raise util.Abort(_('no rebase in progress'))
438 438
439 439 def abort(repo, originalwd, target, state):
440 440 'Restore the repository to its original state'
441 441 if set(repo.changelog.descendants(target)) - set(state.values()):
442 442 repo.ui.warn(_("warning: new changesets detected on target branch, "
443 443 "can't abort\n"))
444 444 return -1
445 445 else:
446 446 # Strip from the first rebased revision
447 447 merge.update(repo, repo[originalwd].rev(), False, True, False)
448 448 rebased = filter(lambda x: x > -1 and x != target, state.values())
449 449 if rebased:
450 450 strippoint = min(rebased)
451 451 # no backup of rebased cset versions needed
452 452 repair.strip(repo.ui, repo, repo[strippoint].node())
453 453 clearstatus(repo)
454 454 repo.ui.warn(_('rebase aborted\n'))
455 455 return 0
456 456
457 457 def buildstate(repo, dest, src, base, detach):
458 458 'Define which revisions are going to be rebased and where'
459 459 targetancestors = set()
460 460 detachset = set()
461 461
462 462 if not dest:
463 463 # Destination defaults to the latest revision in the current branch
464 464 branch = repo[None].branch()
465 465 dest = repo[branch].rev()
466 466 else:
467 467 dest = repo[dest].rev()
468 468
469 469 # This check isn't strictly necessary, since mq detects commits over an
470 470 # applied patch. But it prevents messing up the working directory when
471 471 # a partially completed rebase is blocked by mq.
472 472 if 'qtip' in repo.tags() and (repo[dest].node() in
473 473 [s.node for s in repo.mq.applied]):
474 474 raise util.Abort(_('cannot rebase onto an applied mq patch'))
475 475
476 476 if src:
477 477 commonbase = repo[src].ancestor(repo[dest])
478 478 if commonbase == repo[src]:
479 479 raise util.Abort(_('source is ancestor of destination'))
480 480 if commonbase == repo[dest]:
481 481 raise util.Abort(_('source is descendant of destination'))
482 482 source = repo[src].rev()
483 483 if detach:
484 484 # We need to keep track of source's ancestors up to the common base
485 485 srcancestors = set(repo.changelog.ancestors(source))
486 486 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
487 487 detachset = srcancestors - baseancestors
488 detachset.remove(commonbase.rev())
488 detachset.discard(commonbase.rev())
489 489 else:
490 490 if base:
491 491 cwd = repo[base].rev()
492 492 else:
493 493 cwd = repo['.'].rev()
494 494
495 495 if cwd == dest:
496 496 repo.ui.debug('source and destination are the same\n')
497 497 return None
498 498
499 499 targetancestors = set(repo.changelog.ancestors(dest))
500 500 if cwd in targetancestors:
501 501 repo.ui.debug('source is ancestor of destination\n')
502 502 return None
503 503
504 504 cwdancestors = set(repo.changelog.ancestors(cwd))
505 505 if dest in cwdancestors:
506 506 repo.ui.debug('source is descendant of destination\n')
507 507 return None
508 508
509 509 cwdancestors.add(cwd)
510 510 rebasingbranch = cwdancestors - targetancestors
511 511 source = min(rebasingbranch)
512 512
513 513 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
514 514 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
515 515 state.update(dict.fromkeys(detachset, nullmerge))
516 516 state[source] = nullrev
517 517 return repo['.'].rev(), repo[dest].rev(), state
518 518
519 519 def pullrebase(orig, ui, repo, *args, **opts):
520 520 'Call rebase after pull if the latter has been invoked with --rebase'
521 521 if opts.get('rebase'):
522 522 if opts.get('update'):
523 523 del opts['update']
524 524 ui.debug('--update and --rebase are not compatible, ignoring '
525 525 'the update flag\n')
526 526
527 527 cmdutil.bail_if_changed(repo)
528 528 revsprepull = len(repo)
529 529 origpostincoming = commands.postincoming
530 530 def _dummy(*args, **kwargs):
531 531 pass
532 532 commands.postincoming = _dummy
533 533 try:
534 534 orig(ui, repo, *args, **opts)
535 535 finally:
536 536 commands.postincoming = origpostincoming
537 537 revspostpull = len(repo)
538 538 if revspostpull > revsprepull:
539 539 rebase(ui, repo, **opts)
540 540 branch = repo[None].branch()
541 541 dest = repo[branch].rev()
542 542 if dest != repo['.'].rev():
543 543 # there was nothing to rebase we force an update
544 544 hg.update(repo, dest)
545 545 else:
546 546 orig(ui, repo, *args, **opts)
547 547
548 548 def uisetup(ui):
549 549 'Replace pull with a decorator to provide --rebase option'
550 550 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
551 551 entry[1].append(('', 'rebase', None,
552 552 _("rebase working directory to branch head"))
553 553 )
554 554
555 555 cmdtable = {
556 556 "rebase":
557 557 (rebase,
558 558 [
559 559 ('s', 'source', '',
560 560 _('rebase from the specified changeset'), _('REV')),
561 561 ('b', 'base', '',
562 562 _('rebase from the base of the specified changeset '
563 563 '(up to greatest common ancestor of base and dest)'),
564 564 _('REV')),
565 565 ('d', 'dest', '',
566 566 _('rebase onto the specified changeset'), _('REV')),
567 567 ('', 'collapse', False, _('collapse the rebased changesets')),
568 568 ('', 'keep', False, _('keep original changesets')),
569 569 ('', 'keepbranches', False, _('keep original branch names')),
570 570 ('', 'detach', False, _('force detaching of source from its original '
571 571 'branch')),
572 572 ('c', 'continue', False, _('continue an interrupted rebase')),
573 573 ('a', 'abort', False, _('abort an interrupted rebase'))] +
574 574 templateopts,
575 575 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
576 576 'hg rebase {-a|-c}'))
577 577 }
@@ -1,193 +1,234 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > graphlog=
4 4 > rebase=
5 5 >
6 6 > [alias]
7 7 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
8 8 > EOF
9 9
10 10
11 11 $ hg init a
12 12 $ cd a
13 13
14 14 $ echo A > A
15 15 $ hg ci -Am A
16 16 adding A
17 17
18 18 $ echo B > B
19 19 $ hg ci -Am B
20 20 adding B
21 21
22 22 $ echo C > C
23 23 $ hg ci -Am C
24 24 adding C
25 25
26 26 $ echo D > D
27 27 $ hg ci -Am D
28 28 adding D
29 29
30 30 $ hg up -q -C 0
31 31
32 32 $ echo E > E
33 33 $ hg ci -Am E
34 34 adding E
35 35 created new head
36 36
37 37 $ cd ..
38 38
39 39
40 40 Rebasing D onto E detaching from C:
41 41
42 42 $ hg clone -q -u . a a1
43 43 $ cd a1
44 44
45 45 $ hg tglog
46 46 @ 4: 'E'
47 47 |
48 48 | o 3: 'D'
49 49 | |
50 50 | o 2: 'C'
51 51 | |
52 52 | o 1: 'B'
53 53 |/
54 54 o 0: 'A'
55 55
56 56 $ hg rebase --detach -s 3 -d 4
57 57 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
58 58
59 59 $ hg tglog
60 60 @ 4: 'D'
61 61 |
62 62 o 3: 'E'
63 63 |
64 64 | o 2: 'C'
65 65 | |
66 66 | o 1: 'B'
67 67 |/
68 68 o 0: 'A'
69 69
70 70 $ hg manifest
71 71 A
72 72 D
73 73 E
74 74
75 75 $ cd ..
76 76
77 77
78 78 Rebasing C onto E detaching from B:
79 79
80 80 $ hg clone -q -u . a a2
81 81 $ cd a2
82 82
83 83 $ hg tglog
84 84 @ 4: 'E'
85 85 |
86 86 | o 3: 'D'
87 87 | |
88 88 | o 2: 'C'
89 89 | |
90 90 | o 1: 'B'
91 91 |/
92 92 o 0: 'A'
93 93
94 94 $ hg rebase --detach -s 2 -d 4
95 95 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
96 96
97 97 $ hg tglog
98 98 @ 4: 'D'
99 99 |
100 100 o 3: 'C'
101 101 |
102 102 o 2: 'E'
103 103 |
104 104 | o 1: 'B'
105 105 |/
106 106 o 0: 'A'
107 107
108 108 $ hg manifest
109 109 A
110 110 C
111 111 D
112 112 E
113 113
114 114 $ cd ..
115 115
116 116
117 117 Rebasing B onto E using detach (same as not using it):
118 118
119 119 $ hg clone -q -u . a a3
120 120 $ cd a3
121 121
122 122 $ hg tglog
123 123 @ 4: 'E'
124 124 |
125 125 | o 3: 'D'
126 126 | |
127 127 | o 2: 'C'
128 128 | |
129 129 | o 1: 'B'
130 130 |/
131 131 o 0: 'A'
132 132
133 133 $ hg rebase --detach -s 1 -d 4
134 134 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
135 135
136 136 $ hg tglog
137 137 @ 4: 'D'
138 138 |
139 139 o 3: 'C'
140 140 |
141 141 o 2: 'B'
142 142 |
143 143 o 1: 'E'
144 144 |
145 145 o 0: 'A'
146 146
147 147 $ hg manifest
148 148 A
149 149 B
150 150 C
151 151 D
152 152 E
153 153
154 154 $ cd ..
155 155
156 156
157 157 Rebasing C onto E detaching from B and collapsing:
158 158
159 159 $ hg clone -q -u . a a4
160 160 $ cd a4
161 161
162 162 $ hg tglog
163 163 @ 4: 'E'
164 164 |
165 165 | o 3: 'D'
166 166 | |
167 167 | o 2: 'C'
168 168 | |
169 169 | o 1: 'B'
170 170 |/
171 171 o 0: 'A'
172 172
173 173 $ hg rebase --detach --collapse -s 2 -d 4
174 174 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
175 175
176 176 $ hg tglog
177 177 @ 3: 'Collapsed revision
178 178 | * C
179 179 | * D'
180 180 o 2: 'E'
181 181 |
182 182 | o 1: 'B'
183 183 |/
184 184 o 0: 'A'
185 185
186 186 $ hg manifest
187 187 A
188 188 C
189 189 D
190 190 E
191 191
192 192 $ cd ..
193 193
194 Rebasing across null as ancestor
195 $ hg clone -q -U a a5
196
197 $ cd a5
198
199 $ echo x > x
200
201 $ hg add x
202
203 $ hg ci -m "extra branch"
204 created new head
205
206 $ hg tglog
207 @ 5: 'extra branch'
208
209 o 4: 'E'
210 |
211 | o 3: 'D'
212 | |
213 | o 2: 'C'
214 | |
215 | o 1: 'B'
216 |/
217 o 0: 'A'
218
219 $ hg rebase --detach -s 1 -d tip
220 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
221
222 $ hg tglog
223 @ 5: 'D'
224 |
225 o 4: 'C'
226 |
227 o 3: 'B'
228 |
229 o 2: 'extra branch'
230
231 o 1: 'E'
232 |
233 o 0: 'A'
234
General Comments 0
You need to be logged in to leave comments. Login now