Basic Usage of git and gh with GitHub
Table of Contents
On macOS, I mostly use GitHub Desktop instead of git on the command line. Both my GitHub OAuth Token and Credentials are stored in the macOS KeyChain. This setup was configured a long time ago. I don’t even remember if I used git-credential-osxkeychain
or not.
I always wanted to learn git. For this post, I’ll create a new GitHub account, and set it up with SSH on an Alpine Linux VM.
Setup GitHub CLI (optional) #
Download the binary for your OS here. Run gh auth login
to start the authentication process. Select HTTPS as the protocol, and follow the rest of the instructions:
alpine:~# gh auth login
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations? HTTPS
? How would you like to authenticate GitHub CLI? Paste an authentication token
Tip: you can generate a Personal Access Token here https://github.com/settings/tokens
The minimum required scopes are 'repo', 'read:org', 'workflow'.
? Paste your authentication token: ****************************************
- gh config set -h github.com git_protocol https
✓ Configured git protocol
✓ Logged in as kgrtest
The oauth
token will be stored in $HOME/.config/gh/hosts.yml
. Keep it safe.
Setup SSH #
Generating the keys:
ssh-keygen -C "kavishhunter01@gmail.com"
Browse to your github account and add the public key. Or use gh
:
alpine:~/test_github_sshkeys# gh ssh-key add testkey.pub -t "Test Key"
✓ Public key added to your account
Test the connection:
alpine:~/test_github_sshkeys# eval "$(ssh-agent -s)"
Agent pid 4885
alpine:~/test_github_sshkeys# ssh-add testkey
Enter passphrase for testkey:
Identity added: testkey (kavishhunter01@gmail.com)
alpine:~/test_github_sshkeys#
alpine:~/test_github_sshkeys# ssh -T git@github.com
Hi kgrtest! You've successfully authenticated, but GitHub does not provide shell access.
Note: SSH Keys that were added with
gh
, will be removed when the token specified duringgh auth login
is deleted or if you re-ran the authentication process.
On macOS #
The ssh-add
utility that comes with macOS has two special options: -AK
to store your SSH Keys passphrase in the macOS KeyChain. You’ll also need to modify ~/.ssh/config
. You can read more about it here.
Fork, Branching, and Pull Requests #
To be able to send a pull request, you have to fork the repo you’ll be working on, clone it locally, switch to a new branch, commit your new changes, and push the new branch to your origin. Then you log in to your github account, browse to your fork, and open the new pull request for the upstream repo.
Origin: Is your fork that you have cloned locally. Upstream: Is the repo that you have forked from, hence the source of truth.
Most of your work will take place on origin. The origin is the only repo that you have permission to modify. Communication with upstream is to only fetch/pull new changes or to open a pull request.
Fork and Clone #
The upstream is kavishgr/Test-Repo
and the origin is kgrtest/Test-Repo
.
When you fork a repo on github, and clone it with git
, the origin shortcut is added automatically:
alpine:~# git clone git@github.com:kgrtest/Test-Repo.git
Cloning into 'Test-Repo'...
...
...
...
...
alpine:~#
alpine:~# cd Test-Repo/
alpine:~/Test-Repo#
alpine:~/Test-Repo# git remote -v
origin git@github.com:kgrtest/Test-Repo.git (fetch)
origin git@github.com:kgrtest/Test-Repo.git (push)
alpine:~/Test-Repo#
Origin is pointing to the forked repo: kgrtest/Test-Repo.git
With gh
, upstream is configured by default:
alpine:~/test# gh repo clone kgrtest/Test-Repo
Cloning into 'Test-Repo'...
...
...
...
Updating upstream
From https://github.com/kavishgr/Test-Repo
* [new branch] main -> upstream/main
alpine:~/test#
alpine:~/test# cd Test-Repo/ ; git remote -v
origin https://github.com/kgrtest/Test-Repo.git (fetch)
origin https://github.com/kgrtest/Test-Repo.git (push)
upstream https://github.com/kavishgr/Test-Repo.git (fetch)
upstream https://github.com/kavishgr/Test-Repo.git (push)
alpine:~/test/Test-Repo#
To set the upstream, run git remote add upstream [URL]
:
alpine:~/Test-Repo# git remote add upstream git@github.com:kavishgr/Test-Repo.git
alpine:~/Test-Repo# git remote -v
origin git@github.com:kgrtest/Test-Repo.git (fetch)
origin git@github.com:kgrtest/Test-Repo.git (push)
upstream git@github.com:kavishgr/Test-Repo.git (fetch)
upstream git@github.com:kavishgr/Test-Repo.git (push)
alpine:~/Test-Repo#
Set username and email:
alpine:~/Test-Repo# git config user.name "Test user"
alpine:~/Test-Repo# git config user.email "test@email.com"
Branches #
By convention, you should switch to a new branch to add new changes, and then push the new branch. If you’re working on a solo project you can just push your code to origin.
Create a new branch with git checkout -b
:
alpine:~/Test-Repo# git checkout -b quick-test
Switched to a new branch 'quick-test'
The new branch quick-test
is created.
Push new branch to create a pull request #
Add new changes and commit:
alpine:~/Test-Repo# echo "A new line" > dummyfile.txt
alpine:~/Test-Repo#
alpine:~/Test-Repo# git add .
alpine:~/Test-Repo# git commit -m "added a dummy file"
[quick-test 96eb77b] added a dummy file
1 file changed, 1 insertion(+)
create mode 100644 dummyfile.txt
If you don’t commit you new changes inside the new branch, the changes will reflect in all branches that are currently available.
Push quick-test
to your origin:
alpine:~/Test-Repo# git push origin quick-test
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 289 bytes | 48.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for 'quick-test' on GitHub by visiting:
remote: https://github.com/kgrtest/Test-Repo/pull/new/quick-test
remote:
To github.com:kgrtest/Test-Repo.git
* [new branch] quick-test -> quick-test
A pull request is automatically created for you. Go to your github account, and create the pull request. Once it’s merged, you need to keep your origin and local repo in sync with upstream. The commit that took place in quick-test
, only happend in that particular branch. The main
or master
branch is not aware of that:
### quick-test
alpine:~/Test-Repo# git log
commit 7212a7dcbc678b87ca9fc68654ef2c4ae1120824 (HEAD -> quick-test, origin/quick-test)
Author: Test user <test@email.com>
Date: Wed Oct 6 11:22:03 2021 +0400
added a dummy file
commit 8213af3f355712bdbdb98d0bc85e733cb2b380c8 (origin/main, origin/HEAD, main)
Author: Kavish Gour <kavishgr@protonmail.com>
Date: Wed Oct 6 11:15:26 2021 +0400
Initial commit
### main
alpine:~/Test-Repo# git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
alpine:~/Test-Repo#
alpine:~/Test-Repo# git log
commit 8213af3f355712bdbdb98d0bc85e733cb2b380c8 (HEAD -> main, origin/main, origin/HEAD)
Author: Kavish Gour <kavishgr@protonmail.com>
Date: Wed Oct 6 11:15:26 2021 +0400
Initial commit
You need to fetch the changes on upstream to your local repo(clone) and push it to your origin(fork). On GitHub you can click fetch upstream. I’m gonna do it manually. Switch to your main/master branch, and fetch all the commits from both origin and upstream(including deleted ones):
alpine:~/Test-Repo# git fetch --all --prune
Fetching origin
Fetching upstream
remote: Enumerating objects: 1, done.
remote: Counting objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (1/1), 625 bytes | 625.00 KiB/s, done.
From github.com:kavishgr/Test-Repo
* [new branch] main -> upstream/main
Now, reset the main branch on origin to the main branch on upstream:
alpine:~/Test-Repo# git reset --hard upstream/main
HEAD is now at 24fda2a Merge pull request #1 from kgrtest/quick-test
alpine:~/Test-Repo#
The logs should now contains all the commits:
alpine:~/Test-Repo# git log
commit 24fda2a0864625da8e09266e40283754ab448be7 (HEAD -> main, upstream/main)
Merge: 8213af3 7212a7d
Author: Kavish Gour <kavishgr@protonmail.com>
Date: Wed Oct 6 11:22:54 2021 +0400
Merge pull request #1 from kgrtest/quick-test
added a dummy file
commit 7212a7dcbc678b87ca9fc68654ef2c4ae1120824 (origin/quick-test, quick-test)
Author: Test user <test@email.com>
Date: Wed Oct 6 11:22:03 2021 +0400
added a dummy file
commit 8213af3f355712bdbdb98d0bc85e733cb2b380c8 (origin/main, origin/HEAD)
Author: Kavish Gour <kavishgr@protonmail.com>
Date: Wed Oct 6 11:15:26 2021 +0400
Initial commit
Push the changes to your origin(fork):
alpine:~/Test-Repo# git push origin main
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:kgrtest/Test-Repo.git
8213af3..24fda2a main -> main
alpine:~/Test-Repo#
Now, origin and upstream are the same. This is how it’s done manually. Another command called git pull
does the same thing(fetch and reset). Let’s say there’re new changes on the upstream. You can just pull the changes locally:
alpine:~/Test-Repo# git pull upstream main
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 688 bytes | 137.00 KiB/s, done.
From github.com:kavishgr/Test-Repo
* branch main -> FETCH_HEAD
24fda2a..8c3e6f1 main -> upstream/main
Updating 24fda2a..8c3e6f1
Fast-forward
dummyfile-2.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 dummyfile-2.txt
And push it to your origin:
alpine:~/Test-Repo# git push origin main
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:kgrtest/Test-Repo.git
24fda2a..8c3e6f1 main -> main
My gh aliases #
You can do a lot of stuff with gh
just as you would on GitHub. My list of aliases:
MacBook-Pro:~ kavish$ gh alias list
co: pr checkout
del-ssh-key-by-id: api -X DELETE "user/keys/$1"
get-ssh-key-id: api -X GET "user/keys"
repo-delete: api -X DELETE "repos/$1"
I work a lot with ssh keys. This just make things a lot more easier. Run the following to set them:
$ gh alias set repo-delete 'api -X DELETE "repos/$1"'
$ gh alias set get-ssh-key-id 'api -X GET "user/keys"'
$ gh alias set del-ssh-key-by-id 'api -X DELETE "user/keys/$1"'
Give your oauth token the necessary permissions:
$ gh auth refresh -s read:public_key
$ gh auth refresh -s delete_repo
I found the repo-delete alias here. You can setup your own aliases by using the GitHub REST api as a reference.
Delete ssh keys with gh #
To delete an ssh key, you’ll need its id. You can pipe gh get-ssh-key-id
to gron
to get both the id
and title
with sed
:
$ echo ; gh get-ssh-key-id | gron | egrep "id|title" | gsed '/title/a -----' | cut -d "." -f 2 | tr -d ';'
Note: I’m using
gsed
here. On Linux justsed
. On macOS, you can downloadgnu-sed
with homebrew.
The output will look like this:
id = 57542413
title = "macOS test key"
-----
id = 57578083
title = "Dummy key"
-----
It’s not that elegant, but it gets the job done. Now you can delete an ssh key like so:
$ gh del-ssh-key-by-id 57578083
“If you need to invoke your academic pedigree or job title for people to believe what you say, then you need a better argument.”
― Neil deGrasse Tyson