Page 1 of 2
Choosing a branching strategy for one team is a style preference; choosing one for an organization is a design decision with real cost on both sides. The core tension is integration frequency vs isolation - branch longer for more isolation from other people's half-finished work, and you pay for it later in a bigger, riskier merge; integrate constantly (trunk-based) and merge pain nearly disappears, but it demands feature flags, strong automated testing, and comfort deploying incomplete features hidden behind flags.
At organization scale this isn't just "pick GitFlow or trunk-based" - it's deciding what's enforced by tooling versus convention, how release branches interact with hotfixes across a dozen services with different release cadences, and how a strategy that works for a 5-person team degrades at 200 people (review queues backing up, merge conflict rates climbing, CI capacity becoming a bottleneck). The right answer genuinely differs by deploy cadence: a SaaS product deploying 20 times a day wants trunk-based with flags; a firmware team shipping quarterly, signed builds wants something closer to GitFlow's release branches. Importing one team's strategy wholesale onto another team's constraints is a common, expensive mistake.
Everything Git does eventually reduces to a simple idea: Git is a content-addressable filesystem. Every piece of content gets hashed (SHA-1, moving to SHA-256 in newer setups), and that hash is its address.
Four object types make up everything:
git cat-file -p a3f9c2d # inspect any object by hash
A blob stores raw file content (no filename - filenames live one level up). A tree is a directory listing, mapping names to blob or tree hashes. A commit points to one tree (the project snapshot) plus parent commit(s), author, and message. A ref (like refs/heads/main) is just a file containing a commit hash - branches are refs, HEAD is a pointer to the current ref.
This explains behavior that otherwise looks mysterious: branching is instant because it's writing one small file, not copying content; identical files anywhere in history are stored once because content is addressed by hash, not by path; and commits are technically immutable - "rewriting history" with rebase or filter-repo doesn't edit commits, it creates new commits with new hashes and moves refs to point at them, leaving the old objects unreferenced until garbage collected.
For storage, loose objects eventually get compressed into packfiles - delta-compressed archives that store objects as diffs against similar objects rather than full copies, which is why git gc on an old, never-packed repo can dramatically shrink .git size.
At enough scale - thousands of engineers, huge histories, huge working trees - the naive Git protocol starts to strain, and infrastructure decisions become deliberate engineering.
Protocol v2 improved server-side filtering during fetch/clone (the server used to advertise every ref before the client could even ask for anything, which is wasteful with tens of thousands of branches). Partial clone (--filter=blob:none) lets a clone fetch commit and tree history immediately while deferring file content until it's actually needed on checkout - hugely valuable for enormous repos where most engineers only ever touch a fraction of the tree.
Self-hosted (GitLab self-managed, Gitea, a bare Git server) versus hosted (GitHub, GitLab.com, Bitbucket) is a real infrastructure tradeoff: self-hosting buys full control over compute sizing, network placement, and compliance boundaries, at the cost of owning uptime, backups, and scaling the service yourself. Companies operating at real scale (Google, Microsoft, Meta) have historically hit walls with vanilla Git entirely and built custom systems (Piper, VFS for Git/Scalar, Sapling) - a strong signal that "just use Git normally" stops scaling cleanly somewhere past a certain repo size and engineer count, well before most organizations will ever reach it.
Whether an organization keeps all its code in one repository or many is a platform-level decision with consequences that compound over years, not a preference to set once and forget.
A monorepo gives atomic cross-project commits (one PR changes a library and every consumer of it, in lockstep, and CI proves nothing broke), simple dependency management (no version-pinning drift between internal packages), and unified tooling. The cost is scale pressure on the Git host and CI itself - naive full clones and full-repo CI runs get untenable, which is exactly why partial clone, sparse-checkout (materializing only the subdirectories a given team needs on disk), and build-graph-aware CI (only test/build what a change could actually affect) become load-bearing infrastructure rather than optional nice-to-haves.
git sparse-checkout set services/api services/shared-libs # only these paths on disk
A polyrepo setup keeps repos small and fast individually and gives clean ownership boundaries, at the cost of cross-project changes becoming multi-repo, multi-PR coordination problems, and dependency versions drifting between repos unless actively managed. Neither is universally correct - it's a bet on which coordination cost your organization would rather carry.
Real scenario-based DevOps questions, hands-on practice, and clear explanations for every answer.