github.com/StackExchange/blackbox/v2@v2.0.1-0.20220331193400-d84e904973ab/pkg/vcs/git/git.go (about) 1 package git 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strings" 7 8 "github.com/StackExchange/blackbox/v2/pkg/bbutil" 9 "github.com/StackExchange/blackbox/v2/pkg/commitlater" 10 "github.com/StackExchange/blackbox/v2/pkg/makesafe" 11 "github.com/StackExchange/blackbox/v2/pkg/vcs" 12 ) 13 14 var pluginName = "GIT" 15 16 func init() { 17 vcs.Register(pluginName, 100, newGit) 18 } 19 20 // VcsHandle is the handle 21 type VcsHandle struct { 22 commitTitle string 23 commitHeaderPrinted bool // Has the "NEXT STEPS" header been printed? 24 toCommit *commitlater.List // List of future commits 25 } 26 27 func newGit() (vcs.Vcs, error) { 28 l := &commitlater.List{} 29 return &VcsHandle{toCommit: l}, nil 30 } 31 32 // Name returns my name. 33 func (v VcsHandle) Name() string { 34 return pluginName 35 } 36 37 func ultimate(s string) int { return len(s) - 1 } 38 39 // Discover returns true if we are a repo of this type; along with the Abs path to the repo root (or "" if we don't know). 40 func (v VcsHandle) Discover() (bool, string) { 41 out, err := bbutil.RunBashOutputSilent("git", "rev-parse", "--show-toplevel") 42 if err != nil { 43 return false, "" 44 } 45 if out == "" { 46 fmt.Printf("WARNING: git rev-parse --show-toplevel has NO output??. Seems broken.") 47 return false, "" 48 } 49 if out[ultimate(out)] == '\n' { 50 out = out[0:ultimate(out)] 51 } 52 return err == nil, out 53 } 54 55 // SetFileTypeUnix informs the VCS that files should maintain unix-style line endings. 56 func (v VcsHandle) SetFileTypeUnix(repobasedir string, files ...string) error { 57 seen := make(map[string]bool) 58 59 // Add to the .gitattributes in the same directory as the file. 60 for _, file := range files { 61 d, n := filepath.Split(file) 62 af := filepath.Join(repobasedir, d, ".gitattributes") 63 err := bbutil.Touch(af) 64 if err != nil { 65 return err 66 } 67 err = bbutil.AddLinesToFile(af, fmt.Sprintf("%q text eol=lf", n)) 68 if err != nil { 69 return err 70 } 71 seen[af] = true 72 } 73 74 var changedfiles []string 75 for k := range seen { 76 changedfiles = append(changedfiles, k) 77 } 78 79 v.NeedsCommit( 80 "set gitattr=UNIX "+strings.Join(makesafe.RedactMany(files), " "), 81 repobasedir, 82 changedfiles, 83 ) 84 85 return nil 86 } 87 88 // IgnoreAnywhere tells the VCS to ignore these files anywhere rin the repo. 89 func (v VcsHandle) IgnoreAnywhere(repobasedir string, files []string) error { 90 // Add to the .gitignore file in the repobasedir. 91 ignore := filepath.Join(repobasedir, ".gitignore") 92 err := bbutil.Touch(ignore) 93 if err != nil { 94 return err 95 } 96 97 err = bbutil.AddLinesToFile(ignore, files...) 98 if err != nil { 99 return err 100 } 101 102 v.NeedsCommit( 103 "gitignore "+strings.Join(makesafe.RedactMany(files), " "), 104 repobasedir, 105 []string{".gitignore"}, 106 ) 107 return nil 108 } 109 110 func gitSafeFilename(name string) string { 111 // TODO(tlim): Add unit tests. 112 // TODO(tlim): Confirm that *?[] escaping works. 113 if name == "" { 114 return "ERROR" 115 } 116 var b strings.Builder 117 b.Grow(len(name) + 2) 118 for _, r := range name { 119 if r == ' ' || r == '*' || r == '?' || r == '[' || r == ']' { 120 b.WriteRune('\\') 121 b.WriteRune(r) 122 } else { 123 b.WriteRune(r) 124 } 125 } 126 if name[0] == '!' || name[0] == '#' { 127 return `\` + b.String() 128 } 129 return b.String() 130 } 131 132 // IgnoreFiles tells the VCS to ignore these files, specified relative to RepoBaseDir. 133 func (v VcsHandle) IgnoreFiles(repobasedir string, files []string) error { 134 135 var lines []string 136 for _, f := range files { 137 lines = append(lines, "/"+gitSafeFilename(f)) 138 } 139 140 // Add to the .gitignore file in the repobasedir. 141 ignore := filepath.Join(repobasedir, ".gitignore") 142 err := bbutil.Touch(ignore) 143 if err != nil { 144 return err 145 } 146 err = bbutil.AddLinesToFile(ignore, lines...) 147 if err != nil { 148 return err 149 } 150 151 v.NeedsCommit( 152 "gitignore "+strings.Join(makesafe.RedactMany(files), " "), 153 repobasedir, 154 []string{".gitignore"}, 155 ) 156 return nil 157 } 158 159 // Add makes a file visible to the VCS (like "git add"). 160 func (v VcsHandle) Add(repobasedir string, files []string) error { 161 162 if len(files) == 0 { 163 return nil 164 } 165 166 // TODO(tlim): Make sure that files are within repobasedir. 167 168 var gpgnames []string 169 for _, n := range files { 170 gpgnames = append(gpgnames, n+".gpg") 171 } 172 return bbutil.RunBash("git", append([]string{"add"}, gpgnames...)...) 173 } 174 175 // CommitTitle indicates what the next commit title will be. 176 // This is used if a group of commits are merged into one. 177 func (v *VcsHandle) CommitTitle(title string) { 178 v.commitTitle = title 179 } 180 181 // NeedsCommit queues up commits for later execution. 182 func (v *VcsHandle) NeedsCommit(message string, repobasedir string, names []string) { 183 v.toCommit.Add(message, repobasedir, names) 184 } 185 186 // DebugCommits dumps the list of future commits. 187 func (v VcsHandle) DebugCommits() commitlater.List { 188 return *v.toCommit 189 } 190 191 // FlushCommits informs the VCS to do queued up commits. 192 func (v VcsHandle) FlushCommits() error { 193 return v.toCommit.Flush( 194 v.commitTitle, 195 func(files []string) error { 196 return bbutil.RunBash("git", append([]string{"add"}, files...)...) 197 }, 198 v.suggestCommit, 199 ) 200 // TODO(tlim): Some day we can add a command line flag that indicates that commits are 201 // to be done for real, not just suggested to the user. At that point, this function 202 // can call v.toCommit.Flush() with a function that actually does the commits instead 203 // of suggesting them. Flag could be called --commit=auto vs --commit=suggest. 204 } 205 206 // suggestCommit tells the user what commits are needed. 207 func (v *VcsHandle) suggestCommit(messages []string, repobasedir string, files []string) error { 208 if !v.commitHeaderPrinted { 209 fmt.Printf("NEXT STEP: You need to manually check these in:\n") 210 } 211 v.commitHeaderPrinted = true 212 213 fmt.Print(` git commit -m'`, strings.Join(messages, `' -m'`)+`'`) 214 fmt.Print(" ") 215 fmt.Print(strings.Join(makesafe.ShellMany(files), " ")) 216 fmt.Println() 217 return nil 218 } 219 220 // The following are "secret" functions only used by the integration testing system. 221 222 // TestingInitRepo initializes a repo. 223 func (v VcsHandle) TestingInitRepo() error { 224 return bbutil.RunBash("git", "init") 225 226 }