Git & GitHub - Complete Guide

History of Version Control

Before Version Control

Developers would:

1972 - SCCS (Source Code Control System)

First version control system, proprietary to AT&T. Stored deltas (differences) between versions. Single-file locking.

1982 - RCS (Revision Control System)

Open-source alternative to SCCS. Improved storage efficiency. Still file-based, not project-based.

1986 - CVS (Concurrent Versions System)

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

2000 - Subversion (SVN)

Designed to replace CVS. Better handling of binary files, atomic commits, directory versioning. Still centralized.

Key limitation: Branching and merging were slow and painful

2000 - BitKeeper

Commercial distributed VCS used by Linux kernel development (2002-2005). Fast, distributed architecture. Controversy when free license was revoked.

2005 - Git Created

Linus Torvalds created Git in April 2005 after BitKeeper's license revocation. Designed from scratch with these goals:

  • Speed: Operations should be fast (most operations local)
  • Distributed: Every clone is a full backup
  • Data integrity: Cryptographic (SHA-1) checksums for everything
  • Support for non-linear development: Thousands of parallel branches
  • Efficiency: Handle large projects like Linux kernel

Fun fact: The first version was written in a few days. Git was self-hosting within days of its creation.

2008 - GitHub Launched

GitHub revolutionized collaboration by adding a web interface, pull requests, and social coding features to Git.

Centralized vs Distributed VCS

Centralized (CVS, SVN)

  • Single central server
  • Clients check out working copies
  • Must be online to commit
  • Single point of failure
  • History only on server
  • Slow operations (network latency)

Distributed (Git, Mercurial)

  • Every clone is a full repository
  • Complete history locally
  • Can commit offline
  • No single point of failure
  • Fast local operations
  • Flexible workflows

Git Basics

Core Concepts

The Three States

Git has three main states that files can be in:

  1. Modified: You've changed the file but haven't committed it yet
  2. Staged: You've marked a modified file to go into your next commit
  3. Committed: The data is safely stored in your local database

The Three Sections

  1. Working Directory: Your actual files on disk
  2. Staging Area (Index): A file that stores what will go into your next commit
  3. Git Directory (.git): Where Git stores metadata and object database
Working Directory → git add → Staging Area → git commit → Repository
                                  (staged)              (committed)

Git Configuration

# 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)

Basic Workflow

Starting a Repository

# 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

Making Changes

# 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 Objects: The Internal Model

Git stores everything as objects in a content-addressable filesystem. Understanding this helps you understand how Git really works.

Four Object Types

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

Refs and HEAD

Branches are Just Pointers

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

What is HEAD?

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

Special References

Branching and Merging

Understanding Branches

Branches are incredibly lightweight in Git - just a 41-byte file (40 char SHA-1 + newline). This makes branch creation and switching nearly instantaneous.

Working with Branches

# 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

Merging Strategies

1. Fast-Forward Merge

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
Example:
$ git checkout main
$ git merge feature-login
Updating f42c576..3a0874c
Fast-forward
 login.js | 10 ++++++++++
 1 file changed, 10 insertions(+)

2. Three-Way Merge

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
Example:
$ git checkout main
$ git merge feature-login
Merge made by the 'ort' strategy.
 login.js | 10 ++++++++++
 auth.js  | 5 +++++
 2 files changed, 15 insertions(+)

3. Squash Merge

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

Merge Conflicts

Conflicts occur when the same part of a file was changed differently in two branches.

Conflict markers:
<<<<<<< HEAD
function login(username, password) {
    return authenticateUser(username, password);
}
=======
function login(email, password) {
    return authenticateWithEmail(email, password);
}
>>>>>>> feature-login

Resolving Conflicts

# 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

Useful Tools

# 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

Rebase vs Merge

What is Rebasing?

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

How to Rebase

# 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

Merge vs Rebase: The Key Differences

Merge

  • Preserves history: Shows exactly what happened
  • Creates merge commits: Can clutter history
  • Non-destructive: Doesn't change existing commits
  • Safe: Can merge public branches
  • Traceability: Easy to see when features were integrated

Rebase

  • Rewrites history: Creates linear history
  • No merge commits: Cleaner history
  • Destructive: Changes commit SHAs
  • Dangerous: Never rebase public branches!
  • Linear: Appears as if work was done sequentially

The Golden Rule of Rebasing

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

When to Use Each

Use Merge When:

  • Working on a public/shared branch
  • You want to preserve complete history
  • Integrating a completed feature branch
  • You want to see when features were integrated
  • Working with others on the same branch
  • The branch has already been pushed

Use Rebase When:

  • Cleaning up local commits before pushing
  • Keeping a feature branch up to date with main
  • You want a linear, clean history
  • Working on a private feature branch
  • Incorporating upstream changes
  • Commits haven't been shared yet

Interactive Rebase

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
Common use cases:
# 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

Common Workflow: Feature Branch Rebase

# 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

Branching Strategies

1. Git Flow

A comprehensive branching model with specific branch types and rules. Popular for projects with scheduled releases.

Branch Types:

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

Workflow:

# 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

Pros:

  • Clear separation of concerns
  • Well-defined release process
  • Multiple versions in production supported
  • Good for scheduled releases

Cons:

  • Complex with many branches
  • Overkill for continuous deployment
  • More merge commits
  • Longer feedback loops

2. GitHub Flow

A simpler alternative to Git Flow, designed for continuous deployment. Popular at GitHub and many web companies.

Rules:

  1. main is always deployable
  2. Create descriptive feature branches from main
  3. Commit to that branch locally and push regularly
  4. Open a pull request at any time
  5. After review and approval, merge to main
  6. Deploy immediately after merging

Workflow:

# 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

Pros:

  • Simple and easy to understand
  • Perfect for continuous deployment
  • Fast feedback through pull requests
  • Only one main branch to worry about
  • Encourages small, frequent changes

Cons:

  • Assumes continuous deployment capability
  • Harder with scheduled releases
  • No explicit release branches
  • Requires good CI/CD pipeline

3. GitLab Flow

Combines GitHub Flow simplicity with environment branches. Good for projects with different environments.

With Environment Branches:

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

With Release Branches:

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

4. Trunk-Based Development

Developers merge small, frequent changes directly to main (the trunk). Extreme version of continuous integration.

Principles:

# 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

Pros:

  • Simplest possible workflow
  • Minimizes merge conflicts
  • Fast feedback
  • Encourages small changes
  • Good for high-performing teams

Cons:

  • Requires excellent CI/CD
  • Needs feature flags for incomplete work
  • Can be scary (committing to main)
  • Requires team discipline
  • Less code review without PRs

Choosing a Strategy

Consider:

GitHub Specifics

What is GitHub?

GitHub is a cloud-based hosting service for Git repositories. It adds:

Alternatives: GitLab, Bitbucket, Gitea, Azure DevOps

Remote Repositories

Understanding Remotes

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 vs Pull

# 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

# 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

Forking Workflow

Used for contributing to open source projects.

  1. Fork: Create your own copy of the repository on GitHub
  2. Clone: Clone your fork locally
  3. Add upstream: Add original repo as upstream remote
  4. Branch: Create feature branch
  5. Work: Make changes and commit
  6. Push: Push to your fork
  7. Pull Request: Request original repo to pull your changes
# 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

Pull requests (PRs) are GitHub's way to propose changes. They facilitate code review and discussion.

Best Practices:

PR Template Example:

## 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

Review Process:

# 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)

GitHub Actions (CI/CD)

Automate workflows directly in GitHub.

Example: Test on every push

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.

Advanced Topics

Cherry-Pick

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
Use case: Apply hotfix from release branch to main
# 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

Stashing

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

Tagging

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)

Reset, Revert, and Restore

Git Reset

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
Warning: git reset --hard permanently discards changes. Use with caution!

Git Revert

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

Git Restore

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

Reflog

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}
Recovery scenario:
# 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).

Git Bisect

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

Submodules

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.

Git Hooks

Scripts that run automatically on Git events. Located in .git/hooks/.

Common 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
Example pre-commit hook:

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

Best Practices

Commit Messages

Good commit messages are essential for understanding project history.

Conventional Commits Format:

type(scope): subject

body

footer

Types:

Good commit messages:
feat(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+.

Rules:

  1. Separate subject from body with a blank line
  2. Limit subject line to 50 characters
  3. Capitalize the subject line
  4. Don't end subject line with a period
  5. Use imperative mood ("Add feature" not "Added feature")
  6. Wrap body at 72 characters
  7. Use body to explain what and why, not how

When to Commit

Good Practices:

Good commit breakdown:
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
Avoid:
  • Committing broken code
  • Giant commits with unrelated changes
  • Commits like "fix", "wip", "stuff" (in shared branches)
  • Committing sensitive data (passwords, API keys)

Branch Naming Conventions

Common Patterns:

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

Rules:

.gitignore

Specify files that Git should ignore.

Example .gitignore:
# 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

Patterns:

# 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.

General Best Practices Summary

Common Git Workflows Summary

For Personal Projects:

1. Create feature branch
2. Make changes and commit
3. Push to GitHub
4. Merge to main when ready
5. Deploy

For Team Projects:

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

For Open Source Contributions:

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

Additional Resources