Setting up a Git repository and branching strategy (GitHub Flow)

Branches in the repository keep multiplying, releases stall, and merge conflicts eat up the team's time. We set up branch workflows turnkey: rules, checks, and a release process that cut change approval time severalfold.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

Where GitHub Flow breaks down on real projects

GitHub Flow relies on short branches from the main branch and frequent merges — while a team has two or three developers and one release per week, the branching strategy does not create friction.

As soon as there are a dozen branches, merge conflicts surface not with the author of the edit, but at the moment of merging, when the context is already lost: another engineer has to sort out someone else's change, and the branch lives for several days instead of several hours.

The second breakdown is stuck checks. Required statuses on a protected branch block merging, and one unstable check stops the entire queue: branches hang unmerged, changes accumulate, and release delay grows faster than the volume of code in them.

Next, the main branch suffers. If checks run only on the author's branch and not after merging, combinations of edits seep into the main branch, each of which passed individually.

An unstable main branch breaks the next branch before work even starts, and time goes into sorting out other people's breakages instead of the task.

A separate story is manual releases: the tag is placed manually, the build composition is not recorded anywhere, and the question “what is in prod right now” requires archaeology through commit history. Rollback turns into rebuilding a past state and re-running all checks.

These are four different breakdowns with different causes, and they are not cured by one rule: branch protection, branch size discipline, release automation. We break down how this gets closed.

What changes after putting branches in order

Branches live for hours or days, not weeks: a change goes into the mainline immediately after review. This is how GitHub Flow works — one long-lived branch, short working branches from it, and merging through a pull request with mandatory checks.

Merge time is determined by time to readiness, not the duration of the operation itself.

A short set of changes passes automated checks in minutes, the review is read in one pass, a queue of large branches does not pile up — the cycle from commit to landing in the mainline shrinks from days to hours.

Release frequency grows as a consequence: the main branch is constantly in a releasable state, and a release is a tag on a commit, not multi-day stabilization of a separate branch.

Recovery time after a failed release drops: one small change is rolled back instead of sorting out a month-old merge.

Check quality rests on rules, not discipline: main branch protection, required statuses, and the required number of approvals.

A small change is easier to cover with tests and easier to roll back, so the share of rollbacks decreases — the defect is localized in one commit, and its cause is visible without archaeology through history.

All four metrics — time to merge, release frequency, rollback rate, recovery time — move together: the branching structure changes, not individual team habits.

Comparison of GitHub Flow with other branching strategies

A branching strategy is a derivative of the release model, not of team size. In some cases one main branch and short task branches are enough; in others, permanent development and release branches are needed for parallel version support. Below are the alternatives that teams actually choose between, and their engineering cost.

Option When it fits Limitations
Main branch and short task branches (GitHub Flow) Continuous delivery, one release stream No long stabilization; without feature flags, unfinished work goes to prod
Permanent development and release branches Several supported versions, release windows Branches diverge, merges become more expensive, release slows down
Environment branches (development, acceptance, production) Manual control of promotion across environments Environment as source of truth, back-merges, configuration drift
Release by tags Immutable artifact, deploy by tag Does not describe environment state without strict versioning

The choice comes down to two questions: how many product versions are supported simultaneously and how much time passes from merge to release.

If there is one version and a release takes minutes, the main branch with short task branches leaves fewer entities in the repository and fewer merge conflicts.

Creating release branches is worth it only for a specific support task — their number directly converts into engineering time for synchronization.

How we implement branch workflow: step-by-step breakdown

We keep the main trunk always operational, and we conduct edits in short branches for one task — this is the essence of GitHub Flow. Below are six steps through which we bring a repository to this state, and what remains with the team after handover.

  1. Repository audit. We look at merge history, the number of long-lived branches, conflict frequency, and time from commit to release. The output is a map of bottlenecks: exactly where edits get stuck in review.
  2. Branching scheme. We fix a single main trunk, branch names for the task, and linkage to the ticket number. We remove long-lived branches: the shorter the branch, the cheaper the conflict.
  3. Branch protection rules and pre-merge checks. Prohibition of direct pushes to the main trunk, mandatory pull request, required number of approvals, auto-deletion of merged branches. Checks cannot be bypassed locally — they live on the repository side.
  4. Automated test run. For every pull request, build, unit tests, and style checks are launched; merging is blocked until checks are green. This removes manual follow-up and arguments like “it works on my machine.”
  5. Trial with one team. We run the new order with one team for two or three weeks, catch friction in the rules, and fix them before rolling out to the rest.
  6. Training and handover of the guidelines. Walkthroughs with live examples, a short cheat sheet, and branch workflow guidelines in the repository itself — so the rules are read where they are applied.

After that, the path from branch to main trunk takes minutes and does not require verbal agreements: a new engineer follows it according to the guidelines, and release history remains readable.

What remains in the repository after process setup

A process is considered implemented not at the moment of agreement, but when the repository contains artifacts by which any new engineer can work without verbal explanations.

Below is the set we hand over to the customer: every item is visible in the repository settings and files, and can be checked independently of who makes changes.

  • Main branch protection configuration: prohibition of direct pushes and force-pushing history rewrites, mandatory review, required green status checks, requirement for an up-to-date base before merging.
  • Branching scheme — a short diagram: which branch work branches from, what it is called, where and when it merges, how long it lives.
  • Branch naming rules: prefix by change type and issue number in the tracking system, so the branch is readable in the list and linked to the discussion.
  • Change description template: what and why changed, how tested, what it affects, whether data migration or rollback is needed.
  • Set of automated checks for every change: static analysis, build, tests, dependency vulnerability scanning.
  • Path owners file: for critical directories, explicitly named those whose approval is required — protection cannot be bypassed by a random review.
  • Process documentation and release guidelines: merge order, release frequency, who and how rolls back, what to do in case of an urgent fix.
  • Cleanup policy: merged and stale branches are deleted on schedule so the list remains a working tool, not an archive.

Such a set reads like an instruction: an engineer opens the repository and immediately sees where to target the branch, what to attach to the change, and which checks must pass.

Case study: fewer conflicts and faster change releases

A team of several dozen engineers worked with long branches: changes lived for weeks, merging into the main branch became a separate task. Each release accumulated conflicts, some edits were lost during manual resolution, and the fix moved to the next cycle.

We restructured the process using the GitHub Flow model — short branches and mandatory pre-merge checks.

Before the transition

Measured over a quarter: 6–8 conflicts per release, of which 2–3 required manual resolution and rechecking affected areas. Average time from a ready change to production — about nine days, release — once every two weeks.

About a third of changes returned for rework after review because the reviewer saw a large volume of edits at once.

What we changed and what we got

A branch branches off from the main one and lives no longer than a couple of days; merging goes only through review and green automated tests. The main branch is protected: direct push is closed, at least one code review is required, build and tests run on every change. Small edits go to production immediately after review instead of waiting for a release window.

Conflicts decreased by about four times — 1–2 per cycle, manual resolution almost disappeared. Time to production — less than a day, share of rework after review — about one tenth.

Rollback takes minutes: each merge leaves a built version that can be returned to without rebuilding the branch.

What to do if the repository already has long-lived branches?

Long-lived branches diverge from the main branch the more the longer they live: every week adds conflicts in the same files, and merging turns from a routine operation into a separate multi-day project.

Switching to short branches in such a repository is possible without stopping work — but requires strict order of actions.

The first step is a freeze. For long branches, a date is announced after which only critical bug fixes go there, and everything new goes through short branches from the main one. The divergence stops growing, and the volume of the upcoming merge becomes predictable.

Then branches are merged in stages, one at a time — from the closest to the main branch to the oldest. A dedicated engineer leads the merge: first merges the main branch into the branch, runs the build and checks, and only then transfers the changes.

During the branching scheme migration, temporary exceptions are made in protection rules: mandatory approval is removed for the receiving branch and direct edits are allowed for a limited circle of engineers.

Exceptions are documented in writing and removed immediately after merging so they do not remain forever.

After that, the rules are restored: mandatory checks, prohibition of direct edits to the main branch, auto-deletion of merged branches.

The transition to GitHub Flow is considered complete when no branch older than a few days remains in the repository, and the path from change to main takes hours instead of weeks.

Common technical doubts before transition

The transition to a model with short-lived branches almost always runs into the same common questions: what to do with large changes, how many open pull requests it is reasonable to keep, and whether branch protection will turn into a formality that people start bypassing. Below are answers on the merits, so the decision is made on numbers, not fears.

The change is large and does not fit into one branch — what then?

The change is split into a chain of small merges, each of which lives for a day or two. The unfinished part goes into the main branch behind a feature flag: the code is in place, the behavior is off.

The review remains readable, and rollback comes down to turning off one flag rather than rewriting the branch.

How many open pull requests is it normal to keep?

A guideline — no more than two or three days of life for one pull request. Beyond that, the volume of conflicts and the cost of review grow: edits have to be checked against the main branch that has already moved ahead.

A hard limit on the number of simultaneous pull requests per engineer and automatic rebuild from the current state before merging help.

We release versions rarely — is the approach applicable at all?

Yes, the model is about the readiness of the main branch, not about release frequency. The branch is always in a state that can be delivered to users, and a release is a tag on a specific commit.

Therefore, infrequent releases do not require a separate long-lived branch and back-merging dozens of edits.

Branch protection rules will start being bypassed. How is this controlled?

Usually, people bypass restrictions whose purpose has not been explained. A working minimum: prohibition of direct pushes to the main branch, mandatory review, and a mandatory set of build and test checks.

Exceptions are granted by a separate role for a limited time and remain in the log — the rule stops being a matter of personal agreements.

Let's discuss your task — we'll see which branch protection rules the team can sustain and where short branches are enough.

Let's discuss your branching scheme and release rules

To discuss the task, a link to the repository and a couple of phrases about how releases are currently made are enough.

If a branching scheme already exists, send its description and branch protection rules; if not — tell us who makes changes and how, and how many people work in parallel.

We return an implementation plan and an estimate of work, not general recommendations. The response will include:

  • analysis of the current scheme: where branches diverge for a long time, which merges happen without checks, and what this breaks
  • target scheme on GitHub Flow: branch naming rules, short branch lifetime, moment of merging into the main branch
  • step-by-step implementation plan: order of enabling main branch protection, mandatory checks, and review
  • estimate of work: stages, timelines, what we do and what remains on your side
  • access map: who is the repository owner, who reviews changes, who needs write access
  • templates: pull request description, release checklist, release versioning rules
  • repository handover: documentation on the branching scheme, settings, team training on live examples

We separately discuss rollback: if the new release process interferes with work, returning to the previous rules takes one step and does not require rewriting history.

Describe the task in free form — we will return with a plan and an estimate.