github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/git/localgit/localgit.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package localgit creates a local git repo that can be used for testing code
    18  // that uses a git.Client.
    19  package localgit
    20  
    21  import (
    22  	"fmt"
    23  	"os"
    24  	"os/exec"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	v2 "sigs.k8s.io/prow/pkg/git/v2"
    29  )
    30  
    31  type Clients func() (*LocalGit, v2.ClientFactory, error)
    32  
    33  func DefaultBranch(dir string) string {
    34  	cmd := exec.Command("git", "config", "init.defaultBranch")
    35  	cmd.Dir = dir
    36  	out, err := cmd.Output()
    37  	if err != nil {
    38  		return "master"
    39  	}
    40  	return strings.TrimSpace(string(out))
    41  }
    42  
    43  // LocalGit stores the repos in a temp dir. Create with New and delete with
    44  // Clean.
    45  type LocalGit struct {
    46  	// Dir is the path to the base temp dir. Repos are at Dir/org/repo.
    47  	Dir string
    48  	// Git is the location of the git binary.
    49  	Git string
    50  	// InitialBranch is sent to git init
    51  	InitialBranch string
    52  }
    53  
    54  // Clean deletes the local git dir.
    55  func (lg *LocalGit) Clean() error {
    56  	return os.RemoveAll(lg.Dir)
    57  }
    58  
    59  func runCmd(cmd, dir string, arg ...string) error {
    60  	c := exec.Command(cmd, arg...)
    61  	c.Dir = dir
    62  	if b, err := c.CombinedOutput(); err != nil {
    63  		return fmt.Errorf("%s %v: %v, %s", cmd, arg, err, string(b))
    64  	}
    65  	return nil
    66  }
    67  
    68  func runCmdOutput(cmd, dir string, arg ...string) (string, error) {
    69  	c := exec.Command(cmd, arg...)
    70  	c.Dir = dir
    71  	b, err := c.CombinedOutput()
    72  	if err != nil {
    73  		return "", fmt.Errorf("%s %v: %v, %s", cmd, arg, err, string(b))
    74  	}
    75  	return strings.TrimSpace(string(b)), nil
    76  }
    77  
    78  // MakeFakeRepo creates the given repo and makes an initial commit.
    79  func (lg *LocalGit) MakeFakeRepo(org, repo string) error {
    80  	rdir := filepath.Join(lg.Dir, org, repo)
    81  	if err := os.MkdirAll(rdir, os.ModePerm); err != nil {
    82  		return err
    83  	}
    84  
    85  	initArgs := []string{"init"}
    86  	if lg.InitialBranch != "" {
    87  		initArgs = append(initArgs, "--initial-branch", lg.InitialBranch)
    88  	}
    89  	if err := runCmd(lg.Git, rdir, initArgs...); err != nil {
    90  		return err
    91  	}
    92  	if err := runCmd(lg.Git, rdir, "config", "user.email", "test@test.test"); err != nil {
    93  		return err
    94  	}
    95  	if err := runCmd(lg.Git, rdir, "config", "user.name", "test test"); err != nil {
    96  		return err
    97  	}
    98  	if err := runCmd(lg.Git, rdir, "config", "commit.gpgsign", "false"); err != nil {
    99  		return err
   100  	}
   101  	if err := lg.AddCommit(org, repo, map[string][]byte{"initial": {}}); err != nil {
   102  		return err
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  // AddCommit adds the files to a new commit in the repo.
   109  func (lg *LocalGit) AddCommit(org, repo string, files map[string][]byte) error {
   110  	rdir := filepath.Join(lg.Dir, org, repo)
   111  	for f, b := range files {
   112  		path := filepath.Join(rdir, f)
   113  		if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
   114  			return err
   115  		}
   116  		if err := os.WriteFile(path, b, os.ModePerm); err != nil {
   117  			return err
   118  		}
   119  		if err := runCmd(lg.Git, rdir, "add", f); err != nil {
   120  			return err
   121  		}
   122  	}
   123  	return runCmd(lg.Git, rdir, "commit", "-m", "wow")
   124  }
   125  
   126  // RmCommit adds a commit that removes some files from the repo
   127  func (lg *LocalGit) RmCommit(org, repo string, files []string) error {
   128  	rdir := filepath.Join(lg.Dir, org, repo)
   129  	for _, f := range files {
   130  		if err := runCmd(lg.Git, rdir, "rm", f); err != nil {
   131  			return err
   132  		}
   133  	}
   134  	return runCmd(lg.Git, rdir, "commit", "-m", "remove some files")
   135  }
   136  
   137  // CheckoutNewBranch does git checkout -b.
   138  func (lg *LocalGit) CheckoutNewBranch(org, repo, branch string) error {
   139  	rdir := filepath.Join(lg.Dir, org, repo)
   140  	return runCmd(lg.Git, rdir, "checkout", "-b", branch)
   141  }
   142  
   143  // Checkout does git checkout.
   144  func (lg *LocalGit) Checkout(org, repo, commitlike string) error {
   145  	rdir := filepath.Join(lg.Dir, org, repo)
   146  	return runCmd(lg.Git, rdir, "checkout", commitlike)
   147  }
   148  
   149  // RevParse does git rev-parse.
   150  func (lg *LocalGit) RevParse(org, repo, commitlike string) (string, error) {
   151  	rdir := filepath.Join(lg.Dir, org, repo)
   152  	return runCmdOutput(lg.Git, rdir, "rev-parse", commitlike)
   153  }
   154  
   155  // Merge does git merge.
   156  func (lg *LocalGit) Merge(org, repo, commitlike string) (string, error) {
   157  	rdir := filepath.Join(lg.Dir, org, repo)
   158  	return runCmdOutput(lg.Git, rdir, "merge", "--no-ff", "--no-stat", "-m merge", commitlike)
   159  }
   160  
   161  // Rebase does git rebase.
   162  func (lg *LocalGit) Rebase(org, repo, commitlike string) (string, error) {
   163  	rdir := filepath.Join(lg.Dir, org, repo)
   164  	return runCmdOutput(lg.Git, rdir, "rebase", commitlike)
   165  }
   166  
   167  // NewV2 creates a LocalGit and a v2 client factory pointing at it.
   168  func NewV2() (*LocalGit, v2.ClientFactory, error) {
   169  	g, err := exec.LookPath("git")
   170  	if err != nil {
   171  		return nil, nil, err
   172  	}
   173  	t, err := os.MkdirTemp("", "localgit")
   174  	if err != nil {
   175  		return nil, nil, err
   176  	}
   177  	c, err := v2.NewLocalClientFactory(t,
   178  		func() (name, email string, err error) { return "robot", "robot@beep.boop", nil },
   179  		func(content []byte) []byte { return content })
   180  	if err != nil {
   181  		return nil, nil, err
   182  	}
   183  	return &LocalGit{
   184  		Dir: t,
   185  		Git: g,
   186  	}, c, nil
   187  }