##// END OF EJS Templates
rebase: catch RepoLookupError at restoring rebase state for summary...
FUJIWARA Katsunori -
r19849:e7fa36d2 2.7.2 stable
parent child Browse files
Show More
@@ -1,834 +1,840 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.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26 revignored = -3
26 revignored = -3
27
27
28 cmdtable = {}
28 cmdtable = {}
29 command = cmdutil.command(cmdtable)
29 command = cmdutil.command(cmdtable)
30 testedwith = 'internal'
30 testedwith = 'internal'
31
31
32 @command('rebase',
32 @command('rebase',
33 [('s', 'source', '',
33 [('s', 'source', '',
34 _('rebase from the specified changeset'), _('REV')),
34 _('rebase from the specified changeset'), _('REV')),
35 ('b', 'base', '',
35 ('b', 'base', '',
36 _('rebase from the base of the specified changeset '
36 _('rebase from the base of the specified changeset '
37 '(up to greatest common ancestor of base and dest)'),
37 '(up to greatest common ancestor of base and dest)'),
38 _('REV')),
38 _('REV')),
39 ('r', 'rev', [],
39 ('r', 'rev', [],
40 _('rebase these revisions'),
40 _('rebase these revisions'),
41 _('REV')),
41 _('REV')),
42 ('d', 'dest', '',
42 ('d', 'dest', '',
43 _('rebase onto the specified changeset'), _('REV')),
43 _('rebase onto the specified changeset'), _('REV')),
44 ('', 'collapse', False, _('collapse the rebased changesets')),
44 ('', 'collapse', False, _('collapse the rebased changesets')),
45 ('m', 'message', '',
45 ('m', 'message', '',
46 _('use text as collapse commit message'), _('TEXT')),
46 _('use text as collapse commit message'), _('TEXT')),
47 ('e', 'edit', False, _('invoke editor on commit messages')),
47 ('e', 'edit', False, _('invoke editor on commit messages')),
48 ('l', 'logfile', '',
48 ('l', 'logfile', '',
49 _('read collapse commit message from file'), _('FILE')),
49 _('read collapse commit message from file'), _('FILE')),
50 ('', 'keep', False, _('keep original changesets')),
50 ('', 'keep', False, _('keep original changesets')),
51 ('', 'keepbranches', False, _('keep original branch names')),
51 ('', 'keepbranches', False, _('keep original branch names')),
52 ('D', 'detach', False, _('(DEPRECATED)')),
52 ('D', 'detach', False, _('(DEPRECATED)')),
53 ('t', 'tool', '', _('specify merge tool')),
53 ('t', 'tool', '', _('specify merge tool')),
54 ('c', 'continue', False, _('continue an interrupted rebase')),
54 ('c', 'continue', False, _('continue an interrupted rebase')),
55 ('a', 'abort', False, _('abort an interrupted rebase'))] +
55 ('a', 'abort', False, _('abort an interrupted rebase'))] +
56 templateopts,
56 templateopts,
57 _('[-s REV | -b REV] [-d REV] [OPTION]'))
57 _('[-s REV | -b REV] [-d REV] [OPTION]'))
58 def rebase(ui, repo, **opts):
58 def rebase(ui, repo, **opts):
59 """move changeset (and descendants) to a different branch
59 """move changeset (and descendants) to a different branch
60
60
61 Rebase uses repeated merging to graft changesets from one part of
61 Rebase uses repeated merging to graft changesets from one part of
62 history (the source) onto another (the destination). This can be
62 history (the source) onto another (the destination). This can be
63 useful for linearizing *local* changes relative to a master
63 useful for linearizing *local* changes relative to a master
64 development tree.
64 development tree.
65
65
66 You should not rebase changesets that have already been shared
66 You should not rebase changesets that have already been shared
67 with others. Doing so will force everybody else to perform the
67 with others. Doing so will force everybody else to perform the
68 same rebase or they will end up with duplicated changesets after
68 same rebase or they will end up with duplicated changesets after
69 pulling in your rebased changesets.
69 pulling in your rebased changesets.
70
70
71 In its default configuration, Mercurial will prevent you from
71 In its default configuration, Mercurial will prevent you from
72 rebasing published changes. See :hg:`help phases` for details.
72 rebasing published changes. See :hg:`help phases` for details.
73
73
74 If you don't specify a destination changeset (``-d/--dest``),
74 If you don't specify a destination changeset (``-d/--dest``),
75 rebase uses the current branch tip as the destination. (The
75 rebase uses the current branch tip as the destination. (The
76 destination changeset is not modified by rebasing, but new
76 destination changeset is not modified by rebasing, but new
77 changesets are added as its descendants.)
77 changesets are added as its descendants.)
78
78
79 You can specify which changesets to rebase in two ways: as a
79 You can specify which changesets to rebase in two ways: as a
80 "source" changeset or as a "base" changeset. Both are shorthand
80 "source" changeset or as a "base" changeset. Both are shorthand
81 for a topologically related set of changesets (the "source
81 for a topologically related set of changesets (the "source
82 branch"). If you specify source (``-s/--source``), rebase will
82 branch"). If you specify source (``-s/--source``), rebase will
83 rebase that changeset and all of its descendants onto dest. If you
83 rebase that changeset and all of its descendants onto dest. If you
84 specify base (``-b/--base``), rebase will select ancestors of base
84 specify base (``-b/--base``), rebase will select ancestors of base
85 back to but not including the common ancestor with dest. Thus,
85 back to but not including the common ancestor with dest. Thus,
86 ``-b`` is less precise but more convenient than ``-s``: you can
86 ``-b`` is less precise but more convenient than ``-s``: you can
87 specify any changeset in the source branch, and rebase will select
87 specify any changeset in the source branch, and rebase will select
88 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
88 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
89 uses the parent of the working directory as the base.
89 uses the parent of the working directory as the base.
90
90
91 For advanced usage, a third way is available through the ``--rev``
91 For advanced usage, a third way is available through the ``--rev``
92 option. It allows you to specify an arbitrary set of changesets to
92 option. It allows you to specify an arbitrary set of changesets to
93 rebase. Descendants of revs you specify with this option are not
93 rebase. Descendants of revs you specify with this option are not
94 automatically included in the rebase.
94 automatically included in the rebase.
95
95
96 By default, rebase recreates the changesets in the source branch
96 By default, rebase recreates the changesets in the source branch
97 as descendants of dest and then destroys the originals. Use
97 as descendants of dest and then destroys the originals. Use
98 ``--keep`` to preserve the original source changesets. Some
98 ``--keep`` to preserve the original source changesets. Some
99 changesets in the source branch (e.g. merges from the destination
99 changesets in the source branch (e.g. merges from the destination
100 branch) may be dropped if they no longer contribute any change.
100 branch) may be dropped if they no longer contribute any change.
101
101
102 One result of the rules for selecting the destination changeset
102 One result of the rules for selecting the destination changeset
103 and source branch is that, unlike ``merge``, rebase will do
103 and source branch is that, unlike ``merge``, rebase will do
104 nothing if you are at the branch tip of a named branch
104 nothing if you are at the branch tip of a named branch
105 with two heads. You need to explicitly specify source and/or
105 with two heads. You need to explicitly specify source and/or
106 destination (or ``update`` to the other head, if it's the head of
106 destination (or ``update`` to the other head, if it's the head of
107 the intended source branch).
107 the intended source branch).
108
108
109 If a rebase is interrupted to manually resolve a merge, it can be
109 If a rebase is interrupted to manually resolve a merge, it can be
110 continued with --continue/-c or aborted with --abort/-a.
110 continued with --continue/-c or aborted with --abort/-a.
111
111
112 Returns 0 on success, 1 if nothing to rebase.
112 Returns 0 on success, 1 if nothing to rebase.
113 """
113 """
114 originalwd = target = None
114 originalwd = target = None
115 activebookmark = None
115 activebookmark = None
116 external = nullrev
116 external = nullrev
117 state = {}
117 state = {}
118 skipped = set()
118 skipped = set()
119 targetancestors = set()
119 targetancestors = set()
120
120
121 editor = None
121 editor = None
122 if opts.get('edit'):
122 if opts.get('edit'):
123 editor = cmdutil.commitforceeditor
123 editor = cmdutil.commitforceeditor
124
124
125 lock = wlock = None
125 lock = wlock = None
126 try:
126 try:
127 wlock = repo.wlock()
127 wlock = repo.wlock()
128 lock = repo.lock()
128 lock = repo.lock()
129
129
130 # Validate input and define rebasing points
130 # Validate input and define rebasing points
131 destf = opts.get('dest', None)
131 destf = opts.get('dest', None)
132 srcf = opts.get('source', None)
132 srcf = opts.get('source', None)
133 basef = opts.get('base', None)
133 basef = opts.get('base', None)
134 revf = opts.get('rev', [])
134 revf = opts.get('rev', [])
135 contf = opts.get('continue')
135 contf = opts.get('continue')
136 abortf = opts.get('abort')
136 abortf = opts.get('abort')
137 collapsef = opts.get('collapse', False)
137 collapsef = opts.get('collapse', False)
138 collapsemsg = cmdutil.logmessage(ui, opts)
138 collapsemsg = cmdutil.logmessage(ui, opts)
139 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
139 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
140 keepf = opts.get('keep', False)
140 keepf = opts.get('keep', False)
141 keepbranchesf = opts.get('keepbranches', False)
141 keepbranchesf = opts.get('keepbranches', False)
142 # keepopen is not meant for use on the command line, but by
142 # keepopen is not meant for use on the command line, but by
143 # other extensions
143 # other extensions
144 keepopen = opts.get('keepopen', False)
144 keepopen = opts.get('keepopen', False)
145
145
146 if collapsemsg and not collapsef:
146 if collapsemsg and not collapsef:
147 raise util.Abort(
147 raise util.Abort(
148 _('message can only be specified with collapse'))
148 _('message can only be specified with collapse'))
149
149
150 if contf or abortf:
150 if contf or abortf:
151 if contf and abortf:
151 if contf and abortf:
152 raise util.Abort(_('cannot use both abort and continue'))
152 raise util.Abort(_('cannot use both abort and continue'))
153 if collapsef:
153 if collapsef:
154 raise util.Abort(
154 raise util.Abort(
155 _('cannot use collapse with continue or abort'))
155 _('cannot use collapse with continue or abort'))
156 if srcf or basef or destf:
156 if srcf or basef or destf:
157 raise util.Abort(
157 raise util.Abort(
158 _('abort and continue do not allow specifying revisions'))
158 _('abort and continue do not allow specifying revisions'))
159 if opts.get('tool', False):
159 if opts.get('tool', False):
160 ui.warn(_('tool option will be ignored\n'))
160 ui.warn(_('tool option will be ignored\n'))
161
161
162 try:
162 try:
163 (originalwd, target, state, skipped, collapsef, keepf,
163 (originalwd, target, state, skipped, collapsef, keepf,
164 keepbranchesf, external, activebookmark) = restorestatus(repo)
164 keepbranchesf, external, activebookmark) = restorestatus(repo)
165 except error.RepoLookupError:
165 except error.RepoLookupError:
166 if abortf:
166 if abortf:
167 clearstatus(repo)
167 clearstatus(repo)
168 repo.ui.warn(_('rebase aborted (no revision is removed,'
168 repo.ui.warn(_('rebase aborted (no revision is removed,'
169 ' only broken state is cleared)\n'))
169 ' only broken state is cleared)\n'))
170 return 0
170 return 0
171 else:
171 else:
172 msg = _('cannot continue inconsistent rebase')
172 msg = _('cannot continue inconsistent rebase')
173 hint = _('use "hg rebase --abort" to clear borken state')
173 hint = _('use "hg rebase --abort" to clear borken state')
174 raise util.Abort(msg, hint=hint)
174 raise util.Abort(msg, hint=hint)
175 if abortf:
175 if abortf:
176 return abort(repo, originalwd, target, state)
176 return abort(repo, originalwd, target, state)
177 else:
177 else:
178 if srcf and basef:
178 if srcf and basef:
179 raise util.Abort(_('cannot specify both a '
179 raise util.Abort(_('cannot specify both a '
180 'source and a base'))
180 'source and a base'))
181 if revf and basef:
181 if revf and basef:
182 raise util.Abort(_('cannot specify both a '
182 raise util.Abort(_('cannot specify both a '
183 'revision and a base'))
183 'revision and a base'))
184 if revf and srcf:
184 if revf and srcf:
185 raise util.Abort(_('cannot specify both a '
185 raise util.Abort(_('cannot specify both a '
186 'revision and a source'))
186 'revision and a source'))
187
187
188 cmdutil.checkunfinished(repo)
188 cmdutil.checkunfinished(repo)
189 cmdutil.bailifchanged(repo)
189 cmdutil.bailifchanged(repo)
190
190
191 if not destf:
191 if not destf:
192 # Destination defaults to the latest revision in the
192 # Destination defaults to the latest revision in the
193 # current branch
193 # current branch
194 branch = repo[None].branch()
194 branch = repo[None].branch()
195 dest = repo[branch]
195 dest = repo[branch]
196 else:
196 else:
197 dest = scmutil.revsingle(repo, destf)
197 dest = scmutil.revsingle(repo, destf)
198
198
199 if revf:
199 if revf:
200 rebaseset = scmutil.revrange(repo, revf)
200 rebaseset = scmutil.revrange(repo, revf)
201 elif srcf:
201 elif srcf:
202 src = scmutil.revrange(repo, [srcf])
202 src = scmutil.revrange(repo, [srcf])
203 rebaseset = repo.revs('(%ld)::', src)
203 rebaseset = repo.revs('(%ld)::', src)
204 else:
204 else:
205 base = scmutil.revrange(repo, [basef or '.'])
205 base = scmutil.revrange(repo, [basef or '.'])
206 rebaseset = repo.revs(
206 rebaseset = repo.revs(
207 '(children(ancestor(%ld, %d)) and ::(%ld))::',
207 '(children(ancestor(%ld, %d)) and ::(%ld))::',
208 base, dest, base)
208 base, dest, base)
209 if rebaseset:
209 if rebaseset:
210 root = min(rebaseset)
210 root = min(rebaseset)
211 else:
211 else:
212 root = None
212 root = None
213
213
214 if not rebaseset:
214 if not rebaseset:
215 repo.ui.debug('base is ancestor of destination\n')
215 repo.ui.debug('base is ancestor of destination\n')
216 result = None
216 result = None
217 elif (not (keepf or obsolete._enabled)
217 elif (not (keepf or obsolete._enabled)
218 and repo.revs('first(children(%ld) - %ld)',
218 and repo.revs('first(children(%ld) - %ld)',
219 rebaseset, rebaseset)):
219 rebaseset, rebaseset)):
220 raise util.Abort(
220 raise util.Abort(
221 _("can't remove original changesets with"
221 _("can't remove original changesets with"
222 " unrebased descendants"),
222 " unrebased descendants"),
223 hint=_('use --keep to keep original changesets'))
223 hint=_('use --keep to keep original changesets'))
224 else:
224 else:
225 result = buildstate(repo, dest, rebaseset, collapsef)
225 result = buildstate(repo, dest, rebaseset, collapsef)
226
226
227 if not result:
227 if not result:
228 # Empty state built, nothing to rebase
228 # Empty state built, nothing to rebase
229 ui.status(_('nothing to rebase\n'))
229 ui.status(_('nothing to rebase\n'))
230 return 1
230 return 1
231 elif not keepf and not repo[root].mutable():
231 elif not keepf and not repo[root].mutable():
232 raise util.Abort(_("can't rebase immutable changeset %s")
232 raise util.Abort(_("can't rebase immutable changeset %s")
233 % repo[root],
233 % repo[root],
234 hint=_('see hg help phases for details'))
234 hint=_('see hg help phases for details'))
235 else:
235 else:
236 originalwd, target, state = result
236 originalwd, target, state = result
237 if collapsef:
237 if collapsef:
238 targetancestors = repo.changelog.ancestors([target],
238 targetancestors = repo.changelog.ancestors([target],
239 inclusive=True)
239 inclusive=True)
240 external = checkexternal(repo, state, targetancestors)
240 external = checkexternal(repo, state, targetancestors)
241
241
242 if keepbranchesf:
242 if keepbranchesf:
243 assert not extrafn, 'cannot use both keepbranches and extrafn'
243 assert not extrafn, 'cannot use both keepbranches and extrafn'
244 def extrafn(ctx, extra):
244 def extrafn(ctx, extra):
245 extra['branch'] = ctx.branch()
245 extra['branch'] = ctx.branch()
246 if collapsef:
246 if collapsef:
247 branches = set()
247 branches = set()
248 for rev in state:
248 for rev in state:
249 branches.add(repo[rev].branch())
249 branches.add(repo[rev].branch())
250 if len(branches) > 1:
250 if len(branches) > 1:
251 raise util.Abort(_('cannot collapse multiple named '
251 raise util.Abort(_('cannot collapse multiple named '
252 'branches'))
252 'branches'))
253
253
254
254
255 # Rebase
255 # Rebase
256 if not targetancestors:
256 if not targetancestors:
257 targetancestors = repo.changelog.ancestors([target], inclusive=True)
257 targetancestors = repo.changelog.ancestors([target], inclusive=True)
258
258
259 # Keep track of the current bookmarks in order to reset them later
259 # Keep track of the current bookmarks in order to reset them later
260 currentbookmarks = repo._bookmarks.copy()
260 currentbookmarks = repo._bookmarks.copy()
261 activebookmark = activebookmark or repo._bookmarkcurrent
261 activebookmark = activebookmark or repo._bookmarkcurrent
262 if activebookmark:
262 if activebookmark:
263 bookmarks.unsetcurrent(repo)
263 bookmarks.unsetcurrent(repo)
264
264
265 sortedstate = sorted(state)
265 sortedstate = sorted(state)
266 total = len(sortedstate)
266 total = len(sortedstate)
267 pos = 0
267 pos = 0
268 for rev in sortedstate:
268 for rev in sortedstate:
269 pos += 1
269 pos += 1
270 if state[rev] == -1:
270 if state[rev] == -1:
271 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
271 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
272 _('changesets'), total)
272 _('changesets'), total)
273 p1, p2 = defineparents(repo, rev, target, state,
273 p1, p2 = defineparents(repo, rev, target, state,
274 targetancestors)
274 targetancestors)
275 storestatus(repo, originalwd, target, state, collapsef, keepf,
275 storestatus(repo, originalwd, target, state, collapsef, keepf,
276 keepbranchesf, external, activebookmark)
276 keepbranchesf, external, activebookmark)
277 if len(repo.parents()) == 2:
277 if len(repo.parents()) == 2:
278 repo.ui.debug('resuming interrupted rebase\n')
278 repo.ui.debug('resuming interrupted rebase\n')
279 else:
279 else:
280 try:
280 try:
281 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
281 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
282 stats = rebasenode(repo, rev, p1, state, collapsef)
282 stats = rebasenode(repo, rev, p1, state, collapsef)
283 if stats and stats[3] > 0:
283 if stats and stats[3] > 0:
284 raise error.InterventionRequired(
284 raise error.InterventionRequired(
285 _('unresolved conflicts (see hg '
285 _('unresolved conflicts (see hg '
286 'resolve, then hg rebase --continue)'))
286 'resolve, then hg rebase --continue)'))
287 finally:
287 finally:
288 ui.setconfig('ui', 'forcemerge', '')
288 ui.setconfig('ui', 'forcemerge', '')
289 cmdutil.duplicatecopies(repo, rev, target)
289 cmdutil.duplicatecopies(repo, rev, target)
290 if not collapsef:
290 if not collapsef:
291 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
291 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
292 editor=editor)
292 editor=editor)
293 else:
293 else:
294 # Skip commit if we are collapsing
294 # Skip commit if we are collapsing
295 repo.setparents(repo[p1].node())
295 repo.setparents(repo[p1].node())
296 newrev = None
296 newrev = None
297 # Update the state
297 # Update the state
298 if newrev is not None:
298 if newrev is not None:
299 state[rev] = repo[newrev].rev()
299 state[rev] = repo[newrev].rev()
300 else:
300 else:
301 if not collapsef:
301 if not collapsef:
302 ui.note(_('no changes, revision %d skipped\n') % rev)
302 ui.note(_('no changes, revision %d skipped\n') % rev)
303 ui.debug('next revision set to %s\n' % p1)
303 ui.debug('next revision set to %s\n' % p1)
304 skipped.add(rev)
304 skipped.add(rev)
305 state[rev] = p1
305 state[rev] = p1
306
306
307 ui.progress(_('rebasing'), None)
307 ui.progress(_('rebasing'), None)
308 ui.note(_('rebase merging completed\n'))
308 ui.note(_('rebase merging completed\n'))
309
309
310 if collapsef and not keepopen:
310 if collapsef and not keepopen:
311 p1, p2 = defineparents(repo, min(state), target,
311 p1, p2 = defineparents(repo, min(state), target,
312 state, targetancestors)
312 state, targetancestors)
313 if collapsemsg:
313 if collapsemsg:
314 commitmsg = collapsemsg
314 commitmsg = collapsemsg
315 else:
315 else:
316 commitmsg = 'Collapsed revision'
316 commitmsg = 'Collapsed revision'
317 for rebased in state:
317 for rebased in state:
318 if rebased not in skipped and state[rebased] > nullmerge:
318 if rebased not in skipped and state[rebased] > nullmerge:
319 commitmsg += '\n* %s' % repo[rebased].description()
319 commitmsg += '\n* %s' % repo[rebased].description()
320 commitmsg = ui.edit(commitmsg, repo.ui.username())
320 commitmsg = ui.edit(commitmsg, repo.ui.username())
321 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
321 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
322 extrafn=extrafn, editor=editor)
322 extrafn=extrafn, editor=editor)
323
323
324 if 'qtip' in repo.tags():
324 if 'qtip' in repo.tags():
325 updatemq(repo, state, skipped, **opts)
325 updatemq(repo, state, skipped, **opts)
326
326
327 if currentbookmarks:
327 if currentbookmarks:
328 # Nodeids are needed to reset bookmarks
328 # Nodeids are needed to reset bookmarks
329 nstate = {}
329 nstate = {}
330 for k, v in state.iteritems():
330 for k, v in state.iteritems():
331 if v > nullmerge:
331 if v > nullmerge:
332 nstate[repo[k].node()] = repo[v].node()
332 nstate[repo[k].node()] = repo[v].node()
333 # XXX this is the same as dest.node() for the non-continue path --
333 # XXX this is the same as dest.node() for the non-continue path --
334 # this should probably be cleaned up
334 # this should probably be cleaned up
335 targetnode = repo[target].node()
335 targetnode = repo[target].node()
336
336
337 if not keepf:
337 if not keepf:
338 collapsedas = None
338 collapsedas = None
339 if collapsef:
339 if collapsef:
340 collapsedas = newrev
340 collapsedas = newrev
341 clearrebased(ui, repo, state, skipped, collapsedas)
341 clearrebased(ui, repo, state, skipped, collapsedas)
342
342
343 if currentbookmarks:
343 if currentbookmarks:
344 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
344 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
345
345
346 clearstatus(repo)
346 clearstatus(repo)
347 ui.note(_("rebase completed\n"))
347 ui.note(_("rebase completed\n"))
348 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
348 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
349 if skipped:
349 if skipped:
350 ui.note(_("%d revisions have been skipped\n") % len(skipped))
350 ui.note(_("%d revisions have been skipped\n") % len(skipped))
351
351
352 if (activebookmark and
352 if (activebookmark and
353 repo['tip'].node() == repo._bookmarks[activebookmark]):
353 repo['tip'].node() == repo._bookmarks[activebookmark]):
354 bookmarks.setcurrent(repo, activebookmark)
354 bookmarks.setcurrent(repo, activebookmark)
355
355
356 finally:
356 finally:
357 release(lock, wlock)
357 release(lock, wlock)
358
358
359 def checkexternal(repo, state, targetancestors):
359 def checkexternal(repo, state, targetancestors):
360 """Check whether one or more external revisions need to be taken in
360 """Check whether one or more external revisions need to be taken in
361 consideration. In the latter case, abort.
361 consideration. In the latter case, abort.
362 """
362 """
363 external = nullrev
363 external = nullrev
364 source = min(state)
364 source = min(state)
365 for rev in state:
365 for rev in state:
366 if rev == source:
366 if rev == source:
367 continue
367 continue
368 # Check externals and fail if there are more than one
368 # Check externals and fail if there are more than one
369 for p in repo[rev].parents():
369 for p in repo[rev].parents():
370 if (p.rev() not in state
370 if (p.rev() not in state
371 and p.rev() not in targetancestors):
371 and p.rev() not in targetancestors):
372 if external != nullrev:
372 if external != nullrev:
373 raise util.Abort(_('unable to collapse, there is more '
373 raise util.Abort(_('unable to collapse, there is more '
374 'than one external parent'))
374 'than one external parent'))
375 external = p.rev()
375 external = p.rev()
376 return external
376 return external
377
377
378 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
378 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
379 'Commit the changes and store useful information in extra'
379 'Commit the changes and store useful information in extra'
380 try:
380 try:
381 repo.setparents(repo[p1].node(), repo[p2].node())
381 repo.setparents(repo[p1].node(), repo[p2].node())
382 ctx = repo[rev]
382 ctx = repo[rev]
383 if commitmsg is None:
383 if commitmsg is None:
384 commitmsg = ctx.description()
384 commitmsg = ctx.description()
385 extra = {'rebase_source': ctx.hex()}
385 extra = {'rebase_source': ctx.hex()}
386 if extrafn:
386 if extrafn:
387 extrafn(ctx, extra)
387 extrafn(ctx, extra)
388 # Commit might fail if unresolved files exist
388 # Commit might fail if unresolved files exist
389 newrev = repo.commit(text=commitmsg, user=ctx.user(),
389 newrev = repo.commit(text=commitmsg, user=ctx.user(),
390 date=ctx.date(), extra=extra, editor=editor)
390 date=ctx.date(), extra=extra, editor=editor)
391 repo.dirstate.setbranch(repo[newrev].branch())
391 repo.dirstate.setbranch(repo[newrev].branch())
392 targetphase = max(ctx.phase(), phases.draft)
392 targetphase = max(ctx.phase(), phases.draft)
393 # retractboundary doesn't overwrite upper phase inherited from parent
393 # retractboundary doesn't overwrite upper phase inherited from parent
394 newnode = repo[newrev].node()
394 newnode = repo[newrev].node()
395 if newnode:
395 if newnode:
396 phases.retractboundary(repo, targetphase, [newnode])
396 phases.retractboundary(repo, targetphase, [newnode])
397 return newrev
397 return newrev
398 except util.Abort:
398 except util.Abort:
399 # Invalidate the previous setparents
399 # Invalidate the previous setparents
400 repo.dirstate.invalidate()
400 repo.dirstate.invalidate()
401 raise
401 raise
402
402
403 def rebasenode(repo, rev, p1, state, collapse):
403 def rebasenode(repo, rev, p1, state, collapse):
404 'Rebase a single revision'
404 'Rebase a single revision'
405 # Merge phase
405 # Merge phase
406 # Update to target and merge it with local
406 # Update to target and merge it with local
407 if repo['.'].rev() != repo[p1].rev():
407 if repo['.'].rev() != repo[p1].rev():
408 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
408 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
409 merge.update(repo, p1, False, True, False)
409 merge.update(repo, p1, False, True, False)
410 else:
410 else:
411 repo.ui.debug(" already in target\n")
411 repo.ui.debug(" already in target\n")
412 repo.dirstate.write()
412 repo.dirstate.write()
413 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
413 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
414 base = None
414 base = None
415 if repo[rev].rev() != repo[min(state)].rev():
415 if repo[rev].rev() != repo[min(state)].rev():
416 base = repo[rev].p1().node()
416 base = repo[rev].p1().node()
417 # When collapsing in-place, the parent is the common ancestor, we
417 # When collapsing in-place, the parent is the common ancestor, we
418 # have to allow merging with it.
418 # have to allow merging with it.
419 return merge.update(repo, rev, True, True, False, base, collapse)
419 return merge.update(repo, rev, True, True, False, base, collapse)
420
420
421 def nearestrebased(repo, rev, state):
421 def nearestrebased(repo, rev, state):
422 """return the nearest ancestors of rev in the rebase result"""
422 """return the nearest ancestors of rev in the rebase result"""
423 rebased = [r for r in state if state[r] > nullmerge]
423 rebased = [r for r in state if state[r] > nullmerge]
424 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
424 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
425 if candidates:
425 if candidates:
426 return state[candidates[0]]
426 return state[candidates[0]]
427 else:
427 else:
428 return None
428 return None
429
429
430 def defineparents(repo, rev, target, state, targetancestors):
430 def defineparents(repo, rev, target, state, targetancestors):
431 'Return the new parent relationship of the revision that will be rebased'
431 'Return the new parent relationship of the revision that will be rebased'
432 parents = repo[rev].parents()
432 parents = repo[rev].parents()
433 p1 = p2 = nullrev
433 p1 = p2 = nullrev
434
434
435 P1n = parents[0].rev()
435 P1n = parents[0].rev()
436 if P1n in targetancestors:
436 if P1n in targetancestors:
437 p1 = target
437 p1 = target
438 elif P1n in state:
438 elif P1n in state:
439 if state[P1n] == nullmerge:
439 if state[P1n] == nullmerge:
440 p1 = target
440 p1 = target
441 elif state[P1n] == revignored:
441 elif state[P1n] == revignored:
442 p1 = nearestrebased(repo, P1n, state)
442 p1 = nearestrebased(repo, P1n, state)
443 if p1 is None:
443 if p1 is None:
444 p1 = target
444 p1 = target
445 else:
445 else:
446 p1 = state[P1n]
446 p1 = state[P1n]
447 else: # P1n external
447 else: # P1n external
448 p1 = target
448 p1 = target
449 p2 = P1n
449 p2 = P1n
450
450
451 if len(parents) == 2 and parents[1].rev() not in targetancestors:
451 if len(parents) == 2 and parents[1].rev() not in targetancestors:
452 P2n = parents[1].rev()
452 P2n = parents[1].rev()
453 # interesting second parent
453 # interesting second parent
454 if P2n in state:
454 if P2n in state:
455 if p1 == target: # P1n in targetancestors or external
455 if p1 == target: # P1n in targetancestors or external
456 p1 = state[P2n]
456 p1 = state[P2n]
457 elif state[P2n] == revignored:
457 elif state[P2n] == revignored:
458 p2 = nearestrebased(repo, P2n, state)
458 p2 = nearestrebased(repo, P2n, state)
459 if p2 is None:
459 if p2 is None:
460 # no ancestors rebased yet, detach
460 # no ancestors rebased yet, detach
461 p2 = target
461 p2 = target
462 else:
462 else:
463 p2 = state[P2n]
463 p2 = state[P2n]
464 else: # P2n external
464 else: # P2n external
465 if p2 != nullrev: # P1n external too => rev is a merged revision
465 if p2 != nullrev: # P1n external too => rev is a merged revision
466 raise util.Abort(_('cannot use revision %d as base, result '
466 raise util.Abort(_('cannot use revision %d as base, result '
467 'would have 3 parents') % rev)
467 'would have 3 parents') % rev)
468 p2 = P2n
468 p2 = P2n
469 repo.ui.debug(" future parents are %d and %d\n" %
469 repo.ui.debug(" future parents are %d and %d\n" %
470 (repo[p1].rev(), repo[p2].rev()))
470 (repo[p1].rev(), repo[p2].rev()))
471 return p1, p2
471 return p1, p2
472
472
473 def isagitpatch(repo, patchname):
473 def isagitpatch(repo, patchname):
474 'Return true if the given patch is in git format'
474 'Return true if the given patch is in git format'
475 mqpatch = os.path.join(repo.mq.path, patchname)
475 mqpatch = os.path.join(repo.mq.path, patchname)
476 for line in patch.linereader(file(mqpatch, 'rb')):
476 for line in patch.linereader(file(mqpatch, 'rb')):
477 if line.startswith('diff --git'):
477 if line.startswith('diff --git'):
478 return True
478 return True
479 return False
479 return False
480
480
481 def updatemq(repo, state, skipped, **opts):
481 def updatemq(repo, state, skipped, **opts):
482 'Update rebased mq patches - finalize and then import them'
482 'Update rebased mq patches - finalize and then import them'
483 mqrebase = {}
483 mqrebase = {}
484 mq = repo.mq
484 mq = repo.mq
485 original_series = mq.fullseries[:]
485 original_series = mq.fullseries[:]
486 skippedpatches = set()
486 skippedpatches = set()
487
487
488 for p in mq.applied:
488 for p in mq.applied:
489 rev = repo[p.node].rev()
489 rev = repo[p.node].rev()
490 if rev in state:
490 if rev in state:
491 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
491 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
492 (rev, p.name))
492 (rev, p.name))
493 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
493 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
494 else:
494 else:
495 # Applied but not rebased, not sure this should happen
495 # Applied but not rebased, not sure this should happen
496 skippedpatches.add(p.name)
496 skippedpatches.add(p.name)
497
497
498 if mqrebase:
498 if mqrebase:
499 mq.finish(repo, mqrebase.keys())
499 mq.finish(repo, mqrebase.keys())
500
500
501 # We must start import from the newest revision
501 # We must start import from the newest revision
502 for rev in sorted(mqrebase, reverse=True):
502 for rev in sorted(mqrebase, reverse=True):
503 if rev not in skipped:
503 if rev not in skipped:
504 name, isgit = mqrebase[rev]
504 name, isgit = mqrebase[rev]
505 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
505 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
506 mq.qimport(repo, (), patchname=name, git=isgit,
506 mq.qimport(repo, (), patchname=name, git=isgit,
507 rev=[str(state[rev])])
507 rev=[str(state[rev])])
508 else:
508 else:
509 # Rebased and skipped
509 # Rebased and skipped
510 skippedpatches.add(mqrebase[rev][0])
510 skippedpatches.add(mqrebase[rev][0])
511
511
512 # Patches were either applied and rebased and imported in
512 # Patches were either applied and rebased and imported in
513 # order, applied and removed or unapplied. Discard the removed
513 # order, applied and removed or unapplied. Discard the removed
514 # ones while preserving the original series order and guards.
514 # ones while preserving the original series order and guards.
515 newseries = [s for s in original_series
515 newseries = [s for s in original_series
516 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
516 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
517 mq.fullseries[:] = newseries
517 mq.fullseries[:] = newseries
518 mq.seriesdirty = True
518 mq.seriesdirty = True
519 mq.savedirty()
519 mq.savedirty()
520
520
521 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
521 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
522 'Move bookmarks to their correct changesets, and delete divergent ones'
522 'Move bookmarks to their correct changesets, and delete divergent ones'
523 marks = repo._bookmarks
523 marks = repo._bookmarks
524 for k, v in originalbookmarks.iteritems():
524 for k, v in originalbookmarks.iteritems():
525 if v in nstate:
525 if v in nstate:
526 # update the bookmarks for revs that have moved
526 # update the bookmarks for revs that have moved
527 marks[k] = nstate[v]
527 marks[k] = nstate[v]
528 bookmarks.deletedivergent(repo, [targetnode], k)
528 bookmarks.deletedivergent(repo, [targetnode], k)
529
529
530 marks.write()
530 marks.write()
531
531
532 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
532 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
533 external, activebookmark):
533 external, activebookmark):
534 'Store the current status to allow recovery'
534 'Store the current status to allow recovery'
535 f = repo.opener("rebasestate", "w")
535 f = repo.opener("rebasestate", "w")
536 f.write(repo[originalwd].hex() + '\n')
536 f.write(repo[originalwd].hex() + '\n')
537 f.write(repo[target].hex() + '\n')
537 f.write(repo[target].hex() + '\n')
538 f.write(repo[external].hex() + '\n')
538 f.write(repo[external].hex() + '\n')
539 f.write('%d\n' % int(collapse))
539 f.write('%d\n' % int(collapse))
540 f.write('%d\n' % int(keep))
540 f.write('%d\n' % int(keep))
541 f.write('%d\n' % int(keepbranches))
541 f.write('%d\n' % int(keepbranches))
542 f.write('%s\n' % (activebookmark or ''))
542 f.write('%s\n' % (activebookmark or ''))
543 for d, v in state.iteritems():
543 for d, v in state.iteritems():
544 oldrev = repo[d].hex()
544 oldrev = repo[d].hex()
545 if v > nullmerge:
545 if v > nullmerge:
546 newrev = repo[v].hex()
546 newrev = repo[v].hex()
547 else:
547 else:
548 newrev = v
548 newrev = v
549 f.write("%s:%s\n" % (oldrev, newrev))
549 f.write("%s:%s\n" % (oldrev, newrev))
550 f.close()
550 f.close()
551 repo.ui.debug('rebase status stored\n')
551 repo.ui.debug('rebase status stored\n')
552
552
553 def clearstatus(repo):
553 def clearstatus(repo):
554 'Remove the status files'
554 'Remove the status files'
555 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
555 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
556
556
557 def restorestatus(repo):
557 def restorestatus(repo):
558 'Restore a previously stored status'
558 'Restore a previously stored status'
559 try:
559 try:
560 target = None
560 target = None
561 collapse = False
561 collapse = False
562 external = nullrev
562 external = nullrev
563 activebookmark = None
563 activebookmark = None
564 state = {}
564 state = {}
565 f = repo.opener("rebasestate")
565 f = repo.opener("rebasestate")
566 for i, l in enumerate(f.read().splitlines()):
566 for i, l in enumerate(f.read().splitlines()):
567 if i == 0:
567 if i == 0:
568 originalwd = repo[l].rev()
568 originalwd = repo[l].rev()
569 elif i == 1:
569 elif i == 1:
570 target = repo[l].rev()
570 target = repo[l].rev()
571 elif i == 2:
571 elif i == 2:
572 external = repo[l].rev()
572 external = repo[l].rev()
573 elif i == 3:
573 elif i == 3:
574 collapse = bool(int(l))
574 collapse = bool(int(l))
575 elif i == 4:
575 elif i == 4:
576 keep = bool(int(l))
576 keep = bool(int(l))
577 elif i == 5:
577 elif i == 5:
578 keepbranches = bool(int(l))
578 keepbranches = bool(int(l))
579 elif i == 6 and not (len(l) == 81 and ':' in l):
579 elif i == 6 and not (len(l) == 81 and ':' in l):
580 # line 6 is a recent addition, so for backwards compatibility
580 # line 6 is a recent addition, so for backwards compatibility
581 # check that the line doesn't look like the oldrev:newrev lines
581 # check that the line doesn't look like the oldrev:newrev lines
582 activebookmark = l
582 activebookmark = l
583 else:
583 else:
584 oldrev, newrev = l.split(':')
584 oldrev, newrev = l.split(':')
585 if newrev in (str(nullmerge), str(revignored)):
585 if newrev in (str(nullmerge), str(revignored)):
586 state[repo[oldrev].rev()] = int(newrev)
586 state[repo[oldrev].rev()] = int(newrev)
587 else:
587 else:
588 state[repo[oldrev].rev()] = repo[newrev].rev()
588 state[repo[oldrev].rev()] = repo[newrev].rev()
589 skipped = set()
589 skipped = set()
590 # recompute the set of skipped revs
590 # recompute the set of skipped revs
591 if not collapse:
591 if not collapse:
592 seen = set([target])
592 seen = set([target])
593 for old, new in sorted(state.items()):
593 for old, new in sorted(state.items()):
594 if new != nullrev and new in seen:
594 if new != nullrev and new in seen:
595 skipped.add(old)
595 skipped.add(old)
596 seen.add(new)
596 seen.add(new)
597 repo.ui.debug('computed skipped revs: %s\n' % skipped)
597 repo.ui.debug('computed skipped revs: %s\n' % skipped)
598 repo.ui.debug('rebase status resumed\n')
598 repo.ui.debug('rebase status resumed\n')
599 return (originalwd, target, state, skipped,
599 return (originalwd, target, state, skipped,
600 collapse, keep, keepbranches, external, activebookmark)
600 collapse, keep, keepbranches, external, activebookmark)
601 except IOError, err:
601 except IOError, err:
602 if err.errno != errno.ENOENT:
602 if err.errno != errno.ENOENT:
603 raise
603 raise
604 raise util.Abort(_('no rebase in progress'))
604 raise util.Abort(_('no rebase in progress'))
605
605
606 def inrebase(repo, originalwd, state):
606 def inrebase(repo, originalwd, state):
607 '''check whether the workdir is in an interrupted rebase'''
607 '''check whether the workdir is in an interrupted rebase'''
608 parents = [p.rev() for p in repo.parents()]
608 parents = [p.rev() for p in repo.parents()]
609 if originalwd in parents:
609 if originalwd in parents:
610 return True
610 return True
611
611
612 for newrev in state.itervalues():
612 for newrev in state.itervalues():
613 if newrev in parents:
613 if newrev in parents:
614 return True
614 return True
615
615
616 return False
616 return False
617
617
618 def abort(repo, originalwd, target, state):
618 def abort(repo, originalwd, target, state):
619 'Restore the repository to its original state'
619 'Restore the repository to its original state'
620 dstates = [s for s in state.values() if s != nullrev]
620 dstates = [s for s in state.values() if s != nullrev]
621 immutable = [d for d in dstates if not repo[d].mutable()]
621 immutable = [d for d in dstates if not repo[d].mutable()]
622 cleanup = True
622 cleanup = True
623 if immutable:
623 if immutable:
624 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
624 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
625 % ', '.join(str(repo[r]) for r in immutable),
625 % ', '.join(str(repo[r]) for r in immutable),
626 hint=_('see hg help phases for details'))
626 hint=_('see hg help phases for details'))
627 cleanup = False
627 cleanup = False
628
628
629 descendants = set()
629 descendants = set()
630 if dstates:
630 if dstates:
631 descendants = set(repo.changelog.descendants(dstates))
631 descendants = set(repo.changelog.descendants(dstates))
632 if descendants - set(dstates):
632 if descendants - set(dstates):
633 repo.ui.warn(_("warning: new changesets detected on target branch, "
633 repo.ui.warn(_("warning: new changesets detected on target branch, "
634 "can't strip\n"))
634 "can't strip\n"))
635 cleanup = False
635 cleanup = False
636
636
637 if cleanup:
637 if cleanup:
638 # Update away from the rebase if necessary
638 # Update away from the rebase if necessary
639 if inrebase(repo, originalwd, state):
639 if inrebase(repo, originalwd, state):
640 merge.update(repo, repo[originalwd].rev(), False, True, False)
640 merge.update(repo, repo[originalwd].rev(), False, True, False)
641
641
642 # Strip from the first rebased revision
642 # Strip from the first rebased revision
643 rebased = filter(lambda x: x > -1 and x != target, state.values())
643 rebased = filter(lambda x: x > -1 and x != target, state.values())
644 if rebased:
644 if rebased:
645 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
645 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
646 # no backup of rebased cset versions needed
646 # no backup of rebased cset versions needed
647 repair.strip(repo.ui, repo, strippoints)
647 repair.strip(repo.ui, repo, strippoints)
648
648
649 clearstatus(repo)
649 clearstatus(repo)
650 repo.ui.warn(_('rebase aborted\n'))
650 repo.ui.warn(_('rebase aborted\n'))
651 return 0
651 return 0
652
652
653 def buildstate(repo, dest, rebaseset, collapse):
653 def buildstate(repo, dest, rebaseset, collapse):
654 '''Define which revisions are going to be rebased and where
654 '''Define which revisions are going to be rebased and where
655
655
656 repo: repo
656 repo: repo
657 dest: context
657 dest: context
658 rebaseset: set of rev
658 rebaseset: set of rev
659 '''
659 '''
660
660
661 # This check isn't strictly necessary, since mq detects commits over an
661 # This check isn't strictly necessary, since mq detects commits over an
662 # applied patch. But it prevents messing up the working directory when
662 # applied patch. But it prevents messing up the working directory when
663 # a partially completed rebase is blocked by mq.
663 # a partially completed rebase is blocked by mq.
664 if 'qtip' in repo.tags() and (dest.node() in
664 if 'qtip' in repo.tags() and (dest.node() in
665 [s.node for s in repo.mq.applied]):
665 [s.node for s in repo.mq.applied]):
666 raise util.Abort(_('cannot rebase onto an applied mq patch'))
666 raise util.Abort(_('cannot rebase onto an applied mq patch'))
667
667
668 roots = list(repo.set('roots(%ld)', rebaseset))
668 roots = list(repo.set('roots(%ld)', rebaseset))
669 if not roots:
669 if not roots:
670 raise util.Abort(_('no matching revisions'))
670 raise util.Abort(_('no matching revisions'))
671 roots.sort()
671 roots.sort()
672 state = {}
672 state = {}
673 detachset = set()
673 detachset = set()
674 for root in roots:
674 for root in roots:
675 commonbase = root.ancestor(dest)
675 commonbase = root.ancestor(dest)
676 if commonbase == root:
676 if commonbase == root:
677 raise util.Abort(_('source is ancestor of destination'))
677 raise util.Abort(_('source is ancestor of destination'))
678 if commonbase == dest:
678 if commonbase == dest:
679 samebranch = root.branch() == dest.branch()
679 samebranch = root.branch() == dest.branch()
680 if not collapse and samebranch and root in dest.children():
680 if not collapse and samebranch and root in dest.children():
681 repo.ui.debug('source is a child of destination\n')
681 repo.ui.debug('source is a child of destination\n')
682 return None
682 return None
683
683
684 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
684 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
685 state.update(dict.fromkeys(rebaseset, nullrev))
685 state.update(dict.fromkeys(rebaseset, nullrev))
686 # Rebase tries to turn <dest> into a parent of <root> while
686 # Rebase tries to turn <dest> into a parent of <root> while
687 # preserving the number of parents of rebased changesets:
687 # preserving the number of parents of rebased changesets:
688 #
688 #
689 # - A changeset with a single parent will always be rebased as a
689 # - A changeset with a single parent will always be rebased as a
690 # changeset with a single parent.
690 # changeset with a single parent.
691 #
691 #
692 # - A merge will be rebased as merge unless its parents are both
692 # - A merge will be rebased as merge unless its parents are both
693 # ancestors of <dest> or are themselves in the rebased set and
693 # ancestors of <dest> or are themselves in the rebased set and
694 # pruned while rebased.
694 # pruned while rebased.
695 #
695 #
696 # If one parent of <root> is an ancestor of <dest>, the rebased
696 # If one parent of <root> is an ancestor of <dest>, the rebased
697 # version of this parent will be <dest>. This is always true with
697 # version of this parent will be <dest>. This is always true with
698 # --base option.
698 # --base option.
699 #
699 #
700 # Otherwise, we need to *replace* the original parents with
700 # Otherwise, we need to *replace* the original parents with
701 # <dest>. This "detaches" the rebased set from its former location
701 # <dest>. This "detaches" the rebased set from its former location
702 # and rebases it onto <dest>. Changes introduced by ancestors of
702 # and rebases it onto <dest>. Changes introduced by ancestors of
703 # <root> not common with <dest> (the detachset, marked as
703 # <root> not common with <dest> (the detachset, marked as
704 # nullmerge) are "removed" from the rebased changesets.
704 # nullmerge) are "removed" from the rebased changesets.
705 #
705 #
706 # - If <root> has a single parent, set it to <dest>.
706 # - If <root> has a single parent, set it to <dest>.
707 #
707 #
708 # - If <root> is a merge, we cannot decide which parent to
708 # - If <root> is a merge, we cannot decide which parent to
709 # replace, the rebase operation is not clearly defined.
709 # replace, the rebase operation is not clearly defined.
710 #
710 #
711 # The table below sums up this behavior:
711 # The table below sums up this behavior:
712 #
712 #
713 # +------------------+----------------------+-------------------------+
713 # +------------------+----------------------+-------------------------+
714 # | | one parent | merge |
714 # | | one parent | merge |
715 # +------------------+----------------------+-------------------------+
715 # +------------------+----------------------+-------------------------+
716 # | parent in | new parent is <dest> | parents in ::<dest> are |
716 # | parent in | new parent is <dest> | parents in ::<dest> are |
717 # | ::<dest> | | remapped to <dest> |
717 # | ::<dest> | | remapped to <dest> |
718 # +------------------+----------------------+-------------------------+
718 # +------------------+----------------------+-------------------------+
719 # | unrelated source | new parent is <dest> | ambiguous, abort |
719 # | unrelated source | new parent is <dest> | ambiguous, abort |
720 # +------------------+----------------------+-------------------------+
720 # +------------------+----------------------+-------------------------+
721 #
721 #
722 # The actual abort is handled by `defineparents`
722 # The actual abort is handled by `defineparents`
723 if len(root.parents()) <= 1:
723 if len(root.parents()) <= 1:
724 # ancestors of <root> not ancestors of <dest>
724 # ancestors of <root> not ancestors of <dest>
725 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
725 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
726 [root.rev()]))
726 [root.rev()]))
727 for r in detachset:
727 for r in detachset:
728 if r not in state:
728 if r not in state:
729 state[r] = nullmerge
729 state[r] = nullmerge
730 if len(roots) > 1:
730 if len(roots) > 1:
731 # If we have multiple roots, we may have "hole" in the rebase set.
731 # If we have multiple roots, we may have "hole" in the rebase set.
732 # Rebase roots that descend from those "hole" should not be detached as
732 # Rebase roots that descend from those "hole" should not be detached as
733 # other root are. We use the special `revignored` to inform rebase that
733 # other root are. We use the special `revignored` to inform rebase that
734 # the revision should be ignored but that `defineparents` should search
734 # the revision should be ignored but that `defineparents` should search
735 # a rebase destination that make sense regarding rebased topology.
735 # a rebase destination that make sense regarding rebased topology.
736 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
736 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
737 for ignored in set(rebasedomain) - set(rebaseset):
737 for ignored in set(rebasedomain) - set(rebaseset):
738 state[ignored] = revignored
738 state[ignored] = revignored
739 return repo['.'].rev(), dest.rev(), state
739 return repo['.'].rev(), dest.rev(), state
740
740
741 def clearrebased(ui, repo, state, skipped, collapsedas=None):
741 def clearrebased(ui, repo, state, skipped, collapsedas=None):
742 """dispose of rebased revision at the end of the rebase
742 """dispose of rebased revision at the end of the rebase
743
743
744 If `collapsedas` is not None, the rebase was a collapse whose result if the
744 If `collapsedas` is not None, the rebase was a collapse whose result if the
745 `collapsedas` node."""
745 `collapsedas` node."""
746 if obsolete._enabled:
746 if obsolete._enabled:
747 markers = []
747 markers = []
748 for rev, newrev in sorted(state.items()):
748 for rev, newrev in sorted(state.items()):
749 if newrev >= 0:
749 if newrev >= 0:
750 if rev in skipped:
750 if rev in skipped:
751 succs = ()
751 succs = ()
752 elif collapsedas is not None:
752 elif collapsedas is not None:
753 succs = (repo[collapsedas],)
753 succs = (repo[collapsedas],)
754 else:
754 else:
755 succs = (repo[newrev],)
755 succs = (repo[newrev],)
756 markers.append((repo[rev], succs))
756 markers.append((repo[rev], succs))
757 if markers:
757 if markers:
758 obsolete.createmarkers(repo, markers)
758 obsolete.createmarkers(repo, markers)
759 else:
759 else:
760 rebased = [rev for rev in state if state[rev] > nullmerge]
760 rebased = [rev for rev in state if state[rev] > nullmerge]
761 if rebased:
761 if rebased:
762 stripped = []
762 stripped = []
763 for root in repo.set('roots(%ld)', rebased):
763 for root in repo.set('roots(%ld)', rebased):
764 if set(repo.changelog.descendants([root.rev()])) - set(state):
764 if set(repo.changelog.descendants([root.rev()])) - set(state):
765 ui.warn(_("warning: new changesets detected "
765 ui.warn(_("warning: new changesets detected "
766 "on source branch, not stripping\n"))
766 "on source branch, not stripping\n"))
767 else:
767 else:
768 stripped.append(root.node())
768 stripped.append(root.node())
769 if stripped:
769 if stripped:
770 # backup the old csets by default
770 # backup the old csets by default
771 repair.strip(ui, repo, stripped, "all")
771 repair.strip(ui, repo, stripped, "all")
772
772
773
773
774 def pullrebase(orig, ui, repo, *args, **opts):
774 def pullrebase(orig, ui, repo, *args, **opts):
775 'Call rebase after pull if the latter has been invoked with --rebase'
775 'Call rebase after pull if the latter has been invoked with --rebase'
776 if opts.get('rebase'):
776 if opts.get('rebase'):
777 if opts.get('update'):
777 if opts.get('update'):
778 del opts['update']
778 del opts['update']
779 ui.debug('--update and --rebase are not compatible, ignoring '
779 ui.debug('--update and --rebase are not compatible, ignoring '
780 'the update flag\n')
780 'the update flag\n')
781
781
782 movemarkfrom = repo['.'].node()
782 movemarkfrom = repo['.'].node()
783 cmdutil.bailifchanged(repo)
783 cmdutil.bailifchanged(repo)
784 revsprepull = len(repo)
784 revsprepull = len(repo)
785 origpostincoming = commands.postincoming
785 origpostincoming = commands.postincoming
786 def _dummy(*args, **kwargs):
786 def _dummy(*args, **kwargs):
787 pass
787 pass
788 commands.postincoming = _dummy
788 commands.postincoming = _dummy
789 try:
789 try:
790 orig(ui, repo, *args, **opts)
790 orig(ui, repo, *args, **opts)
791 finally:
791 finally:
792 commands.postincoming = origpostincoming
792 commands.postincoming = origpostincoming
793 revspostpull = len(repo)
793 revspostpull = len(repo)
794 if revspostpull > revsprepull:
794 if revspostpull > revsprepull:
795 # --rev option from pull conflict with rebase own --rev
795 # --rev option from pull conflict with rebase own --rev
796 # dropping it
796 # dropping it
797 if 'rev' in opts:
797 if 'rev' in opts:
798 del opts['rev']
798 del opts['rev']
799 rebase(ui, repo, **opts)
799 rebase(ui, repo, **opts)
800 branch = repo[None].branch()
800 branch = repo[None].branch()
801 dest = repo[branch].rev()
801 dest = repo[branch].rev()
802 if dest != repo['.'].rev():
802 if dest != repo['.'].rev():
803 # there was nothing to rebase we force an update
803 # there was nothing to rebase we force an update
804 hg.update(repo, dest)
804 hg.update(repo, dest)
805 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
805 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
806 ui.status(_("updating bookmark %s\n")
806 ui.status(_("updating bookmark %s\n")
807 % repo._bookmarkcurrent)
807 % repo._bookmarkcurrent)
808 else:
808 else:
809 if opts.get('tool'):
809 if opts.get('tool'):
810 raise util.Abort(_('--tool can only be used with --rebase'))
810 raise util.Abort(_('--tool can only be used with --rebase'))
811 orig(ui, repo, *args, **opts)
811 orig(ui, repo, *args, **opts)
812
812
813 def summaryhook(ui, repo):
813 def summaryhook(ui, repo):
814 if not os.path.exists(repo.join('rebasestate')):
814 if not os.path.exists(repo.join('rebasestate')):
815 return
815 return
816 state = restorestatus(repo)[2]
816 try:
817 state = restorestatus(repo)[2]
818 except error.RepoLookupError:
819 # i18n: column positioning for "hg summary"
820 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
821 ui.write(msg)
822 return
817 numrebased = len([i for i in state.itervalues() if i != -1])
823 numrebased = len([i for i in state.itervalues() if i != -1])
818 # i18n: column positioning for "hg summary"
824 # i18n: column positioning for "hg summary"
819 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
825 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
820 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
826 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
821 ui.label(_('%d remaining'), 'rebase.remaining') %
827 ui.label(_('%d remaining'), 'rebase.remaining') %
822 (len(state) - numrebased)))
828 (len(state) - numrebased)))
823
829
824 def uisetup(ui):
830 def uisetup(ui):
825 'Replace pull with a decorator to provide --rebase option'
831 'Replace pull with a decorator to provide --rebase option'
826 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
832 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
827 entry[1].append(('', 'rebase', None,
833 entry[1].append(('', 'rebase', None,
828 _("rebase working directory to branch head")))
834 _("rebase working directory to branch head")))
829 entry[1].append(('t', 'tool', '',
835 entry[1].append(('t', 'tool', '',
830 _("specify merge tool for rebase")))
836 _("specify merge tool for rebase")))
831 cmdutil.summaryhooks.add('rebase', summaryhook)
837 cmdutil.summaryhooks.add('rebase', summaryhook)
832 cmdutil.unfinishedstates.append(
838 cmdutil.unfinishedstates.append(
833 ['rebasestate', False, False, _('rebase in progress'),
839 ['rebasestate', False, False, _('rebase in progress'),
834 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
840 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,181 +1,183 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > graphlog=
3 > graphlog=
4 > rebase=
4 > rebase=
5 >
5 >
6 > [phases]
6 > [phases]
7 > publish=False
7 > publish=False
8 >
8 >
9 > [alias]
9 > [alias]
10 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
10 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
11 > EOF
11 > EOF
12
12
13
13
14 $ hg init a
14 $ hg init a
15 $ cd a
15 $ cd a
16
16
17 $ echo c1 > common
17 $ echo c1 > common
18 $ hg add common
18 $ hg add common
19 $ hg ci -m C1
19 $ hg ci -m C1
20
20
21 $ echo c2 >> common
21 $ echo c2 >> common
22 $ hg ci -m C2
22 $ hg ci -m C2
23
23
24 $ echo c3 >> common
24 $ echo c3 >> common
25 $ hg ci -m C3
25 $ hg ci -m C3
26
26
27 $ hg up -q -C 1
27 $ hg up -q -C 1
28
28
29 $ echo l1 >> extra
29 $ echo l1 >> extra
30 $ hg add extra
30 $ hg add extra
31 $ hg ci -m L1
31 $ hg ci -m L1
32 created new head
32 created new head
33
33
34 $ sed -e 's/c2/l2/' common > common.new
34 $ sed -e 's/c2/l2/' common > common.new
35 $ mv common.new common
35 $ mv common.new common
36 $ hg ci -m L2
36 $ hg ci -m L2
37
37
38 $ hg phase --force --secret 2
38 $ hg phase --force --secret 2
39
39
40 $ hg tglog
40 $ hg tglog
41 @ 4:draft 'L2'
41 @ 4:draft 'L2'
42 |
42 |
43 o 3:draft 'L1'
43 o 3:draft 'L1'
44 |
44 |
45 | o 2:secret 'C3'
45 | o 2:secret 'C3'
46 |/
46 |/
47 o 1:draft 'C2'
47 o 1:draft 'C2'
48 |
48 |
49 o 0:draft 'C1'
49 o 0:draft 'C1'
50
50
51
51
52 Conflicting rebase:
52 Conflicting rebase:
53
53
54 $ hg rebase -s 3 -d 2
54 $ hg rebase -s 3 -d 2
55 merging common
55 merging common
56 warning: conflicts during merge.
56 warning: conflicts during merge.
57 merging common incomplete! (edit conflicts, then use 'hg resolve --mark')
57 merging common incomplete! (edit conflicts, then use 'hg resolve --mark')
58 unresolved conflicts (see hg resolve, then hg rebase --continue)
58 unresolved conflicts (see hg resolve, then hg rebase --continue)
59 [1]
59 [1]
60
60
61 Abort:
61 Abort:
62
62
63 $ hg rebase --abort
63 $ hg rebase --abort
64 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
64 saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob)
65 rebase aborted
65 rebase aborted
66
66
67 $ hg tglog
67 $ hg tglog
68 @ 4:draft 'L2'
68 @ 4:draft 'L2'
69 |
69 |
70 o 3:draft 'L1'
70 o 3:draft 'L1'
71 |
71 |
72 | o 2:secret 'C3'
72 | o 2:secret 'C3'
73 |/
73 |/
74 o 1:draft 'C2'
74 o 1:draft 'C2'
75 |
75 |
76 o 0:draft 'C1'
76 o 0:draft 'C1'
77
77
78 Test safety for inconsistent rebase state, which may be created (and
78 Test safety for inconsistent rebase state, which may be created (and
79 forgotten) by Mercurial earlier than 2.7. This emulates Mercurial
79 forgotten) by Mercurial earlier than 2.7. This emulates Mercurial
80 earlier than 2.7 by renaming ".hg/rebasestate" temporarily.
80 earlier than 2.7 by renaming ".hg/rebasestate" temporarily.
81
81
82 $ hg rebase -s 3 -d 2
82 $ hg rebase -s 3 -d 2
83 merging common
83 merging common
84 warning: conflicts during merge.
84 warning: conflicts during merge.
85 merging common incomplete! (edit conflicts, then use 'hg resolve --mark')
85 merging common incomplete! (edit conflicts, then use 'hg resolve --mark')
86 unresolved conflicts (see hg resolve, then hg rebase --continue)
86 unresolved conflicts (see hg resolve, then hg rebase --continue)
87 [1]
87 [1]
88
88
89 $ mv .hg/rebasestate .hg/rebasestate.back
89 $ mv .hg/rebasestate .hg/rebasestate.back
90 $ hg update --quiet --clean 2
90 $ hg update --quiet --clean 2
91 $ hg --config extensions.mq= strip --quiet "destination()"
91 $ hg --config extensions.mq= strip --quiet "destination()"
92 $ mv .hg/rebasestate.back .hg/rebasestate
92 $ mv .hg/rebasestate.back .hg/rebasestate
93
93
94 $ hg rebase --continue
94 $ hg rebase --continue
95 abort: cannot continue inconsistent rebase
95 abort: cannot continue inconsistent rebase
96 (use "hg rebase --abort" to clear borken state)
96 (use "hg rebase --abort" to clear borken state)
97 [255]
97 [255]
98 $ hg summary | grep '^rebase: '
99 rebase: (use "hg rebase --abort" to clear broken state)
98 $ hg rebase --abort
100 $ hg rebase --abort
99 rebase aborted (no revision is removed, only broken state is cleared)
101 rebase aborted (no revision is removed, only broken state is cleared)
100
102
101 $ cd ..
103 $ cd ..
102
104
103
105
104 Construct new repo:
106 Construct new repo:
105
107
106 $ hg init b
108 $ hg init b
107 $ cd b
109 $ cd b
108
110
109 $ echo a > a
111 $ echo a > a
110 $ hg ci -Am A
112 $ hg ci -Am A
111 adding a
113 adding a
112
114
113 $ echo b > b
115 $ echo b > b
114 $ hg ci -Am B
116 $ hg ci -Am B
115 adding b
117 adding b
116
118
117 $ echo c > c
119 $ echo c > c
118 $ hg ci -Am C
120 $ hg ci -Am C
119 adding c
121 adding c
120
122
121 $ hg up -q 0
123 $ hg up -q 0
122
124
123 $ echo b > b
125 $ echo b > b
124 $ hg ci -Am 'B bis'
126 $ hg ci -Am 'B bis'
125 adding b
127 adding b
126 created new head
128 created new head
127
129
128 $ echo c1 > c
130 $ echo c1 > c
129 $ hg ci -Am C1
131 $ hg ci -Am C1
130 adding c
132 adding c
131
133
132 $ hg phase --force --secret 1
134 $ hg phase --force --secret 1
133 $ hg phase --public 1
135 $ hg phase --public 1
134
136
135 Rebase and abort without generating new changesets:
137 Rebase and abort without generating new changesets:
136
138
137 $ hg tglog
139 $ hg tglog
138 @ 4:draft 'C1'
140 @ 4:draft 'C1'
139 |
141 |
140 o 3:draft 'B bis'
142 o 3:draft 'B bis'
141 |
143 |
142 | o 2:secret 'C'
144 | o 2:secret 'C'
143 | |
145 | |
144 | o 1:public 'B'
146 | o 1:public 'B'
145 |/
147 |/
146 o 0:public 'A'
148 o 0:public 'A'
147
149
148 $ hg rebase -b 4 -d 2
150 $ hg rebase -b 4 -d 2
149 merging c
151 merging c
150 warning: conflicts during merge.
152 warning: conflicts during merge.
151 merging c incomplete! (edit conflicts, then use 'hg resolve --mark')
153 merging c incomplete! (edit conflicts, then use 'hg resolve --mark')
152 unresolved conflicts (see hg resolve, then hg rebase --continue)
154 unresolved conflicts (see hg resolve, then hg rebase --continue)
153 [1]
155 [1]
154
156
155 $ hg tglog
157 $ hg tglog
156 @ 4:draft 'C1'
158 @ 4:draft 'C1'
157 |
159 |
158 o 3:draft 'B bis'
160 o 3:draft 'B bis'
159 |
161 |
160 | @ 2:secret 'C'
162 | @ 2:secret 'C'
161 | |
163 | |
162 | o 1:public 'B'
164 | o 1:public 'B'
163 |/
165 |/
164 o 0:public 'A'
166 o 0:public 'A'
165
167
166 $ hg rebase -a
168 $ hg rebase -a
167 rebase aborted
169 rebase aborted
168
170
169 $ hg tglog
171 $ hg tglog
170 @ 4:draft 'C1'
172 @ 4:draft 'C1'
171 |
173 |
172 o 3:draft 'B bis'
174 o 3:draft 'B bis'
173 |
175 |
174 | o 2:secret 'C'
176 | o 2:secret 'C'
175 | |
177 | |
176 | o 1:public 'B'
178 | o 1:public 'B'
177 |/
179 |/
178 o 0:public 'A'
180 o 0:public 'A'
179
181
180
182
181 $ cd ..
183 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now