Git is the most widely used version control system today. This guide starts from initial setup and covers the vast majority of operations you will use in daily development — branches, merging, rebasing, undoing changes, stash, collaboration workflows, and more.
Initial Setup
Configure Git before using it for the first time:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
Set the default branch name (recommended: main):
git config --global init.defaultBranch main
Set the default editor (VS Code example):
git config --global core.editor "code --wait"
View all configuration:
git config --list
Creating a Repository
From scratch:
mkdir my-project && cd my-project
git init
Clone an existing repository:
# HTTPS (requires username/password or token)
git clone https://github.com/user/repo.git
# SSH (requires SSH key setup)
git clone git@github.com:user/repo.git
# Clone into a specific folder name
git clone git@github.com:user/repo.git my-folder
Basic Workflow
Git has three local layers: working directory → staging area → local repository.
# Check current status
git status
# Add files to staging
git add file.txt
git add . # stage all changes
git add -p # interactively stage chunks (very useful)
# Commit
git commit -m "feat: add user login"
# Skip staging, commit tracked changes directly
git commit -am "fix: correct typo"
Commit Message Convention (Conventional Commits)
Good commit messages make history readable and enable automatic CHANGELOG generation:
type(scope): subject
feat New feature
fix Bug fix
docs Documentation change
style Formatting (no logic change)
refactor Code refactor
test Add or modify tests
chore Build process, tool config, etc.
Viewing History
# View commit history
git log
# Compact one-line format
git log --oneline
# Graphical branch view
git log --oneline --graph --all
# View history of a specific file
git log --follow -- path/to/file
# View changes of a specific commit
git show <commit-hash>
Branch Management
Branching is one of Git's most powerful features — nearly free to create and switch.
# List all branches
git branch
git branch -a # including remote branches
# Create a new branch
git branch feature/login
# Switch to a branch
git switch feature/login
# Create and switch in one step (common shorthand)
git switch -c feature/login
# Delete a branch
git branch -d feature/login # safe delete (warns if unmerged)
git branch -D feature/login # force delete
# Rename current branch
git branch -m new-name
Merging
# Switch to the target branch (usually main)
git switch main
# Merge the feature branch
git merge feature/login
Fast-forward merge: the target branch has no new commits, so Git simply moves the pointer forward — history stays linear.
Non-fast-forward merge: the target has new commits, so Git creates a merge commit and history shows the fork structure.
To always create a merge commit (preserving the branch record):
git merge --no-ff feature/login
Resolving Conflicts
When a merge conflict occurs, the affected file contains markers:
<<<<<<< HEAD
your local content
=======
incoming content
>>>>>>> feature/login
Manually edit the file to keep the correct content, then:
git add .
git commit # Git auto-fills the merge commit message
Rebasing
Rebase replays your branch's commits on top of another branch, keeping history linear and clean.
# On the feature branch, rebase it onto the latest main
git switch feature/login
git rebase main
Interactive rebase (great for cleaning up commit history):
# Edit the last 3 commits
git rebase -i HEAD~3
Available commands in the editor:
| Command | Effect |
|---|---|
pick | Keep the commit as-is |
reword | Keep but edit the message |
squash | Combine into the previous commit |
fixup | Like squash, discard the message |
drop | Delete the commit |
Note: Never rebase commits that have already been pushed to a shared public branch — it rewrites history and causes conflicts for others.
Remote Repositories
# View remotes
git remote -v
# Add a remote
git remote add origin git@github.com:user/repo.git
# Change remote URL
git remote set-url origin git@github.com:user/new-repo.git
# Push (use -u the first time to set upstream)
git push -u origin main
# Subsequent pushes
git push
# Pull (fetch + merge)
git pull
# Fetch only, without merging
git fetch origin
# Delete a remote branch
git push origin --delete feature/login
Stash
When you need to switch branches but have uncommitted changes you are not ready to commit:
# Stash current changes
git stash
# Stash with a description
git stash push -m "wip: half-done login form"
# List all stashes
git stash list
# Restore the most recent stash (keep it in the list)
git stash apply
# Restore and remove from list
git stash pop
# Restore a specific stash
git stash apply stash@{2}
# Delete a specific stash
git stash drop stash@{0}
# Clear all stashes
git stash clear
Undoing Changes
Git has several undo commands — understand them by scenario:
Undo working directory changes (before add)
# Restore a file to its last committed state
git restore file.txt
# Restore all files
git restore .
Unstage files (after add, before commit)
git restore --staged file.txt
Amend the last commit
# Edit the commit message, or add a forgotten file
git add forgotten-file.txt
git commit --amend
Safely undo a commit (creates a reverse commit)
git revert <commit-hash>
Roll back to a previous commit (reset)
# Soft reset: undo the commit, keep changes in staging
git reset --soft HEAD~1
# Mixed reset (default): undo the commit, move changes to working dir
git reset HEAD~1
# Hard reset: discard the commit and all changes (destructive, use carefully)
git reset --hard HEAD~1
revertvsreset:
revertappends a new commit that reverses changes; history is preserved — safe for public branchesresetrewrites history — only use it on local commits that have not been pushed
Tags
Typically used to mark release versions:
# Lightweight tag
git tag v1.0.0
# Annotated tag (includes a message)
git tag -a v1.0.0 -m "Release version 1.0.0"
# List all tags
git tag
# Push a tag to remote
git push origin v1.0.0
git push origin --tags # push all tags
# Delete a tag
git tag -d v1.0.0
git push origin --delete v1.0.0 # delete remote tag
.gitignore
.gitignore tells Git which files to leave untracked:
# Dependencies
node_modules/
vendor/
# Build output
dist/
build/
*.min.js
# Environment variables
.env
.env.local
# OS files
.DS_Store
Thumbs.db
# IDE config
.vscode/
.idea/
Files already tracked by Git are not ignored just by adding them to .gitignore — you need to untrack them first:
git rm --cached file.txt # untrack a single file
git rm --cached -r node_modules/ # untrack a directory
Collaboration Scenarios
Fork + Pull Request Workflow
- Fork the target repository to your account
- Clone your fork
- Create a feature branch
- Develop and commit
- Push to your fork
- Open a Pull Request / Merge Request on GitHub / GitLab
Keep your fork in sync with upstream:
# Add upstream remote
git remote add upstream git@github.com:original/repo.git
# Fetch upstream changes
git fetch upstream
# Merge into local main
git switch main
git merge upstream/main
Key Principles for Team Collaboration
main/masteris usually protected — no direct push allowed- Open one feature branch per feature
- Rebase onto the latest main before merging to reduce conflicts
- Do Code Review in PRs before merging
Handy Commands
# Compare two branches
git diff main feature/login
# Show who changed each line of a file
git blame file.txt
# Binary search for the commit that introduced a bug
git bisect start
git bisect bad # current version is broken
git bisect good v1.0.0 # this version was fine
# Git checks out the midpoint — test it, then mark good or bad
git bisect reset # finish
# Apply a specific commit to the current branch
git cherry-pick <commit-hash>
# Remove untracked files and directories
git clean -fd
Common Issues
Push rejected
Usually because the remote has new commits from others:
git pull --rebase # pull then rebase, keeps linear history
git push
Recovering a deleted branch
Commits do not disappear immediately. Find them via reflog:
git reflog # view all operation history
git switch -c recovered-branch <commit-hash>
Large file accidentally committed
Large files should not live in Git (they permanently bloat the repository).
If not yet pushed:
git rm --cached bigfile.zip
git commit --amend
If already pushed, use git filter-repo (replacement for the deprecated filter-branch) to purge the file from history — the process is involved, so the best fix is preventing it upfront with .gitignore.
Fonnpo