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