Skip to content

Commit 4261dca

Browse files
committed
fix(attest): treat repo-root at its default as unset, and unify explicit-flag detection
A config or env value for --repo-root marked the flag Changed even when it equaled the "." default, because bindFlags applies it with Flags().Set() regardless of value. That made KOSLI_REPO_ROOT=. or a config-file repo-root: "." hard-fail a CI-defaulted commit instead of warning, contradicting the help text. repoRootExplicit now also requires the value to differ from ".". begin trail kept its own commitExplicit/repoRootExplicit booleans because it doesn't go through addAttestationFlags. commitInfoRequest now takes the flag set directly and derives both from it, so there is one place, not two, that knows how "explicit" is decided. Adds coverage for the KOSLI_COMMIT env route (explicit, hard-fails) and the KOSLI_REPO_ROOT=. case (still warns), and converts a map-based test loop to a slice for deterministic failure output. Addresses review feedback on #1202. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HZK6c41JmmUMAB71ZsDpX
1 parent 7bb14d4 commit 4261dca

3 files changed

Lines changed: 81 additions & 47 deletions

File tree

‎cmd/kosli/attestation.go‎

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,6 @@ type CommonAttestationOptions struct {
6464
commitRequiredFor string
6565
}
6666

67-
func (o *CommonAttestationOptions) flagChanged(name string) bool {
68-
return o.flags != nil && o.flags.Changed(name)
69-
}
70-
7167
func (o *CommonAttestationOptions) run(args []string, payload *CommonAttestationPayload) error {
7268
var err error
7369

@@ -91,12 +87,11 @@ func (o *CommonAttestationOptions) run(args []string, payload *CommonAttestation
9187

9288
if o.commitSHA != "" {
9389
payload.Commit, err = commitInfoRequest{
94-
repoRoot: o.srcRepoRoot,
95-
sha: o.commitSHA,
96-
redacted: o.redactedCommitInfo,
97-
commitExplicit: o.flagChanged("commit"),
98-
repoRootExplicit: o.flagChanged("repo-root"),
99-
requiredFor: o.commitRequiredFor,
90+
repoRoot: o.srcRepoRoot,
91+
sha: o.commitSHA,
92+
redacted: o.redactedCommitInfo,
93+
flags: o.flags,
94+
requiredFor: o.commitRequiredFor,
10095
}.resolve()
10196
if err != nil {
10297
return err
@@ -132,13 +127,32 @@ func (o *CommonAttestationOptions) run(args []string, payload *CommonAttestation
132127
}
133128

134129
type commitInfoRequest struct {
135-
repoRoot string
136-
sha string
137-
redacted []string
138-
// commitExplicit is false when --commit was defaulted from the CI environment.
139-
commitExplicit bool
140-
repoRootExplicit bool
141-
requiredFor string
130+
repoRoot string
131+
sha string
132+
redacted []string
133+
flags *pflag.FlagSet
134+
requiredFor string
135+
}
136+
137+
// lookup reads the commit info from the repository at repoRoot, or returns
138+
// the error from opening it or resolving sha within it.
139+
func (r commitInfoRequest) lookup() (*gitview.CommitInfo, error) {
140+
gv, err := gitview.New(r.repoRoot)
141+
if err != nil {
142+
return nil, err
143+
}
144+
return gv.GetCommitInfoFromCommitSHA(r.sha, false, r.redacted)
145+
}
146+
147+
func (r commitInfoRequest) commitExplicit() bool {
148+
return r.flags != nil && r.flags.Changed("commit")
149+
}
150+
151+
// repoRootExplicit is true only when --repo-root carries a value other than
152+
// its "." default: bindFlags marks a config or env value as Changed even when
153+
// it equals the default, and "." itself asks for nothing.
154+
func (r commitInfoRequest) repoRootExplicit() bool {
155+
return r.flags != nil && r.flags.Changed("repo-root") && r.repoRoot != "."
142156
}
143157

144158
// resolve returns nil, nil when the lookup fails but nothing was asked for
@@ -147,26 +161,23 @@ type commitInfoRequest struct {
147161
// (kosli-dev/server#6094). An unresolvable commit, as in a shallow clone,
148162
// deliberately takes the same route.
149163
func (r commitInfoRequest) resolve() (*gitview.BasicCommitInfo, error) {
150-
gv, err := gitview.New(r.repoRoot)
164+
commitInfo, err := r.lookup()
151165
if err == nil {
152-
var commitInfo *gitview.CommitInfo
153-
commitInfo, err = gv.GetCommitInfoFromCommitSHA(r.sha, false, r.redacted)
154-
if err == nil {
155-
return &commitInfo.BasicCommitInfo, nil
156-
}
166+
return &commitInfo.BasicCommitInfo, nil
157167
}
158168

159-
origin := "--commit " + r.sha
160-
if !r.commitExplicit {
161-
origin += " (defaulted from the CI environment)"
169+
describedCommit := "--commit " + r.sha
170+
if !r.commitExplicit() {
171+
describedCommit += " (defaulted from the CI environment)"
162172
}
163173
switch {
164174
case r.requiredFor != "":
165-
return nil, fmt.Errorf("failed to get commit info for %s: %s. The commit is required to %s, so point --repo-root at a repository containing it", origin, err, r.requiredFor)
166-
case r.commitExplicit || r.repoRootExplicit:
167-
return nil, fmt.Errorf("failed to get commit info for %s: %s. Point --repo-root at a repository containing it", origin, err)
175+
return nil, fmt.Errorf("failed to get commit info for %s: %s. The commit is required to %s, so point --repo-root at a repository containing it", describedCommit, err, r.requiredFor)
176+
case r.commitExplicit() || r.repoRootExplicit():
177+
return nil, fmt.Errorf("failed to get commit info for %s: %s. Point --repo-root at a repository containing it", describedCommit, err)
168178
}
169-
logger.Warn("proceeding without commit info: %s could not be read: %s. Kosli binds an attestation reported before its artifact through this commit, so point --repo-root at a repository containing it if that binding is needed.", origin, err)
179+
logger.Warn("proceeding without commit info: %s could not be read: %s.", describedCommit, err)
180+
logger.Warn("Kosli binds an attestation reported before its artifact through this commit, so point --repo-root at a repository containing it if that binding is needed.")
170181
return nil, nil
171182
}
172183

‎cmd/kosli/attestationCommitInfo_test.go‎

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,29 @@ func (suite *AttestationCommitInfoTestSuite) TestCIDefaultedCommitNotInRepositor
8787
})
8888
}
8989

90+
func (suite *AttestationCommitInfoTestSuite) TestCommitFromEnvVarIsExplicit() {
91+
suite.T().Chdir(suite.T().TempDir())
92+
suite.T().Setenv("KOSLI_COMMIT", suite.headHash)
93+
_, out, _, _, err := executeCommandC(suite.attestGeneric(""))
94+
suite.Require().Error(err)
95+
suite.Contains(err.Error(), "failed to get commit info for --commit "+suite.headHash+":")
96+
suite.NotContains(err.Error(), "defaulted from the CI environment")
97+
suite.NotContains(out, "[warning] proceeding without commit info")
98+
}
99+
100+
// A --repo-root set via the environment or config to its own "." default
101+
// must not count as explicit: bindFlags marks the flag Changed regardless of
102+
// whether the applied value differs from the default.
103+
func (suite *AttestationCommitInfoTestSuite) TestRepoRootFromEnvVarAtDefaultValueStillWarns() {
104+
suite.T().Chdir(suite.T().TempDir())
105+
suite.T().Setenv("KOSLI_REPO_ROOT", ".")
106+
suite.inCI(suite.headHash, func() {
107+
_, out, _, _, err := executeCommandC(suite.attestGeneric(""))
108+
suite.Require().NoError(err)
109+
suite.Contains(out, "[warning] proceeding without commit info")
110+
})
111+
}
112+
90113
func (suite *AttestationCommitInfoTestSuite) TestCIDefaultedCommitWithExplicitRepoRootFails() {
91114
suite.inCI(suite.headHash, func() {
92115
for _, cmd := range []string{suite.attestGeneric("--repo-root testdata"), suite.beginTrail("--repo-root testdata")} {
@@ -137,15 +160,15 @@ func (suite *AttestationCommitInfoTestSuite) TestExplicitCommitIsAttached() {
137160
func (suite *AttestationCommitInfoTestSuite) TestCommandsNeedingTheCommitFail() {
138161
suite.T().Chdir(suite.T().TempDir())
139162
suite.inCI(suite.headHash, func() {
140-
for cmd, need := range map[string]string{
141-
"attest pullrequest github --name foo --flow f --trail t --github-token tok --github-org o --repository r" + suite.defaultKosliArguments: "find pull requests",
142-
"attest jira --name foo --flow f --trail t --jira-base-url https://x.atlassian.net --jira-username u --jira-api-token tok" + suite.defaultKosliArguments: "search for Jira issue keys",
163+
for _, tc := range []struct{ cmd, need string }{
164+
{"attest pullrequest github --name foo --flow f --trail t --github-token tok --github-org o --repository r" + suite.defaultKosliArguments, "find pull requests"},
165+
{"attest jira --name foo --flow f --trail t --jira-base-url https://x.atlassian.net --jira-username u --jira-api-token tok" + suite.defaultKosliArguments, "search for Jira issue keys"},
143166
} {
144-
_, out, _, _, err := executeCommandC(cmd)
145-
suite.Require().Error(err, cmd)
146-
suite.Contains(err.Error(), "failed to get commit info for --commit "+suite.headHash+" (defaulted from the CI environment)", cmd)
147-
suite.Contains(err.Error(), "The commit is required to "+need, cmd)
148-
suite.NotContains(out, "[warning] proceeding without commit info", cmd)
167+
_, out, _, _, err := executeCommandC(tc.cmd)
168+
suite.Require().Error(err, tc.cmd)
169+
suite.Contains(err.Error(), "failed to get commit info for --commit "+suite.headHash+" (defaulted from the CI environment)", tc.cmd)
170+
suite.Contains(err.Error(), "The commit is required to "+tc.need, tc.cmd)
171+
suite.NotContains(out, "[warning] proceeding without commit info", tc.cmd)
149172
}
150173
})
151174
}

‎cmd/kosli/beginTrail.go‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/kosli-dev/cli/internal/gitview"
1010
"github.com/kosli-dev/cli/internal/requests"
1111
"github.com/spf13/cobra"
12+
"github.com/spf13/pflag"
1213
)
1314

1415
const beginTrailShortDesc = `Begin or update a Kosli flow trail.`
@@ -50,8 +51,9 @@ type beginTrailOptions struct {
5051
repoURL string
5152
repoProvider string
5253
repoNameExplicit bool
53-
commitExplicit bool
54-
repoRootExplicit bool
54+
// flags lets commitInfoRequest tell a passed --commit/--repo-root from a
55+
// defaulted one.
56+
flags *pflag.FlagSet
5557
}
5658

5759
type TrailPayload struct {
@@ -87,8 +89,7 @@ func newBeginTrailCmd(out io.Writer) *cobra.Command {
8789
},
8890
RunE: func(cmd *cobra.Command, args []string) error {
8991
o.repoNameExplicit = cmd.Flags().Changed("repository")
90-
o.commitExplicit = cmd.Flags().Changed("commit")
91-
o.repoRootExplicit = cmd.Flags().Changed("repo-root")
92+
o.flags = cmd.Flags()
9293
return o.run(args)
9394
},
9495
}
@@ -133,11 +134,10 @@ func (o *beginTrailOptions) run(args []string) error {
133134

134135
if o.commitSHA != "" {
135136
o.payload.Commit, err = commitInfoRequest{
136-
repoRoot: o.srcRepoRoot,
137-
sha: o.commitSHA,
138-
redacted: o.redactedCommitInfo,
139-
commitExplicit: o.commitExplicit,
140-
repoRootExplicit: o.repoRootExplicit,
137+
repoRoot: o.srcRepoRoot,
138+
sha: o.commitSHA,
139+
redacted: o.redactedCommitInfo,
140+
flags: o.flags,
141141
}.resolve()
142142
if err != nil {
143143
return err

0 commit comments

Comments
 (0)