tangled
alpha
login
or
join now
davidpdrsn.tngl.sh
/
jj-sync-prs
1
fork
atom
this repo has no description
1
fork
atom
overview
issues
pulls
2
pipelines
make things run in parallel
davidpdrsn.tngl.sh
8 months ago
2e1ea5d7
702f95f0
+125
-51
4 changed files
expand all
collapse all
unified
split
.envrc
flake.lock
flake.nix
src
main.rs
+3
-4
.envrc
···
1
1
-
# make the gh cli work with jujutsu
2
2
-
if [[ ! -d .git && -d .jj ]]; then
3
3
-
export GIT_DIR=.jj/repo/store/git
4
4
-
fi
1
1
+
if command -v nix &> /dev/null; then
2
2
+
use flake
3
3
+
fi
+36
-1
flake.lock
···
34
34
"type": "github"
35
35
}
36
36
},
37
37
+
"nixpkgs_2": {
38
38
+
"locked": {
39
39
+
"lastModified": 1744536153,
40
40
+
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
41
41
+
"owner": "NixOS",
42
42
+
"repo": "nixpkgs",
43
43
+
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
44
44
+
"type": "github"
45
45
+
},
46
46
+
"original": {
47
47
+
"owner": "NixOS",
48
48
+
"ref": "nixpkgs-unstable",
49
49
+
"repo": "nixpkgs",
50
50
+
"type": "github"
51
51
+
}
52
52
+
},
37
53
"root": {
38
54
"inputs": {
39
55
"flake-utils": "flake-utils",
40
40
-
"nixpkgs": "nixpkgs"
56
56
+
"nixpkgs": "nixpkgs",
57
57
+
"rust-overlay": "rust-overlay"
58
58
+
}
59
59
+
},
60
60
+
"rust-overlay": {
61
61
+
"inputs": {
62
62
+
"nixpkgs": "nixpkgs_2"
63
63
+
},
64
64
+
"locked": {
65
65
+
"lastModified": 1752374969,
66
66
+
"narHash": "sha256-Ky3ynEkJXih7mvWyt9DWoiSiZGqPeHLU1tlBU4b0mcc=",
67
67
+
"owner": "oxalica",
68
68
+
"repo": "rust-overlay",
69
69
+
"rev": "75fb000638e6d0f57cb1e8b7a4550cbdd8c76f1d",
70
70
+
"type": "github"
71
71
+
},
72
72
+
"original": {
73
73
+
"owner": "oxalica",
74
74
+
"repo": "rust-overlay",
75
75
+
"type": "github"
41
76
}
42
77
},
43
78
"systems": {
+42
-27
flake.nix
···
1
1
{
2
2
-
description = "A Rust project built with Nix";
2
2
+
description = "Rust development environment for crate-template";
3
3
4
4
inputs = {
5
5
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6
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
13
+
rust-overlay,
12
14
flake-utils,
13
15
}:
14
14
-
flake-utils.lib.eachDefaultSystem (
15
15
-
system: let
16
16
-
pkgs = import nixpkgs {
17
17
-
inherit system;
18
18
-
};
19
19
-
in {
20
20
-
packages.default = pkgs.rustPlatform.buildRustPackage {
21
21
-
pname = "jj-sync-prs";
22
22
-
version = "0.1.0";
16
16
+
flake-utils.lib.eachDefaultSystem (system: let
17
17
+
overlays = [(import rust-overlay)];
18
18
+
pkgs = import nixpkgs {
19
19
+
inherit system overlays;
20
20
+
};
21
21
+
22
22
+
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
23
23
+
extensions = ["rust-src" "rust-analyzer"];
24
24
+
};
25
25
+
in {
26
26
+
packages.default = pkgs.rustPlatform.buildRustPackage {
27
27
+
pname = (pkgs.lib.importTOML ./Cargo.toml).package.name;
28
28
+
version = (pkgs.lib.importTOML ./Cargo.toml).package.version;
29
29
+
src = self;
30
30
+
cargoLock.lockFile = ./Cargo.lock;
23
31
24
24
-
src = ./.;
32
32
+
nativeBuildInputs = [
33
33
+
pkgs.makeWrapper
34
34
+
];
25
35
26
26
-
cargoLock = {
27
27
-
lockFile = ./Cargo.lock;
28
28
-
};
36
36
+
buildInputs = [
37
37
+
pkgs.graphviz
38
38
+
];
29
39
30
30
-
nativeBuildInputs = [
31
31
-
pkgs.makeWrapper
32
32
-
];
40
40
+
postInstall = ''
41
41
+
wrapProgram $out/bin/jj-sync-prs \
42
42
+
--prefix PATH : ${pkgs.lib.makeBinPath [pkgs.graphviz]}
43
43
+
'';
44
44
+
};
33
45
34
34
-
buildInputs = [
35
35
-
pkgs.graphviz
36
36
-
];
46
46
+
devShells.default = pkgs.mkShell {
47
47
+
buildInputs = with pkgs; [
48
48
+
rustToolchain
49
49
+
cargo-edit
50
50
+
clippy
51
51
+
rustfmt
52
52
+
];
37
53
38
38
-
postInstall = ''
39
39
-
wrapProgram $out/bin/jj-sync-prs \
40
40
-
--prefix PATH : ${pkgs.lib.makeBinPath [pkgs.graphviz]}
41
41
-
'';
42
42
-
};
43
43
-
}
44
44
-
);
54
54
+
shellHook = ''
55
55
+
echo "Rust development environment loaded"
56
56
+
echo "Rust version: $(rustc --version)"
57
57
+
'';
58
58
+
};
59
59
+
});
45
60
}
+44
-19
src/main.rs
···
1
1
use std::fmt::Write;
2
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
14
+
use tokio::sync::mpsc;
13
15
14
16
use crate::graph::Graph;
15
17
···
39
41
},
40
42
}
41
43
42
42
-
#[tokio::main(flavor = "current_thread")]
44
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
63
-
let graph = build_branch_graph().context("failed to build graph")?;
65
65
+
let graph = tokio::task::spawn_blocking(|| {
66
66
+
build_branch_graph().context("failed to build graph")
67
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
68
-
.personal_token(github_token)
72
72
+
.personal_token(&*github_token)
69
73
.build()
70
74
.context("failed to build github client")?;
75
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
86
+
87
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
96
+
97
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
113
+
tx.clone(),
104
114
)
105
105
-
.await
106
115
.context("failed to sync stack comment")?;
107
116
}
108
117
}
118
118
+
drop(tx);
119
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
375
-
async fn create_or_update_comments(
386
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
393
+
tx: mpsc::Sender<()>,
382
394
) -> color_eyre::Result<()> {
383
383
-
create_or_update_comment(comment_lines, branch, pulls, octocrab, repo_info).await?;
395
395
+
tokio::spawn({
396
396
+
let octocrab = octocrab.clone();
397
397
+
let tx = tx.clone();
398
398
+
let comment_lines = comment_lines.to_vec();
399
399
+
let branch = branch.to_owned();
400
400
+
let pulls = pulls.to_vec();
401
401
+
let repo_info = repo_info.clone();
402
402
+
async move {
403
403
+
if let Err(err) =
404
404
+
create_or_update_comment(comment_lines, branch, pulls, octocrab, repo_info).await
405
405
+
{
406
406
+
eprintln!("{err:#}");
407
407
+
}
408
408
+
drop(tx);
409
409
+
}
410
410
+
});
384
411
385
412
for child in graph.iter_edges_from(branch) {
386
386
-
Box::pin(create_or_update_comments(
413
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
393
-
))
394
394
-
.await
420
420
+
tx.clone(),
421
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
402
-
comment_lines: &[CommentLine],
403
403
-
branch: &str,
404
404
-
pulls: &[PullRequest],
405
405
-
octocrab: &Octocrab,
406
406
-
repo_info: &RepoInfo,
429
429
+
comment_lines: Vec<CommentLine>,
430
430
+
branch: String,
431
431
+
pulls: Vec<PullRequest>,
432
432
+
octocrab: Octocrab,
433
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
414
-
finalize_comment(branch, comment_lines, pulls).context("failed to finalize comment")?;
441
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
422
-
.into_stream(octocrab)
423
423
-
.try_filter(|comment| {
424
424
-
std::future::ready(comment.body.as_ref().is_some_and(|body| body.contains(ID)))
425
425
-
});
449
449
+
.into_stream(&octocrab)
450
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) {