gitazure-devopsazure-repos

Azure DevOps Repos Limit or Block PR by Number of Commits


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.


Solution

  • 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:

    1. In Azure DevOps project, go to Project Settings > Teams to create a team (e.g., Developers).

      • Add all the developers in the project as members of this team.
      • Go to Project Settings > Repositories > "Security", set and ensure the permission "Remove others' locks" is "Deny" for this team.

      enter image description here

    2. 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

      enter image description here

    3. Go to Project Settings > Service connections to create a Generic service connection.

      • Server URL: https://dev.azure.com/{organization}/{project}, replace {organization} and {project} with the actual names of your Azure DevOps organization and project.
      • Service connection name: A custom name of the service connection.

      enter image description here

    4. In the Azure DevOps project, create a YAML pipeline (e.g., LockPR) with the configurations like as below.

    # 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
    

    enter image description here

    1. Go to Project Settings > Repositories > "Policies" to create the Cross-Repository Branch Policies within the project.

      • Protect the default branch of each repository: If select this option, the policies will be applied to the default branch of all repositories within the project.
      • Protect current and future branches matching a specified pattern: If select this option, you can set a pattern. The policies will be applied to the branches that the names can match the pattern.
      • Add the YAML pipeline (LockPR) as a required Build Validation on the Branch Policies.

      enter image description here


    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: