Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Build_bundled_static.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ package git
#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 git2go supports libgit2 between v1.5.0 and v1.5.0"
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 9 || LIBGIT2_VER_MINOR > 9
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.9.0 and v1.9.x"
#endif
*/
import "C"
4 changes: 2 additions & 2 deletions Build_system_dynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ package git
#cgo CFLAGS: -DLIBGIT2_DYNAMIC
#include <git2.h>

#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 5 || LIBGIT2_VER_MINOR > 5
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.5.0 and v1.5.0"
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 9 || LIBGIT2_VER_MINOR > 9
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.9.0 and v1.9.x"
#endif
*/
import "C"
4 changes: 2 additions & 2 deletions Build_system_static.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ package git
#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 git2go supports libgit2 between v1.5.0 and v1.5.0"
#if LIBGIT2_VER_MAJOR != 1 || LIBGIT2_VER_MINOR < 9 || LIBGIT2_VER_MINOR > 9
# error "Invalid libgit2 version; this git2go supports libgit2 between v1.9.0 and v1.9.x"
#endif
*/
import "C"
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
git2go
======
[![GoDoc](https://godoc.org/github.com/libgit2/git2go?status.svg)](http://godoc.org/github.com/libgit2/git2go/v34) [![Build Status](https://travis-ci.org/libgit2/git2go.svg?branch=main)](https://travis-ci.org/libgit2/git2go)
[![GoDoc](https://godoc.org/github.com/libgit2/git2go?status.svg)](http://godoc.org/github.com/libgit2/git2go/v35) [![Build Status](https://travis-ci.org/libgit2/git2go.svg?branch=main)](https://travis-ci.org/libgit2/git2go)

Go bindings for [libgit2](http://libgit2.github.com/).

Expand All @@ -10,7 +10,7 @@ Due to the fact that Go 1.11 module versions have semantic meaning and don't nec

| libgit2 | git2go |
|---------|---------------|
| main | (will be v35) |
| 1.9 | v35 |
| 1.5 | v34 |
| 1.3 | v33 |
| 1.2 | v32 |
Expand All @@ -20,13 +20,13 @@ Due to the fact that Go 1.11 module versions have semantic meaning and don't nec
| 0.28 | v28 |
| 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.9 installed, you'd import git2go v35 with:

```sh
go get github.com/libgit2/git2go/v34
go get github.com/libgit2/git2go/v35
```
```go
import "github.com/libgit2/git2go/v34"
import "github.com/libgit2/git2go/v35"
```

which will ensure there are no sudden changes to the API.
Expand All @@ -50,7 +50,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

```go
import "github.com/libgit2/git2go/v34"
import "github.com/libgit2/git2go/v35"
```

### Versioned branch, static linking
Expand Down Expand Up @@ -80,7 +80,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

replace github.com/libgit2/git2go/v34 => ../../libgit2/git2go
replace github.com/libgit2/git2go/v35 => ../../libgit2/git2go

Parallelism and network operations
----------------------------------
Expand Down
12 changes: 9 additions & 3 deletions blame.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ type Blame struct {
}

func (blame *Blame) HunkCount() int {
ret := int(C.git_blame_get_hunk_count(blame.ptr))
ret := int(C.git_blame_hunkcount(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))
ptr := C.git_blame_hunk_byindex(blame.ptr, C.size_t(index))
runtime.KeepAlive(blame)
if ptr == nil {
return BlameHunk{}, ErrInvalid
Expand All @@ -108,7 +108,7 @@ func (blame *Blame) HunkByIndex(index int) (BlameHunk, error) {
}

func (blame *Blame) HunkByLine(lineno int) (BlameHunk, error) {
ptr := C.git_blame_get_hunk_byline(blame.ptr, C.size_t(lineno))
ptr := C.git_blame_hunk_byline(blame.ptr, C.size_t(lineno))
runtime.KeepAlive(blame)
if ptr == nil {
return BlameHunk{}, ErrInvalid
Expand Down Expand Up @@ -144,10 +144,13 @@ type BlameHunk struct {
FinalCommitId *Oid
FinalStartLineNumber uint16
FinalSignature *Signature
FinalCommitter *Signature // The committer of final_commit_id (v1.9+)
OrigCommitId *Oid
OrigPath string
OrigStartLineNumber uint16
OrigSignature *Signature
OrigCommitter *Signature // The committer of orig_commit_id (v1.9+)
Summary string // The commit summary (v1.9+)
Boundary bool
}

Expand All @@ -157,10 +160,13 @@ func blameHunkFromC(hunk *C.git_blame_hunk) BlameHunk {
FinalCommitId: newOidFromC(&hunk.final_commit_id),
FinalStartLineNumber: uint16(hunk.final_start_line_number),
FinalSignature: newSignatureFromC(hunk.final_signature),
FinalCommitter: newSignatureFromC(hunk.final_committer),
OrigCommitId: newOidFromC(&hunk.orig_commit_id),
OrigPath: C.GoString(hunk.orig_path),
OrigStartLineNumber: uint16(hunk.orig_start_line_number),
OrigSignature: newSignatureFromC(hunk.orig_signature),
OrigCommitter: newSignatureFromC(hunk.orig_committer),
Summary: C.GoString(hunk.summary),
Boundary: hunk.boundary == 1,
}
}
6 changes: 6 additions & 0 deletions blame_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func TestBlame(t *testing.T) {
OrigPath: "README",
OrigStartLineNumber: 1,
Boundary: true,
Summary: "This is a commit",
}
wantHunk2 := BlameHunk{
LinesInHunk: 2,
Expand All @@ -43,6 +44,7 @@ func TestBlame(t *testing.T) {
OrigPath: "README",
OrigStartLineNumber: 2,
Boundary: false,
Summary: "This is a commit",
}

hunk1, err := blame.HunkByIndex(0)
Expand All @@ -67,6 +69,10 @@ func checkHunk(t *testing.T, label string, hunk, want BlameHunk) {
want.FinalSignature = nil
hunk.OrigSignature = nil
want.OrigSignature = nil
hunk.FinalCommitter = nil
want.FinalCommitter = nil
hunk.OrigCommitter = nil
want.OrigCommitter = nil
if !reflect.DeepEqual(hunk, want) {
t.Fatalf("%s: got hunk %+v, want %+v", label, hunk, want)
}
Expand Down
10 changes: 6 additions & 4 deletions branch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ func TestBranchIterator(t *testing.T) {

b, bt, err := i.Next()
checkFatal(t, err)
if name, _ := b.Name(); name != "master" {
t.Fatalf("expected master")
branchName := defaultBranchName(t, repo)
if name, _ := b.Name(); name != branchName {
t.Fatalf("expected %s, got %s", branchName, name)
} else if bt != BranchLocal {
t.Fatalf("expected BranchLocal, not %v", t)
}
Expand Down Expand Up @@ -57,7 +58,8 @@ func TestBranchIteratorEach(t *testing.T) {
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])
branchName := defaultBranchName(t, repo)
if names[0] != branchName {
t.Fatalf("expect branch %s, but it was %s\n", branchName, names[0])
}
}
6 changes: 4 additions & 2 deletions checkout.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const (
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
CheckoutNone CheckoutStrategy = C.GIT_CHECKOUT_NONE // Do not do a checkout and do not fire callbacks
CheckoutSafe CheckoutStrategy = C.GIT_CHECKOUT_SAFE // Allow safe updates that cannot overwrite uncommitted data (default)
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
Expand All @@ -44,6 +44,8 @@ const (
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
CheckoutDryRun CheckoutStrategy = C.GIT_CHECKOUT_DRY_RUN // Dry run: check for conflicts but don't make changes
CheckoutConflictStyleZdiff3 CheckoutStrategy = C.GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3 // Include common ancestor data in zdiff3 format for conflicts
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)
)
Expand Down
5 changes: 3 additions & 2 deletions clone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ func TestClone(t *testing.T) {
path, err := ioutil.TempDir("", "git2go")
checkFatal(t, err)

ref, err := repo.References.Lookup("refs/heads/master")
branchName := defaultBranchName(t, repo)
ref, err := repo.References.Lookup("refs/heads/" + branchName)
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")
ref2, err := repo2.References.Lookup("refs/heads/" + branchName)
checkFatal(t, err)

if ref.Cmp(ref2) != 0 {
Expand Down
68 changes: 68 additions & 0 deletions commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ package git
#include <git2.h>

extern int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr);

static void _go_git_commit_create_options_set_allow_empty(git_commit_create_options *opts, int allow) {
opts->allow_empty_commit = allow ? 1 : 0;
}
*/
import "C"

Expand Down Expand Up @@ -227,3 +231,67 @@ func (c *Commit) Amend(refname string, author, committer *Signature, message str

return oid, nil
}

// CommitCreateOptions contains options for creating a commit from stage.
type CommitCreateOptions struct {
// If set, allow an empty commit (no changes from parent).
AllowEmptyCommit bool
// The commit author, or nil for the default.
Author *Signature
// The committer, or nil for the default.
Committer *Signature
// Encoding for the commit message; leave empty for default (UTF-8).
MessageEncoding string
}

// CreateCommitFromStage commits the staged changes in the repository.
// This is a near analog to `git commit -m message`.
// By default, empty commits are not allowed.
func (v *Repository) CreateCommitFromStage(message string, opts *CommitCreateOptions) (*Oid, error) {
oid := new(Oid)

cmsg := C.CString(message)
defer C.free(unsafe.Pointer(cmsg))

var copts C.git_commit_create_options
copts.version = C.GIT_COMMIT_CREATE_OPTIONS_VERSION

var authorSig *C.git_signature
var committerSig *C.git_signature

if opts != nil {
if opts.AllowEmptyCommit {
C._go_git_commit_create_options_set_allow_empty(&copts, 1)
}
if opts.Author != nil {
authorSig, _ = opts.Author.toC()
if authorSig != nil {
defer C.git_signature_free(authorSig)
copts.author = authorSig
}
}
if opts.Committer != nil {
committerSig, _ = opts.Committer.toC()
if committerSig != nil {
defer C.git_signature_free(committerSig)
copts.committer = committerSig
}
}
if opts.MessageEncoding != "" {
cenc := C.CString(opts.MessageEncoding)
defer C.free(unsafe.Pointer(cenc))
copts.message_encoding = cenc
}
}

runtime.LockOSThread()
defer runtime.UnlockOSThread()

ret := C.git_commit_create_from_stage(oid.toC(), v.ptr, cmsg, &copts)
runtime.KeepAlive(v)
if ret < 0 {
return nil, MakeGitError(ret)
}

return oid, nil
}
76 changes: 76 additions & 0 deletions commit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package git

import (
"testing"
)

func TestCreateCommitFromStage(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)

// Configure user for the repo
cfg, err := repo.Config()
checkFatal(t, err)
defer cfg.Free()
err = cfg.SetString("user.name", "Test User")
checkFatal(t, err)
err = cfg.SetString("user.email", "test@example.com")
checkFatal(t, err)

// Stage a file
idx, err := repo.Index()
checkFatal(t, err)
err = idx.AddByPath("README")
checkFatal(t, err)
err = idx.Write()
checkFatal(t, err)

// Create commit from stage
oid, err := repo.CreateCommitFromStage("initial commit from stage", nil)
checkFatal(t, err)

if oid == nil || oid.IsZero() {
t.Fatal("expected a valid commit OID")
}

// Verify the commit
commit, err := repo.LookupCommit(oid)
checkFatal(t, err)
defer commit.Free()

if commit.Message() != "initial commit from stage" {
t.Fatalf("expected 'initial commit from stage', got %q", commit.Message())
}
}

func TestCreateCommitFromStageAllowEmpty(t *testing.T) {
t.Parallel()
repo := createTestRepo(t)
defer cleanupTestRepo(t, repo)

cfg, err := repo.Config()
checkFatal(t, err)
defer cfg.Free()
err = cfg.SetString("user.name", "Test User")
checkFatal(t, err)
err = cfg.SetString("user.email", "test@example.com")
checkFatal(t, err)

seedTestRepo(t, repo)

// Try empty commit without AllowEmptyCommit — should fail
_, err = repo.CreateCommitFromStage("empty commit", nil)
if err == nil {
t.Fatal("expected error for empty commit without AllowEmptyCommit")
}

// Try with AllowEmptyCommit
oid, err := repo.CreateCommitFromStage("empty commit", &CommitCreateOptions{
AllowEmptyCommit: true,
})
checkFatal(t, err)
if oid == nil || oid.IsZero() {
t.Fatal("expected a valid commit OID for empty commit")
}
}
Loading