hnz/src/back.rs
use std::{path::PathBuf, sync::Arc};
use jj_lib::{
backend::{TimestampOutOfRange, TreeValue},
repo::{Repo, RepoLoaderError, StoreLoadError},
revset::{RevsetEvaluationError, RevsetExpression, RevsetIteratorExt},
};
use serde::{Deserialize, Serialize};
use tokio::io::AsyncReadExt;
use crate::Help;
#[derive(Debug)]
#[allow(dead_code)]
pub enum GenBackError {
RepoPathNotValid,
StoreLoad(StoreLoadError),
RepoLoader(RepoLoaderError),
RevsetEvaluation(RevsetEvaluationError),
EmptyHead,
TimestampOutOfRange(TimestampOutOfRange),
IO(std::io::Error),
Json(serde_json::Error),
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TreeNode {
pub name: String,
pub path: String,
#[serde(rename = "type")]
pub node_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
pub children: Option<Vec<TreeNode>>,
}
fn insert_path(
root_children: &mut Vec<TreeNode>,
parts: &[&str],
full_path: &str,
content: Vec<u8>,
) {
if parts.is_empty() {
return;
}
if parts.len() == 1 {
root_children.push(TreeNode {
name: parts[0].to_string(),
path: full_path.to_string(),
node_type: "file".to_string(),
content: String::from_utf8(content).ok(),
children: None,
});
return;
}
let dir_name = parts[0];
let dir_path = full_path
.split('/')
.take(full_path.split('/').count() - (parts.len() - 1))
.collect::<Vec<_>>()
.join("/");
if let Some(existing) = root_children
.iter_mut()
.find(|n| n.name == dir_name && n.node_type == "dir")
{
insert_path(
existing.children.as_mut().unwrap(),
&parts[1..],
full_path,
content,
);
} else {
let mut new_dir = TreeNode {
name: dir_name.to_string(),
path: dir_path,
node_type: "dir".to_string(),
content: None,
children: Some(vec![]),
};
insert_path(
new_dir.children.as_mut().unwrap(),
&parts[1..],
full_path,
content,
);
root_children.push(new_dir);
}
}
pub fn gen_back(help: Help) -> Result<(), GenBackError> {
let repo_path = help.get_one::<PathBuf>("REPO_PATH").expect("required");
let output_path = help.get_one::<PathBuf>("OUTPUT").expect("required");
let repo_name = repo_path
.components()
.last()
.ok_or(GenBackError::RepoPathNotValid)?
.as_os_str();
let repo = help
.load(repo_path.join(".jj").join("repo"))
.map_err(GenBackError::StoreLoad)?
.map_err(GenBackError::RepoLoader)?;
let expression = Arc::new(RevsetExpression::All);
let head = expression
.evaluate(repo.as_ref())
.map_err(GenBackError::RevsetEvaluation)?
.iter()
.commits(repo.store())
.next()
.ok_or(GenBackError::EmptyHead)?
.map_err(GenBackError::RevsetEvaluation)?;
let mut root = TreeNode {
name: String::new(),
path: String::new(),
node_type: "dir".to_string(),
content: None,
children: Some(vec![]),
};
for (path, value) in head.tree().entries() {
let path_str = path
.to_fs_path_unchecked(std::path::Path::new(""))
.to_string_lossy()
.trim_start_matches("./")
.to_string();
if path_str.ends_with(".ttf") || path_str.ends_with(".woff2") || path_str.ends_with(".lock")
{
continue;
}
for value in value.unwrap() {
match value {
Some(TreeValue::File { id, .. }) => {
let mut content =
pollster::block_on(repo.store().read_file(path.as_ref(), &id)).unwrap();
let mut c = Vec::new();
pollster::block_on(content.read_to_end(&mut c)).unwrap();
let parts: Vec<&str> = path_str.split('/').collect();
insert_path(root.children.as_mut().unwrap(), &parts, &path_str, c);
}
_ => {}
}
}
}
std::fs::create_dir_all(output_path).map_err(GenBackError::IO)?;
Ok(std::fs::write(
output_path.join(repo_name).with_extension("json"),
serde_json::to_string_pretty(&root).map_err(GenBackError::Json)?,
)
.map_err(GenBackError::IO)?)
}