use std::collections::HashMap;
use dk_core::Result;
use crate::git::GitRepository;
use crate::parser::ParserRegistry;
use crate::workspace::conflict::{analyze_file_conflict, MergeAnalysis, SemanticConflict};
use crate::workspace::session_workspace::SessionWorkspace;
#[derive(Debug)]
pub enum WorkspaceMergeResult {
FastMerge { commit_hash: String },
RebaseMerge {
commit_hash: String,
auto_rebased_files: Vec<String>,
},
Conflicts {
conflicts: Vec<SemanticConflict>,
},
}
pub fn merge_workspace(
workspace: &SessionWorkspace,
git_repo: &GitRepository,
parser: &ParserRegistry,
commit_message: &str,
author_name: &str,
author_email: &str,
) -> Result<WorkspaceMergeResult> {
let overlay = workspace.overlay_for_tree();
if overlay.is_empty() {
return Err(dk_core::Error::Internal(
"workspace has no changes to merge".into(),
));
}
let head_hash = match git_repo.head_hash()? {
Some(hash) => hash,
None => {
if workspace.base_commit == "initial" {
let commit_hash = git_repo.commit_initial_overlay(
&overlay,
commit_message,
author_name,
author_email,
)?;
return Ok(WorkspaceMergeResult::FastMerge { commit_hash });
}
return Err(dk_core::Error::Git("repository has no HEAD".into()));
}
};
if head_hash == workspace.base_commit {
let commit_hash = git_repo.commit_tree_overlay(
&workspace.base_commit,
&overlay,
&workspace.base_commit,
commit_message,
author_name,
author_email,
)?;
return Ok(WorkspaceMergeResult::FastMerge { commit_hash });
}
let paths: Vec<&String> = overlay.iter().map(|(p, _)| p).collect();
let mut base_entries: HashMap<&str, Option<Vec<u8>>> = HashMap::with_capacity(paths.len());
for path in &paths {
base_entries.insert(path.as_str(), git_repo.read_tree_entry(&workspace.base_commit, path).ok());
}
let mut head_entries: HashMap<&str, Option<Vec<u8>>> = HashMap::with_capacity(paths.len());
for path in &paths {
head_entries.insert(path.as_str(), git_repo.read_tree_entry(&head_hash, path).ok());
}
let mut all_conflicts = Vec::new();
let mut auto_rebased = Vec::new();
let mut rebased_overlay: Vec<(String, Option<Vec<u8>>)> = Vec::new();
for (path, maybe_content) in &overlay {
let base_content = base_entries.get(path.as_str()).and_then(|v| v.as_ref());
let head_content = head_entries.get(path.as_str()).and_then(|v| v.as_ref());
match maybe_content {
None => {
match (base_content, head_content) {
(Some(base), Some(head)) => {
if base == head {
rebased_overlay.push((path.clone(), None));
} else {
all_conflicts.push(SemanticConflict {
file_path: path.clone(),
symbol_name: "<entire file>".to_string(),
our_change: crate::workspace::conflict::SymbolChangeKind::Removed,
their_change: crate::workspace::conflict::SymbolChangeKind::Modified,
});
}
}
_ => {
rebased_overlay.push((path.clone(), None));
}
}
}
Some(overlay_content) => {
match (base_content, head_content) {
(Some(base), Some(head)) => {
if base == head {
rebased_overlay.push((path.clone(), Some(overlay_content.clone())));
} else {
let analysis = analyze_file_conflict(
path,
base,
head,
overlay_content,
parser,
);
match analysis {
MergeAnalysis::AutoMerge { merged_content } => {
rebased_overlay
.push((path.clone(), Some(merged_content)));
auto_rebased.push(path.clone());
}
MergeAnalysis::Conflict { conflicts } => {
all_conflicts.extend(conflicts);
}
}
}
}
(None, Some(head_blob)) => {
if *head_blob == *overlay_content {
rebased_overlay.push((path.clone(), Some(overlay_content.clone())));
} else {
all_conflicts.push(SemanticConflict {
file_path: path.clone(),
symbol_name: "<entire file>".to_string(),
our_change: crate::workspace::conflict::SymbolChangeKind::Added,
their_change: crate::workspace::conflict::SymbolChangeKind::Added,
});
}
}
(None, None) => {
rebased_overlay.push((path.clone(), Some(overlay_content.clone())));
}
(Some(_), None) => {
all_conflicts.push(SemanticConflict {
file_path: path.clone(),
symbol_name: "<entire file>".to_string(),
our_change: crate::workspace::conflict::SymbolChangeKind::Modified,
their_change: crate::workspace::conflict::SymbolChangeKind::Removed,
});
}
}
}
}
}
if !all_conflicts.is_empty() {
return Ok(WorkspaceMergeResult::Conflicts {
conflicts: all_conflicts,
});
}
let commit_hash = git_repo.commit_tree_overlay(
&head_hash,
&rebased_overlay,
&head_hash,
commit_message,
author_name,
author_email,
)?;
Ok(WorkspaceMergeResult::RebaseMerge {
commit_hash,
auto_rebased_files: auto_rebased,
})
}