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