##// END OF EJS Templates
rebase: fix 'rebase onto %d starting from %s' - show root instead of list repr...
Mads Kiilerich -
r20545:5936058f default
parent child Browse files
Show More
@@ -1,948 +1,948
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 or there are
132 132 unresolved conflicts.
133 133 """
134 134 originalwd = target = None
135 135 activebookmark = None
136 136 external = nullrev
137 137 state = {}
138 138 skipped = set()
139 139 targetancestors = set()
140 140
141 141 editor = None
142 142 if opts.get('edit'):
143 143 editor = cmdutil.commitforceeditor
144 144
145 145 lock = wlock = None
146 146 try:
147 147 wlock = repo.wlock()
148 148 lock = repo.lock()
149 149
150 150 # Validate input and define rebasing points
151 151 destf = opts.get('dest', None)
152 152 srcf = opts.get('source', None)
153 153 basef = opts.get('base', None)
154 154 revf = opts.get('rev', [])
155 155 contf = opts.get('continue')
156 156 abortf = opts.get('abort')
157 157 collapsef = opts.get('collapse', False)
158 158 collapsemsg = cmdutil.logmessage(ui, opts)
159 159 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
160 160 extrafns = [_savegraft]
161 161 if e:
162 162 extrafns = [e]
163 163 keepf = opts.get('keep', False)
164 164 keepbranchesf = opts.get('keepbranches', False)
165 165 # keepopen is not meant for use on the command line, but by
166 166 # other extensions
167 167 keepopen = opts.get('keepopen', False)
168 168
169 169 if collapsemsg and not collapsef:
170 170 raise util.Abort(
171 171 _('message can only be specified with collapse'))
172 172
173 173 if contf or abortf:
174 174 if contf and abortf:
175 175 raise util.Abort(_('cannot use both abort and continue'))
176 176 if collapsef:
177 177 raise util.Abort(
178 178 _('cannot use collapse with continue or abort'))
179 179 if srcf or basef or destf:
180 180 raise util.Abort(
181 181 _('abort and continue do not allow specifying revisions'))
182 182 if opts.get('tool', False):
183 183 ui.warn(_('tool option will be ignored\n'))
184 184
185 185 try:
186 186 (originalwd, target, state, skipped, collapsef, keepf,
187 187 keepbranchesf, external, activebookmark) = restorestatus(repo)
188 188 except error.RepoLookupError:
189 189 if abortf:
190 190 clearstatus(repo)
191 191 repo.ui.warn(_('rebase aborted (no revision is removed,'
192 192 ' only broken state is cleared)\n'))
193 193 return 0
194 194 else:
195 195 msg = _('cannot continue inconsistent rebase')
196 196 hint = _('use "hg rebase --abort" to clear broken state')
197 197 raise util.Abort(msg, hint=hint)
198 198 if abortf:
199 199 return abort(repo, originalwd, target, state)
200 200 else:
201 201 if srcf and basef:
202 202 raise util.Abort(_('cannot specify both a '
203 203 'source and a base'))
204 204 if revf and basef:
205 205 raise util.Abort(_('cannot specify both a '
206 206 'revision and a base'))
207 207 if revf and srcf:
208 208 raise util.Abort(_('cannot specify both a '
209 209 'revision and a source'))
210 210
211 211 cmdutil.checkunfinished(repo)
212 212 cmdutil.bailifchanged(repo)
213 213
214 214 if not destf:
215 215 # Destination defaults to the latest revision in the
216 216 # current branch
217 217 branch = repo[None].branch()
218 218 dest = repo[branch]
219 219 else:
220 220 dest = scmutil.revsingle(repo, destf)
221 221
222 222 if revf:
223 223 rebaseset = scmutil.revrange(repo, revf)
224 224 if not rebaseset:
225 225 raise util.Abort(_('empty "rev" revision set - '
226 226 'nothing to rebase'))
227 227 elif srcf:
228 228 src = scmutil.revrange(repo, [srcf])
229 229 if not src:
230 230 raise util.Abort(_('empty "source" revision set - '
231 231 'nothing to rebase'))
232 232 rebaseset = repo.revs('(%ld)::', src)
233 233 assert rebaseset
234 234 else:
235 235 base = scmutil.revrange(repo, [basef or '.'])
236 236 if not base:
237 237 raise util.Abort(_('empty "base" revision set - '
238 238 "can't compute rebase set"))
239 239 rebaseset = repo.revs(
240 240 '(children(ancestor(%ld, %d)) and ::(%ld))::',
241 241 base, dest, base)
242 242 if not rebaseset:
243 243 if base == [dest.rev()]:
244 244 if basef:
245 245 ui.status(_('nothing to rebase - %s is both "base"'
246 246 ' and destination\n') % dest)
247 247 else:
248 248 ui.status(_('nothing to rebase - working directory '
249 249 'parent is also destination\n'))
250 250 elif not repo.revs('%ld - ::%d', base, dest):
251 251 if basef:
252 252 ui.status(_('nothing to rebase - "base" %s is '
253 253 'already an ancestor of destination '
254 254 '%s\n') %
255 255 ('+'.join(str(repo[r]) for r in base),
256 256 dest))
257 257 else:
258 258 ui.status(_('nothing to rebase - working '
259 259 'directory parent is already an '
260 260 'ancestor of destination %s\n') % dest)
261 261 else: # can it happen?
262 262 ui.status(_('nothing to rebase from %s to %s\n') %
263 263 ('+'.join(str(repo[r]) for r in base), dest))
264 264 return 1
265 265
266 266 if (not (keepf or obsolete._enabled)
267 267 and repo.revs('first(children(%ld) - %ld)',
268 268 rebaseset, rebaseset)):
269 269 raise util.Abort(
270 270 _("can't remove original changesets with"
271 271 " unrebased descendants"),
272 272 hint=_('use --keep to keep original changesets'))
273 273
274 274 result = buildstate(repo, dest, rebaseset, collapsef)
275 275 if not result:
276 276 # Empty state built, nothing to rebase
277 277 ui.status(_('nothing to rebase\n'))
278 278 return 1
279 279
280 280 root = min(rebaseset)
281 281 if not keepf and not repo[root].mutable():
282 282 raise util.Abort(_("can't rebase immutable changeset %s")
283 283 % repo[root],
284 284 hint=_('see hg help phases for details'))
285 285
286 286 originalwd, target, state = result
287 287 if collapsef:
288 288 targetancestors = repo.changelog.ancestors([target],
289 289 inclusive=True)
290 290 external = externalparent(repo, state, targetancestors)
291 291
292 292 if keepbranchesf:
293 293 # insert _savebranch at the start of extrafns so if
294 294 # there's a user-provided extrafn it can clobber branch if
295 295 # desired
296 296 extrafns.insert(0, _savebranch)
297 297 if collapsef:
298 298 branches = set()
299 299 for rev in state:
300 300 branches.add(repo[rev].branch())
301 301 if len(branches) > 1:
302 302 raise util.Abort(_('cannot collapse multiple named '
303 303 'branches'))
304 304
305 305 # Rebase
306 306 if not targetancestors:
307 307 targetancestors = repo.changelog.ancestors([target], inclusive=True)
308 308
309 309 # Keep track of the current bookmarks in order to reset them later
310 310 currentbookmarks = repo._bookmarks.copy()
311 311 activebookmark = activebookmark or repo._bookmarkcurrent
312 312 if activebookmark:
313 313 bookmarks.unsetcurrent(repo)
314 314
315 315 extrafn = _makeextrafn(extrafns)
316 316
317 317 sortedstate = sorted(state)
318 318 total = len(sortedstate)
319 319 pos = 0
320 320 for rev in sortedstate:
321 321 pos += 1
322 322 if state[rev] == -1:
323 323 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
324 324 _('changesets'), total)
325 325 p1, p2 = defineparents(repo, rev, target, state,
326 326 targetancestors)
327 327 storestatus(repo, originalwd, target, state, collapsef, keepf,
328 328 keepbranchesf, external, activebookmark)
329 329 if len(repo.parents()) == 2:
330 330 repo.ui.debug('resuming interrupted rebase\n')
331 331 else:
332 332 try:
333 333 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
334 334 stats = rebasenode(repo, rev, p1, state, collapsef)
335 335 if stats and stats[3] > 0:
336 336 raise error.InterventionRequired(
337 337 _('unresolved conflicts (see hg '
338 338 'resolve, then hg rebase --continue)'))
339 339 finally:
340 340 ui.setconfig('ui', 'forcemerge', '')
341 341 cmdutil.duplicatecopies(repo, rev, target)
342 342 if not collapsef:
343 343 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
344 344 editor=editor)
345 345 else:
346 346 # Skip commit if we are collapsing
347 347 repo.setparents(repo[p1].node())
348 348 newrev = None
349 349 # Update the state
350 350 if newrev is not None:
351 351 state[rev] = repo[newrev].rev()
352 352 else:
353 353 if not collapsef:
354 354 ui.note(_('no changes, revision %d skipped\n') % rev)
355 355 ui.debug('next revision set to %s\n' % p1)
356 356 skipped.add(rev)
357 357 state[rev] = p1
358 358
359 359 ui.progress(_('rebasing'), None)
360 360 ui.note(_('rebase merging completed\n'))
361 361
362 362 if collapsef and not keepopen:
363 363 p1, p2 = defineparents(repo, min(state), target,
364 364 state, targetancestors)
365 365 if collapsemsg:
366 366 commitmsg = collapsemsg
367 367 else:
368 368 commitmsg = 'Collapsed revision'
369 369 for rebased in state:
370 370 if rebased not in skipped and state[rebased] > nullmerge:
371 371 commitmsg += '\n* %s' % repo[rebased].description()
372 372 commitmsg = ui.edit(commitmsg, repo.ui.username())
373 373 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
374 374 extrafn=extrafn, editor=editor)
375 375 for oldrev in state.iterkeys():
376 376 if state[oldrev] > nullmerge:
377 377 state[oldrev] = newrev
378 378
379 379 if 'qtip' in repo.tags():
380 380 updatemq(repo, state, skipped, **opts)
381 381
382 382 if currentbookmarks:
383 383 # Nodeids are needed to reset bookmarks
384 384 nstate = {}
385 385 for k, v in state.iteritems():
386 386 if v > nullmerge:
387 387 nstate[repo[k].node()] = repo[v].node()
388 388 # XXX this is the same as dest.node() for the non-continue path --
389 389 # this should probably be cleaned up
390 390 targetnode = repo[target].node()
391 391
392 392 # restore original working directory
393 393 # (we do this before stripping)
394 394 newwd = state.get(originalwd, originalwd)
395 395 if newwd not in [c.rev() for c in repo[None].parents()]:
396 396 ui.note(_("update back to initial working directory parent\n"))
397 397 hg.updaterepo(repo, newwd, False)
398 398
399 399 if not keepf:
400 400 collapsedas = None
401 401 if collapsef:
402 402 collapsedas = newrev
403 403 clearrebased(ui, repo, state, skipped, collapsedas)
404 404
405 405 if currentbookmarks:
406 406 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
407 407 if activebookmark not in repo._bookmarks:
408 408 # active bookmark was divergent one and has been deleted
409 409 activebookmark = None
410 410
411 411 clearstatus(repo)
412 412 ui.note(_("rebase completed\n"))
413 413 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
414 414 if skipped:
415 415 ui.note(_("%d revisions have been skipped\n") % len(skipped))
416 416
417 417 if (activebookmark and
418 418 repo['.'].node() == repo._bookmarks[activebookmark]):
419 419 bookmarks.setcurrent(repo, activebookmark)
420 420
421 421 finally:
422 422 release(lock, wlock)
423 423
424 424 def externalparent(repo, state, targetancestors):
425 425 """Return the revision that should be used as the second parent
426 426 when the revisions in state is collapsed on top of targetancestors.
427 427 Abort if there is more than one parent.
428 428 """
429 429 parents = set()
430 430 source = min(state)
431 431 for rev in state:
432 432 if rev == source:
433 433 continue
434 434 for p in repo[rev].parents():
435 435 if (p.rev() not in state
436 436 and p.rev() not in targetancestors):
437 437 parents.add(p.rev())
438 438 if not parents:
439 439 return nullrev
440 440 if len(parents) == 1:
441 441 return parents.pop()
442 442 raise util.Abort(_('unable to collapse on top of %s, there is more '
443 443 'than one external parent: %s') %
444 444 (max(targetancestors),
445 445 ', '.join(str(p) for p in sorted(parents))))
446 446
447 447 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
448 448 'Commit the changes and store useful information in extra'
449 449 try:
450 450 repo.setparents(repo[p1].node(), repo[p2].node())
451 451 ctx = repo[rev]
452 452 if commitmsg is None:
453 453 commitmsg = ctx.description()
454 454 extra = {'rebase_source': ctx.hex()}
455 455 if extrafn:
456 456 extrafn(ctx, extra)
457 457 # Commit might fail if unresolved files exist
458 458 newrev = repo.commit(text=commitmsg, user=ctx.user(),
459 459 date=ctx.date(), extra=extra, editor=editor)
460 460 repo.dirstate.setbranch(repo[newrev].branch())
461 461 targetphase = max(ctx.phase(), phases.draft)
462 462 # retractboundary doesn't overwrite upper phase inherited from parent
463 463 newnode = repo[newrev].node()
464 464 if newnode:
465 465 phases.retractboundary(repo, targetphase, [newnode])
466 466 return newrev
467 467 except util.Abort:
468 468 # Invalidate the previous setparents
469 469 repo.dirstate.invalidate()
470 470 raise
471 471
472 472 def rebasenode(repo, rev, p1, state, collapse):
473 473 'Rebase a single revision'
474 474 # Merge phase
475 475 # Update to target and merge it with local
476 476 if repo['.'].rev() != repo[p1].rev():
477 477 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
478 478 merge.update(repo, p1, False, True, False)
479 479 else:
480 480 repo.ui.debug(" already in target\n")
481 481 repo.dirstate.write()
482 482 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
483 483 if repo[rev].rev() == repo[min(state)].rev():
484 484 # Case (1) initial changeset of a non-detaching rebase.
485 485 # Let the merge mechanism find the base itself.
486 486 base = None
487 487 elif not repo[rev].p2():
488 488 # Case (2) detaching the node with a single parent, use this parent
489 489 base = repo[rev].p1().node()
490 490 else:
491 491 # In case of merge, we need to pick the right parent as merge base.
492 492 #
493 493 # Imagine we have:
494 494 # - M: currently rebase revision in this step
495 495 # - A: one parent of M
496 496 # - B: second parent of M
497 497 # - D: destination of this merge step (p1 var)
498 498 #
499 499 # If we are rebasing on D, D is the successors of A or B. The right
500 500 # merge base is the one D succeed to. We pretend it is B for the rest
501 501 # of this comment
502 502 #
503 503 # If we pick B as the base, the merge involves:
504 504 # - changes from B to M (actual changeset payload)
505 505 # - changes from B to D (induced by rebase) as D is a rebased
506 506 # version of B)
507 507 # Which exactly represent the rebase operation.
508 508 #
509 509 # If we pick the A as the base, the merge involves
510 510 # - changes from A to M (actual changeset payload)
511 511 # - changes from A to D (with include changes between unrelated A and B
512 512 # plus changes induced by rebase)
513 513 # Which does not represent anything sensible and creates a lot of
514 514 # conflicts.
515 515 for p in repo[rev].parents():
516 516 if state.get(p.rev()) == repo[p1].rev():
517 517 base = p.node()
518 518 break
519 519 if base is not None:
520 520 repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
521 521 # When collapsing in-place, the parent is the common ancestor, we
522 522 # have to allow merging with it.
523 523 return merge.update(repo, rev, True, True, False, base, collapse)
524 524
525 525 def nearestrebased(repo, rev, state):
526 526 """return the nearest ancestors of rev in the rebase result"""
527 527 rebased = [r for r in state if state[r] > nullmerge]
528 528 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
529 529 if candidates:
530 530 return state[candidates[0]]
531 531 else:
532 532 return None
533 533
534 534 def defineparents(repo, rev, target, state, targetancestors):
535 535 'Return the new parent relationship of the revision that will be rebased'
536 536 parents = repo[rev].parents()
537 537 p1 = p2 = nullrev
538 538
539 539 P1n = parents[0].rev()
540 540 if P1n in targetancestors:
541 541 p1 = target
542 542 elif P1n in state:
543 543 if state[P1n] == nullmerge:
544 544 p1 = target
545 545 elif state[P1n] == revignored:
546 546 p1 = nearestrebased(repo, P1n, state)
547 547 if p1 is None:
548 548 p1 = target
549 549 else:
550 550 p1 = state[P1n]
551 551 else: # P1n external
552 552 p1 = target
553 553 p2 = P1n
554 554
555 555 if len(parents) == 2 and parents[1].rev() not in targetancestors:
556 556 P2n = parents[1].rev()
557 557 # interesting second parent
558 558 if P2n in state:
559 559 if p1 == target: # P1n in targetancestors or external
560 560 p1 = state[P2n]
561 561 elif state[P2n] == revignored:
562 562 p2 = nearestrebased(repo, P2n, state)
563 563 if p2 is None:
564 564 # no ancestors rebased yet, detach
565 565 p2 = target
566 566 else:
567 567 p2 = state[P2n]
568 568 else: # P2n external
569 569 if p2 != nullrev: # P1n external too => rev is a merged revision
570 570 raise util.Abort(_('cannot use revision %d as base, result '
571 571 'would have 3 parents') % rev)
572 572 p2 = P2n
573 573 repo.ui.debug(" future parents are %d and %d\n" %
574 574 (repo[p1].rev(), repo[p2].rev()))
575 575 return p1, p2
576 576
577 577 def isagitpatch(repo, patchname):
578 578 'Return true if the given patch is in git format'
579 579 mqpatch = os.path.join(repo.mq.path, patchname)
580 580 for line in patch.linereader(file(mqpatch, 'rb')):
581 581 if line.startswith('diff --git'):
582 582 return True
583 583 return False
584 584
585 585 def updatemq(repo, state, skipped, **opts):
586 586 'Update rebased mq patches - finalize and then import them'
587 587 mqrebase = {}
588 588 mq = repo.mq
589 589 original_series = mq.fullseries[:]
590 590 skippedpatches = set()
591 591
592 592 for p in mq.applied:
593 593 rev = repo[p.node].rev()
594 594 if rev in state:
595 595 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
596 596 (rev, p.name))
597 597 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
598 598 else:
599 599 # Applied but not rebased, not sure this should happen
600 600 skippedpatches.add(p.name)
601 601
602 602 if mqrebase:
603 603 mq.finish(repo, mqrebase.keys())
604 604
605 605 # We must start import from the newest revision
606 606 for rev in sorted(mqrebase, reverse=True):
607 607 if rev not in skipped:
608 608 name, isgit = mqrebase[rev]
609 609 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
610 610 mq.qimport(repo, (), patchname=name, git=isgit,
611 611 rev=[str(state[rev])])
612 612 else:
613 613 # Rebased and skipped
614 614 skippedpatches.add(mqrebase[rev][0])
615 615
616 616 # Patches were either applied and rebased and imported in
617 617 # order, applied and removed or unapplied. Discard the removed
618 618 # ones while preserving the original series order and guards.
619 619 newseries = [s for s in original_series
620 620 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
621 621 mq.fullseries[:] = newseries
622 622 mq.seriesdirty = True
623 623 mq.savedirty()
624 624
625 625 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
626 626 'Move bookmarks to their correct changesets, and delete divergent ones'
627 627 marks = repo._bookmarks
628 628 for k, v in originalbookmarks.iteritems():
629 629 if v in nstate:
630 630 # update the bookmarks for revs that have moved
631 631 marks[k] = nstate[v]
632 632 bookmarks.deletedivergent(repo, [targetnode], k)
633 633
634 634 marks.write()
635 635
636 636 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
637 637 external, activebookmark):
638 638 'Store the current status to allow recovery'
639 639 f = repo.opener("rebasestate", "w")
640 640 f.write(repo[originalwd].hex() + '\n')
641 641 f.write(repo[target].hex() + '\n')
642 642 f.write(repo[external].hex() + '\n')
643 643 f.write('%d\n' % int(collapse))
644 644 f.write('%d\n' % int(keep))
645 645 f.write('%d\n' % int(keepbranches))
646 646 f.write('%s\n' % (activebookmark or ''))
647 647 for d, v in state.iteritems():
648 648 oldrev = repo[d].hex()
649 649 if v > nullmerge:
650 650 newrev = repo[v].hex()
651 651 else:
652 652 newrev = v
653 653 f.write("%s:%s\n" % (oldrev, newrev))
654 654 f.close()
655 655 repo.ui.debug('rebase status stored\n')
656 656
657 657 def clearstatus(repo):
658 658 'Remove the status files'
659 659 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
660 660
661 661 def restorestatus(repo):
662 662 'Restore a previously stored status'
663 663 try:
664 664 keepbranches = None
665 665 target = None
666 666 collapse = False
667 667 external = nullrev
668 668 activebookmark = None
669 669 state = {}
670 670 f = repo.opener("rebasestate")
671 671 for i, l in enumerate(f.read().splitlines()):
672 672 if i == 0:
673 673 originalwd = repo[l].rev()
674 674 elif i == 1:
675 675 target = repo[l].rev()
676 676 elif i == 2:
677 677 external = repo[l].rev()
678 678 elif i == 3:
679 679 collapse = bool(int(l))
680 680 elif i == 4:
681 681 keep = bool(int(l))
682 682 elif i == 5:
683 683 keepbranches = bool(int(l))
684 684 elif i == 6 and not (len(l) == 81 and ':' in l):
685 685 # line 6 is a recent addition, so for backwards compatibility
686 686 # check that the line doesn't look like the oldrev:newrev lines
687 687 activebookmark = l
688 688 else:
689 689 oldrev, newrev = l.split(':')
690 690 if newrev in (str(nullmerge), str(revignored)):
691 691 state[repo[oldrev].rev()] = int(newrev)
692 692 else:
693 693 state[repo[oldrev].rev()] = repo[newrev].rev()
694 694
695 695 if keepbranches is None:
696 696 raise util.Abort(_('.hg/rebasestate is incomplete'))
697 697
698 698 skipped = set()
699 699 # recompute the set of skipped revs
700 700 if not collapse:
701 701 seen = set([target])
702 702 for old, new in sorted(state.items()):
703 703 if new != nullrev and new in seen:
704 704 skipped.add(old)
705 705 seen.add(new)
706 706 repo.ui.debug('computed skipped revs: %s\n' % skipped)
707 707 repo.ui.debug('rebase status resumed\n')
708 708 return (originalwd, target, state, skipped,
709 709 collapse, keep, keepbranches, external, activebookmark)
710 710 except IOError, err:
711 711 if err.errno != errno.ENOENT:
712 712 raise
713 713 raise util.Abort(_('no rebase in progress'))
714 714
715 715 def inrebase(repo, originalwd, state):
716 716 '''check whether the working dir is in an interrupted rebase'''
717 717 parents = [p.rev() for p in repo.parents()]
718 718 if originalwd in parents:
719 719 return True
720 720
721 721 for newrev in state.itervalues():
722 722 if newrev in parents:
723 723 return True
724 724
725 725 return False
726 726
727 727 def abort(repo, originalwd, target, state):
728 728 'Restore the repository to its original state'
729 729 dstates = [s for s in state.values() if s > nullrev]
730 730 immutable = [d for d in dstates if not repo[d].mutable()]
731 731 cleanup = True
732 732 if immutable:
733 733 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
734 734 % ', '.join(str(repo[r]) for r in immutable),
735 735 hint=_('see hg help phases for details'))
736 736 cleanup = False
737 737
738 738 descendants = set()
739 739 if dstates:
740 740 descendants = set(repo.changelog.descendants(dstates))
741 741 if descendants - set(dstates):
742 742 repo.ui.warn(_("warning: new changesets detected on target branch, "
743 743 "can't strip\n"))
744 744 cleanup = False
745 745
746 746 if cleanup:
747 747 # Update away from the rebase if necessary
748 748 if inrebase(repo, originalwd, state):
749 749 merge.update(repo, repo[originalwd].rev(), False, True, False)
750 750
751 751 # Strip from the first rebased revision
752 752 rebased = filter(lambda x: x > -1 and x != target, state.values())
753 753 if rebased:
754 754 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
755 755 # no backup of rebased cset versions needed
756 756 repair.strip(repo.ui, repo, strippoints)
757 757
758 758 clearstatus(repo)
759 759 repo.ui.warn(_('rebase aborted\n'))
760 760 return 0
761 761
762 762 def buildstate(repo, dest, rebaseset, collapse):
763 763 '''Define which revisions are going to be rebased and where
764 764
765 765 repo: repo
766 766 dest: context
767 767 rebaseset: set of rev
768 768 '''
769 769
770 770 # This check isn't strictly necessary, since mq detects commits over an
771 771 # applied patch. But it prevents messing up the working directory when
772 772 # a partially completed rebase is blocked by mq.
773 773 if 'qtip' in repo.tags() and (dest.node() in
774 774 [s.node for s in repo.mq.applied]):
775 775 raise util.Abort(_('cannot rebase onto an applied mq patch'))
776 776
777 777 roots = list(repo.set('roots(%ld)', rebaseset))
778 778 if not roots:
779 779 raise util.Abort(_('no matching revisions'))
780 780 roots.sort()
781 781 state = {}
782 782 detachset = set()
783 783 for root in roots:
784 784 commonbase = root.ancestor(dest)
785 785 if commonbase == root:
786 786 raise util.Abort(_('source is ancestor of destination'))
787 787 if commonbase == dest:
788 788 samebranch = root.branch() == dest.branch()
789 789 if not collapse and samebranch and root in dest.children():
790 790 repo.ui.debug('source is a child of destination\n')
791 791 return None
792 792
793 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
793 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
794 794 state.update(dict.fromkeys(rebaseset, nullrev))
795 795 # Rebase tries to turn <dest> into a parent of <root> while
796 796 # preserving the number of parents of rebased changesets:
797 797 #
798 798 # - A changeset with a single parent will always be rebased as a
799 799 # changeset with a single parent.
800 800 #
801 801 # - A merge will be rebased as merge unless its parents are both
802 802 # ancestors of <dest> or are themselves in the rebased set and
803 803 # pruned while rebased.
804 804 #
805 805 # If one parent of <root> is an ancestor of <dest>, the rebased
806 806 # version of this parent will be <dest>. This is always true with
807 807 # --base option.
808 808 #
809 809 # Otherwise, we need to *replace* the original parents with
810 810 # <dest>. This "detaches" the rebased set from its former location
811 811 # and rebases it onto <dest>. Changes introduced by ancestors of
812 812 # <root> not common with <dest> (the detachset, marked as
813 813 # nullmerge) are "removed" from the rebased changesets.
814 814 #
815 815 # - If <root> has a single parent, set it to <dest>.
816 816 #
817 817 # - If <root> is a merge, we cannot decide which parent to
818 818 # replace, the rebase operation is not clearly defined.
819 819 #
820 820 # The table below sums up this behavior:
821 821 #
822 822 # +------------------+----------------------+-------------------------+
823 823 # | | one parent | merge |
824 824 # +------------------+----------------------+-------------------------+
825 825 # | parent in | new parent is <dest> | parents in ::<dest> are |
826 826 # | ::<dest> | | remapped to <dest> |
827 827 # +------------------+----------------------+-------------------------+
828 828 # | unrelated source | new parent is <dest> | ambiguous, abort |
829 829 # +------------------+----------------------+-------------------------+
830 830 #
831 831 # The actual abort is handled by `defineparents`
832 832 if len(root.parents()) <= 1:
833 833 # ancestors of <root> not ancestors of <dest>
834 834 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
835 835 [root.rev()]))
836 836 for r in detachset:
837 837 if r not in state:
838 838 state[r] = nullmerge
839 839 if len(roots) > 1:
840 840 # If we have multiple roots, we may have "hole" in the rebase set.
841 841 # Rebase roots that descend from those "hole" should not be detached as
842 842 # other root are. We use the special `revignored` to inform rebase that
843 843 # the revision should be ignored but that `defineparents` should search
844 844 # a rebase destination that make sense regarding rebased topology.
845 845 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
846 846 for ignored in set(rebasedomain) - set(rebaseset):
847 847 state[ignored] = revignored
848 848 return repo['.'].rev(), dest.rev(), state
849 849
850 850 def clearrebased(ui, repo, state, skipped, collapsedas=None):
851 851 """dispose of rebased revision at the end of the rebase
852 852
853 853 If `collapsedas` is not None, the rebase was a collapse whose result if the
854 854 `collapsedas` node."""
855 855 if obsolete._enabled:
856 856 markers = []
857 857 for rev, newrev in sorted(state.items()):
858 858 if newrev >= 0:
859 859 if rev in skipped:
860 860 succs = ()
861 861 elif collapsedas is not None:
862 862 succs = (repo[collapsedas],)
863 863 else:
864 864 succs = (repo[newrev],)
865 865 markers.append((repo[rev], succs))
866 866 if markers:
867 867 obsolete.createmarkers(repo, markers)
868 868 else:
869 869 rebased = [rev for rev in state if state[rev] > nullmerge]
870 870 if rebased:
871 871 stripped = []
872 872 for root in repo.set('roots(%ld)', rebased):
873 873 if set(repo.changelog.descendants([root.rev()])) - set(state):
874 874 ui.warn(_("warning: new changesets detected "
875 875 "on source branch, not stripping\n"))
876 876 else:
877 877 stripped.append(root.node())
878 878 if stripped:
879 879 # backup the old csets by default
880 880 repair.strip(ui, repo, stripped, "all")
881 881
882 882
883 883 def pullrebase(orig, ui, repo, *args, **opts):
884 884 'Call rebase after pull if the latter has been invoked with --rebase'
885 885 if opts.get('rebase'):
886 886 if opts.get('update'):
887 887 del opts['update']
888 888 ui.debug('--update and --rebase are not compatible, ignoring '
889 889 'the update flag\n')
890 890
891 891 movemarkfrom = repo['.'].node()
892 892 revsprepull = len(repo)
893 893 origpostincoming = commands.postincoming
894 894 def _dummy(*args, **kwargs):
895 895 pass
896 896 commands.postincoming = _dummy
897 897 try:
898 898 orig(ui, repo, *args, **opts)
899 899 finally:
900 900 commands.postincoming = origpostincoming
901 901 revspostpull = len(repo)
902 902 if revspostpull > revsprepull:
903 903 # --rev option from pull conflict with rebase own --rev
904 904 # dropping it
905 905 if 'rev' in opts:
906 906 del opts['rev']
907 907 rebase(ui, repo, **opts)
908 908 branch = repo[None].branch()
909 909 dest = repo[branch].rev()
910 910 if dest != repo['.'].rev():
911 911 # there was nothing to rebase we force an update
912 912 hg.update(repo, dest)
913 913 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
914 914 ui.status(_("updating bookmark %s\n")
915 915 % repo._bookmarkcurrent)
916 916 else:
917 917 if opts.get('tool'):
918 918 raise util.Abort(_('--tool can only be used with --rebase'))
919 919 orig(ui, repo, *args, **opts)
920 920
921 921 def summaryhook(ui, repo):
922 922 if not os.path.exists(repo.join('rebasestate')):
923 923 return
924 924 try:
925 925 state = restorestatus(repo)[2]
926 926 except error.RepoLookupError:
927 927 # i18n: column positioning for "hg summary"
928 928 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
929 929 ui.write(msg)
930 930 return
931 931 numrebased = len([i for i in state.itervalues() if i != -1])
932 932 # i18n: column positioning for "hg summary"
933 933 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
934 934 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
935 935 ui.label(_('%d remaining'), 'rebase.remaining') %
936 936 (len(state) - numrebased)))
937 937
938 938 def uisetup(ui):
939 939 'Replace pull with a decorator to provide --rebase option'
940 940 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
941 941 entry[1].append(('', 'rebase', None,
942 942 _("rebase working directory to branch head")))
943 943 entry[1].append(('t', 'tool', '',
944 944 _("specify merge tool for rebase")))
945 945 cmdutil.summaryhooks.add('rebase', summaryhook)
946 946 cmdutil.unfinishedstates.append(
947 947 ['rebasestate', False, False, _('rebase in progress'),
948 948 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,306 +1,306
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 >
5 5 > [phases]
6 6 > publish=False
7 7 >
8 8 > [alias]
9 9 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches} {bookmarks}\n"
10 10 > EOF
11 11
12 12 $ hg init a
13 13 $ cd a
14 14 $ echo c1 >common
15 15 $ hg add common
16 16 $ hg ci -m C1
17 17
18 18 $ echo c2 >>common
19 19 $ hg ci -m C2
20 20
21 21 $ echo c3 >>common
22 22 $ hg ci -m C3
23 23
24 24 $ hg up -q -C 1
25 25
26 26 $ echo l1 >>extra
27 27 $ hg add extra
28 28 $ hg ci -m L1
29 29 created new head
30 30
31 31 $ sed -e 's/c2/l2/' common > common.new
32 32 $ mv common.new common
33 33 $ hg ci -m L2
34 34
35 35 $ echo l3 >> extra2
36 36 $ hg add extra2
37 37 $ hg ci -m L3
38 38 $ hg bookmark mybook
39 39
40 40 $ hg phase --force --secret 4
41 41
42 42 $ hg tglog
43 43 @ 5:secret 'L3' mybook
44 44 |
45 45 o 4:secret 'L2'
46 46 |
47 47 o 3:draft 'L1'
48 48 |
49 49 | o 2:draft 'C3'
50 50 |/
51 51 o 1:draft 'C2'
52 52 |
53 53 o 0:draft 'C1'
54 54
55 55 Try to call --continue:
56 56
57 57 $ hg rebase --continue
58 58 abort: no rebase in progress
59 59 [255]
60 60
61 61 Conflicting rebase:
62 62
63 63 $ hg rebase -s 3 -d 2
64 64 merging common
65 65 warning: conflicts during merge.
66 66 merging common incomplete! (edit conflicts, then use 'hg resolve --mark')
67 67 unresolved conflicts (see hg resolve, then hg rebase --continue)
68 68 [1]
69 69
70 70 Try to continue without solving the conflict:
71 71
72 72 $ hg rebase --continue
73 73 abort: unresolved merge conflicts (see hg help resolve)
74 74 [255]
75 75
76 76 Conclude rebase:
77 77
78 78 $ echo 'resolved merge' >common
79 79 $ hg resolve -m common
80 80 $ hg rebase --continue
81 81 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
82 82
83 83 $ hg tglog
84 84 @ 5:secret 'L3' mybook
85 85 |
86 86 o 4:secret 'L2'
87 87 |
88 88 o 3:draft 'L1'
89 89 |
90 90 o 2:draft 'C3'
91 91 |
92 92 o 1:draft 'C2'
93 93 |
94 94 o 0:draft 'C1'
95 95
96 96 Check correctness:
97 97
98 98 $ hg cat -r 0 common
99 99 c1
100 100
101 101 $ hg cat -r 1 common
102 102 c1
103 103 c2
104 104
105 105 $ hg cat -r 2 common
106 106 c1
107 107 c2
108 108 c3
109 109
110 110 $ hg cat -r 3 common
111 111 c1
112 112 c2
113 113 c3
114 114
115 115 $ hg cat -r 4 common
116 116 resolved merge
117 117
118 118 $ hg cat -r 5 common
119 119 resolved merge
120 120
121 121 Bookmark stays active after --continue
122 122 $ hg bookmarks
123 123 * mybook 5:d67b21408fc0
124 124
125 125 $ cd ..
126 126
127 127 Check that the right ancestors is used while rebasing a merge (issue4041)
128 128
129 129 $ hg clone "$TESTDIR/bundles/issue4041.hg" issue4041
130 130 requesting all changes
131 131 adding changesets
132 132 adding manifests
133 133 adding file changes
134 134 added 11 changesets with 8 changes to 3 files (+1 heads)
135 135 updating to branch default
136 136 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 137 $ cd issue4041
138 138 $ hg phase --draft --force 9
139 139 $ hg log -G
140 140 o changeset: 10:2f2496ddf49d
141 141 |\ branch: f1
142 142 | | tag: tip
143 143 | | parent: 7:4c9fbe56a16f
144 144 | | parent: 9:e31216eec445
145 145 | | user: szhang
146 146 | | date: Thu Sep 05 12:59:39 2013 -0400
147 147 | | summary: merge
148 148 | |
149 149 | o changeset: 9:e31216eec445
150 150 | | branch: f1
151 151 | | user: szhang
152 152 | | date: Thu Sep 05 12:59:10 2013 -0400
153 153 | | summary: more changes to f1
154 154 | |
155 155 | o changeset: 8:8e4e2c1a07ae
156 156 | |\ branch: f1
157 157 | | | parent: 2:4bc80088dc6b
158 158 | | | parent: 6:400110238667
159 159 | | | user: szhang
160 160 | | | date: Thu Sep 05 12:57:59 2013 -0400
161 161 | | | summary: bad merge
162 162 | | |
163 163 o | | changeset: 7:4c9fbe56a16f
164 164 |/ / branch: f1
165 165 | | parent: 2:4bc80088dc6b
166 166 | | user: szhang
167 167 | | date: Thu Sep 05 12:54:00 2013 -0400
168 168 | | summary: changed f1
169 169 | |
170 170 | o changeset: 6:400110238667
171 171 | | branch: f2
172 172 | | parent: 4:12e8ec6bb010
173 173 | | user: szhang
174 174 | | date: Tue Sep 03 13:58:02 2013 -0400
175 175 | | summary: changed f2 on f2
176 176 | |
177 177 | | @ changeset: 5:d79e2059b5c0
178 178 | | | parent: 3:8a951942e016
179 179 | | | user: szhang
180 180 | | | date: Tue Sep 03 13:57:39 2013 -0400
181 181 | | | summary: changed f2 on default
182 182 | | |
183 183 | o | changeset: 4:12e8ec6bb010
184 184 | |/ branch: f2
185 185 | | user: szhang
186 186 | | date: Tue Sep 03 13:57:18 2013 -0400
187 187 | | summary: created f2 branch
188 188 | |
189 189 | o changeset: 3:8a951942e016
190 190 | | parent: 0:24797d4f68de
191 191 | | user: szhang
192 192 | | date: Tue Sep 03 13:57:11 2013 -0400
193 193 | | summary: added f2.txt
194 194 | |
195 195 o | changeset: 2:4bc80088dc6b
196 196 | | branch: f1
197 197 | | user: szhang
198 198 | | date: Tue Sep 03 13:56:20 2013 -0400
199 199 | | summary: added f1.txt
200 200 | |
201 201 o | changeset: 1:ef53c9e6b608
202 202 |/ branch: f1
203 203 | user: szhang
204 204 | date: Tue Sep 03 13:55:26 2013 -0400
205 205 | summary: created f1 branch
206 206 |
207 207 o changeset: 0:24797d4f68de
208 208 user: szhang
209 209 date: Tue Sep 03 13:55:08 2013 -0400
210 210 summary: added default.txt
211 211
212 212 $ hg rebase -s9 -d2 --debug # use debug to really check merge base used
213 rebase onto 2 starting from [<changectx e31216eec445>]
213 rebase onto 2 starting from e31216eec445
214 214 rebasing: 9:e31216eec445 5/6 changesets (83.33%)
215 215 future parents are 2 and -1
216 216 rebase status stored
217 217 update to 2:4bc80088dc6b
218 218 resolving manifests
219 219 branchmerge: False, force: True, partial: False
220 220 ancestor: d79e2059b5c0+, local: d79e2059b5c0+, remote: 4bc80088dc6b
221 221 f2.txt: other deleted -> r
222 222 f1.txt: remote created -> g
223 223 removing f2.txt
224 224 updating: f2.txt 1/2 files (50.00%)
225 225 getting f1.txt
226 226 updating: f1.txt 2/2 files (100.00%)
227 227 merge against 9:e31216eec445
228 228 detach base 8:8e4e2c1a07ae
229 229 searching for copies back to rev 3
230 230 resolving manifests
231 231 branchmerge: True, force: True, partial: False
232 232 ancestor: 8e4e2c1a07ae, local: 4bc80088dc6b+, remote: e31216eec445
233 233 f1.txt: remote is newer -> g
234 234 getting f1.txt
235 235 updating: f1.txt 1/1 files (100.00%)
236 236 f1.txt
237 237 rebasing: 10:2f2496ddf49d 6/6 changesets (100.00%)
238 238 future parents are 11 and 7
239 239 rebase status stored
240 240 already in target
241 241 merge against 10:2f2496ddf49d
242 242 detach base 9:e31216eec445
243 243 searching for copies back to rev 3
244 244 resolving manifests
245 245 branchmerge: True, force: True, partial: False
246 246 ancestor: e31216eec445, local: 19c888675e13+, remote: 2f2496ddf49d
247 247 f1.txt: remote is newer -> g
248 248 getting f1.txt
249 249 updating: f1.txt 1/1 files (100.00%)
250 250 f1.txt
251 251 rebase merging completed
252 252 update back to initial working directory parent
253 253 resolving manifests
254 254 branchmerge: False, force: False, partial: False
255 255 ancestor: 2a7f09cac94c, local: 2a7f09cac94c+, remote: d79e2059b5c0
256 256 f1.txt: other deleted -> r
257 257 f2.txt: remote created -> g
258 258 removing f1.txt
259 259 updating: f1.txt 1/2 files (50.00%)
260 260 getting f2.txt
261 261 updating: f2.txt 2/2 files (100.00%)
262 262 3 changesets found
263 263 list of changesets:
264 264 4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c
265 265 e31216eec445e44352c5f01588856059466a24c9
266 266 2f2496ddf49d69b5ef23ad8cf9fb2e0e4faf0ac2
267 267 bundling: 1/3 changesets (33.33%)
268 268 bundling: 2/3 changesets (66.67%)
269 269 bundling: 3/3 changesets (100.00%)
270 270 bundling: 1/3 manifests (33.33%)
271 271 bundling: 2/3 manifests (66.67%)
272 272 bundling: 3/3 manifests (100.00%)
273 273 bundling: f1.txt 1/1 files (100.00%)
274 274 saved backup bundle to $TESTTMP/issue4041/.hg/strip-backup/e31216eec445-backup.hg (glob)
275 275 3 changesets found
276 276 list of changesets:
277 277 4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c
278 278 19c888675e133ab5dff84516926a65672eaf04d9
279 279 2a7f09cac94c7f4b73ebd5cd1a62d3b2e8e336bf
280 280 bundling: 1/3 changesets (33.33%)
281 281 bundling: 2/3 changesets (66.67%)
282 282 bundling: 3/3 changesets (100.00%)
283 283 bundling: 1/3 manifests (33.33%)
284 284 bundling: 2/3 manifests (66.67%)
285 285 bundling: 3/3 manifests (100.00%)
286 286 bundling: f1.txt 1/1 files (100.00%)
287 287 adding branch
288 288 adding changesets
289 289 changesets: 1 chunks
290 290 add changeset 4c9fbe56a16f
291 291 changesets: 2 chunks
292 292 add changeset 19c888675e13
293 293 changesets: 3 chunks
294 294 add changeset 2a7f09cac94c
295 295 adding manifests
296 296 manifests: 1/2 chunks (50.00%)
297 297 manifests: 2/2 chunks (100.00%)
298 298 manifests: 3/2 chunks (150.00%)
299 299 adding file changes
300 300 adding f1.txt revisions
301 301 files: 1/1 chunks (100.00%)
302 302 added 2 changesets with 2 changes to 1 files
303 303 removing unknown node e31216eec445 from 1-phase boundary
304 304 invalid branchheads cache (served): tip differs
305 305 rebase completed
306 306 updating the branch cache
General Comments 0
You need to be logged in to leave comments. Login now