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