Compare commits

..

2 Commits

Author SHA1 Message Date
Carlos Martín Nieto 16ef893af9 Rename the revwalk constructor Walk() -> NewRevWalk()
This makes it be more in line with Go conventions and uses its full
name.
2013-09-13 14:11:12 +02:00
Carlos Martín Nieto 56acff247b Do a basic test of the walker 2013-09-13 10:57:34 +02:00
112 changed files with 736 additions and 19038 deletions

4
.gitignore vendored
View File

@ -1,4 +0,0 @@
/static-build/
/dynamic-build/
go.*

View File

@ -1,17 +0,0 @@
//go:build static && !system_libgit2
// +build static,!system_libgit2
package git
/*
#cgo windows CFLAGS: -I${SRCDIR}/static-build/install/include/
#cgo windows LDFLAGS: -L${SRCDIR}/static-build/install/lib/ -lgit2 -lwinhttp -lws2_32 -lole32 -lrpcrt4 -lcrypt32
#cgo !windows pkg-config: --static ${SRCDIR}/static-build/install/lib/pkgconfig/libgit2.pc
#cgo CFLAGS: -DLIBGIT2_STATIC
#include <git2.h>
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 5 || LIBGIT2_VER_MINOR > 5
# error "Invalid libgit2 version; this libgit2 supports libgit2 between v1.5.0 and v1.5.0"
#endif
*/
import "C"

View File

@ -1,14 +0,0 @@
//go:build !static
// +build !static
package git
/*
#cgo pkg-config: libgit2
#cgo CFLAGS: -DLIBGIT2_DYNAMIC -I/opt/libgit2/include
#cgo LDFLAGS: -L/opt/libgit2 -lgit2
#include <git2.h>
*/
import "C"

View File

@ -1,15 +0,0 @@
//go:build static && system_libgit2
// +build static,system_libgit2
package git
/*
#cgo pkg-config: libgit2 --static
#cgo CFLAGS: -DLIBGIT2_STATIC
#include <git2.h>
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 5 || LIBGIT2_VER_MINOR > 5
# error "Invalid libgit2 version; this libgit2 supports libgit2 between v1.5.0 and v1.5.0"
#endif
*/
import "C"

View File

@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2013 The libgit2 contributors
Copyright (c) 2013 The git2go contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,70 +0,0 @@
TEST_ARGS ?= --count=1
PKG_CONFIG_PATH=/opt/libgit2/
default: goimports test
goimports:
goimports -w *.go
generate: static-build/install/lib/libgit2.a
go generate --tags "static" ./...
# System library
# ==============
# This uses whatever version of libgit2 can be found in the system.
test:
-go-mod-clean # go install go.wit.com/apps/go-mod-clean@latest
go run script/check-MakeGitError-thread-lock.go
LD_LIBRARY_PATH=/opt/libgit2 PKG_CONFIG_PATH=/opt/libgit2/ go test -v -x $(TEST_ARGS) ./...
add-remote:
git remote add git2go https://github.com/libgit2/git2go.git
install:
go install ./...
clean:
rm go.*
# Bundled dynamic library
# =======================
# In order to avoid having to manipulate `git_dynamic.go`, which would prevent
# the system-wide libgit2.so from being used in a sort of ergonomic way, this
# instead moves the complexity of overriding the paths so that the built
# libraries can be found by the build and tests.
.PHONY: build-libgit2-dynamic
build-libgit2-dynamic:
./script/build-libgit2-dynamic.sh
dynamic-build/install/lib/libgit2.so:
./script/build-libgit2-dynamic.sh
test-dynamic: dynamic-build/install/lib/libgit2.so
PKG_CONFIG_PATH=dynamic-build/install/lib/pkgconfig \
go run script/check-MakeGitError-thread-lock.go
PKG_CONFIG_PATH=dynamic-build/install/lib/pkgconfig \
LD_LIBRARY_PATH=dynamic-build/install/lib \
go test $(TEST_ARGS) ./...
install-dynamic: dynamic-build/install/lib/libgit2.so
PKG_CONFIG_PATH=dynamic-build/install/lib/pkgconfig \
go install ./...
# Bundled static library
# ======================
# This is mostly used in tests, but can also be used to provide a
# statically-linked library with the bundled version of libgit2.
.PHONY: build-libgit2-static
build-libgit2-static:
./script/build-libgit2-static.sh
static-build/install/lib/libgit2.a:
./script/build-libgit2-static.sh
test-static: static-build/install/lib/libgit2.a
go run script/check-MakeGitError-thread-lock.go
go test --tags "static" $(TEST_ARGS) ./...
install-static: static-build/install/lib/libgit2.a
go install --tags "static" ./...

View File

@ -1,69 +1,21 @@
GO libgit2
git2go
======
[![GoDoc](https://godoc.org/go.wit.com/lib/libgit2?status.svg)](http://godoc.org/go.wit.com/lib/libgit2) [![Build Status](https://travis-ci.org/libgit2/libgit2.svg?branch=main)](https://travis-ci.org/libgit2/libgit2)
Go bindings for [libgit2](http://libgit2.github.com/).
### Updated 2024/12/16
### Which Go version to use
* This package is updated to work against libgit2 version 1.8 on Debian sid
* There is one line commented out which needs to be fixed in remote.go
* some of the tests seem to run
```sh
go install go.wit.com/apps/go-clone@latest
go install go.wit.com/apps/go-mod-clean@latest
go-clone --recusive go.wit.com/lib/libgit2
```
### Which branch to send Pull requests to
TODO: not sure yet
Go bindings for [libgit2](http://libgit2.github.com/). These bindings are for top-of-the-branch libgit2, and they move fast, things may or may not work. Operator get me Beijing-jing-jing-jing!
Installing
----------
This project wraps the functionality provided by libgit2. It thus needs it in order to perform the work.
This project wraps the functionality provided by libgit2. If you're using a versioned branch, install it to your system via your system's package manager and then install libgit2.
### Versioned branch, dynamic linking
When linking dynamically against a released version of libgit2, install it via your system's package manager. CGo will take care of finding its pkg-config file and set up the linking. Import via Go modules, e.g. to work against libgit2 v1.2
```go
goimports -w *.go
```
Parallelism and network operations
----------------------------------
libgit2 may use OpenSSL and LibSSH2 for performing encrypted network connections. For now, libgit2 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 `main` branch, similarly to installing, running the tests requires building a local libgit2 library, so the Makefile provides a wrapper that makes sure it's built
make test-static
Alternatively, you can build the library manually first and then run the tests
make install-static
go test -v -tags static ./...
Just `go get github.com/libgit2/git2go`. You'll need to have top-of-the-branch libgit2 from development installed in your system and available via `pkg-config`. These bindings are in sync with the top of `development`.
License
-------
M to the I to the T. See the LICENSE file if you've never seen an MIT license before.
M to the I to the T. See the LICENSE file if you've never seen a MIT license before.
Authors
-------
- Carlos Martín (github@carlosmn)
- Vicent Martí (github@vmg)
- Carlos Martín (@carlosmn)
- Vicent Martí (@vmg)

166
blame.go
View File

@ -1,166 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
type BlameOptions struct {
Flags BlameOptionsFlag
MinMatchCharacters uint16
NewestCommit *Oid
OldestCommit *Oid
MinLine uint32
MaxLine uint32
}
func DefaultBlameOptions() (BlameOptions, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
opts := C.git_blame_options{}
ecode := C.git_blame_options_init(&opts, C.GIT_BLAME_OPTIONS_VERSION)
if ecode < 0 {
return BlameOptions{}, MakeGitError(ecode)
}
return BlameOptions{
Flags: BlameOptionsFlag(opts.flags),
MinMatchCharacters: uint16(opts.min_match_characters),
NewestCommit: newOidFromC(&opts.newest_commit),
OldestCommit: newOidFromC(&opts.oldest_commit),
MinLine: uint32(opts.min_line),
MaxLine: uint32(opts.max_line),
}, nil
}
type BlameOptionsFlag uint32
const (
BlameNormal BlameOptionsFlag = C.GIT_BLAME_NORMAL
BlameTrackCopiesSameFile BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_FILE
BlameTrackCopiesSameCommitMoves BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES
BlameTrackCopiesSameCommitCopies BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES
BlameTrackCopiesAnyCommitCopies BlameOptionsFlag = C.GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES
BlameFirstParent BlameOptionsFlag = C.GIT_BLAME_FIRST_PARENT
BlameUseMailmap BlameOptionsFlag = C.GIT_BLAME_USE_MAILMAP
BlameIgnoreWhitespace BlameOptionsFlag = C.GIT_BLAME_IGNORE_WHITESPACE
)
func (v *Repository) BlameFile(path string, opts *BlameOptions) (*Blame, error) {
var blamePtr *C.git_blame
var copts *C.git_blame_options
if opts != nil {
copts = &C.git_blame_options{
version: C.GIT_BLAME_OPTIONS_VERSION,
flags: C.uint32_t(opts.Flags),
min_match_characters: C.uint16_t(opts.MinMatchCharacters),
min_line: C.size_t(opts.MinLine),
max_line: C.size_t(opts.MaxLine),
}
if opts.NewestCommit != nil {
copts.newest_commit = *opts.NewestCommit.toC()
}
if opts.OldestCommit != nil {
copts.oldest_commit = *opts.OldestCommit.toC()
}
}
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_blame_file(&blamePtr, v.ptr, cpath, copts)
runtime.KeepAlive(v)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newBlameFromC(blamePtr), nil
}
type Blame struct {
doNotCompare
ptr *C.git_blame
}
func (blame *Blame) HunkCount() int {
ret := int(C.git_blame_get_hunk_count(blame.ptr))
runtime.KeepAlive(blame)
return ret
}
func (blame *Blame) HunkByIndex(index int) (BlameHunk, error) {
ptr := C.git_blame_get_hunk_byindex(blame.ptr, C.uint32_t(index))
runtime.KeepAlive(blame)
if ptr == nil {
return BlameHunk{}, ErrInvalid
}
return blameHunkFromC(ptr), nil
}
func (blame *Blame) HunkByLine(lineno int) (BlameHunk, error) {
ptr := C.git_blame_get_hunk_byline(blame.ptr, C.size_t(lineno))
runtime.KeepAlive(blame)
if ptr == nil {
return BlameHunk{}, ErrInvalid
}
return blameHunkFromC(ptr), nil
}
func newBlameFromC(ptr *C.git_blame) *Blame {
if ptr == nil {
return nil
}
blame := &Blame{
ptr: ptr,
}
runtime.SetFinalizer(blame, (*Blame).Free)
return blame
}
func (blame *Blame) Free() error {
if blame.ptr == nil {
return ErrInvalid
}
runtime.SetFinalizer(blame, nil)
C.git_blame_free(blame.ptr)
blame.ptr = nil
return nil
}
type BlameHunk struct {
LinesInHunk uint16
FinalCommitId *Oid
FinalStartLineNumber uint16
FinalSignature *Signature
OrigCommitId *Oid
OrigPath string
OrigStartLineNumber uint16
OrigSignature *Signature
Boundary bool
}
func blameHunkFromC(hunk *C.git_blame_hunk) BlameHunk {
return BlameHunk{
LinesInHunk: uint16(hunk.lines_in_hunk),
FinalCommitId: newOidFromC(&hunk.final_commit_id),
FinalStartLineNumber: uint16(hunk.final_start_line_number),
FinalSignature: newSignatureFromC(hunk.final_signature),
OrigCommitId: newOidFromC(&hunk.orig_commit_id),
OrigPath: C.GoString(hunk.orig_path),
OrigStartLineNumber: uint16(hunk.orig_start_line_number),
OrigSignature: newSignatureFromC(hunk.orig_signature),
Boundary: hunk.boundary == 1,
}
}

View File

@ -1,73 +0,0 @@
package git
import (
"reflect"
"testing"
)
func TestBlame(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId1, _ := seedTestRepo(t, repo)
commitId2, _ := updateReadme(t, repo, "foo\nbar\nbaz\n")
opts := BlameOptions{
NewestCommit: commitId2,
OldestCommit: nil,
MinLine: 1,
MaxLine: 3,
}
blame, err := repo.BlameFile("README", &opts)
checkFatal(t, err)
defer blame.Free()
if blame.HunkCount() != 2 {
t.Errorf("got hunk count %d, want 2", blame.HunkCount())
}
wantHunk1 := BlameHunk{
LinesInHunk: 1,
FinalCommitId: commitId1,
FinalStartLineNumber: 1,
OrigCommitId: commitId1,
OrigPath: "README",
OrigStartLineNumber: 1,
Boundary: true,
}
wantHunk2 := BlameHunk{
LinesInHunk: 2,
FinalCommitId: commitId2,
FinalStartLineNumber: 2,
OrigCommitId: commitId2,
OrigPath: "README",
OrigStartLineNumber: 2,
Boundary: false,
}
hunk1, err := blame.HunkByIndex(0)
checkFatal(t, err)
checkHunk(t, "index 0", hunk1, wantHunk1)
hunk2, err := blame.HunkByIndex(1)
checkFatal(t, err)
checkHunk(t, "index 1", hunk2, wantHunk2)
hunkLine1, err := blame.HunkByLine(1)
checkFatal(t, err)
checkHunk(t, "line 1", hunkLine1, wantHunk1)
hunkLine2, err := blame.HunkByLine(3)
checkFatal(t, err)
checkHunk(t, "line 2", hunkLine2, wantHunk2)
}
func checkHunk(t *testing.T, label string, hunk, want BlameHunk) {
hunk.FinalSignature = nil
want.FinalSignature = nil
hunk.OrigSignature = nil
want.OrigSignature = nil
if !reflect.DeepEqual(hunk, want) {
t.Fatalf("%s: got hunk %+v, want %+v", label, hunk, want)
}
}

143
blob.go
View File

@ -1,151 +1,26 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
#include <string.h>
int _go_git_writestream_write(git_writestream *stream, const char *buffer, size_t len);
void _go_git_writestream_free(git_writestream *stream);
#include <git2/errors.h>
*/
import "C"
import (
"reflect"
"runtime"
"unsafe"
)
type Blob struct {
doNotCompare
Object
cast_ptr *C.git_blob
gitObject
}
func (b *Blob) AsObject() *Object {
return &b.Object
func (v Blob) Size() int64 {
return int64(C.git_blob_rawsize(v.ptr))
}
func (v *Blob) Size() int64 {
ret := int64(C.git_blob_rawsize(v.cast_ptr))
runtime.KeepAlive(v)
return ret
func (v Blob) Contents() []byte {
size := C.int(C.git_blob_rawsize(v.ptr))
buffer := unsafe.Pointer(C.git_blob_rawcontent(v.ptr))
return C.GoBytes(buffer, size)
}
func (v *Blob) Contents() []byte {
size := C.int(C.git_blob_rawsize(v.cast_ptr))
buffer := unsafe.Pointer(C.git_blob_rawcontent(v.cast_ptr))
goBytes := C.GoBytes(buffer, size)
runtime.KeepAlive(v)
return goBytes
}
func (v *Blob) IsBinary() bool {
ret := C.git_blob_is_binary(v.cast_ptr) == 1
runtime.KeepAlive(v)
return ret
}
func (repo *Repository) CreateBlobFromBuffer(data []byte) (*Oid, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var id C.git_oid
var size C.size_t
// Go 1.6 added some increased checking of passing pointer to
// C, but its check depends on its expectations of what we
// pass to the C function, so unless we take the address of
// its contents at the call site itself, it can fail when
// 'data' is a slice of a slice.
//
// When we're given an empty slice, create a dummy one where 0
// isn't out of bounds.
if len(data) > 0 {
size = C.size_t(len(data))
} else {
data = []byte{0}
size = C.size_t(0)
}
ecode := C.git_blob_create_from_buffer(&id, repo.ptr, unsafe.Pointer(&data[0]), size)
runtime.KeepAlive(repo)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newOidFromC(&id), nil
}
func (repo *Repository) CreateFromStream(hintPath string) (*BlobWriteStream, error) {
var chintPath *C.char = nil
var stream *C.git_writestream
if len(hintPath) > 0 {
chintPath = C.CString(hintPath)
defer C.free(unsafe.Pointer(chintPath))
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_blob_create_from_stream(&stream, repo.ptr, chintPath)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newBlobWriteStreamFromC(stream, repo), nil
}
type BlobWriteStream struct {
doNotCompare
ptr *C.git_writestream
repo *Repository
}
func newBlobWriteStreamFromC(ptr *C.git_writestream, repo *Repository) *BlobWriteStream {
stream := &BlobWriteStream{
ptr: ptr,
repo: repo,
}
runtime.SetFinalizer(stream, (*BlobWriteStream).Free)
return stream
}
// Implement io.Writer
func (stream *BlobWriteStream) Write(p []byte) (int, error) {
header := (*reflect.SliceHeader)(unsafe.Pointer(&p))
ptr := (*C.char)(unsafe.Pointer(header.Data))
size := C.size_t(header.Len)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C._go_git_writestream_write(stream.ptr, ptr, size)
runtime.KeepAlive(stream)
if ecode < 0 {
return 0, MakeGitError(ecode)
}
return len(p), nil
}
func (stream *BlobWriteStream) Free() {
runtime.SetFinalizer(stream, nil)
C._go_git_writestream_free(stream.ptr)
}
func (stream *BlobWriteStream) Commit() (*Oid, error) {
oid := C.git_oid{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_blob_create_from_stream_commit(&oid, stream.ptr)
runtime.KeepAlive(stream)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newOidFromC(&oid), nil
}

View File

@ -1,60 +0,0 @@
package git
import (
"bytes"
"testing"
)
type bufWrapper struct {
buf [64]byte
pointer []byte
}
func doublePointerBytes() []byte {
o := &bufWrapper{}
o.pointer = o.buf[0:10]
return o.pointer[0:1]
}
func TestCreateBlobFromBuffer(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
id, err := repo.CreateBlobFromBuffer(make([]byte, 0))
checkFatal(t, err)
if id.String() != "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" {
t.Fatal("Empty buffer did not deliver empty blob id")
}
tests := []struct {
data []byte
isBinary bool
}{
{
data: []byte("hello there"),
isBinary: false,
},
{
data: doublePointerBytes(),
isBinary: true,
},
}
for _, tt := range tests {
data := tt.data
id, err = repo.CreateBlobFromBuffer(data)
checkFatal(t, err)
blob, err := repo.LookupBlob(id)
checkFatal(t, err)
if !bytes.Equal(blob.Contents(), data) {
t.Fatal("Loaded bytes don't match original bytes:",
blob.Contents(), "!=", data)
}
want := tt.isBinary
if got := blob.IsBinary(); got != want {
t.Fatalf("IsBinary() = %v, want %v", got, want)
}
}
}

264
branch.go
View File

@ -1,264 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
type BranchType uint
const (
BranchAll BranchType = C.GIT_BRANCH_ALL
BranchLocal BranchType = C.GIT_BRANCH_LOCAL
BranchRemote BranchType = C.GIT_BRANCH_REMOTE
)
type Branch struct {
doNotCompare
*Reference
}
func (r *Reference) Branch() *Branch {
return &Branch{Reference: r}
}
type BranchIterator struct {
doNotCompare
ptr *C.git_branch_iterator
repo *Repository
}
type BranchIteratorFunc func(*Branch, BranchType) error
func newBranchIteratorFromC(repo *Repository, ptr *C.git_branch_iterator) *BranchIterator {
i := &BranchIterator{repo: repo, ptr: ptr}
runtime.SetFinalizer(i, (*BranchIterator).Free)
return i
}
func (i *BranchIterator) Next() (*Branch, BranchType, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var refPtr *C.git_reference
var refType C.git_branch_t
ecode := C.git_branch_next(&refPtr, &refType, i.ptr)
if ecode < 0 {
return nil, BranchLocal, MakeGitError(ecode)
}
branch := newReferenceFromC(refPtr, i.repo).Branch()
return branch, BranchType(refType), nil
}
func (i *BranchIterator) Free() {
runtime.SetFinalizer(i, nil)
C.git_branch_iterator_free(i.ptr)
}
func (i *BranchIterator) ForEach(f BranchIteratorFunc) error {
b, t, err := i.Next()
for err == nil {
err = f(b, t)
if err == nil {
b, t, err = i.Next()
}
}
if err != nil && IsErrorCode(err, ErrorCodeIterOver) {
return nil
}
return err
}
func (repo *Repository) NewBranchIterator(flags BranchType) (*BranchIterator, error) {
refType := C.git_branch_t(flags)
var ptr *C.git_branch_iterator
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_branch_iterator_new(&ptr, repo.ptr, refType)
runtime.KeepAlive(repo)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newBranchIteratorFromC(repo, ptr), nil
}
func (repo *Repository) CreateBranch(branchName string, target *Commit, force bool) (*Branch, error) {
var ptr *C.git_reference
cBranchName := C.CString(branchName)
defer C.free(unsafe.Pointer(cBranchName))
cForce := cbool(force)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_create(&ptr, repo.ptr, cBranchName, target.cast_ptr, cForce)
runtime.KeepAlive(repo)
runtime.KeepAlive(target)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newReferenceFromC(ptr, repo).Branch(), nil
}
func (b *Branch) Delete() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_delete(b.Reference.ptr)
runtime.KeepAlive(b.Reference)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (b *Branch) Move(newBranchName string, force bool) (*Branch, error) {
var ptr *C.git_reference
cNewBranchName := C.CString(newBranchName)
defer C.free(unsafe.Pointer(cNewBranchName))
cForce := cbool(force)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_move(&ptr, b.Reference.ptr, cNewBranchName, cForce)
runtime.KeepAlive(b.Reference)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newReferenceFromC(ptr, b.repo).Branch(), nil
}
func (b *Branch) IsHead() (bool, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_is_head(b.Reference.ptr)
runtime.KeepAlive(b.Reference)
switch ret {
case 1:
return true, nil
case 0:
return false, nil
}
return false, MakeGitError(ret)
}
func (repo *Repository) LookupBranch(branchName string, bt BranchType) (*Branch, error) {
var ptr *C.git_reference
cName := C.CString(branchName)
defer C.free(unsafe.Pointer(cName))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_lookup(&ptr, repo.ptr, cName, C.git_branch_t(bt))
runtime.KeepAlive(repo)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newReferenceFromC(ptr, repo).Branch(), nil
}
func (b *Branch) Name() (string, error) {
var cName *C.char
defer C.free(unsafe.Pointer(cName))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_name(&cName, b.Reference.ptr)
runtime.KeepAlive(b.Reference)
if ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(cName), nil
}
func (repo *Repository) RemoteName(canonicalBranchName string) (string, error) {
cName := C.CString(canonicalBranchName)
defer C.free(unsafe.Pointer(cName))
nameBuf := C.git_buf{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_remote_name(&nameBuf, repo.ptr, cName)
runtime.KeepAlive(repo)
if ret < 0 {
return "", MakeGitError(ret)
}
defer C.git_buf_dispose(&nameBuf)
return C.GoString(nameBuf.ptr), nil
}
func (b *Branch) SetUpstream(upstreamName string) error {
cName := C.CString(upstreamName)
defer C.free(unsafe.Pointer(cName))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_set_upstream(b.Reference.ptr, cName)
runtime.KeepAlive(b.Reference)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (b *Branch) Upstream() (*Reference, error) {
var ptr *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_upstream(&ptr, b.Reference.ptr)
runtime.KeepAlive(b.Reference)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newReferenceFromC(ptr, b.repo), nil
}
func (repo *Repository) UpstreamName(canonicalBranchName string) (string, error) {
cName := C.CString(canonicalBranchName)
defer C.free(unsafe.Pointer(cName))
nameBuf := C.git_buf{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_branch_upstream_name(&nameBuf, repo.ptr, cName)
runtime.KeepAlive(repo)
if ret < 0 {
return "", MakeGitError(ret)
}
defer C.git_buf_dispose(&nameBuf)
return C.GoString(nameBuf.ptr), nil
}

View File

@ -1,63 +0,0 @@
package git
import (
"testing"
)
func TestBranchIterator(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
i, err := repo.NewBranchIterator(BranchLocal)
checkFatal(t, err)
b, bt, err := i.Next()
checkFatal(t, err)
if name, _ := b.Name(); name != "master" {
t.Fatalf("expected master")
} else if bt != BranchLocal {
t.Fatalf("expected BranchLocal, not %v", t)
}
b, bt, err = i.Next()
if !IsErrorCode(err, ErrorCodeIterOver) {
t.Fatal("expected iterover")
}
}
func TestBranchIteratorEach(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
i, err := repo.NewBranchIterator(BranchLocal)
checkFatal(t, err)
var names []string
f := func(b *Branch, t BranchType) error {
name, err := b.Name()
if err != nil {
return err
}
names = append(names, name)
return nil
}
err = i.ForEach(f)
if err != nil && !IsErrorCode(err, ErrorCodeIterOver) {
t.Fatal(err)
}
if len(names) != 1 {
t.Fatalf("expect 1 branch, but it was %d\n", len(names))
}
if names[0] != "master" {
t.Fatalf("expect branch master, but it was %s\n", names[0])
}
}

View File

@ -1,266 +1,81 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
extern void _go_git_populate_checkout_callbacks(git_checkout_options *opts);
git_checkout_opts git_checkout_opts_init() {
git_checkout_opts ret = GIT_CHECKOUT_OPTS_INIT;
return ret;
}
*/
import "C"
import (
"os"
"runtime"
"unsafe"
)
type CheckoutNotifyType uint
type CheckoutStrategy uint
const (
CheckoutNotifyNone CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_NONE
CheckoutNotifyConflict CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_CONFLICT
CheckoutNotifyDirty CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_DIRTY
CheckoutNotifyUpdated CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_UPDATED
CheckoutNotifyUntracked CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_UNTRACKED
CheckoutNotifyIgnored CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_IGNORED
CheckoutNotifyAll CheckoutNotifyType = C.GIT_CHECKOUT_NOTIFY_ALL
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)
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
CheckoutUseOurs 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
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)
CheckoutSafe = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data
CheckoutSafeCreate = C.GIT_CHECKOUT_SAFE_CREATE // Allow safe updates plus creation of missing files
CheckoutForce = C.GIT_CHECKOUT_FORCE // Allow all updates to force working directory to look like index
CheckoutAllowConflicts = C.GIT_CHECKOUT_ALLOW_CONFLICTS // Allow checkout to make safe updates even if conflicts are found
CheckoutRemoveUntracked = C.GIT_CHECKOUT_REMOVE_UNTRACKED // Remove untracked files not in index (that are not ignored)
CheckoutRemoveIgnored = C.GIT_CHECKOUT_REMOVE_IGNORED // Remove ignored files not in index
CheckotUpdateOnly = C.GIT_CHECKOUT_UPDATE_ONLY // Only update existing files, don't create new ones
CheckoutDontUpdateIndex = C.GIT_CHECKOUT_DONT_UPDATE_INDEX // Normally checkout updates index entries as it goes; this stops that
CheckoutNoRefresh = C.GIT_CHECKOUT_NO_REFRESH // Don't refresh index/config/etc before doing checkout
CheckooutDisablePathspecMatch = C.GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH // Treat pathspec as simple list of exact match file paths
CheckoutSkipUnmerged = C.GIT_CHECKOUT_SKIP_UNMERGED // Allow checkout to skip unmerged files (NOT IMPLEMENTED)
CheckoutUserOurs = C.GIT_CHECKOUT_USE_OURS // For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED)
CheckoutUseTheirs = C.GIT_CHECKOUT_USE_THEIRS // For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED)
CheckoutUpdateSubmodules = C.GIT_CHECKOUT_UPDATE_SUBMODULES // Recursively checkout submodules with same options (NOT IMPLEMENTED)
CheckoutUpdateSubmodulesIfChanged = C.GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED // Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED)
)
type CheckoutNotifyCallback func(why CheckoutNotifyType, path string, baseline, target, workdir DiffFile) error
type CheckoutProgressCallback func(path string, completed, total uint)
type CheckoutOptions struct {
Strategy CheckoutStrategy // Default will be a dry run
DisableFilters bool // Don't apply filters like CRLF conversion
DirMode os.FileMode // Default is 0755
FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob
FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY
NotifyFlags CheckoutNotifyType // Default will be none
NotifyCallback CheckoutNotifyCallback
ProgressCallback CheckoutProgressCallback
TargetDirectory string // Alternative checkout path to workdir
Paths []string
Baseline *Tree
type CheckoutOpts struct {
Strategy CheckoutStrategy // Default will be a dry run
DisableFilters bool // Don't apply filters like CRLF conversion
DirMode os.FileMode // Default is 0755
FileMode os.FileMode // Default is 0644 or 0755 as dictated by blob
FileOpenFlags int // Default is O_CREAT | O_TRUNC | O_WRONLY
}
func checkoutOptionsFromC(c *C.git_checkout_options) CheckoutOptions {
opts := CheckoutOptions{
Strategy: CheckoutStrategy(c.checkout_strategy),
DisableFilters: c.disable_filters != 0,
DirMode: os.FileMode(c.dir_mode),
FileMode: os.FileMode(c.file_mode),
FileOpenFlags: int(c.file_open_flags),
NotifyFlags: CheckoutNotifyType(c.notify_flags),
}
if c.notify_payload != nil {
opts.NotifyCallback = pointerHandles.Get(c.notify_payload).(*checkoutCallbackData).options.NotifyCallback
}
if c.progress_payload != nil {
opts.ProgressCallback = pointerHandles.Get(c.progress_payload).(*checkoutCallbackData).options.ProgressCallback
}
if c.target_directory != nil {
opts.TargetDirectory = C.GoString(c.target_directory)
}
return opts
}
type checkoutCallbackData struct {
options *CheckoutOptions
errorTarget *error
}
//export checkoutNotifyCallback
func checkoutNotifyCallback(
why C.git_checkout_notify_t,
cpath *C.char,
cbaseline, ctarget, cworkdir, handle unsafe.Pointer,
) C.int {
if handle == nil {
return C.int(ErrorCodeOK)
}
path := C.GoString(cpath)
var baseline, target, workdir DiffFile
if cbaseline != nil {
baseline = diffFileFromC((*C.git_diff_file)(cbaseline))
}
if ctarget != nil {
target = diffFileFromC((*C.git_diff_file)(ctarget))
}
if cworkdir != nil {
workdir = diffFileFromC((*C.git_diff_file)(cworkdir))
}
data := pointerHandles.Get(handle).(*checkoutCallbackData)
if data.options.NotifyCallback == nil {
return C.int(ErrorCodeOK)
}
err := data.options.NotifyCallback(CheckoutNotifyType(why), path, baseline, target, workdir)
if err != nil {
*data.errorTarget = err
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}
//export checkoutProgressCallback
func checkoutProgressCallback(
path *C.char,
completed_steps, total_steps C.size_t,
handle unsafe.Pointer,
) {
data := pointerHandles.Get(handle).(*checkoutCallbackData)
if data.options.ProgressCallback == nil {
return
}
data.options.ProgressCallback(C.GoString(path), uint(completed_steps), uint(total_steps))
}
// populateCheckoutOptions populates the provided C-struct with the contents of
// the provided CheckoutOptions struct. Returns copts, or nil if opts is nil,
// in order to help with what to pass.
func populateCheckoutOptions(copts *C.git_checkout_options, opts *CheckoutOptions, errorTarget *error) *C.git_checkout_options {
C.git_checkout_options_init(copts, C.GIT_CHECKOUT_OPTIONS_VERSION)
// Convert the CheckoutOpts struct to the corresponding C-struct
func populateCheckoutOpts(ptr *C.git_checkout_opts, opts *CheckoutOpts) {
*ptr = C.git_checkout_opts_init()
if opts == nil {
return nil
}
copts.checkout_strategy = C.uint(opts.Strategy)
copts.disable_filters = cbool(opts.DisableFilters)
copts.dir_mode = C.uint(opts.DirMode.Perm())
copts.file_mode = C.uint(opts.FileMode.Perm())
copts.notify_flags = C.uint(opts.NotifyFlags)
if opts.NotifyCallback != nil || opts.ProgressCallback != nil {
C._go_git_populate_checkout_callbacks(copts)
data := &checkoutCallbackData{
options: opts,
errorTarget: errorTarget,
}
payload := pointerHandles.Track(data)
if opts.NotifyCallback != nil {
copts.notify_payload = payload
}
if opts.ProgressCallback != nil {
copts.progress_payload = payload
}
}
if opts.TargetDirectory != "" {
copts.target_directory = C.CString(opts.TargetDirectory)
}
if len(opts.Paths) > 0 {
copts.paths.strings = makeCStringsFromStrings(opts.Paths)
copts.paths.count = C.size_t(len(opts.Paths))
}
if opts.Baseline != nil {
copts.baseline = opts.Baseline.cast_ptr
}
return copts
}
func freeCheckoutOptions(copts *C.git_checkout_options) {
if copts == nil {
return
}
C.free(unsafe.Pointer(copts.target_directory))
if copts.paths.count > 0 {
freeStrarray(&copts.paths)
}
if copts.notify_payload != nil {
pointerHandles.Untrack(copts.notify_payload)
} else if copts.progress_payload != nil {
pointerHandles.Untrack(copts.progress_payload)
}
ptr.checkout_strategy = C.uint(opts.Strategy)
ptr.disable_filters = cbool(opts.DisableFilters)
ptr.dir_mode = C.uint(opts.DirMode.Perm())
ptr.file_mode = C.uint(opts.FileMode.Perm())
}
// Updates files in the index and the working tree to match the content of
// the commit pointed at by HEAD. opts may be nil.
func (v *Repository) CheckoutHead(opts *CheckoutOptions) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// the commit pointed at by HEAD.
func (v *Repository) Checkout(opts *CheckoutOpts) error {
var copts C.git_checkout_opts
populateCheckoutOpts(&copts, opts)
var err error
cOpts := populateCheckoutOptions(&C.git_checkout_options{}, opts, &err)
defer freeCheckoutOptions(cOpts)
ret := C.git_checkout_head(v.ptr, cOpts)
runtime.KeepAlive(v)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
ret := C.git_checkout_head(v.ptr, &copts)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
// Updates files in the working tree to match the content of the given
// index. If index is nil, the repository's index will be used. opts
// may be nil.
func (v *Repository) CheckoutIndex(index *Index, opts *CheckoutOptions) error {
var iptr *C.git_index = nil
if index != nil {
iptr = index.ptr
}
// Updates files in the working tree to match the content of the index.
func (v *Repository) CheckoutIndex(index *Index, opts *CheckoutOpts) error {
var copts C.git_checkout_opts
populateCheckoutOpts(&copts, opts)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var err error
cOpts := populateCheckoutOptions(&C.git_checkout_options{}, opts, &err)
defer freeCheckoutOptions(cOpts)
ret := C.git_checkout_index(v.ptr, iptr, cOpts)
runtime.KeepAlive(v)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
ret := C.git_checkout_index(v.ptr, index.ptr, &copts)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (v *Repository) CheckoutTree(tree *Tree, opts *CheckoutOptions) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var err error
cOpts := populateCheckoutOptions(&C.git_checkout_options{}, opts, &err)
defer freeCheckoutOptions(cOpts)
ret := C.git_checkout_tree(v.ptr, tree.ptr, cOpts)
runtime.KeepAlive(v)
runtime.KeepAlive(tree)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil

View File

@ -1,94 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
)
type CherrypickOptions struct {
Mainline uint
MergeOptions MergeOptions
CheckoutOptions CheckoutOptions
}
func cherrypickOptionsFromC(c *C.git_cherrypick_options) CherrypickOptions {
opts := CherrypickOptions{
Mainline: uint(c.mainline),
MergeOptions: mergeOptionsFromC(&c.merge_opts),
CheckoutOptions: checkoutOptionsFromC(&c.checkout_opts),
}
return opts
}
func populateCherrypickOptions(copts *C.git_cherrypick_options, opts *CherrypickOptions, errorTarget *error) *C.git_cherrypick_options {
C.git_cherrypick_options_init(copts, C.GIT_CHERRYPICK_OPTIONS_VERSION)
if opts == nil {
return nil
}
copts.mainline = C.uint(opts.Mainline)
populateMergeOptions(&copts.merge_opts, &opts.MergeOptions)
populateCheckoutOptions(&copts.checkout_opts, &opts.CheckoutOptions, errorTarget)
return copts
}
func freeCherrypickOpts(copts *C.git_cherrypick_options) {
if copts == nil {
return
}
freeMergeOptions(&copts.merge_opts)
freeCheckoutOptions(&copts.checkout_opts)
}
func DefaultCherrypickOptions() (CherrypickOptions, error) {
c := C.git_cherrypick_options{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_cherrypick_options_init(&c, C.GIT_CHERRYPICK_OPTIONS_VERSION)
if ecode < 0 {
return CherrypickOptions{}, MakeGitError(ecode)
}
defer freeCherrypickOpts(&c)
return cherrypickOptionsFromC(&c), nil
}
func (v *Repository) Cherrypick(commit *Commit, opts CherrypickOptions) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var err error
cOpts := populateCherrypickOptions(&C.git_cherrypick_options{}, &opts, &err)
defer freeCherrypickOpts(cOpts)
ret := C.git_cherrypick(v.ptr, commit.cast_ptr, cOpts)
runtime.KeepAlive(v)
runtime.KeepAlive(commit)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (r *Repository) CherrypickCommit(pick, our *Commit, opts CherrypickOptions) (*Index, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cOpts := populateMergeOptions(&C.git_merge_options{}, &opts.MergeOptions)
defer freeMergeOptions(cOpts)
var ptr *C.git_index
ret := C.git_cherrypick_commit(&ptr, r.ptr, pick.cast_ptr, our.cast_ptr, C.uint(opts.Mainline), cOpts)
runtime.KeepAlive(pick)
runtime.KeepAlive(our)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newIndexFromC(ptr, r), nil
}

View File

@ -1,140 +0,0 @@
package git
import (
"io/ioutil"
"testing"
)
func checkout(t *testing.T, repo *Repository, commit *Commit) {
tree, err := commit.Tree()
if err != nil {
t.Fatal(err)
}
err = repo.CheckoutTree(tree, &CheckoutOptions{Strategy: CheckoutSafe})
if err != nil {
t.Fatal(err)
}
err = repo.SetHeadDetached(commit.Id())
if err != nil {
t.Fatal(err)
}
}
const content = "Herro, Worrd!"
func readReadme(t *testing.T, repo *Repository) string {
bytes, err := ioutil.ReadFile(pathInRepo(repo, "README"))
if err != nil {
t.Fatal(err)
}
return string(bytes)
}
func TestCherrypick(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
c1, _ := seedTestRepo(t, repo)
c2, _ := updateReadme(t, repo, content)
commit1, err := repo.LookupCommit(c1)
if err != nil {
t.Fatal(err)
}
commit2, err := repo.LookupCommit(c2)
if err != nil {
t.Fatal(err)
}
checkout(t, repo, commit1)
if readReadme(t, repo) == content {
t.Fatalf("README has wrong content after checking out initial commit")
}
opts, err := DefaultCherrypickOptions()
if err != nil {
t.Fatal(err)
}
err = repo.Cherrypick(commit2, opts)
if err != nil {
t.Fatal(err)
}
if readReadme(t, repo) != content {
t.Fatalf("README has wrong contents after cherry-picking")
}
state := repo.State()
if state != RepositoryStateCherrypick {
t.Fatal("Incorrect repository state: ", state)
}
err = repo.StateCleanup()
if err != nil {
t.Fatal(err)
}
state = repo.State()
if state != RepositoryStateNone {
t.Fatal("Incorrect repository state: ", state)
}
}
func TestCherrypickCommit(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
c1, _ := seedTestRepo(t, repo)
c2, _ := updateReadme(t, repo, content)
commit1, err := repo.LookupCommit(c1)
if err != nil {
t.Fatal(err)
}
commit2, err := repo.LookupCommit(c2)
if err != nil {
t.Fatal(err)
}
checkout(t, repo, commit1)
if got := readReadme(t, repo); got == content {
t.Fatalf("README = %q, want %q", got, content)
}
opts, err := DefaultCherrypickOptions()
if err != nil {
t.Fatal(err)
}
idx, err := repo.CherrypickCommit(commit2, commit1, opts)
if err != nil {
t.Fatal(err)
}
defer idx.Free()
// The file is only updated in the index, not in the working directory.
if got := readReadme(t, repo); got == content {
t.Errorf("README = %q, want %q", got, content)
}
if got := repo.State(); got != RepositoryStateNone {
t.Errorf("repo.State() = %v, want %v", got, RepositoryStateCherrypick)
}
if got := idx.EntryCount(); got != 1 {
t.Fatalf("idx.EntryCount() = %v, want %v", got, 1)
}
entry, err := idx.EntryByIndex(0)
if err != nil {
t.Fatal(err)
}
if entry.Path != "README" {
t.Errorf("entry.Path = %v, want %v", entry.Path, "README")
}
}

133
clone.go
View File

@ -1,133 +0,0 @@
package git
/*
#include <git2.h>
extern void _go_git_populate_clone_callbacks(git_clone_options *opts);
*/
import "C"
import (
"runtime"
"unsafe"
)
type RemoteCreateCallback func(repo *Repository, name, url string) (*Remote, error)
type CloneOptions struct {
CheckoutOptions CheckoutOptions
FetchOptions FetchOptions
Bare bool
CheckoutBranch string
RemoteCreateCallback RemoteCreateCallback
}
func Clone(url string, path string, options *CloneOptions) (*Repository, error) {
curl := C.CString(url)
defer C.free(unsafe.Pointer(curl))
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
var err error
cOptions := populateCloneOptions(&C.git_clone_options{}, options, &err)
defer freeCloneOptions(cOptions)
if len(options.CheckoutBranch) != 0 {
cOptions.checkout_branch = C.CString(options.CheckoutBranch)
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_repository
ret := C.git_clone(&ptr, curl, cpath, cOptions)
if ret == C.int(ErrorCodeUser) && err != nil {
return nil, err
}
if ret < 0 {
return nil, MakeGitError(ret)
}
return newRepositoryFromC(ptr), nil
}
//export remoteCreateCallback
func remoteCreateCallback(
out **C.git_remote,
crepo *C.git_repository,
cname, curl *C.char,
handle unsafe.Pointer,
) C.int {
name := C.GoString(cname)
url := C.GoString(curl)
repo := newRepositoryFromC(crepo)
repo.weak = true
defer repo.Free()
data, ok := pointerHandles.Get(handle).(*cloneCallbackData)
if !ok {
panic("invalid remote create callback")
}
remote, err := data.options.RemoteCreateCallback(repo, name, url)
if err != nil {
*data.errorTarget = err
return C.int(ErrorCodeUser)
}
if remote == nil {
panic("no remote created by callback")
}
*out = remote.ptr
// clear finalizer as the calling C function will
// free the remote itself
runtime.SetFinalizer(remote, nil)
remote.repo.Remotes.untrackRemote(remote)
return C.int(ErrorCodeOK)
}
type cloneCallbackData struct {
options *CloneOptions
errorTarget *error
}
func populateCloneOptions(copts *C.git_clone_options, opts *CloneOptions, errorTarget *error) *C.git_clone_options {
C.git_clone_options_init(copts, C.GIT_CLONE_OPTIONS_VERSION)
if opts == nil {
return nil
}
populateCheckoutOptions(&copts.checkout_opts, &opts.CheckoutOptions, errorTarget)
populateFetchOptions(&copts.fetch_opts, &opts.FetchOptions, errorTarget)
copts.bare = cbool(opts.Bare)
if opts.RemoteCreateCallback != nil {
data := &cloneCallbackData{
options: opts,
errorTarget: errorTarget,
}
// Go v1.1 does not allow to assign a C function pointer
C._go_git_populate_clone_callbacks(copts)
copts.remote_cb_payload = pointerHandles.Track(data)
}
return copts
}
func freeCloneOptions(copts *C.git_clone_options) {
if copts == nil {
return
}
freeCheckoutOptions(&copts.checkout_opts)
freeFetchOptions(&copts.fetch_opts)
if copts.remote_cb_payload != nil {
pointerHandles.Untrack(copts.remote_cb_payload)
}
C.free(unsafe.Pointer(copts.checkout_branch))
}

View File

@ -1,87 +0,0 @@
package git
import (
"io/ioutil"
"os"
"testing"
)
const (
REMOTENAME = "testremote"
)
func TestClone(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
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")
}
}
func TestCloneWithCallback(t *testing.T) {
t.Parallel()
testPayload := 0
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
path, err := ioutil.TempDir("", "git2go")
checkFatal(t, err)
opts := CloneOptions{
Bare: true,
RemoteCreateCallback: func(r *Repository, name, url string) (*Remote, error) {
testPayload += 1
return r.Remotes.Create(REMOTENAME, url)
},
}
repo2, err := Clone(repo.Path(), path, &opts)
defer cleanupTestRepo(t, repo2)
checkFatal(t, err)
if testPayload != 1 {
t.Fatal("Payload's value has not been changed")
}
remote, err := repo2.Remotes.Lookup(REMOTENAME)
if err != nil || remote == nil {
t.Fatal("Remote was not created properly")
}
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)
}
}

245
commit.go
View File

@ -2,228 +2,105 @@ package git
/*
#include <git2.h>
#include <git2/errors.h>
extern int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr);
*/
import "C"
import (
"runtime"
"unsafe"
)
// MessageEncoding is the encoding of commit messages.
type MessageEncoding string
const (
// MessageEncodingUTF8 is the default message encoding.
MessageEncodingUTF8 MessageEncoding = "UTF-8"
"time"
)
// Commit
type Commit struct {
doNotCompare
Object
cast_ptr *C.git_commit
gitObject
}
func (c *Commit) AsObject() *Object {
return &c.Object
func (c Commit) Message() string {
return C.GoString(C.git_commit_message(c.ptr))
}
func (c *Commit) Message() string {
ret := C.GoString(C.git_commit_message(c.cast_ptr))
runtime.KeepAlive(c)
return ret
}
func (c Commit) Tree() (*Tree, error) {
var ptr *C.git_object
func (c *Commit) MessageEncoding() MessageEncoding {
ptr := C.git_commit_message_encoding(c.cast_ptr)
if ptr == nil {
return MessageEncodingUTF8
}
ret := C.GoString(ptr)
runtime.KeepAlive(c)
return MessageEncoding(ret)
}
func (c *Commit) RawMessage() string {
ret := C.GoString(C.git_commit_message_raw(c.cast_ptr))
runtime.KeepAlive(c)
return ret
}
// RawHeader gets the full raw text of the commit header.
func (c *Commit) RawHeader() string {
ret := C.GoString(C.git_commit_raw_header(c.cast_ptr))
runtime.KeepAlive(c)
return ret
}
// ContentToSign returns the content that will be passed to a signing function for this commit
func (c *Commit) ContentToSign() string {
return c.RawHeader() + "\n" + c.RawMessage()
}
// CommitSigningCallback defines a function type that takes some data to sign and returns (signature, signature_field, error)
type CommitSigningCallback func(string) (signature, signatureField string, err error)
// CommitCreateCallback defines a function type that is called when another
// function is going to create commits (for example, Rebase) to allow callers
// to override the commit creation behavior. For example, users may wish to
// sign commits by providing this information to Repository.CreateCommitBuffer,
// signing that buffer, then calling Repository.CreateCommitWithSignature.
type CommitCreateCallback func(
author, committer *Signature,
messageEncoding MessageEncoding,
message string,
tree *Tree,
parents ...*Commit,
) (oid *Oid, err error)
// WithSignatureUsing creates a new signed commit from this one using the given signing callback
func (c *Commit) WithSignatureUsing(f CommitSigningCallback) (*Oid, error) {
signature, signatureField, err := f(c.ContentToSign())
if err != nil {
return nil, err
}
return c.WithSignature(signature, signatureField)
}
// WithSignature creates a new signed commit from the given signature and signature field
func (c *Commit) WithSignature(signature string, signatureField string) (*Oid, error) {
return c.Owner().CreateCommitWithSignature(
c.ContentToSign(),
signature,
signatureField,
)
}
func (c *Commit) ExtractSignature() (string, string, error) {
var c_signed C.git_buf
defer C.git_buf_dispose(&c_signed)
var c_signature C.git_buf
defer C.git_buf_dispose(&c_signature)
oid := c.Id()
repo := C.git_commit_owner(c.cast_ptr)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_commit_extract_signature(&c_signature, &c_signed, repo, oid.toC(), nil)
runtime.KeepAlive(oid)
if ret < 0 {
return "", "", MakeGitError(ret)
} else {
return C.GoString(c_signature.ptr), C.GoString(c_signed.ptr), nil
}
}
func (c *Commit) Summary() string {
ret := C.GoString(C.git_commit_summary(c.cast_ptr))
runtime.KeepAlive(c)
return ret
}
func (c *Commit) Tree() (*Tree, error) {
var ptr *C.git_tree
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C.git_commit_tree(&ptr, c.cast_ptr)
runtime.KeepAlive(c)
err := C.git_commit_tree(&ptr, c.ptr)
if err < 0 {
return nil, MakeGitError(err)
return nil, LastError()
}
return allocTree(ptr, c.repo), nil
return allocObject(ptr).(*Tree), nil
}
func (c *Commit) TreeId() *Oid {
ret := newOidFromC(C.git_commit_tree_id(c.cast_ptr))
runtime.KeepAlive(c)
return ret
func (c Commit) TreeId() *Oid {
return newOidFromC(C.git_commit_tree_id(c.ptr))
}
func (c *Commit) Author() *Signature {
cast_ptr := C.git_commit_author(c.cast_ptr)
ret := newSignatureFromC(cast_ptr)
runtime.KeepAlive(c)
return ret
func (c Commit) Author() *Signature {
ptr := C.git_commit_author(c.ptr)
return newSignatureFromC(ptr)
}
func (c *Commit) Committer() *Signature {
cast_ptr := C.git_commit_committer(c.cast_ptr)
ret := newSignatureFromC(cast_ptr)
runtime.KeepAlive(c)
return ret
func (c Commit) Committer() *Signature {
ptr := C.git_commit_committer(c.ptr)
return newSignatureFromC(ptr)
}
func (c *Commit) Parent(n uint) *Commit {
var cobj *C.git_commit
ret := C.git_commit_parent(&cobj, c.cast_ptr, C.uint(n))
par := &Commit{}
ret := C.git_commit_parent(&par.ptr, c.ptr, C.uint(n))
if ret != 0 {
return nil
}
parent := allocCommit(cobj, c.repo)
runtime.KeepAlive(c)
return parent
return par
}
func (c *Commit) ParentId(n uint) *Oid {
ret := newOidFromC(C.git_commit_parent_id(c.cast_ptr, C.uint(n)))
runtime.KeepAlive(c)
return ret
return newOidFromC(C.git_commit_parent_id(c.ptr, C.uint(n)))
}
func (c *Commit) ParentCount() uint {
ret := uint(C.git_commit_parentcount(c.cast_ptr))
runtime.KeepAlive(c)
return ret
return uint(C.git_commit_parentcount(c.ptr))
}
func (c *Commit) Amend(refname string, author, committer *Signature, message string, tree *Tree) (*Oid, error) {
var cref *C.char
if refname == "" {
cref = nil
} else {
cref = C.CString(refname)
defer C.free(unsafe.Pointer(cref))
}
// Signature
cmsg := C.CString(message)
defer C.free(unsafe.Pointer(cmsg))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
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)
oid := new(Oid)
cerr := C.git_commit_amend(oid.toC(), c.cast_ptr, cref, authorSig, committerSig, nil, cmsg, tree.cast_ptr)
runtime.KeepAlive(oid)
runtime.KeepAlive(c)
runtime.KeepAlive(tree)
if cerr < 0 {
return nil, MakeGitError(cerr)
}
return oid, nil
type Signature struct {
Name string
Email string
When time.Time
}
func newSignatureFromC(sig *C.git_signature) *Signature {
// git stores minutes, go wants seconds
loc := time.FixedZone("", int(sig.when.offset)*60)
return &Signature{
C.GoString(sig.name),
C.GoString(sig.email),
time.Unix(int64(sig.when.time), 0).In(loc),
}
}
// the offset in mintes, which is what git wants
func (v *Signature) Offset() int {
_, offset := v.When.Zone()
return offset/60
}
func (sig *Signature) toC() (*C.git_signature) {
var out *C.git_signature
name := C.CString(sig.Name)
defer C.free(unsafe.Pointer(name))
email := C.CString(sig.Email)
defer C.free(unsafe.Pointer(email))
ret := C.git_signature_new(&out, name, email, C.git_time_t(sig.When.Unix()), C.int(sig.Offset()))
if ret < 0 {
return nil
}
return out
}

425
config.go
View File

@ -1,468 +1,69 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
#include <git2/errors.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
type ConfigLevel int
const (
// System-wide on Windows, for compatibility with portable git
ConfigLevelProgramdata ConfigLevel = C.GIT_CONFIG_LEVEL_PROGRAMDATA
// System-wide configuration file; /etc/gitconfig on Linux systems
ConfigLevelSystem ConfigLevel = C.GIT_CONFIG_LEVEL_SYSTEM
// XDG compatible configuration file; typically ~/.config/git/config
ConfigLevelXDG ConfigLevel = C.GIT_CONFIG_LEVEL_XDG
// User-specific configuration file (also called Global configuration
// file); typically ~/.gitconfig
ConfigLevelGlobal ConfigLevel = C.GIT_CONFIG_LEVEL_GLOBAL
// Repository specific configuration file; $WORK_DIR/.git/config on
// non-bare repos
ConfigLevelLocal ConfigLevel = C.GIT_CONFIG_LEVEL_LOCAL
// Application specific configuration file; freely defined by applications
ConfigLevelApp ConfigLevel = C.GIT_CONFIG_LEVEL_APP
// Represents the highest level available config file (i.e. the most
// specific config file available that actually is loaded)
ConfigLevelHighest ConfigLevel = C.GIT_CONFIG_HIGHEST_LEVEL
)
type ConfigEntry struct {
Name string
Value string
Level ConfigLevel
}
func newConfigEntryFromC(centry *C.git_config_entry) *ConfigEntry {
return &ConfigEntry{
Name: C.GoString(centry.name),
Value: C.GoString(centry.value),
Level: ConfigLevel(centry.level),
}
}
type Config struct {
doNotCompare
ptr *C.git_config
}
// NewConfig creates a new empty configuration object
func NewConfig() (*Config, error) {
config := new(Config)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if ret := C.git_config_new(&config.ptr); ret < 0 {
return nil, MakeGitError(ret)
}
return config, nil
}
// AddFile adds a file-backed backend to the config object at the specified level.
func (c *Config) AddFile(path string, level ConfigLevel, force bool) error {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_add_file_ondisk(c.ptr, cpath, C.git_config_level_t(level), nil, cbool(force))
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (c *Config) LookupInt32(name string) (int32, error) {
func (c *Config) LookupInt32(name string) (v int32, err error) {
var out C.int32_t
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_get_int32(&out, c.ptr, cname)
runtime.KeepAlive(c)
if ret < 0 {
return 0, MakeGitError(ret)
return 0, LastError()
}
return int32(out), nil
}
func (c *Config) LookupInt64(name string) (int64, error) {
func (c *Config) LookupInt64(name string) (v int64, err error) {
var out C.int64_t
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_get_int64(&out, c.ptr, cname)
runtime.KeepAlive(c)
if ret < 0 {
return 0, MakeGitError(ret)
return 0, LastError()
}
return int64(out), nil
}
func (c *Config) LookupString(name string) (string, error) {
func (c *Config) LookupString(name string) (v string, err error) {
var ptr *C.char
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
valBuf := C.git_buf{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_get_string_buf(&valBuf, c.ptr, cname)
runtime.KeepAlive(c)
ret := C.git_config_get_string(&ptr, c.ptr, cname)
if ret < 0 {
return "", MakeGitError(ret)
return "", LastError()
}
defer C.git_buf_dispose(&valBuf)
return C.GoString(valBuf.ptr), nil
return C.GoString(ptr), nil
}
func (c *Config) LookupBool(name string) (bool, error) {
var out C.int
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_get_bool(&out, c.ptr, cname)
runtime.KeepAlive(c)
if ret < 0 {
return false, MakeGitError(ret)
}
return out != 0, nil
}
func (c *Config) NewMultivarIterator(name, regexp string) (*ConfigIterator, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
var cregexp *C.char
if regexp == "" {
cregexp = nil
} else {
cregexp = C.CString(regexp)
defer C.free(unsafe.Pointer(cregexp))
}
iter := &ConfigIterator{cfg: c}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_multivar_iterator_new(&iter.ptr, c.ptr, cname, cregexp)
if ret < 0 {
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(iter, (*ConfigIterator).Free)
return iter, nil
}
// NewIterator creates an iterator over each entry in the
// configuration
func (c *Config) NewIterator() (*ConfigIterator, error) {
iter := &ConfigIterator{cfg: c}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_iterator_new(&iter.ptr, c.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
}
return iter, nil
}
// NewIteratorGlob creates an iterator over each entry in the
// configuration whose name matches the given regular expression
func (c *Config) NewIteratorGlob(regexp string) (*ConfigIterator, error) {
iter := &ConfigIterator{cfg: c}
cregexp := C.CString(regexp)
defer C.free(unsafe.Pointer(cregexp))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_iterator_glob_new(&iter.ptr, c.ptr, cregexp)
if ret < 0 {
return nil, MakeGitError(ret)
}
return iter, nil
}
func (c *Config) SetString(name, value string) (err error) {
func (c *Config) Set(name, value string) (err error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cvalue := C.CString(value)
defer C.free(unsafe.Pointer(cvalue))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_set_string(c.ptr, cname, cvalue)
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (c *Config) Free() {
runtime.SetFinalizer(c, nil)
C.git_config_free(c.ptr)
}
func (c *Config) SetInt32(name string, value int32) (err error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_set_int32(c.ptr, cname, C.int32_t(value))
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (c *Config) SetInt64(name string, value int64) (err error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_set_int64(c.ptr, cname, C.int64_t(value))
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (c *Config) SetBool(name string, value bool) (err error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_set_bool(c.ptr, cname, cbool(value))
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (c *Config) SetMultivar(name, regexp, value string) (err error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cregexp := C.CString(regexp)
defer C.free(unsafe.Pointer(cregexp))
cvalue := C.CString(value)
defer C.free(unsafe.Pointer(cvalue))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_set_multivar(c.ptr, cname, cregexp, cvalue)
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (c *Config) Delete(name string) error {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_delete_entry(c.ptr, cname)
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// OpenLevel creates a single-level focused config object from a multi-level one
func (c *Config) OpenLevel(parent *Config, level ConfigLevel) (*Config, error) {
config := new(Config)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_open_level(&config.ptr, parent.ptr, C.git_config_level_t(level))
runtime.KeepAlive(c)
runtime.KeepAlive(parent)
if ret < 0 {
return nil, MakeGitError(ret)
}
return config, nil
}
// OpenOndisk creates a new config instance containing a single on-disk file
func OpenOndisk(path string) (*Config, error) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
config := new(Config)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if ret := C.git_config_open_ondisk(&config.ptr, cpath); ret < 0 {
return nil, MakeGitError(ret)
}
return config, nil
}
type ConfigIterator struct {
doNotCompare
ptr *C.git_config_iterator
cfg *Config
}
// Next returns the next entry for this iterator
func (iter *ConfigIterator) Next() (*ConfigEntry, error) {
var centry *C.git_config_entry
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_next(&centry, iter.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
}
entry := newConfigEntryFromC(centry)
runtime.KeepAlive(iter)
return entry, nil
}
func (iter *ConfigIterator) Free() {
runtime.SetFinalizer(iter, nil)
C.free(unsafe.Pointer(iter.ptr))
}
func ConfigFindGlobal() (string, error) {
var buf C.git_buf
defer C.git_buf_dispose(&buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_find_global(&buf)
if ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(buf.ptr), nil
}
func ConfigFindSystem() (string, error) {
var buf C.git_buf
defer C.git_buf_dispose(&buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_find_system(&buf)
if ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(buf.ptr), nil
}
func ConfigFindXDG() (string, error) {
var buf C.git_buf
defer C.git_buf_dispose(&buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_find_xdg(&buf)
if ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(buf.ptr), nil
}
// ConfigFindProgramdata locate the path to the configuration file in ProgramData.
//
// Look for the file in %PROGRAMDATA%\Git\config used by portable git.
func ConfigFindProgramdata() (string, error) {
var buf C.git_buf
defer C.git_buf_dispose(&buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_config_find_programdata(&buf)
if ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(buf.ptr), nil
}
// OpenDefault opens the default config according to git rules
func OpenDefault() (*Config, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
config := new(Config)
if ret := C.git_config_open_default(&config.ptr); ret < 0 {
return nil, MakeGitError(ret)
}
return config, nil
}

View File

@ -1,119 +0,0 @@
package git
import (
"os"
"testing"
)
var tempConfig = "./temp.gitconfig"
func setupConfig() (*Config, error) {
var (
c *Config
err error
)
c, err = OpenOndisk(tempConfig)
if err != nil {
return nil, err
}
err = c.SetString("foo.bar", "baz")
if err != nil {
return nil, err
}
err = c.SetBool("foo.bool", true)
if err != nil {
return nil, err
}
err = c.SetInt32("foo.int32", 32)
if err != nil {
return nil, err
}
err = c.SetInt64("foo.int64", 64)
if err != nil {
return nil, err
}
return c, err
}
func cleanupConfig() {
os.Remove(tempConfig)
}
type TestRunner func(*Config, *testing.T)
var tests = []TestRunner{
// LookupString
func(c *Config, t *testing.T) {
val, err := c.LookupString("foo.bar")
if err != nil {
t.Errorf("Got LookupString error: '%v', expected none\n", err)
}
if val != "baz" {
t.Errorf("Got '%s' from LookupString, expected 'bar'\n", val)
}
},
// LookupBool
func(c *Config, t *testing.T) {
val, err := c.LookupBool("foo.bool")
if err != nil {
t.Errorf("Got LookupBool error: '%v', expected none\n", err)
}
if !val {
t.Errorf("Got %t from LookupBool, expected 'false'\n", val)
}
},
// LookupInt32
func(c *Config, t *testing.T) {
val, err := c.LookupInt32("foo.int32")
if err != nil {
t.Errorf("Got LookupInt32 error: '%v', expected none\n", err)
}
if val != 32 {
t.Errorf("Got %v, expected 32\n", val)
}
},
// LookupInt64
func(c *Config, t *testing.T) {
val, err := c.LookupInt64("foo.int64")
if err != nil {
t.Errorf("Got LookupInt64 error: '%v', expected none\n", err)
}
if val != 64 {
t.Errorf("Got %v, expected 64\n", val)
}
},
}
func TestConfigLookups(t *testing.T) {
t.Parallel()
var (
err error
c *Config
)
c, err = setupConfig()
defer cleanupConfig()
if err != nil {
t.Errorf("Setup error: '%v'. Expected none\n", err)
return
}
defer c.Free()
for _, test := range tests {
test(c, t)
}
}
func TestOpenDefault(t *testing.T) {
c, err := OpenDefault()
if err != nil {
t.Errorf("OpenDefault error: '%v'. Expected none\n", err)
return
}
defer c.Free()
}

View File

@ -1,298 +0,0 @@
package git
/*
#include <git2.h>
#include <git2/credential.h>
#include <git2/sys/credential.h>
git_credential_t _go_git_credential_credtype(git_credential *cred);
void _go_git_populate_credential_ssh_custom(git_credential_ssh_custom *cred);
*/
import "C"
import (
"crypto/rand"
"errors"
"fmt"
"runtime"
"strings"
"unsafe"
"golang.org/x/crypto/ssh"
)
// CredentialType is a bitmask of supported credential types.
//
// This represents the various types of authentication methods supported by the
// library.
type CredentialType uint
const (
CredentialTypeUserpassPlaintext CredentialType = C.GIT_CREDENTIAL_USERPASS_PLAINTEXT
CredentialTypeSSHKey CredentialType = C.GIT_CREDENTIAL_SSH_KEY
CredentialTypeSSHCustom CredentialType = C.GIT_CREDENTIAL_SSH_CUSTOM
CredentialTypeDefault CredentialType = C.GIT_CREDENTIAL_DEFAULT
CredentialTypeSSHInteractive CredentialType = C.GIT_CREDENTIAL_SSH_INTERACTIVE
CredentialTypeUsername CredentialType = C.GIT_CREDENTIAL_USERNAME
CredentialTypeSSHMemory CredentialType = C.GIT_CREDENTIAL_SSH_MEMORY
)
func (t CredentialType) String() string {
if t == 0 {
return "CredentialType(0)"
}
var parts []string
if (t & CredentialTypeUserpassPlaintext) != 0 {
parts = append(parts, "UserpassPlaintext")
t &= ^CredentialTypeUserpassPlaintext
}
if (t & CredentialTypeSSHKey) != 0 {
parts = append(parts, "SSHKey")
t &= ^CredentialTypeSSHKey
}
if (t & CredentialTypeSSHCustom) != 0 {
parts = append(parts, "SSHCustom")
t &= ^CredentialTypeSSHCustom
}
if (t & CredentialTypeDefault) != 0 {
parts = append(parts, "Default")
t &= ^CredentialTypeDefault
}
if (t & CredentialTypeSSHInteractive) != 0 {
parts = append(parts, "SSHInteractive")
t &= ^CredentialTypeSSHInteractive
}
if (t & CredentialTypeUsername) != 0 {
parts = append(parts, "Username")
t &= ^CredentialTypeUsername
}
if (t & CredentialTypeSSHMemory) != 0 {
parts = append(parts, "SSHMemory")
t &= ^CredentialTypeSSHMemory
}
if t != 0 {
parts = append(parts, fmt.Sprintf("CredentialType(%#x)", t))
}
return strings.Join(parts, "|")
}
type Credential struct {
doNotCompare
ptr *C.git_credential
}
func newCredential() *Credential {
cred := &Credential{}
runtime.SetFinalizer(cred, (*Credential).Free)
return cred
}
func (o *Credential) HasUsername() bool {
if C.git_credential_has_username(o.ptr) == 1 {
return true
}
return false
}
func (o *Credential) Type() CredentialType {
return (CredentialType)(C._go_git_credential_credtype(o.ptr))
}
func (o *Credential) Free() {
C.git_credential_free(o.ptr)
runtime.SetFinalizer(o, nil)
o.ptr = nil
}
// GetUserpassPlaintext returns the plaintext username/password combination stored in the Cred.
func (o *Credential) GetUserpassPlaintext() (username, password string, err error) {
if o.Type() != CredentialTypeUserpassPlaintext {
err = errors.New("credential is not userpass plaintext")
return
}
plaintextCredPtr := (*C.git_cred_userpass_plaintext)(unsafe.Pointer(o.ptr))
username = C.GoString(plaintextCredPtr.username)
password = C.GoString(plaintextCredPtr.password)
return
}
// GetSSHKey returns the SSH-specific key information from the Cred object.
func (o *Credential) GetSSHKey() (username, publickey, privatekey, passphrase string, err error) {
if o.Type() != CredentialTypeSSHKey && o.Type() != CredentialTypeSSHMemory {
err = fmt.Errorf("credential is not an SSH key: %v", o.Type())
return
}
sshKeyCredPtr := (*C.git_cred_ssh_key)(unsafe.Pointer(o.ptr))
username = C.GoString(sshKeyCredPtr.username)
publickey = C.GoString(sshKeyCredPtr.publickey)
privatekey = C.GoString(sshKeyCredPtr.privatekey)
passphrase = C.GoString(sshKeyCredPtr.passphrase)
return
}
func NewCredentialUsername(username string) (*Credential, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cred := newCredential()
cusername := C.CString(username)
ret := C.git_credential_username_new(&cred.ptr, cusername)
if ret != 0 {
cred.Free()
return nil, MakeGitError(ret)
}
return cred, nil
}
func NewCredentialUserpassPlaintext(username string, password string) (*Credential, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cred := newCredential()
cusername := C.CString(username)
defer C.free(unsafe.Pointer(cusername))
cpassword := C.CString(password)
defer C.free(unsafe.Pointer(cpassword))
ret := C.git_credential_userpass_plaintext_new(&cred.ptr, cusername, cpassword)
if ret != 0 {
cred.Free()
return nil, MakeGitError(ret)
}
return cred, nil
}
// NewCredentialSSHKey creates new ssh credentials reading the public and private keys
// from the file system.
func NewCredentialSSHKey(username string, publicKeyPath string, privateKeyPath string, passphrase string) (*Credential, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cred := newCredential()
cusername := C.CString(username)
defer C.free(unsafe.Pointer(cusername))
cpublickey := C.CString(publicKeyPath)
defer C.free(unsafe.Pointer(cpublickey))
cprivatekey := C.CString(privateKeyPath)
defer C.free(unsafe.Pointer(cprivatekey))
cpassphrase := C.CString(passphrase)
defer C.free(unsafe.Pointer(cpassphrase))
ret := C.git_credential_ssh_key_new(&cred.ptr, cusername, cpublickey, cprivatekey, cpassphrase)
if ret != 0 {
cred.Free()
return nil, MakeGitError(ret)
}
return cred, nil
}
// NewCredentialSSHKeyFromMemory creates new ssh credentials using the publicKey and privateKey
// arguments as the values for the public and private keys.
func NewCredentialSSHKeyFromMemory(username string, publicKey string, privateKey string, passphrase string) (*Credential, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cred := newCredential()
cusername := C.CString(username)
defer C.free(unsafe.Pointer(cusername))
cpublickey := C.CString(publicKey)
defer C.free(unsafe.Pointer(cpublickey))
cprivatekey := C.CString(privateKey)
defer C.free(unsafe.Pointer(cprivatekey))
cpassphrase := C.CString(passphrase)
defer C.free(unsafe.Pointer(cpassphrase))
ret := C.git_credential_ssh_key_memory_new(&cred.ptr, cusername, cpublickey, cprivatekey, cpassphrase)
if ret != 0 {
cred.Free()
return nil, MakeGitError(ret)
}
return cred, nil
}
func NewCredentialSSHKeyFromAgent(username string) (*Credential, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cred := newCredential()
cusername := C.CString(username)
defer C.free(unsafe.Pointer(cusername))
ret := C.git_credential_ssh_key_from_agent(&cred.ptr, cusername)
if ret != 0 {
cred.Free()
return nil, MakeGitError(ret)
}
return cred, nil
}
type credentialSSHCustomData struct {
signer ssh.Signer
}
//export credentialSSHCustomFree
func credentialSSHCustomFree(cred *C.git_credential_ssh_custom) {
if cred == nil {
return
}
C.free(unsafe.Pointer(cred.username))
C.free(unsafe.Pointer(cred.publickey))
pointerHandles.Untrack(cred.payload)
C.free(unsafe.Pointer(cred))
}
//export credentialSSHSignCallback
func credentialSSHSignCallback(
errorMessage **C.char,
sig **C.uchar,
sig_len *C.size_t,
data *C.uchar,
data_len C.size_t,
handle unsafe.Pointer,
) C.int {
signer := pointerHandles.Get(handle).(*credentialSSHCustomData).signer
signature, err := signer.Sign(rand.Reader, C.GoBytes(unsafe.Pointer(data), C.int(data_len)))
if err != nil {
return setCallbackError(errorMessage, err)
}
*sig = (*C.uchar)(C.CBytes(signature.Blob))
*sig_len = C.size_t(len(signature.Blob))
return C.int(ErrorCodeOK)
}
// NewCredentialSSHKeyFromSigner creates new SSH credentials using the provided signer.
func NewCredentialSSHKeyFromSigner(username string, signer ssh.Signer) (*Credential, error) {
publicKey := signer.PublicKey().Marshal()
ccred := (*C.git_credential_ssh_custom)(C.calloc(1, C.size_t(unsafe.Sizeof(C.git_credential_ssh_custom{}))))
ccred.parent.credtype = C.GIT_CREDENTIAL_SSH_CUSTOM
ccred.username = C.CString(username)
ccred.publickey = (*C.char)(C.CBytes(publicKey))
ccred.publickey_len = C.size_t(len(publicKey))
C._go_git_populate_credential_ssh_custom(ccred)
data := credentialSSHCustomData{
signer: signer,
}
ccred.payload = pointerHandles.Track(&data)
cred := newCredential()
cred.ptr = &ccred.parent
return cred, nil
}
func NewCredentialDefault() (*Credential, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cred := newCredential()
ret := C.git_credential_default_new(&cred.ptr)
if ret != 0 {
cred.Free()
return nil, MakeGitError(ret)
}
return cred, nil
}

View File

@ -1,33 +0,0 @@
// Code generated by "stringer -type Delta -trimprefix Delta -tags static"; DO NOT EDIT.
package git
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[DeltaUnmodified-0]
_ = x[DeltaAdded-1]
_ = x[DeltaDeleted-2]
_ = x[DeltaModified-3]
_ = x[DeltaRenamed-4]
_ = x[DeltaCopied-5]
_ = x[DeltaIgnored-6]
_ = x[DeltaUntracked-7]
_ = x[DeltaTypeChange-8]
_ = x[DeltaUnreadable-9]
_ = x[DeltaConflicted-10]
}
const _Delta_name = "UnmodifiedAddedDeletedModifiedRenamedCopiedIgnoredUntrackedTypeChangeUnreadableConflicted"
var _Delta_index = [...]uint8{0, 10, 15, 22, 30, 37, 43, 50, 59, 69, 79, 89}
func (i Delta) String() string {
if i < 0 || i >= Delta(len(_Delta_index)-1) {
return "Delta(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Delta_name[_Delta_index[i]:_Delta_index[i+1]]
}

View File

@ -1,271 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"unsafe"
)
// The constants, functions, and types in this files are slated for deprecation
// in the next major version.
// blob.go
// Deprecated: BlobChunkCallback is not used.
type BlobChunkCallback func(maxLen int) ([]byte, error)
// Deprecated: BlobCallbackData is not used.
type BlobCallbackData struct {
Callback BlobChunkCallback
Error error
}
// checkout.go
// Deprecated: CheckoutOpts is a deprecated alias of CheckoutOptions.
type CheckoutOpts = CheckoutOptions
// credentials.go
// Deprecated: CredType is a deprecated alias of CredentialType
type CredType = CredentialType
const (
CredTypeUserpassPlaintext = CredentialTypeUserpassPlaintext
CredTypeSshKey = CredentialTypeSSHKey
CredTypeSshCustom = CredentialTypeSSHCustom
CredTypeDefault = CredentialTypeDefault
)
// Deprecated: Cred is a deprecated alias of Credential
type Cred = Credential
// Deprecated: NewCredUsername is a deprecated alias of NewCredentialUsername.
func NewCredUsername(username string) (*Cred, error) {
return NewCredentialUsername(username)
}
// Deprecated: NewCredUserpassPlaintext is a deprecated alias of NewCredentialUserpassPlaintext.
func NewCredUserpassPlaintext(username string, password string) (*Cred, error) {
return NewCredentialUserpassPlaintext(username, password)
}
// Deprecated: NewCredSshKey is a deprecated alias of NewCredentialSshKey.
func NewCredSshKey(username string, publicKeyPath string, privateKeyPath string, passphrase string) (*Cred, error) {
return NewCredentialSSHKey(username, publicKeyPath, privateKeyPath, passphrase)
}
// Deprecated: NewCredSshKeyFromMemory is a deprecated alias of NewCredentialSSHKeyFromMemory.
func NewCredSshKeyFromMemory(username string, publicKey string, privateKey string, passphrase string) (*Cred, error) {
return NewCredentialSSHKeyFromMemory(username, publicKey, privateKey, passphrase)
}
// Deprecated: NewCredSshKeyFromAgent is a deprecated alias of NewCredentialSSHFromAgent.
func NewCredSshKeyFromAgent(username string) (*Cred, error) {
return NewCredentialSSHKeyFromAgent(username)
}
// Deprecated: NewCredDefault is a deprecated alias fof NewCredentialDefault.
func NewCredDefault() (*Cred, error) {
return NewCredentialDefault()
}
// diff.go
const (
// Deprecated: DiffIgnoreWhitespaceEol is a deprecated alias of DiffIgnoreWhitespaceEOL.
DiffIgnoreWitespaceEol = DiffIgnoreWhitespaceEOL
)
// features.go
const (
// Deprecated: FeatureHttps is a deprecated alias of FeatureHTTPS.
FeatureHttps = FeatureHTTPS
// Deprecated: FeatureSsh is a deprecated alias of FeatureSSH.
FeatureSsh = FeatureSSH
)
// git.go
const (
// Deprecated: ErrClassNone is a deprecated alias of ErrorClassNone.
ErrClassNone = ErrorClassNone
// Deprecated: ErrClassNoMemory is a deprecated alias of ErrorClassNoMemory.
ErrClassNoMemory = ErrorClassNoMemory
// Deprecated: ErrClassOs is a deprecated alias of ErrorClassOS.
ErrClassOs = ErrorClassOS
// Deprecated: ErrClassInvalid is a deprecated alias of ErrorClassInvalid.
ErrClassInvalid = ErrorClassInvalid
// Deprecated: ErrClassReference is a deprecated alias of ErrorClassReference.
ErrClassReference = ErrorClassReference
// Deprecated: ErrClassZlib is a deprecated alias of ErrorClassZlib.
ErrClassZlib = ErrorClassZlib
// Deprecated: ErrClassRepository is a deprecated alias of ErrorClassRepository.
ErrClassRepository = ErrorClassRepository
// Deprecated: ErrClassConfig is a deprecated alias of ErrorClassConfig.
ErrClassConfig = ErrorClassConfig
// Deprecated: ErrClassRegex is a deprecated alias of ErrorClassRegex.
ErrClassRegex = ErrorClassRegex
// Deprecated: ErrClassOdb is a deprecated alias of ErrorClassOdb.
ErrClassOdb = ErrorClassOdb
// Deprecated: ErrClassIndex is a deprecated alias of ErrorClassIndex.
ErrClassIndex = ErrorClassIndex
// Deprecated: ErrClassObject is a deprecated alias of ErrorClassObject.
ErrClassObject = ErrorClassObject
// Deprecated: ErrClassNet is a deprecated alias of ErrorClassNet.
ErrClassNet = ErrorClassNet
// Deprecated: ErrClassTag is a deprecated alias of ErrorClassTag.
ErrClassTag = ErrorClassTag
// Deprecated: ErrClassTree is a deprecated alias of ErrorClassTree.
ErrClassTree = ErrorClassTree
// Deprecated: ErrClassIndexer is a deprecated alias of ErrorClassIndexer.
ErrClassIndexer = ErrorClassIndexer
// Deprecated: ErrClassSSL is a deprecated alias of ErrorClassSSL.
ErrClassSSL = ErrorClassSSL
// Deprecated: ErrClassSubmodule is a deprecated alias of ErrorClassSubmodule.
ErrClassSubmodule = ErrorClassSubmodule
// Deprecated: ErrClassThread is a deprecated alias of ErrorClassThread.
ErrClassThread = ErrorClassThread
// Deprecated: ErrClassStash is a deprecated alias of ErrorClassStash.
ErrClassStash = ErrorClassStash
// Deprecated: ErrClassCheckout is a deprecated alias of ErrorClassCheckout.
ErrClassCheckout = ErrorClassCheckout
// Deprecated: ErrClassFetchHead is a deprecated alias of ErrorClassFetchHead.
ErrClassFetchHead = ErrorClassFetchHead
// Deprecated: ErrClassMerge is a deprecated alias of ErrorClassMerge.
ErrClassMerge = ErrorClassMerge
// Deprecated: ErrClassSsh is a deprecated alias of ErrorClassSSH.
ErrClassSsh = ErrorClassSSH
// Deprecated: ErrClassFilter is a deprecated alias of ErrorClassFilter.
ErrClassFilter = ErrorClassFilter
// Deprecated: ErrClassRevert is a deprecated alias of ErrorClassRevert.
ErrClassRevert = ErrorClassRevert
// Deprecated: ErrClassCallback is a deprecated alias of ErrorClassCallback.
ErrClassCallback = ErrorClassCallback
// Deprecated: ErrClassRebase is a deprecated alias of ErrorClassRebase.
ErrClassRebase = ErrorClassRebase
// Deprecated: ErrClassPatch is a deprecated alias of ErrorClassPatch.
ErrClassPatch = ErrorClassPatch
)
const (
// Deprecated: ErrOk is a deprecated alias of ErrorCodeOK.
ErrOk = ErrorCodeOK
// Deprecated: ErrGeneric is a deprecated alias of ErrorCodeGeneric.
ErrGeneric = ErrorCodeGeneric
// Deprecated: ErrNotFound is a deprecated alias of ErrorCodeNotFound.
ErrNotFound = ErrorCodeNotFound
// Deprecated: ErrExists is a deprecated alias of ErrorCodeExists.
ErrExists = ErrorCodeExists
// Deprecated: ErrAmbiguous is a deprecated alias of ErrorCodeAmbiguous.
ErrAmbiguous = ErrorCodeAmbiguous
// Deprecated: ErrAmbigious is a deprecated alias of ErrorCodeAmbiguous.
ErrAmbigious = ErrorCodeAmbiguous
// Deprecated: ErrBuffs is a deprecated alias of ErrorCodeBuffs.
ErrBuffs = ErrorCodeBuffs
// Deprecated: ErrUser is a deprecated alias of ErrorCodeUser.
ErrUser = ErrorCodeUser
// Deprecated: ErrBareRepo is a deprecated alias of ErrorCodeBareRepo.
ErrBareRepo = ErrorCodeBareRepo
// Deprecated: ErrUnbornBranch is a deprecated alias of ErrorCodeUnbornBranch.
ErrUnbornBranch = ErrorCodeUnbornBranch
// Deprecated: ErrUnmerged is a deprecated alias of ErrorCodeUnmerged.
ErrUnmerged = ErrorCodeUnmerged
// Deprecated: ErrNonFastForward is a deprecated alias of ErrorCodeNonFastForward.
ErrNonFastForward = ErrorCodeNonFastForward
// Deprecated: ErrInvalidSpec is a deprecated alias of ErrorCodeInvalidSpec.
ErrInvalidSpec = ErrorCodeInvalidSpec
// Deprecated: ErrConflict is a deprecated alias of ErrorCodeConflict.
ErrConflict = ErrorCodeConflict
// Deprecated: ErrLocked is a deprecated alias of ErrorCodeLocked.
ErrLocked = ErrorCodeLocked
// Deprecated: ErrModified is a deprecated alias of ErrorCodeModified.
ErrModified = ErrorCodeModified
// Deprecated: ErrAuth is a deprecated alias of ErrorCodeAuth.
ErrAuth = ErrorCodeAuth
// Deprecated: ErrCertificate is a deprecated alias of ErrorCodeCertificate.
ErrCertificate = ErrorCodeCertificate
// Deprecated: ErrApplied is a deprecated alias of ErrorCodeApplied.
ErrApplied = ErrorCodeApplied
// Deprecated: ErrPeel is a deprecated alias of ErrorCodePeel.
ErrPeel = ErrorCodePeel
// Deprecated: ErrEOF is a deprecated alias of ErrorCodeEOF.
ErrEOF = ErrorCodeEOF
// Deprecated: ErrUncommitted is a deprecated alias of ErrorCodeUncommitted.
ErrUncommitted = ErrorCodeUncommitted
// Deprecated: ErrDirectory is a deprecated alias of ErrorCodeDirectory.
ErrDirectory = ErrorCodeDirectory
// Deprecated: ErrMergeConflict is a deprecated alias of ErrorCodeMergeConflict.
ErrMergeConflict = ErrorCodeMergeConflict
// Deprecated: ErrPassthrough is a deprecated alias of ErrorCodePassthrough.
ErrPassthrough = ErrorCodePassthrough
// Deprecated: ErrIterOver is a deprecated alias of ErrorCodeIterOver.
ErrIterOver = ErrorCodeIterOver
// Deprecated: ErrApplyFail is a deprecated alias of ErrorCodeApplyFail.
ErrApplyFail = ErrorCodeApplyFail
)
// index.go
// Deprecated: IndexAddOpts is a deprecated alias of IndexAddOption.
type IndexAddOpts = IndexAddOption
// Deprecated: IndexStageOpts is a deprecated alias of IndexStageState.
type IndexStageOpts = IndexStageState
// submodule.go
// Deprecated: SubmoduleCbk is a deprecated alias of SubmoduleCallback.
type SubmoduleCbk = SubmoduleCallback
// Deprecated: SubmoduleVisitor is not used.
func SubmoduleVisitor(csub unsafe.Pointer, name *C.char, handle unsafe.Pointer) C.int {
sub := &Submodule{ptr: (*C.git_submodule)(csub)}
callback, ok := pointerHandles.Get(handle).(SubmoduleCallback)
if !ok {
panic("invalid submodule visitor callback")
}
err := callback(sub, C.GoString(name))
if err != nil {
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}
// reference.go
// Deprecated: ReferenceIsValidName is a deprecated alias of ReferenceNameIsValid.
func ReferenceIsValidName(name string) bool {
valid, _ := ReferenceNameIsValid(name)
return valid
}
// remote.go
// Deprecated: RemoteIsValidName is a deprecated alias of RemoteNameIsValid.
func RemoteIsValidName(name string) bool {
valid, _ := RemoteNameIsValid(name)
return valid
}
// tree.go
// Deprecated: CallbackGitTreeWalk is not used.
func CallbackGitTreeWalk(_root *C.char, entry *C.git_tree_entry, ptr unsafe.Pointer) C.int {
root := C.GoString(_root)
callback, ok := pointerHandles.Get(ptr).(TreeWalkCallback)
if !ok {
panic("invalid treewalk callback")
}
err := callback(root, newTreeEntry(entry))
if err != nil {
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}

View File

@ -1,226 +0,0 @@
package git
/*
#include <git2.h>
*/
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_options_init(&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_format_options_init(&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.ptr, cDescribeOpts)
runtime.KeepAlive(c)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newDescribeResultFromC(resultPtr), nil
}
// DescribeWorkdir describes the working tree. It means describe HEAD
// and appends <mark> (-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)
runtime.KeepAlive(repo)
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 {
doNotCompare
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)
runtime.KeepAlive(result)
if ecode < 0 {
return "", MakeGitError(ecode)
}
defer C.git_buf_dispose(&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
}

View File

@ -1,109 +0,0 @@
package git
import (
"path"
"runtime"
"strings"
"testing"
)
func TestDescribeCommit(t *testing.T) {
t.Parallel()
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)
expectedString := "no reference found, cannot describe anything"
if !ok {
t.Fatalf("Unable to get caller")
}
if err == nil || !strings.Contains(err.Error(), expectedString) {
t.Fatalf(
"%s:%v: was expecting error %v, got %v",
path.Base(file),
line,
expectedString,
err,
)
}
}

1103
diff.go

File diff suppressed because it is too large Load Diff

View File

@ -1,685 +0,0 @@
package git
import (
"errors"
"fmt"
"io/ioutil"
"path"
"reflect"
"strings"
"testing"
)
func TestFindSimilar(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
originalTree, newTree := createTestTrees(t, repo)
diffOpt, _ := DefaultDiffOptions()
diff, err := repo.DiffTreeToTree(originalTree, newTree, &diffOpt)
checkFatal(t, err)
if diff == nil {
t.Fatal("no diff returned")
}
findOpts, err := DefaultDiffFindOptions()
checkFatal(t, err)
findOpts.Flags = DiffFindBreakRewrites
err = diff.FindSimilar(&findOpts)
checkFatal(t, err)
numDiffs := 0
numAdded := 0
numDeleted := 0
err = diff.ForEach(func(file DiffDelta, progress float64) (DiffForEachHunkCallback, error) {
numDiffs++
switch file.Status {
case DeltaAdded:
numAdded++
case DeltaDeleted:
numDeleted++
}
return func(hunk DiffHunk) (DiffForEachLineCallback, error) {
return func(line DiffLine) error {
return nil
}, nil
}, nil
}, DiffDetailLines)
if numDiffs != 2 {
t.Fatal("Incorrect number of files in diff")
}
if numAdded != 1 {
t.Fatal("Incorrect number of new files in diff")
}
if numDeleted != 1 {
t.Fatal("Incorrect number of deleted files in diff")
}
}
func TestDiffTreeToTree(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
originalTree, newTree := createTestTrees(t, repo)
callbackInvoked := false
opts := DiffOptions{
NotifyCallback: func(diffSoFar *Diff, delta DiffDelta, matchedPathSpec string) error {
callbackInvoked = true
return nil
},
OldPrefix: "x1/",
NewPrefix: "y1/",
}
diff, err := repo.DiffTreeToTree(originalTree, newTree, &opts)
checkFatal(t, err)
if !callbackInvoked {
t.Fatal("callback not invoked")
}
if diff == nil {
t.Fatal("no diff returned")
}
files := make([]string, 0)
hunks := make([]DiffHunk, 0)
lines := make([]DiffLine, 0)
patches := make([]string, 0)
err = diff.ForEach(func(file DiffDelta, progress float64) (DiffForEachHunkCallback, error) {
patch, err := diff.Patch(len(patches))
if err != nil {
return nil, err
}
defer patch.Free()
patchStr, err := patch.String()
if err != nil {
return nil, err
}
patches = append(patches, patchStr)
files = append(files, file.OldFile.Path)
return func(hunk DiffHunk) (DiffForEachLineCallback, error) {
hunks = append(hunks, hunk)
return func(line DiffLine) error {
lines = append(lines, line)
return nil
}, nil
}, nil
}, DiffDetailLines)
checkFatal(t, err)
if len(files) != 1 {
t.Fatal("Incorrect number of files in diff")
}
if files[0] != "README" {
t.Fatal("File in diff was expected to be README")
}
if len(hunks) != 1 {
t.Fatal("Incorrect number of hunks in diff")
}
if hunks[0].OldStart != 1 || hunks[0].NewStart != 1 {
t.Fatal("Incorrect hunk")
}
if len(lines) != 2 {
t.Fatal("Incorrect number of lines in diff")
}
if lines[0].Content != "foo\n" {
t.Fatal("Incorrect lines in diff")
}
if lines[1].Content != "file changed\n" {
t.Fatal("Incorrect lines in diff")
}
if want1, want2 := "x1/README", "y1/README"; !strings.Contains(patches[0], want1) || !strings.Contains(patches[0], want2) {
t.Errorf("Diff patch doesn't contain %q or %q\n\n%s", want1, want2, patches[0])
}
stats, err := diff.Stats()
checkFatal(t, err)
if stats.Insertions() != 1 {
t.Fatal("Incorrect number of insertions in diff")
}
if stats.Deletions() != 1 {
t.Fatal("Incorrect number of deletions in diff")
}
if stats.FilesChanged() != 1 {
t.Fatal("Incorrect number of changed files in diff")
}
errTest := errors.New("test error")
err = diff.ForEach(func(file DiffDelta, progress float64) (DiffForEachHunkCallback, error) {
return nil, errTest
}, DiffDetailLines)
if err != errTest {
t.Fatalf("Expected custom error to be returned, got %v, want %v", err, errTest)
}
}
func createTestTrees(t *testing.T, repo *Repository) (originalTree *Tree, newTree *Tree) {
var err error
_, originalTreeId := seedTestRepo(t, repo)
originalTree, err = repo.LookupTree(originalTreeId)
checkFatal(t, err)
_, newTreeId := updateReadme(t, repo, "file changed\n")
newTree, err = repo.LookupTree(newTreeId)
checkFatal(t, err)
return originalTree, newTree
}
func TestDiffBlobs(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
odb, err := repo.Odb()
checkFatal(t, err)
id1, err := odb.Write([]byte("hello\nhello\n"), ObjectBlob)
checkFatal(t, err)
id2, err := odb.Write([]byte("hallo\nhallo\n"), ObjectBlob)
checkFatal(t, err)
blob1, err := repo.LookupBlob(id1)
checkFatal(t, err)
blob2, err := repo.LookupBlob(id2)
checkFatal(t, err)
var files, hunks, lines int
err = DiffBlobs(blob1, "hi", blob2, "hi", nil,
func(delta DiffDelta, progress float64) (DiffForEachHunkCallback, error) {
files++
return func(hunk DiffHunk) (DiffForEachLineCallback, error) {
hunks++
return func(line DiffLine) error {
lines++
return nil
}, nil
}, nil
},
DiffDetailLines)
if files != 1 {
t.Fatal("Bad number of files iterated")
}
if hunks != 1 {
t.Fatal("Bad number of hunks iterated")
}
// two removals, two additions
if lines != 4 {
t.Fatalf("Bad number of lines iterated")
}
}
func TestApplyDiffAddfile(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
addFirstFileCommit, addFirstFileTree := addAndGetTree(t, repo, "file1", `hello`)
defer addFirstFileCommit.Free()
defer addFirstFileTree.Free()
addSecondFileCommit, addSecondFileTree := addAndGetTree(t, repo, "file2", `hello2`)
defer addSecondFileCommit.Free()
defer addSecondFileTree.Free()
diff, err := repo.DiffTreeToTree(addFirstFileTree, addSecondFileTree, nil)
checkFatal(t, err)
defer diff.Free()
t.Run("check does not apply to current tree because file exists", func(t *testing.T) {
err = repo.ResetToCommit(addSecondFileCommit, ResetHard, &CheckoutOptions{})
checkFatal(t, err)
err = repo.ApplyDiff(diff, ApplyLocationBoth, nil)
if err == nil {
t.Error("expecting applying patch to current repo to fail")
}
})
t.Run("check apply to correct commit", func(t *testing.T) {
err = repo.ResetToCommit(addFirstFileCommit, ResetHard, &CheckoutOptions{})
checkFatal(t, err)
err = repo.ApplyDiff(diff, ApplyLocationBoth, nil)
checkFatal(t, err)
t.Run("Check that diff only changed one file", func(t *testing.T) {
checkSecondFileStaged(t, repo)
index, err := repo.Index()
checkFatal(t, err)
defer index.Free()
newTreeOID, err := index.WriteTreeTo(repo)
checkFatal(t, err)
newTree, err := repo.LookupTree(newTreeOID)
checkFatal(t, err)
defer newTree.Free()
_, err = repo.CreateCommit("HEAD", signature(), signature(), fmt.Sprintf("patch apply"), newTree, addFirstFileCommit)
checkFatal(t, err)
})
t.Run("test applying patch produced the same diff", func(t *testing.T) {
head, err := repo.Head()
checkFatal(t, err)
commit, err := repo.LookupCommit(head.Target())
checkFatal(t, err)
defer commit.Free()
tree, err := commit.Tree()
checkFatal(t, err)
defer tree.Free()
newDiff, err := repo.DiffTreeToTree(addFirstFileTree, tree, nil)
checkFatal(t, err)
defer newDiff.Free()
raw1b, err := diff.ToBuf(DiffFormatPatch)
checkFatal(t, err)
raw2b, err := newDiff.ToBuf(DiffFormatPatch)
checkFatal(t, err)
raw1 := string(raw1b)
raw2 := string(raw2b)
if raw1 != raw2 {
t.Error("diffs should be the same")
}
})
})
t.Run("check convert to raw buffer and apply", func(t *testing.T) {
err = repo.ResetToCommit(addFirstFileCommit, ResetHard, &CheckoutOptions{})
checkFatal(t, err)
raw, err := diff.ToBuf(DiffFormatPatch)
checkFatal(t, err)
if len(raw) == 0 {
t.Error("empty diff created")
}
diff2, err := DiffFromBuffer(raw, repo)
checkFatal(t, err)
defer diff2.Free()
err = repo.ApplyDiff(diff2, ApplyLocationBoth, nil)
checkFatal(t, err)
})
t.Run("check apply callbacks work", func(t *testing.T) {
// reset the state and get new default options for test
resetAndGetOpts := func(t *testing.T) *ApplyOptions {
err = repo.ResetToCommit(addFirstFileCommit, ResetHard, &CheckoutOptions{})
checkFatal(t, err)
opts, err := DefaultApplyOptions()
checkFatal(t, err)
return opts
}
t.Run("Check hunk callback working applies patch", func(t *testing.T) {
opts := resetAndGetOpts(t)
called := false
opts.ApplyHunkCallback = func(hunk *DiffHunk) (apply bool, err error) {
called = true
return true, nil
}
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
checkFatal(t, err)
if called == false {
t.Error("apply hunk callback was not called")
}
checkSecondFileStaged(t, repo)
})
t.Run("Check delta callback working applies patch", func(t *testing.T) {
opts := resetAndGetOpts(t)
called := false
opts.ApplyDeltaCallback = func(hunk *DiffDelta) (apply bool, err error) {
if hunk.NewFile.Path != "file2" {
t.Error("Unexpected delta in diff application")
}
called = true
return true, nil
}
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
checkFatal(t, err)
if called == false {
t.Error("apply hunk callback was not called")
}
checkSecondFileStaged(t, repo)
})
t.Run("Check delta callback returning false does not apply patch", func(t *testing.T) {
opts := resetAndGetOpts(t)
called := false
opts.ApplyDeltaCallback = func(hunk *DiffDelta) (apply bool, err error) {
if hunk.NewFile.Path != "file2" {
t.Error("Unexpected hunk in diff application")
}
called = true
return false, nil
}
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
checkFatal(t, err)
if called == false {
t.Error("apply hunk callback was not called")
}
checkNoFilesStaged(t, repo)
})
t.Run("Check hunk callback returning causes application to fail", func(t *testing.T) {
opts := resetAndGetOpts(t)
called := false
opts.ApplyHunkCallback = func(hunk *DiffHunk) (apply bool, err error) {
called = true
return false, errors.New("something happened")
}
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
if err == nil {
t.Error("expected an error after trying to apply")
}
if called == false {
t.Error("apply hunk callback was not called")
}
checkNoFilesStaged(t, repo)
})
t.Run("Check delta callback returning causes application to fail", func(t *testing.T) {
opts := resetAndGetOpts(t)
called := false
opts.ApplyDeltaCallback = func(hunk *DiffDelta) (apply bool, err error) {
if hunk.NewFile.Path != "file2" {
t.Error("Unexpected delta in diff application")
}
called = true
return false, errors.New("something happened")
}
err = repo.ApplyDiff(diff, ApplyLocationBoth, opts)
if err == nil {
t.Error("expected an error after trying to apply")
}
if called == false {
t.Error("apply hunk callback was not called")
}
checkNoFilesStaged(t, repo)
})
})
}
func TestApplyToTree(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
commitA, treeA := addAndGetTree(t, repo, "file", "a")
defer commitA.Free()
defer treeA.Free()
commitB, treeB := addAndGetTree(t, repo, "file", "b")
defer commitB.Free()
defer treeB.Free()
commitC, treeC := addAndGetTree(t, repo, "file", "c")
defer commitC.Free()
defer treeC.Free()
diffAB, err := repo.DiffTreeToTree(treeA, treeB, nil)
checkFatal(t, err)
diffAC, err := repo.DiffTreeToTree(treeA, treeC, nil)
checkFatal(t, err)
errMessageDropped := errors.New("message dropped")
for _, tc := range []struct {
name string
tree *Tree
diff *Diff
applyHunkCallback ApplyHunkCallback
applyDeltaCallback ApplyDeltaCallback
err error
expectedDiff *Diff
}{
{
name: "applying patch produces the same diff",
tree: treeA,
diff: diffAB,
expectedDiff: diffAB,
},
{
name: "applying a conflicting patch errors",
tree: treeB,
diff: diffAC,
err: &GitError{
Message: "hunk at line 1 did not apply",
Code: ErrorCodeApplyFail,
Class: ErrorClassPatch,
},
},
{
name: "callbacks succeeding apply the diff",
tree: treeA,
diff: diffAB,
applyHunkCallback: func(*DiffHunk) (bool, error) { return true, nil },
applyDeltaCallback: func(*DiffDelta) (bool, error) { return true, nil },
expectedDiff: diffAB,
},
{
name: "hunk callback returning false does not apply",
tree: treeA,
diff: diffAB,
applyHunkCallback: func(*DiffHunk) (bool, error) { return false, nil },
},
{
name: "hunk callback erroring fails the call",
tree: treeA,
diff: diffAB,
applyHunkCallback: func(*DiffHunk) (bool, error) { return true, errMessageDropped },
err: errMessageDropped,
},
{
name: "delta callback returning false does not apply",
tree: treeA,
diff: diffAB,
applyDeltaCallback: func(*DiffDelta) (bool, error) { return false, nil },
},
{
name: "delta callback erroring fails the call",
tree: treeA,
diff: diffAB,
applyDeltaCallback: func(*DiffDelta) (bool, error) { return true, errMessageDropped },
err: errMessageDropped,
},
} {
t.Run(tc.name, func(t *testing.T) {
opts, err := DefaultApplyOptions()
checkFatal(t, err)
opts.ApplyHunkCallback = tc.applyHunkCallback
opts.ApplyDeltaCallback = tc.applyDeltaCallback
index, err := repo.ApplyToTree(tc.diff, tc.tree, opts)
if tc.err != nil {
if !reflect.DeepEqual(tc.err, err) {
t.Fatalf("expected error %q but got %q", tc.err, err)
}
return
}
checkFatal(t, err)
patchedTreeOID, err := index.WriteTreeTo(repo)
checkFatal(t, err)
patchedTree, err := repo.LookupTree(patchedTreeOID)
checkFatal(t, err)
patchedDiff, err := repo.DiffTreeToTree(tc.tree, patchedTree, nil)
checkFatal(t, err)
appliedRaw, err := patchedDiff.ToBuf(DiffFormatPatch)
checkFatal(t, err)
if tc.expectedDiff == nil {
if len(appliedRaw) > 0 {
t.Fatalf("expected no diff but got: %s", appliedRaw)
}
return
}
expectedDiff, err := tc.expectedDiff.ToBuf(DiffFormatPatch)
checkFatal(t, err)
if string(expectedDiff) != string(appliedRaw) {
t.Fatalf("diffs do not match:\nexpected: %s\n\nactual: %s", expectedDiff, appliedRaw)
}
})
}
}
// checkSecondFileStaged checks that there is a single file called "file2" uncommitted in the repo
func checkSecondFileStaged(t *testing.T, repo *Repository) {
opts := StatusOptions{
Show: StatusShowIndexAndWorkdir,
Flags: StatusOptIncludeUntracked,
}
statuses, err := repo.StatusList(&opts)
checkFatal(t, err)
count, err := statuses.EntryCount()
checkFatal(t, err)
if count != 1 {
t.Error("diff should affect exactly one file")
}
if count == 0 {
t.Fatal("no statuses, cannot continue test")
}
entry, err := statuses.ByIndex(0)
checkFatal(t, err)
if entry.Status != StatusIndexNew {
t.Error("status should be 'new' as file has been added between commits")
}
if entry.HeadToIndex.NewFile.Path != "file2" {
t.Error("new file should be 'file2")
}
return
}
// checkNoFilesStaged checks that there is a single file called "file2" uncommitted in the repo
func checkNoFilesStaged(t *testing.T, repo *Repository) {
opts := StatusOptions{
Show: StatusShowIndexAndWorkdir,
Flags: StatusOptIncludeUntracked,
}
statuses, err := repo.StatusList(&opts)
checkFatal(t, err)
count, err := statuses.EntryCount()
checkFatal(t, err)
if count != 0 {
t.Error("files changed unexpectedly")
}
}
// addAndGetTree creates a file and commits it, returning the commit and tree
func addAndGetTree(t *testing.T, repo *Repository, filename string, content string) (*Commit, *Tree) {
headCommit, err := headCommit(repo)
checkFatal(t, err)
defer headCommit.Free()
p := repo.Path()
p = strings.TrimSuffix(p, ".git")
p = strings.TrimSuffix(p, ".git/")
err = ioutil.WriteFile(path.Join(p, filename), []byte((content)), 0777)
checkFatal(t, err)
index, err := repo.Index()
checkFatal(t, err)
defer index.Free()
err = index.AddByPath(filename)
checkFatal(t, err)
newTreeOID, err := index.WriteTreeTo(repo)
checkFatal(t, err)
newTree, err := repo.LookupTree(newTreeOID)
checkFatal(t, err)
defer newTree.Free()
commitId, err := repo.CreateCommit("HEAD", signature(), signature(), fmt.Sprintf("add %s", filename), newTree, headCommit)
checkFatal(t, err)
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
tree, err := commit.Tree()
checkFatal(t, err)
return commit, tree
}

View File

@ -1,56 +0,0 @@
// Code generated by "stringer -type DiffLineType -trimprefix DiffLine -tags static"; DO NOT EDIT.
package git
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[DiffLineContext-32]
_ = x[DiffLineAddition-43]
_ = x[DiffLineDeletion-45]
_ = x[DiffLineContextEOFNL-61]
_ = x[DiffLineAddEOFNL-62]
_ = x[DiffLineDelEOFNL-60]
_ = x[DiffLineFileHdr-70]
_ = x[DiffLineHunkHdr-72]
_ = x[DiffLineBinary-66]
}
const (
_DiffLineType_name_0 = "Context"
_DiffLineType_name_1 = "Addition"
_DiffLineType_name_2 = "Deletion"
_DiffLineType_name_3 = "DelEOFNLContextEOFNLAddEOFNL"
_DiffLineType_name_4 = "Binary"
_DiffLineType_name_5 = "FileHdr"
_DiffLineType_name_6 = "HunkHdr"
)
var (
_DiffLineType_index_3 = [...]uint8{0, 8, 20, 28}
)
func (i DiffLineType) String() string {
switch {
case i == 32:
return _DiffLineType_name_0
case i == 43:
return _DiffLineType_name_1
case i == 45:
return _DiffLineType_name_2
case 60 <= i && i <= 62:
i -= 60
return _DiffLineType_name_3[_DiffLineType_index_3[i]:_DiffLineType_index_3[i+1]]
case i == 66:
return _DiffLineType_name_4
case i == 70:
return _DiffLineType_name_5
case i == 72:
return _DiffLineType_name_6
default:
return "DiffLineType(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@ -1,63 +0,0 @@
// Code generated by "stringer -type ErrorClass -trimprefix ErrorClass -tags static"; DO NOT EDIT.
package git
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ErrorClassNone-0]
_ = x[ErrorClassNoMemory-1]
_ = x[ErrorClassOS-2]
_ = x[ErrorClassInvalid-3]
_ = x[ErrorClassReference-4]
_ = x[ErrorClassZlib-5]
_ = x[ErrorClassRepository-6]
_ = x[ErrorClassConfig-7]
_ = x[ErrorClassRegex-8]
_ = x[ErrorClassOdb-9]
_ = x[ErrorClassIndex-10]
_ = x[ErrorClassObject-11]
_ = x[ErrorClassNet-12]
_ = x[ErrorClassTag-13]
_ = x[ErrorClassTree-14]
_ = x[ErrorClassIndexer-15]
_ = x[ErrorClassSSL-16]
_ = x[ErrorClassSubmodule-17]
_ = x[ErrorClassThread-18]
_ = x[ErrorClassStash-19]
_ = x[ErrorClassCheckout-20]
_ = x[ErrorClassFetchHead-21]
_ = x[ErrorClassMerge-22]
_ = x[ErrorClassSSH-23]
_ = x[ErrorClassFilter-24]
_ = x[ErrorClassRevert-25]
_ = x[ErrorClassCallback-26]
_ = x[ErrorClassRebase-29]
_ = x[ErrorClassPatch-31]
}
const (
_ErrorClass_name_0 = "NoneNoMemoryOSInvalidReferenceZlibRepositoryConfigRegexOdbIndexObjectNetTagTreeIndexerSSLSubmoduleThreadStashCheckoutFetchHeadMergeSSHFilterRevertCallback"
_ErrorClass_name_1 = "Rebase"
_ErrorClass_name_2 = "Patch"
)
var (
_ErrorClass_index_0 = [...]uint8{0, 4, 12, 14, 21, 30, 34, 44, 50, 55, 58, 63, 69, 72, 75, 79, 86, 89, 98, 104, 109, 117, 126, 131, 134, 140, 146, 154}
)
func (i ErrorClass) String() string {
switch {
case 0 <= i && i <= 26:
return _ErrorClass_name_0[_ErrorClass_index_0[i]:_ErrorClass_index_0[i+1]]
case i == 29:
return _ErrorClass_name_1
case i == 31:
return _ErrorClass_name_2
default:
return "ErrorClass(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@ -1,69 +0,0 @@
// Code generated by "stringer -type ErrorCode -trimprefix ErrorCode -tags static"; DO NOT EDIT.
package git
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[ErrorCodeOK-0]
_ = x[ErrorCodeGeneric - -1]
_ = x[ErrorCodeNotFound - -3]
_ = x[ErrorCodeExists - -4]
_ = x[ErrorCodeAmbiguous - -5]
_ = x[ErrorCodeBuffs - -6]
_ = x[ErrorCodeUser - -7]
_ = x[ErrorCodeBareRepo - -8]
_ = x[ErrorCodeUnbornBranch - -9]
_ = x[ErrorCodeUnmerged - -10]
_ = x[ErrorCodeNonFastForward - -11]
_ = x[ErrorCodeInvalidSpec - -12]
_ = x[ErrorCodeConflict - -13]
_ = x[ErrorCodeLocked - -14]
_ = x[ErrorCodeModified - -15]
_ = x[ErrorCodeAuth - -16]
_ = x[ErrorCodeCertificate - -17]
_ = x[ErrorCodeApplied - -18]
_ = x[ErrorCodePeel - -19]
_ = x[ErrorCodeEOF - -20]
_ = x[ErrorCodeInvalid - -21]
_ = x[ErrorCodeUncommitted - -22]
_ = x[ErrorCodeDirectory - -23]
_ = x[ErrorCodeMergeConflict - -24]
_ = x[ErrorCodePassthrough - -30]
_ = x[ErrorCodeIterOver - -31]
_ = x[ErrorCodeRetry - -32]
_ = x[ErrorCodeMismatch - -33]
_ = x[ErrorCodeIndexDirty - -34]
_ = x[ErrorCodeApplyFail - -35]
}
const (
_ErrorCode_name_0 = "ApplyFailIndexDirtyMismatchRetryIterOverPassthrough"
_ErrorCode_name_1 = "MergeConflictDirectoryUncommittedInvalidEOFPeelAppliedCertificateAuthModifiedLockedConflictInvalidSpecNonFastForwardUnmergedUnbornBranchBareRepoUserBuffsAmbiguousExistsNotFound"
_ErrorCode_name_2 = "GenericOK"
)
var (
_ErrorCode_index_0 = [...]uint8{0, 9, 19, 27, 32, 40, 51}
_ErrorCode_index_1 = [...]uint8{0, 13, 22, 33, 40, 43, 47, 54, 65, 69, 77, 83, 91, 102, 116, 124, 136, 144, 148, 153, 162, 168, 176}
_ErrorCode_index_2 = [...]uint8{0, 7, 9}
)
func (i ErrorCode) String() string {
switch {
case -35 <= i && i <= -30:
i -= -35
return _ErrorCode_name_0[_ErrorCode_index_0[i]:_ErrorCode_index_0[i+1]]
case -24 <= i && i <= -3:
i -= -24
return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]]
case -1 <= i && i <= 0:
i -= -1
return _ErrorCode_name_2[_ErrorCode_index_2[i]:_ErrorCode_index_2[i+1]]
default:
return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@ -1,30 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
type Feature int
const (
// libgit2 was built with threading support
FeatureThreads Feature = C.GIT_FEATURE_THREADS
// libgit2 was built with HTTPS support built-in
FeatureHTTPS Feature = C.GIT_FEATURE_HTTPS
// libgit2 was build with SSH support built-in
FeatureSSH Feature = C.GIT_FEATURE_SSH
// libgit2 was built with nanosecond support for files
FeatureNSec Feature = C.GIT_FEATURE_NSEC
)
// Features returns a bit-flag of Feature values indicating which features the
// loaded libgit2 library has.
func Features() Feature {
features := C.git_libgit2_features()
return Feature(features)
}

322
git.go
View File

@ -1,264 +1,104 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
#include <git2/sys/openssl.h>
#include <git2/errors.h>
*/
import "C"
import (
"bytes"
"encoding/hex"
"errors"
"runtime"
"strings"
"unsafe"
"strings"
)
//go:generate stringer -type ErrorClass -trimprefix ErrorClass -tags static
type ErrorClass int
const (
ErrorClassNone ErrorClass = C.GIT_ERROR_NONE
ErrorClassNoMemory ErrorClass = C.GIT_ERROR_NOMEMORY
ErrorClassOS ErrorClass = C.GIT_ERROR_OS
ErrorClassInvalid ErrorClass = C.GIT_ERROR_INVALID
ErrorClassReference ErrorClass = C.GIT_ERROR_REFERENCE
ErrorClassZlib ErrorClass = C.GIT_ERROR_ZLIB
ErrorClassRepository ErrorClass = C.GIT_ERROR_REPOSITORY
ErrorClassConfig ErrorClass = C.GIT_ERROR_CONFIG
ErrorClassRegex ErrorClass = C.GIT_ERROR_REGEX
ErrorClassOdb ErrorClass = C.GIT_ERROR_ODB
ErrorClassIndex ErrorClass = C.GIT_ERROR_INDEX
ErrorClassObject ErrorClass = C.GIT_ERROR_OBJECT
ErrorClassNet ErrorClass = C.GIT_ERROR_NET
ErrorClassTag ErrorClass = C.GIT_ERROR_TAG
ErrorClassTree ErrorClass = C.GIT_ERROR_TREE
ErrorClassIndexer ErrorClass = C.GIT_ERROR_INDEXER
ErrorClassSSL ErrorClass = C.GIT_ERROR_SSL
ErrorClassSubmodule ErrorClass = C.GIT_ERROR_SUBMODULE
ErrorClassThread ErrorClass = C.GIT_ERROR_THREAD
ErrorClassStash ErrorClass = C.GIT_ERROR_STASH
ErrorClassCheckout ErrorClass = C.GIT_ERROR_CHECKOUT
ErrorClassFetchHead ErrorClass = C.GIT_ERROR_FETCHHEAD
ErrorClassMerge ErrorClass = C.GIT_ERROR_MERGE
ErrorClassSSH ErrorClass = C.GIT_ERROR_SSH
ErrorClassFilter ErrorClass = C.GIT_ERROR_FILTER
ErrorClassRevert ErrorClass = C.GIT_ERROR_REVERT
ErrorClassCallback ErrorClass = C.GIT_ERROR_CALLBACK
ErrorClassRebase ErrorClass = C.GIT_ERROR_REBASE
ErrorClassPatch ErrorClass = C.GIT_ERROR_PATCH
)
//go:generate stringer -type ErrorCode -trimprefix ErrorCode -tags static
type ErrorCode int
const (
// ErrorCodeOK indicates that the operation completed successfully.
ErrorCodeOK ErrorCode = C.GIT_OK
// ErrorCodeGeneric represents a generic error.
ErrorCodeGeneric ErrorCode = C.GIT_ERROR
// ErrorCodeNotFound represents that the requested object could not be found
ErrorCodeNotFound ErrorCode = C.GIT_ENOTFOUND
// ErrorCodeExists represents that the object exists preventing operation.
ErrorCodeExists ErrorCode = C.GIT_EEXISTS
// ErrorCodeAmbiguous represents that more than one object matches.
ErrorCodeAmbiguous ErrorCode = C.GIT_EAMBIGUOUS
// ErrorCodeBuffs represents that the output buffer is too short to hold data.
ErrorCodeBuffs ErrorCode = C.GIT_EBUFS
// ErrorCodeUser is a special error that is never generated by libgit2
// code. You can return it from a callback (e.g to stop an iteration)
// to know that it was generated by the callback and not by libgit2.
ErrorCodeUser ErrorCode = C.GIT_EUSER
// ErrorCodeBareRepo represents that the operation not allowed on bare repository
ErrorCodeBareRepo ErrorCode = C.GIT_EBAREREPO
// ErrorCodeUnbornBranch represents that HEAD refers to branch with no commits.
ErrorCodeUnbornBranch ErrorCode = C.GIT_EUNBORNBRANCH
// ErrorCodeUnmerged represents that a merge in progress prevented operation.
ErrorCodeUnmerged ErrorCode = C.GIT_EUNMERGED
// ErrorCodeNonFastForward represents that the reference was not fast-forwardable.
ErrorCodeNonFastForward ErrorCode = C.GIT_ENONFASTFORWARD
// ErrorCodeInvalidSpec represents that the name/ref spec was not in a valid format.
ErrorCodeInvalidSpec ErrorCode = C.GIT_EINVALIDSPEC
// ErrorCodeConflict represents that checkout conflicts prevented operation.
ErrorCodeConflict ErrorCode = C.GIT_ECONFLICT
// ErrorCodeLocked represents that lock file prevented operation.
ErrorCodeLocked ErrorCode = C.GIT_ELOCKED
// ErrorCodeModified represents that the reference value does not match expected.
ErrorCodeModified ErrorCode = C.GIT_EMODIFIED
// ErrorCodeAuth represents that the authentication failed.
ErrorCodeAuth ErrorCode = C.GIT_EAUTH
// ErrorCodeCertificate represents that the server certificate is invalid.
ErrorCodeCertificate ErrorCode = C.GIT_ECERTIFICATE
// ErrorCodeApplied represents that the patch/merge has already been applied.
ErrorCodeApplied ErrorCode = C.GIT_EAPPLIED
// ErrorCodePeel represents that the requested peel operation is not possible.
ErrorCodePeel ErrorCode = C.GIT_EPEEL
// ErrorCodeEOF represents an unexpected EOF.
ErrorCodeEOF ErrorCode = C.GIT_EEOF
// ErrorCodeInvalid represents an invalid operation or input.
ErrorCodeInvalid ErrorCode = C.GIT_EINVALID
// ErrorCodeUIncommitted represents that uncommitted changes in index prevented operation.
ErrorCodeUncommitted ErrorCode = C.GIT_EUNCOMMITTED
// ErrorCodeDirectory represents that the operation is not valid for a directory.
ErrorCodeDirectory ErrorCode = C.GIT_EDIRECTORY
// ErrorCodeMergeConflict represents that a merge conflict exists and cannot continue.
ErrorCodeMergeConflict ErrorCode = C.GIT_EMERGECONFLICT
// ErrorCodePassthrough represents that a user-configured callback refused to act.
ErrorCodePassthrough ErrorCode = C.GIT_PASSTHROUGH
// ErrorCodeIterOver signals end of iteration with iterator.
ErrorCodeIterOver ErrorCode = C.GIT_ITEROVER
// ErrorCodeRetry is an internal-only error code.
ErrorCodeRetry ErrorCode = C.GIT_RETRY
// ErrorCodeMismatch represents a hashsum mismatch in object.
ErrorCodeMismatch ErrorCode = C.GIT_EMISMATCH
// ErrorCodeIndexDirty represents that unsaved changes in the index would be overwritten.
ErrorCodeIndexDirty ErrorCode = C.GIT_EINDEXDIRTY
// ErrorCodeApplyFail represents that a patch application failed.
ErrorCodeApplyFail ErrorCode = C.GIT_EAPPLYFAIL
ITEROVER = C.GIT_ITEROVER
EEXISTS = C.GIT_EEXISTS
ENOTFOUND = C.GIT_ENOTFOUND
)
var (
ErrInvalid = errors.New("Invalid state for operation")
ErrIterOver = errors.New("Iteration is over")
)
// doNotCompare is an idiomatic way of making structs non-comparable to avoid
// future field additions to make them non-comparable.
type doNotCompare [0]func()
var pointerHandles *HandleList
var remotePointers *remotePointerList
func init() {
initLibGit2()
C.git_threads_init()
}
func initLibGit2() {
pointerHandles = NewHandleList()
remotePointers = newRemotePointerList()
C.git_libgit2_init()
features := Features()
// Due to the multithreaded nature of Go and its interaction with
// calling C functions, we cannot work with a library that was not built
// with multi-threading support. The most likely outcome is a segfault
// or panic at an incomprehensible time, so let's make it easy by
// panicking right here.
if features&FeatureThreads == 0 {
panic("libgit2 was not built with threading support")
}
if features&FeatureHTTPS == 0 {
if err := registerManagedHTTP(); err != nil {
panic(err)
}
} else {
// This is not something we should be doing, as we may be stomping all over
// someone else's setup. The user should do this themselves or use some
// binding/wrapper which does it in such a way that they can be sure
// they're the only ones setting it up.
C.git_openssl_set_locking()
}
if features&FeatureSSH == 0 {
if err := registerManagedSSH(); err != nil {
panic(err)
}
}
// Oid
type Oid struct {
bytes [20]byte
}
// Shutdown frees all the resources acquired by libgit2. Make sure no
// references to any libgit2 go objects are live before calling this.
// After this is called, invoking any function from this library will result in
// undefined behavior, so make sure this is called carefully.
func Shutdown() {
if err := unregisterManagedTransports(); err != nil {
panic(err)
}
pointerHandles.Clear()
remotePointers.clear()
C.git_libgit2_shutdown()
}
// ReInit reinitializes the global state, this is useful if the effective user
// id has changed and you want to update the stored search paths for gitconfig
// files. This function frees any references to objects, so it should be called
// before any other functions are called.
func ReInit() {
Shutdown()
initLibGit2()
}
// Oid represents the id for a Git object.
type Oid [20]byte
func newOidFromC(coid *C.git_oid) *Oid {
if coid == nil {
return nil
}
oid := new(Oid)
copy(oid[0:20], C.GoBytes(unsafe.Pointer(coid), 20))
copy(oid.bytes[0:20], C.GoBytes(unsafe.Pointer(coid), 20))
return oid
}
func NewOidFromBytes(b []byte) *Oid {
func NewOid(b []byte) *Oid {
oid := new(Oid)
copy(oid[0:20], b[0:20])
copy(oid.bytes[0:20], b[0:20])
return oid
}
func (oid *Oid) toC() *C.git_oid {
return (*C.git_oid)(unsafe.Pointer(oid))
return (*C.git_oid)(unsafe.Pointer(&oid.bytes))
}
func NewOid(s string) (*Oid, error) {
if len(s) > C.GIT_OID_HEXSZ {
return nil, errors.New("string is too long for oid")
}
func NewOidFromString(s string) (*Oid, error) {
o := new(Oid)
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
slice, err := hex.DecodeString(s)
if err != nil {
return nil, err
if C.git_oid_fromstr(o.toC(), cs) < 0 {
return nil, LastError()
}
if len(slice) != 20 {
return nil, &GitError{"invalid oid", ErrorClassNone, ErrorCodeGeneric}
}
copy(o[:], slice[:20])
return o, nil
}
func (oid *Oid) String() string {
return hex.EncodeToString(oid[:])
buf := make([]byte, 40)
C.git_oid_fmt((*C.char)(unsafe.Pointer(&buf[0])), oid.toC())
return string(buf)
}
func (oid *Oid) Bytes() []byte {
return oid.bytes[0:]
}
func (oid *Oid) Cmp(oid2 *Oid) int {
return bytes.Compare(oid[:], oid2[:])
return bytes.Compare(oid.bytes[:], oid2.bytes[:])
}
func (oid *Oid) Copy() *Oid {
ret := *oid
return &ret
ret := new(Oid)
copy(ret.bytes[:], oid.bytes[:])
return ret
}
func (oid *Oid) Equal(oid2 *Oid) bool {
return *oid == *oid2
return bytes.Equal(oid.bytes[:], oid2.bytes[:])
}
func (oid *Oid) IsZero() bool {
return *oid == Oid{}
for _, a := range(oid.bytes) {
if a != '0' {
return false
}
}
return true
}
func (oid *Oid) NCmp(oid2 *Oid, n uint) int {
return bytes.Compare(oid[:n], oid2[:n])
return bytes.Compare(oid.bytes[:n], oid2.bytes[:n])
}
func ShortenOids(ids []*Oid, minlen int) (int, error) {
@ -269,101 +109,49 @@ func ShortenOids(ids []*Oid, minlen int) (int, error) {
defer C.git_oid_shorten_free(shorten)
var ret C.int
runtime.LockOSThread()
defer runtime.UnlockOSThread()
for _, id := range ids {
buf := make([]byte, 41)
C.git_oid_fmt((*C.char)(unsafe.Pointer(&buf[0])), id.toC())
buf[40] = 0
ret = C.git_oid_shorten_add(shorten, (*C.char)(unsafe.Pointer(&buf[0])))
if ret < 0 {
return int(ret), MakeGitError(ret)
return int(ret), LastError()
}
}
runtime.KeepAlive(ids)
return int(ret), nil
}
type GitError struct {
Message string
Class ErrorClass
Code ErrorCode
Code int
}
func (e GitError) Error() string {
func (e GitError) Error() string{
return e.Message
}
func IsErrorClass(err error, c ErrorClass) bool {
func LastError() error {
err := C.giterr_last()
if err == nil {
return false
return &GitError{"No message", 0}
}
if gitError, ok := err.(*GitError); ok {
return gitError.Class == c
}
return false
}
func IsErrorCode(err error, c ErrorCode) bool {
if err == nil {
return false
}
if gitError, ok := err.(*GitError); ok {
return gitError.Code == c
}
return false
}
func MakeGitError(c C.int) error {
var errMessage string
var errClass ErrorClass
errorCode := ErrorCode(c)
if errorCode != ErrorCodeIterOver {
err := C.git_error_last()
if err != nil {
errMessage = C.GoString(err.message)
errClass = ErrorClass(err.klass)
} else {
errClass = ErrorClassInvalid
}
}
if errMessage == "" {
errMessage = errorCode.String()
}
return &GitError{errMessage, errClass, errorCode}
}
func MakeGitError2(err int) error {
return MakeGitError(C.int(err))
return &GitError{C.GoString(err.message), int(err.klass)}
}
func cbool(b bool) C.int {
if b {
if (b) {
return C.int(1)
}
return C.int(0)
}
func ucbool(b bool) C.uint {
if b {
if (b) {
return C.uint(1)
}
return C.uint(0)
}
func setCallbackError(errorMessage **C.char, err error) C.int {
if err != nil {
*errorMessage = C.CString(err.Error())
if gitError, ok := err.(*GitError); ok {
return C.int(gitError.Code)
}
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}
func Discover(start string, across_fs bool, ceiling_dirs []string) (string, error) {
ceildirs := C.CString(strings.Join(ceiling_dirs, string(C.GIT_PATH_LIST_SEPARATOR)))
defer C.free(unsafe.Pointer(ceildirs))
@ -371,16 +159,14 @@ func Discover(start string, across_fs bool, ceiling_dirs []string) (string, erro
cstart := C.CString(start)
defer C.free(unsafe.Pointer(cstart))
var buf C.git_buf
defer C.git_buf_dispose(&buf)
retpath := (*C.char)(C.malloc(C.GIT_PATH_MAX))
defer C.free(unsafe.Pointer(retpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
r := C.git_repository_discover(retpath, C.GIT_PATH_MAX, cstart, cbool(across_fs), ceildirs)
ret := C.git_repository_discover(&buf, cstart, cbool(across_fs), ceildirs)
if ret < 0 {
return "", MakeGitError(ret)
if r == 0 {
return C.GoString(retpath), nil
}
return C.GoString(buf.ptr), nil
return "", LastError()
}

View File

@ -1,63 +1,11 @@
package git
import (
"fmt"
"io/ioutil"
"os"
"path"
"reflect"
"testing"
"io/ioutil"
"time"
)
func TestMain(m *testing.M) {
if err := registerManagedHTTP(); err != nil {
panic(err)
}
ret := m.Run()
if err := unregisterManagedTransports(); err != nil {
panic(err)
}
// Ensure that we are not leaking any pointer handles.
pointerHandles.Lock()
if len(pointerHandles.handles) > 0 {
for h, ptr := range pointerHandles.handles {
fmt.Printf("%016p: %v %+v\n", h, reflect.TypeOf(ptr), ptr)
}
panic("pointer handle list not empty")
}
pointerHandles.Unlock()
// Or remote pointers.
remotePointers.Lock()
if len(remotePointers.pointers) > 0 {
for ptr, remote := range remotePointers.pointers {
fmt.Printf("%016p: %+v\n", ptr, remote)
}
panic("remote pointer list not empty")
}
remotePointers.Unlock()
Shutdown()
os.Exit(ret)
}
func cleanupTestRepo(t *testing.T, r *Repository) {
var err error
if r.IsBare() {
err = os.RemoveAll(r.Path())
} else {
err = os.RemoveAll(r.Workdir())
}
checkFatal(t, err)
r.Free()
}
func createTestRepo(t *testing.T) *Repository {
// figure out where we can create the test repo
path, err := ioutil.TempDir("", "git2go")
@ -66,33 +14,13 @@ func createTestRepo(t *testing.T) *Repository {
checkFatal(t, err)
tmpfile := "README"
err = ioutil.WriteFile(path+"/"+tmpfile, []byte("foo\n"), 0644)
err = ioutil.WriteFile(path + "/" + tmpfile, []byte("foo\n"), 0644)
checkFatal(t, err)
return repo
}
func createBareTestRepo(t *testing.T) *Repository {
// figure out where we can create the test repo
path, err := ioutil.TempDir("", "git2go")
checkFatal(t, err)
repo, err := InitRepository(path, true)
checkFatal(t, err)
return repo
}
// commitOptions contains any extra options for creating commits in the seed repo
type commitOptions struct {
CommitSigningCallback
}
func seedTestRepo(t *testing.T, repo *Repository) (*Oid, *Oid) {
return seedTestRepoOpt(t, repo, commitOptions{})
}
func seedTestRepoOpt(t *testing.T, repo *Repository, opts commitOptions) (*Oid, *Oid) {
loc, err := time.LoadLocation("Europe/Berlin")
checkFatal(t, err)
sig := &Signature{
@ -105,8 +33,6 @@ func seedTestRepoOpt(t *testing.T, repo *Repository, opts commitOptions) (*Oid,
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
err = idx.Write()
checkFatal(t, err)
treeId, err := idx.WriteTree()
checkFatal(t, err)
@ -116,84 +42,6 @@ func seedTestRepoOpt(t *testing.T, repo *Repository, opts commitOptions) (*Oid,
commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
checkFatal(t, err)
if opts.CommitSigningCallback != nil {
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
signature, signatureField, err := opts.CommitSigningCallback(commit.ContentToSign())
checkFatal(t, err)
oid, err := commit.WithSignature(signature, signatureField)
checkFatal(t, err)
newCommit, err := repo.LookupCommit(oid)
checkFatal(t, err)
head, err := repo.Head()
checkFatal(t, err)
_, err = repo.References.Create(
head.Name(),
newCommit.Id(),
true,
"repoint to signed commit",
)
checkFatal(t, err)
}
return commitId, treeId
}
func pathInRepo(repo *Repository, name string) string {
return path.Join(path.Dir(path.Dir(repo.Path())), name)
}
func updateReadme(t *testing.T, repo *Repository, content string) (*Oid, *Oid) {
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),
}
tmpfile := "README"
err = ioutil.WriteFile(pathInRepo(repo, tmpfile), []byte(content), 0644)
checkFatal(t, err)
idx, err := repo.Index()
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
err = idx.Write()
checkFatal(t, err)
treeId, err := idx.WriteTree()
checkFatal(t, err)
currentBranch, err := repo.Head()
checkFatal(t, err)
currentTip, err := repo.LookupCommit(currentBranch.Target())
checkFatal(t, err)
message := "This is a commit\n"
tree, err := repo.LookupTree(treeId)
checkFatal(t, err)
commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree, currentTip)
checkFatal(t, err)
return commitId, treeId
}
func TestOidZero(t *testing.T) {
t.Parallel()
var zeroId Oid
if !zeroId.IsZero() {
t.Error("Zero Oid is not zero")
}
}
func TestEmptyOid(t *testing.T) {
t.Parallel()
_, err := NewOid("")
if err == nil || !IsErrorCode(err, ErrorCodeGeneric) {
t.Fatal("Should have returned invalid error")
}
}

View File

@ -1,69 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
)
func (repo *Repository) DescendantOf(commit, ancestor *Oid) (bool, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_graph_descendant_of(repo.ptr, commit.toC(), ancestor.toC())
runtime.KeepAlive(repo)
runtime.KeepAlive(commit)
runtime.KeepAlive(ancestor)
if ret < 0 {
return false, MakeGitError(ret)
}
return (ret > 0), nil
}
func (repo *Repository) AheadBehind(local, upstream *Oid) (ahead, behind int, err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var aheadT C.size_t
var behindT C.size_t
ret := C.git_graph_ahead_behind(&aheadT, &behindT, repo.ptr, local.toC(), upstream.toC())
runtime.KeepAlive(repo)
runtime.KeepAlive(local)
runtime.KeepAlive(upstream)
if ret < 0 {
return 0, 0, MakeGitError(ret)
}
return int(aheadT), int(behindT), nil
}
// ReachableFromAny returns whether a commit is reachable from any of a list of
// commits by following parent edges.
func (repo *Repository) ReachableFromAny(commit *Oid, descendants []*Oid) (bool, error) {
if len(descendants) == 0 {
return false, nil
}
coids := make([]C.git_oid, len(descendants))
for i := 0; i < len(descendants); i++ {
coids[i] = *descendants[i].toC()
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_graph_reachable_from_any(repo.ptr, commit.toC(), &coids[0], C.size_t(len(descendants)))
runtime.KeepAlive(repo)
runtime.KeepAlive(commit)
runtime.KeepAlive(coids)
runtime.KeepAlive(descendants)
if ret < 0 {
return false, MakeGitError(ret)
}
return (ret > 0), nil
}

View File

@ -1,75 +0,0 @@
package git
import (
"testing"
)
func TestReachableFromAny(t *testing.T) {
repo, err := OpenRepository("testdata/TestGitRepository.git")
checkFatal(t, err)
defer repo.Free()
for name, tc := range map[string]struct {
reachable bool
commit string
descendants []string
}{
"empty": {
reachable: false,
commit: "49322bb17d3acc9146f98c97d078513228bbf3c0",
},
"same": {
reachable: true,
commit: "49322bb17d3acc9146f98c97d078513228bbf3c0",
descendants: []string{"49322bb17d3acc9146f98c97d078513228bbf3c0"},
},
"unreachable": {
reachable: false,
commit: "ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
descendants: []string{"58be4659bb571194ed4562d04b359d26216f526e"},
},
"unreachable-reverse": {
reachable: false,
commit: "58be4659bb571194ed4562d04b359d26216f526e",
descendants: []string{"ac7e7e44c1885efb472ad54a78327d66bfc4ecef"},
},
"root": {
reachable: false,
commit: "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1",
descendants: []string{
"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
"d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864",
"f73b95671f326616d66b2afb3bdfcdbbce110b44",
"d0114ab8ac326bab30e3a657a0397578c5a1af88",
},
},
"head": {
reachable: false,
commit: "49322bb17d3acc9146f98c97d078513228bbf3c0",
descendants: []string{
"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
"d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864",
"f73b95671f326616d66b2afb3bdfcdbbce110b44",
"d0114ab8ac326bab30e3a657a0397578c5a1af88",
},
},
} {
tc := tc
t.Run(name, func(t *testing.T) {
commit, err := NewOid(tc.commit)
checkFatal(t, err)
descendants := make([]*Oid, len(tc.descendants))
for i, o := range tc.descendants {
descendants[i], err = NewOid(o)
checkFatal(t, err)
}
reachable, err := repo.ReachableFromAny(commit, descendants)
checkFatal(t, err)
if reachable != tc.reachable {
t.Errorf("ReachableFromAny(%s, %v) = %v, wanted %v", tc.commit, tc.descendants, reachable, tc.reachable)
}
})
}
}

View File

@ -1,68 +0,0 @@
package git
/*
#include <stdlib.h>
*/
import "C"
import (
"fmt"
"sync"
"unsafe"
)
type HandleList struct {
doNotCompare
sync.RWMutex
// stores the Go pointers
handles map[unsafe.Pointer]interface{}
}
func NewHandleList() *HandleList {
return &HandleList{
handles: make(map[unsafe.Pointer]interface{}),
}
}
// Track adds the given pointer to the list of pointers to track and
// returns a pointer value which can be passed to C as an opaque
// pointer.
func (v *HandleList) Track(pointer interface{}) unsafe.Pointer {
handle := C.malloc(1)
v.Lock()
v.handles[handle] = pointer
v.Unlock()
return handle
}
// Untrack stops tracking the pointer given by the handle
func (v *HandleList) Untrack(handle unsafe.Pointer) {
v.Lock()
delete(v.handles, handle)
C.free(handle)
v.Unlock()
}
// Clear stops tracking all the managed pointers.
func (v *HandleList) Clear() {
v.Lock()
for handle := range v.handles {
delete(v.handles, handle)
C.free(handle)
}
v.Unlock()
}
// Get retrieves the pointer from the given handle
func (v *HandleList) Get(handle unsafe.Pointer) interface{} {
v.RLock()
defer v.RUnlock()
ptr, ok := v.handles[handle]
if !ok {
panic(fmt.Sprintf("invalid pointer handle: %p", handle))
}
return ptr
}

243
http.go
View File

@ -1,243 +0,0 @@
package git
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"sync"
)
// RegisterManagedHTTPTransport registers a Go-native implementation of an
// HTTP/S transport that doesn't rely on any system libraries (e.g.
// libopenssl/libmbedtls).
//
// If Shutdown or ReInit are called, make sure that the smart transports are
// freed before it.
func RegisterManagedHTTPTransport(protocol string) (*RegisteredSmartTransport, error) {
return NewRegisteredSmartTransport(protocol, true, httpSmartSubtransportFactory)
}
func registerManagedHTTP() error {
globalRegisteredSmartTransports.Lock()
defer globalRegisteredSmartTransports.Unlock()
for _, protocol := range []string{"http", "https"} {
if _, ok := globalRegisteredSmartTransports.transports[protocol]; ok {
continue
}
managed, err := newRegisteredSmartTransport(protocol, true, httpSmartSubtransportFactory, true)
if err != nil {
return fmt.Errorf("failed to register transport for %q: %v", protocol, err)
}
globalRegisteredSmartTransports.transports[protocol] = managed
}
return nil
}
func httpSmartSubtransportFactory(remote *Remote, transport *Transport) (SmartSubtransport, error) {
var proxyFn func(*http.Request) (*url.URL, error)
remoteConnectOpts, err := transport.SmartRemoteConnectOptions()
if err != nil {
return nil, err
}
switch remoteConnectOpts.ProxyOptions.Type {
case ProxyTypeNone:
proxyFn = nil
case ProxyTypeAuto:
proxyFn = http.ProxyFromEnvironment
case ProxyTypeSpecified:
parsedUrl, err := url.Parse(remoteConnectOpts.ProxyOptions.Url)
if err != nil {
return nil, err
}
proxyFn = http.ProxyURL(parsedUrl)
}
return &httpSmartSubtransport{
transport: transport,
client: &http.Client{
Transport: &http.Transport{
Proxy: proxyFn,
},
},
}, nil
}
type httpSmartSubtransport struct {
transport *Transport
client *http.Client
}
func (t *httpSmartSubtransport) Action(url string, action SmartServiceAction) (SmartSubtransportStream, error) {
var req *http.Request
var err error
switch action {
case SmartServiceActionUploadpackLs:
req, err = http.NewRequest("GET", url+"/info/refs?service=git-upload-pack", nil)
case SmartServiceActionUploadpack:
req, err = http.NewRequest("POST", url+"/git-upload-pack", nil)
if err != nil {
break
}
req.Header.Set("Content-Type", "application/x-git-upload-pack-request")
case SmartServiceActionReceivepackLs:
req, err = http.NewRequest("GET", url+"/info/refs?service=git-receive-pack", nil)
case SmartServiceActionReceivepack:
req, err = http.NewRequest("POST", url+"/info/refs?service=git-upload-pack", nil)
if err != nil {
break
}
req.Header.Set("Content-Type", "application/x-git-receive-pack-request")
default:
err = errors.New("unknown action")
}
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", "git/2.0 (libgit2)")
stream := newManagedHttpStream(t, req)
if req.Method == "POST" {
stream.recvReply.Add(1)
stream.sendRequestBackground()
}
return stream, nil
}
func (t *httpSmartSubtransport) Close() error {
return nil
}
func (t *httpSmartSubtransport) Free() {
t.client = nil
}
type httpSmartSubtransportStream struct {
owner *httpSmartSubtransport
req *http.Request
resp *http.Response
reader *io.PipeReader
writer *io.PipeWriter
sentRequest bool
recvReply sync.WaitGroup
httpError error
}
func newManagedHttpStream(owner *httpSmartSubtransport, req *http.Request) *httpSmartSubtransportStream {
r, w := io.Pipe()
return &httpSmartSubtransportStream{
owner: owner,
req: req,
reader: r,
writer: w,
}
}
func (self *httpSmartSubtransportStream) Read(buf []byte) (int, error) {
if !self.sentRequest {
self.recvReply.Add(1)
if err := self.sendRequest(); err != nil {
return 0, err
}
}
if err := self.writer.Close(); err != nil {
return 0, err
}
self.recvReply.Wait()
if self.httpError != nil {
return 0, self.httpError
}
return self.resp.Body.Read(buf)
}
func (self *httpSmartSubtransportStream) Write(buf []byte) (int, error) {
if self.httpError != nil {
return 0, self.httpError
}
return self.writer.Write(buf)
}
func (self *httpSmartSubtransportStream) Free() {
if self.resp != nil {
self.resp.Body.Close()
}
}
func (self *httpSmartSubtransportStream) sendRequestBackground() {
go func() {
self.httpError = self.sendRequest()
}()
self.sentRequest = true
}
func (self *httpSmartSubtransportStream) sendRequest() error {
defer self.recvReply.Done()
self.resp = nil
var resp *http.Response
var err error
var userName string
var password string
for {
req := &http.Request{
Method: self.req.Method,
URL: self.req.URL,
Header: self.req.Header,
}
if req.Method == "POST" {
req.Body = self.reader
req.ContentLength = -1
}
if userName != "" && password != "" {
req.SetBasicAuth(userName, password)
}
resp, err = http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode == http.StatusOK {
break
}
if resp.StatusCode == http.StatusUnauthorized {
resp.Body.Close()
cred, err := self.owner.transport.SmartCredentials("", CredentialTypeUserpassPlaintext)
if err != nil {
return err
}
defer cred.Free()
userName, password, err = cred.GetUserpassPlaintext()
if err != nil {
return err
}
continue
}
// Any other error we treat as a hard error and punt back to the caller
resp.Body.Close()
return fmt.Errorf("Unhandled HTTP error %s", resp.Status)
}
self.sentRequest = true
self.resp = resp
return nil
}

View File

@ -1,54 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
func (v *Repository) AddIgnoreRule(rules string) error {
crules := C.CString(rules)
defer C.free(unsafe.Pointer(crules))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_ignore_add_rule(v.ptr, crules)
runtime.KeepAlive(v)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (v *Repository) ClearInternalIgnoreRules() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_ignore_clear_internal_rules(v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (v *Repository) IsPathIgnored(path string) (bool, error) {
var ignored C.int
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_ignore_path_is_ignored(&ignored, v.ptr, cpath)
runtime.KeepAlive(v)
if ret < 0 {
return false, MakeGitError(ret)
}
return ignored == 1, nil
}

636
index.go
View File

@ -1,416 +1,33 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
extern int _go_git_index_add_all(git_index*, const git_strarray*, unsigned int, void*);
extern int _go_git_index_update_all(git_index*, const git_strarray*, void*);
extern int _go_git_index_remove_all(git_index*, const git_strarray*, void*);
#include <git2/errors.h>
*/
import "C"
import (
"fmt"
"runtime"
"unsafe"
)
type IndexMatchedPathCallback func(string, string) error
type indexMatchedPathCallbackData struct {
callback IndexMatchedPathCallback
errorTarget *error
}
// IndexAddOption is a set of flags for APIs that add files matching pathspec.
type IndexAddOption uint
const (
IndexAddDefault IndexAddOption = C.GIT_INDEX_ADD_DEFAULT
IndexAddForce IndexAddOption = C.GIT_INDEX_ADD_FORCE
IndexAddDisablePathspecMatch IndexAddOption = C.GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH
IndexAddCheckPathspec IndexAddOption = C.GIT_INDEX_ADD_CHECK_PATHSPEC
)
// IndexStageState indicates the state of the git index.
type IndexStageState int
const (
// IndexStageAny matches any index stage.
//
// Some index APIs take a stage to match; pass this value to match
// any entry matching the path regardless of stage.
IndexStageAny IndexStageState = C.GIT_INDEX_STAGE_ANY
// IndexStageNormal is a normal staged file in the index.
IndexStageNormal IndexStageState = C.GIT_INDEX_STAGE_NORMAL
// IndexStageAncestor is the ancestor side of a conflict.
IndexStageAncestor IndexStageState = C.GIT_INDEX_STAGE_ANCESTOR
// IndexStageOurs is the "ours" side of a conflict.
IndexStageOurs IndexStageState = C.GIT_INDEX_STAGE_OURS
// IndexStageTheirs is the "theirs" side of a conflict.
IndexStageTheirs IndexStageState = C.GIT_INDEX_STAGE_THEIRS
)
type Index struct {
doNotCompare
ptr *C.git_index
repo *Repository
ptr *C.git_index
}
type IndexTime struct {
Seconds int32
Nanoseconds uint32
}
type IndexEntry struct {
Ctime IndexTime
Mtime IndexTime
Mode Filemode
Uid uint32
Gid uint32
Size uint32
Id *Oid
Path string
}
func newIndexEntryFromC(entry *C.git_index_entry) *IndexEntry {
if entry == nil {
return nil
}
return &IndexEntry{
IndexTime{int32(entry.ctime.seconds), uint32(entry.ctime.nanoseconds)},
IndexTime{int32(entry.mtime.seconds), uint32(entry.mtime.nanoseconds)},
Filemode(entry.mode),
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.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)
if source.Id != nil {
dest.id = *source.Id.toC()
}
dest.path = C.CString(source.Path)
}
func freeCIndexEntry(entry *C.git_index_entry) {
C.free(unsafe.Pointer(entry.path))
}
func newIndexFromC(ptr *C.git_index, repo *Repository) *Index {
idx := &Index{ptr: ptr, repo: repo}
func newIndexFromC(ptr *C.git_index) *Index {
idx := &Index{ptr}
runtime.SetFinalizer(idx, (*Index).Free)
return idx
}
// NewIndex allocates a new index. It won't be associated with any
// file on the filesystem or repository
func NewIndex() (*Index, error) {
var ptr *C.git_index
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := C.git_index_new(&ptr); err < 0 {
return nil, MakeGitError(err)
}
return newIndexFromC(ptr, nil), nil
}
// OpenIndex creates a new index at the given path. If the file does
// not exist it will be created when Write() is called.
func OpenIndex(path string) (*Index, error) {
var ptr *C.git_index
var cpath = C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := C.git_index_open(&ptr, cpath); err < 0 {
return nil, MakeGitError(err)
}
return newIndexFromC(ptr, nil), nil
}
// Path returns the index' path on disk or an empty string if it
// exists only in memory.
func (v *Index) Path() string {
ret := C.GoString(C.git_index_path(v.ptr))
runtime.KeepAlive(v)
return ret
}
// Clear clears the index object in memory; changes must be explicitly
// written to disk for them to take effect persistently
func (v *Index) Clear() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C.git_index_clear(v.ptr)
runtime.KeepAlive(v)
if err < 0 {
return MakeGitError(err)
}
return nil
}
// Add adds or replaces the given entry to the index, making a copy of
// the data
func (v *Index) Add(entry *IndexEntry) error {
var centry C.git_index_entry
populateCIndexEntry(entry, &centry)
defer freeCIndexEntry(&centry)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C.git_index_add(v.ptr, &centry)
runtime.KeepAlive(v)
if err < 0 {
return MakeGitError(err)
}
return nil
}
func (v *Index) AddByPath(path string) error {
cstr := C.CString(path)
defer C.free(unsafe.Pointer(cstr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_index_add_bypath(v.ptr, cstr)
runtime.KeepAlive(v)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// AddFromBuffer adds or replaces an index entry from a buffer in memory
func (v *Index) AddFromBuffer(entry *IndexEntry, buffer []byte) error {
var centry C.git_index_entry
populateCIndexEntry(entry, &centry)
defer freeCIndexEntry(&centry)
var cbuffer unsafe.Pointer
if len(buffer) > 0 {
cbuffer = unsafe.Pointer(&buffer[0])
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := C.git_index_add_from_buffer(v.ptr, &centry, cbuffer, C.size_t(len(buffer))); err < 0 {
return MakeGitError(err)
}
return nil
}
func (v *Index) AddAll(pathspecs []string, flags IndexAddOption, callback IndexMatchedPathCallback) error {
cpathspecs := C.git_strarray{}
cpathspecs.count = C.size_t(len(pathspecs))
cpathspecs.strings = makeCStringsFromStrings(pathspecs)
defer freeStrarray(&cpathspecs)
var err error
data := indexMatchedPathCallbackData{
callback: callback,
errorTarget: &err,
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var handle unsafe.Pointer
if callback != nil {
handle = pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)
}
ret := C._go_git_index_add_all(
v.ptr,
&cpathspecs,
C.uint(flags),
handle,
)
runtime.KeepAlive(v)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (v *Index) UpdateAll(pathspecs []string, callback IndexMatchedPathCallback) error {
cpathspecs := C.git_strarray{}
cpathspecs.count = C.size_t(len(pathspecs))
cpathspecs.strings = makeCStringsFromStrings(pathspecs)
defer freeStrarray(&cpathspecs)
var err error
data := indexMatchedPathCallbackData{
callback: callback,
errorTarget: &err,
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var handle unsafe.Pointer
if callback != nil {
handle = pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)
}
ret := C._go_git_index_update_all(
v.ptr,
&cpathspecs,
handle,
)
runtime.KeepAlive(v)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (v *Index) RemoveAll(pathspecs []string, callback IndexMatchedPathCallback) error {
cpathspecs := C.git_strarray{}
cpathspecs.count = C.size_t(len(pathspecs))
cpathspecs.strings = makeCStringsFromStrings(pathspecs)
defer freeStrarray(&cpathspecs)
var err error
data := indexMatchedPathCallbackData{
callback: callback,
errorTarget: &err,
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var handle unsafe.Pointer
if callback != nil {
handle = pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)
}
ret := C._go_git_index_remove_all(
v.ptr,
&cpathspecs,
handle,
)
runtime.KeepAlive(v)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
//export indexMatchedPathCallback
func indexMatchedPathCallback(cPath, cMatchedPathspec *C.char, payload unsafe.Pointer) C.int {
data, ok := pointerHandles.Get(payload).(*indexMatchedPathCallbackData)
if !ok {
panic("invalid matched path callback")
}
err := data.callback(C.GoString(cPath), C.GoString(cMatchedPathspec))
if err != nil {
*data.errorTarget = err
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}
func (v *Index) RemoveByPath(path string) error {
cstr := C.CString(path)
defer C.free(unsafe.Pointer(cstr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_index_remove_bypath(v.ptr, cstr)
runtime.KeepAlive(v)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// RemoveDirectory removes all entries from the index under a given directory.
func (v *Index) RemoveDirectory(dir string, stage int) error {
cstr := C.CString(dir)
defer C.free(unsafe.Pointer(cstr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_index_remove_directory(v.ptr, cstr, C.int(stage))
runtime.KeepAlive(v)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (v *Index) WriteTreeTo(repo *Repository) (*Oid, error) {
oid := new(Oid)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_index_write_tree_to(oid.toC(), v.ptr, repo.ptr)
runtime.KeepAlive(v)
runtime.KeepAlive(repo)
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
// ReadTree replaces the contents of the index with those of the given
// tree
func (v *Index) ReadTree(tree *Tree) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_index_read_tree(v.ptr, tree.cast_ptr)
runtime.KeepAlive(v)
runtime.KeepAlive(tree)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
@ -418,254 +35,15 @@ func (v *Index) ReadTree(tree *Tree) error {
func (v *Index) WriteTree() (*Oid, error) {
oid := new(Oid)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_index_write_tree(oid.toC(), v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return oid, nil
}
func (v *Index) Write() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_index_write(v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (v *Index) Free() {
runtime.SetFinalizer(v, nil)
C.git_index_free(v.ptr)
}
func (v *Index) EntryCount() uint {
ret := uint(C.git_index_entrycount(v.ptr))
runtime.KeepAlive(v)
return ret
}
func (v *Index) EntryByIndex(index uint) (*IndexEntry, error) {
centry := C.git_index_get_byindex(v.ptr, C.size_t(index))
if centry == nil {
return nil, fmt.Errorf("Index out of Bounds")
}
ret := newIndexEntryFromC(centry)
runtime.KeepAlive(v)
return ret, nil
}
func (v *Index) EntryByPath(path string, stage int) (*IndexEntry, error) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
centry := C.git_index_get_bypath(v.ptr, cpath, C.int(stage))
if centry == nil {
return nil, MakeGitError(C.int(ErrorCodeNotFound))
}
ret := newIndexEntryFromC(centry)
runtime.KeepAlive(v)
return ret, nil
}
func (v *Index) Find(path string) (uint, error) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var pos C.size_t
ret := C.git_index_find(&pos, v.ptr, cpath)
runtime.KeepAlive(v)
if ret < 0 {
return uint(0), MakeGitError(ret)
}
return uint(pos), nil
}
func (v *Index) FindPrefix(prefix string) (uint, error) {
cprefix := C.CString(prefix)
defer C.free(unsafe.Pointer(cprefix))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var pos C.size_t
ret := C.git_index_find_prefix(&pos, v.ptr, cprefix)
runtime.KeepAlive(v)
if ret < 0 {
return uint(0), MakeGitError(ret)
}
return uint(pos), nil
}
func (v *Index) HasConflicts() bool {
ret := C.git_index_has_conflicts(v.ptr) != 0
runtime.KeepAlive(v)
return ret
}
// FIXME: this might return an error
func (v *Index) CleanupConflicts() {
C.git_index_conflict_cleanup(v.ptr)
runtime.KeepAlive(v)
}
func (v *Index) AddConflict(ancestor *IndexEntry, our *IndexEntry, their *IndexEntry) error {
var cancestor *C.git_index_entry
var cour *C.git_index_entry
var ctheir *C.git_index_entry
if ancestor != nil {
cancestor = &C.git_index_entry{}
populateCIndexEntry(ancestor, cancestor)
defer freeCIndexEntry(cancestor)
}
if our != nil {
cour = &C.git_index_entry{}
populateCIndexEntry(our, cour)
defer freeCIndexEntry(cour)
}
if their != nil {
ctheir = &C.git_index_entry{}
populateCIndexEntry(their, ctheir)
defer freeCIndexEntry(ctheir)
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_index_conflict_add(v.ptr, cancestor, cour, ctheir)
runtime.KeepAlive(v)
runtime.KeepAlive(ancestor)
runtime.KeepAlive(our)
runtime.KeepAlive(their)
if ecode < 0 {
return MakeGitError(ecode)
}
return nil
}
type IndexConflict struct {
Ancestor *IndexEntry
Our *IndexEntry
Their *IndexEntry
}
func (v *Index) Conflict(path string) (IndexConflict, error) {
var cancestor *C.git_index_entry
var cour *C.git_index_entry
var ctheir *C.git_index_entry
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_index_conflict_get(&cancestor, &cour, &ctheir, v.ptr, cpath)
if ecode < 0 {
return IndexConflict{}, MakeGitError(ecode)
}
ret := IndexConflict{
Ancestor: newIndexEntryFromC(cancestor),
Our: newIndexEntryFromC(cour),
Their: newIndexEntryFromC(ctheir),
}
runtime.KeepAlive(v)
return ret, nil
}
// deprecated: You should use `Index.Conflict()` instead.
func (v *Index) GetConflict(path string) (IndexConflict, error) {
return v.Conflict(path)
}
func (v *Index) RemoveConflict(path string) error {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_index_conflict_remove(v.ptr, cpath)
runtime.KeepAlive(v)
if ecode < 0 {
return MakeGitError(ecode)
}
return nil
}
type IndexConflictIterator struct {
doNotCompare
ptr *C.git_index_conflict_iterator
index *Index
}
func newIndexConflictIteratorFromC(index *Index, ptr *C.git_index_conflict_iterator) *IndexConflictIterator {
i := &IndexConflictIterator{ptr: ptr, index: index}
runtime.SetFinalizer(i, (*IndexConflictIterator).Free)
return i
}
func (v *IndexConflictIterator) Index() *Index {
return v.index
}
func (v *IndexConflictIterator) Free() {
runtime.SetFinalizer(v, nil)
C.git_index_conflict_iterator_free(v.ptr)
}
func (v *Index) ConflictIterator() (*IndexConflictIterator, error) {
var i *C.git_index_conflict_iterator
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_index_conflict_iterator_new(&i, v.ptr)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newIndexConflictIteratorFromC(v, i), nil
}
func (v *IndexConflictIterator) Next() (IndexConflict, error) {
var cancestor *C.git_index_entry
var cour *C.git_index_entry
var ctheir *C.git_index_entry
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_index_conflict_next(&cancestor, &cour, &ctheir, v.ptr)
if ecode < 0 {
return IndexConflict{}, MakeGitError(ecode)
}
ret := IndexConflict{
Ancestor: newIndexEntryFromC(cancestor),
Our: newIndexEntryFromC(cour),
Their: newIndexEntryFromC(ctheir),
}
runtime.KeepAlive(v)
return ret, nil
}

View File

@ -1,17 +1,14 @@
package git
import (
"io/ioutil"
"os"
"path"
"runtime"
"testing"
)
func TestCreateRepoAndStage(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
defer os.RemoveAll(repo.Workdir())
idx, err := repo.Index()
checkFatal(t, err)
@ -25,249 +22,6 @@ func TestCreateRepoAndStage(t *testing.T) {
}
}
func TestIndexReadTree(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, _ = seedTestRepo(t, repo)
ref, err := repo.Head()
checkFatal(t, err)
obj, err := ref.Peel(ObjectTree)
checkFatal(t, err)
tree, err := obj.AsTree()
checkFatal(t, err)
idx, err := NewIndex()
checkFatal(t, err)
err = idx.ReadTree(tree)
checkFatal(t, err)
id, err := idx.WriteTreeTo(repo)
checkFatal(t, err)
if tree.Id().Cmp(id) != 0 {
t.Fatalf("Read and written trees are not the same")
}
}
func TestIndexWriteTreeTo(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
idx, err := NewIndex()
checkFatal(t, err)
odb, err := repo.Odb()
checkFatal(t, err)
content, err := ioutil.ReadFile(path.Join(repo.Workdir(), "README"))
checkFatal(t, err)
id, err := odb.Write(content, ObjectBlob)
checkFatal(t, err)
err = idx.Add(&IndexEntry{
Mode: FilemodeBlob,
Uid: 0,
Gid: 0,
Size: uint32(len(content)),
Id: id,
Path: "README",
})
checkFatal(t, err)
treeId, err := idx.WriteTreeTo(repo)
checkFatal(t, err)
if treeId.String() != "b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e" {
t.Fatalf("%v", treeId.String())
}
}
func TestIndexAddAndWriteTreeTo(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
odb, err := repo.Odb()
checkFatal(t, err)
blobID, err := odb.Write([]byte("foo\n"), ObjectBlob)
checkFatal(t, err)
idx, err := NewIndex()
checkFatal(t, err)
if idx.Path() != "" {
t.Fatal("in-memory repo has a path")
}
entry := IndexEntry{
Path: "README",
Id: blobID,
Mode: FilemodeBlob,
}
err = idx.Add(&entry)
checkFatal(t, err)
treeId, err := idx.WriteTreeTo(repo)
checkFatal(t, err)
if treeId.String() != "b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e" {
t.Fatalf("%v", treeId.String())
}
}
func TestIndexRemoveDirectory(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
odb, err := repo.Odb()
checkFatal(t, err)
blobID, err := odb.Write([]byte("fou\n"), ObjectBlob)
checkFatal(t, err)
idx, err := NewIndex()
checkFatal(t, err)
entryCount := idx.EntryCount()
if entryCount != 0 {
t.Fatal("Index should count 0 entry")
}
entry := IndexEntry{
Path: "path/to/LISEZ_MOI",
Id: blobID,
Mode: FilemodeBlob,
}
err = idx.Add(&entry)
checkFatal(t, err)
entryCount = idx.EntryCount()
if entryCount != 1 {
t.Fatal("Index should count 1 entry")
}
err = idx.RemoveDirectory("path", 0)
entryCount = idx.EntryCount()
if entryCount != 0 {
t.Fatal("Index should count 0 entry")
}
}
func TestIndexAddFromBuffer(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
idx, err := repo.Index()
checkFatal(t, err)
entry := IndexEntry{
Path: "README",
Mode: FilemodeBlob,
}
err = idx.AddFromBuffer(&entry, []byte("foo\n"))
checkFatal(t, err)
treeId, err := idx.WriteTreeTo(repo)
checkFatal(t, err)
if treeId.String() != "b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e" {
t.Fatalf("%v", treeId.String())
}
}
func TestIndexAddAllNoCallback(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
err := ioutil.WriteFile(repo.Workdir()+"/README", []byte("foo\n"), 0644)
checkFatal(t, err)
idx, err := repo.Index()
checkFatal(t, err)
err = idx.AddAll([]string{}, IndexAddDefault, nil)
checkFatal(t, err)
treeId, err := idx.WriteTreeTo(repo)
checkFatal(t, err)
if treeId.String() != "b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e" {
t.Fatalf("%v", treeId.String())
}
}
func TestIndexAddAllCallback(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
err := ioutil.WriteFile(repo.Workdir()+"/README", []byte("foo\n"), 0644)
checkFatal(t, err)
idx, err := repo.Index()
checkFatal(t, err)
cbPath := ""
err = idx.AddAll([]string{}, IndexAddDefault, func(p, mP string) error {
cbPath = p
return nil
})
checkFatal(t, err)
if cbPath != "README" {
t.Fatalf("%v", cbPath)
}
treeId, err := idx.WriteTreeTo(repo)
checkFatal(t, err)
if treeId.String() != "b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e" {
t.Fatalf("%v", treeId.String())
}
}
func TestIndexOpen(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
path := repo.Workdir() + "/heyindex"
_, err := os.Stat(path)
if !os.IsNotExist(err) {
t.Fatal("new index file already exists")
}
idx, err := OpenIndex(path)
checkFatal(t, err)
if path != idx.Path() {
t.Fatalf("mismatched index paths, expected %v, got %v", path, idx.Path())
}
err = idx.Write()
checkFatal(t, err)
_, err = os.Stat(path)
if os.IsNotExist(err) {
t.Fatal("new index file did not get written")
}
}
func checkFatal(t *testing.T, err error) {
if err == nil {
return
@ -276,7 +30,8 @@ func checkFatal(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")
t.Fatal()
}
t.Fatalf("Fail at %v:%v; %v", file, line, err)
}

View File

@ -1,97 +0,0 @@
package git
/*
#include <git2.h>
extern const git_oid * git_indexer_hash(const git_indexer *idx);
extern int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats);
extern int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats);
extern int _go_git_indexer_new(git_indexer **out, const char *path, unsigned int mode, git_odb *odb, void *progress_cb_payload);
extern void git_indexer_free(git_indexer *idx);
*/
import "C"
import (
"reflect"
"runtime"
"unsafe"
)
// Indexer can post-process packfiles and create an .idx file for efficient
// lookup.
type Indexer struct {
doNotCompare
ptr *C.git_indexer
stats C.git_transfer_progress
ccallbacks C.git_remote_callbacks
}
// NewIndexer creates a new indexer instance.
func NewIndexer(packfilePath string, odb *Odb, callback TransferProgressCallback) (indexer *Indexer, err error) {
var odbPtr *C.git_odb = nil
if odb != nil {
odbPtr = odb.ptr
}
indexer = new(Indexer)
populateRemoteCallbacks(&indexer.ccallbacks, &RemoteCallbacks{TransferProgressCallback: callback}, nil)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cstr := C.CString(packfilePath)
defer C.free(unsafe.Pointer(cstr))
ret := C._go_git_indexer_new(&indexer.ptr, cstr, 0, odbPtr, indexer.ccallbacks.payload)
runtime.KeepAlive(odb)
if ret < 0 {
untrackCallbacksPayload(&indexer.ccallbacks)
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(indexer, (*Indexer).Free)
return indexer, nil
}
// Write adds data to the indexer.
func (indexer *Indexer) Write(data []byte) (int, error) {
header := (*reflect.SliceHeader)(unsafe.Pointer(&data))
ptr := unsafe.Pointer(header.Data)
size := C.size_t(header.Len)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_indexer_append(indexer.ptr, ptr, size, &indexer.stats)
runtime.KeepAlive(indexer)
if ret < 0 {
return 0, MakeGitError(ret)
}
return len(data), nil
}
// Commit finalizes the pack and index. It resolves any pending deltas and
// writes out the index file.
//
// It also returns the packfile's hash. A packfile's name is derived from the
// sorted hashing of all object names.
func (indexer *Indexer) Commit() (*Oid, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_indexer_commit(indexer.ptr, &indexer.stats)
if ret < 0 {
return nil, MakeGitError(ret)
}
id := newOidFromC(C.git_indexer_hash(indexer.ptr))
runtime.KeepAlive(indexer)
return id, nil
}
// Free frees the indexer and its resources.
func (indexer *Indexer) Free() {
untrackCallbacksPayload(&indexer.ccallbacks)
runtime.SetFinalizer(indexer, nil)
C.git_indexer_free(indexer.ptr)
}

View File

@ -1,93 +0,0 @@
package git
import (
"fmt"
"io/ioutil"
"os"
"path"
"testing"
)
var (
// This is a packfile with three objects. The second is a delta which
// depends on the third, which is also a delta.
outOfOrderPack = []byte{
0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03,
0x32, 0x78, 0x9c, 0x63, 0x67, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x76,
0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95, 0x10,
0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63, 0x62,
0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x75, 0x01,
0xd7, 0x71, 0x36, 0x66, 0xf4, 0xde, 0x82, 0x27, 0x76, 0xc7, 0x62, 0x2c,
0x10, 0xf1, 0xb0, 0x7d, 0xe2, 0x80, 0xdc, 0x78, 0x9c, 0x63, 0x62, 0x62,
0x62, 0xb7, 0x03, 0x00, 0x00, 0x69, 0x00, 0x4c, 0xde, 0x7d, 0xaa, 0xe4,
0x19, 0x87, 0x58, 0x80, 0x61, 0x09, 0x9a, 0x33, 0xca, 0x7a, 0x31, 0x92,
0x6f, 0xae, 0x66, 0x75,
}
)
func TestIndexerOutOfOrder(t *testing.T) {
t.Parallel()
tmpPath, err := ioutil.TempDir("", "git2go")
checkFatal(t, err)
defer os.RemoveAll(tmpPath)
var finalStats TransferProgress
idx, err := NewIndexer(tmpPath, nil, func(stats TransferProgress) error {
finalStats = stats
return nil
})
checkFatal(t, err)
defer idx.Free()
_, err = idx.Write(outOfOrderPack)
checkFatal(t, err)
oid, err := idx.Commit()
checkFatal(t, err)
// The packfile contains the hash as the last 20 bytes.
expectedOid := NewOidFromBytes(outOfOrderPack[len(outOfOrderPack)-20:])
if !expectedOid.Equal(oid) {
t.Errorf("mismatched packfile hash, expected %v, got %v", expectedOid, oid)
}
if finalStats.TotalObjects != 3 {
t.Errorf("mismatched transferred objects, expected 3, got %v", finalStats.TotalObjects)
}
if finalStats.ReceivedObjects != 3 {
t.Errorf("mismatched received objects, expected 3, got %v", finalStats.ReceivedObjects)
}
if finalStats.IndexedObjects != 3 {
t.Errorf("mismatched indexed objects, expected 3, got %v", finalStats.IndexedObjects)
}
odb, err := NewOdb()
checkFatal(t, err)
defer odb.Free()
backend, err := NewOdbBackendOnePack(path.Join(tmpPath, fmt.Sprintf("pack-%s.idx", oid.String())))
checkFatal(t, err)
// Transfer the ownership of the backend to the odb, no freeing needed.
err = odb.AddBackend(backend, 1)
checkFatal(t, err)
packfileObjects := 0
err = odb.ForEach(func(id *Oid) error {
packfileObjects += 1
return nil
})
checkFatal(t, err)
if packfileObjects != 3 {
t.Errorf("mismatched packfile objects, expected 3, got %v", packfileObjects)
}
// Inspect one of the well-known objects in the packfile.
obj, err := odb.Read(NewOidFromBytes([]byte{
0x19, 0x10, 0x28, 0x15, 0x66, 0x3d, 0x23, 0xf8, 0xb7, 0x5a, 0x47, 0xe7,
0xa0, 0x19, 0x65, 0xdc, 0xdc, 0x96, 0x46, 0x8c,
}))
checkFatal(t, err)
defer obj.Free()
if "foo" != string(obj.Data()) {
t.Errorf("mismatched packfile object contents, expected foo, got %q", string(obj.Data()))
}
}

View File

@ -1,92 +0,0 @@
package git
/*
#include <git2.h>
#include <git2/sys/mempack.h>
extern int git_mempack_new(git_odb_backend **out);
extern int git_mempack_dump(git_buf *pack, git_repository *repo, git_odb_backend *backend);
extern int git_mempack_reset(git_odb_backend *backend);
extern void _go_git_odb_backend_free(git_odb_backend *backend);
*/
import "C"
import (
"runtime"
"unsafe"
)
// Mempack is a custom ODB backend that permits packing object in-memory.
type Mempack struct {
doNotCompare
ptr *C.git_odb_backend
}
// NewMempack creates a new mempack instance and registers it to the ODB.
func NewMempack(odb *Odb) (mempack *Mempack, err error) {
mempack = new(Mempack)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_mempack_new(&mempack.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
}
ret = C.git_odb_add_backend(odb.ptr, mempack.ptr, C.int(999))
runtime.KeepAlive(odb)
if ret < 0 {
// Since git_odb_add_alternate() takes ownership of the ODB backend, the
// only case in which we free the mempack's memory is if it fails to be
// added to the ODB.
C._go_git_odb_backend_free(mempack.ptr)
return nil, MakeGitError(ret)
}
return mempack, nil
}
// Dump dumps all the queued in-memory writes to a packfile.
//
// It is the caller's responsibility to ensure that the generated packfile is
// available to the repository (e.g. by writing it to disk, or doing something
// crazy like distributing it across several copies of the repository over a
// network).
//
// Once the generated packfile is available to the repository, call
// Mempack.Reset to cleanup the memory store.
//
// Calling Mempack.Reset before the packfile has been written to disk will
// result in an inconsistent repository (the objects in the memory store won't
// be accessible).
func (mempack *Mempack) Dump(repository *Repository) ([]byte, error) {
buf := C.git_buf{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_mempack_dump(&buf, repository.ptr, mempack.ptr)
runtime.KeepAlive(repository)
if ret < 0 {
return nil, MakeGitError(ret)
}
defer C.git_buf_dispose(&buf)
return C.GoBytes(unsafe.Pointer(buf.ptr), C.int(buf.size)), nil
}
// Reset resets the memory packer by clearing all the queued objects.
//
// This assumes that Mempack.Dump has been called before to store all the
// queued objects into a single packfile.
func (mempack *Mempack) Reset() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_mempack_reset(mempack.ptr)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}

View File

@ -1,60 +0,0 @@
package git
import (
"bytes"
"testing"
)
func TestMempack(t *testing.T) {
t.Parallel()
odb, err := NewOdb()
checkFatal(t, err)
repo, err := NewRepositoryWrapOdb(odb)
checkFatal(t, err)
mempack, err := NewMempack(odb)
checkFatal(t, err)
id, err := odb.Write([]byte("hello, world!"), ObjectBlob)
checkFatal(t, err)
expectedId, err := NewOid("30f51a3fba5274d53522d0f19748456974647b4f")
checkFatal(t, err)
if !expectedId.Equal(id) {
t.Errorf("mismatched id. expected %v, got %v", expectedId.String(), id.String())
}
// The object should be available from the odb.
{
obj, err := odb.Read(expectedId)
checkFatal(t, err)
defer obj.Free()
}
data, err := mempack.Dump(repo)
checkFatal(t, err)
expectedData := []byte{
0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x02, 0x9d, 0x08, 0x82, 0x3b, 0xd8, 0xa8, 0xea, 0xb5, 0x10, 0xad, 0x6a,
0xc7, 0x5c, 0x82, 0x3c, 0xfd, 0x3e, 0xd3, 0x1e,
}
if !bytes.Equal(expectedData, data) {
t.Errorf("mismatched mempack data. expected %v, got %v", expectedData, data)
}
mempack.Reset()
// After the reset, the object should now be unavailable.
{
obj, err := odb.Read(expectedId)
if err == nil {
t.Errorf("object %s unexpectedly found", obj.Id().String())
obj.Free()
} else if !IsErrorCode(err, ErrorCodeNotFound) {
t.Errorf("unexpected error %v", err)
}
}
}

596
merge.go
View File

@ -1,596 +0,0 @@
package git
/*
#include <git2.h>
extern git_annotated_commit** _go_git_make_merge_head_array(size_t len);
extern void _go_git_annotated_commit_array_set(git_annotated_commit** array, git_annotated_commit* ptr, size_t n);
extern git_annotated_commit* _go_git_annotated_commit_array_get(git_annotated_commit** array, size_t n);
extern int _go_git_merge_file(git_merge_file_result*, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, char*, size_t, char*, unsigned int, git_merge_file_options*);
*/
import "C"
import (
"reflect"
"runtime"
"unsafe"
)
type AnnotatedCommit struct {
doNotCompare
ptr *C.git_annotated_commit
r *Repository
}
func newAnnotatedCommitFromC(ptr *C.git_annotated_commit, r *Repository) *AnnotatedCommit {
mh := &AnnotatedCommit{ptr: ptr, r: r}
runtime.SetFinalizer(mh, (*AnnotatedCommit).Free)
return mh
}
func (mh *AnnotatedCommit) Id() *Oid {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := newOidFromC(C.git_annotated_commit_id(mh.ptr))
runtime.KeepAlive(mh)
return ret
}
func (mh *AnnotatedCommit) Free() {
runtime.SetFinalizer(mh, nil)
C.git_annotated_commit_free(mh.ptr)
}
func (r *Repository) AnnotatedCommitFromFetchHead(branchName string, remoteURL string, oid *Oid) (*AnnotatedCommit, error) {
cbranchName := C.CString(branchName)
defer C.free(unsafe.Pointer(cbranchName))
cremoteURL := C.CString(remoteURL)
defer C.free(unsafe.Pointer(cremoteURL))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_annotated_commit
ret := C.git_annotated_commit_from_fetchhead(&ptr, r.ptr, cbranchName, cremoteURL, oid.toC())
runtime.KeepAlive(oid)
if ret < 0 {
return nil, MakeGitError(ret)
}
annotatedCommit := newAnnotatedCommitFromC(ptr, r)
runtime.KeepAlive(r)
return annotatedCommit, nil
}
func (r *Repository) LookupAnnotatedCommit(oid *Oid) (*AnnotatedCommit, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_annotated_commit
ret := C.git_annotated_commit_lookup(&ptr, r.ptr, oid.toC())
runtime.KeepAlive(oid)
if ret < 0 {
return nil, MakeGitError(ret)
}
annotatedCommit := newAnnotatedCommitFromC(ptr, r)
runtime.KeepAlive(r)
return annotatedCommit, nil
}
func (r *Repository) AnnotatedCommitFromRef(ref *Reference) (*AnnotatedCommit, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_annotated_commit
ret := C.git_annotated_commit_from_ref(&ptr, r.ptr, ref.ptr)
runtime.KeepAlive(r)
runtime.KeepAlive(ref)
if ret < 0 {
return nil, MakeGitError(ret)
}
annotatedCommit := newAnnotatedCommitFromC(ptr, r)
runtime.KeepAlive(r)
return annotatedCommit, nil
}
func (r *Repository) AnnotatedCommitFromRevspec(spec string) (*AnnotatedCommit, error) {
crevspec := C.CString(spec)
defer C.free(unsafe.Pointer(crevspec))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_annotated_commit
ret := C.git_annotated_commit_from_revspec(&ptr, r.ptr, crevspec)
runtime.KeepAlive(r)
if ret < 0 {
return nil, MakeGitError(ret)
}
annotatedCommit := newAnnotatedCommitFromC(ptr, r)
runtime.KeepAlive(r)
return annotatedCommit, nil
}
type MergeTreeFlag int
const (
// Detect renames that occur between the common ancestor and the "ours"
// side or the common ancestor and the "theirs" side. This will enable
// the ability to merge between a modified and renamed file.
MergeTreeFindRenames MergeTreeFlag = C.GIT_MERGE_FIND_RENAMES
// If a conflict occurs, exit immediately instead of attempting to
// continue resolving conflicts. The merge operation will fail with
// GIT_EMERGECONFLICT and no index will be returned.
MergeTreeFailOnConflict MergeTreeFlag = C.GIT_MERGE_FAIL_ON_CONFLICT
// MergeTreeSkipREUC specifies not to write the REUC extension on the
// generated index.
MergeTreeSkipREUC MergeTreeFlag = C.GIT_MERGE_SKIP_REUC
// MergeTreeNoRecursive specifies not to build a recursive merge base (by
// merging the multiple merge bases) if the commits being merged have
// multiple merge bases. Instead, the first base is used.
// This flag provides a similar merge base to `git-merge-resolve`.
MergeTreeNoRecursive MergeTreeFlag = C.GIT_MERGE_NO_RECURSIVE
)
type MergeOptions struct {
TreeFlags MergeTreeFlag
RenameThreshold uint
TargetLimit uint
RecursionLimit uint
FileFavor MergeFileFavor
//TODO: Diff similarity metric
}
func mergeOptionsFromC(opts *C.git_merge_options) MergeOptions {
return MergeOptions{
TreeFlags: MergeTreeFlag(opts.flags),
RenameThreshold: uint(opts.rename_threshold),
TargetLimit: uint(opts.target_limit),
RecursionLimit: uint(opts.recursion_limit),
FileFavor: MergeFileFavor(opts.file_favor),
}
}
func DefaultMergeOptions() (MergeOptions, error) {
opts := C.git_merge_options{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_merge_options_init(&opts, C.GIT_MERGE_OPTIONS_VERSION)
if ecode < 0 {
return MergeOptions{}, MakeGitError(ecode)
}
return mergeOptionsFromC(&opts), nil
}
func populateMergeOptions(copts *C.git_merge_options, opts *MergeOptions) *C.git_merge_options {
C.git_merge_options_init(copts, C.GIT_MERGE_OPTIONS_VERSION)
if opts == nil {
return nil
}
copts.flags = C.uint32_t(opts.TreeFlags)
copts.rename_threshold = C.uint(opts.RenameThreshold)
copts.target_limit = C.uint(opts.TargetLimit)
copts.recursion_limit = C.uint(opts.RecursionLimit)
copts.file_favor = C.git_merge_file_favor_t(opts.FileFavor)
return copts
}
func freeMergeOptions(copts *C.git_merge_options) {
}
type MergeFileFavor int
const (
MergeFileFavorNormal MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_NORMAL
MergeFileFavorOurs MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_OURS
MergeFileFavorTheirs MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_THEIRS
MergeFileFavorUnion MergeFileFavor = C.GIT_MERGE_FILE_FAVOR_UNION
)
func (r *Repository) Merge(theirHeads []*AnnotatedCommit, mergeOptions *MergeOptions, checkoutOptions *CheckoutOptions) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var err error
cMergeOpts := populateMergeOptions(&C.git_merge_options{}, mergeOptions)
defer freeMergeOptions(cMergeOpts)
cCheckoutOptions := populateCheckoutOptions(&C.git_checkout_options{}, checkoutOptions, &err)
defer freeCheckoutOptions(cCheckoutOptions)
gmerge_head_array := make([]*C.git_annotated_commit, len(theirHeads))
for i := 0; i < len(theirHeads); i++ {
gmerge_head_array[i] = theirHeads[i].ptr
}
ptr := unsafe.Pointer(&gmerge_head_array[0])
ret := C.git_merge(r.ptr, (**C.git_annotated_commit)(ptr), C.size_t(len(theirHeads)), cMergeOpts, cCheckoutOptions)
runtime.KeepAlive(theirHeads)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
type MergeAnalysis int
const (
MergeAnalysisNone MergeAnalysis = C.GIT_MERGE_ANALYSIS_NONE
MergeAnalysisNormal MergeAnalysis = C.GIT_MERGE_ANALYSIS_NORMAL
MergeAnalysisUpToDate MergeAnalysis = C.GIT_MERGE_ANALYSIS_UP_TO_DATE
MergeAnalysisFastForward MergeAnalysis = C.GIT_MERGE_ANALYSIS_FASTFORWARD
MergeAnalysisUnborn MergeAnalysis = C.GIT_MERGE_ANALYSIS_UNBORN
)
type MergePreference int
const (
MergePreferenceNone MergePreference = C.GIT_MERGE_PREFERENCE_NONE
MergePreferenceNoFastForward MergePreference = C.GIT_MERGE_PREFERENCE_NO_FASTFORWARD
MergePreferenceFastForwardOnly MergePreference = C.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY
)
// MergeAnalysis returns the possible actions which could be taken by
// a 'git-merge' command. There may be multiple answers, so the first
// return value is a bitmask of MergeAnalysis values.
func (r *Repository) MergeAnalysis(theirHeads []*AnnotatedCommit) (MergeAnalysis, MergePreference, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
gmerge_head_array := make([]*C.git_annotated_commit, len(theirHeads))
for i := 0; i < len(theirHeads); i++ {
gmerge_head_array[i] = theirHeads[i].ptr
}
ptr := unsafe.Pointer(&gmerge_head_array[0])
var analysis C.git_merge_analysis_t
var preference C.git_merge_preference_t
err := C.git_merge_analysis(&analysis, &preference, r.ptr, (**C.git_annotated_commit)(ptr), C.size_t(len(theirHeads)))
runtime.KeepAlive(theirHeads)
if err < 0 {
return MergeAnalysisNone, MergePreferenceNone, MakeGitError(err)
}
return MergeAnalysis(analysis), MergePreference(preference), nil
}
func (r *Repository) MergeCommits(ours *Commit, theirs *Commit, options *MergeOptions) (*Index, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
copts := populateMergeOptions(&C.git_merge_options{}, options)
defer freeMergeOptions(copts)
var ptr *C.git_index
ret := C.git_merge_commits(&ptr, r.ptr, ours.cast_ptr, theirs.cast_ptr, copts)
runtime.KeepAlive(ours)
runtime.KeepAlive(theirs)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newIndexFromC(ptr, r), nil
}
func (r *Repository) MergeTrees(ancestor *Tree, ours *Tree, theirs *Tree, options *MergeOptions) (*Index, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
copts := populateMergeOptions(&C.git_merge_options{}, options)
defer freeMergeOptions(copts)
var ancestor_ptr *C.git_tree
if ancestor != nil {
ancestor_ptr = ancestor.cast_ptr
}
var ptr *C.git_index
ret := C.git_merge_trees(&ptr, r.ptr, ancestor_ptr, ours.cast_ptr, theirs.cast_ptr, copts)
runtime.KeepAlive(ancestor)
runtime.KeepAlive(ours)
runtime.KeepAlive(theirs)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newIndexFromC(ptr, r), nil
}
func (r *Repository) MergeBase(one *Oid, two *Oid) (*Oid, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var oid C.git_oid
ret := C.git_merge_base(&oid, r.ptr, one.toC(), two.toC())
runtime.KeepAlive(one)
runtime.KeepAlive(two)
runtime.KeepAlive(r)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newOidFromC(&oid), nil
}
// MergeBases retrieves the list of merge bases between two commits.
//
// If none are found, an empty slice is returned and the error is set
// approprately
func (r *Repository) MergeBases(one, two *Oid) ([]*Oid, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var coids C.git_oidarray
ret := C.git_merge_bases(&coids, r.ptr, one.toC(), two.toC())
runtime.KeepAlive(one)
runtime.KeepAlive(two)
if ret < 0 {
return nil, MakeGitError(ret)
}
oids := make([]*Oid, coids.count)
hdr := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(coids.ids)),
Len: int(coids.count),
Cap: int(coids.count),
}
goSlice := *(*[]C.git_oid)(unsafe.Pointer(&hdr))
for i, cid := range goSlice {
oids[i] = newOidFromC(&cid)
}
return oids, nil
}
// MergeBaseMany finds a merge base given a list of commits.
func (r *Repository) MergeBaseMany(oids []*Oid) (*Oid, error) {
coids := make([]C.git_oid, len(oids))
for i := 0; i < len(oids); i++ {
coids[i] = *oids[i].toC()
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var oid C.git_oid
ret := C.git_merge_base_many(&oid, r.ptr, C.size_t(len(oids)), &coids[0])
runtime.KeepAlive(r)
runtime.KeepAlive(coids)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newOidFromC(&oid), nil
}
// MergeBasesMany finds all merge bases given a list of commits.
func (r *Repository) MergeBasesMany(oids []*Oid) ([]*Oid, error) {
inCoids := make([]C.git_oid, len(oids))
for i := 0; i < len(oids); i++ {
inCoids[i] = *oids[i].toC()
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var outCoids C.git_oidarray
ret := C.git_merge_bases_many(&outCoids, r.ptr, C.size_t(len(oids)), &inCoids[0])
runtime.KeepAlive(r)
runtime.KeepAlive(inCoids)
if ret < 0 {
return nil, MakeGitError(ret)
}
outOids := make([]*Oid, outCoids.count)
hdr := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(outCoids.ids)),
Len: int(outCoids.count),
Cap: int(outCoids.count),
}
goSlice := *(*[]C.git_oid)(unsafe.Pointer(&hdr))
for i, cid := range goSlice {
outOids[i] = newOidFromC(&cid)
}
return outOids, nil
}
// MergeBaseOctopus finds a merge base in preparation for an octopus merge.
func (r *Repository) MergeBaseOctopus(oids []*Oid) (*Oid, error) {
coids := make([]C.git_oid, len(oids))
for i := 0; i < len(oids); i++ {
coids[i] = *oids[i].toC()
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var oid C.git_oid
ret := C.git_merge_base_octopus(&oid, r.ptr, C.size_t(len(oids)), &coids[0])
runtime.KeepAlive(r)
runtime.KeepAlive(coids)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newOidFromC(&oid), nil
}
type MergeFileResult struct {
doNotCompare
ptr *C.git_merge_file_result
Automergeable bool
Path string
Mode uint
Contents []byte
}
func newMergeFileResultFromC(c *C.git_merge_file_result) *MergeFileResult {
var path string
if c.path != nil {
path = C.GoString(c.path)
}
originalBytes := C.GoBytes(unsafe.Pointer(c.ptr), C.int(c.len))
gobytes := make([]byte, len(originalBytes))
copy(gobytes, originalBytes)
r := &MergeFileResult{
Automergeable: c.automergeable != 0,
Path: path,
Mode: uint(c.mode),
Contents: gobytes,
ptr: c,
}
runtime.SetFinalizer(r, (*MergeFileResult).Free)
return r
}
func (r *MergeFileResult) Free() {
runtime.SetFinalizer(r, nil)
C.git_merge_file_result_free(r.ptr)
}
type MergeFileInput struct {
Path string
Mode uint
Contents []byte
}
type MergeFileFlags int
const (
MergeFileDefault MergeFileFlags = C.GIT_MERGE_FILE_DEFAULT
// Create standard conflicted merge files
MergeFileStyleMerge MergeFileFlags = C.GIT_MERGE_FILE_STYLE_MERGE
// Create diff3-style files
MergeFileStyleDiff MergeFileFlags = C.GIT_MERGE_FILE_STYLE_DIFF3
// Condense non-alphanumeric regions for simplified diff file
MergeFileStyleSimplifyAlnum MergeFileFlags = C.GIT_MERGE_FILE_SIMPLIFY_ALNUM
// Ignore all whitespace
MergeFileIgnoreWhitespace MergeFileFlags = C.GIT_MERGE_FILE_IGNORE_WHITESPACE
// Ignore changes in amount of whitespace
MergeFileIgnoreWhitespaceChange MergeFileFlags = C.GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE
// Ignore whitespace at end of line
MergeFileIgnoreWhitespaceEOL MergeFileFlags = C.GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL
// Use the "patience diff" algorithm
MergeFileDiffPatience MergeFileFlags = C.GIT_MERGE_FILE_DIFF_PATIENCE
// Take extra time to find minimal diff
MergeFileDiffMinimal MergeFileFlags = C.GIT_MERGE_FILE_DIFF_MINIMAL
)
type MergeFileOptions struct {
AncestorLabel string
OurLabel string
TheirLabel string
Favor MergeFileFavor
Flags MergeFileFlags
MarkerSize uint16
}
func mergeFileOptionsFromC(c C.git_merge_file_options) MergeFileOptions {
return MergeFileOptions{
AncestorLabel: C.GoString(c.ancestor_label),
OurLabel: C.GoString(c.our_label),
TheirLabel: C.GoString(c.their_label),
Favor: MergeFileFavor(c.favor),
Flags: MergeFileFlags(c.flags),
MarkerSize: uint16(c.marker_size),
}
}
func populateMergeFileOptions(copts *C.git_merge_file_options, opts *MergeFileOptions) *C.git_merge_file_options {
C.git_merge_file_options_init(copts, C.GIT_MERGE_FILE_OPTIONS_VERSION)
if opts == nil {
return nil
}
copts.ancestor_label = C.CString(opts.AncestorLabel)
copts.our_label = C.CString(opts.OurLabel)
copts.their_label = C.CString(opts.TheirLabel)
copts.favor = C.git_merge_file_favor_t(opts.Favor)
copts.flags = C.uint32_t(opts.Flags)
copts.marker_size = C.ushort(opts.MarkerSize)
return copts
}
func freeMergeFileOptions(copts *C.git_merge_file_options) {
if copts == nil {
return
}
C.free(unsafe.Pointer(copts.ancestor_label))
C.free(unsafe.Pointer(copts.our_label))
C.free(unsafe.Pointer(copts.their_label))
}
func MergeFile(ancestor MergeFileInput, ours MergeFileInput, theirs MergeFileInput, options *MergeFileOptions) (*MergeFileResult, error) {
ancestorPath := C.CString(ancestor.Path)
defer C.free(unsafe.Pointer(ancestorPath))
var ancestorContents *byte
if len(ancestor.Contents) > 0 {
ancestorContents = &ancestor.Contents[0]
}
oursPath := C.CString(ours.Path)
defer C.free(unsafe.Pointer(oursPath))
var oursContents *byte
if len(ours.Contents) > 0 {
oursContents = &ours.Contents[0]
}
theirsPath := C.CString(theirs.Path)
defer C.free(unsafe.Pointer(theirsPath))
var theirsContents *byte
if len(theirs.Contents) > 0 {
theirsContents = &theirs.Contents[0]
}
var copts *C.git_merge_file_options
if options != nil {
copts = &C.git_merge_file_options{}
ecode := C.git_merge_file_options_init(copts, C.GIT_MERGE_FILE_OPTIONS_VERSION)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
populateMergeFileOptions(copts, options)
defer freeMergeFileOptions(copts)
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var result C.git_merge_file_result
ecode := C._go_git_merge_file(&result,
(*C.char)(unsafe.Pointer(ancestorContents)), C.size_t(len(ancestor.Contents)), ancestorPath, C.uint(ancestor.Mode),
(*C.char)(unsafe.Pointer(oursContents)), C.size_t(len(ours.Contents)), oursPath, C.uint(ours.Mode),
(*C.char)(unsafe.Pointer(theirsContents)), C.size_t(len(theirs.Contents)), theirsPath, C.uint(theirs.Mode),
copts)
runtime.KeepAlive(ancestor)
runtime.KeepAlive(ours)
runtime.KeepAlive(theirs)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newMergeFileResultFromC(&result), nil
}
// TODO: GIT_EXTERN(int) git_merge_file_from_index(git_merge_file_result *out,git_repository *repo,const git_index_entry *ancestor, const git_index_entry *ours, const git_index_entry *theirs, const git_merge_file_options *opts);

View File

@ -1,254 +0,0 @@
package git
import (
"testing"
"time"
)
func TestAnnotatedCommitFromRevspec(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
mergeHead, err := repo.AnnotatedCommitFromRevspec("refs/heads/master")
checkFatal(t, err)
expectedId := "473bf778b67b6d53e2ab289e0f1a2e8addef2fc2"
if mergeHead.Id().String() != expectedId {
t.Errorf("mergeHead.Id() = %v, want %v", mergeHead.Id(), expectedId)
}
}
func TestMergeWithSelf(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
master, err := repo.References.Lookup("refs/heads/master")
checkFatal(t, err)
mergeHead, err := repo.AnnotatedCommitFromRef(master)
checkFatal(t, err)
expectedId := "473bf778b67b6d53e2ab289e0f1a2e8addef2fc2"
if mergeHead.Id().String() != expectedId {
t.Errorf("mergeHead.Id() = %v, want %v", mergeHead.Id(), expectedId)
}
mergeHeads := make([]*AnnotatedCommit, 1)
mergeHeads[0] = mergeHead
err = repo.Merge(mergeHeads, nil, nil)
checkFatal(t, err)
mergeMessage, err := repo.Message()
checkFatal(t, err)
expectedMessage := "Merge branch 'master'\n"
if mergeMessage != expectedMessage {
t.Errorf("merge Message = %v, want %v", mergeMessage, expectedMessage)
}
}
func TestMergeAnalysisWithSelf(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
master, err := repo.References.Lookup("refs/heads/master")
checkFatal(t, err)
mergeHead, err := repo.AnnotatedCommitFromRef(master)
checkFatal(t, err)
mergeHeads := make([]*AnnotatedCommit, 1)
mergeHeads[0] = mergeHead
a, _, err := repo.MergeAnalysis(mergeHeads)
checkFatal(t, err)
if a != MergeAnalysisUpToDate {
t.Fatalf("Expected up to date merge, not %v", a)
}
}
func TestMergeSameFile(t *testing.T) {
t.Parallel()
file := MergeFileInput{
Path: "test",
Mode: 33188,
Contents: []byte("hello world"),
}
result, err := MergeFile(file, file, file, nil)
checkFatal(t, err)
if !result.Automergeable {
t.Fatal("expected automergeable")
}
if result.Path != file.Path {
t.Fatal("path was incorrect")
}
if result.Mode != file.Mode {
t.Fatal("mode was incorrect")
}
compareBytes(t, file.Contents, result.Contents)
}
func TestMergeTreesWithoutAncestor(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, originalTreeId := seedTestRepo(t, repo)
originalTree, err := repo.LookupTree(originalTreeId)
checkFatal(t, err)
_, newTreeId := updateReadme(t, repo, "file changed\n")
newTree, err := repo.LookupTree(newTreeId)
checkFatal(t, err)
index, err := repo.MergeTrees(nil, originalTree, newTree, nil)
if !index.HasConflicts() {
t.Fatal("expected conflicts in the index")
}
_, err = index.Conflict("README")
checkFatal(t, err)
}
func appendCommit(t *testing.T, repo *Repository) (*Oid, *Oid) {
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),
}
idx, err := repo.Index()
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
treeId, err := idx.WriteTree()
checkFatal(t, err)
message := "This is another commit\n"
tree, err := repo.LookupTree(treeId)
checkFatal(t, err)
ref, err := repo.References.Lookup("HEAD")
checkFatal(t, err)
parent, err := ref.Peel(ObjectCommit)
checkFatal(t, err)
parentCommit, err := parent.AsCommit()
checkFatal(t, err)
commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree, parentCommit)
checkFatal(t, err)
return commitId, treeId
}
func TestMergeBase(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitAId, _ := seedTestRepo(t, repo)
commitBId, _ := appendCommit(t, repo)
mergeBase, err := repo.MergeBase(commitAId, commitBId)
checkFatal(t, err)
if mergeBase.Cmp(commitAId) != 0 {
t.Fatalf("unexpected merge base")
}
}
func TestMergeBases(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitAId, _ := seedTestRepo(t, repo)
commitBId, _ := appendCommit(t, repo)
mergeBases, err := repo.MergeBases(commitAId, commitBId)
checkFatal(t, err)
if len(mergeBases) != 1 {
t.Fatalf("expected merge bases len to be 1, got %v", len(mergeBases))
}
if mergeBases[0].Cmp(commitAId) != 0 {
t.Fatalf("unexpected merge base")
}
}
func TestMergeBaseMany(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitAId, _ := seedTestRepo(t, repo)
commitBId, _ := appendCommit(t, repo)
mergeBase, err := repo.MergeBaseMany([]*Oid{commitAId, commitBId})
checkFatal(t, err)
if mergeBase.Cmp(commitAId) != 0 {
t.Fatalf("unexpected merge base")
}
}
func TestMergeBasesMany(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitAId, _ := seedTestRepo(t, repo)
commitBId, _ := appendCommit(t, repo)
mergeBases, err := repo.MergeBasesMany([]*Oid{commitAId, commitBId})
checkFatal(t, err)
if len(mergeBases) != 1 {
t.Fatalf("expected merge bases len to be 1, got %v", len(mergeBases))
}
if mergeBases[0].Cmp(commitAId) != 0 {
t.Fatalf("unexpected merge base")
}
}
func TestMergeBaseOctopus(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitAId, _ := seedTestRepo(t, repo)
commitBId, _ := appendCommit(t, repo)
mergeBase, err := repo.MergeBaseOctopus([]*Oid{commitAId, commitBId})
checkFatal(t, err)
if mergeBase.Cmp(commitAId) != 0 {
t.Fatalf("unexpected merge base")
}
}
func compareBytes(t *testing.T, expected, actual []byte) {
for i, v := range expected {
if actual[i] != v {
t.Fatalf("Bad bytes")
}
}
}

View File

@ -1,42 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
// Trailer represents a single git message trailer.
type Trailer struct {
Key string
Value string
}
// MessageTrailers parses trailers out of a message, returning a slice of
// Trailer structs. Trailers are key/value pairs in the last paragraph of a
// message, not including any patches or conflicts that may be present.
func MessageTrailers(message string) ([]Trailer, error) {
var trailersC C.git_message_trailer_array
messageC := C.CString(message)
defer C.free(unsafe.Pointer(messageC))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_message_trailers(&trailersC, messageC)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
defer C.git_message_trailer_array_free(&trailersC)
trailers := make([]Trailer, trailersC.count)
var trailer *C.git_message_trailer
for i, p := 0, uintptr(unsafe.Pointer(trailersC.trailers)); i < int(trailersC.count); i, p = i+1, p+unsafe.Sizeof(C.git_message_trailer{}) {
trailer = (*C.git_message_trailer)(unsafe.Pointer(p))
trailers[i] = Trailer{Key: C.GoString(trailer.key), Value: C.GoString(trailer.value)}
}
return trailers, nil
}

View File

@ -1,42 +0,0 @@
package git
import (
"fmt"
"reflect"
"testing"
)
func TestTrailers(t *testing.T) {
t.Parallel()
tests := []struct {
input string
expected []Trailer
}{
{
"commit with zero trailers\n",
[]Trailer{},
},
{
"commit with one trailer\n\nCo-authored-by: Alice <alice@example.com>\n",
[]Trailer{
Trailer{Key: "Co-authored-by", Value: "Alice <alice@example.com>"},
},
},
{
"commit with two trailers\n\nCo-authored-by: Alice <alice@example.com>\nSigned-off-by: Bob <bob@example.com>\n",
[]Trailer{
Trailer{Key: "Co-authored-by", Value: "Alice <alice@example.com>"},
Trailer{Key: "Signed-off-by", Value: "Bob <bob@example.com>"}},
},
}
for _, test := range tests {
fmt.Printf("%s", test.input)
actual, err := MessageTrailers(test.input)
if err != nil {
t.Errorf("Trailers returned an unexpected error: %v", err)
}
if !reflect.DeepEqual(test.expected, actual) {
t.Errorf("expecting %#v\ngot %#v", test.expected, actual)
}
}
}

247
note.go
View File

@ -1,247 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
// This object represents the possible operations which can be
// performed on the collection of notes for a repository.
type NoteCollection struct {
doNotCompare
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))
runtime.KeepAlive(c)
runtime.KeepAlive(id)
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))
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_note
ret := C.git_note_read(&ptr, c.repo.ptr, cref, id.toC())
runtime.KeepAlive(c)
runtime.KeepAlive(id)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newNoteFromC(ptr, c.repo), 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())
runtime.KeepAlive(c)
runtime.KeepAlive(id)
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()
ecode := C.git_note_default_ref(&buf, c.repo.ptr)
runtime.KeepAlive(c)
if ecode < 0 {
return "", MakeGitError(ecode)
}
ret := C.GoString(buf.ptr)
C.git_buf_dispose(&buf)
return ret, nil
}
// Note
type Note struct {
doNotCompare
ptr *C.git_note
r *Repository
}
func newNoteFromC(ptr *C.git_note, r *Repository) *Note {
note := &Note{ptr: ptr, r: r}
runtime.SetFinalizer(note, (*Note).Free)
return note
}
// Free frees a git_note object
func (n *Note) Free() error {
if n.ptr == nil {
return ErrInvalid
}
runtime.SetFinalizer(n, nil)
C.git_note_free(n.ptr)
n.ptr = nil
return nil
}
// Author returns the signature of the note author
func (n *Note) Author() *Signature {
ptr := C.git_note_author(n.ptr)
return newSignatureFromC(ptr)
}
// Id returns the note object's id
func (n *Note) Id() *Oid {
ptr := C.git_note_id(n.ptr)
runtime.KeepAlive(n)
return newOidFromC(ptr)
}
// Committer returns the signature of the note committer
func (n *Note) Committer() *Signature {
ptr := C.git_note_committer(n.ptr)
runtime.KeepAlive(n)
return newSignatureFromC(ptr)
}
// Message returns the note message
func (n *Note) Message() string {
ret := C.GoString(C.git_note_message(n.ptr))
runtime.KeepAlive(n)
return ret
}
// NoteIterator
type NoteIterator struct {
doNotCompare
ptr *C.git_note_iterator
r *Repository
}
// NewNoteIterator creates a new iterator for notes
func (repo *Repository) NewNoteIterator(ref string) (*NoteIterator, error) {
var cref *C.char
if ref == "" {
cref = nil
} else {
cref = C.CString(ref)
defer C.free(unsafe.Pointer(cref))
}
var ptr *C.git_note_iterator
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_note_iterator_new(&ptr, repo.ptr, cref)
runtime.KeepAlive(repo)
if ret < 0 {
return nil, MakeGitError(ret)
}
iter := &NoteIterator{ptr: ptr, r: repo}
runtime.SetFinalizer(iter, (*NoteIterator).Free)
return iter, nil
}
// Free frees the note interator
func (v *NoteIterator) Free() {
runtime.SetFinalizer(v, nil)
C.git_note_iterator_free(v.ptr)
}
// Next returns the current item (note id & annotated id) and advances the
// iterator internally to the next item
func (it *NoteIterator) Next() (noteId, annotatedId *Oid, err error) {
noteId, annotatedId = new(Oid), new(Oid)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_note_next(noteId.toC(), annotatedId.toC(), it.ptr)
runtime.KeepAlive(noteId)
runtime.KeepAlive(annotatedId)
runtime.KeepAlive(it)
if ret < 0 {
err = MakeGitError(ret)
}
return
}

View File

@ -1,117 +0,0 @@
package git
import (
"fmt"
"reflect"
"testing"
"time"
)
func TestCreateNote(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId, _ := seedTestRepo(t, repo)
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
note, noteId := createTestNote(t, repo, commit)
compareStrings(t, "I am a note\n", note.Message())
compareStrings(t, noteId.String(), note.Id().String())
compareStrings(t, "alice", note.Author().Name)
compareStrings(t, "alice@example.com", note.Author().Email)
compareStrings(t, "alice", note.Committer().Name)
compareStrings(t, "alice@example.com", note.Committer().Email)
}
func TestNoteIterator(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
notes := make([]*Note, 5)
for i := range notes {
commitId, _ := updateReadme(t, repo, fmt.Sprintf("README v%d\n", i+1))
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
note, _ := createTestNote(t, repo, commit)
notes[i] = note
}
iter, err := repo.NewNoteIterator("")
checkFatal(t, err)
for {
noteId, commitId, err := iter.Next()
if err != nil {
if !IsErrorCode(err, ErrorCodeIterOver) {
checkFatal(t, err)
}
break
}
note, err := repo.Notes.Read("", commitId)
checkFatal(t, err)
if !reflect.DeepEqual(note.Id(), noteId) {
t.Errorf("expected note oid '%v', actual '%v'", note.Id(), noteId)
}
}
}
func TestRemoveNote(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId, _ := seedTestRepo(t, repo)
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
note, _ := createTestNote(t, repo, commit)
_, err = repo.Notes.Read("", commit.Id())
checkFatal(t, err)
err = repo.Notes.Remove("", note.Author(), note.Committer(), commitId)
checkFatal(t, err)
_, err = repo.Notes.Read("", commit.Id())
if err == nil {
t.Fatal("note remove failed")
}
}
func TestDefaultNoteRef(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
ref, err := repo.Notes.DefaultRef()
checkFatal(t, err)
compareStrings(t, "refs/notes/commits", ref)
}
func createTestNote(t *testing.T, repo *Repository, commit *Commit) (*Note, *Oid) {
loc, err := time.LoadLocation("Europe/Berlin")
sig := &Signature{
Name: "alice",
Email: "alice@example.com",
When: time.Date(2015, 01, 05, 13, 0, 0, 0, loc),
}
noteId, err := repo.Notes.Create("", sig, sig, commit.Id(), "I am a note\n", false)
checkFatal(t, err)
note, err := repo.Notes.Read("", commit.Id())
checkFatal(t, err)
return note, noteId
}

238
object.go
View File

@ -1,43 +1,40 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
#include <git2/errors.h>
*/
import "C"
import (
"errors"
"fmt"
"runtime"
)
import "runtime"
type ObjectType int
const (
ObjectAny ObjectType = C.GIT_OBJECT_ANY
ObjectInvalid ObjectType = C.GIT_OBJECT_INVALID
ObjectCommit ObjectType = C.GIT_OBJECT_COMMIT
ObjectTree ObjectType = C.GIT_OBJECT_TREE
ObjectBlob ObjectType = C.GIT_OBJECT_BLOB
ObjectTag ObjectType = C.GIT_OBJECT_TAG
ObjectAny ObjectType = C.GIT_OBJ_ANY
ObjectBad = C.GIT_OBJ_BAD
ObjectCommit = C.GIT_OBJ_COMMIT
ObjectTree = C.GIT_OBJ_TREE
ObjectBlob = C.GIT_OBJ_BLOB
ObjectTag = C.GIT_OBJ_TAG
)
type Object struct {
doNotCompare
ptr *C.git_object
repo *Repository
type Object interface {
Free()
Id() *Oid
Type() ObjectType
}
// Objecter lets us accept any kind of Git object in functions.
type Objecter interface {
AsObject() *Object
type gitObject struct {
ptr *C.git_object
}
func (t ObjectType) String() string {
switch t {
func (t ObjectType) String() (string) {
switch (t) {
case ObjectAny:
return "Any"
case ObjectInvalid:
return "Invalid"
case ObjectBad:
return "Bad"
case ObjectCommit:
return "Commit"
case ObjectTree:
@ -45,194 +42,43 @@ func (t ObjectType) String() string {
case ObjectBlob:
return "Blob"
case ObjectTag:
return "Tag"
return "tag"
}
// Never reached
return ""
}
func (o *Object) Id() *Oid {
ret := newOidFromC(C.git_object_id(o.ptr))
runtime.KeepAlive(o)
return ret
func (o gitObject) Id() *Oid {
return newOidFromC(C.git_commit_id(o.ptr))
}
func (o *Object) ShortId() (string, error) {
resultBuf := C.git_buf{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_object_short_id(&resultBuf, o.ptr)
runtime.KeepAlive(o)
if ecode < 0 {
return "", MakeGitError(ecode)
}
defer C.git_buf_dispose(&resultBuf)
return C.GoString(resultBuf.ptr), nil
func (o gitObject) Type() ObjectType {
return ObjectType(C.git_object_type(o.ptr))
}
func (o *Object) Type() ObjectType {
ret := ObjectType(C.git_object_type(o.ptr))
runtime.KeepAlive(o)
return ret
}
// Owner returns a weak reference to the repository which owns this object.
// This won't keep the underlying repository alive, but it should still be
// Freed.
func (o *Object) Owner() *Repository {
repo := newRepositoryFromC(C.git_object_owner(o.ptr))
runtime.KeepAlive(o)
repo.weak = true
return repo
}
func dupObject(obj *Object, kind ObjectType) (*C.git_object, error) {
if obj.Type() != kind {
return nil, errors.New(fmt.Sprintf("object is not a %v", kind))
}
var cobj *C.git_object
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C.git_object_dup(&cobj, obj.ptr)
runtime.KeepAlive(obj)
if err < 0 {
return nil, MakeGitError(err)
}
return cobj, nil
}
func allocTree(ptr *C.git_tree, repo *Repository) *Tree {
tree := &Tree{
Object: Object{
ptr: (*C.git_object)(ptr),
repo: repo,
},
cast_ptr: ptr,
}
runtime.SetFinalizer(tree, (*Tree).Free)
return tree
}
func (o *Object) AsTree() (*Tree, error) {
cobj, err := dupObject(o, ObjectTree)
if err != nil {
return nil, err
}
return allocTree((*C.git_tree)(cobj), o.repo), nil
}
func allocCommit(ptr *C.git_commit, repo *Repository) *Commit {
commit := &Commit{
Object: Object{
ptr: (*C.git_object)(ptr),
repo: repo,
},
cast_ptr: ptr,
}
runtime.SetFinalizer(commit, (*Commit).Free)
return commit
}
func (o *Object) AsCommit() (*Commit, error) {
cobj, err := dupObject(o, ObjectCommit)
if err != nil {
return nil, err
}
return allocCommit((*C.git_commit)(cobj), o.repo), nil
}
func allocBlob(ptr *C.git_blob, repo *Repository) *Blob {
blob := &Blob{
Object: Object{
ptr: (*C.git_object)(ptr),
repo: repo,
},
cast_ptr: ptr,
}
runtime.SetFinalizer(blob, (*Blob).Free)
return blob
}
func (o *Object) AsBlob() (*Blob, error) {
cobj, err := dupObject(o, ObjectBlob)
if err != nil {
return nil, err
}
return allocBlob((*C.git_blob)(cobj), o.repo), nil
}
func allocTag(ptr *C.git_tag, repo *Repository) *Tag {
tag := &Tag{
Object: Object{
ptr: (*C.git_object)(ptr),
repo: repo,
},
cast_ptr: ptr,
}
runtime.SetFinalizer(tag, (*Tag).Free)
return tag
}
func (o *Object) AsTag() (*Tag, error) {
cobj, err := dupObject(o, ObjectTag)
if err != nil {
return nil, err
}
return allocTag((*C.git_tag)(cobj), o.repo), nil
}
func (o *Object) Free() {
func (o *gitObject) Free() {
runtime.SetFinalizer(o, nil)
C.git_object_free(o.ptr)
C.git_commit_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, ErrorCodeInvalidSpec
// 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 ErrorCodeInvalidSpec.
//
// 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 *Object) Peel(t ObjectType) (*Object, error) {
var cobj *C.git_object
func allocObject(cobj *C.git_object) Object {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
switch ObjectType(C.git_object_type(cobj)) {
case ObjectCommit:
commit := &Commit{gitObject{cobj}}
runtime.SetFinalizer(commit, (*Commit).Free)
return commit
err := C.git_object_peel(&cobj, o.ptr, C.git_object_t(t))
runtime.KeepAlive(o)
if err < 0 {
return nil, MakeGitError(err)
case ObjectTree:
tree := &Tree{gitObject{cobj}}
runtime.SetFinalizer(tree, (*Tree).Free)
return tree
case ObjectBlob:
blob := &Blob{gitObject{cobj}}
runtime.SetFinalizer(blob, (*Blob).Free)
return blob
}
return allocObject(cobj, o.repo), nil
}
func allocObject(cobj *C.git_object, repo *Repository) *Object {
obj := &Object{
ptr: cobj,
repo: repo,
}
runtime.SetFinalizer(obj, (*Object).Free)
return obj
return nil
}

View File

@ -1,23 +1,21 @@
package git
import (
"strings"
"os"
"testing"
)
func TestObjectPoymorphism(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
defer os.RemoveAll(repo.Workdir())
commitId, treeId := seedTestRepo(t, repo)
var obj *Object
var obj Object
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
obj = &commit.Object
obj = commit
if obj.Type() != ObjectCommit {
t.Fatalf("Wrong object type, expected commit, have %v", obj.Type())
}
@ -29,13 +27,13 @@ func TestObjectPoymorphism(t *testing.T) {
tree, err := repo.LookupTree(treeId)
checkFatal(t, err)
obj = &tree.Object
obj = tree
if obj.Type() != ObjectTree {
t.Fatalf("Wrong object type, expected tree, have %v", obj.Type())
}
tree2, err := obj.AsTree()
if err != nil {
tree2, ok := obj.(*Tree)
if !ok {
t.Fatalf("Converting back to *Tree is not ok")
}
@ -48,16 +46,16 @@ func TestObjectPoymorphism(t *testing.T) {
t.Fatal("Wrong filemode for \"README\"")
}
_, err = obj.AsCommit()
if err == nil {
_, ok = obj.(*Commit)
if ok {
t.Fatalf("*Tree is somehow the same as *Commit")
}
obj, err = repo.Lookup(tree.Id())
checkFatal(t, err)
_, err = obj.AsTree()
if err != nil {
_, ok = obj.(*Tree)
if !ok {
t.Fatalf("Lookup creates the wrong type")
}
@ -77,118 +75,3 @@ func TestObjectPoymorphism(t *testing.T) {
t.Fatalf("Failed to parse the right revision")
}
}
func checkOwner(t *testing.T, repo *Repository, obj Object) {
owner := obj.Owner()
if owner == nil {
t.Fatal("bad owner")
}
if owner.ptr != repo.ptr {
t.Fatalf("bad owner, got %v expected %v\n", owner.ptr, repo.ptr)
}
}
func TestObjectOwner(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId, treeId := seedTestRepo(t, repo)
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
tree, err := repo.LookupTree(treeId)
checkFatal(t, err)
checkOwner(t, repo, commit.Object)
checkOwner(t, repo, tree.Object)
}
func checkShortId(t *testing.T, Id, shortId string) {
if len(shortId) < 7 || len(shortId) >= len(Id) {
t.Fatalf("bad shortId length %d", len(shortId))
}
if !strings.HasPrefix(Id, shortId) {
t.Fatalf("bad shortId, should be prefix of %s, but is %s\n", Id, shortId)
}
}
func TestObjectShortId(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId, _ := seedTestRepo(t, repo)
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
shortId, err := commit.ShortId()
checkFatal(t, err)
checkShortId(t, commitId.String(), shortId)
}
func TestObjectPeel(t *testing.T) {
t.Parallel()
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, ErrorCodeInvalidSpec) {
t.Fatalf("Wrong error when peeling a commit to a tag, expected ErrorCodeInvalidSpec, have %v", err)
}
tree, err := repo.LookupTree(treeID)
checkFatal(t, err)
obj, err = tree.Peel(ObjectAny)
if !IsErrorCode(err, ErrorCodeInvalidSpec) {
t.Fatalf("Wrong error when peeling a tree, expected ErrorCodeInvalidSpec, have %v", err)
}
entry := tree.EntryByName("README")
blob, err := repo.LookupBlob(entry.Id)
checkFatal(t, err)
obj, err = blob.Peel(ObjectAny)
if !IsErrorCode(err, ErrorCodeInvalidSpec) {
t.Fatalf("Wrong error when peeling a blob, expected ErrorCodeInvalidSpec, 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.
}

470
odb.go
View File

@ -1,347 +1,78 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
#include <git2/errors.h>
extern int git_odb_backend_one_pack(git_odb_backend **out, const char *index_file);
extern int git_odb_backend_loose(git_odb_backend **out, const char *objects_dir, int compression_level, int do_fsync, unsigned int dir_mode, unsigned int file_mode);
extern int _go_git_odb_foreach(git_odb *db, void *payload);
extern void _go_git_odb_backend_free(git_odb_backend *backend);
extern int _go_git_odb_write_pack(git_odb_writepack **out, git_odb *db, void *progress_payload);
extern int _go_git_odb_writepack_append(git_odb_writepack *writepack, const void *, size_t, git_transfer_progress *);
extern int _go_git_odb_writepack_commit(git_odb_writepack *writepack, git_transfer_progress *);
extern void _go_git_odb_writepack_free(git_odb_writepack *writepack);
*/
import "C"
import (
"io"
"os"
"unsafe"
"reflect"
"runtime"
"unsafe"
)
type Odb struct {
doNotCompare
ptr *C.git_odb
}
type OdbBackend struct {
doNotCompare
ptr *C.git_odb_backend
}
func NewOdb() (odb *Odb, err error) {
odb = new(Odb)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_new(&odb.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(odb, (*Odb).Free)
return odb, nil
}
func NewOdbBackendFromC(ptr unsafe.Pointer) (backend *OdbBackend) {
backend = &OdbBackend{ptr: (*C.git_odb_backend)(ptr)}
return backend
}
func (v *Odb) AddAlternate(backend *OdbBackend, priority int) (err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_add_alternate(v.ptr, backend.ptr, C.int(priority))
runtime.KeepAlive(v)
if ret < 0 {
backend.Free()
return MakeGitError(ret)
}
return nil
}
func (v *Odb) AddBackend(backend *OdbBackend, priority int) (err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_add_backend(v.ptr, backend.ptr, C.int(priority))
runtime.KeepAlive(v)
if ret < 0 {
backend.Free()
return MakeGitError(ret)
}
return nil
}
func NewOdbBackendOnePack(packfileIndexPath string) (backend *OdbBackend, err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cstr := C.CString(packfileIndexPath)
defer C.free(unsafe.Pointer(cstr))
var odbOnePack *C.git_odb_backend = nil
ret := C.git_odb_backend_one_pack(&odbOnePack, cstr)
if ret < 0 {
return nil, MakeGitError(ret)
}
return NewOdbBackendFromC(unsafe.Pointer(odbOnePack)), nil
}
// NewOdbBackendLoose creates a backend for loose objects.
func NewOdbBackendLoose(objectsDir string, compressionLevel int, doFsync bool, dirMode os.FileMode, fileMode os.FileMode) (backend *OdbBackend, err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var odbLoose *C.git_odb_backend = nil
var doFsyncInt C.int
if doFsync {
doFsyncInt = C.int(1)
}
cstr := C.CString(objectsDir)
defer C.free(unsafe.Pointer(cstr))
ret := C.git_odb_backend_loose(&odbLoose, cstr, C.int(compressionLevel), doFsyncInt, C.uint(dirMode), C.uint(fileMode))
if ret < 0 {
return nil, MakeGitError(ret)
}
return NewOdbBackendFromC(unsafe.Pointer(odbLoose)), nil
}
func (v *Odb) ReadHeader(oid *Oid) (uint64, ObjectType, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var sz C.size_t
var cotype C.git_object_t
ret := C.git_odb_read_header(&sz, &cotype, v.ptr, oid.toC())
runtime.KeepAlive(v)
if ret < 0 {
return 0, ObjectInvalid, MakeGitError(ret)
}
return uint64(sz), ObjectType(cotype), nil
}
func (v *Odb) Exists(oid *Oid) bool {
ret := C.git_odb_exists(v.ptr, oid.toC())
runtime.KeepAlive(v)
runtime.KeepAlive(oid)
return ret != 0
}
func (v *Odb) Write(data []byte, otype ObjectType) (oid *Oid, err error) {
oid = new(Oid)
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&data))
ret := C.git_odb_write(oid.toC(), v.ptr, unsafe.Pointer(hdr.Data), C.size_t(hdr.Len), C.git_otype(otype))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var size C.size_t
if len(data) > 0 {
size = C.size_t(len(data))
} else {
data = []byte{0}
size = C.size_t(0)
}
ret := C.git_odb_write(oid.toC(), v.ptr, unsafe.Pointer(&data[0]), size, C.git_object_t(otype))
runtime.KeepAlive(v)
if ret < 0 {
return nil, MakeGitError(ret)
err = LastError()
}
return oid, nil
return
}
func (v *Odb) Read(oid *Oid) (obj *OdbObject, err error) {
obj = new(OdbObject)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_read(&obj.ptr, v.ptr, oid.toC())
runtime.KeepAlive(v)
runtime.KeepAlive(oid)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
runtime.SetFinalizer(obj, (*OdbObject).Free)
return obj, nil
return
}
func (odb *Odb) Refresh() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_refresh(odb.ptr)
runtime.KeepAlive(odb)
if ret < 0 {
return MakeGitError(ret)
//export odbForEachCb
func odbForEachCb(id *C.git_oid, payload unsafe.Pointer) int {
ch := *(*chan *Oid)(payload)
oid := newOidFromC(id)
// Because the channel is unbuffered, we never read our own data. If ch is
// readable, the user has sent something on it, which means we should
// abort.
select {
case ch <- oid:
case <-ch:
return -1
}
return nil
return 0;
}
func (odb *Odb) WriteMultiPackIndex() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_write_multi_pack_index(odb.ptr)
runtime.KeepAlive(odb)
if ret < 0 {
return MakeGitError(ret)
}
return nil
func (v *Odb) forEachWrap(ch chan *Oid) {
C._go_git_odb_foreach(v.ptr, unsafe.Pointer(&ch))
close(ch)
}
type OdbForEachCallback func(id *Oid) error
type odbForEachCallbackData struct {
callback OdbForEachCallback
errorTarget *error
}
//export odbForEachCallback
func odbForEachCallback(id *C.git_oid, handle unsafe.Pointer) C.int {
data, ok := pointerHandles.Get(handle).(*odbForEachCallbackData)
if !ok {
panic("could not retrieve handle")
}
err := data.callback(newOidFromC(id))
if err != nil {
*data.errorTarget = err
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}
func (v *Odb) ForEach(callback OdbForEachCallback) error {
var err error
data := odbForEachCallbackData{
callback: callback,
errorTarget: &err,
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
handle := pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)
ret := C._go_git_odb_foreach(v.ptr, handle)
runtime.KeepAlive(v)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// Hash determines the object-ID (sha1) of a data buffer.
func (v *Odb) Hash(data []byte, otype ObjectType) (oid *Oid, err error) {
oid = new(Oid)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var size C.size_t
if len(data) > 0 {
size = C.size_t(len(data))
} else {
data = []byte{0}
size = C.size_t(0)
}
ret := C.git_odb_hash(oid.toC(), unsafe.Pointer(&data[0]), size, C.git_object_t(otype))
runtime.KeepAlive(data)
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
// NewReadStream opens a read stream from the ODB. Reading from it will give you the
// contents of the object.
func (v *Odb) NewReadStream(id *Oid) (*OdbReadStream, error) {
stream := new(OdbReadStream)
var ctype C.git_object_t
var csize C.size_t
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_open_rstream(&stream.ptr, &csize, &ctype, v.ptr, id.toC())
runtime.KeepAlive(v)
runtime.KeepAlive(id)
if ret < 0 {
return nil, MakeGitError(ret)
}
stream.Size = uint64(csize)
stream.Type = ObjectType(ctype)
runtime.SetFinalizer(stream, (*OdbReadStream).Free)
return stream, nil
}
// 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 int64, otype ObjectType) (*OdbWriteStream, error) {
stream := new(OdbWriteStream)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_open_wstream(&stream.ptr, v.ptr, C.git_object_size_t(size), C.git_object_t(otype))
runtime.KeepAlive(v)
if ret < 0 {
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(stream, (*OdbWriteStream).Free)
return stream, nil
}
// NewWritePack opens a stream for writing a pack file to the ODB. If the ODB
// layer understands pack files, then the given packfile will likely be
// streamed directly to disk (and a corresponding index created). If the ODB
// layer does not understand pack files, the objects will be stored in whatever
// format the ODB layer uses.
func (v *Odb) NewWritePack(callback TransferProgressCallback) (*OdbWritepack, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
writepack := new(OdbWritepack)
populateRemoteCallbacks(&writepack.ccallbacks, &RemoteCallbacks{TransferProgressCallback: callback}, nil)
ret := C._go_git_odb_write_pack(&writepack.ptr, v.ptr, writepack.ccallbacks.payload)
runtime.KeepAlive(v)
if ret < 0 {
untrackCallbacksPayload(&writepack.ccallbacks)
return nil, MakeGitError(ret)
}
runtime.SetFinalizer(writepack, (*OdbWritepack).Free)
return writepack, nil
}
func (v *OdbBackend) Free() {
C._go_git_odb_backend_free(v.ptr)
func (v *Odb) ForEach() chan *Oid {
ch := make(chan *Oid, 0)
go v.forEachWrap(ch)
return ch
}
type OdbObject struct {
doNotCompare
ptr *C.git_odb_object
}
@ -351,25 +82,13 @@ func (v *OdbObject) Free() {
}
func (object *OdbObject) Id() (oid *Oid) {
ret := newOidFromC(C.git_odb_object_id(object.ptr))
runtime.KeepAlive(object)
return ret
return newOidFromC(C.git_odb_object_id(object.ptr))
}
func (object *OdbObject) Len() (len uint64) {
ret := uint64(C.git_odb_object_size(object.ptr))
runtime.KeepAlive(object)
return ret
return uint64(C.git_odb_object_size(object.ptr))
}
func (object *OdbObject) Type() ObjectType {
ret := ObjectType(C.git_odb_object_type(object.ptr))
runtime.KeepAlive(object)
return ret
}
// Data returns a slice pointing to the unmanaged object memory. You must make
// sure the object is referenced for at least as long as the slice is used.
func (object *OdbObject) Data() (data []byte) {
var c_blob unsafe.Pointer = C.git_odb_object_data(object.ptr)
var blob []byte
@ -383,132 +102,3 @@ func (object *OdbObject) Data() (data []byte) {
return blob
}
type OdbReadStream struct {
doNotCompare
ptr *C.git_odb_stream
Size uint64
Type ObjectType
}
// Read reads from the stream
func (stream *OdbReadStream) Read(data []byte) (int, error) {
header := (*reflect.SliceHeader)(unsafe.Pointer(&data))
ptr := (*C.char)(unsafe.Pointer(header.Data))
size := C.size_t(header.Cap)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_stream_read(stream.ptr, ptr, size)
runtime.KeepAlive(stream)
if ret < 0 {
return 0, MakeGitError(ret)
}
if ret == 0 {
return 0, io.EOF
}
header.Len = int(ret)
return len(data), nil
}
// Close is a dummy function in order to implement the Closer and
// ReadCloser interfaces
func (stream *OdbReadStream) Close() error {
return nil
}
func (stream *OdbReadStream) Free() {
runtime.SetFinalizer(stream, nil)
C.git_odb_stream_free(stream.ptr)
}
type OdbWriteStream struct {
doNotCompare
ptr *C.git_odb_stream
Id Oid
}
// Write writes to the stream
func (stream *OdbWriteStream) Write(data []byte) (int, error) {
header := (*reflect.SliceHeader)(unsafe.Pointer(&data))
ptr := (*C.char)(unsafe.Pointer(header.Data))
size := C.size_t(header.Len)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_stream_write(stream.ptr, ptr, size)
runtime.KeepAlive(stream)
if ret < 0 {
return 0, MakeGitError(ret)
}
return len(data), nil
}
// Close signals that all the data has been written and stores the
// resulting object id in the stream's Id field.
func (stream *OdbWriteStream) Close() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_odb_stream_finalize_write(stream.Id.toC(), stream.ptr)
runtime.KeepAlive(stream)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (stream *OdbWriteStream) Free() {
runtime.SetFinalizer(stream, nil)
C.git_odb_stream_free(stream.ptr)
}
// OdbWritepack is a stream to write a packfile to the ODB.
type OdbWritepack struct {
doNotCompare
ptr *C.git_odb_writepack
stats C.git_transfer_progress
ccallbacks C.git_remote_callbacks
}
func (writepack *OdbWritepack) Write(data []byte) (int, error) {
header := (*reflect.SliceHeader)(unsafe.Pointer(&data))
ptr := unsafe.Pointer(header.Data)
size := C.size_t(header.Len)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C._go_git_odb_writepack_append(writepack.ptr, ptr, size, &writepack.stats)
runtime.KeepAlive(writepack)
if ret < 0 {
return 0, MakeGitError(ret)
}
return len(data), nil
}
func (writepack *OdbWritepack) Commit() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C._go_git_odb_writepack_commit(writepack.ptr, &writepack.stats)
runtime.KeepAlive(writepack)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (writepack *OdbWritepack) Free() {
untrackCallbacksPayload(&writepack.ccallbacks)
runtime.SetFinalizer(writepack, nil)
C._go_git_odb_writepack_free(writepack.ptr)
}

View File

@ -1,235 +0,0 @@
package git
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"testing"
)
func TestOdbRead(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, _ = seedTestRepo(t, repo)
odb, err := repo.Odb()
if err != nil {
t.Fatalf("Odb: %v", err)
}
data := []byte("hello")
id, err := odb.Write(data, ObjectBlob)
if err != nil {
t.Fatalf("odb.Write: %v", err)
}
sz, typ, err := odb.ReadHeader(id)
if err != nil {
t.Fatalf("ReadHeader: %v", err)
}
if sz != uint64(len(data)) {
t.Errorf("ReadHeader got size %d, want %d", sz, len(data))
}
if typ != ObjectBlob {
t.Errorf("ReadHeader got object type %s", typ)
}
obj, err := odb.Read(id)
if err != nil {
t.Fatalf("Read: %v", err)
}
if !bytes.Equal(obj.Data(), data) {
t.Errorf("Read got wrong data")
}
if sz := obj.Len(); sz != uint64(len(data)) {
t.Errorf("Read got size %d, want %d", sz, len(data))
}
if typ := obj.Type(); typ != ObjectBlob {
t.Errorf("Read got object type %s", typ)
}
}
func TestOdbStream(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, _ = seedTestRepo(t, repo)
odb, err := repo.Odb()
checkFatal(t, err)
str := "hello, world!"
writeStream, err := odb.NewWriteStream(int64(len(str)), ObjectBlob)
checkFatal(t, err)
n, err := io.WriteString(writeStream, str)
checkFatal(t, err)
if n != len(str) {
t.Fatalf("Bad write length %v != %v", n, len(str))
}
err = writeStream.Close()
checkFatal(t, err)
expectedId, err := NewOid("30f51a3fba5274d53522d0f19748456974647b4f")
checkFatal(t, err)
if writeStream.Id.Cmp(expectedId) != 0 {
t.Fatal("Wrong data written")
}
readStream, err := odb.NewReadStream(&writeStream.Id)
checkFatal(t, err)
data, err := ioutil.ReadAll(readStream)
if str != string(data) {
t.Fatalf("Wrong data read %v != %v", str, string(data))
}
}
func TestOdbHash(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, _ = seedTestRepo(t, repo)
odb, err := repo.Odb()
checkFatal(t, err)
str := `tree 115fcae49287c82eb55bb275cbbd4556fbed72b7
parent 66e1c476199ebcd3e304659992233132c5a52c6c
author John Doe <john@doe.com> 1390682018 +0000
committer John Doe <john@doe.com> 1390682018 +0000
Initial commit.`
for _, data := range [][]byte{[]byte(str), doublePointerBytes()} {
oid, err := odb.Hash(data, ObjectCommit)
checkFatal(t, err)
coid, err := odb.Write(data, ObjectCommit)
checkFatal(t, err)
if oid.Cmp(coid) != 0 {
t.Fatal("Hash and write Oids are different")
}
}
}
func TestOdbForeach(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, _ = seedTestRepo(t, repo)
odb, err := repo.Odb()
checkFatal(t, err)
expect := 3
count := 0
err = odb.ForEach(func(id *Oid) error {
count++
return nil
})
checkFatal(t, err)
if count != expect {
t.Fatalf("Expected %v objects, got %v", expect, count)
}
expect = 1
count = 0
to_return := errors.New("not really an error")
err = odb.ForEach(func(id *Oid) error {
count++
return to_return
})
if err != to_return {
t.Fatalf("Odb.ForEach() did not return the expected error, got %v", err)
}
}
func TestOdbWritepack(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, _ = seedTestRepo(t, repo)
odb, err := repo.Odb()
checkFatal(t, err)
var finalStats TransferProgress
writepack, err := odb.NewWritePack(func(stats TransferProgress) error {
finalStats = stats
return nil
})
checkFatal(t, err)
defer writepack.Free()
_, err = writepack.Write(outOfOrderPack)
checkFatal(t, err)
err = writepack.Commit()
checkFatal(t, err)
if finalStats.TotalObjects != 3 {
t.Errorf("mismatched transferred objects, expected 3, got %v", finalStats.TotalObjects)
}
if finalStats.ReceivedObjects != 3 {
t.Errorf("mismatched received objects, expected 3, got %v", finalStats.ReceivedObjects)
}
if finalStats.IndexedObjects != 3 {
t.Errorf("mismatched indexed objects, expected 3, got %v", finalStats.IndexedObjects)
}
}
func TestOdbBackendLoose(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, _ = seedTestRepo(t, repo)
odb, err := repo.Odb()
checkFatal(t, err)
looseObjectsDir, err := ioutil.TempDir("", fmt.Sprintf("loose_objects_%s", path.Base(repo.Path())))
checkFatal(t, err)
defer os.RemoveAll(looseObjectsDir)
looseObjectsBackend, err := NewOdbBackendLoose(looseObjectsDir, -1, false, 0, 0)
checkFatal(t, err)
if err := odb.AddBackend(looseObjectsBackend, 999); err != nil {
looseObjectsBackend.Free()
checkFatal(t, err)
}
str := "hello, world!"
writeStream, err := odb.NewWriteStream(int64(len(str)), ObjectBlob)
checkFatal(t, err)
n, err := io.WriteString(writeStream, str)
checkFatal(t, err)
if n != len(str) {
t.Fatalf("Bad write length %v != %v", n, len(str))
}
err = writeStream.Close()
checkFatal(t, err)
expectedId, err := NewOid("30f51a3fba5274d53522d0f19748456974647b4f")
checkFatal(t, err)
if !writeStream.Id.Equal(expectedId) {
t.Fatalf("writeStream.id = %v; want %v", writeStream.Id, expectedId)
}
_, err = os.Stat(path.Join(looseObjectsDir, expectedId.String()[:2], expectedId.String()[2:]))
checkFatal(t, err)
}

View File

@ -1,7 +1,9 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
#include <git2/errors.h>
#include <git2/pack.h>
#include <stdlib.h>
@ -10,33 +12,22 @@ extern int _go_git_packbuilder_foreach(git_packbuilder *pb, void *payload);
import "C"
import (
"io"
"os"
"runtime"
"unsafe"
)
type Packbuilder struct {
doNotCompare
ptr *C.git_packbuilder
r *Repository
}
func (repo *Repository) NewPackbuilder() (*Packbuilder, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_packbuilder
ret := C.git_packbuilder_new(&ptr, repo.ptr)
builder := &Packbuilder{}
ret := C.git_packbuilder_new(&builder.ptr, repo.ptr)
if ret != 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newPackbuilderFromC(ptr, repo), nil
}
func newPackbuilderFromC(ptr *C.git_packbuilder, r *Repository) *Packbuilder {
pb := &Packbuilder{ptr: ptr, r: r}
runtime.SetFinalizer(pb, (*Packbuilder).Free)
return pb
runtime.SetFinalizer(builder, (*Packbuilder).Free)
return builder, nil
}
func (pb *Packbuilder) Free() {
@ -47,139 +38,93 @@ func (pb *Packbuilder) Free() {
func (pb *Packbuilder) Insert(id *Oid, name string) error {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_packbuilder_insert(pb.ptr, id.toC(), cname)
runtime.KeepAlive(pb)
runtime.KeepAlive(id)
if ret != 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (pb *Packbuilder) InsertCommit(id *Oid) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_packbuilder_insert_commit(pb.ptr, id.toC())
runtime.KeepAlive(pb)
runtime.KeepAlive(id)
if ret != 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (pb *Packbuilder) InsertTree(id *Oid) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_packbuilder_insert_tree(pb.ptr, id.toC())
runtime.KeepAlive(pb)
runtime.KeepAlive(id)
if ret != 0 {
return MakeGitError(ret)
}
return nil
}
func (pb *Packbuilder) InsertWalk(walk *RevWalk) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_packbuilder_insert_walk(pb.ptr, walk.ptr)
runtime.KeepAlive(pb)
runtime.KeepAlive(walk)
if ret != 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (pb *Packbuilder) ObjectCount() uint32 {
ret := uint32(C.git_packbuilder_object_count(pb.ptr))
runtime.KeepAlive(pb)
return ret
return uint32(C.git_packbuilder_object_count(pb.ptr))
}
func (pb *Packbuilder) WriteToFile(name string, mode os.FileMode) error {
func (pb *Packbuilder) WriteToFile(name string) error {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_packbuilder_write(pb.ptr, cname, C.uint(mode.Perm()), nil, nil)
runtime.KeepAlive(pb)
ret := C.git_packbuilder_write(pb.ptr, cname, nil, nil)
if ret != 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (pb *Packbuilder) Write(w io.Writer) error {
return pb.ForEach(func(slice []byte) error {
ch, stop := pb.ForEach()
for slice := range ch {
_, err := w.Write(slice)
return err
})
if err != nil {
close(stop)
return err
}
}
return nil
}
func (pb *Packbuilder) Written() uint32 {
ret := uint32(C.git_packbuilder_written(pb.ptr))
runtime.KeepAlive(pb)
return ret
return uint32(C.git_packbuilder_written(pb.ptr))
}
type PackbuilderForeachCallback func([]byte) error
type packbuilderCallbackData struct {
callback PackbuilderForeachCallback
errorTarget *error
type packbuilderCbData struct {
ch chan<- []byte
stop <-chan bool
}
//export packbuilderForEachCallback
func packbuilderForEachCallback(buf unsafe.Pointer, size C.size_t, handle unsafe.Pointer) C.int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*packbuilderCallbackData)
if !ok {
panic("could not get packbuilder CB data")
}
//export packbuilderForEachCb
func packbuilderForEachCb(buf unsafe.Pointer, size C.size_t, payload unsafe.Pointer) int {
data := (*packbuilderCbData)(payload)
ch := data.ch
stop := data.stop
slice := C.GoBytes(buf, C.int(size))
err := data.callback(slice)
if err != nil {
*data.errorTarget = err
return C.int(ErrorCodeUser)
select {
case <- stop:
return -1
case ch <- slice:
}
return C.int(ErrorCodeOK)
return 0
}
// ForEach repeatedly calls the callback with new packfile data until
// there is no more data or the callback returns an error
func (pb *Packbuilder) ForEach(callback PackbuilderForeachCallback) error {
var err error
data := packbuilderCallbackData{
callback: callback,
errorTarget: &err,
}
handle := pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C._go_git_packbuilder_foreach(pb.ptr, handle)
runtime.KeepAlive(pb)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
func (pb *Packbuilder) forEachWrap(data *packbuilderCbData) {
C._go_git_packbuilder_foreach(pb.ptr, unsafe.Pointer(data))
close(data.ch)
}
// Foreach sends the packfile as slices through the "data" channel. If
// you want to stop the pack-building process (e.g. there's an error
// writing to the output), close or write a value into the "stop"
// channel.
func (pb *Packbuilder) ForEach() (<-chan []byte, chan<- bool) {
ch := make(chan []byte)
stop := make(chan bool)
data := packbuilderCbData{ch, stop}
go pb.forEachWrap(&data)
return ch, stop
}

View File

@ -1,99 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
"unsafe"
)
type Patch struct {
doNotCompare
ptr *C.git_patch
}
func newPatchFromC(ptr *C.git_patch) *Patch {
if ptr == nil {
return nil
}
patch := &Patch{
ptr: ptr,
}
runtime.SetFinalizer(patch, (*Patch).Free)
return patch
}
func (patch *Patch) Free() error {
if patch.ptr == nil {
return ErrInvalid
}
runtime.SetFinalizer(patch, nil)
C.git_patch_free(patch.ptr)
patch.ptr = nil
return nil
}
func (patch *Patch) String() (string, error) {
if patch.ptr == nil {
return "", ErrInvalid
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var buf C.git_buf
ecode := C.git_patch_to_buf(&buf, patch.ptr)
runtime.KeepAlive(patch)
if ecode < 0 {
return "", MakeGitError(ecode)
}
defer C.git_buf_dispose(&buf)
return C.GoString(buf.ptr), nil
}
func toPointer(data []byte) (ptr unsafe.Pointer) {
if len(data) > 0 {
ptr = unsafe.Pointer(&data[0])
} else {
ptr = unsafe.Pointer(nil)
}
return
}
func (v *Repository) PatchFromBuffers(oldPath, newPath string, oldBuf, newBuf []byte, opts *DiffOptions) (*Patch, error) {
var patchPtr *C.git_patch
oldPtr := toPointer(oldBuf)
newPtr := toPointer(newBuf)
cOldPath := C.CString(oldPath)
defer C.free(unsafe.Pointer(cOldPath))
cNewPath := C.CString(newPath)
defer C.free(unsafe.Pointer(cNewPath))
var err error
copts := populateDiffOptions(&C.git_diff_options{}, opts, v, &err)
defer freeDiffOptions(copts)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_patch_from_buffers(&patchPtr, oldPtr, C.size_t(len(oldBuf)), cOldPath, newPtr, C.size_t(len(newBuf)), cNewPath, copts)
runtime.KeepAlive(oldBuf)
runtime.KeepAlive(newBuf)
if ret == C.int(ErrorCodeUser) && err != nil {
return nil, err
}
if ret < 0 {
return nil, MakeGitError(ret)
}
return newPatchFromC(patchPtr), nil
}

View File

@ -1,38 +0,0 @@
package git
import (
"strings"
"testing"
)
func TestPatch(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, originalTreeId := seedTestRepo(t, repo)
originalTree, err := repo.LookupTree(originalTreeId)
checkFatal(t, err)
_, newTreeId := updateReadme(t, repo, "file changed\n")
newTree, err := repo.LookupTree(newTreeId)
checkFatal(t, err)
opts := &DiffOptions{
OldPrefix: "a",
NewPrefix: "b",
}
diff, err := repo.DiffTreeToTree(originalTree, newTree, opts)
checkFatal(t, err)
patch, err := diff.Patch(0)
checkFatal(t, err)
patchStr, err := patch.String()
checkFatal(t, err)
if strings.Index(patchStr, "diff --git a/README b/README\nindex 257cc56..820734a 100644\n--- a/README\n+++ b/README\n@@ -1 +1 @@\n-foo\n+file changed") == -1 {
t.Fatalf("patch was bad")
}
}

View File

@ -1,31 +0,0 @@
package git
import (
"testing"
)
func TestRemotePush(t *testing.T) {
t.Parallel()
repo := createBareTestRepo(t)
defer cleanupTestRepo(t, repo)
localRepo := createTestRepo(t)
defer cleanupTestRepo(t, localRepo)
remote, err := localRepo.Remotes.Create("test_push", repo.Path())
checkFatal(t, err)
defer remote.Free()
seedTestRepo(t, localRepo)
err = remote.Push([]string{"refs/heads/master"}, nil)
checkFatal(t, err)
ref, err := localRepo.References.Lookup("refs/remotes/test_push/master")
checkFatal(t, err)
defer ref.Free()
ref, err = repo.References.Lookup("refs/heads/master")
checkFatal(t, err)
defer ref.Free()
}

485
rebase.go
View File

@ -1,485 +0,0 @@
package git
/*
#include <git2.h>
extern void _go_git_populate_rebase_callbacks(git_rebase_options *opts);
*/
import "C"
import (
"errors"
"fmt"
"reflect"
"runtime"
"unsafe"
)
// RebaseOperationType is the type of rebase operation
type RebaseOperationType uint
const (
// RebaseOperationPick The given commit is to be cherry-picked. The client should commit the changes and continue if there are no conflicts.
RebaseOperationPick RebaseOperationType = C.GIT_REBASE_OPERATION_PICK
// RebaseOperationReword The given commit is to be cherry-picked, but the client should prompt the user to provide an updated commit message.
RebaseOperationReword RebaseOperationType = C.GIT_REBASE_OPERATION_REWORD
// RebaseOperationEdit The given commit is to be cherry-picked, but the client should stop to allow the user to edit the changes before committing them.
RebaseOperationEdit RebaseOperationType = C.GIT_REBASE_OPERATION_EDIT
// RebaseOperationSquash The given commit is to be squashed into the previous commit. The commit message will be merged with the previous message.
RebaseOperationSquash RebaseOperationType = C.GIT_REBASE_OPERATION_SQUASH
// RebaseOperationFixup No commit will be cherry-picked. The client should run the given command and (if successful) continue.
RebaseOperationFixup RebaseOperationType = C.GIT_REBASE_OPERATION_FIXUP
// RebaseOperationExec No commit will be cherry-picked. The client should run the given command and (if successful) continue.
RebaseOperationExec RebaseOperationType = C.GIT_REBASE_OPERATION_EXEC
)
func (t RebaseOperationType) String() string {
switch t {
case RebaseOperationPick:
return "pick"
case RebaseOperationReword:
return "reword"
case RebaseOperationEdit:
return "edit"
case RebaseOperationSquash:
return "squash"
case RebaseOperationFixup:
return "fixup"
case RebaseOperationExec:
return "exec"
}
return fmt.Sprintf("RebaseOperationType(%d)", t)
}
// Special value indicating that there is no currently active operation
var RebaseNoOperation uint = ^uint(0)
// Error returned if there is no current rebase operation
var ErrRebaseNoOperation = errors.New("no current rebase operation")
// RebaseOperation describes a single instruction/operation to be performed during the rebase.
type RebaseOperation struct {
Type RebaseOperationType
Id *Oid
Exec string
}
func newRebaseOperationFromC(c *C.git_rebase_operation) *RebaseOperation {
operation := &RebaseOperation{}
operation.Type = RebaseOperationType(c._type)
operation.Id = newOidFromC(&c.id)
operation.Exec = C.GoString(c.exec)
return operation
}
//export commitCreateCallback
func commitCreateCallback(
errorMessage **C.char,
_out *C.git_oid,
_author, _committer *C.git_signature,
_message_encoding, _message *C.char,
_tree *C.git_tree,
_parent_count C.size_t,
_parents **C.git_commit,
handle unsafe.Pointer,
) C.int {
data, ok := pointerHandles.Get(handle).(*rebaseOptionsData)
if !ok {
panic("invalid sign payload")
}
if data.options.CommitCreateCallback == nil && data.options.CommitSigningCallback == nil {
return C.int(ErrorCodePassthrough)
}
messageEncoding := MessageEncodingUTF8
if _message_encoding != nil {
messageEncoding = MessageEncoding(C.GoString(_message_encoding))
}
tree := &Tree{
Object: Object{
ptr: (*C.git_object)(_tree),
repo: data.repo,
},
cast_ptr: _tree,
}
var goParents []*C.git_commit
if _parent_count > 0 {
hdr := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(_parents)),
Len: int(_parent_count),
Cap: int(_parent_count),
}
goParents = *(*[]*C.git_commit)(unsafe.Pointer(&hdr))
}
parents := make([]*Commit, int(_parent_count))
for i, p := range goParents {
parents[i] = &Commit{
Object: Object{
ptr: (*C.git_object)(p),
repo: data.repo,
},
cast_ptr: p,
}
}
if data.options.CommitCreateCallback != nil {
oid, err := data.options.CommitCreateCallback(
newSignatureFromC(_author),
newSignatureFromC(_committer),
messageEncoding,
C.GoString(_message),
tree,
parents...,
)
if err != nil {
if data.errorTarget != nil {
*data.errorTarget = err
}
return setCallbackError(errorMessage, err)
}
if oid == nil {
return C.int(ErrorCodePassthrough)
}
*_out = *oid.toC()
} else if data.options.CommitSigningCallback != nil {
commitContent, err := data.repo.CreateCommitBuffer(
newSignatureFromC(_author),
newSignatureFromC(_committer),
messageEncoding,
C.GoString(_message),
tree,
parents...,
)
if err != nil {
if data.errorTarget != nil {
*data.errorTarget = err
}
return setCallbackError(errorMessage, err)
}
signature, signatureField, err := data.options.CommitSigningCallback(string(commitContent))
if err != nil {
if data.errorTarget != nil {
*data.errorTarget = err
}
return setCallbackError(errorMessage, err)
}
oid, err := data.repo.CreateCommitWithSignature(string(commitContent), signature, signatureField)
if err != nil {
if data.errorTarget != nil {
*data.errorTarget = err
}
return setCallbackError(errorMessage, err)
}
*_out = *oid.toC()
}
return C.int(ErrorCodeOK)
}
// RebaseOptions are used to tell the rebase machinery how to operate.
type RebaseOptions struct {
Quiet int
InMemory int
RewriteNotesRef string
MergeOptions MergeOptions
CheckoutOptions CheckoutOptions
// CommitCreateCallback is an optional callback that allows users to override
// commit creation when rebasing. If specified, users can create
// their own commit and provide the commit ID, which may be useful for
// signing commits or otherwise customizing the commit creation. If this
// callback returns a nil Oid, then the rebase will continue to create the
// commit.
CommitCreateCallback CommitCreateCallback
// Deprecated: CommitSigningCallback is an optional callback that will be
// called with the commit content, allowing a signature to be added to the
// rebase commit. This field is only used when rebasing. This callback is
// not invoked if a CommitCreateCallback is specified. CommitCreateCallback
// should be used instead of this.
CommitSigningCallback CommitSigningCallback
}
type rebaseOptionsData struct {
options *RebaseOptions
repo *Repository
errorTarget *error
}
// DefaultRebaseOptions returns a RebaseOptions with default values.
func DefaultRebaseOptions() (RebaseOptions, error) {
opts := C.git_rebase_options{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_rebase_options_init(&opts, C.GIT_REBASE_OPTIONS_VERSION)
if ecode < 0 {
return RebaseOptions{}, MakeGitError(ecode)
}
return rebaseOptionsFromC(&opts), nil
}
func rebaseOptionsFromC(opts *C.git_rebase_options) RebaseOptions {
return RebaseOptions{
Quiet: int(opts.quiet),
InMemory: int(opts.inmemory),
RewriteNotesRef: C.GoString(opts.rewrite_notes_ref),
MergeOptions: mergeOptionsFromC(&opts.merge_options),
CheckoutOptions: checkoutOptionsFromC(&opts.checkout_options),
}
}
func populateRebaseOptions(copts *C.git_rebase_options, opts *RebaseOptions, repo *Repository, errorTarget *error) *C.git_rebase_options {
C.git_rebase_options_init(copts, C.GIT_REBASE_OPTIONS_VERSION)
if opts == nil {
return nil
}
copts.quiet = C.int(opts.Quiet)
copts.inmemory = C.int(opts.InMemory)
copts.rewrite_notes_ref = mapEmptyStringToNull(opts.RewriteNotesRef)
populateMergeOptions(&copts.merge_options, &opts.MergeOptions)
populateCheckoutOptions(&copts.checkout_options, &opts.CheckoutOptions, errorTarget)
if opts.CommitCreateCallback != nil || opts.CommitSigningCallback != nil {
data := &rebaseOptionsData{
options: opts,
repo: repo,
errorTarget: errorTarget,
}
C._go_git_populate_rebase_callbacks(copts)
copts.payload = pointerHandles.Track(data)
}
return copts
}
func freeRebaseOptions(copts *C.git_rebase_options) {
if copts == nil {
return
}
C.free(unsafe.Pointer(copts.rewrite_notes_ref))
freeMergeOptions(&copts.merge_options)
freeCheckoutOptions(&copts.checkout_options)
if copts.payload != nil {
pointerHandles.Untrack(copts.payload)
}
}
func mapEmptyStringToNull(ref string) *C.char {
if ref == "" {
return nil
}
return C.CString(ref)
}
// Rebase is the struct representing a Rebase object.
type Rebase struct {
doNotCompare
ptr *C.git_rebase
r *Repository
options *C.git_rebase_options
}
// InitRebase initializes a rebase operation to rebase the changes in branch relative to upstream onto another branch.
func (r *Repository) InitRebase(branch *AnnotatedCommit, upstream *AnnotatedCommit, onto *AnnotatedCommit, opts *RebaseOptions) (*Rebase, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if branch == nil {
branch = &AnnotatedCommit{ptr: nil}
}
if upstream == nil {
upstream = &AnnotatedCommit{ptr: nil}
}
if onto == nil {
onto = &AnnotatedCommit{ptr: nil}
}
var ptr *C.git_rebase
var err error
cOpts := populateRebaseOptions(&C.git_rebase_options{}, opts, r, &err)
ret := C.git_rebase_init(&ptr, r.ptr, branch.ptr, upstream.ptr, onto.ptr, cOpts)
runtime.KeepAlive(branch)
runtime.KeepAlive(upstream)
runtime.KeepAlive(onto)
if ret == C.int(ErrorCodeUser) && err != nil {
freeRebaseOptions(cOpts)
return nil, err
}
if ret < 0 {
freeRebaseOptions(cOpts)
return nil, MakeGitError(ret)
}
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.
func (r *Repository) OpenRebase(opts *RebaseOptions) (*Rebase, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_rebase
var err error
cOpts := populateRebaseOptions(&C.git_rebase_options{}, opts, r, &err)
ret := C.git_rebase_open(&ptr, r.ptr, cOpts)
runtime.KeepAlive(r)
if ret == C.int(ErrorCodeUser) && err != nil {
freeRebaseOptions(cOpts)
return nil, err
}
if ret < 0 {
freeRebaseOptions(cOpts)
return nil, MakeGitError(ret)
}
return newRebaseFromC(ptr, r, cOpts), nil
}
// OperationAt gets the rebase operation specified by the given index.
func (rebase *Rebase) OperationAt(index uint) *RebaseOperation {
operation := C.git_rebase_operation_byindex(rebase.ptr, C.size_t(index))
return newRebaseOperationFromC(operation)
}
// CurrentOperationIndex gets the index of the rebase operation that is
// currently being applied. There is also an error returned for API
// compatibility.
func (rebase *Rebase) CurrentOperationIndex() (uint, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var err error
operationIndex := uint(C.git_rebase_operation_current(rebase.ptr))
runtime.KeepAlive(rebase)
if operationIndex == RebaseNoOperation {
err = ErrRebaseNoOperation
}
return uint(operationIndex), err
}
// OperationCount gets the count of rebase operations that are to be applied.
func (rebase *Rebase) OperationCount() uint {
ret := uint(C.git_rebase_operation_entrycount(rebase.ptr))
runtime.KeepAlive(rebase)
return ret
}
// Next performs the next rebase operation and returns the information about it.
// If the operation is one that applies a patch (which is any operation except RebaseOperationExec)
// then the patch will be applied and the index and working directory will be updated with the changes.
// If there are conflicts, you will need to address those before committing the changes.
func (rebase *Rebase) Next() (*RebaseOperation, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_rebase_operation
err := C.git_rebase_next(&ptr, rebase.ptr)
runtime.KeepAlive(rebase)
if err < 0 {
return nil, MakeGitError(err)
}
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.
// 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 {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
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)
cmsg := C.CString(message)
defer C.free(unsafe.Pointer(cmsg))
cerr := C.git_rebase_commit(ID.toC(), rebase.ptr, authorSig, committerSig, nil, cmsg)
runtime.KeepAlive(ID)
runtime.KeepAlive(rebase)
if cerr < 0 {
return MakeGitError(cerr)
}
return nil
}
// Finish finishes a rebase that is currently in progress once all patches have been applied.
func (rebase *Rebase) Finish() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C.git_rebase_finish(rebase.ptr, nil)
runtime.KeepAlive(rebase)
if err < 0 {
return MakeGitError(err)
}
return nil
}
// Abort aborts a rebase that is currently in progress, resetting the repository and working directory to their state before rebase began.
func (rebase *Rebase) Abort() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C.git_rebase_abort(rebase.ptr)
runtime.KeepAlive(rebase)
if err < 0 {
return MakeGitError(err)
}
return nil
}
// Free frees the Rebase object.
func (r *Rebase) Free() {
runtime.SetFinalizer(r, nil)
C.git_rebase_free(r.ptr)
freeRebaseOptions(r.options)
}
func newRebaseFromC(ptr *C.git_rebase, repo *Repository, opts *C.git_rebase_options) *Rebase {
rebase := &Rebase{ptr: ptr, r: repo, options: opts}
runtime.SetFinalizer(rebase, (*Rebase).Free)
return rebase
}

View File

@ -1,602 +0,0 @@
package git
import (
"bytes"
"errors"
"strconv"
"strings"
"testing"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
)
// 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) {
// TEST DATA
// Inputs
branchName := "emile"
masterCommit := "something"
emileCommits := []string{
"fou",
"barre",
}
// Outputs
expectedHistory := []string{
"Test rebase, Baby! " + emileCommits[1],
"Test rebase, Baby! " + emileCommits[0],
"This is a commit\n",
}
// TEST
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
// Setup a repo with 2 branches and a different tree
err := setupRepoForRebase(repo, masterCommit, branchName, commitOptions{})
checkFatal(t, err)
// Create several commits in emile
for _, commit := range emileCommits {
_, err = commitSomething(repo, commit, commit, commitOptions{})
checkFatal(t, err)
}
// Check history
actualHistory, err := commitMsgsList(repo)
checkFatal(t, err)
assertStringList(t, expectedHistory, actualHistory)
// Rebase onto master
rebase, err := performRebaseOnto(repo, "master", nil)
checkFatal(t, err)
defer rebase.Free()
// Abort rebase
rebase.Abort()
// Check history is still the same
actualHistory, err = commitMsgsList(repo)
checkFatal(t, err)
assertStringList(t, expectedHistory, actualHistory)
}
func TestRebaseNoConflicts(t *testing.T) {
// TEST DATA
// Inputs
branchName := "emile"
masterCommit := "something"
emileCommits := []string{
"fou",
"barre",
"ouich",
}
// Outputs
expectedHistory := []string{
"Test rebase, Baby! " + emileCommits[2],
"Test rebase, Baby! " + emileCommits[1],
"Test rebase, Baby! " + emileCommits[0],
"Test rebase, Baby! " + masterCommit,
"This is a commit\n",
}
// TEST
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
// Try to open existing rebase
oRebase, err := repo.OpenRebase(nil)
if err == nil {
t.Fatal("Did not expect to find a rebase in progress")
}
// Setup a repo with 2 branches and a different tree
err = setupRepoForRebase(repo, masterCommit, branchName, commitOptions{})
checkFatal(t, err)
// Create several commits in emile
for _, commit := range emileCommits {
_, err = commitSomething(repo, commit, commit, commitOptions{})
checkFatal(t, err)
}
// Rebase onto master
rebase, err := performRebaseOnto(repo, "master", nil)
checkFatal(t, err)
defer rebase.Free()
// Open existing rebase
oRebase, err = repo.OpenRebase(nil)
checkFatal(t, err)
defer oRebase.Free()
if oRebase == nil {
t.Fatal("Expected to find an existing rebase in progress")
}
// Finish the rebase properly
err = rebase.Finish()
checkFatal(t, err)
// Check no more rebase is in progress
oRebase, err = repo.OpenRebase(nil)
if err == nil {
t.Fatal("Did not expect to find a rebase in progress")
}
// Check history is in correct order
actualHistory, err := commitMsgsList(repo)
checkFatal(t, err)
assertStringList(t, expectedHistory, actualHistory)
}
func TestRebaseGpgSigned(t *testing.T) {
// TEST DATA
entity, err := openpgp.NewEntity("Namey mcnameface", "test comment", "test@example.com", nil)
checkFatal(t, err)
rebaseOpts, err := DefaultRebaseOptions()
checkFatal(t, err)
signCommitContent := func(commitContent string) (string, string, error) {
cipherText := new(bytes.Buffer)
err := openpgp.ArmoredDetachSignText(cipherText, entity, strings.NewReader(commitContent), &packet.Config{})
if err != nil {
return "", "", errors.New("error signing payload")
}
return cipherText.String(), "", nil
}
rebaseOpts.CommitSigningCallback = signCommitContent
commitOpts := commitOptions{
CommitSigningCallback: signCommitContent,
}
// Inputs
branchName := "emile"
masterCommit := "something"
emileCommits := []string{
"fou",
"barre",
"ouich",
}
// Outputs
expectedHistory := []string{
"Test rebase, Baby! " + emileCommits[2],
"Test rebase, Baby! " + emileCommits[1],
"Test rebase, Baby! " + emileCommits[0],
"Test rebase, Baby! " + masterCommit,
"This is a commit\n",
}
// TEST
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepoOpt(t, repo, commitOpts)
// Try to open existing rebase
_, err = repo.OpenRebase(nil)
if err == nil {
t.Fatal("Did not expect to find a rebase in progress")
}
// Setup a repo with 2 branches and a different tree
err = setupRepoForRebase(repo, masterCommit, branchName, commitOpts)
checkFatal(t, err)
// Create several commits in emile
for _, commit := range emileCommits {
_, err = commitSomething(repo, commit, commit, commitOpts)
checkFatal(t, err)
}
// Rebase onto master
rebase, err := performRebaseOnto(repo, "master", &rebaseOpts)
checkFatal(t, err)
defer rebase.Free()
// Finish the rebase properly
err = rebase.Finish()
checkFatal(t, err)
// Check history is in correct order
actualHistory, err := commitMsgsList(repo)
checkFatal(t, err)
assertStringList(t, expectedHistory, actualHistory)
checkAllCommitsSigned(t, entity, repo)
}
func checkAllCommitsSigned(t *testing.T, entity *openpgp.Entity, repo *Repository) {
head, err := headCommit(repo)
checkFatal(t, err)
defer head.Free()
parent := head
err = checkCommitSigned(t, entity, parent)
checkFatal(t, err)
for parent.ParentCount() != 0 {
parent = parent.Parent(0)
defer parent.Free()
err = checkCommitSigned(t, entity, parent)
checkFatal(t, err)
}
}
func checkCommitSigned(t *testing.T, entity *openpgp.Entity, commit *Commit) error {
t.Helper()
signature, signedData, err := commit.ExtractSignature()
if err != nil {
t.Logf("No signature on commit\n%s", commit.ContentToSign())
return err
}
_, err = openpgp.CheckArmoredDetachedSignature(openpgp.EntityList{entity}, strings.NewReader(signedData), bytes.NewBufferString(signature), nil)
if err != nil {
t.Logf("Commit is not signed correctly\n%s", commit.ContentToSign())
return err
}
return nil
}
// Utils
func setupRepoForRebase(repo *Repository, masterCommit, branchName string, commitOpts commitOptions) error {
// Create a new branch from master
err := createBranch(repo, branchName)
if err != nil {
return err
}
// Create a commit in master
_, err = commitSomething(repo, masterCommit, masterCommit, commitOpts)
if err != nil {
return err
}
// Switch to emile
err = repo.SetHead("refs/heads/" + branchName)
if err != nil {
return err
}
// Check master commit is not in emile branch
if entryExists(repo, masterCommit) {
return errors.New(masterCommit + " entry should not exist in " + branchName + " branch.")
}
return nil
}
func performRebaseOnto(repo *Repository, branch string, rebaseOpts *RebaseOptions) (*Rebase, error) {
master, err := repo.LookupBranch(branch, BranchLocal)
if err != nil {
return nil, err
}
defer master.Free()
onto, err := repo.AnnotatedCommitFromRef(master.Reference)
if err != nil {
return nil, err
}
defer onto.Free()
// Init rebase
rebase, err := repo.InitRebase(nil, nil, onto, rebaseOpts)
if err != nil {
return nil, err
}
// Check no operation has been started yet
rebaseOperationIndex, err := rebase.CurrentOperationIndex()
if rebaseOperationIndex != RebaseNoOperation && err != ErrRebaseNoOperation {
return nil, errors.New("No operation should have been started yet")
}
// Iterate in rebase operations regarding operation count
opCount := int(rebase.OperationCount())
for op := 0; op < opCount; op++ {
operation, err := rebase.Next()
if err != nil {
return nil, err
}
// Check operation index is correct
rebaseOperationIndex, err = rebase.CurrentOperationIndex()
if int(rebaseOperationIndex) != op {
return nil, errors.New("Bad operation index")
}
if !operationsAreEqual(rebase.OperationAt(uint(op)), operation) {
return nil, errors.New("Rebase operations should be equal")
}
// Get current rebase operation created commit
commit, err := repo.LookupCommit(operation.Id)
if err != nil {
return nil, err
}
defer commit.Free()
// Apply commit
err = rebase.Commit(operation.Id, signature(), signature(), commit.Message())
if err != nil {
return nil, err
}
}
return rebase, nil
}
func operationsAreEqual(l, r *RebaseOperation) bool {
return l.Exec == r.Exec && l.Type == r.Type && l.Id.String() == r.Id.String()
}
func createBranch(repo *Repository, branch string) error {
commit, err := headCommit(repo)
if err != nil {
return err
}
defer commit.Free()
_, err = repo.CreateBranch(branch, commit, false)
if err != nil {
return err
}
return nil
}
func signature() *Signature {
return &Signature{
Name: "Emile",
Email: "emile@emile.com",
When: time.Now(),
}
}
func headCommit(repo *Repository) (*Commit, error) {
head, err := repo.Head()
if err != nil {
return nil, err
}
defer head.Free()
commit, err := repo.LookupCommit(head.Target())
if err != nil {
return nil, err
}
return commit, nil
}
func headTree(repo *Repository) (*Tree, error) {
headCommit, err := headCommit(repo)
if err != nil {
return nil, err
}
defer headCommit.Free()
tree, err := headCommit.Tree()
if err != nil {
return nil, err
}
return tree, nil
}
func commitSomething(repo *Repository, something, content string, commitOpts commitOptions) (*Oid, error) {
headCommit, err := headCommit(repo)
if err != nil {
return nil, err
}
defer headCommit.Free()
index, err := NewIndex()
if err != nil {
return nil, err
}
defer index.Free()
blobOID, err := repo.CreateBlobFromBuffer([]byte(content))
if err != nil {
return nil, err
}
entry := &IndexEntry{
Mode: FilemodeBlob,
Id: blobOID,
Path: something,
}
if err := index.Add(entry); err != nil {
return nil, err
}
newTreeOID, err := index.WriteTreeTo(repo)
if err != nil {
return nil, err
}
newTree, err := repo.LookupTree(newTreeOID)
if err != nil {
return nil, err
}
defer newTree.Free()
commit, err := repo.CreateCommit("HEAD", signature(), signature(), "Test rebase, Baby! "+something, newTree, headCommit)
if err != nil {
return nil, err
}
if commitOpts.CommitSigningCallback != nil {
commit, err := repo.LookupCommit(commit)
if err != nil {
return nil, err
}
oid, err := commit.WithSignatureUsing(commitOpts.CommitSigningCallback)
if err != nil {
return nil, err
}
newCommit, err := repo.LookupCommit(oid)
if err != nil {
return nil, err
}
head, err := repo.Head()
if err != nil {
return nil, err
}
_, err = repo.References.Create(
head.Name(),
newCommit.Id(),
true,
"repoint to signed commit",
)
if err != nil {
return nil, err
}
}
checkoutOpts := &CheckoutOptions{
Strategy: CheckoutRemoveUntracked | CheckoutForce,
}
err = repo.CheckoutIndex(index, checkoutOpts)
if err != nil {
return nil, err
}
return commit, nil
}
func entryExists(repo *Repository, file string) bool {
headTree, err := headTree(repo)
if err != nil {
return false
}
defer headTree.Free()
_, err = headTree.EntryByPath(file)
return err == nil
}
func commitMsgsList(repo *Repository) ([]string, error) {
head, err := headCommit(repo)
if err != nil {
return nil, err
}
defer head.Free()
var commits []string
parent := head.Parent(0)
defer parent.Free()
commits = append(commits, head.Message(), parent.Message())
for parent.ParentCount() != 0 {
parent = parent.Parent(0)
defer parent.Free()
commits = append(commits, parent.Message())
}
return commits, nil
}
func assertStringList(t *testing.T, expected, actual []string) {
if len(expected) != len(actual) {
t.Fatal("Lists are not the same size, expected " + strconv.Itoa(len(expected)) +
", got " + strconv.Itoa(len(actual)))
}
for index, element := range expected {
if element != actual[index] {
t.Error("Expected element " + strconv.Itoa(index) + " to be " + element + ", got " + actual[index])
}
}
}

View File

@ -1,64 +0,0 @@
package git
/*
#include <git2.h>
#include <git2/sys/refdb_backend.h>
extern void _go_git_refdb_backend_free(git_refdb_backend *backend);
*/
import "C"
import (
"runtime"
"unsafe"
)
type Refdb struct {
doNotCompare
ptr *C.git_refdb
r *Repository
}
type RefdbBackend struct {
doNotCompare
ptr *C.git_refdb_backend
}
func (v *Repository) NewRefdb() (refdb *Refdb, err error) {
var ptr *C.git_refdb
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_refdb_new(&ptr, v.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
}
refdb = &Refdb{ptr: ptr, r: v}
runtime.SetFinalizer(refdb, (*Refdb).Free)
return refdb, nil
}
func NewRefdbBackendFromC(ptr unsafe.Pointer) (backend *RefdbBackend) {
backend = &RefdbBackend{ptr: (*C.git_refdb_backend)(ptr)}
return backend
}
func (v *Refdb) SetBackend(backend *RefdbBackend) (err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_refdb_set_backend(v.ptr, backend.ptr)
runtime.KeepAlive(v)
runtime.KeepAlive(backend)
if ret < 0 {
backend.Free()
return MakeGitError(ret)
}
return nil
}
func (v *RefdbBackend) Free() {
runtime.SetFinalizer(v, nil)
C._go_git_refdb_backend_free(v.ptr)
}

View File

@ -1,7 +1,9 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
#include <git2/errors.h>
*/
import "C"
import (
@ -10,353 +12,100 @@ import (
)
type ReferenceType int
const (
ReferenceSymbolic ReferenceType = C.GIT_REF_SYMBOLIC
ReferenceOid ReferenceType = C.GIT_REF_OID
ReferenceOid = C.GIT_REF_OID
)
type Reference struct {
doNotCompare
ptr *C.git_reference
repo *Repository
ptr *C.git_reference
}
type ReferenceCollection struct {
doNotCompare
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)
runtime.KeepAlive(c)
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)
runtime.KeepAlive(c)
runtime.KeepAlive(id)
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)
runtime.KeepAlive(c)
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)
runtime.KeepAlive(c)
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)
runtime.KeepAlive(c)
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)
runtime.KeepAlive(c)
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}
func newReferenceFromC(ptr *C.git_reference) *Reference {
ref := &Reference{ptr}
runtime.SetFinalizer(ref, (*Reference).Free)
return ref
}
func (v *Reference) SetSymbolicTarget(target string, msg string) (*Reference, error) {
func (v *Reference) SetSymbolicTarget(target string) (*Reference, error) {
var ptr *C.git_reference
ctarget := C.CString(target)
defer C.free(unsafe.Pointer(ctarget))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var cmsg *C.char
if msg == "" {
cmsg = nil
} else {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget, cmsg)
runtime.KeepAlive(v)
ret := C.git_reference_symbolic_set_target(&ptr, v.ptr, ctarget)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newReferenceFromC(ptr, v.repo), nil
return newReferenceFromC(ptr), nil
}
func (v *Reference) SetTarget(target *Oid, msg string) (*Reference, error) {
func (v *Reference) SetTarget(target *Oid) (*Reference, error) {
var ptr *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var cmsg *C.char
if msg == "" {
cmsg = nil
} else {
cmsg = C.CString(msg)
defer C.free(unsafe.Pointer(cmsg))
}
ret := C.git_reference_set_target(&ptr, v.ptr, target.toC(), cmsg)
runtime.KeepAlive(v)
ret := C.git_reference_set_target(&ptr, v.ptr, target.toC())
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newReferenceFromC(ptr, v.repo), nil
return newReferenceFromC(ptr), nil
}
func (v *Reference) Resolve() (*Reference, error) {
var ptr *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_resolve(&ptr, v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newReferenceFromC(ptr, v.repo), nil
return newReferenceFromC(ptr), nil
}
func (v *Reference) Rename(name string, force bool, msg string) (*Reference, error) {
func (v *Reference) Rename(name string, force bool) (*Reference, error) {
var ptr *C.git_reference
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))
}
ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_rename(&ptr, v.ptr, cname, cbool(force), cmsg)
runtime.KeepAlive(v)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newReferenceFromC(ptr, v.repo), nil
return newReferenceFromC(ptr), nil
}
func (v *Reference) Target() *Oid {
ret := newOidFromC(C.git_reference_target(v.ptr))
runtime.KeepAlive(v)
return ret
return newOidFromC(C.git_reference_target(v.ptr))
}
func (v *Reference) SymbolicTarget() string {
var ret string
cstr := C.git_reference_symbolic_target(v.ptr)
if cstr != nil {
return C.GoString(cstr)
if cstr == nil {
return ""
}
runtime.KeepAlive(v)
return ret
return C.GoString(cstr)
}
func (v *Reference) Delete() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_delete(v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (v *Reference) Peel(t ObjectType) (*Object, error) {
var cobj *C.git_object
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C.git_reference_peel(&cobj, v.ptr, C.git_object_t(t))
runtime.KeepAlive(v)
if err < 0 {
return nil, MakeGitError(err)
}
return allocObject(cobj, v.repo), nil
}
// Owner returns a weak reference to the repository which owns this reference.
// This won't keep the underlying repository alive, but it should still be
// Freed.
func (v *Reference) Owner() *Repository {
repo := newRepositoryFromC(C.git_reference_owner(v.ptr))
runtime.KeepAlive(v)
repo.weak = true
return repo
}
// Cmp compares v to ref2. It returns 0 on equality, otherwise a
// stable sorting.
func (v *Reference) Cmp(ref2 *Reference) int {
ret := int(C.git_reference_cmp(v.ptr, ref2.ptr))
runtime.KeepAlive(v)
runtime.KeepAlive(ref2)
return ret
}
// Shorthand returns a "human-readable" short reference name.
func (v *Reference) Shorthand() string {
ret := C.GoString(C.git_reference_shorthand(v.ptr))
runtime.KeepAlive(v)
return ret
}
// Name returns the full name of v.
func (v *Reference) Name() string {
ret := C.GoString(C.git_reference_name(v.ptr))
runtime.KeepAlive(v)
return ret
return C.GoString(C.git_reference_name(v.ptr))
}
func (v *Reference) Type() ReferenceType {
ret := ReferenceType(C.git_reference_type(v.ptr))
runtime.KeepAlive(v)
return ret
}
func (v *Reference) IsBranch() bool {
ret := C.git_reference_is_branch(v.ptr) == 1
runtime.KeepAlive(v)
return ret
}
func (v *Reference) IsRemote() bool {
ret := C.git_reference_is_remote(v.ptr) == 1
runtime.KeepAlive(v)
return ret
}
func (v *Reference) IsTag() bool {
ret := C.git_reference_is_tag(v.ptr) == 1
runtime.KeepAlive(v)
return ret
}
// IsNote checks if the reference is a note.
func (v *Reference) IsNote() bool {
ret := C.git_reference_is_note(v.ptr) == 1
runtime.KeepAlive(v)
return ret
return ReferenceType(C.git_reference_type(v.ptr))
}
func (v *Reference) Free() {
@ -365,45 +114,21 @@ func (v *Reference) Free() {
}
type ReferenceIterator struct {
doNotCompare
ptr *C.git_reference_iterator
repo *Repository
}
type ReferenceNameIterator struct {
doNotCompare
*ReferenceIterator
}
// NewReferenceIterator creates a new iterator over reference names
func (repo *Repository) NewReferenceIterator() (*ReferenceIterator, error) {
var ptr *C.git_reference_iterator
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_iterator_new(&ptr, repo.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newReferenceIteratorFromC(ptr, repo), nil
}
// NewReferenceIterator creates a new branch iterator over reference names
func (repo *Repository) NewReferenceNameIterator() (*ReferenceNameIterator, error) {
var ptr *C.git_reference_iterator
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_iterator_new(&ptr, repo.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
}
iter := newReferenceIteratorFromC(ptr, repo)
return iter.Names(), nil
iter := &ReferenceIterator{repo: repo, ptr: ptr}
runtime.SetFinalizer(iter, (*ReferenceIterator).Free)
return iter, nil
}
// NewReferenceIteratorGlob creates an iterator over reference names
@ -413,61 +138,46 @@ func (repo *Repository) NewReferenceIteratorGlob(glob string) (*ReferenceIterato
cstr := C.CString(glob)
defer C.free(unsafe.Pointer(cstr))
var ptr *C.git_reference_iterator
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_iterator_glob_new(&ptr, repo.ptr, cstr)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newReferenceIteratorFromC(ptr, repo), nil
iter := &ReferenceIterator{repo: repo, ptr: ptr}
runtime.SetFinalizer(iter, (*ReferenceIterator).Free)
return iter, nil
}
func (i *ReferenceIterator) Names() *ReferenceNameIterator {
return &ReferenceNameIterator{ReferenceIterator: i}
}
// NextName retrieves the next reference name. If the iteration is over,
// the returned error code is git.ErrorCodeIterOver
func (v *ReferenceNameIterator) Next() (string, error) {
// Next retrieves the next reference name. If the iteration is over,
// the returned error is git.ErrIterOver
func (v *ReferenceIterator) NextName() (string, error) {
var ptr *C.char
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_next_name(&ptr, v.ptr)
if ret == ITEROVER {
return "", ErrIterOver
}
if ret < 0 {
return "", MakeGitError(ret)
return "", LastError()
}
return C.GoString(ptr), nil
}
// Next retrieves the next reference. If the iterationis over, the
// returned error code is git.ErrorCodeIterOver
func (v *ReferenceIterator) Next() (*Reference, error) {
var ptr *C.git_reference
// Create a channel from the iterator. You can use range on the
// returned channel to iterate over all the references names. The channel
// will be closed in case any error is found.
func (v *ReferenceIterator) NameIter() <-chan string {
ch := make(chan string)
go func() {
defer close(ch)
name, err := v.NextName()
for err == nil {
ch <- name
name, err = v.NextName()
}
}()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reference_next(&ptr, v.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newReferenceFromC(ptr, v.repo), nil
}
func newReferenceIteratorFromC(ptr *C.git_reference_iterator, r *Repository) *ReferenceIterator {
iter := &ReferenceIterator{
ptr: ptr,
repo: r,
}
runtime.SetFinalizer(iter, (*ReferenceIterator).Free)
return iter
return ch
}
// Free the reference iterator
@ -475,67 +185,3 @@ func (v *ReferenceIterator) Free() {
runtime.SetFinalizer(v, nil)
C.git_reference_iterator_free(v.ptr)
}
// ReferenceNameIsValid returns whether 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 ReferenceNameIsValid(name string) (bool, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var valid C.int
ret := C.git_reference_name_is_valid(&valid, cname)
if ret < 0 {
return false, MakeGitError(ret)
}
return valid == 1, nil
}
const (
// This should match GIT_REFNAME_MAX in src/refs.h
_refnameMaxLength = C.size_t(1024)
)
type ReferenceFormat uint
const (
ReferenceFormatNormal ReferenceFormat = C.GIT_REFERENCE_FORMAT_NORMAL
ReferenceFormatAllowOnelevel ReferenceFormat = C.GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL
ReferenceFormatRefspecPattern ReferenceFormat = C.GIT_REFERENCE_FORMAT_REFSPEC_PATTERN
ReferenceFormatRefspecShorthand ReferenceFormat = C.GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND
)
// ReferenceNormalizeName normalizes the reference name and checks validity.
//
// This will normalize the reference name by removing any leading slash '/'
// characters and collapsing runs of adjacent slashes between name components
// into a single slash.
//
// See git_reference_symbolic_create() for rules about valid names.
func ReferenceNormalizeName(name string, flags ReferenceFormat) (string, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
buf := (*C.char)(C.malloc(_refnameMaxLength))
defer C.free(unsafe.Pointer(buf))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_reference_normalize_name(buf, _refnameMaxLength, cname, C.uint(flags))
if ecode < 0 {
return "", MakeGitError(ecode)
}
return C.GoString(buf), nil
}

View File

@ -1,6 +1,7 @@
package git
import (
"os"
"runtime"
"sort"
"testing"
@ -8,20 +9,19 @@ import (
)
func TestRefModification(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
defer os.RemoveAll(repo.Workdir())
commitId, treeId := seedTestRepo(t, repo)
_, err := repo.References.Create("refs/tags/tree", treeId, true, "testTreeTag")
_, err := repo.CreateReference("refs/tags/tree", treeId, true)
checkFatal(t, err)
tag, err := repo.References.Lookup("refs/tags/tree")
tag, err := repo.LookupReference("refs/tags/tree")
checkFatal(t, err)
checkRefType(t, tag, ReferenceOid)
ref, err := repo.References.Lookup("HEAD")
ref, err := repo.LookupReference("HEAD")
checkFatal(t, err)
checkRefType(t, ref, ReferenceSymbolic)
@ -45,18 +45,17 @@ func TestRefModification(t *testing.T) {
t.Fatalf("Wrong ref target")
}
_, err = tag.Rename("refs/tags/renamed", false, "")
_, err = tag.Rename("refs/tags/renamed", false)
checkFatal(t, err)
tag, err = repo.References.Lookup("refs/tags/renamed")
tag, err = repo.LookupReference("refs/tags/renamed")
checkFatal(t, err)
checkRefType(t, ref, ReferenceOid)
}
func TestReferenceIterator(t *testing.T) {
t.Parallel()
func TestIterator(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
defer os.RemoveAll(repo.Workdir())
loc, err := time.LoadLocation("Europe/Berlin")
checkFatal(t, err)
@ -79,13 +78,13 @@ func TestReferenceIterator(t *testing.T) {
commitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
checkFatal(t, err)
_, err = repo.References.Create("refs/heads/one", commitId, true, "headOne")
_, err = repo.CreateReference("refs/heads/one", commitId, true)
checkFatal(t, err)
_, err = repo.References.Create("refs/heads/two", commitId, true, "headTwo")
_, err = repo.CreateReference("refs/heads/two", commitId, true)
checkFatal(t, err)
_, err = repo.References.Create("refs/heads/three", commitId, true, "headThree")
_, err = repo.CreateReference("refs/heads/three", commitId, true)
checkFatal(t, err)
iter, err := repo.NewReferenceIterator()
@ -100,155 +99,41 @@ func TestReferenceIterator(t *testing.T) {
}
// test some manual iteration
nameIter := iter.Names()
name, err := nameIter.Next()
name, err := iter.NextName()
for err == nil {
list = append(list, name)
name, err = nameIter.Next()
name, err = iter.NextName()
}
if !IsErrorCode(err, ErrorCodeIterOver) {
if err != ErrIterOver {
t.Fatal("Iteration not over")
}
sort.Strings(list)
compareStringList(t, expected, list)
// test the channel iteration
list = []string{}
iter, err = repo.NewReferenceIterator()
for name := range iter.NameIter() {
list = append(list, name)
}
sort.Strings(list)
compareStringList(t, expected, list)
// test the iterator for full refs, rather than just names
iter, err = repo.NewReferenceIterator()
checkFatal(t, err)
count := 0
_, err = iter.Next()
for err == nil {
count++
_, err = iter.Next()
}
if !IsErrorCode(err, ErrorCodeIterOver) {
t.Fatal("Iteration not over")
iter, err = repo.NewReferenceIteratorGlob("refs/heads/t*")
expected = []string{
"refs/heads/three",
"refs/heads/two",
}
if count != 4 {
t.Fatalf("Wrong number of references returned %v", count)
list = []string{}
for name := range iter.NameIter() {
list = append(list, name)
}
}
func TestReferenceOwner(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId, _ := seedTestRepo(t, repo)
ref, err := repo.References.Create("refs/heads/foo", commitId, true, "")
checkFatal(t, err)
owner := ref.Owner()
if owner == nil {
t.Fatal("nil owner")
}
if owner.ptr != repo.ptr {
t.Fatalf("bad ptr, expected %v have %v\n", repo.ptr, owner.ptr)
}
}
func TestUtil(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId, _ := seedTestRepo(t, repo)
ref, err := repo.References.Create("refs/heads/foo", commitId, true, "")
checkFatal(t, err)
ref2, err := repo.References.Dwim("foo")
checkFatal(t, err)
if ref.Cmp(ref2) != 0 {
t.Fatalf("foo didn't dwim to the right thing")
}
if ref.Shorthand() != "foo" {
t.Fatalf("refs/heads/foo has no foo shorthand")
}
hasLog, err := repo.References.HasLog("refs/heads/foo")
checkFatal(t, err)
if !hasLog {
t.Fatalf("branches have logs by default")
}
}
func TestIsNote(t *testing.T) {
t.Parallel()
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 TestReferenceNameIsValid(t *testing.T) {
t.Parallel()
valid, err := ReferenceNameIsValid("HEAD")
checkFatal(t, err)
if !valid {
t.Errorf("HEAD should be a valid reference name")
}
valid, err = ReferenceNameIsValid("HEAD1")
checkFatal(t, err)
if valid {
t.Errorf("HEAD1 should not be a valid reference name")
}
}
func TestReferenceNormalizeName(t *testing.T) {
t.Parallel()
ref, err := ReferenceNormalizeName("refs/heads//master", ReferenceFormatNormal)
checkFatal(t, err)
if ref != "refs/heads/master" {
t.Errorf("ReferenceNormalizeName(%q) = %q; want %q", "refs/heads//master", ref, "refs/heads/master")
}
ref, err = ReferenceNormalizeName("master", ReferenceFormatAllowOnelevel|ReferenceFormatRefspecShorthand)
checkFatal(t, err)
if ref != "master" {
t.Errorf("ReferenceNormalizeName(%q) = %q; want %q", "master", ref, "master")
}
ref, err = ReferenceNormalizeName("foo^", ReferenceFormatNormal)
if !IsErrorCode(err, ErrorCodeInvalidSpec) {
t.Errorf("foo^ should be invalid")
}
compareStringList(t, expected, list)
}
func compareStringList(t *testing.T, expected, actual []string) {
@ -267,7 +152,8 @@ func checkRefType(t *testing.T, ref *Reference, kind ReferenceType) {
// The failure happens at wherever we were called, not here
_, file, line, ok := runtime.Caller(1)
if !ok {
t.Fatalf("Unable to get caller")
t.Fatal()
}
t.Fatalf("Wrong ref type at %v:%v; have %v, expected %v", file, line, ref.Type(), kind)
}

View File

@ -1,149 +0,0 @@
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
}

View File

@ -1,75 +0,0 @@
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)
}
}

1267
remote.go

File diff suppressed because it is too large Load Diff

View File

@ -1,521 +0,0 @@
package git
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io"
"net"
"os"
"os/exec"
"strings"
"sync"
"testing"
"time"
"github.com/google/shlex"
"golang.org/x/crypto/ssh"
)
func TestListRemotes(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.Remotes.Create("test", "git://foo/bar")
checkFatal(t, err)
defer remote.Free()
expected := []string{
"test",
}
actual, err := repo.Remotes.List()
checkFatal(t, err)
compareStringList(t, expected, actual)
}
func assertHostname(cert *Certificate, valid bool, hostname string, t *testing.T) error {
if hostname != "github.com" {
t.Fatal("hostname does not match")
return errors.New("hostname does not match")
}
return nil
}
func TestCertificateCheck(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
defer remote.Free()
options := FetchOptions{
RemoteCallbacks: RemoteCallbacks{
CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) error {
return assertHostname(cert, valid, hostname, t)
},
},
}
err = remote.Fetch([]string{}, &options, "")
checkFatal(t, err)
}
func TestRemoteConnect(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
defer remote.Free()
err = remote.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
}
func TestRemoteConnectOption(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
config, err := repo.Config()
checkFatal(t, err)
err = config.SetString("url.git@github.com:.insteadof", "https://github.com/")
checkFatal(t, err)
option, err := DefaultRemoteCreateOptions()
checkFatal(t, err)
option.Name = "origin"
option.Flags = RemoteCreateSkipInsteadof
remote, err := repo.Remotes.CreateWithOptions("https://github.com/libgit2/TestGitRepository", option)
checkFatal(t, err)
defer remote.Free()
err = remote.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
}
func TestRemoteLs(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
defer remote.Free()
err = remote.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
heads, err := remote.Ls()
checkFatal(t, err)
if len(heads) == 0 {
t.Error("Expected remote heads")
}
}
func TestRemoteLsFiltering(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
defer remote.Free()
err = remote.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
heads, err := remote.Ls("master")
checkFatal(t, err)
if len(heads) != 1 {
t.Fatalf("Expected one head for master but I got %d", len(heads))
}
if heads[0].Id == nil {
t.Fatalf("Expected head to have an Id, but it's nil")
}
if heads[0].Name == "" {
t.Fatalf("Expected head to have a name, but it's empty")
}
}
func TestRemotePruneRefs(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
config, err := repo.Config()
checkFatal(t, err)
defer config.Free()
err = config.SetBool("remote.origin.prune", true)
checkFatal(t, err)
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
checkFatal(t, err)
defer remote.Free()
remote, err = repo.Remotes.Lookup("origin")
checkFatal(t, err)
defer remote.Free()
if !remote.PruneRefs() {
t.Fatal("Expected remote to be configured to prune references")
}
}
func TestRemotePrune(t *testing.T) {
t.Parallel()
remoteRepo := createTestRepo(t)
defer cleanupTestRepo(t, remoteRepo)
head, _ := seedTestRepo(t, remoteRepo)
commit, err := remoteRepo.LookupCommit(head)
checkFatal(t, err)
defer commit.Free()
remoteRef, err := remoteRepo.CreateBranch("test-prune", commit, true)
checkFatal(t, err)
defer remoteRef.Free()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
config, err := repo.Config()
checkFatal(t, err)
defer config.Free()
remoteUrl := fmt.Sprintf("file://%s", remoteRepo.Workdir())
remote, err := repo.Remotes.Create("origin", remoteUrl)
checkFatal(t, err)
defer remote.Free()
err = remote.Fetch([]string{"test-prune"}, nil, "")
checkFatal(t, err)
ref, err := repo.References.Create("refs/remotes/origin/test-prune", head, true, "remote reference")
checkFatal(t, err)
defer ref.Free()
err = remoteRef.Delete()
checkFatal(t, err)
err = config.SetBool("remote.origin.prune", true)
checkFatal(t, err)
rr, err := repo.Remotes.Lookup("origin")
checkFatal(t, err)
defer rr.Free()
err = rr.ConnectFetch(nil, nil, nil)
checkFatal(t, err)
err = rr.Prune(nil)
checkFatal(t, err)
ref, err = repo.References.Lookup("refs/remotes/origin/test-prune")
if err == nil {
ref.Free()
t.Fatal("Expected error getting a pruned reference")
}
}
func TestRemoteCredentialsCalled(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
remote, err := repo.Remotes.CreateAnonymous("https://github.com/libgit2/non-existent")
checkFatal(t, err)
defer remote.Free()
errNonExistent := errors.New("non-existent repository")
fetchOpts := FetchOptions{
RemoteCallbacks: RemoteCallbacks{
CredentialsCallback: func(url, username string, allowedTypes CredentialType) (*Credential, error) {
return nil, errNonExistent
},
},
}
err = remote.Fetch(nil, &fetchOpts, "fetch")
if err != errNonExistent {
t.Fatalf("remote.Fetch() = %v, want %v", err, errNonExistent)
}
}
func newChannelPipe(t *testing.T, w io.Writer, wg *sync.WaitGroup) (*os.File, error) {
pr, pw, err := os.Pipe()
if err != nil {
return nil, err
}
wg.Add(1)
go func() {
_, err := io.Copy(w, pr)
if err != nil && err != io.EOF {
t.Logf("Failed to copy: %v", err)
}
wg.Done()
}()
return pw, nil
}
func startSSHServer(t *testing.T, hostKey ssh.Signer, authorizedKeys []ssh.PublicKey) net.Listener {
t.Helper()
marshaledAuthorizedKeys := make([][]byte, len(authorizedKeys))
for i, authorizedKey := range authorizedKeys {
marshaledAuthorizedKeys[i] = authorizedKey.Marshal()
}
config := &ssh.ServerConfig{
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
marshaledPubKey := pubKey.Marshal()
for _, marshaledAuthorizedKey := range marshaledAuthorizedKeys {
if bytes.Equal(marshaledPubKey, marshaledAuthorizedKey) {
return &ssh.Permissions{
// Record the public key used for authentication.
Extensions: map[string]string{
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
},
}, nil
}
}
t.Logf("unknown public key for %q:\n\t%+v\n\t%+v\n", c.User(), pubKey.Marshal(), authorizedKeys)
return nil, fmt.Errorf("unknown public key for %q", c.User())
},
}
config.AddHostKey(hostKey)
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("Failed to listen for connection: %v", err)
}
go func() {
nConn, err := listener.Accept()
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
return
}
t.Logf("Failed to accept incoming connection: %v", err)
return
}
defer nConn.Close()
conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
if err != nil {
t.Logf("failed to handshake: %+v, %+v", conn, err)
return
}
// The incoming Request channel must be serviced.
go func() {
for newRequest := range reqs {
t.Logf("new request %v", newRequest)
}
}()
// Service only the first channel request
newChannel := <-chans
defer func() {
for newChannel := range chans {
t.Logf("new channel %v", newChannel)
newChannel.Reject(ssh.UnknownChannelType, "server closing")
}
}()
// Channels have a type, depending on the application level
// protocol intended. In the case of a shell, the type is
// "session" and ServerShell may be used to present a simple
// terminal interface.
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
return
}
channel, requests, err := newChannel.Accept()
if err != nil {
t.Logf("Could not accept channel: %v", err)
return
}
defer channel.Close()
// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "exec" request.
req := <-requests
if req.Type != "exec" {
req.Reply(false, nil)
return
}
// RFC 4254 Section 6.5.
var payload struct {
Command string
}
if err := ssh.Unmarshal(req.Payload, &payload); err != nil {
t.Logf("invalid payload on channel %v: %v", channel, err)
req.Reply(false, nil)
return
}
args, err := shlex.Split(payload.Command)
if err != nil {
t.Logf("invalid command on channel %v: %v", channel, err)
req.Reply(false, nil)
return
}
if len(args) < 2 || (args[0] != "git-upload-pack" && args[0] != "git-receive-pack") {
t.Logf("invalid command (%v) on channel %v: %v", args, channel, err)
req.Reply(false, nil)
return
}
req.Reply(true, nil)
go func(in <-chan *ssh.Request) {
for req := range in {
t.Logf("draining request %v", req)
}
}(requests)
// The first parameter is the (absolute) path of the repository.
args[1] = "./testdata" + args[1]
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = channel
var wg sync.WaitGroup
stdoutPipe, err := newChannelPipe(t, channel, &wg)
if err != nil {
t.Logf("Failed to create stdout pipe: %v", err)
return
}
cmd.Stdout = stdoutPipe
stderrPipe, err := newChannelPipe(t, channel.Stderr(), &wg)
if err != nil {
t.Logf("Failed to create stderr pipe: %v", err)
return
}
cmd.Stderr = stderrPipe
go func() {
wg.Wait()
channel.CloseWrite()
}()
err = cmd.Start()
if err != nil {
t.Logf("Failed to start %v: %v", args, err)
return
}
// Once the process has started, we need to close the write end of the
// pipes from this process so that we can know when the child has done
// writing to it.
stdoutPipe.Close()
stderrPipe.Close()
timer := time.AfterFunc(5*time.Second, func() {
t.Log("process timed out, terminating")
cmd.Process.Kill()
})
defer timer.Stop()
err = cmd.Wait()
if err != nil {
t.Logf("Failed to run %v: %v", args, err)
return
}
}()
return listener
}
func TestRemoteSSH(t *testing.T) {
t.Parallel()
pubKeyUsername := "testuser"
hostPrivKey, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
t.Fatalf("Failed to generate the host RSA private key: %v", err)
}
hostSigner, err := ssh.NewSignerFromKey(hostPrivKey)
if err != nil {
t.Fatalf("Failed to generate SSH hostSigner: %v", err)
}
privKey, err := rsa.GenerateKey(rand.Reader, 512)
if err != nil {
t.Fatalf("Failed to generate the user RSA private key: %v", err)
}
signer, err := ssh.NewSignerFromKey(privKey)
if err != nil {
t.Fatalf("Failed to generate SSH signer: %v", err)
}
// This is in the format "xx:xx:xx:...", so we remove the colons so that it
// matches the fmt.Sprintf() below.
// Note that not all libssh2 implementations support the SHA256 fingerprint,
// so we use MD5 here for testing.
publicKeyFingerprint := strings.Replace(ssh.FingerprintLegacyMD5(hostSigner.PublicKey()), ":", "", -1)
listener := startSSHServer(t, hostSigner, []ssh.PublicKey{signer.PublicKey()})
defer listener.Close()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
certificateCheckCallbackCalled := false
fetchOpts := FetchOptions{
RemoteCallbacks: RemoteCallbacks{
CertificateCheckCallback: func(cert *Certificate, valid bool, hostname string) error {
hostkeyFingerprint := fmt.Sprintf("%x", cert.Hostkey.HashMD5[:])
if hostkeyFingerprint != publicKeyFingerprint {
return fmt.Errorf("server hostkey %q, want %q", hostkeyFingerprint, publicKeyFingerprint)
}
certificateCheckCallbackCalled = true
return nil
},
CredentialsCallback: func(url, username string, allowedTypes CredentialType) (*Credential, error) {
if allowedTypes&(CredentialTypeSSHKey|CredentialTypeSSHCustom|CredentialTypeSSHMemory) != 0 {
return NewCredentialSSHKeyFromSigner(pubKeyUsername, signer)
}
if (allowedTypes & CredentialTypeUsername) != 0 {
return NewCredentialUsername(pubKeyUsername)
}
return nil, fmt.Errorf("unknown credential type %+v", allowedTypes)
},
},
}
remote, err := repo.Remotes.Create(
"origin",
fmt.Sprintf("ssh://%s/TestGitRepository", listener.Addr().String()),
)
checkFatal(t, err)
defer remote.Free()
err = remote.Fetch(nil, &fetchOpts, "")
checkFatal(t, err)
if !certificateCheckCallbackCalled {
t.Fatalf("CertificateCheckCallback was not called")
}
heads, err := remote.Ls()
checkFatal(t, err)
if len(heads) == 0 {
t.Error("Expected remote heads")
}
}

View File

@ -1,432 +1,169 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
#include <git2/sys/repository.h>
#include <git2/sys/commit.h>
#include <string.h>
#include <git2/errors.h>
*/
import "C"
import (
"runtime"
"unsafe"
"runtime"
)
// Repository
type Repository struct {
doNotCompare
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
// Submodules represents the collection of submodules and can
// be used to add, remove and configure submodules in this
// repository.
Submodules SubmoduleCollection
// References represents the collection of references and can
// be used to create, remove or update references 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
// Tags represents the collection of tags and can be used to create,
// list, iterate and remove tags in this repository.
Tags TagsCollection
// Stashes represents the collection of stashes and can be used to
// save, apply and iterate over stash states in this repository.
Stashes StashCollection
// weak indicates that a repository is a weak pointer and should not be
// freed.
weak bool
}
func newRepositoryFromC(ptr *C.git_repository) *Repository {
repo := &Repository{ptr: ptr}
repo.Remotes.repo = repo
repo.Remotes.remotes = make(map[*C.git_remote]*Remote)
repo.Submodules.repo = repo
repo.References.repo = repo
repo.Notes.repo = repo
repo.Tags.repo = repo
repo.Stashes.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()
var ptr *C.git_repository
ret := C.git_repository_open(&ptr, cpath)
ret := C.git_repository_open(&repo.ptr, cpath)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newRepositoryFromC(ptr), nil
}
type RepositoryOpenFlag int
const (
RepositoryOpenNoSearch RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_NO_SEARCH
RepositoryOpenCrossFs RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_CROSS_FS
RepositoryOpenBare RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_BARE
RepositoryOpenFromEnv RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_FROM_ENV
RepositoryOpenNoDotGit RepositoryOpenFlag = C.GIT_REPOSITORY_OPEN_NO_DOTGIT
)
func OpenRepositoryExtended(path string, flags RepositoryOpenFlag, ceiling string) (*Repository, error) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
var cceiling *C.char = nil
if len(ceiling) > 0 {
cceiling = C.CString(ceiling)
defer C.free(unsafe.Pointer(cceiling))
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_repository
ret := C.git_repository_open_ext(&ptr, cpath, C.uint(flags), cceiling)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newRepositoryFromC(ptr), nil
runtime.SetFinalizer(repo, (*Repository).Free)
return repo, 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()
var ptr *C.git_repository
ret := C.git_repository_init(&ptr, cpath, ucbool(isbare))
ret := C.git_repository_init(&repo.ptr, cpath, ucbool(isbare))
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newRepositoryFromC(ptr), nil
}
func NewRepositoryWrapOdb(odb *Odb) (repo *Repository, err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_repository
ret := C.git_repository_wrap_odb(&ptr, odb.ptr)
runtime.KeepAlive(odb)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newRepositoryFromC(ptr), nil
}
func (v *Repository) SetRefdb(refdb *Refdb) {
C.git_repository_set_refdb(v.ptr, refdb.ptr)
runtime.KeepAlive(v)
runtime.SetFinalizer(repo, (*Repository).Free)
return repo, nil
}
func (v *Repository) Free() {
ptr := v.ptr
v.ptr = nil
runtime.SetFinalizer(v, nil)
v.Remotes.Free()
if v.weak {
return
}
C.git_repository_free(ptr)
C.git_repository_free(v.ptr)
}
func (v *Repository) Config() (*Config, error) {
config := new(Config)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_repository_config(&config.ptr, v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
runtime.SetFinalizer(config, (*Config).Free)
return config, nil
}
// SetConfig sets the configuration file for this repository.
//
// This configuration file will be used for all configuration queries involving
// this repository.
func (v *Repository) SetConfig(c *Config) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_repository_set_config(v.ptr, c.ptr)
runtime.KeepAlive(v)
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (v *Repository) Index() (*Index, error) {
var ptr *C.git_index
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_repository_index(&ptr, v.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newIndexFromC(ptr, v), nil
return newIndexFromC(ptr), nil
}
func (v *Repository) lookupType(id *Oid, t ObjectType) (*Object, error) {
func (v *Repository) lookupType(oid *Oid, t ObjectType) (Object, error) {
var ptr *C.git_object
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_object_lookup(&ptr, v.ptr, id.toC(), C.git_object_t(t))
runtime.KeepAlive(id)
ret := C.git_object_lookup(&ptr, v.ptr, oid.toC(), C.git_otype(t))
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return allocObject(ptr, v), nil
return allocObject(ptr), nil
}
func (v *Repository) lookupPrefixType(id *Oid, prefix uint, t ObjectType) (*Object, error) {
var ptr *C.git_object
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_object_lookup_prefix(&ptr, v.ptr, id.toC(), C.size_t(prefix), C.git_object_t(t))
runtime.KeepAlive(id)
if ret < 0 {
return nil, MakeGitError(ret)
}
return allocObject(ptr, v), nil
func (v *Repository) Lookup(oid *Oid) (Object, error) {
return v.lookupType(oid, ObjectAny)
}
func (v *Repository) Lookup(id *Oid) (*Object, error) {
return v.lookupType(id, ObjectAny)
}
// LookupPrefix looks up an object by its OID given a prefix of its identifier.
func (v *Repository) LookupPrefix(id *Oid, prefix uint) (*Object, error) {
return v.lookupPrefixType(id, prefix, ObjectAny)
}
func (v *Repository) LookupTree(id *Oid) (*Tree, error) {
obj, err := v.lookupType(id, ObjectTree)
func (v *Repository) LookupTree(oid *Oid) (*Tree, error) {
obj, err := v.lookupType(oid, ObjectTree)
if err != nil {
return nil, err
}
defer obj.Free()
return obj.AsTree()
return obj.(*Tree), nil
}
// LookupPrefixTree looks up a tree by its OID given a prefix of its identifier.
func (v *Repository) LookupPrefixTree(id *Oid, prefix uint) (*Tree, error) {
obj, err := v.lookupPrefixType(id, prefix, ObjectTree)
func (v *Repository) LookupCommit(oid *Oid) (*Commit, error) {
obj, err := v.lookupType(oid, ObjectCommit)
if err != nil {
return nil, err
}
defer obj.Free()
return obj.AsTree()
return obj.(*Commit), nil
}
func (v *Repository) LookupCommit(id *Oid) (*Commit, error) {
obj, err := v.lookupType(id, ObjectCommit)
func (v *Repository) LookupBlob(oid *Oid) (*Blob, error) {
obj, err := v.lookupType(oid, ObjectBlob)
if err != nil {
return nil, err
}
defer obj.Free()
return obj.AsCommit()
return obj.(*Blob), nil
}
// LookupPrefixCommit looks up a commit by its OID given a prefix of its identifier.
func (v *Repository) LookupPrefixCommit(id *Oid, prefix uint) (*Commit, error) {
obj, err := v.lookupPrefixType(id, prefix, ObjectCommit)
if err != nil {
return nil, err
}
defer obj.Free()
return obj.AsCommit()
}
func (v *Repository) LookupBlob(id *Oid) (*Blob, error) {
obj, err := v.lookupType(id, ObjectBlob)
if err != nil {
return nil, err
}
defer obj.Free()
return obj.AsBlob()
}
// LookupPrefixBlob looks up a blob by its OID given a prefix of its identifier.
func (v *Repository) LookupPrefixBlob(id *Oid, prefix uint) (*Blob, error) {
obj, err := v.lookupPrefixType(id, prefix, ObjectBlob)
if err != nil {
return nil, err
}
defer obj.Free()
return obj.AsBlob()
}
func (v *Repository) LookupTag(id *Oid) (*Tag, error) {
obj, err := v.lookupType(id, ObjectTag)
if err != nil {
return nil, err
}
defer obj.Free()
return obj.AsTag()
}
// LookupPrefixTag looks up a tag by its OID given a prefix of its identifier.
func (v *Repository) LookupPrefixTag(id *Oid, prefix uint) (*Tag, error) {
obj, err := v.lookupPrefixType(id, prefix, ObjectTag)
if err != nil {
return nil, err
}
defer obj.Free()
return obj.AsTag()
}
func (v *Repository) Head() (*Reference, error) {
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_repository_head(&ptr, v.ptr)
ecode := C.git_reference_lookup(&ptr, v.ptr, cname)
if ecode < 0 {
return nil, MakeGitError(ecode)
return nil, LastError()
}
return newReferenceFromC(ptr, v), nil
return newReferenceFromC(ptr), nil
}
func (v *Repository) SetHead(refname string) error {
cname := C.CString(refname)
func (v *Repository) CreateReference(name string, oid *Oid, force bool) (*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_repository_set_head(v.ptr, cname)
runtime.KeepAlive(v)
if ecode != 0 {
return MakeGitError(ecode)
}
return nil
}
func (v *Repository) SetHeadDetached(id *Oid) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_repository_set_head_detached(v.ptr, id.toC())
runtime.KeepAlive(v)
runtime.KeepAlive(id)
if ecode != 0 {
return MakeGitError(ecode)
}
return nil
}
func (v *Repository) IsHeadDetached() (bool, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_repository_head_detached(v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return false, MakeGitError(ret)
}
return ret != 0, nil
}
func (v *Repository) IsHeadUnborn() (bool, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_repository_head_unborn(v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return false, MakeGitError(ret)
}
return ret != 0, nil
}
func (v *Repository) IsEmpty() (bool, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_repository_is_empty(v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return false, MakeGitError(ret)
}
return ret != 0, nil
}
func (v *Repository) IsShallow() (bool, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_repository_is_shallow(v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return false, MakeGitError(ret)
}
return ret != 0, nil
}
func (v *Repository) Walk() (*RevWalk, error) {
var walkPtr *C.git_revwalk
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_revwalk_new(&walkPtr, v.ptr)
ecode := C.git_reference_create(&ptr, v.ptr, cname, oid.toC(), cbool(force))
if ecode < 0 {
return nil, MakeGitError(ecode)
return nil, LastError()
}
return revWalkFromC(v, walkPtr), nil
return newReferenceFromC(ptr), nil
}
func (v *Repository) CreateSymbolicReference(name, target string, force bool) (*Reference, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
ctarget := C.CString(target)
defer C.free(unsafe.Pointer(ctarget))
var ptr *C.git_reference
ecode := C.git_reference_symbolic_create(&ptr, v.ptr, cname, ctarget, cbool(force))
if ecode < 0 {
return nil, LastError()
}
return newReferenceFromC(ptr), nil
}
func (v *Repository) NewRevWalk() (*RevWalk, error) {
walk := new(RevWalk)
ecode := C.git_revwalk_new(&walk.ptr, v.ptr)
if ecode < 0 {
return nil, LastError()
}
walk.repo = v
runtime.SetFinalizer(walk, freeRevWalk)
return walk, nil
}
func (v *Repository) CreateCommit(
@ -435,13 +172,8 @@ func (v *Repository) CreateCommit(
oid := new(Oid)
var cref *C.char
if refname == "" {
cref = nil
} else {
cref = C.CString(refname)
defer C.free(unsafe.Pointer(cref))
}
cref := C.CString(refname)
defer C.free(unsafe.Pointer(cref))
cmsg := C.CString(message)
defer C.free(unsafe.Pointer(cmsg))
@ -449,204 +181,28 @@ func (v *Repository) CreateCommit(
var cparents []*C.git_commit = nil
var parentsarg **C.git_commit = nil
nparents := len(parents)
nparents:= len(parents)
if nparents > 0 {
cparents = make([]*C.git_commit, nparents)
for i, v := range parents {
cparents[i] = v.cast_ptr
cparents[i] = v.ptr
}
parentsarg = &cparents[0]
}
authorSig, err := author.toC()
if err != nil {
return nil, err
}
authorSig := author.toC()
defer C.git_signature_free(authorSig)
committerSig, err := committer.toC()
if err != nil {
return nil, err
}
committerSig := committer.toC()
defer C.git_signature_free(committerSig)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_commit_create(
oid.toC(), v.ptr, cref,
authorSig, committerSig,
nil, cmsg, tree.cast_ptr, C.size_t(nparents), parentsarg)
nil, cmsg, tree.ptr, C.int(nparents), parentsarg)
runtime.KeepAlive(v)
runtime.KeepAlive(oid)
runtime.KeepAlive(parents)
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
// CreateCommitWithSignature creates a commit object from the given contents and
// signature.
func (v *Repository) CreateCommitWithSignature(
commitContent, signature, signatureField string,
) (*Oid, error) {
cCommitContent := C.CString(commitContent)
defer C.free(unsafe.Pointer(cCommitContent))
var cSignature *C.char
if signature != "" {
cSignature = C.CString(string(signature))
defer C.free(unsafe.Pointer(cSignature))
}
var cSignatureField *C.char
if signatureField != "" {
cSignatureField = C.CString(string(signatureField))
defer C.free(unsafe.Pointer(cSignatureField))
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
oid := new(Oid)
ret := C.git_commit_create_with_signature(oid.toC(), v.ptr, cCommitContent, cSignature, cSignatureField)
runtime.KeepAlive(v)
runtime.KeepAlive(oid)
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
// CreateCommitBuffer creates a commit and write it into a buffer.
func (v *Repository) CreateCommitBuffer(
author, committer *Signature,
messageEncoding MessageEncoding,
message string,
tree *Tree,
parents ...*Commit,
) ([]byte, error) {
cmsg := C.CString(message)
defer C.free(unsafe.Pointer(cmsg))
var cencoding *C.char
// Since the UTF-8 encoding is the default, pass in nil whenever UTF-8 is
// provided. That will cause the commit to not have an explicit header for
// it.
if messageEncoding != MessageEncodingUTF8 && messageEncoding != MessageEncoding("") {
cencoding = C.CString(string(messageEncoding))
defer C.free(unsafe.Pointer(cencoding))
}
var cparents []*C.git_commit = nil
var parentsarg **C.git_commit = nil
nparents := len(parents)
if nparents > 0 {
cparents = make([]*C.git_commit, nparents)
for i, v := range parents {
cparents[i] = v.cast_ptr
}
parentsarg = &cparents[0]
}
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)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var buf C.git_buf
defer C.git_buf_dispose(&buf)
ret := C.git_commit_create_buffer(
&buf, v.ptr,
authorSig, committerSig,
cencoding, cmsg, tree.cast_ptr, C.size_t(nparents), parentsarg)
runtime.KeepAlive(v)
runtime.KeepAlive(buf)
runtime.KeepAlive(parents)
if ret < 0 {
return nil, MakeGitError(ret)
}
return C.GoBytes(unsafe.Pointer(buf.ptr), C.int(buf.size)), nil
}
func (v *Repository) CreateCommitFromIds(
refname string, author, committer *Signature,
message string, tree *Oid, parents ...*Oid) (*Oid, error) {
oid := new(Oid)
var cref *C.char
if refname == "" {
cref = nil
} else {
cref = C.CString(refname)
defer C.free(unsafe.Pointer(cref))
}
cmsg := C.CString(message)
defer C.free(unsafe.Pointer(cmsg))
var parentsarg **C.git_oid = nil
nparents := len(parents)
if nparents > 0 {
// All this awful pointer arithmetic is needed to avoid passing a Go
// pointer to Go pointer into C. Other methods (like CreateCommits) are
// fine without this workaround because they are just passing Go pointers
// to C pointers, but arrays-of-pointers-to-git_oid are a bit special since
// both the array and the objects are allocated from Go.
var emptyOidPtr *C.git_oid
sizeofOidPtr := unsafe.Sizeof(emptyOidPtr)
parentsarg = (**C.git_oid)(C.calloc(C.size_t(uintptr(nparents)), C.size_t(sizeofOidPtr)))
defer C.free(unsafe.Pointer(parentsarg))
parentsptr := uintptr(unsafe.Pointer(parentsarg))
for _, v := range parents {
*(**C.git_oid)(unsafe.Pointer(parentsptr)) = v.toC()
parentsptr += sizeofOidPtr
}
}
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)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_commit_create_from_ids(
oid.toC(), v.ptr, cref,
authorSig, committerSig,
nil, cmsg, tree.toC(), C.size_t(nparents), parentsarg)
runtime.KeepAlive(v)
runtime.KeepAlive(oid)
runtime.KeepAlive(tree)
runtime.KeepAlive(parents)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return oid, nil
@ -657,220 +213,57 @@ func (v *Odb) Free() {
C.git_odb_free(v.ptr)
}
func (v *Refdb) Free() {
runtime.SetFinalizer(v, nil)
C.git_refdb_free(v.ptr)
}
func (v *Repository) Odb() (odb *Odb, err error) {
odb = new(Odb)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_repository_odb(&odb.ptr, v.ptr)
runtime.KeepAlive(v)
if ret < 0 {
return nil, MakeGitError(ret)
if ret := C.git_repository_odb(&odb.ptr, v.ptr); ret < 0 {
return nil, LastError()
}
runtime.SetFinalizer(odb, (*Odb).Free)
return odb, nil
return
}
func (repo *Repository) Path() string {
s := C.GoString(C.git_repository_path(repo.ptr))
runtime.KeepAlive(repo)
return s
return C.GoString(C.git_repository_path(repo.ptr))
}
func (repo *Repository) IsBare() bool {
ret := C.git_repository_is_bare(repo.ptr) != 0
runtime.KeepAlive(repo)
return ret
func (repo *Repository) IsBare() (bool) {
return C.git_repository_is_bare(repo.ptr) != 0
}
func (repo *Repository) Workdir() string {
s := C.GoString(C.git_repository_workdir(repo.ptr))
runtime.KeepAlive(repo)
return s
return C.GoString(C.git_repository_workdir(repo.ptr))
}
func (repo *Repository) SetWorkdir(workdir string, updateGitlink bool) error {
cstr := C.CString(workdir)
defer C.free(unsafe.Pointer(cstr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if ret := C.git_repository_set_workdir(repo.ptr, cstr, cbool(updateGitlink)); ret < 0 {
return MakeGitError(ret)
if C.git_repository_set_workdir(repo.ptr, cstr, cbool(updateGitlink)) < 0 {
return LastError()
}
runtime.KeepAlive(repo)
return nil
}
func (v *Repository) TreeBuilder() (*TreeBuilder, error) {
bld := new(TreeBuilder)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if ret := C.git_treebuilder_new(&bld.ptr, v.ptr, nil); ret < 0 {
return nil, MakeGitError(ret)
if ret := C.git_treebuilder_create(&bld.ptr, nil); ret < 0 {
return nil, LastError()
}
runtime.SetFinalizer(bld, (*TreeBuilder).Free)
bld.repo = v
return bld, nil
}
func (v *Repository) TreeBuilderFromTree(tree *Tree) (*TreeBuilder, error) {
bld := new(TreeBuilder)
func (v *Repository) RevparseSingle(spec string) (Object, error) {
cspec := C.CString(spec)
defer C.free(unsafe.Pointer(cspec))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if ret := C.git_treebuilder_new(&bld.ptr, v.ptr, tree.cast_ptr); ret < 0 {
return nil, MakeGitError(ret)
var ptr *C.git_object
ecode := C.git_revparse_single(&ptr, v.ptr, cspec)
if ecode < 0 {
return nil, LastError()
}
runtime.SetFinalizer(bld, (*TreeBuilder).Free)
bld.repo = v
return bld, nil
}
type RepositoryState int
const (
RepositoryStateNone RepositoryState = C.GIT_REPOSITORY_STATE_NONE
RepositoryStateMerge RepositoryState = C.GIT_REPOSITORY_STATE_MERGE
RepositoryStateRevert RepositoryState = C.GIT_REPOSITORY_STATE_REVERT
RepositoryStateCherrypick RepositoryState = C.GIT_REPOSITORY_STATE_CHERRYPICK
RepositoryStateBisect RepositoryState = C.GIT_REPOSITORY_STATE_BISECT
RepositoryStateRebase RepositoryState = C.GIT_REPOSITORY_STATE_REBASE
RepositoryStateRebaseInteractive RepositoryState = C.GIT_REPOSITORY_STATE_REBASE_INTERACTIVE
RepositoryStateRebaseMerge RepositoryState = C.GIT_REPOSITORY_STATE_REBASE_MERGE
RepositoryStateApplyMailbox RepositoryState = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX
RepositoryStateApplyMailboxOrRebase RepositoryState = C.GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE
)
func (r *Repository) State() RepositoryState {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := RepositoryState(C.git_repository_state(r.ptr))
runtime.KeepAlive(r)
return ret
}
func (r *Repository) StateCleanup() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cErr := C.git_repository_state_cleanup(r.ptr)
runtime.KeepAlive(r)
if cErr < 0 {
return MakeGitError(cErr)
}
return nil
}
func (r *Repository) AddGitIgnoreRules(rules string) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
crules := C.CString(rules)
defer C.free(unsafe.Pointer(crules))
ret := C.git_ignore_add_rule(r.ptr, crules)
runtime.KeepAlive(r)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (r *Repository) ClearGitIgnoreRules() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_ignore_clear_internal_rules(r.ptr)
runtime.KeepAlive(r)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// Message retrieves git's prepared message.
// Operations such as git revert/cherry-pick/merge with the -n option stop just
// short of creating a commit with the changes and save their prepared message
// in .git/MERGE_MSG so the next git-commit execution can present it to the
// user for them to amend if they wish.
//
// Use this function to get the contents of this file. Don't forget to remove
// the file after you create the commit.
func (r *Repository) Message() (string, error) {
buf := C.git_buf{}
defer C.git_buf_dispose(&buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cErr := C.git_repository_message(&buf, r.ptr)
runtime.KeepAlive(r)
if cErr < 0 {
return "", MakeGitError(cErr)
}
return C.GoString(buf.ptr), nil
}
// RemoveMessage removes git's prepared message.
func (r *Repository) RemoveMessage() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cErr := C.git_repository_message_remove(r.ptr)
runtime.KeepAlive(r)
if cErr < 0 {
return MakeGitError(cErr)
}
return nil
}
type RepositoryItem int
const (
RepositoryItemGitDir RepositoryItem = C.GIT_REPOSITORY_ITEM_GITDIR
RepositoryItemWorkDir RepositoryItem = C.GIT_REPOSITORY_ITEM_WORKDIR
RepositoryItemCommonDir RepositoryItem = C.GIT_REPOSITORY_ITEM_COMMONDIR
RepositoryItemIndex RepositoryItem = C.GIT_REPOSITORY_ITEM_INDEX
RepositoryItemObjects RepositoryItem = C.GIT_REPOSITORY_ITEM_OBJECTS
RepositoryItemRefs RepositoryItem = C.GIT_REPOSITORY_ITEM_REFS
RepositoryItemPackedRefs RepositoryItem = C.GIT_REPOSITORY_ITEM_PACKED_REFS
RepositoryItemRemotes RepositoryItem = C.GIT_REPOSITORY_ITEM_REMOTES
RepositoryItemConfig RepositoryItem = C.GIT_REPOSITORY_ITEM_CONFIG
RepositoryItemInfo RepositoryItem = C.GIT_REPOSITORY_ITEM_INFO
RepositoryItemHooks RepositoryItem = C.GIT_REPOSITORY_ITEM_HOOKS
RepositoryItemLogs RepositoryItem = C.GIT_REPOSITORY_ITEM_LOGS
RepositoryItemModules RepositoryItem = C.GIT_REPOSITORY_ITEM_MODULES
RepositoryItemWorkTrees RepositoryItem = C.GIT_REPOSITORY_ITEM_WORKTREES
)
func (r *Repository) ItemPath(item RepositoryItem) (string, error) {
var c_buf C.git_buf
defer C.git_buf_dispose(&c_buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_repository_item_path(&c_buf, r.ptr, C.git_repository_item_t(item))
runtime.KeepAlive(r)
if ret < 0 {
return "", MakeGitError(ret)
}
return C.GoString(c_buf.ptr), nil
return allocObject(ptr), nil
}

View File

@ -1,159 +0,0 @@
package git
import (
"testing"
"time"
)
func TestCreateCommitBuffer(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(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),
}
idx, err := repo.Index()
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
err = idx.Write()
checkFatal(t, err)
treeId, err := idx.WriteTree()
checkFatal(t, err)
message := "This is a commit\n"
tree, err := repo.LookupTree(treeId)
checkFatal(t, err)
for encoding, expected := range map[MessageEncoding]string{
MessageEncodingUTF8: `tree b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e
author Rand Om Hacker <random@hacker.com> 1362576600 +0100
committer Rand Om Hacker <random@hacker.com> 1362576600 +0100
This is a commit
`,
MessageEncoding("ASCII"): `tree b7119b11e8ef7a1a5a34d3ac87f5b075228ac81e
author Rand Om Hacker <random@hacker.com> 1362576600 +0100
committer Rand Om Hacker <random@hacker.com> 1362576600 +0100
encoding ASCII
This is a commit
`,
} {
encoding := encoding
expected := expected
t.Run(string(encoding), func(t *testing.T) {
buf, err := repo.CreateCommitBuffer(sig, sig, encoding, message, tree)
checkFatal(t, err)
if expected != string(buf) {
t.Errorf("mismatched commit buffer, expected %v, got %v", expected, string(buf))
}
})
}
}
func TestCreateCommitFromIds(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(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),
}
idx, err := repo.Index()
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
err = idx.Write()
checkFatal(t, err)
treeId, err := idx.WriteTree()
checkFatal(t, err)
message := "This is a commit\n"
tree, err := repo.LookupTree(treeId)
checkFatal(t, err)
expectedCommitId, err := repo.CreateCommit("HEAD", sig, sig, message, tree)
checkFatal(t, err)
commitId, err := repo.CreateCommitFromIds("", sig, sig, message, treeId)
checkFatal(t, err)
if !expectedCommitId.Equal(commitId) {
t.Errorf("mismatched commit ids, expected %v, got %v", expectedCommitId.String(), commitId.String())
}
}
func TestRepositorySetConfig(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(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),
}
idx, err := repo.Index()
checkFatal(t, err)
err = idx.AddByPath("README")
treeId, err := idx.WriteTree()
checkFatal(t, err)
message := "This is a commit\n"
tree, err := repo.LookupTree(treeId)
checkFatal(t, err)
_, err = repo.CreateCommit("HEAD", sig, sig, message, tree)
checkFatal(t, err)
repoConfig, err := repo.Config()
checkFatal(t, err)
temp := Config{}
localConfig, err := temp.OpenLevel(repoConfig, ConfigLevelLocal)
checkFatal(t, err)
repoConfig = nil
err = repo.SetConfig(localConfig)
checkFatal(t, err)
configFieldName := "core.filemode"
err = localConfig.SetBool(configFieldName, true)
checkFatal(t, err)
localConfig = nil
repoConfig, err = repo.Config()
checkFatal(t, err)
result, err := repoConfig.LookupBool(configFieldName)
checkFatal(t, err)
if result != true {
t.Fatal("result must be true")
}
}
func TestRepositoryItemPath(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
gitDir, err := repo.ItemPath(RepositoryItemGitDir)
checkFatal(t, err)
if gitDir == "" {
t.Error("expected not empty gitDir")
}
}

View File

@ -1,49 +0,0 @@
package git
/*
#include <git2.h>
*/
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 *CheckoutOptions) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var err error
cOpts := populateCheckoutOptions(&C.git_checkout_options{}, opts, &err)
defer freeCheckoutOptions(cOpts)
ret := C.git_reset(r.ptr, commit.ptr, C.git_reset_t(resetType), cOpts)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
func (r *Repository) ResetDefaultToCommit(commit *Commit, pathspecs []string) error {
cpathspecs := C.git_strarray{}
cpathspecs.count = C.size_t(len(pathspecs))
cpathspecs.strings = makeCStringsFromStrings(pathspecs)
defer freeStrarray(&cpathspecs)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_reset_default(r.ptr, commit.ptr, &cpathspecs)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}

View File

@ -1,48 +0,0 @@
package git
import (
"io/ioutil"
"testing"
)
func TestResetToCommit(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
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, &CheckoutOptions{})
// 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))
}
}

104
revert.go
View File

@ -1,104 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
)
// RevertOptions contains options for performing a revert
type RevertOptions struct {
Mainline uint
MergeOptions MergeOptions
CheckoutOptions CheckoutOptions
}
func populateRevertOptions(copts *C.git_revert_options, opts *RevertOptions, errorTarget *error) *C.git_revert_options {
C.git_revert_options_init(copts, C.GIT_REVERT_OPTIONS_VERSION)
if opts == nil {
return nil
}
copts.mainline = C.uint(opts.Mainline)
populateMergeOptions(&copts.merge_opts, &opts.MergeOptions)
populateCheckoutOptions(&copts.checkout_opts, &opts.CheckoutOptions, errorTarget)
return copts
}
func revertOptionsFromC(copts *C.git_revert_options) RevertOptions {
return RevertOptions{
Mainline: uint(copts.mainline),
MergeOptions: mergeOptionsFromC(&copts.merge_opts),
CheckoutOptions: checkoutOptionsFromC(&copts.checkout_opts),
}
}
func freeRevertOptions(copts *C.git_revert_options) {
if copts != nil {
return
}
freeMergeOptions(&copts.merge_opts)
freeCheckoutOptions(&copts.checkout_opts)
}
// DefaultRevertOptions initialises a RevertOptions struct with default values
func DefaultRevertOptions() (RevertOptions, error) {
copts := C.git_revert_options{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_revert_options_init(&copts, C.GIT_REVERT_OPTIONS_VERSION)
if ecode < 0 {
return RevertOptions{}, MakeGitError(ecode)
}
defer freeRevertOptions(&copts)
return revertOptionsFromC(&copts), nil
}
// Revert the provided commit leaving the index updated with the results of the revert
func (r *Repository) Revert(commit *Commit, revertOptions *RevertOptions) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var err error
cOpts := populateRevertOptions(&C.git_revert_options{}, revertOptions, &err)
defer freeRevertOptions(cOpts)
ret := C.git_revert(r.ptr, commit.cast_ptr, cOpts)
runtime.KeepAlive(r)
runtime.KeepAlive(commit)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// RevertCommit reverts the provided commit against "ourCommit"
// The returned index contains the result of the revert and should be freed
func (r *Repository) RevertCommit(revertCommit *Commit, ourCommit *Commit, mainline uint, mergeOptions *MergeOptions) (*Index, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cOpts := populateMergeOptions(&C.git_merge_options{}, mergeOptions)
defer freeMergeOptions(cOpts)
var index *C.git_index
ecode := C.git_revert_commit(&index, r.ptr, revertCommit.cast_ptr, ourCommit.cast_ptr, C.uint(mainline), cOpts)
runtime.KeepAlive(revertCommit)
runtime.KeepAlive(ourCommit)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return newIndexFromC(index, r), nil
}

View File

@ -1,76 +0,0 @@
package git
import (
"testing"
)
const (
expectedRevertedReadmeContents = "foo\n"
)
func TestRevert(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
commitID, _ := updateReadme(t, repo, content)
commit, err := repo.LookupCommit(commitID)
checkFatal(t, err)
revertOptions, err := DefaultRevertOptions()
checkFatal(t, err)
err = repo.Revert(commit, &revertOptions)
checkFatal(t, err)
actualReadmeContents := readReadme(t, repo)
if actualReadmeContents != expectedRevertedReadmeContents {
t.Fatalf(`README has incorrect contents after revert. Expected: "%v", Actual: "%v"`,
expectedRevertedReadmeContents, actualReadmeContents)
}
state := repo.State()
if state != RepositoryStateRevert {
t.Fatalf("Incorrect repository state. Expected: %v, Actual: %v", RepositoryStateRevert, state)
}
err = repo.StateCleanup()
checkFatal(t, err)
state = repo.State()
if state != RepositoryStateNone {
t.Fatalf("Incorrect repository state. Expected: %v, Actual: %v", RepositoryStateNone, state)
}
}
func TestRevertCommit(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
commitID, _ := updateReadme(t, repo, content)
commit, err := repo.LookupCommit(commitID)
checkFatal(t, err)
revertOptions, err := DefaultRevertOptions()
checkFatal(t, err)
index, err := repo.RevertCommit(commit, commit, 0, &revertOptions.MergeOptions)
checkFatal(t, err)
defer index.Free()
err = repo.CheckoutIndex(index, &revertOptions.CheckoutOptions)
checkFatal(t, err)
actualReadmeContents := readReadme(t, repo)
if actualReadmeContents != expectedRevertedReadmeContents {
t.Fatalf(`README has incorrect contents after revert. Expected: "%v", Actual: "%v"`,
expectedRevertedReadmeContents, actualReadmeContents)
}
}

View File

@ -1,114 +0,0 @@
package git
/*
#include <git2.h>
extern void _go_git_revspec_free(git_revspec *revspec);
*/
import "C"
import (
"runtime"
"unsafe"
)
type RevparseFlag int
const (
RevparseSingle RevparseFlag = C.GIT_REVPARSE_SINGLE
RevparseRange RevparseFlag = C.GIT_REVPARSE_RANGE
RevparseMergeBase RevparseFlag = C.GIT_REVPARSE_MERGE_BASE
)
type Revspec struct {
doNotCompare
to *Object
from *Object
flags RevparseFlag
}
func (rs *Revspec) To() *Object {
return rs.to
}
func (rs *Revspec) From() *Object {
return rs.from
}
func (rs *Revspec) Flags() RevparseFlag {
return rs.flags
}
func newRevspecFromC(ptr *C.git_revspec, repo *Repository) *Revspec {
var to *Object
var from *Object
if ptr.to != nil {
to = allocObject(ptr.to, repo)
}
if ptr.from != nil {
from = allocObject(ptr.from, repo)
}
return &Revspec{
to: to,
from: from,
flags: RevparseFlag(ptr.flags),
}
}
func (r *Repository) Revparse(spec string) (*Revspec, error) {
cspec := C.CString(spec)
defer C.free(unsafe.Pointer(cspec))
var crevspec C.git_revspec
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_revparse(&crevspec, r.ptr, cspec)
if ecode != 0 {
return nil, MakeGitError(ecode)
}
return newRevspecFromC(&crevspec, r), nil
}
func (v *Repository) RevparseSingle(spec string) (*Object, error) {
cspec := C.CString(spec)
defer C.free(unsafe.Pointer(cspec))
var ptr *C.git_object
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_revparse_single(&ptr, v.ptr, cspec)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
return allocObject(ptr, v), nil
}
func (r *Repository) RevparseExt(spec string) (*Object, *Reference, error) {
cspec := C.CString(spec)
defer C.free(unsafe.Pointer(cspec))
var obj *C.git_object
var ref *C.git_reference
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_revparse_ext(&obj, &ref, r.ptr, cspec)
if ecode != 0 {
return nil, nil, MakeGitError(ecode)
}
if ref == nil {
return allocObject(obj, r), nil, nil
}
return allocObject(obj, r), newReferenceFromC(ref, r), nil
}

View File

@ -1,60 +0,0 @@
package git
import (
"testing"
)
func TestRevparse(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId, _ := seedTestRepo(t, repo)
revSpec, err := repo.Revparse("HEAD")
checkFatal(t, err)
checkObject(t, revSpec.From(), commitId)
}
func TestRevparseSingle(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId, _ := seedTestRepo(t, repo)
obj, err := repo.RevparseSingle("HEAD")
checkFatal(t, err)
checkObject(t, obj, commitId)
}
func TestRevparseExt(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
_, treeId := seedTestRepo(t, repo)
ref, err := repo.References.Create("refs/heads/master", treeId, true, "")
checkFatal(t, err)
obj, ref, err := repo.RevparseExt("master")
checkFatal(t, err)
checkObject(t, obj, treeId)
if ref == nil {
t.Fatalf("bad reference")
}
}
func checkObject(t *testing.T, obj *Object, id *Oid) {
if obj == nil {
t.Fatalf("bad object")
}
if !obj.Id().Equal(id) {
t.Fatalf("bad object, expected %s, got %s", id.String(), obj.Id().String())
}
}

View File

@ -1,5 +0,0 @@
#!/bin/sh
set -e
exec "$(dirname "$0")/build-libgit2.sh" --dynamic

View File

@ -1,5 +0,0 @@
#!/bin/sh
set -e
exec "$(dirname "$0")/build-libgit2.sh" --static

View File

@ -1,90 +0,0 @@
#!/bin/sh
# Since CMake cannot build the static and dynamic libraries in the same
# directory, this script helps build both static and dynamic versions of it and
# have the common flags in one place instead of split between two places.
set -e
usage() {
echo "Usage: $0 <--dynamic|--static> [--system]">&2
exit 1
}
if [ "$#" -eq "0" ]; then
usage
fi
ROOT=${ROOT-"$(cd "$(dirname "$0")/.." && echo "${PWD}")"}
VENDORED_PATH=${VENDORED_PATH-"${ROOT}/vendor/libgit2"}
BUILD_SYSTEM=OFF
while [ $# -gt 0 ]; do
case "$1" in
--static)
BUILD_PATH="${ROOT}/static-build"
BUILD_SHARED_LIBS=OFF
;;
--dynamic)
BUILD_PATH="${ROOT}/dynamic-build"
BUILD_SHARED_LIBS=ON
;;
--system)
BUILD_SYSTEM=ON
;;
*)
usage
;;
esac
shift
done
if [ -z "${BUILD_SHARED_LIBS}" ]; then
usage
fi
if [ -n "${BUILD_LIBGIT_REF}" ]; then
git -C "${VENDORED_PATH}" checkout "${BUILD_LIBGIT_REF}"
trap "git submodule update --init" EXIT
fi
BUILD_DEPRECATED_HARD="ON"
if [ "${BUILD_SYSTEM}" = "ON" ]; then
BUILD_INSTALL_PREFIX=${SYSTEM_INSTALL_PREFIX-"/usr"}
# Most system-wide installations won't intentionally omit deprecated symbols.
BUILD_DEPRECATED_HARD="OFF"
else
BUILD_INSTALL_PREFIX="${BUILD_PATH}/install"
mkdir -p "${BUILD_PATH}/install/lib"
fi
USE_BUNDLED_ZLIB="ON"
if [ "${USE_CHROMIUM_ZLIB}" = "ON" ]; then
USE_BUNDLED_ZLIB="Chromium"
fi
mkdir -p "${BUILD_PATH}/build" &&
cd "${BUILD_PATH}/build" &&
cmake -DTHREADSAFE=ON \
-DBUILD_CLAR=OFF \
-DBUILD_SHARED_LIBS"=${BUILD_SHARED_LIBS}" \
-DREGEX_BACKEND=builtin \
-DUSE_BUNDLED_ZLIB="${USE_BUNDLED_ZLIB}" \
-DUSE_HTTPS=OFF \
-DUSE_SSH=OFF \
-DCMAKE_C_FLAGS=-fPIC \
-DCMAKE_BUILD_TYPE="RelWithDebInfo" \
-DCMAKE_INSTALL_PREFIX="${BUILD_INSTALL_PREFIX}" \
-DCMAKE_INSTALL_LIBDIR="lib" \
-DDEPRECATE_HARD="${BUILD_DEPRECATE_HARD}" \
"${VENDORED_PATH}"
if which make nproc >/dev/null && [ -f Makefile ]; then
# Make the build parallel if make is available and cmake used Makefiles.
exec make "-j$(nproc --all)" install
else
exec cmake --build . --target install
fi

View File

@ -1,73 +0,0 @@
// +build ignore
package main
import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/printer"
"go/token"
"log"
"os"
"path/filepath"
"strings"
)
var (
fset = token.NewFileSet()
)
func main() {
log.SetFlags(0)
bpkg, err := build.ImportDir(".", 0)
if err != nil {
log.Fatal(err)
}
pkgs, err := parser.ParseDir(fset, bpkg.Dir, func(fi os.FileInfo) bool { return filepath.Ext(fi.Name()) == ".go" }, 0)
if err != nil {
log.Fatal(err)
}
for _, pkg := range pkgs {
if err := checkPkg(pkg); err != nil {
log.Fatal(err)
}
}
if len(pkgs) == 0 {
log.Fatal("No packages to check.")
}
}
var ignoreViolationsInFunc = map[string]bool{
"MakeGitError": true,
"MakeGitError2": true,
}
func checkPkg(pkg *ast.Package) error {
var violations []string
ast.Inspect(pkg, func(node ast.Node) bool {
switch node := node.(type) {
case *ast.FuncDecl:
var b bytes.Buffer
if err := printer.Fprint(&b, fset, node); err != nil {
log.Fatal(err)
}
src := b.String()
if strings.Contains(src, "MakeGitError") && !strings.Contains(src, "runtime.LockOSThread()") && !strings.Contains(src, "defer runtime.UnlockOSThread()") && !ignoreViolationsInFunc[node.Name.Name] {
pos := fset.Position(node.Pos())
violations = append(violations, fmt.Sprintf("%s at %s:%d", node.Name.Name, pos.Filename, pos.Line))
}
}
return true
})
if len(violations) > 0 {
return fmt.Errorf("%d non-thread-locked calls to MakeGitError found. To fix, add the following to each func below that calls MakeGitError, before the cgo call that might produce the error:\n\n\truntime.LockOSThread()\n\tdefer runtime.UnlockOSThread()\n\n%s", len(violations), strings.Join(violations, "\n"))
}
return nil
}

View File

@ -1,22 +0,0 @@
#!/bin/sh
#
# Install libgit2 to go libgit2 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}"
LG2VER="0.24.0"
wget -O libgit2-${LG2VER}.tar.gz https://github.com/libgit2/libgit2/archive/v${LG2VER}.tar.gz
tar -xzvf libgit2-${LG2VER}.tar.gz
cd libgit2-${LG2VER} && mkdir build && cd build
cmake -DTHREADSAFE=ON -DBUILD_CLAR=OFF -DCMAKE_BUILD_TYPE="RelWithDebInfo" .. && make && sudo make install
sudo ldconfig
cd "${TRAVIS_BUILD_DIR}"

View File

@ -1,174 +0,0 @@
package git
/*
#include <git2.h>
int _go_git_opts_get_search_path(int level, git_buf *buf)
{
return git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, buf);
}
int _go_git_opts_set_search_path(int level, const char *path)
{
return git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, level, path);
}
int _go_git_opts_set_size_t(int opt, size_t val)
{
return git_libgit2_opts(opt, val);
}
int _go_git_opts_set_cache_object_limit(git_object_t type, size_t size)
{
return git_libgit2_opts(GIT_OPT_SET_CACHE_OBJECT_LIMIT, type, size);
}
int _go_git_opts_get_size_t(int opt, size_t *val)
{
return git_libgit2_opts(opt, val);
}
int _go_git_opts_get_size_t_size_t(int opt, size_t *val1, size_t *val2)
{
return git_libgit2_opts(opt, val1, val2);
}
*/
import "C"
import (
"runtime"
"unsafe"
)
func SearchPath(level ConfigLevel) (string, error) {
var buf C.git_buf
defer C.git_buf_dispose(&buf)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C._go_git_opts_get_search_path(C.int(level), &buf)
if err < 0 {
return "", MakeGitError(err)
}
return C.GoString(buf.ptr), nil
}
func SetSearchPath(level ConfigLevel, path string) error {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C._go_git_opts_set_search_path(C.int(level), cpath)
if err < 0 {
return MakeGitError(err)
}
return nil
}
func MwindowSize() (int, error) {
return getSizet(C.GIT_OPT_GET_MWINDOW_SIZE)
}
func SetMwindowSize(size int) error {
return setSizet(C.GIT_OPT_SET_MWINDOW_SIZE, size)
}
func MwindowMappedLimit() (int, error) {
return getSizet(C.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT)
}
func SetMwindowMappedLimit(size int) error {
return setSizet(C.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, size)
}
func EnableCaching(enabled bool) error {
if enabled {
return setSizet(C.GIT_OPT_ENABLE_CACHING, 1)
} else {
return setSizet(C.GIT_OPT_ENABLE_CACHING, 0)
}
}
func EnableStrictHashVerification(enabled bool) error {
if enabled {
return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 1)
} else {
return setSizet(C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0)
}
}
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) {
return getSizetSizet(C.GIT_OPT_GET_CACHED_MEMORY)
}
// deprecated: You should use `CachedMemory()` instead.
func GetCachedMemory() (current int, allowed int, err error) {
return CachedMemory()
}
func SetCacheMaxSize(maxSize int) error {
return setSizet(C.GIT_OPT_SET_CACHE_MAX_SIZE, maxSize)
}
func SetCacheObjectLimit(objectType ObjectType, size int) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err := C._go_git_opts_set_cache_object_limit(C.git_object_t(objectType), C.size_t(size))
if err < 0 {
return MakeGitError(err)
}
return nil
}
func getSizet(opt C.int) (int, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var val C.size_t
err := C._go_git_opts_get_size_t(opt, &val)
if err < 0 {
return 0, MakeGitError(err)
}
return int(val), nil
}
func getSizetSizet(opt C.int) (int, int, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var val1, val2 C.size_t
err := C._go_git_opts_get_size_t_size_t(opt, &val1, &val2)
if err < 0 {
return 0, 0, MakeGitError(err)
}
return int(val1), int(val2), nil
}
func setSizet(opt C.int, val int) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cval := C.size_t(val)
err := C._go_git_opts_set_size_t(opt, cval)
if err < 0 {
return MakeGitError(err)
}
return nil
}

View File

@ -1,99 +0,0 @@
package git
import (
"testing"
)
type pathPair struct {
Level ConfigLevel
Path string
}
func TestSearchPath(t *testing.T) {
paths := []pathPair{
pathPair{ConfigLevelSystem, "/tmp/system"},
pathPair{ConfigLevelGlobal, "/tmp/global"},
pathPair{ConfigLevelXDG, "/tmp/xdg"},
}
for _, pair := range paths {
err := SetSearchPath(pair.Level, pair.Path)
checkFatal(t, err)
actual, err := SearchPath(pair.Level)
checkFatal(t, err)
if pair.Path != actual {
t.Fatal("Search paths don't match")
}
}
}
func TestMmapSizes(t *testing.T) {
size := 42 * 1024
err := SetMwindowSize(size)
checkFatal(t, err)
actual, err := MwindowSize()
if size != actual {
t.Fatal("Sizes don't match")
}
err = SetMwindowMappedLimit(size)
checkFatal(t, err)
actual, err = MwindowMappedLimit()
if size != actual {
t.Fatal("Sizes don't match")
}
}
func TestEnableCaching(t *testing.T) {
err := EnableCaching(false)
checkFatal(t, err)
err = EnableCaching(true)
checkFatal(t, err)
}
func TestEnableStrictHashVerification(t *testing.T) {
err := EnableStrictHashVerification(false)
checkFatal(t, err)
err = EnableStrictHashVerification(true)
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) {
current, allowed, err := CachedMemory()
checkFatal(t, err)
if current < 0 {
t.Fatal("current < 0")
}
if allowed < 0 {
t.Fatal("allowed < 0")
}
}
func TestSetCacheMaxSize(t *testing.T) {
err := SetCacheMaxSize(0)
checkFatal(t, err)
err = SetCacheMaxSize(1024 * 1024)
checkFatal(t, err)
// revert to default 256MB
err = SetCacheMaxSize(256 * 1024 * 1024)
checkFatal(t, err)
}

View File

@ -1,78 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"runtime"
"time"
"unsafe"
)
type Signature struct {
Name string
Email string
When time.Time
}
func newSignatureFromC(sig *C.git_signature) *Signature {
if sig == nil {
return nil
}
// git stores minutes, go wants seconds
loc := time.FixedZone("", int(sig.when.offset)*60)
return &Signature{
C.GoString(sig.name),
C.GoString(sig.email),
time.Unix(int64(sig.when.time), 0).In(loc),
}
}
// Offset returns the time zone offset of v.When in minutes, which is what git wants.
func (v *Signature) Offset() int {
_, offset := v.When.Zone()
return offset / 60
}
func (sig *Signature) toC() (*C.git_signature, error) {
if sig == nil {
return nil, nil
}
var out *C.git_signature
runtime.LockOSThread()
defer runtime.UnlockOSThread()
name := C.CString(sig.Name)
defer C.free(unsafe.Pointer(name))
email := C.CString(sig.Email)
defer C.free(unsafe.Pointer(email))
ret := C.git_signature_new(&out, name, email, C.git_time_t(sig.When.Unix()), C.int(sig.Offset()))
if ret < 0 {
return nil, MakeGitError(ret)
}
return out, nil
}
func (repo *Repository) DefaultSignature() (*Signature, error) {
var out *C.git_signature
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cErr := C.git_signature_default(&out, repo.ptr)
runtime.KeepAlive(repo)
if cErr < 0 {
return nil, MakeGitError(cErr)
}
defer C.git_signature_free(out)
return newSignatureFromC(out), nil
}

250
ssh.go
View File

@ -1,250 +0,0 @@
package git
/*
#include <git2.h>
#include <git2/sys/credential.h>
*/
import "C"
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/url"
"runtime"
"strings"
"unsafe"
"golang.org/x/crypto/ssh"
)
// RegisterManagedSSHTransport registers a Go-native implementation of an SSH
// transport that doesn't rely on any system libraries (e.g. libssh2).
//
// If Shutdown or ReInit are called, make sure that the smart transports are
// freed before it.
func RegisterManagedSSHTransport(protocol string) (*RegisteredSmartTransport, error) {
return NewRegisteredSmartTransport(protocol, false, sshSmartSubtransportFactory)
}
func registerManagedSSH() error {
globalRegisteredSmartTransports.Lock()
defer globalRegisteredSmartTransports.Unlock()
for _, protocol := range []string{"ssh", "ssh+git", "git+ssh"} {
if _, ok := globalRegisteredSmartTransports.transports[protocol]; ok {
continue
}
managed, err := newRegisteredSmartTransport(protocol, false, sshSmartSubtransportFactory, true)
if err != nil {
return fmt.Errorf("failed to register transport for %q: %v", protocol, err)
}
globalRegisteredSmartTransports.transports[protocol] = managed
}
return nil
}
func sshSmartSubtransportFactory(remote *Remote, transport *Transport) (SmartSubtransport, error) {
return &sshSmartSubtransport{
transport: transport,
}, nil
}
type sshSmartSubtransport struct {
transport *Transport
lastAction SmartServiceAction
client *ssh.Client
session *ssh.Session
stdin io.WriteCloser
stdout io.Reader
currentStream *sshSmartSubtransportStream
}
func (t *sshSmartSubtransport) Action(urlString string, action SmartServiceAction) (SmartSubtransportStream, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
u, err := url.Parse(urlString)
if err != nil {
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
switch action {
case SmartServiceActionUploadpackLs, SmartServiceActionUploadpack:
if t.currentStream != nil {
if t.lastAction == SmartServiceActionUploadpackLs {
return t.currentStream, nil
}
t.Close()
}
cmd = fmt.Sprintf("git-upload-pack '%s'", uPath)
case SmartServiceActionReceivepackLs, SmartServiceActionReceivepack:
if t.currentStream != nil {
if t.lastAction == SmartServiceActionReceivepackLs {
return t.currentStream, nil
}
t.Close()
}
cmd = fmt.Sprintf("git-receive-pack '%s'", uPath)
default:
return nil, fmt.Errorf("unexpected action: %v", action)
}
cred, err := t.transport.SmartCredentials("", CredentialTypeSSHKey|CredentialTypeSSHMemory)
if err != nil {
return nil, err
}
defer cred.Free()
sshConfig, err := getSSHConfigFromCredential(cred)
if err != nil {
return nil, err
}
sshConfig.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
marshaledKey := key.Marshal()
cert := &Certificate{
Kind: CertificateHostkey,
Hostkey: HostkeyCertificate{
Kind: HostkeySHA1 | HostkeyMD5 | HostkeySHA256 | HostkeyRaw,
HashMD5: md5.Sum(marshaledKey),
HashSHA1: sha1.Sum(marshaledKey),
HashSHA256: sha256.Sum256(marshaledKey),
Hostkey: marshaledKey,
SSHPublicKey: key,
},
}
return t.transport.SmartCertificateCheck(cert, true, hostname)
}
var addr string
if u.Port() != "" {
addr = fmt.Sprintf("%s:%s", u.Hostname(), u.Port())
} else {
addr = fmt.Sprintf("%s:22", u.Hostname())
}
t.client, err = ssh.Dial("tcp", addr, sshConfig)
if err != nil {
return nil, err
}
t.session, err = t.client.NewSession()
if err != nil {
return nil, err
}
t.stdin, err = t.session.StdinPipe()
if err != nil {
return nil, err
}
t.stdout, err = t.session.StdoutPipe()
if err != nil {
return nil, err
}
if err := t.session.Start(cmd); err != nil {
return nil, err
}
t.lastAction = action
t.currentStream = &sshSmartSubtransportStream{
owner: t,
}
return t.currentStream, nil
}
func (t *sshSmartSubtransport) Close() error {
t.currentStream = nil
if t.client != nil {
t.stdin.Close()
t.session.Wait()
t.session.Close()
t.client = nil
}
return nil
}
func (t *sshSmartSubtransport) Free() {
}
type sshSmartSubtransportStream struct {
owner *sshSmartSubtransport
}
func (stream *sshSmartSubtransportStream) Read(buf []byte) (int, error) {
return stream.owner.stdout.Read(buf)
}
func (stream *sshSmartSubtransportStream) Write(buf []byte) (int, error) {
return stream.owner.stdin.Write(buf)
}
func (stream *sshSmartSubtransportStream) Free() {
}
func getSSHConfigFromCredential(cred *Credential) (*ssh.ClientConfig, error) {
switch cred.Type() {
case CredentialTypeSSHCustom:
credSSHCustom := (*C.git_credential_ssh_custom)(unsafe.Pointer(cred.ptr))
data, ok := pointerHandles.Get(credSSHCustom.payload).(*credentialSSHCustomData)
if !ok {
return nil, errors.New("unsupported custom SSH credentials")
}
return &ssh.ClientConfig{
User: C.GoString(credSSHCustom.username),
Auth: []ssh.AuthMethod{ssh.PublicKeys(data.signer)},
}, nil
}
username, _, privatekey, passphrase, err := cred.GetSSHKey()
if err != nil {
return nil, err
}
var pemBytes []byte
if cred.Type() == CredentialTypeSSHMemory {
pemBytes = []byte(privatekey)
} else {
pemBytes, err = ioutil.ReadFile(privatekey)
if err != nil {
return nil, err
}
}
var key ssh.Signer
if passphrase != "" {
key, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(passphrase))
if err != nil {
return nil, err
}
} else {
key, err = ssh.ParsePrivateKey(pemBytes)
if err != nil {
return nil, err
}
}
return &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{ssh.PublicKeys(key)},
}, nil
}

337
stash.go
View File

@ -1,337 +0,0 @@
package git
/*
#include <git2.h>
extern void _go_git_populate_stash_apply_callbacks(git_stash_apply_options *opts);
extern int _go_git_stash_foreach(git_repository *repo, void *payload);
*/
import "C"
import (
"runtime"
"unsafe"
)
// StashFlag are flags that affect the stash save operation.
type StashFlag int
const (
// StashDefault represents no option, default.
StashDefault StashFlag = C.GIT_STASH_DEFAULT
// StashKeepIndex leaves all changes already added to the
// index intact in the working directory.
StashKeepIndex StashFlag = C.GIT_STASH_KEEP_INDEX
// StashIncludeUntracked means all untracked files are also
// stashed and then cleaned up from the working directory.
StashIncludeUntracked StashFlag = C.GIT_STASH_INCLUDE_UNTRACKED
// StashIncludeIgnored means all ignored files are also
// stashed and then cleaned up from the working directory.
StashIncludeIgnored StashFlag = C.GIT_STASH_INCLUDE_IGNORED
)
// StashCollection represents the possible operations that can be
// performed on the collection of stashes for a repository.
type StashCollection struct {
doNotCompare
repo *Repository
}
// Save saves the local modifications to a new stash.
//
// Stasher is the identity of the person performing the stashing.
// Message is the optional description along with the stashed state.
// Flags control the stashing process and are given as bitwise OR.
func (c *StashCollection) Save(
stasher *Signature, message string, flags StashFlag) (*Oid, error) {
oid := new(Oid)
stasherC, err := stasher.toC()
if err != nil {
return nil, err
}
defer C.git_signature_free(stasherC)
messageC := C.CString(message)
defer C.free(unsafe.Pointer(messageC))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_stash_save(
oid.toC(), c.repo.ptr,
stasherC, messageC, C.uint32_t(flags))
runtime.KeepAlive(c)
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
// StashApplyFlag are flags that affect the stash apply operation.
type StashApplyFlag int
const (
// StashApplyDefault is the default.
StashApplyDefault StashApplyFlag = C.GIT_STASH_APPLY_DEFAULT
// StashApplyReinstateIndex will try to reinstate not only the
// working tree's changes, but also the index's changes.
StashApplyReinstateIndex StashApplyFlag = C.GIT_STASH_APPLY_REINSTATE_INDEX
)
// StashApplyProgress are flags describing the progress of the apply operation.
type StashApplyProgress int
const (
// StashApplyProgressNone means loading the stashed data from the object store.
StashApplyProgressNone StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_NONE
// StashApplyProgressLoadingStash means the stored index is being analyzed.
StashApplyProgressLoadingStash StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_LOADING_STASH
// StashApplyProgressAnalyzeIndex means the stored index is being analyzed.
StashApplyProgressAnalyzeIndex StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX
// StashApplyProgressAnalyzeModified means the modified files are being analyzed.
StashApplyProgressAnalyzeModified StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED
// StashApplyProgressAnalyzeUntracked means the untracked and ignored files are being analyzed.
StashApplyProgressAnalyzeUntracked StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED
// StashApplyProgressCheckoutUntracked means the untracked files are being written to disk.
StashApplyProgressCheckoutUntracked StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED
// StashApplyProgressCheckoutModified means the modified files are being written to disk.
StashApplyProgressCheckoutModified StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED
// StashApplyProgressDone means the stash was applied successfully.
StashApplyProgressDone StashApplyProgress = C.GIT_STASH_APPLY_PROGRESS_DONE
)
// StashApplyProgressCallback is the apply operation notification callback.
type StashApplyProgressCallback func(progress StashApplyProgress) error
type stashApplyProgressCallbackData struct {
callback StashApplyProgressCallback
errorTarget *error
}
//export stashApplyProgressCallback
func stashApplyProgressCallback(progress C.git_stash_apply_progress_t, handle unsafe.Pointer) C.int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*stashApplyProgressCallbackData)
if !ok {
panic("could not retrieve data for handle")
}
if data == nil || data.callback == nil {
return C.int(ErrorCodeOK)
}
err := data.callback(StashApplyProgress(progress))
if err != nil {
*data.errorTarget = err
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}
// StashApplyOptions represents options to control the apply operation.
type StashApplyOptions struct {
Flags StashApplyFlag
CheckoutOptions CheckoutOptions // options to use when writing files to the working directory
ProgressCallback StashApplyProgressCallback // optional callback to notify the consumer of application progress
}
// DefaultStashApplyOptions initializes the structure with default values.
func DefaultStashApplyOptions() (StashApplyOptions, error) {
optsC := C.git_stash_apply_options{}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ecode := C.git_stash_apply_options_init(&optsC, C.GIT_STASH_APPLY_OPTIONS_VERSION)
if ecode < 0 {
return StashApplyOptions{}, MakeGitError(ecode)
}
return StashApplyOptions{
Flags: StashApplyFlag(optsC.flags),
CheckoutOptions: checkoutOptionsFromC(&optsC.checkout_options),
}, nil
}
func populateStashApplyOptions(copts *C.git_stash_apply_options, opts *StashApplyOptions, errorTarget *error) *C.git_stash_apply_options {
C.git_stash_apply_options_init(copts, C.GIT_STASH_APPLY_OPTIONS_VERSION)
if opts == nil {
return nil
}
copts.flags = C.uint32_t(opts.Flags)
populateCheckoutOptions(&copts.checkout_options, &opts.CheckoutOptions, errorTarget)
if opts.ProgressCallback != nil {
progressData := &stashApplyProgressCallbackData{
callback: opts.ProgressCallback,
errorTarget: errorTarget,
}
C._go_git_populate_stash_apply_callbacks(copts)
copts.progress_payload = pointerHandles.Track(progressData)
}
return copts
}
func freeStashApplyOptions(copts *C.git_stash_apply_options) {
if copts == nil {
return
}
if copts.progress_payload != nil {
pointerHandles.Untrack(copts.progress_payload)
}
freeCheckoutOptions(&copts.checkout_options)
}
// Apply applies a single stashed state from the stash list.
//
// If local changes in the working directory conflict with changes in the
// stash then ErrorCodeConflict will be returned. In this case, the index
// will always remain unmodified and all files in the working directory will
// remain unmodified. However, if you are restoring untracked files or
// ignored files and there is a conflict when applying the modified files,
// then those files will remain in the working directory.
//
// If passing the StashApplyReinstateIndex flag and there would be conflicts
// when reinstating the index, the function will return ErrorCodeConflict
// and both the working directory and index will be left unmodified.
//
// Note that a minimum checkout strategy of 'CheckoutSafe' is implied.
//
// 'index' is the position within the stash list. 0 points to the most
// recent stashed state.
//
// Returns error code ErrorCodeNotFound if there's no stashed state for the given
// index, error code ErrorCodeConflict if local changes in the working directory
// conflict with changes in the stash, the user returned error from the
// StashApplyProgressCallback, if any, or other error code.
//
// Error codes can be interogated with IsErrorCode(err, ErrorCodeNotFound).
func (c *StashCollection) Apply(index int, opts StashApplyOptions) error {
var err error
optsC := populateStashApplyOptions(&C.git_stash_apply_options{}, &opts, &err)
defer freeStashApplyOptions(optsC)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_stash_apply(c.repo.ptr, C.size_t(index), optsC)
runtime.KeepAlive(c)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// StashCallback is called per entry when interating over all
// the stashed states.
//
// 'index' is the position of the current stash in the stash list,
// 'message' is the message used when creating the stash and 'id'
// is the commit id of the stash.
type StashCallback func(index int, message string, id *Oid) error
type stashCallbackData struct {
callback StashCallback
errorTarget *error
}
//export stashForeachCallback
func stashForeachCallback(index C.size_t, message *C.char, id *C.git_oid, handle unsafe.Pointer) C.int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*stashCallbackData)
if !ok {
panic("could not retrieve data for handle")
}
err := data.callback(int(index), C.GoString(message), newOidFromC(id))
if err != nil {
*data.errorTarget = err
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}
// Foreach loops over all the stashed states and calls the callback
// for each one.
//
// If callback returns an error, this will stop looping.
func (c *StashCollection) Foreach(callback StashCallback) error {
var err error
data := stashCallbackData{
callback: callback,
errorTarget: &err,
}
handle := pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C._go_git_stash_foreach(c.repo.ptr, handle)
runtime.KeepAlive(c)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// Drop removes a single stashed state from the stash list.
//
// 'index' is the position within the stash list. 0 points
// to the most recent stashed state.
//
// Returns error code ErrorCodeNotFound if there's no stashed
// state for the given index.
func (c *StashCollection) Drop(index int) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_stash_drop(c.repo.ptr, C.size_t(index))
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// Pop applies a single stashed state from the stash list
// and removes it from the list if successful.
//
// 'index' is the position within the stash list. 0 points
// to the most recent stashed state.
//
// 'opts' controls how stashes are applied.
//
// Returns error code ErrorCodeNotFound if there's no stashed
// state for the given index.
func (c *StashCollection) Pop(index int, opts StashApplyOptions) error {
var err error
optsC := populateStashApplyOptions(&C.git_stash_apply_options{}, &opts, &err)
defer freeStashApplyOptions(optsC)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_stash_pop(c.repo.ptr, C.size_t(index), optsC)
runtime.KeepAlive(c)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}

View File

@ -1,198 +0,0 @@
package git
import (
"fmt"
"io/ioutil"
"os"
"path"
"reflect"
"runtime"
"testing"
"time"
)
func TestStash(t *testing.T) {
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
prepareStashRepo(t, repo)
sig := &Signature{
Name: "Rand Om Hacker",
Email: "random@hacker.com",
When: time.Now(),
}
stash1, err := repo.Stashes.Save(sig, "First stash", StashDefault)
checkFatal(t, err)
_, err = repo.LookupCommit(stash1)
checkFatal(t, err)
b, err := ioutil.ReadFile(pathInRepo(repo, "README"))
checkFatal(t, err)
if string(b) == "Update README goes to stash\n" {
t.Errorf("README still contains the uncommitted changes")
}
if !fileExistsInRepo(repo, "untracked.txt") {
t.Errorf("untracked.txt doesn't exist in the repo; should be untracked")
}
// Apply: default
opts, err := DefaultStashApplyOptions()
checkFatal(t, err)
err = repo.Stashes.Apply(0, opts)
checkFatal(t, err)
b, err = ioutil.ReadFile(pathInRepo(repo, "README"))
checkFatal(t, err)
if string(b) != "Update README goes to stash\n" {
t.Errorf("README changes aren't here")
}
// Apply: no stash for the given index
err = repo.Stashes.Apply(1, opts)
if !IsErrorCode(err, ErrorCodeNotFound) {
t.Errorf("expecting GIT_ENOTFOUND error code %d, got %v", ErrorCodeNotFound, err)
}
// Apply: callback stopped
opts.ProgressCallback = func(progress StashApplyProgress) error {
if progress == StashApplyProgressCheckoutModified {
return fmt.Errorf("Stop")
}
return nil
}
err = repo.Stashes.Apply(0, opts)
if err.Error() != "Stop" {
t.Errorf("expecting error 'Stop', got %v", err)
}
// Create second stash with ignored files
os.MkdirAll(pathInRepo(repo, "tmp"), os.ModeDir|os.ModePerm)
err = ioutil.WriteFile(pathInRepo(repo, "tmp/ignored.txt"), []byte("Ignore me\n"), 0644)
checkFatal(t, err)
stash2, err := repo.Stashes.Save(sig, "Second stash", StashIncludeIgnored)
checkFatal(t, err)
if fileExistsInRepo(repo, "tmp/ignored.txt") {
t.Errorf("tmp/ignored.txt should not exist anymore in the work dir")
}
// Stash foreach
expected := []stash{
{0, "On master: Second stash", stash2.String()},
{1, "On master: First stash", stash1.String()},
}
checkStashes(t, repo, expected)
// Stash pop
opts, _ = DefaultStashApplyOptions()
err = repo.Stashes.Pop(1, opts)
checkFatal(t, err)
b, err = ioutil.ReadFile(pathInRepo(repo, "README"))
checkFatal(t, err)
if string(b) != "Update README goes to stash\n" {
t.Errorf("README changes aren't here")
}
expected = []stash{
{0, "On master: Second stash", stash2.String()},
}
checkStashes(t, repo, expected)
// Stash drop
err = repo.Stashes.Drop(0)
checkFatal(t, err)
expected = []stash{}
checkStashes(t, repo, expected)
}
type stash struct {
index int
msg string
id string
}
func checkStashes(t *testing.T, repo *Repository, expected []stash) {
var actual []stash
repo.Stashes.Foreach(func(index int, msg string, id *Oid) error {
stash := stash{index, msg, id.String()}
if len(expected) > len(actual) {
if s := expected[len(actual)]; s.id == "" {
stash.id = "" // don't check id
}
}
actual = append(actual, stash)
return nil
})
if len(expected) > 0 && !reflect.DeepEqual(expected, actual) {
// The failure happens at wherever we were called, not here
_, file, line, ok := runtime.Caller(1)
if !ok {
t.Fatalf("Unable to get caller")
}
t.Errorf("%v:%v: expecting %#v\ngot %#v", path.Base(file), line, expected, actual)
}
}
func prepareStashRepo(t *testing.T, repo *Repository) {
seedTestRepo(t, repo)
err := ioutil.WriteFile(pathInRepo(repo, ".gitignore"), []byte("tmp\n"), 0644)
checkFatal(t, err)
sig := &Signature{
Name: "Rand Om Hacker",
Email: "random@hacker.com",
When: time.Now(),
}
idx, err := repo.Index()
checkFatal(t, err)
err = idx.AddByPath(".gitignore")
checkFatal(t, err)
treeID, err := idx.WriteTree()
checkFatal(t, err)
err = idx.Write()
checkFatal(t, err)
currentBranch, err := repo.Head()
checkFatal(t, err)
currentTip, err := repo.LookupCommit(currentBranch.Target())
checkFatal(t, err)
message := "Add .gitignore\n"
tree, err := repo.LookupTree(treeID)
checkFatal(t, err)
_, err = repo.CreateCommit("HEAD", sig, sig, message, tree, currentTip)
checkFatal(t, err)
err = ioutil.WriteFile(pathInRepo(repo, "README"), []byte("Update README goes to stash\n"), 0644)
checkFatal(t, err)
err = ioutil.WriteFile(pathInRepo(repo, "untracked.txt"), []byte("Hello, World\n"), 0644)
checkFatal(t, err)
}
func fileExistsInRepo(repo *Repository, name string) bool {
if _, err := os.Stat(pathInRepo(repo, name)); err != nil {
return false
}
return true
}

194
status.go
View File

@ -1,194 +0,0 @@
package git
/*
#include <git2.h>
*/
import "C"
import (
"errors"
"runtime"
"unsafe"
)
type Status int
const (
StatusCurrent Status = C.GIT_STATUS_CURRENT
StatusIndexNew Status = C.GIT_STATUS_INDEX_NEW
StatusIndexModified Status = C.GIT_STATUS_INDEX_MODIFIED
StatusIndexDeleted Status = C.GIT_STATUS_INDEX_DELETED
StatusIndexRenamed Status = C.GIT_STATUS_INDEX_RENAMED
StatusIndexTypeChange Status = C.GIT_STATUS_INDEX_TYPECHANGE
StatusWtNew Status = C.GIT_STATUS_WT_NEW
StatusWtModified Status = C.GIT_STATUS_WT_MODIFIED
StatusWtDeleted Status = C.GIT_STATUS_WT_DELETED
StatusWtTypeChange Status = C.GIT_STATUS_WT_TYPECHANGE
StatusWtRenamed Status = C.GIT_STATUS_WT_RENAMED
StatusIgnored Status = C.GIT_STATUS_IGNORED
StatusConflicted Status = C.GIT_STATUS_CONFLICTED
)
type StatusEntry struct {
Status Status
HeadToIndex DiffDelta
IndexToWorkdir DiffDelta
}
func statusEntryFromC(statusEntry *C.git_status_entry) StatusEntry {
var headToIndex DiffDelta = DiffDelta{}
var indexToWorkdir DiffDelta = DiffDelta{}
// Based on the libgit2 status example, head_to_index can be null in some cases
if statusEntry.head_to_index != nil {
headToIndex = diffDeltaFromC(statusEntry.head_to_index)
}
if statusEntry.index_to_workdir != nil {
indexToWorkdir = diffDeltaFromC(statusEntry.index_to_workdir)
}
return StatusEntry{
Status: Status(statusEntry.status),
HeadToIndex: headToIndex,
IndexToWorkdir: indexToWorkdir,
}
}
type StatusList struct {
doNotCompare
ptr *C.git_status_list
r *Repository
}
func newStatusListFromC(ptr *C.git_status_list, r *Repository) *StatusList {
if ptr == nil {
return nil
}
statusList := &StatusList{
ptr: ptr,
r: r,
}
runtime.SetFinalizer(statusList, (*StatusList).Free)
return statusList
}
func (statusList *StatusList) Free() {
if statusList.ptr == nil {
return
}
runtime.SetFinalizer(statusList, nil)
C.git_status_list_free(statusList.ptr)
statusList.ptr = nil
}
func (statusList *StatusList) ByIndex(index int) (StatusEntry, error) {
if statusList.ptr == nil {
return StatusEntry{}, ErrInvalid
}
ptr := C.git_status_byindex(statusList.ptr, C.size_t(index))
if ptr == nil {
return StatusEntry{}, errors.New("index out of Bounds")
}
entry := statusEntryFromC(ptr)
runtime.KeepAlive(statusList)
return entry, nil
}
func (statusList *StatusList) EntryCount() (int, error) {
if statusList.ptr == nil {
return -1, ErrInvalid
}
ret := int(C.git_status_list_entrycount(statusList.ptr))
runtime.KeepAlive(statusList)
return ret, nil
}
type StatusOpt int
const (
StatusOptIncludeUntracked StatusOpt = C.GIT_STATUS_OPT_INCLUDE_UNTRACKED
StatusOptIncludeIgnored StatusOpt = C.GIT_STATUS_OPT_INCLUDE_IGNORED
StatusOptIncludeUnmodified StatusOpt = C.GIT_STATUS_OPT_INCLUDE_UNMODIFIED
StatusOptExcludeSubmodules StatusOpt = C.GIT_STATUS_OPT_EXCLUDE_SUBMODULES
StatusOptRecurseUntrackedDirs StatusOpt = C.GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS
StatusOptDisablePathspecMatch StatusOpt = C.GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH
StatusOptRecurseIgnoredDirs StatusOpt = C.GIT_STATUS_OPT_RECURSE_IGNORED_DIRS
StatusOptRenamesHeadToIndex StatusOpt = C.GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
StatusOptRenamesIndexToWorkdir StatusOpt = C.GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR
StatusOptSortCaseSensitively StatusOpt = C.GIT_STATUS_OPT_SORT_CASE_SENSITIVELY
StatusOptSortCaseInsensitively StatusOpt = C.GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY
StatusOptRenamesFromRewrites StatusOpt = C.GIT_STATUS_OPT_RENAMES_FROM_REWRITES
StatusOptNoRefresh StatusOpt = C.GIT_STATUS_OPT_NO_REFRESH
StatusOptUpdateIndex StatusOpt = C.GIT_STATUS_OPT_UPDATE_INDEX
)
type StatusShow int
const (
StatusShowIndexAndWorkdir StatusShow = C.GIT_STATUS_SHOW_INDEX_AND_WORKDIR
StatusShowIndexOnly StatusShow = C.GIT_STATUS_SHOW_INDEX_ONLY
StatusShowWorkdirOnly StatusShow = C.GIT_STATUS_SHOW_WORKDIR_ONLY
)
type StatusOptions struct {
Show StatusShow
Flags StatusOpt
Pathspec []string
}
func (v *Repository) StatusList(opts *StatusOptions) (*StatusList, error) {
var ptr *C.git_status_list
var copts *C.git_status_options
if opts != nil {
cpathspec := C.git_strarray{}
if opts.Pathspec != nil {
cpathspec.count = C.size_t(len(opts.Pathspec))
cpathspec.strings = makeCStringsFromStrings(opts.Pathspec)
defer freeStrarray(&cpathspec)
}
copts = &C.git_status_options{
version: C.GIT_STATUS_OPTIONS_VERSION,
show: C.git_status_show_t(opts.Show),
flags: C.uint(opts.Flags),
pathspec: cpathspec,
}
} else {
copts = &C.git_status_options{}
ret := C.git_status_options_init(copts, C.GIT_STATUS_OPTIONS_VERSION)
if ret < 0 {
return nil, MakeGitError(ret)
}
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_status_list_new(&ptr, v.ptr, copts)
if ret < 0 {
return nil, MakeGitError(ret)
}
return newStatusListFromC(ptr, v), nil
}
func (v *Repository) StatusFile(path string) (Status, error) {
var statusFlags C.uint
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_status_file(&statusFlags, v.ptr, cPath)
runtime.KeepAlive(v)
if ret < 0 {
return 0, MakeGitError(ret)
}
return Status(statusFlags), nil
}

View File

@ -1,91 +0,0 @@
package git
import (
"io/ioutil"
"path"
"testing"
)
func TestStatusFile(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
state := repo.State()
if state != RepositoryStateNone {
t.Fatal("Incorrect repository state: ", state)
}
err := ioutil.WriteFile(path.Join(path.Dir(repo.Workdir()), "hello.txt"), []byte("Hello, World"), 0644)
checkFatal(t, err)
status, err := repo.StatusFile("hello.txt")
checkFatal(t, err)
if status != StatusWtNew {
t.Fatal("Incorrect status flags: ", status)
}
}
func TestStatusList(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
// This commits the test repo README, so it doesn't show up in the status list and there's a head to compare to
seedTestRepo(t, repo)
err := ioutil.WriteFile(path.Join(path.Dir(repo.Workdir()), "hello.txt"), []byte("Hello, World"), 0644)
checkFatal(t, err)
opts := &StatusOptions{}
opts.Show = StatusShowIndexAndWorkdir
opts.Flags = StatusOptIncludeUntracked | StatusOptRenamesHeadToIndex | StatusOptSortCaseSensitively
statusList, err := repo.StatusList(opts)
checkFatal(t, err)
entryCount, err := statusList.EntryCount()
checkFatal(t, err)
if entryCount != 1 {
t.Fatal("Incorrect number of status entries: ", entryCount)
}
entry, err := statusList.ByIndex(0)
checkFatal(t, err)
if entry.Status != StatusWtNew {
t.Fatal("Incorrect status flags: ", entry.Status)
}
if entry.IndexToWorkdir.NewFile.Path != "hello.txt" {
t.Fatal("Incorrect entry path: ", entry.IndexToWorkdir.NewFile.Path)
}
}
func TestStatusNothing(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
opts := &StatusOptions{
Show: StatusShowIndexAndWorkdir,
Flags: StatusOptIncludeUntracked | StatusOptRenamesHeadToIndex | StatusOptSortCaseSensitively,
}
statusList, err := repo.StatusList(opts)
checkFatal(t, err)
entryCount, err := statusList.EntryCount()
checkFatal(t, err)
if entryCount != 0 {
t.Fatal("expected no statuses in empty repo")
}
_, err = statusList.ByIndex(0)
if err == nil {
t.Error("expected error getting status by index")
}
}

View File

@ -1,405 +1,263 @@
package git
/*
#cgo pkg-config: libgit2
#include <git2.h>
#include <git2/errors.h>
extern int _go_git_visit_submodule(git_repository *repo, void *fct);
*/
import "C"
import (
"runtime"
"unsafe"
)
// SubmoduleUpdateOptions
type SubmoduleUpdateOptions struct {
CheckoutOptions CheckoutOptions
FetchOptions FetchOptions
}
// Submodule
type Submodule struct {
doNotCompare
ptr *C.git_submodule
r *Repository
}
func newSubmoduleFromC(ptr *C.git_submodule, r *Repository) *Submodule {
s := &Submodule{ptr: ptr, r: r}
runtime.SetFinalizer(s, (*Submodule).Free)
return s
}
func (sub *Submodule) Free() {
runtime.SetFinalizer(sub, nil)
C.git_submodule_free(sub.ptr)
}
type SubmoduleUpdate int
const (
SubmoduleUpdateCheckout SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_CHECKOUT
SubmoduleUpdateRebase SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_REBASE
SubmoduleUpdateMerge SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_MERGE
SubmoduleUpdateNone SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_NONE
SubmoduleUpdateReset SubmoduleUpdate = C.GIT_SUBMODULE_UPDATE_RESET
SubmoduleUpdateCheckout = C.GIT_SUBMODULE_UPDATE_CHECKOUT
SubmoduleUpdateRebase = C.GIT_SUBMODULE_UPDATE_REBASE
SubmoduleUpdateMerge = C.GIT_SUBMODULE_UPDATE_MERGE
SubmoduleUpdateNone = C.GIT_SUBMODULE_UPDATE_NONE
)
type SubmoduleIgnore int
const (
SubmoduleIgnoreNone SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_NONE
SubmoduleIgnoreUntracked SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_UNTRACKED
SubmoduleIgnoreDirty SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_DIRTY
SubmoduleIgnoreAll SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_ALL
SubmoduleIgnoreReset SubmoduleIgnore = C.GIT_SUBMODULE_IGNORE_RESET
SubmoduleIgnoreNone = C.GIT_SUBMODULE_IGNORE_NONE
SubmoduleIgnoreUntracked = C.GIT_SUBMODULE_IGNORE_UNTRACKED
SubmoduleIgnoreDirty = C.GIT_SUBMODULE_IGNORE_DIRTY
SubmoduleIgnoreAll = C.GIT_SUBMODULE_IGNORE_ALL
)
type SubmoduleStatus int
const (
SubmoduleStatusInHead SubmoduleStatus = C.GIT_SUBMODULE_STATUS_IN_HEAD
SubmoduleStatusInIndex SubmoduleStatus = C.GIT_SUBMODULE_STATUS_IN_INDEX
SubmoduleStatusInConfig SubmoduleStatus = C.GIT_SUBMODULE_STATUS_IN_CONFIG
SubmoduleStatusInWd SubmoduleStatus = C.GIT_SUBMODULE_STATUS_IN_WD
SubmoduleStatusIndexAdded SubmoduleStatus = C.GIT_SUBMODULE_STATUS_INDEX_ADDED
SubmoduleStatusIndexDeleted SubmoduleStatus = C.GIT_SUBMODULE_STATUS_INDEX_DELETED
SubmoduleStatusIndexModified SubmoduleStatus = C.GIT_SUBMODULE_STATUS_INDEX_MODIFIED
SubmoduleStatusWdUninitialized SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_UNINITIALIZED
SubmoduleStatusWdAdded SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_ADDED
SubmoduleStatusWdDeleted SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_DELETED
SubmoduleStatusWdModified SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_MODIFIED
SubmoduleStatusWdIndexModified SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED
SubmoduleStatusWdWdModified SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_WD_MODIFIED
SubmoduleStatusWdUntracked SubmoduleStatus = C.GIT_SUBMODULE_STATUS_WD_UNTRACKED
SubmoduleStatusInIndex = C.GIT_SUBMODULE_STATUS_IN_INDEX
SubmoduleStatusInConfig = C.GIT_SUBMODULE_STATUS_IN_CONFIG
SubmoduleStatusInWd = C.GIT_SUBMODULE_STATUS_IN_WD
SubmoduleStatusIndexAdded = C.GIT_SUBMODULE_STATUS_INDEX_ADDED
SubmoduleStatusIndexDeleted = C.GIT_SUBMODULE_STATUS_INDEX_DELETED
SubmoduleStatusIndexModified = C.GIT_SUBMODULE_STATUS_INDEX_MODIFIED
SubmoduleStatusWdUninitialized = C.GIT_SUBMODULE_STATUS_WD_UNINITIALIZED
SubmoduleStatusWdAdded = C.GIT_SUBMODULE_STATUS_WD_ADDED
SubmoduleStatusWdDeleted = C.GIT_SUBMODULE_STATUS_WD_DELETED
SubmoduleStatusWdModified = C.GIT_SUBMODULE_STATUS_WD_MODIFIED
SubmoduleStatusWdIndexModified = C.GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED
SubmoduleStatusWdWdModified = C.GIT_SUBMODULE_STATUS_WD_WD_MODIFIED
SubmoduleStatusWdUntracked = C.GIT_SUBMODULE_STATUS_WD_UNTRACKED
)
type SubmoduleRecurse int
const (
SubmoduleRecurseNo SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_NO
SubmoduleRecurseYes SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_YES
SubmoduleRecurseOndemand SubmoduleRecurse = C.GIT_SUBMODULE_RECURSE_ONDEMAND
)
type SubmoduleCollection struct {
doNotCompare
repo *Repository
}
func SubmoduleStatusIsUnmodified(status int) bool {
o := SubmoduleStatus(status) & ^(SubmoduleStatusInHead | SubmoduleStatusInIndex |
SubmoduleStatusInConfig | SubmoduleStatusInWd)
return o == 0
}
func (c *SubmoduleCollection) Lookup(name string) (*Submodule, error) {
func (repo *Repository) LookupSubmodule(name string) (*Submodule, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
var ptr *C.git_submodule
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_lookup(&ptr, c.repo.ptr, cname)
sub := new(Submodule)
ret := C.git_submodule_lookup(&sub.ptr, repo.ptr, cname)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newSubmoduleFromC(ptr, c.repo), nil
return sub, nil
}
// SubmoduleCallback is a function that is called for every submodule found in SubmoduleCollection.Foreach.
type SubmoduleCallback func(sub *Submodule, name string) error
type submoduleCallbackData struct {
callback SubmoduleCallback
errorTarget *error
type SubmoduleCbk func(sub *Submodule, name string) int
//export SubmoduleVisitor
func SubmoduleVisitor(csub unsafe.Pointer, name string, cfct unsafe.Pointer) int {
sub := &Submodule{(*C.git_submodule)(csub)}
fct := *(*SubmoduleCbk)(cfct)
return fct(sub, name)
}
//export submoduleCallback
func submoduleCallback(csub unsafe.Pointer, name *C.char, handle unsafe.Pointer) C.int {
sub := &Submodule{ptr: (*C.git_submodule)(csub)}
data, ok := pointerHandles.Get(handle).(submoduleCallbackData)
if !ok {
panic("invalid submodule visitor callback")
}
err := data.callback(sub, C.GoString(name))
if err != nil {
*data.errorTarget = err
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}
func (c *SubmoduleCollection) Foreach(callback SubmoduleCallback) error {
var err error
data := submoduleCallbackData{
callback: callback,
errorTarget: &err,
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
handle := pointerHandles.Track(data)
defer pointerHandles.Untrack(handle)
ret := C._go_git_visit_submodule(c.repo.ptr, handle)
runtime.KeepAlive(c)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
func (repo *Repository) ForeachSubmodule(cbk SubmoduleCbk) error {
ret := C._go_git_visit_submodule(repo.ptr, unsafe.Pointer(&cbk))
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (c *SubmoduleCollection) Add(url, path string, use_git_link bool) (*Submodule, error) {
func (repo *Repository) AddSubmodule(url, path string, use_git_link bool) (*Submodule, error) {
curl := C.CString(url)
defer C.free(unsafe.Pointer(curl))
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_submodule
ret := C.git_submodule_add_setup(&ptr, c.repo.ptr, curl, cpath, cbool(use_git_link))
sub := new(Submodule)
ret := C.git_submodule_add_setup(&sub.ptr, repo.ptr, curl, cpath, cbool(use_git_link))
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newSubmoduleFromC(ptr, c.repo), nil
return sub, nil
}
func (sub *Submodule) FinalizeAdd() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_add_finalize(sub.ptr)
runtime.KeepAlive(sub)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (sub *Submodule) AddToIndex(write_index bool) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_add_to_index(sub.ptr, cbool(write_index))
runtime.KeepAlive(sub)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (sub *Submodule) Save() error {
ret := C.git_submodule_save(sub.ptr)
if ret < 0 {
return LastError()
}
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.GoString(C.git_submodule_name(sub.ptr))
runtime.KeepAlive(sub)
return n
n := C.git_submodule_name(sub.ptr)
return C.GoString(n)
}
func (sub *Submodule) Path() string {
n := C.GoString(C.git_submodule_path(sub.ptr))
runtime.KeepAlive(sub)
return n
n := C.git_submodule_path(sub.ptr)
return C.GoString(n)
}
func (sub *Submodule) Url() string {
n := C.GoString(C.git_submodule_url(sub.ptr))
runtime.KeepAlive(sub)
return n
n := C.git_submodule_url(sub.ptr)
return C.GoString(n)
}
func (c *SubmoduleCollection) SetUrl(submodule, url string) error {
csubmodule := C.CString(submodule)
defer C.free(unsafe.Pointer(csubmodule))
func (sub *Submodule) SetUrl(url string) error {
curl := C.CString(url)
defer C.free(unsafe.Pointer(curl))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_set_url(c.repo.ptr, csubmodule, curl)
runtime.KeepAlive(c)
ret := C.git_submodule_set_url(sub.ptr, curl)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (sub *Submodule) IndexId() *Oid {
var id *Oid
idx := C.git_submodule_index_id(sub.ptr)
if idx != nil {
id = newOidFromC(idx)
if idx == nil {
return nil
}
runtime.KeepAlive(sub)
return id
return newOidFromC(idx)
}
func (sub *Submodule) HeadId() *Oid {
var id *Oid
idx := C.git_submodule_head_id(sub.ptr)
if idx != nil {
id = newOidFromC(idx)
if idx == nil {
return nil
}
runtime.KeepAlive(sub)
return id
return newOidFromC(idx)
}
func (sub *Submodule) WdId() *Oid {
var id *Oid
idx := C.git_submodule_wd_id(sub.ptr)
if idx != nil {
id = newOidFromC(idx)
if idx == nil {
return nil
}
runtime.KeepAlive(sub)
return id
return newOidFromC(idx)
}
func (sub *Submodule) Ignore() SubmoduleIgnore {
o := C.git_submodule_ignore(sub.ptr)
runtime.KeepAlive(sub)
return SubmoduleIgnore(o)
}
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(c.repo.ptr, csubmodule, C.git_submodule_ignore_t(ignore))
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
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 (sub *Submodule) UpdateStrategy() SubmoduleUpdate {
o := C.git_submodule_update_strategy(sub.ptr)
runtime.KeepAlive(sub)
func (sub *Submodule) Update() SubmoduleUpdate {
o := C.git_submodule_update(sub.ptr)
return SubmoduleUpdate(o)
}
func (c *SubmoduleCollection) SetUpdate(submodule string, update SubmoduleUpdate) error {
csubmodule := C.CString(submodule)
defer C.free(unsafe.Pointer(csubmodule))
func (sub *Submodule) SetUpdate(update SubmoduleUpdate) SubmoduleUpdate {
o := C.git_submodule_set_update(sub.ptr, C.git_submodule_update_t(update))
return SubmoduleUpdate(o)
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_set_update(c.repo.ptr, csubmodule, C.git_submodule_update_t(update))
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
func (sub *Submodule) FetchRecurseSubmodules() bool {
if 0 == C.git_submodule_fetch_recurse_submodules(sub.ptr) {
return false
}
return nil
return true
}
func (sub *Submodule) FetchRecurseSubmodules() SubmoduleRecurse {
return SubmoduleRecurse(C.git_submodule_fetch_recurse_submodules(sub.ptr))
}
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(c.repo.ptr, csubmodule, C.git_submodule_recurse_t(recurse))
runtime.KeepAlive(c)
func (sub *Submodule) SetFetchRecurseSubmodules(v bool) error {
ret := C.git_submodule_set_fetch_recurse_submodules(sub.ptr, cbool(v))
if ret < 0 {
return MakeGitError(C.int(ret))
return LastError()
}
return nil
}
func (sub *Submodule) Init(overwrite bool) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_init(sub.ptr, cbool(overwrite))
runtime.KeepAlive(sub)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (sub *Submodule) Sync() error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_sync(sub.ptr)
runtime.KeepAlive(sub)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func (sub *Submodule) Open() (*Repository, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var ptr *C.git_repository
ret := C.git_submodule_open(&ptr, sub.ptr)
runtime.KeepAlive(sub)
repo := new(Repository)
ret := C.git_submodule_open(&repo.ptr, sub.ptr)
if ret < 0 {
return nil, MakeGitError(ret)
return nil, LastError()
}
return newRepositoryFromC(ptr), nil
return repo, nil
}
func (sub *Submodule) Update(init bool, opts *SubmoduleUpdateOptions) error {
var err error
cOpts := populateSubmoduleUpdateOptions(&C.git_submodule_update_options{}, opts, &err)
defer freeSubmoduleUpdateOptions(cOpts)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C.git_submodule_update(sub.ptr, cbool(init), cOpts)
runtime.KeepAlive(sub)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
func (sub *Submodule) Reload() error {
ret := C.git_submodule_reload(sub.ptr)
if ret < 0 {
return MakeGitError(ret)
return LastError()
}
return nil
}
func populateSubmoduleUpdateOptions(copts *C.git_submodule_update_options, opts *SubmoduleUpdateOptions, errorTarget *error) *C.git_submodule_update_options {
C.git_submodule_update_options_init(copts, C.GIT_SUBMODULE_UPDATE_OPTIONS_VERSION)
if opts == nil {
return nil
func (repo *Repository) ReloadAllSubmodules() error {
ret := C.git_submodule_reload_all(repo.ptr)
if ret < 0 {
return LastError()
}
populateCheckoutOptions(&copts.checkout_opts, &opts.CheckoutOptions, errorTarget)
populateFetchOptions(&copts.fetch_opts, &opts.FetchOptions, errorTarget)
return copts
}
func freeSubmoduleUpdateOptions(copts *C.git_submodule_update_options) {
if copts == nil {
return
}
freeCheckoutOptions(&copts.checkout_opts)
freeFetchOptions(&copts.fetch_opts)
return nil
}

View File

@ -1,27 +0,0 @@
package git
import (
"testing"
)
func TestSubmoduleForeach(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
seedTestRepo(t, repo)
_, err := repo.Submodules.Add("http://example.org/submodule", "submodule", true)
checkFatal(t, err)
i := 0
err = repo.Submodules.Foreach(func(sub *Submodule, name string) error {
i++
return nil
})
checkFatal(t, err)
if i != 1 {
t.Fatalf("expected one submodule found but got %d", i)
}
}

247
tag.go
View File

@ -1,247 +0,0 @@
package git
/*
#include <git2.h>
extern int _go_git_tag_foreach(git_repository *repo, void *payload);
*/
import "C"
import (
"runtime"
"unsafe"
)
// Tag
type Tag struct {
doNotCompare
Object
cast_ptr *C.git_tag
}
func (t *Tag) AsObject() *Object {
return &t.Object
}
func (t *Tag) Message() string {
ret := C.GoString(C.git_tag_message(t.cast_ptr))
runtime.KeepAlive(t)
return ret
}
func (t *Tag) Name() string {
ret := C.GoString(C.git_tag_name(t.cast_ptr))
runtime.KeepAlive(t)
return ret
}
func (t *Tag) Tagger() *Signature {
cast_ptr := C.git_tag_tagger(t.cast_ptr)
ret := newSignatureFromC(cast_ptr)
runtime.KeepAlive(t)
return ret
}
func (t *Tag) Target() *Object {
var ptr *C.git_object
ret := C.git_tag_target(&ptr, t.cast_ptr)
runtime.KeepAlive(t)
if ret != 0 {
return nil
}
return allocObject(ptr, t.repo)
}
func (t *Tag) TargetId() *Oid {
ret := newOidFromC(C.git_tag_target_id(t.cast_ptr))
runtime.KeepAlive(t)
return ret
}
func (t *Tag) TargetType() ObjectType {
ret := ObjectType(C.git_tag_target_type(t.cast_ptr))
runtime.KeepAlive(t)
return ret
}
type TagsCollection struct {
doNotCompare
repo *Repository
}
func (c *TagsCollection) Create(name string, obj Objecter, 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)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
o := obj.AsObject()
ret := C.git_tag_create(oid.toC(), c.repo.ptr, cname, o.ptr, taggerSig, cmessage, 0)
runtime.KeepAlive(c)
runtime.KeepAlive(obj)
if ret < 0 {
return nil, MakeGitError(ret)
}
return oid, nil
}
func (c *TagsCollection) Remove(name string) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
ret := C.git_tag_delete(c.repo.ptr, cname)
runtime.KeepAlive(c)
if ret < 0 {
return MakeGitError(ret)
}
return nil
}
// CreateLightweight creates a new lightweight tag pointing to an object
// 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/<name>"). The name of the tag (eg "v1.0.0")
// is queried with ref.Shorthand().
func (c *TagsCollection) CreateLightweight(name string, obj Objecter, force bool) (*Oid, error) {
oid := new(Oid)
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
o := obj.AsObject()
err := C.git_tag_create_lightweight(oid.toC(), c.repo.ptr, cname, o.ptr, cbool(force))
runtime.KeepAlive(c)
runtime.KeepAlive(obj)
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)
runtime.KeepAlive(c)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
defer C.git_strarray_dispose(&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)
runtime.KeepAlive(c)
if ecode < 0 {
return nil, MakeGitError(ecode)
}
defer C.git_strarray_dispose(&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 tagForeachCallbackData struct {
callback TagForeachCallback
errorTarget *error
}
//export tagForeachCallback
func tagForeachCallback(name *C.char, id *C.git_oid, handle unsafe.Pointer) C.int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*tagForeachCallbackData)
if !ok {
panic("could not retrieve tag foreach CB handle")
}
err := data.callback(C.GoString(name), newOidFromC(id))
if err != nil {
*data.errorTarget = err
return C.int(ErrorCodeUser)
}
return C.int(ErrorCodeOK)
}
// Foreach calls the callback for each tag in the repository.
func (c *TagsCollection) Foreach(callback TagForeachCallback) error {
var err error
data := tagForeachCallbackData{
callback: callback,
errorTarget: &err,
}
handle := pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)
runtime.LockOSThread()
defer runtime.UnlockOSThread()
ret := C._go_git_tag_foreach(c.repo.ptr, handle)
runtime.KeepAlive(c)
if ret == C.int(ErrorCodeUser) && err != nil {
return err
}
if ret < 0 {
return MakeGitError(ret)
}
return nil
}

View File

@ -1,205 +0,0 @@
package git
import (
"errors"
"testing"
"time"
)
func TestCreateTag(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)
commitId, _ := seedTestRepo(t, repo)
commit, err := repo.LookupCommit(commitId)
checkFatal(t, err)
tagId := createTestTag(t, repo, commit)
tag, err := repo.LookupTag(tagId)
checkFatal(t, err)
compareStrings(t, "v0.0.0", tag.Name())
compareStrings(t, "This is a tag", tag.Message())
compareStrings(t, commitId.String(), tag.TargetId().String())
}
func TestCreateTagLightweight(t *testing.T) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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) {
t.Parallel()
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)
}
}
func createTestTag(t *testing.T, repo *Repository, commit *Commit) *Oid {
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),
}
tagId, err := repo.Tags.Create("v0.0.0", commit, sig, "This is a tag")
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.Tags.Create(name, commit, sig, message)
checkFatal(t, err)
return tagId
}

View File

@ -1 +0,0 @@
ref: refs/heads/master

View File

@ -1,6 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true
[remote "origin"]
url = https://github.com/libgit2/TestGitRepository

View File

@ -1 +0,0 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@ -1,6 +0,0 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View File

@ -1,8 +0,0 @@
0966a434eb1a025db6b71485ab63a3bfbea520b6 refs/heads/first-merge
49322bb17d3acc9146f98c97d078513228bbf3c0 refs/heads/master
42e4e7c5e507e113ebbb7801b16b52cf867b7ce1 refs/heads/no-parent
d96c4e80345534eccee5ac7b07fc7603b56124cb refs/tags/annotated_tag
c070ad8c08840c8116da865b2d65593a6bb9cd2a refs/tags/annotated_tag^{}
55a1a760df4b86a02094a904dfa511deb5655905 refs/tags/blob
8f50ba15d49353813cc6e20298002c0d17b0a9ee refs/tags/commit_tree
6e0c7bdb9b4ed93212491ee778ca1c65047cab4e refs/tags/nearly-dangling

View File

@ -1,2 +0,0 @@
P pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack

Some files were not shown because too many files have changed in this diff Show More