##// END OF EJS Templates
rebase: do not bail on uncomitted changes if rebasing in-memory...
Phil Cohen -
r35292:aa660c12 default
parent child Browse files
Show More
@@ -1,1728 +1,1730 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 nullid,
24 nullid,
25 nullrev,
25 nullrev,
26 short,
26 short,
27 )
27 )
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 lock,
38 lock,
39 merge as mergemod,
39 merge as mergemod,
40 mergeutil,
40 mergeutil,
41 obsolete,
41 obsolete,
42 obsutil,
42 obsutil,
43 patch,
43 patch,
44 phases,
44 phases,
45 pycompat,
45 pycompat,
46 registrar,
46 registrar,
47 repair,
47 repair,
48 revset,
48 revset,
49 revsetlang,
49 revsetlang,
50 scmutil,
50 scmutil,
51 smartset,
51 smartset,
52 util,
52 util,
53 )
53 )
54
54
55 release = lock.release
55 release = lock.release
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 = '-1'
62 revtodostr = '-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 = {'-2', '-3', '-4', '-5'}
66 legacystates = {'-2', '-3', '-4', '-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 = 'ships-with-hg-core'
74 testedwith = 'ships-with-hg-core'
75
75
76 def _nothingtorebase():
76 def _nothingtorebase():
77 return 1
77 return 1
78
78
79 def _savegraft(ctx, extra):
79 def _savegraft(ctx, extra):
80 s = ctx.extra().get('source', None)
80 s = ctx.extra().get('source', None)
81 if s is not None:
81 if s is not None:
82 extra['source'] = s
82 extra['source'] = s
83 s = ctx.extra().get('intermediate-source', None)
83 s = ctx.extra().get('intermediate-source', None)
84 if s is not None:
84 if s is not None:
85 extra['intermediate-source'] = s
85 extra['intermediate-source'] = s
86
86
87 def _savebranch(ctx, extra):
87 def _savebranch(ctx, extra):
88 extra['branch'] = ctx.branch()
88 extra['branch'] = ctx.branch()
89
89
90 def _makeextrafn(copiers):
90 def _makeextrafn(copiers):
91 """make an extrafn out of the given copy-functions.
91 """make an extrafn out of the given copy-functions.
92
92
93 A copy function takes a context and an extra dict, and mutates the
93 A copy function takes a context and an extra dict, and mutates the
94 extra dict as needed based on the given context.
94 extra dict as needed based on the given context.
95 """
95 """
96 def extrafn(ctx, extra):
96 def extrafn(ctx, extra):
97 for c in copiers:
97 for c in copiers:
98 c(ctx, extra)
98 c(ctx, extra)
99 return extrafn
99 return extrafn
100
100
101 def _destrebase(repo, sourceset, destspace=None):
101 def _destrebase(repo, sourceset, destspace=None):
102 """small wrapper around destmerge to pass the right extra args
102 """small wrapper around destmerge to pass the right extra args
103
103
104 Please wrap destutil.destmerge instead."""
104 Please wrap destutil.destmerge instead."""
105 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
105 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
106 onheadcheck=False, destspace=destspace)
106 onheadcheck=False, destspace=destspace)
107
107
108 revsetpredicate = registrar.revsetpredicate()
108 revsetpredicate = registrar.revsetpredicate()
109
109
110 @revsetpredicate('_destrebase')
110 @revsetpredicate('_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 def _ctxdesc(ctx):
123 def _ctxdesc(ctx):
124 """short description for a context"""
124 """short description for a context"""
125 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
125 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
126 ctx.description().split('\n', 1)[0])
126 ctx.description().split('\n', 1)[0])
127 repo = ctx.repo()
127 repo = ctx.repo()
128 names = []
128 names = []
129 for nsname, ns in repo.names.iteritems():
129 for nsname, ns in repo.names.iteritems():
130 if nsname == 'branches':
130 if nsname == 'branches':
131 continue
131 continue
132 names.extend(ns.names(repo, ctx.node()))
132 names.extend(ns.names(repo, ctx.node()))
133 if names:
133 if names:
134 desc += ' (%s)' % ' '.join(names)
134 desc += ' (%s)' % ' '.join(names)
135 return desc
135 return desc
136
136
137 class rebaseruntime(object):
137 class rebaseruntime(object):
138 """This class is a container for rebase runtime state"""
138 """This class is a container for rebase runtime state"""
139 def __init__(self, repo, ui, opts=None):
139 def __init__(self, repo, ui, opts=None):
140 if opts is None:
140 if opts is None:
141 opts = {}
141 opts = {}
142
142
143 # prepared: whether we have rebasestate prepared or not. Currently it
143 # prepared: whether we have rebasestate prepared or not. Currently it
144 # decides whether "self.repo" is unfiltered or not.
144 # decides whether "self.repo" is unfiltered or not.
145 # The rebasestate has explicit hash to hash instructions not depending
145 # The rebasestate has explicit hash to hash instructions not depending
146 # on visibility. If rebasestate exists (in-memory or on-disk), use
146 # on visibility. If rebasestate exists (in-memory or on-disk), use
147 # unfiltered repo to avoid visibility issues.
147 # unfiltered repo to avoid visibility issues.
148 # Before knowing rebasestate (i.e. when starting a new rebase (not
148 # Before knowing rebasestate (i.e. when starting a new rebase (not
149 # --continue or --abort)), the original repo should be used so
149 # --continue or --abort)), the original repo should be used so
150 # visibility-dependent revsets are correct.
150 # visibility-dependent revsets are correct.
151 self.prepared = False
151 self.prepared = False
152 self._repo = repo
152 self._repo = repo
153
153
154 self.ui = ui
154 self.ui = ui
155 self.opts = opts
155 self.opts = opts
156 self.originalwd = None
156 self.originalwd = None
157 self.external = nullrev
157 self.external = nullrev
158 # Mapping between the old revision id and either what is the new rebased
158 # Mapping between the old revision id and either what is the new rebased
159 # revision or what needs to be done with the old revision. The state
159 # revision or what needs to be done with the old revision. The state
160 # dict will be what contains most of the rebase progress state.
160 # dict will be what contains most of the rebase progress state.
161 self.state = {}
161 self.state = {}
162 self.activebookmark = None
162 self.activebookmark = None
163 self.destmap = {}
163 self.destmap = {}
164 self.skipped = set()
164 self.skipped = set()
165
165
166 self.collapsef = opts.get('collapse', False)
166 self.collapsef = opts.get('collapse', False)
167 self.collapsemsg = cmdutil.logmessage(ui, opts)
167 self.collapsemsg = cmdutil.logmessage(ui, opts)
168 self.date = opts.get('date', None)
168 self.date = opts.get('date', None)
169
169
170 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
170 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
171 self.extrafns = [_savegraft]
171 self.extrafns = [_savegraft]
172 if e:
172 if e:
173 self.extrafns = [e]
173 self.extrafns = [e]
174
174
175 self.keepf = opts.get('keep', False)
175 self.keepf = opts.get('keep', False)
176 self.keepbranchesf = opts.get('keepbranches', False)
176 self.keepbranchesf = opts.get('keepbranches', False)
177 # keepopen is not meant for use on the command line, but by
177 # keepopen is not meant for use on the command line, but by
178 # other extensions
178 # other extensions
179 self.keepopen = opts.get('keepopen', False)
179 self.keepopen = opts.get('keepopen', False)
180 self.obsoletenotrebased = {}
180 self.obsoletenotrebased = {}
181 self.obsoletewithoutsuccessorindestination = set()
181 self.obsoletewithoutsuccessorindestination = set()
182 self.inmemory = opts.get('inmemory', False)
182 self.inmemory = opts.get('inmemory', False)
183
183
184 @property
184 @property
185 def repo(self):
185 def repo(self):
186 if self.prepared:
186 if self.prepared:
187 return self._repo.unfiltered()
187 return self._repo.unfiltered()
188 else:
188 else:
189 return self._repo
189 return self._repo
190
190
191 def storestatus(self, tr=None):
191 def storestatus(self, tr=None):
192 """Store the current status to allow recovery"""
192 """Store the current status to allow recovery"""
193 if tr:
193 if tr:
194 tr.addfilegenerator('rebasestate', ('rebasestate',),
194 tr.addfilegenerator('rebasestate', ('rebasestate',),
195 self._writestatus, location='plain')
195 self._writestatus, location='plain')
196 else:
196 else:
197 with self.repo.vfs("rebasestate", "w") as f:
197 with self.repo.vfs("rebasestate", "w") as f:
198 self._writestatus(f)
198 self._writestatus(f)
199
199
200 def _writestatus(self, f):
200 def _writestatus(self, f):
201 repo = self.repo
201 repo = self.repo
202 assert repo.filtername is None
202 assert repo.filtername is None
203 f.write(repo[self.originalwd].hex() + '\n')
203 f.write(repo[self.originalwd].hex() + '\n')
204 # was "dest". we now write dest per src root below.
204 # was "dest". we now write dest per src root below.
205 f.write('\n')
205 f.write('\n')
206 f.write(repo[self.external].hex() + '\n')
206 f.write(repo[self.external].hex() + '\n')
207 f.write('%d\n' % int(self.collapsef))
207 f.write('%d\n' % int(self.collapsef))
208 f.write('%d\n' % int(self.keepf))
208 f.write('%d\n' % int(self.keepf))
209 f.write('%d\n' % int(self.keepbranchesf))
209 f.write('%d\n' % int(self.keepbranchesf))
210 f.write('%s\n' % (self.activebookmark or ''))
210 f.write('%s\n' % (self.activebookmark or ''))
211 destmap = self.destmap
211 destmap = self.destmap
212 for d, v in self.state.iteritems():
212 for d, v in self.state.iteritems():
213 oldrev = repo[d].hex()
213 oldrev = repo[d].hex()
214 if v >= 0:
214 if v >= 0:
215 newrev = repo[v].hex()
215 newrev = repo[v].hex()
216 else:
216 else:
217 newrev = v
217 newrev = v
218 destnode = repo[destmap[d]].hex()
218 destnode = repo[destmap[d]].hex()
219 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
219 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
220 repo.ui.debug('rebase status stored\n')
220 repo.ui.debug('rebase status stored\n')
221
221
222 def restorestatus(self):
222 def restorestatus(self):
223 """Restore a previously stored status"""
223 """Restore a previously stored status"""
224 self.prepared = True
224 self.prepared = True
225 repo = self.repo
225 repo = self.repo
226 assert repo.filtername is None
226 assert repo.filtername is None
227 keepbranches = None
227 keepbranches = None
228 legacydest = None
228 legacydest = None
229 collapse = False
229 collapse = False
230 external = nullrev
230 external = nullrev
231 activebookmark = None
231 activebookmark = None
232 state = {}
232 state = {}
233 destmap = {}
233 destmap = {}
234
234
235 try:
235 try:
236 f = repo.vfs("rebasestate")
236 f = repo.vfs("rebasestate")
237 for i, l in enumerate(f.read().splitlines()):
237 for i, l in enumerate(f.read().splitlines()):
238 if i == 0:
238 if i == 0:
239 originalwd = repo[l].rev()
239 originalwd = repo[l].rev()
240 elif i == 1:
240 elif i == 1:
241 # this line should be empty in newer version. but legacy
241 # this line should be empty in newer version. but legacy
242 # clients may still use it
242 # clients may still use it
243 if l:
243 if l:
244 legacydest = repo[l].rev()
244 legacydest = repo[l].rev()
245 elif i == 2:
245 elif i == 2:
246 external = repo[l].rev()
246 external = repo[l].rev()
247 elif i == 3:
247 elif i == 3:
248 collapse = bool(int(l))
248 collapse = bool(int(l))
249 elif i == 4:
249 elif i == 4:
250 keep = bool(int(l))
250 keep = bool(int(l))
251 elif i == 5:
251 elif i == 5:
252 keepbranches = bool(int(l))
252 keepbranches = bool(int(l))
253 elif i == 6 and not (len(l) == 81 and ':' in l):
253 elif i == 6 and not (len(l) == 81 and ':' in l):
254 # line 6 is a recent addition, so for backwards
254 # line 6 is a recent addition, so for backwards
255 # compatibility check that the line doesn't look like the
255 # compatibility check that the line doesn't look like the
256 # oldrev:newrev lines
256 # oldrev:newrev lines
257 activebookmark = l
257 activebookmark = l
258 else:
258 else:
259 args = l.split(':')
259 args = l.split(':')
260 oldrev = args[0]
260 oldrev = args[0]
261 newrev = args[1]
261 newrev = args[1]
262 if newrev in legacystates:
262 if newrev in legacystates:
263 continue
263 continue
264 if len(args) > 2:
264 if len(args) > 2:
265 destnode = args[2]
265 destnode = args[2]
266 else:
266 else:
267 destnode = legacydest
267 destnode = legacydest
268 destmap[repo[oldrev].rev()] = repo[destnode].rev()
268 destmap[repo[oldrev].rev()] = repo[destnode].rev()
269 if newrev in (nullid, revtodostr):
269 if newrev in (nullid, revtodostr):
270 state[repo[oldrev].rev()] = revtodo
270 state[repo[oldrev].rev()] = revtodo
271 # Legacy compat special case
271 # Legacy compat special case
272 else:
272 else:
273 state[repo[oldrev].rev()] = repo[newrev].rev()
273 state[repo[oldrev].rev()] = repo[newrev].rev()
274
274
275 except IOError as err:
275 except IOError as err:
276 if err.errno != errno.ENOENT:
276 if err.errno != errno.ENOENT:
277 raise
277 raise
278 cmdutil.wrongtooltocontinue(repo, _('rebase'))
278 cmdutil.wrongtooltocontinue(repo, _('rebase'))
279
279
280 if keepbranches is None:
280 if keepbranches is None:
281 raise error.Abort(_('.hg/rebasestate is incomplete'))
281 raise error.Abort(_('.hg/rebasestate is incomplete'))
282
282
283 skipped = set()
283 skipped = set()
284 # recompute the set of skipped revs
284 # recompute the set of skipped revs
285 if not collapse:
285 if not collapse:
286 seen = set(destmap.values())
286 seen = set(destmap.values())
287 for old, new in sorted(state.items()):
287 for old, new in sorted(state.items()):
288 if new != revtodo and new in seen:
288 if new != revtodo and new in seen:
289 skipped.add(old)
289 skipped.add(old)
290 seen.add(new)
290 seen.add(new)
291 repo.ui.debug('computed skipped revs: %s\n' %
291 repo.ui.debug('computed skipped revs: %s\n' %
292 (' '.join(str(r) for r in sorted(skipped)) or None))
292 (' '.join(str(r) for r in sorted(skipped)) or None))
293 repo.ui.debug('rebase status resumed\n')
293 repo.ui.debug('rebase status resumed\n')
294
294
295 self.originalwd = originalwd
295 self.originalwd = originalwd
296 self.destmap = destmap
296 self.destmap = destmap
297 self.state = state
297 self.state = state
298 self.skipped = skipped
298 self.skipped = skipped
299 self.collapsef = collapse
299 self.collapsef = collapse
300 self.keepf = keep
300 self.keepf = keep
301 self.keepbranchesf = keepbranches
301 self.keepbranchesf = keepbranches
302 self.external = external
302 self.external = external
303 self.activebookmark = activebookmark
303 self.activebookmark = activebookmark
304
304
305 def _handleskippingobsolete(self, obsoleterevs, destmap):
305 def _handleskippingobsolete(self, obsoleterevs, destmap):
306 """Compute structures necessary for skipping obsolete revisions
306 """Compute structures necessary for skipping obsolete revisions
307
307
308 obsoleterevs: iterable of all obsolete revisions in rebaseset
308 obsoleterevs: iterable of all obsolete revisions in rebaseset
309 destmap: {srcrev: destrev} destination revisions
309 destmap: {srcrev: destrev} destination revisions
310 """
310 """
311 self.obsoletenotrebased = {}
311 self.obsoletenotrebased = {}
312 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
312 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
313 return
313 return
314 obsoleteset = set(obsoleterevs)
314 obsoleteset = set(obsoleterevs)
315 self.obsoletenotrebased, self.obsoletewithoutsuccessorindestination = \
315 self.obsoletenotrebased, self.obsoletewithoutsuccessorindestination = \
316 _computeobsoletenotrebased(self.repo, obsoleteset, destmap)
316 _computeobsoletenotrebased(self.repo, obsoleteset, destmap)
317 skippedset = set(self.obsoletenotrebased)
317 skippedset = set(self.obsoletenotrebased)
318 skippedset.update(self.obsoletewithoutsuccessorindestination)
318 skippedset.update(self.obsoletewithoutsuccessorindestination)
319 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
319 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
320
320
321 def _prepareabortorcontinue(self, isabort):
321 def _prepareabortorcontinue(self, isabort):
322 try:
322 try:
323 self.restorestatus()
323 self.restorestatus()
324 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
324 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
325 except error.RepoLookupError:
325 except error.RepoLookupError:
326 if isabort:
326 if isabort:
327 clearstatus(self.repo)
327 clearstatus(self.repo)
328 clearcollapsemsg(self.repo)
328 clearcollapsemsg(self.repo)
329 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
329 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
330 ' only broken state is cleared)\n'))
330 ' only broken state is cleared)\n'))
331 return 0
331 return 0
332 else:
332 else:
333 msg = _('cannot continue inconsistent rebase')
333 msg = _('cannot continue inconsistent rebase')
334 hint = _('use "hg rebase --abort" to clear broken state')
334 hint = _('use "hg rebase --abort" to clear broken state')
335 raise error.Abort(msg, hint=hint)
335 raise error.Abort(msg, hint=hint)
336 if isabort:
336 if isabort:
337 return abort(self.repo, self.originalwd, self.destmap,
337 return abort(self.repo, self.originalwd, self.destmap,
338 self.state, activebookmark=self.activebookmark)
338 self.state, activebookmark=self.activebookmark)
339
339
340 def _preparenewrebase(self, destmap):
340 def _preparenewrebase(self, destmap):
341 if not destmap:
341 if not destmap:
342 return _nothingtorebase()
342 return _nothingtorebase()
343
343
344 rebaseset = destmap.keys()
344 rebaseset = destmap.keys()
345 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
345 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
346 if (not (self.keepf or allowunstable)
346 if (not (self.keepf or allowunstable)
347 and self.repo.revs('first(children(%ld) - %ld)',
347 and self.repo.revs('first(children(%ld) - %ld)',
348 rebaseset, rebaseset)):
348 rebaseset, rebaseset)):
349 raise error.Abort(
349 raise error.Abort(
350 _("can't remove original changesets with"
350 _("can't remove original changesets with"
351 " unrebased descendants"),
351 " unrebased descendants"),
352 hint=_('use --keep to keep original changesets'))
352 hint=_('use --keep to keep original changesets'))
353
353
354 result = buildstate(self.repo, destmap, self.collapsef)
354 result = buildstate(self.repo, destmap, self.collapsef)
355
355
356 if not result:
356 if not result:
357 # Empty state built, nothing to rebase
357 # Empty state built, nothing to rebase
358 self.ui.status(_('nothing to rebase\n'))
358 self.ui.status(_('nothing to rebase\n'))
359 return _nothingtorebase()
359 return _nothingtorebase()
360
360
361 for root in self.repo.set('roots(%ld)', rebaseset):
361 for root in self.repo.set('roots(%ld)', rebaseset):
362 if not self.keepf and not root.mutable():
362 if not self.keepf and not root.mutable():
363 raise error.Abort(_("can't rebase public changeset %s")
363 raise error.Abort(_("can't rebase public changeset %s")
364 % root,
364 % root,
365 hint=_("see 'hg help phases' for details"))
365 hint=_("see 'hg help phases' for details"))
366
366
367 (self.originalwd, self.destmap, self.state) = result
367 (self.originalwd, self.destmap, self.state) = result
368 if self.collapsef:
368 if self.collapsef:
369 dests = set(self.destmap.values())
369 dests = set(self.destmap.values())
370 if len(dests) != 1:
370 if len(dests) != 1:
371 raise error.Abort(
371 raise error.Abort(
372 _('--collapse does not work with multiple destinations'))
372 _('--collapse does not work with multiple destinations'))
373 destrev = next(iter(dests))
373 destrev = next(iter(dests))
374 destancestors = self.repo.changelog.ancestors([destrev],
374 destancestors = self.repo.changelog.ancestors([destrev],
375 inclusive=True)
375 inclusive=True)
376 self.external = externalparent(self.repo, self.state, destancestors)
376 self.external = externalparent(self.repo, self.state, destancestors)
377
377
378 for destrev in sorted(set(destmap.values())):
378 for destrev in sorted(set(destmap.values())):
379 dest = self.repo[destrev]
379 dest = self.repo[destrev]
380 if dest.closesbranch() and not self.keepbranchesf:
380 if dest.closesbranch() and not self.keepbranchesf:
381 self.ui.status(_('reopening closed branch head %s\n') % dest)
381 self.ui.status(_('reopening closed branch head %s\n') % dest)
382
382
383 self.prepared = True
383 self.prepared = True
384
384
385 def _performrebase(self, tr):
385 def _performrebase(self, tr):
386 repo, ui = self.repo, self.ui
386 repo, ui = self.repo, self.ui
387 # Assign a working copy object.
387 # Assign a working copy object.
388 if self.inmemory:
388 if self.inmemory:
389 from mercurial.context import overlayworkingctx
389 from mercurial.context import overlayworkingctx
390 self.wctx = overlayworkingctx(self.repo)
390 self.wctx = overlayworkingctx(self.repo)
391 else:
391 else:
392 self.wctx = self.repo[None]
392 self.wctx = self.repo[None]
393 if self.keepbranchesf:
393 if self.keepbranchesf:
394 # insert _savebranch at the start of extrafns so if
394 # insert _savebranch at the start of extrafns so if
395 # there's a user-provided extrafn it can clobber branch if
395 # there's a user-provided extrafn it can clobber branch if
396 # desired
396 # desired
397 self.extrafns.insert(0, _savebranch)
397 self.extrafns.insert(0, _savebranch)
398 if self.collapsef:
398 if self.collapsef:
399 branches = set()
399 branches = set()
400 for rev in self.state:
400 for rev in self.state:
401 branches.add(repo[rev].branch())
401 branches.add(repo[rev].branch())
402 if len(branches) > 1:
402 if len(branches) > 1:
403 raise error.Abort(_('cannot collapse multiple named '
403 raise error.Abort(_('cannot collapse multiple named '
404 'branches'))
404 'branches'))
405
405
406 # Calculate self.obsoletenotrebased
406 # Calculate self.obsoletenotrebased
407 obsrevs = _filterobsoleterevs(self.repo, self.state)
407 obsrevs = _filterobsoleterevs(self.repo, self.state)
408 self._handleskippingobsolete(obsrevs, self.destmap)
408 self._handleskippingobsolete(obsrevs, self.destmap)
409
409
410 # Keep track of the active bookmarks in order to reset them later
410 # Keep track of the active bookmarks in order to reset them later
411 self.activebookmark = self.activebookmark or repo._activebookmark
411 self.activebookmark = self.activebookmark or repo._activebookmark
412 if self.activebookmark:
412 if self.activebookmark:
413 bookmarks.deactivate(repo)
413 bookmarks.deactivate(repo)
414
414
415 # Store the state before we begin so users can run 'hg rebase --abort'
415 # Store the state before we begin so users can run 'hg rebase --abort'
416 # if we fail before the transaction closes.
416 # if we fail before the transaction closes.
417 self.storestatus()
417 self.storestatus()
418
418
419 cands = [k for k, v in self.state.iteritems() if v == revtodo]
419 cands = [k for k, v in self.state.iteritems() if v == revtodo]
420 total = len(cands)
420 total = len(cands)
421 pos = 0
421 pos = 0
422 for subset in sortsource(self.destmap):
422 for subset in sortsource(self.destmap):
423 pos = self._performrebasesubset(tr, subset, pos, total)
423 pos = self._performrebasesubset(tr, subset, pos, total)
424 ui.progress(_('rebasing'), None)
424 ui.progress(_('rebasing'), None)
425 ui.note(_('rebase merging completed\n'))
425 ui.note(_('rebase merging completed\n'))
426
426
427 def _performrebasesubset(self, tr, subset, pos, total):
427 def _performrebasesubset(self, tr, subset, pos, total):
428 repo, ui, opts = self.repo, self.ui, self.opts
428 repo, ui, opts = self.repo, self.ui, self.opts
429 sortedrevs = repo.revs('sort(%ld, -topo)', subset)
429 sortedrevs = repo.revs('sort(%ld, -topo)', subset)
430 allowdivergence = self.ui.configbool(
430 allowdivergence = self.ui.configbool(
431 'experimental', 'evolution.allowdivergence')
431 'experimental', 'evolution.allowdivergence')
432 if not allowdivergence:
432 if not allowdivergence:
433 sortedrevs -= repo.revs(
433 sortedrevs -= repo.revs(
434 'descendants(%ld) and not %ld',
434 'descendants(%ld) and not %ld',
435 self.obsoletewithoutsuccessorindestination,
435 self.obsoletewithoutsuccessorindestination,
436 self.obsoletewithoutsuccessorindestination,
436 self.obsoletewithoutsuccessorindestination,
437 )
437 )
438 for rev in sortedrevs:
438 for rev in sortedrevs:
439 dest = self.destmap[rev]
439 dest = self.destmap[rev]
440 ctx = repo[rev]
440 ctx = repo[rev]
441 desc = _ctxdesc(ctx)
441 desc = _ctxdesc(ctx)
442 if self.state[rev] == rev:
442 if self.state[rev] == rev:
443 ui.status(_('already rebased %s\n') % desc)
443 ui.status(_('already rebased %s\n') % desc)
444 elif (not allowdivergence
444 elif (not allowdivergence
445 and rev in self.obsoletewithoutsuccessorindestination):
445 and rev in self.obsoletewithoutsuccessorindestination):
446 msg = _('note: not rebasing %s and its descendants as '
446 msg = _('note: not rebasing %s and its descendants as '
447 'this would cause divergence\n') % desc
447 'this would cause divergence\n') % desc
448 repo.ui.status(msg)
448 repo.ui.status(msg)
449 self.skipped.add(rev)
449 self.skipped.add(rev)
450 elif rev in self.obsoletenotrebased:
450 elif rev in self.obsoletenotrebased:
451 succ = self.obsoletenotrebased[rev]
451 succ = self.obsoletenotrebased[rev]
452 if succ is None:
452 if succ is None:
453 msg = _('note: not rebasing %s, it has no '
453 msg = _('note: not rebasing %s, it has no '
454 'successor\n') % desc
454 'successor\n') % desc
455 else:
455 else:
456 succdesc = _ctxdesc(repo[succ])
456 succdesc = _ctxdesc(repo[succ])
457 msg = (_('note: not rebasing %s, already in '
457 msg = (_('note: not rebasing %s, already in '
458 'destination as %s\n') % (desc, succdesc))
458 'destination as %s\n') % (desc, succdesc))
459 repo.ui.status(msg)
459 repo.ui.status(msg)
460 # Make clearrebased aware state[rev] is not a true successor
460 # Make clearrebased aware state[rev] is not a true successor
461 self.skipped.add(rev)
461 self.skipped.add(rev)
462 # Record rev as moved to its desired destination in self.state.
462 # Record rev as moved to its desired destination in self.state.
463 # This helps bookmark and working parent movement.
463 # This helps bookmark and working parent movement.
464 dest = max(adjustdest(repo, rev, self.destmap, self.state,
464 dest = max(adjustdest(repo, rev, self.destmap, self.state,
465 self.skipped))
465 self.skipped))
466 self.state[rev] = dest
466 self.state[rev] = dest
467 elif self.state[rev] == revtodo:
467 elif self.state[rev] == revtodo:
468 pos += 1
468 pos += 1
469 ui.status(_('rebasing %s\n') % desc)
469 ui.status(_('rebasing %s\n') % desc)
470 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
470 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
471 _('changesets'), total)
471 _('changesets'), total)
472 p1, p2, base = defineparents(repo, rev, self.destmap,
472 p1, p2, base = defineparents(repo, rev, self.destmap,
473 self.state, self.skipped,
473 self.state, self.skipped,
474 self.obsoletenotrebased)
474 self.obsoletenotrebased)
475 self.storestatus(tr=tr)
475 self.storestatus(tr=tr)
476 storecollapsemsg(repo, self.collapsemsg)
476 storecollapsemsg(repo, self.collapsemsg)
477 if len(repo[None].parents()) == 2:
477 if len(repo[None].parents()) == 2:
478 repo.ui.debug('resuming interrupted rebase\n')
478 repo.ui.debug('resuming interrupted rebase\n')
479 else:
479 else:
480 try:
480 try:
481 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
481 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
482 'rebase')
482 'rebase')
483 stats = rebasenode(repo, rev, p1, base, self.state,
483 stats = rebasenode(repo, rev, p1, base, self.state,
484 self.collapsef, dest)
484 self.collapsef, dest)
485 if stats and stats[3] > 0:
485 if stats and stats[3] > 0:
486 raise error.InterventionRequired(
486 raise error.InterventionRequired(
487 _('unresolved conflicts (see hg '
487 _('unresolved conflicts (see hg '
488 'resolve, then hg rebase --continue)'))
488 'resolve, then hg rebase --continue)'))
489 finally:
489 finally:
490 ui.setconfig('ui', 'forcemerge', '', 'rebase')
490 ui.setconfig('ui', 'forcemerge', '', 'rebase')
491 if not self.collapsef:
491 if not self.collapsef:
492 merging = p2 != nullrev
492 merging = p2 != nullrev
493 editform = cmdutil.mergeeditform(merging, 'rebase')
493 editform = cmdutil.mergeeditform(merging, 'rebase')
494 editor = cmdutil.getcommiteditor(editform=editform, **opts)
494 editor = cmdutil.getcommiteditor(editform=editform, **opts)
495 newnode = concludenode(repo, rev, p1, p2,
495 newnode = concludenode(repo, rev, p1, p2,
496 extrafn=_makeextrafn(self.extrafns),
496 extrafn=_makeextrafn(self.extrafns),
497 editor=editor,
497 editor=editor,
498 keepbranches=self.keepbranchesf,
498 keepbranches=self.keepbranchesf,
499 date=self.date)
499 date=self.date)
500 if newnode is None:
500 if newnode is None:
501 # If it ended up being a no-op commit, then the normal
501 # If it ended up being a no-op commit, then the normal
502 # merge state clean-up path doesn't happen, so do it
502 # merge state clean-up path doesn't happen, so do it
503 # here. Fix issue5494
503 # here. Fix issue5494
504 mergemod.mergestate.clean(repo)
504 mergemod.mergestate.clean(repo)
505 else:
505 else:
506 # Skip commit if we are collapsing
506 # Skip commit if we are collapsing
507 repo.setparents(repo[p1].node())
507 repo.setparents(repo[p1].node())
508 newnode = None
508 newnode = None
509 # Update the state
509 # Update the state
510 if newnode is not None:
510 if newnode is not None:
511 self.state[rev] = repo[newnode].rev()
511 self.state[rev] = repo[newnode].rev()
512 ui.debug('rebased as %s\n' % short(newnode))
512 ui.debug('rebased as %s\n' % short(newnode))
513 else:
513 else:
514 if not self.collapsef:
514 if not self.collapsef:
515 ui.warn(_('note: rebase of %d:%s created no changes '
515 ui.warn(_('note: rebase of %d:%s created no changes '
516 'to commit\n') % (rev, ctx))
516 'to commit\n') % (rev, ctx))
517 self.skipped.add(rev)
517 self.skipped.add(rev)
518 self.state[rev] = p1
518 self.state[rev] = p1
519 ui.debug('next revision set to %s\n' % p1)
519 ui.debug('next revision set to %s\n' % p1)
520 else:
520 else:
521 ui.status(_('already rebased %s as %s\n') %
521 ui.status(_('already rebased %s as %s\n') %
522 (desc, repo[self.state[rev]]))
522 (desc, repo[self.state[rev]]))
523 return pos
523 return pos
524
524
525 def _finishrebase(self):
525 def _finishrebase(self):
526 repo, ui, opts = self.repo, self.ui, self.opts
526 repo, ui, opts = self.repo, self.ui, self.opts
527 fm = ui.formatter('rebase', opts)
527 fm = ui.formatter('rebase', opts)
528 fm.startitem()
528 fm.startitem()
529 if self.collapsef and not self.keepopen:
529 if self.collapsef and not self.keepopen:
530 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
530 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
531 self.state, self.skipped,
531 self.state, self.skipped,
532 self.obsoletenotrebased)
532 self.obsoletenotrebased)
533 editopt = opts.get('edit')
533 editopt = opts.get('edit')
534 editform = 'rebase.collapse'
534 editform = 'rebase.collapse'
535 if self.collapsemsg:
535 if self.collapsemsg:
536 commitmsg = self.collapsemsg
536 commitmsg = self.collapsemsg
537 else:
537 else:
538 commitmsg = 'Collapsed revision'
538 commitmsg = 'Collapsed revision'
539 for rebased in sorted(self.state):
539 for rebased in sorted(self.state):
540 if rebased not in self.skipped:
540 if rebased not in self.skipped:
541 commitmsg += '\n* %s' % repo[rebased].description()
541 commitmsg += '\n* %s' % repo[rebased].description()
542 editopt = True
542 editopt = True
543 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
543 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
544 revtoreuse = max(self.state)
544 revtoreuse = max(self.state)
545
545
546 dsguard = None
546 dsguard = None
547 if ui.configbool('rebase', 'singletransaction'):
547 if ui.configbool('rebase', 'singletransaction'):
548 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
548 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
549 with util.acceptintervention(dsguard):
549 with util.acceptintervention(dsguard):
550 newnode = concludenode(repo, revtoreuse, p1, self.external,
550 newnode = concludenode(repo, revtoreuse, p1, self.external,
551 commitmsg=commitmsg,
551 commitmsg=commitmsg,
552 extrafn=_makeextrafn(self.extrafns),
552 extrafn=_makeextrafn(self.extrafns),
553 editor=editor,
553 editor=editor,
554 keepbranches=self.keepbranchesf,
554 keepbranches=self.keepbranchesf,
555 date=self.date)
555 date=self.date)
556 if newnode is not None:
556 if newnode is not None:
557 newrev = repo[newnode].rev()
557 newrev = repo[newnode].rev()
558 for oldrev in self.state.iterkeys():
558 for oldrev in self.state.iterkeys():
559 self.state[oldrev] = newrev
559 self.state[oldrev] = newrev
560
560
561 if 'qtip' in repo.tags():
561 if 'qtip' in repo.tags():
562 updatemq(repo, self.state, self.skipped, **opts)
562 updatemq(repo, self.state, self.skipped, **opts)
563
563
564 # restore original working directory
564 # restore original working directory
565 # (we do this before stripping)
565 # (we do this before stripping)
566 newwd = self.state.get(self.originalwd, self.originalwd)
566 newwd = self.state.get(self.originalwd, self.originalwd)
567 if newwd < 0:
567 if newwd < 0:
568 # original directory is a parent of rebase set root or ignored
568 # original directory is a parent of rebase set root or ignored
569 newwd = self.originalwd
569 newwd = self.originalwd
570 if newwd not in [c.rev() for c in repo[None].parents()]:
570 if newwd not in [c.rev() for c in repo[None].parents()]:
571 ui.note(_("update back to initial working directory parent\n"))
571 ui.note(_("update back to initial working directory parent\n"))
572 hg.updaterepo(repo, newwd, False)
572 hg.updaterepo(repo, newwd, False)
573
573
574 collapsedas = None
574 collapsedas = None
575 if not self.keepf:
575 if not self.keepf:
576 if self.collapsef:
576 if self.collapsef:
577 collapsedas = newnode
577 collapsedas = newnode
578 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
578 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
579 collapsedas, self.keepf, fm=fm)
579 collapsedas, self.keepf, fm=fm)
580
580
581 clearstatus(repo)
581 clearstatus(repo)
582 clearcollapsemsg(repo)
582 clearcollapsemsg(repo)
583
583
584 ui.note(_("rebase completed\n"))
584 ui.note(_("rebase completed\n"))
585 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
585 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
586 if self.skipped:
586 if self.skipped:
587 skippedlen = len(self.skipped)
587 skippedlen = len(self.skipped)
588 ui.note(_("%d revisions have been skipped\n") % skippedlen)
588 ui.note(_("%d revisions have been skipped\n") % skippedlen)
589 fm.end()
589 fm.end()
590
590
591 if (self.activebookmark and self.activebookmark in repo._bookmarks and
591 if (self.activebookmark and self.activebookmark in repo._bookmarks and
592 repo['.'].node() == repo._bookmarks[self.activebookmark]):
592 repo['.'].node() == repo._bookmarks[self.activebookmark]):
593 bookmarks.activate(repo, self.activebookmark)
593 bookmarks.activate(repo, self.activebookmark)
594
594
595 @command('rebase',
595 @command('rebase',
596 [('s', 'source', '',
596 [('s', 'source', '',
597 _('rebase the specified changeset and descendants'), _('REV')),
597 _('rebase the specified changeset and descendants'), _('REV')),
598 ('b', 'base', '',
598 ('b', 'base', '',
599 _('rebase everything from branching point of specified changeset'),
599 _('rebase everything from branching point of specified changeset'),
600 _('REV')),
600 _('REV')),
601 ('r', 'rev', [],
601 ('r', 'rev', [],
602 _('rebase these revisions'),
602 _('rebase these revisions'),
603 _('REV')),
603 _('REV')),
604 ('d', 'dest', '',
604 ('d', 'dest', '',
605 _('rebase onto the specified changeset'), _('REV')),
605 _('rebase onto the specified changeset'), _('REV')),
606 ('', 'collapse', False, _('collapse the rebased changesets')),
606 ('', 'collapse', False, _('collapse the rebased changesets')),
607 ('m', 'message', '',
607 ('m', 'message', '',
608 _('use text as collapse commit message'), _('TEXT')),
608 _('use text as collapse commit message'), _('TEXT')),
609 ('e', 'edit', False, _('invoke editor on commit messages')),
609 ('e', 'edit', False, _('invoke editor on commit messages')),
610 ('l', 'logfile', '',
610 ('l', 'logfile', '',
611 _('read collapse commit message from file'), _('FILE')),
611 _('read collapse commit message from file'), _('FILE')),
612 ('k', 'keep', False, _('keep original changesets')),
612 ('k', 'keep', False, _('keep original changesets')),
613 ('', 'keepbranches', False, _('keep original branch names')),
613 ('', 'keepbranches', False, _('keep original branch names')),
614 ('D', 'detach', False, _('(DEPRECATED)')),
614 ('D', 'detach', False, _('(DEPRECATED)')),
615 ('i', 'interactive', False, _('(DEPRECATED)')),
615 ('i', 'interactive', False, _('(DEPRECATED)')),
616 ('t', 'tool', '', _('specify merge tool')),
616 ('t', 'tool', '', _('specify merge tool')),
617 ('c', 'continue', False, _('continue an interrupted rebase')),
617 ('c', 'continue', False, _('continue an interrupted rebase')),
618 ('', 'inmemory', False, _('run rebase in-memory (EXPERIMENTAL)')),
618 ('', 'inmemory', False, _('run rebase in-memory (EXPERIMENTAL)')),
619 ('a', 'abort', False, _('abort an interrupted rebase'))] +
619 ('a', 'abort', False, _('abort an interrupted rebase'))] +
620 cmdutil.formatteropts,
620 cmdutil.formatteropts,
621 _('[-s REV | -b REV] [-d REV] [OPTION]'))
621 _('[-s REV | -b REV] [-d REV] [OPTION]'))
622 def rebase(ui, repo, **opts):
622 def rebase(ui, repo, **opts):
623 """move changeset (and descendants) to a different branch
623 """move changeset (and descendants) to a different branch
624
624
625 Rebase uses repeated merging to graft changesets from one part of
625 Rebase uses repeated merging to graft changesets from one part of
626 history (the source) onto another (the destination). This can be
626 history (the source) onto another (the destination). This can be
627 useful for linearizing *local* changes relative to a master
627 useful for linearizing *local* changes relative to a master
628 development tree.
628 development tree.
629
629
630 Published commits cannot be rebased (see :hg:`help phases`).
630 Published commits cannot be rebased (see :hg:`help phases`).
631 To copy commits, see :hg:`help graft`.
631 To copy commits, see :hg:`help graft`.
632
632
633 If you don't specify a destination changeset (``-d/--dest``), rebase
633 If you don't specify a destination changeset (``-d/--dest``), rebase
634 will use the same logic as :hg:`merge` to pick a destination. if
634 will use the same logic as :hg:`merge` to pick a destination. if
635 the current branch contains exactly one other head, the other head
635 the current branch contains exactly one other head, the other head
636 is merged with by default. Otherwise, an explicit revision with
636 is merged with by default. Otherwise, an explicit revision with
637 which to merge with must be provided. (destination changeset is not
637 which to merge with must be provided. (destination changeset is not
638 modified by rebasing, but new changesets are added as its
638 modified by rebasing, but new changesets are added as its
639 descendants.)
639 descendants.)
640
640
641 Here are the ways to select changesets:
641 Here are the ways to select changesets:
642
642
643 1. Explicitly select them using ``--rev``.
643 1. Explicitly select them using ``--rev``.
644
644
645 2. Use ``--source`` to select a root changeset and include all of its
645 2. Use ``--source`` to select a root changeset and include all of its
646 descendants.
646 descendants.
647
647
648 3. Use ``--base`` to select a changeset; rebase will find ancestors
648 3. Use ``--base`` to select a changeset; rebase will find ancestors
649 and their descendants which are not also ancestors of the destination.
649 and their descendants which are not also ancestors of the destination.
650
650
651 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
651 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
652 rebase will use ``--base .`` as above.
652 rebase will use ``--base .`` as above.
653
653
654 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
654 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
655 can be used in ``--dest``. Destination would be calculated per source
655 can be used in ``--dest``. Destination would be calculated per source
656 revision with ``SRC`` substituted by that single source revision and
656 revision with ``SRC`` substituted by that single source revision and
657 ``ALLSRC`` substituted by all source revisions.
657 ``ALLSRC`` substituted by all source revisions.
658
658
659 Rebase will destroy original changesets unless you use ``--keep``.
659 Rebase will destroy original changesets unless you use ``--keep``.
660 It will also move your bookmarks (even if you do).
660 It will also move your bookmarks (even if you do).
661
661
662 Some changesets may be dropped if they do not contribute changes
662 Some changesets may be dropped if they do not contribute changes
663 (e.g. merges from the destination branch).
663 (e.g. merges from the destination branch).
664
664
665 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
665 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
666 a named branch with two heads. You will need to explicitly specify source
666 a named branch with two heads. You will need to explicitly specify source
667 and/or destination.
667 and/or destination.
668
668
669 If you need to use a tool to automate merge/conflict decisions, you
669 If you need to use a tool to automate merge/conflict decisions, you
670 can specify one with ``--tool``, see :hg:`help merge-tools`.
670 can specify one with ``--tool``, see :hg:`help merge-tools`.
671 As a caveat: the tool will not be used to mediate when a file was
671 As a caveat: the tool will not be used to mediate when a file was
672 deleted, there is no hook presently available for this.
672 deleted, there is no hook presently available for this.
673
673
674 If a rebase is interrupted to manually resolve a conflict, it can be
674 If a rebase is interrupted to manually resolve a conflict, it can be
675 continued with --continue/-c or aborted with --abort/-a.
675 continued with --continue/-c or aborted with --abort/-a.
676
676
677 .. container:: verbose
677 .. container:: verbose
678
678
679 Examples:
679 Examples:
680
680
681 - move "local changes" (current commit back to branching point)
681 - move "local changes" (current commit back to branching point)
682 to the current branch tip after a pull::
682 to the current branch tip after a pull::
683
683
684 hg rebase
684 hg rebase
685
685
686 - move a single changeset to the stable branch::
686 - move a single changeset to the stable branch::
687
687
688 hg rebase -r 5f493448 -d stable
688 hg rebase -r 5f493448 -d stable
689
689
690 - splice a commit and all its descendants onto another part of history::
690 - splice a commit and all its descendants onto another part of history::
691
691
692 hg rebase --source c0c3 --dest 4cf9
692 hg rebase --source c0c3 --dest 4cf9
693
693
694 - rebase everything on a branch marked by a bookmark onto the
694 - rebase everything on a branch marked by a bookmark onto the
695 default branch::
695 default branch::
696
696
697 hg rebase --base myfeature --dest default
697 hg rebase --base myfeature --dest default
698
698
699 - collapse a sequence of changes into a single commit::
699 - collapse a sequence of changes into a single commit::
700
700
701 hg rebase --collapse -r 1520:1525 -d .
701 hg rebase --collapse -r 1520:1525 -d .
702
702
703 - move a named branch while preserving its name::
703 - move a named branch while preserving its name::
704
704
705 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
705 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
706
706
707 - stabilize orphaned changesets so history looks linear::
707 - stabilize orphaned changesets so history looks linear::
708
708
709 hg rebase -r 'orphan()-obsolete()'\
709 hg rebase -r 'orphan()-obsolete()'\
710 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
710 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
711 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
711 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
712
712
713 Configuration Options:
713 Configuration Options:
714
714
715 You can make rebase require a destination if you set the following config
715 You can make rebase require a destination if you set the following config
716 option::
716 option::
717
717
718 [commands]
718 [commands]
719 rebase.requiredest = True
719 rebase.requiredest = True
720
720
721 By default, rebase will close the transaction after each commit. For
721 By default, rebase will close the transaction after each commit. For
722 performance purposes, you can configure rebase to use a single transaction
722 performance purposes, you can configure rebase to use a single transaction
723 across the entire rebase. WARNING: This setting introduces a significant
723 across the entire rebase. WARNING: This setting introduces a significant
724 risk of losing the work you've done in a rebase if the rebase aborts
724 risk of losing the work you've done in a rebase if the rebase aborts
725 unexpectedly::
725 unexpectedly::
726
726
727 [rebase]
727 [rebase]
728 singletransaction = True
728 singletransaction = True
729
729
730 Return Values:
730 Return Values:
731
731
732 Returns 0 on success, 1 if nothing to rebase or there are
732 Returns 0 on success, 1 if nothing to rebase or there are
733 unresolved conflicts.
733 unresolved conflicts.
734
734
735 """
735 """
736 opts = pycompat.byteskwargs(opts)
736 opts = pycompat.byteskwargs(opts)
737 if 'inmemory' not in opts:
737 if 'inmemory' not in opts:
738 opts['inmemory'] = False
738 opts['inmemory'] = False
739 rbsrt = rebaseruntime(repo, ui, opts)
739 rbsrt = rebaseruntime(repo, ui, opts)
740
740
741 with repo.wlock(), repo.lock():
741 with repo.wlock(), repo.lock():
742 # Validate input and define rebasing points
742 # Validate input and define rebasing points
743 destf = opts.get('dest', None)
743 destf = opts.get('dest', None)
744 srcf = opts.get('source', None)
744 srcf = opts.get('source', None)
745 basef = opts.get('base', None)
745 basef = opts.get('base', None)
746 revf = opts.get('rev', [])
746 revf = opts.get('rev', [])
747 # search default destination in this space
747 # search default destination in this space
748 # used in the 'hg pull --rebase' case, see issue 5214.
748 # used in the 'hg pull --rebase' case, see issue 5214.
749 destspace = opts.get('_destspace')
749 destspace = opts.get('_destspace')
750 contf = opts.get('continue')
750 contf = opts.get('continue')
751 abortf = opts.get('abort')
751 abortf = opts.get('abort')
752 if opts.get('interactive'):
752 if opts.get('interactive'):
753 try:
753 try:
754 if extensions.find('histedit'):
754 if extensions.find('histedit'):
755 enablehistedit = ''
755 enablehistedit = ''
756 except KeyError:
756 except KeyError:
757 enablehistedit = " --config extensions.histedit="
757 enablehistedit = " --config extensions.histedit="
758 help = "hg%s help -e histedit" % enablehistedit
758 help = "hg%s help -e histedit" % enablehistedit
759 msg = _("interactive history editing is supported by the "
759 msg = _("interactive history editing is supported by the "
760 "'histedit' extension (see \"%s\")") % help
760 "'histedit' extension (see \"%s\")") % help
761 raise error.Abort(msg)
761 raise error.Abort(msg)
762
762
763 if rbsrt.collapsemsg and not rbsrt.collapsef:
763 if rbsrt.collapsemsg and not rbsrt.collapsef:
764 raise error.Abort(
764 raise error.Abort(
765 _('message can only be specified with collapse'))
765 _('message can only be specified with collapse'))
766
766
767 if contf or abortf:
767 if contf or abortf:
768 if contf and abortf:
768 if contf and abortf:
769 raise error.Abort(_('cannot use both abort and continue'))
769 raise error.Abort(_('cannot use both abort and continue'))
770 if rbsrt.collapsef:
770 if rbsrt.collapsef:
771 raise error.Abort(
771 raise error.Abort(
772 _('cannot use collapse with continue or abort'))
772 _('cannot use collapse with continue or abort'))
773 if srcf or basef or destf:
773 if srcf or basef or destf:
774 raise error.Abort(
774 raise error.Abort(
775 _('abort and continue do not allow specifying revisions'))
775 _('abort and continue do not allow specifying revisions'))
776 if abortf and opts.get('tool', False):
776 if abortf and opts.get('tool', False):
777 ui.warn(_('tool option will be ignored\n'))
777 ui.warn(_('tool option will be ignored\n'))
778 if contf:
778 if contf:
779 ms = mergemod.mergestate.read(repo)
779 ms = mergemod.mergestate.read(repo)
780 mergeutil.checkunresolved(ms)
780 mergeutil.checkunresolved(ms)
781
781
782 retcode = rbsrt._prepareabortorcontinue(abortf)
782 retcode = rbsrt._prepareabortorcontinue(abortf)
783 if retcode is not None:
783 if retcode is not None:
784 return retcode
784 return retcode
785 else:
785 else:
786 destmap = _definedestmap(ui, repo, destf, srcf, basef, revf,
786 destmap = _definedestmap(ui, repo, destf, srcf, basef, revf,
787 destspace=destspace)
787 destspace=destspace,
788 inmemory=opts['inmemory'])
788 retcode = rbsrt._preparenewrebase(destmap)
789 retcode = rbsrt._preparenewrebase(destmap)
789 if retcode is not None:
790 if retcode is not None:
790 return retcode
791 return retcode
791
792
792 tr = None
793 tr = None
793 dsguard = None
794 dsguard = None
794
795
795 singletr = ui.configbool('rebase', 'singletransaction')
796 singletr = ui.configbool('rebase', 'singletransaction')
796 if singletr:
797 if singletr:
797 tr = repo.transaction('rebase')
798 tr = repo.transaction('rebase')
798 with util.acceptintervention(tr):
799 with util.acceptintervention(tr):
799 if singletr:
800 if singletr:
800 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
801 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
801 with util.acceptintervention(dsguard):
802 with util.acceptintervention(dsguard):
802 rbsrt._performrebase(tr)
803 rbsrt._performrebase(tr)
803
804
804 rbsrt._finishrebase()
805 rbsrt._finishrebase()
805
806
806 def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None,
807 def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None,
807 destspace=None):
808 destspace=None, inmemory=False):
808 """use revisions argument to define destmap {srcrev: destrev}"""
809 """use revisions argument to define destmap {srcrev: destrev}"""
809 if revf is None:
810 if revf is None:
810 revf = []
811 revf = []
811
812
812 # destspace is here to work around issues with `hg pull --rebase` see
813 # destspace is here to work around issues with `hg pull --rebase` see
813 # issue5214 for details
814 # issue5214 for details
814 if srcf and basef:
815 if srcf and basef:
815 raise error.Abort(_('cannot specify both a source and a base'))
816 raise error.Abort(_('cannot specify both a source and a base'))
816 if revf and basef:
817 if revf and basef:
817 raise error.Abort(_('cannot specify both a revision and a base'))
818 raise error.Abort(_('cannot specify both a revision and a base'))
818 if revf and srcf:
819 if revf and srcf:
819 raise error.Abort(_('cannot specify both a revision and a source'))
820 raise error.Abort(_('cannot specify both a revision and a source'))
820
821
822 if not inmemory:
821 cmdutil.checkunfinished(repo)
823 cmdutil.checkunfinished(repo)
822 cmdutil.bailifchanged(repo)
824 cmdutil.bailifchanged(repo)
823
825
824 if ui.configbool('commands', 'rebase.requiredest') and not destf:
826 if ui.configbool('commands', 'rebase.requiredest') and not destf:
825 raise error.Abort(_('you must specify a destination'),
827 raise error.Abort(_('you must specify a destination'),
826 hint=_('use: hg rebase -d REV'))
828 hint=_('use: hg rebase -d REV'))
827
829
828 dest = None
830 dest = None
829
831
830 if revf:
832 if revf:
831 rebaseset = scmutil.revrange(repo, revf)
833 rebaseset = scmutil.revrange(repo, revf)
832 if not rebaseset:
834 if not rebaseset:
833 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
835 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
834 return None
836 return None
835 elif srcf:
837 elif srcf:
836 src = scmutil.revrange(repo, [srcf])
838 src = scmutil.revrange(repo, [srcf])
837 if not src:
839 if not src:
838 ui.status(_('empty "source" revision set - nothing to rebase\n'))
840 ui.status(_('empty "source" revision set - nothing to rebase\n'))
839 return None
841 return None
840 rebaseset = repo.revs('(%ld)::', src)
842 rebaseset = repo.revs('(%ld)::', src)
841 assert rebaseset
843 assert rebaseset
842 else:
844 else:
843 base = scmutil.revrange(repo, [basef or '.'])
845 base = scmutil.revrange(repo, [basef or '.'])
844 if not base:
846 if not base:
845 ui.status(_('empty "base" revision set - '
847 ui.status(_('empty "base" revision set - '
846 "can't compute rebase set\n"))
848 "can't compute rebase set\n"))
847 return None
849 return None
848 if destf:
850 if destf:
849 # --base does not support multiple destinations
851 # --base does not support multiple destinations
850 dest = scmutil.revsingle(repo, destf)
852 dest = scmutil.revsingle(repo, destf)
851 else:
853 else:
852 dest = repo[_destrebase(repo, base, destspace=destspace)]
854 dest = repo[_destrebase(repo, base, destspace=destspace)]
853 destf = str(dest)
855 destf = str(dest)
854
856
855 roots = [] # selected children of branching points
857 roots = [] # selected children of branching points
856 bpbase = {} # {branchingpoint: [origbase]}
858 bpbase = {} # {branchingpoint: [origbase]}
857 for b in base: # group bases by branching points
859 for b in base: # group bases by branching points
858 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
860 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
859 bpbase[bp] = bpbase.get(bp, []) + [b]
861 bpbase[bp] = bpbase.get(bp, []) + [b]
860 if None in bpbase:
862 if None in bpbase:
861 # emulate the old behavior, showing "nothing to rebase" (a better
863 # emulate the old behavior, showing "nothing to rebase" (a better
862 # behavior may be abort with "cannot find branching point" error)
864 # behavior may be abort with "cannot find branching point" error)
863 bpbase.clear()
865 bpbase.clear()
864 for bp, bs in bpbase.iteritems(): # calculate roots
866 for bp, bs in bpbase.iteritems(): # calculate roots
865 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
867 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
866
868
867 rebaseset = repo.revs('%ld::', roots)
869 rebaseset = repo.revs('%ld::', roots)
868
870
869 if not rebaseset:
871 if not rebaseset:
870 # transform to list because smartsets are not comparable to
872 # transform to list because smartsets are not comparable to
871 # lists. This should be improved to honor laziness of
873 # lists. This should be improved to honor laziness of
872 # smartset.
874 # smartset.
873 if list(base) == [dest.rev()]:
875 if list(base) == [dest.rev()]:
874 if basef:
876 if basef:
875 ui.status(_('nothing to rebase - %s is both "base"'
877 ui.status(_('nothing to rebase - %s is both "base"'
876 ' and destination\n') % dest)
878 ' and destination\n') % dest)
877 else:
879 else:
878 ui.status(_('nothing to rebase - working directory '
880 ui.status(_('nothing to rebase - working directory '
879 'parent is also destination\n'))
881 'parent is also destination\n'))
880 elif not repo.revs('%ld - ::%d', base, dest):
882 elif not repo.revs('%ld - ::%d', base, dest):
881 if basef:
883 if basef:
882 ui.status(_('nothing to rebase - "base" %s is '
884 ui.status(_('nothing to rebase - "base" %s is '
883 'already an ancestor of destination '
885 'already an ancestor of destination '
884 '%s\n') %
886 '%s\n') %
885 ('+'.join(str(repo[r]) for r in base),
887 ('+'.join(str(repo[r]) for r in base),
886 dest))
888 dest))
887 else:
889 else:
888 ui.status(_('nothing to rebase - working '
890 ui.status(_('nothing to rebase - working '
889 'directory parent is already an '
891 'directory parent is already an '
890 'ancestor of destination %s\n') % dest)
892 'ancestor of destination %s\n') % dest)
891 else: # can it happen?
893 else: # can it happen?
892 ui.status(_('nothing to rebase from %s to %s\n') %
894 ui.status(_('nothing to rebase from %s to %s\n') %
893 ('+'.join(str(repo[r]) for r in base), dest))
895 ('+'.join(str(repo[r]) for r in base), dest))
894 return None
896 return None
895
897
896 if not destf:
898 if not destf:
897 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
899 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
898 destf = str(dest)
900 destf = str(dest)
899
901
900 allsrc = revsetlang.formatspec('%ld', rebaseset)
902 allsrc = revsetlang.formatspec('%ld', rebaseset)
901 alias = {'ALLSRC': allsrc}
903 alias = {'ALLSRC': allsrc}
902
904
903 if dest is None:
905 if dest is None:
904 try:
906 try:
905 # fast path: try to resolve dest without SRC alias
907 # fast path: try to resolve dest without SRC alias
906 dest = scmutil.revsingle(repo, destf, localalias=alias)
908 dest = scmutil.revsingle(repo, destf, localalias=alias)
907 except error.RepoLookupError:
909 except error.RepoLookupError:
908 # multi-dest path: resolve dest for each SRC separately
910 # multi-dest path: resolve dest for each SRC separately
909 destmap = {}
911 destmap = {}
910 for r in rebaseset:
912 for r in rebaseset:
911 alias['SRC'] = revsetlang.formatspec('%d', r)
913 alias['SRC'] = revsetlang.formatspec('%d', r)
912 # use repo.anyrevs instead of scmutil.revsingle because we
914 # use repo.anyrevs instead of scmutil.revsingle because we
913 # don't want to abort if destset is empty.
915 # don't want to abort if destset is empty.
914 destset = repo.anyrevs([destf], user=True, localalias=alias)
916 destset = repo.anyrevs([destf], user=True, localalias=alias)
915 size = len(destset)
917 size = len(destset)
916 if size == 1:
918 if size == 1:
917 destmap[r] = destset.first()
919 destmap[r] = destset.first()
918 elif size == 0:
920 elif size == 0:
919 ui.note(_('skipping %s - empty destination\n') % repo[r])
921 ui.note(_('skipping %s - empty destination\n') % repo[r])
920 else:
922 else:
921 raise error.Abort(_('rebase destination for %s is not '
923 raise error.Abort(_('rebase destination for %s is not '
922 'unique') % repo[r])
924 'unique') % repo[r])
923
925
924 if dest is not None:
926 if dest is not None:
925 # single-dest case: assign dest to each rev in rebaseset
927 # single-dest case: assign dest to each rev in rebaseset
926 destrev = dest.rev()
928 destrev = dest.rev()
927 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
929 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
928
930
929 if not destmap:
931 if not destmap:
930 ui.status(_('nothing to rebase - empty destination\n'))
932 ui.status(_('nothing to rebase - empty destination\n'))
931 return None
933 return None
932
934
933 return destmap
935 return destmap
934
936
935 def externalparent(repo, state, destancestors):
937 def externalparent(repo, state, destancestors):
936 """Return the revision that should be used as the second parent
938 """Return the revision that should be used as the second parent
937 when the revisions in state is collapsed on top of destancestors.
939 when the revisions in state is collapsed on top of destancestors.
938 Abort if there is more than one parent.
940 Abort if there is more than one parent.
939 """
941 """
940 parents = set()
942 parents = set()
941 source = min(state)
943 source = min(state)
942 for rev in state:
944 for rev in state:
943 if rev == source:
945 if rev == source:
944 continue
946 continue
945 for p in repo[rev].parents():
947 for p in repo[rev].parents():
946 if (p.rev() not in state
948 if (p.rev() not in state
947 and p.rev() not in destancestors):
949 and p.rev() not in destancestors):
948 parents.add(p.rev())
950 parents.add(p.rev())
949 if not parents:
951 if not parents:
950 return nullrev
952 return nullrev
951 if len(parents) == 1:
953 if len(parents) == 1:
952 return parents.pop()
954 return parents.pop()
953 raise error.Abort(_('unable to collapse on top of %s, there is more '
955 raise error.Abort(_('unable to collapse on top of %s, there is more '
954 'than one external parent: %s') %
956 'than one external parent: %s') %
955 (max(destancestors),
957 (max(destancestors),
956 ', '.join(str(p) for p in sorted(parents))))
958 ', '.join(str(p) for p in sorted(parents))))
957
959
958 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
960 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
959 keepbranches=False, date=None):
961 keepbranches=False, date=None):
960 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
962 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
961 but also store useful information in extra.
963 but also store useful information in extra.
962 Return node of committed revision.'''
964 Return node of committed revision.'''
963 dsguard = util.nullcontextmanager()
965 dsguard = util.nullcontextmanager()
964 if not repo.ui.configbool('rebase', 'singletransaction'):
966 if not repo.ui.configbool('rebase', 'singletransaction'):
965 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
967 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
966 with dsguard:
968 with dsguard:
967 repo.setparents(repo[p1].node(), repo[p2].node())
969 repo.setparents(repo[p1].node(), repo[p2].node())
968 ctx = repo[rev]
970 ctx = repo[rev]
969 if commitmsg is None:
971 if commitmsg is None:
970 commitmsg = ctx.description()
972 commitmsg = ctx.description()
971 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
973 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
972 extra = {'rebase_source': ctx.hex()}
974 extra = {'rebase_source': ctx.hex()}
973 if extrafn:
975 if extrafn:
974 extrafn(ctx, extra)
976 extrafn(ctx, extra)
975
977
976 destphase = max(ctx.phase(), phases.draft)
978 destphase = max(ctx.phase(), phases.draft)
977 overrides = {('phases', 'new-commit'): destphase}
979 overrides = {('phases', 'new-commit'): destphase}
978 with repo.ui.configoverride(overrides, 'rebase'):
980 with repo.ui.configoverride(overrides, 'rebase'):
979 if keepbranch:
981 if keepbranch:
980 repo.ui.setconfig('ui', 'allowemptycommit', True)
982 repo.ui.setconfig('ui', 'allowemptycommit', True)
981 # Commit might fail if unresolved files exist
983 # Commit might fail if unresolved files exist
982 if date is None:
984 if date is None:
983 date = ctx.date()
985 date = ctx.date()
984 newnode = repo.commit(text=commitmsg, user=ctx.user(),
986 newnode = repo.commit(text=commitmsg, user=ctx.user(),
985 date=date, extra=extra, editor=editor)
987 date=date, extra=extra, editor=editor)
986
988
987 repo.dirstate.setbranch(repo[newnode].branch())
989 repo.dirstate.setbranch(repo[newnode].branch())
988 return newnode
990 return newnode
989
991
990 def rebasenode(repo, rev, p1, base, state, collapse, dest):
992 def rebasenode(repo, rev, p1, base, state, collapse, dest):
991 'Rebase a single revision rev on top of p1 using base as merge ancestor'
993 'Rebase a single revision rev on top of p1 using base as merge ancestor'
992 # Merge phase
994 # Merge phase
993 # Update to destination and merge it with local
995 # Update to destination and merge it with local
994 if repo['.'].rev() != p1:
996 if repo['.'].rev() != p1:
995 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
997 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
996 mergemod.update(repo, p1, False, True)
998 mergemod.update(repo, p1, False, True)
997 else:
999 else:
998 repo.ui.debug(" already in destination\n")
1000 repo.ui.debug(" already in destination\n")
999 repo.dirstate.write(repo.currenttransaction())
1001 repo.dirstate.write(repo.currenttransaction())
1000 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1002 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1001 if base is not None:
1003 if base is not None:
1002 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1004 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1003 # When collapsing in-place, the parent is the common ancestor, we
1005 # When collapsing in-place, the parent is the common ancestor, we
1004 # have to allow merging with it.
1006 # have to allow merging with it.
1005 wctx = repo[None]
1007 wctx = repo[None]
1006 stats = mergemod.update(repo, rev, True, True, base, collapse,
1008 stats = mergemod.update(repo, rev, True, True, base, collapse,
1007 labels=['dest', 'source'])
1009 labels=['dest', 'source'])
1008 if collapse:
1010 if collapse:
1009 copies.duplicatecopies(repo, wctx, rev, dest)
1011 copies.duplicatecopies(repo, wctx, rev, dest)
1010 else:
1012 else:
1011 # If we're not using --collapse, we need to
1013 # If we're not using --collapse, we need to
1012 # duplicate copies between the revision we're
1014 # duplicate copies between the revision we're
1013 # rebasing and its first parent, but *not*
1015 # rebasing and its first parent, but *not*
1014 # duplicate any copies that have already been
1016 # duplicate any copies that have already been
1015 # performed in the destination.
1017 # performed in the destination.
1016 p1rev = repo[rev].p1().rev()
1018 p1rev = repo[rev].p1().rev()
1017 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1019 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1018 return stats
1020 return stats
1019
1021
1020 def adjustdest(repo, rev, destmap, state, skipped):
1022 def adjustdest(repo, rev, destmap, state, skipped):
1021 """adjust rebase destination given the current rebase state
1023 """adjust rebase destination given the current rebase state
1022
1024
1023 rev is what is being rebased. Return a list of two revs, which are the
1025 rev is what is being rebased. Return a list of two revs, which are the
1024 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1026 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1025 nullrev, return dest without adjustment for it.
1027 nullrev, return dest without adjustment for it.
1026
1028
1027 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1029 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1028 to B1, and E's destination will be adjusted from F to B1.
1030 to B1, and E's destination will be adjusted from F to B1.
1029
1031
1030 B1 <- written during rebasing B
1032 B1 <- written during rebasing B
1031 |
1033 |
1032 F <- original destination of B, E
1034 F <- original destination of B, E
1033 |
1035 |
1034 | E <- rev, which is being rebased
1036 | E <- rev, which is being rebased
1035 | |
1037 | |
1036 | D <- prev, one parent of rev being checked
1038 | D <- prev, one parent of rev being checked
1037 | |
1039 | |
1038 | x <- skipped, ex. no successor or successor in (::dest)
1040 | x <- skipped, ex. no successor or successor in (::dest)
1039 | |
1041 | |
1040 | C <- rebased as C', different destination
1042 | C <- rebased as C', different destination
1041 | |
1043 | |
1042 | B <- rebased as B1 C'
1044 | B <- rebased as B1 C'
1043 |/ |
1045 |/ |
1044 A G <- destination of C, different
1046 A G <- destination of C, different
1045
1047
1046 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1048 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1047 first move C to C1, G to G1, and when it's checking H, the adjusted
1049 first move C to C1, G to G1, and when it's checking H, the adjusted
1048 destinations will be [C1, G1].
1050 destinations will be [C1, G1].
1049
1051
1050 H C1 G1
1052 H C1 G1
1051 /| | /
1053 /| | /
1052 F G |/
1054 F G |/
1053 K | | -> K
1055 K | | -> K
1054 | C D |
1056 | C D |
1055 | |/ |
1057 | |/ |
1056 | B | ...
1058 | B | ...
1057 |/ |/
1059 |/ |/
1058 A A
1060 A A
1059
1061
1060 Besides, adjust dest according to existing rebase information. For example,
1062 Besides, adjust dest according to existing rebase information. For example,
1061
1063
1062 B C D B needs to be rebased on top of C, C needs to be rebased on top
1064 B C D B needs to be rebased on top of C, C needs to be rebased on top
1063 \|/ of D. We will rebase C first.
1065 \|/ of D. We will rebase C first.
1064 A
1066 A
1065
1067
1066 C' After rebasing C, when considering B's destination, use C'
1068 C' After rebasing C, when considering B's destination, use C'
1067 | instead of the original C.
1069 | instead of the original C.
1068 B D
1070 B D
1069 \ /
1071 \ /
1070 A
1072 A
1071 """
1073 """
1072 # pick already rebased revs with same dest from state as interesting source
1074 # pick already rebased revs with same dest from state as interesting source
1073 dest = destmap[rev]
1075 dest = destmap[rev]
1074 source = [s for s, d in state.items()
1076 source = [s for s, d in state.items()
1075 if d > 0 and destmap[s] == dest and s not in skipped]
1077 if d > 0 and destmap[s] == dest and s not in skipped]
1076
1078
1077 result = []
1079 result = []
1078 for prev in repo.changelog.parentrevs(rev):
1080 for prev in repo.changelog.parentrevs(rev):
1079 adjusted = dest
1081 adjusted = dest
1080 if prev != nullrev:
1082 if prev != nullrev:
1081 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1083 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1082 if candidate is not None:
1084 if candidate is not None:
1083 adjusted = state[candidate]
1085 adjusted = state[candidate]
1084 if adjusted == dest and dest in state:
1086 if adjusted == dest and dest in state:
1085 adjusted = state[dest]
1087 adjusted = state[dest]
1086 if adjusted == revtodo:
1088 if adjusted == revtodo:
1087 # sortsource should produce an order that makes this impossible
1089 # sortsource should produce an order that makes this impossible
1088 raise error.ProgrammingError(
1090 raise error.ProgrammingError(
1089 'rev %d should be rebased already at this time' % dest)
1091 'rev %d should be rebased already at this time' % dest)
1090 result.append(adjusted)
1092 result.append(adjusted)
1091 return result
1093 return result
1092
1094
1093 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1095 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1094 """
1096 """
1095 Abort if rebase will create divergence or rebase is noop because of markers
1097 Abort if rebase will create divergence or rebase is noop because of markers
1096
1098
1097 `rebaseobsrevs`: set of obsolete revision in source
1099 `rebaseobsrevs`: set of obsolete revision in source
1098 `rebaseobsskipped`: set of revisions from source skipped because they have
1100 `rebaseobsskipped`: set of revisions from source skipped because they have
1099 successors in destination
1101 successors in destination
1100 """
1102 """
1101 # Obsolete node with successors not in dest leads to divergence
1103 # Obsolete node with successors not in dest leads to divergence
1102 divergenceok = ui.configbool('experimental',
1104 divergenceok = ui.configbool('experimental',
1103 'evolution.allowdivergence')
1105 'evolution.allowdivergence')
1104 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1106 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1105
1107
1106 if divergencebasecandidates and not divergenceok:
1108 if divergencebasecandidates and not divergenceok:
1107 divhashes = (str(repo[r])
1109 divhashes = (str(repo[r])
1108 for r in divergencebasecandidates)
1110 for r in divergencebasecandidates)
1109 msg = _("this rebase will cause "
1111 msg = _("this rebase will cause "
1110 "divergences from: %s")
1112 "divergences from: %s")
1111 h = _("to force the rebase please set "
1113 h = _("to force the rebase please set "
1112 "experimental.evolution.allowdivergence=True")
1114 "experimental.evolution.allowdivergence=True")
1113 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1115 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1114
1116
1115 def successorrevs(unfi, rev):
1117 def successorrevs(unfi, rev):
1116 """yield revision numbers for successors of rev"""
1118 """yield revision numbers for successors of rev"""
1117 assert unfi.filtername is None
1119 assert unfi.filtername is None
1118 nodemap = unfi.changelog.nodemap
1120 nodemap = unfi.changelog.nodemap
1119 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1121 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1120 if s in nodemap:
1122 if s in nodemap:
1121 yield nodemap[s]
1123 yield nodemap[s]
1122
1124
1123 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1125 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1124 """Return new parents and optionally a merge base for rev being rebased
1126 """Return new parents and optionally a merge base for rev being rebased
1125
1127
1126 The destination specified by "dest" cannot always be used directly because
1128 The destination specified by "dest" cannot always be used directly because
1127 previously rebase result could affect destination. For example,
1129 previously rebase result could affect destination. For example,
1128
1130
1129 D E rebase -r C+D+E -d B
1131 D E rebase -r C+D+E -d B
1130 |/ C will be rebased to C'
1132 |/ C will be rebased to C'
1131 B C D's new destination will be C' instead of B
1133 B C D's new destination will be C' instead of B
1132 |/ E's new destination will be C' instead of B
1134 |/ E's new destination will be C' instead of B
1133 A
1135 A
1134
1136
1135 The new parents of a merge is slightly more complicated. See the comment
1137 The new parents of a merge is slightly more complicated. See the comment
1136 block below.
1138 block below.
1137 """
1139 """
1138 # use unfiltered changelog since successorrevs may return filtered nodes
1140 # use unfiltered changelog since successorrevs may return filtered nodes
1139 assert repo.filtername is None
1141 assert repo.filtername is None
1140 cl = repo.changelog
1142 cl = repo.changelog
1141 def isancestor(a, b):
1143 def isancestor(a, b):
1142 # take revision numbers instead of nodes
1144 # take revision numbers instead of nodes
1143 if a == b:
1145 if a == b:
1144 return True
1146 return True
1145 elif a > b:
1147 elif a > b:
1146 return False
1148 return False
1147 return cl.isancestor(cl.node(a), cl.node(b))
1149 return cl.isancestor(cl.node(a), cl.node(b))
1148
1150
1149 dest = destmap[rev]
1151 dest = destmap[rev]
1150 oldps = repo.changelog.parentrevs(rev) # old parents
1152 oldps = repo.changelog.parentrevs(rev) # old parents
1151 newps = [nullrev, nullrev] # new parents
1153 newps = [nullrev, nullrev] # new parents
1152 dests = adjustdest(repo, rev, destmap, state, skipped)
1154 dests = adjustdest(repo, rev, destmap, state, skipped)
1153 bases = list(oldps) # merge base candidates, initially just old parents
1155 bases = list(oldps) # merge base candidates, initially just old parents
1154
1156
1155 if all(r == nullrev for r in oldps[1:]):
1157 if all(r == nullrev for r in oldps[1:]):
1156 # For non-merge changeset, just move p to adjusted dest as requested.
1158 # For non-merge changeset, just move p to adjusted dest as requested.
1157 newps[0] = dests[0]
1159 newps[0] = dests[0]
1158 else:
1160 else:
1159 # For merge changeset, if we move p to dests[i] unconditionally, both
1161 # For merge changeset, if we move p to dests[i] unconditionally, both
1160 # parents may change and the end result looks like "the merge loses a
1162 # parents may change and the end result looks like "the merge loses a
1161 # parent", which is a surprise. This is a limit because "--dest" only
1163 # parent", which is a surprise. This is a limit because "--dest" only
1162 # accepts one dest per src.
1164 # accepts one dest per src.
1163 #
1165 #
1164 # Therefore, only move p with reasonable conditions (in this order):
1166 # Therefore, only move p with reasonable conditions (in this order):
1165 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1167 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1166 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1168 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1167 #
1169 #
1168 # Comparing with adjustdest, the logic here does some additional work:
1170 # Comparing with adjustdest, the logic here does some additional work:
1169 # 1. decide which parents will not be moved towards dest
1171 # 1. decide which parents will not be moved towards dest
1170 # 2. if the above decision is "no", should a parent still be moved
1172 # 2. if the above decision is "no", should a parent still be moved
1171 # because it was rebased?
1173 # because it was rebased?
1172 #
1174 #
1173 # For example:
1175 # For example:
1174 #
1176 #
1175 # C # "rebase -r C -d D" is an error since none of the parents
1177 # C # "rebase -r C -d D" is an error since none of the parents
1176 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1178 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1177 # A B D # B (using rule "2."), since B will be rebased.
1179 # A B D # B (using rule "2."), since B will be rebased.
1178 #
1180 #
1179 # The loop tries to be not rely on the fact that a Mercurial node has
1181 # The loop tries to be not rely on the fact that a Mercurial node has
1180 # at most 2 parents.
1182 # at most 2 parents.
1181 for i, p in enumerate(oldps):
1183 for i, p in enumerate(oldps):
1182 np = p # new parent
1184 np = p # new parent
1183 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1185 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1184 np = dests[i]
1186 np = dests[i]
1185 elif p in state and state[p] > 0:
1187 elif p in state and state[p] > 0:
1186 np = state[p]
1188 np = state[p]
1187
1189
1188 # "bases" only record "special" merge bases that cannot be
1190 # "bases" only record "special" merge bases that cannot be
1189 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1191 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1190 # For example:
1192 # For example:
1191 #
1193 #
1192 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1194 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1193 # | C # is B', but merge base for C is B, instead of
1195 # | C # is B', but merge base for C is B, instead of
1194 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1196 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1195 # | B # "state" edges are merged (so there will be an edge from
1197 # | B # "state" edges are merged (so there will be an edge from
1196 # |/ # B to B'), the merge base is still ancestor(C, B') in
1198 # |/ # B to B'), the merge base is still ancestor(C, B') in
1197 # A # the merged graph.
1199 # A # the merged graph.
1198 #
1200 #
1199 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1201 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1200 # which uses "virtual null merge" to explain this situation.
1202 # which uses "virtual null merge" to explain this situation.
1201 if isancestor(p, np):
1203 if isancestor(p, np):
1202 bases[i] = nullrev
1204 bases[i] = nullrev
1203
1205
1204 # If one parent becomes an ancestor of the other, drop the ancestor
1206 # If one parent becomes an ancestor of the other, drop the ancestor
1205 for j, x in enumerate(newps[:i]):
1207 for j, x in enumerate(newps[:i]):
1206 if x == nullrev:
1208 if x == nullrev:
1207 continue
1209 continue
1208 if isancestor(np, x): # CASE-1
1210 if isancestor(np, x): # CASE-1
1209 np = nullrev
1211 np = nullrev
1210 elif isancestor(x, np): # CASE-2
1212 elif isancestor(x, np): # CASE-2
1211 newps[j] = np
1213 newps[j] = np
1212 np = nullrev
1214 np = nullrev
1213 # New parents forming an ancestor relationship does not
1215 # New parents forming an ancestor relationship does not
1214 # mean the old parents have a similar relationship. Do not
1216 # mean the old parents have a similar relationship. Do not
1215 # set bases[x] to nullrev.
1217 # set bases[x] to nullrev.
1216 bases[j], bases[i] = bases[i], bases[j]
1218 bases[j], bases[i] = bases[i], bases[j]
1217
1219
1218 newps[i] = np
1220 newps[i] = np
1219
1221
1220 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1222 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1221 # base. If only p2 changes, merging using unchanged p1 as merge base is
1223 # base. If only p2 changes, merging using unchanged p1 as merge base is
1222 # suboptimal. Therefore swap parents to make the merge sane.
1224 # suboptimal. Therefore swap parents to make the merge sane.
1223 if newps[1] != nullrev and oldps[0] == newps[0]:
1225 if newps[1] != nullrev and oldps[0] == newps[0]:
1224 assert len(newps) == 2 and len(oldps) == 2
1226 assert len(newps) == 2 and len(oldps) == 2
1225 newps.reverse()
1227 newps.reverse()
1226 bases.reverse()
1228 bases.reverse()
1227
1229
1228 # No parent change might be an error because we fail to make rev a
1230 # No parent change might be an error because we fail to make rev a
1229 # descendent of requested dest. This can happen, for example:
1231 # descendent of requested dest. This can happen, for example:
1230 #
1232 #
1231 # C # rebase -r C -d D
1233 # C # rebase -r C -d D
1232 # /| # None of A and B will be changed to D and rebase fails.
1234 # /| # None of A and B will be changed to D and rebase fails.
1233 # A B D
1235 # A B D
1234 if set(newps) == set(oldps) and dest not in newps:
1236 if set(newps) == set(oldps) and dest not in newps:
1235 raise error.Abort(_('cannot rebase %d:%s without '
1237 raise error.Abort(_('cannot rebase %d:%s without '
1236 'moving at least one of its parents')
1238 'moving at least one of its parents')
1237 % (rev, repo[rev]))
1239 % (rev, repo[rev]))
1238
1240
1239 # Source should not be ancestor of dest. The check here guarantees it's
1241 # Source should not be ancestor of dest. The check here guarantees it's
1240 # impossible. With multi-dest, the initial check does not cover complex
1242 # impossible. With multi-dest, the initial check does not cover complex
1241 # cases since we don't have abstractions to dry-run rebase cheaply.
1243 # cases since we don't have abstractions to dry-run rebase cheaply.
1242 if any(p != nullrev and isancestor(rev, p) for p in newps):
1244 if any(p != nullrev and isancestor(rev, p) for p in newps):
1243 raise error.Abort(_('source is ancestor of destination'))
1245 raise error.Abort(_('source is ancestor of destination'))
1244
1246
1245 # "rebasenode" updates to new p1, use the corresponding merge base.
1247 # "rebasenode" updates to new p1, use the corresponding merge base.
1246 if bases[0] != nullrev:
1248 if bases[0] != nullrev:
1247 base = bases[0]
1249 base = bases[0]
1248 else:
1250 else:
1249 base = None
1251 base = None
1250
1252
1251 # Check if the merge will contain unwanted changes. That may happen if
1253 # Check if the merge will contain unwanted changes. That may happen if
1252 # there are multiple special (non-changelog ancestor) merge bases, which
1254 # there are multiple special (non-changelog ancestor) merge bases, which
1253 # cannot be handled well by the 3-way merge algorithm. For example:
1255 # cannot be handled well by the 3-way merge algorithm. For example:
1254 #
1256 #
1255 # F
1257 # F
1256 # /|
1258 # /|
1257 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1259 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1258 # | | # as merge base, the difference between D and F will include
1260 # | | # as merge base, the difference between D and F will include
1259 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1261 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1260 # |/ # chosen, the rebased F will contain B.
1262 # |/ # chosen, the rebased F will contain B.
1261 # A Z
1263 # A Z
1262 #
1264 #
1263 # But our merge base candidates (D and E in above case) could still be
1265 # But our merge base candidates (D and E in above case) could still be
1264 # better than the default (ancestor(F, Z) == null). Therefore still
1266 # better than the default (ancestor(F, Z) == null). Therefore still
1265 # pick one (so choose p1 above).
1267 # pick one (so choose p1 above).
1266 if sum(1 for b in bases if b != nullrev) > 1:
1268 if sum(1 for b in bases if b != nullrev) > 1:
1267 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1269 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1268 for i, base in enumerate(bases):
1270 for i, base in enumerate(bases):
1269 if base == nullrev:
1271 if base == nullrev:
1270 continue
1272 continue
1271 # Revisions in the side (not chosen as merge base) branch that
1273 # Revisions in the side (not chosen as merge base) branch that
1272 # might contain "surprising" contents
1274 # might contain "surprising" contents
1273 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1275 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1274 bases, base, base, dest))
1276 bases, base, base, dest))
1275
1277
1276 # If those revisions are covered by rebaseset, the result is good.
1278 # If those revisions are covered by rebaseset, the result is good.
1277 # A merge in rebaseset would be considered to cover its ancestors.
1279 # A merge in rebaseset would be considered to cover its ancestors.
1278 if siderevs:
1280 if siderevs:
1279 rebaseset = [r for r, d in state.items()
1281 rebaseset = [r for r, d in state.items()
1280 if d > 0 and r not in obsskipped]
1282 if d > 0 and r not in obsskipped]
1281 merges = [r for r in rebaseset
1283 merges = [r for r in rebaseset
1282 if cl.parentrevs(r)[1] != nullrev]
1284 if cl.parentrevs(r)[1] != nullrev]
1283 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1285 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1284 siderevs, merges, rebaseset))
1286 siderevs, merges, rebaseset))
1285
1287
1286 # Choose a merge base that has a minimal number of unwanted revs.
1288 # Choose a merge base that has a minimal number of unwanted revs.
1287 l, i = min((len(revs), i)
1289 l, i = min((len(revs), i)
1288 for i, revs in enumerate(unwanted) if revs is not None)
1290 for i, revs in enumerate(unwanted) if revs is not None)
1289 base = bases[i]
1291 base = bases[i]
1290
1292
1291 # newps[0] should match merge base if possible. Currently, if newps[i]
1293 # newps[0] should match merge base if possible. Currently, if newps[i]
1292 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1294 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1293 # the other's ancestor. In that case, it's fine to not swap newps here.
1295 # the other's ancestor. In that case, it's fine to not swap newps here.
1294 # (see CASE-1 and CASE-2 above)
1296 # (see CASE-1 and CASE-2 above)
1295 if i != 0 and newps[i] != nullrev:
1297 if i != 0 and newps[i] != nullrev:
1296 newps[0], newps[i] = newps[i], newps[0]
1298 newps[0], newps[i] = newps[i], newps[0]
1297
1299
1298 # The merge will include unwanted revisions. Abort now. Revisit this if
1300 # The merge will include unwanted revisions. Abort now. Revisit this if
1299 # we have a more advanced merge algorithm that handles multiple bases.
1301 # we have a more advanced merge algorithm that handles multiple bases.
1300 if l > 0:
1302 if l > 0:
1301 unwanteddesc = _(' or ').join(
1303 unwanteddesc = _(' or ').join(
1302 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1304 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1303 for revs in unwanted if revs is not None))
1305 for revs in unwanted if revs is not None))
1304 raise error.Abort(
1306 raise error.Abort(
1305 _('rebasing %d:%s will include unwanted changes from %s')
1307 _('rebasing %d:%s will include unwanted changes from %s')
1306 % (rev, repo[rev], unwanteddesc))
1308 % (rev, repo[rev], unwanteddesc))
1307
1309
1308 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1310 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1309
1311
1310 return newps[0], newps[1], base
1312 return newps[0], newps[1], base
1311
1313
1312 def isagitpatch(repo, patchname):
1314 def isagitpatch(repo, patchname):
1313 'Return true if the given patch is in git format'
1315 'Return true if the given patch is in git format'
1314 mqpatch = os.path.join(repo.mq.path, patchname)
1316 mqpatch = os.path.join(repo.mq.path, patchname)
1315 for line in patch.linereader(file(mqpatch, 'rb')):
1317 for line in patch.linereader(file(mqpatch, 'rb')):
1316 if line.startswith('diff --git'):
1318 if line.startswith('diff --git'):
1317 return True
1319 return True
1318 return False
1320 return False
1319
1321
1320 def updatemq(repo, state, skipped, **opts):
1322 def updatemq(repo, state, skipped, **opts):
1321 'Update rebased mq patches - finalize and then import them'
1323 'Update rebased mq patches - finalize and then import them'
1322 mqrebase = {}
1324 mqrebase = {}
1323 mq = repo.mq
1325 mq = repo.mq
1324 original_series = mq.fullseries[:]
1326 original_series = mq.fullseries[:]
1325 skippedpatches = set()
1327 skippedpatches = set()
1326
1328
1327 for p in mq.applied:
1329 for p in mq.applied:
1328 rev = repo[p.node].rev()
1330 rev = repo[p.node].rev()
1329 if rev in state:
1331 if rev in state:
1330 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1332 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1331 (rev, p.name))
1333 (rev, p.name))
1332 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1334 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1333 else:
1335 else:
1334 # Applied but not rebased, not sure this should happen
1336 # Applied but not rebased, not sure this should happen
1335 skippedpatches.add(p.name)
1337 skippedpatches.add(p.name)
1336
1338
1337 if mqrebase:
1339 if mqrebase:
1338 mq.finish(repo, mqrebase.keys())
1340 mq.finish(repo, mqrebase.keys())
1339
1341
1340 # We must start import from the newest revision
1342 # We must start import from the newest revision
1341 for rev in sorted(mqrebase, reverse=True):
1343 for rev in sorted(mqrebase, reverse=True):
1342 if rev not in skipped:
1344 if rev not in skipped:
1343 name, isgit = mqrebase[rev]
1345 name, isgit = mqrebase[rev]
1344 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1346 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1345 (name, state[rev], repo[state[rev]]))
1347 (name, state[rev], repo[state[rev]]))
1346 mq.qimport(repo, (), patchname=name, git=isgit,
1348 mq.qimport(repo, (), patchname=name, git=isgit,
1347 rev=[str(state[rev])])
1349 rev=[str(state[rev])])
1348 else:
1350 else:
1349 # Rebased and skipped
1351 # Rebased and skipped
1350 skippedpatches.add(mqrebase[rev][0])
1352 skippedpatches.add(mqrebase[rev][0])
1351
1353
1352 # Patches were either applied and rebased and imported in
1354 # Patches were either applied and rebased and imported in
1353 # order, applied and removed or unapplied. Discard the removed
1355 # order, applied and removed or unapplied. Discard the removed
1354 # ones while preserving the original series order and guards.
1356 # ones while preserving the original series order and guards.
1355 newseries = [s for s in original_series
1357 newseries = [s for s in original_series
1356 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1358 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1357 mq.fullseries[:] = newseries
1359 mq.fullseries[:] = newseries
1358 mq.seriesdirty = True
1360 mq.seriesdirty = True
1359 mq.savedirty()
1361 mq.savedirty()
1360
1362
1361 def storecollapsemsg(repo, collapsemsg):
1363 def storecollapsemsg(repo, collapsemsg):
1362 'Store the collapse message to allow recovery'
1364 'Store the collapse message to allow recovery'
1363 collapsemsg = collapsemsg or ''
1365 collapsemsg = collapsemsg or ''
1364 f = repo.vfs("last-message.txt", "w")
1366 f = repo.vfs("last-message.txt", "w")
1365 f.write("%s\n" % collapsemsg)
1367 f.write("%s\n" % collapsemsg)
1366 f.close()
1368 f.close()
1367
1369
1368 def clearcollapsemsg(repo):
1370 def clearcollapsemsg(repo):
1369 'Remove collapse message file'
1371 'Remove collapse message file'
1370 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1372 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1371
1373
1372 def restorecollapsemsg(repo, isabort):
1374 def restorecollapsemsg(repo, isabort):
1373 'Restore previously stored collapse message'
1375 'Restore previously stored collapse message'
1374 try:
1376 try:
1375 f = repo.vfs("last-message.txt")
1377 f = repo.vfs("last-message.txt")
1376 collapsemsg = f.readline().strip()
1378 collapsemsg = f.readline().strip()
1377 f.close()
1379 f.close()
1378 except IOError as err:
1380 except IOError as err:
1379 if err.errno != errno.ENOENT:
1381 if err.errno != errno.ENOENT:
1380 raise
1382 raise
1381 if isabort:
1383 if isabort:
1382 # Oh well, just abort like normal
1384 # Oh well, just abort like normal
1383 collapsemsg = ''
1385 collapsemsg = ''
1384 else:
1386 else:
1385 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1387 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1386 return collapsemsg
1388 return collapsemsg
1387
1389
1388 def clearstatus(repo):
1390 def clearstatus(repo):
1389 'Remove the status files'
1391 'Remove the status files'
1390 # Make sure the active transaction won't write the state file
1392 # Make sure the active transaction won't write the state file
1391 tr = repo.currenttransaction()
1393 tr = repo.currenttransaction()
1392 if tr:
1394 if tr:
1393 tr.removefilegenerator('rebasestate')
1395 tr.removefilegenerator('rebasestate')
1394 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1396 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1395
1397
1396 def needupdate(repo, state):
1398 def needupdate(repo, state):
1397 '''check whether we should `update --clean` away from a merge, or if
1399 '''check whether we should `update --clean` away from a merge, or if
1398 somehow the working dir got forcibly updated, e.g. by older hg'''
1400 somehow the working dir got forcibly updated, e.g. by older hg'''
1399 parents = [p.rev() for p in repo[None].parents()]
1401 parents = [p.rev() for p in repo[None].parents()]
1400
1402
1401 # Are we in a merge state at all?
1403 # Are we in a merge state at all?
1402 if len(parents) < 2:
1404 if len(parents) < 2:
1403 return False
1405 return False
1404
1406
1405 # We should be standing on the first as-of-yet unrebased commit.
1407 # We should be standing on the first as-of-yet unrebased commit.
1406 firstunrebased = min([old for old, new in state.iteritems()
1408 firstunrebased = min([old for old, new in state.iteritems()
1407 if new == nullrev])
1409 if new == nullrev])
1408 if firstunrebased in parents:
1410 if firstunrebased in parents:
1409 return True
1411 return True
1410
1412
1411 return False
1413 return False
1412
1414
1413 def abort(repo, originalwd, destmap, state, activebookmark=None):
1415 def abort(repo, originalwd, destmap, state, activebookmark=None):
1414 '''Restore the repository to its original state. Additional args:
1416 '''Restore the repository to its original state. Additional args:
1415
1417
1416 activebookmark: the name of the bookmark that should be active after the
1418 activebookmark: the name of the bookmark that should be active after the
1417 restore'''
1419 restore'''
1418
1420
1419 try:
1421 try:
1420 # If the first commits in the rebased set get skipped during the rebase,
1422 # If the first commits in the rebased set get skipped during the rebase,
1421 # their values within the state mapping will be the dest rev id. The
1423 # their values within the state mapping will be the dest rev id. The
1422 # dstates list must must not contain the dest rev (issue4896)
1424 # dstates list must must not contain the dest rev (issue4896)
1423 dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]]
1425 dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]]
1424 immutable = [d for d in dstates if not repo[d].mutable()]
1426 immutable = [d for d in dstates if not repo[d].mutable()]
1425 cleanup = True
1427 cleanup = True
1426 if immutable:
1428 if immutable:
1427 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1429 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1428 % ', '.join(str(repo[r]) for r in immutable),
1430 % ', '.join(str(repo[r]) for r in immutable),
1429 hint=_("see 'hg help phases' for details"))
1431 hint=_("see 'hg help phases' for details"))
1430 cleanup = False
1432 cleanup = False
1431
1433
1432 descendants = set()
1434 descendants = set()
1433 if dstates:
1435 if dstates:
1434 descendants = set(repo.changelog.descendants(dstates))
1436 descendants = set(repo.changelog.descendants(dstates))
1435 if descendants - set(dstates):
1437 if descendants - set(dstates):
1436 repo.ui.warn(_("warning: new changesets detected on destination "
1438 repo.ui.warn(_("warning: new changesets detected on destination "
1437 "branch, can't strip\n"))
1439 "branch, can't strip\n"))
1438 cleanup = False
1440 cleanup = False
1439
1441
1440 if cleanup:
1442 if cleanup:
1441 shouldupdate = False
1443 shouldupdate = False
1442 rebased = [s for r, s in state.items()
1444 rebased = [s for r, s in state.items()
1443 if s >= 0 and s != destmap[r]]
1445 if s >= 0 and s != destmap[r]]
1444 if rebased:
1446 if rebased:
1445 strippoints = [
1447 strippoints = [
1446 c.node() for c in repo.set('roots(%ld)', rebased)]
1448 c.node() for c in repo.set('roots(%ld)', rebased)]
1447
1449
1448 updateifonnodes = set(rebased)
1450 updateifonnodes = set(rebased)
1449 updateifonnodes.update(destmap.values())
1451 updateifonnodes.update(destmap.values())
1450 updateifonnodes.add(originalwd)
1452 updateifonnodes.add(originalwd)
1451 shouldupdate = repo['.'].rev() in updateifonnodes
1453 shouldupdate = repo['.'].rev() in updateifonnodes
1452
1454
1453 # Update away from the rebase if necessary
1455 # Update away from the rebase if necessary
1454 if shouldupdate or needupdate(repo, state):
1456 if shouldupdate or needupdate(repo, state):
1455 mergemod.update(repo, originalwd, False, True)
1457 mergemod.update(repo, originalwd, False, True)
1456
1458
1457 # Strip from the first rebased revision
1459 # Strip from the first rebased revision
1458 if rebased:
1460 if rebased:
1459 # no backup of rebased cset versions needed
1461 # no backup of rebased cset versions needed
1460 repair.strip(repo.ui, repo, strippoints)
1462 repair.strip(repo.ui, repo, strippoints)
1461
1463
1462 if activebookmark and activebookmark in repo._bookmarks:
1464 if activebookmark and activebookmark in repo._bookmarks:
1463 bookmarks.activate(repo, activebookmark)
1465 bookmarks.activate(repo, activebookmark)
1464
1466
1465 finally:
1467 finally:
1466 clearstatus(repo)
1468 clearstatus(repo)
1467 clearcollapsemsg(repo)
1469 clearcollapsemsg(repo)
1468 repo.ui.warn(_('rebase aborted\n'))
1470 repo.ui.warn(_('rebase aborted\n'))
1469 return 0
1471 return 0
1470
1472
1471 def sortsource(destmap):
1473 def sortsource(destmap):
1472 """yield source revisions in an order that we only rebase things once
1474 """yield source revisions in an order that we only rebase things once
1473
1475
1474 If source and destination overlaps, we should filter out revisions
1476 If source and destination overlaps, we should filter out revisions
1475 depending on other revisions which hasn't been rebased yet.
1477 depending on other revisions which hasn't been rebased yet.
1476
1478
1477 Yield a sorted list of revisions each time.
1479 Yield a sorted list of revisions each time.
1478
1480
1479 For example, when rebasing A to B, B to C. This function yields [B], then
1481 For example, when rebasing A to B, B to C. This function yields [B], then
1480 [A], indicating B needs to be rebased first.
1482 [A], indicating B needs to be rebased first.
1481
1483
1482 Raise if there is a cycle so the rebase is impossible.
1484 Raise if there is a cycle so the rebase is impossible.
1483 """
1485 """
1484 srcset = set(destmap)
1486 srcset = set(destmap)
1485 while srcset:
1487 while srcset:
1486 srclist = sorted(srcset)
1488 srclist = sorted(srcset)
1487 result = []
1489 result = []
1488 for r in srclist:
1490 for r in srclist:
1489 if destmap[r] not in srcset:
1491 if destmap[r] not in srcset:
1490 result.append(r)
1492 result.append(r)
1491 if not result:
1493 if not result:
1492 raise error.Abort(_('source and destination form a cycle'))
1494 raise error.Abort(_('source and destination form a cycle'))
1493 srcset -= set(result)
1495 srcset -= set(result)
1494 yield result
1496 yield result
1495
1497
1496 def buildstate(repo, destmap, collapse):
1498 def buildstate(repo, destmap, collapse):
1497 '''Define which revisions are going to be rebased and where
1499 '''Define which revisions are going to be rebased and where
1498
1500
1499 repo: repo
1501 repo: repo
1500 destmap: {srcrev: destrev}
1502 destmap: {srcrev: destrev}
1501 '''
1503 '''
1502 rebaseset = destmap.keys()
1504 rebaseset = destmap.keys()
1503 originalwd = repo['.'].rev()
1505 originalwd = repo['.'].rev()
1504
1506
1505 # This check isn't strictly necessary, since mq detects commits over an
1507 # This check isn't strictly necessary, since mq detects commits over an
1506 # applied patch. But it prevents messing up the working directory when
1508 # applied patch. But it prevents messing up the working directory when
1507 # a partially completed rebase is blocked by mq.
1509 # a partially completed rebase is blocked by mq.
1508 if 'qtip' in repo.tags():
1510 if 'qtip' in repo.tags():
1509 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1511 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1510 if set(destmap.values()) & mqapplied:
1512 if set(destmap.values()) & mqapplied:
1511 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1513 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1512
1514
1513 # Get "cycle" error early by exhausting the generator.
1515 # Get "cycle" error early by exhausting the generator.
1514 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1516 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1515 if not sortedsrc:
1517 if not sortedsrc:
1516 raise error.Abort(_('no matching revisions'))
1518 raise error.Abort(_('no matching revisions'))
1517
1519
1518 # Only check the first batch of revisions to rebase not depending on other
1520 # Only check the first batch of revisions to rebase not depending on other
1519 # rebaseset. This means "source is ancestor of destination" for the second
1521 # rebaseset. This means "source is ancestor of destination" for the second
1520 # (and following) batches of revisions are not checked here. We rely on
1522 # (and following) batches of revisions are not checked here. We rely on
1521 # "defineparents" to do that check.
1523 # "defineparents" to do that check.
1522 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1524 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1523 if not roots:
1525 if not roots:
1524 raise error.Abort(_('no matching revisions'))
1526 raise error.Abort(_('no matching revisions'))
1525 roots.sort()
1527 roots.sort()
1526 state = dict.fromkeys(rebaseset, revtodo)
1528 state = dict.fromkeys(rebaseset, revtodo)
1527 emptyrebase = (len(sortedsrc) == 1)
1529 emptyrebase = (len(sortedsrc) == 1)
1528 for root in roots:
1530 for root in roots:
1529 dest = repo[destmap[root.rev()]]
1531 dest = repo[destmap[root.rev()]]
1530 commonbase = root.ancestor(dest)
1532 commonbase = root.ancestor(dest)
1531 if commonbase == root:
1533 if commonbase == root:
1532 raise error.Abort(_('source is ancestor of destination'))
1534 raise error.Abort(_('source is ancestor of destination'))
1533 if commonbase == dest:
1535 if commonbase == dest:
1534 wctx = repo[None]
1536 wctx = repo[None]
1535 if dest == wctx.p1():
1537 if dest == wctx.p1():
1536 # when rebasing to '.', it will use the current wd branch name
1538 # when rebasing to '.', it will use the current wd branch name
1537 samebranch = root.branch() == wctx.branch()
1539 samebranch = root.branch() == wctx.branch()
1538 else:
1540 else:
1539 samebranch = root.branch() == dest.branch()
1541 samebranch = root.branch() == dest.branch()
1540 if not collapse and samebranch and dest in root.parents():
1542 if not collapse and samebranch and dest in root.parents():
1541 # mark the revision as done by setting its new revision
1543 # mark the revision as done by setting its new revision
1542 # equal to its old (current) revisions
1544 # equal to its old (current) revisions
1543 state[root.rev()] = root.rev()
1545 state[root.rev()] = root.rev()
1544 repo.ui.debug('source is a child of destination\n')
1546 repo.ui.debug('source is a child of destination\n')
1545 continue
1547 continue
1546
1548
1547 emptyrebase = False
1549 emptyrebase = False
1548 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1550 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1549 if emptyrebase:
1551 if emptyrebase:
1550 return None
1552 return None
1551 for rev in sorted(state):
1553 for rev in sorted(state):
1552 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1554 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1553 # if all parents of this revision are done, then so is this revision
1555 # if all parents of this revision are done, then so is this revision
1554 if parents and all((state.get(p) == p for p in parents)):
1556 if parents and all((state.get(p) == p for p in parents)):
1555 state[rev] = rev
1557 state[rev] = rev
1556 return originalwd, destmap, state
1558 return originalwd, destmap, state
1557
1559
1558 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1560 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1559 keepf=False, fm=None):
1561 keepf=False, fm=None):
1560 """dispose of rebased revision at the end of the rebase
1562 """dispose of rebased revision at the end of the rebase
1561
1563
1562 If `collapsedas` is not None, the rebase was a collapse whose result if the
1564 If `collapsedas` is not None, the rebase was a collapse whose result if the
1563 `collapsedas` node.
1565 `collapsedas` node.
1564
1566
1565 If `keepf` is not True, the rebase has --keep set and no nodes should be
1567 If `keepf` is not True, the rebase has --keep set and no nodes should be
1566 removed (but bookmarks still need to be moved).
1568 removed (but bookmarks still need to be moved).
1567 """
1569 """
1568 tonode = repo.changelog.node
1570 tonode = repo.changelog.node
1569 replacements = {}
1571 replacements = {}
1570 moves = {}
1572 moves = {}
1571 for rev, newrev in sorted(state.items()):
1573 for rev, newrev in sorted(state.items()):
1572 if newrev >= 0 and newrev != rev:
1574 if newrev >= 0 and newrev != rev:
1573 oldnode = tonode(rev)
1575 oldnode = tonode(rev)
1574 newnode = collapsedas or tonode(newrev)
1576 newnode = collapsedas or tonode(newrev)
1575 moves[oldnode] = newnode
1577 moves[oldnode] = newnode
1576 if not keepf:
1578 if not keepf:
1577 if rev in skipped:
1579 if rev in skipped:
1578 succs = ()
1580 succs = ()
1579 else:
1581 else:
1580 succs = (newnode,)
1582 succs = (newnode,)
1581 replacements[oldnode] = succs
1583 replacements[oldnode] = succs
1582 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1584 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1583 if fm:
1585 if fm:
1584 hf = fm.hexfunc
1586 hf = fm.hexfunc
1585 fl = fm.formatlist
1587 fl = fm.formatlist
1586 fd = fm.formatdict
1588 fd = fm.formatdict
1587 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1589 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1588 for oldn, newn in replacements.iteritems()},
1590 for oldn, newn in replacements.iteritems()},
1589 key="oldnode", value="newnodes")
1591 key="oldnode", value="newnodes")
1590 fm.data(nodechanges=nodechanges)
1592 fm.data(nodechanges=nodechanges)
1591
1593
1592 def pullrebase(orig, ui, repo, *args, **opts):
1594 def pullrebase(orig, ui, repo, *args, **opts):
1593 'Call rebase after pull if the latter has been invoked with --rebase'
1595 'Call rebase after pull if the latter has been invoked with --rebase'
1594 ret = None
1596 ret = None
1595 if opts.get(r'rebase'):
1597 if opts.get(r'rebase'):
1596 if ui.configbool('commands', 'rebase.requiredest'):
1598 if ui.configbool('commands', 'rebase.requiredest'):
1597 msg = _('rebase destination required by configuration')
1599 msg = _('rebase destination required by configuration')
1598 hint = _('use hg pull followed by hg rebase -d DEST')
1600 hint = _('use hg pull followed by hg rebase -d DEST')
1599 raise error.Abort(msg, hint=hint)
1601 raise error.Abort(msg, hint=hint)
1600
1602
1601 with repo.wlock(), repo.lock():
1603 with repo.wlock(), repo.lock():
1602 if opts.get(r'update'):
1604 if opts.get(r'update'):
1603 del opts[r'update']
1605 del opts[r'update']
1604 ui.debug('--update and --rebase are not compatible, ignoring '
1606 ui.debug('--update and --rebase are not compatible, ignoring '
1605 'the update flag\n')
1607 'the update flag\n')
1606
1608
1607 cmdutil.checkunfinished(repo)
1609 cmdutil.checkunfinished(repo)
1608 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1610 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1609 'please commit or shelve your changes first'))
1611 'please commit or shelve your changes first'))
1610
1612
1611 revsprepull = len(repo)
1613 revsprepull = len(repo)
1612 origpostincoming = commands.postincoming
1614 origpostincoming = commands.postincoming
1613 def _dummy(*args, **kwargs):
1615 def _dummy(*args, **kwargs):
1614 pass
1616 pass
1615 commands.postincoming = _dummy
1617 commands.postincoming = _dummy
1616 try:
1618 try:
1617 ret = orig(ui, repo, *args, **opts)
1619 ret = orig(ui, repo, *args, **opts)
1618 finally:
1620 finally:
1619 commands.postincoming = origpostincoming
1621 commands.postincoming = origpostincoming
1620 revspostpull = len(repo)
1622 revspostpull = len(repo)
1621 if revspostpull > revsprepull:
1623 if revspostpull > revsprepull:
1622 # --rev option from pull conflict with rebase own --rev
1624 # --rev option from pull conflict with rebase own --rev
1623 # dropping it
1625 # dropping it
1624 if r'rev' in opts:
1626 if r'rev' in opts:
1625 del opts[r'rev']
1627 del opts[r'rev']
1626 # positional argument from pull conflicts with rebase's own
1628 # positional argument from pull conflicts with rebase's own
1627 # --source.
1629 # --source.
1628 if r'source' in opts:
1630 if r'source' in opts:
1629 del opts[r'source']
1631 del opts[r'source']
1630 # revsprepull is the len of the repo, not revnum of tip.
1632 # revsprepull is the len of the repo, not revnum of tip.
1631 destspace = list(repo.changelog.revs(start=revsprepull))
1633 destspace = list(repo.changelog.revs(start=revsprepull))
1632 opts[r'_destspace'] = destspace
1634 opts[r'_destspace'] = destspace
1633 try:
1635 try:
1634 rebase(ui, repo, **opts)
1636 rebase(ui, repo, **opts)
1635 except error.NoMergeDestAbort:
1637 except error.NoMergeDestAbort:
1636 # we can maybe update instead
1638 # we can maybe update instead
1637 rev, _a, _b = destutil.destupdate(repo)
1639 rev, _a, _b = destutil.destupdate(repo)
1638 if rev == repo['.'].rev():
1640 if rev == repo['.'].rev():
1639 ui.status(_('nothing to rebase\n'))
1641 ui.status(_('nothing to rebase\n'))
1640 else:
1642 else:
1641 ui.status(_('nothing to rebase - updating instead\n'))
1643 ui.status(_('nothing to rebase - updating instead\n'))
1642 # not passing argument to get the bare update behavior
1644 # not passing argument to get the bare update behavior
1643 # with warning and trumpets
1645 # with warning and trumpets
1644 commands.update(ui, repo)
1646 commands.update(ui, repo)
1645 else:
1647 else:
1646 if opts.get(r'tool'):
1648 if opts.get(r'tool'):
1647 raise error.Abort(_('--tool can only be used with --rebase'))
1649 raise error.Abort(_('--tool can only be used with --rebase'))
1648 ret = orig(ui, repo, *args, **opts)
1650 ret = orig(ui, repo, *args, **opts)
1649
1651
1650 return ret
1652 return ret
1651
1653
1652 def _filterobsoleterevs(repo, revs):
1654 def _filterobsoleterevs(repo, revs):
1653 """returns a set of the obsolete revisions in revs"""
1655 """returns a set of the obsolete revisions in revs"""
1654 return set(r for r in revs if repo[r].obsolete())
1656 return set(r for r in revs if repo[r].obsolete())
1655
1657
1656 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1658 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1657 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1659 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1658
1660
1659 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1661 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1660 obsolete nodes to be rebased given in `rebaseobsrevs`.
1662 obsolete nodes to be rebased given in `rebaseobsrevs`.
1661
1663
1662 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1664 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1663 without a successor in destination.
1665 without a successor in destination.
1664 """
1666 """
1665 obsoletenotrebased = {}
1667 obsoletenotrebased = {}
1666 obsoletewithoutsuccessorindestination = set([])
1668 obsoletewithoutsuccessorindestination = set([])
1667
1669
1668 assert repo.filtername is None
1670 assert repo.filtername is None
1669 cl = repo.changelog
1671 cl = repo.changelog
1670 nodemap = cl.nodemap
1672 nodemap = cl.nodemap
1671 for srcrev in rebaseobsrevs:
1673 for srcrev in rebaseobsrevs:
1672 srcnode = cl.node(srcrev)
1674 srcnode = cl.node(srcrev)
1673 destnode = cl.node(destmap[srcrev])
1675 destnode = cl.node(destmap[srcrev])
1674 # XXX: more advanced APIs are required to handle split correctly
1676 # XXX: more advanced APIs are required to handle split correctly
1675 successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1677 successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1676 if len(successors) == 1:
1678 if len(successors) == 1:
1677 # obsutil.allsuccessors includes node itself. When the list only
1679 # obsutil.allsuccessors includes node itself. When the list only
1678 # contains one element, it means there are no successors.
1680 # contains one element, it means there are no successors.
1679 obsoletenotrebased[srcrev] = None
1681 obsoletenotrebased[srcrev] = None
1680 else:
1682 else:
1681 for succnode in successors:
1683 for succnode in successors:
1682 if succnode == srcnode or succnode not in nodemap:
1684 if succnode == srcnode or succnode not in nodemap:
1683 continue
1685 continue
1684 if cl.isancestor(succnode, destnode):
1686 if cl.isancestor(succnode, destnode):
1685 obsoletenotrebased[srcrev] = nodemap[succnode]
1687 obsoletenotrebased[srcrev] = nodemap[succnode]
1686 break
1688 break
1687 else:
1689 else:
1688 # If 'srcrev' has a successor in rebase set but none in
1690 # If 'srcrev' has a successor in rebase set but none in
1689 # destination (which would be catched above), we shall skip it
1691 # destination (which would be catched above), we shall skip it
1690 # and its descendants to avoid divergence.
1692 # and its descendants to avoid divergence.
1691 if any(nodemap[s] in destmap
1693 if any(nodemap[s] in destmap
1692 for s in successors if s != srcnode):
1694 for s in successors if s != srcnode):
1693 obsoletewithoutsuccessorindestination.add(srcrev)
1695 obsoletewithoutsuccessorindestination.add(srcrev)
1694
1696
1695 return obsoletenotrebased, obsoletewithoutsuccessorindestination
1697 return obsoletenotrebased, obsoletewithoutsuccessorindestination
1696
1698
1697 def summaryhook(ui, repo):
1699 def summaryhook(ui, repo):
1698 if not repo.vfs.exists('rebasestate'):
1700 if not repo.vfs.exists('rebasestate'):
1699 return
1701 return
1700 try:
1702 try:
1701 rbsrt = rebaseruntime(repo, ui, {})
1703 rbsrt = rebaseruntime(repo, ui, {})
1702 rbsrt.restorestatus()
1704 rbsrt.restorestatus()
1703 state = rbsrt.state
1705 state = rbsrt.state
1704 except error.RepoLookupError:
1706 except error.RepoLookupError:
1705 # i18n: column positioning for "hg summary"
1707 # i18n: column positioning for "hg summary"
1706 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1708 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1707 ui.write(msg)
1709 ui.write(msg)
1708 return
1710 return
1709 numrebased = len([i for i in state.itervalues() if i >= 0])
1711 numrebased = len([i for i in state.itervalues() if i >= 0])
1710 # i18n: column positioning for "hg summary"
1712 # i18n: column positioning for "hg summary"
1711 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1713 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1712 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1714 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1713 ui.label(_('%d remaining'), 'rebase.remaining') %
1715 ui.label(_('%d remaining'), 'rebase.remaining') %
1714 (len(state) - numrebased)))
1716 (len(state) - numrebased)))
1715
1717
1716 def uisetup(ui):
1718 def uisetup(ui):
1717 #Replace pull with a decorator to provide --rebase option
1719 #Replace pull with a decorator to provide --rebase option
1718 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1720 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1719 entry[1].append(('', 'rebase', None,
1721 entry[1].append(('', 'rebase', None,
1720 _("rebase working directory to branch head")))
1722 _("rebase working directory to branch head")))
1721 entry[1].append(('t', 'tool', '',
1723 entry[1].append(('t', 'tool', '',
1722 _("specify merge tool for rebase")))
1724 _("specify merge tool for rebase")))
1723 cmdutil.summaryhooks.add('rebase', summaryhook)
1725 cmdutil.summaryhooks.add('rebase', summaryhook)
1724 cmdutil.unfinishedstates.append(
1726 cmdutil.unfinishedstates.append(
1725 ['rebasestate', False, False, _('rebase in progress'),
1727 ['rebasestate', False, False, _('rebase in progress'),
1726 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1728 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1727 cmdutil.afterresolvedstates.append(
1729 cmdutil.afterresolvedstates.append(
1728 ['rebasestate', _('hg rebase --continue')])
1730 ['rebasestate', _('hg rebase --continue')])
General Comments 0
You need to be logged in to leave comments. Login now