Skip to main content
TellaDev
Learn Git Collaborative Git Workflows
intermediate Git

Collaborative Git Workflows

Pull requests, code review etiquette, and merge strategies for teams working together on GitHub.

Biplab Adhikari 878 words
git pull-request code-review github collaboration
Collaborative Git Workflows

The Pull Request Lifecycle

A pull request (PR) is a request to merge your branch into a shared branch. The lifecycle looks like this:

1. Branch from main → 2. Commit work → 3. Push & open PR

4. Automated CI runs (tests, lint, build)

5. Code review (teammates leave comments)

6. Address feedback → push new commits → re-review

7. Approved → merge → branch deleted

Most teams require at least one approval and a passing CI build before merging. Some require two approvals for sensitive areas (auth, payments).


Writing a Good PR Description

A good PR description answers three questions before the reviewer even looks at the diff:

What changed? — A brief summary of the code changes. Why? — The problem being solved or feature being added (link to the issue). How? — Any non-obvious implementation choices worth explaining.

Template:

## Summary

Closes #123

Adds rate limiting to the `/api/search` endpoint to prevent abuse.
Limit is 100 requests per minute per IP, configurable via env var.

## Changes

- Added `RateLimiter` middleware using a sliding window algorithm
- Added `RATE_LIMIT_RPM` env var (default: 100)
- Added unit tests for the limiter and integration test for the endpoint

## Testing

- [ ] Unit tests pass (`npm test`)
- [ ] Manually tested: confirmed 429 returned after limit exceeded
- [ ] Tested limit reset after 60 seconds

## Notes

The Redis-based implementation was considered but skipped — the
in-memory store is sufficient for single-instance deployments.

Code Review Etiquette

As a reviewer:

  • Review the code, not the person. “This function is too long” not “you wrote this wrong.”
  • Ask questions before assuming mistakes: “I’m curious why you chose X over Y — is there a reason?”
  • Distinguish between blocking issues and suggestions. Use prefixes like nit: for minor style preferences that shouldn’t block the PR.
  • Be specific. “This could be improved” is not actionable. “Extract this into a helper function to reduce duplication on lines 34 and 67” is.
  • Respond within a reasonable time. A PR that sits unreviewed for 3 days blocks the author.

As an author:

  • Keep PRs small and focused. A 50-line PR gets thorough review. A 1000-line PR gets a skim.
  • Respond to all comments, even if only to say “Fixed” or “Good point, left a comment explaining why I kept it.”
  • Don’t resolve reviewer threads yourself — let the reviewer confirm the fix is acceptable.
  • Mark work-in-progress PRs as drafts so reviewers know not to do a full review yet.

Merge Strategies

GitHub and GitLab offer three options when merging a PR:

Merge Commit

git merge --no-ff feature/my-feature

Preserves the full branch history. Creates a merge commit. Best when you want to see exactly which commits were part of which feature.

History looks like:

*   Merge pull request #45 (merge commit)
|\
| * Add validation logic
| * Add form HTML
|/
* Previous main commit

Squash and Merge

All commits from the branch are squashed into a single commit on main.

Best for: Messy WIP histories (fix typo, oops, another fix) that don’t need to be preserved. Keeps main history clean and linear.

* Add login form with validation (squashed)   ← one clean commit
* Previous main commit

Rebase and Merge

Replays each commit from the branch onto main without a merge commit.

Best for: When you want a fully linear history and your commits are already clean and atomic.

* Add CSS for login button
* Add form validation
* Add login form HTML
* Previous main commit

Recommendation for most teams: Use Squash and Merge as the default. It keeps main clean, each PR becomes one commit, and it’s easy to revert a feature by reverting a single commit.


Make Pull Requests Reviewable

A good pull request is small enough to understand and complete enough to test. Reviewers should be able to answer three questions quickly: what changed, why it changed, and how it was verified.

Write a short description that explains the intent, not just the files touched. Include screenshots for UI changes, sample requests for API changes, and migration notes for data changes. If there is a risky tradeoff, name it directly.

Code Review Tradeoffs

As an author, review your own diff before asking others to review it. Remove debug logs, unrelated formatting churn, and accidental files. As a reviewer, focus first on correctness, maintainability, security, and user impact. Style comments are useful only when they reflect an agreed standard.

Ask questions when intent is unclear. Do not turn every preference into a blocking comment. A healthy review process improves the code without making contributors dread opening a pull request.

Merge Strategy

Squash merges keep main history tidy when pull requests contain many small checkpoint commits. Merge commits preserve branch context. Rebase merges keep history linear. Pick one default and document exceptions.

For most small teams, squash merge plus clear pull request titles is simple and effective. For larger teams with release auditing needs, preserving merge commits may be worth the extra history noise.

What I Would Do In Practice

I would use short-lived branches, required CI, at least one review for meaningful changes, and a pull request template with verification steps. I would keep changes small enough that review is a normal part of daily work, not a ceremony that happens after weeks of isolation.

The point of a Git workflow is not process for its own sake. It is shared confidence: the team knows what changed, why it changed, and that the change passed the agreed checks before it reached main.

Keeping Forks and Branches Up to Date

When working from a fork (common in open source):

# Add the upstream remote once
git remote add upstream https://github.com/original/repo.git

# Fetch latest changes from upstream
git fetch upstream

# Rebase your branch onto upstream/main
git rebase upstream/main

# Force-push your updated branch
git push origin feature/my-fix --force-with-lease

When working on a long-running feature branch in a shared repo:

# Keep your feature branch current with main
git fetch origin
git rebase origin/main

# Or, if your team prefers merge over rebase:
git merge origin/main

Rebase more frequently to avoid large conflicts — daily is not excessive for active branches.


Automating Quality Gates with CI

Most teams integrate a CI system (GitHub Actions, GitLab CI, CircleCI) that automatically runs on every PR push:

# .github/workflows/ci.yml (example)
on: [pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint
      - run: npm test
      - run: npm run build

Branch protection rules enforce that CI must pass before merging. Set these in your repository settings to prevent accidental merges of broken code.

Keep The Workflow Human

The best Git workflow is not the one with the most rules. It is the one that helps people review small changes, understand intent, and recover when something goes wrong. Automation should reduce review burden, not replace judgment.

If pull requests keep getting stuck, look for process friction: unclear ownership, oversized diffs, flaky tests, missing screenshots, or review comments that arrive too late. Fixing those habits often improves delivery more than changing the merge button.