use std::{
collections::{BTreeMap, BTreeSet},
thread,
};
use crate::{
CommitSnapshot, HistoryOptions, ObjectId, RepositoryHistory, RepositoryId, RepositorySet,
Result, SharedCommit, TreeChange,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RepositorySnapshot {
pub repository: RepositoryId,
pub snapshot: CommitSnapshot,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RepositoryTimelineEntry {
pub repository: RepositoryId,
pub id: ObjectId,
pub tree: ObjectId,
pub parents: Vec<ObjectId>,
pub committer_time: i64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RepositoryChangeSet {
pub repository: RepositoryId,
pub old: ObjectId,
pub new: ObjectId,
pub changes: Vec<TreeChange>,
}
impl RepositorySet {
pub fn histories_from(
&self,
revision: &str,
options: HistoryOptions,
) -> Result<Vec<RepositoryHistory>> {
self.ids()
.map(|id| history(self, id, revision, options))
.collect()
}
pub fn histories_from_parallel(
&self,
revision: &str,
options: HistoryOptions,
) -> Result<Vec<RepositoryHistory>> {
thread::scope(|scope| {
self.ids()
.map(|id| scope.spawn(move || history(self, id, revision, options)))
.collect::<Vec<_>>()
.into_iter()
.map(join)
.collect()
})
}
pub fn snapshots(&self, revision: &str) -> Result<Vec<RepositorySnapshot>> {
self.ids().map(|id| snapshot(self, id, revision)).collect()
}
pub fn snapshots_parallel(&self, revision: &str) -> Result<Vec<RepositorySnapshot>> {
thread::scope(|scope| {
self.ids()
.map(|id| scope.spawn(move || snapshot(self, id, revision)))
.collect::<Vec<_>>()
.into_iter()
.map(join)
.collect()
})
}
pub fn timeline(
&self,
revision: &str,
options: HistoryOptions,
) -> Result<Vec<RepositoryTimelineEntry>> {
let mut entries = thread::scope(|scope| {
self.ids()
.map(|id| scope.spawn(move || repository_timeline(self, id, revision, options)))
.collect::<Vec<_>>()
.into_iter()
.map(join)
.collect::<Result<Vec<_>>>()
})?
.into_iter()
.flatten()
.collect::<Vec<_>>();
entries.sort_unstable_by(|left, right| {
right
.committer_time
.cmp(&left.committer_time)
.then_with(|| left.repository.cmp(&right.repository))
.then_with(|| left.id.cmp(&right.id))
});
Ok(entries)
}
pub fn shared_commits_from(
&self,
revision: &str,
options: HistoryOptions,
) -> Result<Vec<SharedCommit>> {
let mut repositories = BTreeMap::<ObjectId, BTreeSet<RepositoryId>>::new();
for history in self.histories_from_parallel(revision, options)? {
for id in history.commits {
repositories
.entry(id)
.or_default()
.insert(history.repository);
}
}
Ok(repositories
.into_iter()
.filter_map(|(id, repositories)| {
(repositories.len() > 1).then(|| SharedCommit {
id,
repositories: repositories.into_iter().collect(),
})
})
.collect())
}
pub fn changes(
&self,
old_revision: &str,
new_revision: &str,
) -> Result<Vec<RepositoryChangeSet>> {
self.ids()
.map(|id| changes(self, id, old_revision, new_revision))
.collect()
}
pub fn changes_parallel(
&self,
old_revision: &str,
new_revision: &str,
) -> Result<Vec<RepositoryChangeSet>> {
thread::scope(|scope| {
self.ids()
.map(|id| scope.spawn(move || changes(self, id, old_revision, new_revision)))
.collect::<Vec<_>>()
.into_iter()
.map(join)
.collect()
})
}
}
fn history(
set: &RepositorySet,
id: RepositoryId,
revision: &str,
options: HistoryOptions,
) -> Result<RepositoryHistory> {
let repository = set.get(id)?;
let head = repository.resolve(revision)?;
Ok(RepositoryHistory {
repository: id,
head,
commits: repository.history_ids(head, options)?,
})
}
fn snapshot(set: &RepositorySet, id: RepositoryId, revision: &str) -> Result<RepositorySnapshot> {
Ok(RepositorySnapshot {
repository: id,
snapshot: set.get(id)?.snapshot(revision)?,
})
}
fn repository_timeline(
set: &RepositorySet,
id: RepositoryId,
revision: &str,
options: HistoryOptions,
) -> Result<Vec<RepositoryTimelineEntry>> {
let repository = set.get(id)?;
let start = repository.resolve(revision)?;
repository
.history_ids(start, options)?
.into_iter()
.map(|commit_id| {
let commit = repository.commit_metadata(commit_id)?;
Ok(RepositoryTimelineEntry {
repository: id,
id: commit.id,
tree: commit.tree,
parents: commit.parents,
committer_time: commit.committer_time,
})
})
.collect()
}
fn changes(
set: &RepositorySet,
id: RepositoryId,
old_revision: &str,
new_revision: &str,
) -> Result<RepositoryChangeSet> {
let repository = set.get(id)?;
let old = repository.resolve(old_revision)?;
let new = repository.resolve(new_revision)?;
Ok(RepositoryChangeSet {
repository: id,
old,
new,
changes: repository.diff_commits(old, new)?,
})
}
fn join<T>(handle: thread::ScopedJoinHandle<'_, Result<T>>) -> Result<T> {
handle
.join()
.map_err(|_| crate::GitError::Unsupported("repository worker panicked".to_owned()))?
}