summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES8
-rw-r--r--lib/git/__init__.py2
-rw-r--r--lib/git/db.py7
-rw-r--r--lib/git/diff.py2
-rw-r--r--lib/git/errors.py70
m---------lib/git/ext/gitdb0
-rw-r--r--lib/git/index/base.py256
-rw-r--r--lib/git/index/fun.py197
-rw-r--r--lib/git/index/typ.py22
-rw-r--r--lib/git/objects/base.py1
-rw-r--r--lib/git/objects/fun.py66
-rw-r--r--lib/git/objects/tree.py121
-rw-r--r--lib/git/utils.py14
-rw-r--r--test/git/test_index.py88
-rw-r--r--test/testlib/helper.py6
15 files changed, 514 insertions, 346 deletions
diff --git a/CHANGES b/CHANGES
index 0097269a..2f93b3e7 100644
--- a/CHANGES
+++ b/CHANGES
@@ -4,6 +4,14 @@ CHANGES
0.3
===
* ConcurrentWriteOperation was removed, and replaced by LockedFD
+ * IndexFile.get_entries_key was renamed to entry_key
+ * IndexEntry instances contained in IndexFile.entries now use binary sha's. Use
+ the .hexsha property to obtain the hexadecimal version
+ * IndexFile.write_tree: removed missing_ok keyword, its always True now
+ Instead of raising GitCommandError it raises UnmergedEntriesError
+ * diff.Diff.null_hex_sha renamed to NULL_HEX_SHA, to be conforming with
+ the naming in the Object base class
+
0.2 Beta 2
===========
diff --git a/lib/git/__init__.py b/lib/git/__init__.py
index 7a0d000d..7860a3c1 100644
--- a/lib/git/__init__.py
+++ b/lib/git/__init__.py
@@ -28,7 +28,7 @@ from git.config import GitConfigParser
from git.objects import *
from git.refs import *
from git.diff import *
-from git.errors import InvalidGitRepositoryError, NoSuchPathError, GitCommandError
+from git.errors import *
from git.cmd import Git
from git.repo import Repo
from git.remote import *
diff --git a/lib/git/db.py b/lib/git/db.py
index f3698b76..b7cf0fc7 100644
--- a/lib/git/db.py
+++ b/lib/git/db.py
@@ -4,9 +4,12 @@ from gitdb.base import (
OStream
)
+from gitdb.util import to_hex_sha
+
from gitdb.db import GitDB
from gitdb.db import LooseObjectDB
+
__all__ = ('GitCmdObjectDB', 'GitDB' )
#class GitCmdObjectDB(CompoundDB, ObjectDBW):
@@ -24,11 +27,11 @@ class GitCmdObjectDB(LooseObjectDB):
self._git = git
def info(self, sha):
- t = self._git.get_object_header(sha)
+ t = self._git.get_object_header(to_hex_sha(sha))
return OInfo(*t)
def stream(self, sha):
"""For now, all lookup is done by git itself"""
- t = self._git.stream_object_data(sha)
+ t = self._git.stream_object_data(to_hex_sha(sha))
return OStream(*t)
diff --git a/lib/git/diff.py b/lib/git/diff.py
index 9df0c499..f9a0a66f 100644
--- a/lib/git/diff.py
+++ b/lib/git/diff.py
@@ -196,7 +196,7 @@ class Diff(object):
\.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
""", re.VERBOSE | re.MULTILINE)
# can be used for comparisons
- null_hex_sha = "0"*40
+ NULL_HEX_SHA = "0"*40
__slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "new_file", "deleted_file",
"rename_from", "rename_to", "diff")
diff --git a/lib/git/errors.py b/lib/git/errors.py
index 5fe99915..93919d5e 100644
--- a/lib/git/errors.py
+++ b/lib/git/errors.py
@@ -6,43 +6,51 @@
""" Module containing all exceptions thrown througout the git package, """
class InvalidGitRepositoryError(Exception):
- """ Thrown if the given repository appears to have an invalid format. """
+ """ Thrown if the given repository appears to have an invalid format. """
class NoSuchPathError(OSError):
- """ Thrown if a path could not be access by the system. """
+ """ Thrown if a path could not be access by the system. """
class GitCommandError(Exception):
- """ Thrown if execution of the git command fails with non-zero status code. """
- def __init__(self, command, status, stderr=None):
- self.stderr = stderr
- self.status = status
- self.command = command
-
- def __str__(self):
- return ("'%s' returned exit status %i: %s" %
- (' '.join(str(i) for i in self.command), self.status, self.stderr))
+ """ Thrown if execution of the git command fails with non-zero status code. """
+ def __init__(self, command, status, stderr=None):
+ self.stderr = stderr
+ self.status = status
+ self.command = command
+
+ def __str__(self):
+ return ("'%s' returned exit status %i: %s" %
+ (' '.join(str(i) for i in self.command), self.status, self.stderr))
class CheckoutError( Exception ):
- """Thrown if a file could not be checked out from the index as it contained
- changes.
-
- The .failed_files attribute contains a list of relative paths that failed
- to be checked out as they contained changes that did not exist in the index.
-
- The .failed_reasons attribute contains a string informing about the actual
- cause of the issue.
-
- The .valid_files attribute contains a list of relative paths to files that
- were checked out successfully and hence match the version stored in the
- index"""
- def __init__(self, message, failed_files, valid_files, failed_reasons):
- Exception.__init__(self, message)
- self.failed_files = failed_files
- self.failed_reasons = failed_reasons
- self.valid_files = valid_files
-
- def __str__(self):
- return Exception.__str__(self) + ":%s" % self.failed_files
+ """Thrown if a file could not be checked out from the index as it contained
+ changes.
+
+ The .failed_files attribute contains a list of relative paths that failed
+ to be checked out as they contained changes that did not exist in the index.
+
+ The .failed_reasons attribute contains a string informing about the actual
+ cause of the issue.
+
+ The .valid_files attribute contains a list of relative paths to files that
+ were checked out successfully and hence match the version stored in the
+ index"""
+ def __init__(self, message, failed_files, valid_files, failed_reasons):
+ Exception.__init__(self, message)
+ self.failed_files = failed_files
+ self.failed_reasons = failed_reasons
+ self.valid_files = valid_files
+
+ def __str__(self):
+ return Exception.__str__(self) + ":%s" % self.failed_files
+
+
+class CacheError(Exception):
+ """Base for all errors related to the git index, which is called cache internally"""
+
+class UnmergedEntriesError(CacheError):
+ """Thrown if an operation cannot proceed as there are still unmerged
+ entries in the cache"""
diff --git a/lib/git/ext/gitdb b/lib/git/ext/gitdb
-Subproject 92ca2e4ad606fbec7c934ad9e467a1b51fddcc9
+Subproject 9b53ab02cb44571e6167a125a5296b7c3395563
diff --git a/lib/git/index/base.py b/lib/git/index/base.py
index b003195c..06437702 100644
--- a/lib/git/index/base.py
+++ b/lib/git/index/base.py
@@ -5,13 +5,13 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
"""Module containing Index implementation, allowing to perform all kinds of index
manipulations such as querying and merging. """
-import binascii
import tempfile
import os
import sys
import subprocess
import glob
from cStringIO import StringIO
+from binascii import b2a_hex
from stat import (
S_ISLNK,
@@ -25,16 +25,12 @@ from stat import (
from typ import (
BaseIndexEntry,
IndexEntry,
- CE_NAMEMASK,
- CE_STAGESHIFT
)
from util import (
TemporaryFileSwap,
post_clear_cache,
default_index,
- pack,
- unpack
)
import git.objects
@@ -59,13 +55,18 @@ from git.utils import (
LazyMixin,
LockedFD,
join_path_native,
- file_contents_ro
+ file_contents_ro,
)
+from fun import (
+ write_cache,
+ read_cache,
+ write_tree_from_cache,
+ entry_key
+ )
-from gitdb.base import (
- IStream
- )
+from gitdb.base import IStream
+from gitdb.db import MemoryDB
__all__ = ( 'IndexFile', 'CheckoutError' )
@@ -84,7 +85,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
to facilitate access.
You may read the entries dict or manipulate it using IndexEntry instance, i.e.::
- index.entries[index.get_entries_key(index_entry_instance)] = index_entry_instance
+ index.entries[index.entry_key(index_entry_instance)] = index_entry_instance
Otherwise changes to it will be lost when changing the index using its methods.
"""
__slots__ = ( "repo", "version", "entries", "_extension_data", "_file_path" )
@@ -147,123 +148,39 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
pass
# END exception handling
- @classmethod
- def _read_entry(cls, stream):
- """Return: One entry of the given stream"""
- beginoffset = stream.tell()
- ctime = unpack(">8s", stream.read(8))[0]
- mtime = unpack(">8s", stream.read(8))[0]
- (dev, ino, mode, uid, gid, size, sha, flags) = \
- unpack(">LLLLLL20sH", stream.read(20 + 4 * 6 + 2))
- path_size = flags & CE_NAMEMASK
- path = stream.read(path_size)
-
- real_size = ((stream.tell() - beginoffset + 8) & ~7)
- data = stream.read((beginoffset + real_size) - stream.tell())
- return IndexEntry((mode, binascii.hexlify(sha), flags, path, ctime, mtime, dev, ino, uid, gid, size))
-
- @classmethod
- def _read_header(cls, stream):
- """Return tuple(version_long, num_entries) from the given stream"""
- type_id = stream.read(4)
- if type_id != "DIRC":
- raise AssertionError("Invalid index file header: %r" % type_id)
- version, num_entries = unpack(">LL", stream.read(4 * 2))
- assert version in (1, 2)
- return version, num_entries
-
#{ Serializable Interface
def _deserialize(self, stream):
""" Initialize this instance with index values read from the given stream """
- self.version, num_entries = self._read_header(stream)
- count = 0
- self.entries = dict()
- while count < num_entries:
- entry = self._read_entry(stream)
- self.entries[self.get_entries_key(entry)] = entry
- count += 1
- # END for each entry
-
- # the footer contains extension data and a sha on the content so far
- # Keep the extension footer,and verify we have a sha in the end
- # Extension data format is:
- # 4 bytes ID
- # 4 bytes length of chunk
- # repeated 0 - N times
- self._extension_data = stream.read(~0)
- assert len(self._extension_data) > 19, "Index Footer was not at least a sha on content as it was only %i bytes in size" % len(self._extension_data)
-
- content_sha = self._extension_data[-20:]
-
- # truncate the sha in the end as we will dynamically create it anyway
- self._extension_data = self._extension_data[:-20]
-
+ self.version, self.entries, self._extension_data, conten_sha = read_cache(stream)
return self
- def _serialize(self, stream, ignore_tree_extension_data=False):
-
- # wrap the stream into a compatible writer
- stream = IndexFileSHA1Writer(stream)
-
- # header
- stream.write("DIRC")
- stream.write(pack(">LL", self.version, len(self.entries)))
-
- # body
+ def _entries_sorted(self):
+ """:return: list of entries, in a sorted fashion, first by path, then by stage"""
entries_sorted = self.entries.values()
- entries_sorted.sort(key=lambda e: (e[3], e.stage)) # use path/stage as sort key
- for entry in entries_sorted:
- self._write_cache_entry(stream, entry)
- # END for each entry
-
- stored_ext_data = None
- if ignore_tree_extension_data and self._extension_data and self._extension_data[:4] == 'TREE':
- stored_ext_data = self._extension_data
- self._extension_data = ''
- # END extension data special handling
-
- # write previously cached extensions data
- stream.write(self._extension_data)
-
- if stored_ext_data:
- self._extension_data = stored_ext_data
- # END reset previous ext data
-
- # write the sha over the content
- stream.write_sha()
+ entries_sorted.sort(key=lambda e: (e.path, e.stage)) # use path/stage as sort key
+ return entries_sorted
+
+ def _serialize(self, stream, ignore_tree_extension_data=False):
+ entries = self._entries_sorted()
+ write_cache(entries,
+ stream,
+ (ignore_tree_extension_data and None) or self._extension_data)
return self
-
+
+
#} END serializable interface
- @classmethod
- def _write_cache_entry(cls, stream, entry):
- """ Write an IndexEntry to a stream """
- beginoffset = stream.tell()
- write = stream.write
- write(entry[4]) # ctime
- write(entry[5]) # mtime
- path = entry[3]
- plen = len(path) & CE_NAMEMASK # path length
- assert plen == len(path), "Path %s too long to fit into index" % entry[3]
- flags = plen | entry[2]
- write(pack(">LLLLLL20sH", entry[6], entry[7], entry[0],
- entry[8], entry[9], entry[10], binascii.unhexlify(entry[1]), flags))
- write(path)
- real_size = ((stream.tell() - beginoffset + 8) & ~7)
- write("\0" * ((beginoffset + real_size) - stream.tell()))
-
def write(self, file_path = None, ignore_tree_extension_data=False):
- """
- Write the current state to our file path or to the given one
+ """Write the current state to our file path or to the given one
- ``file_path``
+ :param file_path:
If None, we will write to our stored file path from which we have
been initialized. Otherwise we write to the given file path.
Please note that this will change the file_path of this index to
the one you gave.
- ``ignore_tree_extension_data``
+ :param ignore_tree_extension_data:
If True, the TREE type extension data read in the index will not
be written to disk. Use this if you have altered the index and
would like to use git-write-tree afterwards to create a tree
@@ -273,12 +190,10 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
Alternatively, use IndexFile.write_tree() to handle this case
automatically
- Returns
- self
- """
+ :return: self"""
lfd = LockedFD(file_path or self._file_path)
stream = lfd.open(write=True, stream=True)
-
+
self._serialize(stream, ignore_tree_extension_data)
lfd.commit()
@@ -487,7 +402,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# TODO: is it necessary to convert the mode ? We did that when adding
# it to the index, right ?
mode = self._stat_mode_to_index_mode(entry.mode)
- blob = Blob(self.repo, entry.sha, mode, entry.path)
+ blob = Blob(self.repo, entry.hexsha, mode, entry.path)
blob.size = entry.size
output = (entry.stage, blob)
if predicate(output):
@@ -516,19 +431,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
return path_map
@classmethod
- def get_entries_key(cls, *entry):
- """
- Returns
- Key suitable to be used for the index.entries dictionary
-
- ``entry``
- One instance of type BaseIndexEntry or the path and the stage
- """
- if len(entry) == 1:
- return (entry[0].path, entry[0].stage)
- else:
- return tuple(entry)
-
+ def entry_key(cls, *entry):
+ return entry_key(*entry)
def resolve_blobs(self, iter_blobs):
"""
@@ -585,28 +489,34 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# allows to lazily reread on demand
return self
- def write_tree(self, missing_ok=False):
- """
- Writes the Index in self to a corresponding Tree file into the repository
- object database and returns it as corresponding Tree object.
-
- ``missing_ok``
- If True, missing objects referenced by this index will not result
- in an error.
-
- Returns
- Tree object representing this index
- """
- index_path = self._index_path()
- tmp_index_mover = TemporaryFileSwap(index_path)
-
- self.write(index_path, ignore_tree_extension_data=True)
- tree_sha = self.repo.git.write_tree(missing_ok=missing_ok)
-
- del(tmp_index_mover) # as soon as possible
-
- return Tree(self.repo, tree_sha, 0, '')
-
+ def write_tree(self):
+ """Writes this index to a corresponding Tree object into the repository's
+ object database and return it.
+
+ :return: Tree object representing this index
+ :note: The tree will be written even if one or more objects the tree refers to
+ does not yet exist in the object database. This could happen if you added
+ Entries to the index directly.
+ :raise ValueError: if there are no entries in the cache
+ :raise UnmergedEntriesError: """
+ # we obtain no lock as we just flush our contents to disk as tree
+ if not self.entries:
+ raise ValueError("Cannot write empty index")
+
+ mdb = MemoryDB()
+ entries = self._entries_sorted()
+ binsha, tree_items = write_tree_from_cache(entries, mdb, slice(0, len(entries)))
+
+ # copy changed trees only
+ mdb.stream_copy(mdb.sha_iter(), self.repo.odb)
+
+
+ # note: additional deserialization could be saved if write_tree_from_cache
+ # would return sorted tree entries
+ root_tree = Tree(self.repo, b2a_hex(binsha), path='')
+ root_tree._cache = tree_items
+ return root_tree
+
def _process_diff_args(self, args):
try:
args.pop(args.index(self))
@@ -615,7 +525,6 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# END remove self
return args
-
def _to_relative_path(self, path):
""":return: Version of path relative to our git directory or raise ValueError
if it is not within our git direcotory"""
@@ -689,7 +598,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
- BaseIndexEntry or type
Handling equals the one of Blob objects, but the stage may be
- explicitly set.
+ explicitly set. Please note that Index Entries require binary sha's.
:param force:
If True, otherwise ignored or excluded files will be
@@ -756,7 +665,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
fprogress(filepath, True, filepath)
return BaseIndexEntry((self._stat_mode_to_index_mode(st.st_mode),
- istream.sha, 0, filepath))
+ istream.binsha, 0, filepath))
# END utility method
@@ -781,14 +690,14 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# HANLDE ENTRY OBJECT CREATION
# create objects if required, otherwise go with the existing shas
- null_entries_indices = [ i for i,e in enumerate(entries) if e.sha == Object.NULL_HEX_SHA ]
+ null_entries_indices = [ i for i,e in enumerate(entries) if e.binsha == Object.NULL_BIN_SHA ]
if null_entries_indices:
for ei in null_entries_indices:
null_entry = entries[ei]
new_entry = store_path(null_entry.path)
# update null entry
- entries[ei] = BaseIndexEntry((null_entry.mode, new_entry.sha, null_entry.stage, null_entry.path))
+ entries[ei] = BaseIndexEntry((null_entry.mode, new_entry.binsha, null_entry.stage, null_entry.path))
# END for each entry index
# END null_entry handling
@@ -797,7 +706,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
# all object sha's
if path_rewriter:
for i,e in enumerate(entries):
- entries[i] = BaseIndexEntry((e.mode, e.sha, e.stage, path_rewriter(e)))
+ entries[i] = BaseIndexEntry((e.mode, e.binsha, e.stage, path_rewriter(e)))
# END for each entry
# END handle path rewriting
@@ -837,11 +746,10 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
@post_clear_cache
@default_index
def remove(self, items, working_tree=False, **kwargs):
- """
- Remove the given items from the index and optionally from
+ """Remove the given items from the index and optionally from
the working tree as well.
- ``items``
+ :param items:
Multiple types of items are supported which may be be freely mixed.
- path string
@@ -859,21 +767,20 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
- BaseIndexEntry or compatible type
The only relevant information here Yis the path. The stage is ignored.
- ``working_tree``
+ :param working_tree:
If True, the entry will also be removed from the working tree, physically
removing the respective file. This may fail if there are uncommited changes
in it.
- ``**kwargs``
+ :param **kwargs:
Additional keyword arguments to be passed to git-rm, such
as 'r' to allow recurive removal of
- Returns
+ :return:
List(path_string, ...) list of repository relative paths that have
been removed effectively.
This is interesting to know in case you have provided a directory or
- globs. Paths are relative to the repository.
- """
+ globs. Paths are relative to the repository. """
args = list()
if not working_tree:
args.append("--cached")
@@ -986,40 +893,41 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable):
@default_index
def checkout(self, paths=None, force=False, fprogress=lambda *args: None, **kwargs):
- """
- Checkout the given paths or all files from the version known to the index into
+ """Checkout the given paths or all files from the version known to the index into
the working tree.
+
+ :note: Be sure you have written pending changes using the ``write`` method
+ in case you have altered the enties dictionary directly
- ``paths``
+ :param paths:
If None, all paths in the index will be checked out. Otherwise an iterable
of relative or absolute paths or a single path pointing to files or directories
in the index is expected.
- ``force``
+ :param force:
If True, existing files will be overwritten even if they contain local modifications.
If False, these will trigger a CheckoutError.
- ``fprogress``
+ :param fprogress:
see Index.add_ for signature and explanation.
The provided progress information will contain None as path and item if no
explicit paths are given. Otherwise progress information will be send
prior and after a file has been checked out
- ``**kwargs``
+ :param **kwargs:
Additional arguments to be pasesd to git-checkout-index
- Returns
+ :return:
iterable yielding paths to files which have been checked out and are
guaranteed to match the version stored in the index
- Raise CheckoutError
+ :raise CheckoutError:
If at least one file failed to be checked out. This is a summary,
hence it will checkout as many files as it can anyway.
If one of files or directories do not exist in the index
( as opposed to the original git command who ignores them ).
Raise GitCommandError if error lines could not be parsed - this truly is
- an exceptional state
- """
+ an exceptional state"""
args = ["--index"]
if force:
args.append("--force")
diff --git a/lib/git/index/fun.py b/lib/git/index/fun.py
new file mode 100644
index 00000000..9f877a66
--- /dev/null
+++ b/lib/git/index/fun.py
@@ -0,0 +1,197 @@
+"""
+Contains standalone functions to accompany the index implementation and make it
+more versatile
+"""
+from stat import S_IFDIR
+from cStringIO import StringIO
+
+from git.errors import UnmergedEntriesError
+from git.objects.fun import tree_to_stream
+from git.utils import (
+ IndexFileSHA1Writer,
+ )
+
+from typ import (
+ IndexEntry,
+ CE_NAMEMASK
+ )
+
+from util import (
+ pack,
+ unpack
+ )
+
+from gitdb.base import IStream
+from gitdb.typ import str_tree_type
+from binascii import a2b_hex
+
+__all__ = ('write_cache', 'read_cache', 'write_tree_from_cache', 'entry_key' )
+
+def write_cache_entry(entry, stream):
+ """Write the given entry to the stream"""
+ beginoffset = stream.tell()
+ write = stream.write
+ write(entry[4]) # ctime
+ write(entry[5]) # mtime
+ path = entry[3]
+ plen = len(path) & CE_NAMEMASK # path length
+ assert plen == len(path), "Path %s too long to fit into index" % entry[3]
+ flags = plen | entry[2]
+ write(pack(">LLLLLL20sH", entry[6], entry[7], entry[0],
+ entry[8], entry[9], entry[10], entry[1], flags))
+ write(path)
+ real_size = ((stream.tell() - beginoffset + 8) & ~7)
+ write("\0" * ((beginoffset + real_size) - stream.tell()))
+
+def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1Writer):
+ """Write the cache represented by entries to a stream
+ :param entries: **sorted** list of entries
+ :param stream: stream to wrap into the AdapterStreamCls - it is used for
+ final output.
+ :param ShaStreamCls: Type to use when writing to the stream. It produces a sha
+ while writing to it, before the data is passed on to the wrapped stream
+ :param extension_data: any kind of data to write as a trailer, it must begin
+ a 4 byte identifier, followed by its size ( 4 bytes )"""
+ # wrap the stream into a compatible writer
+ stream = ShaStreamCls(stream)
+
+ # header
+ version = 2
+ stream.write("DIRC")
+ stream.write(pack(">LL", version, len(entries)))
+
+ # body
+ for entry in entries:
+ write_cache_entry(entry, stream)
+ # END for each entry
+
+ # write previously cached extensions data
+ if extension_data is not None:
+ stream.write(extension_data)
+
+ # write the sha over the content
+ stream.write_sha()
+
+def read_entry(stream):
+ """Return: One entry of the given stream"""
+ beginoffset = stream.tell()
+ ctime = unpack(">8s", stream.read(8))[0]
+ mtime = unpack(">8s", stream.read(8))[0]
+ (dev, ino, mode, uid, gid, size, sha, flags) = \
+ unpack(">LLLLLL20sH", stream.read(20 + 4 * 6 + 2))
+ path_size = flags & CE_NAMEMASK
+ path = stream.read(path_size)
+
+ real_size = ((stream.tell() - beginoffset + 8) & ~7)
+ data = stream.read((beginoffset + real_size) - stream.tell())
+ return IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size))
+
+def read_header(stream):
+ """Return tuple(version_long, num_entries) from the given stream"""
+ type_id = stream.read(4)
+ if type_id != "DIRC":
+ raise AssertionError("Invalid index file header: %r" % type_id)
+ version, num_entries = unpack(">LL", stream.read(4 * 2))
+
+ # TODO: handle version 3: extended data, see read-cache.c
+ assert version in (1, 2)
+ return version, num_entries
+
+def entry_key(*entry):
+ """:return: Key suitable to be used for the index.entries dictionary
+ :param *entry: One instance of type BaseIndexEntry or the path and the stage"""
+ if len(entry) == 1:
+ return (entry[0].path, entry[0].stage)
+ else:
+ return tuple(entry)
+ # END handle entry
+
+def read_cache(stream):
+ """Read a cache file from the given stream
+ :return: tuple(version, entries_dict, extension_data, content_sha)
+ * version is the integer version number
+ * entries dict is a dictionary which maps IndexEntry instances to a path
+ at a stage
+ * extension_data is '' or 4 bytes of type + 4 bytes of size + size bytes
+ * content_sha is a 20 byte sha on all cache file contents"""
+ version, num_entries = read_header(stream)
+ count = 0
+ entries = dict()
+ while count < num_entries:
+ entry = read_entry(stream)
+ # entry_key would be the method to use, but we safe the effort
+ entries[(entry.path, entry.stage)] = entry
+ count += 1
+ # END for each entry
+
+ # the footer contains extension data and a sha on the content so far
+ # Keep the extension footer,and verify we have a sha in the end
+ # Extension data format is:
+ # 4 bytes ID
+ # 4 bytes length of chunk
+ # repeated 0 - N times
+ extension_data = stream.read(~0)
+ assert len(extension_data) > 19, "Index Footer was not at least a sha on content as it was only %i bytes in size" % len(extension_data)
+
+ content_sha = extension_data[-20:]
+
+ # truncate the sha in the end as we will dynamically create it anyway
+ extension_data = extension_data[:-20]
+
+ return (version, entries, extension_data, content_sha)
+
+def write_tree_from_cache(entries, odb, sl, si=0):
+ """Create a tree from the given sorted list of entries and put the respective
+ trees into the given object database
+ :param entries: **sorted** list of IndexEntries
+ :param odb: object database to store the trees in
+ :param si: start index at which we should start creating subtrees
+ :param sl: slice indicating the range we should process on the entries list
+ :return: tuple(binsha, list(tree_entry, ...)) a tuple of a sha and a list of
+ tree entries being a tuple of hexsha, mode, name"""
+ tree_items = list()
+ ci = sl.start
+ end = sl.stop
+ while ci < end:
+ entry = entries[ci]
+ if entry.stage != 0:
+ raise UnmergedEntriesError(entry)
+ # END abort on unmerged
+ ci += 1
+ rbound = entry.path.find('/', si)
+ if rbound == -1:
+ # its not a tree
+ tree_items.append((entry.binsha, entry.mode, entry.path[si:]))
+ else:
+ # find common base range
+ base = entry.path[si:rbound]
+ xi = ci
+ while xi < end:
+ oentry = entries[xi]
+ orbound = oentry.path.find('/', si)
+ if orbound == -1 or oentry.path[si:orbound] != base:
+ break
+ # END abort on base mismatch
+ xi += 1
+ # END find common base
+
+ # enter recursion
+ # ci - 1 as we want to count our current item as well
+ sha, tree_entry_list = write_tree_from_cache(entries, odb, slice(ci-1, xi), rbound+1)
+ tree_items.append((sha, S_IFDIR, base))
+
+ # skip ahead
+ ci = xi
+ # END handle bounds
+ # END for each entry
+
+ # finally create the tree
+ sio = StringIO()
+ tree_to_stream(tree_items, sio.write)
+ sio.seek(0)
+
+ istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
+ return (istream.binsha, tree_items)
+
+
+
diff --git a/lib/git/index/typ.py b/lib/git/index/typ.py
index b5dac58a..6ef1d2f2 100644
--- a/lib/git/index/typ.py
+++ b/lib/git/index/typ.py
@@ -5,6 +5,11 @@ from util import (
unpack
)
+from binascii import (
+ b2a_hex,
+ a2b_hex
+ )
+
__all__ = ('BlobFilter', 'BaseIndexEntry', 'IndexEntry')
#{ Invariants
@@ -50,7 +55,7 @@ class BaseIndexEntry(tuple):
use numeric indices for performance reasons. """
def __str__(self):
- return "%o %s %i\t%s" % (self.mode, self.sha, self.stage, self.path)
+ return "%o %s %i\t%s" % (self.mode, self.hexsha, self.stage, self.path)
@property
def mode(self):
@@ -58,9 +63,14 @@ class BaseIndexEntry(tuple):
return self[0]
@property
- def sha(self):
- """ hex sha of the blob """
+ def binsha(self):
+ """binary sha of the blob """
return self[1]
+
+ @property
+ def hexsha(self):
+ """hex version of our sha"""
+ return b2a_hex(self[1])
@property
def stage(self):
@@ -88,7 +98,7 @@ class BaseIndexEntry(tuple):
@classmethod
def from_blob(cls, blob, stage = 0):
""":return: Fully equipped BaseIndexEntry at the given stage"""
- return cls((blob.mode, blob.sha, stage << CE_STAGESHIFT, blob.path))
+ return cls((blob.mode, a2b_hex(blob.sha), stage << CE_STAGESHIFT, blob.path))
class IndexEntry(BaseIndexEntry):
@@ -145,12 +155,12 @@ class IndexEntry(BaseIndexEntry):
:param base: Instance of type BaseIndexEntry"""
time = pack(">LL", 0, 0)
- return IndexEntry((base.mode, base.sha, base.flags, base.path, time, time, 0, 0, 0, 0, 0))
+ return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0))
@classmethod
def from_blob(cls, blob, stage = 0):
""":return: Minimal entry resembling the given blob object"""
time = pack(">LL", 0, 0)
- return IndexEntry((blob.mode, blob.sha, stage << CE_STAGESHIFT, blob.path, time, time, 0, 0, 0, 0, blob.size))
+ return IndexEntry((blob.mode, a2b_hex(blob.sha), stage << CE_STAGESHIFT, blob.path, time, time, 0, 0, 0, 0, blob.size))
diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py
index 36bbccb2..90aa8ca2 100644
--- a/lib/git/objects/base.py
+++ b/lib/git/objects/base.py
@@ -21,6 +21,7 @@ class Object(LazyMixin):
inst.data # byte string containing the whole data of the object
"""
NULL_HEX_SHA = '0'*40
+ NULL_BIN_SHA = '\0'*20
TYPES = ("blob", "tree", "commit", "tag")
__slots__ = ("repo", "sha", "size", "data" )
type = None # to be set by subclass
diff --git a/lib/git/objects/fun.py b/lib/git/objects/fun.py
new file mode 100644
index 00000000..7882437d
--- /dev/null
+++ b/lib/git/objects/fun.py
@@ -0,0 +1,66 @@
+"""Module with functions which are supposed to be as fast as possible"""
+
+__all__ = ('tree_to_stream', 'tree_entries_from_data')
+
+def tree_to_stream(entries, write):
+ """Write the give list of entries into a stream using its write method
+ :param entries: **sorted** list of tuples with (binsha, mode, name)
+ :param write: write method which takes a data string"""
+ ord_zero = ord('0')
+ bit_mask = 7 # 3 bits set
+
+ for binsha, mode, name in entries:
+ mode_str = ''
+ for i in xrange(6):
+ mode_str = chr(((mode >> (i*3)) & bit_mask) + ord_zero) + mode_str
+ # END for each 8 octal value
+
+ # git slices away the first octal if its zero
+ if mode_str[0] == '0':
+ mode_str = mode_str[1:]
+ # END save a byte
+
+ write("%s %s\0%s" % (mode_str, name, binsha))
+ # END for each item
+
+
+def tree_entries_from_data(data):
+ """Reads the binary representation of a tree and returns tuples of Tree items
+ :param data: data block with tree data
+ :return: list(tuple(binsha, mode, tree_relative_path), ...)"""
+ ord_zero = ord('0')
+ len_data = len(data)
+ i = 0
+ out = list()
+ while i < len_data:
+ mode = 0
+
+ # read mode
+ # Some git versions truncate the leading 0, some don't
+ # The type will be extracted from the mode later
+ while data[i] != ' ':
+ # move existing mode integer up one level being 3 bits
+ # and add the actual ordinal value of the character
+ mode = (mode << 3) + (ord(data[i]) - ord_zero)
+ i += 1
+ # END while reading mode
+
+ # byte is space now, skip it
+ i += 1
+
+ # parse name, it is NULL separated
+
+ ns = i
+ while data[i] != '\0':
+ i += 1
+ # END while not reached NULL
+ name = data[ns:i]
+
+ # byte is NULL, get next 20
+ i += 1
+ sha = data[i:i+20]
+ i = i + 20
+
+ out.append((sha, mode, name))
+ # END for each byte in data stream
+ return out
diff --git a/lib/git/objects/tree.py b/lib/git/objects/tree.py
index eb8aa9eb..6b1d13c1 100644
--- a/lib/git/objects/tree.py
+++ b/lib/git/objects/tree.py
@@ -5,21 +5,21 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import os
+import utils
+import base
+
from blob import Blob
from submodule import Submodule
-import base
-import binascii
import git.diff as diff
-import utils
-from git.utils import join_path
-
join = os.path.join
-def sha_to_hex(sha):
- """Takes a string and returns the hex of the sha within"""
- hexsha = binascii.hexlify(sha)
- return hexsha
-
+from fun import (
+ tree_entries_from_data,
+ tree_to_stream
+ )
+
+from gitdb.util import to_bin_sha
+from binascii import b2a_hex
class TreeModifier(object):
"""A utility class providing methods to alter the underlying cache in a list-like
@@ -51,25 +51,24 @@ class TreeModifier(object):
#} END interface
#{ Mutators
- def add(self, hexsha, mode, name, force=False):
+ def add(self, sha, mode, name, force=False):
"""Add the given item to the tree. If an item with the given name already
exists, nothing will be done, but a ValueError will be raised if the
sha and mode of the existing item do not match the one you add, unless
force is True
- :param hexsha: The 40 byte sha of the item to add
+ :param sha: The 20 or 40 byte sha of the item to add
:param mode: int representing the stat compatible mode of the item
:param force: If True, an item with your name and information will overwrite
any existing item with the same name, no matter which information it has
:return: self"""
if '/' in name:
raise ValueError("Name must not contain '/' characters")
- if len(hexsha) != 40:
- raise ValueError("Hexsha required, got %r" % hexsha)
if (mode >> 12) not in Tree._map_id_to_type:
raise ValueError("Invalid object type according to mode %o" % mode)
-
+
+ sha = to_bin_sha(sha)
index = self._index_by_name(name)
- item = (hexsha, mode, name)
+ item = (sha, mode, name)
if index == -1:
self._cache.append(item)
else:
@@ -77,18 +76,19 @@ class TreeModifier(object):
self._cache[index] = item
else:
ex_item = self._cache[index]
- if ex_item[0] != hexsha or ex_item[1] != mode:
+ if ex_item[0] != sha or ex_item[1] != mode:
raise ValueError("Item %r existed with different properties" % name)
# END handle mismatch
# END handle force
# END handle name exists
return self
- def add_unchecked(self, hexsha, mode, name):
+ def add_unchecked(self, binsha, mode, name):
"""Add the given item to the tree, its correctness is assumed, which
puts the caller into responsibility to assure the input is correct.
- For more information on the parameters, see ``add``"""
- self._cache.append((hexsha, mode, name))
+ For more information on the parameters, see ``add``
+ :param binsha: 20 byte binary sha"""
+ self._cache.append((binsha, mode, name))
def __delitem__(self, name):
"""Deletes an item with the given name if it exists"""
@@ -146,70 +146,21 @@ class Tree(base.IndexObject, diff.Diffable, utils.Traversable, utils.Serializabl
def _set_cache_(self, attr):
if attr == "_cache":
# Set the data when we need it
- self._cache = self._get_tree_cache(self.data)
+ self._cache = tree_entries_from_data(self.data)
else:
super(Tree, self)._set_cache_(attr)
- def _get_tree_cache(self, data):
- """ :return: list(object_instance, ...)
- :param data: data string containing our serialized information"""
- return list(self._iter_from_data(data))
-
def _iter_convert_to_object(self, iterable):
"""Iterable yields tuples of (hexsha, mode, name), which will be converted
to the respective object representation"""
- for hexsha, mode, name in iterable:
+ for binsha, mode, name in iterable:
path = join(self.path, name)
type_id = mode >> 12
try:
- yield self._map_id_to_type[type_id](self.repo, hexsha, mode, path)
+ yield self._map_id_to_type[type_id](self.repo, b2a_hex(binsha), mode, path)
except KeyError:
raise TypeError( "Unknown type %i found in tree data for path '%s'" % (type_id, path))
# END for each item
-
- def _iter_from_data(self, data):
- """
- Reads the binary non-pretty printed representation of a tree and converts
- it into Blob, Tree or Commit objects.
-
- Note: This method was inspired by the parse_tree method in dulwich.
-
- :yield: Tuple(hexsha, mode, tree_relative_path)
- """
- ord_zero = ord('0')
- len_data = len(data)
- i = 0
- while i < len_data:
- mode = 0
-
- # read mode
- # Some git versions truncate the leading 0, some don't
- # The type will be extracted from the mode later
- while data[i] != ' ':
- # move existing mode integer up one level being 3 bits
- # and add the actual ordinal value of the character
- mode = (mode << 3) + (ord(data[i]) - ord_zero)
- i += 1
- # END while reading mode
-
- # byte is space now, skip it
- i += 1
-
- # parse name, it is NULL separated
-
- ns = i
- while data[i] != '\0':
- i += 1
- # END while not reached NULL
- name = data[ns:i]
-
- # byte is NULL, get next 20
- i += 1
- sha = data[i:i+20]
- i = i + 20
-
- yield (sha_to_hex(sha), mode, name)
- # END for each byte in data stream
def __div__(self, file):
"""
@@ -250,7 +201,7 @@ class Tree(base.IndexObject, diff.Diffable, utils.Traversable, utils.Serializabl
else:
for info in self._cache:
if info[2] == file: # [2] == name
- return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join(self.path, info[2]))
+ return self._map_id_to_type[info[1] >> 12](self.repo, b2a_hex(info[0]), info[1], join(self.path, info[2]))
# END for each obj
raise KeyError( msg % file )
# END handle long paths
@@ -304,7 +255,7 @@ class Tree(base.IndexObject, diff.Diffable, utils.Traversable, utils.Serializabl
def __getitem__(self, item):
if isinstance(item, int):
info = self._cache[item]
- return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join(self.path, info[2]))
+ return self._map_id_to_type[info[1] >> 12](self.repo, b2a_hex(info[0]), info[1], join(self.path, info[2]))
if isinstance(item, basestring):
# compatability
@@ -335,32 +286,16 @@ class Tree(base.IndexObject, diff.Diffable, utils.Traversable, utils.Serializabl
def __reversed__(self):
return reversed(self._iter_convert_to_object(self._cache))
- def _serialize(self, stream, presort=False):
+ def _serialize(self, stream):
"""Serialize this tree into the stream. Please note that we will assume
our tree data to be in a sorted state. If this is not the case, serialization
will not generate a correct tree representation as these are assumed to be sorted
by algorithms"""
- ord_zero = ord('0')
- bit_mask = 7 # 3 bits set
- hex_to_bin = binascii.a2b_hex
-
- for hexsha, mode, name in self._cache:
- mode_str = ''
- for i in xrange(6):
- mode_str = chr(((mode >> (i*3)) & bit_mask) + ord_zero) + mode_str
- # END for each 8 octal value
-
- # git slices away the first octal if its zero
- if mode_str[0] == '0':
- mode_str = mode_str[1:]
- # END save a byte
-
- stream.write("%s %s\0%s" % (mode_str, name, hex_to_bin(hexsha)))
- # END for each item
+ tree_to_stream(self._cache, stream.write)
return self
def _deserialize(self, stream):
- self._cache = self._get_tree_cache(stream.read())
+ self._cache = tree_entries_from_data(stream.read())
return self
diff --git a/lib/git/utils.py b/lib/git/utils.py
index 3fb7fbf8..38501292 100644
--- a/lib/git/utils.py
+++ b/lib/git/utils.py
@@ -10,12 +10,14 @@ import time
import tempfile
from gitdb.util import (
- make_sha,
- FDStreamWrapper,
- LockedFD,
- file_contents_ro,
- LazyMixin
- )
+ make_sha,
+ FDStreamWrapper,
+ LockedFD,
+ file_contents_ro,
+ LazyMixin,
+ to_hex_sha,
+ to_bin_sha
+ )
def stream_copy(source, destination, chunk_size=512*1024):
diff --git a/test/git/test_index.py b/test/git/test_index.py
index cbe1f982..d0063e89 100644
--- a/test/git/test_index.py
+++ b/test/git/test_index.py
@@ -55,7 +55,7 @@ class TestIndex(TestBase):
last_val = None
entry = index.entries.itervalues().next()
for attr in ("path","ctime","mtime","dev","inode","mode","uid",
- "gid","size","sha","stage"):
+ "gid","size","binsha", "hexsha", "stage"):
val = getattr(entry, attr)
# END for each method
@@ -94,23 +94,24 @@ class TestIndex(TestBase):
raise AssertionError( "CMP Failed: Missing entries in index: %s, missing in tree: %s" % (bset-iset, iset-bset) )
# END assertion message
- def test_index_file_from_tree(self):
+ @with_rw_repo('0.1.6')
+ def test_index_file_from_tree(self, rw_repo):
common_ancestor_sha = "5117c9c8a4d3af19a9958677e45cda9269de1541"
cur_sha = "4b43ca7ff72d5f535134241e7c797ddc9c7a3573"
other_sha = "39f85c4358b7346fee22169da9cad93901ea9eb9"
# simple index from tree
- base_index = IndexFile.from_tree(self.rorepo, common_ancestor_sha)
+ base_index = IndexFile.from_tree(rw_repo, common_ancestor_sha)
assert base_index.entries
self._cmp_tree_index(common_ancestor_sha, base_index)
# merge two trees - its like a fast-forward
- two_way_index = IndexFile.from_tree(self.rorepo, common_ancestor_sha, cur_sha)
+ two_way_index = IndexFile.from_tree(rw_repo, common_ancestor_sha, cur_sha)
assert two_way_index.entries
self._cmp_tree_index(cur_sha, two_way_index)
# merge three trees - here we have a merge conflict
- three_way_index = IndexFile.from_tree(self.rorepo, common_ancestor_sha, cur_sha, other_sha)
+ three_way_index = IndexFile.from_tree(rw_repo, common_ancestor_sha, cur_sha, other_sha)
assert len(list(e for e in three_way_index.entries.values() if e.stage != 0))
@@ -128,7 +129,7 @@ class TestIndex(TestBase):
# writing a tree should fail with an unmerged index
- self.failUnlessRaises(GitCommandError, three_way_index.write_tree)
+ self.failUnlessRaises(UnmergedEntriesError, three_way_index.write_tree)
# removed unmerged entries
unmerged_blob_map = three_way_index.unmerged_blobs()
@@ -155,31 +156,31 @@ class TestIndex(TestBase):
# current index is at the (virtual) cur_commit
next_commit = "4c39f9da792792d4e73fc3a5effde66576ae128c"
parent_commit = rw_repo.head.commit.parents[0]
- manifest_key = IndexFile.get_entries_key('MANIFEST.in', 0)
+ manifest_key = IndexFile.entry_key('MANIFEST.in', 0)
manifest_entry = rw_repo.index.entries[manifest_key]
rw_repo.index.merge_tree(next_commit)
# only one change should be recorded
- assert manifest_entry.sha != rw_repo.index.entries[manifest_key].sha
+ assert manifest_entry.binsha != rw_repo.index.entries[manifest_key].binsha
rw_repo.index.reset(rw_repo.head)
- assert rw_repo.index.entries[manifest_key].sha == manifest_entry.sha
+ assert rw_repo.index.entries[manifest_key].binsha == manifest_entry.binsha
# FAKE MERGE
#############
# Add a change with a NULL sha that should conflict with next_commit. We
# pretend there was a change, but we do not even bother adding a proper
# sha for it ( which makes things faster of course )
- manifest_fake_entry = BaseIndexEntry((manifest_entry[0], Diff.null_hex_sha, 0, manifest_entry[3]))
+ manifest_fake_entry = BaseIndexEntry((manifest_entry[0], "\0"*20, 0, manifest_entry[3]))
rw_repo.index.add([manifest_fake_entry])
# add actually resolves the null-hex-sha for us as a feature, but we can
# edit the index manually
- assert rw_repo.index.entries[manifest_key].sha != Diff.null_hex_sha
+ assert rw_repo.index.entries[manifest_key].binsha != Object.NULL_BIN_SHA
# must operate on the same index for this ! Its a bit problematic as
# it might confuse people
index = rw_repo.index
index.entries[manifest_key] = IndexEntry.from_base(manifest_fake_entry)
index.write()
- assert rw_repo.index.entries[manifest_key].sha == Diff.null_hex_sha
+ assert rw_repo.index.entries[manifest_key].hexsha == Diff.NULL_HEX_SHA
# a three way merge would result in a conflict and fails as the command will
# not overwrite any entries in our index and hence leave them unmerged. This is
@@ -189,10 +190,11 @@ class TestIndex(TestBase):
# the only way to get the merged entries is to safe the current index away into a tree,
# which is like a temporary commit for us. This fails as well as the NULL sha deos not
# have a corresponding object
- self.failUnlessRaises(GitCommandError, index.write_tree)
+ # NOTE: missing_ok is not a kwarg anymore, missing_ok is always true
+ # self.failUnlessRaises(GitCommandError, index.write_tree)
- # if missing objects are okay, this would work though
- tree = index.write_tree(missing_ok = True)
+ # if missing objects are okay, this would work though ( they are always okay now )
+ tree = index.write_tree()
# now make a proper three way merge with unmerged entries
unmerged_tree = IndexFile.from_tree(rw_repo, parent_commit, tree, next_commit)
@@ -348,7 +350,7 @@ class TestIndex(TestBase):
if type_id == 0: # path
yield entry.path
elif type_id == 1: # blob
- yield Blob(rw_repo, entry.sha, entry.mode, entry.path)
+ yield Blob(rw_repo, entry.hexsha, entry.mode, entry.path)
elif type_id == 2: # BaseIndexEntry
yield BaseIndexEntry(entry[:4])
elif type_id == 3: # IndexEntry
@@ -442,18 +444,19 @@ class TestIndex(TestBase):
old_blob = new_commit.parents[0].tree.blobs[0]
entries = index.reset(new_commit).add([old_blob], fprogress=self._fprogress_add)
self._assert_fprogress(entries)
- assert index.entries[(old_blob.path,0)].sha == old_blob.sha and len(entries) == 1
+ assert index.entries[(old_blob.path,0)].hexsha == old_blob.sha and len(entries) == 1
# mode 0 not allowed
- null_sha = "0"*40
- self.failUnlessRaises(ValueError, index.reset(new_commit).add, [BaseIndexEntry((0, null_sha,0,"doesntmatter"))])
+ null_hex_sha = Diff.NULL_HEX_SHA
+ null_bin_sha = "\0" * 20
+ self.failUnlessRaises(ValueError, index.reset(new_commit).add, [BaseIndexEntry((0, null_bin_sha,0,"doesntmatter"))])
# add new file
new_file_relapath = "my_new_file"
new_file_path = self._make_file(new_file_relapath, "hello world", rw_repo)
- entries = index.reset(new_commit).add([BaseIndexEntry((010644, null_sha, 0, new_file_relapath))], fprogress=self._fprogress_add)
+ entries = index.reset(new_commit).add([BaseIndexEntry((010644, null_bin_sha, 0, new_file_relapath))], fprogress=self._fprogress_add)
self._assert_fprogress(entries)
- assert len(entries) == 1 and entries[0].sha != null_sha
+ assert len(entries) == 1 and entries[0].hexsha != null_hex_sha
# add symlink
if sys.platform != "win32":
@@ -464,25 +467,25 @@ class TestIndex(TestBase):
entries = index.reset(new_commit).add([link_file], fprogress=self._fprogress_add)
self._assert_fprogress(entries)
assert len(entries) == 1 and S_ISLNK(entries[0].mode)
- assert S_ISLNK(index.entries[index.get_entries_key("my_real_symlink", 0)].mode)
+ assert S_ISLNK(index.entries[index.entry_key("my_real_symlink", 0)].mode)
# we expect only the target to be written
- assert index.repo.odb.stream(entries[0].sha).read() == target
+ assert index.repo.odb.stream(entries[0].binsha).read() == target
# END real symlink test
# add fake symlink and assure it checks-our as symlink
fake_symlink_relapath = "my_fake_symlink"
link_target = "/etc/that"
fake_symlink_path = self._make_file(fake_symlink_relapath, link_target, rw_repo)
- fake_entry = BaseIndexEntry((0120000, null_sha, 0, fake_symlink_relapath))
+ fake_entry = BaseIndexEntry((0120000, null_bin_sha, 0, fake_symlink_relapath))
entries = index.reset(new_commit).add([fake_entry], fprogress=self._fprogress_add)
self._assert_fprogress(entries)
- assert entries[0].sha != null_sha
+ assert entries[0].hexsha != null_hex_sha
assert len(entries) == 1 and S_ISLNK(entries[0].mode)
# assure this also works with an alternate method
- full_index_entry = IndexEntry.from_base(BaseIndexEntry((0120000, entries[0].sha, 0, entries[0].path)))
- entry_key = index.get_entries_key(full_index_entry)
+ full_index_entry = IndexEntry.from_base(BaseIndexEntry((0120000, entries[0].binsha, 0, entries[0].path)))
+ entry_key = index.entry_key(full_index_entry)
index.reset(new_commit)
assert entry_key not in index.entries
@@ -493,8 +496,9 @@ class TestIndex(TestBase):
assert S_ISLNK(new_entry.mode)
# a tree created from this should contain the symlink
- tree = index.write_tree(True)
+ tree = index.write_tree()
assert fake_symlink_relapath in tree
+ index.write() # flush our changes for the checkout
# checkout the fakelink, should be a link then
assert not S_ISLNK(os.stat(fake_symlink_path)[ST_MODE])
@@ -552,8 +556,8 @@ class TestIndex(TestBase):
# two existing ones, one new one
yield 'CHANGES'
yield 'ez_setup.py'
- yield index.entries[index.get_entries_key('README', 0)]
- yield index.entries[index.get_entries_key('.gitignore', 0)]
+ yield index.entries[index.entry_key('README', 0)]
+ yield index.entries[index.entry_key('.gitignore', 0)]
for fid in range(3):
fname = 'newfile%i' % fid
@@ -565,5 +569,27 @@ class TestIndex(TestBase):
index.add(paths, path_rewriter=rewriter)
for filenum in range(len(paths)):
- assert index.get_entries_key(str(filenum), 0) in index.entries
+ assert index.entry_key(str(filenum), 0) in index.entries
+
+ @with_rw_repo('HEAD')
+ def test_compare_write_tree(self, rw_repo):
+ def write_tree(index):
+ tree_sha = index.repo.git.write_tree(missing_ok=True)
+ return Tree(index.repo, tree_sha, 0, '')
+ # END git cmd write tree
+
+ # write all trees and compare them
+ # its important to have a few submodules in there too
+ max_count = 100
+ count = 0
+ for commit in rw_repo.head.commit.traverse():
+ if count >= max_count:
+ break
+ count += 1
+ index = rw_repo.index.reset(commit)
+ orig_tree = commit.tree
+ new_git_tree = write_tree(index)
+ assert new_git_tree == orig_tree
+ assert index.write_tree() == orig_tree
+ # END for each commit
diff --git a/test/testlib/helper.py b/test/testlib/helper.py
index 715427c2..ebff57f6 100644
--- a/test/testlib/helper.py
+++ b/test/testlib/helper.py
@@ -5,6 +5,7 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import os
+import sys
from git import Repo, Remote, GitCommandError
from unittest import TestCase
import tempfile
@@ -105,7 +106,10 @@ def with_rw_repo(working_tree_ref):
os.chdir(rw_repo.working_dir)
try:
return func(self, rw_repo)
- finally:
+ except:
+ print >> sys.stderr, "Keeping repo after failure: %s" % repo_dir
+ raise
+ else:
os.chdir(prev_cwd)
rw_repo.git.clear_cache()
shutil.rmtree(repo_dir, onerror=_rmtree_onerror)