From dd84b9d64fb98746a230cd24233ff50a562c39c9 Mon Sep 17 00:00:00 2001 From: 简律纯 Date: Fri, 28 Apr 2023 01:36:44 +0800 Subject: --- cli/internal/scm/git_go.go | 111 +++++++++++++++++++++++++++++++++++++++++++ cli/internal/scm/git_rust.go | 34 +++++++++++++ cli/internal/scm/scm.go | 53 +++++++++++++++++++++ cli/internal/scm/stub.go | 14 ++++++ 4 files changed, 212 insertions(+) create mode 100644 cli/internal/scm/git_go.go create mode 100644 cli/internal/scm/git_rust.go create mode 100644 cli/internal/scm/scm.go create mode 100644 cli/internal/scm/stub.go (limited to 'cli/internal/scm') diff --git a/cli/internal/scm/git_go.go b/cli/internal/scm/git_go.go new file mode 100644 index 0000000..0dac2bf --- /dev/null +++ b/cli/internal/scm/git_go.go @@ -0,0 +1,111 @@ +//go:build go || !rust +// +build go !rust + +// Package scm abstracts operations on various tools like git +// Currently, only git is supported. +// +// Adapted from https://github.com/thought-machine/please/tree/master/src/scm +// Copyright Thought Machine, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package scm + +import ( + "fmt" + "github.com/vercel/turbo/cli/internal/turbopath" + "os/exec" + "path/filepath" + "strings" + + "github.com/pkg/errors" +) + +// git implements operations on a git repository. +type git struct { + repoRoot turbopath.AbsoluteSystemPath +} + +// ChangedFiles returns a list of modified files since the given commit, optionally including untracked files. +func (g *git) ChangedFiles(fromCommit string, toCommit string, relativeTo string) ([]string, error) { + if relativeTo == "" { + relativeTo = g.repoRoot.ToString() + } + relSuffix := []string{"--", relativeTo} + command := []string{"diff", "--name-only", toCommit} + + out, err := exec.Command("git", append(command, relSuffix...)...).CombinedOutput() + if err != nil { + return nil, errors.Wrapf(err, "finding changes relative to %v", relativeTo) + } + files := strings.Split(string(out), "\n") + + if fromCommit != "" { + // Grab the diff from the merge-base to HEAD using ... syntax. This ensures we have just + // the changes that have occurred on the current branch. + command = []string{"diff", "--name-only", fromCommit + "..." + toCommit} + out, err = exec.Command("git", append(command, relSuffix...)...).CombinedOutput() + if err != nil { + // Check if we can provide a better error message for non-existent commits. + // If we error on the check or can't find it, fall back to whatever error git + // reported. + if exists, err := commitExists(fromCommit); err == nil && !exists { + return nil, fmt.Errorf("commit %v does not exist", fromCommit) + } + return nil, errors.Wrapf(err, "git comparing with %v", fromCommit) + } + committedChanges := strings.Split(string(out), "\n") + files = append(files, committedChanges...) + } + command = []string{"ls-files", "--other", "--exclude-standard"} + out, err = exec.Command("git", append(command, relSuffix...)...).CombinedOutput() + if err != nil { + return nil, errors.Wrap(err, "finding untracked files") + } + untracked := strings.Split(string(out), "\n") + files = append(files, untracked...) + // git will report changed files relative to the worktree: re-relativize to relativeTo + normalized := make([]string, 0) + for _, f := range files { + if f == "" { + continue + } + normalizedFile, err := g.fixGitRelativePath(strings.TrimSpace(f), relativeTo) + if err != nil { + return nil, err + } + normalized = append(normalized, normalizedFile) + } + return normalized, nil +} + +func (g *git) PreviousContent(fromCommit string, filePath string) ([]byte, error) { + if fromCommit == "" { + return nil, fmt.Errorf("Need commit sha to inspect file contents") + } + + out, err := exec.Command("git", "show", fmt.Sprintf("%s:%s", fromCommit, filePath)).CombinedOutput() + if err != nil { + return nil, errors.Wrapf(err, "unable to get contents of %s", filePath) + } + + return out, nil +} + +func commitExists(commit string) (bool, error) { + err := exec.Command("git", "cat-file", "-t", commit).Run() + if err != nil { + exitErr := &exec.ExitError{} + if errors.As(err, &exitErr) && exitErr.ExitCode() == 128 { + return false, nil + } + return false, err + } + return true, nil +} + +func (g *git) fixGitRelativePath(worktreePath, relativeTo string) (string, error) { + p, err := filepath.Rel(relativeTo, filepath.Join(g.repoRoot, worktreePath)) + if err != nil { + return "", errors.Wrapf(err, "unable to determine relative path for %s and %s", g.repoRoot, relativeTo) + } + return p, nil +} diff --git a/cli/internal/scm/git_rust.go b/cli/internal/scm/git_rust.go new file mode 100644 index 0000000..4b4cd2d --- /dev/null +++ b/cli/internal/scm/git_rust.go @@ -0,0 +1,34 @@ +// Package scm abstracts operations on various tools like git +// Currently, only git is supported. +// +// Adapted from https://github.com/thought-machine/please/tree/master/src/scm +// Copyright Thought Machine, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +//go:build rust +// +build rust + +package scm + +import ( + "fmt" + "github.com/vercel/turbo/cli/internal/ffi" + "github.com/vercel/turbo/cli/internal/turbopath" +) + +// git implements operations on a git repository. +type git struct { + repoRoot turbopath.AbsoluteSystemPath +} + +// ChangedFiles returns a list of modified files since the given commit, optionally including untracked files. +func (g *git) ChangedFiles(fromCommit string, toCommit string, monorepoRoot string) ([]string, error) { + return ffi.ChangedFiles(g.repoRoot.ToString(), monorepoRoot, fromCommit, toCommit) +} + +func (g *git) PreviousContent(fromCommit string, filePath string) ([]byte, error) { + if fromCommit == "" { + return nil, fmt.Errorf("Need commit sha to inspect file contents") + } + + return ffi.PreviousContent(g.repoRoot.ToString(), fromCommit, filePath) +} diff --git a/cli/internal/scm/scm.go b/cli/internal/scm/scm.go new file mode 100644 index 0000000..e7f17c8 --- /dev/null +++ b/cli/internal/scm/scm.go @@ -0,0 +1,53 @@ +// Package scm abstracts operations on various tools like git +// Currently, only git is supported. +// +// Adapted from https://github.com/thought-machine/please/tree/master/src/scm +// Copyright Thought Machine, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package scm + +import ( + "github.com/pkg/errors" + + "github.com/vercel/turbo/cli/internal/turbopath" +) + +var ErrFallback = errors.New("cannot find a .git folder. Falling back to manual file hashing (which may be slower). If you are running this build in a pruned directory, you can ignore this message. Otherwise, please initialize a git repository in the root of your monorepo") + +// An SCM represents an SCM implementation that we can ask for various things. +type SCM interface { + // ChangedFiles returns a list of modified files since the given commit, including untracked files + ChangedFiles(fromCommit string, toCommit string, relativeTo string) ([]string, error) + // PreviousContent Returns the content of the file at fromCommit + PreviousContent(fromCommit string, filePath string) ([]byte, error) +} + +// newGitSCM returns a new SCM instance for this repo root. +// It returns nil if there is no known implementation there. +func newGitSCM(repoRoot turbopath.AbsoluteSystemPath) SCM { + if repoRoot.UntypedJoin(".git").Exists() { + return &git{repoRoot: repoRoot} + } + return nil +} + +// newFallback returns a new SCM instance for this repo root. +// If there is no known implementation it returns a stub. +func newFallback(repoRoot turbopath.AbsoluteSystemPath) (SCM, error) { + if scm := newGitSCM(repoRoot); scm != nil { + return scm, nil + } + + return &stub{}, ErrFallback +} + +// FromInRepo produces an SCM instance, given a path within a +// repository. It does not need to be a git repository, and if +// it is not, the given path is assumed to be the root. +func FromInRepo(repoRoot turbopath.AbsoluteSystemPath) (SCM, error) { + dotGitDir, err := repoRoot.Findup(".git") + if err != nil { + return nil, err + } + return newFallback(dotGitDir.Dir()) +} diff --git a/cli/internal/scm/stub.go b/cli/internal/scm/stub.go new file mode 100644 index 0000000..2e356c5 --- /dev/null +++ b/cli/internal/scm/stub.go @@ -0,0 +1,14 @@ +// Adapted from https://github.com/thought-machine/please/tree/master/src/scm +// Copyright Thought Machine, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package scm + +type stub struct{} + +func (s *stub) ChangedFiles(fromCommit string, toCommit string, relativeTo string) ([]string, error) { + return nil, nil +} + +func (s *stub) PreviousContent(fromCommit string, filePath string) ([]byte, error) { + return nil, nil +} -- cgit v1.2.3-70-g09d2