github.com/adevinta/lava@v0.7.2/internal/gitserver/gittest/gittest.go (about) 1 // Copyright 2023 Adevinta 2 3 // Package gittest provides utilities for Git testing. 4 package gittest 5 6 import ( 7 "archive/tar" 8 "bytes" 9 "errors" 10 "fmt" 11 "io" 12 "io/fs" 13 "os" 14 "os/exec" 15 "path/filepath" 16 ) 17 18 // CloneTemp clones the specified local repository into a temporary 19 // directory. It returns the temporary directory. 20 func CloneTemp(path string) (tmpPath string, err error) { 21 tmpPath, err = os.MkdirTemp("", "") 22 if err != nil { 23 return "", fmt.Errorf("make temp dir: %w", err) 24 } 25 defer func() { 26 if err != nil { 27 if rmErr := os.RemoveAll(tmpPath); rmErr != nil { 28 err = errors.Join(err, fmt.Errorf("remove temp dir %s: %w", path, rmErr)) 29 } 30 } 31 }() 32 33 buf := &bytes.Buffer{} 34 cmd := exec.Command("git", "clone", path, tmpPath) 35 cmd.Stderr = buf 36 if err = cmd.Run(); err != nil { 37 return "", fmt.Errorf("git clone %v: %w: %#q", path, err, buf) 38 } 39 40 return tmpPath, nil 41 } 42 43 // ExtractTemp extracts the provided tar archive into a temporary 44 // directory. It returns the temporary directory. Test repositories 45 // are distributed as tar files. 46 func ExtractTemp(tarfile string) (tmpPath string, err error) { 47 tmpPath, err = os.MkdirTemp("", "") 48 if err != nil { 49 return "", fmt.Errorf("make temp dir: %w", err) 50 } 51 defer func() { 52 if err != nil { 53 if rmErr := os.RemoveAll(tmpPath); rmErr != nil { 54 err = errors.Join(err, fmt.Errorf("remove temp dir %s: %w", tmpPath, rmErr)) 55 } 56 } 57 }() 58 59 if err = untar(tarfile, tmpPath); err != nil { 60 return "", fmt.Errorf("untar test repository: %w", err) 61 } 62 63 return tmpPath, nil 64 } 65 66 // untar extracts the specified tar file into path. 67 func untar(tarfile string, path string) error { 68 f, err := os.Open(tarfile) 69 if err != nil { 70 return fmt.Errorf("open tar file: %w", err) 71 } 72 defer f.Close() 73 74 tr := tar.NewReader(f) 75 for { 76 hdr, err := tr.Next() 77 if err == io.EOF { 78 // End of archive. 79 break 80 } 81 if err != nil { 82 return fmt.Errorf("next entry: %w", err) 83 } 84 85 target := filepath.Join(path, hdr.Name) 86 87 switch hdr.Typeflag { 88 case tar.TypeDir: 89 if err := os.MkdirAll(target, 0755); err != nil { 90 return fmt.Errorf("create dir: %w", err) 91 } 92 case tar.TypeReg: 93 if err = writeFile(target, fs.FileMode(hdr.Mode), tr); err != nil { 94 return fmt.Errorf("create file: %w", err) 95 } 96 default: 97 return fmt.Errorf("unexpected type flag: %v", hdr.Typeflag) 98 } 99 } 100 101 return nil 102 } 103 104 // writeFile writes the data from the reader to the named file until 105 // EOF is reached, creating the file if necessary. If the file does 106 // not exist, writeFile creates it with permissions perm (before 107 // umask); otherwise writeFile truncates it before writing, without 108 // changing permissions. 109 func writeFile(name string, mode fs.FileMode, r io.Reader) error { 110 f, err := os.OpenFile(name, os.O_CREATE|os.O_TRUNC|os.O_RDWR, mode) 111 if err != nil { 112 return fmt.Errorf("open destination file: %w", err) 113 } 114 defer f.Close() 115 116 if _, err = io.Copy(f, r); err != nil { 117 return fmt.Errorf("write file contents: %w", err) 118 } 119 return nil 120 }