Add support for Repository.describe(...).

This commit is contained in:
Noah Fontes
2015-12-05 23:22:37 -08:00
parent 7a8474cd44
commit 99dfce9ab8
5 changed files with 247 additions and 0 deletions

View File

@@ -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

View File

@@ -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 ...

View File

@@ -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
#

View File

@@ -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
View 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'))