Advanced AZ-400

AZ-400: Source Control

Git workflows, Azure Repos and GitHub, branching strategies and data recovery.

Module 1 – Introduction to Source Control

Definition

  • Source Control = system for tracking and managing changes in the codebase.
  • Other names: version control, source repository, repos.
  • Role:
    • Central source of truth for code.
    • Enables collaboration (multiple developers on the same code).
    • Tracking changes with full history.
    • Rollback and conflict resolution.

The 3 Platforms Covered

PlatformTypeDescription
Git (Core Git)Agnostic, open-sourceDistributed VCS, branching, merging. Used by GitHub, Azure Repos, GitLab, Bitbucket.
GitHubVendor-specific (Microsoft)Cloud-based Git, PRs, code reviews, collaboration, strong open-source community.
Azure ReposVendor-specific (Microsoft)Integrated in Azure DevOps, supports Git and TFVC (legacy).

Module 2 – Configuring Git Repositories

Optimizing Large Repositories

Scalar

  • Microsoft tool for very large repos with long history.
  • Features :
    • Selective sync : clone only what you need.
    • Sparse checkouts : load only the necessary folders.
    • Automatic maintenance : automatic compaction of Git objects.
  • Usage: git clone --filter=blob:none <url> or enable Scalar via config.

Git LFS (Large File Storage)

  • Stores large binary files outside the Git repo.
  • The repo contains pointers to LFS files.
  • Prevents every clone from downloading all versions of all large files.
  • Usage: git lfs install, git lfs track "*.psd".

Git Submodules

  • Include a Git repo inside another Git repo (cross-repository sharing).
  • Useful for sharing common libraries between projects.
  • Commands: git submodule add <url>, git submodule update --init.

Best Practices — What NOT to Put in the Repo

  • Build artifacts (binaries, .dll, .exe).
  • Secrets and credentials.
  • Local configuration files (e.g. .env).
  • Large binary files (images, videos) → use LFS.
  • Use .gitignore to systematically exclude these files.

Changelogs

  • Purpose : chronological list of changes in a project for humans.
  • Typical content: versions, added/modified/removed features, bug fixes.
  • Example format: CHANGELOG.md at the root of the repo.
  • Creation: manual, or automatic via tools like git-changelog-command-line.

Git Tags

Two Types of Tags

TypeDescriptionCommand
LightweightSimple pointer to a commitgit tag v1.2
AnnotatedFull Git object with message, author, dategit tag -a v1.2 -m "Release 1.2"
  • Naming conventions: v1.2, v1.2.0, or commit hash.
  • Pushing tags: git push origin --tags.

Removing Data from the Repo

Scenario 1: Local commit not yet pushed

# Amend the last commit
git commit --amend
# Or reset to remove the commit
git reset HEAD~1

Scenario 2: Commit already pushed

  • Use tools like git filter-repo (replaces git filter-branch).
  • Remove the file from all history.
  • Force push: git push --force-with-lease.

Recovering Deleted Data

Restore from an old commit

git log                          # Find the commit
git checkout <hash> -- file.txt  # Restore a file from that commit

Restore a deleted commit (git reflog)

git reflog                       # View full ref history
git cherry-pick <hash>           # Apply a lost commit

Restore specific lines

git log -p -- file.txt          # View changes in a file
git blame file.txt              # See who modified each line

Module 3 – Managing Azure Repos and GitHub

Azure Repos Permissions

Permission Levels (parent → child inheritance)

Organization
  └── Project
        └── Repository
              └── Branch (can override repo permissions)
  • Inheritance can be explicitly disabled at each level.
  • Special permission: “Bypass policies when completing pull requests” → must be granted explicitly (not part of any group by default).

Default Azure Repos Groups

GroupPermissions
Project AdministratorsFull admin
ContributorsPush, create branches, create PRs
Build AdministratorsManage pipelines
ReadersRead-only

GitHub Permissions (Organization)

Role Types

  • Organization roles : high-level administration (billing, security) — not linked to repos.
  • Repository roles : access control for the repos themselves.

Repository Roles

RolePermissions
ReadRead-only, report issues
TriageManage issues and PRs, no push
WritePush code, create branches
MaintainManage the repo (no delete/critical settings)
AdminFull control

Module 4 – Branching Strategies

Why Branch?

  • Work in isolation → do not impact production.
  • Multiple features/bugs in parallel.
  • Branches are merged into main via Pull Requests.

The 3 Branching Strategies

1. Trunk-Based Branching

main ──────────────────────────────→
       ↑ quick fix  ↑ feature
       (short branch, fast merge)
  • Short and ephemeral branches, always merged into main quickly.
  • Emphasis on continuous integration.
  • Ideal for: small teams, strict CI, continuous delivery.
  • Drawbacks: risk of conflicts if branches are too long.

2. Feature (Task) Branching

main ────────────────────────────→
        └── feature/login ──┘
        └── feature/cart ───────┘
  • Dedicated branch per feature or task.
  • Isolated development until completion.
  • Ideal for: medium-sized teams, well-defined features.
  • Drawbacks: risk of complex merge if branches are long.

3. Release Branching

main ────────────────────→
     └── release/v1.2 (stabilization)
                └── hotfix on release
  • Dedicated branch to stabilize before a release.
  • Allows hotfixes on the production version.
  • Ideal for: planned releases, software supporting multiple versions.
  • Drawbacks: complexity, many branches.

Pull Requests (PRs)

Components of a Quality PR

ComponentDescription
WhatWhat changes were made?
WhyBusiness/technical objectives of these changes
HowDesign decisions and justifications
VerificationTests performed, screenshots

PR Lifecycle

  1. Create a branch.
  2. Commit changes.
  3. Formally create the Pull Request.
  4. Review + discussions + suggestions.
  5. Approval by reviewers.
  6. Merge into main.

Branch Policies and Permissions

Problem Without Policies

  • Developers can merge directly without review → bugs in production.

Branch Policies in Azure Repos

PolicyDescription
Require a successful buildCI must pass before merge
Require minimum number of reviewersE.g. at least 2 approvers
Check for comment resolutionAll comments must be resolved
Require linked work itemsPR must reference a work item
Enforce a merge strategySquash, rebase, or merge commit

⚠️ “Bypass policies when completing pull requests” is not enabled for any group by default → must be explicitly assigned at the branch level.

GitHub Branch Protection Rules

  • Equivalent of Branch Policies in Azure Repos.
  • Configurable per branch (e.g. main, release/*).

Module 5 – Review Questions

ScenarioTool/Answer
Large repo with performance issues (cloning, metadata) → tool?Scalar (selective sync, sparse checkouts, maintenance)
User needs to bypass branch policies only for one branchExplicitly assign “Bypass policies when completing pull requests” at the branch level
Small team, frequent commits to main, short branchesTrunk-Based Branching
Which Azure Repos policy is NOT a real policy?None of the typical examples (require build, reviewers, work items, merge strategy are all valid)
Secrets accidentally committed → remove from historygit filter-repo + force push
Lightweight vs annotated tagsLightweight = simple pointer. Annotated = full object (message, author, date)

Module 6 — Complete Git Workflow: Operational Reference

Fundamental Git Commands

# ─── Initial Configuration ───────────────────────────────────────────────
git config --global user.name "Jane Smith"
git config --global user.email "jane@company.com"
git config --global core.editor "code --wait"       # VS Code as editor
git config --global init.defaultBranch main          # Default branch = main
git config --global pull.rebase false                # Merge by default (vs rebase)

# ─── Create and Clone ────────────────────────────────────────────────────
git init                                             # Initialize a new repo
git clone https://github.com/org/repo.git            # Clone a remote repo
git clone --depth 1 https://github.com/org/repo.git  # Shallow clone (last commit only)
git clone --filter=blob:none https://...             # Clone without blobs (Scalar-style)

# ─── Branches ────────────────────────────────────────────────────────────
git branch                                           # List local branches
git branch -a                                        # List local + remote branches
git branch feature/login                             # Create a branch
git checkout feature/login                           # Switch to a branch
git checkout -b feature/cart                         # Create + switch in one command
git switch -c feature/payment                        # Modern command (git 2.23+)
git branch -d feature/login                          # Delete a local branch
git push origin --delete feature/old-branch          # Delete a remote branch

# ─── Staging and Commit ──────────────────────────────────────────────────
git status                                           # Check repo status
git diff                                             # View unstaged changes
git diff --staged                                    # View staged changes
git add .                                            # Stage all changes
git add src/                                         # Stage a specific folder
git add -p                                           # Stage interactively (hunks)
git commit -m "feat: add login page"                 # Commit with message
git commit --amend --no-edit                         # Amend last commit (without changing message)
git commit --amend -m "New message"                  # Amend with new message

# ─── Push and Pull ───────────────────────────────────────────────────────
git push origin feature/login                        # Push a branch
git push -u origin feature/login                     # Push and track remote branch
git pull                                             # Fetch + merge
git pull --rebase                                    # Fetch + rebase (cleaner history)
git fetch --all                                      # Fetch all remote branches
git fetch --prune                                    # Remove refs of deleted remote branches

# ─── Merge and Rebase ────────────────────────────────────────────────────
git merge feature/login                              # Standard merge (merge commit)
git merge --squash feature/login                     # Squash all commits into 1
git merge --no-ff feature/login                      # Force a merge commit (even if fast-forward possible)
git rebase main                                      # Rebase current branch onto main
git rebase -i HEAD~3                                 # Interactive rebase (modify last 3 commits)
git cherry-pick abc123                               # Apply a specific commit

# ─── History ─────────────────────────────────────────────────────────────
git log --oneline                                    # Compact log
git log --oneline --graph --all                      # Graphical log of all branches
git log --author="Jane"                              # Filter by author
git log --since="2 weeks ago"                        # Commits in the last 2 weeks
git log --follow -- src/app.js                       # File history (includes renames)
git blame src/app.cs                                 # Who modified each line
git show abc123                                      # Details of a commit

# ─── Reset and Revert ────────────────────────────────────────────────────
git revert abc123                                    # Undo a commit (creates a new commit)
git reset HEAD~1                                     # Undo last commit (keep changes)
git reset --hard HEAD~1                              # Undo + discard changes (dangerous!)
git restore src/app.cs                               # Discard unstaged changes in a file
git restore --staged src/app.cs                      # Unstage a file

# ─── Stash ───────────────────────────────────────────────────────────────
git stash                                            # Temporarily save changes
git stash push -m "work in progress on login"        # Stash with message
git stash list                                       # List stashes
git stash pop                                        # Apply and remove last stash
git stash apply stash@{2}                            # Apply a specific stash

# ─── Tags ────────────────────────────────────────────────────────────────
git tag v1.0.0                                       # Lightweight tag
git tag -a v1.0.0 -m "Release 1.0.0"               # Annotated tag
git push origin v1.0.0                               # Push a tag
git push origin --tags                               # Push all tags
git tag -d v1.0.0-beta                               # Delete a local tag

# ─── Cleanup and Maintenance ─────────────────────────────────────────────
git gc                                               # Garbage collection
git gc --aggressive                                  # Deep GC
git fsck                                             # Check repo integrity
git clean -fd                                        # Remove untracked files
git remote prune origin                              # Clean up stale remote refs

Git LFS — Large File Storage

# Install and configure Git LFS
git lfs install                                      # Enable LFS in the repo
git lfs track "*.psd"                                # Track PSD files with LFS
git lfs track "*.mp4" "*.mov" "*.avi"               # Track videos
git lfs track "assets/**"                            # Track all assets
cat .gitattributes                                   # View LFS rules

# Add and commit with LFS
git add .gitattributes
git add images/logo.psd                              # Added via LFS automatically
git commit -m "chore: add logo with LFS"

# Check LFS files
git lfs ls-files                                     # List LFS files
git lfs status                                       # View LFS file status
git lfs check-pointer path/to/file                   # Check if a file is an LFS pointer

# Migrate existing files to LFS
git lfs migrate import --include="*.psd,*.zip"       # Migrate existing files
git lfs migrate import --everything --include="*.bin" # Migrate entire history
git push origin --force --all                         # Force push after migration

Scalar — Large Repository Optimization

# Enable Scalar on an existing repo
scalar register .

# Clone with Scalar (optimized for large repos)
scalar clone https://dev.azure.com/company/project/_git/large-repo
# Equivalent to:
# git clone --filter=tree:0 --no-checkout [url]
# git sparse-checkout init --cone

# View Scalar configuration
scalar diagnose

# Enable background maintenance
git maintenance start

# Sparse-checkout configuration (load only certain folders)
git sparse-checkout init
git sparse-checkout set src/ tests/ docs/    # Load only these folders
git sparse-checkout list                      # View active patterns
git checkout                                  # Update the working tree

Submodules — Cross-Repository Sharing

# Add a submodule
git submodule add https://github.com/company/shared-lib.git libs/shared
git commit -m "chore: add shared-lib submodule"

# Clone a repo with its submodules
git clone --recurse-submodules https://github.com/company/app.git
# Or after a normal clone:
git submodule init
git submodule update

# Update a submodule to its latest version
cd libs/shared
git pull origin main
cd ../..
git add libs/shared
git commit -m "chore: update shared-lib to latest"

# Initialize all submodules recursively
git submodule update --init --recursive

# Remove a submodule
git submodule deinit libs/shared
git rm libs/shared
rm -rf .git/modules/libs/shared
git commit -m "chore: remove shared-lib submodule"

Module 7 — Advanced Branching Strategies

GitFlow — Structured Workflow

gitGraph
   commit id: "Initial Commit"
   branch develop
   checkout develop
   commit id: "Setup project"
   branch feature/user-auth
   checkout feature/user-auth
   commit id: "Add login"
   commit id: "Add OAuth"
   checkout develop
   merge feature/user-auth id: "Merge user-auth"
   branch feature/shopping-cart
   checkout feature/shopping-cart
   commit id: "Add cart"
   checkout develop
   merge feature/shopping-cart id: "Merge cart"
   branch release/v1.0
   checkout release/v1.0
   commit id: "Version bump 1.0.0"
   commit id: "Fix minor bugs"
   checkout main
   merge release/v1.0 id: "Release v1.0.0"
   checkout develop
   merge release/v1.0 id: "Sync develop"
   commit id: "Continue dev"
   branch hotfix/security-patch
   checkout hotfix/security-patch
   commit id: "Fix security issue"
   checkout main
   merge hotfix/security-patch id: "Hotfix v1.0.1"
   checkout develop
   merge hotfix/security-patch id: "Sync hotfix"

GitFlow Branches:

BranchLifetimeObjectiveSourceMerge Target
mainPermanentProduction code
developPermanentFeature integrationmain
feature/*TemporaryFeature developmentdevelopdevelop
release/*TemporaryStabilization before releasedevelopmain + develop
hotfix/*TemporaryUrgent production fixmainmain + develop

Branching Strategy Comparison

CriterionTrunk-BasedFeature BranchingGitFlowGitHub Flow
ComplexityLowMediumHighLow
Active branches1N featuresmain + develop + featuresmain + features
CI/CD✅ Ideal✅ Good⚠️ Complex✅ Ideal
Team sizeSmall (1-5)Medium (5-20)Large (20+)Small to large
Release cadenceContinuousIterationsPlannedContinuous
Merge conflict riskLowMediumHighLow
Multi-version supportNoNo✅ YesNo

Conventional Commits — Message Standard

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

Standard Types:

TypeDescriptionSemVer
featNew featureMINOR
fixBug fixPATCH
docsDocumentation only
styleFormatting, whitespace (no logic)
refactorRefactoring without new feature or bug fix
perfPerformance improvementPATCH
testAdding/modifying tests
choreMaintenance, dependencies, tooling
ciCI/CD changes
BREAKING CHANGEBreaking change (in footer)MAJOR
# Examples of good commit messages
git commit -m "feat(auth): add Google OAuth 2.0 login"
git commit -m "fix(cart): correct total calculation with discounts"
git commit -m "docs(api): update authentication endpoint documentation"
git commit -m "refactor(orders): extract payment logic to separate service"

# Breaking change (footer)
git commit -m "feat(api): change authentication endpoint

BREAKING CHANGE: The /api/v1/auth endpoint now requires Bearer token
instead of API key. See migration guide in MIGRATION.md."

# Link an Azure Boards work item
git commit -m "fix(login): resolve session timeout bug AB#1234"

Module 8 — Azure Repos: Advanced Configuration

Branch Policies — YAML Configuration

# Configure branch policies via Azure DevOps CLI
# (or via the web interface: Repos → Branches → ⋮ → Branch policies)

# 1. Require a successful CI build before merge
az repos policy build create \
  --branch main \
  --branch-match-type exact \
  --build-definition-id 42 \
  --display-name "Require CI Build" \
  --enabled true \
  --blocking true \
  --queue-on-source-update-only false \
  --manual-queue-only false \
  --valid-duration 720 \   # Max 12 hours before re-validation
  --repository-id $(az repos show --repository MyRepo --query id -o tsv) \
  --project MyProject \
  --org https://dev.azure.com/MyOrg

# 2. Require reviewers
az repos policy approver-count create \
  --branch main \
  --branch-match-type exact \
  --minimum-approver-count 2 \
  --allow-downvotes false \
  --reset-on-source-push true \    # Re-validation if new push
  --creator-vote-counts false \    # Author cannot approve their own PR
  --repository-id $(az repos show --repository MyRepo --query id -o tsv) \
  --blocking true \
  --enabled true \
  --project MyProject \
  --org https://dev.azure.com/MyOrg

# 3. Require comment resolution
az repos policy comment-required create \
  --branch main \
  --branch-match-type exact \
  --repository-id $(az repos show --repository MyRepo --query id -o tsv) \
  --blocking true \
  --enabled true \
  --project MyProject \
  --org https://dev.azure.com/MyOrg

CODEOWNERS — Automatic Code Review

# GitHub: .github/CODEOWNERS
# Azure DevOps: Configured in Branch Policies (Required Reviewers)

# In GitHub, the CODEOWNERS file automatically assigns reviewers
# based on the paths of modified files

# Global owners (if no more specific rule)
*                           @team-leads

# Backend Python
/backend/                   @backend-team
*.py                        @python-experts

# Infrastructure
/infra/                     @devops-team @security-team
*.bicep                     @devops-team
*.tf                        @devops-team

# Frontend
/frontend/                  @frontend-team
*.tsx *.ts                  @frontend-team

# Documentation (anyone can modify)
/docs/                      

# Security configurations (2 approvers required)
.github/workflows/          @security-team @devops-team
/infra/security/            @ciso @security-team

Merge Strategies — Comparison

flowchart TD
    subgraph "Standard Merge"
        M1[commit A] --> M2[commit B] --> M3[commit C]
        M4[commit D] --> M5[commit E]
        M3 & M5 --> M6[Merge commit\n\n+ Preserves full history\n- More complex history]
    end

    subgraph "Squash Merge"
        S1[commit A] --> S2[commit B] --> S3[commit C]
        S4[commit D] --> S5[commit E]
        S3 & S5 --> S6[Single squashed commit\n\n+ Clean history\n- Loses individual commits]
    end

    subgraph "Rebase Merge"
        R1[commit A] --> R2[commit B] --> R3[commit C]
        R4[commit D'] --> R5[commit E']
        R3 --> R4
        R5 --> R6[No merge commit\n\n+ Linear history\n- Rewrites commits]
    end
# Standard merge (--no-ff forces a merge commit)
git checkout main
git merge --no-ff feature/login

# Squash merge (all feature commits into 1)
git merge --squash feature/login
git commit -m "feat: add user authentication feature"  # One commit summarizing everything

# Rebase then fast-forward merge
git checkout feature/login
git rebase main                    # Rebase onto main
git checkout main
git merge feature/login            # Fast-forward (no merge commit)

Module 9 — Removing and Recovering Data

Removing Sensitive Data from Git History

# ⚠️ WARNING: These operations rewrite history
# Coordinate with THE ENTIRE TEAM before proceeding

# Method 1: git filter-repo (recommended, faster)
pip install git-filter-repo

# Remove a file from all history
git filter-repo --path secrets.json --invert-paths

# Remove a directory
git filter-repo --path-glob "config/secrets/*" --invert-paths

# Replace a text pattern (e.g. hardcoded password)
git filter-repo --replace-text <(echo "password123==>***REMOVED***")

# Remove files > 10MB from history
git filter-repo --strip-blobs-bigger-than 10M

# Method 2: BFG Repo Cleaner (alternative, simpler)
# Download: https://rtyley.github.io/bfg-repo-cleaner/
java -jar bfg.jar --delete-files secrets.json
java -jar bfg.jar --replace-text passwords.txt    # File containing patterns
java -jar bfg.jar --strip-blobs-bigger-than 50M

# After filter-repo or BFG:
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# Force push to all remotes
git push origin --force --all
git push origin --force --tags

# Invalidate existing clones
# All team members must re-clone the repository

Data Recovery

# Recover a file from an old commit
git log --all -- path/to/file.cs         # Find when the file existed
git checkout abc123 -- path/to/file.cs   # Restore from that commit
git commit -m "restore: recover deleted file.cs"

# Recover a "lost" commit (after reset --hard)
git reflog                               # View all recent refs
git reflog show --all                    # View all refs (all branches)
git checkout -b recovery abc123          # Create a branch from the lost commit
# Or:
git cherry-pick abc123                   # Apply the commit on the current branch

# Recover a deleted branch (Azure Repos)
# In the portal: Repos → Branches → Deleted branches → Restore
# Or via CLI:
az repos ref create \
  --name refs/heads/feature/deleted-branch \
  --object-id abc123def456 \
  --repository MyRepo \
  --project MyProject

# Recover a deleted repo (Azure DevOps)
# Organization Settings → Projects → Deleted repositories → Restore
# (available for 30 days after deletion)

# View "lost" objects in Git
git fsck --lost-found
ls .git/lost-found/commit/              # Commits without references
ls .git/lost-found/other/               # Blobs without references

Module 10 — GitHub vs Azure Repos: Advanced Features

GitHub vs Azure Repos Comparison

FeatureGitHubAzure Repos
VCS supportedGit onlyGit + TFVC (legacy)
CI/CD integrationGitHub ActionsAzure Pipelines
Open Source community✅ Very strongWeak
Branch PoliciesBranch Protection RulesBranch Policies (richer)
Code ReviewPull RequestsPull Requests
CODEOWNERS✅ NativeVia Required Reviewers policy
Wiki✅ Native (simplified)✅ Integrated Azure DevOps (rich)
Issues / Work ItemsGitHub IssuesAzure Boards (more complete)
PackagesGitHub PackagesAzure Artifacts
Security ScanningGHAS (paid for private)Defender for DevOps
Self-hostedGitHub Enterprise ServerAzure DevOps Server
SSO IntegrationGitHub Enterprise + Entra IDNative Entra ID

Migrating from TFVC to Git

# TFVC (Team Foundation Version Control) is centralized (legacy)
# Recommendation: migrate to Git

# Tool: git-tfs
# Installation
choco install gittfs    # Windows with Chocolatey

# Clone a TFVC repo to Git
git tfs clone https://dev.azure.com/company/project $/project/trunk

# Selective migration (specific branch)
git tfs clone https://dev.azure.com/company/project $/project/main \
  --branches=none \
  --with-labels

# Push to Azure Repos Git
git remote add origin https://dev.azure.com/company/project/_git/migrated-repo
git push origin --all
git push origin --tags

Review Questions — Source Control AZ-400

Q1 — Scalar

Question: Your organization has a 50 GB Git repository with 10 years of history. git clone operations take 45 minutes for new developers. Which tool allows you to reduce this time by only cloning the necessary files?

  • A. Git LFS
  • B. Git Submodules
  • C. Scalar
  • D. git shallow clone

Explanation: Scalar is specifically designed for very large repositories with long history. It uses sparse checkout (load only necessary folders) and partial clone (--filter=blob:none) to drastically reduce clone time. Git LFS helps with large binary files but not with history. A shallow clone (--depth 1) helps but Scalar offers much more functionality.


Q2 — Branch Policy Bypass

Question: A Lead Developer occasionally needs to approve their own Pull Requests for urgent hotfixes on the main branch. This permission is not assigned to any group by default. How do you configure this?

  • A. Add the developer to the “Project Administrators” group
  • B. Disable the Branch Policy for administrators
  • C. Explicitly assign “Bypass policies when completing pull requests” at the main branch level
  • D. Create a custom group with the “Edit policies” permission

Explanation: The “Bypass policies when completing pull requests” permission is unique because it is not assigned to any group by default, not even Project Administrators. It must be explicitly assigned at the specific branch level (not at the project or repo level). This minimizes the principle of least privilege.


Q3 — Branching Strategy

Question: Your team simultaneously maintains versions 2.x and 3.x of your application for different clients. Some clients cannot migrate to v3. Which branching strategy is most appropriate?

  • A. Trunk-Based Development
  • B. GitHub Flow (main + feature branches)
  • C. Release Branching
  • D. Feature Flag Branching

Explanation: Release Branching is specifically designed to support multiple simultaneous versions. You maintain separate release/v2.x and release/v3.x branches, allowing hotfixes to be applied to both versions independently. Trunk-Based and GitHub Flow do not natively manage multiple versions in parallel.


Q4 — git filter-repo

Question: A developer accidentally committed and pushed a .env file containing API keys to a public GitHub repository 3 days ago. Several commits have been added since. What is the correct approach?

  • A. Create a new commit that deletes the .env file
  • B. git revert the original commit
  • C. git filter-repo --path .env --invert-paths then force push, and immediately invalidate all API keys
  • D. Clone the repo, delete the file, create a new repo

Explanation: Simply removing the file in a new commit does not remove the data from history — the file remains visible in old commits. git filter-repo is the recommended tool for rewriting history and completely removing the file. The force push updates the remote. But most importantly: exposed API keys must be revoked immediately as they could have been copied during these 3 days.


Q5 — Git Tags

Question: Your team wants to tag version 2.1.3 of the application with a message indicating the main changes in this release. What type of tag do you use and what command?

  • A. Lightweight tag: git tag v2.1.3
  • B. Annotated tag: git tag v2.1.3 HEAD
  • C. Annotated tag: git tag -a v2.1.3 -m "Release 2.1.3: Adds OAuth2, fixes cart bug"
  • D. Signed tag: git tag -s v2.1.3

Explanation: An Annotated Tag (-a) is a complete Git object with a message, author, and date. It is appropriate for official releases. A Lightweight Tag is just a pointer without additional metadata. Annotated Tags are best practice for releases.


Glossary — Source Control AZ-400

TermDefinition
Annotated TagGit tag containing a message, an author and a date (full Git object)
Blamegit blame command showing who modified each line of a file
Branch PolicyRule protecting a branch against unauthorized merges
CODEOWNERSGitHub file defining automatic reviewers by file path
Cherry-pickApply a specific commit to another branch
Conventional CommitsStandard commit message format (feat, fix, docs…)
Feature BranchingStrategy: one branch per feature, merged via PR
Git LFSLarge File Storage — stores large files outside the main Git repo
git filter-repoTool for rewriting Git history (remove sensitive files)
Git ReflogJournal of all local Git operations (allows data recovery)
GitFlowStructured workflow with main, develop, feature, release, hotfix branches
GitHub FlowSimplified workflow: main + feature branches + PRs
Lightweight TagSimple Git tag (pointer to a commit, no metadata)
Merge CommitCommit created during a merge (preserves history of both branches)
Pull RequestFormal process for code review and validation before merge
RebaseReapply commits from one branch onto another base
Release BranchingStrategy: dedicated branches to stabilize and maintain versions
ScalarMicrosoft tool to optimize very large Git repositories
Sparse CheckoutLoad only certain folders from the repository into the working tree
Squash MergeCombine all commits of a branch into a single commit at merge
TFVCTeam Foundation Version Control — legacy centralized VCS (alternative: Git)
Trunk-Based DevelopmentStrategy: short branches, continuous integration toward main

Appendix — Essential Git Configurations

Complete .gitignore for .NET / Node.js Projects

# ─── .NET / C# ──────────────────────────────────────────────────────────
# Build outputs
bin/
obj/
*.user
*.suo
.vs/
*.vshost.exe*
*.vsp

# NuGet packages
packages/
*.nupkg
*.nuspec
project.lock.json

# Test results
TestResults/
*.trx
*.coveragexml
coverage.opencover.xml

# Secrets / Credentials - NEVER commit these
*.pfx
*.key
*.crt
secrets.json
appsettings.Development.json
appsettings.Local.json
local.settings.json

# ─── Node.js / Frontend ────────────────────────────────────────────────
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
dist/
build/
.next/
.nuxt/
.cache/

# Environment files - NEVER commit
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# ─── IDEs ─────────────────────────────────────────────────────────────
.idea/
*.swp
*.swo
.DS_Store
Thumbs.db

# ─── Azure / Cloud ────────────────────────────────────────────────────
*.tfstate
*.tfstate.backup
.terraform/
.terraform.lock.hcl
terraform.tfvars     # If it contains secrets

# Docker
.dockerignore       # Don't include itself in the build context

# ─── Miscellaneous ───────────────────────────────────────────────────
*.log
*.tmp
*.temp
.vscode/settings.json  # Local VS Code config (keep launch.json and extensions.json)

.gitattributes — Line Ending Configuration

# .gitattributes - Normalize line endings between Windows and Linux

# Text files: normalize to LF line endings
* text=auto

# C# files: LF
*.cs    text eol=lf
*.csproj text eol=lf
*.sln   text eol=lf

# YAML
*.yml   text eol=lf
*.yaml  text eol=lf

# JSON
*.json  text eol=lf

# Shell scripts: LF required (do not modify)
*.sh    text eol=lf
*.bash  text eol=lf

# Windows scripts: CRLF
*.bat   text eol=crlf
*.cmd   text eol=crlf
*.ps1   text eol=crlf

# Binaries: do not modify
*.png   binary
*.jpg   binary
*.gif   binary
*.ico   binary
*.pdf   binary
*.exe   binary
*.dll   binary
*.zip   binary

# Git LFS (large files)
*.psd   filter=lfs diff=lfs merge=lfs -text
*.sketch filter=lfs diff=lfs merge=lfs -text
*.ai    filter=lfs diff=lfs merge=lfs -text
*.mp4   filter=lfs diff=lfs merge=lfs -text
*.mov   filter=lfs diff=lfs merge=lfs -text

Git Hooks — Local Automation

# Git hooks are in .git/hooks/
# To share them with the team: use Husky (Node.js) or lefthook

# Install Husky for Node.js projects
npm install --save-dev husky
npx husky init

# .husky/pre-commit - Checks before each commit
#!/bin/sh
# Check that the code compiles
dotnet build --no-restore || exit 1

# Linter
dotnet format --verify-no-changes || exit 1

# .husky/commit-msg - Validate commit message format (Conventional Commits)
#!/bin/sh
commit_msg=$(cat "$1")
# Check format: type(scope): description
if ! echo "$commit_msg" | grep -qE "^(feat|fix|docs|style|refactor|perf|test|chore|ci|build)(\(.+\))?: .{1,100}$"; then
  echo "❌ Invalid commit message!"
  echo "Expected format: type(scope): description"
  echo "Example: feat(auth): add Google OAuth login"
  exit 1
fi
echo "✅ Valid commit message format"

Git Aliases — Useful Shortcuts

# Configure global Git aliases
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.cm "commit -m"
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.unstage "restore --staged"
git config --global alias.undo "reset HEAD~1"
git config --global alias.amend "commit --amend --no-edit"
git config --global alias.pushf "push --force-with-lease"
git config --global alias.contributors "shortlog -sn --no-merges"
git config --global alias.find "log --all --full-history --"  # git find -- file.txt

# Usage
git lg                          # Graphical log
git st                          # Status
git cm "feat: add feature"      # Commit with message
git undo                        # Undo last commit (keep changes)
git find -- secrets.json        # Find a file in history
git contributors                 # Top contributors

Azure Repos — Variables and Templates in Pipelines

# Use source branch in a pipeline
trigger:
  - main
  - feature/*

variables:
  # Branch name without "refs/heads/"
  branchName: $[replace(variables['Build.SourceBranch'], 'refs/heads/', '')]
  
  # Detect if it's a PR
  isPR: $[eq(variables['Build.Reason'], 'PullRequest')]
  
  # Detect if it's main
  isMain: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]

steps:
  - script: |
      echo "Branch: $(branchName)"
      echo "Is PR: $(isPR)"
      echo "Is main: $(isMain)"
    displayName: 'Print Build Info'

  # Deploy only if the branch is main and it's not a PR
  - ${{ if and(eq(variables.isMain, 'true'), eq(variables.isPR, 'false')) }}:
    - task: AzureWebApp@1
      displayName: 'Deploy to Production'
      inputs:
        azureSubscription: 'AzureServiceConnection'
        appName: 'my-app-prod'
        package: '$(Build.ArtifactStagingDirectory)/**/*.zip'

Pull Request Templates

<!-- .github/PULL_REQUEST_TEMPLATE.md (GitHub) -->
<!-- or .azuredevops/pull_request_template.md (Azure Repos) -->

## Description
<!-- Briefly describe the changes made -->

## Type of Change
- [ ] Bug fix (non-breaking fix)
- [ ] New feature (non-breaking new functionality)
- [ ] Breaking change (change that breaks the existing API)
- [ ] Documentation update

## How to Test
<!-- Describe the steps to test the changes -->
1. 
2. 
3. 

## Checklist
- [ ] My code follows the project's style conventions
- [ ] I have performed a self-review of my code
- [ ] I have commented on the difficult-to-understand parts
- [ ] I have updated the documentation
- [ ] My changes do not generate new warnings
- [ ] I have added tests that prove my fix/feature works
- [ ] Unit and integration tests pass locally
- [ ] Existing dependencies are not broken

## Linked Work Items
<!-- Azure Boards: AB#1234 | GitHub Issues: Fixes #456 -->

## Screenshots (if applicable)
<!-- Before/after screenshots for UI changes -->

Automated Release Notes

# Generate release notes from Git commits (between two tags)
param(
    [string]$FromTag = "v1.0.0",
    [string]$ToTag = "HEAD",
    [string]$OutputFile = "RELEASE_NOTES.md"
)

# Get commits between the two tags
$commits = git log "$FromTag..$ToTag" --pretty=format:"%s|%h|%an|%ae" --no-merges |
    Where-Object { $_ -ne "" }

$features = @()
$fixes = @()
$docs = @()
$breaking = @()
$other = @()

foreach ($commit in $commits) {
    $parts = $commit -split "\|"
    $message = $parts[0]
    $hash = $parts[1]
    $author = $parts[2]
    
    $entry = "- $message ([``$hash``](https://github.com/org/repo/commit/$hash)) by @$author"
    
    switch -Regex ($message) {
        "^feat" { $features += $entry }
        "^fix" { $fixes += $entry }
        "^docs" { $docs += $entry }
        "BREAKING CHANGE" { $breaking += $entry }
        default { $other += $entry }
    }
}

# Generate the Markdown file
$content = @"
# Release Notes — $(Get-Date -Format "yyyy-MM-dd")

## 🚀 New Features
$(if ($features) { $features -join "`n" } else { "None" })

## 🐛 Bug Fixes
$(if ($fixes) { $fixes -join "`n" } else { "None" })

## 📚 Documentation
$(if ($docs) { $docs -join "`n" } else { "None" })

## ⚠️ Breaking Changes
$(if ($breaking) { $breaking -join "`n" } else { "None" })

## 🔧 Other Changes
$(if ($other) { $other -join "`n" } else { "None" })

---
*Generated from $FromTag to $ToTag*
"@

$content | Out-File -FilePath $OutputFile -Encoding UTF8
Write-Host "✅ Release notes generated in $OutputFile"

Quick Reference for the Exam

Git Commands to Memorize

# Remove data from history
git filter-repo --path secrets.json --invert-paths

# Recover "lost" commits
git reflog
git checkout -b recovery <hash>

# Optimize a large repo
scalar register .
git sparse-checkout set src/ tests/

# LFS
git lfs track "*.psd"
git lfs install

# Tags
git tag -a v1.0.0 -m "Release message"    # Annotated
git tag v1.0.0                             # Lightweight
git push origin --tags                     # Push all tags

# Submodules
git submodule add <url> <path>
git submodule update --init --recursive

# Changelogs
git log --oneline v1.0..v2.0              # Commits between versions
git log --pretty=format:"%h %s" --no-merges  # Custom format

Key Points for AZ-400 Source Control Exam

ConceptWhat to know
ScalarOptimizes large repos: partial clone + sparse checkout + auto maintenance
Git LFSLarge binary files: stored outside the main repo
git filter-repoRemove data from ALL history + force push required
git reflogRecover locally deleted commits/branches after reset --hard
Annotated Tag-a = full object with message (recommended for releases)
Branch PolicyBypass permission = NOT in any group by default, must be explicitly assigned
Trunk-BasedSmall team, strict CI, short branches (hours/days)
Feature BranchingMedium team, PRs for each feature, code isolation
Release BranchingMulti-version, stabilization before release, hotfixes on old versions
CODEOWNERSAutomatic reviewers by file path (GitHub)
Azure ReposSupports Git AND TFVC (legacy)
GitHubGit only, strong open-source community

Practical Git Exercises

Scenario 1: Complete Feature Branch Workflow

# Context: you are working on the "user authentication" feature

# 1. Sync with main
git checkout main
git pull origin main

# 2. Create a feature branch from main
git checkout -b feature/user-auth-oauth

# 3. Development and commits
echo "// OAuth implementation" >> src/auth/OAuthService.cs
git add src/auth/OAuthService.cs
git commit -m "feat(auth): scaffold OAuth service structure"

echo "// Token validation" >> src/auth/TokenValidator.cs
git add src/auth/TokenValidator.cs
git commit -m "feat(auth): add JWT token validation"

# 4. Unit tests
echo "// Tests" >> tests/auth/OAuthServiceTests.cs
git add tests/auth/OAuthServiceTests.cs
git commit -m "test(auth): add unit tests for OAuth service"

# 5. Check divergences before PR
git fetch origin
git log --oneline origin/main..HEAD     # My commits not yet on main
git diff origin/main                     # Changes relative to main

# 6. Push the branch and create the PR
git push -u origin feature/user-auth-oauth
# → Create the PR in GitHub / Azure DevOps

# After approval and merge:
git checkout main
git pull origin main
git branch -d feature/user-auth-oauth    # Delete the local branch

Scenario 2: Urgent Production Hotfix

# Context: critical bug found in production v2.3.1

# 1. Create a hotfix branch from the production tag
git checkout -b hotfix/security-patch v2.3.1

# 2. Fix the bug
git commit -m "fix(security): patch SQL injection vulnerability AB#9999"

# 3. Tag and merge into main
git checkout main
git merge --no-ff hotfix/security-patch
git tag -a v2.3.2 -m "Hotfix: SQL injection patch"
git push origin main --follow-tags

# 4. Also merge into develop (GitFlow)
git checkout develop
git merge --no-ff hotfix/security-patch
git push origin develop

# 5. Cleanup
git branch -d hotfix/security-patch
git push origin --delete hotfix/security-patch

Scenario 3: Resolve a Merge Conflict

# Context: conflict between feature/cart and main

git checkout feature/cart
git rebase main    # Rebase onto main for linear history

# Conflict detected in src/Cart.cs
git status         # View conflicting files
git diff           # View conflict markers

# Manually resolve in src/Cart.cs:
# Remove markers <<<<<<, =======, >>>>>>>
# Keep the correct version or combine both

git add src/Cart.cs
git rebase --continue    # Continue the rebase

# If the conflict is too complex, abort
# git rebase --abort

# Once rebase is done, force push is required
git push --force-with-lease origin feature/cart
# (force-with-lease is safer than --force: checks that no other push has occurred)

Exam Checkpoints

  • git filter-repo is the recommended tool (faster and safer than git filter-branch).
  • git reflog allows recovery of locally lost commits after a reset --hard.
  • Branch Policy “Bypass” is NOT assigned to ANY group by default.
  • Sparse checkout reduces files in the working tree, not the history.
  • Partial clone (--filter=blob:none) reduces what is downloaded from the remote.
  • Annotated tags (-a): include author, date, message. Recommended for releases.
  • Submodules: useful for sharing code between multiple repos. Manual update required.
  • Git LFS: blobs are stored separately, the Git repo contains only pointers.

Search Terms

az-400 · source · control · azure · devops · iac · microsoft · git · branching · repos · branch · data · github · scenario · commit · policies · strategies · comparison · configuration · exam · permissions · removing · restore · scalar

Interested in this course?

Contact us to book it or get a custom training plan for your team.