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