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