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