Skip to content

Commit

Permalink
Use Git instead of go-git (#7)
Browse files Browse the repository at this point in the history
This switches out dependence on the go-git module with just using the installed git executable. This drastically reduces the number of dependencies, and hopefully improves compatibility (if git changes their plumbing, go-git might break, breaking this executable).

This also fixes renames getting marked as add+delete, and adds support for type change statuses.
  • Loading branch information
spenserblack authored Feb 17, 2023
1 parent bae878c commit f68a84b
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 485 deletions.
10 changes: 3 additions & 7 deletions cmd/git-lzc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ package main
import (
"fmt"
"os"
"strings"

lazycommit "github.com/spenserblack/git-lazy-commit"
)

func main() {
repo, err := lazycommit.OpenRepo(".")
onError(err)
repo := lazycommit.Repo(".")

noStaged, err := repo.NoStaged()
onError(err)
Expand All @@ -19,12 +17,10 @@ func main() {
onError(repo.StageAll())
}

hash, msg, err := repo.Commit()
out, err := repo.Commit()
onError(err)

msgLines := strings.Split(msg, "\n")

fmt.Printf("[%s] %s\n", hash, msgLines[0])
fmt.Printf("%s", out)
}

func onError(err error) {
Expand Down
76 changes: 29 additions & 47 deletions commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,57 @@ import (
"fmt"
"strings"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"

"github.com/spenserblack/git-lazy-commit/pkg/fileutils"
)

// Commit commits all changes in the repository.
//
// It returns the commit hash and the commit message.
func (r *LazyRepo) Commit() (hash plumbing.Hash, msg string, err error) {
msg, err = r.CommitMsg()
// It returns the output of the commit command.
func (repo Repo) Commit() ([]byte, error) {
msg, err := repo.CommitMsg()
if err != nil {
return
return nil, err
}

hash, err = r.wt.Commit(msg, &git.CommitOptions{})
return
cmd, err := repo.cmd("commit", "-m", msg)
if err != nil {
return nil, err
}
return cmd.Output()
}

// CommitMsg builds a commit message using the tracked files in the repository.
func (r *LazyRepo) CommitMsg() (string, error) {
status, err := r.status()
func (repo Repo) CommitMsg() (string, error) {
statuses, err := repo.Status()
if err != nil {
return "", err
}
for filename, fileStatus := range status {
if fileStatus.Staging == git.Unmodified || fileStatus.Staging == git.Untracked {
delete(status, filename)

// NOTE: Filtering to only statuses that are staged and can be used for the commit message.
commitableStatuses := make([]StatusRecord, 0, len(statuses))
for _, status := range statuses {
if _, ok := statusMap[status.Staged]; ok {
commitableStatuses = append(commitableStatuses, status)
}
}

if len(status) == 0 {
if len(commitableStatuses) == 0 {
return "", errors.New("no tracked files")
}
if len(status) == 1 {
for filename, fileStatus := range status {
return singleFileMsg(filename, fileStatus), nil
}
}
return multiFileMsg(status), nil
}

func singleFileMsg(filename string, fileStatus *git.FileStatus) string {
statusString := ""
switch fileStatus.Staging {
case git.Added:
statusString = "Create"
case git.Deleted:
statusString = "Delete"
case git.Modified:
statusString = "Update"
case git.Renamed:
statusString = "Rename to"
case git.Copied:
statusString = "Copy to"
default:
statusString = "Do something to"
if len(commitableStatuses) == 1 {
status := commitableStatuses[0]
return status.Message(), nil
}

return fmt.Sprintf("%s %s", statusString, filename)
return multiFileMsg(commitableStatuses), nil
}

func multiFileMsg(status git.Status) string {
// MultiFileMsg builds a commit message from multiple files.
func multiFileMsg(statuses []StatusRecord) string {
var builder strings.Builder

filenames := make([]string, 0, len(status))
for name := range status {
filenames = append(filenames, name)
filenames := make([]string, 0, len(statuses))
for _, status := range statuses {
filenames = append(filenames, status.Path)
}

sharedDir := fileutils.SharedDirectory(filenames)
Expand All @@ -84,9 +67,8 @@ func multiFileMsg(status git.Status) string {
}
builder.WriteRune('\n')

for filename, fileStatus := range status {
msgItem := singleFileMsg(filename, fileStatus)
builder.WriteString(fmt.Sprintf("- %s\n", msgItem))
for _, status := range statuses {
builder.WriteString(fmt.Sprintf("- %s\n", status.Message()))
}

return builder.String()
Expand Down
80 changes: 50 additions & 30 deletions commit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,73 @@ package lazycommit
import (
"strings"
"testing"

gitconfig "github.com/go-git/go-git/v5/config"
)

// Tests that a commit message can't be built when there are no staged changes.
func TestBuildCommitMessageNoStaged(t *testing.T) {
func TestBuildCommitMessage(t *testing.T) {
t.Log("Creating a new repo.")
dir := tempRepo(t)
repo, err := OpenRepo(dir)
repo := Repo(dir)

_, err := repo.CommitMsg()
if err == nil && err.Error() != "no tracked files" {
t.Errorf(`Expected "no tracked files", got %v`, err)
}

f := commitFile(t, dir, "test.txt", "test")
defer f.Close()

t.Log(`Modifying test.txt`)
commitFile(t, dir, "test.txt", "")
addFile(t, dir, "test.txt", "different text")

msg, err := repo.CommitMsg()
if err != nil {
t.Fatal(err)
}
_, err = repo.CommitMsg()
if err == nil {
t.Fatal("expected error")
if msg != "Update test.txt" {
t.Errorf(`Expected "Update test.txt", got %v`, msg)
}
}

// Tests that commit commits all files in the worktree.
func TestCommit(t *testing.T) {
dir := tempRepo(t)
updateConfig(t, dir, func(config *gitconfig.Config) {
config.User.Name = "Test User"
config.User.Email = "[email protected]"
})
addFile(t, dir, "test.txt", "test")
t.Log(`Adding a new file`)
addFile(t, dir, "test2.txt", "test")

repo, err := OpenRepo(dir)
msg, err = repo.CommitMsg()
if err != nil {
t.Fatal(err)
}

_, msg, err := repo.Commit()
if err != nil {
t.Fatal(err)
lines := strings.Split(msg, "\n")
if lines[0] != "Update files" {
t.Errorf(`Expected "Update files" in the header, got %v`, lines[0])
}
if lines[1] != "" {
t.Errorf(`Expected an empty line after the header, got %v`, lines[1])
}
body := strings.Join(lines[2:], "\n")
t.Logf("Body:\n %v", body)
for _, want := range []string{"- Update test.txt", "- Create test2.txt"} {
if !strings.Contains(body, want) {
t.Errorf(`Expected %v in the body`, want)
}
}
}

wantHeader := "Update files"
wantBodyLines := []string{"- Create test.txt", "- Create test2.txt"}
// TestBuildCommitMessageWithRename tests that a commit message can be built when a file is renamed.
func TestBuildCommitMessageWithRename(t *testing.T) {
dir := tempRepo(t)
repo := Repo(dir)

if !strings.HasPrefix(msg, wantHeader) {
t.Errorf("expected commit message to start with %q, got %q", wantHeader, msg)
}
f := commitFile(t, dir, "foo.txt", "test")
defer f.Close()

for _, line := range wantBodyLines {
if !strings.Contains(msg, line) {
t.Errorf("expected commit message to contain %q, got %q", line, msg)
}
t.Log(`Renaming test.txt to test2.txt`)
moveFile(t, dir, "foo.txt", "bar.txt")

msg, err := repo.CommitMsg()
if err != nil {
t.Fatal(err)
}
if msg != "Rename foo.txt to bar.txt" {
t.Errorf(`Expected "Rename foo.txt to bar.txt", got %v`, msg)
}
}
25 changes: 1 addition & 24 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,4 @@ module github.com/spenserblack/git-lazy-commit

go 1.20

require (
github.com/go-git/go-billy/v5 v5.4.1
github.com/go-git/go-git/v5 v5.5.2
)

require (
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/pjbgf/sha1cd v0.2.3 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.3.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/sys v0.3.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
require github.com/cli/safeexec v1.0.1
Loading

0 comments on commit f68a84b

Please sign in to comment.