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