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