package repostatus import ( "bufio" "os" "path/filepath" "strings" "go.wit.com/log" ) // GitConfig represents the parsed .git/config data // type GitConfig map[string]map[string]string type remote struct { url string fetch string } type branch struct { remote string merge string } type GitConfig struct { core map[string]string // map[origin] = "https:/git.wit.org/gui/gadgets" remotes map[string]*remote // map[origin] = "https:/git.wit.org/gui/gadgets" branches map[string]*branch // map[guimaster] = origin guimaster hashes map[string]string versions map[string]string } type GoConfig map[string]string func ListGitDirectories() []string { var all []string homeDir, err := os.UserHomeDir() if err != nil { log.Log(WARN, "Error getting home directory:", err) return nil } srcDir := filepath.Join(homeDir, "go/src") err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { if err != nil { log.Log(WARN, "Error accessing path:", path, err) return nil } // Check if the current path is a directory and has a .git subdirectory if info.IsDir() && isGitDir(path) { all = append(all, path) // fmt.Println(path) } return nil }) if err != nil { log.Log(WARN, "Error walking the path:", srcDir, err) } return all } // isGitDir checks if a .git directory exists inside the given directory func isGitDir(dir string) bool { gitDir := filepath.Join(dir, ".git") info, err := os.Stat(gitDir) if os.IsNotExist(err) { return false } return info.IsDir() } // readGitConfig reads and parses the .git/config file func (rs *RepoStatus) readGitConfig() error { filename := filepath.Join(rs.realPath.String(), "/.git/config") file, err := os.Open(filename) if err != nil { filename = filepath.Join(rs.realPath.String(), "../.git/config") log.Log(WARN, "readGitConfig() trying up one directory instead", filename) file, err = os.Open(filename) if err != nil { panic("couldn't open .git/config") return nil } } defer file.Close() var currentSection string = "" var currentName string = "" rs.gitConfig = new(GitConfig) rs.gitConfig.core = make(map[string]string) rs.gitConfig.remotes = make(map[string]*remote) rs.gitConfig.branches = make(map[string]*branch) rs.gitConfig.versions = make(map[string]string) rs.gitConfig.hashes = make(map[string]string) scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) // Skip empty lines and comments if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { continue } // Check for section headers if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { line = strings.Trim(line, "[]") parts := strings.Split(line, " ") currentSection = parts[0] if len(parts) == 2 { line = strings.Trim(line, "[]") currentName = strings.Trim(parts[1], "\"") } continue } partsNew := strings.SplitN(line, "=", 2) if len(partsNew) != 2 { log.Log(WARN, "error on config section:", currentSection, "line:", line) } key := strings.TrimSpace(partsNew[0]) key = strings.TrimSuffix(key, "\"") value := strings.TrimSpace(partsNew[1]) value = strings.TrimSuffix(value, "\"") switch currentSection { case "core": rs.gitConfig.core[key] = value case "remote": test, ok := rs.gitConfig.remotes[currentName] if !ok { test = new(remote) rs.gitConfig.remotes[currentName] = test } log.Log(INFO, "switch currentSection", currentSection, currentName) switch key { case "url": if test.url == value { continue } if test.url == "" { test.url = value continue } log.Log(WARN, "error url mismatch", test.url, value) case "fetch": if test.fetch == value { continue } if test.fetch == "" { test.fetch = value continue } log.Log(WARN, "error fetch mismatch", test.fetch, value) default: log.Log(WARN, "error unknown remote:", currentSection, currentName, "key", key, "value", value) } case "branch": test, ok := rs.gitConfig.branches[currentName] if !ok { test = new(branch) rs.gitConfig.branches[currentName] = test rs.processBranch(currentName) } switch key { case "remote": rs.gitConfig.branches[currentName].remote = value case "merge": rs.gitConfig.branches[currentName].merge = value default: log.Log(WARN, "error unknown remote:", currentSection, currentName, key, value) } default: log.Log(WARN, "error unknown currentSection", currentSection, "line:", line) } } if err := scanner.Err(); err != nil { return err } return nil } // this checks to see if the repo is truly not dependent on _anything_ else // like spew or lib/widget func (rs *RepoStatus) CheckPrimativeGoMod() bool { log.Log(WARN, "CheckPrimativeGoMod()", rs.realPath.String()) tmp := filepath.Join(rs.realPath.String(), "go.mod") gomod, err := os.Open(tmp) if err != nil { log.Log(WARN, "missing go.mod", rs.realPath.String()) rs.goConfig = nil return false } defer gomod.Close() scanner := bufio.NewScanner(gomod) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) parts := strings.Split(line, " ") log.Log(INFO, " gomod:", parts) if len(parts) >= 1 { log.Log(INFO, " gomod: part[0] =", parts[0]) if parts[0] == "require" { log.Log(INFO, " should return false here") return false } } } return true } // readGoMod reads and parses the go.sum file (TODO: do the go.mod file) func (rs *RepoStatus) ReadGoMod() bool { if rs.CheckPrimativeGoMod() { log.Info("PRIMATIVE repo:", rs.String()) return true } tmp := filepath.Join(rs.realPath.String(), "go.sum") gosum, err := os.Open(tmp) if err != nil { log.Log(WARN, "missing go.sum", rs.realPath.String()) rs.goConfig = nil return false } defer gosum.Close() var deps GoConfig deps = make(GoConfig) scanner := bufio.NewScanner(gosum) log.Log(INFO, "gosum:", tmp) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) parts := strings.Split(line, " ") if len(parts) == 3 { godep := strings.TrimSpace(parts[0]) version := strings.TrimSpace(parts[1]) if strings.HasSuffix(version, "/go.mod") { version = strings.TrimSuffix(version, "/go.mod") } currentversion, ok := deps[godep] if ok { if currentversion != version { log.Log(WARN, "REPO:", rs.String(), rs.realPath.String()) log.Log(WARN, " version mismatch:", godep, version, currentversion) } } else { deps[godep] = version log.Log(INFO, "\t", godep, "=", version) } } else { log.Log(WARN, "\t INVALID:", parts) } } if err := scanner.Err(); err != nil { rs.goConfig = nil return false } rs.goConfig = deps return true } func ScanGoSrc() { log.Log(WARN, "Scanning all go.sum files") for path, rs := range windowMap { if rs.ReadGoMod() { // everything is ok } else { log.Log(WARN, "failed reading go.sum repo:", path) } } } func ScanGitConfig() { /* for i, path := range listGitDirectories() { filename := filepath.Join(path, ".git/config") _, err := readGitConfig(filename) if err != nil { log.Log(WARN, "repo =", i, path) log.Log(WARN, "Error reading .git/config:", err) } } */ } func (rs *RepoStatus) ScanGoSrc() { if rs.ReadGoMod() { log.Log(INFO, "parsed go.mod", rs.realPath.String()) } else { log.Log(WARN, "Something went wrong parsing go.mod", rs.realPath.String()) } log.Log(WARN, "go.sum:", rs.realPath.String()) for depname, version := range rs.goConfig { log.Log(WARN, " ", depname, version) } } // check if it is safe to remake the go.sum & go.mod files func (rs *RepoStatus) CheckSafeGoSumRemake() (bool, []string) { if rs.ReadGoMod() { log.Log(INFO, "parsed go.mod", rs.realPath.String()) } else { log.Log(WARN, "Something went wrong parsing go.mod", rs.realPath.String()) return false, nil } log.Log(WARN, "go.sum:", rs.realPath.String()) var clean []string for depname, version := range rs.goConfig { if strings.HasSuffix(depname, "/v2") { log.Log(WARN, " FOUND /v2 wierd golang stuff. instead, look for:", depname) depname = strings.TrimSuffix(depname, "/v2") } log.Log(WARN, " ", depname, version) deprs, ok := windowMap[depname] if ok { if deprs.CheckDirty() { log.Log(WARN, " IS DIRTY", deprs.String()) clean = append(clean, deprs.String()) } if deprs.readOnly.String() == "true" { log.Log(WARN, " SKIPPING Read Only", deprs.String()) } else { goSumS := deprs.goSumStatus.String() log.Log(WARN, " FOUND", deprs.String(), goSumS) username := deprs.userWorkingName.String() userhash, _ := deprs.gitConfig.hashes[username] userversion, _ := deprs.gitConfig.versions[userhash] log.Log(WARN, " username :"+username, userhash) log.Log(WARN, " username :"+username, userversion) if version == userversion { log.Log(WARN, " USER VERSIONS MATCH", version, userversion) if deprs.goSumStatus.String() == "BAD" { log.Log(WARN, " USER VERSION IS BAD!! return false") clean = append(clean, deprs.String()) } } else { switch goSumS { case "SAFE": log.Log(WARN, " USER VERSIONS MISMATCH", version, userversion) log.Log(WARN, " IGNORE SAFE REPO") err, output := rs.RunCmd([]string{"go", "get", depname + "@" + userversion}) log.Log(WARN, " go get", depname, err, output) case "CLEAN": log.Log(WARN, " USER VERSIONS MISMATCH", version, userversion) log.Log(WARN, " IGNORE CLEAN REPO") case "PRIMATIVE": log.Log(WARN, " USER VERSIONS MISMATCH", version, userversion) log.Log(WARN, " IGNORE PRIMATIVE REPO") err, output := rs.RunCmd([]string{"go", "get", depname + "@" + userversion}) log.Log(WARN, " go get", depname, err, output) default: log.Log(WARN, " USER VERSIONS MISMATCH", version, userversion) clean = append(clean, deprs.String()) } } } } else { log.Log(WARN, " NOT FOUND", depname) // only fail on our stuff if strings.HasPrefix(depname, "go.wit.com") { // log.Log(WARN, " go get -v", depname) // rs.RunCmd([]string{"go", "get", "-v", depname}) return false, clean } log.Log(WARN, " NOT FOUND BUT IGNORING FOR NOW") } } if len(clean) == 0 { return true, nil } return false, clean } func (rs *RepoStatus) CheckGoSum() (bool, string) { if rs.ReadGoMod() { log.Log(INFO, "parsed go.mod", rs.realPath.String()) } else { log.Log(WARN, "Something went wrong parsing go.mod", rs.realPath.String()) return false, "" } log.Log(WARN, "go.sum:", rs.realPath.String()) for depname, version := range rs.goConfig { if strings.HasSuffix(depname, "/v2") { log.Log(WARN, " FOUND /v2 wierd golang stuff. instead, look for:", depname) depname = strings.TrimSuffix(depname, "/v2") } log.Log(WARN, " ", depname, version) deprs, ok := windowMap[depname] if ok { if deprs.CheckDirty() { log.Log(WARN, " IS DIRTY", deprs.String()) return false, "" } if deprs.readOnly.String() == "true" { log.Log(WARN, " SKIPPING Read Only", deprs.String()) } else { log.Log(WARN, " FOUND", deprs.String(), deprs.goSumStatus.String()) username := deprs.userWorkingName.String() userhash, _ := deprs.gitConfig.hashes[username] userversion, _ := deprs.gitConfig.versions[userhash] log.Log(WARN, " username :"+username, userhash) log.Log(WARN, " username :"+username, userversion) if version == userversion { log.Log(WARN, " USER VERSIONS MATCH", version, userversion) if deprs.goSumStatus.String() == "BAD" { log.Log(WARN, " USER VERSION IS BAD!! return false") return false, "" } } else { log.Log(WARN, " USER VERSIONS MISMATCH", version, userversion) return false, "" } } } else { log.Log(WARN, " NOT FOUND", depname) if strings.HasSuffix(depname, "/v2") { log.Log(WARN, " FOUND /v2 wierd golang stuff. instead, look for:", depname) } // only fail on our stuff if strings.HasPrefix(depname, "go.wit.com") { // log.Log(WARN, " go get -v", depname) // rs.RunCmd([]string{"go", "get", "-v", depname}) return false, depname } log.Log(WARN, " NOT FOUND BUT IGNORING FOR NOW") } } err, output := rs.RunCmd([]string{"git", "branch", "--remotes"}) if err == nil { lines := strings.Split(output, "\n") for i, s := range lines { log.Log(WARN, "add line", i, s) } } else { log.Log(WARN, "git branch --remotes failed", err) } return true, "" } func (rs *RepoStatus) MakeRedomod() { var err error var b bool var output string var worked bool = true if rs.ReadOnly() { log.Log(WARN, "will not go mod redo read only repos", rs.String()) return } os.Unsetenv("GO111MODULE") path := rs.realPath.String() err, b, output = RunCmd(path, []string{"rm", "-f", "go.mod", "go.sum"}) if err != nil { worked = false log.Log(WARN, "rm failed", err, b, output) } err, b, output = RunCmd(path, []string{"go", "mod", "init"}) if err != nil { worked = false log.Log(WARN, "go mod init failed", err, b, output) } err, b, output = RunCmd(path, []string{"go", "mod", "tidy"}) if err != nil { worked = false log.Log(WARN, "go mod tidy failed", err, b, output) } if worked { log.Log(WARN, "MakeRedomod() worked", path) } else { log.Log(WARN, "MakeRedomod() failed", path) } } func (rs *RepoStatus) ReadOnly() bool { if rs.readOnly.String() == "true" { return true } else { return false } } func (rs *RepoStatus) processBranch(branch string) { fullpath := rs.realPath.String() log.Log(WARN, " ", branch) hash, ok := rs.gitConfig.hashes[branch] filename := fullpath + "/.git/refs/heads/" + branch log.Log(WARN, " hash: need to open", filename) newhash, err := readFileToString(filename) if err != nil { log.Log(WARN, " hash: read failed", filename) return } log.Log(WARN, " hash:", newhash) rs.gitConfig.hashes[branch] = newhash if ok { if hash != newhash { log.Log(WARN, " hash changed!!!!!!!!!", hash) } } var cmd []string cmd = append(cmd, "git", "describe", "--tags", newhash) _, _, output := RunCmd(rs.realPath.String(), cmd) output = strings.TrimSpace(output) rs.gitConfig.versions[newhash] = output log.Log(WARN, " hash: version", output) }