(2023/1/17追記:全然ちゃんとした環境で動いてなかったので大幅に書き直しました)
azure devopsの特定コミット間のコミット一覧の中から、それに紐づいた作業項目を取得するpythonコードです。
たぶんリリース情報を整理するのに活用できるはずです。
近しい提供されているAPIとして、 Build - Get Work Items Between Builds というのはあるのですが、こちらはBuild IDがないとだめなのと、「同リポジトリだけど、パイプラインIDが違うと動かない」という片手落ちだったので今回のものを作りました。
やりたいこと
- 特定の2コミット間の間にあるコミットから
- 含まれる作業項目を取り出し
- いい感じに整形して出力したい
できるとうれしいこと
リリースが捗ります。
個人的には2~3割は工数の削減につながります。
環境
- VSCode
- python 3.11.0
- azure devops
使用するライブラリ
- azure-devops
- rest apiはv6.0を使用
コード
概要
azure devops rest apiのうち、今回は
- Commits - Get Commits
- Git - Commits
- Work Items - Get Work Item
- Work Item Tracking - Work Items
を使用しています。
前者で特定コミット間のコミット一覧を取得し、その中に含まれるWork Item IDの詳細を、後者で取得しています。
一番わかりにくかったのが、コミットIDを示す「to」と「from」の意味合いが思っていたのと逆だったこと。
「from」が新しいほうのコミットIDを指すようです。
from_commit_id
とto_commit_id
ではまともに情報取れなかったので、from_date
とto_date
を使うように書き換えてます。
そのためコミットIDからコミット時刻を特定する処理を追加しています。
コード全文(全面書き直し)
import datetime from azure.devops.connection import Connection from msrest.authentication import BasicAuthentication import azure.devops.v6_0.git as git import azure.devops.v6_0.pipelines as pipe import azure.devops.v6_0.work_item_tracking as wit class Setup : def __init__(self, org_url, project, repository_id, pat, target_branch=None) : self.org_url = org_url self.project = project self.repos_id = repository_id self.pat = pat self.branch = target_branch def __get_date_from_commit(setup: Setup, gc : git.git_client.GitClient, commit_id) -> datetime.datetime : """__get_date_from_commit get date from commit. utc offset :9h """ commit : git.models.GitCommit = gc.get_commit( commit_id=commit_id, repository_id=setup.repos_id, project=setup.project) # コミットした人を特定 committer : git.models.GitUserDate = commit.committer # コミット時刻を取得(タイムゾーン合わせる) d : datetime.datetime = committer.date + datetime.timedelta(hours=9) # タイムゾーンの設定を削除 d = d.replace(tzinfo=None) return d def __find_pipeline_id_from_build_id(setup: Setup, pc: pipe.PipelinesClient, build_id) : """__find_pipeline_id_from_build_id """ # パイプライン一覧を取得 pipelines = pc.list_pipelines(setup.project) for pl in pipelines : pl : pipe.models.Pipeline # ビルド結果を取得 runs = pc.list_runs(setup.project, pipeline_id=pl.id) # build_idを探す for r in runs : r : pipe.models.Run if r.id == build_id : return pl.id return None def get_commit_id_from_build_id(setup: Setup, build_id) : credentials = BasicAuthentication('', setup.pat) connection = Connection(base_url=setup.org_url, creds=credentials) pc : pipe.PipelinesClient = connection.clients_v6_0.get_pipelines_client() pipeline_id = __find_pipeline_id_from_build_id(setup, pc, build_id) run: pipe.models.Run = pc.get_run(project=setup.project, pipeline_id=pipeline_id, run_id=build_id) return run.as_dict()["resources"]["repositories"]["__designer_repo"]["versiom"] def get_work_items_between_commits(setup : Setup, from_commit_id : str, to_commit_id : str) : credentials = BasicAuthentication('', setup.pat) connection = Connection(base_url=setup.org_url, creds=credentials) # GitClient gc : git.git_client.GitClient = connection.clients_v6_0.get_git_client() # Work Item Tracking witc : wit.work_item_tracking_client.WorkItemTrackingClient = connection.clients_v6_0.get_work_item_tracking_client() # get date from commit id from_date = __get_date_from_commit(setup, gc, from_commit_id) to_date = __get_date_from_commit(setup, gc, to_commit_id) # Git Get Commits criteria = git.models.GitQueryCommitsCriteria() criteria.from_date = from_date criteria.to_date = to_date criteria.top = 10000 # 取得対象のブランチを指定 if setup.branch != None : criteria.item_version = git.models.GitVersionDescriptor(setup.branch) # 紐づいている作業項目を取得 criteria.include_work_items = True criteria.history_mode = "firstParent" commits = gc.get_commits( repository_id=setup.repos_id, search_criteria=criteria, project=setup.project ) result = [] for commit in commits : commit : git.models.GitCommitRef dic_commit = commit.as_dict() if dic_commit["work_items"] == None : continue commit_id = dic_commit["commit_id"] for wi in dic_commit["work_items"] : # コミットに紐づいた作業項目の内容を出力 id = wi["id"] work_item : wit.models.WorkItem = witc.get_work_item(id, project=setup.project) wi_dict = work_item.as_dict() result.append(wi_dict) return result
使い方
シンプルです。必要な情報を渡すだけです。
setup = Setup( org_url="https://dev.azure.com/<your organization>", project="<your project>", repository_id="<your repository>", target_branch="<target branch>(option)", pat="<your pat>") ret = azure_test.get_work_items_between_commits( setup=setup, from_commit_id="from commit id", to_commit_id="to commit id", )
おまけ(取得した作業項目から解決済みバグ一覧を取得)
for r in ret : id = r["id"] title = r["fields"]["System.Title"] url = r["url"] print(f"{id}\t{title}\t{url}")
おしまい
あるものを組み合わせて作業を効率化するのって楽しいですね。