@@ -158,6 +158,7 @@
changegroup,
error,
obsolete,
+ phases,
pushkey,
pycompat,
tags,
@@ -178,6 +179,8 @@
_fpayloadsize = '>i'
_fpartparamcount = '>BB'
+_fphasesentry = '>i20s'
+
preferedchunksize = 4096
_parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
@@ -1387,6 +1390,13 @@
obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing)
buildobsmarkerspart(bundler, obsmarkers)
+ if opts.get('phases', False):
+ rootphases = phases.subsetphases(repo, outgoing.missing)
+ phasedata = []
+ for root, phase in rootphases.iteritems():
+ phasedata.append(_pack(_fphasesentry, phase, root))
+ bundler.newpart('phases', data=''.join(phasedata))
+
def addparttagsfnodescache(repo, bundler, outgoing):
# we include the tags fnode cache for the bundle changeset
# (as an optional parts)
@@ -1721,6 +1731,25 @@
kwargs[key] = inpart.params[key]
raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
+@parthandler('phases')
+def handlephases(op, inpart):
+ """apply phases from bundle part to repo"""
+ phaseroots = {}
+ entrysize = struct.calcsize(_fphasesentry)
+ while True:
+ entry = inpart.read(entrysize)
+ if len(entry) < entrysize:
+ if entry:
+ op.ui.debug('ignoring incomplete phase entry%s\n' % entry)
+ break
+ phase, node = struct.unpack(_fphasesentry, entry)
+ phaseroots[node] = phase
+ addednodes = []
+ for entry in op.records['changegroup']:
+ addednodes.extend(entry['addednodes'])
+ phases.updatephases(op.repo.unfiltered(), op.gettransaction(), phaseroots,
+ addednodes)
+
@parthandler('reply:pushkey', ('return', 'in-reply-to'))
def handlepushkeyreply(op, inpart):
"""retrieve the result of a pushkey request"""
@@ -1351,6 +1351,8 @@
contentopts = {'cg.version': cgversion}
if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker', False):
contentopts['obsolescence'] = True
+ if repo.ui.configbool('experimental', 'bundle-phases', False):
+ contentopts['phases'] = True
bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
contentopts, compression=bcompression,
compopts=compopts)
@@ -430,6 +430,41 @@
else:
return False
+def subsetphases(repo, subset):
+ """Finds the phase roots for a subset of a history
+
+ Returns a dict with a root as key and phase as value. The keys will
+ be the phases root within the nodes subset, plus the roots of the subset.
+ """
+ phases = {}
+ for phase in trackedphases:
+ for root in repo._phasecache.phaseroots[phase]:
+ if root in subset:
+ phases[root] = phase
+ for ctx in repo.set('roots(%ln)', subset):
+ phases[ctx.node()] = ctx.phase()
+ return phases
+
+def updatephases(repo, tr, phaseroots, addednodes):
+ """Updates the repo with the given phase roots (node->phase dict)"""
+ cl = repo.changelog
+
+ # First make all the added revisions secret because changegroup.apply()
+ # currently sets the phase to draft.
+ addedroots = [cl.node(rev) for rev in repo.revs('roots(%ln)', addednodes)]
+ retractboundary(repo, tr, secret, addedroots)
+
+ # Now advance phase boundaries of all but secret phase
+ nodesbyphase = [[] for i in allphases]
+ for node, phase in phaseroots.iteritems():
+ nodesbyphase[phase].append(node)
+ publicheads = repo.revs('heads((%ln::) - (%ln::))', nodesbyphase[public],
+ nodesbyphase[draft] + nodesbyphase[secret])
+ advanceboundary(repo, tr, public, [cl.node(rev) for rev in publicheads])
+ draftheads = repo.revs('heads((%ln::) - (%ln::))', nodesbyphase[draft],
+ nodesbyphase[secret])
+ advanceboundary(repo, tr, draft, [cl.node(rev) for rev in draftheads])
+
def analyzeremotephases(repo, subset, roots):
"""Compute phases heads and root in a subset of node from root dict
new file mode 100644
@@ -0,0 +1,148 @@
+ $ cat >> $HGRCPATH <<EOF
+ > usegeneraldelta=yes
+ > [experimental]
+ > bundle-phases=yes
+ > [extensions]
+ > strip=
+ > drawdag=$TESTDIR/drawdag.py
+ > EOF
+
+Set up repo with linear history
+ $ hg init linear
+ $ cd linear
+ $ hg debugdrawdag <<'EOF'
+ > E
+ > |
+ > D
+ > |
+ > C
+ > |
+ > B
+ > |
+ > A
+ > EOF
+ $ hg phase --public A
+ $ hg phase --force --secret D
+ $ hg log -G -T '{desc} {phase}\n'
+ o E secret
+ |
+ o D secret
+ |
+ o C draft
+ |
+ o B draft
+ |
+ o A public
+
+Phases are restored when unbundling
+ $ hg bundle --base B -r E bundle
+ 3 changesets found
+ $ hg strip --no-backup C
+ $ hg unbundle -q bundle
+ $ rm bundle
+ $ hg log -G -T '{desc} {phase}\n'
+ o E secret
+ |
+ o D secret
+ |
+ o C draft
+ |
+ o B draft
+ |
+ o A public
+
+Root revision's phase is preserved
+ $ hg bundle -a bundle
+ 5 changesets found
+ $ hg strip --no-backup A
+ $ hg unbundle -q bundle
+ $ rm bundle
+ $ hg log -G -T '{desc} {phase}\n'
+ o E secret
+ |
+ o D secret
+ |
+ o C draft
+ |
+ o B draft
+ |
+ o A public
+
+Direct transition from public to root can be restored
+ $ hg phase --public C
+ $ hg bundle -a bundle
+ 5 changesets found
+ $ hg strip --no-backup A
+ $ hg unbundle -q bundle
+ $ rm bundle
+ $ hg log -G -T '{desc} {phase}\n'
+ o E secret
+ |
+ o D secret
+ |
+ o C public
+ |
+ o B public
+ |
+ o A public
+
+Revisions within bundle preserve their phase even if parent changes its phase
+ $ hg phase --draft --force B
+ $ hg bundle --base B -r E bundle
+ 3 changesets found
+ $ hg strip --no-backup C
+ $ hg phase --public B
+ $ hg unbundle -q bundle
+ $ rm bundle
+ $ hg log -G -T '{desc} {phase}\n'
+ o E secret
+ |
+ o D secret
+ |
+ o C draft
+ |
+ o B public
+ |
+ o A public
+
+Phase of ancestors of stripped node get advanced to accommodate child
+ $ hg bundle --base B -r E bundle
+ 3 changesets found
+ $ hg strip --no-backup C
+ $ hg phase --force --secret B
+ $ hg unbundle -q bundle
+ $ rm bundle
+ $ hg log -G -T '{desc} {phase}\n'
+ o E secret
+ |
+ o D secret
+ |
+ o C draft
+ |
+ o B draft
+ |
+ o A public
+
+Unbundling advances phases of changesets even if they were already in the repo.
+To test that, create a bundle of everything in draft phase and then unbundle
+to see that secret becomes draft, but public remains public.
+ $ hg phase --draft --force A
+ $ hg phase --draft E
+ $ hg bundle -a bundle
+ 5 changesets found
+ $ hg phase --public A
+ $ hg phase --secret --force E
+ $ hg unbundle -q bundle
+ $ rm bundle
+ $ hg log -G -T '{desc} {phase}\n'
+ o E draft
+ |
+ o D draft
+ |
+ o C draft
+ |
+ o B draft
+ |
+ o A public
+
+ $ cd ..