this repo has no description

make things run in parallel

+125 -51
+3 -4
.envrc
··· 1 - # make the gh cli work with jujutsu 2 - if [[ ! -d .git && -d .jj ]]; then 3 - export GIT_DIR=.jj/repo/store/git 4 - fi 1 + if command -v nix &> /dev/null; then 2 + use flake 3 + fi
+36 -1
flake.lock
··· 34 34 "type": "github" 35 35 } 36 36 }, 37 + "nixpkgs_2": { 38 + "locked": { 39 + "lastModified": 1744536153, 40 + "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", 41 + "owner": "NixOS", 42 + "repo": "nixpkgs", 43 + "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", 44 + "type": "github" 45 + }, 46 + "original": { 47 + "owner": "NixOS", 48 + "ref": "nixpkgs-unstable", 49 + "repo": "nixpkgs", 50 + "type": "github" 51 + } 52 + }, 37 53 "root": { 38 54 "inputs": { 39 55 "flake-utils": "flake-utils", 40 - "nixpkgs": "nixpkgs" 56 + "nixpkgs": "nixpkgs", 57 + "rust-overlay": "rust-overlay" 58 + } 59 + }, 60 + "rust-overlay": { 61 + "inputs": { 62 + "nixpkgs": "nixpkgs_2" 63 + }, 64 + "locked": { 65 + "lastModified": 1752374969, 66 + "narHash": "sha256-Ky3ynEkJXih7mvWyt9DWoiSiZGqPeHLU1tlBU4b0mcc=", 67 + "owner": "oxalica", 68 + "repo": "rust-overlay", 69 + "rev": "75fb000638e6d0f57cb1e8b7a4550cbdd8c76f1d", 70 + "type": "github" 71 + }, 72 + "original": { 73 + "owner": "oxalica", 74 + "repo": "rust-overlay", 75 + "type": "github" 41 76 } 42 77 }, 43 78 "systems": {
+42 -27
flake.nix
··· 1 1 { 2 - description = "A Rust project built with Nix"; 2 + description = "Rust development environment for crate-template"; 3 3 4 4 inputs = { 5 5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 + rust-overlay.url = "github:oxalica/rust-overlay"; 6 7 flake-utils.url = "github:numtide/flake-utils"; 7 8 }; 8 9 9 10 outputs = { 10 11 self, 11 12 nixpkgs, 13 + rust-overlay, 12 14 flake-utils, 13 15 }: 14 - flake-utils.lib.eachDefaultSystem ( 15 - system: let 16 - pkgs = import nixpkgs { 17 - inherit system; 18 - }; 19 - in { 20 - packages.default = pkgs.rustPlatform.buildRustPackage { 21 - pname = "jj-sync-prs"; 22 - version = "0.1.0"; 16 + flake-utils.lib.eachDefaultSystem (system: let 17 + overlays = [(import rust-overlay)]; 18 + pkgs = import nixpkgs { 19 + inherit system overlays; 20 + }; 21 + 22 + rustToolchain = pkgs.rust-bin.stable.latest.default.override { 23 + extensions = ["rust-src" "rust-analyzer"]; 24 + }; 25 + in { 26 + packages.default = pkgs.rustPlatform.buildRustPackage { 27 + pname = (pkgs.lib.importTOML ./Cargo.toml).package.name; 28 + version = (pkgs.lib.importTOML ./Cargo.toml).package.version; 29 + src = self; 30 + cargoLock.lockFile = ./Cargo.lock; 23 31 24 - src = ./.; 32 + nativeBuildInputs = [ 33 + pkgs.makeWrapper 34 + ]; 25 35 26 - cargoLock = { 27 - lockFile = ./Cargo.lock; 28 - }; 36 + buildInputs = [ 37 + pkgs.graphviz 38 + ]; 29 39 30 - nativeBuildInputs = [ 31 - pkgs.makeWrapper 32 - ]; 40 + postInstall = '' 41 + wrapProgram $out/bin/jj-sync-prs \ 42 + --prefix PATH : ${pkgs.lib.makeBinPath [pkgs.graphviz]} 43 + ''; 44 + }; 33 45 34 - buildInputs = [ 35 - pkgs.graphviz 36 - ]; 46 + devShells.default = pkgs.mkShell { 47 + buildInputs = with pkgs; [ 48 + rustToolchain 49 + cargo-edit 50 + clippy 51 + rustfmt 52 + ]; 37 53 38 - postInstall = '' 39 - wrapProgram $out/bin/jj-sync-prs \ 40 - --prefix PATH : ${pkgs.lib.makeBinPath [pkgs.graphviz]} 41 - ''; 42 - }; 43 - } 44 - ); 54 + shellHook = '' 55 + echo "Rust development environment loaded" 56 + echo "Rust version: $(rustc --version)" 57 + ''; 58 + }; 59 + }); 45 60 }
+44 -19
src/main.rs
··· 1 1 use std::fmt::Write; 2 + use std::future::ready; 2 3 use std::io::Write as _; 3 4 use std::path::PathBuf; 4 5 use std::pin::pin; ··· 10 11 use futures::TryStreamExt as _; 11 12 use octocrab::{Octocrab, models::pulls::PullRequest}; 12 13 use serde::Deserialize; 14 + use tokio::sync::mpsc; 13 15 14 16 use crate::graph::Graph; 15 17 ··· 39 41 }, 40 42 } 41 43 42 - #[tokio::main(flavor = "current_thread")] 44 + #[tokio::main] 43 45 async fn main() -> color_eyre::Result<()> { 44 46 color_eyre::install()?; 45 47 ··· 60 62 async fn run_subcommand(subcommand: Subcommand) -> color_eyre::Result<()> { 61 63 match subcommand { 62 64 Subcommand::Sync { github_token } => { 63 - let graph = build_branch_graph().context("failed to build graph")?; 65 + let graph = tokio::task::spawn_blocking(|| { 66 + build_branch_graph().context("failed to build graph") 67 + }); 64 68 65 69 let repo_info = repo_info().context("failed to find repo info")?; 66 70 67 71 let octocrab = octocrab::OctocrabBuilder::default() 68 - .personal_token(github_token) 72 + .personal_token(&*github_token) 69 73 .build() 70 74 .context("failed to build github client")?; 75 + 71 76 let mut pulls = octocrab 72 77 .pulls(&repo_info.owner, &repo_info.name) 73 78 .list() ··· 78 83 .try_collect::<Vec<_>>() 79 84 .await 80 85 .context("failed to fetch all pull requests")?; 86 + 87 + let graph = graph.await??; 81 88 82 89 for stack_root in graph.iter_edges_from("main") { 83 90 find_or_create_prs( ··· 86 93 .await 87 94 .context("failed to sync prs")?; 88 95 } 96 + 97 + let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(1024); 89 98 90 99 for stack_root in graph.iter_edges_from("main") { 91 100 let mut comment_lines = Vec::new(); ··· 101 110 &pulls, 102 111 &octocrab, 103 112 &repo_info, 113 + tx.clone(), 104 114 ) 105 - .await 106 115 .context("failed to sync stack comment")?; 107 116 } 108 117 } 118 + drop(tx); 119 + while rx.recv().await.is_some() {} 109 120 } 110 121 Subcommand::Graph { out } => { 111 122 let graph = build_branch_graph().context("failed to build graph")?; ··· 372 383 Ok(()) 373 384 } 374 385 375 - async fn create_or_update_comments( 386 + fn create_or_update_comments( 376 387 comment_lines: &[CommentLine], 377 388 branch: &str, 378 389 graph: &Graph, 379 390 pulls: &[PullRequest], 380 391 octocrab: &Octocrab, 381 392 repo_info: &RepoInfo, 393 + tx: mpsc::Sender<()>, 382 394 ) -> color_eyre::Result<()> { 383 - create_or_update_comment(comment_lines, branch, pulls, octocrab, repo_info).await?; 395 + tokio::spawn({ 396 + let octocrab = octocrab.clone(); 397 + let tx = tx.clone(); 398 + let comment_lines = comment_lines.to_vec(); 399 + let branch = branch.to_owned(); 400 + let pulls = pulls.to_vec(); 401 + let repo_info = repo_info.clone(); 402 + async move { 403 + if let Err(err) = 404 + create_or_update_comment(comment_lines, branch, pulls, octocrab, repo_info).await 405 + { 406 + eprintln!("{err:#}"); 407 + } 408 + drop(tx); 409 + } 410 + }); 384 411 385 412 for child in graph.iter_edges_from(branch) { 386 - Box::pin(create_or_update_comments( 413 + create_or_update_comments( 387 414 comment_lines, 388 415 child, 389 416 graph, 390 417 pulls, 391 418 octocrab, 392 419 repo_info, 393 - )) 394 - .await 420 + tx.clone(), 421 + ) 395 422 .context("failed to sync stack comment")?; 396 423 } 397 424 ··· 399 426 } 400 427 401 428 async fn create_or_update_comment( 402 - comment_lines: &[CommentLine], 403 - branch: &str, 404 - pulls: &[PullRequest], 405 - octocrab: &Octocrab, 406 - repo_info: &RepoInfo, 429 + comment_lines: Vec<CommentLine>, 430 + branch: String, 431 + pulls: Vec<PullRequest>, 432 + octocrab: Octocrab, 433 + repo_info: RepoInfo, 407 434 ) -> color_eyre::Result<()> { 408 435 let pull = pulls 409 436 .iter() ··· 411 438 .with_context(|| format!("PR from {branch} not found"))?; 412 439 413 440 let comment = 414 - finalize_comment(branch, comment_lines, pulls).context("failed to finalize comment")?; 441 + finalize_comment(&branch, &comment_lines, &pulls).context("failed to finalize comment")?; 415 442 416 443 let comment_stream = octocrab 417 444 .issues(&repo_info.owner, &repo_info.name) ··· 419 446 .send() 420 447 .await 421 448 .context("failed to fetch comments")? 422 - .into_stream(octocrab) 423 - .try_filter(|comment| { 424 - std::future::ready(comment.body.as_ref().is_some_and(|body| body.contains(ID))) 425 - }); 449 + .into_stream(&octocrab) 450 + .try_filter(|comment| ready(comment.body.as_ref().is_some_and(|body| body.contains(ID)))); 426 451 427 452 if let Some(existing_comment) = pin!(comment_stream).try_next().await? { 428 453 if existing_comment.body.is_none_or(|body| body != comment) {