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