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