feat(cli): auto-detect git branch as default context
ci / vet, staticcheck, test, build (push) Successful in 2m28s
ci / vet, staticcheck, test, build (push) Successful in 2m28s
When no --branch, --tag, --source, or --var flags are given, glint now runs "git rev-parse --abbrev-ref HEAD" in the pipeline file's directory to determine the current branch. Falls back to "main" when the directory is not inside a git repository or the repo is in detached-HEAD state. This makes implicit context simulation accurate without requiring users to pass --branch on every invocation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+53
-16
@@ -44,6 +44,29 @@ var execCommandOutput = func(name string, args ...string) ([]byte, error) {
|
||||
return exec.Command(name, args...).Output()
|
||||
}
|
||||
|
||||
// gitBranchInDir is a variable so tests can mock branch detection.
|
||||
var gitBranchInDir = func(dir string) ([]byte, error) {
|
||||
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
||||
cmd.Dir = dir
|
||||
return cmd.Output()
|
||||
}
|
||||
|
||||
// detectGitBranch returns the current git branch name by running
|
||||
// "git rev-parse --abbrev-ref HEAD" in dir. Returns "" when dir is not
|
||||
// inside a git repository, when the repo is in detached-HEAD state, or
|
||||
// when git is not available.
|
||||
func detectGitBranch(dir string) string {
|
||||
out, err := gitBranchInDir(dir)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
b := strings.TrimSpace(string(out))
|
||||
if b == "" || b == "HEAD" {
|
||||
return ""
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// gitDiffFiles runs "git diff --name-only <ref>" and returns the list of changed
|
||||
// file paths. Returns nil + error when the command fails (e.g. not in a git repo
|
||||
// or the ref doesn't exist).
|
||||
@@ -232,8 +255,10 @@ Options:
|
||||
Print help
|
||||
|
||||
Note: when none of --branch, --tag, --source, or --var are given, glint
|
||||
defaults to --branch main --source push so that rules:if: expressions are
|
||||
always evaluated.
|
||||
detects the current git branch automatically and uses it as the default
|
||||
(falling back to 'main' when not inside a git repository or in detached-HEAD
|
||||
state). --source defaults to 'push' so that rules:if: expressions are always
|
||||
evaluated.
|
||||
|
||||
Examples:
|
||||
glint check .gitlab-ci.yml
|
||||
@@ -260,12 +285,6 @@ Examples:
|
||||
}
|
||||
_ = fs.Parse(args)
|
||||
|
||||
// Apply implicit defaults only in single-context mode when no flags are given.
|
||||
if len(contexts) == 0 && *branch == "" && *tag == "" && *source == "" && len(vars) == 0 {
|
||||
*branch = "main"
|
||||
*source = "push"
|
||||
}
|
||||
|
||||
validFormats := map[string]bool{
|
||||
"text": true, "json": true, "sarif": true, "junit": true, "github": true,
|
||||
}
|
||||
@@ -283,6 +302,17 @@ Examples:
|
||||
path := fs.Arg(0)
|
||||
rootDir := filepath.Dir(filepath.Clean(path))
|
||||
|
||||
// Apply implicit defaults only in single-context mode when no flags are given.
|
||||
// Prefer the actual git branch of the repository; fall back to "main".
|
||||
if len(contexts) == 0 && *branch == "" && *tag == "" && *source == "" && len(vars) == 0 {
|
||||
if detected := detectGitBranch(rootDir); detected != "" {
|
||||
*branch = detected
|
||||
} else {
|
||||
*branch = "main"
|
||||
}
|
||||
*source = "push"
|
||||
}
|
||||
|
||||
// Load project config (.glint.yml), searching from the pipeline directory
|
||||
// up to the git root.
|
||||
glintCfg, cfgErr := config.Load(rootDir)
|
||||
@@ -586,8 +616,10 @@ Options:
|
||||
Print help
|
||||
|
||||
Note: when none of --branch, --tag, --source, or --var are given, glint
|
||||
defaults to --branch main --source push so that rules:if: expressions are
|
||||
always evaluated.
|
||||
detects the current git branch automatically and uses it as the default
|
||||
(falling back to 'main' when not inside a git repository or in detached-HEAD
|
||||
state). --source defaults to 'push' so that rules:if: expressions are always
|
||||
evaluated.
|
||||
|
||||
Examples:
|
||||
glint graph .gitlab-ci.yml
|
||||
@@ -618,12 +650,6 @@ Examples:
|
||||
changesFrom := fs.String("changes-from", "", "git ref to diff against for rules:changes: evaluation (e.g. HEAD~1, origin/main)")
|
||||
_ = fs.Parse(args)
|
||||
|
||||
// Apply implicit defaults when no context flag is given at all.
|
||||
if *branch == "" && *tag == "" && *source == "" && len(vars) == 0 {
|
||||
*branch = "main"
|
||||
*source = "push"
|
||||
}
|
||||
|
||||
if fs.NArg() != 1 {
|
||||
fs.Usage()
|
||||
exit(2)
|
||||
@@ -632,6 +658,17 @@ Examples:
|
||||
path := fs.Arg(0)
|
||||
rootDir := filepath.Dir(filepath.Clean(path))
|
||||
|
||||
// Apply implicit defaults when no context flag is given at all.
|
||||
// Prefer the actual git branch of the repository; fall back to "main".
|
||||
if *branch == "" && *tag == "" && *source == "" && len(vars) == 0 {
|
||||
if detected := detectGitBranch(rootDir); detected != "" {
|
||||
*branch = detected
|
||||
} else {
|
||||
*branch = "main"
|
||||
}
|
||||
*source = "push"
|
||||
}
|
||||
|
||||
glintCfg, cfgErr := config.Load(rootDir)
|
||||
if cfgErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: [warning] %s: %v\n", path, config.Filename, cfgErr)
|
||||
|
||||
@@ -1250,3 +1250,31 @@ func TestIsSuppressed(t *testing.T) {
|
||||
if !isSuppressed("all-job", "GL042", suppressions) { t.Error("wildcard should suppress") }
|
||||
if isSuppressed("unknown-job", "GL001", suppressions) { t.Error("unknown job: not suppressed") }
|
||||
}
|
||||
|
||||
func TestDetectGitBranch(t *testing.T) {
|
||||
orig := gitBranchInDir
|
||||
t.Cleanup(func() { gitBranchInDir = orig })
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
output string
|
||||
err error
|
||||
want string
|
||||
}{
|
||||
{"normal branch", "main\n", nil, "main"},
|
||||
{"branch with trailing newline", "feature/my-branch\n", nil, "feature/my-branch"},
|
||||
{"detached HEAD", "HEAD\n", nil, ""},
|
||||
{"git error (not a repo)", "", errors.New("exit 128"), ""},
|
||||
{"empty output", "\n", nil, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gitBranchInDir = func(_ string) ([]byte, error) {
|
||||
return []byte(tt.output), tt.err
|
||||
}
|
||||
if got := detectGitBranch("."); got != tt.want {
|
||||
t.Errorf("detectGitBranch() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user