Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e96fa5c
Feat: pace: normal の new sprint が承認ゲート 2 と本番のステージを書き出す(#1914)
takemi-ohama Oct 25, 2026
b68a1cf
Docs: normal の承認ゲート 2 の後は目録の本番のステージを打つ手順にする(#1914)
takemi-ohama Oct 25, 2026
e6d34cd
fix(ndf): fast / auto の昇格は、導入の確認で確かめたコミットのときだけマージする(#1529)
takemi-ohama Oct 25, 2026
bd48668
docs(ndf): pace.md の昇格のプランに、確かめたコミットとの照合と止まった後の続きを書く(#1529)
takemi-ohama Oct 25, 2026
9bf8ff9
feat(cross-refactoring): init で範囲テストのテンプレートを試走し、ファイルを受けなければ止める(#1630)
takemi-ohama Oct 25, 2026
b0c9fcf
docs(development-workflow): {paths} に入るパスの種類と、ファイルを受けないランナーの書き方を宣言の契約…
takemi-ohama Oct 25, 2026
e289596
cross-review: テストの無い宣言で最終スイープを止めず、件数に継続的統合の判定を載せる(#1593)
takemi-ohama Oct 25, 2026
58809cf
supervise: 駆動の stopped で止まった理由に元の終了コードを添える(#1593)
takemi-ohama Oct 25, 2026
5116d6c
cross-review: 駆動の件数の ci_counts を counts の中で読む(#1593)
takemi-ohama Oct 25, 2026
8cbf86a
refactor(cross-refactoring): template_check の関数名を他と重ならない名へ変える(#1630)
takemi-ohama Oct 25, 2026
ef582e6
Update: 宣言の整形を当てる(lint-fix)
takemi-ohama Oct 25, 2026
896bf2e
fix(ndf): merged-steps.py を 500 行に収める(#1529)
takemi-ohama Oct 25, 2026
eb4cd8a
docs(ndf): 確定仕様の昇格の経路を、compare_approved ではなく --expect-head-from の照合に直…
takemi-ohama Oct 25, 2026
9ea2ab0
Merge pull request #2122 from devbasex/feat/issue-1914-m1914
takemi-ohama Oct 25, 2026
428b249
Merge pull request #2123 from devbasex/feat/issue-1630-m1914
takemi-ohama Oct 25, 2026
def295b
Merge pull request #2124 from devbasex/feat/issue-1529-m1914
takemi-ohama Oct 25, 2026
48ea62b
docs: テストの無い宣言で最終スイープが探し方へ進むことと、止まった理由に元の終了コードが添うことを仕様へ写す(#1593)
takemi-ohama Oct 25, 2026
88b9337
docs: 修正と輪番の文書を分割の基準(500 行)に収める(#1593)
takemi-ohama Oct 25, 2026
2ffc243
docs: 追加した行から課題番号の由来を外す
takemi-ohama Oct 25, 2026
3d464c5
Merge pull request #2125 from devbasex/feat/issue-1593-m1914
takemi-ohama Oct 25, 2026
f237a10
Update: .ndf/project.json を解析器の版 2 で解析し直す
takemi-ohama Oct 25, 2026
977507c
Fix: test_usage_capture の偽の CLI に呼び出し元の環境変数を持ち込まない
takemi-ohama Oct 25, 2026
36acf9f
Merge pull request #2136 from devbasex/fix/usage-capture-env
takemi-ohama Oct 25, 2026
65055e4
Refactor: extract_method — plugins/ndf/scripts/lib/approved_commit.py…
takemi-ohama Oct 25, 2026
e07fb5d
Refactor: extract_method — plugins/ndf/scripts/merged_lib/merge.py#me…
takemi-ohama Oct 25, 2026
7fbcb5b
Refactor: extract_method — plugins/ndf/skills/cross-refactoring/scrip…
takemi-ohama Oct 25, 2026
7513b0f
Refactor: consolidate_duplication — plugins/ndf/scripts/lib/approved_…
takemi-ohama Oct 25, 2026
eb52dca
Refactor: extract_method — plugins/ndf/scripts/supervise_lib/sprint.p…
takemi-ohama Oct 25, 2026
b6f6521
Refactor: consolidate_duplication — plugins/ndf/scripts/supervise_lib…
takemi-ohama Oct 25, 2026
f78c2a6
Refactor: introduce_named_constant — plugins/ndf/skills/cross-refacto…
takemi-ohama Oct 25, 2026
c9dd3a9
Refactor: extract_method — plugins/ndf/scripts/lib/test_strategy.py#p…
takemi-ohama Oct 25, 2026
2225d60
Refactor: flatten_conditional — plugins/ndf/scripts/supervise_lib/eng…
takemi-ohama Oct 25, 2026
fa843e2
Refactor: introduce_parameter_object — plugins/ndf/skills/cross-refac…
takemi-ohama Oct 25, 2026
c3bd9dc
Fix: 検証の失敗を直す — plugins/ndf/scripts/lib/approved_commit.py#verified_sha
takemi-ohama Oct 25, 2026
0621051
Fix: 検証の失敗を直す — plugins/ndf/scripts/lib/approved_commit.py#compare_ap…
takemi-ohama Oct 25, 2026
c1ca4ff
Fix: 検証の失敗を直す — plugins/ndf/scripts/supervise_lib/sprint_waves.py#use…
takemi-ohama Oct 25, 2026
f561d2a
Fix: 検証の失敗を直す — plugins/ndf/scripts/supervise_lib/sprint_waves.py#use…
takemi-ohama Oct 25, 2026
d2ab411
Fix: 関門 2 の文に承認の記録(release-steps.py approve)を本番の前に打つ手順を足す
takemi-ohama Oct 25, 2026
e939ac4
Merge origin/develop into sprint/m1914
takemi-ohama Oct 25, 2026
2889d45
Fix: develop を取り込んだ後の CI の失敗を直す
takemi-ohama Oct 25, 2026
b830399
fix(ndf): 関門 2 の文の承認資料を元のリポジトリの下の絶対パスで示す (#1914)
takemi-ohama Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 44 additions & 33 deletions .ndf/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
{
"name": "python",
"frameworks": [],
"files": 633,
"files": 917,
"version": "3.11"
},
{
"name": "shell",
"frameworks": [],
"files": 77
"files": 79
},
{
"name": "javascript",
"frameworks": [],
"files": 2
"files": 8
}
],
"test": {
Expand Down Expand Up @@ -92,9 +92,9 @@
"test_duration": {
"measured": [
{
"seconds": 424.0,
"seconds": 1113.0,
"source": "ci-steps",
"detail": "run 36284835140(.github/workflows/pytest.yml)のテストの step 4 個の合計"
"detail": "run 40160663747(.github/workflows/pytest.yml)のテストの step 4 個の合計"
}
]
},
Expand All @@ -104,66 +104,77 @@
{
"path": ".github/workflows/glossary.yml",
"jobs": 1,
"wall_seconds": 12.0
"wall_seconds": 10.0
},
{
"path": ".github/workflows/lint.yml",
"jobs": 1,
"wall_seconds": 16.0
"wall_seconds": 15.0
},
{
"path": ".github/workflows/pr-base-guard.yml",
"jobs": 1,
"wall_seconds": 9.0
"wall_seconds": 8.0
},
{
"path": ".github/workflows/pr-body-decisions.yml",
"jobs": 1,
"wall_seconds": 12.0
"wall_seconds": 9.0
},
{
"path": ".github/workflows/pytest.yml",
"jobs": 4,
"wall_seconds": 296.0
"wall_seconds": 616.0
},
{
"path": ".github/workflows/runtime-plugin-authenticated-smoke.yml",
"jobs": 1
},
{
"path": ".github/workflows/runtime-plugin-smoke-latest.yml",
"jobs": 1
},
{
"path": ".github/workflows/runtime-plugin-smoke.yml",
"jobs": 5,
"wall_seconds": 60.0
"wall_seconds": 65.0
},
{
"path": ".github/workflows/runtime-plugin-validate.yml",
"jobs": 8,
"wall_seconds": 25.0
"jobs": 9,
"wall_seconds": 28.0
},
{
"path": ".github/workflows/script-structure.yml",
"jobs": 1,
"wall_seconds": 13.0
"wall_seconds": 15.0
},
{
"path": "dynamic/pages/pages-build-deployment",
"jobs": 3,
"wall_seconds": 29.0
"wall_seconds": 27.0
}
],
"required_checks": [
"doc-line-limit-check",
"glossary-check",
"guard",
"instruction-files-check",
"lint",
"markdown-link-check",
"pr-body-decisions-check",
"pytest",
"required-checks-check",
"runtime-plugin-build-check",
"runtime-plugin-validate",
"runtime-smoke (agy)",
"runtime-smoke (claude)",
"runtime-smoke (codex)",
"runtime-smoke (kiro)",
"script-structure-check",
"skill-frontmatter-check",
"skill-repo-assumptions-check"
"skill-repo-assumptions-check",
"skill-shell-vars-check"
]
},
"services": {
Expand Down Expand Up @@ -208,22 +219,23 @@
"notes": []
},
"analysis": {
"analyzer": 1,
"at": "2026-09-27T03:04:04+00:00",
"analyzer": 2,
"at": "2026-10-25T04:18:08+00:00",
"inputs": {
".github/workflows/glossary.yml": "5b79e8c79e18ad4df0d8f2f63e909c4481bf1aa6",
".github/workflows/glossary.yml": "8ca7da924298f00cf75220e28294a9b0bb473743",
".github/workflows/lint.yml": "56129aea1429a46cadbcdc1d99e02eb264cbbb95",
".github/workflows/pr-base-guard.yml": "71c0cda668b533694c48064813b68bbd03cd6efc",
".github/workflows/pr-body-decisions.yml": "189d6e18ce07444d9947b72cc4f91e2181aeb8db",
".github/workflows/pytest.yml": "28b588d100201a77607a6e601bb50ac547243698",
".github/workflows/runtime-plugin-authenticated-smoke.yml": "1235813a9da744349b7a8a632aebdb7e3398d060",
".github/workflows/runtime-plugin-smoke.yml": "f5ddec37af8f7893488433084f0a2cb082d3f718",
".github/workflows/runtime-plugin-validate.yml": "a35749abebcf580d6a7bd00175741e2d69cb78fd",
".github/workflows/script-structure.yml": "69dbe26a83b9728e823e2f68314ed07e025f62be",
"AGENTS.md": "284d264afd238bb74f2450c8c97c45887d081c19",
"CLAUDE.md": "9ddeb20e7b8480012e218e60891971907f886206",
"KIRO.md": "73383b2d2a593eaaba35d626f73931df78da47c1",
"pyproject.toml": "8070c90c878003392767b4fc877edcd5ab9fd9ce",
".github/workflows/pr-body-decisions.yml": "4c9a78fae1d3766973daa66110ad9c6a212d0fe6",
".github/workflows/pytest.yml": "fdd26324b1360e1f56c215873ecff6060b161fbc",
".github/workflows/runtime-plugin-authenticated-smoke.yml": "15e89f83316c0f275f94c1dfff1981a0add61477",
".github/workflows/runtime-plugin-smoke-latest.yml": "4495e9fe0d54537c07e98dc93d5592258801261c",
".github/workflows/runtime-plugin-smoke.yml": "ff951c3adc274597eba75904fe5498f5f9175d4e",
".github/workflows/runtime-plugin-validate.yml": "ff3bfbce63461fca5e024045154bf53487cf16f6",
".github/workflows/script-structure.yml": "8d07e6bbe7a20e80da9d8fa830d53be7c698a254",
"AGENTS.md": "1f92a717ed8590f528efe8132975c288a16bf6b9",
"CLAUDE.md": "3e34760f5ad5d1df9f965f1acb795e279babdb1a",
"KIRO.md": "f15c72bb7ec964a5c3b04e649972d3948f03b12a",
"pyproject.toml": "4fe2a1d2450f2ce5a90e34f20340a467e35d0d01",
"pytest.ini": "47b2c6e3d414cf343d7f45cda0d08980d28b1a5d"
},
"branches": {
Expand All @@ -234,10 +246,9 @@
]
},
"written": {
"languages": "5d3339f70f1b230e90928da915da309a6854c2277d0c14ba9e8fea42c7cb3a75",
"test": "82dfd6cdb46070f65d57323b597a620812d3dc70f945bf55f727498a1867e0dd",
"test_duration": "fee345186423404ef87f5fc2eaad90581a699fc14222e23c9be0a44d81fe24a4",
"ci": "410acdb0a2a2713bb935fa7d61e39474cb512549a87695d6e6b9c74f9e394db9",
"languages": "320a7cb8a0983114f280919dfd4a931307ee34f149b1983226b870e32817124f",
"test_duration": "3dc9bfb50364d4c9e4fea7b4ec4f17084026c8d9d7168f8c87b33647c60aad01",
"ci": "110c8db4b84130256ae88b38e3e4ece30c91da1c568be8cc6ac8b564864d8ab4",
"services": "53f4e4c15a04bde773f9d0d89be06cd65f1a9248c0603b06b78bfa8a5f8a0771",
"delivery": "d4f1c6e5121b913e9147eb9552c3c78f2a4944bba1562c14c6a4056b1da7ac77",
"issues": "c851aa29d3c11745f1307194db49eba6a488771771ea86c21c7237e400e336b8",
Expand Down
8 changes: 4 additions & 4 deletions docs/specifications/ndf-release-approved-commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
| 扱う | 扱わない |
| --- | --- |
| `release-steps.py approval-facts` が承認したコミットを書くこと | `--channel dev` の振る舞い(開発版はマージした変更がそのまま載るチャネルで、承認を要しない。引数も結果も変わらない) |
| `release-steps.py approve` が承認の記録を書くこと、`mvv-gate.py check --gate release` が `by: mvv` で書くこと | 昇格の経路(`merged-steps.py promote`)。比較の関数は promote からも呼べる形で `lib/` に置いてある(「決定の理由」) |
| `release-steps.py approve` が承認の記録を書くこと、`mvv-gate.py check --gate release` が `by: mvv` で書くこと | 昇格の経路(`merged-steps.py promote`)。昇格は `compare_approved` を使わず、導入の確認の承認資料の確かめたコミットを `--expect-head-from` で受けて `--expect-head` と同じ照合を使う(#1529。振る舞いの正本は `merged-steps.py` の冒頭) |
| `release-steps.py release --channel prod` が承認したコミットを受け取り、承認の外のコミットがあれば止まること | 承認資料のほかの欄、MVV 判定の判定の中身 |
| `merged-steps.py merge-when-green --expect-head` が比べた先端だけをマージすること | 食い違ったときに承認資料を自動で作り直して再承認を求める仕組み(止まって人へ戻すまでを扱う) |
| 本番のリリースプラン(`supervise.py new release --channel prod`。`--mvv` 付きを含む)が承認資料を渡すこと | |
Expand Down Expand Up @@ -154,7 +154,7 @@ A = 承認したコミット、T = 比べた先端、H = 配布の PR の先端
| 4 | `git rev-list --no-merges A..T ^H` から、配布の PR の `mergeCommit` と、配布の PR のコミットと同じ `git patch-id --stable` のものを除いた残り | `outside_commits`(I9) |
| 5 | `git merge-tree --write-tree A H` の木と `T^{tree}` | 等しければ `match`、違えば `tree_differs`、`merge-tree` が失敗すれば `undecidable` |

`allowed`(入ってよい PR)を空にすると、5 は「T の木が A の木と等しいか」になる(昇格の経路から使う形)。
`allowed`(入ってよい PR)を空にすると、5 は「T の木が A の木と等しいか」になる(昇格の経路はこの形を使わない。#1529)。
`allowed` が 2 つ以上のときは期待する木を作らず `undecidable` を返す。`git merge-tree --write-tree` は git 2.38 からで、
使えない git では 0 以外を返し `undecidable`(I6)に倒れる。

Expand Down Expand Up @@ -241,7 +241,7 @@ git を読むだけで書かず、終了コードも出力も持たない。ブ
| 比べる時点は配布の PR のマージの後の 1 つ | 配布の PR はベースブランチへ入るだけで本番へは届かない。マージの前にも比べると判定の時点が 2 つになる。承認したコミットの形と有無・承認の記録だけは最初に確かめ、何もマージせずに止まる |
| 判定できないときは承認ゲートで止まる(I6) | 通すと承認の外の変更を見落としうる。失敗(1)にすると、本番のリリースプランの judge が直す(fix)か同じステップをもう一度(retry)を選び、人の承認を経ずに打ち直されうる |
| `--mvv` 付きのプランは承認ゲートで `handoff` へ進む(I7) | MVV 判定が承認したのは承認資料の中身で、承認の外のコミットは判定の材料に入っていない。人が外のコミットを見てから承認し直す。`gate_next` を置かないと、10 を受けて `next` の `verify` へ進み、配っていない版の導入を確かめに行く |
| 比べる関数を `lib/` に置く | 昇格の経路にも同じ欠けがあり、`release-steps.py` の中に置くと同じ役割の関数が 2 つになる。`allowed` を空にすれば promote の判定になる |
| 比べる関数を `lib/` に置く | 昇格の経路にも同じ欠けがあり、`release-steps.py` の中に置くと同じ役割の関数が 2 つになる。`allowed` を空にすれば promote の判定になると見込んだが、#1529 は木が等しい別のコミットを通すため採らず、昇格は `--expect-head` の照合を使う |

## 既知の制約

Expand All @@ -268,7 +268,7 @@ git を読むだけで書かず、終了コードも出力も持たない。ブ
| MVV 判定の記録(I10) | `mvv-gate.py check --gate release` が従うと判定したときだけ承認資料へ `by: mvv` の承認の記録を書き、判定できない場合と `--advise` では書かないこと(`test_mvv_gate.py`) |
| 本番のリリースプラン(I7) | 本番の `release` のステップが `--approval <承認資料>` を持ち、`--mvv` 付きは `gate_next: handoff`、無しは `end` であること。開発版のステップに `--approval` も `gate_next` も無いこと(`test_supervise_new.py`) |
| 退行しない | `--channel dev` の引数・`items`・`metrics` が変わらないこと。`--channel prod` で配布の PR がマージ済みのとき、打ち直しで続きから進み、一致すれば `ok` になること |
| 昇格の形 | `allowed` を空にしたとき、先端の木が承認したコミットの木と等しいかで判定すること |
| `allowed` が空の形 | `allowed` を空にしたとき、先端の木が承認したコミットの木と等しいかで判定すること |

## 関連リンク

Expand Down
1 change: 1 addition & 0 deletions docs/specifications/ndf-supervise-base-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ sequenceDiagram

止まったときの理由は「ステップ <id> が失敗した(exit=N)」に、そのステップの `summary` と、`on_fail` でそこへ回した直前のステップの
`<id>: <summary>` が続く(決定 7)。実装のプランは `base-return` の要約が、検査のプランは `abort` の理由の後に `base-return:` の要約が載る。
どちらの `summary` も、結果 JSON の `status` が `stopped` で `metrics.exit` が整数なら、後ろへ「(元の終了コード N)」が添う。

## セキュリティ

Expand Down
69 changes: 56 additions & 13 deletions plugins/ndf/scripts/lib/approved_commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

- 承認資料(`release-steps.py approval-facts` が書く Markdown)の欄の名前・SHA の形の検査・読み取り・承認の記録の書き込み
- 承認したコミットから比べた先端までに、許したもの(配布の PR)の外のコミットや中身が無いかの比較(`compare_approved`)
- 導入の確認の承認資料(`release-steps.py verify-facts` が書く)の、確かめたコミットの行の形と読み取り
(`verified_line` / `verified_sha`。昇格の経路 `merged-steps.py promote --expect-head-from` が読む。#1529)

`compare_approved` は git を読むだけで書かず、終了コードも出力も持たずに判定(`Verdict`)を値で返す。読み替えは呼び手が行う。
ブランチ名は受け取らず SHA だけを受け取る。`allowed` を空にすると、先端の木が承認したコミットの木と等しいかの判定になる
(昇格の経路 `merged-steps.py promote` から使う形)。
ブランチ名は受け取らず SHA だけを受け取る。`allowed` を空にすると、先端の木が承認したコミットの木と等しいかの判定になる。
"""

from __future__ import annotations
Expand Down Expand Up @@ -153,6 +154,32 @@ def material_row(sha: str) -> tuple[str, str]:
return (ROW, parse_sha(sha))


# --- 確かめたコミット(導入の確認の承認資料。#1529) ------------------------------

_VERIFIED_HEAD = "- ベースブランチ "
_VERIFIED_MID = " の先頭: `"
_VERIFIED_TAIL = "`。導入の確認はこのコミットで走らせた"
_VERIFIED_RE = re.compile("^" + re.escape(_VERIFIED_HEAD) + r"\S+" + re.escape(_VERIFIED_MID) + "([^`]*)" + re.escape(_VERIFIED_TAIL) + "$")


def verified_line(base: str, sha: str) -> str:
"""導入の確認の承認資料(`release-steps.py verify-facts`)の、確かめたコミットの行。"""
return f"{_VERIFIED_HEAD}{base}{_VERIFIED_MID}{sha}{_VERIFIED_TAIL}"


def verified_sha(path) -> str:
"""導入の確認の承認資料から確かめたコミットを読む(`merged-steps.py promote --expect-head-from`)。
資料が無い・行が無い・行が 2 つ以上・40 桁の 16 進でなければ ValueError(照合を飛ばさない)。"""
p = Path(path)
if not p.is_file():
raise ValueError(f"導入の確認の承認資料 {p} が無い")
found = [m.group(1) for m in (_VERIFIED_RE.match(line) for line in p.read_text(encoding="utf-8").split("\n")) if m]
if len(found) != 1:
what = "無い" if not found else f"{len(found)} 行ある"
raise ValueError(f"導入の確認の承認資料 {p} に確かめたコミットの行が{what}")
return parse_sha(found[0])


# --- 比較 ---------------------------------------------------------------------


Expand Down Expand Up @@ -195,6 +222,28 @@ def _expected_tree(root, approved, allowed) -> str | None:
return out.split()[0] if out else None


def _outside_of_prs(root, approved, allowed) -> list[str] | None:
"""approved..配布の PR の先端のうち、配布の PR のコミットでないもの(I3)。読めなければ None。"""
outside: list[str] = []
for pr in allowed:
listed = _rev_list(root, f"{approved}..{pr.head}")
if listed is None:
return None
outside += [c for c in listed if c not in pr.commits and c not in outside]
return outside


def _outside_to_tip(root, approved, tip, allowed) -> list[str] | None:
"""approved..tip の(マージでない)コミットのうち、配布の PR の先端から届かず、配布の PR の mergeCommit でも
同じ patch-id でもないもの(I9)。読めなければ None。"""
listed = _rev_list(root, "--no-merges", f"{approved}..{tip}", *(f"^{pr.head}" for pr in allowed))
if listed is None:
return None
merges = {pr.merge_commit for pr in allowed if pr.merge_commit}
patches = {pid for pr in allowed for c in pr.commits if (pid := _patch_id(root, c))}
return [c for c in listed if c not in merges and _patch_id(root, c) not in patches]


def compare_approved(root, approved: str, tip: str, allowed: list[AllowedPR] = ()) -> Verdict:
"""承認したコミット approved から比べた先端 tip までを比べる(#815 の設計の「compare の中の順序」)。

Expand All @@ -215,19 +264,13 @@ def compare_approved(root, approved: str, tip: str, allowed: list[AllowedPR] = (
if not _ok(root, "merge-base", "--is-ancestor", approved, tip):
v.reason = NOT_ANCESTOR
return v
outside: list[str] = []
for pr in allowed:
listed = _rev_list(root, f"{approved}..{pr.head}")
if listed is None:
return v
outside += [c for c in listed if c not in pr.commits and c not in outside]
outside = _outside_of_prs(root, approved, allowed)
if outside is None:
return v
if not outside:
listed = _rev_list(root, "--no-merges", f"{approved}..{tip}", *(f"^{pr.head}" for pr in allowed))
if listed is None:
outside = _outside_to_tip(root, approved, tip, allowed)
if outside is None:
return v
merges = {pr.merge_commit for pr in allowed if pr.merge_commit}
patches = {pid for pr in allowed for c in pr.commits if (pid := _patch_id(root, c))}
outside = [c for c in listed if c not in merges and _patch_id(root, c) not in patches]
if outside:
v.reason = OUTSIDE_COMMITS
v.outside = [Outside(c, _subject(root, c)) for c in outside]
Expand Down
Loading
Loading