diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2a03529 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - 1.0 + - 1.1 + - tip + +install: + - script/build-libgit2.sh + - export PKG_CONFIG_PATH=$PWD/libgit2/install/lib/pkgconfig + - export LD_LIBRARY_PATH=$PWD/libgit2/install/lib diff --git a/blob.go b/blob.go index b638c4f..4277127 100644 --- a/blob.go +++ b/blob.go @@ -3,23 +3,85 @@ package git /* #include #include +#include + +extern int _go_git_blob_create_fromchunks(git_oid *id, + git_repository *repo, + const char *hintpath, + void *payload); + */ import "C" import ( + "io" + "runtime" "unsafe" ) type Blob struct { gitObject + cast_ptr *C.git_blob } -func (v Blob) Size() int64 { - return int64(C.git_blob_rawsize(v.ptr)) +func (v *Blob) Size() int64 { + return int64(C.git_blob_rawsize(v.cast_ptr)) } -func (v Blob) Contents() []byte { - size := C.int(C.git_blob_rawsize(v.ptr)) - buffer := unsafe.Pointer(C.git_blob_rawcontent(v.ptr)) +func (v *Blob) Contents() []byte { + size := C.int(C.git_blob_rawsize(v.cast_ptr)) + buffer := unsafe.Pointer(C.git_blob_rawcontent(v.cast_ptr)) return C.GoBytes(buffer, size) } +func (repo *Repository) CreateBlobFromBuffer(data []byte) (*Oid, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + oid := C.git_oid{} + ecode := C.git_blob_create_frombuffer(&oid, repo.ptr, unsafe.Pointer(&data[0]), C.size_t(len(data))) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + return newOidFromC(&oid), nil +} + +type BlobChunkCallback func(maxLen int) ([]byte, error) + +type BlobCallbackData struct { + Callback BlobChunkCallback + Error error +} + +//export blobChunkCb +func blobChunkCb(buffer *C.char, maxLen C.size_t, payload unsafe.Pointer) int { + data := (*BlobCallbackData)(payload) + goBuf, err := data.Callback(int(maxLen)) + if err == io.EOF { + return 0 + } else if err != nil { + data.Error = err + return -1 + } + C.memcpy(unsafe.Pointer(buffer), unsafe.Pointer(&goBuf[0]), C.size_t(len(goBuf))) + return len(goBuf) +} + +func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunkCallback) (*Oid, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var chintPath *C.char = nil + if len(hintPath) > 0 { + C.CString(hintPath) + defer C.free(unsafe.Pointer(chintPath)) + } + oid := C.git_oid{} + payload := &BlobCallbackData{Callback: callback} + ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, unsafe.Pointer(payload)) + if payload.Error != nil { + return nil, payload.Error + } + if ecode < 0 { + return nil, MakeGitError(ecode) + } + return newOidFromC(&oid), nil +} diff --git a/branch.go b/branch.go new file mode 100644 index 0000000..bd7312b --- /dev/null +++ b/branch.go @@ -0,0 +1,193 @@ +package git + +/* +#cgo pkg-config: libgit2 +#include +#include +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +type BranchType uint + +const ( + BranchLocal BranchType = C.GIT_BRANCH_LOCAL + BranchRemote = C.GIT_BRANCH_REMOTE +) + +type Branch struct { + Reference +} + +func (repo *Repository) CreateBranch(branchName string, target *Commit, force bool, signature *Signature, msg string) (*Reference, error) { + + ref := new(Reference) + cBranchName := C.CString(branchName) + cForce := cbool(force) + + cSignature := signature.toC() + 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() + defer runtime.UnlockOSThread() + + ret := C.git_branch_create(&ref.ptr, repo.ptr, cBranchName, target.cast_ptr, cForce, cSignature, cmsg) + if ret < 0 { + return nil, MakeGitError(ret) + } + return ref, nil +} + +func (b *Branch) Delete() error { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_branch_delete(b.ptr) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (b *Branch) Move(newBranchName string, force bool, signature *Signature, msg string) (*Branch, error) { + newBranch := new(Branch) + cNewBranchName := C.CString(newBranchName) + cForce := cbool(force) + + cSignature := signature.toC() + 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() + defer runtime.UnlockOSThread() + + ret := C.git_branch_move(&newBranch.ptr, b.ptr, cNewBranchName, cForce, cSignature, cmsg) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newBranch, nil +} + +func (b *Branch) IsHead() (bool, error) { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_is_head(b.ptr) + switch ret { + case 1: + return true, nil + case 0: + return false, nil + } + return false, MakeGitError(ret) + +} + +func (repo *Repository) LookupBranch(branchName string, bt BranchType) (*Branch, error) { + branch := new(Branch) + cName := C.CString(branchName) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_lookup(&branch.ptr, repo.ptr, cName, C.git_branch_t(bt)) + if ret < 0 { + return nil, MakeGitError(ret) + } + return branch, nil +} + +func (b *Branch) Name() (string, error) { + var cName *C.char + defer C.free(unsafe.Pointer(cName)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_name(&cName, b.ptr) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(cName), nil +} + +func (repo *Repository) RemoteName(canonicalBranchName string) (string, error) { + cName := C.CString(canonicalBranchName) + + nameBuf := C.git_buf{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_remote_name(&nameBuf, repo.ptr, cName) + if ret < 0 { + return "", MakeGitError(ret) + } + defer C.git_buf_free(&nameBuf) + + return C.GoString(nameBuf.ptr), nil +} + +func (b *Branch) SetUpstream(upstreamName string) error { + cName := C.CString(upstreamName) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_set_upstream(b.ptr, cName) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (b *Branch) Upstream() (*Branch, error) { + upstream := new(Branch) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_upstream(&upstream.ptr, b.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + return upstream, nil +} + +func (repo *Repository) UpstreamName(canonicalBranchName string) (string, error) { + cName := C.CString(canonicalBranchName) + + nameBuf := C.git_buf{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_branch_upstream_name(&nameBuf, repo.ptr, cName) + if ret < 0 { + return "", MakeGitError(ret) + } + defer C.git_buf_free(&nameBuf) + + return C.GoString(nameBuf.ptr), nil +} diff --git a/checkout.go b/checkout.go index f4c1d4e..5b72b9a 100644 --- a/checkout.go +++ b/checkout.go @@ -2,10 +2,6 @@ package git /* #include -git_checkout_opts git_checkout_opts_init() { - git_checkout_opts ret = GIT_CHECKOUT_OPTS_INIT; - return ret; -} */ import "C" import ( @@ -42,46 +38,59 @@ type CheckoutOpts struct { FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY } -// Convert the CheckoutOpts struct to the corresponding C-struct -func populateCheckoutOpts(ptr *C.git_checkout_opts, opts *CheckoutOpts) { - *ptr = C.git_checkout_opts_init() +// Convert the CheckoutOpts struct to the corresponding +// C-struct. Returns a pointer to ptr, or nil if opts is nil, in order +// to help with what to pass. +func populateCheckoutOpts(ptr *C.git_checkout_options, opts *CheckoutOpts) *C.git_checkout_options { if opts == nil { - return + return nil } + + C.git_checkout_init_opts(ptr, 1) ptr.checkout_strategy = C.uint(opts.Strategy) ptr.disable_filters = cbool(opts.DisableFilters) ptr.dir_mode = C.uint(opts.DirMode.Perm()) ptr.file_mode = C.uint(opts.FileMode.Perm()) + + return ptr } // Updates files in the index and the working tree to match the content of -// the commit pointed at by HEAD. -func (v *Repository) Checkout(opts *CheckoutOpts) error { - var copts C.git_checkout_opts - populateCheckoutOpts(&copts, opts) +// the commit pointed at by HEAD. opts may be nil. +func (v *Repository) CheckoutHead(opts *CheckoutOpts) error { + var copts C.git_checkout_options + + ptr := populateCheckoutOpts(&copts, opts) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_checkout_head(v.ptr, &copts) + ret := C.git_checkout_head(v.ptr, ptr) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } -// Updates files in the working tree to match the content of the index. +// Updates files in the working tree to match the content of the given +// index. If index is nil, the repository's index will be used. opts +// may be nil. func (v *Repository) CheckoutIndex(index *Index, opts *CheckoutOpts) error { - var copts C.git_checkout_opts - populateCheckoutOpts(&copts, opts) + var copts C.git_checkout_options + ptr := populateCheckoutOpts(&copts, opts) + + var iptr *C.git_index = nil + if index != nil { + iptr = index.ptr + } runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_checkout_index(v.ptr, index.ptr, &copts) + ret := C.git_checkout_index(v.ptr, iptr, ptr) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil diff --git a/clone.go b/clone.go new file mode 100644 index 0000000..1bc3261 --- /dev/null +++ b/clone.go @@ -0,0 +1,75 @@ +package git + +/* +#include +#include + +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type CloneOptions struct { + *CheckoutOpts + *RemoteCallbacks + Bare bool + IgnoreCertErrors bool + RemoteName string + CheckoutBranch string +} + +func Clone(url string, path string, options *CloneOptions) (*Repository, error) { + repo := new(Repository) + + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + var copts C.git_clone_options + populateCloneOptions(&copts, options) + + // finish populating clone options here so we can defer CString free + if len(options.RemoteName) != 0 { + copts.remote_name = C.CString(options.RemoteName) + defer C.free(unsafe.Pointer(copts.remote_name)) + } + + if len(options.CheckoutBranch) != 0 { + copts.checkout_branch = C.CString(options.CheckoutBranch) + defer C.free(unsafe.Pointer(copts.checkout_branch)) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_clone(&repo.ptr, curl, cpath, &copts) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(repo, (*Repository).Free) + return repo, nil +} + +func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) { + C.git_clone_init_options(ptr, C.GIT_CLONE_OPTIONS_VERSION) + + if opts == nil { + return + } + populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts) + populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks) + if opts.Bare { + ptr.bare = 1 + } else { + ptr.bare = 0 + } + if opts.IgnoreCertErrors { + ptr.ignore_cert_errors = 1 + } else { + ptr.ignore_cert_errors = 0 + } +} diff --git a/clone_test.go b/clone_test.go new file mode 100644 index 0000000..97366bf --- /dev/null +++ b/clone_test.go @@ -0,0 +1,23 @@ +package git + +import ( + "io/ioutil" + "os" + "testing" +) + +func TestClone(t *testing.T) { + + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + seedTestRepo(t, repo) + + path, err := ioutil.TempDir("", "git2go") + checkFatal(t, err) + + _, err = Clone(repo.Path(), path, &CloneOptions{Bare: true}) + defer os.RemoveAll(path) + + checkFatal(t, err) +} diff --git a/commit.go b/commit.go index 0c64c76..0a5cfce 100644 --- a/commit.go +++ b/commit.go @@ -17,56 +17,57 @@ import ( // Commit type Commit struct { gitObject + cast_ptr *C.git_commit } func (c Commit) Message() string { - return C.GoString(C.git_commit_message(c.ptr)) + return C.GoString(C.git_commit_message(c.cast_ptr)) } func (c Commit) Tree() (*Tree, error) { - var ptr *C.git_object + var ptr *C.git_tree runtime.LockOSThread() defer runtime.UnlockOSThread() - err := C.git_commit_tree(&ptr, c.ptr) + err := C.git_commit_tree(&ptr, c.cast_ptr) if err < 0 { - return nil, LastError() + return nil, MakeGitError(err) } - return allocObject(ptr).(*Tree), nil + return allocObject((*C.git_object)(ptr)).(*Tree), nil } func (c Commit) TreeId() *Oid { - return newOidFromC(C.git_commit_tree_id(c.ptr)) + return newOidFromC(C.git_commit_tree_id(c.cast_ptr)) } func (c Commit) Author() *Signature { - ptr := C.git_commit_author(c.ptr) - return newSignatureFromC(ptr) + cast_ptr := C.git_commit_author(c.cast_ptr) + return newSignatureFromC(cast_ptr) } func (c Commit) Committer() *Signature { - ptr := C.git_commit_committer(c.ptr) - return newSignatureFromC(ptr) + cast_ptr := C.git_commit_committer(c.cast_ptr) + return newSignatureFromC(cast_ptr) } func (c *Commit) Parent(n uint) *Commit { - var cobj *C.git_object - ret := C.git_commit_parent(&cobj, c.ptr, C.uint(n)) + var cobj *C.git_commit + ret := C.git_commit_parent(&cobj, c.cast_ptr, C.uint(n)) if ret != 0 { return nil } - return allocObject(cobj).(*Commit) + return allocObject((*C.git_object)(cobj)).(*Commit) } func (c *Commit) ParentId(n uint) *Oid { - return newOidFromC(C.git_commit_parent_id(c.ptr, C.uint(n))) + return newOidFromC(C.git_commit_parent_id(c.cast_ptr, C.uint(n))) } func (c *Commit) ParentCount() uint { - return uint(C.git_commit_parentcount(c.ptr)) + return uint(C.git_commit_parentcount(c.cast_ptr)) } // Signature diff --git a/config.go b/config.go index 2aa073a..6b1f093 100644 --- a/config.go +++ b/config.go @@ -10,11 +10,81 @@ import ( "unsafe" ) +type ConfigLevel int + +const ( + // System-wide configuration file; /etc/gitconfig on Linux systems + ConfigLevelSystem ConfigLevel = C.GIT_CONFIG_LEVEL_SYSTEM + + // XDG compatible configuration file; typically ~/.config/git/config + ConfigLevelXDG ConfigLevel = C.GIT_CONFIG_LEVEL_XDG + + // User-specific configuration file (also called Global configuration + // file); typically ~/.gitconfig + ConfigLevelGlobal ConfigLevel = C.GIT_CONFIG_LEVEL_GLOBAL + + // Repository specific configuration file; $WORK_DIR/.git/config on + // non-bare repos + ConfigLevelLocal ConfigLevel = C.GIT_CONFIG_LEVEL_LOCAL + + // Application specific configuration file; freely defined by applications + ConfigLevelApp ConfigLevel = C.GIT_CONFIG_LEVEL_APP + + // Represents the highest level available config file (i.e. the most + // specific config file available that actually is loaded) + ConfigLevelHighest ConfigLevel = C.GIT_CONFIG_HIGHEST_LEVEL +) + +type ConfigEntry struct { + Name string + Value string + Level ConfigLevel +} + +func newConfigEntryFromC(centry *C.git_config_entry) *ConfigEntry { + return &ConfigEntry{ + Name: C.GoString(centry.name), + Value: C.GoString(centry.value), + Level: ConfigLevel(centry.level), + } +} + type Config struct { ptr *C.git_config } -func (c *Config) LookupInt32(name string) (v int32, err error) { +// NewConfig creates a new empty configuration object +func NewConfig() (*Config, error) { + config := new(Config) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_config_new(&config.ptr); ret < 0 { + return nil, MakeGitError(ret) + } + + return config, nil +} + +// AddFile adds a file-backed backend to the config object at the specified level. +func (c *Config) AddFile(path string, level ConfigLevel, force bool) error { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + 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) + } + + return nil +} + +func (c *Config) LookupInt32(name string) (int32, error) { var out C.int32_t cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) @@ -24,13 +94,13 @@ func (c *Config) LookupInt32(name string) (v int32, err error) { ret := C.git_config_get_int32(&out, c.ptr, cname) if ret < 0 { - return 0, LastError() + return 0, MakeGitError(ret) } return int32(out), nil } -func (c *Config) LookupInt64(name string) (v int64, err error) { +func (c *Config) LookupInt64(name string) (int64, error) { var out C.int64_t cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) @@ -40,13 +110,13 @@ func (c *Config) LookupInt64(name string) (v int64, err error) { ret := C.git_config_get_int64(&out, c.ptr, cname) if ret < 0 { - return 0, LastError() + return 0, MakeGitError(ret) } return int64(out), nil } -func (c *Config) LookupString(name string) (v string, err error) { +func (c *Config) LookupString(name string) (string, error) { var ptr *C.char cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) @@ -54,15 +124,91 @@ func (c *Config) LookupString(name string) (v string, err error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_config_get_string(&ptr, c.ptr, cname) - if ret < 0 { - return "", LastError() + if ret := C.git_config_get_string(&ptr, c.ptr, cname); ret < 0 { + return "", MakeGitError(ret) } return C.GoString(ptr), nil } -func (c *Config) Set(name, value string) (err error) { + +func (c *Config) LookupBool(name string) (bool, error) { + var out C.int + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_get_bool(&out, c.ptr, cname) + if ret < 0 { + return false, MakeGitError(ret) + } + + return out != 0, nil +} + +func (c *Config) NewMultivarIterator(name, regexp string) (*ConfigIterator, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + var cregexp *C.char + if regexp == "" { + cregexp = nil + } else { + cregexp = C.CString(regexp) + defer C.free(unsafe.Pointer(cregexp)) + } + + iter := new(ConfigIterator) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_multivar_iterator_new(&iter.ptr, c.ptr, cname, cregexp) + if ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(iter, (*ConfigIterator).Free) + return iter, nil +} + +// NewIterator creates an iterator over each entry in the +// configuration +func (c *Config) NewIterator() (*ConfigIterator, error) { + iter := new(ConfigIterator) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_iterator_new(&iter.ptr, c.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return iter, nil +} + +// NewIteratorGlob creates an iterator over each entry in the +// configuration whose name matches the given regular expression +func (c *Config) NewIteratorGlob(regexp string) (*ConfigIterator, error) { + iter := new(ConfigIterator) + cregexp := C.CString(regexp) + defer C.free(unsafe.Pointer(cregexp)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_iterator_glob_new(&iter.ptr, c.ptr, cregexp) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return iter, nil +} + +func (c *Config) SetString(name, value string) (err error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) @@ -74,7 +220,7 @@ func (c *Config) Set(name, value string) (err error) { ret := C.git_config_set_string(c.ptr, cname, cvalue) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil @@ -84,3 +230,148 @@ func (c *Config) Free() { runtime.SetFinalizer(c, nil) C.git_config_free(c.ptr) } + +func (c *Config) SetInt32(name string, value int32) (err error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + ret := C.git_config_set_int32(c.ptr, cname, C.int32_t(value)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) SetInt64(name string, value int64) (err error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_set_int64(c.ptr, cname, C.int64_t(value)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) SetBool(name string, value bool) (err error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_set_bool(c.ptr, cname, cbool(value)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) SetMultivar(name, regexp, value string) (err error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + cregexp := C.CString(regexp) + defer C.free(unsafe.Pointer(cregexp)) + + cvalue := C.CString(value) + defer C.free(unsafe.Pointer(cvalue)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_set_multivar(c.ptr, cname, cregexp, cvalue) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func (c *Config) Delete(name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_delete_entry(c.ptr, cname) + + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +// OpenLevel creates a single-level focused config object from a multi-level one +func (c *Config) OpenLevel(parent *Config, level ConfigLevel) (*Config, error) { + config := new(Config) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_open_level(&config.ptr, parent.ptr, C.git_config_level_t(level)) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return config, nil +} + +// OpenOndisk creates a new config instance containing a single on-disk file +func OpenOndisk(parent *Config, path string) (*Config, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + config := new(Config) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_config_open_ondisk(&config.ptr, cpath); ret < 0 { + return nil, MakeGitError(ret) + } + + return config, nil +} + +// Refresh refreshes the configuration to reflect any changes made externally e.g. on disk +func (c *Config) Refresh() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_config_refresh(c.ptr); ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +type ConfigIterator struct { + ptr *C.git_config_iterator +} + +// Next returns the next entry for this iterator +func (iter *ConfigIterator) Next() (*ConfigEntry, error) { + var centry *C.git_config_entry + + ret := C.git_config_next(¢ry, iter.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newConfigEntryFromC(centry), nil +} + +func (iter *ConfigIterator) Free() { + runtime.SetFinalizer(iter, nil) + C.free(unsafe.Pointer(iter.ptr)) +} + diff --git a/credentials.go b/credentials.go new file mode 100644 index 0000000..c5ed055 --- /dev/null +++ b/credentials.go @@ -0,0 +1,74 @@ +package git + +/* +#include +#include +*/ +import "C" +import "unsafe" + +type CredType uint + +const ( + CredTypeUserpassPlaintext CredType = C.GIT_CREDTYPE_USERPASS_PLAINTEXT + CredTypeSshKey = C.GIT_CREDTYPE_SSH_KEY + CredTypeSshCustom = C.GIT_CREDTYPE_SSH_CUSTOM + CredTypeDefault = C.GIT_CREDTYPE_DEFAULT +) + +type Cred struct { + ptr *C.git_cred +} + +func (o *Cred) HasUsername() bool { + if C.git_cred_has_username(o.ptr) == 1 { + return true + } + return false +} + +func (o *Cred) Type() CredType { + return (CredType)(o.ptr.credtype) +} + +func credFromC(ptr *C.git_cred) *Cred { + return &Cred{ptr} +} + +func NewCredUserpassPlaintext(username string, password string) (int, Cred) { + cred := Cred{} + cusername := C.CString(username) + defer C.free(unsafe.Pointer(cusername)) + cpassword := C.CString(password) + defer C.free(unsafe.Pointer(cpassword)) + ret := C.git_cred_userpass_plaintext_new(&cred.ptr, cusername, cpassword) + return int(ret), cred +} + +func NewCredSshKey(username string, publickey string, privatekey string, passphrase string) (int, Cred) { + cred := Cred{} + cusername := C.CString(username) + defer C.free(unsafe.Pointer(cusername)) + cpublickey := C.CString(publickey) + defer C.free(unsafe.Pointer(cpublickey)) + cprivatekey := C.CString(privatekey) + defer C.free(unsafe.Pointer(cprivatekey)) + cpassphrase := C.CString(passphrase) + defer C.free(unsafe.Pointer(cpassphrase)) + ret := C.git_cred_ssh_key_new(&cred.ptr, cusername, cpublickey, cprivatekey, cpassphrase) + return int(ret), cred +} + +func NewCredSshKeyFromAgent(username string) (int, Cred) { + cred := Cred{} + cusername := C.CString(username) + defer C.free(unsafe.Pointer(cusername)) + ret := C.git_cred_ssh_key_from_agent(&cred.ptr, cusername) + return int(ret), cred +} + +func NewCredDefault() (int, Cred) { + cred := Cred{} + ret := C.git_cred_default_new(&cred.ptr) + return int(ret), cred +} diff --git a/git.go b/git.go index 72ea33e..8159244 100644 --- a/git.go +++ b/git.go @@ -8,10 +8,11 @@ package git import "C" import ( "bytes" + "encoding/hex" "errors" "runtime" - "unsafe" "strings" + "unsafe" ) const ( @@ -28,10 +29,8 @@ func init() { C.git_threads_init() } -// Oid -type Oid struct { - bytes [20]byte -} +// Oid represents the id for a Git object. +type Oid [20]byte func newOidFromC(coid *C.git_oid) *Oid { if coid == nil { @@ -39,62 +38,57 @@ func newOidFromC(coid *C.git_oid) *Oid { } oid := new(Oid) - copy(oid.bytes[0:20], C.GoBytes(unsafe.Pointer(coid), 20)) + copy(oid[0:20], C.GoBytes(unsafe.Pointer(coid), 20)) return oid } -func NewOid(b []byte) *Oid { +func NewOidFromBytes(b []byte) *Oid { oid := new(Oid) - copy(oid.bytes[0:20], b[0:20]) + copy(oid[0:20], b[0:20]) return oid } func (oid *Oid) toC() *C.git_oid { - return (*C.git_oid)(unsafe.Pointer(&oid.bytes)) + return (*C.git_oid)(unsafe.Pointer(oid)) } -func NewOidFromString(s string) (*Oid, error) { - o := new(Oid) - cs := C.CString(s) - defer C.free(unsafe.Pointer(cs)) - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - if C.git_oid_fromstr(o.toC(), cs) < 0 { - return nil, LastError() +func NewOid(s string) (*Oid, error) { + if len(s) > C.GIT_OID_HEXSZ { + return nil, errors.New("string is too long for oid") } + o := new(Oid) + + slice, error := hex.DecodeString(s) + if error != nil { + return nil, error + } + + copy(o[:], slice[:20]) return o, nil } func (oid *Oid) String() string { - buf := make([]byte, 40) - C.git_oid_fmt((*C.char)(unsafe.Pointer(&buf[0])), oid.toC()) - return string(buf) -} - -func (oid *Oid) Bytes() []byte { - return oid.bytes[0:] + return hex.EncodeToString(oid[:]) } func (oid *Oid) Cmp(oid2 *Oid) int { - return bytes.Compare(oid.bytes[:], oid2.bytes[:]) + return bytes.Compare(oid[:], oid2[:]) } func (oid *Oid) Copy() *Oid { ret := new(Oid) - copy(ret.bytes[:], oid.bytes[:]) + copy(ret[:], oid[:]) return ret } func (oid *Oid) Equal(oid2 *Oid) bool { - return bytes.Equal(oid.bytes[:], oid2.bytes[:]) + return bytes.Equal(oid[:], oid2[:]) } func (oid *Oid) IsZero() bool { - for _, a := range oid.bytes { - if a != '0' { + for _, a := range oid { + if a != 0 { return false } } @@ -102,7 +96,7 @@ func (oid *Oid) IsZero() bool { } func (oid *Oid) NCmp(oid2 *Oid, n uint) int { - return bytes.Compare(oid.bytes[:n], oid2.bytes[:n]) + return bytes.Compare(oid[:n], oid2[:n]) } func ShortenOids(ids []*Oid, minlen int) (int, error) { @@ -123,27 +117,40 @@ func ShortenOids(ids []*Oid, minlen int) (int, error) { buf[40] = 0 ret = C.git_oid_shorten_add(shorten, (*C.char)(unsafe.Pointer(&buf[0]))) if ret < 0 { - return int(ret), LastError() + return int(ret), MakeGitError(ret) } } return int(ret), nil } type GitError struct { - Message string - Code int + Message string + Class int + ErrorCode int } func (e GitError) Error() string { return e.Message } -func LastError() error { +func IsNotExist(err error) bool { + return err.(*GitError).ErrorCode == C.GIT_ENOTFOUND +} + +func IsExist(err error) bool { + return err.(*GitError).ErrorCode == C.GIT_EEXISTS +} + +func MakeGitError(errorCode C.int) error { err := C.giterr_last() if err == nil { - return &GitError{"No message", 0} + return &GitError{"No message", C.GITERR_INVALID, C.GIT_ERROR} } - return &GitError{C.GoString(err.message), int(err.klass)} + return &GitError{C.GoString(err.message), int(err.klass), int(errorCode)} +} + +func MakeGitError2(err int) error { + return MakeGitError(C.int(err)) } func cbool(b bool) C.int { @@ -175,7 +182,7 @@ func Discover(start string, across_fs bool, ceiling_dirs []string) (string, erro ret := C.git_repository_discover(&buf, cstart, cbool(across_fs), ceildirs) if ret < 0 { - return "", LastError() + return "", MakeGitError(ret) } return C.GoString(buf.ptr), nil diff --git a/git_test.go b/git_test.go index 52aea1d..6542ca0 100644 --- a/git_test.go +++ b/git_test.go @@ -1,8 +1,8 @@ package git import ( - "testing" "io/ioutil" + "testing" "time" ) @@ -14,7 +14,17 @@ func createTestRepo(t *testing.T) *Repository { checkFatal(t, err) tmpfile := "README" - err = ioutil.WriteFile(path + "/" + tmpfile, []byte("foo\n"), 0644) + err = ioutil.WriteFile(path+"/"+tmpfile, []byte("foo\n"), 0644) + checkFatal(t, err) + + return repo +} + +func createBareTestRepo(t *testing.T) *Repository { + // figure out where we can create the test repo + path, err := ioutil.TempDir("", "git2go") + checkFatal(t, err) + repo, err := InitRepository(path, true) checkFatal(t, err) return repo @@ -45,3 +55,10 @@ func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) { return commitId, treeId } +func TestOidZero(t *testing.T) { + var zeroId Oid + + if !zeroId.IsZero() { + t.Error("Zero Oid is not zero") + } +} diff --git a/index.go b/index.go index fb32010..d3178a2 100644 --- a/index.go +++ b/index.go @@ -6,7 +6,9 @@ package git */ import "C" import ( + "fmt" "runtime" + "time" "unsafe" ) @@ -14,6 +16,50 @@ type Index struct { ptr *C.git_index } +type IndexEntry struct { + Ctime time.Time + Mtime time.Time + Mode uint + Uid uint + Gid uint + Size uint + Id *Oid + Path string +} + +func newIndexEntryFromC(entry *C.git_index_entry) *IndexEntry { + if entry == nil { + return nil + } + return &IndexEntry{ + time.Unix(int64(entry.ctime.seconds), int64(entry.ctime.nanoseconds)), + time.Unix(int64(entry.mtime.seconds), int64(entry.mtime.nanoseconds)), + uint(entry.mode), + uint(entry.uid), + uint(entry.gid), + uint(entry.file_size), + newOidFromC(&entry.id), + C.GoString(entry.path), + } +} + +func populateCIndexEntry(source *IndexEntry, dest *C.git_index_entry) { + dest.ctime.seconds = C.git_time_t(source.Ctime.Unix()) + dest.ctime.nanoseconds = C.uint(source.Ctime.UnixNano()) + dest.mtime.seconds = C.git_time_t(source.Mtime.Unix()) + dest.mtime.nanoseconds = C.uint(source.Mtime.UnixNano()) + dest.mode = C.uint(source.Mode) + dest.uid = C.uint(source.Uid) + dest.gid = C.uint(source.Gid) + dest.file_size = C.git_off_t(source.Size) + dest.id = *source.Id.toC() + dest.path = C.CString(source.Path) +} + +func freeCIndexEntry(entry *C.git_index_entry) { + C.free(unsafe.Pointer(entry.path)) +} + func newIndexFromC(ptr *C.git_index) *Index { idx := &Index{ptr} runtime.SetFinalizer(idx, (*Index).Free) @@ -29,12 +75,26 @@ func (v *Index) AddByPath(path string) error { ret := C.git_index_add_bypath(v.ptr, cstr) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } +func (v *Index) WriteTreeTo(repo *Repository) (*Oid, error) { + oid := new(Oid) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_index_write_tree_to(oid.toC(), v.ptr, repo.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return oid, nil +} + func (v *Index) WriteTree() (*Oid, error) { oid := new(Oid) @@ -43,19 +103,19 @@ func (v *Index) WriteTree() (*Oid, error) { ret := C.git_index_write_tree(oid.toC(), v.ptr) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return oid, nil } -func (v *Index) Write() (error) { +func (v *Index) Write() error { runtime.LockOSThread() defer runtime.UnlockOSThread() ret := C.git_index_write(v.ptr) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil @@ -65,3 +125,157 @@ func (v *Index) Free() { runtime.SetFinalizer(v, nil) C.git_index_free(v.ptr) } + +func (v *Index) EntryCount() uint { + return uint(C.git_index_entrycount(v.ptr)) +} + +func (v *Index) EntryByIndex(index uint) (*IndexEntry, error) { + centry := C.git_index_get_byindex(v.ptr, C.size_t(index)) + if centry == nil { + return nil, fmt.Errorf("Index out of Bounds") + } + return newIndexEntryFromC(centry), nil +} + +func (v *Index) HasConflicts() bool { + return C.git_index_has_conflicts(v.ptr) != 0 +} + +func (v *Index) CleanupConflicts() { + C.git_index_conflict_cleanup(v.ptr) +} + +func (v *Index) AddConflict(ancestor *IndexEntry, our *IndexEntry, their *IndexEntry) error { + + var cancestor *C.git_index_entry + var cour *C.git_index_entry + var ctheir *C.git_index_entry + + if ancestor != nil { + cancestor = &C.git_index_entry{} + populateCIndexEntry(ancestor, cancestor) + defer freeCIndexEntry(cancestor) + } + + if our != nil { + cour = &C.git_index_entry{} + populateCIndexEntry(our, cour) + defer freeCIndexEntry(cour) + } + + if their != nil { + ctheir = &C.git_index_entry{} + populateCIndexEntry(their, ctheir) + defer freeCIndexEntry(ctheir) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_add(v.ptr, cancestor, cour, ctheir) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +type IndexConflict struct { + Ancestor *IndexEntry + Our *IndexEntry + Their *IndexEntry +} + +func (v *Index) GetConflict(path string) (IndexConflict, error) { + + var cancestor *C.git_index_entry + var cour *C.git_index_entry + var ctheir *C.git_index_entry + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_get(&cancestor, &cour, &ctheir, v.ptr, cpath) + if ecode < 0 { + return IndexConflict{}, MakeGitError(ecode) + } + return IndexConflict{ + Ancestor: newIndexEntryFromC(cancestor), + Our: newIndexEntryFromC(cour), + Their: newIndexEntryFromC(ctheir), + }, nil +} + +func (v *Index) RemoveConflict(path string) error { + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_remove(v.ptr, cpath) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +type IndexConflictIterator struct { + ptr *C.git_index_conflict_iterator + index *Index +} + +func newIndexConflictIteratorFromC(index *Index, ptr *C.git_index_conflict_iterator) *IndexConflictIterator { + i := &IndexConflictIterator{ptr: ptr, index: index} + runtime.SetFinalizer(i, (*IndexConflictIterator).Free) + return i +} + +func (v *IndexConflictIterator) Index() *Index { + return v.index +} + +func (v *IndexConflictIterator) Free() { + runtime.SetFinalizer(v, nil) + C.git_index_conflict_iterator_free(v.ptr) +} + +func (v *Index) ConflictIterator() (*IndexConflictIterator, error) { + var i *C.git_index_conflict_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_iterator_new(&i, v.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + return newIndexConflictIteratorFromC(v, i), nil +} + +func (v *IndexConflictIterator) Next() (IndexConflict, error) { + var cancestor *C.git_index_entry + var cour *C.git_index_entry + var ctheir *C.git_index_entry + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_index_conflict_next(&cancestor, &cour, &ctheir, v.ptr) + if ecode == C.GIT_ITEROVER { + return IndexConflict{}, ErrIterOver + } + + if ecode < 0 { + return IndexConflict{}, MakeGitError(ecode) + } + return IndexConflict{ + Ancestor: newIndexEntryFromC(cancestor), + Our: newIndexEntryFromC(cour), + Their: newIndexEntryFromC(ctheir), + }, nil +} diff --git a/index_test.go b/index_test.go index 9828d0f..5920b93 100644 --- a/index_test.go +++ b/index_test.go @@ -22,6 +22,25 @@ func TestCreateRepoAndStage(t *testing.T) { } } +func TestIndexWriteTreeTo(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + repo2 := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + idx, err := repo.Index() + checkFatal(t, err) + err = idx.AddByPath("README") + checkFatal(t, err) + treeId, err := idx.WriteTreeTo(repo2) + checkFatal(t, err) + + if treeId.String() != "b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e" { + t.Fatalf("%v", treeId.String()) + } +} + func checkFatal(t *testing.T, err error) { if err == nil { return diff --git a/object.go b/object.go index 090be1f..101d15e 100644 --- a/object.go +++ b/object.go @@ -48,7 +48,7 @@ func (t ObjectType) String() (string) { } func (o gitObject) Id() *Oid { - return newOidFromC(C.git_commit_id(o.ptr)) + return newOidFromC(C.git_object_id(o.ptr)) } func (o gitObject) Type() ObjectType { @@ -57,24 +57,33 @@ func (o gitObject) Type() ObjectType { func (o *gitObject) Free() { runtime.SetFinalizer(o, nil) - C.git_commit_free(o.ptr) + C.git_object_free(o.ptr) } func allocObject(cobj *C.git_object) Object { switch ObjectType(C.git_object_type(cobj)) { case ObjectCommit: - commit := &Commit{gitObject{cobj}} + commit := &Commit{ + gitObject: gitObject{cobj}, + cast_ptr: (*C.git_commit)(cobj), + } runtime.SetFinalizer(commit, (*Commit).Free) return commit case ObjectTree: - tree := &Tree{gitObject{cobj}} + tree := &Tree{ + gitObject: gitObject{cobj}, + cast_ptr: (*C.git_tree)(cobj), + } runtime.SetFinalizer(tree, (*Tree).Free) return tree case ObjectBlob: - blob := &Blob{gitObject{cobj}} + blob := &Blob{ + gitObject: gitObject{cobj}, + cast_ptr: (*C.git_blob)(cobj), + } runtime.SetFinalizer(blob, (*Blob).Free) return blob } diff --git a/odb.go b/odb.go index 80f8312..3b0c864 100644 --- a/odb.go +++ b/odb.go @@ -63,7 +63,7 @@ func (v *Odb) Write(data []byte, otype ObjectType) (oid *Oid, err error) { ret := C.git_odb_write(oid.toC(), v.ptr, unsafe.Pointer(hdr.Data), C.size_t(hdr.Len), C.git_otype(otype)) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return oid, nil @@ -77,7 +77,7 @@ func (v *Odb) Read(oid *Oid) (obj *OdbObject, err error) { ret := C.git_odb_read(&obj.ptr, v.ptr, oid.toC()) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } runtime.SetFinalizer(obj, (*OdbObject).Free) @@ -121,7 +121,7 @@ func (v *Odb) Hash(data []byte, otype ObjectType) (oid *Oid, err error) { ret := C.git_odb_hash(oid.toC(), ptr, C.size_t(header.Len), C.git_otype(otype)); if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return oid, nil } @@ -132,7 +132,7 @@ func (v *Odb) NewReadStream(id *Oid) (*OdbReadStream, error) { stream := new(OdbReadStream) ret := C.git_odb_open_rstream(&stream.ptr, v.ptr, id.toC()) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } runtime.SetFinalizer(stream, (*OdbReadStream).Free) @@ -146,7 +146,7 @@ func (v *Odb) NewWriteStream(size int, otype ObjectType) (*OdbWriteStream, error stream := new(OdbWriteStream) ret := C.git_odb_open_wstream(&stream.ptr, v.ptr, C.size_t(size), C.git_otype(otype)) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } runtime.SetFinalizer(stream, (*OdbWriteStream).Free) @@ -199,7 +199,7 @@ func (stream *OdbReadStream) Read(data []byte) (int, error) { size := C.size_t(header.Cap) ret := C.git_odb_stream_read(stream.ptr, ptr, size) if ret < 0 { - return 0, LastError() + return 0, MakeGitError(ret) } header.Len = int(ret) @@ -231,7 +231,7 @@ func (stream *OdbWriteStream) Write(data []byte) (int, error) { ret := C.git_odb_stream_write(stream.ptr, ptr, size) if ret < 0 { - return 0, LastError() + return 0, MakeGitError(ret) } return len(data), nil @@ -242,7 +242,7 @@ func (stream *OdbWriteStream) Write(data []byte) (int, error) { func (stream *OdbWriteStream) Close() error { ret := C.git_odb_stream_finalize_write(stream.Id.toC(), stream.ptr) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil diff --git a/odb_test.go b/odb_test.go index a4f8943..17b3ad2 100644 --- a/odb_test.go +++ b/odb_test.go @@ -27,7 +27,7 @@ func TestOdbStream(t *testing.T) { error = stream.Close() checkFatal(t, error) - expectedId, error := NewOidFromString("30f51a3fba5274d53522d0f19748456974647b4f") + expectedId, error := NewOid("30f51a3fba5274d53522d0f19748456974647b4f") checkFatal(t, error) if stream.Id.Cmp(expectedId) != 0 { t.Fatal("Wrong data written") @@ -59,4 +59,4 @@ Initial commit.`; if oid.Cmp(coid) != 0 { t.Fatal("Hash and write Oids are different") } -} \ No newline at end of file +} diff --git a/packbuilder.go b/packbuilder.go index 333f183..70c4530 100644 --- a/packbuilder.go +++ b/packbuilder.go @@ -28,7 +28,7 @@ func (repo *Repository) NewPackbuilder() (*Packbuilder, error) { ret := C.git_packbuilder_new(&builder.ptr, repo.ptr) if ret != 0 { - return nil, LastError() + return nil, MakeGitError(ret) } runtime.SetFinalizer(builder, (*Packbuilder).Free) return builder, nil @@ -48,7 +48,7 @@ func (pb *Packbuilder) Insert(id *Oid, name string) error { ret := C.git_packbuilder_insert(pb.ptr, id.toC(), cname) if ret != 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -59,7 +59,7 @@ func (pb *Packbuilder) InsertCommit(id *Oid) error { ret := C.git_packbuilder_insert_commit(pb.ptr, id.toC()) if ret != 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -70,7 +70,7 @@ func (pb *Packbuilder) InsertTree(id *Oid) error { ret := C.git_packbuilder_insert_tree(pb.ptr, id.toC()) if ret != 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -88,7 +88,7 @@ func (pb *Packbuilder) WriteToFile(name string, mode os.FileMode) error { ret := C.git_packbuilder_write(pb.ptr, cname, C.uint(mode.Perm()), nil, nil) if ret != 0 { - return LastError() + return MakeGitError(ret) } return nil } diff --git a/push.go b/push.go new file mode 100644 index 0000000..5fb7f07 --- /dev/null +++ b/push.go @@ -0,0 +1,182 @@ +package git + +/* +#include +#include + +int _go_git_push_status_foreach(git_push *push, void *data); +int _go_git_push_set_callbacks(git_push *push, void *packbuilder_progress_data, void *transfer_progress_data); + +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type Push struct { + ptr *C.git_push + + packbuilderProgress *PackbuilderProgressCallback + transferProgress *PushTransferProgressCallback +} + +func newPushFromC(cpush *C.git_push) *Push { + p := &Push{ptr: cpush} + runtime.SetFinalizer(p, (*Push).Free) + return p +} + +func (p *Push) Free() { + runtime.SetFinalizer(p, nil) + C.git_push_free(p.ptr) +} + +func (remote *Remote) NewPush() (*Push, error) { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var cpush *C.git_push + ret := C.git_push_new(&cpush, remote.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + return newPushFromC(cpush), nil +} + +func (p *Push) Finish() error { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_push_finish(p.ptr) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (p *Push) UnpackOk() bool { + + ret := C.git_push_unpack_ok(p.ptr) + if ret == 0 { + return false + } + return true + +} + +func (p *Push) UpdateTips(sig *Signature, msg string) error { + + var csig *C.git_signature = nil + if sig != nil { + csig = sig.toC() + defer C.free(unsafe.Pointer(csig)) + } + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_push_update_tips(p.ptr, csig, cmsg) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (p *Push) AddRefspec(refspec string) error { + + crefspec := C.CString(refspec) + defer C.free(unsafe.Pointer(crefspec)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_push_add_refspec(p.ptr, crefspec) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +type PushOptions struct { + Version uint + PbParallelism uint +} + +func (p *Push) SetOptions(opts PushOptions) error { + copts := C.git_push_options{version: C.uint(opts.Version), pb_parallelism: C.uint(opts.PbParallelism)} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_push_set_options(p.ptr, &copts) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +type StatusForeachFunc func(ref string, msg string) int + +//export statusForeach +func statusForeach(_ref *C.char, _msg *C.char, _data unsafe.Pointer) C.int { + ref := C.GoString(_ref) + msg := C.GoString(_msg) + + cb := (*StatusForeachFunc)(_data) + + return C.int((*cb)(ref, msg)) +} + +func (p *Push) StatusForeach(callback StatusForeachFunc) error { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C._go_git_push_status_foreach(p.ptr, unsafe.Pointer(&callback)) + if ret < 0 { + return MakeGitError(ret) + } + return nil + +} + +type PushCallbacks struct { + PackbuilderProgress *PackbuilderProgressCallback + TransferProgress *PushTransferProgressCallback +} + +type PackbuilderProgressCallback func(stage int, current uint, total uint) int +type PushTransferProgressCallback func(current uint, total uint, bytes uint) int + +//export packbuilderProgress +func packbuilderProgress(stage C.int, current C.uint, total C.uint, data unsafe.Pointer) C.int { + return C.int((*(*PackbuilderProgressCallback)(data))(int(stage), uint(current), uint(total))) +} + +//export pushTransferProgress +func pushTransferProgress(current C.uint, total C.uint, bytes C.size_t, data unsafe.Pointer) C.int { + return C.int((*(*PushTransferProgressCallback)(data))(uint(current), uint(total), uint(bytes))) +} + +func (p *Push) SetCallbacks(callbacks PushCallbacks) { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + // save callbacks so they don't get GC'd + p.packbuilderProgress = callbacks.PackbuilderProgress + p.transferProgress = callbacks.TransferProgress + + C._go_git_push_set_callbacks(p.ptr, unsafe.Pointer(p.packbuilderProgress), unsafe.Pointer(p.transferProgress)) +} diff --git a/push_test.go b/push_test.go new file mode 100644 index 0000000..c1e6a22 --- /dev/null +++ b/push_test.go @@ -0,0 +1,59 @@ +package git + +import ( + "log" + "os" + "testing" + "time" +) + +func Test_Push_ToRemote(t *testing.T) { + repo := createBareTestRepo(t) + defer os.RemoveAll(repo.Path()) + repo2 := createTestRepo(t) + defer os.RemoveAll(repo2.Workdir()) + + remote, err := repo2.CreateRemote("test_push", repo.Path()) + checkFatal(t, err) + + index, err := repo2.Index() + checkFatal(t, err) + + index.AddByPath("README") + + err = index.Write() + checkFatal(t, err) + + newTreeId, err := index.WriteTree() + checkFatal(t, err) + + tree, err := repo2.LookupTree(newTreeId) + checkFatal(t, err) + + sig := &Signature{Name: "Rand Om Hacker", Email: "random@hacker.com", When: time.Now()} + // this should cause master branch to be created if it does not already exist + _, err = repo2.CreateCommit("HEAD", sig, sig, "message", tree) + checkFatal(t, err) + + push, err := remote.NewPush() + checkFatal(t, err) + + err = push.AddRefspec("refs/heads/master") + checkFatal(t, err) + + err = push.Finish() + checkFatal(t, err) + + err = push.StatusForeach(func(ref string, msg string) int { + log.Printf("%s -> %s", ref, msg) + return 0 + }) + checkFatal(t, err) + + if !push.UnpackOk() { + t.Fatalf("unable to unpack") + } + + defer remote.Free() + defer repo.Free() +} diff --git a/reference.go b/reference.go index a2f1636..d246c55 100644 --- a/reference.go +++ b/reference.go @@ -40,12 +40,17 @@ func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string) csig := sig.toC() defer C.free(unsafe.Pointer(csig)) - cmsg := C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget, csig, cmsg) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return newReferenceFromC(ptr), nil @@ -60,12 +65,17 @@ func (v *Reference) SetTarget(target *Oid, sig *Signature, msg string) (*Referen csig := sig.toC() defer C.free(unsafe.Pointer(csig)) - cmsg := C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } ret := C.git_reference_set_target(&ptr, v.ptr, target.toC(), csig, cmsg) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return newReferenceFromC(ptr), nil @@ -79,7 +89,7 @@ func (v *Reference) Resolve() (*Reference, error) { ret := C.git_reference_resolve(&ptr, v.ptr) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return newReferenceFromC(ptr), nil @@ -93,8 +103,13 @@ func (v *Reference) Rename(name string, force bool, sig *Signature, msg string) csig := sig.toC() defer C.free(unsafe.Pointer(csig)) - cmsg := C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -102,7 +117,7 @@ func (v *Reference) Rename(name string, force bool, sig *Signature, msg string) ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), csig, cmsg) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return newReferenceFromC(ptr), nil @@ -128,12 +143,23 @@ func (v *Reference) Delete() error { ret := C.git_reference_delete(v.ptr) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } +// Cmp compares both references, retursn 0 on equality, otherwise a +// stable sorting. +func (v *Reference) Cmp(ref2 *Reference) int { + return int(C.git_reference_cmp(v.ptr, ref2.ptr)) +} + +// Shorthand returns a "human-readable" short reference name +func (v *Reference) Shorthand() string { + return C.GoString(C.git_reference_shorthand(v.ptr)) +} + func (v *Reference) Name() string { return C.GoString(C.git_reference_name(v.ptr)) } @@ -173,7 +199,7 @@ func (repo *Repository) NewReferenceIterator() (*ReferenceIterator, error) { ret := C.git_reference_iterator_new(&ptr, repo.ptr) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } iter := &ReferenceIterator{repo: repo, ptr: ptr} @@ -194,7 +220,7 @@ func (repo *Repository) NewReferenceIteratorGlob(glob string) (*ReferenceIterato ret := C.git_reference_iterator_glob_new(&ptr, repo.ptr, cstr) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } iter := &ReferenceIterator{repo: repo, ptr: ptr} @@ -215,7 +241,7 @@ func (v *ReferenceIterator) NextName() (string, error) { return "", ErrIterOver } if ret < 0 { - return "", LastError() + return "", MakeGitError(ret) } return C.GoString(ptr), nil @@ -247,7 +273,7 @@ func (v *ReferenceIterator) Next() (*Reference, error) { return nil, ErrIterOver } if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return newReferenceFromC(ptr), nil diff --git a/reference_test.go b/reference_test.go index 156960a..ffa9f35 100644 --- a/reference_test.go +++ b/reference_test.go @@ -159,6 +159,33 @@ func TestIterator(t *testing.T) { compareStringList(t, expected, list) } +func TestUtil(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + commitId, _ := seedTestRepo(t, repo) + + ref, err := repo.CreateReference("refs/heads/foo", commitId, true, nil, "") + checkFatal(t, err) + + ref2, err := repo.DwimReference("foo") + checkFatal(t, err) + + if ref.Cmp(ref2) != 0 { + t.Fatalf("foo didn't dwim to the right thing") + } + + if ref.Shorthand() != "foo" { + t.Fatalf("refs/heads/foo has no foo shorthand") + } + + hasLog, err := repo.HasLog("refs/heads/foo") + checkFatal(t, err) + if !hasLog { + t.Fatalf("branches ahve logs by default") + } +} + func compareStringList(t *testing.T, expected, actual []string) { for i, v := range expected { if actual[i] != v { diff --git a/remote.go b/remote.go new file mode 100644 index 0000000..eb5d7a7 --- /dev/null +++ b/remote.go @@ -0,0 +1,436 @@ +package git + +/* +#include +#include + +extern void _go_git_setup_callbacks(git_remote_callbacks *callbacks); + +*/ +import "C" +import "unsafe" +import "runtime" + +type TransferProgress struct { + TotalObjects uint + IndexedObjects uint + ReceivedObjects uint + LocalObjects uint + TotalDeltas uint + ReceivedBytes uint +} + +func newTransferProgressFromC(c *C.git_transfer_progress) TransferProgress { + return TransferProgress{ + TotalObjects: uint(c.total_objects), + IndexedObjects: uint(c.indexed_objects), + ReceivedObjects: uint(c.received_objects), + LocalObjects: uint(c.local_objects), + TotalDeltas: uint(c.total_deltas), + ReceivedBytes: uint(c.received_bytes)} +} + +type RemoteCompletion uint + +const ( + RemoteCompletionDownload RemoteCompletion = C.GIT_REMOTE_COMPLETION_DOWNLOAD + RemoteCompletionIndexing = C.GIT_REMOTE_COMPLETION_INDEXING + RemoteCompletionError = C.GIT_REMOTE_COMPLETION_ERROR +) + +type ProgressCallback func(str string) int +type CompletionCallback func(RemoteCompletion) int +type CredentialsCallback func(url string, username_from_url string, allowed_types CredType) (int, *Cred) +type TransferProgressCallback func(stats TransferProgress) int +type UpdateTipsCallback func(refname string, a *Oid, b *Oid) int + +type RemoteCallbacks struct { + ProgressCallback + CompletionCallback + CredentialsCallback + TransferProgressCallback + UpdateTipsCallback +} + +type Remote struct { + ptr *C.git_remote +} + +func populateRemoteCallbacks(ptr *C.git_remote_callbacks, callbacks *RemoteCallbacks) { + C.git_remote_init_callbacks(ptr, C.GIT_REMOTE_CALLBACKS_VERSION) + if callbacks == nil { + return + } + C._go_git_setup_callbacks(ptr) + ptr.payload = unsafe.Pointer(callbacks) +} + +//export progressCallback +func progressCallback(_str *C.char, _len C.int, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.ProgressCallback == nil { + return 0 + } + str := C.GoStringN(_str, _len) + return callbacks.ProgressCallback(str) +} + +//export completionCallback +func completionCallback(completion_type C.git_remote_completion_type, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.CompletionCallback == nil { + return 0 + } + return callbacks.CompletionCallback((RemoteCompletion)(completion_type)) +} + +//export credentialsCallback +func credentialsCallback(_cred **C.git_cred, _url *C.char, _username_from_url *C.char, allowed_types uint, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.CredentialsCallback == nil { + return 0 + } + url := C.GoString(_url) + username_from_url := C.GoString(_username_from_url) + ret, cred := callbacks.CredentialsCallback(url, username_from_url, (CredType)(allowed_types)) + *_cred = cred.ptr + return ret +} + +//export transferProgressCallback +func transferProgressCallback(stats *C.git_transfer_progress, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.TransferProgressCallback == nil { + return 0 + } + return callbacks.TransferProgressCallback(newTransferProgressFromC(stats)) +} + +//export updateTipsCallback +func updateTipsCallback(_refname *C.char, _a *C.git_oid, _b *C.git_oid, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.UpdateTipsCallback == nil { + return 0 + } + refname := C.GoString(_refname) + a := newOidFromC(_a) + b := newOidFromC(_b) + return callbacks.UpdateTipsCallback(refname, a, b) +} + +func RemoteIsValidName(name string) bool { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + if C.git_remote_is_valid_name(cname) == 1 { + return true + } + return false +} + +func (r *Remote) Free() { + runtime.SetFinalizer(r, nil) + C.git_remote_free(r.ptr) +} + +func (repo *Repository) ListRemotes() ([]string, error) { + var r C.git_strarray + ecode := C.git_remote_list(&r, repo.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + defer C.git_strarray_free(&r) + + remotes := makeStringsFromCStrings(r.strings, int(r.count)) + return remotes, nil +} + +func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) { + remote := &Remote{} + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_create(&remote.ptr, repo.ptr, cname, curl) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(remote, (*Remote).Free) + return remote, nil +} + +func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch string) (*Remote, error) { + remote := &Remote{} + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + cfetch := C.CString(fetch) + defer C.free(unsafe.Pointer(cfetch)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_create_with_fetchspec(&remote.ptr, repo.ptr, cname, curl, cfetch) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(remote, (*Remote).Free) + return remote, nil +} + +func (repo *Repository) CreateAnonymousRemote(url, fetch string) (*Remote, error) { + remote := &Remote{} + + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + cfetch := C.CString(fetch) + defer C.free(unsafe.Pointer(cfetch)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_create_anonymous(&remote.ptr, repo.ptr, curl, cfetch) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(remote, (*Remote).Free) + return remote, nil +} + +func (repo *Repository) LoadRemote(name string) (*Remote, error) { + remote := &Remote{} + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_load(&remote.ptr, repo.ptr, cname) + if ret < 0 { + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(remote, (*Remote).Free) + 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 { + return C.GoString(C.git_remote_name(o.ptr)) +} + +func (o *Remote) Url() string { + return C.GoString(C.git_remote_url(o.ptr)) +} + +func (o *Remote) PushUrl() string { + return C.GoString(C.git_remote_pushurl(o.ptr)) +} + +func (o *Remote) SetUrl(url string) error { + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_set_url(o.ptr, curl) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *Remote) SetPushUrl(url string) error { + curl := C.CString(url) + defer C.free(unsafe.Pointer(curl)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_set_pushurl(o.ptr, curl) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *Remote) AddFetch(refspec string) error { + crefspec := C.CString(refspec) + defer C.free(unsafe.Pointer(crefspec)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_add_fetch(o.ptr, crefspec) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func sptr(p uintptr) *C.char { + return *(**C.char)(unsafe.Pointer(p)) +} + +func makeStringsFromCStrings(x **C.char, l int) []string { + s := make([]string, l) + i := 0 + for p := uintptr(unsafe.Pointer(x)); i < l; p += unsafe.Sizeof(uintptr(0)) { + s[i] = C.GoString(sptr(p)) + i++ + } + return s +} + +func makeCStringsFromStrings(s []string) **C.char { + l := len(s) + x := (**C.char)(C.malloc(C.size_t(unsafe.Sizeof(unsafe.Pointer(nil)) * uintptr(l)))) + i := 0 + for p := uintptr(unsafe.Pointer(x)); i < l; p += unsafe.Sizeof(uintptr(0)) { + *(**C.char)(unsafe.Pointer(p)) = C.CString(s[i]) + i++ + } + return x +} + +func freeStrarray(arr *C.git_strarray) { + count := int(arr.count) + size := unsafe.Sizeof(unsafe.Pointer(nil)) + + i := 0 + for p := uintptr(unsafe.Pointer(arr.strings)); i < count; p += size { + C.free(unsafe.Pointer(sptr(p))) + i++ + } + + C.free(unsafe.Pointer(arr.strings)) +} + +func (o *Remote) FetchRefspecs() ([]string, error) { + crefspecs := C.git_strarray{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_get_fetch_refspecs(&crefspecs, o.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + defer C.git_strarray_free(&crefspecs) + + refspecs := makeStringsFromCStrings(crefspecs.strings, int(crefspecs.count)) + return refspecs, nil +} + +func (o *Remote) SetFetchRefspecs(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_fetch_refspecs(o.ptr, &crefspecs) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *Remote) AddPush(refspec string) error { + crefspec := C.CString(refspec) + defer C.free(unsafe.Pointer(crefspec)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_add_push(o.ptr, crefspec) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *Remote) PushRefspecs() ([]string, error) { + crefspecs := C.git_strarray{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_get_push_refspecs(&crefspecs, o.ptr) + if ret < 0 { + return nil, MakeGitError(ret) + } + defer C.git_strarray_free(&crefspecs) + refspecs := makeStringsFromCStrings(crefspecs.strings, int(crefspecs.count)) + 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 { + return uint(C.git_remote_refspec_count(o.ptr)) +} + +func (o *Remote) Fetch(sig *Signature, msg string) error { + + var csig *C.git_signature = nil + if sig != nil { + csig = sig.toC() + defer C.free(unsafe.Pointer(csig)) + } + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + ret := C.git_remote_fetch(o.ptr, csig, cmsg) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} diff --git a/remote_test.go b/remote_test.go new file mode 100644 index 0000000..7cef1ec --- /dev/null +++ b/remote_test.go @@ -0,0 +1,47 @@ +package git + +import ( + "os" + "testing" +) + +func TestRefspecs(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + defer repo.Free() + + 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) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + defer repo.Free() + + _, err := repo.CreateRemote("test", "git://foo/bar") + + checkFatal(t, err) + + expected := []string{ + "test", + } + + actual, err := repo.ListRemotes() + checkFatal(t, err) + + compareStringList(t, expected, actual) +} diff --git a/repository.go b/repository.go index 1f273fb..d5aa753 100644 --- a/repository.go +++ b/repository.go @@ -26,7 +26,7 @@ func OpenRepository(path string) (*Repository, error) { ret := C.git_repository_open(&repo.ptr, cpath) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } runtime.SetFinalizer(repo, (*Repository).Free) @@ -44,7 +44,7 @@ func InitRepository(path string, isbare bool) (*Repository, error) { ret := C.git_repository_init(&repo.ptr, cpath, ucbool(isbare)) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } runtime.SetFinalizer(repo, (*Repository).Free) @@ -80,7 +80,7 @@ func (v *Repository) Config() (*Config, error) { ret := C.git_repository_config(&config.ptr, v.ptr) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } runtime.SetFinalizer(config, (*Config).Free) @@ -95,32 +95,32 @@ func (v *Repository) Index() (*Index, error) { ret := C.git_repository_index(&ptr, v.ptr) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return newIndexFromC(ptr), nil } -func (v *Repository) lookupType(oid *Oid, t ObjectType) (Object, error) { +func (v *Repository) lookupType(id *Oid, t ObjectType) (Object, error) { var ptr *C.git_object runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_object_lookup(&ptr, v.ptr, oid.toC(), C.git_otype(t)) + ret := C.git_object_lookup(&ptr, v.ptr, id.toC(), C.git_otype(t)) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return allocObject(ptr), nil } -func (v *Repository) Lookup(oid *Oid) (Object, error) { - return v.lookupType(oid, ObjectAny) +func (v *Repository) Lookup(id *Oid) (Object, error) { + return v.lookupType(id, ObjectAny) } -func (v *Repository) LookupTree(oid *Oid) (*Tree, error) { - obj, err := v.lookupType(oid, ObjectTree) +func (v *Repository) LookupTree(id *Oid) (*Tree, error) { + obj, err := v.lookupType(id, ObjectTree) if err != nil { return nil, err } @@ -128,8 +128,8 @@ func (v *Repository) LookupTree(oid *Oid) (*Tree, error) { return obj.(*Tree), nil } -func (v *Repository) LookupCommit(oid *Oid) (*Commit, error) { - obj, err := v.lookupType(oid, ObjectCommit) +func (v *Repository) LookupCommit(id *Oid) (*Commit, error) { + obj, err := v.lookupType(id, ObjectCommit) if err != nil { return nil, err } @@ -137,8 +137,8 @@ func (v *Repository) LookupCommit(oid *Oid) (*Commit, error) { return obj.(*Commit), nil } -func (v *Repository) LookupBlob(oid *Oid) (*Blob, error) { - obj, err := v.lookupType(oid, ObjectBlob) +func (v *Repository) LookupBlob(id *Oid) (*Blob, error) { + obj, err := v.lookupType(id, ObjectBlob) if err != nil { return nil, err } @@ -156,30 +156,35 @@ func (v *Repository) LookupReference(name string) (*Reference, error) { ecode := C.git_reference_lookup(&ptr, v.ptr, cname) if ecode < 0 { - return nil, LastError() + return nil, MakeGitError(ecode) } return newReferenceFromC(ptr), nil } -func (v *Repository) CreateReference(name string, oid *Oid, force bool, sig *Signature, msg string) (*Reference, error) { +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 := sig.toC() defer C.free(unsafe.Pointer(csig)) - cmsg := C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) + 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, oid.toC(), cbool(force), csig, cmsg) + ecode := C.git_reference_create(&ptr, v.ptr, cname, id.toC(), cbool(force), csig, cmsg) if ecode < 0 { - return nil, LastError() + return nil, MakeGitError(ecode) } return newReferenceFromC(ptr), nil @@ -195,8 +200,13 @@ func (v *Repository) CreateSymbolicReference(name, target string, force bool, si csig := sig.toC() defer C.free(unsafe.Pointer(csig)) - cmsg := C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } var ptr *C.git_reference @@ -205,26 +215,25 @@ func (v *Repository) CreateSymbolicReference(name, target string, force bool, si ecode := C.git_reference_symbolic_create(&ptr, v.ptr, cname, ctarget, cbool(force), csig, cmsg) if ecode < 0 { - return nil, LastError() + return nil, MakeGitError(ecode) } return newReferenceFromC(ptr), nil } func (v *Repository) Walk() (*RevWalk, error) { - walk := new(RevWalk) + + var walkPtr *C.git_revwalk runtime.LockOSThread() defer runtime.UnlockOSThread() - ecode := C.git_revwalk_new(&walk.ptr, v.ptr) + ecode := C.git_revwalk_new(&walkPtr, v.ptr) if ecode < 0 { - return nil, LastError() + return nil, MakeGitError(ecode) } - walk.repo = v - runtime.SetFinalizer(walk, freeRevWalk) - return walk, nil + return revWalkFromC(v, walkPtr), nil } func (v *Repository) CreateCommit( @@ -246,7 +255,7 @@ func (v *Repository) CreateCommit( if nparents > 0 { cparents = make([]*C.git_commit, nparents) for i, v := range parents { - cparents[i] = v.ptr + cparents[i] = v.cast_ptr } parentsarg = &cparents[0] } @@ -263,10 +272,10 @@ func (v *Repository) CreateCommit( ret := C.git_commit_create( oid.toC(), v.ptr, cref, authorSig, committerSig, - nil, cmsg, tree.ptr, C.size_t(nparents), parentsarg) + nil, cmsg, tree.cast_ptr, C.size_t(nparents), parentsarg) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return oid, nil @@ -289,7 +298,7 @@ func (v *Repository) Odb() (odb *Odb, err error) { defer runtime.UnlockOSThread() if ret := C.git_repository_odb(&odb.ptr, v.ptr); ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } runtime.SetFinalizer(odb, (*Odb).Free) @@ -315,9 +324,10 @@ func (repo *Repository) SetWorkdir(workdir string, updateGitlink bool) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - if C.git_repository_set_workdir(repo.ptr, cstr, cbool(updateGitlink)) < 0 { - return LastError() + if ret := C.git_repository_set_workdir(repo.ptr, cstr, cbool(updateGitlink)); ret < 0 { + return MakeGitError(ret) } + return nil } @@ -328,7 +338,22 @@ func (v *Repository) TreeBuilder() (*TreeBuilder, error) { defer runtime.UnlockOSThread() if ret := C.git_treebuilder_create(&bld.ptr, nil); ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) + } + runtime.SetFinalizer(bld, (*TreeBuilder).Free) + + bld.repo = v + return bld, nil +} + +func (v *Repository) TreeBuilderFromTree(tree *Tree) (*TreeBuilder, error) { + bld := new(TreeBuilder) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_treebuilder_create(&bld.ptr, tree.cast_ptr); ret < 0 { + return nil, MakeGitError(ret) } runtime.SetFinalizer(bld, (*TreeBuilder).Free) @@ -347,8 +372,57 @@ func (v *Repository) RevparseSingle(spec string) (Object, error) { ecode := C.git_revparse_single(&ptr, v.ptr, cspec) if ecode < 0 { - return nil, LastError() + return nil, MakeGitError(ecode) } return allocObject(ptr), 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() + + if ret := C.git_reference_ensure_log(v.ptr, cname); 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 + if ret := C.git_reference_dwim(&ptr, v.ptr, cname); ret < 0 { + return nil, MakeGitError(ret) + } + + return newReferenceFromC(ptr), nil +} diff --git a/script/build-libgit2.sh b/script/build-libgit2.sh new file mode 100755 index 0000000..8376a15 --- /dev/null +++ b/script/build-libgit2.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -ex + +git clone --depth 1 --single-branch git://github.com/libgit2/libgit2 libgit2 + +cd libgit2 +cmake -DTHREADSAFE=ON \ + -DBUILD_CLAR=OFF \ + -DCMAKE_INSTALL_PREFIX=$PWD/install \ + . + +make install diff --git a/settings/settings.go b/settings/settings.go new file mode 100644 index 0000000..6661c5d --- /dev/null +++ b/settings/settings.go @@ -0,0 +1,109 @@ +package settings + +/* +#cgo pkg-config: libgit2 +#include + +int _go_git_opts_get_search_path(int level, git_buf *buf) +{ + return git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, buf); +} + +int _go_git_opts_set_search_path(int level, const char *path) +{ + return git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, path); +} + +int _go_git_opts_set_size_t(int opt, size_t val) +{ + return git_libgit2_opts(opt, val); +} + +int _go_git_opts_get_size_t(int opt, size_t *val) +{ + return git_libgit2_opts(opt, val); +} + +*/ +import "C" +import ( + "runtime" + "unsafe" + "github.com/libgit2/git2go" +) + +func MakeGitError(err C.int) error { + return git.MakeGitError2(int(err)) +} + +func SearchPath(level git.ConfigLevel) (string, error) { + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C._go_git_opts_get_search_path(C.int(level), &buf) + if err < 0 { + return "", MakeGitError(err) + } + + return C.GoString(buf.ptr), nil +} + +func SetSearchPath(level git.ConfigLevel, path string) error { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C._go_git_opts_set_search_path(C.int(level), cpath) + if err < 0 { + return MakeGitError(err) + } + + return nil +} + +func getSizet(opt C.int) (int, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var val C.size_t + err := C._go_git_opts_get_size_t(opt, &val); + if err < 0 { + return 0, MakeGitError(err) + } + + return int(val), nil +} + +func setSizet(opt C.int, val int) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cval := C.size_t(val) + err := C._go_git_opts_set_size_t(opt, cval); + if err < 0 { + return MakeGitError(err) + } + + return nil +} + +func MwindowSize() (int, error) { + return getSizet(C.GIT_OPT_GET_MWINDOW_SIZE) +} + +func SetMwindowSize(size int) error { + return setSizet(C.GIT_OPT_SET_MWINDOW_SIZE, size) +} + +func MwindowMappedLimit() (int, error) { + return getSizet(C.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT) +} + +func SetMwindowMappedLimit(size int) error { + return setSizet(C.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, size) +} diff --git a/settings/settings_test.go b/settings/settings_test.go new file mode 100644 index 0000000..55b08c8 --- /dev/null +++ b/settings/settings_test.go @@ -0,0 +1,66 @@ +package settings + +import ( + "testing" + "runtime" + "github.com/libgit2/git2go" +) + +type pathPair struct { + Level git.ConfigLevel + Path string +} + +func TestSearchPath(t *testing.T) { + paths := []pathPair{ + pathPair{git.ConfigLevelSystem, "/tmp/system"}, + pathPair{git.ConfigLevelGlobal, "/tmp/global"}, + pathPair{git.ConfigLevelXDG, "/tmp/xdg"}, + } + + for _, pair := range paths { + err := SetSearchPath(pair.Level, pair.Path) + checkFatal(t, err) + + actual, err := SearchPath(pair.Level) + checkFatal(t, err) + + if pair.Path != actual { + t.Fatal("Search paths don't match") + } + } +} + +func TestMmapSizes(t *testing.T) { + size := 42 * 1024 + + err := SetMwindowSize(size) + checkFatal(t, err) + + actual, err := MwindowSize() + if size != actual { + t.Fatal("Sizes don't match") + } + + err = SetMwindowMappedLimit(size) + checkFatal(t, err) + + actual, err = MwindowMappedLimit() + if size != actual { + t.Fatal("Sizes don't match") + } +} + +func checkFatal(t *testing.T, err error) { + if err == nil { + return + } + + // The failure happens at wherever we were called, not here + _, file, line, ok := runtime.Caller(1) + if !ok { + t.Fatal() + } + + t.Fatalf("Fail at %v:%v; %v", file, line, err) +} diff --git a/submodule.go b/submodule.go index 9abf333..f6ab5e1 100644 --- a/submodule.go +++ b/submodule.go @@ -81,7 +81,7 @@ func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) { ret := C.git_submodule_lookup(&sub.ptr, repo.ptr, cname) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return sub, nil @@ -102,7 +102,7 @@ func (repo *Repository) ForeachSubmodule(cbk SubmoduleCbk) error { ret := C._go_git_visit_submodule(repo.ptr, unsafe.Pointer(&cbk)) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -120,7 +120,7 @@ func (repo *Repository) AddSubmodule(url, path string, use_git_link bool) (*Subm ret := C.git_submodule_add_setup(&sub.ptr, repo.ptr, curl, cpath, cbool(use_git_link)) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return sub, nil } @@ -131,7 +131,7 @@ func (sub *Submodule) FinalizeAdd() error { ret := C.git_submodule_add_finalize(sub.ptr) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -142,7 +142,7 @@ func (sub *Submodule) AddToIndex(write_index bool) error { ret := C.git_submodule_add_to_index(sub.ptr, cbool(write_index)) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -153,7 +153,7 @@ func (sub *Submodule) Save() error { ret := C.git_submodule_save(sub.ptr) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -188,7 +188,7 @@ func (sub *Submodule) SetUrl(url string) error { ret := C.git_submodule_set_url(sub.ptr, curl) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -238,7 +238,7 @@ func (sub *Submodule) SetUpdate(update SubmoduleUpdate) SubmoduleUpdate { } 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 { @@ -247,7 +247,7 @@ func (sub *Submodule) SetFetchRecurseSubmodules(recurse SubmoduleRecurse) error ret := C.git_submodule_set_fetch_recurse_submodules(sub.ptr, C.git_submodule_recurse_t(recurse)) if ret < 0 { - return LastError() + return MakeGitError(C.int(ret)) } return nil } @@ -258,7 +258,7 @@ func (sub *Submodule) Init(overwrite bool) error { ret := C.git_submodule_init(sub.ptr, cbool(overwrite)) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -269,7 +269,7 @@ func (sub *Submodule) Sync() error { ret := C.git_submodule_sync(sub.ptr) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } @@ -282,29 +282,29 @@ func (sub *Submodule) Open() (*Repository, error) { ret := C.git_submodule_open(&repo.ptr, sub.ptr) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return repo, nil } -func (sub *Submodule) Reload() error { +func (sub *Submodule) Reload(force bool) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_reload(sub.ptr) + ret := C.git_submodule_reload(sub.ptr, cbool(force)) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } -func (repo *Repository) ReloadAllSubmodules() error { +func (repo *Repository) ReloadAllSubmodules(force bool) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_reload_all(repo.ptr) + ret := C.git_submodule_reload_all(repo.ptr, cbool(force)) if ret < 0 { - return LastError() + return MakeGitError(ret) } return nil } diff --git a/tree.go b/tree.go index 8c74e5d..8356fba 100644 --- a/tree.go +++ b/tree.go @@ -14,23 +14,25 @@ import ( ) type Filemode int + const ( - FilemodeNew Filemode = C.GIT_FILEMODE_NEW - FilemodeTree = C.GIT_FILEMODE_TREE - FilemodeBlob = C.GIT_FILEMODE_BLOB - FilemodeBlobExecutable = C.GIT_FILEMODE_BLOB_EXECUTABLE - FilemodeLink = C.GIT_FILEMODE_LINK - FilemodeCommit = C.GIT_FILEMODE_COMMIT + FilemodeNew Filemode = C.GIT_FILEMODE_NEW + FilemodeTree = C.GIT_FILEMODE_TREE + FilemodeBlob = C.GIT_FILEMODE_BLOB + FilemodeBlobExecutable = C.GIT_FILEMODE_BLOB_EXECUTABLE + FilemodeLink = C.GIT_FILEMODE_LINK + FilemodeCommit = C.GIT_FILEMODE_COMMIT ) type Tree struct { gitObject + cast_ptr *C.git_tree } type TreeEntry struct { - Name string - Id *Oid - Type ObjectType + Name string + Id *Oid + Type ObjectType Filemode int } @@ -47,7 +49,7 @@ func (t Tree) EntryByName(filename string) *TreeEntry { cname := C.CString(filename) defer C.free(unsafe.Pointer(cname)) - entry := C.git_tree_entry_byname(t.ptr, cname) + entry := C.git_tree_entry_byname(t.cast_ptr, cname) if entry == nil { return nil } @@ -65,16 +67,16 @@ func (t Tree) EntryByPath(path string) (*TreeEntry, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_tree_entry_bypath(&entry, t.ptr, cpath) + ret := C.git_tree_entry_bypath(&entry, t.cast_ptr, cpath) if ret < 0 { - return nil, LastError() + return nil, MakeGitError(ret) } return newTreeEntry(entry), nil } func (t Tree) EntryByIndex(index uint64) *TreeEntry { - entry := C.git_tree_entry_byindex(t.ptr, C.size_t(index)) + entry := C.git_tree_entry_byindex(t.cast_ptr, C.size_t(index)) if entry == nil { return nil } @@ -83,7 +85,7 @@ func (t Tree) EntryByIndex(index uint64) *TreeEntry { } func (t Tree) EntryCount() uint64 { - num := C.git_tree_entrycount(t.ptr) + num := C.git_tree_entrycount(t.cast_ptr) return uint64(num) } @@ -103,20 +105,20 @@ func (t Tree) Walk(callback TreeWalkCallback) error { defer runtime.UnlockOSThread() err := C._go_git_treewalk( - t.ptr, + t.cast_ptr, C.GIT_TREEWALK_PRE, unsafe.Pointer(&callback), ) if err < 0 { - return LastError() + return MakeGitError(err) } return nil } type TreeBuilder struct { - ptr *C.git_treebuilder + ptr *C.git_treebuilder repo *Repository } @@ -125,7 +127,7 @@ func (v *TreeBuilder) Free() { C.git_treebuilder_free(v.ptr) } -func (v *TreeBuilder) Insert(filename string, id *Oid, filemode int) (error) { +func (v *TreeBuilder) Insert(filename string, id *Oid, filemode int) error { cfilename := C.CString(filename) defer C.free(unsafe.Pointer(cfilename)) @@ -134,7 +136,22 @@ func (v *TreeBuilder) Insert(filename string, id *Oid, filemode int) (error) { err := C.git_treebuilder_insert(nil, v.ptr, cfilename, id.toC(), C.git_filemode_t(filemode)) if err < 0 { - return LastError() + return MakeGitError(err) + } + + return nil +} + +func (v *TreeBuilder) Remove(filename string) error { + cfilename := C.CString(filename) + defer C.free(unsafe.Pointer(cfilename)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C.git_treebuilder_remove(v.ptr, cfilename) + if err < 0 { + return MakeGitError(err) } return nil @@ -149,7 +166,7 @@ func (v *TreeBuilder) Write() (*Oid, error) { err := C.git_treebuilder_write(oid.toC(), v.repo.ptr, v.ptr) if err < 0 { - return nil, LastError() + return nil, MakeGitError(err) } return oid, nil diff --git a/walk.go b/walk.go index 6979b6b..f7c147d 100644 --- a/walk.go +++ b/walk.go @@ -9,16 +9,18 @@ import "C" import ( "io" "runtime" + "unsafe" ) // RevWalk type SortType uint + const ( - SortNone SortType = C.GIT_SORT_NONE - SortTopological = C.GIT_SORT_TOPOLOGICAL - SortTime = C.GIT_SORT_TIME - SortReverse = C.GIT_SORT_REVERSE + SortNone SortType = C.GIT_SORT_NONE + SortTopological = C.GIT_SORT_TOPOLOGICAL + SortTime = C.GIT_SORT_TIME + SortReverse = C.GIT_SORT_REVERSE ) type RevWalk struct { @@ -26,12 +28,67 @@ type RevWalk struct { repo *Repository } +func revWalkFromC(repo *Repository, c *C.git_revwalk) *RevWalk { + v := &RevWalk{ptr: c, repo: repo} + runtime.SetFinalizer(v, (*RevWalk).Free) + return v +} + func (v *RevWalk) Reset() { C.git_revwalk_reset(v.ptr) } -func (v *RevWalk) Push(id *Oid) { - C.git_revwalk_push(v.ptr, id.toC()) +func (v *RevWalk) Push(id *Oid) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revwalk_push(v.ptr, id.toC()) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) PushGlob(glob string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(glob) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_push_glob(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) PushRange(r string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(r) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_push_range(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) PushRef(r string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(r) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_push_ref(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil } func (v *RevWalk) PushHead() (err error) { @@ -40,22 +97,71 @@ func (v *RevWalk) PushHead() (err error) { ecode := C.git_revwalk_push_head(v.ptr) if ecode < 0 { - err = LastError() + err = MakeGitError(ecode) } - - return + return nil } -func (v *RevWalk) Next(oid *Oid) (err error) { +func (v *RevWalk) Hide(id *Oid) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_revwalk_next(oid.toC(), v.ptr) + ecode := C.git_revwalk_hide(v.ptr, id.toC()) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) HideGlob(glob string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(glob) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_hide_glob(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) HideRef(r string) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + cstr := C.CString(r) + defer C.free(unsafe.Pointer(cstr)) + + ecode := C.git_revwalk_hide_ref(v.ptr, cstr) + if ecode < 0 { + return MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) HideHead() (err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revwalk_hide_head(v.ptr) + if ecode < 0 { + err = MakeGitError(ecode) + } + return nil +} + +func (v *RevWalk) Next(id *Oid) (err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_revwalk_next(id.toC(), v.ptr) switch { case ret == ITEROVER: err = io.EOF case ret < 0: - err = LastError() + err = MakeGitError(ret) } return @@ -92,6 +198,8 @@ func (v *RevWalk) Sorting(sm SortType) { C.git_revwalk_sorting(v.ptr, C.uint(sm)) } -func freeRevWalk(walk *RevWalk) { - C.git_revwalk_free(walk.ptr) +func (v *RevWalk) Free() { + + runtime.SetFinalizer(v, nil) + C.git_revwalk_free(v.ptr) } diff --git a/wrapper.c b/wrapper.c index 66a6110..f2232e4 100644 --- a/wrapper.c +++ b/wrapper.c @@ -42,4 +42,42 @@ void _go_git_refdb_backend_free(git_refdb_backend *backend) return; } + +void _go_git_setup_callbacks(git_remote_callbacks *callbacks) { + typedef int (*progress_cb)(const char *str, int len, void *data); + typedef int (*completion_cb)(git_remote_completion_type type, void *data); + typedef int (*credentials_cb)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data); + typedef int (*transfer_progress_cb)(const git_transfer_progress *stats, void *data); + typedef int (*update_tips_cb)(const char *refname, const git_oid *a, const git_oid *b, void *data); + callbacks->progress = (progress_cb)progressCallback; + callbacks->completion = (completion_cb)completionCallback; + callbacks->credentials = (credentials_cb)credentialsCallback; + callbacks->transfer_progress = (transfer_progress_cb)transferProgressCallback; + callbacks->update_tips = (update_tips_cb)updateTipsCallback; +} + +typedef int (*status_foreach_cb)(const char *ref, const char *msg, void *data); + +int _go_git_push_status_foreach(git_push *push, void *data) +{ + return git_push_status_foreach(push, (status_foreach_cb)statusForeach, data); +} + +int _go_git_push_set_callbacks(git_push *push, void *packbuilder_progress_data, void *transfer_progress_data) +{ + return git_push_set_callbacks(push, packbuilderProgress, packbuilder_progress_data, pushTransferProgress, transfer_progress_data); +} + +int _go_blob_chunk_cb(char *buffer, size_t maxLen, void *payload) +{ + return blobChunkCb(buffer, maxLen, payload); +} + +int _go_git_blob_create_fromchunks(git_oid *id, + git_repository *repo, + const char *hintpath, + void *payload) +{ + return git_blob_create_fromchunks(id, repo, hintpath, _go_blob_chunk_cb, payload); +} /* EOF */