Compare commits
11 Commits
main
...
cherry-pic
Author | SHA1 | Date |
---|---|---|
|
7c1ecf79b4 | |
|
a53cd8b512 | |
|
a7dc7d41b9 | |
|
8230ed934e | |
|
9dfdab765a | |
|
a3f32b93cd | |
|
a964575453 | |
|
a6446ac047 | |
|
3033505879 | |
|
bacde67336 | |
|
37d4d3ddf8 |
|
@ -63,6 +63,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
libgit2:
|
libgit2:
|
||||||
- '109b4c887ffb63962c7017a66fc4a1f48becb48e' # v1.2.0 with a fixed symbol
|
- '109b4c887ffb63962c7017a66fc4a1f48becb48e' # v1.2.0 with a fixed symbol
|
||||||
|
- 'v1.3.0'
|
||||||
name: Go (system-wide, dynamic)
|
name: Go (system-wide, dynamic)
|
||||||
|
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
@ -105,3 +106,26 @@ jobs:
|
||||||
sudo ./script/build-libgit2.sh --static --system
|
sudo ./script/build-libgit2.sh --static --system
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test --count=1 --tags "static,system_libgit2" ./...
|
run: go test --count=1 --tags "static,system_libgit2" ./...
|
||||||
|
|
||||||
|
check-generate:
|
||||||
|
name: Check generated files were not modified
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: '1.17'
|
||||||
|
id: go
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Install libgit2 build dependencies
|
||||||
|
run: |
|
||||||
|
git submodule update --init
|
||||||
|
sudo apt-get install -y --no-install-recommends libssh2-1-dev
|
||||||
|
go install golang.org/x/tools/cmd/stringer@latest
|
||||||
|
- name: Generate files
|
||||||
|
run: |
|
||||||
|
export PATH=$(go env GOPATH)/bin:$PATH
|
||||||
|
make generate
|
||||||
|
- name: Check nothing changed
|
||||||
|
run: git diff --quiet --exit-code || (echo "detected changes after generate" ; git status ; exit 1)
|
||||||
|
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Bump version and push tag
|
- name: Bump version and push tag
|
||||||
id: bump-version
|
id: bump-version
|
||||||
uses: anothrNick/github-tag-action@9aaabdb5e989894e95288328d8b17a6347217ae3
|
uses: anothrNick/github-tag-action@43ed073f5c1445ca8b80d920ce2f8fa550ae4e8d
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
WITH_V: true
|
WITH_V: true
|
||||||
|
|
|
@ -10,8 +10,8 @@ package git
|
||||||
#cgo CFLAGS: -DLIBGIT2_STATIC
|
#cgo CFLAGS: -DLIBGIT2_STATIC
|
||||||
#include <git2.h>
|
#include <git2.h>
|
||||||
|
|
||||||
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 2 || LIBGIT2_VER_MINOR > 2
|
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 2 || LIBGIT2_VER_MINOR > 3
|
||||||
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.2.0 and v1.2.0"
|
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.2.0 and v1.3.0"
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
|
@ -8,8 +8,8 @@ package git
|
||||||
#cgo CFLAGS: -DLIBGIT2_DYNAMIC
|
#cgo CFLAGS: -DLIBGIT2_DYNAMIC
|
||||||
#include <git2.h>
|
#include <git2.h>
|
||||||
|
|
||||||
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 2 || LIBGIT2_VER_MINOR > 2
|
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 2 || LIBGIT2_VER_MINOR > 3
|
||||||
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.2.0 and v1.2.0"
|
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.2.0 and v1.3.0"
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
|
@ -8,8 +8,8 @@ package git
|
||||||
#cgo CFLAGS: -DLIBGIT2_STATIC
|
#cgo CFLAGS: -DLIBGIT2_STATIC
|
||||||
#include <git2.h>
|
#include <git2.h>
|
||||||
|
|
||||||
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 2 || LIBGIT2_VER_MINOR > 2
|
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 2 || LIBGIT2_VER_MINOR > 3
|
||||||
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.2.0 and v1.2.0"
|
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.2.0 and v1.3.0"
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -2,6 +2,10 @@ TEST_ARGS ?= --count=1
|
||||||
|
|
||||||
default: test
|
default: test
|
||||||
|
|
||||||
|
|
||||||
|
generate: static-build/install/lib/libgit2.a
|
||||||
|
go generate --tags "static" ./...
|
||||||
|
|
||||||
# System library
|
# System library
|
||||||
# ==============
|
# ==============
|
||||||
# This uses whatever version of libgit2 can be found in the system.
|
# This uses whatever version of libgit2 can be found in the system.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
git2go
|
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=main)](https://travis-ci.org/libgit2/git2go)
|
[![GoDoc](https://godoc.org/github.com/libgit2/git2go?status.svg)](http://godoc.org/github.com/libgit2/git2go/v32) [![Build Status](https://travis-ci.org/libgit2/git2go.svg?branch=main)](https://travis-ci.org/libgit2/git2go)
|
||||||
|
|
||||||
Go bindings for [libgit2](http://libgit2.github.com/).
|
Go bindings for [libgit2](http://libgit2.github.com/).
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ In order to let Go pass the correct flags to `pkg-config`, `-tags static` needs
|
||||||
|
|
||||||
One thing to take into account is that since Go expects the `pkg-config` file to be within the same directory where `make install-static` was called, so the `go.mod` file may need to have a [`replace` directive](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) so that the correct setup is achieved. So if `git2go` is checked out at `$GOPATH/src/github.com/libgit2/git2go` and your project at `$GOPATH/src/github.com/my/project`, the `go.mod` file of `github.com/my/project` might need to have a line like
|
One thing to take into account is that since Go expects the `pkg-config` file to be within the same directory where `make install-static` was called, so the `go.mod` file may need to have a [`replace` directive](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) so that the correct setup is achieved. So if `git2go` is checked out at `$GOPATH/src/github.com/libgit2/git2go` and your project at `$GOPATH/src/github.com/my/project`, the `go.mod` file of `github.com/my/project` might need to have a line like
|
||||||
|
|
||||||
replace github.com/libgit2/git2go/v32 ../../libgit2/git2go
|
replace github.com/libgit2/git2go/v32 => ../../libgit2/git2go
|
||||||
|
|
||||||
Parallelism and network operations
|
Parallelism and network operations
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
|
@ -2,6 +2,7 @@ package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,3 +71,17 @@ func TestCloneWithCallback(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer remote.Free()
|
defer remote.Free()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCloneWithExternalHTTPUrl
|
||||||
|
func TestCloneWithExternalHTTPUrl(t *testing.T) {
|
||||||
|
|
||||||
|
path, err := ioutil.TempDir("", "git2go")
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
// clone the repo
|
||||||
|
url := "https://github.com/libgit2/TestGitRepository"
|
||||||
|
_, err = Clone(url, path, &CloneOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("cannot clone remote repo via https, error: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2
git.go
2
git.go
|
@ -14,6 +14,7 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate stringer -type ErrorClass -trimprefix ErrorClass -tags static
|
||||||
type ErrorClass int
|
type ErrorClass int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -48,6 +49,7 @@ const (
|
||||||
ErrorClassPatch ErrorClass = C.GIT_ERROR_PATCH
|
ErrorClassPatch ErrorClass = C.GIT_ERROR_PATCH
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate stringer -type ErrorCode -trimprefix ErrorCode -tags static
|
||||||
type ErrorCode int
|
type ErrorCode int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
29
rebase.go
29
rebase.go
|
@ -318,7 +318,7 @@ func (r *Repository) InitRebase(branch *AnnotatedCommit, upstream *AnnotatedComm
|
||||||
return nil, MakeGitError(ret)
|
return nil, MakeGitError(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newRebaseFromC(ptr, cOpts), nil
|
return newRebaseFromC(ptr, r, cOpts), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenRebase opens an existing rebase that was previously started by either an invocation of InitRebase or by another client.
|
// OpenRebase opens an existing rebase that was previously started by either an invocation of InitRebase or by another client.
|
||||||
|
@ -340,7 +340,7 @@ func (r *Repository) OpenRebase(opts *RebaseOptions) (*Rebase, error) {
|
||||||
return nil, MakeGitError(ret)
|
return nil, MakeGitError(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newRebaseFromC(ptr, cOpts), nil
|
return newRebaseFromC(ptr, r, cOpts), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OperationAt gets the rebase operation specified by the given index.
|
// OperationAt gets the rebase operation specified by the given index.
|
||||||
|
@ -392,6 +392,27 @@ func (rebase *Rebase) Next() (*RebaseOperation, error) {
|
||||||
return newRebaseOperationFromC(ptr), nil
|
return newRebaseOperationFromC(ptr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InmemoryIndex gets the index produced by the last operation, which is the
|
||||||
|
// result of `Next()` and which will be committed by the next invocation of
|
||||||
|
// `Commit()`. This is useful for resolving conflicts in an in-memory rebase
|
||||||
|
// before committing them.
|
||||||
|
//
|
||||||
|
// This is only applicable for in-memory rebases; for rebases within a working
|
||||||
|
// directory, the changes were applied to the repository's index.
|
||||||
|
func (rebase *Rebase) InmemoryIndex() (*Index, error) {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
var ptr *C.git_index
|
||||||
|
err := C.git_rebase_inmemory_index(&ptr, rebase.ptr)
|
||||||
|
runtime.KeepAlive(rebase)
|
||||||
|
if err < 0 {
|
||||||
|
return nil, MakeGitError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newIndexFromC(ptr, rebase.r), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Commit commits the current patch.
|
// Commit commits the current patch.
|
||||||
// You must have resolved any conflicts that were introduced during the patch application from the Next() invocation.
|
// You must have resolved any conflicts that were introduced during the patch application from the Next() invocation.
|
||||||
func (rebase *Rebase) Commit(ID *Oid, author, committer *Signature, message string) error {
|
func (rebase *Rebase) Commit(ID *Oid, author, committer *Signature, message string) error {
|
||||||
|
@ -457,8 +478,8 @@ func (r *Rebase) Free() {
|
||||||
freeRebaseOptions(r.options)
|
freeRebaseOptions(r.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRebaseFromC(ptr *C.git_rebase, opts *C.git_rebase_options) *Rebase {
|
func newRebaseFromC(ptr *C.git_rebase, repo *Repository, opts *C.git_rebase_options) *Rebase {
|
||||||
rebase := &Rebase{ptr: ptr, options: opts}
|
rebase := &Rebase{ptr: ptr, r: repo, options: opts}
|
||||||
runtime.SetFinalizer(rebase, (*Rebase).Free)
|
runtime.SetFinalizer(rebase, (*Rebase).Free)
|
||||||
return rebase
|
return rebase
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,80 @@ import (
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
|
|
||||||
|
func TestRebaseInMemoryWithConflict(t *testing.T) {
|
||||||
|
repo := createTestRepo(t)
|
||||||
|
defer cleanupTestRepo(t, repo)
|
||||||
|
seedTestRepo(t, repo)
|
||||||
|
|
||||||
|
// Create two branches with common history, where both modify "common-file"
|
||||||
|
// in a conflicting way.
|
||||||
|
_, err := commitSomething(repo, "common-file", "a\nb\nc\n", commitOptions{})
|
||||||
|
checkFatal(t, err)
|
||||||
|
checkFatal(t, createBranch(repo, "branch-a"))
|
||||||
|
checkFatal(t, createBranch(repo, "branch-b"))
|
||||||
|
|
||||||
|
checkFatal(t, repo.SetHead("refs/heads/branch-a"))
|
||||||
|
_, err = commitSomething(repo, "common-file", "1\nb\nc\n", commitOptions{})
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
checkFatal(t, repo.SetHead("refs/heads/branch-b"))
|
||||||
|
_, err = commitSomething(repo, "common-file", "x\nb\nc\n", commitOptions{})
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
branchA, err := repo.LookupBranch("branch-a", BranchLocal)
|
||||||
|
checkFatal(t, err)
|
||||||
|
onto, err := repo.AnnotatedCommitFromRef(branchA.Reference)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
// We then rebase "branch-b" onto "branch-a" in-memory, which should result
|
||||||
|
// in a conflict.
|
||||||
|
rebase, err := repo.InitRebase(nil, nil, onto, &RebaseOptions{InMemory: 1})
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
_, err = rebase.Next()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
index, err := rebase.InmemoryIndex()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
// We simply resolve the conflict and commit the rebase.
|
||||||
|
if !index.HasConflicts() {
|
||||||
|
t.Fatal("expected index to have conflicts")
|
||||||
|
}
|
||||||
|
|
||||||
|
conflict, err := index.Conflict("common-file")
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
resolvedBlobID, err := repo.CreateBlobFromBuffer([]byte("resolved contents"))
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
resolvedEntry := *conflict.Our
|
||||||
|
resolvedEntry.Id = resolvedBlobID
|
||||||
|
checkFatal(t, index.Add(&resolvedEntry))
|
||||||
|
checkFatal(t, index.RemoveConflict("common-file"))
|
||||||
|
|
||||||
|
var commitID Oid
|
||||||
|
checkFatal(t, rebase.Commit(&commitID, signature(), signature(), "rebased message"))
|
||||||
|
checkFatal(t, rebase.Finish())
|
||||||
|
|
||||||
|
// And then assert that we can look up the new merge commit, and that the
|
||||||
|
// "common-file" has the expected contents.
|
||||||
|
commit, err := repo.LookupCommit(&commitID)
|
||||||
|
checkFatal(t, err)
|
||||||
|
if commit.Message() != "rebased message" {
|
||||||
|
t.Fatalf("unexpected commit message %q", commit.Message())
|
||||||
|
}
|
||||||
|
|
||||||
|
tree, err := commit.Tree()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
blob, err := repo.LookupBlob(tree.EntryByName("common-file").Id)
|
||||||
|
checkFatal(t, err)
|
||||||
|
if string(blob.Contents()) != "resolved contents" {
|
||||||
|
t.Fatalf("unexpected resolved blob contents %q", string(blob.Contents()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRebaseAbort(t *testing.T) {
|
func TestRebaseAbort(t *testing.T) {
|
||||||
// TEST DATA
|
// TEST DATA
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <git2.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Refspec struct {
|
||||||
|
doNotCompare
|
||||||
|
ptr *C.git_refspec
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRefspec parses a given refspec string
|
||||||
|
func ParseRefspec(input string, isFetch bool) (*Refspec, error) {
|
||||||
|
var ptr *C.git_refspec
|
||||||
|
|
||||||
|
cinput := C.CString(input)
|
||||||
|
defer C.free(unsafe.Pointer(cinput))
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_refspec_parse(&ptr, cinput, cbool(isFetch))
|
||||||
|
if ret < 0 {
|
||||||
|
return nil, MakeGitError(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := &Refspec{ptr: ptr}
|
||||||
|
runtime.SetFinalizer(spec, (*Refspec).Free)
|
||||||
|
return spec, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free releases a refspec object which has been created by ParseRefspec
|
||||||
|
func (s *Refspec) Free() {
|
||||||
|
runtime.SetFinalizer(s, nil)
|
||||||
|
C.git_refspec_free(s.ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direction returns the refspec's direction
|
||||||
|
func (s *Refspec) Direction() ConnectDirection {
|
||||||
|
direction := C.git_refspec_direction(s.ptr)
|
||||||
|
return ConnectDirection(direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Src returns the refspec's source specifier
|
||||||
|
func (s *Refspec) Src() string {
|
||||||
|
var ret string
|
||||||
|
cstr := C.git_refspec_src(s.ptr)
|
||||||
|
|
||||||
|
if cstr != nil {
|
||||||
|
ret = C.GoString(cstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.KeepAlive(s)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dst returns the refspec's destination specifier
|
||||||
|
func (s *Refspec) Dst() string {
|
||||||
|
var ret string
|
||||||
|
cstr := C.git_refspec_dst(s.ptr)
|
||||||
|
|
||||||
|
if cstr != nil {
|
||||||
|
ret = C.GoString(cstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.KeepAlive(s)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force returns the refspec's force-update setting
|
||||||
|
func (s *Refspec) Force() bool {
|
||||||
|
force := C.git_refspec_force(s.ptr)
|
||||||
|
return force != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the refspec's string representation
|
||||||
|
func (s *Refspec) String() string {
|
||||||
|
var ret string
|
||||||
|
cstr := C.git_refspec_string(s.ptr)
|
||||||
|
|
||||||
|
if cstr != nil {
|
||||||
|
ret = C.GoString(cstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.KeepAlive(s)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// SrcMatches checks if a refspec's source descriptor matches a reference
|
||||||
|
func (s *Refspec) SrcMatches(refname string) bool {
|
||||||
|
cname := C.CString(refname)
|
||||||
|
defer C.free(unsafe.Pointer(cname))
|
||||||
|
|
||||||
|
matches := C.git_refspec_src_matches(s.ptr, cname)
|
||||||
|
return matches != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SrcMatches checks if a refspec's destination descriptor matches a reference
|
||||||
|
func (s *Refspec) DstMatches(refname string) bool {
|
||||||
|
cname := C.CString(refname)
|
||||||
|
defer C.free(unsafe.Pointer(cname))
|
||||||
|
|
||||||
|
matches := C.git_refspec_dst_matches(s.ptr, cname)
|
||||||
|
return matches != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform a reference to its target following the refspec's rules
|
||||||
|
func (s *Refspec) Transform(refname string) (string, error) {
|
||||||
|
buf := C.git_buf{}
|
||||||
|
|
||||||
|
cname := C.CString(refname)
|
||||||
|
defer C.free(unsafe.Pointer(cname))
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_refspec_transform(&buf, s.ptr, cname)
|
||||||
|
if ret < 0 {
|
||||||
|
return "", MakeGitError(ret)
|
||||||
|
}
|
||||||
|
defer C.git_buf_dispose(&buf)
|
||||||
|
|
||||||
|
return C.GoString(buf.ptr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rtransform converts a target reference to its source reference following the
|
||||||
|
// refspec's rules
|
||||||
|
func (s *Refspec) Rtransform(refname string) (string, error) {
|
||||||
|
buf := C.git_buf{}
|
||||||
|
|
||||||
|
cname := C.CString(refname)
|
||||||
|
defer C.free(unsafe.Pointer(cname))
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_refspec_rtransform(&buf, s.ptr, cname)
|
||||||
|
if ret < 0 {
|
||||||
|
return "", MakeGitError(ret)
|
||||||
|
}
|
||||||
|
defer C.git_buf_dispose(&buf)
|
||||||
|
|
||||||
|
return C.GoString(buf.ptr), nil
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRefspec(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const (
|
||||||
|
input = "+refs/heads/*:refs/remotes/origin/*"
|
||||||
|
mainLocal = "refs/heads/main"
|
||||||
|
mainRemote = "refs/remotes/origin/main"
|
||||||
|
)
|
||||||
|
|
||||||
|
refspec, err := ParseRefspec(input, true)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
// Accessors
|
||||||
|
|
||||||
|
s := refspec.String()
|
||||||
|
if s != input {
|
||||||
|
t.Errorf("expected string %q, got %q", input, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d := refspec.Direction(); d != ConnectDirectionFetch {
|
||||||
|
t.Errorf("expected fetch refspec, got direction %v", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pat, expected := refspec.Src(), "refs/heads/*"; pat != expected {
|
||||||
|
t.Errorf("expected refspec src %q, got %q", expected, pat)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pat, expected := refspec.Dst(), "refs/remotes/origin/*"; pat != expected {
|
||||||
|
t.Errorf("expected refspec dst %q, got %q", expected, pat)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !refspec.Force() {
|
||||||
|
t.Error("expected refspec force flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SrcMatches
|
||||||
|
|
||||||
|
if !refspec.SrcMatches(mainLocal) {
|
||||||
|
t.Errorf("refspec source did not match %q", mainLocal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if refspec.SrcMatches("refs/tags/v1.0") {
|
||||||
|
t.Error("refspec source matched under refs/tags")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DstMatches
|
||||||
|
|
||||||
|
if !refspec.DstMatches(mainRemote) {
|
||||||
|
t.Errorf("refspec destination did not match %q", mainRemote)
|
||||||
|
}
|
||||||
|
|
||||||
|
if refspec.DstMatches("refs/tags/v1.0") {
|
||||||
|
t.Error("refspec destination matched under refs/tags")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transforms
|
||||||
|
|
||||||
|
fromLocal, err := refspec.Transform(mainLocal)
|
||||||
|
checkFatal(t, err)
|
||||||
|
if fromLocal != mainRemote {
|
||||||
|
t.Errorf("transform by refspec returned %s; expected %s", fromLocal, mainRemote)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromRemote, err := refspec.Rtransform(mainRemote)
|
||||||
|
checkFatal(t, err)
|
||||||
|
if fromRemote != mainLocal {
|
||||||
|
t.Errorf("rtransform by refspec returned %s; expected %s", fromRemote, mainLocal)
|
||||||
|
}
|
||||||
|
}
|
20
remote.go
20
remote.go
|
@ -182,6 +182,9 @@ type Remote struct {
|
||||||
ptr *C.git_remote
|
ptr *C.git_remote
|
||||||
callbacks RemoteCallbacks
|
callbacks RemoteCallbacks
|
||||||
repo *Repository
|
repo *Repository
|
||||||
|
// weak indicates that a remote is a weak pointer and should not be
|
||||||
|
// freed.
|
||||||
|
weak bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type remotePointerList struct {
|
type remotePointerList struct {
|
||||||
|
@ -288,6 +291,9 @@ type PushOptions struct {
|
||||||
|
|
||||||
// Headers are extra headers for the push operation.
|
// Headers are extra headers for the push operation.
|
||||||
Headers []string
|
Headers []string
|
||||||
|
|
||||||
|
// Proxy options to use for this push operation
|
||||||
|
ProxyOptions ProxyOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoteHead struct {
|
type RemoteHead struct {
|
||||||
|
@ -602,6 +608,9 @@ func (r *Remote) free() {
|
||||||
// Free releases the resources of the Remote.
|
// Free releases the resources of the Remote.
|
||||||
func (r *Remote) Free() {
|
func (r *Remote) Free() {
|
||||||
r.repo.Remotes.untrackRemote(r)
|
r.repo.Remotes.untrackRemote(r)
|
||||||
|
if r.weak {
|
||||||
|
return
|
||||||
|
}
|
||||||
r.free()
|
r.free()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -995,6 +1004,7 @@ func populatePushOptions(copts *C.git_push_options, opts *PushOptions, errorTarg
|
||||||
strings: makeCStringsFromStrings(opts.Headers),
|
strings: makeCStringsFromStrings(opts.Headers),
|
||||||
}
|
}
|
||||||
populateRemoteCallbacks(&copts.callbacks, &opts.RemoteCallbacks, errorTarget)
|
populateRemoteCallbacks(&copts.callbacks, &opts.RemoteCallbacks, errorTarget)
|
||||||
|
populateProxyOptions(&copts.proxy_opts, &opts.ProxyOptions)
|
||||||
return copts
|
return copts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1004,6 +1014,7 @@ func freePushOptions(copts *C.git_push_options) {
|
||||||
}
|
}
|
||||||
untrackCallbacksPayload(&copts.callbacks)
|
untrackCallbacksPayload(&copts.callbacks)
|
||||||
freeStrarray(&copts.custom_headers)
|
freeStrarray(&copts.custom_headers)
|
||||||
|
freeProxyOptions(&copts.proxy_opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch performs a fetch operation. refspecs specifies which refspecs
|
// Fetch performs a fetch operation. refspecs specifies which refspecs
|
||||||
|
@ -1231,3 +1242,12 @@ func freeRemoteCreateOptions(ptr *C.git_remote_create_options) {
|
||||||
C.free(unsafe.Pointer(ptr.name))
|
C.free(unsafe.Pointer(ptr.name))
|
||||||
C.free(unsafe.Pointer(ptr.fetchspec))
|
C.free(unsafe.Pointer(ptr.fetchspec))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createNewEmptyRemote used to get a new empty object of *Remote
|
||||||
|
func createNewEmptyRemote() *Remote {
|
||||||
|
return &Remote{
|
||||||
|
callbacks: RemoteCallbacks{},
|
||||||
|
repo: nil,
|
||||||
|
weak: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -101,6 +101,14 @@ func EnableStrictHashVerification(enabled bool) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EnableFsyncGitDir(enabled bool) error {
|
||||||
|
if enabled {
|
||||||
|
return setSizet(C.GIT_OPT_ENABLE_FSYNC_GITDIR, 1)
|
||||||
|
} else {
|
||||||
|
return setSizet(C.GIT_OPT_ENABLE_FSYNC_GITDIR, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func CachedMemory() (current int, allowed int, err error) {
|
func CachedMemory() (current int, allowed int, err error) {
|
||||||
return getSizetSizet(C.GIT_OPT_GET_CACHED_MEMORY)
|
return getSizetSizet(C.GIT_OPT_GET_CACHED_MEMORY)
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,14 @@ func TestEnableStrictHashVerification(t *testing.T) {
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnableFsyncGitDir(t *testing.T) {
|
||||||
|
err := EnableFsyncGitDir(false)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
err = EnableFsyncGitDir(true)
|
||||||
|
checkFatal(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCachedMemory(t *testing.T) {
|
func TestCachedMemory(t *testing.T) {
|
||||||
current, allowed, err := CachedMemory()
|
current, allowed, err := CachedMemory()
|
||||||
checkFatal(t, err)
|
checkFatal(t, err)
|
||||||
|
|
12
ssh.go
12
ssh.go
|
@ -17,6 +17,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
@ -74,6 +75,13 @@ func (t *sshSmartSubtransport) Action(urlString string, action SmartServiceActio
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Escape \ and '.
|
||||||
|
uPath := strings.Replace(u.Path, `\`, `\\`, -1)
|
||||||
|
uPath = strings.Replace(uPath, `'`, `\'`, -1)
|
||||||
|
|
||||||
|
// TODO: Add percentage decode similar to libgit2.
|
||||||
|
// Refer: https://github.com/libgit2/libgit2/blob/358a60e1b46000ea99ef10b4dd709e92f75ff74b/src/str.c#L455-L481
|
||||||
|
|
||||||
var cmd string
|
var cmd string
|
||||||
switch action {
|
switch action {
|
||||||
case SmartServiceActionUploadpackLs, SmartServiceActionUploadpack:
|
case SmartServiceActionUploadpackLs, SmartServiceActionUploadpack:
|
||||||
|
@ -83,7 +91,7 @@ func (t *sshSmartSubtransport) Action(urlString string, action SmartServiceActio
|
||||||
}
|
}
|
||||||
t.Close()
|
t.Close()
|
||||||
}
|
}
|
||||||
cmd = fmt.Sprintf("git-upload-pack %q", u.Path)
|
cmd = fmt.Sprintf("git-upload-pack '%s'", uPath)
|
||||||
|
|
||||||
case SmartServiceActionReceivepackLs, SmartServiceActionReceivepack:
|
case SmartServiceActionReceivepackLs, SmartServiceActionReceivepack:
|
||||||
if t.currentStream != nil {
|
if t.currentStream != nil {
|
||||||
|
@ -92,7 +100,7 @@ func (t *sshSmartSubtransport) Action(urlString string, action SmartServiceActio
|
||||||
}
|
}
|
||||||
t.Close()
|
t.Close()
|
||||||
}
|
}
|
||||||
cmd = fmt.Sprintf("git-receive-pack %q", u.Path)
|
cmd = fmt.Sprintf("git-receive-pack '%s'", uPath)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unexpected action: %v", action)
|
return nil, fmt.Errorf("unexpected action: %v", action)
|
||||||
|
|
|
@ -22,7 +22,6 @@ void _go_git_setup_smart_subtransport_stream(_go_managed_smart_subtransport_stre
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -306,8 +305,10 @@ func smartTransportCallback(
|
||||||
registeredSmartTransport := pointerHandles.Get(handle).(*RegisteredSmartTransport)
|
registeredSmartTransport := pointerHandles.Get(handle).(*RegisteredSmartTransport)
|
||||||
remote, ok := remotePointers.get(owner)
|
remote, ok := remotePointers.get(owner)
|
||||||
if !ok {
|
if !ok {
|
||||||
err := errors.New("remote pointer not found")
|
// create a new empty remote and set it
|
||||||
return setCallbackError(errorMessage, err)
|
// as a weak pointer, so that control stays in golang
|
||||||
|
remote = createNewEmptyRemote()
|
||||||
|
remote.weak = true
|
||||||
}
|
}
|
||||||
|
|
||||||
managed := &managedSmartSubtransport{
|
managed := &managedSmartSubtransport{
|
||||||
|
|
Loading…
Reference in New Issue