Release notes are a good method to inform your users about the additions, bug fixes and changes which have been made to the software since the latest release. You can distribute the release notes before the upcoming release (a.k.a. “Pre-release notes”), or after the release has been done. These notes can be made manually or scripted.
Let’s start
In this article the release branch strategy requires you to create a monthly release branch for every production deployment. For instance, a monthly release could be named “Release-202401”. You are free to use any other cadence, but you will need to make some small modifications.
To create release notes for a new release, you need to do a comparison between this release and the previous release in order to get all commits in between. Within Azure DevOps this can be done within the browser, or even better with a PowerShell script. Whilst collecting the commits, it is necessary to get all linked work items, because those contain the actual information you’ll need for the release notes.
Setting up
Prerequisite: a Private Access Token (PAT) to call the Azure DevOps Services REST API. Read here
Variables
Start off with creating important variables which we need to call the API.
# Authentication for ADO REST API
$privateAccessToken = "XXXX"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f "",$privateAccessToken)))
$headers = @{Authorization=("Basic {0}" -f $base64AuthInfo)}
# ADO Project settings
$organisation = "myorganisation"
$serverUri = "https://dev.azure.com/$organisation"
$project = "myproject"
Get repository IDs
Next we need to get the IDs of the two repositories we want to compare. This piece of script will list all repositories within the project, i.e. ID and name. The ID is required for the next section. You will only have to do this once for every repository you want to do a comparison.
# Repository names, for now fixed parameters
$releasePrevious = "Release-202311"
$releaseCurrent = "Release-202312"
# Get repositories within project
$uri1 = "$serverUri/$project/_apis/git/repositories" + "?api-version=7.1-preview.1"
$repos = Invoke-RestMethod -Uri $uri1 -Method Get -ContentType "application/json" -Headers $headers
# List found repositories
foreach($repo in $repos.value) {
Write-Host $repo.id $repo.name
}
Collect the commits
Next we will collect all the commits which were done between both release branches.
# This must contain ID of repository found in previous section
$repositoryId = "XXX"
# Get commits between the 2 release branches including work items
$uri2 = "$serverUri/$project/_apis/git/repositories/" + $repositoryId + "/commits" + "?searchCriteria.itemVersion.version=$releasePrevious&searchCriteria.compareVersion.version=$releaseCurrent&searchCriteria.itemVersion.versionType=branch&searchCriteria.compareVersion.versionType=branch&searchCriteria.includeWorkItems=true&api-version=7.1-preview.1"
$results2 = Invoke-RestMethod -Uri $uri2 -Method Get -ContentType "application/json" -Headers $headers
Iterate the commits
Next a big piece of code is needed to iterate through all commits found, at the end the arrays $workitems
and $commits
will have all the info we will need.
Write-Host "Comparing branches`n" -ForegroundColor Green
# List found commits
foreach($result in $results2.value) {
Write-Host $result.commitId " | Committed by: " $result.committer.name "| Committed on: " ([DateTime]$result.committer.date).ToLocalTime()
Write-Host "Title: " $result.comment
$commitWorkitemList = ""
$status = ""
$tags = ""
$relbranch = ""
# If a work item(s) attached to commit, collect the work items
if($result.workItems -ne $null) {
foreach($wit in $result.workItems) {
$allWorkitems += "$($wit.id)"
$commitWorkitemList += "$($wit.id),"
}
# Remove extraneous comma for later usage ..
$commitWorkitemList = $commitWorkitemList.Substring(0,$commitWorkitemList.Length-1)
# .. and convert to array for processing
$commitWorkitems = $commitWorkitemList.Split(',')
# Get information from commit's workitem(s)
foreach($commitWorkitem in $commitWorkitems) {
# Specify which fields to retrieve in request
$uri4 = "$serverUri/$project/_apis/wit/workitems/" + $commitWorkitem + "?fields=System.WorkItemType,System.Title,System.State,System.AssignedTo,System.Tags,Custom.Releasebranch&api-version=7.1-preview.3"
$results4 = Invoke-RestMethod -Uri $uri4 -Method Get -ContentType "application/json" -Headers $headers
$status += $results4.fields.'System.State'+','
$tags += $results4.fields.'System.Tags'+'#'
$relbranch = $results4.fields.'Custom.Releasebranch'
}
}
else {
Write-Host "No workitem attached!" -ForegroundColor Yellow
}
# Remove extraneous character
$status = if($status -ne "") { $status.Substring(0,$status.Length-1) }
$tags = if($tags -ne "") { $tags.Substring(0,$tags.Length-1) }
# Collect info of all commits
$commits += [pscustomobject]@{Repo=$repository.Name; Title=$result.comment; Author=$result.committer.name; Date=([DateTime]$result.committer.date).ToLocalTime(); Items=$commitWorkitemList; Status=$status; Branch=$relbranch; Tags=$tags}
}
Iterate the work items
Now that all the work items attached to the commits have been collected, the work items need to be enriched as well.
# Filter out duplicate work items
$workitems = $workitems | Select -Unique
# Loop through work items and collect info (yes, again)
$workitems2 =@()
foreach($workitem in $workitems) {
# Get info for this work item
$uri4 = "$serverUri/$project/_apis/wit/workitems/" + $workitem + "?fields=System.WorkItemType,System.Title,System.State,System.AssignedTo,System.Tags,Custom.Releasebranch&api-version=7.1-preview.3"
$results4 = Invoke-RestMethod -Uri $uri4 -Method Get -ContentType "application/json" -Headers $headers
# Collect info of all work items
$workitems2 += [pscustomobject]@{Workitem=$results4.id; Title=$results4.fields.'System.Title'; Status=$results4.fields.'System.State'; Assigned=$results4.fields.'System.AssignedTo'.displayName; Branch=$results4.fields.'Custom.Releasebranch'; Tags=$results4.fields.'System.Tags'}
}
Showing the results
After all the hard work, it’s time to show off the results. The work items overview is the source for the release notes. In this example it is only showing the work items which are not yet closed, but you can expand the filter to e.g. “Closed, Active” or what you require.
# Showing all work items which are not yet Closed
$workitems2 | sort -Property WorkItem | where -Property Status -notin "Closed" | FT -AutoSize
# Showing all commits which have their work item(s) not yet Closed
$commits | where -Property Status -notin "Closed" | FT -AutoSize
Conclusion
If you’re a developer you like to automate as much as possible and focus on the interesting and challenging stuff, not doing a lot of administration like release notes. With the Azure DevOps Services REST API you can retrieve a lot of information to use to your advantage.
In Part 2, I will continue with creating the release notes …