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