Git Advanced Workflows & Conflict Resolution in Practice

DevOps

Git Workflow Panoramic Comparison

Choosing the right Git workflow is the foundation of team collaboration. Teams of different sizes and release cadences need different branching strategies.

Three Mainstream Workflows

Workflow Branch Model Use Case Release Cadence
Git Flow main + develop + feature + release + hotfix Projects with explicit version numbers Planned releases (v1.0, v2.0)
GitHub Flow main + feature Continuously deployed Web/SaaS projects Continuous release
Trunk-Based main + short-lived feature (<1 day) Mature CI/CD teams Continuous integration + Feature Flags

Git Flow in Detail

Git Flow was proposed by Vincent Driessen and is the most classic branching model:

# Initialize Git Flow (requires git-flow installation)
git flow init

# Start feature development
git flow feature start user-auth
# → Creates feature/user-auth from develop

# Finish feature (auto-merges back to develop and deletes branch)
git flow feature finish user-auth

# Start release preparation
git flow release start v1.2.0
# → Creates release/v1.2.0 from develop

# Finish release (merges to main + develop, tags)
git flow release finish v1.2.0

# Emergency fix
git flow hotfix start critical-bug
# → Creates hotfix/critical-bug from main
git flow hotfix finish critical-bug
# → Merges to main + develop, tags

Branch Lifecycle Diagram:

main:      ●────────────────●────────●
            \              /        /
develop:    ●──●──●──●──●──●──●──●──●──●
            /      /        \     /
feature:   ●──●──●  release: ●──●  hotfix: ●──●

GitHub Flow in Detail

Minimalist model, ideal for continuous deployment:

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

# 2. Develop and commit
git add . && git commit -m "feat: add search component"

# 3. Push and create PR
git push -u origin feature/add-search

# 4. After Code Review approval, merge to main
# 5. Auto-deploy to production

Trunk-Based Development in Detail

Everyone integrates frequently on main (trunk), using Feature Flags to control incomplete features:

# Short-lived branch (must merge within <1 day)
git checkout -b fix/login-timeout main
git add . && git commit -m "fix: login timeout handling"
git push -u origin fix/login-timeout
# → Merge immediately after CI passes, delete branch

# Feature Flag example (controlled in code)
# if (flags.NEW_SEARCH_ENABLED) {
#   return newSearch(query);
# }
# return legacySearch(query);

Workflow Pros & Cons Comparison

Dimension Git Flow GitHub Flow Trunk-Based
Complexity High Low Medium
Parallel Development Excellent Good Excellent (requires Feature Flags)
Release Control Strong Weak Medium
Conflict Risk High (long-lived branches) Low Lowest
Rollback Difficulty Easy (hotfix branch) Easy (revert PR) Needs Feature Flags
CI/CD Friendliness Medium High Highest
Team Size Fit 5-50 people 2-20 people 10+ mature teams

💡 Use the Hash Tool to generate commit signatures, ensuring code integrity.


Interactive Rebase Step-by-Step

Interactive Rebase is Git's most powerful history rewriting tool. Mastering it means mastering the "time machine" for commit history.

Basic Syntax

# Interactive rebase of the last 3 commits
git rebase -i HEAD~3

# Interactive rebase after a specific commit
git rebase -i abc1234^

# The editor will show:
# pick f1a2b3c feat: add user model
# pick d4e5f6g fix: validation error
# pick h7i8j9k docs: update README

Squash: Combine Commits

Compress multiple small commits into one meaningful commit:

git rebase -i HEAD~3
# Change to:
# pick f1a2b3c feat: add user model
# squash d4e5f6g fix: validation error
# squash h7i8j9k docs: update README
# → 3 commits combined into 1, edit the combined commit message

Typical scenario: Combine "WIP", "fix typo", "fix again" etc. into a single meaningful record.

Fixup: Silent Combine

Similar to squash, but automatically discards the merged commit's message:

git rebase -i HEAD~3
# pick f1a2b3c feat: add user model
# fixup d4e5f6g fix: validation error
# fixup h7i8j9k fix: another fix
# → Only keeps the first commit's message

Faster approach — mark fixup at commit time:

git commit --fixup=f1a2b3c
# Then auto-rebase in one step:
git rebase -i --autosquash HEAD~3

Reorder: Adjust Commit Order

Simply rearrange lines in the editor:

git rebase -i HEAD~3
# Original order:
# pick f1a2b3c feat: add user model
# pick d4e5f6g feat: add user service
# pick h7i8j9k test: user model test
# Rearranged (test follows model):
# pick f1a2b3c feat: add user model
# pick h7i8j9k test: user model test
# pick d4e5f6g feat: add user service

Edit: Split a Commit

Split an oversized commit into multiple:

git rebase -i HEAD~2
# pick f1a2b3c feat: add user module (too large)
# edit d4e5f6g feat: add auth module

# Git pauses at the edit-marked commit
git reset HEAD~1
# Now all files are back in staging area, commit in batches
git add src/models/user.ts
git commit -m "feat: add user model"
git add src/services/user.ts
git commit -m "feat: add user service"
git rebase --continue

Mid-Rebase Operations

# Continue to the next commit
git rebase --continue

# Skip the current commit
git rebase --skip

# Abort the entire rebase, return to original state
git rebase --abort

⚠️ Golden Rule: Never rebase commits that have been pushed to a remote shared branch! This will corrupt others' history.


Merge vs Rebase Deep Comparison

This is the most classic debate in the Git community. Both have their use cases.

Visual Comparison

Merge — preserves full history, creates a merge commit:

main:     ●──●──●──M──●
               \   /
feature:        ●──●

Rebase — linear history, no merge commit:

main:     ●──●──●──●'──●'
                     ↑ feature commits replayed after main

When to Use Merge

# Merge feature branch into main (preserve branch history)
git checkout main
git merge --no-ff feature/user-auth
# --no-ff disables fast-forward merge, ensures merge commit is created

Use cases:

  • Merging feature branches into main/develop
  • Need to preserve trace of branch existence
  • Team requires complete project history

When to Use Rebase

# Sync main updates onto feature branch
git checkout feature/user-auth
git rebase main
# → feature commits are "appended after" main's latest commit

Use cases:

  • Syncing upstream branch updates to local feature branch
  • Cleaning up local fragmented commits
  • Keeping commit history linear and readable

Comparison Summary

Dimension Merge Rebase
History Preserves real branch history Rewrites as linear history
Merge Commits Yes (potentially many) None
Readability Complex branch graph Clean linear
Safety Safe (doesn't modify history) Dangerous (rewrites pushed commits)
Conflict Handling Resolve all at once Resolve per commit
Rollback git revert -m Requires rebase or reset
Recommended For Merging into main branch Syncing upstream updates

Practical Recommendation

# Recommended team workflow combination:
# 1. During feature development: use rebase to sync develop
git checkout feature/x
git fetch origin
git rebase origin/develop

# 2. When feature is done: use merge (--no-ff) back to develop
git checkout develop
git merge --no-ff feature/x

# 3. develop to main: use merge (--no-ff)
git checkout main
git merge --no-ff develop

Conflict Resolution Strategies

Conflicts are inevitable in collaborative development. Systematic resolution strategies are far more efficient than "guessing".

3-Way Merge Principle

Git uses the three-way merge algorithm:

      Base (common ancestor)
       /    \
  Ours        Theirs
       \    /
      Merge Result
  1. Find the common ancestor (Base) of both branches
  2. Compare Ours vs Base, Theirs vs Base
  3. Only one side modified → auto-merge
  4. Both sides modified the same line → conflict, requires manual resolution
# See which files have conflicts
git diff --name-only --diff-filter=U

# See detailed conflict content
git diff

Manual Conflict Resolution

Conflict marker format:

<<<<<<< HEAD
Content from current branch
=======
Content from merging branch
>>>>>>> feature/other-branch

Resolution steps:

# 1. Open conflicted files, edit manually
# 2. Remove conflict markers, keep correct content
# 3. Mark as resolved
git add <conflicted-file>

# 4. Complete the merge
git commit
# Or during rebase:
git rebase --continue

Using Merge Tools

# Use VS Code as merge tool
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

# Use vimdiff
git config --global merge.tool vimdiff

# Launch merge tool
git mergetool

git rerere: Auto-Remember Conflict Resolutions

rerere (Reuse Recorded Resolution) lets Git remember how you resolved similar conflicts, auto-applying next time:

# Enable rerere globally
git config --global rerere.enabled true

# Or enable in a specific repo
cd your-repo
git config rerere.enabled true

How it works:

  1. First encounter → manual resolution → Git records the solution
  2. Same conflict again → auto-applies previous solution
  3. If auto-resolution is wrong → git rerere forget to delete the memory
# View recorded conflict resolutions
git rerere status

# Forget the recorded resolution for current conflict
git rerere forget

# Manually trigger replay
git rerere

Typical scenario: Long-lived feature branch repeatedly merged with develop, same conflicts keep appearing.

Conflict Resolution Strategy Selection

Strategy Use Case Command
Manual Edit Few conflicts, need careful judgment Edit files directly
Merge Tool Many conflicts, need visualization git mergetool
Ours Strategy Always use our version git merge -X ours
Theirs Strategy Always use their version git merge -X theirs
rerere Repeated similar conflicts git config rerere.enabled true

💡 Use the JSON Formatter tool to tidy up package.json after conflict resolution.


Cherry-Pick and Its Pitfalls

Cherry-pick lets you "pick" a specific commit onto the current branch without merging the entire branch.

Basic Usage

# Pick a single commit
git cherry-pick abc1234

# Pick multiple commits
git cherry-pick abc1234 def5678

# Pick a range (excludes start, includes end)
git cherry-pick start..end

# Pick a range (includes both start and end)
git cherry-pick start^..end

Common Pitfalls

Pitfall 1: Duplicate Commits

# Commit abc1234 on feature was already merged to main
# Later cherry-pick the same commit
git cherry-pick abc1234
# → Creates a new commit (different SHA) with same content
# → Git sees two different commits, future merge may produce empty merge or conflict

Pitfall 2: Missing Dependencies

# Commit B depends on Commit A's changes
# Only cherry-pick B without picking A
git cherry-pick B
# → May fail to compile or introduce bugs

Pitfall 3: Conflict Handling

# cherry-pick encounters conflict
git cherry-pick abc1234
# CONFLICT!

# Resolve conflict and continue
git add .
git cherry-pick --continue

# Or abort this cherry-pick
git cherry-pick --abort

Safe Cherry-Pick Principles

  1. Only cherry-pick to branches you don't plan to merge (e.g., hotfix to release)
  2. Ensure picked commits are self-contained (don't depend on other unpicked commits)
  3. Record cherry-picked commits to avoid duplication
  4. Prefer merge or rebase; cherry-pick is a last resort

Typical Scenario: Hotfix Sync

# Urgent fix on main
git checkout main
git cherry-pick hotfix-commit-sha

# Sync to develop
git checkout develop
git cherry-pick hotfix-commit-sha

# Sync to other release branches
git checkout release/v1.1
git cherry-pick hotfix-commit-sha

Bisect: Binary Search for Bugs

git bisect uses binary search to find the commit that introduced a bug, exponentially faster than linear checking.

Basic Flow

# 1. Start binary search
git bisect start

# 2. Mark current version as "bad"
git bisect bad

# 3. Mark a known good version
git bisect good v1.0.0

# Git auto-checkouts the middle commit
# Test this version...

# 4. Mark the middle commit
git bisect good  # If this version is fine
git bisect bad   # If this version has the bug

# 5. Repeat step 4 until Git finds the first bad commit
# → Git shows: abc1234 is the first bad commit

# 6. End binary search
git bisect reset

Automated Bisect

Use a script to automatically judge each commit:

# Use test script for automatic judgment
git bisect start HEAD v1.0.0 --
git bisect run npm test

# Use custom script
git bisect run ./scripts/bisect-test.sh

bisect-test.sh example:

#!/bin/bash
npm build || exit 125   # Skip commits that can't build
npm test
# exit 0 = good, exit 1 = bad, exit 125 = skip

Bisect Efficiency

For a range of 1000 commits, bisect needs at most 10 iterations (log₂(1000) ≈ 10) to locate the problematic commit, while linear search could take up to 1000.


Worktree: Parallel Work Without Branch Switching

git worktree lets you check out multiple branches into different directories in the same repo, without stash or clone.

Basic Usage

# Add a worktree
git worktree add ../feature-x feature/x
# → Checks out feature/x in ../feature-x directory

# Add a worktree with a new branch
git worktree add -b feature/y ../feature-y main
# → Creates feature/y branch and checks out in ../feature-y

# List all worktrees
git worktree list

# Remove a worktree
git worktree remove ../feature-x

# Force remove (when there are uncommitted changes)
git worktree remove --force ../feature-x

Typical Scenarios

# Scenario 1: Working on two features simultaneously
git worktree add ../feature-a feature/a
git worktree add ../feature-b feature/b
# → Two directories don't interfere, can open in different IDEs

# Scenario 2: Urgent fix without interrupting current work
git worktree add -b hotfix/urgent ../hotfix main
cd ../hotfix
# → Fix, commit, push
cd ../main-project
git worktree remove ../hotfix

# Scenario 3: Long-running tests
git worktree add ../test-branch test-env
# → Run tests in test-branch, continue dev in main directory

Worktree Notes

  • The same branch cannot be checked out in two worktrees simultaneously
  • Ensure changes are committed or saved before removing a worktree
  • Worktrees share the same .git directory, saving disk space

Stash Advanced Usage

git stash temporarily saves uncommitted changes, the best way to "pause current work".

Basic Operations

# Save all changes (tracked file modifications and staging)
git stash

# Save with a description
git stash push -m "WIP: user auth refactor"

# View all stashes
git stash list

# Restore the latest stash
git stash pop

# Restore without deleting the stash
git stash apply

# Restore a specific stash
git stash apply stash@{2}

Advanced Techniques

# Stash only certain files
git stash push -m "partial" src/auth.ts src/auth.test.ts

# Stash untracked files (new files)
git stash -u

# Stash all files (including ignored files)
git stash -a

# Stash only staged content
git stash --staged

# View contents of a specific stash
git stash show -p stash@{0}

# Create a branch from a stash
git stash branch feature/from-stash stash@{0}

Managing Multiple Stashes

# Scenario: multiple stashes accumulated
git stash list
# stash@{0}: On feature/x: WIP: refactor
# stash@{1}: On feature/x: WIP: fix tests
# stash@{2}: On main: hotfix draft

# Delete a specific stash
git stash drop stash@{1}

# Clear all stashes
git stash clear

Stash and Conflicts

# When pop/apply encounters conflict
git stash pop
# CONFLICT!

# After resolving conflict, manually drop the stash
git stash drop

# Or abort the pop
git checkout -- .
git stash pop  # retry

Git Hooks Automation

Git Hooks are scripts that execute automatically when specific Git events are triggered, a powerful tool for local automation.

Common Hooks Location

# Hooks are stored in .git/hooks/ directory
ls .git/hooks/
# applypatch-msg.sample
# pre-commit.sample
# commit-msg.sample
# ...

pre-commit: Pre-Commit Checks

# Create pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash

# Check for console.log
if git diff --cached | grep -q "console.log"; then
  echo "❌ Found console.log, please remove before committing"
  exit 1
fi

# Run lint
npm run lint
if [ $? -ne 0 ]; then
  echo "❌ Lint check failed"
  exit 1
fi

# Run type check
npm run typecheck
if [ $? -ne 0 ]; then
  echo "❌ Type check failed"
  exit 1
fi

exit 0
EOF
chmod +x .git/hooks/pre-commit

commit-msg: Enforce Commit Message Convention

cat > .git/hooks/commit-msg << 'EOF'
#!/bin/bash
COMMIT_MSG=$(cat "$1")

# Validate Conventional Commits format
PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,100}"

if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
  echo "❌ Commit message doesn't follow Conventional Commits"
  echo "Format: type(scope): description"
  echo "Example: feat(auth): add login page"
  exit 1
fi

exit 0
EOF
chmod +x .git/hooks/commit-msg

Managing hooks manually isn't easy to share across teams. Use tools instead:

# Install Husky
npm install husky -D
npx husky init

# Add pre-commit hook
echo "npx lint-staged" > .husky/pre-commit

# Configure lint-staged (package.json)
# "lint-staged": {
#   "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
#   "*.{json,md}": ["prettier --write"]
# }

# Add commit-msg hook
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg

Other Useful Hooks

Hook Trigger Typical Use
pre-push Before git push Run full test suite
post-merge After git pull merge Auto npm install
post-checkout After branch switch Switch node_modules
prepare-commit-msg Before editing commit message Auto-add issue number

Git LFS: Large File Storage

Git handles binary large files (images, videos, models) inefficiently. Git LFS solves this.

Installation & Initialization

# Install Git LFS
git lfs install

# Track large file types
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "*.model"
git lfs track "datasets/**"

# View tracking rules
git lfs track

# Commit .gitattributes (tracking rules must be versioned)
git add .gitattributes
git commit -m "chore: configure Git LFS"

Daily Usage

# Normal add/commit, LFS handles automatically
git add large-file.psd
git commit -m "feat: add design file"

# View LFS-stored files
git lfs ls-files

# View LFS storage usage
git lfs ls-files --size

# Pull LFS objects
git lfs pull

# Pull LFS objects for specific files only
git lfs fetch --include="*.psd"

Migrating Existing Files to LFS

# Migrate tracked .psd files to LFS
git lfs migrate import --include="*.psd"

# Migrate large files in all history (rewrites history!)
git lfs migrate import --include="*.psd" --everything

LFS Notes

  • Requires server support (GitHub/GitLab both support it, with quota limits)
  • .gitattributes must be committed to the repo
  • Clone doesn't download LFS objects by default (needs git lfs pull)
  • LFS has storage and bandwidth quotas; watch costs

Monorepo Strategies

Monorepo places multiple projects in the same repository, making it easier to share code and manage uniformly.

Directory Structure

monorepo/
├── apps/
│   ├── web/          # Frontend app
│   ├── mobile/       # Mobile app
│   └── api/          # Backend service
├── packages/
│   ├── ui/           # Shared UI components
│   ├── utils/        # Shared utility functions
│   └── config/       # Shared configuration
├── package.json
├── turbo.json        # Turborepo config
└── pnpm-workspace.yaml

Sparse Checkout

Only check out needed directories, speeding up clone:

# Enable sparse checkout
git sparse-checkout init --cone

# Only check out apps/web and packages/ui
git sparse-checkout set apps/web packages/ui

# Add more directories
git sparse-checkout add apps/api

# View currently checked-out directories
git sparse-checkout list

Shallow Clone

Only fetch recent commit history:

# Only fetch the latest 1 commit
git clone --depth=1 https://github.com/org/monorepo.git

# Fetch the latest 10 commits
git clone --depth=10 https://github.com/org/monorepo.git

# Fetch more history later
git fetch --unshallow

Monorepo Tool Choices

Tool Language Core Feature
Turborepo JS/TS Incremental builds, remote caching, Pipeline
Nx JS/TS Dependency graph analysis, affected project detection
Lerna JS/TS Version management, publish workflow
Bazel Multi-language Google-grade incremental builds
PNPM Workspace JS/TS Native monorepo support

Common Errors & Rescue

Detached HEAD

# Accidentally entered detached HEAD state
git checkout abc1234
# → HEAD detached at abc1234

# Check current state
git status
# → HEAD detached at abc1234

# Rescue option 1: Create new branch to preserve
git checkout -b feature/from-detached

# Rescue option 2: If you have uncommitted changes
git stash
git checkout main
git stash pop

Force Push Recovery

# Someone force-pushed and overwrote the remote branch
# 1. Use reflog to find the overwritten commit
git reflog
# abc1234 HEAD@{0}: checkout: moving from main to feature/x
# def5678 HEAD@{1}: commit: feat: important feature

# 2. Restore to the specific commit
git checkout def5678
git checkout -b feature/x-recovered
git push -f origin feature/x-recovered:feature/x

Reflog: The Ultimate Rescue Tool

Reflog records every movement of HEAD. Even "deleted" commits can be recovered:

# View full reflog
git reflog

# View reflog for a specific branch
git reflog show feature/x

# View the last 10 entries
git reflog -10

# Restore to a reflog entry
git reset --hard HEAD@{5}

# Recover commits lost by reset
git reflog
# Find the commit SHA before the reset
git cherry-pick <lost-commit-sha>

Reflog retention time:

[gc]
    reflogExpire = 90.days
    reflogExpireUnreachable = 30.days

Accidentally Committed Large File

# 1. Completely remove large file from history
git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch path/to/large-file.mp4' \
  --prune-empty -- --all

# 2. Or use the faster git-filter-repo
pip install git-filter-repo
git filter-repo --path path/to/large-file.mp4 --invert-paths

# 3. Force push to update remote
git push --force --all

# 4. Clean up locally
git reflog expire --expire=now --all
git gc --prune=now --aggressive

.gitignore Best Practices

Layered Strategy

# Global ignore (~/.gitignore_global)
# OS files
.DS_Store
Thumbs.db
Desktop.ini

# IDE
.idea/
.vscode/
*.swp

# Environment variables
.env
.env.local

Project-Level .gitignore

# Dependencies
node_modules/
vendor/
.venv/

# Build output
dist/
build/
*.egg-info/

# Logs
*.log
logs/

# Test coverage
coverage/
.nyc_output/

# Temporary files
*.tmp
*.temp
.cache/

# Large files (use LFS instead of ignoring)
# *.psd  → use LFS tracking, not ignore

Ignore Rule Syntax

# Ignore all .log files
*.log

# But keep important.log
!important.log

# Ignore all contents under build directory
build/**

# Ignore TODO in root only, not in subdirectories
/TODO

# Ignore all .pdf under doc directory
doc/**/*.pdf

# Ignore __pycache__ in all directories
__pycache__/

Common Mistakes

# ❌ Wrong: Adding already-tracked files to .gitignore has no effect

# ✅ Correct: Untrack first, then ignore
git rm --cached debug.log
# Then add to .gitignore
echo "debug.log" >> .gitignore
git add .gitignore
git commit -m "chore: ignore debug.log"

Use gitignore.io to generate project templates:

# Generate ignore rules for Node + VSCode + macOS
curl -sL https://www.gitignore.io/api/node,visualstudiocode,macos > .gitignore

💡 Use the Base64 Encode tool to encode sensitive information in .env files for transmission.


FAQ

Q1: Too many conflicts during rebase, what to do?

Options:

  1. git rebase --abort and use git merge instead
  2. Rebase from upstream frequently to reduce accumulated differences
  3. Split large features into smaller ones to reduce rebase scope

Q2: How to undo a pushed commit?

# Option 1: revert (safe, creates new commit)
git revert abc1234
git push

# Option 2: reset + force push (dangerous, rewrites history)
git reset --hard abc1234^
git push --force
# ⚠️ Only use when sure no one else is based on this commit

Q3: How to view modification history of a specific file?

# View file modification history
git log --follow -p src/auth/login.ts

# See who modified each line
git blame src/auth/login.ts

# View file content at a specific commit
git show abc1234:src/auth/login.ts

Q4: How to compare differences between two branches?

# View file differences between branches
git diff main..feature/x

# View commit differences between branches
git log main..feature/x --oneline

# View statistics between branches
git diff main..feature/x --stat

Q5: How to clean up local references to deleted remote branches?

# Prune remote branch references that no longer exist
git remote prune origin

# Or auto-prune during fetch
git fetch -p
# Or configure auto-pruning
git config --global fetch.prune true

Q6: How to revert a merge commit?

# Reverting a merge commit requires specifying which parent to keep
# -m 1: keep first parent (usually main)
git revert -m 1 <merge-commit-sha>

Q7: How to make Git ignore file permission changes?

git config core.fileMode false

Q8: How to speed up cloning of large repositories?

# Combined strategy: shallow clone + sparse checkout + single branch
git clone --depth=1 --single-branch --filter=blob:none \
  https://github.com/org/large-repo.git

Try these browser-local tools — no sign-up required →

#Git#工作流#冲突解决#Rebase#教程