##// END OF EJS Templates
rebase: allow destination-free continue and abort (issue5513)
Ryan McElroy -
r31731:b5afec71 default
parent child Browse files
Show More
@@ -1,1519 +1,1517
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 = cmdutil.command(cmdtable)
71 command = cmdutil.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.target = None
142 self.target = None
143 self.skipped = set()
143 self.skipped = set()
144 self.targetancestors = set()
144 self.targetancestors = 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.target].hex() + '\n')
174 f.write(repo[self.target].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 target = None
197 target = 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 target = repo[l].rev()
209 target = 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 = set([target])
245 seen = set([target])
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()) | set([originalwd]))
253 _setrebasesetvisibility(repo, set(state.keys()) | set([originalwd]))
254
254
255 self.originalwd = originalwd
255 self.originalwd = originalwd
256 self.target = target
256 self.target = target
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, target):
265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
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 target: a destination revision for the rebase operation
270 target: 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, target)
279 obsoleteset, target)
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.target,
299 return abort(self.repo, self.originalwd, self.target,
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.target)
303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
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.target, self.state) = result
335 (self.originalwd, self.target, self.state) = result
336 if self.collapsef:
336 if self.collapsef:
337 self.targetancestors = self.repo.changelog.ancestors(
337 self.targetancestors = self.repo.changelog.ancestors(
338 [self.target],
338 [self.target],
339 inclusive=True)
339 inclusive=True)
340 self.external = externalparent(self.repo, self.state,
340 self.external = externalparent(self.repo, self.state,
341 self.targetancestors)
341 self.targetancestors)
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.targetancestors:
362 if not self.targetancestors:
363 self.targetancestors = repo.changelog.ancestors([self.target],
363 self.targetancestors = repo.changelog.ancestors([self.target],
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] == revtodo:
387 if self.state[rev] == revtodo:
388 pos += 1
388 pos += 1
389 ui.status(_('rebasing %s\n') % desc)
389 ui.status(_('rebasing %s\n') % desc)
390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
391 _('changesets'), total)
391 _('changesets'), total)
392 p1, p2, base = defineparents(repo, rev, self.target,
392 p1, p2, base = defineparents(repo, rev, self.target,
393 self.state,
393 self.state,
394 self.targetancestors,
394 self.targetancestors,
395 self.obsoletenotrebased)
395 self.obsoletenotrebased)
396 self.storestatus(tr=tr)
396 self.storestatus(tr=tr)
397 storecollapsemsg(repo, self.collapsemsg)
397 storecollapsemsg(repo, self.collapsemsg)
398 if len(repo[None].parents()) == 2:
398 if len(repo[None].parents()) == 2:
399 repo.ui.debug('resuming interrupted rebase\n')
399 repo.ui.debug('resuming interrupted rebase\n')
400 else:
400 else:
401 try:
401 try:
402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
403 'rebase')
403 'rebase')
404 stats = rebasenode(repo, rev, p1, base, self.state,
404 stats = rebasenode(repo, rev, p1, base, self.state,
405 self.collapsef, self.target)
405 self.collapsef, self.target)
406 if stats and stats[3] > 0:
406 if stats and stats[3] > 0:
407 raise error.InterventionRequired(
407 raise error.InterventionRequired(
408 _('unresolved conflicts (see hg '
408 _('unresolved conflicts (see hg '
409 'resolve, then hg rebase --continue)'))
409 'resolve, then hg rebase --continue)'))
410 finally:
410 finally:
411 ui.setconfig('ui', 'forcemerge', '', 'rebase')
411 ui.setconfig('ui', 'forcemerge', '', 'rebase')
412 if not self.collapsef:
412 if not self.collapsef:
413 merging = p2 != nullrev
413 merging = p2 != nullrev
414 editform = cmdutil.mergeeditform(merging, 'rebase')
414 editform = cmdutil.mergeeditform(merging, 'rebase')
415 editor = cmdutil.getcommiteditor(editform=editform, **opts)
415 editor = cmdutil.getcommiteditor(editform=editform, **opts)
416 newnode = concludenode(repo, rev, p1, p2,
416 newnode = concludenode(repo, rev, p1, p2,
417 extrafn=_makeextrafn(self.extrafns),
417 extrafn=_makeextrafn(self.extrafns),
418 editor=editor,
418 editor=editor,
419 keepbranches=self.keepbranchesf,
419 keepbranches=self.keepbranchesf,
420 date=self.date)
420 date=self.date)
421 else:
421 else:
422 # Skip commit if we are collapsing
422 # Skip commit if we are collapsing
423 repo.dirstate.beginparentchange()
423 repo.dirstate.beginparentchange()
424 repo.setparents(repo[p1].node())
424 repo.setparents(repo[p1].node())
425 repo.dirstate.endparentchange()
425 repo.dirstate.endparentchange()
426 newnode = None
426 newnode = None
427 # Update the state
427 # Update the state
428 if newnode is not None:
428 if newnode is not None:
429 self.state[rev] = repo[newnode].rev()
429 self.state[rev] = repo[newnode].rev()
430 ui.debug('rebased as %s\n' % short(newnode))
430 ui.debug('rebased as %s\n' % short(newnode))
431 else:
431 else:
432 if not self.collapsef:
432 if not self.collapsef:
433 ui.warn(_('note: rebase of %d:%s created no changes '
433 ui.warn(_('note: rebase of %d:%s created no changes '
434 'to commit\n') % (rev, ctx))
434 'to commit\n') % (rev, ctx))
435 self.skipped.add(rev)
435 self.skipped.add(rev)
436 self.state[rev] = p1
436 self.state[rev] = p1
437 ui.debug('next revision set to %s\n' % p1)
437 ui.debug('next revision set to %s\n' % p1)
438 elif self.state[rev] == nullmerge:
438 elif self.state[rev] == nullmerge:
439 ui.debug('ignoring null merge rebase of %s\n' % rev)
439 ui.debug('ignoring null merge rebase of %s\n' % rev)
440 elif self.state[rev] == revignored:
440 elif self.state[rev] == revignored:
441 ui.status(_('not rebasing ignored %s\n') % desc)
441 ui.status(_('not rebasing ignored %s\n') % desc)
442 elif self.state[rev] == revprecursor:
442 elif self.state[rev] == revprecursor:
443 targetctx = repo[self.obsoletenotrebased[rev]]
443 targetctx = repo[self.obsoletenotrebased[rev]]
444 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
444 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
445 targetctx.description().split('\n', 1)[0])
445 targetctx.description().split('\n', 1)[0])
446 msg = _('note: not rebasing %s, already in destination as %s\n')
446 msg = _('note: not rebasing %s, already in destination as %s\n')
447 ui.status(msg % (desc, desctarget))
447 ui.status(msg % (desc, desctarget))
448 elif self.state[rev] == revpruned:
448 elif self.state[rev] == revpruned:
449 msg = _('note: not rebasing %s, it has no successor\n')
449 msg = _('note: not rebasing %s, it has no successor\n')
450 ui.status(msg % desc)
450 ui.status(msg % desc)
451 else:
451 else:
452 ui.status(_('already rebased %s as %s\n') %
452 ui.status(_('already rebased %s as %s\n') %
453 (desc, repo[self.state[rev]]))
453 (desc, repo[self.state[rev]]))
454
454
455 ui.progress(_('rebasing'), None)
455 ui.progress(_('rebasing'), None)
456 ui.note(_('rebase merging completed\n'))
456 ui.note(_('rebase merging completed\n'))
457
457
458 def _finishrebase(self):
458 def _finishrebase(self):
459 repo, ui, opts = self.repo, self.ui, self.opts
459 repo, ui, opts = self.repo, self.ui, self.opts
460 if self.collapsef and not self.keepopen:
460 if self.collapsef and not self.keepopen:
461 p1, p2, _base = defineparents(repo, min(self.state),
461 p1, p2, _base = defineparents(repo, min(self.state),
462 self.target, self.state,
462 self.target, self.state,
463 self.targetancestors,
463 self.targetancestors,
464 self.obsoletenotrebased)
464 self.obsoletenotrebased)
465 editopt = opts.get('edit')
465 editopt = opts.get('edit')
466 editform = 'rebase.collapse'
466 editform = 'rebase.collapse'
467 if self.collapsemsg:
467 if self.collapsemsg:
468 commitmsg = self.collapsemsg
468 commitmsg = self.collapsemsg
469 else:
469 else:
470 commitmsg = 'Collapsed revision'
470 commitmsg = 'Collapsed revision'
471 for rebased in self.state:
471 for rebased in self.state:
472 if rebased not in self.skipped and\
472 if rebased not in self.skipped and\
473 self.state[rebased] > nullmerge:
473 self.state[rebased] > nullmerge:
474 commitmsg += '\n* %s' % repo[rebased].description()
474 commitmsg += '\n* %s' % repo[rebased].description()
475 editopt = True
475 editopt = True
476 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
476 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
477 revtoreuse = max(self.state)
477 revtoreuse = max(self.state)
478 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
478 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
479 try:
479 try:
480 newnode = concludenode(repo, revtoreuse, p1, self.external,
480 newnode = concludenode(repo, revtoreuse, p1, self.external,
481 commitmsg=commitmsg,
481 commitmsg=commitmsg,
482 extrafn=_makeextrafn(self.extrafns),
482 extrafn=_makeextrafn(self.extrafns),
483 editor=editor,
483 editor=editor,
484 keepbranches=self.keepbranchesf,
484 keepbranches=self.keepbranchesf,
485 date=self.date)
485 date=self.date)
486 dsguard.close()
486 dsguard.close()
487 release(dsguard)
487 release(dsguard)
488 except error.InterventionRequired:
488 except error.InterventionRequired:
489 dsguard.close()
489 dsguard.close()
490 release(dsguard)
490 release(dsguard)
491 raise
491 raise
492 except Exception:
492 except Exception:
493 release(dsguard)
493 release(dsguard)
494 raise
494 raise
495
495
496 if newnode is None:
496 if newnode is None:
497 newrev = self.target
497 newrev = self.target
498 else:
498 else:
499 newrev = repo[newnode].rev()
499 newrev = repo[newnode].rev()
500 for oldrev in self.state.iterkeys():
500 for oldrev in self.state.iterkeys():
501 if self.state[oldrev] > nullmerge:
501 if self.state[oldrev] > nullmerge:
502 self.state[oldrev] = newrev
502 self.state[oldrev] = newrev
503
503
504 if 'qtip' in repo.tags():
504 if 'qtip' in repo.tags():
505 updatemq(repo, self.state, self.skipped, **opts)
505 updatemq(repo, self.state, self.skipped, **opts)
506
506
507 if self.currentbookmarks:
507 if self.currentbookmarks:
508 # Nodeids are needed to reset bookmarks
508 # Nodeids are needed to reset bookmarks
509 nstate = {}
509 nstate = {}
510 for k, v in self.state.iteritems():
510 for k, v in self.state.iteritems():
511 if v > nullmerge:
511 if v > nullmerge:
512 nstate[repo[k].node()] = repo[v].node()
512 nstate[repo[k].node()] = repo[v].node()
513 elif v == revprecursor:
513 elif v == revprecursor:
514 succ = self.obsoletenotrebased[k]
514 succ = self.obsoletenotrebased[k]
515 nstate[repo[k].node()] = repo[succ].node()
515 nstate[repo[k].node()] = repo[succ].node()
516 # XXX this is the same as dest.node() for the non-continue path --
516 # XXX this is the same as dest.node() for the non-continue path --
517 # this should probably be cleaned up
517 # this should probably be cleaned up
518 targetnode = repo[self.target].node()
518 targetnode = repo[self.target].node()
519
519
520 # restore original working directory
520 # restore original working directory
521 # (we do this before stripping)
521 # (we do this before stripping)
522 newwd = self.state.get(self.originalwd, self.originalwd)
522 newwd = self.state.get(self.originalwd, self.originalwd)
523 if newwd == revprecursor:
523 if newwd == revprecursor:
524 newwd = self.obsoletenotrebased[self.originalwd]
524 newwd = self.obsoletenotrebased[self.originalwd]
525 elif newwd < 0:
525 elif newwd < 0:
526 # original directory is a parent of rebase set root or ignored
526 # original directory is a parent of rebase set root or ignored
527 newwd = self.originalwd
527 newwd = self.originalwd
528 if newwd not in [c.rev() for c in repo[None].parents()]:
528 if newwd not in [c.rev() for c in repo[None].parents()]:
529 ui.note(_("update back to initial working directory parent\n"))
529 ui.note(_("update back to initial working directory parent\n"))
530 hg.updaterepo(repo, newwd, False)
530 hg.updaterepo(repo, newwd, False)
531
531
532 if self.currentbookmarks:
532 if self.currentbookmarks:
533 with repo.transaction('bookmark') as tr:
533 with repo.transaction('bookmark') as tr:
534 updatebookmarks(repo, targetnode, nstate,
534 updatebookmarks(repo, targetnode, nstate,
535 self.currentbookmarks, tr)
535 self.currentbookmarks, tr)
536 if self.activebookmark not in repo._bookmarks:
536 if self.activebookmark not in repo._bookmarks:
537 # active bookmark was divergent one and has been deleted
537 # active bookmark was divergent one and has been deleted
538 self.activebookmark = None
538 self.activebookmark = None
539
539
540 if not self.keepf:
540 if not self.keepf:
541 collapsedas = None
541 collapsedas = None
542 if self.collapsef:
542 if self.collapsef:
543 collapsedas = newnode
543 collapsedas = newnode
544 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
544 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
545
545
546 clearstatus(repo)
546 clearstatus(repo)
547 clearcollapsemsg(repo)
547 clearcollapsemsg(repo)
548
548
549 ui.note(_("rebase completed\n"))
549 ui.note(_("rebase completed\n"))
550 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
550 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
551 if self.skipped:
551 if self.skipped:
552 skippedlen = len(self.skipped)
552 skippedlen = len(self.skipped)
553 ui.note(_("%d revisions have been skipped\n") % skippedlen)
553 ui.note(_("%d revisions have been skipped\n") % skippedlen)
554
554
555 if (self.activebookmark and
555 if (self.activebookmark and
556 repo['.'].node() == repo._bookmarks[self.activebookmark]):
556 repo['.'].node() == repo._bookmarks[self.activebookmark]):
557 bookmarks.activate(repo, self.activebookmark)
557 bookmarks.activate(repo, self.activebookmark)
558
558
559 @command('rebase',
559 @command('rebase',
560 [('s', 'source', '',
560 [('s', 'source', '',
561 _('rebase the specified changeset and descendants'), _('REV')),
561 _('rebase the specified changeset and descendants'), _('REV')),
562 ('b', 'base', '',
562 ('b', 'base', '',
563 _('rebase everything from branching point of specified changeset'),
563 _('rebase everything from branching point of specified changeset'),
564 _('REV')),
564 _('REV')),
565 ('r', 'rev', [],
565 ('r', 'rev', [],
566 _('rebase these revisions'),
566 _('rebase these revisions'),
567 _('REV')),
567 _('REV')),
568 ('d', 'dest', '',
568 ('d', 'dest', '',
569 _('rebase onto the specified changeset'), _('REV')),
569 _('rebase onto the specified changeset'), _('REV')),
570 ('', 'collapse', False, _('collapse the rebased changesets')),
570 ('', 'collapse', False, _('collapse the rebased changesets')),
571 ('m', 'message', '',
571 ('m', 'message', '',
572 _('use text as collapse commit message'), _('TEXT')),
572 _('use text as collapse commit message'), _('TEXT')),
573 ('e', 'edit', False, _('invoke editor on commit messages')),
573 ('e', 'edit', False, _('invoke editor on commit messages')),
574 ('l', 'logfile', '',
574 ('l', 'logfile', '',
575 _('read collapse commit message from file'), _('FILE')),
575 _('read collapse commit message from file'), _('FILE')),
576 ('k', 'keep', False, _('keep original changesets')),
576 ('k', 'keep', False, _('keep original changesets')),
577 ('', 'keepbranches', False, _('keep original branch names')),
577 ('', 'keepbranches', False, _('keep original branch names')),
578 ('D', 'detach', False, _('(DEPRECATED)')),
578 ('D', 'detach', False, _('(DEPRECATED)')),
579 ('i', 'interactive', False, _('(DEPRECATED)')),
579 ('i', 'interactive', False, _('(DEPRECATED)')),
580 ('t', 'tool', '', _('specify merge tool')),
580 ('t', 'tool', '', _('specify merge tool')),
581 ('c', 'continue', False, _('continue an interrupted rebase')),
581 ('c', 'continue', False, _('continue an interrupted rebase')),
582 ('a', 'abort', False, _('abort an interrupted rebase'))] +
582 ('a', 'abort', False, _('abort an interrupted rebase'))] +
583 templateopts,
583 templateopts,
584 _('[-s REV | -b REV] [-d REV] [OPTION]'))
584 _('[-s REV | -b REV] [-d REV] [OPTION]'))
585 def rebase(ui, repo, **opts):
585 def rebase(ui, repo, **opts):
586 """move changeset (and descendants) to a different branch
586 """move changeset (and descendants) to a different branch
587
587
588 Rebase uses repeated merging to graft changesets from one part of
588 Rebase uses repeated merging to graft changesets from one part of
589 history (the source) onto another (the destination). This can be
589 history (the source) onto another (the destination). This can be
590 useful for linearizing *local* changes relative to a master
590 useful for linearizing *local* changes relative to a master
591 development tree.
591 development tree.
592
592
593 Published commits cannot be rebased (see :hg:`help phases`).
593 Published commits cannot be rebased (see :hg:`help phases`).
594 To copy commits, see :hg:`help graft`.
594 To copy commits, see :hg:`help graft`.
595
595
596 If you don't specify a destination changeset (``-d/--dest``), rebase
596 If you don't specify a destination changeset (``-d/--dest``), rebase
597 will use the same logic as :hg:`merge` to pick a destination. if
597 will use the same logic as :hg:`merge` to pick a destination. if
598 the current branch contains exactly one other head, the other head
598 the current branch contains exactly one other head, the other head
599 is merged with by default. Otherwise, an explicit revision with
599 is merged with by default. Otherwise, an explicit revision with
600 which to merge with must be provided. (destination changeset is not
600 which to merge with must be provided. (destination changeset is not
601 modified by rebasing, but new changesets are added as its
601 modified by rebasing, but new changesets are added as its
602 descendants.)
602 descendants.)
603
603
604 Here are the ways to select changesets:
604 Here are the ways to select changesets:
605
605
606 1. Explicitly select them using ``--rev``.
606 1. Explicitly select them using ``--rev``.
607
607
608 2. Use ``--source`` to select a root changeset and include all of its
608 2. Use ``--source`` to select a root changeset and include all of its
609 descendants.
609 descendants.
610
610
611 3. Use ``--base`` to select a changeset; rebase will find ancestors
611 3. Use ``--base`` to select a changeset; rebase will find ancestors
612 and their descendants which are not also ancestors of the destination.
612 and their descendants which are not also ancestors of the destination.
613
613
614 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
614 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
615 rebase will use ``--base .`` as above.
615 rebase will use ``--base .`` as above.
616
616
617 Rebase will destroy original changesets unless you use ``--keep``.
617 Rebase will destroy original changesets unless you use ``--keep``.
618 It will also move your bookmarks (even if you do).
618 It will also move your bookmarks (even if you do).
619
619
620 Some changesets may be dropped if they do not contribute changes
620 Some changesets may be dropped if they do not contribute changes
621 (e.g. merges from the destination branch).
621 (e.g. merges from the destination branch).
622
622
623 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
623 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
624 a named branch with two heads. You will need to explicitly specify source
624 a named branch with two heads. You will need to explicitly specify source
625 and/or destination.
625 and/or destination.
626
626
627 If you need to use a tool to automate merge/conflict decisions, you
627 If you need to use a tool to automate merge/conflict decisions, you
628 can specify one with ``--tool``, see :hg:`help merge-tools`.
628 can specify one with ``--tool``, see :hg:`help merge-tools`.
629 As a caveat: the tool will not be used to mediate when a file was
629 As a caveat: the tool will not be used to mediate when a file was
630 deleted, there is no hook presently available for this.
630 deleted, there is no hook presently available for this.
631
631
632 If a rebase is interrupted to manually resolve a conflict, it can be
632 If a rebase is interrupted to manually resolve a conflict, it can be
633 continued with --continue/-c or aborted with --abort/-a.
633 continued with --continue/-c or aborted with --abort/-a.
634
634
635 .. container:: verbose
635 .. container:: verbose
636
636
637 Examples:
637 Examples:
638
638
639 - move "local changes" (current commit back to branching point)
639 - move "local changes" (current commit back to branching point)
640 to the current branch tip after a pull::
640 to the current branch tip after a pull::
641
641
642 hg rebase
642 hg rebase
643
643
644 - move a single changeset to the stable branch::
644 - move a single changeset to the stable branch::
645
645
646 hg rebase -r 5f493448 -d stable
646 hg rebase -r 5f493448 -d stable
647
647
648 - splice a commit and all its descendants onto another part of history::
648 - splice a commit and all its descendants onto another part of history::
649
649
650 hg rebase --source c0c3 --dest 4cf9
650 hg rebase --source c0c3 --dest 4cf9
651
651
652 - rebase everything on a branch marked by a bookmark onto the
652 - rebase everything on a branch marked by a bookmark onto the
653 default branch::
653 default branch::
654
654
655 hg rebase --base myfeature --dest default
655 hg rebase --base myfeature --dest default
656
656
657 - collapse a sequence of changes into a single commit::
657 - collapse a sequence of changes into a single commit::
658
658
659 hg rebase --collapse -r 1520:1525 -d .
659 hg rebase --collapse -r 1520:1525 -d .
660
660
661 - move a named branch while preserving its name::
661 - move a named branch while preserving its name::
662
662
663 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
663 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
664
664
665 Configuration Options:
665 Configuration Options:
666
666
667 You can make rebase require a destination if you set the following config
667 You can make rebase require a destination if you set the following config
668 option:
668 option:
669
669
670 [commands]
670 [commands]
671 rebase.requiredest = False
671 rebase.requiredest = False
672
672
673 Return Values:
673 Return Values:
674
674
675 Returns 0 on success, 1 if nothing to rebase or there are
675 Returns 0 on success, 1 if nothing to rebase or there are
676 unresolved conflicts.
676 unresolved conflicts.
677
677
678 """
678 """
679 rbsrt = rebaseruntime(repo, ui, opts)
679 rbsrt = rebaseruntime(repo, ui, opts)
680
680
681 lock = wlock = None
681 lock = wlock = None
682 try:
682 try:
683 wlock = repo.wlock()
683 wlock = repo.wlock()
684 lock = repo.lock()
684 lock = repo.lock()
685
685
686 # Validate input and define rebasing points
686 # Validate input and define rebasing points
687 destf = opts.get('dest', None)
687 destf = opts.get('dest', None)
688
689 if ui.configbool('commands', 'rebase.requiredest'):
690 if not destf:
691 raise error.Abort(_('you must specify a destination'),
692 hint=_('use: hg rebase -d REV'))
693
694 srcf = opts.get('source', None)
688 srcf = opts.get('source', None)
695 basef = opts.get('base', None)
689 basef = opts.get('base', None)
696 revf = opts.get('rev', [])
690 revf = opts.get('rev', [])
697 # search default destination in this space
691 # search default destination in this space
698 # used in the 'hg pull --rebase' case, see issue 5214.
692 # used in the 'hg pull --rebase' case, see issue 5214.
699 destspace = opts.get('_destspace')
693 destspace = opts.get('_destspace')
700 contf = opts.get('continue')
694 contf = opts.get('continue')
701 abortf = opts.get('abort')
695 abortf = opts.get('abort')
702 if opts.get('interactive'):
696 if opts.get('interactive'):
703 try:
697 try:
704 if extensions.find('histedit'):
698 if extensions.find('histedit'):
705 enablehistedit = ''
699 enablehistedit = ''
706 except KeyError:
700 except KeyError:
707 enablehistedit = " --config extensions.histedit="
701 enablehistedit = " --config extensions.histedit="
708 help = "hg%s help -e histedit" % enablehistedit
702 help = "hg%s help -e histedit" % enablehistedit
709 msg = _("interactive history editing is supported by the "
703 msg = _("interactive history editing is supported by the "
710 "'histedit' extension (see \"%s\")") % help
704 "'histedit' extension (see \"%s\")") % help
711 raise error.Abort(msg)
705 raise error.Abort(msg)
712
706
713 if rbsrt.collapsemsg and not rbsrt.collapsef:
707 if rbsrt.collapsemsg and not rbsrt.collapsef:
714 raise error.Abort(
708 raise error.Abort(
715 _('message can only be specified with collapse'))
709 _('message can only be specified with collapse'))
716
710
717 if contf or abortf:
711 if contf or abortf:
718 if contf and abortf:
712 if contf and abortf:
719 raise error.Abort(_('cannot use both abort and continue'))
713 raise error.Abort(_('cannot use both abort and continue'))
720 if rbsrt.collapsef:
714 if rbsrt.collapsef:
721 raise error.Abort(
715 raise error.Abort(
722 _('cannot use collapse with continue or abort'))
716 _('cannot use collapse with continue or abort'))
723 if srcf or basef or destf:
717 if srcf or basef or destf:
724 raise error.Abort(
718 raise error.Abort(
725 _('abort and continue do not allow specifying revisions'))
719 _('abort and continue do not allow specifying revisions'))
726 if abortf and opts.get('tool', False):
720 if abortf and opts.get('tool', False):
727 ui.warn(_('tool option will be ignored\n'))
721 ui.warn(_('tool option will be ignored\n'))
728 if contf:
722 if contf:
729 ms = mergemod.mergestate.read(repo)
723 ms = mergemod.mergestate.read(repo)
730 mergeutil.checkunresolved(ms)
724 mergeutil.checkunresolved(ms)
731
725
732 retcode = rbsrt._prepareabortorcontinue(abortf)
726 retcode = rbsrt._prepareabortorcontinue(abortf)
733 if retcode is not None:
727 if retcode is not None:
734 return retcode
728 return retcode
735 else:
729 else:
736 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
730 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
737 destspace=destspace)
731 destspace=destspace)
738 retcode = rbsrt._preparenewrebase(dest, rebaseset)
732 retcode = rbsrt._preparenewrebase(dest, rebaseset)
739 if retcode is not None:
733 if retcode is not None:
740 return retcode
734 return retcode
741
735
742 with repo.transaction('rebase') as tr:
736 with repo.transaction('rebase') as tr:
743 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
737 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
744 try:
738 try:
745 rbsrt._performrebase(tr)
739 rbsrt._performrebase(tr)
746 dsguard.close()
740 dsguard.close()
747 release(dsguard)
741 release(dsguard)
748 except error.InterventionRequired:
742 except error.InterventionRequired:
749 dsguard.close()
743 dsguard.close()
750 release(dsguard)
744 release(dsguard)
751 tr.close()
745 tr.close()
752 raise
746 raise
753 except Exception:
747 except Exception:
754 release(dsguard)
748 release(dsguard)
755 raise
749 raise
756 rbsrt._finishrebase()
750 rbsrt._finishrebase()
757 finally:
751 finally:
758 release(lock, wlock)
752 release(lock, wlock)
759
753
760 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
754 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
761 destspace=None):
755 destspace=None):
762 """use revisions argument to define destination and rebase set
756 """use revisions argument to define destination and rebase set
763 """
757 """
764 if revf is None:
758 if revf is None:
765 revf = []
759 revf = []
766
760
767 # destspace is here to work around issues with `hg pull --rebase` see
761 # destspace is here to work around issues with `hg pull --rebase` see
768 # issue5214 for details
762 # issue5214 for details
769 if srcf and basef:
763 if srcf and basef:
770 raise error.Abort(_('cannot specify both a source and a base'))
764 raise error.Abort(_('cannot specify both a source and a base'))
771 if revf and basef:
765 if revf and basef:
772 raise error.Abort(_('cannot specify both a revision and a base'))
766 raise error.Abort(_('cannot specify both a revision and a base'))
773 if revf and srcf:
767 if revf and srcf:
774 raise error.Abort(_('cannot specify both a revision and a source'))
768 raise error.Abort(_('cannot specify both a revision and a source'))
775
769
776 cmdutil.checkunfinished(repo)
770 cmdutil.checkunfinished(repo)
777 cmdutil.bailifchanged(repo)
771 cmdutil.bailifchanged(repo)
778
772
773 if ui.configbool('commands', 'rebase.requiredest') and not destf:
774 raise error.Abort(_('you must specify a destination'),
775 hint=_('use: hg rebase -d REV'))
776
779 if destf:
777 if destf:
780 dest = scmutil.revsingle(repo, destf)
778 dest = scmutil.revsingle(repo, destf)
781
779
782 if revf:
780 if revf:
783 rebaseset = scmutil.revrange(repo, revf)
781 rebaseset = scmutil.revrange(repo, revf)
784 if not rebaseset:
782 if not rebaseset:
785 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
783 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
786 return None, None
784 return None, None
787 elif srcf:
785 elif srcf:
788 src = scmutil.revrange(repo, [srcf])
786 src = scmutil.revrange(repo, [srcf])
789 if not src:
787 if not src:
790 ui.status(_('empty "source" revision set - nothing to rebase\n'))
788 ui.status(_('empty "source" revision set - nothing to rebase\n'))
791 return None, None
789 return None, None
792 rebaseset = repo.revs('(%ld)::', src)
790 rebaseset = repo.revs('(%ld)::', src)
793 assert rebaseset
791 assert rebaseset
794 else:
792 else:
795 base = scmutil.revrange(repo, [basef or '.'])
793 base = scmutil.revrange(repo, [basef or '.'])
796 if not base:
794 if not base:
797 ui.status(_('empty "base" revision set - '
795 ui.status(_('empty "base" revision set - '
798 "can't compute rebase set\n"))
796 "can't compute rebase set\n"))
799 return None, None
797 return None, None
800 if not destf:
798 if not destf:
801 dest = repo[_destrebase(repo, base, destspace=destspace)]
799 dest = repo[_destrebase(repo, base, destspace=destspace)]
802 destf = str(dest)
800 destf = str(dest)
803
801
804 roots = [] # selected children of branching points
802 roots = [] # selected children of branching points
805 bpbase = {} # {branchingpoint: [origbase]}
803 bpbase = {} # {branchingpoint: [origbase]}
806 for b in base: # group bases by branching points
804 for b in base: # group bases by branching points
807 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
805 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
808 bpbase[bp] = bpbase.get(bp, []) + [b]
806 bpbase[bp] = bpbase.get(bp, []) + [b]
809 if None in bpbase:
807 if None in bpbase:
810 # emulate the old behavior, showing "nothing to rebase" (a better
808 # emulate the old behavior, showing "nothing to rebase" (a better
811 # behavior may be abort with "cannot find branching point" error)
809 # behavior may be abort with "cannot find branching point" error)
812 bpbase.clear()
810 bpbase.clear()
813 for bp, bs in bpbase.iteritems(): # calculate roots
811 for bp, bs in bpbase.iteritems(): # calculate roots
814 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
812 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
815
813
816 rebaseset = repo.revs('%ld::', roots)
814 rebaseset = repo.revs('%ld::', roots)
817
815
818 if not rebaseset:
816 if not rebaseset:
819 # transform to list because smartsets are not comparable to
817 # transform to list because smartsets are not comparable to
820 # lists. This should be improved to honor laziness of
818 # lists. This should be improved to honor laziness of
821 # smartset.
819 # smartset.
822 if list(base) == [dest.rev()]:
820 if list(base) == [dest.rev()]:
823 if basef:
821 if basef:
824 ui.status(_('nothing to rebase - %s is both "base"'
822 ui.status(_('nothing to rebase - %s is both "base"'
825 ' and destination\n') % dest)
823 ' and destination\n') % dest)
826 else:
824 else:
827 ui.status(_('nothing to rebase - working directory '
825 ui.status(_('nothing to rebase - working directory '
828 'parent is also destination\n'))
826 'parent is also destination\n'))
829 elif not repo.revs('%ld - ::%d', base, dest):
827 elif not repo.revs('%ld - ::%d', base, dest):
830 if basef:
828 if basef:
831 ui.status(_('nothing to rebase - "base" %s is '
829 ui.status(_('nothing to rebase - "base" %s is '
832 'already an ancestor of destination '
830 'already an ancestor of destination '
833 '%s\n') %
831 '%s\n') %
834 ('+'.join(str(repo[r]) for r in base),
832 ('+'.join(str(repo[r]) for r in base),
835 dest))
833 dest))
836 else:
834 else:
837 ui.status(_('nothing to rebase - working '
835 ui.status(_('nothing to rebase - working '
838 'directory parent is already an '
836 'directory parent is already an '
839 'ancestor of destination %s\n') % dest)
837 'ancestor of destination %s\n') % dest)
840 else: # can it happen?
838 else: # can it happen?
841 ui.status(_('nothing to rebase from %s to %s\n') %
839 ui.status(_('nothing to rebase from %s to %s\n') %
842 ('+'.join(str(repo[r]) for r in base), dest))
840 ('+'.join(str(repo[r]) for r in base), dest))
843 return None, None
841 return None, None
844
842
845 if not destf:
843 if not destf:
846 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
844 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
847 destf = str(dest)
845 destf = str(dest)
848
846
849 return dest, rebaseset
847 return dest, rebaseset
850
848
851 def externalparent(repo, state, targetancestors):
849 def externalparent(repo, state, targetancestors):
852 """Return the revision that should be used as the second parent
850 """Return the revision that should be used as the second parent
853 when the revisions in state is collapsed on top of targetancestors.
851 when the revisions in state is collapsed on top of targetancestors.
854 Abort if there is more than one parent.
852 Abort if there is more than one parent.
855 """
853 """
856 parents = set()
854 parents = set()
857 source = min(state)
855 source = min(state)
858 for rev in state:
856 for rev in state:
859 if rev == source:
857 if rev == source:
860 continue
858 continue
861 for p in repo[rev].parents():
859 for p in repo[rev].parents():
862 if (p.rev() not in state
860 if (p.rev() not in state
863 and p.rev() not in targetancestors):
861 and p.rev() not in targetancestors):
864 parents.add(p.rev())
862 parents.add(p.rev())
865 if not parents:
863 if not parents:
866 return nullrev
864 return nullrev
867 if len(parents) == 1:
865 if len(parents) == 1:
868 return parents.pop()
866 return parents.pop()
869 raise error.Abort(_('unable to collapse on top of %s, there is more '
867 raise error.Abort(_('unable to collapse on top of %s, there is more '
870 'than one external parent: %s') %
868 'than one external parent: %s') %
871 (max(targetancestors),
869 (max(targetancestors),
872 ', '.join(str(p) for p in sorted(parents))))
870 ', '.join(str(p) for p in sorted(parents))))
873
871
874 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
872 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
875 keepbranches=False, date=None):
873 keepbranches=False, date=None):
876 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
874 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
877 but also store useful information in extra.
875 but also store useful information in extra.
878 Return node of committed revision.'''
876 Return node of committed revision.'''
879 repo.setparents(repo[p1].node(), repo[p2].node())
877 repo.setparents(repo[p1].node(), repo[p2].node())
880 ctx = repo[rev]
878 ctx = repo[rev]
881 if commitmsg is None:
879 if commitmsg is None:
882 commitmsg = ctx.description()
880 commitmsg = ctx.description()
883 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
881 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
884 extra = {'rebase_source': ctx.hex()}
882 extra = {'rebase_source': ctx.hex()}
885 if extrafn:
883 if extrafn:
886 extrafn(ctx, extra)
884 extrafn(ctx, extra)
887
885
888 targetphase = max(ctx.phase(), phases.draft)
886 targetphase = max(ctx.phase(), phases.draft)
889 overrides = {('phases', 'new-commit'): targetphase}
887 overrides = {('phases', 'new-commit'): targetphase}
890 with repo.ui.configoverride(overrides, 'rebase'):
888 with repo.ui.configoverride(overrides, 'rebase'):
891 if keepbranch:
889 if keepbranch:
892 repo.ui.setconfig('ui', 'allowemptycommit', True)
890 repo.ui.setconfig('ui', 'allowemptycommit', True)
893 # Commit might fail if unresolved files exist
891 # Commit might fail if unresolved files exist
894 if date is None:
892 if date is None:
895 date = ctx.date()
893 date = ctx.date()
896 newnode = repo.commit(text=commitmsg, user=ctx.user(),
894 newnode = repo.commit(text=commitmsg, user=ctx.user(),
897 date=date, extra=extra, editor=editor)
895 date=date, extra=extra, editor=editor)
898
896
899 repo.dirstate.setbranch(repo[newnode].branch())
897 repo.dirstate.setbranch(repo[newnode].branch())
900 return newnode
898 return newnode
901
899
902 def rebasenode(repo, rev, p1, base, state, collapse, target):
900 def rebasenode(repo, rev, p1, base, state, collapse, target):
903 'Rebase a single revision rev on top of p1 using base as merge ancestor'
901 'Rebase a single revision rev on top of p1 using base as merge ancestor'
904 # Merge phase
902 # Merge phase
905 # Update to target and merge it with local
903 # Update to target and merge it with local
906 if repo['.'].rev() != p1:
904 if repo['.'].rev() != p1:
907 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
905 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
908 mergemod.update(repo, p1, False, True)
906 mergemod.update(repo, p1, False, True)
909 else:
907 else:
910 repo.ui.debug(" already in target\n")
908 repo.ui.debug(" already in target\n")
911 repo.dirstate.write(repo.currenttransaction())
909 repo.dirstate.write(repo.currenttransaction())
912 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
910 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
913 if base is not None:
911 if base is not None:
914 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
912 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
915 # When collapsing in-place, the parent is the common ancestor, we
913 # When collapsing in-place, the parent is the common ancestor, we
916 # have to allow merging with it.
914 # have to allow merging with it.
917 stats = mergemod.update(repo, rev, True, True, base, collapse,
915 stats = mergemod.update(repo, rev, True, True, base, collapse,
918 labels=['dest', 'source'])
916 labels=['dest', 'source'])
919 if collapse:
917 if collapse:
920 copies.duplicatecopies(repo, rev, target)
918 copies.duplicatecopies(repo, rev, target)
921 else:
919 else:
922 # If we're not using --collapse, we need to
920 # If we're not using --collapse, we need to
923 # duplicate copies between the revision we're
921 # duplicate copies between the revision we're
924 # rebasing and its first parent, but *not*
922 # rebasing and its first parent, but *not*
925 # duplicate any copies that have already been
923 # duplicate any copies that have already been
926 # performed in the destination.
924 # performed in the destination.
927 p1rev = repo[rev].p1().rev()
925 p1rev = repo[rev].p1().rev()
928 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
926 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
929 return stats
927 return stats
930
928
931 def nearestrebased(repo, rev, state):
929 def nearestrebased(repo, rev, state):
932 """return the nearest ancestors of rev in the rebase result"""
930 """return the nearest ancestors of rev in the rebase result"""
933 rebased = [r for r in state if state[r] > nullmerge]
931 rebased = [r for r in state if state[r] > nullmerge]
934 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
932 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
935 if candidates:
933 if candidates:
936 return state[candidates.first()]
934 return state[candidates.first()]
937 else:
935 else:
938 return None
936 return None
939
937
940 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
938 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
941 """
939 """
942 Abort if rebase will create divergence or rebase is noop because of markers
940 Abort if rebase will create divergence or rebase is noop because of markers
943
941
944 `rebaseobsrevs`: set of obsolete revision in source
942 `rebaseobsrevs`: set of obsolete revision in source
945 `rebasesetrevs`: set of revisions to be rebased from source
943 `rebasesetrevs`: set of revisions to be rebased from source
946 `rebaseobsskipped`: set of revisions from source skipped because they have
944 `rebaseobsskipped`: set of revisions from source skipped because they have
947 successors in destination
945 successors in destination
948 """
946 """
949 # Obsolete node with successors not in dest leads to divergence
947 # Obsolete node with successors not in dest leads to divergence
950 divergenceok = ui.configbool('experimental',
948 divergenceok = ui.configbool('experimental',
951 'allowdivergence')
949 'allowdivergence')
952 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
950 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
953
951
954 if divergencebasecandidates and not divergenceok:
952 if divergencebasecandidates and not divergenceok:
955 divhashes = (str(repo[r])
953 divhashes = (str(repo[r])
956 for r in divergencebasecandidates)
954 for r in divergencebasecandidates)
957 msg = _("this rebase will cause "
955 msg = _("this rebase will cause "
958 "divergences from: %s")
956 "divergences from: %s")
959 h = _("to force the rebase please set "
957 h = _("to force the rebase please set "
960 "experimental.allowdivergence=True")
958 "experimental.allowdivergence=True")
961 raise error.Abort(msg % (",".join(divhashes),), hint=h)
959 raise error.Abort(msg % (",".join(divhashes),), hint=h)
962
960
963 def defineparents(repo, rev, target, state, targetancestors,
961 def defineparents(repo, rev, target, state, targetancestors,
964 obsoletenotrebased):
962 obsoletenotrebased):
965 'Return the new parent relationship of the revision that will be rebased'
963 'Return the new parent relationship of the revision that will be rebased'
966 parents = repo[rev].parents()
964 parents = repo[rev].parents()
967 p1 = p2 = nullrev
965 p1 = p2 = nullrev
968 rp1 = None
966 rp1 = None
969
967
970 p1n = parents[0].rev()
968 p1n = parents[0].rev()
971 if p1n in targetancestors:
969 if p1n in targetancestors:
972 p1 = target
970 p1 = target
973 elif p1n in state:
971 elif p1n in state:
974 if state[p1n] == nullmerge:
972 if state[p1n] == nullmerge:
975 p1 = target
973 p1 = target
976 elif state[p1n] in revskipped:
974 elif state[p1n] in revskipped:
977 p1 = nearestrebased(repo, p1n, state)
975 p1 = nearestrebased(repo, p1n, state)
978 if p1 is None:
976 if p1 is None:
979 p1 = target
977 p1 = target
980 else:
978 else:
981 p1 = state[p1n]
979 p1 = state[p1n]
982 else: # p1n external
980 else: # p1n external
983 p1 = target
981 p1 = target
984 p2 = p1n
982 p2 = p1n
985
983
986 if len(parents) == 2 and parents[1].rev() not in targetancestors:
984 if len(parents) == 2 and parents[1].rev() not in targetancestors:
987 p2n = parents[1].rev()
985 p2n = parents[1].rev()
988 # interesting second parent
986 # interesting second parent
989 if p2n in state:
987 if p2n in state:
990 if p1 == target: # p1n in targetancestors or external
988 if p1 == target: # p1n in targetancestors or external
991 p1 = state[p2n]
989 p1 = state[p2n]
992 if p1 == revprecursor:
990 if p1 == revprecursor:
993 rp1 = obsoletenotrebased[p2n]
991 rp1 = obsoletenotrebased[p2n]
994 elif state[p2n] in revskipped:
992 elif state[p2n] in revskipped:
995 p2 = nearestrebased(repo, p2n, state)
993 p2 = nearestrebased(repo, p2n, state)
996 if p2 is None:
994 if p2 is None:
997 # no ancestors rebased yet, detach
995 # no ancestors rebased yet, detach
998 p2 = target
996 p2 = target
999 else:
997 else:
1000 p2 = state[p2n]
998 p2 = state[p2n]
1001 else: # p2n external
999 else: # p2n external
1002 if p2 != nullrev: # p1n external too => rev is a merged revision
1000 if p2 != nullrev: # p1n external too => rev is a merged revision
1003 raise error.Abort(_('cannot use revision %d as base, result '
1001 raise error.Abort(_('cannot use revision %d as base, result '
1004 'would have 3 parents') % rev)
1002 'would have 3 parents') % rev)
1005 p2 = p2n
1003 p2 = p2n
1006 repo.ui.debug(" future parents are %d and %d\n" %
1004 repo.ui.debug(" future parents are %d and %d\n" %
1007 (repo[rp1 or p1].rev(), repo[p2].rev()))
1005 (repo[rp1 or p1].rev(), repo[p2].rev()))
1008
1006
1009 if not any(p.rev() in state for p in parents):
1007 if not any(p.rev() in state for p in parents):
1010 # Case (1) root changeset of a non-detaching rebase set.
1008 # Case (1) root changeset of a non-detaching rebase set.
1011 # Let the merge mechanism find the base itself.
1009 # Let the merge mechanism find the base itself.
1012 base = None
1010 base = None
1013 elif not repo[rev].p2():
1011 elif not repo[rev].p2():
1014 # Case (2) detaching the node with a single parent, use this parent
1012 # Case (2) detaching the node with a single parent, use this parent
1015 base = repo[rev].p1().rev()
1013 base = repo[rev].p1().rev()
1016 else:
1014 else:
1017 # Assuming there is a p1, this is the case where there also is a p2.
1015 # Assuming there is a p1, this is the case where there also is a p2.
1018 # We are thus rebasing a merge and need to pick the right merge base.
1016 # We are thus rebasing a merge and need to pick the right merge base.
1019 #
1017 #
1020 # Imagine we have:
1018 # Imagine we have:
1021 # - M: current rebase revision in this step
1019 # - M: current rebase revision in this step
1022 # - A: one parent of M
1020 # - A: one parent of M
1023 # - B: other parent of M
1021 # - B: other parent of M
1024 # - D: destination of this merge step (p1 var)
1022 # - D: destination of this merge step (p1 var)
1025 #
1023 #
1026 # Consider the case where D is a descendant of A or B and the other is
1024 # Consider the case where D is a descendant of A or B and the other is
1027 # 'outside'. In this case, the right merge base is the D ancestor.
1025 # 'outside'. In this case, the right merge base is the D ancestor.
1028 #
1026 #
1029 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1027 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1030 #
1028 #
1031 # If we pick B as the base, the merge involves:
1029 # If we pick B as the base, the merge involves:
1032 # - changes from B to M (actual changeset payload)
1030 # - changes from B to M (actual changeset payload)
1033 # - changes from B to D (induced by rebase) as D is a rebased
1031 # - changes from B to D (induced by rebase) as D is a rebased
1034 # version of B)
1032 # version of B)
1035 # Which exactly represent the rebase operation.
1033 # Which exactly represent the rebase operation.
1036 #
1034 #
1037 # If we pick A as the base, the merge involves:
1035 # If we pick A as the base, the merge involves:
1038 # - changes from A to M (actual changeset payload)
1036 # - changes from A to M (actual changeset payload)
1039 # - changes from A to D (with include changes between unrelated A and B
1037 # - changes from A to D (with include changes between unrelated A and B
1040 # plus changes induced by rebase)
1038 # plus changes induced by rebase)
1041 # Which does not represent anything sensible and creates a lot of
1039 # Which does not represent anything sensible and creates a lot of
1042 # conflicts. A is thus not the right choice - B is.
1040 # conflicts. A is thus not the right choice - B is.
1043 #
1041 #
1044 # Note: The base found in this 'proof' is only correct in the specified
1042 # Note: The base found in this 'proof' is only correct in the specified
1045 # case. This base does not make sense if is not D a descendant of A or B
1043 # case. This base does not make sense if is not D a descendant of A or B
1046 # or if the other is not parent 'outside' (especially not if the other
1044 # or if the other is not parent 'outside' (especially not if the other
1047 # parent has been rebased). The current implementation does not
1045 # parent has been rebased). The current implementation does not
1048 # make it feasible to consider different cases separately. In these
1046 # make it feasible to consider different cases separately. In these
1049 # other cases we currently just leave it to the user to correctly
1047 # other cases we currently just leave it to the user to correctly
1050 # resolve an impossible merge using a wrong ancestor.
1048 # resolve an impossible merge using a wrong ancestor.
1051 #
1049 #
1052 # xx, p1 could be -4, and both parents could probably be -4...
1050 # xx, p1 could be -4, and both parents could probably be -4...
1053 for p in repo[rev].parents():
1051 for p in repo[rev].parents():
1054 if state.get(p.rev()) == p1:
1052 if state.get(p.rev()) == p1:
1055 base = p.rev()
1053 base = p.rev()
1056 break
1054 break
1057 else: # fallback when base not found
1055 else: # fallback when base not found
1058 base = None
1056 base = None
1059
1057
1060 # Raise because this function is called wrong (see issue 4106)
1058 # Raise because this function is called wrong (see issue 4106)
1061 raise AssertionError('no base found to rebase on '
1059 raise AssertionError('no base found to rebase on '
1062 '(defineparents called wrong)')
1060 '(defineparents called wrong)')
1063 return rp1 or p1, p2, base
1061 return rp1 or p1, p2, base
1064
1062
1065 def isagitpatch(repo, patchname):
1063 def isagitpatch(repo, patchname):
1066 'Return true if the given patch is in git format'
1064 'Return true if the given patch is in git format'
1067 mqpatch = os.path.join(repo.mq.path, patchname)
1065 mqpatch = os.path.join(repo.mq.path, patchname)
1068 for line in patch.linereader(file(mqpatch, 'rb')):
1066 for line in patch.linereader(file(mqpatch, 'rb')):
1069 if line.startswith('diff --git'):
1067 if line.startswith('diff --git'):
1070 return True
1068 return True
1071 return False
1069 return False
1072
1070
1073 def updatemq(repo, state, skipped, **opts):
1071 def updatemq(repo, state, skipped, **opts):
1074 'Update rebased mq patches - finalize and then import them'
1072 'Update rebased mq patches - finalize and then import them'
1075 mqrebase = {}
1073 mqrebase = {}
1076 mq = repo.mq
1074 mq = repo.mq
1077 original_series = mq.fullseries[:]
1075 original_series = mq.fullseries[:]
1078 skippedpatches = set()
1076 skippedpatches = set()
1079
1077
1080 for p in mq.applied:
1078 for p in mq.applied:
1081 rev = repo[p.node].rev()
1079 rev = repo[p.node].rev()
1082 if rev in state:
1080 if rev in state:
1083 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1081 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1084 (rev, p.name))
1082 (rev, p.name))
1085 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1083 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1086 else:
1084 else:
1087 # Applied but not rebased, not sure this should happen
1085 # Applied but not rebased, not sure this should happen
1088 skippedpatches.add(p.name)
1086 skippedpatches.add(p.name)
1089
1087
1090 if mqrebase:
1088 if mqrebase:
1091 mq.finish(repo, mqrebase.keys())
1089 mq.finish(repo, mqrebase.keys())
1092
1090
1093 # We must start import from the newest revision
1091 # We must start import from the newest revision
1094 for rev in sorted(mqrebase, reverse=True):
1092 for rev in sorted(mqrebase, reverse=True):
1095 if rev not in skipped:
1093 if rev not in skipped:
1096 name, isgit = mqrebase[rev]
1094 name, isgit = mqrebase[rev]
1097 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1095 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1098 (name, state[rev], repo[state[rev]]))
1096 (name, state[rev], repo[state[rev]]))
1099 mq.qimport(repo, (), patchname=name, git=isgit,
1097 mq.qimport(repo, (), patchname=name, git=isgit,
1100 rev=[str(state[rev])])
1098 rev=[str(state[rev])])
1101 else:
1099 else:
1102 # Rebased and skipped
1100 # Rebased and skipped
1103 skippedpatches.add(mqrebase[rev][0])
1101 skippedpatches.add(mqrebase[rev][0])
1104
1102
1105 # Patches were either applied and rebased and imported in
1103 # Patches were either applied and rebased and imported in
1106 # order, applied and removed or unapplied. Discard the removed
1104 # order, applied and removed or unapplied. Discard the removed
1107 # ones while preserving the original series order and guards.
1105 # ones while preserving the original series order and guards.
1108 newseries = [s for s in original_series
1106 newseries = [s for s in original_series
1109 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1107 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1110 mq.fullseries[:] = newseries
1108 mq.fullseries[:] = newseries
1111 mq.seriesdirty = True
1109 mq.seriesdirty = True
1112 mq.savedirty()
1110 mq.savedirty()
1113
1111
1114 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1112 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1115 'Move bookmarks to their correct changesets, and delete divergent ones'
1113 'Move bookmarks to their correct changesets, and delete divergent ones'
1116 marks = repo._bookmarks
1114 marks = repo._bookmarks
1117 for k, v in originalbookmarks.iteritems():
1115 for k, v in originalbookmarks.iteritems():
1118 if v in nstate:
1116 if v in nstate:
1119 # update the bookmarks for revs that have moved
1117 # update the bookmarks for revs that have moved
1120 marks[k] = nstate[v]
1118 marks[k] = nstate[v]
1121 bookmarks.deletedivergent(repo, [targetnode], k)
1119 bookmarks.deletedivergent(repo, [targetnode], k)
1122 marks.recordchange(tr)
1120 marks.recordchange(tr)
1123
1121
1124 def storecollapsemsg(repo, collapsemsg):
1122 def storecollapsemsg(repo, collapsemsg):
1125 'Store the collapse message to allow recovery'
1123 'Store the collapse message to allow recovery'
1126 collapsemsg = collapsemsg or ''
1124 collapsemsg = collapsemsg or ''
1127 f = repo.vfs("last-message.txt", "w")
1125 f = repo.vfs("last-message.txt", "w")
1128 f.write("%s\n" % collapsemsg)
1126 f.write("%s\n" % collapsemsg)
1129 f.close()
1127 f.close()
1130
1128
1131 def clearcollapsemsg(repo):
1129 def clearcollapsemsg(repo):
1132 'Remove collapse message file'
1130 'Remove collapse message file'
1133 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1131 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1134
1132
1135 def restorecollapsemsg(repo, isabort):
1133 def restorecollapsemsg(repo, isabort):
1136 'Restore previously stored collapse message'
1134 'Restore previously stored collapse message'
1137 try:
1135 try:
1138 f = repo.vfs("last-message.txt")
1136 f = repo.vfs("last-message.txt")
1139 collapsemsg = f.readline().strip()
1137 collapsemsg = f.readline().strip()
1140 f.close()
1138 f.close()
1141 except IOError as err:
1139 except IOError as err:
1142 if err.errno != errno.ENOENT:
1140 if err.errno != errno.ENOENT:
1143 raise
1141 raise
1144 if isabort:
1142 if isabort:
1145 # Oh well, just abort like normal
1143 # Oh well, just abort like normal
1146 collapsemsg = ''
1144 collapsemsg = ''
1147 else:
1145 else:
1148 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1146 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1149 return collapsemsg
1147 return collapsemsg
1150
1148
1151 def clearstatus(repo):
1149 def clearstatus(repo):
1152 'Remove the status files'
1150 'Remove the status files'
1153 _clearrebasesetvisibiliy(repo)
1151 _clearrebasesetvisibiliy(repo)
1154 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1152 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1155
1153
1156 def needupdate(repo, state):
1154 def needupdate(repo, state):
1157 '''check whether we should `update --clean` away from a merge, or if
1155 '''check whether we should `update --clean` away from a merge, or if
1158 somehow the working dir got forcibly updated, e.g. by older hg'''
1156 somehow the working dir got forcibly updated, e.g. by older hg'''
1159 parents = [p.rev() for p in repo[None].parents()]
1157 parents = [p.rev() for p in repo[None].parents()]
1160
1158
1161 # Are we in a merge state at all?
1159 # Are we in a merge state at all?
1162 if len(parents) < 2:
1160 if len(parents) < 2:
1163 return False
1161 return False
1164
1162
1165 # We should be standing on the first as-of-yet unrebased commit.
1163 # We should be standing on the first as-of-yet unrebased commit.
1166 firstunrebased = min([old for old, new in state.iteritems()
1164 firstunrebased = min([old for old, new in state.iteritems()
1167 if new == nullrev])
1165 if new == nullrev])
1168 if firstunrebased in parents:
1166 if firstunrebased in parents:
1169 return True
1167 return True
1170
1168
1171 return False
1169 return False
1172
1170
1173 def abort(repo, originalwd, target, state, activebookmark=None):
1171 def abort(repo, originalwd, target, state, activebookmark=None):
1174 '''Restore the repository to its original state. Additional args:
1172 '''Restore the repository to its original state. Additional args:
1175
1173
1176 activebookmark: the name of the bookmark that should be active after the
1174 activebookmark: the name of the bookmark that should be active after the
1177 restore'''
1175 restore'''
1178
1176
1179 try:
1177 try:
1180 # If the first commits in the rebased set get skipped during the rebase,
1178 # If the first commits in the rebased set get skipped during the rebase,
1181 # their values within the state mapping will be the target rev id. The
1179 # their values within the state mapping will be the target rev id. The
1182 # dstates list must must not contain the target rev (issue4896)
1180 # dstates list must must not contain the target rev (issue4896)
1183 dstates = [s for s in state.values() if s >= 0 and s != target]
1181 dstates = [s for s in state.values() if s >= 0 and s != target]
1184 immutable = [d for d in dstates if not repo[d].mutable()]
1182 immutable = [d for d in dstates if not repo[d].mutable()]
1185 cleanup = True
1183 cleanup = True
1186 if immutable:
1184 if immutable:
1187 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1185 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1188 % ', '.join(str(repo[r]) for r in immutable),
1186 % ', '.join(str(repo[r]) for r in immutable),
1189 hint=_("see 'hg help phases' for details"))
1187 hint=_("see 'hg help phases' for details"))
1190 cleanup = False
1188 cleanup = False
1191
1189
1192 descendants = set()
1190 descendants = set()
1193 if dstates:
1191 if dstates:
1194 descendants = set(repo.changelog.descendants(dstates))
1192 descendants = set(repo.changelog.descendants(dstates))
1195 if descendants - set(dstates):
1193 if descendants - set(dstates):
1196 repo.ui.warn(_("warning: new changesets detected on target branch, "
1194 repo.ui.warn(_("warning: new changesets detected on target branch, "
1197 "can't strip\n"))
1195 "can't strip\n"))
1198 cleanup = False
1196 cleanup = False
1199
1197
1200 if cleanup:
1198 if cleanup:
1201 shouldupdate = False
1199 shouldupdate = False
1202 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1200 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1203 if rebased:
1201 if rebased:
1204 strippoints = [
1202 strippoints = [
1205 c.node() for c in repo.set('roots(%ld)', rebased)]
1203 c.node() for c in repo.set('roots(%ld)', rebased)]
1206
1204
1207 updateifonnodes = set(rebased)
1205 updateifonnodes = set(rebased)
1208 updateifonnodes.add(target)
1206 updateifonnodes.add(target)
1209 updateifonnodes.add(originalwd)
1207 updateifonnodes.add(originalwd)
1210 shouldupdate = repo['.'].rev() in updateifonnodes
1208 shouldupdate = repo['.'].rev() in updateifonnodes
1211
1209
1212 # Update away from the rebase if necessary
1210 # Update away from the rebase if necessary
1213 if shouldupdate or needupdate(repo, state):
1211 if shouldupdate or needupdate(repo, state):
1214 mergemod.update(repo, originalwd, False, True)
1212 mergemod.update(repo, originalwd, False, True)
1215
1213
1216 # Strip from the first rebased revision
1214 # Strip from the first rebased revision
1217 if rebased:
1215 if rebased:
1218 # no backup of rebased cset versions needed
1216 # no backup of rebased cset versions needed
1219 repair.strip(repo.ui, repo, strippoints)
1217 repair.strip(repo.ui, repo, strippoints)
1220
1218
1221 if activebookmark and activebookmark in repo._bookmarks:
1219 if activebookmark and activebookmark in repo._bookmarks:
1222 bookmarks.activate(repo, activebookmark)
1220 bookmarks.activate(repo, activebookmark)
1223
1221
1224 finally:
1222 finally:
1225 clearstatus(repo)
1223 clearstatus(repo)
1226 clearcollapsemsg(repo)
1224 clearcollapsemsg(repo)
1227 repo.ui.warn(_('rebase aborted\n'))
1225 repo.ui.warn(_('rebase aborted\n'))
1228 return 0
1226 return 0
1229
1227
1230 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1228 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1231 '''Define which revisions are going to be rebased and where
1229 '''Define which revisions are going to be rebased and where
1232
1230
1233 repo: repo
1231 repo: repo
1234 dest: context
1232 dest: context
1235 rebaseset: set of rev
1233 rebaseset: set of rev
1236 '''
1234 '''
1237 originalwd = repo['.'].rev()
1235 originalwd = repo['.'].rev()
1238 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1236 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1239
1237
1240 # This check isn't strictly necessary, since mq detects commits over an
1238 # This check isn't strictly necessary, since mq detects commits over an
1241 # applied patch. But it prevents messing up the working directory when
1239 # applied patch. But it prevents messing up the working directory when
1242 # a partially completed rebase is blocked by mq.
1240 # a partially completed rebase is blocked by mq.
1243 if 'qtip' in repo.tags() and (dest.node() in
1241 if 'qtip' in repo.tags() and (dest.node() in
1244 [s.node for s in repo.mq.applied]):
1242 [s.node for s in repo.mq.applied]):
1245 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1243 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1246
1244
1247 roots = list(repo.set('roots(%ld)', rebaseset))
1245 roots = list(repo.set('roots(%ld)', rebaseset))
1248 if not roots:
1246 if not roots:
1249 raise error.Abort(_('no matching revisions'))
1247 raise error.Abort(_('no matching revisions'))
1250 roots.sort()
1248 roots.sort()
1251 state = {}
1249 state = {}
1252 detachset = set()
1250 detachset = set()
1253 for root in roots:
1251 for root in roots:
1254 commonbase = root.ancestor(dest)
1252 commonbase = root.ancestor(dest)
1255 if commonbase == root:
1253 if commonbase == root:
1256 raise error.Abort(_('source is ancestor of destination'))
1254 raise error.Abort(_('source is ancestor of destination'))
1257 if commonbase == dest:
1255 if commonbase == dest:
1258 wctx = repo[None]
1256 wctx = repo[None]
1259 if dest == wctx.p1():
1257 if dest == wctx.p1():
1260 # when rebasing to '.', it will use the current wd branch name
1258 # when rebasing to '.', it will use the current wd branch name
1261 samebranch = root.branch() == wctx.branch()
1259 samebranch = root.branch() == wctx.branch()
1262 else:
1260 else:
1263 samebranch = root.branch() == dest.branch()
1261 samebranch = root.branch() == dest.branch()
1264 if not collapse and samebranch and root in dest.children():
1262 if not collapse and samebranch and root in dest.children():
1265 repo.ui.debug('source is a child of destination\n')
1263 repo.ui.debug('source is a child of destination\n')
1266 return None
1264 return None
1267
1265
1268 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1266 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1269 state.update(dict.fromkeys(rebaseset, revtodo))
1267 state.update(dict.fromkeys(rebaseset, revtodo))
1270 # Rebase tries to turn <dest> into a parent of <root> while
1268 # Rebase tries to turn <dest> into a parent of <root> while
1271 # preserving the number of parents of rebased changesets:
1269 # preserving the number of parents of rebased changesets:
1272 #
1270 #
1273 # - A changeset with a single parent will always be rebased as a
1271 # - A changeset with a single parent will always be rebased as a
1274 # changeset with a single parent.
1272 # changeset with a single parent.
1275 #
1273 #
1276 # - A merge will be rebased as merge unless its parents are both
1274 # - A merge will be rebased as merge unless its parents are both
1277 # ancestors of <dest> or are themselves in the rebased set and
1275 # ancestors of <dest> or are themselves in the rebased set and
1278 # pruned while rebased.
1276 # pruned while rebased.
1279 #
1277 #
1280 # If one parent of <root> is an ancestor of <dest>, the rebased
1278 # If one parent of <root> is an ancestor of <dest>, the rebased
1281 # version of this parent will be <dest>. This is always true with
1279 # version of this parent will be <dest>. This is always true with
1282 # --base option.
1280 # --base option.
1283 #
1281 #
1284 # Otherwise, we need to *replace* the original parents with
1282 # Otherwise, we need to *replace* the original parents with
1285 # <dest>. This "detaches" the rebased set from its former location
1283 # <dest>. This "detaches" the rebased set from its former location
1286 # and rebases it onto <dest>. Changes introduced by ancestors of
1284 # and rebases it onto <dest>. Changes introduced by ancestors of
1287 # <root> not common with <dest> (the detachset, marked as
1285 # <root> not common with <dest> (the detachset, marked as
1288 # nullmerge) are "removed" from the rebased changesets.
1286 # nullmerge) are "removed" from the rebased changesets.
1289 #
1287 #
1290 # - If <root> has a single parent, set it to <dest>.
1288 # - If <root> has a single parent, set it to <dest>.
1291 #
1289 #
1292 # - If <root> is a merge, we cannot decide which parent to
1290 # - If <root> is a merge, we cannot decide which parent to
1293 # replace, the rebase operation is not clearly defined.
1291 # replace, the rebase operation is not clearly defined.
1294 #
1292 #
1295 # The table below sums up this behavior:
1293 # The table below sums up this behavior:
1296 #
1294 #
1297 # +------------------+----------------------+-------------------------+
1295 # +------------------+----------------------+-------------------------+
1298 # | | one parent | merge |
1296 # | | one parent | merge |
1299 # +------------------+----------------------+-------------------------+
1297 # +------------------+----------------------+-------------------------+
1300 # | parent in | new parent is <dest> | parents in ::<dest> are |
1298 # | parent in | new parent is <dest> | parents in ::<dest> are |
1301 # | ::<dest> | | remapped to <dest> |
1299 # | ::<dest> | | remapped to <dest> |
1302 # +------------------+----------------------+-------------------------+
1300 # +------------------+----------------------+-------------------------+
1303 # | unrelated source | new parent is <dest> | ambiguous, abort |
1301 # | unrelated source | new parent is <dest> | ambiguous, abort |
1304 # +------------------+----------------------+-------------------------+
1302 # +------------------+----------------------+-------------------------+
1305 #
1303 #
1306 # The actual abort is handled by `defineparents`
1304 # The actual abort is handled by `defineparents`
1307 if len(root.parents()) <= 1:
1305 if len(root.parents()) <= 1:
1308 # ancestors of <root> not ancestors of <dest>
1306 # ancestors of <root> not ancestors of <dest>
1309 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1307 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1310 [root.rev()]))
1308 [root.rev()]))
1311 for r in detachset:
1309 for r in detachset:
1312 if r not in state:
1310 if r not in state:
1313 state[r] = nullmerge
1311 state[r] = nullmerge
1314 if len(roots) > 1:
1312 if len(roots) > 1:
1315 # If we have multiple roots, we may have "hole" in the rebase set.
1313 # If we have multiple roots, we may have "hole" in the rebase set.
1316 # Rebase roots that descend from those "hole" should not be detached as
1314 # Rebase roots that descend from those "hole" should not be detached as
1317 # other root are. We use the special `revignored` to inform rebase that
1315 # other root are. We use the special `revignored` to inform rebase that
1318 # the revision should be ignored but that `defineparents` should search
1316 # the revision should be ignored but that `defineparents` should search
1319 # a rebase destination that make sense regarding rebased topology.
1317 # a rebase destination that make sense regarding rebased topology.
1320 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1318 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1321 for ignored in set(rebasedomain) - set(rebaseset):
1319 for ignored in set(rebasedomain) - set(rebaseset):
1322 state[ignored] = revignored
1320 state[ignored] = revignored
1323 for r in obsoletenotrebased:
1321 for r in obsoletenotrebased:
1324 if obsoletenotrebased[r] is None:
1322 if obsoletenotrebased[r] is None:
1325 state[r] = revpruned
1323 state[r] = revpruned
1326 else:
1324 else:
1327 state[r] = revprecursor
1325 state[r] = revprecursor
1328 return originalwd, dest.rev(), state
1326 return originalwd, dest.rev(), state
1329
1327
1330 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1328 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1331 """dispose of rebased revision at the end of the rebase
1329 """dispose of rebased revision at the end of the rebase
1332
1330
1333 If `collapsedas` is not None, the rebase was a collapse whose result if the
1331 If `collapsedas` is not None, the rebase was a collapse whose result if the
1334 `collapsedas` node."""
1332 `collapsedas` node."""
1335 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1333 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1336 markers = []
1334 markers = []
1337 for rev, newrev in sorted(state.items()):
1335 for rev, newrev in sorted(state.items()):
1338 if newrev >= 0:
1336 if newrev >= 0:
1339 if rev in skipped:
1337 if rev in skipped:
1340 succs = ()
1338 succs = ()
1341 elif collapsedas is not None:
1339 elif collapsedas is not None:
1342 succs = (repo[collapsedas],)
1340 succs = (repo[collapsedas],)
1343 else:
1341 else:
1344 succs = (repo[newrev],)
1342 succs = (repo[newrev],)
1345 markers.append((repo[rev], succs))
1343 markers.append((repo[rev], succs))
1346 if markers:
1344 if markers:
1347 obsolete.createmarkers(repo, markers)
1345 obsolete.createmarkers(repo, markers)
1348 else:
1346 else:
1349 rebased = [rev for rev in state if state[rev] > nullmerge]
1347 rebased = [rev for rev in state if state[rev] > nullmerge]
1350 if rebased:
1348 if rebased:
1351 stripped = []
1349 stripped = []
1352 for root in repo.set('roots(%ld)', rebased):
1350 for root in repo.set('roots(%ld)', rebased):
1353 if set(repo.changelog.descendants([root.rev()])) - set(state):
1351 if set(repo.changelog.descendants([root.rev()])) - set(state):
1354 ui.warn(_("warning: new changesets detected "
1352 ui.warn(_("warning: new changesets detected "
1355 "on source branch, not stripping\n"))
1353 "on source branch, not stripping\n"))
1356 else:
1354 else:
1357 stripped.append(root.node())
1355 stripped.append(root.node())
1358 if stripped:
1356 if stripped:
1359 # backup the old csets by default
1357 # backup the old csets by default
1360 repair.strip(ui, repo, stripped, "all")
1358 repair.strip(ui, repo, stripped, "all")
1361
1359
1362
1360
1363 def pullrebase(orig, ui, repo, *args, **opts):
1361 def pullrebase(orig, ui, repo, *args, **opts):
1364 'Call rebase after pull if the latter has been invoked with --rebase'
1362 'Call rebase after pull if the latter has been invoked with --rebase'
1365 ret = None
1363 ret = None
1366 if opts.get('rebase'):
1364 if opts.get('rebase'):
1367 wlock = lock = None
1365 wlock = lock = None
1368 try:
1366 try:
1369 wlock = repo.wlock()
1367 wlock = repo.wlock()
1370 lock = repo.lock()
1368 lock = repo.lock()
1371 if opts.get('update'):
1369 if opts.get('update'):
1372 del opts['update']
1370 del opts['update']
1373 ui.debug('--update and --rebase are not compatible, ignoring '
1371 ui.debug('--update and --rebase are not compatible, ignoring '
1374 'the update flag\n')
1372 'the update flag\n')
1375
1373
1376 cmdutil.checkunfinished(repo)
1374 cmdutil.checkunfinished(repo)
1377 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1375 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1378 'please commit or shelve your changes first'))
1376 'please commit or shelve your changes first'))
1379
1377
1380 revsprepull = len(repo)
1378 revsprepull = len(repo)
1381 origpostincoming = commands.postincoming
1379 origpostincoming = commands.postincoming
1382 def _dummy(*args, **kwargs):
1380 def _dummy(*args, **kwargs):
1383 pass
1381 pass
1384 commands.postincoming = _dummy
1382 commands.postincoming = _dummy
1385 try:
1383 try:
1386 ret = orig(ui, repo, *args, **opts)
1384 ret = orig(ui, repo, *args, **opts)
1387 finally:
1385 finally:
1388 commands.postincoming = origpostincoming
1386 commands.postincoming = origpostincoming
1389 revspostpull = len(repo)
1387 revspostpull = len(repo)
1390 if revspostpull > revsprepull:
1388 if revspostpull > revsprepull:
1391 # --rev option from pull conflict with rebase own --rev
1389 # --rev option from pull conflict with rebase own --rev
1392 # dropping it
1390 # dropping it
1393 if 'rev' in opts:
1391 if 'rev' in opts:
1394 del opts['rev']
1392 del opts['rev']
1395 # positional argument from pull conflicts with rebase's own
1393 # positional argument from pull conflicts with rebase's own
1396 # --source.
1394 # --source.
1397 if 'source' in opts:
1395 if 'source' in opts:
1398 del opts['source']
1396 del opts['source']
1399 # revsprepull is the len of the repo, not revnum of tip.
1397 # revsprepull is the len of the repo, not revnum of tip.
1400 destspace = list(repo.changelog.revs(start=revsprepull))
1398 destspace = list(repo.changelog.revs(start=revsprepull))
1401 opts['_destspace'] = destspace
1399 opts['_destspace'] = destspace
1402 try:
1400 try:
1403 rebase(ui, repo, **opts)
1401 rebase(ui, repo, **opts)
1404 except error.NoMergeDestAbort:
1402 except error.NoMergeDestAbort:
1405 # we can maybe update instead
1403 # we can maybe update instead
1406 rev, _a, _b = destutil.destupdate(repo)
1404 rev, _a, _b = destutil.destupdate(repo)
1407 if rev == repo['.'].rev():
1405 if rev == repo['.'].rev():
1408 ui.status(_('nothing to rebase\n'))
1406 ui.status(_('nothing to rebase\n'))
1409 else:
1407 else:
1410 ui.status(_('nothing to rebase - updating instead\n'))
1408 ui.status(_('nothing to rebase - updating instead\n'))
1411 # not passing argument to get the bare update behavior
1409 # not passing argument to get the bare update behavior
1412 # with warning and trumpets
1410 # with warning and trumpets
1413 commands.update(ui, repo)
1411 commands.update(ui, repo)
1414 finally:
1412 finally:
1415 release(lock, wlock)
1413 release(lock, wlock)
1416 else:
1414 else:
1417 if opts.get('tool'):
1415 if opts.get('tool'):
1418 raise error.Abort(_('--tool can only be used with --rebase'))
1416 raise error.Abort(_('--tool can only be used with --rebase'))
1419 ret = orig(ui, repo, *args, **opts)
1417 ret = orig(ui, repo, *args, **opts)
1420
1418
1421 return ret
1419 return ret
1422
1420
1423 def _setrebasesetvisibility(repo, revs):
1421 def _setrebasesetvisibility(repo, revs):
1424 """store the currently rebased set on the repo object
1422 """store the currently rebased set on the repo object
1425
1423
1426 This is used by another function to prevent rebased revision to because
1424 This is used by another function to prevent rebased revision to because
1427 hidden (see issue4504)"""
1425 hidden (see issue4504)"""
1428 repo = repo.unfiltered()
1426 repo = repo.unfiltered()
1429 repo._rebaseset = revs
1427 repo._rebaseset = revs
1430 # invalidate cache if visibility changes
1428 # invalidate cache if visibility changes
1431 hiddens = repo.filteredrevcache.get('visible', set())
1429 hiddens = repo.filteredrevcache.get('visible', set())
1432 if revs & hiddens:
1430 if revs & hiddens:
1433 repo.invalidatevolatilesets()
1431 repo.invalidatevolatilesets()
1434
1432
1435 def _clearrebasesetvisibiliy(repo):
1433 def _clearrebasesetvisibiliy(repo):
1436 """remove rebaseset data from the repo"""
1434 """remove rebaseset data from the repo"""
1437 repo = repo.unfiltered()
1435 repo = repo.unfiltered()
1438 if '_rebaseset' in vars(repo):
1436 if '_rebaseset' in vars(repo):
1439 del repo._rebaseset
1437 del repo._rebaseset
1440
1438
1441 def _rebasedvisible(orig, repo):
1439 def _rebasedvisible(orig, repo):
1442 """ensure rebased revs stay visible (see issue4504)"""
1440 """ensure rebased revs stay visible (see issue4504)"""
1443 blockers = orig(repo)
1441 blockers = orig(repo)
1444 blockers.update(getattr(repo, '_rebaseset', ()))
1442 blockers.update(getattr(repo, '_rebaseset', ()))
1445 return blockers
1443 return blockers
1446
1444
1447 def _filterobsoleterevs(repo, revs):
1445 def _filterobsoleterevs(repo, revs):
1448 """returns a set of the obsolete revisions in revs"""
1446 """returns a set of the obsolete revisions in revs"""
1449 return set(r for r in revs if repo[r].obsolete())
1447 return set(r for r in revs if repo[r].obsolete())
1450
1448
1451 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1449 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1452 """return a mapping obsolete => successor for all obsolete nodes to be
1450 """return a mapping obsolete => successor for all obsolete nodes to be
1453 rebased that have a successors in the destination
1451 rebased that have a successors in the destination
1454
1452
1455 obsolete => None entries in the mapping indicate nodes with no successor"""
1453 obsolete => None entries in the mapping indicate nodes with no successor"""
1456 obsoletenotrebased = {}
1454 obsoletenotrebased = {}
1457
1455
1458 # Build a mapping successor => obsolete nodes for the obsolete
1456 # Build a mapping successor => obsolete nodes for the obsolete
1459 # nodes to be rebased
1457 # nodes to be rebased
1460 allsuccessors = {}
1458 allsuccessors = {}
1461 cl = repo.changelog
1459 cl = repo.changelog
1462 for r in rebaseobsrevs:
1460 for r in rebaseobsrevs:
1463 node = cl.node(r)
1461 node = cl.node(r)
1464 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1462 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1465 try:
1463 try:
1466 allsuccessors[cl.rev(s)] = cl.rev(node)
1464 allsuccessors[cl.rev(s)] = cl.rev(node)
1467 except LookupError:
1465 except LookupError:
1468 pass
1466 pass
1469
1467
1470 if allsuccessors:
1468 if allsuccessors:
1471 # Look for successors of obsolete nodes to be rebased among
1469 # Look for successors of obsolete nodes to be rebased among
1472 # the ancestors of dest
1470 # the ancestors of dest
1473 ancs = cl.ancestors([repo[dest].rev()],
1471 ancs = cl.ancestors([repo[dest].rev()],
1474 stoprev=min(allsuccessors),
1472 stoprev=min(allsuccessors),
1475 inclusive=True)
1473 inclusive=True)
1476 for s in allsuccessors:
1474 for s in allsuccessors:
1477 if s in ancs:
1475 if s in ancs:
1478 obsoletenotrebased[allsuccessors[s]] = s
1476 obsoletenotrebased[allsuccessors[s]] = s
1479 elif (s == allsuccessors[s] and
1477 elif (s == allsuccessors[s] and
1480 allsuccessors.values().count(s) == 1):
1478 allsuccessors.values().count(s) == 1):
1481 # plain prune
1479 # plain prune
1482 obsoletenotrebased[s] = None
1480 obsoletenotrebased[s] = None
1483
1481
1484 return obsoletenotrebased
1482 return obsoletenotrebased
1485
1483
1486 def summaryhook(ui, repo):
1484 def summaryhook(ui, repo):
1487 if not repo.vfs.exists('rebasestate'):
1485 if not repo.vfs.exists('rebasestate'):
1488 return
1486 return
1489 try:
1487 try:
1490 rbsrt = rebaseruntime(repo, ui, {})
1488 rbsrt = rebaseruntime(repo, ui, {})
1491 rbsrt.restorestatus()
1489 rbsrt.restorestatus()
1492 state = rbsrt.state
1490 state = rbsrt.state
1493 except error.RepoLookupError:
1491 except error.RepoLookupError:
1494 # i18n: column positioning for "hg summary"
1492 # i18n: column positioning for "hg summary"
1495 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1493 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1496 ui.write(msg)
1494 ui.write(msg)
1497 return
1495 return
1498 numrebased = len([i for i in state.itervalues() if i >= 0])
1496 numrebased = len([i for i in state.itervalues() if i >= 0])
1499 # i18n: column positioning for "hg summary"
1497 # i18n: column positioning for "hg summary"
1500 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1498 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1501 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1499 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1502 ui.label(_('%d remaining'), 'rebase.remaining') %
1500 ui.label(_('%d remaining'), 'rebase.remaining') %
1503 (len(state) - numrebased)))
1501 (len(state) - numrebased)))
1504
1502
1505 def uisetup(ui):
1503 def uisetup(ui):
1506 #Replace pull with a decorator to provide --rebase option
1504 #Replace pull with a decorator to provide --rebase option
1507 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1505 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1508 entry[1].append(('', 'rebase', None,
1506 entry[1].append(('', 'rebase', None,
1509 _("rebase working directory to branch head")))
1507 _("rebase working directory to branch head")))
1510 entry[1].append(('t', 'tool', '',
1508 entry[1].append(('t', 'tool', '',
1511 _("specify merge tool for rebase")))
1509 _("specify merge tool for rebase")))
1512 cmdutil.summaryhooks.add('rebase', summaryhook)
1510 cmdutil.summaryhooks.add('rebase', summaryhook)
1513 cmdutil.unfinishedstates.append(
1511 cmdutil.unfinishedstates.append(
1514 ['rebasestate', False, False, _('rebase in progress'),
1512 ['rebasestate', False, False, _('rebase in progress'),
1515 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1513 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1516 cmdutil.afterresolvedstates.append(
1514 cmdutil.afterresolvedstates.append(
1517 ['rebasestate', _('hg rebase --continue')])
1515 ['rebasestate', _('hg rebase --continue')])
1518 # ensure rebased rev are not hidden
1516 # ensure rebased rev are not hidden
1519 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1517 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
@@ -1,60 +1,59
1 Require a destination
1 Require a destination
2 $ cat >> $HGRCPATH <<EOF
2 $ cat >> $HGRCPATH <<EOF
3 > [extensions]
3 > [extensions]
4 > rebase =
4 > rebase =
5 > [commands]
5 > [commands]
6 > rebase.requiredest = True
6 > rebase.requiredest = True
7 > EOF
7 > EOF
8 $ hg init repo
8 $ hg init repo
9 $ cd repo
9 $ cd repo
10 $ echo a >> a
10 $ echo a >> a
11 $ hg commit -qAm aa
11 $ hg commit -qAm aa
12 $ echo b >> b
12 $ echo b >> b
13 $ hg commit -qAm bb
13 $ hg commit -qAm bb
14 $ hg up ".^"
14 $ hg up ".^"
15 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
15 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
16 $ echo c >> c
16 $ echo c >> c
17 $ hg commit -qAm cc
17 $ hg commit -qAm cc
18 $ hg rebase
18 $ hg rebase
19 abort: you must specify a destination
19 abort: you must specify a destination
20 (use: hg rebase -d REV)
20 (use: hg rebase -d REV)
21 [255]
21 [255]
22 $ hg rebase -d 1
22 $ hg rebase -d 1
23 rebasing 2:5db65b93a12b "cc" (tip)
23 rebasing 2:5db65b93a12b "cc" (tip)
24 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5db65b93a12b-4fb789ec-backup.hg (glob)
24 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5db65b93a12b-4fb789ec-backup.hg (glob)
25 $ hg rebase -d 0 -r . -q
25 $ hg rebase -d 0 -r . -q
26 $ HGPLAIN=1 hg rebase
26 $ HGPLAIN=1 hg rebase
27 rebasing 2:889b0bc6a730 "cc" (tip)
27 rebasing 2:889b0bc6a730 "cc" (tip)
28 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/889b0bc6a730-41ec4f81-backup.hg (glob)
28 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/889b0bc6a730-41ec4f81-backup.hg (glob)
29 $ hg rebase -d 0 -r . -q
29 $ hg rebase -d 0 -r . -q
30 $ hg --config commands.rebase.requiredest=False rebase
30 $ hg --config commands.rebase.requiredest=False rebase
31 rebasing 2:279de9495438 "cc" (tip)
31 rebasing 2:279de9495438 "cc" (tip)
32 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/279de9495438-ab0a5128-backup.hg (glob)
32 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/279de9495438-ab0a5128-backup.hg (glob)
33
33
34 Requiring dest should not break continue or other rebase options
34 Requiring dest should not break continue or other rebase options
35 $ hg up 1 -q
35 $ hg up 1 -q
36 $ echo d >> c
36 $ echo d >> c
37 $ hg commit -qAm dc
37 $ hg commit -qAm dc
38 $ hg log -G -T '{rev} {desc}'
38 $ hg log -G -T '{rev} {desc}'
39 @ 3 dc
39 @ 3 dc
40 |
40 |
41 | o 2 cc
41 | o 2 cc
42 |/
42 |/
43 o 1 bb
43 o 1 bb
44 |
44 |
45 o 0 aa
45 o 0 aa
46
46
47 $ hg rebase -d 2
47 $ hg rebase -d 2
48 rebasing 3:0537f6b50def "dc" (tip)
48 rebasing 3:0537f6b50def "dc" (tip)
49 merging c
49 merging c
50 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
50 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
51 unresolved conflicts (see hg resolve, then hg rebase --continue)
51 unresolved conflicts (see hg resolve, then hg rebase --continue)
52 [1]
52 [1]
53 $ echo d > c
53 $ echo d > c
54 $ hg resolve --mark --all
54 $ hg resolve --mark --all
55 (no more unresolved files)
55 (no more unresolved files)
56 continue: hg rebase --continue
56 continue: hg rebase --continue
57 $ hg rebase --continue
57 $ hg rebase --continue
58 abort: you must specify a destination
58 rebasing 3:0537f6b50def "dc" (tip)
59 (use: hg rebase -d REV)
59 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/0537f6b50def-be4c7386-backup.hg (glob)
60 [255]
General Comments 0
You need to be logged in to leave comments. Login now