Merge branch 'oid'

This commit is contained in:
J. David Ibáñez
2013-04-20 22:43:33 +02:00
22 changed files with 486 additions and 120 deletions

View File

@@ -5,10 +5,66 @@ Git objects
.. contents:: Contents .. contents:: Contents
:local: :local:
In the first place Git is a key-value storage system. The keys are called
OIDs, for Object id, and the values stored are called Objects.
In the first place Git is a key-value storage system. The values stored are Oids
called *objects*, there are four types (commits, trees, blobs and tags), =================
for each type pygit2 has a Python class::
The oid is the `SHA-1 <http://en.wikipedia.org/wiki/SHA-1>`_ hash of an
object. It is 20 bytes long:
- When we represent an oid as a 20 bytes Python string, we say it is a raw
oid.
- When we represent an oid as a 40 chars Python string, we sayt it is a hex
oid.
However, most of the time we will use the Oid type. We can explicetly create
an Oid object from its raw or hexadecimal form::
>>> hex = "cff3ceaefc955f0dbe1957017db181bc49913781"
>>> oid1 = Oid(hex=hex)
>>> from binascii import unhexlify
>>> raw = unhexlify(hex)
>>> oid2 = Oid(raw=raw)
>>> print oid1 == oid2
True
And in the opposite direction, we can get the raw or hexadecimal form from
an Oid object:
.. autoattribute:: pygit2.Oid.raw
.. autoattribute:: pygit2.Oid.hex
The Oid type supports:
- rich comparisons, not just for equality, also: lesser-than, lesser-or-equal,
etc.
- hashing, so Oid objects can be used as keys in a dictionary
Python 2 and Python 3
---------------------
There is a difference on how the library handles hex oids, depending on
whether we are using Python 2 or 3.
- In Python 2, we can represent an hexadecimal oid using a bytes string
(``str``) or a text string (``unicode``)
- In Python 3, hexadecimal oids can only be represented using unicode
strings.
Objects
=================
There are four types (commits, trees, blobs and tags), for each type pygit2
has a Python class::
>>> # Show commits and trees >>> # Show commits and trees
>>> commit >>> commit

View File

@@ -423,7 +423,7 @@ Index_write_tree(Index *self)
if (err < 0) if (err < 0)
return Error_set(err); return Error_set(err);
return git_oid_to_python(oid.id); return git_oid_to_python(&oid);
} }
PyMethodDef Index_methods[] = { PyMethodDef Index_methods[] = {
@@ -585,7 +585,7 @@ PyDoc_STRVAR(IndexEntry_oid__doc__, "Object id.");
PyObject * PyObject *
IndexEntry_oid__get__(IndexEntry *self) IndexEntry_oid__get__(IndexEntry *self)
{ {
return git_oid_to_python(self->entry->oid.id); return git_oid_to_python(&self->entry->oid);
} }

View File

@@ -72,7 +72,7 @@ PyDoc_STRVAR(Note_oid__doc__,
PyObject * PyObject *
Note_oid__get__(Note *self) Note_oid__get__(Note *self)
{ {
return git_oid_to_py_str(git_note_oid(self->note)); return git_oid_to_python(git_note_oid(self->note));
} }

View File

@@ -60,7 +60,7 @@ Object_oid__get__(Object *self)
oid = git_object_id(self->obj); oid = git_object_id(self->obj);
assert(oid); assert(oid);
return git_oid_to_python(oid->id); return git_oid_to_python(oid);
} }

235
src/oid.c
View File

@@ -32,56 +32,84 @@
#include "error.h" #include "error.h"
#include "oid.h" #include "oid.h"
PyTypeObject OidType;
PyObject *
git_oid_to_python(const git_oid *oid)
{
Oid *py_oid;
py_oid = PyObject_New(Oid, &OidType);
git_oid_cpy(&(py_oid->oid), oid);
return (PyObject*)py_oid;
}
int int
py_str_to_git_oid(PyObject *py_str, git_oid *oid) _oid_from_hex(PyObject *py_oid, git_oid *oid)
{ {
PyObject *py_hex; PyObject *py_hex;
char *hex_or_bin;
int err; int err;
char *hex;
Py_ssize_t len; Py_ssize_t len;
/* Case 1: raw sha */ #if PY_MAJOR_VERSION == 2
if (PyBytes_Check(py_str)) { /* Bytes (only supported in Python 2) */
err = PyBytes_AsStringAndSize(py_str, &hex_or_bin, &len); if (PyBytes_Check(py_oid)) {
err = PyBytes_AsStringAndSize(py_oid, &hex, &len);
if (err) if (err)
return -1; return -1;
if (len > GIT_OID_RAWSZ) {
PyErr_SetObject(PyExc_ValueError, py_str); err = git_oid_fromstrn(oid, hex, len);
if (err < 0) {
PyErr_SetObject(Error_type(err), py_oid);
return -1; return -1;
} }
memcpy(oid->id, (const unsigned char*)hex_or_bin, len);
return len * 2;
}
/* Case 2: hex sha */ return len;
if (PyUnicode_Check(py_str)) { }
py_hex = PyUnicode_AsASCIIString(py_str); #endif
/* Unicode */
if (PyUnicode_Check(py_oid)) {
py_hex = PyUnicode_AsASCIIString(py_oid);
if (py_hex == NULL) if (py_hex == NULL)
return -1; return -1;
err = PyBytes_AsStringAndSize(py_hex, &hex_or_bin, &len);
err = PyBytes_AsStringAndSize(py_hex, &hex, &len);
if (err) { if (err) {
Py_DECREF(py_hex); Py_DECREF(py_hex);
return -1; return -1;
} }
err = git_oid_fromstrn(oid, hex_or_bin, len); err = git_oid_fromstrn(oid, hex, len);
Py_DECREF(py_hex); Py_DECREF(py_hex);
if (err < 0) { if (err < 0) {
PyErr_SetObject(Error_type(err), py_str); PyErr_SetObject(Error_type(err), py_oid);
return -1; return -1;
} }
return len; return len;
} }
/* Type error */ /* Type error */
PyErr_Format(PyExc_TypeError, PyErr_SetObject(PyExc_TypeError, py_oid);
"Git object id must be byte or a text string, not: %.200s",
Py_TYPE(py_str)->tp_name);
return -1; return -1;
} }
int
py_str_to_git_oid(PyObject *py_oid, git_oid *oid)
{
/* Oid */
if (PyObject_TypeCheck(py_oid, (PyTypeObject*)&OidType)) {
git_oid_cpy(oid, &((Oid*)py_oid)->oid);
return GIT_OID_RAWSZ;
}
/* Hex */
return _oid_from_hex(py_oid, oid);
}
int int
py_str_to_git_oid_expand(git_repository *repo, PyObject *py_str, git_oid *oid) py_str_to_git_oid_expand(git_repository *repo, PyObject *py_str, git_oid *oid)
{ {
@@ -122,6 +150,171 @@ git_oid_to_py_str(const git_oid *oid)
char hex[GIT_OID_HEXSZ]; char hex[GIT_OID_HEXSZ];
git_oid_fmt(hex, oid); git_oid_fmt(hex, oid);
#if PY_MAJOR_VERSION == 2
return PyBytes_FromStringAndSize(hex, GIT_OID_HEXSZ);
#else
return to_unicode_n(hex, GIT_OID_HEXSZ, "utf-8", "strict"); return to_unicode_n(hex, GIT_OID_HEXSZ, "utf-8", "strict");
#endif
} }
int
Oid_init(Oid *self, PyObject *args, PyObject *kw)
{
char *keywords[] = {"raw", "hex", NULL};
PyObject *raw = NULL, *hex = NULL;
int err;
char *bytes;
Py_ssize_t len;
if (!PyArg_ParseTupleAndKeywords(args, kw, "|OO", keywords, &raw, &hex))
return -1;
/* We expect one or the other, but not both. */
if (raw == NULL && hex == NULL) {
PyErr_SetString(PyExc_ValueError, "Expected raw or hex.");
return -1;
}
if (raw != NULL && hex != NULL) {
PyErr_SetString(PyExc_ValueError, "Expected raw or hex, not both.");
return -1;
}
/* Case 1: raw */
if (raw != NULL) {
err = PyBytes_AsStringAndSize(raw, &bytes, &len);
if (err)
return -1;
if (len > GIT_OID_RAWSZ) {
PyErr_SetObject(PyExc_ValueError, raw);
return -1;
}
memcpy(self->oid.id, (const unsigned char*)bytes, len);
return 0;
}
/* Case 2: hex */
err = _oid_from_hex(hex, &self->oid);
if (err < 0)
return -1;
return 0;
}
Py_hash_t
Oid_hash(PyObject *oid)
{
/* TODO Randomize (use _Py_HashSecret) to avoid collission DoS attacks? */
return *(Py_hash_t*) ((Oid*)oid)->oid.id;
}
PyObject *
Oid_richcompare(PyObject *o1, PyObject *o2, int op)
{
PyObject *res;
int cmp;
/* Comparing to something else than an Oid is not supported. */
if (!PyObject_TypeCheck(o2, &OidType)) {
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
/* Ok go. */
cmp = git_oid_cmp(&((Oid*)o1)->oid, &((Oid*)o2)->oid);
switch (op) {
case Py_LT:
res = (cmp <= 0) ? Py_True: Py_False;
break;
case Py_LE:
res = (cmp < 0) ? Py_True: Py_False;
break;
case Py_EQ:
res = (cmp == 0) ? Py_True: Py_False;
break;
case Py_NE:
res = (cmp != 0) ? Py_True: Py_False;
break;
case Py_GT:
res = (cmp > 0) ? Py_True: Py_False;
break;
case Py_GE:
res = (cmp >= 0) ? Py_True: Py_False;
break;
}
Py_INCREF(res);
return res;
}
PyDoc_STRVAR(Oid_raw__doc__, "Raw oid.");
PyObject *
Oid_raw__get__(Oid *self)
{
return PyBytes_FromStringAndSize((const char*)self->oid.id, GIT_OID_RAWSZ);
}
PyDoc_STRVAR(Oid_hex__doc__, "Hex oid.");
PyObject *
Oid_hex__get__(Oid *self)
{
return git_oid_to_py_str(&self->oid);
}
PyGetSetDef Oid_getseters[] = {
GETTER(Oid, raw),
GETTER(Oid, hex),
{NULL},
};
PyDoc_STRVAR(Oid__doc__, "Object id.");
PyTypeObject OidType = {
PyVarObject_HEAD_INIT(NULL, 0)
"_pygit2.Oid", /* tp_name */
sizeof(Oid), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc)Oid_hash, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
Oid__doc__, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
(richcmpfunc)Oid_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
Oid_getseters, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Oid_init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};

View File

@@ -35,9 +35,7 @@
int py_str_to_git_oid(PyObject *py_str, git_oid *oid); int py_str_to_git_oid(PyObject *py_str, git_oid *oid);
int py_str_to_git_oid_expand(git_repository *repo, PyObject *py_str, int py_str_to_git_oid_expand(git_repository *repo, PyObject *py_str,
git_oid *oid); git_oid *oid);
PyObject* git_oid_to_python(const git_oid *oid);
PyObject* git_oid_to_py_str(const git_oid *oid); PyObject* git_oid_to_py_str(const git_oid *oid);
#define git_oid_to_python(id) \
PyBytes_FromStringAndSize((const char*)id, GIT_OID_RAWSZ)
#endif #endif

View File

@@ -38,6 +38,7 @@
extern PyObject *GitError; extern PyObject *GitError;
extern PyTypeObject RepositoryType; extern PyTypeObject RepositoryType;
extern PyTypeObject OidType;
extern PyTypeObject ObjectType; extern PyTypeObject ObjectType;
extern PyTypeObject CommitType; extern PyTypeObject CommitType;
extern PyTypeObject DiffType; extern PyTypeObject DiffType;
@@ -141,7 +142,7 @@ hashfile(PyObject *self, PyObject *args)
if (err < 0) if (err < 0)
return Error_set(err); return Error_set(err);
return git_oid_to_python(oid.id); return git_oid_to_python(&oid);
} }
PyDoc_STRVAR(hash__doc__, PyDoc_STRVAR(hash__doc__,
@@ -165,7 +166,7 @@ hash(PyObject *self, PyObject *args)
return Error_set(err); return Error_set(err);
} }
return git_oid_to_python(oid.id); return git_oid_to_python(&oid);
} }
@@ -193,6 +194,10 @@ moduleinit(PyObject* m)
INIT_TYPE(RepositoryType, NULL, PyType_GenericNew) INIT_TYPE(RepositoryType, NULL, PyType_GenericNew)
ADD_TYPE(m, Repository); ADD_TYPE(m, Repository);
/* Oid */
INIT_TYPE(OidType, NULL, PyType_GenericNew)
ADD_TYPE(m, Oid);
/* Objects (make them with the Repository.create_XXX methods). */ /* Objects (make them with the Repository.create_XXX methods). */
INIT_TYPE(ObjectType, NULL, NULL) INIT_TYPE(ObjectType, NULL, NULL)
INIT_TYPE(CommitType, &ObjectType, NULL) INIT_TYPE(CommitType, &ObjectType, NULL)

View File

@@ -204,18 +204,17 @@ Reference_target__get__(Reference *self)
CHECK_REFERENCE(self); CHECK_REFERENCE(self);
/* Get the target */ /* Case 1: Direct */
if (GIT_REF_OID == git_reference_type(self->reference)) { if (GIT_REF_OID == git_reference_type(self->reference))
return git_oid_to_py_str(git_reference_target(self->reference)); return git_oid_to_python(git_reference_target(self->reference));
} else {
/* Case 2: Symbolic */
c_name = git_reference_symbolic_target(self->reference); c_name = git_reference_symbolic_target(self->reference);
if (c_name == NULL) { if (c_name == NULL) {
PyErr_SetString(PyExc_ValueError, "no target available"); PyErr_SetString(PyExc_ValueError, "no target available");
return NULL; return NULL;
} }
}
/* Make a PyString and return it */
return to_path(c_name); return to_path(c_name);
} }
@@ -262,21 +261,17 @@ PyDoc_STRVAR(Reference_oid__doc__, "Object id.");
PyObject * PyObject *
Reference_oid__get__(Reference *self) Reference_oid__get__(Reference *self)
{ {
const git_oid *oid;
CHECK_REFERENCE(self); CHECK_REFERENCE(self);
/* Case 1: Direct */
if (GIT_REF_OID == git_reference_type(self->reference))
return git_oid_to_python(git_reference_target(self->reference));
/* Get the oid (only for "direct" references) */ /* Get the oid (only for "direct" references) */
oid = git_reference_target(self->reference);
if (oid == NULL) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"oid is only available if the reference is direct " "oid is only available if the reference is direct "
"(i.e. not symbolic)"); "(i.e. not symbolic)");
return NULL; return NULL;
}
/* Convert and return it */
return git_oid_to_python(oid->id);
} }
int int

View File

@@ -132,10 +132,10 @@ static int
Repository_build_as_iter(const git_oid *oid, void *accum) Repository_build_as_iter(const git_oid *oid, void *accum)
{ {
int err; int err;
PyObject *oid_str = git_oid_to_py_str(oid); PyObject *py_oid = git_oid_to_python(oid);
err = PyList_Append((PyObject*)accum, oid_str); err = PyList_Append((PyObject*)accum, py_oid);
Py_DECREF(oid_str); Py_DECREF(py_oid);
return err; return err;
} }
@@ -152,11 +152,10 @@ Repository_as_iter(Repository *self)
err = git_odb_foreach(odb, Repository_build_as_iter, (void*)accum); err = git_odb_foreach(odb, Repository_build_as_iter, (void*)accum);
git_odb_free(odb); git_odb_free(odb);
if (err == GIT_EUSER) { if (err == GIT_EUSER)
return NULL; return NULL;
} else if (err < 0) { if (err < 0)
return Error_set(err); return Error_set(err);
}
return PyObject_GetIter(accum); return PyObject_GetIter(accum);
} }
@@ -399,7 +398,7 @@ Repository_write(Repository *self, PyObject *args)
stream->write(stream, buffer, buflen); stream->write(stream, buffer, buflen);
err = stream->finalize_write(&oid, stream); err = stream->finalize_write(&oid, stream);
stream->free(stream); stream->free(stream);
return git_oid_to_python(oid.id); return git_oid_to_python(&oid);
} }
@@ -574,7 +573,7 @@ Repository_create_blob(Repository *self, PyObject *args)
if (err < 0) if (err < 0)
return Error_set(err); return Error_set(err);
return git_oid_to_python(oid.id); return git_oid_to_python(&oid);
} }
@@ -597,7 +596,7 @@ Repository_create_blob_fromfile(Repository *self, PyObject *args)
if (err < 0) if (err < 0)
return Error_set(err); return Error_set(err);
return git_oid_to_python(oid.id); return git_oid_to_python(&oid);
} }
@@ -670,7 +669,7 @@ Repository_create_commit(Repository *self, PyObject *args)
goto out; goto out;
} }
py_result = git_oid_to_python(oid.id); py_result = git_oid_to_python(&oid);
out: out:
free(message); free(message);
@@ -718,7 +717,7 @@ Repository_create_tag(Repository *self, PyObject *args)
git_object_free(target); git_object_free(target);
if (err < 0) if (err < 0)
return Error_set_oid(err, &oid, len); return Error_set_oid(err, &oid, len);
return git_oid_to_python(oid.id); return git_oid_to_python(&oid);
} }
@@ -1153,7 +1152,7 @@ Repository_create_note(Repository *self, PyObject* args)
if (err < 0) if (err < 0)
return Error_set(err); return Error_set(err);
return git_oid_to_python(note_id.id); return git_oid_to_python(&note_id);
} }

View File

@@ -43,7 +43,7 @@ Tag_target__get__(Tag *self)
const git_oid *oid; const git_oid *oid;
oid = git_tag_target_id(self->tag); oid = git_tag_target_id(self->tag);
return git_oid_to_python(oid->id); return git_oid_to_python(oid);
} }

View File

@@ -74,7 +74,7 @@ TreeEntry_oid__get__(TreeEntry *self)
const git_oid *oid; const git_oid *oid;
oid = git_tree_entry_id(self->entry); oid = git_tree_entry_id(self->entry);
return git_oid_to_python(oid->id); return git_oid_to_python(oid);
} }

View File

@@ -46,6 +46,12 @@ typedef struct {
} Repository; } Repository;
typedef struct {
PyObject_HEAD
git_oid oid;
} Oid;
#define SIMPLE_TYPE(_name, _ptr_type, _ptr_name) \ #define SIMPLE_TYPE(_name, _ptr_type, _ptr_name) \
typedef struct {\ typedef struct {\
PyObject_HEAD\ PyObject_HEAD\

View File

@@ -54,6 +54,11 @@
#define to_encoding(x) PyUnicode_DecodeASCII(x, strlen(x), "strict") #define to_encoding(x) PyUnicode_DecodeASCII(x, strlen(x), "strict")
#endif #endif
#ifndef Py_hash_t
#define Py_hash_t long
#endif
#define CHECK_REFERENCE(self)\ #define CHECK_REFERENCE(self)\
if (self->reference == NULL) {\ if (self->reference == NULL) {\
PyErr_SetString(GitError, "deleted reference");\ PyErr_SetString(GitError, "deleted reference");\

View File

@@ -35,9 +35,9 @@ import sys
import unittest import unittest
names = ['blob', 'commit', 'config', 'diff', 'index', 'refs', 'remote', names = ['blob', 'commit', 'config', 'diff', 'index', 'note', 'oid', 'refs',
'repository', 'revwalk', 'signature', 'status', 'tag', 'tree', 'remote', 'repository', 'revwalk', 'signature', 'status', 'tag',
'treebuilder', 'note'] 'tree', 'treebuilder']
def test_suite(): def test_suite():
# Sometimes importing pygit2 fails, we try this first to get an # Sometimes importing pygit2 fails, we try this first to get an

View File

@@ -49,7 +49,7 @@ class BlobTest(utils.RepoTestCase):
def test_read_blob(self): def test_read_blob(self):
blob = self.repo[BLOB_SHA] blob = self.repo[BLOB_SHA]
self.assertEqual(blob.hex, BLOB_SHA) self.assertEqual(blob.hex, BLOB_SHA)
sha = utils.oid_to_hex(blob.oid) sha = blob.oid.hex
self.assertEqual(sha, BLOB_SHA) self.assertEqual(sha, BLOB_SHA)
self.assertTrue(isinstance(blob, pygit2.Blob)) self.assertTrue(isinstance(blob, pygit2.Blob))
self.assertEqual(pygit2.GIT_OBJ_BLOB, blob.type) self.assertEqual(pygit2.GIT_OBJ_BLOB, blob.type)
@@ -67,8 +67,7 @@ class BlobTest(utils.RepoTestCase):
self.assertEqual(blob_oid, blob.oid) self.assertEqual(blob_oid, blob.oid)
self.assertEqual( self.assertEqual(
utils.gen_blob_sha1(BLOB_NEW_CONTENT), utils.gen_blob_sha1(BLOB_NEW_CONTENT),
utils.oid_to_hex(blob_oid) blob_oid.hex)
)
self.assertEqual(BLOB_NEW_CONTENT, blob.data) self.assertEqual(BLOB_NEW_CONTENT, blob.data)
self.assertEqual(len(BLOB_NEW_CONTENT), blob.size) self.assertEqual(len(BLOB_NEW_CONTENT), blob.size)
@@ -85,8 +84,7 @@ class BlobTest(utils.RepoTestCase):
self.assertEqual(blob_oid, blob.oid) self.assertEqual(blob_oid, blob.oid)
self.assertEqual( self.assertEqual(
utils.gen_blob_sha1(BLOB_FILE_CONTENT), utils.gen_blob_sha1(BLOB_FILE_CONTENT),
utils.oid_to_hex(blob_oid) blob_oid.hex)
)
self.assertEqual(BLOB_FILE_CONTENT, blob.data) self.assertEqual(BLOB_FILE_CONTENT, blob.data)
self.assertEqual(len(BLOB_FILE_CONTENT), blob.size) self.assertEqual(len(BLOB_FILE_CONTENT), blob.size)

View File

@@ -98,8 +98,7 @@ class IndexTest(utils.RepoTestCase):
self.assertEqual(len(index), 1) self.assertEqual(len(index), 1)
# Test read-write returns the same oid # Test read-write returns the same oid
oid = index.write_tree() oid = index.write_tree()
oid = utils.oid_to_hex(oid) self.assertEqual(oid.hex, tree_oid)
self.assertEqual(oid, tree_oid)
# Test the index is only modified in memory # Test the index is only modified in memory
index.read() index.read()
self.assertEqual(len(index), 2) self.assertEqual(len(index), 2)
@@ -107,8 +106,7 @@ class IndexTest(utils.RepoTestCase):
def test_write_tree(self): def test_write_tree(self):
oid = self.repo.index.write_tree() oid = self.repo.index.write_tree()
sha = utils.oid_to_hex(oid) self.assertEqual(oid.hex, 'fd937514cb799514d4b81bb24c5fcfeb6472b245')
self.assertEqual(sha, 'fd937514cb799514d4b81bb24c5fcfeb6472b245')
def test_iter(self): def test_iter(self):
index = self.repo.index index = self.repo.index

View File

@@ -41,15 +41,16 @@ NOTES = [
'784855caf26449a1914d2cf62d12b9374d76ae78'), '784855caf26449a1914d2cf62d12b9374d76ae78'),
('d879714d880671ed84f8aaed8b27fca23ba01f27', 'Second Note - HEAD~1\n', ('d879714d880671ed84f8aaed8b27fca23ba01f27', 'Second Note - HEAD~1\n',
'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87') 'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87')
] ]
class NotesTest(utils.BareRepoTestCase): class NotesTest(utils.BareRepoTestCase):
def test_create_note(self): def test_create_note(self):
annotated_id = self.repo.revparse_single('HEAD~3').hex annotated_id = self.repo.revparse_single('HEAD~3').hex
author = committer = Signature('Foo bar', 'foo@bar.com', 12346, 0) author = committer = Signature('Foo bar', 'foo@bar.com', 12346, 0)
note_id = self.repo.create_note(NOTE[1], author, committer, annotated_id) note_id = self.repo.create_note(NOTE[1], author, committer,
self.assertEqual(NOTE[0], utils.oid_to_hex(note_id)) annotated_id)
self.assertEqual(NOTE[0], note_id.hex)
# check the note blob # check the note blob
self.assertEqual(NOTE[1].encode(), self.repo[note_id].data) self.assertEqual(NOTE[1].encode(), self.repo[note_id].data)
@@ -57,22 +58,23 @@ class NotesTest(utils.BareRepoTestCase):
def test_lookup_note(self): def test_lookup_note(self):
annotated_id = self.repo.head.hex annotated_id = self.repo.head.hex
note = self.repo.lookup_note(annotated_id) note = self.repo.lookup_note(annotated_id)
self.assertEqual(NOTES[0][0], note.oid) self.assertEqual(NOTES[0][0], note.oid.hex)
self.assertEqual(NOTES[0][1], note.message) self.assertEqual(NOTES[0][1], note.message)
def test_remove_note(self): def test_remove_note(self):
note = self.repo.lookup_note(self.repo.head.hex) note = self.repo.lookup_note(self.repo.head.hex)
author = committer = Signature('Foo bar', 'foo@bar.com', 12346, 0) author = committer = Signature('Foo bar', 'foo@bar.com', 12346, 0)
note.remove(author, committer) note.remove(author, committer)
self.assertRaises(KeyError, lambda: self.repo.lookup_note(self.repo.head.hex)) self.assertRaises(KeyError, self.repo.lookup_note, self.repo.head.hex)
def test_iterate_notes(self): def test_iterate_notes(self):
for i, note in enumerate(self.repo.notes()): for i, note in enumerate(self.repo.notes()):
entry = (note.oid, note.message, note.annotated_id) entry = (note.oid.hex, note.message, note.annotated_id)
self.assertEqual(NOTES[i],entry) self.assertEqual(NOTES[i], entry)
def test_iterate_non_existing_ref(self): def test_iterate_non_existing_ref(self):
self.assertRaises(KeyError, lambda: self.repo.notes("refs/notes/bad_ref")) self.assertRaises(KeyError, self.repo.notes, "refs/notes/bad_ref")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

109
test/test_oid.py Normal file
View File

@@ -0,0 +1,109 @@
# -*- coding: UTF-8 -*-
#
# Copyright 2010-2013 The pygit2 contributors
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.
#
# In addition to the permissions in the GNU General Public License,
# the authors give you unlimited permission to link the compiled
# version of this file into combinations with other programs,
# and to distribute those combinations without any restriction
# coming from the use of this file. (The General Public License
# restrictions do apply in other respects; for example, they cover
# modification of the file, and distribution when not linked into
# a combined executable.)
#
# This file is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
"""Tests for Object ids."""
# Import from the future
from __future__ import absolute_import
from __future__ import unicode_literals
# Import from the Standard Library
from binascii import unhexlify
from sys import version_info
import unittest
# Import from pygit2
from pygit2 import Oid
from . import utils
HEX = "15b648aec6ed045b5ca6f57f8b7831a8b4757298"
RAW = unhexlify(HEX.encode('ascii'))
class OidTest(utils.BareRepoTestCase):
def test_raw(self):
oid = Oid(raw=RAW)
self.assertEqual(oid.raw, RAW)
self.assertEqual(oid.hex, HEX)
def test_hex(self):
oid = Oid(hex=HEX)
self.assertEqual(oid.raw, RAW)
self.assertEqual(oid.hex, HEX)
def test_hex_bytes(self):
if version_info[0] == 2:
hex = bytes(HEX)
oid = Oid(hex=hex)
self.assertEqual(oid.raw, RAW)
self.assertEqual(oid.hex, HEX)
else:
hex = bytes(HEX, "ascii")
self.assertRaises(TypeError, Oid, hex=hex)
def test_none(self):
self.assertRaises(ValueError, Oid)
def test_both(self):
self.assertRaises(ValueError, Oid, raw=RAW, hex=HEX)
def test_long(self):
self.assertRaises(ValueError, Oid, raw=RAW + b'a')
self.assertRaises(ValueError, Oid, hex=HEX + 'a')
def test_cmp(self):
oid1 = Oid(raw=RAW)
# Equal
oid2 = Oid(hex=HEX)
self.assertEqual(oid1, oid2)
# Not equal
oid2 = Oid(hex="15b648aec6ed045b5ca6f57f8b7831a8b4757299")
self.assertNotEqual(oid1, oid2)
# Other
self.assertTrue(oid1 < oid2)
self.assertTrue(oid1 <= oid2)
self.assertFalse(oid1 == oid2)
self.assertFalse(oid1 > oid2)
self.assertFalse(oid1 >= oid2)
def test_hash(self):
s = set()
s.add(Oid(raw=RAW))
s.add(Oid(hex=HEX))
self.assertEqual(len(s), 1)
s.add(Oid(hex="0000000000000000000000000000000000000000"))
s.add(Oid(hex="0000000000000000000000000000000000000001"))
self.assertEqual(len(s), 3)
if __name__ == '__main__':
unittest.main()

View File

@@ -176,7 +176,7 @@ class ReferencesTest(utils.RepoTestCase):
self.assertTrue('refs/tags/version1' in refs) self.assertTrue('refs/tags/version1' in refs)
reference = self.repo.lookup_reference('refs/tags/version1') reference = self.repo.lookup_reference('refs/tags/version1')
self.assertEqual(reference.hex, LAST_COMMIT) self.assertEqual(reference.hex, LAST_COMMIT)
self.assertEqual(reference.target, LAST_COMMIT) self.assertEqual(reference.target.hex, LAST_COMMIT)
# try to create existing reference # try to create existing reference
self.assertRaises(ValueError, self.repo.create_reference, self.assertRaises(ValueError, self.repo.create_reference,

View File

@@ -41,14 +41,16 @@ from os.path import join, realpath
# Import from pygit2 # Import from pygit2
from pygit2 import GIT_OBJ_ANY, GIT_OBJ_BLOB, GIT_OBJ_COMMIT from pygit2 import GIT_OBJ_ANY, GIT_OBJ_BLOB, GIT_OBJ_COMMIT
from pygit2 import init_repository, discover_repository, Reference, hashfile from pygit2 import init_repository, discover_repository, Reference, hashfile
from pygit2 import Oid
import pygit2 import pygit2
from . import utils from . import utils
HEAD_SHA = '784855caf26449a1914d2cf62d12b9374d76ae78' HEAD_SHA = '784855caf26449a1914d2cf62d12b9374d76ae78'
PARENT_SHA = 'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87' # HEAD^ PARENT_SHA = 'f5e5aa4e36ab0fe62ee1ccc6eb8f79b866863b87' # HEAD^
A_HEX_SHA = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16' BLOB_HEX = 'af431f20fc541ed6d5afede3e2dc7160f6f01f16'
A_BIN_SHA = binascii.unhexlify(A_HEX_SHA.encode('ascii')) BLOB_RAW = binascii.unhexlify(BLOB_HEX.encode('ascii'))
BLOB_OID = Oid(raw=BLOB_RAW)
class RepositoryTest(utils.BareRepoTestCase): class RepositoryTest(utils.BareRepoTestCase):
@@ -70,15 +72,15 @@ class RepositoryTest(utils.BareRepoTestCase):
self.assertRaises(TypeError, self.repo.read, 123) self.assertRaises(TypeError, self.repo.read, 123)
self.assertRaisesWithArg(KeyError, '1' * 40, self.repo.read, '1' * 40) self.assertRaisesWithArg(KeyError, '1' * 40, self.repo.read, '1' * 40)
ab = self.repo.read(A_BIN_SHA) ab = self.repo.read(BLOB_OID)
a = self.repo.read(A_HEX_SHA) a = self.repo.read(BLOB_HEX)
self.assertEqual(ab, a) self.assertEqual(ab, a)
self.assertEqual((GIT_OBJ_BLOB, b'a contents\n'), a) self.assertEqual((GIT_OBJ_BLOB, b'a contents\n'), a)
a2 = self.repo.read('7f129fd57e31e935c6d60a0c794efe4e6927664b') a2 = self.repo.read('7f129fd57e31e935c6d60a0c794efe4e6927664b')
self.assertEqual((GIT_OBJ_BLOB, b'a contents 2\n'), a2) self.assertEqual((GIT_OBJ_BLOB, b'a contents 2\n'), a2)
a_hex_prefix = A_HEX_SHA[:4] a_hex_prefix = BLOB_HEX[:4]
a3 = self.repo.read(a_hex_prefix) a3 = self.repo.read(a_hex_prefix)
self.assertEqual((GIT_OBJ_BLOB, b'a contents\n'), a3) self.assertEqual((GIT_OBJ_BLOB, b'a contents\n'), a3)
@@ -88,34 +90,33 @@ class RepositoryTest(utils.BareRepoTestCase):
self.assertRaises(ValueError, self.repo.write, GIT_OBJ_ANY, data) self.assertRaises(ValueError, self.repo.write, GIT_OBJ_ANY, data)
oid = self.repo.write(GIT_OBJ_BLOB, data) oid = self.repo.write(GIT_OBJ_BLOB, data)
self.assertEqual(type(oid), bytes) self.assertEqual(type(oid), Oid)
self.assertEqual(len(oid), 20)
def test_contains(self): def test_contains(self):
self.assertRaises(TypeError, lambda: 123 in self.repo) self.assertRaises(TypeError, lambda: 123 in self.repo)
self.assertTrue(A_BIN_SHA in self.repo) self.assertTrue(BLOB_OID in self.repo)
self.assertTrue(A_BIN_SHA[:10] in self.repo) self.assertTrue(BLOB_HEX in self.repo)
self.assertTrue(A_HEX_SHA in self.repo) self.assertTrue(BLOB_HEX[:10] in self.repo)
self.assertTrue(A_HEX_SHA[:10] in self.repo)
self.assertFalse('a' * 40 in self.repo) self.assertFalse('a' * 40 in self.repo)
self.assertFalse('a' * 20 in self.repo) self.assertFalse('a' * 20 in self.repo)
def test_iterable(self): def test_iterable(self):
l = [ obj for obj in self.repo ] l = [ obj for obj in self.repo ]
self.assertTrue(A_HEX_SHA in l) oid = Oid(hex=BLOB_HEX)
self.assertTrue(oid in l)
def test_lookup_blob(self): def test_lookup_blob(self):
self.assertRaises(TypeError, lambda: self.repo[123]) self.assertRaises(TypeError, lambda: self.repo[123])
self.assertEqual(self.repo[A_BIN_SHA].hex, A_HEX_SHA) self.assertEqual(self.repo[BLOB_OID].hex, BLOB_HEX)
a = self.repo[A_HEX_SHA] a = self.repo[BLOB_HEX]
self.assertEqual(b'a contents\n', a.read_raw()) self.assertEqual(b'a contents\n', a.read_raw())
self.assertEqual(A_HEX_SHA, a.hex) self.assertEqual(BLOB_HEX, a.hex)
self.assertEqual(GIT_OBJ_BLOB, a.type) self.assertEqual(GIT_OBJ_BLOB, a.type)
def test_lookup_blob_prefix(self): def test_lookup_blob_prefix(self):
a = self.repo[A_HEX_SHA[:5]] a = self.repo[BLOB_HEX[:5]]
self.assertEqual(b'a contents\n', a.read_raw()) self.assertEqual(b'a contents\n', a.read_raw())
self.assertEqual(A_HEX_SHA, a.hex) self.assertEqual(BLOB_HEX, a.hex)
self.assertEqual(GIT_OBJ_BLOB, a.type) self.assertEqual(GIT_OBJ_BLOB, a.type)
def test_lookup_commit(self): def test_lookup_commit(self):
@@ -230,16 +231,18 @@ class RepositoryTest_II(utils.RepoTestCase):
self.repo.checkout(pygit2.GIT_CHECKOUT_FORCE, head=True) self.repo.checkout(pygit2.GIT_CHECKOUT_FORCE, head=True)
self.assertTrue('bye.txt' not in self.repo.status()) self.assertTrue('bye.txt' not in self.repo.status())
class NewRepositoryTest(utils.NoRepoTestCase): class NewRepositoryTest(utils.NoRepoTestCase):
def test_new_repo(self): def test_new_repo(self):
repo = init_repository(self._temp_dir, False) repo = init_repository(self._temp_dir, False)
oid = repo.write(GIT_OBJ_BLOB, "Test") oid = repo.write(GIT_OBJ_BLOB, "Test")
self.assertEqual(type(oid), bytes) self.assertEqual(type(oid), Oid)
self.assertEqual(len(oid), 20)
assert os.path.exists(os.path.join(self._temp_dir, '.git')) assert os.path.exists(os.path.join(self._temp_dir, '.git'))
class InitRepositoryTest(utils.NoRepoTestCase): class InitRepositoryTest(utils.NoRepoTestCase):
# under the assumption that repo.is_bare works # under the assumption that repo.is_bare works

View File

@@ -72,7 +72,7 @@ class TagTest(utils.BareRepoTestCase):
self.assertEqual('3ee44658fd11660e828dfc96b9b5c5f38d5b49bb', tag.hex) self.assertEqual('3ee44658fd11660e828dfc96b9b5c5f38d5b49bb', tag.hex)
self.assertEqual(name, tag.name) self.assertEqual(name, tag.name)
self.assertEqual(target, utils.oid_to_hex(tag.target)) self.assertEqual(target, tag.target.hex)
self.assertEqualSignature(tagger, tag.tagger) self.assertEqualSignature(tagger, tag.tagger)
self.assertEqual(message, tag.message) self.assertEqual(message, tag.message)
self.assertEqual(name, self.repo[tag.hex].name) self.assertEqual(name, self.repo[tag.hex].name)

View File

@@ -47,8 +47,6 @@ def force_rm_handle(remove_path, path, excinfo):
) )
remove_path(path) remove_path(path)
def oid_to_hex(oid):
return b2a_hex(oid).decode('ascii')
def gen_blob_sha1(data): def gen_blob_sha1(data):
# http://stackoverflow.com/questions/552659/assigning-git-sha1s-without-git # http://stackoverflow.com/questions/552659/assigning-git-sha1s-without-git
@@ -58,6 +56,7 @@ def gen_blob_sha1(data):
return m.hexdigest() return m.hexdigest()
def rmtree(path): def rmtree(path):
"""In Windows a read-only file cannot be removed, and shutil.rmtree fails. """In Windows a read-only file cannot be removed, and shutil.rmtree fails.
So we implement our own version of rmtree to address this issue. So we implement our own version of rmtree to address this issue.