A last-commits walk reads history 48 commits at a time, within its budget
1 file+23−100/1 viewed
| 19 | 19 | /// Commits whose trees are read together, ahead of the walk: each read is a | |
| 20 | 20 | /// round trip to the store, so reading them one by one is what is slow. | |
| 21 | 21 | const READ_AHEAD: usize = 24; | |
| 22 | + | /// Commits of history read at a time. | |
| 23 | + | const PAGE: u32 = 48; | |
| 22 | 24 | ||
| 23 | 25 | /// Reads trees, remembering those already read: commits share most of them. | |
| 24 | 26 | struct Trees<'a, R: GitRepo> { | |
| 96 | 98 | path: &str, | |
| 97 | 99 | out_of_time: &dyn Fn() -> bool, | |
| 98 | 100 | ) -> 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 { | |
| 101 | 105 | return Ok((Vec::new(), true)); | |
| 102 | 106 | }; | |
| 103 | 107 | let mut trees = Trees { repo, read: HashMap::new() }; | |
| 106 | 110 | let mut open: Vec<String> = current.keys().cloned().collect(); | |
| 107 | 111 | let mut found: Vec<LastCommit> = Vec::new(); | |
| 108 | 112 | 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 | + | } | |
| 112 | 121 | } | |
| 113 | 122 | if index % READ_AHEAD == 0 { | |
| 114 | 123 | if index > 0 && out_of_time() { | |
| 117 | 126 | let ahead = history.iter().skip(index + 1).take(READ_AHEAD).map(|commit| commit.tree_hash.clone()).collect(); | |
| 118 | 127 | trees.prefetch_dirs(ahead, path).await?; | |
| 119 | 128 | } | |
| 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 { | |
| 121 | 131 | // The oldest commit read. If it is the first commit there is, | |
| 122 | 132 | // what is left was added by it. | |
| 123 | 133 | if commit.parents.is_empty() { | |
| 124 | 134 | for name in open.drain(..) { | |
| 125 | − | give(&mut found, name, commit); | |
| 135 | + | give(&mut found, name, &commit); | |
| 126 | 136 | } | |
| 127 | 137 | } | |
| 128 | 138 | break; | |
| 129 | 139 | }; | |
| 140 | + | index += 1; | |
| 130 | 141 | let parent_dir = trees.dir(&parent.tree_hash, path).await?; | |
| 131 | 142 | if parent_dir == dir { | |
| 132 | 143 | continue; | |
| 134 | 145 | let before = trees.entries(parent_dir.as_deref()).await?; | |
| 135 | 146 | let (changed, still): (Vec<String>, Vec<String>) = open.into_iter().partition(|name| before.get(name) != current.get(name)); | |
| 136 | 147 | for name in changed { | |
| 137 | − | give(&mut found, name, commit); | |
| 148 | + | give(&mut found, name, &commit); | |
| 138 | 149 | } | |
| 139 | 150 | open = still; | |
| 140 | 151 | dir = parent_dir; | |
| 175 | 186 | async fn branches(&self) -> Result<Vec<Branch>> { | |
| 176 | 187 | Ok(Vec::new()) | |
| 177 | 188 | } | |
| 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()) | |
| 180 | 193 | } | |
| 181 | 194 | async fn parents(&self, _commit_hash: &str) -> Result<Option<Vec<String>>> { | |
| 182 | 195 | Ok(None) |