##// END OF EJS Templates
rebase: remove "state >= revtodo" condition...
Jun Wu -
r33847:bc9e0751 default
parent child Browse files
Show More
@@ -1,1531 +1,1529
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 and\
448 if rebased not in self.skipped:
449 self.state[rebased] >= revtodo:
450 commitmsg += '\n* %s' % repo[rebased].description()
449 commitmsg += '\n* %s' % repo[rebased].description()
451 editopt = True
450 editopt = True
452 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
451 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
453 revtoreuse = max(self.state)
452 revtoreuse = max(self.state)
454
453
455 dsguard = None
454 dsguard = None
456 if ui.configbool('rebase', 'singletransaction'):
455 if ui.configbool('rebase', 'singletransaction'):
457 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
456 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
458 with util.acceptintervention(dsguard):
457 with util.acceptintervention(dsguard):
459 newnode = concludenode(repo, revtoreuse, p1, self.external,
458 newnode = concludenode(repo, revtoreuse, p1, self.external,
460 commitmsg=commitmsg,
459 commitmsg=commitmsg,
461 extrafn=_makeextrafn(self.extrafns),
460 extrafn=_makeextrafn(self.extrafns),
462 editor=editor,
461 editor=editor,
463 keepbranches=self.keepbranchesf,
462 keepbranches=self.keepbranchesf,
464 date=self.date)
463 date=self.date)
465 if newnode is None:
464 if newnode is None:
466 newrev = self.dest
465 newrev = self.dest
467 else:
466 else:
468 newrev = repo[newnode].rev()
467 newrev = repo[newnode].rev()
469 for oldrev in self.state.iterkeys():
468 for oldrev in self.state.iterkeys():
470 if self.state[oldrev] >= revtodo:
471 self.state[oldrev] = newrev
469 self.state[oldrev] = newrev
472
470
473 if 'qtip' in repo.tags():
471 if 'qtip' in repo.tags():
474 updatemq(repo, self.state, self.skipped, **opts)
472 updatemq(repo, self.state, self.skipped, **opts)
475
473
476 # restore original working directory
474 # restore original working directory
477 # (we do this before stripping)
475 # (we do this before stripping)
478 newwd = self.state.get(self.originalwd, self.originalwd)
476 newwd = self.state.get(self.originalwd, self.originalwd)
479 if newwd < 0:
477 if newwd < 0:
480 # original directory is a parent of rebase set root or ignored
478 # original directory is a parent of rebase set root or ignored
481 newwd = self.originalwd
479 newwd = self.originalwd
482 if newwd not in [c.rev() for c in repo[None].parents()]:
480 if newwd not in [c.rev() for c in repo[None].parents()]:
483 ui.note(_("update back to initial working directory parent\n"))
481 ui.note(_("update back to initial working directory parent\n"))
484 hg.updaterepo(repo, newwd, False)
482 hg.updaterepo(repo, newwd, False)
485
483
486 if not self.keepf:
484 if not self.keepf:
487 collapsedas = None
485 collapsedas = None
488 if self.collapsef:
486 if self.collapsef:
489 collapsedas = newnode
487 collapsedas = newnode
490 clearrebased(ui, repo, self.dest, self.state, self.skipped,
488 clearrebased(ui, repo, self.dest, self.state, self.skipped,
491 collapsedas)
489 collapsedas)
492
490
493 clearstatus(repo)
491 clearstatus(repo)
494 clearcollapsemsg(repo)
492 clearcollapsemsg(repo)
495
493
496 ui.note(_("rebase completed\n"))
494 ui.note(_("rebase completed\n"))
497 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
495 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
498 if self.skipped:
496 if self.skipped:
499 skippedlen = len(self.skipped)
497 skippedlen = len(self.skipped)
500 ui.note(_("%d revisions have been skipped\n") % skippedlen)
498 ui.note(_("%d revisions have been skipped\n") % skippedlen)
501
499
502 if (self.activebookmark and self.activebookmark in repo._bookmarks and
500 if (self.activebookmark and self.activebookmark in repo._bookmarks and
503 repo['.'].node() == repo._bookmarks[self.activebookmark]):
501 repo['.'].node() == repo._bookmarks[self.activebookmark]):
504 bookmarks.activate(repo, self.activebookmark)
502 bookmarks.activate(repo, self.activebookmark)
505
503
506 @command('rebase',
504 @command('rebase',
507 [('s', 'source', '',
505 [('s', 'source', '',
508 _('rebase the specified changeset and descendants'), _('REV')),
506 _('rebase the specified changeset and descendants'), _('REV')),
509 ('b', 'base', '',
507 ('b', 'base', '',
510 _('rebase everything from branching point of specified changeset'),
508 _('rebase everything from branching point of specified changeset'),
511 _('REV')),
509 _('REV')),
512 ('r', 'rev', [],
510 ('r', 'rev', [],
513 _('rebase these revisions'),
511 _('rebase these revisions'),
514 _('REV')),
512 _('REV')),
515 ('d', 'dest', '',
513 ('d', 'dest', '',
516 _('rebase onto the specified changeset'), _('REV')),
514 _('rebase onto the specified changeset'), _('REV')),
517 ('', 'collapse', False, _('collapse the rebased changesets')),
515 ('', 'collapse', False, _('collapse the rebased changesets')),
518 ('m', 'message', '',
516 ('m', 'message', '',
519 _('use text as collapse commit message'), _('TEXT')),
517 _('use text as collapse commit message'), _('TEXT')),
520 ('e', 'edit', False, _('invoke editor on commit messages')),
518 ('e', 'edit', False, _('invoke editor on commit messages')),
521 ('l', 'logfile', '',
519 ('l', 'logfile', '',
522 _('read collapse commit message from file'), _('FILE')),
520 _('read collapse commit message from file'), _('FILE')),
523 ('k', 'keep', False, _('keep original changesets')),
521 ('k', 'keep', False, _('keep original changesets')),
524 ('', 'keepbranches', False, _('keep original branch names')),
522 ('', 'keepbranches', False, _('keep original branch names')),
525 ('D', 'detach', False, _('(DEPRECATED)')),
523 ('D', 'detach', False, _('(DEPRECATED)')),
526 ('i', 'interactive', False, _('(DEPRECATED)')),
524 ('i', 'interactive', False, _('(DEPRECATED)')),
527 ('t', 'tool', '', _('specify merge tool')),
525 ('t', 'tool', '', _('specify merge tool')),
528 ('c', 'continue', False, _('continue an interrupted rebase')),
526 ('c', 'continue', False, _('continue an interrupted rebase')),
529 ('a', 'abort', False, _('abort an interrupted rebase'))] +
527 ('a', 'abort', False, _('abort an interrupted rebase'))] +
530 templateopts,
528 templateopts,
531 _('[-s REV | -b REV] [-d REV] [OPTION]'))
529 _('[-s REV | -b REV] [-d REV] [OPTION]'))
532 def rebase(ui, repo, **opts):
530 def rebase(ui, repo, **opts):
533 """move changeset (and descendants) to a different branch
531 """move changeset (and descendants) to a different branch
534
532
535 Rebase uses repeated merging to graft changesets from one part of
533 Rebase uses repeated merging to graft changesets from one part of
536 history (the source) onto another (the destination). This can be
534 history (the source) onto another (the destination). This can be
537 useful for linearizing *local* changes relative to a master
535 useful for linearizing *local* changes relative to a master
538 development tree.
536 development tree.
539
537
540 Published commits cannot be rebased (see :hg:`help phases`).
538 Published commits cannot be rebased (see :hg:`help phases`).
541 To copy commits, see :hg:`help graft`.
539 To copy commits, see :hg:`help graft`.
542
540
543 If you don't specify a destination changeset (``-d/--dest``), rebase
541 If you don't specify a destination changeset (``-d/--dest``), rebase
544 will use the same logic as :hg:`merge` to pick a destination. if
542 will use the same logic as :hg:`merge` to pick a destination. if
545 the current branch contains exactly one other head, the other head
543 the current branch contains exactly one other head, the other head
546 is merged with by default. Otherwise, an explicit revision with
544 is merged with by default. Otherwise, an explicit revision with
547 which to merge with must be provided. (destination changeset is not
545 which to merge with must be provided. (destination changeset is not
548 modified by rebasing, but new changesets are added as its
546 modified by rebasing, but new changesets are added as its
549 descendants.)
547 descendants.)
550
548
551 Here are the ways to select changesets:
549 Here are the ways to select changesets:
552
550
553 1. Explicitly select them using ``--rev``.
551 1. Explicitly select them using ``--rev``.
554
552
555 2. Use ``--source`` to select a root changeset and include all of its
553 2. Use ``--source`` to select a root changeset and include all of its
556 descendants.
554 descendants.
557
555
558 3. Use ``--base`` to select a changeset; rebase will find ancestors
556 3. Use ``--base`` to select a changeset; rebase will find ancestors
559 and their descendants which are not also ancestors of the destination.
557 and their descendants which are not also ancestors of the destination.
560
558
561 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
559 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
562 rebase will use ``--base .`` as above.
560 rebase will use ``--base .`` as above.
563
561
564 Rebase will destroy original changesets unless you use ``--keep``.
562 Rebase will destroy original changesets unless you use ``--keep``.
565 It will also move your bookmarks (even if you do).
563 It will also move your bookmarks (even if you do).
566
564
567 Some changesets may be dropped if they do not contribute changes
565 Some changesets may be dropped if they do not contribute changes
568 (e.g. merges from the destination branch).
566 (e.g. merges from the destination branch).
569
567
570 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
568 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
571 a named branch with two heads. You will need to explicitly specify source
569 a named branch with two heads. You will need to explicitly specify source
572 and/or destination.
570 and/or destination.
573
571
574 If you need to use a tool to automate merge/conflict decisions, you
572 If you need to use a tool to automate merge/conflict decisions, you
575 can specify one with ``--tool``, see :hg:`help merge-tools`.
573 can specify one with ``--tool``, see :hg:`help merge-tools`.
576 As a caveat: the tool will not be used to mediate when a file was
574 As a caveat: the tool will not be used to mediate when a file was
577 deleted, there is no hook presently available for this.
575 deleted, there is no hook presently available for this.
578
576
579 If a rebase is interrupted to manually resolve a conflict, it can be
577 If a rebase is interrupted to manually resolve a conflict, it can be
580 continued with --continue/-c or aborted with --abort/-a.
578 continued with --continue/-c or aborted with --abort/-a.
581
579
582 .. container:: verbose
580 .. container:: verbose
583
581
584 Examples:
582 Examples:
585
583
586 - move "local changes" (current commit back to branching point)
584 - move "local changes" (current commit back to branching point)
587 to the current branch tip after a pull::
585 to the current branch tip after a pull::
588
586
589 hg rebase
587 hg rebase
590
588
591 - move a single changeset to the stable branch::
589 - move a single changeset to the stable branch::
592
590
593 hg rebase -r 5f493448 -d stable
591 hg rebase -r 5f493448 -d stable
594
592
595 - splice a commit and all its descendants onto another part of history::
593 - splice a commit and all its descendants onto another part of history::
596
594
597 hg rebase --source c0c3 --dest 4cf9
595 hg rebase --source c0c3 --dest 4cf9
598
596
599 - rebase everything on a branch marked by a bookmark onto the
597 - rebase everything on a branch marked by a bookmark onto the
600 default branch::
598 default branch::
601
599
602 hg rebase --base myfeature --dest default
600 hg rebase --base myfeature --dest default
603
601
604 - collapse a sequence of changes into a single commit::
602 - collapse a sequence of changes into a single commit::
605
603
606 hg rebase --collapse -r 1520:1525 -d .
604 hg rebase --collapse -r 1520:1525 -d .
607
605
608 - move a named branch while preserving its name::
606 - move a named branch while preserving its name::
609
607
610 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
608 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
611
609
612 Configuration Options:
610 Configuration Options:
613
611
614 You can make rebase require a destination if you set the following config
612 You can make rebase require a destination if you set the following config
615 option::
613 option::
616
614
617 [commands]
615 [commands]
618 rebase.requiredest = True
616 rebase.requiredest = True
619
617
620 By default, rebase will close the transaction after each commit. For
618 By default, rebase will close the transaction after each commit. For
621 performance purposes, you can configure rebase to use a single transaction
619 performance purposes, you can configure rebase to use a single transaction
622 across the entire rebase. WARNING: This setting introduces a significant
620 across the entire rebase. WARNING: This setting introduces a significant
623 risk of losing the work you've done in a rebase if the rebase aborts
621 risk of losing the work you've done in a rebase if the rebase aborts
624 unexpectedly::
622 unexpectedly::
625
623
626 [rebase]
624 [rebase]
627 singletransaction = True
625 singletransaction = True
628
626
629 Return Values:
627 Return Values:
630
628
631 Returns 0 on success, 1 if nothing to rebase or there are
629 Returns 0 on success, 1 if nothing to rebase or there are
632 unresolved conflicts.
630 unresolved conflicts.
633
631
634 """
632 """
635 rbsrt = rebaseruntime(repo, ui, opts)
633 rbsrt = rebaseruntime(repo, ui, opts)
636
634
637 with repo.wlock(), repo.lock():
635 with repo.wlock(), repo.lock():
638 # Validate input and define rebasing points
636 # Validate input and define rebasing points
639 destf = opts.get('dest', None)
637 destf = opts.get('dest', None)
640 srcf = opts.get('source', None)
638 srcf = opts.get('source', None)
641 basef = opts.get('base', None)
639 basef = opts.get('base', None)
642 revf = opts.get('rev', [])
640 revf = opts.get('rev', [])
643 # search default destination in this space
641 # search default destination in this space
644 # used in the 'hg pull --rebase' case, see issue 5214.
642 # used in the 'hg pull --rebase' case, see issue 5214.
645 destspace = opts.get('_destspace')
643 destspace = opts.get('_destspace')
646 contf = opts.get('continue')
644 contf = opts.get('continue')
647 abortf = opts.get('abort')
645 abortf = opts.get('abort')
648 if opts.get('interactive'):
646 if opts.get('interactive'):
649 try:
647 try:
650 if extensions.find('histedit'):
648 if extensions.find('histedit'):
651 enablehistedit = ''
649 enablehistedit = ''
652 except KeyError:
650 except KeyError:
653 enablehistedit = " --config extensions.histedit="
651 enablehistedit = " --config extensions.histedit="
654 help = "hg%s help -e histedit" % enablehistedit
652 help = "hg%s help -e histedit" % enablehistedit
655 msg = _("interactive history editing is supported by the "
653 msg = _("interactive history editing is supported by the "
656 "'histedit' extension (see \"%s\")") % help
654 "'histedit' extension (see \"%s\")") % help
657 raise error.Abort(msg)
655 raise error.Abort(msg)
658
656
659 if rbsrt.collapsemsg and not rbsrt.collapsef:
657 if rbsrt.collapsemsg and not rbsrt.collapsef:
660 raise error.Abort(
658 raise error.Abort(
661 _('message can only be specified with collapse'))
659 _('message can only be specified with collapse'))
662
660
663 if contf or abortf:
661 if contf or abortf:
664 if contf and abortf:
662 if contf and abortf:
665 raise error.Abort(_('cannot use both abort and continue'))
663 raise error.Abort(_('cannot use both abort and continue'))
666 if rbsrt.collapsef:
664 if rbsrt.collapsef:
667 raise error.Abort(
665 raise error.Abort(
668 _('cannot use collapse with continue or abort'))
666 _('cannot use collapse with continue or abort'))
669 if srcf or basef or destf:
667 if srcf or basef or destf:
670 raise error.Abort(
668 raise error.Abort(
671 _('abort and continue do not allow specifying revisions'))
669 _('abort and continue do not allow specifying revisions'))
672 if abortf and opts.get('tool', False):
670 if abortf and opts.get('tool', False):
673 ui.warn(_('tool option will be ignored\n'))
671 ui.warn(_('tool option will be ignored\n'))
674 if contf:
672 if contf:
675 ms = mergemod.mergestate.read(repo)
673 ms = mergemod.mergestate.read(repo)
676 mergeutil.checkunresolved(ms)
674 mergeutil.checkunresolved(ms)
677
675
678 retcode = rbsrt._prepareabortorcontinue(abortf)
676 retcode = rbsrt._prepareabortorcontinue(abortf)
679 if retcode is not None:
677 if retcode is not None:
680 return retcode
678 return retcode
681 else:
679 else:
682 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
680 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
683 destspace=destspace)
681 destspace=destspace)
684 retcode = rbsrt._preparenewrebase(dest, rebaseset)
682 retcode = rbsrt._preparenewrebase(dest, rebaseset)
685 if retcode is not None:
683 if retcode is not None:
686 return retcode
684 return retcode
687
685
688 tr = None
686 tr = None
689 dsguard = None
687 dsguard = None
690
688
691 singletr = ui.configbool('rebase', 'singletransaction')
689 singletr = ui.configbool('rebase', 'singletransaction')
692 if singletr:
690 if singletr:
693 tr = repo.transaction('rebase')
691 tr = repo.transaction('rebase')
694 with util.acceptintervention(tr):
692 with util.acceptintervention(tr):
695 if singletr:
693 if singletr:
696 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
694 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
697 with util.acceptintervention(dsguard):
695 with util.acceptintervention(dsguard):
698 rbsrt._performrebase(tr)
696 rbsrt._performrebase(tr)
699
697
700 rbsrt._finishrebase()
698 rbsrt._finishrebase()
701
699
702 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
700 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
703 destspace=None):
701 destspace=None):
704 """use revisions argument to define destination and rebase set
702 """use revisions argument to define destination and rebase set
705 """
703 """
706 if revf is None:
704 if revf is None:
707 revf = []
705 revf = []
708
706
709 # destspace is here to work around issues with `hg pull --rebase` see
707 # destspace is here to work around issues with `hg pull --rebase` see
710 # issue5214 for details
708 # issue5214 for details
711 if srcf and basef:
709 if srcf and basef:
712 raise error.Abort(_('cannot specify both a source and a base'))
710 raise error.Abort(_('cannot specify both a source and a base'))
713 if revf and basef:
711 if revf and basef:
714 raise error.Abort(_('cannot specify both a revision and a base'))
712 raise error.Abort(_('cannot specify both a revision and a base'))
715 if revf and srcf:
713 if revf and srcf:
716 raise error.Abort(_('cannot specify both a revision and a source'))
714 raise error.Abort(_('cannot specify both a revision and a source'))
717
715
718 cmdutil.checkunfinished(repo)
716 cmdutil.checkunfinished(repo)
719 cmdutil.bailifchanged(repo)
717 cmdutil.bailifchanged(repo)
720
718
721 if ui.configbool('commands', 'rebase.requiredest') and not destf:
719 if ui.configbool('commands', 'rebase.requiredest') and not destf:
722 raise error.Abort(_('you must specify a destination'),
720 raise error.Abort(_('you must specify a destination'),
723 hint=_('use: hg rebase -d REV'))
721 hint=_('use: hg rebase -d REV'))
724
722
725 if destf:
723 if destf:
726 dest = scmutil.revsingle(repo, destf)
724 dest = scmutil.revsingle(repo, destf)
727
725
728 if revf:
726 if revf:
729 rebaseset = scmutil.revrange(repo, revf)
727 rebaseset = scmutil.revrange(repo, revf)
730 if not rebaseset:
728 if not rebaseset:
731 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
729 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
732 return None, None
730 return None, None
733 elif srcf:
731 elif srcf:
734 src = scmutil.revrange(repo, [srcf])
732 src = scmutil.revrange(repo, [srcf])
735 if not src:
733 if not src:
736 ui.status(_('empty "source" revision set - nothing to rebase\n'))
734 ui.status(_('empty "source" revision set - nothing to rebase\n'))
737 return None, None
735 return None, None
738 rebaseset = repo.revs('(%ld)::', src)
736 rebaseset = repo.revs('(%ld)::', src)
739 assert rebaseset
737 assert rebaseset
740 else:
738 else:
741 base = scmutil.revrange(repo, [basef or '.'])
739 base = scmutil.revrange(repo, [basef or '.'])
742 if not base:
740 if not base:
743 ui.status(_('empty "base" revision set - '
741 ui.status(_('empty "base" revision set - '
744 "can't compute rebase set\n"))
742 "can't compute rebase set\n"))
745 return None, None
743 return None, None
746 if not destf:
744 if not destf:
747 dest = repo[_destrebase(repo, base, destspace=destspace)]
745 dest = repo[_destrebase(repo, base, destspace=destspace)]
748 destf = str(dest)
746 destf = str(dest)
749
747
750 roots = [] # selected children of branching points
748 roots = [] # selected children of branching points
751 bpbase = {} # {branchingpoint: [origbase]}
749 bpbase = {} # {branchingpoint: [origbase]}
752 for b in base: # group bases by branching points
750 for b in base: # group bases by branching points
753 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
751 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
754 bpbase[bp] = bpbase.get(bp, []) + [b]
752 bpbase[bp] = bpbase.get(bp, []) + [b]
755 if None in bpbase:
753 if None in bpbase:
756 # emulate the old behavior, showing "nothing to rebase" (a better
754 # emulate the old behavior, showing "nothing to rebase" (a better
757 # behavior may be abort with "cannot find branching point" error)
755 # behavior may be abort with "cannot find branching point" error)
758 bpbase.clear()
756 bpbase.clear()
759 for bp, bs in bpbase.iteritems(): # calculate roots
757 for bp, bs in bpbase.iteritems(): # calculate roots
760 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
758 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
761
759
762 rebaseset = repo.revs('%ld::', roots)
760 rebaseset = repo.revs('%ld::', roots)
763
761
764 if not rebaseset:
762 if not rebaseset:
765 # transform to list because smartsets are not comparable to
763 # transform to list because smartsets are not comparable to
766 # lists. This should be improved to honor laziness of
764 # lists. This should be improved to honor laziness of
767 # smartset.
765 # smartset.
768 if list(base) == [dest.rev()]:
766 if list(base) == [dest.rev()]:
769 if basef:
767 if basef:
770 ui.status(_('nothing to rebase - %s is both "base"'
768 ui.status(_('nothing to rebase - %s is both "base"'
771 ' and destination\n') % dest)
769 ' and destination\n') % dest)
772 else:
770 else:
773 ui.status(_('nothing to rebase - working directory '
771 ui.status(_('nothing to rebase - working directory '
774 'parent is also destination\n'))
772 'parent is also destination\n'))
775 elif not repo.revs('%ld - ::%d', base, dest):
773 elif not repo.revs('%ld - ::%d', base, dest):
776 if basef:
774 if basef:
777 ui.status(_('nothing to rebase - "base" %s is '
775 ui.status(_('nothing to rebase - "base" %s is '
778 'already an ancestor of destination '
776 'already an ancestor of destination '
779 '%s\n') %
777 '%s\n') %
780 ('+'.join(str(repo[r]) for r in base),
778 ('+'.join(str(repo[r]) for r in base),
781 dest))
779 dest))
782 else:
780 else:
783 ui.status(_('nothing to rebase - working '
781 ui.status(_('nothing to rebase - working '
784 'directory parent is already an '
782 'directory parent is already an '
785 'ancestor of destination %s\n') % dest)
783 'ancestor of destination %s\n') % dest)
786 else: # can it happen?
784 else: # can it happen?
787 ui.status(_('nothing to rebase from %s to %s\n') %
785 ui.status(_('nothing to rebase from %s to %s\n') %
788 ('+'.join(str(repo[r]) for r in base), dest))
786 ('+'.join(str(repo[r]) for r in base), dest))
789 return None, None
787 return None, None
790
788
791 if not destf:
789 if not destf:
792 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
790 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
793 destf = str(dest)
791 destf = str(dest)
794
792
795 return dest, rebaseset
793 return dest, rebaseset
796
794
797 def externalparent(repo, state, destancestors):
795 def externalparent(repo, state, destancestors):
798 """Return the revision that should be used as the second parent
796 """Return the revision that should be used as the second parent
799 when the revisions in state is collapsed on top of destancestors.
797 when the revisions in state is collapsed on top of destancestors.
800 Abort if there is more than one parent.
798 Abort if there is more than one parent.
801 """
799 """
802 parents = set()
800 parents = set()
803 source = min(state)
801 source = min(state)
804 for rev in state:
802 for rev in state:
805 if rev == source:
803 if rev == source:
806 continue
804 continue
807 for p in repo[rev].parents():
805 for p in repo[rev].parents():
808 if (p.rev() not in state
806 if (p.rev() not in state
809 and p.rev() not in destancestors):
807 and p.rev() not in destancestors):
810 parents.add(p.rev())
808 parents.add(p.rev())
811 if not parents:
809 if not parents:
812 return nullrev
810 return nullrev
813 if len(parents) == 1:
811 if len(parents) == 1:
814 return parents.pop()
812 return parents.pop()
815 raise error.Abort(_('unable to collapse on top of %s, there is more '
813 raise error.Abort(_('unable to collapse on top of %s, there is more '
816 'than one external parent: %s') %
814 'than one external parent: %s') %
817 (max(destancestors),
815 (max(destancestors),
818 ', '.join(str(p) for p in sorted(parents))))
816 ', '.join(str(p) for p in sorted(parents))))
819
817
820 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
818 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
821 keepbranches=False, date=None):
819 keepbranches=False, date=None):
822 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
820 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
823 but also store useful information in extra.
821 but also store useful information in extra.
824 Return node of committed revision.'''
822 Return node of committed revision.'''
825 dsguard = util.nullcontextmanager()
823 dsguard = util.nullcontextmanager()
826 if not repo.ui.configbool('rebase', 'singletransaction'):
824 if not repo.ui.configbool('rebase', 'singletransaction'):
827 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
825 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
828 with dsguard:
826 with dsguard:
829 repo.setparents(repo[p1].node(), repo[p2].node())
827 repo.setparents(repo[p1].node(), repo[p2].node())
830 ctx = repo[rev]
828 ctx = repo[rev]
831 if commitmsg is None:
829 if commitmsg is None:
832 commitmsg = ctx.description()
830 commitmsg = ctx.description()
833 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
831 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
834 extra = {'rebase_source': ctx.hex()}
832 extra = {'rebase_source': ctx.hex()}
835 if extrafn:
833 if extrafn:
836 extrafn(ctx, extra)
834 extrafn(ctx, extra)
837
835
838 destphase = max(ctx.phase(), phases.draft)
836 destphase = max(ctx.phase(), phases.draft)
839 overrides = {('phases', 'new-commit'): destphase}
837 overrides = {('phases', 'new-commit'): destphase}
840 with repo.ui.configoverride(overrides, 'rebase'):
838 with repo.ui.configoverride(overrides, 'rebase'):
841 if keepbranch:
839 if keepbranch:
842 repo.ui.setconfig('ui', 'allowemptycommit', True)
840 repo.ui.setconfig('ui', 'allowemptycommit', True)
843 # Commit might fail if unresolved files exist
841 # Commit might fail if unresolved files exist
844 if date is None:
842 if date is None:
845 date = ctx.date()
843 date = ctx.date()
846 newnode = repo.commit(text=commitmsg, user=ctx.user(),
844 newnode = repo.commit(text=commitmsg, user=ctx.user(),
847 date=date, extra=extra, editor=editor)
845 date=date, extra=extra, editor=editor)
848
846
849 repo.dirstate.setbranch(repo[newnode].branch())
847 repo.dirstate.setbranch(repo[newnode].branch())
850 return newnode
848 return newnode
851
849
852 def rebasenode(repo, rev, p1, base, state, collapse, dest):
850 def rebasenode(repo, rev, p1, base, state, collapse, dest):
853 'Rebase a single revision rev on top of p1 using base as merge ancestor'
851 'Rebase a single revision rev on top of p1 using base as merge ancestor'
854 # Merge phase
852 # Merge phase
855 # Update to destination and merge it with local
853 # Update to destination and merge it with local
856 if repo['.'].rev() != p1:
854 if repo['.'].rev() != p1:
857 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
855 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
858 mergemod.update(repo, p1, False, True)
856 mergemod.update(repo, p1, False, True)
859 else:
857 else:
860 repo.ui.debug(" already in destination\n")
858 repo.ui.debug(" already in destination\n")
861 repo.dirstate.write(repo.currenttransaction())
859 repo.dirstate.write(repo.currenttransaction())
862 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
860 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
863 if base is not None:
861 if base is not None:
864 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
862 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
865 # When collapsing in-place, the parent is the common ancestor, we
863 # When collapsing in-place, the parent is the common ancestor, we
866 # have to allow merging with it.
864 # have to allow merging with it.
867 stats = mergemod.update(repo, rev, True, True, base, collapse,
865 stats = mergemod.update(repo, rev, True, True, base, collapse,
868 labels=['dest', 'source'])
866 labels=['dest', 'source'])
869 if collapse:
867 if collapse:
870 copies.duplicatecopies(repo, rev, dest)
868 copies.duplicatecopies(repo, rev, dest)
871 else:
869 else:
872 # If we're not using --collapse, we need to
870 # If we're not using --collapse, we need to
873 # duplicate copies between the revision we're
871 # duplicate copies between the revision we're
874 # rebasing and its first parent, but *not*
872 # rebasing and its first parent, but *not*
875 # duplicate any copies that have already been
873 # duplicate any copies that have already been
876 # performed in the destination.
874 # performed in the destination.
877 p1rev = repo[rev].p1().rev()
875 p1rev = repo[rev].p1().rev()
878 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
876 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
879 return stats
877 return stats
880
878
881 def adjustdest(repo, rev, dest, state):
879 def adjustdest(repo, rev, dest, state):
882 """adjust rebase destination given the current rebase state
880 """adjust rebase destination given the current rebase state
883
881
884 rev is what is being rebased. Return a list of two revs, which are the
882 rev is what is being rebased. Return a list of two revs, which are the
885 adjusted destinations for rev's p1 and p2, respectively. If a parent is
883 adjusted destinations for rev's p1 and p2, respectively. If a parent is
886 nullrev, return dest without adjustment for it.
884 nullrev, return dest without adjustment for it.
887
885
888 For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
886 For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
889 and E's destination will be adjusted from F to B1.
887 and E's destination will be adjusted from F to B1.
890
888
891 B1 <- written during rebasing B
889 B1 <- written during rebasing B
892 |
890 |
893 F <- original destination of B, E
891 F <- original destination of B, E
894 |
892 |
895 | E <- rev, which is being rebased
893 | E <- rev, which is being rebased
896 | |
894 | |
897 | D <- prev, one parent of rev being checked
895 | D <- prev, one parent of rev being checked
898 | |
896 | |
899 | x <- skipped, ex. no successor or successor in (::dest)
897 | x <- skipped, ex. no successor or successor in (::dest)
900 | |
898 | |
901 | C
899 | C
902 | |
900 | |
903 | B <- rebased as B1
901 | B <- rebased as B1
904 |/
902 |/
905 A
903 A
906
904
907 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
905 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
908 first move C to C1, G to G1, and when it's checking H, the adjusted
906 first move C to C1, G to G1, and when it's checking H, the adjusted
909 destinations will be [C1, G1].
907 destinations will be [C1, G1].
910
908
911 H C1 G1
909 H C1 G1
912 /| | /
910 /| | /
913 F G |/
911 F G |/
914 K | | -> K
912 K | | -> K
915 | C D |
913 | C D |
916 | |/ |
914 | |/ |
917 | B | ...
915 | B | ...
918 |/ |/
916 |/ |/
919 A A
917 A A
920 """
918 """
921 result = []
919 result = []
922 for prev in repo.changelog.parentrevs(rev):
920 for prev in repo.changelog.parentrevs(rev):
923 adjusted = dest
921 adjusted = dest
924 if prev != nullrev:
922 if prev != nullrev:
925 # pick already rebased revs from state
923 # pick already rebased revs from state
926 source = [s for s, d in state.items() if d > 0]
924 source = [s for s, d in state.items() if d > 0]
927 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
925 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
928 if candidate is not None:
926 if candidate is not None:
929 adjusted = state[candidate]
927 adjusted = state[candidate]
930 result.append(adjusted)
928 result.append(adjusted)
931 return result
929 return result
932
930
933 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
931 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
934 """
932 """
935 Abort if rebase will create divergence or rebase is noop because of markers
933 Abort if rebase will create divergence or rebase is noop because of markers
936
934
937 `rebaseobsrevs`: set of obsolete revision in source
935 `rebaseobsrevs`: set of obsolete revision in source
938 `rebaseobsskipped`: set of revisions from source skipped because they have
936 `rebaseobsskipped`: set of revisions from source skipped because they have
939 successors in destination
937 successors in destination
940 """
938 """
941 # Obsolete node with successors not in dest leads to divergence
939 # Obsolete node with successors not in dest leads to divergence
942 divergenceok = ui.configbool('experimental',
940 divergenceok = ui.configbool('experimental',
943 'allowdivergence')
941 'allowdivergence')
944 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
942 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
945
943
946 if divergencebasecandidates and not divergenceok:
944 if divergencebasecandidates and not divergenceok:
947 divhashes = (str(repo[r])
945 divhashes = (str(repo[r])
948 for r in divergencebasecandidates)
946 for r in divergencebasecandidates)
949 msg = _("this rebase will cause "
947 msg = _("this rebase will cause "
950 "divergences from: %s")
948 "divergences from: %s")
951 h = _("to force the rebase please set "
949 h = _("to force the rebase please set "
952 "experimental.allowdivergence=True")
950 "experimental.allowdivergence=True")
953 raise error.Abort(msg % (",".join(divhashes),), hint=h)
951 raise error.Abort(msg % (",".join(divhashes),), hint=h)
954
952
955 def successorrevs(repo, rev):
953 def successorrevs(repo, rev):
956 """yield revision numbers for successors of rev"""
954 """yield revision numbers for successors of rev"""
957 unfi = repo.unfiltered()
955 unfi = repo.unfiltered()
958 nodemap = unfi.changelog.nodemap
956 nodemap = unfi.changelog.nodemap
959 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
957 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
960 if s in nodemap:
958 if s in nodemap:
961 yield nodemap[s]
959 yield nodemap[s]
962
960
963 def defineparents(repo, rev, dest, state):
961 def defineparents(repo, rev, dest, state):
964 """Return new parents and optionally a merge base for rev being rebased
962 """Return new parents and optionally a merge base for rev being rebased
965
963
966 The destination specified by "dest" cannot always be used directly because
964 The destination specified by "dest" cannot always be used directly because
967 previously rebase result could affect destination. For example,
965 previously rebase result could affect destination. For example,
968
966
969 D E rebase -r C+D+E -d B
967 D E rebase -r C+D+E -d B
970 |/ C will be rebased to C'
968 |/ C will be rebased to C'
971 B C D's new destination will be C' instead of B
969 B C D's new destination will be C' instead of B
972 |/ E's new destination will be C' instead of B
970 |/ E's new destination will be C' instead of B
973 A
971 A
974
972
975 The new parents of a merge is slightly more complicated. See the comment
973 The new parents of a merge is slightly more complicated. See the comment
976 block below.
974 block below.
977 """
975 """
978 cl = repo.changelog
976 cl = repo.changelog
979 def isancestor(a, b):
977 def isancestor(a, b):
980 # take revision numbers instead of nodes
978 # take revision numbers instead of nodes
981 if a == b:
979 if a == b:
982 return True
980 return True
983 elif a > b:
981 elif a > b:
984 return False
982 return False
985 return cl.isancestor(cl.node(a), cl.node(b))
983 return cl.isancestor(cl.node(a), cl.node(b))
986
984
987 oldps = repo.changelog.parentrevs(rev) # old parents
985 oldps = repo.changelog.parentrevs(rev) # old parents
988 newps = [nullrev, nullrev] # new parents
986 newps = [nullrev, nullrev] # new parents
989 dests = adjustdest(repo, rev, dest, state) # adjusted destinations
987 dests = adjustdest(repo, rev, dest, state) # adjusted destinations
990 bases = list(oldps) # merge base candidates, initially just old parents
988 bases = list(oldps) # merge base candidates, initially just old parents
991
989
992 if all(r == nullrev for r in oldps[1:]):
990 if all(r == nullrev for r in oldps[1:]):
993 # For non-merge changeset, just move p to adjusted dest as requested.
991 # For non-merge changeset, just move p to adjusted dest as requested.
994 newps[0] = dests[0]
992 newps[0] = dests[0]
995 else:
993 else:
996 # For merge changeset, if we move p to dests[i] unconditionally, both
994 # For merge changeset, if we move p to dests[i] unconditionally, both
997 # parents may change and the end result looks like "the merge loses a
995 # parents may change and the end result looks like "the merge loses a
998 # parent", which is a surprise. This is a limit because "--dest" only
996 # parent", which is a surprise. This is a limit because "--dest" only
999 # accepts one dest per src.
997 # accepts one dest per src.
1000 #
998 #
1001 # Therefore, only move p with reasonable conditions (in this order):
999 # Therefore, only move p with reasonable conditions (in this order):
1002 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1000 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1003 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1001 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1004 #
1002 #
1005 # Comparing with adjustdest, the logic here does some additional work:
1003 # Comparing with adjustdest, the logic here does some additional work:
1006 # 1. decide which parents will not be moved towards dest
1004 # 1. decide which parents will not be moved towards dest
1007 # 2. if the above decision is "no", should a parent still be moved
1005 # 2. if the above decision is "no", should a parent still be moved
1008 # because it was rebased?
1006 # because it was rebased?
1009 #
1007 #
1010 # For example:
1008 # For example:
1011 #
1009 #
1012 # C # "rebase -r C -d D" is an error since none of the parents
1010 # C # "rebase -r C -d D" is an error since none of the parents
1013 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1011 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1014 # A B D # B (using rule "2."), since B will be rebased.
1012 # A B D # B (using rule "2."), since B will be rebased.
1015 #
1013 #
1016 # The loop tries to be not rely on the fact that a Mercurial node has
1014 # The loop tries to be not rely on the fact that a Mercurial node has
1017 # at most 2 parents.
1015 # at most 2 parents.
1018 for i, p in enumerate(oldps):
1016 for i, p in enumerate(oldps):
1019 np = p # new parent
1017 np = p # new parent
1020 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1018 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1021 np = dests[i]
1019 np = dests[i]
1022 elif p in state and state[p] > 0:
1020 elif p in state and state[p] > 0:
1023 np = state[p]
1021 np = state[p]
1024
1022
1025 # "bases" only record "special" merge bases that cannot be
1023 # "bases" only record "special" merge bases that cannot be
1026 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1024 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1027 # For example:
1025 # For example:
1028 #
1026 #
1029 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1027 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1030 # | C # is B', but merge base for C is B, instead of
1028 # | C # is B', but merge base for C is B, instead of
1031 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1029 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1032 # | B # "state" edges are merged (so there will be an edge from
1030 # | B # "state" edges are merged (so there will be an edge from
1033 # |/ # B to B'), the merge base is still ancestor(C, B') in
1031 # |/ # B to B'), the merge base is still ancestor(C, B') in
1034 # A # the merged graph.
1032 # A # the merged graph.
1035 #
1033 #
1036 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1034 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1037 # which uses "virtual null merge" to explain this situation.
1035 # which uses "virtual null merge" to explain this situation.
1038 if isancestor(p, np):
1036 if isancestor(p, np):
1039 bases[i] = nullrev
1037 bases[i] = nullrev
1040
1038
1041 # If one parent becomes an ancestor of the other, drop the ancestor
1039 # If one parent becomes an ancestor of the other, drop the ancestor
1042 for j, x in enumerate(newps[:i]):
1040 for j, x in enumerate(newps[:i]):
1043 if x == nullrev:
1041 if x == nullrev:
1044 continue
1042 continue
1045 if isancestor(np, x):
1043 if isancestor(np, x):
1046 np = nullrev
1044 np = nullrev
1047 elif isancestor(x, np):
1045 elif isancestor(x, np):
1048 newps[j] = np
1046 newps[j] = np
1049 np = nullrev
1047 np = nullrev
1050 bases[j], bases[i] = bases[i], bases[j]
1048 bases[j], bases[i] = bases[i], bases[j]
1051
1049
1052 newps[i] = np
1050 newps[i] = np
1053
1051
1054 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1052 # "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
1053 # base. If only p2 changes, merging using unchanged p1 as merge base is
1056 # suboptimal. Therefore swap parents to make the merge sane.
1054 # suboptimal. Therefore swap parents to make the merge sane.
1057 if newps[1] != nullrev and oldps[0] == newps[0]:
1055 if newps[1] != nullrev and oldps[0] == newps[0]:
1058 assert len(newps) == 2 and len(oldps) == 2
1056 assert len(newps) == 2 and len(oldps) == 2
1059 newps.reverse()
1057 newps.reverse()
1060 bases.reverse()
1058 bases.reverse()
1061
1059
1062 # No parent change might be an error because we fail to make rev a
1060 # No parent change might be an error because we fail to make rev a
1063 # descendent of requested dest. This can happen, for example:
1061 # descendent of requested dest. This can happen, for example:
1064 #
1062 #
1065 # C # rebase -r C -d D
1063 # C # rebase -r C -d D
1066 # /| # None of A and B will be changed to D and rebase fails.
1064 # /| # None of A and B will be changed to D and rebase fails.
1067 # A B D
1065 # A B D
1068 if set(newps) == set(oldps) and dest not in newps:
1066 if set(newps) == set(oldps) and dest not in newps:
1069 raise error.Abort(_('cannot rebase %d:%s without '
1067 raise error.Abort(_('cannot rebase %d:%s without '
1070 'moving at least one of its parents')
1068 'moving at least one of its parents')
1071 % (rev, repo[rev]))
1069 % (rev, repo[rev]))
1072
1070
1073 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1071 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1074
1072
1075 # "rebasenode" updates to new p1, use the corresponding merge base.
1073 # "rebasenode" updates to new p1, use the corresponding merge base.
1076 if bases[0] != nullrev:
1074 if bases[0] != nullrev:
1077 base = bases[0]
1075 base = bases[0]
1078 else:
1076 else:
1079 base = None
1077 base = None
1080
1078
1081 # 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
1082 # there are multiple special (non-changelog ancestor) merge bases, which
1080 # there are multiple special (non-changelog ancestor) merge bases, which
1083 # 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:
1084 #
1082 #
1085 # F
1083 # F
1086 # /|
1084 # /|
1087 # 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
1088 # | | # as merge base, the difference between D and F will include
1086 # | | # as merge base, the difference between D and F will include
1089 # 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
1090 # |/ # chosen, the rebased F will contain B.
1088 # |/ # chosen, the rebased F will contain B.
1091 # A Z
1089 # A Z
1092 #
1090 #
1093 # 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
1094 # better than the default (ancestor(F, Z) == null). Therefore still
1092 # better than the default (ancestor(F, Z) == null). Therefore still
1095 # pick one (so choose p1 above).
1093 # pick one (so choose p1 above).
1096 if sum(1 for b in bases if b != nullrev) > 1:
1094 if sum(1 for b in bases if b != nullrev) > 1:
1097 assert base is not None
1095 assert base is not None
1098
1096
1099 # Revisions in the side (not chosen as merge base) branch that might
1097 # Revisions in the side (not chosen as merge base) branch that might
1100 # contain "surprising" contents
1098 # contain "surprising" contents
1101 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1099 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1102 bases, base, base, dest))
1100 bases, base, base, dest))
1103
1101
1104 # If those revisions are covered by rebaseset, the result is good.
1102 # If those revisions are covered by rebaseset, the result is good.
1105 # A merge in rebaseset would be considered to cover its ancestors.
1103 # A merge in rebaseset would be considered to cover its ancestors.
1106 if siderevs:
1104 if siderevs:
1107 rebaseset = [r for r, d in state.items() if d > 0]
1105 rebaseset = [r for r, d in state.items() if d > 0]
1108 merges = [r for r in rebaseset if cl.parentrevs(r)[1] != nullrev]
1106 merges = [r for r in rebaseset if cl.parentrevs(r)[1] != nullrev]
1109 unwantedrevs = list(repo.revs('%ld - (::%ld) - %ld',
1107 unwantedrevs = list(repo.revs('%ld - (::%ld) - %ld',
1110 siderevs, merges, rebaseset))
1108 siderevs, merges, rebaseset))
1111
1109
1112 # For revs not covered, it is worth a warning.
1110 # For revs not covered, it is worth a warning.
1113 if unwantedrevs:
1111 if unwantedrevs:
1114 repo.ui.warn(
1112 repo.ui.warn(
1115 _('warning: rebasing %d:%s may include unwanted changes '
1113 _('warning: rebasing %d:%s may include unwanted changes '
1116 'from %s\n')
1114 'from %s\n')
1117 % (rev, repo[rev], ', '.join('%d:%s' % (r, repo[r])
1115 % (rev, repo[rev], ', '.join('%d:%s' % (r, repo[r])
1118 for r in unwantedrevs)))
1116 for r in unwantedrevs)))
1119
1117
1120 return newps[0], newps[1], base
1118 return newps[0], newps[1], base
1121
1119
1122 def isagitpatch(repo, patchname):
1120 def isagitpatch(repo, patchname):
1123 'Return true if the given patch is in git format'
1121 'Return true if the given patch is in git format'
1124 mqpatch = os.path.join(repo.mq.path, patchname)
1122 mqpatch = os.path.join(repo.mq.path, patchname)
1125 for line in patch.linereader(file(mqpatch, 'rb')):
1123 for line in patch.linereader(file(mqpatch, 'rb')):
1126 if line.startswith('diff --git'):
1124 if line.startswith('diff --git'):
1127 return True
1125 return True
1128 return False
1126 return False
1129
1127
1130 def updatemq(repo, state, skipped, **opts):
1128 def updatemq(repo, state, skipped, **opts):
1131 'Update rebased mq patches - finalize and then import them'
1129 'Update rebased mq patches - finalize and then import them'
1132 mqrebase = {}
1130 mqrebase = {}
1133 mq = repo.mq
1131 mq = repo.mq
1134 original_series = mq.fullseries[:]
1132 original_series = mq.fullseries[:]
1135 skippedpatches = set()
1133 skippedpatches = set()
1136
1134
1137 for p in mq.applied:
1135 for p in mq.applied:
1138 rev = repo[p.node].rev()
1136 rev = repo[p.node].rev()
1139 if rev in state:
1137 if rev in state:
1140 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1138 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1141 (rev, p.name))
1139 (rev, p.name))
1142 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1140 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1143 else:
1141 else:
1144 # Applied but not rebased, not sure this should happen
1142 # Applied but not rebased, not sure this should happen
1145 skippedpatches.add(p.name)
1143 skippedpatches.add(p.name)
1146
1144
1147 if mqrebase:
1145 if mqrebase:
1148 mq.finish(repo, mqrebase.keys())
1146 mq.finish(repo, mqrebase.keys())
1149
1147
1150 # We must start import from the newest revision
1148 # We must start import from the newest revision
1151 for rev in sorted(mqrebase, reverse=True):
1149 for rev in sorted(mqrebase, reverse=True):
1152 if rev not in skipped:
1150 if rev not in skipped:
1153 name, isgit = mqrebase[rev]
1151 name, isgit = mqrebase[rev]
1154 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1152 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1155 (name, state[rev], repo[state[rev]]))
1153 (name, state[rev], repo[state[rev]]))
1156 mq.qimport(repo, (), patchname=name, git=isgit,
1154 mq.qimport(repo, (), patchname=name, git=isgit,
1157 rev=[str(state[rev])])
1155 rev=[str(state[rev])])
1158 else:
1156 else:
1159 # Rebased and skipped
1157 # Rebased and skipped
1160 skippedpatches.add(mqrebase[rev][0])
1158 skippedpatches.add(mqrebase[rev][0])
1161
1159
1162 # Patches were either applied and rebased and imported in
1160 # Patches were either applied and rebased and imported in
1163 # order, applied and removed or unapplied. Discard the removed
1161 # order, applied and removed or unapplied. Discard the removed
1164 # ones while preserving the original series order and guards.
1162 # ones while preserving the original series order and guards.
1165 newseries = [s for s in original_series
1163 newseries = [s for s in original_series
1166 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1164 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1167 mq.fullseries[:] = newseries
1165 mq.fullseries[:] = newseries
1168 mq.seriesdirty = True
1166 mq.seriesdirty = True
1169 mq.savedirty()
1167 mq.savedirty()
1170
1168
1171 def storecollapsemsg(repo, collapsemsg):
1169 def storecollapsemsg(repo, collapsemsg):
1172 'Store the collapse message to allow recovery'
1170 'Store the collapse message to allow recovery'
1173 collapsemsg = collapsemsg or ''
1171 collapsemsg = collapsemsg or ''
1174 f = repo.vfs("last-message.txt", "w")
1172 f = repo.vfs("last-message.txt", "w")
1175 f.write("%s\n" % collapsemsg)
1173 f.write("%s\n" % collapsemsg)
1176 f.close()
1174 f.close()
1177
1175
1178 def clearcollapsemsg(repo):
1176 def clearcollapsemsg(repo):
1179 'Remove collapse message file'
1177 'Remove collapse message file'
1180 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1178 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1181
1179
1182 def restorecollapsemsg(repo, isabort):
1180 def restorecollapsemsg(repo, isabort):
1183 'Restore previously stored collapse message'
1181 'Restore previously stored collapse message'
1184 try:
1182 try:
1185 f = repo.vfs("last-message.txt")
1183 f = repo.vfs("last-message.txt")
1186 collapsemsg = f.readline().strip()
1184 collapsemsg = f.readline().strip()
1187 f.close()
1185 f.close()
1188 except IOError as err:
1186 except IOError as err:
1189 if err.errno != errno.ENOENT:
1187 if err.errno != errno.ENOENT:
1190 raise
1188 raise
1191 if isabort:
1189 if isabort:
1192 # Oh well, just abort like normal
1190 # Oh well, just abort like normal
1193 collapsemsg = ''
1191 collapsemsg = ''
1194 else:
1192 else:
1195 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1193 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1196 return collapsemsg
1194 return collapsemsg
1197
1195
1198 def clearstatus(repo):
1196 def clearstatus(repo):
1199 'Remove the status files'
1197 'Remove the status files'
1200 _clearrebasesetvisibiliy(repo)
1198 _clearrebasesetvisibiliy(repo)
1201 # Make sure the active transaction won't write the state file
1199 # Make sure the active transaction won't write the state file
1202 tr = repo.currenttransaction()
1200 tr = repo.currenttransaction()
1203 if tr:
1201 if tr:
1204 tr.removefilegenerator('rebasestate')
1202 tr.removefilegenerator('rebasestate')
1205 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1203 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1206
1204
1207 def needupdate(repo, state):
1205 def needupdate(repo, state):
1208 '''check whether we should `update --clean` away from a merge, or if
1206 '''check whether we should `update --clean` away from a merge, or if
1209 somehow the working dir got forcibly updated, e.g. by older hg'''
1207 somehow the working dir got forcibly updated, e.g. by older hg'''
1210 parents = [p.rev() for p in repo[None].parents()]
1208 parents = [p.rev() for p in repo[None].parents()]
1211
1209
1212 # Are we in a merge state at all?
1210 # Are we in a merge state at all?
1213 if len(parents) < 2:
1211 if len(parents) < 2:
1214 return False
1212 return False
1215
1213
1216 # We should be standing on the first as-of-yet unrebased commit.
1214 # We should be standing on the first as-of-yet unrebased commit.
1217 firstunrebased = min([old for old, new in state.iteritems()
1215 firstunrebased = min([old for old, new in state.iteritems()
1218 if new == nullrev])
1216 if new == nullrev])
1219 if firstunrebased in parents:
1217 if firstunrebased in parents:
1220 return True
1218 return True
1221
1219
1222 return False
1220 return False
1223
1221
1224 def abort(repo, originalwd, dest, state, activebookmark=None):
1222 def abort(repo, originalwd, dest, state, activebookmark=None):
1225 '''Restore the repository to its original state. Additional args:
1223 '''Restore the repository to its original state. Additional args:
1226
1224
1227 activebookmark: the name of the bookmark that should be active after the
1225 activebookmark: the name of the bookmark that should be active after the
1228 restore'''
1226 restore'''
1229
1227
1230 try:
1228 try:
1231 # If the first commits in the rebased set get skipped during the rebase,
1229 # If the first commits in the rebased set get skipped during the rebase,
1232 # their values within the state mapping will be the dest rev id. The
1230 # their values within the state mapping will be the dest rev id. The
1233 # dstates list must must not contain the dest rev (issue4896)
1231 # dstates list must must not contain the dest rev (issue4896)
1234 dstates = [s for s in state.values() if s >= 0 and s != dest]
1232 dstates = [s for s in state.values() if s >= 0 and s != dest]
1235 immutable = [d for d in dstates if not repo[d].mutable()]
1233 immutable = [d for d in dstates if not repo[d].mutable()]
1236 cleanup = True
1234 cleanup = True
1237 if immutable:
1235 if immutable:
1238 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1236 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1239 % ', '.join(str(repo[r]) for r in immutable),
1237 % ', '.join(str(repo[r]) for r in immutable),
1240 hint=_("see 'hg help phases' for details"))
1238 hint=_("see 'hg help phases' for details"))
1241 cleanup = False
1239 cleanup = False
1242
1240
1243 descendants = set()
1241 descendants = set()
1244 if dstates:
1242 if dstates:
1245 descendants = set(repo.changelog.descendants(dstates))
1243 descendants = set(repo.changelog.descendants(dstates))
1246 if descendants - set(dstates):
1244 if descendants - set(dstates):
1247 repo.ui.warn(_("warning: new changesets detected on destination "
1245 repo.ui.warn(_("warning: new changesets detected on destination "
1248 "branch, can't strip\n"))
1246 "branch, can't strip\n"))
1249 cleanup = False
1247 cleanup = False
1250
1248
1251 if cleanup:
1249 if cleanup:
1252 shouldupdate = False
1250 shouldupdate = False
1253 rebased = filter(lambda x: x >= 0 and x != dest, state.values())
1251 rebased = filter(lambda x: x >= 0 and x != dest, state.values())
1254 if rebased:
1252 if rebased:
1255 strippoints = [
1253 strippoints = [
1256 c.node() for c in repo.set('roots(%ld)', rebased)]
1254 c.node() for c in repo.set('roots(%ld)', rebased)]
1257
1255
1258 updateifonnodes = set(rebased)
1256 updateifonnodes = set(rebased)
1259 updateifonnodes.add(dest)
1257 updateifonnodes.add(dest)
1260 updateifonnodes.add(originalwd)
1258 updateifonnodes.add(originalwd)
1261 shouldupdate = repo['.'].rev() in updateifonnodes
1259 shouldupdate = repo['.'].rev() in updateifonnodes
1262
1260
1263 # Update away from the rebase if necessary
1261 # Update away from the rebase if necessary
1264 if shouldupdate or needupdate(repo, state):
1262 if shouldupdate or needupdate(repo, state):
1265 mergemod.update(repo, originalwd, False, True)
1263 mergemod.update(repo, originalwd, False, True)
1266
1264
1267 # Strip from the first rebased revision
1265 # Strip from the first rebased revision
1268 if rebased:
1266 if rebased:
1269 # no backup of rebased cset versions needed
1267 # no backup of rebased cset versions needed
1270 repair.strip(repo.ui, repo, strippoints)
1268 repair.strip(repo.ui, repo, strippoints)
1271
1269
1272 if activebookmark and activebookmark in repo._bookmarks:
1270 if activebookmark and activebookmark in repo._bookmarks:
1273 bookmarks.activate(repo, activebookmark)
1271 bookmarks.activate(repo, activebookmark)
1274
1272
1275 finally:
1273 finally:
1276 clearstatus(repo)
1274 clearstatus(repo)
1277 clearcollapsemsg(repo)
1275 clearcollapsemsg(repo)
1278 repo.ui.warn(_('rebase aborted\n'))
1276 repo.ui.warn(_('rebase aborted\n'))
1279 return 0
1277 return 0
1280
1278
1281 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1279 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1282 '''Define which revisions are going to be rebased and where
1280 '''Define which revisions are going to be rebased and where
1283
1281
1284 repo: repo
1282 repo: repo
1285 dest: context
1283 dest: context
1286 rebaseset: set of rev
1284 rebaseset: set of rev
1287 '''
1285 '''
1288 originalwd = repo['.'].rev()
1286 originalwd = repo['.'].rev()
1289 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
1287 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
1290
1288
1291 # This check isn't strictly necessary, since mq detects commits over an
1289 # This check isn't strictly necessary, since mq detects commits over an
1292 # applied patch. But it prevents messing up the working directory when
1290 # applied patch. But it prevents messing up the working directory when
1293 # a partially completed rebase is blocked by mq.
1291 # a partially completed rebase is blocked by mq.
1294 if 'qtip' in repo.tags() and (dest.node() in
1292 if 'qtip' in repo.tags() and (dest.node() in
1295 [s.node for s in repo.mq.applied]):
1293 [s.node for s in repo.mq.applied]):
1296 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1294 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1297
1295
1298 roots = list(repo.set('roots(%ld)', rebaseset))
1296 roots = list(repo.set('roots(%ld)', rebaseset))
1299 if not roots:
1297 if not roots:
1300 raise error.Abort(_('no matching revisions'))
1298 raise error.Abort(_('no matching revisions'))
1301 roots.sort()
1299 roots.sort()
1302 state = dict.fromkeys(rebaseset, revtodo)
1300 state = dict.fromkeys(rebaseset, revtodo)
1303 emptyrebase = True
1301 emptyrebase = True
1304 for root in roots:
1302 for root in roots:
1305 commonbase = root.ancestor(dest)
1303 commonbase = root.ancestor(dest)
1306 if commonbase == root:
1304 if commonbase == root:
1307 raise error.Abort(_('source is ancestor of destination'))
1305 raise error.Abort(_('source is ancestor of destination'))
1308 if commonbase == dest:
1306 if commonbase == dest:
1309 wctx = repo[None]
1307 wctx = repo[None]
1310 if dest == wctx.p1():
1308 if dest == wctx.p1():
1311 # when rebasing to '.', it will use the current wd branch name
1309 # when rebasing to '.', it will use the current wd branch name
1312 samebranch = root.branch() == wctx.branch()
1310 samebranch = root.branch() == wctx.branch()
1313 else:
1311 else:
1314 samebranch = root.branch() == dest.branch()
1312 samebranch = root.branch() == dest.branch()
1315 if not collapse and samebranch and dest in root.parents():
1313 if not collapse and samebranch and dest in root.parents():
1316 # mark the revision as done by setting its new revision
1314 # mark the revision as done by setting its new revision
1317 # equal to its old (current) revisions
1315 # equal to its old (current) revisions
1318 state[root.rev()] = root.rev()
1316 state[root.rev()] = root.rev()
1319 repo.ui.debug('source is a child of destination\n')
1317 repo.ui.debug('source is a child of destination\n')
1320 continue
1318 continue
1321
1319
1322 emptyrebase = False
1320 emptyrebase = False
1323 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1321 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1324 if emptyrebase:
1322 if emptyrebase:
1325 return None
1323 return None
1326 for rev in sorted(state):
1324 for rev in sorted(state):
1327 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1325 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1328 # if all parents of this revision are done, then so is this revision
1326 # if all parents of this revision are done, then so is this revision
1329 if parents and all((state.get(p) == p for p in parents)):
1327 if parents and all((state.get(p) == p for p in parents)):
1330 state[rev] = rev
1328 state[rev] = rev
1331 unfi = repo.unfiltered()
1329 unfi = repo.unfiltered()
1332 for r in obsoletenotrebased:
1330 for r in obsoletenotrebased:
1333 desc = _ctxdesc(unfi[r])
1331 desc = _ctxdesc(unfi[r])
1334 succ = obsoletenotrebased[r]
1332 succ = obsoletenotrebased[r]
1335 if succ is None:
1333 if succ is None:
1336 msg = _('note: not rebasing %s, it has no successor\n') % desc
1334 msg = _('note: not rebasing %s, it has no successor\n') % desc
1337 del state[r]
1335 del state[r]
1338 else:
1336 else:
1339 destctx = unfi[succ]
1337 destctx = unfi[succ]
1340 destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
1338 destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
1341 destctx.description().split('\n', 1)[0])
1339 destctx.description().split('\n', 1)[0])
1342 msg = (_('note: not rebasing %s, already in destination as %s\n')
1340 msg = (_('note: not rebasing %s, already in destination as %s\n')
1343 % (desc, destdesc))
1341 % (desc, destdesc))
1344 del state[r]
1342 del state[r]
1345 repo.ui.status(msg)
1343 repo.ui.status(msg)
1346 return originalwd, dest.rev(), state
1344 return originalwd, dest.rev(), state
1347
1345
1348 def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
1346 def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
1349 """dispose of rebased revision at the end of the rebase
1347 """dispose of rebased revision at the end of the rebase
1350
1348
1351 If `collapsedas` is not None, the rebase was a collapse whose result if the
1349 If `collapsedas` is not None, the rebase was a collapse whose result if the
1352 `collapsedas` node."""
1350 `collapsedas` node."""
1353 tonode = repo.changelog.node
1351 tonode = repo.changelog.node
1354 # Move bookmark of skipped nodes to destination. This cannot be handled
1352 # Move bookmark of skipped nodes to destination. This cannot be handled
1355 # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
1353 # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
1356 # and move bookmark backwards.
1354 # and move bookmark backwards.
1357 bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state))))
1355 bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state))))
1358 for rev in skipped
1356 for rev in skipped
1359 for name in repo.nodebookmarks(tonode(rev))]
1357 for name in repo.nodebookmarks(tonode(rev))]
1360 if bmchanges:
1358 if bmchanges:
1361 with repo.transaction('rebase') as tr:
1359 with repo.transaction('rebase') as tr:
1362 repo._bookmarks.applychanges(repo, tr, bmchanges)
1360 repo._bookmarks.applychanges(repo, tr, bmchanges)
1363 mapping = {}
1361 mapping = {}
1364 for rev, newrev in sorted(state.items()):
1362 for rev, newrev in sorted(state.items()):
1365 if newrev >= 0 and newrev != rev:
1363 if newrev >= 0 and newrev != rev:
1366 if rev in skipped:
1364 if rev in skipped:
1367 succs = ()
1365 succs = ()
1368 elif collapsedas is not None:
1366 elif collapsedas is not None:
1369 succs = (collapsedas,)
1367 succs = (collapsedas,)
1370 else:
1368 else:
1371 succs = (tonode(newrev),)
1369 succs = (tonode(newrev),)
1372 mapping[tonode(rev)] = succs
1370 mapping[tonode(rev)] = succs
1373 scmutil.cleanupnodes(repo, mapping, 'rebase')
1371 scmutil.cleanupnodes(repo, mapping, 'rebase')
1374
1372
1375 def pullrebase(orig, ui, repo, *args, **opts):
1373 def pullrebase(orig, ui, repo, *args, **opts):
1376 'Call rebase after pull if the latter has been invoked with --rebase'
1374 'Call rebase after pull if the latter has been invoked with --rebase'
1377 ret = None
1375 ret = None
1378 if opts.get('rebase'):
1376 if opts.get('rebase'):
1379 if ui.configbool('commands', 'rebase.requiredest'):
1377 if ui.configbool('commands', 'rebase.requiredest'):
1380 msg = _('rebase destination required by configuration')
1378 msg = _('rebase destination required by configuration')
1381 hint = _('use hg pull followed by hg rebase -d DEST')
1379 hint = _('use hg pull followed by hg rebase -d DEST')
1382 raise error.Abort(msg, hint=hint)
1380 raise error.Abort(msg, hint=hint)
1383
1381
1384 with repo.wlock(), repo.lock():
1382 with repo.wlock(), repo.lock():
1385 if opts.get('update'):
1383 if opts.get('update'):
1386 del opts['update']
1384 del opts['update']
1387 ui.debug('--update and --rebase are not compatible, ignoring '
1385 ui.debug('--update and --rebase are not compatible, ignoring '
1388 'the update flag\n')
1386 'the update flag\n')
1389
1387
1390 cmdutil.checkunfinished(repo)
1388 cmdutil.checkunfinished(repo)
1391 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1389 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1392 'please commit or shelve your changes first'))
1390 'please commit or shelve your changes first'))
1393
1391
1394 revsprepull = len(repo)
1392 revsprepull = len(repo)
1395 origpostincoming = commands.postincoming
1393 origpostincoming = commands.postincoming
1396 def _dummy(*args, **kwargs):
1394 def _dummy(*args, **kwargs):
1397 pass
1395 pass
1398 commands.postincoming = _dummy
1396 commands.postincoming = _dummy
1399 try:
1397 try:
1400 ret = orig(ui, repo, *args, **opts)
1398 ret = orig(ui, repo, *args, **opts)
1401 finally:
1399 finally:
1402 commands.postincoming = origpostincoming
1400 commands.postincoming = origpostincoming
1403 revspostpull = len(repo)
1401 revspostpull = len(repo)
1404 if revspostpull > revsprepull:
1402 if revspostpull > revsprepull:
1405 # --rev option from pull conflict with rebase own --rev
1403 # --rev option from pull conflict with rebase own --rev
1406 # dropping it
1404 # dropping it
1407 if 'rev' in opts:
1405 if 'rev' in opts:
1408 del opts['rev']
1406 del opts['rev']
1409 # positional argument from pull conflicts with rebase's own
1407 # positional argument from pull conflicts with rebase's own
1410 # --source.
1408 # --source.
1411 if 'source' in opts:
1409 if 'source' in opts:
1412 del opts['source']
1410 del opts['source']
1413 # revsprepull is the len of the repo, not revnum of tip.
1411 # revsprepull is the len of the repo, not revnum of tip.
1414 destspace = list(repo.changelog.revs(start=revsprepull))
1412 destspace = list(repo.changelog.revs(start=revsprepull))
1415 opts['_destspace'] = destspace
1413 opts['_destspace'] = destspace
1416 try:
1414 try:
1417 rebase(ui, repo, **opts)
1415 rebase(ui, repo, **opts)
1418 except error.NoMergeDestAbort:
1416 except error.NoMergeDestAbort:
1419 # we can maybe update instead
1417 # we can maybe update instead
1420 rev, _a, _b = destutil.destupdate(repo)
1418 rev, _a, _b = destutil.destupdate(repo)
1421 if rev == repo['.'].rev():
1419 if rev == repo['.'].rev():
1422 ui.status(_('nothing to rebase\n'))
1420 ui.status(_('nothing to rebase\n'))
1423 else:
1421 else:
1424 ui.status(_('nothing to rebase - updating instead\n'))
1422 ui.status(_('nothing to rebase - updating instead\n'))
1425 # not passing argument to get the bare update behavior
1423 # not passing argument to get the bare update behavior
1426 # with warning and trumpets
1424 # with warning and trumpets
1427 commands.update(ui, repo)
1425 commands.update(ui, repo)
1428 else:
1426 else:
1429 if opts.get('tool'):
1427 if opts.get('tool'):
1430 raise error.Abort(_('--tool can only be used with --rebase'))
1428 raise error.Abort(_('--tool can only be used with --rebase'))
1431 ret = orig(ui, repo, *args, **opts)
1429 ret = orig(ui, repo, *args, **opts)
1432
1430
1433 return ret
1431 return ret
1434
1432
1435 def _setrebasesetvisibility(repo, revs):
1433 def _setrebasesetvisibility(repo, revs):
1436 """store the currently rebased set on the repo object
1434 """store the currently rebased set on the repo object
1437
1435
1438 This is used by another function to prevent rebased revision to because
1436 This is used by another function to prevent rebased revision to because
1439 hidden (see issue4504)"""
1437 hidden (see issue4504)"""
1440 repo = repo.unfiltered()
1438 repo = repo.unfiltered()
1441 repo._rebaseset = revs
1439 repo._rebaseset = revs
1442 # invalidate cache if visibility changes
1440 # invalidate cache if visibility changes
1443 hiddens = repo.filteredrevcache.get('visible', set())
1441 hiddens = repo.filteredrevcache.get('visible', set())
1444 if revs & hiddens:
1442 if revs & hiddens:
1445 repo.invalidatevolatilesets()
1443 repo.invalidatevolatilesets()
1446
1444
1447 def _clearrebasesetvisibiliy(repo):
1445 def _clearrebasesetvisibiliy(repo):
1448 """remove rebaseset data from the repo"""
1446 """remove rebaseset data from the repo"""
1449 repo = repo.unfiltered()
1447 repo = repo.unfiltered()
1450 if '_rebaseset' in vars(repo):
1448 if '_rebaseset' in vars(repo):
1451 del repo._rebaseset
1449 del repo._rebaseset
1452
1450
1453 def _rebasedvisible(orig, repo):
1451 def _rebasedvisible(orig, repo):
1454 """ensure rebased revs stay visible (see issue4504)"""
1452 """ensure rebased revs stay visible (see issue4504)"""
1455 blockers = orig(repo)
1453 blockers = orig(repo)
1456 blockers.update(getattr(repo, '_rebaseset', ()))
1454 blockers.update(getattr(repo, '_rebaseset', ()))
1457 return blockers
1455 return blockers
1458
1456
1459 def _filterobsoleterevs(repo, revs):
1457 def _filterobsoleterevs(repo, revs):
1460 """returns a set of the obsolete revisions in revs"""
1458 """returns a set of the obsolete revisions in revs"""
1461 return set(r for r in revs if repo[r].obsolete())
1459 return set(r for r in revs if repo[r].obsolete())
1462
1460
1463 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1461 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1464 """return a mapping obsolete => successor for all obsolete nodes to be
1462 """return a mapping obsolete => successor for all obsolete nodes to be
1465 rebased that have a successors in the destination
1463 rebased that have a successors in the destination
1466
1464
1467 obsolete => None entries in the mapping indicate nodes with no successor"""
1465 obsolete => None entries in the mapping indicate nodes with no successor"""
1468 obsoletenotrebased = {}
1466 obsoletenotrebased = {}
1469
1467
1470 # Build a mapping successor => obsolete nodes for the obsolete
1468 # Build a mapping successor => obsolete nodes for the obsolete
1471 # nodes to be rebased
1469 # nodes to be rebased
1472 allsuccessors = {}
1470 allsuccessors = {}
1473 cl = repo.changelog
1471 cl = repo.changelog
1474 for r in rebaseobsrevs:
1472 for r in rebaseobsrevs:
1475 node = cl.node(r)
1473 node = cl.node(r)
1476 for s in obsutil.allsuccessors(repo.obsstore, [node]):
1474 for s in obsutil.allsuccessors(repo.obsstore, [node]):
1477 try:
1475 try:
1478 allsuccessors[cl.rev(s)] = cl.rev(node)
1476 allsuccessors[cl.rev(s)] = cl.rev(node)
1479 except LookupError:
1477 except LookupError:
1480 pass
1478 pass
1481
1479
1482 if allsuccessors:
1480 if allsuccessors:
1483 # Look for successors of obsolete nodes to be rebased among
1481 # Look for successors of obsolete nodes to be rebased among
1484 # the ancestors of dest
1482 # the ancestors of dest
1485 ancs = cl.ancestors([dest],
1483 ancs = cl.ancestors([dest],
1486 stoprev=min(allsuccessors),
1484 stoprev=min(allsuccessors),
1487 inclusive=True)
1485 inclusive=True)
1488 for s in allsuccessors:
1486 for s in allsuccessors:
1489 if s in ancs:
1487 if s in ancs:
1490 obsoletenotrebased[allsuccessors[s]] = s
1488 obsoletenotrebased[allsuccessors[s]] = s
1491 elif (s == allsuccessors[s] and
1489 elif (s == allsuccessors[s] and
1492 allsuccessors.values().count(s) == 1):
1490 allsuccessors.values().count(s) == 1):
1493 # plain prune
1491 # plain prune
1494 obsoletenotrebased[s] = None
1492 obsoletenotrebased[s] = None
1495
1493
1496 return obsoletenotrebased
1494 return obsoletenotrebased
1497
1495
1498 def summaryhook(ui, repo):
1496 def summaryhook(ui, repo):
1499 if not repo.vfs.exists('rebasestate'):
1497 if not repo.vfs.exists('rebasestate'):
1500 return
1498 return
1501 try:
1499 try:
1502 rbsrt = rebaseruntime(repo, ui, {})
1500 rbsrt = rebaseruntime(repo, ui, {})
1503 rbsrt.restorestatus()
1501 rbsrt.restorestatus()
1504 state = rbsrt.state
1502 state = rbsrt.state
1505 except error.RepoLookupError:
1503 except error.RepoLookupError:
1506 # i18n: column positioning for "hg summary"
1504 # i18n: column positioning for "hg summary"
1507 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1505 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1508 ui.write(msg)
1506 ui.write(msg)
1509 return
1507 return
1510 numrebased = len([i for i in state.itervalues() if i >= 0])
1508 numrebased = len([i for i in state.itervalues() if i >= 0])
1511 # i18n: column positioning for "hg summary"
1509 # i18n: column positioning for "hg summary"
1512 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1510 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1513 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1511 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1514 ui.label(_('%d remaining'), 'rebase.remaining') %
1512 ui.label(_('%d remaining'), 'rebase.remaining') %
1515 (len(state) - numrebased)))
1513 (len(state) - numrebased)))
1516
1514
1517 def uisetup(ui):
1515 def uisetup(ui):
1518 #Replace pull with a decorator to provide --rebase option
1516 #Replace pull with a decorator to provide --rebase option
1519 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1517 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1520 entry[1].append(('', 'rebase', None,
1518 entry[1].append(('', 'rebase', None,
1521 _("rebase working directory to branch head")))
1519 _("rebase working directory to branch head")))
1522 entry[1].append(('t', 'tool', '',
1520 entry[1].append(('t', 'tool', '',
1523 _("specify merge tool for rebase")))
1521 _("specify merge tool for rebase")))
1524 cmdutil.summaryhooks.add('rebase', summaryhook)
1522 cmdutil.summaryhooks.add('rebase', summaryhook)
1525 cmdutil.unfinishedstates.append(
1523 cmdutil.unfinishedstates.append(
1526 ['rebasestate', False, False, _('rebase in progress'),
1524 ['rebasestate', False, False, _('rebase in progress'),
1527 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1525 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1528 cmdutil.afterresolvedstates.append(
1526 cmdutil.afterresolvedstates.append(
1529 ['rebasestate', _('hg rebase --continue')])
1527 ['rebasestate', _('hg rebase --continue')])
1530 # ensure rebased rev are not hidden
1528 # ensure rebased rev are not hidden
1531 extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
1529 extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
General Comments 0
You need to be logged in to leave comments. Login now