// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Use of this source code is governed by the GPL 3.0 package main import ( "os" "path/filepath" "sync" "go.wit.com/lib/gadgets" "go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/gitpb" "go.wit.com/log" "go.wit.com/gui" ) type patchWindow struct { once sync.Once // only init() the window once win *gadgets.BasicWindow // the patches window stack *gui.Node // the top box set as vertical shelf *gui.Node // the first box in the stack, set as horizontal grid *gui.Node // the list of available patches // summary *patchSummary // summary of current patches setgrid *gui.Node // the list of each patchset pset *forgepb.Patchset // the patchset in question } // todo: autogenerate these or make them standared 'gui' package functions // make this an go interface somehow // is the window hidden right now? func (w *patchWindow) Hidden() bool { return w.win.Hidden() } // switches between the window being visable or hidden on the desktop func (w *patchWindow) Toggle() { if w.Hidden() { w.Show() } else { w.Hide() } } // hides the window completely func (w *patchWindow) Show() { w.win.Show() } func (w *patchWindow) Hide() { w.win.Hide() } // should be the first box/widget in the window // greys out the window to the user func (w *patchWindow) Disable() { w.stack.Disable() } func (w *patchWindow) Enable() { w.stack.Enable() } // you can only have one of these func makePatchWindow(pset *forgepb.Patchset) *patchWindow { pw := new(patchWindow) // sync.Once() pw.win = gadgets.RawBasicWindow("Patcheset for " + pset.Name + " (" + pset.Comment + ")") pw.win.Make() pw.stack = pw.win.Box().NewBox("bw vbox", false) // me.reposwin.Draw() pw.win.Custom = func() { // sets the hidden flag to false so Toggle() works pw.win.Hide() } grid := pw.stack.NewGrid("", 0, 0) grid.NewLabel("Patchset Details:") grid.NewLabel(pset.GitAuthorName) grid.NewLabel(pset.GitAuthorEmail) grid.NewLabel("start branch: " + pset.StartBranchName) grid.NewLabel(pset.StartBranchHash) grid.NewLabel("end branch: " + pset.EndBranchName) grid.NewLabel(pset.EndBranchHash) grid.NextRow() g := pw.stack.NewGroup("PatchSet List") // make a grid to put the list of git repos that have patches // in this particular patchset grid = g.NewGrid("", 0, 0) grid.NewLabel("repo") grid.NewLabel("patch name") grid.NewLabel("Applied in current branch?") grid.NewLabel("start hash") grid.NewLabel("") /* grid.NewButton("Extract", func() { if err := savePatchset(pset); err != nil { log.Info("Save err:", err) return } }) grid.NewButton("Apply with git am", func() { if _, _, _, err := IsEverythingOnUser(); err != nil { log.Info("You can only apply patches to the user branch") return } if IsAnythingDirty() { log.Info("You can't apply patches when repos are dirty") me.forge.PrintHumanTable(me.found) return } if argv.Force { applyPatchset(pset) } }) */ grid.NextRow() // add the patches to the grid pw.addPatchset(grid, pset) return pw } func (r *patchWindow) addPatchset(grid *gui.Node, pset *forgepb.Patchset) { /* repomap := make(map[*gitpb.Repo][]*forgepb.Patch) repohash := make(map[*gitpb.Repo]string) // sort patches by repo namespace all := pset.Patches.SortByFilename() for all.Scan() { p := all.Next() s := p.RepoNamespace repo := me.forge.FindByGoPath(s) if repo == nil { log.Info("Could not figure out repo path", s) continue } repomap[repo] = append(repomap[repo], p) repohash[repo] = p.StartHash } */ all := pset.Patches.SortByFilename() for all.Scan() { p := all.Next() // for repo, patches := range repomap { rn := p.RepoNamespace repo := me.forge.FindByGoPath(rn) if repo == nil { log.Info("Could not figure out repo path", rn) rn += " bad repo" } log.Info("Adding patches for", rn) grid.NewLabel(rn) /* for i, p := range patches { log.Info(i, p.Filename) grid.NewLabel(p.Comment) grid.NewLabel("in current branch?") break } */ // hash := repohash[repo] grid.NewLabel(p.StartHash) grid.NewLabel(p.Filename) if repo == nil { continue } // var win *repoPatchWindow grid.NewButton("apply", func() { filename, _ := savePatch(p) if err := applyPatch(repo, filename); err != nil { log.Info("warn user of git am error", err) } // win = makeRepoPatchWindow(repo, p) // win.Show() }) grid.NewCheckbox("").SetChecked(true) grid.NewCheckbox("").SetChecked(true) grid.NewButton("save patch to /tmp", func() { savePatch(p) // for _, pat := range patches { // } }) /* grid.NewButton("view hash", func() { cmd := []string{"git", "whatchanged", hash} log.Info(repo.GetFullPath(), cmd) }) grid.NewButton("git am", func() { cmd := []string{"git", "whatchanged", hash} log.Info(repo.GetFullPath(), cmd) }) */ grid.NextRow() } } func applyPatch(repo *gitpb.Repo, filename string) error { cmd := []string{"git", "am", filename} _, err := repo.RunVerbose(cmd) return err } func savePatch(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() return tmpname, nil } // saves the patches in ~/.config/forge/currentpatches/ func savePatchset(pset *forgepb.Patchset) error { log.Info("savePatches() NAME", pset.Name) log.Info("savePatches() COMMENT", pset.Comment) log.Info("savePatches() GIT_AUTHOR_NAME", pset.GetGitAuthorName()) log.Info("savePatches() GIT_AUTHOR_EMAIL", pset.GetGitAuthorEmail()) log.Info("savePatches() Branch Name", pset.GetStartBranchName()) log.Info("savePatches() Start Hash", pset.GetStartBranchHash()) var count int var bad int var lasterr error all := pset.Patches.SortByFilename() for all.Scan() { p := all.Next() basedir := filepath.Join(os.Getenv("FORGE_CONFIG"), "currentpatches") if fullname, err := savePatchFile(p, basedir); err != nil { log.Info(fullname, "save failed", err) bad += 1 lasterr = err } count += 1 } log.Info("pset has", count, "total patches, ", bad, "bad save patches") if bad == 0 { return lasterr } return nil }