Merge remote-tracking branch 'carlos/cffi-index'

This commit is contained in:
J. David Ibáñez
2014-07-10 15:52:30 +02:00
14 changed files with 785 additions and 965 deletions

View File

@@ -37,25 +37,14 @@ tree. You can use it independently of the index file, e.g.
The Index type
====================
.. automethod:: pygit2.Index.add
.. automethod:: pygit2.Index.remove
.. automethod:: pygit2.Index.clear
.. automethod:: pygit2.Index.read
.. automethod:: pygit2.Index.write
.. automethod:: pygit2.Index.read_tree
.. automethod:: pygit2.Index.write_tree
.. automethod:: pygit2.Index.diff_to_tree
.. automethod:: pygit2.Index.diff_to_workdir
.. autoclass:: pygit2.Index
:members:
The IndexEntry type
--------------------
.. autoattribute:: pygit2.IndexEntry.id
.. autoattribute:: pygit2.IndexEntry.hex
.. autoattribute:: pygit2.IndexEntry.path
.. autoattribute:: pygit2.IndexEntry.mode
.. autoclass:: pygit2.IndexEntry
:members:
Status
====================

View File

@@ -38,6 +38,7 @@ from .settings import Settings
from .credentials import *
from .remote import Remote, get_credentials
from .config import Config
from .index import Index, IndexEntry
from .errors import check_error
from .ffi import ffi, C, to_str

View File

@@ -3,9 +3,11 @@ typedef ... git_remote;
typedef ... git_refspec;
typedef ... git_push;
typedef ... git_cred;
typedef ... git_diff_file;
typedef ... git_tree;
typedef ... git_signature;
typedef ... git_index;
typedef ... git_diff;
typedef ... git_index_conflict_iterator;
#define GIT_OID_RAWSZ ...
#define GIT_PATH_MAX ...
@@ -25,6 +27,7 @@ typedef struct git_strarray {
size_t count;
} git_strarray;
typedef int64_t git_off_t;
typedef enum {
GIT_OK = 0,
@@ -181,6 +184,76 @@ int git_cred_ssh_key_new(
const char *privatekey,
const char *passphrase);
/*
* git_diff
*/
typedef enum {
GIT_SUBMODULE_IGNORE_RESET = -1,
GIT_SUBMODULE_IGNORE_NONE = 1,
GIT_SUBMODULE_IGNORE_UNTRACKED = 2,
GIT_SUBMODULE_IGNORE_DIRTY = 3,
GIT_SUBMODULE_IGNORE_ALL = 4,
GIT_SUBMODULE_IGNORE_DEFAULT = 0
} git_submodule_ignore_t;
typedef enum {
GIT_DELTA_UNMODIFIED = 0,
GIT_DELTA_ADDED = 1,
GIT_DELTA_DELETED = 2,
GIT_DELTA_MODIFIED = 3,
GIT_DELTA_RENAMED = 4,
GIT_DELTA_COPIED = 5,
GIT_DELTA_IGNORED = 6,
GIT_DELTA_UNTRACKED = 7,
GIT_DELTA_TYPECHANGE = 8,
} git_delta_t;
typedef struct {
git_oid id;
const char *path;
git_off_t size;
uint32_t flags;
uint16_t mode;
} git_diff_file;
typedef struct {
git_delta_t status;
uint32_t flags;
uint16_t similarity;
uint16_t nfiles;
git_diff_file old_file;
git_diff_file new_file;
} git_diff_delta;
typedef int (*git_diff_notify_cb)(
const git_diff *diff_so_far,
const git_diff_delta *delta_to_add,
const char *matched_pathspec,
void *payload);
typedef struct {
unsigned int version;
uint32_t flags;
git_submodule_ignore_t ignore_submodules;
git_strarray pathspec;
git_diff_notify_cb notify_cb;
void *notify_payload;
uint16_t context_lines;
uint16_t interhunk_lines;
uint16_t id_abbrev;
git_off_t max_size;
const char *old_prefix;
const char *new_prefix;
} git_diff_options;
int git_diff_init_options(git_diff_options *opts, unsigned int version);
int git_diff_index_to_workdir(git_diff **diff, git_repository *repo, git_index *index, const git_diff_options *opts);
int git_diff_tree_to_index(git_diff **diff, git_repository *repo, git_tree *old_tree, git_index *index, const git_diff_options *opts);
/*
* git_checkout
*/
@@ -369,3 +442,60 @@ int git_repository_init_ext(
git_repository **out,
const char *repo_path,
git_repository_init_options *opts);
/*
* git_index
*/
typedef int64_t git_time_t;
typedef struct {
git_time_t seconds;
unsigned int nanoseconds;
} git_index_time;
typedef struct git_index_entry {
git_index_time ctime;
git_index_time mtime;
unsigned int dev;
unsigned int ino;
unsigned int mode;
unsigned int uid;
unsigned int gid;
git_off_t file_size;
git_oid id;
unsigned short flags;
unsigned short flags_extended;
const char *path;
} git_index_entry;
typedef int (*git_index_matched_path_cb)(
const char *path, const char *matched_pathspec, void *payload);
void git_index_free(git_index *index);
int git_repository_index(git_index **out, git_repository *repo);
int git_index_open(git_index **out, const char *index_path);
int git_index_read(git_index *index, int force);
int git_index_write(git_index *index);
size_t git_index_entrycount(const git_index *index);
int git_index_find(size_t *at_pos, git_index *index, const char *path);
int git_index_add_bypath(git_index *index, const char *path);
int git_index_add(git_index *index, const git_index_entry *source_entry);
int git_index_remove(git_index *index, const char *path, int stage);
int git_index_read_tree(git_index *index, const git_tree *tree);
int git_index_clear(git_index *index);
int git_index_write_tree(git_oid *out, git_index *index);
int git_index_write_tree_to(git_oid *out, git_index *index, git_repository *repo);
const git_index_entry *git_index_get_bypath(git_index *index, const char *path, int stage);
const git_index_entry *git_index_get_byindex(git_index *index, size_t n);
int git_index_add_all(git_index *index, const git_strarray *pathspec, unsigned int flags,
git_index_matched_path_cb callback, void *payload);
int git_index_has_conflicts(const git_index *index);
void git_index_conflict_iterator_free(git_index_conflict_iterator *iterator);
int git_index_conflict_iterator_new(git_index_conflict_iterator **iterator_out, git_index *index);
int git_index_conflict_get(const git_index_entry **ancestor_out, const git_index_entry **our_out, const git_index_entry **their_out, git_index *index, const char *path);
int git_index_conflict_next(const git_index_entry **ancestor_out, const git_index_entry **our_out, const git_index_entry **their_out, git_index_conflict_iterator *iterator);
int git_index_conflict_remove(git_index *index, const char *path);

428
pygit2/index.py Normal file
View File

@@ -0,0 +1,428 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010-2014 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.
# Import from the future
from __future__ import absolute_import, unicode_literals
from _pygit2 import Oid, Tree, Diff
from .ffi import ffi, C, to_str, is_string, strings_to_strarray
from .errors import check_error, GitError
class Index(object):
def __init__(self, path=None):
"""Create a new Index
If path is supplied, the read and write methods will use that path
to read from and write to.
"""
cindex = ffi.new('git_index **')
err = C.git_index_open(cindex, to_str(path))
check_error(err)
self._index = cindex[0]
self._cindex = cindex
@classmethod
def from_c(cls, repo, ptr):
index = cls.__new__(cls);
index._repo = repo
index._index = ptr[0]
index._cindex = ptr
return index
@property
def _pointer(self):
return bytes(ffi.buffer(self._cindex)[:])
def __del__(self):
C.git_index_free(self._index)
def __len__(self):
return C.git_index_entrycount(self._index)
def __contains__(self, path):
err = C.git_index_find(ffi.NULL, self._index, to_str(path))
if err == C.GIT_ENOTFOUND:
return False
check_error(err)
return True
def __getitem__(self, key):
centry = ffi.NULL
if is_string(key):
centry = C.git_index_get_bypath(self._index, to_str(key), 0)
elif not key >= 0:
raise ValueError(key)
else:
centry = C.git_index_get_byindex(self._index, key)
if centry == ffi.NULL:
raise KeyError(key)
return IndexEntry._from_c(centry)
def __iter__(self):
return IndexIterator(self)
def read(self, force=True):
"""Update the contents the Index
Update the contents by reading from a file
Arguments:
force: if True (the default) allways reload. If False, only if the file has changed
"""
err = C.git_index_read(self._index, force)
check_error(err, True)
def write(self):
"""Write the contents of the Index to disk
"""
err = C.git_index_write(self._index)
check_error(err, True)
def clear(self):
err = C.git_index_clear(self._index)
check_error(err)
def read_tree(self, tree):
"""read_tree([Tree|Oid])
Replace the contents of the Index with those of a tree
The tree will be read recursively and all its children will also be
inserted into the Index.
"""
if is_string(tree):
tree = self._repo[tree]
if isinstance(tree, Oid):
if not hasattr(self, '_repo'):
raise TypeError("id given but no associated repository")
tree = self._repo[tree]
elif not isinstance(tree, Tree):
raise TypeError("argument must be Oid or Tree")
tree_cptr = ffi.new('git_tree **')
ffi.buffer(tree_cptr)[:] = tree._pointer[:]
err = C.git_index_read_tree(self._index, tree_cptr[0])
check_error(err)
def write_tree(self, repo=None):
"""write_tree([repo]) -> Oid
Create a tree out of the Index
The contents of the index will be written out to the object
database. If there is no associated repository, 'repo' must be
passed. If there is an associated repository and 'repo' is
passed, then that repository will be used instead.
It returns the id of the resulting tree.
"""
coid = ffi.new('git_oid *')
if repo:
err = C.git_index_write_tree_to(coid, self._index, repo._repo)
else:
err = C.git_index_write_tree(coid, self._index)
check_error(err)
return Oid(raw=bytes(ffi.buffer(coid)[:]))
def remove(self, path):
"""Remove an entry from the Index.
"""
err = C.git_index_remove(self._index, to_str(path), 0)
check_error(err, True)
def add_all(self, pathspecs=[]):
"""Add or update index entries matching files in the working directory.
If pathspecs are specified, only files matching those pathspecs will be added.
"""
arr, refs = strings_to_strarray(pathspecs)
err = C.git_index_add_all(self._index, arr, 0, ffi.NULL, ffi.NULL)
check_error(err, True)
def add(self, path_or_entry):
"""add([path|entry])
Add or update an entry in the Index
If a path is given, that file will be added. The path must be
relative to the root of the worktree and the Index must be associated
with a repository.
If an IndexEntry is given, that entry will be added or update in the Index
without checking for the existence of the path or id.
"""
if is_string(path_or_entry):
path = path_or_entry
err = C.git_index_add_bypath(self._index, to_str(path))
elif isinstance(path_or_entry, IndexEntry):
entry = path_or_entry
centry, str_ref = entry._to_c()
err = C.git_index_add(self._index, centry)
else:
raise AttributeError('argument must be string or IndexEntry')
check_error(err, True)
@property
def has_conflicts(self):
"""Whether this Index contains conflict information
"""
return C.git_index_has_conflicts(self._index) != 0
def diff_to_workdir(self, flags=0, context_lines=3, interhunk_lines=0):
"""diff_to_workdir(flags=0, context_lines=3, interhunk_lines=0) -> Diff
Diff the index against the working directory
Return a :py:class:`~pygit2.Diff` object with the differences
between the index and the working copy.
Arguments:
flags: a GIT_DIFF_* constant.
context_lines: the number of unchanged lines that define the
boundary of a hunk (and to display before and after)
interhunk_lines: the maximum number of unchanged lines between hunk
boundaries before the hunks will be merged into a one
"""
if not hasattr(self, '_repo'):
raise ValueError('diff needs an associated repository')
copts = ffi.new('git_diff_options *')
err = C.git_diff_init_options(copts, 1)
check_error(err)
copts.flags = flags
copts.context_lines = context_lines
copts.interhunk_lines = interhunk_lines
cdiff = ffi.new('git_diff **')
err = C.git_diff_index_to_workdir(cdiff, self._repo._repo, self._index, copts)
check_error(err)
return Diff.from_c(bytes(ffi.buffer(cdiff)[:]), self._repo)
def diff_to_tree(self, tree, flags=0, context_lines=3, interhunk_lines=0):
"""diff_to_tree(flags=0, context_lines=3, interhunk_lines=0) -> Diff
Diff the index against a tree
Return a :py:class:`~pygit2.Diff` object with the differences between the
index and the given tree.
Arguments:
tree: the tree to diff.
flags: a GIT_DIFF_* constant.
context_lines: the number of unchanged lines that define the boundary
of a hunk (and to display before and after)
interhunk_lines: the maximum number of unchanged lines between hunk
boundaries before the hunks will be merged into a one.
"""
if not hasattr(self, '_repo'):
raise ValueError('diff needs an associated repository')
if not isinstance(tree, Tree):
raise TypeError('tree must be a Tree')
copts = ffi.new('git_diff_options *')
err = C.git_diff_init_options(copts, 1)
check_error(err)
copts.flags = flags
copts.context_lines = context_lines
copts.interhunk_lines = interhunk_lines
ctree = ffi.new('git_tree **')
ffi.buffer(ctree)[:] = tree._pointer[:]
cdiff = ffi.new('git_diff **')
err = C.git_diff_tree_to_index(cdiff, self._repo._repo, ctree[0], self._index, copts)
check_error(err)
return Diff.from_c(bytes(ffi.buffer(cdiff)[:]), self._repo)
@property
def conflicts(self):
"""A collection of conflict information
This presents a mapping interface with the paths as keys. You
can use the ``del`` operator to remove a conflict form the Index.
Each conflict is made up of three elements. Access or iteration
of the conflicts returns a three-tuple of
:py:class:`~pygit2.IndexEntry`. The first is the common
ancestor, the second is the "ours" side of the conflict and the
thirs is the "theirs" side.
These elements may be None depending on which sides exist for
the particular conflict.
"""
if not hasattr(self, '_conflicts'):
self._conflicts = ConflictCollection(self)
return self._conflicts
class IndexEntry(object):
__slots__ = ['id', 'path', 'mode', '_index']
def __init__(self, path, object_id, mode):
self.path = path
"""The path of this entry"""
self.id = object_id
"""The id of the referenced object"""
self.mode = mode
"""The mode of this entry, a GIT_FILEMODE_ value"""
@property
def hex(self):
"""The id of the referenced object as a hex string"""
return self.id.hex
def _to_c(self):
"""Convert this entry into the C structure
The first returned arg is the pointer, the second is the reference to
the string we allocated, which we need to exist past this function
"""
centry = ffi.new('git_index_entry *')
# basically memcpy()
ffi.buffer(ffi.addressof(centry, 'id'))[:] = self.id.raw[:]
centry.mode = self.mode
path = ffi.new('char[]', to_str(self.path))
centry.path = path
return centry, path
@classmethod
def _from_c(cls, centry):
if centry == ffi.NULL:
return None
entry = cls.__new__(cls)
entry.path = ffi.string(centry.path).decode()
entry.mode = centry.mode
entry.id = Oid(raw=bytes(ffi.buffer(ffi.addressof(centry, 'id'))[:]))
return entry
class IndexIterator(object):
def __init__(self, index):
self.index = index
self.n = 0
self.max = len(index)
def next(self):
return self.__next__()
def __next__(self):
if self.n >= self.max:
raise StopIteration
entry = self.index[self.n]
self.n += 1
return entry
class ConflictCollection(object):
def __init__(self, index):
self._index = index
def __getitem__(self, path):
cancestor = ffi.new('git_index_entry **')
cours = ffi.new('git_index_entry **')
ctheirs = ffi.new('git_index_entry **')
err = C.git_index_conflict_get(cancestor, cours, ctheirs, self._index._index, to_str(path))
check_error(err)
ancestor = IndexEntry._from_c(cancestor[0])
ours = IndexEntry._from_c(cours[0])
theirs = IndexEntry._from_c(ctheirs[0])
return ancestor, ours, theirs
def __delitem__(self, path):
err = C.git_index_conflict_remove(self._index._index, to_str(path))
check_error(err)
def __iter__(self):
return ConflictIterator(self._index)
class ConflictIterator(object):
def __init__(self, index):
citer = ffi.new('git_index_conflict_iterator **')
err = C.git_index_conflict_iterator_new(citer, index._index)
check_error(err)
self._index = index
self._iter = citer[0]
def __del__(self):
C.git_index_conflict_iterator_free(self._iter)
def next(self):
return self.__next__()
def __next__(self):
cancestor = ffi.new('git_index_entry **')
cours = ffi.new('git_index_entry **')
ctheirs = ffi.new('git_index_entry **')
err = C.git_index_conflict_next(cancestor, cours, ctheirs, self._iter)
if err == C.GIT_ITEROVER:
raise StopIteration
check_error(err)
ancestor = IndexEntry._from_c(cancestor[0])
ours = IndexEntry._from_c(cours[0])
theirs = IndexEntry._from_c(ctheirs[0])
return ancestor, ours, theirs

View File

@@ -39,6 +39,7 @@ from .ffi import ffi, C, to_str
from .errors import check_error
from .remote import Remote
from .config import Config
from .index import Index
class Repository(_Repository):
@@ -307,3 +308,16 @@ class Repository(_Repository):
etc.
"""
C.git_repository_state_cleanup(self._repo)
#
# Index
#
@property
def index(self):
"""Index representing the repository's index file
"""
cindex = ffi.new('git_index **')
err = C.git_repository_index(cindex, self._repo)
check_error(err, True)
return Index.from_c(self, cindex)

View File

@@ -39,6 +39,7 @@ extern PyTypeObject TreeType;
extern PyTypeObject IndexType;
extern PyTypeObject DiffType;
extern PyTypeObject HunkType;
extern PyTypeObject RepositoryType;
PyTypeObject PatchType;
@@ -383,6 +384,33 @@ PyTypeObject HunkType = {
0, /* tp_new */
};
PyDoc_STRVAR(Diff_from_c__doc__, "Method exposed for Index to hook into");
PyObject *
Diff_from_c(Diff *dummy, PyObject *args)
{
PyObject *py_diff, *py_repository;
git_diff *diff;
char *buffer;
Py_ssize_t length;
if (!PyArg_ParseTuple(args, "OO!", &py_diff, &RepositoryType, &py_repository))
return NULL;
/* Here we need to do the opposite conversion from the _pointer getters */
if (PyBytes_AsStringAndSize(py_diff, &buffer, &length))
return NULL;
if (length != sizeof(git_diff *)) {
PyErr_SetString(PyExc_TypeError, "passed value is not a pointer");
return NULL;
}
/* the "buffer" contains the pointer */
diff = *((git_diff **) buffer);
return wrap_diff(diff, (Repository *) py_repository);
}
PyDoc_STRVAR(Diff_merge__doc__,
"merge(diff)\n"
@@ -481,6 +509,7 @@ PyMappingMethods Diff_as_mapping = {
static PyMethodDef Diff_methods[] = {
METHOD(Diff, merge, METH_VARARGS),
METHOD(Diff, find_similar, METH_VARARGS),
METHOD(Diff, from_c, METH_STATIC | METH_VARARGS),
{NULL}
};

View File

@@ -1,787 +0,0 @@
/*
* Copyright 2010-2014 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.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "error.h"
#include "types.h"
#include "utils.h"
#include "oid.h"
#include "diff.h"
#include "index.h"
extern PyTypeObject IndexType;
extern PyTypeObject TreeType;
extern PyTypeObject DiffType;
extern PyTypeObject IndexIterType;
extern PyTypeObject IndexEntryType;
extern PyTypeObject OidType;
extern PyTypeObject RepositoryType;
int
Index_init(Index *self, PyObject *args, PyObject *kwds)
{
char *path = NULL;
int err;
if (kwds && PyDict_Size(kwds) > 0) {
PyErr_SetString(PyExc_TypeError, "Index takes no keyword arguments");
return -1;
}
if (!PyArg_ParseTuple(args, "|s", &path))
return -1;
self->repo = NULL;
err = git_index_open(&self->index, path);
if (err < 0) {
Error_set_str(err, path);
return -1;
}
return 0;
}
void
Index_dealloc(Index* self)
{
PyObject_GC_UnTrack(self);
Py_XDECREF(self->repo);
git_index_free(self->index);
Py_TYPE(self)->tp_free(self);
}
int
Index_traverse(Index *self, visitproc visit, void *arg)
{
Py_VISIT(self->repo);
return 0;
}
PyDoc_STRVAR(Index_add__doc__,
"add([path|entry])\n"
"\n"
"Add or update an index entry from a file in disk.");
PyObject *
Index_add(Index *self, PyObject *args)
{
int err;
const char *path;
IndexEntry *py_entry;
if (PyArg_ParseTuple(args, "O!", &IndexEntryType, &py_entry)) {
err = git_index_add(self->index, &py_entry->entry);
if (err < 0)
return Error_set(err);
Py_RETURN_NONE;
}
PyErr_Clear();
if (!PyArg_ParseTuple(args, "s", &path))
return NULL;
err = git_index_add_bypath(self->index, path);
if (err < 0)
return Error_set_str(err, path);
Py_RETURN_NONE;
}
PyDoc_STRVAR(Index_add_all__doc__,
"add_all([file names|glob pattern])\n"
"\n"
"Add or update index entries matching files in the working directory.");
PyObject *
Index_add_all(Index *self, PyObject *pylist)
{
int err;
git_strarray pathspec;
if (get_strarraygit_from_pylist(&pathspec, pylist) < 0)
return NULL;
err = git_index_add_all(self->index, &pathspec, 0, NULL, NULL);
git_strarray_free(&pathspec);
if (err < 0) {
Error_set(err);
return NULL;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(Index_clear__doc__,
"clear()\n"
"\n"
"Clear the contents (all the entries) of an index object.");
PyObject *
Index_clear(Index *self)
{
git_index_clear(self->index);
Py_RETURN_NONE;
}
PyDoc_STRVAR(Index_diff_to_workdir__doc__,
"diff_to_workdir([flag, context_lines, interhunk_lines]) -> Diff\n"
"\n"
"Return a :py:class:`~pygit2.Diff` object with the differences between the\n"
"index and the working copy.\n"
"\n"
"Arguments:\n"
"\n"
"flag: a GIT_DIFF_* constant.\n"
"\n"
"context_lines: the number of unchanged lines that define the boundary\n"
" of a hunk (and to display before and after)\n"
"\n"
"interhunk_lines: the maximum number of unchanged lines between hunk\n"
" boundaries before the hunks will be merged into a one.\n");
PyObject *
Index_diff_to_workdir(Index *self, PyObject *args)
{
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff *diff;
int err;
if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines,
&opts.interhunk_lines))
return NULL;
err = git_diff_index_to_workdir(
&diff,
self->repo->repo,
self->index,
&opts);
if (err < 0)
return Error_set(err);
return wrap_diff(diff, self->repo);
}
PyDoc_STRVAR(Index_diff_to_tree__doc__,
"diff_to_tree(tree [, flag, context_lines, interhunk_lines]) -> Diff\n"
"\n"
"Return a :py:class:`~pygit2.Diff` object with the differences between the\n"
"index and the given tree.\n"
"\n"
"Arguments:\n"
"\n"
"tree: the tree to diff.\n"
"\n"
"flag: a GIT_DIFF_* constant.\n"
"\n"
"context_lines: the number of unchanged lines that define the boundary\n"
" of a hunk (and to display before and after)\n"
"\n"
"interhunk_lines: the maximum number of unchanged lines between hunk\n"
" boundaries before the hunks will be merged into a one.\n");
PyObject *
Index_diff_to_tree(Index *self, PyObject *args)
{
Repository *py_repo;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff *diff;
int err;
Tree *py_tree = NULL;
if (!PyArg_ParseTuple(args, "O!|IHH", &TreeType, &py_tree, &opts.flags,
&opts.context_lines, &opts.interhunk_lines))
return NULL;
py_repo = py_tree->repo;
err = git_diff_tree_to_index(&diff, py_repo->repo, py_tree->tree,
self->index, &opts);
if (err < 0)
return Error_set(err);
return wrap_diff(diff, py_repo);
}
PyDoc_STRVAR(Index__find__doc__,
"_find(path) -> integer\n"
"\n"
"Find the first index of any entries which point to given path in the\n"
"index file.");
PyObject *
Index__find(Index *self, PyObject *py_path)
{
char *path;
size_t idx;
int err;
path = PyBytes_AsString(py_path);
if (!path)
return NULL;
err = git_index_find(&idx, self->index, path);
if (err < 0)
return Error_set_str(err, path);
return PyLong_FromSize_t(idx);
}
PyDoc_STRVAR(Index_read__doc__,
"read(force=True)\n"
"\n"
"Update the contents of an existing index object in memory by reading from\n"
"the hard disk."
"Arguments:\n"
"\n"
"force: if True (the default) allways reload. If False, only if the file has changed"
);
PyObject *
Index_read(Index *self, PyObject *args)
{
int err, force = 1;
if (!PyArg_ParseTuple(args, "|i", &force))
return NULL;
err = git_index_read(self->index, force);
if (err < GIT_OK)
return Error_set(err);
Py_RETURN_NONE;
}
PyDoc_STRVAR(Index_write__doc__,
"write()\n"
"\n"
"Write an existing index object from memory back to disk using an atomic\n"
"file lock.");
PyObject *
Index_write(Index *self)
{
int err;
err = git_index_write(self->index);
if (err < GIT_OK)
return Error_set(err);
Py_RETURN_NONE;
}
int
Index_contains(Index *self, PyObject *value)
{
char *path;
int err;
path = py_path_to_c_str(value);
if (!path)
return -1;
err = git_index_find(NULL, self->index, path);
if (err == GIT_ENOTFOUND) {
free(path);
return 0;
}
if (err < 0) {
Error_set_str(err, path);
free(path);
return -1;
}
free(path);
return 1;
}
PyObject *
Index_iter(Index *self)
{
IndexIter *iter;
iter = PyObject_New(IndexIter, &IndexIterType);
if (iter) {
Py_INCREF(self);
iter->owner = self;
iter->i = 0;
}
return (PyObject*)iter;
}
Py_ssize_t
Index_len(Index *self)
{
return (Py_ssize_t)git_index_entrycount(self->index);
}
PyObject *
wrap_index_entry(const git_index_entry *entry, Index *index)
{
IndexEntry *py_entry;
py_entry = PyObject_New(IndexEntry, &IndexEntryType);
if (!py_entry)
return NULL;
memcpy(&py_entry->entry, entry, sizeof(struct git_index_entry));
py_entry->entry.path = strdup(entry->path);
if (!py_entry->entry.path) {
Py_CLEAR(py_entry);
return NULL;
}
return (PyObject*)py_entry;
}
PyObject *
Index_getitem(Index *self, PyObject *value)
{
long idx;
char *path;
const git_index_entry *index_entry;
/* Case 1: integer */
if (PyLong_Check(value)) {
idx = PyLong_AsLong(value);
if (idx == -1 && PyErr_Occurred())
return NULL;
if (idx < 0) {
PyErr_SetObject(PyExc_ValueError, value);
return NULL;
}
index_entry = git_index_get_byindex(self->index, (size_t)idx);
/* Case 2: byte or text string */
} else {
path = py_path_to_c_str(value);
if (!path)
return NULL;
index_entry = git_index_get_bypath(self->index, path, 0);
free(path);
}
if (!index_entry) {
PyErr_SetObject(PyExc_KeyError, value);
return NULL;
}
return wrap_index_entry(index_entry, self);
}
PyDoc_STRVAR(Index_remove__doc__,
"remove(path)\n"
"\n"
"Removes an entry from index.");
PyObject *
Index_remove(Index *self, PyObject *args)
{
int err;
const char *path;
if (!PyArg_ParseTuple(args, "s", &path))
return NULL;
err = git_index_remove(self->index, path, 0);
if (err < 0) {
Error_set(err);
return NULL;
}
Py_RETURN_NONE;
}
PyDoc_STRVAR(Index_read_tree__doc__,
"read_tree(tree)\n"
"\n"
"Update the index file from the specified tree. The tree can be a Tree object or an Oid.\n"
"Using an Oid is only possible if this index is associated with a repository");
PyObject *
Index_read_tree(Index *self, PyObject *value)
{
git_oid oid;
git_tree *tree = NULL;
int err, need_free = 0;
size_t len;
len = py_oid_to_git_oid(value, &oid);
if (len == 0) {
Tree *py_tree;
if (!PyObject_TypeCheck(value, &TreeType)) {
return NULL;
}
PyErr_Clear();
py_tree = (Tree *) value;
tree = py_tree->tree;
}
/*
* if the user passed in an id but we're not associated with a
* repo, we can't do anything
*/
if (tree == NULL && self->repo == NULL) {
PyErr_SetString(PyExc_TypeError, "id given but no associated repository");
return NULL;
} else if (tree == NULL) {
need_free = 1;
err = git_tree_lookup_prefix(&tree, self->repo->repo, &oid, len);
if (err < 0)
return Error_set(err);
}
err = git_index_read_tree(self->index, tree);
if (need_free)
git_tree_free(tree);
if (err < 0)
return Error_set(err);
Py_RETURN_NONE;
}
PyDoc_STRVAR(Index_write_tree__doc__,
"write_tree([repo]) -> Oid\n"
"\n"
"Create a tree object from the index file, return its oid.\n"
"If 'repo' is passed, write to that repository's odb.");
PyObject *
Index_write_tree(Index *self, PyObject *args)
{
git_oid oid;
Repository *repo = NULL;
int err;
if (!PyArg_ParseTuple(args, "|O!", &RepositoryType, &repo))
return NULL;
if (repo)
err = git_index_write_tree_to(&oid, self->index, repo->repo);
else
err = git_index_write_tree(&oid, self->index);
if (err < 0)
return Error_set(err);
return git_oid_to_python(&oid);
}
PyMethodDef Index_methods[] = {
METHOD(Index, add, METH_VARARGS),
METHOD(Index, add_all, METH_O),
METHOD(Index, remove, METH_VARARGS),
METHOD(Index, clear, METH_NOARGS),
METHOD(Index, diff_to_workdir, METH_VARARGS),
METHOD(Index, diff_to_tree, METH_VARARGS),
METHOD(Index, _find, METH_O),
METHOD(Index, read, METH_VARARGS),
METHOD(Index, write, METH_NOARGS),
METHOD(Index, read_tree, METH_O),
METHOD(Index, write_tree, METH_VARARGS),
{NULL}
};
PySequenceMethods Index_as_sequence = {
0, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
0, /* sq_item */
0, /* sq_slice */
0, /* sq_ass_item */
0, /* sq_ass_slice */
(objobjproc)Index_contains, /* sq_contains */
};
PyMappingMethods Index_as_mapping = {
(lenfunc)Index_len, /* mp_length */
(binaryfunc)Index_getitem, /* mp_subscript */
NULL, /* mp_ass_subscript */
};
PyDoc_STRVAR(Index__doc__, "Index file.");
PyTypeObject IndexType = {
PyVarObject_HEAD_INIT(NULL, 0)
"_pygit2.Index", /* tp_name */
sizeof(Index), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)Index_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
&Index_as_sequence, /* tp_as_sequence */
&Index_as_mapping, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_BASETYPE |
Py_TPFLAGS_HAVE_GC, /* tp_flags */
Index__doc__, /* tp_doc */
(traverseproc)Index_traverse, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
(getiterfunc)Index_iter, /* tp_iter */
0, /* tp_iternext */
Index_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Index_init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};
void
IndexIter_dealloc(IndexIter *self)
{
Py_CLEAR(self->owner);
Py_TYPE(self)->tp_free(self);
}
PyObject *
IndexIter_iternext(IndexIter *self)
{
const git_index_entry *index_entry;
index_entry = git_index_get_byindex(self->owner->index, self->i);
if (!index_entry)
return NULL;
self->i += 1;
return wrap_index_entry(index_entry, self->owner);
}
PyDoc_STRVAR(IndexIter__doc__, "Index iterator.");
PyTypeObject IndexIterType = {
PyVarObject_HEAD_INIT(NULL, 0)
"_pygit2.IndexIter", /* tp_name */
sizeof(IndexIter), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)IndexIter_dealloc , /* 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 */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
IndexIter__doc__, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
PyObject_SelfIter, /* tp_iter */
(iternextfunc)IndexIter_iternext, /* tp_iternext */
};
int
IndexEntry_init(IndexEntry *self, PyObject *args, PyObject *kwds)
{
char *c_path = NULL;
Oid *id = NULL;
unsigned int mode;
char *keywords[] = {"path", "oid", "mode", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO!I", keywords,
&c_path, &OidType, &id, &mode))
return -1;
memset(&self->entry, 0, sizeof(struct git_index_entry));
self->entry.path = strdup(c_path);
if (!self->entry.path)
return -1;
if (id)
git_oid_cpy(&self->entry.id, &id->oid);
if (mode)
self->entry.mode = mode;
return 0;
}
void
IndexEntry_dealloc(IndexEntry *self)
{
free(self->entry.path);
PyObject_Del(self);
}
PyDoc_STRVAR(IndexEntry_mode__doc__, "Mode.");
PyObject *
IndexEntry_mode__get__(IndexEntry *self)
{
return PyLong_FromLong(self->entry.mode);
}
int
IndexEntry_mode__set__(IndexEntry *self, PyObject *py_mode)
{
long c_val;
c_val = PyLong_AsLong(py_mode);
if (c_val == -1 && PyErr_Occurred())
return -1;
self->entry.mode = (unsigned int) c_val;
return 0;
}
PyDoc_STRVAR(IndexEntry_path__doc__, "Path.");
PyObject *
IndexEntry_path__get__(IndexEntry *self)
{
return to_path(self->entry.path);
}
int
IndexEntry_path__set__(IndexEntry *self, PyObject *py_path)
{
char *c_path;
c_path = py_str_to_c_str(py_path, NULL);
if (!c_path)
return -1;
free(self->entry.path);
self->entry.path = c_path;
return 0;
}
PyDoc_STRVAR(IndexEntry_id__doc__, "Object id.");
PyObject *
IndexEntry_id__get__(IndexEntry *self)
{
return git_oid_to_python(&self->entry.id);
}
int
IndexEntry_id__set__(IndexEntry *self, PyObject *py_id)
{
if (!py_oid_to_git_oid(py_id, &self->entry.id))
return -1;
return 0;
}
PyDoc_STRVAR(IndexEntry_hex__doc__, "Hex id.");
PyObject *
IndexEntry_hex__get__(IndexEntry *self)
{
return git_oid_to_py_str(&self->entry.id);
}
PyGetSetDef IndexEntry_getseters[] = {
GETSET(IndexEntry, mode),
GETSET(IndexEntry, path),
GETSET(IndexEntry, id),
GETTER(IndexEntry, hex),
{NULL},
};
PyDoc_STRVAR(IndexEntry__doc__, "Index entry.");
PyTypeObject IndexEntryType = {
PyVarObject_HEAD_INIT(NULL, 0)
"_pygit2.IndexEntry", /* tp_name */
sizeof(IndexEntry), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)IndexEntry_dealloc, /* 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 */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
IndexEntry__doc__, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
IndexEntry_getseters, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)IndexEntry_init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2010-2014 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.
*/
#ifndef INCLUDE_pygit2_index_h
#define INCLUDE_pygit2_index_h
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <git2.h>
PyObject* Index_add(Index *self, PyObject *args);
PyObject* Index_add_all(Index *self, PyObject *pylist);
PyObject* Index_clear(Index *self);
PyObject* Index_find(Index *self, PyObject *py_path);
PyObject* Index_read(Index *self, PyObject *args);
PyObject* Index_write(Index *self);
PyObject* Index_iter(Index *self);
PyObject* Index_getitem(Index *self, PyObject *value);
PyObject* Index_read_tree(Index *self, PyObject *value);
PyObject* Index_write_tree(Index *self, PyObject *args);
Py_ssize_t Index_len(Index *self);
int Index_setitem(Index *self, PyObject *key, PyObject *value);
#endif

View File

@@ -100,6 +100,14 @@ Object_type__get__(Object *self)
return PyLong_FromLong(git_object_type(self->obj));
}
PyDoc_STRVAR(Object__pointer__doc__, "Get the object's pointer. For internal use only.");
PyObject *
Object__pointer__get__(Object *self)
{
/* Bytes means a raw buffer */
return PyBytes_FromStringAndSize((char *) &self->obj, sizeof(git_object *));
}
PyDoc_STRVAR(Object_read_raw__doc__,
"read_raw()\n"
@@ -181,6 +189,7 @@ PyGetSetDef Object_getseters[] = {
GETTER(Object, id),
GETTER(Object, hex),
GETTER(Object, type),
GETTER(Object, _pointer),
{NULL}
};

View File

@@ -52,9 +52,6 @@ extern PyTypeObject TreeEntryType;
extern PyTypeObject TreeIterType;
extern PyTypeObject BlobType;
extern PyTypeObject TagType;
extern PyTypeObject IndexType;
extern PyTypeObject IndexEntryType;
extern PyTypeObject IndexIterType;
extern PyTypeObject WalkerType;
extern PyTypeObject ReferenceType;
extern PyTypeObject RefLogIterType;
@@ -268,11 +265,6 @@ moduleinit(PyObject* m)
/*
* Index & Working copy
*/
INIT_TYPE(IndexType, NULL, PyType_GenericNew)
INIT_TYPE(IndexEntryType, NULL, PyType_GenericNew)
INIT_TYPE(IndexIterType, NULL, NULL)
ADD_TYPE(m, Index)
ADD_TYPE(m, IndexEntry)
/* Status */
ADD_CONSTANT_INT(m, GIT_STATUS_CURRENT)
ADD_CONSTANT_INT(m, GIT_STATUS_INDEX_NEW)

View File

@@ -461,41 +461,6 @@ Repository_write(Repository *self, PyObject *args)
return git_oid_to_python(&oid);
}
PyDoc_STRVAR(Repository_index__doc__, "Index file.");
PyObject *
Repository_index__get__(Repository *self, void *closure)
{
int err;
git_index *index;
Index *py_index;
assert(self->repo);
if (self->index == NULL) {
err = git_repository_index(&index, self->repo);
if (err < 0)
return Error_set(err);
py_index = PyObject_GC_New(Index, &IndexType);
if (!py_index) {
git_index_free(index);
return NULL;
}
Py_INCREF(self);
py_index->repo = self;
py_index->index = index;
PyObject_GC_Track(py_index);
self->index = (PyObject*)py_index;
}
Py_INCREF(self->index);
return self->index;
}
PyDoc_STRVAR(Repository_path__doc__,
"The normalized path to the git repository.");
@@ -1621,7 +1586,6 @@ PyMethodDef Repository_methods[] = {
};
PyGetSetDef Repository_getseters[] = {
GETTER(Repository, index),
GETTER(Repository, path),
GETSET(Repository, head),
GETTER(Repository, head_is_detached),

View File

@@ -395,19 +395,45 @@ Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds)
{
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff *diff;
git_index *index;
char *buffer;
Py_ssize_t length;
Repository *py_repo;
PyObject *py_idx, *py_idx_ptr;
int err;
Index *py_idx = NULL;
if (!PyArg_ParseTuple(args, "O!|IHH", &IndexType, &py_idx, &opts.flags,
if (!PyArg_ParseTuple(args, "O|IHH", &py_idx, &opts.flags,
&opts.context_lines,
&opts.interhunk_lines))
return NULL;
/*
* This is a hack to check whether we're passed an index, as I
* haven't found a good way to grab a type object for
* pygit2.index.Index.
*/
if (!PyObject_GetAttrString(py_idx, "_index")) {
PyErr_SetString(PyExc_TypeError, "argument must be an Index");
return NULL;
}
py_idx_ptr = PyObject_GetAttrString(py_idx, "_pointer");
if (!py_idx_ptr)
return NULL;
/* Here we need to do the opposite conversion from the _pointer getters */
if (PyBytes_AsStringAndSize(py_idx_ptr, &buffer, &length))
return NULL;
if (length != sizeof(git_index *)) {
PyErr_SetString(PyExc_TypeError, "passed value is not a pointer");
return NULL;
}
/* the "buffer" contains the pointer */
index = *((git_index **) buffer);
py_repo = self->repo;
err = git_diff_tree_to_index(&diff, py_repo->repo, self->tree,
py_idx->index, &opts);
err = git_diff_tree_to_index(&diff, py_repo->repo, self->tree, index, &opts);
if (err < 0)
return Error_set(err);

138
test/test_merge.py Normal file
View File

@@ -0,0 +1,138 @@
# -*- coding: UTF-8 -*-
#
# Copyright 2010-2014 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 merging and information about it."""
# Import from the future
from __future__ import absolute_import
from __future__ import unicode_literals
import unittest
import os
from pygit2 import GIT_MERGE_ANALYSIS_NONE, GIT_MERGE_ANALYSIS_NORMAL, GIT_MERGE_ANALYSIS_UP_TO_DATE
from pygit2 import GIT_MERGE_ANALYSIS_FASTFORWARD, GIT_MERGE_ANALYSIS_UNBORN
import pygit2
from . import utils
class MergeTestBasic(utils.RepoTestCaseForMerging):
def test_merge_none(self):
self.assertRaises(TypeError, self.repo.merge, None)
def test_merge_analysis_uptodate(self):
branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533'
branch_id = self.repo.get(branch_head_hex).id
analysis, preference = self.repo.merge_analysis(branch_id)
self.assertTrue(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
self.assertEqual({}, self.repo.status())
def test_merge_analysis_fastforward(self):
branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87'
branch_id = self.repo.get(branch_head_hex).id
analysis, preference = self.repo.merge_analysis(branch_id)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
self.assertTrue(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
self.assertEqual({}, self.repo.status())
def test_merge_no_fastforward_no_conflicts(self):
branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
branch_id = self.repo.get(branch_head_hex).id
analysis, preference = self.repo.merge_analysis(branch_id)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
# Asking twice to assure the reference counting is correct
self.assertEqual({}, self.repo.status())
self.assertEqual({}, self.repo.status())
def test_merge_no_fastforward_conflicts(self):
branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
branch_id = self.repo.get(branch_head_hex).id
analysis, preference = self.repo.merge_analysis(branch_id)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
self.repo.merge(branch_id)
self.assertTrue(self.repo.index.has_conflicts)
status = pygit2.GIT_STATUS_WT_NEW | pygit2.GIT_STATUS_INDEX_DELETED
# Asking twice to assure the reference counting is correct
self.assertEqual({'.gitignore': status}, self.repo.status())
self.assertEqual({'.gitignore': status}, self.repo.status())
# Checking the index works as expected
self.repo.index.add('.gitignore')
self.repo.index.write()
self.assertEqual({'.gitignore': pygit2.GIT_STATUS_INDEX_MODIFIED}, self.repo.status())
def test_merge_invalid_hex(self):
branch_head_hex = '12345678'
self.assertRaises(KeyError, self.repo.merge, branch_head_hex)
def test_merge_already_something_in_index(self):
branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
branch_oid = self.repo.get(branch_head_hex).id
with open(os.path.join(self.repo.workdir, 'inindex.txt'), 'w') as f:
f.write('new content')
self.repo.index.add('inindex.txt')
self.assertRaises(pygit2.GitError, self.repo.merge, branch_oid)
class MergeTestWithConflicts(utils.RepoTestCaseForMerging):
def test_merge_no_fastforward_conflicts(self):
branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
branch_id = self.repo.get(branch_head_hex).id
analysis, preference = self.repo.merge_analysis(branch_id)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
self.repo.merge(branch_id)
self.assertTrue(self.repo.index.has_conflicts)
self.assertRaises(KeyError, self.repo.index.conflicts.__getitem__, 'some-file')
ancestor, ours, theirs = self.repo.index.conflicts['.gitignore']
self.assertEqual(None, ancestor)
self.assertNotEqual(None, ours)
self.assertNotEqual(None, theirs)
self.assertEqual('.gitignore', ours.path)
self.assertEqual('.gitignore', theirs.path)
self.assertEqual(1, len(list(self.repo.index.conflicts)))
# Checking the index works as expected
self.repo.index.add('.gitignore')
self.repo.index.write()
self.assertRaises(KeyError, self.repo.index.conflicts.__getitem__, '.gitignore')
def test_merge_remove_conflicts(self):
other_branch_tip = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
self.repo.merge(other_branch_tip)
idx = self.repo.index
self.assertTrue(idx.has_conflicts)
self.assertRaises(KeyError, idx.conflicts.__delitem__, 'some-file')
del idx.conflicts['.gitignore']
self.assertFalse(idx.has_conflicts)

View File

@@ -40,8 +40,6 @@ from os.path import join, realpath
# Import from pygit2
from pygit2 import GIT_OBJ_ANY, GIT_OBJ_BLOB, GIT_OBJ_COMMIT
from pygit2 import GIT_MERGE_ANALYSIS_NONE, GIT_MERGE_ANALYSIS_NORMAL, GIT_MERGE_ANALYSIS_UP_TO_DATE
from pygit2 import GIT_MERGE_ANALYSIS_FASTFORWARD, GIT_MERGE_ANALYSIS_UNBORN
from pygit2 import init_repository, clone_repository, clone_into, discover_repository
from pygit2 import Oid, Reference, hashfile
import pygit2
@@ -318,69 +316,6 @@ class RepositoryTest_II(utils.RepoTestCase):
self.assertTrue("hola mundo\n" in diff.patch)
self.assertTrue("bonjour le monde\n" in diff.patch)
class RepositoryTest_III(utils.RepoTestCaseForMerging):
def test_merge_none(self):
self.assertRaises(TypeError, self.repo.merge, None)
def test_merge_analysis_uptodate(self):
branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533'
branch_id = self.repo.get(branch_head_hex).id
analysis, preference = self.repo.merge_analysis(branch_id)
self.assertTrue(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
self.assertEqual({}, self.repo.status())
def test_merge_analysis_fastforward(self):
branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87'
branch_id = self.repo.get(branch_head_hex).id
analysis, preference = self.repo.merge_analysis(branch_id)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
self.assertTrue(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
self.assertEqual({}, self.repo.status())
def test_merge_no_fastforward_no_conflicts(self):
branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
branch_id = self.repo.get(branch_head_hex).id
analysis, preference = self.repo.merge_analysis(branch_id)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
# Asking twice to assure the reference counting is correct
self.assertEqual({}, self.repo.status())
self.assertEqual({}, self.repo.status())
def test_merge_no_fastforward_conflicts(self):
branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247'
branch_id = self.repo.get(branch_head_hex).id
analysis, preference = self.repo.merge_analysis(branch_id)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
self.repo.merge(branch_id)
status = pygit2.GIT_STATUS_WT_NEW | pygit2.GIT_STATUS_INDEX_DELETED
# Asking twice to assure the reference counting is correct
self.assertEqual({'.gitignore': status}, self.repo.status())
self.assertEqual({'.gitignore': status}, self.repo.status())
# Checking the index works as expected
self.repo.index.add('.gitignore')
self.repo.index.write()
self.assertEqual({'.gitignore': pygit2.GIT_STATUS_INDEX_MODIFIED}, self.repo.status())
def test_merge_invalid_hex(self):
branch_head_hex = '12345678'
self.assertRaises(KeyError, self.repo.merge, branch_head_hex)
def test_merge_already_something_in_index(self):
branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1'
branch_oid = self.repo.get(branch_head_hex).id
with open(os.path.join(self.repo.workdir, 'inindex.txt'), 'w') as f:
f.write('new content')
self.repo.index.add('inindex.txt')
self.assertRaises(pygit2.GitError, self.repo.merge, branch_oid)
class RepositorySignatureTest(utils.RepoTestCase):
def test_default_signature(self):