-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathmain.go
216 lines (188 loc) · 8.18 KB
/
main.go
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright 2018 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The commitpr command utilizes go-github as a CLI tool for
// pushing files to a branch and creating a pull request from it.
// It takes an auth token as an environment variable and creates
// the commit and the PR under the account affiliated with that token.
//
// The purpose of this example is to show how to use refs, trees and commits to
// create commits and pull requests.
//
// Note, if you want to push a single file, you probably prefer to use the
// content API. An example is available here:
// https://godoc.org/github.com/google/go-github/github#example-RepositoriesService-CreateFile
//
// Note, for this to work at least 1 commit is needed, so you if you use this
// after creating a repository you might want to make sure you set `AutoInit` to
// `true`.
package main
import (
"context"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"time"
"github.com/google/go-github/v28/github"
"golang.org/x/oauth2"
)
var (
sourceOwner = flag.String("source-owner", "", "Name of the owner (user or org) of the repo to create the commit in.")
sourceRepo = flag.String("source-repo", "", "Name of repo to create the commit in.")
commitMessage = flag.String("commit-message", "", "Content of the commit message.")
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")
baseBranch = flag.String("base-branch", "master", "Name of branch to create the `commit-branch` from.")
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.")
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.")
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).")
prSubject = flag.String("pr-title", "", "Title of the pull request. If not specified, no pull request will be created.")
prDescription = flag.String("pr-text", "", "Text to put in the description of the pull request.")
sourceFiles = flag.String("files", "", `Comma-separated list of files to commit and their location.
The local file is separated by its target location by a semi-colon.
If the file should be in the same location with the same name, you can just put the file name and omit the repetition.
Example: README.md,main.go:github/examples/commitpr/main.go`)
authorName = flag.String("author-name", "", "Name of the author of the commit.")
authorEmail = flag.String("author-email", "", "Email of the author of the commit.")
)
var client *github.Client
var ctx = context.Background()
// getRef returns the commit branch reference object if it exists or creates it
// from the base branch before returning it.
func getRef() (ref *github.Reference, err error) {
if ref, _, err = client.Git.GetRef(ctx, *sourceOwner, *sourceRepo, "refs/heads/"+*commitBranch); err == nil {
return ref, nil
}
// We consider that an error means the branch has not been found and needs to
// be created.
if *commitBranch == *baseBranch {
return nil, errors.New("The commit branch does not exist but `-base-branch` is the same as `-commit-branch`")
}
if *baseBranch == "" {
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")
}
var baseRef *github.Reference
if baseRef, _, err = client.Git.GetRef(ctx, *sourceOwner, *sourceRepo, "refs/heads/"+*baseBranch); err != nil {
return nil, err
}
newRef := &github.Reference{Ref: github.String("refs/heads/" + *commitBranch), Object: &github.GitObject{SHA: baseRef.Object.SHA}}
ref, _, err = client.Git.CreateRef(ctx, *sourceOwner, *sourceRepo, newRef)
return ref, err
}
// getTree generates the tree to commit based on the given files and the commit
// of the ref you got in getRef.
func getTree(ref *github.Reference) (tree *github.Tree, err error) {
// Create a tree with what to commit.
entries := []github.TreeEntry{}
// Load each file into the tree.
for _, fileArg := range strings.Split(*sourceFiles, ",") {
file, content, err := getFileContent(fileArg)
if err != nil {
return nil, err
}
entries = append(entries, github.TreeEntry{Path: github.String(file), Type: github.String("blob"), Content: github.String(string(content)), Mode: github.String("100644")})
}
tree, _, err = client.Git.CreateTree(ctx, *sourceOwner, *sourceRepo, *ref.Object.SHA, entries)
return tree, err
}
// getFileContent loads the local content of a file and return the target name
// of the file in the target repository and its contents.
func getFileContent(fileArg string) (targetName string, b []byte, err error) {
var localFile string
files := strings.Split(fileArg, ":")
switch {
case len(files) < 1:
return "", nil, errors.New("empty `-files` parameter")
case len(files) == 1:
localFile = files[0]
targetName = files[0]
default:
localFile = files[0]
targetName = files[1]
}
b, err = ioutil.ReadFile(localFile)
return targetName, b, err
}
// createCommit creates the commit in the given reference using the given tree.
func pushCommit(ref *github.Reference, tree *github.Tree) (err error) {
// Get the parent commit to attach the commit to.
parent, _, err := client.Repositories.GetCommit(ctx, *sourceOwner, *sourceRepo, *ref.Object.SHA)
if err != nil {
return err
}
// This is not always populated, but is needed.
parent.Commit.SHA = parent.SHA
// Create the commit using the tree.
date := time.Now()
author := &github.CommitAuthor{Date: &date, Name: authorName, Email: authorEmail}
commit := &github.Commit{Author: author, Message: commitMessage, Tree: tree, Parents: []github.Commit{*parent.Commit}}
newCommit, _, err := client.Git.CreateCommit(ctx, *sourceOwner, *sourceRepo, commit)
if err != nil {
return err
}
// Attach the commit to the master branch.
ref.Object.SHA = newCommit.SHA
_, _, err = client.Git.UpdateRef(ctx, *sourceOwner, *sourceRepo, ref, false)
return err
}
// createPR creates a pull request. Based on: https://godoc.org/github.com/google/go-github/github#example-PullRequestsService-Create
func createPR() (err error) {
if *prSubject == "" {
return errors.New("missing `-pr-title` flag; skipping PR creation")
}
if *prRepoOwner != "" && *prRepoOwner != *sourceOwner {
*commitBranch = fmt.Sprintf("%s:%s", *sourceOwner, *commitBranch)
} else {
prRepoOwner = sourceOwner
}
if *prRepo == "" {
prRepo = sourceRepo
}
newPR := &github.NewPullRequest{
Title: prSubject,
Head: commitBranch,
Base: prBranch,
Body: prDescription,
MaintainerCanModify: github.Bool(true),
}
pr, _, err := client.PullRequests.Create(ctx, *prRepoOwner, *prRepo, newPR)
if err != nil {
return err
}
fmt.Printf("PR created: %s\n", pr.GetHTMLURL())
return nil
}
func main() {
flag.Parse()
token := os.Getenv("GITHUB_AUTH_TOKEN")
if token == "" {
log.Fatal("Unauthorized: No token present")
}
if *sourceOwner == "" || *sourceRepo == "" || *commitBranch == "" || *sourceFiles == "" || *authorName == "" || *authorEmail == "" {
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`")
}
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
tc := oauth2.NewClient(ctx, ts)
client = github.NewClient(tc)
ref, err := getRef()
if err != nil {
log.Fatalf("Unable to get/create the commit reference: %s\n", err)
}
if ref == nil {
log.Fatalf("No error where returned but the reference is nil")
}
tree, err := getTree(ref)
if err != nil {
log.Fatalf("Unable to create the tree based on the provided files: %s\n", err)
}
if err := pushCommit(ref, tree); err != nil {
log.Fatalf("Unable to create the commit: %s\n", err)
}
if err := createPR(); err != nil {
log.Fatalf("Error while creating the pull request: %s", err)
}
}