##// END OF EJS Templates
rebase: add a "D" short option for detach...
Pierre-Yves David -
r15737:8edd9f2c default
parent child Browse files
Show More
@@ -1,634 +1,634 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
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 ('', 'detach', False, _('force detaching of source from its original '
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 lock = repo.lock()
119 119 wlock = repo.wlock()
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 rebaseset = repo.revs('(%r)::', srcf)
191 191 else:
192 192 base = basef or '.'
193 193 rebaseset = repo.revs('(children(ancestor(%r, %d)) & ::%r)::',
194 194 base, dest, base)
195 195
196 196 if not rebaseset:
197 197 repo.ui.debug('base is ancestor of destination')
198 198 result = None
199 199 elif not keepf and list(repo.revs('first(children(%ld) - %ld)',
200 200 rebaseset, rebaseset)):
201 201 raise util.Abort(
202 202 _("can't remove original changesets with"
203 203 " unrebased descendants"),
204 204 hint=_('use --keep to keep original changesets'))
205 205 else:
206 206 result = buildstate(repo, dest, rebaseset, detachf)
207 207
208 208 if not result:
209 209 # Empty state built, nothing to rebase
210 210 ui.status(_('nothing to rebase\n'))
211 211 return 1
212 212 else:
213 213 originalwd, target, state = result
214 214 if collapsef:
215 215 targetancestors = set(repo.changelog.ancestors(target))
216 216 targetancestors.add(target)
217 217 external = checkexternal(repo, state, targetancestors)
218 218
219 219 if keepbranchesf:
220 220 assert not extrafn, 'cannot use both keepbranches and extrafn'
221 221 def extrafn(ctx, extra):
222 222 extra['branch'] = ctx.branch()
223 223 if collapsef:
224 224 branches = set()
225 225 for rev in state:
226 226 branches.add(repo[rev].branch())
227 227 if len(branches) > 1:
228 228 raise util.Abort(_('cannot collapse multiple named '
229 229 'branches'))
230 230
231 231
232 232 # Rebase
233 233 if not targetancestors:
234 234 targetancestors = set(repo.changelog.ancestors(target))
235 235 targetancestors.add(target)
236 236
237 237 # Keep track of the current bookmarks in order to reset them later
238 238 currentbookmarks = repo._bookmarks.copy()
239 239
240 240 sortedstate = sorted(state)
241 241 total = len(sortedstate)
242 242 pos = 0
243 243 for rev in sortedstate:
244 244 pos += 1
245 245 if state[rev] == -1:
246 246 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
247 247 _('changesets'), total)
248 248 storestatus(repo, originalwd, target, state, collapsef, keepf,
249 249 keepbranchesf, external)
250 250 p1, p2 = defineparents(repo, rev, target, state,
251 251 targetancestors)
252 252 if len(repo.parents()) == 2:
253 253 repo.ui.debug('resuming interrupted rebase\n')
254 254 else:
255 255 try:
256 256 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
257 257 stats = rebasenode(repo, rev, p1, state)
258 258 if stats and stats[3] > 0:
259 259 raise util.Abort(_('unresolved conflicts (see hg '
260 260 'resolve, then hg rebase --continue)'))
261 261 finally:
262 262 ui.setconfig('ui', 'forcemerge', '')
263 263 cmdutil.duplicatecopies(repo, rev, target, p2)
264 264 if not collapsef:
265 265 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
266 266 editor=editor)
267 267 else:
268 268 # Skip commit if we are collapsing
269 269 repo.dirstate.setparents(repo[p1].node())
270 270 newrev = None
271 271 # Update the state
272 272 if newrev is not None:
273 273 state[rev] = repo[newrev].rev()
274 274 else:
275 275 if not collapsef:
276 276 ui.note(_('no changes, revision %d skipped\n') % rev)
277 277 ui.debug('next revision set to %s\n' % p1)
278 278 skipped.add(rev)
279 279 state[rev] = p1
280 280
281 281 ui.progress(_('rebasing'), None)
282 282 ui.note(_('rebase merging completed\n'))
283 283
284 284 if collapsef and not keepopen:
285 285 p1, p2 = defineparents(repo, min(state), target,
286 286 state, targetancestors)
287 287 if collapsemsg:
288 288 commitmsg = collapsemsg
289 289 else:
290 290 commitmsg = 'Collapsed revision'
291 291 for rebased in state:
292 292 if rebased not in skipped and state[rebased] != nullmerge:
293 293 commitmsg += '\n* %s' % repo[rebased].description()
294 294 commitmsg = ui.edit(commitmsg, repo.ui.username())
295 295 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
296 296 extrafn=extrafn, editor=editor)
297 297
298 298 if 'qtip' in repo.tags():
299 299 updatemq(repo, state, skipped, **opts)
300 300
301 301 if currentbookmarks:
302 302 # Nodeids are needed to reset bookmarks
303 303 nstate = {}
304 304 for k, v in state.iteritems():
305 305 if v != nullmerge:
306 306 nstate[repo[k].node()] = repo[v].node()
307 307
308 308 if not keepf:
309 309 # Remove no more useful revisions
310 310 rebased = [rev for rev in state if state[rev] != nullmerge]
311 311 if rebased:
312 312 if set(repo.changelog.descendants(min(rebased))) - set(state):
313 313 ui.warn(_("warning: new changesets detected "
314 314 "on source branch, not stripping\n"))
315 315 else:
316 316 # backup the old csets by default
317 317 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
318 318
319 319 if currentbookmarks:
320 320 updatebookmarks(repo, nstate, currentbookmarks, **opts)
321 321
322 322 clearstatus(repo)
323 323 ui.note(_("rebase completed\n"))
324 324 if os.path.exists(repo.sjoin('undo')):
325 325 util.unlinkpath(repo.sjoin('undo'))
326 326 if skipped:
327 327 ui.note(_("%d revisions have been skipped\n") % len(skipped))
328 328 finally:
329 329 release(lock, wlock)
330 330
331 331 def checkexternal(repo, state, targetancestors):
332 332 """Check whether one or more external revisions need to be taken in
333 333 consideration. In the latter case, abort.
334 334 """
335 335 external = nullrev
336 336 source = min(state)
337 337 for rev in state:
338 338 if rev == source:
339 339 continue
340 340 # Check externals and fail if there are more than one
341 341 for p in repo[rev].parents():
342 342 if (p.rev() not in state
343 343 and p.rev() not in targetancestors):
344 344 if external != nullrev:
345 345 raise util.Abort(_('unable to collapse, there is more '
346 346 'than one external parent'))
347 347 external = p.rev()
348 348 return external
349 349
350 350 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
351 351 'Commit the changes and store useful information in extra'
352 352 try:
353 353 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
354 354 ctx = repo[rev]
355 355 if commitmsg is None:
356 356 commitmsg = ctx.description()
357 357 extra = {'rebase_source': ctx.hex()}
358 358 if extrafn:
359 359 extrafn(ctx, extra)
360 360 # Commit might fail if unresolved files exist
361 361 newrev = repo.commit(text=commitmsg, user=ctx.user(),
362 362 date=ctx.date(), extra=extra, editor=editor)
363 363 repo.dirstate.setbranch(repo[newrev].branch())
364 364 return newrev
365 365 except util.Abort:
366 366 # Invalidate the previous setparents
367 367 repo.dirstate.invalidate()
368 368 raise
369 369
370 370 def rebasenode(repo, rev, p1, state):
371 371 'Rebase a single revision'
372 372 # Merge phase
373 373 # Update to target and merge it with local
374 374 if repo['.'].rev() != repo[p1].rev():
375 375 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
376 376 merge.update(repo, p1, False, True, False)
377 377 else:
378 378 repo.ui.debug(" already in target\n")
379 379 repo.dirstate.write()
380 380 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
381 381 base = None
382 382 if repo[rev].rev() != repo[min(state)].rev():
383 383 base = repo[rev].p1().node()
384 384 return merge.update(repo, rev, True, True, False, base)
385 385
386 386 def defineparents(repo, rev, target, state, targetancestors):
387 387 'Return the new parent relationship of the revision that will be rebased'
388 388 parents = repo[rev].parents()
389 389 p1 = p2 = nullrev
390 390
391 391 P1n = parents[0].rev()
392 392 if P1n in targetancestors:
393 393 p1 = target
394 394 elif P1n in state:
395 395 if state[P1n] == nullmerge:
396 396 p1 = target
397 397 else:
398 398 p1 = state[P1n]
399 399 else: # P1n external
400 400 p1 = target
401 401 p2 = P1n
402 402
403 403 if len(parents) == 2 and parents[1].rev() not in targetancestors:
404 404 P2n = parents[1].rev()
405 405 # interesting second parent
406 406 if P2n in state:
407 407 if p1 == target: # P1n in targetancestors or external
408 408 p1 = state[P2n]
409 409 else:
410 410 p2 = state[P2n]
411 411 else: # P2n external
412 412 if p2 != nullrev: # P1n external too => rev is a merged revision
413 413 raise util.Abort(_('cannot use revision %d as base, result '
414 414 'would have 3 parents') % rev)
415 415 p2 = P2n
416 416 repo.ui.debug(" future parents are %d and %d\n" %
417 417 (repo[p1].rev(), repo[p2].rev()))
418 418 return p1, p2
419 419
420 420 def isagitpatch(repo, patchname):
421 421 'Return true if the given patch is in git format'
422 422 mqpatch = os.path.join(repo.mq.path, patchname)
423 423 for line in patch.linereader(file(mqpatch, 'rb')):
424 424 if line.startswith('diff --git'):
425 425 return True
426 426 return False
427 427
428 428 def updatemq(repo, state, skipped, **opts):
429 429 'Update rebased mq patches - finalize and then import them'
430 430 mqrebase = {}
431 431 mq = repo.mq
432 432 original_series = mq.fullseries[:]
433 433
434 434 for p in mq.applied:
435 435 rev = repo[p.node].rev()
436 436 if rev in state:
437 437 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
438 438 (rev, p.name))
439 439 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
440 440
441 441 if mqrebase:
442 442 mq.finish(repo, mqrebase.keys())
443 443
444 444 # We must start import from the newest revision
445 445 for rev in sorted(mqrebase, reverse=True):
446 446 if rev not in skipped:
447 447 name, isgit = mqrebase[rev]
448 448 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
449 449 mq.qimport(repo, (), patchname=name, git=isgit,
450 450 rev=[str(state[rev])])
451 451
452 452 # restore old series to preserve guards
453 453 mq.fullseries = original_series
454 454 mq.series_dirty = True
455 455 mq.savedirty()
456 456
457 457 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
458 458 'Move bookmarks to their correct changesets'
459 459 current = repo._bookmarkcurrent
460 460 for k, v in originalbookmarks.iteritems():
461 461 if v in nstate:
462 462 if nstate[v] != nullmerge:
463 463 # reset the pointer if the bookmark was moved incorrectly
464 464 if k != current:
465 465 repo._bookmarks[k] = nstate[v]
466 466
467 467 bookmarks.write(repo)
468 468
469 469 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
470 470 external):
471 471 'Store the current status to allow recovery'
472 472 f = repo.opener("rebasestate", "w")
473 473 f.write(repo[originalwd].hex() + '\n')
474 474 f.write(repo[target].hex() + '\n')
475 475 f.write(repo[external].hex() + '\n')
476 476 f.write('%d\n' % int(collapse))
477 477 f.write('%d\n' % int(keep))
478 478 f.write('%d\n' % int(keepbranches))
479 479 for d, v in state.iteritems():
480 480 oldrev = repo[d].hex()
481 481 if v != nullmerge:
482 482 newrev = repo[v].hex()
483 483 else:
484 484 newrev = v
485 485 f.write("%s:%s\n" % (oldrev, newrev))
486 486 f.close()
487 487 repo.ui.debug('rebase status stored\n')
488 488
489 489 def clearstatus(repo):
490 490 'Remove the status files'
491 491 if os.path.exists(repo.join("rebasestate")):
492 492 util.unlinkpath(repo.join("rebasestate"))
493 493
494 494 def restorestatus(repo):
495 495 'Restore a previously stored status'
496 496 try:
497 497 target = None
498 498 collapse = False
499 499 external = nullrev
500 500 state = {}
501 501 f = repo.opener("rebasestate")
502 502 for i, l in enumerate(f.read().splitlines()):
503 503 if i == 0:
504 504 originalwd = repo[l].rev()
505 505 elif i == 1:
506 506 target = repo[l].rev()
507 507 elif i == 2:
508 508 external = repo[l].rev()
509 509 elif i == 3:
510 510 collapse = bool(int(l))
511 511 elif i == 4:
512 512 keep = bool(int(l))
513 513 elif i == 5:
514 514 keepbranches = bool(int(l))
515 515 else:
516 516 oldrev, newrev = l.split(':')
517 517 if newrev != str(nullmerge):
518 518 state[repo[oldrev].rev()] = repo[newrev].rev()
519 519 else:
520 520 state[repo[oldrev].rev()] = int(newrev)
521 521 skipped = set()
522 522 # recompute the set of skipped revs
523 523 if not collapse:
524 524 seen = set([target])
525 525 for old, new in sorted(state.items()):
526 526 if new != nullrev and new in seen:
527 527 skipped.add(old)
528 528 seen.add(new)
529 529 repo.ui.debug('computed skipped revs: %s\n' % skipped)
530 530 repo.ui.debug('rebase status resumed\n')
531 531 return (originalwd, target, state, skipped,
532 532 collapse, keep, keepbranches, external)
533 533 except IOError, err:
534 534 if err.errno != errno.ENOENT:
535 535 raise
536 536 raise util.Abort(_('no rebase in progress'))
537 537
538 538 def abort(repo, originalwd, target, state):
539 539 'Restore the repository to its original state'
540 540 if set(repo.changelog.descendants(target)) - set(state.values()):
541 541 repo.ui.warn(_("warning: new changesets detected on target branch, "
542 542 "can't abort\n"))
543 543 return -1
544 544 else:
545 545 # Strip from the first rebased revision
546 546 merge.update(repo, repo[originalwd].rev(), False, True, False)
547 547 rebased = filter(lambda x: x > -1 and x != target, state.values())
548 548 if rebased:
549 549 strippoint = min(rebased)
550 550 # no backup of rebased cset versions needed
551 551 repair.strip(repo.ui, repo, repo[strippoint].node())
552 552 clearstatus(repo)
553 553 repo.ui.warn(_('rebase aborted\n'))
554 554 return 0
555 555
556 556 def buildstate(repo, dest, rebaseset, detach):
557 557 '''Define which revisions are going to be rebased and where
558 558
559 559 repo: repo
560 560 dest: context
561 561 rebaseset: set of rev
562 562 detach: boolean'''
563 563
564 564 # This check isn't strictly necessary, since mq detects commits over an
565 565 # applied patch. But it prevents messing up the working directory when
566 566 # a partially completed rebase is blocked by mq.
567 567 if 'qtip' in repo.tags() and (dest.node() in
568 568 [s.node for s in repo.mq.applied]):
569 569 raise util.Abort(_('cannot rebase onto an applied mq patch'))
570 570
571 571 detachset = set()
572 572 roots = list(repo.set('roots(%ld)', rebaseset))
573 573 if not roots:
574 574 raise util.Abort(_('no matching revisions'))
575 575 if len(roots) > 1:
576 576 raise util.Abort(_("can't rebase multiple roots"))
577 577 root = roots[0]
578 578
579 579 commonbase = root.ancestor(dest)
580 580 if commonbase == root:
581 581 raise util.Abort(_('source is ancestor of destination'))
582 582 if commonbase == dest:
583 583 samebranch = root.branch() == dest.branch()
584 584 if samebranch and root in dest.children():
585 585 repo.ui.debug('source is a child of destination')
586 586 return None
587 587 # rebase on ancestor, force detach
588 588 detach = True
589 589 if detach:
590 590 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root)
591 591
592 592 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
593 593 state = dict.fromkeys(rebaseset, nullrev)
594 594 state.update(dict.fromkeys(detachset, nullmerge))
595 595 return repo['.'].rev(), dest.rev(), state
596 596
597 597 def pullrebase(orig, ui, repo, *args, **opts):
598 598 'Call rebase after pull if the latter has been invoked with --rebase'
599 599 if opts.get('rebase'):
600 600 if opts.get('update'):
601 601 del opts['update']
602 602 ui.debug('--update and --rebase are not compatible, ignoring '
603 603 'the update flag\n')
604 604
605 605 cmdutil.bailifchanged(repo)
606 606 revsprepull = len(repo)
607 607 origpostincoming = commands.postincoming
608 608 def _dummy(*args, **kwargs):
609 609 pass
610 610 commands.postincoming = _dummy
611 611 try:
612 612 orig(ui, repo, *args, **opts)
613 613 finally:
614 614 commands.postincoming = origpostincoming
615 615 revspostpull = len(repo)
616 616 if revspostpull > revsprepull:
617 617 rebase(ui, repo, **opts)
618 618 branch = repo[None].branch()
619 619 dest = repo[branch].rev()
620 620 if dest != repo['.'].rev():
621 621 # there was nothing to rebase we force an update
622 622 hg.update(repo, dest)
623 623 else:
624 624 if opts.get('tool'):
625 625 raise util.Abort(_('--tool can only be used with --rebase'))
626 626 orig(ui, repo, *args, **opts)
627 627
628 628 def uisetup(ui):
629 629 'Replace pull with a decorator to provide --rebase option'
630 630 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
631 631 entry[1].append(('', 'rebase', None,
632 632 _("rebase working directory to branch head")))
633 633 entry[1].append(('t', 'tool', '',
634 634 _("specify merge tool for rebase")))
General Comments 0
You need to be logged in to leave comments. Login now