##// END OF EJS Templates
Merge with crew-stable
Patrick Mezard -
r8270:3477ad0b merge default
parent child Browse files
Show More
@@ -1,469 +1,470 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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''move sets of revisions to a different ancestor
8 '''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://www.selenic.com/mercurial/wiki/index.cgi/RebaseProject
14 http://www.selenic.com/mercurial/wiki/index.cgi/RebaseProject
15 '''
15 '''
16
16
17 from mercurial import util, repair, merge, cmdutil, commands, error
17 from mercurial import util, repair, merge, cmdutil, commands, error
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 def rebasemerge(repo, rev, first=False):
25 def rebasemerge(repo, rev, first=False):
26 'return the correct ancestor'
26 'return the correct ancestor'
27 oldancestor = ancestor.ancestor
27 oldancestor = ancestor.ancestor
28
28
29 def newancestor(a, b, pfunc):
29 def newancestor(a, b, pfunc):
30 ancestor.ancestor = oldancestor
30 ancestor.ancestor = oldancestor
31 if b == rev:
31 if b == rev:
32 return repo[rev].parents()[0].rev()
32 return repo[rev].parents()[0].rev()
33 return ancestor.ancestor(a, b, pfunc)
33 return ancestor.ancestor(a, b, pfunc)
34
34
35 if not first:
35 if not first:
36 ancestor.ancestor = newancestor
36 ancestor.ancestor = newancestor
37 else:
37 else:
38 repo.ui.debug(_("first revision, do not change ancestor\n"))
38 repo.ui.debug(_("first revision, do not change ancestor\n"))
39 stats = merge.update(repo, rev, True, True, False)
39 stats = merge.update(repo, rev, True, True, False)
40 return stats
40 return stats
41
41
42 def rebase(ui, repo, **opts):
42 def rebase(ui, repo, **opts):
43 """move changeset (and descendants) to a different branch
43 """move changeset (and descendants) to a different branch
44
44
45 Rebase uses repeated merging to graft changesets from one part of
45 Rebase uses repeated merging to graft changesets from one part of
46 history onto another. This can be useful for linearizing local
46 history onto another. This can be useful for linearizing local
47 changes relative to a master development tree.
47 changes relative to a master development tree.
48
48
49 If a rebase is interrupted to manually resolve a merge, it can be
49 If a rebase is interrupted to manually resolve a merge, it can be
50 continued with --continue/-c or aborted with --abort/-a.
50 continued with --continue/-c or aborted with --abort/-a.
51 """
51 """
52 originalwd = target = None
52 originalwd = target = None
53 external = nullrev
53 external = nullrev
54 state = skipped = {}
54 state = skipped = {}
55
55
56 lock = wlock = None
56 lock = wlock = None
57 try:
57 try:
58 lock = repo.lock()
58 lock = repo.lock()
59 wlock = repo.wlock()
59 wlock = repo.wlock()
60
60
61 # Validate input and define rebasing points
61 # Validate input and define rebasing points
62 destf = opts.get('dest', None)
62 destf = opts.get('dest', None)
63 srcf = opts.get('source', None)
63 srcf = opts.get('source', None)
64 basef = opts.get('base', None)
64 basef = opts.get('base', None)
65 contf = opts.get('continue')
65 contf = opts.get('continue')
66 abortf = opts.get('abort')
66 abortf = opts.get('abort')
67 collapsef = opts.get('collapse', False)
67 collapsef = opts.get('collapse', False)
68 extrafn = opts.get('extrafn')
68 extrafn = opts.get('extrafn')
69 keepf = opts.get('keep', False)
69 keepf = opts.get('keep', False)
70 keepbranchesf = opts.get('keepbranches', False)
70 keepbranchesf = opts.get('keepbranches', False)
71
71
72 if contf or abortf:
72 if contf or abortf:
73 if contf and abortf:
73 if contf and abortf:
74 raise error.ParseError('rebase',
74 raise error.ParseError('rebase',
75 _('cannot use both abort and continue'))
75 _('cannot use both abort and continue'))
76 if collapsef:
76 if collapsef:
77 raise error.ParseError(
77 raise error.ParseError(
78 'rebase', _('cannot use collapse with continue or abort'))
78 'rebase', _('cannot use collapse with continue or abort'))
79
79
80 if srcf or basef or destf:
80 if srcf or basef or destf:
81 raise error.ParseError('rebase',
81 raise error.ParseError('rebase',
82 _('abort and continue do not allow specifying revisions'))
82 _('abort and continue do not allow specifying revisions'))
83
83
84 (originalwd, target, state, collapsef, keepf,
84 (originalwd, target, state, collapsef, keepf,
85 keepbranchesf, external) = restorestatus(repo)
85 keepbranchesf, external) = restorestatus(repo)
86 if abortf:
86 if abortf:
87 abort(repo, originalwd, target, state)
87 abort(repo, originalwd, target, state)
88 return
88 return
89 else:
89 else:
90 if srcf and basef:
90 if srcf and basef:
91 raise error.ParseError('rebase', _('cannot specify both a '
91 raise error.ParseError('rebase', _('cannot specify both a '
92 'revision and a base'))
92 'revision and a base'))
93 cmdutil.bail_if_changed(repo)
93 cmdutil.bail_if_changed(repo)
94 result = buildstate(repo, destf, srcf, basef, collapsef)
94 result = buildstate(repo, destf, srcf, basef, collapsef)
95 if result:
95 if result:
96 originalwd, target, state, external = result
96 originalwd, target, state, external = result
97 else: # Empty state built, nothing to rebase
97 else: # Empty state built, nothing to rebase
98 repo.ui.status(_('nothing to rebase\n'))
98 repo.ui.status(_('nothing to rebase\n'))
99 return
99 return
100
100
101 if keepbranchesf:
101 if keepbranchesf:
102 if extrafn:
102 if extrafn:
103 raise error.ParseError(
103 raise error.ParseError(
104 'rebase', _('cannot use both keepbranches and extrafn'))
104 'rebase', _('cannot use both keepbranches and extrafn'))
105 def extrafn(ctx, extra):
105 def extrafn(ctx, extra):
106 extra['branch'] = ctx.branch()
106 extra['branch'] = ctx.branch()
107
107
108 # Rebase
108 # Rebase
109 targetancestors = list(repo.changelog.ancestors(target))
109 targetancestors = list(repo.changelog.ancestors(target))
110 targetancestors.append(target)
110 targetancestors.append(target)
111
111
112 for rev in sorted(state):
112 for rev in sorted(state):
113 if state[rev] == -1:
113 if state[rev] == -1:
114 storestatus(repo, originalwd, target, state, collapsef, keepf,
114 storestatus(repo, originalwd, target, state, collapsef, keepf,
115 keepbranchesf, external)
115 keepbranchesf, external)
116 rebasenode(repo, rev, target, state, skipped, targetancestors,
116 rebasenode(repo, rev, target, state, skipped, targetancestors,
117 collapsef, extrafn)
117 collapsef, extrafn)
118 ui.note(_('rebase merging completed\n'))
118 ui.note(_('rebase merging completed\n'))
119
119
120 if collapsef:
120 if collapsef:
121 p1, p2 = defineparents(repo, min(state), target,
121 p1, p2 = defineparents(repo, min(state), target,
122 state, targetancestors)
122 state, targetancestors)
123 concludenode(repo, rev, p1, external, state, collapsef,
123 concludenode(repo, rev, p1, external, state, collapsef,
124 last=True, skipped=skipped, extrafn=extrafn)
124 last=True, skipped=skipped, extrafn=extrafn)
125
125
126 if 'qtip' in repo.tags():
126 if 'qtip' in repo.tags():
127 updatemq(repo, state, skipped, **opts)
127 updatemq(repo, state, skipped, **opts)
128
128
129 if not keepf:
129 if not keepf:
130 # Remove no more useful revisions
130 # Remove no more useful revisions
131 if set(repo.changelog.descendants(min(state))) - set(state):
131 if set(repo.changelog.descendants(min(state))) - set(state):
132 ui.warn(_("warning: new changesets detected on source branch, "
132 ui.warn(_("warning: new changesets detected on source branch, "
133 "not stripping\n"))
133 "not stripping\n"))
134 else:
134 else:
135 repair.strip(repo.ui, repo, repo[min(state)].node(), "strip")
135 repair.strip(repo.ui, repo, repo[min(state)].node(), "strip")
136
136
137 clearstatus(repo)
137 clearstatus(repo)
138 ui.status(_("rebase completed\n"))
138 ui.status(_("rebase completed\n"))
139 if os.path.exists(repo.sjoin('undo')):
139 if os.path.exists(repo.sjoin('undo')):
140 util.unlink(repo.sjoin('undo'))
140 util.unlink(repo.sjoin('undo'))
141 if skipped:
141 if skipped:
142 ui.note(_("%d revisions have been skipped\n") % len(skipped))
142 ui.note(_("%d revisions have been skipped\n") % len(skipped))
143 finally:
143 finally:
144 release(lock, wlock)
144 release(lock, wlock)
145
145
146 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped={},
146 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped={},
147 extrafn=None):
147 extrafn=None):
148 """Skip commit if collapsing has been required and rev is not the last
148 """Skip commit if collapsing has been required and rev is not the last
149 revision, commit otherwise
149 revision, commit otherwise
150 """
150 """
151 repo.ui.debug(_(" set parents\n"))
151 repo.ui.debug(_(" set parents\n"))
152 if collapse and not last:
152 if collapse and not last:
153 repo.dirstate.setparents(repo[p1].node())
153 repo.dirstate.setparents(repo[p1].node())
154 return None
154 return None
155
155
156 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
156 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
157
157
158 # Commit, record the old nodeid
158 # Commit, record the old nodeid
159 m, a, r = repo.status()[:3]
159 m, a, r = repo.status()[:3]
160 newrev = nullrev
160 newrev = nullrev
161 try:
161 try:
162 if last:
162 if last:
163 commitmsg = 'Collapsed revision'
163 commitmsg = 'Collapsed revision'
164 for rebased in state:
164 for rebased in state:
165 if rebased not in skipped:
165 if rebased not in skipped:
166 commitmsg += '\n* %s' % repo[rebased].description()
166 commitmsg += '\n* %s' % repo[rebased].description()
167 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
167 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
168 else:
168 else:
169 commitmsg = repo[rev].description()
169 commitmsg = repo[rev].description()
170 # Commit might fail if unresolved files exist
170 # Commit might fail if unresolved files exist
171 extra = {'rebase_source': repo[rev].hex()}
171 extra = {'rebase_source': repo[rev].hex()}
172 if extrafn:
172 if extrafn:
173 extrafn(repo[rev], extra)
173 extrafn(repo[rev], extra)
174 newrev = repo.commit(m+a+r,
174 newrev = repo.commit(m+a+r,
175 text=commitmsg,
175 text=commitmsg,
176 user=repo[rev].user(),
176 user=repo[rev].user(),
177 date=repo[rev].date(),
177 date=repo[rev].date(),
178 extra=extra)
178 extra=extra)
179 repo.dirstate.setbranch(repo[newrev].branch())
179 return newrev
180 return newrev
180 except util.Abort:
181 except util.Abort:
181 # Invalidate the previous setparents
182 # Invalidate the previous setparents
182 repo.dirstate.invalidate()
183 repo.dirstate.invalidate()
183 raise
184 raise
184
185
185 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
186 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
186 extrafn):
187 extrafn):
187 'Rebase a single revision'
188 'Rebase a single revision'
188 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
189 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
189
190
190 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
191 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
191
192
192 repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(),
193 repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(),
193 repo[p2].rev()))
194 repo[p2].rev()))
194
195
195 # Merge phase
196 # Merge phase
196 if len(repo.parents()) != 2:
197 if len(repo.parents()) != 2:
197 # Update to target and merge it with local
198 # Update to target and merge it with local
198 if repo['.'].rev() != repo[p1].rev():
199 if repo['.'].rev() != repo[p1].rev():
199 repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
200 repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
200 merge.update(repo, p1, False, True, False)
201 merge.update(repo, p1, False, True, False)
201 else:
202 else:
202 repo.ui.debug(_(" already in target\n"))
203 repo.ui.debug(_(" already in target\n"))
203 repo.dirstate.write()
204 repo.dirstate.write()
204 repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
205 repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
205 first = repo[rev].rev() == repo[min(state)].rev()
206 first = repo[rev].rev() == repo[min(state)].rev()
206 stats = rebasemerge(repo, rev, first)
207 stats = rebasemerge(repo, rev, first)
207
208
208 if stats[3] > 0:
209 if stats[3] > 0:
209 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
210 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
210 'run hg rebase --continue'))
211 'run hg rebase --continue'))
211 else: # we have an interrupted rebase
212 else: # we have an interrupted rebase
212 repo.ui.debug(_('resuming interrupted rebase\n'))
213 repo.ui.debug(_('resuming interrupted rebase\n'))
213
214
214 # Keep track of renamed files in the revision that is going to be rebased
215 # Keep track of renamed files in the revision that is going to be rebased
215 # Here we simulate the copies and renames in the source changeset
216 # Here we simulate the copies and renames in the source changeset
216 cop, diver = copies.copies(repo, repo[rev], repo[target], repo[p2], True)
217 cop, diver = copies.copies(repo, repo[rev], repo[target], repo[p2], True)
217 m1 = repo[rev].manifest()
218 m1 = repo[rev].manifest()
218 m2 = repo[target].manifest()
219 m2 = repo[target].manifest()
219 for k, v in cop.iteritems():
220 for k, v in cop.iteritems():
220 if k in m1:
221 if k in m1:
221 if v in m1 or v in m2:
222 if v in m1 or v in m2:
222 repo.dirstate.copy(v, k)
223 repo.dirstate.copy(v, k)
223 if v in m2 and v not in m1:
224 if v in m2 and v not in m1:
224 repo.dirstate.remove(v)
225 repo.dirstate.remove(v)
225
226
226 newrev = concludenode(repo, rev, p1, p2, state, collapse,
227 newrev = concludenode(repo, rev, p1, p2, state, collapse,
227 extrafn=extrafn)
228 extrafn=extrafn)
228
229
229 # Update the state
230 # Update the state
230 if newrev is not None:
231 if newrev is not None:
231 state[rev] = repo[newrev].rev()
232 state[rev] = repo[newrev].rev()
232 else:
233 else:
233 if not collapse:
234 if not collapse:
234 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
235 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
235 repo.ui.debug(_('next revision set to %s\n') % p1)
236 repo.ui.debug(_('next revision set to %s\n') % p1)
236 skipped[rev] = True
237 skipped[rev] = True
237 state[rev] = p1
238 state[rev] = p1
238
239
239 def defineparents(repo, rev, target, state, targetancestors):
240 def defineparents(repo, rev, target, state, targetancestors):
240 'Return the new parent relationship of the revision that will be rebased'
241 'Return the new parent relationship of the revision that will be rebased'
241 parents = repo[rev].parents()
242 parents = repo[rev].parents()
242 p1 = p2 = nullrev
243 p1 = p2 = nullrev
243
244
244 P1n = parents[0].rev()
245 P1n = parents[0].rev()
245 if P1n in targetancestors:
246 if P1n in targetancestors:
246 p1 = target
247 p1 = target
247 elif P1n in state:
248 elif P1n in state:
248 p1 = state[P1n]
249 p1 = state[P1n]
249 else: # P1n external
250 else: # P1n external
250 p1 = target
251 p1 = target
251 p2 = P1n
252 p2 = P1n
252
253
253 if len(parents) == 2 and parents[1].rev() not in targetancestors:
254 if len(parents) == 2 and parents[1].rev() not in targetancestors:
254 P2n = parents[1].rev()
255 P2n = parents[1].rev()
255 # interesting second parent
256 # interesting second parent
256 if P2n in state:
257 if P2n in state:
257 if p1 == target: # P1n in targetancestors or external
258 if p1 == target: # P1n in targetancestors or external
258 p1 = state[P2n]
259 p1 = state[P2n]
259 else:
260 else:
260 p2 = state[P2n]
261 p2 = state[P2n]
261 else: # P2n external
262 else: # P2n external
262 if p2 != nullrev: # P1n external too => rev is a merged revision
263 if p2 != nullrev: # P1n external too => rev is a merged revision
263 raise util.Abort(_('cannot use revision %d as base, result '
264 raise util.Abort(_('cannot use revision %d as base, result '
264 'would have 3 parents') % rev)
265 'would have 3 parents') % rev)
265 p2 = P2n
266 p2 = P2n
266 return p1, p2
267 return p1, p2
267
268
268 def isagitpatch(repo, patchname):
269 def isagitpatch(repo, patchname):
269 'Return true if the given patch is in git format'
270 'Return true if the given patch is in git format'
270 mqpatch = os.path.join(repo.mq.path, patchname)
271 mqpatch = os.path.join(repo.mq.path, patchname)
271 for line in patch.linereader(file(mqpatch, 'rb')):
272 for line in patch.linereader(file(mqpatch, 'rb')):
272 if line.startswith('diff --git'):
273 if line.startswith('diff --git'):
273 return True
274 return True
274 return False
275 return False
275
276
276 def updatemq(repo, state, skipped, **opts):
277 def updatemq(repo, state, skipped, **opts):
277 'Update rebased mq patches - finalize and then import them'
278 'Update rebased mq patches - finalize and then import them'
278 mqrebase = {}
279 mqrebase = {}
279 for p in repo.mq.applied:
280 for p in repo.mq.applied:
280 if repo[p.rev].rev() in state:
281 if repo[p.rev].rev() in state:
281 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
282 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
282 (repo[p.rev].rev(), p.name))
283 (repo[p.rev].rev(), p.name))
283 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
284 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
284
285
285 if mqrebase:
286 if mqrebase:
286 repo.mq.finish(repo, mqrebase.keys())
287 repo.mq.finish(repo, mqrebase.keys())
287
288
288 # We must start import from the newest revision
289 # We must start import from the newest revision
289 for rev in sorted(mqrebase, reverse=True):
290 for rev in sorted(mqrebase, reverse=True):
290 if rev not in skipped:
291 if rev not in skipped:
291 repo.ui.debug(_('import mq patch %d (%s)\n')
292 repo.ui.debug(_('import mq patch %d (%s)\n')
292 % (state[rev], mqrebase[rev][0]))
293 % (state[rev], mqrebase[rev][0]))
293 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
294 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
294 git=mqrebase[rev][1],rev=[str(state[rev])])
295 git=mqrebase[rev][1],rev=[str(state[rev])])
295 repo.mq.save_dirty()
296 repo.mq.save_dirty()
296
297
297 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
298 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
298 external):
299 external):
299 'Store the current status to allow recovery'
300 'Store the current status to allow recovery'
300 f = repo.opener("rebasestate", "w")
301 f = repo.opener("rebasestate", "w")
301 f.write(repo[originalwd].hex() + '\n')
302 f.write(repo[originalwd].hex() + '\n')
302 f.write(repo[target].hex() + '\n')
303 f.write(repo[target].hex() + '\n')
303 f.write(repo[external].hex() + '\n')
304 f.write(repo[external].hex() + '\n')
304 f.write('%d\n' % int(collapse))
305 f.write('%d\n' % int(collapse))
305 f.write('%d\n' % int(keep))
306 f.write('%d\n' % int(keep))
306 f.write('%d\n' % int(keepbranches))
307 f.write('%d\n' % int(keepbranches))
307 for d, v in state.iteritems():
308 for d, v in state.iteritems():
308 oldrev = repo[d].hex()
309 oldrev = repo[d].hex()
309 newrev = repo[v].hex()
310 newrev = repo[v].hex()
310 f.write("%s:%s\n" % (oldrev, newrev))
311 f.write("%s:%s\n" % (oldrev, newrev))
311 f.close()
312 f.close()
312 repo.ui.debug(_('rebase status stored\n'))
313 repo.ui.debug(_('rebase status stored\n'))
313
314
314 def clearstatus(repo):
315 def clearstatus(repo):
315 'Remove the status files'
316 'Remove the status files'
316 if os.path.exists(repo.join("rebasestate")):
317 if os.path.exists(repo.join("rebasestate")):
317 util.unlink(repo.join("rebasestate"))
318 util.unlink(repo.join("rebasestate"))
318
319
319 def restorestatus(repo):
320 def restorestatus(repo):
320 'Restore a previously stored status'
321 'Restore a previously stored status'
321 try:
322 try:
322 target = None
323 target = None
323 collapse = False
324 collapse = False
324 external = nullrev
325 external = nullrev
325 state = {}
326 state = {}
326 f = repo.opener("rebasestate")
327 f = repo.opener("rebasestate")
327 for i, l in enumerate(f.read().splitlines()):
328 for i, l in enumerate(f.read().splitlines()):
328 if i == 0:
329 if i == 0:
329 originalwd = repo[l].rev()
330 originalwd = repo[l].rev()
330 elif i == 1:
331 elif i == 1:
331 target = repo[l].rev()
332 target = repo[l].rev()
332 elif i == 2:
333 elif i == 2:
333 external = repo[l].rev()
334 external = repo[l].rev()
334 elif i == 3:
335 elif i == 3:
335 collapse = bool(int(l))
336 collapse = bool(int(l))
336 elif i == 4:
337 elif i == 4:
337 keep = bool(int(l))
338 keep = bool(int(l))
338 elif i == 5:
339 elif i == 5:
339 keepbranches = bool(int(l))
340 keepbranches = bool(int(l))
340 else:
341 else:
341 oldrev, newrev = l.split(':')
342 oldrev, newrev = l.split(':')
342 state[repo[oldrev].rev()] = repo[newrev].rev()
343 state[repo[oldrev].rev()] = repo[newrev].rev()
343 repo.ui.debug(_('rebase status resumed\n'))
344 repo.ui.debug(_('rebase status resumed\n'))
344 return originalwd, target, state, collapse, keep, keepbranches, external
345 return originalwd, target, state, collapse, keep, keepbranches, external
345 except IOError, err:
346 except IOError, err:
346 if err.errno != errno.ENOENT:
347 if err.errno != errno.ENOENT:
347 raise
348 raise
348 raise util.Abort(_('no rebase in progress'))
349 raise util.Abort(_('no rebase in progress'))
349
350
350 def abort(repo, originalwd, target, state):
351 def abort(repo, originalwd, target, state):
351 'Restore the repository to its original state'
352 'Restore the repository to its original state'
352 if set(repo.changelog.descendants(target)) - set(state.values()):
353 if set(repo.changelog.descendants(target)) - set(state.values()):
353 repo.ui.warn(_("warning: new changesets detected on target branch, "
354 repo.ui.warn(_("warning: new changesets detected on target branch, "
354 "not stripping\n"))
355 "not stripping\n"))
355 else:
356 else:
356 # Strip from the first rebased revision
357 # Strip from the first rebased revision
357 merge.update(repo, repo[originalwd].rev(), False, True, False)
358 merge.update(repo, repo[originalwd].rev(), False, True, False)
358 rebased = filter(lambda x: x > -1, state.values())
359 rebased = filter(lambda x: x > -1, state.values())
359 if rebased:
360 if rebased:
360 strippoint = min(rebased)
361 strippoint = min(rebased)
361 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
362 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
362 clearstatus(repo)
363 clearstatus(repo)
363 repo.ui.status(_('rebase aborted\n'))
364 repo.ui.status(_('rebase aborted\n'))
364
365
365 def buildstate(repo, dest, src, base, collapse):
366 def buildstate(repo, dest, src, base, collapse):
366 'Define which revisions are going to be rebased and where'
367 'Define which revisions are going to be rebased and where'
367 targetancestors = set()
368 targetancestors = set()
368
369
369 if not dest:
370 if not dest:
370 # Destination defaults to the latest revision in the current branch
371 # Destination defaults to the latest revision in the current branch
371 branch = repo[None].branch()
372 branch = repo[None].branch()
372 dest = repo[branch].rev()
373 dest = repo[branch].rev()
373 else:
374 else:
374 if 'qtip' in repo.tags() and (repo[dest].hex() in
375 if 'qtip' in repo.tags() and (repo[dest].hex() in
375 [s.rev for s in repo.mq.applied]):
376 [s.rev for s in repo.mq.applied]):
376 raise util.Abort(_('cannot rebase onto an applied mq patch'))
377 raise util.Abort(_('cannot rebase onto an applied mq patch'))
377 dest = repo[dest].rev()
378 dest = repo[dest].rev()
378
379
379 if src:
380 if src:
380 commonbase = repo[src].ancestor(repo[dest])
381 commonbase = repo[src].ancestor(repo[dest])
381 if commonbase == repo[src]:
382 if commonbase == repo[src]:
382 raise util.Abort(_('cannot rebase an ancestor'))
383 raise util.Abort(_('cannot rebase an ancestor'))
383 if commonbase == repo[dest]:
384 if commonbase == repo[dest]:
384 raise util.Abort(_('cannot rebase a descendant'))
385 raise util.Abort(_('cannot rebase a descendant'))
385 source = repo[src].rev()
386 source = repo[src].rev()
386 else:
387 else:
387 if base:
388 if base:
388 cwd = repo[base].rev()
389 cwd = repo[base].rev()
389 else:
390 else:
390 cwd = repo['.'].rev()
391 cwd = repo['.'].rev()
391
392
392 if cwd == dest:
393 if cwd == dest:
393 repo.ui.debug(_('already working on current\n'))
394 repo.ui.debug(_('already working on current\n'))
394 return None
395 return None
395
396
396 targetancestors = set(repo.changelog.ancestors(dest))
397 targetancestors = set(repo.changelog.ancestors(dest))
397 if cwd in targetancestors:
398 if cwd in targetancestors:
398 repo.ui.debug(_('already working on the current branch\n'))
399 repo.ui.debug(_('already working on the current branch\n'))
399 return None
400 return None
400
401
401 cwdancestors = set(repo.changelog.ancestors(cwd))
402 cwdancestors = set(repo.changelog.ancestors(cwd))
402 cwdancestors.add(cwd)
403 cwdancestors.add(cwd)
403 rebasingbranch = cwdancestors - targetancestors
404 rebasingbranch = cwdancestors - targetancestors
404 source = min(rebasingbranch)
405 source = min(rebasingbranch)
405
406
406 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
407 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
407 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
408 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
408 external = nullrev
409 external = nullrev
409 if collapse:
410 if collapse:
410 if not targetancestors:
411 if not targetancestors:
411 targetancestors = set(repo.changelog.ancestors(dest))
412 targetancestors = set(repo.changelog.ancestors(dest))
412 for rev in state:
413 for rev in state:
413 # Check externals and fail if there are more than one
414 # Check externals and fail if there are more than one
414 for p in repo[rev].parents():
415 for p in repo[rev].parents():
415 if (p.rev() not in state and p.rev() != source
416 if (p.rev() not in state and p.rev() != source
416 and p.rev() not in targetancestors):
417 and p.rev() not in targetancestors):
417 if external != nullrev:
418 if external != nullrev:
418 raise util.Abort(_('unable to collapse, there is more '
419 raise util.Abort(_('unable to collapse, there is more '
419 'than one external parent'))
420 'than one external parent'))
420 external = p.rev()
421 external = p.rev()
421
422
422 state[source] = nullrev
423 state[source] = nullrev
423 return repo['.'].rev(), repo[dest].rev(), state, external
424 return repo['.'].rev(), repo[dest].rev(), state, external
424
425
425 def pullrebase(orig, ui, repo, *args, **opts):
426 def pullrebase(orig, ui, repo, *args, **opts):
426 'Call rebase after pull if the latter has been invoked with --rebase'
427 'Call rebase after pull if the latter has been invoked with --rebase'
427 if opts.get('rebase'):
428 if opts.get('rebase'):
428 if opts.get('update'):
429 if opts.get('update'):
429 del opts['update']
430 del opts['update']
430 ui.debug(_('--update and --rebase are not compatible, ignoring '
431 ui.debug(_('--update and --rebase are not compatible, ignoring '
431 'the update flag\n'))
432 'the update flag\n'))
432
433
433 cmdutil.bail_if_changed(repo)
434 cmdutil.bail_if_changed(repo)
434 revsprepull = len(repo)
435 revsprepull = len(repo)
435 orig(ui, repo, *args, **opts)
436 orig(ui, repo, *args, **opts)
436 revspostpull = len(repo)
437 revspostpull = len(repo)
437 if revspostpull > revsprepull:
438 if revspostpull > revsprepull:
438 rebase(ui, repo, **opts)
439 rebase(ui, repo, **opts)
439 branch = repo[None].branch()
440 branch = repo[None].branch()
440 dest = repo[branch].rev()
441 dest = repo[branch].rev()
441 if dest != repo['.'].rev():
442 if dest != repo['.'].rev():
442 # there was nothing to rebase we force an update
443 # there was nothing to rebase we force an update
443 merge.update(repo, dest, False, False, False)
444 merge.update(repo, dest, False, False, False)
444 else:
445 else:
445 orig(ui, repo, *args, **opts)
446 orig(ui, repo, *args, **opts)
446
447
447 def uisetup(ui):
448 def uisetup(ui):
448 'Replace pull with a decorator to provide --rebase option'
449 'Replace pull with a decorator to provide --rebase option'
449 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
450 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
450 entry[1].append(('', 'rebase', None,
451 entry[1].append(('', 'rebase', None,
451 _("rebase working directory to branch head"))
452 _("rebase working directory to branch head"))
452 )
453 )
453
454
454 cmdtable = {
455 cmdtable = {
455 "rebase":
456 "rebase":
456 (rebase,
457 (rebase,
457 [
458 [
458 ('s', 'source', '', _('rebase from a given revision')),
459 ('s', 'source', '', _('rebase from a given revision')),
459 ('b', 'base', '', _('rebase from the base of a given revision')),
460 ('b', 'base', '', _('rebase from the base of a given revision')),
460 ('d', 'dest', '', _('rebase onto a given revision')),
461 ('d', 'dest', '', _('rebase onto a given revision')),
461 ('', 'collapse', False, _('collapse the rebased revisions')),
462 ('', 'collapse', False, _('collapse the rebased revisions')),
462 ('', 'keep', False, _('keep original revisions')),
463 ('', 'keep', False, _('keep original revisions')),
463 ('', 'keepbranches', False, _('keep original branches')),
464 ('', 'keepbranches', False, _('keep original branches')),
464 ('c', 'continue', False, _('continue an interrupted rebase')),
465 ('c', 'continue', False, _('continue an interrupted rebase')),
465 ('a', 'abort', False, _('abort an interrupted rebase')),] +
466 ('a', 'abort', False, _('abort an interrupted rebase')),] +
466 templateopts,
467 templateopts,
467 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] '
468 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] '
468 '[--keepbranches] | [-c] | [-a]')),
469 '[--keepbranches] | [-c] | [-a]')),
469 }
470 }
@@ -1,30 +1,32 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 echo "[extensions]" >> $HGRCPATH
3 echo "[extensions]" >> $HGRCPATH
4 echo "graphlog=" >> $HGRCPATH
4 echo "graphlog=" >> $HGRCPATH
5 echo "rebase=" >> $HGRCPATH
5 echo "rebase=" >> $HGRCPATH
6
6
7 addcommit () {
7 addcommit () {
8 echo $1 > $1
8 echo $1 > $1
9 hg add $1
9 hg add $1
10 hg commit -d "${2} 0" -m $1
10 hg commit -d "${2} 0" -m $1
11 }
11 }
12
12
13 hg init a
13 hg init a
14 cd a
14 cd a
15 addcommit "c1" 0
15 addcommit "c1" 0
16 addcommit "c2" 1
16 addcommit "c2" 1
17
17
18 addcommit "l1" 2
18 addcommit "l1" 2
19 addcommit "l2" 3
19 addcommit "l2" 3
20
20
21 hg update -C 1
21 hg update -C 1
22 hg branch 'notdefault'
22 hg branch 'notdefault'
23 addcommit "r1" 4
23 addcommit "r1" 4
24 hg glog --template '{rev}:{desc}:{branches}\n'
24 hg glog --template '{rev}:{desc}:{branches}\n'
25
25
26 echo
26 echo
27 echo '% Rebase a branch while preserving the branch name'
27 echo '% Rebase a branch while preserving the branch name'
28 hg update -C 3
28 hg update -C 3
29 hg rebase -b 4 -d 3 --keepbranches 2>&1 | sed 's/\(saving bundle to \).*/\1/'
29 hg rebase -b 4 -d 3 --keepbranches 2>&1 | sed 's/\(saving bundle to \).*/\1/'
30 hg glog --template '{rev}:{desc}:{branches}\n'
30 hg glog --template '{rev}:{desc}:{branches}\n'
31 echo '% dirstate branch should be "notdefault"'
32 hg branch
@@ -1,33 +1,35 b''
1 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
1 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
2 marked working directory as branch notdefault
2 marked working directory as branch notdefault
3 created new head
3 created new head
4 @ 4:r1:notdefault
4 @ 4:r1:notdefault
5 |
5 |
6 | o 3:l2:
6 | o 3:l2:
7 | |
7 | |
8 | o 2:l1:
8 | o 2:l1:
9 |/
9 |/
10 o 1:c2:
10 o 1:c2:
11 |
11 |
12 o 0:c1:
12 o 0:c1:
13
13
14
14
15 % Rebase a branch while preserving the branch name
15 % Rebase a branch while preserving the branch name
16 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
16 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
17 saving bundle to
17 saving bundle to
18 adding branch
18 adding branch
19 adding changesets
19 adding changesets
20 adding manifests
20 adding manifests
21 adding file changes
21 adding file changes
22 added 1 changesets with 1 changes to 1 files
22 added 1 changesets with 1 changes to 1 files
23 rebase completed
23 rebase completed
24 @ 4:r1:notdefault
24 @ 4:r1:notdefault
25 |
25 |
26 o 3:l2:
26 o 3:l2:
27 |
27 |
28 o 2:l1:
28 o 2:l1:
29 |
29 |
30 o 1:c2:
30 o 1:c2:
31 |
31 |
32 o 0:c1:
32 o 0:c1:
33
33
34 % dirstate branch should be "notdefault"
35 notdefault
General Comments 0
You need to be logged in to leave comments. Login now