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