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