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