Git Workflows, Principally

 |  Writings

Are modern Git workflows actually competing? What is the purpose of each? This writing is to help you decide for yourself. Fewer words, more substance.

Contents

The Context

I always wondered why Git workflows seemed so opposite to one another and so awkward to my personal rationale when reading about them in numerous public articles.

It occurred to me that the articles were just SEO pieces. They merely presented a set of words in context with no actual meaning or purpose for software development professionals.

One day, I set out to formulate my own understanding of the subject, and here it is: all three Git workflows (Git Flow, GitHub Flow and trunk-based development) cannot be used interchangeably at will. The digital product's life stage 1 dictates the code functional weight we incorporate to a mainline. And that, in turn, dictates which Git workflow to use. The workflows may change as the product life stage dictates.

Code Functional Weight Dictates Workflow

The code functional weight, not a Git workflow, drives the set of granular Git workflow techniques.

There are basically 3 types of code functional weights: functionally heavy, moderate and light. At different product life cycle stages we may see first one before launch, and two others after launch in active development or maturity / support stages.

Moreover, some products may contain the different functional weight code changes at the same time after launch in different parts of the product:

  • heavy: big substance features not feasible to be launched in parts;
  • middle: lightweight substantial features;
  • light: fixes, patches, small improvements or changes;

For functionally-heavy we may use Git Flow. For functionally-low we may use GitHub Flow or trunk-based development.

Align Workflows with Code Functional Weight

How Workflows align with the code functional weight:

  1. Small, Incremental Work (Bug Fixes, Minor Updates, Patches):

Starting Point: Trunk-Based Development or GitHub Flow.

  • Trunk-Based: For quick, small updates or bug fixes that are continuously integrated into the main branch with minimal overhead.
  • GitHub Flow: Suitable for lightweight features or small fixes, as long as they can be merged quickly into the main branch.
  1. Medium to Larger Features (Modular Features, Enhancements, Non-Disruptive Changes):

Starting Point: GitHub Flow or Git Flow

  • GitHub Flow or Git Flow: For features that can be completed in a shorter cycle, reviewed, and merged into mainline without disrupting production.
  • Trunk-Based: Later in product life cycle when multiple small increments can be continuously integrated into the main branch (with feature flags if necessary; beware of feature flags imposed issues).
  1. Large, Complex Features (Multiple Components, Long Development Cycles, Release Planning):

Starting Point: GitHub Flow or Git Flow

  • Git Flow: Perfect for larger, more complex features where you need a more structured approach with separate branches for development (develop), release preparation (release), and hotfixes. These large features can be worked on over a longer period and require more careful planning and testing.

Principally Better

But we can make things principally better.

We could combine any the techniques from each of Git workflows into a specific flow that meets our requirements at any given time and for any product state. All we need is to know and learn to apply all of the techniques.

The List of Techniques

Technique/Approach Benefit Reason Used In
Long-lived Feature Branches Isolates new development work from mainline Allows developers to work on features without affecting the codebase in mainline Git Flow, GitHub Flow
Permanent Develop Branch Always releasable code. Integrates the finished functionality. Reflects the finished development history. Accumulates the code ready for releases. Git Flow
Release Branches Facilitates final testing and bug fixes before release Helps isolate release preparation work from ongoing feature development Git Flow
Hotfix Branches Allows urgent fixes to be applied to `main` without affecting `develop` Supports rapid resolution of production issues while maintaining the integrity of `develop` Git Flow
Master Branch Only production code. Reflects the latest production state and previous production states with version tags. Guarantees that mainline is always stable and ready for release Git Flow
Mainline Branch Always releasable code. Integrates the finished functionality. Reflects the past production states and is ready to be released. Guarantees mainline is always stable and ready for release GitHub Flow, Trunk-Based Development
Trunk (mainline) Branch Always kept in a deployable state. Accepts commits continuously. Enforces small commits and very short-lived feature. Applicable for small functionality changes. Trunk-Based Development
Merge Strategy (Feature → Develop, Release → Main and Develop) Keeps mainline stable and ensures thorough integration testing Ensures features are tested and validated before they are merged into mainline. Git Flow
Pull Requests (PRs) for Review Enables peer review and collaboration before merging Ensures code quality through review, preventing defects from entering mainline. GitHub Flow
Continuous Integration (CI) Automatically tests code upon commits or PR creation and before merging Prevents defects from being merged into mainline by ensuring that code passes tests before integration Git Flow, GitHub Flow, Trunk-Based Development
Frequent Merges Keeps the codebase up to date and reduces integration problems Minimizes merge conflicts and promotes a smooth, continuous flow of changes Git Flow, GitHub Flow, Trunk-Based Development
Feature Flags Enables incomplete or experimental features to be merged without affecting users Allows larger features to be developed incrementally while maintaining a deployable state. NB: Imposes more issues than solves. For larger features use Git Flow techniques. Trunk-Based Development
No Long-Lived Feature Branches Reduces the risk of divergence between branches Ensures that changes are integrated frequently and avoids prolonged isolation of features. Fits small functionality. Trunk-Based Development

List of Practices and Their Consequences

Practice Consequences
Frequent Integration
  • Reduced merge conflicts.
  • Easier to track and review changes.
  • Faster detection of integration issues.
  • Smoother collaboration and coordination among developers.
Feature Branching
  • Isolation of work, minimizing disruption to the main codebase.
  • Easier to test individual features before merging.
  • Clearer focus on individual tasks or features.
Pull Requests / Code Reviews
  • Peer review and feedback improve code quality.
  • Helps catch bugs and issues before they are merged into the main branch.
  • Increases knowledge sharing across the team.
Continuous Integration (CI)
  • Early detection of issues.
  • Assurance that code is always in a deployable state.
  • Reduces the risk of bugs or integration issues at later stages.
Feature Flags
  • Allows incomplete features to be merged into the main branch without affecting the production environment.
  • Supports continuous delivery with safe rollout of new functionality.
  • Reduces the need for long-lived branches, avoiding long periods without integration.
  • Increases the risk of feature flag imposed issues.
Releasing Regularly
  • Shorter release cycles, leading to quicker time to market.
  • Immediate feedback from users or production, allowing for faster iteration.
  • Ensures the software remains stable and continually improves.
Stable Mainline
  • Reduces risk by ensuring the production-ready code is always in the main branch.
  • Continuous delivery becomes possible with a constantly stable main branch.
  • Increases developer confidence in pushing changes without breaking the codebase.
Clear Version Control
  • Ensures that releases are well-defined and versioned.
  • Easier to track and manage releases, even with rapid development cycles.
  • Keeps teams aligned on product versions, avoiding confusion about which version is in production.

Threats and Damages Counteracted

List of threats and damages that are usually counteracted by good Git workflow practices irrespectively of the concrete Git workflow.

  1. Merge Conflicts: A signal of infrequent integration of long-lived feature branches, leading to inconsistent codebases.
  2. Long Release Cycles: A signal to revise requirements for more granular feature definitions to establish the release plan, delivering valuable product functionality as close to continuous as customer value granularity suggests.
  3. Infrequent Integrations: A signal of stagnant development process.
  4. Unpredictable Deployments: A signal to introduce continuous automated and manual quality enforcement (lint, A/TDD, E2E, code review, acceptance testing).
  5. Lack of Transparency: A signal of infrequent integrations, establish continuous integration process (in any Git workflow).

Conclusion:

The key takeaway is that Git workflows should be driven by the code functional weight. By recognizing the size and complexity of the task at hand, the project can choose the right workflow that supports that functional weight, leading to comfortably error-free development processes.

The second, implied takeaway, is that a concrete product's Git workflow is not that one of the tree monolithic approaches. The concrete Git workflow contains 3-4 dozens of granular tools 1, 2, being purposefully combined together by a software engineer, serving the concrete goals at the always changing digital product life point in time.

Literature

To be able to establish the specifically appropriate Git workflows, study these essential sources:

Footnotes

  1. The following product life stages generally impact the code functional weigh: early prototyping, massive foundational development, market launch, growth, major product adaptations, maturity (small fixes). An engineering team structure, commonly referred as impacting a Git workflow, is actually the consequence of the product life stage as well.