getdeps/git.go (110 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
package main
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
)
// Git represents a Git repository
type Git struct {
Label string `json:"label"`
URL string `json:"url"`
Dest string `json:"dest,omitempty"`
Branch *string `json:"branch,omitempty"`
Hash *string `json:"hash,omitempty"`
}
// Get downloads a Git repository
func (g *Git) Get(projectDir string, urlOverrides *URLOverrides, hashMode HashMode) error {
branch := defaultBranch
if g.Branch != nil && *g.Branch != "" {
branch = *g.Branch
} else {
g.Branch = &branch
}
dest := "."
if g.Dest != "" {
dest = g.Dest
}
hash := ""
if g.Hash != nil {
hash = *g.Hash
}
currentHash, err := gitClone(g.Label, g.URL, dest, branch, hashMode, hash, urlOverrides)
if err != nil {
return err
}
g.Hash = ¤tHash
return nil
}
func gitCloneShallow(label, repo, ref, dest string) (err error) {
if err = os.MkdirAll(dest, 0o755); err != nil {
return fmt.Errorf("%s: error creating %q: %w", label, dest, err)
}
defer func() {
if err != nil {
if dest != "." {
os.RemoveAll(dest)
} else {
os.RemoveAll(".git")
}
}
}()
if err = runCommand("git", "-C", dest, "init", "-q"); err != nil {
return fmt.Errorf("%s: %w", label, err)
}
if err = runCommand("git", "-C", dest, "remote", "add", "origin", repo); err != nil {
return fmt.Errorf("%s: %w", label, err)
}
if err = runCommand("git", "-C", dest, "fetch", "--depth=1", "origin", ref); err != nil {
return fmt.Errorf("%s: %w", label, err)
}
if err = runCommand("git", "-C", dest, "checkout", "-q", ref); err != nil {
return fmt.Errorf("%s: %w", label, err)
}
return nil
}
func gitClone(label, repo, dest, branch string, hashMode HashMode, hash string, urlOverrides *URLOverrides) (string, error) {
if urlOverrides != nil {
repo = urlOverrides.Override(repo)
}
if branch == "" {
return "", fmt.Errorf("%s: branch not specified", label)
}
if hashMode == hashModeUpdate {
hash = ""
}
log.Printf("%s: Cloning %s (%s %s)...", label, repo, branch, hash)
// Try the shallow clone first. This is much faster but requires
// `uploadpack.allowReachableSHA1InWant` to be enabled on the server,
// which is not the default.
ok := false
ref := hash
if ref == "" {
ref = branch
}
if err := gitCloneShallow(label, repo, ref, dest); err == nil {
ok = true
} else {
log.Printf(
"%s: shallow clone failed: %s,\n"+
"== NOTE: This is likely because uploadpack.allowReachableSHA1InWant is not enabled on the server.",
label, err)
// Fall back to full clone
}
if !ok {
if err := runCommand("git", "clone", "-q", "-b", branch, repo, dest); err != nil {
return "", fmt.Errorf("%s: %w", label, err)
}
if hash != "" {
if err := runCommand("git", "-C", dest, "checkout", "-q", hash); err != nil {
return "", fmt.Errorf("%s: %w", label, err)
}
}
}
cmd := exec.Command("git", "-C", dest, "rev-parse", "HEAD")
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("%s: error running %v: %w", label, cmd, err)
}
currentHash := strings.TrimSpace(string(out))
log.Printf("%s: Current hash is %s", label, currentHash)
if hashMode == hashModeStrict && hash == "" {
return currentHash, fmt.Errorf("%s: %s: hash mode is strict and no hash supplied (current is %s)", label, repo, currentHash)
}
return currentHash, nil
}