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