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