- Workflow Branching Strategies
- Advanced Git Config
- GitHub SSH Key SSO Authorization
- Git HTTPS Authentication
- CLIs
- GitHub Badges
- Basic Tips
- Advanced Tips & Tricks
- Git Clone using a specific SSH Key
- Show files not being tracked due to global & local
.gitignore
files - Find line in
.gitignore
which is causing a given file to be ignored - Trigger CI/CD using empty commit
- Copy a file from another branch
- Ammend Last Commit
- Git Reflog
- Pull from Upstream Origin in a local Fork
- Multi-Origin Remotes
- Set Git Commit Author using Environment Variables (for CI/CD workflows)
- Fix Author / Email in Git Pull Request or History
- Erase Leaked Credential in Pull Request
- Convert GitHub.com links to Raw GitHub URL links
- Git Filter-Repo
- Git Filter-Repo Analyze
- Git Filter-Repo Replace Text in Commit History
- Git Filter-Repo Remove File(s) from Commit History
- Erase Leaked Credential in Git History
- Merge a branch from another repo into the current repo
- Reset and Re-download Git Submodule
- Get the Default Branch
- Get Current Branch
- Find which upstream
<remote>/<branch>
the current branch is set to track - List files changed on current branch vs default branch
- List files added on current branch vs default branch
- Push New Branch and Set Upstream in One Command
- Push New Branch and Raise Pull Request in One Command
- Git LFS
This is a big topic which warrants its own page:
Git Workflow Branching Strategies
HariSekhon/DevOps-Bash-tools - .gitconfig
HariSekhon/DevOps-Bash-tools - .gitignore
You can inherit all configs by just cloning the repo and make link
to symlink them to your home directory:
git clone https://github.com/HariSekhon/DevOps-Bash-tools bash-tools
cd bash-tools
make link
See GitHub page - SSH Key SSO Authorization
Using HTTPS for git clone
/ pull
/ push
are more likely to bypass egress content filters on wifi portals for those of you
who travel a lot such as remote workers and digital nomads.
Credential helper config for this can be added to an individual git repo or at the global level which is shown in the advanced .gitconfig introduced above.
There is a script to add it to your local repo in the DevOps-Bash-tools:
git_remotes_set_https_creds_helpers.sh
After that you need either $GH_TOKEN
or $GITHUB_TOKEN
(in that order of precedence) in your environment variables.
Create your GitHub Personal Access Token (PAT) here:
https://github.com/settings/tokens
Or you can install
Git Credentials Manager
which will prompt for your credentials and cache them the first time you git pull
over HTTPS.
Standard Git CLI is already provided by XCode on Mac but you can also install it from Homebrew to get a different version:
brew install git
GitHub CLI:
brew install gh
You will need to export $GH_TOKEN
for the GitHub CLI. Create your token here.
GitHub specific git commands to make things like cloning GitHub repos shorter:
brew install hub
GitLab CLI:
gem install --user-install gitlab
Bitbucket CLI:
pip install --user bitbucket-cli
https://github.com/commonality/architecture-decision-records/wiki/GitHub-repository-status-badges
- Commit frequently:
- make small, atomic commits for easier tracking and diffs
- Push frequently to your remote origin as a backup:
- in case you have a local disaster
- Write clear commit messages:
- summarize what changed and more importantly why
- some companies prefix Jira ticket numbers to their commits or Pull Requests
- Jira can be configured to automatically link a field in each ticket to such PRs
- Use Comments to explain any clever tricks in code:
- it's better than
git blame
which may get covered up on subsequent edits
- it's better than
- Use branches:
- isolate new features, bug fixes, or experiments
- see Git Workflow Branching Strategies
- TL;DR just use simple GitHub flow to get started, you can change it later
- Keep branches short-lived:
- merge back to the main branch quickly to avoid large hard to review Pull Requests or merge conflicts
- see Why You Shouldn't Use Long Lived Branches
- Pull and merge to your branch frequently:
- this minimizes merge conflicts
- resolve merge conflicts early
- they will get bigger over time if you don't merge and resolve conflicts reguarly
- Don't commit large files:
- it'll slow down your repo cloning and local checkout size
- use .gitignore to exclude unnecessary files like large or binary files
- large files are better handled in external blob storage
- if you really need large files in Git, see Git LFS further down
- Don't Rebase - read The Evils of Git Rebasing
- it's not the default for good reason, it causes more problems
- Use tags for releases:
- mark important commits like version, stable releases
- before and after big refactors
- Use Pull Requests for better tracking
- Use Branch protections to:
- ensure your important trunk branches like
master
/main
or environment branches don't get accidentally deleted - enforce code peer review approvals on Pull Requests before merging for quality control
- this is to sanity check design and approach rather than linting for minor typos and errors which CI/CD should catch instead
- ensure your important trunk branches like
- Use CI/CD for extensive linting and testing:
- set this up asap to trigger on both trunk and especially Pull Requests
- this can block introducing rubbish code & style
- you are inheriting technical debt daily until you set this up and will end up with ever more code to fix up the longer you delay adding this
- set this up asap to trigger on both trunk and especially Pull Requests
Git SSH Clone with a specific private key:
GIT_SSH_COMMAND="ssh -i teamcity_github_ssh_key" git clone git@github.com:ORG/REPO
git status --ignored
If you have a large .gitignore and want to find which line is causing a file to be ignored, do this:
git check-ignore -v -- .github/scripts/
output:
/Users/h.sekhon/.gitignore:3711:[Ss]cripts .github/scripts/
Use for cases where PR checks are failing on PR metadata and you just need a fresh run triggered but don't need to change your branch contents.
This has the advantage of avoiding dismissing your already hard earnt GitHub Pull Request approvals and re-bugging your colleagues to re-approve some trivial unnecessary change.
git commit --allow-empty -m 'Empty commit to trigger CI'
git checkout "$branch" "$filename"
This puts it straight into the cache for commit, so you'll need to git reset
if you don't want to commit it.
It you commit and then notice a small change you needed to make to the commit
git commit --amend --no-edit
Shows the tips of branches that changed in the local repo.
Useful to be able to go back in time to points of pulls.
This will be less commits to wade through than git log
.
git reflog
Can use these reference points in other git commands like git checkout
or
git reset
(use the latter with care it unwinds commits).
HEAD@{2}
means "where HEAD used to be two moves ago".
master@{one.week.ago}
means "where master used to point to one week ago in this local repository".
To make it easier to stay up to date with an upstream source repo when you're in a clone of its fork, add it:
git remote add upstream "$url_to_original_repo"
You can then pull from the upstream to your local fork branch:
git pull upstream main
Remember not to make any commits to your fork's main trunk branch.
It needs to stay cleanly aligned with the upstream and only be used to merge upstream updates into your personal feature branch in your fork.
That personal feature branch will contain your changes to raise pull requests back to the main trunk branch of the upstream repo.
Store your repo on multiple Git repo providers.
Useful for backups in case there is an outage to GitHub / GitLab / Bitbucket / Azure DevOps - you can still pull / push to the other.
Add one or more remote repo URLs to the current git checkout:
See bash-tools/git/git_remotes_set_multi_origin.sh which automates this to all major Git hosting providers
git remote set-url --add origin $url2
git remote set-url --add origin $url3
See the remotes configured for origin:
$ git remote -v
origin git@github.com:HariSekhon/DevOps-Bash-tools (fetch)
origin git@github.com:HariSekhon/DevOps-Bash-tools (push)
origin git@gitlab.com:HariSekhon/DevOps-Bash-tools (push)
origin git@bitbucket.org:HariSekhon/DevOps-Bash-tools (push)
origin git@ssh.dev.azure.com:v3/harisekhon/GitHub/DevOps-Bash-tools (push)
A standard git push will then push to all URLs for upstream hosted repos:
$ git push
To https://github.com/HariSekhon/DevOps-Bash-tools
b51469d1..67f690a4 master -> master
To https://bitbucket.org/HariSekhon/DevOps-Bash-tools
b51469d1..67f690a4 master -> master
To https://dev.azure.com/harisekhon/GitHub/_git/DevOps-Bash-tools
b51469d1..67f690a4 master -> master
To https://gitlab.com/HariSekhon/DevOps-Bash-tools
b51469d1..67f690a4 master -> master
This even works with push deletes so watch out (although you'd have time while it iterates through to Ctrl
-C
it:
$ git push origin gantt --delete
To https://github.com/HariSekhon/DevOps-Bash-tools
- [deleted] gantt
To https://bitbucket.org/HariSekhon/DevOps-Bash-tools
- [deleted] gantt
To https://dev.azure.com/harisekhon/GitHub/_git/DevOps-Bash-tools
- [deleted] gantt
To https://gitlab.com/HariSekhon/DevOps-Bash-tools
- [deleted] gantt
Pull from all remotes:
git pull --all
Fetch all branches from all remotes but does not merge changes in, similar to git fetch
but for all branches:
git remote update
Configures the remote to push local master branch to dev branch upstream
git config remote.<name>.push master:dev
Using Git environment variables is a less hardcoded way of setting the Git Commit Author for git commit
steps in
CI/CD workflows:
Git requires all four of these environment variables otherwise git commit
errors out with Author identity unknown
or
Committer identity unknown
since technically the committer could be different to the code author.
GIT_AUTHOR_NAME
and GIT_AUTHOR_EMAIL
- the identity of the person who made the changes
GIT_COMMITTER_NAME
and GIT_COMMITTER_EMAIL
- the identity of the person who committed the changes
Since setting all four environment variables is for Author vs Committer is a hassle, especially given the Committer is
usually the same as the author, it's probably easier to set the only two GIT_AUTHOR_*
environment variables and then put this in your CI/CD to unify them using the one set of config:
git config user.name "$GIT_AUTHOR_NAME"
git config user.email "$GIT_AUTHOR_EMAIL"
This allows you to abstract out the Git Author & Committer identity to the top of your CI/CD workflow using enironment variable for easier maintenance but variablize the code.
If you've accidentally committed using your personal email at work or worse, your work email in your public github
projects, I've written a script to make this easy by wrapping git filter-branch
.
A force push would be required which will replace all later hash refs after the first change and cause conflicts with existing checkouts which if merged could re-introduce the bit you don't want. You must coordinate with your peers to replace their clones in that case if they're already pulled.
git clone https://github.com/HariSekhon/DevOps-Bash-tools bash-tools
bash-tools/git/git_filter_branch_fix_author.sh --help # for details
GitHub has added automation for support ticket requests to delete a pull request containing a credential.
If you reset the branch contents and force push without the credential then you may not need to even delete the PR as it'll replace the diff.
First remove the credential from the file(s).
Find the common divergence point for your branch:
base_branch="master" # or main
your_branch="$(git branch --show-current)"
base_commit="$(git merge-base "$base_branch" "$yourbranch")"
git diff --name-only "$base_branch".."$your_branch" >> filelist.txt
Roll back the branch to the fork point:
git reset "$base_commit"
Re-add all the files you add/updated in this branch:
git add $(cat filelist.txt)
Re-commit all the changed files:
git commit -m "squashed all branch changes into one commit to wipe out the credential that shouldn't have been in there")
Then force push to overwrite the Pull Request contents to wipe out the leaked credential:
git push --force
(if you get an error you need to temporarily disable the force push branch protection)
Raw content path for PNG images.
Sometimes GitHub doesn't give you the raw link for a PNG file that it does for an SVG file, eg. compare these two pages:
SVG has a raw link button:
https://github.com/HariSekhon/Diagrams-as-Code/blob/master/images/kubernetes_traefik_ingress_gke.svg
but PNG doesn't:
Convert the standard GitHub page link to a raw link:
github_link="https://github.com/HariSekhon/Diagrams-as-Code/blob/master/images/kubernetes_kong_api_gateway_eks.png"
echo "$github_link" |
sed 's|https://github.com/|https://raw.githubusercontent.com/|; s|/blob/|/|'
output:
https://raw.githubusercontent.com/HariSekhon/Diagrams-as-Code/master/images/kubernetes_kong_api_gateway_eks.png
https://github.com/newren/git-filter-repo
3rd party Git command add-on that's useful for replacing a token, author name/email, or excluding files and is recommended over the lower-level git filter-branch
.
Creates a bunch of text files with interesting stats on the contents of the repo to figure out what to replace:
git filter-repo --analyze
less .git/filter-repo/analysis/*
Useful to remove tokens accidentally committed.
It's too late if the commits with the token have been pushed as the upstream repo could have been cloned after push so you can't get it back. If nobody has cloned you can force push to wipe it from the upstream history but you can't guarantee that the repo wasn't pulled/cloned and that the token isn't compromised so it should be rotated and invalidated for safety.
See git_filter_repo_replace_text.sh
Remove all the paths specified in the text file:
git filter-repo --invert-paths --paths-from-file paths_to_remove.txt # --force
(git filter-repo --help
for details on the path file format)
See git_files_in_history.sh to see what files are in the git history that you might want to remove.
Can also be used to remove reference to client names in public projects.
First clone DevOps-Bash-tools, then run the script in there:
bash-tools/git/git_filter_repo_replace_text.sh --help # for details
git fetch "$git_repo_url_or_checkout_directory" +"$remote_branch":"$locally_mirrored_branch"
Merge it into the current branch, eg. main
git merge --allow-unrelated-histories "$locally_mirrored_branch"
Then see the Git Filter-Repo Remove File(s) from Commit History section to remove files from the history of the other repo that you don't want polluting and bloating your new repo.
To resolve submodule update merge conflicts:
- Delete the submodule directory
- Delete the
.git/modules
cache of the submodule
rm -fr ".git/modules/$name"
- Re-initialize the submodule
git submodule update --init --recursive
git symbolic-ref refs/remotes/origin/HEAD | sed 's|.*/||'
git rev-parse --abbrev-ref HEAD
In newer versions of Git version 2.22 (Q2 2019+):
git branch --show-current
git for-each-ref --format='%(upstream:short)' "$(git symbolic-ref -q HEAD)"
First find the trunk default branch to compare to the current branch:
default_branch="$(git symbolic-ref refs/remotes/origin/HEAD | sed 's|.*/||')"
Then find the changed files since branching from the trunk default branch:
git diff --name-only "$default_branch"..
or
git log --name-only --pretty="" "origin/$default_branch".. | sort -u
If you forget to the set the default_branch
by running the first command you'll get this error:
fatal: ..: '..' is outside repository at '/Users/hari/github/bash-tools'
default_branch="$(git symbolic-ref refs/remotes/origin/HEAD | sed 's|.*/||')"
git log --diff-filter=A --name-only --pretty="" "origin/$default_branch".. | sort -u
git push --set-upstream origin "$(git rev-parse --abbrev-ref HEAD)"
Using DevOps-Bash-tools repo scripts:
Push and open the PR in your web browser to click complete:
github_push_pr_preview.sh
Push and create the PR immediately (see --help
description for options including immediately merge):
github_push_pr.sh
Push and open the PR in your web browser to click complete:
gitlab_push_pr_preview.sh
Push and create the PR immediately (see --help
description for options including immediately merge):
gitlab_push_pr.sh
If sourcing DevOps-Bash-tools .bashrc
then you can simply type:
pushu # to push and preview the PR
or
pushup # to push and raise the PR
Store big files in GitHub repos (1GB limit for free accounts).
I used this to store old training materials like videos, PDFs, zip files etc. so they are safe and I can then save space locally.
GitHub will block files over 100MB from being git push
otherwise.
Install Git LFS extension on Mac:
brew install git-lfs
Install the Git LFS config into your $HOME/.gitconfig
:
git lfs install
In your repo add the big file types to be tracked in your local repo (configures .gitattributes
):
git lfs track '*.mp4'
Commit the .gitattributes
changes:
git add .gitattributes
git commit -m "add mp4 file type to be tracked by Git LFS in .gitattributes"
Override global extensive .gitignore
if you've copied it from or installed DevOps-Bash-tools using a local repo .gitignore
.
See files being ignored.
echo '!*.mp4' >> .gitignore
git commit -m "allowed .mp4 files to be git committed in .gitignore"
git add *.mp4
git commit -m "added mp4 videos"
Automatically uploads the files to GitHub or whatever is configured as your upstream Git server using LFS storage:
git push
See LFS details:
git lfs env
On another computer you must install Git LFS before you clone.
Otherwise you'll get a checkout with 4K pointer files instead of the actual file contents because Git LFS smudges the checkout to download the file blobs to replace in your checkout.
You'll get errors like this trying to push large files to GitHub:
$ git push -u origin master
Enumerating objects: 52, done.
Counting objects: 100% (52/52), done.
Delta compression using up to 4 threads
Compressing objects: 100% (52/52), done.
Writing objects: 100% (52/52), 584.76 MiB | 2.05 MiB/s, done.
Total 52 (delta 0), reused 0 (delta 0)
remote: warning: File COURSE.pdf is 69.89 MB; this is larger than GitHub's recommended maximum file size of 50.00 MB
remote: warning: File COURSE2.pdf is 64.61 MB; this is larger than GitHub's recommended maximum file size of 50.00 MB
remote: error: Trace: 98e304dad85b91bcb5f726886ddce1b51a5b450523fc93f44ea51e64b444b69c
remote: error: See https://gh.io/lfs for more information.
remote: error: File COURSE.mp4 is 100.27 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
To github.com:HariSekhon/training-old.git
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@github.com:HariSekhon/training-old.git'
GitLab works fine for both push and clone.
Azure DevOps seems to work for push but doesn't show the file contents in the web UI preview and cloning resulted in a checkout error after clone and neither git restore --source=HEAD :/
nor git reset HEAD --hard
worked to get Git LFS to smudge and download the large files.
Unfortunately GitHub limits large files to only 1GB for free accounts, so only use it for files over 100MB. GitHub will disable your LFS after you go over the thresholds for storage or bandwidth usage for the month.
Bitbucket is useless because the free tier only gives 1GB storage (use GitHub instead):
$ git push bitbucket
Uploading LFS objects: 0% (0/51), 0 B | 0 B/s, done.
batch response:
********************************************************************************
[ERROR] Your LFS push failed because you're out of file storage
[ERROR] Change your plan to get more file storage:
[ERROR] https://bitbucket.org/account/user/harisekhon/plans
********************************************************************************
error: failed to push some refs to 'git@bitbucket.org:HariSekhon/training-old.git'
Bitbucket also requires disabling locking:
git config lfs.https://ssh.dev.azure.com/v3/<user>/<project>/<repo>.git/info/lfs.locksverify false
Multi-origin pushes fail and require individual remote pushes:
$ git push
...
remote: GitLab: LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".
To gitlab.com:HariSekhon/training-old.git
! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@gitlab.com:HariSekhon/training-old.git'
...
$ git push gitlab
Partial port from private Knowledge Base page 2012+