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