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