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