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