├── .circleci
└── config.yml
├── .gitignore
├── .sonarcloud.properties
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── client.go
├── examples
├── .circleci
│ └── config.yml
├── README.md
└── repositories
│ ├── files
│ └── build.gradle
│ └── ms-example.yml
├── flag.go
├── git.go
├── go.mod
├── go.sum
├── img
└── ghctl-demo.gif
├── logger.go
├── main.go
├── member.go
├── protection.go
├── repo.go
├── repo_test.go
├── team.go
└── yaml.go
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | executors:
4 | golang:
5 | docker:
6 | - image: circleci/golang
7 |
8 | orbs:
9 | snyk: snyk/snyk@0.0.13
10 |
11 | commands:
12 | build:
13 | steps:
14 | - run:
15 | name: Build
16 | command: go build
17 | - run:
18 | name: Lint
19 | command: |
20 | go get -u golang.org/x/lint/golint
21 | golint ./...
22 | - run:
23 | name: "Vet: report likely mistakes in packages"
24 | command: go vet ./...
25 | - run:
26 | name: Unit Test
27 | command: go test ./... -v
28 | - snyk/scan:
29 | organization: amirashad
30 | - run:
31 | name: Get version
32 | command: ./ghctl --version
33 |
34 | jobs:
35 | validate:
36 | executor: golang
37 | steps:
38 | - checkout
39 | - build
40 | deploy:
41 | executor: golang
42 | steps:
43 | - checkout
44 | - run:
45 | name: Build for multiple architectures
46 | command: |
47 | GOOS=darwin GOARCH=amd64 go build -o artifacts/${CIRCLE_PROJECT_REPONAME}_darwin_amd64
48 | GOOS=linux GOARCH=amd64 go build -o artifacts/${CIRCLE_PROJECT_REPONAME}_linux_amd64
49 | GOOS=windows GOARCH=amd64 go build -o artifacts/${CIRCLE_PROJECT_REPONAME}_windows_amd64.exe
50 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -o artifacts/${CIRCLE_PROJECT_REPONAME}_alpinelinux_amd64
51 | - setup_remote_docker
52 | - run:
53 | name: Build yq for alpine image
54 | command: |
55 | git clone git@github.com:mikefarah/yq.git
56 | cd yq
57 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a
58 | - run:
59 | name: Publish to DockerHub
60 | command: |
61 | cp artifacts/${CIRCLE_PROJECT_REPONAME}_alpinelinux_amd64 ./${CIRCLE_PROJECT_REPONAME}
62 | VERSION=$(./artifacts/${CIRCLE_PROJECT_REPONAME}_linux_amd64 --version)
63 | echo "$DOCKER_PASS" | docker login --username $DOCKER_USER --password-stdin
64 | IMAGE_NAME=$DOCKER_USER/${CIRCLE_PROJECT_REPONAME}
65 | docker build -t $IMAGE_NAME:latest -t $IMAGE_NAME:$VERSION .
66 | docker push $IMAGE_NAME:$VERSION
67 | docker push $IMAGE_NAME:latest
68 | - run:
69 | name: List artifacts
70 | command: ls -lah artifacts/
71 | - run:
72 | name: Publish Release on GitHub
73 | command: |
74 | go get github.com/tcnksm/ghr
75 | VERSION=v$(./artifacts/${CIRCLE_PROJECT_REPONAME}_linux_amd64 --version)
76 | ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -n ${VERSION} -delete ${VERSION} ./artifacts/
77 |
78 | workflows:
79 | ghctl_workflow:
80 | jobs:
81 | - validate
82 | - deploy:
83 | requires:
84 | - validate
85 | filters:
86 | branches:
87 | only: master
88 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 | artifacts/
14 | run*.sh
15 | ghctl
16 | *.yaml
17 |
--------------------------------------------------------------------------------
/.sonarcloud.properties:
--------------------------------------------------------------------------------
1 | # Path to sources
2 | sonar.sources=.
3 | #sonar.exclusions=**/*_test.go
4 | #sonar.inclusions=
5 |
6 | # Path to tests
7 | #sonar.tests=.
8 | #sonar.test.exclusions=
9 | #sonar.test.inclusions=**/*_test.go
10 |
11 | # Source encoding
12 | #sonar.sourceEncoding=UTF-8
13 |
14 | # Exclusions for copy-paste detection
15 | #sonar.cpd.exclusions=
16 |
17 | # Properties specific to Go
18 | #sonar.go.tests.reportPaths=report.json
19 | #sonar.go.coverage.reportPaths=coverage.out
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change log
2 |
3 | ## v0.5.1 (2021-04-05)
4 |
5 | - Fix the whitespaces issues when reading yaml list
6 |
7 | ## v0.5.0 (2021-03-04)
8 |
9 | - Update go version to 1.16, github-go version to v33, go-git version to v5(.2)
10 |
11 | ## v0.4.3 (2020-05-04)
12 |
13 | - Add new arg for repositories to delete branch on merge: `deleteBranchOnMerge`
14 | - Update go version to 1.14, github-go version to v31, go-git version to v5
15 |
16 | ## v0.4.1 (2020-03-05)
17 |
18 | - No need `--gitname` param for `add file` command anymore
19 | - FIX: Now destination filename is source filename, instead of commitmessage
20 |
21 | ## v0.4.0 (2020-03-05)
22 |
23 | - Update go-github dependency to latest (v29)
24 |
25 | ## v0.3.5 (2020-02-11)
26 |
27 | - FIX: YAML: Add teams to repo before adding users to repo as mergers
28 |
29 | ## v0.3.4 (2019-12-17)
30 |
31 | - README: Add installation instruction for Windows
32 | - README: Enhance features
33 |
34 | ## v0.3.3 (2019-12-17)
35 |
36 | - Add example for repo-creation
37 | - Add demo gif
38 |
39 | ## v0.3.2 (2019-12-13)
40 |
41 | - Update readme to make it understandable
42 |
43 | ## v0.3.1 (2019-12-09)
44 |
45 | - Docker: Add yq tool to docker image
46 |
47 | ## v0.3.0 (2019-12-07)
48 |
49 | - Docker: Create DockerHub image
50 |
51 | ## v0.2.9 (2019-12-06)
52 |
53 | - YAML: Add support to get file content from yaml
54 |
55 | ## v0.2.8 (2019-12-06)
56 |
57 | - YAML: Add files to branch with commits on creation
58 |
59 | ## v0.2.7 (2019-12-04)
60 |
61 | - Support for `apply` command to apply yaml. Now creates repo, adds teams to repo, creates branches and protections
62 |
63 | ## v0.2.6 (2019-12-02)
64 |
65 | - Support for `apply` command to apply yaml then
66 |
67 | ## v0.2.5 (2019-12-02)
68 |
69 | - Add `onCreate` tag to yaml and group them (autoInit, gitignore, license)
70 |
71 | ## v0.2.4.1 (2019-12-02)
72 |
73 | - Comment SNYK Badge, because of they don't support Go Github projects yet.
74 |
75 | ## v0.2.4 (2019-12-01)
76 |
77 | - Get repo protection information as yaml: `get repo REPONAME -o yaml`
78 |
79 | ## v0.2.3 (2019-11-25)
80 |
81 | - Get repo detailed information by name: `get repo REPONAME -o json|yaml`
82 | - Get repo as yaml: `get repo REPONAME -o yaml`
83 |
84 | ## v0.2.2 (2019-11-25)
85 |
86 | - Change multiple argument strategy. Now we should add `,` between args of canpush, canpushteams, candismiss, candismissteams, required-status-checks
87 |
88 | ## v0.2.1 (2019-11-11)
89 |
90 | - Add team to repo: `add team --team "team" --repo "repo" --permission "pull|push|admin"`
91 |
92 | ## v0.2.0 (2019-11-11)
93 |
94 | - Get detailed information about team by team name: `get teams TEAMNAME`
95 |
96 | ## v0.1.10 (2019-11-09)
97 |
98 | - Update `create repo` command with support for `--defaultbranch "develop"`
99 | - Create `update repo` command analogy with `create repo`
100 |
101 | ## v0.1.9 (2019-11-08)
102 |
103 | - Add go style checking (golint) and vet to pipeline
104 | - Add SNYK vulnerability check to pipeline
105 | - Add SNYK badge to README
106 |
107 | ## v0.1.8 (2019-11-08)
108 |
109 | - Accept org name from env variable: GITHUB_ORG
110 |
111 | ## v0.1.7 (2019-11-01)
112 |
113 | - Update go-arg to version 1.2.0
114 | - Add some unit tests
115 |
116 | ## v0.1.6.1 (2019-11-01)
117 |
118 | - Change github badge style to default rounded rect
119 |
120 | ## v0.1.6 (2019-10-17)
121 |
122 | - Add `add collaborator` command with flags `--org SomeOrg --repo some-repo --user some-user --permission pull|push|admin`
123 |
124 | ## v0.1.5 (2019-10-15)
125 |
126 | - Update `create protection` command with flags `-s|--required-status-checks "ci/circleci: build" "SonarCloud Code Analysis"`
127 |
128 | ## v0.1.4 (2019-10-15)
129 |
130 | - Change CircleCI status badge type
131 | - Add SonarCloud properties to check bugs and keep code clean
132 | - Add SonarCloud status badge
133 | - Create GitHub releases only with version
134 |
135 | ## v0.1.3 (2019-10-14)
136 |
137 | - Add Github release badge
138 |
139 | ## v0.1.2 (2019-10-14)
140 |
141 | - Add CircleCI status badge
142 |
143 | ## v0.1.1 (2019-10-12)
144 |
145 | - Use struct based flags
146 | - Add Core Infrastructure Initiative (CII) badge
147 | - Delete wide from output types
148 | - Change command line arguments
149 |
150 | ## v0.1.0 (2019-10-12)
151 |
152 | - Update `create protection` command with flags `-can-push "user1,user2" -can-push-teams "team1,team2" -can-dismiss "user1,user2" -can-dismiss-teams "team1,team2"`
153 |
154 | ## v0.0.9 (2019-10-10)
155 |
156 | - Update `create protection` command with flags `-require-branches-uptodate true|false -admins true|false`
157 |
158 | ## v0.0.8 (2019-10-09)
159 |
160 | - Add `create protection` command with flags `-repo reponame -p protection-pattern -min-approve count -dismiss-stale-pr-approvals true|false -code-owner true|false`
161 |
162 | ## v0.0.7 (2019-10-07)
163 |
164 | - Add `add file` command with flags `-repo reponame -b branchname -f file -gitname "Author Name" -gitemail "author.email@email.com" -m "Commit message"`
165 |
166 | ## v0.0.6 (2019-10-05)
167 |
168 | - Add `create branch` command with flags `-repo reponame -b branchname`
169 |
170 | ## v0.0.5 (2019-10-05)
171 |
172 | - Support `create repo` command with flags `-n name -d description -h homepage`
173 | - Support `create repo` command with flags `-private true|false -issues true|false -projects true|false -wikis true|false`
174 | - Support `create repo` command with flags `-a true|false -g gitignoretemplate -l license`
175 | - Support `create repo` command with flags `-mergecommit true|false -squash true|false -rebase true|false`
176 |
177 | ## v0.0.4 (2019-10-04)
178 |
179 | - Add `create repo -n "repo-name" -o [normal, json, wide]` command
180 |
181 | ## v0.0.3 (2019-10-04)
182 |
183 | - Add `get teams` command
184 | - Add `get members -o [normal, json, wide]` command
185 |
186 | ## v0.0.2 (2019-10-02)
187 |
188 | - Add `get members` command
189 | - Add `get members -o [normal, json, wide]` command
190 |
191 | ## v0.0.1 (2019-09-30)
192 |
193 | - Add `get repos` command
194 | - Add `get repos -o [normal, json, wide]` command
195 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine
2 | LABEL maintainer="amirjanov@gmail.com"
3 |
4 | RUN apk add --no-cache curl
5 |
6 | COPY ./ghctl /bin/
7 | COPY ./yq/yq /bin/
8 |
9 | ENTRYPOINT ["/bin/ghctl"]
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Rashad Amirjanov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ghctl
2 | [](https://github.com/amirashad/ghctl/releases)
3 | [](https://bestpractices.coreinfrastructure.org/projects/3272)
4 | [](https://circleci.com/gh/amirashad/ghctl)
5 | [](https://sonarcloud.io/dashboard?id=amirashad_ghctl)
6 | [](https://hub.docker.com/r/amirashad/ghctl/)
7 |
8 |
9 | a lightweight and portable command-line GitHub repository management tool
10 |
11 | The aim of the project is to be automatize the creation of repositories with yaml files.
12 |
13 | ## Why ghctl is needed?
14 |
15 | GitHub is a great version control system. For creating repositories with branch protection, adding some mandatory reviewers to repo is tedious. Additionally if your organisation works with microservices or serverless architecture, it means that every day you will create repo.
16 |
17 | To automatize this kind of issues ghctl will help you apply your yaml configuration of repo and create repo with some pipeline.
18 |
19 | **`ghctl`** helps you create repository from command-line:
20 | 
21 |
22 | ## Installation
23 |
24 | You can download the binary built for your architecture from [the latest release](https://github.com/amirashad/ghctl/releases/latest).
25 |
26 | macOS
27 |
28 | The following is an example of installation on macOS:
29 |
30 | ```console
31 | $ curl https://github.com/amirashad/ghctl/releases/download/v0.5.0/ghctl_darwin_amd64 -L -o /usr/local/bin/ghctl
32 | $ chmod +x /usr/local/bin/ghctl
33 | ```
34 |
35 |
36 |
37 | Linux
38 |
39 | For Linux based OS, you can use following oneliner to download latest binary for AMD64 architecture.
40 | ```console
41 | $ curl -L "$(curl -Ls https://api.github.com/repos/amirashad/ghctl/releases/latest | grep -o -E "https://.+?_linux_amd64")" -o /usr/local/bin/ghctl && chmod +x /usr/local/bin/ghctl
42 | ```
43 |
44 |
45 |
46 | Windows
47 |
48 | For Windows OS, you can use following PowerShell command to download binary for AMD64 architecture.
49 | ```
50 | Invoke-WebRequest https://github.com/amirashad/ghctl/releases/download/v0.5.0/ghctl_windows_amd64.exe -O ghctl.exe
51 | ```
52 |
53 |
54 |
55 | ### Docker
56 |
57 | You can also use [ghctl via Docker](https://hub.docker.com/r/amirashad/ghctl/).
58 |
59 | ```console
60 | $ docker run --rm -v $(pwd):/data -t amirashad/ghctl
61 | ```
62 |
63 | You can use this image to automatize repo creation with CI/CD tools like Travis CI, CircleCI. `curl` and `yq` was preinstalled to image.
64 |
65 | ## Features
66 |
67 | - Apply with yaml file or with cli args
68 | - Create repository
69 | - Create branch on repository
70 | - Create protection on branch
71 | - Add mandatory reviewers to branch protection
72 | - Add required checks to branch protection
73 | - Add some files to repository, branch
74 | - Get repositories of organisation as yaml or json
75 | - Get repository by name as yaml or json
76 | - Get members of organisation as yaml or json
77 | - Get teams of organisation as yaml or json
78 |
79 | ## Limitations
80 |
81 | ghctl currently only manages repositories.
82 |
83 | ## Usage
84 |
85 | 1) ghctl to work properly should set environment variable `GITHUB_TOKEN` and `GITHUB_ORG` or specify as command-line argument `--token` and `--org` appropriately.
86 |
87 | To get more help:
88 | ```
89 | $ ghctl --help
90 | Usage: ghctl [OPTIONS] [COMMANDS] [FILE]
91 |
92 | OPTIONS:
93 | --token GitHub token
94 | --org GitHub organisation
95 | --version Display version and exit
96 | -o, --outputformat=[normal|json|yaml] Output format (default: normal)
97 | -v, --verbose, Show debug output (default: false)
98 | -h, --help, Display this help and exit
99 |
100 | COMMANDS:
101 | get
102 | repos
103 | members
104 | teams
105 | create
106 | repo
107 | branch
108 | protection
109 | add
110 | file
111 | collaborator
112 | team
113 | update
114 | repo
115 | branch
116 | protection
117 | apply
118 | ```
119 |
120 | 2) Create yaml file with needed configurations. For example, create repo.yml with following content
121 |
122 | ```yaml
123 | github:
124 | repo:
125 | name: << Repository name >>
126 | description: << Repository description >>
127 | homepage:
128 | private: false
129 | defaultBranch: develop
130 | onCreate:
131 | autoInit: true
132 | gitignore: Java
133 | license: null
134 | pages:
135 | issues: true
136 | projects: true
137 | wiki: true
138 | merge:
139 | allowMergeCommit: true
140 | allowSquashMerge: true
141 | allowRebaseMerge: true
142 | teams:
143 | team-developers: push
144 | team-admins: admin
145 | branches:
146 | - name: develop
147 | minApprove: 2
148 | codeOwners: true
149 | includeAdmins: true
150 | requiredStatusChecks:
151 | requiredBranchesUpToDate: true
152 | contexts:
153 | - 'ci/circleci: validate_code'
154 | - 'ci/circleci: validate_infra'
155 | push:
156 | users:
157 | - << Github username which has access to push to this branch >>
158 | teams: []
159 | - name: master
160 | minApprove: 1
161 | codeOwners: true
162 | includeAdmins: true
163 | requiredStatusChecks:
164 | requiredBranchesUpToDate: true
165 | contexts:
166 | - 'ci/circleci: validate_code'
167 | - 'ci/circleci: validate_infra'
168 | - 'ci/circleci: security_checks'
169 | push:
170 | users:
171 | - << Github username which has access to push to this branch >>
172 | teams: []
173 | ```
174 |
175 | 3) Apply script
176 | ```console
177 | $ ghctl apply -f repo.yml
178 | ```
179 |
180 | ## Examples
181 |
182 | - [CircleCI example for automation of repo creation](examples/)
183 |
184 |
185 |
186 | ## Exit Statuses
187 |
188 | ghctl returns the following exit statuses on exit:
189 |
190 | - 0: No issues found
191 | - 1: Errors occurred
192 |
193 | ## FAQ
194 | ### Does ghctl create projects?
195 | - No. ghctl not yet supports project creation.
196 |
197 | ## Debugging
198 |
199 | If you don't get the expected behavior, you can see the detailed logs when running with `--verbose` flag.
200 |
201 | ```console
202 | $ ghctl create repo --name my-repo --verbose
203 | ```
204 |
205 |
--------------------------------------------------------------------------------
/client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/google/go-github/v33/github"
7 | "golang.org/x/oauth2"
8 | )
9 |
10 | func createGithubClient(ctx context.Context) *github.Client {
11 | ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: args.Token})
12 | tc := oauth2.NewClient(ctx, ts)
13 | client := github.NewClient(tc)
14 | return client
15 | }
16 |
--------------------------------------------------------------------------------
/examples/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | executors:
4 | ghctl:
5 | docker:
6 | - image: amirashad/ghctl:0.5.0
7 |
8 | commands:
9 | validate:
10 | steps:
11 | - run:
12 | name: ghctl version
13 | command: ghctl --version
14 | apply:
15 | steps:
16 | - run:
17 | name: apply
18 | command: |
19 | cd repositories
20 | ghctl apply -f ms-example.yml
21 |
22 | jobs:
23 | validate:
24 | executor: ghctl
25 | steps:
26 | - checkout
27 | - validate
28 | apply:
29 | executor: ghctl
30 | steps:
31 | - checkout
32 | - validate
33 | - apply
34 |
35 | workflows:
36 | workflow:
37 | jobs:
38 | - validate
39 | - apply:
40 | context: GITHUB
41 | requires:
42 | - validate
43 | filters:
44 | branches:
45 | only: master
46 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Example repo for automatize repo creation with CI/CD
2 |
3 | In this example we'll use CircleCI as a CI/CD tool
4 |
5 | ## 1) Create CircleCI config file
6 |
7 | CircleCI config (`.circleci/config.yml`) will help you to use ghctl docker image to run ghctl commands inside
8 |
9 | ## 2) Create repository config file
10 |
11 | Describe your repository config in some YAML file, e.g. `repositories/ms-example.yml`
12 |
13 | ## 3) Create CircleCI context
14 |
15 | CircleCI context will help you to keep environment variables secure.
16 | Create two environment variables in that context:
17 | - `GITHUB_ORG` environment variable with value for your organization
18 | - `GITHUB_TOKEN` environment variable with value for your API token. More about [creating API token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line)
19 |
20 | ## 4) Apply repo-creation
21 |
22 | On push to master, CircleCI will apply your changes and will create repository with following command:
23 | ```command
24 | $ ghctl apply -f ms-example.yml
25 | ```
--------------------------------------------------------------------------------
/examples/repositories/files/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | }
4 |
5 | sourceCompatibility = '11'
6 | targetCompatibility = '11'
7 |
--------------------------------------------------------------------------------
/examples/repositories/ms-example.yml:
--------------------------------------------------------------------------------
1 | github:
2 | repo:
3 | name: ms-example
4 | description: Autogenerated repo with ghctl
5 | homepage:
6 | private: false
7 | defaultBranch: develop
8 | deleteBranchOnMerge: true
9 | onCreate:
10 | autoInit: true
11 | gitignore: Java
12 | license: null
13 | pages:
14 | issues: true
15 | projects: true
16 | wiki: true
17 | merge:
18 | allowMergeCommit: true
19 | allowSquashMerge: true
20 | allowRebaseMerge: true
21 | teams:
22 | test-team1: push
23 | test-team2: admin
24 | branches:
25 | - name: develop
26 | minApprove: 2
27 | codeOwners: true
28 | includeAdmins: true
29 | requiredStatusChecks:
30 | requiredBranchesUpToDate: true
31 | contexts:
32 | - 'ci/circleci: validate_code'
33 | - 'ci/circleci: validate_infra'
34 | push:
35 | users:
36 | - amirashad
37 | teams: []
38 | onCreate:
39 | commits:
40 | - fileName: files/build.gradle
41 | destination: build.gradle
42 | message: "Add empty build.gradle for SNYK"
43 | - name: master
44 | minApprove: 1
45 | codeOwners: true
46 | includeAdmins: true
47 | requiredStatusChecks:
48 | requiredBranchesUpToDate: true
49 | contexts:
50 | - 'ci/circleci: validate_code'
51 | - 'ci/circleci: validate_infra'
52 | - 'ci/circleci: security_checks'
53 | push:
54 | users:
55 | - amirashad
56 | teams: []
57 |
58 |
--------------------------------------------------------------------------------
/flag.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type Args struct {
4 | Token string `arg:"env:GITHUB_TOKEN,required"`
5 | Org string `arg:"env:GITHUB_ORG,required"`
6 | OutputFormat string `arg:"-o" help:"output format: normal, json" default:"normal"`
7 | Verbose bool `arg:"-v" default:"false"`
8 |
9 | Get *Get `arg:"subcommand:get"`
10 | Create *Create `arg:"subcommand:create"`
11 | Add *Add `arg:"subcommand:add"`
12 | Update *Create `arg:"subcommand:update"`
13 | Apply *Apply `arg:"subcommand:apply"`
14 | }
15 |
16 | func (Args) Version() string {
17 | return "0.5.1"
18 | }
19 |
20 | type Get struct {
21 | Repos *Repos `arg:"subcommand:repos"`
22 | Members *Members `arg:"subcommand:members"`
23 | Teams *Teams `arg:"subcommand:teams"`
24 | }
25 | type Repos struct {
26 | RepoName *string `arg:"positional"`
27 | }
28 | type Members struct {
29 | }
30 | type Teams struct {
31 | TeamName *string `arg:"positional"`
32 | }
33 |
34 | type Create struct {
35 | Repo *Repo `arg:"subcommand:repo"`
36 | Branch *Branch `arg:"subcommand:branch"`
37 | Protection *Protection `arg:"subcommand:protection"`
38 | }
39 | type Repo struct {
40 | Name *string `arg:"-n,required"`
41 | Description *string `arg:"-d"`
42 | Homepage *string `arg:"-h"`
43 |
44 | Private *bool
45 | NoIssues *bool
46 | NoProjects *bool
47 | NoWiki *bool
48 |
49 | AutoInit *bool `arg:"-a"`
50 | GitignoreTemplate *string `arg:"-i"`
51 | LicenseTemplate *string `arg:"-l"`
52 |
53 | NoMergeCommit *bool
54 | NoSquashMerge *bool
55 | NoRebaseMerge *bool
56 |
57 | DefaultBranch *string
58 | DeleteBranchOnMerge *bool
59 | }
60 | type Branch struct {
61 | Repo string `arg:"-r,required"`
62 | Branch string `arg:"-b,required"`
63 | }
64 | type Protection struct {
65 | Repo string `arg:"-r,required"`
66 | Branch string `arg:"-b,required"`
67 |
68 | MinApprove int `arg:"-p"`
69 | DismissStaleReviews bool `arg:"-d"`
70 | CanDismiss string
71 | CanDismissTeams string
72 | RequireBranchesUpToDate bool
73 |
74 | CodeOwner bool `arg:"-c"`
75 | IncludeAdmins bool `arg:"-a"`
76 |
77 | CanPush string
78 | CanPushTeams string
79 |
80 | RequiredStatusChecks string `arg:"-s,--required-status-checks"`
81 | }
82 |
83 | type Add struct {
84 | File *File `arg:"subcommand:file"`
85 | Collaborator *Collaborator `arg:"subcommand:collaborator"`
86 | Team *Team `arg:"subcommand:team"`
87 | }
88 | type File struct {
89 | Repo string `arg:"-r,required"`
90 | Branch string `arg:"-b,required"`
91 | File string `arg:"-f,required"`
92 | GitEmail string `arg:"-e,required"`
93 | CommitMessage string `arg:"-m,--gitmessage"`
94 | }
95 | type Collaborator struct {
96 | Repo string `arg:"-r,required"`
97 | User string `arg:"-u,required"`
98 | Permission string `arg:"-p,required"`
99 | }
100 | type Team struct {
101 | Repo string `arg:"-r,required"`
102 | Team string `arg:"-t,required"`
103 | Permission string `arg:"-p,required"`
104 | }
105 |
106 | type Apply struct {
107 | FileName string `arg:"-f,required"`
108 | }
109 |
--------------------------------------------------------------------------------
/git.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "log"
7 | "os"
8 | "path/filepath"
9 | "time"
10 |
11 | "github.com/go-git/go-git/v5"
12 | "github.com/go-git/go-git/v5/plumbing"
13 | "github.com/go-git/go-git/v5/plumbing/object"
14 | "github.com/go-git/go-git/v5/plumbing/transport/http"
15 | "github.com/go-git/go-git/v5/storage/memory"
16 | )
17 |
18 | const gitUsername = "some-user"
19 |
20 | // create branch from default branch
21 | func createBranch(org string, repo string, branch string, format string) {
22 | auth := &http.BasicAuth{
23 | Username: gitUsername, // anything except an empty string
24 | Password: args.Token,
25 | }
26 |
27 | // Clone the given repository to the memory
28 | repoURL := fmt.Sprintf("https://github.com/%s/%s.git", org, repo)
29 | Info("git clone %s", repoURL)
30 | r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
31 | URL: repoURL,
32 | Auth: auth,
33 | })
34 | CheckIfError(err)
35 |
36 | // Create a new branch to the current HEAD
37 | Info("git branch %s", branch)
38 | headRef, err := r.Head()
39 | CheckIfError(err)
40 | fmt.Println(headRef)
41 |
42 | // Create a new plumbing.HashReference object with the name of the branch
43 | // and the hash from the HEAD. The reference name should be a full reference
44 | // name and not an abbreviated one, as is used on the git cli.
45 | ref := plumbing.NewHashReference(plumbing.NewBranchReferenceName(branch), headRef.Hash())
46 |
47 | // The created reference is saved in the storage.
48 | err = r.Storer.SetReference(ref)
49 | CheckIfError(err)
50 |
51 | // push using default options
52 | Info("git push")
53 | err = r.Push(&git.PushOptions{Auth: auth})
54 | CheckIfError(err)
55 | }
56 |
57 | func addFile(org, repo, branch, fileFrom, fileTo, commitMessage, gitEmail, format string) {
58 | if fileFrom == "" {
59 | CheckIfError(fmt.Errorf("fileName is empty"))
60 | }
61 | if fileTo == "" {
62 | fileTo = fileFrom
63 | }
64 | if commitMessage == "" {
65 | commitMessage = "Change " + fileTo
66 | }
67 | auth := &http.BasicAuth{
68 | Username: gitUsername, // anything except an empty string
69 | Password: args.Token,
70 | }
71 |
72 | dir, err := ioutil.TempDir("", repo+"-"+branch)
73 | if err != nil {
74 | log.Fatal(err)
75 | }
76 | fmt.Println(dir)
77 | defer os.RemoveAll(dir)
78 |
79 | // Clone the given repository to the memory
80 | repoURL := fmt.Sprintf("https://github.com/%s/%s.git", org, repo)
81 | Info("git clone %s", repoURL)
82 | r, err := git.PlainClone(dir, false, &git.CloneOptions{
83 | URL: repoURL,
84 | Auth: auth,
85 | ReferenceName: plumbing.NewBranchReferenceName(branch),
86 | SingleBranch: true,
87 | Depth: 1,
88 | })
89 | CheckIfError(err)
90 |
91 | w, err := r.Worktree()
92 | CheckIfError(err)
93 |
94 | // ... we need a file to commit so let's create a new file inside of the
95 | // worktree of the project using the go standard library.
96 | // Info("echo \"hello world!\" > example-git-file")
97 | // filename := filepath.Join(dir, files)
98 | copyFile(fileFrom, filepath.Join(dir, fileTo))
99 | // err = ioutil.WriteFile(filename, []byte("hello world!"), 0644)
100 | // CheckIfError(err)
101 |
102 | // Adds the new file to the staging area.
103 | Info("git add %s -> %s", fileFrom, fileTo)
104 | _, err = w.Add(fileTo)
105 | CheckIfError(err)
106 |
107 | // We can verify the current status of the worktree using the method Status.
108 | Info("git status --porcelain")
109 | status, err := w.Status()
110 | CheckIfError(err)
111 |
112 | fmt.Println(status)
113 |
114 | // Commits the current staging area to the repository, with the new file
115 | // just created. We should provide the object.Signature of Author of the
116 | // commit.
117 | Info("git commit -m \"%s\"", commitMessage)
118 | commit, err := w.Commit(commitMessage, &git.CommitOptions{
119 | Author: &object.Signature{
120 | Email: gitEmail,
121 | When: time.Now(),
122 | },
123 | })
124 |
125 | CheckIfError(err)
126 |
127 | // Prints the current HEAD to verify that all worked well.
128 | Info("git show -s")
129 | obj, err := r.CommitObject(commit)
130 | CheckIfError(err)
131 |
132 | fmt.Println(obj)
133 |
134 | // push using default options
135 | Info("git push")
136 | err = r.Push(&git.PushOptions{Auth: auth})
137 | CheckIfError(err)
138 | }
139 |
140 | func ensureDirExists(fileName string) {
141 | dirName := filepath.Dir(fileName)
142 | os.MkdirAll(dirName, os.ModePerm)
143 | }
144 |
145 | func copyFile(from, to string) {
146 | input, err := ioutil.ReadFile(from)
147 | if err != nil {
148 | fmt.Println(err)
149 | return
150 | }
151 |
152 | ensureDirExists(to)
153 | err = ioutil.WriteFile(to, input, 0644)
154 | if err != nil {
155 | fmt.Println("Error creating", to)
156 | fmt.Println(err)
157 | return
158 | }
159 | }
160 |
161 | // CheckIfError should be used to naively panics if an error is not nil.
162 | func CheckIfError(err error) {
163 | if err == nil {
164 | return
165 | }
166 |
167 | Error(err)
168 | os.Exit(1)
169 | }
170 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/amirashad/ghctl
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/alexflint/go-arg v1.3.0
7 | github.com/go-git/go-git/v5 v5.2.0
8 | github.com/google/go-github/v33 v33.0.0
9 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
10 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
11 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 // indirect
12 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 // indirect
13 | gopkg.in/yaml.v2 v2.4.0
14 | )
15 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
36 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
37 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
38 | github.com/alexflint/go-arg v1.3.0 h1:UfldqSdFWeLtoOuVRosqofU4nmhI1pYEbT4ZFS34Bdo=
39 | github.com/alexflint/go-arg v1.3.0/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM=
40 | github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70=
41 | github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
42 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
43 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
44 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
45 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
46 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
47 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
48 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
49 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
50 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
51 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
52 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
53 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
54 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
55 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
56 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
57 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
58 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
59 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
60 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
61 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
62 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
63 | github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
64 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
65 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
66 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
67 | github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
68 | github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
69 | github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
70 | github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
71 | github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
72 | github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
73 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
74 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
75 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
76 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
77 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
78 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
79 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
80 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
81 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
82 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
83 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
84 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
85 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
86 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
87 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
88 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
89 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
90 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
91 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
92 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
93 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
94 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
95 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
96 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
97 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
98 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
99 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
100 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
101 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
102 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
103 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
104 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
105 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
106 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
107 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
108 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
109 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
110 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
111 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
112 | github.com/google/go-github/v33 v33.0.0 h1:qAf9yP0qc54ufQxzwv+u9H0tiVOnPJxo0lI/JXqw3ZM=
113 | github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
114 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
115 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
116 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
117 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
118 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
119 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
120 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
121 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
122 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
123 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
124 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
125 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
126 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
127 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
128 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
129 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
130 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
131 | github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
132 | github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
133 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
134 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
135 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
136 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
137 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
138 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
139 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
140 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
141 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
142 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
143 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
144 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
145 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
146 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
147 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
148 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
149 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
150 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
151 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
152 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
153 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
154 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
155 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
156 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
157 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
158 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
159 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
160 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
161 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
162 | github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
163 | github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
164 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
165 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
166 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
167 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
168 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
169 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
170 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
171 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
172 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
173 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
174 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
175 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
176 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
177 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
178 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
179 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
180 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
181 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
182 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
183 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
184 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
185 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
186 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
187 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
188 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
189 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
190 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
191 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
192 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
193 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
194 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
195 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
196 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
197 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
198 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
199 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
200 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
201 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
202 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
203 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
204 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
205 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
206 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
207 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
208 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
209 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
210 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
211 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
212 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
213 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
214 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
215 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
216 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
217 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
218 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
219 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
220 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
221 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
222 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
223 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
224 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
225 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
226 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
227 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
228 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
229 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
230 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
231 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
232 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
233 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
234 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
235 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
236 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
237 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
238 | golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
239 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
240 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
241 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
242 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
243 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
244 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
245 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
246 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
247 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
248 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 h1:alLDrZkL34Y2bnGHfvC1CYBRBXCXgx8AC2vY4MRtYX4=
249 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
250 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
251 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
252 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
253 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
254 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
255 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
256 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
257 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
258 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
259 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
260 | golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
261 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
262 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
263 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
264 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
265 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
266 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
267 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
268 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
269 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
270 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
271 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
272 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
273 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
274 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
275 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
276 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
277 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
278 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
279 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
280 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
281 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
282 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
283 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
284 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
285 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
286 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
287 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g=
288 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
289 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
290 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
291 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
292 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
293 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
294 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
295 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
296 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
297 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
298 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
299 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
300 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
301 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
302 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
303 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
304 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
305 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
306 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
307 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
308 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
309 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
310 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
311 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
312 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
313 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
314 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
315 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
316 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
317 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
318 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
319 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
320 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
321 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
322 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
323 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
324 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
325 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
326 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
327 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
328 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
329 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
330 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
331 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
332 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
333 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
334 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
335 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
336 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
337 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
338 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
339 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
340 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
341 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
342 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
343 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
344 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
345 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
346 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
347 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
348 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
349 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
350 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
351 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
352 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
353 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
354 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
355 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
356 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
357 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
358 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
359 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
360 | google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
361 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
362 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
363 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
364 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
365 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
366 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
367 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
368 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
369 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
370 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
371 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
372 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
373 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
374 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
375 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
376 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
377 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
378 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
379 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
380 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
381 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
382 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
383 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
384 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
385 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
386 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
387 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
388 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
389 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
390 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
391 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
392 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
393 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
394 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
395 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
396 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
397 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
398 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
399 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
400 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
401 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
402 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
403 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
404 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
405 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
406 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
407 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
408 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
409 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
410 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
411 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
412 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
413 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
414 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
415 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
416 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
417 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
418 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
419 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
420 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
421 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
422 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
423 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
424 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
425 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
426 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
427 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
428 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
429 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
430 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
431 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
432 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
433 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
434 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
435 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
436 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
437 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
438 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
439 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
440 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
441 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
442 |
--------------------------------------------------------------------------------
/img/ghctl-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amirashad/ghctl/4782c0c40239b82cba54dbae3e596c4425f4b21c/img/ghctl-demo.gif
--------------------------------------------------------------------------------
/logger.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // Info should be used to to display a info
6 | func Info(format string, args ...interface{}) {
7 | fmt.Printf("\x1b[34;1m%s\x1b[0m\n", fmt.Sprintf(format, args...))
8 | }
9 |
10 | // Warning should be used to display a warning
11 | func Warning(format string, args ...interface{}) {
12 | fmt.Printf("\x1b[36;1m%s\x1b[0m\n", fmt.Sprintf(format, args...))
13 | }
14 |
15 | // Error should be used to to display a error
16 | func Error(err error, args ...interface{}) {
17 | fmt.Printf("\x1b[31;1m%s\x1b[0m\n", fmt.Sprintf("error: %s", err))
18 | }
19 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/alexflint/go-arg"
5 | )
6 |
7 | var args Args
8 |
9 | func main() {
10 | arg.MustParse(&args)
11 |
12 | if args.Get != nil && args.Get.Repos != nil {
13 | if args.Get.Repos.RepoName != nil {
14 | getRepo(args.Org, args.Get.Repos.RepoName, args.OutputFormat)
15 | } else {
16 | getRepos(args.Org, args.OutputFormat)
17 | }
18 | } else if args.Get != nil && args.Get.Members != nil {
19 | getMembers(args.Org, args.OutputFormat)
20 | } else if args.Get != nil && args.Get.Teams != nil {
21 | if args.Get.Teams.TeamName != nil {
22 | getTeam(args.Org, args.Get.Teams.TeamName, args.OutputFormat)
23 | } else {
24 | getTeams(args.Org, args.OutputFormat)
25 | }
26 | } else if args.Create != nil && args.Create.Repo != nil {
27 | createOrUpdateRepo(args.Org,
28 | args.Create.Repo.Name, args.Create.Repo.Description, args.Create.Repo.Homepage,
29 | args.Create.Repo.Private, args.Create.Repo.NoIssues, args.Create.Repo.NoProjects, args.Create.Repo.NoWiki, args.Create.Repo.AutoInit,
30 | args.Create.Repo.GitignoreTemplate, args.Create.Repo.LicenseTemplate,
31 | args.Create.Repo.NoMergeCommit, args.Create.Repo.NoSquashMerge, args.Create.Repo.NoRebaseMerge,
32 | args.Create.Repo.DefaultBranch, args.Create.Repo.DeleteBranchOnMerge,
33 | args.OutputFormat, true)
34 | } else if args.Update != nil && args.Update.Repo != nil {
35 | createOrUpdateRepo(args.Org,
36 | args.Update.Repo.Name, args.Update.Repo.Description, args.Update.Repo.Homepage,
37 | args.Update.Repo.Private, args.Update.Repo.NoIssues, args.Update.Repo.NoProjects, args.Update.Repo.NoWiki, args.Update.Repo.AutoInit,
38 | args.Update.Repo.GitignoreTemplate, args.Update.Repo.LicenseTemplate,
39 | args.Update.Repo.NoMergeCommit, args.Update.Repo.NoSquashMerge, args.Update.Repo.NoRebaseMerge,
40 | args.Update.Repo.DefaultBranch, args.Update.Repo.DeleteBranchOnMerge,
41 | args.OutputFormat, false)
42 | } else if args.Create != nil && args.Create.Branch != nil {
43 | createBranch(args.Org,
44 | args.Create.Branch.Repo,
45 | args.Create.Branch.Branch,
46 | args.OutputFormat)
47 | } else if args.Add != nil && args.Add.File != nil {
48 | addFile(args.Org,
49 | args.Add.File.Repo, args.Add.File.Branch,
50 | args.Add.File.File,
51 | "",
52 | args.Add.File.CommitMessage,
53 | args.Add.File.GitEmail,
54 | args.OutputFormat)
55 | } else if args.Add != nil && args.Add.Collaborator != nil {
56 | addCollaboratorToRepo(args.Org,
57 | args.Add.Collaborator.Repo, args.Add.Collaborator.User,
58 | args.Add.Collaborator.Permission)
59 | } else if args.Add != nil && args.Add.Team != nil {
60 | addTeamToRepo(args.Org,
61 | args.Add.Team.Repo, args.Add.Team.Team,
62 | args.Add.Team.Permission)
63 | } else if args.Create != nil && args.Create.Protection != nil {
64 | createProtection(args.Org,
65 | args.Create.Protection.Repo, args.Create.Protection.Branch,
66 | args.Create.Protection.MinApprove,
67 | args.Create.Protection.DismissStaleReviews,
68 | args.Create.Protection.CodeOwner,
69 | args.Create.Protection.RequireBranchesUpToDate,
70 | args.Create.Protection.IncludeAdmins,
71 | args.Create.Protection.CanDismiss, args.Create.Protection.CanDismissTeams,
72 | args.Create.Protection.CanPush, args.Create.Protection.CanPushTeams,
73 | args.Create.Protection.RequiredStatusChecks)
74 | } else if args.Apply != nil {
75 | applyYaml(args.Org, args.Apply.FileName, args.OutputFormat)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/member.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "os"
8 | "sort"
9 |
10 | "github.com/google/go-github/v33/github"
11 | )
12 |
13 | func getMembers(org string, format string) {
14 | ctx := context.Background()
15 | client := createGithubClient(ctx)
16 |
17 | opt := &github.ListMembersOptions{ListOptions: github.ListOptions{PerPage: 100}}
18 | var objsAll []*github.User
19 | for {
20 | objs, resp, err := client.Organizations.ListMembers(ctx, org, opt)
21 | if err != nil {
22 | fmt.Println(err)
23 | os.Exit(1)
24 | }
25 | objsAll = append(objsAll, objs...)
26 | if resp.NextPage == 0 {
27 | break
28 | }
29 | opt.Page = resp.NextPage
30 | }
31 |
32 | sort.Slice(objsAll, func(i, j int) bool {
33 | return *objsAll[i].Login < *objsAll[j].Login
34 | })
35 |
36 | if format == "normal" {
37 | for _, repo := range objsAll {
38 | fmt.Println(*repo.Login)
39 | }
40 | } else if format == "json" {
41 | bytes, _ := json.Marshal(objsAll)
42 | fmt.Println(string(bytes))
43 | }
44 | }
45 |
46 | func getPrimaryEmail() string {
47 | ctx := context.Background()
48 | client := createGithubClient(ctx)
49 |
50 | opt := &github.ListOptions{PerPage: 100}
51 | var objsAll []*github.UserEmail
52 | for {
53 | objs, resp, err := client.Users.ListEmails(ctx, opt)
54 | if err != nil {
55 | fmt.Println(err)
56 | os.Exit(1)
57 | }
58 | objsAll = append(objsAll, objs...)
59 | if resp.NextPage == 0 {
60 | break
61 | }
62 | opt.Page = resp.NextPage
63 | }
64 |
65 | for _, e := range objsAll {
66 | if *e.Primary && *e.Verified {
67 | return *e.Email
68 | }
69 | }
70 |
71 | return ""
72 | }
73 |
--------------------------------------------------------------------------------
/protection.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/google/go-github/v33/github"
9 | )
10 |
11 | func createProtection(org, repoName, protectionPattern string, minApprove int, dismissStalePrApprovals, codeOwner bool,
12 | requireBranchesUptodate, includeAdmins bool,
13 | canDismiss, canDismissTeams, canPush, canPushTeams string,
14 | requiredStatusChecks string) {
15 | ctx := context.Background()
16 | client := createGithubClient(ctx)
17 |
18 | preq := &github.ProtectionRequest{
19 | RequiredPullRequestReviews: &github.PullRequestReviewsEnforcementRequest{
20 | RequireCodeOwnerReviews: codeOwner,
21 | RequiredApprovingReviewCount: minApprove,
22 | DismissStaleReviews: dismissStalePrApprovals,
23 | },
24 | RequiredStatusChecks: &github.RequiredStatusChecks{
25 | Strict: requireBranchesUptodate,
26 | Contexts: []string{},
27 | },
28 | EnforceAdmins: includeAdmins,
29 | }
30 |
31 | dismissUsers := splitArgs(canDismiss)
32 | dismissTeams := splitArgs(canDismissTeams)
33 | if len(dismissUsers)+len(dismissTeams) > 0 {
34 | preq.RequiredPullRequestReviews.DismissalRestrictionsRequest = &github.DismissalRestrictionsRequest{
35 | Users: &dismissUsers,
36 | Teams: &dismissTeams,
37 | }
38 | }
39 |
40 | pushUsers := splitArgs(canPush)
41 | pushTeams := splitArgs(canPushTeams)
42 | if len(pushUsers)+len(pushTeams) > 0 {
43 | preq.Restrictions = &github.BranchRestrictionsRequest{
44 | Users: pushUsers,
45 | Teams: pushTeams, // TODO: teams not working
46 | }
47 | }
48 |
49 | if len(requiredStatusChecks) > 0 {
50 | preq.RequiredStatusChecks.Contexts = splitArgs(requiredStatusChecks)
51 | }
52 |
53 | _, _, err := client.Repositories.UpdateBranchProtection(ctx, org, repoName, protectionPattern, preq)
54 | if err == nil {
55 | fmt.Println(protectionPattern)
56 | } else {
57 | fmt.Println(err)
58 | }
59 | }
60 |
61 | func splitArgs(arg string) []string {
62 | splitted := strings.Split(arg, ",")
63 | result := []string{}
64 | for _, s := range splitted {
65 | if len(strings.TrimSpace(s)) != 0 {
66 | result = append(result, s)
67 | }
68 | }
69 | return result
70 | }
71 |
--------------------------------------------------------------------------------
/repo.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "os"
8 | "sort"
9 |
10 | "github.com/google/go-github/v33/github"
11 | "gopkg.in/yaml.v2"
12 | )
13 |
14 | func getRepos(org string, format string) {
15 | ctx := context.Background()
16 | client := createGithubClient(ctx)
17 |
18 | opt := &github.RepositoryListByOrgOptions{ /*Type: "private", */ ListOptions: github.ListOptions{PerPage: 100}}
19 | var objsAll []*github.Repository
20 | for {
21 | objs, resp, err := client.Repositories.ListByOrg(ctx, org, opt)
22 | if err != nil {
23 | fmt.Println(err)
24 | os.Exit(1)
25 | }
26 | objsAll = append(objsAll, objs...)
27 | if resp.NextPage == 0 {
28 | break
29 | }
30 | opt.Page = resp.NextPage
31 | }
32 |
33 | sort.Slice(objsAll, func(i, j int) bool {
34 | return *objsAll[i].Name < *objsAll[j].Name
35 | })
36 |
37 | if format == "normal" {
38 | for _, repo := range objsAll {
39 | fmt.Println(*repo.Name)
40 | }
41 | } else if format == "json" {
42 | bytes, _ := json.Marshal(objsAll)
43 | fmt.Println(string(bytes))
44 | }
45 | }
46 |
47 | func createOrUpdateRepo(org string,
48 | name, descr, homepage *string,
49 | private, noIssues, noProjects, noWiki, autoinit *bool,
50 | gitIgnoreTemplate, licenseTemplate *string,
51 | noMergeCommit, noSquashMerge, noRebaseMerge *bool,
52 | defaultBranch *string, deleteBranchOnMerge *bool,
53 | format string, create bool) {
54 | ctx := context.Background()
55 | client := createGithubClient(ctx)
56 |
57 | repo := &github.Repository{
58 | Name: name,
59 | Description: descr,
60 | Homepage: homepage,
61 |
62 | Private: private,
63 | HasIssues: not(noIssues),
64 | HasProjects: not(noProjects),
65 | HasWiki: not(noWiki),
66 | AutoInit: autoinit,
67 |
68 | GitignoreTemplate: gitIgnoreTemplate,
69 | LicenseTemplate: licenseTemplate,
70 |
71 | AllowMergeCommit: not(noMergeCommit),
72 | AllowSquashMerge: not(noSquashMerge),
73 | AllowRebaseMerge: not(noRebaseMerge),
74 |
75 | DefaultBranch: defaultBranch,
76 |
77 | DeleteBranchOnMerge: deleteBranchOnMerge,
78 | }
79 |
80 | var objs *github.Repository
81 | var err error
82 | if create {
83 | objs, _, err = client.Repositories.Create(ctx, org, repo)
84 | } else {
85 | objs, _, err = client.Repositories.Edit(ctx, org, *name, repo)
86 | }
87 | if err != nil {
88 | fmt.Println(err)
89 | os.Exit(1)
90 | }
91 |
92 | if format == "normal" {
93 | fmt.Println(*objs.Name)
94 | } else if format == "json" {
95 | bytes, _ := json.Marshal(objs)
96 | fmt.Println(string(bytes))
97 | }
98 | }
99 |
100 | func not(o *bool) *bool {
101 | result := true
102 | if o == nil {
103 | return &result
104 | }
105 | result = !*o
106 | return &result
107 | }
108 |
109 | func addCollaboratorToRepo(org string,
110 | repo, user, permission string) {
111 | ctx := context.Background()
112 | client := createGithubClient(ctx)
113 |
114 | perm := &github.RepositoryAddCollaboratorOptions{
115 | Permission: permission,
116 | }
117 |
118 | _, resp, err := client.Repositories.AddCollaborator(ctx, org, repo, user, perm)
119 | if err != nil {
120 | fmt.Println(err)
121 | os.Exit(1)
122 | }
123 |
124 | fmt.Println(resp.Status)
125 | }
126 |
127 | func getRepo(org string, repo *string, format string) {
128 | ctx := context.Background()
129 | client := createGithubClient(ctx)
130 |
131 | obj, _, err := client.Repositories.Get(ctx, org, *repo)
132 | if err != nil {
133 | fmt.Println(err)
134 | os.Exit(1)
135 | }
136 |
137 | if format == "normal" {
138 | fmt.Println(*obj.Name)
139 | } else if format == "json" {
140 | bytes, _ := json.Marshal(obj)
141 | fmt.Println(string(bytes))
142 | } else if format == "yaml" {
143 | yamlTop := YamlTop{
144 | Github: YamlGithub{
145 | Repository: repoToYaml(obj),
146 | },
147 | }
148 | bytes, err := yaml.Marshal(&yamlTop)
149 | if err != nil {
150 | fmt.Println(err)
151 | os.Exit(1)
152 | }
153 | fmt.Println(string(bytes))
154 | }
155 | }
156 |
157 | func getRepoTeams(org string, repo string) map[string]string {
158 | ctx := context.Background()
159 | client := createGithubClient(ctx)
160 |
161 | teams, _, err := client.Repositories.ListTeams(ctx, org, repo, &github.ListOptions{})
162 | if err != nil {
163 | fmt.Println(err)
164 | os.Exit(1)
165 | }
166 |
167 | mapTeams := map[string]string{}
168 | for _, v := range teams {
169 | mapTeams[*v.Name] = *v.Permission
170 | }
171 |
172 | return mapTeams
173 | }
174 |
175 | func getRepoProtections(org string, repo string) []YamlBranch {
176 | ctx := context.Background()
177 | client := createGithubClient(ctx)
178 |
179 | opt := &github.BranchListOptions{ListOptions: github.ListOptions{PerPage: 100}}
180 | var objsAll []*github.Branch
181 | for {
182 | branches, resp, err := client.Repositories.ListBranches(ctx, org, repo, opt)
183 | if err != nil {
184 | fmt.Println(err)
185 | os.Exit(1)
186 | }
187 | objsAll = append(objsAll, branches...)
188 | if resp.NextPage == 0 {
189 | break
190 | }
191 | opt.Page = resp.NextPage
192 | }
193 |
194 | protectedBranches := []YamlBranch{}
195 | for _, v := range objsAll {
196 | if *v.Protected {
197 | protection, _, err := client.Repositories.GetBranchProtection(ctx, org, repo, *v.Name)
198 | if err != nil {
199 | fmt.Println(err)
200 | os.Exit(1)
201 | }
202 |
203 | var users, teams []string
204 | if protection.Restrictions != nil {
205 | for _, v := range protection.Restrictions.Users {
206 | users = append(users, *v.Login)
207 | }
208 | for _, v := range protection.Restrictions.Teams {
209 | teams = append(teams, *v.Name)
210 | }
211 | }
212 |
213 | yamlBranch := YamlBranch{
214 | Name: *v.Name,
215 | MinApprove: protection.RequiredPullRequestReviews.RequiredApprovingReviewCount,
216 | CodeOwners: protection.RequiredPullRequestReviews.RequireCodeOwnerReviews,
217 | IncludeAdmins: protection.EnforceAdmins.Enabled,
218 | RequiredStatusChecks: YamlBranchRequiredStatusChecks{
219 | RequiredBranchesUpToDate: protection.RequiredStatusChecks.Strict,
220 | Contexts: protection.RequiredStatusChecks.Contexts,
221 | },
222 | Push: YamlPush{
223 | Users: users,
224 | Teams: teams,
225 | },
226 | }
227 | protectedBranches = append(protectedBranches, yamlBranch)
228 | }
229 | }
230 |
231 | return protectedBranches
232 | }
233 |
--------------------------------------------------------------------------------
/repo_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "testing"
4 |
5 | var trueVal bool = true
6 | var falseVal bool = false
7 |
8 | func Test_not(t *testing.T) {
9 | type args struct {
10 | o *bool
11 | }
12 | tests := []struct {
13 | name string
14 | args args
15 | want bool
16 | }{
17 | {"Check not(true)", args{&trueVal}, falseVal},
18 | {"Check not(false)", args{&falseVal}, trueVal},
19 | {"Check not(nil)", args{nil}, trueVal},
20 | }
21 | for _, tt := range tests {
22 | t.Run(tt.name, func(t *testing.T) {
23 | if got := not(tt.args.o); *got != tt.want {
24 | t.Errorf("not() = %v, want %v", *got, tt.want)
25 | }
26 | })
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/team.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "os"
8 | "sort"
9 |
10 | "github.com/google/go-github/v33/github"
11 | )
12 |
13 | func getTeams(org string, format string) {
14 | ctx := context.Background()
15 | client := createGithubClient(ctx)
16 |
17 | opt := &github.ListOptions{PerPage: 100}
18 | var objsAll []*github.Team
19 | for {
20 | objs, resp, err := client.Teams.ListTeams(ctx, org, opt)
21 | if err != nil {
22 | fmt.Println(err)
23 | os.Exit(1)
24 | }
25 | objsAll = append(objsAll, objs...)
26 | if resp.NextPage == 0 {
27 | break
28 | }
29 | opt.Page = resp.NextPage
30 | }
31 |
32 | sort.Slice(objsAll, func(i, j int) bool {
33 | return *objsAll[i].Name < *objsAll[j].Name
34 | })
35 |
36 | if format == "normal" {
37 | for _, repo := range objsAll {
38 | fmt.Println(*repo.Name)
39 | }
40 | } else if format == "json" {
41 | bytes, _ := json.Marshal(objsAll)
42 | fmt.Println(string(bytes))
43 | }
44 | }
45 |
46 | func getTeam(org string, team *string, format string) {
47 | ctx := context.Background()
48 | client := createGithubClient(ctx)
49 |
50 | obj, _, err := client.Teams.GetTeamBySlug(ctx, org, *team)
51 | if err != nil {
52 | fmt.Println(err)
53 | os.Exit(1)
54 | }
55 |
56 | if format == "normal" {
57 | fmt.Println(*obj.Name)
58 | } else if format == "json" {
59 | bytes, _ := json.Marshal(obj)
60 | fmt.Println(string(bytes))
61 | }
62 | }
63 |
64 | func addTeamToRepo(org string,
65 | repo, team, permission string) {
66 | ctx := context.Background()
67 | client := createGithubClient(ctx)
68 |
69 | perm := &github.TeamAddTeamRepoOptions{
70 | Permission: permission,
71 | }
72 |
73 | resp, err := client.Teams.AddTeamRepoBySlug(ctx, org, team, org, repo, perm)
74 | if err != nil {
75 | fmt.Println(err)
76 | os.Exit(1)
77 | }
78 |
79 | fmt.Println(resp.Status)
80 | }
81 |
--------------------------------------------------------------------------------
/yaml.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/hex"
6 | "fmt"
7 | "strings"
8 | "io/ioutil"
9 | "os"
10 |
11 | "github.com/google/go-github/v33/github"
12 | "gopkg.in/yaml.v2"
13 | )
14 |
15 | type YamlRepositoryMerge struct {
16 | AllowMergeCommit *bool `yaml:"allowMergeCommit"`
17 | AllowSquashMerge *bool `yaml:"allowSquashMerge"`
18 | AllowRebaseMerge *bool `yaml:"allowRebaseMerge"`
19 | }
20 | type YamlRepositoryPages struct {
21 | Issues *bool `yaml:"issues"`
22 | Projects *bool `yaml:"projects"`
23 | Wiki *bool `yaml:"wiki"`
24 | }
25 | type YamlOnCreate struct {
26 | AutoInit *bool `yaml:"autoInit"`
27 | Gitignore *string `yaml:"gitignore"`
28 | License *string `yaml:"license"`
29 | }
30 | type YamlRepository struct {
31 | Name *string `yaml:"name"`
32 | Description *string `yaml:"description"`
33 | Homepage *string `yaml:"homepage"`
34 | Private *bool `yaml:"private"`
35 | DefaultBranch *string `yaml:"defaultBranch"`
36 | DeleteBranchOnMerge *bool `yaml:"deleteBranchOnMerge"`
37 |
38 | OnCreate YamlOnCreate `yaml:"onCreate"`
39 | Pages YamlRepositoryPages `yaml:"pages"`
40 | Merge YamlRepositoryMerge `yaml:"merge"`
41 | Teams map[string]string `yaml:"teams"`
42 |
43 | Branches []YamlBranch `yaml:"branches"`
44 | }
45 | type YamlGithub struct {
46 | Repository YamlRepository `yaml:"repo"`
47 | }
48 | type YamlTop struct {
49 | Github YamlGithub `yaml:"github"`
50 | }
51 |
52 | type YamlBranchRequiredStatusChecks struct {
53 | RequiredBranchesUpToDate bool `yaml:"requiredBranchesUpToDate"`
54 | Contexts []string `yaml:"contexts"`
55 | }
56 | type YamlPush struct {
57 | Users []string `yaml:"users"`
58 | Teams []string `yaml:"teams"`
59 | }
60 | type YamlBranchOnCreate struct {
61 | Commits []YamlBranchCommit `yaml:"commits"`
62 | }
63 | type YamlBranchCommit struct {
64 | Message string `yaml:"message"`
65 | FileName string `yaml:"fileName"`
66 | Text string `yaml:"text"`
67 | Destination string `yaml:"destination"`
68 | }
69 | type YamlBranch struct {
70 | Name string `yaml:"name"`
71 | MinApprove int `yaml:"minApprove"`
72 | CodeOwners bool `yaml:"codeOwners"`
73 | IncludeAdmins bool `yaml:"includeAdmins"`
74 | RequiredStatusChecks YamlBranchRequiredStatusChecks `yaml:"requiredStatusChecks"`
75 | Push YamlPush `yaml:"push"`
76 | OnCreate YamlBranchOnCreate `yaml:"onCreate"`
77 | }
78 |
79 | func repoToYaml(obj *github.Repository) YamlRepository {
80 | yamlRepo := YamlRepository{
81 | Name: obj.Name,
82 | Description: obj.Description,
83 | Homepage: obj.Homepage,
84 | Private: obj.Private,
85 | DefaultBranch: obj.DefaultBranch,
86 | DeleteBranchOnMerge: obj.DeleteBranchOnMerge,
87 |
88 | OnCreate: YamlOnCreate{
89 | AutoInit: obj.AutoInit,
90 | Gitignore: obj.GitignoreTemplate,
91 | License: obj.LicenseTemplate,
92 | },
93 | Pages: YamlRepositoryPages{
94 | Wiki: obj.HasWiki,
95 | Projects: obj.HasProjects,
96 | Issues: obj.HasIssues,
97 | },
98 | Merge: YamlRepositoryMerge{
99 | AllowMergeCommit: obj.AllowMergeCommit,
100 | AllowRebaseMerge: obj.AllowRebaseMerge,
101 | AllowSquashMerge: obj.AllowSquashMerge,
102 | },
103 | Teams: getRepoTeams(*obj.Owner.Login, *obj.Name),
104 | Branches: getRepoProtections(*obj.Owner.Login, *obj.Name),
105 | }
106 | return yamlRepo
107 | }
108 |
109 | func applyYaml(org string, fileName string, format string) {
110 | bytes, err := ioutil.ReadFile(fileName)
111 | if err != nil {
112 | fmt.Println(err)
113 | return
114 | }
115 |
116 | var yamlTop YamlTop
117 | if err := yaml.Unmarshal(bytes, &yamlTop); err != nil {
118 | fmt.Println("failed to parse ", fileName, ", err: ", err)
119 | return
120 | }
121 |
122 | fmt.Println("Parsed Yaml from file: ", yamlTop)
123 |
124 | repo := yamlTop.Github.Repository
125 | createOrUpdateRepo(org,
126 | repo.Name, repo.Description, repo.Homepage, repo.Private,
127 | not(repo.Pages.Issues), not(repo.Pages.Projects), not(repo.Pages.Wiki),
128 | repo.OnCreate.AutoInit, repo.OnCreate.Gitignore, repo.OnCreate.License,
129 | not(repo.Merge.AllowMergeCommit), not(repo.Merge.AllowRebaseMerge), not(repo.Merge.AllowSquashMerge),
130 | repo.DefaultBranch, repo.DeleteBranchOnMerge,
131 | format, true)
132 | for teamName, teamPerm := range repo.Teams {
133 | addTeamToRepo(org, *repo.Name, teamName, teamPerm)
134 | }
135 | for _, b := range repo.Branches {
136 | if b.Name != "master" {
137 | createBranch(org, *repo.Name, b.Name, format)
138 | }
139 | for _, c := range b.OnCreate.Commits {
140 | e := getPrimaryEmail()
141 | if c.FileName == "" && c.Text == "" {
142 | CheckIfError(fmt.Errorf("eighter specify fileName or text"))
143 | } else if c.Text != "" && c.Destination == "" {
144 | CheckIfError(fmt.Errorf("destination is empty"))
145 | } else if c.Text != "" && c.Destination != "" {
146 | c.FileName, err = createFileWithContent(c.Text)
147 | if err != nil {
148 | CheckIfError(fmt.Errorf("can't write to temp file"))
149 | }
150 | }
151 |
152 | addFile(org, *repo.Name, b.Name, c.FileName, c.Destination, c.Message, e, format)
153 | }
154 | createProtection(org, *repo.Name, b.Name, b.MinApprove,
155 | false, b.CodeOwners, b.RequiredStatusChecks.RequiredBranchesUpToDate, b.IncludeAdmins,
156 | "", "",
157 | makeCommaSeparatedString(b.Push.Users), makeCommaSeparatedString(b.Push.Teams), makeCommaSeparatedString(b.RequiredStatusChecks.Contexts))
158 | }
159 | createOrUpdateRepo(org,
160 | repo.Name, repo.Description, repo.Homepage, repo.Private,
161 | not(repo.Pages.Issues), not(repo.Pages.Projects), not(repo.Pages.Wiki),
162 | repo.OnCreate.AutoInit, repo.OnCreate.Gitignore, repo.OnCreate.License,
163 | not(repo.Merge.AllowMergeCommit), not(repo.Merge.AllowRebaseMerge), not(repo.Merge.AllowSquashMerge),
164 | repo.DefaultBranch, repo.DeleteBranchOnMerge,
165 | format, false)
166 | }
167 |
168 | func makeCommaSeparatedString(arr []string) string {
169 | var result string
170 | for _, v := range arr {
171 | result += strings.TrimSpace(v) + ","
172 | }
173 | return result
174 | }
175 |
176 | func createFileWithContent(content string) (fileName string, err error) {
177 | fileName = os.TempDir() + TempFileName()
178 | Info(fileName)
179 | return fileName, ioutil.WriteFile(fileName, []byte(content), 0644)
180 | }
181 |
182 | // TempFileName generates a temporary filename for use in testing or whatever
183 | func TempFileName() string {
184 | randBytes := make([]byte, 16)
185 | rand.Read(randBytes)
186 | return hex.EncodeToString(randBytes)
187 | }
188 |
--------------------------------------------------------------------------------