summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--blame.go6
-rw-r--r--blob.go2
-rw-r--r--checkout.go5
-rw-r--r--config.go29
-rw-r--r--config_test.go108
-rw-r--r--diff.go6
-rw-r--r--git.go22
-rw-r--r--handles.go58
-rw-r--r--ignore.go51
-rw-r--r--index.go66
-rw-r--r--merge.go67
-rw-r--r--odb.go26
-rw-r--r--odb_test.go28
-rw-r--r--remote.go56
-rw-r--r--remote_test.go8
-rw-r--r--repository.go39
-rw-r--r--tree.go2
-rw-r--r--tree_test.go41
m---------vendor/libgit20
-rw-r--r--walk.go4
-rw-r--r--wrapper.c23
22 files changed, 538 insertions, 111 deletions
diff --git a/README.md b/README.md
index a5e6100..315032f 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ Go bindings for [libgit2](http://libgit2.github.com/). The `master` branch follo
Installing
----------
-This project wraps the functionality provided by libgit2. If you're using a stable version, install it to your system via your system's package manaager and then install git2go as usual.
+This project wraps the functionality provided by libgit2. If you're using a stable version, install it to your system via your system's package manager and then install git2go as usual.
Otherwise (`next` which tracks an unstable version), we need to build libgit2 as well. In order to build it, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively.
diff --git a/blame.go b/blame.go
index c24c934..b07d6bc 100644
--- a/blame.go
+++ b/blame.go
@@ -58,8 +58,8 @@ func (v *Repository) BlameFile(path string, opts *BlameOptions) (*Blame, error)
version: C.GIT_BLAME_OPTIONS_VERSION,
flags: C.uint32_t(opts.Flags),
min_match_characters: C.uint16_t(opts.MinMatchCharacters),
- min_line: C.uint32_t(opts.MinLine),
- max_line: C.uint32_t(opts.MaxLine),
+ min_line: C.size_t(opts.MinLine),
+ max_line: C.size_t(opts.MaxLine),
}
if opts.NewestCommit != nil {
copts.newest_commit = *opts.NewestCommit.toC()
@@ -100,7 +100,7 @@ func (blame *Blame) HunkByIndex(index int) (BlameHunk, error) {
}
func (blame *Blame) HunkByLine(lineno int) (BlameHunk, error) {
- ptr := C.git_blame_get_hunk_byline(blame.ptr, C.uint32_t(lineno))
+ ptr := C.git_blame_get_hunk_byline(blame.ptr, C.size_t(lineno))
if ptr == nil {
return BlameHunk{}, ErrInvalid
}
diff --git a/blob.go b/blob.go
index 16ec183..1a86e60 100644
--- a/blob.go
+++ b/blob.go
@@ -84,7 +84,7 @@ func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunk
var chintPath *C.char = nil
if len(hintPath) > 0 {
- C.CString(hintPath)
+ chintPath = C.CString(hintPath)
defer C.free(unsafe.Pointer(chintPath))
}
oid := C.git_oid{}
diff --git a/checkout.go b/checkout.go
index ce2f469..a2e312b 100644
--- a/checkout.go
+++ b/checkout.go
@@ -45,6 +45,7 @@ type CheckoutOpts struct {
FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY
TargetDirectory string // Alternative checkout path to workdir
Paths []string
+ Baseline *Tree
}
func checkoutOptionsFromC(c *C.git_checkout_options) CheckoutOpts {
@@ -90,6 +91,10 @@ func populateCheckoutOpts(ptr *C.git_checkout_options, opts *CheckoutOpts) *C.gi
ptr.paths.count = C.size_t(len(opts.Paths))
}
+ if opts.Baseline != nil {
+ ptr.baseline = opts.Baseline.cast_ptr
+ }
+
return ptr
}
diff --git a/config.go b/config.go
index 9d25e35..7408fbc 100644
--- a/config.go
+++ b/config.go
@@ -12,6 +12,9 @@ import (
type ConfigLevel int
const (
+ // System-wide on Windows, for compatibility with portable git
+ ConfigLevelProgramdata ConfigLevel = C.GIT_CONFIG_LEVEL_PROGRAMDATA
+
// System-wide configuration file; /etc/gitconfig on Linux systems
ConfigLevelSystem ConfigLevel = C.GIT_CONFIG_LEVEL_SYSTEM
@@ -115,18 +118,20 @@ func (c *Config) LookupInt64(name string) (int64, error) {
}
func (c *Config) LookupString(name string) (string, error) {
- var ptr *C.char
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
+ valBuf := C.git_buf{}
+
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- if ret := C.git_config_get_string(&ptr, c.ptr, cname); ret < 0 {
+ if ret := C.git_config_get_string_buf(&valBuf, c.ptr, cname); ret < 0 {
return "", MakeGitError(ret)
}
+ defer C.git_buf_free(&valBuf)
- return C.GoString(ptr), nil
+ return C.GoString(valBuf.ptr), nil
}
func (c *Config) LookupBool(name string) (bool, error) {
@@ -410,3 +415,21 @@ func ConfigFindXDG() (string, error) {
return C.GoString(buf.ptr), nil
}
+
+// ConfigFindProgramdata locate the path to the configuration file in ProgramData.
+//
+// Look for the file in %PROGRAMDATA%\Git\config used by portable git.
+func ConfigFindProgramdata() (string, error) {
+ var buf C.git_buf
+ defer C.git_buf_free(&buf)
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_config_find_programdata(&buf)
+ if ret < 0 {
+ return "", MakeGitError(ret)
+ }
+
+ return C.GoString(buf.ptr), nil
+}
diff --git a/config_test.go b/config_test.go
new file mode 100644
index 0000000..fea8d8a
--- /dev/null
+++ b/config_test.go
@@ -0,0 +1,108 @@
+package git
+
+import (
+ "os"
+ "testing"
+)
+
+var tempConfig = "./temp.gitconfig"
+
+func setupConfig() (*Config, error) {
+ var (
+ c *Config
+ err error
+ )
+
+ c, err = OpenOndisk(nil, tempConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ err = c.SetString("foo.bar", "baz")
+ if err != nil {
+ return nil, err
+ }
+ err = c.SetBool("foo.bool", true)
+ if err != nil {
+ return nil, err
+ }
+ err = c.SetInt32("foo.int32", 32)
+ if err != nil {
+ return nil, err
+ }
+ err = c.SetInt64("foo.int64", 64)
+ if err != nil {
+ return nil, err
+ }
+
+ return c, err
+}
+
+func cleanupConfig() {
+ os.Remove(tempConfig)
+}
+
+type TestRunner func(*Config, *testing.T)
+
+var tests = []TestRunner{
+ // LookupString
+ func(c *Config, t *testing.T) {
+ val, err := c.LookupString("foo.bar")
+ if err != nil {
+ t.Errorf("Got LookupString error: '%v', expected none\n", err)
+ }
+ if val != "baz" {
+ t.Errorf("Got '%s' from LookupString, expected 'bar'\n", val)
+ }
+ },
+ // LookupBool
+ func(c *Config, t *testing.T) {
+ val, err := c.LookupBool("foo.bool")
+ if err != nil {
+ t.Errorf("Got LookupBool error: '%v', expected none\n", err)
+ }
+ if !val {
+ t.Errorf("Got %b from LookupBool, expected 'false'\n", val)
+ }
+ },
+ // LookupInt32
+ func(c *Config, t *testing.T) {
+ val, err := c.LookupInt32("foo.int32")
+ if err != nil {
+ t.Errorf("Got LookupInt32 error: '%v', expected none\n", err)
+ }
+ if val != 32 {
+ t.Errorf("Got %v, expected 32\n", val)
+ }
+ },
+ // LookupInt64
+ func(c *Config, t *testing.T) {
+ val, err := c.LookupInt64("foo.int64")
+ if err != nil {
+ t.Errorf("Got LookupInt64 error: '%v', expected none\n", err)
+ }
+ if val != 64 {
+ t.Errorf("Got %v, expected 64\n", val)
+ }
+ },
+}
+
+func TestConfigLookups(t *testing.T) {
+ var (
+ err error
+ c *Config
+ )
+
+ c, err = setupConfig()
+ defer cleanupConfig()
+
+ if err != nil {
+ t.Errorf("Setup error: '%v'. Expected none\n", err)
+ return
+ }
+ defer c.Free()
+
+ for _, test := range tests {
+ test(c, t)
+ }
+}
diff --git a/diff.go b/diff.go
index de56374..565fcee 100644
--- a/diff.go
+++ b/diff.go
@@ -550,7 +550,7 @@ func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *d
if opts.NotifyCallback != nil {
C._go_git_setup_diff_notify_callbacks(copts)
- copts.notify_payload = pointerHandles.Track(notifyData)
+ copts.payload = pointerHandles.Track(notifyData)
}
}
return
@@ -562,8 +562,8 @@ func freeDiffOptions(copts *C.git_diff_options) {
freeStrarray(&cpathspec)
C.free(unsafe.Pointer(copts.old_prefix))
C.free(unsafe.Pointer(copts.new_prefix))
- if copts.notify_payload != nil {
- pointerHandles.Untrack(copts.notify_payload)
+ if copts.payload != nil {
+ pointerHandles.Untrack(copts.payload)
}
}
}
diff --git a/git.go b/git.go
index 34d58e6..2b2a909 100644
--- a/git.go
+++ b/git.go
@@ -52,6 +52,7 @@ const (
// No error
ErrOk ErrorCode = C.GIT_OK
+
// Generic error
ErrGeneric ErrorCode = C.GIT_ERROR
// Requested object could not be found
@@ -62,10 +63,12 @@ const (
ErrAmbigious ErrorCode = C.GIT_EAMBIGUOUS
// Output buffer too short to hold data
ErrBuffs ErrorCode = C.GIT_EBUFS
+
// GIT_EUSER is a special error that is never generated by libgit2
// code. You can return it from a callback (e.g to stop an iteration)
// to know that it was generated by the callback and not by libgit2.
ErrUser ErrorCode = C.GIT_EUSER
+
// Operation not allowed on bare repository
ErrBareRepo ErrorCode = C.GIT_EBAREREPO
// HEAD refers to branch with no commits
@@ -82,12 +85,27 @@ const (
ErrLocked ErrorCode = C.GIT_ELOCKED
// Reference value does not match expected
ErrModified ErrorCode = C.GIT_EMODIFIED
+ // Authentication failed
+ ErrAuth ErrorCode = C.GIT_EAUTH
+ // Server certificate is invalid
+ ErrCertificate ErrorCode = C.GIT_ECERTIFICATE
+ // Patch/merge has already been applied
+ ErrApplied ErrorCode = C.GIT_EAPPLIED
+ // The requested peel operation is not possible
+ ErrPeel ErrorCode = C.GIT_EPEEL
+ // Unexpected EOF
+ ErrEOF ErrorCode = C.GIT_EEOF
+ // Uncommitted changes in index prevented operation
+ ErrUncommitted ErrorCode = C.GIT_EUNCOMMITTED
+ // The operation is not valid for a directory
+ ErrDirectory ErrorCode = C.GIT_EDIRECTORY
+ // A merge conflict exists and cannot continue
+ ErrMergeConflict ErrorCode = C.GIT_EMERGECONFLICT
+
// Internal only
ErrPassthrough ErrorCode = C.GIT_PASSTHROUGH
// Signals end of iteration with iterator
ErrIterOver ErrorCode = C.GIT_ITEROVER
- // Authentication failed
- ErrAuth ErrorCode = C.GIT_EAUTH
)
var (
diff --git a/handles.go b/handles.go
index a062231..d27d3c3 100644
--- a/handles.go
+++ b/handles.go
@@ -1,5 +1,9 @@
package git
+/*
+#include <stdlib.h>
+*/
+import "C"
import (
"fmt"
"sync"
@@ -9,77 +13,45 @@ import (
type HandleList struct {
sync.RWMutex
// stores the Go pointers
- handles []interface{}
- // Indicates which indices are in use, and keeps a pointer to slot int variable (the handle)
- // in the Go world, so that the Go garbage collector does not free it.
- set map[int]*int
+ handles map[unsafe.Pointer]interface{}
}
func NewHandleList() *HandleList {
return &HandleList{
- handles: make([]interface{}, 5),
- set: make(map[int]*int),
+ handles: make(map[unsafe.Pointer]interface{}),
}
}
-// findUnusedSlot finds the smallest-index empty space in our
-// list. You must only run this function while holding a write lock.
-func (v *HandleList) findUnusedSlot() int {
- for i := 1; i < len(v.handles); i++ {
- _, isUsed := v.set[i]
- if !isUsed {
- return i
- }
- }
-
- // reaching here means we've run out of entries so append and
- // return the new index, which is equal to the old length.
- slot := len(v.handles)
- v.handles = append(v.handles, nil)
-
- return slot
-}
-
// Track adds the given pointer to the list of pointers to track and
// returns a pointer value which can be passed to C as an opaque
// pointer.
func (v *HandleList) Track(pointer interface{}) unsafe.Pointer {
- v.Lock()
-
- slot := v.findUnusedSlot()
- v.handles[slot] = pointer
- v.set[slot] = &slot // Keep a pointer to slot in Go world, so it's not freed by GC.
+ handle := C.malloc(1)
+ v.Lock()
+ v.handles[handle] = pointer
v.Unlock()
- return unsafe.Pointer(&slot)
+ return handle
}
// Untrack stops tracking the pointer given by the handle
func (v *HandleList) Untrack(handle unsafe.Pointer) {
- slot := *(*int)(handle)
-
v.Lock()
-
- v.handles[slot] = nil
- delete(v.set, slot)
-
+ delete(v.handles, handle)
+ C.free(handle)
v.Unlock()
}
// Get retrieves the pointer from the given handle
func (v *HandleList) Get(handle unsafe.Pointer) interface{} {
- slot := *(*int)(handle)
-
v.RLock()
+ defer v.RUnlock()
- if _, ok := v.set[slot]; !ok {
+ ptr, ok := v.handles[handle]
+ if !ok {
panic(fmt.Sprintf("invalid pointer handle: %p", handle))
}
- ptr := v.handles[slot]
-
- v.RUnlock()
-
return ptr
}
diff --git a/ignore.go b/ignore.go
new file mode 100644
index 0000000..6b12348
--- /dev/null
+++ b/ignore.go
@@ -0,0 +1,51 @@
+package git
+
+/*
+#include <git2.h>
+*/
+import "C"
+import (
+ "runtime"
+ "unsafe"
+)
+
+func (v *Repository) AddIgnoreRule(rules string) error {
+ crules := C.CString(rules)
+ defer C.free(unsafe.Pointer(crules))
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_ignore_add_rule(v.ptr, crules)
+ if ret < 0 {
+ return MakeGitError(ret)
+ }
+ return nil
+}
+
+func (v *Repository) ClearInternalIgnoreRules() error {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_ignore_clear_internal_rules(v.ptr)
+ if ret < 0 {
+ return MakeGitError(ret)
+ }
+ return nil
+}
+
+func (v *Repository) IsPathIgnored(path string) (bool, error) {
+ var ignored C.int
+
+ cpath := C.CString(path)
+ defer C.free(unsafe.Pointer(cpath))
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ ret := C.git_ignore_path_is_ignored(&ignored, v.ptr, cpath)
+ if ret < 0 {
+ return false, MakeGitError(ret)
+ }
+ return ignored == 1, nil
+}
diff --git a/index.go b/index.go
index f4c0c1e..ae94864 100644
--- a/index.go
+++ b/index.go
@@ -26,6 +26,24 @@ const (
IndexAddCheckPathspec IndexAddOpts = C.GIT_INDEX_ADD_CHECK_PATHSPEC
)
+type IndexStageOpts int
+
+const (
+ // IndexStageAny matches any index stage.
+ //
+ // Some index APIs take a stage to match; pass this value to match
+ // any entry matching the path regardless of stage.
+ IndexStageAny IndexStageOpts = C.GIT_INDEX_STAGE_ANY
+ // IndexStageNormal is a normal staged file in the index.
+ IndexStageNormal IndexStageOpts = C.GIT_INDEX_STAGE_NORMAL
+ // IndexStageAncestor is the ancestor side of a conflict.
+ IndexStageAncestor IndexStageOpts = C.GIT_INDEX_STAGE_ANCESTOR
+ // IndexStageOurs is the "ours" side of a conflict.
+ IndexStageOurs IndexStageOpts = C.GIT_INDEX_STAGE_OURS
+ // IndexStageTheirs is the "theirs" side of a conflict.
+ IndexStageTheirs IndexStageOpts = C.GIT_INDEX_STAGE_THEIRS
+)
+
type Index struct {
ptr *C.git_index
}
@@ -97,7 +115,7 @@ func NewIndex() (*Index, error) {
return nil, MakeGitError(err)
}
- return &Index{ptr: ptr}, nil
+ return newIndexFromC(ptr), nil
}
// OpenIndex creates a new index at the given path. If the file does
@@ -115,7 +133,7 @@ func OpenIndex(path string) (*Index, error) {
return nil, MakeGitError(err)
}
- return &Index{ptr: ptr}, nil
+ return newIndexFromC(ptr), nil
}
// Path returns the index' path on disk or an empty string if it
@@ -331,6 +349,50 @@ func (v *Index) EntryByIndex(index uint) (*IndexEntry, error) {
return newIndexEntryFromC(centry), nil
}
+func (v *Index) EntryByPath(path string, stage int) (*IndexEntry, error) {
+ cpath := C.CString(path)
+ defer C.free(unsafe.Pointer(cpath))
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ centry := C.git_index_get_bypath(v.ptr, cpath, C.int(stage))
+ if centry == nil {
+ return nil, MakeGitError(C.GIT_ENOTFOUND)
+ }
+ return newIndexEntryFromC(centry), nil
+}
+
+func (v *Index) Find(path string) (uint, error) {
+ cpath := C.CString(path)
+ defer C.free(unsafe.Pointer(cpath))
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ var pos C.size_t
+ ret := C.git_index_find(&pos, v.ptr, cpath)
+ if ret < 0 {
+ return uint(0), MakeGitError(ret)
+ }
+ return uint(pos), nil
+}
+
+func (v *Index) FindPrefix(prefix string) (uint, error) {
+ cprefix := C.CString(prefix)
+ defer C.free(unsafe.Pointer(cprefix))
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ var pos C.size_t
+ ret := C.git_index_find_prefix(&pos, v.ptr, cprefix)
+ if ret < 0 {
+ return uint(0), MakeGitError(ret)
+ }
+ return uint(pos), nil
+}
+
func (v *Index) HasConflicts() bool {
return C.git_index_has_conflicts(v.ptr) != 0
}
diff --git a/merge.go b/merge.go
index a52e8f8..b3fd818 100644
--- a/merge.go
+++ b/merge.go
@@ -6,6 +6,7 @@ package git
extern git_annotated_commit** _go_git_make_merge_head_array(size_t len);
extern void _go_git_annotated_commit_array_set(git_annotated_commit** array, git_annotated_commit* ptr, size_t n);
extern git_annotated_commit* _go_git_annotated_commit_array_get(git_annotated_commit** array, size_t n);
+extern int _go_git_merge_file(git_merge_file_result*, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, git_merge_file_options*);
*/
import "C"
@@ -81,7 +82,14 @@ func (r *Repository) AnnotatedCommitFromRef(ref *Reference) (*AnnotatedCommit, e
type MergeTreeFlag int
const (
- MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_TREE_FIND_RENAMES
+ // Detect renames that occur between the common ancestor and the "ours"
+ // side or the common ancestor and the "theirs" side. This will enable
+ // the ability to merge between a modified and renamed file.
+ MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_FIND_RENAMES
+ // If a conflict occurs, exit immediately instead of attempting to
+ // continue resolving conflicts. The merge operation will fail with
+ // GIT_EMERGECONFLICT and no index will be returned.
+ MergeTreeFailOnConflict MergeTreeFlag = C.GIT_MERGE_FAIL_ON_CONFLICT
)
type MergeOptions struct {
@@ -98,7 +106,7 @@ type MergeOptions struct {
func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions {
return MergeOptions{
Version: uint(opts.version),
- TreeFlags: MergeTreeFlag(opts.tree_flags),
+ TreeFlags: MergeTreeFlag(opts.flags),
RenameThreshold: uint(opts.rename_threshold),
TargetLimit: uint(opts.target_limit),
FileFavor: MergeFileFavor(opts.file_favor),
@@ -124,7 +132,7 @@ func (mo *MergeOptions) toC() *C.git_merge_options {
}
return &C.git_merge_options{
version: C.uint(mo.Version),
- tree_flags: C.git_merge_tree_flag_t(mo.TreeFlags),
+ flags: C.git_merge_flag_t(mo.TreeFlags),
rename_threshold: C.uint(mo.RenameThreshold),
target_limit: C.uint(mo.TargetLimit),
file_favor: C.git_merge_file_favor_t(mo.FileFavor),
@@ -178,6 +186,9 @@ const (
MergePreferenceFastForwardOnly MergePreference = C.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY
)
+// MergeAnalysis returns the possible actions which could be taken by
+// a 'git-merge' command. There may be multiple answers, so the first
+// return value is a bitmask of MergeAnalysis values.
func (r *Repository) MergeAnalysis(theirHeads []*AnnotatedCommit) (MergeAnalysis, MergePreference, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@@ -317,20 +328,6 @@ type MergeFileInput struct {
Contents []byte
}
-// populate a C struct with merge file input, make sure to use freeMergeFileInput to clean up allocs
-func populateCMergeFileInput(c *C.git_merge_file_input, input MergeFileInput) {
- c.path = C.CString(input.Path)
- if input.Contents != nil {
- c.ptr = (*C.char)(unsafe.Pointer(&input.Contents[0]))
- c.size = C.size_t(len(input.Contents))
- }
- c.mode = C.uint(input.Mode)
-}
-
-func freeCMergeFileInput(c *C.git_merge_file_input) {
- C.free(unsafe.Pointer(c.path))
-}
-
type MergeFileFlags int
const (
@@ -364,7 +361,7 @@ func populateCMergeFileOptions(c *C.git_merge_file_options, options MergeFileOpt
c.our_label = C.CString(options.OurLabel)
c.their_label = C.CString(options.TheirLabel)
c.favor = C.git_merge_file_favor_t(options.Favor)
- c.flags = C.uint(options.Flags)
+ c.flags = C.git_merge_file_flag_t(options.Flags)
}
func freeCMergeFileOptions(c *C.git_merge_file_options) {
@@ -375,16 +372,26 @@ func freeCMergeFileOptions(c *C.git_merge_file_options) {
func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInput, options *MergeFileOptions) (*MergeFileResult, error) {
- var cancestor C.git_merge_file_input
- var cours C.git_merge_file_input
- var ctheirs C.git_merge_file_input
+ ancestorPath := C.CString(ancestor.Path)
+ defer C.free(unsafe.Pointer(ancestorPath))
+ var ancestorContents *byte
+ if len(ancestor.Contents) > 0 {
+ ancestorContents = &ancestor.Contents[0]
+ }
+
+ oursPath := C.CString(ours.Path)
+ defer C.free(unsafe.Pointer(oursPath))
+ var oursContents *byte
+ if len(ours.Contents) > 0 {
+ oursContents = &ours.Contents[0]
+ }
- populateCMergeFileInput(&cancestor, ancestor)
- defer freeCMergeFileInput(&cancestor)
- populateCMergeFileInput(&cours, ours)
- defer freeCMergeFileInput(&cours)
- populateCMergeFileInput(&ctheirs, theirs)
- defer freeCMergeFileInput(&ctheirs)
+ theirsPath := C.CString(theirs.Path)
+ defer C.free(unsafe.Pointer(theirsPath))
+ var theirsContents *byte
+ if len(theirs.Contents) > 0 {
+ theirsContents = &theirs.Contents[0]
+ }
var copts *C.git_merge_file_options
if options != nil {
@@ -401,7 +408,11 @@ func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInp
defer runtime.UnlockOSThread()
var result C.git_merge_file_result
- ecode := C.git_merge_file(&result, &cancestor, &cours, &ctheirs, copts)
+ ecode := C._go_git_merge_file(&result,
+ (*C.char)(unsafe.Pointer(ancestorContents)), C.size_t(len(ancestor.Contents)), ancestorPath, C.uint(ancestor.Mode),
+ (*C.char)(unsafe.Pointer(oursContents)), C.size_t(len(ours.Contents)), oursPath, C.uint(ours.Mode),
+ (*C.char)(unsafe.Pointer(theirsContents)), C.size_t(len(theirs.Contents)), theirsPath, C.uint(theirs.Mode),
+ copts)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
diff --git a/odb.go b/odb.go
index ff8b739..9c6baa3 100644
--- a/odb.go
+++ b/odb.go
@@ -8,7 +8,6 @@ extern void _go_git_odb_backend_free(git_odb_backend *backend);
*/
import "C"
import (
- "fmt"
"reflect"
"runtime"
"unsafe"
@@ -55,6 +54,21 @@ func (v *Odb) AddBackend(backend *OdbBackend, priority int) (err error) {
return nil
}
+func (v *Odb) ReadHeader(oid *Oid) (uint64, ObjectType, error) {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ var sz C.size_t
+ var cotype C.git_otype
+
+ ret := C.git_odb_read_header(&sz, &cotype, v.ptr, oid.toC())
+ if ret < 0 {
+ return 0, C.GIT_OBJ_BAD, MakeGitError(ret)
+ }
+
+ return uint64(sz), ObjectType(cotype), nil
+}
+
func (v *Odb) Exists(oid *Oid) bool {
ret := C.git_odb_exists(v.ptr, oid.toC())
return ret != 0
@@ -62,12 +76,15 @@ func (v *Odb) Exists(oid *Oid) bool {
func (v *Odb) Write(data []byte, otype ObjectType) (oid *Oid, err error) {
oid = new(Oid)
- hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data))
+ var cptr unsafe.Pointer
+ if len(data) > 0 {
+ cptr = unsafe.Pointer(&data[0])
+ }
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))
+ ret := C.git_odb_write(oid.toC(), v.ptr, cptr, C.size_t(len(data)), C.git_otype(otype))
if ret < 0 {
return nil, MakeGitError(ret)
@@ -107,9 +124,7 @@ func odbForEachCb(id *C.git_oid, handle unsafe.Pointer) int {
}
err := data.callback(newOidFromC(id))
- fmt.Println("err %v", err)
if err != nil {
- fmt.Println("returning EUSER")
data.err = err
return C.GIT_EUSER
}
@@ -130,7 +145,6 @@ func (v *Odb) ForEach(callback OdbForEachCallback) error {
defer pointerHandles.Untrack(handle)
ret := C._go_git_odb_foreach(v.ptr, handle)
- fmt.Println("ret %v", ret)
if ret == C.GIT_EUSER {
return data.err
} else if ret < 0 {
diff --git a/odb_test.go b/odb_test.go
index 0d765b9..dfd2ad0 100644
--- a/odb_test.go
+++ b/odb_test.go
@@ -6,6 +6,34 @@ import (
"testing"
)
+func TestOdbReadHeader(t *testing.T) {
+ repo := createTestRepo(t)
+ defer cleanupTestRepo(t, repo)
+
+ _, _ = seedTestRepo(t, repo)
+ odb, err := repo.Odb()
+ if err != nil {
+ t.Fatalf("Odb: %v", err)
+ }
+ data := []byte("hello")
+ id, err := odb.Write(data, ObjectBlob)
+ if err != nil {
+ t.Fatalf("odb.Write: %v", err)
+ }
+
+ sz, typ, err := odb.ReadHeader(id)
+ if err != nil {
+ t.Fatalf("ReadHeader: %v", err)
+ }
+
+ if sz != uint64(len(data)) {
+ t.Errorf("ReadHeader got size %d, want %d", sz, len(data))
+ }
+ if typ != ObjectBlob {
+ t.Errorf("ReadHeader got object type %s", typ)
+ }
+}
+
func TestOdbStream(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
diff --git a/remote.go b/remote.go
index b3aba54..8a57280 100644
--- a/remote.go
+++ b/remote.go
@@ -112,6 +112,9 @@ type FetchOptions struct {
//
// The default is to auto-follow tags.
DownloadTags DownloadTags
+
+ // Headers are extra headers for the fetch operation.
+ Headers []string
}
type Remote struct {
@@ -157,6 +160,9 @@ type PushOptions struct {
RemoteCallbacks RemoteCallbacks
PbParallelism uint
+
+ // Headers are extra headers for the push operation.
+ Headers []string
}
type RemoteHead struct {
@@ -594,6 +600,10 @@ func populateFetchOptions(options *C.git_fetch_options, opts *FetchOptions) {
options.prune = C.git_fetch_prune_t(opts.Prune)
options.update_fetchhead = cbool(opts.UpdateFetchhead)
options.download_tags = C.git_remote_autotag_option_t(opts.DownloadTags)
+
+ options.custom_headers = C.git_strarray{}
+ options.custom_headers.count = C.size_t(len(opts.Headers))
+ options.custom_headers.strings = makeCStringsFromStrings(opts.Headers)
}
func populatePushOptions(options *C.git_push_options, opts *PushOptions) {
@@ -604,6 +614,10 @@ func populatePushOptions(options *C.git_push_options, opts *PushOptions) {
options.pb_parallelism = C.uint(opts.PbParallelism)
+ options.custom_headers = C.git_strarray{}
+ options.custom_headers.count = C.size_t(len(opts.Headers))
+ options.custom_headers.strings = makeCStringsFromStrings(opts.Headers)
+
populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks)
}
@@ -623,36 +637,51 @@ func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error
crefspecs.strings = makeCStringsFromStrings(refspecs)
defer freeStrarray(&crefspecs)
- var coptions C.git_fetch_options
- populateFetchOptions(&coptions, opts)
+ coptions := (*C.git_fetch_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_fetch_options{}))))
+ defer C.free(unsafe.Pointer(coptions))
+
+ populateFetchOptions(coptions, opts)
defer untrackCalbacksPayload(&coptions.callbacks)
+ defer freeStrarray(&coptions.custom_headers)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- ret := C.git_remote_fetch(o.ptr, &crefspecs, &coptions, cmsg)
+ ret := C.git_remote_fetch(o.ptr, &crefspecs, coptions, cmsg)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
-func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks) error {
- return o.Connect(ConnectDirectionFetch, callbacks)
+func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks, headers []string) error {
+ return o.Connect(ConnectDirectionFetch, callbacks, headers)
}
-func (o *Remote) ConnectPush(callbacks *RemoteCallbacks) error {
- return o.Connect(ConnectDirectionPush, callbacks)
+func (o *Remote) ConnectPush(callbacks *RemoteCallbacks, headers []string) error {
+ return o.Connect(ConnectDirectionPush, callbacks, headers)
}
-func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks) error {
+// Connect opens a connection to a remote.
+//
+// The transport is selected based on the URL. The direction argument
+// is due to a limitation of the git protocol (over TCP or SSH) which
+// starts up a specific binary which can only do the one or the other.
+//
+// 'headers' are extra HTTP headers to use in this connection.
+func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks, headers []string) error {
var ccallbacks C.git_remote_callbacks
populateRemoteCallbacks(&ccallbacks, callbacks)
+ cheaders := C.git_strarray{}
+ cheaders.count = C.size_t(len(headers))
+ cheaders.strings = makeCStringsFromStrings(headers)
+ defer freeStrarray(&cheaders)
+
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks); ret != 0 {
+ if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks, &cheaders); ret != 0 {
return MakeGitError(ret)
}
return nil
@@ -710,14 +739,17 @@ func (o *Remote) Push(refspecs []string, opts *PushOptions) error {
crefspecs.strings = makeCStringsFromStrings(refspecs)
defer freeStrarray(&crefspecs)
- var coptions C.git_push_options
- populatePushOptions(&coptions, opts)
+ coptions := (*C.git_push_options)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_push_options{}))))
+ defer C.free(unsafe.Pointer(coptions))
+
+ populatePushOptions(coptions, opts)
defer untrackCalbacksPayload(&coptions.callbacks)
+ defer freeStrarray(&coptions.custom_headers)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
- ret := C.git_remote_push(o.ptr, &crefspecs, &coptions)
+ ret := C.git_remote_push(o.ptr, &crefspecs, coptions)
if ret < 0 {
return MakeGitError(ret)
}
diff --git a/remote_test.go b/remote_test.go
index dac3dbe..978b803 100644
--- a/remote_test.go
+++ b/remote_test.go
@@ -58,7 +58,7 @@ func TestRemoteConnect(t *testing.T) {
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
- err = remote.ConnectFetch(nil)
+ err = remote.ConnectFetch(nil, nil)
checkFatal(t, err)
}
@@ -69,7 +69,7 @@ func TestRemoteLs(t *testing.T) {
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
- err = remote.ConnectFetch(nil)
+ err = remote.ConnectFetch(nil, nil)
checkFatal(t, err)
heads, err := remote.Ls()
@@ -87,7 +87,7 @@ func TestRemoteLsFiltering(t *testing.T) {
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
- err = remote.ConnectFetch(nil)
+ err = remote.ConnectFetch(nil, nil)
checkFatal(t, err)
heads, err := remote.Ls("master")
@@ -166,7 +166,7 @@ func TestRemotePrune(t *testing.T) {
rr, err := repo.Remotes.Lookup("origin")
checkFatal(t, err)
- err = rr.ConnectFetch(nil)
+ err = rr.ConnectFetch(nil, nil)
checkFatal(t, err)
err = rr.Prune(nil)
diff --git a/repository.go b/repository.go
index 4bf8531..2e6b81d 100644
--- a/repository.go
+++ b/repository.go
@@ -66,15 +66,29 @@ func OpenRepository(path string) (*Repository, error) {
return newRepositoryFromC(ptr), nil
}
-func OpenRepositoryExtended(path string) (*Repository, error) {
+type RepositoryOpenFlag int
+
+const (
+ RepositoryOpenNoSearch RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_NO_SEARCH
+ RepositoryOpenCrossFs RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_CROSS_FS
+ RepositoryOpenBare RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_BARE
+)
+
+func OpenRepositoryExtended(path string, flags RepositoryOpenFlag, ceiling string) (*Repository, error) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
+ var cceiling *C.char = nil
+ if len(ceiling) > 0 {
+ cceiling = C.CString(ceiling)
+ defer C.free(unsafe.Pointer(cceiling))
+ }
+
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_repository
- ret := C.git_repository_open_ext(&ptr, cpath, 0, nil)
+ ret := C.git_repository_open_ext(&ptr, cpath, C.uint(flags), cceiling)
if ret < 0 {
return nil, MakeGitError(ret)
}
@@ -437,3 +451,24 @@ func (r *Repository) StateCleanup() error {
}
return nil
}
+func (r *Repository) AddGitIgnoreRules(rules string) error {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ crules := C.CString(rules)
+ defer C.free(unsafe.Pointer(crules))
+ if ret := C.git_ignore_add_rule(r.ptr, crules); ret < 0 {
+ return MakeGitError(ret)
+ }
+ return nil
+}
+
+func (r *Repository) ClearGitIgnoreRules() error {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ if ret := C.git_ignore_clear_internal_rules(r.ptr); ret < 0 {
+ return MakeGitError(ret)
+ }
+ return nil
+}
diff --git a/tree.go b/tree.go
index 8288176..eba9f3d 100644
--- a/tree.go
+++ b/tree.go
@@ -149,7 +149,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 Filemode) error {
cfilename := C.CString(filename)
defer C.free(unsafe.Pointer(cfilename))
diff --git a/tree_test.go b/tree_test.go
index 4c6a4ed..fae395a 100644
--- a/tree_test.go
+++ b/tree_test.go
@@ -20,3 +20,44 @@ func TestTreeEntryById(t *testing.T) {
t.Fatalf("entry id %v was not found", id)
}
}
+
+func TestTreeBuilderInsert(t *testing.T) {
+ repo := createTestRepo(t)
+ defer cleanupTestRepo(t, repo)
+
+ subTree, err := repo.TreeBuilder()
+ if err != nil {
+ t.Fatalf("TreeBuilder: %v", err)
+ }
+ defer subTree.Free()
+
+ odb, err := repo.Odb()
+ if err != nil {
+ t.Fatalf("repo.Odb: %v", err)
+ }
+ blobId, err := odb.Write([]byte("hello"), ObjectBlob)
+ if err != nil {
+ t.Fatalf("odb.Write: %v", err)
+ }
+ if err = subTree.Insert("subfile", blobId, FilemodeBlobExecutable); err != nil {
+ t.Fatalf("TreeBuilder.Insert: %v", err)
+ }
+ treeID, err := subTree.Write()
+ if err != nil {
+ t.Fatalf("TreeBuilder.Write: %v", err)
+ }
+
+ tree, err := repo.LookupTree(treeID)
+ if err != nil {
+ t.Fatalf("LookupTree: %v", err)
+ }
+
+ entry, err := tree.EntryByPath("subfile")
+ if err != nil {
+ t.Fatalf("tree.EntryByPath(%q): %v", "subfile", err)
+ }
+
+ if !entry.Id.Equal(blobId) {
+ t.Fatalf("got oid %v, want %v", entry.Id, blobId)
+ }
+}
diff --git a/vendor/libgit2 b/vendor/libgit2
-Subproject ed38e26db5435b519d8b796e4b6c2c660fe982b
+Subproject f596946f09f3c1e51239a24ff41e27f2c1ffa2b
diff --git a/walk.go b/walk.go
index c314f60..60e618d 100644
--- a/walk.go
+++ b/walk.go
@@ -194,6 +194,10 @@ func (v *RevWalk) Iterate(fun RevWalkIterator) (err error) {
return nil
}
+func (v *RevWalk) SimplifyFirstParent() {
+ C.git_revwalk_simplify_first_parent(v.ptr)
+}
+
func (v *RevWalk) Sorting(sm SortType) {
C.git_revwalk_sorting(v.ptr, C.uint(sm))
}
diff --git a/wrapper.c b/wrapper.c
index a01867c..e39665a 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -141,6 +141,29 @@ int _go_git_tag_foreach(git_repository *repo, void *payload)
return git_tag_foreach(repo, (git_tag_foreach_cb)&gitTagForeachCb, payload);
}
+int _go_git_merge_file(git_merge_file_result* out, char* ancestorContents, size_t ancestorLen, char* ancestorPath, unsigned int ancestorMode, char* oursContents, size_t oursLen, char* oursPath, unsigned int oursMode, char* theirsContents, size_t theirsLen, char* theirsPath, unsigned int theirsMode, git_merge_file_options* copts) {
+ git_merge_file_input ancestor = GIT_MERGE_FILE_INPUT_INIT;
+ git_merge_file_input ours = GIT_MERGE_FILE_INPUT_INIT;
+ git_merge_file_input theirs = GIT_MERGE_FILE_INPUT_INIT;
+
+ ancestor.ptr = ancestorContents;
+ ancestor.size = ancestorLen;
+ ancestor.path = ancestorPath;
+ ancestor.mode = ancestorMode;
+
+ ours.ptr = oursContents;
+ ours.size = oursLen;
+ ours.path = oursPath;
+ ours.mode = oursMode;
+
+ theirs.ptr = theirsContents;
+ theirs.size = theirsLen;
+ theirs.path = theirsPath;
+ theirs.mode = theirsMode;
+
+ return git_merge_file(out, &ancestor, &ours, &theirs, copts);
+}
+
void _go_git_setup_stash_apply_progress_callbacks(git_stash_apply_options *opts) {
opts->progress_cb = (git_stash_apply_progress_cb)stashApplyProgressCb;
}