Commit

A last-commits walk reads history 48 commits at a time, within its budget

syntaqxcommitted Parentdf5ecceBrowse files
1 file+23−100/1 viewed
+23−10
1919 /// Commits whose trees are read together, ahead of the walk: each read is a
2020 /// round trip to the store, so reading them one by one is what is slow.
2121 const READ_AHEAD: usize = 24;
22+/// Commits of history read at a time.
23+const PAGE: u32 = 48;
2224
2325 /// Reads trees, remembering those already read: commits share most of them.
2426 struct Trees<'a, R: GitRepo> {
9698 path: &str,
9799 out_of_time: &dyn Fn() -> bool,
98100 ) -> Result<(Vec<LastCommit>, bool)> {
99− let history = repo.log(git_ref, MAX_COMMITS).await?;
100− let Some(head) = history.first() else {
101+ // History a page at a time, so a walk that is out of time stops
102+ // between pages rather than after reading all of it.
103+ let mut history = repo.log(git_ref, PAGE).await?;
104+ let Some(head) = history.first().cloned() else {
101105 return Ok((Vec::new(), true));
102106 };
103107 let mut trees = Trees { repo, read: HashMap::new() };
106110 let mut open: Vec<String> = current.keys().cloned().collect();
107111 let mut found: Vec<LastCommit> = Vec::new();
108112 let give = |found: &mut Vec<LastCommit>, name: String, commit: &Commit| found.push(LastCommit { name, commit: commit.clone() });
109− for (index, commit) in history.iter().enumerate() {
110− if open.is_empty() {
111− break;
113+ let mut index = 0;
114+ while !open.is_empty() && index < history.len() {
115+ // Read the next page once the walk reaches the end of this one.
116+ if index + 1 >= history.len() && history.len() < MAX_COMMITS as usize && !out_of_time() {
117+ if let Some(parent) = history.last().and_then(|commit| commit.parents.first()).cloned() {
118+ let more = repo.log(&parent, PAGE).await?;
119+ history.extend(more);
120+ }
112121 }
113122 if index % READ_AHEAD == 0 {
114123 if index > 0 && out_of_time() {
117126 let ahead = history.iter().skip(index + 1).take(READ_AHEAD).map(|commit| commit.tree_hash.clone()).collect();
118127 trees.prefetch_dirs(ahead, path).await?;
119128 }
120− let Some(parent) = history.get(index + 1) else {
129+ let commit = history[index].clone();
130+ let Some(parent) = history.get(index + 1).cloned() else {
121131 // The oldest commit read. If it is the first commit there is,
122132 // what is left was added by it.
123133 if commit.parents.is_empty() {
124134 for name in open.drain(..) {
125− give(&mut found, name, commit);
135+ give(&mut found, name, &commit);
126136 }
127137 }
128138 break;
129139 };
140+ index += 1;
130141 let parent_dir = trees.dir(&parent.tree_hash, path).await?;
131142 if parent_dir == dir {
132143 continue;
134145 let before = trees.entries(parent_dir.as_deref()).await?;
135146 let (changed, still): (Vec<String>, Vec<String>) = open.into_iter().partition(|name| before.get(name) != current.get(name));
136147 for name in changed {
137− give(&mut found, name, commit);
148+ give(&mut found, name, &commit);
138149 }
139150 open = still;
140151 dir = parent_dir;
175186 async fn branches(&self) -> Result<Vec<Branch>> {
176187 Ok(Vec::new())
177188 }
178− async fn log(&self, _git_ref: &str, limit: u32) -> Result<Vec<Commit>> {
179− Ok(self.history.iter().take(limit as usize).cloned().collect())
189+ async fn log(&self, git_ref: &str, limit: u32) -> Result<Vec<Commit>> {
190+ // A branch name starts at the head; a hash at that commit; anything else is unknown.
191+ let start = if git_ref == "main" { Some(0) } else { self.history.iter().position(|commit| commit.hash == git_ref) };
192+ Ok(start.map(|start| self.history.iter().skip(start).take(limit as usize).cloned().collect()).unwrap_or_default())
180193 }
181194 async fn parents(&self, _commit_hash: &str) -> Result<Option<Vec<String>>> {
182195 Ok(None)