// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Use of this source code is governed by the GPL 3.0 package main import ( "errors" "fmt" "path/filepath" "go.wit.com/lib/fhelp" "go.wit.com/lib/protobuf/forgepb" "go.wit.com/lib/protobuf/gitpb" "go.wit.com/log" ) func doResetRepo(repo *gitpb.Repo) error { if repo.GetCurrentBranchName() != repo.GetMasterBranchName() { return log.Errorf("not on master branch") } if repo.IsDirty() { return log.Errorf("repo is dirty") } // when publishing, clean out the details of that if it's still there if repo.GetTargetVersion() != "" { repo.SetTargetVersion("") } // try to delete user if err := doRepoCleanUser(repo); err != nil { return err } // try to delete devel err := doRepoCleanDevel(repo) return err } // reverts all repos back to the original master branches // automatically deletes local devel and user branches func doClean() (string, error) { var s string var err error if argv.Clean.List != nil { s, err = doCleanList() return s, err } if me.forge.IsModeNormal() { s := fmt.Sprintf("Reset all (%d) git repos to the original state (non-destructive)?", me.forge.Repos.Len()) if !fhelp.QuestionUser(s) { s, err = doModeMaster() } } me.forge.SetMode(forgepb.ForgeMode_CLEAN) // fix this to work, then delete all the other options for "forge clean' if err := me.forge.DoAllCheckoutMaster(); err != nil { // badExit(err) } me.forge.RescanRepos() // looks for new dirs, checks existing repos for changes all := me.forge.Repos.SortByFullPath() for all.Scan() { repo := all.Next() doResetRepo(repo) } me.forge.RescanRepos() // looks for new dirs, checks existing repos for changes return "all repos should be clean", nil } func doCleanList() (string, error) { found := gitpb.NewRepos() total := 0 // find all repos that aren't "clean" for repo := range me.forge.Repos.IterByFullPath() { total += 1 // find repos not on master branch if repo.GetCurrentBranchName() != repo.GetMasterBranchName() { found.AppendByFullPath(repo) continue } // find dirty repos if repo.IsDirty() { found.AppendByFullPath(repo) continue } // find repos that still have a local user branch if repo.IsLocalBranch(repo.GetUserBranchName()) { found.AppendByFullPath(repo) continue } // find repos that still have a local devel branch if repo.IsLocalBranch(repo.GetDevelBranchName()) { found.AppendByFullPath(repo) continue } } var s string if found.Len() == 0 { s = log.Sprintf("%d repos are not clean", found.Len()) } else { s = log.Sprintf("All %d repos are clean", me.forge.Repos.Len()) } return s, nil } func doRepoCleanDevel(repo *gitpb.Repo) error { if !repo.IsLocalBranch(repo.GetDevelBranchName()) { // there is no local branch named 'devel' return nil } if repo.GetCurrentBranchName() != repo.GetMasterBranchName() { return log.Errorf("%s not on master branch:", repo.GetFullPath()) } if repo.IsDirty() { return log.Errorf("%s is dirty:", repo.GetFullPath()) } if err := justDeleteTheDevelBranchAlready(repo); err != nil { log.Info("justDeleteTheDevel() err", repo.Namespace, err) return err } return nil } // removes all local user branches func doRepoCleanUser(repo *gitpb.Repo) error { if repo.IsDirty() { return nil } bruser := repo.GetUserBranchName() brdevel := repo.GetDevelBranchName() brmaster := repo.GetMasterBranchName() if repo.IsBranchRemote(bruser) { b2, err := repo.CountDiffObjects("refs/heads/"+bruser, "refs/heads/"+brmaster) // should be zero if err != nil { log.Info(repo.FullPath, "doRepoCleanUser() bad branches") return err } if b2 == 0 { log.Info("Local remote branch is safe to delete (is completely in master)", bruser) cmd := []string{"git", "branch", "-D", bruser} s := log.Sprintf("Run: %v in %s", cmd, repo.FullPath) if !argv.Force { if fhelp.QuestionUser(s) { repo.RunVerbose([]string{"git", "branch", "-D", bruser}) // repo.RunVerbose([]string{"git", "checkout", bname}) } } else { repo.RunVerbose([]string{"git", "branch", "-D", bruser}) } } log.Info("forge is designed to always have local only user branches", bruser) return fmt.Errorf("forge is designed to always have local only user branches") } if !repo.IsLocalBranch(bruser) { // there is no local user branch return nil } // will you loose work if you delete your user branch? // if DevelBranchExists() // then if UserBranchCommits exist in DevelBranch // DeleteUserBranch is safe if repo.IsLocalBranch(brdevel) { b1, err := repo.CountDiffObjects(bruser, "refs/heads/"+brdevel) // should be zero if err != nil { log.Info(repo.FullPath, "doRepoCleanUser() bad branches") return err } if b1 == 0 { // every user branch exists in devel. delete user branch cmd := []string{"git", "branch", "-D", bruser} // log.Info("USER IS IN DEVEL", repo.Namespace, cmd) _, err := repo.RunVerboseOnError(cmd) return err } } // will you loose work if you delete your user branch? // if master branch exists() // then if all user commits exist in master // delete user branch is safe if repo.IsLocalBranch(brmaster) { b1, err := repo.CountDiffObjects(bruser, "refs/heads/"+brmaster) // should be zero if err != nil { log.Info(repo.FullPath, "doRepoCleanUser() bad branches") return err } if b1 == 0 { cmd := []string{"git", "branch", "-D", bruser} // log.Info("USER IS IN DEVEL", repo.Namespace, cmd) _, err := repo.RunVerboseOnError(cmd) return err } } return ErrorBranchUnique } var ErrorBranchUnique error = errors.New("branch has unique commits") // if you call this, there is no going back. no checks anymore. nothing // it deletes the 'devel' branch. git branch -D "devel". END OF STORY func justDeleteTheDevelBranchAlready(repo *gitpb.Repo) error { branch := repo.GetDevelBranchName() remote := filepath.Join("origin", branch) if me.forge.Config.IsReadOnly(repo.Namespace) { if repo.IsDevelRemote() { // just make sure the remote & local branches are the same return repo.DeleteLocalDevelBranch() } } // check against remote if it exists if repo.IsDevelRemote() { b1, err := repo.CountDiffObjects(branch, remote) // should be zero if err != nil { log.Info(repo.FullPath, "doRepoCleanUser() bad branches") return err } if b1 == 0 { cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()} // log.Info("DEVEL IS IN REMOTE", repo.Namespace, cmd) _, err := repo.RunVerboseOnError(cmd) return err } cmd := []string{"git", "push"} log.Info("DEVEL LOCAL NEEDS GIT PUSH TO REMOTE", repo.Namespace, cmd) err = repo.RunVerbose(cmd) return err } // remote doesn't exist, check against master master := repo.GetMasterBranchName() b1, err := repo.CountDiffObjects(branch, "refs/heads/"+master) // should be zero if err != nil { log.Info(repo.FullPath, "doRepoCleanUser() bad branches") return err } if b1 == 0 { cmd := []string{"git", "branch", "-D", repo.GetDevelBranchName()} // log.Info("DEVEL IS IN REMOTE", repo.Namespace, cmd) _, err := repo.RunVerboseOnError(cmd) return err } cmd := []string{"git", "merge something somehow"} log.Info("devel local, remote and master branches are wrong", repo.Namespace, cmd, b1) // _, err := repo.RunVerbose(cmd) return nil } func doGitReset() { all := me.forge.Repos.SortByFullPath() for all.Scan() { repo := all.Next() if me.forge.Config.IsReadOnly(repo.Namespace) { // log.Info("is readonly", repo.Namespace) if repo.CheckDirty() { log.Info("is readonly and dirty", repo.Namespace) cmd := []string{"git", "reset", "--hard"} repo.RunRealtime(cmd) } } else { // log.Info("is not readonly", repo.Namespace) } } } func checkRemoteBranches(repo *gitpb.Repo) error { if err := repo.ReloadCheck(); err != nil { log.Info("need to reload", repo.FullPath) } if repo.VerifyRemoteAndLocalBranches(repo.GetDevelBranchName()) { } else { return log.Errorf("remote devel is out of sync with local: todo: git pull or git fetch") } if repo.VerifyRemoteAndLocalBranches(repo.GetMasterBranchName()) { } else { return log.Errorf("remote master is out of sync with local: todo: git pull or git fetch") } return nil } func checkPatchIds(repo *gitpb.Repo, b1 string, b2 string) error { var safe bool = true var baderr error ids := make(map[string]string) s1 := fmt.Sprintf("%s..%s", b1, b2) s2 := fmt.Sprintf("%s..%s", b2, b1) if stats, err := repo.RunStrict([]string{"git", "rev-list", s1}); err != nil { return err } else { for _, hash := range stats.Stdout { patchId, err := repo.FindPatchIdByHash(hash) if err != nil { baderr = err safe = false continue } ids[patchId] = hash } } if stats, err := repo.RunStrict([]string{"git", "rev-list", s2}); err != nil { return err } else { for _, hash := range stats.Stdout { patchId, err := repo.FindPatchIdByHash(hash) if err != nil { baderr = err safe = false continue } if val, ok := ids[patchId]; ok { log.Info(patchId, hash, "already here as", val) } else { log.Info(hash, "this probably means this branch is not safe to delete") safe = false ids[patchId] = hash } } } for key, val := range ids { log.Info(key, val) } if safe { log.Info("branch is probably safe to delete", b1, b2) } else { log.Info("branch is probably not safe to delete", b1, b2, baderr) } return baderr }