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