diff options
| -rw-r--r-- | README.md | 18 | ||||
| -rw-r--r-- | config.go | 52 | ||||
| -rw-r--r-- | diff.go | 46 | ||||
| -rw-r--r-- | diff_test.go | 33 | ||||
| -rw-r--r-- | git.go | 1 | ||||
| -rw-r--r-- | note.go | 99 | ||||
| -rw-r--r-- | note_test.go | 113 | ||||
| -rw-r--r-- | push.go | 181 | ||||
| -rw-r--r-- | push_test.go | 46 | ||||
| -rw-r--r-- | remote.go | 95 | ||||
| -rw-r--r-- | remote_test.go | 83 | ||||
| -rw-r--r-- | repository.go | 104 | ||||
| -rw-r--r-- | submodule.go | 52 | ||||
| -rw-r--r-- | submodule_test.go | 24 | ||||
| -rw-r--r-- | tree.go | 2 | ||||
| m--------- | vendor/libgit2 | 0 | ||||
| -rw-r--r-- | wrapper.c | 25 |
17 files changed, 718 insertions, 256 deletions
@@ -3,12 +3,24 @@ 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. @@ -25,7 +37,7 @@ libgit2 uses OpenSSL and LibSSH2 for performing encrypted network connections. F 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 @@ -35,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), } @@ -74,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) @@ -130,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) @@ -234,7 +232,6 @@ 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() @@ -368,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 +} @@ -190,6 +190,46 @@ func (diff *Diff) FindSimilar(opts *DiffFindOptions) error { 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 @@ -371,6 +411,8 @@ func DefaultDiffOptions() (DiffOptions, error) { InterhunkLines: uint32(opts.interhunk_lines), IdAbbrev: uint16(opts.id_abbrev), MaxSize: int(opts.max_size), + OldPrefix: "a", + NewPrefix: "b", }, nil } @@ -479,6 +521,8 @@ func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *d 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 { @@ -493,6 +537,8 @@ 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)) } } diff --git a/diff_test.go b/diff_test.go index 84d72db..fc6fed9 100644 --- a/diff_test.go +++ b/diff_test.go @@ -3,6 +3,7 @@ package git import ( "errors" "os" + "strings" "testing" ) @@ -75,6 +76,8 @@ func TestDiffTreeToTree(t *testing.T) { callbackInvoked = true return nil }, + OldPrefix: "x1/", + NewPrefix: "y1/", } diff, err := repo.DiffTreeToTree(originalTree, newTree, &opts) @@ -90,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) @@ -131,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) { @@ -2,6 +2,7 @@ package git /* #include <git2.h> +#include <git2/sys/openssl.h> */ import "C" import ( @@ -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 +} diff --git a/push.go b/push.go deleted file mode 100644 index e6fe51f..0000000 --- a/push.go +++ /dev/null @@ -1,181 +0,0 @@ -package git - -/* -#include <git2.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() } @@ -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)) @@ -285,7 +327,7 @@ func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) { func (repo *Repository) DeleteRemote(name string) error { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) - + runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -650,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 2baeb4a..7760c3a 100644 --- a/repository.go +++ b/repository.go @@ -456,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) @@ -471,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) @@ -530,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/submodule.go b/submodule.go index d5ab69f..6923c61 100644 --- a/submodule.go +++ b/submodule.go @@ -11,6 +11,14 @@ import ( "unsafe" ) +// SubmoduleUpdateOptions +type SubmoduleUpdateOptions struct { + *CheckoutOpts + *RemoteCallbacks + CloneCheckoutStrategy CheckoutStrategy + Signature *Signature +} + // Submodule type Submodule struct { ptr *C.git_submodule @@ -20,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 @@ -89,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 { @@ -226,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) } @@ -307,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) + } +} @@ -161,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 169497d1e7c238d2925577d1af3dc03e9a507cd +Subproject 04bdd97f2b63793a8720fd19007911e946ba3c5 @@ -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) |
