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