diff --git a/argv.go b/argv.go index f5551b3..13bedd9 100644 --- a/argv.go +++ b/argv.go @@ -45,8 +45,13 @@ type ExamineCmd struct { } type CleanCmd struct { - Force *EmptyCmd `arg:"subcommand:force" help:"dangerously delete things that are not pushed upstream"` - User *EmptyCmd `arg:"subcommand:user" help:"dump all the user branches"` + Force *EmptyCmd `arg:"subcommand:force" help:"dangerously delete things that are not pushed upstream"` + User *EmptyCmd `arg:"subcommand:user" help:"clean the user branches"` + Devel *CleanDevelCmd `arg:"subcommand:devel" help:"clean and verify the devel branches"` +} + +type CleanDevelCmd struct { + Force bool `arg:"--force" help:"try to strong arm things"` } type PatchCmd struct { diff --git a/argvAutoshell.go b/argvAutoshell.go index 1cdb613..96c5e14 100644 --- a/argvAutoshell.go +++ b/argvAutoshell.go @@ -24,7 +24,7 @@ func (args) doBashAuto() { usr, _ := user.Current() fmt.Println("user devel master " + usr.Username) case "clean": - fmt.Println("force user") + fmt.Println("devel user force") case "commit": fmt.Println("--all") case "config": diff --git a/doCheckout.go b/doCheckout.go index a4d6b2a..85e4fdb 100644 --- a/doCheckout.go +++ b/doCheckout.go @@ -11,7 +11,7 @@ var ErrorNotAllReposOnMaster error = fmt.Errorf("not all repos on are on the mas var ErrorNotAllReposOnDevel error = fmt.Errorf("not all repos on are on the devel branch") var ErrorNotAllReposOnUser error = fmt.Errorf("not all repos on are on the user branch") -func IsEverythingOnMaster() error { +func IsEverythingOnMaster() (int, int, error) { var total int var count int var nope int @@ -30,12 +30,12 @@ func IsEverythingOnMaster() error { log.Printf("Master branch check. %d total repos. (%d ok) (%d not on master branch)\n", total, count, nope) if total != count { // log.Info(ErrorNotAllReposOnMaster) - return ErrorNotAllReposOnMaster + return total, count, ErrorNotAllReposOnMaster } - return nil + return total, count, nil } -func IsEverythingOnDevel() error { +func IsEverythingOnDevel() (int, int, error) { var total int var count int @@ -50,12 +50,12 @@ func IsEverythingOnDevel() error { } log.Printf("Devel branch check. %d total repos. %d repos on the devel branch\n", total, count) if total != count { - return ErrorNotAllReposOnDevel + return total, count, ErrorNotAllReposOnDevel } - return nil + return total, count, nil } -func IsEverythingOnUser() error { +func IsEverythingOnUser() (int, int, error) { var total int var count int @@ -70,9 +70,9 @@ func IsEverythingOnUser() error { } log.Printf("User branch check. %d total repos. %d repos on the user branch\n", total, count) if total != count { - return ErrorNotAllReposOnUser + return total, count, ErrorNotAllReposOnUser } - return nil + return total, count, nil } func doGitReset() { @@ -113,7 +113,7 @@ func doAllCheckoutUser() error { if count != 0 { me.forge.ConfigSave() } - if err := IsEverythingOnUser(); err != nil { + if _, _, err := IsEverythingOnUser(); err != nil { // display all repos not on user me.found = new(gitpb.Repos) all := me.forge.Repos.SortByFullPath() @@ -149,7 +149,7 @@ func doAllCheckoutDevel() error { if count != 0 { me.forge.ConfigSave() } - if err := IsEverythingOnDevel(); err != nil { + if _, _, err := IsEverythingOnDevel(); err != nil { // display all repos not on user me.found = new(gitpb.Repos) all := me.forge.Repos.SortByFullPath() @@ -206,7 +206,7 @@ func doAllCheckoutMaster() error { me.forge.ConfigSave() } - if err := IsEverythingOnMaster(); err != nil { + if _, _, err := IsEverythingOnMaster(); err != nil { // display all repos not on master me.found = new(gitpb.Repos) all := me.forge.Repos.SortByFullPath() diff --git a/doClean.go b/doClean.go index a1c6275..7a04cac 100644 --- a/doClean.go +++ b/doClean.go @@ -10,41 +10,129 @@ import ( ) var ErrorReposHasLocalBranches error = fmt.Errorf("repo still has local branches") +var ErrorMergeBranch error = fmt.Errorf("trunk has things not in the branch") +var ErrorMergeTrunk error = fmt.Errorf("branch has things not in trunk") func doClean() error { - if err := IsEverythingOnMaster(); err != nil { - log.Info("Not all repos are on the master branch") - // return err + if argv.Clean.Devel != nil { + if err := doCleanDevel(); err != nil { + badExit(err) + } + log.Info("finished attempt at cleaning devel branches") + return nil } + if argv.Clean.User != nil { + if err := doCleanUser(); err != nil { + log.Info(err) + okExit("") + } + return nil + } + return nil +} +func doCleanUser() error { all := me.forge.Repos.SortByFullPath() for all.Scan() { repo := all.Next() - if repo.GetCurrentBranchName() != repo.GetMasterBranchName() { - // skip this while in devel - continue + if _, count, err := IsEverythingOnMaster(); err != nil { + if count == 0 { + log.Info("No repos are on the master branch") + return nil + } + log.Info("Not all repos are on the master branch") + // return err } - if repo.IsDirty() { - continue - } - if err := doCleanRepo(repo); err != nil { + + if err := doCleanUserRepo(repo); err != nil { log.Info(repo.GetGoPath(), err) - okExit("") + return err } } - log.Info("All repos on the master branch are clean") + return nil +} + +func doCleanDevel() error { + var total int + var count int + all := me.forge.Repos.SortByFullPath() + for all.Scan() { + repo := all.Next() + if argv.Verbose { + log.Info("Cleaning:", repo.GetGoPath()) + } + total += 1 + if repo.GetCurrentBranchName() != repo.GetDevelBranchName() { + // only process branches in devel + // return nil + } + if repo.IsDirty() { + return nil + } + count += 1 + if err := doCleanDevelRepo(repo); err != nil { + log.Info(repo.GetGoPath(), err) + return err + } + } + log.Printf("attempted cleaning %d branches of %d total branches\n", count, total) + return nil +} + +func doCleanDevelRepo(repo *gitpb.Repo) error { + devel := repo.GetDevelBranchName() + // log.Printf("%s Start verify devel branch: %s\n", repo.GetGoPath(), devel) + + // check if devel branch exists in remote repo + if repo.Exists(filepath.Join(".git/refs/remotes/origin", devel)) { + remote := filepath.Join("origin", devel) + if err := isBranchSubsetOfTrunk(repo, devel, remote); err != nil { + if err == ErrorMergeBranch { + log.Info("can not do this yet. need push to upstream", repo.GetGoPath()) + return nil + } + return err + } + // log.Info("todo: verify against remote devel branch", repo.GetGoPath()) + } + + // verify devel branch is subset of master branch + master := repo.GetMasterBranchName() + if err := isBranchSubsetOfTrunk(repo, devel, master); err != nil { + if err == ErrorMergeBranch { + if argv.Force { + if repo.GetCurrentBranchName() == devel { + cmd := []string{"git", "merge", master} + // only run this if branch is local + _, err := repo.RunVerbose(cmd) + return err + } else { + cmd := []string{"git", "merge", master} + log.Info("can't run. on wrong branch.", cmd, repo.GetGoPath(), "current branch =", repo.GetCurrentBranchName()) + } + } + return nil + } + return err + } + // log.Info("todo: verify against remote devel branch", repo.GetGoPath()) + return nil } // removes all local branches -func doCleanRepo(repo *gitpb.Repo) error { +func doCleanUserRepo(repo *gitpb.Repo) error { var hasLocal bool - if argv.Verbose { - log.Info("Cleaning:", repo.GetGoPath()) - } if repo.GitConfig == nil { return fmt.Errorf("GitConfig == nil") } + if repo.GetCurrentBranchName() != repo.GetMasterBranchName() { + // skip this while in devel + return nil + } + if repo.IsDirty() { + return nil + } for _, l := range repo.GitConfig.Local { log.Info("\tlocal branch name:", l.Name) @@ -83,13 +171,9 @@ func doCleanRepo(repo *gitpb.Repo) error { continue } if name == repo.GetDevelBranchName() { - /* - if err := doCleanDevelBranch(repo, b); err != nil { - log.Info("\tLOCAL BRANCH ERROR devel") - return err - } - log.Info("\tLOCAL BRANCH devel") - */ + continue + } + if name == repo.GetMasterBranchName() { continue } if err := verifyLocalBranchIsMerged(repo, b); err != nil { @@ -150,11 +234,6 @@ func verifyLocalBranchIsMerged(repo *gitpb.Repo, branch *gitpb.GitBranch) error return err } -func doCleanDevelBranch(repo *gitpb.Repo, branch *gitpb.GitBranch) error { - log.Printf("\tDo something %s on branch name:%s merge:%s remote:%s\n", repo.GetGoPath(), branch.Name, branch.Merge, branch.Remote) - return nil -} - func doCleanUserBranch(repo *gitpb.Repo, branch *gitpb.GitBranch) error { if branch.Name != repo.GetUserBranchName() { return fmt.Errorf("repo %s was not user branch %s", repo.GetGoPath(), branch.Name) @@ -364,3 +443,38 @@ func BADforceDeleteBranch(repo *gitpb.Repo, branch string) error { // return fmt.Errorf("one at a time %s", repo.GetGoPath()) return nil } + +// verifies that the branch is a pure subset of the other branch +// sorry about the 'master' 'slave' nameing thing. I guess that isn't +// 'cool' to use anymore. I can't think of other terms that aren't reserved words. +func isBranchSubsetOfTrunk(repo *gitpb.Repo, branch string, trunk string) error { + b1 := countGitDiffLog(repo, branch, trunk) // should be zero + b2 := countGitDiffLog(repo, trunk, branch) // can be greater than 1 + // log.Info(branch, "vs", trunk, "count", b1, b2) + if b1 == 0 && b2 == 0 { + // log.Info("branch and trunk are identical ==", branch, b1, trunk, b2) + return nil + } + if b1 == 0 { + cmd := []string{"git", "merge", trunk} + log.Printf("%-40s branch %s needs merge with trunk %s len(%d) %s\n", repo.GetGoPath(), branch, trunk, b2, cmd) + return ErrorMergeBranch + } + if b2 == 0 { + log.Printf("%-40s trunk %s needs merge with branch %s len(%d)\n", repo.GetGoPath(), branch, trunk, b2) + return ErrorMergeTrunk + } + return fmt.Errorf("branch not clean to delete. needs merge %d %d", b1, b2) +} + +// count all objects only in branch1 +// if zero, that means branch1 is entirely contained in branch2 and can be safely deleted +func countGitDiffLog(repo *gitpb.Repo, branch1, branch2 string) int { + cmd := repo.ConstructGitDiffLog(branch1, branch2) + r, err := repo.RunStrictNew(cmd) + if err != nil { + return -1 + } + // log.Info("countDiffObjects()", cmd, len(r.Stdout), strings.Join(r.Stdout, " ")) + return len(r.Stdout) +} diff --git a/doPull.go b/doPull.go index 44a1b82..09ade34 100644 --- a/doPull.go +++ b/doPull.go @@ -29,3 +29,23 @@ func doGitPull() { } } + +// git fetch origin master:master +func rillFetchMaster(repo *gitpb.Repo) error { + if repo.GetCurrentBranchName() != repo.GetUserBranchName() { + // only fetch when branch is on the user branch + return nil + } + branch := repo.GetMasterBranchName() + cmd := []string{"git", "fetch", "origin", branch + ":" + branch} + _, err := repo.RunVerbose(cmd) + return err +} + +func doGitFetch() { + me.forge.RillFuncError(rillFetchMaster) + count := me.forge.RillReload() + if count != 0 { + me.forge.ConfigSave() + } +} diff --git a/main.go b/main.go index 53a96a7..eb25299 100644 --- a/main.go +++ b/main.go @@ -136,8 +136,8 @@ func main() { } if argv.GitPull != nil { - argv.GitPull.findRepos() - doGitPull() + // argv.GitPull.findRepos() + doGitFetch() okExit("") } diff --git a/windowPatches.go b/windowPatches.go index 916ab66..eeae1e8 100644 --- a/windowPatches.go +++ b/windowPatches.go @@ -104,7 +104,7 @@ func submitPatchesBox(box *gui.Node) *patchSummary { if err != nil { return } - if err := IsEverythingOnDevel(); err != nil { + if _, _, err := IsEverythingOnDevel(); err != nil { log.Info("You can only apply patches to the devel branch") return }