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