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