cli/bridge/
git_import_tree.rs1use std::collections::HashMap;
5
6use objects::object::{Blob, ContentHash, FileMode, Tree, TreeEntry};
7use repo::Repository as HeddleRepository;
8
9use crate::bridge::git_core::{GitBridgeError, GitResult};
10
11const SUBMODULE_PREFIX: &str = "heddle-submodule:";
12
13pub struct GitTreeImporter<'a> {
14 heddle_repo: &'a HeddleRepository,
15 repo: &'a gix::Repository,
16 tree_cache: HashMap<gix::hash::ObjectId, ContentHash>,
17 blob_cache: HashMap<gix::hash::ObjectId, ContentHash>,
18}
19
20impl<'a> GitTreeImporter<'a> {
21 pub fn new(heddle_repo: &'a HeddleRepository, repo: &'a gix::Repository) -> Self {
22 Self {
23 heddle_repo,
24 repo,
25 tree_cache: HashMap::new(),
26 blob_cache: HashMap::new(),
27 }
28 }
29
30 pub fn import_tree(&mut self, tree_oid: gix::hash::ObjectId) -> GitResult<ContentHash> {
31 if let Some(hash) = self.tree_cache.get(&tree_oid) {
32 return Ok(*hash);
33 }
34
35 let git_tree = self
36 .repo
37 .find_tree(tree_oid)
38 .map_err(|err| GitBridgeError::Git(err.to_string()))?;
39
40 let mut entries = Vec::new();
41
42 for entry in git_tree.iter() {
43 let entry = entry.map_err(|err| GitBridgeError::Git(err.to_string()))?;
44 let name = String::from_utf8_lossy(entry.filename().as_ref()).into_owned();
45
46 match entry.kind() {
47 gix::object::tree::EntryKind::Blob
48 | gix::object::tree::EntryKind::BlobExecutable => {
49 let hash = self.import_blob(entry.object_id())?;
50
51 let mode =
52 if matches!(entry.kind(), gix::object::tree::EntryKind::BlobExecutable) {
53 FileMode::Executable
54 } else {
55 FileMode::Normal
56 };
57
58 entries.push(TreeEntry {
59 name,
60 mode,
61 entry_type: objects::object::EntryType::Blob,
62 hash,
63 });
64 }
65 gix::object::tree::EntryKind::Link => {
66 let hash = self.import_blob(entry.object_id())?;
67 entries.push(TreeEntry {
76 name,
77 mode: FileMode::Symlink,
78 entry_type: objects::object::EntryType::Symlink,
79 hash,
80 });
81 }
82 gix::object::tree::EntryKind::Tree => {
83 let subtree_hash = self.import_tree(entry.object_id())?;
84 entries.push(TreeEntry {
85 name,
86 mode: FileMode::Normal,
87 entry_type: objects::object::EntryType::Tree,
88 hash: subtree_hash,
89 });
90 }
91 gix::object::tree::EntryKind::Commit => {
92 let hash = self.import_gitlink(entry.object_id())?;
93 entries.push(TreeEntry {
94 name,
95 mode: FileMode::Normal,
96 entry_type: objects::object::EntryType::Blob,
97 hash,
98 });
99 }
100 }
101 }
102
103 let tree = Tree::from_entries(entries);
104 let hash = self.heddle_repo.store().put_tree(&tree)?;
105 self.tree_cache.insert(tree_oid, hash);
106 Ok(hash)
107 }
108
109 fn import_blob(&mut self, blob_oid: gix::hash::ObjectId) -> GitResult<ContentHash> {
110 if let Some(hash) = self.blob_cache.get(&blob_oid) {
111 return Ok(*hash);
112 }
113
114 let mut blob = self
115 .repo
116 .find_blob(blob_oid)
117 .map_err(|err| GitBridgeError::Git(err.to_string()))?;
118
119 let heddle_blob = Blob::new(blob.take_data());
120 let hash = self.heddle_repo.store().put_blob(&heddle_blob)?;
121 self.blob_cache.insert(blob_oid, hash);
122 Ok(hash)
123 }
124
125 fn import_gitlink(&mut self, oid: gix::hash::ObjectId) -> GitResult<ContentHash> {
126 if let Some(hash) = self.blob_cache.get(&oid) {
127 return Ok(*hash);
128 }
129
130 let blob = Blob::new(format!("{} {}", SUBMODULE_PREFIX, oid).into_bytes());
131 let hash = self.heddle_repo.store().put_blob(&blob)?;
132 self.blob_cache.insert(oid, hash);
133 Ok(hash)
134 }
135}
136
137pub fn import_git_tree(
139 heddle_repo: &HeddleRepository,
140 repo: &gix::Repository,
141 tree_oid: gix::hash::ObjectId,
142) -> GitResult<ContentHash> {
143 GitTreeImporter::new(heddle_repo, repo).import_tree(tree_oid)
144}