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