diff options
| -rw-r--r-- | Makefile | 1 | ||||
| -rw-r--r-- | README.md | 25 | ||||
| -rw-r--r-- | blame.go | 157 | ||||
| -rw-r--r-- | blame_test.go | 74 | ||||
| -rw-r--r-- | blob.go | 1 | ||||
| -rw-r--r-- | branch.go | 1 | ||||
| -rw-r--r-- | checkout.go | 38 | ||||
| -rw-r--r-- | clone.go | 2 | ||||
| -rw-r--r-- | commit.go | 1 | ||||
| -rw-r--r-- | config.go | 58 | ||||
| -rw-r--r-- | credentials.go | 1 | ||||
| -rw-r--r-- | diff.go | 219 | ||||
| -rw-r--r-- | diff_test.go | 106 | ||||
| -rw-r--r-- | git.go | 11 | ||||
| -rw-r--r-- | index.go | 1 | ||||
| -rw-r--r-- | merge.go | 71 | ||||
| -rw-r--r-- | merge_test.go | 8 | ||||
| -rw-r--r-- | note.go | 99 | ||||
| -rw-r--r-- | note_test.go | 113 | ||||
| -rw-r--r-- | object.go | 1 | ||||
| -rw-r--r-- | odb.go | 29 | ||||
| -rw-r--r-- | packbuilder.go | 4 | ||||
| -rw-r--r-- | patch.go | 39 | ||||
| -rw-r--r-- | patch_test.go | 6 | ||||
| -rw-r--r-- | push.go | 182 | ||||
| -rw-r--r-- | push_test.go | 46 | ||||
| -rw-r--r-- | refdb.go | 7 | ||||
| -rw-r--r-- | reference.go | 5 | ||||
| -rw-r--r-- | remote.go | 121 | ||||
| -rw-r--r-- | remote_test.go | 83 | ||||
| -rw-r--r-- | repository.go | 110 | ||||
| -rw-r--r-- | revparse.go | 1 | ||||
| -rw-r--r-- | script/check-MakeGitError-thread-lock.go | 71 | ||||
| -rw-r--r-- | status.go | 1 | ||||
| -rw-r--r-- | submodule.go | 53 | ||||
| -rw-r--r-- | submodule_test.go | 24 | ||||
| -rw-r--r-- | tree.go | 3 | ||||
| m--------- | vendor/libgit2 | 0 | ||||
| -rw-r--r-- | walk.go | 1 | ||||
| -rw-r--r-- | wrapper.c | 25 |
40 files changed, 1424 insertions, 375 deletions
@@ -4,6 +4,7 @@ build-libgit2: ./script/build-libgit2-static.sh test: build-libgit2 + go run script/check-MakeGitError-thread-lock.go ./script/with-static.sh go test ./... install: build-libgit2 @@ -1,14 +1,26 @@ git2go ====== -[](http://godoc.org/github.com/libgit2/git2go) +[](http://godoc.org/github.com/libgit2/git2go) [](https://travis-ci.org/libgit2/git2go) -Go bindings for [libgit2](http://libgit2.github.com/). The master branch follows the latest libgit2 release. +Go bindings for [libgit2](http://libgit2.github.com/). The master branch follows the latest libgit2 release. The versioned branches indicate which libgit2 version they work against. Installing ---------- -This project needs libgit2, which is written in C so we need to build that as well. In order to build libgit2, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 if you want to use HTTPS and SSH respectively. +This project needs libgit2, which is written in C so we need to build that as well. In order to build libgit2, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively. + +### Stable version + +git2go has versioned branches which indicate which version of libgit2 they work against. Install the development package it on your system via your favourite package manager or from source and you can use a service like gopkg.in to use the appropriate version. For the libgit2 v0.22 case, you can use + + import "gopkg.in/libgit2/git2go.v22" + +to use a version of git2go which will work against libgit2 v0.22 and dynamically link to the library. + +### From master + +The master branch follows libgit2's master branch, which means there is no stable API or ABI to link against. git2go can statically link against a vendored version of libgit2. Run `go get -d github.com/libgit2/git2go` to download the code and go to your `$GOPATH/src/github.com/libgit2/git2go` dir. From there, we need to build the C code and put it into the resulting go binary. @@ -17,10 +29,15 @@ Run `go get -d github.com/libgit2/git2go` to download the code and go to your `$ will compile libgit2 and run `go install` such that it's statically linked to the git2go package. +Paralellism and network operations +---------------------------------- + +libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. For now, git2go asks libgit2 to set locking for OpenSSL. This makes HTTPS connections thread-safe, but it is fragile and will likely stop doing it soon. This may also make SSH connections thread-safe if your copy of libssh2 is linked against OpenSSL. Check libgit2's `THREADSAFE.md` for more information. + Running the tests ----------------- -Similarly to installing, running the tests requires linking against the local libgit2 library, so the Makefile provides a wrapper +For the stable version, `go test` will work as usual. For the master branch, similarly to installing, running the tests requires linking against the local libgit2 library, so the Makefile provides a wrapper make test diff --git a/blame.go b/blame.go new file mode 100644 index 0000000..c24c934 --- /dev/null +++ b/blame.go @@ -0,0 +1,157 @@ +package git + +/* +#include <git2.h> +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +type BlameOptions struct { + Flags BlameOptionsFlag + MinMatchCharacters uint16 + NewestCommit *Oid + OldestCommit *Oid + MinLine uint32 + MaxLine uint32 +} + +func DefaultBlameOptions() (BlameOptions, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + opts := C.git_blame_options{} + ecode := C.git_blame_init_options(&opts, C.GIT_BLAME_OPTIONS_VERSION) + if ecode < 0 { + return BlameOptions{}, MakeGitError(ecode) + } + + return BlameOptions{ + Flags: BlameOptionsFlag(opts.flags), + MinMatchCharacters: uint16(opts.min_match_characters), + NewestCommit: newOidFromC(&opts.newest_commit), + OldestCommit: newOidFromC(&opts.oldest_commit), + MinLine: uint32(opts.min_line), + MaxLine: uint32(opts.max_line), + }, nil +} + +type BlameOptionsFlag uint32 + +const ( + BlameNormal BlameOptionsFlag = C.GIT_BLAME_NORMAL + BlameTrackCopiesSameFile BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_FILE + BlameTrackCopiesSameCommitMoves BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES + BlameTrackCopiesSameCommitCopies BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES + BlameTrackCopiesAnyCommitCopies BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES + BlameFirstParent BlameOptionsFlag = C.GIT_BLAME_FIRST_PARENT +) + +func (v *Repository) BlameFile(path string, opts *BlameOptions) (*Blame, error) { + var blamePtr *C.git_blame + + var copts *C.git_blame_options + if opts != nil { + copts = &C.git_blame_options{ + 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), + } + if opts.NewestCommit != nil { + copts.newest_commit = *opts.NewestCommit.toC() + } + if opts.OldestCommit != nil { + copts.oldest_commit = *opts.OldestCommit.toC() + } + } + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_blame_file(&blamePtr, v.ptr, cpath, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newBlameFromC(blamePtr), nil +} + +type Blame struct { + ptr *C.git_blame +} + +func (blame *Blame) HunkCount() int { + return int(C.git_blame_get_hunk_count(blame.ptr)) +} + +func (blame *Blame) HunkByIndex(index int) (BlameHunk, error) { + ptr := C.git_blame_get_hunk_byindex(blame.ptr, C.uint32_t(index)) + if ptr == nil { + return BlameHunk{}, ErrInvalid + } + return blameHunkFromC(ptr), nil +} + +func (blame *Blame) HunkByLine(lineno int) (BlameHunk, error) { + ptr := C.git_blame_get_hunk_byline(blame.ptr, C.uint32_t(lineno)) + if ptr == nil { + return BlameHunk{}, ErrInvalid + } + return blameHunkFromC(ptr), nil +} + +func newBlameFromC(ptr *C.git_blame) *Blame { + if ptr == nil { + return nil + } + + blame := &Blame{ + ptr: ptr, + } + + runtime.SetFinalizer(blame, (*Blame).Free) + return blame +} + +func (blame *Blame) Free() error { + if blame.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(blame, nil) + C.git_blame_free(blame.ptr) + blame.ptr = nil + return nil +} + +type BlameHunk struct { + LinesInHunk uint16 + FinalCommitId *Oid + FinalStartLineNumber uint16 + FinalSignature *Signature + OrigCommitId *Oid + OrigPath string + OrigStartLineNumber uint16 + OrigSignature *Signature + Boundary bool +} + +func blameHunkFromC(hunk *C.git_blame_hunk) BlameHunk { + return BlameHunk{ + LinesInHunk: uint16(hunk.lines_in_hunk), + FinalCommitId: newOidFromC(&hunk.final_commit_id), + FinalStartLineNumber: uint16(hunk.final_start_line_number), + FinalSignature: newSignatureFromC(hunk.final_signature), + OrigCommitId: newOidFromC(&hunk.orig_commit_id), + OrigPath: C.GoString(hunk.orig_path), + OrigStartLineNumber: uint16(hunk.orig_start_line_number), + OrigSignature: newSignatureFromC(hunk.orig_signature), + Boundary: hunk.boundary == 1, + } +} diff --git a/blame_test.go b/blame_test.go new file mode 100644 index 0000000..1785042 --- /dev/null +++ b/blame_test.go @@ -0,0 +1,74 @@ +package git + +import ( + "os" + "reflect" + "testing" +) + +func TestBlame(t *testing.T) { + repo := createTestRepo(t) + defer repo.Free() + defer os.RemoveAll(repo.Workdir()) + + commitId1, _ := seedTestRepo(t, repo) + commitId2, _ := updateReadme(t, repo, "foo\nbar\nbaz\n") + + opts := BlameOptions{ + NewestCommit: commitId2, + OldestCommit: nil, + MinLine: 1, + MaxLine: 3, + } + blame, err := repo.BlameFile("README", &opts) + checkFatal(t, err) + defer blame.Free() + if blame.HunkCount() != 2 { + t.Errorf("got hunk count %d, want 2", blame.HunkCount()) + } + + wantHunk1 := BlameHunk{ + LinesInHunk: 1, + FinalCommitId: commitId1, + FinalStartLineNumber: 1, + OrigCommitId: commitId1, + OrigPath: "README", + OrigStartLineNumber: 1, + Boundary: true, + } + wantHunk2 := BlameHunk{ + LinesInHunk: 2, + FinalCommitId: commitId2, + FinalStartLineNumber: 2, + OrigCommitId: commitId2, + OrigPath: "README", + OrigStartLineNumber: 2, + Boundary: false, + } + + hunk1, err := blame.HunkByIndex(0) + checkFatal(t, err) + checkHunk(t, "index 0", hunk1, wantHunk1) + + hunk2, err := blame.HunkByIndex(1) + checkFatal(t, err) + checkHunk(t, "index 1", hunk2, wantHunk2) + + hunkLine1, err := blame.HunkByLine(1) + checkFatal(t, err) + checkHunk(t, "line 1", hunkLine1, wantHunk1) + + hunkLine2, err := blame.HunkByLine(3) + checkFatal(t, err) + checkHunk(t, "line 2", hunkLine2, wantHunk2) +} + +func checkHunk(t *testing.T, label string, hunk, want BlameHunk) { + hunk.FinalSignature = nil + want.FinalSignature = nil + hunk.OrigSignature = nil + want.OrigSignature = nil + if !reflect.DeepEqual(hunk, want) { + t.Fatalf("%s: got hunk %+v, want %+v", label, hunk, want) + } +} @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> #include <string.h> extern int _go_git_blob_create_fromchunks(git_oid *id, @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> */ import "C" diff --git a/checkout.go b/checkout.go index 9c7188e..06d010c 100644 --- a/checkout.go +++ b/checkout.go @@ -7,6 +7,7 @@ import "C" import ( "os" "runtime" + "unsafe" ) type CheckoutStrategy uint @@ -31,11 +32,12 @@ const ( ) type CheckoutOpts struct { - Strategy CheckoutStrategy // Default will be a dry run - DisableFilters bool // Don't apply filters like CRLF conversion - DirMode os.FileMode // Default is 0755 - FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob - FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY + Strategy CheckoutStrategy // Default will be a dry run + DisableFilters bool // Don't apply filters like CRLF conversion + DirMode os.FileMode // Default is 0755 + FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob + FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY + TargetDirectory string // Alternative checkout path to workdir } func (opts *CheckoutOpts) toC() *C.git_checkout_options { @@ -60,17 +62,29 @@ func populateCheckoutOpts(ptr *C.git_checkout_options, opts *CheckoutOpts) *C.gi ptr.disable_filters = cbool(opts.DisableFilters) ptr.dir_mode = C.uint(opts.DirMode.Perm()) ptr.file_mode = C.uint(opts.FileMode.Perm()) - + if opts.TargetDirectory != "" { + ptr.target_directory = C.CString(opts.TargetDirectory) + } return ptr } +func freeCheckoutOpts(ptr *C.git_checkout_options) { + if ptr == nil { + return + } + C.free(unsafe.Pointer(ptr.target_directory)) +} + // Updates files in the index and the working tree to match the content of // the commit pointed at by HEAD. opts may be nil. func (v *Repository) CheckoutHead(opts *CheckoutOpts) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_checkout_head(v.ptr, opts.toC()) + cOpts := opts.toC() + defer freeCheckoutOpts(cOpts) + + ret := C.git_checkout_head(v.ptr, cOpts) if ret < 0 { return MakeGitError(ret) } @@ -90,7 +104,10 @@ func (v *Repository) CheckoutIndex(index *Index, opts *CheckoutOpts) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_checkout_index(v.ptr, iptr, opts.toC()) + cOpts := opts.toC() + defer freeCheckoutOpts(cOpts) + + ret := C.git_checkout_index(v.ptr, iptr, cOpts) if ret < 0 { return MakeGitError(ret) } @@ -102,7 +119,10 @@ func (v *Repository) CheckoutTree(tree *Tree, opts *CheckoutOpts) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_checkout_tree(v.ptr, tree.ptr, opts.toC()) + cOpts := opts.toC() + defer freeCheckoutOpts(cOpts) + + ret := C.git_checkout_tree(v.ptr, tree.ptr, cOpts) if ret < 0 { return MakeGitError(ret) } @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> */ import "C" @@ -31,6 +30,7 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error) var copts C.git_clone_options populateCloneOptions(&copts, options) + defer freeCheckoutOpts(&copts.checkout_opts) if len(options.CheckoutBranch) != 0 { copts.checkout_branch = C.CString(options.CheckoutBranch) @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> extern int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr); */ @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> */ import "C" import ( @@ -36,14 +35,14 @@ const ( ) type ConfigEntry struct { - Name string + Name string Value string Level ConfigLevel } func newConfigEntryFromC(centry *C.git_config_entry) *ConfigEntry { return &ConfigEntry{ - Name: C.GoString(centry.name), + Name: C.GoString(centry.name), Value: C.GoString(centry.value), Level: ConfigLevel(centry.level), } @@ -75,7 +74,6 @@ func (c *Config) AddFile(path string, level ConfigLevel, force bool) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_config_add_file_ondisk(c.ptr, cpath, C.git_config_level_t(level), cbool(force)) if ret < 0 { return MakeGitError(ret) @@ -131,7 +129,6 @@ func (c *Config) LookupString(name string) (string, error) { return C.GoString(ptr), nil } - func (c *Config) LookupBool(name string) (bool, error) { var out C.int cname := C.CString(name) @@ -235,6 +232,9 @@ func (c *Config) SetInt32(name string, value int32) (err error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_config_set_int32(c.ptr, cname, C.int32_t(value)) if ret < 0 { return MakeGitError(ret) @@ -350,6 +350,9 @@ type ConfigIterator struct { func (iter *ConfigIterator) Next() (*ConfigEntry, error) { var centry *C.git_config_entry + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_config_next(¢ry, iter.ptr) if ret < 0 { return nil, MakeGitError(ret) @@ -362,3 +365,48 @@ func (iter *ConfigIterator) Free() { runtime.SetFinalizer(iter, nil) C.free(unsafe.Pointer(iter.ptr)) } + +func ConfigFindGlobal() (string, error) { + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_find_global(&buf) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(buf.ptr), nil +} + +func ConfigFindSystem() (string, error) { + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_find_system(&buf) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(buf.ptr), nil +} + +func ConfigFindXDG() (string, error) { + var buf C.git_buf + defer C.git_buf_free(&buf) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_config_find_xdg(&buf) + if ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(buf.ptr), nil +} diff --git a/credentials.go b/credentials.go index b04bf98..bb0ec41 100644 --- a/credentials.go +++ b/credentials.go @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> */ import "C" import "unsafe" @@ -164,6 +164,72 @@ func (diff *Diff) Free() error { return nil } +func (diff *Diff) FindSimilar(opts *DiffFindOptions) error { + + var copts *C.git_diff_find_options + if opts != nil { + copts = &C.git_diff_find_options{ + version: C.GIT_DIFF_FIND_OPTIONS_VERSION, + flags: C.uint32_t(opts.Flags), + rename_threshold: C.uint16_t(opts.RenameThreshold), + copy_threshold: C.uint16_t(opts.CopyThreshold), + rename_from_rewrite_threshold: C.uint16_t(opts.RenameFromRewriteThreshold), + break_rewrite_threshold: C.uint16_t(opts.BreakRewriteThreshold), + rename_limit: C.size_t(opts.RenameLimit), + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_find_similar(diff.ptr, copts) + if ecode < 0 { + return MakeGitError(ecode) + } + + return nil +} + +type DiffStats struct { + ptr *C.git_diff_stats +} + +func (stats *DiffStats) Free() error { + if stats.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(stats, nil) + C.git_diff_stats_free(stats.ptr) + stats.ptr = nil + return nil +} + +func (stats *DiffStats) Insertions() int { + return int(C.git_diff_stats_insertions(stats.ptr)) +} + +func (stats *DiffStats) Deletions() int { + return int(C.git_diff_stats_deletions(stats.ptr)) +} + +func (stats *DiffStats) FilesChanged() int { + return int(C.git_diff_stats_files_changed(stats.ptr)) +} + +func (diff *Diff) Stats() (*DiffStats, error) { + stats := new(DiffStats) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ecode := C.git_diff_get_stats(&stats.ptr, diff.ptr); ecode < 0 { + return nil, MakeGitError(ecode) + } + runtime.SetFinalizer(stats, (*DiffStats).Free) + + return stats, nil +} + type diffForEachData struct { FileCallback DiffForEachFileCallback HunkCallback DiffForEachHunkCallback @@ -264,6 +330,9 @@ func (diff *Diff) Patch(deltaIndex int) (*Patch, error) { } var patchPtr *C.git_patch + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ecode := C.git_patch_from_diff(&patchPtr, diff.ptr, C.size_t(deltaIndex)) if ecode < 0 { return nil, MakeGitError(ecode) @@ -313,8 +382,8 @@ type DiffOptions struct { Pathspec []string NotifyCallback DiffNotifyCallback - ContextLines uint16 - InterhunkLines uint16 + ContextLines uint32 + InterhunkLines uint32 IdAbbrev uint16 MaxSize int @@ -325,6 +394,10 @@ type DiffOptions struct { func DefaultDiffOptions() (DiffOptions, error) { opts := C.git_diff_options{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ecode := C.git_diff_init_options(&opts, C.GIT_DIFF_OPTIONS_VERSION) if ecode < 0 { return DiffOptions{}, MakeGitError(ecode) @@ -334,10 +407,64 @@ func DefaultDiffOptions() (DiffOptions, error) { Flags: DiffOptionsFlag(opts.flags), IgnoreSubmodules: SubmoduleIgnore(opts.ignore_submodules), Pathspec: makeStringsFromCStrings(opts.pathspec.strings, int(opts.pathspec.count)), - ContextLines: uint16(opts.context_lines), - InterhunkLines: uint16(opts.interhunk_lines), + ContextLines: uint32(opts.context_lines), + InterhunkLines: uint32(opts.interhunk_lines), IdAbbrev: uint16(opts.id_abbrev), MaxSize: int(opts.max_size), + OldPrefix: "a", + NewPrefix: "b", + }, nil +} + +type DiffFindOptionsFlag int + +const ( + DiffFindByConfig DiffFindOptionsFlag = C.GIT_DIFF_FIND_BY_CONFIG + DiffFindRenames DiffFindOptionsFlag = C.GIT_DIFF_FIND_RENAMES + DiffFindRenamesFromRewrites DiffFindOptionsFlag = C.GIT_DIFF_FIND_RENAMES_FROM_REWRITES + DiffFindCopies DiffFindOptionsFlag = C.GIT_DIFF_FIND_COPIES + DiffFindCopiesFromUnmodified DiffFindOptionsFlag = C.GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED + DiffFindRewrites DiffFindOptionsFlag = C.GIT_DIFF_FIND_REWRITES + DiffFindBreakRewrites DiffFindOptionsFlag = C.GIT_DIFF_BREAK_REWRITES + DiffFindAndBreakRewrites DiffFindOptionsFlag = C.GIT_DIFF_FIND_AND_BREAK_REWRITES + DiffFindForUntracked DiffFindOptionsFlag = C.GIT_DIFF_FIND_FOR_UNTRACKED + DiffFindAll DiffFindOptionsFlag = C.GIT_DIFF_FIND_ALL + DiffFindIgnoreLeadingWhitespace DiffFindOptionsFlag = C.GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE + DiffFindIgnoreWhitespace DiffFindOptionsFlag = C.GIT_DIFF_FIND_IGNORE_WHITESPACE + DiffFindDontIgnoreWhitespace DiffFindOptionsFlag = C.GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE + DiffFindExactMatchOnly DiffFindOptionsFlag = C.GIT_DIFF_FIND_EXACT_MATCH_ONLY + DiffFindBreakRewritesForRenamesOnly DiffFindOptionsFlag = C.GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY + DiffFindRemoveUnmodified DiffFindOptionsFlag = C.GIT_DIFF_FIND_REMOVE_UNMODIFIED +) + +//TODO implement git_diff_similarity_metric +type DiffFindOptions struct { + Flags DiffFindOptionsFlag + RenameThreshold uint16 + CopyThreshold uint16 + RenameFromRewriteThreshold uint16 + BreakRewriteThreshold uint16 + RenameLimit uint +} + +func DefaultDiffFindOptions() (DiffFindOptions, error) { + opts := C.git_diff_find_options{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_diff_find_init_options(&opts, C.GIT_DIFF_FIND_OPTIONS_VERSION) + if ecode < 0 { + return DiffFindOptions{}, MakeGitError(ecode) + } + + return DiffFindOptions{ + Flags: DiffFindOptionsFlag(opts.flags), + RenameThreshold: uint16(opts.rename_threshold), + CopyThreshold: uint16(opts.copy_threshold), + RenameFromRewriteThreshold: uint16(opts.rename_from_rewrite_threshold), + BreakRewriteThreshold: uint16(opts.break_rewrite_threshold), + RenameLimit: uint(opts.rename_limit), }, nil } @@ -374,21 +501,8 @@ func diffNotifyCb(_diff_so_far unsafe.Pointer, delta_to_add *C.git_diff_delta, m return 0 } -func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree, opts *DiffOptions) (*Diff, error) { - var diffPtr *C.git_diff - var oldPtr, newPtr *C.git_tree - - if oldTree != nil { - oldPtr = oldTree.cast_ptr - } - - if newTree != nil { - newPtr = newTree.cast_ptr - } - +func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *diffNotifyData) { cpathspec := C.git_strarray{} - var copts *C.git_diff_options - var notifyData *diffNotifyData if opts != nil { notifyData = &diffNotifyData{ Callback: opts.NotifyCallback, @@ -396,7 +510,6 @@ func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree, opts *DiffOptions) ( if opts.Pathspec != nil { cpathspec.count = C.size_t(len(opts.Pathspec)) cpathspec.strings = makeCStringsFromStrings(opts.Pathspec) - defer freeStrarray(&cpathspec) } copts = &C.git_diff_options{ @@ -404,10 +517,12 @@ func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree, opts *DiffOptions) ( flags: C.uint32_t(opts.Flags), ignore_submodules: C.git_submodule_ignore_t(opts.IgnoreSubmodules), pathspec: cpathspec, - context_lines: C.uint16_t(opts.ContextLines), - interhunk_lines: C.uint16_t(opts.InterhunkLines), + context_lines: C.uint32_t(opts.ContextLines), + interhunk_lines: C.uint32_t(opts.InterhunkLines), id_abbrev: C.uint16_t(opts.IdAbbrev), max_size: C.git_off_t(opts.MaxSize), + old_prefix: C.CString(opts.OldPrefix), + new_prefix: C.CString(opts.NewPrefix), } if opts.NotifyCallback != nil { @@ -415,6 +530,35 @@ func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree, opts *DiffOptions) ( copts.notify_payload = unsafe.Pointer(notifyData) } } + return +} + +func freeDiffOptions(copts *C.git_diff_options) { + if copts != nil { + cpathspec := copts.pathspec + freeStrarray(&cpathspec) + C.free(unsafe.Pointer(copts.old_prefix)) + C.free(unsafe.Pointer(copts.new_prefix)) + } +} + +func (v *Repository) DiffTreeToTree(oldTree, newTree *Tree, opts *DiffOptions) (*Diff, error) { + var diffPtr *C.git_diff + var oldPtr, newPtr *C.git_tree + + if oldTree != nil { + oldPtr = oldTree.cast_ptr + } + + if newTree != nil { + newPtr = newTree.cast_ptr + } + + copts, notifyData := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() ecode := C.git_diff_tree_to_tree(&diffPtr, v.ptr, oldPtr, newPtr, copts) if ecode < 0 { @@ -435,35 +579,11 @@ func (v *Repository) DiffTreeToWorkdir(oldTree *Tree, opts *DiffOptions) (*Diff, oldPtr = oldTree.cast_ptr } - cpathspec := C.git_strarray{} - var copts *C.git_diff_options - var notifyData *diffNotifyData - if opts != nil { - notifyData = &diffNotifyData{ - Callback: opts.NotifyCallback, - } - if opts.Pathspec != nil { - cpathspec.count = C.size_t(len(opts.Pathspec)) - cpathspec.strings = makeCStringsFromStrings(opts.Pathspec) - defer freeStrarray(&cpathspec) - } + copts, notifyData := diffOptionsToC(opts) + defer freeDiffOptions(copts) - copts = &C.git_diff_options{ - version: C.GIT_DIFF_OPTIONS_VERSION, - flags: C.uint32_t(opts.Flags), - ignore_submodules: C.git_submodule_ignore_t(opts.IgnoreSubmodules), - pathspec: cpathspec, - context_lines: C.uint16_t(opts.ContextLines), - interhunk_lines: C.uint16_t(opts.InterhunkLines), - id_abbrev: C.uint16_t(opts.IdAbbrev), - max_size: C.git_off_t(opts.MaxSize), - } - - if opts.NotifyCallback != nil { - C._go_git_setup_diff_notify_callbacks(copts) - copts.notify_payload = unsafe.Pointer(notifyData) - } - } + runtime.LockOSThread() + defer runtime.UnlockOSThread() ecode := C.git_diff_tree_to_workdir(&diffPtr, v.ptr, oldPtr, copts) if ecode < 0 { @@ -474,5 +594,4 @@ func (v *Repository) DiffTreeToWorkdir(oldTree *Tree, opts *DiffOptions) (*Diff, return notifyData.Diff, nil } return newDiffFromC(diffPtr), nil - } diff --git a/diff_test.go b/diff_test.go index b688294..fc6fed9 100644 --- a/diff_test.go +++ b/diff_test.go @@ -3,30 +3,81 @@ package git import ( "errors" "os" + "strings" "testing" ) -func TestDiffTreeToTree(t *testing.T) { +func TestFindSimilar(t *testing.T) { repo := createTestRepo(t) defer repo.Free() defer os.RemoveAll(repo.Workdir()) - _, originalTreeId := seedTestRepo(t, repo) - originalTree, err := repo.LookupTree(originalTreeId) + originalTree, newTree := createTestTrees(t, repo) + diffOpt, _ := DefaultDiffOptions() + + diff, err := repo.DiffTreeToTree(originalTree, newTree, &diffOpt) checkFatal(t, err) + if diff == nil { + t.Fatal("no diff returned") + } - _, newTreeId := updateReadme(t, repo, "file changed\n") + findOpts, err := DefaultDiffFindOptions() + checkFatal(t, err) + findOpts.Flags = DiffFindBreakRewrites - newTree, err := repo.LookupTree(newTreeId) + err = diff.FindSimilar(&findOpts) checkFatal(t, err) + numDiffs := 0 + numAdded := 0 + numDeleted := 0 + + err = diff.ForEach(func(file DiffDelta, progress float64) (DiffForEachHunkCallback, error) { + numDiffs++ + + switch file.Status { + case DeltaAdded: + numAdded++ + case DeltaDeleted: + numDeleted++ + } + + return func(hunk DiffHunk) (DiffForEachLineCallback, error) { + return func(line DiffLine) error { + return nil + }, nil + }, nil + }, DiffDetailLines) + + if numDiffs != 2 { + t.Fatal("Incorrect number of files in diff") + } + if numAdded != 1 { + t.Fatal("Incorrect number of new files in diff") + } + if numDeleted != 1 { + t.Fatal("Incorrect number of deleted files in diff") + } + +} + +func TestDiffTreeToTree(t *testing.T) { + + repo := createTestRepo(t) + defer repo.Free() + defer os.RemoveAll(repo.Workdir()) + + originalTree, newTree := createTestTrees(t, repo) + callbackInvoked := false opts := DiffOptions{ NotifyCallback: func(diffSoFar *Diff, delta DiffDelta, matchedPathSpec string) error { callbackInvoked = true return nil }, + OldPrefix: "x1/", + NewPrefix: "y1/", } diff, err := repo.DiffTreeToTree(originalTree, newTree, &opts) @@ -42,7 +93,19 @@ func TestDiffTreeToTree(t *testing.T) { files := make([]string, 0) hunks := make([]DiffHunk, 0) lines := make([]DiffLine, 0) + patches := make([]string, 0) err = diff.ForEach(func(file DiffDelta, progress float64) (DiffForEachHunkCallback, error) { + patch, err := diff.Patch(len(patches)) + if err != nil { + return nil, err + } + defer patch.Free() + patchStr, err := patch.String() + if err != nil { + return nil, err + } + patches = append(patches, patchStr) + files = append(files, file.OldFile.Path) return func(hunk DiffHunk) (DiffForEachLineCallback, error) { hunks = append(hunks, hunk) @@ -83,6 +146,24 @@ func TestDiffTreeToTree(t *testing.T) { t.Fatal("Incorrect lines in diff") } + if want1, want2 := "x1/README", "y1/README"; !strings.Contains(patches[0], want1) || !strings.Contains(patches[0], want2) { + t.Errorf("Diff patch doesn't contain %q or %q\n\n%s", want1, want2, patches[0]) + + } + + stats, err := diff.Stats() + checkFatal(t, err) + + if stats.Insertions() != 1 { + t.Fatal("Incorrect number of insertions in diff") + } + if stats.Deletions() != 1 { + t.Fatal("Incorrect number of deletions in diff") + } + if stats.FilesChanged() != 1 { + t.Fatal("Incorrect number of changed files in diff") + } + errTest := errors.New("test error") err = diff.ForEach(func(file DiffDelta, progress float64) (DiffForEachHunkCallback, error) { @@ -94,3 +175,18 @@ func TestDiffTreeToTree(t *testing.T) { } } + +func createTestTrees(t *testing.T, repo *Repository) (originalTree *Tree, newTree *Tree) { + var err error + _, originalTreeId := seedTestRepo(t, repo) + originalTree, err = repo.LookupTree(originalTreeId) + + checkFatal(t, err) + + _, newTreeId := updateReadme(t, repo, "file changed\n") + + newTree, err = repo.LookupTree(newTreeId) + checkFatal(t, err) + + return originalTree, newTree +} @@ -2,7 +2,7 @@ package git /* #include <git2.h> -#include <git2/errors.h> +#include <git2/sys/openssl.h> */ import "C" import ( @@ -93,7 +93,14 @@ var ( ) func init() { - C.git_threads_init() + C.git_libgit2_init() + + // This is not something we should be doing, as we may be + // stomping all over someone else's setup. The user should do + // this themselves or use some binding/wrapper which does it + // in such a way that they can be sure they're the only ones + // setting it up. + C.git_openssl_set_locking() } // Oid represents the id for a Git object. @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> extern int _go_git_index_add_all(git_index*, const git_strarray*, unsigned int, void*); extern int _go_git_index_update_all(git_index*, const git_strarray*, void*); @@ -2,11 +2,10 @@ package git /* #include <git2.h> -#include <git2/errors.h> -extern git_merge_head** _go_git_make_merge_head_array(size_t len); -extern void _go_git_merge_head_array_set(git_merge_head** array, git_merge_head* ptr, size_t n); -extern git_merge_head* _go_git_merge_head_array_get(git_merge_head** array, size_t n); +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); */ import "C" @@ -15,23 +14,23 @@ import ( "unsafe" ) -type MergeHead struct { - ptr *C.git_merge_head +type AnnotatedCommit struct { + ptr *C.git_annotated_commit } -func newMergeHeadFromC(c *C.git_merge_head) *MergeHead { - mh := &MergeHead{ptr: c} - runtime.SetFinalizer(mh, (*MergeHead).Free) +func newAnnotatedCommitFromC(c *C.git_annotated_commit) *AnnotatedCommit { + mh := &AnnotatedCommit{ptr: c} + runtime.SetFinalizer(mh, (*AnnotatedCommit).Free) return mh } -func (mh *MergeHead) Free() { +func (mh *AnnotatedCommit) Free() { runtime.SetFinalizer(mh, nil) - C.git_merge_head_free(mh.ptr) + C.git_annotated_commit_free(mh.ptr) } -func (r *Repository) MergeHeadFromFetchHead(branchName string, remoteURL string, oid *Oid) (*MergeHead, error) { - mh := &MergeHead{} +func (r *Repository) AnnotatedCommitFromFetchHead(branchName string, remoteURL string, oid *Oid) (*AnnotatedCommit, error) { + mh := &AnnotatedCommit{} cbranchName := C.CString(branchName) defer C.free(unsafe.Pointer(cbranchName)) @@ -39,33 +38,42 @@ func (r *Repository) MergeHeadFromFetchHead(branchName string, remoteURL string, cremoteURL := C.CString(remoteURL) defer C.free(unsafe.Pointer(cremoteURL)) - ret := C.git_merge_head_from_fetchhead(&mh.ptr, r.ptr, cbranchName, cremoteURL, oid.toC()) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_annotated_commit_from_fetchhead(&mh.ptr, r.ptr, cbranchName, cremoteURL, oid.toC()) if ret < 0 { return nil, MakeGitError(ret) } - runtime.SetFinalizer(mh, (*MergeHead).Free) + runtime.SetFinalizer(mh, (*AnnotatedCommit).Free) return mh, nil } -func (r *Repository) MergeHeadFromId(oid *Oid) (*MergeHead, error) { - mh := &MergeHead{} +func (r *Repository) LookupAnnotatedCommit(oid *Oid) (*AnnotatedCommit, error) { + mh := &AnnotatedCommit{} - ret := C.git_merge_head_from_id(&mh.ptr, r.ptr, oid.toC()) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_annotated_commit_lookup(&mh.ptr, r.ptr, oid.toC()) if ret < 0 { return nil, MakeGitError(ret) } - runtime.SetFinalizer(mh, (*MergeHead).Free) + runtime.SetFinalizer(mh, (*AnnotatedCommit).Free) return mh, nil } -func (r *Repository) MergeHeadFromRef(ref *Reference) (*MergeHead, error) { - mh := &MergeHead{} +func (r *Repository) AnnotatedCommitFromRef(ref *Reference) (*AnnotatedCommit, error) { + mh := &AnnotatedCommit{} - ret := C.git_merge_head_from_ref(&mh.ptr, r.ptr, ref.ptr) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_annotated_commit_from_ref(&mh.ptr, r.ptr, ref.ptr) if ret < 0 { return nil, MakeGitError(ret) } - runtime.SetFinalizer(mh, (*MergeHead).Free) + runtime.SetFinalizer(mh, (*AnnotatedCommit).Free) return mh, nil } @@ -98,6 +106,10 @@ func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions { func DefaultMergeOptions() (MergeOptions, error) { opts := C.git_merge_options{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ecode := C.git_merge_init_options(&opts, C.GIT_MERGE_OPTIONS_VERSION) if ecode < 0 { return MergeOptions{}, MakeGitError(ecode) @@ -127,19 +139,20 @@ const ( MergeFileFavorUnion MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_UNION ) -func (r *Repository) Merge(theirHeads []*MergeHead, mergeOptions *MergeOptions, checkoutOptions *CheckoutOpts) error { +func (r *Repository) Merge(theirHeads []*AnnotatedCommit, mergeOptions *MergeOptions, checkoutOptions *CheckoutOpts) error { runtime.LockOSThread() defer runtime.UnlockOSThread() cMergeOpts := mergeOptions.toC() cCheckoutOpts := checkoutOptions.toC() + defer freeCheckoutOpts(cCheckoutOpts) - gmerge_head_array := make([]*C.git_merge_head, len(theirHeads)) + gmerge_head_array := make([]*C.git_annotated_commit, len(theirHeads)) for i := 0; i < len(theirHeads); i++ { gmerge_head_array[i] = theirHeads[i].ptr } ptr := unsafe.Pointer(&gmerge_head_array[0]) - err := C.git_merge(r.ptr, (**C.git_merge_head)(ptr), C.size_t(len(theirHeads)), cMergeOpts, cCheckoutOpts) + err := C.git_merge(r.ptr, (**C.git_annotated_commit)(ptr), C.size_t(len(theirHeads)), cMergeOpts, cCheckoutOpts) if err < 0 { return MakeGitError(err) } @@ -164,18 +177,18 @@ const ( MergePreferenceFastForwardOnly MergePreference = C.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY ) -func (r *Repository) MergeAnalysis(theirHeads []*MergeHead) (MergeAnalysis, MergePreference, error) { +func (r *Repository) MergeAnalysis(theirHeads []*AnnotatedCommit) (MergeAnalysis, MergePreference, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - gmerge_head_array := make([]*C.git_merge_head, len(theirHeads)) + gmerge_head_array := make([]*C.git_annotated_commit, len(theirHeads)) for i := 0; i < len(theirHeads); i++ { gmerge_head_array[i] = theirHeads[i].ptr } ptr := unsafe.Pointer(&gmerge_head_array[0]) var analysis C.git_merge_analysis_t var preference C.git_merge_preference_t - err := C.git_merge_analysis(&analysis, &preference, r.ptr, (**C.git_merge_head)(ptr), C.size_t(len(theirHeads))) + err := C.git_merge_analysis(&analysis, &preference, r.ptr, (**C.git_annotated_commit)(ptr), C.size_t(len(theirHeads))) if err < 0 { return MergeAnalysisNone, MergePreferenceNone, MakeGitError(err) } diff --git a/merge_test.go b/merge_test.go index 7e884c0..1eba806 100644 --- a/merge_test.go +++ b/merge_test.go @@ -13,10 +13,10 @@ func TestMergeWithSelf(t *testing.T) { master, err := repo.LookupReference("refs/heads/master") checkFatal(t, err) - mergeHead, err := repo.MergeHeadFromRef(master) + mergeHead, err := repo.AnnotatedCommitFromRef(master) checkFatal(t, err) - mergeHeads := make([]*MergeHead, 1) + mergeHeads := make([]*AnnotatedCommit, 1) mergeHeads[0] = mergeHead err = repo.Merge(mergeHeads, nil, nil) checkFatal(t, err) @@ -30,10 +30,10 @@ func TestMergeAnalysisWithSelf(t *testing.T) { master, err := repo.LookupReference("refs/heads/master") checkFatal(t, err) - mergeHead, err := repo.MergeHeadFromRef(master) + mergeHead, err := repo.AnnotatedCommitFromRef(master) checkFatal(t, err) - mergeHeads := make([]*MergeHead, 1) + mergeHeads := make([]*AnnotatedCommit, 1) mergeHeads[0] = mergeHead a, _, err := repo.MergeAnalysis(mergeHeads) checkFatal(t, err) @@ -0,0 +1,99 @@ +package git + +/* +#include <git2.h> +*/ +import "C" + +import ( + "runtime" + "unsafe" +) + +// Note +type Note struct { + ptr *C.git_note +} + +// Free frees a git_note object +func (n *Note) Free() error { + if n.ptr == nil { + return ErrInvalid + } + runtime.SetFinalizer(n, nil) + C.git_note_free(n.ptr) + n.ptr = nil + return nil +} + +// Author returns the signature of the note author +func (n *Note) Author() *Signature { + ptr := C.git_note_author(n.ptr) + return newSignatureFromC(ptr) +} + +// Id returns the note object's id +func (n *Note) Id() *Oid { + ptr := C.git_note_id(n.ptr) + return newOidFromC(ptr) +} + +// Committer returns the signature of the note committer +func (n *Note) Committer() *Signature { + ptr := C.git_note_committer(n.ptr) + return newSignatureFromC(ptr) +} + +// Message returns the note message +func (n *Note) Message() string { + return C.GoString(C.git_note_message(n.ptr)) +} + +// NoteIterator +type NoteIterator struct { + ptr *C.git_note_iterator +} + +// NewNoteIterator creates a new iterator for notes +func (repo *Repository) NewNoteIterator(ref string) (*NoteIterator, error) { + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + var ptr *C.git_note_iterator + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_iterator_new(&ptr, repo.ptr, cref); ret < 0 { + return nil, MakeGitError(ret) + } + + iter := &NoteIterator{ptr: ptr} + runtime.SetFinalizer(iter, (*NoteIterator).Free) + return iter, nil +} + +// Free frees the note interator +func (v *NoteIterator) Free() { + runtime.SetFinalizer(v, nil) + C.git_note_iterator_free(v.ptr) +} + +// Next returns the current item (note id & annotated id) and advances the +// iterator internally to the next item +func (it *NoteIterator) Next() (noteId, annotatedId *Oid, err error) { + noteId, annotatedId = new(Oid), new(Oid) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_next(noteId.toC(), annotatedId.toC(), it.ptr); ret < 0 { + err = MakeGitError(ret) + } + return +} diff --git a/note_test.go b/note_test.go new file mode 100644 index 0000000..f5e9c01 --- /dev/null +++ b/note_test.go @@ -0,0 +1,113 @@ +package git + +import ( + "fmt" + "os" + "reflect" + "testing" + "time" +) + +func TestCreateNote(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + commitId, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + note, noteId := createTestNote(t, repo, commit) + + compareStrings(t, "I am a note\n", note.Message()) + compareStrings(t, noteId.String(), note.Id().String()) + compareStrings(t, "alice", note.Author().Name) + compareStrings(t, "[email protected]", note.Author().Email) + compareStrings(t, "alice", note.Committer().Name) + compareStrings(t, "[email protected]", note.Committer().Email) +} + +func TestNoteIterator(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + seedTestRepo(t, repo) + + notes := make([]*Note, 5) + for i := range notes { + commitId, _ := updateReadme(t, repo, fmt.Sprintf("README v%d\n", i+1)) + commit, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + note, _ := createTestNote(t, repo, commit) + notes[i] = note + } + + iter, err := repo.NewNoteIterator("") + checkFatal(t, err) + for { + noteId, commitId, err := iter.Next() + if err != nil { + if !IsErrorCode(err, ErrIterOver) { + checkFatal(t, err) + } + break + } + + note, err := repo.ReadNote("", commitId) + checkFatal(t, err) + + if !reflect.DeepEqual(note.Id(), noteId) { + t.Errorf("expected note oid '%v', actual '%v'", note.Id(), noteId) + } + } +} + +func TestRemoveNote(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + commitId, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + note, _ := createTestNote(t, repo, commit) + + _, err = repo.ReadNote("", commit.Id()) + checkFatal(t, err) + + err = repo.RemoveNote("", note.Author(), note.Committer(), commitId) + checkFatal(t, err) + + _, err = repo.ReadNote("", commit.Id()) + if err == nil { + t.Fatal("note remove failed") + } +} + +func TestDefaultNoteRef(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + + ref, err := repo.DefaultNoteRef() + checkFatal(t, err) + + compareStrings(t, "refs/notes/commits", ref) +} + +func createTestNote(t *testing.T, repo *Repository, commit *Commit) (*Note, *Oid) { + loc, err := time.LoadLocation("Europe/Berlin") + sig := &Signature{ + Name: "alice", + Email: "[email protected]", + When: time.Date(2015, 01, 05, 13, 0, 0, 0, loc), + } + + noteId, err := repo.CreateNote("", sig, sig, commit.Id(), "I am a note\n", false) + checkFatal(t, err) + + note, err := repo.ReadNote("", commit.Id()) + checkFatal(t, err) + + return note, noteId +} @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> */ import "C" import "runtime" @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> extern int _go_git_odb_foreach(git_odb *db, void *payload); extern void _go_git_odb_backend_free(git_odb_backend *backend); @@ -25,6 +24,9 @@ type OdbBackend struct { func NewOdb() (odb *Odb, err error) { odb = new(Odb) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_odb_new(&odb.ptr) if ret < 0 { return nil, MakeGitError(ret) @@ -40,6 +42,10 @@ func NewOdbBackendFromC(ptr *C.git_odb_backend) (backend *OdbBackend) { } func (v *Odb) AddBackend(backend *OdbBackend, priority int) (err error) { + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_odb_add_backend(v.ptr, backend.ptr, C.int(priority)) if ret < 0 { backend.Free() @@ -110,6 +116,9 @@ func (v *Odb) ForEach(callback OdbForEachCallback) error { err: nil, } + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C._go_git_odb_foreach(v.ptr, unsafe.Pointer(&data)) if ret == C.GIT_EUSER { return data.err @@ -140,6 +149,10 @@ func (v *Odb) Hash(data []byte, otype ObjectType) (oid *Oid, err error) { // contents of the object. func (v *Odb) NewReadStream(id *Oid) (*OdbReadStream, error) { stream := new(OdbReadStream) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_odb_open_rstream(&stream.ptr, v.ptr, id.toC()) if ret < 0 { return nil, MakeGitError(ret) @@ -154,6 +167,10 @@ func (v *Odb) NewReadStream(id *Oid) (*OdbReadStream, error) { // known in advance func (v *Odb) NewWriteStream(size int, otype ObjectType) (*OdbWriteStream, error) { stream := new(OdbWriteStream) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_odb_open_wstream(&stream.ptr, v.ptr, C.size_t(size), C.git_otype(otype)) if ret < 0 { return nil, MakeGitError(ret) @@ -207,6 +224,10 @@ 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) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_odb_stream_read(stream.ptr, ptr, size) if ret < 0 { return 0, MakeGitError(ret) @@ -239,6 +260,9 @@ func (stream *OdbWriteStream) Write(data []byte) (int, error) { ptr := (*C.char)(unsafe.Pointer(header.Data)) size := C.size_t(header.Len) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_odb_stream_write(stream.ptr, ptr, size) if ret < 0 { return 0, MakeGitError(ret) @@ -250,6 +274,9 @@ func (stream *OdbWriteStream) Write(data []byte) (int, error) { // 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 { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_odb_stream_finalize_write(stream.Id.toC(), stream.ptr) if ret < 0 { return MakeGitError(ret) diff --git a/packbuilder.go b/packbuilder.go index 666f5c4..7c94926 100644 --- a/packbuilder.go +++ b/packbuilder.go @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> #include <git2/pack.h> #include <stdlib.h> @@ -132,6 +131,9 @@ func (pb *Packbuilder) ForEach(callback PackbuilderForeachCallback) error { err: nil, } + runtime.LockOSThread() + defer runtime.UnlockOSThread() + err := C._go_git_packbuilder_foreach(pb.ptr, unsafe.Pointer(&data)) if err == C.GIT_EUSER { return data.err @@ -6,6 +6,7 @@ package git import "C" import ( "runtime" + "unsafe" ) type Patch struct { @@ -40,9 +41,47 @@ func (patch *Patch) String() (string, error) { return "", ErrInvalid } var buf C.git_buf + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ecode := C.git_patch_to_buf(&buf, patch.ptr) if ecode < 0 { return "", MakeGitError(ecode) } return C.GoString(buf.ptr), nil } + +func toPointer(data []byte) (ptr unsafe.Pointer) { + if len(data) > 0 { + ptr = unsafe.Pointer(&data[0]) + } else { + ptr = unsafe.Pointer(nil) + } + return +} + +func (v *Repository) PatchFromBuffers(oldPath, newPath string, oldBuf, newBuf []byte, opts *DiffOptions) (*Patch, error) { + var patchPtr *C.git_patch + + oldPtr := toPointer(oldBuf) + newPtr := (*C.char)(toPointer(newBuf)) + + cOldPath := C.CString(oldPath) + defer C.free(unsafe.Pointer(cOldPath)) + + cNewPath := C.CString(newPath) + defer C.free(unsafe.Pointer(cNewPath)) + + copts, _ := diffOptionsToC(opts) + defer freeDiffOptions(copts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_patch_from_buffers(&patchPtr, oldPtr, C.size_t(len(oldBuf)), cOldPath, newPtr, C.size_t(len(newBuf)), cNewPath, copts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + return newPatchFromC(patchPtr), nil +} diff --git a/patch_test.go b/patch_test.go index 569eac2..a061142 100644 --- a/patch_test.go +++ b/patch_test.go @@ -20,7 +20,11 @@ func TestPatch(t *testing.T) { newTree, err := repo.LookupTree(newTreeId) checkFatal(t, err) - diff, err := repo.DiffTreeToTree(originalTree, newTree, nil) + opts := &DiffOptions{ + OldPrefix: "a", + NewPrefix: "b", + } + diff, err := repo.DiffTreeToTree(originalTree, newTree, opts) checkFatal(t, err) patch, err := diff.Patch(0) diff --git a/push.go b/push.go deleted file mode 100644 index 5fb7f07..0000000 --- a/push.go +++ /dev/null @@ -1,182 +0,0 @@ -package git - -/* -#include <git2.h> -#include <git2/errors.h> - -int _go_git_push_status_foreach(git_push *push, void *data); -int _go_git_push_set_callbacks(git_push *push, void *packbuilder_progress_data, void *transfer_progress_data); - -*/ -import "C" -import ( - "runtime" - "unsafe" -) - -type Push struct { - ptr *C.git_push - - packbuilderProgress *PackbuilderProgressCallback - transferProgress *PushTransferProgressCallback -} - -func newPushFromC(cpush *C.git_push) *Push { - p := &Push{ptr: cpush} - runtime.SetFinalizer(p, (*Push).Free) - return p -} - -func (p *Push) Free() { - runtime.SetFinalizer(p, nil) - C.git_push_free(p.ptr) -} - -func (remote *Remote) NewPush() (*Push, error) { - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - var cpush *C.git_push - ret := C.git_push_new(&cpush, remote.ptr) - if ret < 0 { - return nil, MakeGitError(ret) - } - return newPushFromC(cpush), nil -} - -func (p *Push) Finish() error { - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_push_finish(p.ptr) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - -func (p *Push) UnpackOk() bool { - - ret := C.git_push_unpack_ok(p.ptr) - if ret == 0 { - return false - } - return true - -} - -func (p *Push) UpdateTips(sig *Signature, msg string) error { - - var csig *C.git_signature = nil - if sig != nil { - csig = sig.toC() - defer C.free(unsafe.Pointer(csig)) - } - - var cmsg *C.char - if msg == "" { - cmsg = nil - } else { - cmsg = C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) - } - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_push_update_tips(p.ptr, csig, cmsg) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - -func (p *Push) AddRefspec(refspec string) error { - - crefspec := C.CString(refspec) - defer C.free(unsafe.Pointer(crefspec)) - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_push_add_refspec(p.ptr, crefspec) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - -type PushOptions struct { - Version uint - PbParallelism uint -} - -func (p *Push) SetOptions(opts PushOptions) error { - copts := C.git_push_options{version: C.uint(opts.Version), pb_parallelism: C.uint(opts.PbParallelism)} - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_push_set_options(p.ptr, &copts) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - -type StatusForeachFunc func(ref string, msg string) int - -//export statusForeach -func statusForeach(_ref *C.char, _msg *C.char, _data unsafe.Pointer) C.int { - ref := C.GoString(_ref) - msg := C.GoString(_msg) - - cb := (*StatusForeachFunc)(_data) - - return C.int((*cb)(ref, msg)) -} - -func (p *Push) StatusForeach(callback StatusForeachFunc) error { - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C._go_git_push_status_foreach(p.ptr, unsafe.Pointer(&callback)) - if ret < 0 { - return MakeGitError(ret) - } - return nil - -} - -type PushCallbacks struct { - PackbuilderProgress *PackbuilderProgressCallback - TransferProgress *PushTransferProgressCallback -} - -type PackbuilderProgressCallback func(stage int, current uint, total uint) int -type PushTransferProgressCallback func(current uint, total uint, bytes uint) int - -//export packbuilderProgress -func packbuilderProgress(stage C.int, current C.uint, total C.uint, data unsafe.Pointer) C.int { - return C.int((*(*PackbuilderProgressCallback)(data))(int(stage), uint(current), uint(total))) -} - -//export pushTransferProgress -func pushTransferProgress(current C.uint, total C.uint, bytes C.size_t, data unsafe.Pointer) C.int { - return C.int((*(*PushTransferProgressCallback)(data))(uint(current), uint(total), uint(bytes))) -} - -func (p *Push) SetCallbacks(callbacks PushCallbacks) { - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - // save callbacks so they don't get GC'd - p.packbuilderProgress = callbacks.PackbuilderProgress - p.transferProgress = callbacks.TransferProgress - - C._go_git_push_set_callbacks(p.ptr, unsafe.Pointer(p.packbuilderProgress), unsafe.Pointer(p.transferProgress)) -} diff --git a/push_test.go b/push_test.go index 65f4dd2..cd708c6 100644 --- a/push_test.go +++ b/push_test.go @@ -3,55 +3,25 @@ package git import ( "os" "testing" - "time" ) -func Test_Push_ToRemote(t *testing.T) { +func TestRemotePush(t *testing.T) { repo := createBareTestRepo(t) defer os.RemoveAll(repo.Path()) - repo2 := createTestRepo(t) - defer os.RemoveAll(repo2.Workdir()) + localRepo := createTestRepo(t) + defer os.RemoveAll(localRepo.Workdir()) - remote, err := repo2.CreateRemote("test_push", repo.Path()) + remote, err := localRepo.CreateRemote("test_push", repo.Path()) checkFatal(t, err) - index, err := repo2.Index() - checkFatal(t, err) - - index.AddByPath("README") - - err = index.Write() - checkFatal(t, err) - - newTreeId, err := index.WriteTree() - checkFatal(t, err) - - tree, err := repo2.LookupTree(newTreeId) - checkFatal(t, err) - - sig := &Signature{Name: "Rand Om Hacker", Email: "[email protected]", When: time.Now()} - // this should cause master branch to be created if it does not already exist - _, err = repo2.CreateCommit("HEAD", sig, sig, "message", tree) - checkFatal(t, err) + seedTestRepo(t, localRepo) - push, err := remote.NewPush() + err = remote.Push([]string{"refs/heads/master"}, nil, nil, "") checkFatal(t, err) - err = push.AddRefspec("refs/heads/master") + _, err = localRepo.LookupReference("refs/remotes/test_push/master") checkFatal(t, err) - err = push.Finish() + _, err = repo.LookupReference("refs/heads/master") checkFatal(t, err) - - err = push.StatusForeach(func(ref string, msg string) int { - return 0 - }) - checkFatal(t, err) - - if !push.UnpackOk() { - t.Fatalf("unable to unpack") - } - - defer remote.Free() - defer repo.Free() } @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> #include <git2/sys/refdb_backend.h> extern void _go_git_refdb_backend_free(git_refdb_backend *backend); @@ -23,6 +22,9 @@ type RefdbBackend struct { func (v *Repository) NewRefdb() (refdb *Refdb, err error) { refdb = new(Refdb) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_refdb_new(&refdb.ptr, v.ptr) if ret < 0 { return nil, MakeGitError(ret) @@ -38,6 +40,9 @@ func NewRefdbBackendFromC(ptr *C.git_refdb_backend) (backend *RefdbBackend) { } func (v *Refdb) SetBackend(backend *RefdbBackend) (err error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_refdb_set_backend(v.ptr, backend.ptr) if ret < 0 { backend.Free() diff --git a/reference.go b/reference.go index ce9d722..46436a6 100644 --- a/reference.go +++ b/reference.go @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> */ import "C" import ( @@ -294,6 +293,10 @@ func (v *ReferenceNameIterator) Next() (string, error) { // returned error is git.ErrIterOver func (v *ReferenceIterator) Next() (*Reference, error) { var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_reference_next(&ptr, v.ptr) if ret < 0 { return nil, MakeGitError(ret) @@ -53,6 +53,9 @@ type CredentialsCallback func(url string, username_from_url string, allowed_type type TransferProgressCallback func(stats TransferProgress) ErrorCode type UpdateTipsCallback func(refname string, a *Oid, b *Oid) ErrorCode type CertificateCheckCallback func(cert *Certificate, valid bool, hostname string) ErrorCode +type PackbuilderProgressCallback func(stage int32, current, total uint32) ErrorCode +type PushTransferProgressCallback func(current, total uint32, bytes uint) ErrorCode +type PushUpdateReferenceCallback func(refname, status string) ErrorCode type RemoteCallbacks struct { SidebandProgressCallback TransportMessageCallback @@ -61,6 +64,9 @@ type RemoteCallbacks struct { TransferProgressCallback UpdateTipsCallback CertificateCheckCallback + PackProgressCallback PackbuilderProgressCallback + PushTransferProgressCallback + PushUpdateReferenceCallback } type Remote struct { @@ -101,6 +107,10 @@ type HostkeyCertificate struct { HashSHA1 [20]byte } +type PushOptions struct { + PbParallelism uint +} + type RemoteHead struct { Id *Oid Name string @@ -216,6 +226,38 @@ func certificateCheckCallback(_cert *C.git_cert, _valid C.int, _host *C.char, da return int(callbacks.CertificateCheckCallback(&cert, valid, host)) } +//export packProgressCallback +func packProgressCallback(stage C.int, current, total C.uint, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + + if callbacks.PackProgressCallback == nil { + return 0 + } + + return int(callbacks.PackProgressCallback(int32(stage), uint32(current), uint32(total))) +} + +//export pushTransferProgressCallback +func pushTransferProgressCallback(current, total C.uint, bytes C.size_t, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + if callbacks.PushTransferProgressCallback == nil { + return 0 + } + + return int(callbacks.PushTransferProgressCallback(uint32(current), uint32(total), uint(bytes))) +} + +//export pushUpdateReferenceCallback +func pushUpdateReferenceCallback(refname, status *C.char, data unsafe.Pointer) int { + callbacks := (*RemoteCallbacks)(data) + + if callbacks.PushUpdateReferenceCallback == nil { + return 0 + } + + return int(callbacks.PushUpdateReferenceCallback(C.GoString(refname), C.GoString(status))) +} + func RemoteIsValidName(name string) bool { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) @@ -249,6 +291,10 @@ func (r *Remote) Free() { func (repo *Repository) ListRemotes() ([]string, error) { var r C.git_strarray + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ecode := C.git_remote_list(&r, repo.ptr) if ecode < 0 { return nil, MakeGitError(ecode) @@ -278,6 +324,20 @@ func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) { return remote, nil } +func (repo *Repository) DeleteRemote(name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_delete(repo.ptr, cname) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch string) (*Remote, error) { remote := &Remote{} @@ -318,7 +378,7 @@ func (repo *Repository) CreateAnonymousRemote(url, fetch string) (*Remote, error return remote, nil } -func (repo *Repository) LoadRemote(name string) (*Remote, error) { +func (repo *Repository) LookupRemote(name string) (*Remote, error) { remote := &Remote{} cname := C.CString(name) @@ -327,7 +387,7 @@ func (repo *Repository) LoadRemote(name string) (*Remote, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_load(&remote.ptr, repo.ptr, cname) + ret := C.git_remote_lookup(&remote.ptr, repo.ptr, cname) if ret < 0 { return nil, MakeGitError(ret) } @@ -559,6 +619,9 @@ func (o *Remote) Fetch(refspecs []string, sig *Signature, msg string) error { crefspecs.strings = makeCStringsFromStrings(refspecs) defer freeStrarray(&crefspecs) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_remote_fetch(o.ptr, &crefspecs, csig, cmsg) if ret < 0 { return MakeGitError(ret) @@ -589,6 +652,9 @@ func (o *Remote) Ls(filterRefs ...string) ([]RemoteHead, error) { var refs **C.git_remote_head var length C.size_t + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if ret := C.git_remote_ls(&refs, &length, o.ptr); ret != 0 { return nil, MakeGitError(ret) } @@ -626,3 +692,54 @@ func (o *Remote) Ls(filterRefs ...string) ([]RemoteHead, error) { return heads, nil } + +func (o *Remote) Push(refspecs []string, opts *PushOptions, sig *Signature, msg string) error { + var csig *C.git_signature = nil + if sig != nil { + csig = sig.toC() + defer C.free(unsafe.Pointer(csig)) + } + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + var copts C.git_push_options + C.git_push_init_options(&copts, C.GIT_PUSH_OPTIONS_VERSION) + if opts != nil { + copts.pb_parallelism = C.uint(opts.PbParallelism) + } + + crefspecs := C.git_strarray{} + crefspecs.count = C.size_t(len(refspecs)) + crefspecs.strings = makeCStringsFromStrings(refspecs) + defer freeStrarray(&crefspecs) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_push(o.ptr, &crefspecs, &copts, csig, cmsg) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +func (o *Remote) PruneRefs() bool { + return C.git_remote_prune_refs(o.ptr) > 0 +} + +func (o *Remote) Prune() error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_remote_prune(o.ptr) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} diff --git a/remote_test.go b/remote_test.go index 631a6cd..54a66ed 100644 --- a/remote_test.go +++ b/remote_test.go @@ -1,8 +1,10 @@ package git import ( + "fmt" "os" "testing" + "time" ) func TestRefspecs(t *testing.T) { @@ -132,3 +134,84 @@ func TestRemoteLsFiltering(t *testing.T) { t.Fatalf("Expected head to have a name, but it's empty") } } + +func TestRemotePruneRefs(t *testing.T) { + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + defer repo.Free() + + config, err := repo.Config() + checkFatal(t, err) + defer config.Free() + + err = config.SetBool("remote.origin.prune", true) + checkFatal(t, err) + + _, err = repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") + checkFatal(t, err) + + remote, err := repo.LookupRemote("origin") + checkFatal(t, err) + + if !remote.PruneRefs() { + t.Fatal("Expected remote to be configured to prune references") + } +} + +func TestRemotePrune(t *testing.T) { + remoteRepo := createTestRepo(t) + defer os.RemoveAll(remoteRepo.Workdir()) + defer remoteRepo.Free() + + head, _ := seedTestRepo(t, remoteRepo) + commit, err := remoteRepo.LookupCommit(head) + checkFatal(t, err) + defer commit.Free() + + sig := &Signature{ + Name: "Rand Om Hacker", + Email: "[email protected]", + When: time.Now(), + } + + remoteRef, err := remoteRepo.CreateBranch("test-prune", commit, true, sig, "branch test-prune") + checkFatal(t, err) + + repo := createTestRepo(t) + defer os.RemoveAll(repo.Workdir()) + defer repo.Free() + + config, err := repo.Config() + checkFatal(t, err) + defer config.Free() + + remoteUrl := fmt.Sprintf("file://%s", remoteRepo.Workdir()) + remote, err := repo.CreateRemote("origin", remoteUrl) + checkFatal(t, err) + + err = remote.Fetch([]string{"test-prune"}, sig, "") + checkFatal(t, err) + + _, err = repo.CreateReference("refs/remotes/origin/test-prune", head, true, sig, "remote reference") + checkFatal(t, err) + + err = remoteRef.Delete() + checkFatal(t, err) + + err = config.SetBool("remote.origin.prune", true) + checkFatal(t, err) + + rr, err := repo.LookupRemote("origin") + checkFatal(t, err) + + err = rr.ConnectFetch() + checkFatal(t, err) + + err = rr.Prune() + checkFatal(t, err) + + _, err = repo.LookupReference("refs/remotes/origin/test-prune") + if err == nil { + t.Fatal("Expected error getting a pruned reference") + } +} diff --git a/repository.go b/repository.go index 09f5fef..7760c3a 100644 --- a/repository.go +++ b/repository.go @@ -72,6 +72,9 @@ func InitRepository(path string, isbare bool) (*Repository, error) { func NewRepositoryWrapOdb(odb *Odb) (repo *Repository, err error) { repo = new(Repository) + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_repository_wrap_odb(&repo.ptr, odb.ptr) if ret < 0 { return nil, MakeGitError(ret) @@ -386,6 +389,9 @@ func (v *Repository) CreateTag( ctarget := commit.gitObject.ptr + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_tag_create(oid.toC(), v.ptr, cname, ctarget, taggerSig, cmessage, 0) if ret < 0 { return nil, MakeGitError(ret) @@ -450,7 +456,7 @@ func (v *Repository) TreeBuilder() (*TreeBuilder, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - if ret := C.git_treebuilder_create(&bld.ptr, nil); ret < 0 { + if ret := C.git_treebuilder_new(&bld.ptr, v.ptr, nil); ret < 0 { return nil, MakeGitError(ret) } runtime.SetFinalizer(bld, (*TreeBuilder).Free) @@ -465,7 +471,7 @@ func (v *Repository) TreeBuilderFromTree(tree *Tree) (*TreeBuilder, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - if ret := C.git_treebuilder_create(&bld.ptr, tree.cast_ptr); ret < 0 { + if ret := C.git_treebuilder_new(&bld.ptr, v.ptr, tree.cast_ptr); ret < 0 { return nil, MakeGitError(ret) } runtime.SetFinalizer(bld, (*TreeBuilder).Free) @@ -524,3 +530,103 @@ func (v *Repository) DwimReference(name string) (*Reference, error) { return newReferenceFromC(ptr, v), nil } + +// CreateNote adds a note for an object +func (v *Repository) CreateNote( + ref string, author, committer *Signature, id *Oid, + note string, force bool) (*Oid, error) { + + oid := new(Oid) + + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + authorSig := author.toC() + defer C.git_signature_free(authorSig) + + committerSig := committer.toC() + defer C.git_signature_free(committerSig) + + cnote := C.CString(note) + defer C.free(unsafe.Pointer(cnote)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_note_create( + oid.toC(), v.ptr, cref, authorSig, + committerSig, id.toC(), cnote, cbool(force)) + + if ret < 0 { + return nil, MakeGitError(ret) + } + return oid, nil +} + +// ReadNote reads the note for an object +func (v *Repository) ReadNote(ref string, id *Oid) (*Note, error) { + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + note := new(Note) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_read(¬e.ptr, v.ptr, cref, id.toC()); ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(note, (*Note).Free) + return note, nil +} + +// RemoveNote removes the note for an object +func (v *Repository) RemoveNote(ref string, author, committer *Signature, id *Oid) error { + var cref *C.char + if ref == "" { + cref = nil + } else { + cref = C.CString(ref) + defer C.free(unsafe.Pointer(cref)) + } + + authorSig := author.toC() + defer C.git_signature_free(authorSig) + + committerSig := committer.toC() + defer C.git_signature_free(committerSig) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_note_remove(v.ptr, cref, authorSig, committerSig, id.toC()) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +// DefaultNoteRef returns the default notes reference for a repository +func (v *Repository) DefaultNoteRef() (string, error) { + var ptr *C.char + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_default_ref(&ptr, v.ptr); ret < 0 { + return "", MakeGitError(ret) + } + + return C.GoString(ptr), nil +} diff --git a/revparse.go b/revparse.go index 308da4c..7eb04f1 100644 --- a/revparse.go +++ b/revparse.go @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> extern void _go_git_revspec_free(git_revspec *revspec); */ diff --git a/script/check-MakeGitError-thread-lock.go b/script/check-MakeGitError-thread-lock.go new file mode 100644 index 0000000..f6b01b3 --- /dev/null +++ b/script/check-MakeGitError-thread-lock.go @@ -0,0 +1,71 @@ +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/printer" + "go/token" + "log" + "os" + "path/filepath" + "strings" +) + +var ( + fset = token.NewFileSet() +) + +func main() { + log.SetFlags(0) + + bpkg, err := build.ImportDir(".", 0) + if err != nil { + log.Fatal(err) + } + + pkgs, err := parser.ParseDir(fset, bpkg.Dir, func(fi os.FileInfo) bool { return filepath.Ext(fi.Name()) == ".go" }, 0) + if err != nil { + log.Fatal(err) + } + + for _, pkg := range pkgs { + if err := checkPkg(pkg); err != nil { + log.Fatal(err) + } + } + if len(pkgs) == 0 { + log.Fatal("No packages to check.") + } +} + +var ignoreViolationsInFunc = map[string]bool{ + "MakeGitError": true, + "MakeGitError2": true, +} + +func checkPkg(pkg *ast.Package) error { + var violations []string + ast.Inspect(pkg, func(node ast.Node) bool { + switch node := node.(type) { + case *ast.FuncDecl: + var b bytes.Buffer + if err := printer.Fprint(&b, fset, node); err != nil { + log.Fatal(err) + } + src := b.String() + + if strings.Contains(src, "MakeGitError") && !strings.Contains(src, "runtime.LockOSThread()") && !strings.Contains(src, "defer runtime.UnlockOSThread()") && !ignoreViolationsInFunc[node.Name.Name] { + pos := fset.Position(node.Pos()) + violations = append(violations, fmt.Sprintf("%s at %s:%d", node.Name.Name, pos.Filename, pos.Line)) + } + } + return true + }) + if len(violations) > 0 { + return fmt.Errorf("%d non-thread-locked calls to MakeGitError found. To fix, add the following to each func below that calls MakeGitError, before the cgo call that might produce the error:\n\n\truntime.LockOSThread()\n\tdefer runtime.UnlockOSThread()\n\n%s", len(violations), strings.Join(violations, "\n")) + } + return nil +} @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> */ import "C" diff --git a/submodule.go b/submodule.go index 35ceeb0..6923c61 100644 --- a/submodule.go +++ b/submodule.go @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> extern int _go_git_visit_submodule(git_repository *repo, void *fct); */ @@ -12,6 +11,14 @@ import ( "unsafe" ) +// SubmoduleUpdateOptions +type SubmoduleUpdateOptions struct { + *CheckoutOpts + *RemoteCallbacks + CloneCheckoutStrategy CheckoutStrategy + Signature *Signature +} + // Submodule type Submodule struct { ptr *C.git_submodule @@ -21,10 +28,10 @@ type SubmoduleUpdate int const ( SubmoduleUpdateReset SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_RESET - SubmoduleUpdateCheckout SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_CHECKOUT - SubmoduleUpdateRebase SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_REBASE - SubmoduleUpdateMerge SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_MERGE - SubmoduleUpdateNone SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_NONE + SubmoduleUpdateCheckout SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_CHECKOUT + SubmoduleUpdateRebase SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_REBASE + SubmoduleUpdateMerge SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_MERGE + SubmoduleUpdateNone SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_NONE ) type SubmoduleIgnore int @@ -90,10 +97,10 @@ func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) { type SubmoduleCbk func(sub *Submodule, name string) int //export SubmoduleVisitor -func SubmoduleVisitor(csub unsafe.Pointer, name string, cfct unsafe.Pointer) int { +func SubmoduleVisitor(csub unsafe.Pointer, name *C.char, cfct unsafe.Pointer) C.int { sub := &Submodule{(*C.git_submodule)(csub)} fct := *(*SubmoduleCbk)(cfct) - return fct(sub, name) + return (C.int)(fct(sub, C.GoString(name))) } func (repo *Repository) ForeachSubmodule(cbk SubmoduleCbk) error { @@ -227,8 +234,8 @@ func (sub *Submodule) SetIgnore(ignore SubmoduleIgnore) SubmoduleIgnore { return SubmoduleIgnore(o) } -func (sub *Submodule) Update() SubmoduleUpdate { - o := C.git_submodule_update(sub.ptr) +func (sub *Submodule) UpdateStrategy() SubmoduleUpdate { + o := C.git_submodule_update_strategy(sub.ptr) return SubmoduleUpdate(o) } @@ -308,3 +315,31 @@ func (repo *Repository) ReloadAllSubmodules(force bool) error { } return nil } + +func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error { + var copts C.git_submodule_update_options + populateSubmoduleUpdateOptions(&copts, opts) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_update(sub.ptr, cbool(init), &copts) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +func populateSubmoduleUpdateOptions(ptr *C.git_submodule_update_options, opts *SubmoduleUpdateOptions) { + C.git_submodule_update_init_options(ptr, C.GIT_SUBMODULE_UPDATE_OPTIONS_VERSION) + + if opts == nil { + return + } + + populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts) + populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks) + ptr.clone_checkout_strategy = C.uint(opts.CloneCheckoutStrategy) + ptr.signature = opts.Signature.toC() +} diff --git a/submodule_test.go b/submodule_test.go new file mode 100644 index 0000000..1c8f471 --- /dev/null +++ b/submodule_test.go @@ -0,0 +1,24 @@ +package git + +import ( + "testing" +) + +func TestSubmoduleForeach(t *testing.T) { + repo := createTestRepo(t) + seedTestRepo(t, repo) + + _, err := repo.AddSubmodule("http://example.org/submodule", "submodule", true) + checkFatal(t, err) + + i := 0 + err = repo.ForeachSubmodule(func(sub *Submodule, name string) int { + i++ + return 0 + }) + checkFatal(t, err) + + if i != 1 { + t.Fatalf("expected one submodule found but got %i", i) + } +} @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> extern int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr); */ @@ -162,7 +161,7 @@ func (v *TreeBuilder) Write() (*Oid, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - err := C.git_treebuilder_write(oid.toC(), v.repo.ptr, v.ptr) + err := C.git_treebuilder_write(oid.toC(), v.ptr) if err < 0 { return nil, MakeGitError(err) diff --git a/vendor/libgit2 b/vendor/libgit2 -Subproject d09458f3e9f24afa0689ce90b7d419187237263 +Subproject 04bdd97f2b63793a8720fd19007911e946ba3c5 @@ -2,7 +2,6 @@ package git /* #include <git2.h> -#include <git2/errors.h> */ import "C" @@ -1,9 +1,7 @@ #include "_cgo_export.h" -#include "git2.h" -#include "git2/sys/odb_backend.h" -#include "git2/sys/refdb_backend.h" -#include "git2/submodule.h" -#include "git2/pack.h" +#include <git2.h> +#include <git2/sys/odb_backend.h> +#include <git2/sys/refdb_backend.h> typedef int (*gogit_submodule_cbk)(git_submodule *sm, const char *name, void *payload); @@ -71,24 +69,17 @@ void _go_git_setup_diff_notify_callbacks(git_diff_options *opts) { void _go_git_setup_callbacks(git_remote_callbacks *callbacks) { typedef int (*completion_cb)(git_remote_completion_type type, void *data); typedef int (*update_tips_cb)(const char *refname, const git_oid *a, const git_oid *b, void *data); + typedef int (*push_update_reference_cb)(const char *refname, const char *status, void *data); + callbacks->sideband_progress = (git_transport_message_cb)sidebandProgressCallback; callbacks->completion = (completion_cb)completionCallback; callbacks->credentials = (git_cred_acquire_cb)credentialsCallback; callbacks->transfer_progress = (git_transfer_progress_cb)transferProgressCallback; callbacks->update_tips = (update_tips_cb)updateTipsCallback; callbacks->certificate_check = (git_transport_certificate_check_cb) certificateCheckCallback; -} - -typedef int (*status_foreach_cb)(const char *ref, const char *msg, void *data); - -int _go_git_push_status_foreach(git_push *push, void *data) -{ - return git_push_status_foreach(push, (status_foreach_cb)statusForeach, data); -} - -int _go_git_push_set_callbacks(git_push *push, void *packbuilder_progress_data, void *transfer_progress_data) -{ - return git_push_set_callbacks(push, packbuilderProgress, packbuilder_progress_data, pushTransferProgress, transfer_progress_data); + callbacks->pack_progress = (git_packbuilder_progress) packProgressCallback; + callbacks->push_transfer_progress = (git_push_transfer_progress) pushTransferProgressCallback; + callbacks->push_update_reference = (push_update_reference_cb) pushUpdateReferenceCallback; } int _go_blob_chunk_cb(char *buffer, size_t maxLen, void *payload) |
