commit 1b103f2a1c9beb87e61ebbd04fe7cdbf605988ed Author: Jeff Carr Date: Sat Feb 17 08:39:55 2024 -0600 initial import 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() + } + } +}