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, CommitExtent, 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 retain_commit_bytes: false,
153 retained_commit_extent: CommitExtent::Whole,
154 };
155 Ok(RevWalkCommitIter {
156 inner: open_commit_stream(self.repo, &include, &exclude, ¶ms)?,
157 })
158 }
159}
160
161pub struct RevWalkCommitIter<'repo> {
163 inner: crate::rev_list::CommitStream<'repo>,
164}
165
166impl Iterator for RevWalkCommitIter<'_> {
167 type Item = Result<ObjectId>;
168
169 fn next(&mut self) -> Option<Self::Item> {
170 self.inner.next().map(|item| item.map(|row| row.oid))
171 }
172}
173
174fn is_single_walk_spec(spec: &str) -> bool {
179 if spec.starts_with('-') {
180 return false;
181 }
182 let mut brace_depth = 0usize;
183 for c in spec.chars() {
184 match c {
185 ':' if brace_depth == 0 => return true,
186 '{' => brace_depth += 1,
187 '}' => brace_depth = brace_depth.saturating_sub(1),
188 c if c.is_whitespace() && brace_depth == 0 => return false,
189 _ => {}
190 }
191 }
192 true
193}
194
195#[cfg(test)]
196mod commit_iter_tests {
197 use super::*;
198 use crate::ident::Signature;
199 use crate::porcelain::commit::{create_commit, CommitRequest};
200 use crate::progress::{NeverCancel, NullProgress};
201 use crate::repo::{InitOptions, Repository};
202
203 fn init_repo_with_commits(count: usize) -> (tempfile::TempDir, Repository) {
204 let dir = tempfile::tempdir().expect("tempdir");
205 let repo = Repository::init(dir.path(), &InitOptions::default()).expect("init");
206 for i in 0..count {
207 let mut req = CommitRequest::new(format!("commit {i}"));
208 req.author = Some(
209 Signature::new("Author", "a@example.com", repo.identity_now()).expect("author"),
210 );
211 req.committer = Some(
212 Signature::new("Committer", "c@example.com", repo.identity_now())
213 .expect("committer"),
214 );
215 req.allow_empty = true;
216 create_commit(&repo, &req, &mut NullProgress, &NeverCancel).expect("commit");
217 }
218 (dir, repo)
219 }
220
221 #[test]
222 fn commits_iter_stops_at_limit_without_full_rev_list() {
223 let (_dir, repo) = init_repo_with_commits(5);
224 let options = RevWalkCommitIterOptions {
225 limit: Some(2),
226 ..RevWalkCommitIterOptions::default()
227 };
228 let ids: Vec<_> = repo
229 .rev_walk()
230 .commits_iter("HEAD", &options)
231 .expect("iter")
232 .collect::<Result<Vec<_>>>()
233 .expect("walk");
234 assert_eq!(ids.len(), 2);
235 }
236
237 #[test]
238 fn commits_iter_since_cutoff_filters_old_commits() {
239 let (_dir, repo) = init_repo_with_commits(4);
240 let newest = repo.rev_walk().resolve("HEAD").expect("head");
241 let params = crate::rev_list::CommitStreamParams {
242 limit: Some(10),
243 ..Default::default()
244 };
245 let all: Vec<_> = open_commit_stream(&repo, std::slice::from_ref(&newest), &[], ¶ms)
246 .expect("stream")
247 .map(|item| item.map(|row| row.oid))
248 .collect::<Result<Vec<_>>>()
249 .expect("walk");
250 assert_eq!(all.len(), 4);
251
252 let future = i64::MAX;
253 let filtered: Vec<_> = open_commit_stream(
254 &repo,
255 std::slice::from_ref(&newest),
256 &[],
257 &crate::rev_list::CommitStreamParams {
258 since: Some(future),
259 ..Default::default()
260 },
261 )
262 .expect("stream")
263 .map(|item| item.map(|row| row.oid))
264 .collect::<Result<Vec<_>>>()
265 .expect("walk");
266 assert!(filtered.is_empty());
267 }
268}