Git Workflows for CI/CD
Explore git workflows for CI/CD in this foundational tutorial. Learn how branching strategies, merge patterns, and protected branches shape your pipelines. Includes hands-on examples and next steps.
Focus: explore git workflows for ci/cd
Does this sound familiar? Your team merges a feature branch, the build goes green locally, and then — boom — the production deploy fails because a teammate merged a conflicting change two minutes earlier. Or worse, someone pushes straight to main, skipping tests entirely, and your CI pipeline never even runs. These are classic symptoms of a git workflow that wasn't designed for CI/CD. In this lesson, we'll explore git workflows for CI/CD — the branching strategies, merge patterns, and protection rules that turn your repository from a source of chaos into the engine that drives predictable, automated delivery.
The problem this lesson solves
CI/CD is only as reliable as the git workflow that feeds it. A messy git history, unprotected branches, or a merge strategy that buries every feature in a tangled web of commits can make your pipelines slow, flaky, and hard to debug. When every developer follows a different process, the pipeline becomes a black box — nobody knows what triggered a build, why it failed, or how to roll back safely.
If you've ever been woken up at 3 a.m. because a git push --force wiped out a teammate's work, or stared at a failed pipeline run wondering which of 50 commits broke the build, you already know the pain this lesson addresses. The goal here is to give you a predictable, repeatable git process that makes CI/CD obvious: every push triggers the right checks, every merge is clean, and every release is traceable.
Core concept / mental model
Think of your git repository as the conveyor belt in a factory, and your CI/CD pipeline as the quality-control station at the end of that belt. The workflow you choose determines what items land on the belt, in what order, and how they're inspected before they reach production.
The core idea is that CI/CD should be driven by branch and merge events, not by human habits. Instead of relying on "please remember to run tests before you push," you design your workflow so that the pipeline must run on every relevant change. This is where branching strategies and protected branches come in.
Three key concepts:
- Branching strategy — the rules for where work happens (e.g., feature branches, long-lived
developbranch, short-livedmain). - Merge strategy — how changes are integrated (merge commits, squash merging, rebase merging). This directly affects your commit history and how CI runs.
- Protected branches — repository rules that prevent direct pushes, require pull requests, and demand passing status checks before merging.
In a CI/CD workflow, you want every change to go through a pull request (or merge request) to a protected branch. That PR becomes the staging ground for your pipeline: lint, test, build, and sometimes even deploy to a preview environment — all before the code ever lands on main.
How it works step by step
Let's walk through a typical feature-branch workflow that supports CI/CD. This is the most common pattern in modern teams, and it's the foundation for everything else in this lesson.
Step 1 — Create a feature branch
Never commit directly to main. Create a short-lived branch for each logical unit of work. This isolates changes and makes it easy to run CI in isolation.
git checkout main
git pull origin main
git checkout -b feat/avatar-upload
Step 2 — Implement and commit locally
Make small, focused commits. Think of each commit as a single logical change — this makes it easier to bisect failures later.
git add .
git commit -m "feat: add avatar upload endpoint"
Step 3 — Push and open a pull request
Push the branch to your remote and open a PR. This is the trigger for your CI pipeline. Most platforms (GitHub, GitLab, Bitbucket) will automatically run checks on the PR head.
git push -u origin feat/avatar-upload
# open PR in the UI
Step 4 — CI validates the PR
Your pipeline runs lint, unit tests, integration tests, and possibly a build. The PR status shows green or red. If something fails, you fix it and push again — the pipeline re-runs automatically.
Step 5 — Merge with the right strategy
Once all checks pass and you get approval (if required), merge the PR. The merge strategy you choose shapes your history (we'll compare them in the next section). After merging, your CI runs again on main — this is often where you build and publish artifacts or deploy to staging.
Step 6 — Deploy from a release branch or tag
For production releases, many workflows use a release branch or a git tag. You cut a release from main, and the CI deploys that specific version, making rollbacks trivial.
Hands-on walkthrough
Let's put this into practice with a minimal example. We'll set up a local repo, simulate a feature branch, and observe how the workflow feeds a CI pipeline.
First, create a new repository and add a simple Python file with a test.
mkdir ci-git-demo && cd ci-git-demo
git init
Create a calculator.py:
def add(a, b):
return a + b
def subtract(a, b):
return a - b
Add a test file test_calculator.py:
import unittest
from calculator import add, subtract
class TestCalculator(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 3), 5)
def test_subtract(self):
self.assertEqual(subtract(5, 3), 2)
if __name__ == "__main__":
unittest.main()
Now let's create a feature branch and simulate the workflow:
git add .
git commit -m "initial project"
git branch -M main
git checkout -b feat/multiply
Modify calculator.py to add multiplication, then commit:
def multiply(a, b):
return a * b
git add calculator.py
git commit -m "feat: add multiply function"
In a real CI environment, pushing this branch to a remote would trigger a pipeline that runs your tests:
python -m unittest discover -v
Expected output:
test_add (test_calculator.TestCalculator) ... ok
test_multiply (test_calculator.TestCalculator) ... ok
test_subtract (test_calculator.TestCalculator) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
Now, to see how CI enforces quality, deliberately introduce a bug:
def multiply(a, b):
return a + b # bug!
Run the tests again — they fail, and in a real PR, the status check would block the merge. This is the power of protecting your main branch.
Compare options / when to choose what
Now that you've seen the basic flow, let's compare the main branching strategies and merge strategies you'll encounter in CI/CD.
Branching strategies
| Strategy | Description | Best for | CI/CD impact |
|---|---|---|---|
| Trunk-based development | Short-lived branches, merge to main multiple times a day |
Teams that want fast feedback and continuous deploys | Simplest CI — every merge triggers a deploy pipeline; avoids long-lived integration headaches |
| GitFlow | Long-lived develop and release branches |
Projects with formal release cycles (e.g., enterprise) | More complex — multiple branches mean multiple pipeline triggers; good for versioned releases |
| GitHub Flow | Feature branches + PRs to main |
Small teams, SaaS products | Simple and CI-friendly; main is always deployable |
Merge strategies
| Strategy | Effect on history | Pros | Cons |
|---|---|---|---|
| Merge commit | Non-linear history with explicit branches | Preserves context of parallel work | History can be noisy and hard to follow |
| Squash merge | Linear history, one commit per PR | Clean, easy to bisect and revert | Loses intermediate commit detail |
| Rebase merge | Linear history with all commits | Keeps full history but re-applied onto main |
Requires conflict resolution and may be confusing for beginners |
Pro tip: For most CI/CD teams, a squash merge into a trunk-based workflow gives you the best balance of clean history and simple pipelines. You get one commit per feature, which makes rollbacks and bisects trivial.
Troubleshooting & edge cases
Even with a solid workflow, things go wrong. Here are the most common issues and how to fix them.
“Merge conflicts on every PR”
Cause: Branches are long-lived, or you're always merging from main into feature branches too late. Fix: Use trunk-based development and rebase frequently. Keep feature branches short-lived (a day or less).
git fetch origin
git rebase origin/main
“Pipeline runs twice (on PR and on merge)”
Cause: Your CI is configured to run on both pull_request and push events. This is often intentional, but if it's not, you waste resources. Fix: In GitHub Actions, restrict the push trigger to main only, or use paths-filter to avoid redundant runs.
“Protected branch blocks merge even though all checks passed”
Cause: The branch protection rules require approvals or a specific status check that isn't named correctly. Fix: In your repo settings, ensure the status check name matches exactly what your CI reports (e.g., ci/test), and that you have the required number of reviewers.
“Accidental force push to main”
Cause: A developer used --force and bypassed protection (unlikely if properly configured) or protection was off. Fix: Always enable force-push protection on main at the repository level. Most platforms support this in branch protection rules.
What you learned & what's next
You now understand how git workflows shape your CI/CD pipeline. You learned that a branching strategy (like trunk-based) and a merge strategy (like squash) determine whether your history is clean, your builds are fast, and your rollbacks are simple. You practiced creating feature branches, running tests locally, and saw how a protected main branch turns CI into a gatekeeper, not an afterthought.
Let's recap what you can do now:
- Explain why feature branches and pull requests are critical for CI/CD
- Choose between trunk-based, GitFlow, and GitHub Flow based on your team's release cadence
- Select a merge strategy (squash, merge, rebase) that keeps history clean
- Configure basic branch protection to enforce CI checks
Your next step in the CI/CD foundations track is pipeline anatomy — where we'll dive into the stages of a typical pipeline (build, test, deploy) and how your git workflow triggers each one. You'll take the clean git history you've created here and turn it into a fully automated delivery process. Keep your feature branches short, your main protected, and your pipelines green — you're on your way to mastering CI/CD.
Practice recap
Create a new repository, add a feature branch, push it to a remote (even a local bare repo), and simulate a CI pipeline by running your tests in a pre-commit hook or a simple GitHub Action. Then intentionally break a test to see how the status check would block a merge when branch protection is on.
Common mistakes
- Merging feature branches directly into
mainwithout a pull request — you bypass CI checks and lose the audit trail. - Using a long-lived
developbranch in a small team, causing merge conflicts and delayed integration. - Choosing a merge strategy that creates messy history — like always using merge commits when squash would be cleaner.
- Forgetting to keep feature branches short-lived, leading to constant rebase pain and stale pipelines.
Variations
- Instead of GitHub Flow, some teams use GitFlow for formal releases — but it adds pipeline complexity that's often unnecessary for CI/CD.
- For monorepos, consider a branch-by-change approach combined with path-based CI triggers to avoid running the entire suite on every PR.
- You can automate branch cleanup with tools like
git branch --mergedor platform features that delete merged branches — keeps the repo tidy.
Real-world use cases
- SaaS app: every PR triggers tests and a staging deploy, merging to
mainauto-deploys to production via GitHub Actions. - Enterprise web app: a release branch is cut from
develop, CI builds artifacts, and agit tagtriggers the production deploy after manual approval. - Open-source library: contributors fork, create feature branches, open PRs — CI runs cross-platform tests, and only maintainers can merge to
main.
Key takeaways
- A clean git workflow is the backbone of CI/CD — branch and merge events drive your pipelines.
- Feature branches plus protected
mainmake CI a gatekeeper that prevents broken code from shipping. - Choose a branching strategy that matches your release cadence — trunk-based is usually the simplest for CI/CD.
- Squash merging keeps history linear, making rollbacks and bisects trivial.
- Branch protection rules (status checks, approvals, force-push protection) are non-negotiable for reliable pipelines.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.