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