commit
fba081ddbb
|
@ -0,0 +1,222 @@
|
|||
package git
|
||||
|
||||
/*
|
||||
#include <git2.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// DescribeOptions represents the describe operation configuration.
|
||||
//
|
||||
// You can use DefaultDescribeOptions() to get default options.
|
||||
type DescribeOptions struct {
|
||||
// How many tags as candidates to consider to describe the input commit-ish.
|
||||
// Increasing it above 10 will take slightly longer but may produce a more
|
||||
// accurate result. 0 will cause only exact matches to be output.
|
||||
MaxCandidatesTags uint // default: 10
|
||||
|
||||
// By default describe only shows annotated tags. Change this in order
|
||||
// to show all refs from refs/tags or refs/.
|
||||
Strategy DescribeOptionsStrategy // default: DescribeDefault
|
||||
|
||||
// Only consider tags matching the given glob(7) pattern, excluding
|
||||
// the "refs/tags/" prefix. Can be used to avoid leaking private
|
||||
// tags from the repo.
|
||||
Pattern string
|
||||
|
||||
// When calculating the distance from the matching tag or
|
||||
// reference, only walk down the first-parent ancestry.
|
||||
OnlyFollowFirstParent bool
|
||||
|
||||
// If no matching tag or reference is found, the describe
|
||||
// operation would normally fail. If this option is set, it
|
||||
// will instead fall back to showing the full id of the commit.
|
||||
ShowCommitOidAsFallback bool
|
||||
}
|
||||
|
||||
// DefaultDescribeOptions returns default options for the describe operation.
|
||||
func DefaultDescribeOptions() (DescribeOptions, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
opts := C.git_describe_options{}
|
||||
ecode := C.git_describe_init_options(&opts, C.GIT_DESCRIBE_OPTIONS_VERSION)
|
||||
if ecode < 0 {
|
||||
return DescribeOptions{}, MakeGitError(ecode)
|
||||
}
|
||||
|
||||
return DescribeOptions{
|
||||
MaxCandidatesTags: uint(opts.max_candidates_tags),
|
||||
Strategy: DescribeOptionsStrategy(opts.describe_strategy),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DescribeFormatOptions can be used for formatting the describe string.
|
||||
//
|
||||
// You can use DefaultDescribeFormatOptions() to get default options.
|
||||
type DescribeFormatOptions struct {
|
||||
// Size of the abbreviated commit id to use. This value is the
|
||||
// lower bound for the length of the abbreviated string.
|
||||
AbbreviatedSize uint // default: 7
|
||||
|
||||
// Set to use the long format even when a shorter name could be used.
|
||||
AlwaysUseLongFormat bool
|
||||
|
||||
// If the workdir is dirty and this is set, this string will be
|
||||
// appended to the description string.
|
||||
DirtySuffix string
|
||||
}
|
||||
|
||||
// DefaultDescribeFormatOptions returns default options for formatting
|
||||
// the output.
|
||||
func DefaultDescribeFormatOptions() (DescribeFormatOptions, error) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
opts := C.git_describe_format_options{}
|
||||
ecode := C.git_describe_init_format_options(&opts, C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION)
|
||||
if ecode < 0 {
|
||||
return DescribeFormatOptions{}, MakeGitError(ecode)
|
||||
}
|
||||
|
||||
return DescribeFormatOptions{
|
||||
AbbreviatedSize: uint(opts.abbreviated_size),
|
||||
AlwaysUseLongFormat: opts.always_use_long_format == 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DescribeOptionsStrategy behaves like the --tags and --all options
|
||||
// to git-describe, namely they say to look for any reference in
|
||||
// either refs/tags/ or refs/ respectively.
|
||||
//
|
||||
// By default it only shows annotated tags.
|
||||
type DescribeOptionsStrategy uint
|
||||
|
||||
// Describe strategy options.
|
||||
const (
|
||||
DescribeDefault DescribeOptionsStrategy = C.GIT_DESCRIBE_DEFAULT
|
||||
DescribeTags DescribeOptionsStrategy = C.GIT_DESCRIBE_TAGS
|
||||
DescribeAll DescribeOptionsStrategy = C.GIT_DESCRIBE_ALL
|
||||
)
|
||||
|
||||
// Describe performs the describe operation on the commit.
|
||||
func (c *Commit) Describe(opts *DescribeOptions) (*DescribeResult, error) {
|
||||
var resultPtr *C.git_describe_result
|
||||
|
||||
var cDescribeOpts *C.git_describe_options
|
||||
if opts != nil {
|
||||
var cpattern *C.char
|
||||
if len(opts.Pattern) > 0 {
|
||||
cpattern = C.CString(opts.Pattern)
|
||||
defer C.free(unsafe.Pointer(cpattern))
|
||||
}
|
||||
|
||||
cDescribeOpts = &C.git_describe_options{
|
||||
version: C.GIT_DESCRIBE_OPTIONS_VERSION,
|
||||
max_candidates_tags: C.uint(opts.MaxCandidatesTags),
|
||||
describe_strategy: C.uint(opts.Strategy),
|
||||
pattern: cpattern,
|
||||
only_follow_first_parent: cbool(opts.OnlyFollowFirstParent),
|
||||
show_commit_oid_as_fallback: cbool(opts.ShowCommitOidAsFallback),
|
||||
}
|
||||
}
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
ecode := C.git_describe_commit(&resultPtr, c.gitObject.ptr, cDescribeOpts)
|
||||
if ecode < 0 {
|
||||
return nil, MakeGitError(ecode)
|
||||
}
|
||||
|
||||
return newDescribeResultFromC(resultPtr), nil
|
||||
}
|
||||
|
||||
// DescribeWorkdir describes the working tree. It means describe HEAD
|
||||
// and appends <mark> (-dirty by default) if the working tree is dirty.
|
||||
func (repo *Repository) DescribeWorkdir(opts *DescribeOptions) (*DescribeResult, error) {
|
||||
var resultPtr *C.git_describe_result
|
||||
|
||||
var cDescribeOpts *C.git_describe_options
|
||||
if opts != nil {
|
||||
var cpattern *C.char
|
||||
if len(opts.Pattern) > 0 {
|
||||
cpattern = C.CString(opts.Pattern)
|
||||
defer C.free(unsafe.Pointer(cpattern))
|
||||
}
|
||||
|
||||
cDescribeOpts = &C.git_describe_options{
|
||||
version: C.GIT_DESCRIBE_OPTIONS_VERSION,
|
||||
max_candidates_tags: C.uint(opts.MaxCandidatesTags),
|
||||
describe_strategy: C.uint(opts.Strategy),
|
||||
pattern: cpattern,
|
||||
only_follow_first_parent: cbool(opts.OnlyFollowFirstParent),
|
||||
show_commit_oid_as_fallback: cbool(opts.ShowCommitOidAsFallback),
|
||||
}
|
||||
}
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
ecode := C.git_describe_workdir(&resultPtr, repo.ptr, cDescribeOpts)
|
||||
if ecode < 0 {
|
||||
return nil, MakeGitError(ecode)
|
||||
}
|
||||
|
||||
return newDescribeResultFromC(resultPtr), nil
|
||||
}
|
||||
|
||||
// DescribeResult represents the output from the 'git_describe_commit'
|
||||
// and 'git_describe_workdir' functions in libgit2.
|
||||
//
|
||||
// Use Format() to get a string out of it.
|
||||
type DescribeResult struct {
|
||||
ptr *C.git_describe_result
|
||||
}
|
||||
|
||||
func newDescribeResultFromC(ptr *C.git_describe_result) *DescribeResult {
|
||||
result := &DescribeResult{
|
||||
ptr: ptr,
|
||||
}
|
||||
runtime.SetFinalizer(result, (*DescribeResult).Free)
|
||||
return result
|
||||
}
|
||||
|
||||
// Format prints the DescribeResult as a string.
|
||||
func (result *DescribeResult) Format(opts *DescribeFormatOptions) (string, error) {
|
||||
resultBuf := C.git_buf{}
|
||||
|
||||
var cFormatOpts *C.git_describe_format_options
|
||||
if opts != nil {
|
||||
cDirtySuffix := C.CString(opts.DirtySuffix)
|
||||
defer C.free(unsafe.Pointer(cDirtySuffix))
|
||||
|
||||
cFormatOpts = &C.git_describe_format_options{
|
||||
version: C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION,
|
||||
abbreviated_size: C.uint(opts.AbbreviatedSize),
|
||||
always_use_long_format: cbool(opts.AlwaysUseLongFormat),
|
||||
dirty_suffix: cDirtySuffix,
|
||||
}
|
||||
}
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
ecode := C.git_describe_format(&resultBuf, result.ptr, cFormatOpts)
|
||||
if ecode < 0 {
|
||||
return "", MakeGitError(ecode)
|
||||
}
|
||||
defer C.git_buf_free(&resultBuf)
|
||||
|
||||
return C.GoString(resultBuf.ptr), nil
|
||||
}
|
||||
|
||||
// Free cleans up the C reference.
|
||||
func (result *DescribeResult) Free() {
|
||||
runtime.SetFinalizer(result, nil)
|
||||
C.git_describe_result_free(result.ptr)
|
||||
result.ptr = nil
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDescribeCommit(t *testing.T) {
|
||||
repo := createTestRepo(t)
|
||||
defer cleanupTestRepo(t, repo)
|
||||
|
||||
describeOpts, err := DefaultDescribeOptions()
|
||||
checkFatal(t, err)
|
||||
|
||||
formatOpts, err := DefaultDescribeFormatOptions()
|
||||
checkFatal(t, err)
|
||||
|
||||
commitID, _ := seedTestRepo(t, repo)
|
||||
|
||||
commit, err := repo.LookupCommit(commitID)
|
||||
checkFatal(t, err)
|
||||
|
||||
// No annotated tags can be used to describe master
|
||||
_, err = commit.Describe(&describeOpts)
|
||||
checkDescribeNoRefsFound(t, err)
|
||||
|
||||
// Fallback
|
||||
fallback := describeOpts
|
||||
fallback.ShowCommitOidAsFallback = true
|
||||
result, err := commit.Describe(&fallback)
|
||||
checkFatal(t, err)
|
||||
resultStr, err := result.Format(&formatOpts)
|
||||
checkFatal(t, err)
|
||||
compareStrings(t, "473bf77", resultStr)
|
||||
|
||||
// Abbreviated
|
||||
abbreviated := formatOpts
|
||||
abbreviated.AbbreviatedSize = 2
|
||||
result, err = commit.Describe(&fallback)
|
||||
checkFatal(t, err)
|
||||
resultStr, err = result.Format(&abbreviated)
|
||||
checkFatal(t, err)
|
||||
compareStrings(t, "473b", resultStr)
|
||||
|
||||
createTestTag(t, repo, commit)
|
||||
|
||||
// Exact tag
|
||||
patternOpts := describeOpts
|
||||
patternOpts.Pattern = "v[0-9]*"
|
||||
result, err = commit.Describe(&patternOpts)
|
||||
checkFatal(t, err)
|
||||
resultStr, err = result.Format(&formatOpts)
|
||||
checkFatal(t, err)
|
||||
compareStrings(t, "v0.0.0", resultStr)
|
||||
|
||||
// Pattern no match
|
||||
patternOpts.Pattern = "v[1-9]*"
|
||||
result, err = commit.Describe(&patternOpts)
|
||||
checkDescribeNoRefsFound(t, err)
|
||||
|
||||
commitID, _ = updateReadme(t, repo, "update1")
|
||||
commit, err = repo.LookupCommit(commitID)
|
||||
checkFatal(t, err)
|
||||
|
||||
// Tag-1
|
||||
result, err = commit.Describe(&describeOpts)
|
||||
checkFatal(t, err)
|
||||
resultStr, err = result.Format(&formatOpts)
|
||||
checkFatal(t, err)
|
||||
compareStrings(t, "v0.0.0-1-gd88ef8d", resultStr)
|
||||
|
||||
// Strategy: All
|
||||
describeOpts.Strategy = DescribeAll
|
||||
result, err = commit.Describe(&describeOpts)
|
||||
checkFatal(t, err)
|
||||
resultStr, err = result.Format(&formatOpts)
|
||||
checkFatal(t, err)
|
||||
compareStrings(t, "heads/master", resultStr)
|
||||
|
||||
repo.CreateBranch("hotfix", commit, false)
|
||||
|
||||
// Workdir (branch)
|
||||
result, err = repo.DescribeWorkdir(&describeOpts)
|
||||
checkFatal(t, err)
|
||||
resultStr, err = result.Format(&formatOpts)
|
||||
checkFatal(t, err)
|
||||
compareStrings(t, "heads/hotfix", resultStr)
|
||||
}
|
||||
|
||||
func checkDescribeNoRefsFound(t *testing.T, err error) {
|
||||
// The failure happens at wherever we were called, not here
|
||||
_, file, line, ok := runtime.Caller(1)
|
||||
if !ok {
|
||||
t.Fatalf("Unable to get caller")
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), "No reference found, cannot describe anything") {
|
||||
t.Fatalf(
|
||||
"%s:%v: was expecting error 'No reference found, cannot describe anything', got %v",
|
||||
path.Base(file),
|
||||
line,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue