// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Use of this source code is governed by the GPL 3.0 package main // An app to submit patches for the 30 GO GUI repos import ( "fmt" "os" "time" "go.wit.com/gui" "go.wit.com/lib/debugger" "go.wit.com/lib/gadgets" "go.wit.com/lib/gui/shell" "go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/gitpb" "go.wit.com/log" ) func debug() { time.Sleep(2 * time.Second) for { now := time.Now() if me.repoAllB != nil { tmp := fmt.Sprintf("All (%d)", me.forge.Repos.Len()) me.repoAllB.SetLabel(tmp) } if me.repoDevelMergeB != nil { found := findMergeToDevel() tmp := fmt.Sprintf("needs merge to devel (%d)", found.Len()) me.repoDevelMergeB.SetLabel(tmp) } if me.repoWritableB != nil { found := gitpb.NewRepos() all := me.forge.Repos.SortByFullPath() for all.Scan() { repo := all.Next() if me.forge.Config.IsReadOnly(repo.GetGoPath()) { continue } found.AppendByGoPath(repo) } tmp := fmt.Sprintf("writable (%d)", found.Len()) me.repoWritableB.SetLabel(tmp) } doCheckDirtyAndConfigSave() found := findDirty() dirty := found.Len() if me.repoDirtyB != nil { tmp := fmt.Sprintf("dirty (%d)", dirty) me.repoDirtyB.SetLabel(tmp) } if me.reposWinB != nil { tmp := fmt.Sprintf("Repos (%d)", me.forge.Repos.Len()) if dirty > 0 { tmp = fmt.Sprintf("Repos (%d) (%d dirty)", me.forge.Repos.Len(), dirty) } me.reposWinB.SetLabel(tmp) } log.Printf("finished a forge scan here in (%s)\n", shell.FormatDuration(time.Since(now))) time.Sleep(90 * time.Second) } } func doGui() { if me.forge.Config.GetDefaultGui() == "" { me.forge.Config.DefaultGui = "gocui" me.forge.ConfigSave() } me.myGui = gui.New() me.myGui.InitEmbed(resources) me.myGui.SetAppDefaultPlugin(me.forge.Config.DefaultGui) // sets the default GUI plugin to use me.myGui.Default() mainWindow := gadgets.NewGenericWindow("Forge: (this kinda works sometimes)", "Current Settings") mainWindow.Custom = func() { log.Warn("MAIN WINDOW CLOSE") now := time.Now() count := me.forge.RillReload() log.Info("Repo Reload count =", count) if count != 0 { me.forge.ConfigSave() } log.Printf("rill repos.Reload() took (%s)\n", shell.FormatDuration(time.Since(now))) os.Exit(0) } drawWindow(mainWindow) // sits here forever debug() } func drawWindow(win *gadgets.GenericWindow) { grid := win.Group.RawGrid() me.goSrcPwd = gadgets.NewOneLiner(grid, "repo src home") grid.NewLabel("") var howtoWin *gadgets.GenericWindow me.demoB = grid.NewButton("Howto", func() { if howtoWin != nil { howtoWin.Toggle() return } howtoWin = makeHowtoWin() }) grid.NextRow() me.goSrcPwd.SetText(me.forge.GetGoSrc()) // use ENV GIT_AUTHOR me.gitAuthor = gadgets.NewOneLiner(grid, "Git Author") grid.NextRow() if os.Getenv("GIT_AUTHOR_NAME") == "" { me.gitAuthor.SetText("ENV GIT_AUTHOR_NAME is unset") } else { author := os.Getenv("GIT_AUTHOR_NAME") author += " <" + os.Getenv("GIT_AUTHOR_EMAIL") + ">" me.gitAuthor.SetText(author) } group1 := win.Stack.NewGroup("Forge Mode") grid = group1.RawGrid() me.forgeMode = grid.NewLabel("") me.forgeMode.SetText(me.forge.GetMode()) me.newBranch = grid.NewDropdown() me.newBranch.AddText("master") me.newBranch.AddText("devel") me.newBranch.AddText(me.forge.Config.GetUsername()) me.newBranch.Custom = func() { me.setBranchB.Disable() // toggle global values shared by the command line and the gui for doCheckout() switch me.newBranch.String() { case "master": if me.forge.Config.Mode != forgepb.ForgeMode_MASTER { me.setBranchB.Enable() } case "devel": if me.forge.Config.Mode != forgepb.ForgeMode_DEVEL { me.setBranchB.Enable() } default: if me.forge.Config.Mode != forgepb.ForgeMode_USER { me.setBranchB.Enable() } } } // select the branch you want to test, build and develop against // this lets you select your user branch, but, when you are happy // you can merge everything into the devel branch and make sure it actually // works. Then, when that is good, merge and version everything in master me.setBranchB = grid.NewButton("Switch mode", func() { win.Disable() defer win.Enable() switch me.newBranch.String() { case "master": forgeSwitchMode(forgepb.ForgeMode_MASTER) case "devel": forgeSwitchMode(forgepb.ForgeMode_DEVEL) default: forgeSwitchMode(forgepb.ForgeMode_USER) } me.setBranchB.Disable() }) me.setBranchB.Disable() grid.NextRow() groupM := win.Stack.NewGroup("Mode Windows") gridM := groupM.RawGrid() var releaseWin *gadgets.GenericWindow me.modeReleaseW = gridM.NewButton("Release Window", func() { log.Info("todo: move releaser here") log.Info("for now, run guireleaser") if releaseWin != nil { releaseWin.Toggle() return } releaseWin = makeModeMasterWin() }) me.modeReleaseW.Disable() // the Devel / Patch mode window var patchWin *patchesWindow me.modePatchW = gridM.NewButton("Patch Window", func() { if patchWin != nil { patchWin.Toggle() return } patchWin = new(patchesWindow) patchWin.initWindow() patchWin.Show() }) me.modePatchW.Disable() // the user mode "hack Window" var hackWin *gadgets.GenericWindow me.modeUserW = gridM.NewButton("Hack Window", func() { if hackWin != nil { hackWin.Toggle() return } hackWin := gadgets.NewGenericWindow("Hack / User Mode Window", "Things that might be wrong") grid := hackWin.Group.RawGrid() grid.NewButton("git pull", func() { log.Info("todo: run git pull on each repo") }) me.repoDevelMergeB = grid.NewButton("merge", func() { found := findMergeToDevel() _, box := makeStandardReposWindow("repos to merge from user to devel", found) hbox := box.Box().Horizontal() hbox.NewButton("merge all", func() { win.Disable() defer win.Enable() all := found.SortByFullPath() for all.Scan() { repo := all.Next() if repo.CheckDirty() { log.Info("repo is dirty", repo.GetGoPath()) continue } log.Info("Starting merge on", repo.GetGoPath()) if repo.CheckoutDevel() { log.Info("checkout devel failed", repo.GetGoPath()) return } if _, err := repo.MergeToDevel(); err != nil { log.Info("merge from user failed", repo.GetGoPath(), err) // log.Info(strings.Join(r.Stdout, "\n")) // log.Info(strings.Join(r.Stderr, "\n")) return } if repo.CheckoutMaster() { log.Info("checkout master failed", repo.GetGoPath()) return } if _, err := repo.MergeToMaster(); err != nil { log.Info("merge from devel failed", repo.GetGoPath(), err) return } } }) }) var problemsWin *repoProblemsWindow grid.NewButton("Repo Problems", func() { if problemsWin != nil { problemsWin.Toggle() return } problemsWin = makeRepoProblemsWindow() }) grid.NextRow() // grid := hackWin.Group.RawGrid() group2 := hackWin.Stack.NewGroup("Merge") grid = group2.RawGrid() grid.NewButton("merge to devel", func() { win.Disable() defer win.Enable() mergeUserToDevel(true) }) grid.NewButton("merge to master", func() { win.Disable() defer win.Enable() mergeDevelToMaster(true) }) grid.NewButton("merge all", func() { win.Disable() defer win.Enable() me.argvCheckoutUser = false me.argvCheckoutDevel = true me.argvCheckoutMaster = false if err := doCheckoutShared(); err != nil { log.Info("checkout error:", err) } else { log.Info("checkout was ok") } mergeUserToDevel(true) me.argvCheckoutUser = false me.argvCheckoutDevel = false me.argvCheckoutMaster = true if err := doCheckoutShared(); err != nil { log.Info("checkout error:", err) } else { log.Info("checkout was ok") } mergeDevelToMaster(true) }) group3 := hackWin.Stack.NewGroup("work in progress") grid = group3.RawGrid() grid.NewButton("forge ConfigSave()", func() { me.forge.ConfigSave() }) grid.NewButton("debugger()", func() { debugger.DebugWindow() }) }) var reposWin *gadgets.GenericWindow me.reposWinB = gridM.NewButton("Repos", func() { if reposWin != nil { reposWin.Toggle() return } reposWin = makeReposWin() }) // set the initial button state based on the last // forge mode the user saved in the config file switch me.forge.Config.Mode { case forgepb.ForgeMode_MASTER: me.newBranch.SetText("master") case forgepb.ForgeMode_DEVEL: me.newBranch.SetText("devel") case forgepb.ForgeMode_USER: me.newBranch.SetText(me.forge.Config.GetUsername()) default: me.newBranch.SetText(me.forge.Config.GetUsername()) } forgeSwitchMode(me.forge.Config.Mode) } func makeReposWin() *gadgets.GenericWindow { win := gadgets.NewGenericWindow("git repos", "All about git repos") grid := win.Group.RawGrid() me.repoDirtyB = grid.NewButton("dirty", func() { doCheckDirtyAndConfigSave() found := findDirty() tb, box := makeStandardReposWindow("dirty repos", found) hbox := box.Box().Horizontal() hbox.NewButton("commit all", func() { all := found.SortByFullPath() for all.Scan() { repo := all.Next() log.Info("do commit here on", repo.GetGoPath()) } log.Info("TODO: fix this") log.Info("run 'forge commit --all'") }) hbox.NewButton("update table", func() { me.forge.PrintHumanTable(found) found2 := findDirty() me.forge.PrintHumanTable(found2) tb.Update() tb.UpdateTable(found2) }) hbox.NewButton("delete table", func() { tb.Delete() }) }) var writeWin *gadgets.GenericWindow me.repoWritableB = grid.NewButton("writable", func() { // if the window exists, just toggle it open or closed if writeWin != nil { writeWin.Toggle() return } // make the window for the first time found := new(gitpb.Repos) all := me.forge.Repos.SortByFullPath() for all.Scan() { repo := all.Next() if me.forge.Config.IsReadOnly(repo.GetGoPath()) { continue } found.AppendByGoPath(repo) } writeWin, _ = makeWritableWindow(found) writeWin.Win.Custom = func() { log.Info("closing window. could do somethine here") } }) me.repoAllB = grid.NewButton("All", func() { me.found = new(gitpb.Repos) all := me.forge.Repos.SortByFullPath() for all.Scan() { repo := all.Next() me.found.AppendByGoPath(repo) } makeStandardReposWindow("All repos", me.found) }) grid.NewButton("Configure", func() { log.Info("add a forge config window here") }) return win } // verify the GUI button disable/enable settings func forgeVerifyGuiState() { me.forgeMode.SetText(me.forge.GetMode()) me.argvCheckoutUser = false me.argvCheckoutDevel = false me.argvCheckoutMaster = false me.modeReleaseW.Disable() me.modePatchW.Disable() me.modeUserW.Disable() switch me.forge.Config.Mode { case forgepb.ForgeMode_MASTER: me.argvCheckoutMaster = true me.newBranch.SetText("master") me.modeReleaseW.Enable() case forgepb.ForgeMode_DEVEL: me.argvCheckoutDevel = true me.newBranch.SetText("devel") me.modePatchW.Enable() case forgepb.ForgeMode_USER: me.newBranch.SetText(me.forge.Config.GetUsername()) me.argvCheckoutUser = true me.modeUserW.Enable() default: me.newBranch.SetText(me.forge.Config.GetUsername()) me.argvCheckoutUser = true me.modeUserW.Enable() } } // sets the text in the labels in the window // and hides and shows the buttons func forgeSwitchMode(newMode forgepb.ForgeMode) { if newMode == me.forge.Config.Mode { log.Info("you are already on", newMode.String()) forgeVerifyGuiState() // doing this here initializes the button state return } me.forge.Config.Mode = newMode forgeVerifyGuiState() // update the button states me.forge.Config.ConfigSave() } func doDisableUserW() { me.argvCheckoutUser = false me.modeUserW.Disable() var count int all := me.forge.Repos.SortByFullPath() for all.Scan() { repo := all.Next() if !repo.IsLocalBranch(repo.GetUserBranchName()) { // log.Info("repo doesn't have user branch", repo.GetGoPath()) continue } count += 1 } if count > 0 { s := fmt.Sprintf("git delete %d user branches", count) me.modeUserW.SetLabel(s) me.modeUserW.Enable() return } } // this is the magic that generates a window directly from the protocol buffer func makeStandardReposGrid(pb *gitpb.Repos) *gitpb.ReposTable { t := pb.NewTable("testDirty") sf := t.AddStringFunc("repo", func(r *gitpb.Repo) string { return r.GetGoPath() }) // t.Custom = func() { // log.Info("close grid?") // } sf.Custom = func(r *gitpb.Repo) { log.Info("do button click on", r.GetGoPath()) } t.AddTimeFunc("age", func(repo *gitpb.Repo) time.Time { return repo.NewestTime() }) t.AddMasterVersion() t.AddDevelVersion() t.AddUserVersion() t.AddCurrentBranchName() t.AddState() return t } // this is the magic that generates a window directly from the protocol buffer func makeStandardReposWindow(title string, pb *gitpb.Repos) (*gitpb.ReposTable, *gui.Node) { win := gadgets.RawBasicWindow(title) win.Make() win.Show() win.Custom = func() { // sets the hidden flag to false so Toggle() works win.Hide() } box := win.Box().NewBox("bw vbox", false) t := makeStandardReposGrid(pb) t.SetParent(box) t.ShowTable() return t, box } func makeWritableWindow(pb *gitpb.Repos) (*gadgets.GenericWindow, *gitpb.ReposTable) { win := gadgets.NewGenericWindow("Repos You have write access to", "Configure") t := pb.NewTable("testForgeRepos") t.NewUuid() grid := win.Group.RawGrid() grid.NewButton("git pull", func() { log.Info("todo: run git pull on each repo") }) grid.NewButton("do repos.ReScan()", func() { t.Update() }) tbox := win.Bottom.Box() t.SetParent(tbox) sf := t.AddStringFunc("repo", func(r *gitpb.Repo) string { return r.GetGoPath() }) sf.Custom = func(r *gitpb.Repo) { log.Info("do button click on", r.GetGoPath()) } t.AddTimeFunc("age", func(repo *gitpb.Repo) time.Time { return repo.NewestTime() }) t.AddMasterVersion() t.AddDevelVersion() t.AddUserVersion() t.AddCurrentBranchName() t.AddState() t.ShowTable() return win, t } func findMergeToDevel() *gitpb.Repos { found := gitpb.NewRepos() all := me.forge.Repos.SortByFullPath() for all.Scan() { repo := all.Next() // this sees if user has patches for devel. If it does, add it to me.found if repo.CountDiffObjects(repo.GetUserBranchName(), repo.GetDevelBranchName()) > 0 { found.AppendByGoPath(repo) } } now := time.Now() if found.Len() == 0 { log.Info("nothing to merge with devel") return found } // me.forge.PrintHumanTable(found) // check for merges from devel total, count, nope, _ := IsEverythingOnDevel() log.Printf("devel branch check. %d total repos. (%d ok) (%d not on devel branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now))) return found } func findMergeToMaster() { me.found = new(gitpb.Repos) all := me.forge.Repos.SortByFullPath() for all.Scan() { repo := all.Next() if me.forge.Config.IsReadOnly(repo.GetGoPath()) { continue } /* if repo.IsDirty() { continue } if repo.GetMasterVersion() != repo.GetDevelVersion() { me.found.AppendByGoPath(repo) continue } */ // this sees if devel is behind master. IT SHOULD NOT BE if repo.CountDiffObjects(repo.GetMasterBranchName(), repo.GetDevelBranchName()) == 0 { // everything is normal } else { repo.State = "DEVEL < MASTER" log.Info("SERIOUS ERROR. DEVEL BRANCH IS BEHIND MASTER", repo.GetGoPath()) } // this sees if devel has patches for master. If it does, add it to me.found if repo.CountDiffObjects(repo.GetDevelBranchName(), repo.GetMasterBranchName()) > 0 { me.found.AppendByGoPath(repo) } } now := time.Now() if me.found.Len() == 0 { log.Info("nothing to merge with master") return } me.forge.PrintHumanTable(me.found) // check for merges from devel total, count, nope, _ := IsEverythingOnMaster() log.Printf("Master branch check. %d total repos. (%d ok) (%d not on master branch) (%s)\n", total, count, nope, shell.FormatDuration(time.Since(now))) } func mergeDevelToMaster(doit bool) { findMergeToMaster() if !doit { return } all := me.found.SortByFullPath() for all.Scan() { repo := all.Next() log.Info("repo:", repo.GetGoPath()) if result, err := repo.MergeToMaster(); err == nil { log.Warn("THINGS SEEM OK", repo.GetFullPath()) for _, line := range result.Stdout { log.Warn("stdout:", line) } for _, line := range result.Stderr { log.Warn("stderr:", line) } } else { log.Warn("THINGS FAILED ", repo.GetFullPath()) log.Warn("err", err) if result == nil { break } for _, line := range result.Stdout { log.Warn("stdout:", line) } for _, line := range result.Stderr { log.Warn("stderr:", line) } log.Warn("THINGS FAILED ", repo.GetFullPath()) break } me.forge.SetConfigSave(true) // view.Update() } me.forge.ConfigSave() } func mergeUserToDevel(doit bool) { found := findMergeToDevel() if !doit { return } all := found.SortByFullPath() for all.Scan() { repo := all.Next() bruser := repo.GetUserBranchName() brdevel := repo.GetDevelBranchName() if repo.GetUserVersion() == "uerr" { // no user branch return } log.Info("trying", bruser, repo.GetUserVersion()) b1 := repo.CountDiffObjects(bruser, brdevel) // should be zero if b1 == 0 { // log.Info("User is already merged into Devel", repo.GetGoPath(), cmd) return } log.Info("merging user into devel repo:", repo.GetGoPath()) if result, err := repo.MergeToDevel(); err == nil { log.Warn("THINGS SEEM OK", repo.GetFullPath()) for _, line := range result.Stdout { log.Warn("stdout:", line) } for _, line := range result.Stderr { log.Warn("stderr:", line) } } else { log.Warn("THINGS FAILED ", repo.GetFullPath()) log.Warn("err", err) if result == nil { break } for _, line := range result.Stdout { log.Warn("stdout:", line) } for _, line := range result.Stderr { log.Warn("stderr:", line) } break } me.forge.SetConfigSave(true) // view.Update() } me.forge.ConfigSave() }