Automatically getting API difference diagrams in your .NET PRs

A while back I built a tool that could analyze .NET Source Files as well as assemblies, and generate an Object Model Diagram. I found having OMDs of the APIs you're working on is a really great way to quickly get an overview of what an API looks like and how you could work with it. The public API surface should generally not change once you ship it, so getting it right the first time is important. In addition it was able to also compare two assemblies or two sets of source folders and just give you the difference. This allowed you to review just what is getting added, and if there are any breaking changes that might not have been intended. The end goal was to one day have these OMDs in the PR itself. You can get this tool today as a dotnet tool by running the following command:

   dotnet tool install --global dotMorten.OmdGenerator

For a long time, I’d wanted one specific feature in my .NET Object Model Diagram Generator: the ability to compare API surface directly between git refs. This would simplify comparing against a PR.

As mentioned, the tool could already compare one set of source files against another, and it could generate a clean object model diff from that. But what I really wanted was something more natural for real-world development: compare the code in my working tree to a tag, compare one branch to another, or compare two commits from a remote repository without having to manually check out folders, create temp copies, or script around the tool.

It was one of those ideas that sat on my “I should really add this someday” list for years.

This time, I finally built it — mostly because with AI agents now it's so much faster to iterate on without having to do all the manual work of actually typing :-)  So with GitHub Copilot helping me work through the design, implementation, tests, and documentation, I was able to add the feature much faster than I would have otherwise. Copilot didn’t magically do the work for me, but it absolutely helped me turn a long-delayed idea into a finished feature.

What the new feature does

The generator can now compare source code against a specific commit, branch, or tag.

That means you can now:

  • compare your current checkout against a release tag
  • compare two branches
  • compare two commits
  • compare two refs from a remote git repository

The key new arguments are:

  • gitRepo — a local repository path or remote git URL
  • sourceRef — the git ref for the “new” side of the diff
  • compareRef — the git ref for the “old” side of the diff
  • source — the source path to analyze

The important detail is that source still tells the tool what part of the repository to analyze. The git ref options tell it which versions of that source to compare.

Comparing API changes between two refs

Here’s the simplest case: compare your current source tree against a tag from the same repository.

generateomd --source=C:\github\dotnet\runtime\src\libraries\System.Text.Json\src --compareRef=v8.0.0 --format=html

That says:

  • analyze the current contents of System.Text.Json
  • compare them against the v8.0.0 tag
  • emit html output (you can use md if you want markdown format)

If you want to compare two refs from a remote repository, you can do that too:

generateomd --source=src/libraries/System.Text.Json/src --gitRepo=https://github.com/dotnet/runtime.git --sourceRef=main --compareRef=v8.0.0 --format=md

In this case:

  • gitRepo points to the remote repo
  • source is now a repo-relative path
  • sourceRef is the “new” side
  • compareRef is the baseline

You can also compare two commits directly:

generateomd --source=src/MyLibrary --gitRepo=https://github.com/your-org/your-repo.git --sourceRef=9f4f4cf --compareRef=28630aaf8d27367248358ef064b57d6214b0f103 --format=md

That gives you a markdown API diff suitable for release notes, PR discussions, or review workflows.

Why this is useful

This ends up being much more practical than folder-to-folder comparisons.

Instead of manually exporting source trees or checking out branches into separate directories, you can compare API
shape straight from version control. That makes it far easier to answer questions like:

  • What public API changed in this pull request?
  • What changed between this release tag and main?
  • Did this commit actually alter the public surface area?
  • Are these changes additive, breaking, or just refactorings?

For a tool built around API visualization and change tracking, git-based comparison feels like the missing piece.

Using it in GitHub Actions

Once the tool can compare two git refs, the next obvious step is automation.

A really nice workflow is to generate an API diff for every pull request and post it as a PR comment. That gives reviewers a focused view of API changes without having to inspect every file manually.

The flow looks like this:

  1. trigger on pull_request
  2. compare the PR head SHA to the PR base SHA
  3. generate markdown output
  4. detect whether the markdown actually contains API changes
  5. create or update a PR comment with the result

At a high level, the command looks like this inside the workflow:

generateomd \
--source=src/MyLibrary \
--gitRepo=${{ github.server_url }}/${{ github.repository }} \
--sourceRef=${{ github.event.pull_request.head.sha }} \
--compareRef=${{ github.event.pull_request.base.sha }} \
--format=md \
--output=api-diff

This is a great fit for pull requests because GitHub already gives you the exact two SHAs you care about:

  • github.event.pull_request.head.sha
  • github.event.pull_request.base.sha

That means the comment always reflects the actual API delta introduced by the PR.

The important GitHub Action pieces

Here are the parts of the workflow that matter most.

 

1. Install the tool

- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- run: dotnet tool install --global dotMorten.OmdGenerator

That makes generateomd available in the workflow.

2. Generate markdown output

- name: Generate API diff
run: |
generateomd \
--source=src/MyLibrary \
--gitRepo=${{ github.server_url }}/${{ github.repository }} \
--sourceRef=${{ github.event.pull_request.head.sha }} \
--compareRef=${{ github.event.pull_request.base.sha }} \
--format=md \
--output=api-diff

The output is written to api-diff.md.

3. Only comment when there’s an actual API diff

The workflow checks whether the generated markdown contains any namespace entries:

- name: Check whether API changes were found
id: api_diff
run: |
if grep -q '^namespace ' api-diff.md; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
echo "has_changes=false" >> "$GITHUB_OUTPUT"
fi

That means the bot won’t spam pull requests when nothing in the API surface actually changed.

Auto-updating the original PR comment

This is the part I especially like.

The workflow uses a hidden marker in the PR comment body, such as:

<!-- dotnet-omd-api-diff -->

When the action runs again, it looks for an existing bot comment containing that marker. If it finds one, it updates that comment instead of creating a new one.

That gives you two nice behaviors:

  • if new commits change the API diff, the original comment is refreshed.
  • if the API changes are later reverted, the existing comment can be updated to say that no API changes are detected any longer.

That second behavior matters a lot. It means the bot comment tracks the current truth of the PR, not just the first interesting state it saw.

In practice, the logic becomes:

  • if there are API changes and no prior comment, create one
  • if there are API changes and a prior comment exists, update it
  • if there are no API changes but a prior comment exists, update it to say so
  • if there are no API changes and no prior comment exists, do nothing

That keeps noise low while still making the automation feel smart and stateful.

Why this workflow is valuable

I like this approach because it adds useful review context without adding much maintenance overhead.

Reviewers get a dedicated summary of API changes. Authors get immediate feedback if they accidentally alter public surface area. And because the comment updates in place, the pull request stays clean instead of filling up with bot spam every time a new commit is pushed.

It also turns the tool from something you run manually into something that can enforce visibility around API changes as part of normal team workflow.

 

Closing

This git-based diff support makes the Object Model Diagram Generator much more useful in day-to-day development. Comparing API shape between commits, branches, and tags is now built in, and the GitHub Actions integration makes it easy to surface those changes directly in pull requests.

You can see the full github action documented here: https://github.com/dotMorten/DotNetOMDGenerator/tree/main#github-actions-comment-pr-api-changes 

And here's an example of one of my repos now using this action to validate PRs: https://github.com/dotMorten/WinUIEx/blob/main/.github/workflows/apidiff.yml 

Add comment