Git Advanced Techniques: Team Collaboration and Branch Management Best Practices

Introduction
2 AM. Just as I was about to shut down my computer and call it a day, I saw a colleague @mentioning me in the group: “Dude, the code you just merged overwrote my login functionality, now users can’t log in to production…”
You can imagine how I felt—hands shaking, heart racing, breaking into a cold sweat. I quickly opened the code and saw nothing but <<<<<<< and >>>>>>> conflict markers everywhere. I couldn’t even tell which parts were my code and which were my colleague’s. That night taught me a lesson: Knowing basic Git commands doesn’t mean you know team collaboration.
If you’ve had similar experiences—too many branches to track, afraid to merge code for fear of conflicts, commit history looking like a tangled ball of yarn—this article should help. I’ll share practical experience from years of team collaboration, from choosing workflows to handling conflicts, from organizing commit history to establishing code review processes.
After reading this article, you’ll master:
- How to choose the right branch strategy for your team
- Methods to gracefully resolve merge conflicts
- Techniques for organizing commit history with rebase
- Key points for establishing efficient code review processes
Chapter 1: Choosing the Right Git Workflow for Your Team
Honestly, when I first led a team, I didn’t know which Git workflow to use. I heard people saying Git Flow was very professional, so I copied it directly. It turned out that a small team of 5 people spent half an hour every day just managing branches, and everyone eventually got fed up.
Later I realized, there’s no best workflow, only the most suitable one.
Git Flow vs GitHub Flow vs Trunk Based Development
Let me explain the differences between these three workflows in the simplest way:
Git Flow is like a strict assembly line factory, with dedicated development branches (develop), feature branches (feature), release branches (release), and hotfix branches (hotfix). Companies like Microsoft and IBM use it a lot because they have clear release cycles, like releasing a version every month.
But for small teams, Git Flow is too heavy. My previous team was like this—every release required pulling a release branch from develop, merging it to master after testing, then merging back to develop. The process alone was exhausting enough.
GitHub Flow is much simpler—just one main branch (main/master), all features are pulled from the main branch as feature branches, and after development, they’re merged back via PR. This pattern is especially suitable for teams of 3-10 people like ours, emphasizing rapid iteration and continuous delivery.
My current team uses GitHub Flow, combined with CI/CD automated testing, and we can basically release several times a day. Team members also find it easy—no need to remember so many branch rules.
Trunk Based Development (TBD) is even more extreme—everyone develops directly on the main branch, and branches usually don’t live longer than a day. Only companies like Google and Facebook with strong automated testing capabilities dare to do this.
For ordinary teams like ours, TBD is a bit too aggressive. Unless your test coverage is above 90%, it’s easy to break the main branch.
Trends in 2025
I’ve read quite a bit of material recently and found that the 2025 trend is hybrid models. Small teams (1-10 people) basically use GitHub Flow, combined with GitHub Actions or GitLab CI for automation. Medium and large teams will combine certain parts of Git Flow with GitHub Flow according to their situation.
For example, my friend’s company has a 20-person team using a “GitHub Flow + release branch” hybrid solution: daily development uses GitHub Flow, but they pull a release branch for stabilization before each release.
My recommendation is:
- Small teams of 1-5: Use GitHub Flow directly, don’t overthink it
- Medium teams of 5-15: GitHub Flow + simplified release process
- 15+ people: Consider Git Flow or hybrid solutions, but must combine with complete CI/CD
Oh, and one more key point: don’t over-design. I’ve seen too many teams start with super complex branch strategies, then no one wants to follow them, and finally revert to everyone doing their own thing.
Chapter 2: Golden Rules of Branch Management
The most headache-inducing thing about team collaboration is chaotic branch management. A previous project repository had over 70 remote branches, at least half of which were zombie branches from two years ago. Finding your own branch took forever.
Later I summarized a set of “golden rules” for branch management, and after the team used them, the entire repository became much cleaner.
Branch Naming Conventions
First is naming conventions. We now require all branch names to have prefixes:
feature/- New feature developmentbugfix/- Bug fixeshotfix/- Emergency hotfixesrelease/- Release branches
And they must include task numbers, like: feature/JIRA-1234-user-login
The benefit of doing this is that you can tell at a glance what the branch is for and who’s responsible. We previously had a branch called test, and three months later no one remembered what it was testing—we just deleted it.
A few more tips to avoid pitfalls:
- Don’t use Chinese branch names; although Git supports them, they’ll show as garbled on some systems
- Don’t use special characters, especially spaces and
@symbols - Use hyphens
-to connect words, not underscores_(this is an industry convention)
Branch Lifecycle Management
The most ridiculous situation I’ve seen was a feature branch open for three months without merging. When it came time to merge, there were so many conflicts they couldn’t be resolved, and they had to rewrite it from scratch.
So now I require the team: feature branch lifecycle cannot exceed 3 days. If a feature is too big, split it into multiple small features and merge them in batches.
Delete the branch immediately after merging—this is an iron rule. Many people are reluctant to delete, thinking “what if I need it later?” Believe me, you won’t. Git has complete history, you can always find the merged code.
I now keep branch deletion commands in my phone notes:
# Delete local branch
git branch -d feature/xxx
# Delete remote branch
git push origin --delete feature/xxxIf the repository has accumulated many zombie branches, you can clean them up with this command:
# View branches already merged to main
git branch --merged main
# Batch delete (use carefully)
git branch --merged main | grep -v "main" | xargs git branch -dBranch Protection Strategy
One more important point: protect the main branch.
When I first led a team, everyone could push directly to the main branch. Once an intern mistakenly pushed incomplete code, causing the entire test environment to crash.
Later I set up branch protection rules on GitHub/GitLab:
- main branch prohibits direct push, must go through PR/MR
- Requires at least 1 person to approve before merging
- CI checks must pass (including unit tests, lint checks)
Setting this up is simple—just configure it in the repository’s Settings → Branches → Branch protection rules on GitHub.
These rules seem cumbersome, but they can really prevent many low-level errors. Plus code review itself is a great learning opportunity—team members can learn from each other’s coding styles and business logic.
Chapter 3: Git Rebase vs Merge — When to Use Which?
Speaking of rebase, I was really confused the first time I used it. After executing git rebase, I saw a bunch of commit IDs all changed, and I thought the code was lost—I almost cried.
Later I realized that although both rebase and merge can merge code, the underlying logic is completely different.
The Essential Difference Between Merge and Rebase
I’ll explain with a life analogy:
Merge is like two rivers converging—you can clearly see where the two rivers meet, and after meeting they form a wider river. In Git, merge creates a merge commit, and the commit history tree will have obvious branching.
Rebase is like “re-pouring” the tributary water into the main stream, making it look like there was only one river from the beginning. Rebase rewrites history, “moving” your commits one by one behind the target branch, so commit IDs change.
From a technical perspective:
- Merge preserves complete history, including branch creation and merge processes
- Rebase creates a linear commit history, cleaner and tidier, but loses branch context information
Usage Scenarios and Golden Rules
So when should you use which? My experience is:
Use Merge when:
- Finally merging feature branches to main branch
- Merging team-shared branches
- You want to preserve complete merge history
Use Rebase when:
- Your feature branch is behind main branch and you want to sync the latest code
- Organizing commit history on your personal branch (before pushing)
- You want to keep commit history linear
But there’s one golden rule you must remember:
Never rebase commits that have already been pushed to shared branches!
Why? Because rebase changes commit IDs. If someone else has already worked based on your old commits, after you rebase their branches will be messed up. A colleague of mine made this mistake, and the entire team’s branches needed to be repulled—everyone complained about him for a week.
So my current principle is: Rebase locally as you like, think carefully before pushing.
Interactive Rebase in Practice
Speaking of rebase, I must mention git rebase -i (interactive rebase)—this feature is super powerful.
I previously developed a feature and committed over a dozen times during development, with messages like “fix”, “modify”, “modify again”—very unprofessional. Before merging to main, I used git rebase -i to organize these dozen commits into 3 clear commits.
Here’s how it works:
# Organize the last 10 commits
git rebase -i HEAD~10After execution, an editor will open showing something like:
pick a1b2c3d Add user login functionality
pick e4f5g6h Fix login bug
pick i7j8k9l Modify again
pick m0n1o2p Optimize login logic
...You can change the operation to:
pick- Keep this commitsquashors- Merge this commit into the previous onerewordorr- Modify commit messageeditore- Pause at this commit, allowing you to modify contentdropord- Delete this commit
For example, I would change the above to:
pick a1b2c3d Add user login functionality
squash e4f5g6h Fix login bug
squash i7j8k9l Modify again
reword m0n1o2p Optimize login logicThis way, the first three commits will be merged into one, and the fourth will prompt me to modify the commit message.
Honestly, it might feel complicated the first time you use it, but practice a few times and you’ll get the hang of it. And the commit history after rebase is really much cleaner—no more embarrassing commits like “modify”, “modify again”, “modify yet again”.
The 2025 best practice is: Use rebase to organize commit history locally, use merge when merging to main branch. This ensures both clear main branch history and preserves important merge information.
Chapter 4: No More Panic About Conflict Resolution
Speaking of Git conflicts, I really have trauma. The first time I encountered a conflict, I looked at the screen full of <<<<<<< and >>>>>>> and had no idea what to do. In the end, I just deleted all my code and rewrote it.
Later I discovered that conflicts aren’t that scary—the key is understanding what they’re saying and mastering some prevention techniques.
Best Practices for Preventing Conflicts
Actually, many conflicts can be avoided. I now require the team to do these things:
1. Sync main branch at least once a day
Before starting work each day, execute:
git checkout main
git pull origin main
git checkout feature/xxx
git merge main # or git rebase mainThis way your feature branch won’t fall too far behind, and there will be fewer conflicts when merging.
2. Small commits, quick merges
As I mentioned earlier, feature branch lifecycle should not exceed 3 days. The longer the branch exists, the greater the difference from main branch, the higher the probability of conflicts.
3. Communicate before team collaboration
If two people need to modify the same file, it’s best to say something beforehand. Our team now maintains a “Features in Development” table in Notion, so who’s modifying which file is clear at a glance.
Understanding Conflict Markers and Manual Resolution
Even with prevention, conflicts will still happen. Then you’ll see markers like this:
<<<<<<< HEAD
function login(username, password) {
// Your code
return api.post('/login', { username, password });
}
=======
function login(email, password) {
// Colleague's code
return api.post('/auth/login', { email, password });
}
>>>>>>> feature/new-loginThese markers are actually easy to understand:
- Between
<<<<<<< HEADand=======is your current branch’s code - Between
=======and>>>>>>> feature/new-loginis the code from the branch being merged in
You need to decide which part to keep, or combine both. For example, in this case you might need to discuss with your colleague: use username or email for login? Which interface path to use?
After resolving all conflicts, delete these markers, then:
git add .
git commit -m "Resolve merge conflicts in login functionality"Using Visual Tools to Improve Efficiency
Honestly, manually resolving conflicts is really tiring, especially when there are many. I now basically use VS Code’s built-in conflict resolution tools.
In VS Code, conflicted files are highlighted and several buttons appear:
- “Accept Current Change” - Keep your code
- “Accept Incoming Change” - Keep the other person’s code
- “Accept Both Changes” - Keep both sides
- “Compare Changes” - Open comparison view
Just click a button to resolve conflicts—much more convenient than manual editing.
If the conflict is particularly complex, I’ll use git mergetool with professional tools like p4merge, which allows three-column comparison (base version, your version, their version) for clearer viewing.
Verification After Conflict Resolution
Once I resolved a conflict, committed, and pushed directly, only to find I had deleted a critical piece of logic from my colleague, causing production functionality issues. That lesson taught me a habit: Verification is required after conflict resolution.
Now after resolving conflicts I always:
- Run tests locally to ensure functionality is normal
- Use
git diffto review changes and confirm no code was mistakenly deleted - If possible, have a colleague review it too
This only takes a few minutes but can prevent many production incidents.
Chapter 5: Establishing an Efficient Code Review Process
Honestly, when I first started working, I really disliked code review. I felt my code was already good—why did others need to point fingers? Plus review comments often made me revise back and forth, wasting a lot of time.
Later when I started reviewing others’ code, I understood that the value of code review goes far beyond just “finding bugs”.
The True Value of Code Review
I now think code review has at least three major values:
1. Knowledge Sharing - Let team members understand what each other is doing, avoiding information silos. Several times I only learned through reviewing others’ PRs that a certain functionality could be implemented that way.
2. Quality Assurance - An extra pair of eyes can always find some issues you overlooked. Once I wrote a recursive function that tested fine, but the reviewer noticed I didn’t handle boundary conditions—it almost caused a stack overflow.
3. Technical Growth - Viewing excellent code is the best way to learn. Many of my coding techniques were learned from reviewing senior engineers’ PRs.
PR Best Practices
But for code review to be efficient, the PR itself must be well-written. Our team now has several agreements:
PR Size Should Be Appropriate
A PR should ideally be 200-400 lines of code, maximum not exceeding 500 lines. PRs that are too large—no one wants to review carefully, it just becomes a formality.
If a feature is too large, split it into multiple small PRs and merge in batches. For example, developing a user system can be split into:
- PR1: Database table design and basic model
- PR2: User registration API
- PR3: User login API
- PR4: User information modification API
PR Description Should Be Clear
Our team has a PR description template:
## Background
Briefly describe why this change was made
## Changes
- Added xxx functionality
- Fixed xxx bug
- Refactored xxx module
## Testing
- [ ] Unit tests passed
- [ ] Local functionality tests passed
- [ ] Verified in test environment
## Notes
Configuration changes, database migrations, etc. to be aware ofThis way the reviewer can immediately understand what you did and why.
Commit Messages Should Be Standardized
We now follow the Conventional Commits specification—commit messages must have type prefixes:
feat: Add user login functionalityfix: Fix password validation bugrefactor: Refactor user service layerdocs: Update API documentation
The benefit of this is that later you can automatically generate changelogs, and it’s easier to locate when tracing history.
Responsibilities of Reviewers and Authors
Code review is bidirectional—both reviewers and authors have responsibilities.
Reviewers should:
- Respond to PRs within 24 hours (don’t let PRs hang for days)
- Provide constructive comments, not nitpicking (saying “this isn’t good” isn’t as helpful as “suggest changing to xxx, because…”)
- Focus on code logic, readability, potential bugs, not code style (leave style issues to lint)
Authors should:
- Respond to comments promptly, don’t stay silent
- Explain design thinking—if reviewers don’t understand, maybe the code isn’t clear enough
- Accept suggestions humbly—even if you initially disagree, think about why
When I first started as a reviewer, I often wrote comments like “this is wrong, change it”, but the author had no idea what I wanted. Later I learned to write like this: “This loop might have performance issues if the data volume is large. Suggest changing to xxx or xxx, what do you think?” Communication is much better this way.
2025 New Trend: AI-Assisted Code Review
In 2025, many AI-assisted code review tools emerged. GitHub launched its Code Quality feature in October, which can automatically detect code maintainability and security issues.
I tried it out, and it can indeed find some issues we easily overlook, such as:
- Unhandled exceptions
- Potential memory leaks
- Overly complex functions (high cyclomatic complexity)
However, AI currently cannot replace manual review. It can find technical issues, but for design thinking and business logic reasonableness, people still need to judge.
So my current approach is: AI tools scan first to find obvious issues, then people do deep review of design and logic. This has improved efficiency quite a bit.
Chapter 6: Common Mistakes and Pitfall Guide
Finally, I want to share some pitfalls my team and I have encountered, hoping you can avoid them.
The Danger of Force Push
Once, my feature branch was far behind main branch. After rebasing, I found I couldn’t push because local and remote history were inconsistent.
At the time I didn’t know what to do, so I searched and found the git push -f command. Thinking “force push” sounded powerful, I used it.
As a result, I overwrote all the code my colleague had just pushed. Fortunately we had backups, otherwise his entire day’s work would have been wasted.
Later I learned that git push -f (or git push --force) is one of the most dangerous commands—it forcibly overwrites remote history with your local history, regardless of what new code exists remotely.
When you can use it:
- Your personal feature branch, and you’re sure no one else is using it
When you absolutely cannot use it:
- main/master and other shared branches
- Any branch that other people are using
If you really need to force push, using git push --force-with-lease is safer—it checks if there are new commits on the remote branch and refuses to push if there are.
Other High-Frequency Errors
Besides force push, there are some common errors:
1. Developing directly on main branch
Never do this. Even for a simple one-word change, open a branch and merge via PR. This way there’s a review record, and rollback is easier if something goes wrong.
2. Commit messages too casual
“fix”, “modify”, “111”, “asdf”… I’ve seen too many weird commit messages. When tracing code later, these messages are completely useless.
Develop good habits—for each commit, write the message carefully, explaining what was changed and why.
3. Not deleting merged branches in time
Branches should be deleted immediately after merging, don’t leave them taking up space. GitHub and GitLab both have options to automatically delete branches when merging PRs—remember to check that.
4. Blindly using git add .
git add . adds all changes to the staging area, including files you don’t want to commit. I’ve seen people accidentally commit .env files (containing passwords).
A better approach is git add -p, which asks you for each change whether to add it—slower but safer.
Team Collaboration Standards Checklist
Finally, I’ve compiled a Git standards checklist for teams—you can refer to it:
Branch Management
- Use unified branch naming conventions (feature/, bugfix/, etc.)
- Branch names include task numbers
- Feature branch lifecycle does not exceed 3 days
- Delete branches immediately after merging
- Set main branch protection rules
Code Commits
- Commit messages follow conventions (with type prefixes)
- Each commit does one thing
- Run lint and tests before committing
- Don’t commit sensitive information (.env, keys, etc.)
Code Review
- PR size is reasonable (200-400 lines)
- PR description is clear (background, changes, testing)
- Requires at least 1 approval to merge
- CI checks must pass to merge
- Respond to PRs within 24 hours
Conflict Handling
- Sync main branch once a day
- Run tests after resolving conflicts
- Communicate with colleagues promptly for uncertain conflicts
Prohibited Operations
- Don’t develop directly on main branch
- Don’t use git push -f on shared branches
- Don’t rebase commits already pushed to shared branches
- Don’t commit untested code
Conclusion
Looking back at that night handling the production incident at 2 AM, I really wish I had mastered these techniques then. But fortunately, all the pitfalls encountered have become experience.
Actually Git itself isn’t difficult—what’s difficult is reaching consensus within the team. No matter how good the workflow or how standardized the process, if team members don’t follow it, it’s all for nothing.
So my suggestion is: Start simple, optimize gradually. Don’t start with super complex standards, but adjust little by little according to the team’s actual situation.
For example:
- Week 1: Unify branch naming conventions
- Week 2: Set up branch protection rules
- Week 3: Establish PR review process
- Week 4: Introduce commit message standards
Take it slow, give the team time to adapt.
Most important is communication. When encountering problems, don’t struggle alone—discuss with colleagues. Many Git conflicts are essentially communication problems, not technical ones.
If you want to further improve Git skills, I recommend these resources:
- Atlassian Git Tutorial - Most comprehensive Git tutorial
- Pro Git eBook - Official Git book, free to read online
- GitKraken or SourceTree - Visual Git clients, more friendly for beginners
Next article I might write about advanced Git techniques like cherry-pick, stash, submodule, etc. If you’re interested, feel free to follow me.
Finally, I wish you and your team’s Git collaboration becomes smoother and smoother—no more getting up in the middle of the night to handle merge conflicts!
Published on: Nov 24, 2025 · Modified on: Dec 4, 2025
Related Posts

Complete Guide to Deploying Astro on Cloudflare: SSR Configuration + 3x Speed Boost for China

Building an Astro Blog from Scratch: Complete Guide from Homepage to Deployment in 1 Hour
