Azure DevOps:「TF401028: The reference 'refs/heads/…' has already been updated by another client, so you cannot update it. Please try again.」
Azure Repos は、ブランチが今も言われたとおりのコミットを指しているときにだけ、ブランチを動かします。今回はそうではありませんでした。読んだ後に何かがブランチを動かしたか、作ろうとしたブランチがもうあったかです。何も変わっていません。ブランチの今のコミットを読み、その上でやり直します。
返ってくるもの全体
REST API の POST …/_apis/git/repositories/{repository}/pushes では、公開の paklo の issue の報告によるとこうでした。
409 Conflict
{"$id":"1","innerException":null,"message":"TF401028: The reference 'refs/heads/dependabot/nuget/…' has already been updated by another client, so you cannot update it. Please try again.","typeName":"Microsoft.TeamFoundation.Git.Server.GitReferenceStaleException, Microsoft.TeamFoundation.Git.Server","typeKey":"GitReferenceStaleException","errorCode":0,"eventId":3000}
git push では、複数のリリースが同時に1つの Wiki を書き換えた公開の報告にこうあります。
! [remote rejected] wikiMaster -> wikiMaster (TF401028: The reference 'refs/heads/wikiMaster' has already been updated by another client, so you cannot update it. Please try again.) error: failed to push some refs to 'https://…visualstudio.com/…/_git/….wiki'
コードで見分けるなら、文面でなく typeKey が GitReferenceStaleException かどうかを見ます。
ブランチが動かされなかった理由
push の中のブランチの更新は、それぞれ oldObjectId、つまりブランチが今指しているコミットを持っています。まだ無いブランチなら、Microsoft の Pushes - Create(英語)の例は0を40個送っています。Azure Repos が変更するときにこの ID が合わなければ、断ります。Refs - Update Refs(英語)のページは同じ場合を staleOldObjectId と呼び、いちばんありそうなのは更新の競争に負けたとき、としています。実際には次のどれかです。
- 別の push が先に入った。同僚、パイプライン、同じ道具のもう1つの実行(上の、1つの Wiki を書き換えたリリースのように)。
- 道具が前にブランチを読み、そのときのコミットの ID を持ち続けた。たとえば、ブランチを作ってからファイルを足すまでの間。
- 0を40個送ったのに、ブランチがもうある。前の実行で作ったものが多いです。公開の autoupdate のプルリクエストによると、同じ場合に GitHub は 422「Reference already exists」、GitLab は 400「Branch already exists」、Azure DevOps は 409 と TF401028 を返しました。
REST API を呼ぶ道具での直し方
push の直前に、ブランチのコミットを読みます。
GET https://dev.azure.com/{organization}/{project}/_apis/git/repositories/{repository}/refs?filter=heads/main&api-version=7.1
コミットは value[].objectId にあります。公開のリポジトリで試して分かったことが2つあります。filter は名前の先頭で一致するので、heads/mas で refs/heads/master が返りました。また、filter=refs/heads/master では何も返りませんでした。refs/ は付けずに送り、名前がちょうど refs/heads/<ブランチ> のものを選びます。
その ID を oldObjectId にして送ります。ファイルの編集のように、ブランチの中身によって変わる変更なら、そのコミットから作り直します。それから push します。また TF401028 が返ったら、読み直して push し直します。同じ古い ID をもう一度送っても、同じ理由で断られるだけです。
「無ければブランチを作る」なら、0を40個送った push に TF401028 が返ったときは、ブランチがもうある印として扱います。上のように読み、使い回すかどうかを決めます。autoupdate のプルリクエストも、この断りを「もうある」と読む形で直しました。
git push での直し方
git pull --rebase
git push
今のブランチの上に自分のコミットを載せ直します。同じブランチに同時に push する仕事が2つあると、何度でもぶつかります。順番に動くようにしてください。
作業項目で出る同じ番号
作業項目に受け付けないリンクを足したときにも、別の文面で TF401028 が返ります。microsoft/azure-devops-mcp の issue 1412 では、Wiki のページへのリンクを attributes.name を「Wiki」にして送ると、TF401028 Unrecognized Resource link で断られました。Azure DevOps が待っているのは「Wiki Page」です。こちらはブランチとは関係ありません。
IntelliJ IDEA や Rider から Azure DevOps のプルリクエストを作るなら
Azure DevOps Pull Requests は、Lesstep が作っている JetBrains IDE(2024.1 以降)と Android Studio のプラグインです。Git メニューに Azure DevOps のプルリクエストを作成… を足します。今いるブランチから、選ばなければリポジトリの既定のブランチへ、下書きにもできるプルリクエストを作ります。ブランチがまだ Azure DevOps に無ければそう伝えて、先に IDE の push の画面を開きます。プルリクエストの作成は Pro(月 US$1・30日間の無料試用つき)、受信箱は無料です。画面は日本語です。
Azure DevOps と Azure Repos は Microsoft Corporation の商標、IntelliJ IDEA と Rider は JetBrains s.r.o. の商標です。このページは Microsoft・JetBrains のいずれとも提携しておらず、承認も受けていません。
ほかに:完了するときの「TF401192」(プルリクエストでの同じ種類の断り) · 「TF401179: An active pull request for the source and target branch already exists」 · English