##// END OF EJS Templates
rebase: avoid redundant repo[rev].rev() - just keep working in rev space
Mads Kiilerich -
r23461:ffef6d50 default
parent child Browse files
Show More
@@ -1,1037 +1,1037 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 from mercurial import copies
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 = defineparents(repo, rev, target, state,
373 p1, p2 = 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, state, collapsef,
383 stats = rebasenode(repo, rev, p1, state, collapsef,
384 target)
384 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 = repo[p2].rev() != 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 = defineparents(repo, min(state), target,
417 p1, p2 = 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 changes and store useful information in extra.
512 '''Commit the changes and store useful information in extra.
513 Return node of committed revision.'''
513 Return node of committed revision.'''
514 try:
514 try:
515 repo.dirstate.beginparentchange()
515 repo.dirstate.beginparentchange()
516 repo.setparents(repo[p1].node(), repo[p2].node())
516 repo.setparents(repo[p1].node(), repo[p2].node())
517 repo.dirstate.endparentchange()
517 repo.dirstate.endparentchange()
518 ctx = repo[rev]
518 ctx = repo[rev]
519 if commitmsg is None:
519 if commitmsg is None:
520 commitmsg = ctx.description()
520 commitmsg = ctx.description()
521 extra = {'rebase_source': ctx.hex()}
521 extra = {'rebase_source': ctx.hex()}
522 if extrafn:
522 if extrafn:
523 extrafn(ctx, extra)
523 extrafn(ctx, extra)
524
524
525 backup = repo.ui.backupconfig('phases', 'new-commit')
525 backup = repo.ui.backupconfig('phases', 'new-commit')
526 try:
526 try:
527 targetphase = max(ctx.phase(), phases.draft)
527 targetphase = max(ctx.phase(), phases.draft)
528 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
528 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
529 # Commit might fail if unresolved files exist
529 # Commit might fail if unresolved files exist
530 newnode = repo.commit(text=commitmsg, user=ctx.user(),
530 newnode = repo.commit(text=commitmsg, user=ctx.user(),
531 date=ctx.date(), extra=extra, editor=editor)
531 date=ctx.date(), extra=extra, editor=editor)
532 finally:
532 finally:
533 repo.ui.restoreconfig(backup)
533 repo.ui.restoreconfig(backup)
534
534
535 repo.dirstate.setbranch(repo[newnode].branch())
535 repo.dirstate.setbranch(repo[newnode].branch())
536 return newnode
536 return newnode
537 except util.Abort:
537 except util.Abort:
538 # Invalidate the previous setparents
538 # Invalidate the previous setparents
539 repo.dirstate.invalidate()
539 repo.dirstate.invalidate()
540 raise
540 raise
541
541
542 def rebasenode(repo, rev, p1, state, collapse, target):
542 def rebasenode(repo, rev, p1, state, collapse, target):
543 'Rebase a single revision'
543 'Rebase a single revision'
544 # Merge phase
544 # Merge phase
545 # Update to target and merge it with local
545 # Update to target and merge it with local
546 if repo['.'].rev() != repo[p1].rev():
546 if repo['.'].rev() != p1:
547 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
547 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
548 merge.update(repo, p1, False, True, False)
548 merge.update(repo, p1, False, True, False)
549 else:
549 else:
550 repo.ui.debug(" already in target\n")
550 repo.ui.debug(" already in target\n")
551 repo.dirstate.write()
551 repo.dirstate.write()
552 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
552 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
553 if repo[rev].rev() == repo[min(state)].rev():
553 if rev == min(state):
554 # Case (1) initial changeset of a non-detaching rebase.
554 # Case (1) initial changeset of a non-detaching rebase.
555 # Let the merge mechanism find the base itself.
555 # Let the merge mechanism find the base itself.
556 base = None
556 base = None
557 elif not repo[rev].p2():
557 elif not repo[rev].p2():
558 # Case (2) detaching the node with a single parent, use this parent
558 # Case (2) detaching the node with a single parent, use this parent
559 base = repo[rev].p1().node()
559 base = repo[rev].p1().rev()
560 else:
560 else:
561 # In case of merge, we need to pick the right parent as merge base.
561 # In case of merge, we need to pick the right parent as merge base.
562 #
562 #
563 # Imagine we have:
563 # Imagine we have:
564 # - M: currently rebase revision in this step
564 # - M: currently rebase revision in this step
565 # - A: one parent of M
565 # - A: one parent of M
566 # - B: second parent of M
566 # - B: second parent of M
567 # - D: destination of this merge step (p1 var)
567 # - D: destination of this merge step (p1 var)
568 #
568 #
569 # If we are rebasing on D, D is the successors of A or B. The right
569 # If we are rebasing on D, D is the successors of A or B. The right
570 # merge base is the one D succeed to. We pretend it is B for the rest
570 # merge base is the one D succeed to. We pretend it is B for the rest
571 # of this comment
571 # of this comment
572 #
572 #
573 # If we pick B as the base, the merge involves:
573 # If we pick B as the base, the merge involves:
574 # - changes from B to M (actual changeset payload)
574 # - changes from B to M (actual changeset payload)
575 # - changes from B to D (induced by rebase) as D is a rebased
575 # - changes from B to D (induced by rebase) as D is a rebased
576 # version of B)
576 # version of B)
577 # Which exactly represent the rebase operation.
577 # Which exactly represent the rebase operation.
578 #
578 #
579 # If we pick the A as the base, the merge involves
579 # If we pick the A as the base, the merge involves
580 # - changes from A to M (actual changeset payload)
580 # - changes from A to M (actual changeset payload)
581 # - changes from A to D (with include changes between unrelated A and B
581 # - changes from A to D (with include changes between unrelated A and B
582 # plus changes induced by rebase)
582 # plus changes induced by rebase)
583 # Which does not represent anything sensible and creates a lot of
583 # Which does not represent anything sensible and creates a lot of
584 # conflicts.
584 # conflicts.
585 for p in repo[rev].parents():
585 for p in repo[rev].parents():
586 if state.get(p.rev()) == repo[p1].rev():
586 if state.get(p.rev()) == p1:
587 base = p.node()
587 base = p.rev()
588 break
588 break
589 else: # fallback when base not found
589 else: # fallback when base not found
590 base = None
590 base = None
591
591
592 # Raise because this function is called wrong (see issue 4106)
592 # Raise because this function is called wrong (see issue 4106)
593 raise AssertionError('no base found to rebase on '
593 raise AssertionError('no base found to rebase on '
594 '(rebasenode called wrong)')
594 '(rebasenode called wrong)')
595 if base is not None:
595 if base is not None:
596 repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
596 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
597 # When collapsing in-place, the parent is the common ancestor, we
597 # When collapsing in-place, the parent is the common ancestor, we
598 # have to allow merging with it.
598 # have to allow merging with it.
599 stats = merge.update(repo, rev, True, True, False, base, collapse,
599 stats = merge.update(repo, rev, True, True, False, base, collapse,
600 labels=['dest', 'source'])
600 labels=['dest', 'source'])
601 if collapse:
601 if collapse:
602 copies.duplicatecopies(repo, rev, target)
602 copies.duplicatecopies(repo, rev, target)
603 else:
603 else:
604 # If we're not using --collapse, we need to
604 # If we're not using --collapse, we need to
605 # duplicate copies between the revision we're
605 # duplicate copies between the revision we're
606 # rebasing and its first parent, but *not*
606 # rebasing and its first parent, but *not*
607 # duplicate any copies that have already been
607 # duplicate any copies that have already been
608 # performed in the destination.
608 # performed in the destination.
609 p1rev = repo[rev].p1().rev()
609 p1rev = repo[rev].p1().rev()
610 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
610 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
611 return stats
611 return stats
612
612
613 def nearestrebased(repo, rev, state):
613 def nearestrebased(repo, rev, state):
614 """return the nearest ancestors of rev in the rebase result"""
614 """return the nearest ancestors of rev in the rebase result"""
615 rebased = [r for r in state if state[r] > nullmerge]
615 rebased = [r for r in state if state[r] > nullmerge]
616 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
616 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
617 if candidates:
617 if candidates:
618 return state[candidates.first()]
618 return state[candidates.first()]
619 else:
619 else:
620 return None
620 return None
621
621
622 def defineparents(repo, rev, target, state, targetancestors):
622 def defineparents(repo, rev, target, state, targetancestors):
623 'Return the new parent relationship of the revision that will be rebased'
623 'Return the new parent relationship of the revision that will be rebased'
624 parents = repo[rev].parents()
624 parents = repo[rev].parents()
625 p1 = p2 = nullrev
625 p1 = p2 = nullrev
626
626
627 p1n = parents[0].rev()
627 p1n = parents[0].rev()
628 if p1n in targetancestors:
628 if p1n in targetancestors:
629 p1 = target
629 p1 = target
630 elif p1n in state:
630 elif p1n in state:
631 if state[p1n] == nullmerge:
631 if state[p1n] == nullmerge:
632 p1 = target
632 p1 = target
633 elif state[p1n] == revignored:
633 elif state[p1n] == revignored:
634 p1 = nearestrebased(repo, p1n, state)
634 p1 = nearestrebased(repo, p1n, state)
635 if p1 is None:
635 if p1 is None:
636 p1 = target
636 p1 = target
637 else:
637 else:
638 p1 = state[p1n]
638 p1 = state[p1n]
639 else: # p1n external
639 else: # p1n external
640 p1 = target
640 p1 = target
641 p2 = p1n
641 p2 = p1n
642
642
643 if len(parents) == 2 and parents[1].rev() not in targetancestors:
643 if len(parents) == 2 and parents[1].rev() not in targetancestors:
644 p2n = parents[1].rev()
644 p2n = parents[1].rev()
645 # interesting second parent
645 # interesting second parent
646 if p2n in state:
646 if p2n in state:
647 if p1 == target: # p1n in targetancestors or external
647 if p1 == target: # p1n in targetancestors or external
648 p1 = state[p2n]
648 p1 = state[p2n]
649 elif state[p2n] == revignored:
649 elif state[p2n] == revignored:
650 p2 = nearestrebased(repo, p2n, state)
650 p2 = nearestrebased(repo, p2n, state)
651 if p2 is None:
651 if p2 is None:
652 # no ancestors rebased yet, detach
652 # no ancestors rebased yet, detach
653 p2 = target
653 p2 = target
654 else:
654 else:
655 p2 = state[p2n]
655 p2 = state[p2n]
656 else: # p2n external
656 else: # p2n external
657 if p2 != nullrev: # p1n external too => rev is a merged revision
657 if p2 != nullrev: # p1n external too => rev is a merged revision
658 raise util.Abort(_('cannot use revision %d as base, result '
658 raise util.Abort(_('cannot use revision %d as base, result '
659 'would have 3 parents') % rev)
659 'would have 3 parents') % rev)
660 p2 = p2n
660 p2 = p2n
661 repo.ui.debug(" future parents are %d and %d\n" %
661 repo.ui.debug(" future parents are %d and %d\n" %
662 (repo[p1].rev(), repo[p2].rev()))
662 (repo[p1].rev(), repo[p2].rev()))
663 return p1, p2
663 return p1, p2
664
664
665 def isagitpatch(repo, patchname):
665 def isagitpatch(repo, patchname):
666 'Return true if the given patch is in git format'
666 'Return true if the given patch is in git format'
667 mqpatch = os.path.join(repo.mq.path, patchname)
667 mqpatch = os.path.join(repo.mq.path, patchname)
668 for line in patch.linereader(file(mqpatch, 'rb')):
668 for line in patch.linereader(file(mqpatch, 'rb')):
669 if line.startswith('diff --git'):
669 if line.startswith('diff --git'):
670 return True
670 return True
671 return False
671 return False
672
672
673 def updatemq(repo, state, skipped, **opts):
673 def updatemq(repo, state, skipped, **opts):
674 'Update rebased mq patches - finalize and then import them'
674 'Update rebased mq patches - finalize and then import them'
675 mqrebase = {}
675 mqrebase = {}
676 mq = repo.mq
676 mq = repo.mq
677 original_series = mq.fullseries[:]
677 original_series = mq.fullseries[:]
678 skippedpatches = set()
678 skippedpatches = set()
679
679
680 for p in mq.applied:
680 for p in mq.applied:
681 rev = repo[p.node].rev()
681 rev = repo[p.node].rev()
682 if rev in state:
682 if rev in state:
683 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
683 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
684 (rev, p.name))
684 (rev, p.name))
685 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
685 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
686 else:
686 else:
687 # Applied but not rebased, not sure this should happen
687 # Applied but not rebased, not sure this should happen
688 skippedpatches.add(p.name)
688 skippedpatches.add(p.name)
689
689
690 if mqrebase:
690 if mqrebase:
691 mq.finish(repo, mqrebase.keys())
691 mq.finish(repo, mqrebase.keys())
692
692
693 # We must start import from the newest revision
693 # We must start import from the newest revision
694 for rev in sorted(mqrebase, reverse=True):
694 for rev in sorted(mqrebase, reverse=True):
695 if rev not in skipped:
695 if rev not in skipped:
696 name, isgit = mqrebase[rev]
696 name, isgit = mqrebase[rev]
697 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
697 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
698 mq.qimport(repo, (), patchname=name, git=isgit,
698 mq.qimport(repo, (), patchname=name, git=isgit,
699 rev=[str(state[rev])])
699 rev=[str(state[rev])])
700 else:
700 else:
701 # Rebased and skipped
701 # Rebased and skipped
702 skippedpatches.add(mqrebase[rev][0])
702 skippedpatches.add(mqrebase[rev][0])
703
703
704 # Patches were either applied and rebased and imported in
704 # Patches were either applied and rebased and imported in
705 # order, applied and removed or unapplied. Discard the removed
705 # order, applied and removed or unapplied. Discard the removed
706 # ones while preserving the original series order and guards.
706 # ones while preserving the original series order and guards.
707 newseries = [s for s in original_series
707 newseries = [s for s in original_series
708 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
708 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
709 mq.fullseries[:] = newseries
709 mq.fullseries[:] = newseries
710 mq.seriesdirty = True
710 mq.seriesdirty = True
711 mq.savedirty()
711 mq.savedirty()
712
712
713 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
713 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
714 'Move bookmarks to their correct changesets, and delete divergent ones'
714 'Move bookmarks to their correct changesets, and delete divergent ones'
715 marks = repo._bookmarks
715 marks = repo._bookmarks
716 for k, v in originalbookmarks.iteritems():
716 for k, v in originalbookmarks.iteritems():
717 if v in nstate:
717 if v in nstate:
718 # update the bookmarks for revs that have moved
718 # update the bookmarks for revs that have moved
719 marks[k] = nstate[v]
719 marks[k] = nstate[v]
720 bookmarks.deletedivergent(repo, [targetnode], k)
720 bookmarks.deletedivergent(repo, [targetnode], k)
721
721
722 marks.write()
722 marks.write()
723
723
724 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
724 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
725 external, activebookmark):
725 external, activebookmark):
726 'Store the current status to allow recovery'
726 'Store the current status to allow recovery'
727 f = repo.opener("rebasestate", "w")
727 f = repo.opener("rebasestate", "w")
728 f.write(repo[originalwd].hex() + '\n')
728 f.write(repo[originalwd].hex() + '\n')
729 f.write(repo[target].hex() + '\n')
729 f.write(repo[target].hex() + '\n')
730 f.write(repo[external].hex() + '\n')
730 f.write(repo[external].hex() + '\n')
731 f.write('%d\n' % int(collapse))
731 f.write('%d\n' % int(collapse))
732 f.write('%d\n' % int(keep))
732 f.write('%d\n' % int(keep))
733 f.write('%d\n' % int(keepbranches))
733 f.write('%d\n' % int(keepbranches))
734 f.write('%s\n' % (activebookmark or ''))
734 f.write('%s\n' % (activebookmark or ''))
735 for d, v in state.iteritems():
735 for d, v in state.iteritems():
736 oldrev = repo[d].hex()
736 oldrev = repo[d].hex()
737 if v > nullmerge:
737 if v > nullmerge:
738 newrev = repo[v].hex()
738 newrev = repo[v].hex()
739 else:
739 else:
740 newrev = v
740 newrev = v
741 f.write("%s:%s\n" % (oldrev, newrev))
741 f.write("%s:%s\n" % (oldrev, newrev))
742 f.close()
742 f.close()
743 repo.ui.debug('rebase status stored\n')
743 repo.ui.debug('rebase status stored\n')
744
744
745 def clearstatus(repo):
745 def clearstatus(repo):
746 'Remove the status files'
746 'Remove the status files'
747 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
747 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
748
748
749 def restorestatus(repo):
749 def restorestatus(repo):
750 'Restore a previously stored status'
750 'Restore a previously stored status'
751 try:
751 try:
752 keepbranches = None
752 keepbranches = None
753 target = None
753 target = None
754 collapse = False
754 collapse = False
755 external = nullrev
755 external = nullrev
756 activebookmark = None
756 activebookmark = None
757 state = {}
757 state = {}
758 f = repo.opener("rebasestate")
758 f = repo.opener("rebasestate")
759 for i, l in enumerate(f.read().splitlines()):
759 for i, l in enumerate(f.read().splitlines()):
760 if i == 0:
760 if i == 0:
761 originalwd = repo[l].rev()
761 originalwd = repo[l].rev()
762 elif i == 1:
762 elif i == 1:
763 target = repo[l].rev()
763 target = repo[l].rev()
764 elif i == 2:
764 elif i == 2:
765 external = repo[l].rev()
765 external = repo[l].rev()
766 elif i == 3:
766 elif i == 3:
767 collapse = bool(int(l))
767 collapse = bool(int(l))
768 elif i == 4:
768 elif i == 4:
769 keep = bool(int(l))
769 keep = bool(int(l))
770 elif i == 5:
770 elif i == 5:
771 keepbranches = bool(int(l))
771 keepbranches = bool(int(l))
772 elif i == 6 and not (len(l) == 81 and ':' in l):
772 elif i == 6 and not (len(l) == 81 and ':' in l):
773 # line 6 is a recent addition, so for backwards compatibility
773 # line 6 is a recent addition, so for backwards compatibility
774 # check that the line doesn't look like the oldrev:newrev lines
774 # check that the line doesn't look like the oldrev:newrev lines
775 activebookmark = l
775 activebookmark = l
776 else:
776 else:
777 oldrev, newrev = l.split(':')
777 oldrev, newrev = l.split(':')
778 if newrev in (str(nullmerge), str(revignored)):
778 if newrev in (str(nullmerge), str(revignored)):
779 state[repo[oldrev].rev()] = int(newrev)
779 state[repo[oldrev].rev()] = int(newrev)
780 else:
780 else:
781 state[repo[oldrev].rev()] = repo[newrev].rev()
781 state[repo[oldrev].rev()] = repo[newrev].rev()
782
782
783 if keepbranches is None:
783 if keepbranches is None:
784 raise util.Abort(_('.hg/rebasestate is incomplete'))
784 raise util.Abort(_('.hg/rebasestate is incomplete'))
785
785
786 skipped = set()
786 skipped = set()
787 # recompute the set of skipped revs
787 # recompute the set of skipped revs
788 if not collapse:
788 if not collapse:
789 seen = set([target])
789 seen = set([target])
790 for old, new in sorted(state.items()):
790 for old, new in sorted(state.items()):
791 if new != nullrev and new in seen:
791 if new != nullrev and new in seen:
792 skipped.add(old)
792 skipped.add(old)
793 seen.add(new)
793 seen.add(new)
794 repo.ui.debug('computed skipped revs: %s\n' %
794 repo.ui.debug('computed skipped revs: %s\n' %
795 (' '.join(str(r) for r in sorted(skipped)) or None))
795 (' '.join(str(r) for r in sorted(skipped)) or None))
796 repo.ui.debug('rebase status resumed\n')
796 repo.ui.debug('rebase status resumed\n')
797 return (originalwd, target, state, skipped,
797 return (originalwd, target, state, skipped,
798 collapse, keep, keepbranches, external, activebookmark)
798 collapse, keep, keepbranches, external, activebookmark)
799 except IOError, err:
799 except IOError, err:
800 if err.errno != errno.ENOENT:
800 if err.errno != errno.ENOENT:
801 raise
801 raise
802 raise util.Abort(_('no rebase in progress'))
802 raise util.Abort(_('no rebase in progress'))
803
803
804 def inrebase(repo, originalwd, state):
804 def inrebase(repo, originalwd, state):
805 '''check whether the working dir is in an interrupted rebase'''
805 '''check whether the working dir is in an interrupted rebase'''
806 parents = [p.rev() for p in repo.parents()]
806 parents = [p.rev() for p in repo.parents()]
807 if originalwd in parents:
807 if originalwd in parents:
808 return True
808 return True
809
809
810 for newrev in state.itervalues():
810 for newrev in state.itervalues():
811 if newrev in parents:
811 if newrev in parents:
812 return True
812 return True
813
813
814 return False
814 return False
815
815
816 def abort(repo, originalwd, target, state):
816 def abort(repo, originalwd, target, state):
817 'Restore the repository to its original state'
817 'Restore the repository to its original state'
818 dstates = [s for s in state.values() if s > nullrev]
818 dstates = [s for s in state.values() if s > nullrev]
819 immutable = [d for d in dstates if not repo[d].mutable()]
819 immutable = [d for d in dstates if not repo[d].mutable()]
820 cleanup = True
820 cleanup = True
821 if immutable:
821 if immutable:
822 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
822 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
823 % ', '.join(str(repo[r]) for r in immutable),
823 % ', '.join(str(repo[r]) for r in immutable),
824 hint=_('see hg help phases for details'))
824 hint=_('see hg help phases for details'))
825 cleanup = False
825 cleanup = False
826
826
827 descendants = set()
827 descendants = set()
828 if dstates:
828 if dstates:
829 descendants = set(repo.changelog.descendants(dstates))
829 descendants = set(repo.changelog.descendants(dstates))
830 if descendants - set(dstates):
830 if descendants - set(dstates):
831 repo.ui.warn(_("warning: new changesets detected on target branch, "
831 repo.ui.warn(_("warning: new changesets detected on target branch, "
832 "can't strip\n"))
832 "can't strip\n"))
833 cleanup = False
833 cleanup = False
834
834
835 if cleanup:
835 if cleanup:
836 # Update away from the rebase if necessary
836 # Update away from the rebase if necessary
837 if inrebase(repo, originalwd, state):
837 if inrebase(repo, originalwd, state):
838 merge.update(repo, repo[originalwd].rev(), False, True, False)
838 merge.update(repo, originalwd, False, True, False)
839
839
840 # Strip from the first rebased revision
840 # Strip from the first rebased revision
841 rebased = filter(lambda x: x > -1 and x != target, state.values())
841 rebased = filter(lambda x: x > -1 and x != target, state.values())
842 if rebased:
842 if rebased:
843 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
843 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
844 # no backup of rebased cset versions needed
844 # no backup of rebased cset versions needed
845 repair.strip(repo.ui, repo, strippoints)
845 repair.strip(repo.ui, repo, strippoints)
846
846
847 clearstatus(repo)
847 clearstatus(repo)
848 repo.ui.warn(_('rebase aborted\n'))
848 repo.ui.warn(_('rebase aborted\n'))
849 return 0
849 return 0
850
850
851 def buildstate(repo, dest, rebaseset, collapse):
851 def buildstate(repo, dest, rebaseset, collapse):
852 '''Define which revisions are going to be rebased and where
852 '''Define which revisions are going to be rebased and where
853
853
854 repo: repo
854 repo: repo
855 dest: context
855 dest: context
856 rebaseset: set of rev
856 rebaseset: set of rev
857 '''
857 '''
858
858
859 # This check isn't strictly necessary, since mq detects commits over an
859 # This check isn't strictly necessary, since mq detects commits over an
860 # applied patch. But it prevents messing up the working directory when
860 # applied patch. But it prevents messing up the working directory when
861 # a partially completed rebase is blocked by mq.
861 # a partially completed rebase is blocked by mq.
862 if 'qtip' in repo.tags() and (dest.node() in
862 if 'qtip' in repo.tags() and (dest.node() in
863 [s.node for s in repo.mq.applied]):
863 [s.node for s in repo.mq.applied]):
864 raise util.Abort(_('cannot rebase onto an applied mq patch'))
864 raise util.Abort(_('cannot rebase onto an applied mq patch'))
865
865
866 roots = list(repo.set('roots(%ld)', rebaseset))
866 roots = list(repo.set('roots(%ld)', rebaseset))
867 if not roots:
867 if not roots:
868 raise util.Abort(_('no matching revisions'))
868 raise util.Abort(_('no matching revisions'))
869 roots.sort()
869 roots.sort()
870 state = {}
870 state = {}
871 detachset = set()
871 detachset = set()
872 for root in roots:
872 for root in roots:
873 commonbase = root.ancestor(dest)
873 commonbase = root.ancestor(dest)
874 if commonbase == root:
874 if commonbase == root:
875 raise util.Abort(_('source is ancestor of destination'))
875 raise util.Abort(_('source is ancestor of destination'))
876 if commonbase == dest:
876 if commonbase == dest:
877 samebranch = root.branch() == dest.branch()
877 samebranch = root.branch() == dest.branch()
878 if not collapse and samebranch and root in dest.children():
878 if not collapse and samebranch and root in dest.children():
879 repo.ui.debug('source is a child of destination\n')
879 repo.ui.debug('source is a child of destination\n')
880 return None
880 return None
881
881
882 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
882 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
883 state.update(dict.fromkeys(rebaseset, nullrev))
883 state.update(dict.fromkeys(rebaseset, nullrev))
884 # Rebase tries to turn <dest> into a parent of <root> while
884 # Rebase tries to turn <dest> into a parent of <root> while
885 # preserving the number of parents of rebased changesets:
885 # preserving the number of parents of rebased changesets:
886 #
886 #
887 # - A changeset with a single parent will always be rebased as a
887 # - A changeset with a single parent will always be rebased as a
888 # changeset with a single parent.
888 # changeset with a single parent.
889 #
889 #
890 # - A merge will be rebased as merge unless its parents are both
890 # - A merge will be rebased as merge unless its parents are both
891 # ancestors of <dest> or are themselves in the rebased set and
891 # ancestors of <dest> or are themselves in the rebased set and
892 # pruned while rebased.
892 # pruned while rebased.
893 #
893 #
894 # If one parent of <root> is an ancestor of <dest>, the rebased
894 # If one parent of <root> is an ancestor of <dest>, the rebased
895 # version of this parent will be <dest>. This is always true with
895 # version of this parent will be <dest>. This is always true with
896 # --base option.
896 # --base option.
897 #
897 #
898 # Otherwise, we need to *replace* the original parents with
898 # Otherwise, we need to *replace* the original parents with
899 # <dest>. This "detaches" the rebased set from its former location
899 # <dest>. This "detaches" the rebased set from its former location
900 # and rebases it onto <dest>. Changes introduced by ancestors of
900 # and rebases it onto <dest>. Changes introduced by ancestors of
901 # <root> not common with <dest> (the detachset, marked as
901 # <root> not common with <dest> (the detachset, marked as
902 # nullmerge) are "removed" from the rebased changesets.
902 # nullmerge) are "removed" from the rebased changesets.
903 #
903 #
904 # - If <root> has a single parent, set it to <dest>.
904 # - If <root> has a single parent, set it to <dest>.
905 #
905 #
906 # - If <root> is a merge, we cannot decide which parent to
906 # - If <root> is a merge, we cannot decide which parent to
907 # replace, the rebase operation is not clearly defined.
907 # replace, the rebase operation is not clearly defined.
908 #
908 #
909 # The table below sums up this behavior:
909 # The table below sums up this behavior:
910 #
910 #
911 # +------------------+----------------------+-------------------------+
911 # +------------------+----------------------+-------------------------+
912 # | | one parent | merge |
912 # | | one parent | merge |
913 # +------------------+----------------------+-------------------------+
913 # +------------------+----------------------+-------------------------+
914 # | parent in | new parent is <dest> | parents in ::<dest> are |
914 # | parent in | new parent is <dest> | parents in ::<dest> are |
915 # | ::<dest> | | remapped to <dest> |
915 # | ::<dest> | | remapped to <dest> |
916 # +------------------+----------------------+-------------------------+
916 # +------------------+----------------------+-------------------------+
917 # | unrelated source | new parent is <dest> | ambiguous, abort |
917 # | unrelated source | new parent is <dest> | ambiguous, abort |
918 # +------------------+----------------------+-------------------------+
918 # +------------------+----------------------+-------------------------+
919 #
919 #
920 # The actual abort is handled by `defineparents`
920 # The actual abort is handled by `defineparents`
921 if len(root.parents()) <= 1:
921 if len(root.parents()) <= 1:
922 # ancestors of <root> not ancestors of <dest>
922 # ancestors of <root> not ancestors of <dest>
923 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
923 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
924 [root.rev()]))
924 [root.rev()]))
925 for r in detachset:
925 for r in detachset:
926 if r not in state:
926 if r not in state:
927 state[r] = nullmerge
927 state[r] = nullmerge
928 if len(roots) > 1:
928 if len(roots) > 1:
929 # If we have multiple roots, we may have "hole" in the rebase set.
929 # If we have multiple roots, we may have "hole" in the rebase set.
930 # Rebase roots that descend from those "hole" should not be detached as
930 # Rebase roots that descend from those "hole" should not be detached as
931 # other root are. We use the special `revignored` to inform rebase that
931 # other root are. We use the special `revignored` to inform rebase that
932 # the revision should be ignored but that `defineparents` should search
932 # the revision should be ignored but that `defineparents` should search
933 # a rebase destination that make sense regarding rebased topology.
933 # a rebase destination that make sense regarding rebased topology.
934 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
934 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
935 for ignored in set(rebasedomain) - set(rebaseset):
935 for ignored in set(rebasedomain) - set(rebaseset):
936 state[ignored] = revignored
936 state[ignored] = revignored
937 return repo['.'].rev(), dest.rev(), state
937 return repo['.'].rev(), dest.rev(), state
938
938
939 def clearrebased(ui, repo, state, skipped, collapsedas=None):
939 def clearrebased(ui, repo, state, skipped, collapsedas=None):
940 """dispose of rebased revision at the end of the rebase
940 """dispose of rebased revision at the end of the rebase
941
941
942 If `collapsedas` is not None, the rebase was a collapse whose result if the
942 If `collapsedas` is not None, the rebase was a collapse whose result if the
943 `collapsedas` node."""
943 `collapsedas` node."""
944 if obsolete.isenabled(repo, obsolete.createmarkersopt):
944 if obsolete.isenabled(repo, obsolete.createmarkersopt):
945 markers = []
945 markers = []
946 for rev, newrev in sorted(state.items()):
946 for rev, newrev in sorted(state.items()):
947 if newrev >= 0:
947 if newrev >= 0:
948 if rev in skipped:
948 if rev in skipped:
949 succs = ()
949 succs = ()
950 elif collapsedas is not None:
950 elif collapsedas is not None:
951 succs = (repo[collapsedas],)
951 succs = (repo[collapsedas],)
952 else:
952 else:
953 succs = (repo[newrev],)
953 succs = (repo[newrev],)
954 markers.append((repo[rev], succs))
954 markers.append((repo[rev], succs))
955 if markers:
955 if markers:
956 obsolete.createmarkers(repo, markers)
956 obsolete.createmarkers(repo, markers)
957 else:
957 else:
958 rebased = [rev for rev in state if state[rev] > nullmerge]
958 rebased = [rev for rev in state if state[rev] > nullmerge]
959 if rebased:
959 if rebased:
960 stripped = []
960 stripped = []
961 for root in repo.set('roots(%ld)', rebased):
961 for root in repo.set('roots(%ld)', rebased):
962 if set(repo.changelog.descendants([root.rev()])) - set(state):
962 if set(repo.changelog.descendants([root.rev()])) - set(state):
963 ui.warn(_("warning: new changesets detected "
963 ui.warn(_("warning: new changesets detected "
964 "on source branch, not stripping\n"))
964 "on source branch, not stripping\n"))
965 else:
965 else:
966 stripped.append(root.node())
966 stripped.append(root.node())
967 if stripped:
967 if stripped:
968 # backup the old csets by default
968 # backup the old csets by default
969 repair.strip(ui, repo, stripped, "all")
969 repair.strip(ui, repo, stripped, "all")
970
970
971
971
972 def pullrebase(orig, ui, repo, *args, **opts):
972 def pullrebase(orig, ui, repo, *args, **opts):
973 'Call rebase after pull if the latter has been invoked with --rebase'
973 'Call rebase after pull if the latter has been invoked with --rebase'
974 if opts.get('rebase'):
974 if opts.get('rebase'):
975 if opts.get('update'):
975 if opts.get('update'):
976 del opts['update']
976 del opts['update']
977 ui.debug('--update and --rebase are not compatible, ignoring '
977 ui.debug('--update and --rebase are not compatible, ignoring '
978 'the update flag\n')
978 'the update flag\n')
979
979
980 movemarkfrom = repo['.'].node()
980 movemarkfrom = repo['.'].node()
981 revsprepull = len(repo)
981 revsprepull = len(repo)
982 origpostincoming = commands.postincoming
982 origpostincoming = commands.postincoming
983 def _dummy(*args, **kwargs):
983 def _dummy(*args, **kwargs):
984 pass
984 pass
985 commands.postincoming = _dummy
985 commands.postincoming = _dummy
986 try:
986 try:
987 orig(ui, repo, *args, **opts)
987 orig(ui, repo, *args, **opts)
988 finally:
988 finally:
989 commands.postincoming = origpostincoming
989 commands.postincoming = origpostincoming
990 revspostpull = len(repo)
990 revspostpull = len(repo)
991 if revspostpull > revsprepull:
991 if revspostpull > revsprepull:
992 # --rev option from pull conflict with rebase own --rev
992 # --rev option from pull conflict with rebase own --rev
993 # dropping it
993 # dropping it
994 if 'rev' in opts:
994 if 'rev' in opts:
995 del opts['rev']
995 del opts['rev']
996 rebase(ui, repo, **opts)
996 rebase(ui, repo, **opts)
997 branch = repo[None].branch()
997 branch = repo[None].branch()
998 dest = repo[branch].rev()
998 dest = repo[branch].rev()
999 if dest != repo['.'].rev():
999 if dest != repo['.'].rev():
1000 # there was nothing to rebase we force an update
1000 # there was nothing to rebase we force an update
1001 hg.update(repo, dest)
1001 hg.update(repo, dest)
1002 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1002 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1003 ui.status(_("updating bookmark %s\n")
1003 ui.status(_("updating bookmark %s\n")
1004 % repo._bookmarkcurrent)
1004 % repo._bookmarkcurrent)
1005 else:
1005 else:
1006 if opts.get('tool'):
1006 if opts.get('tool'):
1007 raise util.Abort(_('--tool can only be used with --rebase'))
1007 raise util.Abort(_('--tool can only be used with --rebase'))
1008 orig(ui, repo, *args, **opts)
1008 orig(ui, repo, *args, **opts)
1009
1009
1010 def summaryhook(ui, repo):
1010 def summaryhook(ui, repo):
1011 if not os.path.exists(repo.join('rebasestate')):
1011 if not os.path.exists(repo.join('rebasestate')):
1012 return
1012 return
1013 try:
1013 try:
1014 state = restorestatus(repo)[2]
1014 state = restorestatus(repo)[2]
1015 except error.RepoLookupError:
1015 except error.RepoLookupError:
1016 # i18n: column positioning for "hg summary"
1016 # i18n: column positioning for "hg summary"
1017 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1017 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1018 ui.write(msg)
1018 ui.write(msg)
1019 return
1019 return
1020 numrebased = len([i for i in state.itervalues() if i != -1])
1020 numrebased = len([i for i in state.itervalues() if i != -1])
1021 # i18n: column positioning for "hg summary"
1021 # i18n: column positioning for "hg summary"
1022 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1022 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1023 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1023 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1024 ui.label(_('%d remaining'), 'rebase.remaining') %
1024 ui.label(_('%d remaining'), 'rebase.remaining') %
1025 (len(state) - numrebased)))
1025 (len(state) - numrebased)))
1026
1026
1027 def uisetup(ui):
1027 def uisetup(ui):
1028 'Replace pull with a decorator to provide --rebase option'
1028 'Replace pull with a decorator to provide --rebase option'
1029 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1029 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1030 entry[1].append(('', 'rebase', None,
1030 entry[1].append(('', 'rebase', None,
1031 _("rebase working directory to branch head")))
1031 _("rebase working directory to branch head")))
1032 entry[1].append(('t', 'tool', '',
1032 entry[1].append(('t', 'tool', '',
1033 _("specify merge tool for rebase")))
1033 _("specify merge tool for rebase")))
1034 cmdutil.summaryhooks.add('rebase', summaryhook)
1034 cmdutil.summaryhooks.add('rebase', summaryhook)
1035 cmdutil.unfinishedstates.append(
1035 cmdutil.unfinishedstates.append(
1036 ['rebasestate', False, False, _('rebase in progress'),
1036 ['rebasestate', False, False, _('rebase in progress'),
1037 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1037 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
General Comments 0
You need to be logged in to leave comments. Login now