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