package getter
import (
+ "context"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
+ "runtime"
+ "strconv"
"strings"
urlhelper "github.com/hashicorp/go-getter/helper/url"
- "github.com/hashicorp/go-safetemp"
- "github.com/hashicorp/go-version"
+ safetemp "github.com/hashicorp/go-safetemp"
+ version "github.com/hashicorp/go-version"
)
// GitGetter is a Getter implementation that will download a module from
// a git repository.
-type GitGetter struct{}
+type GitGetter struct {
+ getter
+}
func (g *GitGetter) ClientMode(_ *url.URL) (ClientMode, error) {
return ClientModeDir, nil
}
func (g *GitGetter) Get(dst string, u *url.URL) error {
+ ctx := g.Context()
if _, err := exec.LookPath("git"); err != nil {
return fmt.Errorf("git must be available and on the PATH")
}
+ // The port number must be parseable as an integer. If not, the user
+ // was probably trying to use a scp-style address, in which case the
+ // ssh:// prefix must be removed to indicate that.
+ if portStr := u.Port(); portStr != "" {
+ if _, err := strconv.ParseUint(portStr, 10, 16); err != nil {
+ return fmt.Errorf("invalid port number %q; if using the \"scp-like\" git address scheme where a colon introduces the path instead, remove the ssh:// portion and use just the git:: prefix", portStr)
+ }
+ }
+
// Extract some query parameters we use
var ref, sshKey string
+ var depth int
q := u.Query()
if len(q) > 0 {
ref = q.Get("ref")
sshKey = q.Get("sshkey")
q.Del("sshkey")
+ if n, err := strconv.Atoi(q.Get("depth")); err == nil {
+ depth = n
+ }
+ q.Del("depth")
+
// Copy the URL
var newU url.URL = *u
u = &newU
return err
}
if err == nil {
- err = g.update(dst, sshKeyFile, ref)
+ err = g.update(ctx, dst, sshKeyFile, ref, depth)
} else {
- err = g.clone(dst, sshKeyFile, u)
+ err = g.clone(ctx, dst, sshKeyFile, u, depth)
}
if err != nil {
return err
}
// Lastly, download any/all submodules.
- return g.fetchSubmodules(dst, sshKeyFile)
+ return g.fetchSubmodules(ctx, dst, sshKeyFile, depth)
}
// GetFile for Git doesn't support updating at this time. It will download
return getRunCommand(cmd)
}
-func (g *GitGetter) clone(dst, sshKeyFile string, u *url.URL) error {
- cmd := exec.Command("git", "clone", u.String(), dst)
+func (g *GitGetter) clone(ctx context.Context, dst, sshKeyFile string, u *url.URL, depth int) error {
+ args := []string{"clone"}
+
+ if depth > 0 {
+ args = append(args, "--depth", strconv.Itoa(depth))
+ }
+
+ args = append(args, u.String(), dst)
+ cmd := exec.CommandContext(ctx, "git", args...)
setupGitEnv(cmd, sshKeyFile)
return getRunCommand(cmd)
}
-func (g *GitGetter) update(dst, sshKeyFile, ref string) error {
+func (g *GitGetter) update(ctx context.Context, dst, sshKeyFile, ref string, depth int) error {
// Determine if we're a branch. If we're NOT a branch, then we just
// switch to master prior to checking out
- cmd := exec.Command("git", "show-ref", "-q", "--verify", "refs/heads/"+ref)
+ cmd := exec.CommandContext(ctx, "git", "show-ref", "-q", "--verify", "refs/heads/"+ref)
cmd.Dir = dst
if getRunCommand(cmd) != nil {
return err
}
- cmd = exec.Command("git", "pull", "--ff-only")
+ if depth > 0 {
+ cmd = exec.Command("git", "pull", "--depth", strconv.Itoa(depth), "--ff-only")
+ } else {
+ cmd = exec.Command("git", "pull", "--ff-only")
+ }
+
cmd.Dir = dst
setupGitEnv(cmd, sshKeyFile)
return getRunCommand(cmd)
}
// fetchSubmodules downloads any configured submodules recursively.
-func (g *GitGetter) fetchSubmodules(dst, sshKeyFile string) error {
- cmd := exec.Command("git", "submodule", "update", "--init", "--recursive")
+func (g *GitGetter) fetchSubmodules(ctx context.Context, dst, sshKeyFile string, depth int) error {
+ args := []string{"submodule", "update", "--init", "--recursive"}
+ if depth > 0 {
+ args = append(args, "--depth", strconv.Itoa(depth))
+ }
+ cmd := exec.CommandContext(ctx, "git", args...)
cmd.Dir = dst
setupGitEnv(cmd, sshKeyFile)
return getRunCommand(cmd)
// with versions of Go < 1.9.
env := os.Environ()
for i, v := range env {
- if strings.HasPrefix(v, gitSSHCommand) {
+ if strings.HasPrefix(v, gitSSHCommand) && len(v) > len(gitSSHCommand) {
sshCmd = []string{v}
env[i], env[len(env)-1] = env[len(env)-1], env[i]
if sshKeyFile != "" {
// We have an SSH key temp file configured, tell ssh about this.
+ if runtime.GOOS == "windows" {
+ sshKeyFile = strings.Replace(sshKeyFile, `\`, `/`, -1)
+ }
sshCmd = append(sshCmd, "-i", sshKeyFile)
}
}
fields := strings.Fields(string(out))
- if len(fields) != 3 {
+ if len(fields) < 3 {
return fmt.Errorf("Unexpected 'git version' output: %q", string(out))
}
+ v := fields[2]
+ if runtime.GOOS == "windows" && strings.Contains(v, ".windows.") {
+ // on windows, git version will return for example:
+ // git version 2.20.1.windows.1
+ // Which does not follow the semantic versionning specs
+ // https://semver.org. We remove that part in order for
+ // go-version to not error.
+ v = v[:strings.Index(v, ".windows.")]
+ }
- have, err := version.NewVersion(fields[2])
+ have, err := version.NewVersion(v)
if err != nil {
return err
}