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