use lucky_commit_lib::{iterate_for_match, parse_prefix, HashMatch, HashPrefix, SearchParams};
use std::env;
use std::io;
use std::io::Write;
use std::process::{exit, Command, Stdio};
use std::sync::mpsc;
use std::thread;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() == 2 && args[1] == "--benchmark" {
run_single_core_benchmark();
return;
}
match args.len() {
1 => run_lucky_commit(&parse_prefix("0000000").unwrap()),
2 => match parse_prefix(&args[1]) {
Some(prefix) => run_lucky_commit(&prefix),
None => print_usage_and_exit(),
},
_ => print_usage_and_exit(),
}
}
fn print_usage_and_exit() -> ! {
fail_with_message("Usage: lucky_commit [commit-hash-prefix]")
}
fn fail_with_message(message: &str) -> ! {
eprintln!("{}", message);
exit(1)
}
fn run_lucky_commit(desired_prefix: &HashPrefix) {
let current_commit = run_command("git", &["cat-file", "commit", "HEAD"]);
match find_match(¤t_commit, desired_prefix) {
Some(hash_match) => {
create_git_commit(&hash_match)
.expect("Found a commit, but failed to write it to the git object database.");
run_command("git", &["reset", &to_hex_string(&hash_match.hash)]);
}
None => fail_with_message(
"Sorry, failed to find a commit matching the given prefix despite searching hundreds \
of trillions of possible commits. Hopefully you haven't just been sitting here \
waiting the whole time.",
),
}
}
fn run_command(command: &str, args: &[&str]) -> Vec<u8> {
let output = Command::new(command)
.args(args)
.stderr(Stdio::inherit())
.output()
.unwrap_or_else(|_| {
panic!(
"Failed to spawn command `{}` with args `{:?}`",
command, args
)
});
if !output.status.success() {
panic!(
"Command finished with non-zero exit code: {} {:?}",
command, args
);
}
output.stdout
}
fn find_match(current_commit: &[u8], desired_prefix: &HashPrefix) -> Option<HashMatch> {
let (shared_sender, receiver) = mpsc::channel();
let num_threads = num_cpus::get_physical() as u64;
let workload_per_thread = (1 << 48) / num_threads;
for thread_index in 0..num_threads {
let search_params = SearchParams {
current_commit: current_commit.to_vec(),
desired_prefix: desired_prefix.clone(),
counter_range: (thread_index * workload_per_thread)
..((thread_index + 1) * workload_per_thread),
};
let result_sender = shared_sender.clone();
thread::spawn(move || {
let _ = result_sender.send(iterate_for_match(&search_params));
});
}
for _ in 0..num_threads {
let result = receiver.recv().unwrap();
if result.is_some() {
return result;
}
}
None
}
fn create_git_commit(search_result: &HashMatch) -> io::Result<()> {
let mut git_hash_object_child = Command::new("git")
.args(&["hash-object", "-t", "commit", "-w", "--stdin"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()?;
git_hash_object_child
.stdin
.as_mut()
.unwrap()
.write_all(&search_result.commit)?;
let output = git_hash_object_child.wait_with_output()?;
if !output.status.success() {
panic!("Found a commit, but failed to write it to the git object database.");
}
let git_hash_output =
String::from_utf8(output.stdout).expect("Git produced a hash containing invalid utf8?");
assert!(
git_hash_output.trim_end() == to_hex_string(&search_result.hash),
"Found a commit ({}), but git unexpectedly computed a different hash for it ({})",
to_hex_string(&search_result.hash),
git_hash_output.trim_end(),
);
Ok(())
}
fn to_hex_string(hash: &[u8]) -> String {
hash.iter()
.map(|byte| format!("{:02x}", *byte))
.collect::<String>()
}
fn run_single_core_benchmark() {
assert_eq!(
None,
iterate_for_match(&SearchParams {
current_commit: b"\
tree 6f4e79123e206448f80ec73b9a53e07eb0784fef\n\
author Foo Bar <foo@example.com> 1611912738 -0500\n\
committer Foo Bar <foo@example.com> 1611912738 -0500\n\
\n\
Test commit for benchmarking performance changes\n"
.to_vec(),
desired_prefix: HashPrefix {
data: vec![0; 19],
half_byte: Some(0x0)
},
counter_range: 0..((1 << 28) / num_cpus::get_physical() as u64)
})
);
}