fix(review): require terminal stdin for interactive mode · Entire

fix(review): require terminal stdin for interactive mode

1ac87bc→main·

dipree·3d ago·2 files·+11 added/-12 removed

Sessions

01KXGSC7YR34K5TTMAVFH7PCMAView transcript

Changes

2

272 unmodified lines

273
274
275
276
277
276
277
278
279
280
281
1 unmodified line

283
284
285
285
286
287
288
289
290
291
290
291
292
293
294

272 unmodified lines

// reviewCommandIsInteractive treats a real terminal on both stdin and stdout
// as authoritative for this explicitly user-invoked command. This avoids
// suppressing the review wizard when a normal shell inherits an agent sentinel
// or GIT_TERMINAL_PROMPT=0. The fallback preserves controlling-TTY detection
// for callers whose stdio is not wired directly to the terminal.
// or GIT_TERMINAL_PROMPT=0, while requiring the exact stdin consumed by huh and
// Bubble Tea to accept keypresses. A controlling /dev/tty alone is insufficient
// because the command may still have piped stdin.
func reviewCommandIsInteractive(cmd *cobra.Command) bool {
    testTTY := os.Getenv(interactive.EnvTestTTY)
    ci := os.Getenv("CI")
1 unmodified line

return reviewTTYIsInteractive(
        interactive.IsTerminalReader(cmd.InOrStdin()),
        interactive.IsTerminalWriter(cmd.OutOrStdout()),
        interactive.CanPromptInteractively(),
        hardDisabled,
    )
}

func reviewTTYIsInteractive(stdinTTY, stdoutTTY, canPrompt, hardDisabled bool) bool {
    return !hardDisabled && stdoutTTY && (stdinTTY || canPrompt)
}
func reviewTTYIsInteractive(stdinTTY, stdoutTTY, hardDisabled bool) bool {
    return !hardDisabled && stdinTTY && stdoutTTY
}

func (o reviewConfigureOptions) scripted() bool {

Mcmd/entire/cli/review/cmd.go+5/-5

94 unmodified lines

95
96
97
98
98
99
100
102
103
104
105
101
102
103
104
105
106
107
108
109
111
112
110
111
112
113
114

94 unmodified lines

name         string
    stdinTTY     bool
    stdoutTTY    bool
    canPrompt    bool
    hardDisabled bool
    want         bool
    }{
    {name: "direct terminal overrides inherited sentinel", stdinTTY: true, stdoutTTY: true, canPrompt: false, want: true},
    {name: "controlling terminal fallback", stdinTTY: false, stdoutTTY: true, canPrompt: true, want: true},
    {name: "captured stdout", stdinTTY: true, stdoutTTY: false, canPrompt: true, want: false},
    {name: "agent with piped stdin", stdinTTY: false, stdoutTTY: true, canPrompt: false, want: false},
    {name: "direct terminal overrides inherited sentinel", stdinTTY: true, stdoutTTY: true, want: true},
    {name: "controlling terminal does not override piped stdin", stdinTTY: false, stdoutTTY: true, want: false},
    {name: "captured stdout", stdinTTY: true, stdoutTTY: false, want: false},
    {name: "agent with piped stdin", stdinTTY: false, stdoutTTY: true, want: false},
    {name: "explicitly forced non-interactive", stdinTTY: true, stdoutTTY: true, hardDisabled: true, want: false},
}
for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
        t.Parallel()
        if got := reviewTTYIsInteractive(tt.stdinTTY, tt.stdoutTTY, tt.canPrompt, tt.hardDisabled); got != tt.want {
            t.Fatalf("reviewTTYIsInteractive(%v, %v, %v, %v) = %v, want %v", tt.stdinTTY, tt.stdoutTTY, tt.canPrompt, tt.hardDisabled, got, tt.want)
        if got := reviewTTYIsInteractive(tt.stdinTTY, tt.stdoutTTY, tt.hardDisabled); got != tt.want {
            t.Fatalf("reviewTTYIsInteractive(%v, %v, %v) = %v, want %v", tt.stdinTTY, tt.stdoutTTY, tt.hardDisabled, got, tt.want)
        }
        }
}

Mcmd/entire/cli/review/configure_test.go+6/-7