##// END OF EJS Templates
rebase: move duplicatecopies next to merge...
Matt Mackall -
r22905:63e889cc default
parent child Browse files
Show More
@@ -1,1022 +1,1024
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 import copies
20 20 from mercurial.commands import templateopts
21 21 from mercurial.node import nullrev
22 22 from mercurial.lock import release
23 23 from mercurial.i18n import _
24 24 import os, errno
25 25
26 26 nullmerge = -2
27 27 revignored = -3
28 28
29 29 cmdtable = {}
30 30 command = cmdutil.command(cmdtable)
31 31 testedwith = 'internal'
32 32
33 33 def _savegraft(ctx, extra):
34 34 s = ctx.extra().get('source', None)
35 35 if s is not None:
36 36 extra['source'] = s
37 37
38 38 def _savebranch(ctx, extra):
39 39 extra['branch'] = ctx.branch()
40 40
41 41 def _makeextrafn(copiers):
42 42 """make an extrafn out of the given copy-functions.
43 43
44 44 A copy function takes a context and an extra dict, and mutates the
45 45 extra dict as needed based on the given context.
46 46 """
47 47 def extrafn(ctx, extra):
48 48 for c in copiers:
49 49 c(ctx, extra)
50 50 return extrafn
51 51
52 52 @command('rebase',
53 53 [('s', 'source', '',
54 54 _('rebase the specified changeset and descendants'), _('REV')),
55 55 ('b', 'base', '',
56 56 _('rebase everything from branching point of specified changeset'),
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 ('i', 'interactive', False, _('(DEPRECATED)')),
73 73 ('t', 'tool', '', _('specify merge tool')),
74 74 ('c', 'continue', False, _('continue an interrupted rebase')),
75 75 ('a', 'abort', False, _('abort an interrupted rebase'))] +
76 76 templateopts,
77 77 _('[-s REV | -b REV] [-d REV] [OPTION]'))
78 78 def rebase(ui, repo, **opts):
79 79 """move changeset (and descendants) to a different branch
80 80
81 81 Rebase uses repeated merging to graft changesets from one part of
82 82 history (the source) onto another (the destination). This can be
83 83 useful for linearizing *local* changes relative to a master
84 84 development tree.
85 85
86 86 You should not rebase changesets that have already been shared
87 87 with others. Doing so will force everybody else to perform the
88 88 same rebase or they will end up with duplicated changesets after
89 89 pulling in your rebased changesets.
90 90
91 91 In its default configuration, Mercurial will prevent you from
92 92 rebasing published changes. See :hg:`help phases` for details.
93 93
94 94 If you don't specify a destination changeset (``-d/--dest``),
95 95 rebase uses the current branch tip as the destination. (The
96 96 destination changeset is not modified by rebasing, but new
97 97 changesets are added as its descendants.)
98 98
99 99 You can specify which changesets to rebase in two ways: as a
100 100 "source" changeset or as a "base" changeset. Both are shorthand
101 101 for a topologically related set of changesets (the "source
102 102 branch"). If you specify source (``-s/--source``), rebase will
103 103 rebase that changeset and all of its descendants onto dest. If you
104 104 specify base (``-b/--base``), rebase will select ancestors of base
105 105 back to but not including the common ancestor with dest. Thus,
106 106 ``-b`` is less precise but more convenient than ``-s``: you can
107 107 specify any changeset in the source branch, and rebase will select
108 108 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
109 109 uses the parent of the working directory as the base.
110 110
111 111 For advanced usage, a third way is available through the ``--rev``
112 112 option. It allows you to specify an arbitrary set of changesets to
113 113 rebase. Descendants of revs you specify with this option are not
114 114 automatically included in the rebase.
115 115
116 116 By default, rebase recreates the changesets in the source branch
117 117 as descendants of dest and then destroys the originals. Use
118 118 ``--keep`` to preserve the original source changesets. Some
119 119 changesets in the source branch (e.g. merges from the destination
120 120 branch) may be dropped if they no longer contribute any change.
121 121
122 122 One result of the rules for selecting the destination changeset
123 123 and source branch is that, unlike ``merge``, rebase will do
124 124 nothing if you are at the branch tip of a named branch
125 125 with two heads. You need to explicitly specify source and/or
126 126 destination (or ``update`` to the other head, if it's the head of
127 127 the intended source branch).
128 128
129 129 If a rebase is interrupted to manually resolve a merge, it can be
130 130 continued with --continue/-c or aborted with --abort/-a.
131 131
132 132 .. container:: verbose
133 133
134 134 Examples:
135 135
136 136 - move "local changes" (current commit back to branching point)
137 137 to the current branch tip after a pull::
138 138
139 139 hg rebase
140 140
141 141 - move a single changeset to the stable branch::
142 142
143 143 hg rebase -r 5f493448 -d stable
144 144
145 145 - splice a commit and all its descendants onto another part of history::
146 146
147 147 hg rebase --source c0c3 --dest 4cf9
148 148
149 149 - rebase everything on a branch marked by a bookmark onto the
150 150 default branch::
151 151
152 152 hg rebase --base myfeature --dest default
153 153
154 154 - collapse a sequence of changes into a single commit::
155 155
156 156 hg rebase --collapse -r 1520:1525 -d .
157 157
158 158 - move a named branch while preserving its name::
159 159
160 160 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
161 161
162 162 Returns 0 on success, 1 if nothing to rebase or there are
163 163 unresolved conflicts.
164 164
165 165 """
166 166 originalwd = target = None
167 167 activebookmark = None
168 168 external = nullrev
169 169 state = {}
170 170 skipped = set()
171 171 targetancestors = set()
172 172
173 173
174 174 lock = wlock = None
175 175 try:
176 176 wlock = repo.wlock()
177 177 lock = repo.lock()
178 178
179 179 # Validate input and define rebasing points
180 180 destf = opts.get('dest', None)
181 181 srcf = opts.get('source', None)
182 182 basef = opts.get('base', None)
183 183 revf = opts.get('rev', [])
184 184 contf = opts.get('continue')
185 185 abortf = opts.get('abort')
186 186 collapsef = opts.get('collapse', False)
187 187 collapsemsg = cmdutil.logmessage(ui, opts)
188 188 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
189 189 extrafns = [_savegraft]
190 190 if e:
191 191 extrafns = [e]
192 192 keepf = opts.get('keep', False)
193 193 keepbranchesf = opts.get('keepbranches', False)
194 194 # keepopen is not meant for use on the command line, but by
195 195 # other extensions
196 196 keepopen = opts.get('keepopen', False)
197 197
198 198 if opts.get('interactive'):
199 199 msg = _("interactive history editing is supported by the "
200 200 "'histedit' extension (see 'hg help histedit')")
201 201 raise util.Abort(msg)
202 202
203 203 if collapsemsg and not collapsef:
204 204 raise util.Abort(
205 205 _('message can only be specified with collapse'))
206 206
207 207 if contf or abortf:
208 208 if contf and abortf:
209 209 raise util.Abort(_('cannot use both abort and continue'))
210 210 if collapsef:
211 211 raise util.Abort(
212 212 _('cannot use collapse with continue or abort'))
213 213 if srcf or basef or destf:
214 214 raise util.Abort(
215 215 _('abort and continue do not allow specifying revisions'))
216 216 if opts.get('tool', False):
217 217 ui.warn(_('tool option will be ignored\n'))
218 218
219 219 try:
220 220 (originalwd, target, state, skipped, collapsef, keepf,
221 221 keepbranchesf, external, activebookmark) = restorestatus(repo)
222 222 except error.RepoLookupError:
223 223 if abortf:
224 224 clearstatus(repo)
225 225 repo.ui.warn(_('rebase aborted (no revision is removed,'
226 226 ' only broken state is cleared)\n'))
227 227 return 0
228 228 else:
229 229 msg = _('cannot continue inconsistent rebase')
230 230 hint = _('use "hg rebase --abort" to clear broken state')
231 231 raise util.Abort(msg, hint=hint)
232 232 if abortf:
233 233 return abort(repo, originalwd, target, state)
234 234 else:
235 235 if srcf and basef:
236 236 raise util.Abort(_('cannot specify both a '
237 237 'source and a base'))
238 238 if revf and basef:
239 239 raise util.Abort(_('cannot specify both a '
240 240 'revision and a base'))
241 241 if revf and srcf:
242 242 raise util.Abort(_('cannot specify both a '
243 243 'revision and a source'))
244 244
245 245 cmdutil.checkunfinished(repo)
246 246 cmdutil.bailifchanged(repo)
247 247
248 248 if not destf:
249 249 # Destination defaults to the latest revision in the
250 250 # current branch
251 251 branch = repo[None].branch()
252 252 dest = repo[branch]
253 253 else:
254 254 dest = scmutil.revsingle(repo, destf)
255 255
256 256 if revf:
257 257 rebaseset = scmutil.revrange(repo, revf)
258 258 if not rebaseset:
259 259 ui.status(_('empty "rev" revision set - '
260 260 'nothing to rebase\n'))
261 261 return 1
262 262 elif srcf:
263 263 src = scmutil.revrange(repo, [srcf])
264 264 if not src:
265 265 ui.status(_('empty "source" revision set - '
266 266 'nothing to rebase\n'))
267 267 return 1
268 268 rebaseset = repo.revs('(%ld)::', src)
269 269 assert rebaseset
270 270 else:
271 271 base = scmutil.revrange(repo, [basef or '.'])
272 272 if not base:
273 273 ui.status(_('empty "base" revision set - '
274 274 "can't compute rebase set\n"))
275 275 return 1
276 276 rebaseset = repo.revs(
277 277 '(children(ancestor(%ld, %d)) and ::(%ld))::',
278 278 base, dest, base)
279 279 if not rebaseset:
280 280 # transform to list because smartsets are not comparable to
281 281 # lists. This should be improved to honor lazyness of
282 282 # smartset.
283 283 if list(base) == [dest.rev()]:
284 284 if basef:
285 285 ui.status(_('nothing to rebase - %s is both "base"'
286 286 ' and destination\n') % dest)
287 287 else:
288 288 ui.status(_('nothing to rebase - working directory '
289 289 'parent is also destination\n'))
290 290 elif not repo.revs('%ld - ::%d', base, dest):
291 291 if basef:
292 292 ui.status(_('nothing to rebase - "base" %s is '
293 293 'already an ancestor of destination '
294 294 '%s\n') %
295 295 ('+'.join(str(repo[r]) for r in base),
296 296 dest))
297 297 else:
298 298 ui.status(_('nothing to rebase - working '
299 299 'directory parent is already an '
300 300 'ancestor of destination %s\n') % dest)
301 301 else: # can it happen?
302 302 ui.status(_('nothing to rebase from %s to %s\n') %
303 303 ('+'.join(str(repo[r]) for r in base), dest))
304 304 return 1
305 305
306 306 if (not (keepf or obsolete._enabled)
307 307 and repo.revs('first(children(%ld) - %ld)',
308 308 rebaseset, rebaseset)):
309 309 raise util.Abort(
310 310 _("can't remove original changesets with"
311 311 " unrebased descendants"),
312 312 hint=_('use --keep to keep original changesets'))
313 313
314 314 result = buildstate(repo, dest, rebaseset, collapsef)
315 315 if not result:
316 316 # Empty state built, nothing to rebase
317 317 ui.status(_('nothing to rebase\n'))
318 318 return 1
319 319
320 320 root = min(rebaseset)
321 321 if not keepf and not repo[root].mutable():
322 322 raise util.Abort(_("can't rebase immutable changeset %s")
323 323 % repo[root],
324 324 hint=_('see hg help phases for details'))
325 325
326 326 originalwd, target, state = result
327 327 if collapsef:
328 328 targetancestors = repo.changelog.ancestors([target],
329 329 inclusive=True)
330 330 external = externalparent(repo, state, targetancestors)
331 331
332 332 if dest.closesbranch() and not keepbranchesf:
333 333 ui.status(_('reopening closed branch head %s\n') % dest)
334 334
335 335 if keepbranchesf:
336 336 # insert _savebranch at the start of extrafns so if
337 337 # there's a user-provided extrafn it can clobber branch if
338 338 # desired
339 339 extrafns.insert(0, _savebranch)
340 340 if collapsef:
341 341 branches = set()
342 342 for rev in state:
343 343 branches.add(repo[rev].branch())
344 344 if len(branches) > 1:
345 345 raise util.Abort(_('cannot collapse multiple named '
346 346 'branches'))
347 347
348 348 # Rebase
349 349 if not targetancestors:
350 350 targetancestors = repo.changelog.ancestors([target], inclusive=True)
351 351
352 352 # Keep track of the current bookmarks in order to reset them later
353 353 currentbookmarks = repo._bookmarks.copy()
354 354 activebookmark = activebookmark or repo._bookmarkcurrent
355 355 if activebookmark:
356 356 bookmarks.unsetcurrent(repo)
357 357
358 358 extrafn = _makeextrafn(extrafns)
359 359
360 360 sortedstate = sorted(state)
361 361 total = len(sortedstate)
362 362 pos = 0
363 363 for rev in sortedstate:
364 364 pos += 1
365 365 if state[rev] == -1:
366 366 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
367 367 _('changesets'), total)
368 368 p1, p2 = defineparents(repo, rev, target, state,
369 369 targetancestors)
370 370 storestatus(repo, originalwd, target, state, collapsef, keepf,
371 371 keepbranchesf, external, activebookmark)
372 372 if len(repo.parents()) == 2:
373 373 repo.ui.debug('resuming interrupted rebase\n')
374 374 else:
375 375 try:
376 376 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
377 377 'rebase')
378 stats = rebasenode(repo, rev, p1, state, collapsef)
378 stats = rebasenode(repo, rev, p1, state, collapsef,
379 target)
379 380 if stats and stats[3] > 0:
380 381 raise error.InterventionRequired(
381 382 _('unresolved conflicts (see hg '
382 383 'resolve, then hg rebase --continue)'))
383 384 finally:
384 385 ui.setconfig('ui', 'forcemerge', '', 'rebase')
385 if collapsef:
386 copies.duplicatecopies(repo, rev, target)
387 else:
388 # If we're not using --collapse, we need to
389 # duplicate copies between the revision we're
390 # rebasing and its first parent, but *not*
391 # duplicate any copies that have already been
392 # performed in the destination.
393 p1rev = repo[rev].p1().rev()
394 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
395 386 if not collapsef:
396 387 merging = repo[p2].rev() != nullrev
397 388 editform = cmdutil.mergeeditform(merging, 'rebase')
398 389 editor = cmdutil.getcommiteditor(editform=editform, **opts)
399 390 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
400 391 editor=editor)
401 392 else:
402 393 # Skip commit if we are collapsing
403 394 repo.dirstate.beginparentchange()
404 395 repo.setparents(repo[p1].node())
405 396 repo.dirstate.endparentchange()
406 397 newrev = None
407 398 # Update the state
408 399 if newrev is not None:
409 400 state[rev] = repo[newrev].rev()
410 401 else:
411 402 if not collapsef:
412 403 ui.note(_('no changes, revision %d skipped\n') % rev)
413 404 ui.debug('next revision set to %s\n' % p1)
414 405 skipped.add(rev)
415 406 state[rev] = p1
416 407
417 408 ui.progress(_('rebasing'), None)
418 409 ui.note(_('rebase merging completed\n'))
419 410
420 411 if collapsef and not keepopen:
421 412 p1, p2 = defineparents(repo, min(state), target,
422 413 state, targetancestors)
423 414 editopt = opts.get('edit')
424 415 editform = 'rebase.collapse'
425 416 if collapsemsg:
426 417 commitmsg = collapsemsg
427 418 else:
428 419 commitmsg = 'Collapsed revision'
429 420 for rebased in state:
430 421 if rebased not in skipped and state[rebased] > nullmerge:
431 422 commitmsg += '\n* %s' % repo[rebased].description()
432 423 editopt = True
433 424 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
434 425 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
435 426 extrafn=extrafn, editor=editor)
436 427 for oldrev in state.iterkeys():
437 428 if state[oldrev] > nullmerge:
438 429 state[oldrev] = newrev
439 430
440 431 if 'qtip' in repo.tags():
441 432 updatemq(repo, state, skipped, **opts)
442 433
443 434 if currentbookmarks:
444 435 # Nodeids are needed to reset bookmarks
445 436 nstate = {}
446 437 for k, v in state.iteritems():
447 438 if v > nullmerge:
448 439 nstate[repo[k].node()] = repo[v].node()
449 440 # XXX this is the same as dest.node() for the non-continue path --
450 441 # this should probably be cleaned up
451 442 targetnode = repo[target].node()
452 443
453 444 # restore original working directory
454 445 # (we do this before stripping)
455 446 newwd = state.get(originalwd, originalwd)
456 447 if newwd not in [c.rev() for c in repo[None].parents()]:
457 448 ui.note(_("update back to initial working directory parent\n"))
458 449 hg.updaterepo(repo, newwd, False)
459 450
460 451 if not keepf:
461 452 collapsedas = None
462 453 if collapsef:
463 454 collapsedas = newrev
464 455 clearrebased(ui, repo, state, skipped, collapsedas)
465 456
466 457 if currentbookmarks:
467 458 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
468 459 if activebookmark not in repo._bookmarks:
469 460 # active bookmark was divergent one and has been deleted
470 461 activebookmark = None
471 462
472 463 clearstatus(repo)
473 464 ui.note(_("rebase completed\n"))
474 465 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
475 466 if skipped:
476 467 ui.note(_("%d revisions have been skipped\n") % len(skipped))
477 468
478 469 if (activebookmark and
479 470 repo['.'].node() == repo._bookmarks[activebookmark]):
480 471 bookmarks.setcurrent(repo, activebookmark)
481 472
482 473 finally:
483 474 release(lock, wlock)
484 475
485 476 def externalparent(repo, state, targetancestors):
486 477 """Return the revision that should be used as the second parent
487 478 when the revisions in state is collapsed on top of targetancestors.
488 479 Abort if there is more than one parent.
489 480 """
490 481 parents = set()
491 482 source = min(state)
492 483 for rev in state:
493 484 if rev == source:
494 485 continue
495 486 for p in repo[rev].parents():
496 487 if (p.rev() not in state
497 488 and p.rev() not in targetancestors):
498 489 parents.add(p.rev())
499 490 if not parents:
500 491 return nullrev
501 492 if len(parents) == 1:
502 493 return parents.pop()
503 494 raise util.Abort(_('unable to collapse on top of %s, there is more '
504 495 'than one external parent: %s') %
505 496 (max(targetancestors),
506 497 ', '.join(str(p) for p in sorted(parents))))
507 498
508 499 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
509 500 'Commit the changes and store useful information in extra'
510 501 try:
511 502 repo.dirstate.beginparentchange()
512 503 repo.setparents(repo[p1].node(), repo[p2].node())
513 504 repo.dirstate.endparentchange()
514 505 ctx = repo[rev]
515 506 if commitmsg is None:
516 507 commitmsg = ctx.description()
517 508 extra = {'rebase_source': ctx.hex()}
518 509 if extrafn:
519 510 extrafn(ctx, extra)
520 511
521 512 backup = repo.ui.backupconfig('phases', 'new-commit')
522 513 try:
523 514 targetphase = max(ctx.phase(), phases.draft)
524 515 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
525 516 # Commit might fail if unresolved files exist
526 517 newrev = repo.commit(text=commitmsg, user=ctx.user(),
527 518 date=ctx.date(), extra=extra, editor=editor)
528 519 finally:
529 520 repo.ui.restoreconfig(backup)
530 521
531 522 repo.dirstate.setbranch(repo[newrev].branch())
532 523 return newrev
533 524 except util.Abort:
534 525 # Invalidate the previous setparents
535 526 repo.dirstate.invalidate()
536 527 raise
537 528
538 def rebasenode(repo, rev, p1, state, collapse):
529 def rebasenode(repo, rev, p1, state, collapse, target):
539 530 'Rebase a single revision'
540 531 # Merge phase
541 532 # Update to target and merge it with local
542 533 if repo['.'].rev() != repo[p1].rev():
543 534 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
544 535 merge.update(repo, p1, False, True, False)
545 536 else:
546 537 repo.ui.debug(" already in target\n")
547 538 repo.dirstate.write()
548 539 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
549 540 if repo[rev].rev() == repo[min(state)].rev():
550 541 # Case (1) initial changeset of a non-detaching rebase.
551 542 # Let the merge mechanism find the base itself.
552 543 base = None
553 544 elif not repo[rev].p2():
554 545 # Case (2) detaching the node with a single parent, use this parent
555 546 base = repo[rev].p1().node()
556 547 else:
557 548 # In case of merge, we need to pick the right parent as merge base.
558 549 #
559 550 # Imagine we have:
560 551 # - M: currently rebase revision in this step
561 552 # - A: one parent of M
562 553 # - B: second parent of M
563 554 # - D: destination of this merge step (p1 var)
564 555 #
565 556 # If we are rebasing on D, D is the successors of A or B. The right
566 557 # merge base is the one D succeed to. We pretend it is B for the rest
567 558 # of this comment
568 559 #
569 560 # If we pick B as the base, the merge involves:
570 561 # - changes from B to M (actual changeset payload)
571 562 # - changes from B to D (induced by rebase) as D is a rebased
572 563 # version of B)
573 564 # Which exactly represent the rebase operation.
574 565 #
575 566 # If we pick the A as the base, the merge involves
576 567 # - changes from A to M (actual changeset payload)
577 568 # - changes from A to D (with include changes between unrelated A and B
578 569 # plus changes induced by rebase)
579 570 # Which does not represent anything sensible and creates a lot of
580 571 # conflicts.
581 572 for p in repo[rev].parents():
582 573 if state.get(p.rev()) == repo[p1].rev():
583 574 base = p.node()
584 575 break
585 576 else: # fallback when base not found
586 577 base = None
587 578
588 579 # Raise because this function is called wrong (see issue 4106)
589 580 raise AssertionError('no base found to rebase on '
590 581 '(rebasenode called wrong)')
591 582 if base is not None:
592 583 repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
593 584 # When collapsing in-place, the parent is the common ancestor, we
594 585 # have to allow merging with it.
595 return merge.update(repo, rev, True, True, False, base, collapse,
586 stats = merge.update(repo, rev, True, True, False, base, collapse,
596 587 labels=['dest', 'source'])
588 if collapse:
589 copies.duplicatecopies(repo, rev, target)
590 else:
591 # If we're not using --collapse, we need to
592 # duplicate copies between the revision we're
593 # rebasing and its first parent, but *not*
594 # duplicate any copies that have already been
595 # performed in the destination.
596 p1rev = repo[rev].p1().rev()
597 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
598 return stats
597 599
598 600 def nearestrebased(repo, rev, state):
599 601 """return the nearest ancestors of rev in the rebase result"""
600 602 rebased = [r for r in state if state[r] > nullmerge]
601 603 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
602 604 if candidates:
603 605 return state[candidates.first()]
604 606 else:
605 607 return None
606 608
607 609 def defineparents(repo, rev, target, state, targetancestors):
608 610 'Return the new parent relationship of the revision that will be rebased'
609 611 parents = repo[rev].parents()
610 612 p1 = p2 = nullrev
611 613
612 614 P1n = parents[0].rev()
613 615 if P1n in targetancestors:
614 616 p1 = target
615 617 elif P1n in state:
616 618 if state[P1n] == nullmerge:
617 619 p1 = target
618 620 elif state[P1n] == revignored:
619 621 p1 = nearestrebased(repo, P1n, state)
620 622 if p1 is None:
621 623 p1 = target
622 624 else:
623 625 p1 = state[P1n]
624 626 else: # P1n external
625 627 p1 = target
626 628 p2 = P1n
627 629
628 630 if len(parents) == 2 and parents[1].rev() not in targetancestors:
629 631 P2n = parents[1].rev()
630 632 # interesting second parent
631 633 if P2n in state:
632 634 if p1 == target: # P1n in targetancestors or external
633 635 p1 = state[P2n]
634 636 elif state[P2n] == revignored:
635 637 p2 = nearestrebased(repo, P2n, state)
636 638 if p2 is None:
637 639 # no ancestors rebased yet, detach
638 640 p2 = target
639 641 else:
640 642 p2 = state[P2n]
641 643 else: # P2n external
642 644 if p2 != nullrev: # P1n external too => rev is a merged revision
643 645 raise util.Abort(_('cannot use revision %d as base, result '
644 646 'would have 3 parents') % rev)
645 647 p2 = P2n
646 648 repo.ui.debug(" future parents are %d and %d\n" %
647 649 (repo[p1].rev(), repo[p2].rev()))
648 650 return p1, p2
649 651
650 652 def isagitpatch(repo, patchname):
651 653 'Return true if the given patch is in git format'
652 654 mqpatch = os.path.join(repo.mq.path, patchname)
653 655 for line in patch.linereader(file(mqpatch, 'rb')):
654 656 if line.startswith('diff --git'):
655 657 return True
656 658 return False
657 659
658 660 def updatemq(repo, state, skipped, **opts):
659 661 'Update rebased mq patches - finalize and then import them'
660 662 mqrebase = {}
661 663 mq = repo.mq
662 664 original_series = mq.fullseries[:]
663 665 skippedpatches = set()
664 666
665 667 for p in mq.applied:
666 668 rev = repo[p.node].rev()
667 669 if rev in state:
668 670 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
669 671 (rev, p.name))
670 672 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
671 673 else:
672 674 # Applied but not rebased, not sure this should happen
673 675 skippedpatches.add(p.name)
674 676
675 677 if mqrebase:
676 678 mq.finish(repo, mqrebase.keys())
677 679
678 680 # We must start import from the newest revision
679 681 for rev in sorted(mqrebase, reverse=True):
680 682 if rev not in skipped:
681 683 name, isgit = mqrebase[rev]
682 684 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
683 685 mq.qimport(repo, (), patchname=name, git=isgit,
684 686 rev=[str(state[rev])])
685 687 else:
686 688 # Rebased and skipped
687 689 skippedpatches.add(mqrebase[rev][0])
688 690
689 691 # Patches were either applied and rebased and imported in
690 692 # order, applied and removed or unapplied. Discard the removed
691 693 # ones while preserving the original series order and guards.
692 694 newseries = [s for s in original_series
693 695 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
694 696 mq.fullseries[:] = newseries
695 697 mq.seriesdirty = True
696 698 mq.savedirty()
697 699
698 700 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
699 701 'Move bookmarks to their correct changesets, and delete divergent ones'
700 702 marks = repo._bookmarks
701 703 for k, v in originalbookmarks.iteritems():
702 704 if v in nstate:
703 705 # update the bookmarks for revs that have moved
704 706 marks[k] = nstate[v]
705 707 bookmarks.deletedivergent(repo, [targetnode], k)
706 708
707 709 marks.write()
708 710
709 711 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
710 712 external, activebookmark):
711 713 'Store the current status to allow recovery'
712 714 f = repo.opener("rebasestate", "w")
713 715 f.write(repo[originalwd].hex() + '\n')
714 716 f.write(repo[target].hex() + '\n')
715 717 f.write(repo[external].hex() + '\n')
716 718 f.write('%d\n' % int(collapse))
717 719 f.write('%d\n' % int(keep))
718 720 f.write('%d\n' % int(keepbranches))
719 721 f.write('%s\n' % (activebookmark or ''))
720 722 for d, v in state.iteritems():
721 723 oldrev = repo[d].hex()
722 724 if v > nullmerge:
723 725 newrev = repo[v].hex()
724 726 else:
725 727 newrev = v
726 728 f.write("%s:%s\n" % (oldrev, newrev))
727 729 f.close()
728 730 repo.ui.debug('rebase status stored\n')
729 731
730 732 def clearstatus(repo):
731 733 'Remove the status files'
732 734 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
733 735
734 736 def restorestatus(repo):
735 737 'Restore a previously stored status'
736 738 try:
737 739 keepbranches = None
738 740 target = None
739 741 collapse = False
740 742 external = nullrev
741 743 activebookmark = None
742 744 state = {}
743 745 f = repo.opener("rebasestate")
744 746 for i, l in enumerate(f.read().splitlines()):
745 747 if i == 0:
746 748 originalwd = repo[l].rev()
747 749 elif i == 1:
748 750 target = repo[l].rev()
749 751 elif i == 2:
750 752 external = repo[l].rev()
751 753 elif i == 3:
752 754 collapse = bool(int(l))
753 755 elif i == 4:
754 756 keep = bool(int(l))
755 757 elif i == 5:
756 758 keepbranches = bool(int(l))
757 759 elif i == 6 and not (len(l) == 81 and ':' in l):
758 760 # line 6 is a recent addition, so for backwards compatibility
759 761 # check that the line doesn't look like the oldrev:newrev lines
760 762 activebookmark = l
761 763 else:
762 764 oldrev, newrev = l.split(':')
763 765 if newrev in (str(nullmerge), str(revignored)):
764 766 state[repo[oldrev].rev()] = int(newrev)
765 767 else:
766 768 state[repo[oldrev].rev()] = repo[newrev].rev()
767 769
768 770 if keepbranches is None:
769 771 raise util.Abort(_('.hg/rebasestate is incomplete'))
770 772
771 773 skipped = set()
772 774 # recompute the set of skipped revs
773 775 if not collapse:
774 776 seen = set([target])
775 777 for old, new in sorted(state.items()):
776 778 if new != nullrev and new in seen:
777 779 skipped.add(old)
778 780 seen.add(new)
779 781 repo.ui.debug('computed skipped revs: %s\n' %
780 782 (' '.join(str(r) for r in sorted(skipped)) or None))
781 783 repo.ui.debug('rebase status resumed\n')
782 784 return (originalwd, target, state, skipped,
783 785 collapse, keep, keepbranches, external, activebookmark)
784 786 except IOError, err:
785 787 if err.errno != errno.ENOENT:
786 788 raise
787 789 raise util.Abort(_('no rebase in progress'))
788 790
789 791 def inrebase(repo, originalwd, state):
790 792 '''check whether the working dir is in an interrupted rebase'''
791 793 parents = [p.rev() for p in repo.parents()]
792 794 if originalwd in parents:
793 795 return True
794 796
795 797 for newrev in state.itervalues():
796 798 if newrev in parents:
797 799 return True
798 800
799 801 return False
800 802
801 803 def abort(repo, originalwd, target, state):
802 804 'Restore the repository to its original state'
803 805 dstates = [s for s in state.values() if s > nullrev]
804 806 immutable = [d for d in dstates if not repo[d].mutable()]
805 807 cleanup = True
806 808 if immutable:
807 809 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
808 810 % ', '.join(str(repo[r]) for r in immutable),
809 811 hint=_('see hg help phases for details'))
810 812 cleanup = False
811 813
812 814 descendants = set()
813 815 if dstates:
814 816 descendants = set(repo.changelog.descendants(dstates))
815 817 if descendants - set(dstates):
816 818 repo.ui.warn(_("warning: new changesets detected on target branch, "
817 819 "can't strip\n"))
818 820 cleanup = False
819 821
820 822 if cleanup:
821 823 # Update away from the rebase if necessary
822 824 if inrebase(repo, originalwd, state):
823 825 merge.update(repo, repo[originalwd].rev(), False, True, False)
824 826
825 827 # Strip from the first rebased revision
826 828 rebased = filter(lambda x: x > -1 and x != target, state.values())
827 829 if rebased:
828 830 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
829 831 # no backup of rebased cset versions needed
830 832 repair.strip(repo.ui, repo, strippoints)
831 833
832 834 clearstatus(repo)
833 835 repo.ui.warn(_('rebase aborted\n'))
834 836 return 0
835 837
836 838 def buildstate(repo, dest, rebaseset, collapse):
837 839 '''Define which revisions are going to be rebased and where
838 840
839 841 repo: repo
840 842 dest: context
841 843 rebaseset: set of rev
842 844 '''
843 845
844 846 # This check isn't strictly necessary, since mq detects commits over an
845 847 # applied patch. But it prevents messing up the working directory when
846 848 # a partially completed rebase is blocked by mq.
847 849 if 'qtip' in repo.tags() and (dest.node() in
848 850 [s.node for s in repo.mq.applied]):
849 851 raise util.Abort(_('cannot rebase onto an applied mq patch'))
850 852
851 853 roots = list(repo.set('roots(%ld)', rebaseset))
852 854 if not roots:
853 855 raise util.Abort(_('no matching revisions'))
854 856 roots.sort()
855 857 state = {}
856 858 detachset = set()
857 859 for root in roots:
858 860 commonbase = root.ancestor(dest)
859 861 if commonbase == root:
860 862 raise util.Abort(_('source is ancestor of destination'))
861 863 if commonbase == dest:
862 864 samebranch = root.branch() == dest.branch()
863 865 if not collapse and samebranch and root in dest.children():
864 866 repo.ui.debug('source is a child of destination\n')
865 867 return None
866 868
867 869 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
868 870 state.update(dict.fromkeys(rebaseset, nullrev))
869 871 # Rebase tries to turn <dest> into a parent of <root> while
870 872 # preserving the number of parents of rebased changesets:
871 873 #
872 874 # - A changeset with a single parent will always be rebased as a
873 875 # changeset with a single parent.
874 876 #
875 877 # - A merge will be rebased as merge unless its parents are both
876 878 # ancestors of <dest> or are themselves in the rebased set and
877 879 # pruned while rebased.
878 880 #
879 881 # If one parent of <root> is an ancestor of <dest>, the rebased
880 882 # version of this parent will be <dest>. This is always true with
881 883 # --base option.
882 884 #
883 885 # Otherwise, we need to *replace* the original parents with
884 886 # <dest>. This "detaches" the rebased set from its former location
885 887 # and rebases it onto <dest>. Changes introduced by ancestors of
886 888 # <root> not common with <dest> (the detachset, marked as
887 889 # nullmerge) are "removed" from the rebased changesets.
888 890 #
889 891 # - If <root> has a single parent, set it to <dest>.
890 892 #
891 893 # - If <root> is a merge, we cannot decide which parent to
892 894 # replace, the rebase operation is not clearly defined.
893 895 #
894 896 # The table below sums up this behavior:
895 897 #
896 898 # +------------------+----------------------+-------------------------+
897 899 # | | one parent | merge |
898 900 # +------------------+----------------------+-------------------------+
899 901 # | parent in | new parent is <dest> | parents in ::<dest> are |
900 902 # | ::<dest> | | remapped to <dest> |
901 903 # +------------------+----------------------+-------------------------+
902 904 # | unrelated source | new parent is <dest> | ambiguous, abort |
903 905 # +------------------+----------------------+-------------------------+
904 906 #
905 907 # The actual abort is handled by `defineparents`
906 908 if len(root.parents()) <= 1:
907 909 # ancestors of <root> not ancestors of <dest>
908 910 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
909 911 [root.rev()]))
910 912 for r in detachset:
911 913 if r not in state:
912 914 state[r] = nullmerge
913 915 if len(roots) > 1:
914 916 # If we have multiple roots, we may have "hole" in the rebase set.
915 917 # Rebase roots that descend from those "hole" should not be detached as
916 918 # other root are. We use the special `revignored` to inform rebase that
917 919 # the revision should be ignored but that `defineparents` should search
918 920 # a rebase destination that make sense regarding rebased topology.
919 921 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
920 922 for ignored in set(rebasedomain) - set(rebaseset):
921 923 state[ignored] = revignored
922 924 return repo['.'].rev(), dest.rev(), state
923 925
924 926 def clearrebased(ui, repo, state, skipped, collapsedas=None):
925 927 """dispose of rebased revision at the end of the rebase
926 928
927 929 If `collapsedas` is not None, the rebase was a collapse whose result if the
928 930 `collapsedas` node."""
929 931 if obsolete._enabled:
930 932 markers = []
931 933 for rev, newrev in sorted(state.items()):
932 934 if newrev >= 0:
933 935 if rev in skipped:
934 936 succs = ()
935 937 elif collapsedas is not None:
936 938 succs = (repo[collapsedas],)
937 939 else:
938 940 succs = (repo[newrev],)
939 941 markers.append((repo[rev], succs))
940 942 if markers:
941 943 obsolete.createmarkers(repo, markers)
942 944 else:
943 945 rebased = [rev for rev in state if state[rev] > nullmerge]
944 946 if rebased:
945 947 stripped = []
946 948 for root in repo.set('roots(%ld)', rebased):
947 949 if set(repo.changelog.descendants([root.rev()])) - set(state):
948 950 ui.warn(_("warning: new changesets detected "
949 951 "on source branch, not stripping\n"))
950 952 else:
951 953 stripped.append(root.node())
952 954 if stripped:
953 955 # backup the old csets by default
954 956 repair.strip(ui, repo, stripped, "all")
955 957
956 958
957 959 def pullrebase(orig, ui, repo, *args, **opts):
958 960 'Call rebase after pull if the latter has been invoked with --rebase'
959 961 if opts.get('rebase'):
960 962 if opts.get('update'):
961 963 del opts['update']
962 964 ui.debug('--update and --rebase are not compatible, ignoring '
963 965 'the update flag\n')
964 966
965 967 movemarkfrom = repo['.'].node()
966 968 revsprepull = len(repo)
967 969 origpostincoming = commands.postincoming
968 970 def _dummy(*args, **kwargs):
969 971 pass
970 972 commands.postincoming = _dummy
971 973 try:
972 974 orig(ui, repo, *args, **opts)
973 975 finally:
974 976 commands.postincoming = origpostincoming
975 977 revspostpull = len(repo)
976 978 if revspostpull > revsprepull:
977 979 # --rev option from pull conflict with rebase own --rev
978 980 # dropping it
979 981 if 'rev' in opts:
980 982 del opts['rev']
981 983 rebase(ui, repo, **opts)
982 984 branch = repo[None].branch()
983 985 dest = repo[branch].rev()
984 986 if dest != repo['.'].rev():
985 987 # there was nothing to rebase we force an update
986 988 hg.update(repo, dest)
987 989 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
988 990 ui.status(_("updating bookmark %s\n")
989 991 % repo._bookmarkcurrent)
990 992 else:
991 993 if opts.get('tool'):
992 994 raise util.Abort(_('--tool can only be used with --rebase'))
993 995 orig(ui, repo, *args, **opts)
994 996
995 997 def summaryhook(ui, repo):
996 998 if not os.path.exists(repo.join('rebasestate')):
997 999 return
998 1000 try:
999 1001 state = restorestatus(repo)[2]
1000 1002 except error.RepoLookupError:
1001 1003 # i18n: column positioning for "hg summary"
1002 1004 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1003 1005 ui.write(msg)
1004 1006 return
1005 1007 numrebased = len([i for i in state.itervalues() if i != -1])
1006 1008 # i18n: column positioning for "hg summary"
1007 1009 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1008 1010 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1009 1011 ui.label(_('%d remaining'), 'rebase.remaining') %
1010 1012 (len(state) - numrebased)))
1011 1013
1012 1014 def uisetup(ui):
1013 1015 'Replace pull with a decorator to provide --rebase option'
1014 1016 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1015 1017 entry[1].append(('', 'rebase', None,
1016 1018 _("rebase working directory to branch head")))
1017 1019 entry[1].append(('t', 'tool', '',
1018 1020 _("specify merge tool for rebase")))
1019 1021 cmdutil.summaryhooks.add('rebase', summaryhook)
1020 1022 cmdutil.unfinishedstates.append(
1021 1023 ['rebasestate', False, False, _('rebase in progress'),
1022 1024 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,740 +1,734
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "mq=" >> $HGRCPATH
3 3 $ echo "shelve=" >> $HGRCPATH
4 4 $ echo "[defaults]" >> $HGRCPATH
5 5 $ echo "diff = --nodates --git" >> $HGRCPATH
6 6 $ echo "qnew = --date '0 0'" >> $HGRCPATH
7 7
8 8 $ hg init repo
9 9 $ cd repo
10 10 $ mkdir a b
11 11 $ echo a > a/a
12 12 $ echo b > b/b
13 13 $ echo c > c
14 14 $ echo d > d
15 15 $ echo x > x
16 16 $ hg addremove -q
17 17
18 18 shelving in an empty repo should be possible
19 19 (this tests also that editor is not invoked, if '--edit' is not
20 20 specified)
21 21
22 22 $ HGEDITOR=cat hg shelve
23 23 shelved as default
24 24 0 files updated, 0 files merged, 5 files removed, 0 files unresolved
25 25
26 26 $ hg unshelve
27 27 unshelving change 'default'
28 28
29 29 $ hg commit -q -m 'initial commit'
30 30
31 31 $ hg shelve
32 32 nothing changed
33 33 [1]
34 34
35 35 create an mq patch - shelving should work fine with a patch applied
36 36
37 37 $ echo n > n
38 38 $ hg add n
39 39 $ hg commit n -m second
40 40 $ hg qnew second.patch
41 41
42 42 shelve a change that we will delete later
43 43
44 44 $ echo a >> a/a
45 45 $ hg shelve
46 46 shelved as default
47 47 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
48 48
49 49 set up some more complex changes to shelve
50 50
51 51 $ echo a >> a/a
52 52 $ hg mv b b.rename
53 53 moving b/b to b.rename/b (glob)
54 54 $ hg cp c c.copy
55 55 $ hg status -C
56 56 M a/a
57 57 A b.rename/b
58 58 b/b
59 59 A c.copy
60 60 c
61 61 R b/b
62 62
63 63 prevent some foot-shooting
64 64
65 65 $ hg shelve -n foo/bar
66 66 abort: shelved change names may not contain slashes
67 67 [255]
68 68 $ hg shelve -n .baz
69 69 abort: shelved change names may not start with '.'
70 70 [255]
71 71
72 72 the common case - no options or filenames
73 73
74 74 $ hg shelve
75 75 shelved as default-01
76 76 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
77 77 $ hg status -C
78 78
79 79 ensure that our shelved changes exist
80 80
81 81 $ hg shelve -l
82 82 default-01 (*) changes to '[mq]: second.patch' (glob)
83 83 default (*) changes to '[mq]: second.patch' (glob)
84 84
85 85 $ hg shelve -l -p default
86 86 default (*) changes to '[mq]: second.patch' (glob)
87 87
88 88 diff --git a/a/a b/a/a
89 89 --- a/a/a
90 90 +++ b/a/a
91 91 @@ -1,1 +1,2 @@
92 92 a
93 93 +a
94 94
95 95 $ hg shelve --list --addremove
96 96 abort: options '--list' and '--addremove' may not be used together
97 97 [255]
98 98
99 99 delete our older shelved change
100 100
101 101 $ hg shelve -d default
102 102 $ hg qfinish -a -q
103 103
104 104 local edits should not prevent a shelved change from applying
105 105
106 106 $ printf "z\na\n" > a/a
107 107 $ hg unshelve --keep
108 108 unshelving change 'default-01'
109 109 temporarily committing pending changes (restore with 'hg unshelve --abort')
110 110 rebasing shelved changes
111 111 merging a/a
112 112
113 113 $ hg revert --all -q
114 114 $ rm a/a.orig b.rename/b c.copy
115 115
116 116 apply it and make sure our state is as expected
117 117
118 118 $ hg unshelve
119 119 unshelving change 'default-01'
120 120 $ hg status -C
121 121 M a/a
122 122 A b.rename/b
123 123 b/b
124 124 A c.copy
125 125 c
126 126 R b/b
127 127 $ hg shelve -l
128 128
129 129 $ hg unshelve
130 130 abort: no shelved changes to apply!
131 131 [255]
132 132 $ hg unshelve foo
133 133 abort: shelved change 'foo' not found
134 134 [255]
135 135
136 136 named shelves, specific filenames, and "commit messages" should all work
137 137 (this tests also that editor is invoked, if '--edit' is specified)
138 138
139 139 $ hg status -C
140 140 M a/a
141 141 A b.rename/b
142 142 b/b
143 143 A c.copy
144 144 c
145 145 R b/b
146 146 $ HGEDITOR=cat hg shelve -q -n wibble -m wat -e a
147 147 wat
148 148
149 149
150 150 HG: Enter commit message. Lines beginning with 'HG:' are removed.
151 151 HG: Leave message empty to abort commit.
152 152 HG: --
153 153 HG: user: shelve@localhost
154 154 HG: branch 'default'
155 155 HG: changed a/a
156 156
157 157 expect "a" to no longer be present, but status otherwise unchanged
158 158
159 159 $ hg status -C
160 160 A b.rename/b
161 161 b/b
162 162 A c.copy
163 163 c
164 164 R b/b
165 165 $ hg shelve -l --stat
166 166 wibble (*) wat (glob)
167 167 a/a | 1 +
168 168 1 files changed, 1 insertions(+), 0 deletions(-)
169 169
170 170 and now "a/a" should reappear
171 171
172 172 $ cd a
173 173 $ hg unshelve -q wibble
174 174 $ cd ..
175 175 $ hg status -C
176 176 M a/a
177 177 A b.rename/b
178 178 b/b
179 179 A c.copy
180 180 c
181 181 R b/b
182 182
183 183 cause unshelving to result in a merge with 'a' conflicting
184 184
185 185 $ hg shelve -q
186 186 $ echo c>>a/a
187 187 $ hg commit -m second
188 188 $ hg tip --template '{files}\n'
189 189 a/a
190 190
191 191 add an unrelated change that should be preserved
192 192
193 193 $ mkdir foo
194 194 $ echo foo > foo/foo
195 195 $ hg add foo/foo
196 196
197 197 force a conflicted merge to occur
198 198
199 199 $ hg unshelve
200 200 unshelving change 'default'
201 201 temporarily committing pending changes (restore with 'hg unshelve --abort')
202 202 rebasing shelved changes
203 203 merging a/a
204 204 warning: conflicts during merge.
205 205 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
206 206 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
207 207 [1]
208 208
209 209 ensure that we have a merge with unresolved conflicts
210 210
211 211 $ hg heads -q --template '{rev}\n'
212 212 5
213 213 4
214 214 $ hg parents -q --template '{rev}\n'
215 215 4
216 216 5
217 217 $ hg status
218 218 M a/a
219 219 M b.rename/b
220 220 M c.copy
221 221 R b/b
222 222 ? a/a.orig
223 223 $ hg diff
224 224 diff --git a/a/a b/a/a
225 225 --- a/a/a
226 226 +++ b/a/a
227 227 @@ -1,2 +1,6 @@
228 228 a
229 229 +<<<<<<< dest: * - shelve: pending changes temporary commit (glob)
230 230 c
231 231 +=======
232 232 +a
233 233 +>>>>>>> source: 4702e8911fe0 - shelve: changes to '[mq]: second.patch'
234 diff --git a/b.rename/b b/b.rename/b
235 new file mode 100644
236 --- /dev/null
237 +++ b/b.rename/b
238 @@ -0,0 +1,1 @@
239 +b
234 diff --git a/b/b b/b.rename/b
235 rename from b/b
236 rename to b.rename/b
240 237 diff --git a/b/b b/b/b
241 238 deleted file mode 100644
242 239 --- a/b/b
243 240 +++ /dev/null
244 241 @@ -1,1 +0,0 @@
245 242 -b
246 diff --git a/c.copy b/c.copy
247 new file mode 100644
248 --- /dev/null
249 +++ b/c.copy
250 @@ -0,0 +1,1 @@
251 +c
243 diff --git a/c b/c.copy
244 copy from c
245 copy to c.copy
252 246 $ hg resolve -l
253 247 U a/a
254 248
255 249 $ hg shelve
256 250 abort: unshelve already in progress
257 251 (use 'hg unshelve --continue' or 'hg unshelve --abort')
258 252 [255]
259 253
260 254 abort the unshelve and be happy
261 255
262 256 $ hg status
263 257 M a/a
264 258 M b.rename/b
265 259 M c.copy
266 260 R b/b
267 261 ? a/a.orig
268 262 $ hg unshelve -a
269 263 rebase aborted
270 264 unshelve of 'default' aborted
271 265 $ hg heads -q
272 266 3:2e69b451d1ea
273 267 $ hg parents
274 268 changeset: 3:2e69b451d1ea
275 269 tag: tip
276 270 user: test
277 271 date: Thu Jan 01 00:00:00 1970 +0000
278 272 summary: second
279 273
280 274 $ hg resolve -l
281 275 $ hg status
282 276 A foo/foo
283 277 ? a/a.orig
284 278
285 279 try to continue with no unshelve underway
286 280
287 281 $ hg unshelve -c
288 282 abort: no unshelve operation underway
289 283 [255]
290 284 $ hg status
291 285 A foo/foo
292 286 ? a/a.orig
293 287
294 288 redo the unshelve to get a conflict
295 289
296 290 $ hg unshelve -q
297 291 warning: conflicts during merge.
298 292 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
299 293 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
300 294 [1]
301 295
302 296 attempt to continue
303 297
304 298 $ hg unshelve -c
305 299 abort: unresolved conflicts, can't continue
306 300 (see 'hg resolve', then 'hg unshelve --continue')
307 301 [255]
308 302
309 303 $ hg revert -r . a/a
310 304 $ hg resolve -m a/a
311 305 (no more unresolved files)
312 306
313 307 $ hg commit -m 'commit while unshelve in progress'
314 308 abort: unshelve already in progress
315 309 (use 'hg unshelve --continue' or 'hg unshelve --abort')
316 310 [255]
317 311
318 312 $ hg unshelve -c
319 313 unshelve of 'default' complete
320 314
321 315 ensure the repo is as we hope
322 316
323 317 $ hg parents
324 318 changeset: 3:2e69b451d1ea
325 319 tag: tip
326 320 user: test
327 321 date: Thu Jan 01 00:00:00 1970 +0000
328 322 summary: second
329 323
330 324 $ hg heads -q
331 325 3:2e69b451d1ea
332 326
333 327 $ hg status -C
334 328 A b.rename/b
335 329 b/b
336 330 A c.copy
337 331 c
338 332 A foo/foo
339 333 R b/b
340 334 ? a/a.orig
341 335
342 336 there should be no shelves left
343 337
344 338 $ hg shelve -l
345 339
346 340 #if execbit
347 341
348 342 ensure that metadata-only changes are shelved
349 343
350 344 $ chmod +x a/a
351 345 $ hg shelve -q -n execbit a/a
352 346 $ hg status a/a
353 347 $ hg unshelve -q execbit
354 348 $ hg status a/a
355 349 M a/a
356 350 $ hg revert a/a
357 351
358 352 #endif
359 353
360 354 #if symlink
361 355
362 356 $ rm a/a
363 357 $ ln -s foo a/a
364 358 $ hg shelve -q -n symlink a/a
365 359 $ hg status a/a
366 360 $ hg unshelve -q symlink
367 361 $ hg status a/a
368 362 M a/a
369 363 $ hg revert a/a
370 364
371 365 #endif
372 366
373 367 set up another conflict between a commit and a shelved change
374 368
375 369 $ hg revert -q -C -a
376 370 $ rm a/a.orig b.rename/b c.copy
377 371 $ echo a >> a/a
378 372 $ hg shelve -q
379 373 $ echo x >> a/a
380 374 $ hg ci -m 'create conflict'
381 375 $ hg add foo/foo
382 376
383 377 if we resolve a conflict while unshelving, the unshelve should succeed
384 378
385 379 $ HGMERGE=true hg unshelve
386 380 unshelving change 'default'
387 381 temporarily committing pending changes (restore with 'hg unshelve --abort')
388 382 rebasing shelved changes
389 383 merging a/a
390 384 $ hg parents -q
391 385 4:33f7f61e6c5e
392 386 $ hg shelve -l
393 387 $ hg status
394 388 A foo/foo
395 389 $ cat a/a
396 390 a
397 391 c
398 392 x
399 393
400 394 test keep and cleanup
401 395
402 396 $ hg shelve
403 397 shelved as default
404 398 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
405 399 $ hg shelve --list
406 400 default (*) changes to 'create conflict' (glob)
407 401 $ hg unshelve --keep
408 402 unshelving change 'default'
409 403 $ hg shelve --list
410 404 default (*) changes to 'create conflict' (glob)
411 405 $ hg shelve --cleanup
412 406 $ hg shelve --list
413 407
414 408 $ hg shelve --cleanup --delete
415 409 abort: options '--cleanup' and '--delete' may not be used together
416 410 [255]
417 411 $ hg shelve --cleanup --patch
418 412 abort: options '--cleanup' and '--patch' may not be used together
419 413 [255]
420 414 $ hg shelve --cleanup --message MESSAGE
421 415 abort: options '--cleanup' and '--message' may not be used together
422 416 [255]
423 417
424 418 test bookmarks
425 419
426 420 $ hg bookmark test
427 421 $ hg bookmark
428 422 * test 4:33f7f61e6c5e
429 423 $ hg shelve
430 424 shelved as test
431 425 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
432 426 $ hg bookmark
433 427 * test 4:33f7f61e6c5e
434 428 $ hg unshelve
435 429 unshelving change 'test'
436 430 $ hg bookmark
437 431 * test 4:33f7f61e6c5e
438 432
439 433 shelve should still work even if mq is disabled
440 434
441 435 $ hg --config extensions.mq=! shelve
442 436 shelved as test
443 437 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
444 438 $ hg --config extensions.mq=! shelve --list
445 439 test (*) changes to 'create conflict' (glob)
446 440 $ hg --config extensions.mq=! unshelve
447 441 unshelving change 'test'
448 442
449 443 shelve should leave dirstate clean (issue4055)
450 444
451 445 $ cd ..
452 446 $ hg init shelverebase
453 447 $ cd shelverebase
454 448 $ printf 'x\ny\n' > x
455 449 $ echo z > z
456 450 $ hg commit -Aqm xy
457 451 $ echo z >> x
458 452 $ hg commit -Aqm z
459 453 $ hg up 0
460 454 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
461 455 $ printf 'a\nx\ny\nz\n' > x
462 456 $ hg commit -Aqm xyz
463 457 $ echo c >> z
464 458 $ hg shelve
465 459 shelved as default
466 460 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
467 461 $ hg rebase -d 1 --config extensions.rebase=
468 462 merging x
469 463 saved backup bundle to $TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-backup.hg (glob)
470 464 $ hg unshelve
471 465 unshelving change 'default'
472 466 rebasing shelved changes
473 467 $ hg status
474 468 M z
475 469
476 470 $ cd ..
477 471
478 472 shelve should only unshelve pending changes (issue4068)
479 473
480 474 $ hg init onlypendingchanges
481 475 $ cd onlypendingchanges
482 476 $ touch a
483 477 $ hg ci -Aqm a
484 478 $ touch b
485 479 $ hg ci -Aqm b
486 480 $ hg up -q 0
487 481 $ touch c
488 482 $ hg ci -Aqm c
489 483
490 484 $ touch d
491 485 $ hg add d
492 486 $ hg shelve
493 487 shelved as default
494 488 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
495 489 $ hg up -q 1
496 490 $ hg unshelve
497 491 unshelving change 'default'
498 492 rebasing shelved changes
499 493 $ hg status
500 494 A d
501 495
502 496 unshelve should work on an ancestor of the original commit
503 497
504 498 $ hg shelve
505 499 shelved as default
506 500 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
507 501 $ hg up 0
508 502 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
509 503 $ hg unshelve
510 504 unshelving change 'default'
511 505 rebasing shelved changes
512 506 $ hg status
513 507 A d
514 508
515 509 test bug 4073 we need to enable obsolete markers for it
516 510
517 511 $ cat > ../obs.py << EOF
518 512 > import mercurial.obsolete
519 513 > mercurial.obsolete._enabled = True
520 514 > EOF
521 515 $ echo '[extensions]' >> $HGRCPATH
522 516 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
523 517 $ hg shelve
524 518 shelved as default
525 519 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
526 520 $ hg debugobsolete `hg --debug id -i -r 1`
527 521 $ hg unshelve
528 522 unshelving change 'default'
529 523
530 524 unshelve should leave unknown files alone (issue4113)
531 525
532 526 $ echo e > e
533 527 $ hg shelve
534 528 shelved as default
535 529 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
536 530 $ hg status
537 531 ? e
538 532 $ hg unshelve
539 533 unshelving change 'default'
540 534 $ hg status
541 535 A d
542 536 ? e
543 537 $ cat e
544 538 e
545 539
546 540 unshelve should keep a copy of unknown files
547 541
548 542 $ hg add e
549 543 $ hg shelve
550 544 shelved as default
551 545 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
552 546 $ echo z > e
553 547 $ hg unshelve
554 548 unshelving change 'default'
555 549 $ cat e
556 550 e
557 551 $ cat e.orig
558 552 z
559 553
560 554
561 555 unshelve and conflicts with tracked and untracked files
562 556
563 557 preparing:
564 558
565 559 $ rm *.orig
566 560 $ hg ci -qm 'commit stuff'
567 561 $ hg phase -p null:
568 562
569 563 no other changes - no merge:
570 564
571 565 $ echo f > f
572 566 $ hg add f
573 567 $ hg shelve
574 568 shelved as default
575 569 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
576 570 $ echo g > f
577 571 $ hg unshelve
578 572 unshelving change 'default'
579 573 $ hg st
580 574 A f
581 575 ? f.orig
582 576 $ cat f
583 577 f
584 578 $ cat f.orig
585 579 g
586 580
587 581 other uncommitted changes - merge:
588 582
589 583 $ hg st
590 584 A f
591 585 ? f.orig
592 586 $ hg shelve
593 587 shelved as default
594 588 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
595 589 $ hg log -G --template '{rev} {desc|firstline} {author}' -R bundle://.hg/shelved/default.hg -r 'bundle()'
596 590 o 4 changes to 'commit stuff' shelve@localhost
597 591 |
598 592 $ hg log -G --template '{rev} {desc|firstline} {author}'
599 593 @ 3 commit stuff test
600 594 |
601 595 | o 2 c test
602 596 |/
603 597 o 0 a test
604 598
605 599 $ mv f.orig f
606 600 $ echo 1 > a
607 601 $ hg unshelve --date '1073741824 0'
608 602 unshelving change 'default'
609 603 temporarily committing pending changes (restore with 'hg unshelve --abort')
610 604 rebasing shelved changes
611 605 merging f
612 606 warning: conflicts during merge.
613 607 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
614 608 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
615 609 [1]
616 610 $ hg log -G --template '{rev} {desc|firstline} {author} {date|isodate}'
617 611 @ 5 changes to 'commit stuff' shelve@localhost 1970-01-01 00:00 +0000
618 612 |
619 613 | @ 4 pending changes temporary commit shelve@localhost 2004-01-10 13:37 +0000
620 614 |/
621 615 o 3 commit stuff test 1970-01-01 00:00 +0000
622 616 |
623 617 | o 2 c test 1970-01-01 00:00 +0000
624 618 |/
625 619 o 0 a test 1970-01-01 00:00 +0000
626 620
627 621 $ hg st
628 622 M f
629 623 ? f.orig
630 624 $ cat f
631 625 <<<<<<< dest: 5f6b880e719b - shelve: pending changes temporary commit
632 626 g
633 627 =======
634 628 f
635 629 >>>>>>> source: 23b29cada8ba - shelve: changes to 'commit stuff'
636 630 $ cat f.orig
637 631 g
638 632 $ hg unshelve --abort
639 633 rebase aborted
640 634 unshelve of 'default' aborted
641 635 $ hg st
642 636 M a
643 637 ? f.orig
644 638 $ cat f.orig
645 639 g
646 640 $ hg unshelve
647 641 unshelving change 'default'
648 642 temporarily committing pending changes (restore with 'hg unshelve --abort')
649 643 rebasing shelved changes
650 644 $ hg st
651 645 M a
652 646 A f
653 647 ? f.orig
654 648
655 649 other committed changes - merge:
656 650
657 651 $ hg shelve f
658 652 shelved as default
659 653 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
660 654 $ hg ci a -m 'intermediate other change'
661 655 $ mv f.orig f
662 656 $ hg unshelve
663 657 unshelving change 'default'
664 658 rebasing shelved changes
665 659 merging f
666 660 warning: conflicts during merge.
667 661 merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
668 662 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
669 663 [1]
670 664 $ hg st
671 665 M f
672 666 ? f.orig
673 667 $ cat f
674 668 <<<<<<< dest: * - test: intermediate other change (glob)
675 669 g
676 670 =======
677 671 f
678 672 >>>>>>> source: 23b29cada8ba - shelve: changes to 'commit stuff'
679 673 $ cat f.orig
680 674 g
681 675 $ hg unshelve --abort
682 676 rebase aborted
683 677 unshelve of 'default' aborted
684 678 $ hg st
685 679 ? f.orig
686 680 $ cat f.orig
687 681 g
688 682 $ hg shelve --delete default
689 683
690 684 Recreate some conflict again
691 685
692 686 $ cd ../repo
693 687 $ hg up -C -r 3
694 688 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
695 689 (leaving bookmark test)
696 690 $ echo y >> a/a
697 691 $ hg shelve
698 692 shelved as default
699 693 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
700 694 $ hg up test
701 695 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
702 696 (activating bookmark test)
703 697 $ hg unshelve
704 698 unshelving change 'default'
705 699 rebasing shelved changes
706 700 merging a/a
707 701 warning: conflicts during merge.
708 702 merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
709 703 unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
710 704 [1]
711 705
712 706 Test that resolving all conflicts in one direction (so that the rebase
713 707 is a no-op), works (issue4398)
714 708
715 709 $ hg revert -a -r .
716 710 reverting a/a (glob)
717 711 $ hg resolve -m a/a
718 712 (no more unresolved files)
719 713 $ hg unshelve -c
720 714 unshelve of 'default' complete
721 715 $ hg diff
722 716 $ hg status
723 717 ? a/a.orig
724 718 ? foo/foo
725 719 $ hg summary
726 720 parent: 4:33f7f61e6c5e tip
727 721 create conflict
728 722 branch: default
729 723 bookmarks: *test
730 724 commit: 2 unknown (clean)
731 725 update: (current)
732 726
733 727 $ hg shelve --delete --stat
734 728 abort: options '--delete' and '--stat' may not be used together
735 729 [255]
736 730 $ hg shelve --delete --name NAME
737 731 abort: options '--delete' and '--name' may not be used together
738 732 [255]
739 733
740 734 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now