I am working in Azure DevOps and have junior developers creating PRs with massive code changes and over 100 commits. I have looked at the default policies in Azure DevOps Repos, but did not see anything that limits the number of commits in a PR.
Is there any way to limit the number of commits in a single PR, so it promotes positive development behavior?
Thanks in advance for the help.
Addendum:
I am not looking to make any universal judgements on coding practices. I am simply looking for advice to determine if there is a way to limit number of commits or commit sizes per PR.
I believe there can be legitimate situations to have many large commits per PR. One example of this would be a rigid, strictly regulated systems that do not allow simpler components without error.
From my leadership perspective with my teams' context, it does not constitute one of these valid reasons.
There is no documented limit for the max count of commits on a PR, and also no built-in option to set a custom limit for this.
As a workaround, you can try like as below:
In Azure DevOps project, go to Project Settings > Teams to create a team (e.g., Developers).
Remove others' locks
" is "Deny
" for this team.Go to Project Settings > Repositories > "Security", set and ensure the following permissions are "Allow" for the identities "Project Collection Build Service ({Organization Name})
" and "{Project Name} Build Service ({Organization Name})
":
Contribute
Contribute to pull requests
Edit policies
Remove others' locks
Go to Project Settings > Service connections to create a Generic service connection.
https://dev.azure.com/{organization}/{project}
, replace {organization}
and {project}
with the actual names of your Azure DevOps organization and project.In the Azure DevOps project, create a YAML pipeline (e.g., LockPR) with the configurations like as below.
The pipeline main YAML. Replace {organization}
with the actual name of your Azure DevOps organization.
# pipeline-lock-PR.yml
trigger: none
jobs:
- job: lock
pool: server
variables:
repoName: $[variables['Build.Repository.Name']]
branchName: $[replace(variables['System.PullRequest.SourceBranch'], 'refs/', '')]
steps:
- task: InvokeRESTAPI@1
displayName: 'Lock source branch of PR'
inputs:
connectionType: 'connectedServiceName'
serviceConnection: 'ForGitAPI'
method: 'PATCH'
headers: |
{
"Content-Type":"application/json",
"Authorization": "Bearer $(system.AccessToken)"
}
body: |
{
"isLocked": true
}
urlSuffix: '/_apis/git/repositories/$(repoName)/refs?filter=$(branchName)&api-version=7.0'
waitForCompletion: 'false'
- job: check
dependsOn: lock
steps:
- task: PowerShell@2
displayName: 'Check PR Commits Count'
inputs:
filePath: 'scripts/Check-PR-Commits-Count.ps1'
arguments: '-o "{organization}" -p "$(System.TeamProject)" -r "$(Build.Repository.Name)" -pr $(System.PullRequest.PullRequestId)'
pwsh: true
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
The content of the PowerShell script "scripts/Check-PR-Commits-Count.ps1
".
In this example script, I set the default value of $maxCommitCount
be to 5
for testing. You can change it to be a different value in the script, or pass a different value to overwrite it using the parameter 'maxCount
' or 'max
' when calling the script.
# Check-PR-Commits-Count.ps1
param (
[Alias("org", "o")]
[string] $organization,
[Alias("proj", "p")]
[string] $project,
[Alias("repo", "r")]
[string] $repository,
[Alias("prId", "pr")]
[int] $pullRequestId,
[Alias("maxCount", "max")]
[int] $maxCommitCount = 5,
[Alias("sGenre", "sg")]
[string] $statusGenre = "PR-Status",
[Alias("sName", "sn")]
[string] $statusName = "Lock-SourceBranch"
)
$headers = @{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
"Content-Type" = "application/json"
}
# Get the count of commits on PR.
$uri_list_commits = "https://dev.azure.com/${organization}/${project}/_apis/git/repositories/${repository}/pullRequests/${pullRequestId}/commits?api-version=7.0"
$commit_count = (Invoke-RestMethod -Method GET -Uri $uri_list_commits -Headers $headers).count
# Check whether to unlock the source branch of PR.
$statusDescription = "Allowed max commits count: $maxCommitCount; Current commits count: $commit_count;"
if ($commit_count -lt $maxCommitCount) {
# Unlock the source branch of PR.
$branchName = $env:SYSTEM_PULLREQUEST_SOURCEBRANCH -replace "refs/", ""
$uri_unlock_branch = "https://dev.azure.com/${organization}/${project}/_apis/git/repositories/${repository}/refs?filter=${branchName}&api-version=7.0"
$body_unlock_branch = @{isLocked = $false} | ConvertTo-Json -Depth 5
Invoke-RestMethod -Method PATCH -Uri $uri_unlock_branch -Headers $headers -Body $body_unlock_branch
$statusDescription += " Unlock source branch, allow subsequent new commits."
}
else {
$statusDescription += " Lock source branch, disallow subsequent new commits."
}
# Get the ID of latest iteration for PR.
$uri_list_iterations = "https://dev.azure.com/${organization}/${project}/_apis/git/repositories/${repository}/pullRequests/${pullRequestId}/iterations?api-version=7.0"
$iterationId = (Invoke-RestMethod -Method GET -Uri $uri_list_iterations -Headers $headers).count
# Create a status for the latest iteration of PR.
$uri_create_status = "https://dev.azure.com/${organization}/${project}/_apis/git/repositories/${repository}/pullRequests/${pullRequestId}/statuses?api-version=7.0"
$body_create_status = @{
iterationId = $iterationId
state = "succeeded"
description = $statusDescription
context = @{
genre = $statusGenre
name = $statusName
}
} | ConvertTo-Json -Depth 5
Invoke-RestMethod -Method POST -Uri $uri_create_status -Headers $headers -Body $body_create_status
Go to Project Settings > Repositories > "Policies" to create the Cross-Repository Branch Policies within the project.
With above configuration, if the target branch of PR has the Branch Policies applied, when opening a new PR or have new commits to an active PR, the pipeline (LockPR) automatically gets triggered by the PR:
If the count of commits on the PR is equal or more than the value of $maxCommitCount
, the source branch of the PR gets locked, and the developers cannot push new commits. They need to wait for the the current PR is completed, and the source branch gets unlocked by admin. Then they can create a new PR for the subsequent commits.
When the branch is locked, the developers still can clone the repository to their local machines and make changes on the local repository. But they cannot push the changes from the local repository to the locked branch in remote repository.
If the count of commits on the PR is less than the value of $maxCommitCount
, the source branch of the PR gets unlocked, and the developers can continue pushing new commits.