// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Use of this source code is governed by the GPL 3.0 package main import ( "bytes" "errors" "fmt" "os" "os/exec" "path/filepath" "regexp" "strings" "go.wit.com/lib/fhelp" "go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/gitpb" "go.wit.com/log" ) func isPatchingSafe() bool { if me.forge.Config.Mode == forgepb.ForgeMode_NORMAL { return true } log.Info("This patch command is not safe to run now") log.Info("you must reset the state of your git repositories. Run:") log.Info("") log.Info("forge normal (or use --force)") log.Info("") if argv.Force { return true } return false } func doPatch() error { if argv.Patch.Submit != nil { return doPatchSubmit() } if !isPatchingSafe() { return log.Errorf("not safe to work on patches") } if argv.Patch.Get != nil { psets := forgepb.NewSets() newpb, _, _ := psets.HttpPostVerbose(myServer(), "get") doPatchGet(newpb) return nil } if argv.Patch.List != nil { err := doPatchList() return err } err := doPatchList() return err } // submit's current working patches func doPatchSubmit() error { pset, err := me.forge.MakeDevelPatchSet("testing") if err != nil { return err } if pset.Patches == nil { log.Info("pset.Patches == nil") return err } if pset.Patches.Len() == 0 { log.Info("did not find any patches") return nil } pset.PrintTable() _, _, err = pset.HttpPost(myServer(), "new") return err } func doPatchList() error { curpatches := forgepb.NewPatches() curpatches.Filename = "/tmp/curpatches.pb" if err := curpatches.Load(); err != nil { return err } curpatches.PrintTable() for patch := range curpatches.IterAll() { repo := me.forge.Repos.FindByNamespace(patch.Namespace) if repo == nil { log.Info("no namespace", patch.PatchId, patch.Namespace, patch.Comment) continue } newId, newHash, err := isPatchIdApplied(repo, patch) if errors.Is(err, ErrorGitPullOnDirty) { log.Info("a patch with that comment couldn't be found in the repo") } else if err != nil { log.Info("err", patch.PatchId, patch.Namespace, patch.Comment, err) return patch.Error(err) } if (newId == patch.PatchId) && (newHash == patch.CommitHash) { log.Info(patch.PatchId, "patch made here", patch.Comment) continue } if newId == patch.PatchId { log.Info(patch.PatchId, "patch already applied", patch.Comment) continue } if newId != patch.PatchId { log.Info(patch.PatchId, "probably duplicate subject", patch.Comment) } log.Info("new patch", patch.PatchId, patch.Comment) if !argv.Fix { log.Info("use --fix to attempt to apply new patches") } else { if fhelp.QuestionUser("apply this patch?") { newhash, err := applyPatch(repo, patch) log.Info("apply results:", newhash, err) if err != nil { return err } } } } return nil } func applyPatch(repo *gitpb.Repo, p *forgepb.Patch) (string, error) { _, filen := filepath.Split(p.Filename) tmpname := filepath.Join("/tmp", filen) log.Info("saving as", tmpname, p.Filename) raw, err := os.OpenFile(tmpname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return "", err } raw.Write(p.Data) raw.Close() cmd := []string{"git", "am", tmpname} err = repo.RunVerbose(cmd) if err != nil { log.Info("git am failed. run 'git am --abort' here") return "", log.Errorf("git am failed") } log.Info("Try to find hash value now") return p.NewHash, log.Errorf("did not lookup new hash") } func doPatchGet(newpb *forgepb.Sets) { // var changed bool curpatches := forgepb.NewPatches() curpatches.Filename = "/tmp/curpatches.pb" if err := curpatches.Load(); err != nil { curpatches.Save() curpatches.Save() log.Info(err) panic("no file") // return // // THIS IS NEEDED? NOTSURE curpatches = forgepb.NewPatches() curpatches.Filename = "/tmp/curpatches.pb" curpatches.Save() } // newpb.PrintTable() for pset := range newpb.IterAll() { if pset.Patches.Len() == 0 { log.Info("pset is empty", pset.Name) continue } for patch := range pset.Patches.IterAll() { if len(patch.Data) == 0 { continue } patchid, hash, err := gitpb.FindPatchIdFromGitAm(patch.Data) if err != nil { log.Info("git patchid exec err", err) continue } if hash != patch.CommitHash { log.Info("ERROR: patch commit hashes's didn't match", hash, patch.CommitHash) continue } if patchid != patch.PatchId { log.Info("ERROR: patchid's didn't match", patchid, patch.PatchId) continue } found := curpatches.FindByPatchId(patch.PatchId) if found != nil { // already have this patch continue } // gitpb.FindPatchIdFromGitAmBroken(patch.Data) // doesn't os.Exec() log.Info("adding new patch", patch.CommitHash, patch.PatchId, patch.Filename) curpatches.AppendByPatchId(patch) } } curpatches.Save() curpatches.PrintTable() // me.forge.Patchsets = newpb // me.forge.Patchsets.Save() } var ErrorGitPullOnDirty error = errors.New("git comment is not there") func isPatchIdApplied(repo *gitpb.Repo, patch *forgepb.Patch) (string, string, error) { comment := cleanSubject(patch.Comment) os.Chdir(repo.GetFullPath()) newhash, err := findCommitBySubject(comment) if err != nil { return "", "", ErrorGitPullOnDirty } patchId, err := repo.FindPatchIdByHash(newhash) if err != nil { return "", "", err } // log.Infof("%s %s found hash by comment %s \n", patchId, newhash, patch.Comment) return patchId, newhash, nil } // Shows repos that are: // - git dirty repos // - repos with 'user' branch patches not in 'devel' branch // - repos with awaiting master branch verions // // return true if any are found func showWorkRepos() bool { // always run dirty first me.forge.CheckDirtyQuiet() // if no option is given to patch, list out the // repos that have patches ready in them found := findReposWithPatches() found.SortNamespace() if found.Len() == 0 { log.Info("you currently have no repos with patches") return false } else { footer := me.forge.PrintDefaultTB(found) log.Info("repos with patches or unsaved changes:", footer) } return true } func cleanSubject(line string) string { // Regular expression to remove "Subject:" and "[PATCH...]" patterns re := regexp.MustCompile(`(?i)^Subject:\s*(\[\s*PATCH[^\]]*\]\s*)?`) cleaned := re.ReplaceAllString(line, "") return strings.TrimSpace(cleaned) } // jcarr@framebook:~/go/src/go.wit.com/lib/protobuf/forgepb$ git branch --contains 4a27e7702b9b975b066ec9d2ee7ac932d86552e3 // * jcarr // jcarr@framebook:~/go/src/go.wit.com/lib/protobuf/forgepb$ git merge-base --is-ancestor "4a27e7702b9b975b066ec9d2ee7ac932d86552e3" "devel" ; echo $? // 1 // jcarr@framebook:~/go/src/go.wit.com/lib/protobuf/forgepb$ git merge-base --is-ancestor "4a27e7702b9b975b066ec9d2ee7ac932d86552e3" "jcarr" ; echo $? // 0 func findCommitBySubject(subject string) (string, error) { cmd := exec.Command("git", "log", "--pretty=format:%H %s", "--grep="+subject, "-i") var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() if err != nil { return "", err } lines := strings.Split(out.String(), "\n") for _, line := range lines { if strings.Contains(strings.ToLower(line), strings.ToLower(subject)) { return strings.Fields(line)[0], nil // return the commit hash } } return "", fmt.Errorf("no commit found for subject: %s", subject) }