{
 "number": 35686,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35686",
 "title": "lint: have git-subtree-check check for backportability",
 "author": "Sjors",
 "author_association": "MEMBER",
 "created_at": "2026-07-08T19:01:44Z",
 "updated_at": "2026-08-24T05:32:54Z",
 "age_days": 70,
 "draft": false,
 "labels": [
  "Tests"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f",
 "head_ref": "2026/07/subtree-lint",
 "head_repo": "Sjors/bitcoin",
 "head_history": [
  {
   "t": "2026-07-28T09:40:50Z",
   "sha": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f"
  }
 ],
 "additions": 75,
 "deletions": 20,
 "changed_files": 4,
 "commit_count": 2,
 "size_bucket": "S",
 "mergeable_state": "blocked",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "ryanofsky",
      "url": "https://github.com/bitcoin/bitcoin/pull/35686#pullrequestreview-4996279203"
     },
     {
      "login": "BrandonOdiwuor",
      "url": "https://github.com/bitcoin/bitcoin/pull/35686#pullrequestreview-5003680532"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "ryanofsky": {
   "kind": "ack",
   "hash": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f",
   "t": "2026-08-21T18:36:12Z",
   "stale": false
  },
  "BrandonOdiwuor": {
   "kind": "ack",
   "hash": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f",
   "t": "2026-08-23T23:46:20Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 2,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 2,
  "changes_requested": 0,
  "distinct_reviewers": [
   "BrandonOdiwuor",
   "maflcko",
   "ryanofsky",
   "sedited"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "blocked",
  "last_author_activity": "2026-08-21T18:40:41Z",
  "last_reviewer_activity": "2026-08-23T23:46:20Z",
  "last_reviewer": "BrandonOdiwuor",
  "author_silent_days": 26,
  "waiting_on_author_days": 24,
  "days_since_update": 24
 },
 "refs": {
  "mentioned": [
   34804,
   34952
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 34804,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-03-30",
    "title": "Update libmultiprocess subtree to fix race conditions on disconnects"
   },
   {
    "number": 34952,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-04-01",
    "title": "Update libmultiprocess subtree in 31.x branch to fix race conditions on disconnects"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "doc/developer-notes.md"
 ],
 "body": "A subtree update is easier to backport when its merge commit is based on the previous subtree merge instead of on a newer `master` commit. The exact same merge commit can then be reused on release branches, making the backport trivial to verify without rereviewing a newly generated subtree merge.\n\nUpdate `git-subtree-check.sh` to locate the merge that introduced the latest subtree squash reachable from `COMMIT` and error if its first parent is not the previous subtree merge.\n\nSometimes subtree and Bitcoin Core API changes are incompatible, making it necessary to base an update on a later commit. Add `--incompatible` to explicitly skip the backportability check in that case.\n\nThe CI lint job always sets `--incompatible`, so we rely on reviewers to run the new check.\n\nAs a preparatory refactor, replace `getopts` with explicit option parsing and add `--remote` as the preferred long alias for `-r`.\n\nExample that passes:\n\n```sh\ntest/lint/git-subtree-check.sh src/ipc/libmultiprocess 66b4e30e\n```\n\nExample that errors because the update is not based on the previous subtree merge:\n\n```sh\ntest/lint/git-subtree-check.sh src/ipc/libmultiprocess 02afa661\n```",
 "commits": [
  {
   "sha": "20c9781ba14e3aa559c0c650f732884fd7cf8201",
   "date": "2026-07-28T09:25:19Z",
   "message": "lint: refactor git-subtree-check options parsing\n\nReplace getopts with explicit argument parsing, and give -r a long\noption --remote.\n\nThe next commit adds another long option.\n\nCo-authored-by: Ryan Ofsky <ryan@ofsky.org>"
  },
  {
   "sha": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f",
   "date": "2026-07-28T09:38:16Z",
   "message": "lint: have git-subtree-check check for backportability\n\nA subtree update PR is easier to backport when its merge commit is\nstacked on the previous subtree merge instead of on master.\n\nHave git-subtree-check.sh error when the latest subtree merge does not\nfollow that structure. This is not always possible if changes in the\nsubtree and Bitcoin Core code are not compatible, so it can be\noverridden with an --incompatible flag.\n\nExample without error:\ntest/lint/git-subtree-check.sh src/ipc/libmultiprocess 66b4e30e\n\nExample with error:\ntest/lint/git-subtree-check.sh src/ipc/libmultiprocess 02afa661\n\nCo-authored-by: Ryan Ofsky <ryan@ofsky.org>"
  }
 ],
 "timeline": [
  {
   "t": "2026-07-10T14:33:24Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "text": "I'm not sure. We sometimes cherry-pick commits from the subtree if they contain fixes that need backporting. Is this really solving a problem?"
  },
  {
   "t": "2026-07-10T15:21:35Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "In some rare cases it is required to adjust the code for the subtree bump in the merge commit itself (to avoid build failures on the merge commit itself), so that would warn in that case and somehow encourage build failures?\n\nAlso, can you explain how this simplifies \"backport\"? IIUC you are referring to merging the subtree merge commit as-is into several branches? I think this is nice, and good to keep in mind, but the alternative of having a second similar subtree merge (with only the commit id different) on the backport branch is also fine and harmless?"
  },
  {
   "t": "2026-07-10T16:42:44Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYes, we've done this a few times for libmultiprocess: #34804 and #34952 have the exact same merge commit hash, making the pack-port trivial to review."
  },
  {
   "t": "2026-07-10T16:49:29Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "And it was specifically suggested here: https://github.com/bitcoin/bitcoin/issues/33439#issuecomment-3451232704\n\n[quoted text omitted]"
  },
  {
   "t": "2026-07-16T18:08:31Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI did that in the last update commit (fa911d815dba280f7d4a1b3fe7b786be37afaf75). I guess in theory one could even come up with a copy-pasteable scripted-diff to mark/check such commits. Something like:\n\n```bash\n$ ./test/lint/commit-script-check.sh HEAD~..HEAD\nRunning script for: 84868e9b2c96a14a49cec4a8b30e95bb8857d720\n\n # Verify last commit is also the last merge subtree commit\n [ \"$( git log -1)\" == \"$( git log -1 src/ipc/libmultiprocess )\" ]\n git merge 6d5f753921578eefdd3fce64cfc8ee7951b6cbc4 -m 'dummy'\nMerge made by the 'ort' strategy.\n src/ipc/libmultiprocess/doc/versions.md        | 44 ++++++++++++++++++++++++--------------------\n src/ipc/libmultiprocess/include/mp/type-data.h |  3 ++-\n src/ipc/libmultiprocess/include/mp/version.h   |  2 +-\n src/ipc/libmultiprocess/test/mp/test/foo.capnp |  9 ++++++++-\n src/ipc/libmultiprocess/test/mp/test/foo.h     |  4 +++-\n src/ipc/libmultiprocess/test/mp/test/test.cpp  | 23 +++++++++++++++++------\n 6 files changed, 55 insertions(+), 30 deletions(-)\nOK\n\n$ git log\ncommit 84868e9b2c96a14a49cec4a8b30e95bb8857d720 (HEAD)\nMerge: a9d1b652f3 6d5f753921\n\n    scripted-diff: ipc: Merge libmul subtree update\n\n    -BEGIN VERIFY SCRIPT-\n\n     # Verify last commit is also the last merge subtree commit\n     [ \"$( git log -1)\" == \"$( git log -1 src/ipc/libmultiprocess )\" ]\n     git merge 6d5f753921578eefdd3fce64cfc8ee7951b6cbc4 -m 'dummy'\n\n    -END VERIFY SCRIPT-\n\n```\n\nThis way, each commit can opt-in or out, and enforce it. But :man_shrugging:"
  },
  {
   "t": "2026-07-16T18:55:17Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "@maflcko I'm mainly concerned about the author forgetting to do this, and the reviewer(s) forgetting to check. That's what this linter fixes. Presumably both the author and reviewer will run this script.\n\nIf the author didn't forget, they can just suggest that reviewers confirm that `HEAD^1` is the previous subtree merge.\n\ncc @ryanofsky"
  },
  {
   "t": "2026-07-27T19:17:08Z",
   "kind": "review",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "state": "APPROVED",
   "commit": "4ace058d85b9e7d005b39c31ed5d4eab71afc350",
   "text": "Code review ACK 4ace058d85b9e7d005b39c31ed5d4eab71afc350. I think having the warning is better than not having it, but a stricter approach would seem better here.\n\nThe only reason this check should not be satisfied is when subtree and bitcoin core changes are incompatible. The script could trigger an error instead of warning if that's not the case. Also:\n\n- The warning text seems potentially confusing and hard to act on because it doesn't say what the mismatched hashes mean.\n- The documentation seems vague when it says to use the previous subtree \"when possible\" and because \"this makes backporting easier.\" It should say more specifically when to stack on the subtree and how it does makes backporting easier.\n- Skipping this check when the subtree commit is not the `HEAD` commit seems confusing. Previously the script could find the most recent subtree update by itself and perform a full check. Now it only does a partial check unless it is explicitly given the subtree merge commit hash (or that commit happens to be checked out).\n\nSuggestion: I think a variation of this PR 15b44bfe7e4fa62b867ebcc2465bfaed27d689aa would address all these concerns\n\nAlso marco's verify script idea https://github.com/bitcoin/bitcoin/pull/35686#issuecomment-4995114634 seems interesting. It's awkward because the script it is running is non-trivial and includes a hardcoded hash, but could be simplified if the scripted diff called another script in the repository."
  },
  {
   "t": "2026-07-28T09:40:50Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f"
  },
  {
   "t": "2026-07-28T09:41:21Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "@ryanofsky thanks, I took your version, but added an error if `$merge` somehow isn't set.\n\nI split the options handling refactor into a separate commit. Also added `--remote` and replaced `-r` in the documentation (`-r` is kept as an alias)."
  },
  {
   "t": "2026-08-21T18:31:31Z",
   "kind": "review_comment",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "path": "doc/developer-notes.md",
   "commit": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f",
   "in_reply_to": null,
   "text": "In commit \"lint: have git-subtree-check check for backportability\" (5b8973e355441cfed24b16cf2b9bb2aade7ca89f)\n\nNote sure whether the following is a good idea because it makes the document more verbose, but we could mention a way to check out the right base commit:\n\n```sh\nprevious_subtree_merge=$(git log -1 --pretty=%H src/ipc/libmultiprocess)\ngit checkout \"$previous_subtree_merge\"\n```\n\nI think I'd lean towards keeping the current version for now though to keep the example simple."
  },
  {
   "t": "2026-08-21T18:36:12Z",
   "kind": "review",
   "who": "ryanofsky",
   "assoc": "CONTRIBUTOR",
   "state": "APPROVED",
   "commit": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f",
   "text": "Code review ACK 5b8973e355441cfed24b16cf2b9bb2aade7ca89f. Thanks for the updates. New commit split makes this easier to review and I think the script messages and documentation are easier to understand now.\n\nThe main benefit I see to this PR is just documenting the practice of basing new subtrees updates on previous updates, which is nice because it allows the same update commits to be merged into multiple branches, saving reviewer effort. The PR also makes this condition easy for reviewers to check by running the normal linter command.\n\nNote: The new linter `--incompatible` option could be dropped in the future by requiring a stricter commit message format for subtree merge commits, requiring a tag in the merge commit messages to indicate whether the merged changes are not compatible and should be allowed to use a newer base. But for now I think having the option is better, because it makes it straightforward for reviewers and authors to check the stricter base condition now, without requiring the stricter condition to be imposed on all subtrees going forward.\n\n---\n\nre: sedited https://github.com/bitcoin/bitcoin/pull/35686#issuecomment-4936395031\n\n[quoted text omitted]\nWow I didn't know this was done. Apparently this is implemented by disabling the subtree check entirely. Examples:\n\n  - 29.x: 53a5c7f1c95 in https://github.com/bitcoin/bitcoin/pull/35674\n  - 30.x: d09bd0f4025 in https://github.com/bitcoin/bitcoin/pull/35668\n  - 31.x: 1ee11d8ba64 in https://github.com/bitcoin/bitcoin/pull/35699\n\nThis can make sense when there is an isolated fix that needs to be cherry-picked, but it requires more manual review.\n\nThis PR is primarily trying to address a different use-case, when there are wider improvements like stability fixes in the subtree that you want to backport to multiple release branches without requiring the same changes to be reviewed multiple times. Being able to merge the same commit hash into multiple branches is especially nice in that case. (It could be useful for cherry-picks too, but that's not the aim.)"
  },
  {
   "t": "2026-08-21T18:40:41Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "doc/developer-notes.md",
   "commit": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f",
   "in_reply_to": 3832717041,
   "text": "Going to leave this alone for now."
  },
  {
   "t": "2026-08-23T23:46:20Z",
   "kind": "review",
   "who": "BrandonOdiwuor",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f",
   "text": "ACK 5b8973e355441cfed24b16cf2b9bb2aade7ca89f\n\nTested the new backportability check:\n\n- fa911d815: GOOD:  correctly stacked; first parent is the previous subtree merge a9d1b652\n- 02afa661: ERROR: first parent is not the previous subtree merge 2063f02bd5.\n\nThis matches the intended behaviour. Also verified that `--incompatible` correctly suppresses the check.\n\n**Test runs:**\n\n**Parent inspection:**"
  }
 ],
 "labels_log": [
  {
   "t": "2026-07-08T19:01:47Z",
   "action": "labeled",
   "label": "Tests",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-07-28T09:41:28Z",
   "kind": "renamed",
   "who": "Sjors",
   "from": "lint: have git-subtree-check warn about backportability",
   "to": "lint: have git-subtree-check check for backportability"
  }
 ],
 "text_chars": 9634,
 "text_tokens_estimate": 2408,
 "changed_paths": [
  "doc/developer-notes.md",
  "test/lint/README.md",
  "test/lint/git-subtree-check.sh",
  "test/lint/test_runner/src/lint_repo_hygiene.rs"
 ],
 "files": [
  {
   "path": "doc/developer-notes.md",
   "add": 9,
   "del": 0
  },
  {
   "path": "test/lint/README.md",
   "add": 9,
   "del": 3
  },
  {
   "path": "test/lint/git-subtree-check.sh",
   "add": 51,
   "del": 16
  },
  {
   "path": "test/lint/test_runner/src/lint_repo_hygiene.rs",
   "add": 6,
   "del": 1
  }
 ],
 "test_lines": 86,
 "git": {
  "head": "5b8973e355441cfed24b16cf2b9bb2aade7ca89f",
  "head_matches_backup": true,
  "base": "32941e131479b4aec1eb606d68a9bcc3f5d7cf3d",
  "commits": [
   {
    "sha": "20c9781ba1",
    "subject": "lint: refactor git-subtree-check options parsing",
    "files": 3,
    "add": 22,
    "del": 18
   },
   {
    "sha": "5b8973e355",
    "subject": "lint: have git-subtree-check check for backportability",
    "files": 4,
    "add": 55,
    "del": 4
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "4ff13dbd32342397",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}