github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/util/git.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package util 21 22 import ( 23 "bytes" 24 "io" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "strings" 29 30 "github.com/go-git/go-git/v5" 31 "github.com/go-git/go-git/v5/plumbing" 32 "github.com/pkg/errors" 33 "k8s.io/klog/v2" 34 ) 35 36 // CloneGitRepo clones git repo to local path 37 func CloneGitRepo(url, branch, path string) error { 38 pullFunc := func(repo *git.Repository) error { 39 // Get the working directory for the repository 40 w, err := repo.Worktree() 41 if err != nil { 42 return err 43 } 44 // Pull the latest changes from the origin remote 45 err = w.Pull(&git.PullOptions{ 46 RemoteName: "origin", 47 Progress: os.Stdout, 48 ReferenceName: plumbing.NewBranchReferenceName(branch), 49 SingleBranch: true, 50 }) 51 if err != git.NoErrAlreadyUpToDate && err != git.ErrUnstagedChanges { 52 return err 53 } 54 return nil 55 } 56 57 // check if local repo path already exists 58 repo, err := git.PlainOpen(path) 59 60 // repo exists, pull it 61 if err == nil { 62 if err = pullFunc(repo); err != nil { 63 return err 64 } 65 } 66 67 if err != git.ErrRepositoryNotExists { 68 return err 69 } 70 71 // repo does not exists, clone it 72 _, err = git.PlainClone(path, false, &git.CloneOptions{ 73 URL: url, 74 Progress: os.Stdout, 75 ReferenceName: plumbing.NewBranchReferenceName(branch), 76 SingleBranch: true, 77 }) 78 return err 79 } 80 81 func GitGetRemoteURL(dir string) (string, error) { 82 return ExecGitCommand(dir, "config", "--get", "remote.origin.url") 83 } 84 85 // EnsureCloned clones into the destination path, otherwise returns no error. 86 func EnsureCloned(uri, destinationPath string) error { 87 if ok, err := IsGitCloned(destinationPath); err != nil { 88 return err 89 } else if !ok { 90 _, err = ExecGitCommand("", "clone", "-v", uri, destinationPath) 91 return err 92 } 93 return nil 94 } 95 96 // IsGitCloned tests if the path is a git dir. 97 func IsGitCloned(gitPath string) (bool, error) { 98 f, err := os.Stat(filepath.Join(gitPath, ".git")) 99 if os.IsNotExist(err) { 100 return false, nil 101 } 102 return err == nil && f.IsDir(), err 103 } 104 105 // EnsureUpdated ensures the destination path exists and is up to date. 106 func EnsureUpdated(uri, destinationPath string) error { 107 if err := EnsureCloned(uri, destinationPath); err != nil { 108 return err 109 } 110 return UpdateAndCleanUntracked(destinationPath) 111 } 112 113 // UpdateAndCleanUntracked fetches origin and sets HEAD to origin/HEAD 114 // and also creates a pristine working directory by removing 115 // untracked files and directories. 116 func UpdateAndCleanUntracked(destinationPath string) error { 117 if _, err := ExecGitCommand(destinationPath, "fetch", "-v"); err != nil { 118 return errors.Wrapf(err, "fetch index at %q failed", destinationPath) 119 } 120 121 if _, err := ExecGitCommand(destinationPath, "reset", "--hard", "@{upstream}"); err != nil { 122 return errors.Wrapf(err, "reset index at %q failed", destinationPath) 123 } 124 125 _, err := ExecGitCommand(destinationPath, "clean", "-xfd") 126 return errors.Wrapf(err, "clean index at %q failed", destinationPath) 127 } 128 129 // ExecGitCommand executes a git command in the given directory. 130 func ExecGitCommand(pwd string, args ...string) (string, error) { 131 klog.V(4).Infof("Going to run git %s", strings.Join(args, " ")) 132 cmd := exec.Command("git", args...) 133 cmd.Dir = pwd 134 buf := bytes.Buffer{} 135 var w io.Writer = &buf 136 if klog.V(2).Enabled() { 137 w = io.MultiWriter(w, os.Stderr) 138 } 139 cmd.Stdout, cmd.Stderr = w, w 140 if err := cmd.Run(); err != nil { 141 return "", errors.Wrapf(err, "command execution failure, output=%q", buf.String()) 142 } 143 return strings.TrimSpace(buf.String()), nil 144 }