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