##// END OF EJS Templates
rebase: use fm.formatlist() and fm.formatdict() to support user template...
Pulkit Goyal -
r35123:f56a30b8 default
parent child Browse files
Show More
@@ -1,1706 +1,1709
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,
25 nullid,
24 nullid,
26 nullrev,
25 nullrev,
27 short,
26 short,
28 )
27 )
29 from mercurial import (
28 from mercurial import (
30 bookmarks,
29 bookmarks,
31 cmdutil,
30 cmdutil,
32 commands,
31 commands,
33 copies,
32 copies,
34 destutil,
33 destutil,
35 dirstateguard,
34 dirstateguard,
36 error,
35 error,
37 extensions,
36 extensions,
38 hg,
37 hg,
39 lock,
38 lock,
40 merge as mergemod,
39 merge as mergemod,
41 mergeutil,
40 mergeutil,
42 obsolete,
41 obsolete,
43 obsutil,
42 obsutil,
44 patch,
43 patch,
45 phases,
44 phases,
46 pycompat,
45 pycompat,
47 registrar,
46 registrar,
48 repair,
47 repair,
49 revset,
48 revset,
50 revsetlang,
49 revsetlang,
51 scmutil,
50 scmutil,
52 smartset,
51 smartset,
53 util,
52 util,
54 )
53 )
55
54
56 release = lock.release
55 release = lock.release
57
56
58 # 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
59 # their values must be maintained.
58 # their values must be maintained.
60
59
61 # Indicates that a revision needs to be rebased
60 # Indicates that a revision needs to be rebased
62 revtodo = -1
61 revtodo = -1
63 revtodostr = '-1'
62 revtodostr = '-1'
64
63
65 # legacy revstates no longer needed in current code
64 # legacy revstates no longer needed in current code
66 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
65 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
67 legacystates = {'-2', '-3', '-4', '-5'}
66 legacystates = {'-2', '-3', '-4', '-5'}
68
67
69 cmdtable = {}
68 cmdtable = {}
70 command = registrar.command(cmdtable)
69 command = registrar.command(cmdtable)
71 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
70 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
72 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
71 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
73 # be specifying the version(s) of Mercurial they are tested with, or
72 # be specifying the version(s) of Mercurial they are tested with, or
74 # leave the attribute unspecified.
73 # leave the attribute unspecified.
75 testedwith = 'ships-with-hg-core'
74 testedwith = 'ships-with-hg-core'
76
75
77 def _nothingtorebase():
76 def _nothingtorebase():
78 return 1
77 return 1
79
78
80 def _savegraft(ctx, extra):
79 def _savegraft(ctx, extra):
81 s = ctx.extra().get('source', None)
80 s = ctx.extra().get('source', None)
82 if s is not None:
81 if s is not None:
83 extra['source'] = s
82 extra['source'] = s
84 s = ctx.extra().get('intermediate-source', None)
83 s = ctx.extra().get('intermediate-source', None)
85 if s is not None:
84 if s is not None:
86 extra['intermediate-source'] = s
85 extra['intermediate-source'] = s
87
86
88 def _savebranch(ctx, extra):
87 def _savebranch(ctx, extra):
89 extra['branch'] = ctx.branch()
88 extra['branch'] = ctx.branch()
90
89
91 def _makeextrafn(copiers):
90 def _makeextrafn(copiers):
92 """make an extrafn out of the given copy-functions.
91 """make an extrafn out of the given copy-functions.
93
92
94 A copy function takes a context and an extra dict, and mutates the
93 A copy function takes a context and an extra dict, and mutates the
95 extra dict as needed based on the given context.
94 extra dict as needed based on the given context.
96 """
95 """
97 def extrafn(ctx, extra):
96 def extrafn(ctx, extra):
98 for c in copiers:
97 for c in copiers:
99 c(ctx, extra)
98 c(ctx, extra)
100 return extrafn
99 return extrafn
101
100
102 def _destrebase(repo, sourceset, destspace=None):
101 def _destrebase(repo, sourceset, destspace=None):
103 """small wrapper around destmerge to pass the right extra args
102 """small wrapper around destmerge to pass the right extra args
104
103
105 Please wrap destutil.destmerge instead."""
104 Please wrap destutil.destmerge instead."""
106 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
105 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
107 onheadcheck=False, destspace=destspace)
106 onheadcheck=False, destspace=destspace)
108
107
109 revsetpredicate = registrar.revsetpredicate()
108 revsetpredicate = registrar.revsetpredicate()
110
109
111 @revsetpredicate('_destrebase')
110 @revsetpredicate('_destrebase')
112 def _revsetdestrebase(repo, subset, x):
111 def _revsetdestrebase(repo, subset, x):
113 # ``_rebasedefaultdest()``
112 # ``_rebasedefaultdest()``
114
113
115 # default destination for rebase.
114 # default destination for rebase.
116 # # XXX: Currently private because I expect the signature to change.
115 # # XXX: Currently private because I expect the signature to change.
117 # # XXX: - bailing out in case of ambiguity vs returning all data.
116 # # XXX: - bailing out in case of ambiguity vs returning all data.
118 # i18n: "_rebasedefaultdest" is a keyword
117 # i18n: "_rebasedefaultdest" is a keyword
119 sourceset = None
118 sourceset = None
120 if x is not None:
119 if x is not None:
121 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
120 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
122 return subset & smartset.baseset([_destrebase(repo, sourceset)])
121 return subset & smartset.baseset([_destrebase(repo, sourceset)])
123
122
124 def _ctxdesc(ctx):
123 def _ctxdesc(ctx):
125 """short description for a context"""
124 """short description for a context"""
126 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
125 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
127 ctx.description().split('\n', 1)[0])
126 ctx.description().split('\n', 1)[0])
128 repo = ctx.repo()
127 repo = ctx.repo()
129 names = []
128 names = []
130 for nsname, ns in repo.names.iteritems():
129 for nsname, ns in repo.names.iteritems():
131 if nsname == 'branches':
130 if nsname == 'branches':
132 continue
131 continue
133 names.extend(ns.names(repo, ctx.node()))
132 names.extend(ns.names(repo, ctx.node()))
134 if names:
133 if names:
135 desc += ' (%s)' % ' '.join(names)
134 desc += ' (%s)' % ' '.join(names)
136 return desc
135 return desc
137
136
138 class rebaseruntime(object):
137 class rebaseruntime(object):
139 """This class is a container for rebase runtime state"""
138 """This class is a container for rebase runtime state"""
140 def __init__(self, repo, ui, opts=None):
139 def __init__(self, repo, ui, opts=None):
141 if opts is None:
140 if opts is None:
142 opts = {}
141 opts = {}
143
142
144 # prepared: whether we have rebasestate prepared or not. Currently it
143 # prepared: whether we have rebasestate prepared or not. Currently it
145 # decides whether "self.repo" is unfiltered or not.
144 # decides whether "self.repo" is unfiltered or not.
146 # The rebasestate has explicit hash to hash instructions not depending
145 # The rebasestate has explicit hash to hash instructions not depending
147 # on visibility. If rebasestate exists (in-memory or on-disk), use
146 # on visibility. If rebasestate exists (in-memory or on-disk), use
148 # unfiltered repo to avoid visibility issues.
147 # unfiltered repo to avoid visibility issues.
149 # Before knowing rebasestate (i.e. when starting a new rebase (not
148 # Before knowing rebasestate (i.e. when starting a new rebase (not
150 # --continue or --abort)), the original repo should be used so
149 # --continue or --abort)), the original repo should be used so
151 # visibility-dependent revsets are correct.
150 # visibility-dependent revsets are correct.
152 self.prepared = False
151 self.prepared = False
153 self._repo = repo
152 self._repo = repo
154
153
155 self.ui = ui
154 self.ui = ui
156 self.opts = opts
155 self.opts = opts
157 self.originalwd = None
156 self.originalwd = None
158 self.external = nullrev
157 self.external = nullrev
159 # Mapping between the old revision id and either what is the new rebased
158 # Mapping between the old revision id and either what is the new rebased
160 # revision or what needs to be done with the old revision. The state
159 # revision or what needs to be done with the old revision. The state
161 # dict will be what contains most of the rebase progress state.
160 # dict will be what contains most of the rebase progress state.
162 self.state = {}
161 self.state = {}
163 self.activebookmark = None
162 self.activebookmark = None
164 self.destmap = {}
163 self.destmap = {}
165 self.skipped = set()
164 self.skipped = set()
166
165
167 self.collapsef = opts.get('collapse', False)
166 self.collapsef = opts.get('collapse', False)
168 self.collapsemsg = cmdutil.logmessage(ui, opts)
167 self.collapsemsg = cmdutil.logmessage(ui, opts)
169 self.date = opts.get('date', None)
168 self.date = opts.get('date', None)
170
169
171 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
170 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
172 self.extrafns = [_savegraft]
171 self.extrafns = [_savegraft]
173 if e:
172 if e:
174 self.extrafns = [e]
173 self.extrafns = [e]
175
174
176 self.keepf = opts.get('keep', False)
175 self.keepf = opts.get('keep', False)
177 self.keepbranchesf = opts.get('keepbranches', False)
176 self.keepbranchesf = opts.get('keepbranches', False)
178 # keepopen is not meant for use on the command line, but by
177 # keepopen is not meant for use on the command line, but by
179 # other extensions
178 # other extensions
180 self.keepopen = opts.get('keepopen', False)
179 self.keepopen = opts.get('keepopen', False)
181 self.obsoletenotrebased = {}
180 self.obsoletenotrebased = {}
182 self.obsoletewithoutsuccessorindestination = set()
181 self.obsoletewithoutsuccessorindestination = set()
183
182
184 @property
183 @property
185 def repo(self):
184 def repo(self):
186 if self.prepared:
185 if self.prepared:
187 return self._repo.unfiltered()
186 return self._repo.unfiltered()
188 else:
187 else:
189 return self._repo
188 return self._repo
190
189
191 def storestatus(self, tr=None):
190 def storestatus(self, tr=None):
192 """Store the current status to allow recovery"""
191 """Store the current status to allow recovery"""
193 if tr:
192 if tr:
194 tr.addfilegenerator('rebasestate', ('rebasestate',),
193 tr.addfilegenerator('rebasestate', ('rebasestate',),
195 self._writestatus, location='plain')
194 self._writestatus, location='plain')
196 else:
195 else:
197 with self.repo.vfs("rebasestate", "w") as f:
196 with self.repo.vfs("rebasestate", "w") as f:
198 self._writestatus(f)
197 self._writestatus(f)
199
198
200 def _writestatus(self, f):
199 def _writestatus(self, f):
201 repo = self.repo
200 repo = self.repo
202 assert repo.filtername is None
201 assert repo.filtername is None
203 f.write(repo[self.originalwd].hex() + '\n')
202 f.write(repo[self.originalwd].hex() + '\n')
204 # was "dest". we now write dest per src root below.
203 # was "dest". we now write dest per src root below.
205 f.write('\n')
204 f.write('\n')
206 f.write(repo[self.external].hex() + '\n')
205 f.write(repo[self.external].hex() + '\n')
207 f.write('%d\n' % int(self.collapsef))
206 f.write('%d\n' % int(self.collapsef))
208 f.write('%d\n' % int(self.keepf))
207 f.write('%d\n' % int(self.keepf))
209 f.write('%d\n' % int(self.keepbranchesf))
208 f.write('%d\n' % int(self.keepbranchesf))
210 f.write('%s\n' % (self.activebookmark or ''))
209 f.write('%s\n' % (self.activebookmark or ''))
211 destmap = self.destmap
210 destmap = self.destmap
212 for d, v in self.state.iteritems():
211 for d, v in self.state.iteritems():
213 oldrev = repo[d].hex()
212 oldrev = repo[d].hex()
214 if v >= 0:
213 if v >= 0:
215 newrev = repo[v].hex()
214 newrev = repo[v].hex()
216 else:
215 else:
217 newrev = v
216 newrev = v
218 destnode = repo[destmap[d]].hex()
217 destnode = repo[destmap[d]].hex()
219 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
218 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
220 repo.ui.debug('rebase status stored\n')
219 repo.ui.debug('rebase status stored\n')
221
220
222 def restorestatus(self):
221 def restorestatus(self):
223 """Restore a previously stored status"""
222 """Restore a previously stored status"""
224 self.prepared = True
223 self.prepared = True
225 repo = self.repo
224 repo = self.repo
226 assert repo.filtername is None
225 assert repo.filtername is None
227 keepbranches = None
226 keepbranches = None
228 legacydest = None
227 legacydest = None
229 collapse = False
228 collapse = False
230 external = nullrev
229 external = nullrev
231 activebookmark = None
230 activebookmark = None
232 state = {}
231 state = {}
233 destmap = {}
232 destmap = {}
234
233
235 try:
234 try:
236 f = repo.vfs("rebasestate")
235 f = repo.vfs("rebasestate")
237 for i, l in enumerate(f.read().splitlines()):
236 for i, l in enumerate(f.read().splitlines()):
238 if i == 0:
237 if i == 0:
239 originalwd = repo[l].rev()
238 originalwd = repo[l].rev()
240 elif i == 1:
239 elif i == 1:
241 # this line should be empty in newer version. but legacy
240 # this line should be empty in newer version. but legacy
242 # clients may still use it
241 # clients may still use it
243 if l:
242 if l:
244 legacydest = repo[l].rev()
243 legacydest = repo[l].rev()
245 elif i == 2:
244 elif i == 2:
246 external = repo[l].rev()
245 external = repo[l].rev()
247 elif i == 3:
246 elif i == 3:
248 collapse = bool(int(l))
247 collapse = bool(int(l))
249 elif i == 4:
248 elif i == 4:
250 keep = bool(int(l))
249 keep = bool(int(l))
251 elif i == 5:
250 elif i == 5:
252 keepbranches = bool(int(l))
251 keepbranches = bool(int(l))
253 elif i == 6 and not (len(l) == 81 and ':' in l):
252 elif i == 6 and not (len(l) == 81 and ':' in l):
254 # line 6 is a recent addition, so for backwards
253 # line 6 is a recent addition, so for backwards
255 # compatibility check that the line doesn't look like the
254 # compatibility check that the line doesn't look like the
256 # oldrev:newrev lines
255 # oldrev:newrev lines
257 activebookmark = l
256 activebookmark = l
258 else:
257 else:
259 args = l.split(':')
258 args = l.split(':')
260 oldrev = args[0]
259 oldrev = args[0]
261 newrev = args[1]
260 newrev = args[1]
262 if newrev in legacystates:
261 if newrev in legacystates:
263 continue
262 continue
264 if len(args) > 2:
263 if len(args) > 2:
265 destnode = args[2]
264 destnode = args[2]
266 else:
265 else:
267 destnode = legacydest
266 destnode = legacydest
268 destmap[repo[oldrev].rev()] = repo[destnode].rev()
267 destmap[repo[oldrev].rev()] = repo[destnode].rev()
269 if newrev in (nullid, revtodostr):
268 if newrev in (nullid, revtodostr):
270 state[repo[oldrev].rev()] = revtodo
269 state[repo[oldrev].rev()] = revtodo
271 # Legacy compat special case
270 # Legacy compat special case
272 else:
271 else:
273 state[repo[oldrev].rev()] = repo[newrev].rev()
272 state[repo[oldrev].rev()] = repo[newrev].rev()
274
273
275 except IOError as err:
274 except IOError as err:
276 if err.errno != errno.ENOENT:
275 if err.errno != errno.ENOENT:
277 raise
276 raise
278 cmdutil.wrongtooltocontinue(repo, _('rebase'))
277 cmdutil.wrongtooltocontinue(repo, _('rebase'))
279
278
280 if keepbranches is None:
279 if keepbranches is None:
281 raise error.Abort(_('.hg/rebasestate is incomplete'))
280 raise error.Abort(_('.hg/rebasestate is incomplete'))
282
281
283 skipped = set()
282 skipped = set()
284 # recompute the set of skipped revs
283 # recompute the set of skipped revs
285 if not collapse:
284 if not collapse:
286 seen = set(destmap.values())
285 seen = set(destmap.values())
287 for old, new in sorted(state.items()):
286 for old, new in sorted(state.items()):
288 if new != revtodo and new in seen:
287 if new != revtodo and new in seen:
289 skipped.add(old)
288 skipped.add(old)
290 seen.add(new)
289 seen.add(new)
291 repo.ui.debug('computed skipped revs: %s\n' %
290 repo.ui.debug('computed skipped revs: %s\n' %
292 (' '.join(str(r) for r in sorted(skipped)) or None))
291 (' '.join(str(r) for r in sorted(skipped)) or None))
293 repo.ui.debug('rebase status resumed\n')
292 repo.ui.debug('rebase status resumed\n')
294
293
295 self.originalwd = originalwd
294 self.originalwd = originalwd
296 self.destmap = destmap
295 self.destmap = destmap
297 self.state = state
296 self.state = state
298 self.skipped = skipped
297 self.skipped = skipped
299 self.collapsef = collapse
298 self.collapsef = collapse
300 self.keepf = keep
299 self.keepf = keep
301 self.keepbranchesf = keepbranches
300 self.keepbranchesf = keepbranches
302 self.external = external
301 self.external = external
303 self.activebookmark = activebookmark
302 self.activebookmark = activebookmark
304
303
305 def _handleskippingobsolete(self, obsoleterevs, destmap):
304 def _handleskippingobsolete(self, obsoleterevs, destmap):
306 """Compute structures necessary for skipping obsolete revisions
305 """Compute structures necessary for skipping obsolete revisions
307
306
308 obsoleterevs: iterable of all obsolete revisions in rebaseset
307 obsoleterevs: iterable of all obsolete revisions in rebaseset
309 destmap: {srcrev: destrev} destination revisions
308 destmap: {srcrev: destrev} destination revisions
310 """
309 """
311 self.obsoletenotrebased = {}
310 self.obsoletenotrebased = {}
312 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
311 if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
313 return
312 return
314 obsoleteset = set(obsoleterevs)
313 obsoleteset = set(obsoleterevs)
315 self.obsoletenotrebased, self.obsoletewithoutsuccessorindestination = \
314 self.obsoletenotrebased, self.obsoletewithoutsuccessorindestination = \
316 _computeobsoletenotrebased(self.repo, obsoleteset, destmap)
315 _computeobsoletenotrebased(self.repo, obsoleteset, destmap)
317 skippedset = set(self.obsoletenotrebased)
316 skippedset = set(self.obsoletenotrebased)
318 skippedset.update(self.obsoletewithoutsuccessorindestination)
317 skippedset.update(self.obsoletewithoutsuccessorindestination)
319 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
318 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
320
319
321 def _prepareabortorcontinue(self, isabort):
320 def _prepareabortorcontinue(self, isabort):
322 try:
321 try:
323 self.restorestatus()
322 self.restorestatus()
324 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
323 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
325 except error.RepoLookupError:
324 except error.RepoLookupError:
326 if isabort:
325 if isabort:
327 clearstatus(self.repo)
326 clearstatus(self.repo)
328 clearcollapsemsg(self.repo)
327 clearcollapsemsg(self.repo)
329 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
328 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
330 ' only broken state is cleared)\n'))
329 ' only broken state is cleared)\n'))
331 return 0
330 return 0
332 else:
331 else:
333 msg = _('cannot continue inconsistent rebase')
332 msg = _('cannot continue inconsistent rebase')
334 hint = _('use "hg rebase --abort" to clear broken state')
333 hint = _('use "hg rebase --abort" to clear broken state')
335 raise error.Abort(msg, hint=hint)
334 raise error.Abort(msg, hint=hint)
336 if isabort:
335 if isabort:
337 return abort(self.repo, self.originalwd, self.destmap,
336 return abort(self.repo, self.originalwd, self.destmap,
338 self.state, activebookmark=self.activebookmark)
337 self.state, activebookmark=self.activebookmark)
339
338
340 def _preparenewrebase(self, destmap):
339 def _preparenewrebase(self, destmap):
341 if not destmap:
340 if not destmap:
342 return _nothingtorebase()
341 return _nothingtorebase()
343
342
344 rebaseset = destmap.keys()
343 rebaseset = destmap.keys()
345 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
344 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
346 if (not (self.keepf or allowunstable)
345 if (not (self.keepf or allowunstable)
347 and self.repo.revs('first(children(%ld) - %ld)',
346 and self.repo.revs('first(children(%ld) - %ld)',
348 rebaseset, rebaseset)):
347 rebaseset, rebaseset)):
349 raise error.Abort(
348 raise error.Abort(
350 _("can't remove original changesets with"
349 _("can't remove original changesets with"
351 " unrebased descendants"),
350 " unrebased descendants"),
352 hint=_('use --keep to keep original changesets'))
351 hint=_('use --keep to keep original changesets'))
353
352
354 result = buildstate(self.repo, destmap, self.collapsef)
353 result = buildstate(self.repo, destmap, self.collapsef)
355
354
356 if not result:
355 if not result:
357 # Empty state built, nothing to rebase
356 # Empty state built, nothing to rebase
358 self.ui.status(_('nothing to rebase\n'))
357 self.ui.status(_('nothing to rebase\n'))
359 return _nothingtorebase()
358 return _nothingtorebase()
360
359
361 for root in self.repo.set('roots(%ld)', rebaseset):
360 for root in self.repo.set('roots(%ld)', rebaseset):
362 if not self.keepf and not root.mutable():
361 if not self.keepf and not root.mutable():
363 raise error.Abort(_("can't rebase public changeset %s")
362 raise error.Abort(_("can't rebase public changeset %s")
364 % root,
363 % root,
365 hint=_("see 'hg help phases' for details"))
364 hint=_("see 'hg help phases' for details"))
366
365
367 (self.originalwd, self.destmap, self.state) = result
366 (self.originalwd, self.destmap, self.state) = result
368 if self.collapsef:
367 if self.collapsef:
369 dests = set(self.destmap.values())
368 dests = set(self.destmap.values())
370 if len(dests) != 1:
369 if len(dests) != 1:
371 raise error.Abort(
370 raise error.Abort(
372 _('--collapse does not work with multiple destinations'))
371 _('--collapse does not work with multiple destinations'))
373 destrev = next(iter(dests))
372 destrev = next(iter(dests))
374 destancestors = self.repo.changelog.ancestors([destrev],
373 destancestors = self.repo.changelog.ancestors([destrev],
375 inclusive=True)
374 inclusive=True)
376 self.external = externalparent(self.repo, self.state, destancestors)
375 self.external = externalparent(self.repo, self.state, destancestors)
377
376
378 for destrev in sorted(set(destmap.values())):
377 for destrev in sorted(set(destmap.values())):
379 dest = self.repo[destrev]
378 dest = self.repo[destrev]
380 if dest.closesbranch() and not self.keepbranchesf:
379 if dest.closesbranch() and not self.keepbranchesf:
381 self.ui.status(_('reopening closed branch head %s\n') % dest)
380 self.ui.status(_('reopening closed branch head %s\n') % dest)
382
381
383 self.prepared = True
382 self.prepared = True
384
383
385 def _performrebase(self, tr):
384 def _performrebase(self, tr):
386 repo, ui = self.repo, self.ui
385 repo, ui = self.repo, self.ui
387 if self.keepbranchesf:
386 if self.keepbranchesf:
388 # insert _savebranch at the start of extrafns so if
387 # insert _savebranch at the start of extrafns so if
389 # there's a user-provided extrafn it can clobber branch if
388 # there's a user-provided extrafn it can clobber branch if
390 # desired
389 # desired
391 self.extrafns.insert(0, _savebranch)
390 self.extrafns.insert(0, _savebranch)
392 if self.collapsef:
391 if self.collapsef:
393 branches = set()
392 branches = set()
394 for rev in self.state:
393 for rev in self.state:
395 branches.add(repo[rev].branch())
394 branches.add(repo[rev].branch())
396 if len(branches) > 1:
395 if len(branches) > 1:
397 raise error.Abort(_('cannot collapse multiple named '
396 raise error.Abort(_('cannot collapse multiple named '
398 'branches'))
397 'branches'))
399
398
400 # Calculate self.obsoletenotrebased
399 # Calculate self.obsoletenotrebased
401 obsrevs = _filterobsoleterevs(self.repo, self.state)
400 obsrevs = _filterobsoleterevs(self.repo, self.state)
402 self._handleskippingobsolete(obsrevs, self.destmap)
401 self._handleskippingobsolete(obsrevs, self.destmap)
403
402
404 # Keep track of the active bookmarks in order to reset them later
403 # Keep track of the active bookmarks in order to reset them later
405 self.activebookmark = self.activebookmark or repo._activebookmark
404 self.activebookmark = self.activebookmark or repo._activebookmark
406 if self.activebookmark:
405 if self.activebookmark:
407 bookmarks.deactivate(repo)
406 bookmarks.deactivate(repo)
408
407
409 # Store the state before we begin so users can run 'hg rebase --abort'
408 # Store the state before we begin so users can run 'hg rebase --abort'
410 # if we fail before the transaction closes.
409 # if we fail before the transaction closes.
411 self.storestatus()
410 self.storestatus()
412
411
413 cands = [k for k, v in self.state.iteritems() if v == revtodo]
412 cands = [k for k, v in self.state.iteritems() if v == revtodo]
414 total = len(cands)
413 total = len(cands)
415 pos = 0
414 pos = 0
416 for subset in sortsource(self.destmap):
415 for subset in sortsource(self.destmap):
417 pos = self._performrebasesubset(tr, subset, pos, total)
416 pos = self._performrebasesubset(tr, subset, pos, total)
418 ui.progress(_('rebasing'), None)
417 ui.progress(_('rebasing'), None)
419 ui.note(_('rebase merging completed\n'))
418 ui.note(_('rebase merging completed\n'))
420
419
421 def _performrebasesubset(self, tr, subset, pos, total):
420 def _performrebasesubset(self, tr, subset, pos, total):
422 repo, ui, opts = self.repo, self.ui, self.opts
421 repo, ui, opts = self.repo, self.ui, self.opts
423 sortedrevs = repo.revs('sort(%ld, -topo)', subset)
422 sortedrevs = repo.revs('sort(%ld, -topo)', subset)
424 allowdivergence = self.ui.configbool(
423 allowdivergence = self.ui.configbool(
425 'experimental', 'evolution.allowdivergence')
424 'experimental', 'evolution.allowdivergence')
426 if not allowdivergence:
425 if not allowdivergence:
427 sortedrevs -= repo.revs(
426 sortedrevs -= repo.revs(
428 'descendants(%ld) and not %ld',
427 'descendants(%ld) and not %ld',
429 self.obsoletewithoutsuccessorindestination,
428 self.obsoletewithoutsuccessorindestination,
430 self.obsoletewithoutsuccessorindestination,
429 self.obsoletewithoutsuccessorindestination,
431 )
430 )
432 for rev in sortedrevs:
431 for rev in sortedrevs:
433 dest = self.destmap[rev]
432 dest = self.destmap[rev]
434 ctx = repo[rev]
433 ctx = repo[rev]
435 desc = _ctxdesc(ctx)
434 desc = _ctxdesc(ctx)
436 if self.state[rev] == rev:
435 if self.state[rev] == rev:
437 ui.status(_('already rebased %s\n') % desc)
436 ui.status(_('already rebased %s\n') % desc)
438 elif (not allowdivergence
437 elif (not allowdivergence
439 and rev in self.obsoletewithoutsuccessorindestination):
438 and rev in self.obsoletewithoutsuccessorindestination):
440 msg = _('note: not rebasing %s and its descendants as '
439 msg = _('note: not rebasing %s and its descendants as '
441 'this would cause divergence\n') % desc
440 'this would cause divergence\n') % desc
442 repo.ui.status(msg)
441 repo.ui.status(msg)
443 self.skipped.add(rev)
442 self.skipped.add(rev)
444 elif rev in self.obsoletenotrebased:
443 elif rev in self.obsoletenotrebased:
445 succ = self.obsoletenotrebased[rev]
444 succ = self.obsoletenotrebased[rev]
446 if succ is None:
445 if succ is None:
447 msg = _('note: not rebasing %s, it has no '
446 msg = _('note: not rebasing %s, it has no '
448 'successor\n') % desc
447 'successor\n') % desc
449 else:
448 else:
450 succdesc = _ctxdesc(repo[succ])
449 succdesc = _ctxdesc(repo[succ])
451 msg = (_('note: not rebasing %s, already in '
450 msg = (_('note: not rebasing %s, already in '
452 'destination as %s\n') % (desc, succdesc))
451 'destination as %s\n') % (desc, succdesc))
453 repo.ui.status(msg)
452 repo.ui.status(msg)
454 # Make clearrebased aware state[rev] is not a true successor
453 # Make clearrebased aware state[rev] is not a true successor
455 self.skipped.add(rev)
454 self.skipped.add(rev)
456 # Record rev as moved to its desired destination in self.state.
455 # Record rev as moved to its desired destination in self.state.
457 # This helps bookmark and working parent movement.
456 # This helps bookmark and working parent movement.
458 dest = max(adjustdest(repo, rev, self.destmap, self.state,
457 dest = max(adjustdest(repo, rev, self.destmap, self.state,
459 self.skipped))
458 self.skipped))
460 self.state[rev] = dest
459 self.state[rev] = dest
461 elif self.state[rev] == revtodo:
460 elif self.state[rev] == revtodo:
462 pos += 1
461 pos += 1
463 ui.status(_('rebasing %s\n') % desc)
462 ui.status(_('rebasing %s\n') % desc)
464 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
463 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
465 _('changesets'), total)
464 _('changesets'), total)
466 p1, p2, base = defineparents(repo, rev, self.destmap,
465 p1, p2, base = defineparents(repo, rev, self.destmap,
467 self.state, self.skipped,
466 self.state, self.skipped,
468 self.obsoletenotrebased)
467 self.obsoletenotrebased)
469 self.storestatus(tr=tr)
468 self.storestatus(tr=tr)
470 storecollapsemsg(repo, self.collapsemsg)
469 storecollapsemsg(repo, self.collapsemsg)
471 if len(repo[None].parents()) == 2:
470 if len(repo[None].parents()) == 2:
472 repo.ui.debug('resuming interrupted rebase\n')
471 repo.ui.debug('resuming interrupted rebase\n')
473 else:
472 else:
474 try:
473 try:
475 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
474 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
476 'rebase')
475 'rebase')
477 stats = rebasenode(repo, rev, p1, base, self.state,
476 stats = rebasenode(repo, rev, p1, base, self.state,
478 self.collapsef, dest)
477 self.collapsef, dest)
479 if stats and stats[3] > 0:
478 if stats and stats[3] > 0:
480 raise error.InterventionRequired(
479 raise error.InterventionRequired(
481 _('unresolved conflicts (see hg '
480 _('unresolved conflicts (see hg '
482 'resolve, then hg rebase --continue)'))
481 'resolve, then hg rebase --continue)'))
483 finally:
482 finally:
484 ui.setconfig('ui', 'forcemerge', '', 'rebase')
483 ui.setconfig('ui', 'forcemerge', '', 'rebase')
485 if not self.collapsef:
484 if not self.collapsef:
486 merging = p2 != nullrev
485 merging = p2 != nullrev
487 editform = cmdutil.mergeeditform(merging, 'rebase')
486 editform = cmdutil.mergeeditform(merging, 'rebase')
488 editor = cmdutil.getcommiteditor(editform=editform, **opts)
487 editor = cmdutil.getcommiteditor(editform=editform, **opts)
489 newnode = concludenode(repo, rev, p1, p2,
488 newnode = concludenode(repo, rev, p1, p2,
490 extrafn=_makeextrafn(self.extrafns),
489 extrafn=_makeextrafn(self.extrafns),
491 editor=editor,
490 editor=editor,
492 keepbranches=self.keepbranchesf,
491 keepbranches=self.keepbranchesf,
493 date=self.date)
492 date=self.date)
494 if newnode is None:
493 if newnode is None:
495 # If it ended up being a no-op commit, then the normal
494 # If it ended up being a no-op commit, then the normal
496 # merge state clean-up path doesn't happen, so do it
495 # merge state clean-up path doesn't happen, so do it
497 # here. Fix issue5494
496 # here. Fix issue5494
498 mergemod.mergestate.clean(repo)
497 mergemod.mergestate.clean(repo)
499 else:
498 else:
500 # Skip commit if we are collapsing
499 # Skip commit if we are collapsing
501 repo.setparents(repo[p1].node())
500 repo.setparents(repo[p1].node())
502 newnode = None
501 newnode = None
503 # Update the state
502 # Update the state
504 if newnode is not None:
503 if newnode is not None:
505 self.state[rev] = repo[newnode].rev()
504 self.state[rev] = repo[newnode].rev()
506 ui.debug('rebased as %s\n' % short(newnode))
505 ui.debug('rebased as %s\n' % short(newnode))
507 else:
506 else:
508 if not self.collapsef:
507 if not self.collapsef:
509 ui.warn(_('note: rebase of %d:%s created no changes '
508 ui.warn(_('note: rebase of %d:%s created no changes '
510 'to commit\n') % (rev, ctx))
509 'to commit\n') % (rev, ctx))
511 self.skipped.add(rev)
510 self.skipped.add(rev)
512 self.state[rev] = p1
511 self.state[rev] = p1
513 ui.debug('next revision set to %s\n' % p1)
512 ui.debug('next revision set to %s\n' % p1)
514 else:
513 else:
515 ui.status(_('already rebased %s as %s\n') %
514 ui.status(_('already rebased %s as %s\n') %
516 (desc, repo[self.state[rev]]))
515 (desc, repo[self.state[rev]]))
517 return pos
516 return pos
518
517
519 def _finishrebase(self):
518 def _finishrebase(self):
520 repo, ui, opts = self.repo, self.ui, self.opts
519 repo, ui, opts = self.repo, self.ui, self.opts
521 fm = ui.formatter('rebase', opts)
520 fm = ui.formatter('rebase', opts)
522 fm.startitem()
521 fm.startitem()
523 if self.collapsef and not self.keepopen:
522 if self.collapsef and not self.keepopen:
524 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
523 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
525 self.state, self.skipped,
524 self.state, self.skipped,
526 self.obsoletenotrebased)
525 self.obsoletenotrebased)
527 editopt = opts.get('edit')
526 editopt = opts.get('edit')
528 editform = 'rebase.collapse'
527 editform = 'rebase.collapse'
529 if self.collapsemsg:
528 if self.collapsemsg:
530 commitmsg = self.collapsemsg
529 commitmsg = self.collapsemsg
531 else:
530 else:
532 commitmsg = 'Collapsed revision'
531 commitmsg = 'Collapsed revision'
533 for rebased in sorted(self.state):
532 for rebased in sorted(self.state):
534 if rebased not in self.skipped:
533 if rebased not in self.skipped:
535 commitmsg += '\n* %s' % repo[rebased].description()
534 commitmsg += '\n* %s' % repo[rebased].description()
536 editopt = True
535 editopt = True
537 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
536 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
538 revtoreuse = max(self.state)
537 revtoreuse = max(self.state)
539
538
540 dsguard = None
539 dsguard = None
541 if ui.configbool('rebase', 'singletransaction'):
540 if ui.configbool('rebase', 'singletransaction'):
542 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
541 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
543 with util.acceptintervention(dsguard):
542 with util.acceptintervention(dsguard):
544 newnode = concludenode(repo, revtoreuse, p1, self.external,
543 newnode = concludenode(repo, revtoreuse, p1, self.external,
545 commitmsg=commitmsg,
544 commitmsg=commitmsg,
546 extrafn=_makeextrafn(self.extrafns),
545 extrafn=_makeextrafn(self.extrafns),
547 editor=editor,
546 editor=editor,
548 keepbranches=self.keepbranchesf,
547 keepbranches=self.keepbranchesf,
549 date=self.date)
548 date=self.date)
550 if newnode is not None:
549 if newnode is not None:
551 newrev = repo[newnode].rev()
550 newrev = repo[newnode].rev()
552 for oldrev in self.state.iterkeys():
551 for oldrev in self.state.iterkeys():
553 self.state[oldrev] = newrev
552 self.state[oldrev] = newrev
554
553
555 if 'qtip' in repo.tags():
554 if 'qtip' in repo.tags():
556 updatemq(repo, self.state, self.skipped, **opts)
555 updatemq(repo, self.state, self.skipped, **opts)
557
556
558 # restore original working directory
557 # restore original working directory
559 # (we do this before stripping)
558 # (we do this before stripping)
560 newwd = self.state.get(self.originalwd, self.originalwd)
559 newwd = self.state.get(self.originalwd, self.originalwd)
561 if newwd < 0:
560 if newwd < 0:
562 # original directory is a parent of rebase set root or ignored
561 # original directory is a parent of rebase set root or ignored
563 newwd = self.originalwd
562 newwd = self.originalwd
564 if newwd not in [c.rev() for c in repo[None].parents()]:
563 if newwd not in [c.rev() for c in repo[None].parents()]:
565 ui.note(_("update back to initial working directory parent\n"))
564 ui.note(_("update back to initial working directory parent\n"))
566 hg.updaterepo(repo, newwd, False)
565 hg.updaterepo(repo, newwd, False)
567
566
568 collapsedas = None
567 collapsedas = None
569 if not self.keepf:
568 if not self.keepf:
570 if self.collapsef:
569 if self.collapsef:
571 collapsedas = newnode
570 collapsedas = newnode
572 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
571 clearrebased(ui, repo, self.destmap, self.state, self.skipped,
573 collapsedas, self.keepf, fm=fm)
572 collapsedas, self.keepf, fm=fm)
574
573
575 clearstatus(repo)
574 clearstatus(repo)
576 clearcollapsemsg(repo)
575 clearcollapsemsg(repo)
577
576
578 ui.note(_("rebase completed\n"))
577 ui.note(_("rebase completed\n"))
579 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
578 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
580 if self.skipped:
579 if self.skipped:
581 skippedlen = len(self.skipped)
580 skippedlen = len(self.skipped)
582 ui.note(_("%d revisions have been skipped\n") % skippedlen)
581 ui.note(_("%d revisions have been skipped\n") % skippedlen)
583 fm.end()
582 fm.end()
584
583
585 if (self.activebookmark and self.activebookmark in repo._bookmarks and
584 if (self.activebookmark and self.activebookmark in repo._bookmarks and
586 repo['.'].node() == repo._bookmarks[self.activebookmark]):
585 repo['.'].node() == repo._bookmarks[self.activebookmark]):
587 bookmarks.activate(repo, self.activebookmark)
586 bookmarks.activate(repo, self.activebookmark)
588
587
589 @command('rebase',
588 @command('rebase',
590 [('s', 'source', '',
589 [('s', 'source', '',
591 _('rebase the specified changeset and descendants'), _('REV')),
590 _('rebase the specified changeset and descendants'), _('REV')),
592 ('b', 'base', '',
591 ('b', 'base', '',
593 _('rebase everything from branching point of specified changeset'),
592 _('rebase everything from branching point of specified changeset'),
594 _('REV')),
593 _('REV')),
595 ('r', 'rev', [],
594 ('r', 'rev', [],
596 _('rebase these revisions'),
595 _('rebase these revisions'),
597 _('REV')),
596 _('REV')),
598 ('d', 'dest', '',
597 ('d', 'dest', '',
599 _('rebase onto the specified changeset'), _('REV')),
598 _('rebase onto the specified changeset'), _('REV')),
600 ('', 'collapse', False, _('collapse the rebased changesets')),
599 ('', 'collapse', False, _('collapse the rebased changesets')),
601 ('m', 'message', '',
600 ('m', 'message', '',
602 _('use text as collapse commit message'), _('TEXT')),
601 _('use text as collapse commit message'), _('TEXT')),
603 ('e', 'edit', False, _('invoke editor on commit messages')),
602 ('e', 'edit', False, _('invoke editor on commit messages')),
604 ('l', 'logfile', '',
603 ('l', 'logfile', '',
605 _('read collapse commit message from file'), _('FILE')),
604 _('read collapse commit message from file'), _('FILE')),
606 ('k', 'keep', False, _('keep original changesets')),
605 ('k', 'keep', False, _('keep original changesets')),
607 ('', 'keepbranches', False, _('keep original branch names')),
606 ('', 'keepbranches', False, _('keep original branch names')),
608 ('D', 'detach', False, _('(DEPRECATED)')),
607 ('D', 'detach', False, _('(DEPRECATED)')),
609 ('i', 'interactive', False, _('(DEPRECATED)')),
608 ('i', 'interactive', False, _('(DEPRECATED)')),
610 ('t', 'tool', '', _('specify merge tool')),
609 ('t', 'tool', '', _('specify merge tool')),
611 ('c', 'continue', False, _('continue an interrupted rebase')),
610 ('c', 'continue', False, _('continue an interrupted rebase')),
612 ('a', 'abort', False, _('abort an interrupted rebase'))] +
611 ('a', 'abort', False, _('abort an interrupted rebase'))] +
613 cmdutil.formatteropts,
612 cmdutil.formatteropts,
614 _('[-s REV | -b REV] [-d REV] [OPTION]'))
613 _('[-s REV | -b REV] [-d REV] [OPTION]'))
615 def rebase(ui, repo, **opts):
614 def rebase(ui, repo, **opts):
616 """move changeset (and descendants) to a different branch
615 """move changeset (and descendants) to a different branch
617
616
618 Rebase uses repeated merging to graft changesets from one part of
617 Rebase uses repeated merging to graft changesets from one part of
619 history (the source) onto another (the destination). This can be
618 history (the source) onto another (the destination). This can be
620 useful for linearizing *local* changes relative to a master
619 useful for linearizing *local* changes relative to a master
621 development tree.
620 development tree.
622
621
623 Published commits cannot be rebased (see :hg:`help phases`).
622 Published commits cannot be rebased (see :hg:`help phases`).
624 To copy commits, see :hg:`help graft`.
623 To copy commits, see :hg:`help graft`.
625
624
626 If you don't specify a destination changeset (``-d/--dest``), rebase
625 If you don't specify a destination changeset (``-d/--dest``), rebase
627 will use the same logic as :hg:`merge` to pick a destination. if
626 will use the same logic as :hg:`merge` to pick a destination. if
628 the current branch contains exactly one other head, the other head
627 the current branch contains exactly one other head, the other head
629 is merged with by default. Otherwise, an explicit revision with
628 is merged with by default. Otherwise, an explicit revision with
630 which to merge with must be provided. (destination changeset is not
629 which to merge with must be provided. (destination changeset is not
631 modified by rebasing, but new changesets are added as its
630 modified by rebasing, but new changesets are added as its
632 descendants.)
631 descendants.)
633
632
634 Here are the ways to select changesets:
633 Here are the ways to select changesets:
635
634
636 1. Explicitly select them using ``--rev``.
635 1. Explicitly select them using ``--rev``.
637
636
638 2. Use ``--source`` to select a root changeset and include all of its
637 2. Use ``--source`` to select a root changeset and include all of its
639 descendants.
638 descendants.
640
639
641 3. Use ``--base`` to select a changeset; rebase will find ancestors
640 3. Use ``--base`` to select a changeset; rebase will find ancestors
642 and their descendants which are not also ancestors of the destination.
641 and their descendants which are not also ancestors of the destination.
643
642
644 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
643 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
645 rebase will use ``--base .`` as above.
644 rebase will use ``--base .`` as above.
646
645
647 Rebase will destroy original changesets unless you use ``--keep``.
646 Rebase will destroy original changesets unless you use ``--keep``.
648 It will also move your bookmarks (even if you do).
647 It will also move your bookmarks (even if you do).
649
648
650 Some changesets may be dropped if they do not contribute changes
649 Some changesets may be dropped if they do not contribute changes
651 (e.g. merges from the destination branch).
650 (e.g. merges from the destination branch).
652
651
653 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
652 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
654 a named branch with two heads. You will need to explicitly specify source
653 a named branch with two heads. You will need to explicitly specify source
655 and/or destination.
654 and/or destination.
656
655
657 If you need to use a tool to automate merge/conflict decisions, you
656 If you need to use a tool to automate merge/conflict decisions, you
658 can specify one with ``--tool``, see :hg:`help merge-tools`.
657 can specify one with ``--tool``, see :hg:`help merge-tools`.
659 As a caveat: the tool will not be used to mediate when a file was
658 As a caveat: the tool will not be used to mediate when a file was
660 deleted, there is no hook presently available for this.
659 deleted, there is no hook presently available for this.
661
660
662 If a rebase is interrupted to manually resolve a conflict, it can be
661 If a rebase is interrupted to manually resolve a conflict, it can be
663 continued with --continue/-c or aborted with --abort/-a.
662 continued with --continue/-c or aborted with --abort/-a.
664
663
665 .. container:: verbose
664 .. container:: verbose
666
665
667 Examples:
666 Examples:
668
667
669 - move "local changes" (current commit back to branching point)
668 - move "local changes" (current commit back to branching point)
670 to the current branch tip after a pull::
669 to the current branch tip after a pull::
671
670
672 hg rebase
671 hg rebase
673
672
674 - move a single changeset to the stable branch::
673 - move a single changeset to the stable branch::
675
674
676 hg rebase -r 5f493448 -d stable
675 hg rebase -r 5f493448 -d stable
677
676
678 - splice a commit and all its descendants onto another part of history::
677 - splice a commit and all its descendants onto another part of history::
679
678
680 hg rebase --source c0c3 --dest 4cf9
679 hg rebase --source c0c3 --dest 4cf9
681
680
682 - rebase everything on a branch marked by a bookmark onto the
681 - rebase everything on a branch marked by a bookmark onto the
683 default branch::
682 default branch::
684
683
685 hg rebase --base myfeature --dest default
684 hg rebase --base myfeature --dest default
686
685
687 - collapse a sequence of changes into a single commit::
686 - collapse a sequence of changes into a single commit::
688
687
689 hg rebase --collapse -r 1520:1525 -d .
688 hg rebase --collapse -r 1520:1525 -d .
690
689
691 - move a named branch while preserving its name::
690 - move a named branch while preserving its name::
692
691
693 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
692 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
694
693
695 Configuration Options:
694 Configuration Options:
696
695
697 You can make rebase require a destination if you set the following config
696 You can make rebase require a destination if you set the following config
698 option::
697 option::
699
698
700 [commands]
699 [commands]
701 rebase.requiredest = True
700 rebase.requiredest = True
702
701
703 By default, rebase will close the transaction after each commit. For
702 By default, rebase will close the transaction after each commit. For
704 performance purposes, you can configure rebase to use a single transaction
703 performance purposes, you can configure rebase to use a single transaction
705 across the entire rebase. WARNING: This setting introduces a significant
704 across the entire rebase. WARNING: This setting introduces a significant
706 risk of losing the work you've done in a rebase if the rebase aborts
705 risk of losing the work you've done in a rebase if the rebase aborts
707 unexpectedly::
706 unexpectedly::
708
707
709 [rebase]
708 [rebase]
710 singletransaction = True
709 singletransaction = True
711
710
712 Return Values:
711 Return Values:
713
712
714 Returns 0 on success, 1 if nothing to rebase or there are
713 Returns 0 on success, 1 if nothing to rebase or there are
715 unresolved conflicts.
714 unresolved conflicts.
716
715
717 """
716 """
718 opts = pycompat.byteskwargs(opts)
717 opts = pycompat.byteskwargs(opts)
719 rbsrt = rebaseruntime(repo, ui, opts)
718 rbsrt = rebaseruntime(repo, ui, opts)
720
719
721 with repo.wlock(), repo.lock():
720 with repo.wlock(), repo.lock():
722 # Validate input and define rebasing points
721 # Validate input and define rebasing points
723 destf = opts.get('dest', None)
722 destf = opts.get('dest', None)
724 srcf = opts.get('source', None)
723 srcf = opts.get('source', None)
725 basef = opts.get('base', None)
724 basef = opts.get('base', None)
726 revf = opts.get('rev', [])
725 revf = opts.get('rev', [])
727 # search default destination in this space
726 # search default destination in this space
728 # used in the 'hg pull --rebase' case, see issue 5214.
727 # used in the 'hg pull --rebase' case, see issue 5214.
729 destspace = opts.get('_destspace')
728 destspace = opts.get('_destspace')
730 contf = opts.get('continue')
729 contf = opts.get('continue')
731 abortf = opts.get('abort')
730 abortf = opts.get('abort')
732 if opts.get('interactive'):
731 if opts.get('interactive'):
733 try:
732 try:
734 if extensions.find('histedit'):
733 if extensions.find('histedit'):
735 enablehistedit = ''
734 enablehistedit = ''
736 except KeyError:
735 except KeyError:
737 enablehistedit = " --config extensions.histedit="
736 enablehistedit = " --config extensions.histedit="
738 help = "hg%s help -e histedit" % enablehistedit
737 help = "hg%s help -e histedit" % enablehistedit
739 msg = _("interactive history editing is supported by the "
738 msg = _("interactive history editing is supported by the "
740 "'histedit' extension (see \"%s\")") % help
739 "'histedit' extension (see \"%s\")") % help
741 raise error.Abort(msg)
740 raise error.Abort(msg)
742
741
743 if rbsrt.collapsemsg and not rbsrt.collapsef:
742 if rbsrt.collapsemsg and not rbsrt.collapsef:
744 raise error.Abort(
743 raise error.Abort(
745 _('message can only be specified with collapse'))
744 _('message can only be specified with collapse'))
746
745
747 if contf or abortf:
746 if contf or abortf:
748 if contf and abortf:
747 if contf and abortf:
749 raise error.Abort(_('cannot use both abort and continue'))
748 raise error.Abort(_('cannot use both abort and continue'))
750 if rbsrt.collapsef:
749 if rbsrt.collapsef:
751 raise error.Abort(
750 raise error.Abort(
752 _('cannot use collapse with continue or abort'))
751 _('cannot use collapse with continue or abort'))
753 if srcf or basef or destf:
752 if srcf or basef or destf:
754 raise error.Abort(
753 raise error.Abort(
755 _('abort and continue do not allow specifying revisions'))
754 _('abort and continue do not allow specifying revisions'))
756 if abortf and opts.get('tool', False):
755 if abortf and opts.get('tool', False):
757 ui.warn(_('tool option will be ignored\n'))
756 ui.warn(_('tool option will be ignored\n'))
758 if contf:
757 if contf:
759 ms = mergemod.mergestate.read(repo)
758 ms = mergemod.mergestate.read(repo)
760 mergeutil.checkunresolved(ms)
759 mergeutil.checkunresolved(ms)
761
760
762 retcode = rbsrt._prepareabortorcontinue(abortf)
761 retcode = rbsrt._prepareabortorcontinue(abortf)
763 if retcode is not None:
762 if retcode is not None:
764 return retcode
763 return retcode
765 else:
764 else:
766 destmap = _definedestmap(ui, repo, destf, srcf, basef, revf,
765 destmap = _definedestmap(ui, repo, destf, srcf, basef, revf,
767 destspace=destspace)
766 destspace=destspace)
768 retcode = rbsrt._preparenewrebase(destmap)
767 retcode = rbsrt._preparenewrebase(destmap)
769 if retcode is not None:
768 if retcode is not None:
770 return retcode
769 return retcode
771
770
772 tr = None
771 tr = None
773 dsguard = None
772 dsguard = None
774
773
775 singletr = ui.configbool('rebase', 'singletransaction')
774 singletr = ui.configbool('rebase', 'singletransaction')
776 if singletr:
775 if singletr:
777 tr = repo.transaction('rebase')
776 tr = repo.transaction('rebase')
778 with util.acceptintervention(tr):
777 with util.acceptintervention(tr):
779 if singletr:
778 if singletr:
780 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
779 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
781 with util.acceptintervention(dsguard):
780 with util.acceptintervention(dsguard):
782 rbsrt._performrebase(tr)
781 rbsrt._performrebase(tr)
783
782
784 rbsrt._finishrebase()
783 rbsrt._finishrebase()
785
784
786 def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None,
785 def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None,
787 destspace=None):
786 destspace=None):
788 """use revisions argument to define destmap {srcrev: destrev}"""
787 """use revisions argument to define destmap {srcrev: destrev}"""
789 if revf is None:
788 if revf is None:
790 revf = []
789 revf = []
791
790
792 # destspace is here to work around issues with `hg pull --rebase` see
791 # destspace is here to work around issues with `hg pull --rebase` see
793 # issue5214 for details
792 # issue5214 for details
794 if srcf and basef:
793 if srcf and basef:
795 raise error.Abort(_('cannot specify both a source and a base'))
794 raise error.Abort(_('cannot specify both a source and a base'))
796 if revf and basef:
795 if revf and basef:
797 raise error.Abort(_('cannot specify both a revision and a base'))
796 raise error.Abort(_('cannot specify both a revision and a base'))
798 if revf and srcf:
797 if revf and srcf:
799 raise error.Abort(_('cannot specify both a revision and a source'))
798 raise error.Abort(_('cannot specify both a revision and a source'))
800
799
801 cmdutil.checkunfinished(repo)
800 cmdutil.checkunfinished(repo)
802 cmdutil.bailifchanged(repo)
801 cmdutil.bailifchanged(repo)
803
802
804 if ui.configbool('commands', 'rebase.requiredest') and not destf:
803 if ui.configbool('commands', 'rebase.requiredest') and not destf:
805 raise error.Abort(_('you must specify a destination'),
804 raise error.Abort(_('you must specify a destination'),
806 hint=_('use: hg rebase -d REV'))
805 hint=_('use: hg rebase -d REV'))
807
806
808 dest = None
807 dest = None
809
808
810 if revf:
809 if revf:
811 rebaseset = scmutil.revrange(repo, revf)
810 rebaseset = scmutil.revrange(repo, revf)
812 if not rebaseset:
811 if not rebaseset:
813 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
812 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
814 return None
813 return None
815 elif srcf:
814 elif srcf:
816 src = scmutil.revrange(repo, [srcf])
815 src = scmutil.revrange(repo, [srcf])
817 if not src:
816 if not src:
818 ui.status(_('empty "source" revision set - nothing to rebase\n'))
817 ui.status(_('empty "source" revision set - nothing to rebase\n'))
819 return None
818 return None
820 rebaseset = repo.revs('(%ld)::', src)
819 rebaseset = repo.revs('(%ld)::', src)
821 assert rebaseset
820 assert rebaseset
822 else:
821 else:
823 base = scmutil.revrange(repo, [basef or '.'])
822 base = scmutil.revrange(repo, [basef or '.'])
824 if not base:
823 if not base:
825 ui.status(_('empty "base" revision set - '
824 ui.status(_('empty "base" revision set - '
826 "can't compute rebase set\n"))
825 "can't compute rebase set\n"))
827 return None
826 return None
828 if destf:
827 if destf:
829 # --base does not support multiple destinations
828 # --base does not support multiple destinations
830 dest = scmutil.revsingle(repo, destf)
829 dest = scmutil.revsingle(repo, destf)
831 else:
830 else:
832 dest = repo[_destrebase(repo, base, destspace=destspace)]
831 dest = repo[_destrebase(repo, base, destspace=destspace)]
833 destf = str(dest)
832 destf = str(dest)
834
833
835 roots = [] # selected children of branching points
834 roots = [] # selected children of branching points
836 bpbase = {} # {branchingpoint: [origbase]}
835 bpbase = {} # {branchingpoint: [origbase]}
837 for b in base: # group bases by branching points
836 for b in base: # group bases by branching points
838 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
837 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
839 bpbase[bp] = bpbase.get(bp, []) + [b]
838 bpbase[bp] = bpbase.get(bp, []) + [b]
840 if None in bpbase:
839 if None in bpbase:
841 # emulate the old behavior, showing "nothing to rebase" (a better
840 # emulate the old behavior, showing "nothing to rebase" (a better
842 # behavior may be abort with "cannot find branching point" error)
841 # behavior may be abort with "cannot find branching point" error)
843 bpbase.clear()
842 bpbase.clear()
844 for bp, bs in bpbase.iteritems(): # calculate roots
843 for bp, bs in bpbase.iteritems(): # calculate roots
845 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
844 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
846
845
847 rebaseset = repo.revs('%ld::', roots)
846 rebaseset = repo.revs('%ld::', roots)
848
847
849 if not rebaseset:
848 if not rebaseset:
850 # transform to list because smartsets are not comparable to
849 # transform to list because smartsets are not comparable to
851 # lists. This should be improved to honor laziness of
850 # lists. This should be improved to honor laziness of
852 # smartset.
851 # smartset.
853 if list(base) == [dest.rev()]:
852 if list(base) == [dest.rev()]:
854 if basef:
853 if basef:
855 ui.status(_('nothing to rebase - %s is both "base"'
854 ui.status(_('nothing to rebase - %s is both "base"'
856 ' and destination\n') % dest)
855 ' and destination\n') % dest)
857 else:
856 else:
858 ui.status(_('nothing to rebase - working directory '
857 ui.status(_('nothing to rebase - working directory '
859 'parent is also destination\n'))
858 'parent is also destination\n'))
860 elif not repo.revs('%ld - ::%d', base, dest):
859 elif not repo.revs('%ld - ::%d', base, dest):
861 if basef:
860 if basef:
862 ui.status(_('nothing to rebase - "base" %s is '
861 ui.status(_('nothing to rebase - "base" %s is '
863 'already an ancestor of destination '
862 'already an ancestor of destination '
864 '%s\n') %
863 '%s\n') %
865 ('+'.join(str(repo[r]) for r in base),
864 ('+'.join(str(repo[r]) for r in base),
866 dest))
865 dest))
867 else:
866 else:
868 ui.status(_('nothing to rebase - working '
867 ui.status(_('nothing to rebase - working '
869 'directory parent is already an '
868 'directory parent is already an '
870 'ancestor of destination %s\n') % dest)
869 'ancestor of destination %s\n') % dest)
871 else: # can it happen?
870 else: # can it happen?
872 ui.status(_('nothing to rebase from %s to %s\n') %
871 ui.status(_('nothing to rebase from %s to %s\n') %
873 ('+'.join(str(repo[r]) for r in base), dest))
872 ('+'.join(str(repo[r]) for r in base), dest))
874 return None
873 return None
875
874
876 if not destf:
875 if not destf:
877 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
876 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
878 destf = str(dest)
877 destf = str(dest)
879
878
880 allsrc = revsetlang.formatspec('%ld', rebaseset)
879 allsrc = revsetlang.formatspec('%ld', rebaseset)
881 alias = {'ALLSRC': allsrc}
880 alias = {'ALLSRC': allsrc}
882
881
883 if dest is None:
882 if dest is None:
884 try:
883 try:
885 # fast path: try to resolve dest without SRC alias
884 # fast path: try to resolve dest without SRC alias
886 dest = scmutil.revsingle(repo, destf, localalias=alias)
885 dest = scmutil.revsingle(repo, destf, localalias=alias)
887 except error.RepoLookupError:
886 except error.RepoLookupError:
888 if not ui.configbool('experimental', 'rebase.multidest'):
887 if not ui.configbool('experimental', 'rebase.multidest'):
889 raise
888 raise
890 # multi-dest path: resolve dest for each SRC separately
889 # multi-dest path: resolve dest for each SRC separately
891 destmap = {}
890 destmap = {}
892 for r in rebaseset:
891 for r in rebaseset:
893 alias['SRC'] = revsetlang.formatspec('%d', r)
892 alias['SRC'] = revsetlang.formatspec('%d', r)
894 # use repo.anyrevs instead of scmutil.revsingle because we
893 # use repo.anyrevs instead of scmutil.revsingle because we
895 # don't want to abort if destset is empty.
894 # don't want to abort if destset is empty.
896 destset = repo.anyrevs([destf], user=True, localalias=alias)
895 destset = repo.anyrevs([destf], user=True, localalias=alias)
897 size = len(destset)
896 size = len(destset)
898 if size == 1:
897 if size == 1:
899 destmap[r] = destset.first()
898 destmap[r] = destset.first()
900 elif size == 0:
899 elif size == 0:
901 ui.note(_('skipping %s - empty destination\n') % repo[r])
900 ui.note(_('skipping %s - empty destination\n') % repo[r])
902 else:
901 else:
903 raise error.Abort(_('rebase destination for %s is not '
902 raise error.Abort(_('rebase destination for %s is not '
904 'unique') % repo[r])
903 'unique') % repo[r])
905
904
906 if dest is not None:
905 if dest is not None:
907 # single-dest case: assign dest to each rev in rebaseset
906 # single-dest case: assign dest to each rev in rebaseset
908 destrev = dest.rev()
907 destrev = dest.rev()
909 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
908 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
910
909
911 if not destmap:
910 if not destmap:
912 ui.status(_('nothing to rebase - empty destination\n'))
911 ui.status(_('nothing to rebase - empty destination\n'))
913 return None
912 return None
914
913
915 return destmap
914 return destmap
916
915
917 def externalparent(repo, state, destancestors):
916 def externalparent(repo, state, destancestors):
918 """Return the revision that should be used as the second parent
917 """Return the revision that should be used as the second parent
919 when the revisions in state is collapsed on top of destancestors.
918 when the revisions in state is collapsed on top of destancestors.
920 Abort if there is more than one parent.
919 Abort if there is more than one parent.
921 """
920 """
922 parents = set()
921 parents = set()
923 source = min(state)
922 source = min(state)
924 for rev in state:
923 for rev in state:
925 if rev == source:
924 if rev == source:
926 continue
925 continue
927 for p in repo[rev].parents():
926 for p in repo[rev].parents():
928 if (p.rev() not in state
927 if (p.rev() not in state
929 and p.rev() not in destancestors):
928 and p.rev() not in destancestors):
930 parents.add(p.rev())
929 parents.add(p.rev())
931 if not parents:
930 if not parents:
932 return nullrev
931 return nullrev
933 if len(parents) == 1:
932 if len(parents) == 1:
934 return parents.pop()
933 return parents.pop()
935 raise error.Abort(_('unable to collapse on top of %s, there is more '
934 raise error.Abort(_('unable to collapse on top of %s, there is more '
936 'than one external parent: %s') %
935 'than one external parent: %s') %
937 (max(destancestors),
936 (max(destancestors),
938 ', '.join(str(p) for p in sorted(parents))))
937 ', '.join(str(p) for p in sorted(parents))))
939
938
940 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
939 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
941 keepbranches=False, date=None):
940 keepbranches=False, date=None):
942 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
941 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
943 but also store useful information in extra.
942 but also store useful information in extra.
944 Return node of committed revision.'''
943 Return node of committed revision.'''
945 dsguard = util.nullcontextmanager()
944 dsguard = util.nullcontextmanager()
946 if not repo.ui.configbool('rebase', 'singletransaction'):
945 if not repo.ui.configbool('rebase', 'singletransaction'):
947 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
946 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
948 with dsguard:
947 with dsguard:
949 repo.setparents(repo[p1].node(), repo[p2].node())
948 repo.setparents(repo[p1].node(), repo[p2].node())
950 ctx = repo[rev]
949 ctx = repo[rev]
951 if commitmsg is None:
950 if commitmsg is None:
952 commitmsg = ctx.description()
951 commitmsg = ctx.description()
953 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
952 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
954 extra = {'rebase_source': ctx.hex()}
953 extra = {'rebase_source': ctx.hex()}
955 if extrafn:
954 if extrafn:
956 extrafn(ctx, extra)
955 extrafn(ctx, extra)
957
956
958 destphase = max(ctx.phase(), phases.draft)
957 destphase = max(ctx.phase(), phases.draft)
959 overrides = {('phases', 'new-commit'): destphase}
958 overrides = {('phases', 'new-commit'): destphase}
960 with repo.ui.configoverride(overrides, 'rebase'):
959 with repo.ui.configoverride(overrides, 'rebase'):
961 if keepbranch:
960 if keepbranch:
962 repo.ui.setconfig('ui', 'allowemptycommit', True)
961 repo.ui.setconfig('ui', 'allowemptycommit', True)
963 # Commit might fail if unresolved files exist
962 # Commit might fail if unresolved files exist
964 if date is None:
963 if date is None:
965 date = ctx.date()
964 date = ctx.date()
966 newnode = repo.commit(text=commitmsg, user=ctx.user(),
965 newnode = repo.commit(text=commitmsg, user=ctx.user(),
967 date=date, extra=extra, editor=editor)
966 date=date, extra=extra, editor=editor)
968
967
969 repo.dirstate.setbranch(repo[newnode].branch())
968 repo.dirstate.setbranch(repo[newnode].branch())
970 return newnode
969 return newnode
971
970
972 def rebasenode(repo, rev, p1, base, state, collapse, dest):
971 def rebasenode(repo, rev, p1, base, state, collapse, dest):
973 'Rebase a single revision rev on top of p1 using base as merge ancestor'
972 'Rebase a single revision rev on top of p1 using base as merge ancestor'
974 # Merge phase
973 # Merge phase
975 # Update to destination and merge it with local
974 # Update to destination and merge it with local
976 if repo['.'].rev() != p1:
975 if repo['.'].rev() != p1:
977 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
976 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
978 mergemod.update(repo, p1, False, True)
977 mergemod.update(repo, p1, False, True)
979 else:
978 else:
980 repo.ui.debug(" already in destination\n")
979 repo.ui.debug(" already in destination\n")
981 repo.dirstate.write(repo.currenttransaction())
980 repo.dirstate.write(repo.currenttransaction())
982 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
981 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
983 if base is not None:
982 if base is not None:
984 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
983 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
985 # When collapsing in-place, the parent is the common ancestor, we
984 # When collapsing in-place, the parent is the common ancestor, we
986 # have to allow merging with it.
985 # have to allow merging with it.
987 wctx = repo[None]
986 wctx = repo[None]
988 stats = mergemod.update(repo, rev, True, True, base, collapse,
987 stats = mergemod.update(repo, rev, True, True, base, collapse,
989 labels=['dest', 'source'])
988 labels=['dest', 'source'])
990 if collapse:
989 if collapse:
991 copies.duplicatecopies(repo, wctx, rev, dest)
990 copies.duplicatecopies(repo, wctx, rev, dest)
992 else:
991 else:
993 # If we're not using --collapse, we need to
992 # If we're not using --collapse, we need to
994 # duplicate copies between the revision we're
993 # duplicate copies between the revision we're
995 # rebasing and its first parent, but *not*
994 # rebasing and its first parent, but *not*
996 # duplicate any copies that have already been
995 # duplicate any copies that have already been
997 # performed in the destination.
996 # performed in the destination.
998 p1rev = repo[rev].p1().rev()
997 p1rev = repo[rev].p1().rev()
999 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
998 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1000 return stats
999 return stats
1001
1000
1002 def adjustdest(repo, rev, destmap, state, skipped):
1001 def adjustdest(repo, rev, destmap, state, skipped):
1003 """adjust rebase destination given the current rebase state
1002 """adjust rebase destination given the current rebase state
1004
1003
1005 rev is what is being rebased. Return a list of two revs, which are the
1004 rev is what is being rebased. Return a list of two revs, which are the
1006 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1005 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1007 nullrev, return dest without adjustment for it.
1006 nullrev, return dest without adjustment for it.
1008
1007
1009 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1008 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1010 to B1, and E's destination will be adjusted from F to B1.
1009 to B1, and E's destination will be adjusted from F to B1.
1011
1010
1012 B1 <- written during rebasing B
1011 B1 <- written during rebasing B
1013 |
1012 |
1014 F <- original destination of B, E
1013 F <- original destination of B, E
1015 |
1014 |
1016 | E <- rev, which is being rebased
1015 | E <- rev, which is being rebased
1017 | |
1016 | |
1018 | D <- prev, one parent of rev being checked
1017 | D <- prev, one parent of rev being checked
1019 | |
1018 | |
1020 | x <- skipped, ex. no successor or successor in (::dest)
1019 | x <- skipped, ex. no successor or successor in (::dest)
1021 | |
1020 | |
1022 | C <- rebased as C', different destination
1021 | C <- rebased as C', different destination
1023 | |
1022 | |
1024 | B <- rebased as B1 C'
1023 | B <- rebased as B1 C'
1025 |/ |
1024 |/ |
1026 A G <- destination of C, different
1025 A G <- destination of C, different
1027
1026
1028 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1027 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1029 first move C to C1, G to G1, and when it's checking H, the adjusted
1028 first move C to C1, G to G1, and when it's checking H, the adjusted
1030 destinations will be [C1, G1].
1029 destinations will be [C1, G1].
1031
1030
1032 H C1 G1
1031 H C1 G1
1033 /| | /
1032 /| | /
1034 F G |/
1033 F G |/
1035 K | | -> K
1034 K | | -> K
1036 | C D |
1035 | C D |
1037 | |/ |
1036 | |/ |
1038 | B | ...
1037 | B | ...
1039 |/ |/
1038 |/ |/
1040 A A
1039 A A
1041
1040
1042 Besides, adjust dest according to existing rebase information. For example,
1041 Besides, adjust dest according to existing rebase information. For example,
1043
1042
1044 B C D B needs to be rebased on top of C, C needs to be rebased on top
1043 B C D B needs to be rebased on top of C, C needs to be rebased on top
1045 \|/ of D. We will rebase C first.
1044 \|/ of D. We will rebase C first.
1046 A
1045 A
1047
1046
1048 C' After rebasing C, when considering B's destination, use C'
1047 C' After rebasing C, when considering B's destination, use C'
1049 | instead of the original C.
1048 | instead of the original C.
1050 B D
1049 B D
1051 \ /
1050 \ /
1052 A
1051 A
1053 """
1052 """
1054 # pick already rebased revs with same dest from state as interesting source
1053 # pick already rebased revs with same dest from state as interesting source
1055 dest = destmap[rev]
1054 dest = destmap[rev]
1056 source = [s for s, d in state.items()
1055 source = [s for s, d in state.items()
1057 if d > 0 and destmap[s] == dest and s not in skipped]
1056 if d > 0 and destmap[s] == dest and s not in skipped]
1058
1057
1059 result = []
1058 result = []
1060 for prev in repo.changelog.parentrevs(rev):
1059 for prev in repo.changelog.parentrevs(rev):
1061 adjusted = dest
1060 adjusted = dest
1062 if prev != nullrev:
1061 if prev != nullrev:
1063 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1062 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1064 if candidate is not None:
1063 if candidate is not None:
1065 adjusted = state[candidate]
1064 adjusted = state[candidate]
1066 if adjusted == dest and dest in state:
1065 if adjusted == dest and dest in state:
1067 adjusted = state[dest]
1066 adjusted = state[dest]
1068 if adjusted == revtodo:
1067 if adjusted == revtodo:
1069 # sortsource should produce an order that makes this impossible
1068 # sortsource should produce an order that makes this impossible
1070 raise error.ProgrammingError(
1069 raise error.ProgrammingError(
1071 'rev %d should be rebased already at this time' % dest)
1070 'rev %d should be rebased already at this time' % dest)
1072 result.append(adjusted)
1071 result.append(adjusted)
1073 return result
1072 return result
1074
1073
1075 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1074 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1076 """
1075 """
1077 Abort if rebase will create divergence or rebase is noop because of markers
1076 Abort if rebase will create divergence or rebase is noop because of markers
1078
1077
1079 `rebaseobsrevs`: set of obsolete revision in source
1078 `rebaseobsrevs`: set of obsolete revision in source
1080 `rebaseobsskipped`: set of revisions from source skipped because they have
1079 `rebaseobsskipped`: set of revisions from source skipped because they have
1081 successors in destination
1080 successors in destination
1082 """
1081 """
1083 # Obsolete node with successors not in dest leads to divergence
1082 # Obsolete node with successors not in dest leads to divergence
1084 divergenceok = ui.configbool('experimental',
1083 divergenceok = ui.configbool('experimental',
1085 'evolution.allowdivergence')
1084 'evolution.allowdivergence')
1086 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1085 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1087
1086
1088 if divergencebasecandidates and not divergenceok:
1087 if divergencebasecandidates and not divergenceok:
1089 divhashes = (str(repo[r])
1088 divhashes = (str(repo[r])
1090 for r in divergencebasecandidates)
1089 for r in divergencebasecandidates)
1091 msg = _("this rebase will cause "
1090 msg = _("this rebase will cause "
1092 "divergences from: %s")
1091 "divergences from: %s")
1093 h = _("to force the rebase please set "
1092 h = _("to force the rebase please set "
1094 "experimental.evolution.allowdivergence=True")
1093 "experimental.evolution.allowdivergence=True")
1095 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1094 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1096
1095
1097 def successorrevs(unfi, rev):
1096 def successorrevs(unfi, rev):
1098 """yield revision numbers for successors of rev"""
1097 """yield revision numbers for successors of rev"""
1099 assert unfi.filtername is None
1098 assert unfi.filtername is None
1100 nodemap = unfi.changelog.nodemap
1099 nodemap = unfi.changelog.nodemap
1101 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1100 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1102 if s in nodemap:
1101 if s in nodemap:
1103 yield nodemap[s]
1102 yield nodemap[s]
1104
1103
1105 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1104 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1106 """Return new parents and optionally a merge base for rev being rebased
1105 """Return new parents and optionally a merge base for rev being rebased
1107
1106
1108 The destination specified by "dest" cannot always be used directly because
1107 The destination specified by "dest" cannot always be used directly because
1109 previously rebase result could affect destination. For example,
1108 previously rebase result could affect destination. For example,
1110
1109
1111 D E rebase -r C+D+E -d B
1110 D E rebase -r C+D+E -d B
1112 |/ C will be rebased to C'
1111 |/ C will be rebased to C'
1113 B C D's new destination will be C' instead of B
1112 B C D's new destination will be C' instead of B
1114 |/ E's new destination will be C' instead of B
1113 |/ E's new destination will be C' instead of B
1115 A
1114 A
1116
1115
1117 The new parents of a merge is slightly more complicated. See the comment
1116 The new parents of a merge is slightly more complicated. See the comment
1118 block below.
1117 block below.
1119 """
1118 """
1120 # use unfiltered changelog since successorrevs may return filtered nodes
1119 # use unfiltered changelog since successorrevs may return filtered nodes
1121 assert repo.filtername is None
1120 assert repo.filtername is None
1122 cl = repo.changelog
1121 cl = repo.changelog
1123 def isancestor(a, b):
1122 def isancestor(a, b):
1124 # take revision numbers instead of nodes
1123 # take revision numbers instead of nodes
1125 if a == b:
1124 if a == b:
1126 return True
1125 return True
1127 elif a > b:
1126 elif a > b:
1128 return False
1127 return False
1129 return cl.isancestor(cl.node(a), cl.node(b))
1128 return cl.isancestor(cl.node(a), cl.node(b))
1130
1129
1131 dest = destmap[rev]
1130 dest = destmap[rev]
1132 oldps = repo.changelog.parentrevs(rev) # old parents
1131 oldps = repo.changelog.parentrevs(rev) # old parents
1133 newps = [nullrev, nullrev] # new parents
1132 newps = [nullrev, nullrev] # new parents
1134 dests = adjustdest(repo, rev, destmap, state, skipped)
1133 dests = adjustdest(repo, rev, destmap, state, skipped)
1135 bases = list(oldps) # merge base candidates, initially just old parents
1134 bases = list(oldps) # merge base candidates, initially just old parents
1136
1135
1137 if all(r == nullrev for r in oldps[1:]):
1136 if all(r == nullrev for r in oldps[1:]):
1138 # For non-merge changeset, just move p to adjusted dest as requested.
1137 # For non-merge changeset, just move p to adjusted dest as requested.
1139 newps[0] = dests[0]
1138 newps[0] = dests[0]
1140 else:
1139 else:
1141 # For merge changeset, if we move p to dests[i] unconditionally, both
1140 # For merge changeset, if we move p to dests[i] unconditionally, both
1142 # parents may change and the end result looks like "the merge loses a
1141 # parents may change and the end result looks like "the merge loses a
1143 # parent", which is a surprise. This is a limit because "--dest" only
1142 # parent", which is a surprise. This is a limit because "--dest" only
1144 # accepts one dest per src.
1143 # accepts one dest per src.
1145 #
1144 #
1146 # Therefore, only move p with reasonable conditions (in this order):
1145 # Therefore, only move p with reasonable conditions (in this order):
1147 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1146 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1148 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1147 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1149 #
1148 #
1150 # Comparing with adjustdest, the logic here does some additional work:
1149 # Comparing with adjustdest, the logic here does some additional work:
1151 # 1. decide which parents will not be moved towards dest
1150 # 1. decide which parents will not be moved towards dest
1152 # 2. if the above decision is "no", should a parent still be moved
1151 # 2. if the above decision is "no", should a parent still be moved
1153 # because it was rebased?
1152 # because it was rebased?
1154 #
1153 #
1155 # For example:
1154 # For example:
1156 #
1155 #
1157 # C # "rebase -r C -d D" is an error since none of the parents
1156 # C # "rebase -r C -d D" is an error since none of the parents
1158 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1157 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1159 # A B D # B (using rule "2."), since B will be rebased.
1158 # A B D # B (using rule "2."), since B will be rebased.
1160 #
1159 #
1161 # The loop tries to be not rely on the fact that a Mercurial node has
1160 # The loop tries to be not rely on the fact that a Mercurial node has
1162 # at most 2 parents.
1161 # at most 2 parents.
1163 for i, p in enumerate(oldps):
1162 for i, p in enumerate(oldps):
1164 np = p # new parent
1163 np = p # new parent
1165 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1164 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1166 np = dests[i]
1165 np = dests[i]
1167 elif p in state and state[p] > 0:
1166 elif p in state and state[p] > 0:
1168 np = state[p]
1167 np = state[p]
1169
1168
1170 # "bases" only record "special" merge bases that cannot be
1169 # "bases" only record "special" merge bases that cannot be
1171 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1170 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1172 # For example:
1171 # For example:
1173 #
1172 #
1174 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1173 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1175 # | C # is B', but merge base for C is B, instead of
1174 # | C # is B', but merge base for C is B, instead of
1176 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1175 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1177 # | B # "state" edges are merged (so there will be an edge from
1176 # | B # "state" edges are merged (so there will be an edge from
1178 # |/ # B to B'), the merge base is still ancestor(C, B') in
1177 # |/ # B to B'), the merge base is still ancestor(C, B') in
1179 # A # the merged graph.
1178 # A # the merged graph.
1180 #
1179 #
1181 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1180 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1182 # which uses "virtual null merge" to explain this situation.
1181 # which uses "virtual null merge" to explain this situation.
1183 if isancestor(p, np):
1182 if isancestor(p, np):
1184 bases[i] = nullrev
1183 bases[i] = nullrev
1185
1184
1186 # If one parent becomes an ancestor of the other, drop the ancestor
1185 # If one parent becomes an ancestor of the other, drop the ancestor
1187 for j, x in enumerate(newps[:i]):
1186 for j, x in enumerate(newps[:i]):
1188 if x == nullrev:
1187 if x == nullrev:
1189 continue
1188 continue
1190 if isancestor(np, x): # CASE-1
1189 if isancestor(np, x): # CASE-1
1191 np = nullrev
1190 np = nullrev
1192 elif isancestor(x, np): # CASE-2
1191 elif isancestor(x, np): # CASE-2
1193 newps[j] = np
1192 newps[j] = np
1194 np = nullrev
1193 np = nullrev
1195 # New parents forming an ancestor relationship does not
1194 # New parents forming an ancestor relationship does not
1196 # mean the old parents have a similar relationship. Do not
1195 # mean the old parents have a similar relationship. Do not
1197 # set bases[x] to nullrev.
1196 # set bases[x] to nullrev.
1198 bases[j], bases[i] = bases[i], bases[j]
1197 bases[j], bases[i] = bases[i], bases[j]
1199
1198
1200 newps[i] = np
1199 newps[i] = np
1201
1200
1202 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1201 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1203 # base. If only p2 changes, merging using unchanged p1 as merge base is
1202 # base. If only p2 changes, merging using unchanged p1 as merge base is
1204 # suboptimal. Therefore swap parents to make the merge sane.
1203 # suboptimal. Therefore swap parents to make the merge sane.
1205 if newps[1] != nullrev and oldps[0] == newps[0]:
1204 if newps[1] != nullrev and oldps[0] == newps[0]:
1206 assert len(newps) == 2 and len(oldps) == 2
1205 assert len(newps) == 2 and len(oldps) == 2
1207 newps.reverse()
1206 newps.reverse()
1208 bases.reverse()
1207 bases.reverse()
1209
1208
1210 # No parent change might be an error because we fail to make rev a
1209 # No parent change might be an error because we fail to make rev a
1211 # descendent of requested dest. This can happen, for example:
1210 # descendent of requested dest. This can happen, for example:
1212 #
1211 #
1213 # C # rebase -r C -d D
1212 # C # rebase -r C -d D
1214 # /| # None of A and B will be changed to D and rebase fails.
1213 # /| # None of A and B will be changed to D and rebase fails.
1215 # A B D
1214 # A B D
1216 if set(newps) == set(oldps) and dest not in newps:
1215 if set(newps) == set(oldps) and dest not in newps:
1217 raise error.Abort(_('cannot rebase %d:%s without '
1216 raise error.Abort(_('cannot rebase %d:%s without '
1218 'moving at least one of its parents')
1217 'moving at least one of its parents')
1219 % (rev, repo[rev]))
1218 % (rev, repo[rev]))
1220
1219
1221 # Source should not be ancestor of dest. The check here guarantees it's
1220 # Source should not be ancestor of dest. The check here guarantees it's
1222 # impossible. With multi-dest, the initial check does not cover complex
1221 # impossible. With multi-dest, the initial check does not cover complex
1223 # cases since we don't have abstractions to dry-run rebase cheaply.
1222 # cases since we don't have abstractions to dry-run rebase cheaply.
1224 if any(p != nullrev and isancestor(rev, p) for p in newps):
1223 if any(p != nullrev and isancestor(rev, p) for p in newps):
1225 raise error.Abort(_('source is ancestor of destination'))
1224 raise error.Abort(_('source is ancestor of destination'))
1226
1225
1227 # "rebasenode" updates to new p1, use the corresponding merge base.
1226 # "rebasenode" updates to new p1, use the corresponding merge base.
1228 if bases[0] != nullrev:
1227 if bases[0] != nullrev:
1229 base = bases[0]
1228 base = bases[0]
1230 else:
1229 else:
1231 base = None
1230 base = None
1232
1231
1233 # Check if the merge will contain unwanted changes. That may happen if
1232 # Check if the merge will contain unwanted changes. That may happen if
1234 # there are multiple special (non-changelog ancestor) merge bases, which
1233 # there are multiple special (non-changelog ancestor) merge bases, which
1235 # cannot be handled well by the 3-way merge algorithm. For example:
1234 # cannot be handled well by the 3-way merge algorithm. For example:
1236 #
1235 #
1237 # F
1236 # F
1238 # /|
1237 # /|
1239 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1238 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1240 # | | # as merge base, the difference between D and F will include
1239 # | | # as merge base, the difference between D and F will include
1241 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1240 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1242 # |/ # chosen, the rebased F will contain B.
1241 # |/ # chosen, the rebased F will contain B.
1243 # A Z
1242 # A Z
1244 #
1243 #
1245 # But our merge base candidates (D and E in above case) could still be
1244 # But our merge base candidates (D and E in above case) could still be
1246 # better than the default (ancestor(F, Z) == null). Therefore still
1245 # better than the default (ancestor(F, Z) == null). Therefore still
1247 # pick one (so choose p1 above).
1246 # pick one (so choose p1 above).
1248 if sum(1 for b in bases if b != nullrev) > 1:
1247 if sum(1 for b in bases if b != nullrev) > 1:
1249 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1248 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1250 for i, base in enumerate(bases):
1249 for i, base in enumerate(bases):
1251 if base == nullrev:
1250 if base == nullrev:
1252 continue
1251 continue
1253 # Revisions in the side (not chosen as merge base) branch that
1252 # Revisions in the side (not chosen as merge base) branch that
1254 # might contain "surprising" contents
1253 # might contain "surprising" contents
1255 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1254 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1256 bases, base, base, dest))
1255 bases, base, base, dest))
1257
1256
1258 # If those revisions are covered by rebaseset, the result is good.
1257 # If those revisions are covered by rebaseset, the result is good.
1259 # A merge in rebaseset would be considered to cover its ancestors.
1258 # A merge in rebaseset would be considered to cover its ancestors.
1260 if siderevs:
1259 if siderevs:
1261 rebaseset = [r for r, d in state.items()
1260 rebaseset = [r for r, d in state.items()
1262 if d > 0 and r not in obsskipped]
1261 if d > 0 and r not in obsskipped]
1263 merges = [r for r in rebaseset
1262 merges = [r for r in rebaseset
1264 if cl.parentrevs(r)[1] != nullrev]
1263 if cl.parentrevs(r)[1] != nullrev]
1265 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1264 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1266 siderevs, merges, rebaseset))
1265 siderevs, merges, rebaseset))
1267
1266
1268 # Choose a merge base that has a minimal number of unwanted revs.
1267 # Choose a merge base that has a minimal number of unwanted revs.
1269 l, i = min((len(revs), i)
1268 l, i = min((len(revs), i)
1270 for i, revs in enumerate(unwanted) if revs is not None)
1269 for i, revs in enumerate(unwanted) if revs is not None)
1271 base = bases[i]
1270 base = bases[i]
1272
1271
1273 # newps[0] should match merge base if possible. Currently, if newps[i]
1272 # newps[0] should match merge base if possible. Currently, if newps[i]
1274 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1273 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1275 # the other's ancestor. In that case, it's fine to not swap newps here.
1274 # the other's ancestor. In that case, it's fine to not swap newps here.
1276 # (see CASE-1 and CASE-2 above)
1275 # (see CASE-1 and CASE-2 above)
1277 if i != 0 and newps[i] != nullrev:
1276 if i != 0 and newps[i] != nullrev:
1278 newps[0], newps[i] = newps[i], newps[0]
1277 newps[0], newps[i] = newps[i], newps[0]
1279
1278
1280 # The merge will include unwanted revisions. Abort now. Revisit this if
1279 # The merge will include unwanted revisions. Abort now. Revisit this if
1281 # we have a more advanced merge algorithm that handles multiple bases.
1280 # we have a more advanced merge algorithm that handles multiple bases.
1282 if l > 0:
1281 if l > 0:
1283 unwanteddesc = _(' or ').join(
1282 unwanteddesc = _(' or ').join(
1284 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1283 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1285 for revs in unwanted if revs is not None))
1284 for revs in unwanted if revs is not None))
1286 raise error.Abort(
1285 raise error.Abort(
1287 _('rebasing %d:%s will include unwanted changes from %s')
1286 _('rebasing %d:%s will include unwanted changes from %s')
1288 % (rev, repo[rev], unwanteddesc))
1287 % (rev, repo[rev], unwanteddesc))
1289
1288
1290 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1289 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1291
1290
1292 return newps[0], newps[1], base
1291 return newps[0], newps[1], base
1293
1292
1294 def isagitpatch(repo, patchname):
1293 def isagitpatch(repo, patchname):
1295 'Return true if the given patch is in git format'
1294 'Return true if the given patch is in git format'
1296 mqpatch = os.path.join(repo.mq.path, patchname)
1295 mqpatch = os.path.join(repo.mq.path, patchname)
1297 for line in patch.linereader(file(mqpatch, 'rb')):
1296 for line in patch.linereader(file(mqpatch, 'rb')):
1298 if line.startswith('diff --git'):
1297 if line.startswith('diff --git'):
1299 return True
1298 return True
1300 return False
1299 return False
1301
1300
1302 def updatemq(repo, state, skipped, **opts):
1301 def updatemq(repo, state, skipped, **opts):
1303 'Update rebased mq patches - finalize and then import them'
1302 'Update rebased mq patches - finalize and then import them'
1304 mqrebase = {}
1303 mqrebase = {}
1305 mq = repo.mq
1304 mq = repo.mq
1306 original_series = mq.fullseries[:]
1305 original_series = mq.fullseries[:]
1307 skippedpatches = set()
1306 skippedpatches = set()
1308
1307
1309 for p in mq.applied:
1308 for p in mq.applied:
1310 rev = repo[p.node].rev()
1309 rev = repo[p.node].rev()
1311 if rev in state:
1310 if rev in state:
1312 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1311 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1313 (rev, p.name))
1312 (rev, p.name))
1314 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1313 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1315 else:
1314 else:
1316 # Applied but not rebased, not sure this should happen
1315 # Applied but not rebased, not sure this should happen
1317 skippedpatches.add(p.name)
1316 skippedpatches.add(p.name)
1318
1317
1319 if mqrebase:
1318 if mqrebase:
1320 mq.finish(repo, mqrebase.keys())
1319 mq.finish(repo, mqrebase.keys())
1321
1320
1322 # We must start import from the newest revision
1321 # We must start import from the newest revision
1323 for rev in sorted(mqrebase, reverse=True):
1322 for rev in sorted(mqrebase, reverse=True):
1324 if rev not in skipped:
1323 if rev not in skipped:
1325 name, isgit = mqrebase[rev]
1324 name, isgit = mqrebase[rev]
1326 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1325 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1327 (name, state[rev], repo[state[rev]]))
1326 (name, state[rev], repo[state[rev]]))
1328 mq.qimport(repo, (), patchname=name, git=isgit,
1327 mq.qimport(repo, (), patchname=name, git=isgit,
1329 rev=[str(state[rev])])
1328 rev=[str(state[rev])])
1330 else:
1329 else:
1331 # Rebased and skipped
1330 # Rebased and skipped
1332 skippedpatches.add(mqrebase[rev][0])
1331 skippedpatches.add(mqrebase[rev][0])
1333
1332
1334 # Patches were either applied and rebased and imported in
1333 # Patches were either applied and rebased and imported in
1335 # order, applied and removed or unapplied. Discard the removed
1334 # order, applied and removed or unapplied. Discard the removed
1336 # ones while preserving the original series order and guards.
1335 # ones while preserving the original series order and guards.
1337 newseries = [s for s in original_series
1336 newseries = [s for s in original_series
1338 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1337 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1339 mq.fullseries[:] = newseries
1338 mq.fullseries[:] = newseries
1340 mq.seriesdirty = True
1339 mq.seriesdirty = True
1341 mq.savedirty()
1340 mq.savedirty()
1342
1341
1343 def storecollapsemsg(repo, collapsemsg):
1342 def storecollapsemsg(repo, collapsemsg):
1344 'Store the collapse message to allow recovery'
1343 'Store the collapse message to allow recovery'
1345 collapsemsg = collapsemsg or ''
1344 collapsemsg = collapsemsg or ''
1346 f = repo.vfs("last-message.txt", "w")
1345 f = repo.vfs("last-message.txt", "w")
1347 f.write("%s\n" % collapsemsg)
1346 f.write("%s\n" % collapsemsg)
1348 f.close()
1347 f.close()
1349
1348
1350 def clearcollapsemsg(repo):
1349 def clearcollapsemsg(repo):
1351 'Remove collapse message file'
1350 'Remove collapse message file'
1352 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1351 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1353
1352
1354 def restorecollapsemsg(repo, isabort):
1353 def restorecollapsemsg(repo, isabort):
1355 'Restore previously stored collapse message'
1354 'Restore previously stored collapse message'
1356 try:
1355 try:
1357 f = repo.vfs("last-message.txt")
1356 f = repo.vfs("last-message.txt")
1358 collapsemsg = f.readline().strip()
1357 collapsemsg = f.readline().strip()
1359 f.close()
1358 f.close()
1360 except IOError as err:
1359 except IOError as err:
1361 if err.errno != errno.ENOENT:
1360 if err.errno != errno.ENOENT:
1362 raise
1361 raise
1363 if isabort:
1362 if isabort:
1364 # Oh well, just abort like normal
1363 # Oh well, just abort like normal
1365 collapsemsg = ''
1364 collapsemsg = ''
1366 else:
1365 else:
1367 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1366 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1368 return collapsemsg
1367 return collapsemsg
1369
1368
1370 def clearstatus(repo):
1369 def clearstatus(repo):
1371 'Remove the status files'
1370 'Remove the status files'
1372 # Make sure the active transaction won't write the state file
1371 # Make sure the active transaction won't write the state file
1373 tr = repo.currenttransaction()
1372 tr = repo.currenttransaction()
1374 if tr:
1373 if tr:
1375 tr.removefilegenerator('rebasestate')
1374 tr.removefilegenerator('rebasestate')
1376 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1375 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1377
1376
1378 def needupdate(repo, state):
1377 def needupdate(repo, state):
1379 '''check whether we should `update --clean` away from a merge, or if
1378 '''check whether we should `update --clean` away from a merge, or if
1380 somehow the working dir got forcibly updated, e.g. by older hg'''
1379 somehow the working dir got forcibly updated, e.g. by older hg'''
1381 parents = [p.rev() for p in repo[None].parents()]
1380 parents = [p.rev() for p in repo[None].parents()]
1382
1381
1383 # Are we in a merge state at all?
1382 # Are we in a merge state at all?
1384 if len(parents) < 2:
1383 if len(parents) < 2:
1385 return False
1384 return False
1386
1385
1387 # We should be standing on the first as-of-yet unrebased commit.
1386 # We should be standing on the first as-of-yet unrebased commit.
1388 firstunrebased = min([old for old, new in state.iteritems()
1387 firstunrebased = min([old for old, new in state.iteritems()
1389 if new == nullrev])
1388 if new == nullrev])
1390 if firstunrebased in parents:
1389 if firstunrebased in parents:
1391 return True
1390 return True
1392
1391
1393 return False
1392 return False
1394
1393
1395 def abort(repo, originalwd, destmap, state, activebookmark=None):
1394 def abort(repo, originalwd, destmap, state, activebookmark=None):
1396 '''Restore the repository to its original state. Additional args:
1395 '''Restore the repository to its original state. Additional args:
1397
1396
1398 activebookmark: the name of the bookmark that should be active after the
1397 activebookmark: the name of the bookmark that should be active after the
1399 restore'''
1398 restore'''
1400
1399
1401 try:
1400 try:
1402 # If the first commits in the rebased set get skipped during the rebase,
1401 # If the first commits in the rebased set get skipped during the rebase,
1403 # their values within the state mapping will be the dest rev id. The
1402 # their values within the state mapping will be the dest rev id. The
1404 # dstates list must must not contain the dest rev (issue4896)
1403 # dstates list must must not contain the dest rev (issue4896)
1405 dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]]
1404 dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]]
1406 immutable = [d for d in dstates if not repo[d].mutable()]
1405 immutable = [d for d in dstates if not repo[d].mutable()]
1407 cleanup = True
1406 cleanup = True
1408 if immutable:
1407 if immutable:
1409 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1408 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1410 % ', '.join(str(repo[r]) for r in immutable),
1409 % ', '.join(str(repo[r]) for r in immutable),
1411 hint=_("see 'hg help phases' for details"))
1410 hint=_("see 'hg help phases' for details"))
1412 cleanup = False
1411 cleanup = False
1413
1412
1414 descendants = set()
1413 descendants = set()
1415 if dstates:
1414 if dstates:
1416 descendants = set(repo.changelog.descendants(dstates))
1415 descendants = set(repo.changelog.descendants(dstates))
1417 if descendants - set(dstates):
1416 if descendants - set(dstates):
1418 repo.ui.warn(_("warning: new changesets detected on destination "
1417 repo.ui.warn(_("warning: new changesets detected on destination "
1419 "branch, can't strip\n"))
1418 "branch, can't strip\n"))
1420 cleanup = False
1419 cleanup = False
1421
1420
1422 if cleanup:
1421 if cleanup:
1423 shouldupdate = False
1422 shouldupdate = False
1424 rebased = [s for r, s in state.items()
1423 rebased = [s for r, s in state.items()
1425 if s >= 0 and s != destmap[r]]
1424 if s >= 0 and s != destmap[r]]
1426 if rebased:
1425 if rebased:
1427 strippoints = [
1426 strippoints = [
1428 c.node() for c in repo.set('roots(%ld)', rebased)]
1427 c.node() for c in repo.set('roots(%ld)', rebased)]
1429
1428
1430 updateifonnodes = set(rebased)
1429 updateifonnodes = set(rebased)
1431 updateifonnodes.update(destmap.values())
1430 updateifonnodes.update(destmap.values())
1432 updateifonnodes.add(originalwd)
1431 updateifonnodes.add(originalwd)
1433 shouldupdate = repo['.'].rev() in updateifonnodes
1432 shouldupdate = repo['.'].rev() in updateifonnodes
1434
1433
1435 # Update away from the rebase if necessary
1434 # Update away from the rebase if necessary
1436 if shouldupdate or needupdate(repo, state):
1435 if shouldupdate or needupdate(repo, state):
1437 mergemod.update(repo, originalwd, False, True)
1436 mergemod.update(repo, originalwd, False, True)
1438
1437
1439 # Strip from the first rebased revision
1438 # Strip from the first rebased revision
1440 if rebased:
1439 if rebased:
1441 # no backup of rebased cset versions needed
1440 # no backup of rebased cset versions needed
1442 repair.strip(repo.ui, repo, strippoints)
1441 repair.strip(repo.ui, repo, strippoints)
1443
1442
1444 if activebookmark and activebookmark in repo._bookmarks:
1443 if activebookmark and activebookmark in repo._bookmarks:
1445 bookmarks.activate(repo, activebookmark)
1444 bookmarks.activate(repo, activebookmark)
1446
1445
1447 finally:
1446 finally:
1448 clearstatus(repo)
1447 clearstatus(repo)
1449 clearcollapsemsg(repo)
1448 clearcollapsemsg(repo)
1450 repo.ui.warn(_('rebase aborted\n'))
1449 repo.ui.warn(_('rebase aborted\n'))
1451 return 0
1450 return 0
1452
1451
1453 def sortsource(destmap):
1452 def sortsource(destmap):
1454 """yield source revisions in an order that we only rebase things once
1453 """yield source revisions in an order that we only rebase things once
1455
1454
1456 If source and destination overlaps, we should filter out revisions
1455 If source and destination overlaps, we should filter out revisions
1457 depending on other revisions which hasn't been rebased yet.
1456 depending on other revisions which hasn't been rebased yet.
1458
1457
1459 Yield a sorted list of revisions each time.
1458 Yield a sorted list of revisions each time.
1460
1459
1461 For example, when rebasing A to B, B to C. This function yields [B], then
1460 For example, when rebasing A to B, B to C. This function yields [B], then
1462 [A], indicating B needs to be rebased first.
1461 [A], indicating B needs to be rebased first.
1463
1462
1464 Raise if there is a cycle so the rebase is impossible.
1463 Raise if there is a cycle so the rebase is impossible.
1465 """
1464 """
1466 srcset = set(destmap)
1465 srcset = set(destmap)
1467 while srcset:
1466 while srcset:
1468 srclist = sorted(srcset)
1467 srclist = sorted(srcset)
1469 result = []
1468 result = []
1470 for r in srclist:
1469 for r in srclist:
1471 if destmap[r] not in srcset:
1470 if destmap[r] not in srcset:
1472 result.append(r)
1471 result.append(r)
1473 if not result:
1472 if not result:
1474 raise error.Abort(_('source and destination form a cycle'))
1473 raise error.Abort(_('source and destination form a cycle'))
1475 srcset -= set(result)
1474 srcset -= set(result)
1476 yield result
1475 yield result
1477
1476
1478 def buildstate(repo, destmap, collapse):
1477 def buildstate(repo, destmap, collapse):
1479 '''Define which revisions are going to be rebased and where
1478 '''Define which revisions are going to be rebased and where
1480
1479
1481 repo: repo
1480 repo: repo
1482 destmap: {srcrev: destrev}
1481 destmap: {srcrev: destrev}
1483 '''
1482 '''
1484 rebaseset = destmap.keys()
1483 rebaseset = destmap.keys()
1485 originalwd = repo['.'].rev()
1484 originalwd = repo['.'].rev()
1486
1485
1487 # This check isn't strictly necessary, since mq detects commits over an
1486 # This check isn't strictly necessary, since mq detects commits over an
1488 # applied patch. But it prevents messing up the working directory when
1487 # applied patch. But it prevents messing up the working directory when
1489 # a partially completed rebase is blocked by mq.
1488 # a partially completed rebase is blocked by mq.
1490 if 'qtip' in repo.tags():
1489 if 'qtip' in repo.tags():
1491 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1490 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1492 if set(destmap.values()) & mqapplied:
1491 if set(destmap.values()) & mqapplied:
1493 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1492 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1494
1493
1495 # Get "cycle" error early by exhausting the generator.
1494 # Get "cycle" error early by exhausting the generator.
1496 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1495 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1497 if not sortedsrc:
1496 if not sortedsrc:
1498 raise error.Abort(_('no matching revisions'))
1497 raise error.Abort(_('no matching revisions'))
1499
1498
1500 # Only check the first batch of revisions to rebase not depending on other
1499 # Only check the first batch of revisions to rebase not depending on other
1501 # rebaseset. This means "source is ancestor of destination" for the second
1500 # rebaseset. This means "source is ancestor of destination" for the second
1502 # (and following) batches of revisions are not checked here. We rely on
1501 # (and following) batches of revisions are not checked here. We rely on
1503 # "defineparents" to do that check.
1502 # "defineparents" to do that check.
1504 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1503 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1505 if not roots:
1504 if not roots:
1506 raise error.Abort(_('no matching revisions'))
1505 raise error.Abort(_('no matching revisions'))
1507 roots.sort()
1506 roots.sort()
1508 state = dict.fromkeys(rebaseset, revtodo)
1507 state = dict.fromkeys(rebaseset, revtodo)
1509 emptyrebase = (len(sortedsrc) == 1)
1508 emptyrebase = (len(sortedsrc) == 1)
1510 for root in roots:
1509 for root in roots:
1511 dest = repo[destmap[root.rev()]]
1510 dest = repo[destmap[root.rev()]]
1512 commonbase = root.ancestor(dest)
1511 commonbase = root.ancestor(dest)
1513 if commonbase == root:
1512 if commonbase == root:
1514 raise error.Abort(_('source is ancestor of destination'))
1513 raise error.Abort(_('source is ancestor of destination'))
1515 if commonbase == dest:
1514 if commonbase == dest:
1516 wctx = repo[None]
1515 wctx = repo[None]
1517 if dest == wctx.p1():
1516 if dest == wctx.p1():
1518 # when rebasing to '.', it will use the current wd branch name
1517 # when rebasing to '.', it will use the current wd branch name
1519 samebranch = root.branch() == wctx.branch()
1518 samebranch = root.branch() == wctx.branch()
1520 else:
1519 else:
1521 samebranch = root.branch() == dest.branch()
1520 samebranch = root.branch() == dest.branch()
1522 if not collapse and samebranch and dest in root.parents():
1521 if not collapse and samebranch and dest in root.parents():
1523 # mark the revision as done by setting its new revision
1522 # mark the revision as done by setting its new revision
1524 # equal to its old (current) revisions
1523 # equal to its old (current) revisions
1525 state[root.rev()] = root.rev()
1524 state[root.rev()] = root.rev()
1526 repo.ui.debug('source is a child of destination\n')
1525 repo.ui.debug('source is a child of destination\n')
1527 continue
1526 continue
1528
1527
1529 emptyrebase = False
1528 emptyrebase = False
1530 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1529 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1531 if emptyrebase:
1530 if emptyrebase:
1532 return None
1531 return None
1533 for rev in sorted(state):
1532 for rev in sorted(state):
1534 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1533 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1535 # if all parents of this revision are done, then so is this revision
1534 # if all parents of this revision are done, then so is this revision
1536 if parents and all((state.get(p) == p for p in parents)):
1535 if parents and all((state.get(p) == p for p in parents)):
1537 state[rev] = rev
1536 state[rev] = rev
1538 return originalwd, destmap, state
1537 return originalwd, destmap, state
1539
1538
1540 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1539 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1541 keepf=False, fm=None):
1540 keepf=False, fm=None):
1542 """dispose of rebased revision at the end of the rebase
1541 """dispose of rebased revision at the end of the rebase
1543
1542
1544 If `collapsedas` is not None, the rebase was a collapse whose result if the
1543 If `collapsedas` is not None, the rebase was a collapse whose result if the
1545 `collapsedas` node.
1544 `collapsedas` node.
1546
1545
1547 If `keepf` is not True, the rebase has --keep set and no nodes should be
1546 If `keepf` is not True, the rebase has --keep set and no nodes should be
1548 removed (but bookmarks still need to be moved).
1547 removed (but bookmarks still need to be moved).
1549 """
1548 """
1550 tonode = repo.changelog.node
1549 tonode = repo.changelog.node
1551 replacements = {}
1550 replacements = {}
1552 moves = {}
1551 moves = {}
1553 for rev, newrev in sorted(state.items()):
1552 for rev, newrev in sorted(state.items()):
1554 if newrev >= 0 and newrev != rev:
1553 if newrev >= 0 and newrev != rev:
1555 oldnode = tonode(rev)
1554 oldnode = tonode(rev)
1556 newnode = collapsedas or tonode(newrev)
1555 newnode = collapsedas or tonode(newrev)
1557 moves[oldnode] = newnode
1556 moves[oldnode] = newnode
1558 if not keepf:
1557 if not keepf:
1559 if rev in skipped:
1558 if rev in skipped:
1560 succs = ()
1559 succs = ()
1561 else:
1560 else:
1562 succs = (newnode,)
1561 succs = (newnode,)
1563 replacements[oldnode] = succs
1562 replacements[oldnode] = succs
1564 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1563 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1565 if fm:
1564 if fm:
1566 nodechanges = {hex(oldn): [hex(n) for n in newn]
1565 hf = fm.hexfunc
1567 for oldn, newn in replacements.iteritems()}
1566 fl = fm.formatlist
1567 fd = fm.formatdict
1568 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1569 for oldn, newn in replacements.iteritems()},
1570 key="oldnode", value="newnodes")
1568 fm.data(nodechanges=nodechanges)
1571 fm.data(nodechanges=nodechanges)
1569
1572
1570 def pullrebase(orig, ui, repo, *args, **opts):
1573 def pullrebase(orig, ui, repo, *args, **opts):
1571 'Call rebase after pull if the latter has been invoked with --rebase'
1574 'Call rebase after pull if the latter has been invoked with --rebase'
1572 ret = None
1575 ret = None
1573 if opts.get(r'rebase'):
1576 if opts.get(r'rebase'):
1574 if ui.configbool('commands', 'rebase.requiredest'):
1577 if ui.configbool('commands', 'rebase.requiredest'):
1575 msg = _('rebase destination required by configuration')
1578 msg = _('rebase destination required by configuration')
1576 hint = _('use hg pull followed by hg rebase -d DEST')
1579 hint = _('use hg pull followed by hg rebase -d DEST')
1577 raise error.Abort(msg, hint=hint)
1580 raise error.Abort(msg, hint=hint)
1578
1581
1579 with repo.wlock(), repo.lock():
1582 with repo.wlock(), repo.lock():
1580 if opts.get(r'update'):
1583 if opts.get(r'update'):
1581 del opts[r'update']
1584 del opts[r'update']
1582 ui.debug('--update and --rebase are not compatible, ignoring '
1585 ui.debug('--update and --rebase are not compatible, ignoring '
1583 'the update flag\n')
1586 'the update flag\n')
1584
1587
1585 cmdutil.checkunfinished(repo)
1588 cmdutil.checkunfinished(repo)
1586 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1589 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1587 'please commit or shelve your changes first'))
1590 'please commit or shelve your changes first'))
1588
1591
1589 revsprepull = len(repo)
1592 revsprepull = len(repo)
1590 origpostincoming = commands.postincoming
1593 origpostincoming = commands.postincoming
1591 def _dummy(*args, **kwargs):
1594 def _dummy(*args, **kwargs):
1592 pass
1595 pass
1593 commands.postincoming = _dummy
1596 commands.postincoming = _dummy
1594 try:
1597 try:
1595 ret = orig(ui, repo, *args, **opts)
1598 ret = orig(ui, repo, *args, **opts)
1596 finally:
1599 finally:
1597 commands.postincoming = origpostincoming
1600 commands.postincoming = origpostincoming
1598 revspostpull = len(repo)
1601 revspostpull = len(repo)
1599 if revspostpull > revsprepull:
1602 if revspostpull > revsprepull:
1600 # --rev option from pull conflict with rebase own --rev
1603 # --rev option from pull conflict with rebase own --rev
1601 # dropping it
1604 # dropping it
1602 if r'rev' in opts:
1605 if r'rev' in opts:
1603 del opts[r'rev']
1606 del opts[r'rev']
1604 # positional argument from pull conflicts with rebase's own
1607 # positional argument from pull conflicts with rebase's own
1605 # --source.
1608 # --source.
1606 if r'source' in opts:
1609 if r'source' in opts:
1607 del opts[r'source']
1610 del opts[r'source']
1608 # revsprepull is the len of the repo, not revnum of tip.
1611 # revsprepull is the len of the repo, not revnum of tip.
1609 destspace = list(repo.changelog.revs(start=revsprepull))
1612 destspace = list(repo.changelog.revs(start=revsprepull))
1610 opts[r'_destspace'] = destspace
1613 opts[r'_destspace'] = destspace
1611 try:
1614 try:
1612 rebase(ui, repo, **opts)
1615 rebase(ui, repo, **opts)
1613 except error.NoMergeDestAbort:
1616 except error.NoMergeDestAbort:
1614 # we can maybe update instead
1617 # we can maybe update instead
1615 rev, _a, _b = destutil.destupdate(repo)
1618 rev, _a, _b = destutil.destupdate(repo)
1616 if rev == repo['.'].rev():
1619 if rev == repo['.'].rev():
1617 ui.status(_('nothing to rebase\n'))
1620 ui.status(_('nothing to rebase\n'))
1618 else:
1621 else:
1619 ui.status(_('nothing to rebase - updating instead\n'))
1622 ui.status(_('nothing to rebase - updating instead\n'))
1620 # not passing argument to get the bare update behavior
1623 # not passing argument to get the bare update behavior
1621 # with warning and trumpets
1624 # with warning and trumpets
1622 commands.update(ui, repo)
1625 commands.update(ui, repo)
1623 else:
1626 else:
1624 if opts.get(r'tool'):
1627 if opts.get(r'tool'):
1625 raise error.Abort(_('--tool can only be used with --rebase'))
1628 raise error.Abort(_('--tool can only be used with --rebase'))
1626 ret = orig(ui, repo, *args, **opts)
1629 ret = orig(ui, repo, *args, **opts)
1627
1630
1628 return ret
1631 return ret
1629
1632
1630 def _filterobsoleterevs(repo, revs):
1633 def _filterobsoleterevs(repo, revs):
1631 """returns a set of the obsolete revisions in revs"""
1634 """returns a set of the obsolete revisions in revs"""
1632 return set(r for r in revs if repo[r].obsolete())
1635 return set(r for r in revs if repo[r].obsolete())
1633
1636
1634 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1637 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1635 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1638 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1636
1639
1637 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1640 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1638 obsolete nodes to be rebased given in `rebaseobsrevs`.
1641 obsolete nodes to be rebased given in `rebaseobsrevs`.
1639
1642
1640 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1643 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1641 without a successor in destination.
1644 without a successor in destination.
1642 """
1645 """
1643 obsoletenotrebased = {}
1646 obsoletenotrebased = {}
1644 obsoletewithoutsuccessorindestination = set([])
1647 obsoletewithoutsuccessorindestination = set([])
1645
1648
1646 assert repo.filtername is None
1649 assert repo.filtername is None
1647 cl = repo.changelog
1650 cl = repo.changelog
1648 nodemap = cl.nodemap
1651 nodemap = cl.nodemap
1649 for srcrev in rebaseobsrevs:
1652 for srcrev in rebaseobsrevs:
1650 srcnode = cl.node(srcrev)
1653 srcnode = cl.node(srcrev)
1651 destnode = cl.node(destmap[srcrev])
1654 destnode = cl.node(destmap[srcrev])
1652 # XXX: more advanced APIs are required to handle split correctly
1655 # XXX: more advanced APIs are required to handle split correctly
1653 successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1656 successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1654 if len(successors) == 1:
1657 if len(successors) == 1:
1655 # obsutil.allsuccessors includes node itself. When the list only
1658 # obsutil.allsuccessors includes node itself. When the list only
1656 # contains one element, it means there are no successors.
1659 # contains one element, it means there are no successors.
1657 obsoletenotrebased[srcrev] = None
1660 obsoletenotrebased[srcrev] = None
1658 else:
1661 else:
1659 for succnode in successors:
1662 for succnode in successors:
1660 if succnode == srcnode or succnode not in nodemap:
1663 if succnode == srcnode or succnode not in nodemap:
1661 continue
1664 continue
1662 if cl.isancestor(succnode, destnode):
1665 if cl.isancestor(succnode, destnode):
1663 obsoletenotrebased[srcrev] = nodemap[succnode]
1666 obsoletenotrebased[srcrev] = nodemap[succnode]
1664 break
1667 break
1665 else:
1668 else:
1666 # If 'srcrev' has a successor in rebase set but none in
1669 # If 'srcrev' has a successor in rebase set but none in
1667 # destination (which would be catched above), we shall skip it
1670 # destination (which would be catched above), we shall skip it
1668 # and its descendants to avoid divergence.
1671 # and its descendants to avoid divergence.
1669 if any(nodemap[s] in destmap
1672 if any(nodemap[s] in destmap
1670 for s in successors if s != srcnode):
1673 for s in successors if s != srcnode):
1671 obsoletewithoutsuccessorindestination.add(srcrev)
1674 obsoletewithoutsuccessorindestination.add(srcrev)
1672
1675
1673 return obsoletenotrebased, obsoletewithoutsuccessorindestination
1676 return obsoletenotrebased, obsoletewithoutsuccessorindestination
1674
1677
1675 def summaryhook(ui, repo):
1678 def summaryhook(ui, repo):
1676 if not repo.vfs.exists('rebasestate'):
1679 if not repo.vfs.exists('rebasestate'):
1677 return
1680 return
1678 try:
1681 try:
1679 rbsrt = rebaseruntime(repo, ui, {})
1682 rbsrt = rebaseruntime(repo, ui, {})
1680 rbsrt.restorestatus()
1683 rbsrt.restorestatus()
1681 state = rbsrt.state
1684 state = rbsrt.state
1682 except error.RepoLookupError:
1685 except error.RepoLookupError:
1683 # i18n: column positioning for "hg summary"
1686 # i18n: column positioning for "hg summary"
1684 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1687 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1685 ui.write(msg)
1688 ui.write(msg)
1686 return
1689 return
1687 numrebased = len([i for i in state.itervalues() if i >= 0])
1690 numrebased = len([i for i in state.itervalues() if i >= 0])
1688 # i18n: column positioning for "hg summary"
1691 # i18n: column positioning for "hg summary"
1689 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1692 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1690 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1693 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1691 ui.label(_('%d remaining'), 'rebase.remaining') %
1694 ui.label(_('%d remaining'), 'rebase.remaining') %
1692 (len(state) - numrebased)))
1695 (len(state) - numrebased)))
1693
1696
1694 def uisetup(ui):
1697 def uisetup(ui):
1695 #Replace pull with a decorator to provide --rebase option
1698 #Replace pull with a decorator to provide --rebase option
1696 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1699 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1697 entry[1].append(('', 'rebase', None,
1700 entry[1].append(('', 'rebase', None,
1698 _("rebase working directory to branch head")))
1701 _("rebase working directory to branch head")))
1699 entry[1].append(('t', 'tool', '',
1702 entry[1].append(('t', 'tool', '',
1700 _("specify merge tool for rebase")))
1703 _("specify merge tool for rebase")))
1701 cmdutil.summaryhooks.add('rebase', summaryhook)
1704 cmdutil.summaryhooks.add('rebase', summaryhook)
1702 cmdutil.unfinishedstates.append(
1705 cmdutil.unfinishedstates.append(
1703 ['rebasestate', False, False, _('rebase in progress'),
1706 ['rebasestate', False, False, _('rebase in progress'),
1704 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1707 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1705 cmdutil.afterresolvedstates.append(
1708 cmdutil.afterresolvedstates.append(
1706 ['rebasestate', _('hg rebase --continue')])
1709 ['rebasestate', _('hg rebase --continue')])
@@ -1,58 +1,57
1 Testing templating for rebase command
1 Testing templating for rebase command
2
2
3 Setup
3 Setup
4
4
5 $ cat >> $HGRCPATH <<EOF
5 $ cat >> $HGRCPATH <<EOF
6 > [extensions]
6 > [extensions]
7 > rebase=
7 > rebase=
8 > [experimental]
8 > [experimental]
9 > evolution=createmarkers
9 > evolution=createmarkers
10 > EOF
10 > EOF
11
11
12 $ hg init repo
12 $ hg init repo
13 $ cd repo
13 $ cd repo
14 $ for ch in a b c d; do echo foo > $ch; hg commit -Aqm "Added "$ch; done
14 $ for ch in a b c d; do echo foo > $ch; hg commit -Aqm "Added "$ch; done
15
15
16 $ hg log -G -T "{rev}:{node|short} {desc}"
16 $ hg log -G -T "{rev}:{node|short} {desc}"
17 @ 3:62615734edd5 Added d
17 @ 3:62615734edd5 Added d
18 |
18 |
19 o 2:28ad74487de9 Added c
19 o 2:28ad74487de9 Added c
20 |
20 |
21 o 1:29becc82797a Added b
21 o 1:29becc82797a Added b
22 |
22 |
23 o 0:18d04c59bb5d Added a
23 o 0:18d04c59bb5d Added a
24
24
25 Getting the JSON output for nodechanges
25 Getting the JSON output for nodechanges
26
26
27 $ hg rebase -s 2 -d 0 -q -Tjson
27 $ hg rebase -s 2 -d 0 -q -Tjson
28 [
28 [
29 {
29 {
30 "nodechanges": {"28ad74487de9599d00d81085be739c61fc340652": ["849767420fd5519cf0026232411a943ed03cc9fb"], "62615734edd52f06b6fb9c2beb429e4fe30d57b8": ["df21b32134ba85d86bca590cbe9b8b7cbc346c53"]}
30 "nodechanges": {"28ad74487de9599d00d81085be739c61fc340652": ["849767420fd5519cf0026232411a943ed03cc9fb"], "62615734edd52f06b6fb9c2beb429e4fe30d57b8": ["df21b32134ba85d86bca590cbe9b8b7cbc346c53"]}
31 }
31 }
32 ]
32 ]
33
33
34 $ hg log -G -T "{rev}:{node|short} {desc}"
34 $ hg log -G -T "{rev}:{node|short} {desc}"
35 @ 5:df21b32134ba Added d
35 @ 5:df21b32134ba Added d
36 |
36 |
37 o 4:849767420fd5 Added c
37 o 4:849767420fd5 Added c
38 |
38 |
39 | o 1:29becc82797a Added b
39 | o 1:29becc82797a Added b
40 |/
40 |/
41 o 0:18d04c59bb5d Added a
41 o 0:18d04c59bb5d Added a
42
42
43 $ hg rebase -s 1 -d 5 -q -T "{nodechanges|json}"
43 $ hg rebase -s 1 -d 5 -q -T "{nodechanges|json}"
44 {"29becc82797a4bc11ec8880b58eaecd2ab3e7760": ["d9d6773efc831c274eace04bc13e8e6412517139"]} (no-eol)
44 {"29becc82797a4bc11ec8880b58eaecd2ab3e7760": ["d9d6773efc831c274eace04bc13e8e6412517139"]} (no-eol)
45
45
46 $ hg log -G -T "{rev}:{node|short} {desc}"
46 $ hg log -G -T "{rev}:{node|short} {desc}"
47 o 6:d9d6773efc83 Added b
47 o 6:d9d6773efc83 Added b
48 |
48 |
49 @ 5:df21b32134ba Added d
49 @ 5:df21b32134ba Added d
50 |
50 |
51 o 4:849767420fd5 Added c
51 o 4:849767420fd5 Added c
52 |
52 |
53 o 0:18d04c59bb5d Added a
53 o 0:18d04c59bb5d Added a
54
54
55
55
56 $ hg rebase -s 6 -d 4 -q -T "{nodechanges % '{oldnode}:{newnodes % ' {node} '}'}"
56 $ hg rebase -s 6 -d 4 -q -T "{nodechanges % '{oldnode}:{newnodes % ' {node} '}'}"
57 d9d6773efc831c274eace04bc13e8e6412517139: f48cd65c6dc3d2acb55da54402a5b029546e546f (no-eol) (false !)
57 d9d6773efc831c274eace04bc13e8e6412517139: f48cd65c6dc3d2acb55da54402a5b029546e546f (no-eol)
58 d9d6773efc831c274eace04bc13e8e6412517139 (no-eol)
General Comments 0
You need to be logged in to leave comments. Login now