grit_lib/revwalk/
handle.rs1use crate::error::Result;
3use crate::object_inspect::{ResolveError, ResolvedObject};
4use crate::objects::ObjectId;
5use crate::repo::Repository;
6use crate::rev_list::{
7 open_commit_stream, rev_list, CommitStreamParams, RevListOptions, RevListResult,
8};
9use crate::rev_list_error::RevListError;
10use crate::rev_parse::resolve_revision;
11use crate::revwalk::parse_rev_walk_tokens;
12use crate::revwalk::RevWalkCommitIterOptions;
13use std::borrow::Cow;
14
15#[derive(Debug, Copy, Clone)]
17pub struct RevWalk<'repo> {
18 repo: &'repo Repository,
19}
20
21impl<'repo> RevWalk<'repo> {
22 #[inline]
24 #[must_use]
25 pub fn new(repo: &'repo Repository) -> Self {
26 Self { repo }
27 }
28
29 #[inline]
31 #[must_use]
32 pub fn repository(&self) -> &'repo Repository {
33 self.repo
34 }
35
36 #[inline]
62 pub fn resolve(&self, spec: &str) -> Result<ObjectId> {
63 resolve_revision(self.repo, spec)
64 }
65
66 #[inline]
72 pub fn resolve_object(&self, spec: &str) -> std::result::Result<ResolvedObject, ResolveError> {
73 self.repo.resolve(spec)
74 }
75
76 pub fn walk(&self, spec: &str, options: &RevListOptions) -> Result<RevListResult> {
100 if !is_single_walk_spec(spec) {
101 return Err(RevListError::UnsupportedWalkSpec {
102 spec: spec.to_owned(),
103 }
104 .into());
105 }
106 let parsed = parse_rev_walk_tokens(self.repo, &[spec])?;
107 let options = if parsed.symmetric_left.is_some()
108 && (options.symmetric_left.is_none() || options.symmetric_right.is_none())
109 {
110 let mut symmetric = options.clone();
111 symmetric.symmetric_left = symmetric.symmetric_left.or(parsed.symmetric_left);
112 symmetric.symmetric_right = symmetric.symmetric_right.or(parsed.symmetric_right);
113 Cow::Owned(symmetric)
114 } else {
115 Cow::Borrowed(options)
116 };
117 rev_list(self.repo, &parsed.positive, &parsed.negative, &options)
118 }
119
120 pub fn commits_iter(
131 &self,
132 spec: &str,
133 options: &RevWalkCommitIterOptions,
134 ) -> Result<RevWalkCommitIter<'repo>> {
135 if !is_single_walk_spec(spec) {
136 return Err(RevListError::UnsupportedWalkSpec {
137 spec: spec.to_owned(),
138 }
139 .into());
140 }
141 let parsed = parse_rev_walk_tokens(self.repo, &[spec])?;
142 let include =
143 crate::rev_list::resolve_specs_for_commit_stream(self.repo, &parsed.positive)?;
144 let exclude =
145 crate::rev_list::resolve_specs_for_commit_stream(self.repo, &parsed.negative)?;
146 let params = CommitStreamParams {
147 skip: options.skip,
148 limit: options.limit,
149 since: options.since,
150 until: options.until,
151 first_parent: options.first_parent,
152 };
153 Ok(RevWalkCommitIter {
154 inner: open_commit_stream(self.repo, &include, &exclude, ¶ms)?,
155 })
156 }
157}
158
159pub struct RevWalkCommitIter<'repo> {
161 inner: crate::rev_list::CommitStream<'repo>,
162}
163
164impl Iterator for RevWalkCommitIter<'_> {
165 type Item = Result<ObjectId>;
166
167 fn next(&mut self) -> Option<Self::Item> {
168 self.inner.next()
169 }
170}
171
172fn is_single_walk_spec(spec: &str) -> bool {
177 if spec.starts_with('-') {
178 return false;
179 }
180 let mut brace_depth = 0usize;
181 for c in spec.chars() {
182 match c {
183 ':' if brace_depth == 0 => return true,
184 '{' => brace_depth += 1,
185 '}' => brace_depth = brace_depth.saturating_sub(1),
186 c if c.is_whitespace() && brace_depth == 0 => return false,
187 _ => {}
188 }
189 }
190 true
191}
192
193#[cfg(test)]
194mod commit_iter_tests {
195 use super::*;
196 use crate::ident::Signature;
197 use crate::porcelain::commit::{create_commit, CommitRequest};
198 use crate::progress::{NeverCancel, NullProgress};
199 use crate::repo::{InitOptions, Repository};
200
201 fn init_repo_with_commits(count: usize) -> (tempfile::TempDir, Repository) {
202 let dir = tempfile::tempdir().expect("tempdir");
203 let repo = Repository::init(dir.path(), &InitOptions::default()).expect("init");
204 for i in 0..count {
205 let mut req = CommitRequest::new(format!("commit {i}"));
206 req.author = Some(
207 Signature::new("Author", "a@example.com", repo.identity_now()).expect("author"),
208 );
209 req.committer = Some(
210 Signature::new("Committer", "c@example.com", repo.identity_now())
211 .expect("committer"),
212 );
213 req.allow_empty = true;
214 create_commit(&repo, &req, &mut NullProgress, &NeverCancel).expect("commit");
215 }
216 (dir, repo)
217 }
218
219 #[test]
220 fn commits_iter_stops_at_limit_without_full_rev_list() {
221 let (_dir, repo) = init_repo_with_commits(5);
222 let options = RevWalkCommitIterOptions {
223 limit: Some(2),
224 ..RevWalkCommitIterOptions::default()
225 };
226 let ids: Vec<_> = repo
227 .rev_walk()
228 .commits_iter("HEAD", &options)
229 .expect("iter")
230 .collect::<Result<Vec<_>>>()
231 .expect("walk");
232 assert_eq!(ids.len(), 2);
233 }
234
235 #[test]
236 fn commits_iter_since_cutoff_filters_old_commits() {
237 let (_dir, repo) = init_repo_with_commits(4);
238 let newest = repo.rev_walk().resolve("HEAD").expect("head");
239 let params = crate::rev_list::CommitStreamParams {
240 limit: Some(10),
241 ..Default::default()
242 };
243 let all: Vec<_> = open_commit_stream(&repo, std::slice::from_ref(&newest), &[], ¶ms)
244 .expect("stream")
245 .collect::<Result<Vec<_>>>()
246 .expect("walk");
247 assert_eq!(all.len(), 4);
248
249 let future = i64::MAX;
250 let filtered: Vec<_> = open_commit_stream(
251 &repo,
252 std::slice::from_ref(&newest),
253 &[],
254 &crate::rev_list::CommitStreamParams {
255 since: Some(future),
256 ..Default::default()
257 },
258 )
259 .expect("stream")
260 .collect::<Result<Vec<_>>>()
261 .expect("walk");
262 assert!(filtered.is_empty());
263 }
264}