13 urlhelper "github.com/hashicorp/go-getter/helper/url"
14 "github.com/hashicorp/go-version"
17 // GitGetter is a Getter implementation that will download a module from
19 type GitGetter struct{}
21 func (g *GitGetter) ClientMode(_ *url.URL) (ClientMode, error) {
22 return ClientModeDir, nil
25 func (g *GitGetter) Get(dst string, u *url.URL) error {
26 if _, err := exec.LookPath("git"); err != nil {
27 return fmt.Errorf("git must be available and on the PATH")
30 // Extract some query parameters we use
31 var ref, sshKey string
37 sshKey = q.Get("sshkey")
43 u.RawQuery = q.Encode()
48 // Check that the git version is sufficiently new.
49 if err := checkGitVersion("2.3"); err != nil {
50 return fmt.Errorf("Error using ssh key: %v", err)
53 // We have an SSH key - decode it.
54 raw, err := base64.StdEncoding.DecodeString(sshKey)
59 // Create a temp file for the key and ensure it is removed.
60 fh, err := ioutil.TempFile("", "go-getter")
64 sshKeyFile = fh.Name()
65 defer os.Remove(sshKeyFile)
67 // Set the permissions prior to writing the key material.
68 if err := os.Chmod(sshKeyFile, 0600); err != nil {
72 // Write the raw key into the temp file.
73 _, err = fh.Write(raw)
80 // Clone or update the repository
81 _, err := os.Stat(dst)
82 if err != nil && !os.IsNotExist(err) {
86 err = g.update(dst, sshKeyFile, ref)
88 err = g.clone(dst, sshKeyFile, u)
94 // Next: check out the proper tag/branch if it is specified, and checkout
96 if err := g.checkout(dst, ref); err != nil {
101 // Lastly, download any/all submodules.
102 return g.fetchSubmodules(dst, sshKeyFile)
105 // GetFile for Git doesn't support updating at this time. It will download
106 // the file every time.
107 func (g *GitGetter) GetFile(dst string, u *url.URL) error {
108 td, err := ioutil.TempDir("", "getter-git")
112 if err := os.RemoveAll(td); err != nil {
116 // Get the filename, and strip the filename from the URL so we can
117 // just get the repository directly.
118 filename := filepath.Base(u.Path)
119 u.Path = filepath.Dir(u.Path)
121 // Get the full repository
122 if err := g.Get(td, u); err != nil {
126 // Copy the single file
127 u, err = urlhelper.Parse(fmtFileURL(filepath.Join(td, filename)))
132 fg := &FileGetter{Copy: true}
133 return fg.GetFile(dst, u)
136 func (g *GitGetter) checkout(dst string, ref string) error {
137 cmd := exec.Command("git", "checkout", ref)
139 return getRunCommand(cmd)
142 func (g *GitGetter) clone(dst, sshKeyFile string, u *url.URL) error {
143 cmd := exec.Command("git", "clone", u.String(), dst)
144 setupGitEnv(cmd, sshKeyFile)
145 return getRunCommand(cmd)
148 func (g *GitGetter) update(dst, sshKeyFile, ref string) error {
149 // Determine if we're a branch. If we're NOT a branch, then we just
150 // switch to master prior to checking out
151 cmd := exec.Command("git", "show-ref", "-q", "--verify", "refs/heads/"+ref)
154 if getRunCommand(cmd) != nil {
155 // Not a branch, switch to master. This will also catch non-existent
156 // branches, in which case we want to switch to master and then
157 // checkout the proper branch later.
161 // We have to be on a branch to pull
162 if err := g.checkout(dst, ref); err != nil {
166 cmd = exec.Command("git", "pull", "--ff-only")
168 setupGitEnv(cmd, sshKeyFile)
169 return getRunCommand(cmd)
172 // fetchSubmodules downloads any configured submodules recursively.
173 func (g *GitGetter) fetchSubmodules(dst, sshKeyFile string) error {
174 cmd := exec.Command("git", "submodule", "update", "--init", "--recursive")
176 setupGitEnv(cmd, sshKeyFile)
177 return getRunCommand(cmd)
180 // setupGitEnv sets up the environment for the given command. This is used to
181 // pass configuration data to git and ssh and enables advanced cloning methods.
182 func setupGitEnv(cmd *exec.Cmd, sshKeyFile string) {
185 if sshKeyFile != "" {
186 // We have an SSH key temp file configured, tell ssh about this.
187 sshOpts = append(sshOpts, "-i", sshKeyFile)
190 cmd.Env = append(os.Environ(),
191 // Set the ssh command to use for clones.
192 "GIT_SSH_COMMAND=ssh "+strings.Join(sshOpts, " "),
196 // checkGitVersion is used to check the version of git installed on the system
197 // against a known minimum version. Returns an error if the installed version
198 // is older than the given minimum.
199 func checkGitVersion(min string) error {
200 want, err := version.NewVersion(min)
205 out, err := exec.Command("git", "version").Output()
210 fields := strings.Fields(string(out))
211 if len(fields) != 3 {
212 return fmt.Errorf("Unexpected 'git version' output: %q", string(out))
215 have, err := version.NewVersion(fields[2])
220 if have.LessThan(want) {
221 return fmt.Errorf("Required git version = %s, have %s", want, have)