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