@@ -14,6 +14,7 @@ import (
1414 "github.com/cego/gitte/config"
1515 "github.com/cego/gitte/executor"
1616 "github.com/spf13/cobra"
17+ "golang.org/x/term"
1718)
1819
1920func newCleanCmd () * cobra.Command {
@@ -203,28 +204,27 @@ func runCleanLocalChanges(ctx context.Context, cfg *config.GitteConfig, cwd stri
203204 fmt .Printf (" %s\n " , r .name )
204205 }
205206
206- scanner := bufio .NewScanner (stdin )
207-
208- fmt .Print ("\n Reset all, handle individually, or cancel? [all/individually/cancel]: " )
209- scanner .Scan ()
210- choice := strings .TrimSpace (strings .ToLower (scanner .Text ()))
211- if err := scanner .Err (); err != nil {
207+ fmt .Print ("\n Reset [a]ll / [i]ndividually / [C]ancel? " )
208+ choice , err := readKey (stdin )
209+ if err != nil {
212210 return fmt .Errorf ("reading stdin: %w" , err )
213211 }
214212
215213 switch choice {
216- case "all " :
214+ case "a " :
217215 for _ , r := range dirty {
218216 if err := hardReset (ctx , r .path ); err != nil {
219217 fmt .Fprintf (os .Stderr , "warning: [%s] %v\n " , r .name , err )
220218 }
221219 }
222- case "individually " :
220+ case "i " :
223221 for _ , r := range dirty {
224- fmt .Printf ("Reset %s? [y/N]: " , r .name )
225- scanner .Scan ()
226- answer := strings .TrimSpace (strings .ToLower (scanner .Text ()))
227- if answer == "y" || answer == "yes" {
222+ fmt .Printf ("Reset %s? [y/N] " , r .name )
223+ answer , err := readKey (stdin )
224+ if err != nil {
225+ return fmt .Errorf ("reading stdin: %w" , err )
226+ }
227+ if answer == "y" {
228228 if err := hardReset (ctx , r .path ); err != nil {
229229 fmt .Fprintf (os .Stderr , "warning: [%s] %v\n " , r .name , err )
230230 }
@@ -303,6 +303,43 @@ func hardReset(ctx context.Context, dir string) error {
303303 return nil
304304}
305305
306+ // readKey reads a single keypress and returns it as a lowercase string.
307+ // When stdin is a terminal it switches to raw mode so the user does not need
308+ // to press Enter; the character is echoed followed by a newline.
309+ // Falls back to line-reading for non-TTY stdin (pipes, tests).
310+ func readKey (stdin io.Reader ) (string , error ) {
311+ if f , ok := stdin .(* os.File ); ok {
312+ if fd := int (f .Fd ()); term .IsTerminal (fd ) {
313+ old , err := term .MakeRaw (fd )
314+ if err == nil {
315+ defer term .Restore (fd , old ) //nolint:errcheck
316+ buf := make ([]byte , 1 )
317+ if _ , err := f .Read (buf ); err != nil {
318+ return "" , err
319+ }
320+ ch := buf [0 ]
321+ switch ch {
322+ case 3 : // Ctrl+C — treat as cancel
323+ fmt .Println ()
324+ return "" , nil
325+ case '\r' , '\n' : // Enter — default (empty → caller uses default)
326+ fmt .Println ()
327+ return "" , nil
328+ }
329+ fmt .Printf ("%c\n " , ch )
330+ return strings .ToLower (string (ch )), nil
331+ }
332+ }
333+ }
334+ // non-TTY fallback: read a full line
335+ scanner := bufio .NewScanner (stdin )
336+ scanner .Scan ()
337+ if err := scanner .Err (); err != nil {
338+ return "" , err
339+ }
340+ return strings .TrimSpace (strings .ToLower (scanner .Text ())), nil
341+ }
342+
306343// sortedProjectNames returns project names in alphabetical order.
307344func sortedProjectNames (cfg * config.GitteConfig ) []string {
308345 names := make ([]string , 0 , len (cfg .Projects ))
0 commit comments