Files
sausage/userland/git/src/history.rs
T

78 lines
2.3 KiB
Rust

//! Path-limited history follows a tree-same parent at merges, as Git's default history simplification does.
use crate::{Result, changes, repo::error};
use std::collections::BTreeSet;
pub(crate) fn paths(
repo: &gix::Repository,
start: gix::ObjectId,
path: &str,
) -> Result<Vec<gix::ObjectId>> {
let empty = repo
.write_object(&gix::objs::Tree { entries: vec![] })
.map_err(|e| error("log empty tree", e))?
.detach();
let time = |id| -> Result<i64> {
Ok(repo
.find_commit(id)
.map_err(|e| error("log commit", e))?
.committer()
.map_err(|e| error("log committer", e))?
.time()
.map_err(|e| error("log time", e))?
.seconds)
};
let mut todo = vec![(start, time(start)?)];
let mut visited = BTreeSet::new();
let mut visible = Vec::new();
while !todo.is_empty() {
let next = todo
.iter()
.enumerate()
.min_by_key(|(_, (_, time))| std::cmp::Reverse(*time))
.unwrap()
.0;
let (id, _) = todo.remove(next);
if !visited.insert(id) {
continue;
}
let commit = repo.find_commit(id).map_err(|e| error("log commit", e))?;
let parents = commit
.parent_ids()
.map(|id| id.detach())
.collect::<Vec<_>>();
let mut same = None;
for parent in &parents {
if diff(repo, *parent, id, path)?.is_empty() {
same = Some(*parent);
break;
}
}
if let Some(parent) = same {
todo.push((parent, time(parent)?));
} else {
if !parents.is_empty() || !diff(repo, empty, id, path)?.is_empty() {
visible.push(id);
}
for parent in parents {
todo.push((parent, time(parent)?));
}
}
}
Ok(visible)
}
fn diff(
repo: &gix::Repository,
from: gix::ObjectId,
to: gix::ObjectId,
path: &str,
) -> Result<Vec<changes::FileDiff>> {
changes::diff(changes::DiffParams {
repo: repo.workdir().unwrap().to_string_lossy().into_owned(),
from: Some(from.to_string()),
to: Some(to.to_string()),
staged: None,
path: Some(path.into()),
context: None,
})
}