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