Common Git Collaboration Problems and Fixes
Table of Content
When working on a team, it’s easy to run into Git pitfalls: unresolvable conflicts, accidentally overwriting someone else’s code, a messy commit history… This article covers the most common problems in team development and practical solutions.
Scenario 1: How to Handle Merge Conflicts
When multiple people edit the same file, you’ll run into conflicts during git pull or git merge. This is the most common issue in team development.
Case 1: Conflict During Pull
When you run git pull and get a conflict:
$ git pull
Auto-merging src/utils.js
CONFLICT (content): Merge conflict in src/utils.js
Automatic merge failed; fix conflicts and then commit the result.
Steps to resolve:
- Check which files are conflicted
git status
Files marked as both modified are the ones with conflicts.
- Open the conflicted files and resolve manually
Conflicted sections are marked like this:
<<<<<<< HEAD
function calculate(a, b) {
return a + b;
}
=======
function calculate(x, y) {
return x * y;
}
>>>>>>> origin/main
- The section between
<<<<<<< HEADand=======is your local changes - The section between
=======and>>>>>>> origin/mainis the remote changes
Edit manually, keep the code you want, and remove the conflict markers.
- Mark the conflict as resolved and commit
git add src/utils.js
git commit -m "Resolve conflict: merge calculate function"
git push
Case 2: Discard Local Changes and Use the Remote Version
# Abort the merge, return to the state before the conflict
git merge --abort
# Forcefully overwrite local with remote
git fetch origin
git reset --hard origin/main
Warning: reset --hard will discard all uncommitted local changes. Use with caution!
Case 3: Keep Local Changes and Discard the Remote Version
# Abort the merge
git merge --abort
# Force push local to remote (this overwrites the remote -- coordinate with your team first!)
git push -f origin main
Scenario 2: Accidentally Committed Work on the main Branch
The right approach is to create a feature branch from main for development, but sometimes you forget to switch branches and end up writing code directly on main.
Solution: Move Your Changes to a New Branch
Case 1: Haven’t committed yet
# Stash current changes
git stash
# Switch to main and make sure it's up to date
git checkout main
git pull
# Create a new branch
git checkout -b feature/new-feature
# Restore the stashed changes
git stash pop
Case 2: Already committed, but haven’t pushed
# Create a new branch (this carries the current commit over)
git checkout -b feature/new-feature
# Switch back to main and reset it to the previous state
git checkout main
git reset --hard origin/main
Case 3: Already committed and pushed
This is trickier. It’s best to coordinate with your team and ask others not to pull temporarily. Then:
# Create a new branch to preserve the changes
git checkout -b feature/new-feature
git push origin feature/new-feature
# Go back to main and reset to the remote history
git checkout main
git reset --hard origin/main~1 # Reset to the previous commit
# Force push (coordinate with your team first)
git push -f origin main
Scenario 3: A Teammate’s Code Overwrote My Local Changes
This usually happens because a direct git pull triggers an automatic merge, and when there’s a conflict between someone else’s code and yours, Git picks their version.
Prevention: Use Rebase Instead of Merge
# Don't just git pull
# Use this approach instead:
# First, fetch remote updates
git fetch origin
# Check the diff between remote and local
git log HEAD..origin/main --oneline
# Rebase onto remote (keeps the commit history cleaner)
git rebase origin/main
The difference between git pull and git pull --rebase:
git pull=git fetch+git merge(creates a merge commit, history branches)git pull --rebase=git fetch+git rebase(keeps a linear history, cleaner)
Recommended: set rebase as the default behavior:
git config --global pull.rebase true
How to Recover Overwritten Code
Git’s reflog records all operations and can help you find lost commits:
# View all operation records
git reflog
# Find the commit with your lost code, e.g. abc1234
git checkout abc1234
# View the contents of that commit
git show abc1234
# Reset to that commit
git reset --hard abc1234
Scenario 4: Undoing a Pushed Commit That Was a Mistake
Case 1: Undo the Most Recent Commit (Recommended)
# Create a new commit that undoes the previous changes (doesn't alter history)
git revert HEAD
git push
This is the safest approach, suitable when others have already pulled your commit.
Case 2: Force Reset (Requires Team Coordination)
If you just pushed and nobody has pulled yet:
# Reset to the previous commit
git reset --hard HEAD^
# Force push
git push -f origin main
Warning: If someone has already pulled your commit, force pushing will cause problems in their repositories.
Case 3: Undo a Specific Historical Commit
# Revert a specific commit
git revert <commit-id>
# Revert a range of consecutive commits
git revert <start-commit>..<end-commit>
Scenario 5: Feature Branch Is Out of Date with main
When a feature branch has been in development for a long time and main has accumulated many merges from others, how do you sync up?
Method 1: Merge main into the Feature Branch (Creates a Merge Commit)
git checkout feature/my-feature
git merge main
Pros: Simple operation, preserves the complete history.
Cons: Creates a merge commit, which makes the history graph branch.
Method 2: Rebase onto main (Keeps a Linear History)
git checkout feature/my-feature
git rebase main
Pros: Clean, linear commit history.
Cons: If there are many conflicts, resolving them can be tedious.
If you encounter conflicts during rebase:
# After resolving conflicts
git add .
git rebase --continue
# If you want to abort the rebase
git rebase --abort
Note: If the feature branch has already been pushed, you’ll need to force push after rebasing:
git push -f origin feature/my-feature
Scenario 6: Preserving Full Commit History When Merging
Sometimes when merging a feature branch into main, Git uses fast-forward by default, which applies all the feature branch’s commits directly onto main – making the history less clear.
Use --no-ff to Preserve Branch Information
git checkout main
git merge --no-ff feature/my-feature -m "Merge feature: user login"
This forces the creation of a merge commit, preserving the complete branch information.
Use Squash Merge (Condense Multiple Commits into One)
If the feature branch has many small, granular commits, you can squash them into one:
git checkout main
git merge --squash feature/my-feature
git commit -m "Add: user login feature"
This leaves just a single, clean commit on the main branch.
Scenario 7: A Teammate Force Pushed and My Code Was Lost
When someone runs git push -f, it rewrites the remote history, causing other people’s local repositories to diverge from the remote.
Symptoms
You get an error when running git pull:
fatal: refusing to merge unrelated histories
Or a message saying your local and remote histories have diverged.
Solution
# Method 1: Force sync with remote (this will lose unpushed local changes)
git fetch origin
git reset --hard origin/main
# Method 2: If you have important local changes, back them up first
git checkout -b backup-branch # Create a backup branch
git checkout main
git reset --hard origin/main
Recommended team practices:
- Never use
git push -fonmain/masteror other primary branches - Enable branch protection on GitHub/GitLab to block force pushes
Scenario 8: Messy Commit History That Needs Cleaning Up
In team development, the commit history can easily turn into:
fix typo
fix again
final fix
fix for real this time
You can clean this up with rebase -i.
Interactive Rebase
# Tidy up the last 5 commits
git rebase -i HEAD~5
This opens an editor showing something like:
pick abc1234 Add login feature
pick def5678 fix typo
pick ghi9012 fix again
pick jkl3456 Optimize performance
pick mno7890 fix for real
Change it to:
pick abc1234 Add login feature
squash def5678 fix typo
squash ghi9012 fix again
pick jkl3456 Optimize performance
squash mno7890 fix for real
After saving, Git will merge the commits marked as squash into the preceding pick commit.
Common operations:
pick: Keep this commitsquash: Merge into the previous commitreword: Edit the commit messagedrop: Remove this commit
Note: Only do this for commits that haven’t been pushed yet. Otherwise, you’ll need push -f.
Scenario 9: Want to See What a Teammate Changed
View Changes in a Specific Commit
# View the detailed changes in a commit
git show <commit-id>
# See only which files were changed
git show --name-only <commit-id>
# View the modification history of a specific file
git log -p <filename>
View the Difference Between Two Branches
# View differences between the feature branch and main
git diff main..feature/new-feature
# See only which files were changed
git diff --name-only main..feature/new-feature
# View all commits by a specific person
git log --author="zhangsan"
See Who Last Modified Each Line of a File
git blame <filename>
This shows who last modified each line and in which commit.
Scenario 10: Best Practices for Collaboration
1. Pull Before You Commit
# Before starting development
git checkout main
git pull
# Create a feature branch
git checkout -b feature/new-feature
# When done, sync with main before committing
git checkout main
git pull
git checkout feature/new-feature
git rebase main
# Push
git push origin feature/new-feature
2. Write Clear Commit Messages
Bad commit messages:
fix bug
update
修改
Good commit messages:
Fix: Resolve token expiration issue during user login
Add: Add user avatar upload feature
Improve: Optimize homepage query performance, reduce SQL queries
Recommended format: Type: Brief description
Common types:
Add/feat: New featureFix/fix: Bug fixImprove/refactor: RefactoringDocs/docs: Documentation changesTest/test: Test-related
3. Commit in Small Steps and Push Frequently
Don’t batch up a pile of changes before committing. Commit each small feature as you finish it:
git add .
git commit -m "Add: user registration form validation"
git push
Benefits:
- Easier code reviews
- Easier rollbacks when something goes wrong
- Fewer conflicts
4. Feature Branch Naming Conventions
feature/login-page # New feature
bugfix/user-avatar # Bug fix
hotfix/critical-bug # Urgent fix
refactor/api-structure # Refactoring
5. Don’t Commit Sensitive Information
Never commit .env, config.json, key files, etc. to the repository.
Add them to .gitignore:
.env
.env.local
config.json
*.key
*.pem
node_modules/
If you accidentally committed something sensitive, remove it immediately:
# Permanently remove from Git history
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
# Force push
git push -f origin --all
FAQ
1. git pull keeps creating merge commits, which is annoying
Configure rebase mode:
git config --global pull.rebase true
2. How can I prevent accidentally pushing to the main branch?
Add a protection script in .git/hooks/pre-push, or enable branch protection on GitHub/GitLab.
3. Will mismatched Git versions within the team cause issues?
It’s recommended that the team standardize on a recent Git version (>= 2.30) to avoid inconsistent command behavior.
4. How do I handle binary file conflicts (e.g., images)?
Binary files can’t be auto-merged; you can only choose one version:
# Use the local version
git checkout --ours <filename>
# Use the remote version
git checkout --theirs <filename>
git add <filename>
git commit
5. What’s the Real Difference Between Merge and Rebase?
This is the most commonly confused concept in team collaboration. Simply put: Merge preserves the full history but creates branches, while Rebase rewrites history to make it linear.
How Merge Works
Imagine you’re developing on a feature branch while main also has new commits:
# Initial state: feature branch created from B
A---B main
\
C---D feature
# main branch has new commits
C---D feature
/
A---B---E---F main
# On feature branch, run: git merge main
C---D-------G feature
/ /
A---B---E---F---/ main
A new merge commit G is created with two parents (D and F), preserving the complete history of both branches.
How Rebase Works
Same scenario, but on the feature branch you run git rebase main:
# Initial state: feature branch created from B
A---B main
\
C---D feature
# main branch has new commits
C---D feature
/
A---B---E---F main
# On feature branch, run: git rebase main
# Git will:
# 1. Find the common ancestor B
# 2. Temporarily set aside feature's commits C, D
# 3. Move the feature pointer to main's latest commit F
# 4. Apply C and D's changes one by one, creating new commits C' and D'
A---B---E---F main
\
C'---D' feature
# The original C and D are discarded (commit hashes have changed)
This “moves” the feature branch’s commits C and D to after main’s latest commit F, creating a linear history. Note that C' and D' are entirely new commits (their hashes have changed); the original C and D are discarded.
A practical workflow:
# While developing on a feature branch, periodically sync with main
git checkout feature/my-feature
git fetch origin
git rebase origin/main # Use rebase to maintain linear history
# When development is done, merge back into main
git checkout main
git merge --no-ff feature/my-feature # Use merge to preserve branch information
Remember this principle: use rebase on your own branches, and merge when merging into shared branches.