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