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