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