├── .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 | [![GitHub release](http://img.shields.io/github/release/amirashad/ghctl.svg?style=flat)](https://github.com/amirashad/ghctl/releases) 3 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3272/badge)](https://bestpractices.coreinfrastructure.org/projects/3272) 4 | [![CircleCI](https://circleci.com/gh/amirashad/ghctl.svg?style=shield)](https://circleci.com/gh/amirashad/ghctl) 5 | [![SonarCloud Status](https://sonarcloud.io/api/project_badges/measure?project=amirashad_ghctl&metric=alert_status)](https://sonarcloud.io/dashboard?id=amirashad_ghctl) 6 | [![Docker Hub](https://img.shields.io/badge/docker-ready-blue.svg)](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 | ![ghctl demo GIF](img/ghctl-demo.gif) 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 | --------------------------------------------------------------------------------