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