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 +*/ +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 { + handle := C.malloc(1) + 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. - + 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 +*/ +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] + } - populateCMergeFileInput(&cancestor, ancestor) - defer freeCMergeFileInput(&cancestor) - populateCMergeFileInput(&cours, ours) - defer freeCMergeFileInput(&cours) - populateCMergeFileInput(&ctheirs, theirs) - defer freeCMergeFileInput(&ctheirs) + oursPath := C.CString(ours.Path) + defer C.free(unsafe.Pointer(oursPath)) + var oursContents *byte + if len(ours.Contents) > 0 { + oursContents = &ours.Contents[0] + } + + 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 index ed38e26..f596946 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit ed38e26db5435b519d8b796e4b6c2c660fe982b5 +Subproject commit f596946f09f3c1e51239a24ff41e27f2c1ffa2b7 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; }