##// END OF EJS Templates
rebase: block collapse with keepbranches on multiple named branches (issue2112)...
Stefano Tortarolo -
r14897:b9daa5b7 stable
parent child Browse files
Show More
@@ -1,610 +1,618 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, copies, patch
18 from mercurial import extensions, copies, patch
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
26
27 cmdtable = {}
27 cmdtable = {}
28 command = cmdutil.command(cmdtable)
28 command = cmdutil.command(cmdtable)
29
29
30 @command('rebase',
30 @command('rebase',
31 [('s', 'source', '',
31 [('s', 'source', '',
32 _('rebase from the specified changeset'), _('REV')),
32 _('rebase from the specified changeset'), _('REV')),
33 ('b', 'base', '',
33 ('b', 'base', '',
34 _('rebase from the base of the specified changeset '
34 _('rebase from the base of the specified changeset '
35 '(up to greatest common ancestor of base and dest)'),
35 '(up to greatest common ancestor of base and dest)'),
36 _('REV')),
36 _('REV')),
37 ('d', 'dest', '',
37 ('d', 'dest', '',
38 _('rebase onto the specified changeset'), _('REV')),
38 _('rebase onto the specified changeset'), _('REV')),
39 ('', 'collapse', False, _('collapse the rebased changesets')),
39 ('', 'collapse', False, _('collapse the rebased changesets')),
40 ('m', 'message', '',
40 ('m', 'message', '',
41 _('use text as collapse commit message'), _('TEXT')),
41 _('use text as collapse commit message'), _('TEXT')),
42 ('l', 'logfile', '',
42 ('l', 'logfile', '',
43 _('read collapse commit message from file'), _('FILE')),
43 _('read collapse commit message from file'), _('FILE')),
44 ('', 'keep', False, _('keep original changesets')),
44 ('', 'keep', False, _('keep original changesets')),
45 ('', 'keepbranches', False, _('keep original branch names')),
45 ('', 'keepbranches', False, _('keep original branch names')),
46 ('', 'detach', False, _('force detaching of source from its original '
46 ('', 'detach', False, _('force detaching of source from its original '
47 'branch')),
47 'branch')),
48 ('t', 'tool', '', _('specify merge tool')),
48 ('t', 'tool', '', _('specify merge tool')),
49 ('c', 'continue', False, _('continue an interrupted rebase')),
49 ('c', 'continue', False, _('continue an interrupted rebase')),
50 ('a', 'abort', False, _('abort an interrupted rebase'))] +
50 ('a', 'abort', False, _('abort an interrupted rebase'))] +
51 templateopts,
51 templateopts,
52 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
52 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
53 'hg rebase {-a|-c}'))
53 'hg rebase {-a|-c}'))
54 def rebase(ui, repo, **opts):
54 def rebase(ui, repo, **opts):
55 """move changeset (and descendants) to a different branch
55 """move changeset (and descendants) to a different branch
56
56
57 Rebase uses repeated merging to graft changesets from one part of
57 Rebase uses repeated merging to graft changesets from one part of
58 history (the source) onto another (the destination). This can be
58 history (the source) onto another (the destination). This can be
59 useful for linearizing *local* changes relative to a master
59 useful for linearizing *local* changes relative to a master
60 development tree.
60 development tree.
61
61
62 You should not rebase changesets that have already been shared
62 You should not rebase changesets that have already been shared
63 with others. Doing so will force everybody else to perform the
63 with others. Doing so will force everybody else to perform the
64 same rebase or they will end up with duplicated changesets after
64 same rebase or they will end up with duplicated changesets after
65 pulling in your rebased changesets.
65 pulling in your rebased changesets.
66
66
67 If you don't specify a destination changeset (``-d/--dest``),
67 If you don't specify a destination changeset (``-d/--dest``),
68 rebase uses the tipmost head of the current named branch as the
68 rebase uses the tipmost head of the current named branch as the
69 destination. (The destination changeset is not modified by
69 destination. (The destination changeset is not modified by
70 rebasing, but new changesets are added as its descendants.)
70 rebasing, but new changesets are added as its descendants.)
71
71
72 You can specify which changesets to rebase in two ways: as a
72 You can specify which changesets to rebase in two ways: as a
73 "source" changeset or as a "base" changeset. Both are shorthand
73 "source" changeset or as a "base" changeset. Both are shorthand
74 for a topologically related set of changesets (the "source
74 for a topologically related set of changesets (the "source
75 branch"). If you specify source (``-s/--source``), rebase will
75 branch"). If you specify source (``-s/--source``), rebase will
76 rebase that changeset and all of its descendants onto dest. If you
76 rebase that changeset and all of its descendants onto dest. If you
77 specify base (``-b/--base``), rebase will select ancestors of base
77 specify base (``-b/--base``), rebase will select ancestors of base
78 back to but not including the common ancestor with dest. Thus,
78 back to but not including the common ancestor with dest. Thus,
79 ``-b`` is less precise but more convenient than ``-s``: you can
79 ``-b`` is less precise but more convenient than ``-s``: you can
80 specify any changeset in the source branch, and rebase will select
80 specify any changeset in the source branch, and rebase will select
81 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
81 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
82 uses the parent of the working directory as the base.
82 uses the parent of the working directory as the base.
83
83
84 By default, rebase recreates the changesets in the source branch
84 By default, rebase recreates the changesets in the source branch
85 as descendants of dest and then destroys the originals. Use
85 as descendants of dest and then destroys the originals. Use
86 ``--keep`` to preserve the original source changesets. Some
86 ``--keep`` to preserve the original source changesets. Some
87 changesets in the source branch (e.g. merges from the destination
87 changesets in the source branch (e.g. merges from the destination
88 branch) may be dropped if they no longer contribute any change.
88 branch) may be dropped if they no longer contribute any change.
89
89
90 One result of the rules for selecting the destination changeset
90 One result of the rules for selecting the destination changeset
91 and source branch is that, unlike ``merge``, rebase will do
91 and source branch is that, unlike ``merge``, rebase will do
92 nothing if you are at the latest (tipmost) head of a named branch
92 nothing if you are at the latest (tipmost) head of a named branch
93 with two heads. You need to explicitly specify source and/or
93 with two heads. You need to explicitly specify source and/or
94 destination (or ``update`` to the other head, if it's the head of
94 destination (or ``update`` to the other head, if it's the head of
95 the intended source branch).
95 the intended source branch).
96
96
97 If a rebase is interrupted to manually resolve a merge, it can be
97 If a rebase is interrupted to manually resolve a merge, it can be
98 continued with --continue/-c or aborted with --abort/-a.
98 continued with --continue/-c or aborted with --abort/-a.
99
99
100 Returns 0 on success, 1 if nothing to rebase.
100 Returns 0 on success, 1 if nothing to rebase.
101 """
101 """
102 originalwd = target = None
102 originalwd = target = None
103 external = nullrev
103 external = nullrev
104 state = {}
104 state = {}
105 skipped = set()
105 skipped = set()
106 targetancestors = set()
106 targetancestors = set()
107
107
108 lock = wlock = None
108 lock = wlock = None
109 try:
109 try:
110 lock = repo.lock()
110 lock = repo.lock()
111 wlock = repo.wlock()
111 wlock = repo.wlock()
112
112
113 # Validate input and define rebasing points
113 # Validate input and define rebasing points
114 destf = opts.get('dest', None)
114 destf = opts.get('dest', None)
115 srcf = opts.get('source', None)
115 srcf = opts.get('source', None)
116 basef = opts.get('base', None)
116 basef = opts.get('base', None)
117 contf = opts.get('continue')
117 contf = opts.get('continue')
118 abortf = opts.get('abort')
118 abortf = opts.get('abort')
119 collapsef = opts.get('collapse', False)
119 collapsef = opts.get('collapse', False)
120 collapsemsg = cmdutil.logmessage(ui, opts)
120 collapsemsg = cmdutil.logmessage(ui, opts)
121 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
121 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
122 keepf = opts.get('keep', False)
122 keepf = opts.get('keep', False)
123 keepbranchesf = opts.get('keepbranches', False)
123 keepbranchesf = opts.get('keepbranches', False)
124 detachf = opts.get('detach', False)
124 detachf = opts.get('detach', False)
125 # keepopen is not meant for use on the command line, but by
125 # keepopen is not meant for use on the command line, but by
126 # other extensions
126 # other extensions
127 keepopen = opts.get('keepopen', False)
127 keepopen = opts.get('keepopen', False)
128
128
129 if collapsemsg and not collapsef:
129 if collapsemsg and not collapsef:
130 raise util.Abort(
130 raise util.Abort(
131 _('message can only be specified with collapse'))
131 _('message can only be specified with collapse'))
132
132
133 if contf or abortf:
133 if contf or abortf:
134 if contf and abortf:
134 if contf and abortf:
135 raise util.Abort(_('cannot use both abort and continue'))
135 raise util.Abort(_('cannot use both abort and continue'))
136 if collapsef:
136 if collapsef:
137 raise util.Abort(
137 raise util.Abort(
138 _('cannot use collapse with continue or abort'))
138 _('cannot use collapse with continue or abort'))
139 if detachf:
139 if detachf:
140 raise util.Abort(_('cannot use detach with continue or abort'))
140 raise util.Abort(_('cannot use detach with continue or abort'))
141 if srcf or basef or destf:
141 if srcf or basef or destf:
142 raise util.Abort(
142 raise util.Abort(
143 _('abort and continue do not allow specifying revisions'))
143 _('abort and continue do not allow specifying revisions'))
144 if opts.get('tool', False):
144 if opts.get('tool', False):
145 ui.warn(_('tool option will be ignored\n'))
145 ui.warn(_('tool option will be ignored\n'))
146
146
147 (originalwd, target, state, skipped, collapsef, keepf,
147 (originalwd, target, state, skipped, collapsef, keepf,
148 keepbranchesf, external) = restorestatus(repo)
148 keepbranchesf, external) = restorestatus(repo)
149 if abortf:
149 if abortf:
150 return abort(repo, originalwd, target, state)
150 return abort(repo, originalwd, target, state)
151 else:
151 else:
152 if srcf and basef:
152 if srcf and basef:
153 raise util.Abort(_('cannot specify both a '
153 raise util.Abort(_('cannot specify both a '
154 'revision and a base'))
154 'revision and a base'))
155 if detachf:
155 if detachf:
156 if not srcf:
156 if not srcf:
157 raise util.Abort(
157 raise util.Abort(
158 _('detach requires a revision to be specified'))
158 _('detach requires a revision to be specified'))
159 if basef:
159 if basef:
160 raise util.Abort(_('cannot specify a base with detach'))
160 raise util.Abort(_('cannot specify a base with detach'))
161
161
162 cmdutil.bailifchanged(repo)
162 cmdutil.bailifchanged(repo)
163 result = buildstate(repo, destf, srcf, basef, detachf)
163 result = buildstate(repo, destf, srcf, basef, detachf)
164 if not result:
164 if not result:
165 # Empty state built, nothing to rebase
165 # Empty state built, nothing to rebase
166 ui.status(_('nothing to rebase\n'))
166 ui.status(_('nothing to rebase\n'))
167 return 1
167 return 1
168 else:
168 else:
169 originalwd, target, state = result
169 originalwd, target, state = result
170 if collapsef:
170 if collapsef:
171 targetancestors = set(repo.changelog.ancestors(target))
171 targetancestors = set(repo.changelog.ancestors(target))
172 external = checkexternal(repo, state, targetancestors)
172 external = checkexternal(repo, state, targetancestors)
173
173
174 if keepbranchesf:
174 if keepbranchesf:
175 assert not extrafn, 'cannot use both keepbranches and extrafn'
175 assert not extrafn, 'cannot use both keepbranches and extrafn'
176 def extrafn(ctx, extra):
176 def extrafn(ctx, extra):
177 extra['branch'] = ctx.branch()
177 extra['branch'] = ctx.branch()
178 if collapsef:
179 branches = set()
180 for rev in state:
181 branches.add(repo[rev].branch())
182 if len(branches) > 1:
183 raise util.Abort(_('cannot collapse multiple named '
184 'branches'))
185
178
186
179 # Rebase
187 # Rebase
180 if not targetancestors:
188 if not targetancestors:
181 targetancestors = set(repo.changelog.ancestors(target))
189 targetancestors = set(repo.changelog.ancestors(target))
182 targetancestors.add(target)
190 targetancestors.add(target)
183
191
184 # Keep track of the current bookmarks in order to reset them later
192 # Keep track of the current bookmarks in order to reset them later
185 currentbookmarks = repo._bookmarks.copy()
193 currentbookmarks = repo._bookmarks.copy()
186
194
187 sortedstate = sorted(state)
195 sortedstate = sorted(state)
188 total = len(sortedstate)
196 total = len(sortedstate)
189 pos = 0
197 pos = 0
190 for rev in sortedstate:
198 for rev in sortedstate:
191 pos += 1
199 pos += 1
192 if state[rev] == -1:
200 if state[rev] == -1:
193 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
201 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
194 _('changesets'), total)
202 _('changesets'), total)
195 storestatus(repo, originalwd, target, state, collapsef, keepf,
203 storestatus(repo, originalwd, target, state, collapsef, keepf,
196 keepbranchesf, external)
204 keepbranchesf, external)
197 p1, p2 = defineparents(repo, rev, target, state,
205 p1, p2 = defineparents(repo, rev, target, state,
198 targetancestors)
206 targetancestors)
199 if len(repo.parents()) == 2:
207 if len(repo.parents()) == 2:
200 repo.ui.debug('resuming interrupted rebase\n')
208 repo.ui.debug('resuming interrupted rebase\n')
201 else:
209 else:
202 try:
210 try:
203 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
211 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
204 stats = rebasenode(repo, rev, p1, state)
212 stats = rebasenode(repo, rev, p1, state)
205 if stats and stats[3] > 0:
213 if stats and stats[3] > 0:
206 raise util.Abort(_('unresolved conflicts (see hg '
214 raise util.Abort(_('unresolved conflicts (see hg '
207 'resolve, then hg rebase --continue)'))
215 'resolve, then hg rebase --continue)'))
208 finally:
216 finally:
209 ui.setconfig('ui', 'forcemerge', '')
217 ui.setconfig('ui', 'forcemerge', '')
210 updatedirstate(repo, rev, target, p2)
218 updatedirstate(repo, rev, target, p2)
211 if not collapsef:
219 if not collapsef:
212 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
220 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
213 else:
221 else:
214 # Skip commit if we are collapsing
222 # Skip commit if we are collapsing
215 repo.dirstate.setparents(repo[p1].node())
223 repo.dirstate.setparents(repo[p1].node())
216 newrev = None
224 newrev = None
217 # Update the state
225 # Update the state
218 if newrev is not None:
226 if newrev is not None:
219 state[rev] = repo[newrev].rev()
227 state[rev] = repo[newrev].rev()
220 else:
228 else:
221 if not collapsef:
229 if not collapsef:
222 ui.note(_('no changes, revision %d skipped\n') % rev)
230 ui.note(_('no changes, revision %d skipped\n') % rev)
223 ui.debug('next revision set to %s\n' % p1)
231 ui.debug('next revision set to %s\n' % p1)
224 skipped.add(rev)
232 skipped.add(rev)
225 state[rev] = p1
233 state[rev] = p1
226
234
227 ui.progress(_('rebasing'), None)
235 ui.progress(_('rebasing'), None)
228 ui.note(_('rebase merging completed\n'))
236 ui.note(_('rebase merging completed\n'))
229
237
230 if collapsef and not keepopen:
238 if collapsef and not keepopen:
231 p1, p2 = defineparents(repo, min(state), target,
239 p1, p2 = defineparents(repo, min(state), target,
232 state, targetancestors)
240 state, targetancestors)
233 if collapsemsg:
241 if collapsemsg:
234 commitmsg = collapsemsg
242 commitmsg = collapsemsg
235 else:
243 else:
236 commitmsg = 'Collapsed revision'
244 commitmsg = 'Collapsed revision'
237 for rebased in state:
245 for rebased in state:
238 if rebased not in skipped and state[rebased] != nullmerge:
246 if rebased not in skipped and state[rebased] != nullmerge:
239 commitmsg += '\n* %s' % repo[rebased].description()
247 commitmsg += '\n* %s' % repo[rebased].description()
240 commitmsg = ui.edit(commitmsg, repo.ui.username())
248 commitmsg = ui.edit(commitmsg, repo.ui.username())
241 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
249 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
242 extrafn=extrafn)
250 extrafn=extrafn)
243
251
244 if 'qtip' in repo.tags():
252 if 'qtip' in repo.tags():
245 updatemq(repo, state, skipped, **opts)
253 updatemq(repo, state, skipped, **opts)
246
254
247 if currentbookmarks:
255 if currentbookmarks:
248 # Nodeids are needed to reset bookmarks
256 # Nodeids are needed to reset bookmarks
249 nstate = {}
257 nstate = {}
250 for k, v in state.iteritems():
258 for k, v in state.iteritems():
251 if v != nullmerge:
259 if v != nullmerge:
252 nstate[repo[k].node()] = repo[v].node()
260 nstate[repo[k].node()] = repo[v].node()
253
261
254 if not keepf:
262 if not keepf:
255 # Remove no more useful revisions
263 # Remove no more useful revisions
256 rebased = [rev for rev in state if state[rev] != nullmerge]
264 rebased = [rev for rev in state if state[rev] != nullmerge]
257 if rebased:
265 if rebased:
258 if set(repo.changelog.descendants(min(rebased))) - set(state):
266 if set(repo.changelog.descendants(min(rebased))) - set(state):
259 ui.warn(_("warning: new changesets detected "
267 ui.warn(_("warning: new changesets detected "
260 "on source branch, not stripping\n"))
268 "on source branch, not stripping\n"))
261 else:
269 else:
262 # backup the old csets by default
270 # backup the old csets by default
263 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
271 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
264
272
265 if currentbookmarks:
273 if currentbookmarks:
266 updatebookmarks(repo, nstate, currentbookmarks, **opts)
274 updatebookmarks(repo, nstate, currentbookmarks, **opts)
267
275
268 clearstatus(repo)
276 clearstatus(repo)
269 ui.note(_("rebase completed\n"))
277 ui.note(_("rebase completed\n"))
270 if os.path.exists(repo.sjoin('undo')):
278 if os.path.exists(repo.sjoin('undo')):
271 util.unlinkpath(repo.sjoin('undo'))
279 util.unlinkpath(repo.sjoin('undo'))
272 if skipped:
280 if skipped:
273 ui.note(_("%d revisions have been skipped\n") % len(skipped))
281 ui.note(_("%d revisions have been skipped\n") % len(skipped))
274 finally:
282 finally:
275 release(lock, wlock)
283 release(lock, wlock)
276
284
277 def checkexternal(repo, state, targetancestors):
285 def checkexternal(repo, state, targetancestors):
278 """Check whether one or more external revisions need to be taken in
286 """Check whether one or more external revisions need to be taken in
279 consideration. In the latter case, abort.
287 consideration. In the latter case, abort.
280 """
288 """
281 external = nullrev
289 external = nullrev
282 source = min(state)
290 source = min(state)
283 for rev in state:
291 for rev in state:
284 if rev == source:
292 if rev == source:
285 continue
293 continue
286 # Check externals and fail if there are more than one
294 # Check externals and fail if there are more than one
287 for p in repo[rev].parents():
295 for p in repo[rev].parents():
288 if (p.rev() not in state
296 if (p.rev() not in state
289 and p.rev() not in targetancestors):
297 and p.rev() not in targetancestors):
290 if external != nullrev:
298 if external != nullrev:
291 raise util.Abort(_('unable to collapse, there is more '
299 raise util.Abort(_('unable to collapse, there is more '
292 'than one external parent'))
300 'than one external parent'))
293 external = p.rev()
301 external = p.rev()
294 return external
302 return external
295
303
296 def updatedirstate(repo, rev, p1, p2):
304 def updatedirstate(repo, rev, p1, p2):
297 """Keep track of renamed files in the revision that is going to be rebased
305 """Keep track of renamed files in the revision that is going to be rebased
298 """
306 """
299 # Here we simulate the copies and renames in the source changeset
307 # Here we simulate the copies and renames in the source changeset
300 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
308 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
301 m1 = repo[rev].manifest()
309 m1 = repo[rev].manifest()
302 m2 = repo[p1].manifest()
310 m2 = repo[p1].manifest()
303 for k, v in cop.iteritems():
311 for k, v in cop.iteritems():
304 if k in m1:
312 if k in m1:
305 if v in m1 or v in m2:
313 if v in m1 or v in m2:
306 repo.dirstate.copy(v, k)
314 repo.dirstate.copy(v, k)
307 if v in m2 and v not in m1 and k in m2:
315 if v in m2 and v not in m1 and k in m2:
308 repo.dirstate.remove(v)
316 repo.dirstate.remove(v)
309
317
310 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
318 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
311 'Commit the changes and store useful information in extra'
319 'Commit the changes and store useful information in extra'
312 try:
320 try:
313 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
321 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
314 ctx = repo[rev]
322 ctx = repo[rev]
315 if commitmsg is None:
323 if commitmsg is None:
316 commitmsg = ctx.description()
324 commitmsg = ctx.description()
317 extra = {'rebase_source': ctx.hex()}
325 extra = {'rebase_source': ctx.hex()}
318 if extrafn:
326 if extrafn:
319 extrafn(ctx, extra)
327 extrafn(ctx, extra)
320 # Commit might fail if unresolved files exist
328 # Commit might fail if unresolved files exist
321 newrev = repo.commit(text=commitmsg, user=ctx.user(),
329 newrev = repo.commit(text=commitmsg, user=ctx.user(),
322 date=ctx.date(), extra=extra)
330 date=ctx.date(), extra=extra)
323 repo.dirstate.setbranch(repo[newrev].branch())
331 repo.dirstate.setbranch(repo[newrev].branch())
324 return newrev
332 return newrev
325 except util.Abort:
333 except util.Abort:
326 # Invalidate the previous setparents
334 # Invalidate the previous setparents
327 repo.dirstate.invalidate()
335 repo.dirstate.invalidate()
328 raise
336 raise
329
337
330 def rebasenode(repo, rev, p1, state):
338 def rebasenode(repo, rev, p1, state):
331 'Rebase a single revision'
339 'Rebase a single revision'
332 # Merge phase
340 # Merge phase
333 # Update to target and merge it with local
341 # Update to target and merge it with local
334 if repo['.'].rev() != repo[p1].rev():
342 if repo['.'].rev() != repo[p1].rev():
335 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
343 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
336 merge.update(repo, p1, False, True, False)
344 merge.update(repo, p1, False, True, False)
337 else:
345 else:
338 repo.ui.debug(" already in target\n")
346 repo.ui.debug(" already in target\n")
339 repo.dirstate.write()
347 repo.dirstate.write()
340 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
348 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
341 base = None
349 base = None
342 if repo[rev].rev() != repo[min(state)].rev():
350 if repo[rev].rev() != repo[min(state)].rev():
343 base = repo[rev].p1().node()
351 base = repo[rev].p1().node()
344 return merge.update(repo, rev, True, True, False, base)
352 return merge.update(repo, rev, True, True, False, base)
345
353
346 def defineparents(repo, rev, target, state, targetancestors):
354 def defineparents(repo, rev, target, state, targetancestors):
347 'Return the new parent relationship of the revision that will be rebased'
355 'Return the new parent relationship of the revision that will be rebased'
348 parents = repo[rev].parents()
356 parents = repo[rev].parents()
349 p1 = p2 = nullrev
357 p1 = p2 = nullrev
350
358
351 P1n = parents[0].rev()
359 P1n = parents[0].rev()
352 if P1n in targetancestors:
360 if P1n in targetancestors:
353 p1 = target
361 p1 = target
354 elif P1n in state:
362 elif P1n in state:
355 if state[P1n] == nullmerge:
363 if state[P1n] == nullmerge:
356 p1 = target
364 p1 = target
357 else:
365 else:
358 p1 = state[P1n]
366 p1 = state[P1n]
359 else: # P1n external
367 else: # P1n external
360 p1 = target
368 p1 = target
361 p2 = P1n
369 p2 = P1n
362
370
363 if len(parents) == 2 and parents[1].rev() not in targetancestors:
371 if len(parents) == 2 and parents[1].rev() not in targetancestors:
364 P2n = parents[1].rev()
372 P2n = parents[1].rev()
365 # interesting second parent
373 # interesting second parent
366 if P2n in state:
374 if P2n in state:
367 if p1 == target: # P1n in targetancestors or external
375 if p1 == target: # P1n in targetancestors or external
368 p1 = state[P2n]
376 p1 = state[P2n]
369 else:
377 else:
370 p2 = state[P2n]
378 p2 = state[P2n]
371 else: # P2n external
379 else: # P2n external
372 if p2 != nullrev: # P1n external too => rev is a merged revision
380 if p2 != nullrev: # P1n external too => rev is a merged revision
373 raise util.Abort(_('cannot use revision %d as base, result '
381 raise util.Abort(_('cannot use revision %d as base, result '
374 'would have 3 parents') % rev)
382 'would have 3 parents') % rev)
375 p2 = P2n
383 p2 = P2n
376 repo.ui.debug(" future parents are %d and %d\n" %
384 repo.ui.debug(" future parents are %d and %d\n" %
377 (repo[p1].rev(), repo[p2].rev()))
385 (repo[p1].rev(), repo[p2].rev()))
378 return p1, p2
386 return p1, p2
379
387
380 def isagitpatch(repo, patchname):
388 def isagitpatch(repo, patchname):
381 'Return true if the given patch is in git format'
389 'Return true if the given patch is in git format'
382 mqpatch = os.path.join(repo.mq.path, patchname)
390 mqpatch = os.path.join(repo.mq.path, patchname)
383 for line in patch.linereader(file(mqpatch, 'rb')):
391 for line in patch.linereader(file(mqpatch, 'rb')):
384 if line.startswith('diff --git'):
392 if line.startswith('diff --git'):
385 return True
393 return True
386 return False
394 return False
387
395
388 def updatemq(repo, state, skipped, **opts):
396 def updatemq(repo, state, skipped, **opts):
389 'Update rebased mq patches - finalize and then import them'
397 'Update rebased mq patches - finalize and then import them'
390 mqrebase = {}
398 mqrebase = {}
391 mq = repo.mq
399 mq = repo.mq
392 original_series = mq.fullseries[:]
400 original_series = mq.fullseries[:]
393
401
394 for p in mq.applied:
402 for p in mq.applied:
395 rev = repo[p.node].rev()
403 rev = repo[p.node].rev()
396 if rev in state:
404 if rev in state:
397 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
405 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
398 (rev, p.name))
406 (rev, p.name))
399 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
407 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
400
408
401 if mqrebase:
409 if mqrebase:
402 mq.finish(repo, mqrebase.keys())
410 mq.finish(repo, mqrebase.keys())
403
411
404 # We must start import from the newest revision
412 # We must start import from the newest revision
405 for rev in sorted(mqrebase, reverse=True):
413 for rev in sorted(mqrebase, reverse=True):
406 if rev not in skipped:
414 if rev not in skipped:
407 name, isgit = mqrebase[rev]
415 name, isgit = mqrebase[rev]
408 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
416 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
409 mq.qimport(repo, (), patchname=name, git=isgit,
417 mq.qimport(repo, (), patchname=name, git=isgit,
410 rev=[str(state[rev])])
418 rev=[str(state[rev])])
411
419
412 # restore old series to preserve guards
420 # restore old series to preserve guards
413 mq.fullseries = original_series
421 mq.fullseries = original_series
414 mq.series_dirty = True
422 mq.series_dirty = True
415 mq.savedirty()
423 mq.savedirty()
416
424
417 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
425 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
418 'Move bookmarks to their correct changesets'
426 'Move bookmarks to their correct changesets'
419 current = repo._bookmarkcurrent
427 current = repo._bookmarkcurrent
420 for k, v in originalbookmarks.iteritems():
428 for k, v in originalbookmarks.iteritems():
421 if v in nstate:
429 if v in nstate:
422 if nstate[v] != nullmerge:
430 if nstate[v] != nullmerge:
423 # reset the pointer if the bookmark was moved incorrectly
431 # reset the pointer if the bookmark was moved incorrectly
424 if k != current:
432 if k != current:
425 repo._bookmarks[k] = nstate[v]
433 repo._bookmarks[k] = nstate[v]
426
434
427 bookmarks.write(repo)
435 bookmarks.write(repo)
428
436
429 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
437 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
430 external):
438 external):
431 'Store the current status to allow recovery'
439 'Store the current status to allow recovery'
432 f = repo.opener("rebasestate", "w")
440 f = repo.opener("rebasestate", "w")
433 f.write(repo[originalwd].hex() + '\n')
441 f.write(repo[originalwd].hex() + '\n')
434 f.write(repo[target].hex() + '\n')
442 f.write(repo[target].hex() + '\n')
435 f.write(repo[external].hex() + '\n')
443 f.write(repo[external].hex() + '\n')
436 f.write('%d\n' % int(collapse))
444 f.write('%d\n' % int(collapse))
437 f.write('%d\n' % int(keep))
445 f.write('%d\n' % int(keep))
438 f.write('%d\n' % int(keepbranches))
446 f.write('%d\n' % int(keepbranches))
439 for d, v in state.iteritems():
447 for d, v in state.iteritems():
440 oldrev = repo[d].hex()
448 oldrev = repo[d].hex()
441 newrev = repo[v].hex()
449 newrev = repo[v].hex()
442 f.write("%s:%s\n" % (oldrev, newrev))
450 f.write("%s:%s\n" % (oldrev, newrev))
443 f.close()
451 f.close()
444 repo.ui.debug('rebase status stored\n')
452 repo.ui.debug('rebase status stored\n')
445
453
446 def clearstatus(repo):
454 def clearstatus(repo):
447 'Remove the status files'
455 'Remove the status files'
448 if os.path.exists(repo.join("rebasestate")):
456 if os.path.exists(repo.join("rebasestate")):
449 util.unlinkpath(repo.join("rebasestate"))
457 util.unlinkpath(repo.join("rebasestate"))
450
458
451 def restorestatus(repo):
459 def restorestatus(repo):
452 'Restore a previously stored status'
460 'Restore a previously stored status'
453 try:
461 try:
454 target = None
462 target = None
455 collapse = False
463 collapse = False
456 external = nullrev
464 external = nullrev
457 state = {}
465 state = {}
458 f = repo.opener("rebasestate")
466 f = repo.opener("rebasestate")
459 for i, l in enumerate(f.read().splitlines()):
467 for i, l in enumerate(f.read().splitlines()):
460 if i == 0:
468 if i == 0:
461 originalwd = repo[l].rev()
469 originalwd = repo[l].rev()
462 elif i == 1:
470 elif i == 1:
463 target = repo[l].rev()
471 target = repo[l].rev()
464 elif i == 2:
472 elif i == 2:
465 external = repo[l].rev()
473 external = repo[l].rev()
466 elif i == 3:
474 elif i == 3:
467 collapse = bool(int(l))
475 collapse = bool(int(l))
468 elif i == 4:
476 elif i == 4:
469 keep = bool(int(l))
477 keep = bool(int(l))
470 elif i == 5:
478 elif i == 5:
471 keepbranches = bool(int(l))
479 keepbranches = bool(int(l))
472 else:
480 else:
473 oldrev, newrev = l.split(':')
481 oldrev, newrev = l.split(':')
474 state[repo[oldrev].rev()] = repo[newrev].rev()
482 state[repo[oldrev].rev()] = repo[newrev].rev()
475 skipped = set()
483 skipped = set()
476 # recompute the set of skipped revs
484 # recompute the set of skipped revs
477 if not collapse:
485 if not collapse:
478 seen = set([target])
486 seen = set([target])
479 for old, new in sorted(state.items()):
487 for old, new in sorted(state.items()):
480 if new != nullrev and new in seen:
488 if new != nullrev and new in seen:
481 skipped.add(old)
489 skipped.add(old)
482 seen.add(new)
490 seen.add(new)
483 repo.ui.debug('computed skipped revs: %s\n' % skipped)
491 repo.ui.debug('computed skipped revs: %s\n' % skipped)
484 repo.ui.debug('rebase status resumed\n')
492 repo.ui.debug('rebase status resumed\n')
485 return (originalwd, target, state, skipped,
493 return (originalwd, target, state, skipped,
486 collapse, keep, keepbranches, external)
494 collapse, keep, keepbranches, external)
487 except IOError, err:
495 except IOError, err:
488 if err.errno != errno.ENOENT:
496 if err.errno != errno.ENOENT:
489 raise
497 raise
490 raise util.Abort(_('no rebase in progress'))
498 raise util.Abort(_('no rebase in progress'))
491
499
492 def abort(repo, originalwd, target, state):
500 def abort(repo, originalwd, target, state):
493 'Restore the repository to its original state'
501 'Restore the repository to its original state'
494 if set(repo.changelog.descendants(target)) - set(state.values()):
502 if set(repo.changelog.descendants(target)) - set(state.values()):
495 repo.ui.warn(_("warning: new changesets detected on target branch, "
503 repo.ui.warn(_("warning: new changesets detected on target branch, "
496 "can't abort\n"))
504 "can't abort\n"))
497 return -1
505 return -1
498 else:
506 else:
499 # Strip from the first rebased revision
507 # Strip from the first rebased revision
500 merge.update(repo, repo[originalwd].rev(), False, True, False)
508 merge.update(repo, repo[originalwd].rev(), False, True, False)
501 rebased = filter(lambda x: x > -1 and x != target, state.values())
509 rebased = filter(lambda x: x > -1 and x != target, state.values())
502 if rebased:
510 if rebased:
503 strippoint = min(rebased)
511 strippoint = min(rebased)
504 # no backup of rebased cset versions needed
512 # no backup of rebased cset versions needed
505 repair.strip(repo.ui, repo, repo[strippoint].node())
513 repair.strip(repo.ui, repo, repo[strippoint].node())
506 clearstatus(repo)
514 clearstatus(repo)
507 repo.ui.warn(_('rebase aborted\n'))
515 repo.ui.warn(_('rebase aborted\n'))
508 return 0
516 return 0
509
517
510 def buildstate(repo, dest, src, base, detach):
518 def buildstate(repo, dest, src, base, detach):
511 'Define which revisions are going to be rebased and where'
519 'Define which revisions are going to be rebased and where'
512 targetancestors = set()
520 targetancestors = set()
513 detachset = set()
521 detachset = set()
514
522
515 if not dest:
523 if not dest:
516 # Destination defaults to the latest revision in the current branch
524 # Destination defaults to the latest revision in the current branch
517 branch = repo[None].branch()
525 branch = repo[None].branch()
518 dest = repo[branch].rev()
526 dest = repo[branch].rev()
519 else:
527 else:
520 dest = repo[dest].rev()
528 dest = repo[dest].rev()
521
529
522 # This check isn't strictly necessary, since mq detects commits over an
530 # This check isn't strictly necessary, since mq detects commits over an
523 # applied patch. But it prevents messing up the working directory when
531 # applied patch. But it prevents messing up the working directory when
524 # a partially completed rebase is blocked by mq.
532 # a partially completed rebase is blocked by mq.
525 if 'qtip' in repo.tags() and (repo[dest].node() in
533 if 'qtip' in repo.tags() and (repo[dest].node() in
526 [s.node for s in repo.mq.applied]):
534 [s.node for s in repo.mq.applied]):
527 raise util.Abort(_('cannot rebase onto an applied mq patch'))
535 raise util.Abort(_('cannot rebase onto an applied mq patch'))
528
536
529 if src:
537 if src:
530 commonbase = repo[src].ancestor(repo[dest])
538 commonbase = repo[src].ancestor(repo[dest])
531 samebranch = repo[src].branch() == repo[dest].branch()
539 samebranch = repo[src].branch() == repo[dest].branch()
532 if commonbase == repo[src]:
540 if commonbase == repo[src]:
533 raise util.Abort(_('source is ancestor of destination'))
541 raise util.Abort(_('source is ancestor of destination'))
534 if samebranch and commonbase == repo[dest]:
542 if samebranch and commonbase == repo[dest]:
535 raise util.Abort(_('source is descendant of destination'))
543 raise util.Abort(_('source is descendant of destination'))
536 source = repo[src].rev()
544 source = repo[src].rev()
537 if detach:
545 if detach:
538 # We need to keep track of source's ancestors up to the common base
546 # We need to keep track of source's ancestors up to the common base
539 srcancestors = set(repo.changelog.ancestors(source))
547 srcancestors = set(repo.changelog.ancestors(source))
540 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
548 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
541 detachset = srcancestors - baseancestors
549 detachset = srcancestors - baseancestors
542 detachset.discard(commonbase.rev())
550 detachset.discard(commonbase.rev())
543 else:
551 else:
544 if base:
552 if base:
545 cwd = repo[base].rev()
553 cwd = repo[base].rev()
546 else:
554 else:
547 cwd = repo['.'].rev()
555 cwd = repo['.'].rev()
548
556
549 if cwd == dest:
557 if cwd == dest:
550 repo.ui.debug('source and destination are the same\n')
558 repo.ui.debug('source and destination are the same\n')
551 return None
559 return None
552
560
553 targetancestors = set(repo.changelog.ancestors(dest))
561 targetancestors = set(repo.changelog.ancestors(dest))
554 if cwd in targetancestors:
562 if cwd in targetancestors:
555 repo.ui.debug('source is ancestor of destination\n')
563 repo.ui.debug('source is ancestor of destination\n')
556 return None
564 return None
557
565
558 cwdancestors = set(repo.changelog.ancestors(cwd))
566 cwdancestors = set(repo.changelog.ancestors(cwd))
559 if dest in cwdancestors:
567 if dest in cwdancestors:
560 repo.ui.debug('source is descendant of destination\n')
568 repo.ui.debug('source is descendant of destination\n')
561 return None
569 return None
562
570
563 cwdancestors.add(cwd)
571 cwdancestors.add(cwd)
564 rebasingbranch = cwdancestors - targetancestors
572 rebasingbranch = cwdancestors - targetancestors
565 source = min(rebasingbranch)
573 source = min(rebasingbranch)
566
574
567 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
575 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
568 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
576 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
569 state.update(dict.fromkeys(detachset, nullmerge))
577 state.update(dict.fromkeys(detachset, nullmerge))
570 state[source] = nullrev
578 state[source] = nullrev
571 return repo['.'].rev(), repo[dest].rev(), state
579 return repo['.'].rev(), repo[dest].rev(), state
572
580
573 def pullrebase(orig, ui, repo, *args, **opts):
581 def pullrebase(orig, ui, repo, *args, **opts):
574 'Call rebase after pull if the latter has been invoked with --rebase'
582 'Call rebase after pull if the latter has been invoked with --rebase'
575 if opts.get('rebase'):
583 if opts.get('rebase'):
576 if opts.get('update'):
584 if opts.get('update'):
577 del opts['update']
585 del opts['update']
578 ui.debug('--update and --rebase are not compatible, ignoring '
586 ui.debug('--update and --rebase are not compatible, ignoring '
579 'the update flag\n')
587 'the update flag\n')
580
588
581 cmdutil.bailifchanged(repo)
589 cmdutil.bailifchanged(repo)
582 revsprepull = len(repo)
590 revsprepull = len(repo)
583 origpostincoming = commands.postincoming
591 origpostincoming = commands.postincoming
584 def _dummy(*args, **kwargs):
592 def _dummy(*args, **kwargs):
585 pass
593 pass
586 commands.postincoming = _dummy
594 commands.postincoming = _dummy
587 try:
595 try:
588 orig(ui, repo, *args, **opts)
596 orig(ui, repo, *args, **opts)
589 finally:
597 finally:
590 commands.postincoming = origpostincoming
598 commands.postincoming = origpostincoming
591 revspostpull = len(repo)
599 revspostpull = len(repo)
592 if revspostpull > revsprepull:
600 if revspostpull > revsprepull:
593 rebase(ui, repo, **opts)
601 rebase(ui, repo, **opts)
594 branch = repo[None].branch()
602 branch = repo[None].branch()
595 dest = repo[branch].rev()
603 dest = repo[branch].rev()
596 if dest != repo['.'].rev():
604 if dest != repo['.'].rev():
597 # there was nothing to rebase we force an update
605 # there was nothing to rebase we force an update
598 hg.update(repo, dest)
606 hg.update(repo, dest)
599 else:
607 else:
600 if opts.get('tool'):
608 if opts.get('tool'):
601 raise util.Abort(_('--tool can only be used with --rebase'))
609 raise util.Abort(_('--tool can only be used with --rebase'))
602 orig(ui, repo, *args, **opts)
610 orig(ui, repo, *args, **opts)
603
611
604 def uisetup(ui):
612 def uisetup(ui):
605 'Replace pull with a decorator to provide --rebase option'
613 'Replace pull with a decorator to provide --rebase option'
606 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
614 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
607 entry[1].append(('', 'rebase', None,
615 entry[1].append(('', 'rebase', None,
608 _("rebase working directory to branch head")))
616 _("rebase working directory to branch head")))
609 entry[1].append(('t', 'tool', '',
617 entry[1].append(('t', 'tool', '',
610 _("specify merge tool for rebase")))
618 _("specify merge tool for rebase")))
@@ -1,444 +1,482 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > graphlog=
3 > graphlog=
4 > rebase=
4 > rebase=
5 >
5 >
6 > [alias]
6 > [alias]
7 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
7 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
8 > EOF
8 > EOF
9
9
10 Create repo a:
10 Create repo a:
11
11
12 $ hg init a
12 $ hg init a
13 $ cd a
13 $ cd a
14 $ hg unbundle $TESTDIR/bundles/rebase.hg
14 $ hg unbundle $TESTDIR/bundles/rebase.hg
15 adding changesets
15 adding changesets
16 adding manifests
16 adding manifests
17 adding file changes
17 adding file changes
18 added 8 changesets with 7 changes to 7 files (+2 heads)
18 added 8 changesets with 7 changes to 7 files (+2 heads)
19 (run 'hg heads' to see heads, 'hg merge' to merge)
19 (run 'hg heads' to see heads, 'hg merge' to merge)
20 $ hg up tip
20 $ hg up tip
21 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
21 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
22
22
23 $ hg tglog
23 $ hg tglog
24 @ 7: 'H'
24 @ 7: 'H'
25 |
25 |
26 | o 6: 'G'
26 | o 6: 'G'
27 |/|
27 |/|
28 o | 5: 'F'
28 o | 5: 'F'
29 | |
29 | |
30 | o 4: 'E'
30 | o 4: 'E'
31 |/
31 |/
32 | o 3: 'D'
32 | o 3: 'D'
33 | |
33 | |
34 | o 2: 'C'
34 | o 2: 'C'
35 | |
35 | |
36 | o 1: 'B'
36 | o 1: 'B'
37 |/
37 |/
38 o 0: 'A'
38 o 0: 'A'
39
39
40 $ cd ..
40 $ cd ..
41
41
42
42
43 Rebasing B onto H:
43 Rebasing B onto H:
44
44
45 $ hg clone -q -u 3 a a1
45 $ hg clone -q -u 3 a a1
46 $ cd a1
46 $ cd a1
47
47
48 $ hg rebase --collapse --keepbranches
48 $ hg rebase --collapse --keepbranches
49 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
49 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
50
50
51 $ hg tglog
51 $ hg tglog
52 @ 5: 'Collapsed revision
52 @ 5: 'Collapsed revision
53 | * B
53 | * B
54 | * C
54 | * C
55 | * D'
55 | * D'
56 o 4: 'H'
56 o 4: 'H'
57 |
57 |
58 | o 3: 'G'
58 | o 3: 'G'
59 |/|
59 |/|
60 o | 2: 'F'
60 o | 2: 'F'
61 | |
61 | |
62 | o 1: 'E'
62 | o 1: 'E'
63 |/
63 |/
64 o 0: 'A'
64 o 0: 'A'
65
65
66 $ hg manifest
66 $ hg manifest
67 A
67 A
68 B
68 B
69 C
69 C
70 D
70 D
71 F
71 F
72 H
72 H
73
73
74 $ cd ..
74 $ cd ..
75
75
76
76
77 Rebasing G onto H:
77 Rebasing G onto H:
78
78
79 $ hg clone -q -u . a a2
79 $ hg clone -q -u . a a2
80 $ cd a2
80 $ cd a2
81
81
82 $ hg rebase --base 6 --collapse
82 $ hg rebase --base 6 --collapse
83 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
83 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
84
84
85 $ hg tglog
85 $ hg tglog
86 @ 6: 'Collapsed revision
86 @ 6: 'Collapsed revision
87 | * E
87 | * E
88 | * G'
88 | * G'
89 o 5: 'H'
89 o 5: 'H'
90 |
90 |
91 o 4: 'F'
91 o 4: 'F'
92 |
92 |
93 | o 3: 'D'
93 | o 3: 'D'
94 | |
94 | |
95 | o 2: 'C'
95 | o 2: 'C'
96 | |
96 | |
97 | o 1: 'B'
97 | o 1: 'B'
98 |/
98 |/
99 o 0: 'A'
99 o 0: 'A'
100
100
101 $ hg manifest
101 $ hg manifest
102 A
102 A
103 E
103 E
104 F
104 F
105 H
105 H
106
106
107 $ cd ..
107 $ cd ..
108
108
109 Rebasing G onto H with custom message:
109 Rebasing G onto H with custom message:
110
110
111 $ hg clone -q -u . a a3
111 $ hg clone -q -u . a a3
112 $ cd a3
112 $ cd a3
113
113
114 $ hg rebase --base 6 -m 'custom message'
114 $ hg rebase --base 6 -m 'custom message'
115 abort: message can only be specified with collapse
115 abort: message can only be specified with collapse
116 [255]
116 [255]
117
117
118 $ hg rebase --base 6 --collapse -m 'custom message'
118 $ hg rebase --base 6 --collapse -m 'custom message'
119 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
119 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
120
120
121 $ hg tglog
121 $ hg tglog
122 @ 6: 'custom message'
122 @ 6: 'custom message'
123 |
123 |
124 o 5: 'H'
124 o 5: 'H'
125 |
125 |
126 o 4: 'F'
126 o 4: 'F'
127 |
127 |
128 | o 3: 'D'
128 | o 3: 'D'
129 | |
129 | |
130 | o 2: 'C'
130 | o 2: 'C'
131 | |
131 | |
132 | o 1: 'B'
132 | o 1: 'B'
133 |/
133 |/
134 o 0: 'A'
134 o 0: 'A'
135
135
136 $ hg manifest
136 $ hg manifest
137 A
137 A
138 E
138 E
139 F
139 F
140 H
140 H
141
141
142 $ cd ..
142 $ cd ..
143
143
144 Create repo b:
144 Create repo b:
145
145
146 $ hg init b
146 $ hg init b
147 $ cd b
147 $ cd b
148
148
149 $ echo A > A
149 $ echo A > A
150 $ hg ci -Am A
150 $ hg ci -Am A
151 adding A
151 adding A
152 $ echo B > B
152 $ echo B > B
153 $ hg ci -Am B
153 $ hg ci -Am B
154 adding B
154 adding B
155
155
156 $ hg up -q 0
156 $ hg up -q 0
157
157
158 $ echo C > C
158 $ echo C > C
159 $ hg ci -Am C
159 $ hg ci -Am C
160 adding C
160 adding C
161 created new head
161 created new head
162
162
163 $ hg merge
163 $ hg merge
164 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
165 (branch merge, don't forget to commit)
165 (branch merge, don't forget to commit)
166
166
167 $ echo D > D
167 $ echo D > D
168 $ hg ci -Am D
168 $ hg ci -Am D
169 adding D
169 adding D
170
170
171 $ hg up -q 1
171 $ hg up -q 1
172
172
173 $ echo E > E
173 $ echo E > E
174 $ hg ci -Am E
174 $ hg ci -Am E
175 adding E
175 adding E
176 created new head
176 created new head
177
177
178 $ echo F > F
178 $ echo F > F
179 $ hg ci -Am F
179 $ hg ci -Am F
180 adding F
180 adding F
181
181
182 $ hg merge
182 $ hg merge
183 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
183 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
184 (branch merge, don't forget to commit)
184 (branch merge, don't forget to commit)
185 $ hg ci -m G
185 $ hg ci -m G
186
186
187 $ hg up -q 0
187 $ hg up -q 0
188
188
189 $ echo H > H
189 $ echo H > H
190 $ hg ci -Am H
190 $ hg ci -Am H
191 adding H
191 adding H
192 created new head
192 created new head
193
193
194 $ hg tglog
194 $ hg tglog
195 @ 7: 'H'
195 @ 7: 'H'
196 |
196 |
197 | o 6: 'G'
197 | o 6: 'G'
198 | |\
198 | |\
199 | | o 5: 'F'
199 | | o 5: 'F'
200 | | |
200 | | |
201 | | o 4: 'E'
201 | | o 4: 'E'
202 | | |
202 | | |
203 | o | 3: 'D'
203 | o | 3: 'D'
204 | |\|
204 | |\|
205 | o | 2: 'C'
205 | o | 2: 'C'
206 |/ /
206 |/ /
207 | o 1: 'B'
207 | o 1: 'B'
208 |/
208 |/
209 o 0: 'A'
209 o 0: 'A'
210
210
211 $ cd ..
211 $ cd ..
212
212
213
213
214 Rebase and collapse - more than one external (fail):
214 Rebase and collapse - more than one external (fail):
215
215
216 $ hg clone -q -u . b b1
216 $ hg clone -q -u . b b1
217 $ cd b1
217 $ cd b1
218
218
219 $ hg rebase -s 2 --collapse
219 $ hg rebase -s 2 --collapse
220 abort: unable to collapse, there is more than one external parent
220 abort: unable to collapse, there is more than one external parent
221 [255]
221 [255]
222
222
223 Rebase and collapse - E onto H:
223 Rebase and collapse - E onto H:
224
224
225 $ hg rebase -s 4 --collapse
225 $ hg rebase -s 4 --collapse
226 saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob)
226 saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob)
227
227
228 $ hg tglog
228 $ hg tglog
229 @ 5: 'Collapsed revision
229 @ 5: 'Collapsed revision
230 |\ * E
230 |\ * E
231 | | * F
231 | | * F
232 | | * G'
232 | | * G'
233 | o 4: 'H'
233 | o 4: 'H'
234 | |
234 | |
235 o | 3: 'D'
235 o | 3: 'D'
236 |\ \
236 |\ \
237 | o | 2: 'C'
237 | o | 2: 'C'
238 | |/
238 | |/
239 o / 1: 'B'
239 o / 1: 'B'
240 |/
240 |/
241 o 0: 'A'
241 o 0: 'A'
242
242
243 $ hg manifest
243 $ hg manifest
244 A
244 A
245 B
245 B
246 C
246 C
247 D
247 D
248 E
248 E
249 F
249 F
250 H
250 H
251
251
252 $ cd ..
252 $ cd ..
253
253
254
254
255 Create repo c:
255 Create repo c:
256
256
257 $ hg init c
257 $ hg init c
258 $ cd c
258 $ cd c
259
259
260 $ echo A > A
260 $ echo A > A
261 $ hg ci -Am A
261 $ hg ci -Am A
262 adding A
262 adding A
263 $ echo B > B
263 $ echo B > B
264 $ hg ci -Am B
264 $ hg ci -Am B
265 adding B
265 adding B
266
266
267 $ hg up -q 0
267 $ hg up -q 0
268
268
269 $ echo C > C
269 $ echo C > C
270 $ hg ci -Am C
270 $ hg ci -Am C
271 adding C
271 adding C
272 created new head
272 created new head
273
273
274 $ hg merge
274 $ hg merge
275 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
275 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
276 (branch merge, don't forget to commit)
276 (branch merge, don't forget to commit)
277
277
278 $ echo D > D
278 $ echo D > D
279 $ hg ci -Am D
279 $ hg ci -Am D
280 adding D
280 adding D
281
281
282 $ hg up -q 1
282 $ hg up -q 1
283
283
284 $ echo E > E
284 $ echo E > E
285 $ hg ci -Am E
285 $ hg ci -Am E
286 adding E
286 adding E
287 created new head
287 created new head
288 $ echo F > E
288 $ echo F > E
289 $ hg ci -m 'F'
289 $ hg ci -m 'F'
290
290
291 $ echo G > G
291 $ echo G > G
292 $ hg ci -Am G
292 $ hg ci -Am G
293 adding G
293 adding G
294
294
295 $ hg merge
295 $ hg merge
296 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
297 (branch merge, don't forget to commit)
297 (branch merge, don't forget to commit)
298
298
299 $ hg ci -m H
299 $ hg ci -m H
300
300
301 $ hg up -q 0
301 $ hg up -q 0
302
302
303 $ echo I > I
303 $ echo I > I
304 $ hg ci -Am I
304 $ hg ci -Am I
305 adding I
305 adding I
306 created new head
306 created new head
307
307
308 $ hg tglog
308 $ hg tglog
309 @ 8: 'I'
309 @ 8: 'I'
310 |
310 |
311 | o 7: 'H'
311 | o 7: 'H'
312 | |\
312 | |\
313 | | o 6: 'G'
313 | | o 6: 'G'
314 | | |
314 | | |
315 | | o 5: 'F'
315 | | o 5: 'F'
316 | | |
316 | | |
317 | | o 4: 'E'
317 | | o 4: 'E'
318 | | |
318 | | |
319 | o | 3: 'D'
319 | o | 3: 'D'
320 | |\|
320 | |\|
321 | o | 2: 'C'
321 | o | 2: 'C'
322 |/ /
322 |/ /
323 | o 1: 'B'
323 | o 1: 'B'
324 |/
324 |/
325 o 0: 'A'
325 o 0: 'A'
326
326
327 $ cd ..
327 $ cd ..
328
328
329
329
330 Rebase and collapse - E onto I:
330 Rebase and collapse - E onto I:
331
331
332 $ hg clone -q -u . c c1
332 $ hg clone -q -u . c c1
333 $ cd c1
333 $ cd c1
334
334
335 $ hg rebase -s 4 --collapse
335 $ hg rebase -s 4 --collapse
336 merging E
336 merging E
337 saved backup bundle to $TESTTMP/c1/.hg/strip-backup/*-backup.hg (glob)
337 saved backup bundle to $TESTTMP/c1/.hg/strip-backup/*-backup.hg (glob)
338
338
339 $ hg tglog
339 $ hg tglog
340 @ 5: 'Collapsed revision
340 @ 5: 'Collapsed revision
341 |\ * E
341 |\ * E
342 | | * F
342 | | * F
343 | | * G
343 | | * G
344 | | * H'
344 | | * H'
345 | o 4: 'I'
345 | o 4: 'I'
346 | |
346 | |
347 o | 3: 'D'
347 o | 3: 'D'
348 |\ \
348 |\ \
349 | o | 2: 'C'
349 | o | 2: 'C'
350 | |/
350 | |/
351 o / 1: 'B'
351 o / 1: 'B'
352 |/
352 |/
353 o 0: 'A'
353 o 0: 'A'
354
354
355 $ hg manifest
355 $ hg manifest
356 A
356 A
357 B
357 B
358 C
358 C
359 D
359 D
360 E
360 E
361 G
361 G
362 I
362 I
363
363
364 $ cat E
364 $ cat E
365 F
365 F
366
366
367 $ cd ..
367 $ cd ..
368
368
369
369
370 Create repo d:
370 Create repo d:
371
371
372 $ hg init d
372 $ hg init d
373 $ cd d
373 $ cd d
374
374
375 $ echo A > A
375 $ echo A > A
376 $ hg ci -Am A
376 $ hg ci -Am A
377 adding A
377 adding A
378 $ echo B > B
378 $ echo B > B
379 $ hg ci -Am B
379 $ hg ci -Am B
380 adding B
380 adding B
381 $ echo C > C
381 $ echo C > C
382 $ hg ci -Am C
382 $ hg ci -Am C
383 adding C
383 adding C
384
384
385 $ hg up -q 1
385 $ hg up -q 1
386
386
387 $ echo D > D
387 $ echo D > D
388 $ hg ci -Am D
388 $ hg ci -Am D
389 adding D
389 adding D
390 created new head
390 created new head
391 $ hg merge
391 $ hg merge
392 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
392 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 (branch merge, don't forget to commit)
393 (branch merge, don't forget to commit)
394
394
395 $ hg ci -m E
395 $ hg ci -m E
396
396
397 $ hg up -q 0
397 $ hg up -q 0
398
398
399 $ echo F > F
399 $ echo F > F
400 $ hg ci -Am F
400 $ hg ci -Am F
401 adding F
401 adding F
402 created new head
402 created new head
403
403
404 $ hg tglog
404 $ hg tglog
405 @ 5: 'F'
405 @ 5: 'F'
406 |
406 |
407 | o 4: 'E'
407 | o 4: 'E'
408 | |\
408 | |\
409 | | o 3: 'D'
409 | | o 3: 'D'
410 | | |
410 | | |
411 | o | 2: 'C'
411 | o | 2: 'C'
412 | |/
412 | |/
413 | o 1: 'B'
413 | o 1: 'B'
414 |/
414 |/
415 o 0: 'A'
415 o 0: 'A'
416
416
417 $ cd ..
417 $ cd ..
418
418
419
419
420 Rebase and collapse - B onto F:
420 Rebase and collapse - B onto F:
421
421
422 $ hg clone -q -u . d d1
422 $ hg clone -q -u . d d1
423 $ cd d1
423 $ cd d1
424
424
425 $ hg rebase -s 1 --collapse
425 $ hg rebase -s 1 --collapse
426 saved backup bundle to $TESTTMP/d1/.hg/strip-backup/*-backup.hg (glob)
426 saved backup bundle to $TESTTMP/d1/.hg/strip-backup/*-backup.hg (glob)
427
427
428 $ hg tglog
428 $ hg tglog
429 @ 2: 'Collapsed revision
429 @ 2: 'Collapsed revision
430 | * B
430 | * B
431 | * C
431 | * C
432 | * D
432 | * D
433 | * E'
433 | * E'
434 o 1: 'F'
434 o 1: 'F'
435 |
435 |
436 o 0: 'A'
436 o 0: 'A'
437
437
438 $ hg manifest
438 $ hg manifest
439 A
439 A
440 B
440 B
441 C
441 C
442 D
442 D
443 F
443 F
444
444
445 Interactions between collapse and keepbranches
446 $ cd ..
447 $ hg init e
448 $ cd e
449 $ echo 'a' > a
450 $ hg ci -Am 'A'
451 adding a
452
453 $ hg branch '1'
454 marked working directory as branch 1
455 $ echo 'b' > b
456 $ hg ci -Am 'B'
457 adding b
458
459 $ hg branch '2'
460 marked working directory as branch 2
461 $ echo 'c' > c
462 $ hg ci -Am 'C'
463 adding c
464
465 $ hg up -q 0
466 $ echo 'd' > d
467 $ hg ci -Am 'D'
468 adding d
469
470 $ hg tglog
471 @ 3: 'D'
472 |
473 | o 2: 'C' 2
474 | |
475 | o 1: 'B' 1
476 |/
477 o 0: 'A'
478
479 $ hg rebase --keepbranches --collapse -s 1 -d 3
480 abort: cannot collapse multiple named branches
481 [255]
482
General Comments 0
You need to be logged in to leave comments. Login now