Skip to content

Commit

Permalink
release(1.0.3): added support to tidy-up stale tags; added support to…
Browse files Browse the repository at this point in the history
… delete generic refs that target either branch or tag names
  • Loading branch information
pcanilho committed Aug 25, 2023
1 parent 764106b commit 7a2af24
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 25 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ The `gh-tidy` project is an extension for the standard `gh` cli that aims at off
Examples:
$ gh tidy stale branches <owner/repo> -t 72h
$ gh tidy stale prs <owner/repo> -t 72h -s OPEN -s MERGED
$ gh tidy stale tags <owner/repo> -t 72h
$ gh tidy delete <owner/repo> -t 72h --ref <branch_name> --ref <tag_name>
Flags:
-f, --force If specified, all interactive operations will be disabled
Expand Down Expand Up @@ -77,6 +79,11 @@ Global Flags:
$ gh tidy stale branches <owner/repository> -t 128h --exclude '<regex>' --rm
```
* <ins>Delete</ins> all tags with a `stale` ref for the last `128 hours`:
```shell
$ gh tidy stale tags <owner/repository> -t 128h --rm
```
#### `Close`
* <ins>Close</ins> all PRs with `stale` commits for the last `128 hours`:
Expand Down
30 changes: 26 additions & 4 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,14 @@ func (gh *GitHub) ListPRs(ctx context.Context, states []string, owner, repo stri
return out, nil
}

func (gh *GitHub) ListBranches(ctx context.Context, owner, repo string) ([]*models.GitHubBranch, error) {
type RefType = string

const (
BranchRefType RefType = "refs/heads/"
TagRefType = "refs/tags/"
)

func (gh *GitHub) ListRefs(ctx context.Context, owner, repo string, refType RefType) ([]*models.GitHubRef, error) {
var query struct {
Repository struct {
Refs struct {
Expand All @@ -94,6 +101,11 @@ func (gh *GitHub) ListBranches(ctx context.Context, owner, repo string) ([]*mode
Commit struct {
CommittedDate time.Time
} `graphql:"... on Commit"`
Tag struct {
Tagger struct {
Date time.Time
}
} `graphql:"... on Tag"`
}
}
PageInfo struct {
Expand All @@ -107,19 +119,29 @@ func (gh *GitHub) ListBranches(ctx context.Context, owner, repo string) ([]*mode
variables := map[string]interface{}{
"owner": githubv4.String(owner),
"name": githubv4.String(repo),
"refPrefix": githubv4.String("refs/heads/"),
"refPrefix": githubv4.String(refType),
"first": githubv4.Int(100),
"after": (*githubv4.String)(nil),
}

var out []*models.GitHubBranch
var out []*models.GitHubRef
for {
if err := gh.clientV4.Query(ctx, &query, variables); err != nil {
return nil, err
}

for _, n := range query.Repository.Refs.Nodes {
out = append(out, &models.GitHubBranch{Name: n.Name, LastCommitDate: n.Target.Commit.CommittedDate, Id: n.Id})
commitDate := n.Target.Commit.CommittedDate
tagDate := n.Target.Tag.Tagger.Date

model := &models.GitHubRef{Name: n.Name, Id: n.Id}
if !commitDate.IsZero() {
model.LastCommitDate = &commitDate
}
if !tagDate.IsZero() {
model.TagDate = &tagDate
}
out = append(out, model)
}

if !query.Repository.Refs.PageInfo.HasNextPage {
Expand Down
75 changes: 75 additions & 0 deletions cmd/ref.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package cmd

import (
"fmt"
"github.com/pcanilho/gh-tidy/api"
"github.com/pcanilho/gh-tidy/helpers"
"github.com/spf13/cobra"
"os"
"strings"
)

var deleteRefCmd = &cobra.Command{
Use: "delete",
Example: `$ gh tidy delete --ref <ref>`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("only on <owner>/<repository> can to be provided")
}

if refs == nil || len(refs) == 0 {
return fmt.Errorf("at least one ref must be provided")
}

repo := args[0]
if len(owner) == 0 && strings.Contains(args[0], "/") {
composite := strings.Split(args[0], "/")
owner, repo = composite[0], composite[1]
}

// Owner
if len(owner) == 0 {
return fmt.Errorf("the [owner] flag must be provided")
}
brs, err := ghApi.ListRefs(cmd.Context(), owner, repo, api.BranchRefType)
if err != nil {
return err
}

tgs, err := ghApi.ListRefs(cmd.Context(), owner, repo, api.TagRefType)
if err != nil {
return err
}

rfs := append(brs, tgs...)
var toDeleteIds []string
for _, rf := range rfs {
for _, ref := range refs {
if rf.Name == ref {
toDeleteIds = append(toDeleteIds, rf.Id)
}
}
}

if len(toDeleteIds) == 0 {
return nil
}

if !force {
if !helpers.Prompt(fmt.Sprintf("Delete [%d] refs?", len(toDeleteIds))) {
os.Exit(0)
}
}
_, err = ghApi.DeleteRefs(cmd.Context(), toDeleteIds...)
if err != nil {
return fmt.Errorf("unable to delete [refs=%v]. error: %v%v", refs, err)
}

out = fmt.Sprintf("Deleted [refs=%v] with [ids=%v]\n", refs, toDeleteIds)
return nil
},
}

func init() {
deleteRefCmd.PersistentFlags().StringArrayVar(&refs, "ref", nil, "The provided ref(s) to be deleted")
}
6 changes: 6 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var (
staleThreshold time.Duration
excludePattern string
excludeRegex *regexp.Regexp
refs []string
owner string
format string
remove bool
Expand Down Expand Up @@ -57,6 +58,9 @@ var rootCmd = &cobra.Command{
return nil
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
if out == nil || len(strings.TrimSpace(fmt.Sprintf("%v", out))) == 0 {
return nil
}
content, err := serializer.Serialize(out)
if err != nil {
return fmt.Errorf("[INTERNAL] unable to serialise output. Error: %v", err)
Expand Down Expand Up @@ -85,6 +89,8 @@ func init() {

staleCmd.AddCommand(staleBranchesCmd)
staleCmd.AddCommand(stalePrsCmd)
staleCmd.AddCommand(staleTagsCmd)

rootCmd.AddCommand(staleCmd)
rootCmd.AddCommand(deleteRefCmd)
}
17 changes: 5 additions & 12 deletions cmd/branches.go → cmd/stale_branches.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"github.com/pcanilho/gh-tidy/api"
"github.com/pcanilho/gh-tidy/helpers"
"github.com/pcanilho/gh-tidy/models"
"github.com/spf13/cobra"
Expand All @@ -17,7 +18,7 @@ var staleBranchesCmd = &cobra.Command{
if len(args) < 1 {
return fmt.Errorf("at least one <owner>/<repository> needs to be provided")
}
view := make(map[string][]*models.GitHubBranch)
view := make(map[string][]*models.GitHubRef)
for _, repo := range args {
if len(owner) == 0 && strings.Contains(repo, "/") {
composite := strings.Split(repo, "/")
Expand All @@ -28,15 +29,15 @@ var staleBranchesCmd = &cobra.Command{
if len(owner) == 0 {
return fmt.Errorf("the [owner] flag must be provided")
}
brs, err := ghApi.ListBranches(cmd.Context(), owner, repo)
brs, err := ghApi.ListRefs(cmd.Context(), owner, repo, api.BranchRefType)
if err != nil {
return err
}
view[repo] = brs
}

for repo, branches := range view {
var filteredBranches []*models.GitHubBranch
var filteredBranches []*models.GitHubRef
for _, branch := range branches {
if excludeRegex != nil && excludeRegex.MatchString(branch.Name) {
continue
Expand All @@ -49,21 +50,13 @@ var staleBranchesCmd = &cobra.Command{
view[repo] = filteredBranches
}
out = view
//if len(args) == 1 {
// repo := args[0]
// if strings.Contains(repo, "/") {
// repo = strings.Split(repo, "/")[1]
// }
// out = view[repo]
//}

return nil
},
PostRunE: func(cmd *cobra.Command, args []string) error {
if out == nil {
return fmt.Errorf("no results found")
}
view := out.(map[string][]*models.GitHubBranch)
view := out.(map[string][]*models.GitHubRef)
if remove {
for repo, branches := range view {
if !force {
Expand Down
File renamed without changes.
86 changes: 86 additions & 0 deletions cmd/stale_tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package cmd

import (
"fmt"
"github.com/pcanilho/gh-tidy/api"
"github.com/pcanilho/gh-tidy/helpers"
"github.com/pcanilho/gh-tidy/models"
"github.com/spf13/cobra"
"strings"
"time"
)

var staleTagsCmd = &cobra.Command{
Use: "tags",
Aliases: []string{"t"},
Example: `$ gh tidy stale tags <owner/repo> -t 72h`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("at least one <owner>/<repository> needs to be provided")
}
view := make(map[string][]*models.GitHubRef)
for _, repo := range args {
if len(owner) == 0 && strings.Contains(repo, "/") {
composite := strings.Split(repo, "/")
owner, repo = composite[0], composite[1]
}

// Owner
if len(owner) == 0 {
return fmt.Errorf("the [owner] flag must be provided")
}
tags, err := ghApi.ListRefs(cmd.Context(), owner, repo, api.TagRefType)
if err != nil {
return err
}
view[repo] = tags
}

for repo, tags := range view {
var filteredTags []*models.GitHubRef
for _, tag := range tags {
if excludeRegex != nil && excludeRegex.MatchString(tag.Name) {
continue
}

timeToCompare := tag.TagDate
if timeToCompare == nil {
timeToCompare = tag.LastCommitDate
}
if timeToCompare.Before(time.Now().Add(-staleThreshold)) {
filteredTags = append(filteredTags, tag)
}
}
view[repo] = filteredTags
}
out = view
return nil
},
PostRunE: func(cmd *cobra.Command, args []string) error {
if out == nil {
return fmt.Errorf("no results found")
}
view := out.(map[string][]*models.GitHubRef)
if remove {
for repo, tags := range view {
if !force {
if !helpers.Prompt(fmt.Sprintf("Delete [%d] tags in repo [%v]?", len(tags), repo)) {
continue
}
}

for _, tag := range tags {
_, err := ghApi.DeleteRefs(cmd.Context(), tag.Id)
if err != nil {
return fmt.Errorf("unable to delete tags: %v. error: %v", tag.Name, err)
}
}
}
}
return nil
},
}

func init() {
staleTagsCmd.PersistentFlags().StringVar(&excludePattern, "exclude", "", "If provided, it will be used to exclude tags that match the pattern (regexp)")
}
8 changes: 8 additions & 0 deletions delete.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mr/2023.5.10-on-demand-mr
mr/2023.5.10-on-demand-mr-cex
mr/2023.5.11-on-demand-mr
mr/2023.5.11-on-demand-mr-cex
release/NL-2023.7.0
release/2023.6.0-fix-conflicts
release/2023.6.0-fix-conflicts-update-MR
release/2023.6.0
20 changes: 11 additions & 9 deletions models/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ package models

import "time"

type GitHubBranch struct {
Id string
Name string
LastCommitDate time.Time
type GitHubRef struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
LastCommitDate *time.Time `json:"last_commit_date"`
TagDate *time.Time `json:"tag_date"`
}

type GitHubPR struct {
Id string
Source, Target string
LastCommitDate time.Time
Number int
Url string
Id string `json:"id,omitempty"`
Source string `json:"source,omitempty"`
Target string `json:"target,omitempty"`
LastCommitDate time.Time `json:"last_commit_date"`
Number int `json:"number,omitempty"`
Url string `json:"url,omitempty"`
}

0 comments on commit 7a2af24

Please sign in to comment.