1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package main
22
23 import (
24 "context"
25 "errors"
26 "flag"
27 "fmt"
28 "io/ioutil"
29 "log"
30 "os"
31 "strings"
32 "time"
33
34 "github.com/google/go-github/v33/github"
35 "golang.org/x/oauth2"
36 )
37
38 var (
39 sourceOwner = flag.String("source-owner", "", "Name of the owner (user or org) of the repo to create the commit in.")
40 sourceRepo = flag.String("source-repo", "", "Name of repo to create the commit in.")
41 commitMessage = flag.String("commit-message", "", "Content of the commit message.")
42 commitBranch = flag.String("commit-branch", "", "Name of branch to create the commit in. If it does not already exists, it will be created using the `base-branch` parameter")
43 baseBranch = flag.String("base-branch", "master", "Name of branch to create the `commit-branch` from.")
44 prRepoOwner = flag.String("merge-repo-owner", "", "Name of the owner (user or org) of the repo to create the PR against. If not specified, the value of the `-source-owner` flag will be used.")
45 prRepo = flag.String("merge-repo", "", "Name of repo to create the PR against. If not specified, the value of the `-source-repo` flag will be used.")
46 prBranch = flag.String("merge-branch", "master", "Name of branch to create the PR against (the one you want to merge your branch in via the PR).")
47 prSubject = flag.String("pr-title", "", "Title of the pull request. If not specified, no pull request will be created.")
48 prDescription = flag.String("pr-text", "", "Text to put in the description of the pull request.")
49 sourceFiles = flag.String("files", "", `Comma-separated list of files to commit and their location.
50 The local file is separated by its target location by a semi-colon.
51 If the file should be in the same location with the same name, you can just put the file name and omit the repetition.
52 Example: README.md,main.go:github/examples/commitpr/main.go`)
53 authorName = flag.String("author-name", "", "Name of the author of the commit.")
54 authorEmail = flag.String("author-email", "", "Email of the author of the commit.")
55 )
56
57 var client *github.Client
58 var ctx = context.Background()
59
60
61
62 func getRef() (ref *github.Reference, err error) {
63 if ref, _, err = client.Git.GetRef(ctx, *sourceOwner, *sourceRepo, "refs/heads/"+*commitBranch); err == nil {
64 return ref, nil
65 }
66
67
68
69 if *commitBranch == *baseBranch {
70 return nil, errors.New("The commit branch does not exist but `-base-branch` is the same as `-commit-branch`")
71 }
72
73 if *baseBranch == "" {
74 return nil, errors.New("The `-base-branch` should not be set to an empty string when the branch specified by `-commit-branch` does not exists")
75 }
76
77 var baseRef *github.Reference
78 if baseRef, _, err = client.Git.GetRef(ctx, *sourceOwner, *sourceRepo, "refs/heads/"+*baseBranch); err != nil {
79 return nil, err
80 }
81 newRef := &github.Reference{Ref: github.String("refs/heads/" + *commitBranch), Object: &github.GitObject{SHA: baseRef.Object.SHA}}
82 ref, _, err = client.Git.CreateRef(ctx, *sourceOwner, *sourceRepo, newRef)
83 return ref, err
84 }
85
86
87
88 func getTree(ref *github.Reference) (tree *github.Tree, err error) {
89
90 entries := []*github.TreeEntry{}
91
92
93 for _, fileArg := range strings.Split(*sourceFiles, ",") {
94 file, content, err := getFileContent(fileArg)
95 if err != nil {
96 return nil, err
97 }
98 entries = append(entries, &github.TreeEntry{Path: github.String(file), Type: github.String("blob"), Content: github.String(string(content)), Mode: github.String("100644")})
99 }
100
101 tree, _, err = client.Git.CreateTree(ctx, *sourceOwner, *sourceRepo, *ref.Object.SHA, entries)
102 return tree, err
103 }
104
105
106
107 func getFileContent(fileArg string) (targetName string, b []byte, err error) {
108 var localFile string
109 files := strings.Split(fileArg, ":")
110 switch {
111 case len(files) < 1:
112 return "", nil, errors.New("empty `-files` parameter")
113 case len(files) == 1:
114 localFile = files[0]
115 targetName = files[0]
116 default:
117 localFile = files[0]
118 targetName = files[1]
119 }
120
121 b, err = ioutil.ReadFile(localFile)
122 return targetName, b, err
123 }
124
125
126 func pushCommit(ref *github.Reference, tree *github.Tree) (err error) {
127
128 parent, _, err := client.Repositories.GetCommit(ctx, *sourceOwner, *sourceRepo, *ref.Object.SHA)
129 if err != nil {
130 return err
131 }
132
133 parent.Commit.SHA = parent.SHA
134
135
136 date := time.Now()
137 author := &github.CommitAuthor{Date: &date, Name: authorName, Email: authorEmail}
138 commit := &github.Commit{Author: author, Message: commitMessage, Tree: tree, Parents: []*github.Commit{parent.Commit}}
139 newCommit, _, err := client.Git.CreateCommit(ctx, *sourceOwner, *sourceRepo, commit)
140 if err != nil {
141 return err
142 }
143
144
145 ref.Object.SHA = newCommit.SHA
146 _, _, err = client.Git.UpdateRef(ctx, *sourceOwner, *sourceRepo, ref, false)
147 return err
148 }
149
150
151 func createPR() (err error) {
152 if *prSubject == "" {
153 return errors.New("missing `-pr-title` flag; skipping PR creation")
154 }
155
156 if *prRepoOwner != "" && *prRepoOwner != *sourceOwner {
157 *commitBranch = fmt.Sprintf("%s:%s", *sourceOwner, *commitBranch)
158 } else {
159 prRepoOwner = sourceOwner
160 }
161
162 if *prRepo == "" {
163 prRepo = sourceRepo
164 }
165
166 newPR := &github.NewPullRequest{
167 Title: prSubject,
168 Head: commitBranch,
169 Base: prBranch,
170 Body: prDescription,
171 MaintainerCanModify: github.Bool(true),
172 }
173
174 pr, _, err := client.PullRequests.Create(ctx, *prRepoOwner, *prRepo, newPR)
175 if err != nil {
176 return err
177 }
178
179 fmt.Printf("PR created: %s\n", pr.GetHTMLURL())
180 return nil
181 }
182
183 func main() {
184 flag.Parse()
185 token := os.Getenv("GITHUB_AUTH_TOKEN")
186 if token == "" {
187 log.Fatal("Unauthorized: No token present")
188 }
189 if *sourceOwner == "" || *sourceRepo == "" || *commitBranch == "" || *sourceFiles == "" || *authorName == "" || *authorEmail == "" {
190 log.Fatal("You need to specify a non-empty value for the flags `-source-owner`, `-source-repo`, `-commit-branch`, `-files`, `-author-name` and `-author-email`")
191 }
192 ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
193 tc := oauth2.NewClient(ctx, ts)
194 client = github.NewClient(tc)
195
196 ref, err := getRef()
197 if err != nil {
198 log.Fatalf("Unable to get/create the commit reference: %s\n", err)
199 }
200 if ref == nil {
201 log.Fatalf("No error where returned but the reference is nil")
202 }
203
204 tree, err := getTree(ref)
205 if err != nil {
206 log.Fatalf("Unable to create the tree based on the provided files: %s\n", err)
207 }
208
209 if err := pushCommit(ref, tree); err != nil {
210 log.Fatalf("Unable to create the commit: %s\n", err)
211 }
212
213 if err := createPR(); err != nil {
214 log.Fatalf("Error while creating the pull request: %s", err)
215 }
216 }
217
View as plain text