Skip to main content

Git & Azure DevOps Guide

Companion guide for the data team working with Power BI (PBIP format) and Azure DevOps. This document does not repeat general Git fundamentals β€” refer to the Git & GitHub Training Material for those. Everything here is specific to our tools, branching model, naming conventions, and review process.


Table of Contents​

  1. How This Differs from the Developer Guide
  2. Our Tools & Environment
  3. Understanding PBIP Format
  4. Getting PBIP Changes into the Repo
  5. Branching Strategy
  6. Naming Conventions
  7. Ticket Workflow β€” From Request to Production
  8. Daily Workflow β€” Step by Step
  9. Keeping Your Branch Up to Date
  10. Pull Requests in Azure DevOps
  11. Branch Policies & Approval Rules
  12. Code Review Expectations
  13. Common Mistakes & Fixes (Data Team Edition)
  14. Quick Reference

1. How This Differs from the Developer Guide​

The main training material covers Git with GitHub for a programming team. Our world is different in a few important ways:

  • We use Azure DevOps (Azure Repos + Azure Boards), not GitHub. The Git commands are identical, but the web interface, pull request workflow, and work item linking are different.
  • Our "code" is Power BI projects saved in PBIP format β€” folders of JSON and TMDL files, not traditional source code. Merge conflicts look different, and some files must be ignored.
  • Most tickets are small visual changes (fix a title, move a table, add a slicer) that take 1–2 minutes. Our branching model accounts for this β€” we use one commit per ticket, not one branch per ticket.
  • Code review focuses on DAX measures and data model changes, not visual layout. The client handles the visual side; we ensure the logic is sound. DAX standards are covered in a separate DAX & Semantic Model Standards document.

2. Our Tools & Environment​

ToolPurpose
Power BI DesktopAuthor reports, save as PBIP
VS Code (recommended)View diffs, stage commits, manage branches, resolve conflicts
Git CLI (Git Bash / terminal)Alternative to VS Code for Git operations
Azure DevOpsRemote repository, pull requests, work items (Azure Boards)

Our Azure DevOps project: https://dev.azure.com/srg-dev/SRG%20Power%20BI

Clone URL (SSH or HTTPS) β€” find it in Azure DevOps: Repos β†’ Files β†’ Clone (top right button) β†’ copy the URL.

Tip for beginners: VS Code has a built-in Git panel (the Source Control icon in the left sidebar). It lets you stage, commit, push, pull, and resolve conflicts without touching the command line. If you are new to Git, start here.


3. Understanding PBIP Format​

When you save a Power BI file as a .pbip (Power BI Project), Power BI Desktop breaks the monolithic .pbix binary into a folder of human-readable text files:

MyReport/
β”œβ”€β”€ MyReport.Report/
β”‚ β”œβ”€β”€ report.json ← page layouts, visuals, filters
β”‚ β”œβ”€β”€ definition.pbir ← report pointer
β”‚ └── ...
β”œβ”€β”€ MyReport.SemanticModel/
β”‚ β”œβ”€β”€ definition/
β”‚ β”‚ β”œβ”€β”€ tables/ ← one file per table (TMDL)
β”‚ β”‚ β”œβ”€β”€ relationships.tmdl
β”‚ β”‚ └── ...
β”‚ β”œβ”€β”€ definition.pbism ← model pointer
β”‚ └── ...
β”œβ”€β”€ .gitignore ← auto-generated by PBI Desktop
└── MyReport.pbip ← project pointer file

Why this matters for Git:

  • Each measure, table, and relationship is stored as a separate text file. Git can diff and track changes at the individual object level.
  • When you change a single measure, only that measure's file shows up as modified β€” reviewers see exactly what changed.
  • The report.json file changes frequently and can be large. Visual-only changes (moving a chart, resizing a table) show up here.

Important: Always open the .pbip file in Power BI Desktop going forward β€” not a .pbix. The PBIP is your working format now.


4. Getting PBIP Changes into the Repo​

How it works:

  1. Clone the repo to your machine
  2. Open the .pbip file in Power BI Desktop
  3. Make changes, save (Ctrl+S) β€” Power BI updates the text files on disk
  4. Switch to VS Code β†’ review diffs β†’ stage β†’ commit β†’ push

5. Branching Strategy​

We use a three-tier branching model tailored to how data development actually works.

main
β”œβ”€β”€ feature/asset-management/dev ← full report build
β”‚ β”œβ”€β”€ feature/asset-management/batch-1 ← tickets 101–108
β”‚ β”œβ”€β”€ feature/asset-management/batch-2 ← tickets 109–115
β”‚ └── ...
β”‚
└── hotfix/191-fix-sales-total ← urgent fix, straight to main

Tier 1 β€” main​

  • Always matches what is deployed to the production Power BI environment.
  • No one pushes directly to main. All changes arrive via pull request.

Tier 2 β€” Feature branches (feature/report-name)​

  • Created when building a new report or major feature addition.
  • Lives until the entire report is complete and reviewed, then merges into main via PR.
  • Example: feature/asset-management, feature/director-dashboard

Tier 3 β€” Batch branches (feature/report-name/batch-N)​

  • Created off the feature branch to group a set of related tickets.
  • Each ticket gets its own commit (not its own branch) with the work item ID in the message.
  • When the batch is done, open a PR from the batch branch into the feature branch.
  • This is where code review happens for new DAX measures.

Hotfix branches (hotfix/ID-description)​

  • Created directly off main for urgent fixes.
  • Merged back into main via PR.
  • For a cluster of small urgent fixes: hotfix/sales-q1-fixes with a commit per ticket.

When to use which​

SituationBranch type
Building a brand new report from scratchfeature/report-name β†’ batch branches
Adding a major new section to an existing reportfeature/report-name β†’ batch branches
Fixing a typo, broken visual, or wrong total in productionhotfix/ID-description
15 small production fixes in one dayhotfix/descriptive-name with one commit per fix

6. Naming Conventions​

Branch names​

TypePatternExample
Feature (report)feature/report-namefeature/asset-management
Batchfeature/report-name/batch-Nfeature/asset-management/batch-1
Hotfixhotfix/ID-descriptionhotfix/191-fix-sales-total

Rules:

  • All lowercase
  • Use hyphens to separate words (not underscores or spaces)
  • Include the work item ID when it is a single-ticket hotfix

Commit messages​

Every commit must reference the Azure Boards work item ID so the change is traceable.

[#101] Fix Move-Ins title wording on page 3
[#102] Add Property Type slicer to overview page
[#103] Correct occupancy rate calculation in DAX measure

Format: [#work-item-ID] Short present-tense description

Rules:

  • Use # only in the commit message β€” never in a branch name
  • Present tense: "Fix" not "Fixed", "Add" not "Added"
  • Keep the subject line under 72 characters
  • Reference the work item ID so Azure DevOps auto-links the commit

Pull request titles​

PR typeTitle formatExample
Batch β†’ Feature branchBatch N: Short summaryBatch 1: Occupancy measures and slicers
Feature β†’ mainFeature: Report NameFeature: Asset Management
Hotfix β†’ mainHotfix: Short descriptionHotfix: Fix sales total calculation

7. Ticket Workflow β€” From Request to Production​

This section explains how work moves from a request to a finished, deployed change. If you are new to the team, read this first β€” it is the context for everything else in this document.

Work item hierarchy in Azure Boards​

We use three work item types in Azure Boards:

Work Item TypeWhat it representsExample
EpicA full report or major deliverableAsset Management, Director Dashboard, Sales Analytics
TaskAn individual piece of work under an EpicAdd Property Type slicer to overview page, Create YTD Move-Ins measure
IssueA bug or problem that needs fixingMove-Ins total not tying out on page 3, Slicer not filtering correctly

Every Task and Issue lives under an Epic. The Epic is the report.

Where tickets come from​

Tickets arrive from two sources:

Client-created tickets: The client has direct access to Azure Boards and creates Tasks or Issues themselves. These typically have a title and a description β€” often short and to the point (e.g., "Move-Ins isn't tying out in this visual" or "Add a slicer for Property Type"). The client does not fill in technical details, priority, or effort estimates β€” that is our responsibility.

Internally-created tickets: When we are building a new report from scratch, working on internal documentation, or managing database changes, the data team writes their own tickets. These should follow the same structure as client tickets but will generally include more technical detail since we are writing them for ourselves.

What to do when you receive a client ticket​

When a new Task or Issue appears under your Epic:

  1. Read the title and description β€” understand what the client is asking for
  2. Fill in the missing fields:
    • Assigned To β€” assign it to yourself (since each report is generally owned by one developer)
    • State β€” move it from New to Active when you start working on it
    • Priority β€” set based on urgency and impact (coordinate with your lead if unclear)
    • Description (add detail if needed) β€” if the client's description is vague, add a comment clarifying what you understood and what you plan to do. This creates a paper trail if questions come up later
  3. Link it to the correct Epic β€” if the client didn't do this, drag it under the right Epic on the board or set the parent link manually

How tickets map to Git​

This is where the ticket process connects to the branching strategy from Section 6:

Epic: Asset Management
β”‚
β”œβ”€β”€ Task #101: Add Property Type slicer ──→ commit [#101] on batch branch
β”œβ”€β”€ Task #102: Create YTD Move-Ins measure ──→ commit [#102] on batch branch
β”œβ”€β”€ Issue #103: Move-Ins total not tying out ──→ commit [#103] on batch branch
β”‚ ... (batch branch merges into feature branch via PR)
β”‚
└── Issue #191: Sales total wrong on page 1 ──→ commit [#191] on hotfix branch
... (hotfix branch merges into main via PR)

For new report development (Epic with many Tasks):

  • The Epic maps to a feature/report-name branch
  • Groups of Tasks become batch branches (feature/report-name/batch-N)
  • Each Task or Issue is a single commit with the work item ID in the message

For production fixes (Issues against a live report):

  • Individual Issues become hotfix/ID-description branches off main
  • Or group several small fixes into hotfix/descriptive-name with a commit per Issue

Ticket lifecycle​

A ticket moves through these states:

StateWhat it meansWho moves it
NewClient or team member just created itCreator
ActiveA developer is working on itDeveloper (when they start)
ResolvedThe work is done and merged to the target branchDeveloper (after PR is merged)
ClosedVerified in production or confirmed by clientDeveloper or lead

Move the ticket to Resolved when the PR containing its commit is merged. Move it to Closed once the change is confirmed working in the production environment.

Writing good tickets (for internally-created work)​

When you create your own tickets for internal work, include enough context that someone else on the team could pick it up if needed:

βœ… Title: Create fiscal year date table for Asset Management model
Description: The current calendar table uses calendar year. Asset Management
reporting requires fiscal year starting April 1. Add a FiscalYear, FiscalQuarter,
and FiscalMonth column to the date table. Mark as the model's date table.

βœ… Title: Refactor occupancy measures to use REMOVEFILTERS
Description: Current occupancy measures use ALL() to clear filters on the
denominator. Switch to REMOVEFILTERS for clarity and consistency with our
DAX standards. Affects: Occupancy Rate %, Vacant Units %, Net Absorption.

❌ Title: Fix measures
Description: (empty)

❌ Title: Update stuff
Description: Various changes needed

8. Daily Workflow β€” Step by Step​

This is the loop you will repeat every day. Instructions are shown for both VS Code and the command line.

Starting your day β€” pull the latest changes​

VS Code: Open Source Control (Ctrl+Shift+G) β†’ click ... menu β†’ Pull

Command line:

# Switch to your working branch
git checkout feature/asset-management/batch-1

# Pull the latest from the remote
git pull origin feature/asset-management/batch-1

Making changes​

  1. Open the .pbip file in Power BI Desktop
  2. Make your changes (fix a visual, add a measure, etc.)
  3. Save in Power BI Desktop (Ctrl+S) β€” this writes the changes to the text files on disk
  4. Close Power BI Desktop or at minimum save before switching to VS Code β€” PBI holds file locks

Reviewing and committing​

VS Code:

  1. Open the project folder in VS Code
  2. Click the Source Control icon (left sidebar) β€” you'll see changed files listed
  3. Click on a file to see the diff (what changed)
  4. Review the changes β€” make sure nothing unexpected is there
  5. Stage the files: click the + icon next to each file (or + next to "Changes" to stage all)
  6. Type your commit message in the text box: [#101] Fix Move-Ins title wording
  7. Click the checkmark βœ“ to commit

Command line:

# See what changed
git status

# Review the actual changes (optional but recommended)
git diff

# Stage all changes
git add .

# Commit with work item reference
git commit -m "[#101] Fix Move-Ins title wording on page 3"

Pushing to Azure DevOps​

VS Code: Click ... menu in Source Control β†’ Push (or click the sync icon in the status bar)

Command line:

git push origin feature/asset-management/batch-1

Multiple tickets in one session​

When knocking out many small tickets in a row:

# Make changes for ticket 101, save in PBI Desktop
git add .
git commit -m "[#101] Fix Move-Ins title wording"

# Make changes for ticket 102, save in PBI Desktop
git add .
git commit -m "[#102] Add Property Type slicer"

# Make changes for ticket 103, save in PBI Desktop
git add .
git commit -m "[#103] Move summary table below chart on page 2"

# Push all commits at once
git push

Each ticket gets its own commit, but you only push once when you are ready.


9. Keeping Your Branch Up to Date​

When multiple people are working, main or the parent feature branch may get updated while you are on your batch branch. You need to pull those changes in regularly to avoid large merge conflicts later.

Syncing your batch branch with the feature branch​

# Make sure your work is committed first
git status # should say "nothing to commit"

# Fetch the latest from the remote
git fetch origin

# Merge the parent feature branch into your batch branch
git merge origin/feature/asset-management

If there are conflicts, VS Code will highlight them. For PBIP files:

  • report.json conflicts are common and usually safe to resolve by accepting the incoming version (the visual side is the client's domain)
  • TMDL/measure conflicts need careful manual review β€” both versions may have valid logic changes

Syncing the feature branch with main (before final PR)​

git checkout feature/asset-management
git pull origin feature/asset-management
git merge origin/main
# Resolve any conflicts, commit, push

10. Pull Requests in Azure DevOps​

A pull request (PR) is how changes move between branches. Here is the step-by-step process in Azure DevOps.

Creating a PR from the web portal​

  1. Go to Azure DevOps β†’ https://dev.azure.com/srg-dev/SRG%20Power%20BI
  2. Navigate to Repos β†’ Pull Requests
  3. Click New Pull Request
  4. Set the source branch (your branch, e.g. feature/asset-management/batch-1)
  5. Set the target branch (where it's merging into, e.g. feature/asset-management)
  6. Fill in the Title following the naming convention (e.g. Batch 1: Occupancy measures and slicers)
  7. Write a Description β€” summarize what changed and why:
    • List the work item IDs addressed
    • Call out any new DAX measures that need review
    • Note any known issues or things to watch for
  8. Link work items: In the "Work items to link" section, search for and link all relevant work items
  9. Add reviewers: Add at least one team member. For PRs containing new measures, this is required.
  10. Click Create

Creating a PR from VS Code​

  1. Install the Azure Repos extension from the VS Code marketplace
  2. Open the command palette (Ctrl+Shift+P) β†’ search "Azure Repos: Create Pull Request"
  3. Select source branch, target branch, fill in title and description
  4. The extension creates the PR and gives you a link to view it in the browser

PR description template​

Use this structure for consistency:

## Summary
Brief description of what this batch/hotfix accomplishes.

## Work Items
- [#101] Fix Move-Ins title wording
- [#102] Add Property Type slicer
- [#103] Correct occupancy rate measure

## New or Modified DAX Measures
- `Occupancy Rate` β€” new measure, calculates occupied units / total units
- `YTD Move-Ins` β€” modified, added fiscal year filter

## Testing Notes
- Verified totals match source data for Jan–Mar 2026
- Tested slicer interactions on all pages

## Screenshots (if applicable)
Attach before/after screenshots for visual changes.

Completing (merging) a PR​

  1. Once approved, click Complete on the PR page
  2. Merge type: select Merge (no fast-forward) β€” this preserves individual commit history
  3. Optionally check "Delete source branch after merging" (recommended for batch branches, not for feature branches until the full report is done)
  4. Click Complete merge

11. Branch Policies & Approval Rules​

Configure these in Azure DevOps: Repos β†’ Branches β†’ click ... next to the branch β†’ Branch policies.

Protections for main​

PolicySetting
Require a pull request before mergingβœ… Enabled
Minimum number of reviewers1 (for PRs with DAX/model changes)
Allow requestors to approve their own changes❌ Disabled
Check for linked work itemsβœ… Required
Check for comment resolutionβœ… Required
Allow bypass with reasonβœ… Enabled β€” for typo fixes and emergency hotfixes

Bypass policy for emergencies​

Sometimes a typo fix or critical hotfix needs to go through immediately without waiting for a reviewer. Azure DevOps allows "bypass with reason" β€” the person completing the PR must type a justification. This is logged and auditable.

When bypass is acceptable:

  • Cosmetic-only changes (typo in a title, label correction) with no measure or model changes
  • Emergency hotfix during off-hours when no reviewer is available

Future: Automated review with Claude agent​

We plan to add an automated review step where a Claude agent scans PRs for DAX best practices. This will run as part of the PR validation and flag issues like:

  • Measures missing descriptions
  • Use of FILTER on fact tables where CALCULATE would be more efficient
  • Inconsistent naming conventions
  • Missing error handling (IFERROR / DIVIDE with alternate result)

12. Code Review Expectations​

Note: Our DAX naming conventions, measure documentation requirements, display folder standards, and formatting rules are covered in the separate DAX & Semantic Model Standards document. Reviewers should be familiar with that document before reviewing PRs that contain measure changes.

What reviewers should check​

Always review (new or modified DAX measures):

  • Does the measure have a description?
  • Is the DAX logic correct? Does it handle edge cases (nulls, empty tables, division by zero)?
  • Is DIVIDE used instead of / for division (to avoid divide-by-zero errors)?
  • Are CALCULATE filter contexts correct? Are there unintended filter propagation issues?
  • Are variable names clear and descriptive?
  • Does the measure follow our naming conventions?
  • Are inline comments present for non-trivial logic?

Skim or skip (visual/layout changes):

  • Visual position changes in report.json β€” these are the client's domain
  • Formatting changes to existing visuals (colors, fonts, sizes)

How to leave good review comments​

  • Ask questions: "What happens to this measure when Property Type is filtered to null?"
  • Suggest alternatives: "Could this use CALCULATE + REMOVEFILTERS instead of FILTER on the fact table?"
  • Approve when the logic is correct, even if you would have written it differently

Responding to review feedback​

  • Address all comments β€” either fix the code or explain why you disagree
  • Reply "Fixed in [commit hash]" when done
  • Push the fixes as a new commit (don't amend β€” the reviewer needs to see what changed)

13. Common Mistakes & Fixes (Data Team Edition)​

"I see changes but I didn't edit anything"​

This happens when you open the PBIP and Power BI Desktop updates cache files or internal metadata. Check your .gitignore β€” the .pbi/ folder and *.pbip.user should be listed. If they are already ignored and you still see phantom changes, look for auto-generated timestamp fields in report.json and consider ignoring those specific patterns.

# Discard all unstaged changes (when you know they are just PBI noise)
git checkout -- .
# Modern syntax
git restore .

"I committed to the wrong branch"​

# Move the last commit to the correct branch
git branch correct-branch-name # create a branch at current commit
git reset HEAD~1 --soft # undo commit on current branch (keep changes)
git stash # stash the changes
git checkout correct-branch-name # switch to correct branch
git stash pop # restore changes
git commit -m "[#101] Your message" # recommit on the right branch

"report.json has a merge conflict"​

This is common because report.json is a large file that changes with any visual edit. If the conflict is only in visual/layout sections (not measure references or data bindings):

# Accept the incoming version (the other branch's changes)
git checkout --theirs MyReport.Report/report.json
git add MyReport.Report/report.json
git commit -m "merge: accept incoming visual layout changes"

If the conflict involves data bindings or measure references, open both versions side by side in VS Code and merge carefully.

"I need to undo my last push"​

# DON'T use git reset --force on shared branches
# Instead, create a new commit that reverses the change
git revert HEAD
git push

"Power BI says the file is in use"​

Close Power BI Desktop before running Git operations that modify files on disk (pull, merge, checkout). PBI holds file locks that prevent Git from updating files.


14. Quick Reference​

Commands you'll use daily​

TaskCommand
Check what changedgit status
See the actual diffgit diff
Stage all changesgit add .
Commit with work item linkgit commit -m "[#101] Description"
Push to Azure DevOpsgit push
Pull latest changesgit pull
Create and switch to a new branchgit checkout -b feature/report-name/batch-1
Switch to an existing branchgit checkout feature/report-name
Merge parent branch into yoursgit merge origin/feature/report-name
Discard all uncommitted changesgit restore .
Stash work in progressgit stash
Restore stashed workgit stash pop
View commit historygit log --oneline

Azure DevOps URLs​

PageURL
Project homehttps://dev.azure.com/srg-dev/SRG%20Power%20BI
Repos (files & branches)https://dev.azure.com/srg-dev/SRG%20Power%20BI/_git
Pull requestshttps://dev.azure.com/srg-dev/SRG%20Power%20BI/_git/pullrequests
Boards (work items)https://dev.azure.com/srg-dev/SRG%20Power%20BI/_boards

Key principles​

  1. Save in PBI Desktop, then commit in VS Code β€” this is the rhythm
  2. One commit per ticket β€” reference the work item ID every time
  3. Never push directly to main β€” always use a pull request
  4. Follow the DAX & Semantic Model Standards β€” see the separate document for measure naming, descriptions, and formatting
  5. Close PBI Desktop before pulling or merging β€” avoid file lock conflicts
  6. When in doubt, git status β€” it tells you exactly where you are