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