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