Uncommon Git Commands

I like to use the git CLI. I have tried UIs like GitKraken, Fork or GitHub Desktop. I don't like them.

The majority of commands can be remembered after a short amount of time because you use them daily. However, there are some commands you only need here and there, which makes them really difficult to memorize. Every time I need them, I haven't used them for so long that I forget which option it was. Then I have to look them up again, feeling frustrated and uncool for not knowing Git by heart. It's embarrassing.

So I decided to write this little post, which doesn't help memorizing them, but I'll always know where I can look them up quickly 😅

Checkout a remote branch on fork

When contributing to open source, it is common that you want to checkout a branch of the upstream branch, that doesn't exist in your remote fork yet. Neither does it exist in your local repository. Now when it comes to checking out a remote branch on a local repository, git switch is your friend. Just remember to fetch the upstream, so git knows the reference you want to track. However, what if the branch is not in your remote repository - your fork.

I usually have set up the original remote repository as the upstream repo locally, which can be done with:

git remote add upstream https://github.com/<org>/<repo>

With this information, git can then fetch the upstream:

git fetch upstream

And now comes the magic: Create and checkout a branch, that until then only exists in the original remote repo with:

git checkout -b <branch> upstream/<branch>

I know that it is pretty annoying that you have to specify the branch name two times, but in theory you could name the local branch differently.

Also don't forget that this branch does not yet exist in your remote fork, but you can push it easily with:

git push -u origin <branch>

Rename the commit message of an older commit

If you want to rename the last commit, just use:

git commit --amend

When you already have multiple local commits, you have to start an interactive rebase with:

git rebase -i HEAD~3

Here, 3 is the number of commits you want to adapt. If your commit is back 10 commits, you need to put a 10 there instead.

Git will open your favourite editor with some text like:

pick 64e085f Commit Message 1

pick e5f607a Commit Message 2

pick a9b0c1d Commit Message 3

You can now edit this text like a normal file and replace all the pick keywords where you want to change the commit message with reword. After you save and close the file, git will repeatedly open text files with the old commit message, which you can freely edit to your likings1. After you adapted everything you wanted and close the last file, the rebase is finished and you can check your corrections with:

git log --oneline

Interactive rebasing is incredibly useful, as other options like squash, break or even exec are available. Just keep in mind that it makes more sense to do this on a branch where only you work to avoid team friction.

Clean up local branches

When a PR is merged, the branch is usually deleted after it is safely merged into the default branch. But the branch still exists locally. If you want to delete all the local branches, which got deleted in your remote repository, you can run this command in most shells (use wsl on Windows)2:

git fetch -p && git branch -vv | awk '/: gone] / {print $1}' | grep -v '^\*$' | xargs -I{} git branch -d "{}"

Remove tracked files with .gitignore

We’ve all been there: you create a config.json or a .env file, write some code, and commit it. Later, you realize that file should definitely not be in the repository, so you add it to your .gitignore.

The problem? Git is already tracking it. Adding a file to .gitignore only stops Git from tracking new files. To tell Git to "forget" a file it is already tracking without deleting it from your hard drive, you use the --cached flag:

git rm --cached <file_path>

If you accidentally committed an entire directory (like node_modules or dist), you can do it recursively:

git rm -r --cached <folder_name>/

If your repository has become a bit of a mess and you want to "reset" the tracking for everything to strictly follow your current .gitignore rules, run this sequence from the repository root:

git rm -r --cached .

git add .

git commit -m "Refactor: apply .gitignore to all tracked files"

This removes everything from the index and adds it all back. Because the files are now in your .gitignore, Git will simply skip over them during the add step, effectively untracking them3.

Summary

🛰️ Track Upstream Branch

git fetch upstream && git checkout -b <branch> upstream/<branch>

Fetches the latest references from the original repository and creates a local branch that tracks the upstream version in one logical flow.

🔄 Rename Older Commits

git rebase -i HEAD~n

Use this to rewrite history. In the interactive editor, change pick to reword for any of the last n commits you want to modify.

🧹 Prune Local Branches

git fetch -p && git branch -vv | awk '/: gone] / {print $1}' | grep -v '^\*$' | xargs -I{} git branch -d "{}"

A pipeline that identifies local branches whose remote counterparts have been deleted ("gone") and attempts to delete them safely.

🧺 Untrack "Ignored" Files

git rm --cached <file>

Removes files from the Git index while keeping them physically on your disk. Essential for when you added a file to .gitignore after it was already committed.

Resources

Here are some helpful resources for learning git and other situations you often find yourself in:

https://learngitbranching.js.org/

https://ohshitgit.com/

That's it for now, more useful uncommon git commands might be added in the future!

Renaming commits is not recommended when the commit is already pushed to the remote as you are changing the commit history for everyone.

This is a destructive action. Therefore I included a safeguard by using the lowercase -d parameter. If your branches are not cleanly merged, git will not delete them. If you are sure you want to delete all those branches no matter what, use -D instead.

If the file contained secrets, untracking it is not enough. The secret is still present in earlier commits and may already be on the remote, so rotate the secret and consider rewriting history as well.