hennzau

21 commits
Updated 2026-04-25 12:39:37
hnz/src
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)?)
}