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