Repository navigation
Expand file tree
/
Copy pathmain.go
More file actions
180 lines (163 loc) · 4.45 KB
/
Copy pathmain.go
File metadata and controls
180 lines (163 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/gocarina/gocsv"
"golang.org/x/net/http2"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func main() {
args := os.Args
if len(args) != 7 {
fmt.Println("Wrong number of arguments\n" +
"Input in this form: ./github2youtrack token user repo inputFile label milestone")
os.Exit(0)
}
token := args[1]
user := args[2]
repo := args[3]
inputFile := args[4]
label := args[5]
milestone, err := strconv.ParseInt(args[6], 10, 64)
if err != nil {
log.Panic(err)
}
err, youtrackIssues := parseYouTrackIssues(inputFile)
if err != nil {
log.Panic(err)
}
if isLegacyToken(token) {
fmt.Println("Classic GitHub personal access token are disallowed\n" +
"Please create a fine-grained token at https://github.com/settings/personal-access-tokens/new")
os.Exit(0)
}
fmt.Println("Due to the API rate limiting, don't create any issues on any projects on GitHub until I'm done!")
for _, issue := range *youtrackIssues {
err := addIssue(user, repo, token, issue, label, int(milestone))
if err == nil {
log.Printf("Issue %s created", issue.IssueId)
} else {
log.Printf("Unable to add issue %s: %s", issue.IssueId, err)
}
time.Sleep(2 * time.Second)
}
}
type GitHubIssue struct {
Title string `json:"title"`
Body string `json:"body"`
Assignees []string `json:"assignees"`
Milestone int `json:"milestone"`
Labels []string `json:"labels"`
}
func addIssue(owner string, repoName string, token string, ytIssue YouTrackIssue, label string, milestone int) error {
client := &http.Client{Transport: &http2.Transport{}}
uri := "https://api.github.com/repos/" + owner + "/" + repoName + "/issues"
issue := &GitHubIssue{
Title: ytIssue.Summary,
Body: ytIssue.Description,
Assignees: []string{owner},
Labels: []string{label},
Milestone: milestone}
data, err := json.Marshal(issue)
if err != nil {
return err
}
buf := bytes.NewReader(data)
req, err := http.NewRequest("POST", uri, buf)
if err != nil {
return err
}
req.Header.Add("Accept", "application/vnd.github+json")
req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("X-GitHub-Api-Version", "2022-11-28")
doRequest:
resp, err := client.Do(req)
if err != nil {
return err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
rateLimit, err := strconv.Atoi(resp.Header.Get("X-Ratelimit-Remaining"))
if err != nil {
return err
}
rateLimitReset, err := strconv.ParseInt(resp.Header.Get("X-Ratelimit-Reset"), 10, 64)
if err != nil {
return err
}
rateLimitResetTime := time.Unix(rateLimitReset, 0)
if rateLimit < 5 || resp.StatusCode == 403 {
for {
if time.Now().Before(rateLimitResetTime) {
eta, err := getETA(rateLimitResetTime)
if err != nil {
return err
}
log.Printf("Waiting %s to pass rate limit threshold", eta.String())
time.Sleep(eta)
} else {
goto doRequest
}
}
}
if resp.StatusCode != 201 {
return errors.New(fmt.Sprintf("unable to create issue: status code: %d", resp.StatusCode))
}
return nil
}
type YouTrackIssues []YouTrackIssue
type YouTrackIssue struct {
IssueId string `csv:"Issue Id"`
Project string `csv:"Project"`
Tags string `csv:"Tags"`
Summary string `csv:"Summary"`
Reporter string `csv:"Reporter"`
Created string `csv:"Created"`
Updated string `csv:"Updated"`
Resolved string `csv:"Resolved"`
Priority string `csv:"Priority"`
Motivation string `csv:"Motivation"`
State string `csv:"State"`
Area string `csv:"Area"`
Description string `csv:"Description"`
Votes string `csv:"Votes"`
}
func parseYouTrackIssues(path string) (error, *YouTrackIssues) {
data, err := os.ReadFile(path)
if err != nil {
return err, nil
}
var youtrackIssues YouTrackIssues
lines := strings.Split(string(data), "\n")
var newData string
for n, line := range lines {
if n == 0 {
header := strings.ReplaceAll(line, "\"", "")
newData += header + "\n"
} else {
newData += line + "\n"
}
}
err = gocsv.UnmarshalBytes([]byte(newData), &youtrackIssues)
return err, &youtrackIssues
}
func getETA(t time.Time) (time.Duration, error) {
remaining := t.Unix() - time.Now().Unix()
var duration time.Duration
if remaining <= 0 {
return duration, errors.New("ETA is less than or equal to 0")
}
return time.ParseDuration(fmt.Sprintf("%ds", remaining))
}
func isLegacyToken(token string) bool {
return strings.HasPrefix(token, "ghp_")
}