35 min read

The Complete Git Workflow Guide: From Beginner to Advanced

Everything you need to know about Git: from your first commit to advanced rebasing, branching strategies used by top engineering teams, and the commands that will save you when things go wrong.

Table of Contents

Git Fundamentals

Git is a distributed version control system. Unlike centralized systems (SVN, Perforce), every developer has a complete copy of the repository history on their local machine. This means you can commit, branch, merge, and view history without a network connection. Understanding this distributed nature is key to understanding why Git works the way it does.

The Three Areas

Git manages your code across three areas. Understanding these is the single most important concept for Git proficiency:

BASH
# Initialize a new repository
git init

# Check the status of all three areas
git status

# Move changes from Working Directory → Staging Area
git add file.js              # stage a specific file
git add src/                # stage an entire directory
git add -p                   # interactively stage hunks

# Move changes from Staging Area → Repository
git commit -m "Add user authentication"

# View commit history
git log --oneline --graph

# View changes in working directory (unstaged)
git diff

# View changes in staging area (staged, ready to commit)
git diff --staged

Configuring Git

Before your first commit, configure your identity. These settings are stored in ~/.gitconfig (global) or .git/config (per-repo).

BASH
# Required: your name and email (used in commit metadata)
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

# Recommended: default branch name
git config --global init.defaultBranch main

# Recommended: auto-setup rebase on pull
git config --global pull.rebase true

# Recommended: colorized diff output
git config --global core.pager "delta"

# Set default editor
git config --global core.editor "code --wait"

# Useful aliases
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.st status
git config --global alias.lg "log --oneline --graph --all"

The .gitignore File

A .gitignore file tells Git which files and directories to ignore. This is essential for keeping build artifacts, dependencies, secrets, and OS-specific files out of your repository.

.gitignore
# Dependencies
node_modules/
vendor/
.venv/

# Build output
dist/
build/
.next/

# Environment & secrets
.env
.env.local
*.pem

# OS files
.DS_Store
Thumbs.db

# IDE
.idea/
.vscode/
*.swp

# Logs
*.log
npm-debug.log*
🛠
NexTool .gitignore Generator
Use the NexTool .gitignore generator to create a comprehensive .gitignore file for your project's tech stack. Select your languages, frameworks, and IDEs, and get a production-ready file instantly.

Branching Strategies

How your team manages branches determines your development velocity, release stability, and merge conflict frequency. There is no universally best strategy. The right choice depends on your team size, release cadence, and infrastructure.

1. Git Flow

Git Flow uses long-lived main and develop branches with short-lived feature, release, and hotfix branches. It was designed for projects with scheduled releases and is well-suited for mobile apps, enterprise software, and anything with a formal release process.

Git Flow Branch Diagram
main
release
develop
feature
hotfix
BASH
# Start a feature
git checkout develop
git checkout -b feature/user-auth

# Work on the feature, commit regularly
git add . && git commit -m "Add login form component"
git add . && git commit -m "Add JWT token validation"

# Finish feature: merge back to develop
git checkout develop
git merge --no-ff feature/user-auth
git branch -d feature/user-auth

# Create a release
git checkout -b release/1.2.0 develop
# ... fix bugs, bump version ...
git checkout main && git merge --no-ff release/1.2.0
git tag -a v1.2.0 -m "Release 1.2.0"
git checkout develop && git merge --no-ff release/1.2.0

# Emergency hotfix
git checkout -b hotfix/critical-fix main
# ... fix the issue ...
git checkout main && git merge --no-ff hotfix/critical-fix
git checkout develop && git merge --no-ff hotfix/critical-fix

2. GitHub Flow

GitHub Flow is simpler: one main branch that is always deployable, plus short-lived feature branches. Every change goes through a pull request and code review. After merging, you deploy. This works well for web applications with continuous deployment, small teams, and projects that ship multiple times per day.

BASH
# The entire GitHub Flow workflow:

# 1. Create a branch from main
git checkout main
git pull origin main
git checkout -b feature/add-search

# 2. Make commits
git add . && git commit -m "Add search component"

# 3. Push and open a Pull Request
git push -u origin feature/add-search
gh pr create --title "Add search functionality"

# 4. Review, discuss, iterate
# 5. Merge via GitHub UI (squash merge recommended)
# 6. Deploy automatically from main

3. Trunk-Based Development

Trunk-Based Development (TBD) has developers commit directly to main (or use very short-lived branches that last at most one to two days). It requires robust CI/CD, feature flags for incomplete work, and high test coverage. Used by Google, Meta, and most large-scale tech companies. It eliminates merge conflicts and long-lived branch divergence.

BASH
# Trunk-Based: short-lived branches (max 1-2 days)

# Pull latest main
git checkout main && git pull

# Create a short-lived branch
git checkout -b short/add-button

# Make a small, focused change
git add . && git commit -m "Add submit button to form"

# Push and merge same day
git push -u origin short/add-button
# PR → Review → Merge → Delete branch

# Feature flags for incomplete features:
# if (featureFlags.newSearch) { showNewSearch() }

Strategy Comparison

Criteria Git Flow GitHub Flow Trunk-Based
Complexity High Low Low (process), High (infra)
Release cadence Scheduled Continuous Continuous
Merge conflicts Frequent Moderate Rare
Team size Large teams Small-medium Any size
Requires feature flags No Optional Yes
Best for Mobile, enterprise Web apps, startups High-velocity teams

Merge vs Rebase

This is the most debated topic in Git. Both merge and rebase integrate changes from one branch into another, but they do it differently, and the choice has real implications for your team's workflow.

Merge: Preserves History

git merge creates a new "merge commit" that ties two branch histories together. The original commit history of both branches is preserved exactly as it happened. This is the safest option because it never rewrites history.

BASH
# Merge feature branch into main
git checkout main
git merge feature/add-search

# --no-ff forces a merge commit even if fast-forward is possible
git merge --no-ff feature/add-search

Rebase: Linear History

git rebase replays your branch's commits on top of another branch, creating new commits with the same changes but different hashes. The result is a clean, linear history. The trade-off: it rewrites history, which can cause problems if the branch has already been shared.

BASH
# Rebase feature branch onto latest main
git checkout feature/add-search
git rebase main

# Interactive rebase to clean up commits before merging
git rebase -i main
# In the editor, you can:
# pick   - keep the commit
# squash - combine with previous commit
# reword - change the commit message
# drop   - remove the commit entirely
# edit   - pause to amend the commit
The Golden Rule of Rebase
Never rebase commits that have been pushed to a shared branch. Rebasing rewrites commit hashes, which will cause conflicts for anyone who has already pulled those commits. Only rebase local, unshared commits.

Recommended Approach

The most common professional workflow combines both: rebase locally, merge publicly. Before opening a PR, rebase your feature branch onto the latest main to get a clean, up-to-date set of commits. Then merge (or squash-merge) the PR via the GitHub/GitLab UI.

BASH
# Before opening a PR: rebase onto latest main
git checkout feature/my-feature
git fetch origin
git rebase origin/main

# If conflicts, resolve them, then:
git add .
git rebase --continue

# Force push (safe because only you work on this branch)
git push --force-with-lease

Conflict Resolution

Merge conflicts happen when two branches modify the same lines of the same file. Git cannot automatically determine which change to keep, so it marks the conflict and asks you to resolve it manually. The key is to not panic. Conflicts are normal and resolvable.

CONFLICT MARKERS
// This is what a conflict looks like in your file:

<<<<<<< HEAD
const theme = 'dark';
=======
const theme = 'light';
>>>>>>> feature/light-mode

// HEAD = your current branch's version
// feature/light-mode = the incoming branch's version
// You choose: keep one, keep both, or write something new

Resolution Steps

BASH
# 1. See which files have conflicts
git status

# 2. Open the conflicted file, resolve manually
#    Remove the <<<<<<, =======, >>>>>>> markers
#    Keep the correct code

# 3. Mark as resolved by staging
git add conflicted-file.js

# 4. Continue the merge or rebase
git merge --continue    # if merging
git rebase --continue   # if rebasing

# If it gets messy, abort and start over
git merge --abort
git rebase --abort

# Use a visual merge tool
git mergetool
💡
Preventing Conflicts
The best conflict resolution is conflict prevention. Keep branches short-lived (1-2 days max), rebase onto main frequently, break large changes into smaller PRs, and coordinate with teammates when working on the same files.

Git Hooks

Git hooks are scripts that run automatically at specific points in the Git workflow: before a commit, before a push, after a merge, and more. They are the enforcement mechanism for code quality standards.

BASH
# Hooks live in .git/hooks/ (local, not committed)
# Use Husky to manage hooks via package.json (committed)

# Install Husky
npx husky init

# Create a pre-commit hook that runs linting
echo "npx lint-staged" > .husky/pre-commit

# Create a commit-msg hook for conventional commits
echo "npx commitlint --edit \$1" > .husky/commit-msg

Common Hook Types

BASH - .husky/pre-commit
#!/bin/sh

# Run lint-staged (only checks staged files)
npx lint-staged

# Run type checking
npx tsc --noEmit
JSON - package.json (lint-staged config)
{
  "lint-staged": {
    "*.{js,ts,jsx,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.css": [
      "stylelint --fix",
      "prettier --write"
    ],
    "*.md": [
      "prettier --write"
    ]
  }
}

Advanced Git Commands

These are the commands that separate Git beginners from Git power users. Each one solves a specific problem that you will inevitably encounter.

git stash -- Save Work Without Committing

BASH
# Stash current changes (both staged and unstaged)
git stash

# Stash with a descriptive name
git stash push -m "WIP: auth refactor"

# Include untracked files
git stash -u

# List all 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 a specific stash
git stash apply stash@{2}

# Create a branch from a stash
git stash branch new-branch-from-stash

git cherry-pick -- Copy Specific Commits

BASH
# Apply a specific commit to current branch
git cherry-pick abc123

# Cherry-pick a range of commits
git cherry-pick abc123..def456

# Cherry-pick without committing (stage only)
git cherry-pick --no-commit abc123

# Use case: backport a fix to a release branch
git checkout release/1.0
git cherry-pick abc123  # the bugfix commit from main

git bisect -- Find the Bug-Introducing Commit

BASH
# Start bisect
git bisect start

# Mark current commit as bad (has the bug)
git bisect bad

# Mark a known good commit (before the bug existed)
git bisect good v1.0.0

# Git checks out a midpoint commit. Test it, then:
git bisect good    # if this commit is fine
git bisect bad     # if this commit has the bug

# Repeat until Git identifies the exact bad commit
# Output: "abc123 is the first bad commit"

# Automated bisect with a test script
git bisect run npm test

# Reset when done
git bisect reset

git reflog -- Your Safety Net

git reflog is the most important recovery tool in Git. It records every time HEAD changes, even across resets, rebases, and checkouts. If you accidentally lose commits, reflog has them.

BASH
# View reflog (recent HEAD movements)
git reflog
abc1234 HEAD@{0}: commit: Add new feature
def5678 HEAD@{1}: rebase: fast-forward
ghi9012 HEAD@{2}: checkout: moving from main to feature
jkl3456 HEAD@{3}: reset: moving to HEAD~3

# Recover a "lost" commit after a bad rebase/reset
git checkout -b recovery-branch HEAD@{3}

# Or reset current branch to a previous state
git reset --hard HEAD@{3}
💡
Reflog Saves Lives
If you ever think you have lost work in Git, check the reflog first. Commits are not actually deleted for at least 30 days (the garbage collection grace period). The reflog is how you find them.

git worktree -- Multiple Working Directories

BASH
# Create a worktree for a different branch (no stash needed!)
git worktree add ../project-hotfix hotfix/urgent-fix

# Work on the hotfix in ../project-hotfix
# while your main work stays untouched in the original directory

# List worktrees
git worktree list

# Remove when done
git worktree remove ../project-hotfix

Common Mistakes and How to Fix Them

Mistake
Committed to the wrong branch
You made commits on main when you meant to commit on a feature branch.
Fix
BASH
# Create the branch you wanted (it will have your commits)
git branch feature/my-feature

# Reset main back to before your commits
git reset --hard origin/main

# Switch to your feature branch
git checkout feature/my-feature
Mistake
Committed a file with secrets
You accidentally committed a .env file or API key.
Fix
BASH
# If not pushed yet: amend the commit
git rm --cached .env
echo ".env" >> .gitignore
git add .gitignore
git commit --amend

# If already pushed: rewrite history (WARNING: rewrites all history)
git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch .env" \
  --prune-empty --tag-name-filter cat -- --all

# IMPORTANT: Rotate the exposed secrets immediately!
Mistake
Need to undo the last commit
You committed too early or included the wrong files.
Fix
BASH
# Undo commit, keep changes staged
git reset --soft HEAD~1

# Undo commit, keep changes unstaged
git reset HEAD~1

# Undo commit AND discard changes (DANGEROUS)
git reset --hard HEAD~1

# If already pushed: create a revert commit (safe)
git revert HEAD
Mistake
Detached HEAD state
You checked out a commit hash or tag and now you are in "detached HEAD" state.
Fix
BASH
# If you made commits in detached HEAD and want to keep them:
git checkout -b save-my-work

# If you just want to go back to a branch:
git checkout main
Mistake
Accidentally ran git reset --hard
You lost commits by resetting too far back.
Fix
BASH
# Reflog to the rescue
git reflog
# Find the commit hash before the reset
git reset --hard HEAD@{1}  # or the specific hash

Collaboration Best Practices

Write Good Commit Messages

Commit messages are not just for you right now. They are for your teammates trying to understand a change six months from now, and for your future self running git blame on a confusing line.

CONVENTIONAL COMMITS
# Format: type(scope): description

feat(auth): add OAuth2 login with Google provider
fix(api): handle null response from payment gateway
docs(readme): update installation instructions for v2
refactor(database): replace raw SQL with query builder
test(cart): add integration tests for checkout flow
chore(deps): update React from 19.0 to 19.1
perf(images): add lazy loading to product gallery

# Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore

Pull Request Etiquette

Protecting Main

BASH
# Branch protection rules (configure in GitHub/GitLab settings):
# - Require pull request reviews (1-2 approvals)
# - Require status checks to pass (CI/CD must be green)
# - Require branches to be up to date before merging
# - Require signed commits (optional, for high-security projects)
# - Restrict force push to main

# Using GitHub CLI to set up branch protection:
gh api repos/{owner}/{repo}/branches/main/protection \
  --method PUT \
  -f "required_pull_request_reviews[required_approving_review_count]=1" \
  -F "enforce_admins=true"

Keeping a Clean History

BASH
# Squash merge: combine all feature commits into one clean commit
git merge --squash feature/my-feature
git commit -m "feat(auth): add OAuth2 login with Google"

# Interactive rebase before PR to clean up messy commits
git rebase -i HEAD~5
# squash "wip" and "fix typo" commits into meaningful ones

# Amend the last commit message
git commit --amend -m "Better commit message"

# Add forgotten file to last commit
git add forgotten-file.js
git commit --amend --no-edit

Tools and Resources

Git CLI Enhancements

BASH
# Install delta (beautiful diffs)
brew install git-delta
git config --global core.pager delta

# Install lazygit (terminal UI)
brew install lazygit

# Install GitHub CLI
brew install gh
gh auth login
🛠
NexTool Git Tools
Use the NexTool .gitignore generator and Git command builder for quick reference and template generation. Available free at nextool.app/free-tools.

Quick Reference: Essential Commands

Task Command
Undo last commit (keep changes) git reset --soft HEAD~1
Discard all local changes git checkout -- .
See who changed a line git blame file.js
Find when a bug was introduced git bisect start
Save work without committing git stash
Copy a specific commit git cherry-pick <hash>
Recover lost commits git reflog
Clean up local branches git branch --merged | xargs git branch -d
Search commit messages git log --grep="keyword"
Search code changes git log -S "function_name"
Show changes in a commit git show <hash>
Compare two branches git diff main..feature

Final Thoughts

Git is a tool that rewards depth of knowledge. You can use it productively with just add, commit, push, and pull, but understanding the full toolkit transforms how you work. You stop fearing experiments because you know you can always recover. You stop dreading merge conflicts because you have a systematic process. And you stop wasting time because your tools work with you, not against you.

The commands and workflows in this guide represent real-world practices used by professional engineering teams. Start with the basics, adopt a branching strategy that fits your team, and gradually integrate the advanced techniques as you encounter the problems they solve. Git mastery is a career-long investment that pays dividends every day.

Build Better, Ship Faster

Generate .gitignore files, build Git commands visually, and access 20+ free developer tools at NexTool.

Explore Free Tools Get Ultimate Bundle