Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: return error exit codes and add windows fixes #39

Merged
merged 8 commits into from
Mar 23, 2024
Merged
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
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ module github.com/mikesmithgh/git-prompt-string
go 1.22.0

require github.com/pelletier/go-toml/v2 v2.1.1

require github.com/buildkite/shellwords v0.0.0-20180315110454-59467a9b8e10
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/buildkite/shellwords v0.0.0-20180315110454-59467a9b8e10 h1:XwHQ5xDtYPdtBbVPyRO6UZoWZe8/mbKUb076f8x7RvI=
github.com/buildkite/shellwords v0.0.0-20180315110454-59467a9b8e10/go.mod h1:gv0DYOzHEsKgo31lTCDGauIg4DTTGn41Bzp+t3wSOlk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
103 changes: 50 additions & 53 deletions integration/gps_test.go
Original file line number Diff line number Diff line change
@@ -1,86 +1,81 @@
package integration

import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
)

func TestGPS(t *testing.T) {
var notFoundMsg string
if runtime.GOOS == "windows" {
notFoundMsg = "The system cannot find the path specified."
} else {
notFoundMsg = "no such file or directory"
}
tests := []struct {
dir string
input []string
expected string
environ []string
err error
}{
{"bare", []string{"--config=NONE"}, "\x1b[90m \ue0a0 BARE:main\x1b[0m", nil},
{"no_upstream", []string{"--config=NONE"}, "\x1b[90m \ue0a0 main\x1b[0m", nil},
{"no_upstream_remote", []string{"--config=NONE"}, "\x1b[90m \ue0a0 main → mikesmithgh/test/main\x1b[0m", nil},
{"git_dir", []string{"--config=NONE"}, "\x1b[90m \ue0a0 GIT_DIR!\x1b[0m", nil},
{"clean", []string{"--config=NONE"}, "\x1b[32m \ue0a0 main\x1b[0m", nil},
{"tag", []string{"--config=NONE"}, "\x1b[90m \ue0a0 (v1.0.0)\x1b[0m", nil},
{"commit", []string{"--config=NONE"}, "\x1b[90m \ue0a0 (24afc95)\x1b[0m", nil},
{"dirty", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main *\x1b[0m", nil},
{"dirty_staged", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main *\x1b[0m", nil},
{"conflict_ahead", []string{"--config=NONE"}, "\x1b[33m \ue0a0 main ↑[1]\x1b[0m", nil},
{"conflict_behind", []string{"--config=NONE"}, "\x1b[33m \ue0a0 main ↓[1]\x1b[0m", nil},
{"conflict_diverged", []string{"--config=NONE"}, "\x1b[33m \ue0a0 main ↕ ↑[1] ↓[1]\x1b[0m", nil},
{"untracked", []string{"--config=NONE"}, "\x1b[35m \ue0a0 main *\x1b[0m", nil},
{"sparse", []string{"--config=NONE"}, "\x1b[32m \ue0a0 main|SPARSE\x1b[0m", nil},
{"sparse_merge_conflict", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|SPARSE|MERGING|CONFLICT *↕ ↑[1] ↓[1]\x1b[0m", nil},
{"bare", []string{"--config=NONE"}, "\x1b[90m \ue0a0 BARE:main\x1b[0m", nil, nil},
{"no_upstream", []string{"--config=NONE"}, "\x1b[90m \ue0a0 main\x1b[0m", nil, nil},
{"no_upstream_remote", []string{"--config=NONE"}, "\x1b[90m \ue0a0 main → mikesmithgh/test/main\x1b[0m", nil, nil},
{"git_dir", []string{"--config=NONE"}, "\x1b[90m \ue0a0 GIT_DIR!\x1b[0m", nil, nil},
{"clean", []string{"--config=NONE"}, "\x1b[32m \ue0a0 main\x1b[0m", nil, nil},
{"tag", []string{"--config=NONE"}, "\x1b[90m \ue0a0 (v1.0.0)\x1b[0m", nil, nil},
{"commit", []string{"--config=NONE"}, "\x1b[90m \ue0a0 (24afc95)\x1b[0m", nil, nil},
{"dirty", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main *\x1b[0m", nil, nil},
{"dirty_staged", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main *\x1b[0m", nil, nil},
{"conflict_ahead", []string{"--config=NONE"}, "\x1b[33m \ue0a0 main ↑[1]\x1b[0m", nil, nil},
{"conflict_behind", []string{"--config=NONE"}, "\x1b[33m \ue0a0 main ↓[1]\x1b[0m", nil, nil},
{"conflict_diverged", []string{"--config=NONE"}, "\x1b[33m \ue0a0 main ↕ ↑[1] ↓[1]\x1b[0m", nil, nil},
{"untracked", []string{"--config=NONE"}, "\x1b[35m \ue0a0 main *\x1b[0m", nil, nil},
{"sparse", []string{"--config=NONE"}, "\x1b[32m \ue0a0 main|SPARSE\x1b[0m", nil, nil},
{"sparse_merge_conflict", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|SPARSE|MERGING|CONFLICT *↕ ↑[1] ↓[1]\x1b[0m", nil, nil},

// rebase merge
{"rebase_i", []string{"--config=NONE"}, "\x1b[34m \ue0a0 main|REBASE-i 1/1\x1b[0m", nil},
{"rebase_m", []string{"--config=NONE"}, "\x1b[34m \ue0a0 main|REBASE-m 1/1\x1b[0m", nil},
{"rebase_i", []string{"--config=NONE"}, "\x1b[34m \ue0a0 main|REBASE-i 1/1\x1b[0m", nil, nil},
{"rebase_m", []string{"--config=NONE"}, "\x1b[34m \ue0a0 main|REBASE-m 1/1\x1b[0m", nil, nil},
// rebase apply
{"am_rebase", []string{"--config=NONE"}, "\x1b[34m \ue0a0 (b69e688)|AM/REBASE 1/1\x1b[0m", nil},
{"am", []string{"--config=NONE"}, "\x1b[34m \ue0a0 (b69e688)|AM 1/1\x1b[0m", nil},
{"rebase", []string{"--config=NONE"}, "\x1b[34m \ue0a0 main|REBASE 1/1\x1b[0m", nil},
{"am_rebase", []string{"--config=NONE"}, "\x1b[34m \ue0a0 (b69e688)|AM/REBASE 1/1\x1b[0m", nil, nil},
{"am", []string{"--config=NONE"}, "\x1b[34m \ue0a0 (b69e688)|AM 1/1\x1b[0m", nil, nil},
{"rebase", []string{"--config=NONE"}, "\x1b[34m \ue0a0 main|REBASE 1/1\x1b[0m", nil, nil},
// merge
{"merge_conflict", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|MERGING|CONFLICT *↕ ↑[1] ↓[1]\x1b[0m", nil},
{"merge", []string{"--config=NONE"}, "\x1b[35m \ue0a0 main|MERGING *↕ ↑[1] ↓[1]\x1b[0m", nil},
{"merge_conflict", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|MERGING|CONFLICT *↕ ↑[1] ↓[1]\x1b[0m", nil, nil},
{"merge", []string{"--config=NONE"}, "\x1b[35m \ue0a0 main|MERGING *↕ ↑[1] ↓[1]\x1b[0m", nil, nil},
// cherry pick
{"cherry_pick_conflict", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|CHERRY-PICKING|CONFLICT *↕ ↑[1] ↓[1]\x1b[0m", nil},
{"cherry_pick", []string{"--config=NONE"}, "\x1b[35m \ue0a0 main|CHERRY-PICKING *↕ ↑[1] ↓[1]\x1b[0m", nil},
{"cherry_pick_conflict", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|CHERRY-PICKING|CONFLICT *↕ ↑[1] ↓[1]\x1b[0m", nil, nil},
{"cherry_pick", []string{"--config=NONE"}, "\x1b[35m \ue0a0 main|CHERRY-PICKING *↕ ↑[1] ↓[1]\x1b[0m", nil, nil},
// revert
{"revert_conflict", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|REVERTING|CONFLICT *↕ ↑[2] ↓[1]\x1b[0m", nil},
{"revert", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|REVERTING *↕ ↑[2] ↓[1]\x1b[0m", nil},
{"revert_conflict", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|REVERTING|CONFLICT *↕ ↑[2] ↓[1]\x1b[0m", nil, nil},
{"revert", []string{"--config=NONE"}, "\x1b[31m \ue0a0 main|REVERTING *↕ ↑[2] ↓[1]\x1b[0m", nil, nil},
// bisect
{"bisect", []string{"--config=NONE"}, "\x1b[34m \ue0a0 main|BISECTING ↓[1]\x1b[0m", nil},
{"bisect", []string{"--config=NONE"}, "\x1b[34m \ue0a0 main|BISECTING ↓[1]\x1b[0m", nil, nil},

// formatting
{"clean", []string{"--config=NONE", "--color-disabled"}, " \ue0a0 main", nil},
{"clean", []string{"--config=NONE", "--color-disabled", "--prompt-prefix= start "}, " start main", nil},
{"clean", []string{"--config=NONE", "--color-disabled", "--prompt-suffix= stop"}, " \ue0a0 main stop", nil},
{"conflict_ahead", []string{"--config=NONE", "--color-disabled", "--ahead-format=ahead by %d"}, " \ue0a0 main ahead by 1", nil},
{"conflict_behind", []string{"--config=NONE", "--color-disabled", "--behind-format=behind by %d"}, " \ue0a0 main behind by 1", nil},
{"conflict_diverged", []string{"--config=NONE", "--color-disabled", "--diverged-format=ahead by %d behind by %d"}, " \ue0a0 main ahead by 1 behind by 1", nil},
{"no_upstream_remote", []string{"--config=NONE", "--color-disabled", "--no-upstream-remote-format= upstream=[repo: %s branch: %s]"}, " \ue0a0 main upstream=[repo: mikesmithgh/test branch: main]", nil},
{"clean", []string{"--config=NONE", "--color-disabled"}, " \ue0a0 main", nil, nil},
{"clean", []string{"--config=NONE", "--color-disabled", "--prompt-prefix= start "}, " start main", nil, nil},
{"clean", []string{"--config=NONE", "--color-disabled", "--prompt-suffix= stop"}, " \ue0a0 main stop", nil, nil},
{"conflict_ahead", []string{"--config=NONE", "--color-disabled", "--ahead-format=ahead by %d"}, " \ue0a0 main ahead by 1", nil, nil},
{"conflict_behind", []string{"--config=NONE", "--color-disabled", "--behind-format=behind by %d"}, " \ue0a0 main behind by 1", nil, nil},
{"conflict_diverged", []string{"--config=NONE", "--color-disabled", "--diverged-format=ahead by %d behind by %d"}, " \ue0a0 main ahead by 1 behind by 1", nil, nil},
{"no_upstream_remote", []string{"--config=NONE", "--color-disabled", "--no-upstream-remote-format= upstream=[repo: %s branch: %s]"}, " \ue0a0 main upstream=[repo: mikesmithgh/test branch: main]", nil, nil},

// color overrides
{"clean", []string{"--config=../configs/color_overrides.toml"}, "\x1b[38;2;230;238;4m \ue0a0 main\x1b[0m", nil},
{"no_upstream", []string{"--config=../configs/color_overrides.toml"}, "\x1b[30m\x1b[47m \ue0a0 main\x1b[0m", nil},
{"dirty", []string{"--config=../configs/color_overrides.toml"}, "\x1b[48;2;179;5;89m \ue0a0 main *\x1b[0m", nil},
{"conflict_ahead", []string{"--config=../configs/color_overrides.toml"}, "\x1b[38;2;252;183;40m \ue0a0 main ↑[1]\x1b[0m", nil},
{"untracked", []string{"--config=../configs/color_overrides.toml"}, "\x1b[38;2;255;0;0m\x1b[48;2;22;242;170m \ue0a0 main *\x1b[0m", nil},
{"bisect", []string{}, "\x1b[48;2;204;204;255m\x1b[35m \ue0a0 main|BISECTING ↓[1]\x1b[0m", []string{"GIT_PROMPT_STRING_CONFIG=../configs/color_overrides.toml"}},
{"clean", []string{"--config=../configs/color_overrides.toml"}, "\x1b[38;2;230;238;4m \ue0a0 main\x1b[0m", nil, nil},
{"no_upstream", []string{"--config=../configs/color_overrides.toml"}, "\x1b[30m\x1b[47m \ue0a0 main\x1b[0m", nil, nil},
{"dirty", []string{"--config=../configs/color_overrides.toml"}, "\x1b[48;2;179;5;89m \ue0a0 main *\x1b[0m", nil, nil},
{"conflict_ahead", []string{"--config=../configs/color_overrides.toml"}, "\x1b[38;2;252;183;40m \ue0a0 main ↑[1]\x1b[0m", nil, nil},
{"untracked", []string{"--config=../configs/color_overrides.toml"}, "\x1b[38;2;255;0;0m\x1b[48;2;22;242;170m \ue0a0 main *\x1b[0m", nil, nil},
{"bisect", []string{}, "\x1b[48;2;204;204;255m\x1b[35m \ue0a0 main|BISECTING ↓[1]\x1b[0m", []string{"GIT_PROMPT_STRING_CONFIG=../configs/color_overrides.toml"}, nil},

// config errors
{"clean", []string{"--config=/fromparam/does/not/exist"}, fmt.Sprintf("\x1b[31m git-prompt-string error(read config): open /fromparam/does/not/exist: %s\x1b[0m", notFoundMsg), nil},
{"configs", []string{}, fmt.Sprintf("\x1b[31m git-prompt-string error(read config): open /fromenvvar/does/not/exist: %s\x1b[0m", notFoundMsg), []string{"GIT_PROMPT_STRING_CONFIG=/fromenvvar/does/not/exist"}},
{"configs", []string{"--config=invalid_syntax.toml"}, "\x1b[31m git-prompt-string error(unmarshal config): toml: expected character =\x1b[0m", nil},
{"configs", []string{}, "\x1b[31m git-prompt-string error(unmarshal config): toml: expected character =\x1b[0m", []string{"GIT_PROMPT_STRING_CONFIG=invalid_syntax.toml"}},
{"clean", []string{"--config=/fromparam/does/not/exist"}, fmt.Sprintf("\x1b[31m git-prompt-string error(read config): \"open /fromparam/does/not/exist: %s\"\x1b[0m", notFoundMsg), nil, errors.New("exit status 1")},
{"configs", []string{}, fmt.Sprintf("\x1b[31m git-prompt-string error(read config): \"open /fromenvvar/does/not/exist: %s\"\x1b[0m", notFoundMsg), []string{"GIT_PROMPT_STRING_CONFIG=/fromenvvar/does/not/exist"}, errors.New("exit status 1")},
{"configs", []string{"--config=invalid_syntax.toml"}, fmt.Sprintf("\x1b[31m git-prompt-string error(unmarshal config): \"toml: expected character %s\"\x1b[0m", escapedEqualSign), nil, errors.New("exit status 1")},
{"configs", []string{}, fmt.Sprintf("\x1b[31m git-prompt-string error(unmarshal config): \"toml: expected character %s\"\x1b[0m", escapedEqualSign), []string{"GIT_PROMPT_STRING_CONFIG=invalid_syntax.toml"}, errors.New("exit status 1")},

{"norepo", []string{"--config=NONE"}, "", nil},
{"norepo", []string{"--config=NONE"}, "", nil, nil},
}

for _, test := range tests {
Expand All @@ -91,8 +86,10 @@ func TestGPS(t *testing.T) {
cmd.Env = append(cmd.Env, test.environ...)
}
result, err := cmd.CombinedOutput()
if err != nil {
if test.err == nil && err != nil {
t.Errorf("Unexpected error: %s", err)
} else if test.err != nil && test.err.Error() != err.Error() {
t.Errorf("Expected error: %s, got: %s", test.err, err)
}
actual := string(result)
if actual != test.expected {
Expand Down
10 changes: 3 additions & 7 deletions integration/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
)

Expand All @@ -22,19 +21,16 @@ func TestMain(m *testing.M) {
}
defer os.RemoveAll(tmpDir)

gps := "git-prompt-string"
if runtime.GOOS == "windows" {
gps += ".exe"
}
builtBinaryPath = filepath.Join(tmpDir, gps)
builtBinaryPath = filepath.Join(tmpDir, git_prompt_string_bin)

cmd := exec.Command("go", "build", "-o", builtBinaryPath, "..")
output, err := cmd.CombinedOutput()
if err != nil {
panic(fmt.Sprintf("failed to build gps: %s, %s", output, err))
}

cmd = exec.Command("cp", "-r", "../testdata", tmpDir)
copyCommand, copyArgs := copyTestDataCmd("..", tmpDir)
cmd = exec.Command(copyCommand, copyArgs...)
err = cmd.Run()
if err != nil {
panic(fmt.Sprintf("failed to copy test data: %s", err))
Expand Down
15 changes: 15 additions & 0 deletions integration/init_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build !windows

package integration

import "path/filepath"

var (
notFoundMsg string = "no such file or directory"
git_prompt_string_bin string = "git-prompt-string"
escapedEqualSign string = "\\="
)

func copyTestDataCmd(src string, dest string) (string, []string) {
return "cp", []string{"-r", filepath.Join(src, "testdata"), dest}
}
13 changes: 13 additions & 0 deletions integration/init_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package integration

import "path/filepath"

var (
notFoundMsg string = "The system cannot find the path specified."
git_prompt_string_bin string = "git-prompt-string.exe"
escapedEqualSign string = "^="
)

func copyTestDataCmd(src string, dest string) (string, []string) {
return "xcopy", []string{"/S", "/E", "/I", filepath.Join(src, "testdata"), filepath.Join(dest, "testdata")}
}
33 changes: 17 additions & 16 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import (
"flag"
"fmt"
"os"
"os/exec"
"path"
"runtime"
"strconv"
"strings"

Expand Down Expand Up @@ -38,6 +38,7 @@ var (
)

func main() {
// TODO: check if git is installed?
cfg := config.GPSConfig{
PromptPrefix: *promptPrefix,
PromptSuffix: *promptSuffix,
Expand Down Expand Up @@ -68,30 +69,26 @@ func main() {
if xdgConfigHome == "" {
home, err := os.UserHomeDir()
if err != nil {
util.ErrMsg("user home", err, 0)
}
if runtime.GOOS == "windows" {
xdgConfigHome = path.Join(home, "AppData", "Local")
} else {
xdgConfigHome = path.Join(home, ".config")
util.ErrMsg("user home", err)
}
xdgConfigHome = path.Join(home, util.XDGConfigPath)
}
gpsConfig = path.Join(xdgConfigHome, "git-prompt-string", "config.toml")
}

if gpsConfig != "NONE" {
gpsConfigRaw, err := os.ReadFile(gpsConfig)
if err != nil && !os.IsNotExist(err) {
util.ErrMsg("read config exists", err, 0)
util.ErrMsg("read config exists", err)
}

if err != nil && (*configPath != "" || gpsConfigEnv != "") {
util.ErrMsg("read config", err, 0)
util.ErrMsg("read config", err)
}

err = toml.Unmarshal(gpsConfigRaw, &cfg)
if err != nil {
util.ErrMsg("unmarshal config", err, 0)
util.ErrMsg("unmarshal config", err)
}
}

Expand All @@ -112,7 +109,7 @@ func main() {
case "color-disabled":
colorDisabled, err := strconv.ParseBool(f.Value.String())
if err != nil {
util.ErrMsg("parse color disabled", err, 0)
util.ErrMsg("parse color disabled", err)
}
cfg.ColorDisabled = colorDisabled
case "color-clean":
Expand Down Expand Up @@ -147,24 +144,28 @@ func main() {

clearColor, err := color.Color("none")
if err != nil {
util.ErrMsg("color none", err, 0)
util.ErrMsg("color none", err)
}

gitRepo, stderr, err := git.RevParse()
if err != nil {
if strings.Contains(string(stderr), "not a git repository") {
switch {
case strings.Contains(err.Error(), exec.ErrNotFound.Error()):
util.ErrMsg("rev parse", err)
case strings.Contains(string(stderr), "not a git repository"):
os.Exit(0)
default:
// allow other errors to pass through, the git repo may not have upstream
}
// allow other errors to pass through, the git repo may not have upstream
}

branchInfo, err := gitRepo.BranchInfo(cfg)
if err != nil {
util.ErrMsg("branch info", err, 0)
util.ErrMsg("branch info", err)
}
branchStatus, promptColor, err := gitRepo.BranchStatus(cfg)
if err != nil {
util.ErrMsg("branch status", err, 0)
util.ErrMsg("branch status", err)
}

fmt.Printf("%s%s%s%s%s%s", promptColor, cfg.PromptPrefix, branchInfo, branchStatus, cfg.PromptSuffix, clearColor)
Expand Down
Loading