For a client, we manage over 60 repositories with frontends and an API behind them. To realize some features, we need to update all repositories with the same change. That seems like a lot of work doesn’t it?

Clone 60 projects?

I talked in previous posts about automating these kinds of changes. But for this case, we don’t want to clone all 60 repositories. We could go for a temp folder and clone them one by one and then clean them up. Another solution could be creating a yaml pipeline for it, that clones on the agent machine and loops over all repositories, but I wanted to keep the blame on the person of the initial change.

For this case I used GitHub copilot to help me brainstorm for solutions, another solution I did not follow was using a $gitClient object in Powershell.

Push files

In the underlying script, I can create a branch from the main branch and push the contents of the readme file. This is a simple example, but it could be any file. The script can push the file to the repository and create a pull request.

PushReadmeAzureDevOps.ps1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# Prompt the user to login and get the access token
# see https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?toc=%2Fazure%2Fdevops%2Forganizations%2Fsecurity%2Ftoc.json&view=azure-devops#q-can-i-use-a-service-principal-or-managed-identity-with-azure-cli
$accessToken = az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query "accessToken" --output tsv
if ($null -eq $accessToken) {
    exit 1
}
$orgUrl = "https://dev.azure.com/MARTORG"
$project = "MARTPROJECT"

$repositoryId = "YOURREPO"
$readmeFilePath = "README.md"

$headers = @{
    "Authorization" = ("Bearer {0}" -f $accessToken)
    "Accept"        = "application/json"
    "Content-Type" = "application/json"
}

# Convert the README file to base64
$readmeContent = Get-Content -Path $readmeFilePath -Raw
$readmeBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($readmeContent))
Write-Host "README file content: $readmeContent"

# get current commit object id
$commitUrl = "$orgUrl/$project/_apis/git/repositories/$repositoryId/refs/heads/main?api-version=6.0"
$commitResponse = Invoke-RestMethod -Uri $commitUrl -Headers $headers -Method Get
Write-Host $commitResponse.Value
$commitObjectId = $commitResponse.Value.objectId

Write-Host "Current commit object id: $commitObjectId"

# Create the request body
$changes = @(
                @{
                    changeType = "edit"
                    item = @{
                        path = "README.md"
                    }
                    newContent = @{
                        content = $readmeBase64
                        contentType = "base64encoded"
                    }
                }
            )
$requestBody = @{
    refUpdates = @(
        @{
            name = "refs/heads/add-readme"
            oldObjectId = $commitObjectId
        }
    )
    commits = @(
        @{
            comment = "Adding README file"
            changes = $changes
        }
    )
} | ConvertTo-Json -Depth 6

# Set the API endpoint
$apiUrl = "$orgUrl/$project/_apis/git/repositories/$repositoryId/pushes?api-version=6.0"

# Send the API request
$response = Invoke-RestMethod -Uri $apiUrl -Method Post -Headers $headers -Body $requestBody

# Check the response
if ($response.pushId) {
    Write-Host "README file pushed successfully."
} else {
    Write-Host "Failed to push README file. Error: $($response.message)"
}

# create a pull request for myfirstbranch
$pullRequestUrl = "$orgUrl/$project/_apis/git/repositories/$repositoryId/pullrequests?api-version=6.0"
$pullRequestBody = @{
    sourceRefName = "refs/heads/add-readme"
    targetRefName = "refs/heads/main"
    title = "Add README file"
    description = "Adding README file"
} | ConvertTo-Json -Depth 6

$pullResponse = Invoke-RestMethod -Uri $pullRequestUrl -Method Post -Headers $headers -Body $pullRequestBody

if ($pullResponse.pullRequestId) {
    Write-Host "Pull request created successfully."
} else {
    Write-Host "Failed to create pull request. Error: $($pullResponse.message)"
}

Conclusion

I am going to use this script to update files across all of our repositories. For this client, the code review is important, so the automation would end in a set pull request. In the next blog post, I am going to create a script that will create files to be pushed for every repository.