Scan performs a scan of all subscribed repos and computes deltas against the previously emitted snapshots.
(ctx context.Context)
| 149 | // Scan performs a scan of all subscribed repos and computes deltas |
| 150 | // against the previously emitted snapshots. |
| 151 | func (h *Handler) Scan(ctx context.Context) *codersdk.WorkspaceAgentGitServerMessage { |
| 152 | if !h.gitAvailable() { |
| 153 | return nil |
| 154 | } |
| 155 | |
| 156 | h.mu.Lock() |
| 157 | roots := make([]string, 0, len(h.repoRoots)) |
| 158 | for r := range h.repoRoots { |
| 159 | roots = append(roots, r) |
| 160 | } |
| 161 | h.mu.Unlock() |
| 162 | |
| 163 | if len(roots) == 0 { |
| 164 | return nil |
| 165 | } |
| 166 | |
| 167 | now := h.clock.Now().UTC() |
| 168 | var repos []codersdk.WorkspaceAgentRepoChanges |
| 169 | |
| 170 | // Perform all I/O outside the lock to avoid blocking |
| 171 | // AddPaths/GetPaths/Subscribe callers during disk-heavy scans. |
| 172 | type scanResult struct { |
| 173 | root string |
| 174 | changes codersdk.WorkspaceAgentRepoChanges |
| 175 | err error |
| 176 | } |
| 177 | results := make([]scanResult, 0, len(roots)) |
| 178 | for _, root := range roots { |
| 179 | changes, err := getRepoChanges(ctx, h.logger, h.gitBin, root) |
| 180 | results = append(results, scanResult{root: root, changes: changes, err: err}) |
| 181 | } |
| 182 | |
| 183 | // Re-acquire the lock only to commit snapshot updates. |
| 184 | h.mu.Lock() |
| 185 | defer h.mu.Unlock() |
| 186 | |
| 187 | for _, res := range results { |
| 188 | if res.err != nil { |
| 189 | if isRepoDeleted(h.gitBin, res.root) { |
| 190 | // Repo root or .git directory was removed. |
| 191 | // Emit a removal entry, then evict from watch set. |
| 192 | removal := codersdk.WorkspaceAgentRepoChanges{ |
| 193 | RepoRoot: res.root, |
| 194 | Removed: true, |
| 195 | } |
| 196 | delete(h.repoRoots, res.root) |
| 197 | delete(h.lastSnapshots, res.root) |
| 198 | repos = append(repos, removal) |
| 199 | } else { |
| 200 | // Transient error — log and skip without |
| 201 | // removing the repo from the watch set. |
| 202 | h.logger.Warn(ctx, "scan repo failed", |
| 203 | slog.F("root", res.root), |
| 204 | slog.Error(res.err), |
| 205 | ) |
| 206 | } |
| 207 | continue |
| 208 | } |