Table of Contents
A solid git collaboration workflow is what separates PHP teams that ship cleanly from those that spend half their time resolving conflicts and overwriting each other’s work. Git works differently when more than one person is using it. Commands that are fine on a solo project become dangerous on a shared repository.
This guide covers the Git practices that actually work on PHP teams – pull requests, code review, keeping a shared repository clean, and resolving conflicts without breaking each other’s work.
For official Git team workflow documentation see Git distributed workflows documentation.
The Core Rule: Never Force Push to Shared Branches
Get this one into muscle memory before anything else.
# This can destroy other developers' work - never on shared branches
git push --force origin main
# This is safer if you absolutely must force push
git push --force-with-lease origin feature/your-own-branch
--force overwrites the remote branch with your local version, discarding anything that was pushed since your last pull. If a teammate pushed commits in the last ten minutes, those commits are gone.
--force-with-lease checks that the remote hasn’t changed since your last fetch before force pushing. If someone else pushed, it refuses and tells you. Use this if you need to force push your own feature branch after a rebase – never on main or develop.
The Git Collaboration Workflow: Pull Requests
Pull requests (PRs) – called Merge Requests on GitLab – are the standard way to integrate code on a team. The workflow:
# Developer A: Create a feature branch and push it
git checkout -b feature/product-search-filter
# ... do work ...
git push -u origin feature/product-search-filter
On GitHub or GitLab, open a Pull Request from feature/product-search-filter to main or develop. Write a description that explains:
- What the change does
- Why it’s needed
- How to test it
- Any known limitations
# Developer B: Review the changes locally
git fetch origin
git checkout feature/product-search-filter
# Run the code, test it, read it carefully
php artisan test
php -l src/Search/ProductSearchFilter.php
# Leave review comments on GitHub/GitLab
# Then approve or request changes
Fetching and Reviewing Someone Else’s Branch
# See all remote branches
git fetch --all
git branch -r
# Output:
# origin/feature/product-search-filter
# origin/feature/email-notifications
# origin/main
# Check out a colleague's branch to test it
git checkout -b feature/product-search-filter origin/feature/product-search-filter
# Or using the shorter version
git checkout --track origin/feature/product-search-filter
Always run the code locally before approving a PR. Reading the diff on GitHub tells you what changed. Running it locally tells you whether it actually works.
Keeping Your Branch Current With the Team
While you’re building your feature, teammates are merging theirs. Your branch falls behind main. Update it before merging:
# Option 1: Merge main into your branch
git checkout feature/product-search-filter
git fetch origin
git merge origin/main
# Option 2: Rebase onto current main (cleaner)
git checkout feature/product-search-filter
git fetch origin
git rebase origin/main
If the rebase causes conflicts:
# Git stops and shows the conflict
# Fix the conflicting files
# Then continue
git add src/Search/ProductSearchFilter.php
git rebase --continue
# If it's too messy, abort and use merge instead
git rebase --abort
After rebasing a pushed branch you’ll need to force push – but only because it’s your own feature branch:
git push --force-with-lease origin feature/product-search-filter
Cherry-Picking Specific Commits
Sometimes you need one specific commit from another branch without merging everything. A colleague fixed a bug on their feature branch that you also need:
# Find the commit hash on their branch
git log origin/feature/email-notifications --oneline
# Output:
# 3f7a9b2 Fix SMTP timeout on slow connections ← want this one
# 8c4d1e5 Add email template rendering
# a1b2c3d Add EmailNotifier base class
# Apply just that commit to your branch
git checkout feature/product-search-filter
git cherry-pick 3f7a9b2
Output:
[feature/product-search-filter b5c3d2e] Fix SMTP timeout on slow connections
Date: Sat May 3 08:30:00 2026 +0530
1 file changed, 3 insertions(+), 1 deletion(-)
Cherry-pick copies the commit onto your branch. The original commit still exists on the other branch unchanged.
Tagging Releases
Tags mark specific commits as release points. Every time code goes to production, tag it:
# Create an annotated tag with message
git tag -a v1.2.0 -m "Release v1.2.0 - Add product search filter and email notifications"
# Push the tag to remote
git push origin v1.2.0
# Push all tags at once
git push origin --tags
# List all tags
git tag
# View tag details
git show v1.2.0
Output of git show v1.2.0:
tag v1.2.0
Tagger: Kapil Verma <kapil@example.com>
Date: Sat May 3 09:00:00 2026 +0530
Release v1.2.0 - Add product search filter and email notifications
commit a1b2c3d4e5f6...
Author: Kapil Verma <kapil@example.com>
Date: Sat May 3 08:55:00 2026 +0530
Merge feature/email-notifications into main
Tags are permanent reference points. If a release breaks production you can check out the previous tag immediately:
# Roll back to the previous release
git checkout v1.1.0
# Or create a hotfix branch from the previous release
git checkout -b hotfix/revert-to-v1.1 v1.1.0
Comparing Work Between Developers
# What commits are on feature A but not in main?
git log main..origin/feature/product-search-filter --oneline
# What files differ between two branches?
git diff main..origin/feature/product-search-filter --name-only
# Who changed a specific file and when?
git log --follow src/Search/ProductSearchFilter.php
# Who wrote each line of a file?
git blame src/Search/ProductSearchFilter.php
Output of git blame:
3f7a9b2 (Kapil Verma 2026-05-01 09:00:00 +0530 1) <?php
3f7a9b2 (Kapil Verma 2026-05-01 09:00:00 +0530 2)
8c4d1e5 (Jane Smith 2026-05-02 14:30:00 +0530 3) namespace App\Search;
8c4d1e5 (Jane Smith 2026-05-02 14:30:00 +0530 4)
a1b2c3d (Kapil Verma 2026-05-03 08:00:00 +0530 5) class ProductSearchFilter
git blame is for understanding history, not assigning fault. It tells you who introduced each line so you know who to ask when something is confusing.
Finding the Commit That Introduced a Bug
A bug appeared sometime in the last two weeks and you don’t know which commit caused it. git bisect does a binary search through commits to find the culprit:
# Start bisect
git bisect start
# Mark current commit as bad (bug is present)
git bisect bad
# Mark a commit where the bug didn't exist as good
git bisect good v1.1.0
# Git checks out the midpoint commit
# Test whether the bug is present
# Then tell Git the result
git bisect good # bug not here
# or
git bisect bad # bug is here
# Git keeps narrowing down
# When finished it shows the first bad commit:
# a1b2c3d is the first bad commit
# End bisect - returns to original HEAD
git bisect reset
On a PHP project this typically means running your test suite or manually testing the specific bug at each bisect step. Git handles the binary search – you just mark good or bad.
Team Git Configuration
Consistent configuration across the team prevents unnecessary conflicts and confusion:
# Set in your project's .gitattributes file
# Ensures consistent line endings across OS
* text=auto
# PHP files always use LF
*.php text eol=lf
# Ensure these are treated as binary
*.png binary
*.jpg binary
*.pdf binary
# Set globally for every developer on the team
# Prevent pushing to wrong branch accidentally
git config --global push.default current
# Show branch tracking info in status
git config --global status.showUntrackedFiles all
# Better diff output
git config --global diff.algorithm patience
Frequently Asked Questions
Should we use merge or rebase for integrating feature branches?
This is a genuine team preference question with valid arguments on both sides. Merge preserves the exact history of what happened and when – useful for auditing. Rebase creates a linear history that’s easier to read in git log. The only rule that matters: agree on one approach and use it consistently. Mixed rebase and merge workflows on the same repository create confusing history that’s hard to follow.
How small should a pull request be?
Small enough to review in 15-20 minutes. PRs over 400-500 lines of changes get rubber-stamped because reviewers lose focus. Break large features into smaller PRs that each add one piece of working functionality. A user authentication feature could be: registration, login, password reset – three PRs instead of one massive one. Each piece is reviewable, mergeable, and testable independently.
What goes in a good pull request description?
Four things at minimum: what the change does in one sentence, why it’s needed, how to test it locally, and any known limitations or follow-up work. Screenshots for UI changes. Migration notes if the database schema changed. The person reviewing shouldn’t have to ask “what does this do” – the description should answer that before they open the diff.
A teammate force pushed to main and overwrote my commits. Can I recover them?
Yes if you acted quickly. Your local copy still has the commits. Push them back:
# Your local main still has the overwritten commits
git push --force-with-lease origin main
If your local copy was also updated, use git reflog to find the commit hashes before they were overwritten and recover from there. This situation is exactly why force pushing to shared branches is a team rule violation, not just a technical mistake.
Summary
Git collaboration on PHP teams comes down to a few non-negotiable practices:
- Never force push to shared branches – use
--force-with-leaseon your own feature branches only - Pull requests for all merges – no direct pushes to main without review
- Keep branches current – rebase or merge from main regularly to minimize conflicts
- Tag every release – gives you rollback points and clear history
- Small PRs – easier to review, faster to merge, less conflict risk
For the branching strategy that underpins this collaboration workflow, the Git branching for PHP projects guide covers feature branches, hotfixes, and release patterns. For setting up the Git foundation before applying team workflows, the Git for PHP developers setup guide covers configuration and daily workflow.
