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