Domain 2 · 10–15%

Design and implement a source control strategy

Choosing a branching model that matches how the team releases, enforcing quality at the merge point, and keeping the repository fast, permissioned and free of things that should never have been committed.

Two objectives:

Terminology alert: Azure Repos calls the enforcement mechanism branch policies; GitHub calls it branch protection rules (and, at scale, rulesets). Both appear in the skills measured by name.

2.1 Branching strategies for the source code

Choosing a branch strategy

StrategyLong-lived branchesBranch lifetimeBest forComplexity
Trunk-basedmain onlyHours to 1–2 days (or none)Continuous delivery, microservices, mature CILow
Feature branchmainDaysMost teams; natural PR review pointLow–medium
GitHub FlowmainDaysWeb/SaaS deployed continuouslyLow
Release branchmain + release/x.yLife of the releaseMultiple supported versions in productionMedium
Git Flowmain + developWeeksBoxed/versioned software with QA hardeningHigh

Trunk-based development

Everyone integrates into main at least daily. Branches, if used at all, live for hours.

gitGraph commit id: "init" commit id: "feat A" branch short-lived commit id: "feat B (1 day)" checkout main merge short-lived commit id: "feat C" commit id: "feat D"
Trunk-based development: one integration point, tiny branches, everything else hidden behind feature flags.

Release branching

gitGraph commit id: "v1 dev" branch release/1.0 commit id: "stabilise" tag: "v1.0.0" checkout main commit id: "v2 dev" checkout release/1.0 commit id: "hotfix" tag: "v1.0.1" checkout main merge release/1.0 id: "port fix forward" commit id: "more v2"
Cut a release branch at feature freeze; fix on the branch and always port the fix forward to main.

Caveat — always merge the fix forward

A hotfix applied only to release/1.0 will regress the moment 2.0 ships. Every release-branch fix must also be merged or cherry-picked back into main. Expect a build-list question ordering: branch from the release tag → fix → PR → merge to release → tag → deploy → cherry-pick to main.

Naming conventions

convention branch names
feature/1234-user-authentication   # new work, references the work item
bugfix/5678-null-reference         # non-urgent defect
hotfix/payment-gateway-timeout     # urgent production fix
release/2.4                        # stabilisation branch for a version
users/marco/spike-caching          # personal/experimental (Azure Repos convention)

Consistent prefixes matter operationally: branch policies, pipeline triggers and branch-control checks are all configured with wildcards such as release/* and feature/*.

Pull request workflow

flowchart LR A[Create branch] --> B[Commit work] B --> C[Open PR
draft → ready] C --> D[Automated checks
build validation · status checks] C --> E[Human review
required reviewers · CODEOWNERS] D --> F{All policies met?} E --> F F -->|No| B F -->|Yes| G[Complete / merge] G --> H[Delete source branch]
A PR completes only when every required policy — automated and human — is satisfied.

Azure Repos branch policies

PolicyWhat it enforces
Require a minimum number of reviewersN approvals; options to reset votes on new push, disallow the requester approving their own PR, and allow completion with pending votes
Check for linked work itemsPR must link at least one work item — the traceability enforcement point
Check for comment resolutionAll PR comments must be resolved before completion
Limit merge typesRestrict which of the four merge strategies are allowed
Build validationA named pipeline must succeed; can be required or optional, with an expiry after N hours
Status checksAn external service (SonarQube, a scanner, a custom app) must post a successful status via the REST API
Automatically included reviewersAdd specific people/groups as required or optional reviewers, optionally scoped to file paths
Require merge from specific branchesRestrict which source branches may merge into the target

GitHub branch protection rules & rulesets

ProtectionWhat it enforces
Require a pull request before mergingNo direct pushes; set required approvals, dismiss stale approvals on new commits, require review from Code Owners
Require status checks to passNamed checks must be green; optionally require branches to be up to date before merging
Require conversation resolutionAll review threads resolved
Require signed commitsOnly GPG/S-MIME/SSH-signed commits
Require linear historyBlocks merge commits — forces squash or rebase
Require deployments to succeedThe branch must have deployed successfully to named environments
Restrict who can pushLimit push access to named users, teams or apps
Do not allow bypassing / include administratorsRules apply to admins too
Block force pushes & deletionsProtect history and the branch's existence

Rulesets — the modern, scalable form

GitHub rulesets supersede classic branch protection for organisation-wide governance: they can be defined at the org level and applied across many repositories, they target branches and tags, they support explicit bypass lists, and they have an evaluate (dry-run) mode so you can see what would be blocked before enforcing. Multiple rulesets layer additively — the most restrictive outcome wins.

Caveat — the vocabulary is the question

Azure Repos → branch policies, applied per branch or by wildcard, configured under Repos → Branches → Branch policies. GitHub → branch protection rules / rulesets, under Settings → Rules. "Build validation" (Azure) and "required status checks" (GitHub) are the same idea with different names — and both are the answer to "prevent merging code that fails CI".

Merge strategies and restrictions

StrategyResulting historyChoose when
Merge (no fast-forward)Preserves every commit plus a merge commit; non-linearYou want the full development history of the branch
Squash mergeOne new commit on the target; branch commits are discardedNoisy WIP commits; you want one commit per PR/feature
Rebase and fast-forwardBranch commits replayed onto the target; fully linear, no merge commitYou want a clean linear log and keep individual commits
Semi-linear merge (rebase + merge commit)Rebases, then adds a merge commitLinear-ish history that still records where each PR landed

Caveat — squash merge and traceability

Squash merge rewrites the branch into a single new commit, so the original commit SHAs disappear from the target branch. That is exactly what you want for a tidy main, but it means the source branch should be deleted afterwards (re-merging it later causes conflicts) and per-commit authorship is lost. If a scenario demands "preserve individual commit attribution", squash is the wrong answer.

CODEOWNERS

github .github/CODEOWNERS (also valid in / or docs/)
# Later matches win — the LAST matching pattern takes precedence.
*                       @org/platform-team

/docs/                  @org/docs-team
*.ts                    @org/frontend-team @lead-developer
/infrastructure/        @org/devops-team
/src/auth/              @org/security-team
/.github/workflows/     @org/devops-team

2.2 Configure and manage repositories

Managing large files

Git stores a complete copy of every version of every file in every clone. Large binaries — which do not delta-compress — bloat the repository permanently, because history is immutable.

Git LFS (Large File Storage)

LFS replaces large files in the repo with small text pointer files; the real content lives on a separate LFS server and is fetched on checkout.

bash git-lfs setup
git lfs install                # one-time, per machine

git lfs track "*.psd"          # writes rules into .gitattributes
git lfs track "*.mp4"
git lfs track "assets/**/*.bin"

# .gitattributes now contains:
#   *.psd filter=lfs diff=lfs merge=lfs -text

git add .gitattributes         # MUST be committed — it is what makes LFS work for everyone
git add design.psd
git commit -m "Add design assets"
git push origin main

git lfs ls-files               # list files currently managed by LFS
git lfs migrate import --include="*.psd"   # convert EXISTING history to LFS (rewrites history)

Caveat — LFS gotchas

Tracking is not retroactive. git lfs track only affects files committed after the rule exists; converting existing history needs git lfs migrate import, which rewrites history. Also: .gitattributes must be committed and pushed or other clones will not use LFS. And LFS files are not de-duplicated across branches — quota is consumed by every distinct version.

Azure ReposGitHub
LFS supportSupported; no extra charge for LFS storage beyond repo limitsSupported; free tier of 1 GB storage + 1 GB bandwidth/month, then paid data packs
AuthenticationSame as the repo (PAT / Entra)Same as the repo (token / SSH)

git-fat

A lighter-weight, third-party alternative to LFS: large files are replaced by a placeholder containing a SHA-1, and the real payload is synced to an external store over rsync (or S3-style backends).

config .gitfat
[rsync]
remote = storage-server:/srv/fat-store

Use it when you need a self-hosted binary store with no vendor dependency. In practice LFS is the default answer; git-fat appears in the skills measured mainly so you recognise the name.

Scaling and optimizing a Git repository

Scalar

Scalar ships with Git itself and applies the large-repo configuration that Microsoft developed for the Windows and Office repositories.

TechniqueWhat it avoids
Partial clone (--filter=blob:none)Downloading file contents you never open — blobs are fetched on demand
Sparse checkout (cone mode)Materialising directories your team does not work in
Commit-graphWalking history from scratch — speeds up log, blame, merge-base
Multi-pack indexSlow object lookup across many pack files
FSMonitor (file system watcher)Scanning the whole working tree on every git status
Background maintenanceForeground stalls — prefetch and repack run on a schedule
bash scalar
scalar clone https://dev.azure.com/org/project/_git/large-repo
scalar register            # opt an EXISTING clone into Scalar's maintenance + config
scalar run all             # run maintenance tasks now
scalar unregister

# The underlying primitives, if asked about them directly:
git clone --filter=blob:none <url>
git sparse-checkout set --cone src/api src/shared

Cross-repository sharing

TechniqueHow it worksTrade-off
Package feed (Azure Artifacts / GitHub Packages)Publish the shared code as a versioned packagePreferred — proper versioning and dependency resolution
Git submoduleA pointer to another repo pinned at a specific commitExplicit versions, but easy to forget --recurse-submodules
Git subtreeCopies the other repo's content (and optionally history) into a subdirectoryNo special client commands for consumers; messier history
Sparse checkout of a monorepoOne repo, each team checks out only its foldersSingle source of truth; needs monorepo tooling and CI path filters
bash submodules
git submodule add https://github.com/org/shared-lib ./libs/shared
git clone --recurse-submodules https://github.com/org/project
git submodule update --init --recursive     # after a plain clone
git submodule update --remote --merge       # advance to the submodule's latest

Caveat — share code as a package, not a submodule

When the exam asks how to share a library across teams or repositories, the expected answer is a package feed (Azure Artifacts / GitHub Packages) — it gives you semantic versioning, upstream caching and independent release cadence. Submodules are the answer only when the scenario explicitly needs the source pinned at a commit (e.g. vendored tooling built from source).

Permissions in the source control repository

Azure DevOps

Access is the intersection of an access level (what you paid for), security group membership (what role you hold) and object-level permissions (Allow / Deny / Not set on a specific repo or branch).

Repository permissionGrants
ReadClone, fetch, browse
ContributePush to branches that have no blocking policy
Create branchCreate new branches
Contribute to pull requestsComment on and vote in PRs
Force push (rewrite history, delete branches)Destructive history rewriting — grant to almost nobody
Bypass policies when pushing / completing pull requestsIgnore branch policies — audit these grants
Manage permissionsEdit the repo's ACLs
Delete or disable repositoryRemove the repo

Caveat — Deny beats Allow

In Azure DevOps, an explicit Deny always wins over an Allow inherited from any group — with one exception: members of Project Collection Administrators can override. Permissions also inherit from project → repository → branch, so the cleanest model is to grant at the group level and only set object-level permissions where you genuinely need an exception.

GitHub

RoleCan
ReadView, clone, open issues and PRs
TriageRead + manage issues and PRs (label, assign, close) — no write access to code
WriteTriage + push to unprotected branches, manage releases
MaintainWrite + repo settings management, excluding destructive/sensitive actions
AdminFull control, including deletion, visibility changes and protection rules

Tags

bash tagging
git tag v1.0.0                             # lightweight: just a pointer, no metadata
git tag -a v2.1.0 -m "Adds payment gateway" # annotated: tagger, date, message — RECOMMENDED for releases
git tag -s v2.1.0 -m "Signed release"       # signed: annotated + GPG signature
git tag -a v1.9.5 abc1234 -m "Hotfix"       # tag a specific historical commit

git push origin v2.1.0                      # tags are NOT pushed by default
git push origin --tags                      # push all tags

git tag --list "v2.*"
git tag -d v1.0.0                           # delete locally
git push origin --delete v1.0.0             # delete on the remote

Caveat — lightweight vs annotated

Annotated tags are full Git objects carrying the tagger, date, message and optional signature; lightweight tags are just a named pointer. For releases — and anywhere auditability matters — the answer is annotated (-a) or signed (-s). Also remember tags are not pushed by git push alone, and git describe only sees annotated tags unless you pass --tags.

Recovering specific data with Git commands

SituationCommandSafe on a shared branch?
Undo last commit, keep changes stagedgit reset --soft HEAD~1No — rewrites history
Undo last commit, keep changes in the working treegit reset --mixed HEAD~1 (default)No
Undo last commit and discard the changesgit reset --hard HEAD~1No
Undo a commit that is already pushedgit revert <sha> — creates a new inverse commitYes
Restore one file from the last commitgit restore --source=HEAD -- path/to/fileYes
Recover a deleted branchgit reflog to find the SHA, then git branch <name> <sha>Yes
Find any "lost" commitgit reflog / git fsck --lost-foundYes
Copy one commit onto another branchgit cherry-pick <sha>Yes
Shelve work temporarilygit stash push -m "wip" / git stash popYes
bash reflog rescue
git reflog
# abc1234 HEAD@{0}: reset: moving to HEAD~1
# def5678 HEAD@{1}: commit: the work I just destroyed   ← recover this
# ghi9012 HEAD@{2}: commit: earlier work

git reset --hard HEAD@{1}          # put HEAD back
git branch recovered def5678       # or rescue it onto a new branch

Caveat — revert vs reset

git revert is the only safe undo for anything already pushed. It adds a new commit that reverses the change, so collaborators' history stays valid. git reset rewrites history and requires a force push, which breaks every other clone. The exam phrases this as "the change is already in production / already shared" → revert. Note that reflog is local only and entries expire (90 days for reachable, 30 for unreachable, by default).

Removing specific data from source control

These operations rewrite history

Every commit SHA after the rewrite point changes. Coordinate with the whole team, and expect every existing clone to need re-cloning.

ToolUseNotes
git filter-repoThe recommended modern tool for rewriting historyFast, safe defaults; replaced git filter-branch, which Git now actively discourages
BFG Repo-CleanerSimple, very fast removal of files or secret stringsJava tool; will not touch the commit currently at HEAD — clean that up first
git filter-branchLegacySlow and error-prone — a wrong answer if a modern option is offered
bash git filter-repo
# Remove one file from ALL history
git filter-repo --path secrets.env --invert-paths

# Remove every file matching a glob
git filter-repo --path-glob '*.pem' --invert-paths

# Redact a literal string everywhere
git filter-repo --replace-text patterns.txt
bash BFG Repo-Cleaner
git clone --mirror https://.../repo.git

java -jar bfg.jar --delete-files secrets.env repo.git
java -jar bfg.jar --replace-text passwords.txt repo.git
java -jar bfg.jar --strip-blobs-bigger-than 50M repo.git

cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force

The full incident response for a leaked secret

  1. Rotate the secret first. Assume it is compromised the moment it was pushed — history rewriting is cleanup, not containment.
  2. Rewrite history with git filter-repo or BFG, then force push all branches and tags.
  3. Tell every collaborator to re-clone. Old clones still contain the secret and can push it back.
  4. Purge cached copies. Forks, PR refs and the server-side cache can still serve the old blobs — on GitHub, open a support request to purge cached views; on Azure DevOps, contact support to clear server-side data after the force push.
  5. Prevent recurrence — enable secret scanning with push protection, add pre-commit hooks (gitleaks, detect-secrets) and move the value into Key Vault. (See Domain 4.)

Caveat — rotation beats rewriting

If a question offers both "remove it from history" and "rotate the credential", the first action is always to rotate. A build-list question will usually want: rotate → rewrite history → force push → notify collaborators to re-clone → enable push protection.

Domain 2 rapid recap

The requirement says…Answer
Daily deploys, experienced team, minimal branchingTrunk-based development + feature flags
Support several versions in production at onceRelease branching (fix on the branch, port forward)
Block merging when CI fails (Azure Repos)Branch policy: Build validation
Block merging when CI fails (GitHub)Required status checks in a protection rule / ruleset
Apply the same rules to 200 repos at onceGitHub organisation-level ruleset
Enforce one commit per feature on mainSquash merge (limit merge types)
Forbid merge commits entirelyRequire linear history / rebase + fast-forward
Reviewers auto-assigned by folderCODEOWNERS (or automatically included reviewers)
Repo bloated by design files and videosGit LFS (+ git lfs migrate import for existing history)
Clone of a huge monorepo takes an hourScalar — partial clone + sparse checkout
Share a library between five reposAzure Artifacts / GitHub Packages feed
Undo a change already merged and deployedgit revert
Recover a branch someone deletedgit refloggit branch <name> <sha>
A password was committed last monthRotate it, then git filter-repo/BFG, force push, re-clone
Tag a release with author and messageAnnotated tag (git tag -a)