package repostatus import ( "path/filepath" "regexp" "slices" "strings" "go.wit.com/gui" "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 // the tag comment subject *gui.Node // a button to delete the tag deleteB *gui.Node } // a GUI box of all the tags in a repo type gitTagBox struct { // 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) makeTagBox(box *gui.Node) { if rs.Tags != nil { log.Log(WARN, "already scanned tags") return } tagB := new(gitTagBox) rs.Tags = tagB tagB.group = box.NewGroup(".git tags for " + rs.String()) // tagB.group.NewButton("prune tags", func() { // tagB.Prune() // }) var dups *gui.Node dups = tagB.group.NewCheckbox("Show duplicate tags").SetChecked(false) dups.Custom = func() { if dups.Checked() { tagB.Prune() } else { for _, t := range tagB.tags { t.Show() } } } tagB.group.NewButton("delete all", func() { for i, t := range tagB.tags { if t.hidden { // log.Info("tag is hidden", i, t.tag.String()) continue } log.Info("tag is shown", i, t.tag.String()) rs.DeleteTag(t) } }) grid := tagB.group.NewGrid("tags", 0, 0) tagB.grid = grid grid.NewLabel("version") grid.NewLabel("ref") grid.NewLabel("date") grid.NewLabel("release subject") // 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{"%(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, " ")) err, output := rs.RunCmd(cmd) if err != nil { output = "git error_,,,_a_,,,_b_,,,c" } lines := strings.Split(output, "\n") // reverse the git order slices.Reverse(lines) tagB.tags = make([]*repoTag, 0) 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 } // log.Info("found tag:", i, parts) rTag := new(repoTag) rTag.tag = grid.NewLabel(parts[3]) rTag.ref = grid.NewEntrybox(parts[0]) _, stamp, dur := getGitDateStamp(parts[1]) rTag.date = grid.NewLabel(stamp) grid.NewLabel(dur) rTag.subject = grid.NewLabel(parts[4]) rTag.deleteB = grid.NewButton("delete", func() { 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}) if rs.DoAll(all) { log.Info("TAG DELETED", rs.String(), tagversion) } else { log.Info("TAG DELETE FAILED", rs.String(), tagversion) } }) tagB.tags = append(tagB.tags, rTag) // works like a typerwriter grid.NextRow() } // reverse the git order // slices.Reverse(rtags.tags) } func (rtags *gitTagBox) ListAll() []*repoTag { var tags []*repoTag for _, t := range rtags.tags { tags = append(tags, t) } return tags } func (rtags *gitTagBox) 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 *gitTagBox) 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 *gitTagBox) 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()) { if first { log.Info("keep first tag", i, t.tag.String()) t.Hide() first = false continue } 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 (rs *RepoStatus) DeleteTag(rt *repoTag) { 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.subject.Hide() rt.deleteB.Hide() } func (rt *repoTag) Show() { rt.hidden = false rt.tag.Show() rt.ref.Show() rt.date.Show() rt.subject.Show() rt.deleteB.Show() } 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 }