diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2025-06-12 09:11:18 +0200 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2025-06-19 00:19:37 +0200 |
| commit | 2b54f5c51ff9a26d4077037631ed39d62ed2b3fb (patch) | |
| tree | 8544278dba24645a063472a3005a3021879a4bf1 /server/common | |
| parent | baa7c85ff3db2366d67ac875fca48ad6dcabf212 (diff) | |
Initial support for translation reviews
Diffstat (limited to 'server/common')
| -rw-r--r-- | server/common/src/git.rs | 61 | ||||
| -rw-r--r-- | server/common/src/grit.rs | 98 |
2 files changed, 136 insertions, 23 deletions
diff --git a/server/common/src/git.rs b/server/common/src/git.rs index 8fe7863..e396d8a 100644 --- a/server/common/src/git.rs +++ b/server/common/src/git.rs @@ -4,6 +4,7 @@ use futures::future::TryFutureExt; use pathdiff::diff_paths; use std::collections::HashMap; use std::fmt; +use std::io::{self, Cursor, Read}; use std::path::{Path, PathBuf}; use std::process::Stdio; use tokio::fs; @@ -78,6 +79,24 @@ pub struct TreeEntry { pub path: String, } +pub struct GitFile { + cursor: Cursor<Vec<u8>>, +} + +impl GitFile { + pub fn new(data: Vec<u8>) -> Self { + GitFile { + cursor: Cursor::new(data), + } + } +} + +impl Read for GitFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + self.cursor.read(buf) + } +} + fn io_err(action: &str, e: std::io::Error) -> Error { Error::new(format!("{action}: {e}")) } @@ -161,6 +180,8 @@ impl RepoData { cmd.arg("--porcelain"); // This option disables this automatic tag following. cmd.arg("--no-tags"); + // Write out refs even if they didn't change + cmd.arg("--verbose"); cmd.arg("origin"); // <+ force update><remote branch>:<local branch> cmd.arg(format!("+{branch}:{branch}")); @@ -430,6 +451,23 @@ impl RepoData { self.output(&mut cmd).map_ok(parse_tree_entries).await } + async fn cat_file( + &self, + repo: &Repository, + object_type: ObjectType, + object_name: &str, + ) -> Result<GitFile, Error> { + let mut cmd = self.git_cmd(repo); + cmd.arg("cat-file") + .arg(match object_type { + ObjectType::BLOB => "blob", + ObjectType::COMMIT => "commit", + ObjectType::TREE => "tree", + }) + .arg(object_name); + self.raw_output(&mut cmd).map_ok(GitFile::new).await + } + async fn get_log_format( &self, repo: &Repository, @@ -516,6 +554,14 @@ impl RepoData { } async fn output(&self, cmd: &mut Command) -> Result<String, Error> { + match self.raw_output(cmd).await { + Ok(bytes) => String::from_utf8(bytes) + .map_err(|e| Error::new(format!("git command had invalid output: {e}"))), + Err(e) => Err(e), + } + } + + async fn raw_output(&self, cmd: &mut Command) -> Result<Vec<u8>, Error> { cmd.stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -530,9 +576,7 @@ impl RepoData { .await?; if output.status.success() { - let output_utf8 = String::from_utf8(output.stdout) - .map_err(|e| Error::new(format!("git command had invalid output: {e}")))?; - Ok(output_utf8) + Ok(output.stdout) } else { Err(Error::new(format!( "git command failed with exitcode: {}\n{:?}\n{}", @@ -693,4 +737,15 @@ impl Repository { data.ls_tree(self, commit.as_str(), recursive).await } + + pub async fn cat_file( + &self, + object_type: ObjectType, + object_name: impl Into<String>, + ) -> Result<GitFile, Error> { + let object_name = object_name.into(); + let data = self.lock.read().await; + + data.cat_file(self, object_type, object_name.as_str()).await + } } diff --git a/server/common/src/grit.rs b/server/common/src/grit.rs index ee96500..9d01dac 100644 --- a/server/common/src/grit.rs +++ b/server/common/src/grit.rs @@ -4,7 +4,7 @@ use anyhow::Error; use std::collections::VecDeque; use std::fs; use std::io::{BufReader, Read}; -use std::path::Path; +use std::path::{Path, PathBuf}; use tokio::task::spawn_blocking; use xml::attribute::OwnedAttribute; use xml::reader::{EventReader, ParserConfig, XmlEvent}; @@ -1018,9 +1018,16 @@ fn parse_grit_part_element<R: Read>( pub async fn parse_grit(path: impl AsRef<Path>) -> anyhow::Result<Grit> { let path = path.as_ref().to_path_buf(); + parse_grit_with_opener(move || Ok(BufReader::new(fs::File::open(path)?))).await +} + +pub async fn parse_grit_with_opener<F, R>(opener: F) -> anyhow::Result<Grit> +where + F: FnOnce() -> anyhow::Result<BufReader<R>> + Send + 'static, + R: Read, +{ spawn_blocking(move || { - let file = fs::File::open(path)?; - let reader = BufReader::new(file); + let reader = opener()?; let mut ereader = ParserConfig::new() .ignore_comments(true) .whitespace_to_characters(true) @@ -1064,9 +1071,16 @@ pub async fn parse_grit(path: impl AsRef<Path>) -> anyhow::Result<Grit> { pub async fn parse_grit_part(path: impl AsRef<Path>) -> anyhow::Result<GritPart> { let path = path.as_ref().to_path_buf(); + parse_grit_part_with_opener(|| Ok(BufReader::new(fs::File::open(path)?))).await +} + +pub async fn parse_grit_part_with_opener<F, R>(opener: F) -> anyhow::Result<GritPart> +where + F: FnOnce() -> anyhow::Result<BufReader<R>> + Send + 'static, + R: Read, +{ spawn_blocking(move || { - let file = fs::File::open(path)?; - let reader = BufReader::new(file); + let reader = opener()?; let mut ereader = ParserConfig::new() .ignore_comments(true) .whitespace_to_characters(true) @@ -1121,20 +1135,20 @@ fn if_message_to_if_message_part(messages: Vec<IfMessage>) -> Vec<IfMessagePart> .collect() } -async fn maybe_expand_message(message: &mut IfMessagePart, basepath: &Path) -> anyhow::Result<()> { +async fn maybe_expand_message<F, R>(message: &mut IfMessagePart, opener: &F) -> anyhow::Result<()> +where + F: Fn(&str) -> anyhow::Result<BufReader<R>> + Clone + Send + 'static, + R: Read, +{ match message { IfMessagePart::Message(_) => Ok(()), IfMessagePart::Part { file, ref mut messages, } => { - let file_path = Path::new(file.as_str()); - let part_path = if let Some(parent) = basepath.parent() { - parent.join(file_path) - } else { - file_path.to_path_buf() - }; - let grit_part = parse_grit_part(part_path).await?; + let file = file.to_string(); + let opener = opener.clone(); + let grit_part = parse_grit_part_with_opener(move || opener(file.as_str())).await?; *messages = if_message_to_if_message_part(grit_part.messages); Ok(()) } @@ -1142,23 +1156,60 @@ async fn maybe_expand_message(message: &mut IfMessagePart, basepath: &Path) -> a expr: _, ref mut message, } => { - Box::pin(expand_messages(message, basepath)).await?; + Box::pin(expand_messages(message, opener)).await?; Ok(()) } } } -async fn expand_messages(messages: &mut Vec<IfMessagePart>, basepath: &Path) -> anyhow::Result<()> { +async fn expand_messages<F, R>(messages: &mut Vec<IfMessagePart>, opener: &F) -> anyhow::Result<()> +where + F: Fn(&str) -> anyhow::Result<BufReader<R>> + Clone + Send + 'static, + R: Read, +{ for message in messages { - maybe_expand_message(message, basepath).await?; + maybe_expand_message(message, opener).await?; } Ok(()) } pub async fn parse_grit_with_parts(path: impl AsRef<Path>) -> anyhow::Result<Grit> { let path = path.as_ref(); - let mut grit = parse_grit(path).await?; - expand_messages(&mut grit.release.messages.messages, path).await?; + if let Some(basepath) = path.parent() { + let basepath = basepath.to_path_buf(); + parse_grit_with_parts_and_resolver(path, move |x| basepath.join(x)).await + } else { + parse_grit_with_parts_and_resolver(path, |x| PathBuf::from(x)).await + } +} + +pub async fn parse_grit_with_parts_and_resolver<F>( + path: impl AsRef<Path>, + path_resolver: F, +) -> anyhow::Result<Grit> +where + F: Fn(&str) -> PathBuf + Send + Clone + 'static, +{ + let path = path.as_ref().to_path_buf(); + let grit_opener = || Ok(BufReader::new(fs::File::open(path)?)); + let part_opener = move |x: &str| { + let part_path = path_resolver(x); + Ok(BufReader::new(fs::File::open(part_path)?)) + }; + parse_grit_with_parts_and_opener(grit_opener, part_opener).await +} + +pub async fn parse_grit_with_parts_and_opener<F, G, R>( + grit_opener: F, + part_opener: G, +) -> anyhow::Result<Grit> +where + F: FnOnce() -> anyhow::Result<BufReader<R>> + Send + 'static, + G: Fn(&str) -> anyhow::Result<BufReader<R>> + Clone + Send + 'static, + R: Read, +{ + let mut grit = parse_grit_with_opener(grit_opener).await?; + expand_messages(&mut grit.release.messages.messages, &part_opener).await?; Ok(grit) } @@ -1513,9 +1564,16 @@ fn parse_xliff_element<R: Read>( pub async fn parse_xlf(path: impl AsRef<Path>) -> anyhow::Result<TranslationFile> { let path = path.as_ref().to_path_buf(); + parse_xlf_with_opener(|| Ok(BufReader::new(fs::File::open(path)?))).await +} + +pub async fn parse_xlf_with_opener<F, R>(opener: F) -> anyhow::Result<TranslationFile> +where + F: FnOnce() -> anyhow::Result<BufReader<R>> + Send + 'static, + R: Read, +{ spawn_blocking(move || { - let file = fs::File::open(path)?; - let reader = BufReader::new(file); + let reader = opener()?; let mut ereader = ParserConfig::new() .ignore_comments(true) .whitespace_to_characters(true) |
