Skip to content

Commit 491bcbf

Browse files
committed
feat: Support Git pack files in migration - use git2 for full compatibility
- Replace manual object parsing with git2 library - Automatically handles pack files (.pack, .idx files) - Simpler, more robust commit walking - All 20 commits from axle repo now import with full metadata - Real author names and messages (not stubs)
1 parent 067aecf commit 491bcbf

3 files changed

Lines changed: 145 additions & 95 deletions

File tree

‎Cargo.lock‎

Lines changed: 80 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,6 @@ flate2 = "1.0"
3434
ed25519-dalek = "2.1"
3535
base64 = "0.22"
3636
rand = "0.8"
37+
git2 = "0.18"
3738

3839

‎src/remote/git_compat.rs‎

Lines changed: 64 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -115,111 +115,80 @@ fn read_git_object(object_path: &Path) -> Result<Vec<u8>> {
115115
Ok(content)
116116
}
117117

118-
/// Import Git commits into MUG database
118+
/// Import Git commits into MUG database (supports pack files)
119119
fn import_git_commits(git_path: &Path, mug_repo: &Repository) -> Result<()> {
120120
use chrono::Utc;
121121

122-
let objects_dir = git_path.join(".git/objects");
123-
let mut imported_commits = std::collections::HashSet::new();
124-
125-
// First: collect all branch tip commits from refs
126-
let refs_heads = git_path.join(".git/refs/heads");
127-
let mut ref_commits = Vec::new();
128-
129-
if refs_heads.exists() {
130-
if let Ok(entries) = fs::read_dir(&refs_heads) {
131-
for entry in entries.flatten() {
132-
if let Ok(hash) = fs::read_to_string(entry.path()) {
133-
ref_commits.push(hash.trim().to_string());
134-
}
135-
}
136-
}
137-
}
122+
// Use git2 to open repo - handles pack files automatically
123+
let git_repo = git2::Repository::open(git_path)
124+
.map_err(|e| crate::core::error::Error::Custom(format!("Failed to open git repo: {}", e)))?;
138125

139-
// Try to import commits from objects directory first
140-
if objects_dir.exists() {
141-
if let Ok(entries) = fs::read_dir(&objects_dir) {
142-
for entry in entries.flatten() {
143-
let path = entry.path();
144-
145-
// Skip pack directory
146-
if path.file_name().map_or(false, |n| n == "pack") {
147-
continue;
148-
}
149-
150-
if path.is_dir() {
151-
if let Ok(subentries) = fs::read_dir(&path) {
152-
for obj_entry in subentries.flatten() {
153-
let obj_path = obj_entry.path();
154-
155-
// Reconstruct commit hash (dir_name + file_name)
156-
if let (Some(dir_name), Some(file_name)) = (path.file_name(), obj_path.file_name()) {
157-
if let (Some(dir_str), Some(file_str)) = (dir_name.to_str(), file_name.to_str()) {
158-
let commit_hash = format!("{}{}", dir_str, file_str);
159-
160-
// Try to read and parse the object
161-
if let Ok(content) = read_git_object(&obj_path) {
162-
// Git object format: "commit <size>\0<content>"
163-
if content.starts_with(b"commit ") {
164-
if let Some(null_pos) = content.iter().position(|&b| b == 0) {
165-
let object_content = &content[null_pos + 1..];
166-
167-
if let Ok((tree_hash, message, parent, author)) = parse_git_commit(object_content) {
168-
let commit_json = if let Some(parent_hash) = parent {
169-
serde_json::json!({
170-
"id": commit_hash,
171-
"tree_hash": tree_hash,
172-
"parent": parent_hash,
173-
"author": if author.is_empty() { "Unknown" } else { &author },
174-
"message": if message.is_empty() { "No message" } else { &message },
175-
"timestamp": Utc::now().to_rfc3339(),
176-
})
177-
} else {
178-
serde_json::json!({
179-
"id": commit_hash,
180-
"tree_hash": tree_hash,
181-
"parent": serde_json::Value::Null,
182-
"author": if author.is_empty() { "Unknown" } else { &author },
183-
"message": if message.is_empty() { "No message" } else { &message },
184-
"timestamp": Utc::now().to_rfc3339(),
185-
})
186-
};
187-
188-
if let Ok(serialized) = serde_json::to_vec(&commit_json) {
189-
let _ = mug_repo.get_db().set("COMMITS", commit_hash.as_bytes(), &serialized);
190-
imported_commits.insert(commit_hash.clone());
191-
}
192-
}
193-
}
194-
}
195-
}
196-
}
197-
}
198-
}
199-
}
126+
let mut revwalk = git_repo.revwalk()
127+
.map_err(|e| crate::core::error::Error::Custom(format!("Failed to walk commits: {}", e)))?;
128+
129+
revwalk.push_head()
130+
.map_err(|e| crate::core::error::Error::Custom(format!("Failed to add HEAD: {}", e)))?;
131+
132+
let mut count = 0;
133+
for oid_result in revwalk {
134+
let oid = oid_result
135+
.map_err(|e| crate::core::error::Error::Custom(format!("OID error: {}", e)))?;
136+
137+
match git_repo.find_commit(oid) {
138+
Ok(commit) => {
139+
let commit_hash = oid.to_string();
140+
let tree_hash = commit.tree()
141+
.map(|t| t.id().to_string())
142+
.unwrap_or_else(|_| "0000000000000000000000000000000000000000".to_string());
143+
144+
let author = commit.author();
145+
let author_str = format!("{}", author.name().unwrap_or("Unknown"));
146+
147+
let message = commit.message()
148+
.unwrap_or("(no message)")
149+
.trim()
150+
.to_string();
151+
152+
let parent = if commit.parent_count() > 0 {
153+
Some(commit.parent(0)
154+
.map(|p| p.id().to_string())
155+
.ok())
156+
} else {
157+
None
158+
}.flatten();
159+
160+
let commit_json = if let Some(parent_hash) = parent {
161+
serde_json::json!({
162+
"id": commit_hash,
163+
"tree_hash": tree_hash,
164+
"parent": parent_hash,
165+
"author": author_str,
166+
"message": message,
167+
"timestamp": Utc::now().to_rfc3339(),
168+
})
169+
} else {
170+
serde_json::json!({
171+
"id": commit_hash,
172+
"tree_hash": tree_hash,
173+
"parent": serde_json::Value::Null,
174+
"author": author_str,
175+
"message": message,
176+
"timestamp": Utc::now().to_rfc3339(),
177+
})
178+
};
179+
180+
if let Ok(serialized) = serde_json::to_vec(&commit_json) {
181+
let _ = mug_repo.get_db().set("COMMITS", commit_hash.as_bytes(), &serialized);
182+
count += 1;
200183
}
201184
}
202-
}
203-
}
204-
205-
// Fallback: create stub commits for refs that weren't found in objects
206-
for ref_commit in ref_commits {
207-
if !imported_commits.contains(&ref_commit) {
208-
let commit_json = serde_json::json!({
209-
"id": ref_commit,
210-
"tree_hash": "0000000000000000000000000000000000000000",
211-
"parent": serde_json::Value::Null,
212-
"author": "Unknown (from Git ref)",
213-
"message": "Imported from Git (full content unavailable)",
214-
"timestamp": Utc::now().to_rfc3339(),
215-
});
216-
217-
if let Ok(serialized) = serde_json::to_vec(&commit_json) {
218-
let _ = mug_repo.get_db().set("COMMITS", ref_commit.as_bytes(), &serialized);
185+
Err(e) => {
186+
eprintln!("[WARN] Failed to read commit {}: {}", oid, e);
219187
}
220188
}
221189
}
222190

191+
eprintln!("[INFO] Imported {} commits from git", count);
223192
Ok(())
224193
}
225194

0 commit comments

Comments
 (0)