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