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