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