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