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