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 { 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 } // readGoMod reads and parses the go.sum file (TODO: do the go.mod file) func (rs *RepoStatus) ReadGoMod() bool { tmp := filepath.Join(rs.realPath.String(), "go.sum") 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() 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) } } func (rs *RepoStatus) CheckGoSum() bool { 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 { log.Log(WARN, " ", depname, version) newrs, ok := windowMap[depname] if ok { if newrs.CheckDirty() { log.Log(WARN, " IS DIRTY", newrs.String()) return false } log.Log(WARN, " FOUND", newrs.String()) username := newrs.userWorkingName.String() userhash, _ := newrs.gitConfig.hashes[username] userversion, _ := newrs.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) } else { log.Log(WARN, " USER VERSIONS MISMATCH", version, userversion) return false } } else { log.Log(WARN, " NOT FOUND", depname) return false } } return true } 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) }