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