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