From 1b103f2a1c9beb87e61ebbd04fe7cdbf605988ed Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Sat, 17 Feb 2024 08:39:55 -0600 Subject: [PATCH] initial import --- .gitignore | 3 + Makefile | 12 +++ addRepo.go | 139 +++++++++++++++++++++++++++++ common.go | 99 +++++++++++++++++++++ new.go | 46 ++++++++++ repolist.go | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++++ scan.go | 75 ++++++++++++++++ structs.go | 55 ++++++++++++ watchdog.go | 75 ++++++++++++++++ 9 files changed, 752 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 addRepo.go create mode 100644 common.go create mode 100644 new.go create mode 100644 repolist.go create mode 100644 scan.go create mode 100644 structs.go create mode 100644 watchdog.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a630ed4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.swp +go.mod +go.sum diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2a5b530 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +all: + GO111MODULE=off go build + +goimports: + goimports -w *.go + +redomod: + rm -f go.* + goimports -w *.go + GO111MODULE= go mod init + GO111MODULE= go mod tidy + diff --git a/addRepo.go b/addRepo.go new file mode 100644 index 0000000..5d82568 --- /dev/null +++ b/addRepo.go @@ -0,0 +1,139 @@ +package repolist + +import ( + "strings" + + "go.wit.com/gui" + "go.wit.com/lib/gui/repostatus" + "go.wit.com/log" +) + +func (r *Repo) Hide() { + r.pLabel.Hide() + r.lastTag.Hide() + r.vLabel.Hide() + + r.masterVersion.Hide() + r.develVersion.Hide() + r.userVersion.Hide() + + r.dirtyLabel.Hide() + r.endBox.Hide() + // r.statusButton.Hide() + // r.diffButton.Hide() + r.hidden = true +} + +func (r *Repo) Hidden() bool { + return r.hidden +} + +func (r *Repo) Show() { + r.pLabel.Show() + r.lastTag.Show() + r.vLabel.Show() + + r.masterVersion.Show() + r.develVersion.Show() + r.userVersion.Show() + + r.dirtyLabel.Show() + r.endBox.Show() + // r.statusButton.Show() + // r.diffButton.Show() + r.hidden = false +} + +func (r *RepoList) AddRepo(path string, master string, devel string, user string) *Repo { + return addRepo(r.reposgrid, path, master, devel, user) +} + +func addRepo(grid *gui.Node, path string, master string, devel string, user string) *Repo { + _, ok := me.allrepos[path] + if ok { + log.Info("addRepo() already had path", path) + return nil + } + // log.Info("addRepo() attempting to add path", path) + rstatus := repostatus.NewRepoStatusWindow(path) + + if rstatus == nil { + // log.Info("path isn't a repo I can figure out yet", path) + // probably this isn't downloaded + return nil + } + + newRepo := new(Repo) + newRepo.status = rstatus + + path = strings.TrimSuffix(path, "/") // trim any extranous '/' chars put in the config file by the user + if path == "" { + // just an empty line in the config file + return nil + } + + newRepo.pLabel = grid.NewLabel(path).SetProgName("path") + newRepo.lastTag = grid.NewLabel("").SetProgName("lastTag") + newRepo.masterVersion = grid.NewLabel("").SetProgName("masterVersion") + newRepo.develVersion = grid.NewLabel("").SetProgName("develVersion") + newRepo.userVersion = grid.NewLabel("").SetProgName("userVersion") + newRepo.dirtyLabel = grid.NewLabel("") + newRepo.vLabel = grid.NewLabel("").SetProgName("current") + newRepo.endBox = grid.NewHorizontalBox("HBOX") + newRepo.endBox.NewButton("Configure", func() { + if newRepo.status == nil { + log.Warn("status window wasn't created") + return + } + newRepo.status.Toggle() + }) + + newRepo.endBox.NewButton("show diff", func() { + me.reposwin.Disable() + // newRepo.status.XtermNohup([]string{"git diff"}) + newRepo.status.Xterm("git diff; bash") + me.reposwin.Enable() + }) + + newRepo.endBox.NewButton("commit all", func() { + me.reposwin.Disable() + // restore anything staged so everything can be reviewed + newRepo.status.RunCmd([]string{"git", "restore", "--staged", "."}) + newRepo.status.XtermWait("git diff") + newRepo.status.XtermWait("git add --all") + newRepo.status.XtermWait("git commit -a") + newRepo.status.XtermWait("git push") + if newRepo.status.CheckDirty() { + // commit was not done, restore diff + newRepo.status.RunCmd([]string{"git", "restore", "--staged", "."}) + } else { + newRepo.newScan() + } + me.reposwin.Enable() + }) + + newRepo.hidden = false + // newRepo.status.SetMainWorkingName(master) + // newRepo.status.SetDevelWorkingName(devel) + // newRepo.status.SetUserWorkingName(user) + + var showBuildB bool = false + switch newRepo.status.RepoType() { + case "binary": + // log.Info("compile here. Show()") + showBuildB = true + case "library": + // log.Info("library here. Hide()") + default: + // log.Info("unknown RepoType", newRepo.status.RepoType()) + } + if showBuildB { + newRepo.endBox.NewButton("build", func() { + newRepo.status.Build() + }) + } + grid.NextRow() + + me.allrepos[path] = newRepo + return newRepo +} diff --git a/common.go b/common.go new file mode 100644 index 0000000..b96d85d --- /dev/null +++ b/common.go @@ -0,0 +1,99 @@ +package repolist + +import ( + "go.wit.com/lib/gui/repostatus" +) + +// deprecate this +func (r *Repo) String() string { + return r.status.String() +} + +func (r *RepoList) Hidden() bool { + return r.reposwin.Hidden() +} + +func (r *RepoList) Show() { + r.reposwin.Show() +} + +func (r *RepoList) Hide() { + r.reposwin.Hide() +} + +func (r *RepoList) AllRepos() []*Repo { + var all []*Repo + for _, repo := range me.allrepos { + all = append(all, repo) + } + return all +} + +// deprecate this +func AllRepos() []*Repo { + var all []*Repo + for _, repo := range me.allrepos { + all = append(all, repo) + } + return all +} + +func (r *Repo) Scan() bool { + return r.newScan() +} + +func (r *Repo) Exists(s string) bool { + return false +} + +func (r *Repo) MakeRedomod() { +} + +func (r *Repo) CheckoutBranch(b string) bool { + return r.status.CheckoutBranch(b) +} + +func (r *Repo) CheckDirty() bool { + return r.status.CheckDirty() +} + +func (r *Repo) IsDirty() bool { + return r.status.IsDirty() +} + +func (r *Repo) ReadOnly() bool { + return r.status.ReadOnly() +} + +func (r *Repo) IsPerfect() bool { + if r.dirtyLabel.String() == "PERFECT" { + return true + } + return false +} + +func (r *Repo) RunCmd(cmd []string) (error, string) { + return r.status.RunCmd(cmd) +} + +func (r *Repo) GetDevelBranchName() string { + return r.status.GetDevelBranchName() +} + +func (r *Repo) GetUserBranchName() string { + return r.status.GetUserBranchName() +} + +func (r *Repo) AllTags() []*repostatus.Tag { + return r.status.Tags.ListAll() +} + +func (r *Repo) TagsBox() *repostatus.GitTagBox { + return r.status.Tags +} + +// todo, fix bool return for deletetag() +func (r *Repo) DeleteTag(t *repostatus.Tag) bool { + r.status.DeleteTag(t) + return true +} diff --git a/new.go b/new.go new file mode 100644 index 0000000..4fe37dd --- /dev/null +++ b/new.go @@ -0,0 +1,46 @@ +package repolist + +import ( + "go.wit.com/lib/gadgets" + "go.wit.com/log" +) + +// This creates a view of the repos +// you can only have one at this point +func AutotypistView() *RepoList { + if me != nil { + return me + } + me = new(RepoList) + me.allrepos = make(map[string]*Repo) + + me.reposwin = gadgets.RawBasicWindow("All git repositories in ~/go/src/") + me.reposwin.Make() + + me.reposbox = me.reposwin.Box().NewBox("bw vbox", false) + // me.reposwin.Draw() + me.reposwin.Custom = func() { + log.Warn("GOT HERE: main() gadgets.NewBasicWindow() close") + log.Warn("Should I do something special here?") + } + + repoAllButtons(me.reposbox) + + me.reposgroup = me.reposbox.NewGroup("git repositories (configure in ~/.config/myrepolist)") + me.reposgrid = me.reposgroup.NewGrid("test", 0, 0) + + me.reposgrid.NewLabel("") // path goes here + + me.reposgrid.NewLabel("last tag").SetProgName("last tag") + + me.reposgrid.NewLabel("master version") + me.reposgrid.NewLabel("devel version") + me.reposgrid.NewLabel("user version") + + me.reposgrid.NewLabel("Status") + + me.reposgrid.NewLabel("Current Version").SetProgName("Current Version") + me.reposgrid.NextRow() + + return me +} diff --git a/repolist.go b/repolist.go new file mode 100644 index 0000000..2878c09 --- /dev/null +++ b/repolist.go @@ -0,0 +1,248 @@ +package repolist + +import ( + "io/ioutil" + "os" + "os/user" + "path/filepath" + "strings" + + "go.wit.com/gui" + "go.wit.com/lib/gadgets" + "go.wit.com/lib/gui/repostatus" + "go.wit.com/log" +) + +func RemoveFirstElement(slice []string) (string, []string) { + if len(slice) == 0 { + return "", slice // Return the original slice if it's empty + } + return slice[0], slice[1:] // Return the slice without the first element +} + +// returns path, master branch name, devel branch name, user branch name +func splitLine(line string) (string, string, string, string) { + var path, master, devel, user string + parts := strings.Split(line, " ") + path, parts = RemoveFirstElement(parts) + master, parts = RemoveFirstElement(parts) + devel, parts = RemoveFirstElement(parts) + user, parts = RemoveFirstElement(parts) + // path, master, devel, user := strings.Split(line, " ") + return path, master, devel, user +} + +func myrepolist() []string { + homeDir, _ := os.UserHomeDir() + cfgfile := filepath.Join(homeDir, ".config/autotypist") + content, _ := ioutil.ReadFile(cfgfile) + out := string(content) + out = strings.TrimSpace(out) + lines := strings.Split(out, "\n") + return lines +} + +// This creates a window +func RepolistWindow() *gadgets.BasicWindow { + me.reposwin = gadgets.RawBasicWindow("All git repositories in ~/go/src/") + me.reposwin.Make() + + me.reposbox = me.reposwin.Box().NewBox("bw vbox", false) + // me.reposwin.Draw() + me.reposwin.Custom = func() { + log.Warn("GOT HERE: main() gadgets.NewBasicWindow() close") + log.Warn("Should I do something special here?") + } + + repoAllButtons(me.reposbox) + + me.reposgroup = me.reposbox.NewGroup("git repositories (configure in ~/.config/myrepolist)") + me.reposgrid = me.reposgroup.NewGrid("test", 0, 0) + + me.reposgrid.NewLabel("") // path goes here + + me.reposgrid.NewLabel("last tag").SetProgName("last tag") + + me.reposgrid.NewLabel("master version") + me.reposgrid.NewLabel("devel version") + me.reposgrid.NewLabel("user version") + + me.reposgrid.NewLabel("Status") + + me.reposgrid.NewLabel("Current Version").SetProgName("Current Version") + me.reposgrid.NextRow() + + usr, _ := user.Current() + repos := myrepolist() + for _, line := range repos { + log.Verbose("repo =", line) + path, mbranch, dbranch, ubranch := splitLine(line) + if mbranch == "" { + mbranch = "master" + } + if dbranch == "" { + dbranch = "devel" + } + if ubranch == "" { + ubranch = usr.Username + } + newrepo := addRepo(me.reposgrid, path, mbranch, dbranch, ubranch) + if newrepo != nil { + // assume repos from ~/.config/autotypist file might be modified + newrepo.status.Writable() + } + me.reposgrid.NextRow() + } + + // if me.onlyMe { + // log.Info("not scanning everything") + // } else { + log.Info("scanning everything in ~/go/src") + for i, path := range repostatus.ListGitDirectories() { + // log.Info("addRepo()", i, path) + path = strings.TrimPrefix(path, me.goSrcPwd) + log.Info("addRepo()", i, path) + addRepo(me.reposgrid, path, "master", "devel", usr.Username) + me.reposgrid.NextRow() + } + // } + + return me.reposwin +} + +func showApps() { + for _, repo := range me.allrepos { + switch repo.status.RepoType() { + case "binary": + //log.Info("compile here. Show()") + repo.Show() + case "library": + //log.Info("library here. Hide()") + repo.Hide() + default: + log.Info("showApps() unknown. Show()") + repo.Hide() + } + + } +} + +func repoAllButtons(box *gui.Node) { + // reposbox.SetExpand(false) + group1 := box.NewGroup("Run on all repos:") + + hbox := group1.Box() + // hbox.Horizontal() + hbox.Vertical() + + box2 := hbox.Box().Vertical() + box2.NewButton("merge all user to devel", func() { + me.reposwin.Disable() + if !mergeAllUserToDevel() { + return + } + me.reposwin.Enable() + }) + + box2.NewButton("merge all devel to main", func() { + me.reposwin.Disable() + if !mergeAllDevelToMain() { + return + } + me.reposwin.Enable() + }) + + box2.NewButton("merge it all", func() { + me.reposwin.Disable() + if !mergeAllUserToDevel() { + return + } + if !mergeAllDevelToMain() { + return + } + me.reposwin.Enable() + }) + + box2.NewButton("test all builds", func() { + me.reposwin.Disable() + defer me.reposwin.Enable() + showApps() + for _, repo := range me.allrepos { + if repo.Hidden() { + // log.Info("skip hidden", repo.String()) + } else { + log.Info("try to build", repo.String()) + if repo.status.Build() { + log.Info("build worked", repo.String()) + } else { + log.Info("build failed", repo.String()) + go repo.status.Xterm("bash") + return + } + } + } + log.Info("") + log.Info("every build worked !!!") + log.Info("") + }) +} + +func mergeAllDevelToMain() bool { + log.Info("merge all here") + for _, repo := range me.allrepos { + if repo.status.ReadOnly() { + log.Info("skipping readonly", repo.String(), repo.dirtyLabel.String()) + continue + } + if repo.dirtyLabel.String() != "merge to main" { + log.Info("skipping. not merge to main", repo.String(), repo.dirtyLabel.String()) + continue + } + if repo.status.CheckDirty() { + log.Info("skipping dirty", repo.String(), repo.dirtyLabel.String()) + continue + } + log.Info("found", repo.String(), repo.dirtyLabel.String()) + repo.newScan() + if repo.status.MergeDevelToMaster() { + log.Warn("THINGS SEEM OK fullAutomation() returned true.") + } else { + log.Warn("last repo:", repo.status.Path()) + log.Warn("THINGS FAILED fullAutomation() returned false") + return false + } + repo.newScan() + } + log.Warn("EVERYTHING WORKED") + return true +} + +func mergeAllUserToDevel() bool { + log.Info("merge all here") + for _, repo := range me.allrepos { + if repo.status.ReadOnly() { + log.Info("skipping readonly", repo.String(), repo.dirtyLabel.String()) + continue + } + if repo.dirtyLabel.String() != "merge to devel" { + log.Info("skipping. not merge to devel", repo.String(), repo.dirtyLabel.String()) + continue + } + if repo.status.CheckDirty() { + log.Info("skipping dirty", repo.String(), repo.dirtyLabel.String()) + continue + } + log.Info("found", repo.String(), repo.dirtyLabel.String()) + repo.newScan() + if repo.status.MergeUserToDevel() { + log.Warn("THINGS SEEM OK fullAutomation() returned true.") + } else { + log.Warn("last repo:", repo.status.Path()) + log.Warn("THINGS FAILED fullAutomation() returned false") + return false + } + repo.newScan() + } + log.Warn("EVERYTHING WORKED") + return true +} diff --git a/scan.go b/scan.go new file mode 100644 index 0000000..8f36f1b --- /dev/null +++ b/scan.go @@ -0,0 +1,75 @@ +package repolist + +import ( + "fmt" + "os/user" + "strings" + + "go.wit.com/log" +) + +func ScanRepositories() (int, string) { + var i int + t := timeFunction(func() { + for _, repo := range me.allrepos { + i += 1 + repo.newScan() + } + }) + s := fmt.Sprint(t) + log.Info("Scanned", i, "repositories. todo: count/show changes", s) + return i, s +} + +func (r *Repo) newScan() bool { + if r.status == nil { + log.Warn("repo.status = nil. not initialized for some reason") + return false + } + + // first run the repostatus update + r.status.UpdateNew() + + // now read those values and display them in our table + mname := r.status.GetMasterBranchName() + mver := r.status.GetMasterVersion() + mver = mver + " (" + mname + ")" + r.masterVersion.SetLabel(mver) + + dname := r.status.GetDevelBranchName() + dver := r.status.GetDevelVersion() + if dname != "devel" { + dver = dver + " (" + dname + ")" + } + r.develVersion.SetLabel(dver) + + uname := r.status.GetUserBranchName() + uver := r.status.GetUserVersion() + usr, _ := user.Current() + if uname != usr.Username { + uver = uver + " (" + uname + ")" + } + r.userVersion.SetLabel(uver) + + cbname := r.status.GetCurrentBranchName() + cbversion := r.status.GetCurrentBranchVersion() + lasttag := r.status.GetLastTagVersion() + r.lastTag.SetLabel(lasttag) + r.vLabel.SetLabel(cbname + " " + cbversion) + + if c, ok := r.status.Changed(); ok { + c := strings.TrimSpace(c) + for _, line := range strings.Split(c, "\n") { + log.Info(r.status.Path(), line) + } + } + status := r.status.GetStatus() + r.dirtyLabel.SetLabel(status) + if status == "PERFECT" { + if me.autoHidePerfect { + r.Hide() + } + return true + } + return false +} diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..1a779a4 --- /dev/null +++ b/structs.go @@ -0,0 +1,55 @@ +package repolist + +import ( + "go.wit.com/gui" + "go.wit.com/lib/gadgets" + "go.wit.com/lib/gui/repostatus" +) + +var me *RepoList + +func (b *RepoList) Disable() { + b.reposbox.Disable() +} + +func (b *RepoList) Enable() { + b.reposbox.Enable() +} + +// this app's variables +type RepoList struct { + onlyMe bool + goSrcPwd string + autoHidePerfect bool + autoScan bool + allrepos map[string]*Repo + + reposwin *gadgets.BasicWindow + reposbox *gui.Node + reposgrid *gui.Node + reposgroup *gui.Node +} + +type Repo struct { + hidden bool + lasttagrev string + lasttag string + giturl string + + pLabel *gui.Node // path label + + lastTag *gui.Node // last tagged version label + vLabel *gui.Node // version label + dirtyLabel *gui.Node // git state (dirty or not?) + goSumStatus *gui.Node // what is the state of the go.sum file + + masterVersion *gui.Node // the master branch version + develVersion *gui.Node // the devel branch version + userVersion *gui.Node // the user branch version + + endBox *gui.Node // a general box at the end of the row + statusButton *gui.Node // opens up the status window + diffButton *gui.Node // opens up the status window + + status *repostatus.RepoStatus +} diff --git a/watchdog.go b/watchdog.go new file mode 100644 index 0000000..949d455 --- /dev/null +++ b/watchdog.go @@ -0,0 +1,75 @@ +package repolist + +import ( + "fmt" + "time" + + "go.wit.com/log" +) + +// scan repos every i seconds +// check every 'delay seconds for the checkbox changing +// this logic is unintuitive because I want it to fluidly +// never tricker quickly but also want to print something +// out that the app is alive since, technically +// the GUI is *NOT* this app and could be alive when +// the application is actually stalled somewhere +// plus these things are fun for me and a distraction when +// I've been working 50 days in a row on this gui code + +// this also means that if you click the checkbox after +// the delay, then the scan will run right away, but if +// you check the checkbox twice in 5 seconds, it won't +// rerun until the delay again +func Watchdog() { + var delay int = 99 + var i int = delay + MyTicker(1*time.Second, "newScan()", func() { + i += 1 + // check if the checkbox is checked + if !me.autoScan { + if i < delay { + i = delay + } + // print every 'delay' seconds + if i%delay == 0 { + log.Info("Not auto scanning", i) + } + return + } + if i < delay { + return + } + i = 0 + ScanRepositories() + }) +} + +// timeFunction takes a function as an argument and returns the execution time. +func timeFunction(f func()) time.Duration { + startTime := time.Now() // Record the start time + f() // Execute the function + return time.Since(startTime) // Calculate the elapsed time +} + +func MyTicker(t time.Duration, name string, f func()) { + ticker := time.NewTicker(t) + defer ticker.Stop() + done := make(chan bool) + /* + go func() { + time.Sleep(10 * time.Second) + done <- true + }() + */ + for { + select { + case <-done: + fmt.Println("Done!") + return + case t := <-ticker.C: + log.Verbose(name, "Current time: ", t) + f() + } + } +}