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