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