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