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