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