##// END OF EJS Templates
rebase: inline single-use variables passed to _definedestmap()...
Martin von Zweigbergk -
r44384:9fb9f3a5 default
parent child Browse files
Show More
@@ -1,2300 +1,2295 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 action = cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
1022 action = cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
1023 if action:
1023 if action:
1024 cmdutil.check_incompatible_arguments(
1024 cmdutil.check_incompatible_arguments(
1025 opts, action, b'confirm', b'dry_run'
1025 opts, action, b'confirm', b'dry_run'
1026 )
1026 )
1027 cmdutil.check_incompatible_arguments(
1027 cmdutil.check_incompatible_arguments(
1028 opts, action, b'rev', b'source', b'base', b'dest'
1028 opts, action, b'rev', b'source', b'base', b'dest'
1029 )
1029 )
1030 cmdutil.check_at_most_one_arg(opts, b'confirm', b'dry_run')
1030 cmdutil.check_at_most_one_arg(opts, b'confirm', b'dry_run')
1031 cmdutil.check_at_most_one_arg(opts, b'rev', b'source', b'base')
1031 cmdutil.check_at_most_one_arg(opts, b'rev', b'source', b'base')
1032
1032
1033 if action or repo.currenttransaction() is not None:
1033 if action or repo.currenttransaction() is not None:
1034 # in-memory rebase is not compatible with resuming rebases.
1034 # in-memory rebase is not compatible with resuming rebases.
1035 # (Or if it is run within a transaction, since the restart logic can
1035 # (Or if it is run within a transaction, since the restart logic can
1036 # fail the entire transaction.)
1036 # fail the entire transaction.)
1037 inmemory = False
1037 inmemory = False
1038
1038
1039 if opts.get(b'auto_orphans'):
1039 if opts.get(b'auto_orphans'):
1040 disallowed_opts = set(opts) - {b'auto_orphans'}
1040 disallowed_opts = set(opts) - {b'auto_orphans'}
1041 cmdutil.check_incompatible_arguments(
1041 cmdutil.check_incompatible_arguments(
1042 opts, b'auto_orphans', *disallowed_opts
1042 opts, b'auto_orphans', *disallowed_opts
1043 )
1043 )
1044
1044
1045 userrevs = list(repo.revs(opts.get(b'auto_orphans')))
1045 userrevs = list(repo.revs(opts.get(b'auto_orphans')))
1046 opts[b'rev'] = [revsetlang.formatspec(b'%ld and orphan()', userrevs)]
1046 opts[b'rev'] = [revsetlang.formatspec(b'%ld and orphan()', userrevs)]
1047 opts[b'dest'] = b'_destautoorphanrebase(SRC)'
1047 opts[b'dest'] = b'_destautoorphanrebase(SRC)'
1048
1048
1049 if opts.get(b'dry_run') or opts.get(b'confirm'):
1049 if opts.get(b'dry_run') or opts.get(b'confirm'):
1050 return _dryrunrebase(ui, repo, action, opts)
1050 return _dryrunrebase(ui, repo, action, opts)
1051 elif action == b'stop':
1051 elif action == b'stop':
1052 rbsrt = rebaseruntime(repo, ui)
1052 rbsrt = rebaseruntime(repo, ui)
1053 with repo.wlock(), repo.lock():
1053 with repo.wlock(), repo.lock():
1054 rbsrt.restorestatus()
1054 rbsrt.restorestatus()
1055 if rbsrt.collapsef:
1055 if rbsrt.collapsef:
1056 raise error.Abort(_(b"cannot stop in --collapse session"))
1056 raise error.Abort(_(b"cannot stop in --collapse session"))
1057 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1057 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1058 if not (rbsrt.keepf or allowunstable):
1058 if not (rbsrt.keepf or allowunstable):
1059 raise error.Abort(
1059 raise error.Abort(
1060 _(
1060 _(
1061 b"cannot remove original changesets with"
1061 b"cannot remove original changesets with"
1062 b" unrebased descendants"
1062 b" unrebased descendants"
1063 ),
1063 ),
1064 hint=_(
1064 hint=_(
1065 b'either enable obsmarkers to allow unstable '
1065 b'either enable obsmarkers to allow unstable '
1066 b'revisions or use --keep to keep original '
1066 b'revisions or use --keep to keep original '
1067 b'changesets'
1067 b'changesets'
1068 ),
1068 ),
1069 )
1069 )
1070 if needupdate(repo, rbsrt.state):
1070 if needupdate(repo, rbsrt.state):
1071 # update to the current working revision
1071 # update to the current working revision
1072 # to clear interrupted merge
1072 # to clear interrupted merge
1073 hg.updaterepo(repo, rbsrt.originalwd, overwrite=True)
1073 hg.updaterepo(repo, rbsrt.originalwd, overwrite=True)
1074 rbsrt._finishrebase()
1074 rbsrt._finishrebase()
1075 return 0
1075 return 0
1076 elif inmemory:
1076 elif inmemory:
1077 try:
1077 try:
1078 # in-memory merge doesn't support conflicts, so if we hit any, abort
1078 # in-memory merge doesn't support conflicts, so if we hit any, abort
1079 # and re-run as an on-disk merge.
1079 # and re-run as an on-disk merge.
1080 overrides = {(b'rebase', b'singletransaction'): True}
1080 overrides = {(b'rebase', b'singletransaction'): True}
1081 with ui.configoverride(overrides, b'rebase'):
1081 with ui.configoverride(overrides, b'rebase'):
1082 return _dorebase(ui, repo, action, opts, inmemory=inmemory)
1082 return _dorebase(ui, repo, action, opts, inmemory=inmemory)
1083 except error.InMemoryMergeConflictsError:
1083 except error.InMemoryMergeConflictsError:
1084 ui.warn(
1084 ui.warn(
1085 _(
1085 _(
1086 b'hit merge conflicts; re-running rebase without in-memory'
1086 b'hit merge conflicts; re-running rebase without in-memory'
1087 b' merge\n'
1087 b' merge\n'
1088 )
1088 )
1089 )
1089 )
1090 # TODO: Make in-memory merge not use the on-disk merge state, so
1090 # TODO: Make in-memory merge not use the on-disk merge state, so
1091 # we don't have to clean it here
1091 # we don't have to clean it here
1092 mergemod.mergestate.clean(repo)
1092 mergemod.mergestate.clean(repo)
1093 clearstatus(repo)
1093 clearstatus(repo)
1094 clearcollapsemsg(repo)
1094 clearcollapsemsg(repo)
1095 return _dorebase(ui, repo, action, opts, inmemory=False)
1095 return _dorebase(ui, repo, action, opts, inmemory=False)
1096 else:
1096 else:
1097 return _dorebase(ui, repo, action, opts)
1097 return _dorebase(ui, repo, action, opts)
1098
1098
1099
1099
1100 def _dryrunrebase(ui, repo, action, opts):
1100 def _dryrunrebase(ui, repo, action, opts):
1101 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
1101 rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
1102 confirm = opts.get(b'confirm')
1102 confirm = opts.get(b'confirm')
1103 if confirm:
1103 if confirm:
1104 ui.status(_(b'starting in-memory rebase\n'))
1104 ui.status(_(b'starting in-memory rebase\n'))
1105 else:
1105 else:
1106 ui.status(
1106 ui.status(
1107 _(b'starting dry-run rebase; repository will not be changed\n')
1107 _(b'starting dry-run rebase; repository will not be changed\n')
1108 )
1108 )
1109 with repo.wlock(), repo.lock():
1109 with repo.wlock(), repo.lock():
1110 needsabort = True
1110 needsabort = True
1111 try:
1111 try:
1112 overrides = {(b'rebase', b'singletransaction'): True}
1112 overrides = {(b'rebase', b'singletransaction'): True}
1113 with ui.configoverride(overrides, b'rebase'):
1113 with ui.configoverride(overrides, b'rebase'):
1114 _origrebase(
1114 _origrebase(
1115 ui,
1115 ui,
1116 repo,
1116 repo,
1117 action,
1117 action,
1118 opts,
1118 opts,
1119 rbsrt,
1119 rbsrt,
1120 inmemory=True,
1120 inmemory=True,
1121 leaveunfinished=True,
1121 leaveunfinished=True,
1122 )
1122 )
1123 except error.InMemoryMergeConflictsError:
1123 except error.InMemoryMergeConflictsError:
1124 ui.status(_(b'hit a merge conflict\n'))
1124 ui.status(_(b'hit a merge conflict\n'))
1125 return 1
1125 return 1
1126 except error.Abort:
1126 except error.Abort:
1127 needsabort = False
1127 needsabort = False
1128 raise
1128 raise
1129 else:
1129 else:
1130 if confirm:
1130 if confirm:
1131 ui.status(_(b'rebase completed successfully\n'))
1131 ui.status(_(b'rebase completed successfully\n'))
1132 if not ui.promptchoice(_(b'apply changes (yn)?$$ &Yes $$ &No')):
1132 if not ui.promptchoice(_(b'apply changes (yn)?$$ &Yes $$ &No')):
1133 # finish unfinished rebase
1133 # finish unfinished rebase
1134 rbsrt._finishrebase()
1134 rbsrt._finishrebase()
1135 else:
1135 else:
1136 rbsrt._prepareabortorcontinue(
1136 rbsrt._prepareabortorcontinue(
1137 isabort=True, backup=False, suppwarns=True
1137 isabort=True, backup=False, suppwarns=True
1138 )
1138 )
1139 needsabort = False
1139 needsabort = False
1140 else:
1140 else:
1141 ui.status(
1141 ui.status(
1142 _(
1142 _(
1143 b'dry-run rebase completed successfully; run without'
1143 b'dry-run rebase completed successfully; run without'
1144 b' -n/--dry-run to perform this rebase\n'
1144 b' -n/--dry-run to perform this rebase\n'
1145 )
1145 )
1146 )
1146 )
1147 return 0
1147 return 0
1148 finally:
1148 finally:
1149 if needsabort:
1149 if needsabort:
1150 # no need to store backup in case of dryrun
1150 # no need to store backup in case of dryrun
1151 rbsrt._prepareabortorcontinue(
1151 rbsrt._prepareabortorcontinue(
1152 isabort=True, backup=False, suppwarns=True
1152 isabort=True, backup=False, suppwarns=True
1153 )
1153 )
1154
1154
1155
1155
1156 def _dorebase(ui, repo, action, opts, inmemory=False):
1156 def _dorebase(ui, repo, action, opts, inmemory=False):
1157 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
1157 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
1158 return _origrebase(ui, repo, action, opts, rbsrt, inmemory=inmemory)
1158 return _origrebase(ui, repo, action, opts, rbsrt, inmemory=inmemory)
1159
1159
1160
1160
1161 def _origrebase(
1161 def _origrebase(
1162 ui, repo, action, opts, rbsrt, inmemory=False, leaveunfinished=False
1162 ui, repo, action, opts, rbsrt, inmemory=False, leaveunfinished=False
1163 ):
1163 ):
1164 assert action != b'stop'
1164 assert action != b'stop'
1165 with repo.wlock(), repo.lock():
1165 with repo.wlock(), repo.lock():
1166 # Validate input and define rebasing points
1167 destf = opts.get(b'dest', None)
1168 srcf = opts.get(b'source', None)
1169 basef = opts.get(b'base', None)
1170 revf = opts.get(b'rev', [])
1171 # search default destination in this space
1172 # used in the 'hg pull --rebase' case, see issue 5214.
1173 destspace = opts.get(b'_destspace')
1174 if opts.get(b'interactive'):
1166 if opts.get(b'interactive'):
1175 try:
1167 try:
1176 if extensions.find(b'histedit'):
1168 if extensions.find(b'histedit'):
1177 enablehistedit = b''
1169 enablehistedit = b''
1178 except KeyError:
1170 except KeyError:
1179 enablehistedit = b" --config extensions.histedit="
1171 enablehistedit = b" --config extensions.histedit="
1180 help = b"hg%s help -e histedit" % enablehistedit
1172 help = b"hg%s help -e histedit" % enablehistedit
1181 msg = (
1173 msg = (
1182 _(
1174 _(
1183 b"interactive history editing is supported by the "
1175 b"interactive history editing is supported by the "
1184 b"'histedit' extension (see \"%s\")"
1176 b"'histedit' extension (see \"%s\")"
1185 )
1177 )
1186 % help
1178 % help
1187 )
1179 )
1188 raise error.Abort(msg)
1180 raise error.Abort(msg)
1189
1181
1190 if rbsrt.collapsemsg and not rbsrt.collapsef:
1182 if rbsrt.collapsemsg and not rbsrt.collapsef:
1191 raise error.Abort(_(b'message can only be specified with collapse'))
1183 raise error.Abort(_(b'message can only be specified with collapse'))
1192
1184
1193 if action:
1185 if action:
1194 if rbsrt.collapsef:
1186 if rbsrt.collapsef:
1195 raise error.Abort(
1187 raise error.Abort(
1196 _(b'cannot use collapse with continue or abort')
1188 _(b'cannot use collapse with continue or abort')
1197 )
1189 )
1198 if action == b'abort' and opts.get(b'tool', False):
1190 if action == b'abort' and opts.get(b'tool', False):
1199 ui.warn(_(b'tool option will be ignored\n'))
1191 ui.warn(_(b'tool option will be ignored\n'))
1200 if action == b'continue':
1192 if action == b'continue':
1201 ms = mergemod.mergestate.read(repo)
1193 ms = mergemod.mergestate.read(repo)
1202 mergeutil.checkunresolved(ms)
1194 mergeutil.checkunresolved(ms)
1203
1195
1204 retcode = rbsrt._prepareabortorcontinue(
1196 retcode = rbsrt._prepareabortorcontinue(
1205 isabort=(action == b'abort')
1197 isabort=(action == b'abort')
1206 )
1198 )
1207 if retcode is not None:
1199 if retcode is not None:
1208 return retcode
1200 return retcode
1209 else:
1201 else:
1202 # search default destination in this space
1203 # used in the 'hg pull --rebase' case, see issue 5214.
1204 destspace = opts.get(b'_destspace')
1210 destmap = _definedestmap(
1205 destmap = _definedestmap(
1211 ui,
1206 ui,
1212 repo,
1207 repo,
1213 inmemory,
1208 inmemory,
1214 destf,
1209 opts.get(b'dest', None),
1215 srcf,
1210 opts.get(b'source', None),
1216 basef,
1211 opts.get(b'base', None),
1217 revf,
1212 opts.get(b'rev', []),
1218 destspace=destspace,
1213 destspace=destspace,
1219 )
1214 )
1220 retcode = rbsrt._preparenewrebase(destmap)
1215 retcode = rbsrt._preparenewrebase(destmap)
1221 if retcode is not None:
1216 if retcode is not None:
1222 return retcode
1217 return retcode
1223 storecollapsemsg(repo, rbsrt.collapsemsg)
1218 storecollapsemsg(repo, rbsrt.collapsemsg)
1224
1219
1225 tr = None
1220 tr = None
1226
1221
1227 singletr = ui.configbool(b'rebase', b'singletransaction')
1222 singletr = ui.configbool(b'rebase', b'singletransaction')
1228 if singletr:
1223 if singletr:
1229 tr = repo.transaction(b'rebase')
1224 tr = repo.transaction(b'rebase')
1230
1225
1231 # If `rebase.singletransaction` is enabled, wrap the entire operation in
1226 # If `rebase.singletransaction` is enabled, wrap the entire operation in
1232 # one transaction here. Otherwise, transactions are obtained when
1227 # one transaction here. Otherwise, transactions are obtained when
1233 # committing each node, which is slower but allows partial success.
1228 # committing each node, which is slower but allows partial success.
1234 with util.acceptintervention(tr):
1229 with util.acceptintervention(tr):
1235 # Same logic for the dirstate guard, except we don't create one when
1230 # Same logic for the dirstate guard, except we don't create one when
1236 # rebasing in-memory (it's not needed).
1231 # rebasing in-memory (it's not needed).
1237 dsguard = None
1232 dsguard = None
1238 if singletr and not inmemory:
1233 if singletr and not inmemory:
1239 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1234 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1240 with util.acceptintervention(dsguard):
1235 with util.acceptintervention(dsguard):
1241 rbsrt._performrebase(tr)
1236 rbsrt._performrebase(tr)
1242 if not leaveunfinished:
1237 if not leaveunfinished:
1243 rbsrt._finishrebase()
1238 rbsrt._finishrebase()
1244
1239
1245
1240
1246 def _definedestmap(
1241 def _definedestmap(
1247 ui,
1242 ui,
1248 repo,
1243 repo,
1249 inmemory,
1244 inmemory,
1250 destf=None,
1245 destf=None,
1251 srcf=None,
1246 srcf=None,
1252 basef=None,
1247 basef=None,
1253 revf=None,
1248 revf=None,
1254 destspace=None,
1249 destspace=None,
1255 ):
1250 ):
1256 """use revisions argument to define destmap {srcrev: destrev}"""
1251 """use revisions argument to define destmap {srcrev: destrev}"""
1257 if revf is None:
1252 if revf is None:
1258 revf = []
1253 revf = []
1259
1254
1260 # destspace is here to work around issues with `hg pull --rebase` see
1255 # destspace is here to work around issues with `hg pull --rebase` see
1261 # issue5214 for details
1256 # issue5214 for details
1262
1257
1263 cmdutil.checkunfinished(repo)
1258 cmdutil.checkunfinished(repo)
1264 if not inmemory:
1259 if not inmemory:
1265 cmdutil.bailifchanged(repo)
1260 cmdutil.bailifchanged(repo)
1266
1261
1267 if ui.configbool(b'commands', b'rebase.requiredest') and not destf:
1262 if ui.configbool(b'commands', b'rebase.requiredest') and not destf:
1268 raise error.Abort(
1263 raise error.Abort(
1269 _(b'you must specify a destination'),
1264 _(b'you must specify a destination'),
1270 hint=_(b'use: hg rebase -d REV'),
1265 hint=_(b'use: hg rebase -d REV'),
1271 )
1266 )
1272
1267
1273 dest = None
1268 dest = None
1274
1269
1275 if revf:
1270 if revf:
1276 rebaseset = scmutil.revrange(repo, revf)
1271 rebaseset = scmutil.revrange(repo, revf)
1277 if not rebaseset:
1272 if not rebaseset:
1278 ui.status(_(b'empty "rev" revision set - nothing to rebase\n'))
1273 ui.status(_(b'empty "rev" revision set - nothing to rebase\n'))
1279 return None
1274 return None
1280 elif srcf:
1275 elif srcf:
1281 src = scmutil.revrange(repo, [srcf])
1276 src = scmutil.revrange(repo, [srcf])
1282 if not src:
1277 if not src:
1283 ui.status(_(b'empty "source" revision set - nothing to rebase\n'))
1278 ui.status(_(b'empty "source" revision set - nothing to rebase\n'))
1284 return None
1279 return None
1285 rebaseset = repo.revs(b'(%ld)::', src)
1280 rebaseset = repo.revs(b'(%ld)::', src)
1286 assert rebaseset
1281 assert rebaseset
1287 else:
1282 else:
1288 base = scmutil.revrange(repo, [basef or b'.'])
1283 base = scmutil.revrange(repo, [basef or b'.'])
1289 if not base:
1284 if not base:
1290 ui.status(
1285 ui.status(
1291 _(b'empty "base" revision set - ' b"can't compute rebase set\n")
1286 _(b'empty "base" revision set - ' b"can't compute rebase set\n")
1292 )
1287 )
1293 return None
1288 return None
1294 if destf:
1289 if destf:
1295 # --base does not support multiple destinations
1290 # --base does not support multiple destinations
1296 dest = scmutil.revsingle(repo, destf)
1291 dest = scmutil.revsingle(repo, destf)
1297 else:
1292 else:
1298 dest = repo[_destrebase(repo, base, destspace=destspace)]
1293 dest = repo[_destrebase(repo, base, destspace=destspace)]
1299 destf = bytes(dest)
1294 destf = bytes(dest)
1300
1295
1301 roots = [] # selected children of branching points
1296 roots = [] # selected children of branching points
1302 bpbase = {} # {branchingpoint: [origbase]}
1297 bpbase = {} # {branchingpoint: [origbase]}
1303 for b in base: # group bases by branching points
1298 for b in base: # group bases by branching points
1304 bp = repo.revs(b'ancestor(%d, %d)', b, dest.rev()).first()
1299 bp = repo.revs(b'ancestor(%d, %d)', b, dest.rev()).first()
1305 bpbase[bp] = bpbase.get(bp, []) + [b]
1300 bpbase[bp] = bpbase.get(bp, []) + [b]
1306 if None in bpbase:
1301 if None in bpbase:
1307 # emulate the old behavior, showing "nothing to rebase" (a better
1302 # emulate the old behavior, showing "nothing to rebase" (a better
1308 # behavior may be abort with "cannot find branching point" error)
1303 # behavior may be abort with "cannot find branching point" error)
1309 bpbase.clear()
1304 bpbase.clear()
1310 for bp, bs in pycompat.iteritems(bpbase): # calculate roots
1305 for bp, bs in pycompat.iteritems(bpbase): # calculate roots
1311 roots += list(repo.revs(b'children(%d) & ancestors(%ld)', bp, bs))
1306 roots += list(repo.revs(b'children(%d) & ancestors(%ld)', bp, bs))
1312
1307
1313 rebaseset = repo.revs(b'%ld::', roots)
1308 rebaseset = repo.revs(b'%ld::', roots)
1314
1309
1315 if not rebaseset:
1310 if not rebaseset:
1316 # transform to list because smartsets are not comparable to
1311 # transform to list because smartsets are not comparable to
1317 # lists. This should be improved to honor laziness of
1312 # lists. This should be improved to honor laziness of
1318 # smartset.
1313 # smartset.
1319 if list(base) == [dest.rev()]:
1314 if list(base) == [dest.rev()]:
1320 if basef:
1315 if basef:
1321 ui.status(
1316 ui.status(
1322 _(
1317 _(
1323 b'nothing to rebase - %s is both "base"'
1318 b'nothing to rebase - %s is both "base"'
1324 b' and destination\n'
1319 b' and destination\n'
1325 )
1320 )
1326 % dest
1321 % dest
1327 )
1322 )
1328 else:
1323 else:
1329 ui.status(
1324 ui.status(
1330 _(
1325 _(
1331 b'nothing to rebase - working directory '
1326 b'nothing to rebase - working directory '
1332 b'parent is also destination\n'
1327 b'parent is also destination\n'
1333 )
1328 )
1334 )
1329 )
1335 elif not repo.revs(b'%ld - ::%d', base, dest.rev()):
1330 elif not repo.revs(b'%ld - ::%d', base, dest.rev()):
1336 if basef:
1331 if basef:
1337 ui.status(
1332 ui.status(
1338 _(
1333 _(
1339 b'nothing to rebase - "base" %s is '
1334 b'nothing to rebase - "base" %s is '
1340 b'already an ancestor of destination '
1335 b'already an ancestor of destination '
1341 b'%s\n'
1336 b'%s\n'
1342 )
1337 )
1343 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1338 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1344 )
1339 )
1345 else:
1340 else:
1346 ui.status(
1341 ui.status(
1347 _(
1342 _(
1348 b'nothing to rebase - working '
1343 b'nothing to rebase - working '
1349 b'directory parent is already an '
1344 b'directory parent is already an '
1350 b'ancestor of destination %s\n'
1345 b'ancestor of destination %s\n'
1351 )
1346 )
1352 % dest
1347 % dest
1353 )
1348 )
1354 else: # can it happen?
1349 else: # can it happen?
1355 ui.status(
1350 ui.status(
1356 _(b'nothing to rebase from %s to %s\n')
1351 _(b'nothing to rebase from %s to %s\n')
1357 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1352 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1358 )
1353 )
1359 return None
1354 return None
1360
1355
1361 rebasingwcp = repo[b'.'].rev() in rebaseset
1356 rebasingwcp = repo[b'.'].rev() in rebaseset
1362 ui.log(
1357 ui.log(
1363 b"rebase",
1358 b"rebase",
1364 b"rebasing working copy parent: %r\n",
1359 b"rebasing working copy parent: %r\n",
1365 rebasingwcp,
1360 rebasingwcp,
1366 rebase_rebasing_wcp=rebasingwcp,
1361 rebase_rebasing_wcp=rebasingwcp,
1367 )
1362 )
1368 if inmemory and rebasingwcp:
1363 if inmemory and rebasingwcp:
1369 # Check these since we did not before.
1364 # Check these since we did not before.
1370 cmdutil.checkunfinished(repo)
1365 cmdutil.checkunfinished(repo)
1371 cmdutil.bailifchanged(repo)
1366 cmdutil.bailifchanged(repo)
1372
1367
1373 if not destf:
1368 if not destf:
1374 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1369 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1375 destf = bytes(dest)
1370 destf = bytes(dest)
1376
1371
1377 allsrc = revsetlang.formatspec(b'%ld', rebaseset)
1372 allsrc = revsetlang.formatspec(b'%ld', rebaseset)
1378 alias = {b'ALLSRC': allsrc}
1373 alias = {b'ALLSRC': allsrc}
1379
1374
1380 if dest is None:
1375 if dest is None:
1381 try:
1376 try:
1382 # fast path: try to resolve dest without SRC alias
1377 # fast path: try to resolve dest without SRC alias
1383 dest = scmutil.revsingle(repo, destf, localalias=alias)
1378 dest = scmutil.revsingle(repo, destf, localalias=alias)
1384 except error.RepoLookupError:
1379 except error.RepoLookupError:
1385 # multi-dest path: resolve dest for each SRC separately
1380 # multi-dest path: resolve dest for each SRC separately
1386 destmap = {}
1381 destmap = {}
1387 for r in rebaseset:
1382 for r in rebaseset:
1388 alias[b'SRC'] = revsetlang.formatspec(b'%d', r)
1383 alias[b'SRC'] = revsetlang.formatspec(b'%d', r)
1389 # use repo.anyrevs instead of scmutil.revsingle because we
1384 # use repo.anyrevs instead of scmutil.revsingle because we
1390 # don't want to abort if destset is empty.
1385 # don't want to abort if destset is empty.
1391 destset = repo.anyrevs([destf], user=True, localalias=alias)
1386 destset = repo.anyrevs([destf], user=True, localalias=alias)
1392 size = len(destset)
1387 size = len(destset)
1393 if size == 1:
1388 if size == 1:
1394 destmap[r] = destset.first()
1389 destmap[r] = destset.first()
1395 elif size == 0:
1390 elif size == 0:
1396 ui.note(_(b'skipping %s - empty destination\n') % repo[r])
1391 ui.note(_(b'skipping %s - empty destination\n') % repo[r])
1397 else:
1392 else:
1398 raise error.Abort(
1393 raise error.Abort(
1399 _(b'rebase destination for %s is not unique') % repo[r]
1394 _(b'rebase destination for %s is not unique') % repo[r]
1400 )
1395 )
1401
1396
1402 if dest is not None:
1397 if dest is not None:
1403 # single-dest case: assign dest to each rev in rebaseset
1398 # single-dest case: assign dest to each rev in rebaseset
1404 destrev = dest.rev()
1399 destrev = dest.rev()
1405 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1400 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1406
1401
1407 if not destmap:
1402 if not destmap:
1408 ui.status(_(b'nothing to rebase - empty destination\n'))
1403 ui.status(_(b'nothing to rebase - empty destination\n'))
1409 return None
1404 return None
1410
1405
1411 return destmap
1406 return destmap
1412
1407
1413
1408
1414 def externalparent(repo, state, destancestors):
1409 def externalparent(repo, state, destancestors):
1415 """Return the revision that should be used as the second parent
1410 """Return the revision that should be used as the second parent
1416 when the revisions in state is collapsed on top of destancestors.
1411 when the revisions in state is collapsed on top of destancestors.
1417 Abort if there is more than one parent.
1412 Abort if there is more than one parent.
1418 """
1413 """
1419 parents = set()
1414 parents = set()
1420 source = min(state)
1415 source = min(state)
1421 for rev in state:
1416 for rev in state:
1422 if rev == source:
1417 if rev == source:
1423 continue
1418 continue
1424 for p in repo[rev].parents():
1419 for p in repo[rev].parents():
1425 if p.rev() not in state and p.rev() not in destancestors:
1420 if p.rev() not in state and p.rev() not in destancestors:
1426 parents.add(p.rev())
1421 parents.add(p.rev())
1427 if not parents:
1422 if not parents:
1428 return nullrev
1423 return nullrev
1429 if len(parents) == 1:
1424 if len(parents) == 1:
1430 return parents.pop()
1425 return parents.pop()
1431 raise error.Abort(
1426 raise error.Abort(
1432 _(
1427 _(
1433 b'unable to collapse on top of %d, there is more '
1428 b'unable to collapse on top of %d, there is more '
1434 b'than one external parent: %s'
1429 b'than one external parent: %s'
1435 )
1430 )
1436 % (max(destancestors), b', '.join(b"%d" % p for p in sorted(parents)))
1431 % (max(destancestors), b', '.join(b"%d" % p for p in sorted(parents)))
1437 )
1432 )
1438
1433
1439
1434
1440 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1435 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1441 '''Commit the memory changes with parents p1 and p2.
1436 '''Commit the memory changes with parents p1 and p2.
1442 Return node of committed revision.'''
1437 Return node of committed revision.'''
1443 # Replicates the empty check in ``repo.commit``.
1438 # Replicates the empty check in ``repo.commit``.
1444 if wctx.isempty() and not repo.ui.configbool(b'ui', b'allowemptycommit'):
1439 if wctx.isempty() and not repo.ui.configbool(b'ui', b'allowemptycommit'):
1445 return None
1440 return None
1446
1441
1447 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1442 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1448 # ``branch`` (used when passing ``--keepbranches``).
1443 # ``branch`` (used when passing ``--keepbranches``).
1449 branch = repo[p1].branch()
1444 branch = repo[p1].branch()
1450 if b'branch' in extra:
1445 if b'branch' in extra:
1451 branch = extra[b'branch']
1446 branch = extra[b'branch']
1452
1447
1453 memctx = wctx.tomemctx(
1448 memctx = wctx.tomemctx(
1454 commitmsg,
1449 commitmsg,
1455 parents=(p1, p2),
1450 parents=(p1, p2),
1456 date=date,
1451 date=date,
1457 extra=extra,
1452 extra=extra,
1458 user=user,
1453 user=user,
1459 branch=branch,
1454 branch=branch,
1460 editor=editor,
1455 editor=editor,
1461 )
1456 )
1462 commitres = repo.commitctx(memctx)
1457 commitres = repo.commitctx(memctx)
1463 wctx.clean() # Might be reused
1458 wctx.clean() # Might be reused
1464 return commitres
1459 return commitres
1465
1460
1466
1461
1467 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1462 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1468 '''Commit the wd changes with parents p1 and p2.
1463 '''Commit the wd changes with parents p1 and p2.
1469 Return node of committed revision.'''
1464 Return node of committed revision.'''
1470 dsguard = util.nullcontextmanager()
1465 dsguard = util.nullcontextmanager()
1471 if not repo.ui.configbool(b'rebase', b'singletransaction'):
1466 if not repo.ui.configbool(b'rebase', b'singletransaction'):
1472 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1467 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1473 with dsguard:
1468 with dsguard:
1474 repo.setparents(repo[p1].node(), repo[p2].node())
1469 repo.setparents(repo[p1].node(), repo[p2].node())
1475
1470
1476 # Commit might fail if unresolved files exist
1471 # Commit might fail if unresolved files exist
1477 newnode = repo.commit(
1472 newnode = repo.commit(
1478 text=commitmsg, user=user, date=date, extra=extra, editor=editor
1473 text=commitmsg, user=user, date=date, extra=extra, editor=editor
1479 )
1474 )
1480
1475
1481 repo.dirstate.setbranch(repo[newnode].branch())
1476 repo.dirstate.setbranch(repo[newnode].branch())
1482 return newnode
1477 return newnode
1483
1478
1484
1479
1485 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1480 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1486 """Rebase a single revision rev on top of p1 using base as merge ancestor"""
1481 """Rebase a single revision rev on top of p1 using base as merge ancestor"""
1487 # Merge phase
1482 # Merge phase
1488 # Update to destination and merge it with local
1483 # Update to destination and merge it with local
1489 if wctx.isinmemory():
1484 if wctx.isinmemory():
1490 wctx.setbase(repo[p1])
1485 wctx.setbase(repo[p1])
1491 else:
1486 else:
1492 if repo[b'.'].rev() != p1:
1487 if repo[b'.'].rev() != p1:
1493 repo.ui.debug(b" update to %d:%s\n" % (p1, repo[p1]))
1488 repo.ui.debug(b" update to %d:%s\n" % (p1, repo[p1]))
1494 mergemod.update(repo, p1, branchmerge=False, force=True)
1489 mergemod.update(repo, p1, branchmerge=False, force=True)
1495 else:
1490 else:
1496 repo.ui.debug(b" already in destination\n")
1491 repo.ui.debug(b" already in destination\n")
1497 # This is, alas, necessary to invalidate workingctx's manifest cache,
1492 # This is, alas, necessary to invalidate workingctx's manifest cache,
1498 # as well as other data we litter on it in other places.
1493 # as well as other data we litter on it in other places.
1499 wctx = repo[None]
1494 wctx = repo[None]
1500 repo.dirstate.write(repo.currenttransaction())
1495 repo.dirstate.write(repo.currenttransaction())
1501 repo.ui.debug(b" merge against %d:%s\n" % (rev, repo[rev]))
1496 repo.ui.debug(b" merge against %d:%s\n" % (rev, repo[rev]))
1502 if base is not None:
1497 if base is not None:
1503 repo.ui.debug(b" detach base %d:%s\n" % (base, repo[base]))
1498 repo.ui.debug(b" detach base %d:%s\n" % (base, repo[base]))
1504 # When collapsing in-place, the parent is the common ancestor, we
1499 # When collapsing in-place, the parent is the common ancestor, we
1505 # have to allow merging with it.
1500 # have to allow merging with it.
1506 stats = mergemod.update(
1501 stats = mergemod.update(
1507 repo,
1502 repo,
1508 rev,
1503 rev,
1509 branchmerge=True,
1504 branchmerge=True,
1510 force=True,
1505 force=True,
1511 ancestor=base,
1506 ancestor=base,
1512 mergeancestor=collapse,
1507 mergeancestor=collapse,
1513 labels=[b'dest', b'source'],
1508 labels=[b'dest', b'source'],
1514 wc=wctx,
1509 wc=wctx,
1515 )
1510 )
1516 if collapse:
1511 if collapse:
1517 copies.duplicatecopies(repo, wctx, rev, dest)
1512 copies.duplicatecopies(repo, wctx, rev, dest)
1518 else:
1513 else:
1519 # If we're not using --collapse, we need to
1514 # If we're not using --collapse, we need to
1520 # duplicate copies between the revision we're
1515 # duplicate copies between the revision we're
1521 # rebasing and its first parent, but *not*
1516 # rebasing and its first parent, but *not*
1522 # duplicate any copies that have already been
1517 # duplicate any copies that have already been
1523 # performed in the destination.
1518 # performed in the destination.
1524 p1rev = repo[rev].p1().rev()
1519 p1rev = repo[rev].p1().rev()
1525 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1520 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1526 return stats
1521 return stats
1527
1522
1528
1523
1529 def adjustdest(repo, rev, destmap, state, skipped):
1524 def adjustdest(repo, rev, destmap, state, skipped):
1530 r"""adjust rebase destination given the current rebase state
1525 r"""adjust rebase destination given the current rebase state
1531
1526
1532 rev is what is being rebased. Return a list of two revs, which are the
1527 rev is what is being rebased. Return a list of two revs, which are the
1533 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1528 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1534 nullrev, return dest without adjustment for it.
1529 nullrev, return dest without adjustment for it.
1535
1530
1536 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1531 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1537 to B1, and E's destination will be adjusted from F to B1.
1532 to B1, and E's destination will be adjusted from F to B1.
1538
1533
1539 B1 <- written during rebasing B
1534 B1 <- written during rebasing B
1540 |
1535 |
1541 F <- original destination of B, E
1536 F <- original destination of B, E
1542 |
1537 |
1543 | E <- rev, which is being rebased
1538 | E <- rev, which is being rebased
1544 | |
1539 | |
1545 | D <- prev, one parent of rev being checked
1540 | D <- prev, one parent of rev being checked
1546 | |
1541 | |
1547 | x <- skipped, ex. no successor or successor in (::dest)
1542 | x <- skipped, ex. no successor or successor in (::dest)
1548 | |
1543 | |
1549 | C <- rebased as C', different destination
1544 | C <- rebased as C', different destination
1550 | |
1545 | |
1551 | B <- rebased as B1 C'
1546 | B <- rebased as B1 C'
1552 |/ |
1547 |/ |
1553 A G <- destination of C, different
1548 A G <- destination of C, different
1554
1549
1555 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1550 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1556 first move C to C1, G to G1, and when it's checking H, the adjusted
1551 first move C to C1, G to G1, and when it's checking H, the adjusted
1557 destinations will be [C1, G1].
1552 destinations will be [C1, G1].
1558
1553
1559 H C1 G1
1554 H C1 G1
1560 /| | /
1555 /| | /
1561 F G |/
1556 F G |/
1562 K | | -> K
1557 K | | -> K
1563 | C D |
1558 | C D |
1564 | |/ |
1559 | |/ |
1565 | B | ...
1560 | B | ...
1566 |/ |/
1561 |/ |/
1567 A A
1562 A A
1568
1563
1569 Besides, adjust dest according to existing rebase information. For example,
1564 Besides, adjust dest according to existing rebase information. For example,
1570
1565
1571 B C D B needs to be rebased on top of C, C needs to be rebased on top
1566 B C D B needs to be rebased on top of C, C needs to be rebased on top
1572 \|/ of D. We will rebase C first.
1567 \|/ of D. We will rebase C first.
1573 A
1568 A
1574
1569
1575 C' After rebasing C, when considering B's destination, use C'
1570 C' After rebasing C, when considering B's destination, use C'
1576 | instead of the original C.
1571 | instead of the original C.
1577 B D
1572 B D
1578 \ /
1573 \ /
1579 A
1574 A
1580 """
1575 """
1581 # pick already rebased revs with same dest from state as interesting source
1576 # pick already rebased revs with same dest from state as interesting source
1582 dest = destmap[rev]
1577 dest = destmap[rev]
1583 source = [
1578 source = [
1584 s
1579 s
1585 for s, d in state.items()
1580 for s, d in state.items()
1586 if d > 0 and destmap[s] == dest and s not in skipped
1581 if d > 0 and destmap[s] == dest and s not in skipped
1587 ]
1582 ]
1588
1583
1589 result = []
1584 result = []
1590 for prev in repo.changelog.parentrevs(rev):
1585 for prev in repo.changelog.parentrevs(rev):
1591 adjusted = dest
1586 adjusted = dest
1592 if prev != nullrev:
1587 if prev != nullrev:
1593 candidate = repo.revs(b'max(%ld and (::%d))', source, prev).first()
1588 candidate = repo.revs(b'max(%ld and (::%d))', source, prev).first()
1594 if candidate is not None:
1589 if candidate is not None:
1595 adjusted = state[candidate]
1590 adjusted = state[candidate]
1596 if adjusted == dest and dest in state:
1591 if adjusted == dest and dest in state:
1597 adjusted = state[dest]
1592 adjusted = state[dest]
1598 if adjusted == revtodo:
1593 if adjusted == revtodo:
1599 # sortsource should produce an order that makes this impossible
1594 # sortsource should produce an order that makes this impossible
1600 raise error.ProgrammingError(
1595 raise error.ProgrammingError(
1601 b'rev %d should be rebased already at this time' % dest
1596 b'rev %d should be rebased already at this time' % dest
1602 )
1597 )
1603 result.append(adjusted)
1598 result.append(adjusted)
1604 return result
1599 return result
1605
1600
1606
1601
1607 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1602 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1608 """
1603 """
1609 Abort if rebase will create divergence or rebase is noop because of markers
1604 Abort if rebase will create divergence or rebase is noop because of markers
1610
1605
1611 `rebaseobsrevs`: set of obsolete revision in source
1606 `rebaseobsrevs`: set of obsolete revision in source
1612 `rebaseobsskipped`: set of revisions from source skipped because they have
1607 `rebaseobsskipped`: set of revisions from source skipped because they have
1613 successors in destination or no non-obsolete successor.
1608 successors in destination or no non-obsolete successor.
1614 """
1609 """
1615 # Obsolete node with successors not in dest leads to divergence
1610 # Obsolete node with successors not in dest leads to divergence
1616 divergenceok = ui.configbool(b'experimental', b'evolution.allowdivergence')
1611 divergenceok = ui.configbool(b'experimental', b'evolution.allowdivergence')
1617 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1612 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1618
1613
1619 if divergencebasecandidates and not divergenceok:
1614 if divergencebasecandidates and not divergenceok:
1620 divhashes = (bytes(repo[r]) for r in divergencebasecandidates)
1615 divhashes = (bytes(repo[r]) for r in divergencebasecandidates)
1621 msg = _(b"this rebase will cause divergences from: %s")
1616 msg = _(b"this rebase will cause divergences from: %s")
1622 h = _(
1617 h = _(
1623 b"to force the rebase please set "
1618 b"to force the rebase please set "
1624 b"experimental.evolution.allowdivergence=True"
1619 b"experimental.evolution.allowdivergence=True"
1625 )
1620 )
1626 raise error.Abort(msg % (b",".join(divhashes),), hint=h)
1621 raise error.Abort(msg % (b",".join(divhashes),), hint=h)
1627
1622
1628
1623
1629 def successorrevs(unfi, rev):
1624 def successorrevs(unfi, rev):
1630 """yield revision numbers for successors of rev"""
1625 """yield revision numbers for successors of rev"""
1631 assert unfi.filtername is None
1626 assert unfi.filtername is None
1632 get_rev = unfi.changelog.index.get_rev
1627 get_rev = unfi.changelog.index.get_rev
1633 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1628 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1634 r = get_rev(s)
1629 r = get_rev(s)
1635 if r is not None:
1630 if r is not None:
1636 yield r
1631 yield r
1637
1632
1638
1633
1639 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1634 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1640 """Return new parents and optionally a merge base for rev being rebased
1635 """Return new parents and optionally a merge base for rev being rebased
1641
1636
1642 The destination specified by "dest" cannot always be used directly because
1637 The destination specified by "dest" cannot always be used directly because
1643 previously rebase result could affect destination. For example,
1638 previously rebase result could affect destination. For example,
1644
1639
1645 D E rebase -r C+D+E -d B
1640 D E rebase -r C+D+E -d B
1646 |/ C will be rebased to C'
1641 |/ C will be rebased to C'
1647 B C D's new destination will be C' instead of B
1642 B C D's new destination will be C' instead of B
1648 |/ E's new destination will be C' instead of B
1643 |/ E's new destination will be C' instead of B
1649 A
1644 A
1650
1645
1651 The new parents of a merge is slightly more complicated. See the comment
1646 The new parents of a merge is slightly more complicated. See the comment
1652 block below.
1647 block below.
1653 """
1648 """
1654 # use unfiltered changelog since successorrevs may return filtered nodes
1649 # use unfiltered changelog since successorrevs may return filtered nodes
1655 assert repo.filtername is None
1650 assert repo.filtername is None
1656 cl = repo.changelog
1651 cl = repo.changelog
1657 isancestor = cl.isancestorrev
1652 isancestor = cl.isancestorrev
1658
1653
1659 dest = destmap[rev]
1654 dest = destmap[rev]
1660 oldps = repo.changelog.parentrevs(rev) # old parents
1655 oldps = repo.changelog.parentrevs(rev) # old parents
1661 newps = [nullrev, nullrev] # new parents
1656 newps = [nullrev, nullrev] # new parents
1662 dests = adjustdest(repo, rev, destmap, state, skipped)
1657 dests = adjustdest(repo, rev, destmap, state, skipped)
1663 bases = list(oldps) # merge base candidates, initially just old parents
1658 bases = list(oldps) # merge base candidates, initially just old parents
1664
1659
1665 if all(r == nullrev for r in oldps[1:]):
1660 if all(r == nullrev for r in oldps[1:]):
1666 # For non-merge changeset, just move p to adjusted dest as requested.
1661 # For non-merge changeset, just move p to adjusted dest as requested.
1667 newps[0] = dests[0]
1662 newps[0] = dests[0]
1668 else:
1663 else:
1669 # For merge changeset, if we move p to dests[i] unconditionally, both
1664 # For merge changeset, if we move p to dests[i] unconditionally, both
1670 # parents may change and the end result looks like "the merge loses a
1665 # parents may change and the end result looks like "the merge loses a
1671 # parent", which is a surprise. This is a limit because "--dest" only
1666 # parent", which is a surprise. This is a limit because "--dest" only
1672 # accepts one dest per src.
1667 # accepts one dest per src.
1673 #
1668 #
1674 # Therefore, only move p with reasonable conditions (in this order):
1669 # Therefore, only move p with reasonable conditions (in this order):
1675 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1670 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1676 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1671 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1677 #
1672 #
1678 # Comparing with adjustdest, the logic here does some additional work:
1673 # Comparing with adjustdest, the logic here does some additional work:
1679 # 1. decide which parents will not be moved towards dest
1674 # 1. decide which parents will not be moved towards dest
1680 # 2. if the above decision is "no", should a parent still be moved
1675 # 2. if the above decision is "no", should a parent still be moved
1681 # because it was rebased?
1676 # because it was rebased?
1682 #
1677 #
1683 # For example:
1678 # For example:
1684 #
1679 #
1685 # C # "rebase -r C -d D" is an error since none of the parents
1680 # C # "rebase -r C -d D" is an error since none of the parents
1686 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1681 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1687 # A B D # B (using rule "2."), since B will be rebased.
1682 # A B D # B (using rule "2."), since B will be rebased.
1688 #
1683 #
1689 # The loop tries to be not rely on the fact that a Mercurial node has
1684 # The loop tries to be not rely on the fact that a Mercurial node has
1690 # at most 2 parents.
1685 # at most 2 parents.
1691 for i, p in enumerate(oldps):
1686 for i, p in enumerate(oldps):
1692 np = p # new parent
1687 np = p # new parent
1693 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1688 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1694 np = dests[i]
1689 np = dests[i]
1695 elif p in state and state[p] > 0:
1690 elif p in state and state[p] > 0:
1696 np = state[p]
1691 np = state[p]
1697
1692
1698 # "bases" only record "special" merge bases that cannot be
1693 # "bases" only record "special" merge bases that cannot be
1699 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1694 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1700 # For example:
1695 # For example:
1701 #
1696 #
1702 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1697 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1703 # | C # is B', but merge base for C is B, instead of
1698 # | C # is B', but merge base for C is B, instead of
1704 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1699 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1705 # | B # "state" edges are merged (so there will be an edge from
1700 # | B # "state" edges are merged (so there will be an edge from
1706 # |/ # B to B'), the merge base is still ancestor(C, B') in
1701 # |/ # B to B'), the merge base is still ancestor(C, B') in
1707 # A # the merged graph.
1702 # A # the merged graph.
1708 #
1703 #
1709 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1704 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1710 # which uses "virtual null merge" to explain this situation.
1705 # which uses "virtual null merge" to explain this situation.
1711 if isancestor(p, np):
1706 if isancestor(p, np):
1712 bases[i] = nullrev
1707 bases[i] = nullrev
1713
1708
1714 # If one parent becomes an ancestor of the other, drop the ancestor
1709 # If one parent becomes an ancestor of the other, drop the ancestor
1715 for j, x in enumerate(newps[:i]):
1710 for j, x in enumerate(newps[:i]):
1716 if x == nullrev:
1711 if x == nullrev:
1717 continue
1712 continue
1718 if isancestor(np, x): # CASE-1
1713 if isancestor(np, x): # CASE-1
1719 np = nullrev
1714 np = nullrev
1720 elif isancestor(x, np): # CASE-2
1715 elif isancestor(x, np): # CASE-2
1721 newps[j] = np
1716 newps[j] = np
1722 np = nullrev
1717 np = nullrev
1723 # New parents forming an ancestor relationship does not
1718 # New parents forming an ancestor relationship does not
1724 # mean the old parents have a similar relationship. Do not
1719 # mean the old parents have a similar relationship. Do not
1725 # set bases[x] to nullrev.
1720 # set bases[x] to nullrev.
1726 bases[j], bases[i] = bases[i], bases[j]
1721 bases[j], bases[i] = bases[i], bases[j]
1727
1722
1728 newps[i] = np
1723 newps[i] = np
1729
1724
1730 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1725 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1731 # base. If only p2 changes, merging using unchanged p1 as merge base is
1726 # base. If only p2 changes, merging using unchanged p1 as merge base is
1732 # suboptimal. Therefore swap parents to make the merge sane.
1727 # suboptimal. Therefore swap parents to make the merge sane.
1733 if newps[1] != nullrev and oldps[0] == newps[0]:
1728 if newps[1] != nullrev and oldps[0] == newps[0]:
1734 assert len(newps) == 2 and len(oldps) == 2
1729 assert len(newps) == 2 and len(oldps) == 2
1735 newps.reverse()
1730 newps.reverse()
1736 bases.reverse()
1731 bases.reverse()
1737
1732
1738 # No parent change might be an error because we fail to make rev a
1733 # No parent change might be an error because we fail to make rev a
1739 # descendent of requested dest. This can happen, for example:
1734 # descendent of requested dest. This can happen, for example:
1740 #
1735 #
1741 # C # rebase -r C -d D
1736 # C # rebase -r C -d D
1742 # /| # None of A and B will be changed to D and rebase fails.
1737 # /| # None of A and B will be changed to D and rebase fails.
1743 # A B D
1738 # A B D
1744 if set(newps) == set(oldps) and dest not in newps:
1739 if set(newps) == set(oldps) and dest not in newps:
1745 raise error.Abort(
1740 raise error.Abort(
1746 _(
1741 _(
1747 b'cannot rebase %d:%s without '
1742 b'cannot rebase %d:%s without '
1748 b'moving at least one of its parents'
1743 b'moving at least one of its parents'
1749 )
1744 )
1750 % (rev, repo[rev])
1745 % (rev, repo[rev])
1751 )
1746 )
1752
1747
1753 # Source should not be ancestor of dest. The check here guarantees it's
1748 # Source should not be ancestor of dest. The check here guarantees it's
1754 # impossible. With multi-dest, the initial check does not cover complex
1749 # impossible. With multi-dest, the initial check does not cover complex
1755 # cases since we don't have abstractions to dry-run rebase cheaply.
1750 # cases since we don't have abstractions to dry-run rebase cheaply.
1756 if any(p != nullrev and isancestor(rev, p) for p in newps):
1751 if any(p != nullrev and isancestor(rev, p) for p in newps):
1757 raise error.Abort(_(b'source is ancestor of destination'))
1752 raise error.Abort(_(b'source is ancestor of destination'))
1758
1753
1759 # "rebasenode" updates to new p1, use the corresponding merge base.
1754 # "rebasenode" updates to new p1, use the corresponding merge base.
1760 if bases[0] != nullrev:
1755 if bases[0] != nullrev:
1761 base = bases[0]
1756 base = bases[0]
1762 else:
1757 else:
1763 base = None
1758 base = None
1764
1759
1765 # Check if the merge will contain unwanted changes. That may happen if
1760 # Check if the merge will contain unwanted changes. That may happen if
1766 # there are multiple special (non-changelog ancestor) merge bases, which
1761 # there are multiple special (non-changelog ancestor) merge bases, which
1767 # cannot be handled well by the 3-way merge algorithm. For example:
1762 # cannot be handled well by the 3-way merge algorithm. For example:
1768 #
1763 #
1769 # F
1764 # F
1770 # /|
1765 # /|
1771 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1766 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1772 # | | # as merge base, the difference between D and F will include
1767 # | | # as merge base, the difference between D and F will include
1773 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1768 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1774 # |/ # chosen, the rebased F will contain B.
1769 # |/ # chosen, the rebased F will contain B.
1775 # A Z
1770 # A Z
1776 #
1771 #
1777 # But our merge base candidates (D and E in above case) could still be
1772 # But our merge base candidates (D and E in above case) could still be
1778 # better than the default (ancestor(F, Z) == null). Therefore still
1773 # better than the default (ancestor(F, Z) == null). Therefore still
1779 # pick one (so choose p1 above).
1774 # pick one (so choose p1 above).
1780 if sum(1 for b in set(bases) if b != nullrev) > 1:
1775 if sum(1 for b in set(bases) if b != nullrev) > 1:
1781 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1776 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1782 for i, base in enumerate(bases):
1777 for i, base in enumerate(bases):
1783 if base == nullrev:
1778 if base == nullrev:
1784 continue
1779 continue
1785 # Revisions in the side (not chosen as merge base) branch that
1780 # Revisions in the side (not chosen as merge base) branch that
1786 # might contain "surprising" contents
1781 # might contain "surprising" contents
1787 siderevs = list(
1782 siderevs = list(
1788 repo.revs(b'((%ld-%d) %% (%d+%d))', bases, base, base, dest)
1783 repo.revs(b'((%ld-%d) %% (%d+%d))', bases, base, base, dest)
1789 )
1784 )
1790
1785
1791 # If those revisions are covered by rebaseset, the result is good.
1786 # If those revisions are covered by rebaseset, the result is good.
1792 # A merge in rebaseset would be considered to cover its ancestors.
1787 # A merge in rebaseset would be considered to cover its ancestors.
1793 if siderevs:
1788 if siderevs:
1794 rebaseset = [
1789 rebaseset = [
1795 r for r, d in state.items() if d > 0 and r not in obsskipped
1790 r for r, d in state.items() if d > 0 and r not in obsskipped
1796 ]
1791 ]
1797 merges = [
1792 merges = [
1798 r for r in rebaseset if cl.parentrevs(r)[1] != nullrev
1793 r for r in rebaseset if cl.parentrevs(r)[1] != nullrev
1799 ]
1794 ]
1800 unwanted[i] = list(
1795 unwanted[i] = list(
1801 repo.revs(
1796 repo.revs(
1802 b'%ld - (::%ld) - %ld', siderevs, merges, rebaseset
1797 b'%ld - (::%ld) - %ld', siderevs, merges, rebaseset
1803 )
1798 )
1804 )
1799 )
1805
1800
1806 # Choose a merge base that has a minimal number of unwanted revs.
1801 # Choose a merge base that has a minimal number of unwanted revs.
1807 l, i = min(
1802 l, i = min(
1808 (len(revs), i)
1803 (len(revs), i)
1809 for i, revs in enumerate(unwanted)
1804 for i, revs in enumerate(unwanted)
1810 if revs is not None
1805 if revs is not None
1811 )
1806 )
1812 base = bases[i]
1807 base = bases[i]
1813
1808
1814 # newps[0] should match merge base if possible. Currently, if newps[i]
1809 # newps[0] should match merge base if possible. Currently, if newps[i]
1815 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1810 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1816 # the other's ancestor. In that case, it's fine to not swap newps here.
1811 # the other's ancestor. In that case, it's fine to not swap newps here.
1817 # (see CASE-1 and CASE-2 above)
1812 # (see CASE-1 and CASE-2 above)
1818 if i != 0 and newps[i] != nullrev:
1813 if i != 0 and newps[i] != nullrev:
1819 newps[0], newps[i] = newps[i], newps[0]
1814 newps[0], newps[i] = newps[i], newps[0]
1820
1815
1821 # The merge will include unwanted revisions. Abort now. Revisit this if
1816 # The merge will include unwanted revisions. Abort now. Revisit this if
1822 # we have a more advanced merge algorithm that handles multiple bases.
1817 # we have a more advanced merge algorithm that handles multiple bases.
1823 if l > 0:
1818 if l > 0:
1824 unwanteddesc = _(b' or ').join(
1819 unwanteddesc = _(b' or ').join(
1825 (
1820 (
1826 b', '.join(b'%d:%s' % (r, repo[r]) for r in revs)
1821 b', '.join(b'%d:%s' % (r, repo[r]) for r in revs)
1827 for revs in unwanted
1822 for revs in unwanted
1828 if revs is not None
1823 if revs is not None
1829 )
1824 )
1830 )
1825 )
1831 raise error.Abort(
1826 raise error.Abort(
1832 _(b'rebasing %d:%s will include unwanted changes from %s')
1827 _(b'rebasing %d:%s will include unwanted changes from %s')
1833 % (rev, repo[rev], unwanteddesc)
1828 % (rev, repo[rev], unwanteddesc)
1834 )
1829 )
1835
1830
1836 repo.ui.debug(b" future parents are %d and %d\n" % tuple(newps))
1831 repo.ui.debug(b" future parents are %d and %d\n" % tuple(newps))
1837
1832
1838 return newps[0], newps[1], base
1833 return newps[0], newps[1], base
1839
1834
1840
1835
1841 def isagitpatch(repo, patchname):
1836 def isagitpatch(repo, patchname):
1842 """Return true if the given patch is in git format"""
1837 """Return true if the given patch is in git format"""
1843 mqpatch = os.path.join(repo.mq.path, patchname)
1838 mqpatch = os.path.join(repo.mq.path, patchname)
1844 for line in patch.linereader(open(mqpatch, b'rb')):
1839 for line in patch.linereader(open(mqpatch, b'rb')):
1845 if line.startswith(b'diff --git'):
1840 if line.startswith(b'diff --git'):
1846 return True
1841 return True
1847 return False
1842 return False
1848
1843
1849
1844
1850 def updatemq(repo, state, skipped, **opts):
1845 def updatemq(repo, state, skipped, **opts):
1851 """Update rebased mq patches - finalize and then import them"""
1846 """Update rebased mq patches - finalize and then import them"""
1852 mqrebase = {}
1847 mqrebase = {}
1853 mq = repo.mq
1848 mq = repo.mq
1854 original_series = mq.fullseries[:]
1849 original_series = mq.fullseries[:]
1855 skippedpatches = set()
1850 skippedpatches = set()
1856
1851
1857 for p in mq.applied:
1852 for p in mq.applied:
1858 rev = repo[p.node].rev()
1853 rev = repo[p.node].rev()
1859 if rev in state:
1854 if rev in state:
1860 repo.ui.debug(
1855 repo.ui.debug(
1861 b'revision %d is an mq patch (%s), finalize it.\n'
1856 b'revision %d is an mq patch (%s), finalize it.\n'
1862 % (rev, p.name)
1857 % (rev, p.name)
1863 )
1858 )
1864 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1859 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1865 else:
1860 else:
1866 # Applied but not rebased, not sure this should happen
1861 # Applied but not rebased, not sure this should happen
1867 skippedpatches.add(p.name)
1862 skippedpatches.add(p.name)
1868
1863
1869 if mqrebase:
1864 if mqrebase:
1870 mq.finish(repo, mqrebase.keys())
1865 mq.finish(repo, mqrebase.keys())
1871
1866
1872 # We must start import from the newest revision
1867 # We must start import from the newest revision
1873 for rev in sorted(mqrebase, reverse=True):
1868 for rev in sorted(mqrebase, reverse=True):
1874 if rev not in skipped:
1869 if rev not in skipped:
1875 name, isgit = mqrebase[rev]
1870 name, isgit = mqrebase[rev]
1876 repo.ui.note(
1871 repo.ui.note(
1877 _(b'updating mq patch %s to %d:%s\n')
1872 _(b'updating mq patch %s to %d:%s\n')
1878 % (name, state[rev], repo[state[rev]])
1873 % (name, state[rev], repo[state[rev]])
1879 )
1874 )
1880 mq.qimport(
1875 mq.qimport(
1881 repo,
1876 repo,
1882 (),
1877 (),
1883 patchname=name,
1878 patchname=name,
1884 git=isgit,
1879 git=isgit,
1885 rev=[b"%d" % state[rev]],
1880 rev=[b"%d" % state[rev]],
1886 )
1881 )
1887 else:
1882 else:
1888 # Rebased and skipped
1883 # Rebased and skipped
1889 skippedpatches.add(mqrebase[rev][0])
1884 skippedpatches.add(mqrebase[rev][0])
1890
1885
1891 # Patches were either applied and rebased and imported in
1886 # Patches were either applied and rebased and imported in
1892 # order, applied and removed or unapplied. Discard the removed
1887 # order, applied and removed or unapplied. Discard the removed
1893 # ones while preserving the original series order and guards.
1888 # ones while preserving the original series order and guards.
1894 newseries = [
1889 newseries = [
1895 s
1890 s
1896 for s in original_series
1891 for s in original_series
1897 if mq.guard_re.split(s, 1)[0] not in skippedpatches
1892 if mq.guard_re.split(s, 1)[0] not in skippedpatches
1898 ]
1893 ]
1899 mq.fullseries[:] = newseries
1894 mq.fullseries[:] = newseries
1900 mq.seriesdirty = True
1895 mq.seriesdirty = True
1901 mq.savedirty()
1896 mq.savedirty()
1902
1897
1903
1898
1904 def storecollapsemsg(repo, collapsemsg):
1899 def storecollapsemsg(repo, collapsemsg):
1905 """Store the collapse message to allow recovery"""
1900 """Store the collapse message to allow recovery"""
1906 collapsemsg = collapsemsg or b''
1901 collapsemsg = collapsemsg or b''
1907 f = repo.vfs(b"last-message.txt", b"w")
1902 f = repo.vfs(b"last-message.txt", b"w")
1908 f.write(b"%s\n" % collapsemsg)
1903 f.write(b"%s\n" % collapsemsg)
1909 f.close()
1904 f.close()
1910
1905
1911
1906
1912 def clearcollapsemsg(repo):
1907 def clearcollapsemsg(repo):
1913 """Remove collapse message file"""
1908 """Remove collapse message file"""
1914 repo.vfs.unlinkpath(b"last-message.txt", ignoremissing=True)
1909 repo.vfs.unlinkpath(b"last-message.txt", ignoremissing=True)
1915
1910
1916
1911
1917 def restorecollapsemsg(repo, isabort):
1912 def restorecollapsemsg(repo, isabort):
1918 """Restore previously stored collapse message"""
1913 """Restore previously stored collapse message"""
1919 try:
1914 try:
1920 f = repo.vfs(b"last-message.txt")
1915 f = repo.vfs(b"last-message.txt")
1921 collapsemsg = f.readline().strip()
1916 collapsemsg = f.readline().strip()
1922 f.close()
1917 f.close()
1923 except IOError as err:
1918 except IOError as err:
1924 if err.errno != errno.ENOENT:
1919 if err.errno != errno.ENOENT:
1925 raise
1920 raise
1926 if isabort:
1921 if isabort:
1927 # Oh well, just abort like normal
1922 # Oh well, just abort like normal
1928 collapsemsg = b''
1923 collapsemsg = b''
1929 else:
1924 else:
1930 raise error.Abort(_(b'missing .hg/last-message.txt for rebase'))
1925 raise error.Abort(_(b'missing .hg/last-message.txt for rebase'))
1931 return collapsemsg
1926 return collapsemsg
1932
1927
1933
1928
1934 def clearstatus(repo):
1929 def clearstatus(repo):
1935 """Remove the status files"""
1930 """Remove the status files"""
1936 # Make sure the active transaction won't write the state file
1931 # Make sure the active transaction won't write the state file
1937 tr = repo.currenttransaction()
1932 tr = repo.currenttransaction()
1938 if tr:
1933 if tr:
1939 tr.removefilegenerator(b'rebasestate')
1934 tr.removefilegenerator(b'rebasestate')
1940 repo.vfs.unlinkpath(b"rebasestate", ignoremissing=True)
1935 repo.vfs.unlinkpath(b"rebasestate", ignoremissing=True)
1941
1936
1942
1937
1943 def needupdate(repo, state):
1938 def needupdate(repo, state):
1944 '''check whether we should `update --clean` away from a merge, or if
1939 '''check whether we should `update --clean` away from a merge, or if
1945 somehow the working dir got forcibly updated, e.g. by older hg'''
1940 somehow the working dir got forcibly updated, e.g. by older hg'''
1946 parents = [p.rev() for p in repo[None].parents()]
1941 parents = [p.rev() for p in repo[None].parents()]
1947
1942
1948 # Are we in a merge state at all?
1943 # Are we in a merge state at all?
1949 if len(parents) < 2:
1944 if len(parents) < 2:
1950 return False
1945 return False
1951
1946
1952 # We should be standing on the first as-of-yet unrebased commit.
1947 # We should be standing on the first as-of-yet unrebased commit.
1953 firstunrebased = min(
1948 firstunrebased = min(
1954 [old for old, new in pycompat.iteritems(state) if new == nullrev]
1949 [old for old, new in pycompat.iteritems(state) if new == nullrev]
1955 )
1950 )
1956 if firstunrebased in parents:
1951 if firstunrebased in parents:
1957 return True
1952 return True
1958
1953
1959 return False
1954 return False
1960
1955
1961
1956
1962 def sortsource(destmap):
1957 def sortsource(destmap):
1963 """yield source revisions in an order that we only rebase things once
1958 """yield source revisions in an order that we only rebase things once
1964
1959
1965 If source and destination overlaps, we should filter out revisions
1960 If source and destination overlaps, we should filter out revisions
1966 depending on other revisions which hasn't been rebased yet.
1961 depending on other revisions which hasn't been rebased yet.
1967
1962
1968 Yield a sorted list of revisions each time.
1963 Yield a sorted list of revisions each time.
1969
1964
1970 For example, when rebasing A to B, B to C. This function yields [B], then
1965 For example, when rebasing A to B, B to C. This function yields [B], then
1971 [A], indicating B needs to be rebased first.
1966 [A], indicating B needs to be rebased first.
1972
1967
1973 Raise if there is a cycle so the rebase is impossible.
1968 Raise if there is a cycle so the rebase is impossible.
1974 """
1969 """
1975 srcset = set(destmap)
1970 srcset = set(destmap)
1976 while srcset:
1971 while srcset:
1977 srclist = sorted(srcset)
1972 srclist = sorted(srcset)
1978 result = []
1973 result = []
1979 for r in srclist:
1974 for r in srclist:
1980 if destmap[r] not in srcset:
1975 if destmap[r] not in srcset:
1981 result.append(r)
1976 result.append(r)
1982 if not result:
1977 if not result:
1983 raise error.Abort(_(b'source and destination form a cycle'))
1978 raise error.Abort(_(b'source and destination form a cycle'))
1984 srcset -= set(result)
1979 srcset -= set(result)
1985 yield result
1980 yield result
1986
1981
1987
1982
1988 def buildstate(repo, destmap, collapse):
1983 def buildstate(repo, destmap, collapse):
1989 '''Define which revisions are going to be rebased and where
1984 '''Define which revisions are going to be rebased and where
1990
1985
1991 repo: repo
1986 repo: repo
1992 destmap: {srcrev: destrev}
1987 destmap: {srcrev: destrev}
1993 '''
1988 '''
1994 rebaseset = destmap.keys()
1989 rebaseset = destmap.keys()
1995 originalwd = repo[b'.'].rev()
1990 originalwd = repo[b'.'].rev()
1996
1991
1997 # This check isn't strictly necessary, since mq detects commits over an
1992 # This check isn't strictly necessary, since mq detects commits over an
1998 # applied patch. But it prevents messing up the working directory when
1993 # applied patch. But it prevents messing up the working directory when
1999 # a partially completed rebase is blocked by mq.
1994 # a partially completed rebase is blocked by mq.
2000 if b'qtip' in repo.tags():
1995 if b'qtip' in repo.tags():
2001 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1996 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
2002 if set(destmap.values()) & mqapplied:
1997 if set(destmap.values()) & mqapplied:
2003 raise error.Abort(_(b'cannot rebase onto an applied mq patch'))
1998 raise error.Abort(_(b'cannot rebase onto an applied mq patch'))
2004
1999
2005 # Get "cycle" error early by exhausting the generator.
2000 # Get "cycle" error early by exhausting the generator.
2006 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
2001 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
2007 if not sortedsrc:
2002 if not sortedsrc:
2008 raise error.Abort(_(b'no matching revisions'))
2003 raise error.Abort(_(b'no matching revisions'))
2009
2004
2010 # Only check the first batch of revisions to rebase not depending on other
2005 # Only check the first batch of revisions to rebase not depending on other
2011 # rebaseset. This means "source is ancestor of destination" for the second
2006 # rebaseset. This means "source is ancestor of destination" for the second
2012 # (and following) batches of revisions are not checked here. We rely on
2007 # (and following) batches of revisions are not checked here. We rely on
2013 # "defineparents" to do that check.
2008 # "defineparents" to do that check.
2014 roots = list(repo.set(b'roots(%ld)', sortedsrc[0]))
2009 roots = list(repo.set(b'roots(%ld)', sortedsrc[0]))
2015 if not roots:
2010 if not roots:
2016 raise error.Abort(_(b'no matching revisions'))
2011 raise error.Abort(_(b'no matching revisions'))
2017
2012
2018 def revof(r):
2013 def revof(r):
2019 return r.rev()
2014 return r.rev()
2020
2015
2021 roots = sorted(roots, key=revof)
2016 roots = sorted(roots, key=revof)
2022 state = dict.fromkeys(rebaseset, revtodo)
2017 state = dict.fromkeys(rebaseset, revtodo)
2023 emptyrebase = len(sortedsrc) == 1
2018 emptyrebase = len(sortedsrc) == 1
2024 for root in roots:
2019 for root in roots:
2025 dest = repo[destmap[root.rev()]]
2020 dest = repo[destmap[root.rev()]]
2026 commonbase = root.ancestor(dest)
2021 commonbase = root.ancestor(dest)
2027 if commonbase == root:
2022 if commonbase == root:
2028 raise error.Abort(_(b'source is ancestor of destination'))
2023 raise error.Abort(_(b'source is ancestor of destination'))
2029 if commonbase == dest:
2024 if commonbase == dest:
2030 wctx = repo[None]
2025 wctx = repo[None]
2031 if dest == wctx.p1():
2026 if dest == wctx.p1():
2032 # when rebasing to '.', it will use the current wd branch name
2027 # when rebasing to '.', it will use the current wd branch name
2033 samebranch = root.branch() == wctx.branch()
2028 samebranch = root.branch() == wctx.branch()
2034 else:
2029 else:
2035 samebranch = root.branch() == dest.branch()
2030 samebranch = root.branch() == dest.branch()
2036 if not collapse and samebranch and dest in root.parents():
2031 if not collapse and samebranch and dest in root.parents():
2037 # mark the revision as done by setting its new revision
2032 # mark the revision as done by setting its new revision
2038 # equal to its old (current) revisions
2033 # equal to its old (current) revisions
2039 state[root.rev()] = root.rev()
2034 state[root.rev()] = root.rev()
2040 repo.ui.debug(b'source is a child of destination\n')
2035 repo.ui.debug(b'source is a child of destination\n')
2041 continue
2036 continue
2042
2037
2043 emptyrebase = False
2038 emptyrebase = False
2044 repo.ui.debug(b'rebase onto %s starting from %s\n' % (dest, root))
2039 repo.ui.debug(b'rebase onto %s starting from %s\n' % (dest, root))
2045 if emptyrebase:
2040 if emptyrebase:
2046 return None
2041 return None
2047 for rev in sorted(state):
2042 for rev in sorted(state):
2048 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
2043 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
2049 # if all parents of this revision are done, then so is this revision
2044 # if all parents of this revision are done, then so is this revision
2050 if parents and all((state.get(p) == p for p in parents)):
2045 if parents and all((state.get(p) == p for p in parents)):
2051 state[rev] = rev
2046 state[rev] = rev
2052 return originalwd, destmap, state
2047 return originalwd, destmap, state
2053
2048
2054
2049
2055 def clearrebased(
2050 def clearrebased(
2056 ui,
2051 ui,
2057 repo,
2052 repo,
2058 destmap,
2053 destmap,
2059 state,
2054 state,
2060 skipped,
2055 skipped,
2061 collapsedas=None,
2056 collapsedas=None,
2062 keepf=False,
2057 keepf=False,
2063 fm=None,
2058 fm=None,
2064 backup=True,
2059 backup=True,
2065 ):
2060 ):
2066 """dispose of rebased revision at the end of the rebase
2061 """dispose of rebased revision at the end of the rebase
2067
2062
2068 If `collapsedas` is not None, the rebase was a collapse whose result if the
2063 If `collapsedas` is not None, the rebase was a collapse whose result if the
2069 `collapsedas` node.
2064 `collapsedas` node.
2070
2065
2071 If `keepf` is not True, the rebase has --keep set and no nodes should be
2066 If `keepf` is not True, the rebase has --keep set and no nodes should be
2072 removed (but bookmarks still need to be moved).
2067 removed (but bookmarks still need to be moved).
2073
2068
2074 If `backup` is False, no backup will be stored when stripping rebased
2069 If `backup` is False, no backup will be stored when stripping rebased
2075 revisions.
2070 revisions.
2076 """
2071 """
2077 tonode = repo.changelog.node
2072 tonode = repo.changelog.node
2078 replacements = {}
2073 replacements = {}
2079 moves = {}
2074 moves = {}
2080 stripcleanup = not obsolete.isenabled(repo, obsolete.createmarkersopt)
2075 stripcleanup = not obsolete.isenabled(repo, obsolete.createmarkersopt)
2081
2076
2082 collapsednodes = []
2077 collapsednodes = []
2083 for rev, newrev in sorted(state.items()):
2078 for rev, newrev in sorted(state.items()):
2084 if newrev >= 0 and newrev != rev:
2079 if newrev >= 0 and newrev != rev:
2085 oldnode = tonode(rev)
2080 oldnode = tonode(rev)
2086 newnode = collapsedas or tonode(newrev)
2081 newnode = collapsedas or tonode(newrev)
2087 moves[oldnode] = newnode
2082 moves[oldnode] = newnode
2088 succs = None
2083 succs = None
2089 if rev in skipped:
2084 if rev in skipped:
2090 if stripcleanup or not repo[rev].obsolete():
2085 if stripcleanup or not repo[rev].obsolete():
2091 succs = ()
2086 succs = ()
2092 elif collapsedas:
2087 elif collapsedas:
2093 collapsednodes.append(oldnode)
2088 collapsednodes.append(oldnode)
2094 else:
2089 else:
2095 succs = (newnode,)
2090 succs = (newnode,)
2096 if succs is not None:
2091 if succs is not None:
2097 replacements[(oldnode,)] = succs
2092 replacements[(oldnode,)] = succs
2098 if collapsednodes:
2093 if collapsednodes:
2099 replacements[tuple(collapsednodes)] = (collapsedas,)
2094 replacements[tuple(collapsednodes)] = (collapsedas,)
2100 if fm:
2095 if fm:
2101 hf = fm.hexfunc
2096 hf = fm.hexfunc
2102 fl = fm.formatlist
2097 fl = fm.formatlist
2103 fd = fm.formatdict
2098 fd = fm.formatdict
2104 changes = {}
2099 changes = {}
2105 for oldns, newn in pycompat.iteritems(replacements):
2100 for oldns, newn in pycompat.iteritems(replacements):
2106 for oldn in oldns:
2101 for oldn in oldns:
2107 changes[hf(oldn)] = fl([hf(n) for n in newn], name=b'node')
2102 changes[hf(oldn)] = fl([hf(n) for n in newn], name=b'node')
2108 nodechanges = fd(changes, key=b"oldnode", value=b"newnodes")
2103 nodechanges = fd(changes, key=b"oldnode", value=b"newnodes")
2109 fm.data(nodechanges=nodechanges)
2104 fm.data(nodechanges=nodechanges)
2110 if keepf:
2105 if keepf:
2111 replacements = {}
2106 replacements = {}
2112 scmutil.cleanupnodes(repo, replacements, b'rebase', moves, backup=backup)
2107 scmutil.cleanupnodes(repo, replacements, b'rebase', moves, backup=backup)
2113
2108
2114
2109
2115 def pullrebase(orig, ui, repo, *args, **opts):
2110 def pullrebase(orig, ui, repo, *args, **opts):
2116 """Call rebase after pull if the latter has been invoked with --rebase"""
2111 """Call rebase after pull if the latter has been invoked with --rebase"""
2117 if opts.get('rebase'):
2112 if opts.get('rebase'):
2118 if ui.configbool(b'commands', b'rebase.requiredest'):
2113 if ui.configbool(b'commands', b'rebase.requiredest'):
2119 msg = _(b'rebase destination required by configuration')
2114 msg = _(b'rebase destination required by configuration')
2120 hint = _(b'use hg pull followed by hg rebase -d DEST')
2115 hint = _(b'use hg pull followed by hg rebase -d DEST')
2121 raise error.Abort(msg, hint=hint)
2116 raise error.Abort(msg, hint=hint)
2122
2117
2123 with repo.wlock(), repo.lock():
2118 with repo.wlock(), repo.lock():
2124 if opts.get('update'):
2119 if opts.get('update'):
2125 del opts['update']
2120 del opts['update']
2126 ui.debug(
2121 ui.debug(
2127 b'--update and --rebase are not compatible, ignoring '
2122 b'--update and --rebase are not compatible, ignoring '
2128 b'the update flag\n'
2123 b'the update flag\n'
2129 )
2124 )
2130
2125
2131 cmdutil.checkunfinished(repo, skipmerge=True)
2126 cmdutil.checkunfinished(repo, skipmerge=True)
2132 cmdutil.bailifchanged(
2127 cmdutil.bailifchanged(
2133 repo,
2128 repo,
2134 hint=_(
2129 hint=_(
2135 b'cannot pull with rebase: '
2130 b'cannot pull with rebase: '
2136 b'please commit or shelve your changes first'
2131 b'please commit or shelve your changes first'
2137 ),
2132 ),
2138 )
2133 )
2139
2134
2140 revsprepull = len(repo)
2135 revsprepull = len(repo)
2141 origpostincoming = commands.postincoming
2136 origpostincoming = commands.postincoming
2142
2137
2143 def _dummy(*args, **kwargs):
2138 def _dummy(*args, **kwargs):
2144 pass
2139 pass
2145
2140
2146 commands.postincoming = _dummy
2141 commands.postincoming = _dummy
2147 try:
2142 try:
2148 ret = orig(ui, repo, *args, **opts)
2143 ret = orig(ui, repo, *args, **opts)
2149 finally:
2144 finally:
2150 commands.postincoming = origpostincoming
2145 commands.postincoming = origpostincoming
2151 revspostpull = len(repo)
2146 revspostpull = len(repo)
2152 if revspostpull > revsprepull:
2147 if revspostpull > revsprepull:
2153 # --rev option from pull conflict with rebase own --rev
2148 # --rev option from pull conflict with rebase own --rev
2154 # dropping it
2149 # dropping it
2155 if 'rev' in opts:
2150 if 'rev' in opts:
2156 del opts['rev']
2151 del opts['rev']
2157 # positional argument from pull conflicts with rebase's own
2152 # positional argument from pull conflicts with rebase's own
2158 # --source.
2153 # --source.
2159 if 'source' in opts:
2154 if 'source' in opts:
2160 del opts['source']
2155 del opts['source']
2161 # revsprepull is the len of the repo, not revnum of tip.
2156 # revsprepull is the len of the repo, not revnum of tip.
2162 destspace = list(repo.changelog.revs(start=revsprepull))
2157 destspace = list(repo.changelog.revs(start=revsprepull))
2163 opts['_destspace'] = destspace
2158 opts['_destspace'] = destspace
2164 try:
2159 try:
2165 rebase(ui, repo, **opts)
2160 rebase(ui, repo, **opts)
2166 except error.NoMergeDestAbort:
2161 except error.NoMergeDestAbort:
2167 # we can maybe update instead
2162 # we can maybe update instead
2168 rev, _a, _b = destutil.destupdate(repo)
2163 rev, _a, _b = destutil.destupdate(repo)
2169 if rev == repo[b'.'].rev():
2164 if rev == repo[b'.'].rev():
2170 ui.status(_(b'nothing to rebase\n'))
2165 ui.status(_(b'nothing to rebase\n'))
2171 else:
2166 else:
2172 ui.status(_(b'nothing to rebase - updating instead\n'))
2167 ui.status(_(b'nothing to rebase - updating instead\n'))
2173 # not passing argument to get the bare update behavior
2168 # not passing argument to get the bare update behavior
2174 # with warning and trumpets
2169 # with warning and trumpets
2175 commands.update(ui, repo)
2170 commands.update(ui, repo)
2176 else:
2171 else:
2177 if opts.get('tool'):
2172 if opts.get('tool'):
2178 raise error.Abort(_(b'--tool can only be used with --rebase'))
2173 raise error.Abort(_(b'--tool can only be used with --rebase'))
2179 ret = orig(ui, repo, *args, **opts)
2174 ret = orig(ui, repo, *args, **opts)
2180
2175
2181 return ret
2176 return ret
2182
2177
2183
2178
2184 def _filterobsoleterevs(repo, revs):
2179 def _filterobsoleterevs(repo, revs):
2185 """returns a set of the obsolete revisions in revs"""
2180 """returns a set of the obsolete revisions in revs"""
2186 return set(r for r in revs if repo[r].obsolete())
2181 return set(r for r in revs if repo[r].obsolete())
2187
2182
2188
2183
2189 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
2184 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
2190 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
2185 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
2191
2186
2192 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
2187 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
2193 obsolete nodes to be rebased given in `rebaseobsrevs`.
2188 obsolete nodes to be rebased given in `rebaseobsrevs`.
2194
2189
2195 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
2190 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
2196 without a successor in destination.
2191 without a successor in destination.
2197
2192
2198 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
2193 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
2199 obsolete successors.
2194 obsolete successors.
2200 """
2195 """
2201 obsoletenotrebased = {}
2196 obsoletenotrebased = {}
2202 obsoletewithoutsuccessorindestination = set()
2197 obsoletewithoutsuccessorindestination = set()
2203 obsoleteextinctsuccessors = set()
2198 obsoleteextinctsuccessors = set()
2204
2199
2205 assert repo.filtername is None
2200 assert repo.filtername is None
2206 cl = repo.changelog
2201 cl = repo.changelog
2207 get_rev = cl.index.get_rev
2202 get_rev = cl.index.get_rev
2208 extinctrevs = set(repo.revs(b'extinct()'))
2203 extinctrevs = set(repo.revs(b'extinct()'))
2209 for srcrev in rebaseobsrevs:
2204 for srcrev in rebaseobsrevs:
2210 srcnode = cl.node(srcrev)
2205 srcnode = cl.node(srcrev)
2211 # XXX: more advanced APIs are required to handle split correctly
2206 # XXX: more advanced APIs are required to handle split correctly
2212 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
2207 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
2213 # obsutil.allsuccessors includes node itself
2208 # obsutil.allsuccessors includes node itself
2214 successors.remove(srcnode)
2209 successors.remove(srcnode)
2215 succrevs = {get_rev(s) for s in successors}
2210 succrevs = {get_rev(s) for s in successors}
2216 succrevs.discard(None)
2211 succrevs.discard(None)
2217 if succrevs.issubset(extinctrevs):
2212 if succrevs.issubset(extinctrevs):
2218 # all successors are extinct
2213 # all successors are extinct
2219 obsoleteextinctsuccessors.add(srcrev)
2214 obsoleteextinctsuccessors.add(srcrev)
2220 if not successors:
2215 if not successors:
2221 # no successor
2216 # no successor
2222 obsoletenotrebased[srcrev] = None
2217 obsoletenotrebased[srcrev] = None
2223 else:
2218 else:
2224 dstrev = destmap[srcrev]
2219 dstrev = destmap[srcrev]
2225 for succrev in succrevs:
2220 for succrev in succrevs:
2226 if cl.isancestorrev(succrev, dstrev):
2221 if cl.isancestorrev(succrev, dstrev):
2227 obsoletenotrebased[srcrev] = succrev
2222 obsoletenotrebased[srcrev] = succrev
2228 break
2223 break
2229 else:
2224 else:
2230 # If 'srcrev' has a successor in rebase set but none in
2225 # If 'srcrev' has a successor in rebase set but none in
2231 # destination (which would be catched above), we shall skip it
2226 # destination (which would be catched above), we shall skip it
2232 # and its descendants to avoid divergence.
2227 # and its descendants to avoid divergence.
2233 if srcrev in extinctrevs or any(s in destmap for s in succrevs):
2228 if srcrev in extinctrevs or any(s in destmap for s in succrevs):
2234 obsoletewithoutsuccessorindestination.add(srcrev)
2229 obsoletewithoutsuccessorindestination.add(srcrev)
2235
2230
2236 return (
2231 return (
2237 obsoletenotrebased,
2232 obsoletenotrebased,
2238 obsoletewithoutsuccessorindestination,
2233 obsoletewithoutsuccessorindestination,
2239 obsoleteextinctsuccessors,
2234 obsoleteextinctsuccessors,
2240 )
2235 )
2241
2236
2242
2237
2243 def abortrebase(ui, repo):
2238 def abortrebase(ui, repo):
2244 with repo.wlock(), repo.lock():
2239 with repo.wlock(), repo.lock():
2245 rbsrt = rebaseruntime(repo, ui)
2240 rbsrt = rebaseruntime(repo, ui)
2246 rbsrt._prepareabortorcontinue(isabort=True)
2241 rbsrt._prepareabortorcontinue(isabort=True)
2247
2242
2248
2243
2249 def continuerebase(ui, repo):
2244 def continuerebase(ui, repo):
2250 with repo.wlock(), repo.lock():
2245 with repo.wlock(), repo.lock():
2251 rbsrt = rebaseruntime(repo, ui)
2246 rbsrt = rebaseruntime(repo, ui)
2252 ms = mergemod.mergestate.read(repo)
2247 ms = mergemod.mergestate.read(repo)
2253 mergeutil.checkunresolved(ms)
2248 mergeutil.checkunresolved(ms)
2254 retcode = rbsrt._prepareabortorcontinue(isabort=False)
2249 retcode = rbsrt._prepareabortorcontinue(isabort=False)
2255 if retcode is not None:
2250 if retcode is not None:
2256 return retcode
2251 return retcode
2257 rbsrt._performrebase(None)
2252 rbsrt._performrebase(None)
2258 rbsrt._finishrebase()
2253 rbsrt._finishrebase()
2259
2254
2260
2255
2261 def summaryhook(ui, repo):
2256 def summaryhook(ui, repo):
2262 if not repo.vfs.exists(b'rebasestate'):
2257 if not repo.vfs.exists(b'rebasestate'):
2263 return
2258 return
2264 try:
2259 try:
2265 rbsrt = rebaseruntime(repo, ui, {})
2260 rbsrt = rebaseruntime(repo, ui, {})
2266 rbsrt.restorestatus()
2261 rbsrt.restorestatus()
2267 state = rbsrt.state
2262 state = rbsrt.state
2268 except error.RepoLookupError:
2263 except error.RepoLookupError:
2269 # i18n: column positioning for "hg summary"
2264 # i18n: column positioning for "hg summary"
2270 msg = _(b'rebase: (use "hg rebase --abort" to clear broken state)\n')
2265 msg = _(b'rebase: (use "hg rebase --abort" to clear broken state)\n')
2271 ui.write(msg)
2266 ui.write(msg)
2272 return
2267 return
2273 numrebased = len([i for i in pycompat.itervalues(state) if i >= 0])
2268 numrebased = len([i for i in pycompat.itervalues(state) if i >= 0])
2274 # i18n: column positioning for "hg summary"
2269 # i18n: column positioning for "hg summary"
2275 ui.write(
2270 ui.write(
2276 _(b'rebase: %s, %s (rebase --continue)\n')
2271 _(b'rebase: %s, %s (rebase --continue)\n')
2277 % (
2272 % (
2278 ui.label(_(b'%d rebased'), b'rebase.rebased') % numrebased,
2273 ui.label(_(b'%d rebased'), b'rebase.rebased') % numrebased,
2279 ui.label(_(b'%d remaining'), b'rebase.remaining')
2274 ui.label(_(b'%d remaining'), b'rebase.remaining')
2280 % (len(state) - numrebased),
2275 % (len(state) - numrebased),
2281 )
2276 )
2282 )
2277 )
2283
2278
2284
2279
2285 def uisetup(ui):
2280 def uisetup(ui):
2286 # Replace pull with a decorator to provide --rebase option
2281 # Replace pull with a decorator to provide --rebase option
2287 entry = extensions.wrapcommand(commands.table, b'pull', pullrebase)
2282 entry = extensions.wrapcommand(commands.table, b'pull', pullrebase)
2288 entry[1].append(
2283 entry[1].append(
2289 (b'', b'rebase', None, _(b"rebase working directory to branch head"))
2284 (b'', b'rebase', None, _(b"rebase working directory to branch head"))
2290 )
2285 )
2291 entry[1].append((b't', b'tool', b'', _(b"specify merge tool for rebase")))
2286 entry[1].append((b't', b'tool', b'', _(b"specify merge tool for rebase")))
2292 cmdutil.summaryhooks.add(b'rebase', summaryhook)
2287 cmdutil.summaryhooks.add(b'rebase', summaryhook)
2293 statemod.addunfinished(
2288 statemod.addunfinished(
2294 b'rebase',
2289 b'rebase',
2295 fname=b'rebasestate',
2290 fname=b'rebasestate',
2296 stopflag=True,
2291 stopflag=True,
2297 continueflag=True,
2292 continueflag=True,
2298 abortfunc=abortrebase,
2293 abortfunc=abortrebase,
2299 continuefunc=continuerebase,
2294 continuefunc=continuerebase,
2300 )
2295 )
General Comments 0
You need to be logged in to leave comments. Login now