use std::io::{Cursor, Write};
use gix_hash::{Kind, ObjectId};
use gunnar_sendpack::driver;
use gunnar_sendpack::{advertisement, send_pack, PushCommand, SendPackOptions, Transport};
fn oid(c: char) -> ObjectId {
ObjectId::from_hex(c.to_string().repeat(40).as_bytes()).unwrap()
}
struct Recorder {
reader: Cursor<Vec<u8>>,
written: Vec<u8>,
}
impl Recorder {
fn new() -> Self {
Recorder {
reader: Cursor::new(Vec::new()),
written: Vec::new(),
}
}
}
impl Transport for Recorder {
fn io(&mut self) -> (&mut dyn std::io::Read, &mut dyn Write) {
(&mut self.reader, &mut self.written)
}
}
fn adv(caps: &str) -> gunnar_sendpack::Advertisement {
advertisement::parse(&[format!("{} refs/heads/main\0{caps}\n", oid('1')).into_bytes()]).unwrap()
}
const CAPS: &str = "report-status-v2 delete-refs side-band-64k atomic object-format=sha1";
fn pack_writer(w: &mut dyn Write) -> gunnar_sendpack::Result<()> {
w.write_all(b"PACK-not-really")?;
Ok(())
}
#[test]
fn an_all_deletes_push_carrying_a_pack_is_refused_before_a_byte_is_written() {
let mut t = Recorder::new();
let err = send_pack(
&mut t,
&adv(CAPS),
&[PushCommand::delete("refs/heads/main", oid('1'))],
Kind::Sha1,
Some(pack_writer),
&SendPackOptions::default(),
)
.unwrap_err();
assert!(format!("{err}").contains("must not carry a pack"), "{err}");
assert!(
t.written.is_empty(),
"a refused push must put NOTHING on the wire; it wrote {} bytes",
t.written.len()
);
}
#[test]
fn an_update_with_no_pack_is_refused_before_a_byte_is_written() {
let mut t = Recorder::new();
let err = send_pack(
&mut t,
&adv(CAPS),
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
Kind::Sha1,
None::<fn(&mut dyn Write) -> gunnar_sendpack::Result<()>>,
&SendPackOptions::default(),
)
.unwrap_err();
assert!(format!("{err}").contains("must carry a pack"), "{err}");
assert!(t.written.is_empty(), "a refused push writes nothing");
}
#[test]
fn an_unofferable_guarantee_is_refused_before_the_command_list_goes_out() {
let mut t = Recorder::new();
let err = send_pack(
&mut t,
&adv("report-status object-format=sha1"),
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
Kind::Sha1,
Some(pack_writer),
&SendPackOptions {
atomic: true,
..Default::default()
},
)
.unwrap_err();
assert!(format!("{err}").contains("atomic"), "{err}");
assert!(t.written.is_empty());
}
#[test]
fn the_packfile_is_written_unframed_after_the_command_lists_flush() {
let mut t = Recorder::new();
let _ = send_pack(
&mut t,
&adv("delete-refs object-format=sha1"),
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
Kind::Sha1,
Some(pack_writer),
&SendPackOptions::default(),
)
.expect("a remote offering no report-status is legal");
let expected_commands = driver::encode_command_list(
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
&["object-format=sha1".to_string()],
)
.unwrap();
assert!(
t.written.starts_with(&expected_commands),
"the command list must lead, framed and flush-terminated"
);
assert_eq!(
&t.written[expected_commands.len()..],
b"PACK-not-really",
"the pack follows the flush-pkt with NO length prefix in front of it"
);
}
#[test]
fn a_framed_section_is_byte_exact_and_the_newline_belongs_to_the_framer() {
let mut out = Vec::new();
driver::frame_section(
&[b"unpack ok".to_vec(), b"ok refs/heads/main".to_vec()],
&mut out,
)
.unwrap();
assert_eq!(
out,
b"000e"
.to_vec() .into_iter()
.chain(b"unpack ok\n".iter().copied())
.chain(b"0017".iter().copied()) .chain(b"ok refs/heads/main\n".iter().copied())
.chain(b"0000".iter().copied())
.collect::<Vec<u8>>()
);
}
#[test]
fn push_options_are_one_pkt_line_each_and_a_newline_in_one_is_refused() {
let out =
driver::encode_push_options(&["ci.skip".to_string(), "reviewer=r".to_string()]).unwrap();
assert_eq!(out, b"000cci.skip\n000freviewer=r\n0000".to_vec());
assert!(driver::encode_push_options(&["bad\nline".to_string()]).is_err());
}
#[test]
fn the_client_declares_ofs_delta_exactly_when_it_will_use_it() {
fn capabilities_on_the_wire(written: &[u8]) -> String {
let text = String::from_utf8_lossy(written);
let nul = text
.find('\0')
.expect("the first command line carries a NUL and a capability list");
let rest = &text[nul + 1..];
rest[..rest.find('\n').unwrap_or(rest.len())].to_owned()
}
fn push_to(caps: &str) -> String {
let mut t = Recorder::new();
let _ = send_pack(
&mut t,
&adv(caps),
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
Kind::Sha1,
Some(pack_writer),
&SendPackOptions::default(),
)
.expect("a remote offering no report-status is legal");
capabilities_on_the_wire(&t.written)
}
let offered = push_to("ofs-delta side-band-64k object-format=sha1");
assert!(
offered.split(' ').any(|c| c == "ofs-delta"),
"the client used `OBJ_OFS_DELTA` in the pack it writes and did not say `ofs-delta` on \
the wire. The remote is entitled to refuse an encoding it never offered to accept. \
The capability line was {offered:?}"
);
let withheld = push_to("side-band-64k object-format=sha1");
assert!(
!withheld.split(' ').any(|c| c == "ofs-delta"),
"the client claimed `ofs-delta` against a remote that never offered it: {withheld:?}"
);
assert!(
withheld.split(' ').any(|c| c == "side-band-64k")
&& withheld.contains("object-format=sha1"),
"withholding one capability emptied the whole line: {withheld:?}"
);
let difference: Vec<&str> = offered
.split(' ')
.filter(|c| !withheld.split(' ').any(|w| w == *c))
.collect();
assert_eq!(
difference,
vec!["ofs-delta"],
"the two pushes differ in more than the capability under test: {offered:?} against \
{withheld:?}"
);
assert!(gunnar_sendpack::options::ofs_delta_agreed(&adv(
"ofs-delta object-format=sha1"
)));
assert!(!gunnar_sendpack::options::ofs_delta_agreed(&adv(
"object-format=sha1"
)));
}