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