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