22# cSpell:ignore PULLREQUEST
33# cSpell:ignore TARGETBRANCH
44# cSpell:ignore SOURCECOMMITID
5+
6+ function RequestGithubGraphQL {
7+ param (
8+ [string ]$query ,
9+ [object ]$variables = @ {}
10+ )
11+
12+ $payload = @ {
13+ query = $query
14+ variables = $variables
15+ } | ConvertTo-Json - Depth 100
16+
17+ $response = $payload | gh api graphql -- input -
18+ $data = $response | ConvertFrom-Json
19+
20+ if ($LASTEXITCODE ) {
21+ LogWarning " Failed graphql operation:"
22+ LogWarning ($payload -replace ' \\n' , " `n " )
23+ if ($data.errors ) {
24+ LogWarning ($data.errors.message -join " `n " )
25+ }
26+ throw " graphql operation failed ($LASTEXITCODE )"
27+ }
28+
29+ return $data
30+ }
31+
532function Get-ChangedFiles {
633 param (
7- [string ]$SourceCommittish = " ${env: SYSTEM_PULLREQUEST_SOURCECOMMITID} " ,
34+ [string ]$SourceCommittish = " ${env: SYSTEM_PULLREQUEST_SOURCECOMMITID} " ,
835 [string ]$TargetCommittish = (" origin/${env: SYSTEM_PULLREQUEST_TARGETBRANCH} " -replace " refs/heads/" ),
936 [string ]$DiffPath ,
1037 [string ]$DiffFilterType = " d"
@@ -21,18 +48,18 @@ function Get-ChangedFiles {
2148 # Git PR diff: https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-comparing-branches-in-pull-requests#three-dot-and-two-dot-git-diff-comparisons
2249 $command = " git -c core.quotepath=off -c i18n.logoutputencoding=utf-8 diff `" $TargetCommittish ...$SourceCommittish `" --name-only --diff-filter=$DiffFilterType "
2350 if ($DiffPath ) {
24- $command = $command + " -- `' $DiffPath `' "
51+ $command = $command + " -- `' $DiffPath `' "
2552 }
2653 Write-Host $command
2754 $changedFiles = Invoke-Expression - Command $command
28- if (! $changedFiles ) {
55+ if (! $changedFiles ) {
2956 Write-Host " No changed files in git diff between $TargetCommittish and $SourceCommittish "
3057 }
3158 else {
32- Write-Host " Here are the diff files:"
33- foreach ($file in $changedFiles ) {
59+ Write-Host " Here are the diff files:"
60+ foreach ($file in $changedFiles ) {
3461 Write-Host " $file "
35- }
62+ }
3663 }
3764 return $changedFiles
3865}
@@ -58,7 +85,7 @@ class ConflictedFile {
5885 $this.ParseContent ($this.Content )
5986 }
6087
61- [array ] Left(){
88+ [array ] Left() {
6289 if ($this.IsConflicted ) {
6390 # we are forced to get this line by line and reassemble via join because of how powershell is interacting with
6491 # git show --textconv commitsh:path
@@ -75,7 +102,7 @@ class ConflictedFile {
75102 }
76103 }
77104
78- [array ] Right(){
105+ [array ] Right() {
79106 if ($this.IsConflicted ) {
80107 $toShow = " $ ( $this.RightSource ) :$ ( $this.Path ) " -replace " \\" , " /"
81108 Write-Host " git show $toShow "
@@ -92,7 +119,7 @@ class ConflictedFile {
92119 $l = @ ()
93120 $r = @ ()
94121
95- foreach ($line in $lines ) {
122+ foreach ($line in $lines ) {
96123 if ($line -match " ^<<<<<<<\s*(.+)" ) {
97124 $this.IsConflicted = $true
98125 $this.LeftSource = $matches [1 ]
@@ -247,4 +274,89 @@ function Write-RateLimit {
247274 Write-Host " remaining=$ ( $RateLimit.remaining ) "
248275 Write-Host " reset=$ ( $RateLimit.reset ) "
249276 Write-Host " "
250- }
277+ }
278+
279+ function GetUnresolvedAIReviewThreads {
280+ param (
281+ [string ]$repoOwner ,
282+ [string ]$repoName ,
283+ [string ]$prNumber ,
284+ [array ]$reviewers
285+ )
286+
287+ $reviewers ?? = @ (' copilot-pull-request-reviewer' )
288+
289+ $reviewThreadsQuery = @'
290+ query ReviewThreads($owner: String!, $name: String!, $number: Int!) {
291+ repository(owner: $owner, name: $name) {
292+ pullRequest(number: $number) {
293+ reviewThreads(first: 100) {
294+ nodes {
295+ id
296+ isResolved
297+ comments(first: 100) {
298+ nodes {
299+ body
300+ author {
301+ login
302+ }
303+ }
304+ }
305+ }
306+ }
307+ }
308+ }
309+ }
310+ '@
311+
312+ $variables = @ { owner = $repoOwner ; name = $repoName ; number = [int ]$prNumber }
313+ $response = RequestGithubGraphQL - query $reviewThreadsQuery - variables $variables
314+ $reviews = $response.data.repository.pullRequest.reviewThreads.nodes
315+
316+ $threadIds = @ ()
317+ # There should be only one threadId for copilot, but make it an array in case there
318+ # are more, or if we want to resolve threads from multiple ai authors in the future.
319+ # Don't mark threads from humans as resolved, as those may be real questions/blockers.
320+ foreach ($thread in $reviews ) {
321+ if ($thread.comments.nodes | Where-Object { $_.author.login -in $reviewers }) {
322+ if (! $thread.isResolved ) {
323+ $threadIds += $thread.id
324+ }
325+ continue
326+ }
327+ }
328+
329+ return $threadIds
330+ }
331+
332+ function TryResolveAIReviewThreads {
333+ param (
334+ [string ]$repoOwner ,
335+ [string ]$repoName ,
336+ [string ]$prNumber ,
337+ [array ]$reviewers
338+ )
339+
340+ $resolveThreadMutation = @'
341+ mutation ResolveThread($id: ID!) {
342+ resolveReviewThread(input: { threadId: $id }) {
343+ thread {
344+ isResolved
345+ }
346+ }
347+ }
348+ '@
349+
350+ $threadIds = GetUnresolvedAIReviewThreads - repoOwner $repoOwner - repoName $repoName - prNumber $prNumber - reviewers $reviewers
351+
352+ if (! $threadIds ) {
353+ return $false
354+ }
355+
356+ foreach ($threadId in $threadIds ) {
357+ LogInfo " Resolving review thread '$threadId ' for '$repoName ' PR '$prNumber '"
358+ RequestGithubGraphQL - query $resolveThreadMutation - variables @ { id = $threadId }
359+ }
360+
361+ return $true
362+ }
0 commit comments