github.com/google/go-github/v33@v33.0.0/example/commitpr/main.go (about) 1 // Copyright 2018 The go-github AUTHORS. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 // The commitpr command utilizes go-github as a CLI tool for 7 // pushing files to a branch and creating a pull request from it. 8 // It takes an auth token as an environment variable and creates 9 // the commit and the PR under the account affiliated with that token. 10 // 11 // The purpose of this example is to show how to use refs, trees and commits to 12 // create commits and pull requests. 13 // 14 // Note, if you want to push a single file, you probably prefer to use the 15 // content API. An example is available here: 16 // https://godoc.org/github.com/google/go-github/github#example-RepositoriesService-CreateFile 17 // 18 // Note, for this to work at least 1 commit is needed, so you if you use this 19 // after creating a repository you might want to make sure you set `AutoInit` to 20 // `true`. 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 // getRef returns the commit branch reference object if it exists or creates it 61 // from the base branch before returning it. 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 // We consider that an error means the branch has not been found and needs to 68 // be created. 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 // getTree generates the tree to commit based on the given files and the commit 87 // of the ref you got in getRef. 88 func getTree(ref *github.Reference) (tree *github.Tree, err error) { 89 // Create a tree with what to commit. 90 entries := []*github.TreeEntry{} 91 92 // Load each file into the tree. 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 // getFileContent loads the local content of a file and return the target name 106 // of the file in the target repository and its contents. 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 // pushCommit creates the commit in the given reference using the given tree. 126 func pushCommit(ref *github.Reference, tree *github.Tree) (err error) { 127 // Get the parent commit to attach the commit to. 128 parent, _, err := client.Repositories.GetCommit(ctx, *sourceOwner, *sourceRepo, *ref.Object.SHA) 129 if err != nil { 130 return err 131 } 132 // This is not always populated, but is needed. 133 parent.Commit.SHA = parent.SHA 134 135 // Create the commit using the tree. 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 // Attach the commit to the master branch. 145 ref.Object.SHA = newCommit.SHA 146 _, _, err = client.Git.UpdateRef(ctx, *sourceOwner, *sourceRepo, ref, false) 147 return err 148 } 149 150 // createPR creates a pull request. Based on: https://godoc.org/github.com/google/go-github/github#example-PullRequestsService-Create 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 }