##// END OF EJS Templates
destroyed: invalidate phraserevs cache in all case (issue3858)...
Pierre-Yves David -
r18983:31bcc511 default
parent child Browse files
Show More
@@ -1,400 +1,408 b''
1 """ Mercurial phases support code
1 """ Mercurial phases support code
2
2
3 ---
3 ---
4
4
5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 Logilab SA <contact@logilab.fr>
6 Logilab SA <contact@logilab.fr>
7 Augie Fackler <durin42@gmail.com>
7 Augie Fackler <durin42@gmail.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License version 2 or any later version.
10 of the GNU General Public License version 2 or any later version.
11
11
12 ---
12 ---
13
13
14 This module implements most phase logic in mercurial.
14 This module implements most phase logic in mercurial.
15
15
16
16
17 Basic Concept
17 Basic Concept
18 =============
18 =============
19
19
20 A 'changeset phase' is an indicator that tells us how a changeset is
20 A 'changeset phase' is an indicator that tells us how a changeset is
21 manipulated and communicated. The details of each phase is described
21 manipulated and communicated. The details of each phase is described
22 below, here we describe the properties they have in common.
22 below, here we describe the properties they have in common.
23
23
24 Like bookmarks, phases are not stored in history and thus are not
24 Like bookmarks, phases are not stored in history and thus are not
25 permanent and leave no audit trail.
25 permanent and leave no audit trail.
26
26
27 First, no changeset can be in two phases at once. Phases are ordered,
27 First, no changeset can be in two phases at once. Phases are ordered,
28 so they can be considered from lowest to highest. The default, lowest
28 so they can be considered from lowest to highest. The default, lowest
29 phase is 'public' - this is the normal phase of existing changesets. A
29 phase is 'public' - this is the normal phase of existing changesets. A
30 child changeset can not be in a lower phase than its parents.
30 child changeset can not be in a lower phase than its parents.
31
31
32 These phases share a hierarchy of traits:
32 These phases share a hierarchy of traits:
33
33
34 immutable shared
34 immutable shared
35 public: X X
35 public: X X
36 draft: X
36 draft: X
37 secret:
37 secret:
38
38
39 Local commits are draft by default.
39 Local commits are draft by default.
40
40
41 Phase Movement and Exchange
41 Phase Movement and Exchange
42 ===========================
42 ===========================
43
43
44 Phase data is exchanged by pushkey on pull and push. Some servers have
44 Phase data is exchanged by pushkey on pull and push. Some servers have
45 a publish option set, we call such a server a "publishing server".
45 a publish option set, we call such a server a "publishing server".
46 Pushing a draft changeset to a publishing server changes the phase to
46 Pushing a draft changeset to a publishing server changes the phase to
47 public.
47 public.
48
48
49 A small list of fact/rules define the exchange of phase:
49 A small list of fact/rules define the exchange of phase:
50
50
51 * old client never changes server states
51 * old client never changes server states
52 * pull never changes server states
52 * pull never changes server states
53 * publish and old server changesets are seen as public by client
53 * publish and old server changesets are seen as public by client
54 * any secret changeset seen in another repository is lowered to at
54 * any secret changeset seen in another repository is lowered to at
55 least draft
55 least draft
56
56
57 Here is the final table summing up the 49 possible use cases of phase
57 Here is the final table summing up the 49 possible use cases of phase
58 exchange:
58 exchange:
59
59
60 server
60 server
61 old publish non-publish
61 old publish non-publish
62 N X N D P N D P
62 N X N D P N D P
63 old client
63 old client
64 pull
64 pull
65 N - X/X - X/D X/P - X/D X/P
65 N - X/X - X/D X/P - X/D X/P
66 X - X/X - X/D X/P - X/D X/P
66 X - X/X - X/D X/P - X/D X/P
67 push
67 push
68 X X/X X/X X/P X/P X/P X/D X/D X/P
68 X X/X X/X X/P X/P X/P X/D X/D X/P
69 new client
69 new client
70 pull
70 pull
71 N - P/X - P/D P/P - D/D P/P
71 N - P/X - P/D P/P - D/D P/P
72 D - P/X - P/D P/P - D/D P/P
72 D - P/X - P/D P/P - D/D P/P
73 P - P/X - P/D P/P - P/D P/P
73 P - P/X - P/D P/P - P/D P/P
74 push
74 push
75 D P/X P/X P/P P/P P/P D/D D/D P/P
75 D P/X P/X P/P P/P P/P D/D D/D P/P
76 P P/X P/X P/P P/P P/P P/P P/P P/P
76 P P/X P/X P/P P/P P/P P/P P/P P/P
77
77
78 Legend:
78 Legend:
79
79
80 A/B = final state on client / state on server
80 A/B = final state on client / state on server
81
81
82 * N = new/not present,
82 * N = new/not present,
83 * P = public,
83 * P = public,
84 * D = draft,
84 * D = draft,
85 * X = not tracked (i.e., the old client or server has no internal
85 * X = not tracked (i.e., the old client or server has no internal
86 way of recording the phase.)
86 way of recording the phase.)
87
87
88 passive = only pushes
88 passive = only pushes
89
89
90
90
91 A cell here can be read like this:
91 A cell here can be read like this:
92
92
93 "When a new client pushes a draft changeset (D) to a publishing
93 "When a new client pushes a draft changeset (D) to a publishing
94 server where it's not present (N), it's marked public on both
94 server where it's not present (N), it's marked public on both
95 sides (P/P)."
95 sides (P/P)."
96
96
97 Note: old client behave as a publishing server with draft only content
97 Note: old client behave as a publishing server with draft only content
98 - other people see it as public
98 - other people see it as public
99 - content is pushed as draft
99 - content is pushed as draft
100
100
101 """
101 """
102
102
103 import errno
103 import errno
104 from node import nullid, nullrev, bin, hex, short
104 from node import nullid, nullrev, bin, hex, short
105 from i18n import _
105 from i18n import _
106 import util, error
106 import util, error
107
107
108 allphases = public, draft, secret = range(3)
108 allphases = public, draft, secret = range(3)
109 trackedphases = allphases[1:]
109 trackedphases = allphases[1:]
110 phasenames = ['public', 'draft', 'secret']
110 phasenames = ['public', 'draft', 'secret']
111
111
112 def _readroots(repo, phasedefaults=None):
112 def _readroots(repo, phasedefaults=None):
113 """Read phase roots from disk
113 """Read phase roots from disk
114
114
115 phasedefaults is a list of fn(repo, roots) callable, which are
115 phasedefaults is a list of fn(repo, roots) callable, which are
116 executed if the phase roots file does not exist. When phases are
116 executed if the phase roots file does not exist. When phases are
117 being initialized on an existing repository, this could be used to
117 being initialized on an existing repository, this could be used to
118 set selected changesets phase to something else than public.
118 set selected changesets phase to something else than public.
119
119
120 Return (roots, dirty) where dirty is true if roots differ from
120 Return (roots, dirty) where dirty is true if roots differ from
121 what is being stored.
121 what is being stored.
122 """
122 """
123 repo = repo.unfiltered()
123 repo = repo.unfiltered()
124 dirty = False
124 dirty = False
125 roots = [set() for i in allphases]
125 roots = [set() for i in allphases]
126 try:
126 try:
127 f = repo.sopener('phaseroots')
127 f = repo.sopener('phaseroots')
128 try:
128 try:
129 for line in f:
129 for line in f:
130 phase, nh = line.split()
130 phase, nh = line.split()
131 roots[int(phase)].add(bin(nh))
131 roots[int(phase)].add(bin(nh))
132 finally:
132 finally:
133 f.close()
133 f.close()
134 except IOError, inst:
134 except IOError, inst:
135 if inst.errno != errno.ENOENT:
135 if inst.errno != errno.ENOENT:
136 raise
136 raise
137 if phasedefaults:
137 if phasedefaults:
138 for f in phasedefaults:
138 for f in phasedefaults:
139 roots = f(repo, roots)
139 roots = f(repo, roots)
140 dirty = True
140 dirty = True
141 return roots, dirty
141 return roots, dirty
142
142
143 class phasecache(object):
143 class phasecache(object):
144 def __init__(self, repo, phasedefaults, _load=True):
144 def __init__(self, repo, phasedefaults, _load=True):
145 if _load:
145 if _load:
146 # Cheap trick to allow shallow-copy without copy module
146 # Cheap trick to allow shallow-copy without copy module
147 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
147 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
148 self._phaserevs = None
148 self._phaserevs = None
149 self.filterunknown(repo)
149 self.filterunknown(repo)
150 self.opener = repo.sopener
150 self.opener = repo.sopener
151
151
152 def copy(self):
152 def copy(self):
153 # Shallow copy meant to ensure isolation in
153 # Shallow copy meant to ensure isolation in
154 # advance/retractboundary(), nothing more.
154 # advance/retractboundary(), nothing more.
155 ph = phasecache(None, None, _load=False)
155 ph = phasecache(None, None, _load=False)
156 ph.phaseroots = self.phaseroots[:]
156 ph.phaseroots = self.phaseroots[:]
157 ph.dirty = self.dirty
157 ph.dirty = self.dirty
158 ph.opener = self.opener
158 ph.opener = self.opener
159 ph._phaserevs = self._phaserevs
159 ph._phaserevs = self._phaserevs
160 return ph
160 return ph
161
161
162 def replace(self, phcache):
162 def replace(self, phcache):
163 for a in 'phaseroots dirty opener _phaserevs'.split():
163 for a in 'phaseroots dirty opener _phaserevs'.split():
164 setattr(self, a, getattr(phcache, a))
164 setattr(self, a, getattr(phcache, a))
165
165
166 def getphaserevs(self, repo, rebuild=False):
166 def getphaserevs(self, repo, rebuild=False):
167 if rebuild or self._phaserevs is None:
167 if rebuild or self._phaserevs is None:
168 repo = repo.unfiltered()
168 repo = repo.unfiltered()
169 revs = [public] * len(repo.changelog)
169 revs = [public] * len(repo.changelog)
170 for phase in trackedphases:
170 for phase in trackedphases:
171 roots = map(repo.changelog.rev, self.phaseroots[phase])
171 roots = map(repo.changelog.rev, self.phaseroots[phase])
172 if roots:
172 if roots:
173 for rev in roots:
173 for rev in roots:
174 revs[rev] = phase
174 revs[rev] = phase
175 for rev in repo.changelog.descendants(roots):
175 for rev in repo.changelog.descendants(roots):
176 revs[rev] = phase
176 revs[rev] = phase
177 self._phaserevs = revs
177 self._phaserevs = revs
178 return self._phaserevs
178 return self._phaserevs
179
179
180 def phase(self, repo, rev):
180 def phase(self, repo, rev):
181 # We need a repo argument here to be able to build _phaserevs
181 # We need a repo argument here to be able to build _phaserevs
182 # if necessary. The repository instance is not stored in
182 # if necessary. The repository instance is not stored in
183 # phasecache to avoid reference cycles. The changelog instance
183 # phasecache to avoid reference cycles. The changelog instance
184 # is not stored because it is a filecache() property and can
184 # is not stored because it is a filecache() property and can
185 # be replaced without us being notified.
185 # be replaced without us being notified.
186 if rev == nullrev:
186 if rev == nullrev:
187 return public
187 return public
188 if self._phaserevs is None or rev >= len(self._phaserevs):
188 if self._phaserevs is None or rev >= len(self._phaserevs):
189 self._phaserevs = self.getphaserevs(repo, rebuild=True)
189 self._phaserevs = self.getphaserevs(repo, rebuild=True)
190 return self._phaserevs[rev]
190 return self._phaserevs[rev]
191
191
192 def write(self):
192 def write(self):
193 if not self.dirty:
193 if not self.dirty:
194 return
194 return
195 f = self.opener('phaseroots', 'w', atomictemp=True)
195 f = self.opener('phaseroots', 'w', atomictemp=True)
196 try:
196 try:
197 for phase, roots in enumerate(self.phaseroots):
197 for phase, roots in enumerate(self.phaseroots):
198 for h in roots:
198 for h in roots:
199 f.write('%i %s\n' % (phase, hex(h)))
199 f.write('%i %s\n' % (phase, hex(h)))
200 finally:
200 finally:
201 f.close()
201 f.close()
202 self.dirty = False
202 self.dirty = False
203
203
204 def _updateroots(self, phase, newroots):
204 def _updateroots(self, phase, newroots):
205 self.phaseroots[phase] = newroots
205 self.phaseroots[phase] = newroots
206 self._phaserevs = None
206 self._phaserevs = None
207 self.dirty = True
207 self.dirty = True
208
208
209 def advanceboundary(self, repo, targetphase, nodes):
209 def advanceboundary(self, repo, targetphase, nodes):
210 # Be careful to preserve shallow-copied values: do not update
210 # Be careful to preserve shallow-copied values: do not update
211 # phaseroots values, replace them.
211 # phaseroots values, replace them.
212
212
213 repo = repo.unfiltered()
213 repo = repo.unfiltered()
214 delroots = [] # set of root deleted by this path
214 delroots = [] # set of root deleted by this path
215 for phase in xrange(targetphase + 1, len(allphases)):
215 for phase in xrange(targetphase + 1, len(allphases)):
216 # filter nodes that are not in a compatible phase already
216 # filter nodes that are not in a compatible phase already
217 nodes = [n for n in nodes
217 nodes = [n for n in nodes
218 if self.phase(repo, repo[n].rev()) >= phase]
218 if self.phase(repo, repo[n].rev()) >= phase]
219 if not nodes:
219 if not nodes:
220 break # no roots to move anymore
220 break # no roots to move anymore
221 olds = self.phaseroots[phase]
221 olds = self.phaseroots[phase]
222 roots = set(ctx.node() for ctx in repo.set(
222 roots = set(ctx.node() for ctx in repo.set(
223 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
223 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
224 if olds != roots:
224 if olds != roots:
225 self._updateroots(phase, roots)
225 self._updateroots(phase, roots)
226 # some roots may need to be declared for lower phases
226 # some roots may need to be declared for lower phases
227 delroots.extend(olds - roots)
227 delroots.extend(olds - roots)
228 # declare deleted root in the target phase
228 # declare deleted root in the target phase
229 if targetphase != 0:
229 if targetphase != 0:
230 self.retractboundary(repo, targetphase, delroots)
230 self.retractboundary(repo, targetphase, delroots)
231 repo.invalidatevolatilesets()
231 repo.invalidatevolatilesets()
232
232
233 def retractboundary(self, repo, targetphase, nodes):
233 def retractboundary(self, repo, targetphase, nodes):
234 # Be careful to preserve shallow-copied values: do not update
234 # Be careful to preserve shallow-copied values: do not update
235 # phaseroots values, replace them.
235 # phaseroots values, replace them.
236
236
237 repo = repo.unfiltered()
237 repo = repo.unfiltered()
238 currentroots = self.phaseroots[targetphase]
238 currentroots = self.phaseroots[targetphase]
239 newroots = [n for n in nodes
239 newroots = [n for n in nodes
240 if self.phase(repo, repo[n].rev()) < targetphase]
240 if self.phase(repo, repo[n].rev()) < targetphase]
241 if newroots:
241 if newroots:
242 if nullid in newroots:
242 if nullid in newroots:
243 raise util.Abort(_('cannot change null revision phase'))
243 raise util.Abort(_('cannot change null revision phase'))
244 currentroots = currentroots.copy()
244 currentroots = currentroots.copy()
245 currentroots.update(newroots)
245 currentroots.update(newroots)
246 ctxs = repo.set('roots(%ln::)', currentroots)
246 ctxs = repo.set('roots(%ln::)', currentroots)
247 currentroots.intersection_update(ctx.node() for ctx in ctxs)
247 currentroots.intersection_update(ctx.node() for ctx in ctxs)
248 self._updateroots(targetphase, currentroots)
248 self._updateroots(targetphase, currentroots)
249 repo.invalidatevolatilesets()
249 repo.invalidatevolatilesets()
250
250
251 def filterunknown(self, repo):
251 def filterunknown(self, repo):
252 """remove unknown nodes from the phase boundary
252 """remove unknown nodes from the phase boundary
253
253
254 Nothing is lost as unknown nodes only hold data for their descendants.
254 Nothing is lost as unknown nodes only hold data for their descendants.
255 """
255 """
256 filtered = False
256 filtered = False
257 nodemap = repo.changelog.nodemap # to filter unknown nodes
257 nodemap = repo.changelog.nodemap # to filter unknown nodes
258 for phase, nodes in enumerate(self.phaseroots):
258 for phase, nodes in enumerate(self.phaseroots):
259 missing = [node for node in nodes if node not in nodemap]
259 missing = [node for node in nodes if node not in nodemap]
260 if missing:
260 if missing:
261 for mnode in missing:
261 for mnode in missing:
262 repo.ui.debug(
262 repo.ui.debug(
263 'removing unknown node %s from %i-phase boundary\n'
263 'removing unknown node %s from %i-phase boundary\n'
264 % (short(mnode), phase))
264 % (short(mnode), phase))
265 nodes.symmetric_difference_update(missing)
265 nodes.symmetric_difference_update(missing)
266 filtered = True
266 filtered = True
267 if filtered:
267 if filtered:
268 self.dirty = True
268 self.dirty = True
269 self._phaserevs = None
269 # filterunknown is called by repo.destroyed, we may have no changes in
270 # root but phaserevs contents is certainly invalide (or at least we
271 # have not proper way to check that. related to issue 3858.
272 #
273 # The other caller is __init__ that have no _phaserevs initialized
274 # anyway. If this change we should consider adding a dedicated
275 # "destroyed" function to phasecache or a proper cache key mechanisme
276 # (see branchmap one)
277 self._phaserevs = None
270
278
271 def advanceboundary(repo, targetphase, nodes):
279 def advanceboundary(repo, targetphase, nodes):
272 """Add nodes to a phase changing other nodes phases if necessary.
280 """Add nodes to a phase changing other nodes phases if necessary.
273
281
274 This function move boundary *forward* this means that all nodes
282 This function move boundary *forward* this means that all nodes
275 are set in the target phase or kept in a *lower* phase.
283 are set in the target phase or kept in a *lower* phase.
276
284
277 Simplify boundary to contains phase roots only."""
285 Simplify boundary to contains phase roots only."""
278 phcache = repo._phasecache.copy()
286 phcache = repo._phasecache.copy()
279 phcache.advanceboundary(repo, targetphase, nodes)
287 phcache.advanceboundary(repo, targetphase, nodes)
280 repo._phasecache.replace(phcache)
288 repo._phasecache.replace(phcache)
281
289
282 def retractboundary(repo, targetphase, nodes):
290 def retractboundary(repo, targetphase, nodes):
283 """Set nodes back to a phase changing other nodes phases if
291 """Set nodes back to a phase changing other nodes phases if
284 necessary.
292 necessary.
285
293
286 This function move boundary *backward* this means that all nodes
294 This function move boundary *backward* this means that all nodes
287 are set in the target phase or kept in a *higher* phase.
295 are set in the target phase or kept in a *higher* phase.
288
296
289 Simplify boundary to contains phase roots only."""
297 Simplify boundary to contains phase roots only."""
290 phcache = repo._phasecache.copy()
298 phcache = repo._phasecache.copy()
291 phcache.retractboundary(repo, targetphase, nodes)
299 phcache.retractboundary(repo, targetphase, nodes)
292 repo._phasecache.replace(phcache)
300 repo._phasecache.replace(phcache)
293
301
294 def listphases(repo):
302 def listphases(repo):
295 """List phases root for serialization over pushkey"""
303 """List phases root for serialization over pushkey"""
296 keys = {}
304 keys = {}
297 value = '%i' % draft
305 value = '%i' % draft
298 for root in repo._phasecache.phaseroots[draft]:
306 for root in repo._phasecache.phaseroots[draft]:
299 keys[hex(root)] = value
307 keys[hex(root)] = value
300
308
301 if repo.ui.configbool('phases', 'publish', True):
309 if repo.ui.configbool('phases', 'publish', True):
302 # Add an extra data to let remote know we are a publishing
310 # Add an extra data to let remote know we are a publishing
303 # repo. Publishing repo can't just pretend they are old repo.
311 # repo. Publishing repo can't just pretend they are old repo.
304 # When pushing to a publishing repo, the client still need to
312 # When pushing to a publishing repo, the client still need to
305 # push phase boundary
313 # push phase boundary
306 #
314 #
307 # Push do not only push changeset. It also push phase data.
315 # Push do not only push changeset. It also push phase data.
308 # New phase data may apply to common changeset which won't be
316 # New phase data may apply to common changeset which won't be
309 # push (as they are common). Here is a very simple example:
317 # push (as they are common). Here is a very simple example:
310 #
318 #
311 # 1) repo A push changeset X as draft to repo B
319 # 1) repo A push changeset X as draft to repo B
312 # 2) repo B make changeset X public
320 # 2) repo B make changeset X public
313 # 3) repo B push to repo A. X is not pushed but the data that
321 # 3) repo B push to repo A. X is not pushed but the data that
314 # X as now public should
322 # X as now public should
315 #
323 #
316 # The server can't handle it on it's own as it has no idea of
324 # The server can't handle it on it's own as it has no idea of
317 # client phase data.
325 # client phase data.
318 keys['publishing'] = 'True'
326 keys['publishing'] = 'True'
319 return keys
327 return keys
320
328
321 def pushphase(repo, nhex, oldphasestr, newphasestr):
329 def pushphase(repo, nhex, oldphasestr, newphasestr):
322 """List phases root for serialization over pushkey"""
330 """List phases root for serialization over pushkey"""
323 repo = repo.unfiltered()
331 repo = repo.unfiltered()
324 lock = repo.lock()
332 lock = repo.lock()
325 try:
333 try:
326 currentphase = repo[nhex].phase()
334 currentphase = repo[nhex].phase()
327 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
335 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
328 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
336 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
329 if currentphase == oldphase and newphase < oldphase:
337 if currentphase == oldphase and newphase < oldphase:
330 advanceboundary(repo, newphase, [bin(nhex)])
338 advanceboundary(repo, newphase, [bin(nhex)])
331 return 1
339 return 1
332 elif currentphase == newphase:
340 elif currentphase == newphase:
333 # raced, but got correct result
341 # raced, but got correct result
334 return 1
342 return 1
335 else:
343 else:
336 return 0
344 return 0
337 finally:
345 finally:
338 lock.release()
346 lock.release()
339
347
340 def analyzeremotephases(repo, subset, roots):
348 def analyzeremotephases(repo, subset, roots):
341 """Compute phases heads and root in a subset of node from root dict
349 """Compute phases heads and root in a subset of node from root dict
342
350
343 * subset is heads of the subset
351 * subset is heads of the subset
344 * roots is {<nodeid> => phase} mapping. key and value are string.
352 * roots is {<nodeid> => phase} mapping. key and value are string.
345
353
346 Accept unknown element input
354 Accept unknown element input
347 """
355 """
348 repo = repo.unfiltered()
356 repo = repo.unfiltered()
349 # build list from dictionary
357 # build list from dictionary
350 draftroots = []
358 draftroots = []
351 nodemap = repo.changelog.nodemap # to filter unknown nodes
359 nodemap = repo.changelog.nodemap # to filter unknown nodes
352 for nhex, phase in roots.iteritems():
360 for nhex, phase in roots.iteritems():
353 if nhex == 'publishing': # ignore data related to publish option
361 if nhex == 'publishing': # ignore data related to publish option
354 continue
362 continue
355 node = bin(nhex)
363 node = bin(nhex)
356 phase = int(phase)
364 phase = int(phase)
357 if phase == 0:
365 if phase == 0:
358 if node != nullid:
366 if node != nullid:
359 repo.ui.warn(_('ignoring inconsistent public root'
367 repo.ui.warn(_('ignoring inconsistent public root'
360 ' from remote: %s\n') % nhex)
368 ' from remote: %s\n') % nhex)
361 elif phase == 1:
369 elif phase == 1:
362 if node in nodemap:
370 if node in nodemap:
363 draftroots.append(node)
371 draftroots.append(node)
364 else:
372 else:
365 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
373 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
366 % (phase, nhex))
374 % (phase, nhex))
367 # compute heads
375 # compute heads
368 publicheads = newheads(repo, subset, draftroots)
376 publicheads = newheads(repo, subset, draftroots)
369 return publicheads, draftroots
377 return publicheads, draftroots
370
378
371 def newheads(repo, heads, roots):
379 def newheads(repo, heads, roots):
372 """compute new head of a subset minus another
380 """compute new head of a subset minus another
373
381
374 * `heads`: define the first subset
382 * `heads`: define the first subset
375 * `roots`: define the second we subtract from the first"""
383 * `roots`: define the second we subtract from the first"""
376 repo = repo.unfiltered()
384 repo = repo.unfiltered()
377 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
385 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
378 heads, roots, roots, heads)
386 heads, roots, roots, heads)
379 return [c.node() for c in revset]
387 return [c.node() for c in revset]
380
388
381
389
382 def newcommitphase(ui):
390 def newcommitphase(ui):
383 """helper to get the target phase of new commit
391 """helper to get the target phase of new commit
384
392
385 Handle all possible values for the phases.new-commit options.
393 Handle all possible values for the phases.new-commit options.
386
394
387 """
395 """
388 v = ui.config('phases', 'new-commit', draft)
396 v = ui.config('phases', 'new-commit', draft)
389 try:
397 try:
390 return phasenames.index(v)
398 return phasenames.index(v)
391 except ValueError:
399 except ValueError:
392 try:
400 try:
393 return int(v)
401 return int(v)
394 except ValueError:
402 except ValueError:
395 msg = _("phases.new-commit: not a valid phase name ('%s')")
403 msg = _("phases.new-commit: not a valid phase name ('%s')")
396 raise error.ConfigError(msg % v)
404 raise error.ConfigError(msg % v)
397
405
398 def hassecret(repo):
406 def hassecret(repo):
399 """utility function that check if a repo have any secret changeset."""
407 """utility function that check if a repo have any secret changeset."""
400 return bool(repo._phasecache.phaseroots[2])
408 return bool(repo._phasecache.phaseroots[2])
@@ -1,387 +1,496 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > graphlog=
3 > graphlog=
4 > rebase=
4 > rebase=
5 > mq=
5 > mq=
6 >
6 >
7 > [phases]
7 > [phases]
8 > publish=False
8 > publish=False
9 >
9 >
10 > [alias]
10 > [alias]
11 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
11 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
12 > theads = heads --template "{rev}: '{desc}' {branches}\n"
12 > theads = heads --template "{rev}: '{desc}' {branches}\n"
13 > EOF
13 > EOF
14
14
15 $ hg init a
15 $ hg init a
16 $ cd a
16 $ cd a
17
17
18 $ echo a > a
18 $ echo a > a
19 $ hg ci -Am A
19 $ hg ci -Am A
20 adding a
20 adding a
21
21
22 $ hg branch branch1
22 $ hg branch branch1
23 marked working directory as branch branch1
23 marked working directory as branch branch1
24 (branches are permanent and global, did you want a bookmark?)
24 (branches are permanent and global, did you want a bookmark?)
25 $ hg ci -m 'branch1'
25 $ hg ci -m 'branch1'
26
26
27 $ echo b > b
27 $ echo b > b
28 $ hg ci -Am B
28 $ hg ci -Am B
29 adding b
29 adding b
30
30
31 $ hg up -q 0
31 $ hg up -q 0
32
32
33 $ hg branch branch2
33 $ hg branch branch2
34 marked working directory as branch branch2
34 marked working directory as branch branch2
35 (branches are permanent and global, did you want a bookmark?)
35 (branches are permanent and global, did you want a bookmark?)
36 $ hg ci -m 'branch2'
36 $ hg ci -m 'branch2'
37
37
38 $ echo c > C
38 $ echo c > C
39 $ hg ci -Am C
39 $ hg ci -Am C
40 adding C
40 adding C
41
41
42 $ hg up -q 2
42 $ hg up -q 2
43
43
44 $ hg branch -f branch2
44 $ hg branch -f branch2
45 marked working directory as branch branch2
45 marked working directory as branch branch2
46 (branches are permanent and global, did you want a bookmark?)
46 (branches are permanent and global, did you want a bookmark?)
47 $ echo d > d
47 $ echo d > d
48 $ hg ci -Am D
48 $ hg ci -Am D
49 adding d
49 adding d
50 created new head
50 created new head
51
51
52 $ echo e > e
52 $ echo e > e
53 $ hg ci -Am E
53 $ hg ci -Am E
54 adding e
54 adding e
55
55
56 $ hg update default
56 $ hg update default
57 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
57 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
58
58
59 $ hg branch branch3
59 $ hg branch branch3
60 marked working directory as branch branch3
60 marked working directory as branch branch3
61 (branches are permanent and global, did you want a bookmark?)
61 (branches are permanent and global, did you want a bookmark?)
62 $ hg ci -m 'branch3'
62 $ hg ci -m 'branch3'
63
63
64 $ echo f > f
64 $ echo f > f
65 $ hg ci -Am F
65 $ hg ci -Am F
66 adding f
66 adding f
67
67
68 $ cd ..
68 $ cd ..
69
69
70
70
71 Rebase part of branch2 (5-6) onto branch3 (8):
71 Rebase part of branch2 (5-6) onto branch3 (8):
72
72
73 $ hg clone -q -u . a a1
73 $ hg clone -q -u . a a1
74 $ cd a1
74 $ cd a1
75
75
76 $ hg tglog
76 $ hg tglog
77 @ 8: 'F' branch3
77 @ 8: 'F' branch3
78 |
78 |
79 o 7: 'branch3' branch3
79 o 7: 'branch3' branch3
80 |
80 |
81 | o 6: 'E' branch2
81 | o 6: 'E' branch2
82 | |
82 | |
83 | o 5: 'D' branch2
83 | o 5: 'D' branch2
84 | |
84 | |
85 | | o 4: 'C' branch2
85 | | o 4: 'C' branch2
86 | | |
86 | | |
87 +---o 3: 'branch2' branch2
87 +---o 3: 'branch2' branch2
88 | |
88 | |
89 | o 2: 'B' branch1
89 | o 2: 'B' branch1
90 | |
90 | |
91 | o 1: 'branch1' branch1
91 | o 1: 'branch1' branch1
92 |/
92 |/
93 o 0: 'A'
93 o 0: 'A'
94
94
95 $ hg branches
95 $ hg branches
96 branch3 8:4666b71e8e32
96 branch3 8:4666b71e8e32
97 branch2 6:5097051d331d
97 branch2 6:5097051d331d
98 branch1 2:0a03079c47fd (inactive)
98 branch1 2:0a03079c47fd (inactive)
99 default 0:1994f17a630e (inactive)
99 default 0:1994f17a630e (inactive)
100
100
101 $ hg theads
101 $ hg theads
102 8: 'F' branch3
102 8: 'F' branch3
103 6: 'E' branch2
103 6: 'E' branch2
104 4: 'C' branch2
104 4: 'C' branch2
105 2: 'B' branch1
105 2: 'B' branch1
106 0: 'A'
106 0: 'A'
107
107
108 $ hg rebase -s 5 -d 8
108 $ hg rebase -s 5 -d 8
109 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
109 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
110
110
111 $ hg branches
111 $ hg branches
112 branch3 8:466cdfb14b62
112 branch3 8:466cdfb14b62
113 branch2 4:e4fdb121d036
113 branch2 4:e4fdb121d036
114 branch1 2:0a03079c47fd
114 branch1 2:0a03079c47fd
115 default 0:1994f17a630e (inactive)
115 default 0:1994f17a630e (inactive)
116
116
117 $ hg theads
117 $ hg theads
118 8: 'E' branch3
118 8: 'E' branch3
119 4: 'C' branch2
119 4: 'C' branch2
120 2: 'B' branch1
120 2: 'B' branch1
121 0: 'A'
121 0: 'A'
122
122
123 $ hg tglog
123 $ hg tglog
124 @ 8: 'E' branch3
124 @ 8: 'E' branch3
125 |
125 |
126 o 7: 'D' branch3
126 o 7: 'D' branch3
127 |
127 |
128 o 6: 'F' branch3
128 o 6: 'F' branch3
129 |
129 |
130 o 5: 'branch3' branch3
130 o 5: 'branch3' branch3
131 |
131 |
132 | o 4: 'C' branch2
132 | o 4: 'C' branch2
133 | |
133 | |
134 | o 3: 'branch2' branch2
134 | o 3: 'branch2' branch2
135 |/
135 |/
136 | o 2: 'B' branch1
136 | o 2: 'B' branch1
137 | |
137 | |
138 | o 1: 'branch1' branch1
138 | o 1: 'branch1' branch1
139 |/
139 |/
140 o 0: 'A'
140 o 0: 'A'
141
141
142 $ cd ..
142 $ cd ..
143
143
144
144
145 Rebase head of branch3 (8) onto branch2 (6):
145 Rebase head of branch3 (8) onto branch2 (6):
146
146
147 $ hg clone -q -u . a a2
147 $ hg clone -q -u . a a2
148 $ cd a2
148 $ cd a2
149
149
150 $ hg tglog
150 $ hg tglog
151 @ 8: 'F' branch3
151 @ 8: 'F' branch3
152 |
152 |
153 o 7: 'branch3' branch3
153 o 7: 'branch3' branch3
154 |
154 |
155 | o 6: 'E' branch2
155 | o 6: 'E' branch2
156 | |
156 | |
157 | o 5: 'D' branch2
157 | o 5: 'D' branch2
158 | |
158 | |
159 | | o 4: 'C' branch2
159 | | o 4: 'C' branch2
160 | | |
160 | | |
161 +---o 3: 'branch2' branch2
161 +---o 3: 'branch2' branch2
162 | |
162 | |
163 | o 2: 'B' branch1
163 | o 2: 'B' branch1
164 | |
164 | |
165 | o 1: 'branch1' branch1
165 | o 1: 'branch1' branch1
166 |/
166 |/
167 o 0: 'A'
167 o 0: 'A'
168
168
169 $ hg rebase -s 8 -d 6
169 $ hg rebase -s 8 -d 6
170 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
170 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
171
171
172 $ hg branches
172 $ hg branches
173 branch2 8:6b4bdc1b5ac0
173 branch2 8:6b4bdc1b5ac0
174 branch3 7:653b9feb4616
174 branch3 7:653b9feb4616
175 branch1 2:0a03079c47fd (inactive)
175 branch1 2:0a03079c47fd (inactive)
176 default 0:1994f17a630e (inactive)
176 default 0:1994f17a630e (inactive)
177
177
178 $ hg theads
178 $ hg theads
179 8: 'F' branch2
179 8: 'F' branch2
180 7: 'branch3' branch3
180 7: 'branch3' branch3
181 4: 'C' branch2
181 4: 'C' branch2
182 2: 'B' branch1
182 2: 'B' branch1
183 0: 'A'
183 0: 'A'
184
184
185 $ hg tglog
185 $ hg tglog
186 @ 8: 'F' branch2
186 @ 8: 'F' branch2
187 |
187 |
188 | o 7: 'branch3' branch3
188 | o 7: 'branch3' branch3
189 | |
189 | |
190 o | 6: 'E' branch2
190 o | 6: 'E' branch2
191 | |
191 | |
192 o | 5: 'D' branch2
192 o | 5: 'D' branch2
193 | |
193 | |
194 | | o 4: 'C' branch2
194 | | o 4: 'C' branch2
195 | | |
195 | | |
196 | | o 3: 'branch2' branch2
196 | | o 3: 'branch2' branch2
197 | |/
197 | |/
198 o | 2: 'B' branch1
198 o | 2: 'B' branch1
199 | |
199 | |
200 o | 1: 'branch1' branch1
200 o | 1: 'branch1' branch1
201 |/
201 |/
202 o 0: 'A'
202 o 0: 'A'
203
203
204 $ hg verify -q
204 $ hg verify -q
205
205
206 $ cd ..
206 $ cd ..
207
207
208
208
209 Rebase entire branch3 (7-8) onto branch2 (6):
209 Rebase entire branch3 (7-8) onto branch2 (6):
210
210
211 $ hg clone -q -u . a a3
211 $ hg clone -q -u . a a3
212 $ cd a3
212 $ cd a3
213
213
214 $ hg tglog
214 $ hg tglog
215 @ 8: 'F' branch3
215 @ 8: 'F' branch3
216 |
216 |
217 o 7: 'branch3' branch3
217 o 7: 'branch3' branch3
218 |
218 |
219 | o 6: 'E' branch2
219 | o 6: 'E' branch2
220 | |
220 | |
221 | o 5: 'D' branch2
221 | o 5: 'D' branch2
222 | |
222 | |
223 | | o 4: 'C' branch2
223 | | o 4: 'C' branch2
224 | | |
224 | | |
225 +---o 3: 'branch2' branch2
225 +---o 3: 'branch2' branch2
226 | |
226 | |
227 | o 2: 'B' branch1
227 | o 2: 'B' branch1
228 | |
228 | |
229 | o 1: 'branch1' branch1
229 | o 1: 'branch1' branch1
230 |/
230 |/
231 o 0: 'A'
231 o 0: 'A'
232
232
233 $ hg rebase -s 7 -d 6
233 $ hg rebase -s 7 -d 6
234 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
234 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
235
235
236 $ hg branches
236 $ hg branches
237 branch2 7:6b4bdc1b5ac0
237 branch2 7:6b4bdc1b5ac0
238 branch1 2:0a03079c47fd (inactive)
238 branch1 2:0a03079c47fd (inactive)
239 default 0:1994f17a630e (inactive)
239 default 0:1994f17a630e (inactive)
240
240
241 $ hg theads
241 $ hg theads
242 7: 'F' branch2
242 7: 'F' branch2
243 4: 'C' branch2
243 4: 'C' branch2
244 2: 'B' branch1
244 2: 'B' branch1
245 0: 'A'
245 0: 'A'
246
246
247 $ hg tglog
247 $ hg tglog
248 @ 7: 'F' branch2
248 @ 7: 'F' branch2
249 |
249 |
250 o 6: 'E' branch2
250 o 6: 'E' branch2
251 |
251 |
252 o 5: 'D' branch2
252 o 5: 'D' branch2
253 |
253 |
254 | o 4: 'C' branch2
254 | o 4: 'C' branch2
255 | |
255 | |
256 | o 3: 'branch2' branch2
256 | o 3: 'branch2' branch2
257 | |
257 | |
258 o | 2: 'B' branch1
258 o | 2: 'B' branch1
259 | |
259 | |
260 o | 1: 'branch1' branch1
260 o | 1: 'branch1' branch1
261 |/
261 |/
262 o 0: 'A'
262 o 0: 'A'
263
263
264 $ hg verify -q
264 $ hg verify -q
265
265
266 Stripping multiple branches in one go bypasses the fast-case code to
266 Stripping multiple branches in one go bypasses the fast-case code to
267 update the branch cache.
267 update the branch cache.
268
268
269 $ hg strip 2
269 $ hg strip 2
270 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
270 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
271 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
271 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
272
272
273 $ hg tglog
273 $ hg tglog
274 o 3: 'C' branch2
274 o 3: 'C' branch2
275 |
275 |
276 o 2: 'branch2' branch2
276 o 2: 'branch2' branch2
277 |
277 |
278 | @ 1: 'branch1' branch1
278 | @ 1: 'branch1' branch1
279 |/
279 |/
280 o 0: 'A'
280 o 0: 'A'
281
281
282
282
283 $ hg branches
283 $ hg branches
284 branch2 3:e4fdb121d036
284 branch2 3:e4fdb121d036
285 branch1 1:63379ac49655
285 branch1 1:63379ac49655
286 default 0:1994f17a630e (inactive)
286 default 0:1994f17a630e (inactive)
287
287
288 $ hg theads
288 $ hg theads
289 3: 'C' branch2
289 3: 'C' branch2
290 1: 'branch1' branch1
290 1: 'branch1' branch1
291 0: 'A'
291 0: 'A'
292
292
293 Fast path branchcache code should not be invoked if branches stripped is not
293 Fast path branchcache code should not be invoked if branches stripped is not
294 the same as branches remaining.
294 the same as branches remaining.
295
295
296 $ hg init b
296 $ hg init b
297 $ cd b
297 $ cd b
298
298
299 $ hg branch branch1
299 $ hg branch branch1
300 marked working directory as branch branch1
300 marked working directory as branch branch1
301 (branches are permanent and global, did you want a bookmark?)
301 (branches are permanent and global, did you want a bookmark?)
302 $ hg ci -m 'branch1'
302 $ hg ci -m 'branch1'
303
303
304 $ hg branch branch2
304 $ hg branch branch2
305 marked working directory as branch branch2
305 marked working directory as branch branch2
306 (branches are permanent and global, did you want a bookmark?)
306 (branches are permanent and global, did you want a bookmark?)
307 $ hg ci -m 'branch2'
307 $ hg ci -m 'branch2'
308
308
309 $ hg branch -f branch1
309 $ hg branch -f branch1
310 marked working directory as branch branch1
310 marked working directory as branch branch1
311 (branches are permanent and global, did you want a bookmark?)
311 (branches are permanent and global, did you want a bookmark?)
312
312
313 $ echo a > A
313 $ echo a > A
314 $ hg ci -Am A
314 $ hg ci -Am A
315 adding A
315 adding A
316 created new head
316 created new head
317
317
318 $ hg tglog
318 $ hg tglog
319 @ 2: 'A' branch1
319 @ 2: 'A' branch1
320 |
320 |
321 o 1: 'branch2' branch2
321 o 1: 'branch2' branch2
322 |
322 |
323 o 0: 'branch1' branch1
323 o 0: 'branch1' branch1
324
324
325
325
326 $ hg theads
326 $ hg theads
327 2: 'A' branch1
327 2: 'A' branch1
328 1: 'branch2' branch2
328 1: 'branch2' branch2
329
329
330 $ hg strip 2
330 $ hg strip 2
331 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
331 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
332 saved backup bundle to $TESTTMP/a3/b/.hg/strip-backup/*-backup.hg (glob)
332 saved backup bundle to $TESTTMP/a3/b/.hg/strip-backup/*-backup.hg (glob)
333
333
334 $ hg theads
334 $ hg theads
335 1: 'branch2' branch2
335 1: 'branch2' branch2
336 0: 'branch1' branch1
336 0: 'branch1' branch1
337
337
338
338
339 Make sure requesting to strip a revision already stripped does not confuse things.
339 Make sure requesting to strip a revision already stripped does not confuse things.
340 Try both orders.
340 Try both orders.
341
341
342 $ cd ..
342 $ cd ..
343
343
344 $ hg init c
344 $ hg init c
345 $ cd c
345 $ cd c
346
346
347 $ echo a > a
347 $ echo a > a
348 $ hg ci -Am A
348 $ hg ci -Am A
349 adding a
349 adding a
350 $ echo b > b
350 $ echo b > b
351 $ hg ci -Am B
351 $ hg ci -Am B
352 adding b
352 adding b
353 $ echo c > c
353 $ echo c > c
354 $ hg ci -Am C
354 $ hg ci -Am C
355 adding c
355 adding c
356 $ echo d > d
356 $ echo d > d
357 $ hg ci -Am D
357 $ hg ci -Am D
358 adding d
358 adding d
359 $ echo e > e
359 $ echo e > e
360 $ hg ci -Am E
360 $ hg ci -Am E
361 adding e
361 adding e
362
362
363 $ hg tglog
363 $ hg tglog
364 @ 4: 'E'
364 @ 4: 'E'
365 |
365 |
366 o 3: 'D'
366 o 3: 'D'
367 |
367 |
368 o 2: 'C'
368 o 2: 'C'
369 |
369 |
370 o 1: 'B'
370 o 1: 'B'
371 |
371 |
372 o 0: 'A'
372 o 0: 'A'
373
373
374
374
375 $ hg strip 3 4
375 $ hg strip 3 4
376 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
376 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
377 saved backup bundle to $TESTTMP/a3/c/.hg/strip-backup/*-backup.hg (glob)
377 saved backup bundle to $TESTTMP/a3/c/.hg/strip-backup/*-backup.hg (glob)
378
378
379 $ hg theads
379 $ hg theads
380 2: 'C'
380 2: 'C'
381
381
382 $ hg strip 2 1
382 $ hg strip 2 1
383 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
383 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
384 saved backup bundle to $TESTTMP/a3/c/.hg/strip-backup/*-backup.hg (glob)
384 saved backup bundle to $TESTTMP/a3/c/.hg/strip-backup/*-backup.hg (glob)
385
385
386 $ hg theads
386 $ hg theads
387 0: 'A'
387 0: 'A'
388
389 Make sure rebase does not break for phase/filter related reason
390 ----------------------------------------------------------------
391 (issue3858)
392
393 $ cd ..
394
395 $ cat >> $HGRCPATH << EOF
396 > [ui]
397 > logtemplate={rev} {desc} {phase}\n
398 > EOF
399 $ cat $HGRCPATH
400 [ui]
401 slash = True
402 interactive = False
403 [defaults]
404 backout = -d "0 0"
405 commit = -d "0 0"
406 tag = -d "0 0"
407 [extensions]
408 graphlog=
409 rebase=
410 mq=
411
412 [phases]
413 publish=False
414
415 [alias]
416 tglog = log -G --template "{rev}: '{desc}' {branches}\n"
417 theads = heads --template "{rev}: '{desc}' {branches}\n"
418 [ui]
419 logtemplate={rev} {desc} {phase}\n
420
421
422 $ hg init c4
423 $ cd c4
424
425 $ echo a > a
426 $ hg ci -Am A
427 adding a
428 $ echo b > b
429 $ hg ci -Am B
430 adding b
431 $ hg up 0
432 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
433 $ echo c > c
434 $ hg ci -Am C
435 adding c
436 created new head
437 $ hg up 1
438 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
439 $ hg merge
440 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
441 (branch merge, don't forget to commit)
442 $ hg ci -m d
443 $ hg up 2
444 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
445 $ echo e > e
446 $ hg ci -Am E
447 adding e
448 created new head
449 $ hg merge 3
450 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
451 (branch merge, don't forget to commit)
452 $ hg ci -m F
453 $ hg up 3
454 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
455 $ echo g > g
456 $ hg ci -Am G
457 adding g
458 created new head
459 $ echo h > h
460 $ hg ci -Am H
461 adding h
462 $ hg up 5
463 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
464 $ echo i > i
465 $ hg ci -Am I
466 adding i
467
468 Turn most changeset public
469
470 $ hg ph -p 7
471
472 $ hg heads
473 8 I draft
474 7 H public
475 $ hg log -G
476 @ 8 I draft
477 |
478 | o 7 H public
479 | |
480 | o 6 G public
481 | |
482 o | 5 F draft
483 |\|
484 o | 4 E draft
485 | |
486 | o 3 d public
487 |/|
488 o | 2 C public
489 | |
490 | o 1 B public
491 |/
492 o 0 A public
493
494
495 $ hg rebase --dest 7 --source 5
496 saved backup bundle to $TESTTMP/a3/c4/.hg/strip-backup/*-backup.hg (glob)
General Comments 0
You need to be logged in to leave comments. Login now