Skip to content

Remote configuration improvements - #416

Open
helq wants to merge 7 commits into
sashiko-dev:mainfrom
helq:upstream/remote-config
Open

Remote configuration improvements#416
helq wants to merge 7 commits into
sashiko-dev:mainfrom
helq:upstream/remote-config

Conversation

@helq

@helq helq commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

On top of #415

Commits 7f947df and 82864af.

Two highly correlated changes:

Support map-format deserialization: allow custom_remotes in Settings.toml to be specified as either a TOML array ([[...]]) or as indexed environment variables (CUSTOM_REMOTES__0__URL), matching the pattern used by the config crate.

Add branch_patterns for selective fetching: when a custom remote has thousands of branches, fetching all refs downloads millions of objects and saturates the pod for 30 minutes. branch_patterns accepts glob patterns (e.g., /6.18, 14) so git fetch only downloads matching refs. The same patterns filter baseline branch candidates.

@rgushchin

Copy link
Copy Markdown
Member

🤖 AI Code Review (Sashiko / Jetski)

Here is a review of PR #416 (remote-config):


📋 Summary

The PR introduces map-format deserialization for custom_remotes and adds branch_patterns for selective refspec fetching. While the deserialization pattern and glob-based branch candidate filtering are solid ideas, there are critical regression and performance risks that should be addressed before merging.


🚨 1. Critical Regression Risks

1.1 Complete Removal of git fetch Timeout (src/git_ops.rs)

  • Issue: The original implementation wrapped the git fetch invocation (and retry) in tokio::time::timeout(timeout_duration, fetch_future) (30 min for initial, 5 min for routine updates). In PR Remote configuration improvements #416, tokio::time::timeout was removed entirely from ensure_remote_with_refspecs.
  • Impact: If an external remote hangs, stalls on SSL/SSH negotiation, or suffers network partitioning, fetch_cmd.output().await will block indefinitely. Because ensure_remote is called during server boot (main.rs), candidate baseline resolution (baseline.rs), and periodic sync (sync.rs), any unresponsive remote will hang the entire server or worker loop indefinitely.
  • Fix: Re-introduce tokio::time::timeout around fetch_cmd.output().await and retry_cmd.output().await.

1.2 Fetch Caching Permanently Bypassed for Selective Remotes (src/git_ops.rs)

  • Issue: In ensure_remote_with_refspecs, should_fetch is determined by:
    let head_exists = Command::new("git").args(["show-ref", "--verify", "-q", &head_ref])...;
    let should_fetch = if just_added || !head_exists || force_fetch { true } else { ... age check ... };
    When selective refspecs are fetched (or when HEAD is not among the fetched refs), refs/remotes/<name>/HEAD is never created, and git remote set-head <name> --auto fails.
  • Impact: head_exists evaluates to false on every invocation. As a result, the 1-hour timestamp freshness check is completely bypassed, forcing a synchronous network git fetch on every call to BaselineRegistry::get_candidates for every patchset processed.
  • Fix: Avoid treating !head_exists as a mandatory trigger to fetch when a valid, fresh timestamp file exists in .sashiko/fetch_timestamps/.

⚠️ 2. Architectural & Functional Concerns

2.1 Unfiltered Full Fetches on Startup and in Sync Worker

  • Startup (src/main.rs:856): Server startup iterates over custom_remotes and calls ensure_remote(&repo_path, &remote.name, &remote.url, false) without passing branch_patterns (passing refspecs = None). This runs git fetch --prune --no-tags <remote>, downloading all thousands of branches on every server restart.
  • Sync Worker (src/worker/sync.rs:77): GitSyncWorker lists remotes via git remote and calls ensure_remote with refspecs = None, triggering an unfiltered fetch of all branches every hour.
  • Reviewer Setup (src/reviewer.rs:826): On initial remote setup in Reviewer, ensure_remote is called with refspecs = None.
  • Impact: The primary goal—preventing the download of thousands of branches and millions of objects—is defeated on server boot and hourly sync.
  • Fix: Thread branch_patterns (or refspecs) into main.rs, GitSyncWorker, and Reviewer when calling ensure_remote.

2.2 Git Refspec Incompatibility with Custom Glob Syntax (src/git_ops.rs vs src/baseline.rs)

  • Issue: glob_match supports ? and arbitrary multi-* patterns. However, BaselineRegistry converts patterns directly to refspecs:
    format!("+refs/heads/{}:refs/remotes/{}/{}", p, remote.name, p)
  • Impact: Git refspecs do not support ? or multiple * wildcards. If a user sets a pattern like v?.? or *6.18*, git fetch fails immediately with fatal: invalid refspec, causing ensure_remote_with_refspecs to fail and branches to be ignored.
  • Fix: Validate that branch_patterns contains valid Git refspec patterns (single * wildcard only, no ?), or document and sanitize the pattern input.

💡 3. Code Quality & Maintainability

3.1 Deduplicate Custom Deserializer Visitors (src/settings.rs)

  • There are now three nearly identical custom visitors for indexed maps vs sequences:
    • OptVecVisitor (deserialize_optional_string_vec)
    • CustomRemotesVisitor (deserialize_custom_remotes)
    • IndexedVecVisitor<T> (deserialize_indexed_vec)
  • Since IndexedVecVisitor<T> is generic over T: Deserialize, it can be reused or adapted to handle Option<Vec<T>>, eliminating ~100 lines of boilerplate.

3.2 Update Documentation

  • Consider updating docs/configuration.md to document the new branch_patterns configuration option under [[git.custom_remotes]] along with examples and supported pattern syntax.

helq added 7 commits August 21, 2026 15:48
Add a submitted_at field to Event::RawMboxSubmitted, stamped with
the server's current time when a raw mbox is submitted via POST
/api/submit (Inject variant). This timestamp is propagated through
metadata.received_date and used as the patchset date instead of
the email's Date: header.

This prevents stale mbox timestamps (which can be arbitrarily far
in the past) from skewing queue ordering. The original email date
is preserved in the messages table for display purposes.

The Thread and Remote submission paths are unaffected as they
already use server-side timestamps via create_fetching_patchset().

Signed-off-by: Elkin Cruz <elkin@google.com>
Define PriorityRule and CompiledPriorityRule structs for regex-based
patchset priority classification. Add a custom serde deserializer
(deserialize_indexed_vec) to handle both TOML array and env-var
indexed-map representations. Add the priority_rules field to
ReviewSettings with serde(default) so existing configs are
unaffected.

Signed-off-by: Elkin Cruz <elkin@google.com>
Add a priority INTEGER DEFAULT 500 column to the patchsets table
and a composite index idx_patchsets_status_priority_date on
(status, priority DESC, date ASC) for efficient priority-ordered
queries.

The column defaults to 500 so existing patchsets are unaffected.

Signed-off-by: Elkin Cruz <elkin@google.com>
Add migration to create the priority column and composite index.
Introduce create_patchset_with_priority() which threads an explicit
priority through all INSERT/UPDATE paths, with MIN(priority, ?)
semantics to preserve manual deprioritization. Refactor
create_patchset() to delegate with default None.

Change get_pending_patchsets() ordering to priority DESC, date ASC.

Add calculate_priority() for evaluating compiled regex rules against
subjects (last match wins).

Signed-off-by: Elkin Cruz <elkin@google.com>
Compile priority_rules from settings at startup and thread them
through the DB worker into process_parsed_article(). Use
calculate_priority() to compute priority from the patchset subject
before calling create_patchset_with_priority().

API callers pass None for priority to create_fetching_patchset().

Signed-off-by: Elkin Cruz <elkin@google.com>
The config crate deserializes custom_remotes environment variables
SASHIKO__GIT__CUSTOM_REMOTES__* as a map with numeric string keys
instead of a sequence. This caused a deserialization failure when
attempting to parse into a Vec structure.

Replace the untagged enum deserialization strategy with a custom
Visitor pattern in settings.rs. This Visitor successfully handles both
sequential TOML/JSON arrays and indexed map formats, preserving type
coercion for nested fields.

Signed-off-by: Elkin Cruz <elkin@google.com>
When a custom remote has thousands of branches (e.g. icebreaker
with 4,467 branches), fetching all refs downloads millions of
objects and saturates the pod for hours.

Add a branch_patterns field to CustomRemoteSettings that accepts
glob patterns (e.g. "*/6.18", "14*"). When configured:

1. ensure_remote builds refspecs from patterns so git fetch only
   downloads matching refs instead of all branches
2. check_all_branches filters the branch list through the same
   patterns before adding baseline candidates

Implement a simple glob matcher supporting "*" (any substring)
and "?" (any single character) in git_ops, avoiding an external
dependency for this small use case.

Signed-off-by: Elkin Cruz <elkin@google.com>
@helq
helq force-pushed the upstream/remote-config branch from 82864af to 0906d97 Compare August 21, 2026 18:38
@helq

helq commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

After a couple of rounds of AI reviews and subsequent fixes, I present to you the new code. It has been hardened and tested in production. Thanks for the review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants