blob: 7128e5b58a50c1efcf6b6862989d1aef59ec285f [file] [log] [blame]
// Copyright 2023 The Shac Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package engine
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"sort"
"strings"
"sync"
"go.fuchsia.dev/shac-project/shac/internal/execsupport"
)
// cachedGitEnv should never be accessed directly, only by calling `gitEnv()`.
var cachedGitEnv []string
var populateGitEnvOnce sync.Once
func gitEnv() []string {
populateGitEnvOnce.Do(func() {
// First is for git version before 2.32, the rest are to skip the user and system config.
cachedGitEnv = append(os.Environ(),
"GIT_CONFIG_NOGLOBAL=true",
"GIT_CONFIG_GLOBAL=",
"GIT_CONFIG_SYSTEM=",
"LANG=C",
"GIT_EXTERNAL_DIFF=",
"GIT_DIFF_OPTS=",
)
gitConfig := map[string]string{
// Prevents automatic unicode decomposition of filenames. Only has
// an effect on macOS.
"core.precomposeUnicode": "true",
}
cachedGitEnv = append(cachedGitEnv, gitConfigEnv(gitConfig)...)
})
return cachedGitEnv
}
func runGitCmd(ctx context.Context, dir string, args ...string) (string, error) {
args = append([]string{
// Don't update the git index during read operations.
"--no-optional-locks",
}, args...)
cmd := exec.CommandContext(ctx, "git", args...)
cmd.Dir = dir
cmd.Env = gitEnv()
b := buffers.get()
cmd.Stdout = b
cmd.Stderr = b
err := execsupport.Run(cmd)
// Always make a copy of the output, since it could be persisted. Only reuse
// the temporary buffer.
out := b.String()
buffers.push(b)
if err != nil {
if errExit := (&exec.ExitError{}); errors.As(err, &errExit) {
return "", fmt.Errorf("error running git %s: %w\n%s", strings.Join(args, " "), err, out)
}
return "", err
}
return strings.TrimSpace(out), nil
}
// gitConfigEnv converts a map of key-value git config pairs into corresponding
// environment variables.
//
// See https://git-scm.com/docs/git-config#ENVIRONMENT for details on how git
// configs are set via environment variables.
func gitConfigEnv(gitConfig map[string]string) []string {
// GIT_CONFIG_COUNT specifies how many key/value env var pairs to look for.
res := []string{fmt.Sprintf("GIT_CONFIG_COUNT=%d", len(gitConfig))}
keys := make([]string, 0, len(gitConfig))
for k := range gitConfig {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
// Each config setting is specified by setting a pair of
// GIT_CONFIG_KEY_<N> and GIT_CONFIG_VALUE_<N> variables.
res = append(res,
fmt.Sprintf("GIT_CONFIG_KEY_%d=%s", i, k),
fmt.Sprintf("GIT_CONFIG_VALUE_%d=%s", i, gitConfig[k]))
}
return res
}