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