##// END OF EJS Templates
rebase: move handling of obsolete commits to be a separate RR class method
Kostia Balytskyi -
r29479:5d16ebe7 default
parent child Browse files
Show More
@@ -1,1451 +1,1450
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 error,
35 error,
36 extensions,
36 extensions,
37 hg,
37 hg,
38 lock,
38 lock,
39 merge,
39 merge,
40 obsolete,
40 obsolete,
41 patch,
41 patch,
42 phases,
42 phases,
43 registrar,
43 registrar,
44 repair,
44 repair,
45 repoview,
45 repoview,
46 revset,
46 revset,
47 scmutil,
47 scmutil,
48 util,
48 util,
49 )
49 )
50
50
51 release = lock.release
51 release = lock.release
52 templateopts = commands.templateopts
52 templateopts = commands.templateopts
53
53
54 # The following constants are used throughout the rebase module. The ordering of
54 # The following constants are used throughout the rebase module. The ordering of
55 # their values must be maintained.
55 # their values must be maintained.
56
56
57 # Indicates that a revision needs to be rebased
57 # Indicates that a revision needs to be rebased
58 revtodo = -1
58 revtodo = -1
59 nullmerge = -2
59 nullmerge = -2
60 revignored = -3
60 revignored = -3
61 # successor in rebase destination
61 # successor in rebase destination
62 revprecursor = -4
62 revprecursor = -4
63 # plain prune (no successor)
63 # plain prune (no successor)
64 revpruned = -5
64 revpruned = -5
65 revskipped = (revignored, revprecursor, revpruned)
65 revskipped = (revignored, revprecursor, revpruned)
66
66
67 cmdtable = {}
67 cmdtable = {}
68 command = cmdutil.command(cmdtable)
68 command = cmdutil.command(cmdtable)
69 # Note for extension authors: ONLY specify testedwith = 'internal' for
69 # Note for extension authors: ONLY specify testedwith = 'internal' for
70 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
70 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
71 # be specifying the version(s) of Mercurial they are tested with, or
71 # be specifying the version(s) of Mercurial they are tested with, or
72 # leave the attribute unspecified.
72 # leave the attribute unspecified.
73 testedwith = 'internal'
73 testedwith = 'internal'
74
74
75 def _nothingtorebase():
75 def _nothingtorebase():
76 return 1
76 return 1
77
77
78 def _savegraft(ctx, extra):
78 def _savegraft(ctx, extra):
79 s = ctx.extra().get('source', None)
79 s = ctx.extra().get('source', None)
80 if s is not None:
80 if s is not None:
81 extra['source'] = s
81 extra['source'] = s
82 s = ctx.extra().get('intermediate-source', None)
82 s = ctx.extra().get('intermediate-source', None)
83 if s is not None:
83 if s is not None:
84 extra['intermediate-source'] = s
84 extra['intermediate-source'] = s
85
85
86 def _savebranch(ctx, extra):
86 def _savebranch(ctx, extra):
87 extra['branch'] = ctx.branch()
87 extra['branch'] = ctx.branch()
88
88
89 def _makeextrafn(copiers):
89 def _makeextrafn(copiers):
90 """make an extrafn out of the given copy-functions.
90 """make an extrafn out of the given copy-functions.
91
91
92 A copy function takes a context and an extra dict, and mutates the
92 A copy function takes a context and an extra dict, and mutates the
93 extra dict as needed based on the given context.
93 extra dict as needed based on the given context.
94 """
94 """
95 def extrafn(ctx, extra):
95 def extrafn(ctx, extra):
96 for c in copiers:
96 for c in copiers:
97 c(ctx, extra)
97 c(ctx, extra)
98 return extrafn
98 return extrafn
99
99
100 def _destrebase(repo, sourceset, destspace=None):
100 def _destrebase(repo, sourceset, destspace=None):
101 """small wrapper around destmerge to pass the right extra args
101 """small wrapper around destmerge to pass the right extra args
102
102
103 Please wrap destutil.destmerge instead."""
103 Please wrap destutil.destmerge instead."""
104 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
104 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
105 onheadcheck=False, destspace=destspace)
105 onheadcheck=False, destspace=destspace)
106
106
107 revsetpredicate = registrar.revsetpredicate()
107 revsetpredicate = registrar.revsetpredicate()
108
108
109 @revsetpredicate('_destrebase')
109 @revsetpredicate('_destrebase')
110 def _revsetdestrebase(repo, subset, x):
110 def _revsetdestrebase(repo, subset, x):
111 # ``_rebasedefaultdest()``
111 # ``_rebasedefaultdest()``
112
112
113 # default destination for rebase.
113 # default destination for rebase.
114 # # XXX: Currently private because I expect the signature to change.
114 # # XXX: Currently private because I expect the signature to change.
115 # # XXX: - bailing out in case of ambiguity vs returning all data.
115 # # XXX: - bailing out in case of ambiguity vs returning all data.
116 # i18n: "_rebasedefaultdest" is a keyword
116 # i18n: "_rebasedefaultdest" is a keyword
117 sourceset = None
117 sourceset = None
118 if x is not None:
118 if x is not None:
119 sourceset = revset.getset(repo, revset.fullreposet(repo), x)
119 sourceset = revset.getset(repo, revset.fullreposet(repo), x)
120 return subset & revset.baseset([_destrebase(repo, sourceset)])
120 return subset & revset.baseset([_destrebase(repo, sourceset)])
121
121
122 class rebaseruntime(object):
122 class rebaseruntime(object):
123 """This class is a container for rebase runtime state"""
123 """This class is a container for rebase runtime state"""
124 def __init__(self, repo, ui, opts=None):
124 def __init__(self, repo, ui, opts=None):
125 if opts is None:
125 if opts is None:
126 opts = {}
126 opts = {}
127
127
128 self.repo = repo
128 self.repo = repo
129 self.ui = ui
129 self.ui = ui
130 self.opts = opts
130 self.opts = opts
131 self.originalwd = None
131 self.originalwd = None
132 self.external = nullrev
132 self.external = nullrev
133 # Mapping between the old revision id and either what is the new rebased
133 # Mapping between the old revision id and either what is the new rebased
134 # revision or what needs to be done with the old revision. The state
134 # revision or what needs to be done with the old revision. The state
135 # dict will be what contains most of the rebase progress state.
135 # dict will be what contains most of the rebase progress state.
136 self.state = {}
136 self.state = {}
137 self.activebookmark = None
137 self.activebookmark = None
138 self.currentbookmarks = None
138 self.currentbookmarks = None
139 self.target = None
139 self.target = None
140 self.skipped = set()
140 self.skipped = set()
141 self.targetancestors = set()
141 self.targetancestors = set()
142
142
143 self.collapsef = opts.get('collapse', False)
143 self.collapsef = opts.get('collapse', False)
144 self.collapsemsg = cmdutil.logmessage(ui, opts)
144 self.collapsemsg = cmdutil.logmessage(ui, opts)
145 self.date = opts.get('date', None)
145 self.date = opts.get('date', None)
146
146
147 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
147 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
148 self.extrafns = [_savegraft]
148 self.extrafns = [_savegraft]
149 if e:
149 if e:
150 self.extrafns = [e]
150 self.extrafns = [e]
151 self.extrafn = None
151 self.extrafn = None
152
152
153 self.keepf = opts.get('keep', False)
153 self.keepf = opts.get('keep', False)
154 self.keepbranchesf = opts.get('keepbranches', False)
154 self.keepbranchesf = opts.get('keepbranches', False)
155 # keepopen is not meant for use on the command line, but by
155 # keepopen is not meant for use on the command line, but by
156 # other extensions
156 # other extensions
157 self.keepopen = opts.get('keepopen', False)
157 self.keepopen = opts.get('keepopen', False)
158 self.obsoletenotrebased = {}
158 self.obsoletenotrebased = {}
159
159
160 def restorestatus(self):
160 def restorestatus(self):
161 """Restore a previously stored status"""
161 """Restore a previously stored status"""
162 repo = self.repo
162 repo = self.repo
163 keepbranches = None
163 keepbranches = None
164 target = None
164 target = None
165 collapse = False
165 collapse = False
166 external = nullrev
166 external = nullrev
167 activebookmark = None
167 activebookmark = None
168 state = {}
168 state = {}
169
169
170 try:
170 try:
171 f = repo.vfs("rebasestate")
171 f = repo.vfs("rebasestate")
172 for i, l in enumerate(f.read().splitlines()):
172 for i, l in enumerate(f.read().splitlines()):
173 if i == 0:
173 if i == 0:
174 originalwd = repo[l].rev()
174 originalwd = repo[l].rev()
175 elif i == 1:
175 elif i == 1:
176 target = repo[l].rev()
176 target = repo[l].rev()
177 elif i == 2:
177 elif i == 2:
178 external = repo[l].rev()
178 external = repo[l].rev()
179 elif i == 3:
179 elif i == 3:
180 collapse = bool(int(l))
180 collapse = bool(int(l))
181 elif i == 4:
181 elif i == 4:
182 keep = bool(int(l))
182 keep = bool(int(l))
183 elif i == 5:
183 elif i == 5:
184 keepbranches = bool(int(l))
184 keepbranches = bool(int(l))
185 elif i == 6 and not (len(l) == 81 and ':' in l):
185 elif i == 6 and not (len(l) == 81 and ':' in l):
186 # line 6 is a recent addition, so for backwards
186 # line 6 is a recent addition, so for backwards
187 # compatibility check that the line doesn't look like the
187 # compatibility check that the line doesn't look like the
188 # oldrev:newrev lines
188 # oldrev:newrev lines
189 activebookmark = l
189 activebookmark = l
190 else:
190 else:
191 oldrev, newrev = l.split(':')
191 oldrev, newrev = l.split(':')
192 if newrev in (str(nullmerge), str(revignored),
192 if newrev in (str(nullmerge), str(revignored),
193 str(revprecursor), str(revpruned)):
193 str(revprecursor), str(revpruned)):
194 state[repo[oldrev].rev()] = int(newrev)
194 state[repo[oldrev].rev()] = int(newrev)
195 elif newrev == nullid:
195 elif newrev == nullid:
196 state[repo[oldrev].rev()] = revtodo
196 state[repo[oldrev].rev()] = revtodo
197 # Legacy compat special case
197 # Legacy compat special case
198 else:
198 else:
199 state[repo[oldrev].rev()] = repo[newrev].rev()
199 state[repo[oldrev].rev()] = repo[newrev].rev()
200
200
201 except IOError as err:
201 except IOError as err:
202 if err.errno != errno.ENOENT:
202 if err.errno != errno.ENOENT:
203 raise
203 raise
204 cmdutil.wrongtooltocontinue(repo, _('rebase'))
204 cmdutil.wrongtooltocontinue(repo, _('rebase'))
205
205
206 if keepbranches is None:
206 if keepbranches is None:
207 raise error.Abort(_('.hg/rebasestate is incomplete'))
207 raise error.Abort(_('.hg/rebasestate is incomplete'))
208
208
209 skipped = set()
209 skipped = set()
210 # recompute the set of skipped revs
210 # recompute the set of skipped revs
211 if not collapse:
211 if not collapse:
212 seen = set([target])
212 seen = set([target])
213 for old, new in sorted(state.items()):
213 for old, new in sorted(state.items()):
214 if new != revtodo and new in seen:
214 if new != revtodo and new in seen:
215 skipped.add(old)
215 skipped.add(old)
216 seen.add(new)
216 seen.add(new)
217 repo.ui.debug('computed skipped revs: %s\n' %
217 repo.ui.debug('computed skipped revs: %s\n' %
218 (' '.join(str(r) for r in sorted(skipped)) or None))
218 (' '.join(str(r) for r in sorted(skipped)) or None))
219 repo.ui.debug('rebase status resumed\n')
219 repo.ui.debug('rebase status resumed\n')
220 _setrebasesetvisibility(repo, state.keys())
220 _setrebasesetvisibility(repo, state.keys())
221
221
222 self.originalwd = originalwd
222 self.originalwd = originalwd
223 self.target = target
223 self.target = target
224 self.state = state
224 self.state = state
225 self.skipped = skipped
225 self.skipped = skipped
226 self.collapsef = collapse
226 self.collapsef = collapse
227 self.keepf = keep
227 self.keepf = keep
228 self.keepbranchesf = keepbranches
228 self.keepbranchesf = keepbranches
229 self.external = external
229 self.external = external
230 self.activebookmark = activebookmark
230 self.activebookmark = activebookmark
231
231
232 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
233 """Compute structures necessary for skipping obsolete revisions
234
235 rebaserevs: iterable of all revisions that are to be rebased
236 obsoleterevs: iterable of all obsolete revisions in rebaseset
237 target: a destination revision for the rebase operation
238 """
239 self.obsoletenotrebased = {}
240 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
241 default=True):
242 return
243 rebaseset = set(rebaserevs)
244 obsoleteset = set(obsoleterevs)
245 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
246 obsoleteset, target)
247 skippedset = set(self.obsoletenotrebased)
248 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
249
232 def _prepareabortorcontinue(self, isabort):
250 def _prepareabortorcontinue(self, isabort):
233 try:
251 try:
234 self.restorestatus()
252 self.restorestatus()
235 self.collapsemsg = restorecollapsemsg(self.repo)
253 self.collapsemsg = restorecollapsemsg(self.repo)
236 except error.RepoLookupError:
254 except error.RepoLookupError:
237 if isabort:
255 if isabort:
238 clearstatus(self.repo)
256 clearstatus(self.repo)
239 clearcollapsemsg(self.repo)
257 clearcollapsemsg(self.repo)
240 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
258 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
241 ' only broken state is cleared)\n'))
259 ' only broken state is cleared)\n'))
242 return 0
260 return 0
243 else:
261 else:
244 msg = _('cannot continue inconsistent rebase')
262 msg = _('cannot continue inconsistent rebase')
245 hint = _('use "hg rebase --abort" to clear broken state')
263 hint = _('use "hg rebase --abort" to clear broken state')
246 raise error.Abort(msg, hint=hint)
264 raise error.Abort(msg, hint=hint)
247 if isabort:
265 if isabort:
248 return abort(self.repo, self.originalwd, self.target,
266 return abort(self.repo, self.originalwd, self.target,
249 self.state, activebookmark=self.activebookmark)
267 self.state, activebookmark=self.activebookmark)
250
268
251 self.obsoletenotrebased = {}
269 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
252 if self.ui.configbool('experimental', 'rebaseskipobsolete',
270 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
253 default=True):
254 rebaseobsrevs = set([r for r, st in self.state.items()
255 if st == revprecursor])
256 rebasesetrevs = set(self.state.keys())
257 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
258 rebaseobsrevs, self.target)
259 rebaseobsskipped = set(self.obsoletenotrebased)
260 _checkobsrebase(self.repo, self.ui, rebaseobsrevs, rebasesetrevs,
261 rebaseobsskipped)
262
271
263 def _preparenewrebase(self, dest, rebaseset):
272 def _preparenewrebase(self, dest, rebaseset):
264 if dest is None:
273 if dest is None:
265 return _nothingtorebase()
274 return _nothingtorebase()
266
275
267 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
276 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
268 if (not (self.keepf or allowunstable)
277 if (not (self.keepf or allowunstable)
269 and self.repo.revs('first(children(%ld) - %ld)',
278 and self.repo.revs('first(children(%ld) - %ld)',
270 rebaseset, rebaseset)):
279 rebaseset, rebaseset)):
271 raise error.Abort(
280 raise error.Abort(
272 _("can't remove original changesets with"
281 _("can't remove original changesets with"
273 " unrebased descendants"),
282 " unrebased descendants"),
274 hint=_('use --keep to keep original changesets'))
283 hint=_('use --keep to keep original changesets'))
275
284
276 self.obsoletenotrebased = {}
285 obsrevs = _filterobsoleterevs(self.repo, rebaseset)
277 if self.ui.configbool('experimental', 'rebaseskipobsolete',
286 self._handleskippingobsolete(rebaseset, obsrevs, dest)
278 default=True):
279 rebasesetrevs = set(rebaseset)
280 rebaseobsrevs = _filterobsoleterevs(self.repo, rebasesetrevs)
281 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
282 rebaseobsrevs,
283 dest)
284 rebaseobsskipped = set(self.obsoletenotrebased)
285 _checkobsrebase(self.repo, self.ui, rebaseobsrevs,
286 rebasesetrevs,
287 rebaseobsskipped)
288
287
289 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
288 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
290 self.obsoletenotrebased)
289 self.obsoletenotrebased)
291
290
292 if not result:
291 if not result:
293 # Empty state built, nothing to rebase
292 # Empty state built, nothing to rebase
294 self.ui.status(_('nothing to rebase\n'))
293 self.ui.status(_('nothing to rebase\n'))
295 return _nothingtorebase()
294 return _nothingtorebase()
296
295
297 root = min(rebaseset)
296 root = min(rebaseset)
298 if not self.keepf and not self.repo[root].mutable():
297 if not self.keepf and not self.repo[root].mutable():
299 raise error.Abort(_("can't rebase public changeset %s")
298 raise error.Abort(_("can't rebase public changeset %s")
300 % self.repo[root],
299 % self.repo[root],
301 hint=_('see "hg help phases" for details'))
300 hint=_('see "hg help phases" for details'))
302
301
303 (self.originalwd, self.target, self.state) = result
302 (self.originalwd, self.target, self.state) = result
304 if self.collapsef:
303 if self.collapsef:
305 self.targetancestors = self.repo.changelog.ancestors(
304 self.targetancestors = self.repo.changelog.ancestors(
306 [self.target],
305 [self.target],
307 inclusive=True)
306 inclusive=True)
308 self.external = externalparent(self.repo, self.state,
307 self.external = externalparent(self.repo, self.state,
309 self.targetancestors)
308 self.targetancestors)
310
309
311 if dest.closesbranch() and not self.keepbranchesf:
310 if dest.closesbranch() and not self.keepbranchesf:
312 self.ui.status(_('reopening closed branch head %s\n') % dest)
311 self.ui.status(_('reopening closed branch head %s\n') % dest)
313
312
314 def _performrebase(self):
313 def _performrebase(self):
315 repo, ui, opts = self.repo, self.ui, self.opts
314 repo, ui, opts = self.repo, self.ui, self.opts
316 if self.keepbranchesf:
315 if self.keepbranchesf:
317 # insert _savebranch at the start of extrafns so if
316 # insert _savebranch at the start of extrafns so if
318 # there's a user-provided extrafn it can clobber branch if
317 # there's a user-provided extrafn it can clobber branch if
319 # desired
318 # desired
320 self.extrafns.insert(0, _savebranch)
319 self.extrafns.insert(0, _savebranch)
321 if self.collapsef:
320 if self.collapsef:
322 branches = set()
321 branches = set()
323 for rev in self.state:
322 for rev in self.state:
324 branches.add(repo[rev].branch())
323 branches.add(repo[rev].branch())
325 if len(branches) > 1:
324 if len(branches) > 1:
326 raise error.Abort(_('cannot collapse multiple named '
325 raise error.Abort(_('cannot collapse multiple named '
327 'branches'))
326 'branches'))
328
327
329 # Rebase
328 # Rebase
330 if not self.targetancestors:
329 if not self.targetancestors:
331 self.targetancestors = repo.changelog.ancestors([self.target],
330 self.targetancestors = repo.changelog.ancestors([self.target],
332 inclusive=True)
331 inclusive=True)
333
332
334 # Keep track of the current bookmarks in order to reset them later
333 # Keep track of the current bookmarks in order to reset them later
335 self.currentbookmarks = repo._bookmarks.copy()
334 self.currentbookmarks = repo._bookmarks.copy()
336 self.activebookmark = self.activebookmark or repo._activebookmark
335 self.activebookmark = self.activebookmark or repo._activebookmark
337 if self.activebookmark:
336 if self.activebookmark:
338 bookmarks.deactivate(repo)
337 bookmarks.deactivate(repo)
339
338
340 self.extrafn = _makeextrafn(self.extrafns)
339 self.extrafn = _makeextrafn(self.extrafns)
341
340
342 self.sortedstate = sorted(self.state)
341 self.sortedstate = sorted(self.state)
343 total = len(self.sortedstate)
342 total = len(self.sortedstate)
344 pos = 0
343 pos = 0
345 for rev in self.sortedstate:
344 for rev in self.sortedstate:
346 ctx = repo[rev]
345 ctx = repo[rev]
347 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
346 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
348 ctx.description().split('\n', 1)[0])
347 ctx.description().split('\n', 1)[0])
349 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
348 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
350 if names:
349 if names:
351 desc += ' (%s)' % ' '.join(names)
350 desc += ' (%s)' % ' '.join(names)
352 pos += 1
351 pos += 1
353 if self.state[rev] == revtodo:
352 if self.state[rev] == revtodo:
354 ui.status(_('rebasing %s\n') % desc)
353 ui.status(_('rebasing %s\n') % desc)
355 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
354 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
356 _('changesets'), total)
355 _('changesets'), total)
357 p1, p2, base = defineparents(repo, rev, self.target,
356 p1, p2, base = defineparents(repo, rev, self.target,
358 self.state,
357 self.state,
359 self.targetancestors,
358 self.targetancestors,
360 self.obsoletenotrebased)
359 self.obsoletenotrebased)
361 storestatus(repo, self.originalwd, self.target,
360 storestatus(repo, self.originalwd, self.target,
362 self.state, self.collapsef, self.keepf,
361 self.state, self.collapsef, self.keepf,
363 self.keepbranchesf, self.external,
362 self.keepbranchesf, self.external,
364 self.activebookmark)
363 self.activebookmark)
365 storecollapsemsg(repo, self.collapsemsg)
364 storecollapsemsg(repo, self.collapsemsg)
366 if len(repo[None].parents()) == 2:
365 if len(repo[None].parents()) == 2:
367 repo.ui.debug('resuming interrupted rebase\n')
366 repo.ui.debug('resuming interrupted rebase\n')
368 else:
367 else:
369 try:
368 try:
370 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
369 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
371 'rebase')
370 'rebase')
372 stats = rebasenode(repo, rev, p1, base, self.state,
371 stats = rebasenode(repo, rev, p1, base, self.state,
373 self.collapsef, self.target)
372 self.collapsef, self.target)
374 if stats and stats[3] > 0:
373 if stats and stats[3] > 0:
375 raise error.InterventionRequired(
374 raise error.InterventionRequired(
376 _('unresolved conflicts (see hg '
375 _('unresolved conflicts (see hg '
377 'resolve, then hg rebase --continue)'))
376 'resolve, then hg rebase --continue)'))
378 finally:
377 finally:
379 ui.setconfig('ui', 'forcemerge', '', 'rebase')
378 ui.setconfig('ui', 'forcemerge', '', 'rebase')
380 if not self.collapsef:
379 if not self.collapsef:
381 merging = p2 != nullrev
380 merging = p2 != nullrev
382 editform = cmdutil.mergeeditform(merging, 'rebase')
381 editform = cmdutil.mergeeditform(merging, 'rebase')
383 editor = cmdutil.getcommiteditor(editform=editform, **opts)
382 editor = cmdutil.getcommiteditor(editform=editform, **opts)
384 newnode = concludenode(repo, rev, p1, p2,
383 newnode = concludenode(repo, rev, p1, p2,
385 extrafn=self.extrafn,
384 extrafn=self.extrafn,
386 editor=editor,
385 editor=editor,
387 keepbranches=self.keepbranchesf,
386 keepbranches=self.keepbranchesf,
388 date=self.date)
387 date=self.date)
389 else:
388 else:
390 # Skip commit if we are collapsing
389 # Skip commit if we are collapsing
391 repo.dirstate.beginparentchange()
390 repo.dirstate.beginparentchange()
392 repo.setparents(repo[p1].node())
391 repo.setparents(repo[p1].node())
393 repo.dirstate.endparentchange()
392 repo.dirstate.endparentchange()
394 newnode = None
393 newnode = None
395 # Update the state
394 # Update the state
396 if newnode is not None:
395 if newnode is not None:
397 self.state[rev] = repo[newnode].rev()
396 self.state[rev] = repo[newnode].rev()
398 ui.debug('rebased as %s\n' % short(newnode))
397 ui.debug('rebased as %s\n' % short(newnode))
399 else:
398 else:
400 if not self.collapsef:
399 if not self.collapsef:
401 ui.warn(_('note: rebase of %d:%s created no changes '
400 ui.warn(_('note: rebase of %d:%s created no changes '
402 'to commit\n') % (rev, ctx))
401 'to commit\n') % (rev, ctx))
403 self.skipped.add(rev)
402 self.skipped.add(rev)
404 self.state[rev] = p1
403 self.state[rev] = p1
405 ui.debug('next revision set to %s\n' % p1)
404 ui.debug('next revision set to %s\n' % p1)
406 elif self.state[rev] == nullmerge:
405 elif self.state[rev] == nullmerge:
407 ui.debug('ignoring null merge rebase of %s\n' % rev)
406 ui.debug('ignoring null merge rebase of %s\n' % rev)
408 elif self.state[rev] == revignored:
407 elif self.state[rev] == revignored:
409 ui.status(_('not rebasing ignored %s\n') % desc)
408 ui.status(_('not rebasing ignored %s\n') % desc)
410 elif self.state[rev] == revprecursor:
409 elif self.state[rev] == revprecursor:
411 targetctx = repo[self.obsoletenotrebased[rev]]
410 targetctx = repo[self.obsoletenotrebased[rev]]
412 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
411 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
413 targetctx.description().split('\n', 1)[0])
412 targetctx.description().split('\n', 1)[0])
414 msg = _('note: not rebasing %s, already in destination as %s\n')
413 msg = _('note: not rebasing %s, already in destination as %s\n')
415 ui.status(msg % (desc, desctarget))
414 ui.status(msg % (desc, desctarget))
416 elif self.state[rev] == revpruned:
415 elif self.state[rev] == revpruned:
417 msg = _('note: not rebasing %s, it has no successor\n')
416 msg = _('note: not rebasing %s, it has no successor\n')
418 ui.status(msg % desc)
417 ui.status(msg % desc)
419 else:
418 else:
420 ui.status(_('already rebased %s as %s\n') %
419 ui.status(_('already rebased %s as %s\n') %
421 (desc, repo[self.state[rev]]))
420 (desc, repo[self.state[rev]]))
422
421
423 ui.progress(_('rebasing'), None)
422 ui.progress(_('rebasing'), None)
424 ui.note(_('rebase merging completed\n'))
423 ui.note(_('rebase merging completed\n'))
425
424
426 def _finishrebase(self):
425 def _finishrebase(self):
427 repo, ui, opts = self.repo, self.ui, self.opts
426 repo, ui, opts = self.repo, self.ui, self.opts
428 if self.collapsef and not self.keepopen:
427 if self.collapsef and not self.keepopen:
429 p1, p2, _base = defineparents(repo, min(self.state),
428 p1, p2, _base = defineparents(repo, min(self.state),
430 self.target, self.state,
429 self.target, self.state,
431 self.targetancestors,
430 self.targetancestors,
432 self.obsoletenotrebased)
431 self.obsoletenotrebased)
433 editopt = opts.get('edit')
432 editopt = opts.get('edit')
434 editform = 'rebase.collapse'
433 editform = 'rebase.collapse'
435 if self.collapsemsg:
434 if self.collapsemsg:
436 commitmsg = self.collapsemsg
435 commitmsg = self.collapsemsg
437 else:
436 else:
438 commitmsg = 'Collapsed revision'
437 commitmsg = 'Collapsed revision'
439 for rebased in self.state:
438 for rebased in self.state:
440 if rebased not in self.skipped and\
439 if rebased not in self.skipped and\
441 self.state[rebased] > nullmerge:
440 self.state[rebased] > nullmerge:
442 commitmsg += '\n* %s' % repo[rebased].description()
441 commitmsg += '\n* %s' % repo[rebased].description()
443 editopt = True
442 editopt = True
444 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
443 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
445 revtoreuse = self.sortedstate[-1]
444 revtoreuse = self.sortedstate[-1]
446 newnode = concludenode(repo, revtoreuse, p1, self.external,
445 newnode = concludenode(repo, revtoreuse, p1, self.external,
447 commitmsg=commitmsg,
446 commitmsg=commitmsg,
448 extrafn=self.extrafn, editor=editor,
447 extrafn=self.extrafn, editor=editor,
449 keepbranches=self.keepbranchesf,
448 keepbranches=self.keepbranchesf,
450 date=self.date)
449 date=self.date)
451 if newnode is None:
450 if newnode is None:
452 newrev = self.target
451 newrev = self.target
453 else:
452 else:
454 newrev = repo[newnode].rev()
453 newrev = repo[newnode].rev()
455 for oldrev in self.state.iterkeys():
454 for oldrev in self.state.iterkeys():
456 if self.state[oldrev] > nullmerge:
455 if self.state[oldrev] > nullmerge:
457 self.state[oldrev] = newrev
456 self.state[oldrev] = newrev
458
457
459 if 'qtip' in repo.tags():
458 if 'qtip' in repo.tags():
460 updatemq(repo, self.state, self.skipped, **opts)
459 updatemq(repo, self.state, self.skipped, **opts)
461
460
462 if self.currentbookmarks:
461 if self.currentbookmarks:
463 # Nodeids are needed to reset bookmarks
462 # Nodeids are needed to reset bookmarks
464 nstate = {}
463 nstate = {}
465 for k, v in self.state.iteritems():
464 for k, v in self.state.iteritems():
466 if v > nullmerge:
465 if v > nullmerge:
467 nstate[repo[k].node()] = repo[v].node()
466 nstate[repo[k].node()] = repo[v].node()
468 elif v == revprecursor:
467 elif v == revprecursor:
469 succ = self.obsoletenotrebased[k]
468 succ = self.obsoletenotrebased[k]
470 nstate[repo[k].node()] = repo[succ].node()
469 nstate[repo[k].node()] = repo[succ].node()
471 # XXX this is the same as dest.node() for the non-continue path --
470 # XXX this is the same as dest.node() for the non-continue path --
472 # this should probably be cleaned up
471 # this should probably be cleaned up
473 targetnode = repo[self.target].node()
472 targetnode = repo[self.target].node()
474
473
475 # restore original working directory
474 # restore original working directory
476 # (we do this before stripping)
475 # (we do this before stripping)
477 newwd = self.state.get(self.originalwd, self.originalwd)
476 newwd = self.state.get(self.originalwd, self.originalwd)
478 if newwd == revprecursor:
477 if newwd == revprecursor:
479 newwd = self.obsoletenotrebased[self.originalwd]
478 newwd = self.obsoletenotrebased[self.originalwd]
480 elif newwd < 0:
479 elif newwd < 0:
481 # original directory is a parent of rebase set root or ignored
480 # original directory is a parent of rebase set root or ignored
482 newwd = self.originalwd
481 newwd = self.originalwd
483 if newwd not in [c.rev() for c in repo[None].parents()]:
482 if newwd not in [c.rev() for c in repo[None].parents()]:
484 ui.note(_("update back to initial working directory parent\n"))
483 ui.note(_("update back to initial working directory parent\n"))
485 hg.updaterepo(repo, newwd, False)
484 hg.updaterepo(repo, newwd, False)
486
485
487 if not self.keepf:
486 if not self.keepf:
488 collapsedas = None
487 collapsedas = None
489 if self.collapsef:
488 if self.collapsef:
490 collapsedas = newnode
489 collapsedas = newnode
491 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
490 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
492
491
493 with repo.transaction('bookmark') as tr:
492 with repo.transaction('bookmark') as tr:
494 if self.currentbookmarks:
493 if self.currentbookmarks:
495 updatebookmarks(repo, targetnode, nstate,
494 updatebookmarks(repo, targetnode, nstate,
496 self.currentbookmarks, tr)
495 self.currentbookmarks, tr)
497 if self.activebookmark not in repo._bookmarks:
496 if self.activebookmark not in repo._bookmarks:
498 # active bookmark was divergent one and has been deleted
497 # active bookmark was divergent one and has been deleted
499 self.activebookmark = None
498 self.activebookmark = None
500 clearstatus(repo)
499 clearstatus(repo)
501 clearcollapsemsg(repo)
500 clearcollapsemsg(repo)
502
501
503 ui.note(_("rebase completed\n"))
502 ui.note(_("rebase completed\n"))
504 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
503 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
505 if self.skipped:
504 if self.skipped:
506 skippedlen = len(self.skipped)
505 skippedlen = len(self.skipped)
507 ui.note(_("%d revisions have been skipped\n") % skippedlen)
506 ui.note(_("%d revisions have been skipped\n") % skippedlen)
508
507
509 if (self.activebookmark and
508 if (self.activebookmark and
510 repo['.'].node() == repo._bookmarks[self.activebookmark]):
509 repo['.'].node() == repo._bookmarks[self.activebookmark]):
511 bookmarks.activate(repo, self.activebookmark)
510 bookmarks.activate(repo, self.activebookmark)
512
511
513 @command('rebase',
512 @command('rebase',
514 [('s', 'source', '',
513 [('s', 'source', '',
515 _('rebase the specified changeset and descendants'), _('REV')),
514 _('rebase the specified changeset and descendants'), _('REV')),
516 ('b', 'base', '',
515 ('b', 'base', '',
517 _('rebase everything from branching point of specified changeset'),
516 _('rebase everything from branching point of specified changeset'),
518 _('REV')),
517 _('REV')),
519 ('r', 'rev', [],
518 ('r', 'rev', [],
520 _('rebase these revisions'),
519 _('rebase these revisions'),
521 _('REV')),
520 _('REV')),
522 ('d', 'dest', '',
521 ('d', 'dest', '',
523 _('rebase onto the specified changeset'), _('REV')),
522 _('rebase onto the specified changeset'), _('REV')),
524 ('', 'collapse', False, _('collapse the rebased changesets')),
523 ('', 'collapse', False, _('collapse the rebased changesets')),
525 ('m', 'message', '',
524 ('m', 'message', '',
526 _('use text as collapse commit message'), _('TEXT')),
525 _('use text as collapse commit message'), _('TEXT')),
527 ('e', 'edit', False, _('invoke editor on commit messages')),
526 ('e', 'edit', False, _('invoke editor on commit messages')),
528 ('l', 'logfile', '',
527 ('l', 'logfile', '',
529 _('read collapse commit message from file'), _('FILE')),
528 _('read collapse commit message from file'), _('FILE')),
530 ('k', 'keep', False, _('keep original changesets')),
529 ('k', 'keep', False, _('keep original changesets')),
531 ('', 'keepbranches', False, _('keep original branch names')),
530 ('', 'keepbranches', False, _('keep original branch names')),
532 ('D', 'detach', False, _('(DEPRECATED)')),
531 ('D', 'detach', False, _('(DEPRECATED)')),
533 ('i', 'interactive', False, _('(DEPRECATED)')),
532 ('i', 'interactive', False, _('(DEPRECATED)')),
534 ('t', 'tool', '', _('specify merge tool')),
533 ('t', 'tool', '', _('specify merge tool')),
535 ('c', 'continue', False, _('continue an interrupted rebase')),
534 ('c', 'continue', False, _('continue an interrupted rebase')),
536 ('a', 'abort', False, _('abort an interrupted rebase'))] +
535 ('a', 'abort', False, _('abort an interrupted rebase'))] +
537 templateopts,
536 templateopts,
538 _('[-s REV | -b REV] [-d REV] [OPTION]'))
537 _('[-s REV | -b REV] [-d REV] [OPTION]'))
539 def rebase(ui, repo, **opts):
538 def rebase(ui, repo, **opts):
540 """move changeset (and descendants) to a different branch
539 """move changeset (and descendants) to a different branch
541
540
542 Rebase uses repeated merging to graft changesets from one part of
541 Rebase uses repeated merging to graft changesets from one part of
543 history (the source) onto another (the destination). This can be
542 history (the source) onto another (the destination). This can be
544 useful for linearizing *local* changes relative to a master
543 useful for linearizing *local* changes relative to a master
545 development tree.
544 development tree.
546
545
547 Published commits cannot be rebased (see :hg:`help phases`).
546 Published commits cannot be rebased (see :hg:`help phases`).
548 To copy commits, see :hg:`help graft`.
547 To copy commits, see :hg:`help graft`.
549
548
550 If you don't specify a destination changeset (``-d/--dest``), rebase
549 If you don't specify a destination changeset (``-d/--dest``), rebase
551 will use the same logic as :hg:`merge` to pick a destination. if
550 will use the same logic as :hg:`merge` to pick a destination. if
552 the current branch contains exactly one other head, the other head
551 the current branch contains exactly one other head, the other head
553 is merged with by default. Otherwise, an explicit revision with
552 is merged with by default. Otherwise, an explicit revision with
554 which to merge with must be provided. (destination changeset is not
553 which to merge with must be provided. (destination changeset is not
555 modified by rebasing, but new changesets are added as its
554 modified by rebasing, but new changesets are added as its
556 descendants.)
555 descendants.)
557
556
558 Here are the ways to select changesets:
557 Here are the ways to select changesets:
559
558
560 1. Explicitly select them using ``--rev``.
559 1. Explicitly select them using ``--rev``.
561
560
562 2. Use ``--source`` to select a root changeset and include all of its
561 2. Use ``--source`` to select a root changeset and include all of its
563 descendants.
562 descendants.
564
563
565 3. Use ``--base`` to select a changeset; rebase will find ancestors
564 3. Use ``--base`` to select a changeset; rebase will find ancestors
566 and their descendants which are not also ancestors of the destination.
565 and their descendants which are not also ancestors of the destination.
567
566
568 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
567 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
569 rebase will use ``--base .`` as above.
568 rebase will use ``--base .`` as above.
570
569
571 Rebase will destroy original changesets unless you use ``--keep``.
570 Rebase will destroy original changesets unless you use ``--keep``.
572 It will also move your bookmarks (even if you do).
571 It will also move your bookmarks (even if you do).
573
572
574 Some changesets may be dropped if they do not contribute changes
573 Some changesets may be dropped if they do not contribute changes
575 (e.g. merges from the destination branch).
574 (e.g. merges from the destination branch).
576
575
577 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
576 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
578 a named branch with two heads. You will need to explicitly specify source
577 a named branch with two heads. You will need to explicitly specify source
579 and/or destination.
578 and/or destination.
580
579
581 If you need to use a tool to automate merge/conflict decisions, you
580 If you need to use a tool to automate merge/conflict decisions, you
582 can specify one with ``--tool``, see :hg:`help merge-tools`.
581 can specify one with ``--tool``, see :hg:`help merge-tools`.
583 As a caveat: the tool will not be used to mediate when a file was
582 As a caveat: the tool will not be used to mediate when a file was
584 deleted, there is no hook presently available for this.
583 deleted, there is no hook presently available for this.
585
584
586 If a rebase is interrupted to manually resolve a conflict, it can be
585 If a rebase is interrupted to manually resolve a conflict, it can be
587 continued with --continue/-c or aborted with --abort/-a.
586 continued with --continue/-c or aborted with --abort/-a.
588
587
589 .. container:: verbose
588 .. container:: verbose
590
589
591 Examples:
590 Examples:
592
591
593 - move "local changes" (current commit back to branching point)
592 - move "local changes" (current commit back to branching point)
594 to the current branch tip after a pull::
593 to the current branch tip after a pull::
595
594
596 hg rebase
595 hg rebase
597
596
598 - move a single changeset to the stable branch::
597 - move a single changeset to the stable branch::
599
598
600 hg rebase -r 5f493448 -d stable
599 hg rebase -r 5f493448 -d stable
601
600
602 - splice a commit and all its descendants onto another part of history::
601 - splice a commit and all its descendants onto another part of history::
603
602
604 hg rebase --source c0c3 --dest 4cf9
603 hg rebase --source c0c3 --dest 4cf9
605
604
606 - rebase everything on a branch marked by a bookmark onto the
605 - rebase everything on a branch marked by a bookmark onto the
607 default branch::
606 default branch::
608
607
609 hg rebase --base myfeature --dest default
608 hg rebase --base myfeature --dest default
610
609
611 - collapse a sequence of changes into a single commit::
610 - collapse a sequence of changes into a single commit::
612
611
613 hg rebase --collapse -r 1520:1525 -d .
612 hg rebase --collapse -r 1520:1525 -d .
614
613
615 - move a named branch while preserving its name::
614 - move a named branch while preserving its name::
616
615
617 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
616 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
618
617
619 Returns 0 on success, 1 if nothing to rebase or there are
618 Returns 0 on success, 1 if nothing to rebase or there are
620 unresolved conflicts.
619 unresolved conflicts.
621
620
622 """
621 """
623 rbsrt = rebaseruntime(repo, ui, opts)
622 rbsrt = rebaseruntime(repo, ui, opts)
624
623
625 lock = wlock = None
624 lock = wlock = None
626 try:
625 try:
627 wlock = repo.wlock()
626 wlock = repo.wlock()
628 lock = repo.lock()
627 lock = repo.lock()
629
628
630 # Validate input and define rebasing points
629 # Validate input and define rebasing points
631 destf = opts.get('dest', None)
630 destf = opts.get('dest', None)
632 srcf = opts.get('source', None)
631 srcf = opts.get('source', None)
633 basef = opts.get('base', None)
632 basef = opts.get('base', None)
634 revf = opts.get('rev', [])
633 revf = opts.get('rev', [])
635 # search default destination in this space
634 # search default destination in this space
636 # used in the 'hg pull --rebase' case, see issue 5214.
635 # used in the 'hg pull --rebase' case, see issue 5214.
637 destspace = opts.get('_destspace')
636 destspace = opts.get('_destspace')
638 contf = opts.get('continue')
637 contf = opts.get('continue')
639 abortf = opts.get('abort')
638 abortf = opts.get('abort')
640 if opts.get('interactive'):
639 if opts.get('interactive'):
641 try:
640 try:
642 if extensions.find('histedit'):
641 if extensions.find('histedit'):
643 enablehistedit = ''
642 enablehistedit = ''
644 except KeyError:
643 except KeyError:
645 enablehistedit = " --config extensions.histedit="
644 enablehistedit = " --config extensions.histedit="
646 help = "hg%s help -e histedit" % enablehistedit
645 help = "hg%s help -e histedit" % enablehistedit
647 msg = _("interactive history editing is supported by the "
646 msg = _("interactive history editing is supported by the "
648 "'histedit' extension (see \"%s\")") % help
647 "'histedit' extension (see \"%s\")") % help
649 raise error.Abort(msg)
648 raise error.Abort(msg)
650
649
651 if rbsrt.collapsemsg and not rbsrt.collapsef:
650 if rbsrt.collapsemsg and not rbsrt.collapsef:
652 raise error.Abort(
651 raise error.Abort(
653 _('message can only be specified with collapse'))
652 _('message can only be specified with collapse'))
654
653
655 if contf or abortf:
654 if contf or abortf:
656 if contf and abortf:
655 if contf and abortf:
657 raise error.Abort(_('cannot use both abort and continue'))
656 raise error.Abort(_('cannot use both abort and continue'))
658 if rbsrt.collapsef:
657 if rbsrt.collapsef:
659 raise error.Abort(
658 raise error.Abort(
660 _('cannot use collapse with continue or abort'))
659 _('cannot use collapse with continue or abort'))
661 if srcf or basef or destf:
660 if srcf or basef or destf:
662 raise error.Abort(
661 raise error.Abort(
663 _('abort and continue do not allow specifying revisions'))
662 _('abort and continue do not allow specifying revisions'))
664 if abortf and opts.get('tool', False):
663 if abortf and opts.get('tool', False):
665 ui.warn(_('tool option will be ignored\n'))
664 ui.warn(_('tool option will be ignored\n'))
666
665
667 retcode = rbsrt._prepareabortorcontinue(abortf)
666 retcode = rbsrt._prepareabortorcontinue(abortf)
668 if retcode is not None:
667 if retcode is not None:
669 return retcode
668 return retcode
670 else:
669 else:
671 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
670 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
672 destspace=destspace)
671 destspace=destspace)
673 retcode = rbsrt._preparenewrebase(dest, rebaseset)
672 retcode = rbsrt._preparenewrebase(dest, rebaseset)
674 if retcode is not None:
673 if retcode is not None:
675 return retcode
674 return retcode
676
675
677 rbsrt._performrebase()
676 rbsrt._performrebase()
678 rbsrt._finishrebase()
677 rbsrt._finishrebase()
679 finally:
678 finally:
680 release(lock, wlock)
679 release(lock, wlock)
681
680
682 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[],
681 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[],
683 destspace=None):
682 destspace=None):
684 """use revisions argument to define destination and rebase set
683 """use revisions argument to define destination and rebase set
685 """
684 """
686 # destspace is here to work around issues with `hg pull --rebase` see
685 # destspace is here to work around issues with `hg pull --rebase` see
687 # issue5214 for details
686 # issue5214 for details
688 if srcf and basef:
687 if srcf and basef:
689 raise error.Abort(_('cannot specify both a source and a base'))
688 raise error.Abort(_('cannot specify both a source and a base'))
690 if revf and basef:
689 if revf and basef:
691 raise error.Abort(_('cannot specify both a revision and a base'))
690 raise error.Abort(_('cannot specify both a revision and a base'))
692 if revf and srcf:
691 if revf and srcf:
693 raise error.Abort(_('cannot specify both a revision and a source'))
692 raise error.Abort(_('cannot specify both a revision and a source'))
694
693
695 cmdutil.checkunfinished(repo)
694 cmdutil.checkunfinished(repo)
696 cmdutil.bailifchanged(repo)
695 cmdutil.bailifchanged(repo)
697
696
698 if destf:
697 if destf:
699 dest = scmutil.revsingle(repo, destf)
698 dest = scmutil.revsingle(repo, destf)
700
699
701 if revf:
700 if revf:
702 rebaseset = scmutil.revrange(repo, revf)
701 rebaseset = scmutil.revrange(repo, revf)
703 if not rebaseset:
702 if not rebaseset:
704 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
703 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
705 return None, None
704 return None, None
706 elif srcf:
705 elif srcf:
707 src = scmutil.revrange(repo, [srcf])
706 src = scmutil.revrange(repo, [srcf])
708 if not src:
707 if not src:
709 ui.status(_('empty "source" revision set - nothing to rebase\n'))
708 ui.status(_('empty "source" revision set - nothing to rebase\n'))
710 return None, None
709 return None, None
711 rebaseset = repo.revs('(%ld)::', src)
710 rebaseset = repo.revs('(%ld)::', src)
712 assert rebaseset
711 assert rebaseset
713 else:
712 else:
714 base = scmutil.revrange(repo, [basef or '.'])
713 base = scmutil.revrange(repo, [basef or '.'])
715 if not base:
714 if not base:
716 ui.status(_('empty "base" revision set - '
715 ui.status(_('empty "base" revision set - '
717 "can't compute rebase set\n"))
716 "can't compute rebase set\n"))
718 return None, None
717 return None, None
719 if not destf:
718 if not destf:
720 dest = repo[_destrebase(repo, base, destspace=destspace)]
719 dest = repo[_destrebase(repo, base, destspace=destspace)]
721 destf = str(dest)
720 destf = str(dest)
722
721
723 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
722 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
724 if commonanc is not None:
723 if commonanc is not None:
725 rebaseset = repo.revs('(%d::(%ld) - %d)::',
724 rebaseset = repo.revs('(%d::(%ld) - %d)::',
726 commonanc, base, commonanc)
725 commonanc, base, commonanc)
727 else:
726 else:
728 rebaseset = []
727 rebaseset = []
729
728
730 if not rebaseset:
729 if not rebaseset:
731 # transform to list because smartsets are not comparable to
730 # transform to list because smartsets are not comparable to
732 # lists. This should be improved to honor laziness of
731 # lists. This should be improved to honor laziness of
733 # smartset.
732 # smartset.
734 if list(base) == [dest.rev()]:
733 if list(base) == [dest.rev()]:
735 if basef:
734 if basef:
736 ui.status(_('nothing to rebase - %s is both "base"'
735 ui.status(_('nothing to rebase - %s is both "base"'
737 ' and destination\n') % dest)
736 ' and destination\n') % dest)
738 else:
737 else:
739 ui.status(_('nothing to rebase - working directory '
738 ui.status(_('nothing to rebase - working directory '
740 'parent is also destination\n'))
739 'parent is also destination\n'))
741 elif not repo.revs('%ld - ::%d', base, dest):
740 elif not repo.revs('%ld - ::%d', base, dest):
742 if basef:
741 if basef:
743 ui.status(_('nothing to rebase - "base" %s is '
742 ui.status(_('nothing to rebase - "base" %s is '
744 'already an ancestor of destination '
743 'already an ancestor of destination '
745 '%s\n') %
744 '%s\n') %
746 ('+'.join(str(repo[r]) for r in base),
745 ('+'.join(str(repo[r]) for r in base),
747 dest))
746 dest))
748 else:
747 else:
749 ui.status(_('nothing to rebase - working '
748 ui.status(_('nothing to rebase - working '
750 'directory parent is already an '
749 'directory parent is already an '
751 'ancestor of destination %s\n') % dest)
750 'ancestor of destination %s\n') % dest)
752 else: # can it happen?
751 else: # can it happen?
753 ui.status(_('nothing to rebase from %s to %s\n') %
752 ui.status(_('nothing to rebase from %s to %s\n') %
754 ('+'.join(str(repo[r]) for r in base), dest))
753 ('+'.join(str(repo[r]) for r in base), dest))
755 return None, None
754 return None, None
756
755
757 if not destf:
756 if not destf:
758 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
757 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
759 destf = str(dest)
758 destf = str(dest)
760
759
761 return dest, rebaseset
760 return dest, rebaseset
762
761
763 def externalparent(repo, state, targetancestors):
762 def externalparent(repo, state, targetancestors):
764 """Return the revision that should be used as the second parent
763 """Return the revision that should be used as the second parent
765 when the revisions in state is collapsed on top of targetancestors.
764 when the revisions in state is collapsed on top of targetancestors.
766 Abort if there is more than one parent.
765 Abort if there is more than one parent.
767 """
766 """
768 parents = set()
767 parents = set()
769 source = min(state)
768 source = min(state)
770 for rev in state:
769 for rev in state:
771 if rev == source:
770 if rev == source:
772 continue
771 continue
773 for p in repo[rev].parents():
772 for p in repo[rev].parents():
774 if (p.rev() not in state
773 if (p.rev() not in state
775 and p.rev() not in targetancestors):
774 and p.rev() not in targetancestors):
776 parents.add(p.rev())
775 parents.add(p.rev())
777 if not parents:
776 if not parents:
778 return nullrev
777 return nullrev
779 if len(parents) == 1:
778 if len(parents) == 1:
780 return parents.pop()
779 return parents.pop()
781 raise error.Abort(_('unable to collapse on top of %s, there is more '
780 raise error.Abort(_('unable to collapse on top of %s, there is more '
782 'than one external parent: %s') %
781 'than one external parent: %s') %
783 (max(targetancestors),
782 (max(targetancestors),
784 ', '.join(str(p) for p in sorted(parents))))
783 ', '.join(str(p) for p in sorted(parents))))
785
784
786 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
785 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
787 keepbranches=False, date=None):
786 keepbranches=False, date=None):
788 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
787 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
789 but also store useful information in extra.
788 but also store useful information in extra.
790 Return node of committed revision.'''
789 Return node of committed revision.'''
791 dsguard = cmdutil.dirstateguard(repo, 'rebase')
790 dsguard = cmdutil.dirstateguard(repo, 'rebase')
792 try:
791 try:
793 repo.setparents(repo[p1].node(), repo[p2].node())
792 repo.setparents(repo[p1].node(), repo[p2].node())
794 ctx = repo[rev]
793 ctx = repo[rev]
795 if commitmsg is None:
794 if commitmsg is None:
796 commitmsg = ctx.description()
795 commitmsg = ctx.description()
797 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
796 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
798 extra = {'rebase_source': ctx.hex()}
797 extra = {'rebase_source': ctx.hex()}
799 if extrafn:
798 if extrafn:
800 extrafn(ctx, extra)
799 extrafn(ctx, extra)
801
800
802 backup = repo.ui.backupconfig('phases', 'new-commit')
801 backup = repo.ui.backupconfig('phases', 'new-commit')
803 try:
802 try:
804 targetphase = max(ctx.phase(), phases.draft)
803 targetphase = max(ctx.phase(), phases.draft)
805 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
804 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
806 if keepbranch:
805 if keepbranch:
807 repo.ui.setconfig('ui', 'allowemptycommit', True)
806 repo.ui.setconfig('ui', 'allowemptycommit', True)
808 # Commit might fail if unresolved files exist
807 # Commit might fail if unresolved files exist
809 if date is None:
808 if date is None:
810 date = ctx.date()
809 date = ctx.date()
811 newnode = repo.commit(text=commitmsg, user=ctx.user(),
810 newnode = repo.commit(text=commitmsg, user=ctx.user(),
812 date=date, extra=extra, editor=editor)
811 date=date, extra=extra, editor=editor)
813 finally:
812 finally:
814 repo.ui.restoreconfig(backup)
813 repo.ui.restoreconfig(backup)
815
814
816 repo.dirstate.setbranch(repo[newnode].branch())
815 repo.dirstate.setbranch(repo[newnode].branch())
817 dsguard.close()
816 dsguard.close()
818 return newnode
817 return newnode
819 finally:
818 finally:
820 release(dsguard)
819 release(dsguard)
821
820
822 def rebasenode(repo, rev, p1, base, state, collapse, target):
821 def rebasenode(repo, rev, p1, base, state, collapse, target):
823 'Rebase a single revision rev on top of p1 using base as merge ancestor'
822 'Rebase a single revision rev on top of p1 using base as merge ancestor'
824 # Merge phase
823 # Merge phase
825 # Update to target and merge it with local
824 # Update to target and merge it with local
826 if repo['.'].rev() != p1:
825 if repo['.'].rev() != p1:
827 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
826 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
828 merge.update(repo, p1, False, True)
827 merge.update(repo, p1, False, True)
829 else:
828 else:
830 repo.ui.debug(" already in target\n")
829 repo.ui.debug(" already in target\n")
831 repo.dirstate.write(repo.currenttransaction())
830 repo.dirstate.write(repo.currenttransaction())
832 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
831 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
833 if base is not None:
832 if base is not None:
834 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
833 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
835 # When collapsing in-place, the parent is the common ancestor, we
834 # When collapsing in-place, the parent is the common ancestor, we
836 # have to allow merging with it.
835 # have to allow merging with it.
837 stats = merge.update(repo, rev, True, True, base, collapse,
836 stats = merge.update(repo, rev, True, True, base, collapse,
838 labels=['dest', 'source'])
837 labels=['dest', 'source'])
839 if collapse:
838 if collapse:
840 copies.duplicatecopies(repo, rev, target)
839 copies.duplicatecopies(repo, rev, target)
841 else:
840 else:
842 # If we're not using --collapse, we need to
841 # If we're not using --collapse, we need to
843 # duplicate copies between the revision we're
842 # duplicate copies between the revision we're
844 # rebasing and its first parent, but *not*
843 # rebasing and its first parent, but *not*
845 # duplicate any copies that have already been
844 # duplicate any copies that have already been
846 # performed in the destination.
845 # performed in the destination.
847 p1rev = repo[rev].p1().rev()
846 p1rev = repo[rev].p1().rev()
848 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
847 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
849 return stats
848 return stats
850
849
851 def nearestrebased(repo, rev, state):
850 def nearestrebased(repo, rev, state):
852 """return the nearest ancestors of rev in the rebase result"""
851 """return the nearest ancestors of rev in the rebase result"""
853 rebased = [r for r in state if state[r] > nullmerge]
852 rebased = [r for r in state if state[r] > nullmerge]
854 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
853 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
855 if candidates:
854 if candidates:
856 return state[candidates.first()]
855 return state[candidates.first()]
857 else:
856 else:
858 return None
857 return None
859
858
860 def _checkobsrebase(repo, ui,
859 def _checkobsrebase(repo, ui,
861 rebaseobsrevs,
860 rebaseobsrevs,
862 rebasesetrevs,
861 rebasesetrevs,
863 rebaseobsskipped):
862 rebaseobsskipped):
864 """
863 """
865 Abort if rebase will create divergence or rebase is noop because of markers
864 Abort if rebase will create divergence or rebase is noop because of markers
866
865
867 `rebaseobsrevs`: set of obsolete revision in source
866 `rebaseobsrevs`: set of obsolete revision in source
868 `rebasesetrevs`: set of revisions to be rebased from source
867 `rebasesetrevs`: set of revisions to be rebased from source
869 `rebaseobsskipped`: set of revisions from source skipped because they have
868 `rebaseobsskipped`: set of revisions from source skipped because they have
870 successors in destination
869 successors in destination
871 """
870 """
872 # Obsolete node with successors not in dest leads to divergence
871 # Obsolete node with successors not in dest leads to divergence
873 divergenceok = ui.configbool('experimental',
872 divergenceok = ui.configbool('experimental',
874 'allowdivergence')
873 'allowdivergence')
875 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
874 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
876
875
877 if divergencebasecandidates and not divergenceok:
876 if divergencebasecandidates and not divergenceok:
878 divhashes = (str(repo[r])
877 divhashes = (str(repo[r])
879 for r in divergencebasecandidates)
878 for r in divergencebasecandidates)
880 msg = _("this rebase will cause "
879 msg = _("this rebase will cause "
881 "divergences from: %s")
880 "divergences from: %s")
882 h = _("to force the rebase please set "
881 h = _("to force the rebase please set "
883 "experimental.allowdivergence=True")
882 "experimental.allowdivergence=True")
884 raise error.Abort(msg % (",".join(divhashes),), hint=h)
883 raise error.Abort(msg % (",".join(divhashes),), hint=h)
885
884
886 def defineparents(repo, rev, target, state, targetancestors,
885 def defineparents(repo, rev, target, state, targetancestors,
887 obsoletenotrebased):
886 obsoletenotrebased):
888 'Return the new parent relationship of the revision that will be rebased'
887 'Return the new parent relationship of the revision that will be rebased'
889 parents = repo[rev].parents()
888 parents = repo[rev].parents()
890 p1 = p2 = nullrev
889 p1 = p2 = nullrev
891 rp1 = None
890 rp1 = None
892
891
893 p1n = parents[0].rev()
892 p1n = parents[0].rev()
894 if p1n in targetancestors:
893 if p1n in targetancestors:
895 p1 = target
894 p1 = target
896 elif p1n in state:
895 elif p1n in state:
897 if state[p1n] == nullmerge:
896 if state[p1n] == nullmerge:
898 p1 = target
897 p1 = target
899 elif state[p1n] in revskipped:
898 elif state[p1n] in revskipped:
900 p1 = nearestrebased(repo, p1n, state)
899 p1 = nearestrebased(repo, p1n, state)
901 if p1 is None:
900 if p1 is None:
902 p1 = target
901 p1 = target
903 else:
902 else:
904 p1 = state[p1n]
903 p1 = state[p1n]
905 else: # p1n external
904 else: # p1n external
906 p1 = target
905 p1 = target
907 p2 = p1n
906 p2 = p1n
908
907
909 if len(parents) == 2 and parents[1].rev() not in targetancestors:
908 if len(parents) == 2 and parents[1].rev() not in targetancestors:
910 p2n = parents[1].rev()
909 p2n = parents[1].rev()
911 # interesting second parent
910 # interesting second parent
912 if p2n in state:
911 if p2n in state:
913 if p1 == target: # p1n in targetancestors or external
912 if p1 == target: # p1n in targetancestors or external
914 p1 = state[p2n]
913 p1 = state[p2n]
915 if p1 == revprecursor:
914 if p1 == revprecursor:
916 rp1 = obsoletenotrebased[p2n]
915 rp1 = obsoletenotrebased[p2n]
917 elif state[p2n] in revskipped:
916 elif state[p2n] in revskipped:
918 p2 = nearestrebased(repo, p2n, state)
917 p2 = nearestrebased(repo, p2n, state)
919 if p2 is None:
918 if p2 is None:
920 # no ancestors rebased yet, detach
919 # no ancestors rebased yet, detach
921 p2 = target
920 p2 = target
922 else:
921 else:
923 p2 = state[p2n]
922 p2 = state[p2n]
924 else: # p2n external
923 else: # p2n external
925 if p2 != nullrev: # p1n external too => rev is a merged revision
924 if p2 != nullrev: # p1n external too => rev is a merged revision
926 raise error.Abort(_('cannot use revision %d as base, result '
925 raise error.Abort(_('cannot use revision %d as base, result '
927 'would have 3 parents') % rev)
926 'would have 3 parents') % rev)
928 p2 = p2n
927 p2 = p2n
929 repo.ui.debug(" future parents are %d and %d\n" %
928 repo.ui.debug(" future parents are %d and %d\n" %
930 (repo[rp1 or p1].rev(), repo[p2].rev()))
929 (repo[rp1 or p1].rev(), repo[p2].rev()))
931
930
932 if not any(p.rev() in state for p in parents):
931 if not any(p.rev() in state for p in parents):
933 # Case (1) root changeset of a non-detaching rebase set.
932 # Case (1) root changeset of a non-detaching rebase set.
934 # Let the merge mechanism find the base itself.
933 # Let the merge mechanism find the base itself.
935 base = None
934 base = None
936 elif not repo[rev].p2():
935 elif not repo[rev].p2():
937 # Case (2) detaching the node with a single parent, use this parent
936 # Case (2) detaching the node with a single parent, use this parent
938 base = repo[rev].p1().rev()
937 base = repo[rev].p1().rev()
939 else:
938 else:
940 # Assuming there is a p1, this is the case where there also is a p2.
939 # Assuming there is a p1, this is the case where there also is a p2.
941 # We are thus rebasing a merge and need to pick the right merge base.
940 # We are thus rebasing a merge and need to pick the right merge base.
942 #
941 #
943 # Imagine we have:
942 # Imagine we have:
944 # - M: current rebase revision in this step
943 # - M: current rebase revision in this step
945 # - A: one parent of M
944 # - A: one parent of M
946 # - B: other parent of M
945 # - B: other parent of M
947 # - D: destination of this merge step (p1 var)
946 # - D: destination of this merge step (p1 var)
948 #
947 #
949 # Consider the case where D is a descendant of A or B and the other is
948 # Consider the case where D is a descendant of A or B and the other is
950 # 'outside'. In this case, the right merge base is the D ancestor.
949 # 'outside'. In this case, the right merge base is the D ancestor.
951 #
950 #
952 # An informal proof, assuming A is 'outside' and B is the D ancestor:
951 # An informal proof, assuming A is 'outside' and B is the D ancestor:
953 #
952 #
954 # If we pick B as the base, the merge involves:
953 # If we pick B as the base, the merge involves:
955 # - changes from B to M (actual changeset payload)
954 # - changes from B to M (actual changeset payload)
956 # - changes from B to D (induced by rebase) as D is a rebased
955 # - changes from B to D (induced by rebase) as D is a rebased
957 # version of B)
956 # version of B)
958 # Which exactly represent the rebase operation.
957 # Which exactly represent the rebase operation.
959 #
958 #
960 # If we pick A as the base, the merge involves:
959 # If we pick A as the base, the merge involves:
961 # - changes from A to M (actual changeset payload)
960 # - changes from A to M (actual changeset payload)
962 # - changes from A to D (with include changes between unrelated A and B
961 # - changes from A to D (with include changes between unrelated A and B
963 # plus changes induced by rebase)
962 # plus changes induced by rebase)
964 # Which does not represent anything sensible and creates a lot of
963 # Which does not represent anything sensible and creates a lot of
965 # conflicts. A is thus not the right choice - B is.
964 # conflicts. A is thus not the right choice - B is.
966 #
965 #
967 # Note: The base found in this 'proof' is only correct in the specified
966 # Note: The base found in this 'proof' is only correct in the specified
968 # case. This base does not make sense if is not D a descendant of A or B
967 # case. This base does not make sense if is not D a descendant of A or B
969 # or if the other is not parent 'outside' (especially not if the other
968 # or if the other is not parent 'outside' (especially not if the other
970 # parent has been rebased). The current implementation does not
969 # parent has been rebased). The current implementation does not
971 # make it feasible to consider different cases separately. In these
970 # make it feasible to consider different cases separately. In these
972 # other cases we currently just leave it to the user to correctly
971 # other cases we currently just leave it to the user to correctly
973 # resolve an impossible merge using a wrong ancestor.
972 # resolve an impossible merge using a wrong ancestor.
974 #
973 #
975 # xx, p1 could be -4, and both parents could probably be -4...
974 # xx, p1 could be -4, and both parents could probably be -4...
976 for p in repo[rev].parents():
975 for p in repo[rev].parents():
977 if state.get(p.rev()) == p1:
976 if state.get(p.rev()) == p1:
978 base = p.rev()
977 base = p.rev()
979 break
978 break
980 else: # fallback when base not found
979 else: # fallback when base not found
981 base = None
980 base = None
982
981
983 # Raise because this function is called wrong (see issue 4106)
982 # Raise because this function is called wrong (see issue 4106)
984 raise AssertionError('no base found to rebase on '
983 raise AssertionError('no base found to rebase on '
985 '(defineparents called wrong)')
984 '(defineparents called wrong)')
986 return rp1 or p1, p2, base
985 return rp1 or p1, p2, base
987
986
988 def isagitpatch(repo, patchname):
987 def isagitpatch(repo, patchname):
989 'Return true if the given patch is in git format'
988 'Return true if the given patch is in git format'
990 mqpatch = os.path.join(repo.mq.path, patchname)
989 mqpatch = os.path.join(repo.mq.path, patchname)
991 for line in patch.linereader(file(mqpatch, 'rb')):
990 for line in patch.linereader(file(mqpatch, 'rb')):
992 if line.startswith('diff --git'):
991 if line.startswith('diff --git'):
993 return True
992 return True
994 return False
993 return False
995
994
996 def updatemq(repo, state, skipped, **opts):
995 def updatemq(repo, state, skipped, **opts):
997 'Update rebased mq patches - finalize and then import them'
996 'Update rebased mq patches - finalize and then import them'
998 mqrebase = {}
997 mqrebase = {}
999 mq = repo.mq
998 mq = repo.mq
1000 original_series = mq.fullseries[:]
999 original_series = mq.fullseries[:]
1001 skippedpatches = set()
1000 skippedpatches = set()
1002
1001
1003 for p in mq.applied:
1002 for p in mq.applied:
1004 rev = repo[p.node].rev()
1003 rev = repo[p.node].rev()
1005 if rev in state:
1004 if rev in state:
1006 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1005 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1007 (rev, p.name))
1006 (rev, p.name))
1008 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1007 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1009 else:
1008 else:
1010 # Applied but not rebased, not sure this should happen
1009 # Applied but not rebased, not sure this should happen
1011 skippedpatches.add(p.name)
1010 skippedpatches.add(p.name)
1012
1011
1013 if mqrebase:
1012 if mqrebase:
1014 mq.finish(repo, mqrebase.keys())
1013 mq.finish(repo, mqrebase.keys())
1015
1014
1016 # We must start import from the newest revision
1015 # We must start import from the newest revision
1017 for rev in sorted(mqrebase, reverse=True):
1016 for rev in sorted(mqrebase, reverse=True):
1018 if rev not in skipped:
1017 if rev not in skipped:
1019 name, isgit = mqrebase[rev]
1018 name, isgit = mqrebase[rev]
1020 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1019 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1021 (name, state[rev], repo[state[rev]]))
1020 (name, state[rev], repo[state[rev]]))
1022 mq.qimport(repo, (), patchname=name, git=isgit,
1021 mq.qimport(repo, (), patchname=name, git=isgit,
1023 rev=[str(state[rev])])
1022 rev=[str(state[rev])])
1024 else:
1023 else:
1025 # Rebased and skipped
1024 # Rebased and skipped
1026 skippedpatches.add(mqrebase[rev][0])
1025 skippedpatches.add(mqrebase[rev][0])
1027
1026
1028 # Patches were either applied and rebased and imported in
1027 # Patches were either applied and rebased and imported in
1029 # order, applied and removed or unapplied. Discard the removed
1028 # order, applied and removed or unapplied. Discard the removed
1030 # ones while preserving the original series order and guards.
1029 # ones while preserving the original series order and guards.
1031 newseries = [s for s in original_series
1030 newseries = [s for s in original_series
1032 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1031 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1033 mq.fullseries[:] = newseries
1032 mq.fullseries[:] = newseries
1034 mq.seriesdirty = True
1033 mq.seriesdirty = True
1035 mq.savedirty()
1034 mq.savedirty()
1036
1035
1037 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1036 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1038 'Move bookmarks to their correct changesets, and delete divergent ones'
1037 'Move bookmarks to their correct changesets, and delete divergent ones'
1039 marks = repo._bookmarks
1038 marks = repo._bookmarks
1040 for k, v in originalbookmarks.iteritems():
1039 for k, v in originalbookmarks.iteritems():
1041 if v in nstate:
1040 if v in nstate:
1042 # update the bookmarks for revs that have moved
1041 # update the bookmarks for revs that have moved
1043 marks[k] = nstate[v]
1042 marks[k] = nstate[v]
1044 bookmarks.deletedivergent(repo, [targetnode], k)
1043 bookmarks.deletedivergent(repo, [targetnode], k)
1045 marks.recordchange(tr)
1044 marks.recordchange(tr)
1046
1045
1047 def storecollapsemsg(repo, collapsemsg):
1046 def storecollapsemsg(repo, collapsemsg):
1048 'Store the collapse message to allow recovery'
1047 'Store the collapse message to allow recovery'
1049 collapsemsg = collapsemsg or ''
1048 collapsemsg = collapsemsg or ''
1050 f = repo.vfs("last-message.txt", "w")
1049 f = repo.vfs("last-message.txt", "w")
1051 f.write("%s\n" % collapsemsg)
1050 f.write("%s\n" % collapsemsg)
1052 f.close()
1051 f.close()
1053
1052
1054 def clearcollapsemsg(repo):
1053 def clearcollapsemsg(repo):
1055 'Remove collapse message file'
1054 'Remove collapse message file'
1056 util.unlinkpath(repo.join("last-message.txt"), ignoremissing=True)
1055 util.unlinkpath(repo.join("last-message.txt"), ignoremissing=True)
1057
1056
1058 def restorecollapsemsg(repo):
1057 def restorecollapsemsg(repo):
1059 'Restore previously stored collapse message'
1058 'Restore previously stored collapse message'
1060 try:
1059 try:
1061 f = repo.vfs("last-message.txt")
1060 f = repo.vfs("last-message.txt")
1062 collapsemsg = f.readline().strip()
1061 collapsemsg = f.readline().strip()
1063 f.close()
1062 f.close()
1064 except IOError as err:
1063 except IOError as err:
1065 if err.errno != errno.ENOENT:
1064 if err.errno != errno.ENOENT:
1066 raise
1065 raise
1067 raise error.Abort(_('no rebase in progress'))
1066 raise error.Abort(_('no rebase in progress'))
1068 return collapsemsg
1067 return collapsemsg
1069
1068
1070 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
1069 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
1071 external, activebookmark):
1070 external, activebookmark):
1072 'Store the current status to allow recovery'
1071 'Store the current status to allow recovery'
1073 f = repo.vfs("rebasestate", "w")
1072 f = repo.vfs("rebasestate", "w")
1074 f.write(repo[originalwd].hex() + '\n')
1073 f.write(repo[originalwd].hex() + '\n')
1075 f.write(repo[target].hex() + '\n')
1074 f.write(repo[target].hex() + '\n')
1076 f.write(repo[external].hex() + '\n')
1075 f.write(repo[external].hex() + '\n')
1077 f.write('%d\n' % int(collapse))
1076 f.write('%d\n' % int(collapse))
1078 f.write('%d\n' % int(keep))
1077 f.write('%d\n' % int(keep))
1079 f.write('%d\n' % int(keepbranches))
1078 f.write('%d\n' % int(keepbranches))
1080 f.write('%s\n' % (activebookmark or ''))
1079 f.write('%s\n' % (activebookmark or ''))
1081 for d, v in state.iteritems():
1080 for d, v in state.iteritems():
1082 oldrev = repo[d].hex()
1081 oldrev = repo[d].hex()
1083 if v >= 0:
1082 if v >= 0:
1084 newrev = repo[v].hex()
1083 newrev = repo[v].hex()
1085 elif v == revtodo:
1084 elif v == revtodo:
1086 # To maintain format compatibility, we have to use nullid.
1085 # To maintain format compatibility, we have to use nullid.
1087 # Please do remove this special case when upgrading the format.
1086 # Please do remove this special case when upgrading the format.
1088 newrev = hex(nullid)
1087 newrev = hex(nullid)
1089 else:
1088 else:
1090 newrev = v
1089 newrev = v
1091 f.write("%s:%s\n" % (oldrev, newrev))
1090 f.write("%s:%s\n" % (oldrev, newrev))
1092 f.close()
1091 f.close()
1093 repo.ui.debug('rebase status stored\n')
1092 repo.ui.debug('rebase status stored\n')
1094
1093
1095 def clearstatus(repo):
1094 def clearstatus(repo):
1096 'Remove the status files'
1095 'Remove the status files'
1097 _clearrebasesetvisibiliy(repo)
1096 _clearrebasesetvisibiliy(repo)
1098 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
1097 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
1099
1098
1100 def needupdate(repo, state):
1099 def needupdate(repo, state):
1101 '''check whether we should `update --clean` away from a merge, or if
1100 '''check whether we should `update --clean` away from a merge, or if
1102 somehow the working dir got forcibly updated, e.g. by older hg'''
1101 somehow the working dir got forcibly updated, e.g. by older hg'''
1103 parents = [p.rev() for p in repo[None].parents()]
1102 parents = [p.rev() for p in repo[None].parents()]
1104
1103
1105 # Are we in a merge state at all?
1104 # Are we in a merge state at all?
1106 if len(parents) < 2:
1105 if len(parents) < 2:
1107 return False
1106 return False
1108
1107
1109 # We should be standing on the first as-of-yet unrebased commit.
1108 # We should be standing on the first as-of-yet unrebased commit.
1110 firstunrebased = min([old for old, new in state.iteritems()
1109 firstunrebased = min([old for old, new in state.iteritems()
1111 if new == nullrev])
1110 if new == nullrev])
1112 if firstunrebased in parents:
1111 if firstunrebased in parents:
1113 return True
1112 return True
1114
1113
1115 return False
1114 return False
1116
1115
1117 def abort(repo, originalwd, target, state, activebookmark=None):
1116 def abort(repo, originalwd, target, state, activebookmark=None):
1118 '''Restore the repository to its original state. Additional args:
1117 '''Restore the repository to its original state. Additional args:
1119
1118
1120 activebookmark: the name of the bookmark that should be active after the
1119 activebookmark: the name of the bookmark that should be active after the
1121 restore'''
1120 restore'''
1122
1121
1123 try:
1122 try:
1124 # If the first commits in the rebased set get skipped during the rebase,
1123 # If the first commits in the rebased set get skipped during the rebase,
1125 # their values within the state mapping will be the target rev id. The
1124 # their values within the state mapping will be the target rev id. The
1126 # dstates list must must not contain the target rev (issue4896)
1125 # dstates list must must not contain the target rev (issue4896)
1127 dstates = [s for s in state.values() if s >= 0 and s != target]
1126 dstates = [s for s in state.values() if s >= 0 and s != target]
1128 immutable = [d for d in dstates if not repo[d].mutable()]
1127 immutable = [d for d in dstates if not repo[d].mutable()]
1129 cleanup = True
1128 cleanup = True
1130 if immutable:
1129 if immutable:
1131 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1130 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1132 % ', '.join(str(repo[r]) for r in immutable),
1131 % ', '.join(str(repo[r]) for r in immutable),
1133 hint=_('see "hg help phases" for details'))
1132 hint=_('see "hg help phases" for details'))
1134 cleanup = False
1133 cleanup = False
1135
1134
1136 descendants = set()
1135 descendants = set()
1137 if dstates:
1136 if dstates:
1138 descendants = set(repo.changelog.descendants(dstates))
1137 descendants = set(repo.changelog.descendants(dstates))
1139 if descendants - set(dstates):
1138 if descendants - set(dstates):
1140 repo.ui.warn(_("warning: new changesets detected on target branch, "
1139 repo.ui.warn(_("warning: new changesets detected on target branch, "
1141 "can't strip\n"))
1140 "can't strip\n"))
1142 cleanup = False
1141 cleanup = False
1143
1142
1144 if cleanup:
1143 if cleanup:
1145 shouldupdate = False
1144 shouldupdate = False
1146 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1145 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1147 if rebased:
1146 if rebased:
1148 strippoints = [
1147 strippoints = [
1149 c.node() for c in repo.set('roots(%ld)', rebased)]
1148 c.node() for c in repo.set('roots(%ld)', rebased)]
1150 shouldupdate = len([
1149 shouldupdate = len([
1151 c.node() for c in repo.set('. & (%ld)', rebased)]) > 0
1150 c.node() for c in repo.set('. & (%ld)', rebased)]) > 0
1152
1151
1153 # Update away from the rebase if necessary
1152 # Update away from the rebase if necessary
1154 if shouldupdate or needupdate(repo, state):
1153 if shouldupdate or needupdate(repo, state):
1155 merge.update(repo, originalwd, False, True)
1154 merge.update(repo, originalwd, False, True)
1156
1155
1157 # Strip from the first rebased revision
1156 # Strip from the first rebased revision
1158 if rebased:
1157 if rebased:
1159 # no backup of rebased cset versions needed
1158 # no backup of rebased cset versions needed
1160 repair.strip(repo.ui, repo, strippoints)
1159 repair.strip(repo.ui, repo, strippoints)
1161
1160
1162 if activebookmark and activebookmark in repo._bookmarks:
1161 if activebookmark and activebookmark in repo._bookmarks:
1163 bookmarks.activate(repo, activebookmark)
1162 bookmarks.activate(repo, activebookmark)
1164
1163
1165 finally:
1164 finally:
1166 clearstatus(repo)
1165 clearstatus(repo)
1167 clearcollapsemsg(repo)
1166 clearcollapsemsg(repo)
1168 repo.ui.warn(_('rebase aborted\n'))
1167 repo.ui.warn(_('rebase aborted\n'))
1169 return 0
1168 return 0
1170
1169
1171 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1170 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1172 '''Define which revisions are going to be rebased and where
1171 '''Define which revisions are going to be rebased and where
1173
1172
1174 repo: repo
1173 repo: repo
1175 dest: context
1174 dest: context
1176 rebaseset: set of rev
1175 rebaseset: set of rev
1177 '''
1176 '''
1178 _setrebasesetvisibility(repo, rebaseset)
1177 _setrebasesetvisibility(repo, rebaseset)
1179
1178
1180 # This check isn't strictly necessary, since mq detects commits over an
1179 # This check isn't strictly necessary, since mq detects commits over an
1181 # applied patch. But it prevents messing up the working directory when
1180 # applied patch. But it prevents messing up the working directory when
1182 # a partially completed rebase is blocked by mq.
1181 # a partially completed rebase is blocked by mq.
1183 if 'qtip' in repo.tags() and (dest.node() in
1182 if 'qtip' in repo.tags() and (dest.node() in
1184 [s.node for s in repo.mq.applied]):
1183 [s.node for s in repo.mq.applied]):
1185 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1184 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1186
1185
1187 roots = list(repo.set('roots(%ld)', rebaseset))
1186 roots = list(repo.set('roots(%ld)', rebaseset))
1188 if not roots:
1187 if not roots:
1189 raise error.Abort(_('no matching revisions'))
1188 raise error.Abort(_('no matching revisions'))
1190 roots.sort()
1189 roots.sort()
1191 state = {}
1190 state = {}
1192 detachset = set()
1191 detachset = set()
1193 for root in roots:
1192 for root in roots:
1194 commonbase = root.ancestor(dest)
1193 commonbase = root.ancestor(dest)
1195 if commonbase == root:
1194 if commonbase == root:
1196 raise error.Abort(_('source is ancestor of destination'))
1195 raise error.Abort(_('source is ancestor of destination'))
1197 if commonbase == dest:
1196 if commonbase == dest:
1198 samebranch = root.branch() == dest.branch()
1197 samebranch = root.branch() == dest.branch()
1199 if not collapse and samebranch and root in dest.children():
1198 if not collapse and samebranch and root in dest.children():
1200 repo.ui.debug('source is a child of destination\n')
1199 repo.ui.debug('source is a child of destination\n')
1201 return None
1200 return None
1202
1201
1203 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
1202 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
1204 state.update(dict.fromkeys(rebaseset, revtodo))
1203 state.update(dict.fromkeys(rebaseset, revtodo))
1205 # Rebase tries to turn <dest> into a parent of <root> while
1204 # Rebase tries to turn <dest> into a parent of <root> while
1206 # preserving the number of parents of rebased changesets:
1205 # preserving the number of parents of rebased changesets:
1207 #
1206 #
1208 # - A changeset with a single parent will always be rebased as a
1207 # - A changeset with a single parent will always be rebased as a
1209 # changeset with a single parent.
1208 # changeset with a single parent.
1210 #
1209 #
1211 # - A merge will be rebased as merge unless its parents are both
1210 # - A merge will be rebased as merge unless its parents are both
1212 # ancestors of <dest> or are themselves in the rebased set and
1211 # ancestors of <dest> or are themselves in the rebased set and
1213 # pruned while rebased.
1212 # pruned while rebased.
1214 #
1213 #
1215 # If one parent of <root> is an ancestor of <dest>, the rebased
1214 # If one parent of <root> is an ancestor of <dest>, the rebased
1216 # version of this parent will be <dest>. This is always true with
1215 # version of this parent will be <dest>. This is always true with
1217 # --base option.
1216 # --base option.
1218 #
1217 #
1219 # Otherwise, we need to *replace* the original parents with
1218 # Otherwise, we need to *replace* the original parents with
1220 # <dest>. This "detaches" the rebased set from its former location
1219 # <dest>. This "detaches" the rebased set from its former location
1221 # and rebases it onto <dest>. Changes introduced by ancestors of
1220 # and rebases it onto <dest>. Changes introduced by ancestors of
1222 # <root> not common with <dest> (the detachset, marked as
1221 # <root> not common with <dest> (the detachset, marked as
1223 # nullmerge) are "removed" from the rebased changesets.
1222 # nullmerge) are "removed" from the rebased changesets.
1224 #
1223 #
1225 # - If <root> has a single parent, set it to <dest>.
1224 # - If <root> has a single parent, set it to <dest>.
1226 #
1225 #
1227 # - If <root> is a merge, we cannot decide which parent to
1226 # - If <root> is a merge, we cannot decide which parent to
1228 # replace, the rebase operation is not clearly defined.
1227 # replace, the rebase operation is not clearly defined.
1229 #
1228 #
1230 # The table below sums up this behavior:
1229 # The table below sums up this behavior:
1231 #
1230 #
1232 # +------------------+----------------------+-------------------------+
1231 # +------------------+----------------------+-------------------------+
1233 # | | one parent | merge |
1232 # | | one parent | merge |
1234 # +------------------+----------------------+-------------------------+
1233 # +------------------+----------------------+-------------------------+
1235 # | parent in | new parent is <dest> | parents in ::<dest> are |
1234 # | parent in | new parent is <dest> | parents in ::<dest> are |
1236 # | ::<dest> | | remapped to <dest> |
1235 # | ::<dest> | | remapped to <dest> |
1237 # +------------------+----------------------+-------------------------+
1236 # +------------------+----------------------+-------------------------+
1238 # | unrelated source | new parent is <dest> | ambiguous, abort |
1237 # | unrelated source | new parent is <dest> | ambiguous, abort |
1239 # +------------------+----------------------+-------------------------+
1238 # +------------------+----------------------+-------------------------+
1240 #
1239 #
1241 # The actual abort is handled by `defineparents`
1240 # The actual abort is handled by `defineparents`
1242 if len(root.parents()) <= 1:
1241 if len(root.parents()) <= 1:
1243 # ancestors of <root> not ancestors of <dest>
1242 # ancestors of <root> not ancestors of <dest>
1244 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1243 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1245 [root.rev()]))
1244 [root.rev()]))
1246 for r in detachset:
1245 for r in detachset:
1247 if r not in state:
1246 if r not in state:
1248 state[r] = nullmerge
1247 state[r] = nullmerge
1249 if len(roots) > 1:
1248 if len(roots) > 1:
1250 # If we have multiple roots, we may have "hole" in the rebase set.
1249 # If we have multiple roots, we may have "hole" in the rebase set.
1251 # Rebase roots that descend from those "hole" should not be detached as
1250 # Rebase roots that descend from those "hole" should not be detached as
1252 # other root are. We use the special `revignored` to inform rebase that
1251 # other root are. We use the special `revignored` to inform rebase that
1253 # the revision should be ignored but that `defineparents` should search
1252 # the revision should be ignored but that `defineparents` should search
1254 # a rebase destination that make sense regarding rebased topology.
1253 # a rebase destination that make sense regarding rebased topology.
1255 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1254 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1256 for ignored in set(rebasedomain) - set(rebaseset):
1255 for ignored in set(rebasedomain) - set(rebaseset):
1257 state[ignored] = revignored
1256 state[ignored] = revignored
1258 for r in obsoletenotrebased:
1257 for r in obsoletenotrebased:
1259 if obsoletenotrebased[r] is None:
1258 if obsoletenotrebased[r] is None:
1260 state[r] = revpruned
1259 state[r] = revpruned
1261 else:
1260 else:
1262 state[r] = revprecursor
1261 state[r] = revprecursor
1263 return repo['.'].rev(), dest.rev(), state
1262 return repo['.'].rev(), dest.rev(), state
1264
1263
1265 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1264 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1266 """dispose of rebased revision at the end of the rebase
1265 """dispose of rebased revision at the end of the rebase
1267
1266
1268 If `collapsedas` is not None, the rebase was a collapse whose result if the
1267 If `collapsedas` is not None, the rebase was a collapse whose result if the
1269 `collapsedas` node."""
1268 `collapsedas` node."""
1270 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1269 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1271 markers = []
1270 markers = []
1272 for rev, newrev in sorted(state.items()):
1271 for rev, newrev in sorted(state.items()):
1273 if newrev >= 0:
1272 if newrev >= 0:
1274 if rev in skipped:
1273 if rev in skipped:
1275 succs = ()
1274 succs = ()
1276 elif collapsedas is not None:
1275 elif collapsedas is not None:
1277 succs = (repo[collapsedas],)
1276 succs = (repo[collapsedas],)
1278 else:
1277 else:
1279 succs = (repo[newrev],)
1278 succs = (repo[newrev],)
1280 markers.append((repo[rev], succs))
1279 markers.append((repo[rev], succs))
1281 if markers:
1280 if markers:
1282 obsolete.createmarkers(repo, markers)
1281 obsolete.createmarkers(repo, markers)
1283 else:
1282 else:
1284 rebased = [rev for rev in state if state[rev] > nullmerge]
1283 rebased = [rev for rev in state if state[rev] > nullmerge]
1285 if rebased:
1284 if rebased:
1286 stripped = []
1285 stripped = []
1287 for root in repo.set('roots(%ld)', rebased):
1286 for root in repo.set('roots(%ld)', rebased):
1288 if set(repo.changelog.descendants([root.rev()])) - set(state):
1287 if set(repo.changelog.descendants([root.rev()])) - set(state):
1289 ui.warn(_("warning: new changesets detected "
1288 ui.warn(_("warning: new changesets detected "
1290 "on source branch, not stripping\n"))
1289 "on source branch, not stripping\n"))
1291 else:
1290 else:
1292 stripped.append(root.node())
1291 stripped.append(root.node())
1293 if stripped:
1292 if stripped:
1294 # backup the old csets by default
1293 # backup the old csets by default
1295 repair.strip(ui, repo, stripped, "all")
1294 repair.strip(ui, repo, stripped, "all")
1296
1295
1297
1296
1298 def pullrebase(orig, ui, repo, *args, **opts):
1297 def pullrebase(orig, ui, repo, *args, **opts):
1299 'Call rebase after pull if the latter has been invoked with --rebase'
1298 'Call rebase after pull if the latter has been invoked with --rebase'
1300 ret = None
1299 ret = None
1301 if opts.get('rebase'):
1300 if opts.get('rebase'):
1302 wlock = lock = None
1301 wlock = lock = None
1303 try:
1302 try:
1304 wlock = repo.wlock()
1303 wlock = repo.wlock()
1305 lock = repo.lock()
1304 lock = repo.lock()
1306 if opts.get('update'):
1305 if opts.get('update'):
1307 del opts['update']
1306 del opts['update']
1308 ui.debug('--update and --rebase are not compatible, ignoring '
1307 ui.debug('--update and --rebase are not compatible, ignoring '
1309 'the update flag\n')
1308 'the update flag\n')
1310
1309
1311 revsprepull = len(repo)
1310 revsprepull = len(repo)
1312 origpostincoming = commands.postincoming
1311 origpostincoming = commands.postincoming
1313 def _dummy(*args, **kwargs):
1312 def _dummy(*args, **kwargs):
1314 pass
1313 pass
1315 commands.postincoming = _dummy
1314 commands.postincoming = _dummy
1316 try:
1315 try:
1317 ret = orig(ui, repo, *args, **opts)
1316 ret = orig(ui, repo, *args, **opts)
1318 finally:
1317 finally:
1319 commands.postincoming = origpostincoming
1318 commands.postincoming = origpostincoming
1320 revspostpull = len(repo)
1319 revspostpull = len(repo)
1321 if revspostpull > revsprepull:
1320 if revspostpull > revsprepull:
1322 # --rev option from pull conflict with rebase own --rev
1321 # --rev option from pull conflict with rebase own --rev
1323 # dropping it
1322 # dropping it
1324 if 'rev' in opts:
1323 if 'rev' in opts:
1325 del opts['rev']
1324 del opts['rev']
1326 # positional argument from pull conflicts with rebase's own
1325 # positional argument from pull conflicts with rebase's own
1327 # --source.
1326 # --source.
1328 if 'source' in opts:
1327 if 'source' in opts:
1329 del opts['source']
1328 del opts['source']
1330 # revsprepull is the len of the repo, not revnum of tip.
1329 # revsprepull is the len of the repo, not revnum of tip.
1331 destspace = list(repo.changelog.revs(start=revsprepull))
1330 destspace = list(repo.changelog.revs(start=revsprepull))
1332 opts['_destspace'] = destspace
1331 opts['_destspace'] = destspace
1333 try:
1332 try:
1334 rebase(ui, repo, **opts)
1333 rebase(ui, repo, **opts)
1335 except error.NoMergeDestAbort:
1334 except error.NoMergeDestAbort:
1336 # we can maybe update instead
1335 # we can maybe update instead
1337 rev, _a, _b = destutil.destupdate(repo)
1336 rev, _a, _b = destutil.destupdate(repo)
1338 if rev == repo['.'].rev():
1337 if rev == repo['.'].rev():
1339 ui.status(_('nothing to rebase\n'))
1338 ui.status(_('nothing to rebase\n'))
1340 else:
1339 else:
1341 ui.status(_('nothing to rebase - updating instead\n'))
1340 ui.status(_('nothing to rebase - updating instead\n'))
1342 # not passing argument to get the bare update behavior
1341 # not passing argument to get the bare update behavior
1343 # with warning and trumpets
1342 # with warning and trumpets
1344 commands.update(ui, repo)
1343 commands.update(ui, repo)
1345 finally:
1344 finally:
1346 release(lock, wlock)
1345 release(lock, wlock)
1347 else:
1346 else:
1348 if opts.get('tool'):
1347 if opts.get('tool'):
1349 raise error.Abort(_('--tool can only be used with --rebase'))
1348 raise error.Abort(_('--tool can only be used with --rebase'))
1350 ret = orig(ui, repo, *args, **opts)
1349 ret = orig(ui, repo, *args, **opts)
1351
1350
1352 return ret
1351 return ret
1353
1352
1354 def _setrebasesetvisibility(repo, revs):
1353 def _setrebasesetvisibility(repo, revs):
1355 """store the currently rebased set on the repo object
1354 """store the currently rebased set on the repo object
1356
1355
1357 This is used by another function to prevent rebased revision to because
1356 This is used by another function to prevent rebased revision to because
1358 hidden (see issue4505)"""
1357 hidden (see issue4505)"""
1359 repo = repo.unfiltered()
1358 repo = repo.unfiltered()
1360 revs = set(revs)
1359 revs = set(revs)
1361 repo._rebaseset = revs
1360 repo._rebaseset = revs
1362 # invalidate cache if visibility changes
1361 # invalidate cache if visibility changes
1363 hiddens = repo.filteredrevcache.get('visible', set())
1362 hiddens = repo.filteredrevcache.get('visible', set())
1364 if revs & hiddens:
1363 if revs & hiddens:
1365 repo.invalidatevolatilesets()
1364 repo.invalidatevolatilesets()
1366
1365
1367 def _clearrebasesetvisibiliy(repo):
1366 def _clearrebasesetvisibiliy(repo):
1368 """remove rebaseset data from the repo"""
1367 """remove rebaseset data from the repo"""
1369 repo = repo.unfiltered()
1368 repo = repo.unfiltered()
1370 if '_rebaseset' in vars(repo):
1369 if '_rebaseset' in vars(repo):
1371 del repo._rebaseset
1370 del repo._rebaseset
1372
1371
1373 def _rebasedvisible(orig, repo):
1372 def _rebasedvisible(orig, repo):
1374 """ensure rebased revs stay visible (see issue4505)"""
1373 """ensure rebased revs stay visible (see issue4505)"""
1375 blockers = orig(repo)
1374 blockers = orig(repo)
1376 blockers.update(getattr(repo, '_rebaseset', ()))
1375 blockers.update(getattr(repo, '_rebaseset', ()))
1377 return blockers
1376 return blockers
1378
1377
1379 def _filterobsoleterevs(repo, revs):
1378 def _filterobsoleterevs(repo, revs):
1380 """returns a set of the obsolete revisions in revs"""
1379 """returns a set of the obsolete revisions in revs"""
1381 return set(r for r in revs if repo[r].obsolete())
1380 return set(r for r in revs if repo[r].obsolete())
1382
1381
1383 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1382 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1384 """return a mapping obsolete => successor for all obsolete nodes to be
1383 """return a mapping obsolete => successor for all obsolete nodes to be
1385 rebased that have a successors in the destination
1384 rebased that have a successors in the destination
1386
1385
1387 obsolete => None entries in the mapping indicate nodes with no succesor"""
1386 obsolete => None entries in the mapping indicate nodes with no succesor"""
1388 obsoletenotrebased = {}
1387 obsoletenotrebased = {}
1389
1388
1390 # Build a mapping successor => obsolete nodes for the obsolete
1389 # Build a mapping successor => obsolete nodes for the obsolete
1391 # nodes to be rebased
1390 # nodes to be rebased
1392 allsuccessors = {}
1391 allsuccessors = {}
1393 cl = repo.changelog
1392 cl = repo.changelog
1394 for r in rebaseobsrevs:
1393 for r in rebaseobsrevs:
1395 node = cl.node(r)
1394 node = cl.node(r)
1396 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1395 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1397 try:
1396 try:
1398 allsuccessors[cl.rev(s)] = cl.rev(node)
1397 allsuccessors[cl.rev(s)] = cl.rev(node)
1399 except LookupError:
1398 except LookupError:
1400 pass
1399 pass
1401
1400
1402 if allsuccessors:
1401 if allsuccessors:
1403 # Look for successors of obsolete nodes to be rebased among
1402 # Look for successors of obsolete nodes to be rebased among
1404 # the ancestors of dest
1403 # the ancestors of dest
1405 ancs = cl.ancestors([repo[dest].rev()],
1404 ancs = cl.ancestors([repo[dest].rev()],
1406 stoprev=min(allsuccessors),
1405 stoprev=min(allsuccessors),
1407 inclusive=True)
1406 inclusive=True)
1408 for s in allsuccessors:
1407 for s in allsuccessors:
1409 if s in ancs:
1408 if s in ancs:
1410 obsoletenotrebased[allsuccessors[s]] = s
1409 obsoletenotrebased[allsuccessors[s]] = s
1411 elif (s == allsuccessors[s] and
1410 elif (s == allsuccessors[s] and
1412 allsuccessors.values().count(s) == 1):
1411 allsuccessors.values().count(s) == 1):
1413 # plain prune
1412 # plain prune
1414 obsoletenotrebased[s] = None
1413 obsoletenotrebased[s] = None
1415
1414
1416 return obsoletenotrebased
1415 return obsoletenotrebased
1417
1416
1418 def summaryhook(ui, repo):
1417 def summaryhook(ui, repo):
1419 if not os.path.exists(repo.join('rebasestate')):
1418 if not os.path.exists(repo.join('rebasestate')):
1420 return
1419 return
1421 try:
1420 try:
1422 rbsrt = rebaseruntime(repo, ui, {})
1421 rbsrt = rebaseruntime(repo, ui, {})
1423 rbsrt.restorestatus()
1422 rbsrt.restorestatus()
1424 state = rbsrt.state
1423 state = rbsrt.state
1425 except error.RepoLookupError:
1424 except error.RepoLookupError:
1426 # i18n: column positioning for "hg summary"
1425 # i18n: column positioning for "hg summary"
1427 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1426 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1428 ui.write(msg)
1427 ui.write(msg)
1429 return
1428 return
1430 numrebased = len([i for i in state.itervalues() if i >= 0])
1429 numrebased = len([i for i in state.itervalues() if i >= 0])
1431 # i18n: column positioning for "hg summary"
1430 # i18n: column positioning for "hg summary"
1432 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1431 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1433 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1432 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1434 ui.label(_('%d remaining'), 'rebase.remaining') %
1433 ui.label(_('%d remaining'), 'rebase.remaining') %
1435 (len(state) - numrebased)))
1434 (len(state) - numrebased)))
1436
1435
1437 def uisetup(ui):
1436 def uisetup(ui):
1438 #Replace pull with a decorator to provide --rebase option
1437 #Replace pull with a decorator to provide --rebase option
1439 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1438 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1440 entry[1].append(('', 'rebase', None,
1439 entry[1].append(('', 'rebase', None,
1441 _("rebase working directory to branch head")))
1440 _("rebase working directory to branch head")))
1442 entry[1].append(('t', 'tool', '',
1441 entry[1].append(('t', 'tool', '',
1443 _("specify merge tool for rebase")))
1442 _("specify merge tool for rebase")))
1444 cmdutil.summaryhooks.add('rebase', summaryhook)
1443 cmdutil.summaryhooks.add('rebase', summaryhook)
1445 cmdutil.unfinishedstates.append(
1444 cmdutil.unfinishedstates.append(
1446 ['rebasestate', False, False, _('rebase in progress'),
1445 ['rebasestate', False, False, _('rebase in progress'),
1447 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1446 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1448 cmdutil.afterresolvedstates.append(
1447 cmdutil.afterresolvedstates.append(
1449 ['rebasestate', _('hg rebase --continue')])
1448 ['rebasestate', _('hg rebase --continue')])
1450 # ensure rebased rev are not hidden
1449 # ensure rebased rev are not hidden
1451 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1450 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
General Comments 0
You need to be logged in to leave comments. Login now