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