DevOps Foundations · Module 4: Git and team workflow
Commit, Branch and Merge
Shared history is the team's collective memory. This lesson makes commits small and branches cheap so merging stays boring.
10 min reading
Objectives
- Write commits that are small, named and independently revertible
- Choose feature branches over long-lived forks of shared code
- Merge without fear by predicting fast-forward versus true merge
- Read git log as the project's memory, not just a list
Why this matters
A release breaks and the team needs the one change that caused it, out, in ten minutes. With small named commits the answer is one revert; with week-long mega-commits mixing five features the answer is surgery under pressure. Branch and commit discipline is not bureaucracy. It is the mechanism that makes recovery a command instead of a project.
Concepts
A commit is a snapshot plus a message plus a parent. Small commits win for three reasons: they revert cleanly (one change in, one change out), they review quickly (a reviewer holds one idea, not five), and they bisect precisely (git bisect lands on the guilty change, not the guilty month). Atomic means the tree builds and tests pass at every commit; a red commit in the middle poisons bisect and blame for everyone after it.
Branches are movable pointers, cheap by design. A feature branch isolates one idea until it is ready; main stays releasable at all times. Short-lived branches (hours to days) merge with small conflicts; branches that live for weeks diverge until merging becomes an event with a meeting. Merge early and often is not enthusiasm, it is conflict interest paid daily instead of compounded.
Merges come in two shapes. Fast-forward moves the pointer when nothing diverged: linear, silent, no merge commit. A true merge joins two histories with a merge commit that records the decision to combine them. Neither is morally superior; the team's convention decides. What matters is knowing which you are about to do: git log --graph --oneline shows the shape before you act, and --no-ff versus --ff-only enforce the policy instead of hoping.
Worked example
A clean feature merge, read before doing:
$ git log --graph --oneline -5 9f2c1ab (feature/rate-limit) Add token bucket check 4d6e802 Add bucket config with default 100/min | 7a11b90 (main) Fix healthcheck path |/ 2c03dd1 Release 1.4.0 $ git checkout main && git merge --no-ff feature/rate-limit -m "Merge rate limiting (100/min default)"
Expected reading: the graph shows exactly two commits on each side since the fork, so the merge combines one idea with one fix and the merge commit names the feature. If the graph showed thirty commits over three weeks, the correct move would be to stop and split, not to merge and pray. Verify after: the tree builds, tests pass, and log shows the merge joining the two lines.
The common wrong move
Committing everything at end of day with messages like fixes and updates. These commits cannot be reverted (which part of everything?), cannot be reviewed (what is the idea?), and cannot be bisected (the guilty change hides inside the bundle). Commit when the idea completes, with a message a stranger could act on, even if that means five commits a day. Frequency is a feature.
Lab and next step
Module labs L10-L12 all assume this shape: small commits, short branches, readable graphs. Next, lesson 2 handles the moment two lines of history collide: conflicts.
Quick check
An optional 4-question self-check. Answers never leave your device, are not stored, and never count toward any assessment.
Lesson feedback
No published feedback yet.
Log in and complete the lesson to leave feedback.
Exercise
Create a scratch repo, make two branches with two small commits each, merge with --no-ff, and draw the resulting graph from git log --graph. Then show the same merge would have fast-forwarded if main had not moved.
Pass criteria
Scratch repo with two branches of two atomic commits each; merge commit present with a naming message; graph drawn correctly; fast-forward condition stated accurately.