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