summaryrefslogtreecommitdiff
path: root/mercurial/obsolete.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/obsolete.py')
-rw-r--r--mercurial/obsolete.py331
1 files changed, 0 insertions, 331 deletions
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
deleted file mode 100644
index aea116d..0000000
--- a/mercurial/obsolete.py
+++ /dev/null
@@ -1,331 +0,0 @@
-# obsolete.py - obsolete markers handling
-#
-# Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
-# Logilab SA <contact@logilab.fr>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-"""Obsolete markers handling
-
-An obsolete marker maps an old changeset to a list of new
-changesets. If the list of new changesets is empty, the old changeset
-is said to be "killed". Otherwise, the old changeset is being
-"replaced" by the new changesets.
-
-Obsolete markers can be used to record and distribute changeset graph
-transformations performed by history rewriting operations, and help
-building new tools to reconciliate conflicting rewriting actions. To
-facilitate conflicts resolution, markers include various annotations
-besides old and news changeset identifiers, such as creation date or
-author name.
-
-
-Format
-------
-
-Markers are stored in an append-only file stored in
-'.hg/store/obsstore'.
-
-The file starts with a version header:
-
-- 1 unsigned byte: version number, starting at zero.
-
-
-The header is followed by the markers. Each marker is made of:
-
-- 1 unsigned byte: number of new changesets "R", could be zero.
-
-- 1 unsigned 32-bits integer: metadata size "M" in bytes.
-
-- 1 byte: a bit field. It is reserved for flags used in obsolete
- markers common operations, to avoid repeated decoding of metadata
- entries.
-
-- 20 bytes: obsoleted changeset identifier.
-
-- N*20 bytes: new changesets identifiers.
-
-- M bytes: metadata as a sequence of nul-terminated strings. Each
- string contains a key and a value, separated by a color ':', without
- additional encoding. Keys cannot contain '\0' or ':' and values
- cannot contain '\0'.
-"""
-import struct
-from mercurial import util, base85
-from i18n import _
-
-# the obsolete feature is not mature enought to be enabled by default.
-# you have to rely on third party extension extension to enable this.
-_enabled = False
-
-_pack = struct.pack
-_unpack = struct.unpack
-
-# the obsolete feature is not mature enought to be enabled by default.
-# you have to rely on third party extension extension to enable this.
-_enabled = False
-
-# data used for parsing and writing
-_fmversion = 0
-_fmfixed = '>BIB20s'
-_fmnode = '20s'
-_fmfsize = struct.calcsize(_fmfixed)
-_fnodesize = struct.calcsize(_fmnode)
-
-def _readmarkers(data):
- """Read and enumerate markers from raw data"""
- off = 0
- diskversion = _unpack('>B', data[off:off + 1])[0]
- off += 1
- if diskversion != _fmversion:
- raise util.Abort(_('parsing obsolete marker: unknown version %r')
- % diskversion)
-
- # Loop on markers
- l = len(data)
- while off + _fmfsize <= l:
- # read fixed part
- cur = data[off:off + _fmfsize]
- off += _fmfsize
- nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
- # read replacement
- sucs = ()
- if nbsuc:
- s = (_fnodesize * nbsuc)
- cur = data[off:off + s]
- sucs = _unpack(_fmnode * nbsuc, cur)
- off += s
- # read metadata
- # (metadata will be decoded on demand)
- metadata = data[off:off + mdsize]
- if len(metadata) != mdsize:
- raise util.Abort(_('parsing obsolete marker: metadata is too '
- 'short, %d bytes expected, got %d')
- % (mdsize, len(metadata)))
- off += mdsize
- yield (pre, sucs, flags, metadata)
-
-def encodemeta(meta):
- """Return encoded metadata string to string mapping.
-
- Assume no ':' in key and no '\0' in both key and value."""
- for key, value in meta.iteritems():
- if ':' in key or '\0' in key:
- raise ValueError("':' and '\0' are forbidden in metadata key'")
- if '\0' in value:
- raise ValueError("':' are forbidden in metadata value'")
- return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
-
-def decodemeta(data):
- """Return string to string dictionary from encoded version."""
- d = {}
- for l in data.split('\0'):
- if l:
- key, value = l.split(':')
- d[key] = value
- return d
-
-class marker(object):
- """Wrap obsolete marker raw data"""
-
- def __init__(self, repo, data):
- # the repo argument will be used to create changectx in later version
- self._repo = repo
- self._data = data
- self._decodedmeta = None
-
- def precnode(self):
- """Precursor changeset node identifier"""
- return self._data[0]
-
- def succnodes(self):
- """List of successor changesets node identifiers"""
- return self._data[1]
-
- def metadata(self):
- """Decoded metadata dictionary"""
- if self._decodedmeta is None:
- self._decodedmeta = decodemeta(self._data[3])
- return self._decodedmeta
-
- def date(self):
- """Creation date as (unixtime, offset)"""
- parts = self.metadata()['date'].split(' ')
- return (float(parts[0]), int(parts[1]))
-
-class obsstore(object):
- """Store obsolete markers
-
- Markers can be accessed with two mappings:
- - precursors: old -> set(new)
- - successors: new -> set(old)
- """
-
- def __init__(self, sopener):
- self._all = []
- # new markers to serialize
- self.precursors = {}
- self.successors = {}
- self.sopener = sopener
- data = sopener.tryread('obsstore')
- if data:
- self._load(_readmarkers(data))
-
- def __iter__(self):
- return iter(self._all)
-
- def __nonzero__(self):
- return bool(self._all)
-
- def create(self, transaction, prec, succs=(), flag=0, metadata=None):
- """obsolete: add a new obsolete marker
-
- * ensuring it is hashable
- * check mandatory metadata
- * encode metadata
- """
- if metadata is None:
- metadata = {}
- if len(prec) != 20:
- raise ValueError(prec)
- for succ in succs:
- if len(succ) != 20:
- raise ValueError(succ)
- marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
- self.add(transaction, [marker])
-
- def add(self, transaction, markers):
- """Add new markers to the store
-
- Take care of filtering duplicate.
- Return the number of new marker."""
- if not _enabled:
- raise util.Abort('obsolete feature is not enabled on this repo')
- new = [m for m in markers if m not in self._all]
- if new:
- f = self.sopener('obsstore', 'ab')
- try:
- # Whether the file's current position is at the begin or at
- # the end after opening a file for appending is implementation
- # defined. So we must seek to the end before calling tell(),
- # or we may get a zero offset for non-zero sized files on
- # some platforms (issue3543).
- f.seek(0, 2) # os.SEEK_END
- offset = f.tell()
- transaction.add('obsstore', offset)
- # offset == 0: new file - add the version header
- for bytes in _encodemarkers(new, offset == 0):
- f.write(bytes)
- finally:
- # XXX: f.close() == filecache invalidation == obsstore rebuilt.
- # call 'filecacheentry.refresh()' here
- f.close()
- self._load(new)
- return len(new)
-
- def mergemarkers(self, transation, data):
- markers = _readmarkers(data)
- self.add(transation, markers)
-
- def _load(self, markers):
- for mark in markers:
- self._all.append(mark)
- pre, sucs = mark[:2]
- self.precursors.setdefault(pre, set()).add(mark)
- for suc in sucs:
- self.successors.setdefault(suc, set()).add(mark)
-
-def _encodemarkers(markers, addheader=False):
- # Kept separate from flushmarkers(), it will be reused for
- # markers exchange.
- if addheader:
- yield _pack('>B', _fmversion)
- for marker in markers:
- yield _encodeonemarker(marker)
-
-
-def _encodeonemarker(marker):
- pre, sucs, flags, metadata = marker
- nbsuc = len(sucs)
- format = _fmfixed + (_fmnode * nbsuc)
- data = [nbsuc, len(metadata), flags, pre]
- data.extend(sucs)
- return _pack(format, *data) + metadata
-
-# arbitrary picked to fit into 8K limit from HTTP server
-# you have to take in account:
-# - the version header
-# - the base85 encoding
-_maxpayload = 5300
-
-def listmarkers(repo):
- """List markers over pushkey"""
- if not repo.obsstore:
- return {}
- keys = {}
- parts = []
- currentlen = _maxpayload * 2 # ensure we create a new part
- for marker in repo.obsstore:
- nextdata = _encodeonemarker(marker)
- if (len(nextdata) + currentlen > _maxpayload):
- currentpart = []
- currentlen = 0
- parts.append(currentpart)
- currentpart.append(nextdata)
- currentlen += len(nextdata)
- for idx, part in enumerate(reversed(parts)):
- data = ''.join([_pack('>B', _fmversion)] + part)
- keys['dump%i' % idx] = base85.b85encode(data)
- return keys
-
-def pushmarker(repo, key, old, new):
- """Push markers over pushkey"""
- if not key.startswith('dump'):
- repo.ui.warn(_('unknown key: %r') % key)
- return 0
- if old:
- repo.ui.warn(_('unexpected old value') % key)
- return 0
- data = base85.b85decode(new)
- lock = repo.lock()
- try:
- tr = repo.transaction('pushkey: obsolete markers')
- try:
- repo.obsstore.mergemarkers(tr, data)
- tr.close()
- return 1
- finally:
- tr.release()
- finally:
- lock.release()
-
-def allmarkers(repo):
- """all obsolete markers known in a repository"""
- for markerdata in repo.obsstore:
- yield marker(repo, markerdata)
-
-def precursormarkers(ctx):
- """obsolete marker making this changeset obsolete"""
- for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
- yield marker(ctx._repo, data)
-
-def successormarkers(ctx):
- """obsolete marker marking this changeset as a successors"""
- for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
- yield marker(ctx._repo, data)
-
-def anysuccessors(obsstore, node):
- """Yield every successor of <node>
-
- This this a linear yield unsuitable to detect splitted changeset."""
- remaining = set([node])
- seen = set(remaining)
- while remaining:
- current = remaining.pop()
- yield current
- for mark in obsstore.precursors.get(current, ()):
- for suc in mark[1]:
- if suc not in seen:
- seen.add(suc)
- remaining.add(suc)