Skip to content
Clone
c9fb93e
283 lines 10.6 KB Rust
use std::convert::Infallible;
use std::ops::ControlFlow;
use crate::GitRepository;
use crate::diff::impls::DiffOps;
use crate::diff::types::{Diff, DiffFile, DiffFileType, DiffShortStat, DiffTreeRecord};
#[derive(Debug, Clone)]
struct ChangeRecord {
status: String,
head_path: String,
base_path: String,
head_mode: String,
base_mode: String,
head_oid: gix::hash::ObjectId,
base_oid: gix::hash::ObjectId,
}
fn collect_changes(
repo: &gix::Repository,
base_id: gix::hash::ObjectId,
head_id: gix::hash::ObjectId,
) -> Result<Vec<ChangeRecord>, crate::Error> {
let base_tree = repo
.find_tree(base_id)
.map_err(|e| crate::Error::Find(e.to_string()))?;
let head_tree = repo
.find_tree(head_id)
.map_err(|e| crate::Error::Find(e.to_string()))?;
let mut out: Vec<ChangeRecord> = Vec::new();
let mut for_each =
|change: gix::object::tree::diff::Change| -> Result<ControlFlow<()>, Infallible> {
match change {
gix::object::tree::diff::Change::Addition {
location,
entry_mode,
id,
..
} => {
out.push(ChangeRecord {
status: "added".to_string(),
head_path: location.to_string(),
base_path: String::new(),
head_mode: format!("{:o}", entry_mode.value()),
base_mode: String::new(),
head_oid: id.detach(),
base_oid: gix::hash::ObjectId::null(repo.object_hash()),
});
}
gix::object::tree::diff::Change::Deletion {
location,
entry_mode,
id,
..
} => {
out.push(ChangeRecord {
status: "deleted".to_string(),
head_path: String::new(),
base_path: location.to_string(),
head_mode: String::new(),
base_mode: format!("{:o}", entry_mode.value()),
head_oid: gix::hash::ObjectId::null(repo.object_hash()),
base_oid: id.detach(),
});
}
gix::object::tree::diff::Change::Modification {
location,
previous_entry_mode,
previous_id,
entry_mode,
id,
..
} => {
out.push(ChangeRecord {
status: "modified".to_string(),
head_path: location.to_string(),
base_path: location.to_string(),
head_mode: format!("{:o}", entry_mode.value()),
base_mode: format!("{:o}", previous_entry_mode.value()),
head_oid: id.detach(),
base_oid: previous_id.detach(),
});
}
gix::object::tree::diff::Change::Rewrite {
source_location,
source_entry_mode,
source_id,
location,
entry_mode,
id,
copy,
..
} => {
let status = if copy { "copied" } else { "renamed" };
out.push(ChangeRecord {
status: status.to_string(),
head_path: location.to_string(),
base_path: source_location.to_string(),
head_mode: format!("{:o}", entry_mode.value()),
base_mode: format!("{:o}", source_entry_mode.value()),
head_oid: id.detach(),
base_oid: source_id.detach(),
});
}
}
Ok(ControlFlow::Continue(()))
};
base_tree
.changes()
.map_err(|e| crate::Error::Find(e.to_string()))?
.for_each_to_obtain_tree(&head_tree, &mut for_each)
.map_err(|e| crate::Error::Find(e.to_string()))?;
drop(base_tree);
Ok(out)
}
fn resolve_tree_id(repo: &gix::Repository, rev: &str) -> Result<gix::hash::ObjectId, crate::Error> {
let id = repo
.rev_parse_single(rev.as_bytes())
.map_err(|e| crate::Error::RevParse(e.to_string()))?
.detach();
let commit = repo
.find_commit(id)
.map_err(|e| crate::Error::Find(e.to_string()))?;
Ok(commit
.tree_id()
.map_err(|e| crate::Error::Find(e.to_string()))?
.detach())
}
impl DiffOps for GitRepository {
fn diff_diff(&self, base: &str, head: &str) -> Result<Diff, crate::Error> {
let tip = self.tip_id()?;
let key = format!("{base}.{head}");
if let Some(hit) = self.cache_get_rev::<Diff>("diff", "diff", &key, tip) {
return Ok(hit);
}
let repo = self.repo()?;
let base_tree_id = resolve_tree_id(&repo, base)?;
let head_tree_id = resolve_tree_id(&repo, head)?;
let records = collect_changes(&repo, base_tree_id, head_tree_id)?;
let files = records
.into_iter()
.map(|r| {
let (tp, name, old_name) = match r.status.as_str() {
"added" => (DiffFileType::Add, r.head_path.clone(), String::new()),
"deleted" => (DiffFileType::Del, r.head_path.clone(), r.base_path.clone()),
"renamed" => (
DiffFileType::Rename,
r.head_path.clone(),
r.base_path.clone(),
),
"copied" => (DiffFileType::Copy, r.head_path.clone(), r.base_path.clone()),
_ => (
DiffFileType::Change,
r.head_path.clone(),
r.base_path.clone(),
),
};
DiffFile {
name,
old_name,
addition: 0,
deletion: 0,
r#type: tp,
entry_mode: r.head_mode,
old_entry_mode: r.base_mode,
is_created: r.status == "added",
is_deleted: r.status == "deleted",
is_bin: false,
is_lfs_file: false,
is_renamed: r.status == "renamed",
is_submodule: false,
is_generated: false,
is_vendored: false,
is_incomplete: false,
is_incomplete_line_too_long: false,
sections: Vec::new(),
left_blob_size: 0,
right_blob_size: 0,
left_blob_mime_type: String::new(),
right_blob_mime_type: String::new(),
is_blob_type_image: false,
is_blob_type_csv: false,
}
})
.collect();
let v = Diff {
start: base.to_string(),
end: head.to_string(),
files,
is_incomplete: false,
};
self.cache_put_rev("diff", "diff", &key, tip, &v);
Ok(v)
}
fn diff_short_stat(&self, base: &str, head: &str) -> Result<DiffShortStat, crate::Error> {
let tip = self.tip_id()?;
let key = format!("{base}.{head}");
if let Some(hit) = self.cache_get_rev::<DiffShortStat>("diff", "short", &key, tip) {
return Ok(hit);
}
let repo = self.repo()?;
let base_tree_id = resolve_tree_id(&repo, base)?;
let head_tree_id = resolve_tree_id(&repo, head)?;
let records = collect_changes(&repo, base_tree_id, head_tree_id)?;
let v = DiffShortStat {
num_files: records.len() as i32,
total_addition: 0,
total_deletion: 0,
};
self.cache_put_rev("diff", "short", &key, tip, &v);
Ok(v)
}
fn diff_files_changed_between(
&self,
base: &str,
head: &str,
) -> Result<Vec<String>, crate::Error> {
let tip = self.tip_id()?;
let key = format!("{base}.{head}");
if let Some(hit) = self.cache_get_rev::<Vec<String>>("diff", "files", &key, tip) {
return Ok(hit);
}
let repo = self.repo()?;
let base_tree_id = resolve_tree_id(&repo, base)?;
let head_tree_id = resolve_tree_id(&repo, head)?;
let records = collect_changes(&repo, base_tree_id, head_tree_id)?;
let v: Vec<String> = records.into_iter().map(|r| r.head_path).collect();
self.cache_put_rev("diff", "files", &key, tip, &v);
Ok(v)
}
fn diff_tree(
&self,
base: &str,
head: &str,
use_merge_base: bool,
) -> Result<Vec<DiffTreeRecord>, crate::Error> {
let tip = self.tip_id()?;
let key = format!("{base}.{head}.{use_merge_base}");
if let Some(hit) = self.cache_get_rev::<Vec<DiffTreeRecord>>("diff", "tree", &key, tip) {
return Ok(hit);
}
let repo = self.repo()?;
let (base_rev, head_rev) = if use_merge_base {
let h = repo
.rev_parse_single(head.as_bytes())
.map_err(|e| crate::Error::RevParse(e.to_string()))?
.detach();
let b = repo
.rev_parse_single(base.as_bytes())
.map_err(|e| crate::Error::RevParse(e.to_string()))?
.detach();
let merged = repo
.merge_base(b, h)
.map_err(|e| crate::Error::Find(e.to_string()))?;
(merged.to_string(), h.to_string())
} else {
(base.to_string(), head.to_string())
};
let base_tree_id = resolve_tree_id(&repo, &base_rev)?;
let head_tree_id = resolve_tree_id(&repo, &head_rev)?;
let records = collect_changes(&repo, base_tree_id, head_tree_id)?;
let v: Vec<DiffTreeRecord> = records
.into_iter()
.map(|r| DiffTreeRecord {
status: r.status,
score: 0,
head_path: r.head_path,
base_path: r.base_path,
head_mode: r.head_mode,
base_mode: r.base_mode,
head_blob_id: r.head_oid,
base_blob_id: r.base_oid,
})
.collect();
self.cache_put_rev("diff", "tree", &key, tip, &v);
Ok(v)
}
}