// 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() (string, error) { if argv.Patch.Submit { return doPatchSubmit() } if !isPatchingSafe() { return "not safe", errors.New("not safe to work on patches") } if argv.Patch.Get != nil { psets := forgepb.NewSets() newpb, _, _ := psets.HttpPostVerbose(myServer(), "get") footer, err := doPatchGet(newpb) return footer, err } s, err := doPatchList() return s, err } // submit's current working patches func doPatchSubmit() (string, error) { pset, err := me.forge.MakeDevelPatchSet("testing") if err != nil { return "MakeDevelPatchSet(testing)", err } if pset.Patches == nil { return "pset.Patches == nil", err } if pset.Patches.Len() == 0 { return "did not find any patches", nil } footer := pset.PrintTable() _, _, err = pset.HttpPost(myServer(), "new") return footer, err } func doPatchList() (string, error) { curpatches := forgepb.NewPatches() curpatches.Filename = "/tmp/curpatches.pb" if err := curpatches.Load(); err != nil { return "fix curpatches.pb", err } footer := curpatches.PrintTable() log.Info("curpatches:", footer) var needfix int 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 "isPatchIdApplied() error", patch.Error(err) } if newId == "" { // new patch ! // log.Info(patch.PatchId, "newId==''", patch.Comment) } else { if (newId == patch.PatchId) && (newHash == patch.CommitHash) { log.Info(patch.PatchId, newId, "patch made here", patch.Comment) patch.NewHash = "author" continue } if newId == patch.PatchId { patch.NewHash = patch.CommitHash log.Info(patch.PatchId, newId, "patch already applied", patch.Comment) continue } if newId != patch.PatchId { log.Info(patch.PatchId, newId, "probably duplicate subject? (mismatch)", patch.Comment) // try this. it should compute every patch id in the repo // os.Chdir(repo.FullPath) // newNewId, err := searchAllCommits(targetPatchID string) (string, error) { } } log.Info(patch.PatchId, newId, repo.Namespace, "new patch", patch.Comment) if !argv.Fix { needfix += 1 } else { log.Info(string(patch.Data)) log.Info("repo:", repo.FullPath, "patch header:", patch.Comment, patch.CommitHash) if fhelp.QuestionUser("apply this patch? (--force to autoapply)") { newhash, err := applyPatch(repo, patch) if err != nil { log.Info("apply results:", newhash, err) } if err != nil { return "git am problem. manually investigate or purge everything and start over", err } } } } var s string if needfix == 0 { s = "no new patches" } else { s = log.Sprintf("There are %d new patches. Use --fix to apply them", needfix) curpatches.Save() } return s, 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("TODO: Try to find hash value now") return "TODO", nil } func doPatchGet(newpb *forgepb.Sets) (string, error) { // 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() } 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() footer := curpatches.PrintTable() return footer, nil } 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(repo, comment, patch) 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(repo *gitpb.Repo, subject string, newpatch *forgepb.Patch) (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)) { parts := strings.Fields(line) patchId, _ := repo.FindPatchIdByHash(parts[0]) if patchId == newpatch.PatchId { return strings.Fields(line)[0], nil // return the commit hash } } } return "", fmt.Errorf("no commit found for subject: %s", subject) }