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