mcpField
A
ado

ado

github.com/jimweller开发69 次安装
代码仓库

关于 ado

使用 `az` CLI 和 REST API 操作 Azure DevOps 资源的编码技能。涵盖仓库、拉取请求、服务连接、Agent 池与 Agent 等流程,并说明如何解析远程地址和使用环境默认值。

主要功能

  1. 01管理 Azure DevOps 项目与仓库
  2. 02创建和更新拉取请求
  3. 03读取和发布 PR 评论
  4. 04管理服务连接
  5. 05查看 Agent 池与 Agent

适用场景

  1. 01列出项目中的仓库
  2. 02审查或完成拉取请求
  3. 03检查服务连接配置
  4. 04查找在线构建 Agent

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jimweller/clanker-skills --skill ado

安装后在 Claude Code 中输入/ado确认已加载。

详细介绍

STARTER_CHARACTER = 🔷

Azure DevOps 运维

使用 az CLI 和 ADO REST API 与 Azure DevOps 资源交互。

前置条件

身份验证:始终设置 $AZURE_DEVOPS_EXT_PAT。无需登录。

仓库操作需要 ADO 远程地址(dev.azure.com 或 visualstudio.com)。

默认组织和项目

Shell 环境通过 $ADO_DEFAULT_ORG 和 $ADO_DEFAULT_PROJECT 提供默认值。当无法从 git 远程地址解析组织或项目,或在 git 仓库之外操作时,将这些值用作回退值。

org="${org:-$ADO_DEFAULT_ORG}"
project="${project:-$ADO_DEFAULT_PROJECT}"

通过 --org "https://dev.azure.com/${org}" 和 --project "$project" 将它们传递给 az 命令。

URL 解析

Azure DevOps 远程地址有三种格式。解析远程 URL 中的组织、项目和仓库,以构造网页 URL 并将参数传递给 az 命令。

remote_url=$(git remote get-url origin 2>/dev/null)
格式 1:dev.azure.com HTTPS

模式:https://dev.azure.com/{org}/{project}/_git/{repo} 或 https://{user}@dev.azure.com/{org}/{project}/_git/{repo}

clean_url=$(echo "$remote_url" | sed 's|https://[^@]*@|https://|')
org=$(echo "$clean_url" | sed -n 's|https://dev\.azure\.com/\([^/]*\)/.*|\1|p')
project=$(echo "$clean_url" | sed -n 's|https://dev\.azure\.com/[^/]*/\([^/]*\)/_git/.*|\1|p')
repo=$(echo "$clean_url" | sed -n 's|.*/_git/\(.*\)|\1|p')
格式 2:visualstudio.com

模式:https://{org}.visualstudio.com/DefaultCollection/{project}/_git/{repo}

clean_url=$(echo "$remote_url" | sed 's|https://[^@]*@|https://|')
org=$(echo "$clean_url" | sed -n 's|^https://\([^.]*\)\..*|\1|p')
project=$(echo "$clean_url" | sed -n 's|.*/DefaultCollection/\([^/]*\)/_git/.*|\1|p')
repo=$(echo "$clean_url" | sed -n 's|.*/_git/\(.*\)|\1|p')
格式 3:SSH

模式:[email protected]:v3/{org}/{project}/{repo}

org=$(echo "$remote_url" | sed -n 's|git@ssh\.dev\.azure\.com:v3/\([^/]*\)/.*|\1|p')
project=$(echo "$remote_url" | sed -n 's|git@ssh\.dev\.azure\.com:v3/[^/]*/\([^/]*\)/.*|\1|p')
repo=$(echo "$remote_url" | sed -n 's|.*[^/]*/\([^/]*\)$|\1|p')
构造网页 URL

对项目名称中的空格进行 URL 编码(%20),然后构造:

base_url=https://dev.azure.com/{org}/{project}/_git/{repo}
pr_url={base_url}/pullrequest/{pr_id}

在 macOS 上,使用 open "$url" 打开 URL。


浏览

打开仓库

解析远程 URL,构造网页 URL 并打开。

open "https://dev.azure.com/${org}/${project}/_git/${repo}"
打开 PR
open "https://dev.azure.com/${org}/${project}/_git/${repo}/pullrequest/${pr_id}"

项目

列出项目
az devops project list --organization "https://dev.azure.com/${org}" --query "value[].{Name:name,ID:id,State:state}" -o table
显示项目详情
az devops project show --project "$project" --organization "https://dev.azure.com/${org}"
获取项目 ID
az devops project show --project "$project" --organization "https://dev.azure.com/${org}" --query id -o tsv

仓库

创建
az repos create \
  --name "<repo-name>" \
  --project "$project" \
  --org "https://dev.azure.com/${org}" \
  --output table \
  --query "{Name:name,Project:project.name,URL:webUrl}"
删除

步骤 1:查找仓库 ID。

repo_id=$(az repos show \
  --repository "<repo-name>" \
  --project "$project" \
  --query id --output tsv)

步骤 2:按 ID 删除。

az repos delete \
  --id "$repo_id" \
  --project "$project" \
  --yes
列出
az repos list --organization "https://dev.azure.com/${org}" --project "$project" --query "[].{Name:name,ID:id,DefaultBranch:defaultBranch}" -o table
显示
az repos show --repository <REPO_NAME> --organization "https://dev.azure.com/${org}" --project "$project"
获取克隆 URL
az repos show --repository <REPO_NAME> --organization "https://dev.azure.com/${org}" --project "$project" --query remoteUrl -o tsv

拉取请求(CLI)

所有 PR 命令都使用 JMESPath 查询来输出结构化结果。base_url 变量由解析后的远程 URL 构造(参见上文的 URL 解析)。

创建
az repos pr create \
  "$@" \
  --output table \
  --query "{PR:pullRequestId,Status:status,URL:join(\`\`,[\ \`${base_url}\`,\`/pullrequest/\`,to_string(pullRequestId)])}"

常用标志:--title、--description、--source-branch、--target-branch、--reviewers、--auto-complete、--draft

列出
az repos pr list \
  --output table \
  --query "[].{PR:pullRequestId,Title:title,Creator:createdBy.uniqueName,Created:creationDate,URL:join(\`\`,[\`${base_url}\`,\`/pullrequest/\`,to_string(pullRequestId)])}"

常用标志:--status、--creator、--reviewer、--source-branch、--target-branch

显示
az repos pr show \
  --id <pr-id> \
  --output table \
  --query "{PR:pullRequestId,Title:title,Creator:createdBy.uniqueName,Created:creationDate,URL:join(\`\`,[\`${base_url}\`,\`/pullrequest/\`,to_string(pullRequestId)])}"
更新
az repos pr update \
  --id <pr-id> \
  --output table \
  --query "{PR:pullRequestId,Status:status,URL:join(\`\`,[\`${base_url}\`,\`/pullrequest/\`,to_string(pullRequestId)])}"

常用标志:--title、--description、--status、--auto-complete、--draft

完成

通过将状态设为 completed 并绕过策略来完成(合并)拉取请求。

az repos pr update \
  --id <pr-id> \
  --status completed \
  --bypass-policy true \
  --output table \
  --query "{PR:pullRequestId,Status:status,URL:join(\`\`,[\`${base_url}\`,\`/pullrequest/\`,to_string(pullRequestId)])}"

其他标志:--merge-commit-message

完成操作为异步操作。响应中的状态仍会显示为 active。随后运行 az repos pr show --id <pr-id>,确认 status: completed 且 mergeStatus: succeeded。

放弃
az repos pr update \
  --id <pr-id> \
  --status abandoned \
  --output table \
  --query "{PR:pullRequestId,Status:status,URL:join(\`\`,[\`${base_url}\`,\`/pullrequest/\`,to_string(pullRequestId)])}"
投票/批准
az repos pr set-vote \
  --id <pr-id> \
  --vote approve

投票后,显示 PR 详情:

az repos pr show \
  --id <pr-id> \
  --output table \
  --query "{PR:pullRequestId,Title:title,Creator:createdBy.uniqueName,Created:creationDate,URL:join(\`\`,[\`${base_url}\`,\`/pullrequest/\`,to_string(pullRequestId)])}"

有效的投票值:approve、approve-with-suggestions、wait-for-author、reject、reset


PR 评论和讨论串(REST API)

发布评论
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pullRequests/<PR_ID>/threads?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '{
    "comments": [{"parentCommentId": 0, "content": "Comment text here", "commentType": 1}],
    "status": 1
  }'
获取评论/讨论串
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pullRequests/<PR_ID>/threads?api-version=7.0" \
  | jq '.value[] | {id: .id, status: .status, comments: [.comments[] | {author: .author.displayName, content: .content}]}'
搜索包含指定文本的评论
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pullRequests/<PR_ID>/threads?api-version=7.0" \
  | jq '.value[].comments[] | select(.content | contains("SEARCH_TEXT")) | {author: .author.displayName, content: .content}'

服务连接

列出
az devops service-endpoint list --organization "https://dev.azure.com/${org}" --project "$project" --query "[].{Name:name,Type:type,ID:id,Scheme:authorization.scheme}" -o table
显示详情
az devops service-endpoint show --id <ENDPOINT_ID> --organization "https://dev.azure.com/${org}" --project "$project"
按名称获取
az devops service-endpoint list --organization "https://dev.azure.com/${org}" --project "$project" --query "[?name=='<NAME>']" -o json
删除
az devops service-endpoint delete --id <ENDPOINT_ID> --organization "https://dev.azure.com/${org}" --project "$project" --yes
获取 WIF 详情(REST API)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/serviceendpoint/endpoints?api-version=7.1" \
  | jq ".value[] | select(.name == \"<NAME>\") | {issuer: .authorization.parameters.workloadIdentityFederationIssuer, subject: .authorization.parameters.workloadIdentityFederationSubject}"
授权所有管道(REST API)
SC_ID=$(az devops service-endpoint list --organization "https://dev.azure.com/${org}" --project "$project" --query "[?name=='<NAME>'].id" -o tsv)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X PATCH \
  "https://dev.azure.com/${org}/${project}/_apis/pipelines/pipelinePermissions/endpoint/${SC_ID}?api-version=7.1-preview.1" \
  -H "Content-Type: application/json" \
  -d '{"allPipelines": {"authorized": true}}'

Agent 池

池属于组织级别,可在多个项目间共享。

列出所有
az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[].{Name:name,ID:id,PoolType:poolType,IsHosted:isHosted,Size:size}" -o table
按名称模式列出
az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?contains(name, '<PATTERN>')].{Name:name,ID:id,PoolType:poolType,Size:size}" -o table
按名称获取池 ID
az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?name=='<POOL_NAME>'].id" -o tsv
显示详情
az pipelines pool show --organization "https://dev.azure.com/${org}" --pool-id <POOL_ID>
创建(REST API)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/_apis/distributedtask/pools?api-version=7.1" \
  -H "Content-Type: application/json" \
  -d '{"name": "<POOL_NAME>", "poolType": "automation", "isHosted": false, "autoProvision": false}'
删除(REST API)
POOL_ID=$(az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?name=='<POOL_NAME>'].id" -o tsv)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X DELETE \
  "https://dev.azure.com/${org}/_apis/distributedtask/pools/${POOL_ID}?api-version=7.1"

Agent

Agent 在池中运行。

列出池中的 Agent
POOL_ID=$(az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?name=='<POOL_NAME>'].id" -o tsv)
az pipelines agent list --organization "https://dev.azure.com/${org}" --pool-id $POOL_ID --query "[].{Name:name,ID:id,Status:status,Version:version}" -o table
列出在线 Agent
POOL_ID=$(az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?name=='<POOL_NAME>'].id" -o tsv)
az pipelines agent list --organization "https://dev.azure.com/${org}" --pool-id $POOL_ID --query "[?status=='online'].{Name:name,ID:id,Version:version}" -o table
显示 Agent 详情
az pipelines agent show --organization "https://dev.azure.com/${org}" --pool-id <POOL_ID> --agent-id <AGENT_ID>
统计在线 Agent 数量
POOL_ID=$(az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?name=='<POOL_NAME>'].id" -o tsv)
az pipelines agent list --organization "https://dev.azure.com/${org}" --pool-id $POOL_ID --query "[?status=='online']" -o tsv | wc -l

队列

队列是项目级别对组织级别池的引用。管道授权针对队列而非池。

列出所有
az pipelines queue list --organization "https://dev.azure.com/${org}" --project "$project" --query "[].{Name:name,QueueID:id,PoolID:pool.id}" -o table
按名称模式列出
az pipelines queue list --organization "https://dev.azure.com/${org}" --project "$project" --query "[?contains(name, '<PATTERN>')].{Name:name,QueueID:id,PoolID:pool.id}" -o table
获取池对应的队列 ID
POOL_ID=$(az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?name=='<POOL_NAME>'].id" -o tsv)
az pipelines queue list --organization "https://dev.azure.com/${org}" --project "$project" --query "[?pool.id==\`${POOL_ID}\`].id" -o tsv
为池创建队列(REST API)
POOL_ID=$(az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?name=='<POOL_NAME>'].id" -o tsv)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/${project}/_apis/distributedtask/queues?api-version=7.1" \
  -H "Content-Type: application/json" \
  -d '{"name": "<QUEUE_NAME>", "pool": {"id": '${POOL_ID}'}}'
为所有管道授权队列(REST API)
QUEUE_ID=$(az pipelines queue list --organization "https://dev.azure.com/${org}" --project "$project" --query "[?name=='<QUEUE_NAME>'].id" -o tsv)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X PATCH \
  "https://dev.azure.com/${org}/${project}/_apis/pipelines/pipelinePermissions/queue/${QUEUE_ID}?api-version=7.1-preview.1" \
  -H "Content-Type: application/json" \
  -d '{"allPipelines": {"authorized": true}}'
为特定管道授权队列(REST API)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X PATCH \
  "https://dev.azure.com/${org}/${project}/_apis/pipelines/pipelinePermissions/queue/<QUEUE_ID>?api-version=7.1-preview.1" \
  -H "Content-Type: application/json" \
  -d '{"pipelines": [{"id": <PIPELINE_ID>, "authorized": true}]}'

管道

列出全部
az pipelines list --organization "https://dev.azure.com/${org}" --project "$project" --query "[].{Name:name,ID:id,Folder:folder,QueueStatus:queueStatus}" -o table
按名称模式列出
az pipelines list --organization "https://dev.azure.com/${org}" --project "$project" --name "<PATTERN>*" --query "[].{Name:name,ID:id}" -o table
显示详情
az pipelines show --organization "https://dev.azure.com/${org}" --project "$project" --name <PIPELINE_NAME>
根据名称获取管道 ID
az pipelines list --organization "https://dev.azure.com/${org}" --project "$project" --query "[?name=='<PIPELINE_NAME>'].id" -o tsv
创建
az pipelines create \
  --organization "https://dev.azure.com/${org}" \
  --project "$project" \
  --name <PIPELINE_NAME> \
  --repository <REPO_NAME> \
  --repository-type tfsgit \
  --branch main \
  --yml-path azure-pipelines.yml \
  --skip-first-run
删除
PIPELINE_ID=$(az pipelines list --organization "https://dev.azure.com/${org}" --project "$project" --query "[?name=='<PIPELINE_NAME>'].id" -o tsv)
az pipelines delete --id $PIPELINE_ID --organization "https://dev.azure.com/${org}" --project "$project" --yes

管道运行(构建)

运行管道
az pipelines run --organization "https://dev.azure.com/${org}" --project "$project" --name <PIPELINE_NAME>
运行并获取运行 ID
RUN_ID=$(az pipelines run --organization "https://dev.azure.com/${org}" --project "$project" --name <PIPELINE_NAME> --query id -o tsv)
echo "Run ID: $RUN_ID"
使用变量运行
az pipelines run --organization "https://dev.azure.com/${org}" --project "$project" --name <PIPELINE_NAME> --variables "VAR1=value1" "VAR2=value2"
列出最近的运行
az pipelines runs list --organization "https://dev.azure.com/${org}" --project "$project" --top 10 --query "[].{ID:id,Pipeline:definition.name,Status:status,Result:result,StartTime:startTime}" -o table
列出特定管道的运行
PIPELINE_ID=$(az pipelines list --organization "https://dev.azure.com/${org}" --project "$project" --query "[?name=='<PIPELINE_NAME>'].id" -o tsv)
az pipelines runs list --organization "https://dev.azure.com/${org}" --project "$project" --pipeline-ids $PIPELINE_ID --query "[].{ID:id,Status:status,Result:result}" -o table
显示运行状态
az pipelines runs show --organization "https://dev.azure.com/${org}" --project "$project" --id <RUN_ID> --query "{Status:status,Result:result}" -o json
监视运行直至完成
RUN_ID=<RUN_ID>
while true; do
  STATUS=$(az pipelines runs show --organization "https://dev.azure.com/${org}" --project "$project" --id $RUN_ID --query status -o tsv)
  RESULT=$(az pipelines runs show --organization "https://dev.azure.com/${org}" --project "$project" --id $RUN_ID --query result -o tsv)
  echo "Status: $STATUS, Result: ${RESULT:-pending}"
  if [ "$STATUS" == "completed" ]; then break; fi
  sleep 10
done

构建日志

获取构建时间线(阶段/作业)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/build/builds/<RUN_ID>/timeline?api-version=7.1" \
  | jq '.records[] | {name: .name, type: .type, state: .state, result: .result}'
列出构建日志
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/build/builds/<RUN_ID>/logs?api-version=7.1" \
  | jq '.value[] | {id: .id, type: .type, lineCount: .lineCount}'
下载指定日志
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/build/builds/<RUN_ID>/logs/<LOG_ID>?api-version=7.1"
获取失败步骤的日志
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/build/builds/<RUN_ID>/timeline?api-version=7.1" \
  | jq '.records[] | select(.result == "failed") | {name: .name, log: .log.url}'

Git 操作(REST API)

无需本地克隆即可通过编程方式执行 Git 工作流(分支、提交、推送)。

按名称获取存储库
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>?api-version=7.0" \
  | jq '{id: .id, name: .name, defaultBranch: .defaultBranch}'
获取分支引用
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/refs?filter=heads/main&api-version=7.0" \
  | jq '.value[] | {name: .name, objectId: .objectId}'
获取主分支提交 SHA
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/refs?filter=heads/main&api-version=7.0" \
  | jq -r '.value[0].objectId'
从主分支创建分支
MAIN_SHA=$(curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/refs?filter=heads/main&api-version=7.0" \
  | jq -r '.value[0].objectId')

curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/refs?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '[{"name": "refs/heads/<BRANCH_NAME>", "oldObjectId": "0000000000000000000000000000000000000000", "newObjectId": "'${MAIN_SHA}'"}]'
删除分支
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/refs?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '[{"name": "refs/heads/<BRANCH_NAME>", "oldObjectId": "<LAST_COMMIT_SHA>", "newObjectId": "0000000000000000000000000000000000000000"}]'
推送提交(添加/编辑文件)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pushes?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '{
    "refUpdates": [{"name": "refs/heads/<BRANCH_NAME>", "oldObjectId": "<BASE_COMMIT_SHA>"}],
    "commits": [{
      "comment": "Commit message here",
      "changes": [
        {
          "changeType": "edit",
          "item": {"path": "/path/to/file"},
          "newContent": {"content": "file content here", "contentType": "rawtext"}
        }
      ]
    }]
  }'
推送初始提交(新存储库)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pushes?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '{
    "refUpdates": [{"name": "refs/heads/main", "oldObjectId": "0000000000000000000000000000000000000000"}],
    "commits": [{
      "comment": "Initial commit",
      "changes": [
        {
          "changeType": "add",
          "item": {"path": "/README.md"},
          "newContent": {"content": "# Repo Title", "contentType": "rawtext"}
        }
      ]
    }]
  }'
推送 Base64 编码的内容
CONTENT_B64=$(echo -n "file contents here" | base64)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pushes?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '{
    "refUpdates": [{"name": "refs/heads/<BRANCH>", "oldObjectId": "<SHA>"}],
    "commits": [{
      "comment": "Add file",
      "changes": [{
        "changeType": "add",
        "item": {"path": "/file.txt"},
        "newContent": {"content": "'${CONTENT_B64}'", "contentType": "base64encoded"}
      }]
    }]
  }'

拉取请求(REST API)

需要更多控制时,可替代 az repos pr 命令。

创建
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pullrequests?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceRefName": "refs/heads/<SOURCE_BRANCH>",
    "targetRefName": "refs/heads/main",
    "title": "PR Title",
    "description": "PR description"
  }' | jq '{pullRequestId: .pullRequestId, status: .status}'
获取详情
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pullRequests/<PR_ID>?api-version=7.0" \
  | jq '{id: .pullRequestId, title: .title, status: .status, sourceRefName: .sourceRefName}'
列出
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pullrequests?api-version=7.0" \
  | jq '.value[] | {id: .pullRequestId, title: .title, status: .status}'
放弃
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X PATCH \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pullRequests/<PR_ID>?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '{"status": "abandoned"}'
完成(合并)
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X PATCH \
  "https://dev.azure.com/${org}/${project}/_apis/git/repositories/<REPO_NAME>/pullRequests/<PR_ID>?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "completed",
    "lastMergeSourceCommit": {"commitId": "<LAST_COMMIT_SHA>"},
    "completionOptions": {"deleteSourceBranch": true, "mergeStrategy": "squash"}
  }'

代码搜索(REST API)

跨存储库搜索代码。使用 almsearch.dev.azure.com 主机,而非 dev.azure.com。

在项目中搜索
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://almsearch.dev.azure.com/${org}/${project}/_apis/search/codesearchresults?api-version=7.1" \
  -H "Content-Type: application/json" \
  -d '{"searchText":"<TEXT>","$top":25}' \
  | jq '{count: .count, results: [.results[] | {repo: .repository.name, path: .path}]}'
使用筛选条件跨组织搜索
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://almsearch.dev.azure.com/${org}/_apis/search/codesearchresults?api-version=7.1" \
  -H "Content-Type: application/json" \
  -d '{
    "searchText":"<TEXT>",
    "filters":{"Project":["<PROJECT>"],"Repository":["<REPO>"],"Branch":["main"],"Path":["/src"]},
    "$top":25
  }' \
  | jq '.results[] | {project: .project.name, repo: .repository.name, path: .path}'

可用的筛选键:Project、Repository、Branch、Path、CodeElement(例如 ["def","class"])。

搜索运算符

searchText 字段接受 ADO 搜索语法:

  • ext:go quiver-bolt -- 文件扩展名筛选
  • file:Makefile quiver-bolt -- 文件名筛选
  • def:MyFunction -- 函数/方法定义
  • "exact phrase" -- 精确匹配
  • foo AND bar、foo OR bar、foo NOT bar -- 布尔运算

Webhook(REST API)

列出服务挂钩
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" \
  "https://dev.azure.com/${org}/_apis/hooks/subscriptions?api-version=7.0" \
  | jq '.value[] | {id: .id, eventType: .eventType, consumerActionId: .consumerActionId, publisherInputs: .publisherInputs}'
为 PR 事件创建 Webhook
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X POST \
  "https://dev.azure.com/${org}/_apis/hooks/subscriptions?api-version=7.0" \
  -H "Content-Type: application/json" \
  -d '{
    "publisherId": "tfs",
    "eventType": "git.pullrequest.created",
    "resourceVersion": "1.0",
    "consumerId": "webHooks",
    "consumerActionId": "httpRequest",
    "publisherInputs": {
      "projectId": "<PROJECT_ID>",
      "repository": "<REPO_ID>"
    },
    "consumerInputs": {
      "url": "<WEBHOOK_URL>"
    }
  }'
删除 Webhook
curl -s -u ":${AZURE_DEVOPS_EXT_PAT}" -X DELETE \
  "https://dev.azure.com/${org}/_apis/hooks/subscriptions/<SUBSCRIPTION_ID>?api-version=7.0"

诊断

检查 ADO 基础设施运行状况
echo "=== Service Connections ==="
az devops service-endpoint list --organization "https://dev.azure.com/${org}" --project "$project" --query "[].{Name:name,Type:type}" -o table

echo ""
echo "=== Agent Pools ==="
az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[].{Name:name,Size:size,IsHosted:isHosted}" -o table

echo ""
echo "=== Queues ==="
az pipelines queue list --organization "https://dev.azure.com/${org}" --project "$project" --query "[].{Name:name,QueueID:id}" -o table
检查池中的 Agent
POOL_ID=$(az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?name=='<POOL_NAME>'].id" -o tsv)
if [ -n "$POOL_ID" ]; then
  az pipelines agent list --organization "https://dev.azure.com/${org}" --pool-id $POOL_ID --query "[].{Name:name,Status:status}" -o table
fi
等待 Agent 上线
POOL_ID=$(az pipelines pool list --organization "https://dev.azure.com/${org}" --query "[?name=='<POOL_NAME>'].id" -o tsv)
MAX_WAIT=300
ELAPSED=0
while [ $ELAPSED -lt $MAX_WAIT ]; do
  ONLINE=$(az pipelines agent list --organization "https://dev.azure.com/${org}" --pool-id $POOL_ID --query "[?status=='online']" -o tsv | wc -l)
  if [ "$ONLINE" -gt 0 ]; then
    echo "Found $ONLINE online agent(s)"
    break
  fi
  echo "Waiting for agents... ($ELAPSED/${MAX_WAIT}s)"
  sleep 10
  ELAPSED=$((ELAPSED + 10))
done

关键概念

池与队列
  • 池是组织级别的(在多个项目间共享)
  • 队列是项目级别的池引用
  • 管道授权针对的是队列,而非池
工作负载身份联合

服务连接使用 WIF(OIDC)。ADO 中不存储任何密钥。联合凭据在 App Registration 中管理。

REST API 与 CLI

某些操作需要使用 REST API(没有对应的 CLI 命令):

  • 创建具有特定设置的池
  • 为管道授权队列
  • 查询服务连接中的 WIF 主体
  • Git 推送操作(分支、提交)
  • PR 评论线程
  • Webhook

评论

支持 Markdown