2024-02-07 08:51:45 -06:00
|
|
|
package repostatus
|
|
|
|
|
|
|
|
import (
|
2024-02-16 17:55:13 -06:00
|
|
|
"path/filepath"
|
2024-02-07 08:51:45 -06:00
|
|
|
"regexp"
|
|
|
|
"slices"
|
|
|
|
"strings"
|
2024-02-22 15:29:22 -06:00
|
|
|
"sync"
|
|
|
|
"time"
|
2024-02-07 08:51:45 -06:00
|
|
|
|
|
|
|
"go.wit.com/gui"
|
|
|
|
"go.wit.com/log"
|
|
|
|
)
|
|
|
|
|
2024-02-17 08:39:09 -06:00
|
|
|
type Tag struct {
|
2024-02-07 08:51:45 -06:00
|
|
|
// tracks if the tag is displayed
|
|
|
|
hidden bool
|
|
|
|
|
|
|
|
// the tag "v0.1.3"
|
|
|
|
tag *gui.Node
|
|
|
|
|
|
|
|
// the .git/ref hash
|
|
|
|
ref *gui.Node
|
|
|
|
|
|
|
|
// the .git/ref date
|
2024-02-23 00:10:30 -06:00
|
|
|
date *gui.Node
|
|
|
|
duration *gui.Node
|
2024-02-07 08:51:45 -06:00
|
|
|
|
2024-02-12 06:23:31 -06:00
|
|
|
// the tag comment
|
|
|
|
subject *gui.Node
|
|
|
|
|
2024-02-07 08:51:45 -06:00
|
|
|
// a button to delete the tag
|
|
|
|
deleteB *gui.Node
|
|
|
|
}
|
|
|
|
|
2024-02-15 22:50:50 -06:00
|
|
|
// a GUI box of all the tags in a repo
|
2024-02-17 08:39:09 -06:00
|
|
|
type GitTagBox struct {
|
2024-02-07 08:51:45 -06:00
|
|
|
// the box to list all the tags in
|
|
|
|
box *gui.Node
|
|
|
|
group *gui.Node
|
|
|
|
grid *gui.Node
|
|
|
|
|
|
|
|
// all the tags
|
2024-02-17 08:39:09 -06:00
|
|
|
tags []*Tag
|
2024-02-07 08:51:45 -06:00
|
|
|
}
|
|
|
|
|
2024-02-15 22:50:50 -06:00
|
|
|
func (rs *RepoStatus) makeTagBox(box *gui.Node) {
|
|
|
|
if rs.Tags != nil {
|
|
|
|
log.Log(WARN, "already scanned tags")
|
|
|
|
return
|
|
|
|
}
|
2024-02-17 08:39:09 -06:00
|
|
|
tagB := new(GitTagBox)
|
2024-02-15 22:50:50 -06:00
|
|
|
rs.Tags = tagB
|
|
|
|
tagB.group = box.NewGroup(".git tags for " + rs.String())
|
2024-02-07 08:51:45 -06:00
|
|
|
|
2024-02-15 22:50:50 -06:00
|
|
|
// tagB.group.NewButton("prune tags", func() {
|
|
|
|
// tagB.Prune()
|
2024-02-12 06:23:31 -06:00
|
|
|
// })
|
|
|
|
var dups *gui.Node
|
|
|
|
|
2024-02-15 22:50:50 -06:00
|
|
|
dups = tagB.group.NewCheckbox("Show duplicate tags").SetChecked(false)
|
2024-02-12 06:23:31 -06:00
|
|
|
dups.Custom = func() {
|
|
|
|
if dups.Checked() {
|
2024-02-15 22:50:50 -06:00
|
|
|
tagB.Prune()
|
2024-02-12 06:23:31 -06:00
|
|
|
} else {
|
2024-02-15 22:50:50 -06:00
|
|
|
for _, t := range tagB.tags {
|
2024-02-12 06:23:31 -06:00
|
|
|
t.Show()
|
|
|
|
}
|
2024-02-07 08:51:45 -06:00
|
|
|
}
|
2024-02-12 06:23:31 -06:00
|
|
|
}
|
2024-02-15 22:50:50 -06:00
|
|
|
tagB.group.NewButton("delete all", func() {
|
|
|
|
for i, t := range tagB.tags {
|
2024-02-07 08:51:45 -06:00
|
|
|
if t.hidden {
|
|
|
|
// log.Info("tag is hidden", i, t.tag.String())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
log.Info("tag is shown", i, t.tag.String())
|
2024-02-15 22:50:50 -06:00
|
|
|
rs.DeleteTag(t)
|
2024-02-07 08:51:45 -06:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-02-15 22:50:50 -06:00
|
|
|
grid := tagB.group.NewGrid("tags", 0, 0)
|
|
|
|
tagB.grid = grid
|
2024-02-07 08:51:45 -06:00
|
|
|
|
|
|
|
grid.NewLabel("version")
|
|
|
|
grid.NewLabel("ref")
|
|
|
|
grid.NewLabel("date")
|
2024-02-12 06:23:31 -06:00
|
|
|
grid.NewLabel("release subject")
|
2024-02-07 08:51:45 -06:00
|
|
|
// works like a typerwriter
|
2024-02-12 06:23:31 -06:00
|
|
|
grid.NextRow()
|
2024-02-07 08:51:45 -06:00
|
|
|
|
|
|
|
// git tag --list --sort=taggerdate
|
|
|
|
// git for-each-ref --sort=taggerdate --format '%(tag) %(*objectname) %(taggerdate)'
|
|
|
|
// git rev-parse HEAD
|
|
|
|
// if last tag == HEAD, then remove it
|
|
|
|
|
2024-02-16 01:22:37 -06:00
|
|
|
tags := []string{"%(objectname)", "%(creatordate)", "%(*authordate)", "%(refname)", "%(subject)"}
|
2024-02-07 08:51:45 -06:00
|
|
|
format := strings.Join(tags, "_,,,_")
|
2024-02-14 00:09:58 -06:00
|
|
|
cmd := []string{"git", "for-each-ref", "--sort=taggerdate", "--format", format}
|
2024-02-16 01:22:37 -06:00
|
|
|
// log.Info("RUNNING:", strings.Join(cmd, " "))
|
2024-02-15 22:50:50 -06:00
|
|
|
err, output := rs.RunCmd(cmd)
|
2024-02-07 08:51:45 -06:00
|
|
|
if err != nil {
|
|
|
|
output = "git error_,,,_a_,,,_b_,,,c"
|
|
|
|
}
|
|
|
|
|
|
|
|
lines := strings.Split(output, "\n")
|
|
|
|
// reverse the git order
|
|
|
|
slices.Reverse(lines)
|
2024-02-17 08:39:09 -06:00
|
|
|
tagB.tags = make([]*Tag, 0)
|
2024-02-07 08:51:45 -06:00
|
|
|
|
|
|
|
for i, line := range lines {
|
|
|
|
var parts []string
|
2024-02-16 01:22:37 -06:00
|
|
|
parts = make([]string, 0)
|
2024-02-07 08:51:45 -06:00
|
|
|
parts = strings.Split(line, "_,,,_")
|
2024-02-16 01:22:37 -06:00
|
|
|
if len(parts) != 5 {
|
|
|
|
log.Info("tag error:", i, parts)
|
2024-02-07 08:51:45 -06:00
|
|
|
continue
|
|
|
|
}
|
2024-02-16 01:22:37 -06:00
|
|
|
// log.Info("found tag:", i, parts)
|
2024-02-17 08:39:09 -06:00
|
|
|
rTag := new(Tag)
|
2024-02-16 01:22:37 -06:00
|
|
|
rTag.tag = grid.NewLabel(parts[3])
|
|
|
|
rTag.ref = grid.NewEntrybox(parts[0])
|
2024-02-12 06:23:31 -06:00
|
|
|
|
2024-02-16 01:22:37 -06:00
|
|
|
_, stamp, dur := getGitDateStamp(parts[1])
|
2024-02-12 06:23:31 -06:00
|
|
|
rTag.date = grid.NewLabel(stamp)
|
2024-02-23 00:10:30 -06:00
|
|
|
rTag.duration = grid.NewLabel(dur)
|
2024-02-12 06:23:31 -06:00
|
|
|
|
2024-02-16 01:22:37 -06:00
|
|
|
rTag.subject = grid.NewLabel(parts[4])
|
2024-02-07 08:51:45 -06:00
|
|
|
rTag.deleteB = grid.NewButton("delete", func() {
|
2024-02-14 00:09:58 -06:00
|
|
|
tagversion := parts[0]
|
|
|
|
log.Info("remove tag", tagversion)
|
|
|
|
var all [][]string
|
|
|
|
all = append(all, []string{"git", "tag", "--delete", tagversion})
|
|
|
|
all = append(all, []string{"git", "push", "--delete", "origin", tagversion})
|
|
|
|
|
2024-02-15 22:50:50 -06:00
|
|
|
if rs.DoAll(all) {
|
|
|
|
log.Info("TAG DELETED", rs.String(), tagversion)
|
2024-02-14 00:09:58 -06:00
|
|
|
} else {
|
2024-02-15 22:50:50 -06:00
|
|
|
log.Info("TAG DELETE FAILED", rs.String(), tagversion)
|
2024-02-14 00:09:58 -06:00
|
|
|
}
|
2024-02-07 08:51:45 -06:00
|
|
|
})
|
|
|
|
|
2024-02-15 22:50:50 -06:00
|
|
|
tagB.tags = append(tagB.tags, rTag)
|
2024-02-07 08:51:45 -06:00
|
|
|
// works like a typerwriter
|
2024-02-12 06:23:31 -06:00
|
|
|
grid.NextRow()
|
2024-02-07 08:51:45 -06:00
|
|
|
}
|
|
|
|
// reverse the git order
|
|
|
|
// slices.Reverse(rtags.tags)
|
|
|
|
}
|
|
|
|
|
2024-02-17 08:39:09 -06:00
|
|
|
func (rtags *GitTagBox) ListAll() []*Tag {
|
|
|
|
var tags []*Tag
|
2024-02-07 08:51:45 -06:00
|
|
|
for _, t := range rtags.tags {
|
|
|
|
tags = append(tags, t)
|
|
|
|
}
|
|
|
|
return tags
|
|
|
|
}
|
|
|
|
|
2024-02-17 08:39:09 -06:00
|
|
|
func (rtags *GitTagBox) List() []*Tag {
|
|
|
|
var tags []*Tag
|
2024-02-07 08:51:45 -06:00
|
|
|
for _, t := range rtags.tags {
|
|
|
|
if t.hidden {
|
|
|
|
// log.Info("tag is hidden", i, t.tag.String())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// log.Info("tag is shown", t.tag.String(), rtags.rs.String())
|
|
|
|
tags = append(tags, t)
|
|
|
|
}
|
|
|
|
return tags
|
|
|
|
}
|
|
|
|
|
2024-02-17 08:39:09 -06:00
|
|
|
func (rtags *GitTagBox) Prune() {
|
|
|
|
dups := make(map[string]*Tag)
|
2024-02-07 08:51:45 -06:00
|
|
|
for i, t := range rtags.tags {
|
|
|
|
if t == nil {
|
|
|
|
log.Info("tag empty:", i)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ref := t.ref.String()
|
|
|
|
_, ok := dups[ref]
|
|
|
|
if ok {
|
|
|
|
log.Info("tag is duplicate:", i, t.tag.String())
|
|
|
|
} else {
|
|
|
|
log.Info("new tag", i, t.tag.String())
|
|
|
|
dups[ref] = t
|
|
|
|
t.Hide()
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// hide tags worth keeping
|
2024-02-17 08:39:09 -06:00
|
|
|
func (rtags *GitTagBox) PruneSmart() {
|
2024-02-07 08:51:45 -06:00
|
|
|
// always keep the first tag
|
|
|
|
var first bool = true
|
|
|
|
|
2024-02-17 08:39:09 -06:00
|
|
|
dups := make(map[string]*Tag)
|
2024-02-07 08:51:45 -06:00
|
|
|
for i, t := range rtags.tags {
|
|
|
|
if t == nil {
|
|
|
|
log.Info("tag empty:", i)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for duplicate tags
|
|
|
|
ref := t.ref.String()
|
|
|
|
_, ok := dups[ref]
|
|
|
|
if ok {
|
|
|
|
log.Info("tag is duplicate:", i, t.tag.String())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
dups[ref] = t
|
|
|
|
|
|
|
|
// dump any tags that don't start with 'v'
|
|
|
|
//if !strings.HasPrefix(t.tag.String(), "v") {
|
|
|
|
// log.Info("tag does not start with v", i, t.tag.String())
|
|
|
|
// continue
|
|
|
|
//}
|
|
|
|
|
|
|
|
isVersion := regexp.MustCompile("v[0-9]+.[0-9]+.[0-9]+").MatchString
|
|
|
|
if isVersion(t.tag.String()) {
|
2024-02-09 04:55:08 -06:00
|
|
|
if first {
|
|
|
|
log.Info("keep first tag", i, t.tag.String())
|
|
|
|
t.Hide()
|
|
|
|
first = false
|
|
|
|
continue
|
|
|
|
}
|
2024-02-07 08:51:45 -06:00
|
|
|
log.Info("valid tag", i, t.tag.String())
|
|
|
|
t.Hide()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if first {
|
|
|
|
log.Info("keep first tag", i, t.tag.String())
|
|
|
|
t.Hide()
|
|
|
|
first = false
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Info("keep tag", i, t.tag.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// deleting it locally triggers some but when
|
|
|
|
// the git server was uncontactable (over IPv6 if that matters, probably it doesn't)
|
|
|
|
// and then the local delete re-added it into the tag
|
2024-02-17 08:39:09 -06:00
|
|
|
func (rs *RepoStatus) DeleteTag(rt *Tag) {
|
2024-02-07 08:51:45 -06:00
|
|
|
cmd := []string{"git", "push", "--delete", "origin", rt.tag.String()}
|
|
|
|
log.Info("RUN:", cmd)
|
|
|
|
err, output := rs.RunCmd(cmd)
|
|
|
|
if err != nil {
|
|
|
|
log.Info("cmd failed", err)
|
|
|
|
log.Info("output:", output)
|
|
|
|
}
|
|
|
|
log.Info("output:", output)
|
|
|
|
|
|
|
|
cmd = []string{"git", "tag", "--delete", rt.tag.String()}
|
|
|
|
log.Info("RUN:", cmd)
|
|
|
|
err, output = rs.RunCmd(cmd)
|
|
|
|
if err != nil {
|
|
|
|
log.Info("cmd failed", err)
|
|
|
|
log.Info("output:", output)
|
|
|
|
}
|
|
|
|
log.Info("output:", output)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-02-17 08:39:09 -06:00
|
|
|
func (rt *Tag) TagString() string {
|
2024-02-07 08:51:45 -06:00
|
|
|
return rt.tag.String()
|
|
|
|
}
|
|
|
|
|
2024-02-17 08:39:09 -06:00
|
|
|
func (rt *Tag) Hide() {
|
2024-02-07 08:51:45 -06:00
|
|
|
rt.hidden = true
|
|
|
|
rt.tag.Hide()
|
|
|
|
rt.ref.Hide()
|
|
|
|
rt.date.Hide()
|
2024-02-23 00:10:30 -06:00
|
|
|
rt.duration.Hide()
|
2024-02-12 06:23:31 -06:00
|
|
|
rt.subject.Hide()
|
2024-02-07 08:51:45 -06:00
|
|
|
rt.deleteB.Hide()
|
|
|
|
}
|
|
|
|
|
2024-02-17 08:39:09 -06:00
|
|
|
func (rt *Tag) Show() {
|
2024-02-07 08:51:45 -06:00
|
|
|
rt.hidden = false
|
|
|
|
rt.tag.Show()
|
|
|
|
rt.ref.Show()
|
|
|
|
rt.date.Show()
|
2024-02-23 00:10:30 -06:00
|
|
|
rt.duration.Show()
|
2024-02-12 06:23:31 -06:00
|
|
|
rt.subject.Show()
|
2024-02-07 08:51:45 -06:00
|
|
|
rt.deleteB.Show()
|
|
|
|
}
|
2024-02-16 17:55:13 -06:00
|
|
|
|
|
|
|
func (rs *RepoStatus) TagExists(findname string) bool {
|
|
|
|
allTags := rs.Tags.ListAll()
|
|
|
|
for _, t := range allTags {
|
|
|
|
tagname := t.TagString()
|
|
|
|
_, filename := filepath.Split(tagname)
|
|
|
|
if filename == findname {
|
|
|
|
// log.Info("found tag:", path, filename, "from", rs.Path())
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rs *RepoStatus) LocalTagExists(findname string) bool {
|
|
|
|
allTags := rs.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
|
|
|
|
}
|
2024-02-22 15:29:22 -06:00
|
|
|
|
|
|
|
func (t *Tag) Age() time.Duration {
|
|
|
|
const gitLayout = "Mon Jan 2 15:04:05 2006 -0700"
|
|
|
|
tagTime, _ := time.Parse(gitLayout, t.date.String())
|
|
|
|
return time.Since(tagTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tag) Name() string {
|
|
|
|
return t.tag.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tag) getDate() (time.Time, error) {
|
|
|
|
const gitLayout = "Mon Jan 2 15:04:05 2006 -0700"
|
|
|
|
tagTime, err := time.Parse(gitLayout, t.date.String())
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Log(REPOWARN, "tag date err", t.ref.String(), t.tag.String(), err)
|
|
|
|
return time.Now(), err
|
|
|
|
}
|
|
|
|
return tagTime, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rs *RepoStatus) NewestTag() *Tag {
|
|
|
|
var newest *Tag
|
|
|
|
var newestTime time.Time
|
|
|
|
var tagTime time.Time
|
|
|
|
var err error
|
|
|
|
var allTags []*Tag
|
|
|
|
var mu sync.Mutex
|
|
|
|
|
|
|
|
allTags = make([]*Tag, 0, 0)
|
|
|
|
junk := rs.Tags.ListAll()
|
|
|
|
allTags = append(allTags, junk...)
|
|
|
|
for _, t := range allTags {
|
|
|
|
mu.Lock()
|
|
|
|
if tagTime, err = t.getDate(); err != nil {
|
|
|
|
mu.Unlock()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// log.Log(REPOWARN, "tag", t.ref.String(), t.date.String(), t.tag.String())
|
|
|
|
// if this is the first tag, use it
|
|
|
|
if newest == nil {
|
|
|
|
newestTime = tagTime
|
|
|
|
newest = t
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the tag date is after the newest date, it's newer so use this tag
|
|
|
|
if tagTime.After(newestTime) {
|
|
|
|
newestTime = tagTime
|
|
|
|
newest = t
|
|
|
|
}
|
|
|
|
mu.Unlock()
|
|
|
|
}
|
|
|
|
return newest
|
|
|
|
}
|