diff options
| -rw-r--r-- | blob.go | 13 | ||||
| -rw-r--r-- | checkout.go | 40 | ||||
| -rw-r--r-- | commit.go | 55 | ||||
| -rw-r--r-- | config.go | 322 | ||||
| -rw-r--r-- | git.go | 60 | ||||
| -rw-r--r-- | git_test.go | 47 | ||||
| -rw-r--r-- | index.go | 20 | ||||
| -rw-r--r-- | index_test.go | 15 | ||||
| -rw-r--r-- | object.go | 83 | ||||
| -rw-r--r-- | object_test.go | 77 | ||||
| -rw-r--r-- | odb.go | 155 | ||||
| -rw-r--r-- | odb_test.go | 62 | ||||
| -rw-r--r-- | packbuilder.go | 65 | ||||
| -rw-r--r-- | reference.go | 188 | ||||
| -rw-r--r-- | reference_test.go | 138 | ||||
| -rw-r--r-- | repository.go | 138 | ||||
| -rw-r--r-- | submodule.go | 67 | ||||
| -rw-r--r-- | tree.go | 70 | ||||
| -rw-r--r-- | walk.go | 19 | ||||
| -rw-r--r-- | wrapper.c | 4 |
20 files changed, 1437 insertions, 201 deletions
@@ -1,30 +1,23 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> */ import "C" import ( - "runtime" "unsafe" ) type Blob struct { - ptr *C.git_object + gitObject } -func (v *Blob) Free() { - runtime.SetFinalizer(v, nil) - C.git_object_free(v.ptr) -} - -func (v *Blob) Size() int64 { +func (v Blob) Size() int64 { return int64(C.git_blob_rawsize(v.ptr)) } -func (v *Blob) Contents() []byte { +func (v Blob) Contents() []byte { size := C.int(C.git_blob_rawsize(v.ptr)) buffer := unsafe.Pointer(C.git_blob_rawcontent(v.ptr)) return C.GoBytes(buffer, size) diff --git a/checkout.go b/checkout.go index bbeb240..f4c1d4e 100644 --- a/checkout.go +++ b/checkout.go @@ -1,7 +1,6 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> git_checkout_opts git_checkout_opts_init() { git_checkout_opts ret = GIT_CHECKOUT_OPTS_INIT; @@ -10,28 +9,29 @@ git_checkout_opts git_checkout_opts_init() { */ import "C" import ( + "runtime" "os" ) type CheckoutStrategy uint const ( - CHECKOUT_NONE CheckoutStrategy = C.GIT_CHECKOUT_NONE // Dry run, no actual updates - CHECKOUT_SAFE = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data - CHECKOUT_SAFE_CREATE = C.GIT_CHECKOUT_SAFE_CREATE // Allow safe updates plus creation of missing files - CHECKOUT_FORCE = C.GIT_CHECKOUT_FORCE // Allow all updates to force working directory to look like index - CHECKOUT_ALLOW_CONFLICTS = C.GIT_CHECKOUT_ALLOW_CONFLICTS // Allow checkout to make safe updates even if conflicts are found - CHECKOUT_REMOVE_UNTRACKED = C.GIT_CHECKOUT_REMOVE_UNTRACKED // Remove untracked files not in index (that are not ignored) - CHECKOUT_REMOVE_IGNORED = C.GIT_CHECKOUT_REMOVE_IGNORED // Remove ignored files not in index - CHECKOUT_UPDATE_ONLY = C.GIT_CHECKOUT_UPDATE_ONLY // Only update existing files, don't create new ones - CHECKOUT_DONT_UPDATE_INDEX = C.GIT_CHECKOUT_DONT_UPDATE_INDEX // Normally checkout updates index entries as it goes; this stops that - CHECKOUT_NO_REFRESH = C.GIT_CHECKOUT_NO_REFRESH // Don't refresh index/config/etc before doing checkout - CHECKOUT_DISABLE_PATHSPEC_MATCH = C.GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH // Treat pathspec as simple list of exact match file paths - CHECKOUT_SKIP_UNMERGED = C.GIT_CHECKOUT_SKIP_UNMERGED // Allow checkout to skip unmerged files (NOT IMPLEMENTED) - CHECKOUT_USE_OURS = C.GIT_CHECKOUT_USE_OURS // For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) - CHECKOUT_USE_THEIRS = C.GIT_CHECKOUT_USE_THEIRS // For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) - CHECKOUT_UPDATE_SUBMODULES = C.GIT_CHECKOUT_UPDATE_SUBMODULES // Recursively checkout submodules with same options (NOT IMPLEMENTED) - CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = C.GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED // Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) + CheckoutNone CheckoutStrategy = C.GIT_CHECKOUT_NONE // Dry run, no actual updates + CheckoutSafe = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data + CheckoutSafeCreate = C.GIT_CHECKOUT_SAFE_CREATE // Allow safe updates plus creation of missing files + CheckoutForce = C.GIT_CHECKOUT_FORCE // Allow all updates to force working directory to look like index + CheckoutAllowConflicts = C.GIT_CHECKOUT_ALLOW_CONFLICTS // Allow checkout to make safe updates even if conflicts are found + CheckoutRemoveUntracked = C.GIT_CHECKOUT_REMOVE_UNTRACKED // Remove untracked files not in index (that are not ignored) + CheckoutRemoveIgnored = C.GIT_CHECKOUT_REMOVE_IGNORED // Remove ignored files not in index + CheckotUpdateOnly = C.GIT_CHECKOUT_UPDATE_ONLY // Only update existing files, don't create new ones + CheckoutDontUpdateIndex = C.GIT_CHECKOUT_DONT_UPDATE_INDEX // Normally checkout updates index entries as it goes; this stops that + CheckoutNoRefresh = C.GIT_CHECKOUT_NO_REFRESH // Don't refresh index/config/etc before doing checkout + CheckooutDisablePathspecMatch = C.GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH // Treat pathspec as simple list of exact match file paths + CheckoutSkipUnmerged = C.GIT_CHECKOUT_SKIP_UNMERGED // Allow checkout to skip unmerged files (NOT IMPLEMENTED) + CheckoutUserOurs = C.GIT_CHECKOUT_USE_OURS // For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) + CheckoutUseTheirs = C.GIT_CHECKOUT_USE_THEIRS // For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) + CheckoutUpdateSubmodules = C.GIT_CHECKOUT_UPDATE_SUBMODULES // Recursively checkout submodules with same options (NOT IMPLEMENTED) + CheckoutUpdateSubmodulesIfChanged = C.GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED // Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) ) type CheckoutOpts struct { @@ -60,6 +60,9 @@ func (v *Repository) Checkout(opts *CheckoutOpts) error { var copts C.git_checkout_opts populateCheckoutOpts(&copts, opts) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_checkout_head(v.ptr, &copts) if ret < 0 { return LastError() @@ -73,6 +76,9 @@ func (v *Repository) CheckoutIndex(index *Index, opts *CheckoutOpts) error { var copts C.git_checkout_opts populateCheckoutOpts(&copts, opts) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_checkout_index(v.ptr, index.ptr, &copts) if ret < 0 { return LastError() @@ -10,49 +10,65 @@ import "C" import ( "runtime" - "unsafe" "time" + "unsafe" ) // Commit type Commit struct { - ptr *C.git_commit + gitObject } -func (c *Commit) Id() *Oid { - return newOidFromC(C.git_commit_id(c.ptr)) -} - -func (c *Commit) Message() string { +func (c Commit) Message() string { return C.GoString(C.git_commit_message(c.ptr)) } -func (c *Commit) Tree() (*Tree, error) { - tree := new(Tree) +func (c Commit) Tree() (*Tree, error) { + var ptr *C.git_object + + runtime.LockOSThread() + defer runtime.UnlockOSThread() - err := C.git_commit_tree(&tree.ptr, c.ptr) + err := C.git_commit_tree(&ptr, c.ptr) if err < 0 { return nil, LastError() } - runtime.SetFinalizer(tree, (*Tree).Free) - return tree, nil + return allocObject(ptr).(*Tree), nil } -func (c *Commit) TreeId() *Oid { +func (c Commit) TreeId() *Oid { return newOidFromC(C.git_commit_tree_id(c.ptr)) } -func (c *Commit) Author() *Signature { +func (c Commit) Author() *Signature { ptr := C.git_commit_author(c.ptr) return newSignatureFromC(ptr) } -func (c *Commit) Committer() *Signature { +func (c Commit) Committer() *Signature { ptr := C.git_commit_committer(c.ptr) return newSignatureFromC(ptr) } +func (c *Commit) Parent(n uint) *Commit { + var cobj *C.git_object + ret := C.git_commit_parent(&cobj, c.ptr, C.uint(n)) + if ret != 0 { + return nil + } + + return allocObject(cobj).(*Commit) +} + +func (c *Commit) ParentId(n uint) *Oid { + return newOidFromC(C.git_commit_parent_id(c.ptr, C.uint(n))) +} + +func (c *Commit) ParentCount() uint { + return uint(C.git_commit_parentcount(c.ptr)) +} + // Signature type Signature struct { @@ -74,10 +90,15 @@ func newSignatureFromC(sig *C.git_signature) *Signature { // the offset in mintes, which is what git wants func (v *Signature) Offset() int { _, offset := v.When.Zone() - return offset/60 + return offset / 60 } -func (sig *Signature) toC() (*C.git_signature) { +func (sig *Signature) toC() *C.git_signature { + + if sig == nil { + return nil + } + var out *C.git_signature name := C.CString(sig.Name) @@ -1,24 +1,97 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> */ import "C" import ( + "runtime" "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, LastError() + } + + 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 LastError() + } + + 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)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_config_get_int32(&out, c.ptr, cname) if ret < 0 { return 0, LastError() @@ -27,11 +100,14 @@ func (c *Config) LookupInt32(name string) (v int32, err error) { 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)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_config_get_int64(&out, c.ptr, cname) if ret < 0 { return 0, LastError() @@ -40,26 +116,108 @@ func (c *Config) LookupInt64(name string) (v int64, err error) { 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)) - ret := C.git_config_get_string(&ptr, c.ptr, cname) - if ret < 0 { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_config_get_string(&ptr, c.ptr, cname); ret < 0 { return "", LastError() } 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, LastError() + } + + 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, LastError() + } + + 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, LastError() + } + + 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, LastError() + } + + return iter, nil +} + +func (c *Config) SetString(name, value string) (err error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) cvalue := C.CString(value) defer C.free(unsafe.Pointer(cvalue)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_config_set_string(c.ptr, cname, cvalue) if ret < 0 { return LastError() @@ -67,3 +225,153 @@ func (c *Config) Set(name, value string) (err error) { return nil } + +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 LastError() + } + + 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 LastError() + } + + 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 LastError() + } + + 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 LastError() + } + + 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 LastError() + } + + 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, LastError() + } + + 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, LastError() + } + + 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 LastError() + } + + 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, LastError() + } + + return newConfigEntryFromC(centry), nil +} + +func (iter *ConfigIterator) Free() { + runtime.SetFinalizer(iter, nil) + C.free(unsafe.Pointer(iter.ptr)) +} + @@ -8,6 +8,8 @@ package git import "C" import ( "bytes" + "errors" + "runtime" "unsafe" "strings" ) @@ -18,6 +20,10 @@ const ( ENOTFOUND = C.GIT_ENOTFOUND ) +var ( + ErrIterOver = errors.New("Iteration is over") +) + func init() { C.git_threads_init() } @@ -52,6 +58,9 @@ func NewOidFromString(s string) (*Oid, error) { 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() } @@ -84,7 +93,7 @@ func (oid *Oid) Equal(oid2 *Oid) bool { } func (oid *Oid) IsZero() bool { - for _, a := range(oid.bytes) { + for _, a := range oid.bytes { if a != '0' { return false } @@ -96,29 +105,56 @@ func (oid *Oid) NCmp(oid2 *Oid, n uint) int { return bytes.Compare(oid.bytes[:n], oid2.bytes[:n]) } +func ShortenOids(ids []*Oid, minlen int) (int, error) { + shorten := C.git_oid_shorten_new(C.size_t(minlen)) + if shorten == nil { + panic("Out of memory") + } + defer C.git_oid_shorten_free(shorten) + + var ret C.int + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + for _, id := range ids { + buf := make([]byte, 41) + C.git_oid_fmt((*C.char)(unsafe.Pointer(&buf[0])), id.toC()) + 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), nil +} + type GitError struct { Message string - Code int + Code int } -func (e GitError) Error() string{ +func (e GitError) Error() string { return e.Message } func LastError() error { err := C.giterr_last() + if err == nil { + return &GitError{"No message", 0} + } return &GitError{C.GoString(err.message), int(err.klass)} } func cbool(b bool) C.int { - if (b) { + if b { return C.int(1) } return C.int(0) } func ucbool(b bool) C.uint { - if (b) { + if b { return C.uint(1) } return C.uint(0) @@ -131,14 +167,16 @@ func Discover(start string, across_fs bool, ceiling_dirs []string) (string, erro cstart := C.CString(start) defer C.free(unsafe.Pointer(cstart)) - retpath := (*C.char)(C.malloc(C.GIT_PATH_MAX)) - defer C.free(unsafe.Pointer(retpath)) + var buf C.git_buf + defer C.git_buf_free(&buf) - r := C.git_repository_discover(retpath, C.GIT_PATH_MAX, cstart, cbool(across_fs), ceildirs) + runtime.LockOSThread() + defer runtime.UnlockOSThread() - if r == 0 { - return C.GoString(retpath), nil + ret := C.git_repository_discover(&buf, cstart, cbool(across_fs), ceildirs) + if ret < 0 { + return "", LastError() } - return "", LastError() + return C.GoString(buf.ptr), nil } diff --git a/git_test.go b/git_test.go new file mode 100644 index 0000000..52aea1d --- /dev/null +++ b/git_test.go @@ -0,0 +1,47 @@ +package git + +import ( + "testing" + "io/ioutil" + "time" +) + +func createTestRepo(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, false) + checkFatal(t, err) + + tmpfile := "README" + err = ioutil.WriteFile(path + "/" + tmpfile, []byte("foo\n"), 0644) + checkFatal(t, err) + + return repo +} + +func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) { + loc, err := time.LoadLocation("Europe/Berlin") + checkFatal(t, err) + sig := &Signature{ + Name: "Rand Om Hacker", + Email: "[email protected]", + When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), + } + + idx, err := repo.Index() + checkFatal(t, err) + err = idx.AddByPath("README") + checkFatal(t, err) + treeId, err := idx.WriteTree() + checkFatal(t, err) + + message := "This is a commit\n" + tree, err := repo.LookupTree(treeId) + checkFatal(t, err) + commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) + checkFatal(t, err) + + return commitId, treeId +} + @@ -1,7 +1,6 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> */ @@ -38,6 +37,9 @@ func (v *Index) AddByPath(path string) error { cstr := C.CString(path) defer C.free(unsafe.Pointer(cstr)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_index_add_bypath(v.ptr, cstr) if ret < 0 { return LastError() @@ -48,6 +50,10 @@ func (v *Index) AddByPath(path string) error { func (v *Index) WriteTree() (*Oid, error) { oid := new(Oid) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_index_write_tree(oid.toC(), v.ptr) if ret < 0 { return nil, LastError() @@ -56,6 +62,18 @@ func (v *Index) WriteTree() (*Oid, error) { return oid, nil } +func (v *Index) Write() (error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_index_write(v.ptr) + if ret < 0 { + return LastError() + } + + return nil +} + func (v *Index) Free() { runtime.SetFinalizer(v, nil) C.git_index_free(v.ptr) diff --git a/index_test.go b/index_test.go index fe6fb87..9828d0f 100644 --- a/index_test.go +++ b/index_test.go @@ -4,23 +4,8 @@ import ( "os" "runtime" "testing" - "io/ioutil" ) -func createTestRepo(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, false) - checkFatal(t, err) - - tmpfile := "README" - err = ioutil.WriteFile(path + "/" + tmpfile, []byte("foo\n"), 0644) - checkFatal(t, err) - - return repo -} - func TestCreateRepoAndStage(t *testing.T) { repo := createTestRepo(t) defer os.RemoveAll(repo.Workdir()) diff --git a/object.go b/object.go new file mode 100644 index 0000000..090be1f --- /dev/null +++ b/object.go @@ -0,0 +1,83 @@ +package git + +/* +#include <git2.h> +#include <git2/errors.h> +*/ +import "C" +import "runtime" + +type ObjectType int + +const ( + ObjectAny ObjectType = C.GIT_OBJ_ANY + ObjectBad = C.GIT_OBJ_BAD + ObjectCommit = C.GIT_OBJ_COMMIT + ObjectTree = C.GIT_OBJ_TREE + ObjectBlob = C.GIT_OBJ_BLOB + ObjectTag = C.GIT_OBJ_TAG +) + +type Object interface { + Free() + Id() *Oid + Type() ObjectType +} + +type gitObject struct { + ptr *C.git_object +} + +func (t ObjectType) String() (string) { + switch (t) { + case ObjectAny: + return "Any" + case ObjectBad: + return "Bad" + case ObjectCommit: + return "Commit" + case ObjectTree: + return "Tree" + case ObjectBlob: + return "Blob" + case ObjectTag: + return "Tag" + } + // Never reached + return "" +} + +func (o gitObject) Id() *Oid { + return newOidFromC(C.git_commit_id(o.ptr)) +} + +func (o gitObject) Type() ObjectType { + return ObjectType(C.git_object_type(o.ptr)) +} + +func (o *gitObject) Free() { + runtime.SetFinalizer(o, nil) + C.git_commit_free(o.ptr) +} + +func allocObject(cobj *C.git_object) Object { + + switch ObjectType(C.git_object_type(cobj)) { + case ObjectCommit: + commit := &Commit{gitObject{cobj}} + runtime.SetFinalizer(commit, (*Commit).Free) + return commit + + case ObjectTree: + tree := &Tree{gitObject{cobj}} + runtime.SetFinalizer(tree, (*Tree).Free) + return tree + + case ObjectBlob: + blob := &Blob{gitObject{cobj}} + runtime.SetFinalizer(blob, (*Blob).Free) + return blob + } + + return nil +} diff --git a/object_test.go b/object_test.go new file mode 100644 index 0000000..85daf78 --- /dev/null +++ b/object_test.go @@ -0,0 +1,77 @@ +package git + +import ( + "os" + "testing" +) + +func TestObjectPoymorphism(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + commitId, treeId := seedTestRepo(t, repo) + + var obj Object + + commit, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + obj = commit + if obj.Type() != ObjectCommit { + t.Fatalf("Wrong object type, expected commit, have %v", obj.Type()) + } + + commitTree, err := commit.Tree() + checkFatal(t, err) + commitTree.EntryCount() + + tree, err := repo.LookupTree(treeId) + checkFatal(t, err) + + obj = tree + if obj.Type() != ObjectTree { + t.Fatalf("Wrong object type, expected tree, have %v", obj.Type()) + } + + tree2, ok := obj.(*Tree) + if !ok { + t.Fatalf("Converting back to *Tree is not ok") + } + + entry := tree2.EntryByName("README") + if entry == nil { + t.Fatalf("Tree did not have expected \"README\" entry") + } + + if entry.Filemode != FilemodeBlob { + t.Fatal("Wrong filemode for \"README\"") + } + + _, ok = obj.(*Commit) + if ok { + t.Fatalf("*Tree is somehow the same as *Commit") + } + + obj, err = repo.Lookup(tree.Id()) + checkFatal(t, err) + + _, ok = obj.(*Tree) + if !ok { + t.Fatalf("Lookup creates the wrong type") + } + + if obj.Type() != ObjectTree { + t.Fatalf("Type() doesn't agree with dynamic type") + } + + obj, err = repo.RevparseSingle("HEAD") + checkFatal(t, err) + if obj.Type() != ObjectCommit || obj.Id().String() != commit.Id().String() { + t.Fatalf("Failed to parse the right revision") + } + + obj, err = repo.RevparseSingle("HEAD^{tree}") + checkFatal(t, err) + if obj.Type() != ObjectTree || obj.Id().String() != tree.Id().String() { + t.Fatalf("Failed to parse the right revision") + } +} @@ -1,9 +1,10 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> + +extern int _go_git_odb_foreach(git_odb *db, void *payload); */ import "C" import ( @@ -12,15 +13,6 @@ import ( "runtime" ) -var ( - OBJ_ANY = C.GIT_OBJ_ANY - OBJ_BAD = C.GIT_OBJ_BAD - OBJ_COMMIT = C.GIT_OBJ_COMMIT - OBJ_TREE = C.GIT_OBJ_TREE - OBJ_BLOB = C.GIT_OBJ_BLOB - OBJ_TAG = C.GIT_OBJ_TAG -) - type Odb struct { ptr *C.git_odb } @@ -30,9 +22,13 @@ func (v *Odb) Exists(oid *Oid) bool { return ret != 0 } -func (v *Odb) Write(data []byte, otype int) (oid *Oid, err error) { +func (v *Odb) Write(data []byte, otype ObjectType) (oid *Oid, err error) { oid = new(Oid) hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + 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 { @@ -44,6 +40,10 @@ func (v *Odb) Write(data []byte, otype int) (oid *Oid, err error) { func (v *Odb) Read(oid *Oid) (obj *OdbObject, err error) { obj = new(OdbObject) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_odb_read(&obj.ptr, v.ptr, oid.toC()) if ret < 0 { return nil, LastError() @@ -53,6 +53,75 @@ func (v *Odb) Read(oid *Oid) (obj *OdbObject, err error) { return } +//export odbForEachCb +func odbForEachCb(id *C.git_oid, payload unsafe.Pointer) int { + ch := *(*chan *Oid)(payload) + oid := newOidFromC(id) + // Because the channel is unbuffered, we never read our own data. If ch is + // readable, the user has sent something on it, which means we should + // abort. + select { + case ch <- oid: + case <-ch: + return -1 + } + return 0; +} + +func (v *Odb) forEachWrap(ch chan *Oid) { + C._go_git_odb_foreach(v.ptr, unsafe.Pointer(&ch)) + close(ch) +} + +func (v *Odb) ForEach() chan *Oid { + ch := make(chan *Oid, 0) + go v.forEachWrap(ch) + return ch +} + +// Hash determines the object-ID (sha1) of a data buffer. +func (v *Odb) Hash(data []byte, otype ObjectType) (oid *Oid, err error) { + oid = new(Oid) + header := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + ptr := unsafe.Pointer(header.Data) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_odb_hash(oid.toC(), ptr, C.size_t(header.Len), C.git_otype(otype)); + if ret < 0 { + err = LastError() + } + return +} + +// NewReadStream opens a read stream from the ODB. Reading from it will give you the +// contents of the object. +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() + } + + runtime.SetFinalizer(stream, (*OdbReadStream).Free) + return stream, nil +} + +// NewWriteStream opens a write stream to the ODB, which allows you to +// create a new object in the database. The size and type must be +// known in advance +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() + } + + runtime.SetFinalizer(stream, (*OdbWriteStream).Free) + return stream, nil +} + type OdbObject struct { ptr *C.git_odb_object } @@ -84,3 +153,67 @@ func (object *OdbObject) Data() (data []byte) { return blob } +type OdbReadStream struct { + ptr *C.git_odb_stream +} + +// Read reads from the stream +func (stream *OdbReadStream) Read(data []byte) (int, error) { + header := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + ptr := (*C.char)(unsafe.Pointer(header.Data)) + size := C.size_t(header.Cap) + ret := C.git_odb_stream_read(stream.ptr, ptr, size) + if ret < 0 { + return 0, LastError() + } + + header.Len = int(ret) + + return len(data), nil +} + +// Close is a dummy function in order to implement the Closer and +// ReadCloser interfaces +func (stream *OdbReadStream) Close() error { + return nil +} + +func (stream *OdbReadStream) Free() { + runtime.SetFinalizer(stream, nil) + C.git_odb_stream_free(stream.ptr) +} + +type OdbWriteStream struct { + ptr *C.git_odb_stream + Id Oid +} + +// Write writes to the stream +func (stream *OdbWriteStream) Write(data []byte) (int, error) { + header := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + ptr := (*C.char)(unsafe.Pointer(header.Data)) + size := C.size_t(header.Len) + + ret := C.git_odb_stream_write(stream.ptr, ptr, size) + if ret < 0 { + return 0, LastError() + } + + return len(data), nil +} + +// Close signals that all the data has been written and stores the +// resulting object id in the stream's Id field. +func (stream *OdbWriteStream) Close() error { + ret := C.git_odb_stream_finalize_write(stream.Id.toC(), stream.ptr) + if ret < 0 { + return LastError() + } + + return nil +} + +func (stream *OdbWriteStream) Free() { + runtime.SetFinalizer(stream, nil) + C.git_odb_stream_free(stream.ptr) +} diff --git a/odb_test.go b/odb_test.go new file mode 100644 index 0000000..a4f8943 --- /dev/null +++ b/odb_test.go @@ -0,0 +1,62 @@ +package git + +import ( + "io" + "os" + "testing" +) + +func TestOdbStream(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + _, _ = seedTestRepo(t, repo) + + odb, error := repo.Odb() + checkFatal(t, error) + + str := "hello, world!" + + stream, error := odb.NewWriteStream(len(str), ObjectBlob) + checkFatal(t, error) + n, error := io.WriteString(stream, str) + checkFatal(t, error) + if n != len(str) { + t.Fatalf("Bad write length %v != %v", n, len(str)) + } + + error = stream.Close() + checkFatal(t, error) + + expectedId, error := NewOidFromString("30f51a3fba5274d53522d0f19748456974647b4f") + checkFatal(t, error) + if stream.Id.Cmp(expectedId) != 0 { + t.Fatal("Wrong data written") + } +} + +func TestOdbHash(t *testing.T) { + + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + _, _ = seedTestRepo(t, repo) + + odb, error := repo.Odb() + checkFatal(t, error) + + str := `tree 115fcae49287c82eb55bb275cbbd4556fbed72b7 +parent 66e1c476199ebcd3e304659992233132c5a52c6c +author John Doe <[email protected]> 1390682018 +0000 +committer John Doe <[email protected]> 1390682018 +0000 + +Initial commit.`; + + oid, error := odb.Hash([]byte(str), ObjectCommit) + checkFatal(t, error) + + coid, error := odb.Write([]byte(str), ObjectCommit) + checkFatal(t, error) + + 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 8cc03bd..333f183 100644 --- a/packbuilder.go +++ b/packbuilder.go @@ -1,7 +1,6 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> #include <git2/pack.h> @@ -11,9 +10,10 @@ extern int _go_git_packbuilder_foreach(git_packbuilder *pb, void *payload); */ import "C" import ( + "io" + "os" "runtime" "unsafe" - "io" ) type Packbuilder struct { @@ -22,6 +22,10 @@ type Packbuilder struct { func (repo *Repository) NewPackbuilder() (*Packbuilder, error) { builder := &Packbuilder{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_packbuilder_new(&builder.ptr, repo.ptr) if ret != 0 { return nil, LastError() @@ -38,6 +42,10 @@ func (pb *Packbuilder) Free() { func (pb *Packbuilder) Insert(id *Oid, name string) error { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_packbuilder_insert(pb.ptr, id.toC(), cname) if ret != 0 { return LastError() @@ -46,6 +54,9 @@ func (pb *Packbuilder) Insert(id *Oid, name string) error { } func (pb *Packbuilder) InsertCommit(id *Oid) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_packbuilder_insert_commit(pb.ptr, id.toC()) if ret != 0 { return LastError() @@ -54,6 +65,9 @@ func (pb *Packbuilder) InsertCommit(id *Oid) error { } func (pb *Packbuilder) InsertTree(id *Oid) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_packbuilder_insert_tree(pb.ptr, id.toC()) if ret != 0 { return LastError() @@ -65,10 +79,14 @@ func (pb *Packbuilder) ObjectCount() uint32 { return uint32(C.git_packbuilder_object_count(pb.ptr)) } -func (pb *Packbuilder) WriteToFile(name string) error { +func (pb *Packbuilder) WriteToFile(name string, mode os.FileMode) error { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) - ret := C.git_packbuilder_write(pb.ptr, cname) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_packbuilder_write(pb.ptr, cname, C.uint(mode.Perm()), nil, nil) if ret != 0 { return LastError() } @@ -76,10 +94,11 @@ func (pb *Packbuilder) WriteToFile(name string) error { } func (pb *Packbuilder) Write(w io.Writer) error { - ch := pb.ForEach() + ch, stop := pb.ForEach() for slice := range ch { _, err := w.Write(slice) if err != nil { + close(stop) return err } } @@ -90,22 +109,40 @@ func (pb *Packbuilder) Written() uint32 { return uint32(C.git_packbuilder_written(pb.ptr)) } +type packbuilderCbData struct { + ch chan<- []byte + stop <-chan bool +} + //export packbuilderForEachCb func packbuilderForEachCb(buf unsafe.Pointer, size C.size_t, payload unsafe.Pointer) int { - ch := *(*chan []byte)(payload) + data := (*packbuilderCbData)(payload) + ch := data.ch + stop := data.stop slice := C.GoBytes(buf, C.int(size)) - ch <- slice + select { + case <- stop: + return -1 + case ch <- slice: + } + return 0 } -func (pb *Packbuilder) forEachWrap(ch chan []byte) { - C._go_git_packbuilder_foreach(pb.ptr, unsafe.Pointer(&ch)) - close(ch) +func (pb *Packbuilder) forEachWrap(data *packbuilderCbData) { + C._go_git_packbuilder_foreach(pb.ptr, unsafe.Pointer(data)) + close(data.ch) } -func (pb *Packbuilder) ForEach() chan []byte { - ch := make(chan []byte, 0) - go pb.forEachWrap(ch) - return ch +// Foreach sends the packfile as slices through the "data" channel. If +// you want to stop the pack-building process (e.g. there's an error +// writing to the output), close or write a value into the "stop" +// channel. +func (pb *Packbuilder) ForEach() (<-chan []byte, chan<- bool) { + ch := make(chan []byte) + stop := make(chan bool) + data := packbuilderCbData{ch, stop} + go pb.forEachWrap(&data) + return ch, stop } diff --git a/reference.go b/reference.go index 820d166..a2f1636 100644 --- a/reference.go +++ b/reference.go @@ -1,7 +1,6 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> */ @@ -11,9 +10,11 @@ import ( "unsafe" ) -var ( - SYMBOLIC = C.GIT_REF_SYMBOLIC - OID = C.GIT_REF_OID +type ReferenceType int + +const ( + ReferenceSymbolic ReferenceType = C.GIT_REF_SYMBOLIC + ReferenceOid = C.GIT_REF_OID ) type Reference struct { @@ -27,12 +28,22 @@ func newReferenceFromC(ptr *C.git_reference) *Reference { return ref } -func (v *Reference) SetSymbolicTarget(target string) (*Reference, error) { +func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string) (*Reference, error) { var ptr *C.git_reference + ctarget := C.CString(target) defer C.free(unsafe.Pointer(ctarget)) - ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + csig := sig.toC() + defer C.free(unsafe.Pointer(csig)) + + 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() } @@ -40,10 +51,19 @@ func (v *Reference) SetSymbolicTarget(target string) (*Reference, error) { return newReferenceFromC(ptr), nil } -func (v *Reference) SetTarget(target *Oid) (*Reference, error) { +func (v *Reference) SetTarget(target *Oid, sig *Signature, msg string) (*Reference, error) { var ptr *C.git_reference - ret := C.git_reference_set_target(&ptr, v.ptr, target.toC()) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + csig := sig.toC() + defer C.free(unsafe.Pointer(csig)) + + 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() } @@ -54,6 +74,9 @@ func (v *Reference) SetTarget(target *Oid) (*Reference, error) { func (v *Reference) Resolve() (*Reference, error) { var ptr *C.git_reference + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_reference_resolve(&ptr, v.ptr) if ret < 0 { return nil, LastError() @@ -62,12 +85,21 @@ func (v *Reference) Resolve() (*Reference, error) { return newReferenceFromC(ptr), nil } -func (v *Reference) Rename(name string, force bool) (*Reference, error) { +func (v *Reference) Rename(name string, force bool, sig *Signature, msg string) (*Reference, error) { var ptr *C.git_reference cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) - ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force)) + csig := sig.toC() + defer C.free(unsafe.Pointer(csig)) + + cmsg := C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), csig, cmsg) if ret < 0 { return nil, LastError() @@ -90,6 +122,9 @@ func (v *Reference) SymbolicTarget() string { } func (v *Reference) Delete() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_reference_delete(v.ptr) if ret < 0 { @@ -103,11 +138,140 @@ func (v *Reference) Name() string { return C.GoString(C.git_reference_name(v.ptr)) } -func (v *Reference) Type() int { - return int(C.git_reference_type(v.ptr)) +func (v *Reference) Type() ReferenceType { + return ReferenceType(C.git_reference_type(v.ptr)) +} + +func (v *Reference) IsBranch() bool { + return C.git_reference_is_branch(v.ptr) == 1 +} + +func (v *Reference) IsRemote() bool { + return C.git_reference_is_remote(v.ptr) == 1 +} + +func (v *Reference) IsTag() bool { + return C.git_reference_is_tag(v.ptr) == 1 } func (v *Reference) Free() { runtime.SetFinalizer(v, nil) C.git_reference_free(v.ptr) } + +type ReferenceIterator struct { + ptr *C.git_reference_iterator + repo *Repository +} + +// NewReferenceIterator creates a new iterator over reference names +func (repo *Repository) NewReferenceIterator() (*ReferenceIterator, error) { + var ptr *C.git_reference_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_iterator_new(&ptr, repo.ptr) + if ret < 0 { + return nil, LastError() + } + + iter := &ReferenceIterator{repo: repo, ptr: ptr} + runtime.SetFinalizer(iter, (*ReferenceIterator).Free) + return iter, nil +} + +// NewReferenceIteratorGlob creates an iterator over reference names +// that match the speicified glob. The glob is of the usual fnmatch +// type. +func (repo *Repository) NewReferenceIteratorGlob(glob string) (*ReferenceIterator, error) { + cstr := C.CString(glob) + defer C.free(unsafe.Pointer(cstr)) + var ptr *C.git_reference_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_iterator_glob_new(&ptr, repo.ptr, cstr) + if ret < 0 { + return nil, LastError() + } + + iter := &ReferenceIterator{repo: repo, ptr: ptr} + runtime.SetFinalizer(iter, (*ReferenceIterator).Free) + return iter, nil +} + +// NextName retrieves the next reference name. If the iteration is over, +// the returned error is git.ErrIterOver +func (v *ReferenceIterator) NextName() (string, error) { + var ptr *C.char + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_next_name(&ptr, v.ptr) + if ret == ITEROVER { + return "", ErrIterOver + } + if ret < 0 { + return "", LastError() + } + + return C.GoString(ptr), nil +} + +// Create a channel from the iterator. You can use range on the +// returned channel to iterate over all the references names. The channel +// will be closed in case any error is found. +func (v *ReferenceIterator) NameIter() <-chan string { + ch := make(chan string) + go func() { + defer close(ch) + name, err := v.NextName() + for err == nil { + ch <- name + name, err = v.NextName() + } + }() + + return ch +} + +// Next retrieves the next reference. If the iterationis over, the +// returned error is git.ErrIterOver +func (v *ReferenceIterator) Next() (*Reference, error) { + var ptr *C.git_reference + ret := C.git_reference_next(&ptr, v.ptr) + if ret == ITEROVER { + return nil, ErrIterOver + } + if ret < 0 { + return nil, LastError() + } + + return newReferenceFromC(ptr), nil +} + +// Create a channel from the iterator. You can use range on the +// returned channel to iterate over all the references names. The channel +// will be closed in case any error is found. +func (v *ReferenceIterator) Iter() <-chan *Reference { + ch := make(chan *Reference) + go func() { + defer close(ch) + name, err := v.Next() + for err == nil { + ch <- name + name, err = v.Next() + } + }() + + return ch +} + +// Free the reference iterator +func (v *ReferenceIterator) Free() { + runtime.SetFinalizer(v, nil) + C.git_reference_iterator_free(v.ptr) +} diff --git a/reference_test.go b/reference_test.go index 8043833..156960a 100644 --- a/reference_test.go +++ b/reference_test.go @@ -3,6 +3,7 @@ package git import ( "os" "runtime" + "sort" "testing" "time" ) @@ -11,6 +12,8 @@ func TestRefModification(t *testing.T) { repo := createTestRepo(t) defer os.RemoveAll(repo.Workdir()) + commitId, treeId := seedTestRepo(t, repo) + loc, err := time.LoadLocation("Europe/Berlin") checkFatal(t, err) sig := &Signature{ @@ -18,30 +21,16 @@ func TestRefModification(t *testing.T) { Email: "[email protected]", When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), } - - idx, err := repo.Index() - checkFatal(t, err) - err = idx.AddByPath("README") - checkFatal(t, err) - treeId, err := idx.WriteTree() - checkFatal(t, err) - - message := "This is a commit\n" - tree, err := repo.LookupTree(treeId) - checkFatal(t, err) - commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) - checkFatal(t, err) - - _, err = repo.CreateReference("refs/tags/tree", treeId, true) + _, err = repo.CreateReference("refs/tags/tree", treeId, true, sig, "testTreeTag") checkFatal(t, err) tag, err := repo.LookupReference("refs/tags/tree") checkFatal(t, err) - checkRefType(t, tag, OID) + checkRefType(t, tag, ReferenceOid) ref, err := repo.LookupReference("HEAD") checkFatal(t, err) - checkRefType(t, ref, SYMBOLIC) + checkRefType(t, ref, ReferenceSymbolic) if target := ref.Target(); target != nil { t.Fatalf("Expected nil *Oid, got %v", target) @@ -49,7 +38,7 @@ func TestRefModification(t *testing.T) { ref, err = ref.Resolve() checkFatal(t, err) - checkRefType(t, ref, OID) + checkRefType(t, ref, ReferenceOid) if target := ref.Target(); target == nil { t.Fatalf("Expected valid target got nil") @@ -63,15 +52,122 @@ func TestRefModification(t *testing.T) { t.Fatalf("Wrong ref target") } - _, err = tag.Rename("refs/tags/renamed", false) + _, err = tag.Rename("refs/tags/renamed", false, nil, "") checkFatal(t, err) tag, err = repo.LookupReference("refs/tags/renamed") checkFatal(t, err) - checkRefType(t, ref, OID) + checkRefType(t, ref, ReferenceOid) + +} + +func TestIterator(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + loc, err := time.LoadLocation("Europe/Berlin") + checkFatal(t, err) + sig := &Signature{ + Name: "Rand Om Hacker", + Email: "[email protected]", + When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), + } + + idx, err := repo.Index() + checkFatal(t, err) + err = idx.AddByPath("README") + checkFatal(t, err) + treeId, err := idx.WriteTree() + checkFatal(t, err) + message := "This is a commit\n" + tree, err := repo.LookupTree(treeId) + checkFatal(t, err) + commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) + checkFatal(t, err) + + _, err = repo.CreateReference("refs/heads/one", commitId, true, sig, "headOne") + checkFatal(t, err) + + _, err = repo.CreateReference("refs/heads/two", commitId, true, sig, "headTwo") + checkFatal(t, err) + + _, err = repo.CreateReference("refs/heads/three", commitId, true, sig, "headThree") + checkFatal(t, err) + + iter, err := repo.NewReferenceIterator() + checkFatal(t, err) + + var list []string + expected := []string{ + "refs/heads/master", + "refs/heads/one", + "refs/heads/three", + "refs/heads/two", + } + + // test some manual iteration + name, err := iter.NextName() + for err == nil { + list = append(list, name) + name, err = iter.NextName() + } + if err != ErrIterOver { + t.Fatal("Iteration not over") + } + + sort.Strings(list) + compareStringList(t, expected, list) + + // test the iterator for full refs, rather than just names + iter, err = repo.NewReferenceIterator() + checkFatal(t, err) + count := 0 + _, err = iter.Next() + for err == nil { + count++ + _, err = iter.Next() + } + if err != ErrIterOver { + t.Fatal("Iteration not over") + } + + if count != 4 { + t.Fatalf("Wrong number of references returned %v", count) + } + + // test the channel iteration + list = []string{} + iter, err = repo.NewReferenceIterator() + for name := range iter.NameIter() { + list = append(list, name) + } + + sort.Strings(list) + compareStringList(t, expected, list) + + iter, err = repo.NewReferenceIteratorGlob("refs/heads/t*") + expected = []string{ + "refs/heads/three", + "refs/heads/two", + } + + list = []string{} + for name := range iter.NameIter() { + list = append(list, name) + } + + compareStringList(t, expected, list) +} + +func compareStringList(t *testing.T, expected, actual []string) { + for i, v := range expected { + if actual[i] != v { + t.Fatalf("Bad list") + } + } } -func checkRefType(t *testing.T, ref *Reference, kind int) { +func checkRefType(t *testing.T, ref *Reference, kind ReferenceType) { if ref.Type() == kind { return } diff --git a/repository.go b/repository.go index b9a10ad..48c2b46 100644 --- a/repository.go +++ b/repository.go @@ -1,14 +1,13 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> */ import "C" import ( - "unsafe" "runtime" + "unsafe" ) // Repository @@ -22,6 +21,9 @@ func OpenRepository(path string) (*Repository, error) { cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_repository_open(&repo.ptr, cpath) if ret < 0 { return nil, LastError() @@ -37,6 +39,9 @@ func InitRepository(path string, isbare bool) (*Repository, error) { cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_repository_init(&repo.ptr, cpath, ucbool(isbare)) if ret < 0 { return nil, LastError() @@ -54,16 +59,24 @@ func (v *Repository) Free() { func (v *Repository) Config() (*Config, error) { config := new(Config) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_repository_config(&config.ptr, v.ptr) if ret < 0 { return nil, LastError() } + runtime.SetFinalizer(config, (*Config).Free) return config, nil } func (v *Repository) Index() (*Index, error) { var ptr *C.git_index + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_repository_index(&ptr, v.ptr) if ret < 0 { return nil, LastError() @@ -72,35 +85,49 @@ func (v *Repository) Index() (*Index, error) { return newIndexFromC(ptr), nil } -func (v *Repository) LookupTree(oid *Oid) (*Tree, error) { - tree := new(Tree) - ret := C.git_tree_lookup(&tree.ptr, v.ptr, oid.toC()) +func (v *Repository) lookupType(oid *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)) if ret < 0 { return nil, LastError() } - return tree, nil + return allocObject(ptr), nil } -func (v *Repository) LookupCommit(o *Oid) (*Commit, error) { - commit := new(Commit) - ecode := C.git_commit_lookup(&commit.ptr, v.ptr, o.toC()) - if ecode < 0 { - return nil, LastError() +func (v *Repository) Lookup(oid *Oid) (Object, error) { + return v.lookupType(oid, ObjectAny) +} + +func (v *Repository) LookupTree(oid *Oid) (*Tree, error) { + obj, err := v.lookupType(oid, ObjectTree) + if err != nil { + return nil, err } - return commit, nil + return obj.(*Tree), nil } -func (v *Repository) LookupBlob(o *Oid) (*Blob, error) { - blob := new(Blob) - ecode := C.git_blob_lookup(&blob.ptr, v.ptr, o.toC()) - if ecode < 0 { - return nil, LastError() +func (v *Repository) LookupCommit(oid *Oid) (*Commit, error) { + obj, err := v.lookupType(oid, ObjectCommit) + if err != nil { + return nil, err + } + + return obj.(*Commit), nil +} + +func (v *Repository) LookupBlob(oid *Oid) (*Blob, error) { + obj, err := v.lookupType(oid, ObjectBlob) + if err != nil { + return nil, err } - runtime.SetFinalizer(blob, (*Blob).Free) - return blob, nil + return obj.(*Blob), nil } func (v *Repository) LookupReference(name string) (*Reference, error) { @@ -108,6 +135,9 @@ func (v *Repository) LookupReference(name string) (*Reference, error) { defer C.free(unsafe.Pointer(cname)) var ptr *C.git_reference + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ecode := C.git_reference_lookup(&ptr, v.ptr, cname) if ecode < 0 { return nil, LastError() @@ -116,12 +146,22 @@ func (v *Repository) LookupReference(name string) (*Reference, error) { return newReferenceFromC(ptr), nil } -func (v *Repository) CreateReference(name string, oid *Oid, force bool) (*Reference, error) { +func (v *Repository) CreateReference(name string, oid *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 ptr *C.git_reference - ecode := C.git_reference_create(&ptr, v.ptr, cname, oid.toC(), cbool(force)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_reference_create(&ptr, v.ptr, cname, oid.toC(), cbool(force), csig, cmsg) if ecode < 0 { return nil, LastError() } @@ -129,14 +169,25 @@ func (v *Repository) CreateReference(name string, oid *Oid, force bool) (*Refere return newReferenceFromC(ptr), nil } -func (v *Repository) CreateSymbolicReference(name, target string, force bool) (*Reference, error) { +func (v *Repository) CreateSymbolicReference(name, target string, force bool, sig *Signature, msg string) (*Reference, error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) + ctarget := C.CString(target) defer C.free(unsafe.Pointer(ctarget)) + + csig := sig.toC() + defer C.free(unsafe.Pointer(csig)) + + cmsg := C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + var ptr *C.git_reference - ecode := C.git_reference_symbolic_create(&ptr, v.ptr, cname, ctarget, cbool(force)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_reference_symbolic_create(&ptr, v.ptr, cname, ctarget, cbool(force), csig, cmsg) if ecode < 0 { return nil, LastError() } @@ -146,6 +197,10 @@ func (v *Repository) CreateSymbolicReference(name, target string, force bool) (* func (v *Repository) Walk() (*RevWalk, error) { walk := new(RevWalk) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ecode := C.git_revwalk_new(&walk.ptr, v.ptr) if ecode < 0 { return nil, LastError() @@ -171,7 +226,7 @@ func (v *Repository) CreateCommit( var cparents []*C.git_commit = nil var parentsarg **C.git_commit = nil - nparents:= len(parents) + nparents := len(parents) if nparents > 0 { cparents = make([]*C.git_commit, nparents) for i, v := range parents { @@ -186,10 +241,13 @@ func (v *Repository) CreateCommit( committerSig := committer.toC() defer C.git_signature_free(committerSig) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_commit_create( oid.toC(), v.ptr, cref, authorSig, committerSig, - nil, cmsg, tree.ptr, C.int(nparents), parentsarg) + nil, cmsg, tree.ptr, C.size_t(nparents), parentsarg) if ret < 0 { return nil, LastError() @@ -205,6 +263,10 @@ func (v *Odb) Free() { func (v *Repository) Odb() (odb *Odb, err error) { odb = new(Odb) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if ret := C.git_repository_odb(&odb.ptr, v.ptr); ret < 0 { return nil, LastError() } @@ -217,7 +279,7 @@ func (repo *Repository) Path() string { return C.GoString(C.git_repository_path(repo.ptr)) } -func (repo *Repository) IsBare() (bool) { +func (repo *Repository) IsBare() bool { return C.git_repository_is_bare(repo.ptr) != 0 } @@ -229,6 +291,9 @@ func (repo *Repository) SetWorkdir(workdir string, updateGitlink bool) error { cstr := C.CString(workdir) defer C.free(unsafe.Pointer(cstr)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if C.git_repository_set_workdir(repo.ptr, cstr, cbool(updateGitlink)) < 0 { return LastError() } @@ -237,11 +302,32 @@ func (repo *Repository) SetWorkdir(workdir string, updateGitlink bool) error { func (v *Repository) TreeBuilder() (*TreeBuilder, error) { bld := new(TreeBuilder) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if ret := C.git_treebuilder_create(&bld.ptr, nil); ret < 0 { return nil, LastError() } + runtime.SetFinalizer(bld, (*TreeBuilder).Free) bld.repo = v return bld, nil } +func (v *Repository) RevparseSingle(spec string) (Object, error) { + cspec := C.CString(spec) + defer C.free(unsafe.Pointer(cspec)) + + var ptr *C.git_object + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_revparse_single(&ptr, v.ptr, cspec) + if ecode < 0 { + return nil, LastError() + } + + return allocObject(ptr), nil +} diff --git a/submodule.go b/submodule.go index e1e031b..9abf333 100644 --- a/submodule.go +++ b/submodule.go @@ -1,7 +1,6 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> @@ -9,6 +8,7 @@ extern int _go_git_visit_submodule(git_repository *repo, void *fct); */ import "C" import ( + "runtime" "unsafe" ) @@ -20,7 +20,7 @@ type Submodule struct { type SubmoduleUpdate int const ( - SubmoduleUpdateDefault SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_DEFAULT + SubmoduleUpdateReset SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_RESET SubmoduleUpdateCheckout = C.GIT_SUBMODULE_UPDATE_CHECKOUT SubmoduleUpdateRebase = C.GIT_SUBMODULE_UPDATE_REBASE SubmoduleUpdateMerge = C.GIT_SUBMODULE_UPDATE_MERGE @@ -30,7 +30,7 @@ const ( type SubmoduleIgnore int const ( - SubmoduleIgnoreDefault SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_DEFAULT + SubmoduleIgnoreReset SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_RESET SubmoduleIgnoreNone = C.GIT_SUBMODULE_IGNORE_NONE SubmoduleIgnoreUntracked = C.GIT_SUBMODULE_IGNORE_UNTRACKED SubmoduleIgnoreDirty = C.GIT_SUBMODULE_IGNORE_DIRTY @@ -56,6 +56,14 @@ const ( SubmoduleStatusWdUntracked = C.GIT_SUBMODULE_STATUS_WD_UNTRACKED ) +type SubmoduleRecurse int + +const ( + SubmoduleRecurseNo SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_NO + SubmoduleRecurseYes = C.GIT_SUBMODULE_RECURSE_YES + SubmoduleRecurseOndemand = C.GIT_SUBMODULE_RECURSE_ONDEMAND +) + func SubmoduleStatusIsUnmodified(status int) bool { o := SubmoduleStatus(status) & ^(SubmoduleStatusInHead | SubmoduleStatusInIndex | SubmoduleStatusInConfig | SubmoduleStatusInWd) @@ -67,6 +75,10 @@ func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) { defer C.free(unsafe.Pointer(cname)) sub := new(Submodule) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_lookup(&sub.ptr, repo.ptr, cname) if ret < 0 { return nil, LastError() @@ -85,6 +97,9 @@ func SubmoduleVisitor(csub unsafe.Pointer, name string, cfct unsafe.Pointer) int } func (repo *Repository) ForeachSubmodule(cbk SubmoduleCbk) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C._go_git_visit_submodule(repo.ptr, unsafe.Pointer(&cbk)) if ret < 0 { return LastError() @@ -99,6 +114,10 @@ func (repo *Repository) AddSubmodule(url, path string, use_git_link bool) (*Subm defer C.free(unsafe.Pointer(cpath)) sub := new(Submodule) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_add_setup(&sub.ptr, repo.ptr, curl, cpath, cbool(use_git_link)) if ret < 0 { return nil, LastError() @@ -107,6 +126,9 @@ func (repo *Repository) AddSubmodule(url, path string, use_git_link bool) (*Subm } func (sub *Submodule) FinalizeAdd() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_add_finalize(sub.ptr) if ret < 0 { return LastError() @@ -115,6 +137,9 @@ func (sub *Submodule) FinalizeAdd() error { } func (sub *Submodule) AddToIndex(write_index bool) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_add_to_index(sub.ptr, cbool(write_index)) if ret < 0 { return LastError() @@ -123,6 +148,9 @@ func (sub *Submodule) AddToIndex(write_index bool) error { } func (sub *Submodule) Save() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_save(sub.ptr) if ret < 0 { return LastError() @@ -155,6 +183,9 @@ func (sub *Submodule) SetUrl(url string) error { curl := C.CString(url) defer C.free(unsafe.Pointer(curl)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_set_url(sub.ptr, curl) if ret < 0 { return LastError() @@ -206,15 +237,15 @@ func (sub *Submodule) SetUpdate(update SubmoduleUpdate) SubmoduleUpdate { return SubmoduleUpdate(o) } -func (sub *Submodule) FetchRecurseSubmodules() bool { - if 0 == C.git_submodule_fetch_recurse_submodules(sub.ptr) { - return false - } - return true +func (sub *Submodule) FetchRecurseSubmodules() SubmoduleRecurse { + return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr)); } -func (sub *Submodule) SetFetchRecurseSubmodules(v bool) error { - ret := C.git_submodule_set_fetch_recurse_submodules(sub.ptr, cbool(v)) +func (sub *Submodule) SetFetchRecurseSubmodules(recurse SubmoduleRecurse) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_set_fetch_recurse_submodules(sub.ptr, C.git_submodule_recurse_t(recurse)) if ret < 0 { return LastError() } @@ -222,6 +253,9 @@ func (sub *Submodule) SetFetchRecurseSubmodules(v bool) error { } func (sub *Submodule) Init(overwrite bool) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_init(sub.ptr, cbool(overwrite)) if ret < 0 { return LastError() @@ -230,6 +264,9 @@ func (sub *Submodule) Init(overwrite bool) error { } func (sub *Submodule) Sync() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_sync(sub.ptr) if ret < 0 { return LastError() @@ -239,6 +276,10 @@ func (sub *Submodule) Sync() error { func (sub *Submodule) Open() (*Repository, error) { repo := new(Repository) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_open(&repo.ptr, sub.ptr) if ret < 0 { return nil, LastError() @@ -247,6 +288,9 @@ func (sub *Submodule) Open() (*Repository, error) { } func (sub *Submodule) Reload() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_reload(sub.ptr) if ret < 0 { return LastError() @@ -255,6 +299,9 @@ func (sub *Submodule) Reload() error { } func (repo *Repository) ReloadAllSubmodules() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_submodule_reload_all(repo.ptr) if ret < 0 { return LastError() @@ -13,41 +13,37 @@ import ( "unsafe" ) +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 +) + type Tree struct { - ptr *C.git_tree + gitObject } type TreeEntry struct { Name string Id *Oid - Type int + Type ObjectType + Filemode int } func newTreeEntry(entry *C.git_tree_entry) *TreeEntry { return &TreeEntry{ C.GoString(C.git_tree_entry_name(entry)), newOidFromC(C.git_tree_entry_id(entry)), - int(C.git_tree_entry_type(entry)), + ObjectType(C.git_tree_entry_type(entry)), + int(C.git_tree_entry_filemode(entry)), } } -func (t *Tree) Free() { - runtime.SetFinalizer(t, nil) - C.git_tree_free(t.ptr) -} - -func TreeLookup(repo *Repository, oid *Oid) (*Tree, error) { - tree := new(Tree) - err := C.git_tree_lookup(&tree.ptr, repo.ptr, oid.toC()) - if err < 0 { - return nil, LastError() - } - - runtime.SetFinalizer(tree, (*Tree).Free) - return tree, nil -} - -func (t *Tree) EntryByName(filename string) *TreeEntry { +func (t Tree) EntryByName(filename string) *TreeEntry { cname := C.CString(filename) defer C.free(unsafe.Pointer(cname)) @@ -59,7 +55,25 @@ func (t *Tree) EntryByName(filename string) *TreeEntry { return newTreeEntry(entry) } -func (t *Tree) EntryByIndex(index uint64) *TreeEntry { +// EntryByPath looks up an entry by its full path, recursing into +// deeper trees if necessary (i.e. if there are slashes in the path) +func (t Tree) EntryByPath(path string) (*TreeEntry, error) { + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + var entry *C.git_tree_entry + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_tree_entry_bypath(&entry, t.ptr, cpath) + if ret < 0 { + return nil, LastError() + } + + return newTreeEntry(entry), nil +} + +func (t Tree) EntryByIndex(index uint64) *TreeEntry { entry := C.git_tree_entry_byindex(t.ptr, C.size_t(index)) if entry == nil { return nil @@ -68,7 +82,7 @@ func (t *Tree) EntryByIndex(index uint64) *TreeEntry { return newTreeEntry(entry) } -func (t *Tree) EntryCount() uint64 { +func (t Tree) EntryCount() uint64 { num := C.git_tree_entrycount(t.ptr) return uint64(num) } @@ -84,7 +98,10 @@ func CallbackGitTreeWalk(_root unsafe.Pointer, _entry unsafe.Pointer, ptr unsafe return C.int(callback(root, newTreeEntry(entry))) } -func (t *Tree) Walk(callback TreeWalkCallback) error { +func (t Tree) Walk(callback TreeWalkCallback) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + err := C._go_git_treewalk( t.ptr, C.GIT_TREEWALK_PRE, @@ -112,6 +129,9 @@ func (v *TreeBuilder) Insert(filename string, id *Oid, filemode int) (error) { cfilename := C.CString(filename) defer C.free(unsafe.Pointer(cfilename)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + err := C.git_treebuilder_insert(nil, v.ptr, cfilename, id.toC(), C.git_filemode_t(filemode)) if err < 0 { return LastError() @@ -122,6 +142,10 @@ func (v *TreeBuilder) Insert(filename string, id *Oid, filemode int) (error) { func (v *TreeBuilder) Write() (*Oid, error) { oid := new(Oid) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + err := C.git_treebuilder_write(oid.toC(), v.repo.ptr, v.ptr) if err < 0 { @@ -1,7 +1,6 @@ package git /* -#cgo pkg-config: libgit2 #include <git2.h> #include <git2/errors.h> */ @@ -9,15 +8,17 @@ import "C" import ( "io" + "runtime" ) // RevWalk +type SortType uint const ( - SORT_NONE = C.GIT_SORT_NONE - SORT_TOPOLOGICAL = C.GIT_SORT_TOPOLOGICAL - SORT_TIME = C.GIT_SORT_TIME - SORT_REVERSE = 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 { @@ -34,6 +35,9 @@ func (v *RevWalk) Push(id *Oid) { } func (v *RevWalk) PushHead() (err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ecode := C.git_revwalk_push_head(v.ptr) if ecode < 0 { err = LastError() @@ -43,6 +47,9 @@ func (v *RevWalk) PushHead() (err error) { } func (v *RevWalk) Next(oid *Oid) (err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_revwalk_next(oid.toC(), v.ptr) switch { case ret == ITEROVER: @@ -81,7 +88,7 @@ func (v *RevWalk) Iterate(fun RevWalkIterator) (err error) { return nil } -func (v *RevWalk) Sorting(sm uint) { +func (v *RevWalk) Sorting(sm SortType) { C.git_revwalk_sorting(v.ptr, C.uint(sm)) } @@ -20,4 +20,8 @@ int _go_git_packbuilder_foreach(git_packbuilder *pb, void *payload) return git_packbuilder_foreach(pb, (git_packbuilder_foreach_cb)&packbuilderForEachCb, payload); } +int _go_git_odb_foreach(git_odb *db, void *payload) +{ + return git_odb_foreach(db, (git_odb_foreach_cb)&odbForEachCb, payload); +} /* EOF */ |
