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