1
1
mirror of https://github.com/Byron/gitoxide synced 2025-10-06 01:52:40 +02:00
Files
gitoxide/gix-filter/examples/arrow.rs
Sebastian Thiel 17835bccb0 chore: bump rust-version to 1.70
That way clippy will allow to use the fantastic `Option::is_some_and()`
and friends.
2025-01-12 13:51:47 +01:00

208 lines
9.2 KiB
Rust

use std::{
io::{stdin, stdout, Read, Write},
time::Duration,
};
use bstr::{ByteSlice, ByteVec};
use gix_filter::driver::process;
static PREFIX: &str = "";
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args();
let sub_command = args.nth(1).ok_or("Need sub-command")?;
let next_arg = args.next(); // possibly %f
let needs_failure = next_arg.as_deref().is_some_and(|file| file.ends_with("fail"));
if needs_failure {
panic!("failure requested for {sub_command}");
}
match sub_command.as_str() {
"process" => {
let disallow_delay = next_arg.as_deref() == Some("disallow-delay");
let mut srv = gix_filter::driver::process::Server::handshake(
stdin(),
stdout(),
"git-filter",
&mut |versions| versions.contains(&2).then_some(2),
if disallow_delay {
&["clean", "smudge"]
} else {
&["clean", "smudge", "delay"]
},
)?;
let mut next_smudge_aborts = false;
let mut next_smudge_fails_permanently = false; // a test validates that we don't actually hang
let mut delayed = Vec::new();
while let Some(mut request) = srv.next_request()? {
let needs_failure = request
.meta
.iter()
.find_map(|(key, value)| (key == "pathname").then_some(value))
.is_some_and(|path| path.ends_with(b"fail"));
let pathname = request
.meta
.iter()
.find_map(|(key, value)| (key == "pathname").then(|| value.clone()));
if needs_failure {
panic!("process failure requested: {:?}", request.meta);
}
let can_delay = request
.meta
.iter()
.any(|(key, value)| key == "can-delay" && value == "1");
match request.command.as_str() {
"clean" => {
let mut buf = Vec::new();
request.as_read().read_to_end(&mut buf)?;
request.write_status(if can_delay {
process::Status::delayed()
} else {
process::Status::success()
})?;
let lines = if let Some(delayed_lines) = buf
.is_empty()
.then(|| {
delayed
.iter()
.position(|(cmd, path, _)| {
*cmd == request.command.as_str() && Some(path) == pathname.as_ref()
})
.map(|pos| delayed.remove(pos).2)
})
.flatten()
{
delayed_lines
} else {
let mut lines = Vec::new();
for mut line in buf.lines_with_terminator() {
if line.starts_with(PREFIX.as_bytes()) {
line = &line[PREFIX.len()..];
}
lines.push_str(line);
}
lines
};
if can_delay {
delayed.push(("clean", pathname.expect("needed for delayed operation"), lines));
} else {
request.as_write().write_all(&lines)?;
request.write_status(process::Status::Previous)?;
}
}
"smudge" => {
let mut buf = Vec::new();
request.as_read().read_to_end(&mut buf)?;
let status = if next_smudge_aborts {
next_smudge_aborts = false;
process::Status::abort()
} else if next_smudge_fails_permanently {
process::Status::exit()
} else if can_delay {
process::Status::delayed()
} else {
process::Status::success()
};
request.write_status(status)?;
let lines = if let Some(delayed_lines) = buf
.is_empty()
.then(|| {
delayed
.iter()
.position(|(cmd, path, _)| {
*cmd == request.command.as_str() && Some(path) == pathname.as_ref()
})
.map(|pos| delayed.remove(pos).2)
})
.flatten()
{
delayed_lines
} else {
let mut lines = Vec::new();
for line in buf.lines_with_terminator() {
if !line.starts_with(PREFIX.as_bytes()) {
lines.push_str(PREFIX.as_bytes());
}
lines.push_str(line);
}
lines
};
if can_delay {
delayed.push(("smudge", pathname.expect("needed for delayed operation"), lines));
} else {
request.as_write().write_all(&lines)?;
request.write_status(process::Status::Previous)?;
}
}
"list_available_blobs" => {
{
let mut out = request.as_write();
let mut last_cmd = None;
let mut buf = Vec::<u8>::new();
for (cmd, path, _) in &delayed {
if last_cmd.get_or_insert(*cmd) != cmd {
panic!("the API doesn't support mixing cmds as paths might not be unique anymore")
}
buf.clear();
buf.push_str("pathname=");
buf.extend_from_slice(path);
out.write_all(&buf)?;
}
}
request.write_status(process::Status::success())?;
}
"wait-1-s" => {
std::io::copy(&mut request.as_read(), &mut std::io::sink())?;
request.write_status(process::Status::success())?;
std::thread::sleep(Duration::from_secs(1));
}
"next-smudge-aborts" => {
std::io::copy(&mut request.as_read(), &mut std::io::sink())?;
request.write_status(process::Status::success())?;
next_smudge_aborts = true;
}
"next-invocation-returns-strange-status-and-smudge-fails-permanently" => {
std::io::copy(&mut request.as_read(), &mut std::io::sink())?;
request.write_status(process::Status::success())?;
next_smudge_fails_permanently = true;
}
unknown => panic!("Unknown capability requested: {unknown}"),
}
}
}
// simple filters actually don't support streaming - they have to first read all input, then produce all output,
// but can't mix reading stdin and write to stdout at the same time as `git` (or `gitoxide`) don't read the output while
// writing the input.
"clean" => {
let mut stdin = stdin().lock();
let mut stdout = stdout().lock();
let mut buf = Vec::new();
std::io::copy(&mut stdin, &mut buf)?;
for mut line in buf.lines_with_terminator() {
if line.starts_with(PREFIX.as_bytes()) {
line = &line[PREFIX.len()..];
}
stdout.write_all(line).map(|_| true)?;
}
}
"smudge" => {
let mut stdin = stdin().lock();
let mut stdout = stdout().lock();
let mut buf = Vec::new();
std::io::copy(&mut stdin, &mut buf)?;
for line in buf.lines_with_terminator() {
if !line.starts_with(PREFIX.as_bytes()) {
stdout.write_all(PREFIX.as_bytes())?;
}
stdout.write_all(line).map(|_| true)?;
}
}
unknown => panic!("Unknown sub-command: {unknown}"),
}
Ok(())
}