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