Developers would:
myproject_v1, myproject_v2_final, myproject_v2_final_REALLY_FINALFirst version control system, proprietary to AT&T. Stored deltas (differences) between versions. Single-file locking.
Open-source alternative to SCCS. Improved storage efficiency. Still file-based, not project-based.
Built on RCS. First to allow concurrent editing. Introduced the concept of a central repository. Network support for remote collaboration.
Architecture: Centralized - single server holds the repository
Designed to replace CVS. Better handling of binary files, atomic commits, directory versioning. Still centralized.
Key limitation: Branching and merging were slow and painful
Commercial distributed VCS used by Linux kernel development (2002-2005). Fast, distributed architecture. Controversy when free license was revoked.
Linus Torvalds created Git in April 2005 after BitKeeper's license revocation. Designed from scratch with these goals:
Fun fact: The first version was written in a few days. Git was self-hosting within days of its creation.
GitHub revolutionized collaboration by adding a web interface, pull requests, and social coding features to Git.
Git has three main states that files can be in:
Working Directory → git add → Staging Area → git commit → Repository
(staged) (committed)
# Set your identity (required for commits)
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
# Set default branch name
git config --global init.defaultBranch main
# Set default editor
git config --global core.editor "vim"
# Enable color output
git config --global color.ui auto
# View all settings
git config --list
# Three levels of config:
# --system: /etc/gitconfig (all users)
# --global: ~/.gitconfig (your user)
# --local: .git/config (specific repo)
# Create a new repository
git init
# Clone an existing repository
git clone https://github.com/user/repo.git
git clone https://github.com/user/repo.git my-folder-name
# Check status
git status
# Add files to staging area
git add file.txt # Add specific file
git add . # Add all files in current directory
git add *.js # Add all .js files
git add -A # Add all changes (new, modified, deleted)
# Remove from staging (unstage)
git reset HEAD file.txt
# Commit changes
git commit -m "Commit message"
git commit -am "Add and commit in one step (tracked files only)"
# View commit history
git log
git log --oneline # Compact view
git log --graph --all # Visual branch graph
git log -p # Show patches (diffs)
git log --since="2 weeks ago"
git log --author="John"
# View changes
git diff # Changes not staged
git diff --staged # Changes staged for commit
git diff HEAD # All changes since last commit
git diff branch1..branch2 # Differences between branches
Git stores everything as objects in a content-addressable filesystem. Understanding this helps you understand how Git really works.
| Object Type | Contents | Purpose |
|---|---|---|
| Blob | File contents | Stores the actual data of files |
| Tree | Directory listing (references to blobs and other trees) | Represents directory structure |
| Commit | Tree reference, parent commit(s), author, message, timestamp | Snapshot of the project at a point in time |
| Tag | Reference to a commit with additional metadata | Named reference to a specific commit (releases) |
# All objects identified by SHA-1 hash
# Example commit object:
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
parent 1a410efbd13591db07496601ebc7a059dd55cfe9
author John Doe <john@example.com> 1635789012 -0700
committer John Doe <john@example.com> 1635789012 -0700
Initial commit message
# Inspect objects
git cat-file -t <hash> # Show type
git cat-file -p <hash> # Show contents
A branch in Git is simply a lightweight movable pointer to a commit. The default branch is main (or master in older repos).
# Stored in .git/refs/heads/
# Each branch file contains the SHA-1 of the commit it points to
# Example: .git/refs/heads/main
4b825dc642cb6eb9a060e54bf8d69288fbee4904
HEAD is a special pointer that indicates where you currently are in the repository. Usually points to a branch, which in turn points to a commit.
# .git/HEAD contains:
ref: refs/heads/main
# When you checkout a commit directly (detached HEAD):
4b825dc642cb6eb9a060e54bf8d69288fbee4904
HEAD: Current branch/commitHEAD^ or HEAD~1: Parent of HEADHEAD~2: Grandparent of HEADHEAD~3: Great-grandparent of HEADHEAD^^: Same as HEAD~2main@{yesterday}: Where main pointed yesterdayHEAD@{2}: Where HEAD pointed 2 moves agoBranches are incredibly lightweight in Git - just a 41-byte file (40 char SHA-1 + newline). This makes branch creation and switching nearly instantaneous.
# Create a new branch
git branch feature-login
# Switch to a branch
git checkout feature-login
git switch feature-login # Newer, clearer command (Git 2.23+)
# Create and switch in one command
git checkout -b feature-login
git switch -c feature-login
# List branches
git branch # Local branches
git branch -r # Remote branches
git branch -a # All branches
git branch -v # With last commit
# Rename a branch
git branch -m old-name new-name
git branch -m new-name # Rename current branch
# Delete a branch
git branch -d feature-login # Safe delete (checks if merged)
git branch -D feature-login # Force delete
# Track remote branch
git branch -u origin/feature-login
When the target branch hasn't diverged from the source branch, Git can simply move the pointer forward.
# Situation:
main: A---B---C
\
feature: D---E
# After: git checkout main && git merge feature
main: A---B---C---D---E
\
feature: (same)
# This is a fast-forward: main just moves to E
$ git checkout main
$ git merge feature-login
Updating f42c576..3a0874c
Fast-forward
login.js | 10 ++++++++++
1 file changed, 10 insertions(+)
When both branches have diverged, Git creates a new "merge commit" with two parents.
# Situation:
main: A---B---C---F
\
feature: D---E
# After: git checkout main && git merge feature
main: A---B---C---F---G (merge commit)
\ /
feature: D---E
# G is a merge commit with two parents: F and E
$ git checkout main
$ git merge feature-login
Merge made by the 'ort' strategy.
login.js | 10 ++++++++++
auth.js | 5 +++++
2 files changed, 15 insertions(+)
Combines all commits from feature branch into a single commit on target branch.
git merge --squash feature-login
git commit -m "Add login feature"
# All changes from feature-login appear as one commit on main
Conflicts occur when the same part of a file was changed differently in two branches.
<<<<<<< HEAD
function login(username, password) {
return authenticateUser(username, password);
}
=======
function login(email, password) {
return authenticateWithEmail(email, password);
}
>>>>>>> feature-login
# 1. See which files have conflicts
git status
# 2. Open conflicted files and resolve manually
# Remove conflict markers, keep desired changes
# 3. Stage resolved files
git add file.js
# 4. Complete the merge
git commit
# Or abort the merge
git merge --abort
# Use a merge tool
git mergetool
# See the three versions
git show :1:file.js # Common ancestor
git show :2:file.js # Your version (HEAD)
git show :3:file.js # Their version (merging branch)
# Take their version entirely
git checkout --theirs file.js
# Take your version entirely
git checkout --ours file.js
Rebasing rewrites history by moving a sequence of commits to a new base commit. It creates new commits with the same changes but different parent commits (and therefore different SHA-1 hashes).
# Before rebase:
main: A---B---C
\
feature: D---E
# After: git checkout feature && git rebase main
main: A---B---C
\
feature: D'---E'
# D' and E' are NEW commits with same changes as D and E
# but with C as parent instead of B
# Rebase current branch onto main
git checkout feature-login
git rebase main
# Or in one command (Git 2.23+)
git rebase main feature-login
# If conflicts occur:
# 1. Resolve conflicts in files
# 2. Stage resolved files: git add file.js
# 3. Continue: git rebase --continue
# Or abort: git rebase --abort
# Or skip this commit: git rebase --skip
Never rebase commits that have been pushed to a public/shared repository!
Rebasing rewrites history. If others have based work on your commits, rebasing will cause major problems when they try to pull your rebased commits.
Safe to rebase: Local commits not yet pushed
Dangerous to rebase: Any commits pushed to shared branches
Interactive rebase lets you modify commits as you replay them. Incredibly powerful for cleaning up history before pushing.
# Rebase last 3 commits interactively
git rebase -i HEAD~3
# Rebase all commits on current branch since it diverged from main
git rebase -i main
Opens an editor with commit list:
pick f7f3f6d Change header
pick 310154e Update footer
pick a5f4a0d Fix bug in login
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard commit message
# d, drop = remove commit
# Squash last 3 commits into one
git rebase -i HEAD~3
# Change last 2 to 'squash'
# Reword a commit message
git rebase -i HEAD~3
# Change 'pick' to 'reword' for commit you want to change
# Reorder commits
git rebase -i HEAD~3
# Just reorder the lines
# Split a commit
git rebase -i HEAD~3
# Change 'pick' to 'edit' for commit to split
# When rebase stops: git reset HEAD^
# Then stage and commit in multiple commits
# git rebase --continue
# 1. Create feature branch from main
git checkout -b feature-login main
# 2. Work on feature (multiple commits)
git commit -m "Add login form"
git commit -m "Add validation"
git commit -m "Fix typo"
# 3. Main has moved forward, update your branch
git fetch origin
git rebase origin/main
# Resolve any conflicts
# 4. Clean up commits before pushing (optional)
git rebase -i origin/main
# Squash small commits, fix commit messages
# 5. Push feature branch
git push origin feature-login
# 6. Create pull request on GitHub
# 7. After approval, merge (on GitHub or locally)
git checkout main
git merge feature-login --no-ff # --no-ff creates merge commit
git push origin main
A comprehensive branching model with specific branch types and rules. Popular for projects with scheduled releases.
| Branch | Purpose | Lifetime | Merges Into |
|---|---|---|---|
main |
Production-ready code | Permanent | - |
develop |
Integration branch for features | Permanent | main |
feature/* |
New features | Temporary | develop |
release/* |
Prepare for release | Temporary | main and develop |
hotfix/* |
Emergency fixes for production | Temporary | main and develop |
# Start a feature
git checkout -b feature/login develop
# Work on feature...
git checkout develop
git merge --no-ff feature/login
git branch -d feature/login
# Start a release
git checkout -b release/1.2.0 develop
# Bump version, fix bugs...
git checkout main
git merge --no-ff release/1.2.0
git tag -a v1.2.0
git checkout develop
git merge --no-ff release/1.2.0
git branch -d release/1.2.0
# Hotfix
git checkout -b hotfix/1.2.1 main
# Fix bug...
git checkout main
git merge --no-ff hotfix/1.2.1
git tag -a v1.2.1
git checkout develop
git merge --no-ff hotfix/1.2.1
git branch -d hotfix/1.2.1
A simpler alternative to Git Flow, designed for continuous deployment. Popular at GitHub and many web companies.
main is always deployablemainmain# 1. Create branch from main
git checkout -b add-user-authentication main
# 2. Work and push regularly
git commit -m "Add login endpoint"
git push -u origin add-user-authentication
# 3. Open pull request on GitHub (can be early for feedback)
# 4. Continue working, push updates
git commit -m "Add password validation"
git push
# 5. After approval, merge on GitHub
# 6. Delete branch
git branch -d add-user-authentication
git push origin --delete add-user-authentication
# 7. Deploy main to production
Combines GitHub Flow simplicity with environment branches. Good for projects with different environments.
feature → main → pre-production → production
# Feature merged to main after review
# main auto-deploys to staging
# Cherry-pick or merge main to pre-production when ready
# Cherry-pick or merge to production after testing
feature → main
↓
release/2.3
release/2.2 (for hotfixes)
# Create release branch from main when ready
# Hotfixes go to release branch, then cherry-pick to main
Developers merge small, frequent changes directly to main (the trunk). Extreme version of continuous integration.
main daily (or multiple times per day)main when ready# Variation 1: Direct commits to main (requires strong CI)
git checkout main
git pull
# Make small change
git commit -m "Add logout button"
git push
# Variation 2: Very short-lived branches
git checkout -b add-logout-button
# Make change
git commit -m "Add logout button"
git push -u origin add-logout-button
# Immediately create PR and merge (same day)
# Release
git checkout -b release/v2.3 main
git tag v2.3.0
# Only bug fixes on release branch
Consider:
GitHub is a cloud-based hosting service for Git repositories. It adds:
Alternatives: GitLab, Bitbucket, Gitea, Azure DevOps
A remote is a Git repository hosted elsewhere (GitHub, GitLab, etc). Your local repository can have multiple remotes.
# View remotes
git remote -v
# Add a remote
git remote add origin https://github.com/user/repo.git
git remote add upstream https://github.com/original/repo.git
# Remove a remote
git remote remove origin
# Rename a remote
git remote rename origin upstream
# Change remote URL
git remote set-url origin https://github.com/user/new-repo.git
# Fetch: Download objects and refs from remote, but don't merge
git fetch origin
# Updates origin/main but not your local main
# Pull: Fetch + Merge in one step
git pull origin main
# Equivalent to:
git fetch origin
git merge origin/main
# Pull with rebase instead of merge
git pull --rebase origin main
# Push to remote
git push origin main
# Push and set upstream (tracking)
git push -u origin feature-branch
# After this, just: git push
# Push all branches
git push --all
# Push tags
git push --tags
# Force push (dangerous!)
git push --force origin main
# Safer force push (fails if remote has commits you don't)
git push --force-with-lease origin main
# Delete remote branch
git push origin --delete feature-branch
Used for contributing to open source projects.
# 1. Fork on GitHub (click Fork button)
# 2. Clone your fork
git clone https://github.com/YOUR-USERNAME/repo.git
cd repo
# 3. Add upstream remote
git remote add upstream https://github.com/ORIGINAL-OWNER/repo.git
# 4. Create feature branch
git checkout -b fix-bug-123
# 5. Work on changes
git commit -am "Fix bug in user authentication"
# 6. Push to your fork
git push -u origin fix-bug-123
# 7. Create Pull Request on GitHub
# Keep your fork synced with upstream
git fetch upstream
git checkout main
git merge upstream/main
git push origin main
Pull requests (PRs) are GitHub's way to propose changes. They facilitate code review and discussion.
## Summary
Brief description of changes
## Changes Made
- Added user authentication
- Updated login form validation
- Fixed password reset bug
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manually tested on dev environment
## Screenshots
(if applicable)
## Related Issues
Closes #123
Related to #456
# Request reviews
# Reviewers can: Comment, Approve, or Request Changes
# Address feedback
git checkout feature-branch
# Make changes
git commit -m "Address review feedback"
git push
# PR automatically updates
# Merge options on GitHub:
# 1. Merge commit (default)
# 2. Squash and merge (all commits → 1 commit)
# 3. Rebase and merge (linear history)
Automate workflows directly in GitHub.
Create .github/workflows/test.yml:
name: Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run linter
run: npm run lint
Common use cases: testing, building, deploying, releasing, code quality checks.
Apply specific commits from one branch to another.
# Apply commit from another branch
git cherry-pick abc123
# Cherry-pick multiple commits
git cherry-pick abc123 def456
# Cherry-pick a range
git cherry-pick abc123..def456
# Cherry-pick without committing (allows editing)
git cherry-pick -n abc123
# Resolve conflicts if they occur
# Edit files, then:
git add .
git cherry-pick --continue
# Abort
git cherry-pick --abort
# On release/1.2 branch, fix critical bug
git commit -m "Fix critical security issue"
# Switch to main and apply same fix
git checkout main
git cherry-pick abc123
Temporarily save uncommitted changes to work on something else.
# Stash current changes
git stash
git stash save "WIP: refactoring login"
# List stashes
git stash list
# Apply most recent stash (keeps it in stash list)
git stash apply
# Apply and remove from stash list
git stash pop
# Apply specific stash
git stash apply stash@{2}
# Show stash contents
git stash show
git stash show -p # Show diff
# Create branch from stash
git stash branch new-branch-name
# Drop a stash
git stash drop stash@{0}
# Clear all stashes
git stash clear
# Stash including untracked files
git stash -u
Mark specific commits as important (usually releases).
# Lightweight tag (just a pointer)
git tag v1.2.0
# Annotated tag (recommended - stores tagger, date, message)
git tag -a v1.2.0 -m "Release version 1.2.0"
# Tag a specific commit
git tag -a v1.1.9 abc123 -m "Hotfix release"
# List tags
git tag
git tag -l "v1.2.*"
# Show tag info
git show v1.2.0
# Push tags to remote
git push origin v1.2.0
git push origin --tags # Push all tags
# Delete tag locally
git tag -d v1.2.0
# Delete tag on remote
git push origin --delete v1.2.0
# Checkout tag (creates detached HEAD)
git checkout v1.2.0
Semantic versioning: MAJOR.MINOR.PATCH (e.g., 2.3.1)
Moves HEAD (and optionally the branch) to a different commit.
# Soft reset: Move HEAD, keep changes staged
git reset --soft HEAD~1
# Use case: Undo last commit, keep changes for re-committing
# Mixed reset (default): Move HEAD, unstage changes
git reset HEAD~1
git reset --mixed HEAD~1
# Use case: Undo commits and unstage, keep changes in working directory
# Hard reset: Move HEAD, discard all changes
git reset --hard HEAD~1
# DANGEROUS: Destroys uncommitted changes
# Reset specific file
git reset HEAD file.txt # Unstage file
git reset --hard permanently discards changes. Use with caution!
Creates a new commit that undoes changes from a previous commit. Safe for public branches.
# Revert a commit (creates new commit)
git revert abc123
# Revert without committing (allows editing)
git revert -n abc123
# Revert a merge commit
git revert -m 1 abc123
# -m 1 means keep changes from first parent
New command (Git 2.23+) for restoring files. Clearer than checkout.
# Restore file from staging to working directory (unstage)
git restore --staged file.txt
# Restore file from HEAD (discard changes)
git restore file.txt
# Restore from specific commit
git restore --source=abc123 file.txt
Git's safety net. Records every movement of HEAD, even if commits are "lost".
# View reflog
git reflog
# View reflog for specific branch
git reflog show main
# Output example:
abc123 HEAD@{0}: commit: Add feature
def456 HEAD@{1}: checkout: moving from main to feature
789abc HEAD@{2}: commit: Fix bug
# Recover "lost" commits
git reset --hard HEAD@{2}
# Or create branch from reflog entry
git branch recover-branch HEAD@{2}
# Oops, accidentally did hard reset
git reset --hard HEAD~5
# Find lost commits in reflog
git reflog
# Restore
git reset --hard HEAD@{1}
Note: Reflog entries expire (default 90 days for unreachable commits).
Binary search through commits to find which one introduced a bug.
# Start bisect
git bisect start
# Mark current commit as bad
git bisect bad
# Mark a known good commit
git bisect good v1.2.0
# Git checks out middle commit
# Test it, then mark as good or bad
git bisect good # if bug not present
git bisect bad # if bug is present
# Repeat until Git finds the culprit commit
# When done
git bisect reset
# Automated bisect with test script
git bisect start
git bisect bad
git bisect good v1.2.0
git bisect run npm test
# Git automatically finds first failing commit
Include other Git repositories within your repository.
# Add submodule
git submodule add https://github.com/user/lib.git libs/lib
# Clone repo with submodules
git clone --recursive https://github.com/user/project.git
# Or after cloning
git clone https://github.com/user/project.git
git submodule init
git submodule update
# Or in one command
git submodule update --init --recursive
# Update submodules to latest
cd libs/lib
git pull origin main
cd ../..
git add libs/lib
git commit -m "Update lib submodule"
# Update all submodules
git submodule update --remote
# Remove submodule
git submodule deinit libs/lib
git rm libs/lib
rm -rf .git/modules/libs/lib
Alternatives: Many teams now prefer package managers (npm, pip, etc.) over submodules.
Scripts that run automatically on Git events. Located in .git/hooks/.
| Hook | Trigger | Use Case |
|---|---|---|
pre-commit |
Before commit is created | Run linter, tests, check formatting |
commit-msg |
After commit message entered | Validate commit message format |
pre-push |
Before pushing to remote | Run tests, prevent pushing to main |
post-merge |
After merge completes | Install dependencies if package.json changed |
pre-rebase |
Before rebase | Prevent rebasing certain branches |
Create .git/hooks/pre-commit:
#!/bin/sh
# Run linter
npm run lint
if [ $? -ne 0 ]; then
echo "Linting failed. Fix errors before committing."
exit 1
fi
# Run tests
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Fix tests before committing."
exit 1
fi
exit 0
Make it executable: chmod +x .git/hooks/pre-commit
Tools for managing hooks: Husky, pre-commit framework
Good commit messages are essential for understanding project history.
type(scope): subject
body
footer
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Formatting, white-space (not CSS)refactor: Code change that neither fixes a bug nor adds a featureperf: Performance improvementtest: Adding or updating testschore: Build process, dependencies, toolingci: CI/CD changesfeat(auth): add password reset functionality
Implement password reset flow with email verification.
Users can now request a password reset link that expires
after 1 hour.
Closes #456
---
fix(api): prevent race condition in user creation
Add database transaction to ensure atomic user creation.
Previously, concurrent requests could create duplicate
users.
---
refactor(parser): simplify token parsing logic
Extract token validation to separate function for
better readability and testability.
---
docs(readme): update installation instructions
Add section for Docker-based setup and update
Node.js version requirement to 18+.
Commit 1: feat(auth): add user model and database schema
Commit 2: feat(auth): implement password hashing with bcrypt
Commit 3: feat(auth): add login endpoint
Commit 4: feat(auth): add JWT token generation
Commit 5: test(auth): add unit tests for authentication
Commit 6: docs(auth): document authentication endpoints
feature/add-user-authentication
feature/123-add-password-reset (with issue number)
bugfix/fix-login-redirect
bugfix/456-memory-leak-in-parser
hotfix/security-patch
hotfix/critical-database-error
refactor/simplify-auth-logic
docs/update-api-documentation
test/add-integration-tests
chore/update-dependencies
Specify files that Git should ignore.
# Dependencies
node_modules/
vendor/
# Build outputs
dist/
build/
*.pyc
*.class
# Environment variables
.env
.env.local
.env.*.local
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
*.log
logs/
# Temporary files
tmp/
temp/
*.tmp
# Sensitive data
secrets.yml
credentials.json
*.pem
*.key
# Ignore file
config.json
# Ignore directory
build/
# Ignore all .txt files
*.txt
# But don't ignore this specific file
!important.txt
# Ignore .txt files only in this directory (not subdirectories)
/*.txt
# Ignore all files in any directory named temp
**/temp/**
Tip: Use gitignore.io to generate .gitignore templates for your stack.
git diff to review your changes1. Create feature branch
2. Make changes and commit
3. Push to GitHub
4. Merge to main when ready
5. Deploy
1. Create feature branch from main
2. Make changes, commit often
3. Keep branch updated (merge or rebase main)
4. Push to remote
5. Create pull request
6. Code review and address feedback
7. Merge to main after approval
8. Delete feature branch
9. Pull updated main
1. Fork repository
2. Clone your fork
3. Add upstream remote
4. Create feature branch
5. Make changes and commit
6. Push to your fork
7. Create pull request to upstream
8. Address maintainer feedback
9. Keep fork synced with upstream