16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
20
"""Versioned text file storage api."""
22
22
from copy import copy
23
23
from cStringIO import StringIO
25
26
from zlib import adler32
27
28
from bzrlib.lazy_import import lazy_import
457
461
if isinstance(version_ids, basestring):
458
462
version_ids = [version_ids]
459
463
raise NotImplementedError(self.get_ancestry)
461
465
def get_ancestry_with_ghosts(self, version_ids):
462
466
"""Return a list of all ancestors of given version(s). This
463
467
will not include the null revision.
465
469
Must raise RevisionNotPresent if any of the given versions are
466
470
not present in file history.
468
472
Ghosts that are known about will be included in ancestry list,
469
473
but are not explicitly marked.
471
475
raise NotImplementedError(self.get_ancestry_with_ghosts)
473
477
def get_parent_map(self, version_ids):
474
478
"""Get a map of the parents of version_ids.
538
542
unchanged Alive in both a and b (possibly created in both)
539
543
new-a Created in a
540
544
new-b Created in b
541
ghost-a Killed in a, unborn in b
545
ghost-a Killed in a, unborn in b
542
546
ghost-b Killed in b, unborn in a
543
547
irrelevant Not in either revision
545
549
raise NotImplementedError(VersionedFile.plan_merge)
547
551
def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
548
552
b_marker=TextMerge.B_MARKER):
549
553
return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
825
830
raise NotImplementedError(self.add_lines)
832
def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
833
"""Add a text to the store.
835
This is a private function for use by CommitBuilder.
837
:param key: The key tuple of the text to add. If the last element is
838
None, a CHK string will be generated during the addition.
839
:param parents: The parents key tuples of the text to add.
840
:param text: A string containing the text to be committed.
841
:param nostore_sha: Raise ExistingContent and do not add the lines to
842
the versioned file if the digest of the lines matches this.
843
:param random_id: If True a random id has been selected rather than
844
an id determined by some deterministic process such as a converter
845
from a foreign VCS. When True the backend may choose not to check
846
for uniqueness of the resulting key within the versioned file, so
847
this should only be done when the result is expected to be unique
849
:param check_content: If True, the lines supplied are verified to be
850
bytestrings that are correctly formed lines.
851
:return: The text sha1, the number of bytes in the text, and an opaque
852
representation of the inserted version which can be provided
853
back to future _add_text calls in the parent_texts dictionary.
855
# The default implementation just thunks over to .add_lines(),
856
# inefficient, but it works.
857
return self.add_lines(key, parents, osutils.split_lines(text),
858
nostore_sha=nostore_sha,
827
862
def add_mpdiffs(self, records):
828
863
"""Add mpdiffs to this VersionedFile.
926
961
has_key = index._has_key_from_parent_map
963
def get_missing_compression_parent_keys(self):
964
"""Return an iterable of keys of missing compression parents.
966
Check this after calling insert_record_stream to find out if there are
967
any missing compression parents. If there are, the records that
968
depend on them are not able to be inserted safely. The precise
969
behaviour depends on the concrete VersionedFiles class in use.
971
Classes that do not support this will raise NotImplementedError.
973
raise NotImplementedError(self.get_missing_compression_parent_keys)
928
975
def insert_record_stream(self, stream):
929
976
"""Insert a record stream into this container.
931
:param stream: A stream of records to insert.
978
:param stream: A stream of records to insert.
933
980
:seealso VersionedFile.get_record_stream:
1397
1448
class WeaveMerge(PlanWeaveMerge):
1398
1449
"""Weave merge that takes a VersionedFile and two versions as its input."""
1400
def __init__(self, versionedfile, ver_a, ver_b,
1451
def __init__(self, versionedfile, ver_a, ver_b,
1401
1452
a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1402
1453
plan = versionedfile.plan_merge(ver_a, ver_b)
1403
1454
PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1406
1457
class VirtualVersionedFiles(VersionedFiles):
1407
"""Dummy implementation for VersionedFiles that uses other functions for
1458
"""Dummy implementation for VersionedFiles that uses other functions for
1408
1459
obtaining fulltexts and parent maps.
1410
This is always on the bottom of the stack and uses string keys
1461
This is always on the bottom of the stack and uses string keys
1411
1462
(rather than tuples) internally.
1469
1520
"""See VersionedFile.iter_lines_added_or_present_in_versions()."""
1470
1521
for i, (key,) in enumerate(keys):
1471
1522
if pb is not None:
1472
pb.update("iterating texts", i, len(keys))
1523
pb.update("Finding changed lines", i, len(keys))
1473
1524
for l in self._get_lines(key):
1528
def network_bytes_to_kind_and_offset(network_bytes):
1529
"""Strip of a record kind from the front of network_bytes.
1531
:param network_bytes: The bytes of a record.
1532
:return: A tuple (storage_kind, offset_of_remaining_bytes)
1534
line_end = network_bytes.find('\n')
1535
storage_kind = network_bytes[:line_end]
1536
return storage_kind, line_end + 1
1539
class NetworkRecordStream(object):
1540
"""A record_stream which reconstitures a serialised stream."""
1542
def __init__(self, bytes_iterator):
1543
"""Create a NetworkRecordStream.
1545
:param bytes_iterator: An iterator of bytes. Each item in this
1546
iterator should have been obtained from a record_streams'
1547
record.get_bytes_as(record.storage_kind) call.
1549
self._bytes_iterator = bytes_iterator
1550
self._kind_factory = {'knit-ft-gz':knit.knit_network_to_record,
1551
'knit-delta-gz':knit.knit_network_to_record,
1552
'knit-annotated-ft-gz':knit.knit_network_to_record,
1553
'knit-annotated-delta-gz':knit.knit_network_to_record,
1554
'knit-delta-closure':knit.knit_delta_closure_to_records,
1555
'fulltext':fulltext_network_to_record,
1556
'groupcompress-block':groupcompress.network_block_to_records,
1562
:return: An iterator as per VersionedFiles.get_record_stream().
1564
for bytes in self._bytes_iterator:
1565
storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
1566
for record in self._kind_factory[storage_kind](
1567
storage_kind, bytes, line_end):
1571
def fulltext_network_to_record(kind, bytes, line_end):
1572
"""Convert a network fulltext record to record."""
1573
meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
1574
record_meta = bytes[line_end+4:line_end+4+meta_len]
1575
key, parents = bencode.bdecode_as_tuple(record_meta)
1576
if parents == 'nil':
1578
fulltext = bytes[line_end+4+meta_len:]
1579
return [FulltextContentFactory(key, parents, None, fulltext)]
1582
def _length_prefix(bytes):
1583
return struct.pack('!L', len(bytes))
1586
def record_to_fulltext_bytes(record):
1587
if record.parents is None:
1590
parents = record.parents
1591
record_meta = bencode.bencode((record.key, parents))
1592
record_content = record.get_bytes_as('fulltext')
1593
return "fulltext\n%s%s%s" % (
1594
_length_prefix(record_meta), record_meta, record_content)
1597
def sort_groupcompress(parent_map):
1598
"""Sort and group the keys in parent_map into groupcompress order.
1600
groupcompress is defined (currently) as reverse-topological order, grouped
1603
:return: A sorted-list of keys
1605
# gc-optimal ordering is approximately reverse topological,
1606
# properly grouped by file-id.
1608
for item in parent_map.iteritems():
1610
if isinstance(key, str) or len(key) == 1:
1615
per_prefix_map[prefix].append(item)
1617
per_prefix_map[prefix] = [item]
1620
for prefix in sorted(per_prefix_map):
1621
present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))