Add directory to an empty git repo
- Create the repo
- 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
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
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:
Fetch the latest changes from the upstream repository:
git fetch upstream
Check out the branch you want to synchronize (e.g.,
main
):git checkout main
Reset your local branch to match the upstream branch exactly:
git reset --hard upstream/main
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
- Check out the branch you want your commits to be in:
git checkout <branch>
- Find git ref id one commit before the commit you want to change
git rebase -i <ref #>
- Remove commits you do not want in this new branch
- Edit the messages for commits as needed.
- Save the file.
git commit --amend
- change the commit message to what you wantgit rebase --continue
- 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)
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>
Change the lines for the commits you want to modify by changing them from
pick
toedit
.Save the file once you’re done.
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>"
Once you’ve changed the author for the commit, continue on through the rebase using
git rebase --continue
until all commits have been resolved.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 usinggit rebase --continue
.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.
- Login
- Go to repo
- Settings
- Repository
- Expand button next to Deploy Tokens
- Set scopes based on your criteria
- 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:
- https://docs.github.com/en/github/authenticating-to-github/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent
- https://gist.github.com/Jonalogy/54091c98946cfe4f8cdab2bea79430f9
- provided information about ssh-add -K
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
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
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:
- https://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git
- https://blog.oddbit.com/post/2019-06-17-avoid-rebase-hell-squashing-wi/
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
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>
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
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.
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
Restore directory from previous commit
git checkout $COMMIT_ID -- path/to/the/folder/
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>
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
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:
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
.Check out to the branch where you want to apply the commit:
git checkout $DESIRED_BRANCH
Use
git cherry-pick
to apply the commit to the desired branch:git cherry-pick $DESIRED_BRANCH
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
Make sure you are in the branch that references the new commit(s)
Add your changes to the tagged release:
# Example tag TAG='v1.0.8' git tag -f -a "${TAG}"
Push your new commit and force push your moved tag:
git push origin main git push origin -f "${TAG}"
Check for changes to a local repo
cd /git/directory
if [[ `git status --porcelain` ]]; then
# Changes
else
# No changes
fi
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):
Create a GitHub Personal Access Token
Set the expiration date
Check the box next to repo -> click Generate token
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
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:
- https://cli.github.com/manual/gh_help_reference
- https://cli.github.com/manual/gh_auth_setup-git
- https://github.com/chelnak/gh-changelog
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:
- https://stackoverflow.com/questions/57612428/cloning-private-github-repository-within-organisation-in-actions/70283191#70283191
- https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys
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