Add support for Repository.describe(...).
This commit is contained in:
@@ -71,3 +71,4 @@ Below there are some general attributes and methods:
|
||||
.. automethod:: pygit2.Repository.state_cleanup
|
||||
.. automethod:: pygit2.Repository.write_archive
|
||||
.. automethod:: pygit2.Repository.ahead_behind
|
||||
.. automethod:: pygit2.Repository.describe
|
||||
|
@@ -756,6 +756,47 @@ int git_merge_trees(git_index **out, git_repository *repo, const git_tree *ances
|
||||
int git_merge_file_from_index(git_merge_file_result *out, git_repository *repo, const git_index_entry *ancestor, const git_index_entry *ours, const git_index_entry *theirs, const git_merge_file_options *opts);
|
||||
void git_merge_file_result_free(git_merge_file_result *result);
|
||||
|
||||
/*
|
||||
* Describe
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
GIT_DESCRIBE_DEFAULT,
|
||||
GIT_DESCRIBE_TAGS,
|
||||
GIT_DESCRIBE_ALL,
|
||||
} git_describe_strategy_t;
|
||||
|
||||
typedef struct git_describe_options {
|
||||
unsigned int version;
|
||||
unsigned int max_candidates_tags;
|
||||
unsigned int describe_strategy;
|
||||
const char *pattern;
|
||||
int only_follow_first_parent;
|
||||
int show_commit_oid_as_fallback;
|
||||
} git_describe_options;
|
||||
|
||||
#define GIT_DESCRIBE_OPTIONS_VERSION ...
|
||||
|
||||
int git_describe_init_options(git_describe_options *opts, unsigned int version);
|
||||
|
||||
typedef struct {
|
||||
unsigned int version;
|
||||
unsigned int abbreviated_size;
|
||||
int always_use_long_format;
|
||||
const char *dirty_suffix;
|
||||
} git_describe_format_options;
|
||||
|
||||
#define GIT_DESCRIBE_FORMAT_OPTIONS_VERSION ...
|
||||
|
||||
int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version);
|
||||
|
||||
typedef ... git_describe_result;
|
||||
|
||||
int git_describe_commit(git_describe_result **result, git_object *committish, git_describe_options *opts);
|
||||
int git_describe_workdir(git_describe_result **out, git_repository *repo, git_describe_options *opts);
|
||||
int git_describe_format(git_buf *out, const git_describe_result *result, const git_describe_format_options *opts);
|
||||
void git_describe_result_free(git_describe_result *result);
|
||||
|
||||
#define GIT_ATTR_CHECK_FILE_THEN_INDEX ...
|
||||
#define GIT_ATTR_CHECK_INDEX_THEN_FILE ...
|
||||
#define GIT_ATTR_CHECK_INDEX_ONLY ...
|
||||
|
@@ -637,6 +637,100 @@ class Repository(_Repository):
|
||||
|
||||
return Index.from_c(self, cindex)
|
||||
|
||||
#
|
||||
# Describe
|
||||
#
|
||||
def describe(self, committish=None, max_candidates_tags=None,
|
||||
describe_strategy=None, pattern=None,
|
||||
only_follow_first_parent=None,
|
||||
show_commit_oid_as_fallback=None, abbreviated_size=None,
|
||||
always_use_long_format=None, dirty_suffix=None):
|
||||
"""Describe a commit-ish or the current working tree.
|
||||
|
||||
:param committish: Commit-ish object or object name to describe, or
|
||||
`None` to describe the current working tree.
|
||||
:type committish: `str`, :class:`~.Reference`, or :class:`~.Commit`
|
||||
|
||||
:param int max_candidates_tags: The number of candidate tags to
|
||||
consider. Increasing above 10 will take slightly longer but may
|
||||
produce a more accurate result. A value of 0 will cause only exact
|
||||
matches to be output.
|
||||
:param int describe_strategy: A GIT_DESCRIBE_* constant.
|
||||
:param str pattern: Only consider tags matching the given `glob(7)`
|
||||
pattern, excluding the "refs/tags/" prefix.
|
||||
:param bool only_follow_first_parent: Follow only the first parent
|
||||
commit upon seeing a merge commit.
|
||||
:param bool show_commit_oid_as_fallback: Show uniquely abbreviated
|
||||
commit object as fallback.
|
||||
:param int abbreviated_size: The minimum number of hexadecimal digits
|
||||
to show for abbreviated object names. A value of 0 will suppress
|
||||
long format, only showing the closest tag.
|
||||
:param bool always_use_long_format: Always output the long format (the
|
||||
nearest tag, the number of commits, and the abbrevated commit name)
|
||||
even when the committish matches a tag.
|
||||
:param str dirty_suffix: A string to append if the working tree is
|
||||
dirty.
|
||||
|
||||
:returns: The description.
|
||||
:rtype: `str`
|
||||
|
||||
Example::
|
||||
|
||||
repo.describe(pattern='public/*', dirty_suffix='-dirty')
|
||||
"""
|
||||
|
||||
options = ffi.new('git_describe_options *')
|
||||
C.git_describe_init_options(options, C.GIT_DESCRIBE_OPTIONS_VERSION)
|
||||
|
||||
if max_candidates_tags is not None:
|
||||
options.max_candidates_tags = max_candidates_tags
|
||||
if describe_strategy is not None:
|
||||
options.describe_strategy = describe_strategy
|
||||
if pattern:
|
||||
options.pattern = ffi.new('char[]', to_bytes(pattern))
|
||||
if only_follow_first_parent is not None:
|
||||
options.only_follow_first_parent = only_follow_first_parent
|
||||
if show_commit_oid_as_fallback is not None:
|
||||
options.show_commit_oid_as_fallback = show_commit_oid_as_fallback
|
||||
|
||||
result = ffi.new('git_describe_result **')
|
||||
if committish:
|
||||
if is_string(committish):
|
||||
committish = self.revparse_single(committish)
|
||||
|
||||
commit = committish.peel(Commit)
|
||||
|
||||
cptr = ffi.new('git_object **')
|
||||
ffi.buffer(cptr)[:] = commit._pointer[:]
|
||||
|
||||
err = C.git_describe_commit(result, cptr[0], options)
|
||||
else:
|
||||
err = C.git_describe_workdir(result, self._repo, options)
|
||||
check_error(err)
|
||||
|
||||
try:
|
||||
format_options = ffi.new('git_describe_format_options *')
|
||||
C.git_describe_init_format_options(format_options, C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION)
|
||||
|
||||
if abbreviated_size is not None:
|
||||
format_options.abbreviated_size = abbreviated_size
|
||||
if always_use_long_format is not None:
|
||||
format_options.always_use_long_format = always_use_long_format
|
||||
if dirty_suffix:
|
||||
format_options.dirty_suffix = ffi.new('char[]', to_bytes(dirty_suffix))
|
||||
|
||||
buf = ffi.new('git_buf *', (ffi.NULL, 0))
|
||||
|
||||
err = C.git_describe_format(buf, result[0], format_options)
|
||||
check_error(err)
|
||||
|
||||
try:
|
||||
return ffi.string(buf.ptr).decode('utf-8')
|
||||
finally:
|
||||
C.git_buf_free(buf)
|
||||
finally:
|
||||
C.git_describe_result_free(result[0])
|
||||
|
||||
#
|
||||
# Utility for writing a tree into an archive
|
||||
#
|
||||
|
@@ -381,6 +381,11 @@ moduleinit(PyObject* m)
|
||||
ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_FASTFORWARD)
|
||||
ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_UNBORN)
|
||||
|
||||
/* Describe */
|
||||
ADD_CONSTANT_INT(m, GIT_DESCRIBE_DEFAULT);
|
||||
ADD_CONSTANT_INT(m, GIT_DESCRIBE_TAGS);
|
||||
ADD_CONSTANT_INT(m, GIT_DESCRIBE_ALL);
|
||||
|
||||
/* Global initialization of libgit2 */
|
||||
git_libgit2_init();
|
||||
|
||||
|
106
test/test_describe.py
Normal file
106
test/test_describe.py
Normal file
@@ -0,0 +1,106 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
#
|
||||
# Copyright 2010-2015 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 describing commits."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
|
||||
from pygit2 import GIT_DESCRIBE_DEFAULT, GIT_DESCRIBE_TAGS, GIT_DESCRIBE_ALL
|
||||
import pygit2
|
||||
from . import utils
|
||||
|
||||
|
||||
def add_tag(repo, name, target):
|
||||
message = 'Example tag.\n'
|
||||
tagger = pygit2.Signature('John Doe', 'jdoe@example.com', 12347, 0)
|
||||
|
||||
sha = repo.create_tag(name, target, pygit2.GIT_OBJ_COMMIT, tagger, message)
|
||||
return sha
|
||||
|
||||
|
||||
class DescribeTest(utils.RepoTestCase):
|
||||
|
||||
def test_describe(self):
|
||||
add_tag(self.repo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8')
|
||||
self.assertEqual('thetag-2-g2be5719', self.repo.describe())
|
||||
|
||||
def test_describe_without_ref(self):
|
||||
self.assertRaises(pygit2.GitError, self.repo.describe)
|
||||
|
||||
def test_describe_default_oid(self):
|
||||
self.assertEqual('2be5719', self.repo.describe(show_commit_oid_as_fallback=True))
|
||||
|
||||
def test_describe_strategies(self):
|
||||
self.assertEqual('heads/master', self.repo.describe(describe_strategy=GIT_DESCRIBE_ALL))
|
||||
|
||||
self.repo.create_reference('refs/tags/thetag', '4ec4389a8068641da2d6578db0419484972284c8')
|
||||
self.assertRaises(KeyError, self.repo.describe)
|
||||
self.assertEqual('thetag-2-g2be5719', self.repo.describe(describe_strategy=GIT_DESCRIBE_TAGS))
|
||||
|
||||
def test_describe_pattern(self):
|
||||
add_tag(self.repo, 'private/tag1', '5ebeeebb320790caf276b9fc8b24546d63316533')
|
||||
add_tag(self.repo, 'public/tag2', '4ec4389a8068641da2d6578db0419484972284c8')
|
||||
|
||||
self.assertEqual('public/tag2-2-g2be5719', self.repo.describe(pattern='public/*'))
|
||||
|
||||
def test_describe_committish(self):
|
||||
add_tag(self.repo, 'thetag', 'acecd5ea2924a4b900e7e149496e1f4b57976e51')
|
||||
self.assertEqual('thetag-4-g2be5719', self.repo.describe(committish='HEAD'))
|
||||
self.assertEqual('thetag-1-g5ebeeeb', self.repo.describe(committish='HEAD^'))
|
||||
|
||||
self.assertEqual('thetag-4-g2be5719', self.repo.describe(committish=self.repo.head))
|
||||
|
||||
self.assertEqual('thetag-1-g6aaa262', self.repo.describe(committish='6aaa262e655dd54252e5813c8e5acd7780ed097d'))
|
||||
self.assertEqual('thetag-1-g6aaa262', self.repo.describe(committish='6aaa262'))
|
||||
|
||||
def test_describe_follows_first_branch_only(self):
|
||||
add_tag(self.repo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8')
|
||||
self.assertRaises(KeyError, self.repo.describe, only_follow_first_parent=True)
|
||||
|
||||
def test_describe_abbreviated_size(self):
|
||||
add_tag(self.repo, 'thetag', '4ec4389a8068641da2d6578db0419484972284c8')
|
||||
self.assertEqual('thetag-2-g2be5719152d4f82c', self.repo.describe(abbreviated_size=16))
|
||||
self.assertEqual('thetag', self.repo.describe(abbreviated_size=0))
|
||||
|
||||
def test_describe_long_format(self):
|
||||
add_tag(self.repo, 'thetag', '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98')
|
||||
self.assertEqual('thetag-0-g2be5719', self.repo.describe(always_use_long_format=True))
|
||||
|
||||
|
||||
class DescribeDirtyWorkdirTest(utils.DirtyRepoTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(utils.DirtyRepoTestCase, self).setUp()
|
||||
add_tag(self.repo, 'thetag', 'a763aa560953e7cfb87ccbc2f536d665aa4dff22')
|
||||
|
||||
def test_describe(self):
|
||||
self.assertEqual('thetag', self.repo.describe())
|
||||
|
||||
def test_describe_with_dirty_suffix(self):
|
||||
self.assertEqual('thetag-dirty', self.repo.describe(dirty_suffix='-dirty'))
|
Reference in New Issue
Block a user