Master de II. ULL. 1er cuatrimestre
gh api
gh alias
gh pretends to facilitate the access to GitHub from the command line. It brings pull requests, issues, and other GitHub concepts to the terminal next to where you are already working with git and your code.
1
2
3
➜ apuntes git:(main) gh --version
gh version 2.0.0 (2021-08-23)
https://github.com/cli/cli/releases/tag/v2.0.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
➜ apuntes git:(main) ✗ gh help
Work seamlessly with GitHub from the command line.
USAGE
gh <command> <subcommand> [flags]
CORE COMMANDS
browse: Open the repository in the browser
gist: Manage gists
issue: Manage issues
pr: Manage pull requests
release: Manage GitHub releases
repo: Create, clone, fork, and view repositories
ACTIONS COMMANDS
actions: Learn about working with GitHub actions
run: View details about workflow runs
workflow: View details about GitHub Actions workflows
ADDITIONAL COMMANDS
alias: Create command shortcuts
api: Make an authenticated GitHub API request
auth: Login, logout, and refresh your authentication
completion: Generate shell completion scripts
config: Manage configuration for gh
extension: Manage gh extensions
help: Help about any command
secret: Manage GitHub secrets
ssh-key: Manage SSH keys
EXTENSION COMMANDS
clone-org
gp
project
FLAGS
--help Show help for command
--version Show gh version
EXAMPLES
$ gh issue create
$ gh repo clone cli/cli
$ gh pr checkout 321
ENVIRONMENT VARIABLES
See 'gh help environment' for the list of supported environment variables.
LEARN MORE
Use 'gh <command> <subcommand> --help' for more information about a command.
Read the manual at https://cli.github.com/manual
FEEDBACK
Open an issue using 'gh issue create -R github.com/cli/cli'
To install it, see the installation instructions.
Check the GitHub CLI Manual for more details.
There are several ways you can extend/customize gh
:
gh alias set
gh api
Some gh commands support exporting the data as JSON as an alternative to their usual line-based plain text output.
This is suitable for passing structured data to scripts. The JSON output is enabled with the --json
option, followed by the list of fields to fetch.
Use the flag without a value to get the list of available fields.
1
2
3
4
5
6
$ gh issue list
Showing 2 of 2 open issues in crguezl/learning-graphql-with-gh
#2 second issue about 6 days ago
#1 First test issue about 6 days ago
If we specify a list of comma separated fields we get those fields in JSON format:
1
2
3
4
5
6
7
8
9
10
11
12
13
$ gh issue list --json number,title,body
[
{
"body": "second",
"number": 2,
"title": "second issue"
},
{
"body": "💯 bien!",
"number": 1,
"title": "First test issue"
}
]
gh api
Go to github.com/settings/tokens
to generate a new token for gh
and set then environment variable
GITHUB_TOKEN
(export GITHUB_TOKEN= ...
)
Para generar el token:
user -> settings -> developer settings -> Personal access tokens
o mas rápido vete a https://github.com/settings/tokens
Una vez se tiene un token:
1
2
# authenticate against github.com by reading the token from a file
$ gh auth login --with-token < mytoken.txt
También es posible autenticarse con el browser usando la opción -w
:
1
2
3
4
➜ graphql-examples git:(main) ✗ gh auth login -w
! First copy your one-time code: F4D5-59E6
- Press Enter to open github.com in your browser...
Esto abre el browser, nos pide la contraseña que aparece arriba
y nos pide confirmar los permisos.
Placeholder values :owner
, :repo
, and :branch
in the endpoint argument will get replaced with values from the repository of the current directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ gh api repos/:owner/:repo/issues
[
{
"url": "https://api.github.com/repos/ULL-MII-SYTWS-1920/ull-mii-sytws-1920.github.io/issues/5",
"repository_url": "https://api.github.com/repos/ULL-MII-SYTWS-1920/ull-mii-sytws-1920.github.io",
"labels_url": "https://api.github.com/repos/ULL-MII-SYTWS-1920/ull-mii-sytws-1920.github.io/issues/5/labels{/name}",
"comments_url": "https://api.github.com/repos/ULL-MII-SYTWS-1920/ull-mii-sytws-1920.github.io/issues/5/comments",
"events_url": "https://api.github.com/repos/ULL-MII-SYTWS-1920/ull-mii-sytws-1920.github.io/issues/5/events",
"html_url": "https://github.com/ULL-MII-SYTWS-1920/ull-mii-sytws-1920.github.io/issues/5",
"id": 715027457,
"node_id": "MDU6SXNzdWU3MTUwMjc0NTc=",
"number": 5,
"title": "tema0-presentacion/practicas/pb-gh-campus-expert/",
"user": {
...
}
...
}
]
We can pipe the output to jq or use the -q
or --jq
option of gh api
:
1
2
$ gh api repos/:owner/:repo/issues | jq '.[0] | .title'
"tema0-presentacion/practicas/pb-gh-campus-expert/"
Of course, we can explicit the repo and owner. For example:
1
2
3
➜ gh api repos/ULL-MII-SYTWS-2021/p01-t1-iaas-alu0101040882/issues | jq '.[0] | .user.login, .body'
"crguezl"
"Hola @alu0101040882, \r\n\r\nVeo que alguno ya está trabajando en la práctica de
Let us see an example using the POST
method. We will start from this curl
example
in the GitHub API getting started guide:
1
2
3
4
5
6
7
8
$ curl -i -H "Authorization: token 5199831f4dd3b79e7c5b7e0ebe75d67aa66e79d4" \
-d '{ \
"name": "blog", \
"auto_init": true, \
"private": true, \
"gitignore_template": "nanoc" \
}' \
https://api.github.com/user/repos
and let us adapt to gh api
. We use -X
or --method string
to set the HTTP method for the request (default GET
) and -f
to set the fields:
1
➜ /tmp gh api -X POST -f name=repo-prueba-gh-api -f private=true /user/repos
This way we have created a private repo inside the user scope:
1
2
➜ input-option git:(master) ✗ gh repo-delete crguezl/repo-prueba-gh-api
➜ input-option git:(master) ✗ gh api -f name=repo-prueba-gh-api -f private=true /user/repos
The option --paginate
allow us to make additional HTTP requests to fetch
all pages of results. Here is an example.
1
2
3
4
5
6
➜ gh alias set get-repos 'api /orgs/$1/repos'
- Adding alias for get-repos: api /orgs/$1/repos
✓ Added alias.
➜ gh alias list
co: pr checkout
get-repos: api /orgs/$1/repos
1
➜ gh get-repos ULL-MII-SYTWS-2021
Now we can pipe the output to jq to get the names of the repos:
1
2
3
4
5
6
7
8
➜ gh get-repos ULL-MII-SYTWS-2021 | jq '.[].full_name' -
"ULL-MII-SYTWS-2021/sytws-2021-meta"
"ULL-MII-SYTWS-2021/sytws2021-private"
"ULL-MII-SYTWS-2021/books-shared"
"ULL-MII-SYTWS-2021/p01-t1-iaas-fcohdezc"
"ULL-MII-SYTWS-2021/p01-t1-iaas-crguezl"
"ULL-MII-SYTWS-2021/p01-t1-iaas-alu0100886870"
...
Let ask for the repos in the PL organization for the course 19/20:
1
2
➜ gh api /orgs/ULL-ESIT-PL-1920/repos | jq '.[] | .name' | wc
30 30 1088
It gave us 30 repos. There are much more than that in that organization.
If we use --paginate
the request takes a long time and gives us near a thousand repos:
1
2
➜ gh api --paginate /orgs/ULL-ESIT-PL-1920/repos | jq '.[] | .name' | wc
990 990 32868
The option -t, --template string
of gh api
allows to format the response using a Go template.
Here is an example of template:
1
2
3
4
5
➜ gh-learning git:(master) ✗ cat template.gotemplate
Title: {{range .}}{{.title}}
Labels: ({{.labels | pluck "name" | join ", " | color "yellow"}})
Body: {{.body}}
{{end}}
and let us use it:
1
2
3
4
➜ gh-learning git:(master) ✗ gh api repos/crguezl/learning-bash/issues --template "$(cat template.gotemplate)"
Title: issue de prueba
Labels: (bug, documentation, duplicate, enhancement, help wanted, good first issue, invalid, question)
Body: 👍 blah ...
The Labels
appear in yellow.
gh alias
1
➜ gh alias set <alias> <expansion> [flags]
Declare a word as a command alias that will expand to the specified command(s).
The expansion may specify additional arguments and flags. If the expansion
includes positional placeholders such as $1
, $2
, etc., any extra arguments
that follow the invocation of an alias will be inserted appropriately.
If --shell
is specified, the alias will be run through a shell interpreter (sh). This allows you
to compose commands with |
or redirect with >
. Note that extra arguments following the alias
will not be automatically passed to the expanded expression. To have a shell alias receive
arguments, you must explicitly accept them using $1
, $2
, etc., or $@
to accept all of them.
Platform note: on Windows, shell aliases are executed via sh
as installed by Git For Windows. If
you have installed git on Windows in some other way, shell aliases may not work for you.
Quotes must always be used when defining a command as in the examples.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ gh alias set pv 'pr view'
$ gh pv -w 123
#=> gh pr view -w 123
$ gh alias set bugs 'issue list --label="bugs"'
$ gh alias set epicsBy 'issue list --author="$1" --label="epic"'
$ gh epicsBy vilmibm
#=> gh issue list --author="vilmibm" --label="epic"
$ gh alias set --shell igrep 'gh issue list --label="$1" | grep $2'
$ gh igrep epic foo
#=> gh issue list --label="epic" | grep "foo"
Este request nos da un JSON con informacion sobre los miembros de una org:
1
gh alias set org-members api --paginate "/orgs/$1/members"
Por ejemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
✗ gh org-members ULL-MII-SYTWS-2122 | jq '.[] | .url, .id, .login'
"https://api.github.com/users/alu0100898293"
22496375
"alu0100898293"
"https://api.github.com/users/alu0101102726"
37936358
"alu0101102726"
"https://api.github.com/users/crguezl"
1142554
"crguezl"
"https://api.github.com/users/PaulaExposito"
56047765
"PaulaExposito"
"https://api.github.com/users/Pmolmar"
45513418
"Pmolmar"
A partir de este alias podemos construir sub-alias:
1
✗ gh alias set my-orgs-names --shell "gh my-orgs --jq '.[].organization.login'"
Let us search for repos inside our organization using GitHub API v3:
1
➜ gh api '/search/repositories?q=iaas+org:ULL-MII-SYTWS-2021+in:name'
Here is the JSON with the full output.
Now we can use gh alias set
to make an alias get-lab
to get the repos:
1
2
3
4
5
6
➜ gh alias set get-labs 'api /search/repositories?q=$2+org:$1+in:name'
- Adding alias for get-labs: api /search/repositories?q=$2+org:$1+in:name
✓ Added alias.
➜ gh alias list
co: pr checkout
get-labs: api /search/repositories?q=$2+org:$1+in:name
And now we can use it:
1
➜ gh get-labs ULL-MII-SYTWS-2021 iaas
Next we can pipe the output to jq to get the names of the repos and the date of the last push:
1
2
3
4
5
6
7
8
9
10
11
12
13
➜ gh get-labs ULL-MII-SYTWS-2021 iaas | jq '.items[] | .name, .pushed_at'
"p01-t1-iaas-juanchojbarroso"
"2020-10-21T15:58:32Z"
"p01-t1-iaas-alu0101040882"
"2020-10-17T16:53:39Z"
"p01-t1-iaas-fcohdezc"
"2020-10-06T17:51:52Z"
"p01-t1-iaas-crguezl"
"2020-10-19T13:50:13Z"
"p01-t1-iaas-alu0100886870"
"2020-10-21T17:05:08Z"
"p01-t1-iaas-lardabi"
"2020-10-06T18:01:16Z"
We can improve it by writing a script:
1
➜ cat ~/bin/repos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
ORG=ULL-MII-SYTWS-2021
ASSIGNMENT=iaas
if [[ $# -gt 0 ]] ; then
ASSIGNMENT=$1
fi
if [[ $# -gt 1 ]] ; then
ORG=$2
fi
# echo $ASSIGNMENT $ORG
gh api --paginate /search/repositories?q=$ASSIGNMENT+org:$ORG+in:name |
jq '.items[] | .name, .pushed_at' |
sed 'N;s/\n/ => /'
Let us make an alias for gh
:
1
2
3
➜ gh alias set --shell get-repos 'repos $1 $2'
- Adding alias for get-repos: repos $1 $2
✓ Changed alias get-repos from !repos to !repos $1 $2
Watch the use of single quotes.
Let us use our new alias:
1
2
3
4
5
6
7
8
9
➜ apuntes git:(curso2021) gh get-repos TFA ULL-ESIT-PL-1920
"tfa-module-miguel-tfa" => "2020-09-04T09:40:57Z"
"tfa-daniel-tfa" => "2020-06-02T14:00:30Z"
"tfa-manuel-jorge-tfa" => "2020-09-13T21:40:24Z"
"tfa-basilio-tfa" => "2020-07-14T06:49:29Z"
"tfa-alien-tfa" => "2020-09-05T07:35:52Z"
"tfa-miguel-angel-tfa" => "2020-09-15T13:19:47Z"
"tfa-esther-sergio-tfa" => "2020-07-10T08:53:04Z"
...
GraphQL is a query language for web services APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more.
1
gh api graphql --paginate --field query=@org-num-repos.gql --jq .data.organization.repositories.totalCount
These are the contents of the file org-num-repos.gql
:
query {
organization(login: "ULL-MII-SYTWS-2122") {
repositories {
totalCount
}
}
}
Execution:
1
2
✗ source org-num-repos.bash
17
1
2
3
➜ graphql-learning git:(main) ✗ gh config set pager cat
➜ graphql-learning git:(main) ✗ cat my-repos.bash
gh api graphql --paginate -F number_of_repos=3 --field query=@my-repos.gql
In this example $number_of_repos
is a variable that is set to 3
inside the command using the option -F number_of_repos=3
1
2
3
4
5
6
7
8
9
10
11
12
➜ graphql-learning git:(main) ✗ cat my-repos.gql
query($number_of_repos:Int!){
viewer {
name
repositories(last: $number_of_repos) {
nodes {
name
}
}
}
}
Here is the output of an execution:
1
➜ graphql-learning git:(main) ✗ gh api graphql --paginate -F number_of_repos=3 --field query=@my-repos.gql
{
"data": {
"viewer": {
"name": "Casiano Rodriguez-Leon",
"repositories": {
"nodes": [
{
"name": "asyncmap-crguezl"
},
{
"name": "gh-clone-org"
},
{
"name": "learning-graphql-with-gh"
}
]
}
}
}
}
Follows an example of query using GraphQL (see The Example query in GitHub Docs).
We can set the GraphQL query in a separated file:
1
➜ bin git:(master) cat gh-api-example.graphql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
query {
repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
issues(last:2, states:OPEN) {
edges {
node {
title
url
labels(first:5) {
edges {
node {
name
}
}
}
}
}
}
}
}
To learn more, see the tutorial Forming calls with GraphQL .
Looking at the composition line by line:
1
query {
Because we want to read data from the server, not modify it, query
is the root operation. (If you don’t specify an operation, query
is also the default.)
1
repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882")
To begin the query, we want to find a repository object.
The schema
validation indicates this object requires
owner
name
argument.A schema
defines a GraphQL API’s type system. It describes the complete set of possible data (objects, fields, relationships, everything) that a client can access
1
issues(last:2, states:OPEN) {
A field is a unit of data you can retrieve from an object. As the official GraphQL docs say: The GraphQL query language is basically about selecting fields on objects.
To account for all issues in the repository, we call the issues
object.
Some details about the issues
object:
The docs tell us this object has the type IssueConnection.
Schema validation indicates this object requires a last
or first
number of results as an argument, so we provide 2
.
The docs also tell us this object accepts a states
argument, which is an IssueState
enum that accepts OPEN
or CLOSED
values.
To find only open issues, we give the states key a value of OPEN
.
1
edges {
Edges represent connections between nodes. When you query a connection, you traverse its edges to get to its nodes.
We know issues is a *connection** because the Doc says it has the IssueConnection
type.
Connections let us query related objects as part of the same call. With connections, we can use a single GraphQL call where we would have to use multiple calls to a REST API.
To retrieve data about individual issues, we have to access the node via edges.
1
node {
Here we retrieve the node at the end of the edge.
The IssueConnection docs indicate the node at the end of the IssueConnection
type is an Issue
object.
Now that we know we’re retrieving an Issue
object, we can look at the docs for issue and specify the fields we want to return:
1
2
3
4
5
6
7
8
9
title
url
labels(first:5) {
edges {
node {
name
}
}
}
Here we specify the title
, url
, and labels
fields of the Issue
object.
The labels
field has the type LabelConnection. As with the issues
object, because labels
is a connection, we must travel its edges
to a connected node
: the label
object. At the node, we can specify the label
object fields we want to return, in this case, name
.
In gh
, the --field
flag behaves like --raw-field
with magic type conversion based on the format of the value:
For GraphQL requests, all fields other than “query” and “operationName” are interpreted as GraphQL variables.
Execution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
➜ gh api graphql --paginate -F query=@gh-api-example.graphql | jq .
{
"data": {
"repository": {
"issues": {
"edges": [
{
"node": {
"title": "Revisión",
"url": "https://github.com/ULL-MII-SYTWS-2021/p01-t1-iaas-alu0101040882/issues/2",
"labels": {
"edges": [
{
"node": {
"name": "enhancement"
}
}
]
}
}
}
]
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
➜ graphql-learning git:(main) cat findissueid.bash
gh api graphql --paginate --field query=@findissueid.gql
➜ graphql-learning git:(main) cat findissueid.gql
query FindIssueID {
repository(owner:"crguezl", name:"learning-graphql-with-gh") {
issue(number:2) {
id
}
}
}
➜ graphql-learning git:(main) cat viewissue.bash
gh issue -R crguezl/learning-graphql-with-gh view $@%
➜ graphql-learning git:(main) cat addreactiontoissue.bash
#!/bin/bash
# See
# https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#example-mutation
# and
# https://docs.github.com/en/enterprise-server@3.0/graphql/guides/forming-calls-with-graphql
# for a list of supported emojis
gh api graphql --paginate --field query=@addreactiontoissue.gql
➜ graphql-learning git:(main) cat addreactiontoissue.gql
mutation AddReactionToIssue {
addReaction(input:{subjectId:"I_kwDOGLyMF84838wt",content:ROCKET}) {
reaction {
content
}
subject {
id
}
}
}
GitHub CLI extensions are repositories that provide additional gh commands.
The name of the extension repository must start with “gh-“ and it must contain an executable of the same name. All arguments passed to the gh
inside my mac the gh extensions repos are stored in ~/.local/share/gh/extensions/
1
2
3
4
5
6
7
8
9
➜ gh-learning git:(master) ✗ ls -la ~/.local/share/gh/extensions/
total 0
drwxr-xr-x 7 casianorodriguezleon staff 224 6 oct 10:11 .
drwxr-xr-x 3 casianorodriguezleon staff 96 17 sep 13:07 ..
drwxr-xr-x 7 casianorodriguezleon staff 224 6 oct 10:11 gh-clone-org
drwxr-xr-x 5 casianorodriguezleon staff 160 1 oct 17:18 gh-delete-repo
lrwxr-xr-x 1 casianorodriguezleon staff 89 1 oct 16:23 gh-gitpod-open -> /Users/casianorodriguezleon/campus-virtual/shared/2021learning/gh-learning/gh-gitpod-open
drwxr-xr-x 8 casianorodriguezleon staff 256 17 sep 13:07 gh-gp
drwxr-xr-x 8 casianorodriguezleon staff 256 17 sep 13:19 gh-project
See also:
How it works:
1
2
3
4
5
6
7
8
[/tmp/chuchu]$rm gh clone-org -s set-up -y -n
Retrieving the list of repositories: search/repositories?q=org%3AULL-MII-SYTWS-2122%20set-up
This would have cloned the following 5 repositories to /tmp/chuchu:
ULL-MII-SYTWS-2122/set-up-alu0100898293
ULL-MII-SYTWS-2122/set-up-PaulaExposito
ULL-MII-SYTWS-2122/set-up-alu0101102726
ULL-MII-SYTWS-2122/set-up-crguezl
ULL-MII-SYTWS-2122/set-up-Pmolmar
This is a extension written in Node.js