##// END OF EJS Templates
rebase: move bookmark to destination for commits becoming empty (issue5627)...
Jun Wu -
r33591:52f82e7d stable
parent child Browse files
Show More
@@ -0,0 +1,202
1 $ cat >> $HGRCPATH<<EOF
2 > [extensions]
3 > rebase=
4 > drawdag=$TESTDIR/drawdag.py
5 > EOF
6
7 $ hg init non-merge
8 $ cd non-merge
9 $ hg debugdrawdag<<'EOS'
10 > F
11 > |
12 > E
13 > |
14 > D
15 > |
16 > B C
17 > |/
18 > A
19 > EOS
20
21 $ for i in C D E F; do
22 > hg bookmark -r $i -i BOOK-$i
23 > done
24
25 $ hg debugdrawdag<<'EOS'
26 > E
27 > |
28 > D
29 > |
30 > B
31 > EOS
32
33 $ hg log -G -T '{rev} {desc} {bookmarks}'
34 o 7 E
35 |
36 o 6 D
37 |
38 | o 5 F BOOK-F
39 | |
40 | o 4 E BOOK-E
41 | |
42 | o 3 D BOOK-D
43 | |
44 | o 2 C BOOK-C
45 | |
46 o | 1 B
47 |/
48 o 0 A
49
50 With --keep, bookmark should not move
51
52 $ hg rebase -r 3+4 -d E --keep
53 rebasing 3:e7b3f00ed42e "D" (BOOK-D)
54 note: rebase of 3:e7b3f00ed42e created no changes to commit
55 rebasing 4:69a34c08022a "E" (BOOK-E)
56 note: rebase of 4:69a34c08022a created no changes to commit
57 $ hg log -G -T '{rev} {desc} {bookmarks}'
58 o 7 E
59 |
60 o 6 D
61 |
62 | o 5 F BOOK-F
63 | |
64 | o 4 E BOOK-E
65 | |
66 | o 3 D BOOK-D
67 | |
68 | o 2 C BOOK-C
69 | |
70 o | 1 B
71 |/
72 o 0 A
73
74 Bookmark is usually an indication of a head. For changes that are introduced by
75 an ancestor of bookmark B, after moving B to B-NEW, the changes are ideally
76 still introduced by an ancestor of changeset on B-NEW. In the below case,
77 "BOOK-D", and "BOOK-E" include changes introduced by "C".
78
79 $ hg rebase -s 2 -d E
80 rebasing 2:dc0947a82db8 "C" (C BOOK-C)
81 rebasing 3:e7b3f00ed42e "D" (BOOK-D)
82 note: rebase of 3:e7b3f00ed42e created no changes to commit
83 rebasing 4:69a34c08022a "E" (BOOK-E)
84 note: rebase of 4:69a34c08022a created no changes to commit
85 rebasing 5:6b2aeab91270 "F" (F BOOK-F)
86 saved backup bundle to $TESTTMP/non-merge/.hg/strip-backup/dc0947a82db8-52bb4973-rebase.hg (glob)
87 $ hg log -G -T '{rev} {desc} {bookmarks}'
88 o 5 F BOOK-F
89 |
90 o 4 C BOOK-C BOOK-D BOOK-E
91 |
92 o 3 E
93 |
94 o 2 D
95 |
96 o 1 B
97 |
98 o 0 A
99
100 Merge and its ancestors all become empty
101
102 $ hg init $TESTTMP/merge1
103 $ cd $TESTTMP/merge1
104
105 $ hg debugdrawdag<<'EOS'
106 > E
107 > /|
108 > B C D
109 > \|/
110 > A
111 > EOS
112
113 $ for i in C D E; do
114 > hg bookmark -r $i -i BOOK-$i
115 > done
116
117 $ hg debugdrawdag<<'EOS'
118 > H
119 > |
120 > D
121 > |
122 > C
123 > |
124 > B
125 > EOS
126
127 $ hg rebase -r '(A::)-(B::)-A' -d H
128 rebasing 2:dc0947a82db8 "C" (BOOK-C)
129 note: rebase of 2:dc0947a82db8 created no changes to commit
130 rebasing 3:b18e25de2cf5 "D" (BOOK-D)
131 note: rebase of 3:b18e25de2cf5 created no changes to commit
132 rebasing 4:86a1f6686812 "E" (E BOOK-E)
133 note: rebase of 4:86a1f6686812 created no changes to commit
134 saved backup bundle to $TESTTMP/merge1/.hg/strip-backup/b18e25de2cf5-1fd0a4ba-rebase.hg (glob)
135
136 $ hg log -G -T '{rev} {desc} {bookmarks}'
137 o 4 H BOOK-C BOOK-D BOOK-E
138 |
139 o 3 D
140 |
141 o 2 C
142 |
143 o 1 B
144 |
145 o 0 A
146
147 Part of ancestors of a merge become empty
148
149 $ hg init $TESTTMP/merge2
150 $ cd $TESTTMP/merge2
151
152 $ hg debugdrawdag<<'EOS'
153 > G
154 > /|
155 > E F
156 > | |
157 > B C D
158 > \|/
159 > A
160 > EOS
161
162 $ for i in C D E F G; do
163 > hg bookmark -r $i -i BOOK-$i
164 > done
165
166 $ hg debugdrawdag<<'EOS'
167 > H
168 > |
169 > F
170 > |
171 > C
172 > |
173 > B
174 > EOS
175
176 $ hg rebase -r '(A::)-(B::)-A' -d H
177 rebasing 2:dc0947a82db8 "C" (BOOK-C)
178 note: rebase of 2:dc0947a82db8 created no changes to commit
179 rebasing 3:b18e25de2cf5 "D" (D BOOK-D)
180 rebasing 4:03ca77807e91 "E" (E BOOK-E)
181 rebasing 5:ad6717a6a58e "F" (BOOK-F)
182 note: rebase of 5:ad6717a6a58e created no changes to commit
183 rebasing 6:c58e8bdac1f4 "G" (G BOOK-G)
184 saved backup bundle to $TESTTMP/merge2/.hg/strip-backup/b18e25de2cf5-2d487005-rebase.hg (glob)
185
186 $ hg log -G -T '{rev} {desc} {bookmarks}'
187 o 7 G BOOK-G
188 |\
189 | o 6 E BOOK-E
190 | |
191 o | 5 D BOOK-D BOOK-F
192 |/
193 o 4 H BOOK-C
194 |
195 o 3 F
196 |
197 o 2 C
198 |
199 o 1 B
200 |
201 o 0 A
202
@@ -1,1478 +1,1540
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 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from __future__ import absolute_import
18 18
19 19 import errno
20 20 import os
21 21
22 22 from mercurial.i18n import _
23 23 from mercurial.node import (
24 24 hex,
25 25 nullid,
26 26 nullrev,
27 27 short,
28 28 )
29 29 from mercurial import (
30 30 bookmarks,
31 31 cmdutil,
32 32 commands,
33 33 copies,
34 34 destutil,
35 35 dirstateguard,
36 36 error,
37 37 extensions,
38 38 hg,
39 39 lock,
40 40 merge as mergemod,
41 41 mergeutil,
42 42 obsolete,
43 43 obsutil,
44 44 patch,
45 45 phases,
46 46 registrar,
47 47 repair,
48 48 repoview,
49 49 revset,
50 50 scmutil,
51 51 smartset,
52 52 util,
53 53 )
54 54
55 55 release = lock.release
56 56 templateopts = cmdutil.templateopts
57 57
58 58 # The following constants are used throughout the rebase module. The ordering of
59 59 # their values must be maintained.
60 60
61 61 # Indicates that a revision needs to be rebased
62 62 revtodo = -1
63 63 nullmerge = -2
64 64 revignored = -3
65 65 # successor in rebase destination
66 66 revprecursor = -4
67 67 # plain prune (no successor)
68 68 revpruned = -5
69 69 revskipped = (revignored, revprecursor, revpruned)
70 70
71 71 cmdtable = {}
72 72 command = registrar.command(cmdtable)
73 73 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
74 74 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
75 75 # be specifying the version(s) of Mercurial they are tested with, or
76 76 # leave the attribute unspecified.
77 77 testedwith = 'ships-with-hg-core'
78 78
79 79 def _nothingtorebase():
80 80 return 1
81 81
82 82 def _savegraft(ctx, extra):
83 83 s = ctx.extra().get('source', None)
84 84 if s is not None:
85 85 extra['source'] = s
86 86 s = ctx.extra().get('intermediate-source', None)
87 87 if s is not None:
88 88 extra['intermediate-source'] = s
89 89
90 90 def _savebranch(ctx, extra):
91 91 extra['branch'] = ctx.branch()
92 92
93 93 def _makeextrafn(copiers):
94 94 """make an extrafn out of the given copy-functions.
95 95
96 96 A copy function takes a context and an extra dict, and mutates the
97 97 extra dict as needed based on the given context.
98 98 """
99 99 def extrafn(ctx, extra):
100 100 for c in copiers:
101 101 c(ctx, extra)
102 102 return extrafn
103 103
104 104 def _destrebase(repo, sourceset, destspace=None):
105 105 """small wrapper around destmerge to pass the right extra args
106 106
107 107 Please wrap destutil.destmerge instead."""
108 108 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
109 109 onheadcheck=False, destspace=destspace)
110 110
111 111 revsetpredicate = registrar.revsetpredicate()
112 112
113 113 @revsetpredicate('_destrebase')
114 114 def _revsetdestrebase(repo, subset, x):
115 115 # ``_rebasedefaultdest()``
116 116
117 117 # default destination for rebase.
118 118 # # XXX: Currently private because I expect the signature to change.
119 119 # # XXX: - bailing out in case of ambiguity vs returning all data.
120 120 # i18n: "_rebasedefaultdest" is a keyword
121 121 sourceset = None
122 122 if x is not None:
123 123 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
124 124 return subset & smartset.baseset([_destrebase(repo, sourceset)])
125 125
126 126 class rebaseruntime(object):
127 127 """This class is a container for rebase runtime state"""
128 128 def __init__(self, repo, ui, opts=None):
129 129 if opts is None:
130 130 opts = {}
131 131
132 132 self.repo = repo
133 133 self.ui = ui
134 134 self.opts = opts
135 135 self.originalwd = None
136 136 self.external = nullrev
137 137 # Mapping between the old revision id and either what is the new rebased
138 138 # revision or what needs to be done with the old revision. The state
139 139 # dict will be what contains most of the rebase progress state.
140 140 self.state = {}
141 141 self.activebookmark = None
142 142 self.dest = None
143 143 self.skipped = set()
144 144 self.destancestors = set()
145 145
146 146 self.collapsef = opts.get('collapse', False)
147 147 self.collapsemsg = cmdutil.logmessage(ui, opts)
148 148 self.date = opts.get('date', None)
149 149
150 150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
151 151 self.extrafns = [_savegraft]
152 152 if e:
153 153 self.extrafns = [e]
154 154
155 155 self.keepf = opts.get('keep', False)
156 156 self.keepbranchesf = opts.get('keepbranches', False)
157 157 # keepopen is not meant for use on the command line, but by
158 158 # other extensions
159 159 self.keepopen = opts.get('keepopen', False)
160 160 self.obsoletenotrebased = {}
161 161
162 162 def storestatus(self, tr=None):
163 163 """Store the current status to allow recovery"""
164 164 if tr:
165 165 tr.addfilegenerator('rebasestate', ('rebasestate',),
166 166 self._writestatus, location='plain')
167 167 else:
168 168 with self.repo.vfs("rebasestate", "w") as f:
169 169 self._writestatus(f)
170 170
171 171 def _writestatus(self, f):
172 172 repo = self.repo.unfiltered()
173 173 f.write(repo[self.originalwd].hex() + '\n')
174 174 f.write(repo[self.dest].hex() + '\n')
175 175 f.write(repo[self.external].hex() + '\n')
176 176 f.write('%d\n' % int(self.collapsef))
177 177 f.write('%d\n' % int(self.keepf))
178 178 f.write('%d\n' % int(self.keepbranchesf))
179 179 f.write('%s\n' % (self.activebookmark or ''))
180 180 for d, v in self.state.iteritems():
181 181 oldrev = repo[d].hex()
182 182 if v >= 0:
183 183 newrev = repo[v].hex()
184 184 elif v == revtodo:
185 185 # To maintain format compatibility, we have to use nullid.
186 186 # Please do remove this special case when upgrading the format.
187 187 newrev = hex(nullid)
188 188 else:
189 189 newrev = v
190 190 f.write("%s:%s\n" % (oldrev, newrev))
191 191 repo.ui.debug('rebase status stored\n')
192 192
193 193 def restorestatus(self):
194 194 """Restore a previously stored status"""
195 195 repo = self.repo
196 196 keepbranches = None
197 197 dest = None
198 198 collapse = False
199 199 external = nullrev
200 200 activebookmark = None
201 201 state = {}
202 202
203 203 try:
204 204 f = repo.vfs("rebasestate")
205 205 for i, l in enumerate(f.read().splitlines()):
206 206 if i == 0:
207 207 originalwd = repo[l].rev()
208 208 elif i == 1:
209 209 dest = repo[l].rev()
210 210 elif i == 2:
211 211 external = repo[l].rev()
212 212 elif i == 3:
213 213 collapse = bool(int(l))
214 214 elif i == 4:
215 215 keep = bool(int(l))
216 216 elif i == 5:
217 217 keepbranches = bool(int(l))
218 218 elif i == 6 and not (len(l) == 81 and ':' in l):
219 219 # line 6 is a recent addition, so for backwards
220 220 # compatibility check that the line doesn't look like the
221 221 # oldrev:newrev lines
222 222 activebookmark = l
223 223 else:
224 224 oldrev, newrev = l.split(':')
225 225 if newrev in (str(nullmerge), str(revignored),
226 226 str(revprecursor), str(revpruned)):
227 227 state[repo[oldrev].rev()] = int(newrev)
228 228 elif newrev == nullid:
229 229 state[repo[oldrev].rev()] = revtodo
230 230 # Legacy compat special case
231 231 else:
232 232 state[repo[oldrev].rev()] = repo[newrev].rev()
233 233
234 234 except IOError as err:
235 235 if err.errno != errno.ENOENT:
236 236 raise
237 237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
238 238
239 239 if keepbranches is None:
240 240 raise error.Abort(_('.hg/rebasestate is incomplete'))
241 241
242 242 skipped = set()
243 243 # recompute the set of skipped revs
244 244 if not collapse:
245 245 seen = {dest}
246 246 for old, new in sorted(state.items()):
247 247 if new != revtodo and new in seen:
248 248 skipped.add(old)
249 249 seen.add(new)
250 250 repo.ui.debug('computed skipped revs: %s\n' %
251 251 (' '.join(str(r) for r in sorted(skipped)) or None))
252 252 repo.ui.debug('rebase status resumed\n')
253 253 _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
254 254
255 255 self.originalwd = originalwd
256 256 self.dest = dest
257 257 self.state = state
258 258 self.skipped = skipped
259 259 self.collapsef = collapse
260 260 self.keepf = keep
261 261 self.keepbranchesf = keepbranches
262 262 self.external = external
263 263 self.activebookmark = activebookmark
264 264
265 265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
266 266 """Compute structures necessary for skipping obsolete revisions
267 267
268 268 rebaserevs: iterable of all revisions that are to be rebased
269 269 obsoleterevs: iterable of all obsolete revisions in rebaseset
270 270 dest: a destination revision for the rebase operation
271 271 """
272 272 self.obsoletenotrebased = {}
273 273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
274 274 default=True):
275 275 return
276 276 rebaseset = set(rebaserevs)
277 277 obsoleteset = set(obsoleterevs)
278 278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
279 279 obsoleteset, dest)
280 280 skippedset = set(self.obsoletenotrebased)
281 281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
282 282
283 283 def _prepareabortorcontinue(self, isabort):
284 284 try:
285 285 self.restorestatus()
286 286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
287 287 except error.RepoLookupError:
288 288 if isabort:
289 289 clearstatus(self.repo)
290 290 clearcollapsemsg(self.repo)
291 291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
292 292 ' only broken state is cleared)\n'))
293 293 return 0
294 294 else:
295 295 msg = _('cannot continue inconsistent rebase')
296 296 hint = _('use "hg rebase --abort" to clear broken state')
297 297 raise error.Abort(msg, hint=hint)
298 298 if isabort:
299 299 return abort(self.repo, self.originalwd, self.dest,
300 300 self.state, activebookmark=self.activebookmark)
301 301
302 302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
303 303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.dest)
304 304
305 305 def _preparenewrebase(self, dest, rebaseset):
306 306 if dest is None:
307 307 return _nothingtorebase()
308 308
309 309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
310 310 if (not (self.keepf or allowunstable)
311 311 and self.repo.revs('first(children(%ld) - %ld)',
312 312 rebaseset, rebaseset)):
313 313 raise error.Abort(
314 314 _("can't remove original changesets with"
315 315 " unrebased descendants"),
316 316 hint=_('use --keep to keep original changesets'))
317 317
318 318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
319 319 self._handleskippingobsolete(rebaseset, obsrevs, dest.rev())
320 320
321 321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
322 322 self.obsoletenotrebased)
323 323
324 324 if not result:
325 325 # Empty state built, nothing to rebase
326 326 self.ui.status(_('nothing to rebase\n'))
327 327 return _nothingtorebase()
328 328
329 329 for root in self.repo.set('roots(%ld)', rebaseset):
330 330 if not self.keepf and not root.mutable():
331 331 raise error.Abort(_("can't rebase public changeset %s")
332 332 % root,
333 333 hint=_("see 'hg help phases' for details"))
334 334
335 335 (self.originalwd, self.dest, self.state) = result
336 336 if self.collapsef:
337 337 self.destancestors = self.repo.changelog.ancestors(
338 338 [self.dest],
339 339 inclusive=True)
340 340 self.external = externalparent(self.repo, self.state,
341 341 self.destancestors)
342 342
343 343 if dest.closesbranch() and not self.keepbranchesf:
344 344 self.ui.status(_('reopening closed branch head %s\n') % dest)
345 345
346 346 def _performrebase(self, tr):
347 347 repo, ui, opts = self.repo, self.ui, self.opts
348 348 if self.keepbranchesf:
349 349 # insert _savebranch at the start of extrafns so if
350 350 # there's a user-provided extrafn it can clobber branch if
351 351 # desired
352 352 self.extrafns.insert(0, _savebranch)
353 353 if self.collapsef:
354 354 branches = set()
355 355 for rev in self.state:
356 356 branches.add(repo[rev].branch())
357 357 if len(branches) > 1:
358 358 raise error.Abort(_('cannot collapse multiple named '
359 359 'branches'))
360 360
361 361 # Rebase
362 362 if not self.destancestors:
363 363 self.destancestors = repo.changelog.ancestors([self.dest],
364 364 inclusive=True)
365 365
366 366 # Keep track of the active bookmarks in order to reset them later
367 367 self.activebookmark = self.activebookmark or repo._activebookmark
368 368 if self.activebookmark:
369 369 bookmarks.deactivate(repo)
370 370
371 371 # Store the state before we begin so users can run 'hg rebase --abort'
372 372 # if we fail before the transaction closes.
373 373 self.storestatus()
374 374
375 375 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
376 376 cands = [k for k, v in self.state.iteritems() if v == revtodo]
377 377 total = len(cands)
378 378 pos = 0
379 379 for rev in sortedrevs:
380 380 ctx = repo[rev]
381 381 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
382 382 ctx.description().split('\n', 1)[0])
383 383 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
384 384 if names:
385 385 desc += ' (%s)' % ' '.join(names)
386 386 if self.state[rev] == rev:
387 387 ui.status(_('already rebased %s\n') % desc)
388 388 elif self.state[rev] == revtodo:
389 389 pos += 1
390 390 ui.status(_('rebasing %s\n') % desc)
391 391 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
392 392 _('changesets'), total)
393 393 p1, p2, base = defineparents(repo, rev, self.dest,
394 394 self.state,
395 395 self.destancestors,
396 396 self.obsoletenotrebased)
397 397 self.storestatus(tr=tr)
398 398 storecollapsemsg(repo, self.collapsemsg)
399 399 if len(repo[None].parents()) == 2:
400 400 repo.ui.debug('resuming interrupted rebase\n')
401 401 else:
402 402 try:
403 403 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
404 404 'rebase')
405 405 stats = rebasenode(repo, rev, p1, base, self.state,
406 406 self.collapsef, self.dest)
407 407 if stats and stats[3] > 0:
408 408 raise error.InterventionRequired(
409 409 _('unresolved conflicts (see hg '
410 410 'resolve, then hg rebase --continue)'))
411 411 finally:
412 412 ui.setconfig('ui', 'forcemerge', '', 'rebase')
413 413 if not self.collapsef:
414 414 merging = p2 != nullrev
415 415 editform = cmdutil.mergeeditform(merging, 'rebase')
416 416 editor = cmdutil.getcommiteditor(editform=editform, **opts)
417 417 newnode = concludenode(repo, rev, p1, p2,
418 418 extrafn=_makeextrafn(self.extrafns),
419 419 editor=editor,
420 420 keepbranches=self.keepbranchesf,
421 421 date=self.date)
422 422 if newnode is None:
423 423 # If it ended up being a no-op commit, then the normal
424 424 # merge state clean-up path doesn't happen, so do it
425 425 # here. Fix issue5494
426 426 mergemod.mergestate.clean(repo)
427 427 else:
428 428 # Skip commit if we are collapsing
429 429 repo.setparents(repo[p1].node())
430 430 newnode = None
431 431 # Update the state
432 432 if newnode is not None:
433 433 self.state[rev] = repo[newnode].rev()
434 434 ui.debug('rebased as %s\n' % short(newnode))
435 435 else:
436 436 if not self.collapsef:
437 437 ui.warn(_('note: rebase of %d:%s created no changes '
438 438 'to commit\n') % (rev, ctx))
439 439 self.skipped.add(rev)
440 440 self.state[rev] = p1
441 441 ui.debug('next revision set to %s\n' % p1)
442 442 elif self.state[rev] == nullmerge:
443 443 ui.debug('ignoring null merge rebase of %s\n' % rev)
444 444 elif self.state[rev] == revignored:
445 445 ui.status(_('not rebasing ignored %s\n') % desc)
446 446 elif self.state[rev] == revprecursor:
447 447 destctx = repo[self.obsoletenotrebased[rev]]
448 448 descdest = '%d:%s "%s"' % (destctx.rev(), destctx,
449 449 destctx.description().split('\n', 1)[0])
450 450 msg = _('note: not rebasing %s, already in destination as %s\n')
451 451 ui.status(msg % (desc, descdest))
452 452 elif self.state[rev] == revpruned:
453 453 msg = _('note: not rebasing %s, it has no successor\n')
454 454 ui.status(msg % desc)
455 455 else:
456 456 ui.status(_('already rebased %s as %s\n') %
457 457 (desc, repo[self.state[rev]]))
458 458
459 459 ui.progress(_('rebasing'), None)
460 460 ui.note(_('rebase merging completed\n'))
461 461
462 462 def _finishrebase(self):
463 463 repo, ui, opts = self.repo, self.ui, self.opts
464 464 if self.collapsef and not self.keepopen:
465 465 p1, p2, _base = defineparents(repo, min(self.state),
466 466 self.dest, self.state,
467 467 self.destancestors,
468 468 self.obsoletenotrebased)
469 469 editopt = opts.get('edit')
470 470 editform = 'rebase.collapse'
471 471 if self.collapsemsg:
472 472 commitmsg = self.collapsemsg
473 473 else:
474 474 commitmsg = 'Collapsed revision'
475 475 for rebased in self.state:
476 476 if rebased not in self.skipped and\
477 477 self.state[rebased] > nullmerge:
478 478 commitmsg += '\n* %s' % repo[rebased].description()
479 479 editopt = True
480 480 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
481 481 revtoreuse = max(self.state)
482 482 newnode = concludenode(repo, revtoreuse, p1, self.external,
483 483 commitmsg=commitmsg,
484 484 extrafn=_makeextrafn(self.extrafns),
485 485 editor=editor,
486 486 keepbranches=self.keepbranchesf,
487 487 date=self.date)
488 488 if newnode is None:
489 489 newrev = self.dest
490 490 else:
491 491 newrev = repo[newnode].rev()
492 492 for oldrev in self.state.iterkeys():
493 493 if self.state[oldrev] > nullmerge:
494 494 self.state[oldrev] = newrev
495 495
496 496 if 'qtip' in repo.tags():
497 497 updatemq(repo, self.state, self.skipped, **opts)
498 498
499 499 # restore original working directory
500 500 # (we do this before stripping)
501 501 newwd = self.state.get(self.originalwd, self.originalwd)
502 502 if newwd == revprecursor:
503 503 newwd = self.obsoletenotrebased[self.originalwd]
504 504 elif newwd < 0:
505 505 # original directory is a parent of rebase set root or ignored
506 506 newwd = self.originalwd
507 507 if newwd not in [c.rev() for c in repo[None].parents()]:
508 508 ui.note(_("update back to initial working directory parent\n"))
509 509 hg.updaterepo(repo, newwd, False)
510 510
511 511 if not self.keepf:
512 512 collapsedas = None
513 513 if self.collapsef:
514 514 collapsedas = newnode
515 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
515 clearrebased(ui, repo, self.dest, self.state, self.skipped,
516 collapsedas)
516 517
517 518 clearstatus(repo)
518 519 clearcollapsemsg(repo)
519 520
520 521 ui.note(_("rebase completed\n"))
521 522 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
522 523 if self.skipped:
523 524 skippedlen = len(self.skipped)
524 525 ui.note(_("%d revisions have been skipped\n") % skippedlen)
525 526
526 527 if (self.activebookmark and self.activebookmark in repo._bookmarks and
527 528 repo['.'].node() == repo._bookmarks[self.activebookmark]):
528 529 bookmarks.activate(repo, self.activebookmark)
529 530
530 531 @command('rebase',
531 532 [('s', 'source', '',
532 533 _('rebase the specified changeset and descendants'), _('REV')),
533 534 ('b', 'base', '',
534 535 _('rebase everything from branching point of specified changeset'),
535 536 _('REV')),
536 537 ('r', 'rev', [],
537 538 _('rebase these revisions'),
538 539 _('REV')),
539 540 ('d', 'dest', '',
540 541 _('rebase onto the specified changeset'), _('REV')),
541 542 ('', 'collapse', False, _('collapse the rebased changesets')),
542 543 ('m', 'message', '',
543 544 _('use text as collapse commit message'), _('TEXT')),
544 545 ('e', 'edit', False, _('invoke editor on commit messages')),
545 546 ('l', 'logfile', '',
546 547 _('read collapse commit message from file'), _('FILE')),
547 548 ('k', 'keep', False, _('keep original changesets')),
548 549 ('', 'keepbranches', False, _('keep original branch names')),
549 550 ('D', 'detach', False, _('(DEPRECATED)')),
550 551 ('i', 'interactive', False, _('(DEPRECATED)')),
551 552 ('t', 'tool', '', _('specify merge tool')),
552 553 ('c', 'continue', False, _('continue an interrupted rebase')),
553 554 ('a', 'abort', False, _('abort an interrupted rebase'))] +
554 555 templateopts,
555 556 _('[-s REV | -b REV] [-d REV] [OPTION]'))
556 557 def rebase(ui, repo, **opts):
557 558 """move changeset (and descendants) to a different branch
558 559
559 560 Rebase uses repeated merging to graft changesets from one part of
560 561 history (the source) onto another (the destination). This can be
561 562 useful for linearizing *local* changes relative to a master
562 563 development tree.
563 564
564 565 Published commits cannot be rebased (see :hg:`help phases`).
565 566 To copy commits, see :hg:`help graft`.
566 567
567 568 If you don't specify a destination changeset (``-d/--dest``), rebase
568 569 will use the same logic as :hg:`merge` to pick a destination. if
569 570 the current branch contains exactly one other head, the other head
570 571 is merged with by default. Otherwise, an explicit revision with
571 572 which to merge with must be provided. (destination changeset is not
572 573 modified by rebasing, but new changesets are added as its
573 574 descendants.)
574 575
575 576 Here are the ways to select changesets:
576 577
577 578 1. Explicitly select them using ``--rev``.
578 579
579 580 2. Use ``--source`` to select a root changeset and include all of its
580 581 descendants.
581 582
582 583 3. Use ``--base`` to select a changeset; rebase will find ancestors
583 584 and their descendants which are not also ancestors of the destination.
584 585
585 586 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
586 587 rebase will use ``--base .`` as above.
587 588
588 589 Rebase will destroy original changesets unless you use ``--keep``.
589 590 It will also move your bookmarks (even if you do).
590 591
591 592 Some changesets may be dropped if they do not contribute changes
592 593 (e.g. merges from the destination branch).
593 594
594 595 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
595 596 a named branch with two heads. You will need to explicitly specify source
596 597 and/or destination.
597 598
598 599 If you need to use a tool to automate merge/conflict decisions, you
599 600 can specify one with ``--tool``, see :hg:`help merge-tools`.
600 601 As a caveat: the tool will not be used to mediate when a file was
601 602 deleted, there is no hook presently available for this.
602 603
603 604 If a rebase is interrupted to manually resolve a conflict, it can be
604 605 continued with --continue/-c or aborted with --abort/-a.
605 606
606 607 .. container:: verbose
607 608
608 609 Examples:
609 610
610 611 - move "local changes" (current commit back to branching point)
611 612 to the current branch tip after a pull::
612 613
613 614 hg rebase
614 615
615 616 - move a single changeset to the stable branch::
616 617
617 618 hg rebase -r 5f493448 -d stable
618 619
619 620 - splice a commit and all its descendants onto another part of history::
620 621
621 622 hg rebase --source c0c3 --dest 4cf9
622 623
623 624 - rebase everything on a branch marked by a bookmark onto the
624 625 default branch::
625 626
626 627 hg rebase --base myfeature --dest default
627 628
628 629 - collapse a sequence of changes into a single commit::
629 630
630 631 hg rebase --collapse -r 1520:1525 -d .
631 632
632 633 - move a named branch while preserving its name::
633 634
634 635 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
635 636
636 637 Configuration Options:
637 638
638 639 You can make rebase require a destination if you set the following config
639 640 option::
640 641
641 642 [commands]
642 643 rebase.requiredest = True
643 644
644 645 By default, rebase will close the transaction after each commit. For
645 646 performance purposes, you can configure rebase to use a single transaction
646 647 across the entire rebase. WARNING: This setting introduces a significant
647 648 risk of losing the work you've done in a rebase if the rebase aborts
648 649 unexpectedly::
649 650
650 651 [rebase]
651 652 singletransaction = True
652 653
653 654 Return Values:
654 655
655 656 Returns 0 on success, 1 if nothing to rebase or there are
656 657 unresolved conflicts.
657 658
658 659 """
659 660 rbsrt = rebaseruntime(repo, ui, opts)
660 661
661 662 with repo.wlock(), repo.lock():
662 663 # Validate input and define rebasing points
663 664 destf = opts.get('dest', None)
664 665 srcf = opts.get('source', None)
665 666 basef = opts.get('base', None)
666 667 revf = opts.get('rev', [])
667 668 # search default destination in this space
668 669 # used in the 'hg pull --rebase' case, see issue 5214.
669 670 destspace = opts.get('_destspace')
670 671 contf = opts.get('continue')
671 672 abortf = opts.get('abort')
672 673 if opts.get('interactive'):
673 674 try:
674 675 if extensions.find('histedit'):
675 676 enablehistedit = ''
676 677 except KeyError:
677 678 enablehistedit = " --config extensions.histedit="
678 679 help = "hg%s help -e histedit" % enablehistedit
679 680 msg = _("interactive history editing is supported by the "
680 681 "'histedit' extension (see \"%s\")") % help
681 682 raise error.Abort(msg)
682 683
683 684 if rbsrt.collapsemsg and not rbsrt.collapsef:
684 685 raise error.Abort(
685 686 _('message can only be specified with collapse'))
686 687
687 688 if contf or abortf:
688 689 if contf and abortf:
689 690 raise error.Abort(_('cannot use both abort and continue'))
690 691 if rbsrt.collapsef:
691 692 raise error.Abort(
692 693 _('cannot use collapse with continue or abort'))
693 694 if srcf or basef or destf:
694 695 raise error.Abort(
695 696 _('abort and continue do not allow specifying revisions'))
696 697 if abortf and opts.get('tool', False):
697 698 ui.warn(_('tool option will be ignored\n'))
698 699 if contf:
699 700 ms = mergemod.mergestate.read(repo)
700 701 mergeutil.checkunresolved(ms)
701 702
702 703 retcode = rbsrt._prepareabortorcontinue(abortf)
703 704 if retcode is not None:
704 705 return retcode
705 706 else:
706 707 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
707 708 destspace=destspace)
708 709 retcode = rbsrt._preparenewrebase(dest, rebaseset)
709 710 if retcode is not None:
710 711 return retcode
711 712
712 713 tr = None
713 714 if ui.configbool('rebase', 'singletransaction'):
714 715 tr = repo.transaction('rebase')
715 716 with util.acceptintervention(tr):
716 717 rbsrt._performrebase(tr)
717 718
718 719 rbsrt._finishrebase()
719 720
720 721 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
721 722 destspace=None):
722 723 """use revisions argument to define destination and rebase set
723 724 """
724 725 if revf is None:
725 726 revf = []
726 727
727 728 # destspace is here to work around issues with `hg pull --rebase` see
728 729 # issue5214 for details
729 730 if srcf and basef:
730 731 raise error.Abort(_('cannot specify both a source and a base'))
731 732 if revf and basef:
732 733 raise error.Abort(_('cannot specify both a revision and a base'))
733 734 if revf and srcf:
734 735 raise error.Abort(_('cannot specify both a revision and a source'))
735 736
736 737 cmdutil.checkunfinished(repo)
737 738 cmdutil.bailifchanged(repo)
738 739
739 740 if ui.configbool('commands', 'rebase.requiredest') and not destf:
740 741 raise error.Abort(_('you must specify a destination'),
741 742 hint=_('use: hg rebase -d REV'))
742 743
743 744 if destf:
744 745 dest = scmutil.revsingle(repo, destf)
745 746
746 747 if revf:
747 748 rebaseset = scmutil.revrange(repo, revf)
748 749 if not rebaseset:
749 750 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
750 751 return None, None
751 752 elif srcf:
752 753 src = scmutil.revrange(repo, [srcf])
753 754 if not src:
754 755 ui.status(_('empty "source" revision set - nothing to rebase\n'))
755 756 return None, None
756 757 rebaseset = repo.revs('(%ld)::', src)
757 758 assert rebaseset
758 759 else:
759 760 base = scmutil.revrange(repo, [basef or '.'])
760 761 if not base:
761 762 ui.status(_('empty "base" revision set - '
762 763 "can't compute rebase set\n"))
763 764 return None, None
764 765 if not destf:
765 766 dest = repo[_destrebase(repo, base, destspace=destspace)]
766 767 destf = str(dest)
767 768
768 769 roots = [] # selected children of branching points
769 770 bpbase = {} # {branchingpoint: [origbase]}
770 771 for b in base: # group bases by branching points
771 772 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
772 773 bpbase[bp] = bpbase.get(bp, []) + [b]
773 774 if None in bpbase:
774 775 # emulate the old behavior, showing "nothing to rebase" (a better
775 776 # behavior may be abort with "cannot find branching point" error)
776 777 bpbase.clear()
777 778 for bp, bs in bpbase.iteritems(): # calculate roots
778 779 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
779 780
780 781 rebaseset = repo.revs('%ld::', roots)
781 782
782 783 if not rebaseset:
783 784 # transform to list because smartsets are not comparable to
784 785 # lists. This should be improved to honor laziness of
785 786 # smartset.
786 787 if list(base) == [dest.rev()]:
787 788 if basef:
788 789 ui.status(_('nothing to rebase - %s is both "base"'
789 790 ' and destination\n') % dest)
790 791 else:
791 792 ui.status(_('nothing to rebase - working directory '
792 793 'parent is also destination\n'))
793 794 elif not repo.revs('%ld - ::%d', base, dest):
794 795 if basef:
795 796 ui.status(_('nothing to rebase - "base" %s is '
796 797 'already an ancestor of destination '
797 798 '%s\n') %
798 799 ('+'.join(str(repo[r]) for r in base),
799 800 dest))
800 801 else:
801 802 ui.status(_('nothing to rebase - working '
802 803 'directory parent is already an '
803 804 'ancestor of destination %s\n') % dest)
804 805 else: # can it happen?
805 806 ui.status(_('nothing to rebase from %s to %s\n') %
806 807 ('+'.join(str(repo[r]) for r in base), dest))
807 808 return None, None
808 809
809 810 if not destf:
810 811 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
811 812 destf = str(dest)
812 813
813 814 return dest, rebaseset
814 815
815 816 def externalparent(repo, state, destancestors):
816 817 """Return the revision that should be used as the second parent
817 818 when the revisions in state is collapsed on top of destancestors.
818 819 Abort if there is more than one parent.
819 820 """
820 821 parents = set()
821 822 source = min(state)
822 823 for rev in state:
823 824 if rev == source:
824 825 continue
825 826 for p in repo[rev].parents():
826 827 if (p.rev() not in state
827 828 and p.rev() not in destancestors):
828 829 parents.add(p.rev())
829 830 if not parents:
830 831 return nullrev
831 832 if len(parents) == 1:
832 833 return parents.pop()
833 834 raise error.Abort(_('unable to collapse on top of %s, there is more '
834 835 'than one external parent: %s') %
835 836 (max(destancestors),
836 837 ', '.join(str(p) for p in sorted(parents))))
837 838
838 839 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
839 840 keepbranches=False, date=None):
840 841 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
841 842 but also store useful information in extra.
842 843 Return node of committed revision.'''
843 844 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
844 845 try:
845 846 repo.setparents(repo[p1].node(), repo[p2].node())
846 847 ctx = repo[rev]
847 848 if commitmsg is None:
848 849 commitmsg = ctx.description()
849 850 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
850 851 extra = {'rebase_source': ctx.hex()}
851 852 if extrafn:
852 853 extrafn(ctx, extra)
853 854
854 855 destphase = max(ctx.phase(), phases.draft)
855 856 overrides = {('phases', 'new-commit'): destphase}
856 857 with repo.ui.configoverride(overrides, 'rebase'):
857 858 if keepbranch:
858 859 repo.ui.setconfig('ui', 'allowemptycommit', True)
859 860 # Commit might fail if unresolved files exist
860 861 if date is None:
861 862 date = ctx.date()
862 863 newnode = repo.commit(text=commitmsg, user=ctx.user(),
863 864 date=date, extra=extra, editor=editor)
864 865
865 866 repo.dirstate.setbranch(repo[newnode].branch())
866 867 dsguard.close()
867 868 return newnode
868 869 finally:
869 870 release(dsguard)
870 871
871 872 def rebasenode(repo, rev, p1, base, state, collapse, dest):
872 873 'Rebase a single revision rev on top of p1 using base as merge ancestor'
873 874 # Merge phase
874 875 # Update to destination and merge it with local
875 876 if repo['.'].rev() != p1:
876 877 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
877 878 mergemod.update(repo, p1, False, True)
878 879 else:
879 880 repo.ui.debug(" already in destination\n")
880 881 repo.dirstate.write(repo.currenttransaction())
881 882 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
882 883 if base is not None:
883 884 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
884 885 # When collapsing in-place, the parent is the common ancestor, we
885 886 # have to allow merging with it.
886 887 stats = mergemod.update(repo, rev, True, True, base, collapse,
887 888 labels=['dest', 'source'])
888 889 if collapse:
889 890 copies.duplicatecopies(repo, rev, dest)
890 891 else:
891 892 # If we're not using --collapse, we need to
892 893 # duplicate copies between the revision we're
893 894 # rebasing and its first parent, but *not*
894 895 # duplicate any copies that have already been
895 896 # performed in the destination.
896 897 p1rev = repo[rev].p1().rev()
897 898 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
898 899 return stats
899 900
901 def adjustdest(repo, rev, dest, state):
902 """adjust rebase destination given the current rebase state
903
904 rev is what is being rebased. Return a list of two revs, which are the
905 adjusted destinations for rev's p1 and p2, respectively. If a parent is
906 nullrev, return dest without adjustment for it.
907
908 For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
909 and E's destination will be adjusted from F to B1.
910
911 B1 <- written during rebasing B
912 |
913 F <- original destination of B, E
914 |
915 | E <- rev, which is being rebased
916 | |
917 | D <- prev, one parent of rev being checked
918 | |
919 | x <- skipped, ex. no successor or successor in (::dest)
920 | |
921 | C
922 | |
923 | B <- rebased as B1
924 |/
925 A
926
927 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
928 first move C to C1, G to G1, and when it's checking H, the adjusted
929 destinations will be [C1, G1].
930
931 H C1 G1
932 /| | /
933 F G |/
934 K | | -> K
935 | C D |
936 | |/ |
937 | B | ...
938 |/ |/
939 A A
940 """
941 result = []
942 for prev in repo.changelog.parentrevs(rev):
943 adjusted = dest
944 if prev != nullrev:
945 # pick already rebased revs from state
946 source = [s for s, d in state.items() if d > 0]
947 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
948 if candidate is not None:
949 adjusted = state[candidate]
950 result.append(adjusted)
951 return result
952
900 953 def nearestrebased(repo, rev, state):
901 954 """return the nearest ancestors of rev in the rebase result"""
902 955 rebased = [r for r in state if state[r] > nullmerge]
903 956 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
904 957 if candidates:
905 958 return state[candidates.first()]
906 959 else:
907 960 return None
908 961
909 962 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
910 963 """
911 964 Abort if rebase will create divergence or rebase is noop because of markers
912 965
913 966 `rebaseobsrevs`: set of obsolete revision in source
914 967 `rebasesetrevs`: set of revisions to be rebased from source
915 968 `rebaseobsskipped`: set of revisions from source skipped because they have
916 969 successors in destination
917 970 """
918 971 # Obsolete node with successors not in dest leads to divergence
919 972 divergenceok = ui.configbool('experimental',
920 973 'allowdivergence')
921 974 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
922 975
923 976 if divergencebasecandidates and not divergenceok:
924 977 divhashes = (str(repo[r])
925 978 for r in divergencebasecandidates)
926 979 msg = _("this rebase will cause "
927 980 "divergences from: %s")
928 981 h = _("to force the rebase please set "
929 982 "experimental.allowdivergence=True")
930 983 raise error.Abort(msg % (",".join(divhashes),), hint=h)
931 984
932 985 def defineparents(repo, rev, dest, state, destancestors,
933 986 obsoletenotrebased):
934 987 'Return the new parent relationship of the revision that will be rebased'
935 988 parents = repo[rev].parents()
936 989 p1 = p2 = nullrev
937 990 rp1 = None
938 991
939 992 p1n = parents[0].rev()
940 993 if p1n in destancestors:
941 994 p1 = dest
942 995 elif p1n in state:
943 996 if state[p1n] == nullmerge:
944 997 p1 = dest
945 998 elif state[p1n] in revskipped:
946 999 p1 = nearestrebased(repo, p1n, state)
947 1000 if p1 is None:
948 1001 p1 = dest
949 1002 else:
950 1003 p1 = state[p1n]
951 1004 else: # p1n external
952 1005 p1 = dest
953 1006 p2 = p1n
954 1007
955 1008 if len(parents) == 2 and parents[1].rev() not in destancestors:
956 1009 p2n = parents[1].rev()
957 1010 # interesting second parent
958 1011 if p2n in state:
959 1012 if p1 == dest: # p1n in destancestors or external
960 1013 p1 = state[p2n]
961 1014 if p1 == revprecursor:
962 1015 rp1 = obsoletenotrebased[p2n]
963 1016 elif state[p2n] in revskipped:
964 1017 p2 = nearestrebased(repo, p2n, state)
965 1018 if p2 is None:
966 1019 # no ancestors rebased yet, detach
967 1020 p2 = dest
968 1021 else:
969 1022 p2 = state[p2n]
970 1023 else: # p2n external
971 1024 if p2 != nullrev: # p1n external too => rev is a merged revision
972 1025 raise error.Abort(_('cannot use revision %d as base, result '
973 1026 'would have 3 parents') % rev)
974 1027 p2 = p2n
975 1028 repo.ui.debug(" future parents are %d and %d\n" %
976 1029 (repo[rp1 or p1].rev(), repo[p2].rev()))
977 1030
978 1031 if not any(p.rev() in state for p in parents):
979 1032 # Case (1) root changeset of a non-detaching rebase set.
980 1033 # Let the merge mechanism find the base itself.
981 1034 base = None
982 1035 elif not repo[rev].p2():
983 1036 # Case (2) detaching the node with a single parent, use this parent
984 1037 base = repo[rev].p1().rev()
985 1038 else:
986 1039 # Assuming there is a p1, this is the case where there also is a p2.
987 1040 # We are thus rebasing a merge and need to pick the right merge base.
988 1041 #
989 1042 # Imagine we have:
990 1043 # - M: current rebase revision in this step
991 1044 # - A: one parent of M
992 1045 # - B: other parent of M
993 1046 # - D: destination of this merge step (p1 var)
994 1047 #
995 1048 # Consider the case where D is a descendant of A or B and the other is
996 1049 # 'outside'. In this case, the right merge base is the D ancestor.
997 1050 #
998 1051 # An informal proof, assuming A is 'outside' and B is the D ancestor:
999 1052 #
1000 1053 # If we pick B as the base, the merge involves:
1001 1054 # - changes from B to M (actual changeset payload)
1002 1055 # - changes from B to D (induced by rebase) as D is a rebased
1003 1056 # version of B)
1004 1057 # Which exactly represent the rebase operation.
1005 1058 #
1006 1059 # If we pick A as the base, the merge involves:
1007 1060 # - changes from A to M (actual changeset payload)
1008 1061 # - changes from A to D (with include changes between unrelated A and B
1009 1062 # plus changes induced by rebase)
1010 1063 # Which does not represent anything sensible and creates a lot of
1011 1064 # conflicts. A is thus not the right choice - B is.
1012 1065 #
1013 1066 # Note: The base found in this 'proof' is only correct in the specified
1014 1067 # case. This base does not make sense if is not D a descendant of A or B
1015 1068 # or if the other is not parent 'outside' (especially not if the other
1016 1069 # parent has been rebased). The current implementation does not
1017 1070 # make it feasible to consider different cases separately. In these
1018 1071 # other cases we currently just leave it to the user to correctly
1019 1072 # resolve an impossible merge using a wrong ancestor.
1020 1073 #
1021 1074 # xx, p1 could be -4, and both parents could probably be -4...
1022 1075 for p in repo[rev].parents():
1023 1076 if state.get(p.rev()) == p1:
1024 1077 base = p.rev()
1025 1078 break
1026 1079 else: # fallback when base not found
1027 1080 base = None
1028 1081
1029 1082 # Raise because this function is called wrong (see issue 4106)
1030 1083 raise AssertionError('no base found to rebase on '
1031 1084 '(defineparents called wrong)')
1032 1085 return rp1 or p1, p2, base
1033 1086
1034 1087 def isagitpatch(repo, patchname):
1035 1088 'Return true if the given patch is in git format'
1036 1089 mqpatch = os.path.join(repo.mq.path, patchname)
1037 1090 for line in patch.linereader(file(mqpatch, 'rb')):
1038 1091 if line.startswith('diff --git'):
1039 1092 return True
1040 1093 return False
1041 1094
1042 1095 def updatemq(repo, state, skipped, **opts):
1043 1096 'Update rebased mq patches - finalize and then import them'
1044 1097 mqrebase = {}
1045 1098 mq = repo.mq
1046 1099 original_series = mq.fullseries[:]
1047 1100 skippedpatches = set()
1048 1101
1049 1102 for p in mq.applied:
1050 1103 rev = repo[p.node].rev()
1051 1104 if rev in state:
1052 1105 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1053 1106 (rev, p.name))
1054 1107 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1055 1108 else:
1056 1109 # Applied but not rebased, not sure this should happen
1057 1110 skippedpatches.add(p.name)
1058 1111
1059 1112 if mqrebase:
1060 1113 mq.finish(repo, mqrebase.keys())
1061 1114
1062 1115 # We must start import from the newest revision
1063 1116 for rev in sorted(mqrebase, reverse=True):
1064 1117 if rev not in skipped:
1065 1118 name, isgit = mqrebase[rev]
1066 1119 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1067 1120 (name, state[rev], repo[state[rev]]))
1068 1121 mq.qimport(repo, (), patchname=name, git=isgit,
1069 1122 rev=[str(state[rev])])
1070 1123 else:
1071 1124 # Rebased and skipped
1072 1125 skippedpatches.add(mqrebase[rev][0])
1073 1126
1074 1127 # Patches were either applied and rebased and imported in
1075 1128 # order, applied and removed or unapplied. Discard the removed
1076 1129 # ones while preserving the original series order and guards.
1077 1130 newseries = [s for s in original_series
1078 1131 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1079 1132 mq.fullseries[:] = newseries
1080 1133 mq.seriesdirty = True
1081 1134 mq.savedirty()
1082 1135
1083 1136 def storecollapsemsg(repo, collapsemsg):
1084 1137 'Store the collapse message to allow recovery'
1085 1138 collapsemsg = collapsemsg or ''
1086 1139 f = repo.vfs("last-message.txt", "w")
1087 1140 f.write("%s\n" % collapsemsg)
1088 1141 f.close()
1089 1142
1090 1143 def clearcollapsemsg(repo):
1091 1144 'Remove collapse message file'
1092 1145 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1093 1146
1094 1147 def restorecollapsemsg(repo, isabort):
1095 1148 'Restore previously stored collapse message'
1096 1149 try:
1097 1150 f = repo.vfs("last-message.txt")
1098 1151 collapsemsg = f.readline().strip()
1099 1152 f.close()
1100 1153 except IOError as err:
1101 1154 if err.errno != errno.ENOENT:
1102 1155 raise
1103 1156 if isabort:
1104 1157 # Oh well, just abort like normal
1105 1158 collapsemsg = ''
1106 1159 else:
1107 1160 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1108 1161 return collapsemsg
1109 1162
1110 1163 def clearstatus(repo):
1111 1164 'Remove the status files'
1112 1165 _clearrebasesetvisibiliy(repo)
1113 1166 # Make sure the active transaction won't write the state file
1114 1167 tr = repo.currenttransaction()
1115 1168 if tr:
1116 1169 tr.removefilegenerator('rebasestate')
1117 1170 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1118 1171
1119 1172 def needupdate(repo, state):
1120 1173 '''check whether we should `update --clean` away from a merge, or if
1121 1174 somehow the working dir got forcibly updated, e.g. by older hg'''
1122 1175 parents = [p.rev() for p in repo[None].parents()]
1123 1176
1124 1177 # Are we in a merge state at all?
1125 1178 if len(parents) < 2:
1126 1179 return False
1127 1180
1128 1181 # We should be standing on the first as-of-yet unrebased commit.
1129 1182 firstunrebased = min([old for old, new in state.iteritems()
1130 1183 if new == nullrev])
1131 1184 if firstunrebased in parents:
1132 1185 return True
1133 1186
1134 1187 return False
1135 1188
1136 1189 def abort(repo, originalwd, dest, state, activebookmark=None):
1137 1190 '''Restore the repository to its original state. Additional args:
1138 1191
1139 1192 activebookmark: the name of the bookmark that should be active after the
1140 1193 restore'''
1141 1194
1142 1195 try:
1143 1196 # If the first commits in the rebased set get skipped during the rebase,
1144 1197 # their values within the state mapping will be the dest rev id. The
1145 1198 # dstates list must must not contain the dest rev (issue4896)
1146 1199 dstates = [s for s in state.values() if s >= 0 and s != dest]
1147 1200 immutable = [d for d in dstates if not repo[d].mutable()]
1148 1201 cleanup = True
1149 1202 if immutable:
1150 1203 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1151 1204 % ', '.join(str(repo[r]) for r in immutable),
1152 1205 hint=_("see 'hg help phases' for details"))
1153 1206 cleanup = False
1154 1207
1155 1208 descendants = set()
1156 1209 if dstates:
1157 1210 descendants = set(repo.changelog.descendants(dstates))
1158 1211 if descendants - set(dstates):
1159 1212 repo.ui.warn(_("warning: new changesets detected on destination "
1160 1213 "branch, can't strip\n"))
1161 1214 cleanup = False
1162 1215
1163 1216 if cleanup:
1164 1217 shouldupdate = False
1165 1218 rebased = filter(lambda x: x >= 0 and x != dest, state.values())
1166 1219 if rebased:
1167 1220 strippoints = [
1168 1221 c.node() for c in repo.set('roots(%ld)', rebased)]
1169 1222
1170 1223 updateifonnodes = set(rebased)
1171 1224 updateifonnodes.add(dest)
1172 1225 updateifonnodes.add(originalwd)
1173 1226 shouldupdate = repo['.'].rev() in updateifonnodes
1174 1227
1175 1228 # Update away from the rebase if necessary
1176 1229 if shouldupdate or needupdate(repo, state):
1177 1230 mergemod.update(repo, originalwd, False, True)
1178 1231
1179 1232 # Strip from the first rebased revision
1180 1233 if rebased:
1181 1234 # no backup of rebased cset versions needed
1182 1235 repair.strip(repo.ui, repo, strippoints)
1183 1236
1184 1237 if activebookmark and activebookmark in repo._bookmarks:
1185 1238 bookmarks.activate(repo, activebookmark)
1186 1239
1187 1240 finally:
1188 1241 clearstatus(repo)
1189 1242 clearcollapsemsg(repo)
1190 1243 repo.ui.warn(_('rebase aborted\n'))
1191 1244 return 0
1192 1245
1193 1246 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1194 1247 '''Define which revisions are going to be rebased and where
1195 1248
1196 1249 repo: repo
1197 1250 dest: context
1198 1251 rebaseset: set of rev
1199 1252 '''
1200 1253 originalwd = repo['.'].rev()
1201 1254 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
1202 1255
1203 1256 # This check isn't strictly necessary, since mq detects commits over an
1204 1257 # applied patch. But it prevents messing up the working directory when
1205 1258 # a partially completed rebase is blocked by mq.
1206 1259 if 'qtip' in repo.tags() and (dest.node() in
1207 1260 [s.node for s in repo.mq.applied]):
1208 1261 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1209 1262
1210 1263 roots = list(repo.set('roots(%ld)', rebaseset))
1211 1264 if not roots:
1212 1265 raise error.Abort(_('no matching revisions'))
1213 1266 roots.sort()
1214 1267 state = dict.fromkeys(rebaseset, revtodo)
1215 1268 detachset = set()
1216 1269 emptyrebase = True
1217 1270 for root in roots:
1218 1271 commonbase = root.ancestor(dest)
1219 1272 if commonbase == root:
1220 1273 raise error.Abort(_('source is ancestor of destination'))
1221 1274 if commonbase == dest:
1222 1275 wctx = repo[None]
1223 1276 if dest == wctx.p1():
1224 1277 # when rebasing to '.', it will use the current wd branch name
1225 1278 samebranch = root.branch() == wctx.branch()
1226 1279 else:
1227 1280 samebranch = root.branch() == dest.branch()
1228 1281 if not collapse and samebranch and dest in root.parents():
1229 1282 # mark the revision as done by setting its new revision
1230 1283 # equal to its old (current) revisions
1231 1284 state[root.rev()] = root.rev()
1232 1285 repo.ui.debug('source is a child of destination\n')
1233 1286 continue
1234 1287
1235 1288 emptyrebase = False
1236 1289 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1237 1290 # Rebase tries to turn <dest> into a parent of <root> while
1238 1291 # preserving the number of parents of rebased changesets:
1239 1292 #
1240 1293 # - A changeset with a single parent will always be rebased as a
1241 1294 # changeset with a single parent.
1242 1295 #
1243 1296 # - A merge will be rebased as merge unless its parents are both
1244 1297 # ancestors of <dest> or are themselves in the rebased set and
1245 1298 # pruned while rebased.
1246 1299 #
1247 1300 # If one parent of <root> is an ancestor of <dest>, the rebased
1248 1301 # version of this parent will be <dest>. This is always true with
1249 1302 # --base option.
1250 1303 #
1251 1304 # Otherwise, we need to *replace* the original parents with
1252 1305 # <dest>. This "detaches" the rebased set from its former location
1253 1306 # and rebases it onto <dest>. Changes introduced by ancestors of
1254 1307 # <root> not common with <dest> (the detachset, marked as
1255 1308 # nullmerge) are "removed" from the rebased changesets.
1256 1309 #
1257 1310 # - If <root> has a single parent, set it to <dest>.
1258 1311 #
1259 1312 # - If <root> is a merge, we cannot decide which parent to
1260 1313 # replace, the rebase operation is not clearly defined.
1261 1314 #
1262 1315 # The table below sums up this behavior:
1263 1316 #
1264 1317 # +------------------+----------------------+-------------------------+
1265 1318 # | | one parent | merge |
1266 1319 # +------------------+----------------------+-------------------------+
1267 1320 # | parent in | new parent is <dest> | parents in ::<dest> are |
1268 1321 # | ::<dest> | | remapped to <dest> |
1269 1322 # +------------------+----------------------+-------------------------+
1270 1323 # | unrelated source | new parent is <dest> | ambiguous, abort |
1271 1324 # +------------------+----------------------+-------------------------+
1272 1325 #
1273 1326 # The actual abort is handled by `defineparents`
1274 1327 if len(root.parents()) <= 1:
1275 1328 # ancestors of <root> not ancestors of <dest>
1276 1329 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1277 1330 [root.rev()]))
1278 1331 if emptyrebase:
1279 1332 return None
1280 1333 for rev in sorted(state):
1281 1334 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1282 1335 # if all parents of this revision are done, then so is this revision
1283 1336 if parents and all((state.get(p) == p for p in parents)):
1284 1337 state[rev] = rev
1285 1338 for r in detachset:
1286 1339 if r not in state:
1287 1340 state[r] = nullmerge
1288 1341 if len(roots) > 1:
1289 1342 # If we have multiple roots, we may have "hole" in the rebase set.
1290 1343 # Rebase roots that descend from those "hole" should not be detached as
1291 1344 # other root are. We use the special `revignored` to inform rebase that
1292 1345 # the revision should be ignored but that `defineparents` should search
1293 1346 # a rebase destination that make sense regarding rebased topology.
1294 1347 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1295 1348 for ignored in set(rebasedomain) - set(rebaseset):
1296 1349 state[ignored] = revignored
1297 1350 for r in obsoletenotrebased:
1298 1351 if obsoletenotrebased[r] is None:
1299 1352 state[r] = revpruned
1300 1353 else:
1301 1354 state[r] = revprecursor
1302 1355 return originalwd, dest.rev(), state
1303 1356
1304 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1357 def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
1305 1358 """dispose of rebased revision at the end of the rebase
1306 1359
1307 1360 If `collapsedas` is not None, the rebase was a collapse whose result if the
1308 1361 `collapsedas` node."""
1309 1362 tonode = repo.changelog.node
1363 # Move bookmark of skipped nodes to destination. This cannot be handled
1364 # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
1365 # and move bookmark backwards.
1366 bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state))))
1367 for rev in skipped
1368 for name in repo.nodebookmarks(tonode(rev))]
1369 if bmchanges:
1370 with repo.transaction('rebase') as tr:
1371 repo._bookmarks.applychanges(repo, tr, bmchanges)
1310 1372 mapping = {}
1311 1373 for rev, newrev in sorted(state.items()):
1312 1374 if newrev >= 0 and newrev != rev:
1313 1375 if rev in skipped:
1314 1376 succs = ()
1315 1377 elif collapsedas is not None:
1316 1378 succs = (collapsedas,)
1317 1379 else:
1318 1380 succs = (tonode(newrev),)
1319 1381 mapping[tonode(rev)] = succs
1320 1382 scmutil.cleanupnodes(repo, mapping, 'rebase')
1321 1383
1322 1384 def pullrebase(orig, ui, repo, *args, **opts):
1323 1385 'Call rebase after pull if the latter has been invoked with --rebase'
1324 1386 ret = None
1325 1387 if opts.get('rebase'):
1326 1388 if ui.configbool('commands', 'rebase.requiredest'):
1327 1389 msg = _('rebase destination required by configuration')
1328 1390 hint = _('use hg pull followed by hg rebase -d DEST')
1329 1391 raise error.Abort(msg, hint=hint)
1330 1392
1331 1393 with repo.wlock(), repo.lock():
1332 1394 if opts.get('update'):
1333 1395 del opts['update']
1334 1396 ui.debug('--update and --rebase are not compatible, ignoring '
1335 1397 'the update flag\n')
1336 1398
1337 1399 cmdutil.checkunfinished(repo)
1338 1400 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1339 1401 'please commit or shelve your changes first'))
1340 1402
1341 1403 revsprepull = len(repo)
1342 1404 origpostincoming = commands.postincoming
1343 1405 def _dummy(*args, **kwargs):
1344 1406 pass
1345 1407 commands.postincoming = _dummy
1346 1408 try:
1347 1409 ret = orig(ui, repo, *args, **opts)
1348 1410 finally:
1349 1411 commands.postincoming = origpostincoming
1350 1412 revspostpull = len(repo)
1351 1413 if revspostpull > revsprepull:
1352 1414 # --rev option from pull conflict with rebase own --rev
1353 1415 # dropping it
1354 1416 if 'rev' in opts:
1355 1417 del opts['rev']
1356 1418 # positional argument from pull conflicts with rebase's own
1357 1419 # --source.
1358 1420 if 'source' in opts:
1359 1421 del opts['source']
1360 1422 # revsprepull is the len of the repo, not revnum of tip.
1361 1423 destspace = list(repo.changelog.revs(start=revsprepull))
1362 1424 opts['_destspace'] = destspace
1363 1425 try:
1364 1426 rebase(ui, repo, **opts)
1365 1427 except error.NoMergeDestAbort:
1366 1428 # we can maybe update instead
1367 1429 rev, _a, _b = destutil.destupdate(repo)
1368 1430 if rev == repo['.'].rev():
1369 1431 ui.status(_('nothing to rebase\n'))
1370 1432 else:
1371 1433 ui.status(_('nothing to rebase - updating instead\n'))
1372 1434 # not passing argument to get the bare update behavior
1373 1435 # with warning and trumpets
1374 1436 commands.update(ui, repo)
1375 1437 else:
1376 1438 if opts.get('tool'):
1377 1439 raise error.Abort(_('--tool can only be used with --rebase'))
1378 1440 ret = orig(ui, repo, *args, **opts)
1379 1441
1380 1442 return ret
1381 1443
1382 1444 def _setrebasesetvisibility(repo, revs):
1383 1445 """store the currently rebased set on the repo object
1384 1446
1385 1447 This is used by another function to prevent rebased revision to because
1386 1448 hidden (see issue4504)"""
1387 1449 repo = repo.unfiltered()
1388 1450 repo._rebaseset = revs
1389 1451 # invalidate cache if visibility changes
1390 1452 hiddens = repo.filteredrevcache.get('visible', set())
1391 1453 if revs & hiddens:
1392 1454 repo.invalidatevolatilesets()
1393 1455
1394 1456 def _clearrebasesetvisibiliy(repo):
1395 1457 """remove rebaseset data from the repo"""
1396 1458 repo = repo.unfiltered()
1397 1459 if '_rebaseset' in vars(repo):
1398 1460 del repo._rebaseset
1399 1461
1400 1462 def _rebasedvisible(orig, repo):
1401 1463 """ensure rebased revs stay visible (see issue4504)"""
1402 1464 blockers = orig(repo)
1403 1465 blockers.update(getattr(repo, '_rebaseset', ()))
1404 1466 return blockers
1405 1467
1406 1468 def _filterobsoleterevs(repo, revs):
1407 1469 """returns a set of the obsolete revisions in revs"""
1408 1470 return set(r for r in revs if repo[r].obsolete())
1409 1471
1410 1472 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1411 1473 """return a mapping obsolete => successor for all obsolete nodes to be
1412 1474 rebased that have a successors in the destination
1413 1475
1414 1476 obsolete => None entries in the mapping indicate nodes with no successor"""
1415 1477 obsoletenotrebased = {}
1416 1478
1417 1479 # Build a mapping successor => obsolete nodes for the obsolete
1418 1480 # nodes to be rebased
1419 1481 allsuccessors = {}
1420 1482 cl = repo.changelog
1421 1483 for r in rebaseobsrevs:
1422 1484 node = cl.node(r)
1423 1485 for s in obsutil.allsuccessors(repo.obsstore, [node]):
1424 1486 try:
1425 1487 allsuccessors[cl.rev(s)] = cl.rev(node)
1426 1488 except LookupError:
1427 1489 pass
1428 1490
1429 1491 if allsuccessors:
1430 1492 # Look for successors of obsolete nodes to be rebased among
1431 1493 # the ancestors of dest
1432 1494 ancs = cl.ancestors([dest],
1433 1495 stoprev=min(allsuccessors),
1434 1496 inclusive=True)
1435 1497 for s in allsuccessors:
1436 1498 if s in ancs:
1437 1499 obsoletenotrebased[allsuccessors[s]] = s
1438 1500 elif (s == allsuccessors[s] and
1439 1501 allsuccessors.values().count(s) == 1):
1440 1502 # plain prune
1441 1503 obsoletenotrebased[s] = None
1442 1504
1443 1505 return obsoletenotrebased
1444 1506
1445 1507 def summaryhook(ui, repo):
1446 1508 if not repo.vfs.exists('rebasestate'):
1447 1509 return
1448 1510 try:
1449 1511 rbsrt = rebaseruntime(repo, ui, {})
1450 1512 rbsrt.restorestatus()
1451 1513 state = rbsrt.state
1452 1514 except error.RepoLookupError:
1453 1515 # i18n: column positioning for "hg summary"
1454 1516 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1455 1517 ui.write(msg)
1456 1518 return
1457 1519 numrebased = len([i for i in state.itervalues() if i >= 0])
1458 1520 # i18n: column positioning for "hg summary"
1459 1521 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1460 1522 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1461 1523 ui.label(_('%d remaining'), 'rebase.remaining') %
1462 1524 (len(state) - numrebased)))
1463 1525
1464 1526 def uisetup(ui):
1465 1527 #Replace pull with a decorator to provide --rebase option
1466 1528 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1467 1529 entry[1].append(('', 'rebase', None,
1468 1530 _("rebase working directory to branch head")))
1469 1531 entry[1].append(('t', 'tool', '',
1470 1532 _("specify merge tool for rebase")))
1471 1533 cmdutil.summaryhooks.add('rebase', summaryhook)
1472 1534 cmdutil.unfinishedstates.append(
1473 1535 ['rebasestate', False, False, _('rebase in progress'),
1474 1536 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1475 1537 cmdutil.afterresolvedstates.append(
1476 1538 ['rebasestate', _('hg rebase --continue')])
1477 1539 # ensure rebased rev are not hidden
1478 1540 extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
General Comments 0
You need to be logged in to leave comments. Login now