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()
|
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
|
// 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
|
// file paths. Returns nil + error when the command fails (e.g. not in a git repo
|
||||||
// or the ref doesn't exist).
|
// or the ref doesn't exist).
|
||||||
@@ -232,8 +255,10 @@ Options:
|
|||||||
Print help
|
Print help
|
||||||
|
|
||||||
Note: when none of --branch, --tag, --source, or --var are given, glint
|
Note: when none of --branch, --tag, --source, or --var are given, glint
|
||||||
defaults to --branch main --source push so that rules:if: expressions are
|
detects the current git branch automatically and uses it as the default
|
||||||
always evaluated.
|
(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:
|
Examples:
|
||||||
glint check .gitlab-ci.yml
|
glint check .gitlab-ci.yml
|
||||||
@@ -260,12 +285,6 @@ Examples:
|
|||||||
}
|
}
|
||||||
_ = fs.Parse(args)
|
_ = 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{
|
validFormats := map[string]bool{
|
||||||
"text": true, "json": true, "sarif": true, "junit": true, "github": true,
|
"text": true, "json": true, "sarif": true, "junit": true, "github": true,
|
||||||
}
|
}
|
||||||
@@ -283,6 +302,17 @@ Examples:
|
|||||||
path := fs.Arg(0)
|
path := fs.Arg(0)
|
||||||
rootDir := filepath.Dir(filepath.Clean(path))
|
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
|
// Load project config (.glint.yml), searching from the pipeline directory
|
||||||
// up to the git root.
|
// up to the git root.
|
||||||
glintCfg, cfgErr := config.Load(rootDir)
|
glintCfg, cfgErr := config.Load(rootDir)
|
||||||
@@ -586,8 +616,10 @@ Options:
|
|||||||
Print help
|
Print help
|
||||||
|
|
||||||
Note: when none of --branch, --tag, --source, or --var are given, glint
|
Note: when none of --branch, --tag, --source, or --var are given, glint
|
||||||
defaults to --branch main --source push so that rules:if: expressions are
|
detects the current git branch automatically and uses it as the default
|
||||||
always evaluated.
|
(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:
|
Examples:
|
||||||
glint graph .gitlab-ci.yml
|
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)")
|
changesFrom := fs.String("changes-from", "", "git ref to diff against for rules:changes: evaluation (e.g. HEAD~1, origin/main)")
|
||||||
_ = fs.Parse(args)
|
_ = 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 {
|
if fs.NArg() != 1 {
|
||||||
fs.Usage()
|
fs.Usage()
|
||||||
exit(2)
|
exit(2)
|
||||||
@@ -632,6 +658,17 @@ Examples:
|
|||||||
path := fs.Arg(0)
|
path := fs.Arg(0)
|
||||||
rootDir := filepath.Dir(filepath.Clean(path))
|
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)
|
glintCfg, cfgErr := config.Load(rootDir)
|
||||||
if cfgErr != nil {
|
if cfgErr != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%s: [warning] %s: %v\n", path, config.Filename, cfgErr)
|
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("all-job", "GL042", suppressions) { t.Error("wildcard should suppress") }
|
||||||
if isSuppressed("unknown-job", "GL001", suppressions) { t.Error("unknown job: not suppressed") }
|
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