github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/internal/packager/git/clone.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package git contains functions for interacting with git repositories. 5 package git 6 7 import ( 8 "context" 9 "errors" 10 "strings" 11 12 "github.com/Racer159/jackal/src/pkg/message" 13 "github.com/Racer159/jackal/src/pkg/utils" 14 "github.com/Racer159/jackal/src/pkg/utils/exec" 15 "github.com/go-git/go-git/v5" 16 goConfig "github.com/go-git/go-git/v5/config" 17 "github.com/go-git/go-git/v5/plumbing" 18 ) 19 20 // clone performs a `git clone` of a given repo. 21 func (g *Git) clone(gitURL string, ref plumbing.ReferenceName, shallow bool) error { 22 cloneOptions := &git.CloneOptions{ 23 URL: gitURL, 24 Progress: g.Spinner, 25 RemoteName: onlineRemoteName, 26 } 27 28 // Don't clone all tags / refs if we're cloning a specific tag or branch. 29 if ref.IsTag() || ref.IsBranch() { 30 cloneOptions.Tags = git.NoTags 31 cloneOptions.ReferenceName = ref 32 cloneOptions.SingleBranch = true 33 } 34 35 // If this is a shallow clone set the depth to 1 36 if shallow { 37 cloneOptions.Depth = 1 38 } 39 40 // Setup git credentials if we have them, ignore if we don't. 41 gitCred := utils.FindAuthForHost(gitURL) 42 if gitCred != nil { 43 cloneOptions.Auth = &gitCred.Auth 44 } 45 46 // Clone the given repo. 47 repo, err := git.PlainClone(g.GitPath, false, cloneOptions) 48 if err != nil { 49 message.Notef("Falling back to host 'git', failed to clone the repo %q with Jackal: %s", gitURL, err.Error()) 50 return g.gitCloneFallback(gitURL, ref, shallow) 51 } 52 53 // If we're cloning the whole repo, we need to also fetch the other branches besides the default. 54 if ref == emptyRef { 55 fetchOpts := &git.FetchOptions{ 56 RemoteName: onlineRemoteName, 57 Progress: g.Spinner, 58 RefSpecs: []goConfig.RefSpec{"refs/*:refs/*"}, 59 Tags: git.AllTags, 60 } 61 62 if gitCred != nil { 63 fetchOpts.Auth = &gitCred.Auth 64 } 65 66 if err := repo.Fetch(fetchOpts); err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { 67 return err 68 } 69 } 70 71 return nil 72 } 73 74 // gitCloneFallback is a fallback if go-git fails to clone a repo. 75 func (g *Git) gitCloneFallback(gitURL string, ref plumbing.ReferenceName, shallow bool) error { 76 // If we can't clone with go-git, fallback to the host clone 77 // Only support "all tags" due to the azure clone url format including a username 78 cloneArgs := []string{"clone", "--origin", onlineRemoteName, gitURL, g.GitPath} 79 80 // Don't clone all tags / refs if we're cloning a specific tag or branch. 81 if ref.IsTag() || ref.IsBranch() { 82 cloneArgs = append(cloneArgs, "--no-tags") 83 cloneArgs = append(cloneArgs, "-b", ref.Short()) 84 cloneArgs = append(cloneArgs, "--single-branch") 85 } 86 87 // If this is a shallow clone set the depth to 1 88 if shallow { 89 cloneArgs = append(cloneArgs, "--depth", "1") 90 } 91 92 cloneExecConfig := exec.Config{ 93 Stdout: g.Spinner, 94 Stderr: g.Spinner, 95 } 96 97 message.Command("git %s", strings.Join(cloneArgs, " ")) 98 99 _, _, err := exec.CmdWithContext(context.TODO(), cloneExecConfig, "git", cloneArgs...) 100 if err != nil { 101 return err 102 } 103 104 // If we're cloning the whole repo, we need to also fetch the other branches besides the default. 105 if ref == emptyRef { 106 fetchArgs := []string{"fetch", "--tags", "--update-head-ok", onlineRemoteName, "refs/*:refs/*"} 107 108 fetchExecConfig := exec.Config{ 109 Stdout: g.Spinner, 110 Stderr: g.Spinner, 111 Dir: g.GitPath, 112 } 113 114 message.Command("git %s", strings.Join(fetchArgs, " ")) 115 116 _, _, err := exec.CmdWithContext(context.TODO(), fetchExecConfig, "git", fetchArgs...) 117 if err != nil { 118 return err 119 } 120 } 121 122 return nil 123 }