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