Add directory to an empty git repo

  1. Create the repo
  2. Go to the folder and run:
git init -b main
git add .
git commit -m 'Initial commit'
git remote add origin git@github.com:youruser/yourrepo.git
git push -u origin main

Resource: https://docs.github.com/en/github/importing-your-projects-to-github/adding-an-existing-project-to-github-using-the-command-line

Set global pull strategy to rebase

git config --global pull.rebase true

Commit executable file

FILE=.hooks/go-vet.sh
chmod +x $FILE
git add $FILE

Commit empty directory to repo

mkdir directory
touch directory/.gitkeep
git add directory
git commit -m "Adding directory"
git push origin main

Resource: https://www.digitalocean.com/community/questions/how-to-add-and-commit-an-empty-directory-in-my-git-repository

Update branch name

OLD_NAME=oldbranchname
NEW_NAME=waybettername
git branch -m $OLD_NAME $NEW_NAME

Resource: https://www.ionos.com/digitalguide/websites/web-development/renaming-a-git-branch/

Change branch from master to main

git branch -m master main
git fetch origin
git branch -u origin/main main
git remote set-head origin -a

Merge develop branch into main

git checkout develop
git merge main

At this point, if there are any merge conflicts, you can resolve them.

git checkout main
git merge develop
git push origin main

Resource: https://stackoverflow.com/questions/14168677/merge-development-branch-with-master

Merge specific files from develop branch into main

git checkout main
git checkout source_branch <paths>...

For example, this will get the .gitignore from develop:

git checkout develop .gitignore

Resource: https://jasonrudolph.com/blog/2009/02/25/git-tip-how-to-merge-specific-files-from-another-branch/

List branches by date (newest to oldest)

git branch --sort=-committerdate

To include remote branches:

git branch -a --sort=-committerdate

List branches by date with detailed output:

git branch --format="%(committerdate:iso8601) %(refname:short)" --sort=-committerdate

List branches by date with color formatting and aligned columns:

git branch -a --sort=-creatordate --format='%(color:red)%(committerdate:iso8601)%(color:reset) %(align:8)(%(ahead-behind:HEAD))%(end) %(color:blue)%(align:40)%(refname:short)%(end)%(color:reset) %(color:white)%(contents:subject) %(color:yellow)(%(committerdate:relative))%(color:reset)'

This will display a list of branches with the following information:

  • Commit date in red
  • Ahead/behind information in aligned column
  • Branch name in blue
  • Commit subject in white
  • Relative commit date in yellow

Note: You can customize the colors and formatting to your liking by modifying the –format option.

Syncing a fork

When you’re introducing changes to another open source project, sometimes your fork gets out of sync with the original project. To fix this, do the following:

  1. Fetch the latest changes from the upstream repository:

    git fetch upstream
    
  2. Check out the branch you want to synchronize (e.g., main):

    git checkout main
    
  3. Reset your local branch to match the upstream branch exactly:

    git reset --hard upstream/main
    
  4. Force-push the reset branch to your fork on GitHub:

    git push origin +main
    

    Note: This will overwrite your branch in the forked repository to exactly match the branch of the original repository, discarding all the commits that are unique to your forked branch.

In the event that you get this error:

fatal: 'upstream' does not appear to be a git repository

Check if the upstream for the original project is set:

git remote -v

If it isn’t, get the url for the git repo and run it with this command:

git remote add upstream https://github.com/<original project>

For example, if I was working on my copy of the xssValidator project, and wanted to add the upstream for the original:

git remote add upstream https://github.com/nVisium/xssValidator

Remove latest commit from a git repo

Pushed too soon or by accident? Let’s fix that. Non-rebase approach:

git reset --hard HEAD^
git push origin +master

Rebase approach:

git rebase -i HEAD~2
# Delete the commit (should be the second line in the text file)
git push origin +$BRANCH_NAME

Note: + is used to force a push to only one branch.

Resources: https://git-scm.com/docs/git-push

Redo git commits that have been pushed to remote

  1. Check out the branch you want your commits to be in: git checkout <branch>
  2. Find git ref id one commit before the commit you want to change
  3. git rebase -i <ref #>
  4. Remove commits you do not want in this new branch
  5. Edit the messages for commits as needed.
  6. Save the file.
  7. git commit --amend - change the commit message to what you want
  8. git rebase --continue
  9. Once done with commits, git push -u origin <name of branch>

Merge file from one branch to another

git checkout <branch to merge from> <file name>

Update branch with another

git checkout $BRANCH_TO_OVERWRITE
# Rebase all changes on $BRANCH_TO_OVERWRITE
# onto the tip of main
git rebase origin/main

Amend commit

git add $FILE
git commit --amend

Stash queued changes to go to another branch and then bring them back

git stash
git checkout $ANOTHER_BRANCH
# Do stuff on this branch
git checkout $FIRST_BRANCH
git pop

Fix github sync issue

git stash save --keep-index
git stash drop

Delete file from github history (in case sensitive information goes up on accident)

git filter-branch --force --index-filter "git rm --cached --ignore-unmatch ${FILENAME}"
git push origin --force --all

Change author of pushed commit(s)

  1. Find the commit before (chronologically) the first commit that you want to change:

    git rebase -i <SHA hash of the commit before the one you want to change>
    
  2. Change the lines for the commits you want to modify by changing them from pick to edit.

  3. Save the file once you’re done.

  4. Change the commit to the author you want to be reflected like so:

    git commit --amend --author="New Author Name <newauthor@url.com>"
    # Example:
    git commit --amend --author="Jayson Grace <jayson.e.grace@gmail.com>"
    
  5. Once you’ve changed the author for the commit, continue on through the rebase using git rebase --continue until all commits have been resolved.

  6. You may need to deal with some errors that come up, so be sure to fix those, and then git add the file that you’ve had to resolve before using git rebase --continue.

  7. Finish with git push --force

Resource: https://stackoverflow.com/questions/3042437/change-commit-author-at-one-specific-commit

Change author of last commit

git commit --amend --author="Philip J Fry <someone@example.com>"

Resource: https://makandracards.com/makandra/1717-git-change-author-of-a-commit

Undo last commit and keep changes made

git reset HEAD~1

Resource: https://stackoverflow.com/questions/927358/how-to-undo-the-last-commits-in-git


Submodules

Add the existing git repo as a submodule of the current repo

git submodule add <git repo> <destination path - optional>

This should generate .gitmodules file and the folder of the repo you intended to submodule.

Update submodules

Get latest changes for submodules

The first time you check out a repo:

git submodule update --init --recursive

If you’ve already checked out a repo:

git submodule update --recursive --remote

Resource: https://stackoverflow.com/questions/1030169/pull-latest-changes-for-all-git-submodules

Clone a repo with submodules in it

git clone --recurse-submodules <repo with submodules>

Remove a submodule

git rm -r the_submodule
rm -rf .git/modules/the_submodule
git config -f .git/config --remove-section submodule.the_submodule 2> /dev/null

Resource: https://stackoverflow.com/questions/1260748/how-do-i-remove-a-submodule

Change submodule URLs for local repo

This is very useful if you want to use SSH for the vast majority of the time and a Personal Access Token for certain cases.

git submodule init

# Get submodules
submodules=($(git config --file .gitmodules --get-regexp url \
  | awk -F ' ' '{print $2}'))

# Remove empty cells from submodules array
for i in "${!submodules[@]}" ; do submodules[$i]="${submodules[$i]:-other}"; done

# Replace URLs with HTTPs using the input Personal Access Token
for submodule in "${submodules[@]}"; do
    # Get owner
    owner=$(echo "$submodule" | awk -F '/' '{print $1}' | awk -F ':' '{print $2}')
    # Get full repo name - for example, if you have ansible-role,
    # $full_repo_name will reflect that
    full_repo_name=$(echo "$submodule" \
        | awk -F '/' '{print $2}' | awk -F '.' '{print $1}')
    # Get only repo name - for example, if you have ansible-role,
    # $repo_name will be assigned roll and omit ansible-
    repo_name=$(echo "$full_repo_name" | awk -F '-' '{print $2}')
    # Change the values in ~.git./config
    git config submodule.$repo_name.url https://$PAT@github.com/$owner/$full_repo_name.git
    # Change the values in ~/.gitmodules
    git submodule set-url -- roles/$repo_name https://$PAT@github.com/$owner/$full_repo_name.git
done

# Update local submodules with the changes we made
git submodule update --recursive --remote

Resource: https://www.damirscorner.com/blog/posts/20210423-ChangingUrlsOfGitSubmodules.html


Add changes to a previous commit

git commit --amend

If you don’t want to change the commit message

git commit --amend --no-edit

One-liner with changes to message

git commit --amend -m "new commit message goes here"

You can also omit the -m and the message following it if you want to edit the commit in a text editor.

Force push changes to a commit that’s been pushed previously

git push origin +$BRANCH

Alternatively:

git push -f origin $BRANCH

Resource: https://medium.com/@igor_marques/git-basics-adding-more-changes-to-your-last-commit-1629344cb9a8

Anonymous clone of git repo

git clone git://github.com/SomeUser/SomeRepo.git

Resource: https://superuser.com/questions/557903/clone-github-repository-without-account


Gitlab

Get content of a commit

This is helpful when you run something like Gitleaks, and want to be able to look at specific commits. Unfortunately, the UI does not seem to provide a place to do this.

https://gitlab.com/projectname/reponame/commit/commitid

Create deploy token

This can be used if you need to automatically clone into a private gitlab repo as part of a CI/CD pipeline. Be sure to set an expiration date if you can get away with it for security.

  1. Login
  2. Go to repo
  3. Settings
  4. Repository
  5. Expand button next to Deploy Tokens
  6. Set scopes based on your criteria
  7. Click Create deploy token

Use it to clone the repo:

git clone https://<gitlab+deploy-token-number>:<token_password>@gitlab.com/group/repo.git

Resource: https://docs.gitlab.com/ee/user/project/deploy_tokens/#creating-a-deploy-token


Github

Create key

ssh-keygen -t ed25519 -C "<your email>" -f ~/.ssh/personal_github

Add new key to SSH agent

eval "$(ssh-agent -s)"

Add a section to ~/.ssh/config for the key you just created:

Host github.com-l50
   HostName github.com
   User git
   IdentityFile ~/.ssh/personal_github
   IdentitiesOnly yes

If you’re running OSX, run this command to add your new key to your keychain:

ssh-add ~/.ssh/personal_github

If you’re running on Linux, run this command to add your new key to your keychain:

ssh-add -k ~/.ssh/personal_github

Finally, add your public key to github.

Resources:

Collect information needed to add ssh key

Generate an ssh key using the information under CREATE SSH KEY found on this page

echo "Your public key:"
cat ~/.ssh/id_rsa.pub
echo
echo "Your ssh key fingerprint is:"
ssh-keygen -lf ~/.ssh/id_rsa | awk '{ print $2 }'

Change release commit

If you create a release and have to make some changes and then re-release, here’s what you need to do:

git tag -f -a <tagname> ## i.e. v1.0.0
git push -f --tags

Resource: https://stackoverflow.com/questions/30152632/how-to-change-a-release-on-github-point-to-the-latest-commit

Delete tag locally and push changes remote

for tag in tag1 tag2 tag3 ...; do
    git tag -d $tag
    git push origin :refs/tags/$tag
done

Show staged changes

Useful if you have already run a git add and are trying to remember the changes you’ve made.

git diff --staged

Resource: https://stackoverflow.com/questions/3527856/show-git-diff-on-file-in-staging-area


Find shield for project

https://shields.io/


Squash last n commits

This example will squash the most recent two commits and push the new commit to main, overwriting what was there previously:

branch=main
n=2
# Squash most recent n commits together
git reset --soft HEAD~${n}
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
git push origin +${branch}

With rebasing on the current branch:

git rebase -i HEAD~n

Resources:

Rebase forked branch on top of latest upstream

git fetch upstream
git checkout $BRANCH_IN_FORK_TO_UPDATE
git rebase upstream/$BRANCH_IN_FORK_TO_UPDATE
git rebase --continue
git push origin main --force-with-lease # push fork changes

Gitignore

Show line of gitignore ignoring a file

git check-ignore -v filename

Resource: https://stackoverflow.com/questions/12144633/explain-which-gitignore-rule-is-ignoring-my-file

Exclude file from root folder

Add a / in front, i.e. /mose

Resource: https://stackoverflow.com/questions/3637660/how-to-exclude-file-only-from-root-folder-in-git

Ignore everything except specific files

This example ignores everything in the current directory except for the .gitignore and .chicken files

*
!.chicken
!.gitignore

Git Large File Storage (LFS)

Adding a large file with git-lfs

This is useful if you need to track a file that’s over 100MB in size. It’s important to follow these steps before you attempt to incorporate the large file into the repo.

git lfs install
git lfs track "large.file"
git add .gitattributes
git add large.file
git commit -m "Message"
git push origin <branch>

Resource: https://git-lfs.github.com/

Removing a large file from git-lfs

This is useful if you want to track a file that’s over 100MB in size, but don’t want to use git-lfs anymore because it’s priced ridiculously (consider s3 as a cheaper alternative).

## Delete all instances of the file in git history
bfg --delete-files <file>
## Uninstall git-lfs from the repo
git lfs uninstall
git rm .gitattributes
## Push the changes
git push origin +master

Resource: https://dustinfreeman.org/blog/rip-lfs/


Show modified files in a commit

git show <commit id>

Resource: https://stackoverflow.com/questions/424071/how-to-list-all-the-files-in-a-commit

Retrieve directory from previous commit

COMMIT_ID=0a0aa000a00000aaa0009b89f00a0a000c0005b6
DIR_TO_RETRIEVE=existed_previously
git checkout $COMMIT_ID -- $DIR_TO_RETRIEVE

Show contents of file at specific commit

git show <commit id>:/path/to/file

Resource: https://stackoverflow.com/questions/338436/is-there-a-quick-git-command-to-see-an-old-version-of-a-file


Show diff of unpushed commits

git diff origin/master..HEAD

Resource: https://stackoverflow.com/questions/2016901/viewing-unpushed-git-commits


Delete local && remote branch

Delete remote branch:

git push origin --delete <branch-name>

Delete local branch:

git branch -D <branch-name>

Resource: https://stackoverflow.com/questions/2003505/how-do-i-delete-a-git-branch-locally-and-remotely#:~:text=Simply%20do%20git%20push%20origin,local%20branch%20only!…


Clone a subdirectory of a repo

You don’t want the whole repo, just a specific directory. Seems sensible, this covers how to do it. Note that this is only available as of git 2.19.

git clone \
  --depth 1 \
  --filter=blob:none \
  --no-checkout \
  https://github.com/cirosantilli/test-git-partial-clone \
;
cd test-git-partial-clone
git checkout master -- d1

Resource: https://stackoverflow.com/questions/600079/git-how-do-i-clone-a-subdirectory-only-of-a-git-repository/52269934#52269934

Use patches

Very useful if you make changes but don’t have a remote git repo to push your commits to.

Show source line diffs and output to a file:

git show <COMMIT ID> | tee output

Apply the patch in the output file:

git apply output

Git grep

Super fast tool that is very useful for finding stuff quickly in a repo.

This example will look for the word auth in all tracked files that end with .py:

git grep -n "auth" -- '*.py'

n: Prefix the line number for matching lines

An example to search all tracked .c and .h files for time_t:

git grep 'time_t' -- '*.[ch]'

List all files

git ls-files

Resource: https://github.com/awslabs/git-secrets

Get latest release url from github

This example is for the Sliver c2, but you can obviously modify it to meet your needs:

AUTHOR='BishopFox'
REPO_NAME='sliver'
curl -s "https://api.github.com/repos/$AUTHOR/$REPO_NAME/releases/latest" \
  | jq -r '.assets[].browser_download_url'

Resource: https://gist.github.com/steinwaywhw/a4cd19cda655b8249d908261a62687f8

Download release from github

wget -q -O $binary_name $binary_url

Resource: https://stackoverflow.com/questions/25923939/how-do-i-download-binary-files-of-a-github-release

Delete GHCR container image

This requires a fine-grained personal access token with the delete:packages and read:packages permissions.

ORG=CowDogMoo
CONTAINER=old-and-useless
export CR_PAT=# personal access token goes here
curl -X DELETE -H "Authorization: Bearer $CR_PAT" https://api.github.com/orgs/$ORG/packages/container/$CONTAINER

Show history of changes to a file

git log -p filename

-p: Show the diff between each revision and its parent

Resources: https://stackoverflow.com/questions/278192/view-the-change-history-of-a-file-using-git-versioning

Show differences between two revisions of a file

git diff abc123 def456 -- path/to/file.

In this example, abc123 and def456 are the revision IDs.

Resource: https://stackoverflow.com/questions/1964142/how-can-i-list-all-the-different-versions-of-a-file-and-diff-them-also

Compare file across two branches

BR1=dev
BR2=main

git diff "${BR1}" "${BR2}" -- path/to/file.

Resource: https://stackoverflow.com/questions/2364147/how-to-get-just-one-file-from-another-branch

Find all .git folders in current dir

find . -type d -iname ".git"

Resource: https://www.cyberciti.biz/faq/unix-linux-centos-ubuntu-find-hidden-files-recursively/

Run git diff on all files with a specific extension

This example will run git diff for all modified *.yaml files in a repo:

for file in $(git diff --name-only | grep '\.yaml$'); do
    git diff "$file"
done

Run git diff with file exclusions

This particular example will output everything from the git diff command except for magefiles/go.mod and magefiles/go.sum:

git ds -- . ':(exclude)magefiles/go.mod' ':(exclude)magefiles/go.sum'

Resource: https://stackoverflow.com/questions/10415100/exclude-file-from-git-diff

Ditch local commits

This is useful if you get an error message about diverged branches or have a merge conflict.

git fetch origin
git reset --hard origin/master

Resource: https://stackoverflow.com/questions/19864934/git-your-branch-and-origin-master-have-diverged-how-to-throw-away-local-com

Restore directory from previous commit

git checkout $COMMIT_ID -- path/to/the/folder/

Resource: https://stackoverflow.com/questions/9670745/how-to-restore-a-whole-directory-from-history-of-git-repository

Restore files from a specific branch, excluding certain directories

git restore -s $BRANCH_NAME -- . ':(exclude)$EXCLUDE_DIR1' ':(exclude)$EXCLUDE_DIR2' ':(exclude)$EXCLUDE_DIR3'

Example:

git restore -s branch-with-stuff-we-want -- . ':(exclude)components' ':(exclude)things' ':(exclude)otherstuff'

This command restores files from the ‘branch-with-stuff-we-want’ branch to the current directory, excluding the ‘components’, ’things’, and ‘otherstuff’ directories.

Change permissions for file in repo

FILE=./scripts/moveFile.sh
git update-index --chmod +x $FILE

Resource: https://github.community/t/how-to-execute-a-script-file-using-github-action/16830/4

Move work to new branch

If you’ve started working in the wrong branch, you can do the following to move it to a new one:

git checkout -b <new-branch-name>

Resource: https://intellipaat.com/community/3195/git-move-changes-to-new-branch-move-existing-uncommitted-work-to-a-new-branch-in-git

Create new branch based on upstream trunk

In this example, upstream trunk is on main:

UPSTREAM_TRUNK=upstream/main
git checkout -b $NEW_BRANCH_NAME $UPSTREAM_TRUNK

Change case of file name

git mv -f yOuRfIlEnAmE yourfilename

Resource: https://stackoverflow.com/questions/17683458/how-do-i-commit-case-sensitive-only-filename-changes-in-git

Delete local and remote tag

TAG=v1.0.0
git tag -d "${TAG}"
git push --delete origin "${TAG}"

Resources: https://www.abeautifulsite.net/posts/how-to-delete-a-tag-on-github/ https://devconnected.com/how-to-delete-local-and-remote-tags-on-git/

Cherry Picking

git cherry-pick is a powerful command that allows you to take a commit from one branch and apply it to another branch. This can be thought of as a more surgical version of git merge as it applies a specific commit rather than multiple commits.

Imagine you’ve accidentally committed your changes to the wrong branch, but you want those changes in a different branch. Here’s how you can use git cherry-pick to resolve this:

  1. First, identify the hash of the commit you want to move. This is a unique identifier for each commit. You can view this via git log.

  2. Check out to the branch where you want to apply the commit:

    git checkout $DESIRED_BRANCH
    
  3. Use git cherry-pick to apply the commit to the desired branch:

    git cherry-pick $DESIRED_BRANCH
    
  4. Now, the commit has been applied to the desired branch, you should go back to the original branch and remove the commit there:

    git checkout $ORIGINAL_BRANCH
    git reset --hard HEAD~1
    

Tag release

git tag -a v1.0.0 -m "Releasing version v1.0.0"
git push origin v1.0.0

Resource: https://dev.to/neshaz/a-tutorial-for-tagging-releases-in-git-147e

Update tagged release

  1. Make sure you are in the branch that references the new commit(s)

  2. Add your changes to the tagged release:

    # Example tag
    TAG='v1.0.8'
    git tag -f -a "${TAG}"
    
  3. Push your new commit and force push your moved tag:

    git push origin main
    git push origin -f "${TAG}"
    

Resource: https://stackoverflow.com/questions/30152632/how-to-change-a-release-on-github-point-to-the-latest-commit

Check for changes to a local repo

cd /git/directory
if [[ `git status --porcelain` ]]; then
  # Changes
else
  # No changes
fi

Resource: https://stackoverflow.com/questions/5143795/how-can-i-check-in-a-bash-script-if-my-local-git-repository-has-changes

List releases in private repo

Create a GitHub Personal Access Token

export PAT=# personal access token goes here
export REPO=# repo name goes here
export AUTHOR=# author name goes here

curl -H "Accept: application/vnd.github+json" \
   -H "Authorization: token ${PAT}" \
   "https://api.github.com/repos/${AUTHOR}/${REPO}/releases"

Resource:

Download release from a private repo

Create a GitHub Personal Access Token

Set the $PAT:

export PAT=# personal access token goes here

Create dl.sh (modified version of gh-dl-release, see resources below for link):

#!/usr/bin/env bash
set -ex

# author/repo goes here, e.g. l50/goutils:
REPO=''

# the name of the release asset file, e.g. build.tar.gz
FILE=''

# tag name or the word "latest"
VERSION=$1
GITHUB="https://api.github.com"

alias errcho='>&2 echo'

if [[ -z "${PAT}" ]]; then
  echo 'error: personal access token (PAT) not set'
  exit 1
fi

function gh_curl() {
  curl -H "Authorization: token $PAT" \
       -H "Accept: application/vnd.github.v3.raw" \
       $@
}

if [ "$VERSION" = "latest" ]; then
  # Github should return the latest release first.
  parser=".[0].assets | map(select(.name == \"$FILE\"))[0].id"
else
  parser=". | map(select(.tag_name == \"$VERSION\"))[0].assets \
            | map(select(.name == \"$FILE\"))[0].id"
fi;

asset_id=`gh_curl -s $GITHUB/repos/$REPO/releases | jq "$parser"`
if [ "$asset_id" = "null" ]; then
  errcho "ERROR: version not found $VERSION"
  exit 1
fi;

wget -q --auth-no-challenge --header='Accept:application/octet-stream' \
  https://$PAT:@api.github.com/repos/$REPO/releases/assets/$asset_id \
  -O $2

To use it:

dl latest latest.tar.gz

Resources:

Recursively pull for all repos in cwd

#!/bin/bash

set -x

# Update all git directories below current directory or specified directory
GREEN="\033[01;32m"    # Success
YELLOW='\033[0;33m'    # Informational
RESET="\033[00m"       # Normal
DEBUG='false'

DIRS=("$(pwd)" ansible_roles terraform_modules ansible_playbooks)

pull_repos() {
  # Read all repos into $git_repos array
  mapfile -t git_repos < \
    <(find . -type d \( -exec test -d "{}/.git" -a "{}" != "." \; -print -prune \
    -o -name .git -prune \))

  for r in "${git_repos[@]}"; do
    pushd "${r}" || exit
    if [[ "${DEBUG}" == "true" ]]; then
      echo "Determining if we have the latest version of ${r}"
    fi
    res=$(git pull origin main 2>/dev/null)
    if [[ "${res}" != 'Already up to date.' ]]; then
      echo -e "${GREEN}Now Updating ${r}${RESET}"
    fi
    popd || exit
  done
}

# Pull repos in $DIRS
for d in "${DIRS[@]}"; do
  echo -e "${YELLOW}Updating repos in ${d}${RESET}"
  pushd "${d}" || exit
  pull_repos
  popd || exit
done

Find encrypted files in a repo

git ls-files | git check-attr -a --stdin | grep git-crypt

Github CLI Cheat sheet

I’ve recently started using the gh CLI tool and I’m quite impressed. Here’s my cheat sheet that I hope you’ll find as useful as I do:

Authenticate

Easy mode:

gh auth login

Hard mode (BYO PAT):

  1. Create a GitHub Personal Access Token

    1. Set the expiration date

    2. Check the box next to repo -> click Generate token

    3. Copy the value of the token and set it to the GH_TOKEN env var:

      # Add a space in front of the export so that
      # your GH TOKEN doesn't show up in your history :)
      export GH_TOKEN=tokenvaluecopiedfromgithubgoeshere
      
  2. Run the following command to complete the process:

    echo "${GH_TOKEN}" > .githubtoken
    unset GH_TOKEN
    gh auth login --with-token < .githubtoken
    rm .githubtoken
    

Confirm authentication success:

gh auth status

Install Github CLI

# Get the current OS and architecture
os=$(uname | tr '[:upper:]' '[:lower:]')
arch=$(uname -m)

# Map the architecture name to the one used by GitHub
if [[ "$arch" == "x86_64" ]]; then
    arch="amd64"
elif [[ "$arch" == "aarch64" ]]; then
    arch="arm64"
fi

# Adjust OS name for MacOS
if [[ "$os" == "darwin" ]]; then
    os="macOS"
fi

AUTHOR='cli'
REPO_NAME='cli'

# Get the latest version from the GitHub API
latest_release="https://api.github.com/repos/$AUTHOR/$REPO_NAME/releases/latest"
all_urls=$(curl -s "$latest_release" | jq -r '.assets[].browser_download_url')

# Filter URLs to match the OS and architecture
download_url=$(echo "$all_urls" | grep "${os}_${arch}" | grep -v 'deb\|rpm\|msi')
checksum_url=$(echo "$all_urls" | grep 'checksums')

# Check if URL exists
if [ -z "$download_url" ]; then
    echo "No download URL found for OS: $os, architecture: $arch"
    exit 1
fi

# Download the file
curl -sLO "$download_url"

# Extract the filename from the download URL
filename=$(basename "$download_url")

# Download the checksum file
curl -sLO "$checksum_url"

# Verify the checksum
checksum_file=$(basename "$checksum_url")

if [[ "$os" == "macOS" ]]; then
    # MacOS uses shasum instead of sha256sum and has different behavior for grep
    shasum -a 256 -c <(grep "$filename" "$checksum_file")
else
    sha256sum -c --ignore-missing <(grep "$filename" "$checksum_file")
fi

# Extract the downloaded file
extraction_folder="gh_temp"
if [[ "$os" == "macOS" ]]; then
    unzip "$filename" -d "$extraction_folder"
else
    tar -xvf "$filename" -C "$extraction_folder"
fi

# Check if /usr/local/bin is in the PATH
if echo "$PATH" | grep -q "/usr/local/bin"; then
    install_path="/usr/local/bin"
else
    # Get the first directory in PATH as the install path
    install_path=$(echo "$PATH" | cut -d ':' -f1)
fi

# Extract the version from the filename
version=$(echo "$filename" | sed -e 's/gh_\([0-9.]*\)_.*/\1/')

# Move the binary file to the install path
mv "${extraction_folder}/gh_${version}_${os}_${arch}/bin/gh" "$install_path"

# Clean up the downloaded and extracted files
rm -rf "$filename"
rm -rf "$checksum_file"
rm -rf "$extraction_folder"

Use GitHub CLI as a credential helper

gh auth setup-git

Fix the default editor

gh config set editor vim

Clone Repo

Clone my goutils repo:

gh repo clone l50/goutils

Sync Repo

gh repo sync

Resource: https://github.com/cli/cli/issues/368

Generate Changelog

This will create a CHANGELOG.md:

NEXT_VERSION=v1.1.3
gh extension install chelnak/gh-changelog
gh changelog new --next-version $NEXT_VERSION

Create release

Generate release notes without providing a CHANGELOG.md:

gh release create $NEXT_VERSION --generate-notes

Alternatively, if you created a CHANGELOG.md:

gh release create $NEXT_VERSION -F CHANGELOG.md

Delete release

gh release delete $VERSION --cleanup-tag

List issues

gh issue list

Close issue

ISSUE_ID=4
gh issue close "${ISSUE_ID}"

List Github Actions associated with a repo

gh workflow list

Show runs for a particular action

ACTION_NAME='Run Tests'
gh workflow view "${ACTION_NAME}"

Start an action

ACTION_NAME='Run Tests'
gh workflow run "${ACTION_NAME}"

Resources:

Download latest release

OS="$(uname | python3 -c 'print(open(0).read().lower().strip())')"
ARCH="$(uname -a | awk '{ print $NF }')"
gh release download -p "*${OS}_${ARCH}.tar.gz"
tar -xvf *tar.gz

Close several issues

gh issue list -L 20 -s "open" | \
 grep "Renovate Dashboard" | \
 awk -F ' ' '{print $1}' | \
 xargs -I{} gh issue close {}

Clone all repos in an organization

ORG_NAME=CowDogMoo
gh api --paginate "/orgs/$ORG_NAME/repos" \
  | jq -r '.[].clone_url' | xargs -L1 git clone

Get GITHUB_TOKEN Scope

export GH_TOKEN=your_token_goes_here
gh auth status --show-token

Resource: https://github.com/cli/cli/issues/5174

Get Number of Issues Addressed by a user

GIT_USER=your_username # Note: this is your login, not your full name
ANOTHER_GIT_USER=another_username
gh issue list --state all --json author \
  -q "map(select(.author.login==\"$GIT_USER\" or \
  .author.login==\"$ANOTHER_GIT_USER\")) | length"

Number of PRs Reviewed by Your User

GIT_USER=your_username # Note: this is your login, not your full name
gh pr list --state all --json number,author,reviews \
  -q "[.[] | select((.reviews | any(.[]; .author.login == \"$GIT_USER\")) and .author.login != \"$GIT_USER\")] | length"

Download release and run checksum validation

#!/bin/sh

set -e

TOOL_NAME="$1"
TOOL_BINARY="$2"
DOWNLOAD_BASE_URL="$3"
DOWNLOAD_BINARY_FILE="$4"
DOWNLOAD_CHECKSUM_FILE="$5"
SHASUM_IGNORE_MISSING="$6"
SHASUM_ALGO="$7"
TEST_COMMAND="$8"

echo "Installing $TOOL_NAME..."

# Download checksums and binary
curl -L "${DOWNLOAD_BASE_URL}/${DOWNLOAD_CHECKSUM_FILE}" -o checksums.txt
curl -OL "${DOWNLOAD_BASE_URL}/${DOWNLOAD_BINARY_FILE}"

# Verify checksums
shasum ${SHASUM_IGNORE_MISSING} -a ${SHASUM_ALGO} -c checksums.txt

# Install tool
if [ "${DOWNLOAD_BINARY_FILE##*.}" = "deb" ]; then
    dpkg -i "${DOWNLOAD_BINARY_FILE}"
elif [ "${DOWNLOAD_BINARY_FILE##*.}" = "gz" ]; then
    tar xzf "${DOWNLOAD_BINARY_FILE}"
    mv "${TOOL_BINARY}" /usr/local/bin/
else
    chmod +x "${DOWNLOAD_BINARY_FILE}"
    mv "${DOWNLOAD_BINARY_FILE}" /usr/local/bin/
fi

# Clean up
rm -f checksums.txt "${DOWNLOAD_BINARY_FILE}"

# Verify binary works
eval "${TEST_COMMAND}"

echo "$TOOL_NAME installation complete."

Example:

# Install GitHub CLI
ENV GITHUB_CLI_VERSION 2.27.0
RUN install-tool \
    "GitHub CLI" \
    "gh" \
    "https://github.com/cli/cli/releases/download/v${GITHUB_CLI_VERSION}" \
    "gh_${GITHUB_CLI_VERSION}_${TARGET_OS}_${TARGET_ARCH}.deb" \
    "gh_${GITHUB_CLI_VERSION}_checksums.txt" \
    "--ignore-missing" \
    "512" \
    "gh --version"

Safely delete all commit history

git checkout --orphan latest_branch
git add -A
git commit -am "commit message"
git branch -D main
git branch -m main
git push -f origin main

Resource: https://stackoverflow.com/questions/13716658/how-to-delete-all-commit-history-in-github

Create deploy key

Deploy keys are useful when you need to clone another repo in an action. You can also use PAT, although this gives write access to all private repos, which is suboptimal from a security standpoint.

KEY_NAME=deploy-key
KEY_DESCRIPTION='Deploy key'
ssh-keygen -t ed25519 -C "${KEY_DESCRIPTION}" -f "~/.ssh/${KEY_NAME}" -N ''
mv "${KEY_NAME}"* ~/.ssh

Add the public key to the deploy keys of the repo you want to be able to clone

Add the private key as a secret to the action that you want to use the repo with.

Add this section to the action that’s using the deploy key:

- name: Setup with deploy key
  run: |
    eval `ssh-agent -s`
    ssh-add - <<< '${{ secrets.PRIVATE_SSH_KEY }}'

Resources:

Revert changes to a pushed commit

Note that this will work for commits that are several commits back in your history.

git revert $COMMIT_HASH
git push origin HEAD

Update all repos

This will run a git pull for any repos in the current directory or below:

ORG_NAME=CowDogMoo
find . -type d -name '.git' \
  -exec sh -c 'cd {} && cd .. && \
  if [[ $(git config --get remote.origin.url) == *$ORG_NAME* ]]; then \
    git pull; fi' \;

Rebase changes from trunk to feature branch

# Ensure you are on your feature branch
git checkout my-feature-branch

# Fetch the latest changes from the remote repository
git fetch origin

# Rebase your feature branch onto the latest main branch
git rebase origin/main

# Resolve any conflicts if they arise and stage the changes. Then run:
git rebase --continue

# Once the rebase is successful, push your changes to your remote feature branch
git push -f

Delete all untracked files

git clean -f

Show diff between staged changes and exclude modified files

In this example, we’re ignoring magefiles/go.mod and magefiles/go.sum from the output:

git diff --cached -- . ':(exclude)magefiles/go.mod' ':(exclude)magefiles/go.sum'

Add all files with a specific extension

This example will run git add for all modified *.yaml files in a repo:

git add $(git diff --name-only | grep '\.yaml$')

Get all commit messages from a range of commits

COMMITS=16
git log -n $COMMITS --oneline

Alternatively, if you want the body as well as the commit message, you can run:

COMMITS=16
git log -n $COMMITS | cat

Get Number of Lines of Code Written and Removed by an Author

GIT_NAME='Your Name' # this is not your username
total_added=0
total_deleted=0
# Example if you want to specify start and end dates:
START_DATE='20230701'
END_DATE='20231231'

while IFS= read -r line
do
    if [[ $line =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2} ]]; then
        # Processing date lines
        if [[ "$OSTYPE" == "darwin"* ]]; then
            # macOS uses a different date command syntax
            line=$(date -j -f "%Y-%m-%d" "$line" "+%Y%m%d")
        else
            # Linux date command syntax
            line=$(date -d "$line" "+%Y%m%d")
        fi
        if [[ "$line" > "$END_DATE" ]]; then
            END_DATE="$line"
        fi
    elif [[ $line =~ ^[0-9]+ ]]; then
        # Processing numstat lines
        added=$(echo "$line" | awk '{print $1}')
        deleted=$(echo "$line" | awk '{print $2}')
        total_added=$((total_added + added))
        total_deleted=$((total_deleted + deleted))
    fi
done < <(git log --author="$GIT_NAME" --pretty=format:"%ad" --date=short --numstat)

if [[ "$OSTYPE" == "darwin"* ]]; then
    # macOS uses a different date command syntax
    START_DATE=$(date -j -f "%Y%m%d" "$START_DATE" "+%Y-%m-%d")
    END_DATE=$(date -j -f "%Y%m%d" "$END_DATE" "+%Y-%m-%d")
else
    # Linux date command syntax
    START_DATE=$(date -d "$START_DATE" "+%Y-%m-%d")
    END_DATE=$(date -d "$END_DATE" "+%Y-%m-%d")
fi

echo "Total lines added: $total_added"
echo "Total lines deleted: $total_deleted"
echo "Date range: $START_DATE to $END_DATE"

Summarizing Git Commits by Author Across All Branches

# Simplest method
git shortlog -sn --all

Define a data range

This will return information for all commits made by the author since 2023-07-01:

git shortlog -sn --all --since="2023-07-01"

Resource: https://www.lostindetails.com/articles/get-contributor-stats-from-git