use anyhow::{Context, Result, bail};
use base_d::prelude::*;
use std::process::Command;
const MAX_ENCODE_ATTEMPTS: usize = 5;
pub fn get_staged_diff() -> Result<String> {
let output = Command::new("git")
.args(["diff", "--staged"])
.output()
.context("Failed to run git diff")?;
if !output.status.success() {
bail!(
"git diff failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
pub fn has_staged_changes() -> Result<bool> {
let diff = get_staged_diff()?;
Ok(!diff.trim().is_empty())
}
pub fn stage_all() -> Result<()> {
let output = Command::new("git")
.args(["add", "-A"])
.output()
.context("Failed to run git add")?;
if !output.status.success() {
bail!(
"git add failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
Ok(())
}
fn encode_hash_with_registry(
text: &str,
registry: &DictionaryRegistry,
) -> Result<(String, String, String)> {
let result = hash_encode(text.as_bytes(), registry)
.map_err(|e| anyhow::anyhow!("Hash encode failed: {}", e))?;
Ok((
result.encoded,
result.hash_algo.as_str().to_string(),
result.dictionary_name,
))
}
fn encode_compress_with_registry(
text: &str,
registry: &DictionaryRegistry,
) -> Result<(String, String, String)> {
let result = compress_encode(text.as_bytes(), registry)
.map_err(|e| anyhow::anyhow!("Compress encode failed: {}", e))?;
Ok((
result.encoded,
result.compress_algo.as_str().to_string(),
result.dictionary_name,
))
}
pub fn decode_body(encoded: &str, footer: &str) -> Result<String> {
use base_d::{CompressionAlgorithm, DictionaryRegistry, decode, decompress};
let encoded = encoded.trim();
let compress_algo = parse_compress_algo(footer);
let body_dict_name = parse_body_dict(footer);
let dict = if let Some(ref dict_name) = body_dict_name {
let registry = DictionaryRegistry::load_default()
.map_err(|e| anyhow::anyhow!("Failed to load dictionary registry: {}", e))?;
registry
.dictionary(dict_name)
.map_err(|e| anyhow::anyhow!("Dictionary '{}' not found: {}", dict_name, e))?
} else {
let matches = base_d::detect_dictionary(encoded).map_err(|e| anyhow::anyhow!("{}", e))?;
if matches.is_empty() {
bail!("Could not detect dictionary for encoded text");
}
matches[0].dictionary.clone()
};
let decoded_bytes =
decode(encoded, &dict).map_err(|e| anyhow::anyhow!("Decode failed: {}", e))?;
let final_bytes = if let Some(algo) = compress_algo {
let compression_algo = match algo.to_lowercase().as_str() {
"lzma" => CompressionAlgorithm::Lzma,
"zstd" => CompressionAlgorithm::Zstd,
"brotli" => CompressionAlgorithm::Brotli,
"gzip" | "gz" => CompressionAlgorithm::Gzip,
"lz4" => CompressionAlgorithm::Lz4,
"snappy" => CompressionAlgorithm::Snappy,
_ => return String::from_utf8(decoded_bytes).context("Not valid UTF-8"),
};
decompress(&decoded_bytes, compression_algo)
.map_err(|e| anyhow::anyhow!("Decompression failed: {}", e))?
} else {
decoded_bytes
};
String::from_utf8(final_bytes).context("Decoded content is not valid UTF-8")
}
fn parse_compress_algo(footer: &str) -> Option<String> {
let footer = footer.trim();
if !footer.starts_with('[') || !footer.contains('|') {
return None;
}
let pipe_pos = footer.find('|')?;
let after_pipe = &footer[pipe_pos + 1..];
let colon_pos = after_pipe.find(':')?;
let algo = &after_pipe[..colon_pos];
Some(algo.to_string())
}
fn parse_body_dict(footer: &str) -> Option<String> {
let footer = footer.trim();
if !footer.starts_with('[') || !footer.contains('|') {
return None;
}
let pipe_pos = footer.find('|')?;
let after_pipe = &footer[pipe_pos + 1..];
let colon_pos = after_pipe.find(':')?;
let after_colon = &after_pipe[colon_pos + 1..];
let dict_name = after_colon.split(']').next()?;
if dict_name.is_empty() {
return None;
}
Some(dict_name.to_string())
}
pub fn git_commit(title: &str, body: &str, footer: &str) -> Result<()> {
let message = format!("{}\n\n{}\n\n{}", title, body, footer);
let output = Command::new("git")
.args(["commit", "-m", &message])
.output()
.context("Failed to run git commit")?;
if !output.status.success() {
bail!(
"git commit failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
Ok(())
}
fn git_pull_rebase() -> Result<()> {
let output = Command::new("git")
.args(["pull", "--rebase"])
.output()
.context("Failed to run git pull --rebase")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.contains("There is no tracking information")
&& !stderr.contains("no tracking information")
{
bail!("git pull --rebase failed: {}", stderr);
}
}
Ok(())
}
pub fn git_push() -> Result<()> {
git_pull_rebase()?;
let output = Command::new("git")
.arg("push")
.output()
.context("Failed to run git push")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("no upstream branch") {
let branch = get_current_branch()?;
let output = Command::new("git")
.args(["push", "-u", "origin", &branch])
.output()
.context("Failed to run git push -u")?;
if !output.status.success() {
bail!(
"git push failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
} else {
bail!("git push failed: {}", stderr);
}
}
Ok(())
}
fn get_current_branch() -> Result<String> {
let output = Command::new("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.output()
.context("Failed to get current branch")?;
if !output.status.success() {
bail!(
"Failed to get branch: {}",
String::from_utf8_lossy(&output.stderr)
);
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
pub struct EncodedCommit {
pub title: String,
pub body: String,
pub footer: String,
pub dejavu: bool,
pub title_dict: String,
pub body_dict: String,
}
impl EncodedCommit {
pub fn message(&self) -> String {
format!("{}\n\n{}\n\n{}", self.title, self.body, self.footer)
}
}
fn validate_encoded_output(encoded: &str, context: &str) -> Result<()> {
if let Some(pos) = encoded.find('\0') {
bail!("NUL byte at position {} in {}", pos, context,);
}
for (i, c) in encoded.char_indices() {
let cp = c as u32;
if (cp < 0x20 && cp != 0x0A && cp != 0x09) || (0x80..=0x9F).contains(&cp) {
bail!(
"control character U+{:04X} at position {} in {}",
cp,
i,
context,
);
}
}
Ok(())
}
fn format_footer_tag(
hash_algo: &str,
title_dict: &str,
compress_algo: &str,
body_dict: &str,
) -> String {
format!(
"[{}:{}|{}:{}]",
hash_algo, title_dict, compress_algo, body_dict
)
}
pub fn encode_commit(title_text: &str, body_text: &str) -> Result<EncodedCommit> {
let registry = DictionaryRegistry::load_default()
.map_err(|e| anyhow::anyhow!("Failed to load dictionaries: {}", e))?;
let mut failed_footers: Vec<String> = Vec::new();
for attempt in 1..=MAX_ENCODE_ATTEMPTS {
let (title, hash_algo, title_dict) = encode_hash_with_registry(title_text, ®istry)?;
let (body, compress_algo, body_dict) = encode_compress_with_registry(body_text, ®istry)?;
let dejavu = !title_dict.is_empty() && !body_dict.is_empty() && title_dict == body_dict;
let footer_tag = format_footer_tag(&hash_algo, &title_dict, &compress_algo, &body_dict);
let footer = format!("{}{}", footer_tag, if dejavu { "\nwhoa." } else { "" });
let title_check = validate_encoded_output(&title, "title");
let body_check = validate_encoded_output(&body, "body");
let footer_check = validate_encoded_output(&footer, "footer");
if let Err(e) = title_check.and(body_check).and(footer_check) {
if attempt < MAX_ENCODE_ATTEMPTS {
eprintln!("Tried {}: {}, retrying...", footer_tag, e);
} else {
eprintln!("Tried {}: {}", footer_tag, e);
}
failed_footers.push(footer_tag);
continue;
}
if attempt > 1 {
eprintln!("Tried {}: OK", footer_tag);
}
return Ok(EncodedCommit {
title,
body,
footer,
dejavu,
title_dict,
body_dict,
});
}
bail!(
"All {} encoding attempts produced unsafe output. Failed dictionaries: {}",
MAX_ENCODE_ATTEMPTS,
failed_footers.join(", ")
)
}
pub fn encode_commit_message(title_text: &str, body_text: &str) -> Result<String> {
Ok(encode_commit(title_text, body_text)?.message())
}
pub fn format_encoded_commit(encoded: &EncodedCommit, show_encoded: bool) -> String {
let mut out = String::new();
if show_encoded {
out.push_str(&format!("Title: {}\n", encoded.title));
out.push_str(&format!("Body: {}\n", encoded.body));
if encoded.dejavu {
out.push_str(&format!(
"Dejavu: true (both used {})\n",
encoded.title_dict
));
}
}
out.push_str(&format!("Footer: {}", encoded.footer));
out
}
pub fn upload_commit(
message: &str,
stage_all_flag: bool,
push: bool,
show_encoded: bool,
) -> Result<()> {
if stage_all_flag {
stage_all()?;
}
if !has_staged_changes()? {
bail!("No staged changes to commit");
}
let diff = get_staged_diff()?;
let encoded = encode_commit(&diff, message)?;
println!("{}", format_encoded_commit(&encoded, show_encoded));
git_commit(&encoded.title, &encoded.body, &encoded.footer)?;
println!("Committed.");
if push {
git_push()?;
println!("Pushed.");
}
Ok(())
}
fn get_pr_diff(number: u32) -> Result<String> {
let output = Command::new("gh")
.args(["pr", "diff", &number.to_string()])
.output()
.context("Failed to run gh pr diff")?;
if !output.status.success() {
bail!(
"gh pr diff failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
pub fn pr_merge(number: u32, rebase: bool, merge_commit: bool) -> Result<()> {
let diff = get_pr_diff(number)?;
let pr_info = Command::new("gh")
.args(["pr", "view", &number.to_string(), "--json", "title,body"])
.output()
.context("Failed to run gh pr view")?;
if !pr_info.status.success() {
bail!(
"gh pr view failed: {}",
String::from_utf8_lossy(&pr_info.stderr)
);
}
let json: serde_json::Value =
serde_json::from_slice(&pr_info.stdout).context("Failed to parse PR info")?;
let pr_title = json["title"].as_str().unwrap_or("PR");
let pr_body = json["body"].as_str().unwrap_or("");
let full_message = format!("{}\n\n{}", pr_title, pr_body);
let encoded = encode_commit(&diff, &full_message)?;
let method = if rebase {
"rebase"
} else if merge_commit {
"merge"
} else {
"squash"
};
let body_with_footer = format!("{}\n\n{}", encoded.body, encoded.footer);
let output = Command::new("gh")
.args([
"pr",
"merge",
&number.to_string(),
&format!("--{}", method),
"--subject",
&encoded.title,
"--body",
&body_with_footer,
])
.output()
.context("Failed to run gh pr merge")?;
if !output.status.success() {
bail!(
"gh pr merge failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
println!("Merged PR #{} ({})", number, method);
println!("{}", String::from_utf8_lossy(&output.stdout));
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_encoded_clean_ascii() {
assert!(validate_encoded_output("hello world", "test").is_ok());
}
#[test]
fn test_validate_encoded_nul_byte() {
assert!(validate_encoded_output("hello\0world", "test").is_err());
}
#[test]
fn test_validate_encoded_c0_control() {
assert!(validate_encoded_output("hello\x01world", "test").is_err());
}
#[test]
fn test_validate_encoded_c1_control() {
assert!(validate_encoded_output("hello\u{0085}world", "test").is_err());
}
#[test]
fn test_validate_encoded_newline_allowed() {
assert!(validate_encoded_output("hello\nworld", "test").is_ok());
}
#[test]
fn test_validate_encoded_tab_allowed() {
assert!(validate_encoded_output("hello\tworld", "test").is_ok());
}
#[test]
fn test_validate_encoded_empty() {
assert!(validate_encoded_output("", "test").is_ok());
}
#[test]
fn test_validate_encoded_multibyte_unicode() {
assert!(
validate_encoded_output(
"\u{1f711}\u{1f754}\u{1f72e}\u{1f716}\u{1f723}\u{1f75c}",
"test"
)
.is_ok()
);
}
fn sample_encoded_no_dejavu() -> EncodedCommit {
EncodedCommit {
title: "TTTT-title-glyphs".to_string(),
body: "BBBB-body-glyphs".to_string(),
footer: "[sha384:base62|lzma:uuencode]".to_string(),
dejavu: false,
title_dict: "base62".to_string(),
body_dict: "uuencode".to_string(),
}
}
fn sample_encoded_with_dejavu() -> EncodedCommit {
EncodedCommit {
title: "TTTT-title-glyphs".to_string(),
body: "BBBB-body-glyphs".to_string(),
footer: "[sha384:base62|lzma:base62]\nwhoa.".to_string(),
dejavu: true,
title_dict: "base62".to_string(),
body_dict: "base62".to_string(),
}
}
#[test]
fn test_format_default_omits_title_and_body() {
let encoded = sample_encoded_no_dejavu();
let out = format_encoded_commit(&encoded, false);
assert!(
!out.contains("Title:"),
"default output must not contain Title: -- got {:?}",
out
);
assert!(
!out.contains("Body:"),
"default output must not contain Body: -- got {:?}",
out
);
assert!(
!out.contains("Dejavu:"),
"default output must not contain Dejavu: -- got {:?}",
out
);
}
#[test]
fn test_format_default_contains_footer() {
let encoded = sample_encoded_no_dejavu();
let out = format_encoded_commit(&encoded, false);
assert!(
out.contains("Footer: [sha384:base62|lzma:uuencode]"),
"default output must contain the footer line -- got {:?}",
out
);
}
#[test]
fn test_format_default_dejavu_still_hidden() {
let encoded = sample_encoded_with_dejavu();
let out = format_encoded_commit(&encoded, false);
assert!(!out.contains("Dejavu:"));
assert!(!out.contains("Title:"));
assert!(!out.contains("Body:"));
assert!(out.contains("Footer:"));
}
#[test]
fn test_format_verbose_contains_all_fields() {
let encoded = sample_encoded_no_dejavu();
let out = format_encoded_commit(&encoded, true);
assert!(out.contains("Title: TTTT-title-glyphs"));
assert!(out.contains("Body: BBBB-body-glyphs"));
assert!(out.contains("Footer: [sha384:base62|lzma:uuencode]"));
assert!(!out.contains("Dejavu:"));
}
#[test]
fn test_format_verbose_shows_dejavu_when_true() {
let encoded = sample_encoded_with_dejavu();
let out = format_encoded_commit(&encoded, true);
assert!(out.contains("Title: TTTT-title-glyphs"));
assert!(out.contains("Body: BBBB-body-glyphs"));
assert!(out.contains("Dejavu: true (both used base62)"));
assert!(out.contains("Footer: [sha384:base62|lzma:base62]"));
}
#[test]
fn test_format_verbose_exact_bytes_match_historical_output() {
let encoded = sample_encoded_with_dejavu();
let out = format_encoded_commit(&encoded, true);
let expected = "Title: TTTT-title-glyphs\n\
Body: BBBB-body-glyphs\n\
Dejavu: true (both used base62)\n\
Footer: [sha384:base62|lzma:base62]\nwhoa.";
assert_eq!(out, expected);
}
#[test]
fn test_format_no_trailing_newline() {
let encoded = sample_encoded_no_dejavu();
let out = format_encoded_commit(&encoded, false);
assert!(!out.ends_with('\n'));
let out_v = format_encoded_commit(&encoded, true);
assert!(!out_v.ends_with('\n'));
}
#[test]
fn test_parse_body_dict_standard_footer() {
assert_eq!(
parse_body_dict("[sha384:base62|lzma:uuencode]"),
Some("uuencode".to_string())
);
}
#[test]
fn test_parse_body_dict_base58_variant() {
assert_eq!(
parse_body_dict("[sha256:base64|gzip:base58ripple]"),
Some("base58ripple".to_string())
);
}
#[test]
fn test_parse_body_dict_dejavu_footer() {
assert_eq!(
parse_body_dict("[sha384:base62|lzma:base62]\nwhoa."),
Some("base62".to_string())
);
}
#[test]
fn test_parse_body_dict_no_footer() {
assert_eq!(parse_body_dict("not a footer"), None);
}
#[test]
fn test_parse_body_dict_empty() {
assert_eq!(parse_body_dict(""), None);
}
#[test]
fn test_parse_body_dict_no_pipe() {
assert_eq!(parse_body_dict("[sha384:base62]"), None);
}
#[test]
fn test_parse_body_dict_no_colon_after_pipe() {
assert_eq!(parse_body_dict("[sha384:base62|lzma]"), None);
}
#[test]
fn test_parse_compress_algo_standard() {
assert_eq!(
parse_compress_algo("[sha384:base62|lzma:uuencode]"),
Some("lzma".to_string())
);
}
#[test]
fn test_parse_compress_algo_none() {
assert_eq!(parse_compress_algo("not a footer"), None);
}
}