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