Merge remote-tracking branch 'carlos/cffi-index'
This commit is contained in:
@@ -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
|
||||
====================
|
||||
|
@@ -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
|
||||
|
||||
|
132
pygit2/decl.h
132
pygit2/decl.h
@@ -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
428
pygit2/index.py
Normal 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
|
@@ -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)
|
||||
|
29
src/diff.c
29
src/diff.c
@@ -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}
|
||||
};
|
||||
|
||||
|
787
src/index.c
787
src/index.c
@@ -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 */
|
||||
};
|
48
src/index.h
48
src/index.h
@@ -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
|
@@ -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}
|
||||
};
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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),
|
||||
|
36
src/tree.c
36
src/tree.c
@@ -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
138
test/test_merge.py
Normal 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)
|
@@ -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):
|
||||
|
Reference in New Issue
Block a user