You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
4.0 KiB

package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"github.com/fatih/color"
"github.com/google/go-github/github"
"github.com/urfave/cli"
"golang.org/x/oauth2"
)
func main() {
app := cli.NewApp()
app.Name = "tissue"
app.Description = `
tissue lets you transfer issues to and from github repositories. The
repositories do not necessarily need to be where the archive originated;
allowing you to clone issues between repositories.
It currently will not clone:
* Labels
* Milestones
Because of the additional complications with creating those items.
`
app.Usage = "Archive and Restore Github Issues"
app.Author = "Erik Hollensbe <github@hollensbe.org>"
app.Version = "the last one"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "token, t",
Usage: "The github token to use for the transfer",
EnvVar: "GITHUB_TOKEN",
},
cli.StringFlag{
Name: "repository, r",
Usage: "The repository to transfer; in owner/repo syntax.",
},
cli.BoolFlag{
Name: "open, o",
Usage: "Only capture open state issues",
},
}
app.Commands = []cli.Command{
{
Name: "to",
Usage: "Transfer a JSON blob as issues into the repository",
Description: `
Transferring to a repository requires a tissue-compatible backup, generated
with 'tissue from'.
`,
Action: to,
},
{
Name: "from",
Usage: "Transfer a JSON blob from a repository, containing its issues",
Description: `
Generates a backup suitable for transferring to a new github repository with 'tissue to'.
`,
Action: from,
},
}
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func mkRepo(ctx *cli.Context) (string, string, error) {
repo := ctx.GlobalString("repository")
parts := strings.SplitN(strings.TrimSpace(repo), "/", 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("invalid repository name %v", repo)
}
if strings.TrimSpace(parts[0]) == "" || strings.TrimSpace(parts[1]) == "" {
return "", "", fmt.Errorf("components of repository name %v are missing", repo)
}
return parts[0], parts[1], nil
}
func argCheck(ctx *cli.Context) (string, string, error) {
if strings.TrimSpace(ctx.GlobalString("token")) == "" {
return "", "", errors.New("no token -- seek help")
}
return mkRepo(ctx)
}
func to(ctx *cli.Context) error {
owner, repo, err := argCheck(ctx)
if err != nil {
return err
}
tok := &oauth2.Token{AccessToken: ctx.GlobalString("token")}
client := github.NewClient(oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(tok)))
var totalIssues []*github.Issue
if err := json.NewDecoder(os.Stdin).Decode(&totalIssues); err != nil {
return err
}
for _, issue := range totalIssues {
color.New(color.FgWhite).Printf("Injecting issue for #%d: %q ...", issue.GetNumber(), issue.GetTitle())
assn := []string{}
for _, ass := range issue.Assignees {
assn = append(assn, ass.GetLogin())
}
_, _, err := client.Issues.Create(context.Background(), owner, repo, &github.IssueRequest{
Title: issue.Title,
Body: issue.Body,
State: issue.State,
Assignees: &assn,
})
if err != nil {
return err
}
color.New(color.FgGreen).Println("done")
}
return nil
}
func from(ctx *cli.Context) error {
owner, repo, err := argCheck(ctx)
if err != nil {
return err
}
tok := &oauth2.Token{AccessToken: ctx.GlobalString("token")}
client := github.NewClient(oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(tok)))
var i int
totalIssues := []*github.Issue{}
for {
issues, _, err := client.Issues.ListByRepo(context.Background(), owner, repo, &github.IssueListByRepoOptions{ListOptions: github.ListOptions{Page: i, PerPage: 200}})
if err != nil {
return err
}
if len(issues) == 0 {
break
}
if ctx.GlobalBool("open") {
for _, issue := range issues {
if issue.GetState() == "open" {
totalIssues = append(totalIssues, issue)
}
}
} else {
totalIssues = append(totalIssues, issues...)
}
i++
}
return json.NewEncoder(os.Stdout).Encode(totalIssues)
}