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 } 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 readGitConfig(filePath string) (*GitConfig, error) { file, err := os.Open(filePath) if err != nil { return nil, err } defer file.Close() var currentSection string = "" var currentName string = "" config := new(GitConfig) config.core = make(map[string]string) config.remotes = make(map[string]*remote) config.branches = make(map[string]*branch) 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": config.core[key] = value case "remote": test, ok := config.remotes[currentName] if !ok { test = new(remote) config.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 := config.branches[currentName] if !ok { test = new(branch) config.branches[currentName] = test } switch key { case "remote": config.branches[currentName].remote = value case "merge": config.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 nil, err } return config, 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, "versions do not match!!!", deps[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) } } }