From 93b729e1fe146548b6d06e60c849f5c0ad18490b Mon Sep 17 00:00:00 2001 From: Wilken Rivera Date: Fri, 28 Jun 2024 16:20:23 -0400 Subject: [PATCH] Backport fix from https://github.com/hashicorp/go-getter/pull/497 to v2 --- get_git.go | 83 ++++++++++++++++++++++----- get_git_test.go | 149 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 13 deletions(-) diff --git a/get_git.go b/get_git.go index 6f0c2a7a4..b2416fcfa 100644 --- a/get_git.go +++ b/get_git.go @@ -122,7 +122,7 @@ func (g *GitGetter) Get(ctx context.Context, req *Request) error { return err } if err == nil { - err = g.update(ctx, req.Dst, sshKeyFile, ref, depth) + err = g.update(ctx, req.Dst, sshKeyFile, ref, req.URL(), depth) } else { err = g.clone(ctx, sshKeyFile, depth, req) } @@ -193,28 +193,63 @@ func (g *GitGetter) clone(ctx context.Context, sshKeyFile string, depth int, req return getRunCommand(cmd) } -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.CommandContext(ctx, "git", "show-ref", "-q", "--verify", "refs/heads/"+ref) +func (g *GitGetter) update(ctx context.Context, dst, sshKeyFile, ref string, u *url.URL, depth int) error { + // Remove all variations of .git directories + err := removeCaseInsensitiveGitDirectory(dst) + if err != nil { + return err + + } + + // Initialize the git repository + cmd := exec.CommandContext(ctx, "git", "init") + cmd.Dir = dst + err = getRunCommand(cmd) + if err != nil { + return err + } + + // Add the git remote + cmd = exec.CommandContext(ctx, "git", "remote", "add", "origin", "--", u.String()) + cmd.Dir = dst + err = getRunCommand(cmd) + if err != nil { + return err + } + + // Fetch the remote ref + cmd = exec.CommandContext(ctx, "git", "fetch", "--tags") + cmd.Dir = dst + err = getRunCommand(cmd) + if err != nil { + return err + } + + // Fetch the remote ref + cmd = exec.CommandContext(ctx, "git", "fetch", "origin", "--", ref) cmd.Dir = dst + err = getRunCommand(cmd) + if err != nil { + return err + } - if getRunCommand(cmd) != nil { - // Not a branch, switch to default branch. This will also catch - // non-existent branches, in which case we want to switch to default - // and then checkout the proper branch later. - ref = findDefaultBranch(ctx, dst) + // Reset the branch to the fetched ref + cmd = exec.CommandContext(ctx, "git", "reset", "--hard", "FETCH_HEAD") + cmd.Dir = dst + err = getRunCommand(cmd) + if err != nil { + return err } - // We have to be on a branch to pull + // Checkout ref branch if err := g.checkout(ctx, dst, ref); err != nil { return err } if depth > 0 { - cmd = exec.CommandContext(ctx, "git", "pull", "--depth", strconv.Itoa(depth), "--ff-only") + cmd = exec.CommandContext(ctx, "git", "pull", "origin", "--depth", strconv.Itoa(depth), "--ff-only", "--", ref) } else { - cmd = exec.CommandContext(ctx, "git", "pull", "--ff-only") + cmd = exec.CommandContext(ctx, "git", "pull", "origin", "--ff-only", "--", ref) } cmd.Dir = dst @@ -326,6 +361,28 @@ func checkGitVersion(ctx context.Context, min string) error { return nil } +// removeCaseInsensitiveGitDirectory removes all .git directory variations +func removeCaseInsensitiveGitDirectory(dst string) error { + files, err := os.ReadDir(dst) + if err != nil { + return fmt.Errorf("Failed to read the destination directory %s during git update", dst) + + } + for _, f := range files { + if strings.EqualFold(f.Name(), ".git") && f.IsDir() { + err := os.RemoveAll(filepath.Join(dst, f.Name())) + if err != nil { + return fmt.Errorf("Failed to remove the .git directory in the destination directory %s during git update", dst) + + } + + } + + } + return nil + +} + func (g *GitGetter) Detect(req *Request) (bool, error) { src := req.Src if len(src) == 0 { diff --git a/get_git_test.go b/get_git_test.go index e05b151bb..c4a3df0d7 100644 --- a/get_git_test.go +++ b/get_git_test.go @@ -138,6 +138,7 @@ func TestGitGetter_remoteWithoutMaster(t *testing.T) { } // Get again should work + req.Dst = testing_helper.TempDir(t) if err := g.Get(ctx, req); err != nil { t.Fatalf("err: %s", err) } @@ -841,6 +842,154 @@ func TestGitGetter_subdirectory_traversal(t *testing.T) { } } +func TestGitGetter_BadGitConfig(t *testing.T) { + if !testHasGit { + t.Log("git not found, skipping") + t.Skip() + + } + + ctx := context.Background() + g := new(GitGetter) + dst := testing_helper.TempDir(t) + + url, err := url.Parse("https://github.com/hashicorp/go-getter") + if err != nil { + t.Fatal(err) + + } + + _, err = os.Stat(dst) + if err != nil && !os.IsNotExist(err) { + t.Fatalf(err.Error()) + } + + if err == nil { + // Update the repository containing the bad git config. + // This should remove the bad git config file and initialize a new one. + err = g.update(ctx, dst, testGitToken, "main", url, 1) + + } else { + // Clone a repository with a git config file + req := &Request{ + Dst: dst, + u: url, + } + err = g.clone(ctx, testGitToken, 1, req) + if err != nil { + t.Fatalf(err.Error()) + + } + + // Edit the git config file to simulate a bad git config + gitConfigPath := filepath.Join(dst, ".git", "config") + err = os.WriteFile(gitConfigPath, []byte("bad config"), 0600) + if err != nil { + t.Fatalf(err.Error()) + + } + + // Update the repository containing the bad git config. + // This should remove the bad git config file and initialize a new one. + err = g.update(ctx, dst, testGitToken, "main", url, 1) + + } + if err != nil { + t.Fatalf(err.Error()) + + } + + // Check if the .git/config file contains "bad config" + gitConfigPath := filepath.Join(dst, ".git", "config") + configBytes, err := os.ReadFile(gitConfigPath) + if err != nil { + t.Fatalf(err.Error()) + + } + if strings.Contains(string(configBytes), "bad config") { + t.Fatalf("The .git/config file contains 'bad config'") + + } + +} + +func TestGitGetter_BadGitDirName(t *testing.T) { + if !testHasGit { + t.Log("git not found, skipping") + t.Skip() + + } + + ctx := context.Background() + g := new(GitGetter) + dst := testing_helper.TempDir(t) + + url, err := url.Parse("https://github.com/hashicorp/go-getter") + if err != nil { + t.Fatal(err) + + } + + _, err = os.Stat(dst) + if err != nil && !os.IsNotExist(err) { + t.Fatalf(err.Error()) + } + if err == nil { + // Remove all variations of .git directories + err = removeCaseInsensitiveGitDirectory(dst) + if err != nil { + t.Fatalf(err.Error()) + + } + + } else { + // Clone a repository with a git directory + req := &Request{ + Dst: dst, + u: url, + } + err = g.clone(ctx, testGitToken, 1, req) + if err != nil { + t.Fatalf(err.Error()) + + } + + // Rename the .git directory to .GIT + oldPath := filepath.Join(dst, ".git") + newPath := filepath.Join(dst, ".GIT") + err = os.Rename(oldPath, newPath) + if err != nil { + t.Fatalf(err.Error()) + + } + + // Remove all variations of .git directories + err = removeCaseInsensitiveGitDirectory(dst) + if err != nil { + t.Fatalf(err.Error()) + + } + + } + if err != nil { + t.Fatalf(err.Error()) + + } + + // Check if the .GIT directory exists + if _, err := os.Stat(filepath.Join(dst, ".GIT")); !os.IsNotExist(err) { + t.Fatalf(".GIT directory still exists") + + } + + // Check if the .git directory exists + if _, err := os.Stat(filepath.Join(dst, ".git")); !os.IsNotExist(err) { + t.Fatalf(".git directory still exists") + + } + +} + // gitRepo is a helper struct which controls a single temp git repo. type gitRepo struct { t *testing.T