package gitpb import ( "bytes" "errors" "fmt" "net/url" "os/exec" "path/filepath" "strings" "go.wit.com/log" ) // scans in a new git repo. If it detects the repo is a golang project, // then it parses the go.mod/go.sum files // TODO: try adding python, rails, perl, rust, other language things? // I probably will never have time to try that, but I'd take patches for anyone // that might see this note and feel so inclined. // todo: use Repos.Lock() ? func (all *Repos) NewGoRepo(fullpath string, gopath string) (*Repo, error) { if gopath == "" { return nil, errors.New("blank gopath") } if r := all.FindByFullPath(fullpath); r != nil { log.Info("gitpb.NewGoPath() already has gopath", r.GetGoPath()) log.Info("gitpb.NewGoPath() already has FullPath", r.FullPath) // already had this gopath return r, errors.New("gitpb.NewGoPath() duplicate gopath " + gopath) } // add a new one here newr := Repo{ FullPath: fullpath, Namespace: gopath, } newr.Times = new(GitTimes) newr.GoInfo = new(GoInfo) newr.GoInfo.GoPath = gopath // everything happens in here newr.ReloadForce() newr.ValidateUTF8() if all.AppendByFullPath(&newr) { // worked return &newr, nil } else { // this is dumb, probably never happens. todo: use Repos.Lock() if r := all.FindByFullPath(fullpath); r != nil { // already had this gopath return r, errors.New("gitpb.NewGoPath() AppendUnique() failed but Find() worked" + gopath) } } // todo: use Repos.Lock() return nil, errors.New("repo gitpb.NewGoPath() should never have gotten here " + gopath) } // enforces GoPath is unique // TODO: deprecate this (?) // mutex's should finally work in the autogenpb protobuf code /* func (all *Repos) AppendByGoPathOld(newr *Repo) bool { // all.RLock() repoMu.RLock() for _, r := range all.Repos { if r.GoInfo.GoPath == newr.GoInfo.GoPath { // all.RUnlock() repoMu.RUnlock() return false } } // all.RUnlock() repoMu.RUnlock() all.Append(newr) return true } */ func (all *Repos) NewRepo(fullpath string, namespace string) (*Repo, error) { if r := all.FindByFullPath(fullpath); r != nil { log.Info("gitpb.NewRepo() might already have namespace", r.GetNamespace()) log.Info("gitpb.NewRepo() already has FullPath", r.FullPath) // already had this gopath return r, errors.New("gitpb.NewRepo() duplicate path " + fullpath) } // add a new one here newr := Repo{ FullPath: fullpath, Namespace: namespace, } newr.Times = new(GitTimes) // everything happens in here newr.ReloadForce() newr.ValidateUTF8() if all.AppendByFullPath(&newr) { // worked return &newr, nil } // todo: use Repos.Lock() return nil, errors.New("gitpb.NewRepo() append failed " + fullpath) } func NewRepo(fullpath string) (*Repo, error) { // add a new one here repo := Repo{ FullPath: fullpath, } repo.Times = new(GitTimes) // everything happens in here repo.ReloadForce() repo.ValidateUTF8() if repo.Namespace == "" { giturl := repo.GetURL() if giturl == "" { log.Info(repo.FullPath, "Namespace & URL are both blank. Warn the user of a local repo.") return &repo, nil } // log.Info("GET Namespace from URL", giturl) tmpURL, err := url.Parse(giturl) if err != nil { log.Info(repo.FullPath, "URL parse failed", giturl, err) return &repo, nil } // log.Info(repo.FullPath, "namespace might be:", tmpURL.Hostname(), tmpURL.Path) repo.Namespace = filepath.Join(tmpURL.Hostname(), tmpURL.Path) // log.Info(repo.FullPath, "Namesapce =", repo.Namespace) } return &repo, nil } func (repo *Repo) FindPatchId(hash string) (string, error) { if hash == "" { return "", log.Errorf("commit hash blank") } // 1. Create the command to get the diff for the commit. // "git show" is the perfect tool for this. cmdShow := exec.Command("git", "show", hash) cmdShow.Dir = repo.GetFullPath() // 2. Create the command to calculate the patch-id from stdin. cmdPipeID := exec.Command("git", "patch-id", "--stable") 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 { return "", fmt.Errorf("failed to create pipe: %w", err) } 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 { return "", fmt.Errorf("failed to start git-patch-id: %w", err) } // 6. Run the writing command (show). This will block until it's done. if err := cmdShow.Run(); err != nil { return "", fmt.Errorf("failed to run git-show: %w", err) } // 7. Wait for the reading command to finish. if err := cmdPipeID.Wait(); err != nil { return "", fmt.Errorf("failed to wait for git-patch-id: %w", err) } fields := strings.Fields(output.String()) if len(fields) != 2 { return "", fmt.Errorf("git-patch-id produced empty output") } if fields[1] != hash { return "", fmt.Errorf("patchid did not match %s != %v", hash, fields) } return fields[0], nil }