package repostatus import ( "regexp" "slices" "strings" "go.wit.com/gui" "go.wit.com/lib/gadgets" "go.wit.com/log" ) type repoTag struct { // 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 date *gui.Node // a button to delete the tag deleteB *gui.Node } type repoTags struct { // the originating repository rs *RepoStatus // the window for the tags window *gadgets.BasicWindow // the box to list all the tags in box *gui.Node group *gui.Node grid *gui.Node // all the tags tags []*repoTag } func (rs *RepoStatus) TagWindow() *repoTags { // return the window if it was already created if rs.TagsW != nil { rs.TagsW.window.Toggle() return rs.TagsW } tags := new(repoTags) rs.TagsW = tags tags.rs = rs tags.window = gadgets.RawBasicWindow("tags " + rs.String()) vbox := tags.window.Box() tags.newTagBox(vbox) return tags } func (tagW *repoTags) newTagBox(box *gui.Node) { tagW.group = box.NewGroup(".git tags for " + tagW.rs.String()) tagW.group.NewButton("prune tags", func() { tagW.Prune() }) tagW.group.NewButton("show all", func() { for _, t := range tagW.tags { t.Show() } }) tagW.group.NewButton("delete all", func() { for i, t := range tagW.tags { if t.hidden { // log.Info("tag is hidden", i, t.tag.String()) continue } log.Info("tag is shown", i, t.tag.String()) tagW.Delete(t) } }) grid := tagW.group.NewGrid("tags", 4, 1) tagW.grid = grid grid.NewLabel("version") grid.NewLabel("ref") grid.NewLabel("date") grid.NewLabel("delete") // works like a typerwriter // grid.NextRow() // 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 tags := []string{"%(tag)", "%(*objectname)", "%(taggerdate:raw)"} format := strings.Join(tags, "_,,,_") err, output := tagW.rs.RunCmd([]string{"git", "for-each-ref", "--sort=taggerdate", "--format", format}) if err != nil { output = "git error_,,,_a_,,,_b_,,,c" } lines := strings.Split(output, "\n") // reverse the git order slices.Reverse(lines) tagW.tags = make([]*repoTag, 0) for i, line := range lines { var parts []string parts = make([]string, 6) parts = strings.Split(line, "_,,,_") log.Info("found tag:", i, parts) if parts[0] == "" { continue } rTag := new(repoTag) rTag.tag = grid.NewLabel(parts[0]) rTag.ref = grid.NewEntrybox(parts[1]) rTag.date = grid.NewLabel(parts[2]) rTag.deleteB = grid.NewButton("delete", func() { log.Info("remove tag") }) tagW.tags = append(tagW.tags, rTag) // works like a typerwriter // grid.NextRow() } // reverse the git order // slices.Reverse(rtags.tags) } func (rtags *repoTags) ListAll() []*repoTag { var tags []*repoTag for _, t := range rtags.tags { tags = append(tags, t) } return tags } func (rtags *repoTags) List() []*repoTag { var tags []*repoTag 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 } func (rtags *repoTags) Prune() { dups := make(map[string]*repoTag) 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 func (rtags *repoTags) PruneSmart() { // always keep the first tag var first bool = true dups := make(map[string]*repoTag) 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()) { 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 func (rtags *repoTags) Delete(rt *repoTag) { rs := rtags.rs 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) } func (rt *repoTag) TagString() string { return rt.tag.String() } func (rt *repoTag) Hide() { rt.hidden = true rt.tag.Hide() rt.ref.Hide() rt.date.Hide() rt.deleteB.Hide() } func (rt *repoTag) Show() { rt.hidden = false rt.tag.Show() rt.ref.Show() rt.date.Show() rt.deleteB.Show() }