From 050e6fbc49ac1a173af5e3462b3c2d330cadd113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 15 Mar 2015 00:45:30 +0100 Subject: [PATCH 01/36] Correct README on what master tracks The second mention still said that master tracks master. Add a mention of next which will become the branch to track upstream tip. --- README.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index faeb5f5..c604eaf 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,34 @@ git2go [![GoDoc](https://godoc.org/github.com/libgit2/git2go?status.svg)](http://godoc.org/github.com/libgit2/git2go) [![Build Status](https://travis-ci.org/libgit2/git2go.svg?branch=master)](https://travis-ci.org/libgit2/git2go) -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. +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 installed if you want libgit2 to support HTTPS and SSH respectively. +This project wraps the functionality provided by libgit2. If you're using a stable version, install it to your system via your system's package manger and then install git2go as usual. + +Otherwise (`next` which tracks an unstable version), we need to build libgit2 as well. In order to build it, you need `cmake`, `pkg-config` and a C compiler. You will also need the development packages for OpenSSL and LibSSH2 installed if you want libgit2 to support HTTPS and SSH respectively. ### 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 +git2go has `master` which tracks the latest release of libgit2, and 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. +to use a version of git2go which will work against libgit2 v0.22 and dynamically link to the library. You can use -### From master + import "github.com/libgit2/git2go" -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. +to use the version which works against the latest release. + +### From `next` + +The `next` 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. + git checkout next git submodule update --init # get libgit2 make install @@ -33,11 +40,11 @@ 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 ----------------- -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 +For the stable version, `go test` will work as usual. For the `next` branch, similarly to installing, running the tests requires linking against the local libgit2 library, so the Makefile provides a wrapper make test From c4fce1a218fd33938c0be90e939531c0a00ebf7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 15 Mar 2015 01:49:32 +0100 Subject: [PATCH 02/36] Update to libgit2 d675982a153 There's been some changes to the checkout strategy, especially the SAFE_CREATE mode, which is now the RECREATE_MISSING flag, though that shouldn't be necessary to use in the general case. The largest changes come from the removal of the signture from ref-modifying functions/methods and the removal of the reflog string in all but those directly related to moving references. --- branch.go | 36 ++++---------------------------- checkout.go | 15 ++++++++----- cherrypick_test.go | 2 +- push_test.go | 2 +- reference.go | 30 ++++++-------------------- reference_test.go | 21 +++++++------------ remote.go | 38 ++++++--------------------------- remote_test.go | 15 ++++--------- repository.go | 52 +++++++--------------------------------------- revparse_test.go | 2 +- submodule.go | 7 ------- vendor/libgit2 | 2 +- 12 files changed, 49 insertions(+), 173 deletions(-) diff --git a/branch.go b/branch.go index 22b767e..0daa99d 100644 --- a/branch.go +++ b/branch.go @@ -90,30 +90,16 @@ func (repo *Repository) NewBranchIterator(flags BranchType) (*BranchIterator, er return newBranchIteratorFromC(repo, ptr), nil } -func (repo *Repository) CreateBranch(branchName string, target *Commit, force bool, signature *Signature, msg string) (*Branch, error) { +func (repo *Repository) CreateBranch(branchName string, target *Commit, force bool) (*Branch, error) { ref := new(Reference) cBranchName := C.CString(branchName) cForce := cbool(force) - cSignature, err := signature.toC() - if err != nil { - return nil, err - } - defer C.git_signature_free(cSignature) - - 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_branch_create(&ref.ptr, repo.ptr, cBranchName, target.cast_ptr, cForce, cSignature, cmsg) + ret := C.git_branch_create(&ref.ptr, repo.ptr, cBranchName, target.cast_ptr, cForce) if ret < 0 { return nil, MakeGitError(ret) } @@ -131,29 +117,15 @@ func (b *Branch) Delete() error { return nil } -func (b *Branch) Move(newBranchName string, force bool, signature *Signature, msg string) (*Branch, error) { +func (b *Branch) Move(newBranchName string, force bool) (*Branch, error) { var ptr *C.git_reference cNewBranchName := C.CString(newBranchName) cForce := cbool(force) - cSignature, err := signature.toC() - if err != nil { - return nil, err - } - defer C.git_signature_free(cSignature) - - 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_branch_move(&ptr, b.Reference.ptr, cNewBranchName, cForce, cSignature, cmsg) + ret := C.git_branch_move(&ptr, b.Reference.ptr, cNewBranchName, cForce) if ret < 0 { return nil, MakeGitError(ret) } diff --git a/checkout.go b/checkout.go index 6eb6098..98c1ee6 100644 --- a/checkout.go +++ b/checkout.go @@ -15,18 +15,23 @@ type CheckoutStrategy uint const ( CheckoutNone CheckoutStrategy = C.GIT_CHECKOUT_NONE // Dry run, no actual updates CheckoutSafe CheckoutStrategy = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data - CheckoutSafeCreate CheckoutStrategy = C.GIT_CHECKOUT_SAFE_CREATE // Allow safe updates plus creation of missing files - CheckoutForce CheckoutStrategy = C.GIT_CHECKOUT_FORCE // Allow all updates to force working directory to look like index + CheckoutRecreateMissing CheckoutStrategy = C.GIT_CHECKOUT_RECREATE_MISSING // Allow checkout to recreate missing files CheckoutAllowConflicts CheckoutStrategy = C.GIT_CHECKOUT_ALLOW_CONFLICTS // Allow checkout to make safe updates even if conflicts are found CheckoutRemoveUntracked CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_UNTRACKED // Remove untracked files not in index (that are not ignored) CheckoutRemoveIgnored CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_IGNORED // Remove ignored files not in index CheckoutUpdateOnly CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_ONLY // Only update existing files, don't create new ones CheckoutDontUpdateIndex CheckoutStrategy = C.GIT_CHECKOUT_DONT_UPDATE_INDEX // Normally checkout updates index entries as it goes; this stops that CheckoutNoRefresh CheckoutStrategy = C.GIT_CHECKOUT_NO_REFRESH // Don't refresh index/config/etc before doing checkout + CheckoutSkipUnmerged CheckoutStrategy = C.GIT_CHECKOUT_SKIP_UNMERGED // Allow checkout to skip unmerged files + CheckoutUserOurs CheckoutStrategy = C.GIT_CHECKOUT_USE_OURS // For unmerged files, checkout stage 2 from index + CheckoutUseTheirs CheckoutStrategy = C.GIT_CHECKOUT_USE_THEIRS // For unmerged files, checkout stage 3 from index CheckoutDisablePathspecMatch CheckoutStrategy = C.GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH // Treat pathspec as simple list of exact match file paths - CheckoutSkipUnmerged CheckoutStrategy = C.GIT_CHECKOUT_SKIP_UNMERGED // Allow checkout to skip unmerged files (NOT IMPLEMENTED) - CheckoutUserOurs CheckoutStrategy = C.GIT_CHECKOUT_USE_OURS // For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) - CheckoutUseTheirs CheckoutStrategy = C.GIT_CHECKOUT_USE_THEIRS // For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) + CheckoutSkipLockedDirectories CheckoutStrategy = C.GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES // Ignore directories in use, they will be left empty + CheckoutDontOverwriteIgnored CheckoutStrategy = C.GIT_CHECKOUT_DONT_OVERWRITE_IGNORED // Don't overwrite ignored files that exist in the checkout target + CheckoutConflictStyleMerge CheckoutStrategy = C.GIT_CHECKOUT_CONFLICT_STYLE_MERGE // Write normal merge files for conflicts + CheckoutConflictStyleDiff3 CheckoutStrategy = C.GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 // Include common ancestor data in diff3 format files for conflicts + CheckoutDontRemoveExisting CheckoutStrategy = C.GIT_CHECKOUT_DONT_REMOVE_EXISTING // Don't overwrite existing files or folders + CheckoutDontWriteIndex CheckoutStrategy = C.GIT_CHECKOUT_DONT_WRITE_INDEX // Normally checkout writes the index upon completion; this prevents that CheckoutUpdateSubmodules CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES // Recursively checkout submodules with same options (NOT IMPLEMENTED) CheckoutUpdateSubmodulesIfChanged CheckoutStrategy = C.GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED // Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) ) diff --git a/cherrypick_test.go b/cherrypick_test.go index f06dbdd..141a55d 100644 --- a/cherrypick_test.go +++ b/cherrypick_test.go @@ -16,7 +16,7 @@ func checkout(t *testing.T, repo *Repository, commit *Commit) { t.Fatal(err) } - err = repo.SetHeadDetached(commit.Id(), commit.Author(), "checkout") + err = repo.SetHeadDetached(commit.Id()) if err != nil { t.Fatal(err) } diff --git a/push_test.go b/push_test.go index cd708c6..ad72e4d 100644 --- a/push_test.go +++ b/push_test.go @@ -16,7 +16,7 @@ func TestRemotePush(t *testing.T) { seedTestRepo(t, localRepo) - err = remote.Push([]string{"refs/heads/master"}, nil, nil, "") + err = remote.Push([]string{"refs/heads/master"}, nil) checkFatal(t, err) _, err = localRepo.LookupReference("refs/remotes/test_push/master") diff --git a/reference.go b/reference.go index ef12d0b..61e2b26 100644 --- a/reference.go +++ b/reference.go @@ -27,7 +27,7 @@ func newReferenceFromC(ptr *C.git_reference, repo *Repository) *Reference { return ref } -func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string) (*Reference, error) { +func (v *Reference) SetSymbolicTarget(target string, msg string) (*Reference, error) { var ptr *C.git_reference ctarget := C.CString(target) @@ -36,12 +36,6 @@ func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string) runtime.LockOSThread() defer runtime.UnlockOSThread() - csig, err := sig.toC() - if err != nil { - return nil, err - } - defer C.free(unsafe.Pointer(csig)) - var cmsg *C.char if msg == "" { cmsg = nil @@ -50,7 +44,7 @@ func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string) defer C.free(unsafe.Pointer(cmsg)) } - ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget, csig, cmsg) + ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget, cmsg) if ret < 0 { return nil, MakeGitError(ret) } @@ -58,18 +52,12 @@ func (v *Reference) SetSymbolicTarget(target string, sig *Signature, msg string) return newReferenceFromC(ptr, v.repo), nil } -func (v *Reference) SetTarget(target *Oid, sig *Signature, msg string) (*Reference, error) { +func (v *Reference) SetTarget(target *Oid, msg string) (*Reference, error) { var ptr *C.git_reference runtime.LockOSThread() defer runtime.UnlockOSThread() - csig, err := sig.toC() - if err != nil { - return nil, err - } - defer C.free(unsafe.Pointer(csig)) - var cmsg *C.char if msg == "" { cmsg = nil @@ -78,7 +66,7 @@ func (v *Reference) SetTarget(target *Oid, sig *Signature, msg string) (*Referen defer C.free(unsafe.Pointer(cmsg)) } - ret := C.git_reference_set_target(&ptr, v.ptr, target.toC(), csig, cmsg) + ret := C.git_reference_set_target(&ptr, v.ptr, target.toC(), cmsg) if ret < 0 { return nil, MakeGitError(ret) } @@ -100,17 +88,11 @@ func (v *Reference) Resolve() (*Reference, error) { return newReferenceFromC(ptr, v.repo), nil } -func (v *Reference) Rename(name string, force bool, sig *Signature, msg string) (*Reference, error) { +func (v *Reference) Rename(name string, force bool, msg string) (*Reference, error) { var ptr *C.git_reference cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) - csig, err := sig.toC() - if err != nil { - return nil, err - } - defer C.free(unsafe.Pointer(csig)) - var cmsg *C.char if msg == "" { cmsg = nil @@ -122,7 +104,7 @@ func (v *Reference) Rename(name string, force bool, sig *Signature, msg string) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), csig, cmsg) + ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), cmsg) if ret < 0 { return nil, MakeGitError(ret) diff --git a/reference_test.go b/reference_test.go index c7d52fb..562e276 100644 --- a/reference_test.go +++ b/reference_test.go @@ -14,14 +14,7 @@ func TestRefModification(t *testing.T) { commitId, treeId := seedTestRepo(t, repo) - loc, err := time.LoadLocation("Europe/Berlin") - checkFatal(t, err) - sig := &Signature{ - Name: "Rand Om Hacker", - Email: "random@hacker.com", - When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), - } - _, err = repo.CreateReference("refs/tags/tree", treeId, true, sig, "testTreeTag") + _, err := repo.CreateReference("refs/tags/tree", treeId, true, "testTreeTag") checkFatal(t, err) tag, err := repo.LookupReference("refs/tags/tree") @@ -52,7 +45,7 @@ func TestRefModification(t *testing.T) { t.Fatalf("Wrong ref target") } - _, err = tag.Rename("refs/tags/renamed", false, nil, "") + _, err = tag.Rename("refs/tags/renamed", false, "") checkFatal(t, err) tag, err = repo.LookupReference("refs/tags/renamed") checkFatal(t, err) @@ -85,13 +78,13 @@ func TestReferenceIterator(t *testing.T) { commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) checkFatal(t, err) - _, err = repo.CreateReference("refs/heads/one", commitId, true, sig, "headOne") + _, err = repo.CreateReference("refs/heads/one", commitId, true, "headOne") checkFatal(t, err) - _, err = repo.CreateReference("refs/heads/two", commitId, true, sig, "headTwo") + _, err = repo.CreateReference("refs/heads/two", commitId, true, "headTwo") checkFatal(t, err) - _, err = repo.CreateReference("refs/heads/three", commitId, true, sig, "headThree") + _, err = repo.CreateReference("refs/heads/three", commitId, true, "headThree") checkFatal(t, err) iter, err := repo.NewReferenceIterator() @@ -143,7 +136,7 @@ func TestReferenceOwner(t *testing.T) { defer os.RemoveAll(repo.Workdir()) commitId, _ := seedTestRepo(t, repo) - ref, err := repo.CreateReference("refs/heads/foo", commitId, true, nil, "") + ref, err := repo.CreateReference("refs/heads/foo", commitId, true, "") checkFatal(t, err) owner := ref.Owner() @@ -162,7 +155,7 @@ func TestUtil(t *testing.T) { commitId, _ := seedTestRepo(t, repo) - ref, err := repo.CreateReference("refs/heads/foo", commitId, true, nil, "") + ref, err := repo.CreateReference("refs/heads/foo", commitId, true, "") checkFatal(t, err) ref2, err := repo.DwimReference("foo") diff --git a/remote.go b/remote.go index 84750d3..72d3934 100644 --- a/remote.go +++ b/remote.go @@ -598,18 +598,9 @@ func (o *Remote) UpdateFetchHead() bool { // Fetch performs a fetch operation. refspecs specifies which refspecs // to use for this fetch, use an empty list to use the refspecs from -// the configuration; sig and msg specify what to use for the reflog -// entries. Leave nil and "" to use defaults. -func (o *Remote) Fetch(refspecs []string, sig *Signature, msg string) error { - - var csig *C.git_signature = nil - if sig != nil { - csig, err := sig.toC() - if err != nil { - return err - } - defer C.free(unsafe.Pointer(csig)) - } +// the configuration; msg specifies what to use for the reflog +// entries. Leave "" to use defaults. +func (o *Remote) Fetch(refspecs []string, msg string) error { var cmsg *C.char = nil if msg != "" { @@ -625,7 +616,7 @@ func (o *Remote) Fetch(refspecs []string, sig *Signature, msg string) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_fetch(o.ptr, &crefspecs, csig, cmsg) + ret := C.git_remote_fetch(o.ptr, &crefspecs, cmsg) if ret < 0 { return MakeGitError(ret) } @@ -696,24 +687,7 @@ 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, err := sig.toC() - if err != nil { - return err - } - 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)) - } - +func (o *Remote) Push(refspecs []string, opts *PushOptions) error { var copts C.git_push_options C.git_push_init_options(&copts, C.GIT_PUSH_OPTIONS_VERSION) if opts != nil { @@ -728,7 +702,7 @@ func (o *Remote) Push(refspecs []string, opts *PushOptions, sig *Signature, msg runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_push(o.ptr, &crefspecs, &copts, csig, cmsg) + ret := C.git_remote_push(o.ptr, &crefspecs, &copts) if ret < 0 { return MakeGitError(ret) } diff --git a/remote_test.go b/remote_test.go index 54a66ed..bbbdeb9 100644 --- a/remote_test.go +++ b/remote_test.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "testing" - "time" ) func TestRefspecs(t *testing.T) { @@ -73,7 +72,7 @@ func TestCertificateCheck(t *testing.T) { err = remote.SetCallbacks(&callbacks) checkFatal(t, err) - err = remote.Fetch([]string{}, nil, "") + err = remote.Fetch([]string{}, "") checkFatal(t, err) } @@ -168,13 +167,7 @@ func TestRemotePrune(t *testing.T) { checkFatal(t, err) defer commit.Free() - sig := &Signature{ - Name: "Rand Om Hacker", - Email: "random@hacker.com", - When: time.Now(), - } - - remoteRef, err := remoteRepo.CreateBranch("test-prune", commit, true, sig, "branch test-prune") + remoteRef, err := remoteRepo.CreateBranch("test-prune", commit, true) checkFatal(t, err) repo := createTestRepo(t) @@ -189,10 +182,10 @@ func TestRemotePrune(t *testing.T) { remote, err := repo.CreateRemote("origin", remoteUrl) checkFatal(t, err) - err = remote.Fetch([]string{"test-prune"}, sig, "") + err = remote.Fetch([]string{"test-prune"}, "") checkFatal(t, err) - _, err = repo.CreateReference("refs/remotes/origin/test-prune", head, true, sig, "remote reference") + _, err = repo.CreateReference("refs/remotes/origin/test-prune", head, true, "remote reference") checkFatal(t, err) err = remoteRef.Delete() diff --git a/repository.go b/repository.go index 7fac277..b65867b 100644 --- a/repository.go +++ b/repository.go @@ -206,65 +206,35 @@ func (v *Repository) Head() (*Reference, error) { return newReferenceFromC(ptr, v), nil } -func (v *Repository) SetHead(refname string, sig *Signature, msg string) error { +func (v *Repository) SetHead(refname string) error { cname := C.CString(refname) defer C.free(unsafe.Pointer(cname)) - csig, err := sig.toC() - if err != nil { - return err - } - defer C.free(unsafe.Pointer(csig)) - - var cmsg *C.char - if msg != "" { - cmsg = C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) - } - runtime.LockOSThread() defer runtime.UnlockOSThread() - ecode := C.git_repository_set_head(v.ptr, cname, csig, cmsg) + ecode := C.git_repository_set_head(v.ptr, cname) if ecode != 0 { return MakeGitError(ecode) } return nil } -func (v *Repository) SetHeadDetached(id *Oid, sig *Signature, msg string) error { - csig, err := sig.toC() - if err != nil { - return err - } - defer C.free(unsafe.Pointer(csig)) - - var cmsg *C.char - if msg != "" { - cmsg = C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) - } - +func (v *Repository) SetHeadDetached(id *Oid) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ecode := C.git_repository_set_head_detached(v.ptr, id.toC(), csig, cmsg) + ecode := C.git_repository_set_head_detached(v.ptr, id.toC()) if ecode != 0 { return MakeGitError(ecode) } return nil } -func (v *Repository) CreateReference(name string, id *Oid, force bool, sig *Signature, msg string) (*Reference, error) { +func (v *Repository) CreateReference(name string, id *Oid, force bool, msg string) (*Reference, error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) - csig, err := sig.toC() - if err != nil { - return nil, err - } - defer C.free(unsafe.Pointer(csig)) - var cmsg *C.char if msg == "" { cmsg = nil @@ -278,7 +248,7 @@ func (v *Repository) CreateReference(name string, id *Oid, force bool, sig *Sign runtime.LockOSThread() defer runtime.UnlockOSThread() - ecode := C.git_reference_create(&ptr, v.ptr, cname, id.toC(), cbool(force), csig, cmsg) + ecode := C.git_reference_create(&ptr, v.ptr, cname, id.toC(), cbool(force), cmsg) if ecode < 0 { return nil, MakeGitError(ecode) } @@ -286,19 +256,13 @@ func (v *Repository) CreateReference(name string, id *Oid, force bool, sig *Sign return newReferenceFromC(ptr, v), nil } -func (v *Repository) CreateSymbolicReference(name, target string, force bool, sig *Signature, msg string) (*Reference, error) { +func (v *Repository) CreateSymbolicReference(name, target string, force bool, msg string) (*Reference, error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) ctarget := C.CString(target) defer C.free(unsafe.Pointer(ctarget)) - csig, err := sig.toC() - if err != nil { - return nil, err - } - defer C.free(unsafe.Pointer(csig)) - var cmsg *C.char if msg == "" { cmsg = nil @@ -312,7 +276,7 @@ func (v *Repository) CreateSymbolicReference(name, target string, force bool, si runtime.LockOSThread() defer runtime.UnlockOSThread() - ecode := C.git_reference_symbolic_create(&ptr, v.ptr, cname, ctarget, cbool(force), csig, cmsg) + ecode := C.git_reference_symbolic_create(&ptr, v.ptr, cname, ctarget, cbool(force), cmsg) if ecode < 0 { return nil, MakeGitError(ecode) } diff --git a/revparse_test.go b/revparse_test.go index c046a20..8c3a352 100644 --- a/revparse_test.go +++ b/revparse_test.go @@ -35,7 +35,7 @@ func TestRevparseExt(t *testing.T) { _, treeId := seedTestRepo(t, repo) - ref, err := repo.CreateReference("refs/heads/master", treeId, true, nil, "") + ref, err := repo.CreateReference("refs/heads/master", treeId, true, "") checkFatal(t, err) obj, ref, err := repo.RevparseExt("master") diff --git a/submodule.go b/submodule.go index 7c6c922..fdd38cc 100644 --- a/submodule.go +++ b/submodule.go @@ -16,7 +16,6 @@ type SubmoduleUpdateOptions struct { *CheckoutOpts *RemoteCallbacks CloneCheckoutStrategy CheckoutStrategy - Signature *Signature } // Submodule @@ -345,11 +344,5 @@ func populateSubmoduleUpdateOptions(ptr *C.git_submodule_update_options, opts *S populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks) ptr.clone_checkout_strategy = C.uint(opts.CloneCheckoutStrategy) - sig, err := opts.Signature.toC() - if err != nil { - return err - } - ptr.signature = sig - return nil } diff --git a/vendor/libgit2 b/vendor/libgit2 index 04bdd97..d675982 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit 04bdd97f2b63793a8720fd19007911e946ba3c55 +Subproject commit d675982a15388d8c413acda139b4662062cf3286 From 89d67328f3682c49db296831538c8638fcfe53fe Mon Sep 17 00:00:00 2001 From: Dylan Griffin Date: Sat, 21 Mar 2015 18:37:00 -0400 Subject: [PATCH 03/36] Add support for libgit2's git_reset. Adds a new method to *Repository called ResetToCommit as well as constants for the three reset modes that libgit2 currently supports. This does not need to be limited to Commit, we actually just need something with a gitObject, which blobs and other Objects have, they will just require different methods. I only need to be able to reset to commits, so that's all I'm implementing right now. Also adds a test which updates the test repository README twice and then resets to the first commit. --- reset.go | 26 ++++++++++++++++++++++++++ reset_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 reset.go create mode 100644 reset_test.go diff --git a/reset.go b/reset.go new file mode 100644 index 0000000..b5b7435 --- /dev/null +++ b/reset.go @@ -0,0 +1,26 @@ +package git + +/* +#include +*/ +import "C" +import "runtime" + +type ResetType int + +const ( + ResetSoft ResetType = C.GIT_RESET_SOFT + ResetMixed ResetType = C.GIT_RESET_MIXED + ResetHard ResetType = C.GIT_RESET_HARD +) + +func (r *Repository) ResetToCommit(commit *Commit, resetType ResetType, opts *CheckoutOpts) error { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + ret := C.git_reset(r.ptr, commit.gitObject.ptr, C.git_reset_t(resetType), opts.toC()) + + if ret < 0 { + return MakeGitError(ret) + } + return nil +} diff --git a/reset_test.go b/reset_test.go new file mode 100644 index 0000000..ec578bd --- /dev/null +++ b/reset_test.go @@ -0,0 +1,45 @@ +package git + +import ( + "io/ioutil" + "testing" +) + +func TestResetToCommit(t *testing.T) { + repo := createTestRepo(t) + seedTestRepo(t, repo) + // create commit to reset to + commitId, _ := updateReadme(t, repo, "testing reset") + // create commit to reset from + nextCommitId, _ := updateReadme(t, repo, "will be reset") + + // confirm that we wrote "will be reset" to the readme + newBytes, err := ioutil.ReadFile(pathInRepo(repo, "README")) + checkFatal(t, err) + if string(newBytes) != "will be reset" { + t.Fatalf("expected %s to equal 'will be reset'", string(newBytes)) + } + + // confirm that the head of the repo is the next commit id + head, err := repo.Head() + checkFatal(t, err) + if head.Target().String() != nextCommitId.String() { + t.Fatalf( + "expected to be at latest commit %s, but was %s", + nextCommitId.String(), + head.Target().String(), + ) + } + + commitToResetTo, err := repo.LookupCommit(commitId) + checkFatal(t, err) + + repo.ResetToCommit(commitToResetTo, ResetHard, &CheckoutOpts{}) + + // check that the file now reads "testing reset" like it did before + bytes, err := ioutil.ReadFile(pathInRepo(repo, "README")) + checkFatal(t, err) + if string(bytes) != "testing reset" { + t.Fatalf("expected %s to equal 'testing reset'", string(bytes)) + } +} From acc05926f7d21547a8c9de0bb08d3b0b498a6769 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Thu, 16 Apr 2015 18:45:53 -0700 Subject: [PATCH 04/36] Avoid installing script binary to user's $GOPATH/bin. --- script/check-MakeGitError-thread-lock.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/check-MakeGitError-thread-lock.go b/script/check-MakeGitError-thread-lock.go index f6b01b3..77411f7 100644 --- a/script/check-MakeGitError-thread-lock.go +++ b/script/check-MakeGitError-thread-lock.go @@ -1,3 +1,5 @@ +// +build ignore + package main import ( From b374e16db84c451cddc49d064cae69ca00993982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 18 Apr 2015 04:50:39 +0200 Subject: [PATCH 05/36] Update to libgit2 4c02d3937 --- merge.go | 10 +++++----- repository.go | 9 ++++++--- vendor/libgit2 | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/merge.go b/merge.go index 5b68a8b..65dc03e 100644 --- a/merge.go +++ b/merge.go @@ -84,8 +84,8 @@ const ( ) type MergeOptions struct { - Version uint - Flags MergeTreeFlag + Version uint + TreeFlags MergeTreeFlag RenameThreshold uint TargetLimit uint @@ -97,7 +97,7 @@ type MergeOptions struct { func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions { return MergeOptions{ Version: uint(opts.version), - Flags: MergeTreeFlag(opts.flags), + TreeFlags: MergeTreeFlag(opts.tree_flags), RenameThreshold: uint(opts.rename_threshold), TargetLimit: uint(opts.target_limit), FileFavor: MergeFileFavor(opts.file_favor), @@ -123,7 +123,7 @@ func (mo *MergeOptions) toC() *C.git_merge_options { } return &C.git_merge_options{ version: C.uint(mo.Version), - flags: C.git_merge_tree_flag_t(mo.Flags), + tree_flags: C.git_merge_tree_flag_t(mo.TreeFlags), rename_threshold: C.uint(mo.RenameThreshold), target_limit: C.uint(mo.TargetLimit), file_favor: C.git_merge_file_favor_t(mo.FileFavor), @@ -333,7 +333,7 @@ func populateCMergeFileOptions(c *C.git_merge_file_options, options MergeFileOpt c.our_label = C.CString(options.OurLabel) c.their_label = C.CString(options.TheirLabel) c.favor = C.git_merge_file_favor_t(options.Favor) - c.flags = C.git_merge_file_flags_t(options.Flags) + c.flags = C.uint(options.Flags) } func freeCMergeFileOptions(c *C.git_merge_file_options) { diff --git a/repository.go b/repository.go index b65867b..9917c60 100644 --- a/repository.go +++ b/repository.go @@ -616,16 +616,19 @@ func (v *Repository) RemoveNote(ref string, author, committer *Signature, id *Oi // DefaultNoteRef returns the default notes reference for a repository func (v *Repository) DefaultNoteRef() (string, error) { - var ptr *C.char + buf := C.git_buf{} runtime.LockOSThread() defer runtime.UnlockOSThread() - if ret := C.git_note_default_ref(&ptr, v.ptr); ret < 0 { + if ret := C.git_note_default_ref(&buf, v.ptr); ret < 0 { return "", MakeGitError(ret) } - return C.GoString(ptr), nil + ret := C.GoString(buf.ptr) + C.git_buf_free(&buf) + + return ret, nil } type RepositoryState int diff --git a/vendor/libgit2 b/vendor/libgit2 index d675982..4c02d39 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit d675982a15388d8c413acda139b4662062cf3286 +Subproject commit 4c02d393748d0db382450871ad9ef6898a2ce360 From 36e0a256fe79f87447bb730fda53e5cbc90eb47c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 8 Jun 2015 04:11:21 +0200 Subject: [PATCH 06/36] Update to libgit2 b6011e29 --- clone.go | 4 +- git.go | 4 +- index.go | 42 ++++++----- odb.go | 8 ++- odb_test.go | 2 +- remote.go | 184 ++++++++++++++++++++++++------------------------- remote_test.go | 45 ++++-------- submodule.go | 4 +- vendor/libgit2 | 2 +- 9 files changed, 140 insertions(+), 155 deletions(-) diff --git a/clone.go b/clone.go index b796b6e..233300d 100644 --- a/clone.go +++ b/clone.go @@ -12,7 +12,7 @@ import ( type CloneOptions struct { *CheckoutOpts - *RemoteCallbacks + *FetchOptions Bare bool CheckoutBranch string RemoteCreateCallback C.git_remote_create_cb @@ -55,7 +55,7 @@ func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) { return } populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts) - populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks) + populateFetchOptions(&ptr.fetch_opts, opts.FetchOptions) ptr.bare = cbool(opts.Bare) if opts.RemoteCreateCallback != nil { diff --git a/git.go b/git.go index 5f69837..ae65cb7 100644 --- a/git.go +++ b/git.go @@ -76,8 +76,8 @@ const ( ErrNonFastForward ErrorCode = C.GIT_ENONFASTFORWARD // Name/ref spec was not in a valid format ErrInvalidSpec ErrorCode = C.GIT_EINVALIDSPEC - // Merge conflicts prevented operation - ErrMergeConflict ErrorCode = C.GIT_EMERGECONFLICT + // Checkout conflicts prevented operation + ErrConflict ErrorCode = C.GIT_ECONFLICT // Lock file prevented operation ErrLocked ErrorCode = C.GIT_ELOCKED // Reference value does not match expected diff --git a/index.go b/index.go index c1bfb74..0174dc1 100644 --- a/index.go +++ b/index.go @@ -12,7 +12,6 @@ import "C" import ( "fmt" "runtime" - "time" "unsafe" ) @@ -31,13 +30,18 @@ type Index struct { ptr *C.git_index } +type IndexTime struct { + seconds int32 + nanoseconds uint32 +} + type IndexEntry struct { - Ctime time.Time - Mtime time.Time + Ctime IndexTime + Mtime IndexTime Mode Filemode - Uid uint - Gid uint - Size uint + Uid uint32 + Gid uint32 + Size uint32 Id *Oid Path string } @@ -47,26 +51,26 @@ func newIndexEntryFromC(entry *C.git_index_entry) *IndexEntry { return nil } return &IndexEntry{ - time.Unix(int64(entry.ctime.seconds), int64(entry.ctime.nanoseconds)), - time.Unix(int64(entry.mtime.seconds), int64(entry.mtime.nanoseconds)), + IndexTime { int32(entry.ctime.seconds), uint32(entry.ctime.nanoseconds) }, + IndexTime { int32(entry.mtime.seconds), uint32(entry.mtime.nanoseconds) }, Filemode(entry.mode), - uint(entry.uid), - uint(entry.gid), - uint(entry.file_size), + uint32(entry.uid), + uint32(entry.gid), + uint32(entry.file_size), newOidFromC(&entry.id), C.GoString(entry.path), } } func populateCIndexEntry(source *IndexEntry, dest *C.git_index_entry) { - dest.ctime.seconds = C.git_time_t(source.Ctime.Unix()) - dest.ctime.nanoseconds = C.uint(source.Ctime.UnixNano()) - dest.mtime.seconds = C.git_time_t(source.Mtime.Unix()) - dest.mtime.nanoseconds = C.uint(source.Mtime.UnixNano()) - dest.mode = C.uint(source.Mode) - dest.uid = C.uint(source.Uid) - dest.gid = C.uint(source.Gid) - dest.file_size = C.git_off_t(source.Size) + dest.ctime.seconds = C.int32_t(source.Ctime.seconds) + dest.ctime.nanoseconds = C.uint32_t(source.Ctime.nanoseconds) + dest.mtime.seconds = C.int32_t(source.Mtime.seconds) + dest.mtime.nanoseconds = C.uint32_t(source.Mtime.nanoseconds) + dest.mode = C.uint32_t(source.Mode) + dest.uid = C.uint32_t(source.Uid) + dest.gid = C.uint32_t(source.Gid) + dest.file_size = C.uint32_t(source.Size) dest.id = *source.Id.toC() dest.path = C.CString(source.Path) } diff --git a/odb.go b/odb.go index 6b21329..be0870e 100644 --- a/odb.go +++ b/odb.go @@ -11,6 +11,7 @@ import ( "reflect" "runtime" "unsafe" + "fmt" ) type Odb struct { @@ -106,7 +107,9 @@ func odbForEachCb(id *C.git_oid, handle unsafe.Pointer) int { } err := data.callback(newOidFromC(id)) + fmt.Println("err %v", err) if err != nil { + fmt.Println("returning EUSER") data.err = err return C.GIT_EUSER } @@ -127,6 +130,7 @@ func (v *Odb) ForEach(callback OdbForEachCallback) error { defer pointerHandles.Untrack(handle) ret := C._go_git_odb_foreach(v.ptr, handle) + fmt.Println("ret %v", ret); if ret == C.GIT_EUSER { return data.err } else if ret < 0 { @@ -172,13 +176,13 @@ func (v *Odb) NewReadStream(id *Oid) (*OdbReadStream, error) { // NewWriteStream opens a write stream to the ODB, which allows you to // create a new object in the database. The size and type must be // known in advance -func (v *Odb) NewWriteStream(size int, otype ObjectType) (*OdbWriteStream, error) { +func (v *Odb) NewWriteStream(size int64, 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)) + ret := C.git_odb_open_wstream(&stream.ptr, v.ptr, C.git_off_t(size), C.git_otype(otype)) if ret < 0 { return nil, MakeGitError(ret) } diff --git a/odb_test.go b/odb_test.go index 2fb6840..0d765b9 100644 --- a/odb_test.go +++ b/odb_test.go @@ -17,7 +17,7 @@ func TestOdbStream(t *testing.T) { str := "hello, world!" - stream, error := odb.NewWriteStream(len(str), ObjectBlob) + stream, error := odb.NewWriteStream(int64(len(str)), ObjectBlob) checkFatal(t, error) n, error := io.WriteString(stream, str) checkFatal(t, error) diff --git a/remote.go b/remote.go index 72d3934..705bc0c 100644 --- a/remote.go +++ b/remote.go @@ -69,6 +69,51 @@ type RemoteCallbacks struct { PushUpdateReferenceCallback } +type FetchPrune uint + +const ( + // Use the setting from the configuration + FetchPruneFallback FetchPrune = C.GIT_FETCH_PRUNE_FALLBACK + // Force pruning on + FetchPruneOn FetchPrune = C.GIT_FETCH_PRUNE + // Force pruning off + FetchNoPrune FetchPrune = C.GIT_FETCH_NO_PRUNE +) + +type DownloadTags uint + +const ( + + // Use the setting from the configuration. + DownloadTagsFallback DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_FALLBACK + // Ask the server for tags pointing to objects we're already + // downloading. + DownloadTagsAuto DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_AUTO + + // Don't ask for any tags beyond the refspecs. + DownloadTagsNone DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_NONE + + // Ask for the all the tags. + DownloadTagsAll DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_ALL +) + +type FetchOptions struct { + // Callbacks to use for this fetch operation + RemoteCallbacks RemoteCallbacks + // Whether to perform a prune after the fetch + Prune FetchPrune + // Whether to write the results to FETCH_HEAD. Defaults to + // on. Leave this default in order to behave like git. + UpdateFetchhead bool + + // Determines how to behave regarding tags on the remote, such + // as auto-downloading tags for objects we're downloading or + // downloading all of them. + // + // The default is to auto-follow tags. + DownloadTags DownloadTags +} + type Remote struct { ptr *C.git_remote callbacks RemoteCallbacks @@ -267,23 +312,6 @@ func RemoteIsValidName(name string) bool { return false } -func (r *Remote) SetCallbacks(callbacks *RemoteCallbacks) error { - r.callbacks = *callbacks - - var ccallbacks C.git_remote_callbacks - populateRemoteCallbacks(&ccallbacks, &r.callbacks) - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ecode := C.git_remote_set_callbacks(r.ptr, &ccallbacks) - if ecode < 0 { - return MakeGitError(ecode) - } - - return nil -} - func (r *Remote) Free() { runtime.SetFinalizer(r, nil) C.git_remote_free(r.ptr) @@ -359,18 +387,16 @@ func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch return remote, nil } -func (repo *Repository) CreateAnonymousRemote(url, fetch string) (*Remote, error) { +func (repo *Repository) CreateAnonymousRemote(url string) (*Remote, error) { remote := &Remote{} curl := C.CString(url) defer C.free(unsafe.Pointer(curl)) - cfetch := C.CString(fetch) - defer C.free(unsafe.Pointer(cfetch)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_create_anonymous(&remote.ptr, repo.ptr, curl, cfetch) + ret := C.git_remote_create_anonymous(&remote.ptr, repo.ptr, curl) if ret < 0 { return nil, MakeGitError(ret) } @@ -395,18 +421,6 @@ func (repo *Repository) LookupRemote(name string) (*Remote, error) { return remote, nil } -func (o *Remote) Save() error { - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_remote_save(o.ptr) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - func (o *Remote) Owner() Repository { return Repository{C.git_remote_owner(o.ptr)} } @@ -423,42 +437,48 @@ func (o *Remote) PushUrl() string { return C.GoString(C.git_remote_pushurl(o.ptr)) } -func (o *Remote) SetUrl(url string) error { +func (o *Repository) RemoteSetUrl(remote, url string) error { curl := C.CString(url) defer C.free(unsafe.Pointer(curl)) + cremote := C.CString(remote) + defer C.free(unsafe.Pointer(cremote)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_set_url(o.ptr, curl) + ret := C.git_remote_set_url(o.ptr, cremote, curl) if ret < 0 { return MakeGitError(ret) } return nil } -func (o *Remote) SetPushUrl(url string) error { +func (o *Repository) RemoteSetPushUrl(remote, url string) error { curl := C.CString(url) defer C.free(unsafe.Pointer(curl)) + cremote := C.CString(remote) + defer C.free(unsafe.Pointer(cremote)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_set_pushurl(o.ptr, curl) + ret := C.git_remote_set_pushurl(o.ptr, cremote, curl) if ret < 0 { return MakeGitError(ret) } return nil } -func (o *Remote) AddFetch(refspec string) error { +func (o *Repository) RemoteAddFetch(remote, refspec string) error { crefspec := C.CString(refspec) defer C.free(unsafe.Pointer(crefspec)) + cremote := C.CString(remote) + defer C.free(unsafe.Pointer(cremote)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_add_fetch(o.ptr, crefspec) + ret := C.git_remote_add_fetch(o.ptr, cremote, crefspec) if ret < 0 { return MakeGitError(ret) } @@ -519,30 +539,16 @@ func (o *Remote) FetchRefspecs() ([]string, error) { return refspecs, nil } -func (o *Remote) SetFetchRefspecs(refspecs []string) error { - 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_set_fetch_refspecs(o.ptr, &crefspecs) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - -func (o *Remote) AddPush(refspec string) error { +func (o *Repository) RemoteAddPush(remote, refspec string) error { crefspec := C.CString(refspec) defer C.free(unsafe.Pointer(crefspec)) + cremote := C.CString(remote) + defer C.free(unsafe.Pointer(cremote)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_add_push(o.ptr, crefspec) + ret := C.git_remote_add_push(o.ptr, cremote, crefspec) if ret < 0 { return MakeGitError(ret) } @@ -564,43 +570,26 @@ func (o *Remote) PushRefspecs() ([]string, error) { return refspecs, nil } -func (o *Remote) SetPushRefspecs(refspecs []string) error { - 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_set_push_refspecs(o.ptr, &crefspecs) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - -func (o *Remote) ClearRefspecs() { - C.git_remote_clear_refspecs(o.ptr) -} - func (o *Remote) RefspecCount() uint { return uint(C.git_remote_refspec_count(o.ptr)) } -func (o *Remote) SetUpdateFetchHead(val bool) { - C.git_remote_set_update_fetchhead(o.ptr, cbool(val)) -} - -func (o *Remote) UpdateFetchHead() bool { - return C.git_remote_update_fetchhead(o.ptr) > 0 +func populateFetchOptions(options *C.git_fetch_options, opts *FetchOptions) { + C.git_fetch_init_options(options, C.GIT_FETCH_OPTIONS_VERSION) + if opts == nil { + return; + } + populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks) + options.prune = C.git_fetch_prune_t(opts.Prune) + options.update_fetchhead = cbool(opts.UpdateFetchhead) + options.download_tags = C.git_remote_autotag_option_t(opts.DownloadTags) } // Fetch performs a fetch operation. refspecs specifies which refspecs // to use for this fetch, use an empty list to use the refspecs from // the configuration; msg specifies what to use for the reflog // entries. Leave "" to use defaults. -func (o *Remote) Fetch(refspecs []string, msg string) error { +func (o *Remote) Fetch(refspecs []string, opts *FetchOptions, msg string) error { var cmsg *C.char = nil if msg != "" { @@ -613,29 +602,35 @@ func (o *Remote) Fetch(refspecs []string, msg string) error { crefspecs.strings = makeCStringsFromStrings(refspecs) defer freeStrarray(&crefspecs) + var coptions C.git_fetch_options + populateFetchOptions(&coptions, opts); + runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_fetch(o.ptr, &crefspecs, cmsg) + ret := C.git_remote_fetch(o.ptr, &crefspecs, &coptions, cmsg) if ret < 0 { return MakeGitError(ret) } return nil } -func (o *Remote) ConnectFetch() error { - return o.Connect(ConnectDirectionFetch) +func (o *Remote) ConnectFetch(callbacks *RemoteCallbacks) error { + return o.Connect(ConnectDirectionFetch, callbacks) } -func (o *Remote) ConnectPush() error { - return o.Connect(ConnectDirectionPush) +func (o *Remote) ConnectPush(callbacks *RemoteCallbacks) error { + return o.Connect(ConnectDirectionPush, callbacks) } -func (o *Remote) Connect(direction ConnectDirection) error { +func (o *Remote) Connect(direction ConnectDirection, callbacks *RemoteCallbacks) error { + var ccallbacks C.git_remote_callbacks; + populateRemoteCallbacks(&ccallbacks, callbacks) + runtime.LockOSThread() defer runtime.UnlockOSThread() - if ret := C.git_remote_connect(o.ptr, C.git_direction(direction)); ret != 0 { + if ret := C.git_remote_connect(o.ptr, C.git_direction(direction), &ccallbacks); ret != 0 { return MakeGitError(ret) } return nil @@ -713,11 +708,14 @@ func (o *Remote) PruneRefs() bool { return C.git_remote_prune_refs(o.ptr) > 0 } -func (o *Remote) Prune() error { +func (o *Remote) Prune(callbacks *RemoteCallbacks) error { + var ccallbacks C.git_remote_callbacks; + populateRemoteCallbacks(&ccallbacks, callbacks) + runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_prune(o.ptr) + ret := C.git_remote_prune(o.ptr, &ccallbacks) if ret < 0 { return MakeGitError(ret) } diff --git a/remote_test.go b/remote_test.go index 23c80f5..cbb52aa 100644 --- a/remote_test.go +++ b/remote_test.go @@ -5,27 +5,6 @@ import ( "testing" ) -func TestRefspecs(t *testing.T) { - repo := createTestRepo(t) - defer cleanupTestRepo(t, repo) - - remote, err := repo.CreateAnonymousRemote("git://foo/bar", "refs/heads/*:refs/heads/*") - checkFatal(t, err) - - expected := []string{ - "refs/heads/*:refs/remotes/origin/*", - "refs/pull/*/head:refs/remotes/origin/*", - } - - err = remote.SetFetchRefspecs(expected) - checkFatal(t, err) - - actual, err := remote.FetchRefspecs() - checkFatal(t, err) - - compareStringList(t, expected, actual) -} - func TestListRemotes(t *testing.T) { repo := createTestRepo(t) defer cleanupTestRepo(t, repo) @@ -60,15 +39,15 @@ func TestCertificateCheck(t *testing.T) { remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) - callbacks := RemoteCallbacks{ - CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) ErrorCode { - return assertHostname(cert, valid, hostname, t) + options := FetchOptions { + RemoteCallbacks: RemoteCallbacks{ + CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) ErrorCode { + return assertHostname(cert, valid, hostname, t) + }, }, } - err = remote.SetCallbacks(&callbacks) - checkFatal(t, err) - err = remote.Fetch([]string{}, "") + err = remote.Fetch([]string{}, &options, "") checkFatal(t, err) } @@ -79,7 +58,7 @@ func TestRemoteConnect(t *testing.T) { remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) - err = remote.ConnectFetch() + err = remote.ConnectFetch(nil) checkFatal(t, err) } @@ -90,7 +69,7 @@ func TestRemoteLs(t *testing.T) { remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) - err = remote.ConnectFetch() + err = remote.ConnectFetch(nil) checkFatal(t, err) heads, err := remote.Ls() @@ -108,7 +87,7 @@ func TestRemoteLsFiltering(t *testing.T) { remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) - err = remote.ConnectFetch() + err = remote.ConnectFetch(nil) checkFatal(t, err) heads, err := remote.Ls("master") @@ -172,7 +151,7 @@ func TestRemotePrune(t *testing.T) { remote, err := repo.CreateRemote("origin", remoteUrl) checkFatal(t, err) - err = remote.Fetch([]string{"test-prune"}, "") + err = remote.Fetch([]string{"test-prune"}, nil, "") checkFatal(t, err) _, err = repo.CreateReference("refs/remotes/origin/test-prune", head, true, "remote reference") @@ -187,10 +166,10 @@ func TestRemotePrune(t *testing.T) { rr, err := repo.LookupRemote("origin") checkFatal(t, err) - err = rr.ConnectFetch() + err = rr.ConnectFetch(nil) checkFatal(t, err) - err = rr.Prune() + err = rr.Prune(nil) checkFatal(t, err) _, err = repo.LookupReference("refs/remotes/origin/test-prune") diff --git a/submodule.go b/submodule.go index 6edc1d7..05cfd73 100644 --- a/submodule.go +++ b/submodule.go @@ -14,7 +14,7 @@ import ( // SubmoduleUpdateOptions type SubmoduleUpdateOptions struct { *CheckoutOpts - *RemoteCallbacks + *FetchOptions CloneCheckoutStrategy CheckoutStrategy } @@ -348,7 +348,7 @@ func populateSubmoduleUpdateOptions(ptr *C.git_submodule_update_options, opts *S } populateCheckoutOpts(&ptr.checkout_opts, opts.CheckoutOpts) - populateRemoteCallbacks(&ptr.remote_callbacks, opts.RemoteCallbacks) + populateFetchOptions(&ptr.fetch_opts, opts.FetchOptions) ptr.clone_checkout_strategy = C.uint(opts.CloneCheckoutStrategy) return nil diff --git a/vendor/libgit2 b/vendor/libgit2 index 4c02d39..b6011e2 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit 4c02d393748d0db382450871ad9ef6898a2ce360 +Subproject commit b6011e296e4db6942d71f5a8548d1308353927ee From d400f1d5b2154f00f676b6b4e3c510f37de47573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 00:34:54 +0200 Subject: [PATCH 07/36] Update to libgit2 fa39975 --- remote.go | 4 +-- submodule.go | 80 ++++++++++++++++++++++---------------------------- vendor/libgit2 | 2 +- wrapper.c | 2 +- 4 files changed, 39 insertions(+), 49 deletions(-) diff --git a/remote.go b/remote.go index 705bc0c..a52ff06 100644 --- a/remote.go +++ b/remote.go @@ -73,7 +73,7 @@ type FetchPrune uint const ( // Use the setting from the configuration - FetchPruneFallback FetchPrune = C.GIT_FETCH_PRUNE_FALLBACK + FetchPruneUnspecified FetchPrune = C.GIT_FETCH_PRUNE_UNSPECIFIED // Force pruning on FetchPruneOn FetchPrune = C.GIT_FETCH_PRUNE // Force pruning off @@ -85,7 +85,7 @@ type DownloadTags uint const ( // Use the setting from the configuration. - DownloadTagsFallback DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_FALLBACK + DownloadTagsUnspecified DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED // Ask the server for tags pointing to objects we're already // downloading. DownloadTagsAuto DownloadTags = C.GIT_REMOTE_DOWNLOAD_TAGS_AUTO diff --git a/submodule.go b/submodule.go index 05cfd73..fb7c5e6 100644 --- a/submodule.go +++ b/submodule.go @@ -26,7 +26,6 @@ type Submodule struct { 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 @@ -36,7 +35,6 @@ const ( type SubmoduleIgnore int const ( - SubmoduleIgnoreReset SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_RESET SubmoduleIgnoreNone SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_NONE SubmoduleIgnoreUntracked SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_UNTRACKED SubmoduleIgnoreDirty SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_DIRTY @@ -160,17 +158,6 @@ func (sub *Submodule) AddToIndex(write_index bool) error { return nil } -func (sub *Submodule) Save() error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_submodule_save(sub.ptr) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - func (sub *Submodule) Owner() *Repository { repo := C.git_submodule_owner(sub.ptr) //FIXME: how to handle dangling references ? @@ -192,14 +179,16 @@ func (sub *Submodule) Url() string { return C.GoString(n) } -func (sub *Submodule) SetUrl(url string) error { +func (o *Repository) SubmoduleSetUrl(submodule, url string) error { + csubmodule := C.CString(submodule) + defer C.free(unsafe.Pointer(csubmodule)) curl := C.CString(url) defer C.free(unsafe.Pointer(curl)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_set_url(sub.ptr, curl) + ret := C.git_submodule_set_url(o.ptr, csubmodule, curl) if ret < 0 { return MakeGitError(ret) } @@ -235,9 +224,19 @@ func (sub *Submodule) Ignore() SubmoduleIgnore { return SubmoduleIgnore(o) } -func (sub *Submodule) SetIgnore(ignore SubmoduleIgnore) SubmoduleIgnore { - o := C.git_submodule_set_ignore(sub.ptr, C.git_submodule_ignore_t(ignore)) - return SubmoduleIgnore(o) +func (o *Repository) SubmoduleSetIgnore(submodule string, ignore SubmoduleIgnore) error { + csubmodule := C.CString(submodule) + defer C.free(unsafe.Pointer(csubmodule)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_set_ignore(o.ptr, csubmodule, C.git_submodule_ignore_t(ignore)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil } func (sub *Submodule) UpdateStrategy() SubmoduleUpdate { @@ -245,20 +244,33 @@ func (sub *Submodule) UpdateStrategy() SubmoduleUpdate { return SubmoduleUpdate(o) } -func (sub *Submodule) SetUpdate(update SubmoduleUpdate) SubmoduleUpdate { - o := C.git_submodule_set_update(sub.ptr, C.git_submodule_update_t(update)) - return SubmoduleUpdate(o) +func (o *Repository) SubmoduleSetUpdate(submodule string, update SubmoduleUpdate) error { + csubmodule := C.CString(submodule) + defer C.free(unsafe.Pointer(csubmodule)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_submodule_set_update(o.ptr, csubmodule, C.git_submodule_update_t(update)) + if ret < 0 { + return MakeGitError(ret) + } + + return nil } func (sub *Submodule) FetchRecurseSubmodules() SubmoduleRecurse { return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr)) } -func (sub *Submodule) SetFetchRecurseSubmodules(recurse SubmoduleRecurse) error { +func (o *Repository) SubmoduleSetFetchRecurseSubmodules(submodule string, recurse SubmoduleRecurse) error { + csubmodule := C.CString(submodule) + defer C.free(unsafe.Pointer(csubmodule)) + runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_set_fetch_recurse_submodules(sub.ptr, C.git_submodule_recurse_t(recurse)) + ret := C.git_submodule_set_fetch_recurse_submodules(o.ptr, csubmodule, C.git_submodule_recurse_t(recurse)) if ret < 0 { return MakeGitError(C.int(ret)) } @@ -300,28 +312,6 @@ func (sub *Submodule) Open() (*Repository, error) { return repo, nil } -func (sub *Submodule) Reload(force bool) error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_submodule_reload(sub.ptr, cbool(force)) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - -func (repo *Repository) ReloadAllSubmodules(force bool) error { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_submodule_reload_all(repo.ptr, cbool(force)) - if ret < 0 { - return MakeGitError(ret) - } - return nil -} - func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error { var copts C.git_submodule_update_options err := populateSubmoduleUpdateOptions(&copts, opts) diff --git a/vendor/libgit2 b/vendor/libgit2 index b6011e2..fa39975 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit b6011e296e4db6942d71f5a8548d1308353927ee +Subproject commit fa399750c680aa254784a40193d73d373df5e3ea diff --git a/wrapper.c b/wrapper.c index 938fd17..4f2de60 100644 --- a/wrapper.c +++ b/wrapper.c @@ -59,7 +59,7 @@ int _go_git_diff_foreach(git_diff *diff, int eachFile, int eachHunk, int eachLin lcb = (git_diff_line_cb)&diffForEachLineCb; } - return git_diff_foreach(diff, fcb, hcb, lcb, payload); + return git_diff_foreach(diff, fcb, NULL, hcb, lcb, payload); } void _go_git_setup_diff_notify_callbacks(git_diff_options *opts) { From ba0a24087a8cd1a354872c95f3efe0224ea84b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 00:49:56 +0200 Subject: [PATCH 08/36] Get rid of Owner() on Remote and Submdoule These are inherently unsafe. The underlying pointer might get released at any moment. --- remote.go | 4 ---- submodule.go | 6 ------ 2 files changed, 10 deletions(-) diff --git a/remote.go b/remote.go index a52ff06..4ff2040 100644 --- a/remote.go +++ b/remote.go @@ -421,10 +421,6 @@ func (repo *Repository) LookupRemote(name string) (*Remote, error) { return remote, nil } -func (o *Remote) Owner() Repository { - return Repository{C.git_remote_owner(o.ptr)} -} - func (o *Remote) Name() string { return C.GoString(C.git_remote_name(o.ptr)) } diff --git a/submodule.go b/submodule.go index fb7c5e6..971011b 100644 --- a/submodule.go +++ b/submodule.go @@ -158,12 +158,6 @@ func (sub *Submodule) AddToIndex(write_index bool) error { return nil } -func (sub *Submodule) Owner() *Repository { - repo := C.git_submodule_owner(sub.ptr) - //FIXME: how to handle dangling references ? - return &Repository{repo} -} - func (sub *Submodule) Name() string { n := C.git_submodule_name(sub.ptr) return C.GoString(n) From 4b9cbd78fd266767f6bdf55257c4ee2b1611bbe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 00:51:17 +0200 Subject: [PATCH 09/36] Create a RemoteCollection for managing remotes Instead of making the 'Remote' part of the function calls, create a collection object which serves to namespace the operations for the remotes. --- push_test.go | 2 +- remote.go | 44 ++++++++++++++++++++++++-------------------- remote_test.go | 20 ++++++++++---------- repository.go | 19 ++++++++++++++----- 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/push_test.go b/push_test.go index 4686c65..a9fc90d 100644 --- a/push_test.go +++ b/push_test.go @@ -11,7 +11,7 @@ func TestRemotePush(t *testing.T) { localRepo := createTestRepo(t) defer cleanupTestRepo(t, localRepo) - remote, err := localRepo.CreateRemote("test_push", repo.Path()) + remote, err := localRepo.Remotes.Create("test_push", repo.Path()) checkFatal(t, err) seedTestRepo(t, localRepo) diff --git a/remote.go b/remote.go index 4ff2040..5bed309 100644 --- a/remote.go +++ b/remote.go @@ -317,13 +317,17 @@ func (r *Remote) Free() { C.git_remote_free(r.ptr) } -func (repo *Repository) ListRemotes() ([]string, error) { +type RemoteCollection struct { + repo *Repository +} + +func (c *RemoteCollection) List() ([]string, error) { var r C.git_strarray runtime.LockOSThread() defer runtime.UnlockOSThread() - ecode := C.git_remote_list(&r, repo.ptr) + ecode := C.git_remote_list(&r, c.repo.ptr) if ecode < 0 { return nil, MakeGitError(ecode) } @@ -333,7 +337,7 @@ func (repo *Repository) ListRemotes() ([]string, error) { return remotes, nil } -func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) { +func (c *RemoteCollection) Create(name string, url string) (*Remote, error) { remote := &Remote{} cname := C.CString(name) @@ -344,7 +348,7 @@ func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_create(&remote.ptr, repo.ptr, cname, curl) + ret := C.git_remote_create(&remote.ptr, c.repo.ptr, cname, curl) if ret < 0 { return nil, MakeGitError(ret) } @@ -352,21 +356,21 @@ func (repo *Repository) CreateRemote(name string, url string) (*Remote, error) { return remote, nil } -func (repo *Repository) DeleteRemote(name string) error { +func (c *RemoteCollection) Delete(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) + ret := C.git_remote_delete(c.repo.ptr, cname) if ret < 0 { return MakeGitError(ret) } return nil } -func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch string) (*Remote, error) { +func (c *RemoteCollection) CreateWithFetchspec(name string, url string, fetch string) (*Remote, error) { remote := &Remote{} cname := C.CString(name) @@ -379,7 +383,7 @@ func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_create_with_fetchspec(&remote.ptr, repo.ptr, cname, curl, cfetch) + ret := C.git_remote_create_with_fetchspec(&remote.ptr, c.repo.ptr, cname, curl, cfetch) if ret < 0 { return nil, MakeGitError(ret) } @@ -387,7 +391,7 @@ func (repo *Repository) CreateRemoteWithFetchspec(name string, url string, fetch return remote, nil } -func (repo *Repository) CreateAnonymousRemote(url string) (*Remote, error) { +func (c *RemoteCollection) CreateAnonymous(url string) (*Remote, error) { remote := &Remote{} curl := C.CString(url) @@ -396,7 +400,7 @@ func (repo *Repository) CreateAnonymousRemote(url string) (*Remote, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_create_anonymous(&remote.ptr, repo.ptr, curl) + ret := C.git_remote_create_anonymous(&remote.ptr, c.repo.ptr, curl) if ret < 0 { return nil, MakeGitError(ret) } @@ -404,7 +408,7 @@ func (repo *Repository) CreateAnonymousRemote(url string) (*Remote, error) { return remote, nil } -func (repo *Repository) LookupRemote(name string) (*Remote, error) { +func (c *RemoteCollection) Lookup(name string) (*Remote, error) { remote := &Remote{} cname := C.CString(name) @@ -413,7 +417,7 @@ func (repo *Repository) LookupRemote(name string) (*Remote, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_lookup(&remote.ptr, repo.ptr, cname) + ret := C.git_remote_lookup(&remote.ptr, c.repo.ptr, cname) if ret < 0 { return nil, MakeGitError(ret) } @@ -433,7 +437,7 @@ func (o *Remote) PushUrl() string { return C.GoString(C.git_remote_pushurl(o.ptr)) } -func (o *Repository) RemoteSetUrl(remote, url string) error { +func (c *RemoteCollection) SetUrl(remote, url string) error { curl := C.CString(url) defer C.free(unsafe.Pointer(curl)) cremote := C.CString(remote) @@ -442,14 +446,14 @@ func (o *Repository) RemoteSetUrl(remote, url string) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_set_url(o.ptr, cremote, curl) + ret := C.git_remote_set_url(c.repo.ptr, cremote, curl) if ret < 0 { return MakeGitError(ret) } return nil } -func (o *Repository) RemoteSetPushUrl(remote, url string) error { +func (c *RemoteCollection) SetPushUrl(remote, url string) error { curl := C.CString(url) defer C.free(unsafe.Pointer(curl)) cremote := C.CString(remote) @@ -458,14 +462,14 @@ func (o *Repository) RemoteSetPushUrl(remote, url string) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_set_pushurl(o.ptr, cremote, curl) + ret := C.git_remote_set_pushurl(c.repo.ptr, cremote, curl) if ret < 0 { return MakeGitError(ret) } return nil } -func (o *Repository) RemoteAddFetch(remote, refspec string) error { +func (c *RemoteCollection) AddFetch(remote, refspec string) error { crefspec := C.CString(refspec) defer C.free(unsafe.Pointer(crefspec)) cremote := C.CString(remote) @@ -474,7 +478,7 @@ func (o *Repository) RemoteAddFetch(remote, refspec string) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_add_fetch(o.ptr, cremote, crefspec) + ret := C.git_remote_add_fetch(c.repo.ptr, cremote, crefspec) if ret < 0 { return MakeGitError(ret) } @@ -535,7 +539,7 @@ func (o *Remote) FetchRefspecs() ([]string, error) { return refspecs, nil } -func (o *Repository) RemoteAddPush(remote, refspec string) error { +func (c *RemoteCollection) AddPush(remote, refspec string) error { crefspec := C.CString(refspec) defer C.free(unsafe.Pointer(crefspec)) cremote := C.CString(remote) @@ -544,7 +548,7 @@ func (o *Repository) RemoteAddPush(remote, refspec string) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_remote_add_push(o.ptr, cremote, crefspec) + ret := C.git_remote_add_push(c.repo.ptr, cremote, crefspec) if ret < 0 { return MakeGitError(ret) } diff --git a/remote_test.go b/remote_test.go index cbb52aa..4e64489 100644 --- a/remote_test.go +++ b/remote_test.go @@ -9,7 +9,7 @@ func TestListRemotes(t *testing.T) { repo := createTestRepo(t) defer cleanupTestRepo(t, repo) - _, err := repo.CreateRemote("test", "git://foo/bar") + _, err := repo.Remotes.Create("test", "git://foo/bar") checkFatal(t, err) @@ -17,7 +17,7 @@ func TestListRemotes(t *testing.T) { "test", } - actual, err := repo.ListRemotes() + actual, err := repo.Remotes.List() checkFatal(t, err) compareStringList(t, expected, actual) @@ -36,7 +36,7 @@ func TestCertificateCheck(t *testing.T) { repo := createTestRepo(t) defer cleanupTestRepo(t, repo) - remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") + remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) options := FetchOptions { @@ -55,7 +55,7 @@ func TestRemoteConnect(t *testing.T) { repo := createTestRepo(t) defer cleanupTestRepo(t, repo) - remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") + remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) err = remote.ConnectFetch(nil) @@ -66,7 +66,7 @@ func TestRemoteLs(t *testing.T) { repo := createTestRepo(t) defer cleanupTestRepo(t, repo) - remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") + remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) err = remote.ConnectFetch(nil) @@ -84,7 +84,7 @@ func TestRemoteLsFiltering(t *testing.T) { repo := createTestRepo(t) defer cleanupTestRepo(t, repo) - remote, err := repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") + remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) err = remote.ConnectFetch(nil) @@ -117,10 +117,10 @@ func TestRemotePruneRefs(t *testing.T) { err = config.SetBool("remote.origin.prune", true) checkFatal(t, err) - _, err = repo.CreateRemote("origin", "https://github.com/libgit2/TestGitRepository") + _, err = repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository") checkFatal(t, err) - remote, err := repo.LookupRemote("origin") + remote, err := repo.Remotes.Lookup("origin") checkFatal(t, err) if !remote.PruneRefs() { @@ -148,7 +148,7 @@ func TestRemotePrune(t *testing.T) { defer config.Free() remoteUrl := fmt.Sprintf("file://%s", remoteRepo.Workdir()) - remote, err := repo.CreateRemote("origin", remoteUrl) + remote, err := repo.Remotes.Create("origin", remoteUrl) checkFatal(t, err) err = remote.Fetch([]string{"test-prune"}, nil, "") @@ -163,7 +163,7 @@ func TestRemotePrune(t *testing.T) { err = config.SetBool("remote.origin.prune", true) checkFatal(t, err) - rr, err := repo.LookupRemote("origin") + rr, err := repo.Remotes.Lookup("origin") checkFatal(t, err) err = rr.ConnectFetch(nil) diff --git a/repository.go b/repository.go index 9917c60..860050f 100644 --- a/repository.go +++ b/repository.go @@ -12,7 +12,16 @@ import ( // Repository type Repository struct { - ptr *C.git_repository + ptr *C.git_repository + // Remotes represents the collection of remotes and can be + // used to add, remove and configure remotes for this + // repository. + Remotes RemoteCollection +} + +func initRepositoryObject(repo *Repository) { + repo.Remotes.repo = repo + runtime.SetFinalizer(repo, (*Repository).Free) } func OpenRepository(path string) (*Repository, error) { @@ -29,7 +38,7 @@ func OpenRepository(path string) (*Repository, error) { return nil, MakeGitError(ret) } - runtime.SetFinalizer(repo, (*Repository).Free) + initRepositoryObject(repo) return repo, nil } @@ -47,7 +56,7 @@ func OpenRepositoryExtended(path string) (*Repository, error) { return nil, MakeGitError(ret) } - runtime.SetFinalizer(repo, (*Repository).Free) + initRepositoryObject(repo) return repo, nil } @@ -65,7 +74,7 @@ func InitRepository(path string, isbare bool) (*Repository, error) { return nil, MakeGitError(ret) } - runtime.SetFinalizer(repo, (*Repository).Free) + initRepositoryObject(repo) return repo, nil } @@ -80,7 +89,7 @@ func NewRepositoryWrapOdb(odb *Odb) (repo *Repository, err error) { return nil, MakeGitError(ret) } - runtime.SetFinalizer(repo, (*Repository).Free) + initRepositoryObject(repo) return repo, nil } From d2808d16101e113096b3a1c02e3f91122de74f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 00:58:31 +0200 Subject: [PATCH 10/36] Create a SubmoduleCollection for managing submodules Similarly to RemoteCollection, this allows us to namespace the submodule operations much more concisely and removes API on the Repository. --- repository.go | 7 ++++++- submodule.go | 32 ++++++++++++++++++-------------- submodule_test.go | 4 ++-- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/repository.go b/repository.go index 860050f..0427d85 100644 --- a/repository.go +++ b/repository.go @@ -17,10 +17,15 @@ type Repository struct { // used to add, remove and configure remotes for this // repository. Remotes RemoteCollection + // Submodules represents the collectin of submodules and can + // be used to add, remove and configure submodules in this + // repostiory. + Submodules SubmoduleCollection } func initRepositoryObject(repo *Repository) { - repo.Remotes.repo = repo + repo.Remotes.repo = repo + repo.Submodules.repo = repo runtime.SetFinalizer(repo, (*Repository).Free) } diff --git a/submodule.go b/submodule.go index 971011b..6d4dc28 100644 --- a/submodule.go +++ b/submodule.go @@ -68,13 +68,17 @@ const ( SubmoduleRecurseOndemand SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_ONDEMAND ) +type SubmoduleCollection struct { + repo *Repository +} + func SubmoduleStatusIsUnmodified(status int) bool { o := SubmoduleStatus(status) & ^(SubmoduleStatusInHead | SubmoduleStatusInIndex | SubmoduleStatusInConfig | SubmoduleStatusInWd) return o == 0 } -func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) { +func (c *SubmoduleCollection) Lookup(name string) (*Submodule, error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) @@ -83,7 +87,7 @@ func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_lookup(&sub.ptr, repo.ptr, cname) + ret := C.git_submodule_lookup(&sub.ptr, c.repo.ptr, cname) if ret < 0 { return nil, MakeGitError(ret) } @@ -104,21 +108,21 @@ func SubmoduleVisitor(csub unsafe.Pointer, name *C.char, handle unsafe.Pointer) } } -func (repo *Repository) ForeachSubmodule(cbk SubmoduleCbk) error { +func (c *SubmoduleCollection) Foreach(cbk SubmoduleCbk) error { runtime.LockOSThread() defer runtime.UnlockOSThread() handle := pointerHandles.Track(cbk) defer pointerHandles.Untrack(handle) - ret := C._go_git_visit_submodule(repo.ptr, handle) + ret := C._go_git_visit_submodule(c.repo.ptr, handle) if ret < 0 { return MakeGitError(ret) } return nil } -func (repo *Repository) AddSubmodule(url, path string, use_git_link bool) (*Submodule, error) { +func (c *SubmoduleCollection) Add(url, path string, use_git_link bool) (*Submodule, error) { curl := C.CString(url) defer C.free(unsafe.Pointer(curl)) cpath := C.CString(path) @@ -129,7 +133,7 @@ func (repo *Repository) AddSubmodule(url, path string, use_git_link bool) (*Subm runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_add_setup(&sub.ptr, repo.ptr, curl, cpath, cbool(use_git_link)) + ret := C.git_submodule_add_setup(&sub.ptr, c.repo.ptr, curl, cpath, cbool(use_git_link)) if ret < 0 { return nil, MakeGitError(ret) } @@ -173,7 +177,7 @@ func (sub *Submodule) Url() string { return C.GoString(n) } -func (o *Repository) SubmoduleSetUrl(submodule, url string) error { +func (c *SubmoduleCollection) SetUrl(submodule, url string) error { csubmodule := C.CString(submodule) defer C.free(unsafe.Pointer(csubmodule)) curl := C.CString(url) @@ -182,7 +186,7 @@ func (o *Repository) SubmoduleSetUrl(submodule, url string) error { runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_set_url(o.ptr, csubmodule, curl) + ret := C.git_submodule_set_url(c.repo.ptr, csubmodule, curl) if ret < 0 { return MakeGitError(ret) } @@ -218,14 +222,14 @@ func (sub *Submodule) Ignore() SubmoduleIgnore { return SubmoduleIgnore(o) } -func (o *Repository) SubmoduleSetIgnore(submodule string, ignore SubmoduleIgnore) error { +func (c *SubmoduleCollection) SetIgnore(submodule string, ignore SubmoduleIgnore) error { csubmodule := C.CString(submodule) defer C.free(unsafe.Pointer(csubmodule)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_set_ignore(o.ptr, csubmodule, C.git_submodule_ignore_t(ignore)) + ret := C.git_submodule_set_ignore(c.repo.ptr, csubmodule, C.git_submodule_ignore_t(ignore)) if ret < 0 { return MakeGitError(ret) } @@ -238,14 +242,14 @@ func (sub *Submodule) UpdateStrategy() SubmoduleUpdate { return SubmoduleUpdate(o) } -func (o *Repository) SubmoduleSetUpdate(submodule string, update SubmoduleUpdate) error { +func (c *SubmoduleCollection) SetUpdate(submodule string, update SubmoduleUpdate) error { csubmodule := C.CString(submodule) defer C.free(unsafe.Pointer(csubmodule)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_set_update(o.ptr, csubmodule, C.git_submodule_update_t(update)) + ret := C.git_submodule_set_update(c.repo.ptr, csubmodule, C.git_submodule_update_t(update)) if ret < 0 { return MakeGitError(ret) } @@ -257,14 +261,14 @@ func (sub *Submodule) FetchRecurseSubmodules() SubmoduleRecurse { return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr)) } -func (o *Repository) SubmoduleSetFetchRecurseSubmodules(submodule string, recurse SubmoduleRecurse) error { +func (c *SubmoduleCollection) SetFetchRecurseSubmodules(submodule string, recurse SubmoduleRecurse) error { csubmodule := C.CString(submodule) defer C.free(unsafe.Pointer(csubmodule)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_set_fetch_recurse_submodules(o.ptr, csubmodule, C.git_submodule_recurse_t(recurse)) + ret := C.git_submodule_set_fetch_recurse_submodules(c.repo.ptr, csubmodule, C.git_submodule_recurse_t(recurse)) if ret < 0 { return MakeGitError(C.int(ret)) } diff --git a/submodule_test.go b/submodule_test.go index 27bc193..43c890a 100644 --- a/submodule_test.go +++ b/submodule_test.go @@ -10,11 +10,11 @@ func TestSubmoduleForeach(t *testing.T) { seedTestRepo(t, repo) - _, err := repo.AddSubmodule("http://example.org/submodule", "submodule", true) + _, err := repo.Submodules.Add("http://example.org/submodule", "submodule", true) checkFatal(t, err) i := 0 - err = repo.ForeachSubmodule(func(sub *Submodule, name string) int { + err = repo.Submodules.Foreach(func(sub *Submodule, name string) int { i++ return 0 }) From 01a2d8d38d4683c6e29da21240ffff7ae7b3a680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 01:12:32 +0200 Subject: [PATCH 11/36] Create a ReferenceCollection for managing references As with the other commits, this clears up the clutter in naming and around the Repository's API. --- merge_test.go | 4 +- push_test.go | 4 +- reference.go | 124 +++++++++++++++++++++++++++++++++++++++++++++ reference_test.go | 22 ++++---- remote_test.go | 4 +- repository.go | 126 ++-------------------------------------------- revparse_test.go | 2 +- 7 files changed, 147 insertions(+), 139 deletions(-) diff --git a/merge_test.go b/merge_test.go index 0b1faca..5c62f5c 100644 --- a/merge_test.go +++ b/merge_test.go @@ -10,7 +10,7 @@ func TestMergeWithSelf(t *testing.T) { seedTestRepo(t, repo) - master, err := repo.LookupReference("refs/heads/master") + master, err := repo.References.Lookup("refs/heads/master") checkFatal(t, err) mergeHead, err := repo.AnnotatedCommitFromRef(master) @@ -28,7 +28,7 @@ func TestMergeAnalysisWithSelf(t *testing.T) { seedTestRepo(t, repo) - master, err := repo.LookupReference("refs/heads/master") + master, err := repo.References.Lookup("refs/heads/master") checkFatal(t, err) mergeHead, err := repo.AnnotatedCommitFromRef(master) diff --git a/push_test.go b/push_test.go index a9fc90d..8f6e806 100644 --- a/push_test.go +++ b/push_test.go @@ -19,9 +19,9 @@ func TestRemotePush(t *testing.T) { err = remote.Push([]string{"refs/heads/master"}, nil) checkFatal(t, err) - _, err = localRepo.LookupReference("refs/remotes/test_push/master") + _, err = localRepo.References.Lookup("refs/remotes/test_push/master") checkFatal(t, err) - _, err = repo.LookupReference("refs/heads/master") + _, err = repo.References.Lookup("refs/heads/master") checkFatal(t, err) } diff --git a/reference.go b/reference.go index 61e2b26..d24e054 100644 --- a/reference.go +++ b/reference.go @@ -21,6 +21,130 @@ type Reference struct { repo *Repository } +type ReferenceCollection struct { + repo *Repository +} + +func (c *ReferenceCollection) Lookup(name string) (*Reference, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_reference_lookup(&ptr, c.repo.ptr, cname) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newReferenceFromC(ptr, c.repo), nil +} + +func (c *ReferenceCollection) Create(name string, id *Oid, force bool, msg string) (*Reference, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_reference_create(&ptr, c.repo.ptr, cname, id.toC(), cbool(force), cmsg) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newReferenceFromC(ptr, c.repo), nil +} + +func (c *ReferenceCollection) CreateSymbolic(name, target string, force bool, msg string) (*Reference, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + ctarget := C.CString(target) + defer C.free(unsafe.Pointer(ctarget)) + + var cmsg *C.char + if msg == "" { + cmsg = nil + } else { + cmsg = C.CString(msg) + defer C.free(unsafe.Pointer(cmsg)) + } + + var ptr *C.git_reference + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_reference_symbolic_create(&ptr, c.repo.ptr, cname, ctarget, cbool(force), cmsg) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newReferenceFromC(ptr, c.repo), nil +} + +// EnsureLog ensures that there is a reflog for the given reference +// name and creates an empty one if necessary. +func (c *ReferenceCollection) EnsureLog(name string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_ensure_log(c.repo.ptr, cname) + if ret < 0 { + return MakeGitError(ret) + } + + return nil +} + +// HasLog returns whether there is a reflog for the given reference +// name +func (c *ReferenceCollection) HasLog(name string) (bool, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_reference_has_log(c.repo.ptr, cname) + if ret < 0 { + return false, MakeGitError(ret) + } + + return ret == 1, nil +} + +// Dwim looks up a reference by DWIMing its short name +func (c *ReferenceCollection) Dwim(name string) (*Reference, error) { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + var ptr *C.git_reference + ret := C.git_reference_dwim(&ptr, c.repo.ptr, cname) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return newReferenceFromC(ptr, c.repo), nil +} + func newReferenceFromC(ptr *C.git_reference, repo *Repository) *Reference { ref := &Reference{ptr: ptr, repo: repo} runtime.SetFinalizer(ref, (*Reference).Free) diff --git a/reference_test.go b/reference_test.go index e891e7a..f1546e2 100644 --- a/reference_test.go +++ b/reference_test.go @@ -13,14 +13,14 @@ func TestRefModification(t *testing.T) { commitId, treeId := seedTestRepo(t, repo) - _, err := repo.CreateReference("refs/tags/tree", treeId, true, "testTreeTag") + _, err := repo.References.Create("refs/tags/tree", treeId, true, "testTreeTag") checkFatal(t, err) - tag, err := repo.LookupReference("refs/tags/tree") + tag, err := repo.References.Lookup("refs/tags/tree") checkFatal(t, err) checkRefType(t, tag, ReferenceOid) - ref, err := repo.LookupReference("HEAD") + ref, err := repo.References.Lookup("HEAD") checkFatal(t, err) checkRefType(t, ref, ReferenceSymbolic) @@ -46,7 +46,7 @@ func TestRefModification(t *testing.T) { _, err = tag.Rename("refs/tags/renamed", false, "") checkFatal(t, err) - tag, err = repo.LookupReference("refs/tags/renamed") + tag, err = repo.References.Lookup("refs/tags/renamed") checkFatal(t, err) checkRefType(t, ref, ReferenceOid) @@ -77,13 +77,13 @@ func TestReferenceIterator(t *testing.T) { commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree) checkFatal(t, err) - _, err = repo.CreateReference("refs/heads/one", commitId, true, "headOne") + _, err = repo.References.Create("refs/heads/one", commitId, true, "headOne") checkFatal(t, err) - _, err = repo.CreateReference("refs/heads/two", commitId, true, "headTwo") + _, err = repo.References.Create("refs/heads/two", commitId, true, "headTwo") checkFatal(t, err) - _, err = repo.CreateReference("refs/heads/three", commitId, true, "headThree") + _, err = repo.References.Create("refs/heads/three", commitId, true, "headThree") checkFatal(t, err) iter, err := repo.NewReferenceIterator() @@ -136,7 +136,7 @@ func TestReferenceOwner(t *testing.T) { commitId, _ := seedTestRepo(t, repo) - ref, err := repo.CreateReference("refs/heads/foo", commitId, true, "") + ref, err := repo.References.Create("refs/heads/foo", commitId, true, "") checkFatal(t, err) owner := ref.Owner() @@ -155,10 +155,10 @@ func TestUtil(t *testing.T) { commitId, _ := seedTestRepo(t, repo) - ref, err := repo.CreateReference("refs/heads/foo", commitId, true, "") + ref, err := repo.References.Create("refs/heads/foo", commitId, true, "") checkFatal(t, err) - ref2, err := repo.DwimReference("foo") + ref2, err := repo.References.Dwim("foo") checkFatal(t, err) if ref.Cmp(ref2) != 0 { @@ -169,7 +169,7 @@ func TestUtil(t *testing.T) { t.Fatalf("refs/heads/foo has no foo shorthand") } - hasLog, err := repo.HasLog("refs/heads/foo") + hasLog, err := repo.References.HasLog("refs/heads/foo") checkFatal(t, err) if !hasLog { t.Fatalf("branches have logs by default") diff --git a/remote_test.go b/remote_test.go index 4e64489..73c637f 100644 --- a/remote_test.go +++ b/remote_test.go @@ -154,7 +154,7 @@ func TestRemotePrune(t *testing.T) { err = remote.Fetch([]string{"test-prune"}, nil, "") checkFatal(t, err) - _, err = repo.CreateReference("refs/remotes/origin/test-prune", head, true, "remote reference") + _, err = repo.References.Create("refs/remotes/origin/test-prune", head, true, "remote reference") checkFatal(t, err) err = remoteRef.Delete() @@ -172,7 +172,7 @@ func TestRemotePrune(t *testing.T) { err = rr.Prune(nil) checkFatal(t, err) - _, err = repo.LookupReference("refs/remotes/origin/test-prune") + _, err = repo.References.Lookup("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 0427d85..8aa39ef 100644 --- a/repository.go +++ b/repository.go @@ -17,15 +17,19 @@ type Repository struct { // used to add, remove and configure remotes for this // repository. Remotes RemoteCollection - // Submodules represents the collectin of submodules and can + // Submodules represents the collection of submodules and can // be used to add, remove and configure submodules in this // repostiory. Submodules SubmoduleCollection + // References represents the collection of references and can + // be used to create, remove or update refernces for this repository. + References ReferenceCollection } func initRepositoryObject(repo *Repository) { repo.Remotes.repo = repo repo.Submodules.repo = repo + repo.References.repo = repo runtime.SetFinalizer(repo, (*Repository).Free) } @@ -190,22 +194,6 @@ func (v *Repository) LookupTag(id *Oid) (*Tag, error) { return obj.(*Tag), nil } -func (v *Repository) LookupReference(name string) (*Reference, error) { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - var ptr *C.git_reference - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ecode := C.git_reference_lookup(&ptr, v.ptr, cname) - if ecode < 0 { - return nil, MakeGitError(ecode) - } - - return newReferenceFromC(ptr, v), nil -} - func (v *Repository) Head() (*Reference, error) { var ptr *C.git_reference @@ -245,59 +233,6 @@ func (v *Repository) SetHeadDetached(id *Oid) error { return nil } -func (v *Repository) CreateReference(name string, id *Oid, force bool, msg string) (*Reference, error) { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - - var cmsg *C.char - if msg == "" { - cmsg = nil - } else { - cmsg = C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) - } - - var ptr *C.git_reference - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ecode := C.git_reference_create(&ptr, v.ptr, cname, id.toC(), cbool(force), cmsg) - if ecode < 0 { - return nil, MakeGitError(ecode) - } - - return newReferenceFromC(ptr, v), nil -} - -func (v *Repository) CreateSymbolicReference(name, target string, force bool, msg string) (*Reference, error) { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - - ctarget := C.CString(target) - defer C.free(unsafe.Pointer(ctarget)) - - var cmsg *C.char - if msg == "" { - cmsg = nil - } else { - cmsg = C.CString(msg) - defer C.free(unsafe.Pointer(cmsg)) - } - - var ptr *C.git_reference - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ecode := C.git_reference_symbolic_create(&ptr, v.ptr, cname, ctarget, cbool(force), cmsg) - if ecode < 0 { - return nil, MakeGitError(ecode) - } - - return newReferenceFromC(ptr, v), nil -} - func (v *Repository) Walk() (*RevWalk, error) { var walkPtr *C.git_revwalk @@ -479,57 +414,6 @@ func (v *Repository) TreeBuilderFromTree(tree *Tree) (*TreeBuilder, error) { return bld, nil } -// EnsureLog ensures that there is a reflog for the given reference -// name and creates an empty one if necessary. -func (v *Repository) EnsureLog(name string) error { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_reference_ensure_log(v.ptr, cname) - if ret < 0 { - return MakeGitError(ret) - } - - return nil -} - -// HasLog returns whether there is a reflog for the given reference -// name -func (v *Repository) HasLog(name string) (bool, error) { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - ret := C.git_reference_has_log(v.ptr, cname) - if ret < 0 { - return false, MakeGitError(ret) - } - - return ret == 1, nil -} - -// DwimReference looks up a reference by DWIMing its short name -func (v *Repository) DwimReference(name string) (*Reference, error) { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - var ptr *C.git_reference - ret := C.git_reference_dwim(&ptr, v.ptr, cname) - if ret < 0 { - return nil, MakeGitError(ret) - } - - return newReferenceFromC(ptr, v), nil -} - // CreateNote adds a note for an object func (v *Repository) CreateNote( ref string, author, committer *Signature, id *Oid, diff --git a/revparse_test.go b/revparse_test.go index 4bc327c..091a76b 100644 --- a/revparse_test.go +++ b/revparse_test.go @@ -34,7 +34,7 @@ func TestRevparseExt(t *testing.T) { _, treeId := seedTestRepo(t, repo) - ref, err := repo.CreateReference("refs/heads/master", treeId, true, "") + ref, err := repo.References.Create("refs/heads/master", treeId, true, "") checkFatal(t, err) obj, ref, err := repo.RevparseExt("master") From 70c95a7655eddffda4cd8fddd87536c5580136fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 01:19:22 +0200 Subject: [PATCH 12/36] Create a NotesCollection for managing notes As with the others, move these methods into their own namespace. --- note.go | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++ note_test.go | 14 +++--- repository.go | 119 ++----------------------------------------------- 3 files changed, 132 insertions(+), 122 deletions(-) diff --git a/note.go b/note.go index 3cdd340..a1b15d8 100644 --- a/note.go +++ b/note.go @@ -10,6 +10,127 @@ import ( "unsafe" ) +// This object represents the possible operations which can be +// performed on the collection of notes for a repository. +type NoteCollection struct { + repo *Repository +} + +// Create adds a note for an object +func (c *NoteCollection) Create( + 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, err := author.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(authorSig) + + committerSig, err := committer.toC() + if err != nil { + return nil, err + } + 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(), c.repo.ptr, cref, authorSig, + committerSig, id.toC(), cnote, cbool(force)) + + if ret < 0 { + return nil, MakeGitError(ret) + } + return oid, nil +} + +// Read reads the note for an object +func (c *NoteCollection) Read(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, c.repo.ptr, cref, id.toC()); ret < 0 { + return nil, MakeGitError(ret) + } + + runtime.SetFinalizer(note, (*Note).Free) + return note, nil +} + +// Remove removes the note for an object +func (c *NoteCollection) Remove(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, err := author.toC() + if err != nil { + return err + } + defer C.git_signature_free(authorSig) + + committerSig, err := committer.toC() + if err != nil { + return err + } + defer C.git_signature_free(committerSig) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_note_remove(c.repo.ptr, cref, authorSig, committerSig, id.toC()) + if ret < 0 { + return MakeGitError(ret) + } + return nil +} + +// DefaultRef returns the default notes reference for a repository +func (c *NoteCollection) DefaultRef() (string, error) { + buf := C.git_buf{} + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if ret := C.git_note_default_ref(&buf, c.repo.ptr); ret < 0 { + return "", MakeGitError(ret) + } + + ret := C.GoString(buf.ptr) + C.git_buf_free(&buf) + + return ret, nil +} + // Note type Note struct { ptr *C.git_note diff --git a/note_test.go b/note_test.go index e6c378d..27e04be 100644 --- a/note_test.go +++ b/note_test.go @@ -53,7 +53,7 @@ func TestNoteIterator(t *testing.T) { break } - note, err := repo.ReadNote("", commitId) + note, err := repo.Notes.Read("", commitId) checkFatal(t, err) if !reflect.DeepEqual(note.Id(), noteId) { @@ -73,13 +73,13 @@ func TestRemoveNote(t *testing.T) { note, _ := createTestNote(t, repo, commit) - _, err = repo.ReadNote("", commit.Id()) + _, err = repo.Notes.Read("", commit.Id()) checkFatal(t, err) - err = repo.RemoveNote("", note.Author(), note.Committer(), commitId) + err = repo.Notes.Remove("", note.Author(), note.Committer(), commitId) checkFatal(t, err) - _, err = repo.ReadNote("", commit.Id()) + _, err = repo.Notes.Read("", commit.Id()) if err == nil { t.Fatal("note remove failed") } @@ -89,7 +89,7 @@ func TestDefaultNoteRef(t *testing.T) { repo := createTestRepo(t) defer cleanupTestRepo(t, repo) - ref, err := repo.DefaultNoteRef() + ref, err := repo.Notes.DefaultRef() checkFatal(t, err) compareStrings(t, "refs/notes/commits", ref) @@ -103,10 +103,10 @@ func createTestNote(t *testing.T, repo *Repository, commit *Commit) (*Note, *Oid 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) + noteId, err := repo.Notes.Create("", sig, sig, commit.Id(), "I am a note\n", false) checkFatal(t, err) - note, err := repo.ReadNote("", commit.Id()) + note, err := repo.Notes.Read("", commit.Id()) checkFatal(t, err) return note, noteId diff --git a/repository.go b/repository.go index 8aa39ef..5a2b815 100644 --- a/repository.go +++ b/repository.go @@ -24,12 +24,16 @@ type Repository struct { // References represents the collection of references and can // be used to create, remove or update refernces for this repository. References ReferenceCollection + // Notes represents the collection of notes and can be used to + // read, write and delete notes from this repository. + Notes NoteCollection } func initRepositoryObject(repo *Repository) { repo.Remotes.repo = repo repo.Submodules.repo = repo repo.References.repo = repo + repo.Notes.repo = repo runtime.SetFinalizer(repo, (*Repository).Free) } @@ -414,121 +418,6 @@ func (v *Repository) TreeBuilderFromTree(tree *Tree) (*TreeBuilder, error) { return bld, 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, err := author.toC() - if err != nil { - return nil, err - } - defer C.git_signature_free(authorSig) - - committerSig, err := committer.toC() - if err != nil { - return nil, err - } - 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, err := author.toC() - if err != nil { - return err - } - defer C.git_signature_free(authorSig) - - committerSig, err := committer.toC() - if err != nil { - return err - } - 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) { - buf := C.git_buf{} - - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - if ret := C.git_note_default_ref(&buf, v.ptr); ret < 0 { - return "", MakeGitError(ret) - } - - ret := C.GoString(buf.ptr) - C.git_buf_free(&buf) - - return ret, nil -} - type RepositoryState int const ( From 3115b6c76236517ca6d10e54a1e34965c340531f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 01:32:59 +0200 Subject: [PATCH 13/36] travis: allow containerized builds These builds should be quicker and faster. We don't need sudo, so write that down for Travis to move us to these builds. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f8b7e93..9341639 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: go +sudo: false + install: - cd "${HOME}" - wget -O libgit2-0.22.1.tar.gz https://github.com/libgit2/libgit2/archive/v0.22.1.tar.gz From 5f3a9d76b86efa91d8f1ee26277484419bc95f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 13:17:14 +0200 Subject: [PATCH 14/36] travis: don't install libgit2 for 'next' branch We have our own libgit2 in a submodule, so this is unnecessary. While in the area, update which version of libgit2 would be downloaded and remove unnecessary CMake flags. --- .travis.yml | 9 +-------- script/install-libgit2.sh | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 script/install-libgit2.sh diff --git a/.travis.yml b/.travis.yml index 9341639..fb080b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,14 +2,7 @@ language: go sudo: false -install: - - cd "${HOME}" - - wget -O libgit2-0.22.1.tar.gz https://github.com/libgit2/libgit2/archive/v0.22.1.tar.gz - - tar -xzvf libgit2-0.22.1.tar.gz - - cd libgit2-0.22.1 && mkdir build && cd build - - cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_C_FLAGS=-fPIC -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DCMAKE_INSTALL_PREFIX=/usr/local .. && make && sudo make install - - sudo ldconfig - - cd "${TRAVIS_BUILD_DIR}" +install: ./script/install-libgit2.sh go: - 1.1 diff --git a/script/install-libgit2.sh b/script/install-libgit2.sh new file mode 100644 index 0000000..a6c3202 --- /dev/null +++ b/script/install-libgit2.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# +# Install libgit2 to git2go in dynamic mode on Travis +# + +set -ex + +# We don't want to build libgit2 on the next branch, as we carry a +# submodule with the exact version we support +if [ "x$TRAVIS_BRANCH" = "xnext" ]; then + exit 0 +fi + +cd "${HOME}" +wget -O libgit2-0.22.3.tar.gz https://github.com/libgit2/libgit2/archive/v0.22.1.tar.gz +tar -xzvf libgit2-0.22.3.tar.gz +cd libgit2-0.22.1 && mkdir build && cd build +cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_BUILD_TYPE="RelWithDebInfo" .. && make && sudo make install +sudo ldconfig +cd "${TRAVIS_BUILD_DIR}" From 2d6b1ebd4c8f98d3946920003796b16b702d70c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 13:19:03 +0200 Subject: [PATCH 15/36] travis: make the script executable --- script/install-libgit2.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 script/install-libgit2.sh diff --git a/script/install-libgit2.sh b/script/install-libgit2.sh old mode 100644 new mode 100755 From 0ce52d9aebdb2bfd0dee91b58296a6c70c7de8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 28 Jun 2015 14:35:57 +0200 Subject: [PATCH 16/36] Update to libgit2 fb84cde8 --- vendor/libgit2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/libgit2 b/vendor/libgit2 index fa39975..fb84cde 160000 --- a/vendor/libgit2 +++ b/vendor/libgit2 @@ -1 +1 @@ -Subproject commit fa399750c680aa254784a40193d73d373df5e3ea +Subproject commit fb84cde81e11947add4ff8bb9b4084f7d76e6567 From 66d266f97185020fe80f4b573411c39fc354fc91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 30 Jun 2015 19:03:52 +0200 Subject: [PATCH 17/36] Repository: move to use an actual constructor This should further reduce the changes of the creation of the object going badly. --- repository.go | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/repository.go b/repository.go index 5a2b815..2e2e897 100644 --- a/repository.go +++ b/repository.go @@ -29,81 +29,78 @@ type Repository struct { Notes NoteCollection } -func initRepositoryObject(repo *Repository) { +func newRepositoryFromC(ptr *C.git_repository) *Repository { + repo := &Repository{ptr: ptr} + repo.Remotes.repo = repo repo.Submodules.repo = repo repo.References.repo = repo repo.Notes.repo = repo + runtime.SetFinalizer(repo, (*Repository).Free) + + return repo } func OpenRepository(path string) (*Repository, error) { - repo := new(Repository) - cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_repository_open(&repo.ptr, cpath) + var ptr *C.git_repository + ret := C.git_repository_open(&ptr, cpath) if ret < 0 { return nil, MakeGitError(ret) } - initRepositoryObject(repo) - return repo, nil + return newRepositoryFromC(ptr), nil } func OpenRepositoryExtended(path string) (*Repository, error) { - repo := new(Repository) - cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_repository_open_ext(&repo.ptr, cpath, 0, nil) + var ptr *C.git_repository + ret := C.git_repository_open_ext(&ptr, cpath, 0, nil) if ret < 0 { return nil, MakeGitError(ret) } - initRepositoryObject(repo) - return repo, nil + return newRepositoryFromC(ptr), nil } func InitRepository(path string, isbare bool) (*Repository, error) { - repo := new(Repository) - cpath := C.CString(path) defer C.free(unsafe.Pointer(cpath)) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_repository_init(&repo.ptr, cpath, ucbool(isbare)) + var ptr *C.git_repository + ret := C.git_repository_init(&ptr, cpath, ucbool(isbare)) if ret < 0 { return nil, MakeGitError(ret) } - initRepositoryObject(repo) - return repo, nil + return newRepositoryFromC(ptr), nil } 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) + var ptr *C.git_repository + ret := C.git_repository_wrap_odb(&ptr, odb.ptr) if ret < 0 { return nil, MakeGitError(ret) } - initRepositoryObject(repo) - return repo, nil + return newRepositoryFromC(ptr), nil } func (v *Repository) SetRefdb(refdb *Refdb) { From b6811196e4f7728b25ab37ce2a0862aa74c22253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 30 Jun 2015 19:08:29 +0200 Subject: [PATCH 18/36] Clone: test we clone something usable and fix constructor Clone was still trying to do its own initialisation, which was missing all of the namespacing changes. --- clone.go | 8 +++----- clone_test.go | 10 ++++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/clone.go b/clone.go index f67f511..b5c5a5b 100644 --- a/clone.go +++ b/clone.go @@ -20,8 +20,6 @@ type CloneOptions struct { } func Clone(url string, path string, options *CloneOptions) (*Repository, error) { - repo := new(Repository) - curl := C.CString(url) defer C.free(unsafe.Pointer(curl)) @@ -37,7 +35,8 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error) runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_clone(&repo.ptr, curl, cpath, copts) + var ptr *C.git_repository + ret := C.git_clone(&ptr, curl, cpath, copts) freeCheckoutOpts(&copts.checkout_opts) C.free(unsafe.Pointer(copts.checkout_branch)) C.free(unsafe.Pointer(copts)) @@ -46,8 +45,7 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error) return nil, MakeGitError(ret) } - runtime.SetFinalizer(repo, (*Repository).Free) - return repo, nil + return newRepositoryFromC(ptr), nil } func populateCloneOptions(ptr *C.git_clone_options, opts *CloneOptions) { diff --git a/clone_test.go b/clone_test.go index fd83fec..7cdc362 100644 --- a/clone_test.go +++ b/clone_test.go @@ -15,8 +15,18 @@ func TestClone(t *testing.T) { path, err := ioutil.TempDir("", "git2go") checkFatal(t, err) + ref, err := repo.References.Lookup("refs/heads/master") + checkFatal(t, err) + repo2, err := Clone(repo.Path(), path, &CloneOptions{Bare: true}) defer cleanupTestRepo(t, repo2) checkFatal(t, err) + + ref2, err := repo2.References.Lookup("refs/heads/master") + checkFatal(t, err) + + if ref.Cmp(ref2) != 0 { + t.Fatal("reference in clone does not match original ref") + } } From 47191d12746e39a246d8eb089e267340349728b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 30 Jun 2015 19:12:39 +0200 Subject: [PATCH 19/36] Submodule: use the Repository constructor --- submodule.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/submodule.go b/submodule.go index 6d4dc28..4a32ce4 100644 --- a/submodule.go +++ b/submodule.go @@ -298,16 +298,15 @@ func (sub *Submodule) Sync() error { } func (sub *Submodule) Open() (*Repository, error) { - repo := new(Repository) - runtime.LockOSThread() defer runtime.UnlockOSThread() - ret := C.git_submodule_open(&repo.ptr, sub.ptr) + var ptr *C.git_repository + ret := C.git_submodule_open(&ptr, sub.ptr) if ret < 0 { return nil, MakeGitError(ret) } - return repo, nil + return newRepositoryFromC(ptr), nil } func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error { From b5693c1429ad7247ce75b23ebb866f9428bde8b6 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Mon, 6 Jul 2015 19:27:58 -0700 Subject: [PATCH 20/36] Prevent slot int variable from being GCed. Before this change, there were no users of slot int variable in the Go world (just a pointer to it that ended up in C world only), so Go's garbage collector would free it and its value could not retrieved later (once a pointer to it comes back to Go world from C world). Keep a pointer to it in the Go world so that does not happen. Fixes #218. --- handles.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/handles.go b/handles.go index ec62a48..a062231 100644 --- a/handles.go +++ b/handles.go @@ -10,14 +10,15 @@ type HandleList struct { sync.RWMutex // stores the Go pointers handles []interface{} - // indicates which indices are in use - set map[int]bool + // Indicates which indices are in use, and keeps a pointer to slot int variable (the handle) + // in the Go world, so that the Go garbage collector does not free it. + set map[int]*int } func NewHandleList() *HandleList { return &HandleList{ handles: make([]interface{}, 5), - set: make(map[int]bool), + set: make(map[int]*int), } } @@ -25,7 +26,7 @@ func NewHandleList() *HandleList { // list. You must only run this function while holding a write lock. func (v *HandleList) findUnusedSlot() int { for i := 1; i < len(v.handles); i++ { - isUsed := v.set[i] + _, isUsed := v.set[i] if !isUsed { return i } @@ -47,7 +48,7 @@ func (v *HandleList) Track(pointer interface{}) unsafe.Pointer { slot := v.findUnusedSlot() v.handles[slot] = pointer - v.set[slot] = true + v.set[slot] = &slot // Keep a pointer to slot in Go world, so it's not freed by GC. v.Unlock() From 28dee704cafd347599ae55f30441275b219def03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 10 Jul 2015 00:31:32 +0200 Subject: [PATCH 21/36] Bring back CheckoutForce This was mistakenly deleted when the SafeCreate was replaced with RecreateMissing. --- checkout.go | 1 + 1 file changed, 1 insertion(+) diff --git a/checkout.go b/checkout.go index d747344..e0c067e 100644 --- a/checkout.go +++ b/checkout.go @@ -15,6 +15,7 @@ type CheckoutStrategy uint const ( CheckoutNone CheckoutStrategy = C.GIT_CHECKOUT_NONE // Dry run, no actual updates CheckoutSafe CheckoutStrategy = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data + CheckoutForce CheckoutStrategy = C.GIT_CHECKOUT_FORCE // Allow all updates to force working directory to look like index CheckoutRecreateMissing CheckoutStrategy = C.GIT_CHECKOUT_RECREATE_MISSING // Allow checkout to recreate missing files CheckoutAllowConflicts CheckoutStrategy = C.GIT_CHECKOUT_ALLOW_CONFLICTS // Allow checkout to make safe updates even if conflicts are found CheckoutRemoveUntracked CheckoutStrategy = C.GIT_CHECKOUT_REMOVE_UNTRACKED // Remove untracked files not in index (that are not ignored) From 4b88210cbf495891c8d44c53b3d978e6ff31a5a3 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Fri, 24 Jul 2015 12:14:24 +0300 Subject: [PATCH 22/36] Add check if reference is a note --- reference.go | 5 +++++ reference_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/reference.go b/reference.go index d24e054..452de46 100644 --- a/reference.go +++ b/reference.go @@ -315,6 +315,11 @@ func (v *Reference) IsTag() bool { return C.git_reference_is_tag(v.ptr) == 1 } +// IsNote checks if the reference is a note. +func (v *Reference) IsNote() bool { + return C.git_reference_is_note(v.ptr) == 1 +} + func (v *Reference) Free() { runtime.SetFinalizer(v, nil) C.git_reference_free(v.ptr) diff --git a/reference_test.go b/reference_test.go index f1546e2..b69a274 100644 --- a/reference_test.go +++ b/reference_test.go @@ -176,6 +176,38 @@ func TestUtil(t *testing.T) { } } +func TestIsNote(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + commitID, _ := seedTestRepo(t, repo) + + sig := &Signature{ + Name: "Rand Om Hacker", + Email: "random@hacker.com", + When: time.Now(), + } + + refname, err := repo.Notes.DefaultRef() + checkFatal(t, err) + + _, err = repo.Notes.Create(refname, sig, sig, commitID, "This is a note", false) + checkFatal(t, err) + + ref, err := repo.References.Lookup(refname) + checkFatal(t, err) + + if !ref.IsNote() { + t.Fatalf("%s should be a note", ref.Name()) + } + + ref, err = repo.References.Create("refs/heads/foo", commitID, true, "") + checkFatal(t, err) + + if ref.IsNote() { + t.Fatalf("%s should not be a note", ref.Name()) + } +} func compareStringList(t *testing.T, expected, actual []string) { for i, v := range expected { if actual[i] != v { From ec93213f21f57e6b378bf9f6ceb05c9fd1f15daf Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Fri, 24 Jul 2015 12:14:53 +0300 Subject: [PATCH 23/36] Add ReferenceIsValidName() --- reference.go | 19 +++++++++++++++++++ reference_test.go | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/reference.go b/reference.go index 452de46..140082f 100644 --- a/reference.go +++ b/reference.go @@ -430,3 +430,22 @@ func (v *ReferenceIterator) Free() { runtime.SetFinalizer(v, nil) C.git_reference_iterator_free(v.ptr) } + +// ReferenceIsValidName ensures the reference name is well-formed. +// +// Valid reference names must follow one of two patterns: +// +// 1. Top-level names must contain only capital letters and underscores, +// and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD"). +// +// 2. Names prefixed with "refs/" can be almost anything. You must avoid +// the characters '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences +// ".." and " @ {" which have special meaning to revparse. +func ReferenceIsValidName(name string) bool { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + if C.git_reference_is_valid_name(cname) == 1 { + return true + } + return false +} diff --git a/reference_test.go b/reference_test.go index b69a274..761daf8 100644 --- a/reference_test.go +++ b/reference_test.go @@ -208,6 +208,16 @@ func TestIsNote(t *testing.T) { t.Fatalf("%s should not be a note", ref.Name()) } } + +func TestReferenceIsValidName(t *testing.T) { + if !ReferenceIsValidName("HEAD") { + t.Errorf("HEAD should be a valid reference name") + } + if ReferenceIsValidName("HEAD1") { + t.Errorf("HEAD1 should not be a valid reference name") + } +} + func compareStringList(t *testing.T, expected, actual []string) { for i, v := range expected { if actual[i] != v { From 64c160f6f2300fc675453761471dc8d4726756e0 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Fri, 24 Jul 2015 19:46:57 +0300 Subject: [PATCH 24/36] Find tree entry by id Add support for 'git_tree_entry_byid'. --- tree.go | 18 ++++++++++++++++++ tree_test.go | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tree_test.go diff --git a/tree.go b/tree.go index aad2c8d..f543c11 100644 --- a/tree.go +++ b/tree.go @@ -55,6 +55,24 @@ func (t Tree) EntryByName(filename string) *TreeEntry { return newTreeEntry(entry) } +// EntryById performs a lookup for a tree entry with the given SHA value. +// +// It returns a *TreeEntry that is owned by the Tree. You don't have to +// free it, but you must not use it after the Tree is freed. +// +// Warning: this must examine every entry in the tree, so it is not fast. +func (t Tree) EntryById(id *Oid) *TreeEntry { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + entry := C.git_tree_entry_byid(t.cast_ptr, id.toC()) + if entry == nil { + return nil + } + + return newTreeEntry(entry) +} + // EntryByPath looks up an entry by its full path, recursing into // deeper trees if necessary (i.e. if there are slashes in the path) func (t Tree) EntryByPath(path string) (*TreeEntry, error) { diff --git a/tree_test.go b/tree_test.go new file mode 100644 index 0000000..4c6a4ed --- /dev/null +++ b/tree_test.go @@ -0,0 +1,22 @@ +package git + +import "testing" + +func TestTreeEntryById(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + _, treeID := seedTestRepo(t, repo) + + tree, err := repo.LookupTree(treeID) + checkFatal(t, err) + + id, err := NewOid("257cc5642cb1a054f08cc83f2d943e56fd3ebe99") + checkFatal(t, err) + + entry := tree.EntryById(id) + + if entry == nil { + t.Fatalf("entry id %v was not found", id) + } +} From 12311c8528c577ebb11006f24c026c7a4d2f2de3 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Fri, 31 Jul 2015 09:51:19 +0200 Subject: [PATCH 25/36] Add TagsCollection --- repository.go | 4 ++++ tag.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/repository.go b/repository.go index 44509af..b17745d 100644 --- a/repository.go +++ b/repository.go @@ -27,6 +27,9 @@ type Repository struct { // Notes represents the collection of notes and can be used to // read, write and delete notes from this repository. Notes NoteCollection + // Tags represents the collection of tags and can be used to create, + // list and iterate tags in this repository. + Tags TagsCollection } func newRepositoryFromC(ptr *C.git_repository) *Repository { @@ -36,6 +39,7 @@ func newRepositoryFromC(ptr *C.git_repository) *Repository { repo.Submodules.repo = repo repo.References.repo = repo repo.Notes.repo = repo + repo.Tags.repo = repo runtime.SetFinalizer(repo, (*Repository).Free) diff --git a/tag.go b/tag.go index 89ac8bd..74b18d8 100644 --- a/tag.go +++ b/tag.go @@ -42,3 +42,7 @@ func (t Tag) TargetId() *Oid { func (t Tag) TargetType() ObjectType { return ObjectType(C.git_tag_target_type(t.cast_ptr)) } + +type TagsCollection struct { + repo *Repository +} From 6c4af98c5b2763b020e39357f31bcc6d6f1960e1 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Fri, 31 Jul 2015 10:07:26 +0200 Subject: [PATCH 26/36] Add more support for tags Implement support for the following libgit2 functions: - 'git_tag_list' and 'git_tag_list_match' - 'git_tag_foreach' - 'git_tag_create_lightweight' --- tag.go | 133 ++++++++++++++++++++++++++++++++++++++++++++ tag_test.go | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++ wrapper.c | 5 ++ 3 files changed, 293 insertions(+) diff --git a/tag.go b/tag.go index 74b18d8..5801c99 100644 --- a/tag.go +++ b/tag.go @@ -2,8 +2,14 @@ package git /* #include + +extern int _go_git_tag_foreach(git_repository *repo, void *payload); */ import "C" +import ( + "runtime" + "unsafe" +) // Tag type Tag struct { @@ -46,3 +52,130 @@ func (t Tag) TargetType() ObjectType { type TagsCollection struct { repo *Repository } + +// CreateLightweight creates a new lightweight tag pointing to a commit +// and returns the id of the target object. +// +// The name of the tag is validated for consistency (see git_tag_create() for the rules +// https://libgit2.github.com/libgit2/#HEAD/group/tag/git_tag_create) and should +// not conflict with an already existing tag name. +// +// If force is true and a reference already exists with the given name, it'll be replaced. +// +// The created tag is a simple reference and can be queried using +// repo.References.Lookup("refs/tags/"). The name of the tag (eg "v1.0.0") +// is queried with ref.Shorthand(). +func (c *TagsCollection) CreateLightweight(name string, commit *Commit, force bool) (*Oid, error) { + + oid := new(Oid) + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + ctarget := commit.gitObject.ptr + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C.git_tag_create_lightweight(oid.toC(), c.repo.ptr, cname, ctarget, cbool(force)) + if err < 0 { + return nil, MakeGitError(err) + } + + return oid, nil +} + +// List returns the names of all the tags in the repository, +// eg: ["v1.0.1", "v2.0.0"]. +func (c *TagsCollection) List() ([]string, error) { + var strC C.git_strarray + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_tag_list(&strC, c.repo.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + defer C.git_strarray_free(&strC) + + tags := makeStringsFromCStrings(strC.strings, int(strC.count)) + return tags, nil +} + +// ListWithMatch returns the names of all the tags in the repository +// that match a given pattern. +// +// The pattern is a standard fnmatch(3) pattern http://man7.org/linux/man-pages/man3/fnmatch.3.html +func (c *TagsCollection) ListWithMatch(pattern string) ([]string, error) { + var strC C.git_strarray + + patternC := C.CString(pattern) + defer C.free(unsafe.Pointer(patternC)) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_tag_list_match(&strC, patternC, c.repo.ptr) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + defer C.git_strarray_free(&strC) + + tags := makeStringsFromCStrings(strC.strings, int(strC.count)) + return tags, nil +} + +// TagForeachCallback is called for each tag in the repository. +// +// The name is the full ref name eg: "refs/tags/v1.0.0". +// +// Note that the callback is called for lightweight tags as well, +// so repo.LookupTag() will return an error for these tags. Use +// repo.References.Lookup() instead. +type TagForeachCallback func(name string, id *Oid) error +type tagForeachData struct { + callback TagForeachCallback + err error +} + +//export gitTagForeachCb +func gitTagForeachCb(name *C.char, id *C.git_oid, handle unsafe.Pointer) int { + payload := pointerHandles.Get(handle) + data, ok := payload.(*tagForeachData) + if !ok { + panic("could not retrieve tag foreach CB handle") + } + + err := data.callback(C.GoString(name), newOidFromC(id)) + if err != nil { + data.err = err + return C.GIT_EUSER + } + + return 0 +} + +// Foreach calls the callback for each tag in the repository. +func (c *TagsCollection) Foreach(callback TagForeachCallback) error { + data := tagForeachData{ + callback: callback, + err: nil, + } + + handle := pointerHandles.Track(&data) + defer pointerHandles.Untrack(handle) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + err := C._go_git_tag_foreach(c.repo.ptr, handle) + if err == C.GIT_EUSER { + return data.err + } + if err < 0 { + return MakeGitError(err) + } + + return nil +} diff --git a/tag_test.go b/tag_test.go index 74f9fec..4bf3889 100644 --- a/tag_test.go +++ b/tag_test.go @@ -1,6 +1,7 @@ package git import ( + "errors" "testing" "time" ) @@ -24,6 +25,146 @@ func TestCreateTag(t *testing.T) { compareStrings(t, commitId.String(), tag.TargetId().String()) } +func TestCreateTagLightweight(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + commitID, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitID) + checkFatal(t, err) + + tagID, err := repo.Tags.CreateLightweight("v0.1.0", commit, false) + checkFatal(t, err) + + _, err = repo.Tags.CreateLightweight("v0.1.0", commit, true) + checkFatal(t, err) + + ref, err := repo.References.Lookup("refs/tags/v0.1.0") + checkFatal(t, err) + + compareStrings(t, "refs/tags/v0.1.0", ref.Name()) + compareStrings(t, "v0.1.0", ref.Shorthand()) + compareStrings(t, tagID.String(), commitID.String()) + compareStrings(t, commitID.String(), ref.Target().String()) +} + +func TestListTags(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + commitID, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitID) + checkFatal(t, err) + + createTag(t, repo, commit, "v1.0.1", "Release v1.0.1") + + commitID, _ = updateReadme(t, repo, "Release version 2") + + commit, err = repo.LookupCommit(commitID) + checkFatal(t, err) + + createTag(t, repo, commit, "v2.0.0", "Release v2.0.0") + + expected := []string{ + "v1.0.1", + "v2.0.0", + } + + actual, err := repo.Tags.List() + checkFatal(t, err) + + compareStringList(t, expected, actual) +} + +func TestListTagsWithMatch(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + commitID, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitID) + checkFatal(t, err) + + createTag(t, repo, commit, "v1.0.1", "Release v1.0.1") + + commitID, _ = updateReadme(t, repo, "Release version 2") + + commit, err = repo.LookupCommit(commitID) + checkFatal(t, err) + + createTag(t, repo, commit, "v2.0.0", "Release v2.0.0") + + expected := []string{ + "v2.0.0", + } + + actual, err := repo.Tags.ListWithMatch("v2*") + checkFatal(t, err) + + compareStringList(t, expected, actual) + + expected = []string{ + "v1.0.1", + } + + actual, err = repo.Tags.ListWithMatch("v1*") + checkFatal(t, err) + + compareStringList(t, expected, actual) +} + +func TestTagForeach(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + commitID, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitID) + checkFatal(t, err) + + tag1 := createTag(t, repo, commit, "v1.0.1", "Release v1.0.1") + + commitID, _ = updateReadme(t, repo, "Release version 2") + + commit, err = repo.LookupCommit(commitID) + checkFatal(t, err) + + tag2 := createTag(t, repo, commit, "v2.0.0", "Release v2.0.0") + + expectedNames := []string{ + "refs/tags/v1.0.1", + "refs/tags/v2.0.0", + } + actualNames := []string{} + expectedOids := []string{ + tag1.String(), + tag2.String(), + } + actualOids := []string{} + + err = repo.Tags.Foreach(func(name string, id *Oid) error { + actualNames = append(actualNames, name) + actualOids = append(actualOids, id.String()) + return nil + }) + checkFatal(t, err) + + compareStringList(t, expectedNames, actualNames) + compareStringList(t, expectedOids, actualOids) + + fakeErr := errors.New("fake error") + + err = repo.Tags.Foreach(func(name string, id *Oid) error { + return fakeErr + }) + + if err != fakeErr { + t.Fatalf("Tags.Foreach() did not return the expected error, got %v", err) + } +} + func compareStrings(t *testing.T, expected, value string) { if value != expected { t.Fatalf("expected '%v', actual '%v'", expected, value) @@ -43,3 +184,17 @@ func createTestTag(t *testing.T, repo *Repository, commit *Commit) *Oid { checkFatal(t, err) return tagId } + +func createTag(t *testing.T, repo *Repository, commit *Commit, name, message string) *Oid { + loc, err := time.LoadLocation("Europe/Bucharest") + checkFatal(t, err) + sig := &Signature{ + Name: "Rand Om Hacker", + Email: "random@hacker.com", + When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), + } + + tagId, err := repo.CreateTag(name, commit, sig, message) + checkFatal(t, err) + return tagId +} diff --git a/wrapper.c b/wrapper.c index 75cc03c..1efe5d7 100644 --- a/wrapper.c +++ b/wrapper.c @@ -131,4 +131,9 @@ int _go_git_index_remove_all(git_index *index, const git_strarray *pathspec, voi return git_index_remove_all(index, pathspec, cb, callback); } +int _go_git_tag_foreach(git_repository *repo, void *payload) +{ + return git_tag_foreach(repo, (git_tag_foreach_cb)&gitTagForeachCb, payload); +} + /* EOF */ From def4494b74ec1c8fd12669e3f65bd29d6315c83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 31 Jul 2015 20:23:05 +0200 Subject: [PATCH 27/36] Move CreateTag to the tags collection --- repository.go | 30 ------------------------------ tag.go | 30 ++++++++++++++++++++++++++++++ tag_test.go | 4 ++-- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/repository.go b/repository.go index b17745d..62fde6d 100644 --- a/repository.go +++ b/repository.go @@ -321,36 +321,6 @@ func (v *Repository) CreateCommit( return oid, nil } -func (v *Repository) CreateTag( - name string, commit *Commit, tagger *Signature, message string) (*Oid, error) { - - oid := new(Oid) - - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - - cmessage := C.CString(message) - defer C.free(unsafe.Pointer(cmessage)) - - taggerSig, err := tagger.toC() - if err != nil { - return nil, err - } - defer C.git_signature_free(taggerSig) - - 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) - } - - return oid, nil -} - func (v *Odb) Free() { runtime.SetFinalizer(v, nil) C.git_odb_free(v.ptr) diff --git a/tag.go b/tag.go index 5801c99..ca85156 100644 --- a/tag.go +++ b/tag.go @@ -53,6 +53,36 @@ type TagsCollection struct { repo *Repository } +func (c *TagsCollection) Create( + name string, commit *Commit, tagger *Signature, message string) (*Oid, error) { + + oid := new(Oid) + + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + + cmessage := C.CString(message) + defer C.free(unsafe.Pointer(cmessage)) + + taggerSig, err := tagger.toC() + if err != nil { + return nil, err + } + defer C.git_signature_free(taggerSig) + + ctarget := commit.gitObject.ptr + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ret := C.git_tag_create(oid.toC(), c.repo.ptr, cname, ctarget, taggerSig, cmessage, 0) + if ret < 0 { + return nil, MakeGitError(ret) + } + + return oid, nil +} + // CreateLightweight creates a new lightweight tag pointing to a commit // and returns the id of the target object. // diff --git a/tag_test.go b/tag_test.go index 4bf3889..2fdfe00 100644 --- a/tag_test.go +++ b/tag_test.go @@ -180,7 +180,7 @@ func createTestTag(t *testing.T, repo *Repository, commit *Commit) *Oid { When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), } - tagId, err := repo.CreateTag("v0.0.0", commit, sig, "This is a tag") + tagId, err := repo.Tags.Create("v0.0.0", commit, sig, "This is a tag") checkFatal(t, err) return tagId } @@ -194,7 +194,7 @@ func createTag(t *testing.T, repo *Repository, commit *Commit, name, message str When: time.Date(2013, 03, 06, 14, 30, 0, 0, loc), } - tagId, err := repo.CreateTag(name, commit, sig, message) + tagId, err := repo.Tags.Create(name, commit, sig, message) checkFatal(t, err) return tagId } From 1018ff76d034662e5bba7665a62d6263cd377ab7 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Fri, 24 Jul 2015 01:39:49 +0300 Subject: [PATCH 28/36] Add git-describe support Includes 'git_describe_commit' and 'git_describe_workdir'. --- describe.go | 222 +++++++++++++++++++++++++++++++++++++++++++++++ describe_test.go | 106 ++++++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 describe.go create mode 100644 describe_test.go diff --git a/describe.go b/describe.go new file mode 100644 index 0000000..c6f9a79 --- /dev/null +++ b/describe.go @@ -0,0 +1,222 @@ +package git + +/* +#include +*/ +import "C" +import ( + "runtime" + "unsafe" +) + +// DescribeOptions represents the describe operation configuration. +// +// You can use DefaultDescribeOptions() to get default options. +type DescribeOptions struct { + // How many tags as candidates to consider to describe the input commit-ish. + // Increasing it above 10 will take slightly longer but may produce a more + // accurate result. 0 will cause only exact matches to be output. + MaxCandidatesTags uint // default: 10 + + // By default describe only shows annotated tags. Change this in order + // to show all refs from refs/tags or refs/. + Strategy DescribeOptionsStrategy // default: DescribeDefault + + // Only consider tags matching the given glob(7) pattern, excluding + // the "refs/tags/" prefix. Can be used to avoid leaking private + // tags from the repo. + Pattern string + + // When calculating the distance from the matching tag or + // reference, only walk down the first-parent ancestry. + OnlyFollowFirstParent bool + + // If no matching tag or reference is found, the describe + // operation would normally fail. If this option is set, it + // will instead fall back to showing the full id of the commit. + ShowCommitOidAsFallback bool +} + +// DefaultDescribeOptions returns default options for the describe operation. +func DefaultDescribeOptions() (DescribeOptions, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + opts := C.git_describe_options{} + ecode := C.git_describe_init_options(&opts, C.GIT_DESCRIBE_OPTIONS_VERSION) + if ecode < 0 { + return DescribeOptions{}, MakeGitError(ecode) + } + + return DescribeOptions{ + MaxCandidatesTags: uint(opts.max_candidates_tags), + Strategy: DescribeOptionsStrategy(opts.describe_strategy), + }, nil +} + +// DescribeFormatOptions can be used for formatting the describe string. +// +// You can use DefaultDescribeFormatOptions() to get default options. +type DescribeFormatOptions struct { + // Size of the abbreviated commit id to use. This value is the + // lower bound for the length of the abbreviated string. + AbbreviatedSize uint // default: 7 + + // Set to use the long format even when a shorter name could be used. + AlwaysUseLongFormat bool + + // If the workdir is dirty and this is set, this string will be + // appended to the description string. + DirtySuffix string +} + +// DefaultDescribeFormatOptions returns default options for formatting +// the output. +func DefaultDescribeFormatOptions() (DescribeFormatOptions, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + opts := C.git_describe_format_options{} + ecode := C.git_describe_init_format_options(&opts, C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION) + if ecode < 0 { + return DescribeFormatOptions{}, MakeGitError(ecode) + } + + return DescribeFormatOptions{ + AbbreviatedSize: uint(opts.abbreviated_size), + AlwaysUseLongFormat: opts.always_use_long_format == 1, + }, nil +} + +// DescribeOptionsStrategy behaves like the --tags and --all options +// to git-describe, namely they say to look for any reference in +// either refs/tags/ or refs/ respectively. +// +// By default it only shows annotated tags. +type DescribeOptionsStrategy uint + +// Describe strategy options. +const ( + DescribeDefault DescribeOptionsStrategy = C.GIT_DESCRIBE_DEFAULT + DescribeTags DescribeOptionsStrategy = C.GIT_DESCRIBE_TAGS + DescribeAll DescribeOptionsStrategy = C.GIT_DESCRIBE_ALL +) + +// Describe performs the describe operation on the commit. +func (c *Commit) Describe(opts *DescribeOptions) (*DescribeResult, error) { + var resultPtr *C.git_describe_result + + var cDescribeOpts *C.git_describe_options + if opts != nil { + var cpattern *C.char + if len(opts.Pattern) > 0 { + cpattern = C.CString(opts.Pattern) + defer C.free(unsafe.Pointer(cpattern)) + } + + cDescribeOpts = &C.git_describe_options{ + version: C.GIT_DESCRIBE_OPTIONS_VERSION, + max_candidates_tags: C.uint(opts.MaxCandidatesTags), + describe_strategy: C.uint(opts.Strategy), + pattern: cpattern, + only_follow_first_parent: cbool(opts.OnlyFollowFirstParent), + show_commit_oid_as_fallback: cbool(opts.ShowCommitOidAsFallback), + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_describe_commit(&resultPtr, c.gitObject.ptr, cDescribeOpts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newDescribeResultFromC(resultPtr), nil +} + +// DescribeWorkdir describes the working tree. It means describe HEAD +// and appends (-dirty by default) if the working tree is dirty. +func (repo *Repository) DescribeWorkdir(opts *DescribeOptions) (*DescribeResult, error) { + var resultPtr *C.git_describe_result + + var cDescribeOpts *C.git_describe_options + if opts != nil { + var cpattern *C.char + if len(opts.Pattern) > 0 { + cpattern = C.CString(opts.Pattern) + defer C.free(unsafe.Pointer(cpattern)) + } + + cDescribeOpts = &C.git_describe_options{ + version: C.GIT_DESCRIBE_OPTIONS_VERSION, + max_candidates_tags: C.uint(opts.MaxCandidatesTags), + describe_strategy: C.uint(opts.Strategy), + pattern: cpattern, + only_follow_first_parent: cbool(opts.OnlyFollowFirstParent), + show_commit_oid_as_fallback: cbool(opts.ShowCommitOidAsFallback), + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_describe_workdir(&resultPtr, repo.ptr, cDescribeOpts) + if ecode < 0 { + return nil, MakeGitError(ecode) + } + + return newDescribeResultFromC(resultPtr), nil +} + +// DescribeResult represents the output from the 'git_describe_commit' +// and 'git_describe_workdir' functions in libgit2. +// +// Use Format() to get a string out of it. +type DescribeResult struct { + ptr *C.git_describe_result +} + +func newDescribeResultFromC(ptr *C.git_describe_result) *DescribeResult { + result := &DescribeResult{ + ptr: ptr, + } + runtime.SetFinalizer(result, (*DescribeResult).Free) + return result +} + +// Format prints the DescribeResult as a string. +func (result *DescribeResult) Format(opts *DescribeFormatOptions) (string, error) { + resultBuf := C.git_buf{} + + var cFormatOpts *C.git_describe_format_options + if opts != nil { + cDirtySuffix := C.CString(opts.DirtySuffix) + defer C.free(unsafe.Pointer(cDirtySuffix)) + + cFormatOpts = &C.git_describe_format_options{ + version: C.GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, + abbreviated_size: C.uint(opts.AbbreviatedSize), + always_use_long_format: cbool(opts.AlwaysUseLongFormat), + dirty_suffix: cDirtySuffix, + } + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + ecode := C.git_describe_format(&resultBuf, result.ptr, cFormatOpts) + if ecode < 0 { + return "", MakeGitError(ecode) + } + defer C.git_buf_free(&resultBuf) + + return C.GoString(resultBuf.ptr), nil +} + +// Free cleans up the C reference. +func (result *DescribeResult) Free() { + runtime.SetFinalizer(result, nil) + C.git_describe_result_free(result.ptr) + result.ptr = nil +} diff --git a/describe_test.go b/describe_test.go new file mode 100644 index 0000000..25af107 --- /dev/null +++ b/describe_test.go @@ -0,0 +1,106 @@ +package git + +import ( + "path" + "runtime" + "strings" + "testing" +) + +func TestDescribeCommit(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + describeOpts, err := DefaultDescribeOptions() + checkFatal(t, err) + + formatOpts, err := DefaultDescribeFormatOptions() + checkFatal(t, err) + + commitID, _ := seedTestRepo(t, repo) + + commit, err := repo.LookupCommit(commitID) + checkFatal(t, err) + + // No annotated tags can be used to describe master + _, err = commit.Describe(&describeOpts) + checkDescribeNoRefsFound(t, err) + + // Fallback + fallback := describeOpts + fallback.ShowCommitOidAsFallback = true + result, err := commit.Describe(&fallback) + checkFatal(t, err) + resultStr, err := result.Format(&formatOpts) + checkFatal(t, err) + compareStrings(t, "473bf77", resultStr) + + // Abbreviated + abbreviated := formatOpts + abbreviated.AbbreviatedSize = 2 + result, err = commit.Describe(&fallback) + checkFatal(t, err) + resultStr, err = result.Format(&abbreviated) + checkFatal(t, err) + compareStrings(t, "473b", resultStr) + + createTestTag(t, repo, commit) + + // Exact tag + patternOpts := describeOpts + patternOpts.Pattern = "v[0-9]*" + result, err = commit.Describe(&patternOpts) + checkFatal(t, err) + resultStr, err = result.Format(&formatOpts) + checkFatal(t, err) + compareStrings(t, "v0.0.0", resultStr) + + // Pattern no match + patternOpts.Pattern = "v[1-9]*" + result, err = commit.Describe(&patternOpts) + checkDescribeNoRefsFound(t, err) + + commitID, _ = updateReadme(t, repo, "update1") + commit, err = repo.LookupCommit(commitID) + checkFatal(t, err) + + // Tag-1 + result, err = commit.Describe(&describeOpts) + checkFatal(t, err) + resultStr, err = result.Format(&formatOpts) + checkFatal(t, err) + compareStrings(t, "v0.0.0-1-gd88ef8d", resultStr) + + // Strategy: All + describeOpts.Strategy = DescribeAll + result, err = commit.Describe(&describeOpts) + checkFatal(t, err) + resultStr, err = result.Format(&formatOpts) + checkFatal(t, err) + compareStrings(t, "heads/master", resultStr) + + repo.CreateBranch("hotfix", commit, false) + + // Workdir (branch) + result, err = repo.DescribeWorkdir(&describeOpts) + checkFatal(t, err) + resultStr, err = result.Format(&formatOpts) + checkFatal(t, err) + compareStrings(t, "heads/hotfix", resultStr) +} + +func checkDescribeNoRefsFound(t *testing.T, err error) { + // The failure happens at wherever we were called, not here + _, file, line, ok := runtime.Caller(1) + if !ok { + t.Fatalf("Unable to get caller") + } + if err == nil || !strings.Contains(err.Error(), "No reference found, cannot describe anything") { + t.Fatalf( + "%s:%v: was expecting error 'No reference found, cannot describe anything', got %v", + path.Base(file), + line, + err, + ) + } +} From 17950c198b0d1495091a5784e454a4ca9800e927 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Sat, 1 Aug 2015 14:28:20 +0200 Subject: [PATCH 29/36] Add ability to peel any git object Includes support for 'git_object_peel'. --- object.go | 26 ++++++++++++++++++++++ object_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/object.go b/object.go index 20cee85..6ecebf8 100644 --- a/object.go +++ b/object.go @@ -22,6 +22,7 @@ type Object interface { Id() *Oid Type() ObjectType Owner() *Repository + Peel(t ObjectType) (Object, error) } type gitObject struct { @@ -69,6 +70,31 @@ func (o *gitObject) Free() { C.git_object_free(o.ptr) } +// Peel recursively peels an object until an object of the specified type is met. +// +// If the query cannot be satisfied due to the object model, ErrInvalidSpec +// will be returned (e.g. trying to peel a blob to a tree). +// +// If you pass ObjectAny as the target type, then the object will be peeled +// until the type changes. A tag will be peeled until the referenced object +// is no longer a tag, and a commit will be peeled to a tree. Any other object +// type will return ErrInvalidSpec. +// +// If peeling a tag we discover an object which cannot be peeled to the target +// type due to the object model, an error will be returned. +func (o *gitObject) Peel(t ObjectType) (Object, error) { + var cobj *C.git_object + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := C.git_object_peel(&cobj, o.ptr, C.git_otype(t)); err < 0 { + return nil, MakeGitError(err) + } + + return allocObject(cobj, o.repo), nil +} + func allocObject(cobj *C.git_object, repo *Repository) Object { obj := gitObject{ ptr: cobj, diff --git a/object_test.go b/object_test.go index aa295e5..ef6c5a1 100644 --- a/object_test.go +++ b/object_test.go @@ -102,3 +102,63 @@ func TestObjectOwner(t *testing.T) { checkOwner(t, repo, commit) checkOwner(t, repo, tree) } + +func TestObjectPeel(t *testing.T) { + repo := createTestRepo(t) + defer cleanupTestRepo(t, repo) + + commitID, treeID := seedTestRepo(t, repo) + + var obj Object + + commit, err := repo.LookupCommit(commitID) + checkFatal(t, err) + + obj, err = commit.Peel(ObjectAny) + checkFatal(t, err) + + if obj.Type() != ObjectTree { + t.Fatalf("Wrong object type when peeling a commit, expected tree, have %v", obj.Type()) + } + + obj, err = commit.Peel(ObjectTag) + + if !IsErrorCode(err, ErrInvalidSpec) { + t.Fatalf("Wrong error when peeling a commit to a tag, expected ErrInvalidSpec, have %v", err) + } + + tree, err := repo.LookupTree(treeID) + checkFatal(t, err) + + obj, err = tree.Peel(ObjectAny) + + if !IsErrorCode(err, ErrInvalidSpec) { + t.Fatalf("Wrong error when peeling a tree, expected ErrInvalidSpec, have %v", err) + } + + entry := tree.EntryByName("README") + + blob, err := repo.LookupBlob(entry.Id) + checkFatal(t, err) + + obj, err = blob.Peel(ObjectAny) + + if !IsErrorCode(err, ErrInvalidSpec) { + t.Fatalf("Wrong error when peeling a blob, expected ErrInvalidSpec, have %v", err) + } + + tagID := createTestTag(t, repo, commit) + + tag, err := repo.LookupTag(tagID) + checkFatal(t, err) + + obj, err = tag.Peel(ObjectAny) + checkFatal(t, err) + + if obj.Type() != ObjectCommit { + t.Fatalf("Wrong object type when peeling a tag, expected commit, have %v", obj.Type()) + } + + // TODO: Should test a tag that annotates a different object than a commit + // but it's impossible at the moment to tag such an object. +} From ff6d4a7dfde18dcf330143980c66f426518b8061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 4 Aug 2015 15:01:17 +0200 Subject: [PATCH 30/36] We do require sudo on Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fb080b9..209d89f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: go -sudo: false +sudo: required install: ./script/install-libgit2.sh From a572b15df69c4b0350a1134045a7e7a66b06de21 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Mon, 31 Aug 2015 12:42:34 +0200 Subject: [PATCH 31/36] Add back support for RemoteCallbacks in Remote.Push() --- remote.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/remote.go b/remote.go index 6f3c9ea..e1e4845 100644 --- a/remote.go +++ b/remote.go @@ -153,6 +153,9 @@ type HostkeyCertificate struct { } type PushOptions struct { + // Callbacks to use for this push operation + RemoteCallbacks RemoteCallbacks + PbParallelism uint } @@ -591,6 +594,17 @@ func populateFetchOptions(options *C.git_fetch_options, opts *FetchOptions) { options.download_tags = C.git_remote_autotag_option_t(opts.DownloadTags) } +func populatePushOptions(options *C.git_push_options, opts *PushOptions) { + C.git_push_init_options(options, C.GIT_PUSH_OPTIONS_VERSION) + if opts == nil { + return + } + + options.pb_parallelism = C.uint(opts.PbParallelism) + + populateRemoteCallbacks(&options.callbacks, &opts.RemoteCallbacks) +} + // Fetch performs a fetch operation. refspecs specifies which refspecs // to use for this fetch, use an empty list to use the refspecs from // the configuration; msg specifies what to use for the reflog @@ -689,22 +703,19 @@ func (o *Remote) Ls(filterRefs ...string) ([]RemoteHead, error) { } func (o *Remote) Push(refspecs []string, opts *PushOptions) error { - 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) + var coptions C.git_push_options + populatePushOptions(&coptions, opts) + defer untrackCalbacksPayload(&coptions.callbacks) + runtime.LockOSThread() defer runtime.UnlockOSThread() - defer untrackCalbacksPayload(&copts.callbacks) - ret := C.git_remote_push(o.ptr, &crefspecs, &copts) + ret := C.git_remote_push(o.ptr, &crefspecs, &coptions) if ret < 0 { return MakeGitError(ret) } From 803ef7dad548b1b84c86365d6b61c8b6b4a0cce0 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Mon, 24 Aug 2015 21:37:09 +0200 Subject: [PATCH 32/36] Add nil check on CredentialsCallback wrapper --- remote.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/remote.go b/remote.go index e1e4845..b2fb96f 100644 --- a/remote.go +++ b/remote.go @@ -214,7 +214,9 @@ func credentialsCallback(_cred **C.git_cred, _url *C.char, _username_from_url *C url := C.GoString(_url) username_from_url := C.GoString(_username_from_url) ret, cred := callbacks.CredentialsCallback(url, username_from_url, (CredType)(allowed_types)) - *_cred = cred.ptr + if cred != nil { + *_cred = cred.ptr + } return int(ret) } From 337f25d47e9841ea9c5e1cf6bc25720c08538a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 31 Aug 2015 16:05:48 +0200 Subject: [PATCH 33/36] Remove the vendored libgit2 submodule This is a left-over from the merge from 'next'. --- vendor/libgit2 | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vendor/libgit2 diff --git a/vendor/libgit2 b/vendor/libgit2 deleted file mode 160000 index fb84cde..0000000 --- a/vendor/libgit2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fb84cde81e11947add4ff8bb9b4084f7d76e6567 From 1ea99658246673770b21f8848e257132a29e78d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 31 Aug 2015 14:01:06 +0200 Subject: [PATCH 34/36] Install v23 on Travis --- script/install-libgit2.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/script/install-libgit2.sh b/script/install-libgit2.sh index a6c3202..9bf6b37 100755 --- a/script/install-libgit2.sh +++ b/script/install-libgit2.sh @@ -13,9 +13,9 @@ if [ "x$TRAVIS_BRANCH" = "xnext" ]; then fi cd "${HOME}" -wget -O libgit2-0.22.3.tar.gz https://github.com/libgit2/libgit2/archive/v0.22.1.tar.gz -tar -xzvf libgit2-0.22.3.tar.gz -cd libgit2-0.22.1 && mkdir build && cd build +wget -O libgit2-0.23.1.tar.gz https://github.com/libgit2/libgit2/archive/v0.23.1.tar.gz +tar -xzvf libgit2-0.23.1.tar.gz +cd libgit2-0.23.1 && mkdir build && cd build cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_BUILD_TYPE="RelWithDebInfo" .. && make && sudo make install sudo ldconfig cd "${TRAVIS_BUILD_DIR}" From 2743bbfca3963e2a1cc21513b7ab9e8e8af70bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 31 Aug 2015 15:36:20 +0200 Subject: [PATCH 35/36] Test against Go 1.5 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 209d89f..f796389 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ go: - 1.2 - 1.3 - 1.4 + - 1.5 - tip matrix: From 4090c401c8bf3f062e898f75bde01e2ef27b3911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 31 Aug 2015 16:05:54 +0200 Subject: [PATCH 36/36] Don't call the finalizer on a borrowed repository When libgit2 gives us the repository for us to create the remote in, we do not own it, so we must make sure we don't try to free it. --- clone.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/clone.go b/clone.go index b8579b5..e80d14d 100644 --- a/clone.go +++ b/clone.go @@ -55,15 +55,16 @@ func remoteCreateCallback(cremote unsafe.Pointer, crepo unsafe.Pointer, cname, c name := C.GoString(cname) url := C.GoString(curl) repo := newRepositoryFromC((*C.git_repository)(crepo)) + // We don't own this repository, so make sure we don't try to free it + runtime.SetFinalizer(repo, nil) if opts, ok := pointerHandles.Get(payload).(CloneOptions); ok { remote, err := opts.RemoteCreateCallback(repo, name, url) + // clear finalizer as the calling C function will + // free the remote itself + runtime.SetFinalizer(remote, nil) if err == ErrOk && remote != nil { - // clear finalizer as the calling C function will - // free the remote itself - runtime.SetFinalizer(remote, nil) - cptr := (**C.git_remote)(cremote) *cptr = remote.ptr } else if err == ErrOk && remote == nil {