func_MergeRequests.ps1

# ---------------------------------------------------------------------
# Group merge requests API
# https://docs.gitlab.com/ee/api/merge_requests.html#list-group-merge-requests

# get a list of group merge requests
# (requires version 14.8)
function Get-GitlabGroupMergeRequests( [Parameter(Mandatory=$true)] [string] $group
                                     , [Parameter(Mandatory=$false)][switch] $closed
                                     , [Parameter(Mandatory=$false)][switch] $merged
                                     , [Parameter(Mandatory=$false)][switch] $opened
                                     , [Parameter(Mandatory=$false)][switch] $simple
                                     , [Parameter(Mandatory=$false)][string] $milestone
                                     , [Parameter(Mandatory=$false)][string] $search
                                     )
{
  [string] $GAPI_MERGE = "$CI_API_V4_URL/groups/$group/merge_requests?scope=all&per_page=100&order_by=updated_at&sort=asc"

  if ($closed) {
    $GAPI_MERGE += "&state=closed"
  }
  elseif ($merged) {
    $GAPI_MERGE += "&state=merged"
  }
  elseif ($opened) {
    $GAPI_MERGE += "&state=opened"
  }

  if ($simple) {
    $GAPI_MERGE += "&view=simple"
  }

  if ($PSBoundParameters.ContainsKey('milestone')) {
    $GAPI_MERGE += "&milestone=$milestone"
  }

  if (![string]::IsNullOrWhiteSpace($search)) {
    $search = [uri]::EscapeDataString($search)
    $GAPI_MERGE += "&search=$search"
  }

  return @(Invoke-WebRequestContentToJson -headers $GLPT -uri $GAPI_MERGE)
}


# ---------------------------------------------------------------------
# Merge requests API
# https://docs.gitlab.com/ee/api/merge_requests.html

# get a list of merge requests from project
# (requires version 14.8)
function Get-GitlabMergeRequests( [Parameter(Mandatory=$true)] [string] $project
                                , [Parameter(Mandatory=$false)][switch] $closed
                                , [Parameter(Mandatory=$false)][switch] $merged
                                , [Parameter(Mandatory=$false)][switch] $opened
                                , [Parameter(Mandatory=$false)][switch] $simple
                                , [Parameter(Mandatory=$false)][string] $labels
                                , [Parameter(Mandatory=$false)][string] $milestone
                                , [Parameter(Mandatory=$false)][string] $search
                                , [Parameter(Mandatory=$false)][string] $source_branch
                                , [Parameter(Mandatory=$false)][string] $target_branch
                                , [Parameter(Mandatory=$false)][string] $wip
                                )
{
  [string] $GAPI_MERGE = "$CI_API_V4_URL/projects/$project/merge_requests?scope=all&per_page=100&order_by=updated_at&sort=asc"

  if ($closed) {
    $GAPI_MERGE += "&state=closed"
  }
  elseif ($merged) {
    $GAPI_MERGE += "&state=merged"
  }
  elseif ($opened) {
    $GAPI_MERGE += "&state=opened"
  }

  if ($simple) {
    $GAPI_MERGE += "&view=simple"
  }

  if ($PSBoundParameters.ContainsKey('labels')) {
    $labels = [uri]::EscapeDataString($labels)
    $GAPI_MERGE += "&labels=$labels"
  }

  if ($PSBoundParameters.ContainsKey('milestone')) {
    $milestone = [uri]::EscapeDataString($milestone)
    $GAPI_MERGE += "&milestone=$milestone"
  }

  if (![string]::IsNullOrWhiteSpace($search)) {
    $search = [uri]::EscapeDataString($search)
    $GAPI_MERGE += "&search=$search"
  }

  if ($PSBoundParameters.ContainsKey('source_branch')) {
    $source_branch = [uri]::EscapeDataString($source_branch)
    $GAPI_MERGE += "&source_branch=$source_branch"
  }

  if ($PSBoundParameters.ContainsKey('target_branch')) {
    $target_branch = [uri]::EscapeDataString($target_branch)
    $GAPI_MERGE += "&target_branch=$target_branch"
  }

  if ($PSBoundParameters.ContainsKey('wip')) {
    $GAPI_MERGE += "&wip=$wip"
  }

  return @(Invoke-WebRequestContentToJson -headers $GLPT -uri $GAPI_MERGE)
}

# get a single MR
function Get-GitlabMergeRequest( [Parameter(Mandatory=$true)] [string] $project
                               , [Parameter(Mandatory=$true)] [string] $iid
                               )
{
  [string] $GAPI_MERGE_IID = "$CI_API_V4_URL/projects/$project/merge_requests/$($iid)?include_diverged_commits_count=true&include_rebase_in_progress=true"
  return (Invoke-RestMethod -headers $GLPT -uri $GAPI_MERGE_IID -method GET)
}

# modify an existing merge request
# (requires version 13.8)
function Set-GitlabMergeRequest( [Parameter(Mandatory=$true)] [string] $project
                               , [Parameter(Mandatory=$true)] [string] $iid
                               , [Parameter(Mandatory=$false)][string] $title
                               , [Parameter(Mandatory=$false)][string] $description
                               , [Parameter(Mandatory=$false)][string] $labels
                               , [Parameter(Mandatory=$false)][string] $milestone
                               , [Parameter(Mandatory=$false)][string] $squash
                               , [Parameter(Mandatory=$false)][string] $assignee
                               , [Parameter(Mandatory=$false)][string] $reviewer
                               )
{
  [string] $GAPI_MERGE_IID = "$CI_API_V4_URL/projects/$project/merge_requests/$iid"
  $body_json = @{}

  if ($PSBoundParameters.ContainsKey('title')) {
    $body_json += @{ "title" = "$title" }
  }

  if ($PSBoundParameters.ContainsKey('description')) {
    [string] $desc_coded = $description -replace "\r\n", "`n"
    $body_json += @{ "description" = "$desc_coded" }
  }

  if ($PSBoundParameters.ContainsKey('labels')) {
    $body_json += @{ "labels" = "$labels" }
  }

  if ($PSBoundParameters.ContainsKey('milestone')) {
    [int] $milestone_id = Get-GitlabMilestoneID -project $project -title $milestone
    $body_json += @{ "milestone_id" = $milestone_id }
  }

  if ($PSBoundParameters.ContainsKey('squash')) {
    $body_json += @{ "squash" = "$squash" }
  }

  if ($PSBoundParameters.ContainsKey('assignee')) {
    [int] $assignee_id = Get-GitlabUserID $assignee
    $body_json += @{ "assignee_ids" = @( $assignee_id ) }
  }

  if ($PSBoundParameters.ContainsKey('reviewer')) {
    [int] $reviewer_id = Get-GitlabUserID $reviewer
    $body_json += @{ "reviewer_ids" = @( $reviewer_id ) }
  }

  if (!$body_json.Count) { return $null }
  [string] $body = $body_json | ConvertTo-Json -depth 10

  return (Invoke-RestMethod -headers $GLPT -uri $GAPI_MERGE_IID -method PUT -ContentType 'application/json' -body $body)
}

# create a new merge request
# title and description can contain unescaped quotes "
# description can be multiline, but markdown formatted
function New-GitlabMergeRequest( [Parameter(Mandatory=$true)] [string] $project
                               , [Parameter(Mandatory=$true)] [string] $title
                               , [Parameter(Mandatory=$true)] [string] $source_branch
                               , [Parameter(Mandatory=$false)][string] $target_branch = 'master'
                               , [Parameter(Mandatory=$false)][string] $author
                               , [Parameter(Mandatory=$false)][string] $assignee
                               , [Parameter(Mandatory=$false)][string] $reviewer
                               , [Parameter(Mandatory=$false)][string] $description
                               , [Parameter(Mandatory=$false)][string] $labels
                               , [Parameter(Mandatory=$false)][string] $milestone
                               , [Parameter(Mandatory=$false)][string] $squash
                               )
{
  [string] $title_coded = $title -replace '[\u201C\u201D\u201E\u201F]', '"' # “”„‟

  $body_json = @{ "title"         = "$title_coded";
                  "source_branch" = "$source_branch";
                  "target_branch" = "$target_branch";
                  "remove_source_branch" = "true";
                }

  if (![string]::IsNullOrWhiteSpace($assignee)) {
    [int] $assignee_id = Get-GitlabUserID $assignee
    $body_json += @{ "assignee_ids" = @( $assignee_id ) }
  }

  if (![string]::IsNullOrWhiteSpace($reviewer)) {
    [int] $reviewer_id = Get-GitlabUserID $reviewer
    $body_json += @{ "reviewer_ids" = @( $reviewer_id ) }
  }

  if (![string]::IsNullOrWhiteSpace($description)) {
    [string] $desc_coded = $description -replace "\r\n", "`n"
             $desc_coded = $desc_coded  -replace '[\u201C\u201D\u201E\u201F]', '"' # “”„‟

    $body_json += @{ "description" = "$desc_coded" }
  }

  if (![string]::IsNullOrWhiteSpace($labels)) {
    $body_json += @{ "labels" = "$labels" }
  }

  if (![string]::IsNullOrWhiteSpace($milestone)) {
    [int] $milestone_id = Get-GitlabMilestoneID -project $project -title $milestone
    $body_json += @{ "milestone_id" = $milestone_id }
  }

  if ($PSBoundParameters.ContainsKey('squash')) {
    $body_json += @{ "squash" = "$squash" }
  }

  [string] $GAPI_MERGE = "$CI_API_V4_URL/projects/$project/merge_requests"
  if (![string]::IsNullOrWhiteSpace($author)) {
    $GAPI_MERGE += "?sudo=$author"
  }

  [string] $body = $body_json | ConvertTo-Json -depth 10
  return (Invoke-RestMethod -headers $GLPT -uri $GAPI_MERGE -method POST -ContentType 'application/json' -body $body)
}

# delete a merge request
function Remove-GitlabMergeRequest( [Parameter(Mandatory=$true)] [string] $project
                                  , [Parameter(Mandatory=$true)] [string] $iid
                                  )
{
  [string] $GAPI_MERGE_IID = "$CI_API_V4_URL/projects/$project/merge_requests/$iid"
  return (Invoke-RestMethod -headers $GLPT -uri $GAPI_MERGE_IID -method DELETE)
}

# accept a merge request after pipeline succeeds
function Merge-GitlabMergeRequest( [Parameter(Mandatory=$true)] [string] $project
                                 , [Parameter(Mandatory=$true)] [string] $iid
                                 , [Parameter(Mandatory=$false)][string] $remove_source_branch = 'true'
                                 , [Parameter(Mandatory=$false)][string] $squash
                                 )
{
  [string] $GAPI_MERGE_IID = "$CI_API_V4_URL/projects/$project/merge_requests/$iid/merge"
  $body_json = @{ "merge_when_pipeline_succeeds" = "true";
                  "should_remove_source_branch"  = "$remove_source_branch";
                }

  if ($PSBoundParameters.ContainsKey('squash')) {
    $body_json += @{ "squash" = "$squash" }
  }

  [string] $body = $body_json | ConvertTo-Json -depth 10
  return (Invoke-RestMethod -headers $GLPT -uri $GAPI_MERGE_IID -method PUT -ContentType 'application/json' -body $body)
}

# cancel a merge that is waiting for pipeline to succeed
function Stop-GitlabMergeRequest( [Parameter(Mandatory=$true)] [string] $project
                                , [Parameter(Mandatory=$true)] [string] $iid
                                )
{
  [string] $GAPI_MERGE_IID_CANCEL = "$CI_API_V4_URL/projects/$project/merge_requests/$iid/cancel_merge_when_pipeline_succeeds"
  return (Invoke-RestMethod -headers $GLPT -uri $GAPI_MERGE_IID_CANCEL -method PUT)
}

# rebase a merge request
function Invoke-GitlabMergeRequestRebase( [Parameter(Mandatory=$true)] [string] $project
                                        , [Parameter(Mandatory=$true)] [string] $iid
                                        )
{
  [string] $GAPI_MERGE_IID_REBASE = "$CI_API_V4_URL/projects/$project/merge_requests/$iid/rebase"
  $webreq = Invoke-WebRequest -headers $GLPT -uri $GAPI_MERGE_IID_REBASE -method PUT
  return $webreq.StatusCode
}

# get approvals of a merge request
function Get-GitlabMergeRequestApprovals( [Parameter(Mandatory=$true)] [string] $project
                                        , [Parameter(Mandatory=$true)] [string] $iid
                                        )
{
  [string] $GAPI_MERGE_IID_APPROVALS = "$CI_API_V4_URL/projects/$project/merge_requests/$iid/approvals"
  return (Invoke-RestMethod -headers $GLPT -uri $GAPI_MERGE_IID_APPROVALS -method GET)
}

# get unique authors from all commits of a merge request
function Get-GitlabMergeRequestAuthors( [Parameter(Mandatory=$true)] [string] $project
                                      , [Parameter(Mandatory=$true)] [string] $iid
                                      )
{
  $commits = Get-GitlabMergeRequestCommits -project $project -iid $iid

  [string[]] $authors = @()
  foreach ($commit in $commits) {
    if ($authors -notcontains $commit.author_email) {
      $authors += $commit.author_email
    }
  }

  return $authors
}

# get commits of a merge request
function Get-GitlabMergeRequestCommits( [Parameter(Mandatory=$true)] [string] $project
                                      , [Parameter(Mandatory=$true)] [string] $iid
                                      )
{
  [string] $GAPI_MERGE_IID_COMMITS = "$CI_API_V4_URL/projects/$project/merge_requests/$iid/commits"
  return @(Invoke-RestMethod -headers $GLPT -uri $GAPI_MERGE_IID_COMMITS -method GET)
}

# get merge request with changes
function Get-GitlabMergeRequestChanges( [Parameter(Mandatory=$true)] [string] $project
                                      , [Parameter(Mandatory=$true)] [string] $iid
                                      )
{
  [string] $GAPI_MERGE_IID_CHANGES = "$CI_API_V4_URL/projects/$project/merge_requests/$iid/changes"
  return (Invoke-RestMethod -headers $GLPT -uri $GAPI_MERGE_IID_CHANGES -method GET)
}

# get merge request changes for a specific path
function Get-GitlabMergeRequestDiff( [Parameter(Mandatory=$true)] [string] $project
                                   , [Parameter(Mandatory=$true)] [string] $iid
                                   , [Parameter(Mandatory=$true)] [string] $path
                                   )
{
  $mr = Get-GitlabMergeRequestChanges -project $project -iid $iid
  foreach ($change in $mr.changes) {
    if ($change.new_path -eq $path) {
      return $change.diff
    }
  }

  return ''
}

# delete older merge request pipelines
function Remove-GitlabMergeRequestPipelines( [Parameter(Mandatory=$true)] [string] $project
                                           , [Parameter(Mandatory=$true)] [string] $iid
                                           , [Parameter(Mandatory=$true)] [int]    $keep
                                           )
{
  [string] $GAPI_MERGE_IID_PIPELINES = "$CI_API_V4_URL/projects/$project/merge_requests/$iid/pipelines?per_page=100"
  $mrppl = @(Invoke-WebRequestContentToJson -headers $GLPT -uri $GAPI_MERGE_IID_PIPELINES)

  if (-not ($keep -lt $mrppl.Count)) { return }

  $mr = Get-GitlabMergeRequest -project $project -iid $iid
  foreach ($index in ($mrppl.Count-1)..$keep) {
    $pipeline = $mrppl[$index]

    if ($pipeline.id -ne $mr.head_pipeline.id) {
      Remove-GitlabPipeline -project $project -id $pipeline.id
      Write-Host -NoNewline '.'
    }
  }
  Write-Host ' '
}