Add worktree support - Bump version of libgit2 #956
|
@ -12,7 +12,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
branch: [ 'release-1.3', 'release-1.2', 'release-1.1', 'release-1.0', 'release-0.28', 'release-0.27' ]
|
branch: [ 'release-1.6', 'release-1.5', 'release-1.3', 'release-1.2', 'release-1.1', 'release-1.0', 'release-0.28', 'release-0.27' ]
|
||||||
|
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
libgit2:
|
libgit2:
|
||||||
- 'v1.5.0'
|
- 'v1.7.0'
|
||||||
name: Go (system-wide, dynamic)
|
name: Go (system-wide, dynamic)
|
||||||
|
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
|
@ -10,8 +10,8 @@ package git
|
||||||
#cgo CFLAGS: -DLIBGIT2_STATIC
|
#cgo CFLAGS: -DLIBGIT2_STATIC
|
||||||
#include <git2.h>
|
#include <git2.h>
|
||||||
|
|
||||||
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 5 || LIBGIT2_VER_MINOR > 5
|
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 7 || LIBGIT2_VER_MINOR > 7
|
||||||
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.5.0 and v1.5.0"
|
# error "Invalid libgit2 version; this git2go supports libgit2 v1.7.x"
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
|
@ -8,8 +8,8 @@ package git
|
||||||
#cgo CFLAGS: -DLIBGIT2_DYNAMIC
|
#cgo CFLAGS: -DLIBGIT2_DYNAMIC
|
||||||
#include <git2.h>
|
#include <git2.h>
|
||||||
|
|
||||||
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 5 || LIBGIT2_VER_MINOR > 5
|
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 7 || LIBGIT2_VER_MINOR > 7
|
||||||
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.5.0 and v1.5.0"
|
# error "Invalid libgit2 version; this git2go supports libgit2 v1.7.x"
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
|
@ -8,8 +8,8 @@ package git
|
||||||
#cgo CFLAGS: -DLIBGIT2_STATIC
|
#cgo CFLAGS: -DLIBGIT2_STATIC
|
||||||
#include <git2.h>
|
#include <git2.h>
|
||||||
|
|
||||||
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 5 || LIBGIT2_VER_MINOR > 5
|
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 7 || LIBGIT2_VER_MINOR > 7
|
||||||
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.5.0 and v1.5.0"
|
# error "Invalid libgit2 version; this git2go supports libgit2 v1.7.x"
|
||||||
#endif
|
#endif
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
14
README.md
14
README.md
|
@ -10,7 +10,9 @@ Due to the fact that Go 1.11 module versions have semantic meaning and don't nec
|
||||||
|
|
||||||
| libgit2 | git2go |
|
| libgit2 | git2go |
|
||||||
|---------|---------------|
|
|---------|---------------|
|
||||||
| main | (will be v35) |
|
| main | (will be v37) |
|
||||||
|
| 1.7 | v36 |
|
||||||
|
| 1.6 | v35 |
|
||||||
| 1.5 | v34 |
|
| 1.5 | v34 |
|
||||||
| 1.3 | v33 |
|
| 1.3 | v33 |
|
||||||
| 1.2 | v32 |
|
| 1.2 | v32 |
|
||||||
|
@ -20,13 +22,13 @@ Due to the fact that Go 1.11 module versions have semantic meaning and don't nec
|
||||||
| 0.28 | v28 |
|
| 0.28 | v28 |
|
||||||
| 0.27 | v27 |
|
| 0.27 | v27 |
|
||||||
|
|
||||||
You can import them in your project with the version's major number as a suffix. For example, if you have libgit2 v1.2 installed, you'd import git2go v34 with:
|
You can import them in your project with the version's major number as a suffix. For example, if you have libgit2 v1.2 installed, you'd import git2go v35 with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go get github.com/libgit2/git2go/v34
|
go get github.com/libgit2/git2go/v36
|
||||||
```
|
```
|
||||||
```go
|
```go
|
||||||
import "github.com/libgit2/git2go/v34"
|
import "github.com/libgit2/git2go/v36"
|
||||||
```
|
```
|
||||||
|
|
||||||
which will ensure there are no sudden changes to the API.
|
which will ensure there are no sudden changes to the API.
|
||||||
|
@ -50,7 +52,7 @@ This project wraps the functionality provided by libgit2. If you're using a vers
|
||||||
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
|
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
|
```go
|
||||||
import "github.com/libgit2/git2go/v34"
|
import "github.com/libgit2/git2go/v36"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Versioned branch, static linking
|
### Versioned branch, static linking
|
||||||
|
@ -80,7 +82,7 @@ In order to let Go pass the correct flags to `pkg-config`, `-tags static` needs
|
||||||
|
|
||||||
One thing to take into account is that since Go expects the `pkg-config` file to be within the same directory where `make install-static` was called, so the `go.mod` file may need to have a [`replace` directive](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) so that the correct setup is achieved. So if `git2go` is checked out at `$GOPATH/src/github.com/libgit2/git2go` and your project at `$GOPATH/src/github.com/my/project`, the `go.mod` file of `github.com/my/project` might need to have a line like
|
One thing to take into account is that since Go expects the `pkg-config` file to be within the same directory where `make install-static` was called, so the `go.mod` file may need to have a [`replace` directive](https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive) so that the correct setup is achieved. So if `git2go` is checked out at `$GOPATH/src/github.com/libgit2/git2go` and your project at `$GOPATH/src/github.com/my/project`, the `go.mod` file of `github.com/my/project` might need to have a line like
|
||||||
|
|
||||||
replace github.com/libgit2/git2go/v34 => ../../libgit2/git2go
|
replace github.com/libgit2/git2go/v36 => ../../libgit2/git2go
|
||||||
|
|
||||||
Parallelism and network operations
|
Parallelism and network operations
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,4 +1,4 @@
|
||||||
module github.com/libgit2/git2go/v34
|
module github.com/libgit2/git2go/v36
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,9 @@ type Repository struct {
|
||||||
// Stashes represents the collection of stashes and can be used to
|
// Stashes represents the collection of stashes and can be used to
|
||||||
// save, apply and iterate over stash states in this repository.
|
// save, apply and iterate over stash states in this repository.
|
||||||
Stashes StashCollection
|
Stashes StashCollection
|
||||||
|
// Worktrees represents the collection of worktrees and can be used to
|
||||||
|
// add, list and remove worktrees for this repository
|
||||||
|
Worktrees WorktreeCollection
|
||||||
|
|
||||||
// weak indicates that a repository is a weak pointer and should not be
|
// weak indicates that a repository is a weak pointer and should not be
|
||||||
// freed.
|
// freed.
|
||||||
|
@ -52,6 +55,7 @@ func newRepositoryFromC(ptr *C.git_repository) *Repository {
|
||||||
repo.Notes.repo = repo
|
repo.Notes.repo = repo
|
||||||
repo.Tags.repo = repo
|
repo.Tags.repo = repo
|
||||||
repo.Stashes.repo = repo
|
repo.Stashes.repo = repo
|
||||||
|
repo.Worktrees.repo = repo
|
||||||
|
|
||||||
runtime.SetFinalizer(repo, (*Repository).Free)
|
runtime.SetFinalizer(repo, (*Repository).Free)
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit fbea439d4b6fc91c6b619d01b85ab3b7746e4c19
|
Subproject commit 3e2baa6d0bfb42f9016e24cba1733a6ae26a8ae6
|
|
@ -0,0 +1,271 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <git2.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WorktreeCollection struct {
|
||||||
|
doNotCompare
|
||||||
|
repo *Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
type Worktree struct {
|
||||||
|
doNotCompare
|
||||||
|
ptr *C.git_worktree
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddWorktreeOptions struct {
|
||||||
|
// Lock the newly created worktree
|
||||||
|
Lock bool
|
||||||
|
// Reference to use for the new worktree
|
||||||
|
Reference *Reference
|
||||||
|
// CheckoutOptions is used for configuring the checkout for the newly created worktree
|
||||||
|
CheckoutOptions CheckoutOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a new working tree for the given repository
|
||||||
|
func (c *WorktreeCollection) Add(name string, path string, options *AddWorktreeOptions) (*Worktree, error) {
|
||||||
|
cName := C.CString(name)
|
||||||
|
defer C.free(unsafe.Pointer(cName))
|
||||||
|
|
||||||
|
cPath := C.CString(path)
|
||||||
|
defer C.free(unsafe.Pointer(cPath))
|
||||||
|
|
||||||
|
var err error
|
||||||
|
cOptions := populateAddWorktreeOptions(&C.git_worktree_add_options{}, options, &err)
|
||||||
|
defer freeAddWorktreeOptions(cOptions)
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
var ptr *C.git_worktree
|
||||||
|
ret := C.git_worktree_add(&ptr, c.repo.ptr, cName, cPath, cOptions)
|
||||||
|
runtime.KeepAlive(c)
|
||||||
|
if options != nil && options.Reference != nil {
|
||||||
|
runtime.KeepAlive(options.Reference)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret == C.int(ErrorCodeUser) && err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if ret < 0 {
|
||||||
|
return nil, MakeGitError(ret)
|
||||||
|
}
|
||||||
|
return newWorktreeFromC(ptr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists names of linked working trees for the given repository
|
||||||
|
func (c *WorktreeCollection) List() ([]string, error) {
|
||||||
|
var strC C.git_strarray
|
||||||
|
defer C.git_strarray_dispose(&strC)
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_worktree_list(&strC, c.repo.ptr)
|
||||||
|
runtime.KeepAlive(c)
|
||||||
|
if ret < 0 {
|
||||||
|
return nil, MakeGitError(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := makeStringsFromCStrings(strC.strings, int(strC.count))
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup gets a working tree by its name for the given repository
|
||||||
|
func (c *WorktreeCollection) Lookup(name string) (*Worktree, error) {
|
||||||
|
cname := C.CString(name)
|
||||||
|
defer C.free(unsafe.Pointer(cname))
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
var ptr *C.git_worktree
|
||||||
|
ret := C.git_worktree_lookup(&ptr, c.repo.ptr, cname)
|
||||||
|
runtime.KeepAlive(c)
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return nil, MakeGitError(ret)
|
||||||
|
} else if ptr == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return newWorktreeFromC(ptr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFromRepository retrieves a worktree for the given repository
|
||||||
|
func (c *WorktreeCollection) OpenFromRepository() (*Worktree, error) {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
var ptr *C.git_worktree
|
||||||
|
ret := C.git_worktree_open_from_repository(&ptr, c.repo.ptr)
|
||||||
|
runtime.KeepAlive(c)
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return nil, MakeGitError(ret)
|
||||||
|
}
|
||||||
|
return newWorktreeFromC(ptr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWorktreeFromC(ptr *C.git_worktree) *Worktree {
|
||||||
|
worktree := &Worktree{ptr: ptr}
|
||||||
|
runtime.SetFinalizer(worktree, (*Worktree).Free)
|
||||||
|
return worktree
|
||||||
|
}
|
||||||
|
|
||||||
|
func freeAddWorktreeOptions(cOptions *C.git_worktree_add_options) {
|
||||||
|
if cOptions == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
freeCheckoutOptions(&cOptions.checkout_options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateAddWorktreeOptions(cOptions *C.git_worktree_add_options, options *AddWorktreeOptions, errorTarget *error) *C.git_worktree_add_options {
|
||||||
|
C.git_worktree_add_options_init(cOptions, C.GIT_WORKTREE_ADD_OPTIONS_VERSION)
|
||||||
|
if options == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
populateCheckoutOptions(&cOptions.checkout_options, &options.CheckoutOptions, errorTarget)
|
||||||
|
cOptions.lock = cbool(options.Lock)
|
||||||
|
if options.Reference != nil {
|
||||||
|
cOptions.ref = options.Reference.ptr
|
||||||
|
}
|
||||||
|
return cOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free a previously allocated worktree
|
||||||
|
func (w *Worktree) Free() {
|
||||||
|
runtime.SetFinalizer(w, nil)
|
||||||
|
C.git_worktree_free(w.ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLocked checks if the given worktree is locked
|
||||||
|
func (w *Worktree) IsLocked() (locked bool, reason string, err error) {
|
||||||
|
buf := C.git_buf{}
|
||||||
|
defer C.git_buf_dispose(&buf)
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_worktree_is_locked(&buf, w.ptr)
|
||||||
|
runtime.KeepAlive(w)
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return false, "", MakeGitError(ret)
|
||||||
|
}
|
||||||
|
return ret != 0, C.GoString(buf.ptr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorktreePruneFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// WorktreePruneValid means prune working tree even if working tree is valid
|
||||||
|
WorktreePruneValid WorktreePruneFlag = C.GIT_WORKTREE_PRUNE_VALID
|
||||||
|
// WorktreePruneLocked means prune working tree even if it is locked
|
||||||
|
WorktreePruneLocked WorktreePruneFlag = C.GIT_WORKTREE_PRUNE_LOCKED
|
||||||
|
// WorktreePruneWorkingTree means prune checked out working tree
|
||||||
|
WorktreePruneWorkingTree WorktreePruneFlag = C.GIT_WORKTREE_PRUNE_WORKING_TREE
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsPrunable checks that the worktree is prunable with the given flags
|
||||||
|
func (w *Worktree) IsPrunable(flags WorktreePruneFlag) (bool, error) {
|
||||||
|
cOptions := C.git_worktree_prune_options{}
|
||||||
|
C.git_worktree_prune_options_init(&cOptions, C.GIT_WORKTREE_PRUNE_OPTIONS_VERSION)
|
||||||
|
cOptions.flags = C.uint32_t(flags)
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_worktree_is_prunable(w.ptr, &cOptions)
|
||||||
|
runtime.KeepAlive(w)
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return false, MakeGitError(ret)
|
||||||
|
}
|
||||||
|
return ret != 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock locks the worktree if not already locked
|
||||||
|
func (w *Worktree) Lock(reason string) error {
|
||||||
|
var cReason *C.char
|
||||||
|
if reason != "" {
|
||||||
|
cReason = C.CString(reason)
|
||||||
|
defer C.free(unsafe.Pointer(cReason))
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_worktree_lock(w.ptr, cReason)
|
||||||
|
runtime.KeepAlive(w)
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return MakeGitError(ret)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name retrieves the name of the worktree
|
||||||
|
func (w *Worktree) Name() string {
|
||||||
|
s := C.GoString(C.git_worktree_name(w.ptr))
|
||||||
|
runtime.KeepAlive(w)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path retrieves the path of the worktree
|
||||||
|
func (w *Worktree) Path() string {
|
||||||
|
s := C.GoString(C.git_worktree_path(w.ptr))
|
||||||
|
runtime.KeepAlive(w)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune the worktree with the provided flags
|
||||||
|
func (w *Worktree) Prune(flags WorktreePruneFlag) error {
|
||||||
|
cOptions := C.git_worktree_prune_options{}
|
||||||
|
C.git_worktree_prune_options_init(&cOptions, C.GIT_WORKTREE_PRUNE_OPTIONS_VERSION)
|
||||||
|
cOptions.flags = C.uint32_t(flags)
|
||||||
|
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_worktree_prune(w.ptr, &cOptions)
|
||||||
|
runtime.KeepAlive(w)
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return MakeGitError(ret)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock a locked worktree
|
||||||
|
func (w *Worktree) Unlock() (notLocked bool, err error) {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_worktree_unlock(w.ptr)
|
||||||
|
runtime.KeepAlive(w)
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return false, MakeGitError(ret)
|
||||||
|
}
|
||||||
|
return ret != 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if the given worktree is valid
|
||||||
|
func (w *Worktree) Validate() error {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
ret := C.git_worktree_validate(w.ptr)
|
||||||
|
runtime.KeepAlive(w)
|
||||||
|
|
||||||
|
if ret < 0 {
|
||||||
|
return MakeGitError(ret)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,225 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertSamePath(t *testing.T, expected string, actual string) {
|
||||||
|
var err error
|
||||||
|
expected, err = filepath.EvalSymlinks(expected)
|
||||||
|
checkFatal(t, err)
|
||||||
|
actual, err = filepath.EvalSymlinks(actual)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("wrong path (expected %s, got %s)", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddWorkspace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
repo := createTestRepo(t)
|
||||||
|
defer cleanupTestRepo(t, repo)
|
||||||
|
seedTestRepo(t, repo)
|
||||||
|
|
||||||
|
worktreeTemporaryPath, err := ioutil.TempDir("", "git2go")
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer func() { checkFatal(t, os.RemoveAll(worktreeTemporaryPath)) }()
|
||||||
|
worktreeName := "testWorktree"
|
||||||
|
worktreePath := filepath.Join(worktreeTemporaryPath, "worktree")
|
||||||
|
|
||||||
|
worktree, err := repo.Worktrees.Add(worktreeName, worktreePath, &AddWorktreeOptions{
|
||||||
|
Lock: true, CheckoutOptions: CheckoutOptions{Strategy: CheckoutForce},
|
||||||
|
})
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if name := worktree.Name(); name != worktreeName {
|
||||||
|
t.Fatalf("wrong worktree name: %s != %s", worktreeName, name)
|
||||||
|
}
|
||||||
|
locked, _, err := worktree.IsLocked()
|
||||||
|
checkFatal(t, err)
|
||||||
|
if locked != true {
|
||||||
|
t.Fatal("worktree isn't locked")
|
||||||
|
}
|
||||||
|
assertSamePath(t, worktreePath, worktree.Path())
|
||||||
|
checkFatal(t, worktree.Validate())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddWorkspaceWithoutOptions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
repo := createTestRepo(t)
|
||||||
|
defer cleanupTestRepo(t, repo)
|
||||||
|
seedTestRepo(t, repo)
|
||||||
|
|
||||||
|
worktreeTemporaryPath, err := ioutil.TempDir("", "git2go")
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer func() { checkFatal(t, os.RemoveAll(worktreeTemporaryPath)) }()
|
||||||
|
worktreeName := "testWorktree"
|
||||||
|
worktreePath := filepath.Join(worktreeTemporaryPath, "worktree")
|
||||||
|
|
||||||
|
worktree, err := repo.Worktrees.Add(worktreeName, worktreePath, nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if name := worktree.Name(); name != worktreeName {
|
||||||
|
t.Fatalf("wrong worktree name: %s != %s", worktreeName, name)
|
||||||
|
}
|
||||||
|
locked, _, err := worktree.IsLocked()
|
||||||
|
checkFatal(t, err)
|
||||||
|
if locked != false {
|
||||||
|
t.Fatal("worktree is locked")
|
||||||
|
}
|
||||||
|
assertSamePath(t, worktreePath, worktree.Path())
|
||||||
|
checkFatal(t, worktree.Validate())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupWorkspace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
repo := createTestRepo(t)
|
||||||
|
defer cleanupTestRepo(t, repo)
|
||||||
|
seedTestRepo(t, repo)
|
||||||
|
|
||||||
|
worktreeTemporaryPath, err := ioutil.TempDir("", "git2go")
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer func() { checkFatal(t, os.RemoveAll(worktreeTemporaryPath)) }()
|
||||||
|
worktreeName := "testWorktree"
|
||||||
|
|
||||||
|
worktree, err := repo.Worktrees.Add(worktreeName, filepath.Join(worktreeTemporaryPath, "worktree"), nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
retrievedWorktree, err := repo.Worktrees.Lookup(worktreeName)
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
assertSamePath(t, worktree.Path(), retrievedWorktree.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListWorkspaces(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
repo := createTestRepo(t)
|
||||||
|
defer cleanupTestRepo(t, repo)
|
||||||
|
seedTestRepo(t, repo)
|
||||||
|
|
||||||
|
worktreeTemporaryPath, err := ioutil.TempDir("", "git2go")
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer func() { checkFatal(t, os.RemoveAll(worktreeTemporaryPath)) }()
|
||||||
|
|
||||||
|
worktreeNames := []string{"worktree1", "worktree2", "worktree3"}
|
||||||
|
for _, name := range worktreeNames {
|
||||||
|
_, err = repo.Worktrees.Add(name, filepath.Join(worktreeTemporaryPath, name), nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
}
|
||||||
|
listedWorktree, err := repo.Worktrees.List()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if len(worktreeNames) != len(listedWorktree) {
|
||||||
|
t.Fatalf("len(worktreeNames) != len(listedWorktree) as %d != %d", len(worktreeNames), len(listedWorktree))
|
||||||
|
}
|
||||||
|
for _, name := range worktreeNames {
|
||||||
|
found := false
|
||||||
|
for _, nameToMatch := range listedWorktree {
|
||||||
|
if name == nameToMatch {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("worktree %s is missing", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenWorkspaceFromRepository(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
repo := createTestRepo(t)
|
||||||
|
defer cleanupTestRepo(t, repo)
|
||||||
|
seedTestRepo(t, repo)
|
||||||
|
|
||||||
|
worktreeTemporaryPath, err := ioutil.TempDir("", "git2go")
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer func() { checkFatal(t, os.RemoveAll(worktreeTemporaryPath)) }()
|
||||||
|
|
||||||
|
worktree, err := repo.Worktrees.Add("testWorktree", filepath.Join(worktreeTemporaryPath, "worktree"), nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
worktreeRepo, err := OpenRepository(worktree.Path())
|
||||||
|
checkFatal(t, err)
|
||||||
|
worktreeFromRepo, err := worktreeRepo.Worktrees.OpenFromRepository()
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
if worktreeFromRepo.Name() != worktree.Name() {
|
||||||
|
t.Fatalf("wrong name (expected %s, got %s)", worktreeFromRepo.Name(), worktree.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorktreeIsPrunable(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
repo := createTestRepo(t)
|
||||||
|
defer cleanupTestRepo(t, repo)
|
||||||
|
seedTestRepo(t, repo)
|
||||||
|
|
||||||
|
worktreeTemporaryPath, err := ioutil.TempDir("", "git2go")
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer func() { checkFatal(t, os.RemoveAll(worktreeTemporaryPath)) }()
|
||||||
|
|
||||||
|
worktree, err := repo.Worktrees.Add("testWorktree", filepath.Join(worktreeTemporaryPath, "worktree"), nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
err = worktree.Lock("test")
|
||||||
|
checkFatal(t, err)
|
||||||
|
|
||||||
|
isPrunableWithoutLockedFlag, err := worktree.IsPrunable(WorktreePruneValid)
|
||||||
|
checkFatal(t, err)
|
||||||
|
if isPrunableWithoutLockedFlag {
|
||||||
|
t.Fatal("worktree shouldn't be prunable without the WorktreePruneLocked flag")
|
||||||
|
}
|
||||||
|
isPrunableWithLockedFlag, err := worktree.IsPrunable(WorktreePruneValid | WorktreePruneLocked)
|
||||||
|
checkFatal(t, err)
|
||||||
|
if !isPrunableWithLockedFlag {
|
||||||
|
t.Fatal("worktree should be prunable with the WorktreePruneLocked flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = worktree.Prune(WorktreePruneValid | WorktreePruneLocked)
|
||||||
|
checkFatal(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWorktreeCanBeLockedAndUnlocked(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
repo := createTestRepo(t)
|
||||||
|
defer cleanupTestRepo(t, repo)
|
||||||
|
seedTestRepo(t, repo)
|
||||||
|
|
||||||
|
worktreeTemporaryPath, err := ioutil.TempDir("", "git2go")
|
||||||
|
checkFatal(t, err)
|
||||||
|
defer func() { checkFatal(t, os.RemoveAll(worktreeTemporaryPath)) }()
|
||||||
|
|
||||||
|
worktree, err := repo.Worktrees.Add("testWorktree", filepath.Join(worktreeTemporaryPath, "worktree"), nil)
|
||||||
|
checkFatal(t, err)
|
||||||
|
notLocked, err := worktree.Unlock()
|
||||||
|
checkFatal(t, err)
|
||||||
|
if !notLocked {
|
||||||
|
t.Fatal("worktree should be unlocked by default")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedReason := "toTestIt"
|
||||||
|
err = worktree.Lock(expectedReason)
|
||||||
|
checkFatal(t, err)
|
||||||
|
isLocked, reason, err := worktree.IsLocked()
|
||||||
|
checkFatal(t, err)
|
||||||
|
if !isLocked {
|
||||||
|
t.Fatal("worktree should be locked after the locking operation")
|
||||||
|
}
|
||||||
|
if expectedReason != reason {
|
||||||
|
t.Fatalf("locked reason doesn't match: %s != %s", expectedReason, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
notLocked, err = worktree.Unlock()
|
||||||
|
checkFatal(t, err)
|
||||||
|
if notLocked {
|
||||||
|
t.Fatal("worktree was lock before so notLocked should be false")
|
||||||
|
}
|
||||||
|
isLocked, _, err = worktree.IsLocked()
|
||||||
|
checkFatal(t, err)
|
||||||
|
if isLocked {
|
||||||
|
t.Fatal("worktree should be unlocked after the Unlock() call")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue