diff --git a/Makefile b/Makefile index 7fb1b82..ffbb48f 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ # go install -all: refs.pb.go godep.pb.go repos.pb.go vet +all: refs.pb.go gitTags.pb.go godep.pb.go repos.pb.go vet make -C scanGoSrc/ vet: lint @@ -34,6 +34,11 @@ refs.pb.go: refs.proto --go_opt=Mrefs.proto=go.wit.com/lib/protobuf/gitpb \ refs.proto +gitTags.pb.go: gitTags.proto + cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/gitpb \ + --go_opt=MgitTags.proto=go.wit.com/lib/protobuf/gitpb \ + gitTags.proto + godep.pb.go: godep.proto cd ~/go/src && protoc --go_out=. --proto_path=go.wit.com/lib/protobuf/gitpb \ --go_opt=Mgodep.proto=go.wit.com/lib/protobuf/gitpb \ @@ -44,4 +49,5 @@ repos.pb.go: repos.proto --go_opt=Mrefs.proto=go.wit.com/lib/protobuf/gitpb \ --go_opt=Mgodep.proto=go.wit.com/lib/protobuf/gitpb \ --go_opt=Mrepos.proto=go.wit.com/lib/protobuf/gitpb \ + --go_opt=MgitTags.proto=go.wit.com/lib/protobuf/gitpb \ repos.proto diff --git a/gitTags.proto b/gitTags.proto new file mode 100644 index 0000000..dd3d77a --- /dev/null +++ b/gitTags.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package gitpb; + +import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp + +message GitTag { + string refname = 1; // tag name. treated as unique + google.protobuf.Timestamp creatordate = 2; // git creatordate + google.protobuf.Timestamp authordate = 3; // git creatordate + string objectname = 4; // git hash + string subject = 5; // git tag subject +} diff --git a/gitTags.query.go b/gitTags.query.go new file mode 100644 index 0000000..faea8e2 --- /dev/null +++ b/gitTags.query.go @@ -0,0 +1,78 @@ +package gitpb + +// runs git, parses output +// types faster than you can + +import ( + "go.wit.com/log" +) + +func (repo *Repo) GetLastTag() string { + cmd := []string{"git", "rev-list", "--tags", "--max-count=1"} + result := repo.RunQuiet(cmd) + log.Info("getLastTagVersion()", result.Stdout) + + if len(result.Stdout) != 1 { + log.Info("git LastTag() error:", result.Stdout) + return "error" + } + + hash := result.Stdout[0] + + cmd = []string{"git", "describe", "--tags", "--always", hash} + result = repo.RunQuiet(cmd) + + if len(result.Stdout) != 1 { + log.Info("git LastTag() error:", result.Stdout) + return "error" + } + + return result.Stdout[0] +} + +/* +func (repo *Repo) gitDescribeByName(name string) (string, error) { + name = strings.TrimSpace(name) + + if name == "" { + // git will return the current tag + r := repo.RunQuiet([]string{"git", "describe", "--tags", "--always"}) + output := strings.Join(r.Stdout, "\n") + if r.Error != nil { + log.Warn("gitDescribeByName() not in a git repo?", r.Error, repo.GoPath) + } + return strings.TrimSpace(output), r.Error + } + if !repo.LocalTagExists(name) { + // tag does not exist + return "", errors.New("gitDescribeByName() git fatal: Not a valid object name") + } + cmd := []string{"git", "describe", "--tags", "--always", name} + r := repo.RunQuiet(cmd) + output := strings.Join(r.Stdout, "\n") + if r.Error != nil { + log.Warn("cmd =", cmd) + log.Warn("err =", r.Error) + log.Warn("not in a git repo or bad tag?", rs.Path()) + } + + return strings.TrimSpace(output), r.Error +} + +func (repo *Repo) LocalTagExists(findname string) bool { + allTags := repo.Tags.ListAll() + for _, t := range allTags { + tagname := t.TagString() + if strings.HasPrefix(tagname, "refs/remotes") { + continue + } + path, filename := filepath.Split(tagname) + log.Log(INFO, "tag:", path, filename, "from", rs.Path()) + if filename == findname { + log.Log(INFO, "found tag:", path, filename, "from", rs.Path()) + return true + } + } + return false +} +*/ diff --git a/gitTags.sort.go b/gitTags.sort.go new file mode 100644 index 0000000..8d4305f --- /dev/null +++ b/gitTags.sort.go @@ -0,0 +1,149 @@ +package gitpb + +// this is becoming a standard format +// todo: autogenerate this from the .proto file? + +import ( + "fmt" + "os" + "sort" + sync "sync" + "time" +) + +// bad global lock until I figure out some other plan +var gitTagslock sync.RWMutex + +type GitTagIterator struct { + sync.RWMutex + + packs []*GitTag + index int +} + +// NewGitTagIterator initializes a new iterator. +func NewGitTagIterator(packs []*GitTag) *GitTagIterator { + return &GitTagIterator{packs: packs} +} + +// Scan moves to the next element and returns false if there are no more packs. +func (it *GitTagIterator) Scan() bool { + if it.index >= len(it.packs) { + return false + } + it.index++ + return true +} + +// GitTag returns the current repo. +func (it *GitTagIterator) GitTag() *GitTag { + if it.packs[it.index-1] == nil { + for i, d := range it.packs { + fmt.Println("i =", i, d) + } + fmt.Println("len =", len(it.packs)) + fmt.Println("repo == nil", it.index, it.index-1) + os.Exit(-1) + } + return it.packs[it.index-1] +} + +// Use Scan() in a loop, similar to a while loop +// +// for iterator.Scan() { +// d := iterator.GitTag() +// fmt.Println("GitTag UUID:", d.Uuid) +// } + +func (r *Repo) AllTags() *GitTagIterator { + repoPointers := r.selectAllGitTags() + + iterator := NewGitTagIterator(repoPointers) + return iterator +} + +func (r *Repo) SortTagsByName() *GitTagIterator { + packs := r.selectAllGitTags() + + sort.Sort(GitTagsByName(packs)) + + iterator := NewGitTagIterator(packs) + return iterator +} + +// enforces no duplicate package names +func (repo *Repo) AppendGitTag(newP *GitTag) bool { + gitTagslock.Lock() + defer gitTagslock.Unlock() + + for _, p := range repo.GitTags { + if p.Refname == newP.Refname { + return false + } + } + + repo.GitTags = append(repo.GitTags, newP) + return true +} + +// returns time.Duration since last Update() +func (r *GitTag) Age(newP *GitTag) time.Duration { + t := time.Since(r.Creatordate.AsTime()) + return t +} + +// find a package by name +func (repo *Repo) FindGitTagByName(name string) *GitTag { + gitTagslock.RLock() + defer gitTagslock.RUnlock() + + for _, p := range repo.GitTags { + if p.Refname == name { + return p + } + } + + return nil +} + +func (repo *Repo) LenGitTags() int { + gitTagslock.RLock() + defer gitTagslock.RUnlock() + + return len(repo.GitTags) +} + +type GitTagsByName []*GitTag + +func (a GitTagsByName) Len() int { return len(a) } +func (a GitTagsByName) Less(i, j int) bool { return a[i].Refname < a[j].Refname } +func (a GitTagsByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// safely returns a slice of pointers to the GitTag protobufs +func (repo *Repo) selectAllGitTags() []*GitTag { + gitTagslock.RLock() + defer gitTagslock.RUnlock() + + // Create a new slice to hold pointers to eachGitTag + var allGitTags []*GitTag + allGitTags = make([]*GitTag, len(repo.GitTags)) + for i, p := range repo.GitTags { + allGitTags[i] = p // Copy pointers for safe iteration + } + + return allGitTags +} + +func (repo *Repo) DeleteTagByHash(hash string) *GitTag { + gitTagslock.Lock() + defer gitTagslock.Unlock() + + for i, _ := range repo.GitTags { + if repo.GitTags[i].Objectname == hash { + repo.GitTags[i] = repo.GitTags[len(repo.GitTags)-1] + repo.GitTags = repo.GitTags[:len(repo.GitTags)-1] + return nil + } + } + return nil +} diff --git a/gitTags.update.go b/gitTags.update.go new file mode 100644 index 0000000..465fe70 --- /dev/null +++ b/gitTags.update.go @@ -0,0 +1,83 @@ +package gitpb + +import ( + "slices" + "strings" + "time" + + "go.wit.com/lib/gui/shell" + "go.wit.com/log" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" +) + +// Update repo.Refs from .git/ +func (repo *Repo) UpdateGitTags() error { + // delete the old hash + // r.DeleteByHash(hash) + repo.GitTags = nil + + tags := []string{"%(objectname)", "%(creatordate)", "%(*authordate)", "%(refname)", "%(subject)"} + format := strings.Join(tags, "_,,,_") + cmd := []string{"git", "for-each-ref", "--sort=taggerdate", "--format", format} + // log.Info("RUNNING:", strings.Join(cmd, " ")) + result := shell.PathRunQuiet(repo.FullPath, cmd) + if result.Error != nil { + log.Warn("git for-each-ref error:", result.Error) + return result.Error + } + + lines := result.Stdout + // reverse the git order + slices.Reverse(lines) + + var refname string + var hash string + var subject string + var ctime *timestamppb.Timestamp + var atime *timestamppb.Timestamp + + for i, line := range lines { + var parts []string + parts = make([]string, 0) + parts = strings.Split(line, "_,,,_") + if len(parts) != 5 { + log.Info("tag error:", i, parts) + continue + } + hash = parts[0] + if parts[1] != "" { + tmp := getGitDateStamp(parts[1]) + ctime = timestamppb.New(tmp) + } + if parts[2] != "" { + tmp := getGitDateStamp(parts[2]) + atime = timestamppb.New(tmp) + } + refname = parts[3] + subject = parts[4] + } + + newr := GitTag{ + Refname: refname, + Objectname: hash, + Subject: subject, + Creatordate: ctime, + Authordate: atime, + } + + repo.AppendGitTag(&newr) + return nil +} + +// converts a git for-each-ref date. "Wed Feb 7 10:13:38 2024 -0600" +func getGitDateStamp(gitdefault string) time.Time { + // now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600") + const gitLayout = "Mon Jan 2 15:04:05 2006 -0700" + tagTime, err := time.Parse(gitLayout, gitdefault) + if err != nil { + log.Warn("GOT THIS IN PARSE AAA." + gitdefault + ".AAA") + log.Warn(err) + return time.Now() + } + return tagTime +} diff --git a/refs.update.go b/refs.update.go index 3266fd1..e4d0413 100644 --- a/refs.update.go +++ b/refs.update.go @@ -61,16 +61,3 @@ func (repo *Repo) UpdateGit() error { repo.AppendRef(&newr) return nil } - -// converts a git for-each-ref date. "Wed Feb 7 10:13:38 2024 -0600" -func getGitDateStamp(gitdefault string) time.Time { - // now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600") - const gitLayout = "Mon Jan 2 15:04:05 2006 -0700" - tagTime, err := time.Parse(gitLayout, gitdefault) - if err != nil { - log.Warn("GOT THIS IN PARSE AAA." + gitdefault + ".AAA") - log.Warn(err) - return time.Now() - } - return tagTime -} diff --git a/repos.new.go b/repos.new.go index bab2b25..8638da7 100644 --- a/repos.new.go +++ b/repos.new.go @@ -34,6 +34,7 @@ func (all *Repos) NewGoPath(basepath string, gopath string) (*Repo, error) { GoPath: gopath, } newr.UpdateGit() + newr.UpdateGitTags() all.add(&newr) return &newr, nil diff --git a/repos.proto b/repos.proto index 029bad2..9e02c08 100644 --- a/repos.proto +++ b/repos.proto @@ -7,6 +7,7 @@ package gitpb; import "refs.proto"; import "godep.proto"; +import "gitTags.proto"; import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp message Repo { @@ -20,6 +21,8 @@ message Repo { google.protobuf.Timestamp lastGoDep = 7; // last time go.sum was processed bool goLibrary = 8; // if this is a golang library bool goPrimitive = 9; // if this is a golang primitive + + repeated GitTag gitTags = 10; } message Repos {