mirror of
https://github.com/Byron/gitoxide
synced 2025-10-06 01:52:40 +02:00
208 lines
9.2 KiB
Rust
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(())
|
|
}
|