Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
|
16ef893af9 | |
|
56acff247b |
|
@ -1,4 +0,0 @@
|
|||
/static-build/
|
||||
/dynamic-build/
|
||||
|
||||
go.*
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||
|
|
70
Makefile
70
Makefile
|
@ -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" ./...
|
60
README.md
60
README.md
|
@ -1,69 +1,21 @@
|
|||
GO libgit2
|
||||
git2go
|
||||
======
|
||||
[](http://godoc.org/go.wit.com/lib/libgit2) [](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
166
blame.go
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
143
blob.go
|
@ -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
|
||||
}
|
||||
|
|
60
blob_test.go
60
blob_test.go
|
@ -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
264
branch.go
|
@ -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
|
||||
}
|
|
@ -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])
|
||||
}
|
||||
}
|
275
checkout.go
275
checkout.go
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
133
clone.go
|
@ -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))
|
||||
}
|
|
@ -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
245
commit.go
|
@ -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
425
config.go
|
@ -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(¢ry, 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
|
||||
}
|
||||
|
|
119
config_test.go
119
config_test.go
|
@ -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()
|
||||
}
|
298
credentials.go
298
credentials.go
|
@ -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
|
||||
}
|
|
@ -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]]
|
||||
}
|
271
deprecated.go
271
deprecated.go
|
@ -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)
|
||||
}
|
226
describe.go
226
describe.go
|
@ -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
|
||||
}
|
109
describe_test.go
109
describe_test.go
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
685
diff_test.go
685
diff_test.go
|
@ -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
|
||||
}
|
|
@ -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) + ")"
|
||||
}
|
||||
}
|
|
@ -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) + ")"
|
||||
}
|
||||
}
|
|
@ -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) + ")"
|
||||
}
|
||||
}
|
30
features.go
30
features.go
|
@ -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
322
git.go
|
@ -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()
|
||||
}
|
||||
|
|
156
git_test.go
156
git_test.go
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
69
graph.go
69
graph.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
68
handles.go
68
handles.go
|
@ -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
243
http.go
|
@ -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
|
||||
}
|
54
ignore.go
54
ignore.go
|
@ -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
636
index.go
|
@ -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, ¢ry)
|
||||
defer freeCIndexEntry(¢ry)
|
||||
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
err := C.git_index_add(v.ptr, ¢ry)
|
||||
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, ¢ry)
|
||||
defer freeCIndexEntry(¢ry)
|
||||
|
||||
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, ¢ry, 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
|
||||
}
|
||||
|
|
251
index_test.go
251
index_test.go
|
@ -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)
|
||||
}
|
||||
|
|
97
indexer.go
97
indexer.go
|
@ -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)
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
92
mempack.go
92
mempack.go
|
@ -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
|
||||
}
|
|
@ -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
596
merge.go
|
@ -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);
|
254
merge_test.go
254
merge_test.go
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
42
message.go
42
message.go
|
@ -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
|
||||
}
|
|
@ -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
247
note.go
|
@ -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
|
||||
}
|
117
note_test.go
117
note_test.go
|
@ -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
238
object.go
|
@ -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
|
||||
}
|
||||
|
|
139
object_test.go
139
object_test.go
|
@ -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
470
odb.go
|
@ -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)
|
||||
}
|
||||
|
|
235
odb_test.go
235
odb_test.go
|
@ -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)
|
||||
}
|
157
packbuilder.go
157
packbuilder.go
|
@ -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
|
||||
}
|
||||
|
|
99
patch.go
99
patch.go
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
31
push_test.go
31
push_test.go
|
@ -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
485
rebase.go
|
@ -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
|
||||
}
|
602
rebase_test.go
602
rebase_test.go
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
64
refdb.go
64
refdb.go
|
@ -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)
|
||||
}
|
470
reference.go
470
reference.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
149
refspec.go
149
refspec.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
521
remote_test.go
521
remote_test.go
|
@ -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")
|
||||
}
|
||||
}
|
807
repository.go
807
repository.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
49
reset.go
49
reset.go
|
@ -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
|
||||
}
|
|
@ -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
104
revert.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
114
revparse.go
114
revparse.go
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
exec "$(dirname "$0")/build-libgit2.sh" --dynamic
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
exec "$(dirname "$0")/build-libgit2.sh" --static
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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}"
|
174
settings.go
174
settings.go
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
78
signature.go
78
signature.go
|
@ -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
250
ssh.go
|
@ -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
337
stash.go
|
@ -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
|
||||
}
|
198
stash_test.go
198
stash_test.go
|
@ -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
194
status.go
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
360
submodule.go
360
submodule.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
247
tag.go
|
@ -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
|
||||
}
|
205
tag_test.go
205
tag_test.go
|
@ -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
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
ref: refs/heads/master
|
|
@ -1,6 +0,0 @@
|
|||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
[remote "origin"]
|
||||
url = https://github.com/libgit2/TestGitRepository
|
|
@ -1 +0,0 @@
|
|||
Unnamed repository; edit this file 'description' to name the repository.
|
|
@ -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]
|
||||
# *~
|
|
@ -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
|
Binary file not shown.
|
@ -1,2 +0,0 @@
|
|||
P pack-ccace4e169a0858c13d9ae781a91d76fc33769b8.pack
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue