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