parse .git/config

This commit is contained in:
Jeff Carr 2025-01-18 10:34:58 -06:00
parent 80f602c4d9
commit 0c30a9da2f
6 changed files with 254 additions and 13 deletions

View File

@ -4,6 +4,25 @@ package gitpb;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
message GitRemote { // `autogenpb:nomutex`
string url = 1;
string fetch = 2;
}
message GitBranch { // `autogenpb:nomutex`
string remote = 1;
string merge = 2;
}
// readGitConfig reads and parses the .git/config file
message GitConfig { // `autogenpb:nomutex`
map<string, string> core = 1; // map[origin] = "https:/git.wit.org/gui/gadgets"
map<string, GitRemote> remotes = 2; // map[origin] = "https:/git.wit.org/gui/gadgets"
map<string, GitBranch> branches = 3; // map[guimaster] = origin guimaster
map<string, string> submodules = 4;
map<string, string> hashes = 5;
map<string, string> versions = 6;
}
message GitTag { // `autogenpb:nomutex`
string refname = 1; // `autogenpb:unique` `autogenpb:sort` // tag name. treated as unique

View File

@ -28,6 +28,12 @@ func (repo *Repo) Reload() error {
repo.CheckDirty()
repo.setRepoState()
if repo.GitConfig == nil {
if err := repo.updateGitConfig(); err != nil {
return err
}
}
// LastUpdate should always be the newest time
repo.Times.LastUpdate = timestamppb.New(time.Now())
return nil

176
reloadGitConfig.go Normal file
View File

@ -0,0 +1,176 @@
package gitpb
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"go.wit.com/log"
)
// does processing on the go.mod and go.sum files
func (repo *Repo) updateGitConfig() error {
if repo == nil {
return fmt.Errorf("gitpb.updateGitConfig() repo == nil")
}
if repo.GitConfig == nil {
repo.GitConfig = new(GitConfig)
}
repo.GitConfig.Core = make(map[string]string)
repo.GitConfig.Remotes = make(map[string]*GitRemote)
repo.GitConfig.Branches = make(map[string]*GitBranch)
repo.GitConfig.Submodules = make(map[string]string)
repo.GitConfig.Versions = make(map[string]string)
repo.GitConfig.Hashes = make(map[string]string)
return repo.readGitConfig()
}
// readGitConfig reads and parses the .git/config file
func (repo *Repo) readGitConfig() error {
filename := filepath.Join(repo.GetFullPath(), ".git/config")
file, err := os.Open(filename)
defer file.Close()
if err != nil {
return err
}
var currentSection string = ""
var currentName 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":
repo.GitConfig.Core[key] = value
case "gui":
// don't really need gui stuff right now
case "pull":
// don't store git config pull settings here
// git config probably has 'rebase = false'
case "remote":
test, ok := repo.GitConfig.Remotes[currentName]
if !ok {
test = new(GitRemote)
repo.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(INFO, "error url mismatch", test.Url, value)
case "fetch":
if test.Fetch == value {
continue
}
if test.Fetch == "" {
test.Fetch = value
continue
}
log.Log(INFO, "error fetch mismatch", test.Fetch, value)
default:
log.Log(INFO, "unknown remote:", line)
}
case "branch":
test, ok := repo.GitConfig.Branches[currentName]
if !ok {
test = new(GitBranch)
repo.GitConfig.Branches[currentName] = test
repo.processBranch(currentName)
}
switch key {
case "remote":
repo.GitConfig.Branches[currentName].Remote = value
case "merge":
repo.GitConfig.Branches[currentName].Merge = value
default:
log.Log(INFO, "error unknown remote:", currentSection, currentName, key, value)
log.Log(INFO, "unknown branch:", line)
}
case "submodule":
// test, ok := rs.gitConfig.submodules[currentName]
switch key {
case "active":
// probably 'true' or 'false'
case "url":
repo.GitConfig.Submodules[currentName] = value
default:
log.Log(WARN, "unknown submodule line:", line)
}
default:
log.Log(WARN, "unknown line:", line)
}
}
if err := scanner.Err(); err != nil {
return err
}
return nil
}
func (repo *Repo) processBranch(branch string) {
log.Log(INFO, " ", branch)
hash, ok := repo.GitConfig.Hashes[branch]
filename := filepath.Join(repo.GetFullPath() + "/.git/refs/heads/" + branch)
log.Log(INFO, " hash: need to open", filename)
data, err := ioutil.ReadFile(filename)
if err != nil {
log.Log(WARN, "hash: read failed", filename)
return
}
newhash := strings.TrimSpace(string(data))
log.Log(INFO, " hash:", newhash)
repo.GitConfig.Hashes[branch] = newhash
if ok {
if hash != newhash {
log.Log(WARN, "hash changed", hash)
}
}
name, _ := repo.gitDescribeByHash(newhash)
repo.GitConfig.Versions[newhash] = name
log.Log(INFO, " hash: version", name)
}

View File

@ -119,6 +119,31 @@ func (repo *Repo) changedHead() bool {
return true
}
// check the mtime of the .git/config file
func (repo *Repo) changedConfig() bool {
fname := ".git/config"
fileTime := repo.Mtime(fname)
if fileTime == nil {
// .git/config doesn't exist. something is wrong!
log.Info("gitpb .git/config is missing", repo.GetGoPath())
return false
}
mtime := timestamppb.New(*fileTime)
pbtime := repo.Times.MtimeConfig
if pbtime == nil { // this can happen?
repo.Times.MtimeConfig = mtime
return true
}
if (pbtime.Seconds == mtime.Seconds) && (pbtime.Nanos == mtime.Nanos) {
return false
}
dur := mtime.AsTime().Sub(pbtime.AsTime())
repo.StateChange = fmt.Sprintf("%s changed %s", fname, shell.FormatDuration(dur))
repo.Times.MtimeConfig = mtime
return true
}
func (repo *Repo) changedIndex() bool {
fname := ".git/index"
fileTime := repo.Mtime(fname)
@ -154,6 +179,9 @@ func (repo *Repo) reloadMtimes() bool {
if repo.changedIndex() {
changed = true
}
if repo.changedConfig() {
changed = true
}
if repo.changedDir() {
// changed = true
}
@ -171,6 +199,9 @@ func (repo *Repo) DidRepoChange() bool {
if repo.didFileChange(".git/index", repo.Times.MtimeIndex) {
return true
}
if repo.didFileChange(".git/config", repo.Times.MtimeConfig) {
return true
}
if repo.didFileChange(".git", repo.Times.MtimeDir) {
// todo: do something with CheckDirty()
// return true

View File

@ -135,7 +135,13 @@ func (repo *Repo) NewestTag() *GitTag {
return nil
}
// this should just do is.Exists(".git/refs/heads/findname")
func (repo *Repo) LocalTagExists(findname string) bool {
fname := filepath.Join(".git/refs/heads", findname)
if repo.Exists(fname) {
return true
}
/*
loop := repo.Tags.SortByRefname()
for loop.Scan() {
ref := loop.Next()
@ -143,13 +149,14 @@ func (repo *Repo) LocalTagExists(findname string) bool {
if strings.HasPrefix(ref.Refname, "refs/remotes") {
continue
}
_, tagname := filepath.Split(ref.Refname)
tagname := filepath.Base(ref.Refname)
// log.Info("tag:", path, tagname, "from", repo.GoPath)
if tagname == findname {
// log.Info("found tag:", path, tagname, "from", repo.GoPath)
return true
}
}
*/
return false
}

View File

@ -35,6 +35,7 @@ message Repo { // `autogenpb:marshal` `autogenpb:2nomutex`
repeated string dirtyList = 22; // store the list from git status --porcelain
string state = 23; // status or state. useful for building tooling
GitTag currentTag = 24; // used to examine repo branches
GitConfig gitConfig = 25; // protobuf of the current .git/config
}
message Repos { // `autogenpb:marshal` `autogenpb:sort` `autogenpb:2nomutex`
@ -54,6 +55,7 @@ message GitTimes { // `autogenpb:2nomutex`
google.protobuf.Timestamp mtimeFetch = 7; // mtime for ./git/FETCH_HEAD // last time 'git fetch' or 'git pull' was run on current branch?
google.protobuf.Timestamp lastGoDep = 8; // mtime for last go.sum scan
google.protobuf.Timestamp newestCommit = 9; // when the newest commit was
google.protobuf.Timestamp mtimeConfig = 10; // mtime for the .git/config file
}
// this is probably better. think about moving to this instead