Merge branch 'next'

This commit is contained in:
Carlos Martín Nieto 2015-08-31 13:49:17 +02:00
commit f72db33baf
39 changed files with 1591 additions and 744 deletions

View File

@ -1,13 +1,8 @@
language: go language: go
install: sudo: required
- cd "${HOME}"
- wget -O libgit2-0.22.1.tar.gz https://github.com/libgit2/libgit2/archive/v0.22.1.tar.gz install: ./script/install-libgit2.sh
- tar -xzvf libgit2-0.22.1.tar.gz
- cd libgit2-0.22.1 && mkdir build && cd build
- cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_C_FLAGS=-fPIC -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DCMAKE_INSTALL_PREFIX=/usr/local .. && make && sudo make install
- sudo ldconfig
- cd "${TRAVIS_BUILD_DIR}"
go: go:
- 1.1 - 1.1

View File

@ -90,31 +90,17 @@ func (repo *Repository) NewBranchIterator(flags BranchType) (*BranchIterator, er
return newBranchIteratorFromC(repo, ptr), nil return newBranchIteratorFromC(repo, ptr), nil
} }
func (repo *Repository) CreateBranch(branchName string, target *Commit, force bool, signature *Signature, msg string) (*Branch, error) { func (repo *Repository) CreateBranch(branchName string, target *Commit, force bool) (*Branch, error) {
var ptr *C.git_reference var ptr *C.git_reference
cBranchName := C.CString(branchName) cBranchName := C.CString(branchName)
defer C.free(unsafe.Pointer(cBranchName)) defer C.free(unsafe.Pointer(cBranchName))
cForce := cbool(force) cForce := cbool(force)
cSignature, err := signature.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(cSignature)
var cmsg *C.char
if msg == "" {
cmsg = nil
} else {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_branch_create(&ptr, repo.ptr, cBranchName, target.cast_ptr, cForce, cSignature, cmsg) ret := C.git_branch_create(&ptr, repo.ptr, cBranchName, target.cast_ptr, cForce)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
@ -132,30 +118,16 @@ func (b *Branch) Delete() error {
return nil return nil
} }
func (b *Branch) Move(newBranchName string, force bool, signature *Signature, msg string) (*Branch, error) { func (b *Branch) Move(newBranchName string, force bool) (*Branch, error) {
var ptr *C.git_reference var ptr *C.git_reference
cNewBranchName := C.CString(newBranchName) cNewBranchName := C.CString(newBranchName)
defer C.free(unsafe.Pointer(cNewBranchName)) defer C.free(unsafe.Pointer(cNewBranchName))
cForce := cbool(force) cForce := cbool(force)
cSignature, err := signature.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(cSignature)
var cmsg *C.char
if msg == "" {
cmsg = nil
} else {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_branch_move(&ptr, b.Reference.ptr, cNewBranchName, cForce, cSignature, cmsg) ret := C.git_branch_move(&ptr, b.Reference.ptr, cNewBranchName, cForce)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }

View File

@ -15,18 +15,24 @@ type CheckoutStrategy uint
const ( const (
CheckoutNone CheckoutStrategy = C.GIT_CHECKOUT_NONE // Dry run, no actual updates CheckoutNone CheckoutStrategy = C.GIT_CHECKOUT_NONE // Dry run, no actual updates
CheckoutSafe CheckoutStrategy = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data CheckoutSafe CheckoutStrategy = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data
CheckoutSafeCreate CheckoutStrategy = C.GIT_CHECKOUT_SAFE_CREATE // Allow safe updates plus creation of missing files
CheckoutForce CheckoutStrategy = C.GIT_CHECKOUT_FORCE // Allow all updates to force working directory to look like index CheckoutForce CheckoutStrategy = C.GIT_CHECKOUT_FORCE // Allow all updates to force working directory to look like index
CheckoutRecreateMissing CheckoutStrategy = C.GIT_CHECKOUT_RECREATE_MISSING // Allow checkout to recreate missing files
CheckoutAllowConflicts CheckoutStrategy = C.GIT_CHECKOUT_ALLOW_CONFLICTS // Allow checkout to make safe updates even if conflicts are found CheckoutAllowConflicts CheckoutStrategy = C.GIT_CHECKOUT_ALLOW_CONFLICTS // Allow checkout to make safe updates even if conflicts are found
CheckoutRemoveUntracked CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_UNTRACKED // Remove untracked files not in index (that are not ignored) CheckoutRemoveUntracked CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_UNTRACKED // Remove untracked files not in index (that are not ignored)
CheckoutRemoveIgnored CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_IGNORED // Remove ignored files not in index CheckoutRemoveIgnored CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_IGNORED // Remove ignored files not in index
CheckoutUpdateOnly CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_ONLY // Only update existing files, don't create new ones CheckoutUpdateOnly CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_ONLY // Only update existing files, don't create new ones
CheckoutDontUpdateIndex CheckoutStrategy = C.GIT_CHECKOUT_DONT_UPDATE_INDEX // Normally checkout updates index entries as it goes; this stops that CheckoutDontUpdateIndex CheckoutStrategy = C.GIT_CHECKOUT_DONT_UPDATE_INDEX // Normally checkout updates index entries as it goes; this stops that
CheckoutNoRefresh CheckoutStrategy = C.GIT_CHECKOUT_NO_REFRESH // Don't refresh index/config/etc before doing checkout CheckoutNoRefresh CheckoutStrategy = C.GIT_CHECKOUT_NO_REFRESH // Don't refresh index/config/etc before doing checkout
CheckoutSkipUnmerged CheckoutStrategy = C.GIT_CHECKOUT_SKIP_UNMERGED // Allow checkout to skip unmerged files
CheckoutUserOurs CheckoutStrategy = C.GIT_CHECKOUT_USE_OURS // For unmerged files, checkout stage 2 from index
CheckoutUseTheirs CheckoutStrategy = C.GIT_CHECKOUT_USE_THEIRS // For unmerged files, checkout stage 3 from index
CheckoutDisablePathspecMatch CheckoutStrategy = C.GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH // Treat pathspec as simple list of exact match file paths CheckoutDisablePathspecMatch CheckoutStrategy = C.GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH // Treat pathspec as simple list of exact match file paths
CheckoutSkipUnmerged CheckoutStrategy = C.GIT_CHECKOUT_SKIP_UNMERGED // Allow checkout to skip unmerged files (NOT IMPLEMENTED) CheckoutSkipLockedDirectories CheckoutStrategy = C.GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES // Ignore directories in use, they will be left empty
CheckoutUserOurs CheckoutStrategy = C.GIT_CHECKOUT_USE_OURS // For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) CheckoutDontOverwriteIgnored CheckoutStrategy = C.GIT_CHECKOUT_DONT_OVERWRITE_IGNORED // Don't overwrite ignored files that exist in the checkout target
CheckoutUseTheirs CheckoutStrategy = C.GIT_CHECKOUT_USE_THEIRS // For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) CheckoutConflictStyleMerge CheckoutStrategy = C.GIT_CHECKOUT_CONFLICT_STYLE_MERGE // Write normal merge files for conflicts
CheckoutConflictStyleDiff3 CheckoutStrategy = C.GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 // Include common ancestor data in diff3 format files for conflicts
CheckoutDontRemoveExisting CheckoutStrategy = C.GIT_CHECKOUT_DONT_REMOVE_EXISTING // Don't overwrite existing files or folders
CheckoutDontWriteIndex CheckoutStrategy = C.GIT_CHECKOUT_DONT_WRITE_INDEX // Normally checkout writes the index upon completion; this prevents that
CheckoutUpdateSubmodules CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES // Recursively checkout submodules with same options (NOT IMPLEMENTED) CheckoutUpdateSubmodules CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES // Recursively checkout submodules with same options (NOT IMPLEMENTED)
CheckoutUpdateSubmodulesIfChanged CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED // Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) CheckoutUpdateSubmodulesIfChanged CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED // Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED)
) )

View File

@ -16,7 +16,7 @@ func checkout(t *testing.T, repo *Repository, commit *Commit) {
t.Fatal(err) t.Fatal(err)
} }
err = repo.SetHeadDetached(commit.Id(), commit.Author(), "checkout") err = repo.SetHeadDetached(commit.Id())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -11,19 +11,17 @@ import (
"unsafe" "unsafe"
) )
type RemoteCreateCallback func(repo Repository, name, url string) (*Remote, ErrorCode) type RemoteCreateCallback func(repo *Repository, name, url string) (*Remote, ErrorCode)
type CloneOptions struct { type CloneOptions struct {
*CheckoutOpts *CheckoutOpts
*RemoteCallbacks *FetchOptions
Bare bool Bare bool
CheckoutBranch string CheckoutBranch string
RemoteCreateCallback RemoteCreateCallback RemoteCreateCallback RemoteCreateCallback
} }
func Clone(url string, path string, options *CloneOptions) (*Repository, error) { func Clone(url string, path string, options *CloneOptions) (*Repository, error) {
repo := new(Repository)
curl := C.CString(url) curl := C.CString(url)
defer C.free(unsafe.Pointer(curl)) defer C.free(unsafe.Pointer(curl))
@ -40,21 +38,23 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_clone(&repo.ptr, curl, cpath, copts)
var ptr *C.git_repository
ret := C.git_clone(&ptr, curl, cpath, copts)
freeCheckoutOpts(&copts.checkout_opts)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
runtime.SetFinalizer(repo, (*Repository).Free) return newRepositoryFromC(ptr), nil
return repo, nil
} }
//export remoteCreateCallback //export remoteCreateCallback
func remoteCreateCallback(cremote unsafe.Pointer, crepo unsafe.Pointer, cname, curl *C.char, payload unsafe.Pointer) C.int { func remoteCreateCallback(cremote unsafe.Pointer, crepo unsafe.Pointer, cname, curl *C.char, payload unsafe.Pointer) C.int {
name := C.GoString(cname) name := C.GoString(cname)
url := C.GoString(curl) url := C.GoString(curl)
repo := Repository{(*C.git_repository)(crepo)} repo := newRepositoryFromC((*C.git_repository)(crepo))
if opts, ok := pointerHandles.Get(payload).(CloneOptions); ok { if opts, ok := pointerHandles.Get(payload).(CloneOptions); ok {
remote, err := opts.RemoteCreateCallback(repo, name, url) remote, err := opts.RemoteCreateCallback(repo, name, url)
@ -83,7 +83,7 @@ func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) {
return return
} }
populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts) populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts)
populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks) populateFetchOptions(&ptr.fetch_opts, opts.FetchOptions)
ptr.bare = cbool(opts.Bare) ptr.bare = cbool(opts.Bare)
if opts.RemoteCreateCallback != nil { if opts.RemoteCreateCallback != nil {

View File

@ -18,10 +18,20 @@ func TestClone(t *testing.T) {
path, err := ioutil.TempDir("", "git2go") path, err := ioutil.TempDir("", "git2go")
checkFatal(t, err) checkFatal(t, err)
ref, err := repo.References.Lookup("refs/heads/master")
checkFatal(t, err)
repo2, err := Clone(repo.Path(), path, &CloneOptions{Bare: true}) repo2, err := Clone(repo.Path(), path, &CloneOptions{Bare: true})
defer cleanupTestRepo(t, repo2) defer cleanupTestRepo(t, repo2)
checkFatal(t, err) checkFatal(t, err)
ref2, err := repo2.References.Lookup("refs/heads/master")
checkFatal(t, err)
if ref.Cmp(ref2) != 0 {
t.Fatal("reference in clone does not match original ref")
}
} }
func TestCloneWithCallback(t *testing.T) { func TestCloneWithCallback(t *testing.T) {
@ -37,10 +47,10 @@ func TestCloneWithCallback(t *testing.T) {
opts := CloneOptions{ opts := CloneOptions{
Bare: true, Bare: true,
RemoteCreateCallback: func(r Repository, name, url string) (*Remote, ErrorCode) { RemoteCreateCallback: func(r *Repository, name, url string) (*Remote, ErrorCode) {
testPayload += 1 testPayload += 1
remote, err := r.CreateRemote(REMOTENAME, url) remote, err := r.Remotes.Create(REMOTENAME, url)
if err != nil { if err != nil {
return nil, ErrGeneric return nil, ErrGeneric
} }
@ -58,7 +68,7 @@ func TestCloneWithCallback(t *testing.T) {
t.Fatal("Payload's value has not been changed") t.Fatal("Payload's value has not been changed")
} }
remote, err := repo2.LookupRemote(REMOTENAME) remote, err := repo2.Remotes.Lookup(REMOTENAME)
if err != nil || remote == nil { if err != nil || remote == nil {
t.Fatal("Remote was not created properly") t.Fatal("Remote was not created properly")
} }

222
describe.go Normal file
View File

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

106
describe_test.go Normal file
View File

@ -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,
)
}
}

4
git.go
View File

@ -77,8 +77,8 @@ const (
ErrNonFastForward ErrorCode = C.GIT_ENONFASTFORWARD ErrNonFastForward ErrorCode = C.GIT_ENONFASTFORWARD
// Name/ref spec was not in a valid format // Name/ref spec was not in a valid format
ErrInvalidSpec ErrorCode = C.GIT_EINVALIDSPEC ErrInvalidSpec ErrorCode = C.GIT_EINVALIDSPEC
// Merge conflicts prevented operation // Checkout conflicts prevented operation
ErrMergeConflict ErrorCode = C.GIT_EMERGECONFLICT ErrConflict ErrorCode = C.GIT_ECONFLICT
// Lock file prevented operation // Lock file prevented operation
ErrLocked ErrorCode = C.GIT_ELOCKED ErrLocked ErrorCode = C.GIT_ELOCKED
// Reference value does not match expected // Reference value does not match expected

View File

@ -12,7 +12,6 @@ import "C"
import ( import (
"fmt" "fmt"
"runtime" "runtime"
"time"
"unsafe" "unsafe"
) )
@ -31,13 +30,18 @@ type Index struct {
ptr *C.git_index ptr *C.git_index
} }
type IndexTime struct {
seconds int32
nanoseconds uint32
}
type IndexEntry struct { type IndexEntry struct {
Ctime time.Time Ctime IndexTime
Mtime time.Time Mtime IndexTime
Mode Filemode Mode Filemode
Uid uint Uid uint32
Gid uint Gid uint32
Size uint Size uint32
Id *Oid Id *Oid
Path string Path string
} }
@ -47,26 +51,26 @@ func newIndexEntryFromC(entry *C.git_index_entry) *IndexEntry {
return nil return nil
} }
return &IndexEntry{ return &IndexEntry{
time.Unix(int64(entry.ctime.seconds), int64(entry.ctime.nanoseconds)), IndexTime { int32(entry.ctime.seconds), uint32(entry.ctime.nanoseconds) },
time.Unix(int64(entry.mtime.seconds), int64(entry.mtime.nanoseconds)), IndexTime { int32(entry.mtime.seconds), uint32(entry.mtime.nanoseconds) },
Filemode(entry.mode), Filemode(entry.mode),
uint(entry.uid), uint32(entry.uid),
uint(entry.gid), uint32(entry.gid),
uint(entry.file_size), uint32(entry.file_size),
newOidFromC(&entry.id), newOidFromC(&entry.id),
C.GoString(entry.path), C.GoString(entry.path),
} }
} }
func populateCIndexEntry(source *IndexEntry, dest *C.git_index_entry) { func populateCIndexEntry(source *IndexEntry, dest *C.git_index_entry) {
dest.ctime.seconds = C.git_time_t(source.Ctime.Unix()) dest.ctime.seconds = C.int32_t(source.Ctime.seconds)
dest.ctime.nanoseconds = C.uint(source.Ctime.UnixNano()) dest.ctime.nanoseconds = C.uint32_t(source.Ctime.nanoseconds)
dest.mtime.seconds = C.git_time_t(source.Mtime.Unix()) dest.mtime.seconds = C.int32_t(source.Mtime.seconds)
dest.mtime.nanoseconds = C.uint(source.Mtime.UnixNano()) dest.mtime.nanoseconds = C.uint32_t(source.Mtime.nanoseconds)
dest.mode = C.uint(source.Mode) dest.mode = C.uint32_t(source.Mode)
dest.uid = C.uint(source.Uid) dest.uid = C.uint32_t(source.Uid)
dest.gid = C.uint(source.Gid) dest.gid = C.uint32_t(source.Gid)
dest.file_size = C.git_off_t(source.Size) dest.file_size = C.uint32_t(source.Size)
dest.id = *source.Id.toC() dest.id = *source.Id.toC()
dest.path = C.CString(source.Path) dest.path = C.CString(source.Path)
} }

View File

@ -85,8 +85,8 @@ const (
) )
type MergeOptions struct { type MergeOptions struct {
Version uint Version uint
Flags MergeTreeFlag TreeFlags MergeTreeFlag
RenameThreshold uint RenameThreshold uint
TargetLimit uint TargetLimit uint
@ -98,7 +98,7 @@ type MergeOptions struct {
func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions { func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions {
return MergeOptions{ return MergeOptions{
Version: uint(opts.version), Version: uint(opts.version),
Flags: MergeTreeFlag(opts.flags), TreeFlags: MergeTreeFlag(opts.tree_flags),
RenameThreshold: uint(opts.rename_threshold), RenameThreshold: uint(opts.rename_threshold),
TargetLimit: uint(opts.target_limit), TargetLimit: uint(opts.target_limit),
FileFavor: MergeFileFavor(opts.file_favor), FileFavor: MergeFileFavor(opts.file_favor),
@ -124,7 +124,7 @@ func (mo *MergeOptions) toC() *C.git_merge_options {
} }
return &C.git_merge_options{ return &C.git_merge_options{
version: C.uint(mo.Version), version: C.uint(mo.Version),
flags: C.git_merge_tree_flag_t(mo.Flags), tree_flags: C.git_merge_tree_flag_t(mo.TreeFlags),
rename_threshold: C.uint(mo.RenameThreshold), rename_threshold: C.uint(mo.RenameThreshold),
target_limit: C.uint(mo.TargetLimit), target_limit: C.uint(mo.TargetLimit),
file_favor: C.git_merge_file_favor_t(mo.FileFavor), file_favor: C.git_merge_file_favor_t(mo.FileFavor),
@ -364,7 +364,7 @@ func populateCMergeFileOptions(c *C.git_merge_file_options, options MergeFileOpt
c.our_label = C.CString(options.OurLabel) c.our_label = C.CString(options.OurLabel)
c.their_label = C.CString(options.TheirLabel) c.their_label = C.CString(options.TheirLabel)
c.favor = C.git_merge_file_favor_t(options.Favor) c.favor = C.git_merge_file_favor_t(options.Favor)
c.flags = C.git_merge_file_flags_t(options.Flags) c.flags = C.uint(options.Flags)
} }
func freeCMergeFileOptions(c *C.git_merge_file_options) { func freeCMergeFileOptions(c *C.git_merge_file_options) {

View File

@ -11,7 +11,7 @@ func TestMergeWithSelf(t *testing.T) {
seedTestRepo(t, repo) seedTestRepo(t, repo)
master, err := repo.LookupReference("refs/heads/master") master, err := repo.References.Lookup("refs/heads/master")
checkFatal(t, err) checkFatal(t, err)
mergeHead, err := repo.AnnotatedCommitFromRef(master) mergeHead, err := repo.AnnotatedCommitFromRef(master)
@ -29,7 +29,7 @@ func TestMergeAnalysisWithSelf(t *testing.T) {
seedTestRepo(t, repo) seedTestRepo(t, repo)
master, err := repo.LookupReference("refs/heads/master") master, err := repo.References.Lookup("refs/heads/master")
checkFatal(t, err) checkFatal(t, err)
mergeHead, err := repo.AnnotatedCommitFromRef(master) mergeHead, err := repo.AnnotatedCommitFromRef(master)
@ -109,7 +109,7 @@ func appendCommit(t *testing.T, repo *Repository) (*Oid, *Oid) {
tree, err := repo.LookupTree(treeId) tree, err := repo.LookupTree(treeId)
checkFatal(t, err) checkFatal(t, err)
ref, err := repo.LookupReference("HEAD") ref, err := repo.References.Lookup("HEAD")
checkFatal(t, err) checkFatal(t, err)
parent, err := ref.Peel(ObjectCommit) parent, err := ref.Peel(ObjectCommit)

121
note.go
View File

@ -10,6 +10,127 @@ import (
"unsafe" "unsafe"
) )
// This object represents the possible operations which can be
// performed on the collection of notes for a repository.
type NoteCollection struct {
repo *Repository
}
// Create adds a note for an object
func (c *NoteCollection) Create(
ref string, author, committer *Signature, id *Oid,
note string, force bool) (*Oid, error) {
oid := new(Oid)
var cref *C.char
if ref == "" {
cref = nil
} else {
cref = C.CString(ref)
defer C.free(unsafe.Pointer(cref))
}
authorSig, err := author.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(authorSig)
committerSig, err := committer.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(committerSig)
cnote := C.CString(note)
defer C.free(unsafe.Pointer(cnote))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_note_create(
oid.toC(), c.repo.ptr, cref, authorSig,
committerSig, id.toC(), cnote, cbool(force))
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
// Read reads the note for an object
func (c *NoteCollection) Read(ref string, id *Oid) (*Note, error) {
var cref *C.char
if ref == "" {
cref = nil
} else {
cref = C.CString(ref)
defer C.free(unsafe.Pointer(cref))
}
note := new(Note)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if ret := C.git_note_read(&note.ptr, c.repo.ptr, cref, id.toC()); ret < 0 {
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(note, (*Note).Free)
return note, nil
}
// Remove removes the note for an object
func (c *NoteCollection) Remove(ref string, author, committer *Signature, id *Oid) error {
var cref *C.char
if ref == "" {
cref = nil
} else {
cref = C.CString(ref)
defer C.free(unsafe.Pointer(cref))
}
authorSig, err := author.toC()
if err != nil {
return err
}
defer C.git_signature_free(authorSig)
committerSig, err := committer.toC()
if err != nil {
return err
}
defer C.git_signature_free(committerSig)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_note_remove(c.repo.ptr, cref, authorSig, committerSig, id.toC())
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// DefaultRef returns the default notes reference for a repository
func (c *NoteCollection) DefaultRef() (string, error) {
buf := C.git_buf{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if ret := C.git_note_default_ref(&buf, c.repo.ptr); ret < 0 {
return "", MakeGitError(ret)
}
ret := C.GoString(buf.ptr)
C.git_buf_free(&buf)
return ret, nil
}
// Note // Note
type Note struct { type Note struct {
ptr *C.git_note ptr *C.git_note

View File

@ -53,7 +53,7 @@ func TestNoteIterator(t *testing.T) {
break break
} }
note, err := repo.ReadNote("", commitId) note, err := repo.Notes.Read("", commitId)
checkFatal(t, err) checkFatal(t, err)
if !reflect.DeepEqual(note.Id(), noteId) { if !reflect.DeepEqual(note.Id(), noteId) {
@ -73,13 +73,13 @@ func TestRemoveNote(t *testing.T) {
note, _ := createTestNote(t, repo, commit) note, _ := createTestNote(t, repo, commit)
_, err = repo.ReadNote("", commit.Id()) _, err = repo.Notes.Read("", commit.Id())
checkFatal(t, err) checkFatal(t, err)
err = repo.RemoveNote("", note.Author(), note.Committer(), commitId) err = repo.Notes.Remove("", note.Author(), note.Committer(), commitId)
checkFatal(t, err) checkFatal(t, err)
_, err = repo.ReadNote("", commit.Id()) _, err = repo.Notes.Read("", commit.Id())
if err == nil { if err == nil {
t.Fatal("note remove failed") t.Fatal("note remove failed")
} }
@ -89,7 +89,7 @@ func TestDefaultNoteRef(t *testing.T) {
repo := createTestRepo(t) repo := createTestRepo(t)
defer cleanupTestRepo(t, repo) defer cleanupTestRepo(t, repo)
ref, err := repo.DefaultNoteRef() ref, err := repo.Notes.DefaultRef()
checkFatal(t, err) checkFatal(t, err)
compareStrings(t, "refs/notes/commits", ref) compareStrings(t, "refs/notes/commits", ref)
@ -103,10 +103,10 @@ func createTestNote(t *testing.T, repo *Repository, commit *Commit) (*Note, *Oid
When: time.Date(2015, 01, 05, 13, 0, 0, 0, loc), When: time.Date(2015, 01, 05, 13, 0, 0, 0, loc),
} }
noteId, err := repo.CreateNote("", sig, sig, commit.Id(), "I am a note\n", false) noteId, err := repo.Notes.Create("", sig, sig, commit.Id(), "I am a note\n", false)
checkFatal(t, err) checkFatal(t, err)
note, err := repo.ReadNote("", commit.Id()) note, err := repo.Notes.Read("", commit.Id())
checkFatal(t, err) checkFatal(t, err)
return note, noteId return note, noteId

View File

@ -22,6 +22,7 @@ type Object interface {
Id() *Oid Id() *Oid
Type() ObjectType Type() ObjectType
Owner() *Repository Owner() *Repository
Peel(t ObjectType) (Object, error)
} }
type gitObject struct { type gitObject struct {
@ -69,6 +70,31 @@ func (o *gitObject) Free() {
C.git_object_free(o.ptr) C.git_object_free(o.ptr)
} }
// Peel recursively peels an object until an object of the specified type is met.
//
// If the query cannot be satisfied due to the object model, ErrInvalidSpec
// will be returned (e.g. trying to peel a blob to a tree).
//
// If you pass ObjectAny as the target type, then the object will be peeled
// until the type changes. A tag will be peeled until the referenced object
// is no longer a tag, and a commit will be peeled to a tree. Any other object
// type will return ErrInvalidSpec.
//
// If peeling a tag we discover an object which cannot be peeled to the target
// type due to the object model, an error will be returned.
func (o *gitObject) Peel(t ObjectType) (Object, error) {
var cobj *C.git_object
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := C.git_object_peel(&cobj, o.ptr, C.git_otype(t)); err < 0 {
return nil, MakeGitError(err)
}
return allocObject(cobj, o.repo), nil
}
func allocObject(cobj *C.git_object, repo *Repository) Object { func allocObject(cobj *C.git_object, repo *Repository) Object {
obj := gitObject{ obj := gitObject{
ptr: cobj, ptr: cobj,

View File

@ -102,3 +102,63 @@ func TestObjectOwner(t *testing.T) {
checkOwner(t, repo, commit) checkOwner(t, repo, commit)
checkOwner(t, repo, tree) checkOwner(t, repo, tree)
} }
func TestObjectPeel(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitID, treeID := seedTestRepo(t, repo)
var obj Object
commit, err := repo.LookupCommit(commitID)
checkFatal(t, err)
obj, err = commit.Peel(ObjectAny)
checkFatal(t, err)
if obj.Type() != ObjectTree {
t.Fatalf("Wrong object type when peeling a commit, expected tree, have %v", obj.Type())
}
obj, err = commit.Peel(ObjectTag)
if !IsErrorCode(err, ErrInvalidSpec) {
t.Fatalf("Wrong error when peeling a commit to a tag, expected ErrInvalidSpec, have %v", err)
}
tree, err := repo.LookupTree(treeID)
checkFatal(t, err)
obj, err = tree.Peel(ObjectAny)
if !IsErrorCode(err, ErrInvalidSpec) {
t.Fatalf("Wrong error when peeling a tree, expected ErrInvalidSpec, have %v", err)
}
entry := tree.EntryByName("README")
blob, err := repo.LookupBlob(entry.Id)
checkFatal(t, err)
obj, err = blob.Peel(ObjectAny)
if !IsErrorCode(err, ErrInvalidSpec) {
t.Fatalf("Wrong error when peeling a blob, expected ErrInvalidSpec, have %v", err)
}
tagID := createTestTag(t, repo, commit)
tag, err := repo.LookupTag(tagID)
checkFatal(t, err)
obj, err = tag.Peel(ObjectAny)
checkFatal(t, err)
if obj.Type() != ObjectCommit {
t.Fatalf("Wrong object type when peeling a tag, expected commit, have %v", obj.Type())
}
// TODO: Should test a tag that annotates a different object than a commit
// but it's impossible at the moment to tag such an object.
}

8
odb.go
View File

@ -11,6 +11,7 @@ import (
"reflect" "reflect"
"runtime" "runtime"
"unsafe" "unsafe"
"fmt"
) )
type Odb struct { type Odb struct {
@ -106,7 +107,9 @@ func odbForEachCb(id *C.git_oid, handle unsafe.Pointer) int {
} }
err := data.callback(newOidFromC(id)) err := data.callback(newOidFromC(id))
fmt.Println("err %v", err)
if err != nil { if err != nil {
fmt.Println("returning EUSER")
data.err = err data.err = err
return C.GIT_EUSER return C.GIT_EUSER
} }
@ -127,6 +130,7 @@ func (v *Odb) ForEach(callback OdbForEachCallback) error {
defer pointerHandles.Untrack(handle) defer pointerHandles.Untrack(handle)
ret := C._go_git_odb_foreach(v.ptr, handle) ret := C._go_git_odb_foreach(v.ptr, handle)
fmt.Println("ret %v", ret);
if ret == C.GIT_EUSER { if ret == C.GIT_EUSER {
return data.err return data.err
} else if ret < 0 { } else if ret < 0 {
@ -172,13 +176,13 @@ func (v *Odb) NewReadStream(id *Oid) (*OdbReadStream, error) {
// NewWriteStream opens a write stream to the ODB, which allows you to // NewWriteStream opens a write stream to the ODB, which allows you to
// create a new object in the database. The size and type must be // create a new object in the database. The size and type must be
// known in advance // known in advance
func (v *Odb) NewWriteStream(size int, otype ObjectType) (*OdbWriteStream, error) { func (v *Odb) NewWriteStream(size int64, otype ObjectType) (*OdbWriteStream, error) {
stream := new(OdbWriteStream) stream := new(OdbWriteStream)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_odb_open_wstream(&stream.ptr, v.ptr, C.size_t(size), C.git_otype(otype)) ret := C.git_odb_open_wstream(&stream.ptr, v.ptr, C.git_off_t(size), C.git_otype(otype))
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }

View File

@ -17,7 +17,7 @@ func TestOdbStream(t *testing.T) {
str := "hello, world!" str := "hello, world!"
stream, error := odb.NewWriteStream(len(str), ObjectBlob) stream, error := odb.NewWriteStream(int64(len(str)), ObjectBlob)
checkFatal(t, error) checkFatal(t, error)
n, error := io.WriteString(stream, str) n, error := io.WriteString(stream, str)
checkFatal(t, error) checkFatal(t, error)

View File

@ -11,17 +11,17 @@ func TestRemotePush(t *testing.T) {
localRepo := createTestRepo(t) localRepo := createTestRepo(t)
defer cleanupTestRepo(t, localRepo) defer cleanupTestRepo(t, localRepo)
remote, err := localRepo.CreateRemote("test_push", repo.Path()) remote, err := localRepo.Remotes.Create("test_push", repo.Path())
checkFatal(t, err) checkFatal(t, err)
seedTestRepo(t, localRepo) seedTestRepo(t, localRepo)
err = remote.Push([]string{"refs/heads/master"}, nil, nil, "") err = remote.Push([]string{"refs/heads/master"}, nil)
checkFatal(t, err) checkFatal(t, err)
_, err = localRepo.LookupReference("refs/remotes/test_push/master") _, err = localRepo.References.Lookup("refs/remotes/test_push/master")
checkFatal(t, err) checkFatal(t, err)
_, err = repo.LookupReference("refs/heads/master") _, err = repo.References.Lookup("refs/heads/master")
checkFatal(t, err) checkFatal(t, err)
} }

View File

@ -21,13 +21,137 @@ type Reference struct {
repo *Repository repo *Repository
} }
type ReferenceCollection struct {
repo *Repository
}
func (c *ReferenceCollection) Lookup(name string) (*Reference, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
var ptr *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_reference_lookup(&ptr, c.repo.ptr, cname)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newReferenceFromC(ptr, c.repo), nil
}
func (c *ReferenceCollection) Create(name string, id *Oid, force bool, msg string) (*Reference, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
var cmsg *C.char
if msg == "" {
cmsg = nil
} else {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
var ptr *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_reference_create(&ptr, c.repo.ptr, cname, id.toC(), cbool(force), cmsg)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newReferenceFromC(ptr, c.repo), nil
}
func (c *ReferenceCollection) CreateSymbolic(name, target string, force bool, msg string) (*Reference, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
ctarget := C.CString(target)
defer C.free(unsafe.Pointer(ctarget))
var cmsg *C.char
if msg == "" {
cmsg = nil
} else {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
var ptr *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_reference_symbolic_create(&ptr, c.repo.ptr, cname, ctarget, cbool(force), cmsg)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newReferenceFromC(ptr, c.repo), nil
}
// EnsureLog ensures that there is a reflog for the given reference
// name and creates an empty one if necessary.
func (c *ReferenceCollection) EnsureLog(name string) error {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_ensure_log(c.repo.ptr, cname)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// HasLog returns whether there is a reflog for the given reference
// name
func (c *ReferenceCollection) HasLog(name string) (bool, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_has_log(c.repo.ptr, cname)
if ret < 0 {
return false, MakeGitError(ret)
}
return ret == 1, nil
}
// Dwim looks up a reference by DWIMing its short name
func (c *ReferenceCollection) Dwim(name string) (*Reference, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_reference
ret := C.git_reference_dwim(&ptr, c.repo.ptr, cname)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newReferenceFromC(ptr, c.repo), nil
}
func newReferenceFromC(ptr *C.git_reference, repo *Repository) *Reference { func newReferenceFromC(ptr *C.git_reference, repo *Repository) *Reference {
ref := &Reference{ptr: ptr, repo: repo} ref := &Reference{ptr: ptr, repo: repo}
runtime.SetFinalizer(ref, (*Reference).Free) runtime.SetFinalizer(ref, (*Reference).Free)
return ref return ref
} }
func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string) (*Reference, error) { func (v *Reference) SetSymbolicTarget(target string, msg string) (*Reference, error) {
var ptr *C.git_reference var ptr *C.git_reference
ctarget := C.CString(target) ctarget := C.CString(target)
@ -36,12 +160,6 @@ func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(csig)
var cmsg *C.char var cmsg *C.char
if msg == "" { if msg == "" {
cmsg = nil cmsg = nil
@ -50,7 +168,7 @@ func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string)
defer C.free(unsafe.Pointer(cmsg)) defer C.free(unsafe.Pointer(cmsg))
} }
ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget, csig, cmsg) ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget, cmsg)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
@ -58,18 +176,12 @@ func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string)
return newReferenceFromC(ptr, v.repo), nil return newReferenceFromC(ptr, v.repo), nil
} }
func (v *Reference) SetTarget(target *Oid, sig *Signature, msg string) (*Reference, error) { func (v *Reference) SetTarget(target *Oid, msg string) (*Reference, error) {
var ptr *C.git_reference var ptr *C.git_reference
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(csig)
var cmsg *C.char var cmsg *C.char
if msg == "" { if msg == "" {
cmsg = nil cmsg = nil
@ -78,7 +190,7 @@ func (v *Reference) SetTarget(target *Oid, sig *Signature, msg string) (*Referen
defer C.free(unsafe.Pointer(cmsg)) defer C.free(unsafe.Pointer(cmsg))
} }
ret := C.git_reference_set_target(&ptr, v.ptr, target.toC(), csig, cmsg) ret := C.git_reference_set_target(&ptr, v.ptr, target.toC(), cmsg)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
@ -100,17 +212,11 @@ func (v *Reference) Resolve() (*Reference, error) {
return newReferenceFromC(ptr, v.repo), nil return newReferenceFromC(ptr, v.repo), nil
} }
func (v *Reference) Rename(name string, force bool, sig *Signature, msg string) (*Reference, error) { func (v *Reference) Rename(name string, force bool, msg string) (*Reference, error) {
var ptr *C.git_reference var ptr *C.git_reference
cname := C.CString(name) cname := C.CString(name)
defer C.free(unsafe.Pointer(cname)) defer C.free(unsafe.Pointer(cname))
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(csig)
var cmsg *C.char var cmsg *C.char
if msg == "" { if msg == "" {
cmsg = nil cmsg = nil
@ -122,7 +228,7 @@ func (v *Reference) Rename(name string, force bool, sig *Signature, msg string)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), csig, cmsg) ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), cmsg)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
@ -209,6 +315,11 @@ func (v *Reference) IsTag() bool {
return C.git_reference_is_tag(v.ptr) == 1 return C.git_reference_is_tag(v.ptr) == 1
} }
// IsNote checks if the reference is a note.
func (v *Reference) IsNote() bool {
return C.git_reference_is_note(v.ptr) == 1
}
func (v *Reference) Free() { func (v *Reference) Free() {
runtime.SetFinalizer(v, nil) runtime.SetFinalizer(v, nil)
C.git_reference_free(v.ptr) C.git_reference_free(v.ptr)
@ -319,3 +430,22 @@ func (v *ReferenceIterator) Free() {
runtime.SetFinalizer(v, nil) runtime.SetFinalizer(v, nil)
C.git_reference_iterator_free(v.ptr) C.git_reference_iterator_free(v.ptr)
} }
// ReferenceIsValidName ensures the reference name is well-formed.
//
// Valid reference names must follow one of two patterns:
//
// 1. Top-level names must contain only capital letters and underscores,
// and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
//
// 2. Names prefixed with "refs/" can be almost anything. You must avoid
// the characters '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences
// ".." and " @ {" which have special meaning to revparse.
func ReferenceIsValidName(name string) bool {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
if C.git_reference_is_valid_name(cname) == 1 {
return true
}
return false
}

View File

@ -13,21 +13,14 @@ func TestRefModification(t *testing.T) {
commitId, treeId := seedTestRepo(t, repo) commitId, treeId := seedTestRepo(t, repo)
loc, err := time.LoadLocation("Europe/Berlin") _, err := repo.References.Create("refs/tags/tree", treeId, true, "testTreeTag")
checkFatal(t, err)
sig := &Signature{
Name: "Rand Om Hacker",
Email: "random@hacker.com",
When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
}
_, err = repo.CreateReference("refs/tags/tree", treeId, true, sig, "testTreeTag")
checkFatal(t, err) checkFatal(t, err)
tag, err := repo.LookupReference("refs/tags/tree") tag, err := repo.References.Lookup("refs/tags/tree")
checkFatal(t, err) checkFatal(t, err)
checkRefType(t, tag, ReferenceOid) checkRefType(t, tag, ReferenceOid)
ref, err := repo.LookupReference("HEAD") ref, err := repo.References.Lookup("HEAD")
checkFatal(t, err) checkFatal(t, err)
checkRefType(t, ref, ReferenceSymbolic) checkRefType(t, ref, ReferenceSymbolic)
@ -51,9 +44,9 @@ func TestRefModification(t *testing.T) {
t.Fatalf("Wrong ref target") t.Fatalf("Wrong ref target")
} }
_, err = tag.Rename("refs/tags/renamed", false, nil, "") _, err = tag.Rename("refs/tags/renamed", false, "")
checkFatal(t, err) checkFatal(t, err)
tag, err = repo.LookupReference("refs/tags/renamed") tag, err = repo.References.Lookup("refs/tags/renamed")
checkFatal(t, err) checkFatal(t, err)
checkRefType(t, ref, ReferenceOid) checkRefType(t, ref, ReferenceOid)
@ -84,13 +77,13 @@ func TestReferenceIterator(t *testing.T) {
commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
checkFatal(t, err) checkFatal(t, err)
_, err = repo.CreateReference("refs/heads/one", commitId, true, sig, "headOne") _, err = repo.References.Create("refs/heads/one", commitId, true, "headOne")
checkFatal(t, err) checkFatal(t, err)
_, err = repo.CreateReference("refs/heads/two", commitId, true, sig, "headTwo") _, err = repo.References.Create("refs/heads/two", commitId, true, "headTwo")
checkFatal(t, err) checkFatal(t, err)
_, err = repo.CreateReference("refs/heads/three", commitId, true, sig, "headThree") _, err = repo.References.Create("refs/heads/three", commitId, true, "headThree")
checkFatal(t, err) checkFatal(t, err)
iter, err := repo.NewReferenceIterator() iter, err := repo.NewReferenceIterator()
@ -143,7 +136,7 @@ func TestReferenceOwner(t *testing.T) {
commitId, _ := seedTestRepo(t, repo) commitId, _ := seedTestRepo(t, repo)
ref, err := repo.CreateReference("refs/heads/foo", commitId, true, nil, "") ref, err := repo.References.Create("refs/heads/foo", commitId, true, "")
checkFatal(t, err) checkFatal(t, err)
owner := ref.Owner() owner := ref.Owner()
@ -162,10 +155,10 @@ func TestUtil(t *testing.T) {
commitId, _ := seedTestRepo(t, repo) commitId, _ := seedTestRepo(t, repo)
ref, err := repo.CreateReference("refs/heads/foo", commitId, true, nil, "") ref, err := repo.References.Create("refs/heads/foo", commitId, true, "")
checkFatal(t, err) checkFatal(t, err)
ref2, err := repo.DwimReference("foo") ref2, err := repo.References.Dwim("foo")
checkFatal(t, err) checkFatal(t, err)
if ref.Cmp(ref2) != 0 { if ref.Cmp(ref2) != 0 {
@ -176,13 +169,55 @@ func TestUtil(t *testing.T) {
t.Fatalf("refs/heads/foo has no foo shorthand") t.Fatalf("refs/heads/foo has no foo shorthand")
} }
hasLog, err := repo.HasLog("refs/heads/foo") hasLog, err := repo.References.HasLog("refs/heads/foo")
checkFatal(t, err) checkFatal(t, err)
if !hasLog { if !hasLog {
t.Fatalf("branches have logs by default") t.Fatalf("branches have logs by default")
} }
} }
func TestIsNote(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitID, _ := seedTestRepo(t, repo)
sig := &Signature{
Name: "Rand Om Hacker",
Email: "random@hacker.com",
When: time.Now(),
}
refname, err := repo.Notes.DefaultRef()
checkFatal(t, err)
_, err = repo.Notes.Create(refname, sig, sig, commitID, "This is a note", false)
checkFatal(t, err)
ref, err := repo.References.Lookup(refname)
checkFatal(t, err)
if !ref.IsNote() {
t.Fatalf("%s should be a note", ref.Name())
}
ref, err = repo.References.Create("refs/heads/foo", commitID, true, "")
checkFatal(t, err)
if ref.IsNote() {
t.Fatalf("%s should not be a note", ref.Name())
}
}
func TestReferenceIsValidName(t *testing.T) {
if !ReferenceIsValidName("HEAD") {
t.Errorf("HEAD should be a valid reference name")
}
if ReferenceIsValidName("HEAD1") {
t.Errorf("HEAD1 should not be a valid reference name")
}
}
func compareStringList(t *testing.T, expected, actual []string) { func compareStringList(t *testing.T, expected, actual []string) {
for i, v := range expected { for i, v := range expected {
if actual[i] != v { if actual[i] != v {

284
remote.go
View File

@ -69,6 +69,51 @@ type RemoteCallbacks struct {
PushUpdateReferenceCallback PushUpdateReferenceCallback
} }
type FetchPrune uint
const (
// Use the setting from the configuration
FetchPruneUnspecified FetchPrune = C.GIT_FETCH_PRUNE_UNSPECIFIED
// Force pruning on
FetchPruneOn FetchPrune = C.GIT_FETCH_PRUNE
// Force pruning off
FetchNoPrune FetchPrune = C.GIT_FETCH_NO_PRUNE
)
type DownloadTags uint
const (
// Use the setting from the configuration.
DownloadTagsUnspecified DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED
// Ask the server for tags pointing to objects we're already
// downloading.
DownloadTagsAuto DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_AUTO
// Don't ask for any tags beyond the refspecs.
DownloadTagsNone DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_NONE
// Ask for the all the tags.
DownloadTagsAll DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_ALL
)
type FetchOptions struct {
// Callbacks to use for this fetch operation
RemoteCallbacks RemoteCallbacks
// Whether to perform a prune after the fetch
Prune FetchPrune
// Whether to write the results to FETCH_HEAD. Defaults to
// on. Leave this default in order to behave like git.
UpdateFetchhead bool
// Determines how to behave regarding tags on the remote, such
// as auto-downloading tags for objects we're downloading or
// downloading all of them.
//
// The default is to auto-follow tags.
DownloadTags DownloadTags
}
type Remote struct { type Remote struct {
ptr *C.git_remote ptr *C.git_remote
callbacks RemoteCallbacks callbacks RemoteCallbacks
@ -108,6 +153,9 @@ type HostkeyCertificate struct {
} }
type PushOptions struct { type PushOptions struct {
// Callbacks to use for this push operation
RemoteCallbacks RemoteCallbacks
PbParallelism uint PbParallelism uint
} }
@ -123,6 +171,12 @@ func newRemoteHeadFromC(ptr *C.git_remote_head) RemoteHead {
} }
} }
func untrackCalbacksPayload(callbacks *C.git_remote_callbacks) {
if callbacks != nil && callbacks.payload != nil {
pointerHandles.Untrack(callbacks.payload)
}
}
func populateRemoteCallbacks(ptr *C.git_remote_callbacks, callbacks *RemoteCallbacks) { func populateRemoteCallbacks(ptr *C.git_remote_callbacks, callbacks *RemoteCallbacks) {
C.git_remote_init_callbacks(ptr, C.GIT_REMOTE_CALLBACKS_VERSION) C.git_remote_init_callbacks(ptr, C.GIT_REMOTE_CALLBACKS_VERSION)
if callbacks == nil { if callbacks == nil {
@ -160,7 +214,9 @@ func credentialsCallback(_cred **C.git_cred, _url *C.char, _username_from_url *C
url := C.GoString(_url) url := C.GoString(_url)
username_from_url := C.GoString(_username_from_url) username_from_url := C.GoString(_username_from_url)
ret, cred := callbacks.CredentialsCallback(url, username_from_url, (CredType)(allowed_types)) ret, cred := callbacks.CredentialsCallback(url, username_from_url, (CredType)(allowed_types))
*_cred = cred.ptr if cred != nil {
*_cred = cred.ptr
}
return int(ret) return int(ret)
} }
@ -267,41 +323,22 @@ func RemoteIsValidName(name string) bool {
return false return false
} }
func (r *Remote) SetCallbacks(callbacks *RemoteCallbacks) error {
r.callbacks = *callbacks
var ccallbacks C.git_remote_callbacks
populateRemoteCallbacks(&ccallbacks, &r.callbacks)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_remote_set_callbacks(r.ptr, &ccallbacks)
if ecode < 0 {
return MakeGitError(ecode)
}
return nil
}
func (r *Remote) Free() { func (r *Remote) Free() {
runtime.SetFinalizer(r, nil) runtime.SetFinalizer(r, nil)
callbacks := C.git_remote_get_callbacks(r.ptr)
if callbacks != nil && callbacks.payload != nil {
pointerHandles.Untrack(callbacks.payload)
}
C.git_remote_free(r.ptr) C.git_remote_free(r.ptr)
} }
func (repo *Repository) ListRemotes() ([]string, error) { type RemoteCollection struct {
repo *Repository
}
func (c *RemoteCollection) List() ([]string, error) {
var r C.git_strarray var r C.git_strarray
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ecode := C.git_remote_list(&r, repo.ptr) ecode := C.git_remote_list(&r, c.repo.ptr)
if ecode < 0 { if ecode < 0 {
return nil, MakeGitError(ecode) return nil, MakeGitError(ecode)
} }
@ -311,7 +348,7 @@ func (repo *Repository) ListRemotes() ([]string, error) {
return remotes, nil return remotes, nil
} }
func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) { func (c *RemoteCollection) Create(name string, url string) (*Remote, error) {
remote := &Remote{} remote := &Remote{}
cname := C.CString(name) cname := C.CString(name)
@ -322,7 +359,7 @@ func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_create(&remote.ptr, repo.ptr, cname, curl) ret := C.git_remote_create(&remote.ptr, c.repo.ptr, cname, curl)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
@ -330,21 +367,21 @@ func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) {
return remote, nil return remote, nil
} }
func (repo *Repository) DeleteRemote(name string) error { func (c *RemoteCollection) Delete(name string) error {
cname := C.CString(name) cname := C.CString(name)
defer C.free(unsafe.Pointer(cname)) defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_delete(repo.ptr, cname) ret := C.git_remote_delete(c.repo.ptr, cname)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
return nil return nil
} }
func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch string) (*Remote, error) { func (c *RemoteCollection) CreateWithFetchspec(name string, url string, fetch string) (*Remote, error) {
remote := &Remote{} remote := &Remote{}
cname := C.CString(name) cname := C.CString(name)
@ -357,7 +394,7 @@ func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_create_with_fetchspec(&remote.ptr, repo.ptr, cname, curl, cfetch) ret := C.git_remote_create_with_fetchspec(&remote.ptr, c.repo.ptr, cname, curl, cfetch)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
@ -365,18 +402,16 @@ func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch
return remote, nil return remote, nil
} }
func (repo *Repository) CreateAnonymousRemote(url, fetch string) (*Remote, error) { func (c *RemoteCollection) CreateAnonymous(url string) (*Remote, error) {
remote := &Remote{} remote := &Remote{}
curl := C.CString(url) curl := C.CString(url)
defer C.free(unsafe.Pointer(curl)) defer C.free(unsafe.Pointer(curl))
cfetch := C.CString(fetch)
defer C.free(unsafe.Pointer(cfetch))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_create_anonymous(&remote.ptr, repo.ptr, curl, cfetch) ret := C.git_remote_create_anonymous(&remote.ptr, c.repo.ptr, curl)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
@ -384,7 +419,7 @@ func (repo *Repository) CreateAnonymousRemote(url, fetch string) (*Remote, error
return remote, nil return remote, nil
} }
func (repo *Repository) LookupRemote(name string) (*Remote, error) { func (c *RemoteCollection) Lookup(name string) (*Remote, error) {
remote := &Remote{} remote := &Remote{}
cname := C.CString(name) cname := C.CString(name)
@ -393,7 +428,7 @@ func (repo *Repository) LookupRemote(name string) (*Remote, error) {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_lookup(&remote.ptr, repo.ptr, cname) ret := C.git_remote_lookup(&remote.ptr, c.repo.ptr, cname)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
@ -401,22 +436,6 @@ func (repo *Repository) LookupRemote(name string) (*Remote, error) {
return remote, nil return remote, nil
} }
func (o *Remote) Save() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_remote_save(o.ptr)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (o *Remote) Owner() Repository {
return Repository{C.git_remote_owner(o.ptr)}
}
func (o *Remote) Name() string { func (o *Remote) Name() string {
return C.GoString(C.git_remote_name(o.ptr)) return C.GoString(C.git_remote_name(o.ptr))
} }
@ -429,42 +448,48 @@ func (o *Remote) PushUrl() string {
return C.GoString(C.git_remote_pushurl(o.ptr)) return C.GoString(C.git_remote_pushurl(o.ptr))
} }
func (o *Remote) SetUrl(url string) error { func (c *RemoteCollection) SetUrl(remote, url string) error {
curl := C.CString(url) curl := C.CString(url)
defer C.free(unsafe.Pointer(curl)) defer C.free(unsafe.Pointer(curl))
cremote := C.CString(remote)
defer C.free(unsafe.Pointer(cremote))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_set_url(o.ptr, curl) ret := C.git_remote_set_url(c.repo.ptr, cremote, curl)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
return nil return nil
} }
func (o *Remote) SetPushUrl(url string) error { func (c *RemoteCollection) SetPushUrl(remote, url string) error {
curl := C.CString(url) curl := C.CString(url)
defer C.free(unsafe.Pointer(curl)) defer C.free(unsafe.Pointer(curl))
cremote := C.CString(remote)
defer C.free(unsafe.Pointer(cremote))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_set_pushurl(o.ptr, curl) ret := C.git_remote_set_pushurl(c.repo.ptr, cremote, curl)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
return nil return nil
} }
func (o *Remote) AddFetch(refspec string) error { func (c *RemoteCollection) AddFetch(remote, refspec string) error {
crefspec := C.CString(refspec) crefspec := C.CString(refspec)
defer C.free(unsafe.Pointer(crefspec)) defer C.free(unsafe.Pointer(crefspec))
cremote := C.CString(remote)
defer C.free(unsafe.Pointer(cremote))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_add_fetch(o.ptr, crefspec) ret := C.git_remote_add_fetch(c.repo.ptr, cremote, crefspec)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
@ -525,30 +550,16 @@ func (o *Remote) FetchRefspecs() ([]string, error) {
return refspecs, nil return refspecs, nil
} }
func (o *Remote) SetFetchRefspecs(refspecs []string) error { func (c *RemoteCollection) AddPush(remote, refspec string) error {
crefspecs := C.git_strarray{}
crefspecs.count = C.size_t(len(refspecs))
crefspecs.strings = makeCStringsFromStrings(refspecs)
defer freeStrarray(&crefspecs)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_remote_set_fetch_refspecs(o.ptr, &crefspecs)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (o *Remote) AddPush(refspec string) error {
crefspec := C.CString(refspec) crefspec := C.CString(refspec)
defer C.free(unsafe.Pointer(crefspec)) defer C.free(unsafe.Pointer(crefspec))
cremote := C.CString(remote)
defer C.free(unsafe.Pointer(cremote))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_add_push(o.ptr, crefspec) ret := C.git_remote_add_push(c.repo.ptr, cremote, crefspec)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
@ -570,53 +581,37 @@ func (o *Remote) PushRefspecs() ([]string, error) {
return refspecs, nil return refspecs, nil
} }
func (o *Remote) SetPushRefspecs(refspecs []string) error {
crefspecs := C.git_strarray{}
crefspecs.count = C.size_t(len(refspecs))
crefspecs.strings = makeCStringsFromStrings(refspecs)
defer freeStrarray(&crefspecs)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_remote_set_push_refspecs(o.ptr, &crefspecs)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (o *Remote) ClearRefspecs() {
C.git_remote_clear_refspecs(o.ptr)
}
func (o *Remote) RefspecCount() uint { func (o *Remote) RefspecCount() uint {
return uint(C.git_remote_refspec_count(o.ptr)) return uint(C.git_remote_refspec_count(o.ptr))
} }
func (o *Remote) SetUpdateFetchHead(val bool) { func populateFetchOptions(options *C.git_fetch_options, opts *FetchOptions) {
C.git_remote_set_update_fetchhead(o.ptr, cbool(val)) C.git_fetch_init_options(options, C.GIT_FETCH_OPTIONS_VERSION)
if opts == nil {
return;
}
populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks)
options.prune = C.git_fetch_prune_t(opts.Prune)
options.update_fetchhead = cbool(opts.UpdateFetchhead)
options.download_tags = C.git_remote_autotag_option_t(opts.DownloadTags)
} }
func (o *Remote) UpdateFetchHead() bool { func populatePushOptions(options *C.git_push_options, opts *PushOptions) {
return C.git_remote_update_fetchhead(o.ptr) > 0 C.git_push_init_options(options, C.GIT_PUSH_OPTIONS_VERSION)
if opts == nil {
return
}
options.pb_parallelism = C.uint(opts.PbParallelism)
populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks)
} }
// Fetch performs a fetch operation. refspecs specifies which refspecs // Fetch performs a fetch operation. refspecs specifies which refspecs
// to use for this fetch, use an empty list to use the refspecs from // to use for this fetch, use an empty list to use the refspecs from
// the configuration; sig and msg specify what to use for the reflog // the configuration; msg specifies what to use for the reflog
// entries. Leave nil and "" to use defaults. // entries. Leave "" to use defaults.
func (o *Remote) Fetch(refspecs []string, sig *Signature, msg string) error { func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error {
var csig *C.git_signature = nil
if sig != nil {
csig, err := sig.toC()
if err != nil {
return err
}
defer C.git_signature_free(csig)
}
var cmsg *C.char = nil var cmsg *C.char = nil
if msg != "" { if msg != "" {
cmsg = C.CString(msg) cmsg = C.CString(msg)
@ -628,29 +623,36 @@ func (o *Remote) Fetch(refspecs []string, sig *Signature, msg string) error {
crefspecs.strings = makeCStringsFromStrings(refspecs) crefspecs.strings = makeCStringsFromStrings(refspecs)
defer freeStrarray(&crefspecs) defer freeStrarray(&crefspecs)
var coptions C.git_fetch_options
populateFetchOptions(&coptions, opts);
defer untrackCalbacksPayload(&coptions.callbacks)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_fetch(o.ptr, &crefspecs, csig, cmsg) ret := C.git_remote_fetch(o.ptr, &crefspecs, &coptions, cmsg)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
return nil return nil
} }
func (o *Remote) ConnectFetch() error { func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks) error {
return o.Connect(ConnectDirectionFetch) return o.Connect(ConnectDirectionFetch, callbacks)
} }
func (o *Remote) ConnectPush() error { func (o *Remote) ConnectPush(callbacks *RemoteCallbacks) error {
return o.Connect(ConnectDirectionPush) return o.Connect(ConnectDirectionPush, callbacks)
} }
func (o *Remote) Connect(direction ConnectDirection) error { func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks) error {
var ccallbacks C.git_remote_callbacks;
populateRemoteCallbacks(&ccallbacks, callbacks)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
if ret := C.git_remote_connect(o.ptr, C.git_direction(direction)); ret != 0 { if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks); ret != 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
return nil return nil
@ -702,39 +704,20 @@ func (o *Remote) Ls(filterRefs ...string) ([]RemoteHead, error) {
return heads, nil return heads, nil
} }
func (o *Remote) Push(refspecs []string, opts *PushOptions, sig *Signature, msg string) error { func (o *Remote) Push(refspecs []string, opts *PushOptions) error {
var csig *C.git_signature = nil
if sig != nil {
csig, err := sig.toC()
if err != nil {
return err
}
defer C.git_signature_free(csig)
}
var cmsg *C.char
if msg == "" {
cmsg = nil
} else {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
var copts C.git_push_options
C.git_push_init_options(&copts, C.GIT_PUSH_OPTIONS_VERSION)
if opts != nil {
copts.pb_parallelism = C.uint(opts.PbParallelism)
}
crefspecs := C.git_strarray{} crefspecs := C.git_strarray{}
crefspecs.count = C.size_t(len(refspecs)) crefspecs.count = C.size_t(len(refspecs))
crefspecs.strings = makeCStringsFromStrings(refspecs) crefspecs.strings = makeCStringsFromStrings(refspecs)
defer freeStrarray(&crefspecs) defer freeStrarray(&crefspecs)
var coptions C.git_push_options
populatePushOptions(&coptions, opts)
defer untrackCalbacksPayload(&coptions.callbacks)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_push(o.ptr, &crefspecs, &copts, csig, cmsg) ret := C.git_remote_push(o.ptr, &crefspecs, &coptions)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
@ -745,11 +728,14 @@ func (o *Remote) PruneRefs() bool {
return C.git_remote_prune_refs(o.ptr) > 0 return C.git_remote_prune_refs(o.ptr) > 0
} }
func (o *Remote) Prune() error { func (o *Remote) Prune(callbacks *RemoteCallbacks) error {
var ccallbacks C.git_remote_callbacks;
populateRemoteCallbacks(&ccallbacks, callbacks)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_remote_prune(o.ptr) ret := C.git_remote_prune(o.ptr, &ccallbacks)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }

View File

@ -3,35 +3,13 @@ package git
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
) )
func TestRefspecs(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.CreateAnonymousRemote("git://foo/bar", "refs/heads/*:refs/heads/*")
checkFatal(t, err)
expected := []string{
"refs/heads/*:refs/remotes/origin/*",
"refs/pull/*/head:refs/remotes/origin/*",
}
err = remote.SetFetchRefspecs(expected)
checkFatal(t, err)
actual, err := remote.FetchRefspecs()
checkFatal(t, err)
compareStringList(t, expected, actual)
}
func TestListRemotes(t *testing.T) { func TestListRemotes(t *testing.T) {
repo := createTestRepo(t) repo := createTestRepo(t)
defer cleanupTestRepo(t, repo) defer cleanupTestRepo(t, repo)
_, err := repo.CreateRemote("test", "git://foo/bar") _, err := repo.Remotes.Create("test", "git://foo/bar")
checkFatal(t, err) checkFatal(t, err)
@ -39,7 +17,7 @@ func TestListRemotes(t *testing.T) {
"test", "test",
} }
actual, err := repo.ListRemotes() actual, err := repo.Remotes.List()
checkFatal(t, err) checkFatal(t, err)
compareStringList(t, expected, actual) compareStringList(t, expected, actual)
@ -58,18 +36,18 @@ func TestCertificateCheck(t *testing.T) {
repo := createTestRepo(t) repo := createTestRepo(t)
defer cleanupTestRepo(t, repo) defer cleanupTestRepo(t, repo)
remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err) checkFatal(t, err)
callbacks := RemoteCallbacks{ options := FetchOptions {
CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) ErrorCode { RemoteCallbacks: RemoteCallbacks{
return assertHostname(cert, valid, hostname, t) CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) ErrorCode {
return assertHostname(cert, valid, hostname, t)
},
}, },
} }
err = remote.SetCallbacks(&callbacks) err = remote.Fetch([]string{}, &options, "")
checkFatal(t, err)
err = remote.Fetch([]string{}, nil, "")
checkFatal(t, err) checkFatal(t, err)
} }
@ -77,10 +55,10 @@ func TestRemoteConnect(t *testing.T) {
repo := createTestRepo(t) repo := createTestRepo(t)
defer cleanupTestRepo(t, repo) defer cleanupTestRepo(t, repo)
remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err) checkFatal(t, err)
err = remote.ConnectFetch() err = remote.ConnectFetch(nil)
checkFatal(t, err) checkFatal(t, err)
} }
@ -88,10 +66,10 @@ func TestRemoteLs(t *testing.T) {
repo := createTestRepo(t) repo := createTestRepo(t)
defer cleanupTestRepo(t, repo) defer cleanupTestRepo(t, repo)
remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err) checkFatal(t, err)
err = remote.ConnectFetch() err = remote.ConnectFetch(nil)
checkFatal(t, err) checkFatal(t, err)
heads, err := remote.Ls() heads, err := remote.Ls()
@ -106,10 +84,10 @@ func TestRemoteLsFiltering(t *testing.T) {
repo := createTestRepo(t) repo := createTestRepo(t)
defer cleanupTestRepo(t, repo) defer cleanupTestRepo(t, repo)
remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err) checkFatal(t, err)
err = remote.ConnectFetch() err = remote.ConnectFetch(nil)
checkFatal(t, err) checkFatal(t, err)
heads, err := remote.Ls("master") heads, err := remote.Ls("master")
@ -139,10 +117,10 @@ func TestRemotePruneRefs(t *testing.T) {
err = config.SetBool("remote.origin.prune", true) err = config.SetBool("remote.origin.prune", true)
checkFatal(t, err) checkFatal(t, err)
_, err = repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") _, err = repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err) checkFatal(t, err)
remote, err := repo.LookupRemote("origin") remote, err := repo.Remotes.Lookup("origin")
checkFatal(t, err) checkFatal(t, err)
if !remote.PruneRefs() { if !remote.PruneRefs() {
@ -159,13 +137,7 @@ func TestRemotePrune(t *testing.T) {
checkFatal(t, err) checkFatal(t, err)
defer commit.Free() defer commit.Free()
sig := &Signature{ remoteRef, err := remoteRepo.CreateBranch("test-prune", commit, true)
Name: "Rand Om Hacker",
Email: "random@hacker.com",
When: time.Now(),
}
remoteRef, err := remoteRepo.CreateBranch("test-prune", commit, true, sig, "branch test-prune")
checkFatal(t, err) checkFatal(t, err)
repo := createTestRepo(t) repo := createTestRepo(t)
@ -176,13 +148,13 @@ func TestRemotePrune(t *testing.T) {
defer config.Free() defer config.Free()
remoteUrl := fmt.Sprintf("file://%s", remoteRepo.Workdir()) remoteUrl := fmt.Sprintf("file://%s", remoteRepo.Workdir())
remote, err := repo.CreateRemote("origin", remoteUrl) remote, err := repo.Remotes.Create("origin", remoteUrl)
checkFatal(t, err) checkFatal(t, err)
err = remote.Fetch([]string{"test-prune"}, sig, "") err = remote.Fetch([]string{"test-prune"}, nil, "")
checkFatal(t, err) checkFatal(t, err)
_, err = repo.CreateReference("refs/remotes/origin/test-prune", head, true, sig, "remote reference") _, err = repo.References.Create("refs/remotes/origin/test-prune", head, true, "remote reference")
checkFatal(t, err) checkFatal(t, err)
err = remoteRef.Delete() err = remoteRef.Delete()
@ -191,16 +163,16 @@ func TestRemotePrune(t *testing.T) {
err = config.SetBool("remote.origin.prune", true) err = config.SetBool("remote.origin.prune", true)
checkFatal(t, err) checkFatal(t, err)
rr, err := repo.LookupRemote("origin") rr, err := repo.Remotes.Lookup("origin")
checkFatal(t, err) checkFatal(t, err)
err = rr.ConnectFetch() err = rr.ConnectFetch(nil)
checkFatal(t, err) checkFatal(t, err)
err = rr.Prune() err = rr.Prune(nil)
checkFatal(t, err) checkFatal(t, err)
_, err = repo.LookupReference("refs/remotes/origin/test-prune") _, err = repo.References.Lookup("refs/remotes/origin/test-prune")
if err == nil { if err == nil {
t.Fatal("Expected error getting a pruned reference") t.Fatal("Expected error getting a pruned reference")
} }

View File

@ -12,76 +12,99 @@ import (
// Repository // Repository
type Repository struct { type Repository struct {
ptr *C.git_repository ptr *C.git_repository
// Remotes represents the collection of remotes and can be
// used to add, remove and configure remotes for this
// repository.
Remotes RemoteCollection
// Submodules represents the collection of submodules and can
// be used to add, remove and configure submodules in this
// repostiory.
Submodules SubmoduleCollection
// References represents the collection of references and can
// be used to create, remove or update refernces for this repository.
References ReferenceCollection
// Notes represents the collection of notes and can be used to
// read, write and delete notes from this repository.
Notes NoteCollection
// Tags represents the collection of tags and can be used to create,
// list and iterate tags in this repository.
Tags TagsCollection
}
func newRepositoryFromC(ptr *C.git_repository) *Repository {
repo := &Repository{ptr: ptr}
repo.Remotes.repo = repo
repo.Submodules.repo = repo
repo.References.repo = repo
repo.Notes.repo = repo
repo.Tags.repo = repo
runtime.SetFinalizer(repo, (*Repository).Free)
return repo
} }
func OpenRepository(path string) (*Repository, error) { func OpenRepository(path string) (*Repository, error) {
repo := new(Repository)
cpath := C.CString(path) cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath)) defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_repository_open(&repo.ptr, cpath) var ptr *C.git_repository
ret := C.git_repository_open(&ptr, cpath)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
runtime.SetFinalizer(repo, (*Repository).Free) return newRepositoryFromC(ptr), nil
return repo, nil
} }
func OpenRepositoryExtended(path string) (*Repository, error) { func OpenRepositoryExtended(path string) (*Repository, error) {
repo := new(Repository)
cpath := C.CString(path) cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath)) defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_repository_open_ext(&repo.ptr, cpath, 0, nil) var ptr *C.git_repository
ret := C.git_repository_open_ext(&ptr, cpath, 0, nil)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
runtime.SetFinalizer(repo, (*Repository).Free) return newRepositoryFromC(ptr), nil
return repo, nil
} }
func InitRepository(path string, isbare bool) (*Repository, error) { func InitRepository(path string, isbare bool) (*Repository, error) {
repo := new(Repository)
cpath := C.CString(path) cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath)) defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_repository_init(&repo.ptr, cpath, ucbool(isbare)) var ptr *C.git_repository
ret := C.git_repository_init(&ptr, cpath, ucbool(isbare))
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
runtime.SetFinalizer(repo, (*Repository).Free) return newRepositoryFromC(ptr), nil
return repo, nil
} }
func NewRepositoryWrapOdb(odb *Odb) (repo *Repository, err error) { func NewRepositoryWrapOdb(odb *Odb) (repo *Repository, err error) {
repo = new(Repository)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_repository_wrap_odb(&repo.ptr, odb.ptr) var ptr *C.git_repository
ret := C.git_repository_wrap_odb(&ptr, odb.ptr)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
runtime.SetFinalizer(repo, (*Repository).Free) return newRepositoryFromC(ptr), nil
return repo, nil
} }
func (v *Repository) SetRefdb(refdb *Refdb) { func (v *Repository) SetRefdb(refdb *Refdb) {
@ -176,22 +199,6 @@ func (v *Repository) LookupTag(id *Oid) (*Tag, error) {
return obj.(*Tag), nil return obj.(*Tag), nil
} }
func (v *Repository) LookupReference(name string) (*Reference, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
var ptr *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_reference_lookup(&ptr, v.ptr, cname)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newReferenceFromC(ptr, v), nil
}
func (v *Repository) Head() (*Reference, error) { func (v *Repository) Head() (*Reference, error) {
var ptr *C.git_reference var ptr *C.git_reference
@ -206,49 +213,25 @@ func (v *Repository) Head() (*Reference, error) {
return newReferenceFromC(ptr, v), nil return newReferenceFromC(ptr, v), nil
} }
func (v *Repository) SetHead(refname string, sig *Signature, msg string) error { func (v *Repository) SetHead(refname string) error {
cname := C.CString(refname) cname := C.CString(refname)
defer C.free(unsafe.Pointer(cname)) defer C.free(unsafe.Pointer(cname))
csig, err := sig.toC()
if err != nil {
return err
}
defer C.git_signature_free(csig)
var cmsg *C.char
if msg != "" {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ecode := C.git_repository_set_head(v.ptr, cname, csig, cmsg) ecode := C.git_repository_set_head(v.ptr, cname)
if ecode != 0 { if ecode != 0 {
return MakeGitError(ecode) return MakeGitError(ecode)
} }
return nil return nil
} }
func (v *Repository) SetHeadDetached(id *Oid, sig *Signature, msg string) error { func (v *Repository) SetHeadDetached(id *Oid) error {
csig, err := sig.toC()
if err != nil {
return err
}
defer C.git_signature_free(csig)
var cmsg *C.char
if msg != "" {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ecode := C.git_repository_set_head_detached(v.ptr, id.toC(), csig, cmsg) ecode := C.git_repository_set_head_detached(v.ptr, id.toC())
if ecode != 0 { if ecode != 0 {
return MakeGitError(ecode) return MakeGitError(ecode)
} }
@ -267,71 +250,6 @@ func (v *Repository) IsHeadDetached() (bool, error) {
return ret != 0, nil return ret != 0, nil
} }
func (v *Repository) CreateReference(name string, id *Oid, force bool, sig *Signature, msg string) (*Reference, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(csig)
var cmsg *C.char
if msg == "" {
cmsg = nil
} else {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
var ptr *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_reference_create(&ptr, v.ptr, cname, id.toC(), cbool(force), csig, cmsg)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newReferenceFromC(ptr, v), nil
}
func (v *Repository) CreateSymbolicReference(name, target string, force bool, sig *Signature, msg string) (*Reference, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
ctarget := C.CString(target)
defer C.free(unsafe.Pointer(ctarget))
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(csig)
var cmsg *C.char
if msg == "" {
cmsg = nil
} else {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
var ptr *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_reference_symbolic_create(&ptr, v.ptr, cname, ctarget, cbool(force), csig, cmsg)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newReferenceFromC(ptr, v), nil
}
func (v *Repository) Walk() (*RevWalk, error) { func (v *Repository) Walk() (*RevWalk, error) {
var walkPtr *C.git_revwalk var walkPtr *C.git_revwalk
@ -403,36 +321,6 @@ func (v *Repository) CreateCommit(
return oid, nil return oid, nil
} }
func (v *Repository) CreateTag(
name string, commit *Commit, tagger *Signature, message string) (*Oid, error) {
oid := new(Oid)
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cmessage := C.CString(message)
defer C.free(unsafe.Pointer(cmessage))
taggerSig, err := tagger.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(taggerSig)
ctarget := commit.gitObject.ptr
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_tag_create(oid.toC(), v.ptr, cname, ctarget, taggerSig, cmessage, 0)
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
func (v *Odb) Free() { func (v *Odb) Free() {
runtime.SetFinalizer(v, nil) runtime.SetFinalizer(v, nil)
C.git_odb_free(v.ptr) C.git_odb_free(v.ptr)
@ -513,169 +401,6 @@ func (v *Repository) TreeBuilderFromTree(tree *Tree) (*TreeBuilder, error) {
return bld, nil return bld, nil
} }
// EnsureLog ensures that there is a reflog for the given reference
// name and creates an empty one if necessary.
func (v *Repository) EnsureLog(name string) error {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_ensure_log(v.ptr, cname)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// HasLog returns whether there is a reflog for the given reference
// name
func (v *Repository) HasLog(name string) (bool, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_has_log(v.ptr, cname)
if ret < 0 {
return false, MakeGitError(ret)
}
return ret == 1, nil
}
// DwimReference looks up a reference by DWIMing its short name
func (v *Repository) DwimReference(name string) (*Reference, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_reference
ret := C.git_reference_dwim(&ptr, v.ptr, cname)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newReferenceFromC(ptr, v), nil
}
// CreateNote adds a note for an object
func (v *Repository) CreateNote(
ref string, author, committer *Signature, id *Oid,
note string, force bool) (*Oid, error) {
oid := new(Oid)
var cref *C.char
if ref == "" {
cref = nil
} else {
cref = C.CString(ref)
defer C.free(unsafe.Pointer(cref))
}
authorSig, err := author.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(authorSig)
committerSig, err := committer.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(committerSig)
cnote := C.CString(note)
defer C.free(unsafe.Pointer(cnote))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_note_create(
oid.toC(), v.ptr, cref, authorSig,
committerSig, id.toC(), cnote, cbool(force))
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
// ReadNote reads the note for an object
func (v *Repository) ReadNote(ref string, id *Oid) (*Note, error) {
var cref *C.char
if ref == "" {
cref = nil
} else {
cref = C.CString(ref)
defer C.free(unsafe.Pointer(cref))
}
note := new(Note)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if ret := C.git_note_read(&note.ptr, v.ptr, cref, id.toC()); ret < 0 {
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(note, (*Note).Free)
return note, nil
}
// RemoveNote removes the note for an object
func (v *Repository) RemoveNote(ref string, author, committer *Signature, id *Oid) error {
var cref *C.char
if ref == "" {
cref = nil
} else {
cref = C.CString(ref)
defer C.free(unsafe.Pointer(cref))
}
authorSig, err := author.toC()
if err != nil {
return err
}
defer C.git_signature_free(authorSig)
committerSig, err := committer.toC()
if err != nil {
return err
}
defer C.git_signature_free(committerSig)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_note_remove(v.ptr, cref, authorSig, committerSig, id.toC())
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// DefaultNoteRef returns the default notes reference for a repository
func (v *Repository) DefaultNoteRef() (string, error) {
var ptr *C.char
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if ret := C.git_note_default_ref(&ptr, v.ptr); ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(ptr), nil
}
type RepositoryState int type RepositoryState int
const ( const (

26
reset.go Normal file
View File

@ -0,0 +1,26 @@
package git
/*
#include <git2.h>
*/
import "C"
import "runtime"
type ResetType int
const (
ResetSoft ResetType = C.GIT_RESET_SOFT
ResetMixed ResetType = C.GIT_RESET_MIXED
ResetHard ResetType = C.GIT_RESET_HARD
)
func (r *Repository) ResetToCommit(commit *Commit, resetType ResetType, opts *CheckoutOpts) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reset(r.ptr, commit.gitObject.ptr, C.git_reset_t(resetType), opts.toC())
if ret < 0 {
return MakeGitError(ret)
}
return nil
}

45
reset_test.go Normal file
View File

@ -0,0 +1,45 @@
package git
import (
"io/ioutil"
"testing"
)
func TestResetToCommit(t *testing.T) {
repo := createTestRepo(t)
seedTestRepo(t, repo)
// create commit to reset to
commitId, _ := updateReadme(t, repo, "testing reset")
// create commit to reset from
nextCommitId, _ := updateReadme(t, repo, "will be reset")
// confirm that we wrote "will be reset" to the readme
newBytes, err := ioutil.ReadFile(pathInRepo(repo, "README"))
checkFatal(t, err)
if string(newBytes) != "will be reset" {
t.Fatalf("expected %s to equal 'will be reset'", string(newBytes))
}
// confirm that the head of the repo is the next commit id
head, err := repo.Head()
checkFatal(t, err)
if head.Target().String() != nextCommitId.String() {
t.Fatalf(
"expected to be at latest commit %s, but was %s",
nextCommitId.String(),
head.Target().String(),
)
}
commitToResetTo, err := repo.LookupCommit(commitId)
checkFatal(t, err)
repo.ResetToCommit(commitToResetTo, ResetHard, &CheckoutOpts{})
// check that the file now reads "testing reset" like it did before
bytes, err := ioutil.ReadFile(pathInRepo(repo, "README"))
checkFatal(t, err)
if string(bytes) != "testing reset" {
t.Fatalf("expected %s to equal 'testing reset'", string(bytes))
}
}

View File

@ -34,7 +34,7 @@ func TestRevparseExt(t *testing.T) {
_, treeId := seedTestRepo(t, repo) _, treeId := seedTestRepo(t, repo)
ref, err := repo.CreateReference("refs/heads/master", treeId, true, nil, "") ref, err := repo.References.Create("refs/heads/master", treeId, true, "")
checkFatal(t, err) checkFatal(t, err)
obj, ref, err := repo.RevparseExt("master") obj, ref, err := repo.RevparseExt("master")

19
script/build-libgit2-static.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/sh
set -ex
VENDORED_PATH=vendor/libgit2
cd $VENDORED_PATH &&
mkdir -p install/lib &&
mkdir -p build &&
cd build &&
cmake -DTHREADSAFE=ON \
-DBUILD_CLAR=OFF \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_C_FLAGS=-fPIC \
-DCMAKE_BUILD_TYPE="RelWithDebInfo" \
-DCMAKE_INSTALL_PREFIX=../install \
.. &&
cmake --build .

View File

@ -1,3 +1,5 @@
// +build ignore
package main package main
import ( import (

21
script/install-libgit2.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
#
# Install libgit2 to git2go in dynamic mode on Travis
#
set -ex
# We don't want to build libgit2 on the next branch, as we carry a
# submodule with the exact version we support
if [ "x$TRAVIS_BRANCH" = "xnext" ]; then
exit 0
fi
cd "${HOME}"
wget -O libgit2-0.22.3.tar.gz https://github.com/libgit2/libgit2/archive/v0.22.1.tar.gz
tar -xzvf libgit2-0.22.3.tar.gz
cd libgit2-0.22.1 && mkdir build && cd build
cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_BUILD_TYPE="RelWithDebInfo" .. && make && sudo make install
sudo ldconfig
cd "${TRAVIS_BUILD_DIR}"

12
script/with-static.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/sh
set -ex
export BUILD="$PWD/vendor/libgit2/build"
export PCFILE="$BUILD/libgit2.pc"
FLAGS=$(pkg-config --static --libs $PCFILE) || exit 1
export CGO_LDFLAGS="$BUILD/libgit2.a -L$BUILD ${FLAGS}"
export CGO_CFLAGS="-I$PWD/vendor/libgit2/include"
$@

View File

@ -14,9 +14,8 @@ import (
// SubmoduleUpdateOptions // SubmoduleUpdateOptions
type SubmoduleUpdateOptions struct { type SubmoduleUpdateOptions struct {
*CheckoutOpts *CheckoutOpts
*RemoteCallbacks *FetchOptions
CloneCheckoutStrategy CheckoutStrategy CloneCheckoutStrategy CheckoutStrategy
Signature *Signature
} }
// Submodule // Submodule
@ -27,7 +26,6 @@ type Submodule struct {
type SubmoduleUpdate int type SubmoduleUpdate int
const ( const (
SubmoduleUpdateReset SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_RESET
SubmoduleUpdateCheckout SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_CHECKOUT SubmoduleUpdateCheckout SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_CHECKOUT
SubmoduleUpdateRebase SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_REBASE SubmoduleUpdateRebase SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_REBASE
SubmoduleUpdateMerge SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_MERGE SubmoduleUpdateMerge SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_MERGE
@ -37,7 +35,6 @@ const (
type SubmoduleIgnore int type SubmoduleIgnore int
const ( const (
SubmoduleIgnoreReset SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_RESET
SubmoduleIgnoreNone SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_NONE SubmoduleIgnoreNone SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_NONE
SubmoduleIgnoreUntracked SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_UNTRACKED SubmoduleIgnoreUntracked SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_UNTRACKED
SubmoduleIgnoreDirty SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_DIRTY SubmoduleIgnoreDirty SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_DIRTY
@ -71,13 +68,17 @@ const (
SubmoduleRecurseOndemand SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_ONDEMAND SubmoduleRecurseOndemand SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_ONDEMAND
) )
type SubmoduleCollection struct {
repo *Repository
}
func SubmoduleStatusIsUnmodified(status int) bool { func SubmoduleStatusIsUnmodified(status int) bool {
o := SubmoduleStatus(status) & ^(SubmoduleStatusInHead | SubmoduleStatusInIndex | o := SubmoduleStatus(status) & ^(SubmoduleStatusInHead | SubmoduleStatusInIndex |
SubmoduleStatusInConfig | SubmoduleStatusInWd) SubmoduleStatusInConfig | SubmoduleStatusInWd)
return o == 0 return o == 0
} }
func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) { func (c *SubmoduleCollection) Lookup(name string) (*Submodule, error) {
cname := C.CString(name) cname := C.CString(name)
defer C.free(unsafe.Pointer(cname)) defer C.free(unsafe.Pointer(cname))
@ -86,7 +87,7 @@ func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_submodule_lookup(&sub.ptr, repo.ptr, cname) ret := C.git_submodule_lookup(&sub.ptr, c.repo.ptr, cname)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
@ -107,21 +108,21 @@ func SubmoduleVisitor(csub unsafe.Pointer, name *C.char, handle unsafe.Pointer)
} }
} }
func (repo *Repository) ForeachSubmodule(cbk SubmoduleCbk) error { func (c *SubmoduleCollection) Foreach(cbk SubmoduleCbk) error {
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
handle := pointerHandles.Track(cbk) handle := pointerHandles.Track(cbk)
defer pointerHandles.Untrack(handle) defer pointerHandles.Untrack(handle)
ret := C._go_git_visit_submodule(repo.ptr, handle) ret := C._go_git_visit_submodule(c.repo.ptr, handle)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
return nil return nil
} }
func (repo *Repository) AddSubmodule(url, path string, use_git_link bool) (*Submodule, error) { func (c *SubmoduleCollection) Add(url, path string, use_git_link bool) (*Submodule, error) {
curl := C.CString(url) curl := C.CString(url)
defer C.free(unsafe.Pointer(curl)) defer C.free(unsafe.Pointer(curl))
cpath := C.CString(path) cpath := C.CString(path)
@ -132,7 +133,7 @@ func (repo *Repository) AddSubmodule(url, path string, use_git_link bool) (*Subm
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_submodule_add_setup(&sub.ptr, repo.ptr, curl, cpath, cbool(use_git_link)) ret := C.git_submodule_add_setup(&sub.ptr, c.repo.ptr, curl, cpath, cbool(use_git_link))
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
@ -161,23 +162,6 @@ func (sub *Submodule) AddToIndex(write_index bool) error {
return nil return nil
} }
func (sub *Submodule) Save() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_save(sub.ptr)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (sub *Submodule) Owner() *Repository {
repo := C.git_submodule_owner(sub.ptr)
//FIXME: how to handle dangling references ?
return &Repository{repo}
}
func (sub *Submodule) Name() string { func (sub *Submodule) Name() string {
n := C.git_submodule_name(sub.ptr) n := C.git_submodule_name(sub.ptr)
return C.GoString(n) return C.GoString(n)
@ -193,14 +177,16 @@ func (sub *Submodule) Url() string {
return C.GoString(n) return C.GoString(n)
} }
func (sub *Submodule) SetUrl(url string) error { func (c *SubmoduleCollection) SetUrl(submodule, url string) error {
csubmodule := C.CString(submodule)
defer C.free(unsafe.Pointer(csubmodule))
curl := C.CString(url) curl := C.CString(url)
defer C.free(unsafe.Pointer(curl)) defer C.free(unsafe.Pointer(curl))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_submodule_set_url(sub.ptr, curl) ret := C.git_submodule_set_url(c.repo.ptr, csubmodule, curl)
if ret < 0 { if ret < 0 {
return MakeGitError(ret) return MakeGitError(ret)
} }
@ -236,9 +222,19 @@ func (sub *Submodule) Ignore() SubmoduleIgnore {
return SubmoduleIgnore(o) return SubmoduleIgnore(o)
} }
func (sub *Submodule) SetIgnore(ignore SubmoduleIgnore) SubmoduleIgnore { func (c *SubmoduleCollection) SetIgnore(submodule string, ignore SubmoduleIgnore) error {
o := C.git_submodule_set_ignore(sub.ptr, C.git_submodule_ignore_t(ignore)) csubmodule := C.CString(submodule)
return SubmoduleIgnore(o) defer C.free(unsafe.Pointer(csubmodule))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_set_ignore(c.repo.ptr, csubmodule, C.git_submodule_ignore_t(ignore))
if ret < 0 {
return MakeGitError(ret)
}
return nil
} }
func (sub *Submodule) UpdateStrategy() SubmoduleUpdate { func (sub *Submodule) UpdateStrategy() SubmoduleUpdate {
@ -246,20 +242,33 @@ func (sub *Submodule) UpdateStrategy() SubmoduleUpdate {
return SubmoduleUpdate(o) return SubmoduleUpdate(o)
} }
func (sub *Submodule) SetUpdate(update SubmoduleUpdate) SubmoduleUpdate { func (c *SubmoduleCollection) SetUpdate(submodule string, update SubmoduleUpdate) error {
o := C.git_submodule_set_update(sub.ptr, C.git_submodule_update_t(update)) csubmodule := C.CString(submodule)
return SubmoduleUpdate(o) defer C.free(unsafe.Pointer(csubmodule))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_set_update(c.repo.ptr, csubmodule, C.git_submodule_update_t(update))
if ret < 0 {
return MakeGitError(ret)
}
return nil
} }
func (sub *Submodule) FetchRecurseSubmodules() SubmoduleRecurse { func (sub *Submodule) FetchRecurseSubmodules() SubmoduleRecurse {
return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr)) return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr))
} }
func (sub *Submodule) SetFetchRecurseSubmodules(recurse SubmoduleRecurse) error { func (c *SubmoduleCollection) SetFetchRecurseSubmodules(submodule string, recurse SubmoduleRecurse) error {
csubmodule := C.CString(submodule)
defer C.free(unsafe.Pointer(csubmodule))
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_submodule_set_fetch_recurse_submodules(sub.ptr, C.git_submodule_recurse_t(recurse)) ret := C.git_submodule_set_fetch_recurse_submodules(c.repo.ptr, csubmodule, C.git_submodule_recurse_t(recurse))
if ret < 0 { if ret < 0 {
return MakeGitError(C.int(ret)) return MakeGitError(C.int(ret))
} }
@ -289,38 +298,15 @@ func (sub *Submodule) Sync() error {
} }
func (sub *Submodule) Open() (*Repository, error) { func (sub *Submodule) Open() (*Repository, error) {
repo := new(Repository)
runtime.LockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread() defer runtime.UnlockOSThread()
ret := C.git_submodule_open(&repo.ptr, sub.ptr) var ptr *C.git_repository
ret := C.git_submodule_open(&ptr, sub.ptr)
if ret < 0 { if ret < 0 {
return nil, MakeGitError(ret) return nil, MakeGitError(ret)
} }
return repo, nil return newRepositoryFromC(ptr), nil
}
func (sub *Submodule) Reload(force bool) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_reload(sub.ptr, cbool(force))
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (repo *Repository) ReloadAllSubmodules(force bool) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_reload_all(repo.ptr, cbool(force))
if ret < 0 {
return MakeGitError(ret)
}
return nil
} }
func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error { func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error {
@ -349,14 +335,8 @@ func populateSubmoduleUpdateOptions(ptr *C.git_submodule_update_options, opts *S
} }
populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts) populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts)
populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks) populateFetchOptions(&ptr.fetch_opts, opts.FetchOptions)
ptr.clone_checkout_strategy = C.uint(opts.CloneCheckoutStrategy) ptr.clone_checkout_strategy = C.uint(opts.CloneCheckoutStrategy)
sig, err := opts.Signature.toC()
if err != nil {
return err
}
ptr.signature = sig
return nil return nil
} }

View File

@ -10,11 +10,11 @@ func TestSubmoduleForeach(t *testing.T) {
seedTestRepo(t, repo) seedTestRepo(t, repo)
_, err := repo.AddSubmodule("http://example.org/submodule", "submodule", true) _, err := repo.Submodules.Add("http://example.org/submodule", "submodule", true)
checkFatal(t, err) checkFatal(t, err)
i := 0 i := 0
err = repo.ForeachSubmodule(func(sub *Submodule, name string) int { err = repo.Submodules.Foreach(func(sub *Submodule, name string) int {
i++ i++
return 0 return 0
}) })

167
tag.go
View File

@ -2,8 +2,14 @@ package git
/* /*
#include <git2.h> #include <git2.h>
extern int _go_git_tag_foreach(git_repository *repo, void *payload);
*/ */
import "C" import "C"
import (
"runtime"
"unsafe"
)
// Tag // Tag
type Tag struct { type Tag struct {
@ -42,3 +48,164 @@ func (t Tag) TargetId() *Oid {
func (t Tag) TargetType() ObjectType { func (t Tag) TargetType() ObjectType {
return ObjectType(C.git_tag_target_type(t.cast_ptr)) return ObjectType(C.git_tag_target_type(t.cast_ptr))
} }
type TagsCollection struct {
repo *Repository
}
func (c *TagsCollection) Create(
name string, commit *Commit, tagger *Signature, message string) (*Oid, error) {
oid := new(Oid)
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cmessage := C.CString(message)
defer C.free(unsafe.Pointer(cmessage))
taggerSig, err := tagger.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(taggerSig)
ctarget := commit.gitObject.ptr
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_tag_create(oid.toC(), c.repo.ptr, cname, ctarget, taggerSig, cmessage, 0)
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
// CreateLightweight creates a new lightweight tag pointing to a commit
// and returns the id of the target object.
//
// The name of the tag is validated for consistency (see git_tag_create() for the rules
// https://libgit2.github.com/libgit2/#HEAD/group/tag/git_tag_create) and should
// not conflict with an already existing tag name.
//
// If force is true and a reference already exists with the given name, it'll be replaced.
//
// The created tag is a simple reference and can be queried using
// repo.References.Lookup("refs/tags/<name>"). The name of the tag (eg "v1.0.0")
// is queried with ref.Shorthand().
func (c *TagsCollection) CreateLightweight(name string, commit *Commit, force bool) (*Oid, error) {
oid := new(Oid)
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
ctarget := commit.gitObject.ptr
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C.git_tag_create_lightweight(oid.toC(), c.repo.ptr, cname, ctarget, cbool(force))
if err < 0 {
return nil, MakeGitError(err)
}
return oid, nil
}
// List returns the names of all the tags in the repository,
// eg: ["v1.0.1", "v2.0.0"].
func (c *TagsCollection) List() ([]string, error) {
var strC C.git_strarray
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_tag_list(&strC, c.repo.ptr)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
defer C.git_strarray_free(&strC)
tags := makeStringsFromCStrings(strC.strings, int(strC.count))
return tags, nil
}
// ListWithMatch returns the names of all the tags in the repository
// that match a given pattern.
//
// The pattern is a standard fnmatch(3) pattern http://man7.org/linux/man-pages/man3/fnmatch.3.html
func (c *TagsCollection) ListWithMatch(pattern string) ([]string, error) {
var strC C.git_strarray
patternC := C.CString(pattern)
defer C.free(unsafe.Pointer(patternC))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_tag_list_match(&strC, patternC, c.repo.ptr)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
defer C.git_strarray_free(&strC)
tags := makeStringsFromCStrings(strC.strings, int(strC.count))
return tags, nil
}
// TagForeachCallback is called for each tag in the repository.
//
// The name is the full ref name eg: "refs/tags/v1.0.0".
//
// Note that the callback is called for lightweight tags as well,
// so repo.LookupTag() will return an error for these tags. Use
// repo.References.Lookup() instead.
type TagForeachCallback func(name string, id *Oid) error
type tagForeachData struct {
callback TagForeachCallback
err error
}
//export gitTagForeachCb
func gitTagForeachCb(name *C.char, id *C.git_oid, handle unsafe.Pointer) int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*tagForeachData)
if !ok {
panic("could not retrieve tag foreach CB handle")
}
err := data.callback(C.GoString(name), newOidFromC(id))
if err != nil {
data.err = err
return C.GIT_EUSER
}
return 0
}
// Foreach calls the callback for each tag in the repository.
func (c *TagsCollection) Foreach(callback TagForeachCallback) error {
data := tagForeachData{
callback: callback,
err: nil,
}
handle := pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C._go_git_tag_foreach(c.repo.ptr, handle)
if err == C.GIT_EUSER {
return data.err
}
if err < 0 {
return MakeGitError(err)
}
return nil
}

View File

@ -1,6 +1,7 @@
package git package git
import ( import (
"errors"
"testing" "testing"
"time" "time"
) )
@ -24,6 +25,146 @@ func TestCreateTag(t *testing.T) {
compareStrings(t, commitId.String(), tag.TargetId().String()) compareStrings(t, commitId.String(), tag.TargetId().String())
} }
func TestCreateTagLightweight(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitID, _ := seedTestRepo(t, repo)
commit, err := repo.LookupCommit(commitID)
checkFatal(t, err)
tagID, err := repo.Tags.CreateLightweight("v0.1.0", commit, false)
checkFatal(t, err)
_, err = repo.Tags.CreateLightweight("v0.1.0", commit, true)
checkFatal(t, err)
ref, err := repo.References.Lookup("refs/tags/v0.1.0")
checkFatal(t, err)
compareStrings(t, "refs/tags/v0.1.0", ref.Name())
compareStrings(t, "v0.1.0", ref.Shorthand())
compareStrings(t, tagID.String(), commitID.String())
compareStrings(t, commitID.String(), ref.Target().String())
}
func TestListTags(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitID, _ := seedTestRepo(t, repo)
commit, err := repo.LookupCommit(commitID)
checkFatal(t, err)
createTag(t, repo, commit, "v1.0.1", "Release v1.0.1")
commitID, _ = updateReadme(t, repo, "Release version 2")
commit, err = repo.LookupCommit(commitID)
checkFatal(t, err)
createTag(t, repo, commit, "v2.0.0", "Release v2.0.0")
expected := []string{
"v1.0.1",
"v2.0.0",
}
actual, err := repo.Tags.List()
checkFatal(t, err)
compareStringList(t, expected, actual)
}
func TestListTagsWithMatch(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitID, _ := seedTestRepo(t, repo)
commit, err := repo.LookupCommit(commitID)
checkFatal(t, err)
createTag(t, repo, commit, "v1.0.1", "Release v1.0.1")
commitID, _ = updateReadme(t, repo, "Release version 2")
commit, err = repo.LookupCommit(commitID)
checkFatal(t, err)
createTag(t, repo, commit, "v2.0.0", "Release v2.0.0")
expected := []string{
"v2.0.0",
}
actual, err := repo.Tags.ListWithMatch("v2*")
checkFatal(t, err)
compareStringList(t, expected, actual)
expected = []string{
"v1.0.1",
}
actual, err = repo.Tags.ListWithMatch("v1*")
checkFatal(t, err)
compareStringList(t, expected, actual)
}
func TestTagForeach(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitID, _ := seedTestRepo(t, repo)
commit, err := repo.LookupCommit(commitID)
checkFatal(t, err)
tag1 := createTag(t, repo, commit, "v1.0.1", "Release v1.0.1")
commitID, _ = updateReadme(t, repo, "Release version 2")
commit, err = repo.LookupCommit(commitID)
checkFatal(t, err)
tag2 := createTag(t, repo, commit, "v2.0.0", "Release v2.0.0")
expectedNames := []string{
"refs/tags/v1.0.1",
"refs/tags/v2.0.0",
}
actualNames := []string{}
expectedOids := []string{
tag1.String(),
tag2.String(),
}
actualOids := []string{}
err = repo.Tags.Foreach(func(name string, id *Oid) error {
actualNames = append(actualNames, name)
actualOids = append(actualOids, id.String())
return nil
})
checkFatal(t, err)
compareStringList(t, expectedNames, actualNames)
compareStringList(t, expectedOids, actualOids)
fakeErr := errors.New("fake error")
err = repo.Tags.Foreach(func(name string, id *Oid) error {
return fakeErr
})
if err != fakeErr {
t.Fatalf("Tags.Foreach() did not return the expected error, got %v", err)
}
}
func compareStrings(t *testing.T, expected, value string) { func compareStrings(t *testing.T, expected, value string) {
if value != expected { if value != expected {
t.Fatalf("expected '%v', actual '%v'", expected, value) t.Fatalf("expected '%v', actual '%v'", expected, value)
@ -39,7 +180,21 @@ func createTestTag(t *testing.T, repo *Repository, commit *Commit) *Oid {
When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
} }
tagId, err := repo.CreateTag("v0.0.0", commit, sig, "This is a tag") tagId, err := repo.Tags.Create("v0.0.0", commit, sig, "This is a tag")
checkFatal(t, err)
return tagId
}
func createTag(t *testing.T, repo *Repository, commit *Commit, name, message string) *Oid {
loc, err := time.LoadLocation("Europe/Bucharest")
checkFatal(t, err)
sig := &Signature{
Name: "Rand Om Hacker",
Email: "random@hacker.com",
When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc),
}
tagId, err := repo.Tags.Create(name, commit, sig, message)
checkFatal(t, err) checkFatal(t, err)
return tagId return tagId
} }

18
tree.go
View File

@ -55,6 +55,24 @@ func (t Tree) EntryByName(filename string) *TreeEntry {
return newTreeEntry(entry) return newTreeEntry(entry)
} }
// EntryById performs a lookup for a tree entry with the given SHA value.
//
// It returns a *TreeEntry that is owned by the Tree. You don't have to
// free it, but you must not use it after the Tree is freed.
//
// Warning: this must examine every entry in the tree, so it is not fast.
func (t Tree) EntryById(id *Oid) *TreeEntry {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
entry := C.git_tree_entry_byid(t.cast_ptr, id.toC())
if entry == nil {
return nil
}
return newTreeEntry(entry)
}
// EntryByPath looks up an entry by its full path, recursing into // EntryByPath looks up an entry by its full path, recursing into
// deeper trees if necessary (i.e. if there are slashes in the path) // deeper trees if necessary (i.e. if there are slashes in the path)
func (t Tree) EntryByPath(path string) (*TreeEntry, error) { func (t Tree) EntryByPath(path string) (*TreeEntry, error) {

22
tree_test.go Normal file
View File

@ -0,0 +1,22 @@
package git
import "testing"
func TestTreeEntryById(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, treeID := seedTestRepo(t, repo)
tree, err := repo.LookupTree(treeID)
checkFatal(t, err)
id, err := NewOid("257cc5642cb1a054f08cc83f2d943e56fd3ebe99")
checkFatal(t, err)
entry := tree.EntryById(id)
if entry == nil {
t.Fatalf("entry id %v was not found", id)
}
}

1
vendor/libgit2 vendored Submodule

@ -0,0 +1 @@
Subproject commit fb84cde81e11947add4ff8bb9b4084f7d76e6567

View File

@ -64,7 +64,7 @@ int _go_git_diff_foreach(git_diff *diff, int eachFile, int eachHunk, int eachLin
lcb = (git_diff_line_cb)&diffForEachLineCb; lcb = (git_diff_line_cb)&diffForEachLineCb;
} }
return git_diff_foreach(diff, fcb, hcb, lcb, payload); return git_diff_foreach(diff, fcb, NULL, hcb, lcb, payload);
} }
int _go_git_diff_blobs(git_blob *old, const char *old_path, git_blob *new, const char *new_path, git_diff_options *opts, int eachFile, int eachHunk, int eachLine, void *payload) int _go_git_diff_blobs(git_blob *old, const char *old_path, git_blob *new, const char *new_path, git_diff_options *opts, int eachFile, int eachHunk, int eachLine, void *payload)
@ -85,7 +85,7 @@ int _go_git_diff_blobs(git_blob *old, const char *old_path, git_blob *new, const
lcb = (git_diff_line_cb)&diffForEachLineCb; lcb = (git_diff_line_cb)&diffForEachLineCb;
} }
return git_diff_blobs(old, old_path, new, new_path, opts, fcb, hcb, lcb, payload); return git_diff_blobs(old, old_path, new, new_path, opts, fcb, NULL, hcb, lcb, payload);
} }
void _go_git_setup_diff_notify_callbacks(git_diff_options *opts) { void _go_git_setup_diff_notify_callbacks(git_diff_options *opts) {
@ -136,4 +136,9 @@ int _go_git_index_remove_all(git_index *index, const git_strarray *pathspec, voi
return git_index_remove_all(index, pathspec, cb, callback); return git_index_remove_all(index, pathspec, cb, callback);
} }
int _go_git_tag_foreach(git_repository *repo, void *payload)
{
return git_tag_foreach(repo, (git_tag_foreach_cb)&gitTagForeachCb, payload);
}
/* EOF */ /* EOF */