1use std::{
2 collections::{BTreeMap, BTreeSet},
3 thread,
4};
5
6use crate::{
7 CommitSnapshot, HistoryOptions, ObjectId, RepositoryHistory, RepositoryId, RepositorySet,
8 Result, SharedCommit, TreeChange,
9};
10
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct RepositorySnapshot {
13 pub repository: RepositoryId,
14 pub snapshot: CommitSnapshot,
15}
16
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct RepositoryTimelineEntry {
19 pub repository: RepositoryId,
20 pub id: ObjectId,
21 pub tree: ObjectId,
22 pub parents: Vec<ObjectId>,
23 pub committer_time: i64,
24}
25
26#[derive(Clone, Debug, Eq, PartialEq)]
27pub struct RepositoryChangeSet {
28 pub repository: RepositoryId,
29 pub old: ObjectId,
30 pub new: ObjectId,
31 pub changes: Vec<TreeChange>,
32}
33
34impl RepositorySet {
35 pub fn histories_from(
36 &self,
37 revision: &str,
38 options: HistoryOptions,
39 ) -> Result<Vec<RepositoryHistory>> {
40 self.ids()
41 .map(|id| history(self, id, revision, options))
42 .collect()
43 }
44
45 pub fn histories_from_parallel(
46 &self,
47 revision: &str,
48 options: HistoryOptions,
49 ) -> Result<Vec<RepositoryHistory>> {
50 thread::scope(|scope| {
51 self.ids()
52 .map(|id| scope.spawn(move || history(self, id, revision, options)))
53 .collect::<Vec<_>>()
54 .into_iter()
55 .map(join)
56 .collect()
57 })
58 }
59
60 pub fn snapshots(&self, revision: &str) -> Result<Vec<RepositorySnapshot>> {
61 self.ids().map(|id| snapshot(self, id, revision)).collect()
62 }
63
64 pub fn snapshots_parallel(&self, revision: &str) -> Result<Vec<RepositorySnapshot>> {
65 thread::scope(|scope| {
66 self.ids()
67 .map(|id| scope.spawn(move || snapshot(self, id, revision)))
68 .collect::<Vec<_>>()
69 .into_iter()
70 .map(join)
71 .collect()
72 })
73 }
74
75 pub fn timeline(
76 &self,
77 revision: &str,
78 options: HistoryOptions,
79 ) -> Result<Vec<RepositoryTimelineEntry>> {
80 let mut entries = thread::scope(|scope| {
81 self.ids()
82 .map(|id| scope.spawn(move || repository_timeline(self, id, revision, options)))
83 .collect::<Vec<_>>()
84 .into_iter()
85 .map(join)
86 .collect::<Result<Vec<_>>>()
87 })?
88 .into_iter()
89 .flatten()
90 .collect::<Vec<_>>();
91 entries.sort_unstable_by(|left, right| {
92 right
93 .committer_time
94 .cmp(&left.committer_time)
95 .then_with(|| left.repository.cmp(&right.repository))
96 .then_with(|| left.id.cmp(&right.id))
97 });
98 Ok(entries)
99 }
100
101 pub fn shared_commits_from(
102 &self,
103 revision: &str,
104 options: HistoryOptions,
105 ) -> Result<Vec<SharedCommit>> {
106 let mut repositories = BTreeMap::<ObjectId, BTreeSet<RepositoryId>>::new();
107 for history in self.histories_from_parallel(revision, options)? {
108 for id in history.commits {
109 repositories
110 .entry(id)
111 .or_default()
112 .insert(history.repository);
113 }
114 }
115 Ok(repositories
116 .into_iter()
117 .filter_map(|(id, repositories)| {
118 (repositories.len() > 1).then(|| SharedCommit {
119 id,
120 repositories: repositories.into_iter().collect(),
121 })
122 })
123 .collect())
124 }
125
126 pub fn changes(
127 &self,
128 old_revision: &str,
129 new_revision: &str,
130 ) -> Result<Vec<RepositoryChangeSet>> {
131 self.ids()
132 .map(|id| changes(self, id, old_revision, new_revision))
133 .collect()
134 }
135
136 pub fn changes_parallel(
137 &self,
138 old_revision: &str,
139 new_revision: &str,
140 ) -> Result<Vec<RepositoryChangeSet>> {
141 thread::scope(|scope| {
142 self.ids()
143 .map(|id| scope.spawn(move || changes(self, id, old_revision, new_revision)))
144 .collect::<Vec<_>>()
145 .into_iter()
146 .map(join)
147 .collect()
148 })
149 }
150}
151
152fn history(
153 set: &RepositorySet,
154 id: RepositoryId,
155 revision: &str,
156 options: HistoryOptions,
157) -> Result<RepositoryHistory> {
158 let repository = set.get(id)?;
159 let head = repository.resolve(revision)?;
160 Ok(RepositoryHistory {
161 repository: id,
162 head,
163 commits: repository.history_ids(head, options)?,
164 })
165}
166
167fn snapshot(set: &RepositorySet, id: RepositoryId, revision: &str) -> Result<RepositorySnapshot> {
168 Ok(RepositorySnapshot {
169 repository: id,
170 snapshot: set.get(id)?.snapshot(revision)?,
171 })
172}
173
174fn repository_timeline(
175 set: &RepositorySet,
176 id: RepositoryId,
177 revision: &str,
178 options: HistoryOptions,
179) -> Result<Vec<RepositoryTimelineEntry>> {
180 let repository = set.get(id)?;
181 let start = repository.resolve(revision)?;
182 repository
183 .history_ids(start, options)?
184 .into_iter()
185 .map(|commit_id| {
186 let commit = repository.commit_metadata(commit_id)?;
187 Ok(RepositoryTimelineEntry {
188 repository: id,
189 id: commit.id,
190 tree: commit.tree,
191 parents: commit.parents,
192 committer_time: commit.committer_time,
193 })
194 })
195 .collect()
196}
197
198fn changes(
199 set: &RepositorySet,
200 id: RepositoryId,
201 old_revision: &str,
202 new_revision: &str,
203) -> Result<RepositoryChangeSet> {
204 let repository = set.get(id)?;
205 let old = repository.resolve(old_revision)?;
206 let new = repository.resolve(new_revision)?;
207 Ok(RepositoryChangeSet {
208 repository: id,
209 old,
210 new,
211 changes: repository.diff_commits(old, new)?,
212 })
213}
214
215fn join<T>(handle: thread::ScopedJoinHandle<'_, Result<T>>) -> Result<T> {
216 handle
217 .join()
218 .map_err(|_| crate::GitError::Unsupported("repository worker panicked".to_owned()))?
219}