Git — Basics
Git is a distributed version control system that tracks changes in source code during software development. It enables multiple developers to work on the same project without interfering with each other, provides a complete history of every change, and allows branching and merging for parallel development.
Unlike centralized systems (SVN, CVS), every Git working directory is a full-fledged repository with complete history and version tracking, enabling offline work and resilient backups.
Git manages content through three core areas that files move through during development. Understanding this architecture is essential to using Git effectively.
1. Working Directory
The actual files on your filesystem that you edit. Git does not automatically track changes here — you must explicitly stage them.
| 1 | # Working directory state - files are "untracked" or "modified" |
| 2 | $ echo "new file" > index.html |
| 3 | $ git status |
| 4 | On branch main |
| 5 | Untracked files: |
| 6 | index.html |
2. Staging Area (Index)
A preview of your next commit. You selectively add changes from the working directory to the staging area. This lets you craft atomic commits.
| 1 | # Stage a file for commit |
| 2 | $ git add index.html |
| 3 | $ git status |
| 4 | On branch main |
| 5 | Changes to be committed: |
| 6 | new file: index.html |
3. Repository (Commit History)
The committed history stored in .git. Once you commit, Git permanently stores a snapshot of the staged changes.
| 1 | # Commit the staged snapshot |
| 2 | $ git commit -m "Add index.html" |
| 3 | [main 1a2b3c4] Add index.html |
| 4 | 1 file changed, 1 insertion(+) |
| 5 | |
| 6 | # The three trees after commit: |
| 7 | # Working Directory = Staging = HEAD (clean state) |
info
Git configuration is managed at three levels: system (all users), global (your account), and local (per repository). Each level overrides the one above it.
| 1 | # Set your identity (required for commits) |
| 2 | git config --global user.name "Jane Doe" |
| 3 | git config --global user.email "jane@example.com" |
| 4 | |
| 5 | # Set default branch name |
| 6 | git config --global init.defaultBranch main |
| 7 | |
| 8 | # Set editor for commit messages |
| 9 | git config --global core.editor "code --wait" |
| 10 | |
| 11 | # Enable color output |
| 12 | git config --global color.ui auto |
| 13 | |
| 14 | # Set diff tool |
| 15 | git config --global diff.tool vscode |
| 16 | |
| 17 | # View all config |
| 18 | git config --list |
| 19 | |
| 20 | # View config file directly |
| 21 | cat ~/.gitconfig |
| Level | Location | Scope |
|---|---|---|
| --system | /etc/gitconfig | All users on the machine |
| --global | ~/.gitconfig | Your user account |
| --local | .git/config | Current repository only |
best practice
There are two ways to get a Git repository: creating a new one from scratch or cloning an existing one from a remote.
| 1 | # Create a new repository from scratch |
| 2 | mkdir my-project |
| 3 | cd my-project |
| 4 | git init |
| 5 | # Output: Initialized empty Git repository in /path/my-project/.git/ |
| 6 | |
| 7 | # Clone an existing repository |
| 8 | git clone https://github.com/user/repo.git |
| 9 | # Clones into ./repo directory |
| 10 | |
| 11 | # Clone into a custom directory name |
| 12 | git clone https://github.com/user/repo.git my-custom-name |
| 13 | |
| 14 | # Clone a specific branch |
| 15 | git clone --branch develop https://github.com/user/repo.git |
| 16 | |
| 17 | # Shallow clone (only recent history, faster) |
| 18 | git clone --depth 1 https://github.com/user/repo.git |
After git init, Git creates a hidden .git directory that contains all repository metadata:
| 1 | .git/ |
| 2 | ├── HEAD # Points to current branch reference |
| 3 | ├── config # Repository-specific config |
| 4 | ├── description # Repository description (used by GitWeb) |
| 5 | ├── hooks/ # Scripts that run on specific events |
| 6 | ├── info/ # Additional repository info |
| 7 | ├── objects/ # All Git objects (commits, trees, blobs) |
| 8 | └── refs/ # References (branches, tags) |
| 9 | ├── heads/ # Local branch pointers |
| 10 | └── tags/ # Tag pointers |
warning
The daily Git workflow follows a predictable pattern: modify files, stage changes, commit, and repeat. This section covers the essential commands.
| 1 | # 1. Check what changed |
| 2 | git status |
| 3 | |
| 4 | # 2. See detailed diffs (unstaged) |
| 5 | git diff |
| 6 | |
| 7 | # 3. Stage specific files |
| 8 | git add index.html |
| 9 | git add src/*.js |
| 10 | |
| 11 | # 4. Stage all changes (use with caution) |
| 12 | git add -A |
| 13 | |
| 14 | # 5. See staged diff (what will be committed) |
| 15 | git diff --staged |
| 16 | |
| 17 | # 6. Commit with a message |
| 18 | git commit -m "Add responsive header component" |
| 19 | |
| 20 | # 7. Commit with a multi-line message |
| 21 | git commit -m "Refactor auth module |
| 22 | |
| 23 | - Extract token validation into middleware |
| 24 | - Add rate limiting to login endpoint |
| 25 | - Update test suite for new error types" |
| 26 | |
| 27 | # 8. Shortcut: stage all tracked files + commit |
| 28 | git commit -am "Quick fix to button styles" |
| 29 | |
| 30 | # 9. View commit history |
| 31 | git log |
| 32 | |
| 33 | # 10. One-line log |
| 34 | git log --oneline --graph --decorate |
pro tip
git status is the most frequently used command. It shows the state of your working directory and staging area. Learn to read its output quickly.
| 1 | $ git status |
| 2 | On branch main |
| 3 | Your branch is up to date with 'origin/main'. |
| 4 | |
| 5 | Changes to be committed: |
| 6 | (use "git restore --staged <file>..." to unstage) |
| 7 | modified: src/app.ts |
| 8 | |
| 9 | Changes not staged for commit: |
| 10 | (use "git add <file>..." to update what will be committed) |
| 11 | modified: src/utils.ts |
| 12 | deleted: old-config.json |
| 13 | |
| 14 | Untracked files: |
| 15 | (use "git add <file>..." to include in what will be committed) |
| 16 | new-feature.tsx |
| 17 | temp.log |
Each section tells you a different story:
Changes to be committed (staged)
Files in the staging area, ready for commit. Use git restore --staged to unstage if needed.
Changes not staged for commit (modified)
Tracked files that have been modified but not yet staged. Use git add to stage them.
Untracked files
New files that Git has never seen. They are not part of the repository until you git add them.
best practice
git log displays the commit history. It has dozens of formatting options to help you find specific changes quickly.
| 1 | # Standard log |
| 2 | git log |
| 3 | |
| 4 | # One line per commit (compact) |
| 5 | git log --oneline |
| 6 | |
| 7 | # Graph view with branches |
| 8 | git log --oneline --graph --all |
| 9 | |
| 10 | # Show diffs in log |
| 11 | git log -p |
| 12 | |
| 13 | # Show last 3 commits |
| 14 | git log -3 |
| 15 | |
| 16 | # Filter by author |
| 17 | git log --author="Jane" |
| 18 | |
| 19 | # Filter by date range |
| 20 | git log --after="2026-01-01" --before="2026-06-30" |
| 21 | |
| 22 | # Search commit messages |
| 23 | git log --grep="fix bug" |
| 24 | |
| 25 | # Search in file contents |
| 26 | git log -S "functionName" -- source/ |
| 27 | |
| 28 | # Show commits that modified a specific file |
| 29 | git log -- src/app.ts |
| 30 | |
| 31 | # Custom format |
| 32 | git log --pretty=format:"%h - %an, %ar : %s" |
pro tip
git diff shows the differences between any two states in your repository. It is essential for code review before committing.
| 1 | # Unstaged changes (working dir vs staging) |
| 2 | git diff |
| 3 | |
| 4 | # Staged changes (staging vs last commit) |
| 5 | git diff --staged |
| 6 | # (same as git diff --cached) |
| 7 | |
| 8 | # Working dir vs a specific commit |
| 9 | git diff HEAD~1 |
| 10 | |
| 11 | # Between two commits |
| 12 | git diff abc123..def456 |
| 13 | |
| 14 | # Between branches |
| 15 | git diff main..feature-x |
| 16 | |
| 17 | # Only file names (no content) |
| 18 | git diff --name-only |
| 19 | |
| 20 | # Summary of changes |
| 21 | git diff --stat |
| 22 | |
| 23 | # Ignore whitespace changes |
| 24 | git diff --ignore-all-space |
| 25 | |
| 26 | # See diff in word-level granularity |
| 27 | git diff --word-diff |
info
The .gitignore file tells Git which files to intentionally ignore. This keeps your repository clean of build artifacts, dependencies, environment files, and OS-specific files.
| 1 | # Dependencies |
| 2 | node_modules/ |
| 3 | vendor/ |
| 4 | .pnp |
| 5 | .pnp.js |
| 6 | |
| 7 | # Build outputs |
| 8 | dist/ |
| 9 | build/ |
| 10 | out/ |
| 11 | *.tsbuildinfo |
| 12 | |
| 13 | # Environment files |
| 14 | .env |
| 15 | .env.local |
| 16 | .env.production |
| 17 | |
| 18 | # OS files |
| 19 | .DS_Store |
| 20 | Thumbs.db |
| 21 | |
| 22 | # IDE/Editor |
| 23 | .vscode/ |
| 24 | .idea/ |
| 25 | *.swp |
| 26 | *.swo |
| 27 | |
| 28 | # Logs |
| 29 | *.log |
| 30 | npm-debug.log* |
| 31 | |
| 32 | # Runtime |
| 33 | tmp/ |
| 34 | temp/ |
| 35 | *.pid |
| 36 | *.seed |
Global ignore patterns for your machine (e.g., .DS_Store) go into a global gitignore:
| 1 | # Set a global gitignore |
| 2 | git config --global core.excludesFile ~/.gitignore_global |
| 3 | |
| 4 | # Good candidates for global ignore: |
| 5 | .DS_Store |
| 6 | Thumbs.db |
| 7 | *.swp |
| 8 | *.swo |
| 9 | *~ |
| Pattern | Effect |
|---|---|
| node_modules/ | Ignore directory (trailing /) |
| *.log | Ignore all .log files |
| !important.log | Negation — do not ignore this file |
| build/**/*.js | Ignore all .js in build/ and subdirs |
| [Tt]emp/ | Ignore Temp/ or temp/ |
warning
Git provides explicit commands for removing and renaming tracked files. These operations are also tracked in the repository history.
| 1 | # Remove from working tree + staging |
| 2 | git rm old-file.js |
| 3 | |
| 4 | # Remove only from staging (keep on disk) |
| 5 | git rm --cached config.json |
| 6 | |
| 7 | # Rename a file |
| 8 | git mv old-name.js new-name.js |
| 9 | |
| 10 | # Move to different directory |
| 11 | git mv src/old.js lib/new.js |
info
Git provides multiple ways to undo changes at different levels. Understanding the difference between discarding unstaged changes, un-staging, amending commits, and reverting is critical.
| 1 | # Discard unstaged changes (restore file to last commit) |
| 2 | git restore file.js |
| 3 | |
| 4 | # Unstage a file (keep changes in working dir) |
| 5 | git restore --staged file.js |
| 6 | |
| 7 | # Unstage all files |
| 8 | git restore --staged . |
| 9 | |
| 10 | # Amend last commit (fix message or add forgotten files) |
| 11 | git commit --amend -m "Corrected commit message" |
| 12 | |
| 13 | # Add forgotten files to last commit |
| 14 | git add forgotten.js |
| 15 | git commit --amend --no-edit |
| 16 | |
| 17 | # Revert a commit (creates a new commit that undoes it) |
| 18 | git revert abc123 |
| 19 | |
| 20 | # Reset (careful! rewrite history) |
| 21 | git reset --soft HEAD~1 # Move HEAD back, keep changes staged |
| 22 | git reset --mixed HEAD~1 # Move HEAD back, keep changes unstaged |
| 23 | git reset --hard HEAD~1 # Move HEAD back, DISCARD changes |
warning
git stash temporarily shelves changes so you can work on something else and come back later. It is essential when you need to switch branches mid-task.
| 1 | # Stash current changes |
| 2 | git stash |
| 3 | |
| 4 | # Stash with a descriptive message |
| 5 | git stash push -m "WIP: refactor auth middleware" |
| 6 | |
| 7 | # List stashes |
| 8 | git stash list |
| 9 | |
| 10 | # Show what's in a stash |
| 11 | git stash show stash@{0} |
| 12 | |
| 13 | # Apply last stash (keep in stash list) |
| 14 | git stash apply |
| 15 | |
| 16 | # Apply and remove from stash list |
| 17 | git stash pop |
| 18 | |
| 19 | # Apply a specific stash |
| 20 | git stash apply stash@{2} |
| 21 | |
| 22 | # Create a branch from a stash |
| 23 | git stash branch feature/new-branch stash@{0} |
| 24 | |
| 25 | # Drop a specific stash |
| 26 | git stash drop stash@{1} |
| 27 | |
| 28 | # Clear all stashes |
| 29 | git stash clear |
best practice
Aliases turn long git commands into shortcuts. They significantly improve daily workflow speed.
| 1 | # Common aliases every developer should have |
| 2 | git config --global alias.co checkout |
| 3 | git config --global alias.br branch |
| 4 | git config --global alias.st status |
| 5 | git config --global alias.ci commit |
| 6 | git config --global alias.df diff |
| 7 | git config --global alias.lg "log --oneline --graph --decorate --all" |
| 8 | git config --global alias.undo "reset --soft HEAD~1" |
| 9 | git config --global alias.unstage "restore --staged ." |
| 10 | git config --global alias.last "log -1 HEAD" |
| 11 | git config --global alias.visual "!gitk" |
| 12 | |
| 13 | # Usage after aliases are set |
| 14 | git st # instead of git status |
| 15 | git lg # pretty log graph |
| 16 | git ci -m "msg" # commit with message |
| 17 | git undo # undo last commit (soft) |
✗ Committing without configuring user info
git commit -m "message"Git will use the system hostname/username, creating incorrect attribution. Always set user.name and user.email first.
✗ Committing large files
git add giant-video.mp4 && git commit -m "add video"Git repos bloat permanently. Use .gitignore for build artifacts and Git LFS for large binary files.
✗ Adding sensitive data to a commit
git add .env && git commit -m "add config"Secrets in Git history are hard to remove. Even after deleting the file, the secret remains in history. Use .gitignore and git-secrets to prevent this.
✗ Ignoring .gitignore after tracking
echo "node_modules" >> .gitignore.gitignore only prevents tracking of untracked files. If node_modules is already tracked, you must run git rm --cached node_modules first.