Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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