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