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