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