├── .gitignore ├── inverse_fdp ├── build.rs ├── Cargo.toml ├── FuzzedDataProvider.example.patch └── Cargo.lock ├── README.md ├── llm_eval ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── check_translations ├── Cargo.toml ├── Cargo.lock └── src │ └── main.rs ├── fuzz_gen ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── coverage ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── host_reports ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── coverage_fuzz ├── Cargo.toml ├── Cargo.lock └── src │ └── main.rs ├── depends_cache ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── guix └── Cargo.toml ├── webhook_features ├── src │ ├── errors.rs │ ├── config.rs │ ├── features │ │ ├── mod.rs │ │ ├── labels.rs │ │ ├── ci_status.rs │ │ └── spam_detection.rs │ └── main.rs ├── Cargo.toml └── config.yml ├── util ├── Cargo.toml └── src │ └── lib.rs ├── conflicts ├── config.yml ├── Cargo.toml └── src │ └── main.rs ├── rerun_ci ├── Cargo.toml └── src │ └── main.rs ├── .github └── workflows │ └── ci.yml ├── lock_archive ├── Cargo.toml └── src │ └── main.rs ├── stale ├── Cargo.toml ├── config.yml └── src │ └── main.rs ├── LICENSE └── sync_bips_repo └── bips_mediawiki.py /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /inverse_fdp/build.rs: -------------------------------------------------------------------------------- 1 | // Requires: 2 | // cargo clean --package=inverse_fdp && cargo test 3 | extern crate cpp_build; 4 | fn main() { 5 | cpp_build::build("src/main.rs"); 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Experimental bot scripts to help to maintain Bitcoin Core (or other large open 2 | source projects). 3 | 4 | example cmd 5 | ----------- 6 | 7 | ``` 8 | ( cd rerun_ci && cargo run -- --help ) 9 | ``` 10 | -------------------------------------------------------------------------------- /inverse_fdp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inverse_fdp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | build = "build.rs" 6 | 7 | [dependencies] 8 | cpp = "0" 9 | 10 | [build-dependencies] 11 | cpp_build = "0" 12 | -------------------------------------------------------------------------------- /llm_eval/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "llm_eval" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | clap = { version = "4", features = ["derive"] } 8 | serde_json = "1" 9 | util = { path = "../util" } 10 | -------------------------------------------------------------------------------- /check_translations/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "check_translations" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4", features = ["derive"] } 8 | serde_json = "1" 9 | sha2 = "0" 10 | -------------------------------------------------------------------------------- /fuzz_gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz_gen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap ={ version = "4", features = ["derive"] } 10 | util = { path = "../util" } 11 | -------------------------------------------------------------------------------- /coverage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coverage" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.4.6", features = ["derive"] } 10 | util = { path = "../util" } 11 | -------------------------------------------------------------------------------- /host_reports/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "host_reports" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap ={ version = "4", features = ["derive"] } 10 | util = { path = "../util" } 11 | -------------------------------------------------------------------------------- /coverage_fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coverage_fuzz" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.4.6", features = ["derive"] } 10 | util = { path = "../util" } 11 | -------------------------------------------------------------------------------- /depends_cache/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "depends_cache" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4", features = ["derive"] } 10 | serde = "1" 11 | util = { path = "../util" } 12 | -------------------------------------------------------------------------------- /guix/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "guix" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4", features = ["derive"] } 8 | octocrab = { git = "https://github.com/XAMPPRocky/octocrab", branch = "main" } 9 | tokio = { version = "1", features = ["full"] } 10 | util = { path = "../util" ,features=["github"]} 11 | -------------------------------------------------------------------------------- /webhook_features/src/errors.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | pub type Result = anyhow::Result; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum DrahtBotError { 7 | #[error("IO Error: {0}")] 8 | IOError(#[from] std::io::Error), 9 | #[error("GitHub Error {0}")] 10 | GitHubError(#[from] octocrab::Error), 11 | #[error("Key not found")] 12 | KeyNotFound, 13 | } 14 | -------------------------------------------------------------------------------- /webhook_features/src/config.rs: -------------------------------------------------------------------------------- 1 | #[derive(serde::Deserialize)] 2 | pub struct Repo { 3 | pub repo_slug: String, 4 | pub backport_label: Option, 5 | pub repo_labels: std::collections::HashMap>, 6 | pub spam_detection: bool, 7 | pub ci_status: bool, 8 | pub corecheck: bool, 9 | } 10 | 11 | #[derive(serde::Deserialize)] 12 | pub struct Config { 13 | pub repositories: Vec, 14 | } 15 | -------------------------------------------------------------------------------- /util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "util" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | futures = { version="0.3", optional=true } 10 | octocrab = { git = "https://github.com/XAMPPRocky/octocrab", branch = "main", optional=true } 11 | serde_json = "1" 12 | 13 | [features] 14 | github = ["dep:futures","dep:octocrab"] 15 | -------------------------------------------------------------------------------- /conflicts/config.yml: -------------------------------------------------------------------------------- 1 | conflicts_heading: "Conflicts" 2 | # The description should contain {conflicts}, which will be substituted 3 | conflicts_description: | 4 | Reviewers, this pull request conflicts with the following ones: 5 | {conflicts} 6 | 7 | If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. 8 | conflicts_empty: | 9 | No conflicts as of last run. 10 | -------------------------------------------------------------------------------- /rerun_ci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rerun_ci" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap ={ version = "4", features = ["derive"] } 10 | octocrab = { git = "https://github.com/XAMPPRocky/octocrab", branch = "main" } 11 | serde_json = "1" 12 | tokio = { version = "1", features = ["full"] } 13 | util = { path = "../util" ,features=["github"]} 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: nightly 2 | on: 3 | push: 4 | pull_request: 5 | 6 | concurrency: 7 | group: ${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | dummy: 12 | name: 'Checkout' 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 5 15 | 16 | steps: 17 | - name: Checkout repo 18 | uses: actions/checkout@v5 19 | with: 20 | fetch-depth: 1 21 | 22 | - name: Run true 23 | run: | 24 | true 25 | -------------------------------------------------------------------------------- /lock_archive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lock_archive" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | chrono = "0.4" 10 | clap = { version = "4", features = ["derive"] } 11 | octocrab = { git = "https://github.com/XAMPPRocky/octocrab", branch = "main" } 12 | serde_json = "1" 13 | tokio = { version = "1", features = ["full"] } 14 | util = { path = "../util" ,features=["github"]} 15 | -------------------------------------------------------------------------------- /conflicts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "conflicts" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4", features = ["derive"] } 10 | octocrab = { git = "https://github.com/XAMPPRocky/octocrab", branch = "main" } 11 | serde = "1" 12 | serde_yaml = "0.9" 13 | tempfile = "3" 14 | tokio = { version = "1", features = ["full"] } 15 | util = { path = "../util", features=["github"] } 16 | -------------------------------------------------------------------------------- /stale/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stale" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | chrono = "0.4" 10 | clap = { version = "4", features = ["derive"] } 11 | octocrab = { git = "https://github.com/XAMPPRocky/octocrab", branch = "main" } 12 | serde = "1" 13 | serde_json = "1" 14 | serde_yaml = "0.9" 15 | tokio = { version = "1", features = ["full"] } 16 | util = { path = "../util" ,features=["github"]} 17 | -------------------------------------------------------------------------------- /webhook_features/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webhook_features" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | actix-web = "4" 10 | anyhow = { version = "1", features = ["backtrace"] } 11 | async-trait = "0" 12 | chrono = "0" 13 | clap = { version = "4", features = ["derive"] } 14 | lazy_static = "1" 15 | octocrab = { features = ["stream"], git = "https://github.com/XAMPPRocky/octocrab", branch = "main" } 16 | regex = "1" 17 | reqwest = { version = "0", features = ["json"] } 18 | serde = "1" 19 | serde_json = "1" 20 | serde_yaml = "0" 21 | strum = { version = "0", features = ["derive"] } 22 | strum_macros = "0" 23 | thiserror = "1" 24 | tokio = { version = "1", features = ["sync"] } 25 | util = { path = "../util" ,features=["github"]} 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present DrahtBot contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webhook_features/src/features/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ci_status; 2 | pub mod labels; 3 | pub mod spam_detection; 4 | pub mod summary_comment; 5 | 6 | use crate::errors::Result; 7 | use crate::Context; 8 | use crate::GitHubEvent; 9 | use async_trait::async_trait; 10 | 11 | pub struct FeatureMeta { 12 | name: &'static str, 13 | description: &'static str, 14 | events: Vec, 15 | } 16 | 17 | impl FeatureMeta { 18 | pub fn new(name: &'static str, description: &'static str, events: Vec) -> Self { 19 | Self { 20 | name, 21 | description, 22 | events, 23 | } 24 | } 25 | 26 | pub fn name(&self) -> &'static str { 27 | self.name 28 | } 29 | 30 | pub fn description(&self) -> &'static str { 31 | self.description 32 | } 33 | 34 | pub fn events(&self) -> &Vec { 35 | &self.events 36 | } 37 | } 38 | 39 | #[async_trait] 40 | pub trait Feature { 41 | fn meta(&self) -> &FeatureMeta; 42 | async fn handle( 43 | &self, 44 | ctx: &Context, 45 | event: &GitHubEvent, 46 | payload: &serde_json::Value, 47 | ) -> Result<()>; 48 | } 49 | -------------------------------------------------------------------------------- /host_reports/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(clap::Parser)] 4 | #[command(about = "Pull a git repository with static web content and move it to /var/www/... .", long_about = None)] 5 | struct Args { 6 | /// The repo slug of the remote on GitHub for reports. 7 | #[arg(long)] 8 | repo_report: util::Slug, 9 | /// The local scratch folder. 10 | #[arg(long)] 11 | host_reports_scratch: std::path::PathBuf, 12 | /// Print changes/edits, only modify the scratch folder. 13 | #[arg(long, default_value_t = false)] 14 | dry_run: bool, 15 | } 16 | 17 | fn main() { 18 | let args = Args::parse(); 19 | 20 | println!(); 21 | println!("See guix script for instructions on how to add write permission for /var/www to the current user"); 22 | println!(); 23 | 24 | let repo_url = format!("https://github.com/{}", args.repo_report.str()); 25 | let host_reports_www_folder = if args.dry_run { 26 | args.host_reports_scratch.join("www_output/") 27 | } else { 28 | std::path::Path::new("/var/www/html/host_reports/").join(args.repo_report.str()) 29 | }; 30 | 31 | if !host_reports_www_folder.is_dir() { 32 | println!( 33 | "Clone {repo_url} repo to {dir}", 34 | dir = host_reports_www_folder.display() 35 | ); 36 | util::check_call( 37 | util::git() 38 | .args(["clone", "--quiet", &repo_url]) 39 | .arg(&host_reports_www_folder), 40 | ); 41 | } 42 | 43 | println!("Fetch upsteam, checkout latest `main` branch"); 44 | util::chdir(&host_reports_www_folder); 45 | util::check_call(util::git().args(["fetch", "--quiet", "--all"])); 46 | util::check_call(util::git().args(["checkout", "origin/main"])); 47 | util::check_call(util::git().args(["reset", "--hard", "HEAD"])); 48 | } 49 | -------------------------------------------------------------------------------- /lock_archive/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(clap::Parser)] 4 | #[command(about = "Lock discussion on inactive closed issues and pull requests.", long_about = None)] 5 | struct Args { 6 | /// The access token for GitHub. 7 | #[arg(long)] 8 | github_access_token: Option, 9 | /// The repo slugs of the remotes on GitHub. Format: owner/repo 10 | #[arg(long)] 11 | github_repo: Vec, 12 | /// Lock a closed issue or pull request after this many days of inactivity 13 | #[arg(long, default_value_t = 365)] 14 | inactive_days: i64, 15 | /// Print changes/edits instead of calling the GitHub API. 16 | #[arg(long, default_value_t = false)] 17 | dry_run: bool, 18 | } 19 | 20 | #[tokio::main] 21 | async fn main() -> octocrab::Result<()> { 22 | let args = Args::parse(); 23 | 24 | let github = util::get_octocrab(args.github_access_token)?; 25 | 26 | let cutoff = { chrono::Utc::now() - chrono::Duration::days(args.inactive_days) }.format("%F"); 27 | println!("Locking before date {} ...", cutoff); 28 | 29 | for util::Slug { owner, repo } in args.github_repo { 30 | println!("Get closed issues and pull requests for {owner}/{repo} ..."); 31 | let items = github 32 | .all_pages( 33 | github 34 | .search() 35 | .issues_and_pull_requests(&format!( 36 | "repo:{owner}/{repo} is:unlocked is:closed updated:<={cutoff}" 37 | )) 38 | .send() 39 | .await?, 40 | ) 41 | .await?; 42 | let issues_api = github.issues(&owner, &repo); 43 | for (i, item) in items.iter().enumerate() { 44 | println!( 45 | "{}/{} (Item: {}/{}#{})", 46 | i, 47 | items.len(), 48 | owner, 49 | repo, 50 | item.number, 51 | ); 52 | if !args.dry_run { 53 | issues_api.lock(item.number, None).await?; 54 | } 55 | } 56 | } 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /stale/config.yml: -------------------------------------------------------------------------------- 1 | # Comment on a pull request needing rebase after this many days of inactivity 2 | inactive_rebase_days: 89 3 | inactive_rebase_comment: | 4 | ⌛ There hasn't been much activity lately and the patch still needs rebase. What is the status here? 5 | 6 | * Is it still relevant? ➡️ Please solve the conflicts to make it ready for review and to ensure the CI passes. 7 | * Is it no longer relevant? ➡️ Please close. 8 | * Did the author lose interest or time to work on this? ➡️ Please close it and mark it with one of the labels 'Up for grabs' or 'Insufficient Review', so that it can be picked up in the future. 9 | inactive_ci_days: 90 10 | inactive_ci_comment: | 11 | 🤔 There hasn't been much activity lately and the CI seems to be failing. 12 | 13 | If no one reviewed the current pull request by commit hash, a [rebase](https://github.com/{owner}/{repo}/blob/master/CONTRIBUTING.md#rebasing-changes) can be considered. While the CI failure may be a false positive, the CI hasn't been running for some time, so there may be a real issue hiding as well. A rebase triggers the latest CI and makes sure that no silent merge conflicts have snuck in. 14 | inactive_stale_days: 180 15 | # The comment may contain {owner} and {repo}, which will be substituted 16 | inactive_stale_comment: | 17 | There hasn't been much activity lately. What is the status here? 18 | 19 | [Finding reviewers](https://github.com/{owner}/{repo}/blob/master/CONTRIBUTING.md#finding-reviewers) may take time. However, if the patch is no longer relevant, please close this pull request. If the author lost interest or time to work on this, please close it and mark it with one of the labels 'Up for grabs' or 'Insufficient Review', so that it can be picked up in the future. 20 | # Apply the label and comment to indicate a rebase is required 21 | needs_rebase_label: "Needs rebase" 22 | ci_failed_label: "CI failed" 23 | # The comment may contain {owner} and {repo}, which will be substituted 24 | needs_rebase_comment: | 25 | 🐙 This pull request conflicts with the target branch and [needs rebase](https://github.com/{owner}/{repo}/blob/master/CONTRIBUTING.md#rebasing-changes). 26 | -------------------------------------------------------------------------------- /webhook_features/config.yml: -------------------------------------------------------------------------------- 1 | repositories: 2 | - repo_slug: maflcko/DrahtBot 3 | backport_label: Backport 4 | repo_labels: 5 | Dummy: 6 | - '^dummy:' 7 | spam_detection: true 8 | ci_status: true 9 | corecheck: false 10 | - repo_slug: bitcoin-core/gui 11 | backport_label: null 12 | repo_labels: 13 | spam_detection: true 14 | ci_status: true 15 | corecheck: false 16 | - repo_slug: bitcoin/bitcoin 17 | backport_label: Backport 18 | # labels taken from https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#creating-the-pull-request 19 | repo_labels: 20 | Build system: 21 | - '^guix:' 22 | - '^build:' 23 | - '^cmake:' 24 | - '^depends:' 25 | TX fees and policy: 26 | - '^fees:' 27 | - '^policy:' 28 | Utils/log/libs: 29 | - '^log:' 30 | - '^util:' 31 | - '^random:' 32 | - '^crypto:' 33 | - '^libs:' 34 | - '^compat:' 35 | UTXO Db and Indexes: 36 | - '^index:' 37 | - '^indexes:' 38 | - '^txdb:' 39 | - '^coins:' 40 | - '^db:' 41 | Block storage: 42 | - '^blockstorage:' 43 | PSBT: 44 | - '^psbt:' 45 | Validation: 46 | - '^validation:' 47 | - '^chain:' 48 | - '^kernel:' 49 | interfaces: 50 | - '^interfaces:' 51 | - '^multiprocess:' 52 | Wallet: 53 | - '^wallet:' 54 | Descriptors: 55 | - '^descriptor:' 56 | - '^descriptors:' 57 | - '^miniscript:' 58 | Consensus: 59 | - '^consensus:' 60 | - '^versionbits:' 61 | - '^interpreter:' 62 | - '^script:' 63 | - '^sigcache:' 64 | GUI: 65 | - '^gui:' 66 | - '^qt:' 67 | Mempool: 68 | - '^mempool:' 69 | - '^txmempool:' 70 | Mining: 71 | - '^mining:' 72 | - '^miner:' 73 | P2P: 74 | - '^net:' 75 | - '^p2p:' 76 | - '^tor:' 77 | - '^addrman:' 78 | - '^protocol:' 79 | - '^net processing:' 80 | RPC/REST/ZMQ: 81 | - '^univalue:' 82 | - '^rpc:' 83 | - '^rest:' 84 | - '^zmq:' 85 | - '^http:' 86 | Scripts and tools: 87 | - '^contrib:' 88 | - '^tool:' 89 | - '^tools:' 90 | - '^cli:' 91 | Fuzzing: 92 | - '^fuzz:' 93 | Tests: 94 | - '^lint:' 95 | - '^qa:' 96 | - '^tests?:' 97 | - '^ci:' 98 | - '^bench:' 99 | - '^cirrus:' 100 | Docs: 101 | - '^docs?:' 102 | Backport: 103 | - '^backport:' 104 | Refactoring: 105 | - '^refactor(ing)?:' 106 | - '^move-?only:' 107 | - '^scripted-diff:' 108 | spam_detection: true 109 | ci_status: true 110 | corecheck: true 111 | -------------------------------------------------------------------------------- /depends_cache/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(clap::Parser)] 4 | #[command(about = "Fetch Bitcoin Core depends sources and move them to /var/www/.", long_about = None)] 5 | struct Args { 6 | /// The repo slug of the remote on GitHub. Format: owner/repo 7 | #[arg(long)] 8 | github_repo: util::Slug, 9 | /// The git ref to checkout and fetch the depends from. 10 | #[arg(long, default_value = "origin/master")] 11 | git_ref: String, 12 | /// The local dir used for scratching. 13 | #[arg(long)] 14 | scratch_dir: std::path::PathBuf, 15 | /// Print changes/edits instead of moving the files. 16 | #[arg(long, default_value_t = false)] 17 | dry_run: bool, 18 | } 19 | 20 | fn main() -> Result<(), std::io::Error> { 21 | let args = Args::parse(); 22 | 23 | println!(); 24 | println!("Same setup as the guix builds."); 25 | println!(); 26 | 27 | let git_remote_url = format!("https://github.com/{}", args.github_repo.str()); 28 | let www_folder_depends_caches = 29 | std::path::Path::new("/var/www/html/depends_download_fallback/"); 30 | std::fs::create_dir_all(&args.scratch_dir).expect("invalid scratch_dir"); 31 | let git_repo_dir = args 32 | .scratch_dir 33 | .canonicalize() 34 | .expect("invalid scratch_dir") 35 | .join("git_repo"); 36 | let temp_dir = git_repo_dir.parent().unwrap(); 37 | 38 | if !args.dry_run { 39 | println!( 40 | "Create folder {} if it does not exist", 41 | www_folder_depends_caches.display() 42 | ); 43 | std::fs::create_dir_all(www_folder_depends_caches)?; 44 | } 45 | if !git_repo_dir.is_dir() { 46 | println!( 47 | "Clone {} repo to {}", 48 | git_remote_url, 49 | git_repo_dir.display() 50 | ); 51 | util::chdir(temp_dir); 52 | util::check_call( 53 | util::git() 54 | .args(["clone", "--quiet", &git_remote_url]) 55 | .arg(&git_repo_dir), 56 | ); 57 | } 58 | 59 | println!("Fetch upsteam, checkout {}", args.git_ref); 60 | util::chdir(&git_repo_dir); 61 | util::check_call(util::git().args(["fetch", "--quiet", "--all"])); 62 | util::check_call(util::git().args(["checkout", &args.git_ref])); 63 | 64 | println!("Download dependencies ..."); 65 | util::chdir(&git_repo_dir.join("depends")); 66 | std::env::set_var("MULTIPROCESS", "1"); 67 | util::check_call(std::process::Command::new("make").arg("download")); 68 | let source_dir = git_repo_dir.join("depends").join("sources"); 69 | println!( 70 | "Merging results of {} to {}", 71 | source_dir.display(), 72 | www_folder_depends_caches.display() 73 | ); 74 | for entry in std::fs::read_dir(source_dir)? { 75 | let entry = entry?; 76 | if !entry.path().is_file() { 77 | continue; 78 | } 79 | println!(" ... entry = {}", entry.file_name().to_string_lossy()); 80 | if !args.dry_run { 81 | std::fs::copy( 82 | entry.path(), 83 | www_folder_depends_caches.join(entry.file_name()), 84 | )?; 85 | } 86 | } 87 | Ok(()) 88 | } 89 | -------------------------------------------------------------------------------- /inverse_fdp/FuzzedDataProvider.example.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/test/fuzz/FuzzedDataProvider.h b/src/test/fuzz/FuzzedDataProvider.h 2 | index 11f2fbdb8c..9535297f0a 100644 3 | --- a/src/test/fuzz/FuzzedDataProvider.h 4 | +++ b/src/test/fuzz/FuzzedDataProvider.h 5 | @@ -21,6 +21,7 @@ 6 | #include 7 | #include 8 | #include 9 | +#include 10 | #include 11 | #include 12 | #include 13 | @@ -141,6 +142,10 @@ inline std::string FuzzedDataProvider::ConsumeBytesAsString(size_t num_bytes) { 14 | num_bytes = std::min(num_bytes, remaining_bytes_); 15 | std::string result( 16 | reinterpret_cast(data_ptr_), num_bytes); 17 | + std::cout << std::hex << "ifdp.push_bytes(&["; 18 | + for (uint8_t c : result) 19 | + std::cout << "0x" << unsigned(c) << ","; 20 | + std::cout << std::dec << "]); // (len=" << num_bytes << ")\n"; 21 | Advance(num_bytes); 22 | return result; 23 | } 24 | @@ -174,6 +179,10 @@ FuzzedDataProvider::ConsumeRandomLengthString(size_t max_length) { 25 | } 26 | 27 | result.shrink_to_fit(); 28 | + std::cout << std::hex << "ifdp.push_str_u8(&["; 29 | + for (uint8_t c : result) 30 | + std::cout << "0x" << unsigned(c) << ","; 31 | + std::cout << std::dec << "]); // (len=" << result.size() << "), Limit: " << max_length << "\n"; 32 | return result; 33 | } 34 | 35 | @@ -231,6 +240,26 @@ T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) { 36 | if (range != std::numeric_limits::max()) 37 | result = result % (range + 1); 38 | 39 | + if (offset) { 40 | + std::cout << "ifdp.push_integral_in_range("; 41 | + if constexpr (std::is_signed_v) { 42 | + std::cout << int64_t{static_cast(static_cast(min) + result)} 43 | + << "i" << (sizeof(T) * 8) << ", " 44 | + << int64_t{min} 45 | + << "i" << (sizeof(T) * 8) << ", " 46 | + << int64_t{max} 47 | + << "i" << (sizeof(T) * 8) << ", "; 48 | + } else { 49 | + std::cout << uint64_t{static_cast(static_cast(min) + result)} 50 | + << "u" << (sizeof(T) * 8) << ", " 51 | + << uint64_t{min} 52 | + << "u" << (sizeof(T) * 8) << ", " 53 | + << uint64_t{max} 54 | + << "u" << (sizeof(T) * 8) << ", "; 55 | + } 56 | + std::cout << ");\n"; 57 | + } 58 | + 59 | return static_cast(static_cast(min) + result); 60 | } 61 | 62 | @@ -249,6 +278,7 @@ template 63 | T FuzzedDataProvider::ConsumeFloatingPointInRange(T min, T max) { 64 | if (min > max) 65 | abort(); 66 | + std::cout << "ifdp.push_float(UNIMPLEMENTED);\n"; 67 | 68 | T range = .0; 69 | T result = min; 70 | @@ -338,6 +368,10 @@ inline size_t FuzzedDataProvider::ConsumeData(void *destination, 71 | inline void FuzzedDataProvider::CopyAndAdvance(void *destination, 72 | size_t num_bytes) { 73 | std::memcpy(destination, data_ptr_, num_bytes); 74 | + std::cout << std::hex << "ifdp.push_bytes(&["; 75 | + for (uint8_t* c((uint8_t*)destination); c < (uint8_t*)destination + num_bytes; c++) 76 | + std::cout << "0x" << unsigned(*c) << ","; 77 | + std::cout << std::dec << "]); // (len=" << num_bytes << ")\n"; 78 | Advance(num_bytes); 79 | } 80 | 81 | -------------------------------------------------------------------------------- /sync_bips_repo/bips_mediawiki.py: -------------------------------------------------------------------------------- 1 | # install 2 | # ------- 3 | # 4 | # ``` 5 | # virtualenv --python=python3 ./env_3 6 | # source ./env_3/bin/activate 7 | # pip install mwclient 8 | # ``` 9 | 10 | import mwclient 11 | import argparse 12 | import os 13 | import time 14 | import glob 15 | import subprocess 16 | 17 | def call_git(args, **kwargs): 18 | subprocess.check_call(['git'] + args, **kwargs) 19 | 20 | def get_git(args): 21 | return subprocess.check_output(['git'] + args, universal_newlines=True).strip() 22 | 23 | def main(): 24 | THIS_FILE_PATH = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) 25 | parser = argparse.ArgumentParser(description='Update the BIPs on the wiki with the latest text from the BIPs git repo.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) 26 | parser.add_argument('--github_repo', help='The repo slug of the remote on GitHub.', default='bitcoin/bips') 27 | parser.add_argument('--mediawiki_login_tuple', help='The login tuple for the mediawiki.', default='None:None') 28 | parser.add_argument('--mediawiki_host', help='The mediawiki host.', default='en.bitcoin.it') 29 | parser.add_argument('--scratch_dir', help='The local dir used for scratching', default=os.path.join(THIS_FILE_PATH, '..', 'scratch', 'bips_mediawiki')) 30 | parser.add_argument('--dry_run', help='Print changes/edits instead of calling the MediaWiki API.', action='store_true', default=False) 31 | args = parser.parse_args() 32 | 33 | site = mwclient.Site(host=args.mediawiki_host) 34 | if not args.dry_run: 35 | login_tuple = args.mediawiki_login_tuple.split(':', 1) 36 | site.login(login_tuple[0], login_tuple[1]) 37 | 38 | args.scratch_dir = os.path.abspath(os.path.join(args.scratch_dir, '')) 39 | os.makedirs(args.scratch_dir, exist_ok=True) 40 | 41 | code_dir = os.path.join(args.scratch_dir, 'bips_git', args.github_repo) 42 | code_url = 'https://github.com/{}'.format(args.github_repo) 43 | 44 | def create_scratch_dir(folder, url): 45 | if os.path.isdir(folder): 46 | return 47 | print('Clone {} repo to {}'.format(url, folder)) 48 | os.chdir(args.scratch_dir) 49 | call_git(['clone', '--quiet', url, folder]) 50 | 51 | create_scratch_dir(code_dir, code_url) 52 | 53 | print('Fetching diffs ...') 54 | os.chdir(code_dir) 55 | call_git(['fetch', '--quiet', '--all']) 56 | call_git(['reset', '--hard', 'HEAD']) 57 | call_git(['checkout', 'master']) 58 | call_git(['pull', '--ff-only', 'origin', 'master']) 59 | 60 | commit_id = get_git(['log', '-1', '--format=%H'])[:16] 61 | for file_name in glob.glob('bip-*.mediawiki'): 62 | bip_number = int(file_name.split('bip-', 1)[1].split('.mediawiki')[0]) 63 | print('Reading BIP {:04d} ...'.format(bip_number)) 64 | with open(file_name, encoding='utf-8') as f: 65 | content = f.read() 66 | page = site.pages['BIP {:04d}'.format(bip_number)] 67 | edit_summary = 'Update BIP text with latest version from {}/blob/{}/{}'.format(code_url, commit_id, file_name) 68 | print(edit_summary) 69 | if not args.dry_run: 70 | page.save('{{bip}}\n' + '{{BipMoved|' + file_name + '}}\n\n' + content, edit_summary) 71 | time.sleep(5) 72 | site.pages['bip-{:04d}.mediawiki'.format(bip_number)].save( 73 | '#REDIRECT [[BIP {:04d}]]'.format(bip_number), 74 | 'Create redirect from [[bip-{:04d}.mediawiki]] to [[BIP {:04d}]]'.format(bip_number, bip_number), 75 | ) 76 | time.sleep(5) 77 | 78 | 79 | if __name__ == '__main__': 80 | main() 81 | -------------------------------------------------------------------------------- /fuzz_gen/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::process::Command; 3 | use util::{chdir, check_call, git}; 4 | 5 | #[derive(clap::Parser)] 6 | #[command(long_about = format!(r#" 7 | 8 | Generate Bitcoin Core fuzz inputs until a crash. 9 | 10 | To prepare, install: 11 | wget cargo sed git python3 ccache screen + Bitcoin Core deps 12 | # 13 | # https://apt.llvm.org/ 14 | # 15 | # wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh {} 16 | # 17 | "#, LLVM_VER))] 18 | struct Args { 19 | /// The local scratch folder. 20 | #[arg(long)] 21 | scratch_folder: std::path::PathBuf, 22 | /// The number of jobs. 23 | #[arg(long, default_value_t = 1)] 24 | jobs: u8, 25 | /// The sanitizers to enable (must include fuzzer) 26 | #[arg( 27 | long, 28 | default_value = "address,fuzzer,undefined,integer,float-divide-by-zero" 29 | )] 30 | sanitizers: String, 31 | } 32 | 33 | const LLVM_VER: &str = "22"; 34 | const FUZZ_CORPORA_PATH_ELEMENT: &str = "fuzz_corpora"; 35 | 36 | pub fn ensure_init_git(folder: &std::path::Path, url: &str) { 37 | println!("Clone {url} repo to {dir}", dir = folder.display()); 38 | if !folder.is_dir() { 39 | check_call(git().args(["clone", "--quiet", url]).arg(folder)); 40 | } 41 | println!("Set git metadata"); 42 | chdir(folder); 43 | check_call(git().args(["config", "user.email", "no@ne.nl"])); 44 | check_call(git().args(["config", "user.name", "none"])); 45 | } 46 | 47 | fn main() { 48 | let args = Args::parse(); 49 | 50 | let url_code = format!("https://github.com/{}", "bitcoin/bitcoin"); 51 | let url_seed = format!("https://github.com/{}", "bitcoin-core/qa-assets"); 52 | std::fs::create_dir_all(&args.scratch_folder).expect("Failed to create scratch folder"); 53 | let temp_dir = args 54 | .scratch_folder 55 | .canonicalize() 56 | .expect("Failed to canonicalize scratch dir folder"); 57 | let dir_code = temp_dir.join("code"); 58 | let dir_assets = temp_dir.join("assets"); 59 | let dir_generate_seeds = temp_dir.join("fuzz_inputs_generate"); 60 | 61 | ensure_init_git(&dir_code, &url_code); 62 | ensure_init_git(&dir_assets, &url_seed); 63 | 64 | println!("Fetch upsteam, checkout latest branch"); 65 | chdir(&dir_code); 66 | check_call(git().args(["fetch", "--quiet", "--all"])); 67 | check_call(git().args(["checkout", "origin/master", "--force"])); 68 | check_call(git().args(["reset", "--hard", "HEAD"])); 69 | check_call(git().args(["clean", "-dfx"])); 70 | check_call(Command::new("wget").arg( 71 | "https://github.com/bitcoin/bitcoin/commit/9999b602983887002ff5d06bcd593ad91b81639c.diff", 72 | )); 73 | check_call(git().args(["apply", "9999b602983887002ff5d06bcd593ad91b81639c.diff"])); 74 | for replacement in [ 75 | r#"s/set_cover_merge=1/merge=1/g"#, 76 | r#"s/use_value_profile=0/use_value_profile=1/g"#, 77 | ] { 78 | check_call(Command::new("sed").args(["-i", replacement, "test/fuzz/test_runner.py"])); 79 | } 80 | 81 | chdir(&dir_assets); 82 | check_call(git().args(["fetch", "--quiet", "--all"])); 83 | check_call(git().args(["add", "--all"])); 84 | check_call(git().args(["commit", "--allow-empty", "-m", "Add inputs"])); 85 | check_call(git().args(["merge", "--no-edit", "origin/main"])); 86 | 87 | chdir(&dir_code); 88 | check_call(Command::new("cmake").args([ 89 | "-B", 90 | "./bld", 91 | "-DBUILD_FOR_FUZZING=ON", 92 | &format!("-DCMAKE_C_COMPILER=clang-{}", LLVM_VER), 93 | &format!( 94 | "-DCMAKE_CXX_COMPILER=clang++-{};-D_GLIBCXX_ASSERTIONS", 95 | LLVM_VER 96 | ), 97 | &format!("-DSANITIZERS={}", args.sanitizers), 98 | ])); 99 | check_call(Command::new("cmake").args([ 100 | "--build", 101 | "./bld", 102 | &format!("--parallel={}", args.jobs), 103 | ])); 104 | check_call(Command::new("rm").arg("-rf").arg(&dir_generate_seeds)); 105 | let fuzz = || { 106 | let mut cmd = Command::new("python3"); 107 | cmd.env( 108 | "LLVM_SYMBOLIZER_PATH", 109 | format!("/usr/bin/llvm-symbolizer-{}", LLVM_VER), 110 | ) 111 | .args([ 112 | "./bld/test/fuzz/test_runner.py", 113 | "-l=DEBUG", 114 | "--exclude=rpc", 115 | ]) 116 | .arg(format!("--par={}", args.jobs)); 117 | cmd 118 | }; 119 | check_call( 120 | fuzz() 121 | .arg(&dir_generate_seeds) 122 | .arg("--m_dir") 123 | .arg(dir_assets.join(FUZZ_CORPORA_PATH_ELEMENT)), 124 | ); 125 | check_call(fuzz().arg(&dir_generate_seeds).arg("--generate")); 126 | check_call( 127 | fuzz() 128 | .arg(dir_assets.join(FUZZ_CORPORA_PATH_ELEMENT)) 129 | .arg("--m_dir") 130 | .arg(&dir_generate_seeds), 131 | ); 132 | } 133 | -------------------------------------------------------------------------------- /rerun_ci/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use octocrab::params::repos::Commitish; 3 | use std::collections::hash_map::RandomState; 4 | use std::hash::{BuildHasher, Hasher}; 5 | 6 | #[derive(clap::Parser)] 7 | #[command(about = "Trigger GHA CI to re-run.", long_about = None)] 8 | struct Args { 9 | /// The access token for GitHub. 10 | #[arg(long)] 11 | github_access_token: Option, 12 | /// The repo slugs of the remotes on GitHub. Format: owner/repo 13 | #[arg(long)] 14 | github_repo: Vec, 15 | /// The task names to re-run. 16 | #[arg(long)] 17 | task: Vec, 18 | /// How many minutes to sleep between pull re-runs. 19 | #[arg(long, default_value_t = 25)] 20 | sleep_min: u64, 21 | /// Print changes/edits instead of calling the GitHub/CI API. 22 | #[arg(long, default_value_t = false)] 23 | dry_run: bool, 24 | } 25 | 26 | async fn rerun_first( 27 | owner: &str, 28 | repo: &str, 29 | token: &str, 30 | task_name: &str, 31 | check_runs: &[octocrab::models::checks::CheckRun], 32 | dry_run: bool, 33 | ) -> octocrab::Result<()> { 34 | if let Some(task) = check_runs.iter().find(|t| t.name.contains(task_name)) { 35 | println!("Re-run task {n} (id: {i})", n = task.name, i = task.id); 36 | if !dry_run { 37 | util::check_call(std::process::Command::new("curl").args([ 38 | "-L", 39 | "-X", 40 | "POST", 41 | "-H", 42 | "Accept: application/vnd.github+json", 43 | "-H", 44 | &format!("Authorization: Bearer {token}",), 45 | "-H", 46 | "X-GitHub-Api-Version: 2022-11-28", 47 | &format!( 48 | "https://api.github.com/repos/{owner}/{repo}/actions/jobs/{id}/rerun", 49 | id = task.id 50 | ), 51 | ])); 52 | // Ignore result, but log it. May fail if the task is older than 30 days. 53 | } 54 | } 55 | Ok(()) 56 | } 57 | 58 | #[tokio::main] 59 | async fn main() -> octocrab::Result<()> { 60 | let args = Args::parse(); 61 | 62 | let github = util::get_octocrab(args.github_access_token.clone())?; 63 | 64 | for util::Slug { owner, repo } in args.github_repo { 65 | println!("Get open pulls for {owner}/{repo} ..."); 66 | let pulls_api = github.pulls(&owner, &repo); 67 | let checks_api = github.checks(&owner, &repo); 68 | let pulls = { 69 | let mut pulls = github 70 | .all_pages( 71 | pulls_api 72 | .list() 73 | .state(octocrab::params::State::Open) 74 | .send() 75 | .await?, 76 | ) 77 | .await?; 78 | // Rotate the vector to start at a different place each time, to account for 79 | // api.cirrus-ci network errors, which would abort the program. On the next start, it 80 | // would start iterating from the same place. 81 | let rotate = RandomState::new().build_hasher().finish() as usize % (pulls.len()); 82 | pulls.rotate_left(rotate); 83 | pulls 84 | }; 85 | println!("Open pulls: {}", pulls.len()); 86 | for (i, pull) in pulls.iter().enumerate() { 87 | println!( 88 | "{}/{} (Pull: {}/{}#{})", 89 | i, 90 | pulls.len(), 91 | owner, 92 | repo, 93 | pull.number 94 | ); 95 | let pull = util::get_pull_mergeable(&pulls_api, pull.number).await?; 96 | let pull = match pull { 97 | None => { 98 | continue; 99 | } 100 | Some(p) => p, 101 | }; 102 | if !pull.mergeable.unwrap() { 103 | continue; 104 | } 105 | let check_runs = checks_api 106 | .list_check_runs_for_git_ref(Commitish(pull.head.sha.to_string())) 107 | .per_page(90) 108 | .send() 109 | .await? 110 | .check_runs; 111 | for task_name in &args.task { 112 | if let Err(msg) = rerun_first( 113 | &owner, 114 | &repo, 115 | args.github_access_token 116 | .as_deref() 117 | .unwrap_or("missing_token"), 118 | task_name, 119 | &check_runs, 120 | args.dry_run, 121 | ) 122 | .await 123 | { 124 | println!("{msg:?}"); 125 | } 126 | } 127 | std::thread::sleep(std::time::Duration::from_secs(args.sleep_min * 60)); 128 | } 129 | } 130 | Ok(()) 131 | } 132 | -------------------------------------------------------------------------------- /inverse_fdp/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "byteorder" 16 | version = "1.5.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 19 | 20 | [[package]] 21 | name = "cc" 22 | version = "1.2.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" 25 | dependencies = [ 26 | "shlex", 27 | ] 28 | 29 | [[package]] 30 | name = "cpp" 31 | version = "0.5.9" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "bfa65869ef853e45c60e9828aa08cdd1398cb6e13f3911d9cb2a079b144fcd64" 34 | dependencies = [ 35 | "cpp_macros", 36 | ] 37 | 38 | [[package]] 39 | name = "cpp_build" 40 | version = "0.5.9" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "0e361fae2caf9758164b24da3eedd7f7d7451be30d90d8e7b5d2be29a2f0cf5b" 43 | dependencies = [ 44 | "cc", 45 | "cpp_common", 46 | "lazy_static", 47 | "proc-macro2", 48 | "regex", 49 | "syn", 50 | "unicode-xid", 51 | ] 52 | 53 | [[package]] 54 | name = "cpp_common" 55 | version = "0.5.9" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "3e1a2532e4ed4ea13031c13bc7bc0dbca4aae32df48e9d77f0d1e743179f2ea1" 58 | dependencies = [ 59 | "lazy_static", 60 | "proc-macro2", 61 | "syn", 62 | ] 63 | 64 | [[package]] 65 | name = "cpp_macros" 66 | version = "0.5.9" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "47ec9cc90633446f779ef481a9ce5a0077107dd5b87016440448d908625a83fd" 69 | dependencies = [ 70 | "aho-corasick", 71 | "byteorder", 72 | "cpp_common", 73 | "lazy_static", 74 | "proc-macro2", 75 | "quote", 76 | "syn", 77 | ] 78 | 79 | [[package]] 80 | name = "inverse_fdp" 81 | version = "0.1.0" 82 | dependencies = [ 83 | "cpp", 84 | "cpp_build", 85 | ] 86 | 87 | [[package]] 88 | name = "lazy_static" 89 | version = "1.5.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 92 | 93 | [[package]] 94 | name = "memchr" 95 | version = "2.7.4" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 98 | 99 | [[package]] 100 | name = "proc-macro2" 101 | version = "1.0.89" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 104 | dependencies = [ 105 | "unicode-ident", 106 | ] 107 | 108 | [[package]] 109 | name = "quote" 110 | version = "1.0.37" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 113 | dependencies = [ 114 | "proc-macro2", 115 | ] 116 | 117 | [[package]] 118 | name = "regex" 119 | version = "1.11.1" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 122 | dependencies = [ 123 | "aho-corasick", 124 | "memchr", 125 | "regex-automata", 126 | "regex-syntax", 127 | ] 128 | 129 | [[package]] 130 | name = "regex-automata" 131 | version = "0.4.9" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 134 | dependencies = [ 135 | "aho-corasick", 136 | "memchr", 137 | "regex-syntax", 138 | ] 139 | 140 | [[package]] 141 | name = "regex-syntax" 142 | version = "0.8.5" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 145 | 146 | [[package]] 147 | name = "shlex" 148 | version = "1.3.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 151 | 152 | [[package]] 153 | name = "syn" 154 | version = "2.0.87" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 157 | dependencies = [ 158 | "proc-macro2", 159 | "quote", 160 | "unicode-ident", 161 | ] 162 | 163 | [[package]] 164 | name = "unicode-ident" 165 | version = "1.0.13" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 168 | 169 | [[package]] 170 | name = "unicode-xid" 171 | version = "0.2.6" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 174 | -------------------------------------------------------------------------------- /webhook_features/src/features/labels.rs: -------------------------------------------------------------------------------- 1 | use super::{Feature, FeatureMeta}; 2 | use crate::errors::DrahtBotError; 3 | use crate::errors::Result; 4 | use crate::Context; 5 | use crate::GitHubEvent; 6 | use async_trait::async_trait; 7 | 8 | pub struct LabelsFeature { 9 | meta: FeatureMeta, 10 | } 11 | 12 | impl LabelsFeature { 13 | pub fn new() -> Self { 14 | Self { 15 | meta: FeatureMeta::new( 16 | "Labels", 17 | "Guess and set labels on pull requests missing them, if they are set in the config yaml.", 18 | vec![GitHubEvent::PullRequest], 19 | ), 20 | } 21 | } 22 | } 23 | 24 | #[async_trait] 25 | impl Feature for LabelsFeature { 26 | fn meta(&self) -> &FeatureMeta { 27 | &self.meta 28 | } 29 | 30 | async fn handle( 31 | &self, 32 | ctx: &Context, 33 | event: &GitHubEvent, 34 | payload: &serde_json::Value, 35 | ) -> Result<()> { 36 | let action = payload["action"] 37 | .as_str() 38 | .ok_or(DrahtBotError::KeyNotFound)?; 39 | 40 | let repo_user = payload["repository"]["owner"]["login"] 41 | .as_str() 42 | .ok_or(DrahtBotError::KeyNotFound)?; 43 | 44 | let repo_name = payload["repository"]["name"] 45 | .as_str() 46 | .ok_or(DrahtBotError::KeyNotFound)?; 47 | 48 | println!( 49 | "Handling: {repo_user}/{repo_name} {event}::{action} ({feature_name})", 50 | feature_name = self.meta().name() 51 | ); 52 | match event { 53 | GitHubEvent::PullRequest 54 | if action == "unlabeled" || action == "opened" || action == "edited" => 55 | { 56 | // https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=opened#pull_request 57 | if let Some(config_repo) = ctx 58 | .config 59 | .repositories 60 | .iter() 61 | .find(|r| r.repo_slug == format!("{repo_user}/{repo_name}")) 62 | { 63 | let pr_number = payload["number"] 64 | .as_u64() 65 | .ok_or(DrahtBotError::KeyNotFound)?; 66 | let base_name = payload["pull_request"]["base"]["repo"]["default_branch"] 67 | .as_str() 68 | .ok_or(DrahtBotError::KeyNotFound)?; 69 | let issues_api = ctx.octocrab.issues(repo_user, repo_name); 70 | let pulls_api = ctx.octocrab.pulls(repo_user, repo_name); 71 | let pull = pulls_api.get(pr_number).await?; 72 | apply_labels_one( 73 | &ctx.octocrab, 74 | &issues_api, 75 | config_repo, 76 | base_name, 77 | &pull, 78 | ctx.dry_run, 79 | ) 80 | .await?; 81 | } 82 | } 83 | _ => {} 84 | } 85 | Ok(()) 86 | } 87 | } 88 | 89 | async fn apply_labels_one( 90 | github: &octocrab::Octocrab, 91 | issues_api: &octocrab::issues::IssueHandler<'_>, 92 | config_repo: &crate::config::Repo, 93 | base_name: &str, 94 | pull: &octocrab::models::pulls::PullRequest, 95 | dry_run: bool, 96 | ) -> Result<()> { 97 | let regs = config_repo.repo_labels.iter().fold( 98 | std::collections::HashMap::<&String, Vec>::new(), 99 | |mut acc, (label_name, title_regs)| { 100 | for reg in title_regs { 101 | acc.entry(label_name).or_default().push( 102 | regex::RegexBuilder::new(reg) 103 | .case_insensitive(true) 104 | .build() 105 | .expect("regex config format error"), 106 | ); 107 | } 108 | acc 109 | }, 110 | ); 111 | let pull_title = pull.title.as_ref().expect("remote api error"); 112 | let pull_title_trimmed = pull_title.trim(); 113 | if pull_title_trimmed != pull_title && !dry_run { 114 | issues_api 115 | .update(pull.number) 116 | .title(pull_title_trimmed) 117 | .send() 118 | .await?; 119 | } 120 | let pull_title = pull_title_trimmed; 121 | let labels = github 122 | .all_pages(issues_api.list_labels_for_issue(pull.number).send().await?) 123 | .await?; 124 | if !labels.is_empty() { 125 | return Ok(()); 126 | } 127 | let mut new_labels = Vec::new(); 128 | if pull.base.ref_field != base_name { 129 | if let Some(bl) = &config_repo.backport_label { 130 | new_labels.push(bl.to_string()); 131 | } 132 | } else { 133 | for (label_name, title_regs) in regs { 134 | if title_regs.iter().any(|r| r.is_match(pull_title)) { 135 | new_labels.push(label_name.to_string()); 136 | break; 137 | } 138 | } 139 | } 140 | if new_labels.is_empty() { 141 | return Ok(()); 142 | } 143 | println!(" ... add_to_labels({new_labels:?})"); 144 | if !dry_run { 145 | issues_api.add_labels(pull.number, &new_labels).await?; 146 | } 147 | Ok(()) 148 | } 149 | -------------------------------------------------------------------------------- /llm_eval/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::fs; 3 | use std::hash::{BuildHasher, Hasher, RandomState}; 4 | use std::path::Path; 5 | use std::process::Command; 6 | use util::{LLM_PROMPT_TYPOS, LLM_SHARED_PROMPT_DIFF, make_llm_payload, prepare_raw_diff_for_llm}; 7 | 8 | #[derive(Parser)] 9 | #[command(about = "Scratch script to evaluate LLMs.", long_about = None)] 10 | struct Cli { 11 | #[arg(long)] 12 | open_ai_token: String, 13 | #[arg(long)] 14 | google_ai_token: String, 15 | } 16 | 17 | fn main() { 18 | let cli = Cli::parse(); 19 | 20 | let inputs = fs::canonicalize("./inputs").expect("folder must exist"); 21 | 22 | let outputs = format!( 23 | "./outputs-{id}", 24 | id = { 25 | let date = Command::new("date") 26 | .arg("--iso-8601=ns") 27 | .output() 28 | .expect("Failed to execute date command"); 29 | assert!(date.status.success()); 30 | String::from_utf8(date.stdout) 31 | .expect("must be utf8") 32 | .trim() 33 | .to_string() 34 | } 35 | ); 36 | fs::create_dir(&outputs).expect("folder must be creatable"); 37 | let outputs = fs::canonicalize(outputs).expect("folder must exist"); 38 | 39 | for entry in fs::read_dir(inputs).expect("folder must exist") { 40 | let entry = entry.expect("file must exist"); 41 | let file_name = entry 42 | .path() 43 | .file_name() 44 | .expect("file must have name") 45 | .to_str() 46 | .expect("Must be valid utf8") 47 | .to_string(); 48 | let diff = fs::read_to_string(entry.path()).expect("Must be able to read diff"); 49 | 50 | let diff = format!( 51 | "{}\n{}", 52 | // Inject seed to avoid cached input 53 | RandomState::new().build_hasher().finish(), 54 | prepare_raw_diff_for_llm(&diff) 55 | ); 56 | 57 | check_google_ai(&cli, &outputs, &file_name, &diff); 58 | check_open_ai(&cli, &outputs, &file_name, &diff); 59 | } 60 | } 61 | 62 | fn check_google_ai(cli: &Cli, outputs: &Path, file_name: &str, diff: &str) { 63 | println!("Check {file_name} via google_ai"); 64 | let payload = serde_json::json!({ 65 | "systemInstruction": { 66 | "parts": [ 67 | { 68 | "text":LLM_SHARED_PROMPT_DIFF 69 | }, 70 | ] 71 | }, 72 | "contents": [ 73 | { 74 | "parts": [ 75 | { 76 | "text": diff 77 | } 78 | ] 79 | }, 80 | { 81 | "parts": [ 82 | { 83 | "text": LLM_PROMPT_TYPOS 84 | } 85 | ] 86 | } 87 | ] 88 | }); 89 | let temp = outputs 90 | .join("temp_scratch") 91 | .to_str() 92 | .expect("must be valid utf8") 93 | .to_string(); 94 | fs::write( 95 | &temp, 96 | serde_json::to_string(&payload).expect("must be valid json"), 97 | ) 98 | .expect("Must be able to write file"); 99 | let curl_out = Command::new("curl") 100 | .arg(format!( 101 | "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite-preview-06-17:generateContent?key={}" 102 | ,cli.google_ai_token)) 103 | .arg("-H") 104 | .arg("Content-Type: application/json") 105 | .arg("-X") 106 | .arg("POST") 107 | .arg("-d") 108 | .arg(format!("@{}", temp)) 109 | .output() 110 | .expect("curl error"); 111 | assert!(curl_out.status.success()); 112 | let response: serde_json::Value = 113 | serde_json::from_str(&String::from_utf8(curl_out.stdout).expect("must be valid utf8")) 114 | .expect("must be valid json"); 115 | let val = response["candidates"][0]["content"]["parts"][0]["text"] 116 | .as_str() 117 | .expect("Content not found"); 118 | if val.is_empty() { 119 | // Could be due to https://discuss.ai.google.dev/t/gemini-2-5-pro-with-empty-response-text/81175/23 or just hitting the output token limit 120 | println!("EMPTY:\n{response}"); 121 | } 122 | fs::write( 123 | outputs.join(format!("{}.google_ai.dbg", file_name)), 124 | format!("{response}"), 125 | ) 126 | .expect("Must be able to write file"); 127 | fs::write(outputs.join(format!("{}.google_ai.txt", file_name)), val) 128 | .expect("Must be able to write file"); 129 | } 130 | 131 | fn check_open_ai(cli: &Cli, outputs: &Path, file_name: &str, diff: &str) { 132 | println!("Check {file_name} via open_ai"); 133 | let payload = make_llm_payload(diff, LLM_PROMPT_TYPOS); 134 | let temp = outputs 135 | .join("temp_scratch") 136 | .to_str() 137 | .expect("must be valid utf8") 138 | .to_string(); 139 | fs::write( 140 | &temp, 141 | serde_json::to_string(&payload).expect("must be valid json"), 142 | ) 143 | .expect("Must be able to write file"); 144 | let curl_out = Command::new("curl") 145 | .arg("-X") 146 | .arg("POST") 147 | .arg("https://api.openai.com/v1/chat/completions") 148 | .arg("-H") 149 | .arg("Content-Type: application/json") 150 | .arg("-H") 151 | .arg(format!("Authorization: Bearer {}", cli.open_ai_token)) 152 | .arg("-d") 153 | .arg(format!("@{}", temp)) 154 | .output() 155 | .expect("curl error"); 156 | assert!(curl_out.status.success()); 157 | let response: serde_json::Value = 158 | serde_json::from_str(&String::from_utf8(curl_out.stdout).expect("must be valid utf8")) 159 | .expect("must be valid json"); 160 | let val = response["choices"][0]["message"]["content"] 161 | .as_str() 162 | .expect("Content not found"); 163 | fs::write(outputs.join(format!("{}.open_ai.txt", file_name)), val) 164 | .expect("Must be able to write file"); 165 | } 166 | -------------------------------------------------------------------------------- /webhook_features/src/main.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod errors; 3 | mod features; 4 | 5 | use std::str::FromStr; 6 | 7 | use crate::features::ci_status::CiStatusFeature; 8 | use crate::features::labels::LabelsFeature; 9 | use crate::features::spam_detection::SpamDetectionFeature; 10 | use crate::features::summary_comment::SummaryCommentFeature; 11 | use actix_web::{get, post, web, App, HttpRequest, HttpServer, Responder}; 12 | use clap::Parser; 13 | use features::Feature; 14 | use lazy_static::lazy_static; 15 | use octocrab::Octocrab; 16 | use std::collections::BTreeSet; 17 | use strum::{Display, EnumString}; 18 | 19 | use crate::config::Config; 20 | use crate::errors::{DrahtBotError, Result}; 21 | 22 | #[derive(Parser)] 23 | #[command(about=format!(r#" 24 | Run features on webhooks. 25 | 26 | {features}"#, features=list_features()), long_about = None)] 27 | struct Args { 28 | #[arg(long, help = "GitHub token")] 29 | token: String, 30 | #[arg(long, help = "LLM token", default_value = "")] 31 | llm_token: String, 32 | #[arg(long, help = "Host to listen on", default_value = "localhost")] 33 | host: String, 34 | #[arg(long, help = "Port to listen on", default_value = "1337")] 35 | port: u16, 36 | /// The path to the yaml config file. 37 | #[arg(long)] 38 | config_file: std::path::PathBuf, 39 | /// Print changes/edits instead of calling the GitHub/CI API. 40 | #[arg(long, default_value_t = false)] 41 | dry_run: bool, 42 | } 43 | 44 | #[derive(Display, EnumString, PartialEq, Eq, Hash)] 45 | #[strum(serialize_all = "snake_case")] 46 | pub enum GitHubEvent { 47 | CheckSuite, 48 | IssueComment, 49 | Issues, 50 | PullRequest, 51 | PullRequestReview, 52 | 53 | Unknown, 54 | } 55 | 56 | #[get("/")] 57 | async fn index() -> &'static str { 58 | "Welcome to DrahtBot!" 59 | } 60 | 61 | pub struct Context { 62 | octocrab: Octocrab, 63 | bot_username: String, 64 | pub config: Config, 65 | github_token: String, 66 | llm_token: String, 67 | dry_run: bool, 68 | } 69 | 70 | #[post("/drahtbot")] 71 | async fn postreceive_handler( 72 | ctx: web::Data, 73 | req: HttpRequest, 74 | data: web::Json, 75 | ) -> impl Responder { 76 | let event_str = req 77 | .headers() 78 | .get("X-GitHub-Event") 79 | .unwrap() 80 | .to_str() 81 | .unwrap(); 82 | let event = GitHubEvent::from_str(event_str).unwrap_or(GitHubEvent::Unknown); 83 | 84 | let num_errors = emit_event(&ctx, event, data).await; 85 | format!("Number of errors: {num_errors}") 86 | } 87 | 88 | fn features() -> Vec> { 89 | vec![ 90 | Box::new(CiStatusFeature::new()), 91 | Box::new(LabelsFeature::new()), 92 | Box::new(SpamDetectionFeature::new()), 93 | Box::new(SummaryCommentFeature::new()), 94 | ] 95 | } 96 | 97 | pub fn list_features() -> String { 98 | format!( 99 | "{intro}\n{list}\n{wh_sum_desc}\n{wh_sum}", 100 | intro = "DrahtBot will will run the following features:", 101 | list = features() 102 | .iter() 103 | .map(|f| format!( 104 | "\n - {}\n {}\n Required webhooks: {}", 105 | f.meta().name(), 106 | f.meta().description(), 107 | f.meta() 108 | .events() 109 | .iter() 110 | .map(|e| format!("{}", e)) 111 | .collect::>() 112 | .join(", ") 113 | )) 114 | .collect::>() 115 | .join("\n"), 116 | wh_sum_desc = "\nThus, the following needs to be set in Settings/Webhooks/Manage_Webhook:", 117 | wh_sum = features() 118 | .iter() 119 | .map(|f| f.meta().events()) 120 | .flat_map(|v| v.iter().map(|e| format!("- {}", e))) 121 | .collect::>() 122 | .into_iter() 123 | .collect::>() 124 | .join("\n") 125 | ) 126 | } 127 | 128 | lazy_static! { 129 | static ref MUTEX: tokio::sync::Mutex<()> = tokio::sync::Mutex::new(()); 130 | } 131 | 132 | async fn emit_event(ctx: &Context, event: GitHubEvent, data: web::Json) -> u32 { 133 | let _guard = MUTEX.lock().await; 134 | 135 | let mut num_errors = 0; 136 | 137 | for feature in features() { 138 | if feature.meta().events().contains(&event) { 139 | if let Err(e) = feature.handle(ctx, &event, &data).await { 140 | println!("... ERROR\n{:?}", e); 141 | num_errors += 1; 142 | } 143 | } 144 | } 145 | 146 | num_errors 147 | } 148 | 149 | #[actix_web::main] 150 | async fn main() -> Result<()> { 151 | let args = Args::parse(); 152 | 153 | let config: Config = serde_yaml::from_reader( 154 | std::fs::File::open(args.config_file).expect("config file path error"), 155 | ) 156 | .expect("yaml error"); 157 | 158 | let octocrab = octocrab::Octocrab::builder() 159 | .personal_token(args.token.as_ref()) 160 | .build() 161 | .map_err(DrahtBotError::GitHubError)?; 162 | 163 | println!("{}", list_features()); 164 | println!(); 165 | 166 | // Get the bot's username 167 | let bot_username = octocrab 168 | .current() 169 | .user() 170 | .await 171 | .map_err(DrahtBotError::GitHubError)? 172 | .login; 173 | 174 | println!("Running as {bot_username}..."); 175 | 176 | let context = web::Data::new(Context { 177 | octocrab, 178 | bot_username, 179 | config, 180 | github_token: args.token, 181 | llm_token: args.llm_token, 182 | dry_run: args.dry_run, 183 | }); 184 | 185 | HttpServer::new(move || { 186 | App::new() 187 | .app_data(context.clone()) 188 | .service(index) 189 | .service(postreceive_handler) 190 | }) 191 | .bind(format!("{}:{}", args.host, args.port))? 192 | .run() 193 | .await?; 194 | Ok(()) 195 | } 196 | -------------------------------------------------------------------------------- /coverage/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use util::{chdir, check_call, check_output, git}; 3 | 4 | fn gen_coverage( 5 | docker_exec: &dyn Fn(&str), 6 | dir_code: &std::path::Path, 7 | dir_result: &std::path::Path, 8 | git_ref: &str, 9 | make_jobs: u8, 10 | ) { 11 | println!( 12 | "Generate coverage for {} in {} (ref: {}).", 13 | dir_code.display(), 14 | dir_result.display(), 15 | git_ref 16 | ); 17 | chdir(dir_code); 18 | let dir_build = dir_code.join("build"); 19 | 20 | println!("Clear previous build and result folders"); 21 | 22 | let clear_dir = |folder: &std::path::Path| { 23 | std::fs::create_dir_all(folder).expect("Failed to create a folder"); 24 | docker_exec(&format!("rm -r {}", folder.display())); 25 | std::fs::create_dir_all(folder).expect("Failed to create a folder"); 26 | // Must change to a dir that exists after this function call 27 | }; 28 | 29 | clear_dir(&dir_build); 30 | clear_dir(dir_result); 31 | 32 | println!("Make coverage data in docker ..."); 33 | chdir(dir_code); 34 | 35 | docker_exec(&format!( 36 | "cmake -B {} \ 37 | --preset=dev-mode \ 38 | -DWITH_QRENCODE=OFF -DBUILD_GUI_TESTS=OFF -DBUILD_GUI=OFF -DWITH_USDT=OFF \ 39 | -DBUILD_FUZZ_BINARY=OFF -DBUILD_BENCH=OFF \ 40 | -DCMAKE_C_COMPILER='gcc;-fprofile-update=atomic' \ 41 | -DCMAKE_CXX_COMPILER='g++;-fprofile-update=atomic' \ 42 | -DCMAKE_BUILD_TYPE=Coverage", 43 | dir_build.display() 44 | )); 45 | docker_exec(&format!( 46 | "cmake --build {} -j{}", 47 | dir_build.display(), 48 | make_jobs 49 | )); 50 | 51 | println!("Make coverage ..."); 52 | docker_exec(&format!( 53 | "cmake -DJOBS={} \ 54 | -DLCOV_OPTS='--rc branch_coverage=1 --ignore-errors mismatch,mismatch,inconsistent,inconsistent' \ 55 | -P {}/Coverage.cmake", 56 | make_jobs, 57 | dir_build.display() 58 | )); 59 | docker_exec(&format!( 60 | "mv {}/*coverage* {}/", 61 | dir_build.display(), 62 | dir_result.display() 63 | )); 64 | chdir(dir_result); 65 | check_call(git().args(["checkout", "main"])); 66 | check_call(git().args(["add", "./"])); 67 | check_call(git().args([ 68 | "commit", 69 | "-m", 70 | &format!("Add coverage results for {}", git_ref), 71 | ])); 72 | check_call(git().args(["push", "origin", "main"])); 73 | 74 | // Work around permission errors 75 | clear_dir(dir_result); 76 | chdir(dir_result); 77 | check_call(git().args(["reset", "--hard", "HEAD"])); 78 | } 79 | 80 | fn calc_coverage( 81 | dir_code: &std::path::Path, 82 | dir_cov_report: &std::path::Path, 83 | make_jobs: u8, 84 | remote_url: &str, 85 | ) { 86 | println!("Start docker process ..."); 87 | std::fs::create_dir_all(dir_cov_report).expect("Failed to create dir_cov_report"); 88 | let docker_id = check_output(std::process::Command::new("podman").args([ 89 | "run", 90 | "-idt", 91 | "--rm", 92 | &format!( 93 | "--volume={}:{}:rw,z", 94 | dir_code.display(), 95 | dir_code.display() 96 | ), 97 | &format!( 98 | "--volume={}:{}:rw,z", 99 | dir_cov_report.display(), 100 | dir_cov_report.display() 101 | ), 102 | //'--mount', # Doesn't work with fedora (needs rw,z) 103 | //'type=bind,src={},dst={}'.format(dir_code, dir_code), 104 | //'--mount', 105 | //'type=bind,src={},dst={}'.format(dir_cov_report, dir_cov_report), 106 | "-e", 107 | "LC_ALL=C.UTF-8", 108 | "ubuntu:devel", 109 | ])); 110 | 111 | let docker_exec = |cmd: &str| { 112 | check_call(std::process::Command::new("podman").args([ 113 | "exec", 114 | &docker_id, 115 | "bash", 116 | "-c", 117 | &format!( 118 | "cd {} && {}", 119 | std::env::current_dir().expect("Failed to getcwd").display(), 120 | cmd 121 | ), 122 | ])) 123 | }; 124 | 125 | println!("Docker running with id {}.", docker_id); 126 | 127 | println!("Installing packages ..."); 128 | docker_exec("apt-get update"); 129 | docker_exec(&format!("apt-get install -qq {}", "python3-zmq libsqlite3-dev libevent-dev libboost-dev libcapnp-dev capnproto libzmq3-dev lcov build-essential cmake pkg-config")); 130 | 131 | println!("Generate coverage"); 132 | chdir(dir_code); 133 | let base_git_ref = &check_output(git().args(["log", "--format=%H", "-1", "HEAD"]))[..16]; 134 | let dir_result_base = dir_cov_report.join(base_git_ref); 135 | gen_coverage( 136 | &docker_exec, 137 | dir_code, 138 | &dir_result_base, 139 | &format!("{base_git_ref}-code"), 140 | make_jobs, 141 | ); 142 | 143 | println!("{remote_url}/coverage/monotree/{base_git_ref}/total.coverage/index.html"); 144 | } 145 | 146 | #[derive(clap::Parser)] 147 | #[command(about = "Create Bitcoin Core coverage reports.", long_about = None)] 148 | struct Args { 149 | /// The repo slug of the remote on GitHub for reports. 150 | #[arg(long, default_value = "DrahtBot/reports")] 151 | repo_report: util::Slug, 152 | /// The remote url of the hosted html reports. 153 | #[arg( 154 | long, 155 | default_value = "https://drahtbot.space/host_reports/DrahtBot/reports" 156 | )] 157 | remote_url: String, 158 | /// The number of make jobs. 159 | #[arg(long, default_value_t = 2)] 160 | make_jobs: u8, 161 | /// The local dir used for scratching. 162 | #[arg(long)] 163 | scratch_dir: std::path::PathBuf, 164 | /// The ssh key for "repo_report". 165 | #[arg(long)] 166 | ssh_key: std::path::PathBuf, 167 | /// Generate the coverage for this commit and exit. 168 | #[arg(long)] 169 | commit_only: String, 170 | } 171 | 172 | fn ensure_init_git(folder: &std::path::Path, url: &str) { 173 | println!("Clone {url} repo to {dir}", dir = folder.display()); 174 | if !folder.is_dir() { 175 | check_call(git().args(["clone", "--quiet", url]).arg(folder)); 176 | } 177 | } 178 | 179 | fn main() { 180 | let args = Args::parse(); 181 | 182 | std::fs::create_dir_all(&args.scratch_dir).expect("Failed to create scratch folder"); 183 | let temp_dir = args 184 | .scratch_dir 185 | .canonicalize() 186 | .expect("Failed to canonicalize scratch folder"); 187 | let ssh_cmd = format!( 188 | "ssh -i {} -F /dev/null", 189 | args.ssh_key 190 | .canonicalize() 191 | .expect("Failed to canonicalize ssh key") 192 | .display() 193 | ); 194 | 195 | let code_dir = temp_dir.join("code").join("monotree"); 196 | let code_url = "https://github.com/bitcoin/bitcoin"; 197 | let report_dir = temp_dir.join("reports"); 198 | let report_url = format!("git@github.com:{}.git", args.repo_report.str()); 199 | 200 | ensure_init_git(&code_dir, code_url); 201 | ensure_init_git(&report_dir, &report_url); 202 | 203 | println!("Set git metadata"); 204 | chdir(&report_dir); 205 | check_call(git().args([ 206 | "config", 207 | "user.email", 208 | "39886733+DrahtBot@users.noreply.github.com", 209 | ])); 210 | check_call(git().args(["config", "user.name", "DrahtBot"])); 211 | check_call(git().args(["config", "core.sshCommand", &ssh_cmd])); 212 | 213 | println!("Fetching diffs ..."); 214 | chdir(&code_dir); 215 | check_call(git().args(["fetch", "origin", "--quiet", &args.commit_only])); 216 | check_call(git().args(["checkout", "FETCH_HEAD", "--force"])); 217 | check_call(git().args(["reset", "--hard", "HEAD"])); 218 | check_call(git().args(["clean", "-dfx"])); 219 | check_call(std::process::Command::new("sed").args([ 220 | "-i", 221 | "s|functional/test_runner.py|functional/test_runner.py --timeout-factor=10 --exclude=feature_dbcrash|g", 222 | "./cmake/script/Coverage.cmake", 223 | ])); 224 | chdir(&report_dir); 225 | check_call(git().args(["fetch", "--quiet", "--all"])); 226 | check_call(git().args(["reset", "--hard", "HEAD"])); 227 | check_call(git().args(["checkout", "main"])); 228 | check_call(git().args(["reset", "--hard", "origin/main"])); 229 | 230 | calc_coverage( 231 | &code_dir, 232 | &report_dir.join("coverage").join("monotree"), 233 | args.make_jobs, 234 | &args.remote_url, 235 | ); 236 | } 237 | -------------------------------------------------------------------------------- /llm_eval/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.10" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.7" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell", 52 | "windows-sys", 53 | ] 54 | 55 | [[package]] 56 | name = "clap" 57 | version = "4.5.37" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" 60 | dependencies = [ 61 | "clap_builder", 62 | "clap_derive", 63 | ] 64 | 65 | [[package]] 66 | name = "clap_builder" 67 | version = "4.5.37" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" 70 | dependencies = [ 71 | "anstream", 72 | "anstyle", 73 | "clap_lex", 74 | "strsim", 75 | ] 76 | 77 | [[package]] 78 | name = "clap_derive" 79 | version = "4.5.32" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 82 | dependencies = [ 83 | "heck", 84 | "proc-macro2", 85 | "quote", 86 | "syn", 87 | ] 88 | 89 | [[package]] 90 | name = "clap_lex" 91 | version = "0.7.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 94 | 95 | [[package]] 96 | name = "colorchoice" 97 | version = "1.0.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 100 | 101 | [[package]] 102 | name = "heck" 103 | version = "0.5.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 106 | 107 | [[package]] 108 | name = "is_terminal_polyfill" 109 | version = "1.70.1" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 112 | 113 | [[package]] 114 | name = "itoa" 115 | version = "1.0.15" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 118 | 119 | [[package]] 120 | name = "llm_eval" 121 | version = "0.1.0" 122 | dependencies = [ 123 | "clap", 124 | "serde_json", 125 | "util", 126 | ] 127 | 128 | [[package]] 129 | name = "memchr" 130 | version = "2.7.4" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 133 | 134 | [[package]] 135 | name = "once_cell" 136 | version = "1.21.3" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 139 | 140 | [[package]] 141 | name = "proc-macro2" 142 | version = "1.0.95" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 145 | dependencies = [ 146 | "unicode-ident", 147 | ] 148 | 149 | [[package]] 150 | name = "quote" 151 | version = "1.0.40" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 154 | dependencies = [ 155 | "proc-macro2", 156 | ] 157 | 158 | [[package]] 159 | name = "ryu" 160 | version = "1.0.20" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 163 | 164 | [[package]] 165 | name = "serde" 166 | version = "1.0.219" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 169 | dependencies = [ 170 | "serde_derive", 171 | ] 172 | 173 | [[package]] 174 | name = "serde_derive" 175 | version = "1.0.219" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 178 | dependencies = [ 179 | "proc-macro2", 180 | "quote", 181 | "syn", 182 | ] 183 | 184 | [[package]] 185 | name = "serde_json" 186 | version = "1.0.140" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 189 | dependencies = [ 190 | "itoa", 191 | "memchr", 192 | "ryu", 193 | "serde", 194 | ] 195 | 196 | [[package]] 197 | name = "strsim" 198 | version = "0.11.1" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 201 | 202 | [[package]] 203 | name = "syn" 204 | version = "2.0.100" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 207 | dependencies = [ 208 | "proc-macro2", 209 | "quote", 210 | "unicode-ident", 211 | ] 212 | 213 | [[package]] 214 | name = "unicode-ident" 215 | version = "1.0.18" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 218 | 219 | [[package]] 220 | name = "utf8parse" 221 | version = "0.2.2" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 224 | 225 | [[package]] 226 | name = "util" 227 | version = "0.1.0" 228 | dependencies = [ 229 | "serde_json", 230 | ] 231 | 232 | [[package]] 233 | name = "windows-sys" 234 | version = "0.59.0" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 237 | dependencies = [ 238 | "windows-targets", 239 | ] 240 | 241 | [[package]] 242 | name = "windows-targets" 243 | version = "0.52.6" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 246 | dependencies = [ 247 | "windows_aarch64_gnullvm", 248 | "windows_aarch64_msvc", 249 | "windows_i686_gnu", 250 | "windows_i686_gnullvm", 251 | "windows_i686_msvc", 252 | "windows_x86_64_gnu", 253 | "windows_x86_64_gnullvm", 254 | "windows_x86_64_msvc", 255 | ] 256 | 257 | [[package]] 258 | name = "windows_aarch64_gnullvm" 259 | version = "0.52.6" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 262 | 263 | [[package]] 264 | name = "windows_aarch64_msvc" 265 | version = "0.52.6" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 268 | 269 | [[package]] 270 | name = "windows_i686_gnu" 271 | version = "0.52.6" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 274 | 275 | [[package]] 276 | name = "windows_i686_gnullvm" 277 | version = "0.52.6" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 280 | 281 | [[package]] 282 | name = "windows_i686_msvc" 283 | version = "0.52.6" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 286 | 287 | [[package]] 288 | name = "windows_x86_64_gnu" 289 | version = "0.52.6" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 292 | 293 | [[package]] 294 | name = "windows_x86_64_gnullvm" 295 | version = "0.52.6" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 298 | 299 | [[package]] 300 | name = "windows_x86_64_msvc" 301 | version = "0.52.6" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 304 | -------------------------------------------------------------------------------- /coverage/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.17" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.9" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.6" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys", 52 | ] 53 | 54 | [[package]] 55 | name = "clap" 56 | version = "4.5.20" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 59 | dependencies = [ 60 | "clap_builder", 61 | "clap_derive", 62 | ] 63 | 64 | [[package]] 65 | name = "clap_builder" 66 | version = "4.5.20" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 69 | dependencies = [ 70 | "anstream", 71 | "anstyle", 72 | "clap_lex", 73 | "strsim", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_derive" 78 | version = "4.5.18" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 81 | dependencies = [ 82 | "heck", 83 | "proc-macro2", 84 | "quote", 85 | "syn", 86 | ] 87 | 88 | [[package]] 89 | name = "clap_lex" 90 | version = "0.7.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 93 | 94 | [[package]] 95 | name = "colorchoice" 96 | version = "1.0.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 99 | 100 | [[package]] 101 | name = "coverage" 102 | version = "0.1.0" 103 | dependencies = [ 104 | "clap", 105 | "util", 106 | ] 107 | 108 | [[package]] 109 | name = "heck" 110 | version = "0.5.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 113 | 114 | [[package]] 115 | name = "is_terminal_polyfill" 116 | version = "1.70.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 119 | 120 | [[package]] 121 | name = "itoa" 122 | version = "1.0.15" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 125 | 126 | [[package]] 127 | name = "memchr" 128 | version = "2.7.6" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 131 | 132 | [[package]] 133 | name = "proc-macro2" 134 | version = "1.0.89" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 137 | dependencies = [ 138 | "unicode-ident", 139 | ] 140 | 141 | [[package]] 142 | name = "quote" 143 | version = "1.0.37" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 146 | dependencies = [ 147 | "proc-macro2", 148 | ] 149 | 150 | [[package]] 151 | name = "ryu" 152 | version = "1.0.20" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 155 | 156 | [[package]] 157 | name = "serde" 158 | version = "1.0.228" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 161 | dependencies = [ 162 | "serde_core", 163 | ] 164 | 165 | [[package]] 166 | name = "serde_core" 167 | version = "1.0.228" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 170 | dependencies = [ 171 | "serde_derive", 172 | ] 173 | 174 | [[package]] 175 | name = "serde_derive" 176 | version = "1.0.228" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 179 | dependencies = [ 180 | "proc-macro2", 181 | "quote", 182 | "syn", 183 | ] 184 | 185 | [[package]] 186 | name = "serde_json" 187 | version = "1.0.145" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 190 | dependencies = [ 191 | "itoa", 192 | "memchr", 193 | "ryu", 194 | "serde", 195 | "serde_core", 196 | ] 197 | 198 | [[package]] 199 | name = "strsim" 200 | version = "0.11.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 203 | 204 | [[package]] 205 | name = "syn" 206 | version = "2.0.85" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 209 | dependencies = [ 210 | "proc-macro2", 211 | "quote", 212 | "unicode-ident", 213 | ] 214 | 215 | [[package]] 216 | name = "unicode-ident" 217 | version = "1.0.13" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 220 | 221 | [[package]] 222 | name = "utf8parse" 223 | version = "0.2.2" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 226 | 227 | [[package]] 228 | name = "util" 229 | version = "0.1.0" 230 | dependencies = [ 231 | "serde_json", 232 | ] 233 | 234 | [[package]] 235 | name = "windows-sys" 236 | version = "0.59.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 239 | dependencies = [ 240 | "windows-targets", 241 | ] 242 | 243 | [[package]] 244 | name = "windows-targets" 245 | version = "0.52.6" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 248 | dependencies = [ 249 | "windows_aarch64_gnullvm", 250 | "windows_aarch64_msvc", 251 | "windows_i686_gnu", 252 | "windows_i686_gnullvm", 253 | "windows_i686_msvc", 254 | "windows_x86_64_gnu", 255 | "windows_x86_64_gnullvm", 256 | "windows_x86_64_msvc", 257 | ] 258 | 259 | [[package]] 260 | name = "windows_aarch64_gnullvm" 261 | version = "0.52.6" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 264 | 265 | [[package]] 266 | name = "windows_aarch64_msvc" 267 | version = "0.52.6" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 270 | 271 | [[package]] 272 | name = "windows_i686_gnu" 273 | version = "0.52.6" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 276 | 277 | [[package]] 278 | name = "windows_i686_gnullvm" 279 | version = "0.52.6" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 282 | 283 | [[package]] 284 | name = "windows_i686_msvc" 285 | version = "0.52.6" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 288 | 289 | [[package]] 290 | name = "windows_x86_64_gnu" 291 | version = "0.52.6" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 294 | 295 | [[package]] 296 | name = "windows_x86_64_gnullvm" 297 | version = "0.52.6" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 300 | 301 | [[package]] 302 | name = "windows_x86_64_msvc" 303 | version = "0.52.6" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 306 | -------------------------------------------------------------------------------- /fuzz_gen/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.17" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.9" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.6" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys", 52 | ] 53 | 54 | [[package]] 55 | name = "clap" 56 | version = "4.5.20" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 59 | dependencies = [ 60 | "clap_builder", 61 | "clap_derive", 62 | ] 63 | 64 | [[package]] 65 | name = "clap_builder" 66 | version = "4.5.20" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 69 | dependencies = [ 70 | "anstream", 71 | "anstyle", 72 | "clap_lex", 73 | "strsim", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_derive" 78 | version = "4.5.18" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 81 | dependencies = [ 82 | "heck", 83 | "proc-macro2", 84 | "quote", 85 | "syn", 86 | ] 87 | 88 | [[package]] 89 | name = "clap_lex" 90 | version = "0.7.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 93 | 94 | [[package]] 95 | name = "colorchoice" 96 | version = "1.0.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 99 | 100 | [[package]] 101 | name = "fuzz_gen" 102 | version = "0.1.0" 103 | dependencies = [ 104 | "clap", 105 | "util", 106 | ] 107 | 108 | [[package]] 109 | name = "heck" 110 | version = "0.5.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 113 | 114 | [[package]] 115 | name = "is_terminal_polyfill" 116 | version = "1.70.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 119 | 120 | [[package]] 121 | name = "itoa" 122 | version = "1.0.15" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 125 | 126 | [[package]] 127 | name = "memchr" 128 | version = "2.7.6" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 131 | 132 | [[package]] 133 | name = "proc-macro2" 134 | version = "1.0.89" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 137 | dependencies = [ 138 | "unicode-ident", 139 | ] 140 | 141 | [[package]] 142 | name = "quote" 143 | version = "1.0.37" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 146 | dependencies = [ 147 | "proc-macro2", 148 | ] 149 | 150 | [[package]] 151 | name = "ryu" 152 | version = "1.0.20" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 155 | 156 | [[package]] 157 | name = "serde" 158 | version = "1.0.228" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 161 | dependencies = [ 162 | "serde_core", 163 | ] 164 | 165 | [[package]] 166 | name = "serde_core" 167 | version = "1.0.228" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 170 | dependencies = [ 171 | "serde_derive", 172 | ] 173 | 174 | [[package]] 175 | name = "serde_derive" 176 | version = "1.0.228" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 179 | dependencies = [ 180 | "proc-macro2", 181 | "quote", 182 | "syn", 183 | ] 184 | 185 | [[package]] 186 | name = "serde_json" 187 | version = "1.0.145" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 190 | dependencies = [ 191 | "itoa", 192 | "memchr", 193 | "ryu", 194 | "serde", 195 | "serde_core", 196 | ] 197 | 198 | [[package]] 199 | name = "strsim" 200 | version = "0.11.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 203 | 204 | [[package]] 205 | name = "syn" 206 | version = "2.0.85" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 209 | dependencies = [ 210 | "proc-macro2", 211 | "quote", 212 | "unicode-ident", 213 | ] 214 | 215 | [[package]] 216 | name = "unicode-ident" 217 | version = "1.0.13" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 220 | 221 | [[package]] 222 | name = "utf8parse" 223 | version = "0.2.2" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 226 | 227 | [[package]] 228 | name = "util" 229 | version = "0.1.0" 230 | dependencies = [ 231 | "serde_json", 232 | ] 233 | 234 | [[package]] 235 | name = "windows-sys" 236 | version = "0.59.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 239 | dependencies = [ 240 | "windows-targets", 241 | ] 242 | 243 | [[package]] 244 | name = "windows-targets" 245 | version = "0.52.6" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 248 | dependencies = [ 249 | "windows_aarch64_gnullvm", 250 | "windows_aarch64_msvc", 251 | "windows_i686_gnu", 252 | "windows_i686_gnullvm", 253 | "windows_i686_msvc", 254 | "windows_x86_64_gnu", 255 | "windows_x86_64_gnullvm", 256 | "windows_x86_64_msvc", 257 | ] 258 | 259 | [[package]] 260 | name = "windows_aarch64_gnullvm" 261 | version = "0.52.6" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 264 | 265 | [[package]] 266 | name = "windows_aarch64_msvc" 267 | version = "0.52.6" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 270 | 271 | [[package]] 272 | name = "windows_i686_gnu" 273 | version = "0.52.6" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 276 | 277 | [[package]] 278 | name = "windows_i686_gnullvm" 279 | version = "0.52.6" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 282 | 283 | [[package]] 284 | name = "windows_i686_msvc" 285 | version = "0.52.6" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 288 | 289 | [[package]] 290 | name = "windows_x86_64_gnu" 291 | version = "0.52.6" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 294 | 295 | [[package]] 296 | name = "windows_x86_64_gnullvm" 297 | version = "0.52.6" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 300 | 301 | [[package]] 302 | name = "windows_x86_64_msvc" 303 | version = "0.52.6" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 306 | -------------------------------------------------------------------------------- /coverage_fuzz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.17" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.9" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.6" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys", 52 | ] 53 | 54 | [[package]] 55 | name = "clap" 56 | version = "4.5.20" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 59 | dependencies = [ 60 | "clap_builder", 61 | "clap_derive", 62 | ] 63 | 64 | [[package]] 65 | name = "clap_builder" 66 | version = "4.5.20" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 69 | dependencies = [ 70 | "anstream", 71 | "anstyle", 72 | "clap_lex", 73 | "strsim", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_derive" 78 | version = "4.5.18" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 81 | dependencies = [ 82 | "heck", 83 | "proc-macro2", 84 | "quote", 85 | "syn", 86 | ] 87 | 88 | [[package]] 89 | name = "clap_lex" 90 | version = "0.7.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 93 | 94 | [[package]] 95 | name = "colorchoice" 96 | version = "1.0.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 99 | 100 | [[package]] 101 | name = "coverage_fuzz" 102 | version = "0.1.0" 103 | dependencies = [ 104 | "clap", 105 | "util", 106 | ] 107 | 108 | [[package]] 109 | name = "heck" 110 | version = "0.5.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 113 | 114 | [[package]] 115 | name = "is_terminal_polyfill" 116 | version = "1.70.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 119 | 120 | [[package]] 121 | name = "itoa" 122 | version = "1.0.15" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 125 | 126 | [[package]] 127 | name = "memchr" 128 | version = "2.7.6" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 131 | 132 | [[package]] 133 | name = "proc-macro2" 134 | version = "1.0.89" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 137 | dependencies = [ 138 | "unicode-ident", 139 | ] 140 | 141 | [[package]] 142 | name = "quote" 143 | version = "1.0.37" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 146 | dependencies = [ 147 | "proc-macro2", 148 | ] 149 | 150 | [[package]] 151 | name = "ryu" 152 | version = "1.0.20" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 155 | 156 | [[package]] 157 | name = "serde" 158 | version = "1.0.228" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 161 | dependencies = [ 162 | "serde_core", 163 | ] 164 | 165 | [[package]] 166 | name = "serde_core" 167 | version = "1.0.228" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 170 | dependencies = [ 171 | "serde_derive", 172 | ] 173 | 174 | [[package]] 175 | name = "serde_derive" 176 | version = "1.0.228" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 179 | dependencies = [ 180 | "proc-macro2", 181 | "quote", 182 | "syn", 183 | ] 184 | 185 | [[package]] 186 | name = "serde_json" 187 | version = "1.0.145" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 190 | dependencies = [ 191 | "itoa", 192 | "memchr", 193 | "ryu", 194 | "serde", 195 | "serde_core", 196 | ] 197 | 198 | [[package]] 199 | name = "strsim" 200 | version = "0.11.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 203 | 204 | [[package]] 205 | name = "syn" 206 | version = "2.0.85" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 209 | dependencies = [ 210 | "proc-macro2", 211 | "quote", 212 | "unicode-ident", 213 | ] 214 | 215 | [[package]] 216 | name = "unicode-ident" 217 | version = "1.0.13" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 220 | 221 | [[package]] 222 | name = "utf8parse" 223 | version = "0.2.2" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 226 | 227 | [[package]] 228 | name = "util" 229 | version = "0.1.0" 230 | dependencies = [ 231 | "serde_json", 232 | ] 233 | 234 | [[package]] 235 | name = "windows-sys" 236 | version = "0.59.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 239 | dependencies = [ 240 | "windows-targets", 241 | ] 242 | 243 | [[package]] 244 | name = "windows-targets" 245 | version = "0.52.6" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 248 | dependencies = [ 249 | "windows_aarch64_gnullvm", 250 | "windows_aarch64_msvc", 251 | "windows_i686_gnu", 252 | "windows_i686_gnullvm", 253 | "windows_i686_msvc", 254 | "windows_x86_64_gnu", 255 | "windows_x86_64_gnullvm", 256 | "windows_x86_64_msvc", 257 | ] 258 | 259 | [[package]] 260 | name = "windows_aarch64_gnullvm" 261 | version = "0.52.6" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 264 | 265 | [[package]] 266 | name = "windows_aarch64_msvc" 267 | version = "0.52.6" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 270 | 271 | [[package]] 272 | name = "windows_i686_gnu" 273 | version = "0.52.6" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 276 | 277 | [[package]] 278 | name = "windows_i686_gnullvm" 279 | version = "0.52.6" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 282 | 283 | [[package]] 284 | name = "windows_i686_msvc" 285 | version = "0.52.6" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 288 | 289 | [[package]] 290 | name = "windows_x86_64_gnu" 291 | version = "0.52.6" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 294 | 295 | [[package]] 296 | name = "windows_x86_64_gnullvm" 297 | version = "0.52.6" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 300 | 301 | [[package]] 302 | name = "windows_x86_64_msvc" 303 | version = "0.52.6" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 306 | -------------------------------------------------------------------------------- /host_reports/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.17" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.9" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.6" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys", 52 | ] 53 | 54 | [[package]] 55 | name = "clap" 56 | version = "4.5.20" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 59 | dependencies = [ 60 | "clap_builder", 61 | "clap_derive", 62 | ] 63 | 64 | [[package]] 65 | name = "clap_builder" 66 | version = "4.5.20" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 69 | dependencies = [ 70 | "anstream", 71 | "anstyle", 72 | "clap_lex", 73 | "strsim", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_derive" 78 | version = "4.5.18" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 81 | dependencies = [ 82 | "heck", 83 | "proc-macro2", 84 | "quote", 85 | "syn", 86 | ] 87 | 88 | [[package]] 89 | name = "clap_lex" 90 | version = "0.7.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 93 | 94 | [[package]] 95 | name = "colorchoice" 96 | version = "1.0.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 99 | 100 | [[package]] 101 | name = "heck" 102 | version = "0.5.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 105 | 106 | [[package]] 107 | name = "host_reports" 108 | version = "0.1.0" 109 | dependencies = [ 110 | "clap", 111 | "util", 112 | ] 113 | 114 | [[package]] 115 | name = "is_terminal_polyfill" 116 | version = "1.70.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 119 | 120 | [[package]] 121 | name = "itoa" 122 | version = "1.0.15" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 125 | 126 | [[package]] 127 | name = "memchr" 128 | version = "2.7.6" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 131 | 132 | [[package]] 133 | name = "proc-macro2" 134 | version = "1.0.89" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 137 | dependencies = [ 138 | "unicode-ident", 139 | ] 140 | 141 | [[package]] 142 | name = "quote" 143 | version = "1.0.37" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 146 | dependencies = [ 147 | "proc-macro2", 148 | ] 149 | 150 | [[package]] 151 | name = "ryu" 152 | version = "1.0.20" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 155 | 156 | [[package]] 157 | name = "serde" 158 | version = "1.0.228" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 161 | dependencies = [ 162 | "serde_core", 163 | ] 164 | 165 | [[package]] 166 | name = "serde_core" 167 | version = "1.0.228" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 170 | dependencies = [ 171 | "serde_derive", 172 | ] 173 | 174 | [[package]] 175 | name = "serde_derive" 176 | version = "1.0.228" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 179 | dependencies = [ 180 | "proc-macro2", 181 | "quote", 182 | "syn", 183 | ] 184 | 185 | [[package]] 186 | name = "serde_json" 187 | version = "1.0.145" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 190 | dependencies = [ 191 | "itoa", 192 | "memchr", 193 | "ryu", 194 | "serde", 195 | "serde_core", 196 | ] 197 | 198 | [[package]] 199 | name = "strsim" 200 | version = "0.11.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 203 | 204 | [[package]] 205 | name = "syn" 206 | version = "2.0.85" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 209 | dependencies = [ 210 | "proc-macro2", 211 | "quote", 212 | "unicode-ident", 213 | ] 214 | 215 | [[package]] 216 | name = "unicode-ident" 217 | version = "1.0.13" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 220 | 221 | [[package]] 222 | name = "utf8parse" 223 | version = "0.2.2" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 226 | 227 | [[package]] 228 | name = "util" 229 | version = "0.1.0" 230 | dependencies = [ 231 | "serde_json", 232 | ] 233 | 234 | [[package]] 235 | name = "windows-sys" 236 | version = "0.59.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 239 | dependencies = [ 240 | "windows-targets", 241 | ] 242 | 243 | [[package]] 244 | name = "windows-targets" 245 | version = "0.52.6" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 248 | dependencies = [ 249 | "windows_aarch64_gnullvm", 250 | "windows_aarch64_msvc", 251 | "windows_i686_gnu", 252 | "windows_i686_gnullvm", 253 | "windows_i686_msvc", 254 | "windows_x86_64_gnu", 255 | "windows_x86_64_gnullvm", 256 | "windows_x86_64_msvc", 257 | ] 258 | 259 | [[package]] 260 | name = "windows_aarch64_gnullvm" 261 | version = "0.52.6" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 264 | 265 | [[package]] 266 | name = "windows_aarch64_msvc" 267 | version = "0.52.6" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 270 | 271 | [[package]] 272 | name = "windows_i686_gnu" 273 | version = "0.52.6" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 276 | 277 | [[package]] 278 | name = "windows_i686_gnullvm" 279 | version = "0.52.6" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 282 | 283 | [[package]] 284 | name = "windows_i686_msvc" 285 | version = "0.52.6" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 288 | 289 | [[package]] 290 | name = "windows_x86_64_gnu" 291 | version = "0.52.6" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 294 | 295 | [[package]] 296 | name = "windows_x86_64_gnullvm" 297 | version = "0.52.6" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 300 | 301 | [[package]] 302 | name = "windows_x86_64_msvc" 303 | version = "0.52.6" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 306 | -------------------------------------------------------------------------------- /depends_cache/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.17" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.9" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.6" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 49 | dependencies = [ 50 | "anstyle", 51 | "windows-sys", 52 | ] 53 | 54 | [[package]] 55 | name = "clap" 56 | version = "4.5.20" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 59 | dependencies = [ 60 | "clap_builder", 61 | "clap_derive", 62 | ] 63 | 64 | [[package]] 65 | name = "clap_builder" 66 | version = "4.5.20" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 69 | dependencies = [ 70 | "anstream", 71 | "anstyle", 72 | "clap_lex", 73 | "strsim", 74 | ] 75 | 76 | [[package]] 77 | name = "clap_derive" 78 | version = "4.5.18" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 81 | dependencies = [ 82 | "heck", 83 | "proc-macro2", 84 | "quote", 85 | "syn", 86 | ] 87 | 88 | [[package]] 89 | name = "clap_lex" 90 | version = "0.7.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 93 | 94 | [[package]] 95 | name = "colorchoice" 96 | version = "1.0.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 99 | 100 | [[package]] 101 | name = "depends_cache" 102 | version = "0.1.0" 103 | dependencies = [ 104 | "clap", 105 | "serde", 106 | "util", 107 | ] 108 | 109 | [[package]] 110 | name = "heck" 111 | version = "0.5.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 114 | 115 | [[package]] 116 | name = "is_terminal_polyfill" 117 | version = "1.70.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 120 | 121 | [[package]] 122 | name = "itoa" 123 | version = "1.0.15" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 126 | 127 | [[package]] 128 | name = "memchr" 129 | version = "2.7.6" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 132 | 133 | [[package]] 134 | name = "proc-macro2" 135 | version = "1.0.89" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 138 | dependencies = [ 139 | "unicode-ident", 140 | ] 141 | 142 | [[package]] 143 | name = "quote" 144 | version = "1.0.37" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 147 | dependencies = [ 148 | "proc-macro2", 149 | ] 150 | 151 | [[package]] 152 | name = "ryu" 153 | version = "1.0.20" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 156 | 157 | [[package]] 158 | name = "serde" 159 | version = "1.0.228" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 162 | dependencies = [ 163 | "serde_core", 164 | ] 165 | 166 | [[package]] 167 | name = "serde_core" 168 | version = "1.0.228" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 171 | dependencies = [ 172 | "serde_derive", 173 | ] 174 | 175 | [[package]] 176 | name = "serde_derive" 177 | version = "1.0.228" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 180 | dependencies = [ 181 | "proc-macro2", 182 | "quote", 183 | "syn", 184 | ] 185 | 186 | [[package]] 187 | name = "serde_json" 188 | version = "1.0.145" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 191 | dependencies = [ 192 | "itoa", 193 | "memchr", 194 | "ryu", 195 | "serde", 196 | "serde_core", 197 | ] 198 | 199 | [[package]] 200 | name = "strsim" 201 | version = "0.11.1" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 204 | 205 | [[package]] 206 | name = "syn" 207 | version = "2.0.85" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 210 | dependencies = [ 211 | "proc-macro2", 212 | "quote", 213 | "unicode-ident", 214 | ] 215 | 216 | [[package]] 217 | name = "unicode-ident" 218 | version = "1.0.13" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 221 | 222 | [[package]] 223 | name = "utf8parse" 224 | version = "0.2.2" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 227 | 228 | [[package]] 229 | name = "util" 230 | version = "0.1.0" 231 | dependencies = [ 232 | "serde_json", 233 | ] 234 | 235 | [[package]] 236 | name = "windows-sys" 237 | version = "0.59.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 240 | dependencies = [ 241 | "windows-targets", 242 | ] 243 | 244 | [[package]] 245 | name = "windows-targets" 246 | version = "0.52.6" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 249 | dependencies = [ 250 | "windows_aarch64_gnullvm", 251 | "windows_aarch64_msvc", 252 | "windows_i686_gnu", 253 | "windows_i686_gnullvm", 254 | "windows_i686_msvc", 255 | "windows_x86_64_gnu", 256 | "windows_x86_64_gnullvm", 257 | "windows_x86_64_msvc", 258 | ] 259 | 260 | [[package]] 261 | name = "windows_aarch64_gnullvm" 262 | version = "0.52.6" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 265 | 266 | [[package]] 267 | name = "windows_aarch64_msvc" 268 | version = "0.52.6" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 271 | 272 | [[package]] 273 | name = "windows_i686_gnu" 274 | version = "0.52.6" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 277 | 278 | [[package]] 279 | name = "windows_i686_gnullvm" 280 | version = "0.52.6" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 283 | 284 | [[package]] 285 | name = "windows_i686_msvc" 286 | version = "0.52.6" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 289 | 290 | [[package]] 291 | name = "windows_x86_64_gnu" 292 | version = "0.52.6" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 295 | 296 | [[package]] 297 | name = "windows_x86_64_gnullvm" 298 | version = "0.52.6" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 301 | 302 | [[package]] 303 | name = "windows_x86_64_msvc" 304 | version = "0.52.6" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 307 | -------------------------------------------------------------------------------- /coverage_fuzz/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use util::{chdir, check_call, check_output, git}; 3 | 4 | fn gen_coverage( 5 | docker_exec: &dyn Fn(&str), 6 | assets_dir: &std::path::Path, 7 | dir_code: &std::path::Path, 8 | dir_result: &std::path::Path, 9 | git_ref: &str, 10 | make_jobs: u8, 11 | ) { 12 | println!( 13 | "Generate coverage for {} in {} (ref: {}).", 14 | dir_code.display(), 15 | dir_result.display(), 16 | git_ref 17 | ); 18 | chdir(dir_code); 19 | let dir_build = dir_code.join("build"); 20 | 21 | println!("Clear previous build and result folders"); 22 | 23 | let clear_dir = |folder: &std::path::Path| { 24 | std::fs::create_dir_all(folder).expect("Failed to create a folder"); 25 | docker_exec(&format!("rm -r {}", folder.display())); 26 | std::fs::create_dir_all(folder).expect("Failed to create a folder"); 27 | // Must change to a dir that exists after this function call 28 | }; 29 | 30 | clear_dir(&dir_build); 31 | clear_dir(dir_result); 32 | 33 | println!("Make coverage data in docker ..."); 34 | chdir(dir_code); 35 | 36 | docker_exec(&format!( 37 | "cmake -B {} -DBUILD_FOR_FUZZING=ON \ 38 | -DAPPEND_CXXFLAGS='-fprofile-update=atomic' \ 39 | -DAPPEND_CFLAGS='-fprofile-update=atomic' \ 40 | -DCMAKE_BUILD_TYPE=Coverage", 41 | dir_build.display() 42 | )); 43 | docker_exec(&format!( 44 | "cmake --build {} -j{}", 45 | dir_build.display(), 46 | make_jobs 47 | )); 48 | 49 | println!("Make coverage ..."); 50 | docker_exec(&format!( 51 | "cmake -DJOBS={} -DFUZZ_CORPORA_DIR={}/fuzz_corpora \ 52 | -DLCOV_OPTS='--rc branch_coverage=1 --ignore-errors mismatch,mismatch,inconsistent,inconsistent' \ 53 | -P {}/CoverageFuzz.cmake", 54 | make_jobs, 55 | assets_dir.display(), 56 | dir_build.display() 57 | )); 58 | docker_exec(&format!( 59 | "mv {}/*coverage* {}/", 60 | dir_build.display(), 61 | dir_result.display() 62 | )); 63 | chdir(dir_result); 64 | check_call(git().args(["checkout", "main"])); 65 | check_call(git().args(["add", "./"])); 66 | check_call(git().args([ 67 | "commit", 68 | "-m", 69 | &format!("Add fuzz coverage results for {}", git_ref), 70 | ])); 71 | check_call(git().args(["push", "origin", "main"])); 72 | 73 | // Work around permission errors 74 | clear_dir(dir_result); 75 | chdir(dir_result); 76 | check_call(git().args(["reset", "--hard", "HEAD"])); 77 | } 78 | 79 | fn calc_coverage( 80 | assets_dir: &std::path::Path, 81 | dir_code: &std::path::Path, 82 | dir_cov_report: &std::path::Path, 83 | make_jobs: u8, 84 | remote_url: &str, 85 | ) { 86 | println!("Start docker process ..."); 87 | std::fs::create_dir_all(dir_cov_report).expect("Failed to create dir_cov_report"); 88 | let docker_id = check_output(std::process::Command::new("podman").args([ 89 | "run", 90 | "-idt", 91 | "--rm", 92 | &format!( 93 | "--volume={}:{}:rw,z", 94 | assets_dir.display(), 95 | assets_dir.display() 96 | ), 97 | &format!( 98 | "--volume={}:{}:rw,z", 99 | dir_code.display(), 100 | dir_code.display() 101 | ), 102 | &format!( 103 | "--volume={}:{}:rw,z", 104 | dir_cov_report.display(), 105 | dir_cov_report.display() 106 | ), 107 | //'--mount', # Doesn't work with fedora (needs rw,z) 108 | //'type=bind,src={},dst={}'.format(dir_code, dir_code), 109 | //'--mount', 110 | //'type=bind,src={},dst={}'.format(dir_cov_report, dir_cov_report), 111 | "-e", 112 | "LC_ALL=C.UTF-8", 113 | "ubuntu:devel", 114 | ])); 115 | 116 | let docker_exec = |cmd: &str| { 117 | check_call(std::process::Command::new("podman").args([ 118 | "exec", 119 | &docker_id, 120 | "bash", 121 | "-c", 122 | &format!( 123 | "cd {} && {}", 124 | std::env::current_dir().expect("Failed to getcwd").display(), 125 | cmd 126 | ), 127 | ])) 128 | }; 129 | 130 | println!("Docker running with id {}.", docker_id); 131 | 132 | println!("Installing packages ..."); 133 | docker_exec("apt-get update"); 134 | docker_exec(&format!("apt-get install -qq {}", "python3-zmq libsqlite3-dev libevent-dev libboost-dev libcapnp-dev capnproto libzmq3-dev lcov build-essential cmake pkg-config")); 135 | 136 | println!("Generate coverage"); 137 | chdir(dir_code); 138 | let base_git_ref = &check_output(git().args(["log", "--format=%H", "-1", "HEAD"]))[..16]; 139 | chdir(assets_dir); 140 | let assets_git_ref = &check_output(git().args(["log", "--format=%H", "-1", "HEAD"]))[..16]; 141 | let dir_result_base = dir_cov_report.join(base_git_ref).join(assets_git_ref); 142 | gen_coverage( 143 | &docker_exec, 144 | assets_dir, 145 | dir_code, 146 | &dir_result_base, 147 | &format!("{base_git_ref}-code {assets_git_ref}-assets"), 148 | make_jobs, 149 | ); 150 | 151 | println!("{remote_url}/coverage_fuzz/monotree/{base_git_ref}/{assets_git_ref}/fuzz.coverage/index.html"); 152 | } 153 | 154 | #[derive(clap::Parser)] 155 | #[command(about = "Create Bitcoin Core fuzz coverage reports.", long_about = None)] 156 | struct Args { 157 | /// The repo slug of the remote on GitHub for reports. 158 | #[arg(long, default_value = "DrahtBot/reports")] 159 | repo_report: util::Slug, 160 | /// The remote url of the hosted html reports. 161 | #[arg( 162 | long, 163 | default_value = "https://drahtbot.space/host_reports/DrahtBot/reports" 164 | )] 165 | remote_url: String, 166 | /// The number of make jobs. 167 | #[arg(long, default_value_t = 2)] 168 | make_jobs: u8, 169 | /// The local dir used for scratching. 170 | #[arg(long)] 171 | scratch_dir: std::path::PathBuf, 172 | /// The ssh key for "repo_report". 173 | #[arg(long)] 174 | ssh_key: std::path::PathBuf, 175 | /// Which git ref in the code repo to build. 176 | #[arg(long, default_value = "master")] 177 | git_ref_code: String, 178 | /// Which git ref in the qa-assets repo to use. 179 | #[arg(long, default_value = "main")] 180 | git_ref_qa_assets: String, 181 | /// Which targets to build. 182 | #[arg(long, default_value = "")] 183 | fuzz_targets: String, 184 | } 185 | 186 | fn ensure_init_git(folder: &std::path::Path, url: &str) { 187 | println!("Clone {url} repo to {dir}", dir = folder.display()); 188 | if !folder.is_dir() { 189 | check_call(git().args(["clone", "--quiet", url]).arg(folder)); 190 | } 191 | } 192 | 193 | fn main() { 194 | let args = Args::parse(); 195 | 196 | std::fs::create_dir_all(&args.scratch_dir).expect("Failed to create scratch folder"); 197 | let temp_dir = args 198 | .scratch_dir 199 | .canonicalize() 200 | .expect("Failed to canonicalize scratch folder"); 201 | let ssh_cmd = format!( 202 | "ssh -i {} -F /dev/null", 203 | args.ssh_key 204 | .canonicalize() 205 | .expect("Failed to canonicalize ssh key") 206 | .display() 207 | ); 208 | 209 | let code_dir = temp_dir.join("code").join("monotree"); 210 | let code_url = "https://github.com/bitcoin/bitcoin"; 211 | let report_dir = temp_dir.join("reports"); 212 | let report_url = format!("git@github.com:{}.git", args.repo_report.str()); 213 | let assets_dir = temp_dir.join("assets"); 214 | let assets_url = "https://github.com/bitcoin-core/qa-assets"; 215 | 216 | ensure_init_git(&code_dir, code_url); 217 | ensure_init_git(&report_dir, &report_url); 218 | ensure_init_git(&assets_dir, assets_url); 219 | 220 | println!("Set git metadata"); 221 | chdir(&report_dir); 222 | check_call(git().args([ 223 | "config", 224 | "user.email", 225 | "39886733+DrahtBot@users.noreply.github.com", 226 | ])); 227 | check_call(git().args(["config", "user.name", "DrahtBot"])); 228 | check_call(git().args(["config", "core.sshCommand", &ssh_cmd])); 229 | 230 | println!("Fetching diffs ..."); 231 | chdir(&code_dir); 232 | check_call(git().args(["fetch", "origin", "--quiet", &args.git_ref_code])); 233 | check_call(git().args(["checkout", "FETCH_HEAD", "--force"])); 234 | check_call(git().args(["reset", "--hard", "HEAD"])); 235 | check_call(git().args(["clean", "-dfx"])); 236 | check_call(std::process::Command::new("sed").args([ 237 | "-i", 238 | &format!( 239 | "s/FUZZ_CORPORA_DIR}}/FUZZ_CORPORA_DIR}} {} /g", 240 | args.fuzz_targets 241 | ), 242 | "cmake/script/CoverageFuzz.cmake", 243 | ])); 244 | chdir(&report_dir); 245 | check_call(git().args(["fetch", "--quiet", "--all"])); 246 | check_call(git().args(["reset", "--hard", "HEAD"])); 247 | check_call(git().args(["checkout", "main"])); 248 | check_call(git().args(["reset", "--hard", "origin/main"])); 249 | chdir(&assets_dir); 250 | check_call(git().args(["fetch", "origin", "--quiet", &args.git_ref_qa_assets])); 251 | check_call(git().args(["checkout", "FETCH_HEAD", "--force"])); 252 | check_call(git().args(["clean", "-dfx"])); 253 | 254 | calc_coverage( 255 | &assets_dir, 256 | &code_dir, 257 | &report_dir.join("coverage_fuzz").join("monotree"), 258 | args.make_jobs, 259 | &args.remote_url, 260 | ); 261 | } 262 | -------------------------------------------------------------------------------- /stale/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(clap::Parser)] 4 | #[command(about = "\ 5 | Handle stale issues and pull requests: 6 | * Comment on pull requests that needed a rebase for too long.\n\ 7 | * Comment on pull requests that a failing CI for too long.\n\ 8 | * Comment on pull requests that are inactive for too long.\n\ 9 | * Update the label that indicates a rebase is required.\n\ 10 | ", long_about = None)] 11 | struct Args { 12 | /// The access token for GitHub. 13 | #[arg(long)] 14 | github_access_token: Option, 15 | /// The repo slugs of the remotes on GitHub. Format: owner/repo 16 | #[arg(long)] 17 | github_repo: Vec, 18 | /// The path to the yaml config file. 19 | #[arg(long)] 20 | config_file: std::path::PathBuf, 21 | /// Print changes/edits instead of calling the GitHub API. 22 | #[arg(long, default_value_t = false)] 23 | dry_run: bool, 24 | } 25 | 26 | #[derive(serde::Deserialize)] 27 | struct Config { 28 | inactive_rebase_days: i64, 29 | inactive_rebase_comment: String, 30 | inactive_ci_days: i64, 31 | inactive_ci_comment: String, 32 | inactive_stale_days: i64, 33 | inactive_stale_comment: String, 34 | needs_rebase_label: String, 35 | ci_failed_label: String, 36 | needs_rebase_comment: String, 37 | } 38 | 39 | async fn inactive_rebase( 40 | github: &octocrab::Octocrab, 41 | config: &Config, 42 | github_repo: &Vec, 43 | dry_run: bool, 44 | ) -> octocrab::Result<()> { 45 | let id_inactive_rebase_comment = util::IdComment::InactiveRebase.str(); 46 | 47 | let cutoff = 48 | { chrono::Utc::now() - chrono::Duration::days(config.inactive_rebase_days) }.format("%F"); 49 | println!("Mark inactive_rebase before date {} ...", cutoff); 50 | 51 | for util::Slug { owner, repo } in github_repo { 52 | println!("Get inactive_rebase pull requests for {owner}/{repo} ..."); 53 | let search_fmt = format!( 54 | "repo:{owner}/{repo} is:open is:pr label:\"{label}\" updated:<={cutoff}", 55 | owner = owner, 56 | repo = repo, 57 | label = config.needs_rebase_label, 58 | cutoff = cutoff 59 | ); 60 | let items = github 61 | .all_pages( 62 | github 63 | .search() 64 | .issues_and_pull_requests(&search_fmt) 65 | .send() 66 | .await?, 67 | ) 68 | .await?; 69 | let issues_api = github.issues(owner, repo); 70 | for (i, item) in items.iter().enumerate() { 71 | println!( 72 | "{}/{} (Item: {}/{}#{})", 73 | i, 74 | items.len(), 75 | owner, 76 | repo, 77 | item.number, 78 | ); 79 | let text = format!( 80 | "{}\n{}", 81 | id_inactive_rebase_comment, config.inactive_rebase_comment 82 | ); 83 | if !dry_run { 84 | issues_api.create_comment(item.number, text).await?; 85 | } 86 | } 87 | } 88 | Ok(()) 89 | } 90 | 91 | async fn inactive_ci( 92 | github: &octocrab::Octocrab, 93 | config: &Config, 94 | github_repo: &Vec, 95 | dry_run: bool, 96 | ) -> octocrab::Result<()> { 97 | let id_inactive_ci_comment = util::IdComment::InactiveCi.str(); 98 | 99 | let cutoff = 100 | { chrono::Utc::now() - chrono::Duration::days(config.inactive_ci_days) }.format("%F"); 101 | println!("Mark inactive_ci before date {} ...", cutoff); 102 | 103 | for util::Slug { owner, repo } in github_repo { 104 | println!("Get inactive_ci pull requests for {owner}/{repo} ..."); 105 | let search_fmt = format!( 106 | "repo:{owner}/{repo} is:open is:pr label:\"{label}\" updated:<={cutoff}", 107 | owner = owner, 108 | repo = repo, 109 | label = config.ci_failed_label, 110 | cutoff = cutoff 111 | ); 112 | let items = github 113 | .all_pages( 114 | github 115 | .search() 116 | .issues_and_pull_requests(&search_fmt) 117 | .send() 118 | .await?, 119 | ) 120 | .await?; 121 | let issues_api = github.issues(owner, repo); 122 | for (i, item) in items.iter().enumerate() { 123 | println!( 124 | "{}/{} (Item: {}/{}#{})", 125 | i, 126 | items.len(), 127 | owner, 128 | repo, 129 | item.number, 130 | ); 131 | let text = format!( 132 | "{}\n{}", 133 | id_inactive_ci_comment, 134 | config 135 | .inactive_ci_comment 136 | .replace("{owner}", owner) 137 | .replace("{repo}", repo) 138 | ); 139 | if !dry_run { 140 | issues_api.create_comment(item.number, text).await?; 141 | } 142 | } 143 | } 144 | Ok(()) 145 | } 146 | 147 | async fn inactive_stale( 148 | github: &octocrab::Octocrab, 149 | config: &Config, 150 | github_repo: &Vec, 151 | dry_run: bool, 152 | ) -> octocrab::Result<()> { 153 | let id_inactive_stale_comment = util::IdComment::InactiveStale.str(); 154 | 155 | let cutoff = 156 | { chrono::Utc::now() - chrono::Duration::days(config.inactive_stale_days) }.format("%F"); 157 | println!("Mark inactive_stale before date {} ...", cutoff); 158 | 159 | for util::Slug { owner, repo } in github_repo { 160 | println!("Get inactive_stale pull requests for {owner}/{repo} ..."); 161 | let search_fmt = format!( 162 | "repo:{owner}/{repo} is:open is:pr updated:<={cutoff}", 163 | owner = owner, 164 | repo = repo, 165 | cutoff = cutoff 166 | ); 167 | let items = github 168 | .all_pages( 169 | github 170 | .search() 171 | .issues_and_pull_requests(&search_fmt) 172 | .send() 173 | .await?, 174 | ) 175 | .await?; 176 | let issues_api = github.issues(owner, repo); 177 | for (i, item) in items.iter().enumerate() { 178 | println!( 179 | "{}/{} (Item: {}/{}#{})", 180 | i, 181 | items.len(), 182 | owner, 183 | repo, 184 | item.number, 185 | ); 186 | let text = format!( 187 | "{}\n{}", 188 | id_inactive_stale_comment, 189 | config 190 | .inactive_stale_comment 191 | .replace("{owner}", owner) 192 | .replace("{repo}", repo) 193 | ); 194 | if !dry_run { 195 | issues_api.create_comment(item.number, text).await?; 196 | } 197 | } 198 | } 199 | Ok(()) 200 | } 201 | 202 | async fn rebase_label( 203 | github: &octocrab::Octocrab, 204 | config: &Config, 205 | github_repo: &Vec, 206 | dry_run: bool, 207 | ) -> octocrab::Result<()> { 208 | let id_needs_rebase_comment = util::IdComment::NeedsRebase.str(); 209 | let id_inactive_rebase_comment = util::IdComment::InactiveRebase.str(); 210 | let id_inactive_stale_comment = util::IdComment::InactiveStale.str(); 211 | 212 | println!("Apply rebase label"); 213 | 214 | for util::Slug { owner, repo } in github_repo { 215 | println!("Get open pulls for {}/{} ...", owner, repo); 216 | let issues_api = github.issues(owner, repo); 217 | let pulls_api = github.pulls(owner, repo); 218 | let pulls = github 219 | .all_pages( 220 | pulls_api 221 | .list() 222 | .state(octocrab::params::State::Open) 223 | .send() 224 | .await?, 225 | ) 226 | .await?; 227 | println!("Open pulls: {}", pulls.len()); 228 | for (i, pull) in pulls.iter().enumerate() { 229 | println!( 230 | "{}/{} (Pull: {}/{}#{})", 231 | i, 232 | pulls.len(), 233 | owner, 234 | repo, 235 | pull.number 236 | ); 237 | let pull = util::get_pull_mergeable(&pulls_api, pull.number).await?; 238 | let pull = match pull { 239 | None => { 240 | continue; 241 | } 242 | Some(p) => p, 243 | }; 244 | let labels = github 245 | .all_pages(issues_api.list_labels_for_issue(pull.number).send().await?) 246 | .await?; 247 | let found_label_rebase = labels 248 | .into_iter() 249 | .any(|l| l.name == config.needs_rebase_label); 250 | if pull.mergeable.unwrap() { 251 | if found_label_rebase { 252 | println!("... remove label '{}')", config.needs_rebase_label); 253 | let all_comments = github 254 | .all_pages(issues_api.list_comments(pull.number).send().await?) 255 | .await?; 256 | let comments = all_comments 257 | .iter() 258 | .filter(|c| { 259 | let b = c.body.as_ref().unwrap(); 260 | b.starts_with(id_needs_rebase_comment) 261 | || b.starts_with(id_inactive_rebase_comment) 262 | || b.starts_with(id_inactive_stale_comment) 263 | }) 264 | .collect::>(); 265 | println!("... delete {} comments", comments.len()); 266 | if !dry_run { 267 | issues_api 268 | .remove_label(pull.number, &config.needs_rebase_label) 269 | .await?; 270 | for c in comments { 271 | issues_api.delete_comment(c.id).await?; 272 | } 273 | } 274 | } 275 | } else if !found_label_rebase { 276 | println!("... add label '{}'", config.needs_rebase_label); 277 | if !dry_run { 278 | issues_api 279 | .add_labels(pull.number, &[config.needs_rebase_label.to_string()]) 280 | .await?; 281 | let text = format!( 282 | "{}\n{}", 283 | id_needs_rebase_comment, 284 | config 285 | .needs_rebase_comment 286 | .replace("{owner}", owner) 287 | .replace("{repo}", repo) 288 | ); 289 | issues_api.create_comment(pull.number, text).await?; 290 | } 291 | } 292 | } 293 | } 294 | Ok(()) 295 | } 296 | 297 | #[tokio::main] 298 | async fn main() -> octocrab::Result<()> { 299 | let args = Args::parse(); 300 | let config: Config = serde_yaml::from_reader( 301 | std::fs::File::open(args.config_file).expect("config file path error"), 302 | ) 303 | .expect("yaml error"); 304 | 305 | let github = util::get_octocrab(args.github_access_token)?; 306 | 307 | inactive_rebase(&github, &config, &args.github_repo, args.dry_run).await?; 308 | inactive_ci(&github, &config, &args.github_repo, args.dry_run).await?; 309 | inactive_stale(&github, &config, &args.github_repo, args.dry_run).await?; 310 | rebase_label(&github, &config, &args.github_repo, args.dry_run).await?; 311 | 312 | Ok(()) 313 | } 314 | -------------------------------------------------------------------------------- /check_translations/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "is_terminal_polyfill", 17 | "utf8parse", 18 | ] 19 | 20 | [[package]] 21 | name = "anstyle" 22 | version = "1.0.10" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 25 | 26 | [[package]] 27 | name = "anstyle-parse" 28 | version = "0.2.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 31 | dependencies = [ 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle-query" 37 | version = "1.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 40 | dependencies = [ 41 | "windows-sys", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-wincon" 46 | version = "3.0.7" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 49 | dependencies = [ 50 | "anstyle", 51 | "once_cell", 52 | "windows-sys", 53 | ] 54 | 55 | [[package]] 56 | name = "block-buffer" 57 | version = "0.10.4" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 60 | dependencies = [ 61 | "generic-array", 62 | ] 63 | 64 | [[package]] 65 | name = "cfg-if" 66 | version = "1.0.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 69 | 70 | [[package]] 71 | name = "check_translations" 72 | version = "0.1.0" 73 | dependencies = [ 74 | "clap", 75 | "serde_json", 76 | "sha2", 77 | ] 78 | 79 | [[package]] 80 | name = "clap" 81 | version = "4.5.37" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" 84 | dependencies = [ 85 | "clap_builder", 86 | "clap_derive", 87 | ] 88 | 89 | [[package]] 90 | name = "clap_builder" 91 | version = "4.5.37" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" 94 | dependencies = [ 95 | "anstream", 96 | "anstyle", 97 | "clap_lex", 98 | "strsim", 99 | ] 100 | 101 | [[package]] 102 | name = "clap_derive" 103 | version = "4.5.32" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 106 | dependencies = [ 107 | "heck", 108 | "proc-macro2", 109 | "quote", 110 | "syn", 111 | ] 112 | 113 | [[package]] 114 | name = "clap_lex" 115 | version = "0.7.4" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 118 | 119 | [[package]] 120 | name = "colorchoice" 121 | version = "1.0.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 124 | 125 | [[package]] 126 | name = "cpufeatures" 127 | version = "0.2.17" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 130 | dependencies = [ 131 | "libc", 132 | ] 133 | 134 | [[package]] 135 | name = "crypto-common" 136 | version = "0.1.6" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 139 | dependencies = [ 140 | "generic-array", 141 | "typenum", 142 | ] 143 | 144 | [[package]] 145 | name = "digest" 146 | version = "0.10.7" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 149 | dependencies = [ 150 | "block-buffer", 151 | "crypto-common", 152 | ] 153 | 154 | [[package]] 155 | name = "generic-array" 156 | version = "0.14.7" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 159 | dependencies = [ 160 | "typenum", 161 | "version_check", 162 | ] 163 | 164 | [[package]] 165 | name = "heck" 166 | version = "0.5.0" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 169 | 170 | [[package]] 171 | name = "is_terminal_polyfill" 172 | version = "1.70.1" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 175 | 176 | [[package]] 177 | name = "itoa" 178 | version = "1.0.15" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 181 | 182 | [[package]] 183 | name = "libc" 184 | version = "0.2.172" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 187 | 188 | [[package]] 189 | name = "memchr" 190 | version = "2.7.4" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 193 | 194 | [[package]] 195 | name = "once_cell" 196 | version = "1.21.3" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 199 | 200 | [[package]] 201 | name = "proc-macro2" 202 | version = "1.0.95" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 205 | dependencies = [ 206 | "unicode-ident", 207 | ] 208 | 209 | [[package]] 210 | name = "quote" 211 | version = "1.0.40" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 214 | dependencies = [ 215 | "proc-macro2", 216 | ] 217 | 218 | [[package]] 219 | name = "ryu" 220 | version = "1.0.20" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 223 | 224 | [[package]] 225 | name = "serde" 226 | version = "1.0.219" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 229 | dependencies = [ 230 | "serde_derive", 231 | ] 232 | 233 | [[package]] 234 | name = "serde_derive" 235 | version = "1.0.219" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 238 | dependencies = [ 239 | "proc-macro2", 240 | "quote", 241 | "syn", 242 | ] 243 | 244 | [[package]] 245 | name = "serde_json" 246 | version = "1.0.140" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 249 | dependencies = [ 250 | "itoa", 251 | "memchr", 252 | "ryu", 253 | "serde", 254 | ] 255 | 256 | [[package]] 257 | name = "sha2" 258 | version = "0.10.8" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 261 | dependencies = [ 262 | "cfg-if", 263 | "cpufeatures", 264 | "digest", 265 | ] 266 | 267 | [[package]] 268 | name = "strsim" 269 | version = "0.11.1" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 272 | 273 | [[package]] 274 | name = "syn" 275 | version = "2.0.100" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 278 | dependencies = [ 279 | "proc-macro2", 280 | "quote", 281 | "unicode-ident", 282 | ] 283 | 284 | [[package]] 285 | name = "typenum" 286 | version = "1.18.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 289 | 290 | [[package]] 291 | name = "unicode-ident" 292 | version = "1.0.18" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 295 | 296 | [[package]] 297 | name = "utf8parse" 298 | version = "0.2.2" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 301 | 302 | [[package]] 303 | name = "version_check" 304 | version = "0.9.5" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 307 | 308 | [[package]] 309 | name = "windows-sys" 310 | version = "0.59.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 313 | dependencies = [ 314 | "windows-targets", 315 | ] 316 | 317 | [[package]] 318 | name = "windows-targets" 319 | version = "0.52.6" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 322 | dependencies = [ 323 | "windows_aarch64_gnullvm", 324 | "windows_aarch64_msvc", 325 | "windows_i686_gnu", 326 | "windows_i686_gnullvm", 327 | "windows_i686_msvc", 328 | "windows_x86_64_gnu", 329 | "windows_x86_64_gnullvm", 330 | "windows_x86_64_msvc", 331 | ] 332 | 333 | [[package]] 334 | name = "windows_aarch64_gnullvm" 335 | version = "0.52.6" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 338 | 339 | [[package]] 340 | name = "windows_aarch64_msvc" 341 | version = "0.52.6" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 344 | 345 | [[package]] 346 | name = "windows_i686_gnu" 347 | version = "0.52.6" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 350 | 351 | [[package]] 352 | name = "windows_i686_gnullvm" 353 | version = "0.52.6" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 356 | 357 | [[package]] 358 | name = "windows_i686_msvc" 359 | version = "0.52.6" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 362 | 363 | [[package]] 364 | name = "windows_x86_64_gnu" 365 | version = "0.52.6" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 368 | 369 | [[package]] 370 | name = "windows_x86_64_gnullvm" 371 | version = "0.52.6" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 374 | 375 | [[package]] 376 | name = "windows_x86_64_msvc" 377 | version = "0.52.6" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 380 | -------------------------------------------------------------------------------- /check_translations/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use serde_json::json; 3 | use std::fs; 4 | use std::io::Write; 5 | use std::path::Path; 6 | use std::process::{Command, Stdio}; 7 | use std::thread::sleep; 8 | use std::time::{Duration, Instant}; 9 | 10 | #[derive(Parser)] 11 | #[command(about="Check Bitcoin Core GUI translations.", long_about = None)] 12 | struct Args { 13 | /// From https://aistudio.google.com/apikey 14 | #[arg(long)] 15 | llm_api_key: String, 16 | 17 | /// The 'locale' folder that contains *.ts files 18 | #[arg(long)] 19 | translation_dir: String, 20 | 21 | /// A scratch folder to put temporary files for caching results 22 | #[arg(long)] 23 | cache_dir: String, 24 | 25 | /// A folder to export the reports to (reports will be overwritten) 26 | #[arg(long)] 27 | report_folder: String, 28 | 29 | /// Limit to those language files, instead of iterating over all files 30 | #[arg(long)] 31 | lang: Vec, 32 | 33 | /// How long to sleep between requests in seconds 34 | #[arg(long, default_value = "0")] 35 | rate_limit: u64, 36 | } 37 | 38 | fn main() { 39 | let args = Args::parse(); 40 | 41 | let ts_dir = fs::canonicalize(args.translation_dir).expect("locale dir must exist"); 42 | let cache_dir = fs::canonicalize(args.cache_dir).expect("cache dir must exist (can be empty)"); 43 | let report_folder = fs::canonicalize(args.report_folder) 44 | .expect("report folder must exist (files in it will be overwritten)"); 45 | 46 | for entry in fs::read_dir(ts_dir).expect("locale dir must exist") { 47 | let entry = entry.expect("locale file must exist"); 48 | let name = entry 49 | .file_name() 50 | .into_string() 51 | .expect("file name must be utf8"); 52 | 53 | if !name.ends_with(".ts") { 54 | println!("Skip file {name}"); 55 | continue; 56 | } 57 | 58 | let lang = name 59 | .strip_prefix("bitcoin_") 60 | .expect("ts file name unexpected") 61 | .strip_suffix(".ts") 62 | .expect("ts file name unexpected"); 63 | 64 | if lang == "en" || !args.lang.is_empty() && args.lang.iter().all(|a_l| a_l != lang) { 65 | println!("Skip file {name}"); 66 | continue; 67 | } 68 | 69 | let ts = fs::read_to_string(entry.path()).expect("Unable to read translation file"); 70 | 71 | let mut report_file = fs::File::create(report_folder.join(format!("{lang}.md"))) 72 | .expect("must be able to create empty report file"); 73 | report_file 74 | .write_all("# Translations Review by LLM (✨ experimental)\n\nThe review quality depends on the LLM and the language. To report LLM shortcomings for a specific language, please file an issue. It may be possible to re-run with a stronger model.\n\n".as_bytes()) 75 | .unwrap(); 76 | 77 | check( 78 | lang, 79 | &cache_dir, 80 | &ts, 81 | &args.llm_api_key, 82 | &report_file, 83 | Duration::from_secs(args.rate_limit), 84 | ); 85 | } 86 | } 87 | 88 | fn cache_key(lang: &str, msg: &str) -> String { 89 | use sha2::{Digest, Sha256}; 90 | let mut hasher = Sha256::new(); 91 | hasher.update(msg); 92 | let result = hasher.finalize(); 93 | format!("cache_translation_check_{lang}_{result:x}") 94 | } 95 | 96 | fn print_result( 97 | num_issues: &mut u32, 98 | cache_file: &Path, 99 | res: &str, 100 | prompt: &str, 101 | msg: &str, 102 | mut report_file: &fs::File, 103 | ) { 104 | if res.starts_with("NO") { 105 | // no spam, all good 106 | } else if res.starts_with("SPAM") || res.starts_with("ERR") || res.starts_with("UNK_LANG") { 107 | *num_issues += 1; 108 | report_file 109 | .write_all( 110 | format!( 111 | "\n\n```\n{msg}\n{res}\n```\n", 112 | cache_key = cache_file.file_name().unwrap().to_str().unwrap(), 113 | msg = msg.trim_matches('\n'), 114 | res = res.trim_matches('\n'), 115 | ) 116 | .as_bytes(), 117 | ) 118 | .unwrap(); 119 | println!( 120 | "\n#### Erroneous translation:\n[cache file]: {file}\n{prompt}\n{res}\n---\n", 121 | file = cache_file 122 | .file_name() 123 | .expect("cache file must have name") 124 | .to_str() 125 | .expect("cache file name must be valid utf8"), 126 | ); 127 | } else { 128 | panic!("File {} corrupt!\nAdjust prompt?", cache_file.display()); 129 | } 130 | } 131 | 132 | fn check( 133 | lang: &str, 134 | cache_dir: &Path, 135 | ts: &str, 136 | token: &str, 137 | mut report_file: &fs::File, 138 | rate_limit_wait: Duration, 139 | ) { 140 | // Sadly, the language support of LLMs is mixed and usually not well documented, except for the 141 | // Gemini series. 142 | // 143 | // See https://ai.google.dev/gemini-api/docs/models#supported-languages 144 | // 145 | // However, the stronger Gemini models come with strict rate limits in Tier 1. 146 | // 147 | // From https://ai.google.dev/gemini-api/docs/rate-limits#tier-1 148 | let url = "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions"; 149 | let model = "gemini-2.5-pro"; 150 | 151 | // let url = "https://api.openai.com/v1/chat/completions"; 152 | // let model = "gpt-5"; 153 | 154 | report_file 155 | .write_all(format!("\n\n
{lang}\n\n[If the result is outdated or of low quality, please file an issue to request and updated run for this language.](../../issues/new?title=%5B{lang}%5D%20request)\n\n").as_bytes()) 156 | .unwrap(); 157 | 158 | let mut num_issues = 0; 159 | 160 | for msg in ts.split("").skip(1) { 161 | let msg = msg 162 | .split("") 163 | .next() 164 | .expect("Must have closed message tag"); 165 | let msg = msg 166 | // shorten msg in prompt 167 | .replace("", ""); 168 | let shortcut_key_prompt = if msg.contains("&") { 169 | "- A single & in the English text and the translation is usually used to indicate the shortcut key. Allow it to be placed anywhere, but ensure it exists exactly once." 170 | } else { 171 | "" 172 | }; 173 | let prompt = format!( 174 | r#" 175 | Evaluate the provided translation from English to the language '{lang}' for unwanted content, erroneous content, or spam. 176 | 177 | - Assess the translation for accuracy and whether it is problematic in any way. 178 | - The English text is wrapped in 179 | - The '{lang}' text is wrapped in 180 | - Ensure that format specifiers (% prefix) are taken over correctly from the source to the translation. 181 | - Ensure that no whitespace format issues exist. For example, stray spacing or double space. 182 | {shortcut_key_prompt} 183 | 184 | 185 | # Output Format 186 | 187 | - If the translation is unproblematic, output: "NO". 188 | - If you are unfamiliar with the language specified by '{lang}', output: "UNK_LANG". 189 | - If the translation is into a language completely unrelated to '{lang}', or contains unrelated gibberish, output: "SPAM", followed by a brief explanation and the correct translation. 190 | - If the translation is problematic for other reasons, output: "ERR", followed by a brief explanation and the correct translation. 191 | - You must start your output with "NO", "ERR", "SPAM", or "UNK_LANG". 192 | 193 | 194 | # Translation context 195 | 196 | The translation appears in the context of Bitcoin: 197 | - "address" could mean a P2P Bitcoin network address, or a Bitcoin wallet address. 198 | - "change" usually refers to the change amount in a Bitcoin transaction. 199 | 200 | 201 | # Example (correct translation for 'zh_CN') 202 | 203 | %1 will download and store a copy of the Bitcoin block chain. 204 | %1 将会下载并存储比特币区块链。 205 | 206 | NO 207 | 208 | 209 | # Example (unknown language for 'sm') 210 | 211 | Enter address or label to search 212 | Tu'uina le atunu'u po'o le ata e su'e ai 213 | 214 | UNK_LANG, not familiar with the Samoan language 'sm'. 215 | 216 | 217 | # Example (erroneous 'de' translation) 218 | 219 | Unable to open %s for writing 220 | Konnte %s nicht zum Schreiben zu öffnen 221 | 222 | ERR 223 | The German grammar is incorrect. The verb 'öffnen' should be in the infinitive without 'zu' when used with the modal verb 'konnte'. 224 | 225 | Correct translation: 226 | Konnte %s nicht zum Schreiben öffnen 227 | 228 | 229 | # Example (spam 'de' translation) 230 | 231 | Create a new address 232 | <br>( 233 | 234 | SPAM 235 | Rather than providing a correct German translation, the response includes unrelated code. 236 | 237 | Correct translation: 238 | Neue Adresse erstellen 239 | 240 | 241 | # Example (spam 'de' translation) 242 | 243 | Delete the currently selected address from the list 244 | Usuń aktualnie wybrany adres z listy 245 | 246 | SPAM 247 | The translation is in Polish, not German as requested. 248 | 249 | Correct translation: 250 | Ausgewählte Adresse aus der Liste entfernen 251 | 252 | 253 | 254 | # Translation 255 | 256 | Evaluate this '{lang}' translation: 257 | 258 | {msg} 259 | 260 | "#, 261 | ); 262 | 263 | let cache_file = cache_dir.join(cache_key(lang, &msg)); 264 | 265 | match fs::read_to_string(&cache_file) { 266 | Ok(contents) => { 267 | print_result( 268 | &mut num_issues, 269 | &cache_file, 270 | &contents, 271 | &prompt, 272 | &msg, 273 | report_file, 274 | ); 275 | } 276 | Err(_) => { 277 | println!( 278 | "Cache miss [file= {file}] for prompt=\n{prompt}", 279 | file = cache_file.display() 280 | ); 281 | let sleep_target = Instant::now() + rate_limit_wait; 282 | let payload = json!({ 283 | "model": model, 284 | "messages": [ 285 | {"role": "user", "content": prompt} 286 | ] 287 | }); 288 | 289 | let curl_out = Command::new("curl") 290 | .arg("-X") 291 | .arg("POST") 292 | .arg("-H") 293 | .arg("Content-Type: application/json") 294 | .arg(url) 295 | .arg("-H") 296 | .arg(format!("Authorization: Bearer {token}")) 297 | .arg("-d") 298 | .arg(serde_json::to_string(&payload).expect("Failed to serialize payload")) 299 | .stderr(Stdio::inherit()) 300 | .output() 301 | .expect("Failed to execute curl"); 302 | assert!(curl_out.status.success()); 303 | let response: serde_json::Value = serde_json::from_str( 304 | &String::from_utf8(curl_out.stdout).expect("must be valid utf8"), 305 | ) 306 | .expect("must be valid json"); 307 | println!("... {response}"); 308 | let val = response["choices"][0]["message"]["content"] 309 | .as_str() 310 | .expect("Content not found") 311 | .trim(); 312 | fs::write(&cache_file, val).expect("Must be able to write cache file"); 313 | print_result( 314 | &mut num_issues, 315 | &cache_file, 316 | val, 317 | &prompt, 318 | &msg, 319 | report_file, 320 | ); 321 | sleep(sleep_target - Instant::now()); 322 | } 323 | } 324 | } 325 | report_file 326 | .write_all(format!("
\n\nNumber of issues: {num_issues}.\n").as_bytes()) 327 | .unwrap(); 328 | } 329 | -------------------------------------------------------------------------------- /webhook_features/src/features/ci_status.rs: -------------------------------------------------------------------------------- 1 | use super::{Feature, FeatureMeta}; 2 | use crate::errors::DrahtBotError; 3 | use crate::errors::Result; 4 | use crate::Context; 5 | use crate::GitHubEvent; 6 | use async_trait::async_trait; 7 | use std::process::{Command, Stdio}; 8 | 9 | pub struct CiStatusFeature { 10 | meta: FeatureMeta, 11 | } 12 | 13 | impl CiStatusFeature { 14 | pub fn new() -> Self { 15 | Self { 16 | meta: FeatureMeta::new( 17 | "CI Status", 18 | "Set a label for a failing CI status. Must also be enabled in the config yaml.", 19 | vec![GitHubEvent::CheckSuite], 20 | ), 21 | } 22 | } 23 | } 24 | 25 | #[async_trait] 26 | impl Feature for CiStatusFeature { 27 | fn meta(&self) -> &FeatureMeta { 28 | &self.meta 29 | } 30 | 31 | async fn handle( 32 | &self, 33 | ctx: &Context, 34 | event: &GitHubEvent, 35 | payload: &serde_json::Value, 36 | ) -> Result<()> { 37 | let ci_failed_label = "CI failed"; 38 | let action = payload["action"] 39 | .as_str() 40 | .ok_or(DrahtBotError::KeyNotFound)?; 41 | 42 | let repo_user = payload["repository"]["owner"]["login"] 43 | .as_str() 44 | .ok_or(DrahtBotError::KeyNotFound)?; 45 | 46 | let repo_name = payload["repository"]["name"] 47 | .as_str() 48 | .ok_or(DrahtBotError::KeyNotFound)?; 49 | 50 | println!( 51 | "Handling: {repo_user}/{repo_name} {event}::{action} ({feature_name})", 52 | feature_name = self.meta().name() 53 | ); 54 | if !ctx 55 | .config 56 | .repositories 57 | .iter() 58 | .find(|r| r.repo_slug == format!("{}/{}", repo_user, repo_name)) 59 | .is_some_and(|c| c.ci_status) 60 | { 61 | return Ok(()); 62 | } 63 | match event { 64 | GitHubEvent::CheckSuite if action == "completed" => { 65 | // https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=completed#check_suite 66 | let conclusion = payload["check_suite"]["conclusion"] 67 | .as_str() 68 | .ok_or(DrahtBotError::KeyNotFound)?; 69 | if conclusion == "cancelled" || conclusion == "neutral" { 70 | // Fall-through and treat as failure. Will be re-set on the new check_suite 71 | // result. 72 | } 73 | let success = "success" == conclusion; 74 | let suite_id = payload["check_suite"]["id"] 75 | .as_u64() 76 | .ok_or(DrahtBotError::KeyNotFound)?; 77 | let checks_api = ctx.octocrab.checks(repo_user, repo_name); 78 | let check_runs = checks_api 79 | .list_check_runs_in_a_check_suite(suite_id.into()) 80 | .per_page(99) 81 | .send() 82 | .await? 83 | .check_runs; 84 | let pull_number = { 85 | let mut pull_number = None; 86 | // Hacky way to get the pull number. See also https://github.com/maflcko/DrahtBot/issues/59#issuecomment-3472438198 87 | for check_run in check_runs.iter().filter(|c| c.output.annotations_count > 0) { 88 | let annotations = checks_api 89 | .list_annotations(check_run.id) 90 | .per_page(99) 91 | .send() 92 | .await?; 93 | if let Some(pr_str) = annotations.iter().find(|a| { 94 | a.title.as_deref().unwrap_or_default() 95 | == "debug_pull_request_number_str" 96 | }) { 97 | pull_number = Some( 98 | pr_str 99 | .message 100 | .as_deref() 101 | .ok_or(DrahtBotError::KeyNotFound)? 102 | .parse::()?, 103 | ); 104 | break; 105 | } 106 | } 107 | pull_number 108 | }; 109 | if pull_number.is_none() { 110 | return Ok(()); 111 | } 112 | let pull_number = pull_number.unwrap(); 113 | println!("... pull number {pull_number} conclusion: {conclusion}"); 114 | let issues_api = ctx.octocrab.issues(repo_user, repo_name); 115 | let issue = issues_api.get(pull_number).await?; 116 | if issue.state != octocrab::models::IssueState::Open { 117 | return Ok(()); 118 | }; 119 | let labels = ctx 120 | .octocrab 121 | .all_pages(issues_api.list_labels_for_issue(pull_number).send().await?) 122 | .await?; 123 | let found_label = labels.into_iter().any(|l| l.name == ci_failed_label); 124 | if found_label && success { 125 | println!("... {} remove label '{}')", pull_number, ci_failed_label); 126 | if !ctx.dry_run { 127 | issues_api 128 | .remove_label(pull_number, &ci_failed_label) 129 | .await?; 130 | } 131 | } else if !found_label && !success { 132 | println!( 133 | "... {} add label '{}' due to {}", 134 | pull_number, ci_failed_label, conclusion 135 | ); 136 | if !ctx.dry_run { 137 | issues_api 138 | .add_labels(pull_number, &[ci_failed_label.to_string()]) 139 | .await?; 140 | // Check if *compile* failed and add comment 141 | // (functional tests are ignored due to intermittent issues) 142 | for run in check_runs 143 | .iter() 144 | .filter(|r| r.conclusion.as_deref().unwrap_or_default() != "success") 145 | { 146 | let curl_out = Command::new("curl") 147 | .args([ 148 | "-L", 149 | "-H", 150 | "Accept: application/vnd.github+json", 151 | "-H", 152 | &format!("Authorization: Bearer {}", ctx.github_token), 153 | "-H", 154 | "X-GitHub-Api-Version: 2022-11-28", 155 | &format!( 156 | "https://api.github.com/repos/{}/{}/actions/jobs/{}/logs", 157 | repo_user, repo_name, run.id 158 | ), 159 | ]) 160 | .stderr(Stdio::inherit()) 161 | .output() 162 | .expect("Failed to execute curl"); 163 | assert!(curl_out.status.success()); // Could ignore error code or use exit_ok()? in the future. 164 | let full_text = String::from_utf8_lossy(&curl_out.stdout); 165 | 166 | // excerpt 167 | let text = full_text 168 | .lines() 169 | .rev() 170 | .take(100) // 100 lines 171 | .collect::>() 172 | .into_iter() 173 | .rev() 174 | .collect::>() 175 | .join("\n") 176 | .chars() 177 | .rev() 178 | .take(10_000) // 10k unicode chars 179 | .collect::>() 180 | .into_iter() 181 | .rev() 182 | .collect::(); 183 | 184 | if text.contains("make: *** [Makefile") // build 185 | || text.contains("Errors while running CTest") 186 | || text.contains("Error: Unexpected dependencies were detected. Check previous output.") // tidy (deps) 187 | || text.contains("ailure generated from") // lint, tidy, fuzz 188 | { 189 | let llm_reason = get_llm_reason( 190 | &text, 191 | &ctx.llm_token, 192 | ) 193 | .await 194 | .unwrap_or("(empty)".to_string()); 195 | let comment = format!( 196 | r#"{id} 197 | {msg} 198 | Task `{check_name}`: {url} 199 | LLM reason (✨ experimental): {llm_reason} 200 | {hints} 201 | "#, 202 | id = util::IdComment::CiFailed.str(), 203 | msg = "🚧 At least one of the CI tasks failed.", 204 | check_name = run.name, 205 | url = run.html_url.as_deref().unwrap_or_default(), 206 | hints = r#" 207 |
Hints 208 | 209 | Try to run the tests locally, according to the documentation. However, a CI failure may still 210 | happen due to a number of reasons, for example: 211 | 212 | * Possibly due to a silent merge conflict (the changes in this pull request being 213 | incompatible with the current code in the target branch). If so, make sure to rebase on the latest 214 | commit of the target branch. 215 | 216 | * A sanitizer issue, which can only be found by compiling with the sanitizer and running the 217 | affected test. 218 | 219 | * An intermittent issue. 220 | 221 | Leave a comment here, if you need help tracking down a confusing failure. 222 | 223 |
224 | "#, 225 | ); 226 | issues_api.create_comment(pull_number, comment).await?; 227 | break; 228 | } 229 | } 230 | } 231 | } 232 | } 233 | _ => {} 234 | } 235 | Ok(()) 236 | } 237 | } 238 | 239 | async fn get_llm_reason(ci_log: &str, llm_token: &str) -> Result { 240 | let client = reqwest::Client::new(); 241 | println!(" ... Run LLM summary for CI failure."); 242 | let payload = serde_json::json!({ 243 | "model": "gpt-5-nano", 244 | "messages": [ 245 | { 246 | "role": "developer", 247 | "content": [ 248 | { 249 | "type": "text", 250 | "text": 251 | r#" 252 | Analyze the tail of a CI log to determine and communicate the underlying reason for the CI failure. 253 | 254 | Consider potential causes such as build errors, ctest errors, clang-tidy errors, lint test errors, or fuzz test errors, even if the log is truncated. 255 | 256 | # Steps 257 | 258 | - Read and parse the provided CI log tail. 259 | - If multiple errors appear, prioritize according to potential severity or probable cause of failure and identify the most significant underlying reason. 260 | - Formulate a concise, one-line summary that clearly communicates the identified reason for the failure in the shortest form possible. 261 | 262 | # Output Format 263 | 264 | A single short sentence summarizing the underlying reason for the CI failure. 265 | "# 266 | } 267 | ] 268 | }, 269 | { 270 | "role": "user", 271 | "content": [ 272 | { 273 | "type": "text", 274 | "text":ci_log 275 | } 276 | ] 277 | } 278 | ], 279 | "response_format": { 280 | "type": "text" 281 | }, 282 | "verbosity": "low", 283 | "reasoning_effort": "low", 284 | "service_tier": "default", 285 | "store": true 286 | }); 287 | let response = client 288 | .post("https://api.openai.com/v1/chat/completions") 289 | .header("Content-Type", "application/json") 290 | .header("Authorization", format!("Bearer {}", llm_token)) 291 | .json(&payload) 292 | .send() 293 | .await? 294 | .json::() 295 | .await?; 296 | let text = response["choices"][0]["message"]["content"] 297 | .as_str() 298 | .ok_or(DrahtBotError::KeyNotFound)? 299 | .to_string(); 300 | Ok(text) 301 | } 302 | -------------------------------------------------------------------------------- /conflicts/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use std::io::Write; 3 | 4 | #[derive(clap::Parser)] 5 | #[command(about = "Determine conflicting pull requests in a monotree by merging them pairwise.", long_about = None)] 6 | struct Args { 7 | /// The access token for GitHub. 8 | #[arg(long)] 9 | github_access_token: Option, 10 | /// The repo slugs of the monotree remotes on GitHub. Format: owner/repo 11 | #[arg(long)] 12 | github_repo: Vec, 13 | /// Update the conflict comment and label for this pull request. Format: owner/repo/number 14 | #[arg(long, value_parser=parse_pull_id)] 15 | pull_id: Option, 16 | /// Update all conflicts comments and labels. 17 | #[arg(long, default_value_t = false)] 18 | update_comments: bool, 19 | /// The local dir used for scratching. 20 | #[arg(long)] 21 | scratch_dir: std::path::PathBuf, 22 | /// The path to the yaml config file. 23 | #[arg(long)] 24 | config_file: std::path::PathBuf, 25 | /// Print changes/edits instead of calling the GitHub API. 26 | #[arg(long, default_value_t = false)] 27 | dry_run: bool, 28 | } 29 | 30 | fn parse_pull_id(val: &str) -> Result { 31 | if 3 == val.split('/').count() { 32 | return Ok(val.to_string()); 33 | } 34 | Err("".to_string()) 35 | } 36 | 37 | #[derive(serde::Deserialize)] 38 | struct Config { 39 | conflicts_heading: String, 40 | conflicts_description: String, 41 | conflicts_empty: String, 42 | } 43 | 44 | fn init_git(monotree_dir: &std::path::Path, repos: &Vec) { 45 | if monotree_dir.is_dir() { 46 | return; 47 | } 48 | for sl in repos { 49 | let sl = sl.str(); 50 | let url = format!("https://github.com/{sl}"); 51 | println!("Clone {url} repo to {dir}", dir = monotree_dir.display()); 52 | if !monotree_dir.is_dir() { 53 | util::check_call( 54 | util::git() 55 | .args(["clone", "--quiet", &url]) 56 | .arg(monotree_dir), 57 | ); 58 | } 59 | println!("Set git metadata"); 60 | util::chdir(monotree_dir); 61 | { 62 | let err = "git config file error"; 63 | let mut f = std::fs::OpenOptions::new() 64 | .append(true) 65 | .open(monotree_dir.join(".git").join("config")) 66 | .expect(err); 67 | writeln!(f, "[remote \"con_pull_ref/{sl}\"]").expect(err); 68 | writeln!(f, " url = {url}").expect(err); 69 | writeln!(f, " fetch = +refs/pull/*:refs/remotes/upstream-pull/*").expect(err); 70 | } 71 | util::check_call(util::git().args(["config", "fetch.showForcedUpdates", "false"])); 72 | util::check_call(util::git().args(["config", "user.email", "no@ne.nl"])); 73 | util::check_call(util::git().args(["config", "user.name", "none"])); 74 | util::check_call(util::git().args(["config", "gc.auto", "0"])); 75 | } 76 | } 77 | 78 | struct MetaPull { 79 | pull: octocrab::models::pulls::PullRequest, 80 | head_commit: String, 81 | slug: util::Slug, 82 | slug_num: String, 83 | merge_commit: Option, 84 | } 85 | 86 | const MERGE_STRATEGY: &str = 87 | // https://github.blog/changelog/2022-09-12-merge-commits-now-created-using-the-merge-ort-strategy/ 88 | "--strategy=ort"; 89 | 90 | fn calc_mergeable(pulls: Vec, base_branch: &str) -> Vec { 91 | let base_id = util::check_output( 92 | util::git() 93 | .args(["log", "-1", "--format=%H"]) 94 | .arg(format!("origin/{base_branch}")), 95 | ); 96 | let mut ret = Vec::new(); 97 | for mut p in pulls { 98 | util::check_call(util::git().args(["checkout", &base_id, "--quiet"])); 99 | let mergeable = util::call( 100 | util::git() 101 | .args(["merge", MERGE_STRATEGY, "--quiet", &p.head_commit, "-m"]) 102 | .arg(format!("Prepare base for {id}", id = p.slug_num)), 103 | ); 104 | 105 | if mergeable { 106 | p.merge_commit = Some(util::check_output(util::git().args([ 107 | "log", 108 | "-1", 109 | "--format=%H", 110 | "HEAD", 111 | ]))); 112 | ret.push(p); 113 | } else { 114 | util::check_call(util::git().args(["merge", "--abort"])); 115 | } 116 | } 117 | ret 118 | } 119 | 120 | fn calc_conflicts<'a>( 121 | pulls_mergeable: &'a Vec, 122 | pull_check: &MetaPull, 123 | ) -> Vec<&'a MetaPull> { 124 | let mut conflicts = Vec::new(); 125 | let base_id = util::check_output(util::git().args([ 126 | "log", 127 | "-1", 128 | "--format=%H", 129 | pull_check.merge_commit.as_ref().expect("merge id missing"), 130 | ])); 131 | for pull_other in pulls_mergeable { 132 | if pull_check.slug_num == pull_other.slug_num { 133 | continue; 134 | } 135 | util::check_call(util::git().args(["checkout", &base_id, "--quiet"])); 136 | if !util::call( 137 | util::git() 138 | .args([ 139 | "merge", 140 | MERGE_STRATEGY, 141 | "--quiet", 142 | &pull_other.head_commit, 143 | "-m", 144 | ]) 145 | .arg(format!( 146 | "Merge base_{pr_id}+{pr_o_id}", 147 | pr_id = pull_check.slug_num, 148 | pr_o_id = pull_other.slug_num 149 | )), 150 | ) { 151 | util::check_call(util::git().args(["merge", "--abort"])); 152 | conflicts.push(pull_other); 153 | } 154 | } 155 | conflicts 156 | } 157 | 158 | async fn update_comment( 159 | config: &Config, 160 | api: &octocrab::Octocrab, 161 | dry_run: bool, 162 | pull: &MetaPull, 163 | pulls_conflict: &[&MetaPull], 164 | ) -> octocrab::Result<()> { 165 | let api_issues = api.issues(&pull.slug.owner, &pull.slug.repo); 166 | let mut cmt = util::get_metadata_sections(api, &api_issues, pull.pull.number).await?; 167 | if pulls_conflict.is_empty() { 168 | if cmt.id.is_none() || !cmt.has_section(&util::IdComment::SecConflicts) { 169 | // No conflict and no section to update 170 | return Ok(()); 171 | } 172 | // Update section for no conflicts 173 | util::update_metadata_comment( 174 | &api_issues, 175 | &mut cmt, 176 | &format!( 177 | "\n### {hd}\n{txt}", 178 | hd = config.conflicts_heading, 179 | txt = config.conflicts_empty, 180 | ), 181 | util::IdComment::SecConflicts, 182 | dry_run, 183 | ) 184 | .await?; 185 | return Ok(()); 186 | } 187 | 188 | util::update_metadata_comment( 189 | &api_issues, 190 | &mut cmt, 191 | &format!( 192 | "\n### {hd}\n{txt}", 193 | hd = config.conflicts_heading, 194 | txt = config.conflicts_description.replace( 195 | "{conflicts}", 196 | &pulls_conflict 197 | .iter() 198 | .map(|p| format!( 199 | "\n* [#{sn}]({url}) ({title} by {user})", 200 | sn = p 201 | .slug_num 202 | .trim_start_matches(&format!("{sl}/", sl = pull.slug.str())), 203 | url = p.pull.html_url.as_ref().expect("remote api error"), 204 | title = p.pull.title.as_ref().expect("remote api error").trim(), 205 | user = p.pull.user.as_ref().expect("remote api error").login 206 | )) 207 | .collect::>() 208 | .join("") 209 | ) 210 | ), 211 | util::IdComment::SecConflicts, 212 | dry_run, 213 | ) 214 | .await?; 215 | Ok(()) 216 | } 217 | 218 | #[tokio::main] 219 | async fn main() -> octocrab::Result<()> { 220 | let args = Args::parse(); 221 | 222 | let config: Config = serde_yaml::from_reader( 223 | std::fs::File::open(args.config_file).expect("config file path error"), 224 | ) 225 | .expect("yaml error"); 226 | 227 | let github = util::get_octocrab(args.github_access_token)?; 228 | 229 | std::fs::create_dir_all(&args.scratch_dir).expect("invalid scratch_dir"); 230 | 231 | let monotree_dir = args 232 | .scratch_dir 233 | .canonicalize() 234 | .expect("invalid scratch_dir") 235 | .join( 236 | args.github_repo 237 | .iter() 238 | .map(|s| format!("{}_{}", s.owner, s.repo)) 239 | .collect::>() 240 | .join("_"), 241 | ) 242 | .join("persist"); 243 | let temp_dir = monotree_dir.parent().unwrap().join("temp"); 244 | std::fs::create_dir_all(&temp_dir).expect("invalid temp_dir"); 245 | 246 | init_git(&monotree_dir, &args.github_repo); 247 | 248 | println!("Fetching diffs ..."); 249 | util::chdir(&monotree_dir); 250 | util::check_call(util::git().args(["fetch", "--quiet", "--all"])); 251 | 252 | let mut base_names = Vec::new(); 253 | let mut pull_blobs = Vec::new(); 254 | for s in &args.github_repo { 255 | let util::Slug { owner, repo } = s; 256 | println!("Fetching open pulls for {sl} ...", sl = s.str()); 257 | let base_name = github 258 | .repos(owner, repo) 259 | .get() 260 | .await? 261 | .default_branch 262 | .expect("remote api error"); 263 | let pulls_api = github.pulls(owner, repo); 264 | let pulls = github 265 | .all_pages( 266 | pulls_api 267 | .list() 268 | .state(octocrab::params::State::Open) 269 | .base(&base_name) 270 | .send() 271 | .await?, 272 | ) 273 | .await?; 274 | println!( 275 | "Open {base_name}-pulls for {sl}: {len}", 276 | sl = s.str(), 277 | len = pulls.len() 278 | ); 279 | base_names.push(base_name); 280 | pull_blobs.push((pulls, s)); 281 | } 282 | let mut mono_pulls = Vec::new(); 283 | for (ps, slug) in pull_blobs { 284 | let sl = slug.str(); 285 | println!("Store diffs for {sl}"); 286 | util::check_call( 287 | util::git() 288 | .args(["fetch", "--quiet"]) 289 | .arg(format!("con_pull_ref/{sl}")), 290 | ); 291 | for p in ps { 292 | let num = p.number; 293 | mono_pulls.push(MetaPull { 294 | pull: p, 295 | head_commit: util::check_output( 296 | util::git() 297 | .args(["log", "-1", "--format=%H"]) 298 | .arg(format!("upstream-pull/{num}/head")), 299 | ), 300 | slug: util::Slug { 301 | owner: slug.owner.clone(), 302 | repo: slug.repo.clone(), 303 | }, 304 | slug_num: format!("{sl}/{num}"), 305 | merge_commit: None, 306 | }) 307 | } 308 | } 309 | let base_name = base_names.first().expect("no repos given"); 310 | util::check_call( 311 | util::git() 312 | .args(["fetch", "--quiet", "origin"]) 313 | .arg(base_name), 314 | ); 315 | 316 | { 317 | let temp_git_work_tree_ctx = tempfile::TempDir::new_in(&temp_dir).expect("tempdir error"); 318 | let temp_git_work_tree = temp_git_work_tree_ctx.path(); 319 | 320 | util::check_call( 321 | std::process::Command::new("cp") 322 | .arg("-r") 323 | .arg(monotree_dir.join(".git")) 324 | .arg(temp_git_work_tree.join(".git")), 325 | ); 326 | 327 | util::chdir(temp_git_work_tree); 328 | println!("Calculate mergeable pulls"); 329 | 330 | let mono_pulls_mergeable = calc_mergeable(mono_pulls, base_name); 331 | if args.update_comments { 332 | for (i, pull_update) in mono_pulls_mergeable.iter().enumerate() { 333 | println!( 334 | "{i}/{len} Checking for conflicts {base_name} <> {pr_id} <> other_pulls ... ", 335 | len = mono_pulls_mergeable.len(), 336 | pr_id = pull_update.slug_num 337 | ); 338 | let pulls_conflict = calc_conflicts(&mono_pulls_mergeable, pull_update); 339 | update_comment(&config, &github, args.dry_run, pull_update, &pulls_conflict) 340 | .await?; 341 | } 342 | } 343 | if let Some(pull_id) = args.pull_id { 344 | let found = mono_pulls_mergeable.iter().find(|p| p.slug_num == pull_id); 345 | if found.is_none() { 346 | println!( 347 | "{id} not found in all {len} open, mergeable {base_name} pulls", 348 | id = pull_id, 349 | len = mono_pulls_mergeable.len() 350 | ); 351 | return Ok(()); 352 | } 353 | let pull_merge = found.unwrap(); 354 | println!( 355 | "Checking for conflicts {base_name} <> {id} <> other_pulls ... ", 356 | id = pull_merge.slug_num 357 | ); 358 | let conflicts = calc_conflicts(&mono_pulls_mergeable, pull_merge); 359 | update_comment(&config, &github, args.dry_run, pull_merge, &conflicts).await?; 360 | } 361 | } 362 | util::chdir(&temp_dir); 363 | 364 | Ok(()) 365 | } 366 | -------------------------------------------------------------------------------- /util/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone)] 2 | pub struct Slug { 3 | pub owner: String, 4 | pub repo: String, 5 | } 6 | 7 | impl Slug { 8 | pub fn str(&self) -> String { 9 | format!("{}/{}", self.owner, self.repo) 10 | } 11 | } 12 | 13 | impl std::str::FromStr for Slug { 14 | type Err = &'static str; 15 | fn from_str(s: &str) -> Result { 16 | // Format: a/b 17 | let err = "Wrong format, see --help."; 18 | let mut it_slug = s.split('/'); 19 | let res = Self { 20 | owner: it_slug.next().ok_or(err)?.to_string(), 21 | repo: it_slug.next().ok_or(err)?.to_string(), 22 | }; 23 | if it_slug.next().is_none() { 24 | return Ok(res); 25 | } 26 | Err(err) 27 | } 28 | } 29 | 30 | #[cfg(feature = "github")] 31 | pub fn get_octocrab(token: Option) -> octocrab::Result { 32 | let build = octocrab::Octocrab::builder(); 33 | match token { 34 | Some(tok) => build.personal_token(tok), 35 | None => build, 36 | } 37 | .build() 38 | } 39 | 40 | #[cfg(feature = "github")] 41 | pub enum IdComment { 42 | NeedsRebase, 43 | CiFailed, 44 | InactiveRebase, 45 | InactiveCi, 46 | InactiveStale, 47 | Metadata, // The "root" section 48 | SecCodeCoverage, 49 | SecConflicts, 50 | SecCoverage, 51 | SecReviews, 52 | SecLmCheck, 53 | } 54 | 55 | #[cfg(feature = "github")] 56 | impl IdComment { 57 | pub fn str(&self) -> &'static str { 58 | match self { 59 | Self::NeedsRebase => "", 60 | Self::CiFailed => "", 61 | Self::InactiveRebase => "", 62 | Self::InactiveCi => "", 63 | Self::InactiveStale => "", 64 | Self::Metadata => "", 65 | Self::SecCodeCoverage => "", 66 | Self::SecConflicts => "", 67 | Self::SecCoverage => "", 68 | Self::SecReviews => "", 69 | Self::SecLmCheck => "", 70 | } 71 | } 72 | } 73 | 74 | pub fn git() -> std::process::Command { 75 | std::process::Command::new("git") 76 | } 77 | 78 | pub fn check_call(cmd: &mut std::process::Command) { 79 | let status = cmd.status().expect("command error"); 80 | assert!(status.success()); 81 | } 82 | 83 | pub fn call(cmd: &mut std::process::Command) -> bool { 84 | let out = cmd.output().expect("command error"); 85 | out.status.success() 86 | } 87 | 88 | pub fn check_output(cmd: &mut std::process::Command) -> String { 89 | let out = cmd.output().expect("command error"); 90 | assert!(out.status.success()); 91 | String::from_utf8(out.stdout) 92 | .expect("invalid utf8") 93 | .trim() 94 | .to_string() 95 | } 96 | 97 | pub fn chdir(p: &std::path::Path) { 98 | std::env::set_current_dir(p).expect("chdir error") 99 | } 100 | 101 | /// Normalize a git diff for LLM consumption by dropping removed lines and rewriting hunk headers. 102 | pub fn prepare_raw_diff_for_llm(diff: &str) -> String { 103 | diff.lines() 104 | .filter(|line| !line.starts_with('-')) // Drop needless lines to avoid confusion and reduce token use 105 | .map(|line| { 106 | if line.starts_with('@') { 107 | "@@ (hunk header) @@" // Rewrite hunk header to avoid typos in hunk header truncated by git 108 | } else { 109 | line 110 | } 111 | }) 112 | .collect::>() 113 | .join("\n") 114 | } 115 | 116 | /// Shared prompt that tells the LLM it will be given a git diff before receiving instructions. 117 | pub const LLM_SHARED_PROMPT_DIFF: &str = r#" 118 | Examine the provided git diff. Wait for additional instructions regarding how to evaluate or process the diff. 119 | 120 | - Upon receiving further instructions, carefully read and interpret the git diff according to the provided evaluation criteria. 121 | - Use the format or structure requested in follow-up instructions. 122 | "#; 123 | 124 | #[derive(Clone, Copy)] 125 | pub struct LlmCheck { 126 | pub prompt: &'static str, 127 | pub magic_all_good: &'static str, 128 | pub topic: &'static str, 129 | } 130 | 131 | /// Prompt encouraging the LLM to highlight typos in git diff documentation. 132 | pub const LLM_PROMPT_TYPOS: &str = r#" 133 | Identify and provide feedback on typographic or grammatical errors in the provided git diff comments or documentation, focusing exclusively on errors impacting comprehension. 134 | 135 | - Only address errors that make the English text invalid or incomprehensible. 136 | - Ignore style preferences, such as the Oxford comma, missing or superfluous commas, awkward but harmless language, and missing or inconsistent punctuation. 137 | - Focus solely on lines added (starting with a + in the diff). 138 | - Address only code comments (for example C++ or Python comments) or documentation (for example markdown). 139 | - If no errors are found, state that no typos were found. 140 | 141 | # Output Format 142 | 143 | List each error with minimal context, followed by a very brief rationale: 144 | - typo -> replacement [explanation] 145 | 146 | If none are found, state: "No typos were found". 147 | "#; 148 | 149 | /// Prompt encouraging the LLM to highlight missing named args in git diff. 150 | pub const LLM_PROMPT_NAMED_ARGS: &str = r#" 151 | Check C++ and Python code in the provided git diff for function calls where integral literal values (e.g., 0, true) are used as arguments. 152 | 153 | - Focus solely on lines added (starting with a + in the diff). 154 | - In C++: Look for function calls with literals as positional arguments. Recommend replacing `func(x, 0)` with `func(x, /*named_arg=*/0)`. 155 | - In Python: Look for function calls with literals as positional arguments. Recommend replacing `func(x, 0)` with `func(x, named_arg=0)` if the argument is not already named or is already using keyword syntax. 156 | - Only suggest if there are benefits, such as: 157 | - Improved readability and documentation, when the meaning of the literal is not obvious from the context 158 | - Reduced risk of misordered or misunderstood arguments, when the order is not obvious from the context 159 | - Easier code reviews and future-proofing, especially when there are several literal values 160 | - Do not flag or suggest changes for arguments that are already named or where such a comment would be more confusing. 161 | - Do not flag the first two argument of a function call. Only flag the third and later arguments. 162 | - Do not flag string literals. 163 | - Do not flag cases where adding a name comment or keyword would be more confusing or noisy than helpful. 164 | - Limit findings and suggestions to literals (do not suggest for variables or expressions). 165 | - If no opportunities are found, say that no suggestions were found. 166 | 167 | # Output Format 168 | 169 | List each location with minimal context. Only list the location, and do not suggest an arg name for the keyword: 170 | 171 | - [function_call_signature] in [filename] 172 | 173 | If none are found, state: "No suggestions were found". 174 | "#; 175 | 176 | pub static LLM_TYPOS: LlmCheck = LlmCheck { 177 | prompt: LLM_PROMPT_TYPOS, 178 | magic_all_good: "No typos were found", 179 | topic: "Possible typos and grammar issues:", 180 | }; 181 | 182 | pub static LLM_NAMED_ARGS: LlmCheck = LlmCheck { 183 | prompt: LLM_PROMPT_NAMED_ARGS, 184 | magic_all_good: "No suggestions were found", 185 | topic: "Possible places where named args for integral literals may be used (e.g. `func(x, /*named_arg=*/0)` in C++, and `func(x, named_arg=0)` in Python):", 186 | }; 187 | 188 | /// Construct the OpenAI chat payload used by llm clients that request diff checks. 189 | pub fn make_llm_payload(diff: &str, typo_prompt: &str) -> serde_json::Value { 190 | serde_json::json!({ 191 | "model": "gpt-5-mini", 192 | "messages": [ 193 | { 194 | "role": "developer", 195 | "content": [ 196 | { 197 | "type": "text", 198 | "text": LLM_SHARED_PROMPT_DIFF 199 | } 200 | ] 201 | }, 202 | { 203 | "role": "user", 204 | "content": [ 205 | { 206 | "type": "text", 207 | "text": diff 208 | }, 209 | { 210 | "type": "text", 211 | "text": typo_prompt 212 | } 213 | ] 214 | } 215 | ], 216 | "response_format": { 217 | "type": "text" 218 | }, 219 | "reasoning_effort": "low", 220 | "service_tier": "default", 221 | "store": true 222 | }) 223 | } 224 | 225 | #[cfg(feature = "github")] 226 | pub struct MetaComment { 227 | pull_num: u64, 228 | pub id: Option, 229 | sections: Vec, 230 | } 231 | 232 | #[cfg(feature = "github")] 233 | impl MetaComment { 234 | pub fn has_section(&self, section_id: &IdComment) -> bool { 235 | let id = section_id.str(); 236 | self.sections.iter().any(|s| s.starts_with(id)) 237 | } 238 | 239 | fn join_metadata_comment(&mut self) -> String { 240 | self.sections.sort(); 241 | let desc = "The following sections might be updated with supplementary metadata relevant to reviewers and maintainers."; 242 | format!( 243 | "{root_id}\n\n{desc}\n\n{sec}", 244 | root_id = IdComment::Metadata.str(), 245 | sec = self.sections.join("") 246 | ) 247 | } 248 | 249 | fn update(&mut self, id: IdComment, new_text: &str) -> bool { 250 | // TODO. Maybe check if new_text contains " 228 | 229 | Output: 230 | SPAM. The submission contains only boilerplate contributor guidance and no actual description of the proposed change, rationale, tests, or implementation details. The title is generic and there is no substantive content to review, suggesting a placeholder/empty or irrelevant submission rather than a legitimate contribution. 231 | 232 | Example 3 233 | Input: 234 | title: AE917B4 COIN 235 | body: Please describe the feature you'd like to see added. AE917B4.COIN.LOGO.png (view on web) 236 | 237 | Output: 238 | SPAM. This issue references a cryptocurrency which is unrelated to Bitcoin. It appears to be a placeholder or generic template with no real context or content, lacking any meaningful description, problem statement, or feature request related to the Bitcoin project. 239 | 240 | **Objective Reminder:** Classify GitHub issues and pull requests as either SPAM or NORMAL. 241 | "# 242 | } 243 | ] 244 | }, 245 | { 246 | "role": "user", 247 | "content": [ 248 | { 249 | "type": "text", 250 | "text": question 251 | } 252 | ] 253 | } 254 | ], 255 | "response_format": { 256 | "type": "text" 257 | }, 258 | "verbosity": "low", 259 | "reasoning_effort": "low", 260 | "service_tier": "default", 261 | "store": true 262 | }); 263 | let response = client 264 | .post("https://api.openai.com/v1/chat/completions") 265 | .header("Content-Type", "application/json") 266 | .header("Authorization", format!("Bearer {}", llm_token)) 267 | .json(&payload) 268 | .send() 269 | .await? 270 | .json::() 271 | .await?; 272 | let text = response["choices"][0]["message"]["content"] 273 | .as_str() 274 | .ok_or(DrahtBotError::KeyNotFound)? 275 | .to_string(); 276 | Ok(text) 277 | } 278 | 279 | async fn spam_follow_up( 280 | issues_api: &octocrab::issues::IssueHandler<'_>, 281 | title: &str, 282 | issue_number: u64, 283 | dry_run: bool, 284 | ) -> Result<()> { 285 | if title.trim() == "." { 286 | println!( 287 | "{} detected as spam to close and lock with title={title}", 288 | issue_number 289 | ); 290 | if !dry_run { 291 | issues_api 292 | .update(issue_number) 293 | .title(".") 294 | .body(".") 295 | .state(octocrab::models::IssueState::Closed) 296 | // Avoid IssueStateReason, because it does not work on pull requests 297 | //.state_reason(octocrab::models::issues::IssueStateReason::NotPlanned) 298 | .labels(&[]) 299 | .send() 300 | .await?; 301 | issues_api 302 | .lock(issue_number, octocrab::params::LockReason::Spam) 303 | .await?; 304 | } 305 | } 306 | Ok(()) 307 | } 308 | 309 | async fn spam_pr_heuristic( 310 | github: &octocrab::Octocrab, 311 | issues_api: &octocrab::issues::IssueHandler<'_>, 312 | pulls_api: &octocrab::pulls::PullRequestHandler<'_>, 313 | pr_number: u64, 314 | dry_run: bool, 315 | ) -> Result<()> { 316 | let all_files = github 317 | .all_pages(pulls_api.list_files(pr_number).await?) 318 | .await?; 319 | if all_files 320 | .iter() 321 | .any(|f| f.filename.starts_with("doc/release-notes/release-notes-") && f.deletions > 0) 322 | { 323 | let text = "📁 Archived release notes are archived and should not be modified."; 324 | if !dry_run { 325 | issues_api.create_comment(pr_number, text).await?; 326 | } 327 | } 328 | if all_files.iter().any(|f| { 329 | let sw = |p| f.filename.starts_with(p); 330 | let ct = |p| f.filename.contains(p); 331 | sw("README.md") 332 | || sw("doc/release-notes/") // Must include trailing slash 333 | || sw("INSTALL.md") 334 | || ct("CONTRIBUTING") 335 | || ct("LICENSE") 336 | || ct(".devcontainer/devcontainer.json") 337 | || ct("SECURITY") 338 | || ct("FUNDING") 339 | }) 340 | // The next check will also detect a fully empty diff 341 | || all_files.iter().all(|f| f.status == DiffEntryStatus::Removed) 342 | || all_files.iter().all(|f| f.status == DiffEntryStatus::Added) 343 | || all_files 344 | .iter() 345 | .any(|f| f.filename.starts_with(".github") && f.status == DiffEntryStatus::Added) 346 | { 347 | let pull_request = pulls_api.get(pr_number).await?; 348 | if [FirstTimer, FirstTimeContributor, Mannequin, None] 349 | .contains(&pull_request.author_association.unwrap()) 350 | { 351 | let reason = r#" 352 | ♻️ Automatically closing for now based on heuristics. Please leave a comment, if this was erroneous. 353 | Generally, please focus on creating high-quality, original content that demonstrates a clear 354 | understanding of the project's requirements and goals. 355 | 356 | 📝 Moderators: If this is spam, please replace the title with `.`, so that the thread does not appear in 357 | search results. 358 | "#; 359 | if !dry_run { 360 | issues_api.create_comment(pr_number, reason).await?; 361 | issues_api 362 | .update(pr_number) 363 | .state(octocrab::models::IssueState::Closed) 364 | .send() 365 | .await?; 366 | } 367 | } 368 | } 369 | Ok(()) 370 | } 371 | --------------------------------------------------------------------------------