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