Chapter 17: Exploring Common Git Workflows

Chapter Objectives

By the end of this chapter, you will be able to:

  • Understand that a Git workflow is a prescribed sequence of actions or a branching model to manage software development.
  • Describe the Centralized Workflow and its suitability for small teams or projects transitioning from systems like SVN.
  • Explain the Feature Branch Workflow and its benefits for parallel development and code review.
  • Detail the Gitflow Workflow, including its specific branch types (master/main, develop, feature, release, hotfix) and their purposes.
  • Understand the Forking Workflow, commonly used in open-source projects, involving personal server-side forks.
  • Analyze the considerations for choosing an appropriate Git workflow based on team size, project type, release frequency, and collaboration model.

Introduction

Having mastered the fundamental Git commands for branching, merging, rebasing, and interacting with remotes, the next step is to understand how these tools are orchestrated in a collaborative development environment. A Git workflow is essentially a recipe or a strategy that a team agrees upon for using Git to manage their work. It dictates how branches are used, when and how changes are integrated, and how releases are managed.

Choosing the right workflow (or adapting one) is crucial for maintaining a clean project history, enabling parallel development, facilitating code reviews, and ensuring smooth integration and deployment processes. Different workflows cater to different needs, team sizes, and project complexities. There’s no one-size-fits-all solution, but understanding common patterns will help you select or devise a workflow that best suits your situation.

In this chapter, we’ll explore several popular Git workflows: the simple Centralized Workflow, the widely adopted Feature Branch Workflow, the more structured Gitflow Workflow, and the Forking Workflow prevalent in open-source development. We’ll discuss the mechanics, advantages, and disadvantages of each, providing you with the knowledge to make informed decisions about how your team can best leverage Git’s power.

Theory

A Git workflow defines a set of guidelines for how a team uses Git. This includes conventions for:

  • Branching strategy: What kinds of branches are used (e.g., feature, release, hotfix), how they are named, and their lifespan.
  • Committing changes: How often to commit, what makes a good commit message.
  • Integrating changes: When to merge or rebase, how code reviews are incorporated.
  • Handling releases and hotfixes: Specific procedures for preparing and deploying software.

Let’s examine some common workflows.

1. Centralized Workflow

The Centralized Workflow is one of the simplest and is often familiar to teams transitioning from centralized version control systems like Subversion (SVN). In this model, there is a single central repository (often named origin), and developers clone this repository, make changes locally, and then push their changes back to the central repository for others to pull.

Key Characteristics:

  • Single “source of truth”: The origin remote.
  • Main branch: Typically, development happens directly on a single main branch (e.g., main or master).
  • Process:
    1. Clone the central repository.
    2. Pull the latest changes from the central repository before starting work.
    3. Make local commits.
    4. Pull again and resolve any conflicts if others have pushed changes.
    5. Push local changes to the central repository.
%%{ init: { "theme": "base", "themeVariables": {
  "primaryColor": "#EDE9FE", "primaryTextColor": "#5B21B6", "primaryBorderColor": "#5B21B6",
  "lineColor": "#5B21B6", "textColor": "#1F2937",
  "mainBkg": "#DBEAFE", "nodeBorder": "#2563EB", "nodeTextColor": "#1E40AF",
  "secondaryColor": "#D1FAE5", "secondaryBorderColor": "#059669", "secondaryTextColor": "#065F46",
  "tertiaryColor": "#FEF3C7", "tertiaryBorderColor": "#D97706", "tertiaryTextColor": "#92400E"
} } }%%
graph TD
    subgraph Central Server
        direction LR
        OriginMain["<b>origin/main</b><br>Central Repository's Main Branch"]
        style OriginMain fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    end

    subgraph Developer A
        direction TB
        A_Clone["Clone<br>Central Repo"] -- "git clone" --> A_LocalMain["Local main"]
        A_LocalMain -- "Work locally" --> A_Commit["Commit Changes"]
        A_Commit -- "git pull origin main<br>(fetch & merge)" --> A_Resolve["Resolve Conflicts (if any)"]
        A_Resolve -- "git push origin main" --> OriginMain
        OriginMain -- "git pull origin main" --> A_LocalMain
        style A_Clone fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        style A_LocalMain fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        style A_Commit fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        style A_Resolve fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
    end

    subgraph Developer B
        direction TB
        B_Clone["Clone<br>Central Repo"] -- "git clone" --> B_LocalMain["Local main"]
        B_LocalMain -- "Work locally" --> B_Commit["Commit Changes"]
        B_Commit -- "git pull origin main<br>(fetch & merge)" --> B_Resolve["Resolve Conflicts (if any)"]
        B_Resolve -- "git push origin main" --> OriginMain
        OriginMain -- "git pull origin main" --> B_LocalMain
        style B_Clone fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        style B_LocalMain fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        style B_Commit fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        style B_Resolve fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
    end

    A_LocalMain -.-> B_LocalMain_Update("Developer B pulls<br>Developer A's changes")
    B_LocalMain -.-> A_LocalMain_Update("Developer A pulls<br>Developer B's changes")
    style A_LocalMain_Update fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF,stroke-dasharray: 5 5
    style B_LocalMain_Update fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF,stroke-dasharray: 5 5
    
    linkStyle default interpolate basis

Pros:

  • Simple to understand and implement, especially for small teams.
  • Familiar to those coming from SVN.

Cons:

  • Conflict resolution can be tricky: If multiple developers are working on the same code on the same branch, merge conflicts can become frequent and complex when pushing. The last person to push often has to resolve conflicts from everyone else’s work.
  • Less support for parallel development: Everyone working on the same branch can lead to instability if changes are not carefully coordinated.
  • No isolated development: Difficult to work on larger features without impacting others.

When to Use:

  • Very small teams (1-2 developers).
  • Simple projects with minimal parallel work.
  • As a transition from SVN, before adopting more complex branching models.

2. Feature Branch Workflow

The Feature Branch Workflow is one of the most common and fundamental Git workflows. The core idea is that all feature development takes place in a dedicated branch instead of directly on the main branch (main or master).

Key Characteristics:

  • Main branch (main or master): This branch always contains production-ready code (or the latest stable development state). Direct commits to this branch are usually discouraged or disallowed.
  • Feature branches: For every new feature, bug fix, or task, a developer creates a new branch, typically from the latest state of main.
    • Branch names are often descriptive (e.g., feature/user-authentication, bugfix/login-error, refactor/database-module).
  • Development process:
    1. Create a new feature branch from main: git checkout -b feature/new-thing main
    2. Develop the feature on this branch, making local commits.
    3. Push the feature branch to the central remote repository: git push -u origin feature/new-thing. This allows for collaboration on the feature or serves as a backup.
    4. When the feature is complete, open a Pull Request (PR) or Merge Request (MR) on platforms like GitHub, GitLab, or Bitbucket. This initiates a code review and discussion process.
    5. Once reviewed and approved, the feature branch is merged into main.
    6. The feature branch can then be deleted locally and remotely.
%%{ init: { "theme": "base", "themeVariables": {
  "primaryColor": "#EDE9FE", "primaryTextColor": "#5B21B6", "primaryBorderColor": "#5B21B6",
  "lineColor": "#5B21B6", "textColor": "#1F2937",
  "mainBkg": "#DBEAFE", "nodeBorder": "#2563EB", "nodeTextColor": "#1E40AF",
  "secondaryColor": "#D1FAE5", "secondaryBorderColor": "#059669", "secondaryTextColor": "#065F46",
  "tertiaryColor": "#FEF3C7", "tertiaryBorderColor": "#D97706", "tertiaryTextColor": "#92400E",
  "git0": "#EDE9FE", "git1": "#DBEAFE", "git2": "#FEF3C7", "git3": "#FEE2E2", "git4":"#D1FAE5"
} } }%%

graph TD
    Start["Start: <b>main</b> branch (stable)"] --> CreateBranch["Create <b>feature/new-thing</b><br>from <b>main</b>"]
    style Start fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    style CreateBranch fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF

    CreateBranch --> Develop["Develop on <b>feature/new-thing</b><br>(commits C1, C2, C3)"]
    style Develop fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF

    Develop --> PushFeature["Push <b>feature/new-thing</b><br>to remote (origin)"]
    style PushFeature fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF

    PushFeature --> OpenPR["Open Pull Request (PR)<br><b>feature/new-thing</b> to <b>main</b>"]
    style OpenPR fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E

    OpenPR --> CodeReview["Code Review & Discussion"]
    style CodeReview fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E

    CodeReview -- "Approved" --> MergePR["Merge PR into <b>main</b>"]
    style MergePR fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46

    CodeReview -- "Changes Requested" --> Develop
    
    MergePR --> DeleteBranch["Delete <b>feature/new-thing</b><br>(local & remote)"]
    style DeleteBranch fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    
    DeleteBranch --> End["<b>main</b> updated with new feature"]
    style End fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46

    subgraph "Remote Repository (origin)"
        RMain["<b>main</b>"]
        RFeature["<b>feature/new-thing</b> (pushed)"]
        style RMain fill:#EDE9FE,stroke:#5B21B6,stroke-width:1px,color:#5B21B6
        style RFeature fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    end
    PushFeature -.-> RFeature
    MergePR -.-> RMain

Pros:

  • Isolation: Features are developed in isolation, preventing unstable code from affecting the main codebase.
  • Parallel Development: Multiple developers can work on different features simultaneously without interfering with each other.
  • Code Review: Facilitates code reviews through pull/merge requests before changes are integrated into main.
  • Clearer History: While main might have merge commits, the history of each feature is contained within its branch.
  • Collaboration: Multiple developers can collaborate on the same feature branch.

Cons:

  • Requires discipline in keeping feature branches short-lived and regularly updated (rebased or merged) from main to avoid large, complex merges later.

When to Use:

  • Suitable for almost any project size, from solo developers to large teams.
  • The foundation for many other more complex workflows.
  • Highly recommended for projects hosted on platforms that support pull/merge requests.

3. Gitflow Workflow

The Gitflow Workflow, proposed by Vincent Driessen, is a more structured and robust branching model designed for projects with scheduled release cycles. It defines specific roles for different branches and how they interact.

Key Branches:

  • master (or main): This branch stores the official release history. It should always reflect a production-ready state. Commits to master are typically tagged with version numbers (e.g., v1.0.0, v1.0.1). Only release branches and hotfix branches are merged into master.
  • develop: This is the main integration branch for ongoing development. It contains the latest delivered development changes for the next release. When features are complete, they are merged into develop. This branch is the source for release branches.

Supporting Branches:

  • Feature Branches (feature/*):
    • Branched from: develop
    • Merged back into: develop
    • Naming convention: feature/feature-name (e.g., feature/user-profile)
    • Purpose: For developing new features. They exist as long as the feature is in development, but are eventually merged back into develop (or discarded).
  • Release Branches (release/*):
    • Branched from: develop (when develop is deemed feature-complete for an upcoming release).
    • Merged back into: master (or main) AND develop.
    • Naming convention: release/version-number (e.g., release/1.2.0)
    • Purpose: To prepare for a new production release. This branch allows for last-minute bug fixes, documentation generation, and other release-oriented tasks without disrupting the develop branch. No new features are added here.
  • Hotfix Branches (hotfix/*):
    • Branched from: master (or main) (specifically, from a tagged release commit).
    • Merged back into: master (or main) AND develop.
    • Naming convention: hotfix/issue-number or hotfix/version-number (e.g., hotfix/001 or hotfix/1.0.1)
    • Purpose: To quickly patch production releases. They arise from the need to immediately address an issue in a live production version.
%%{ init: { "theme": "base", "themeVariables": {
  "primaryColor": "#EDE9FE", "primaryTextColor": "#5B21B6", "primaryBorderColor": "#5B21B6",
  "lineColor": "#5B21B6", "textColor": "#1F2937",
  "mainBkg": "#DBEAFE", "nodeBorder": "#2563EB", "nodeTextColor": "#1E40AF", /* Process Node */
  "secondaryColor": "#D1FAE5", "secondaryBorderColor": "#059669", "secondaryTextColor": "#065F46", /* End/Success Node */
  "tertiaryColor": "#FEF3C7", "tertiaryBorderColor": "#D97706", "tertiaryTextColor": "#92400E", /* Decision Node */
  "git0": "#FEE2E2", "git0BorderColor": "#DC2626", "git0TextColor": "#991B1B" /* Check/Validation Node */
} } }%%
graph LR 
    subgraph "Long-Lived Branches"
        Master["<b>master (main)</b><br>Production Releases, Tagged"]
        style Master fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6

        Develop["<b>develop</b><br>Main Integration Branch"]
        style Develop fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    end
    
    Master <-. Tagged Release .-> ReleaseBranchEnd
    Develop <-. Merge Back .-> ReleaseBranchEnd
    Develop <-. Merge Back .-> HotfixBranchEnd
    Master <-. Tagged Hotfix .-> HotfixBranchEnd

    subgraph "Feature Development"
        direction TB
        Develop -- "Branch off for new feature" --> FeatureStart["Create <b>feature/X</b>"]
        style FeatureStart fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        FeatureStart --> FeatureDev["Develop feature X<br>Commit C1, C2"]
        style FeatureDev fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        FeatureDev -- "Feature complete" --> FeatureMerge["Merge <b>feature/X</b> into <b>develop</b>"]
        style FeatureMerge fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46
        FeatureMerge --> Develop
    end

    subgraph "Release Preparation"
        direction TB
        Develop -- "Develop is feature-complete<br>for upcoming release" --> ReleaseStart["Create <b>release/1.0</b><br>from <b>develop</b>"]
        style ReleaseStart fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        ReleaseStart --> ReleaseTasks["Release tasks:<br>Bug fixes, Docs, Version Bump"]
        style ReleaseTasks fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
        ReleaseTasks -- "Ready for release" --> ReleaseBranchEnd("Merge <b>release/1.0</b> to:<br>1. <b>master</b> (tag v1.0)<br>2. <b>develop</b>")
        style ReleaseBranchEnd fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46
    end

    subgraph "Hotfix for Production Issue"
        direction TB
        Master -- "Critical bug in production<br>(e.g., on tag v1.0)" --> HotfixStart["Create <b>hotfix/1.0.1</b><br>from <b>master</b> (tag v1.0)"]
        style HotfixStart fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B
        HotfixStart --> HotfixDev["Fix critical bug<br>Commit H1"]
        style HotfixDev fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B
        HotfixDev -- "Fix verified" --> HotfixBranchEnd("Merge <b>hotfix/1.0.1</b> to:<br>1. <b>master</b> (tag v1.0.1)<br>2. <b>develop</b>")
        style HotfixBranchEnd fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46
    end
    
    linkStyle default interpolate basis

Pros:

  • Highly Structured: Provides a clear, robust framework for managing larger projects and scheduled releases.
  • Parallel Development: develop branch allows for integration while master remains stable. Feature branches enable parallel work.
  • Dedicated Release Preparation: Release branches allow for stabilization without freezing feature development.
  • Clear Hotfix Process: Well-defined way to address urgent production issues.

Cons:

  • Complexity: Can be overly complex for smaller projects or projects that practice continuous delivery/deployment (where develop might be redundant if main is always deployable and features are merged directly).
  • More Branches to Manage: Increases the number of active branches.
  • The develop branch can sometimes become a bottleneck or a long-running integration point if not managed carefully.

When to Use:

  • Projects with scheduled release cycles.
  • Larger projects requiring a formal process for development, testing, and release.
  • When you need to support multiple versions of your software in production simultaneously (though hotfixes are more about patching the latest release).

4. Forking Workflow

The Forking Workflow is fundamentally different from the previous workflows in that it involves multiple server-side repositories. It’s particularly common in public open-source projects where many contributors are involved, and direct push access to the main repository is restricted.

Key Characteristics:

  • Canonical Repository: A single, official server-side repository (often called the “upstream” repository).
  • Developer Forks: Instead of cloning the canonical repository directly to work, each developer forks the canonical repository on the hosting platform (e.g., GitHub, GitLab). This creates a personal server-side copy of the repository under their own account.
  • Local Clones: Developers clone their own fork to their local machine.
  • Development Process:
    1. Fork the canonical (upstream) repository on the hosting platform.
    2. Clone your personal fork to your local machine: git clone https://githost.com/your-username/project.git
    3. Add the canonical repository as a remote, typically named upstream: git remote add upstream https://githost.com/original-owner/project.git
    4. Create a feature branch in your local repository (based on the latest from upstream/main or upstream/develop).
    5. Develop the feature, making local commits.
    6. Push the feature branch to your fork on the server: git push origin feature/new-thing (where origin points to your fork).
    7. Open a Pull Request (PR) or Merge Request (MR) from the feature branch on your fork to the appropriate branch (e.g., main or develop) on the canonical (upstream) repository.
    8. Project maintainers review the PR. If approved, they merge it into the canonical repository.
    9. To keep your local fork’s main branch up-to-date, periodically pull changes from upstream/main and push them to origin/main (your fork’s main).
%%{ init: { "theme": "base", "themeVariables": {
  "primaryColor": "#EDE9FE", "primaryTextColor": "#5B21B6", "primaryBorderColor": "#5B21B6", /* Upstream Repo */
  "lineColor": "#5B21B6", "textColor": "#1F2937",
  "mainBkg": "#DBEAFE", "nodeBorder": "#2563EB", "nodeTextColor": "#1E40AF", /* Process Node */
  "secondaryColor": "#D1FAE5", "secondaryBorderColor": "#059669", "secondaryTextColor": "#065F46", /* Success/Merge Node */
  "tertiaryColor": "#FEF3C7", "tertiaryBorderColor": "#D97706", "tertiaryTextColor": "#92400E" /* PR/Decision Node */
} } }%%
graph TD
    subgraph "Canonical Project (e.g., on GitHub)"
        UpstreamRepo["<b>Upstream Repository</b><br>(original-owner/project)"]
        style UpstreamRepo fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    end

    subgraph "Developer A's Environment"
        ForkA["1- Fork Upstream Repo<br>to <b>Developer A's Fork</b><br>(your-username/project)"]
        style ForkA fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        CloneA["2- Clone <b>Developer A's Fork</b><br>to Local Machine"]
        style CloneA fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        AddUpstreamA["3- Add Upstream Remote<br><code style='font-family:monospace;background-color:#E5E7EB;padding:1px 3px;border-radius:3px;'>git remote add upstream ...</code>"]
        style AddUpstreamA fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        
        SyncA["4- Sync with Upstream<br><code style='font-family:monospace;background-color:#E5E7EB;padding:1px 3px;border-radius:3px;'>git fetch upstream</code><br><code style='font-family:monospace;background-color:#E5E7EB;padding:1px 3px;border-radius:3px;'>git merge upstream/main</code>"]
        style SyncA fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        
        BranchA["5- Create Feature Branch<br>e.g., <b>feature/X</b>"]
        style BranchA fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        
        DevelopA["6- Develop Feature, Commit Locally"]
        style DevelopA fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        
        PushToForkA["7- Push Feature Branch<br>to <b>Developer A's Fork (origin)</b>"]
        style PushToForkA fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
        
        PRA["8- Open Pull Request (PR)<br>from Fork's <b>feature/X</b> to Upstream's <b>main</b>"]
        style PRA fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
        
        ForkA --> CloneA --> AddUpstreamA --> SyncA --> BranchA --> DevelopA --> PushToForkA --> PRA
    end
    
    UpstreamRepo -- Fork Action --> ForkA
    PRA -- "Maintainers Review & Merge" --> UpstreamRepoUpdated["Upstream Repo Updated"]
    style UpstreamRepoUpdated fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46
    UpstreamRepoUpdated --> SyncA_Later["Developer A later syncs<br>local & fork with updated Upstream"]
    style SyncA_Later fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    
    %% Simplified representation of Developer B doing the same
    subgraph "Developer B (Similar Process)"
        ForkB["Fork Upstream to <b>Dev B's Fork</b>"]
        style ForkB fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF,opacity:0.7
        PRB["Open PR from <b>Dev B's Fork</b>"]
        style PRB fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E,opacity:0.7
    end
    UpstreamRepo -- Fork Action --> ForkB
    PRB -- "Maintainers Review & Merge" --> UpstreamRepoUpdated

    linkStyle default interpolate basis

Pros:

  • Clean Project History: Maintainers of the canonical repository have full control over what gets merged, ensuring high quality and a clean history.
  • Contributions without Direct Access: Allows contributions from anyone without giving them direct push access to the main repository. Ideal for open-source.
  • Isolation: Each contributor works in their own fork, preventing accidental pushes to the main codebase.

Cons:

  • More Steps: Involves managing an extra remote (your fork) and keeping it synchronized with the upstream repository.
  • Can be slightly more complex for contributors to set up initially (forking, adding remotes).

When to Use:

  • Open-source projects.
  • Large, distributed teams where direct push access to the main repository needs to be restricted.
  • Situations where you want to experiment with changes to a project you don’t own, with the possibility of contributing back.
Workflow Key Characteristics Pros Cons When to Use
Centralized Workflow
  • Single central repository (origin).
  • Development typically on a single main branch (e.g., main or master).
  • Developers clone, pull, commit locally, pull again, then push.
  • Simple to understand and implement.
  • Familiar for teams from SVN.
  • Conflict resolution can be complex if many push to the same branch.
  • Less support for parallel development.
  • Difficult for isolated feature development.
  • Very small teams (1-2 developers).
  • Simple projects with minimal parallel work.
  • Transitioning from SVN.
Feature Branch Workflow
  • main branch is production-ready.
  • All new development in dedicated feature branches (e.g., feature/user-auth).
  • Feature branches merged into main via Pull/Merge Requests (PRs/MRs).
  • Isolation of features.
  • Enables parallel development.
  • Facilitates code review via PRs.
  • Clearer history for features.
  • Requires discipline to keep feature branches short-lived.
  • Need to regularly update feature branches from main.
  • Almost any project size.
  • Projects using platforms with PR/MR support (GitHub, GitLab).
  • Foundation for other workflows.
Gitflow Workflow
  • Long-lived branches: master (production releases) and develop (integration).
  • Supporting branches: feature/*, release/*, hotfix/*.
  • Strict rules for branching and merging.
  • Highly structured, robust for scheduled releases.
  • Clear separation of concerns (development, release, hotfix).
  • Supports parallel development well.
  • Can be complex for small projects or continuous delivery.
  • More branches to manage.
  • develop can become a bottleneck.
  • Projects with scheduled release cycles.
  • Larger projects needing formal processes.
  • Supporting multiple versions in production (via hotfixes).
Forking Workflow
  • Each developer has their own server-side fork of the canonical repository.
  • Canonical repository often called upstream. Developer’s fork is origin.
  • Contributions via Pull Requests from fork to upstream.
  • Clean project history in canonical repo.
  • Allows contributions without direct push access.
  • Strong isolation for contributors.
  • More steps (forking, managing remotes).
  • Slightly more complex setup for contributors.
  • Open-source projects.
  • Large, distributed teams.
  • Restricted push access to main repository.

Choosing a Workflow

There’s no single “best” workflow. The choice depends on several factors:

  • Team Size and Structure: Smaller teams might thrive with a simple Feature Branch Workflow. Larger, more distributed teams or open-source projects often benefit from the Forking Workflow.
  • Project Type and Complexity: Simple projects might not need the overhead of Gitflow. Complex projects with multiple versions and scheduled releases might find Gitflow’s structure beneficial.
  • Release Cycle: Projects with continuous deployment might simplify Gitflow or use a Feature Branch Workflow where main is always deployable. Projects with distinct versions and longer release cycles might suit Gitflow.
  • Existing Practices: If your team is used to a certain model (e.g., from SVN), a gradual transition might be best, starting with a Centralized or simple Feature Branch Workflow.
  • Tools Used: Platforms like GitHub, GitLab, and Bitbucket are built around the concept of Pull/Merge Requests, which integrate seamlessly with Feature Branch and Forking Workflows.

Many teams also adopt hybrid approaches, taking elements from different workflows to create something that fits their specific needs. The key is clear communication and consistency within the team.

Practical Examples (Conceptual)

Since workflows are more about process and branch management strategy than specific file changes, these examples will focus on the typical command sequences for each workflow. Assume a remote repository named origin exists for Centralized, Feature Branch, and Gitflow. For Forking, origin is the developer’s fork, and upstream is the canonical repository.

1. Centralized Workflow Example Steps

Alice clones and works:

Bash
git clone https://githost.com/org/project.git
cd project
# ... Alice makes changes to files ...
git add .
git commit -m "Alice's initial work"
git push origin main

Bob clones, pulls Alice’s changes, and works:

Bash
git clone https://githost.com/org/project.git
cd project
# At this point, Alice might have pushed. Bob pulls.
# git pull origin main # (This is 'fetch' then 'merge')
# ... Bob makes changes ...
git add .
git commit -m "Bob's contribution"
# Before pushing, Bob should pull again to get any new changes
git pull origin main # If conflicts, resolve them, then commit merge
git push origin main

2. Feature Branch Workflow Example Steps

Recap on Branching Strategies:

Strategy Element Description Considerations / Purpose
Main/Master Branch The primary branch representing the official project history. In many workflows, this branch reflects production-ready code.
  • Should always be stable and deployable.
  • Direct commits are often restricted; changes come via merges from other branches.
  • Tagged for releases (e.g., v1.0, v1.1).
Develop Branch (e.g., in Gitflow) An integration branch for ongoing development. Contains the latest development changes for the next release.
  • Serves as a collection point for completed features.
  • Source for release branches.
  • Aims to be more stable than feature branches but not necessarily production-ready at all times.
Feature Branches Used to develop new features or make specific changes in isolation. Typically short-lived.
  • Branch off from develop (in Gitflow) or main (in simpler Feature Branch workflows).
  • Allows parallel development without destabilizing the main codebase.
  • Merged back into the parent branch (e.g., develop or main) after review, often via Pull Requests.
  • Naming convention: e.g., feature/user-authentication, bugfix/login-error.
Release Branches (e.g., in Gitflow) Used to prepare for a new production release. Allows for final bug fixes, documentation, and other release-oriented tasks.
  • Branch off from develop when it’s feature-complete for a release.
  • No new features are added here, only stabilization work.
  • Merged into master (and tagged) and also back into develop (to incorporate fixes).
  • Naming convention: e.g., release/1.2.0.
Hotfix Branches (e.g., in Gitflow) Used to quickly patch production releases to address critical bugs.
  • Branch off from a tagged commit on master.
  • Addresses only the critical issue.
  • Merged into master (and new tag created) and also back into develop (and potentially active release branches).
  • Naming convention: e.g., hotfix/1.0.1.
Branch Naming Conventions Consistent naming for branches to indicate their purpose and scope.
  • Improves clarity and organization.
  • Examples: feature/name, bugfix/issue-id, hotfix/version, release/version.
  • Can include issue tracker IDs for better traceability (e.g., feature/PROJ-123-new-dashboard).
Pull/Merge Requests A mechanism for proposing changes, discussing them, and conducting code reviews before integrating them into a target branch.
  • Central to Feature Branch, Gitflow, and Forking workflows.
  • Enhances code quality and team collaboration.
  • Provides a documented history of changes and decisions.

Developer starts a new feature:

Bash
# Ensure main is up-to-date
git checkout main
git pull origin main

# Create and switch to a new feature branch
git checkout -b feature/user-login

Develop the feature:

Bash
# ... make changes, add files ...
git add .
git commit -m "Implement login form UI"
# ... more changes and commits ...
git add .
git commit -m "Add login logic"

Push the feature branch to remote:

Bash
git push -u origin feature/user-login

Open a Pull Request (on GitHub/GitLab etc.) from feature/user-login to main.

After review and approval, merge (often done via the platform’s UI):

(If merging locally by a maintainer)

Bash
git checkout main
git pull origin main # Get latest main
git merge --no-ff feature/user-login # Merge feature branch (prefer --no-ff to keep history of feature)
git push origin main

Delete the feature branch (optional but good practice):

Bash
git branch -d feature/user-login      # Delete local branch
git push origin --delete feature/user-login # Delete remote branch

3. Gitflow Workflow Example Steps (Simplified)

Setup (one-time, or using git flow init helper tool):

  • Create master and develop branches. master has initial commit. develop branches from master.

Starting a new feature:

Bash
git checkout develop
git pull origin develop # Get latest develop
git checkout -b feature/new-cool-feature develop

Finishing a feature:

Bash
# ... develop on feature/new-cool-feature, commit changes ...
git push -u origin feature/new-cool-feature # Optional: push for backup/collaboration

# Merge feature into develop
git checkout develop
git pull origin develop
git merge --no-ff feature/new-cool-feature
git push origin develop
git branch -d feature/new-cool-feature

Starting a release:

Bash
git checkout -b release/1.0.0 develop
# ... perform release tasks: bump version, final tests, docs ...
# Make commits on release/1.0.0 for these tasks

Finishing a release:

Bash
# Merge into master
git checkout master
git pull origin master
git merge --no-ff release/1.0.0
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin master
git push origin v1.0.0 # Push tag

# Merge back into develop (to get release bugfixes into develop)
git checkout develop
git pull origin develop
git merge --no-ff release/1.0.0
git push origin develop

# Delete release branch
git branch -d release/1.0.0
# git push origin --delete release/1.0.0 (if pushed)

Starting a hotfix:

Bash
git checkout -b hotfix/1.0.1 master # Or from specific tag v1.0.0
# ... fix the bug, commit ...

Finishing a hotfix:

(Similar to finishing a release: merge to master, tag, merge to develop, delete hotfix branch)

4. Forking Workflow Example Steps

Fork upstream_owner/project on GitHub/GitLab to your_username/project.

Clone your fork:

Bash
git clone https://githost.com/your_username/project.git
cd project

Add upstream remote:

Bash
git remote add upstream https://githost.com/upstream_owner/project.git

Start a new feature (syncing with upstream first):

Bash
git checkout main # Or develop, depending on upstream's main dev branch
git fetch upstream
git merge upstream/main # Or rebase: git rebase upstream/main
git push origin main # Update your fork's main (optional, good practice)

git checkout -b feature/awesome-addition main

Develop the feature and commit:

Bash
# ... make changes ...
git add .
git commit -m "Implement awesome addition"

Push feature branch to your fork (origin):

Bash
git push -u origin feature/awesome-addition

Open a Pull Request on GitHub/GitLab: From your_username/project branch feature/awesome-addition to upstream_owner/project branch main (or develop).

Maintainers review and merge into upstream_owner/project.

Later, update your local main and fork from upstream:

Bash
git checkout main
git fetch upstream
git merge upstream/main
git push origin main

OS-Specific Notes

The concepts and commands related to Git workflows are generally OS-agnostic. The core Git commands for branching, merging, pushing, and pulling behave identically on Windows, macOS, and Linux.

  • Shell Environment: While the Git commands are the same, the shell environment you use (Bash, Zsh on macOS/Linux; PowerShell, CMD, or Git Bash on Windows) might have different scripting capabilities or slightly different ways of handling paths or environment variables if you are scripting parts of your workflow. However, for direct Git command usage as described in these workflows, this is rarely an issue.
  • Tooling: Some helper tools (like git-flow-avh which helps automate Gitflow) might have different installation procedures per OS, but the underlying Git workflow they implement remains the same.

No OS-specific notes are particularly relevant for understanding or implementing these common Git workflows themselves.

Common Mistakes & Troubleshooting Tips

Git Issue / Error Symptom(s) Troubleshooting / Solution
Feature Branch: Messy History Mistake: Merging main into feature branches too often/incorrectly.
Symptom: git log --graph on feature branch is convoluted.
Solution: Prefer rebasing your feature branch onto main (git rebase main after fetching, or git pull --rebase origin main). If merging main in, do it sparingly.
Gitflow: Missing Fixes in Develop Mistake: Forgetting to merge release/* or hotfix/* branches back to develop.
Symptom: Fixes made during release/hotfix are not in ongoing development, causing regressions.
Solution: Always merge release/* and hotfix/* branches into both master (or main) AND develop.
Forking: Pushing to Upstream Mistake: Accidentally trying to push feature branches directly to upstream instead of your fork (origin).
Symptom: Push rejected (common) or unintended direct pushes.
Solution: Ensure origin remote points to your fork and upstream to the canonical repo. Push features to origin; use Pull Requests for upstream.
All Workflows: Long-Lived Feature Branches Mistake: Feature branches active too long without integrating changes from main/develop.
Symptom: Significant divergence, leading to massive merge conflicts.
Solution: Keep feature branches short-lived. Break down large features. Regularly rebase or merge the main development line into your feature branch to resolve conflicts incrementally.
Workflow Choice: Overly Complex Mistake: Using a complex workflow (e.g., Gitflow) for a small team or simple project with continuous deployment.
Symptom: Workflow feels cumbersome, unnecessary overhead.
Solution: Start simple (e.g., Feature Branch Workflow). Introduce complexity only if genuinely needed. Tailor the workflow.
Detached HEAD State Symptom: Commits are made but don’t seem to belong to any branch. Message like “You are in ‘detached HEAD’ state.” Solution: Create a new branch from the current commit: git branch new-branch-name followed by git checkout new-branch-name. Or, if the commits were accidental, checkout an existing branch (e.g., git checkout main) and decide if you need to cherry-pick the detached commits.
Merge Conflicts Symptom: Git reports “Automatic merge failed; fix conflicts and then commit the result.” during a merge or rebase. Solution:
  1. Identify conflicted files: git status.
  2. Open conflicted files. Look for <<<<<<<, =======, >>>>>>> markers.
  3. Edit files to resolve differences, then save.
  4. Stage the resolved files: git add <resolved-file-name>.
  5. Continue the operation: git commit (for merge) or git rebase --continue (for rebase).
Accidentally Committed to Wrong Branch Symptom: Realized recent commits are on main instead of a feature branch. Solution (if not pushed):
  1. Create the correct feature branch from current state: git branch feature/my-feature.
  2. Reset main back to its previous state (e.g., git reset --hard origin/main or git reset --hard HEAD~N where N is number of wrong commits). Be careful with --hard if you have uncommitted changes.
  3. Checkout the feature branch: git checkout feature/my-feature. Your commits will be there.
If pushed, more complex (e.g., git revert or coordinate with team if force push is allowed).

Exercises (Conceptual)

  1. Workflow Selection Scenario:
    • Scenario A: You are a solo developer working on a personal blog website. You want to try out new designs and features before making them live. Which workflow would you choose and why? Briefly outline your typical steps.
    • Scenario B: You are part of a 10-person team developing a commercial software product that has a new version release every three months. Urgent bug fixes for the current live version are sometimes needed. Which workflow seems most appropriate and why? Describe the key branches you would use and their purpose.
    • Scenario C: You want to contribute a bug fix to a popular open-source library hosted on GitHub. You do not have direct push access to its repository. Which workflow would you use? List the main steps involved from getting the code to submitting your fix.
  2. Gitflow Branch Purpose Review:
    • For the Gitflow workflow, explain the primary purpose of each of the following branches and where they typically originate from and merge back into:
      • master (or main)
      • develop
      • feature/*
      • release/*
      • hotfix/*

Summary

  • A Git workflow is a strategy for using Git, defining branching models and integration processes.
  • Centralized Workflow: Simple, single main branch focus. Good for small teams or SVN transitions.
  • Feature Branch Workflow: Develop features in isolated branches, merge to main via Pull/Merge Requests. Widely applicable and promotes code review.
  • Gitflow Workflow: Structured model with master, develop, feature, release, and hotfix branches. Suited for projects with scheduled releases.
  • Forking Workflow: Contributors fork a canonical repository, push to their fork, and submit Pull Requests. Common in open-source.
  • Choosing a Workflow: Consider team size, project complexity, release cycle, and collaboration needs. Start simple and adapt as needed.

Understanding and consistently applying a suitable Git workflow is key to effective team collaboration, maintaining a clean project history, and streamlining the development and release process.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top