1. Define who reviews each change
In a fictional project, a payments application receives a timeout change close to the production window. Ana opened the PR, Bruno added the latest commit and an approval appears on screen. Before concluding that review is complete, identify who approved, which diff was available and which policy conditions apply. In Azure Repos, allowing the creator’s vote and excluding the latest pusher are separate decisions. In the example, Ana can review Bruno’s change if other conditions permit. The PM should explain the control without depending on an indicator’s color. Record its objective: technical competence, separation of responsibilities or review of the new iteration. An approval count chosen without that objective can delay work without improving the decision. Also define who responds when the usual reviewer is unavailable during an out-of-hours intervention.
2. Connect review to the source and validation to the base
The team needs to retain an objection about timeouts while removing approvals when new code arrives. Reset all approval votes retains reject or wait votes; Reset all code reviewer votes removes those too. Choose according to the intended behavior, rather than similar names. There is another axis: the target branch can change without any source push. In the exercise, the adapter passed at 10:00, but another team changed the interface at 10:08. To require immediate revalidation, configure the corresponding expiration on base changes. Discuss the cost of additional runs and the criticality of the changed contract. The earlier result still documents what was exercised, but the current decision needs evidence appropriate to the current combination. In change reporting, distinguish code updates, human review and test execution. Assign an owner for investigating repeated validation failures instead of treating every rerun as routine delay.
3. Identify the control producing the result
A check named validate can represent application tests or only documentation validation. If two workflows use that name, the person assessing the PR needs to resolve ambiguity before changing requirements. Use distinct job names and update the required-check selection. In a second problem, two integrations can publish risk-scan, but only RiskGate decides the risk control. Selecting the expected app can restrict the accepted source. These problems require different questions: which activity ran and who published the decision? In handover, retain the control name, assessed reference and owner of its producing service. A platform-accepted check also does not by itself establish that every functional exercise needed by the project exists. Coverage needs discussion with people who understand application behavior. For a batch interface, include a concrete example of what the check would catch and what still needs a separate rehearsal.
4. Make ownership readable and applicable
The CODEOWNERS file used for a PR belongs to its base branch. If the proposal introduces that file only on the source, do not assume it already governs reviewer selection for that PR. Review the target configuration. The last match takes precedence; put two teams on the same line to associate both with one pattern. Requesting review and requiring approval are related but different configurations. The exercise uses payments paths and fictional teams whose access has already been confirmed. Ask the learner to draw a small matrix: changed path, applicable rule, responsible team and approval requirement. Add a pipeline or infrastructure example to expose gaps outside application code. This matrix helps the PM identify specialist dependencies, availability and points where two teams each believe the other is responsible. Exercise the rule on a representative PR before treating it as operational.
5. Assess the combination that will be merged
Two PRs can pass separately and fail when used together. In the practical case, one changes an event producer and another changes its consumer. A merge queue creates a combination to validate with the base and earlier queued changes. With GitHub Actions, the workflow needs to respond to merge_group; an isolated PR result does not replace that run. When a check is missing, first look for execution against the correct reference. Distinguish no execution, a runner infrastructure failure and a functional test failure. The operational response differs in each case. During a short window, assign responsibility for fixing the trigger and a time limit for deciding on deferral. Publishing manual success based on another result would hide the gap. Explain to the business which risk still lacks evidence and when a new informed decision can be made.
6. Read the graph before reverting a merge
A merge has more than one parent. In git revert -m, the number identifies the parent used as reference, not a person or branch to blame. In the laboratory, the first parent is main before integration; this relationship is checked before reverting. Then observe an important difference: the added file disappears, but the feature commit remains an ancestor of main. Adding only documentation to the feature and merging again does not automatically restore the removed file. The exercise lets you observe content and graph side by side. Any reintroduction should be an explicit decision about the intended set of changes. The laboratory reverses the inverse change in a controlled graph; this is not a universal production recipe. Actual recovery may need to address data, previously emitted messages and external configuration that Git does not represent.
7. Recover a candidate without changing the shared reference
In the second exercise, an experimental commit is no longer on a branch after switching back to main. The local HEAD reflog still identifies it and the object exists. Creating a rescue branch at that identifier preserves a path for inspecting the candidate without moving main. The reflog belongs to the local repository and can expire; do not treat it as a universal backup. Confirm content before promoting it through normal review. The laboratory checks that rescue points to the candidate and main retains its earlier value. For team work, record how the commit was identified and what remains to be validated. The observed identifier proves that an object was found, not that the object resolves the incident. If the lead has disappeared, investigation depends on the other copies and retention policies available in the actual context.
8. Close the loop with a rehearsed backport
A fix on main may be needed on a maintenance version with a different parent and different dependencies. A conflict-free cherry-pick does not establish functional compatibility. In the laboratory, -x adds an origin reference in the conflict-free case; the new commit has a different identity. Inspect the resulting diff and define tests appropriate to the target version. If another exercise encounters a conflict, --abort can cancel the sequence; do not choose a resolution merely to remove markers. The code below creates a temporary repository without remotes, performs eighteen checks and removes that repository on exit. Python and Git must be installed. Before running it, predict which files exist after each integration. Then compare predictions with the checks. Finish by writing a short handover: source reference, applied reference, compatibility evidence and recovery limitations. This record should be understandable to whoever takes over the next support shift.
# Original local Git exercise. Creates only a temporary repository; no remotes.
import os
from pathlib import Path
import subprocess
import tempfile
def run_lab:
env = {k: v for k, v in os.environ.items if not k.startswith('GIT_')}
env.update(GIT_CONFIG_NOSYSTEM='1', GIT_CONFIG_GLOBAL=os.devnull,
GIT_TERMINAL_PROMPT='0', GIT_EDITOR='true', LC_ALL='C')
with tempfile.TemporaryDirectory(prefix='dr-az400-git-') as folder:
root = Path(folder)
def git(*args):
return subprocess.run(
['git', '-c', 'core.hooksPath=' + os.devnull,
'-c', 'commit.gpgSign=false', *args],
cwd=root, env=env, check=True, text=True,
capture_output=True, timeout=15).stdout.strip
def commit_file(name, text, message):
(root / name).write_text(text)
git('add', '--', name)
git('commit', '-m', message)
return git('rev-parse', 'HEAD')
git('init', '--initial-branch=main', '--template=')
git('config', 'user.name', 'DR local exercise')
git('config', 'user.email', 'exercise@example.invalid')
base = commit_file('service.txt', 'version=1\n', 'Initial service')
git('switch', '-c', 'feature')
feature = commit_file('rules.csv', 'limit,100\n', 'Add fictional rule')
git('switch', 'main')
before_merge = commit_file('audit.txt', 'audit=enabled\n', 'Add audit setting')
git('merge', '--no-ff', 'feature', '-m', 'Integrate fictional rule')
merge = git('rev-parse', 'HEAD')
assert git('show', '-s', '--format=%P', merge).split == [before_merge, feature]
assert (root / 'rules.csv').read_text == 'limit,100\n'
git('revert', '--no-edit', '-m', '1', merge)
reversal = git('rev-parse', 'HEAD')
assert not (root / 'rules.csv').exists
assert (root / 'audit.txt').read_text == 'audit=enabled\n'
assert git('merge-base', '--is-ancestor', feature, 'main') == ''
git('switch', 'feature')
commit_file('README.txt', 'Rule documentation updated\n', 'Document the rule')
git('switch', 'main')
git('merge', '--no-ff', 'feature', '-m', 'Integrate later documentation')
assert (root / 'README.txt').read_text == 'Rule documentation updated\n'
assert not (root / 'rules.csv').exists
# Only for this controlled graph: restore the entire reverted change.
# A production recovery needs review of data effects and compatibility.
git('revert', '--no-edit', reversal)
assert (root / 'rules.csv').read_text == 'limit,100\n'
assert (root / 'README.txt').read_text == 'Rule documentation updated\n'
stable = git('rev-parse', 'main')
git('switch', '--detach', 'main')
candidate = commit_file('candidate.txt', 'inspect before adoption\n', 'Recovery candidate')
git('switch', 'main')
assert candidate in git('reflog', 'show', '--format=%H', 'HEAD').splitlines
git('branch', 'rescue', candidate)
assert git('rev-parse', 'rescue') == candidate
assert git('rev-parse', 'main') == stable
assert git('show', 'rescue:candidate.txt') == 'inspect before adoption'
# Backport between divergent local branches; no conflict in this fixture.
git('switch', '-c', 'maintenance', base)
git('cherry-pick', '-x', feature)
picked = git('rev-parse', 'HEAD')
assert picked!= feature
assert feature in git('show', '-s', '--format=%B', picked)
assert (root / 'rules.csv').read_text == 'limit,100\n'
assert git('status', '--porcelain') == ''
assert git('remote') == ''
print('18 local Git assertions passed; temporary repository removed on exit')
if __name__ == '__main__':
run_lab
The laboratory merges rules.csv, reverts the merge and merges only later documentation. The file stays absent until explicit reintroduction. It then recovers a commit through a new branch and rehearses a backport.
Common pitfalls
Counting ineligible votes; accepting validation against an earlier base; confusing check names; waiting for an unconfigured event; assuming revert erases ancestry; treating reflog as backup; confusing conflict-free backport with compatibility.
Related topics: Artifact provenance · Release approvals · Incident response · Contract testing
Integration depends on evidence about the final change. Recovery depends on understanding the graph, retaining useful references and assessing effects outside Git.
Reference: Set and manage branch policies · AZ-400 objectives 2026-07-27