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