pr-merge-queue 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +125 -2
  3. package/dist/claim-pr/claim-pr.d.mts +45 -0
  4. package/dist/claim-pr/claim-pr.d.mts.map +1 -0
  5. package/dist/claim-pr/claim-pr.mjs +217 -0
  6. package/dist/claim-pr/claim-pr.mjs.map +1 -0
  7. package/dist/claim-pr/index.d.mts +2 -0
  8. package/dist/claim-pr/index.d.mts.map +1 -0
  9. package/dist/claim-pr/index.mjs +2 -0
  10. package/dist/claim-pr/index.mjs.map +1 -0
  11. package/dist/cmd/claim-pr.d.mts +3 -0
  12. package/dist/cmd/claim-pr.d.mts.map +1 -0
  13. package/dist/cmd/claim-pr.mjs +20 -0
  14. package/dist/cmd/claim-pr.mjs.map +1 -0
  15. package/dist/cmd/open-pr.d.mts +3 -0
  16. package/dist/cmd/open-pr.d.mts.map +1 -0
  17. package/dist/cmd/open-pr.mjs +20 -0
  18. package/dist/cmd/open-pr.mjs.map +1 -0
  19. package/dist/cmd/pr-report.d.mts +3 -0
  20. package/dist/cmd/pr-report.d.mts.map +1 -0
  21. package/dist/cmd/pr-report.mjs +20 -0
  22. package/dist/cmd/pr-report.mjs.map +1 -0
  23. package/dist/cmd/push-fixed-pr.d.mts +3 -0
  24. package/dist/cmd/push-fixed-pr.d.mts.map +1 -0
  25. package/dist/cmd/push-fixed-pr.mjs +20 -0
  26. package/dist/cmd/push-fixed-pr.mjs.map +1 -0
  27. package/dist/cmd/report-unowned-prs.d.mts +3 -0
  28. package/dist/cmd/report-unowned-prs.d.mts.map +1 -0
  29. package/dist/cmd/report-unowned-prs.mjs +20 -0
  30. package/dist/cmd/report-unowned-prs.mjs.map +1 -0
  31. package/dist/cmd/unblock-prs.d.mts +3 -0
  32. package/dist/cmd/unblock-prs.d.mts.map +1 -0
  33. package/dist/cmd/unblock-prs.mjs +19 -0
  34. package/dist/cmd/unblock-prs.mjs.map +1 -0
  35. package/dist/entry-point.d.mts +3 -0
  36. package/dist/entry-point.d.mts.map +1 -0
  37. package/dist/entry-point.mjs +3 -0
  38. package/dist/entry-point.mjs.map +1 -0
  39. package/dist/lib/cli-args.d.mts +27 -0
  40. package/dist/lib/cli-args.d.mts.map +1 -0
  41. package/dist/lib/cli-args.mjs +55 -0
  42. package/dist/lib/cli-args.mjs.map +1 -0
  43. package/dist/lib/github-api.d.mts +153 -0
  44. package/dist/lib/github-api.d.mts.map +1 -0
  45. package/dist/lib/github-api.mjs +291 -0
  46. package/dist/lib/github-api.mjs.map +1 -0
  47. package/dist/lib/index.d.mts +7 -0
  48. package/dist/lib/index.d.mts.map +1 -0
  49. package/dist/lib/index.mjs +7 -0
  50. package/dist/lib/index.mjs.map +1 -0
  51. package/dist/lib/json.d.mts +12 -0
  52. package/dist/lib/json.d.mts.map +1 -0
  53. package/dist/lib/json.mjs +28 -0
  54. package/dist/lib/json.mjs.map +1 -0
  55. package/dist/lib/log.d.mts +10 -0
  56. package/dist/lib/log.d.mts.map +1 -0
  57. package/dist/lib/log.mjs +13 -0
  58. package/dist/lib/log.mjs.map +1 -0
  59. package/dist/lib/shell.d.mts +18 -0
  60. package/dist/lib/shell.d.mts.map +1 -0
  61. package/dist/lib/shell.mjs +42 -0
  62. package/dist/lib/shell.mjs.map +1 -0
  63. package/dist/lib/time.d.mts +17 -0
  64. package/dist/lib/time.d.mts.map +1 -0
  65. package/dist/lib/time.mjs +35 -0
  66. package/dist/lib/time.mjs.map +1 -0
  67. package/dist/open-pr/github.d.mts +72 -0
  68. package/dist/open-pr/github.d.mts.map +1 -0
  69. package/dist/open-pr/github.mjs +249 -0
  70. package/dist/open-pr/github.mjs.map +1 -0
  71. package/dist/open-pr/index.d.mts +6 -0
  72. package/dist/open-pr/index.d.mts.map +1 -0
  73. package/dist/open-pr/index.mjs +6 -0
  74. package/dist/open-pr/index.mjs.map +1 -0
  75. package/dist/open-pr/main.d.mts +36 -0
  76. package/dist/open-pr/main.d.mts.map +1 -0
  77. package/dist/open-pr/main.mjs +204 -0
  78. package/dist/open-pr/main.mjs.map +1 -0
  79. package/dist/open-pr/options.d.mts +20 -0
  80. package/dist/open-pr/options.d.mts.map +1 -0
  81. package/dist/open-pr/options.mjs +66 -0
  82. package/dist/open-pr/options.mjs.map +1 -0
  83. package/dist/open-pr/steps.d.mts +23 -0
  84. package/dist/open-pr/steps.d.mts.map +1 -0
  85. package/dist/open-pr/steps.mjs +38 -0
  86. package/dist/open-pr/steps.mjs.map +1 -0
  87. package/dist/open-pr/types.d.mts +179 -0
  88. package/dist/open-pr/types.d.mts.map +1 -0
  89. package/dist/open-pr/types.mjs +20 -0
  90. package/dist/open-pr/types.mjs.map +1 -0
  91. package/dist/pr-report/github.d.mts +59 -0
  92. package/dist/pr-report/github.d.mts.map +1 -0
  93. package/dist/pr-report/github.mjs +561 -0
  94. package/dist/pr-report/github.mjs.map +1 -0
  95. package/dist/pr-report/index.d.mts +8 -0
  96. package/dist/pr-report/index.d.mts.map +1 -0
  97. package/dist/pr-report/index.mjs +8 -0
  98. package/dist/pr-report/index.mjs.map +1 -0
  99. package/dist/pr-report/linked-issues.d.mts +15 -0
  100. package/dist/pr-report/linked-issues.d.mts.map +1 -0
  101. package/dist/pr-report/linked-issues.mjs +39 -0
  102. package/dist/pr-report/linked-issues.mjs.map +1 -0
  103. package/dist/pr-report/main.d.mts +42 -0
  104. package/dist/pr-report/main.d.mts.map +1 -0
  105. package/dist/pr-report/main.mjs +163 -0
  106. package/dist/pr-report/main.mjs.map +1 -0
  107. package/dist/pr-report/options.d.mts +36 -0
  108. package/dist/pr-report/options.d.mts.map +1 -0
  109. package/dist/pr-report/options.mjs +90 -0
  110. package/dist/pr-report/options.mjs.map +1 -0
  111. package/dist/pr-report/queue.d.mts +28 -0
  112. package/dist/pr-report/queue.d.mts.map +1 -0
  113. package/dist/pr-report/queue.mjs +74 -0
  114. package/dist/pr-report/queue.mjs.map +1 -0
  115. package/dist/pr-report/render.d.mts +14 -0
  116. package/dist/pr-report/render.d.mts.map +1 -0
  117. package/dist/pr-report/render.mjs +318 -0
  118. package/dist/pr-report/render.mjs.map +1 -0
  119. package/dist/pr-report/repo-settings.d.mts +12 -0
  120. package/dist/pr-report/repo-settings.d.mts.map +1 -0
  121. package/dist/pr-report/repo-settings.mjs +22 -0
  122. package/dist/pr-report/repo-settings.mjs.map +1 -0
  123. package/dist/push-fixed-pr/index.d.mts +2 -0
  124. package/dist/push-fixed-pr/index.d.mts.map +1 -0
  125. package/dist/push-fixed-pr/index.mjs +2 -0
  126. package/dist/push-fixed-pr/index.mjs.map +1 -0
  127. package/dist/push-fixed-pr/push-fixed-pr.d.mts +54 -0
  128. package/dist/push-fixed-pr/push-fixed-pr.d.mts.map +1 -0
  129. package/dist/push-fixed-pr/push-fixed-pr.mjs +172 -0
  130. package/dist/push-fixed-pr/push-fixed-pr.mjs.map +1 -0
  131. package/dist/report-unowned-prs/index.d.mts +2 -0
  132. package/dist/report-unowned-prs/index.d.mts.map +1 -0
  133. package/dist/report-unowned-prs/index.mjs +2 -0
  134. package/dist/report-unowned-prs/index.mjs.map +1 -0
  135. package/dist/report-unowned-prs/report-unowned-prs.d.mts +49 -0
  136. package/dist/report-unowned-prs/report-unowned-prs.d.mts.map +1 -0
  137. package/dist/report-unowned-prs/report-unowned-prs.mjs +127 -0
  138. package/dist/report-unowned-prs/report-unowned-prs.mjs.map +1 -0
  139. package/dist/tsconfig.json +1 -0
  140. package/dist/unblock-prs/auto-fix.d.mts +45 -0
  141. package/dist/unblock-prs/auto-fix.d.mts.map +1 -0
  142. package/dist/unblock-prs/auto-fix.mjs +207 -0
  143. package/dist/unblock-prs/auto-fix.mjs.map +1 -0
  144. package/dist/unblock-prs/checks.d.mts +307 -0
  145. package/dist/unblock-prs/checks.d.mts.map +1 -0
  146. package/dist/unblock-prs/checks.mjs +372 -0
  147. package/dist/unblock-prs/checks.mjs.map +1 -0
  148. package/dist/unblock-prs/claims.d.mts +21 -0
  149. package/dist/unblock-prs/claims.d.mts.map +1 -0
  150. package/dist/unblock-prs/claims.mjs +23 -0
  151. package/dist/unblock-prs/claims.mjs.map +1 -0
  152. package/dist/unblock-prs/config.d.mts +487 -0
  153. package/dist/unblock-prs/config.d.mts.map +1 -0
  154. package/dist/unblock-prs/config.mjs +119 -0
  155. package/dist/unblock-prs/config.mjs.map +1 -0
  156. package/dist/unblock-prs/constants.d.mts +28 -0
  157. package/dist/unblock-prs/constants.d.mts.map +1 -0
  158. package/dist/unblock-prs/constants.mjs +28 -0
  159. package/dist/unblock-prs/constants.mjs.map +1 -0
  160. package/dist/unblock-prs/demotions.d.mts +42 -0
  161. package/dist/unblock-prs/demotions.d.mts.map +1 -0
  162. package/dist/unblock-prs/demotions.mjs +50 -0
  163. package/dist/unblock-prs/demotions.mjs.map +1 -0
  164. package/dist/unblock-prs/github.d.mts +739 -0
  165. package/dist/unblock-prs/github.d.mts.map +1 -0
  166. package/dist/unblock-prs/github.mjs +626 -0
  167. package/dist/unblock-prs/github.mjs.map +1 -0
  168. package/dist/unblock-prs/index.d.mts +25 -0
  169. package/dist/unblock-prs/index.d.mts.map +1 -0
  170. package/dist/unblock-prs/index.mjs +25 -0
  171. package/dist/unblock-prs/index.mjs.map +1 -0
  172. package/dist/unblock-prs/labels.d.mts +49 -0
  173. package/dist/unblock-prs/labels.d.mts.map +1 -0
  174. package/dist/unblock-prs/labels.mjs +49 -0
  175. package/dist/unblock-prs/labels.mjs.map +1 -0
  176. package/dist/unblock-prs/main.d.mts +179 -0
  177. package/dist/unblock-prs/main.d.mts.map +1 -0
  178. package/dist/unblock-prs/main.mjs +640 -0
  179. package/dist/unblock-prs/main.mjs.map +1 -0
  180. package/dist/unblock-prs/merge-after.d.mts +46 -0
  181. package/dist/unblock-prs/merge-after.d.mts.map +1 -0
  182. package/dist/unblock-prs/merge-after.mjs +65 -0
  183. package/dist/unblock-prs/merge-after.mjs.map +1 -0
  184. package/dist/unblock-prs/options.d.mts +35 -0
  185. package/dist/unblock-prs/options.d.mts.map +1 -0
  186. package/dist/unblock-prs/options.mjs +88 -0
  187. package/dist/unblock-prs/options.mjs.map +1 -0
  188. package/dist/unblock-prs/owner.d.mts +32 -0
  189. package/dist/unblock-prs/owner.d.mts.map +1 -0
  190. package/dist/unblock-prs/owner.mjs +63 -0
  191. package/dist/unblock-prs/owner.mjs.map +1 -0
  192. package/dist/unblock-prs/quiet.d.mts +21 -0
  193. package/dist/unblock-prs/quiet.d.mts.map +1 -0
  194. package/dist/unblock-prs/quiet.mjs +42 -0
  195. package/dist/unblock-prs/quiet.mjs.map +1 -0
  196. package/dist/unblock-prs/rebase.d.mts +120 -0
  197. package/dist/unblock-prs/rebase.d.mts.map +1 -0
  198. package/dist/unblock-prs/rebase.mjs +576 -0
  199. package/dist/unblock-prs/rebase.mjs.map +1 -0
  200. package/dist/unblock-prs/release.d.mts +62 -0
  201. package/dist/unblock-prs/release.d.mts.map +1 -0
  202. package/dist/unblock-prs/release.mjs +94 -0
  203. package/dist/unblock-prs/release.mjs.map +1 -0
  204. package/dist/unblock-prs/set-aside-comment.d.mts +20 -0
  205. package/dist/unblock-prs/set-aside-comment.d.mts.map +1 -0
  206. package/dist/unblock-prs/set-aside-comment.mjs +93 -0
  207. package/dist/unblock-prs/set-aside-comment.mjs.map +1 -0
  208. package/dist/unblock-prs/set-aside-detail.d.mts +41 -0
  209. package/dist/unblock-prs/set-aside-detail.d.mts.map +1 -0
  210. package/dist/unblock-prs/set-aside-detail.mjs +56 -0
  211. package/dist/unblock-prs/set-aside-detail.mjs.map +1 -0
  212. package/dist/unblock-prs/skips.d.mts +85 -0
  213. package/dist/unblock-prs/skips.d.mts.map +1 -0
  214. package/dist/unblock-prs/skips.mjs +141 -0
  215. package/dist/unblock-prs/skips.mjs.map +1 -0
  216. package/dist/unblock-prs/stack.d.mts +98 -0
  217. package/dist/unblock-prs/stack.d.mts.map +1 -0
  218. package/dist/unblock-prs/stack.mjs +141 -0
  219. package/dist/unblock-prs/stack.mjs.map +1 -0
  220. package/dist/unblock-prs/triage.d.mts +28 -0
  221. package/dist/unblock-prs/triage.d.mts.map +1 -0
  222. package/dist/unblock-prs/triage.mjs +396 -0
  223. package/dist/unblock-prs/triage.mjs.map +1 -0
  224. package/dist/unblock-prs/types.d.mts +793 -0
  225. package/dist/unblock-prs/types.d.mts.map +1 -0
  226. package/dist/unblock-prs/types.mjs +26 -0
  227. package/dist/unblock-prs/types.mjs.map +1 -0
  228. package/dist/unblock-prs/util.d.mts +23 -0
  229. package/dist/unblock-prs/util.d.mts.map +1 -0
  230. package/dist/unblock-prs/util.mjs +60 -0
  231. package/dist/unblock-prs/util.mjs.map +1 -0
  232. package/dist/unblock-prs/version-pr.d.mts +57 -0
  233. package/dist/unblock-prs/version-pr.d.mts.map +1 -0
  234. package/dist/unblock-prs/version-pr.mjs +79 -0
  235. package/dist/unblock-prs/version-pr.mjs.map +1 -0
  236. package/dist/unblock-prs/watch.d.mts +17 -0
  237. package/dist/unblock-prs/watch.d.mts.map +1 -0
  238. package/dist/unblock-prs/watch.mjs +128 -0
  239. package/dist/unblock-prs/watch.mjs.map +1 -0
  240. package/package.json +118 -3
  241. package/src/claim-pr/claim-pr.mts +380 -0
  242. package/src/claim-pr/claim-pr.test.mts +176 -0
  243. package/src/claim-pr/index.mts +1 -0
  244. package/src/cmd/claim-pr.mts +32 -0
  245. package/src/cmd/open-pr.mts +28 -0
  246. package/src/cmd/pr-report.mts +28 -0
  247. package/src/cmd/push-fixed-pr.mts +36 -0
  248. package/src/cmd/report-unowned-prs.mts +37 -0
  249. package/src/cmd/unblock-prs.mts +24 -0
  250. package/src/entry-point.mts +3 -0
  251. package/src/lib/cli-args.mts +86 -0
  252. package/src/lib/cli-args.test.mts +92 -0
  253. package/src/lib/github-api.mts +463 -0
  254. package/src/lib/github-api.test.mts +40 -0
  255. package/src/lib/index.mts +6 -0
  256. package/src/lib/json.mts +43 -0
  257. package/src/lib/json.test.mts +34 -0
  258. package/src/lib/log.mts +19 -0
  259. package/src/lib/log.test.mts +45 -0
  260. package/src/lib/shell.mts +52 -0
  261. package/src/lib/time.mts +49 -0
  262. package/src/open-pr/README.md +237 -0
  263. package/src/open-pr/github.mts +444 -0
  264. package/src/open-pr/index.mts +5 -0
  265. package/src/open-pr/main.mts +327 -0
  266. package/src/open-pr/options.mts +101 -0
  267. package/src/open-pr/options.test.mts +67 -0
  268. package/src/open-pr/steps.mts +57 -0
  269. package/src/open-pr/steps.test.mts +59 -0
  270. package/src/open-pr/types.mts +42 -0
  271. package/src/pr-report/README.md +244 -0
  272. package/src/pr-report/github.mts +911 -0
  273. package/src/pr-report/index.mts +7 -0
  274. package/src/pr-report/linked-issues.mts +51 -0
  275. package/src/pr-report/linked-issues.test.mts +65 -0
  276. package/src/pr-report/main.mts +257 -0
  277. package/src/pr-report/options.mts +140 -0
  278. package/src/pr-report/queue.mts +125 -0
  279. package/src/pr-report/render.mts +428 -0
  280. package/src/pr-report/render.test.mts +379 -0
  281. package/src/pr-report/repo-settings.mts +34 -0
  282. package/src/push-fixed-pr/index.mts +1 -0
  283. package/src/push-fixed-pr/push-fixed-pr.mts +276 -0
  284. package/src/push-fixed-pr/push-fixed-pr.test.mts +228 -0
  285. package/src/report-unowned-prs/index.mts +1 -0
  286. package/src/report-unowned-prs/report-unowned-prs.mts +190 -0
  287. package/src/report-unowned-prs/report-unowned-prs.test.mts +87 -0
  288. package/src/unblock-prs/README.md +1616 -0
  289. package/src/unblock-prs/auto-fix.mts +334 -0
  290. package/src/unblock-prs/auto-fix.test.mts +159 -0
  291. package/src/unblock-prs/checks.mts +538 -0
  292. package/src/unblock-prs/checks.test.mts +507 -0
  293. package/src/unblock-prs/claims.mts +33 -0
  294. package/src/unblock-prs/claims.test.mts +64 -0
  295. package/src/unblock-prs/config.mts +187 -0
  296. package/src/unblock-prs/config.schema.json +71 -0
  297. package/src/unblock-prs/config.test.mts +139 -0
  298. package/src/unblock-prs/constants.mts +33 -0
  299. package/src/unblock-prs/demotions.mts +73 -0
  300. package/src/unblock-prs/demotions.test.mts +100 -0
  301. package/src/unblock-prs/github.mts +1042 -0
  302. package/src/unblock-prs/github.test.mts +84 -0
  303. package/src/unblock-prs/index.mts +24 -0
  304. package/src/unblock-prs/labels.mts +70 -0
  305. package/src/unblock-prs/main.mts +1051 -0
  306. package/src/unblock-prs/merge-after.mts +117 -0
  307. package/src/unblock-prs/merge-after.test.mts +120 -0
  308. package/src/unblock-prs/options.mts +157 -0
  309. package/src/unblock-prs/owner.mts +91 -0
  310. package/src/unblock-prs/owner.test.mts +114 -0
  311. package/src/unblock-prs/quiet.mts +62 -0
  312. package/src/unblock-prs/quiet.test.mts +175 -0
  313. package/src/unblock-prs/rebase.mts +962 -0
  314. package/src/unblock-prs/release.mts +149 -0
  315. package/src/unblock-prs/release.test.mts +209 -0
  316. package/src/unblock-prs/set-aside-comment.mts +143 -0
  317. package/src/unblock-prs/set-aside-comment.test.mts +148 -0
  318. package/src/unblock-prs/set-aside-detail.mts +82 -0
  319. package/src/unblock-prs/set-aside-detail.test.mts +131 -0
  320. package/src/unblock-prs/skips.mts +215 -0
  321. package/src/unblock-prs/skips.test.mts +280 -0
  322. package/src/unblock-prs/stack.mts +220 -0
  323. package/src/unblock-prs/stack.test.mts +449 -0
  324. package/src/unblock-prs/triage.mts +669 -0
  325. package/src/unblock-prs/triage.test.mts +100 -0
  326. package/src/unblock-prs/types.mts +358 -0
  327. package/src/unblock-prs/util.mts +78 -0
  328. package/src/unblock-prs/util.test.mts +26 -0
  329. package/src/unblock-prs/version-pr.mts +112 -0
  330. package/src/unblock-prs/version-pr.test.mts +78 -0
  331. package/src/unblock-prs/watch.mts +219 -0
@@ -0,0 +1,100 @@
1
+ import { describeAction, orderCandidates } from './triage.mjs';
2
+ import type { PullRequest } from './types.mjs';
3
+
4
+ describe(orderCandidates, () => {
5
+ // The ranks themselves are `pr-report-core`'s; what this run demoted is
6
+ // what only the worker knows.
7
+
8
+ // Last of all, the release included: the release is known to be able to
9
+ // merge, and this one is known to have sat green without merging.
10
+ test('puts a pull request that sat green without merging last', () => {
11
+ assert.deepStrictEqual(
12
+ numbers(
13
+ orderCandidates(
14
+ [
15
+ pullRequest({ number: 1 }),
16
+ pullRequest({
17
+ number: 2,
18
+ headRefName: 'changeset-release/main',
19
+ }),
20
+ pullRequest({ number: 3, mergeStateStatus: 'DIRTY' }),
21
+ pullRequest({ number: 4 }),
22
+ ],
23
+ { defaultBranch: 'main', demoted: new Map([[1, 'a'.repeat(40)]]) },
24
+ ),
25
+ ),
26
+ [4, 3, 2, 1],
27
+ );
28
+ });
29
+
30
+ test('forgets the demotion once someone else has pushed', () => {
31
+ assert.deepStrictEqual(
32
+ numbers(
33
+ orderCandidates(
34
+ [
35
+ pullRequest({ number: 1, headRefOid: 'c'.repeat(40) }),
36
+ pullRequest({ number: 2 }),
37
+ ],
38
+ { defaultBranch: 'main', demoted: new Map([[1, 'a'.repeat(40)]]) },
39
+ ),
40
+ ),
41
+ [1, 2],
42
+ );
43
+ });
44
+ });
45
+
46
+ describe(describeAction, () => {
47
+ test('arms what it picks', () => {
48
+ assert.strictEqual(
49
+ describeAction(pullRequest({ number: 1 }), 'main', {
50
+ stackParents: new Map(),
51
+ toArm: new Set([1]),
52
+ bypass: new Map(),
53
+ }),
54
+ 'rebase onto main, arm auto-merge',
55
+ );
56
+ });
57
+
58
+ test('leaves auto-merge off one that needs a bypass merge, and still runs its checks', () => {
59
+ assert.strictEqual(
60
+ describeAction(
61
+ pullRequest({
62
+ number: 1,
63
+ labels: [queued, { name: 'skip-ci' }],
64
+ autoMergeRequest: null,
65
+ }),
66
+ 'main',
67
+ {
68
+ stackParents: new Map(),
69
+ toArm: new Set(),
70
+ bypass: new Map([[1, 'the author is the only owner']]),
71
+ },
72
+ ),
73
+ 'rebase onto main, leave auto-merge off (the author is the only owner, so it needs a bypass merge), then take skip-ci off it',
74
+ );
75
+ });
76
+ });
77
+
78
+ const queued = { name: 'merge-queued' } as const;
79
+
80
+ const numbers = (prs: readonly PullRequest[]): readonly number[] =>
81
+ prs.map((pr) => pr.number);
82
+
83
+ const pullRequest = (
84
+ fields: Partial<PullRequest> & Readonly<{ number: number }>,
85
+ ): PullRequest =>
86
+ ({
87
+ id: `PR_${fields.number}`,
88
+ title: `pull request #${fields.number}`,
89
+ body: '',
90
+ state: 'OPEN',
91
+ headRefName: `feature/${fields.number}`,
92
+ headRefOid: 'a'.repeat(40),
93
+ baseRefName: 'main',
94
+ isCrossRepository: false,
95
+ isDraft: false,
96
+ mergeStateStatus: 'BEHIND',
97
+ autoMergeRequest: {},
98
+ labels: [queued],
99
+ ...fields,
100
+ }) as const;
@@ -0,0 +1,358 @@
1
+ // cspell:ignore unlabel retargeted
2
+
3
+ /** The shapes every module here passes around. */
4
+
5
+ import type {
6
+ Claim,
7
+ QueueContext,
8
+ QueueOwner,
9
+ RulesetRequirements,
10
+ SetAsideComment,
11
+ SetAsideReason,
12
+ ChecksSummary as SharedChecksSummary,
13
+ } from 'pr-report-core';
14
+ import * as t from 'ts-fortress';
15
+ import type { StrictPick } from 'ts-type-forge';
16
+
17
+ /** The fields read of each pull request (`github.mts`'s `listPullRequests`). */
18
+ export const PullRequestSchema = t.record({
19
+ number: t.number(),
20
+ /** The node id, which the GraphQL mutation that arms auto-merge takes. */
21
+ id: t.string(),
22
+ title: t.string(),
23
+ /** Where a `Merge-After:` declaration is read from. */
24
+ body: t.string(),
25
+ state: t.string(),
26
+ headRefName: t.string(),
27
+ headRefOid: t.string(),
28
+ baseRefName: t.string(),
29
+ /**
30
+ * Whether the head branch lives in a fork. A fork's branch is never the
31
+ * parent of a stack, and never pushed to when one is restacked.
32
+ */
33
+ isCrossRepository: t.boolean(),
34
+ isDraft: t.boolean(),
35
+ mergeStateStatus: t.string(),
36
+ autoMergeRequest: t.unknown(),
37
+ labels: t.array(t.record({ name: t.string() })),
38
+ });
39
+
40
+ export type PullRequest = t.TypeOf<typeof PullRequestSchema>;
41
+
42
+ export const PullRequestListSchema = t.array(PullRequestSchema);
43
+
44
+ /**
45
+ * What moving a branch reads of its pull request, which `push-fixed-pr` fills
46
+ * in from the REST API rather than from GraphQL.
47
+ */
48
+ export type BranchOf = StrictPick<
49
+ PullRequest,
50
+ | 'baseRefName'
51
+ | 'headRefName'
52
+ | 'headRefOid'
53
+ | 'isCrossRepository'
54
+ | 'number'
55
+ | 'state'
56
+ >;
57
+
58
+ /**
59
+ * Why a pull request was set aside: `pr-report-core`'s list, where each
60
+ * reason is given its owner (`queue-owner.mts`), so that a reason this
61
+ * script writes is one every reader knows.
62
+ */
63
+ export type SkipReason = SetAsideReason;
64
+
65
+ /**
66
+ * Why a pull request is being left alone, and the state it was in at the
67
+ * time. A record stops applying as soon as that state changes: a push to the
68
+ * branch clears every reason, and every reason but `checks-failed` and
69
+ * `awaiting-bypass` also clears when the base moves. `awaiting-bypass` is the
70
+ * one that is not a failure: every required check is green on a pull request
71
+ * nothing but a ruleset bypass will merge, and what is left is a person's
72
+ * (`pr-report-core`'s `review-hold.mts`). See `skipStillApplies`.
73
+ */
74
+ export type SkipRecord = Readonly<{
75
+ number: number;
76
+ headSha: string;
77
+ baseSha: string;
78
+ reason: SkipReason;
79
+ /** One sentence, which the log says and the comment leads with. */
80
+ detail: string;
81
+ /** For `checks-failed`: each failed required check, with its run. */
82
+ failedChecks?: ChecksSummary['failedRuns'];
83
+ /** What the command that failed printed, which the comment folds away. */
84
+ output?: string;
85
+ }>;
86
+
87
+ export type SkipRecords = ReadonlyMap<number, SkipRecord>;
88
+
89
+ /**
90
+ * One of this account's set-aside comments on a pull request, and what it
91
+ * says. `pr-report-core`'s `set-aside.mts` says what it is.
92
+ */
93
+ export type OwnSetAsideComment = Readonly<{
94
+ /** The REST id, which editing it takes. */
95
+ databaseId: number;
96
+ says: SetAsideComment;
97
+ }>;
98
+
99
+ /**
100
+ * Pull request number → the head it was demoted at: the pull requests picked
101
+ * last, after a watch saw them green and still open. See `demotions.mts`.
102
+ */
103
+ export type Demotions = ReadonlyMap<number, string>;
104
+
105
+ /**
106
+ * What the ruleset asks of a review, read by `pr-report-core`'s
107
+ * `review-hold.mts`.
108
+ */
109
+ export type ReviewRequirements = StrictPick<
110
+ RulesetRequirements,
111
+ 'requireCodeOwnerReview' | 'requireConversationResolution'
112
+ >;
113
+
114
+ export type Survey = Readonly<{
115
+ pullRequests: readonly PullRequest[];
116
+ /** The tip of the default branch at the time of the survey. */
117
+ baseSha: string;
118
+ /** The contexts the ruleset requires, empty when they cannot be read. */
119
+ requiredContexts: readonly string[];
120
+ /** Nothing is required when the rules cannot be read. */
121
+ reviewRequirements: ReviewRequirements;
122
+ }>;
123
+
124
+ /**
125
+ * `pr-report-core`'s verdict over the required contexts — the one `pr-report`
126
+ * and the page reach too — with what the worker reads besides.
127
+ */
128
+ export type ChecksSummary = SharedChecksSummary &
129
+ Readonly<{
130
+ /**
131
+ * {@link SharedChecksSummary.failed}, each with its run's link, which a
132
+ * set-aside comment points at.
133
+ */
134
+ failedRuns: readonly Readonly<{ name: string; link: string }>[];
135
+ /**
136
+ * Checks on the head that have not finished and are not among `pending`,
137
+ * mostly the jobs a required aggregate is still waiting on.
138
+ */
139
+ running: readonly string[];
140
+ }>;
141
+
142
+ /** What a survey knows before the declared merge order has been read. */
143
+ export type TriageBase = Readonly<{
144
+ defaultBranch: string;
145
+ baseSha: string;
146
+ skipped: SkipRecords;
147
+ /** The claims that still hold their pull requests (`claims.mts`). */
148
+ claimed: ReadonlyMap<number, Claim>;
149
+ demoted: Demotions;
150
+ requiredContexts: readonly string[];
151
+ reviewRequirements: ReviewRequirements;
152
+ /** What the issues the pull requests close add to the merge order. */
153
+ issueOrder: IssueOrder;
154
+ }>;
155
+
156
+ /**
157
+ * Which open issues each open pull request closes, and each such issue's
158
+ * `Start-After:`, which the pull request closing it inherits as a merge
159
+ * order (`docs/issue-start-after.md`).
160
+ */
161
+ export type IssueOrder = Readonly<{
162
+ closes: ReadonlyMap<number, readonly number[]>;
163
+ startAfter: ReadonlyMap<number, readonly number[]>;
164
+ }>;
165
+
166
+ /**
167
+ * What triage decides each pull request against: the survey, and what the
168
+ * open pull requests say about one another (`pr-report-core`'s
169
+ * `queue-order.mts`).
170
+ */
171
+ export type TriageContext = TriageBase & QueueContext;
172
+
173
+ /** What one survey says about one pull request. */
174
+ export type Classification = Readonly<
175
+ | { kind: 'candidate' }
176
+ | { kind: 'failing'; summary: ChecksSummary }
177
+ | { kind: 'held'; reason: string }
178
+ | { kind: 'ignore' }
179
+ | { kind: 'in-flight' }
180
+ | { kind: 'note'; note: string }
181
+ >;
182
+
183
+ export type Failing = Readonly<{ pr: PullRequest; summary: ChecksSummary }>;
184
+
185
+ export type Triage = Readonly<{
186
+ /** How many of the surveyed pull requests this script has anything to say about. */
187
+ inScope: number;
188
+ /**
189
+ * The queued pull requests this cycle may act on — behind the base, or
190
+ * paused by `skip-ci`, or both — with nothing they declared `Merge-After`
191
+ * on still open, and on the default branch rather than stacked. Lowest
192
+ * number first.
193
+ */
194
+ candidates: readonly PullRequest[];
195
+ /**
196
+ * The candidates and the pull requests in flight that have no auto-merge
197
+ * yet, which this script arms when it picks them. See `armOnPick` in
198
+ * `rebase.mts`.
199
+ */
200
+ toArm: ReadonlySet<number>;
201
+ /** Which open pull request each stacked one is on: child → parent. */
202
+ stackParents: ReadonlyMap<number, number>;
203
+ /**
204
+ * The candidates and the pull requests in flight that only a ruleset bypass
205
+ * will merge, with why (`pr-report-core`'s `review-hold.mts`): their checks
206
+ * are run all the same, and they are never armed.
207
+ */
208
+ bypass: ReadonlyMap<number, string>;
209
+ /** Up to date with checks still running, or clean and about to merge. */
210
+ inFlight: readonly PullRequest[];
211
+ /** Up to date, but a required check has failed. */
212
+ failing: readonly Failing[];
213
+ /**
214
+ * Queued pull requests that would have been picked or watched but for
215
+ * their review — an owner's approval or a conversation still open — each
216
+ * as a note like the ones below.
217
+ */
218
+ held: readonly string[];
219
+ /** Everything else, with the reason it was set aside. */
220
+ notes: readonly string[];
221
+ /** The `Merge-After` cycles, each starting at its lowest number. */
222
+ cycles: readonly (readonly number[])[];
223
+ /**
224
+ * Who moves each queued pull request next (`owner.mts`), the same answer
225
+ * `pr-report` gives the skill. "3a" moves only the worker's.
226
+ */
227
+ owners: ReadonlyMap<number, QueueOwner>;
228
+ }>;
229
+
230
+ /**
231
+ * One change of a pull request's base, from its timeline. `stack.mts` reads
232
+ * the last of them to tell which branch GitHub moved a layer off.
233
+ */
234
+ export type BaseChange = Readonly<{ from: string; to: string }>;
235
+
236
+ export type WatchOutcome =
237
+ | 'auto-merge-disabled'
238
+ | 'awaiting-bypass'
239
+ | 'behind-again'
240
+ | 'checks-failed'
241
+ | 'closed'
242
+ | 'error'
243
+ | 'head-moved'
244
+ | 'merged'
245
+ | 'not-merging'
246
+ | 'stopped'
247
+ | 'timeout'
248
+ | 'skip-ci-labelled';
249
+
250
+ /**
251
+ * How a watch ended, and for an outcome that sets the pull request aside,
252
+ * what its comment says: `detail` and, when checks failed, which.
253
+ */
254
+ export type Watched = Readonly<{
255
+ outcome: WatchOutcome;
256
+ detail?: string;
257
+ failedChecks?: ChecksSummary['failedRuns'];
258
+ }>;
259
+
260
+ /**
261
+ * Pull request number → the branch it was last seen stacked on: every layer
262
+ * of a stack, and one whose parent has left the open list while GitHub has
263
+ * yet to move it off that parent's branch. A layer that leaves this map by
264
+ * landing on the default branch is one `stack.mts` may have to rebase off
265
+ * the layer that merged below it.
266
+ */
267
+ export type StackedOn = ReadonlyMap<number, string>;
268
+
269
+ /**
270
+ * A pull request on the default branch that may have been moved there when
271
+ * the layer below it merged: `from` is that layer's branch, or `undefined`
272
+ * when it has to be read from the pull request's timeline.
273
+ */
274
+ export type RetargetedLayer = Readonly<{
275
+ pr: PullRequest;
276
+ from: string | undefined;
277
+ }>;
278
+
279
+ /** What the loop remembers about how long the list has sat still. */
280
+ export type Quiet = Readonly<{
281
+ /** The last survey's fingerprint; none before any survey has succeeded. */
282
+ fingerprint: string | undefined;
283
+ /** How many surveys in a row have seen what the one before them saw. */
284
+ unchanged: number;
285
+ }>;
286
+
287
+ /**
288
+ * What one run carries from cycle to cycle: the pull requests it has given up
289
+ * on, the ones it picks last, the ones it has acted on and last saw open,
290
+ * where the base was when it last looked, how long the list has sat still,
291
+ * and which pull requests were stacked on what.
292
+ */
293
+ export type LoopState = Readonly<{
294
+ skipped: SkipRecords;
295
+ demoted: Demotions;
296
+ tracked: ReadonlySet<number>;
297
+ baseSha: string | undefined;
298
+ quiet: Quiet;
299
+ /** `undefined` until the first survey, which has nothing to compare with. */
300
+ stackedOn: StackedOn | undefined;
301
+ }>;
302
+
303
+ export type CycleResult = Readonly<{
304
+ state: LoopState;
305
+ next: 'idle' | 'stop' | 'survey';
306
+ /**
307
+ * The records the cycle acted from — what it carried over, less what no
308
+ * longer applied, plus what the comments on the pull requests said. A
309
+ * record in `state` that is not among these, or differs from its entry, is
310
+ * one this cycle set aside, and the one its comment has to say.
311
+ */
312
+ settled: SkipRecords;
313
+ }>;
314
+
315
+ /**
316
+ * How far `advance` got with one pull request. `moved` is the branch moving
317
+ * under it — someone else, the skill or a person, is taking that pull
318
+ * request forward — which is neither progress nor a failure of the pull
319
+ * request.
320
+ */
321
+ export type Advanced = Readonly<
322
+ | { kind: 'advanced'; head: string }
323
+ | { kind: 'moved' }
324
+ | { kind: 'stale-merge-state' }
325
+ >;
326
+
327
+ /**
328
+ * What `autoFix` did about a failed head. `declined` covers everything that
329
+ * is not a fixer's diff and everything that went wrong on the way; either way
330
+ * the failure stands and is the skill's. `moved` is what it is for `advance`.
331
+ */
332
+ export type AutoFixed = Readonly<
333
+ | { kind: 'declined'; detail: string }
334
+ | { kind: 'moved' }
335
+ | { kind: 'pushed'; head: string; commands: readonly string[] }
336
+ >;
337
+
338
+ export type RebaseFailure = Readonly<{
339
+ reason:
340
+ | 'already-in-base'
341
+ | 'arm-failed'
342
+ | 'push-failed'
343
+ | 'rebase-failed'
344
+ | 'unlabel-failed';
345
+ detail: string;
346
+ /** What the command that failed printed, when there is more than `detail`. */
347
+ output?: string;
348
+ }>;
349
+
350
+ /**
351
+ * What `advance` is asked to do besides the rebase: arm auto-merge, and carry
352
+ * the layers stacked on it along.
353
+ */
354
+ export type AdvancePlan = Readonly<{
355
+ arm: boolean;
356
+ /** Every open layer above it, each after the one it is on. */
357
+ descendants: readonly PullRequest[];
358
+ }>;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The small things every other module here needs: the stop signal and what
3
+ * listens to it. Quoting and logging are shared with the other commands, in
4
+ * `tools/scripts/lib/`.
5
+ */
6
+
7
+ import { setTimeout as sleep } from 'node:timers/promises';
8
+ import { log } from '../lib/index.mjs';
9
+
10
+ /**
11
+ * Aborted by SIGINT / SIGTERM; every sleep listens to it.
12
+ *
13
+ * One per process, and it is never reset: an `AbortController` cannot be
14
+ * un-aborted, and nothing here wants to be. The stop it carries means "this
15
+ * run is over" — the loop in `main.mts` leaves, the process exits, and a
16
+ * second SIGINT does not wait for any of that (`installStopHandlers` arms
17
+ * `process.exit(130)` for it). The signal handlers it serves are
18
+ * process-scoped too, so a controller of the same scope is the honest shape.
19
+ *
20
+ * What that rules out is calling the loop twice in one process: the second
21
+ * call would find the signal already aborted and return immediately. So the
22
+ * loop has one caller, the `unblock-prs` command (`src/cmd/unblock-prs.mts`),
23
+ * which runs it once, and it is not exported from the package.
24
+ */
25
+ const stopController = new AbortController();
26
+
27
+ /**
28
+ * Read through a call rather than directly, because the flag flips inside a
29
+ * signal handler and TypeScript would otherwise narrow it to `false` for the
30
+ * rest of any block that has already tested it.
31
+ */
32
+ export const stopRequested = (): boolean => stopController.signal.aborted;
33
+
34
+ /**
35
+ * The SHA `git ls-remote` lists for `branch`, by its whole ref name. A ref
36
+ * pattern given to `ls-remote` matches on the tail of the name, so
37
+ * `ls-remote origin main` lists `refs/heads/changeset-release/main` too, and
38
+ * first; the first line is not the branch asked for.
39
+ */
40
+ export const headShaFromListing = (
41
+ listing: string,
42
+ branch: string,
43
+ ): string | undefined =>
44
+ listing
45
+ .split('\n')
46
+ .map((line) => line.trim().split(/\s+/u))
47
+ .find(([, ref]) => ref === `refs/heads/${branch}`)?.[0];
48
+
49
+ export const lastLines = (text: string, count: number): string =>
50
+ text.trim().split('\n').slice(-count).join('\n');
51
+
52
+ /** Sleeps, returning early when a stop has been requested. */
53
+ export const pause = async (ms: number): Promise<void> => {
54
+ if (stopRequested()) {
55
+ return;
56
+ }
57
+
58
+ await sleep(ms, undefined, { signal: stopController.signal }).catch(
59
+ () => undefined,
60
+ );
61
+ };
62
+
63
+ export const installStopHandlers = (): void => {
64
+ process.once('SIGINT', onSignal);
65
+
66
+ process.once('SIGTERM', onSignal);
67
+ };
68
+
69
+ const onSignal = (signal: NodeJS.Signals): void => {
70
+ log(`${signal}: finishing the current step, then stopping.`);
71
+
72
+ stopController.abort();
73
+
74
+ // A second signal is not a request to be tidy.
75
+ process.once(signal, () => {
76
+ process.exit(130);
77
+ });
78
+ };
@@ -0,0 +1,26 @@
1
+ import { headShaFromListing } from './util.mjs';
2
+
3
+ describe(headShaFromListing, () => {
4
+ const listing = [
5
+ '870363ea81583350b5f32664fa27c27fd7b6c9b2\trefs/heads/changeset-release/main',
6
+ '71f48d84b3fffa6a45b654836e9357153e1e949e\trefs/heads/main',
7
+ ].join('\n');
8
+
9
+ test('reads the branch named, not one whose name ends with it', () => {
10
+ assert.strictEqual(
11
+ headShaFromListing(listing, 'main'),
12
+ '71f48d84b3fffa6a45b654836e9357153e1e949e',
13
+ );
14
+
15
+ assert.strictEqual(
16
+ headShaFromListing(listing, 'changeset-release/main'),
17
+ '870363ea81583350b5f32664fa27c27fd7b6c9b2',
18
+ );
19
+ });
20
+
21
+ test('is undefined for a branch the listing does not have', () => {
22
+ assert.isUndefined(headShaFromListing(listing, 'release/main'));
23
+
24
+ assert.isUndefined(headShaFromListing('', 'main'));
25
+ });
26
+ });
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The version pull request — `changeset-release/<base>`, opened and rewritten
3
+ * by `changesets/action` — and what holds it back.
4
+ *
5
+ * It is the one pull request here that is derived rather than written. On
6
+ * every push to the base the release workflow rebuilds the branch from the
7
+ * tip, force-pushes it, and overwrites the title and body through
8
+ * `updatePullRequest`. Two consequences shape everything in this file:
9
+ *
10
+ * - **Nothing written in its body survives.** A `Merge-After:` trailer put
11
+ * there is wiped by the next push to the base — which, when several pull
12
+ * requests are queued, is exactly while the ordering still mattered. So the
13
+ * ordering is declared from the other side, with `blocks-release` on the
14
+ * pull request the release is waiting for.
15
+ * - **It must not be rebased.** `unblock-prs` and the release workflow would
16
+ * be two things force-pushing one branch. Worse, a rebase lands the old
17
+ * version commit on a tip that carries a changeset it never consumed, so
18
+ * what merges is a release missing the change the queue was assembled for.
19
+ * Whether the branch is ready is not this script's to fix: it is the
20
+ * release workflow's, and the only thing to do here is wait for it.
21
+ */
22
+
23
+ import { BLOCKS_RELEASE_LABEL, isVersionBranch } from 'pr-report-core';
24
+ import { Result } from 'ts-data-forge';
25
+ import { git, sh } from '../lib/index.mjs';
26
+ import type { Classification, PullRequest, TriageContext } from './types.mjs';
27
+ import { lastLines } from './util.mjs';
28
+
29
+ /**
30
+ * Whether this is the version pull request, by the branch it comes from
31
+ * (`isVersionBranch`).
32
+ */
33
+ export const isVersionPullRequest = (
34
+ pr: PullRequest,
35
+ defaultBranch: string,
36
+ ): boolean => isVersionBranch(pr.headRefName, defaultBranch);
37
+
38
+ /**
39
+ * What to report while pull requests labelled `blocks-release` are open.
40
+ * Nothing here can clear it: a `blocks-release` pull request ends when a
41
+ * person merges or closes it.
42
+ */
43
+ export const releaseBlockedNote = (
44
+ pr: PullRequest,
45
+ by: readonly Readonly<{ number: number; isDraft: boolean }>[],
46
+ ): Classification =>
47
+ ({
48
+ kind: 'note',
49
+ note: `#${pr.number}: ${BLOCKS_RELEASE_LABEL} on ${by
50
+ .map(
51
+ (blocker) => `#${blocker.number}${blocker.isDraft ? ' (draft)' : ''}`,
52
+ )
53
+ .join(', ')} — still open, so the release waits`,
54
+ }) as const;
55
+
56
+ /**
57
+ * What to report while the branch was built on a stale tip, which the
58
+ * release workflow's next run rebuilds.
59
+ */
60
+ export const releaseStaleNote = (
61
+ pr: PullRequest,
62
+ context: Readonly<{ defaultBranch: string; baseSha: string }>,
63
+ ): Classification =>
64
+ ({
65
+ kind: 'note',
66
+ note: `#${pr.number}: built before ${context.defaultBranch} reached ${context.baseSha.slice(0, 10)}; waiting for the release workflow to rebuild it`,
67
+ }) as const;
68
+
69
+ /**
70
+ * Whether the branch was built on the current tip of the base — the question
71
+ * `mergeStateStatus` is asked everywhere else here, asked of git instead —
72
+ * or, when git cannot say, the note to report.
73
+ *
74
+ * GitHub's answer is computed asynchronously and cached, and being wrong
75
+ * about this one releases a set the branch was built before, so it is worth
76
+ * a fetch. `git merge-base` rather than `--is-ancestor`, whose "no" is an
77
+ * exit status this module could not tell apart from a git that failed.
78
+ */
79
+ export const isRebuiltOnBaseTip = async (
80
+ pr: PullRequest,
81
+ context: TriageContext,
82
+ ): Promise<Result<boolean, Classification>> => {
83
+ const answered = await mergeBaseIsTip(pr, context);
84
+
85
+ return Result.isErr(answered)
86
+ ? Result.err({
87
+ kind: 'note',
88
+ note: `#${pr.number}: cannot tell whether it was rebuilt on ${context.baseSha.slice(0, 10)}: ${answered.value}`,
89
+ })
90
+ : answered;
91
+ };
92
+
93
+ const mergeBaseIsTip = async (
94
+ pr: PullRequest,
95
+ context: TriageContext,
96
+ ): Promise<Result<boolean, string>> => {
97
+ const fetched = await git(
98
+ `git fetch --quiet origin ${sh(context.defaultBranch)} ${sh(pr.headRefName)}`,
99
+ );
100
+
101
+ if (Result.isErr(fetched)) {
102
+ return Result.err(`fetch failed: ${lastLines(fetched.value, 2)}`);
103
+ }
104
+
105
+ const mergeBase = await git(
106
+ `git merge-base ${sh(context.baseSha)} ${sh(pr.headRefOid)}`,
107
+ );
108
+
109
+ return Result.isErr(mergeBase)
110
+ ? Result.err(`merge-base failed: ${lastLines(mergeBase.value, 2)}`)
111
+ : Result.ok(mergeBase.value.trim() === context.baseSha);
112
+ };