142 lines · 5.2 KB · Rust
RawDownload
pub struct Options {    pub patterns: Vec<gix::pathspec::Pattern>,    pub verbatim: bool,    pub max_count: usize,} pub(super) mod function {    use std::{borrow::Cow, path::Path};     use anyhow::{Context, bail};    use gix::{object::tree::EntryKind, objs::FindExt};     use super::Options;     pub fn git_to_sh(        output_dir: &Path,        repo_dir: &Path,        name: &str,        committish: &str,        mut out: impl std::io::Write,        Options {            patterns,            verbatim,            max_count,        }: Options,    ) -> anyhow::Result<()> {        let repo = gix::open(repo_dir)?;        let commit = repo.rev_parse_single(committish)?.object()?.try_into_commit()?;         let assets = output_dir.join(name);        std::fs::create_dir_all(&assets)?;         let mut commits = Vec::new();        let mut tree_buf = Vec::new();        let mut current = 0;        for entry in commit.id().ancestors().first_parent_only().all()? {            let entry = entry?;             let commit = entry.id().object()?.into_commit();            commits.push((commit.id, commit.message_raw_sloppy().to_owned()));            let index = repo.index_from_tree(&commit.tree_id()?)?;             tree_buf.clear();            write_tree_as_update_index_format(&repo, &index, &mut tree_buf, &assets, verbatim, patterns.clone())?;             let tree_file = assets.join(format!("{}.tree", commit.id));            std::fs::write(tree_file, &tree_buf)?;            current += 1;             if current >= max_count {                break;            }        }         writeln!(            &mut out,            "# The following is to be executed in the receiving git repository"        )?;        writeln!(&mut out, "ROOT=to-be-specified-by-user")?;        writeln!(&mut out, "index=.git/index")?;        writeln!(&mut out, "git hash-object -w -t blob -- $ROOT/{name}/*.blob")?;        for (commit_id, commit_msg) in commits.iter().rev() {            writeln!(&mut out, "rm \"$index\"")?;            writeln!(                &mut out,                "git update-index --index-info < \"$ROOT/{name}/{commit_id}.tree\""            )?;            let commit_msg_file = assets.join(format!("{commit_id}.msg"));            std::fs::write(commit_msg_file, commit_msg)?;            writeln!(&mut out, "git commit --allow-empty -F \"$ROOT/{name}/{commit_id}.msg\"")?;        }         Ok(())    }     fn write_tree_as_update_index_format(        repo: &gix::Repository,        index: &gix::index::State,        out: &mut dyn std::io::Write,        output_dir: &Path,        verbatim: bool,        patterns: Vec<gix::pathspec::Pattern>,    ) -> anyhow::Result<()> {        let mut blob_buf = Vec::new();        let mut specs = repo.pathspec(            true,            // TODO: ideally this could accept patterns already.            patterns.clone().into_iter().map(|p| p.to_bstring()),            true,            index,            gix::worktree::stack::state::attributes::Source::IdMapping,        )?;         for (rela_path, entry) in specs.index_entries_with_paths(index).into_iter().flatten() {            if rela_path.contains(&b'\n') {                bail!(                    "Entry at '{rela_path}' contained a newline, which currently can't be encoded. Preferred newlines over NULL separation."                )            }             let (blob_id, blob_data) = match entry.mode.to_tree_entry_mode() {                None => {                    bail!("Couldn't interpret mode of tree entry at '{rela_path}'")                }                Some(mode) => match mode.kind() {                    EntryKind::Tree => {                        unreachable!("Can't have trees in indices")                    }                    EntryKind::Blob | EntryKind::BlobExecutable => {                        let obj = repo.objects.find(&entry.id, &mut blob_buf)?;                        if verbatim {                            (entry.id, Cow::Borrowed(&blob_buf))                        } else {                            let data = std::str::from_utf8(obj.data).with_context(|| {                                format!("Entry at '{rela_path}' was not valid UTF8 and can't be remapped")                            })?;                            let mapped = crate::commands::copy_royal::remapped(data);                            (                                gix::objs::compute_hash(                                    repo.object_hash(),                                    gix::object::Kind::Blob,                                    mapped.as_bytes(),                                )?,                                Cow::Owned(mapped.into()),                            )                        }                    }                    EntryKind::Link => {                        repo.objects.find(&entry.id, &mut blob_buf)?;                        (entry.id, Cow::Borrowed(&blob_buf))                    }                    EntryKind::Commit => continue,                },            };            let blob_path = output_dir.join(format!("{blob_id}.blob"));            std::fs::write(blob_path, blob_data.as_ref())?;             writeln!(out, "{mode:06o} {blob_id}\t{rela_path}", mode = entry.mode)?;        }        Ok(())    }}