Merge branch 'master' into v22

This commit is contained in:
Carlos Martín Nieto 2015-03-15 01:09:11 +01:00
commit 137c4fc3c8
26 changed files with 612 additions and 130 deletions

View File

@ -3,15 +3,28 @@ git2go
[![GoDoc](https://godoc.org/github.com/libgit2/git2go?status.svg)](http://godoc.org/github.com/libgit2/git2go) [![Build Status](https://travis-ci.org/libgit2/git2go.svg?branch=v22)](https://travis-ci.org/libgit2/git2go)
Go bindings for [libgit2](http://libgit2.github.com/). The master branch follows the latest libgit2 release.
Go bindings for [libgit2](http://libgit2.github.com/). The `master` branch follows the latest libgit2 release. The versioned branches indicate which libgit2 version they work against.
Installing
----------
This project needs libgit2, which is written in C so we need to build that as well. In order to build libgit2, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 if you want to use HTTPS and SSH respectively.
This project needs libgit2, which is written in C so we need to build that as well. In order to build libgit2, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively.
### Stable version
git2go has versioned branches which indicate which version of libgit2 they work against. Install the development package it on your system via your favourite package manager or from source and you can use a service like gopkg.in to use the appropriate version. For the libgit2 v0.22 case, you can use
import "gopkg.in/libgit2/git2go.v22"
to use a version of git2go which will work against libgit2 v0.22 and dynamically link to the library.
### From master
The `next` branch follows libgit2's master branch, which means there is no stable API or ABI to link against. git2go can statically link against a vendored version of libgit2.
Run `go get -d github.com/libgit2/git2go` to download the code and go to your `$GOPATH/src/github.com/libgit2/git2go` dir. From there, we need to build the C code and put it into the resulting go binary.
git checkout next
git submodule update --init # get libgit2
make install
@ -25,7 +38,7 @@ libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. F
Running the tests
-----------------
Similarly to installing, running the tests requires linking against the local libgit2 library, so the Makefile provides a wrapper
For the stable version, `go test` will work as usual. For the `next` branch, similarly to installing, running the tests requires linking against the local libgit2 library, so the Makefile provides a wrapper
make test

View File

@ -30,10 +30,7 @@ type BranchIterator struct {
repo *Repository
}
type BranchInfo struct {
Branch *Branch
Type BranchType
}
type BranchIteratorFunc func(*Branch, BranchType) error
func newBranchIteratorFromC(repo *Repository, ptr *C.git_branch_iterator) *BranchIterator {
i := &BranchIterator{repo: repo, ptr: ptr}
@ -65,8 +62,20 @@ func (i *BranchIterator) Free() {
C.git_branch_iterator_free(i.ptr)
}
func (repo *Repository) NewBranchIterator(flags BranchType) (*BranchIterator, error) {
func (i *BranchIterator) ForEach(f BranchIteratorFunc) error {
b, t, err := i.Next()
for err == nil {
err = f(b, t)
if err == nil {
b, t, err = i.Next()
}
}
return err
}
func (repo *Repository) NewBranchIterator(flags BranchType) (*BranchIterator, error) {
refType := C.git_branch_t(flags)
var ptr *C.git_branch_iterator
@ -87,7 +96,10 @@ func (repo *Repository) CreateBranch(branchName string, target *Commit, force bo
cBranchName := C.CString(branchName)
cForce := cbool(force)
cSignature := signature.toC()
cSignature, err := signature.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(cSignature)
var cmsg *C.char
@ -124,7 +136,10 @@ func (b *Branch) Move(newBranchName string, force bool, signature *Signature, ms
cNewBranchName := C.CString(newBranchName)
cForce := cbool(force)
cSignature := signature.toC()
cSignature, err := signature.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(cSignature)
var cmsg *C.char

View File

@ -1,11 +1,8 @@
package git
import (
"testing"
)
import "testing"
func TestBranchIterator(t *testing.T) {
repo := createTestRepo(t)
seedTestRepo(t, repo)
@ -24,3 +21,35 @@ func TestBranchIterator(t *testing.T) {
t.Fatal("expected iterover")
}
}
func TestBranchIteratorEach(t *testing.T) {
repo := createTestRepo(t)
seedTestRepo(t, repo)
i, err := repo.NewBranchIterator(BranchLocal)
checkFatal(t, err)
var names []string
f := func(b *Branch, t BranchType) error {
name, err := b.Name()
if err != nil {
return err
}
names = append(names, name)
return nil
}
err = i.ForEach(f)
if err != nil && !IsErrorCode(err, ErrIterOver) {
t.Fatal(err)
}
if len(names) != 1 {
t.Fatalf("expect 1 branch, but it was %d\n", len(names))
}
if names[0] != "master" {
t.Fatalf("expect branch master, but it was %s\n", names[0])
}
}

View File

@ -7,6 +7,7 @@ import "C"
import (
"os"
"runtime"
"unsafe"
)
type CheckoutStrategy uint
@ -19,10 +20,10 @@ const (
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)
CheckoutRemoveIgnored CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_IGNORED // Remove ignored files not in index
CheckotUpdateOnly 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
CheckoutNoRefresh CheckoutStrategy = C.GIT_CHECKOUT_NO_REFRESH // Don't refresh index/config/etc before doing checkout
CheckooutDisablePathspecMatch 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)
CheckoutUserOurs CheckoutStrategy = C.GIT_CHECKOUT_USE_OURS // For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED)
CheckoutUseTheirs CheckoutStrategy = C.GIT_CHECKOUT_USE_THEIRS // For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED)
@ -31,11 +32,25 @@ const (
)
type CheckoutOpts struct {
Strategy CheckoutStrategy // Default will be a dry run
DisableFilters bool // Don't apply filters like CRLF conversion
DirMode os.FileMode // Default is 0755
FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob
FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY
Strategy CheckoutStrategy // Default will be a dry run
DisableFilters bool // Don't apply filters like CRLF conversion
DirMode os.FileMode // Default is 0755
FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob
FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY
TargetDirectory string // Alternative checkout path to workdir
}
func checkoutOptionsFromC(c *C.git_checkout_options) CheckoutOpts {
opts := CheckoutOpts{}
opts.Strategy = CheckoutStrategy(c.checkout_strategy)
opts.DisableFilters = c.disable_filters != 0
opts.DirMode = os.FileMode(c.dir_mode)
opts.FileMode = os.FileMode(c.file_mode)
opts.FileOpenFlags = int(c.file_open_flags)
if c.target_directory != nil {
opts.TargetDirectory = C.GoString(c.target_directory)
}
return opts
}
func (opts *CheckoutOpts) toC() *C.git_checkout_options {
@ -60,17 +75,29 @@ func populateCheckoutOpts(ptr *C.git_checkout_options, opts *CheckoutOpts) *C.gi
ptr.disable_filters = cbool(opts.DisableFilters)
ptr.dir_mode = C.uint(opts.DirMode.Perm())
ptr.file_mode = C.uint(opts.FileMode.Perm())
if opts.TargetDirectory != "" {
ptr.target_directory = C.CString(opts.TargetDirectory)
}
return ptr
}
func freeCheckoutOpts(ptr *C.git_checkout_options) {
if ptr == nil {
return
}
C.free(unsafe.Pointer(ptr.target_directory))
}
// Updates files in the index and the working tree to match the content of
// the commit pointed at by HEAD. opts may be nil.
func (v *Repository) CheckoutHead(opts *CheckoutOpts) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_checkout_head(v.ptr, opts.toC())
cOpts := opts.toC()
defer freeCheckoutOpts(cOpts)
ret := C.git_checkout_head(v.ptr, cOpts)
if ret < 0 {
return MakeGitError(ret)
}
@ -90,7 +117,10 @@ func (v *Repository) CheckoutIndex(index *Index, opts *CheckoutOpts) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_checkout_index(v.ptr, iptr, opts.toC())
cOpts := opts.toC()
defer freeCheckoutOpts(cOpts)
ret := C.git_checkout_index(v.ptr, iptr, cOpts)
if ret < 0 {
return MakeGitError(ret)
}
@ -102,7 +132,10 @@ func (v *Repository) CheckoutTree(tree *Tree, opts *CheckoutOpts) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_checkout_tree(v.ptr, tree.ptr, opts.toC())
cOpts := opts.toC()
defer freeCheckoutOpts(cOpts)
ret := C.git_checkout_tree(v.ptr, tree.ptr, cOpts)
if ret < 0 {
return MakeGitError(ret)
}

73
cherrypick.go Normal file
View File

@ -0,0 +1,73 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
)
type CherrypickOptions struct {
Version uint
Mainline uint
MergeOpts MergeOptions
CheckoutOpts CheckoutOpts
}
func cherrypickOptionsFromC(c *C.git_cherrypick_options) CherrypickOptions {
opts := CherrypickOptions{
Version: uint(c.version),
Mainline: uint(c.mainline),
MergeOpts: mergeOptionsFromC(&c.merge_opts),
CheckoutOpts: checkoutOptionsFromC(&c.checkout_opts),
}
return opts
}
func (opts *CherrypickOptions) toC() *C.git_cherrypick_options {
if opts == nil {
return nil
}
c := C.git_cherrypick_options{}
c.version = C.uint(opts.Version)
c.mainline = C.uint(opts.Mainline)
c.merge_opts = *opts.MergeOpts.toC()
c.checkout_opts = *opts.CheckoutOpts.toC()
return &c
}
func freeCherrypickOpts(ptr *C.git_cherrypick_options) {
if ptr == nil {
return
}
freeCheckoutOpts(&ptr.checkout_opts)
}
func DefaultCherrypickOptions() (CherrypickOptions, error) {
c := C.git_cherrypick_options{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_cherrypick_init_options(&c, C.GIT_CHERRYPICK_OPTIONS_VERSION)
if ecode < 0 {
return CherrypickOptions{}, MakeGitError(ecode)
}
defer freeCherrypickOpts(&c)
return cherrypickOptionsFromC(&c), nil
}
func (v *Repository) Cherrypick(commit *Commit, opts CherrypickOptions) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cOpts := opts.toC()
defer freeCherrypickOpts(cOpts)
ecode := C.git_cherrypick(v.ptr, commit.cast_ptr, cOpts)
if ecode < 0 {
return MakeGitError(ecode)
}
return nil
}

83
cherrypick_test.go Normal file
View File

@ -0,0 +1,83 @@
package git
import (
"io/ioutil"
"testing"
)
func checkout(t *testing.T, repo *Repository, commit *Commit) {
tree, err := commit.Tree()
if err != nil {
t.Fatal(err)
}
err = repo.CheckoutTree(tree, &CheckoutOpts{Strategy: CheckoutSafe})
if err != nil {
t.Fatal(err)
}
err = repo.SetHeadDetached(commit.Id(), commit.Author(), "checkout")
if err != nil {
t.Fatal(err)
}
}
const content = "Herro, Worrd!"
func readReadme(t *testing.T, repo *Repository) string {
bytes, err := ioutil.ReadFile(pathInRepo(repo, "README"))
if err != nil {
t.Fatal(err)
}
return string(bytes)
}
func TestCherrypick(t *testing.T) {
repo := createTestRepo(t)
c1, _ := seedTestRepo(t, repo)
c2, _ := updateReadme(t, repo, content)
commit1, err := repo.LookupCommit(c1)
if err != nil {
t.Fatal(err)
}
commit2, err := repo.LookupCommit(c2)
if err != nil {
t.Fatal(err)
}
checkout(t, repo, commit1)
if readReadme(t, repo) == content {
t.Fatalf("README has wrong content after checking out initial commit")
}
opts, err := DefaultCherrypickOptions()
if err != nil {
t.Fatal(err)
}
err = repo.Cherrypick(commit2, opts)
if err != nil {
t.Fatal(err)
}
if readReadme(t, repo) != content {
t.Fatalf("README has wrong contents after cherry-picking")
}
state := repo.State()
if state != RepositoryStateCherrypick {
t.Fatal("Incorrect repository state: ", state)
}
err = repo.StateCleanup()
if err != nil {
t.Fatal(err)
}
state = repo.State()
if state != RepositoryStateNone {
t.Fatal("Incorrect repository state: ", state)
}
}

View File

@ -30,6 +30,7 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error)
var copts C.git_clone_options
populateCloneOptions(&copts, options)
defer freeCheckoutOpts(&copts.checkout_opts)
if len(options.CheckoutBranch) != 0 {
copts.checkout_branch = C.CString(options.CheckoutBranch)

View File

@ -9,8 +9,6 @@ import "C"
import (
"runtime"
"time"
"unsafe"
)
// Commit
@ -23,6 +21,10 @@ func (c Commit) Message() string {
return C.GoString(C.git_commit_message(c.cast_ptr))
}
func (c Commit) Summary() string {
return C.GoString(C.git_commit_summary(c.cast_ptr))
}
func (c Commit) Tree() (*Tree, error) {
var ptr *C.git_tree
@ -68,49 +70,3 @@ func (c *Commit) ParentId(n uint) *Oid {
func (c *Commit) ParentCount() uint {
return uint(C.git_commit_parentcount(c.cast_ptr))
}
// Signature
type Signature struct {
Name string
Email string
When time.Time
}
func newSignatureFromC(sig *C.git_signature) *Signature {
// git stores minutes, go wants seconds
loc := time.FixedZone("", int(sig.when.offset)*60)
return &Signature{
C.GoString(sig.name),
C.GoString(sig.email),
time.Unix(int64(sig.when.time), 0).In(loc),
}
}
// the offset in mintes, which is what git wants
func (v *Signature) Offset() int {
_, offset := v.When.Zone()
return offset / 60
}
func (sig *Signature) toC() *C.git_signature {
if sig == nil {
return nil
}
var out *C.git_signature
name := C.CString(sig.Name)
defer C.free(unsafe.Pointer(name))
email := C.CString(sig.Email)
defer C.free(unsafe.Pointer(email))
ret := C.git_signature_new(&out, name, email, C.git_time_t(sig.When.Unix()), C.int(sig.Offset()))
if ret < 0 {
return nil
}
return out
}

View File

@ -35,14 +35,14 @@ const (
)
type ConfigEntry struct {
Name string
Name string
Value string
Level ConfigLevel
}
func newConfigEntryFromC(centry *C.git_config_entry) *ConfigEntry {
return &ConfigEntry{
Name: C.GoString(centry.name),
Name: C.GoString(centry.name),
Value: C.GoString(centry.value),
Level: ConfigLevel(centry.level),
}
@ -74,7 +74,6 @@ func (c *Config) AddFile(path string, level ConfigLevel, force bool) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_add_file_ondisk(c.ptr, cpath, C.git_config_level_t(level), cbool(force))
if ret < 0 {
return MakeGitError(ret)
@ -130,7 +129,6 @@ func (c *Config) LookupString(name string) (string, error) {
return C.GoString(ptr), nil
}
func (c *Config) LookupBool(name string) (bool, error) {
var out C.int
cname := C.CString(name)
@ -234,7 +232,6 @@ func (c *Config) SetInt32(name string, value int32) (err error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@ -368,3 +365,48 @@ func (iter *ConfigIterator) Free() {
runtime.SetFinalizer(iter, nil)
C.free(unsafe.Pointer(iter.ptr))
}
func ConfigFindGlobal() (string, error) {
var buf C.git_buf
defer C.git_buf_free(&buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_find_global(&buf)
if ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(buf.ptr), nil
}
func ConfigFindSystem() (string, error) {
var buf C.git_buf
defer C.git_buf_free(&buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_find_system(&buf)
if ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(buf.ptr), nil
}
func ConfigFindXDG() (string, error) {
var buf C.git_buf
defer C.git_buf_free(&buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_find_xdg(&buf)
if ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(buf.ptr), nil
}

View File

@ -57,6 +57,10 @@ func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) {
return commitId, treeId
}
func pathInRepo(repo *Repository, name string) string {
return path.Join(path.Dir(path.Dir(repo.Path())), name)
}
func updateReadme(t *testing.T, repo *Repository, content string) (*Oid, *Oid) {
loc, err := time.LoadLocation("Europe/Berlin")
checkFatal(t, err)
@ -67,7 +71,7 @@ func updateReadme(t *testing.T, repo *Repository, content string) (*Oid, *Oid) {
}
tmpfile := "README"
err = ioutil.WriteFile(path.Join(path.Dir(path.Dir(repo.Path())), tmpfile), []byte(content), 0644)
err = ioutil.WriteFile(pathInRepo(repo, tmpfile), []byte(content), 0644)
checkFatal(t, err)
idx, err := repo.Index()

36
graph.go Normal file
View File

@ -0,0 +1,36 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
)
func (repo *Repository) DescendantOf(commit, ancestor *Oid) (bool, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_graph_descendant_of(repo.ptr, commit.toC(), ancestor.toC())
if ret < 0 {
return false, MakeGitError(ret)
}
return (ret > 0), nil
}
func (repo *Repository) AheadBehind(local, upstream *Oid) (ahead, behind int, err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var aheadT C.size_t
var behindT C.size_t
ret := C.git_graph_ahead_behind(&aheadT, &behindT, repo.ptr, local.toC(), upstream.toC())
if ret < 0 {
return 0, 0, MakeGitError(ret)
}
return int(aheadT), int(behindT), nil
}

View File

@ -145,6 +145,7 @@ func (r *Repository) Merge(theirHeads []*AnnotatedCommit, mergeOptions *MergeOpt
cMergeOpts := mergeOptions.toC()
cCheckoutOpts := checkoutOptions.toC()
defer freeCheckoutOpts(cCheckoutOpts)
gmerge_head_array := make([]*C.git_annotated_commit, len(theirHeads))
for i := 0; i < len(theirHeads); i++ {

View File

@ -25,12 +25,12 @@ type Object interface {
}
type gitObject struct {
ptr *C.git_object
ptr *C.git_object
repo *Repository
}
func (t ObjectType) String() (string) {
switch (t) {
func (t ObjectType) String() string {
switch t {
case ObjectAny:
return "Any"
case ObjectBad:
@ -71,7 +71,7 @@ func (o *gitObject) Free() {
func allocObject(cobj *C.git_object, repo *Repository) Object {
obj := gitObject{
ptr: cobj,
ptr: cobj,
repo: repo,
}

8
odb.go
View File

@ -94,7 +94,7 @@ type OdbForEachCallback func(id *Oid) error
type foreachData struct {
callback OdbForEachCallback
err error
err error
}
//export odbForEachCb
@ -111,9 +111,9 @@ func odbForEachCb(id *C.git_oid, payload unsafe.Pointer) int {
}
func (v *Odb) ForEach(callback OdbForEachCallback) error {
data := foreachData {
data := foreachData{
callback: callback,
err: nil,
err: nil,
}
runtime.LockOSThread()
@ -138,7 +138,7 @@ func (v *Odb) Hash(data []byte, otype ObjectType) (oid *Oid, err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_hash(oid.toC(), ptr, C.size_t(header.Len), C.git_otype(otype));
ret := C.git_odb_hash(oid.toC(), ptr, C.size_t(header.Len), C.git_otype(otype))
if ret < 0 {
return nil, MakeGitError(ret)
}

View File

@ -1,9 +1,9 @@
package git
import (
"errors"
"io"
"os"
"errors"
"testing"
)
@ -37,7 +37,7 @@ func TestOdbStream(t *testing.T) {
func TestOdbHash(t *testing.T) {
repo := createTestRepo(t)
repo := createTestRepo(t)
defer os.RemoveAll(repo.Workdir())
_, _ = seedTestRepo(t, repo)

View File

@ -128,7 +128,7 @@ func packbuilderForEachCb(buf unsafe.Pointer, size C.size_t, payload unsafe.Poin
func (pb *Packbuilder) ForEach(callback PackbuilderForeachCallback) error {
data := packbuilderCbData{
callback: callback,
err: nil,
err: nil,
}
runtime.LockOSThread()

View File

@ -20,7 +20,11 @@ func TestPatch(t *testing.T) {
newTree, err := repo.LookupTree(newTreeId)
checkFatal(t, err)
diff, err := repo.DiffTreeToTree(originalTree, newTree, nil)
opts := &DiffOptions{
OldPrefix: "a",
NewPrefix: "b",
}
diff, err := repo.DiffTreeToTree(originalTree, newTree, opts)
checkFatal(t, err)
patch, err := diff.Patch(0)

View File

@ -36,7 +36,10 @@ func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
csig := sig.toC()
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.free(unsafe.Pointer(csig))
var cmsg *C.char
@ -61,7 +64,10 @@ func (v *Reference) SetTarget(target *Oid, sig *Signature, msg string) (*Referen
runtime.LockOSThread()
defer runtime.UnlockOSThread()
csig := sig.toC()
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.free(unsafe.Pointer(csig))
var cmsg *C.char
@ -99,7 +105,10 @@ func (v *Reference) Rename(name string, force bool, sig *Signature, msg string)
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
csig := sig.toC()
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.free(unsafe.Pointer(csig))
var cmsg *C.char

View File

@ -604,7 +604,10 @@ func (o *Remote) Fetch(refspecs []string, sig *Signature, msg string) error {
var csig *C.git_signature = nil
if sig != nil {
csig = sig.toC()
csig, err := sig.toC()
if err != nil {
return err
}
defer C.free(unsafe.Pointer(csig))
}
@ -696,7 +699,10 @@ func (o *Remote) Ls(filterRefs ...string) ([]RemoteHead, error) {
func (o *Remote) Push(refspecs []string, opts *PushOptions, sig *Signature, msg string) error {
var csig *C.git_signature = nil
if sig != nil {
csig = sig.toC()
csig, err := sig.toC()
if err != nil {
return err
}
defer C.free(unsafe.Pointer(csig))
}

View File

@ -210,7 +210,10 @@ func (v *Repository) SetHead(refname string, sig *Signature, msg string) error {
cname := C.CString(refname)
defer C.free(unsafe.Pointer(cname))
csig := sig.toC()
csig, err := sig.toC()
if err != nil {
return err
}
defer C.free(unsafe.Pointer(csig))
var cmsg *C.char
@ -230,7 +233,10 @@ func (v *Repository) SetHead(refname string, sig *Signature, msg string) error {
}
func (v *Repository) SetHeadDetached(id *Oid, sig *Signature, msg string) error {
csig := sig.toC()
csig, err := sig.toC()
if err != nil {
return err
}
defer C.free(unsafe.Pointer(csig))
var cmsg *C.char
@ -253,7 +259,10 @@ func (v *Repository) CreateReference(name string, id *Oid, force bool, sig *Sign
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
csig := sig.toC()
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.free(unsafe.Pointer(csig))
var cmsg *C.char
@ -284,7 +293,10 @@ func (v *Repository) CreateSymbolicReference(name, target string, force bool, si
ctarget := C.CString(target)
defer C.free(unsafe.Pointer(ctarget))
csig := sig.toC()
csig, err := sig.toC()
if err != nil {
return nil, err
}
defer C.free(unsafe.Pointer(csig))
var cmsg *C.char
@ -352,10 +364,16 @@ func (v *Repository) CreateCommit(
parentsarg = &cparents[0]
}
authorSig := author.toC()
authorSig, err := author.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(authorSig)
committerSig := committer.toC()
committerSig, err := committer.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(committerSig)
runtime.LockOSThread()
@ -384,7 +402,10 @@ func (v *Repository) CreateTag(
cmessage := C.CString(message)
defer C.free(unsafe.Pointer(cmessage))
taggerSig := tagger.toC()
taggerSig, err := tagger.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(taggerSig)
ctarget := commit.gitObject.ptr
@ -546,10 +567,16 @@ func (v *Repository) CreateNote(
defer C.free(unsafe.Pointer(cref))
}
authorSig := author.toC()
authorSig, err := author.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(authorSig)
committerSig := committer.toC()
committerSig, err := committer.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(committerSig)
cnote := C.CString(note)
@ -601,10 +628,16 @@ func (v *Repository) RemoveNote(ref string, author, committer *Signature, id *Oi
defer C.free(unsafe.Pointer(cref))
}
authorSig := author.toC()
authorSig, err := author.toC()
if err != nil {
return err
}
defer C.git_signature_free(authorSig)
committerSig := committer.toC()
committerSig, err := committer.toC()
if err != nil {
return err
}
defer C.git_signature_free(committerSig)
runtime.LockOSThread()
@ -630,3 +663,36 @@ func (v *Repository) DefaultNoteRef() (string, error) {
return C.GoString(ptr), nil
}
type RepositoryState int
const (
RepositoryStateNone RepositoryState = C.GIT_REPOSITORY_STATE_NONE
RepositoryStateMerge RepositoryState = C.GIT_REPOSITORY_STATE_MERGE
RepositoryStateRevert RepositoryState = C.GIT_REPOSITORY_STATE_REVERT
RepositoryStateCherrypick RepositoryState = C.GIT_REPOSITORY_STATE_CHERRYPICK
RepositoryStateBisect RepositoryState = C.GIT_REPOSITORY_STATE_BISECT
RepositoryStateRebase RepositoryState = C.GIT_REPOSITORY_STATE_REBASE
RepositoryStateRebaseInteractive RepositoryState = C.GIT_REPOSITORY_STATE_REBASE_INTERACTIVE
RepositoryStateRebaseMerge RepositoryState = C.GIT_REPOSITORY_STATE_REBASE_MERGE
RepositoryStateApplyMailbox RepositoryState = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX
RepositoryStateApplyMailboxOrRebase RepositoryState = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE
)
func (r *Repository) State() RepositoryState {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
return RepositoryState(C.git_repository_state(r.ptr))
}
func (r *Repository) StateCleanup() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cErr := C.git_repository_state_cleanup(r.ptr)
if cErr < 0 {
return MakeGitError(cErr)
}
return nil
}

73
signature.go Normal file
View File

@ -0,0 +1,73 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
"time"
"unsafe"
)
type Signature struct {
Name string
Email string
When time.Time
}
func newSignatureFromC(sig *C.git_signature) *Signature {
// git stores minutes, go wants seconds
loc := time.FixedZone("", int(sig.when.offset)*60)
return &Signature{
C.GoString(sig.name),
C.GoString(sig.email),
time.Unix(int64(sig.when.time), 0).In(loc),
}
}
// the offset in mintes, which is what git wants
func (v *Signature) Offset() int {
_, offset := v.When.Zone()
return offset / 60
}
func (sig *Signature) toC() (*C.git_signature, error) {
if sig == nil {
return nil, nil
}
var out *C.git_signature
runtime.LockOSThread()
defer runtime.UnlockOSThread()
name := C.CString(sig.Name)
defer C.free(unsafe.Pointer(name))
email := C.CString(sig.Email)
defer C.free(unsafe.Pointer(email))
ret := C.git_signature_new(&out, name, email, C.git_time_t(sig.When.Unix()), C.int(sig.Offset()))
if ret < 0 {
return nil, MakeGitError(ret)
}
return out, nil
}
func (repo *Repository) DefaultSignature() (*Signature, error) {
var out *C.git_signature
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cErr := C.git_signature_default(&out, repo.ptr)
if cErr < 0 {
return nil, MakeGitError(cErr)
}
defer C.git_signature_free(out)
return newSignatureFromC(out), nil
}

View File

@ -45,7 +45,7 @@ func statusEntryFromC(statusEntry *C.git_status_entry) StatusEntry {
indexToWorkdir = diffDeltaFromC(statusEntry.index_to_workdir)
}
return StatusEntry {
return StatusEntry{
Status: Status(statusEntry.status),
HeadToIndex: headToIndex,
IndexToWorkdir: indexToWorkdir,
@ -96,20 +96,20 @@ func (statusList *StatusList) EntryCount() (int, error) {
type StatusOpt int
const (
StatusOptIncludeUntracked StatusOpt = C.GIT_STATUS_OPT_INCLUDE_UNTRACKED
StatusOptIncludeIgnored StatusOpt = C.GIT_STATUS_OPT_INCLUDE_IGNORED
StatusOptIncludeUnmodified StatusOpt = C.GIT_STATUS_OPT_INCLUDE_UNMODIFIED
StatusOptExcludeSubmodules StatusOpt = C.GIT_STATUS_OPT_EXCLUDE_SUBMODULES
StatusOptRecurseUntrackedDirs StatusOpt = C.GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS
StatusOptDisablePathspecMatch StatusOpt = C.GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH
StatusOptRecurseIgnoredDirs StatusOpt = C.GIT_STATUS_OPT_RECURSE_IGNORED_DIRS
StatusOptRenamesHeadToIndex StatusOpt = C.GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
StatusOptRenamesIndexToWorkdir StatusOpt = C.GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR
StatusOptSortCaseSensitively StatusOpt = C.GIT_STATUS_OPT_SORT_CASE_SENSITIVELY
StatusOptSortCaseInsensitively StatusOpt = C.GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY
StatusOptRenamesFromRewrites StatusOpt = C.GIT_STATUS_OPT_RENAMES_FROM_REWRITES
StatusOptNoRefresh StatusOpt = C.GIT_STATUS_OPT_NO_REFRESH
StatusOptUpdateIndex StatusOpt = C.GIT_STATUS_OPT_UPDATE_INDEX
StatusOptIncludeUntracked StatusOpt = C.GIT_STATUS_OPT_INCLUDE_UNTRACKED
StatusOptIncludeIgnored StatusOpt = C.GIT_STATUS_OPT_INCLUDE_IGNORED
StatusOptIncludeUnmodified StatusOpt = C.GIT_STATUS_OPT_INCLUDE_UNMODIFIED
StatusOptExcludeSubmodules StatusOpt = C.GIT_STATUS_OPT_EXCLUDE_SUBMODULES
StatusOptRecurseUntrackedDirs StatusOpt = C.GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS
StatusOptDisablePathspecMatch StatusOpt = C.GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH
StatusOptRecurseIgnoredDirs StatusOpt = C.GIT_STATUS_OPT_RECURSE_IGNORED_DIRS
StatusOptRenamesHeadToIndex StatusOpt = C.GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
StatusOptRenamesIndexToWorkdir StatusOpt = C.GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR
StatusOptSortCaseSensitively StatusOpt = C.GIT_STATUS_OPT_SORT_CASE_SENSITIVELY
StatusOptSortCaseInsensitively StatusOpt = C.GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY
StatusOptRenamesFromRewrites StatusOpt = C.GIT_STATUS_OPT_RENAMES_FROM_REWRITES
StatusOptNoRefresh StatusOpt = C.GIT_STATUS_OPT_NO_REFRESH
StatusOptUpdateIndex StatusOpt = C.GIT_STATUS_OPT_UPDATE_INDEX
)
type StatusShow int
@ -173,7 +173,6 @@ func (v *Repository) StatusList(opts *StatusOptions) (*StatusList, error) {
return newStatusListFromC(ptr), nil
}
func (v *Repository) StatusFile(path string) (Status, error) {
var statusFlags C.uint
cPath := C.CString(path)

View File

@ -12,6 +12,11 @@ func TestStatusFile(t *testing.T) {
defer repo.Free()
defer os.RemoveAll(repo.Workdir())
state := repo.State()
if state != RepositoryStateNone {
t.Fatal("Incorrect repository state: ", state)
}
err := ioutil.WriteFile(path.Join(path.Dir(repo.Workdir()), "hello.txt"), []byte("Hello, World"), 0644)
checkFatal(t, err)

View File

@ -97,10 +97,10 @@ func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) {
type SubmoduleCbk func(sub *Submodule, name string) int
//export SubmoduleVisitor
func SubmoduleVisitor(csub unsafe.Pointer, name string, cfct unsafe.Pointer) int {
func SubmoduleVisitor(csub unsafe.Pointer, name *C.char, cfct unsafe.Pointer) C.int {
sub := &Submodule{(*C.git_submodule)(csub)}
fct := *(*SubmoduleCbk)(cfct)
return fct(sub, name)
return (C.int)(fct(sub, C.GoString(name)))
}
func (repo *Repository) ForeachSubmodule(cbk SubmoduleCbk) error {
@ -318,7 +318,10 @@ func (repo *Repository) ReloadAllSubmodules(force bool) error {
func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error {
var copts C.git_submodule_update_options
populateSubmoduleUpdateOptions(&copts, opts)
err := populateSubmoduleUpdateOptions(&copts, opts)
if err != nil {
return err
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@ -331,15 +334,22 @@ func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error {
return nil
}
func populateSubmoduleUpdateOptions(ptr *C.git_submodule_update_options, opts *SubmoduleUpdateOptions) {
func populateSubmoduleUpdateOptions(ptr *C.git_submodule_update_options, opts *SubmoduleUpdateOptions) error {
C.git_submodule_update_init_options(ptr, C.GIT_SUBMODULE_UPDATE_OPTIONS_VERSION)
if opts == nil {
return
return nil
}
populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts)
populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks)
ptr.clone_checkout_strategy = C.uint(opts.CloneCheckoutStrategy)
ptr.signature = opts.Signature.toC()
sig, err := opts.Signature.toC()
if err != nil {
return err
}
ptr.signature = sig
return nil
}

24
submodule_test.go Normal file
View File

@ -0,0 +1,24 @@
package git
import (
"testing"
)
func TestSubmoduleForeach(t *testing.T) {
repo := createTestRepo(t)
seedTestRepo(t, repo)
_, err := repo.AddSubmodule("http://example.org/submodule", "submodule", true)
checkFatal(t, err)
i := 0
err = repo.ForeachSubmodule(func(sub *Submodule, name string) int {
i++
return 0
})
checkFatal(t, err)
if i != 1 {
t.Fatalf("expected one submodule found but got %i", i)
}
}

View File

@ -2,8 +2,8 @@ package git
import (
"os"
"time"
"testing"
"time"
)
func TestCreateTag(t *testing.T) {