Kodnos
/
Software Engineering

Git Workflow That Actually Makes Sense for Small Teams

Git Flow is overkill for small teams. Here is the complete workflow I use — branching strategy, commit conventions, PR best practices, conflict resolution, and CI setup that keeps things simple and safe.

A
admin
Mar 28, 2026 14 min read 17 views
Git Workflow That Actually Makes Sense for Small Teams

Most Git workflow guides are written for large companies with dozens or hundreds of developers, complex release trains, and dedicated DevOps teams. If you are on a small team — two to five people — a lot of that advice is overkill. You end up spending more time managing branches than writing code.

I have worked on teams of various sizes, and I want to share the Git workflow that actually works when your team fits in a single room.

The Problem with Git Flow

Git Flow was introduced by Vincent Driessen in 2010, and for a while, it was the gold standard. It defines five types of branches: main, develop, feature, release, and hotfix. Each has specific rules about where it branches from and where it merges back.

For a large team shipping versioned software with scheduled releases, Git Flow makes sense. For a team of three building a web application with continuous deployment? It is way too much ceremony.

Here is what happens in practice: you create a feature branch from develop, finish the feature, merge back to develop, then when you want to release, create a release branch from develop, fix any issues there, merge it to main AND back to develop, tag it, and clean up.

For a startup or a small project, this is exhausting. You have two long-lived branches (main and develop) that need to stay in sync, release branches that exist for days or weeks, and a merge process that requires a flowchart to understand.

A Simpler Approach: Trunk-Based Development (Lite)

Here is the workflow I use with small teams. It is simple, safe, and scales surprisingly well:

1. main — always deployable, protected branch 2. feature branches — short-lived (hours to days, not weeks), one per task 3. That is it. No develop branch. No release branches. No hotfix branches.

The key principles:

  • Feature branches live for 1-3 days maximum
  • Every merge to main goes through a pull request with code review
  • Main is always deployable — if it is broken, fixing it is the top priority
  • Deploy from main directly (continuous deployment) or tag releases from main
  • The Daily Flow in Detail

    Starting a New Feature

    Bash
    # Always start from an up-to-date main
    git checkout main
    git pull origin main

    # Create a feature branch with a descriptive name git checkout -b feature/add-search-bar

    Branch naming matters. I use this convention:

  • feature/description — new functionality
  • fix/description — bug fixes
  • refactor/description — code improvements without behavior change
  • chore/description — tooling, dependencies, configuration
  • Working on the Feature

    Commit often. Small, focused commits are much easier to review and debug than one massive commit at the end.

    Bash
    # After implementing the search input
    git add -A
    git commit -m "feat: add search input component with debounce"

    # After connecting it to the API git add -A git commit -m "feat: connect search input to post search API"

    # After adding loading states and error handling git add -A git commit -m "feat: add loading spinner and error state to search"

    # After writing tests git add -A git commit -m "test: add unit tests for search component"

    Staying Up to Date

    If main has changed while you were working (because a teammate merged their PR), rebase your branch:

    Bash
    git fetch origin
    git rebase origin/main
    

    Rebasing replays your commits on top of the latest main. It keeps history linear and clean. If there are conflicts, you resolve them one commit at a time, which is usually easier than resolving a big merge conflict.

    Some teams prefer merge instead of rebase. Both work. The important thing is to stay current with main — do not let your branch drift for days without syncing.

    Getting Your Code Merged

    Bash
    # Push your branch
    git push origin feature/add-search-bar
    

    Create a pull request on GitHub/GitLab. Write a meaningful description:

  • What does this change?
  • Why is it needed?
  • How can the reviewer test it?
  • Any design decisions worth noting?
  • A teammate reviews the code, leaves comments, you address them, and when approved, squash-merge into main. Squash merging combines all your feature branch commits into a single commit on main, keeping the history clean.

    After merging, delete the feature branch. It has served its purpose.

    Commit Messages That Actually Help

    Good commit messages follow a simple, consistent pattern. I use Conventional Commits:

    type: short description (under 50 chars ideally)

    Optional body with more detail. Explain the why, not the what.

    feat: add user search with debounce fix: prevent duplicate form submissions on slow networks refactor: extract email validation into shared utility docs: update API endpoint documentation for v2 chore: upgrade Spring Boot from 3.2 to 4.0 test: add integration tests for payment processing style: fix indentation in UserService perf: add database index for post search queries

    The prefix tells you what kind of change it is without reading the diff. Keep the description under 50 characters when possible. If you need to explain more, add a body separated by a blank line.

    Bad commit messages I see constantly:

    # Too vague
    "update"
    "fix bug"
    "changes"
    "WIP"

    # Too long "Fixed the bug where users could not log in because the JWT token expiration was set to 0 instead of 3600 seconds and also updated the refresh token logic"

    When Things Go Wrong

    Git is powerful, which means it is also powerful at making a mess. Here are the situations I see most often and how to fix them.

    You Committed to Main by Accident

    Bash
    # Undo the commit but keep the changes
    git reset HEAD~1

    # Now create a proper feature branch git checkout -b feature/the-thing-i-was-working-on git add -A git commit -m "feat: the thing I was working on" git push origin feature/the-thing-i-was-working-on

    # Fix main (if you already pushed, you will need force push — coordinate with team) git checkout main git reset --hard origin/main

    You Need to Undo Your Last Commit

    Bash
    # Undo commit, keep changes staged
    git reset --soft HEAD~1

    # Undo commit, keep changes unstaged git reset HEAD~1

    # Undo commit AND discard all changes (CAREFUL — this is destructive) git reset --hard HEAD~1

    Merge Conflicts

    Do not panic. Merge conflicts are normal and usually not as bad as they look.

    1. Open the conflicting file 2. Look for the <<<<<<< markers:

    <<<<<<< HEAD
    // Your version
    const timeout = 5000;
    =======
    // Their version  
    const timeout = 10000;
    >>>>>>> feature/other-branch
    
    3. Decide which version to keep (or combine them) 4. Remove the conflict markers 5. Stage and commit:
    Bash
    git add resolved-file.java
    git commit -m "resolve merge conflict in timeout configuration"
    

    Tip: if conflicts scare you, use a visual merge tool. VS Code has good built-in conflict resolution. IntelliJ's is even better.

    You Made a Mess and Want to Start Over

    Bash
    # Nuclear option: discard ALL uncommitted changes
    git checkout -- .

    # Or reset to the last known good state git stash # Save changes just in case git checkout main git pull origin main git checkout -b feature/fresh-start

    Pull Request Best Practices

    PRs are where code quality is maintained. Here is what works:

    Keep PRs small. Under 400 lines of changes is ideal. Large PRs (1000+ lines) get rubber-stamped because reviewers lose focus. If your feature is big, break it into sequential PRs.

    Write a good description. What changes, why it changes, and how to verify. Screenshots for UI changes. Links to related issues.

    One PR = one concern. Do not sneak in unrelated refactoring, dependency updates, or "while I was here" fixes. They make the PR harder to review and harder to revert if something goes wrong.

    Review within 24 hours. Stale PRs kill momentum. If someone opens a PR in the morning, review it before end of day. The author's context fades quickly.

    Be constructive in reviews. "This is wrong" is not helpful. "This could cause a null pointer if the user has no email — consider adding a null check here" is helpful.

    Branch Protection Rules

    Even on a team of two, protect your main branch:

  • Require at least one approval before merging
  • Require passing CI checks (tests, lint, build)
  • No direct pushes to main — everything goes through a PR
  • Squash merge only — keeps main history clean and easy to read
  • Auto-delete branches after merge — prevents branch clutter
  • These rules take five minutes to set up on GitHub and prevent hours of headaches.

    CI/CD: Automate What You Can

    At minimum, your CI pipeline should: 1. Run on every PR 2. Build the project 3. Run all tests 4. Run the linter 5. Report results back to the PR

    A simple GitHub Actions workflow:

    YAML
    name: CI
    on:
      pull_request:
        branches: [main]
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-java@v4
            with:
              java-version: 21
              distribution: temurin
          - run: ./mvnw verify
    

    If all checks pass and the PR is approved, merge with confidence.

    The Takeaway

    Git is a tool, not a religion. Use the simplest workflow that keeps your team productive and your code safe. For most small teams, this means:

  • main + feature branches + pull requests
  • Short-lived branches (1-3 days)
  • Squash merges for clean history
  • Branch protection with CI checks
  • Conventional commit messages

That is it. No develop branch, no release branches, no complicated merge ceremonies. Ship code, ship it often, and keep main always deployable.

When your team grows to 10+ people and you need versioned releases, then consider something more structured. Until then, keep it simple.

14 MinShare this article
Oğuzhan Berke Özdil
Author

Oğuzhan Berke Özdil

I have been connected to computers since childhood. On this website, I share what I learn and experience while trying to build a strong foundation in software. I completed my BSc in Computer Science at AGH University of Krakow and I am currently pursuing an MSc in Computer Science with a focus on AI & Data Analysis at the same university.