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