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