##// END OF EJS Templates
rebase: calculate ancestors for --base separately (issue5420)...
Jun Wu -
r30580:51e7c83e default
parent child Browse files
Show More
@@ -0,0 +1,393 b''
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
3 > rebase=
4 > drawdag=$TESTDIR/drawdag.py
5 >
6 > [phases]
7 > publish=False
8 >
9 > [alias]
10 > tglog = log -G --template "{rev}: {desc}"
11 > EOF
12
13 $ rebasewithdag() {
14 > N=`$PYTHON -c "print($N+1)"`
15 > hg init repo$N && cd repo$N
16 > hg debugdrawdag
17 > hg rebase "$@" > _rebasetmp
18 > r=$?
19 > grep -v 'saved backup bundle' _rebasetmp
20 > [ $r -eq 0 ] && hg tglog
21 > cd ..
22 > return $r
23 > }
24
25 Single branching point, without merge:
26
27 $ rebasewithdag -b D -d Z <<'EOS'
28 > D E
29 > |/
30 > Z B C # C: branching point, E should be picked
31 > \|/ # B should not be picked
32 > A
33 > |
34 > R
35 > EOS
36 rebasing 3:d6003a550c2c "C" (C)
37 rebasing 5:4526cf523425 "D" (D)
38 rebasing 6:b296604d9846 "E" (E tip)
39 o 6: E
40 |
41 | o 5: D
42 |/
43 o 4: C
44 |
45 o 3: Z
46 |
47 | o 2: B
48 |/
49 o 1: A
50 |
51 o 0: R
52
53 Multiple branching points caused by selecting a single merge changeset:
54
55 $ rebasewithdag -b E -d Z <<'EOS'
56 > E
57 > /|
58 > B C D # B, C: multiple branching points
59 > | |/ # D should not be picked
60 > Z | /
61 > \|/
62 > A
63 > |
64 > R
65 > EOS
66 rebasing 2:c1e6b162678d "B" (B)
67 rebasing 3:d6003a550c2c "C" (C)
68 rebasing 6:5251e0cb7302 "E" (E tip)
69 o 6: E
70 |\
71 | o 5: C
72 | |
73 o | 4: B
74 |/
75 o 3: Z
76 |
77 | o 2: D
78 |/
79 o 1: A
80 |
81 o 0: R
82
83 Rebase should not extend the "--base" revset using "descendants":
84
85 $ rebasewithdag -b B -d Z <<'EOS'
86 > E
87 > /|
88 > Z B C # descendants(B) = B+E. With E, C will be included incorrectly
89 > \|/
90 > A
91 > |
92 > R
93 > EOS
94 rebasing 2:c1e6b162678d "B" (B)
95 rebasing 5:5251e0cb7302 "E" (E tip)
96 o 5: E
97 |\
98 | o 4: B
99 | |
100 | o 3: Z
101 | |
102 o | 2: C
103 |/
104 o 1: A
105 |
106 o 0: R
107
108 Rebase should not simplify the "--base" revset using "roots":
109
110 $ rebasewithdag -b B+E -d Z <<'EOS'
111 > E
112 > /|
113 > Z B C # roots(B+E) = B. Without E, C will be missed incorrectly
114 > \|/
115 > A
116 > |
117 > R
118 > EOS
119 rebasing 2:c1e6b162678d "B" (B)
120 rebasing 3:d6003a550c2c "C" (C)
121 rebasing 5:5251e0cb7302 "E" (E tip)
122 o 5: E
123 |\
124 | o 4: C
125 | |
126 o | 3: B
127 |/
128 o 2: Z
129 |
130 o 1: A
131 |
132 o 0: R
133
134 The destination is one of the two branching points of a merge:
135
136 $ rebasewithdag -b F -d Z <<'EOS'
137 > F
138 > / \
139 > E D
140 > / /
141 > Z C
142 > \ /
143 > B
144 > |
145 > A
146 > EOS
147 nothing to rebase
148 [1]
149
150 Multiple branching points caused by multiple bases (issue5420):
151
152 $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
153 > Z E2
154 > | /
155 > F E1 C2
156 > |/ /
157 > E C1 B2
158 > |/ /
159 > C B1
160 > |/
161 > B
162 > |
163 > A
164 > |
165 > R
166 > EOS
167 rebasing 3:a113dbaa660a "B1" (B1)
168 rebasing 5:06ce7b1cc8c2 "B2" (B2)
169 rebasing 6:0ac98cce32d3 "C1" (C1)
170 rebasing 8:781512f5e33d "C2" (C2)
171 rebasing 9:428d8c18f641 "E1" (E1)
172 rebasing 11:e1bf82f6b6df "E2" (E2)
173 o 12: E2
174 |
175 o 11: E1
176 |
177 | o 10: C2
178 | |
179 | o 9: C1
180 |/
181 | o 8: B2
182 | |
183 | o 7: B1
184 |/
185 o 6: Z
186 |
187 o 5: F
188 |
189 o 4: E
190 |
191 o 3: C
192 |
193 o 2: B
194 |
195 o 1: A
196 |
197 o 0: R
198
199 Multiple branching points with multiple merges:
200
201 $ rebasewithdag -b G+P -d Z <<'EOS'
202 > G H P
203 > |\ /| |\
204 > F E D M N
205 > \|/| /| |\
206 > Z C B I J K L
207 > \|/ |/ |/
208 > A A A
209 > EOS
210 rebasing 2:dc0947a82db8 "C" (C)
211 rebasing 8:215e7b0814e1 "D" (D)
212 rebasing 9:03ca77807e91 "E" (E)
213 rebasing 10:afc707c82df0 "F" (F)
214 rebasing 13:018caa673317 "G" (G)
215 rebasing 14:4f710fbd68cb "H" (H)
216 rebasing 3:08ebfeb61bac "I" (I)
217 rebasing 4:a0a5005cec67 "J" (J)
218 rebasing 5:83780307a7e8 "K" (K)
219 rebasing 6:e131637a1cb6 "L" (L)
220 rebasing 11:d6fe3d11d95d "M" (M)
221 rebasing 12:fa1e02269063 "N" (N)
222 rebasing 15:448b1a498430 "P" (P tip)
223 o 15: P
224 |\
225 | o 14: N
226 | |\
227 o \ \ 13: M
228 |\ \ \
229 | | | o 12: L
230 | | | |
231 | | o | 11: K
232 | | |/
233 | o / 10: J
234 | |/
235 o / 9: I
236 |/
237 | o 8: H
238 | |\
239 | | | o 7: G
240 | | |/|
241 | | | o 6: F
242 | | | |
243 | | o | 5: E
244 | | |/
245 | o | 4: D
246 | |\|
247 +---o 3: C
248 | |
249 o | 2: Z
250 | |
251 | o 1: B
252 |/
253 o 0: A
254
255 Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
256
257 $ rebasewithdag -b A3+B3 -d Z <<'EOF'
258 > Z C1 A3 B3
259 > | / / \ / \
260 > M3 C0 A1 A2 B1 B2
261 > | / | | | |
262 > M2 M1 C1 C1 M3
263 > |
264 > M1
265 > |
266 > M0
267 > EOF
268 rebasing 4:8817fae53c94 "C0" (C0)
269 rebasing 6:06ca5dfe3b5b "B2" (B2)
270 rebasing 7:73508237b032 "C1" (C1)
271 rebasing 9:fdb955e2faed "A2" (A2)
272 rebasing 11:1b2f368c3cb5 "A3" (A3)
273 rebasing 10:0a33b0519128 "B1" (B1)
274 rebasing 12:bd6a37b5b67a "B3" (B3 tip)
275 o 12: B3
276 |\
277 | o 11: B1
278 | |
279 | | o 10: A3
280 | | |\
281 | +---o 9: A2
282 | | |
283 | o | 8: C1
284 | | |
285 o | | 7: B2
286 | | |
287 | o | 6: C0
288 |/ /
289 o | 5: Z
290 | |
291 o | 4: M3
292 | |
293 o | 3: M2
294 | |
295 | o 2: A1
296 |/
297 o 1: M1
298 |
299 o 0: M0
300
301 Mixed rebasable and non-rebasable bases (unresolved, issue5422):
302
303 $ rebasewithdag -b C+D -d B <<'EOS'
304 > D
305 > /
306 > B C
307 > |/
308 > A
309 > EOS
310 nothing to rebase
311 [1]
312
313 Disconnected graph:
314
315 $ rebasewithdag -b B -d Z <<'EOS'
316 > B
317 > |
318 > Z A
319 > EOS
320 nothing to rebase from 112478962961 to 48b9aae0607f
321 [1]
322
323 Multiple roots. Roots are ancestors of dest:
324
325 $ rebasewithdag -b B+D -d Z <<'EOF'
326 > D Z B
327 > \|\|
328 > C A
329 > EOF
330 rebasing 2:112478962961 "B" (B)
331 rebasing 3:b70f76719894 "D" (D)
332 o 4: D
333 |
334 | o 3: B
335 |/
336 o 2: Z
337 |\
338 | o 1: C
339 |
340 o 0: A
341
342 Multiple roots. One root is not an ancestor of dest:
343
344 $ rebasewithdag -b B+D -d Z <<'EOF'
345 > Z B D
346 > \|\|
347 > A C
348 > EOF
349 nothing to rebase from 86d01f49c0d9+b70f76719894 to 262e37e34f63
350 [1]
351
352 Multiple roots. One root is not an ancestor of dest. Select using a merge:
353
354 $ rebasewithdag -b E -d Z <<'EOF'
355 > E
356 > |\
357 > Z B D
358 > \|\|
359 > A C
360 > EOF
361 rebasing 2:86d01f49c0d9 "B" (B)
362 rebasing 5:539a0ff83ea9 "E" (E tip)
363 o 5: E
364 |\
365 | o 4: B
366 | |\
367 | | o 3: Z
368 | | |
369 o | | 2: D
370 |/ /
371 o / 1: C
372 /
373 o 0: A
374
375 Multiple roots. Two children share two parents while dest has only one parent:
376
377 $ rebasewithdag -b B+D -d Z <<'EOF'
378 > Z B D
379 > \|\|\
380 > A C A
381 > EOF
382 rebasing 2:86d01f49c0d9 "B" (B)
383 rebasing 3:b7df2ca01aa8 "D" (D)
384 o 4: D
385 |\
386 +---o 3: B
387 | |/
388 | o 2: Z
389 | |
390 o | 1: C
391 /
392 o 0: A
393
@@ -1,1455 +1,1462 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 https://mercurial-scm.org/wiki/RebaseExtension
14 https://mercurial-scm.org/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from __future__ import absolute_import
17 from __future__ import absolute_import
18
18
19 import errno
19 import errno
20 import os
20 import os
21
21
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 from mercurial.node import (
23 from mercurial.node import (
24 hex,
24 hex,
25 nullid,
25 nullid,
26 nullrev,
26 nullrev,
27 short,
27 short,
28 )
28 )
29 from mercurial import (
29 from mercurial import (
30 bookmarks,
30 bookmarks,
31 cmdutil,
31 cmdutil,
32 commands,
32 commands,
33 copies,
33 copies,
34 destutil,
34 destutil,
35 dirstateguard,
35 dirstateguard,
36 error,
36 error,
37 extensions,
37 extensions,
38 hg,
38 hg,
39 lock,
39 lock,
40 merge as mergemod,
40 merge as mergemod,
41 mergeutil,
41 mergeutil,
42 obsolete,
42 obsolete,
43 patch,
43 patch,
44 phases,
44 phases,
45 registrar,
45 registrar,
46 repair,
46 repair,
47 repoview,
47 repoview,
48 revset,
48 revset,
49 scmutil,
49 scmutil,
50 util,
50 util,
51 )
51 )
52
52
53 release = lock.release
53 release = lock.release
54 templateopts = commands.templateopts
54 templateopts = commands.templateopts
55
55
56 # The following constants are used throughout the rebase module. The ordering of
56 # The following constants are used throughout the rebase module. The ordering of
57 # their values must be maintained.
57 # their values must be maintained.
58
58
59 # Indicates that a revision needs to be rebased
59 # Indicates that a revision needs to be rebased
60 revtodo = -1
60 revtodo = -1
61 nullmerge = -2
61 nullmerge = -2
62 revignored = -3
62 revignored = -3
63 # successor in rebase destination
63 # successor in rebase destination
64 revprecursor = -4
64 revprecursor = -4
65 # plain prune (no successor)
65 # plain prune (no successor)
66 revpruned = -5
66 revpruned = -5
67 revskipped = (revignored, revprecursor, revpruned)
67 revskipped = (revignored, revprecursor, revpruned)
68
68
69 cmdtable = {}
69 cmdtable = {}
70 command = cmdutil.command(cmdtable)
70 command = cmdutil.command(cmdtable)
71 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
71 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
72 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
72 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
73 # be specifying the version(s) of Mercurial they are tested with, or
73 # be specifying the version(s) of Mercurial they are tested with, or
74 # leave the attribute unspecified.
74 # leave the attribute unspecified.
75 testedwith = 'ships-with-hg-core'
75 testedwith = 'ships-with-hg-core'
76
76
77 def _nothingtorebase():
77 def _nothingtorebase():
78 return 1
78 return 1
79
79
80 def _savegraft(ctx, extra):
80 def _savegraft(ctx, extra):
81 s = ctx.extra().get('source', None)
81 s = ctx.extra().get('source', None)
82 if s is not None:
82 if s is not None:
83 extra['source'] = s
83 extra['source'] = s
84 s = ctx.extra().get('intermediate-source', None)
84 s = ctx.extra().get('intermediate-source', None)
85 if s is not None:
85 if s is not None:
86 extra['intermediate-source'] = s
86 extra['intermediate-source'] = s
87
87
88 def _savebranch(ctx, extra):
88 def _savebranch(ctx, extra):
89 extra['branch'] = ctx.branch()
89 extra['branch'] = ctx.branch()
90
90
91 def _makeextrafn(copiers):
91 def _makeextrafn(copiers):
92 """make an extrafn out of the given copy-functions.
92 """make an extrafn out of the given copy-functions.
93
93
94 A copy function takes a context and an extra dict, and mutates the
94 A copy function takes a context and an extra dict, and mutates the
95 extra dict as needed based on the given context.
95 extra dict as needed based on the given context.
96 """
96 """
97 def extrafn(ctx, extra):
97 def extrafn(ctx, extra):
98 for c in copiers:
98 for c in copiers:
99 c(ctx, extra)
99 c(ctx, extra)
100 return extrafn
100 return extrafn
101
101
102 def _destrebase(repo, sourceset, destspace=None):
102 def _destrebase(repo, sourceset, destspace=None):
103 """small wrapper around destmerge to pass the right extra args
103 """small wrapper around destmerge to pass the right extra args
104
104
105 Please wrap destutil.destmerge instead."""
105 Please wrap destutil.destmerge instead."""
106 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
106 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
107 onheadcheck=False, destspace=destspace)
107 onheadcheck=False, destspace=destspace)
108
108
109 revsetpredicate = registrar.revsetpredicate()
109 revsetpredicate = registrar.revsetpredicate()
110
110
111 @revsetpredicate('_destrebase')
111 @revsetpredicate('_destrebase')
112 def _revsetdestrebase(repo, subset, x):
112 def _revsetdestrebase(repo, subset, x):
113 # ``_rebasedefaultdest()``
113 # ``_rebasedefaultdest()``
114
114
115 # default destination for rebase.
115 # default destination for rebase.
116 # # XXX: Currently private because I expect the signature to change.
116 # # XXX: Currently private because I expect the signature to change.
117 # # XXX: - bailing out in case of ambiguity vs returning all data.
117 # # XXX: - bailing out in case of ambiguity vs returning all data.
118 # i18n: "_rebasedefaultdest" is a keyword
118 # i18n: "_rebasedefaultdest" is a keyword
119 sourceset = None
119 sourceset = None
120 if x is not None:
120 if x is not None:
121 sourceset = revset.getset(repo, revset.fullreposet(repo), x)
121 sourceset = revset.getset(repo, revset.fullreposet(repo), x)
122 return subset & revset.baseset([_destrebase(repo, sourceset)])
122 return subset & revset.baseset([_destrebase(repo, sourceset)])
123
123
124 class rebaseruntime(object):
124 class rebaseruntime(object):
125 """This class is a container for rebase runtime state"""
125 """This class is a container for rebase runtime state"""
126 def __init__(self, repo, ui, opts=None):
126 def __init__(self, repo, ui, opts=None):
127 if opts is None:
127 if opts is None:
128 opts = {}
128 opts = {}
129
129
130 self.repo = repo
130 self.repo = repo
131 self.ui = ui
131 self.ui = ui
132 self.opts = opts
132 self.opts = opts
133 self.originalwd = None
133 self.originalwd = None
134 self.external = nullrev
134 self.external = nullrev
135 # Mapping between the old revision id and either what is the new rebased
135 # Mapping between the old revision id and either what is the new rebased
136 # revision or what needs to be done with the old revision. The state
136 # revision or what needs to be done with the old revision. The state
137 # dict will be what contains most of the rebase progress state.
137 # dict will be what contains most of the rebase progress state.
138 self.state = {}
138 self.state = {}
139 self.activebookmark = None
139 self.activebookmark = None
140 self.currentbookmarks = None
140 self.currentbookmarks = None
141 self.target = None
141 self.target = None
142 self.skipped = set()
142 self.skipped = set()
143 self.targetancestors = set()
143 self.targetancestors = set()
144
144
145 self.collapsef = opts.get('collapse', False)
145 self.collapsef = opts.get('collapse', False)
146 self.collapsemsg = cmdutil.logmessage(ui, opts)
146 self.collapsemsg = cmdutil.logmessage(ui, opts)
147 self.date = opts.get('date', None)
147 self.date = opts.get('date', None)
148
148
149 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
149 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
150 self.extrafns = [_savegraft]
150 self.extrafns = [_savegraft]
151 if e:
151 if e:
152 self.extrafns = [e]
152 self.extrafns = [e]
153
153
154 self.keepf = opts.get('keep', False)
154 self.keepf = opts.get('keep', False)
155 self.keepbranchesf = opts.get('keepbranches', False)
155 self.keepbranchesf = opts.get('keepbranches', False)
156 # keepopen is not meant for use on the command line, but by
156 # keepopen is not meant for use on the command line, but by
157 # other extensions
157 # other extensions
158 self.keepopen = opts.get('keepopen', False)
158 self.keepopen = opts.get('keepopen', False)
159 self.obsoletenotrebased = {}
159 self.obsoletenotrebased = {}
160
160
161 def restorestatus(self):
161 def restorestatus(self):
162 """Restore a previously stored status"""
162 """Restore a previously stored status"""
163 repo = self.repo
163 repo = self.repo
164 keepbranches = None
164 keepbranches = None
165 target = None
165 target = None
166 collapse = False
166 collapse = False
167 external = nullrev
167 external = nullrev
168 activebookmark = None
168 activebookmark = None
169 state = {}
169 state = {}
170
170
171 try:
171 try:
172 f = repo.vfs("rebasestate")
172 f = repo.vfs("rebasestate")
173 for i, l in enumerate(f.read().splitlines()):
173 for i, l in enumerate(f.read().splitlines()):
174 if i == 0:
174 if i == 0:
175 originalwd = repo[l].rev()
175 originalwd = repo[l].rev()
176 elif i == 1:
176 elif i == 1:
177 target = repo[l].rev()
177 target = repo[l].rev()
178 elif i == 2:
178 elif i == 2:
179 external = repo[l].rev()
179 external = repo[l].rev()
180 elif i == 3:
180 elif i == 3:
181 collapse = bool(int(l))
181 collapse = bool(int(l))
182 elif i == 4:
182 elif i == 4:
183 keep = bool(int(l))
183 keep = bool(int(l))
184 elif i == 5:
184 elif i == 5:
185 keepbranches = bool(int(l))
185 keepbranches = bool(int(l))
186 elif i == 6 and not (len(l) == 81 and ':' in l):
186 elif i == 6 and not (len(l) == 81 and ':' in l):
187 # line 6 is a recent addition, so for backwards
187 # line 6 is a recent addition, so for backwards
188 # compatibility check that the line doesn't look like the
188 # compatibility check that the line doesn't look like the
189 # oldrev:newrev lines
189 # oldrev:newrev lines
190 activebookmark = l
190 activebookmark = l
191 else:
191 else:
192 oldrev, newrev = l.split(':')
192 oldrev, newrev = l.split(':')
193 if newrev in (str(nullmerge), str(revignored),
193 if newrev in (str(nullmerge), str(revignored),
194 str(revprecursor), str(revpruned)):
194 str(revprecursor), str(revpruned)):
195 state[repo[oldrev].rev()] = int(newrev)
195 state[repo[oldrev].rev()] = int(newrev)
196 elif newrev == nullid:
196 elif newrev == nullid:
197 state[repo[oldrev].rev()] = revtodo
197 state[repo[oldrev].rev()] = revtodo
198 # Legacy compat special case
198 # Legacy compat special case
199 else:
199 else:
200 state[repo[oldrev].rev()] = repo[newrev].rev()
200 state[repo[oldrev].rev()] = repo[newrev].rev()
201
201
202 except IOError as err:
202 except IOError as err:
203 if err.errno != errno.ENOENT:
203 if err.errno != errno.ENOENT:
204 raise
204 raise
205 cmdutil.wrongtooltocontinue(repo, _('rebase'))
205 cmdutil.wrongtooltocontinue(repo, _('rebase'))
206
206
207 if keepbranches is None:
207 if keepbranches is None:
208 raise error.Abort(_('.hg/rebasestate is incomplete'))
208 raise error.Abort(_('.hg/rebasestate is incomplete'))
209
209
210 skipped = set()
210 skipped = set()
211 # recompute the set of skipped revs
211 # recompute the set of skipped revs
212 if not collapse:
212 if not collapse:
213 seen = set([target])
213 seen = set([target])
214 for old, new in sorted(state.items()):
214 for old, new in sorted(state.items()):
215 if new != revtodo and new in seen:
215 if new != revtodo and new in seen:
216 skipped.add(old)
216 skipped.add(old)
217 seen.add(new)
217 seen.add(new)
218 repo.ui.debug('computed skipped revs: %s\n' %
218 repo.ui.debug('computed skipped revs: %s\n' %
219 (' '.join(str(r) for r in sorted(skipped)) or None))
219 (' '.join(str(r) for r in sorted(skipped)) or None))
220 repo.ui.debug('rebase status resumed\n')
220 repo.ui.debug('rebase status resumed\n')
221 _setrebasesetvisibility(repo, state.keys())
221 _setrebasesetvisibility(repo, state.keys())
222
222
223 self.originalwd = originalwd
223 self.originalwd = originalwd
224 self.target = target
224 self.target = target
225 self.state = state
225 self.state = state
226 self.skipped = skipped
226 self.skipped = skipped
227 self.collapsef = collapse
227 self.collapsef = collapse
228 self.keepf = keep
228 self.keepf = keep
229 self.keepbranchesf = keepbranches
229 self.keepbranchesf = keepbranches
230 self.external = external
230 self.external = external
231 self.activebookmark = activebookmark
231 self.activebookmark = activebookmark
232
232
233 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
233 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
234 """Compute structures necessary for skipping obsolete revisions
234 """Compute structures necessary for skipping obsolete revisions
235
235
236 rebaserevs: iterable of all revisions that are to be rebased
236 rebaserevs: iterable of all revisions that are to be rebased
237 obsoleterevs: iterable of all obsolete revisions in rebaseset
237 obsoleterevs: iterable of all obsolete revisions in rebaseset
238 target: a destination revision for the rebase operation
238 target: a destination revision for the rebase operation
239 """
239 """
240 self.obsoletenotrebased = {}
240 self.obsoletenotrebased = {}
241 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
241 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
242 default=True):
242 default=True):
243 return
243 return
244 rebaseset = set(rebaserevs)
244 rebaseset = set(rebaserevs)
245 obsoleteset = set(obsoleterevs)
245 obsoleteset = set(obsoleterevs)
246 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
246 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
247 obsoleteset, target)
247 obsoleteset, target)
248 skippedset = set(self.obsoletenotrebased)
248 skippedset = set(self.obsoletenotrebased)
249 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
249 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
250
250
251 def _prepareabortorcontinue(self, isabort):
251 def _prepareabortorcontinue(self, isabort):
252 try:
252 try:
253 self.restorestatus()
253 self.restorestatus()
254 self.collapsemsg = restorecollapsemsg(self.repo)
254 self.collapsemsg = restorecollapsemsg(self.repo)
255 except error.RepoLookupError:
255 except error.RepoLookupError:
256 if isabort:
256 if isabort:
257 clearstatus(self.repo)
257 clearstatus(self.repo)
258 clearcollapsemsg(self.repo)
258 clearcollapsemsg(self.repo)
259 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
259 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
260 ' only broken state is cleared)\n'))
260 ' only broken state is cleared)\n'))
261 return 0
261 return 0
262 else:
262 else:
263 msg = _('cannot continue inconsistent rebase')
263 msg = _('cannot continue inconsistent rebase')
264 hint = _('use "hg rebase --abort" to clear broken state')
264 hint = _('use "hg rebase --abort" to clear broken state')
265 raise error.Abort(msg, hint=hint)
265 raise error.Abort(msg, hint=hint)
266 if isabort:
266 if isabort:
267 return abort(self.repo, self.originalwd, self.target,
267 return abort(self.repo, self.originalwd, self.target,
268 self.state, activebookmark=self.activebookmark)
268 self.state, activebookmark=self.activebookmark)
269
269
270 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
270 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
271 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
271 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
272
272
273 def _preparenewrebase(self, dest, rebaseset):
273 def _preparenewrebase(self, dest, rebaseset):
274 if dest is None:
274 if dest is None:
275 return _nothingtorebase()
275 return _nothingtorebase()
276
276
277 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
277 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
278 if (not (self.keepf or allowunstable)
278 if (not (self.keepf or allowunstable)
279 and self.repo.revs('first(children(%ld) - %ld)',
279 and self.repo.revs('first(children(%ld) - %ld)',
280 rebaseset, rebaseset)):
280 rebaseset, rebaseset)):
281 raise error.Abort(
281 raise error.Abort(
282 _("can't remove original changesets with"
282 _("can't remove original changesets with"
283 " unrebased descendants"),
283 " unrebased descendants"),
284 hint=_('use --keep to keep original changesets'))
284 hint=_('use --keep to keep original changesets'))
285
285
286 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
286 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
287 self._handleskippingobsolete(rebaseset, obsrevs, dest)
287 self._handleskippingobsolete(rebaseset, obsrevs, dest)
288
288
289 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
289 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
290 self.obsoletenotrebased)
290 self.obsoletenotrebased)
291
291
292 if not result:
292 if not result:
293 # Empty state built, nothing to rebase
293 # Empty state built, nothing to rebase
294 self.ui.status(_('nothing to rebase\n'))
294 self.ui.status(_('nothing to rebase\n'))
295 return _nothingtorebase()
295 return _nothingtorebase()
296
296
297 root = min(rebaseset)
297 root = min(rebaseset)
298 if not self.keepf and not self.repo[root].mutable():
298 if not self.keepf and not self.repo[root].mutable():
299 raise error.Abort(_("can't rebase public changeset %s")
299 raise error.Abort(_("can't rebase public changeset %s")
300 % self.repo[root],
300 % self.repo[root],
301 hint=_("see 'hg help phases' for details"))
301 hint=_("see 'hg help phases' for details"))
302
302
303 (self.originalwd, self.target, self.state) = result
303 (self.originalwd, self.target, self.state) = result
304 if self.collapsef:
304 if self.collapsef:
305 self.targetancestors = self.repo.changelog.ancestors(
305 self.targetancestors = self.repo.changelog.ancestors(
306 [self.target],
306 [self.target],
307 inclusive=True)
307 inclusive=True)
308 self.external = externalparent(self.repo, self.state,
308 self.external = externalparent(self.repo, self.state,
309 self.targetancestors)
309 self.targetancestors)
310
310
311 if dest.closesbranch() and not self.keepbranchesf:
311 if dest.closesbranch() and not self.keepbranchesf:
312 self.ui.status(_('reopening closed branch head %s\n') % dest)
312 self.ui.status(_('reopening closed branch head %s\n') % dest)
313
313
314 def _performrebase(self):
314 def _performrebase(self):
315 repo, ui, opts = self.repo, self.ui, self.opts
315 repo, ui, opts = self.repo, self.ui, self.opts
316 if self.keepbranchesf:
316 if self.keepbranchesf:
317 # insert _savebranch at the start of extrafns so if
317 # insert _savebranch at the start of extrafns so if
318 # there's a user-provided extrafn it can clobber branch if
318 # there's a user-provided extrafn it can clobber branch if
319 # desired
319 # desired
320 self.extrafns.insert(0, _savebranch)
320 self.extrafns.insert(0, _savebranch)
321 if self.collapsef:
321 if self.collapsef:
322 branches = set()
322 branches = set()
323 for rev in self.state:
323 for rev in self.state:
324 branches.add(repo[rev].branch())
324 branches.add(repo[rev].branch())
325 if len(branches) > 1:
325 if len(branches) > 1:
326 raise error.Abort(_('cannot collapse multiple named '
326 raise error.Abort(_('cannot collapse multiple named '
327 'branches'))
327 'branches'))
328
328
329 # Rebase
329 # Rebase
330 if not self.targetancestors:
330 if not self.targetancestors:
331 self.targetancestors = repo.changelog.ancestors([self.target],
331 self.targetancestors = repo.changelog.ancestors([self.target],
332 inclusive=True)
332 inclusive=True)
333
333
334 # Keep track of the current bookmarks in order to reset them later
334 # Keep track of the current bookmarks in order to reset them later
335 self.currentbookmarks = repo._bookmarks.copy()
335 self.currentbookmarks = repo._bookmarks.copy()
336 self.activebookmark = self.activebookmark or repo._activebookmark
336 self.activebookmark = self.activebookmark or repo._activebookmark
337 if self.activebookmark:
337 if self.activebookmark:
338 bookmarks.deactivate(repo)
338 bookmarks.deactivate(repo)
339
339
340 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
340 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
341 cands = [k for k, v in self.state.iteritems() if v == revtodo]
341 cands = [k for k, v in self.state.iteritems() if v == revtodo]
342 total = len(cands)
342 total = len(cands)
343 pos = 0
343 pos = 0
344 for rev in sortedrevs:
344 for rev in sortedrevs:
345 ctx = repo[rev]
345 ctx = repo[rev]
346 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
346 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
347 ctx.description().split('\n', 1)[0])
347 ctx.description().split('\n', 1)[0])
348 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
348 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
349 if names:
349 if names:
350 desc += ' (%s)' % ' '.join(names)
350 desc += ' (%s)' % ' '.join(names)
351 if self.state[rev] == revtodo:
351 if self.state[rev] == revtodo:
352 pos += 1
352 pos += 1
353 ui.status(_('rebasing %s\n') % desc)
353 ui.status(_('rebasing %s\n') % desc)
354 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
354 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
355 _('changesets'), total)
355 _('changesets'), total)
356 p1, p2, base = defineparents(repo, rev, self.target,
356 p1, p2, base = defineparents(repo, rev, self.target,
357 self.state,
357 self.state,
358 self.targetancestors,
358 self.targetancestors,
359 self.obsoletenotrebased)
359 self.obsoletenotrebased)
360 storestatus(repo, self.originalwd, self.target,
360 storestatus(repo, self.originalwd, self.target,
361 self.state, self.collapsef, self.keepf,
361 self.state, self.collapsef, self.keepf,
362 self.keepbranchesf, self.external,
362 self.keepbranchesf, self.external,
363 self.activebookmark)
363 self.activebookmark)
364 storecollapsemsg(repo, self.collapsemsg)
364 storecollapsemsg(repo, self.collapsemsg)
365 if len(repo[None].parents()) == 2:
365 if len(repo[None].parents()) == 2:
366 repo.ui.debug('resuming interrupted rebase\n')
366 repo.ui.debug('resuming interrupted rebase\n')
367 else:
367 else:
368 try:
368 try:
369 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
369 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
370 'rebase')
370 'rebase')
371 stats = rebasenode(repo, rev, p1, base, self.state,
371 stats = rebasenode(repo, rev, p1, base, self.state,
372 self.collapsef, self.target)
372 self.collapsef, self.target)
373 if stats and stats[3] > 0:
373 if stats and stats[3] > 0:
374 raise error.InterventionRequired(
374 raise error.InterventionRequired(
375 _('unresolved conflicts (see hg '
375 _('unresolved conflicts (see hg '
376 'resolve, then hg rebase --continue)'))
376 'resolve, then hg rebase --continue)'))
377 finally:
377 finally:
378 ui.setconfig('ui', 'forcemerge', '', 'rebase')
378 ui.setconfig('ui', 'forcemerge', '', 'rebase')
379 if not self.collapsef:
379 if not self.collapsef:
380 merging = p2 != nullrev
380 merging = p2 != nullrev
381 editform = cmdutil.mergeeditform(merging, 'rebase')
381 editform = cmdutil.mergeeditform(merging, 'rebase')
382 editor = cmdutil.getcommiteditor(editform=editform, **opts)
382 editor = cmdutil.getcommiteditor(editform=editform, **opts)
383 newnode = concludenode(repo, rev, p1, p2,
383 newnode = concludenode(repo, rev, p1, p2,
384 extrafn=_makeextrafn(self.extrafns),
384 extrafn=_makeextrafn(self.extrafns),
385 editor=editor,
385 editor=editor,
386 keepbranches=self.keepbranchesf,
386 keepbranches=self.keepbranchesf,
387 date=self.date)
387 date=self.date)
388 else:
388 else:
389 # Skip commit if we are collapsing
389 # Skip commit if we are collapsing
390 repo.dirstate.beginparentchange()
390 repo.dirstate.beginparentchange()
391 repo.setparents(repo[p1].node())
391 repo.setparents(repo[p1].node())
392 repo.dirstate.endparentchange()
392 repo.dirstate.endparentchange()
393 newnode = None
393 newnode = None
394 # Update the state
394 # Update the state
395 if newnode is not None:
395 if newnode is not None:
396 self.state[rev] = repo[newnode].rev()
396 self.state[rev] = repo[newnode].rev()
397 ui.debug('rebased as %s\n' % short(newnode))
397 ui.debug('rebased as %s\n' % short(newnode))
398 else:
398 else:
399 if not self.collapsef:
399 if not self.collapsef:
400 ui.warn(_('note: rebase of %d:%s created no changes '
400 ui.warn(_('note: rebase of %d:%s created no changes '
401 'to commit\n') % (rev, ctx))
401 'to commit\n') % (rev, ctx))
402 self.skipped.add(rev)
402 self.skipped.add(rev)
403 self.state[rev] = p1
403 self.state[rev] = p1
404 ui.debug('next revision set to %s\n' % p1)
404 ui.debug('next revision set to %s\n' % p1)
405 elif self.state[rev] == nullmerge:
405 elif self.state[rev] == nullmerge:
406 ui.debug('ignoring null merge rebase of %s\n' % rev)
406 ui.debug('ignoring null merge rebase of %s\n' % rev)
407 elif self.state[rev] == revignored:
407 elif self.state[rev] == revignored:
408 ui.status(_('not rebasing ignored %s\n') % desc)
408 ui.status(_('not rebasing ignored %s\n') % desc)
409 elif self.state[rev] == revprecursor:
409 elif self.state[rev] == revprecursor:
410 targetctx = repo[self.obsoletenotrebased[rev]]
410 targetctx = repo[self.obsoletenotrebased[rev]]
411 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
411 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
412 targetctx.description().split('\n', 1)[0])
412 targetctx.description().split('\n', 1)[0])
413 msg = _('note: not rebasing %s, already in destination as %s\n')
413 msg = _('note: not rebasing %s, already in destination as %s\n')
414 ui.status(msg % (desc, desctarget))
414 ui.status(msg % (desc, desctarget))
415 elif self.state[rev] == revpruned:
415 elif self.state[rev] == revpruned:
416 msg = _('note: not rebasing %s, it has no successor\n')
416 msg = _('note: not rebasing %s, it has no successor\n')
417 ui.status(msg % desc)
417 ui.status(msg % desc)
418 else:
418 else:
419 ui.status(_('already rebased %s as %s\n') %
419 ui.status(_('already rebased %s as %s\n') %
420 (desc, repo[self.state[rev]]))
420 (desc, repo[self.state[rev]]))
421
421
422 ui.progress(_('rebasing'), None)
422 ui.progress(_('rebasing'), None)
423 ui.note(_('rebase merging completed\n'))
423 ui.note(_('rebase merging completed\n'))
424
424
425 def _finishrebase(self):
425 def _finishrebase(self):
426 repo, ui, opts = self.repo, self.ui, self.opts
426 repo, ui, opts = self.repo, self.ui, self.opts
427 if self.collapsef and not self.keepopen:
427 if self.collapsef and not self.keepopen:
428 p1, p2, _base = defineparents(repo, min(self.state),
428 p1, p2, _base = defineparents(repo, min(self.state),
429 self.target, self.state,
429 self.target, self.state,
430 self.targetancestors,
430 self.targetancestors,
431 self.obsoletenotrebased)
431 self.obsoletenotrebased)
432 editopt = opts.get('edit')
432 editopt = opts.get('edit')
433 editform = 'rebase.collapse'
433 editform = 'rebase.collapse'
434 if self.collapsemsg:
434 if self.collapsemsg:
435 commitmsg = self.collapsemsg
435 commitmsg = self.collapsemsg
436 else:
436 else:
437 commitmsg = 'Collapsed revision'
437 commitmsg = 'Collapsed revision'
438 for rebased in self.state:
438 for rebased in self.state:
439 if rebased not in self.skipped and\
439 if rebased not in self.skipped and\
440 self.state[rebased] > nullmerge:
440 self.state[rebased] > nullmerge:
441 commitmsg += '\n* %s' % repo[rebased].description()
441 commitmsg += '\n* %s' % repo[rebased].description()
442 editopt = True
442 editopt = True
443 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
443 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
444 revtoreuse = max(self.state)
444 revtoreuse = max(self.state)
445 newnode = concludenode(repo, revtoreuse, p1, self.external,
445 newnode = concludenode(repo, revtoreuse, p1, self.external,
446 commitmsg=commitmsg,
446 commitmsg=commitmsg,
447 extrafn=_makeextrafn(self.extrafns),
447 extrafn=_makeextrafn(self.extrafns),
448 editor=editor,
448 editor=editor,
449 keepbranches=self.keepbranchesf,
449 keepbranches=self.keepbranchesf,
450 date=self.date)
450 date=self.date)
451 if newnode is None:
451 if newnode is None:
452 newrev = self.target
452 newrev = self.target
453 else:
453 else:
454 newrev = repo[newnode].rev()
454 newrev = repo[newnode].rev()
455 for oldrev in self.state.iterkeys():
455 for oldrev in self.state.iterkeys():
456 if self.state[oldrev] > nullmerge:
456 if self.state[oldrev] > nullmerge:
457 self.state[oldrev] = newrev
457 self.state[oldrev] = newrev
458
458
459 if 'qtip' in repo.tags():
459 if 'qtip' in repo.tags():
460 updatemq(repo, self.state, self.skipped, **opts)
460 updatemq(repo, self.state, self.skipped, **opts)
461
461
462 if self.currentbookmarks:
462 if self.currentbookmarks:
463 # Nodeids are needed to reset bookmarks
463 # Nodeids are needed to reset bookmarks
464 nstate = {}
464 nstate = {}
465 for k, v in self.state.iteritems():
465 for k, v in self.state.iteritems():
466 if v > nullmerge:
466 if v > nullmerge:
467 nstate[repo[k].node()] = repo[v].node()
467 nstate[repo[k].node()] = repo[v].node()
468 elif v == revprecursor:
468 elif v == revprecursor:
469 succ = self.obsoletenotrebased[k]
469 succ = self.obsoletenotrebased[k]
470 nstate[repo[k].node()] = repo[succ].node()
470 nstate[repo[k].node()] = repo[succ].node()
471 # XXX this is the same as dest.node() for the non-continue path --
471 # XXX this is the same as dest.node() for the non-continue path --
472 # this should probably be cleaned up
472 # this should probably be cleaned up
473 targetnode = repo[self.target].node()
473 targetnode = repo[self.target].node()
474
474
475 # restore original working directory
475 # restore original working directory
476 # (we do this before stripping)
476 # (we do this before stripping)
477 newwd = self.state.get(self.originalwd, self.originalwd)
477 newwd = self.state.get(self.originalwd, self.originalwd)
478 if newwd == revprecursor:
478 if newwd == revprecursor:
479 newwd = self.obsoletenotrebased[self.originalwd]
479 newwd = self.obsoletenotrebased[self.originalwd]
480 elif newwd < 0:
480 elif newwd < 0:
481 # original directory is a parent of rebase set root or ignored
481 # original directory is a parent of rebase set root or ignored
482 newwd = self.originalwd
482 newwd = self.originalwd
483 if newwd not in [c.rev() for c in repo[None].parents()]:
483 if newwd not in [c.rev() for c in repo[None].parents()]:
484 ui.note(_("update back to initial working directory parent\n"))
484 ui.note(_("update back to initial working directory parent\n"))
485 hg.updaterepo(repo, newwd, False)
485 hg.updaterepo(repo, newwd, False)
486
486
487 if self.currentbookmarks:
487 if self.currentbookmarks:
488 with repo.transaction('bookmark') as tr:
488 with repo.transaction('bookmark') as tr:
489 updatebookmarks(repo, targetnode, nstate,
489 updatebookmarks(repo, targetnode, nstate,
490 self.currentbookmarks, tr)
490 self.currentbookmarks, tr)
491 if self.activebookmark not in repo._bookmarks:
491 if self.activebookmark not in repo._bookmarks:
492 # active bookmark was divergent one and has been deleted
492 # active bookmark was divergent one and has been deleted
493 self.activebookmark = None
493 self.activebookmark = None
494
494
495 if not self.keepf:
495 if not self.keepf:
496 collapsedas = None
496 collapsedas = None
497 if self.collapsef:
497 if self.collapsef:
498 collapsedas = newnode
498 collapsedas = newnode
499 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
499 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
500
500
501 clearstatus(repo)
501 clearstatus(repo)
502 clearcollapsemsg(repo)
502 clearcollapsemsg(repo)
503
503
504 ui.note(_("rebase completed\n"))
504 ui.note(_("rebase completed\n"))
505 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
505 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
506 if self.skipped:
506 if self.skipped:
507 skippedlen = len(self.skipped)
507 skippedlen = len(self.skipped)
508 ui.note(_("%d revisions have been skipped\n") % skippedlen)
508 ui.note(_("%d revisions have been skipped\n") % skippedlen)
509
509
510 if (self.activebookmark and
510 if (self.activebookmark and
511 repo['.'].node() == repo._bookmarks[self.activebookmark]):
511 repo['.'].node() == repo._bookmarks[self.activebookmark]):
512 bookmarks.activate(repo, self.activebookmark)
512 bookmarks.activate(repo, self.activebookmark)
513
513
514 @command('rebase',
514 @command('rebase',
515 [('s', 'source', '',
515 [('s', 'source', '',
516 _('rebase the specified changeset and descendants'), _('REV')),
516 _('rebase the specified changeset and descendants'), _('REV')),
517 ('b', 'base', '',
517 ('b', 'base', '',
518 _('rebase everything from branching point of specified changeset'),
518 _('rebase everything from branching point of specified changeset'),
519 _('REV')),
519 _('REV')),
520 ('r', 'rev', [],
520 ('r', 'rev', [],
521 _('rebase these revisions'),
521 _('rebase these revisions'),
522 _('REV')),
522 _('REV')),
523 ('d', 'dest', '',
523 ('d', 'dest', '',
524 _('rebase onto the specified changeset'), _('REV')),
524 _('rebase onto the specified changeset'), _('REV')),
525 ('', 'collapse', False, _('collapse the rebased changesets')),
525 ('', 'collapse', False, _('collapse the rebased changesets')),
526 ('m', 'message', '',
526 ('m', 'message', '',
527 _('use text as collapse commit message'), _('TEXT')),
527 _('use text as collapse commit message'), _('TEXT')),
528 ('e', 'edit', False, _('invoke editor on commit messages')),
528 ('e', 'edit', False, _('invoke editor on commit messages')),
529 ('l', 'logfile', '',
529 ('l', 'logfile', '',
530 _('read collapse commit message from file'), _('FILE')),
530 _('read collapse commit message from file'), _('FILE')),
531 ('k', 'keep', False, _('keep original changesets')),
531 ('k', 'keep', False, _('keep original changesets')),
532 ('', 'keepbranches', False, _('keep original branch names')),
532 ('', 'keepbranches', False, _('keep original branch names')),
533 ('D', 'detach', False, _('(DEPRECATED)')),
533 ('D', 'detach', False, _('(DEPRECATED)')),
534 ('i', 'interactive', False, _('(DEPRECATED)')),
534 ('i', 'interactive', False, _('(DEPRECATED)')),
535 ('t', 'tool', '', _('specify merge tool')),
535 ('t', 'tool', '', _('specify merge tool')),
536 ('c', 'continue', False, _('continue an interrupted rebase')),
536 ('c', 'continue', False, _('continue an interrupted rebase')),
537 ('a', 'abort', False, _('abort an interrupted rebase'))] +
537 ('a', 'abort', False, _('abort an interrupted rebase'))] +
538 templateopts,
538 templateopts,
539 _('[-s REV | -b REV] [-d REV] [OPTION]'))
539 _('[-s REV | -b REV] [-d REV] [OPTION]'))
540 def rebase(ui, repo, **opts):
540 def rebase(ui, repo, **opts):
541 """move changeset (and descendants) to a different branch
541 """move changeset (and descendants) to a different branch
542
542
543 Rebase uses repeated merging to graft changesets from one part of
543 Rebase uses repeated merging to graft changesets from one part of
544 history (the source) onto another (the destination). This can be
544 history (the source) onto another (the destination). This can be
545 useful for linearizing *local* changes relative to a master
545 useful for linearizing *local* changes relative to a master
546 development tree.
546 development tree.
547
547
548 Published commits cannot be rebased (see :hg:`help phases`).
548 Published commits cannot be rebased (see :hg:`help phases`).
549 To copy commits, see :hg:`help graft`.
549 To copy commits, see :hg:`help graft`.
550
550
551 If you don't specify a destination changeset (``-d/--dest``), rebase
551 If you don't specify a destination changeset (``-d/--dest``), rebase
552 will use the same logic as :hg:`merge` to pick a destination. if
552 will use the same logic as :hg:`merge` to pick a destination. if
553 the current branch contains exactly one other head, the other head
553 the current branch contains exactly one other head, the other head
554 is merged with by default. Otherwise, an explicit revision with
554 is merged with by default. Otherwise, an explicit revision with
555 which to merge with must be provided. (destination changeset is not
555 which to merge with must be provided. (destination changeset is not
556 modified by rebasing, but new changesets are added as its
556 modified by rebasing, but new changesets are added as its
557 descendants.)
557 descendants.)
558
558
559 Here are the ways to select changesets:
559 Here are the ways to select changesets:
560
560
561 1. Explicitly select them using ``--rev``.
561 1. Explicitly select them using ``--rev``.
562
562
563 2. Use ``--source`` to select a root changeset and include all of its
563 2. Use ``--source`` to select a root changeset and include all of its
564 descendants.
564 descendants.
565
565
566 3. Use ``--base`` to select a changeset; rebase will find ancestors
566 3. Use ``--base`` to select a changeset; rebase will find ancestors
567 and their descendants which are not also ancestors of the destination.
567 and their descendants which are not also ancestors of the destination.
568
568
569 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
569 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
570 rebase will use ``--base .`` as above.
570 rebase will use ``--base .`` as above.
571
571
572 Rebase will destroy original changesets unless you use ``--keep``.
572 Rebase will destroy original changesets unless you use ``--keep``.
573 It will also move your bookmarks (even if you do).
573 It will also move your bookmarks (even if you do).
574
574
575 Some changesets may be dropped if they do not contribute changes
575 Some changesets may be dropped if they do not contribute changes
576 (e.g. merges from the destination branch).
576 (e.g. merges from the destination branch).
577
577
578 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
578 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
579 a named branch with two heads. You will need to explicitly specify source
579 a named branch with two heads. You will need to explicitly specify source
580 and/or destination.
580 and/or destination.
581
581
582 If you need to use a tool to automate merge/conflict decisions, you
582 If you need to use a tool to automate merge/conflict decisions, you
583 can specify one with ``--tool``, see :hg:`help merge-tools`.
583 can specify one with ``--tool``, see :hg:`help merge-tools`.
584 As a caveat: the tool will not be used to mediate when a file was
584 As a caveat: the tool will not be used to mediate when a file was
585 deleted, there is no hook presently available for this.
585 deleted, there is no hook presently available for this.
586
586
587 If a rebase is interrupted to manually resolve a conflict, it can be
587 If a rebase is interrupted to manually resolve a conflict, it can be
588 continued with --continue/-c or aborted with --abort/-a.
588 continued with --continue/-c or aborted with --abort/-a.
589
589
590 .. container:: verbose
590 .. container:: verbose
591
591
592 Examples:
592 Examples:
593
593
594 - move "local changes" (current commit back to branching point)
594 - move "local changes" (current commit back to branching point)
595 to the current branch tip after a pull::
595 to the current branch tip after a pull::
596
596
597 hg rebase
597 hg rebase
598
598
599 - move a single changeset to the stable branch::
599 - move a single changeset to the stable branch::
600
600
601 hg rebase -r 5f493448 -d stable
601 hg rebase -r 5f493448 -d stable
602
602
603 - splice a commit and all its descendants onto another part of history::
603 - splice a commit and all its descendants onto another part of history::
604
604
605 hg rebase --source c0c3 --dest 4cf9
605 hg rebase --source c0c3 --dest 4cf9
606
606
607 - rebase everything on a branch marked by a bookmark onto the
607 - rebase everything on a branch marked by a bookmark onto the
608 default branch::
608 default branch::
609
609
610 hg rebase --base myfeature --dest default
610 hg rebase --base myfeature --dest default
611
611
612 - collapse a sequence of changes into a single commit::
612 - collapse a sequence of changes into a single commit::
613
613
614 hg rebase --collapse -r 1520:1525 -d .
614 hg rebase --collapse -r 1520:1525 -d .
615
615
616 - move a named branch while preserving its name::
616 - move a named branch while preserving its name::
617
617
618 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
618 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
619
619
620 Returns 0 on success, 1 if nothing to rebase or there are
620 Returns 0 on success, 1 if nothing to rebase or there are
621 unresolved conflicts.
621 unresolved conflicts.
622
622
623 """
623 """
624 rbsrt = rebaseruntime(repo, ui, opts)
624 rbsrt = rebaseruntime(repo, ui, opts)
625
625
626 lock = wlock = None
626 lock = wlock = None
627 try:
627 try:
628 wlock = repo.wlock()
628 wlock = repo.wlock()
629 lock = repo.lock()
629 lock = repo.lock()
630
630
631 # Validate input and define rebasing points
631 # Validate input and define rebasing points
632 destf = opts.get('dest', None)
632 destf = opts.get('dest', None)
633 srcf = opts.get('source', None)
633 srcf = opts.get('source', None)
634 basef = opts.get('base', None)
634 basef = opts.get('base', None)
635 revf = opts.get('rev', [])
635 revf = opts.get('rev', [])
636 # search default destination in this space
636 # search default destination in this space
637 # used in the 'hg pull --rebase' case, see issue 5214.
637 # used in the 'hg pull --rebase' case, see issue 5214.
638 destspace = opts.get('_destspace')
638 destspace = opts.get('_destspace')
639 contf = opts.get('continue')
639 contf = opts.get('continue')
640 abortf = opts.get('abort')
640 abortf = opts.get('abort')
641 if opts.get('interactive'):
641 if opts.get('interactive'):
642 try:
642 try:
643 if extensions.find('histedit'):
643 if extensions.find('histedit'):
644 enablehistedit = ''
644 enablehistedit = ''
645 except KeyError:
645 except KeyError:
646 enablehistedit = " --config extensions.histedit="
646 enablehistedit = " --config extensions.histedit="
647 help = "hg%s help -e histedit" % enablehistedit
647 help = "hg%s help -e histedit" % enablehistedit
648 msg = _("interactive history editing is supported by the "
648 msg = _("interactive history editing is supported by the "
649 "'histedit' extension (see \"%s\")") % help
649 "'histedit' extension (see \"%s\")") % help
650 raise error.Abort(msg)
650 raise error.Abort(msg)
651
651
652 if rbsrt.collapsemsg and not rbsrt.collapsef:
652 if rbsrt.collapsemsg and not rbsrt.collapsef:
653 raise error.Abort(
653 raise error.Abort(
654 _('message can only be specified with collapse'))
654 _('message can only be specified with collapse'))
655
655
656 if contf or abortf:
656 if contf or abortf:
657 if contf and abortf:
657 if contf and abortf:
658 raise error.Abort(_('cannot use both abort and continue'))
658 raise error.Abort(_('cannot use both abort and continue'))
659 if rbsrt.collapsef:
659 if rbsrt.collapsef:
660 raise error.Abort(
660 raise error.Abort(
661 _('cannot use collapse with continue or abort'))
661 _('cannot use collapse with continue or abort'))
662 if srcf or basef or destf:
662 if srcf or basef or destf:
663 raise error.Abort(
663 raise error.Abort(
664 _('abort and continue do not allow specifying revisions'))
664 _('abort and continue do not allow specifying revisions'))
665 if abortf and opts.get('tool', False):
665 if abortf and opts.get('tool', False):
666 ui.warn(_('tool option will be ignored\n'))
666 ui.warn(_('tool option will be ignored\n'))
667 if contf:
667 if contf:
668 ms = mergemod.mergestate.read(repo)
668 ms = mergemod.mergestate.read(repo)
669 mergeutil.checkunresolved(ms)
669 mergeutil.checkunresolved(ms)
670
670
671 retcode = rbsrt._prepareabortorcontinue(abortf)
671 retcode = rbsrt._prepareabortorcontinue(abortf)
672 if retcode is not None:
672 if retcode is not None:
673 return retcode
673 return retcode
674 else:
674 else:
675 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
675 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
676 destspace=destspace)
676 destspace=destspace)
677 retcode = rbsrt._preparenewrebase(dest, rebaseset)
677 retcode = rbsrt._preparenewrebase(dest, rebaseset)
678 if retcode is not None:
678 if retcode is not None:
679 return retcode
679 return retcode
680
680
681 rbsrt._performrebase()
681 rbsrt._performrebase()
682 rbsrt._finishrebase()
682 rbsrt._finishrebase()
683 finally:
683 finally:
684 release(lock, wlock)
684 release(lock, wlock)
685
685
686 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[],
686 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[],
687 destspace=None):
687 destspace=None):
688 """use revisions argument to define destination and rebase set
688 """use revisions argument to define destination and rebase set
689 """
689 """
690 # destspace is here to work around issues with `hg pull --rebase` see
690 # destspace is here to work around issues with `hg pull --rebase` see
691 # issue5214 for details
691 # issue5214 for details
692 if srcf and basef:
692 if srcf and basef:
693 raise error.Abort(_('cannot specify both a source and a base'))
693 raise error.Abort(_('cannot specify both a source and a base'))
694 if revf and basef:
694 if revf and basef:
695 raise error.Abort(_('cannot specify both a revision and a base'))
695 raise error.Abort(_('cannot specify both a revision and a base'))
696 if revf and srcf:
696 if revf and srcf:
697 raise error.Abort(_('cannot specify both a revision and a source'))
697 raise error.Abort(_('cannot specify both a revision and a source'))
698
698
699 cmdutil.checkunfinished(repo)
699 cmdutil.checkunfinished(repo)
700 cmdutil.bailifchanged(repo)
700 cmdutil.bailifchanged(repo)
701
701
702 if destf:
702 if destf:
703 dest = scmutil.revsingle(repo, destf)
703 dest = scmutil.revsingle(repo, destf)
704
704
705 if revf:
705 if revf:
706 rebaseset = scmutil.revrange(repo, revf)
706 rebaseset = scmutil.revrange(repo, revf)
707 if not rebaseset:
707 if not rebaseset:
708 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
708 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
709 return None, None
709 return None, None
710 elif srcf:
710 elif srcf:
711 src = scmutil.revrange(repo, [srcf])
711 src = scmutil.revrange(repo, [srcf])
712 if not src:
712 if not src:
713 ui.status(_('empty "source" revision set - nothing to rebase\n'))
713 ui.status(_('empty "source" revision set - nothing to rebase\n'))
714 return None, None
714 return None, None
715 rebaseset = repo.revs('(%ld)::', src)
715 rebaseset = repo.revs('(%ld)::', src)
716 assert rebaseset
716 assert rebaseset
717 else:
717 else:
718 base = scmutil.revrange(repo, [basef or '.'])
718 base = scmutil.revrange(repo, [basef or '.'])
719 if not base:
719 if not base:
720 ui.status(_('empty "base" revision set - '
720 ui.status(_('empty "base" revision set - '
721 "can't compute rebase set\n"))
721 "can't compute rebase set\n"))
722 return None, None
722 return None, None
723 if not destf:
723 if not destf:
724 dest = repo[_destrebase(repo, base, destspace=destspace)]
724 dest = repo[_destrebase(repo, base, destspace=destspace)]
725 destf = str(dest)
725 destf = str(dest)
726
726
727 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
727 roots = [] # selected children of branching points
728 if commonanc is not None:
728 bpbase = {} # {branchingpoint: [origbase]}
729 rebaseset = repo.revs('(%d::(%ld) - %d)::',
729 for b in base: # group bases by branching points
730 commonanc, base, commonanc)
730 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
731 else:
731 bpbase[bp] = bpbase.get(bp, []) + [b]
732 rebaseset = []
732 if None in bpbase:
733 # emulate the old behavior, showing "nothing to rebase" (a better
734 # behavior may be abort with "cannot find branching point" error)
735 bpbase.clear()
736 for bp, bs in bpbase.iteritems(): # calculate roots
737 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
738
739 rebaseset = repo.revs('%ld::', roots)
733
740
734 if not rebaseset:
741 if not rebaseset:
735 # transform to list because smartsets are not comparable to
742 # transform to list because smartsets are not comparable to
736 # lists. This should be improved to honor laziness of
743 # lists. This should be improved to honor laziness of
737 # smartset.
744 # smartset.
738 if list(base) == [dest.rev()]:
745 if list(base) == [dest.rev()]:
739 if basef:
746 if basef:
740 ui.status(_('nothing to rebase - %s is both "base"'
747 ui.status(_('nothing to rebase - %s is both "base"'
741 ' and destination\n') % dest)
748 ' and destination\n') % dest)
742 else:
749 else:
743 ui.status(_('nothing to rebase - working directory '
750 ui.status(_('nothing to rebase - working directory '
744 'parent is also destination\n'))
751 'parent is also destination\n'))
745 elif not repo.revs('%ld - ::%d', base, dest):
752 elif not repo.revs('%ld - ::%d', base, dest):
746 if basef:
753 if basef:
747 ui.status(_('nothing to rebase - "base" %s is '
754 ui.status(_('nothing to rebase - "base" %s is '
748 'already an ancestor of destination '
755 'already an ancestor of destination '
749 '%s\n') %
756 '%s\n') %
750 ('+'.join(str(repo[r]) for r in base),
757 ('+'.join(str(repo[r]) for r in base),
751 dest))
758 dest))
752 else:
759 else:
753 ui.status(_('nothing to rebase - working '
760 ui.status(_('nothing to rebase - working '
754 'directory parent is already an '
761 'directory parent is already an '
755 'ancestor of destination %s\n') % dest)
762 'ancestor of destination %s\n') % dest)
756 else: # can it happen?
763 else: # can it happen?
757 ui.status(_('nothing to rebase from %s to %s\n') %
764 ui.status(_('nothing to rebase from %s to %s\n') %
758 ('+'.join(str(repo[r]) for r in base), dest))
765 ('+'.join(str(repo[r]) for r in base), dest))
759 return None, None
766 return None, None
760
767
761 if not destf:
768 if not destf:
762 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
769 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
763 destf = str(dest)
770 destf = str(dest)
764
771
765 return dest, rebaseset
772 return dest, rebaseset
766
773
767 def externalparent(repo, state, targetancestors):
774 def externalparent(repo, state, targetancestors):
768 """Return the revision that should be used as the second parent
775 """Return the revision that should be used as the second parent
769 when the revisions in state is collapsed on top of targetancestors.
776 when the revisions in state is collapsed on top of targetancestors.
770 Abort if there is more than one parent.
777 Abort if there is more than one parent.
771 """
778 """
772 parents = set()
779 parents = set()
773 source = min(state)
780 source = min(state)
774 for rev in state:
781 for rev in state:
775 if rev == source:
782 if rev == source:
776 continue
783 continue
777 for p in repo[rev].parents():
784 for p in repo[rev].parents():
778 if (p.rev() not in state
785 if (p.rev() not in state
779 and p.rev() not in targetancestors):
786 and p.rev() not in targetancestors):
780 parents.add(p.rev())
787 parents.add(p.rev())
781 if not parents:
788 if not parents:
782 return nullrev
789 return nullrev
783 if len(parents) == 1:
790 if len(parents) == 1:
784 return parents.pop()
791 return parents.pop()
785 raise error.Abort(_('unable to collapse on top of %s, there is more '
792 raise error.Abort(_('unable to collapse on top of %s, there is more '
786 'than one external parent: %s') %
793 'than one external parent: %s') %
787 (max(targetancestors),
794 (max(targetancestors),
788 ', '.join(str(p) for p in sorted(parents))))
795 ', '.join(str(p) for p in sorted(parents))))
789
796
790 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
797 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
791 keepbranches=False, date=None):
798 keepbranches=False, date=None):
792 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
799 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
793 but also store useful information in extra.
800 but also store useful information in extra.
794 Return node of committed revision.'''
801 Return node of committed revision.'''
795 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
802 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
796 try:
803 try:
797 repo.setparents(repo[p1].node(), repo[p2].node())
804 repo.setparents(repo[p1].node(), repo[p2].node())
798 ctx = repo[rev]
805 ctx = repo[rev]
799 if commitmsg is None:
806 if commitmsg is None:
800 commitmsg = ctx.description()
807 commitmsg = ctx.description()
801 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
808 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
802 extra = {'rebase_source': ctx.hex()}
809 extra = {'rebase_source': ctx.hex()}
803 if extrafn:
810 if extrafn:
804 extrafn(ctx, extra)
811 extrafn(ctx, extra)
805
812
806 backup = repo.ui.backupconfig('phases', 'new-commit')
813 backup = repo.ui.backupconfig('phases', 'new-commit')
807 try:
814 try:
808 targetphase = max(ctx.phase(), phases.draft)
815 targetphase = max(ctx.phase(), phases.draft)
809 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
816 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
810 if keepbranch:
817 if keepbranch:
811 repo.ui.setconfig('ui', 'allowemptycommit', True)
818 repo.ui.setconfig('ui', 'allowemptycommit', True)
812 # Commit might fail if unresolved files exist
819 # Commit might fail if unresolved files exist
813 if date is None:
820 if date is None:
814 date = ctx.date()
821 date = ctx.date()
815 newnode = repo.commit(text=commitmsg, user=ctx.user(),
822 newnode = repo.commit(text=commitmsg, user=ctx.user(),
816 date=date, extra=extra, editor=editor)
823 date=date, extra=extra, editor=editor)
817 finally:
824 finally:
818 repo.ui.restoreconfig(backup)
825 repo.ui.restoreconfig(backup)
819
826
820 repo.dirstate.setbranch(repo[newnode].branch())
827 repo.dirstate.setbranch(repo[newnode].branch())
821 dsguard.close()
828 dsguard.close()
822 return newnode
829 return newnode
823 finally:
830 finally:
824 release(dsguard)
831 release(dsguard)
825
832
826 def rebasenode(repo, rev, p1, base, state, collapse, target):
833 def rebasenode(repo, rev, p1, base, state, collapse, target):
827 'Rebase a single revision rev on top of p1 using base as merge ancestor'
834 'Rebase a single revision rev on top of p1 using base as merge ancestor'
828 # Merge phase
835 # Merge phase
829 # Update to target and merge it with local
836 # Update to target and merge it with local
830 if repo['.'].rev() != p1:
837 if repo['.'].rev() != p1:
831 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
838 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
832 mergemod.update(repo, p1, False, True)
839 mergemod.update(repo, p1, False, True)
833 else:
840 else:
834 repo.ui.debug(" already in target\n")
841 repo.ui.debug(" already in target\n")
835 repo.dirstate.write(repo.currenttransaction())
842 repo.dirstate.write(repo.currenttransaction())
836 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
843 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
837 if base is not None:
844 if base is not None:
838 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
845 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
839 # When collapsing in-place, the parent is the common ancestor, we
846 # When collapsing in-place, the parent is the common ancestor, we
840 # have to allow merging with it.
847 # have to allow merging with it.
841 stats = mergemod.update(repo, rev, True, True, base, collapse,
848 stats = mergemod.update(repo, rev, True, True, base, collapse,
842 labels=['dest', 'source'])
849 labels=['dest', 'source'])
843 if collapse:
850 if collapse:
844 copies.duplicatecopies(repo, rev, target)
851 copies.duplicatecopies(repo, rev, target)
845 else:
852 else:
846 # If we're not using --collapse, we need to
853 # If we're not using --collapse, we need to
847 # duplicate copies between the revision we're
854 # duplicate copies between the revision we're
848 # rebasing and its first parent, but *not*
855 # rebasing and its first parent, but *not*
849 # duplicate any copies that have already been
856 # duplicate any copies that have already been
850 # performed in the destination.
857 # performed in the destination.
851 p1rev = repo[rev].p1().rev()
858 p1rev = repo[rev].p1().rev()
852 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
859 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
853 return stats
860 return stats
854
861
855 def nearestrebased(repo, rev, state):
862 def nearestrebased(repo, rev, state):
856 """return the nearest ancestors of rev in the rebase result"""
863 """return the nearest ancestors of rev in the rebase result"""
857 rebased = [r for r in state if state[r] > nullmerge]
864 rebased = [r for r in state if state[r] > nullmerge]
858 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
865 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
859 if candidates:
866 if candidates:
860 return state[candidates.first()]
867 return state[candidates.first()]
861 else:
868 else:
862 return None
869 return None
863
870
864 def _checkobsrebase(repo, ui,
871 def _checkobsrebase(repo, ui,
865 rebaseobsrevs,
872 rebaseobsrevs,
866 rebasesetrevs,
873 rebasesetrevs,
867 rebaseobsskipped):
874 rebaseobsskipped):
868 """
875 """
869 Abort if rebase will create divergence or rebase is noop because of markers
876 Abort if rebase will create divergence or rebase is noop because of markers
870
877
871 `rebaseobsrevs`: set of obsolete revision in source
878 `rebaseobsrevs`: set of obsolete revision in source
872 `rebasesetrevs`: set of revisions to be rebased from source
879 `rebasesetrevs`: set of revisions to be rebased from source
873 `rebaseobsskipped`: set of revisions from source skipped because they have
880 `rebaseobsskipped`: set of revisions from source skipped because they have
874 successors in destination
881 successors in destination
875 """
882 """
876 # Obsolete node with successors not in dest leads to divergence
883 # Obsolete node with successors not in dest leads to divergence
877 divergenceok = ui.configbool('experimental',
884 divergenceok = ui.configbool('experimental',
878 'allowdivergence')
885 'allowdivergence')
879 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
886 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
880
887
881 if divergencebasecandidates and not divergenceok:
888 if divergencebasecandidates and not divergenceok:
882 divhashes = (str(repo[r])
889 divhashes = (str(repo[r])
883 for r in divergencebasecandidates)
890 for r in divergencebasecandidates)
884 msg = _("this rebase will cause "
891 msg = _("this rebase will cause "
885 "divergences from: %s")
892 "divergences from: %s")
886 h = _("to force the rebase please set "
893 h = _("to force the rebase please set "
887 "experimental.allowdivergence=True")
894 "experimental.allowdivergence=True")
888 raise error.Abort(msg % (",".join(divhashes),), hint=h)
895 raise error.Abort(msg % (",".join(divhashes),), hint=h)
889
896
890 def defineparents(repo, rev, target, state, targetancestors,
897 def defineparents(repo, rev, target, state, targetancestors,
891 obsoletenotrebased):
898 obsoletenotrebased):
892 'Return the new parent relationship of the revision that will be rebased'
899 'Return the new parent relationship of the revision that will be rebased'
893 parents = repo[rev].parents()
900 parents = repo[rev].parents()
894 p1 = p2 = nullrev
901 p1 = p2 = nullrev
895 rp1 = None
902 rp1 = None
896
903
897 p1n = parents[0].rev()
904 p1n = parents[0].rev()
898 if p1n in targetancestors:
905 if p1n in targetancestors:
899 p1 = target
906 p1 = target
900 elif p1n in state:
907 elif p1n in state:
901 if state[p1n] == nullmerge:
908 if state[p1n] == nullmerge:
902 p1 = target
909 p1 = target
903 elif state[p1n] in revskipped:
910 elif state[p1n] in revskipped:
904 p1 = nearestrebased(repo, p1n, state)
911 p1 = nearestrebased(repo, p1n, state)
905 if p1 is None:
912 if p1 is None:
906 p1 = target
913 p1 = target
907 else:
914 else:
908 p1 = state[p1n]
915 p1 = state[p1n]
909 else: # p1n external
916 else: # p1n external
910 p1 = target
917 p1 = target
911 p2 = p1n
918 p2 = p1n
912
919
913 if len(parents) == 2 and parents[1].rev() not in targetancestors:
920 if len(parents) == 2 and parents[1].rev() not in targetancestors:
914 p2n = parents[1].rev()
921 p2n = parents[1].rev()
915 # interesting second parent
922 # interesting second parent
916 if p2n in state:
923 if p2n in state:
917 if p1 == target: # p1n in targetancestors or external
924 if p1 == target: # p1n in targetancestors or external
918 p1 = state[p2n]
925 p1 = state[p2n]
919 if p1 == revprecursor:
926 if p1 == revprecursor:
920 rp1 = obsoletenotrebased[p2n]
927 rp1 = obsoletenotrebased[p2n]
921 elif state[p2n] in revskipped:
928 elif state[p2n] in revskipped:
922 p2 = nearestrebased(repo, p2n, state)
929 p2 = nearestrebased(repo, p2n, state)
923 if p2 is None:
930 if p2 is None:
924 # no ancestors rebased yet, detach
931 # no ancestors rebased yet, detach
925 p2 = target
932 p2 = target
926 else:
933 else:
927 p2 = state[p2n]
934 p2 = state[p2n]
928 else: # p2n external
935 else: # p2n external
929 if p2 != nullrev: # p1n external too => rev is a merged revision
936 if p2 != nullrev: # p1n external too => rev is a merged revision
930 raise error.Abort(_('cannot use revision %d as base, result '
937 raise error.Abort(_('cannot use revision %d as base, result '
931 'would have 3 parents') % rev)
938 'would have 3 parents') % rev)
932 p2 = p2n
939 p2 = p2n
933 repo.ui.debug(" future parents are %d and %d\n" %
940 repo.ui.debug(" future parents are %d and %d\n" %
934 (repo[rp1 or p1].rev(), repo[p2].rev()))
941 (repo[rp1 or p1].rev(), repo[p2].rev()))
935
942
936 if not any(p.rev() in state for p in parents):
943 if not any(p.rev() in state for p in parents):
937 # Case (1) root changeset of a non-detaching rebase set.
944 # Case (1) root changeset of a non-detaching rebase set.
938 # Let the merge mechanism find the base itself.
945 # Let the merge mechanism find the base itself.
939 base = None
946 base = None
940 elif not repo[rev].p2():
947 elif not repo[rev].p2():
941 # Case (2) detaching the node with a single parent, use this parent
948 # Case (2) detaching the node with a single parent, use this parent
942 base = repo[rev].p1().rev()
949 base = repo[rev].p1().rev()
943 else:
950 else:
944 # Assuming there is a p1, this is the case where there also is a p2.
951 # Assuming there is a p1, this is the case where there also is a p2.
945 # We are thus rebasing a merge and need to pick the right merge base.
952 # We are thus rebasing a merge and need to pick the right merge base.
946 #
953 #
947 # Imagine we have:
954 # Imagine we have:
948 # - M: current rebase revision in this step
955 # - M: current rebase revision in this step
949 # - A: one parent of M
956 # - A: one parent of M
950 # - B: other parent of M
957 # - B: other parent of M
951 # - D: destination of this merge step (p1 var)
958 # - D: destination of this merge step (p1 var)
952 #
959 #
953 # Consider the case where D is a descendant of A or B and the other is
960 # Consider the case where D is a descendant of A or B and the other is
954 # 'outside'. In this case, the right merge base is the D ancestor.
961 # 'outside'. In this case, the right merge base is the D ancestor.
955 #
962 #
956 # An informal proof, assuming A is 'outside' and B is the D ancestor:
963 # An informal proof, assuming A is 'outside' and B is the D ancestor:
957 #
964 #
958 # If we pick B as the base, the merge involves:
965 # If we pick B as the base, the merge involves:
959 # - changes from B to M (actual changeset payload)
966 # - changes from B to M (actual changeset payload)
960 # - changes from B to D (induced by rebase) as D is a rebased
967 # - changes from B to D (induced by rebase) as D is a rebased
961 # version of B)
968 # version of B)
962 # Which exactly represent the rebase operation.
969 # Which exactly represent the rebase operation.
963 #
970 #
964 # If we pick A as the base, the merge involves:
971 # If we pick A as the base, the merge involves:
965 # - changes from A to M (actual changeset payload)
972 # - changes from A to M (actual changeset payload)
966 # - changes from A to D (with include changes between unrelated A and B
973 # - changes from A to D (with include changes between unrelated A and B
967 # plus changes induced by rebase)
974 # plus changes induced by rebase)
968 # Which does not represent anything sensible and creates a lot of
975 # Which does not represent anything sensible and creates a lot of
969 # conflicts. A is thus not the right choice - B is.
976 # conflicts. A is thus not the right choice - B is.
970 #
977 #
971 # Note: The base found in this 'proof' is only correct in the specified
978 # Note: The base found in this 'proof' is only correct in the specified
972 # case. This base does not make sense if is not D a descendant of A or B
979 # case. This base does not make sense if is not D a descendant of A or B
973 # or if the other is not parent 'outside' (especially not if the other
980 # or if the other is not parent 'outside' (especially not if the other
974 # parent has been rebased). The current implementation does not
981 # parent has been rebased). The current implementation does not
975 # make it feasible to consider different cases separately. In these
982 # make it feasible to consider different cases separately. In these
976 # other cases we currently just leave it to the user to correctly
983 # other cases we currently just leave it to the user to correctly
977 # resolve an impossible merge using a wrong ancestor.
984 # resolve an impossible merge using a wrong ancestor.
978 #
985 #
979 # xx, p1 could be -4, and both parents could probably be -4...
986 # xx, p1 could be -4, and both parents could probably be -4...
980 for p in repo[rev].parents():
987 for p in repo[rev].parents():
981 if state.get(p.rev()) == p1:
988 if state.get(p.rev()) == p1:
982 base = p.rev()
989 base = p.rev()
983 break
990 break
984 else: # fallback when base not found
991 else: # fallback when base not found
985 base = None
992 base = None
986
993
987 # Raise because this function is called wrong (see issue 4106)
994 # Raise because this function is called wrong (see issue 4106)
988 raise AssertionError('no base found to rebase on '
995 raise AssertionError('no base found to rebase on '
989 '(defineparents called wrong)')
996 '(defineparents called wrong)')
990 return rp1 or p1, p2, base
997 return rp1 or p1, p2, base
991
998
992 def isagitpatch(repo, patchname):
999 def isagitpatch(repo, patchname):
993 'Return true if the given patch is in git format'
1000 'Return true if the given patch is in git format'
994 mqpatch = os.path.join(repo.mq.path, patchname)
1001 mqpatch = os.path.join(repo.mq.path, patchname)
995 for line in patch.linereader(file(mqpatch, 'rb')):
1002 for line in patch.linereader(file(mqpatch, 'rb')):
996 if line.startswith('diff --git'):
1003 if line.startswith('diff --git'):
997 return True
1004 return True
998 return False
1005 return False
999
1006
1000 def updatemq(repo, state, skipped, **opts):
1007 def updatemq(repo, state, skipped, **opts):
1001 'Update rebased mq patches - finalize and then import them'
1008 'Update rebased mq patches - finalize and then import them'
1002 mqrebase = {}
1009 mqrebase = {}
1003 mq = repo.mq
1010 mq = repo.mq
1004 original_series = mq.fullseries[:]
1011 original_series = mq.fullseries[:]
1005 skippedpatches = set()
1012 skippedpatches = set()
1006
1013
1007 for p in mq.applied:
1014 for p in mq.applied:
1008 rev = repo[p.node].rev()
1015 rev = repo[p.node].rev()
1009 if rev in state:
1016 if rev in state:
1010 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1017 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1011 (rev, p.name))
1018 (rev, p.name))
1012 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1019 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1013 else:
1020 else:
1014 # Applied but not rebased, not sure this should happen
1021 # Applied but not rebased, not sure this should happen
1015 skippedpatches.add(p.name)
1022 skippedpatches.add(p.name)
1016
1023
1017 if mqrebase:
1024 if mqrebase:
1018 mq.finish(repo, mqrebase.keys())
1025 mq.finish(repo, mqrebase.keys())
1019
1026
1020 # We must start import from the newest revision
1027 # We must start import from the newest revision
1021 for rev in sorted(mqrebase, reverse=True):
1028 for rev in sorted(mqrebase, reverse=True):
1022 if rev not in skipped:
1029 if rev not in skipped:
1023 name, isgit = mqrebase[rev]
1030 name, isgit = mqrebase[rev]
1024 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1031 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1025 (name, state[rev], repo[state[rev]]))
1032 (name, state[rev], repo[state[rev]]))
1026 mq.qimport(repo, (), patchname=name, git=isgit,
1033 mq.qimport(repo, (), patchname=name, git=isgit,
1027 rev=[str(state[rev])])
1034 rev=[str(state[rev])])
1028 else:
1035 else:
1029 # Rebased and skipped
1036 # Rebased and skipped
1030 skippedpatches.add(mqrebase[rev][0])
1037 skippedpatches.add(mqrebase[rev][0])
1031
1038
1032 # Patches were either applied and rebased and imported in
1039 # Patches were either applied and rebased and imported in
1033 # order, applied and removed or unapplied. Discard the removed
1040 # order, applied and removed or unapplied. Discard the removed
1034 # ones while preserving the original series order and guards.
1041 # ones while preserving the original series order and guards.
1035 newseries = [s for s in original_series
1042 newseries = [s for s in original_series
1036 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1043 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1037 mq.fullseries[:] = newseries
1044 mq.fullseries[:] = newseries
1038 mq.seriesdirty = True
1045 mq.seriesdirty = True
1039 mq.savedirty()
1046 mq.savedirty()
1040
1047
1041 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1048 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1042 'Move bookmarks to their correct changesets, and delete divergent ones'
1049 'Move bookmarks to their correct changesets, and delete divergent ones'
1043 marks = repo._bookmarks
1050 marks = repo._bookmarks
1044 for k, v in originalbookmarks.iteritems():
1051 for k, v in originalbookmarks.iteritems():
1045 if v in nstate:
1052 if v in nstate:
1046 # update the bookmarks for revs that have moved
1053 # update the bookmarks for revs that have moved
1047 marks[k] = nstate[v]
1054 marks[k] = nstate[v]
1048 bookmarks.deletedivergent(repo, [targetnode], k)
1055 bookmarks.deletedivergent(repo, [targetnode], k)
1049 marks.recordchange(tr)
1056 marks.recordchange(tr)
1050
1057
1051 def storecollapsemsg(repo, collapsemsg):
1058 def storecollapsemsg(repo, collapsemsg):
1052 'Store the collapse message to allow recovery'
1059 'Store the collapse message to allow recovery'
1053 collapsemsg = collapsemsg or ''
1060 collapsemsg = collapsemsg or ''
1054 f = repo.vfs("last-message.txt", "w")
1061 f = repo.vfs("last-message.txt", "w")
1055 f.write("%s\n" % collapsemsg)
1062 f.write("%s\n" % collapsemsg)
1056 f.close()
1063 f.close()
1057
1064
1058 def clearcollapsemsg(repo):
1065 def clearcollapsemsg(repo):
1059 'Remove collapse message file'
1066 'Remove collapse message file'
1060 util.unlinkpath(repo.join("last-message.txt"), ignoremissing=True)
1067 util.unlinkpath(repo.join("last-message.txt"), ignoremissing=True)
1061
1068
1062 def restorecollapsemsg(repo):
1069 def restorecollapsemsg(repo):
1063 'Restore previously stored collapse message'
1070 'Restore previously stored collapse message'
1064 try:
1071 try:
1065 f = repo.vfs("last-message.txt")
1072 f = repo.vfs("last-message.txt")
1066 collapsemsg = f.readline().strip()
1073 collapsemsg = f.readline().strip()
1067 f.close()
1074 f.close()
1068 except IOError as err:
1075 except IOError as err:
1069 if err.errno != errno.ENOENT:
1076 if err.errno != errno.ENOENT:
1070 raise
1077 raise
1071 raise error.Abort(_('no rebase in progress'))
1078 raise error.Abort(_('no rebase in progress'))
1072 return collapsemsg
1079 return collapsemsg
1073
1080
1074 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
1081 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
1075 external, activebookmark):
1082 external, activebookmark):
1076 'Store the current status to allow recovery'
1083 'Store the current status to allow recovery'
1077 f = repo.vfs("rebasestate", "w")
1084 f = repo.vfs("rebasestate", "w")
1078 f.write(repo[originalwd].hex() + '\n')
1085 f.write(repo[originalwd].hex() + '\n')
1079 f.write(repo[target].hex() + '\n')
1086 f.write(repo[target].hex() + '\n')
1080 f.write(repo[external].hex() + '\n')
1087 f.write(repo[external].hex() + '\n')
1081 f.write('%d\n' % int(collapse))
1088 f.write('%d\n' % int(collapse))
1082 f.write('%d\n' % int(keep))
1089 f.write('%d\n' % int(keep))
1083 f.write('%d\n' % int(keepbranches))
1090 f.write('%d\n' % int(keepbranches))
1084 f.write('%s\n' % (activebookmark or ''))
1091 f.write('%s\n' % (activebookmark or ''))
1085 for d, v in state.iteritems():
1092 for d, v in state.iteritems():
1086 oldrev = repo[d].hex()
1093 oldrev = repo[d].hex()
1087 if v >= 0:
1094 if v >= 0:
1088 newrev = repo[v].hex()
1095 newrev = repo[v].hex()
1089 elif v == revtodo:
1096 elif v == revtodo:
1090 # To maintain format compatibility, we have to use nullid.
1097 # To maintain format compatibility, we have to use nullid.
1091 # Please do remove this special case when upgrading the format.
1098 # Please do remove this special case when upgrading the format.
1092 newrev = hex(nullid)
1099 newrev = hex(nullid)
1093 else:
1100 else:
1094 newrev = v
1101 newrev = v
1095 f.write("%s:%s\n" % (oldrev, newrev))
1102 f.write("%s:%s\n" % (oldrev, newrev))
1096 f.close()
1103 f.close()
1097 repo.ui.debug('rebase status stored\n')
1104 repo.ui.debug('rebase status stored\n')
1098
1105
1099 def clearstatus(repo):
1106 def clearstatus(repo):
1100 'Remove the status files'
1107 'Remove the status files'
1101 _clearrebasesetvisibiliy(repo)
1108 _clearrebasesetvisibiliy(repo)
1102 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
1109 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
1103
1110
1104 def needupdate(repo, state):
1111 def needupdate(repo, state):
1105 '''check whether we should `update --clean` away from a merge, or if
1112 '''check whether we should `update --clean` away from a merge, or if
1106 somehow the working dir got forcibly updated, e.g. by older hg'''
1113 somehow the working dir got forcibly updated, e.g. by older hg'''
1107 parents = [p.rev() for p in repo[None].parents()]
1114 parents = [p.rev() for p in repo[None].parents()]
1108
1115
1109 # Are we in a merge state at all?
1116 # Are we in a merge state at all?
1110 if len(parents) < 2:
1117 if len(parents) < 2:
1111 return False
1118 return False
1112
1119
1113 # We should be standing on the first as-of-yet unrebased commit.
1120 # We should be standing on the first as-of-yet unrebased commit.
1114 firstunrebased = min([old for old, new in state.iteritems()
1121 firstunrebased = min([old for old, new in state.iteritems()
1115 if new == nullrev])
1122 if new == nullrev])
1116 if firstunrebased in parents:
1123 if firstunrebased in parents:
1117 return True
1124 return True
1118
1125
1119 return False
1126 return False
1120
1127
1121 def abort(repo, originalwd, target, state, activebookmark=None):
1128 def abort(repo, originalwd, target, state, activebookmark=None):
1122 '''Restore the repository to its original state. Additional args:
1129 '''Restore the repository to its original state. Additional args:
1123
1130
1124 activebookmark: the name of the bookmark that should be active after the
1131 activebookmark: the name of the bookmark that should be active after the
1125 restore'''
1132 restore'''
1126
1133
1127 try:
1134 try:
1128 # If the first commits in the rebased set get skipped during the rebase,
1135 # If the first commits in the rebased set get skipped during the rebase,
1129 # their values within the state mapping will be the target rev id. The
1136 # their values within the state mapping will be the target rev id. The
1130 # dstates list must must not contain the target rev (issue4896)
1137 # dstates list must must not contain the target rev (issue4896)
1131 dstates = [s for s in state.values() if s >= 0 and s != target]
1138 dstates = [s for s in state.values() if s >= 0 and s != target]
1132 immutable = [d for d in dstates if not repo[d].mutable()]
1139 immutable = [d for d in dstates if not repo[d].mutable()]
1133 cleanup = True
1140 cleanup = True
1134 if immutable:
1141 if immutable:
1135 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1142 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1136 % ', '.join(str(repo[r]) for r in immutable),
1143 % ', '.join(str(repo[r]) for r in immutable),
1137 hint=_("see 'hg help phases' for details"))
1144 hint=_("see 'hg help phases' for details"))
1138 cleanup = False
1145 cleanup = False
1139
1146
1140 descendants = set()
1147 descendants = set()
1141 if dstates:
1148 if dstates:
1142 descendants = set(repo.changelog.descendants(dstates))
1149 descendants = set(repo.changelog.descendants(dstates))
1143 if descendants - set(dstates):
1150 if descendants - set(dstates):
1144 repo.ui.warn(_("warning: new changesets detected on target branch, "
1151 repo.ui.warn(_("warning: new changesets detected on target branch, "
1145 "can't strip\n"))
1152 "can't strip\n"))
1146 cleanup = False
1153 cleanup = False
1147
1154
1148 if cleanup:
1155 if cleanup:
1149 shouldupdate = False
1156 shouldupdate = False
1150 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1157 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1151 if rebased:
1158 if rebased:
1152 strippoints = [
1159 strippoints = [
1153 c.node() for c in repo.set('roots(%ld)', rebased)]
1160 c.node() for c in repo.set('roots(%ld)', rebased)]
1154 shouldupdate = len([
1161 shouldupdate = len([
1155 c.node() for c in repo.set('. & (%ld)', rebased)]) > 0
1162 c.node() for c in repo.set('. & (%ld)', rebased)]) > 0
1156
1163
1157 # Update away from the rebase if necessary
1164 # Update away from the rebase if necessary
1158 if shouldupdate or needupdate(repo, state):
1165 if shouldupdate or needupdate(repo, state):
1159 mergemod.update(repo, originalwd, False, True)
1166 mergemod.update(repo, originalwd, False, True)
1160
1167
1161 # Strip from the first rebased revision
1168 # Strip from the first rebased revision
1162 if rebased:
1169 if rebased:
1163 # no backup of rebased cset versions needed
1170 # no backup of rebased cset versions needed
1164 repair.strip(repo.ui, repo, strippoints)
1171 repair.strip(repo.ui, repo, strippoints)
1165
1172
1166 if activebookmark and activebookmark in repo._bookmarks:
1173 if activebookmark and activebookmark in repo._bookmarks:
1167 bookmarks.activate(repo, activebookmark)
1174 bookmarks.activate(repo, activebookmark)
1168
1175
1169 finally:
1176 finally:
1170 clearstatus(repo)
1177 clearstatus(repo)
1171 clearcollapsemsg(repo)
1178 clearcollapsemsg(repo)
1172 repo.ui.warn(_('rebase aborted\n'))
1179 repo.ui.warn(_('rebase aborted\n'))
1173 return 0
1180 return 0
1174
1181
1175 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1182 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1176 '''Define which revisions are going to be rebased and where
1183 '''Define which revisions are going to be rebased and where
1177
1184
1178 repo: repo
1185 repo: repo
1179 dest: context
1186 dest: context
1180 rebaseset: set of rev
1187 rebaseset: set of rev
1181 '''
1188 '''
1182 _setrebasesetvisibility(repo, rebaseset)
1189 _setrebasesetvisibility(repo, rebaseset)
1183
1190
1184 # This check isn't strictly necessary, since mq detects commits over an
1191 # This check isn't strictly necessary, since mq detects commits over an
1185 # applied patch. But it prevents messing up the working directory when
1192 # applied patch. But it prevents messing up the working directory when
1186 # a partially completed rebase is blocked by mq.
1193 # a partially completed rebase is blocked by mq.
1187 if 'qtip' in repo.tags() and (dest.node() in
1194 if 'qtip' in repo.tags() and (dest.node() in
1188 [s.node for s in repo.mq.applied]):
1195 [s.node for s in repo.mq.applied]):
1189 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1196 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1190
1197
1191 roots = list(repo.set('roots(%ld)', rebaseset))
1198 roots = list(repo.set('roots(%ld)', rebaseset))
1192 if not roots:
1199 if not roots:
1193 raise error.Abort(_('no matching revisions'))
1200 raise error.Abort(_('no matching revisions'))
1194 roots.sort()
1201 roots.sort()
1195 state = {}
1202 state = {}
1196 detachset = set()
1203 detachset = set()
1197 for root in roots:
1204 for root in roots:
1198 commonbase = root.ancestor(dest)
1205 commonbase = root.ancestor(dest)
1199 if commonbase == root:
1206 if commonbase == root:
1200 raise error.Abort(_('source is ancestor of destination'))
1207 raise error.Abort(_('source is ancestor of destination'))
1201 if commonbase == dest:
1208 if commonbase == dest:
1202 samebranch = root.branch() == dest.branch()
1209 samebranch = root.branch() == dest.branch()
1203 if not collapse and samebranch and root in dest.children():
1210 if not collapse and samebranch and root in dest.children():
1204 repo.ui.debug('source is a child of destination\n')
1211 repo.ui.debug('source is a child of destination\n')
1205 return None
1212 return None
1206
1213
1207 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1214 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1208 state.update(dict.fromkeys(rebaseset, revtodo))
1215 state.update(dict.fromkeys(rebaseset, revtodo))
1209 # Rebase tries to turn <dest> into a parent of <root> while
1216 # Rebase tries to turn <dest> into a parent of <root> while
1210 # preserving the number of parents of rebased changesets:
1217 # preserving the number of parents of rebased changesets:
1211 #
1218 #
1212 # - A changeset with a single parent will always be rebased as a
1219 # - A changeset with a single parent will always be rebased as a
1213 # changeset with a single parent.
1220 # changeset with a single parent.
1214 #
1221 #
1215 # - A merge will be rebased as merge unless its parents are both
1222 # - A merge will be rebased as merge unless its parents are both
1216 # ancestors of <dest> or are themselves in the rebased set and
1223 # ancestors of <dest> or are themselves in the rebased set and
1217 # pruned while rebased.
1224 # pruned while rebased.
1218 #
1225 #
1219 # If one parent of <root> is an ancestor of <dest>, the rebased
1226 # If one parent of <root> is an ancestor of <dest>, the rebased
1220 # version of this parent will be <dest>. This is always true with
1227 # version of this parent will be <dest>. This is always true with
1221 # --base option.
1228 # --base option.
1222 #
1229 #
1223 # Otherwise, we need to *replace* the original parents with
1230 # Otherwise, we need to *replace* the original parents with
1224 # <dest>. This "detaches" the rebased set from its former location
1231 # <dest>. This "detaches" the rebased set from its former location
1225 # and rebases it onto <dest>. Changes introduced by ancestors of
1232 # and rebases it onto <dest>. Changes introduced by ancestors of
1226 # <root> not common with <dest> (the detachset, marked as
1233 # <root> not common with <dest> (the detachset, marked as
1227 # nullmerge) are "removed" from the rebased changesets.
1234 # nullmerge) are "removed" from the rebased changesets.
1228 #
1235 #
1229 # - If <root> has a single parent, set it to <dest>.
1236 # - If <root> has a single parent, set it to <dest>.
1230 #
1237 #
1231 # - If <root> is a merge, we cannot decide which parent to
1238 # - If <root> is a merge, we cannot decide which parent to
1232 # replace, the rebase operation is not clearly defined.
1239 # replace, the rebase operation is not clearly defined.
1233 #
1240 #
1234 # The table below sums up this behavior:
1241 # The table below sums up this behavior:
1235 #
1242 #
1236 # +------------------+----------------------+-------------------------+
1243 # +------------------+----------------------+-------------------------+
1237 # | | one parent | merge |
1244 # | | one parent | merge |
1238 # +------------------+----------------------+-------------------------+
1245 # +------------------+----------------------+-------------------------+
1239 # | parent in | new parent is <dest> | parents in ::<dest> are |
1246 # | parent in | new parent is <dest> | parents in ::<dest> are |
1240 # | ::<dest> | | remapped to <dest> |
1247 # | ::<dest> | | remapped to <dest> |
1241 # +------------------+----------------------+-------------------------+
1248 # +------------------+----------------------+-------------------------+
1242 # | unrelated source | new parent is <dest> | ambiguous, abort |
1249 # | unrelated source | new parent is <dest> | ambiguous, abort |
1243 # +------------------+----------------------+-------------------------+
1250 # +------------------+----------------------+-------------------------+
1244 #
1251 #
1245 # The actual abort is handled by `defineparents`
1252 # The actual abort is handled by `defineparents`
1246 if len(root.parents()) <= 1:
1253 if len(root.parents()) <= 1:
1247 # ancestors of <root> not ancestors of <dest>
1254 # ancestors of <root> not ancestors of <dest>
1248 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1255 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1249 [root.rev()]))
1256 [root.rev()]))
1250 for r in detachset:
1257 for r in detachset:
1251 if r not in state:
1258 if r not in state:
1252 state[r] = nullmerge
1259 state[r] = nullmerge
1253 if len(roots) > 1:
1260 if len(roots) > 1:
1254 # If we have multiple roots, we may have "hole" in the rebase set.
1261 # If we have multiple roots, we may have "hole" in the rebase set.
1255 # Rebase roots that descend from those "hole" should not be detached as
1262 # Rebase roots that descend from those "hole" should not be detached as
1256 # other root are. We use the special `revignored` to inform rebase that
1263 # other root are. We use the special `revignored` to inform rebase that
1257 # the revision should be ignored but that `defineparents` should search
1264 # the revision should be ignored but that `defineparents` should search
1258 # a rebase destination that make sense regarding rebased topology.
1265 # a rebase destination that make sense regarding rebased topology.
1259 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1266 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1260 for ignored in set(rebasedomain) - set(rebaseset):
1267 for ignored in set(rebasedomain) - set(rebaseset):
1261 state[ignored] = revignored
1268 state[ignored] = revignored
1262 for r in obsoletenotrebased:
1269 for r in obsoletenotrebased:
1263 if obsoletenotrebased[r] is None:
1270 if obsoletenotrebased[r] is None:
1264 state[r] = revpruned
1271 state[r] = revpruned
1265 else:
1272 else:
1266 state[r] = revprecursor
1273 state[r] = revprecursor
1267 return repo['.'].rev(), dest.rev(), state
1274 return repo['.'].rev(), dest.rev(), state
1268
1275
1269 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1276 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1270 """dispose of rebased revision at the end of the rebase
1277 """dispose of rebased revision at the end of the rebase
1271
1278
1272 If `collapsedas` is not None, the rebase was a collapse whose result if the
1279 If `collapsedas` is not None, the rebase was a collapse whose result if the
1273 `collapsedas` node."""
1280 `collapsedas` node."""
1274 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1281 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1275 markers = []
1282 markers = []
1276 for rev, newrev in sorted(state.items()):
1283 for rev, newrev in sorted(state.items()):
1277 if newrev >= 0:
1284 if newrev >= 0:
1278 if rev in skipped:
1285 if rev in skipped:
1279 succs = ()
1286 succs = ()
1280 elif collapsedas is not None:
1287 elif collapsedas is not None:
1281 succs = (repo[collapsedas],)
1288 succs = (repo[collapsedas],)
1282 else:
1289 else:
1283 succs = (repo[newrev],)
1290 succs = (repo[newrev],)
1284 markers.append((repo[rev], succs))
1291 markers.append((repo[rev], succs))
1285 if markers:
1292 if markers:
1286 obsolete.createmarkers(repo, markers)
1293 obsolete.createmarkers(repo, markers)
1287 else:
1294 else:
1288 rebased = [rev for rev in state if state[rev] > nullmerge]
1295 rebased = [rev for rev in state if state[rev] > nullmerge]
1289 if rebased:
1296 if rebased:
1290 stripped = []
1297 stripped = []
1291 for root in repo.set('roots(%ld)', rebased):
1298 for root in repo.set('roots(%ld)', rebased):
1292 if set(repo.changelog.descendants([root.rev()])) - set(state):
1299 if set(repo.changelog.descendants([root.rev()])) - set(state):
1293 ui.warn(_("warning: new changesets detected "
1300 ui.warn(_("warning: new changesets detected "
1294 "on source branch, not stripping\n"))
1301 "on source branch, not stripping\n"))
1295 else:
1302 else:
1296 stripped.append(root.node())
1303 stripped.append(root.node())
1297 if stripped:
1304 if stripped:
1298 # backup the old csets by default
1305 # backup the old csets by default
1299 repair.strip(ui, repo, stripped, "all")
1306 repair.strip(ui, repo, stripped, "all")
1300
1307
1301
1308
1302 def pullrebase(orig, ui, repo, *args, **opts):
1309 def pullrebase(orig, ui, repo, *args, **opts):
1303 'Call rebase after pull if the latter has been invoked with --rebase'
1310 'Call rebase after pull if the latter has been invoked with --rebase'
1304 ret = None
1311 ret = None
1305 if opts.get('rebase'):
1312 if opts.get('rebase'):
1306 wlock = lock = None
1313 wlock = lock = None
1307 try:
1314 try:
1308 wlock = repo.wlock()
1315 wlock = repo.wlock()
1309 lock = repo.lock()
1316 lock = repo.lock()
1310 if opts.get('update'):
1317 if opts.get('update'):
1311 del opts['update']
1318 del opts['update']
1312 ui.debug('--update and --rebase are not compatible, ignoring '
1319 ui.debug('--update and --rebase are not compatible, ignoring '
1313 'the update flag\n')
1320 'the update flag\n')
1314
1321
1315 revsprepull = len(repo)
1322 revsprepull = len(repo)
1316 origpostincoming = commands.postincoming
1323 origpostincoming = commands.postincoming
1317 def _dummy(*args, **kwargs):
1324 def _dummy(*args, **kwargs):
1318 pass
1325 pass
1319 commands.postincoming = _dummy
1326 commands.postincoming = _dummy
1320 try:
1327 try:
1321 ret = orig(ui, repo, *args, **opts)
1328 ret = orig(ui, repo, *args, **opts)
1322 finally:
1329 finally:
1323 commands.postincoming = origpostincoming
1330 commands.postincoming = origpostincoming
1324 revspostpull = len(repo)
1331 revspostpull = len(repo)
1325 if revspostpull > revsprepull:
1332 if revspostpull > revsprepull:
1326 # --rev option from pull conflict with rebase own --rev
1333 # --rev option from pull conflict with rebase own --rev
1327 # dropping it
1334 # dropping it
1328 if 'rev' in opts:
1335 if 'rev' in opts:
1329 del opts['rev']
1336 del opts['rev']
1330 # positional argument from pull conflicts with rebase's own
1337 # positional argument from pull conflicts with rebase's own
1331 # --source.
1338 # --source.
1332 if 'source' in opts:
1339 if 'source' in opts:
1333 del opts['source']
1340 del opts['source']
1334 # revsprepull is the len of the repo, not revnum of tip.
1341 # revsprepull is the len of the repo, not revnum of tip.
1335 destspace = list(repo.changelog.revs(start=revsprepull))
1342 destspace = list(repo.changelog.revs(start=revsprepull))
1336 opts['_destspace'] = destspace
1343 opts['_destspace'] = destspace
1337 try:
1344 try:
1338 rebase(ui, repo, **opts)
1345 rebase(ui, repo, **opts)
1339 except error.NoMergeDestAbort:
1346 except error.NoMergeDestAbort:
1340 # we can maybe update instead
1347 # we can maybe update instead
1341 rev, _a, _b = destutil.destupdate(repo)
1348 rev, _a, _b = destutil.destupdate(repo)
1342 if rev == repo['.'].rev():
1349 if rev == repo['.'].rev():
1343 ui.status(_('nothing to rebase\n'))
1350 ui.status(_('nothing to rebase\n'))
1344 else:
1351 else:
1345 ui.status(_('nothing to rebase - updating instead\n'))
1352 ui.status(_('nothing to rebase - updating instead\n'))
1346 # not passing argument to get the bare update behavior
1353 # not passing argument to get the bare update behavior
1347 # with warning and trumpets
1354 # with warning and trumpets
1348 commands.update(ui, repo)
1355 commands.update(ui, repo)
1349 finally:
1356 finally:
1350 release(lock, wlock)
1357 release(lock, wlock)
1351 else:
1358 else:
1352 if opts.get('tool'):
1359 if opts.get('tool'):
1353 raise error.Abort(_('--tool can only be used with --rebase'))
1360 raise error.Abort(_('--tool can only be used with --rebase'))
1354 ret = orig(ui, repo, *args, **opts)
1361 ret = orig(ui, repo, *args, **opts)
1355
1362
1356 return ret
1363 return ret
1357
1364
1358 def _setrebasesetvisibility(repo, revs):
1365 def _setrebasesetvisibility(repo, revs):
1359 """store the currently rebased set on the repo object
1366 """store the currently rebased set on the repo object
1360
1367
1361 This is used by another function to prevent rebased revision to because
1368 This is used by another function to prevent rebased revision to because
1362 hidden (see issue4505)"""
1369 hidden (see issue4505)"""
1363 repo = repo.unfiltered()
1370 repo = repo.unfiltered()
1364 revs = set(revs)
1371 revs = set(revs)
1365 repo._rebaseset = revs
1372 repo._rebaseset = revs
1366 # invalidate cache if visibility changes
1373 # invalidate cache if visibility changes
1367 hiddens = repo.filteredrevcache.get('visible', set())
1374 hiddens = repo.filteredrevcache.get('visible', set())
1368 if revs & hiddens:
1375 if revs & hiddens:
1369 repo.invalidatevolatilesets()
1376 repo.invalidatevolatilesets()
1370
1377
1371 def _clearrebasesetvisibiliy(repo):
1378 def _clearrebasesetvisibiliy(repo):
1372 """remove rebaseset data from the repo"""
1379 """remove rebaseset data from the repo"""
1373 repo = repo.unfiltered()
1380 repo = repo.unfiltered()
1374 if '_rebaseset' in vars(repo):
1381 if '_rebaseset' in vars(repo):
1375 del repo._rebaseset
1382 del repo._rebaseset
1376
1383
1377 def _rebasedvisible(orig, repo):
1384 def _rebasedvisible(orig, repo):
1378 """ensure rebased revs stay visible (see issue4505)"""
1385 """ensure rebased revs stay visible (see issue4505)"""
1379 blockers = orig(repo)
1386 blockers = orig(repo)
1380 blockers.update(getattr(repo, '_rebaseset', ()))
1387 blockers.update(getattr(repo, '_rebaseset', ()))
1381 return blockers
1388 return blockers
1382
1389
1383 def _filterobsoleterevs(repo, revs):
1390 def _filterobsoleterevs(repo, revs):
1384 """returns a set of the obsolete revisions in revs"""
1391 """returns a set of the obsolete revisions in revs"""
1385 return set(r for r in revs if repo[r].obsolete())
1392 return set(r for r in revs if repo[r].obsolete())
1386
1393
1387 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1394 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1388 """return a mapping obsolete => successor for all obsolete nodes to be
1395 """return a mapping obsolete => successor for all obsolete nodes to be
1389 rebased that have a successors in the destination
1396 rebased that have a successors in the destination
1390
1397
1391 obsolete => None entries in the mapping indicate nodes with no successor"""
1398 obsolete => None entries in the mapping indicate nodes with no successor"""
1392 obsoletenotrebased = {}
1399 obsoletenotrebased = {}
1393
1400
1394 # Build a mapping successor => obsolete nodes for the obsolete
1401 # Build a mapping successor => obsolete nodes for the obsolete
1395 # nodes to be rebased
1402 # nodes to be rebased
1396 allsuccessors = {}
1403 allsuccessors = {}
1397 cl = repo.changelog
1404 cl = repo.changelog
1398 for r in rebaseobsrevs:
1405 for r in rebaseobsrevs:
1399 node = cl.node(r)
1406 node = cl.node(r)
1400 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1407 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1401 try:
1408 try:
1402 allsuccessors[cl.rev(s)] = cl.rev(node)
1409 allsuccessors[cl.rev(s)] = cl.rev(node)
1403 except LookupError:
1410 except LookupError:
1404 pass
1411 pass
1405
1412
1406 if allsuccessors:
1413 if allsuccessors:
1407 # Look for successors of obsolete nodes to be rebased among
1414 # Look for successors of obsolete nodes to be rebased among
1408 # the ancestors of dest
1415 # the ancestors of dest
1409 ancs = cl.ancestors([repo[dest].rev()],
1416 ancs = cl.ancestors([repo[dest].rev()],
1410 stoprev=min(allsuccessors),
1417 stoprev=min(allsuccessors),
1411 inclusive=True)
1418 inclusive=True)
1412 for s in allsuccessors:
1419 for s in allsuccessors:
1413 if s in ancs:
1420 if s in ancs:
1414 obsoletenotrebased[allsuccessors[s]] = s
1421 obsoletenotrebased[allsuccessors[s]] = s
1415 elif (s == allsuccessors[s] and
1422 elif (s == allsuccessors[s] and
1416 allsuccessors.values().count(s) == 1):
1423 allsuccessors.values().count(s) == 1):
1417 # plain prune
1424 # plain prune
1418 obsoletenotrebased[s] = None
1425 obsoletenotrebased[s] = None
1419
1426
1420 return obsoletenotrebased
1427 return obsoletenotrebased
1421
1428
1422 def summaryhook(ui, repo):
1429 def summaryhook(ui, repo):
1423 if not os.path.exists(repo.join('rebasestate')):
1430 if not os.path.exists(repo.join('rebasestate')):
1424 return
1431 return
1425 try:
1432 try:
1426 rbsrt = rebaseruntime(repo, ui, {})
1433 rbsrt = rebaseruntime(repo, ui, {})
1427 rbsrt.restorestatus()
1434 rbsrt.restorestatus()
1428 state = rbsrt.state
1435 state = rbsrt.state
1429 except error.RepoLookupError:
1436 except error.RepoLookupError:
1430 # i18n: column positioning for "hg summary"
1437 # i18n: column positioning for "hg summary"
1431 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1438 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1432 ui.write(msg)
1439 ui.write(msg)
1433 return
1440 return
1434 numrebased = len([i for i in state.itervalues() if i >= 0])
1441 numrebased = len([i for i in state.itervalues() if i >= 0])
1435 # i18n: column positioning for "hg summary"
1442 # i18n: column positioning for "hg summary"
1436 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1443 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1437 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1444 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1438 ui.label(_('%d remaining'), 'rebase.remaining') %
1445 ui.label(_('%d remaining'), 'rebase.remaining') %
1439 (len(state) - numrebased)))
1446 (len(state) - numrebased)))
1440
1447
1441 def uisetup(ui):
1448 def uisetup(ui):
1442 #Replace pull with a decorator to provide --rebase option
1449 #Replace pull with a decorator to provide --rebase option
1443 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1450 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1444 entry[1].append(('', 'rebase', None,
1451 entry[1].append(('', 'rebase', None,
1445 _("rebase working directory to branch head")))
1452 _("rebase working directory to branch head")))
1446 entry[1].append(('t', 'tool', '',
1453 entry[1].append(('t', 'tool', '',
1447 _("specify merge tool for rebase")))
1454 _("specify merge tool for rebase")))
1448 cmdutil.summaryhooks.add('rebase', summaryhook)
1455 cmdutil.summaryhooks.add('rebase', summaryhook)
1449 cmdutil.unfinishedstates.append(
1456 cmdutil.unfinishedstates.append(
1450 ['rebasestate', False, False, _('rebase in progress'),
1457 ['rebasestate', False, False, _('rebase in progress'),
1451 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1458 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1452 cmdutil.afterresolvedstates.append(
1459 cmdutil.afterresolvedstates.append(
1453 ['rebasestate', _('hg rebase --continue')])
1460 ['rebasestate', _('hg rebase --continue')])
1454 # ensure rebased rev are not hidden
1461 # ensure rebased rev are not hidden
1455 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1462 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
General Comments 0
You need to be logged in to leave comments. Login now