Basics Lesson 2 of 16

Azure Repos & Branching Strategies

Master Git-based version control in Azure DevOps — repositories, branch policies, pull request workflows, merge strategies, and branching models that keep your codebase stable and your team productive.

🧒 Simple Explanation (ELI5)

Imagine a team of artists all working on the same giant painting. If everyone just paints on one canvas at the same time, it's chaos — someone paints a tree where someone else was painting a cloud, colors get mixed up, and nobody knows who changed what.

Version control is like giving each artist their own transparent overlay sheet. Everyone draws on their own sheet. When they're happy with their work, they hold it up and say "Hey team, I'd like to add this to the master painting." The team reviews it, checks it doesn't clash with other changes, and then carefully layers it onto the main canvas.

Without branching? Developers push directly to the main codebase, bugs land in production instantly, and nobody reviews anything. It's the equivalent of all artists painting on the same canvas with no coordination — guaranteed collisions and messes that take hours to untangle.

💡
Fun Fact

The Linux kernel — one of the largest open-source projects in history — manages over 30,000 files and contributions from thousands of developers worldwide. It was the project that motivated Linus Torvalds to create Git in 2005. Azure Repos uses that same Git engine under the hood.

🔧 Technical Explanation

Git vs. TFVC in Azure Repos

Azure Repos supports two version control systems. Git is the default and recommended choice for all new projects.

AspectGit (Distributed)TFVC (Centralized)
ArchitectureEvery developer has a full copy of the repo locally — commits, branches, and history all happen offlineCode lives on a central server. Developers check out files, edit, and check back in. No local history.
BranchingLightweight — branches are just pointers. Creating a branch is instant, even on repos with millions of files.Heavy — branches are server-side copies. Branch creation is slower and consumes server resources.
Offline WorkFull functionality offline — commit, branch, diff, log, blame all work without network.Requires server connection for most operations. Offline edits cause conflicts on reconnect.
Merge ModelThree-way merge with advanced conflict resolution. PRs are native and expected.Server-based merge. Conflicts resolved at check-in time.
EcosystemUniversal — every CI/CD system, IDE, and code review tool supports Git natively.Proprietary — only Visual Studio (classic) and Azure DevOps web UI support TFVC.
RecommendationAlways choose Git for new projectsOnly for legacy codebases migrating from TFS that can't convert yet
Important

Microsoft has announced that TFVC will no longer receive new features. All investment goes into Git-based Azure Repos. If you're starting fresh, there is zero reason to pick TFVC. If you're on TFVC, plan a migration to Git using the built-in import tool or git-tfs.

Azure Repos Key Features

Branch Policies

Branch policies are rules that enforce code quality and team governance on protected branches (typically main, develop, and release/*). They're configured per branch (or branch pattern) in Project Settings → Repositories → Policies.

PolicyWhat It DoesTypical Setting
Minimum number of reviewersPRs must have N approving reviewers before merge is allowed2 reviewers for main, 1 for develop
Check for linked work itemsRequires at least one Azure Boards work item linked to the PRRequired on all protected branches
Check for comment resolutionAll PR comments must be resolved (marked as "resolved" or "won't fix") before mergeRequired — prevents ignored feedback
Build validationA CI pipeline must run and pass on the PR branch before mergeRequired — catches build/test failures before merge
Limit merge typesRestrict which merge strategies are allowed (squash, rebase, merge commit, semi-linear)Squash merge only for feature branches
Automatically include reviewersAuto-add specific reviewers when certain file paths change (e.g., infra team for /terraform/**)Path-based code owners
Reset votes on new pushesIf the author pushes new commits after approval, previous approvals are reset — reviewers must re-approveRequired for high-security branches
⚠️
Warning

Branch policies only apply to changes coming through pull requests. They do not prevent project administrators from bypassing or pushing directly (unless you also remove the "Bypass policies when pushing" permission from the admin group in repository security settings). Always audit these permissions for production-critical branches.

Pull Request Workflow

Pull requests are the core collaboration mechanism in Azure Repos. Here's the complete lifecycle:

  1. Developer creates a branch from main (e.g., feature/user-login)
  2. Developer writes code, makes commits, and pushes the branch to the remote
  3. Developer opens a PR — sets a title, description, links work items, and requests reviewers
  4. Build validation runs automatically — CI pipeline triggers on the PR branch. If it fails, the PR is blocked.
  5. Reviewers review the code — leave inline comments, suggest changes, request modifications, or approve
  6. Developer addresses feedback — pushes additional commits. If "reset votes on push" is enabled, reviewers re-approve.
  7. All policies pass — required reviewers approved, build succeeded, comments resolved, work item linked
  8. PR is merged — using the allowed merge strategy (squash, rebase, or merge commit)
  9. Branch is deleted — the feature branch is cleaned up automatically (if configured)

Merge Strategies

Azure Repos supports four merge strategies. Understanding when to use each is critical for maintaining a clean Git history.

1. Merge Commit (No Fast-Forward)

Creates a merge commit that joins the feature branch into the target. All individual commits from the feature branch are preserved in history.

text
# History after merge commit:
*   M  Merge PR #42: feature/login → main
|\
| * C3  Add login validation
| * C2  Add login form
| * C1  Create auth module
|/
* B  Previous commit on main

Pros: Full history preserved, easy to see what belonged to a feature branch.
Cons: History can get noisy with many branches. Hard to read linear log.
Best for: Teams that value complete history and traceability.

2. Squash Merge

Combines all commits from the feature branch into a single commit on the target branch. The feature branch history is discarded.

text
# History after squash merge:
* S  Add user login feature (squashed from PR #42)
* B  Previous commit on main

# The 3 individual commits (C1, C2, C3) are gone from main's history

Pros: Clean, linear history. Each PR = one commit. Easy to revert an entire feature.
Cons: Loses granular commit history. Can't see intermediate steps.
Best for: Most teams — the recommended default. Feature history lives in the PR; main stays clean.

3. Rebase

Replays each commit from the feature branch on top of the target branch, creating new commit hashes. No merge commit.

text
# History after rebase:
* C3' Add login validation
* C2' Add login form
* C1' Create auth module
* B   Previous commit on main

# Commits are replayed with new hashes (C1', C2', C3')

Pros: Linear history with all individual commits preserved.
Cons: Rewrites history (new hashes). Can cause issues if commits aren't well-structured.
Best for: Teams with disciplined commit hygiene who want linear history without squash.

4. Semi-Linear Merge (Rebase + Merge Commit)

First rebases the feature branch on the target, then creates a merge commit. Combines linear history with merge context.

text
# History after semi-linear merge:
*   M  Merge PR #42: feature/login → main
|\
| * C3' Add login validation
| * C2' Add login form
| * C1' Create auth module
|/
* B   Previous commit on main

Pros: Clean merge topology — every feature branch is based on the latest main.
Cons: More complex. Requires the branch to be up-to-date with main before merging.
Best for: Teams that want the structure of merge commits but the cleanliness of rebased branches.

Branching Strategies: Git Flow vs. Trunk-Based Development

Your branching strategy defines how your team organizes branches and manages the flow of code from development to production. The two dominant strategies are Git Flow and Trunk-Based Development.

Git Flow

A structured model with multiple long-lived branches for different stages of development:

Best for: Teams with scheduled releases, multiple versions in production, or long QA cycles.

Trunk-Based Development

A simpler model where all development happens on a single branch (main) with very short-lived feature branches:

Best for: Teams practicing continuous delivery/deployment, microservices, and modern cloud-native workflows.

AspectGit FlowTrunk-Based Development
Branches5 types (main, develop, feature, release, hotfix)2 types (main + short-lived features)
Branch lifetimeFeatures: days–weeks. Develop: permanent.Features: hours to 1–2 days max.
Merge conflictsHigher — long-lived branches diverge significantlyLower — branches are short, frequent integration
Release processFormal: cut a release branch, QA, merge, tagContinuous: every merge to main is deployable
ComplexityHigher — more branches to manage, more merge pathsLower — one branch, simple rules
CI/CD maturityModerate — CI on develop, CD on release branchesHigh — CI/CD on every commit to main
Feature flagsNot required (features isolated in branches)Essential (incomplete features merged to main)
Best forScheduled releases, versioned products, mobile appsWeb services, microservices, continuous deployment
💡
Tip

Most modern teams (including Microsoft, Google, and Meta) use Trunk-Based Development. If you're building a web application or microservices deployed to Kubernetes, TBD with feature flags is the right choice. Git Flow is still valid for versioned desktop software, mobile apps, or products supporting multiple versions simultaneously.

📊 Branching Strategy Comparison

Git Flow Model
main
production — tagged releases
develop
integration — next release
feature/login
→ PR →
merges back to develop
feature/cart
→ PR →
merges back to develop
release/1.0
cut from develop → QA → merge to main + develop
hotfix/sec
cut from main → fix → merge to main + develop
Trunk-Based Development Model
main (trunk)
always deployable — continuous delivery
feature/login (hours)
→ squash PR →
main
feature/cart (1 day)
→ squash PR →
main
fix/bug-123 (2 hours)
→ squash PR →
main
🚀 Every merge triggers: CI build → automated tests → deploy to staging → deploy to production

🛠️ Hands-on: Branches, Policies & Pull Requests

Follow these steps to set up a professional branching workflow in Azure DevOps from scratch.

Prerequisites

Step 1: Clone the Repository

bash
# Clone via HTTPS (use your PAT as the password when prompted)
git clone https://dev.azure.com/{org}/{project}/_git/{repo}
cd {repo}

# Verify you're on the main branch
git branch
# * main

Step 2: Create a Feature Branch

bash
# Create and switch to a new feature branch
git checkout -b feature/user-authentication

# Verify you're on the new branch
git branch
#   main
# * feature/user-authentication

# Make some changes
echo "# Authentication Module" > auth.md
echo "def login(user, password):" > auth.py
echo "    return authenticate(user, password)" >> auth.py

# Stage and commit
git add .
git commit -m "feat: add authentication module skeleton"

# Push the branch to Azure Repos
git push -u origin feature/user-authentication
💡
Branch Naming Convention

Use a prefix to categorize branches: feature/ for new functionality, bugfix/ for bug fixes, hotfix/ for production emergencies, release/ for release preparation. Include the work item ID when possible: feature/1234-user-login. This makes it easy to link branches to Azure Boards work items.

Step 3: Set Branch Policies on main

Configure branch policies in the Azure DevOps web UI:

  1. Navigate to Project Settings → Repositories → your repo → Policies
  2. Under Branch Policies, select the main branch
  3. Configure the following policies:
text
✅ Require a minimum number of reviewers
   → Minimum reviewers: 2
   → Allow requestors to approve their own changes: No
   → Reset code reviewer votes when there are new changes: Yes

✅ Check for linked work items
   → Required

✅ Check for comment resolution
   → Required

✅ Limit merge types
   → ✅ Squash merge
   → ❌ Basic merge (no fast-forward)
   → ❌ Rebase and fast-forward
   → ❌ Rebase with merge commit

Step 4: Add Build Validation

Still on the branch policies page for main:

  1. Scroll to Build Validation and click "+ Add build policy"
  2. Select your CI pipeline (e.g., MyApp-CI)
  3. Set the trigger to "Automatic" — the build runs on every PR update
  4. Set "Policy requirement" to "Required" — PR cannot be merged unless the build passes
  5. Set a display name: CI Build Validation
  6. Set build expiration: "Immediately when main is updated" — if someone else merges to main before you, your validation re-runs
Important

Build validation is the single most impactful branch policy. It guarantees that no PR can be merged with a broken build or failing tests. Configure this before anything else — it's the safety net that catches 90% of integration issues.

Step 5: Create a Pull Request

bash
# Push your latest changes
git push origin feature/user-authentication

Then in the Azure DevOps web UI:

  1. Navigate to Repos → Pull Requests → "New pull request"
  2. Set source: feature/user-authentication → target: main
  3. Title: feat: add user authentication module
  4. Description: explain what the PR does, why, and any testing notes
  5. Link a work item: click "Link work items" and select the related user story
  6. Add reviewers: select team members from your project
  7. Click "Create"
💡
CLI Alternative

You can also create PRs from the command line using the Azure CLI:

bash
# Install Azure DevOps CLI extension
az extension add --name azure-devops

# Sign in
az devops configure --defaults organization=https://dev.azure.com/{org} project={project}
az login

# Create PR from command line
az repos pr create \
  --repository {repo} \
  --source-branch feature/user-authentication \
  --target-branch main \
  --title "feat: add user authentication module" \
  --description "Adds the auth module skeleton with login function" \
  --work-items 1234

Step 6: Review and Merge the PR

  1. Reviewers open the PR, review the diff, leave inline comments
  2. The author addresses comments and pushes fixes (votes reset if configured)
  3. Reviewers approve — the "Approve" button shows a green checkmark
  4. All policies pass: ✅ Reviewers, ✅ Build passed, ✅ Comments resolved, ✅ Work item linked
  5. Click "Complete" → set merge type to "Squash commit"
  6. Check "Delete feature/user-authentication after merging"
  7. Click "Complete merge"

Step 7: Clean Up Locally

bash
# Switch back to main and pull the merged changes
git checkout main
git pull origin main

# Delete the local feature branch (it's been merged and deleted on remote)
git branch -d feature/user-authentication

# Prune remote-tracking branches that no longer exist on the server
git fetch --prune

# Verify clean state
git log --oneline -5
# abc1234 feat: add user authentication module (squashed)

Essential Git Commands Reference

bash
# ===== BRANCHING =====
git branch                           # List local branches
git branch -r                        # List remote branches
git branch -a                        # List all branches (local + remote)
git checkout -b feature/new-thing    # Create and switch to new branch
git switch -c feature/new-thing      # Same as above (modern Git syntax)
git branch -d feature/merged-branch  # Delete a local branch (safe — only if merged)
git branch -D feature/force-delete   # Force-delete a local branch (use with caution)
git push origin --delete feature/old # Delete a remote branch

# ===== REBASING & UPDATING =====
git fetch origin                     # Download remote changes without merging
git pull --rebase origin main        # Pull main and rebase your branch on top
git rebase main                      # Rebase current branch onto main
git rebase --abort                   # Cancel an in-progress rebase

# ===== STASHING =====
git stash                            # Save uncommitted changes temporarily
git stash pop                        # Restore stashed changes
git stash list                       # List all stashes

# ===== LOG & HISTORY =====
git log --oneline --graph --all      # Visual branch graph
git log --author="name" --since="2 weeks ago"  # Filter by author and date
git blame path/to/file.py           # See who changed each line and when
git diff main..feature/branch        # Compare two branches

# ===== UNDOING =====
git reset --soft HEAD~1              # Undo last commit, keep changes staged
git reset --hard HEAD~1              # Undo last commit AND discard changes (DANGEROUS)
git revert abc1234                   # Create a new commit that undoes a specific commit (safe)

🐛 Debugging Scenarios

Scenario 1: "PR Stuck in Waiting — Build Validation Failing"

Symptom: Your PR shows "1 check failed" or "Waiting for build validation." The Complete button is disabled.

Root Causes & Fixes:

bash
# Quick fix: update your branch with latest main to trigger a rebuild
git fetch origin
git rebase origin/main
git push --force-with-lease origin feature/your-branch

# --force-with-lease is safer than --force — it refuses to push
# if someone else has pushed to your branch since your last fetch

Scenario 2: "Cannot Push to Main — Branch Policy Violation"

Symptom: Running git push origin main returns an error like: "TF402455: Pushes to this branch are not permitted; you must use a pull request to update this branch."

Root Causes & Fixes:

bash
# You accidentally committed to main locally. Move the commit to a feature branch:

# 1. Create a new branch from your current position (which has the accidental commit)
git branch feature/my-accidental-work

# 2. Reset main back to match the remote
git reset --hard origin/main

# 3. Switch to the feature branch and push
git checkout feature/my-accidental-work
git push -u origin feature/my-accidental-work

# 4. Now create a PR in the web UI

Scenario 3: "Merge Conflicts in Pull Request"

Symptom: The PR shows "This pull request has merge conflicts that must be resolved before it can be completed." Files are listed with conflict indicators.

Root Causes & Fixes:

bash
# Option A: Rebase your branch onto latest main (preferred — cleaner history)
git fetch origin
git checkout feature/your-branch
git rebase origin/main

# If conflicts appear, Git will pause at each conflicting commit:
# 1. Open the conflicting files — look for conflict markers:
#    <<<<<<< HEAD
#    (your changes)
#    =======
#    (changes from main)
#    >>>>>>> origin/main

# 2. Edit the file to keep the correct code (remove all markers)

# 3. Stage the resolved file
git add path/to/resolved-file.py

# 4. Continue the rebase
git rebase --continue

# 5. Push (force-with-lease because rebase rewrites history)
git push --force-with-lease origin feature/your-branch

# ====================

# Option B: Merge main into your branch (simpler but creates a merge commit)
git fetch origin
git checkout feature/your-branch
git merge origin/main

# Resolve conflicts the same way (steps 1–3 above)
git add path/to/resolved-file.py
git commit   # Git auto-generates a merge commit message
git push origin feature/your-branch
⚠️
Tip

You can also resolve simple conflicts directly in the Azure DevOps web UI. Click the conflicting file in the PR, and use the inline conflict editor to choose "Take source" or "Take target" for each conflict block. This works for simple cases but is not practical for complex conflicts across many files.

Scenario 4: "Clone Authentication Failed"

Symptom: Running git clone returns fatal: Authentication failed for 'https://dev.azure.com/...'.

Root Causes & Fixes:

bash
# Windows: Clear cached credentials
git credential-manager reject https://dev.azure.com

# Or use Credential Manager UI:
# Control Panel → Credential Manager → Windows Credentials
# Find and delete entries for "git:https://dev.azure.com"

# macOS: Clear from Keychain
git credential-osxkeychain erase <<EOF
host=dev.azure.com
protocol=https
EOF

# Linux: If using credential store
git config --global --unset credential.helper
# Then re-clone to be prompted for fresh credentials

# Alternative: Use SSH instead of HTTPS
# 1. Generate SSH key
ssh-keygen -t ed25519 -C "your-email@company.com"

# 2. Add public key to Azure DevOps:
#    User Settings → SSH Public Keys → + New Key
#    Paste contents of ~/.ssh/id_ed25519.pub

# 3. Clone with SSH URL
git clone git@ssh.dev.azure.com:v3/{org}/{project}/{repo}

🎯 Interview Questions

Beginner

Q: What is Azure Repos and what version control systems does it support?

Azure Repos is the version control service within Azure DevOps that provides enterprise-grade Git repositories. It supports two version control systems: Git (distributed) — the recommended choice for all new projects, providing full local history, lightweight branching, and offline capability. TFVC (Team Foundation Version Control, centralized) — a legacy system carried over from TFS, where code lives on a central server and developers check out files. Each project can have multiple Git repositories. Azure Repos provides branch policies, pull request workflows, code search, web-based editing, Git LFS for large files, and integrates with Azure Boards and Azure Pipelines. Microsoft recommends Git for all new projects — TFVC receives no new features.

Q: What is a branch policy and why is it important?

A branch policy is a rule configured on a branch (typically main) that enforces quality and process requirements before code can be merged via pull request. Key policies include: minimum reviewers (e.g., 2 approvals required), build validation (CI pipeline must pass), linked work items (traceability to user stories), comment resolution (all review comments must be resolved), and merge type restrictions (e.g., squash only). Branch policies are critical because they automate enforcement of team standards — nobody can skip code review, merge broken builds, or bypass traceability. Without them, quality depends entirely on developer discipline, which doesn't scale. They're configured per branch or branch pattern in Project Settings → Repositories → Policies.

Q: What is the difference between a merge commit, squash merge, and rebase?

Merge commit: Creates a new commit that joins two branches, preserving all individual commits from the feature branch in history. The history shows the branch topology. Squash merge: Combines all commits from the feature branch into a single commit on the target branch. The granular history is lost from the main branch (but preserved in the PR). Produces a clean, linear history. Rebase: Replays each commit from the feature branch on top of the target branch, creating new commit hashes. Produces a linear history while preserving individual commits. When to use each: Squash is the most popular for team workflows — each PR becomes one commit, easy to revert. Merge commit when you need full history. Rebase for teams with disciplined commit practices who want linear history without squashing.

Q: How do you create a pull request in Azure DevOps?

You create a PR after pushing a feature branch to Azure Repos. Steps: 1. Push your branch: git push -u origin feature/my-feature. 2. In Azure DevOps web UI, go to Repos → Pull Requests → New Pull Request. 3. Select the source branch (feature/my-feature) and target branch (main). 4. Write a descriptive title and description explaining the changes. 5. Link related work items for traceability. 6. Add reviewers. 7. Click Create. Alternatively, use the Azure CLI: az repos pr create --source-branch feature/my-feature --target-branch main --title "My PR". After creation, build validation runs automatically (if configured), reviewers are notified, and the PR shows policy compliance status.

Q: What is the difference between git pull and git fetch?

git fetch downloads new commits, branches, and tags from the remote repository to your local machine, but does not modify your working directory or current branch. It updates your remote-tracking branches (e.g., origin/main) so you can see what's changed. git pull is essentially git fetch + git merge — it downloads remote changes AND immediately merges them into your current branch. Best practice: Use git fetch first to see what's changed, then decide how to integrate (merge or rebase). Or use git pull --rebase to fetch and rebase your local commits on top of remote changes, avoiding unnecessary merge commits.

Intermediate

Q: Compare Git Flow and Trunk-Based Development. When would you use each?

Git Flow uses multiple long-lived branches: main (production), develop (integration), feature/*, release/*, and hotfix/*. It's structured with formal release processes — features merge to develop, release branches are cut for QA, then merged to main with version tags. Best for: scheduled releases, versioned products (mobile apps, desktop software), and teams supporting multiple versions. Trunk-Based Development (TBD) uses one long-lived branch (main) with very short-lived feature branches (hours to 1–2 days). Incomplete features are hidden behind feature flags. Every merge to main is deployable. Best for: web services, microservices, continuous deployment, and teams with mature CI/CD. Key trade-off: Git Flow provides isolation at the cost of complexity and merge pain. TBD provides simplicity and speed at the cost of requiring feature flags and robust CI/CD. Most modern teams (Google, Microsoft, Meta) use TBD.

Q: How do you configure automatic code reviewers based on file paths in Azure Repos?

Use the "Automatically included reviewers" branch policy. In Project Settings → Repositories → Policies → [branch], add entries under "Automatically include reviewers" with: 1. A path filter (e.g., /terraform/**, /src/security/**, *.sql). 2. The reviewers to auto-add when those paths are modified. 3. Whether the review is "Required" (blocks merge) or "Optional" (advisory). This implements a code-owners-like pattern: infrastructure changes auto-add the DevOps team, database changes auto-add the DBA, and security-sensitive code auto-adds the security team. You can also set a minimum number of approvals from the auto-included reviewers. This ensures domain experts always review changes in their area without relying on PR authors to remember to add them.

Q: What is git rebase and when should you avoid it?

git rebase moves (replays) your branch's commits on top of another branch's latest commit, rewriting commit history with new hashes. It creates a linear history without merge commits. When to use: Rebasing your local feature branch onto main before creating a PR, to ensure a clean merge and up-to-date base. Also useful for interactive rebase (git rebase -i) to squash, reorder, or edit commits before sharing. When to avoid: Never rebase shared branches — if other developers have based work on your branch, rewriting history will cause conflicts and confusion. Never rebase main, develop, or any branch others are pulling from. The rule is: rebase local branches before sharing; never rebase branches that have already been pushed and shared. Use git push --force-with-lease (not --force) after rebasing a branch you've previously pushed, as a safety check.

Q: How do you handle large binary files in Azure Repos?

Use Git LFS (Large File Storage). Git is designed for text files — storing large binaries (images, videos, ML models, compiled assets) directly in Git bloats the repo, slows clones, and wastes storage. Git LFS replaces large files with pointer files in the repo while storing the actual content on a separate LFS server. Steps: 1. Install Git LFS: git lfs install. 2. Track file patterns: git lfs track "*.psd" "*.zip" "*.model". This creates a .gitattributes file. 3. Commit the .gitattributes and push. From now on, matching files are stored in LFS. Azure Repos supports Git LFS natively — no additional configuration needed. LFS storage counts against your Azure DevOps storage quota (free tier: included in the 2GB Artifacts limit). For very large datasets, consider Azure Blob Storage instead of LFS.

Q: What is the "reset votes on new push" policy and why should you enable it?

The "Reset code reviewer votes when there are new changes" policy invalidates all existing approvals on a PR whenever the author pushes new commits. Reviewers must re-review and re-approve. Why enable it: Without this policy, an author could get 2 approvals, then push completely different (potentially malicious or broken) code, and merge — the approvals from the old code would still satisfy the policy. This is a security and quality risk. With reset votes enabled, every code change after approval requires fresh review. Tradeoff: It adds review overhead — trivial typo fixes after approval require re-approval. Some teams use a lighter variant: "Reset votes on source branch changes" which only resets on commits (not on base branch updates). For security-critical branches, always enable full reset.

Scenario-Based

Q: A developer pushes a commit containing an API key to Azure Repos. The commit is already merged to main. What do you do?

Step 1 (Immediate — minutes): Rotate the secret. Generate a new API key in the service that issued it and revoke the old one. This is the #1 priority — removing the commit from history does NOT protect you because clones, forks, caches, and CI logs may already have it. Step 2: Remove from Git history using git filter-repo or BFG Repo Cleaner to purge the file/string from all commits and branches. Force-push the cleaned history to Azure Repos. Step 3: Notify all developers to re-clone the repo (their local history still has the secret). Step 4 (Prevention): Enable GitHub Advanced Security for Azure DevOps (GHAzDO) — it provides real-time secret scanning on push. Add a CredScan pipeline task for CI-time detection. Set up pre-commit hooks (e.g., detect-secrets) for local prevention. Store secrets in Azure Key Vault and reference them via pipeline variables — never in code.

Q: Your team has 6 developers, and PRs are taking 3+ days to get reviewed. How do you speed up the review process?

A multi-pronged approach: 1. Smaller PRs: Enforce a culture of small, focused PRs (under 400 lines changed). Large PRs are overwhelming and get deprioritized. Break features into incremental, mergeable chunks. 2. Auto-assign reviewers: Use the "Automatically include reviewers" branch policy with path-based rules so the right people are assigned instantly — no waiting for the author to tag someone. 3. Reduce required reviewers: Consider requiring 1 reviewer instead of 2 for non-critical branches. Use 2 reviewers only for main. 4. Review SLAs: Establish a team agreement — all PRs reviewed within 4 hours during business hours. Track review time in retrospectives. 5. PR notifications: Ensure Slack/Teams/email notifications are configured for PR assignments so reviewers are alerted immediately. 6. Pair programming: For complex changes, pair-program instead — the pair partner approves immediately after the session, since they co-authored the code.

Q: You're setting up Azure Repos for a team of 30 developers working on a monorepo with 15 microservices. How do you structure branch policies and permissions?

Branch policies on main: Require 2 reviewers, build validation (CI runs all affected services), linked work items, comment resolution, squash merge only. Enable "reset votes on new push." Path-based code owners: Use automatically included reviewers with path filters — /services/auth/** requires Auth Team approval, /services/payments/** requires Payments Team + Security Team, /infrastructure/** requires DevOps Team. Set these as "Required." Build validation: Configure the CI pipeline with path-based triggers so only affected services are built and tested on PR (saves time — don't rebuild all 15 services for a 1-service change). Permissions: Create Azure AD security groups per team. Grant each team "Contribute" on the repo but use branch-level permissions to restrict who can create release/* branches (only release managers). Remove "Bypass policies when pushing" from all non-admin groups. Branch naming: Enforce naming conventions with a branch policy or a server-side hook: feature/*, bugfix/*, hotfix/* only.

Q: A developer accidentally ran git push --force to main and overwrote the last 5 commits. How do you recover?

Immediate recovery: Azure Repos keeps a reflog of branch updates. Go to Repos → Pushes in the web UI — you'll see the force-push event and the previous commit SHA. Step 1: Find the last good commit SHA from the push history or from another developer's local clone who still has the original commits. Step 2: Force-push back to the correct state: git push --force origin <good-sha>:main. Step 3: Notify the team to git fetch and git reset --hard origin/main. Prevention: 1. Enable branch policies on main — with policies active, direct pushes (including force-push) are blocked. 2. Remove the "Force push (rewrite history, delete branches)" permission from all users and groups for protected branches. Go to Project Settings → Repositories → Security → [branch] and set "Force push" to "Deny" for Contributors. 3. Use --force-with-lease instead of --force as a team-wide Git alias — it refuses to overwrite others' work.

Q: Your organization is migrating from GitHub to Azure Repos. Developers are worried about losing their workflow. How do you plan the migration?

Phase 1 — Assessment: Inventory all GitHub repos, branch protection rules, GitHub Actions workflows, and integrations. Map GitHub concepts to Azure DevOps: branch protection → branch policies, CODEOWNERS → auto-included reviewers, Actions → Pipelines, Issues → Boards. Phase 2 — Import repos: Use Azure DevOps' built-in "Import repository" feature (Repos → Import) — it imports the full Git history, all branches, and tags from any Git URL. For large repos, use git clone --mirror then git push --mirror. Phase 3 — Re-create policies: Set up branch policies on main mirroring GitHub's branch protection. Configure auto-included reviewers to replace CODEOWNERS. Phase 4 — Developer training: The Git workflow is identical — developers still use git clone, git push, branches, and PRs. The biggest change is the web UI (PR reviews in Azure DevOps instead of GitHub). Run a 30-minute walkthrough session. Phase 5 — Parallel run (2 weeks): Keep both systems active, freeze GitHub repos as read-only, and have all new work go to Azure Repos. Cut over fully after the parallel period.

🌍 Real-World Use Case

A FinTech Startup Taming Branch Chaos

Consider a FinTech startup with 12 developers, 4 microservices, and a history of production outages caused by untested code reaching production.

The problem:

What they implemented in Azure Repos:

Results after 3 months:

💡
Key Takeaway

Branch policies are not bureaucracy — they're automated quality gates that protect the team from itself. The most common regret teams have is "we should have set up branch policies on day one." It takes 10 minutes to configure and saves hundreds of hours of debugging production issues caused by unreviewed code.

📝 Summary

ConceptKey Takeaway
Azure ReposEnterprise Git hosting in Azure DevOps with unlimited private repos, code search, PRs, and branch policies. Also supports TFVC (legacy).
Git vs. TFVCAlways choose Git for new projects. TFVC is legacy, centralized, and receives no new features.
Branch PoliciesRules on protected branches: minimum reviewers, build validation, linked work items, comment resolution, merge type restrictions.
Pull RequestsThe core collaboration workflow: create branch → push → open PR → review → build validation → merge → delete branch.
Merge StrategiesSquash (recommended default, clean history), Merge Commit (full history), Rebase (linear, individual commits), Semi-Linear (rebase + merge).
Git FlowStructured: main + develop + feature/release/hotfix branches. Best for scheduled releases and versioned products.
Trunk-Based DevSimple: main + short-lived features + feature flags. Best for continuous deployment, microservices, and web services.
Build ValidationThe single most impactful policy — CI pipeline must pass on the PR before merge. Catches 90% of integration issues.
AuthenticationUse PATs for HTTPS Git access. Use SSH keys as an alternative. Never use your Microsoft account password for Git.
Force Push ProtectionRemove "Force push" permission from Contributors on protected branches. Use --force-with-lease instead of --force as a safety net.
← Back to Azure DevOps Course