From 6daaa7c05e7883d4468fc7b40c2f71e0ea75562a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 30 Sep 2026 17:48:18 +0000 Subject: [PATCH] Fix locale-dependent git repo detection (#1099) Fixes #1089 ## Root cause `tea clone` initializes the command context by probing the current directory with `git rev-parse --show-toplevel`. `classifyRepoError` recognizes the "not a git repository" case by matching the English stderr text. When git emits localized errors (for example with `LANG=de_DE.UTF-8`), that match fails, and the otherwise harmless "no repo here" error is returned as fatal before cloning. ## Fix Pin the locale of git subprocesses to `C` by overriding `LC_ALL`, `LANG`, and `LANGUAGE` in `runGitCommand`. This keeps all git output stable for parsing while leaving the parent process locale untouched. ## Tests Added a regression test using a fake git executable that emits a German "not a repository" error unless the locale is forced to `C`, and verifies `ErrRepositoryNotExists` is returned. Reviewed-on: https://gitea.com/gitea/tea/pulls/1099 Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com> --- modules/git/cli_backend.go | 17 +++++++++++++- modules/git/cli_backend_test.go | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 modules/git/cli_backend_test.go diff --git a/modules/git/cli_backend.go b/modules/git/cli_backend.go index d458de2c..11c67c67 100644 --- a/modules/git/cli_backend.go +++ b/modules/git/cli_backend.go @@ -258,6 +258,21 @@ func (e *gitCommandError) Unwrap() error { return e.err } +func gitCommandEnv(authEnv []string) []string { + baseEnv := os.Environ() + env := make([]string, 0, len(baseEnv)+len(authEnv)+3) + for _, value := range baseEnv { + if strings.HasPrefix(value, "LC_ALL=") || + strings.HasPrefix(value, "LANG=") || + strings.HasPrefix(value, "LANGUAGE=") { + continue + } + env = append(env, value) + } + env = append(env, authEnv...) + return append(env, "LC_ALL=C", "LANG=C", "LANGUAGE=C") +} + func runGitCommand(dir string, auth *AuthMethod, extraConfigs []string, args ...string) (string, error) { authConfigs, authEnv, cleanup, err := prepareCLIAuth(auth) if err != nil { @@ -278,7 +293,7 @@ func runGitCommand(dir string, auth *AuthMethod, extraConfigs []string, args ... if dir != "" { cmd.Dir = dir } - cmd.Env = append(os.Environ(), authEnv...) + cmd.Env = gitCommandEnv(authEnv) var stdout bytes.Buffer var stderr bytes.Buffer diff --git a/modules/git/cli_backend_test.go b/modules/git/cli_backend_test.go new file mode 100644 index 00000000..c09be00d --- /dev/null +++ b/modules/git/cli_backend_test.go @@ -0,0 +1,40 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOpenClassifiesLocalizedNotRepoError(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("test uses a POSIX shell script as fake git") + } + + fakeGitDir := t.TempDir() + fakeGit := filepath.Join(fakeGitDir, "git") + script := `#!/bin/sh +locale="${LC_ALL:-${LANG:-C}}" +if [ "$locale" = "C" ]; then + echo "fatal: not a git repository (or any of the parent directories): .git" >&2 +else + echo "fatal: Kein Git-Repository (oder irgendein Elternverzeichnis bis zum Einhangepunkt /)" >&2 +fi +exit 128 +` + require.NoError(t, os.WriteFile(fakeGit, []byte(script), 0o755)) + + t.Setenv("PATH", fakeGitDir+string(os.PathListSeparator)+os.Getenv("PATH")) + t.Setenv("LC_ALL", "de_DE.UTF-8") + t.Setenv("LANG", "de_DE.UTF-8") + t.Setenv("LANGUAGE", "de_DE:de") + + _, err := (cliBackend{}).Open(t.TempDir()) + require.ErrorIs(t, err, ErrRepositoryNotExists) +}