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