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
| Platform | Type | Description |
|---|---|---|
| Git (Core Git) | Agnostic, open-source | Distributed VCS, branching, merging. Used by GitHub, Azure Repos, GitLab, Bitbucket. |
| GitHub | Vendor-specific (Microsoft) | Cloud-based Git, PRs, code reviews, collaboration, strong open-source community. |
| Azure Repos | Vendor-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
.gitignoreto 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.mdat the root of the repo. - Creation: manual, or automatic via tools like
git-changelog-command-line.
Git Tags
Two Types of Tags
| Type | Description | Command |
|---|---|---|
| Lightweight | Simple pointer to a commit | git tag v1.2 |
| Annotated | Full Git object with message, author, date | git 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(replacesgit 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
| Group | Permissions |
|---|---|
| Project Administrators | Full admin |
| Contributors | Push, create branches, create PRs |
| Build Administrators | Manage pipelines |
| Readers | Read-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
| Role | Permissions |
|---|---|
| Read | Read-only, report issues |
| Triage | Manage issues and PRs, no push |
| Write | Push code, create branches |
| Maintain | Manage the repo (no delete/critical settings) |
| Admin | Full 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
| Component | Description |
|---|---|
| What | What changes were made? |
| Why | Business/technical objectives of these changes |
| How | Design decisions and justifications |
| Verification | Tests performed, screenshots |
PR Lifecycle
- Create a branch.
- Commit changes.
- Formally create the Pull Request.
- Review + discussions + suggestions.
- Approval by reviewers.
- Merge into main.
Branch Policies and Permissions
Problem Without Policies
- Developers can merge directly without review → bugs in production.
Branch Policies in Azure Repos
| Policy | Description |
|---|---|
| Require a successful build | CI must pass before merge |
| Require minimum number of reviewers | E.g. at least 2 approvers |
| Check for comment resolution | All comments must be resolved |
| Require linked work items | PR must reference a work item |
| Enforce a merge strategy | Squash, 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
| Scenario | Tool/Answer |
|---|---|
| Large repo with performance issues (cloning, metadata) → tool? | Scalar (selective sync, sparse checkouts, maintenance) |
| User needs to bypass branch policies only for one branch | Explicitly assign “Bypass policies when completing pull requests” at the branch level |
| Small team, frequent commits to main, short branches | Trunk-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 history | git filter-repo + force push |
| Lightweight vs annotated tags | Lightweight = 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:
| Branch | Lifetime | Objective | Source | Merge Target |
|---|---|---|---|---|
main | Permanent | Production code | — | — |
develop | Permanent | Feature integration | main | — |
feature/* | Temporary | Feature development | develop | develop |
release/* | Temporary | Stabilization before release | develop | main + develop |
hotfix/* | Temporary | Urgent production fix | main | main + develop |
Branching Strategy Comparison
| Criterion | Trunk-Based | Feature Branching | GitFlow | GitHub Flow |
|---|---|---|---|---|
| Complexity | Low | Medium | High | Low |
| Active branches | 1 | N features | main + develop + features | main + features |
| CI/CD | ✅ Ideal | ✅ Good | ⚠️ Complex | ✅ Ideal |
| Team size | Small (1-5) | Medium (5-20) | Large (20+) | Small to large |
| Release cadence | Continuous | Iterations | Planned | Continuous |
| Merge conflict risk | Low | Medium | High | Low |
| Multi-version support | No | No | ✅ Yes | No |
Conventional Commits — Message Standard
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
Standard Types:
| Type | Description | SemVer |
|---|---|---|
feat | New feature | MINOR |
fix | Bug fix | PATCH |
docs | Documentation only | — |
style | Formatting, whitespace (no logic) | — |
refactor | Refactoring without new feature or bug fix | — |
perf | Performance improvement | PATCH |
test | Adding/modifying tests | — |
chore | Maintenance, dependencies, tooling | — |
ci | CI/CD changes | — |
BREAKING CHANGE | Breaking 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
| Feature | GitHub | Azure Repos |
|---|---|---|
| VCS supported | Git only | Git + TFVC (legacy) |
| CI/CD integration | GitHub Actions | Azure Pipelines |
| Open Source community | ✅ Very strong | Weak |
| Branch Policies | Branch Protection Rules | Branch Policies (richer) |
| Code Review | Pull Requests | Pull Requests |
| CODEOWNERS | ✅ Native | Via Required Reviewers policy |
| Wiki | ✅ Native (simplified) | ✅ Integrated Azure DevOps (rich) |
| Issues / Work Items | GitHub Issues | Azure Boards (more complete) |
| Packages | GitHub Packages | Azure Artifacts |
| Security Scanning | GHAS (paid for private) | Defender for DevOps |
| Self-hosted | GitHub Enterprise Server | Azure DevOps Server |
| SSO Integration | GitHub Enterprise + Entra ID | Native 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
mainbranch 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.xandrelease/v3.xbranches, 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
.envfile - B.
git revertthe original commit - C.
git filter-repo --path .env --invert-pathsthen 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-repois 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
| Term | Definition |
|---|---|
| Annotated Tag | Git tag containing a message, an author and a date (full Git object) |
| Blame | git blame command showing who modified each line of a file |
| Branch Policy | Rule protecting a branch against unauthorized merges |
| CODEOWNERS | GitHub file defining automatic reviewers by file path |
| Cherry-pick | Apply a specific commit to another branch |
| Conventional Commits | Standard commit message format (feat, fix, docs…) |
| Feature Branching | Strategy: one branch per feature, merged via PR |
| Git LFS | Large File Storage — stores large files outside the main Git repo |
| git filter-repo | Tool for rewriting Git history (remove sensitive files) |
| Git Reflog | Journal of all local Git operations (allows data recovery) |
| GitFlow | Structured workflow with main, develop, feature, release, hotfix branches |
| GitHub Flow | Simplified workflow: main + feature branches + PRs |
| Lightweight Tag | Simple Git tag (pointer to a commit, no metadata) |
| Merge Commit | Commit created during a merge (preserves history of both branches) |
| Pull Request | Formal process for code review and validation before merge |
| Rebase | Reapply commits from one branch onto another base |
| Release Branching | Strategy: dedicated branches to stabilize and maintain versions |
| Scalar | Microsoft tool to optimize very large Git repositories |
| Sparse Checkout | Load only certain folders from the repository into the working tree |
| Squash Merge | Combine all commits of a branch into a single commit at merge |
| TFVC | Team Foundation Version Control — legacy centralized VCS (alternative: Git) |
| Trunk-Based Development | Strategy: 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
| Concept | What to know |
|---|---|
| Scalar | Optimizes large repos: partial clone + sparse checkout + auto maintenance |
| Git LFS | Large binary files: stored outside the main repo |
| git filter-repo | Remove data from ALL history + force push required |
| git reflog | Recover locally deleted commits/branches after reset --hard |
| Annotated Tag | -a = full object with message (recommended for releases) |
| Branch Policy | Bypass permission = NOT in any group by default, must be explicitly assigned |
| Trunk-Based | Small team, strict CI, short branches (hours/days) |
| Feature Branching | Medium team, PRs for each feature, code isolation |
| Release Branching | Multi-version, stabilization before release, hotfixes on old versions |
| CODEOWNERS | Automatic reviewers by file path (GitHub) |
| Azure Repos | Supports Git AND TFVC (legacy) |
| GitHub | Git 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-repois the recommended tool (faster and safer thangit filter-branch).git reflogallows recovery of locally lost commits after areset --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