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