Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions josh-sync.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,16 @@ path = "library/stdarch"
#[[post-pull]]
#cmd = ["cargo", "fmt"]
#commit-message = "reformat"

# Optionally, you can specify a subtree filter.
# This will be applied to the local `HEAD` during the round-trip check.
#
# By default, the round-trip check expects the local repo to be a perfect 1:1 mirror
# of the upstream tree. Leave this empty if that is your intent.
#
# If you are not doing a 1:1 mirror, set this to a filter that transforms your local
# `HEAD` into a tree that exactly matches the upstream tree after the `filter`
# is applied.
# E.g., if the `filter` is ":/compiler/rustc_public:prefix=rustc_public",
# the `subtree-filter` should be:
#subtree-filter = ":/rustc_public:prefix=rustc_public"
1 change: 1 addition & 0 deletions src/bin/rustc_josh_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ fn main() -> anyhow::Result<()> {
path: Some("<relative-subtree-path>".to_string()),
filter: None,
post_pull: vec![],
subtree_filter: None,
};
config
.write(Path::new(DEFAULT_CONFIG_PATH))
Expand Down
3 changes: 3 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pub struct JoshConfig {
/// Can be used to post-process the state of the repository after a pull happens.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub post_pull: Vec<PostPullOperation>,
/// Optional subtree filter applied to the local `HEAD` during round-trip check.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtree_filter: Option<String>,
}

/// Execute an operation after a pull, and if something changes in the local git state,
Expand Down
50 changes: 48 additions & 2 deletions src/josh.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::config::JoshConfig;
use crate::utils::run_command;
use crate::utils::{is_null_sha, run_command, run_command_by_path};
use anyhow::Context;
use std::net::{SocketAddr, TcpStream};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::Duration;

Expand All @@ -14,6 +14,10 @@ pub struct JoshProxy {
path: PathBuf,
}

pub struct JoshFilter {
path: PathBuf,
}

impl JoshProxy {
/// Tries to figure out if `josh-proxy` is installed.
pub fn lookup() -> Option<Self> {
Expand Down Expand Up @@ -82,6 +86,48 @@ pub fn try_install_josh(verbose: bool) -> Option<JoshProxy> {
JoshProxy::lookup()
}

impl JoshFilter {
/// Tries to figure out if `josh-filter` is installed.
pub fn lookup() -> Option<Self> {
which::which("josh-filter").ok().map(|path| Self { path })
}

pub fn run<'a, Args: AsRef<[&'a str]>>(
&self,
args: Args,
workdir: &Path,
verbose: bool,
) -> anyhow::Result<()> {
let args = args.as_ref();
let output = run_command_by_path(&self.path, args, workdir, true, verbose)?;
if is_null_sha(&output) {
return Err(anyhow::anyhow!(
"josh-filter returned null SHA, filter may not match any content"
));
}
Ok(())
}
}

pub fn try_install_josh_filter(verbose: bool) -> Option<JoshFilter> {
run_command(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please pin the version/tag/commit SHA? I don't want josh-sync to install unpinned versions of Josh, we had enough trouble with that in the past.

&[
"cargo",
"+stable",
"install",
"--locked",
"--git",
"https://github.com/josh-project/josh",
"--tag",
JOSH_VERSION,
"josh-filter",
],
Comment thread
Kobzol marked this conversation as resolved.
verbose,
)
.expect("cannot install josh-filter");
JoshFilter::lookup()
}

/// Create a wrapper that represents a running instance of `josh-proxy` and stops it on drop.
pub struct RunningJoshProxy {
process: std::process::Child,
Expand Down
67 changes: 48 additions & 19 deletions src/sync.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::SyncContext;
use crate::config::PostPullOperation;
use crate::josh::JoshProxy;
use crate::config::{JoshConfig, PostPullOperation};
use crate::josh::{JoshFilter, JoshProxy, try_install_josh_filter};
use crate::utils::{ensure_clean_git_state, prompt};
use crate::utils::{get_current_head_sha, run_command_at};
use crate::utils::{run_command, stream_command};
Expand Down Expand Up @@ -329,23 +329,7 @@ After you fix the conflicts, `git add` the changes and run `git merge --continue
println!();

// Do a round-trip check to make sure the push worked as expected.
run_command_at(
&["git", "fetch", &josh_url, &branch],
&std::env::current_dir().unwrap(),
self.verbose,
)?;
let head = get_current_head_sha(self.verbose)?;
let fetch_head = run_command(&["git", "rev-parse", "FETCH_HEAD"], self.verbose)?;
if head != fetch_head {
return Err(anyhow::anyhow!(
"Josh created a non-roundtrip push! Do NOT merge this into rustc!\n\
Expected {head}, got {fetch_head}."
));
}
println!(
"Confirmed that the push round-trips back to {} properly. Please create a rustc PR.",
self.context.config.repo
);
self.roundtrip_check(&self.context.config, &josh_url, &branch)?;

Ok(())
}
Expand All @@ -370,6 +354,51 @@ After you fix the conflicts, `git add` the changes and run `git merge --continue

Ok(())
}

fn roundtrip_check(
&self,
config: &JoshConfig,
josh_url: &str,
branch: &str,
) -> anyhow::Result<()> {
run_command_at(
&["git", "fetch", josh_url, branch],
&std::env::current_dir().unwrap(),
self.verbose,
)?;
let head = if let Some(subtree_filter) = &config.subtree_filter {
let josh_filter = get_josh_filter(self.verbose)?;
josh_filter.run(
&[subtree_filter, "HEAD"],
&std::env::current_dir().unwrap(),
self.verbose,
)?;
run_command(&["git", "rev-parse", "FILTERED_HEAD"], self.verbose)
.context("failed to get FILTERED_HEAD")?
} else {
get_current_head_sha(self.verbose)?
};
let fetch_head = run_command(&["git", "rev-parse", "FETCH_HEAD"], self.verbose)?;
if head != fetch_head {
return Err(anyhow::anyhow!(
"Josh created a non-roundtrip push! Do NOT merge this into rustc!\n\
Expected {head}, got {fetch_head}."
));
}
println!(
"Confirmed that the push round-trips back to {} properly. Please create a rustc PR.",
self.context.config.repo
);
Ok(())
}
}

fn get_josh_filter(verbose: bool) -> anyhow::Result<JoshFilter> {
println!("Updating/installing josh-filter binary...");
match try_install_josh_filter(verbose) {
Some(filter) => Ok(filter),
None => Err(anyhow::anyhow!("Could not install josh-filter")),
}
}

/// Find a rustc repo we can do our push preparation in.
Expand Down
27 changes: 26 additions & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Context;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::Command;

/// Run command and return its stdout.
Expand Down Expand Up @@ -36,6 +36,26 @@ fn run_command_inner<'a, Args: AsRef<[&'a str]>>(
cmd.current_dir(workdir);
cmd.args(&args[1..]);

execute_command(cmd, capture, verbose)
}

pub fn run_command_by_path<'a, Args: AsRef<[&'a str]>>(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was a new function needed here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's basically a copy-paste of run_command_inner but accepting a PathBuf as the command. But yeah I see that's kind of duplicate, I'm going to fix it.

cmd: &PathBuf,
args: Args,
workdir: &Path,
capture: bool,
verbose: bool,
) -> anyhow::Result<String> {
let args = args.as_ref();

let mut cmd = Command::new(cmd);
cmd.current_dir(workdir);
cmd.args(args);

execute_command(cmd, capture, verbose)
}

fn execute_command(mut cmd: Command, capture: bool, verbose: bool) -> anyhow::Result<String> {
if verbose {
eprintln!("+ {cmd:?}");
}
Expand Down Expand Up @@ -105,3 +125,8 @@ pub fn read_line() -> String {
.expect("cannot read line from stdin");
line.trim().to_string()
}

pub fn is_null_sha(s: &str) -> bool {
let s = s.trim();
!s.is_empty() && s.chars().all(|c| c == '0')
}
Loading