package gitpb import ( "bytes" "errors" "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/go-cmd/cmd" "go.wit.com/lib/gui/shell" "go.wit.com/log" ) // execute something with the working directory // set to the FullPath func (repo *Repo) Run(cmd []string) cmd.Status { result := shell.PathRun(repo.FullPath, cmd) output := strings.Join(result.Stdout, "\n") if result.Error != nil { log.Log(WARN, "cmd:", cmd) log.Log(WARN, "ouptput:", output) log.Log(WARN, "failed with error:", result.Error) } return result } func (repo *Repo) RunEcho(cmd []string) cmd.Status { result := shell.PathRunQuiet(repo.FullPath, cmd) log.Log(NOW, "cmd:", repo.FullPath, cmd) log.Warn(WARN, "cmd.Exit:", result.Exit, "cmd.Error:", result.Error) for _, line := range result.Stdout { log.Log(NOW, "STDOUT:", line) } for _, line := range result.Stderr { log.Log(NOW, "STDERR:", line) } return result } func (repo *Repo) RunRealtime(cmd []string) cmd.Status { return shell.PathRunRealtime(repo.GetFullPath(), cmd) } func (repo *Repo) RunRealtimeVerbose(cmd []string) cmd.Status { log.Log(NOW, "EXEC: cd", repo.GetFullPath(), ";", cmd) return shell.PathRunRealtime(repo.GetFullPath(), cmd) } func (repo *Repo) RunQuiet(cmd []string) (*cmd.Status, error) { result := shell.PathRunQuiet(repo.FullPath, cmd) if result.Error != nil { log.Warn(repo.GetGoPath(), cmd, "wow. golang is cool. an os.Error:", result.Error) return &result, result.Error } if result.Exit != 0 { // log.Warn(cmd, "failed with", result.Exit, repo.GetGoPath()) return &result, errors.New(fmt.Sprint(cmd, "failed with", result.Exit)) } return &result, nil } func (repo *Repo) RunStrict(cmd []string) (*cmd.Status, error) { result, err := repo.RunQuiet(cmd) if err != nil { log.Warn(cmd, "failed with", result.Exit, repo.GetGoPath()) return result, errors.New(fmt.Sprint(cmd, "failed with", result.Exit)) } return result, nil } func (repo *Repo) Exists(filename string) bool { if repo == nil { return false } testf := filepath.Join(repo.FullPath, filename) _, err := os.Stat(testf) if err != nil { return false } return true } func (repo *Repo) IsValidDir() bool { if repo == nil { return false } if !repo.IsGitDirectory() { return false } return true } func (repo *Repo) ReadFile(fname string) ([]byte, error) { fullname := filepath.Join(repo.FullPath, fname) return os.ReadFile(fullname) } func (repo *Repo) IsGitDirectory() bool { gitdir := filepath.Join(repo.FullPath, ".git") info, err := os.Stat(gitdir) if err != nil { return false } return info.IsDir() } func (repo *Repo) IsDirectory() bool { info, err := os.Stat(repo.FullPath) if err != nil { return false } return info.IsDir() } func (repo *Repo) RunAll(all [][]string) bool { for _, cmd := range all { log.Log(WARN, "doAll() RUNNING: cmd =", cmd) r := repo.Run(cmd) if r.Error != nil { log.Log(WARN, "doAll() err =", r.Error) log.Log(WARN, "doAll() out =", r.Stdout) return false } } return true } func (repo *Repo) RunStrictAll(all [][]string) (*cmd.Status, error) { for _, cmd := range all { log.Log(WARN, "doAll() RUNNING: cmd =", cmd) if result, err := repo.RunStrict(cmd); err != nil { return result, err } } return nil, nil } func (repo *Repo) RunVerbose(cmd []string) error { // log.Info("EXEC Running:", repo.GetGoPath(), cmd) err := shell.PathExecVerbose(repo.GetFullPath(), cmd) if err != nil { log.Info("Error", cmd, err) return err } return nil } func (repo *Repo) RunVerboseOnError(cmd []string) (*cmd.Status, error) { r, err := repo.RunStrict(cmd) if err == nil { return r, err } log.Info("Run Error:", repo.GetGoPath(), cmd, err) for _, line := range r.Stdout { log.Info(line) } for _, line := range r.Stderr { log.Info(line) } return r, err } // only safe to run len() on STDOUT // DO NOT TRY TO PARSE THIS EXCEPT HASH AS FIRST VALUE // Intended to be human readable func (repo *Repo) ConstructGitDiffLog(branch1, branch2 string) []string { var cmd []string cmd = append(cmd, "git") cmd = append(cmd, "log") cmd = append(cmd, "--format=\"%H %ae %as %s\"") cmd = append(cmd, branch1) cmd = append(cmd, "--not") cmd = append(cmd, branch2) return cmd } // count all objects only in branch1 func (repo *Repo) CountDiffObjects(branch1, branch2 string) int { cmd := repo.ConstructGitDiffLog(branch1, branch2) r, err := repo.RunVerboseOnError(cmd) if err != nil { return -1 } // log.Info("countDiffObjects()", cmd, len(r.Stdout), strings.Join(r.Stdout, " ")) return len(r.Stdout) } func (repo *Repo) RunPipe(cmd1 []string, cmd2 []string) cmd.Status { var s cmd.Status var arg0 string var args []string if len(cmd1) == 0 { s.Error = errors.New("Error: Command slice is empty.") return s } if len(cmd1) == 1 { // Pass the first element as the command, and the rest as variadic arguments arg0 = cmd1[0] } else { arg0 = cmd1[0] args = cmd1[1:] } cmdShow := exec.Command(arg0, args...) cmdShow.Dir = repo.GetFullPath() if len(cmd2) == 0 { s.Error = errors.New("Error: Command slice is empty.") return s } if len(cmd2) == 1 { // Pass the first element as the command, and the rest as variadic arguments arg0 = cmd2[0] } else { arg0 = cmd2[0] args = cmd2[1:] } // 2. Create the command to calculate the patch-id from stdin. cmdPipeID := exec.Command(arg0, args...) cmdPipeID.Dir = repo.GetFullPath() // 3. Connect the output of "git show" to the input of "git patch-id". // This is the Go equivalent of the shell pipe `|`. pipe, err := cmdShow.StdoutPipe() if err != nil { s.Error = fmt.Errorf("failed to create pipe: %w", err) return s } cmdPipeID.Stdin = pipe // 4. We need a buffer to capture the final output from git patch-id. var output bytes.Buffer cmdPipeID.Stdout = &output // 5. Start the reading command (patch-id) first. if err := cmdPipeID.Start(); err != nil { s.Error = fmt.Errorf("failed to start git-patch-id: %w", err) return s } // 6. Run the writing command (show). This will block until it's done. if err := cmdShow.Run(); err != nil { s.Error = fmt.Errorf("failed to run git-show: %w", err) return s } // 7. Wait for the reading command to finish. if err := cmdPipeID.Wait(); err != nil { s.Error = fmt.Errorf("failed to wait for git-patch-id: %w", err) return s } return s }