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