##// END OF EJS Templates
rebase: split _origrebase() for conveniece in dryrun...
Sushil khanchi -
r38516:9c3b48fb default
parent child Browse files
Show More
@@ -1,1884 +1,1884
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 nullrev,
24 nullrev,
25 short,
25 short,
26 )
26 )
27 from mercurial import (
27 from mercurial import (
28 bookmarks,
28 bookmarks,
29 cmdutil,
29 cmdutil,
30 commands,
30 commands,
31 copies,
31 copies,
32 destutil,
32 destutil,
33 dirstateguard,
33 dirstateguard,
34 error,
34 error,
35 extensions,
35 extensions,
36 hg,
36 hg,
37 lock,
37 lock,
38 merge as mergemod,
38 merge as mergemod,
39 mergeutil,
39 mergeutil,
40 obsolete,
40 obsolete,
41 obsutil,
41 obsutil,
42 patch,
42 patch,
43 phases,
43 phases,
44 pycompat,
44 pycompat,
45 registrar,
45 registrar,
46 repair,
46 repair,
47 revset,
47 revset,
48 revsetlang,
48 revsetlang,
49 scmutil,
49 scmutil,
50 smartset,
50 smartset,
51 util,
51 util,
52 )
52 )
53
53
54 release = lock.release
54 release = lock.release
55
55
56 # The following constants are used throughout the rebase module. The ordering of
56 # The following constants are used throughout the rebase module. The ordering of
57 # their values must be maintained.
57 # their values must be maintained.
58
58
59 # Indicates that a revision needs to be rebased
59 # Indicates that a revision needs to be rebased
60 revtodo = -1
60 revtodo = -1
61 revtodostr = '-1'
61 revtodostr = '-1'
62
62
63 # legacy revstates no longer needed in current code
63 # legacy revstates no longer needed in current code
64 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
64 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
65 legacystates = {'-2', '-3', '-4', '-5'}
65 legacystates = {'-2', '-3', '-4', '-5'}
66
66
67 cmdtable = {}
67 cmdtable = {}
68 command = registrar.command(cmdtable)
68 command = registrar.command(cmdtable)
69 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
69 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
70 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
70 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
71 # be specifying the version(s) of Mercurial they are tested with, or
71 # be specifying the version(s) of Mercurial they are tested with, or
72 # leave the attribute unspecified.
72 # leave the attribute unspecified.
73 testedwith = 'ships-with-hg-core'
73 testedwith = 'ships-with-hg-core'
74
74
75 def _nothingtorebase():
75 def _nothingtorebase():
76 return 1
76 return 1
77
77
78 def _savegraft(ctx, extra):
78 def _savegraft(ctx, extra):
79 s = ctx.extra().get('source', None)
79 s = ctx.extra().get('source', None)
80 if s is not None:
80 if s is not None:
81 extra['source'] = s
81 extra['source'] = s
82 s = ctx.extra().get('intermediate-source', None)
82 s = ctx.extra().get('intermediate-source', None)
83 if s is not None:
83 if s is not None:
84 extra['intermediate-source'] = s
84 extra['intermediate-source'] = s
85
85
86 def _savebranch(ctx, extra):
86 def _savebranch(ctx, extra):
87 extra['branch'] = ctx.branch()
87 extra['branch'] = ctx.branch()
88
88
89 def _destrebase(repo, sourceset, destspace=None):
89 def _destrebase(repo, sourceset, destspace=None):
90 """small wrapper around destmerge to pass the right extra args
90 """small wrapper around destmerge to pass the right extra args
91
91
92 Please wrap destutil.destmerge instead."""
92 Please wrap destutil.destmerge instead."""
93 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
93 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
94 onheadcheck=False, destspace=destspace)
94 onheadcheck=False, destspace=destspace)
95
95
96 revsetpredicate = registrar.revsetpredicate()
96 revsetpredicate = registrar.revsetpredicate()
97
97
98 @revsetpredicate('_destrebase')
98 @revsetpredicate('_destrebase')
99 def _revsetdestrebase(repo, subset, x):
99 def _revsetdestrebase(repo, subset, x):
100 # ``_rebasedefaultdest()``
100 # ``_rebasedefaultdest()``
101
101
102 # default destination for rebase.
102 # default destination for rebase.
103 # # XXX: Currently private because I expect the signature to change.
103 # # XXX: Currently private because I expect the signature to change.
104 # # XXX: - bailing out in case of ambiguity vs returning all data.
104 # # XXX: - bailing out in case of ambiguity vs returning all data.
105 # i18n: "_rebasedefaultdest" is a keyword
105 # i18n: "_rebasedefaultdest" is a keyword
106 sourceset = None
106 sourceset = None
107 if x is not None:
107 if x is not None:
108 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
108 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
109 return subset & smartset.baseset([_destrebase(repo, sourceset)])
109 return subset & smartset.baseset([_destrebase(repo, sourceset)])
110
110
111 @revsetpredicate('_destautoorphanrebase')
111 @revsetpredicate('_destautoorphanrebase')
112 def _revsetdestautoorphanrebase(repo, subset, x):
112 def _revsetdestautoorphanrebase(repo, subset, x):
113 """automatic rebase destination for a single orphan revision"""
113 """automatic rebase destination for a single orphan revision"""
114 unfi = repo.unfiltered()
114 unfi = repo.unfiltered()
115 obsoleted = unfi.revs('obsolete()')
115 obsoleted = unfi.revs('obsolete()')
116
116
117 src = revset.getset(repo, subset, x).first()
117 src = revset.getset(repo, subset, x).first()
118
118
119 # Empty src or already obsoleted - Do not return a destination
119 # Empty src or already obsoleted - Do not return a destination
120 if not src or src in obsoleted:
120 if not src or src in obsoleted:
121 return smartset.baseset()
121 return smartset.baseset()
122 dests = destutil.orphanpossibledestination(repo, src)
122 dests = destutil.orphanpossibledestination(repo, src)
123 if len(dests) > 1:
123 if len(dests) > 1:
124 raise error.Abort(
124 raise error.Abort(
125 _("ambiguous automatic rebase: %r could end up on any of %r") % (
125 _("ambiguous automatic rebase: %r could end up on any of %r") % (
126 src, dests))
126 src, dests))
127 # We have zero or one destination, so we can just return here.
127 # We have zero or one destination, so we can just return here.
128 return smartset.baseset(dests)
128 return smartset.baseset(dests)
129
129
130 def _ctxdesc(ctx):
130 def _ctxdesc(ctx):
131 """short description for a context"""
131 """short description for a context"""
132 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
132 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
133 ctx.description().split('\n', 1)[0])
133 ctx.description().split('\n', 1)[0])
134 repo = ctx.repo()
134 repo = ctx.repo()
135 names = []
135 names = []
136 for nsname, ns in repo.names.iteritems():
136 for nsname, ns in repo.names.iteritems():
137 if nsname == 'branches':
137 if nsname == 'branches':
138 continue
138 continue
139 names.extend(ns.names(repo, ctx.node()))
139 names.extend(ns.names(repo, ctx.node()))
140 if names:
140 if names:
141 desc += ' (%s)' % ' '.join(names)
141 desc += ' (%s)' % ' '.join(names)
142 return desc
142 return desc
143
143
144 class rebaseruntime(object):
144 class rebaseruntime(object):
145 """This class is a container for rebase runtime state"""
145 """This class is a container for rebase runtime state"""
146 def __init__(self, repo, ui, inmemory=False, opts=None):
146 def __init__(self, repo, ui, inmemory=False, opts=None):
147 if opts is None:
147 if opts is None:
148 opts = {}
148 opts = {}
149
149
150 # prepared: whether we have rebasestate prepared or not. Currently it
150 # prepared: whether we have rebasestate prepared or not. Currently it
151 # decides whether "self.repo" is unfiltered or not.
151 # decides whether "self.repo" is unfiltered or not.
152 # The rebasestate has explicit hash to hash instructions not depending
152 # The rebasestate has explicit hash to hash instructions not depending
153 # on visibility. If rebasestate exists (in-memory or on-disk), use
153 # on visibility. If rebasestate exists (in-memory or on-disk), use
154 # unfiltered repo to avoid visibility issues.
154 # unfiltered repo to avoid visibility issues.
155 # Before knowing rebasestate (i.e. when starting a new rebase (not
155 # Before knowing rebasestate (i.e. when starting a new rebase (not
156 # --continue or --abort)), the original repo should be used so
156 # --continue or --abort)), the original repo should be used so
157 # visibility-dependent revsets are correct.
157 # visibility-dependent revsets are correct.
158 self.prepared = False
158 self.prepared = False
159 self._repo = repo
159 self._repo = repo
160
160
161 self.ui = ui
161 self.ui = ui
162 self.opts = opts
162 self.opts = opts
163 self.originalwd = None
163 self.originalwd = None
164 self.external = nullrev
164 self.external = nullrev
165 # Mapping between the old revision id and either what is the new rebased
165 # Mapping between the old revision id and either what is the new rebased
166 # revision or what needs to be done with the old revision. The state
166 # revision or what needs to be done with the old revision. The state
167 # dict will be what contains most of the rebase progress state.
167 # dict will be what contains most of the rebase progress state.
168 self.state = {}
168 self.state = {}
169 self.activebookmark = None
169 self.activebookmark = None
170 self.destmap = {}
170 self.destmap = {}
171 self.skipped = set()
171 self.skipped = set()
172
172
173 self.collapsef = opts.get('collapse', False)
173 self.collapsef = opts.get('collapse', False)
174 self.collapsemsg = cmdutil.logmessage(ui, opts)
174 self.collapsemsg = cmdutil.logmessage(ui, opts)
175 self.date = opts.get('date', None)
175 self.date = opts.get('date', None)
176
176
177 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
177 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
178 self.extrafns = [_savegraft]
178 self.extrafns = [_savegraft]
179 if e:
179 if e:
180 self.extrafns = [e]
180 self.extrafns = [e]
181
181
182 self.keepf = opts.get('keep', False)
182 self.keepf = opts.get('keep', False)
183 self.keepbranchesf = opts.get('keepbranches', False)
183 self.keepbranchesf = opts.get('keepbranches', False)
184 self.obsoletenotrebased = {}
184 self.obsoletenotrebased = {}
185 self.obsoletewithoutsuccessorindestination = set()
185 self.obsoletewithoutsuccessorindestination = set()
186 self.inmemory = inmemory
186 self.inmemory = inmemory
187
187
188 @property
188 @property
189 def repo(self):
189 def repo(self):
190 if self.prepared:
190 if self.prepared:
191 return self._repo.unfiltered()
191 return self._repo.unfiltered()
192 else:
192 else:
193 return self._repo
193 return self._repo
194
194
195 def storestatus(self, tr=None):
195 def storestatus(self, tr=None):
196 """Store the current status to allow recovery"""
196 """Store the current status to allow recovery"""
197 if tr:
197 if tr:
198 tr.addfilegenerator('rebasestate', ('rebasestate',),
198 tr.addfilegenerator('rebasestate', ('rebasestate',),
199 self._writestatus, location='plain')
199 self._writestatus, location='plain')
200 else:
200 else:
201 with self.repo.vfs("rebasestate", "w") as f:
201 with self.repo.vfs("rebasestate", "w") as f:
202 self._writestatus(f)
202 self._writestatus(f)
203
203
204 def _writestatus(self, f):
204 def _writestatus(self, f):
205 repo = self.repo
205 repo = self.repo
206 assert repo.filtername is None
206 assert repo.filtername is None
207 f.write(repo[self.originalwd].hex() + '\n')
207 f.write(repo[self.originalwd].hex() + '\n')
208 # was "dest". we now write dest per src root below.
208 # was "dest". we now write dest per src root below.
209 f.write('\n')
209 f.write('\n')
210 f.write(repo[self.external].hex() + '\n')
210 f.write(repo[self.external].hex() + '\n')
211 f.write('%d\n' % int(self.collapsef))
211 f.write('%d\n' % int(self.collapsef))
212 f.write('%d\n' % int(self.keepf))
212 f.write('%d\n' % int(self.keepf))
213 f.write('%d\n' % int(self.keepbranchesf))
213 f.write('%d\n' % int(self.keepbranchesf))
214 f.write('%s\n' % (self.activebookmark or ''))
214 f.write('%s\n' % (self.activebookmark or ''))
215 destmap = self.destmap
215 destmap = self.destmap
216 for d, v in self.state.iteritems():
216 for d, v in self.state.iteritems():
217 oldrev = repo[d].hex()
217 oldrev = repo[d].hex()
218 if v >= 0:
218 if v >= 0:
219 newrev = repo[v].hex()
219 newrev = repo[v].hex()
220 else:
220 else:
221 newrev = "%d" % v
221 newrev = "%d" % v
222 destnode = repo[destmap[d]].hex()
222 destnode = repo[destmap[d]].hex()
223 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
223 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
224 repo.ui.debug('rebase status stored\n')
224 repo.ui.debug('rebase status stored\n')
225
225
226 def restorestatus(self):
226 def restorestatus(self):
227 """Restore a previously stored status"""
227 """Restore a previously stored status"""
228 self.prepared = True
228 self.prepared = True
229 repo = self.repo
229 repo = self.repo
230 assert repo.filtername is None
230 assert repo.filtername is None
231 keepbranches = None
231 keepbranches = None
232 legacydest = None
232 legacydest = None
233 collapse = False
233 collapse = False
234 external = nullrev
234 external = nullrev
235 activebookmark = None
235 activebookmark = None
236 state = {}
236 state = {}
237 destmap = {}
237 destmap = {}
238
238
239 try:
239 try:
240 f = repo.vfs("rebasestate")
240 f = repo.vfs("rebasestate")
241 for i, l in enumerate(f.read().splitlines()):
241 for i, l in enumerate(f.read().splitlines()):
242 if i == 0:
242 if i == 0:
243 originalwd = repo[l].rev()
243 originalwd = repo[l].rev()
244 elif i == 1:
244 elif i == 1:
245 # this line should be empty in newer version. but legacy
245 # this line should be empty in newer version. but legacy
246 # clients may still use it
246 # clients may still use it
247 if l:
247 if l:
248 legacydest = repo[l].rev()
248 legacydest = repo[l].rev()
249 elif i == 2:
249 elif i == 2:
250 external = repo[l].rev()
250 external = repo[l].rev()
251 elif i == 3:
251 elif i == 3:
252 collapse = bool(int(l))
252 collapse = bool(int(l))
253 elif i == 4:
253 elif i == 4:
254 keep = bool(int(l))
254 keep = bool(int(l))
255 elif i == 5:
255 elif i == 5:
256 keepbranches = bool(int(l))
256 keepbranches = bool(int(l))
257 elif i == 6 and not (len(l) == 81 and ':' in l):
257 elif i == 6 and not (len(l) == 81 and ':' in l):
258 # line 6 is a recent addition, so for backwards
258 # line 6 is a recent addition, so for backwards
259 # compatibility check that the line doesn't look like the
259 # compatibility check that the line doesn't look like the
260 # oldrev:newrev lines
260 # oldrev:newrev lines
261 activebookmark = l
261 activebookmark = l
262 else:
262 else:
263 args = l.split(':')
263 args = l.split(':')
264 oldrev = repo[args[0]].rev()
264 oldrev = repo[args[0]].rev()
265 newrev = args[1]
265 newrev = args[1]
266 if newrev in legacystates:
266 if newrev in legacystates:
267 continue
267 continue
268 if len(args) > 2:
268 if len(args) > 2:
269 destrev = repo[args[2]].rev()
269 destrev = repo[args[2]].rev()
270 else:
270 else:
271 destrev = legacydest
271 destrev = legacydest
272 destmap[oldrev] = destrev
272 destmap[oldrev] = destrev
273 if newrev == revtodostr:
273 if newrev == revtodostr:
274 state[oldrev] = revtodo
274 state[oldrev] = revtodo
275 # Legacy compat special case
275 # Legacy compat special case
276 else:
276 else:
277 state[oldrev] = repo[newrev].rev()
277 state[oldrev] = repo[newrev].rev()
278
278
279 except IOError as err:
279 except IOError as err:
280 if err.errno != errno.ENOENT:
280 if err.errno != errno.ENOENT:
281 raise
281 raise
282 cmdutil.wrongtooltocontinue(repo, _('rebase'))
282 cmdutil.wrongtooltocontinue(repo, _('rebase'))
283
283
284 if keepbranches is None:
284 if keepbranches is None:
285 raise error.Abort(_('.hg/rebasestate is incomplete'))
285 raise error.Abort(_('.hg/rebasestate is incomplete'))
286
286
287 skipped = set()
287 skipped = set()
288 # recompute the set of skipped revs
288 # recompute the set of skipped revs
289 if not collapse:
289 if not collapse:
290 seen = set(destmap.values())
290 seen = set(destmap.values())
291 for old, new in sorted(state.items()):
291 for old, new in sorted(state.items()):
292 if new != revtodo and new in seen:
292 if new != revtodo and new in seen:
293 skipped.add(old)
293 skipped.add(old)
294 seen.add(new)
294 seen.add(new)
295 repo.ui.debug('computed skipped revs: %s\n' %
295 repo.ui.debug('computed skipped revs: %s\n' %
296 (' '.join('%d' % r for r in sorted(skipped)) or ''))
296 (' '.join('%d' % r for r in sorted(skipped)) or ''))
297 repo.ui.debug('rebase status resumed\n')
297 repo.ui.debug('rebase status resumed\n')
298
298
299 self.originalwd = originalwd
299 self.originalwd = originalwd
300 self.destmap = destmap
300 self.destmap = destmap
301 self.state = state
301 self.state = state
302 self.skipped = skipped
302 self.skipped = skipped
303 self.collapsef = collapse
303 self.collapsef = collapse
304 self.keepf = keep
304 self.keepf = keep
305 self.keepbranchesf = keepbranches
305 self.keepbranchesf = keepbranches
306 self.external = external
306 self.external = external
307 self.activebookmark = activebookmark
307 self.activebookmark = activebookmark
308
308
309 def _handleskippingobsolete(self, obsoleterevs, destmap):
309 def _handleskippingobsolete(self, obsoleterevs, destmap):
310 """Compute structures necessary for skipping obsolete revisions
310 """Compute structures necessary for skipping obsolete revisions
311
311
312 obsoleterevs: iterable of all obsolete revisions in rebaseset
312 obsoleterevs: iterable of all obsolete revisions in rebaseset
313 destmap: {srcrev: destrev} destination revisions
313 destmap: {srcrev: destrev} destination revisions
314 """
314 """
315 self.obsoletenotrebased = {}
315 self.obsoletenotrebased = {}
316 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
316 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
317 return
317 return
318 obsoleteset = set(obsoleterevs)
318 obsoleteset = set(obsoleterevs)
319 (self.obsoletenotrebased,
319 (self.obsoletenotrebased,
320 self.obsoletewithoutsuccessorindestination,
320 self.obsoletewithoutsuccessorindestination,
321 obsoleteextinctsuccessors) = _computeobsoletenotrebased(
321 obsoleteextinctsuccessors) = _computeobsoletenotrebased(
322 self.repo, obsoleteset, destmap)
322 self.repo, obsoleteset, destmap)
323 skippedset = set(self.obsoletenotrebased)
323 skippedset = set(self.obsoletenotrebased)
324 skippedset.update(self.obsoletewithoutsuccessorindestination)
324 skippedset.update(self.obsoletewithoutsuccessorindestination)
325 skippedset.update(obsoleteextinctsuccessors)
325 skippedset.update(obsoleteextinctsuccessors)
326 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
326 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
327
327
328 def _prepareabortorcontinue(self, isabort):
328 def _prepareabortorcontinue(self, isabort):
329 try:
329 try:
330 self.restorestatus()
330 self.restorestatus()
331 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
331 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
332 except error.RepoLookupError:
332 except error.RepoLookupError:
333 if isabort:
333 if isabort:
334 clearstatus(self.repo)
334 clearstatus(self.repo)
335 clearcollapsemsg(self.repo)
335 clearcollapsemsg(self.repo)
336 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
336 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
337 ' only broken state is cleared)\n'))
337 ' only broken state is cleared)\n'))
338 return 0
338 return 0
339 else:
339 else:
340 msg = _('cannot continue inconsistent rebase')
340 msg = _('cannot continue inconsistent rebase')
341 hint = _('use "hg rebase --abort" to clear broken state')
341 hint = _('use "hg rebase --abort" to clear broken state')
342 raise error.Abort(msg, hint=hint)
342 raise error.Abort(msg, hint=hint)
343 if isabort:
343 if isabort:
344 return abort(self.repo, self.originalwd, self.destmap,
344 return abort(self.repo, self.originalwd, self.destmap,
345 self.state, activebookmark=self.activebookmark)
345 self.state, activebookmark=self.activebookmark)
346
346
347 def _preparenewrebase(self, destmap):
347 def _preparenewrebase(self, destmap):
348 if not destmap:
348 if not destmap:
349 return _nothingtorebase()
349 return _nothingtorebase()
350
350
351 rebaseset = destmap.keys()
351 rebaseset = destmap.keys()
352 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
352 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
353 if (not (self.keepf or allowunstable)
353 if (not (self.keepf or allowunstable)
354 and self.repo.revs('first(children(%ld) - %ld)',
354 and self.repo.revs('first(children(%ld) - %ld)',
355 rebaseset, rebaseset)):
355 rebaseset, rebaseset)):
356 raise error.Abort(
356 raise error.Abort(
357 _("can't remove original changesets with"
357 _("can't remove original changesets with"
358 " unrebased descendants"),
358 " unrebased descendants"),
359 hint=_('use --keep to keep original changesets'))
359 hint=_('use --keep to keep original changesets'))
360
360
361 result = buildstate(self.repo, destmap, self.collapsef)
361 result = buildstate(self.repo, destmap, self.collapsef)
362
362
363 if not result:
363 if not result:
364 # Empty state built, nothing to rebase
364 # Empty state built, nothing to rebase
365 self.ui.status(_('nothing to rebase\n'))
365 self.ui.status(_('nothing to rebase\n'))
366 return _nothingtorebase()
366 return _nothingtorebase()
367
367
368 for root in self.repo.set('roots(%ld)', rebaseset):
368 for root in self.repo.set('roots(%ld)', rebaseset):
369 if not self.keepf and not root.mutable():
369 if not self.keepf and not root.mutable():
370 raise error.Abort(_("can't rebase public changeset %s")
370 raise error.Abort(_("can't rebase public changeset %s")
371 % root,
371 % root,
372 hint=_("see 'hg help phases' for details"))
372 hint=_("see 'hg help phases' for details"))
373
373
374 (self.originalwd, self.destmap, self.state) = result
374 (self.originalwd, self.destmap, self.state) = result
375 if self.collapsef:
375 if self.collapsef:
376 dests = set(self.destmap.values())
376 dests = set(self.destmap.values())
377 if len(dests) != 1:
377 if len(dests) != 1:
378 raise error.Abort(
378 raise error.Abort(
379 _('--collapse does not work with multiple destinations'))
379 _('--collapse does not work with multiple destinations'))
380 destrev = next(iter(dests))
380 destrev = next(iter(dests))
381 destancestors = self.repo.changelog.ancestors([destrev],
381 destancestors = self.repo.changelog.ancestors([destrev],
382 inclusive=True)
382 inclusive=True)
383 self.external = externalparent(self.repo, self.state, destancestors)
383 self.external = externalparent(self.repo, self.state, destancestors)
384
384
385 for destrev in sorted(set(destmap.values())):
385 for destrev in sorted(set(destmap.values())):
386 dest = self.repo[destrev]
386 dest = self.repo[destrev]
387 if dest.closesbranch() and not self.keepbranchesf:
387 if dest.closesbranch() and not self.keepbranchesf:
388 self.ui.status(_('reopening closed branch head %s\n') % dest)
388 self.ui.status(_('reopening closed branch head %s\n') % dest)
389
389
390 self.prepared = True
390 self.prepared = True
391
391
392 def _assignworkingcopy(self):
392 def _assignworkingcopy(self):
393 if self.inmemory:
393 if self.inmemory:
394 from mercurial.context import overlayworkingctx
394 from mercurial.context import overlayworkingctx
395 self.wctx = overlayworkingctx(self.repo)
395 self.wctx = overlayworkingctx(self.repo)
396 self.repo.ui.debug("rebasing in-memory\n")
396 self.repo.ui.debug("rebasing in-memory\n")
397 else:
397 else:
398 self.wctx = self.repo[None]
398 self.wctx = self.repo[None]
399 self.repo.ui.debug("rebasing on disk\n")
399 self.repo.ui.debug("rebasing on disk\n")
400 self.repo.ui.log("rebase", "", rebase_imm_used=self.inmemory)
400 self.repo.ui.log("rebase", "", rebase_imm_used=self.inmemory)
401
401
402 def _performrebase(self, tr):
402 def _performrebase(self, tr):
403 self._assignworkingcopy()
403 self._assignworkingcopy()
404 repo, ui = self.repo, self.ui
404 repo, ui = self.repo, self.ui
405 if self.keepbranchesf:
405 if self.keepbranchesf:
406 # insert _savebranch at the start of extrafns so if
406 # insert _savebranch at the start of extrafns so if
407 # there's a user-provided extrafn it can clobber branch if
407 # there's a user-provided extrafn it can clobber branch if
408 # desired
408 # desired
409 self.extrafns.insert(0, _savebranch)
409 self.extrafns.insert(0, _savebranch)
410 if self.collapsef:
410 if self.collapsef:
411 branches = set()
411 branches = set()
412 for rev in self.state:
412 for rev in self.state:
413 branches.add(repo[rev].branch())
413 branches.add(repo[rev].branch())
414 if len(branches) > 1:
414 if len(branches) > 1:
415 raise error.Abort(_('cannot collapse multiple named '
415 raise error.Abort(_('cannot collapse multiple named '
416 'branches'))
416 'branches'))
417
417
418 # Calculate self.obsoletenotrebased
418 # Calculate self.obsoletenotrebased
419 obsrevs = _filterobsoleterevs(self.repo, self.state)
419 obsrevs = _filterobsoleterevs(self.repo, self.state)
420 self._handleskippingobsolete(obsrevs, self.destmap)
420 self._handleskippingobsolete(obsrevs, self.destmap)
421
421
422 # Keep track of the active bookmarks in order to reset them later
422 # Keep track of the active bookmarks in order to reset them later
423 self.activebookmark = self.activebookmark or repo._activebookmark
423 self.activebookmark = self.activebookmark or repo._activebookmark
424 if self.activebookmark:
424 if self.activebookmark:
425 bookmarks.deactivate(repo)
425 bookmarks.deactivate(repo)
426
426
427 # Store the state before we begin so users can run 'hg rebase --abort'
427 # Store the state before we begin so users can run 'hg rebase --abort'
428 # if we fail before the transaction closes.
428 # if we fail before the transaction closes.
429 self.storestatus()
429 self.storestatus()
430 if tr:
430 if tr:
431 # When using single transaction, store state when transaction
431 # When using single transaction, store state when transaction
432 # commits.
432 # commits.
433 self.storestatus(tr)
433 self.storestatus(tr)
434
434
435 cands = [k for k, v in self.state.iteritems() if v == revtodo]
435 cands = [k for k, v in self.state.iteritems() if v == revtodo]
436 p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
436 p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
437 total=len(cands))
437 total=len(cands))
438 def progress(ctx):
438 def progress(ctx):
439 p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
439 p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
440 allowdivergence = self.ui.configbool(
440 allowdivergence = self.ui.configbool(
441 'experimental', 'evolution.allowdivergence')
441 'experimental', 'evolution.allowdivergence')
442 for subset in sortsource(self.destmap):
442 for subset in sortsource(self.destmap):
443 sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
443 sortedrevs = self.repo.revs('sort(%ld, -topo)', subset)
444 if not allowdivergence:
444 if not allowdivergence:
445 sortedrevs -= self.repo.revs(
445 sortedrevs -= self.repo.revs(
446 'descendants(%ld) and not %ld',
446 'descendants(%ld) and not %ld',
447 self.obsoletewithoutsuccessorindestination,
447 self.obsoletewithoutsuccessorindestination,
448 self.obsoletewithoutsuccessorindestination,
448 self.obsoletewithoutsuccessorindestination,
449 )
449 )
450 for rev in sortedrevs:
450 for rev in sortedrevs:
451 self._rebasenode(tr, rev, allowdivergence, progress)
451 self._rebasenode(tr, rev, allowdivergence, progress)
452 p.complete()
452 p.complete()
453 ui.note(_('rebase merging completed\n'))
453 ui.note(_('rebase merging completed\n'))
454
454
455 def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
455 def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
456 '''Commit the wd changes with parents p1 and p2.
456 '''Commit the wd changes with parents p1 and p2.
457
457
458 Reuse commit info from rev but also store useful information in extra.
458 Reuse commit info from rev but also store useful information in extra.
459 Return node of committed revision.'''
459 Return node of committed revision.'''
460 repo = self.repo
460 repo = self.repo
461 ctx = repo[rev]
461 ctx = repo[rev]
462 if commitmsg is None:
462 if commitmsg is None:
463 commitmsg = ctx.description()
463 commitmsg = ctx.description()
464 date = self.date
464 date = self.date
465 if date is None:
465 if date is None:
466 date = ctx.date()
466 date = ctx.date()
467 extra = {'rebase_source': ctx.hex()}
467 extra = {'rebase_source': ctx.hex()}
468 for c in self.extrafns:
468 for c in self.extrafns:
469 c(ctx, extra)
469 c(ctx, extra)
470 keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
470 keepbranch = self.keepbranchesf and repo[p1].branch() != ctx.branch()
471 destphase = max(ctx.phase(), phases.draft)
471 destphase = max(ctx.phase(), phases.draft)
472 overrides = {('phases', 'new-commit'): destphase}
472 overrides = {('phases', 'new-commit'): destphase}
473 if keepbranch:
473 if keepbranch:
474 overrides[('ui', 'allowemptycommit')] = True
474 overrides[('ui', 'allowemptycommit')] = True
475 with repo.ui.configoverride(overrides, 'rebase'):
475 with repo.ui.configoverride(overrides, 'rebase'):
476 if self.inmemory:
476 if self.inmemory:
477 newnode = commitmemorynode(repo, p1, p2,
477 newnode = commitmemorynode(repo, p1, p2,
478 wctx=self.wctx,
478 wctx=self.wctx,
479 extra=extra,
479 extra=extra,
480 commitmsg=commitmsg,
480 commitmsg=commitmsg,
481 editor=editor,
481 editor=editor,
482 user=ctx.user(),
482 user=ctx.user(),
483 date=date)
483 date=date)
484 mergemod.mergestate.clean(repo)
484 mergemod.mergestate.clean(repo)
485 else:
485 else:
486 newnode = commitnode(repo, p1, p2,
486 newnode = commitnode(repo, p1, p2,
487 extra=extra,
487 extra=extra,
488 commitmsg=commitmsg,
488 commitmsg=commitmsg,
489 editor=editor,
489 editor=editor,
490 user=ctx.user(),
490 user=ctx.user(),
491 date=date)
491 date=date)
492
492
493 if newnode is None:
493 if newnode is None:
494 # If it ended up being a no-op commit, then the normal
494 # If it ended up being a no-op commit, then the normal
495 # merge state clean-up path doesn't happen, so do it
495 # merge state clean-up path doesn't happen, so do it
496 # here. Fix issue5494
496 # here. Fix issue5494
497 mergemod.mergestate.clean(repo)
497 mergemod.mergestate.clean(repo)
498 return newnode
498 return newnode
499
499
500 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
500 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
501 repo, ui, opts = self.repo, self.ui, self.opts
501 repo, ui, opts = self.repo, self.ui, self.opts
502 dest = self.destmap[rev]
502 dest = self.destmap[rev]
503 ctx = repo[rev]
503 ctx = repo[rev]
504 desc = _ctxdesc(ctx)
504 desc = _ctxdesc(ctx)
505 if self.state[rev] == rev:
505 if self.state[rev] == rev:
506 ui.status(_('already rebased %s\n') % desc)
506 ui.status(_('already rebased %s\n') % desc)
507 elif (not allowdivergence
507 elif (not allowdivergence
508 and rev in self.obsoletewithoutsuccessorindestination):
508 and rev in self.obsoletewithoutsuccessorindestination):
509 msg = _('note: not rebasing %s and its descendants as '
509 msg = _('note: not rebasing %s and its descendants as '
510 'this would cause divergence\n') % desc
510 'this would cause divergence\n') % desc
511 repo.ui.status(msg)
511 repo.ui.status(msg)
512 self.skipped.add(rev)
512 self.skipped.add(rev)
513 elif rev in self.obsoletenotrebased:
513 elif rev in self.obsoletenotrebased:
514 succ = self.obsoletenotrebased[rev]
514 succ = self.obsoletenotrebased[rev]
515 if succ is None:
515 if succ is None:
516 msg = _('note: not rebasing %s, it has no '
516 msg = _('note: not rebasing %s, it has no '
517 'successor\n') % desc
517 'successor\n') % desc
518 else:
518 else:
519 succdesc = _ctxdesc(repo[succ])
519 succdesc = _ctxdesc(repo[succ])
520 msg = (_('note: not rebasing %s, already in '
520 msg = (_('note: not rebasing %s, already in '
521 'destination as %s\n') % (desc, succdesc))
521 'destination as %s\n') % (desc, succdesc))
522 repo.ui.status(msg)
522 repo.ui.status(msg)
523 # Make clearrebased aware state[rev] is not a true successor
523 # Make clearrebased aware state[rev] is not a true successor
524 self.skipped.add(rev)
524 self.skipped.add(rev)
525 # Record rev as moved to its desired destination in self.state.
525 # Record rev as moved to its desired destination in self.state.
526 # This helps bookmark and working parent movement.
526 # This helps bookmark and working parent movement.
527 dest = max(adjustdest(repo, rev, self.destmap, self.state,
527 dest = max(adjustdest(repo, rev, self.destmap, self.state,
528 self.skipped))
528 self.skipped))
529 self.state[rev] = dest
529 self.state[rev] = dest
530 elif self.state[rev] == revtodo:
530 elif self.state[rev] == revtodo:
531 ui.status(_('rebasing %s\n') % desc)
531 ui.status(_('rebasing %s\n') % desc)
532 progressfn(ctx)
532 progressfn(ctx)
533 p1, p2, base = defineparents(repo, rev, self.destmap,
533 p1, p2, base = defineparents(repo, rev, self.destmap,
534 self.state, self.skipped,
534 self.state, self.skipped,
535 self.obsoletenotrebased)
535 self.obsoletenotrebased)
536 if len(repo[None].parents()) == 2:
536 if len(repo[None].parents()) == 2:
537 repo.ui.debug('resuming interrupted rebase\n')
537 repo.ui.debug('resuming interrupted rebase\n')
538 else:
538 else:
539 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
539 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
540 with ui.configoverride(overrides, 'rebase'):
540 with ui.configoverride(overrides, 'rebase'):
541 stats = rebasenode(repo, rev, p1, base, self.collapsef,
541 stats = rebasenode(repo, rev, p1, base, self.collapsef,
542 dest, wctx=self.wctx)
542 dest, wctx=self.wctx)
543 if stats.unresolvedcount > 0:
543 if stats.unresolvedcount > 0:
544 if self.inmemory:
544 if self.inmemory:
545 raise error.InMemoryMergeConflictsError()
545 raise error.InMemoryMergeConflictsError()
546 else:
546 else:
547 raise error.InterventionRequired(
547 raise error.InterventionRequired(
548 _('unresolved conflicts (see hg '
548 _('unresolved conflicts (see hg '
549 'resolve, then hg rebase --continue)'))
549 'resolve, then hg rebase --continue)'))
550 if not self.collapsef:
550 if not self.collapsef:
551 merging = p2 != nullrev
551 merging = p2 != nullrev
552 editform = cmdutil.mergeeditform(merging, 'rebase')
552 editform = cmdutil.mergeeditform(merging, 'rebase')
553 editor = cmdutil.getcommiteditor(editform=editform,
553 editor = cmdutil.getcommiteditor(editform=editform,
554 **pycompat.strkwargs(opts))
554 **pycompat.strkwargs(opts))
555 newnode = self._concludenode(rev, p1, p2, editor)
555 newnode = self._concludenode(rev, p1, p2, editor)
556 else:
556 else:
557 # Skip commit if we are collapsing
557 # Skip commit if we are collapsing
558 if self.inmemory:
558 if self.inmemory:
559 self.wctx.setbase(repo[p1])
559 self.wctx.setbase(repo[p1])
560 else:
560 else:
561 repo.setparents(repo[p1].node())
561 repo.setparents(repo[p1].node())
562 newnode = None
562 newnode = None
563 # Update the state
563 # Update the state
564 if newnode is not None:
564 if newnode is not None:
565 self.state[rev] = repo[newnode].rev()
565 self.state[rev] = repo[newnode].rev()
566 ui.debug('rebased as %s\n' % short(newnode))
566 ui.debug('rebased as %s\n' % short(newnode))
567 else:
567 else:
568 if not self.collapsef:
568 if not self.collapsef:
569 ui.warn(_('note: rebase of %d:%s created no changes '
569 ui.warn(_('note: rebase of %d:%s created no changes '
570 'to commit\n') % (rev, ctx))
570 'to commit\n') % (rev, ctx))
571 self.skipped.add(rev)
571 self.skipped.add(rev)
572 self.state[rev] = p1
572 self.state[rev] = p1
573 ui.debug('next revision set to %d\n' % p1)
573 ui.debug('next revision set to %d\n' % p1)
574 else:
574 else:
575 ui.status(_('already rebased %s as %s\n') %
575 ui.status(_('already rebased %s as %s\n') %
576 (desc, repo[self.state[rev]]))
576 (desc, repo[self.state[rev]]))
577 if not tr:
577 if not tr:
578 # When not using single transaction, store state after each
578 # When not using single transaction, store state after each
579 # commit is completely done. On InterventionRequired, we thus
579 # commit is completely done. On InterventionRequired, we thus
580 # won't store the status. Instead, we'll hit the "len(parents) == 2"
580 # won't store the status. Instead, we'll hit the "len(parents) == 2"
581 # case and realize that the commit was in progress.
581 # case and realize that the commit was in progress.
582 self.storestatus()
582 self.storestatus()
583
583
584 def _finishrebase(self):
584 def _finishrebase(self):
585 repo, ui, opts = self.repo, self.ui, self.opts
585 repo, ui, opts = self.repo, self.ui, self.opts
586 fm = ui.formatter('rebase', opts)
586 fm = ui.formatter('rebase', opts)
587 fm.startitem()
587 fm.startitem()
588 if self.collapsef:
588 if self.collapsef:
589 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
589 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
590 self.state, self.skipped,
590 self.state, self.skipped,
591 self.obsoletenotrebased)
591 self.obsoletenotrebased)
592 editopt = opts.get('edit')
592 editopt = opts.get('edit')
593 editform = 'rebase.collapse'
593 editform = 'rebase.collapse'
594 if self.collapsemsg:
594 if self.collapsemsg:
595 commitmsg = self.collapsemsg
595 commitmsg = self.collapsemsg
596 else:
596 else:
597 commitmsg = 'Collapsed revision'
597 commitmsg = 'Collapsed revision'
598 for rebased in sorted(self.state):
598 for rebased in sorted(self.state):
599 if rebased not in self.skipped:
599 if rebased not in self.skipped:
600 commitmsg += '\n* %s' % repo[rebased].description()
600 commitmsg += '\n* %s' % repo[rebased].description()
601 editopt = True
601 editopt = True
602 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
602 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
603 revtoreuse = max(self.state)
603 revtoreuse = max(self.state)
604
604
605 newnode = self._concludenode(revtoreuse, p1, self.external,
605 newnode = self._concludenode(revtoreuse, p1, self.external,
606 editor, commitmsg=commitmsg)
606 editor, commitmsg=commitmsg)
607
607
608 if newnode is not None:
608 if newnode is not None:
609 newrev = repo[newnode].rev()
609 newrev = repo[newnode].rev()
610 for oldrev in self.state:
610 for oldrev in self.state:
611 self.state[oldrev] = newrev
611 self.state[oldrev] = newrev
612
612
613 if 'qtip' in repo.tags():
613 if 'qtip' in repo.tags():
614 updatemq(repo, self.state, self.skipped,
614 updatemq(repo, self.state, self.skipped,
615 **pycompat.strkwargs(opts))
615 **pycompat.strkwargs(opts))
616
616
617 # restore original working directory
617 # restore original working directory
618 # (we do this before stripping)
618 # (we do this before stripping)
619 newwd = self.state.get(self.originalwd, self.originalwd)
619 newwd = self.state.get(self.originalwd, self.originalwd)
620 if newwd < 0:
620 if newwd < 0:
621 # original directory is a parent of rebase set root or ignored
621 # original directory is a parent of rebase set root or ignored
622 newwd = self.originalwd
622 newwd = self.originalwd
623 if newwd not in [c.rev() for c in repo[None].parents()]:
623 if newwd not in [c.rev() for c in repo[None].parents()]:
624 ui.note(_("update back to initial working directory parent\n"))
624 ui.note(_("update back to initial working directory parent\n"))
625 hg.updaterepo(repo, newwd, False)
625 hg.updaterepo(repo, newwd, False)
626
626
627 collapsedas = None
627 collapsedas = None
628 if self.collapsef and not self.keepf:
628 if self.collapsef and not self.keepf:
629 collapsedas = newnode
629 collapsedas = newnode
630 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
630 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
631 collapsedas, self.keepf, fm=fm)
631 collapsedas, self.keepf, fm=fm)
632
632
633 clearstatus(repo)
633 clearstatus(repo)
634 clearcollapsemsg(repo)
634 clearcollapsemsg(repo)
635
635
636 ui.note(_("rebase completed\n"))
636 ui.note(_("rebase completed\n"))
637 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
637 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
638 if self.skipped:
638 if self.skipped:
639 skippedlen = len(self.skipped)
639 skippedlen = len(self.skipped)
640 ui.note(_("%d revisions have been skipped\n") % skippedlen)
640 ui.note(_("%d revisions have been skipped\n") % skippedlen)
641 fm.end()
641 fm.end()
642
642
643 if (self.activebookmark and self.activebookmark in repo._bookmarks and
643 if (self.activebookmark and self.activebookmark in repo._bookmarks and
644 repo['.'].node() == repo._bookmarks[self.activebookmark]):
644 repo['.'].node() == repo._bookmarks[self.activebookmark]):
645 bookmarks.activate(repo, self.activebookmark)
645 bookmarks.activate(repo, self.activebookmark)
646
646
647 @command('rebase',
647 @command('rebase',
648 [('s', 'source', '',
648 [('s', 'source', '',
649 _('rebase the specified changeset and descendants'), _('REV')),
649 _('rebase the specified changeset and descendants'), _('REV')),
650 ('b', 'base', '',
650 ('b', 'base', '',
651 _('rebase everything from branching point of specified changeset'),
651 _('rebase everything from branching point of specified changeset'),
652 _('REV')),
652 _('REV')),
653 ('r', 'rev', [],
653 ('r', 'rev', [],
654 _('rebase these revisions'),
654 _('rebase these revisions'),
655 _('REV')),
655 _('REV')),
656 ('d', 'dest', '',
656 ('d', 'dest', '',
657 _('rebase onto the specified changeset'), _('REV')),
657 _('rebase onto the specified changeset'), _('REV')),
658 ('', 'collapse', False, _('collapse the rebased changesets')),
658 ('', 'collapse', False, _('collapse the rebased changesets')),
659 ('m', 'message', '',
659 ('m', 'message', '',
660 _('use text as collapse commit message'), _('TEXT')),
660 _('use text as collapse commit message'), _('TEXT')),
661 ('e', 'edit', False, _('invoke editor on commit messages')),
661 ('e', 'edit', False, _('invoke editor on commit messages')),
662 ('l', 'logfile', '',
662 ('l', 'logfile', '',
663 _('read collapse commit message from file'), _('FILE')),
663 _('read collapse commit message from file'), _('FILE')),
664 ('k', 'keep', False, _('keep original changesets')),
664 ('k', 'keep', False, _('keep original changesets')),
665 ('', 'keepbranches', False, _('keep original branch names')),
665 ('', 'keepbranches', False, _('keep original branch names')),
666 ('D', 'detach', False, _('(DEPRECATED)')),
666 ('D', 'detach', False, _('(DEPRECATED)')),
667 ('i', 'interactive', False, _('(DEPRECATED)')),
667 ('i', 'interactive', False, _('(DEPRECATED)')),
668 ('t', 'tool', '', _('specify merge tool')),
668 ('t', 'tool', '', _('specify merge tool')),
669 ('c', 'continue', False, _('continue an interrupted rebase')),
669 ('c', 'continue', False, _('continue an interrupted rebase')),
670 ('a', 'abort', False, _('abort an interrupted rebase')),
670 ('a', 'abort', False, _('abort an interrupted rebase')),
671 ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
671 ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
672 'in the specified revset (EXPERIMENTAL)')),
672 'in the specified revset (EXPERIMENTAL)')),
673 ] + cmdutil.dryrunopts + cmdutil.formatteropts,
673 ] + cmdutil.dryrunopts + cmdutil.formatteropts,
674 _('[-s REV | -b REV] [-d REV] [OPTION]'))
674 _('[-s REV | -b REV] [-d REV] [OPTION]'))
675 def rebase(ui, repo, **opts):
675 def rebase(ui, repo, **opts):
676 """move changeset (and descendants) to a different branch
676 """move changeset (and descendants) to a different branch
677
677
678 Rebase uses repeated merging to graft changesets from one part of
678 Rebase uses repeated merging to graft changesets from one part of
679 history (the source) onto another (the destination). This can be
679 history (the source) onto another (the destination). This can be
680 useful for linearizing *local* changes relative to a master
680 useful for linearizing *local* changes relative to a master
681 development tree.
681 development tree.
682
682
683 Published commits cannot be rebased (see :hg:`help phases`).
683 Published commits cannot be rebased (see :hg:`help phases`).
684 To copy commits, see :hg:`help graft`.
684 To copy commits, see :hg:`help graft`.
685
685
686 If you don't specify a destination changeset (``-d/--dest``), rebase
686 If you don't specify a destination changeset (``-d/--dest``), rebase
687 will use the same logic as :hg:`merge` to pick a destination. if
687 will use the same logic as :hg:`merge` to pick a destination. if
688 the current branch contains exactly one other head, the other head
688 the current branch contains exactly one other head, the other head
689 is merged with by default. Otherwise, an explicit revision with
689 is merged with by default. Otherwise, an explicit revision with
690 which to merge with must be provided. (destination changeset is not
690 which to merge with must be provided. (destination changeset is not
691 modified by rebasing, but new changesets are added as its
691 modified by rebasing, but new changesets are added as its
692 descendants.)
692 descendants.)
693
693
694 Here are the ways to select changesets:
694 Here are the ways to select changesets:
695
695
696 1. Explicitly select them using ``--rev``.
696 1. Explicitly select them using ``--rev``.
697
697
698 2. Use ``--source`` to select a root changeset and include all of its
698 2. Use ``--source`` to select a root changeset and include all of its
699 descendants.
699 descendants.
700
700
701 3. Use ``--base`` to select a changeset; rebase will find ancestors
701 3. Use ``--base`` to select a changeset; rebase will find ancestors
702 and their descendants which are not also ancestors of the destination.
702 and their descendants which are not also ancestors of the destination.
703
703
704 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
704 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
705 rebase will use ``--base .`` as above.
705 rebase will use ``--base .`` as above.
706
706
707 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
707 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
708 can be used in ``--dest``. Destination would be calculated per source
708 can be used in ``--dest``. Destination would be calculated per source
709 revision with ``SRC`` substituted by that single source revision and
709 revision with ``SRC`` substituted by that single source revision and
710 ``ALLSRC`` substituted by all source revisions.
710 ``ALLSRC`` substituted by all source revisions.
711
711
712 Rebase will destroy original changesets unless you use ``--keep``.
712 Rebase will destroy original changesets unless you use ``--keep``.
713 It will also move your bookmarks (even if you do).
713 It will also move your bookmarks (even if you do).
714
714
715 Some changesets may be dropped if they do not contribute changes
715 Some changesets may be dropped if they do not contribute changes
716 (e.g. merges from the destination branch).
716 (e.g. merges from the destination branch).
717
717
718 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
718 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
719 a named branch with two heads. You will need to explicitly specify source
719 a named branch with two heads. You will need to explicitly specify source
720 and/or destination.
720 and/or destination.
721
721
722 If you need to use a tool to automate merge/conflict decisions, you
722 If you need to use a tool to automate merge/conflict decisions, you
723 can specify one with ``--tool``, see :hg:`help merge-tools`.
723 can specify one with ``--tool``, see :hg:`help merge-tools`.
724 As a caveat: the tool will not be used to mediate when a file was
724 As a caveat: the tool will not be used to mediate when a file was
725 deleted, there is no hook presently available for this.
725 deleted, there is no hook presently available for this.
726
726
727 If a rebase is interrupted to manually resolve a conflict, it can be
727 If a rebase is interrupted to manually resolve a conflict, it can be
728 continued with --continue/-c or aborted with --abort/-a.
728 continued with --continue/-c or aborted with --abort/-a.
729
729
730 .. container:: verbose
730 .. container:: verbose
731
731
732 Examples:
732 Examples:
733
733
734 - move "local changes" (current commit back to branching point)
734 - move "local changes" (current commit back to branching point)
735 to the current branch tip after a pull::
735 to the current branch tip after a pull::
736
736
737 hg rebase
737 hg rebase
738
738
739 - move a single changeset to the stable branch::
739 - move a single changeset to the stable branch::
740
740
741 hg rebase -r 5f493448 -d stable
741 hg rebase -r 5f493448 -d stable
742
742
743 - splice a commit and all its descendants onto another part of history::
743 - splice a commit and all its descendants onto another part of history::
744
744
745 hg rebase --source c0c3 --dest 4cf9
745 hg rebase --source c0c3 --dest 4cf9
746
746
747 - rebase everything on a branch marked by a bookmark onto the
747 - rebase everything on a branch marked by a bookmark onto the
748 default branch::
748 default branch::
749
749
750 hg rebase --base myfeature --dest default
750 hg rebase --base myfeature --dest default
751
751
752 - collapse a sequence of changes into a single commit::
752 - collapse a sequence of changes into a single commit::
753
753
754 hg rebase --collapse -r 1520:1525 -d .
754 hg rebase --collapse -r 1520:1525 -d .
755
755
756 - move a named branch while preserving its name::
756 - move a named branch while preserving its name::
757
757
758 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
758 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
759
759
760 - stabilize orphaned changesets so history looks linear::
760 - stabilize orphaned changesets so history looks linear::
761
761
762 hg rebase -r 'orphan()-obsolete()'\
762 hg rebase -r 'orphan()-obsolete()'\
763 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
763 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
764 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
764 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
765
765
766 Configuration Options:
766 Configuration Options:
767
767
768 You can make rebase require a destination if you set the following config
768 You can make rebase require a destination if you set the following config
769 option::
769 option::
770
770
771 [commands]
771 [commands]
772 rebase.requiredest = True
772 rebase.requiredest = True
773
773
774 By default, rebase will close the transaction after each commit. For
774 By default, rebase will close the transaction after each commit. For
775 performance purposes, you can configure rebase to use a single transaction
775 performance purposes, you can configure rebase to use a single transaction
776 across the entire rebase. WARNING: This setting introduces a significant
776 across the entire rebase. WARNING: This setting introduces a significant
777 risk of losing the work you've done in a rebase if the rebase aborts
777 risk of losing the work you've done in a rebase if the rebase aborts
778 unexpectedly::
778 unexpectedly::
779
779
780 [rebase]
780 [rebase]
781 singletransaction = True
781 singletransaction = True
782
782
783 By default, rebase writes to the working copy, but you can configure it to
783 By default, rebase writes to the working copy, but you can configure it to
784 run in-memory for for better performance, and to allow it to run if the
784 run in-memory for for better performance, and to allow it to run if the
785 working copy is dirty::
785 working copy is dirty::
786
786
787 [rebase]
787 [rebase]
788 experimental.inmemory = True
788 experimental.inmemory = True
789
789
790 Return Values:
790 Return Values:
791
791
792 Returns 0 on success, 1 if nothing to rebase or there are
792 Returns 0 on success, 1 if nothing to rebase or there are
793 unresolved conflicts.
793 unresolved conflicts.
794
794
795 """
795 """
796 inmemory = ui.configbool('rebase', 'experimental.inmemory')
796 inmemory = ui.configbool('rebase', 'experimental.inmemory')
797 dryrun = opts.get(r'dry_run')
797 dryrun = opts.get(r'dry_run')
798 if dryrun:
798 if dryrun:
799 if opts.get(r'abort'):
799 if opts.get(r'abort'):
800 raise error.Abort(_('cannot specify both --dry-run and --abort'))
800 raise error.Abort(_('cannot specify both --dry-run and --abort'))
801 if opts.get(r'continue'):
801 if opts.get(r'continue'):
802 raise error.Abort(_('cannot specify both --dry-run and --continue'))
802 raise error.Abort(_('cannot specify both --dry-run and --continue'))
803
803
804 if (opts.get(r'continue') or opts.get(r'abort') or
804 if (opts.get(r'continue') or opts.get(r'abort') or
805 repo.currenttransaction() is not None):
805 repo.currenttransaction() is not None):
806 # in-memory rebase is not compatible with resuming rebases.
806 # in-memory rebase is not compatible with resuming rebases.
807 # (Or if it is run within a transaction, since the restart logic can
807 # (Or if it is run within a transaction, since the restart logic can
808 # fail the entire transaction.)
808 # fail the entire transaction.)
809 inmemory = False
809 inmemory = False
810
810
811 if opts.get(r'auto_orphans'):
811 if opts.get(r'auto_orphans'):
812 for key in opts:
812 for key in opts:
813 if key != r'auto_orphans' and opts.get(key):
813 if key != r'auto_orphans' and opts.get(key):
814 raise error.Abort(_('--auto-orphans is incompatible with %s') %
814 raise error.Abort(_('--auto-orphans is incompatible with %s') %
815 ('--' + pycompat.bytestr(key)))
815 ('--' + pycompat.bytestr(key)))
816 userrevs = list(repo.revs(opts.get(r'auto_orphans')))
816 userrevs = list(repo.revs(opts.get(r'auto_orphans')))
817 opts[r'rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
817 opts[r'rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
818 opts[r'dest'] = '_destautoorphanrebase(SRC)'
818 opts[r'dest'] = '_destautoorphanrebase(SRC)'
819
819
820 if dryrun:
820 if dryrun:
821 return _dryrunrebase(ui, repo, **opts)
821 return _dryrunrebase(ui, repo, **opts)
822 elif inmemory:
822 elif inmemory:
823 try:
823 try:
824 # in-memory merge doesn't support conflicts, so if we hit any, abort
824 # in-memory merge doesn't support conflicts, so if we hit any, abort
825 # and re-run as an on-disk merge.
825 # and re-run as an on-disk merge.
826 overrides = {('rebase', 'singletransaction'): True}
826 overrides = {('rebase', 'singletransaction'): True}
827 with ui.configoverride(overrides, 'rebase'):
827 with ui.configoverride(overrides, 'rebase'):
828 return _origrebase(ui, repo, inmemory=inmemory, **opts)
828 return _dorebase(ui, repo, inmemory=inmemory, **opts)
829 except error.InMemoryMergeConflictsError:
829 except error.InMemoryMergeConflictsError:
830 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
830 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
831 ' merge\n'))
831 ' merge\n'))
832 _origrebase(ui, repo, abort=True)
832 _dorebase(ui, repo, abort=True)
833 return _origrebase(ui, repo, inmemory=False, **opts)
833 return _dorebase(ui, repo, inmemory=False, **opts)
834 else:
834 else:
835 return _origrebase(ui, repo, **opts)
835 return _dorebase(ui, repo, **opts)
836
836
837 def _dryrunrebase(ui, repo, **opts):
837 def _dryrunrebase(ui, repo, **opts):
838 rbsrt = rebaseruntime(repo, ui, inmemory=True,
838 rbsrt = rebaseruntime(repo, ui, inmemory=True,
839 opts=pycompat.byteskwargs(opts))
839 opts=pycompat.byteskwargs(opts))
840 with repo.wlock(), repo.lock():
840 with repo.wlock(), repo.lock():
841 try:
841 try:
842 overrides = {('rebase', 'singletransaction'): True}
842 overrides = {('rebase', 'singletransaction'): True}
843 with ui.configoverride(overrides, 'rebase'):
843 with ui.configoverride(overrides, 'rebase'):
844 _origrebase(ui, repo, inmemory=True, rbsrt=rbsrt,
844 _origrebase(ui, repo, rbsrt, inmemory=True,
845 leaveunfinished=True, **opts)
845 leaveunfinished=True, **opts)
846 except error.InMemoryMergeConflictsError:
846 except error.InMemoryMergeConflictsError:
847 ui.status(_('hit a merge conflict\n'))
847 ui.status(_('hit a merge conflict\n'))
848 return 1
848 return 1
849 else:
849 else:
850 ui.status(_('there will be no conflict, you can rebase\n'))
850 ui.status(_('there will be no conflict, you can rebase\n'))
851 return 0
851 return 0
852 finally:
852 finally:
853 rbsrt._prepareabortorcontinue(isabort=True)
853 rbsrt._prepareabortorcontinue(isabort=True)
854
854
855 def _origrebase(ui, repo, inmemory=False, leaveunfinished=False, rbsrt=None,
855 def _dorebase(ui, repo, inmemory=False, **opts):
856 **opts):
856 rbsrt = rebaseruntime(repo, ui, inmemory, pycompat.byteskwargs(opts))
857 return _origrebase(ui, repo, rbsrt, inmemory=inmemory, **opts)
858
859 def _origrebase(ui, repo, rbsrt, inmemory=False, leaveunfinished=False, **opts):
857 opts = pycompat.byteskwargs(opts)
860 opts = pycompat.byteskwargs(opts)
858 if not rbsrt:
859 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
860
861 with repo.wlock(), repo.lock():
861 with repo.wlock(), repo.lock():
862 # Validate input and define rebasing points
862 # Validate input and define rebasing points
863 destf = opts.get('dest', None)
863 destf = opts.get('dest', None)
864 srcf = opts.get('source', None)
864 srcf = opts.get('source', None)
865 basef = opts.get('base', None)
865 basef = opts.get('base', None)
866 revf = opts.get('rev', [])
866 revf = opts.get('rev', [])
867 # search default destination in this space
867 # search default destination in this space
868 # used in the 'hg pull --rebase' case, see issue 5214.
868 # used in the 'hg pull --rebase' case, see issue 5214.
869 destspace = opts.get('_destspace')
869 destspace = opts.get('_destspace')
870 contf = opts.get('continue')
870 contf = opts.get('continue')
871 abortf = opts.get('abort')
871 abortf = opts.get('abort')
872 if opts.get('interactive'):
872 if opts.get('interactive'):
873 try:
873 try:
874 if extensions.find('histedit'):
874 if extensions.find('histedit'):
875 enablehistedit = ''
875 enablehistedit = ''
876 except KeyError:
876 except KeyError:
877 enablehistedit = " --config extensions.histedit="
877 enablehistedit = " --config extensions.histedit="
878 help = "hg%s help -e histedit" % enablehistedit
878 help = "hg%s help -e histedit" % enablehistedit
879 msg = _("interactive history editing is supported by the "
879 msg = _("interactive history editing is supported by the "
880 "'histedit' extension (see \"%s\")") % help
880 "'histedit' extension (see \"%s\")") % help
881 raise error.Abort(msg)
881 raise error.Abort(msg)
882
882
883 if rbsrt.collapsemsg and not rbsrt.collapsef:
883 if rbsrt.collapsemsg and not rbsrt.collapsef:
884 raise error.Abort(
884 raise error.Abort(
885 _('message can only be specified with collapse'))
885 _('message can only be specified with collapse'))
886
886
887 if contf or abortf:
887 if contf or abortf:
888 if contf and abortf:
888 if contf and abortf:
889 raise error.Abort(_('cannot use both abort and continue'))
889 raise error.Abort(_('cannot use both abort and continue'))
890 if rbsrt.collapsef:
890 if rbsrt.collapsef:
891 raise error.Abort(
891 raise error.Abort(
892 _('cannot use collapse with continue or abort'))
892 _('cannot use collapse with continue or abort'))
893 if srcf or basef or destf:
893 if srcf or basef or destf:
894 raise error.Abort(
894 raise error.Abort(
895 _('abort and continue do not allow specifying revisions'))
895 _('abort and continue do not allow specifying revisions'))
896 if abortf and opts.get('tool', False):
896 if abortf and opts.get('tool', False):
897 ui.warn(_('tool option will be ignored\n'))
897 ui.warn(_('tool option will be ignored\n'))
898 if contf:
898 if contf:
899 ms = mergemod.mergestate.read(repo)
899 ms = mergemod.mergestate.read(repo)
900 mergeutil.checkunresolved(ms)
900 mergeutil.checkunresolved(ms)
901
901
902 retcode = rbsrt._prepareabortorcontinue(abortf)
902 retcode = rbsrt._prepareabortorcontinue(abortf)
903 if retcode is not None:
903 if retcode is not None:
904 return retcode
904 return retcode
905 else:
905 else:
906 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
906 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
907 revf, destspace=destspace)
907 revf, destspace=destspace)
908 retcode = rbsrt._preparenewrebase(destmap)
908 retcode = rbsrt._preparenewrebase(destmap)
909 if retcode is not None:
909 if retcode is not None:
910 return retcode
910 return retcode
911 storecollapsemsg(repo, rbsrt.collapsemsg)
911 storecollapsemsg(repo, rbsrt.collapsemsg)
912
912
913 tr = None
913 tr = None
914
914
915 singletr = ui.configbool('rebase', 'singletransaction')
915 singletr = ui.configbool('rebase', 'singletransaction')
916 if singletr:
916 if singletr:
917 tr = repo.transaction('rebase')
917 tr = repo.transaction('rebase')
918
918
919 # If `rebase.singletransaction` is enabled, wrap the entire operation in
919 # If `rebase.singletransaction` is enabled, wrap the entire operation in
920 # one transaction here. Otherwise, transactions are obtained when
920 # one transaction here. Otherwise, transactions are obtained when
921 # committing each node, which is slower but allows partial success.
921 # committing each node, which is slower but allows partial success.
922 with util.acceptintervention(tr):
922 with util.acceptintervention(tr):
923 # Same logic for the dirstate guard, except we don't create one when
923 # Same logic for the dirstate guard, except we don't create one when
924 # rebasing in-memory (it's not needed).
924 # rebasing in-memory (it's not needed).
925 dsguard = None
925 dsguard = None
926 if singletr and not inmemory:
926 if singletr and not inmemory:
927 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
927 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
928 with util.acceptintervention(dsguard):
928 with util.acceptintervention(dsguard):
929 rbsrt._performrebase(tr)
929 rbsrt._performrebase(tr)
930 if not leaveunfinished:
930 if not leaveunfinished:
931 rbsrt._finishrebase()
931 rbsrt._finishrebase()
932
932
933 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
933 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
934 revf=None, destspace=None):
934 revf=None, destspace=None):
935 """use revisions argument to define destmap {srcrev: destrev}"""
935 """use revisions argument to define destmap {srcrev: destrev}"""
936 if revf is None:
936 if revf is None:
937 revf = []
937 revf = []
938
938
939 # destspace is here to work around issues with `hg pull --rebase` see
939 # destspace is here to work around issues with `hg pull --rebase` see
940 # issue5214 for details
940 # issue5214 for details
941 if srcf and basef:
941 if srcf and basef:
942 raise error.Abort(_('cannot specify both a source and a base'))
942 raise error.Abort(_('cannot specify both a source and a base'))
943 if revf and basef:
943 if revf and basef:
944 raise error.Abort(_('cannot specify both a revision and a base'))
944 raise error.Abort(_('cannot specify both a revision and a base'))
945 if revf and srcf:
945 if revf and srcf:
946 raise error.Abort(_('cannot specify both a revision and a source'))
946 raise error.Abort(_('cannot specify both a revision and a source'))
947
947
948 if not inmemory:
948 if not inmemory:
949 cmdutil.checkunfinished(repo)
949 cmdutil.checkunfinished(repo)
950 cmdutil.bailifchanged(repo)
950 cmdutil.bailifchanged(repo)
951
951
952 if ui.configbool('commands', 'rebase.requiredest') and not destf:
952 if ui.configbool('commands', 'rebase.requiredest') and not destf:
953 raise error.Abort(_('you must specify a destination'),
953 raise error.Abort(_('you must specify a destination'),
954 hint=_('use: hg rebase -d REV'))
954 hint=_('use: hg rebase -d REV'))
955
955
956 dest = None
956 dest = None
957
957
958 if revf:
958 if revf:
959 rebaseset = scmutil.revrange(repo, revf)
959 rebaseset = scmutil.revrange(repo, revf)
960 if not rebaseset:
960 if not rebaseset:
961 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
961 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
962 return None
962 return None
963 elif srcf:
963 elif srcf:
964 src = scmutil.revrange(repo, [srcf])
964 src = scmutil.revrange(repo, [srcf])
965 if not src:
965 if not src:
966 ui.status(_('empty "source" revision set - nothing to rebase\n'))
966 ui.status(_('empty "source" revision set - nothing to rebase\n'))
967 return None
967 return None
968 rebaseset = repo.revs('(%ld)::', src)
968 rebaseset = repo.revs('(%ld)::', src)
969 assert rebaseset
969 assert rebaseset
970 else:
970 else:
971 base = scmutil.revrange(repo, [basef or '.'])
971 base = scmutil.revrange(repo, [basef or '.'])
972 if not base:
972 if not base:
973 ui.status(_('empty "base" revision set - '
973 ui.status(_('empty "base" revision set - '
974 "can't compute rebase set\n"))
974 "can't compute rebase set\n"))
975 return None
975 return None
976 if destf:
976 if destf:
977 # --base does not support multiple destinations
977 # --base does not support multiple destinations
978 dest = scmutil.revsingle(repo, destf)
978 dest = scmutil.revsingle(repo, destf)
979 else:
979 else:
980 dest = repo[_destrebase(repo, base, destspace=destspace)]
980 dest = repo[_destrebase(repo, base, destspace=destspace)]
981 destf = bytes(dest)
981 destf = bytes(dest)
982
982
983 roots = [] # selected children of branching points
983 roots = [] # selected children of branching points
984 bpbase = {} # {branchingpoint: [origbase]}
984 bpbase = {} # {branchingpoint: [origbase]}
985 for b in base: # group bases by branching points
985 for b in base: # group bases by branching points
986 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
986 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
987 bpbase[bp] = bpbase.get(bp, []) + [b]
987 bpbase[bp] = bpbase.get(bp, []) + [b]
988 if None in bpbase:
988 if None in bpbase:
989 # emulate the old behavior, showing "nothing to rebase" (a better
989 # emulate the old behavior, showing "nothing to rebase" (a better
990 # behavior may be abort with "cannot find branching point" error)
990 # behavior may be abort with "cannot find branching point" error)
991 bpbase.clear()
991 bpbase.clear()
992 for bp, bs in bpbase.iteritems(): # calculate roots
992 for bp, bs in bpbase.iteritems(): # calculate roots
993 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
993 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
994
994
995 rebaseset = repo.revs('%ld::', roots)
995 rebaseset = repo.revs('%ld::', roots)
996
996
997 if not rebaseset:
997 if not rebaseset:
998 # transform to list because smartsets are not comparable to
998 # transform to list because smartsets are not comparable to
999 # lists. This should be improved to honor laziness of
999 # lists. This should be improved to honor laziness of
1000 # smartset.
1000 # smartset.
1001 if list(base) == [dest.rev()]:
1001 if list(base) == [dest.rev()]:
1002 if basef:
1002 if basef:
1003 ui.status(_('nothing to rebase - %s is both "base"'
1003 ui.status(_('nothing to rebase - %s is both "base"'
1004 ' and destination\n') % dest)
1004 ' and destination\n') % dest)
1005 else:
1005 else:
1006 ui.status(_('nothing to rebase - working directory '
1006 ui.status(_('nothing to rebase - working directory '
1007 'parent is also destination\n'))
1007 'parent is also destination\n'))
1008 elif not repo.revs('%ld - ::%d', base, dest.rev()):
1008 elif not repo.revs('%ld - ::%d', base, dest.rev()):
1009 if basef:
1009 if basef:
1010 ui.status(_('nothing to rebase - "base" %s is '
1010 ui.status(_('nothing to rebase - "base" %s is '
1011 'already an ancestor of destination '
1011 'already an ancestor of destination '
1012 '%s\n') %
1012 '%s\n') %
1013 ('+'.join(bytes(repo[r]) for r in base),
1013 ('+'.join(bytes(repo[r]) for r in base),
1014 dest))
1014 dest))
1015 else:
1015 else:
1016 ui.status(_('nothing to rebase - working '
1016 ui.status(_('nothing to rebase - working '
1017 'directory parent is already an '
1017 'directory parent is already an '
1018 'ancestor of destination %s\n') % dest)
1018 'ancestor of destination %s\n') % dest)
1019 else: # can it happen?
1019 else: # can it happen?
1020 ui.status(_('nothing to rebase from %s to %s\n') %
1020 ui.status(_('nothing to rebase from %s to %s\n') %
1021 ('+'.join(bytes(repo[r]) for r in base), dest))
1021 ('+'.join(bytes(repo[r]) for r in base), dest))
1022 return None
1022 return None
1023
1023
1024 rebasingwcp = repo['.'].rev() in rebaseset
1024 rebasingwcp = repo['.'].rev() in rebaseset
1025 ui.log("rebase", "", rebase_rebasing_wcp=rebasingwcp)
1025 ui.log("rebase", "", rebase_rebasing_wcp=rebasingwcp)
1026 if inmemory and rebasingwcp:
1026 if inmemory and rebasingwcp:
1027 # Check these since we did not before.
1027 # Check these since we did not before.
1028 cmdutil.checkunfinished(repo)
1028 cmdutil.checkunfinished(repo)
1029 cmdutil.bailifchanged(repo)
1029 cmdutil.bailifchanged(repo)
1030
1030
1031 if not destf:
1031 if not destf:
1032 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1032 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1033 destf = bytes(dest)
1033 destf = bytes(dest)
1034
1034
1035 allsrc = revsetlang.formatspec('%ld', rebaseset)
1035 allsrc = revsetlang.formatspec('%ld', rebaseset)
1036 alias = {'ALLSRC': allsrc}
1036 alias = {'ALLSRC': allsrc}
1037
1037
1038 if dest is None:
1038 if dest is None:
1039 try:
1039 try:
1040 # fast path: try to resolve dest without SRC alias
1040 # fast path: try to resolve dest without SRC alias
1041 dest = scmutil.revsingle(repo, destf, localalias=alias)
1041 dest = scmutil.revsingle(repo, destf, localalias=alias)
1042 except error.RepoLookupError:
1042 except error.RepoLookupError:
1043 # multi-dest path: resolve dest for each SRC separately
1043 # multi-dest path: resolve dest for each SRC separately
1044 destmap = {}
1044 destmap = {}
1045 for r in rebaseset:
1045 for r in rebaseset:
1046 alias['SRC'] = revsetlang.formatspec('%d', r)
1046 alias['SRC'] = revsetlang.formatspec('%d', r)
1047 # use repo.anyrevs instead of scmutil.revsingle because we
1047 # use repo.anyrevs instead of scmutil.revsingle because we
1048 # don't want to abort if destset is empty.
1048 # don't want to abort if destset is empty.
1049 destset = repo.anyrevs([destf], user=True, localalias=alias)
1049 destset = repo.anyrevs([destf], user=True, localalias=alias)
1050 size = len(destset)
1050 size = len(destset)
1051 if size == 1:
1051 if size == 1:
1052 destmap[r] = destset.first()
1052 destmap[r] = destset.first()
1053 elif size == 0:
1053 elif size == 0:
1054 ui.note(_('skipping %s - empty destination\n') % repo[r])
1054 ui.note(_('skipping %s - empty destination\n') % repo[r])
1055 else:
1055 else:
1056 raise error.Abort(_('rebase destination for %s is not '
1056 raise error.Abort(_('rebase destination for %s is not '
1057 'unique') % repo[r])
1057 'unique') % repo[r])
1058
1058
1059 if dest is not None:
1059 if dest is not None:
1060 # single-dest case: assign dest to each rev in rebaseset
1060 # single-dest case: assign dest to each rev in rebaseset
1061 destrev = dest.rev()
1061 destrev = dest.rev()
1062 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1062 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1063
1063
1064 if not destmap:
1064 if not destmap:
1065 ui.status(_('nothing to rebase - empty destination\n'))
1065 ui.status(_('nothing to rebase - empty destination\n'))
1066 return None
1066 return None
1067
1067
1068 return destmap
1068 return destmap
1069
1069
1070 def externalparent(repo, state, destancestors):
1070 def externalparent(repo, state, destancestors):
1071 """Return the revision that should be used as the second parent
1071 """Return the revision that should be used as the second parent
1072 when the revisions in state is collapsed on top of destancestors.
1072 when the revisions in state is collapsed on top of destancestors.
1073 Abort if there is more than one parent.
1073 Abort if there is more than one parent.
1074 """
1074 """
1075 parents = set()
1075 parents = set()
1076 source = min(state)
1076 source = min(state)
1077 for rev in state:
1077 for rev in state:
1078 if rev == source:
1078 if rev == source:
1079 continue
1079 continue
1080 for p in repo[rev].parents():
1080 for p in repo[rev].parents():
1081 if (p.rev() not in state
1081 if (p.rev() not in state
1082 and p.rev() not in destancestors):
1082 and p.rev() not in destancestors):
1083 parents.add(p.rev())
1083 parents.add(p.rev())
1084 if not parents:
1084 if not parents:
1085 return nullrev
1085 return nullrev
1086 if len(parents) == 1:
1086 if len(parents) == 1:
1087 return parents.pop()
1087 return parents.pop()
1088 raise error.Abort(_('unable to collapse on top of %d, there is more '
1088 raise error.Abort(_('unable to collapse on top of %d, there is more '
1089 'than one external parent: %s') %
1089 'than one external parent: %s') %
1090 (max(destancestors),
1090 (max(destancestors),
1091 ', '.join("%d" % p for p in sorted(parents))))
1091 ', '.join("%d" % p for p in sorted(parents))))
1092
1092
1093 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1093 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1094 '''Commit the memory changes with parents p1 and p2.
1094 '''Commit the memory changes with parents p1 and p2.
1095 Return node of committed revision.'''
1095 Return node of committed revision.'''
1096 # Replicates the empty check in ``repo.commit``.
1096 # Replicates the empty check in ``repo.commit``.
1097 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1097 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1098 return None
1098 return None
1099
1099
1100 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1100 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1101 # ``branch`` (used when passing ``--keepbranches``).
1101 # ``branch`` (used when passing ``--keepbranches``).
1102 branch = repo[p1].branch()
1102 branch = repo[p1].branch()
1103 if 'branch' in extra:
1103 if 'branch' in extra:
1104 branch = extra['branch']
1104 branch = extra['branch']
1105
1105
1106 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1106 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1107 extra=extra, user=user, branch=branch, editor=editor)
1107 extra=extra, user=user, branch=branch, editor=editor)
1108 commitres = repo.commitctx(memctx)
1108 commitres = repo.commitctx(memctx)
1109 wctx.clean() # Might be reused
1109 wctx.clean() # Might be reused
1110 return commitres
1110 return commitres
1111
1111
1112 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1112 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1113 '''Commit the wd changes with parents p1 and p2.
1113 '''Commit the wd changes with parents p1 and p2.
1114 Return node of committed revision.'''
1114 Return node of committed revision.'''
1115 dsguard = util.nullcontextmanager()
1115 dsguard = util.nullcontextmanager()
1116 if not repo.ui.configbool('rebase', 'singletransaction'):
1116 if not repo.ui.configbool('rebase', 'singletransaction'):
1117 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1117 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1118 with dsguard:
1118 with dsguard:
1119 repo.setparents(repo[p1].node(), repo[p2].node())
1119 repo.setparents(repo[p1].node(), repo[p2].node())
1120
1120
1121 # Commit might fail if unresolved files exist
1121 # Commit might fail if unresolved files exist
1122 newnode = repo.commit(text=commitmsg, user=user, date=date,
1122 newnode = repo.commit(text=commitmsg, user=user, date=date,
1123 extra=extra, editor=editor)
1123 extra=extra, editor=editor)
1124
1124
1125 repo.dirstate.setbranch(repo[newnode].branch())
1125 repo.dirstate.setbranch(repo[newnode].branch())
1126 return newnode
1126 return newnode
1127
1127
1128 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1128 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1129 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1129 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1130 # Merge phase
1130 # Merge phase
1131 # Update to destination and merge it with local
1131 # Update to destination and merge it with local
1132 if wctx.isinmemory():
1132 if wctx.isinmemory():
1133 wctx.setbase(repo[p1])
1133 wctx.setbase(repo[p1])
1134 else:
1134 else:
1135 if repo['.'].rev() != p1:
1135 if repo['.'].rev() != p1:
1136 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1136 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1137 mergemod.update(repo, p1, False, True)
1137 mergemod.update(repo, p1, False, True)
1138 else:
1138 else:
1139 repo.ui.debug(" already in destination\n")
1139 repo.ui.debug(" already in destination\n")
1140 # This is, alas, necessary to invalidate workingctx's manifest cache,
1140 # This is, alas, necessary to invalidate workingctx's manifest cache,
1141 # as well as other data we litter on it in other places.
1141 # as well as other data we litter on it in other places.
1142 wctx = repo[None]
1142 wctx = repo[None]
1143 repo.dirstate.write(repo.currenttransaction())
1143 repo.dirstate.write(repo.currenttransaction())
1144 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1144 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1145 if base is not None:
1145 if base is not None:
1146 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1146 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1147 # When collapsing in-place, the parent is the common ancestor, we
1147 # When collapsing in-place, the parent is the common ancestor, we
1148 # have to allow merging with it.
1148 # have to allow merging with it.
1149 stats = mergemod.update(repo, rev, True, True, base, collapse,
1149 stats = mergemod.update(repo, rev, True, True, base, collapse,
1150 labels=['dest', 'source'], wc=wctx)
1150 labels=['dest', 'source'], wc=wctx)
1151 if collapse:
1151 if collapse:
1152 copies.duplicatecopies(repo, wctx, rev, dest)
1152 copies.duplicatecopies(repo, wctx, rev, dest)
1153 else:
1153 else:
1154 # If we're not using --collapse, we need to
1154 # If we're not using --collapse, we need to
1155 # duplicate copies between the revision we're
1155 # duplicate copies between the revision we're
1156 # rebasing and its first parent, but *not*
1156 # rebasing and its first parent, but *not*
1157 # duplicate any copies that have already been
1157 # duplicate any copies that have already been
1158 # performed in the destination.
1158 # performed in the destination.
1159 p1rev = repo[rev].p1().rev()
1159 p1rev = repo[rev].p1().rev()
1160 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1160 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1161 return stats
1161 return stats
1162
1162
1163 def adjustdest(repo, rev, destmap, state, skipped):
1163 def adjustdest(repo, rev, destmap, state, skipped):
1164 """adjust rebase destination given the current rebase state
1164 """adjust rebase destination given the current rebase state
1165
1165
1166 rev is what is being rebased. Return a list of two revs, which are the
1166 rev is what is being rebased. Return a list of two revs, which are the
1167 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1167 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1168 nullrev, return dest without adjustment for it.
1168 nullrev, return dest without adjustment for it.
1169
1169
1170 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1170 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1171 to B1, and E's destination will be adjusted from F to B1.
1171 to B1, and E's destination will be adjusted from F to B1.
1172
1172
1173 B1 <- written during rebasing B
1173 B1 <- written during rebasing B
1174 |
1174 |
1175 F <- original destination of B, E
1175 F <- original destination of B, E
1176 |
1176 |
1177 | E <- rev, which is being rebased
1177 | E <- rev, which is being rebased
1178 | |
1178 | |
1179 | D <- prev, one parent of rev being checked
1179 | D <- prev, one parent of rev being checked
1180 | |
1180 | |
1181 | x <- skipped, ex. no successor or successor in (::dest)
1181 | x <- skipped, ex. no successor or successor in (::dest)
1182 | |
1182 | |
1183 | C <- rebased as C', different destination
1183 | C <- rebased as C', different destination
1184 | |
1184 | |
1185 | B <- rebased as B1 C'
1185 | B <- rebased as B1 C'
1186 |/ |
1186 |/ |
1187 A G <- destination of C, different
1187 A G <- destination of C, different
1188
1188
1189 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1189 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1190 first move C to C1, G to G1, and when it's checking H, the adjusted
1190 first move C to C1, G to G1, and when it's checking H, the adjusted
1191 destinations will be [C1, G1].
1191 destinations will be [C1, G1].
1192
1192
1193 H C1 G1
1193 H C1 G1
1194 /| | /
1194 /| | /
1195 F G |/
1195 F G |/
1196 K | | -> K
1196 K | | -> K
1197 | C D |
1197 | C D |
1198 | |/ |
1198 | |/ |
1199 | B | ...
1199 | B | ...
1200 |/ |/
1200 |/ |/
1201 A A
1201 A A
1202
1202
1203 Besides, adjust dest according to existing rebase information. For example,
1203 Besides, adjust dest according to existing rebase information. For example,
1204
1204
1205 B C D B needs to be rebased on top of C, C needs to be rebased on top
1205 B C D B needs to be rebased on top of C, C needs to be rebased on top
1206 \|/ of D. We will rebase C first.
1206 \|/ of D. We will rebase C first.
1207 A
1207 A
1208
1208
1209 C' After rebasing C, when considering B's destination, use C'
1209 C' After rebasing C, when considering B's destination, use C'
1210 | instead of the original C.
1210 | instead of the original C.
1211 B D
1211 B D
1212 \ /
1212 \ /
1213 A
1213 A
1214 """
1214 """
1215 # pick already rebased revs with same dest from state as interesting source
1215 # pick already rebased revs with same dest from state as interesting source
1216 dest = destmap[rev]
1216 dest = destmap[rev]
1217 source = [s for s, d in state.items()
1217 source = [s for s, d in state.items()
1218 if d > 0 and destmap[s] == dest and s not in skipped]
1218 if d > 0 and destmap[s] == dest and s not in skipped]
1219
1219
1220 result = []
1220 result = []
1221 for prev in repo.changelog.parentrevs(rev):
1221 for prev in repo.changelog.parentrevs(rev):
1222 adjusted = dest
1222 adjusted = dest
1223 if prev != nullrev:
1223 if prev != nullrev:
1224 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1224 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1225 if candidate is not None:
1225 if candidate is not None:
1226 adjusted = state[candidate]
1226 adjusted = state[candidate]
1227 if adjusted == dest and dest in state:
1227 if adjusted == dest and dest in state:
1228 adjusted = state[dest]
1228 adjusted = state[dest]
1229 if adjusted == revtodo:
1229 if adjusted == revtodo:
1230 # sortsource should produce an order that makes this impossible
1230 # sortsource should produce an order that makes this impossible
1231 raise error.ProgrammingError(
1231 raise error.ProgrammingError(
1232 'rev %d should be rebased already at this time' % dest)
1232 'rev %d should be rebased already at this time' % dest)
1233 result.append(adjusted)
1233 result.append(adjusted)
1234 return result
1234 return result
1235
1235
1236 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1236 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1237 """
1237 """
1238 Abort if rebase will create divergence or rebase is noop because of markers
1238 Abort if rebase will create divergence or rebase is noop because of markers
1239
1239
1240 `rebaseobsrevs`: set of obsolete revision in source
1240 `rebaseobsrevs`: set of obsolete revision in source
1241 `rebaseobsskipped`: set of revisions from source skipped because they have
1241 `rebaseobsskipped`: set of revisions from source skipped because they have
1242 successors in destination or no non-obsolete successor.
1242 successors in destination or no non-obsolete successor.
1243 """
1243 """
1244 # Obsolete node with successors not in dest leads to divergence
1244 # Obsolete node with successors not in dest leads to divergence
1245 divergenceok = ui.configbool('experimental',
1245 divergenceok = ui.configbool('experimental',
1246 'evolution.allowdivergence')
1246 'evolution.allowdivergence')
1247 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1247 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1248
1248
1249 if divergencebasecandidates and not divergenceok:
1249 if divergencebasecandidates and not divergenceok:
1250 divhashes = (bytes(repo[r])
1250 divhashes = (bytes(repo[r])
1251 for r in divergencebasecandidates)
1251 for r in divergencebasecandidates)
1252 msg = _("this rebase will cause "
1252 msg = _("this rebase will cause "
1253 "divergences from: %s")
1253 "divergences from: %s")
1254 h = _("to force the rebase please set "
1254 h = _("to force the rebase please set "
1255 "experimental.evolution.allowdivergence=True")
1255 "experimental.evolution.allowdivergence=True")
1256 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1256 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1257
1257
1258 def successorrevs(unfi, rev):
1258 def successorrevs(unfi, rev):
1259 """yield revision numbers for successors of rev"""
1259 """yield revision numbers for successors of rev"""
1260 assert unfi.filtername is None
1260 assert unfi.filtername is None
1261 nodemap = unfi.changelog.nodemap
1261 nodemap = unfi.changelog.nodemap
1262 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1262 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1263 if s in nodemap:
1263 if s in nodemap:
1264 yield nodemap[s]
1264 yield nodemap[s]
1265
1265
1266 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1266 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1267 """Return new parents and optionally a merge base for rev being rebased
1267 """Return new parents and optionally a merge base for rev being rebased
1268
1268
1269 The destination specified by "dest" cannot always be used directly because
1269 The destination specified by "dest" cannot always be used directly because
1270 previously rebase result could affect destination. For example,
1270 previously rebase result could affect destination. For example,
1271
1271
1272 D E rebase -r C+D+E -d B
1272 D E rebase -r C+D+E -d B
1273 |/ C will be rebased to C'
1273 |/ C will be rebased to C'
1274 B C D's new destination will be C' instead of B
1274 B C D's new destination will be C' instead of B
1275 |/ E's new destination will be C' instead of B
1275 |/ E's new destination will be C' instead of B
1276 A
1276 A
1277
1277
1278 The new parents of a merge is slightly more complicated. See the comment
1278 The new parents of a merge is slightly more complicated. See the comment
1279 block below.
1279 block below.
1280 """
1280 """
1281 # use unfiltered changelog since successorrevs may return filtered nodes
1281 # use unfiltered changelog since successorrevs may return filtered nodes
1282 assert repo.filtername is None
1282 assert repo.filtername is None
1283 cl = repo.changelog
1283 cl = repo.changelog
1284 def isancestor(a, b):
1284 def isancestor(a, b):
1285 # take revision numbers instead of nodes
1285 # take revision numbers instead of nodes
1286 if a == b:
1286 if a == b:
1287 return True
1287 return True
1288 elif a > b:
1288 elif a > b:
1289 return False
1289 return False
1290 return cl.isancestor(cl.node(a), cl.node(b))
1290 return cl.isancestor(cl.node(a), cl.node(b))
1291
1291
1292 dest = destmap[rev]
1292 dest = destmap[rev]
1293 oldps = repo.changelog.parentrevs(rev) # old parents
1293 oldps = repo.changelog.parentrevs(rev) # old parents
1294 newps = [nullrev, nullrev] # new parents
1294 newps = [nullrev, nullrev] # new parents
1295 dests = adjustdest(repo, rev, destmap, state, skipped)
1295 dests = adjustdest(repo, rev, destmap, state, skipped)
1296 bases = list(oldps) # merge base candidates, initially just old parents
1296 bases = list(oldps) # merge base candidates, initially just old parents
1297
1297
1298 if all(r == nullrev for r in oldps[1:]):
1298 if all(r == nullrev for r in oldps[1:]):
1299 # For non-merge changeset, just move p to adjusted dest as requested.
1299 # For non-merge changeset, just move p to adjusted dest as requested.
1300 newps[0] = dests[0]
1300 newps[0] = dests[0]
1301 else:
1301 else:
1302 # For merge changeset, if we move p to dests[i] unconditionally, both
1302 # For merge changeset, if we move p to dests[i] unconditionally, both
1303 # parents may change and the end result looks like "the merge loses a
1303 # parents may change and the end result looks like "the merge loses a
1304 # parent", which is a surprise. This is a limit because "--dest" only
1304 # parent", which is a surprise. This is a limit because "--dest" only
1305 # accepts one dest per src.
1305 # accepts one dest per src.
1306 #
1306 #
1307 # Therefore, only move p with reasonable conditions (in this order):
1307 # Therefore, only move p with reasonable conditions (in this order):
1308 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1308 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1309 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1309 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1310 #
1310 #
1311 # Comparing with adjustdest, the logic here does some additional work:
1311 # Comparing with adjustdest, the logic here does some additional work:
1312 # 1. decide which parents will not be moved towards dest
1312 # 1. decide which parents will not be moved towards dest
1313 # 2. if the above decision is "no", should a parent still be moved
1313 # 2. if the above decision is "no", should a parent still be moved
1314 # because it was rebased?
1314 # because it was rebased?
1315 #
1315 #
1316 # For example:
1316 # For example:
1317 #
1317 #
1318 # C # "rebase -r C -d D" is an error since none of the parents
1318 # C # "rebase -r C -d D" is an error since none of the parents
1319 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1319 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1320 # A B D # B (using rule "2."), since B will be rebased.
1320 # A B D # B (using rule "2."), since B will be rebased.
1321 #
1321 #
1322 # The loop tries to be not rely on the fact that a Mercurial node has
1322 # The loop tries to be not rely on the fact that a Mercurial node has
1323 # at most 2 parents.
1323 # at most 2 parents.
1324 for i, p in enumerate(oldps):
1324 for i, p in enumerate(oldps):
1325 np = p # new parent
1325 np = p # new parent
1326 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1326 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1327 np = dests[i]
1327 np = dests[i]
1328 elif p in state and state[p] > 0:
1328 elif p in state and state[p] > 0:
1329 np = state[p]
1329 np = state[p]
1330
1330
1331 # "bases" only record "special" merge bases that cannot be
1331 # "bases" only record "special" merge bases that cannot be
1332 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1332 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1333 # For example:
1333 # For example:
1334 #
1334 #
1335 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1335 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1336 # | C # is B', but merge base for C is B, instead of
1336 # | C # is B', but merge base for C is B, instead of
1337 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1337 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1338 # | B # "state" edges are merged (so there will be an edge from
1338 # | B # "state" edges are merged (so there will be an edge from
1339 # |/ # B to B'), the merge base is still ancestor(C, B') in
1339 # |/ # B to B'), the merge base is still ancestor(C, B') in
1340 # A # the merged graph.
1340 # A # the merged graph.
1341 #
1341 #
1342 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1342 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1343 # which uses "virtual null merge" to explain this situation.
1343 # which uses "virtual null merge" to explain this situation.
1344 if isancestor(p, np):
1344 if isancestor(p, np):
1345 bases[i] = nullrev
1345 bases[i] = nullrev
1346
1346
1347 # If one parent becomes an ancestor of the other, drop the ancestor
1347 # If one parent becomes an ancestor of the other, drop the ancestor
1348 for j, x in enumerate(newps[:i]):
1348 for j, x in enumerate(newps[:i]):
1349 if x == nullrev:
1349 if x == nullrev:
1350 continue
1350 continue
1351 if isancestor(np, x): # CASE-1
1351 if isancestor(np, x): # CASE-1
1352 np = nullrev
1352 np = nullrev
1353 elif isancestor(x, np): # CASE-2
1353 elif isancestor(x, np): # CASE-2
1354 newps[j] = np
1354 newps[j] = np
1355 np = nullrev
1355 np = nullrev
1356 # New parents forming an ancestor relationship does not
1356 # New parents forming an ancestor relationship does not
1357 # mean the old parents have a similar relationship. Do not
1357 # mean the old parents have a similar relationship. Do not
1358 # set bases[x] to nullrev.
1358 # set bases[x] to nullrev.
1359 bases[j], bases[i] = bases[i], bases[j]
1359 bases[j], bases[i] = bases[i], bases[j]
1360
1360
1361 newps[i] = np
1361 newps[i] = np
1362
1362
1363 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1363 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1364 # base. If only p2 changes, merging using unchanged p1 as merge base is
1364 # base. If only p2 changes, merging using unchanged p1 as merge base is
1365 # suboptimal. Therefore swap parents to make the merge sane.
1365 # suboptimal. Therefore swap parents to make the merge sane.
1366 if newps[1] != nullrev and oldps[0] == newps[0]:
1366 if newps[1] != nullrev and oldps[0] == newps[0]:
1367 assert len(newps) == 2 and len(oldps) == 2
1367 assert len(newps) == 2 and len(oldps) == 2
1368 newps.reverse()
1368 newps.reverse()
1369 bases.reverse()
1369 bases.reverse()
1370
1370
1371 # No parent change might be an error because we fail to make rev a
1371 # No parent change might be an error because we fail to make rev a
1372 # descendent of requested dest. This can happen, for example:
1372 # descendent of requested dest. This can happen, for example:
1373 #
1373 #
1374 # C # rebase -r C -d D
1374 # C # rebase -r C -d D
1375 # /| # None of A and B will be changed to D and rebase fails.
1375 # /| # None of A and B will be changed to D and rebase fails.
1376 # A B D
1376 # A B D
1377 if set(newps) == set(oldps) and dest not in newps:
1377 if set(newps) == set(oldps) and dest not in newps:
1378 raise error.Abort(_('cannot rebase %d:%s without '
1378 raise error.Abort(_('cannot rebase %d:%s without '
1379 'moving at least one of its parents')
1379 'moving at least one of its parents')
1380 % (rev, repo[rev]))
1380 % (rev, repo[rev]))
1381
1381
1382 # Source should not be ancestor of dest. The check here guarantees it's
1382 # Source should not be ancestor of dest. The check here guarantees it's
1383 # impossible. With multi-dest, the initial check does not cover complex
1383 # impossible. With multi-dest, the initial check does not cover complex
1384 # cases since we don't have abstractions to dry-run rebase cheaply.
1384 # cases since we don't have abstractions to dry-run rebase cheaply.
1385 if any(p != nullrev and isancestor(rev, p) for p in newps):
1385 if any(p != nullrev and isancestor(rev, p) for p in newps):
1386 raise error.Abort(_('source is ancestor of destination'))
1386 raise error.Abort(_('source is ancestor of destination'))
1387
1387
1388 # "rebasenode" updates to new p1, use the corresponding merge base.
1388 # "rebasenode" updates to new p1, use the corresponding merge base.
1389 if bases[0] != nullrev:
1389 if bases[0] != nullrev:
1390 base = bases[0]
1390 base = bases[0]
1391 else:
1391 else:
1392 base = None
1392 base = None
1393
1393
1394 # Check if the merge will contain unwanted changes. That may happen if
1394 # Check if the merge will contain unwanted changes. That may happen if
1395 # there are multiple special (non-changelog ancestor) merge bases, which
1395 # there are multiple special (non-changelog ancestor) merge bases, which
1396 # cannot be handled well by the 3-way merge algorithm. For example:
1396 # cannot be handled well by the 3-way merge algorithm. For example:
1397 #
1397 #
1398 # F
1398 # F
1399 # /|
1399 # /|
1400 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1400 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1401 # | | # as merge base, the difference between D and F will include
1401 # | | # as merge base, the difference between D and F will include
1402 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1402 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1403 # |/ # chosen, the rebased F will contain B.
1403 # |/ # chosen, the rebased F will contain B.
1404 # A Z
1404 # A Z
1405 #
1405 #
1406 # But our merge base candidates (D and E in above case) could still be
1406 # But our merge base candidates (D and E in above case) could still be
1407 # better than the default (ancestor(F, Z) == null). Therefore still
1407 # better than the default (ancestor(F, Z) == null). Therefore still
1408 # pick one (so choose p1 above).
1408 # pick one (so choose p1 above).
1409 if sum(1 for b in bases if b != nullrev) > 1:
1409 if sum(1 for b in bases if b != nullrev) > 1:
1410 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1410 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1411 for i, base in enumerate(bases):
1411 for i, base in enumerate(bases):
1412 if base == nullrev:
1412 if base == nullrev:
1413 continue
1413 continue
1414 # Revisions in the side (not chosen as merge base) branch that
1414 # Revisions in the side (not chosen as merge base) branch that
1415 # might contain "surprising" contents
1415 # might contain "surprising" contents
1416 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1416 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1417 bases, base, base, dest))
1417 bases, base, base, dest))
1418
1418
1419 # If those revisions are covered by rebaseset, the result is good.
1419 # If those revisions are covered by rebaseset, the result is good.
1420 # A merge in rebaseset would be considered to cover its ancestors.
1420 # A merge in rebaseset would be considered to cover its ancestors.
1421 if siderevs:
1421 if siderevs:
1422 rebaseset = [r for r, d in state.items()
1422 rebaseset = [r for r, d in state.items()
1423 if d > 0 and r not in obsskipped]
1423 if d > 0 and r not in obsskipped]
1424 merges = [r for r in rebaseset
1424 merges = [r for r in rebaseset
1425 if cl.parentrevs(r)[1] != nullrev]
1425 if cl.parentrevs(r)[1] != nullrev]
1426 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1426 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1427 siderevs, merges, rebaseset))
1427 siderevs, merges, rebaseset))
1428
1428
1429 # Choose a merge base that has a minimal number of unwanted revs.
1429 # Choose a merge base that has a minimal number of unwanted revs.
1430 l, i = min((len(revs), i)
1430 l, i = min((len(revs), i)
1431 for i, revs in enumerate(unwanted) if revs is not None)
1431 for i, revs in enumerate(unwanted) if revs is not None)
1432 base = bases[i]
1432 base = bases[i]
1433
1433
1434 # newps[0] should match merge base if possible. Currently, if newps[i]
1434 # newps[0] should match merge base if possible. Currently, if newps[i]
1435 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1435 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1436 # the other's ancestor. In that case, it's fine to not swap newps here.
1436 # the other's ancestor. In that case, it's fine to not swap newps here.
1437 # (see CASE-1 and CASE-2 above)
1437 # (see CASE-1 and CASE-2 above)
1438 if i != 0 and newps[i] != nullrev:
1438 if i != 0 and newps[i] != nullrev:
1439 newps[0], newps[i] = newps[i], newps[0]
1439 newps[0], newps[i] = newps[i], newps[0]
1440
1440
1441 # The merge will include unwanted revisions. Abort now. Revisit this if
1441 # The merge will include unwanted revisions. Abort now. Revisit this if
1442 # we have a more advanced merge algorithm that handles multiple bases.
1442 # we have a more advanced merge algorithm that handles multiple bases.
1443 if l > 0:
1443 if l > 0:
1444 unwanteddesc = _(' or ').join(
1444 unwanteddesc = _(' or ').join(
1445 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1445 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1446 for revs in unwanted if revs is not None))
1446 for revs in unwanted if revs is not None))
1447 raise error.Abort(
1447 raise error.Abort(
1448 _('rebasing %d:%s will include unwanted changes from %s')
1448 _('rebasing %d:%s will include unwanted changes from %s')
1449 % (rev, repo[rev], unwanteddesc))
1449 % (rev, repo[rev], unwanteddesc))
1450
1450
1451 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1451 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1452
1452
1453 return newps[0], newps[1], base
1453 return newps[0], newps[1], base
1454
1454
1455 def isagitpatch(repo, patchname):
1455 def isagitpatch(repo, patchname):
1456 'Return true if the given patch is in git format'
1456 'Return true if the given patch is in git format'
1457 mqpatch = os.path.join(repo.mq.path, patchname)
1457 mqpatch = os.path.join(repo.mq.path, patchname)
1458 for line in patch.linereader(open(mqpatch, 'rb')):
1458 for line in patch.linereader(open(mqpatch, 'rb')):
1459 if line.startswith('diff --git'):
1459 if line.startswith('diff --git'):
1460 return True
1460 return True
1461 return False
1461 return False
1462
1462
1463 def updatemq(repo, state, skipped, **opts):
1463 def updatemq(repo, state, skipped, **opts):
1464 'Update rebased mq patches - finalize and then import them'
1464 'Update rebased mq patches - finalize and then import them'
1465 mqrebase = {}
1465 mqrebase = {}
1466 mq = repo.mq
1466 mq = repo.mq
1467 original_series = mq.fullseries[:]
1467 original_series = mq.fullseries[:]
1468 skippedpatches = set()
1468 skippedpatches = set()
1469
1469
1470 for p in mq.applied:
1470 for p in mq.applied:
1471 rev = repo[p.node].rev()
1471 rev = repo[p.node].rev()
1472 if rev in state:
1472 if rev in state:
1473 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1473 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1474 (rev, p.name))
1474 (rev, p.name))
1475 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1475 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1476 else:
1476 else:
1477 # Applied but not rebased, not sure this should happen
1477 # Applied but not rebased, not sure this should happen
1478 skippedpatches.add(p.name)
1478 skippedpatches.add(p.name)
1479
1479
1480 if mqrebase:
1480 if mqrebase:
1481 mq.finish(repo, mqrebase.keys())
1481 mq.finish(repo, mqrebase.keys())
1482
1482
1483 # We must start import from the newest revision
1483 # We must start import from the newest revision
1484 for rev in sorted(mqrebase, reverse=True):
1484 for rev in sorted(mqrebase, reverse=True):
1485 if rev not in skipped:
1485 if rev not in skipped:
1486 name, isgit = mqrebase[rev]
1486 name, isgit = mqrebase[rev]
1487 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1487 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1488 (name, state[rev], repo[state[rev]]))
1488 (name, state[rev], repo[state[rev]]))
1489 mq.qimport(repo, (), patchname=name, git=isgit,
1489 mq.qimport(repo, (), patchname=name, git=isgit,
1490 rev=["%d" % state[rev]])
1490 rev=["%d" % state[rev]])
1491 else:
1491 else:
1492 # Rebased and skipped
1492 # Rebased and skipped
1493 skippedpatches.add(mqrebase[rev][0])
1493 skippedpatches.add(mqrebase[rev][0])
1494
1494
1495 # Patches were either applied and rebased and imported in
1495 # Patches were either applied and rebased and imported in
1496 # order, applied and removed or unapplied. Discard the removed
1496 # order, applied and removed or unapplied. Discard the removed
1497 # ones while preserving the original series order and guards.
1497 # ones while preserving the original series order and guards.
1498 newseries = [s for s in original_series
1498 newseries = [s for s in original_series
1499 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1499 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1500 mq.fullseries[:] = newseries
1500 mq.fullseries[:] = newseries
1501 mq.seriesdirty = True
1501 mq.seriesdirty = True
1502 mq.savedirty()
1502 mq.savedirty()
1503
1503
1504 def storecollapsemsg(repo, collapsemsg):
1504 def storecollapsemsg(repo, collapsemsg):
1505 'Store the collapse message to allow recovery'
1505 'Store the collapse message to allow recovery'
1506 collapsemsg = collapsemsg or ''
1506 collapsemsg = collapsemsg or ''
1507 f = repo.vfs("last-message.txt", "w")
1507 f = repo.vfs("last-message.txt", "w")
1508 f.write("%s\n" % collapsemsg)
1508 f.write("%s\n" % collapsemsg)
1509 f.close()
1509 f.close()
1510
1510
1511 def clearcollapsemsg(repo):
1511 def clearcollapsemsg(repo):
1512 'Remove collapse message file'
1512 'Remove collapse message file'
1513 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1513 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1514
1514
1515 def restorecollapsemsg(repo, isabort):
1515 def restorecollapsemsg(repo, isabort):
1516 'Restore previously stored collapse message'
1516 'Restore previously stored collapse message'
1517 try:
1517 try:
1518 f = repo.vfs("last-message.txt")
1518 f = repo.vfs("last-message.txt")
1519 collapsemsg = f.readline().strip()
1519 collapsemsg = f.readline().strip()
1520 f.close()
1520 f.close()
1521 except IOError as err:
1521 except IOError as err:
1522 if err.errno != errno.ENOENT:
1522 if err.errno != errno.ENOENT:
1523 raise
1523 raise
1524 if isabort:
1524 if isabort:
1525 # Oh well, just abort like normal
1525 # Oh well, just abort like normal
1526 collapsemsg = ''
1526 collapsemsg = ''
1527 else:
1527 else:
1528 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1528 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1529 return collapsemsg
1529 return collapsemsg
1530
1530
1531 def clearstatus(repo):
1531 def clearstatus(repo):
1532 'Remove the status files'
1532 'Remove the status files'
1533 # Make sure the active transaction won't write the state file
1533 # Make sure the active transaction won't write the state file
1534 tr = repo.currenttransaction()
1534 tr = repo.currenttransaction()
1535 if tr:
1535 if tr:
1536 tr.removefilegenerator('rebasestate')
1536 tr.removefilegenerator('rebasestate')
1537 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1537 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1538
1538
1539 def needupdate(repo, state):
1539 def needupdate(repo, state):
1540 '''check whether we should `update --clean` away from a merge, or if
1540 '''check whether we should `update --clean` away from a merge, or if
1541 somehow the working dir got forcibly updated, e.g. by older hg'''
1541 somehow the working dir got forcibly updated, e.g. by older hg'''
1542 parents = [p.rev() for p in repo[None].parents()]
1542 parents = [p.rev() for p in repo[None].parents()]
1543
1543
1544 # Are we in a merge state at all?
1544 # Are we in a merge state at all?
1545 if len(parents) < 2:
1545 if len(parents) < 2:
1546 return False
1546 return False
1547
1547
1548 # We should be standing on the first as-of-yet unrebased commit.
1548 # We should be standing on the first as-of-yet unrebased commit.
1549 firstunrebased = min([old for old, new in state.iteritems()
1549 firstunrebased = min([old for old, new in state.iteritems()
1550 if new == nullrev])
1550 if new == nullrev])
1551 if firstunrebased in parents:
1551 if firstunrebased in parents:
1552 return True
1552 return True
1553
1553
1554 return False
1554 return False
1555
1555
1556 def abort(repo, originalwd, destmap, state, activebookmark=None):
1556 def abort(repo, originalwd, destmap, state, activebookmark=None):
1557 '''Restore the repository to its original state. Additional args:
1557 '''Restore the repository to its original state. Additional args:
1558
1558
1559 activebookmark: the name of the bookmark that should be active after the
1559 activebookmark: the name of the bookmark that should be active after the
1560 restore'''
1560 restore'''
1561
1561
1562 try:
1562 try:
1563 # If the first commits in the rebased set get skipped during the rebase,
1563 # If the first commits in the rebased set get skipped during the rebase,
1564 # their values within the state mapping will be the dest rev id. The
1564 # their values within the state mapping will be the dest rev id. The
1565 # rebased list must must not contain the dest rev (issue4896)
1565 # rebased list must must not contain the dest rev (issue4896)
1566 rebased = [s for r, s in state.items()
1566 rebased = [s for r, s in state.items()
1567 if s >= 0 and s != r and s != destmap[r]]
1567 if s >= 0 and s != r and s != destmap[r]]
1568 immutable = [d for d in rebased if not repo[d].mutable()]
1568 immutable = [d for d in rebased if not repo[d].mutable()]
1569 cleanup = True
1569 cleanup = True
1570 if immutable:
1570 if immutable:
1571 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1571 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1572 % ', '.join(bytes(repo[r]) for r in immutable),
1572 % ', '.join(bytes(repo[r]) for r in immutable),
1573 hint=_("see 'hg help phases' for details"))
1573 hint=_("see 'hg help phases' for details"))
1574 cleanup = False
1574 cleanup = False
1575
1575
1576 descendants = set()
1576 descendants = set()
1577 if rebased:
1577 if rebased:
1578 descendants = set(repo.changelog.descendants(rebased))
1578 descendants = set(repo.changelog.descendants(rebased))
1579 if descendants - set(rebased):
1579 if descendants - set(rebased):
1580 repo.ui.warn(_("warning: new changesets detected on destination "
1580 repo.ui.warn(_("warning: new changesets detected on destination "
1581 "branch, can't strip\n"))
1581 "branch, can't strip\n"))
1582 cleanup = False
1582 cleanup = False
1583
1583
1584 if cleanup:
1584 if cleanup:
1585 shouldupdate = False
1585 shouldupdate = False
1586 if rebased:
1586 if rebased:
1587 strippoints = [
1587 strippoints = [
1588 c.node() for c in repo.set('roots(%ld)', rebased)]
1588 c.node() for c in repo.set('roots(%ld)', rebased)]
1589
1589
1590 updateifonnodes = set(rebased)
1590 updateifonnodes = set(rebased)
1591 updateifonnodes.update(destmap.values())
1591 updateifonnodes.update(destmap.values())
1592 updateifonnodes.add(originalwd)
1592 updateifonnodes.add(originalwd)
1593 shouldupdate = repo['.'].rev() in updateifonnodes
1593 shouldupdate = repo['.'].rev() in updateifonnodes
1594
1594
1595 # Update away from the rebase if necessary
1595 # Update away from the rebase if necessary
1596 if shouldupdate or needupdate(repo, state):
1596 if shouldupdate or needupdate(repo, state):
1597 mergemod.update(repo, originalwd, False, True)
1597 mergemod.update(repo, originalwd, False, True)
1598
1598
1599 # Strip from the first rebased revision
1599 # Strip from the first rebased revision
1600 if rebased:
1600 if rebased:
1601 repair.strip(repo.ui, repo, strippoints)
1601 repair.strip(repo.ui, repo, strippoints)
1602
1602
1603 if activebookmark and activebookmark in repo._bookmarks:
1603 if activebookmark and activebookmark in repo._bookmarks:
1604 bookmarks.activate(repo, activebookmark)
1604 bookmarks.activate(repo, activebookmark)
1605
1605
1606 finally:
1606 finally:
1607 clearstatus(repo)
1607 clearstatus(repo)
1608 clearcollapsemsg(repo)
1608 clearcollapsemsg(repo)
1609 repo.ui.warn(_('rebase aborted\n'))
1609 repo.ui.warn(_('rebase aborted\n'))
1610 return 0
1610 return 0
1611
1611
1612 def sortsource(destmap):
1612 def sortsource(destmap):
1613 """yield source revisions in an order that we only rebase things once
1613 """yield source revisions in an order that we only rebase things once
1614
1614
1615 If source and destination overlaps, we should filter out revisions
1615 If source and destination overlaps, we should filter out revisions
1616 depending on other revisions which hasn't been rebased yet.
1616 depending on other revisions which hasn't been rebased yet.
1617
1617
1618 Yield a sorted list of revisions each time.
1618 Yield a sorted list of revisions each time.
1619
1619
1620 For example, when rebasing A to B, B to C. This function yields [B], then
1620 For example, when rebasing A to B, B to C. This function yields [B], then
1621 [A], indicating B needs to be rebased first.
1621 [A], indicating B needs to be rebased first.
1622
1622
1623 Raise if there is a cycle so the rebase is impossible.
1623 Raise if there is a cycle so the rebase is impossible.
1624 """
1624 """
1625 srcset = set(destmap)
1625 srcset = set(destmap)
1626 while srcset:
1626 while srcset:
1627 srclist = sorted(srcset)
1627 srclist = sorted(srcset)
1628 result = []
1628 result = []
1629 for r in srclist:
1629 for r in srclist:
1630 if destmap[r] not in srcset:
1630 if destmap[r] not in srcset:
1631 result.append(r)
1631 result.append(r)
1632 if not result:
1632 if not result:
1633 raise error.Abort(_('source and destination form a cycle'))
1633 raise error.Abort(_('source and destination form a cycle'))
1634 srcset -= set(result)
1634 srcset -= set(result)
1635 yield result
1635 yield result
1636
1636
1637 def buildstate(repo, destmap, collapse):
1637 def buildstate(repo, destmap, collapse):
1638 '''Define which revisions are going to be rebased and where
1638 '''Define which revisions are going to be rebased and where
1639
1639
1640 repo: repo
1640 repo: repo
1641 destmap: {srcrev: destrev}
1641 destmap: {srcrev: destrev}
1642 '''
1642 '''
1643 rebaseset = destmap.keys()
1643 rebaseset = destmap.keys()
1644 originalwd = repo['.'].rev()
1644 originalwd = repo['.'].rev()
1645
1645
1646 # This check isn't strictly necessary, since mq detects commits over an
1646 # This check isn't strictly necessary, since mq detects commits over an
1647 # applied patch. But it prevents messing up the working directory when
1647 # applied patch. But it prevents messing up the working directory when
1648 # a partially completed rebase is blocked by mq.
1648 # a partially completed rebase is blocked by mq.
1649 if 'qtip' in repo.tags():
1649 if 'qtip' in repo.tags():
1650 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1650 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1651 if set(destmap.values()) & mqapplied:
1651 if set(destmap.values()) & mqapplied:
1652 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1652 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1653
1653
1654 # Get "cycle" error early by exhausting the generator.
1654 # Get "cycle" error early by exhausting the generator.
1655 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1655 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1656 if not sortedsrc:
1656 if not sortedsrc:
1657 raise error.Abort(_('no matching revisions'))
1657 raise error.Abort(_('no matching revisions'))
1658
1658
1659 # Only check the first batch of revisions to rebase not depending on other
1659 # Only check the first batch of revisions to rebase not depending on other
1660 # rebaseset. This means "source is ancestor of destination" for the second
1660 # rebaseset. This means "source is ancestor of destination" for the second
1661 # (and following) batches of revisions are not checked here. We rely on
1661 # (and following) batches of revisions are not checked here. We rely on
1662 # "defineparents" to do that check.
1662 # "defineparents" to do that check.
1663 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1663 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1664 if not roots:
1664 if not roots:
1665 raise error.Abort(_('no matching revisions'))
1665 raise error.Abort(_('no matching revisions'))
1666 def revof(r):
1666 def revof(r):
1667 return r.rev()
1667 return r.rev()
1668 roots = sorted(roots, key=revof)
1668 roots = sorted(roots, key=revof)
1669 state = dict.fromkeys(rebaseset, revtodo)
1669 state = dict.fromkeys(rebaseset, revtodo)
1670 emptyrebase = (len(sortedsrc) == 1)
1670 emptyrebase = (len(sortedsrc) == 1)
1671 for root in roots:
1671 for root in roots:
1672 dest = repo[destmap[root.rev()]]
1672 dest = repo[destmap[root.rev()]]
1673 commonbase = root.ancestor(dest)
1673 commonbase = root.ancestor(dest)
1674 if commonbase == root:
1674 if commonbase == root:
1675 raise error.Abort(_('source is ancestor of destination'))
1675 raise error.Abort(_('source is ancestor of destination'))
1676 if commonbase == dest:
1676 if commonbase == dest:
1677 wctx = repo[None]
1677 wctx = repo[None]
1678 if dest == wctx.p1():
1678 if dest == wctx.p1():
1679 # when rebasing to '.', it will use the current wd branch name
1679 # when rebasing to '.', it will use the current wd branch name
1680 samebranch = root.branch() == wctx.branch()
1680 samebranch = root.branch() == wctx.branch()
1681 else:
1681 else:
1682 samebranch = root.branch() == dest.branch()
1682 samebranch = root.branch() == dest.branch()
1683 if not collapse and samebranch and dest in root.parents():
1683 if not collapse and samebranch and dest in root.parents():
1684 # mark the revision as done by setting its new revision
1684 # mark the revision as done by setting its new revision
1685 # equal to its old (current) revisions
1685 # equal to its old (current) revisions
1686 state[root.rev()] = root.rev()
1686 state[root.rev()] = root.rev()
1687 repo.ui.debug('source is a child of destination\n')
1687 repo.ui.debug('source is a child of destination\n')
1688 continue
1688 continue
1689
1689
1690 emptyrebase = False
1690 emptyrebase = False
1691 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1691 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1692 if emptyrebase:
1692 if emptyrebase:
1693 return None
1693 return None
1694 for rev in sorted(state):
1694 for rev in sorted(state):
1695 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1695 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1696 # if all parents of this revision are done, then so is this revision
1696 # if all parents of this revision are done, then so is this revision
1697 if parents and all((state.get(p) == p for p in parents)):
1697 if parents and all((state.get(p) == p for p in parents)):
1698 state[rev] = rev
1698 state[rev] = rev
1699 return originalwd, destmap, state
1699 return originalwd, destmap, state
1700
1700
1701 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1701 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1702 keepf=False, fm=None):
1702 keepf=False, fm=None):
1703 """dispose of rebased revision at the end of the rebase
1703 """dispose of rebased revision at the end of the rebase
1704
1704
1705 If `collapsedas` is not None, the rebase was a collapse whose result if the
1705 If `collapsedas` is not None, the rebase was a collapse whose result if the
1706 `collapsedas` node.
1706 `collapsedas` node.
1707
1707
1708 If `keepf` is not True, the rebase has --keep set and no nodes should be
1708 If `keepf` is not True, the rebase has --keep set and no nodes should be
1709 removed (but bookmarks still need to be moved).
1709 removed (but bookmarks still need to be moved).
1710 """
1710 """
1711 tonode = repo.changelog.node
1711 tonode = repo.changelog.node
1712 replacements = {}
1712 replacements = {}
1713 moves = {}
1713 moves = {}
1714 for rev, newrev in sorted(state.items()):
1714 for rev, newrev in sorted(state.items()):
1715 if newrev >= 0 and newrev != rev:
1715 if newrev >= 0 and newrev != rev:
1716 oldnode = tonode(rev)
1716 oldnode = tonode(rev)
1717 newnode = collapsedas or tonode(newrev)
1717 newnode = collapsedas or tonode(newrev)
1718 moves[oldnode] = newnode
1718 moves[oldnode] = newnode
1719 if not keepf:
1719 if not keepf:
1720 if rev in skipped:
1720 if rev in skipped:
1721 succs = ()
1721 succs = ()
1722 else:
1722 else:
1723 succs = (newnode,)
1723 succs = (newnode,)
1724 replacements[oldnode] = succs
1724 replacements[oldnode] = succs
1725 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1725 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1726 if fm:
1726 if fm:
1727 hf = fm.hexfunc
1727 hf = fm.hexfunc
1728 fl = fm.formatlist
1728 fl = fm.formatlist
1729 fd = fm.formatdict
1729 fd = fm.formatdict
1730 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1730 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1731 for oldn, newn in replacements.iteritems()},
1731 for oldn, newn in replacements.iteritems()},
1732 key="oldnode", value="newnodes")
1732 key="oldnode", value="newnodes")
1733 fm.data(nodechanges=nodechanges)
1733 fm.data(nodechanges=nodechanges)
1734
1734
1735 def pullrebase(orig, ui, repo, *args, **opts):
1735 def pullrebase(orig, ui, repo, *args, **opts):
1736 'Call rebase after pull if the latter has been invoked with --rebase'
1736 'Call rebase after pull if the latter has been invoked with --rebase'
1737 ret = None
1737 ret = None
1738 if opts.get(r'rebase'):
1738 if opts.get(r'rebase'):
1739 if ui.configbool('commands', 'rebase.requiredest'):
1739 if ui.configbool('commands', 'rebase.requiredest'):
1740 msg = _('rebase destination required by configuration')
1740 msg = _('rebase destination required by configuration')
1741 hint = _('use hg pull followed by hg rebase -d DEST')
1741 hint = _('use hg pull followed by hg rebase -d DEST')
1742 raise error.Abort(msg, hint=hint)
1742 raise error.Abort(msg, hint=hint)
1743
1743
1744 with repo.wlock(), repo.lock():
1744 with repo.wlock(), repo.lock():
1745 if opts.get(r'update'):
1745 if opts.get(r'update'):
1746 del opts[r'update']
1746 del opts[r'update']
1747 ui.debug('--update and --rebase are not compatible, ignoring '
1747 ui.debug('--update and --rebase are not compatible, ignoring '
1748 'the update flag\n')
1748 'the update flag\n')
1749
1749
1750 cmdutil.checkunfinished(repo)
1750 cmdutil.checkunfinished(repo)
1751 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1751 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1752 'please commit or shelve your changes first'))
1752 'please commit or shelve your changes first'))
1753
1753
1754 revsprepull = len(repo)
1754 revsprepull = len(repo)
1755 origpostincoming = commands.postincoming
1755 origpostincoming = commands.postincoming
1756 def _dummy(*args, **kwargs):
1756 def _dummy(*args, **kwargs):
1757 pass
1757 pass
1758 commands.postincoming = _dummy
1758 commands.postincoming = _dummy
1759 try:
1759 try:
1760 ret = orig(ui, repo, *args, **opts)
1760 ret = orig(ui, repo, *args, **opts)
1761 finally:
1761 finally:
1762 commands.postincoming = origpostincoming
1762 commands.postincoming = origpostincoming
1763 revspostpull = len(repo)
1763 revspostpull = len(repo)
1764 if revspostpull > revsprepull:
1764 if revspostpull > revsprepull:
1765 # --rev option from pull conflict with rebase own --rev
1765 # --rev option from pull conflict with rebase own --rev
1766 # dropping it
1766 # dropping it
1767 if r'rev' in opts:
1767 if r'rev' in opts:
1768 del opts[r'rev']
1768 del opts[r'rev']
1769 # positional argument from pull conflicts with rebase's own
1769 # positional argument from pull conflicts with rebase's own
1770 # --source.
1770 # --source.
1771 if r'source' in opts:
1771 if r'source' in opts:
1772 del opts[r'source']
1772 del opts[r'source']
1773 # revsprepull is the len of the repo, not revnum of tip.
1773 # revsprepull is the len of the repo, not revnum of tip.
1774 destspace = list(repo.changelog.revs(start=revsprepull))
1774 destspace = list(repo.changelog.revs(start=revsprepull))
1775 opts[r'_destspace'] = destspace
1775 opts[r'_destspace'] = destspace
1776 try:
1776 try:
1777 rebase(ui, repo, **opts)
1777 rebase(ui, repo, **opts)
1778 except error.NoMergeDestAbort:
1778 except error.NoMergeDestAbort:
1779 # we can maybe update instead
1779 # we can maybe update instead
1780 rev, _a, _b = destutil.destupdate(repo)
1780 rev, _a, _b = destutil.destupdate(repo)
1781 if rev == repo['.'].rev():
1781 if rev == repo['.'].rev():
1782 ui.status(_('nothing to rebase\n'))
1782 ui.status(_('nothing to rebase\n'))
1783 else:
1783 else:
1784 ui.status(_('nothing to rebase - updating instead\n'))
1784 ui.status(_('nothing to rebase - updating instead\n'))
1785 # not passing argument to get the bare update behavior
1785 # not passing argument to get the bare update behavior
1786 # with warning and trumpets
1786 # with warning and trumpets
1787 commands.update(ui, repo)
1787 commands.update(ui, repo)
1788 else:
1788 else:
1789 if opts.get(r'tool'):
1789 if opts.get(r'tool'):
1790 raise error.Abort(_('--tool can only be used with --rebase'))
1790 raise error.Abort(_('--tool can only be used with --rebase'))
1791 ret = orig(ui, repo, *args, **opts)
1791 ret = orig(ui, repo, *args, **opts)
1792
1792
1793 return ret
1793 return ret
1794
1794
1795 def _filterobsoleterevs(repo, revs):
1795 def _filterobsoleterevs(repo, revs):
1796 """returns a set of the obsolete revisions in revs"""
1796 """returns a set of the obsolete revisions in revs"""
1797 return set(r for r in revs if repo[r].obsolete())
1797 return set(r for r in revs if repo[r].obsolete())
1798
1798
1799 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1799 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1800 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1800 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1801
1801
1802 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1802 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1803 obsolete nodes to be rebased given in `rebaseobsrevs`.
1803 obsolete nodes to be rebased given in `rebaseobsrevs`.
1804
1804
1805 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1805 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1806 without a successor in destination.
1806 without a successor in destination.
1807
1807
1808 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1808 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1809 obsolete successors.
1809 obsolete successors.
1810 """
1810 """
1811 obsoletenotrebased = {}
1811 obsoletenotrebased = {}
1812 obsoletewithoutsuccessorindestination = set([])
1812 obsoletewithoutsuccessorindestination = set([])
1813 obsoleteextinctsuccessors = set([])
1813 obsoleteextinctsuccessors = set([])
1814
1814
1815 assert repo.filtername is None
1815 assert repo.filtername is None
1816 cl = repo.changelog
1816 cl = repo.changelog
1817 nodemap = cl.nodemap
1817 nodemap = cl.nodemap
1818 extinctnodes = set(cl.node(r) for r in repo.revs('extinct()'))
1818 extinctnodes = set(cl.node(r) for r in repo.revs('extinct()'))
1819 for srcrev in rebaseobsrevs:
1819 for srcrev in rebaseobsrevs:
1820 srcnode = cl.node(srcrev)
1820 srcnode = cl.node(srcrev)
1821 destnode = cl.node(destmap[srcrev])
1821 destnode = cl.node(destmap[srcrev])
1822 # XXX: more advanced APIs are required to handle split correctly
1822 # XXX: more advanced APIs are required to handle split correctly
1823 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1823 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1824 # obsutil.allsuccessors includes node itself
1824 # obsutil.allsuccessors includes node itself
1825 successors.remove(srcnode)
1825 successors.remove(srcnode)
1826 if successors.issubset(extinctnodes):
1826 if successors.issubset(extinctnodes):
1827 # all successors are extinct
1827 # all successors are extinct
1828 obsoleteextinctsuccessors.add(srcrev)
1828 obsoleteextinctsuccessors.add(srcrev)
1829 if not successors:
1829 if not successors:
1830 # no successor
1830 # no successor
1831 obsoletenotrebased[srcrev] = None
1831 obsoletenotrebased[srcrev] = None
1832 else:
1832 else:
1833 for succnode in successors:
1833 for succnode in successors:
1834 if succnode not in nodemap:
1834 if succnode not in nodemap:
1835 continue
1835 continue
1836 if cl.isancestor(succnode, destnode):
1836 if cl.isancestor(succnode, destnode):
1837 obsoletenotrebased[srcrev] = nodemap[succnode]
1837 obsoletenotrebased[srcrev] = nodemap[succnode]
1838 break
1838 break
1839 else:
1839 else:
1840 # If 'srcrev' has a successor in rebase set but none in
1840 # If 'srcrev' has a successor in rebase set but none in
1841 # destination (which would be catched above), we shall skip it
1841 # destination (which would be catched above), we shall skip it
1842 # and its descendants to avoid divergence.
1842 # and its descendants to avoid divergence.
1843 if any(nodemap[s] in destmap for s in successors
1843 if any(nodemap[s] in destmap for s in successors
1844 if s in nodemap):
1844 if s in nodemap):
1845 obsoletewithoutsuccessorindestination.add(srcrev)
1845 obsoletewithoutsuccessorindestination.add(srcrev)
1846
1846
1847 return (
1847 return (
1848 obsoletenotrebased,
1848 obsoletenotrebased,
1849 obsoletewithoutsuccessorindestination,
1849 obsoletewithoutsuccessorindestination,
1850 obsoleteextinctsuccessors,
1850 obsoleteextinctsuccessors,
1851 )
1851 )
1852
1852
1853 def summaryhook(ui, repo):
1853 def summaryhook(ui, repo):
1854 if not repo.vfs.exists('rebasestate'):
1854 if not repo.vfs.exists('rebasestate'):
1855 return
1855 return
1856 try:
1856 try:
1857 rbsrt = rebaseruntime(repo, ui, {})
1857 rbsrt = rebaseruntime(repo, ui, {})
1858 rbsrt.restorestatus()
1858 rbsrt.restorestatus()
1859 state = rbsrt.state
1859 state = rbsrt.state
1860 except error.RepoLookupError:
1860 except error.RepoLookupError:
1861 # i18n: column positioning for "hg summary"
1861 # i18n: column positioning for "hg summary"
1862 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1862 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1863 ui.write(msg)
1863 ui.write(msg)
1864 return
1864 return
1865 numrebased = len([i for i in state.itervalues() if i >= 0])
1865 numrebased = len([i for i in state.itervalues() if i >= 0])
1866 # i18n: column positioning for "hg summary"
1866 # i18n: column positioning for "hg summary"
1867 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1867 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1868 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1868 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1869 ui.label(_('%d remaining'), 'rebase.remaining') %
1869 ui.label(_('%d remaining'), 'rebase.remaining') %
1870 (len(state) - numrebased)))
1870 (len(state) - numrebased)))
1871
1871
1872 def uisetup(ui):
1872 def uisetup(ui):
1873 #Replace pull with a decorator to provide --rebase option
1873 #Replace pull with a decorator to provide --rebase option
1874 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1874 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1875 entry[1].append(('', 'rebase', None,
1875 entry[1].append(('', 'rebase', None,
1876 _("rebase working directory to branch head")))
1876 _("rebase working directory to branch head")))
1877 entry[1].append(('t', 'tool', '',
1877 entry[1].append(('t', 'tool', '',
1878 _("specify merge tool for rebase")))
1878 _("specify merge tool for rebase")))
1879 cmdutil.summaryhooks.add('rebase', summaryhook)
1879 cmdutil.summaryhooks.add('rebase', summaryhook)
1880 cmdutil.unfinishedstates.append(
1880 cmdutil.unfinishedstates.append(
1881 ['rebasestate', False, False, _('rebase in progress'),
1881 ['rebasestate', False, False, _('rebase in progress'),
1882 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1882 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1883 cmdutil.afterresolvedstates.append(
1883 cmdutil.afterresolvedstates.append(
1884 ['rebasestate', _('hg rebase --continue')])
1884 ['rebasestate', _('hg rebase --continue')])
General Comments 0
You need to be logged in to leave comments. Login now