Git Common Commands and Workflow Notes
Learning Git is not about memorizing commands. What actually gets confusing is: which layer a file is in right now, where you want it to go next, and whether a given command will lose something.
These notes follow the order of everyday use. First get the working tree, staging area, local repository and remote repository pipeline working end to end, then fill in rollback, undo, branches and collaboration.
Start with Git’s Three Layers
Almost all everyday Git operations revolve around these three layers:
Working Tree
the files you are currently editing in the folder
Staging Area / Index
changes already added with git add, ready to go into the next commit
Repository
the version history already saved with git commit
A routine commit looks like this:
git status
git diff
git add .
git commit -m "describe change"
git push
When in doubt, run git status first. It is the cheapest command and the best protection against mistakes.
Installation and Initialization
Installing on macOS:
brew install git
The first time you use it, configure your identity:
git config --global user.name "Your Name"
git config --global user.email "email@example.com"
git config --global --list
Initialize a repository after entering the project directory:
git init
After initialization a .git directory appears — this is where Git stores version information. It is hidden by default; you can see it with:
ls -ah
To show hidden files in Finder:
Command + Shift + .
Your First Commit
Add a single file:
git add README.md
Add all changes in the current directory:
git add .
Commit to the local repository:
git commit -m "init project"
git add only puts things in the staging area; only git commit creates a local version.
Checking Status and History
Check the current status:
git status
See changes in the working tree that are not yet staged:
git diff
See the commit history:
git log
The more commonly used compact version:
git log --oneline --graph --decorate --all
This command shows commits, branches and merge relationships all at once.
Rolling Back Versions
HEAD refers to the current commit:
HEAD current commit
HEAD^ previous commit
HEAD^^ the one before that
HEAD~10 10 commits back
Roll back to the previous commit:
git reset --hard HEAD^
Roll back to a specific commit:
git reset --hard <commit_id>
You need to keep the three reset modes straight:
| Command | commit | Staging area | Working tree | When to use |
|---|---|---|---|---|
git reset --soft <id> |
rolled back | kept | kept | Undo the commit but keep changes staged |
git reset --mixed <id> |
rolled back | reset | kept | Undo the commit and the add, keep the file edits |
git reset --hard <id> |
rolled back | reset | reset | Go all the way back to a given version |
--hard changes the working tree directly, and unsaved edits are lost. Check git status before using it.
If you regret a rollback, you can use:
git reflog
git reset --hard <commit_id>
git log shows the commit history; git reflog shows the local record of HEAD movements. It rescues a lot of local mistakes.
Undoing Changes
Handle it according to where the change lives:
| Scenario | Command |
|---|---|
| Working tree is messed up, not yet added | git restore <file> |
| Already added, want to unstage | git restore --staged <file> |
| Unstaged it and now want to discard the file edits too | git restore <file> |
| Already committed, not yet pushed | git reset --soft/--mixed/--hard <id> |
| Already pushed, do not want to rewrite remote history | git revert <commit_id> |
The old commands still work:
git checkout -- <file>
git reset HEAD <file>
These days I prefer restore and switch because the semantics are clear: restore handles file restoration, switch handles branch switching.
Deleting Files
Delete a file and stage the deletion:
git rm <file>
git commit -m "remove file"
If you deleted it manually, you can also do:
git add <file>
git commit -m "remove file"
Deleted by mistake and not yet committed:
git restore <file>
Connecting to GitHub
Link a remote repository:
git remote add origin git@github.com:<user>/<repo>.git
git remote -v
Push for the first time and bind the upstream:
git push -u origin main
If the default branch is called master:
git push -u origin master
Once bound, day-to-day syncing is just:
git push
git pull
Remove the remote link:
git remote rm origin
Clone a remote repository:
git clone git@github.com:<user>/<repo>.git
SSH Key
First check whether the machine already has a key:
ls -ah ~/.ssh
Generate a new key:
ssh-keygen -t ed25519 -C "email@example.com"
On older setups you can use RSA:
ssh-keygen -t rsa -C "email@example.com"
Copy the public key:
pbcopy < ~/.ssh/id_ed25519.pub
The RSA equivalent:
pbcopy < ~/.ssh/id_rsa.pub
id_xxx is the private key — never leak it; id_xxx.pub is the public key, which you can paste into GitHub’s SSH Keys page.
Branches
List branches:
git branch
Create a branch:
git branch <name>
Switch branches:
git switch <name>
Create and switch:
git switch -c <name>
The old commands:
git checkout <name>
git checkout -b <name>
Merge a branch into the current branch:
git merge <name>
Delete a merged branch:
git branch -d <name>
Force-delete an unmerged branch:
git branch -D <name>
-D throws away the unmerged commits on that branch, so only use it once you are sure you do not need them.
Conflicts
When a merge fails, check the status first:
git status
The conflicted file will contain:
<<<<<<< HEAD
content on the current branch
=======
content from the merged branch
>>>>>>> feature
Edit it manually into what you actually want, then:
git add <file>
git commit
View the branch graph:
git log --graph --oneline --decorate --all
stash
Your work is not finished but you need to switch branches temporarily:
git stash
List them:
git stash list
Restore and drop the most recent stash:
git stash pop
Restore only, without deleting the record:
git stash apply
cherry-pick
Copy a specific commit from another branch onto the current branch:
git cherry-pick <commit_id>
A typical scenario is a bug fix that has already landed on main while dev needs the same change. cherry-pick copies a single commit, not an entire branch merge.
Working with Others
List remote branches:
git branch -r
Pull remote updates:
git pull
Push the current branch:
git push origin <branch-name>
Create a local branch based on a remote branch:
git switch -c <branch-name> origin/<branch-name>
Set the upstream:
git branch --set-upstream-to=origin/<branch-name> <branch-name>
If you do not push a new local branch, nobody else can see it. A failed push usually means the remote already has new commits — pull or fetch first, then resolve the conflicts.
rebase
Tidy up local commit history that has not been pushed:
git fetch origin
git rebase origin/main
This suits the case where the local branch has not been pushed yet and you want a straighter history. Do not casually rebase a public branch that has already been pushed and that others have built on top of.
.gitignore
.gitignore holds the files that should not go into the repository:
.DS_Store
node_modules/
.venv/
__pycache__/
*.log
.env
Common things to ignore:
- Files generated automatically by the operating system, such as
.DS_Store. - Build artifacts, caches and temporary files.
- Local virtual environments.
- Local configuration containing passwords, tokens or keys.
.gitignore itself should be committed to Git so the whole team shares the same rules.
If a file is already tracked by Git, adding it to .gitignore later does not automatically take effect; you need:
git rm --cached <file>
Handy macOS Commands
Go back to the user home directory:
cd ~
Jump to the home directory in Finder:
Command + Shift + H
Show hidden files:
ls -ah
List files by modification time, newest at the bottom:
ls -ltr
View a file’s contents:
cat <file>
List only the directory itself without expanding its contents:
ls -d .ssh
The Workflows I Actually Use
Solo development:
git status
git diff
git add .
git commit -m "describe change"
git push
New feature:
git switch main
git pull
git switch -c feature/xxx
git add .
git commit -m "add xxx"
git push -u origin feature/xxx
Quick bug fix:
git stash
git switch main
git pull
git switch -c fix/bug-name
git add .
git commit -m "fix bug-name"
git push -u origin fix/bug-name
git switch <old-branch>
git stash pop
In the end, a few rules are enough:
- When in doubt, run
git statusfirst. git addonly stages; it does not save history.git commitsaves locally.git pushsyncs to the remote.git reflogcan rescue a lot of local mistakes.- Before using
reset --hardorbranch -D, make sure the working tree has no important changes.