summaryrefslogtreecommitdiff
path: root/server/common
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2025-06-12 09:11:18 +0200
committerJoel Klinghed <the_jk@spawned.biz>2025-06-19 00:19:37 +0200
commit2b54f5c51ff9a26d4077037631ed39d62ed2b3fb (patch)
tree8544278dba24645a063472a3005a3021879a4bf1 /server/common
parentbaa7c85ff3db2366d67ac875fca48ad6dcabf212 (diff)
Initial support for translation reviews
Diffstat (limited to 'server/common')
-rw-r--r--server/common/src/git.rs61
-rw-r--r--server/common/src/grit.rs98
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)