1use std::path::PathBuf;
8
9use panproto_project::ProjectBuilder;
10use panproto_vcs::{CommitObject, Object, ObjectId, Store};
11
12use crate::error::GitBridgeError;
13
14#[derive(Debug)]
16pub struct ImportResult {
17 pub commit_count: usize,
19 pub head_id: ObjectId,
21 pub oid_map: Vec<(git2::Oid, ObjectId)>,
23}
24
25pub fn import_git_repo<S: Store>(
39 git_repo: &git2::Repository,
40 panproto_store: &mut S,
41 revspec: &str,
42) -> Result<ImportResult, GitBridgeError> {
43 let obj = git_repo.revparse_single(revspec)?;
45 let head_commit = obj
46 .peel_to_commit()
47 .map_err(|e| GitBridgeError::ObjectRead {
48 oid: obj.id().to_string(),
49 reason: format!("not a commit: {e}"),
50 })?;
51
52 let mut commits = Vec::new();
54 collect_ancestors(git_repo, head_commit.id(), &mut commits)?;
55
56 let mut oid_map: Vec<(git2::Oid, ObjectId)> = Vec::new();
58 let mut git_to_panproto: rustc_hash::FxHashMap<git2::Oid, ObjectId> =
59 rustc_hash::FxHashMap::default();
60 let mut last_id = ObjectId::ZERO;
61
62 for git_oid in &commits {
63 let git_commit = git_repo.find_commit(*git_oid)?;
64 let tree = git_commit.tree()?;
65
66 let mut project_builder = ProjectBuilder::new();
68 walk_git_tree(git_repo, &tree, &PathBuf::new(), &mut project_builder)?;
69
70 let project = if project_builder.file_count() == 0 {
72 let proto = panproto_protocols::raw_file::protocol();
74 let builder = panproto_schema::SchemaBuilder::new(&proto);
75
76 builder
77 .vertex("root", "file", None)
78 .map_err(|e| {
79 GitBridgeError::Project(panproto_project::ProjectError::CoproductFailed {
80 reason: format!("empty tree schema: {e}"),
81 })
82 })?
83 .build()
84 .map_err(|e| {
85 GitBridgeError::Project(panproto_project::ProjectError::CoproductFailed {
86 reason: format!("empty tree build: {e}"),
87 })
88 })?
89 } else {
90 project_builder.build()?.schema
91 };
92
93 let schema_id = panproto_store.put(&Object::Schema(Box::new(project)))?;
95
96 let parents: Vec<ObjectId> = git_commit
98 .parent_ids()
99 .filter_map(|parent_oid| git_to_panproto.get(&parent_oid).copied())
100 .collect();
101
102 let author_sig = git_commit.author();
104 let author = author_sig.name().unwrap_or("unknown").to_owned();
105 let timestamp = u64::try_from(author_sig.when().seconds()).unwrap_or(0);
106 let message = git_commit.message().unwrap_or("(no message)").to_owned();
107
108 let commit = CommitObject {
110 schema_id,
111 parents,
112 migration_id: None,
113 protocol: "project".to_owned(),
114 author,
115 timestamp,
116 message,
117 renames: Vec::new(),
118 protocol_id: None,
119 data_ids: Vec::new(),
120 complement_ids: Vec::new(),
121 edit_log_ids: Vec::new(),
122 };
123
124 let commit_id = panproto_store.put(&Object::Commit(commit))?;
125
126 git_to_panproto.insert(*git_oid, commit_id);
127 oid_map.push((*git_oid, commit_id));
128 last_id = commit_id;
129 }
130
131 if !commits.is_empty() {
133 panproto_store.set_ref("refs/heads/main", last_id)?;
134 }
135
136 Ok(ImportResult {
137 commit_count: commits.len(),
138 head_id: last_id,
139 oid_map,
140 })
141}
142
143fn collect_ancestors(
145 repo: &git2::Repository,
146 head: git2::Oid,
147 result: &mut Vec<git2::Oid>,
148) -> Result<(), GitBridgeError> {
149 let mut revwalk = repo.revwalk()?;
150 revwalk.push(head)?;
151 revwalk.set_sorting(git2::Sort::TOPOLOGICAL | git2::Sort::REVERSE)?;
152
153 for oid_result in revwalk {
154 result.push(oid_result?);
155 }
156
157 Ok(())
158}
159
160fn walk_git_tree(
162 repo: &git2::Repository,
163 tree: &git2::Tree<'_>,
164 prefix: &std::path::Path,
165 builder: &mut ProjectBuilder,
166) -> Result<(), GitBridgeError> {
167 for entry in tree {
168 let name = entry.name().unwrap_or("(unnamed)");
169 let path = prefix.join(name);
170
171 match entry.kind() {
172 Some(git2::ObjectType::Blob) => {
173 let blob = repo.find_blob(entry.id())?;
174 let content = blob.content();
175 builder.add_file(&path, content)?;
176 }
177 Some(git2::ObjectType::Tree) => {
178 let subtree = repo.find_tree(entry.id())?;
179 walk_git_tree(repo, &subtree, &path, builder)?;
180 }
181 _ => {
182 }
184 }
185 }
186
187 Ok(())
188}