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