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