Skip to main content

Repo

Struct Repo 

Source
pub struct Repo {
    pub style: Style,
    pub branch_prefix: String,
    pub state_store: StateStore,
    pub followups: Followups,
    pub drafts: Drafts,
    /* private fields */
}

Fields§

§style: Style§branch_prefix: String§state_store: StateStore§followups: Followups§drafts: Drafts

Implementations§

Source§

impl Repo

Source

pub fn review_threads(&self, number: i64) -> Result<Vec<RawThread>>

Inline review threads, with GitHub’s own resolved flag.

-F number= and not -f: -F converts a bare integer to a JSON number, which is what Int! requires, while -f would send the string “478” and the server would reject the whole query. -F owner={owner} takes the placeholder from the checkout, so this works against any host with no host handling of its own.

--paginate works because the query declares $endCursor and returns pageInfo, and each page arrives as its own JSON document, which is the shape parse_comment_pages already flattens.

Source

pub fn pr_reviews(&self, number: i64) -> Vec<Value>

Submitted review bodies. There is no thread to reply into, so these can only ever be answered with a comment on the pull request.

A PENDING review was never submitted and nobody else can see it. A DISMISSED one has been withdrawn. An empty body is every approval that came with only inline comments, which the threads already carry.

Source

pub fn pr_review_comments(&self, number: i64) -> Vec<Value>

Inline comments without their threads. The fallback for a host where the GraphQL query will not run.

Source

pub fn reply_in_thread(&self, pr: i64, root: i64, body: &str) -> Result<()>

Reply inside an inline review thread.

REST and not GraphQL, deliberately. addPullRequestReviewThreadReply needs the thread’s node id, which only the GraphQL read produces, while this needs the id of the comment that started the thread, which spar has on either path. So replying keeps working on a host where reading the threads did not.

Source

pub fn resolve_thread(&self, thread_id: &str) -> Result<()>

Mark a review thread resolved.

GraphQL only: REST has never exposed it. A token that cannot write to the repository cannot do this, which is not a reason to fail a run that has already said its piece, so the caller logs and carries on.

Source§

impl Repo

Source

pub fn open(root: impl AsRef<Path>, cfg: &Config) -> Result<Self>

Source

pub fn root(&self) -> &Path

Source

pub fn clean(&self, text: &str) -> Result<String>

Scrub, then verify. A leak here reaches GitHub, so it is a hard error rather than a warning: silent partial compliance is how a style rule erodes over a long run.

Source

pub fn clean_body(&self, text: &str) -> Result<String>

Clean, and hold to a length budget. For anything a model wrote.

Source

pub fn clean_issue_body(&self, text: &str) -> Result<String>

The same, with an issue’s far larger budget and its exemption for code.

Source

pub fn clean_title(&self, text: &str) -> Result<String>

The single transform every outbound title goes through.

Scrub first, clip second, and never the other way round. Clipping first lets the scrub lengthen the result past the budget (an em dash becomes two characters), so a second pass would clip again and produce a different string. That broke follow-up deduplication silently: the lookup searched for one title while GitHub had stored another, no match was ever found, and a fresh duplicate issue was filed every review round. Doing it in this order makes the transform idempotent, which the tests assert.

Source

pub fn git(&self, args: &[&str]) -> Result<String>

Source

pub fn git_at(&self, cwd: Option<&Path>, args: &[&str]) -> Result<String>

Source

pub fn git_try(&self, args: &[&str]) -> String

Run git, tolerating failure. Returns whatever landed on stdout.

Source

pub fn git_try_at(&self, cwd: Option<&Path>, args: &[&str]) -> String

Source

pub fn default_branch(&self, configured: &str) -> String

The base branch the remote actually points at, rather than assuming main. Falls back to the configured value when there is no origin.

Source

pub fn branch_for_issue(&self, issue: i64) -> String

Source

pub fn branch_for_pr(&self, number: i64) -> String

Source

pub fn known_branches(&self) -> BTreeMap<String, BranchRecord>

Source

pub fn record_branch(&self, branch: &str, kind: &str, number: i64)

Source

pub fn forget_branch(&self, branch: &str)

Source

pub fn worktree_add(&self, issue: i64, base: &str) -> Result<(PathBuf, String)>

Isolate an issue so a failed run cannot poison the next one’s base.

Source

pub fn commits_held_by(&self, branch: &str, base: &str, other: &str) -> bool

Whether other already contains every commit branch has beyond base. False when either ref fails to resolve, so a ref that is not there cannot vouch for anything.

Source

pub fn worktree_remove(&self, issue: i64)

Source

pub fn worktree_for_pr(&self, pr: &PrView) -> Result<(PathBuf, String)>

Check an existing PR branch out into an isolated worktree.

Source

pub fn worktree_for_pr_head(&self, number: i64) -> Result<PathBuf>

Check a pull request’s head out read only, detached, with no branch.

Fetches refs/pull/N/head, which GitHub serves for every pull request including one from a fork whose branch is not in this repository at all. That is what makes reviewing an outside contribution possible when pushing to it is not.

Detached on purpose. Review only mode has nothing to push, and a branch would only invite something to try.

Source

pub fn release_review_worktree(&self, number: i64)

Source

pub fn release_pr_worktree(&self, number: i64)

Source

pub fn base_ref(&self, cwd: &Path, base: &str) -> String

What to diff against: the remote tracking branch when it resolves, the local branch when it does not.

This is not a nicety. Every “did the agent do anything” check hangs off this ref, and git log against a ref that does not exist fails silently and reads as “no commits”. A checkout whose origin/main was never fetched would report every implementation as abandoned and throw the work away.

Source

pub fn has_changes(&self, cwd: &Path, base: &str) -> bool

Source

pub fn commit_count(&self, cwd: &Path, refname: &str, base: &str) -> usize

How many commits refname carries that the base does not.

Counted from the commits themselves rather than from commit_subjects, which drops a commit whose message is empty. The guards in worktree_add decide whether to delete a branch on this number, and an empty message must not read as an empty branch.

Source

pub fn commit_lines(&self, cwd: &Path, refname: &str, base: &str) -> Vec<String>

One hash subject line per commit refname carries that the base does not, oldest first. For showing a person what is on a branch, so the hash keeps a commit with no message from listing as nothing.

Source

pub fn commit_subjects( &self, cwd: &Path, refname: &str, base: &str, ) -> Vec<String>

The subjects of the commits refname carries that the base does not, oldest first.

Source

pub fn diff_stat(&self, cwd: &Path, base: &str) -> String

Source

pub fn rewrite_commits_if_needed(&self, cwd: &Path, base: &str) -> Result<()>

Scrub commit messages that slipped past the prompt.

git filter-branch calls back into this same binary, so there is no interpreter to find and no second copy of the rules to drift.

Source

pub fn push(&self, cwd: &Path, branch: &str) -> Result<()>

Push by explicit refspec from HEAD.

A resumed PR is checked out under a local name (pr-N) that does not match its remote branch, so pushing by branch name would resolve the wrong local ref or fail outright.

Source

pub fn gh(&self, args: &[&str]) -> Result<String>

Source

pub fn gh_at(&self, cwd: Option<&Path>, args: &[&str]) -> Result<String>

Source

pub fn gh_try(&self, args: &[&str]) -> String

Source

pub fn viewer_login(&self) -> Result<&str>

The login gh is authenticated as.

A hard error, never a degradation. Everything spar wrote has to be excluded from what it answers, and custody cannot be read from git authorship, so this is the only thing that tells spar’s own comments from somebody else’s. Without it the failure is not “answers a bit too much”, it is a thread where spar answers itself until somebody notices.

Not cached on disk: gh auth switch between runs would make a stored answer wrong in exactly the way that produces that thread.

Source

pub fn fetch_issues(&self, numbers: &[i64]) -> Result<Vec<Issue>>

Source

pub fn list_open_issues( &self, limit: usize, min_number: i64, ) -> Result<Vec<i64>>

Open issues, lowest numbered first. gh issue list excludes PRs.

Source

pub fn list_open_prs(&self, limit: usize, min_number: i64) -> Result<Vec<i64>>

Source

pub fn pr_for_branch(&self, branch: &str) -> Option<PrRef>

Source

pub fn item_kind(&self, number: i64) -> Result<ItemKind>

Whether a number names an issue or a pull request.

gh issue view happily returns a pull request when handed its number, so it cannot be used to tell them apart. The issues API carries both and marks a pull request with a pull_request key, which is definitive.

Source

pub fn open_pr_for_issue(&self, issue: i64) -> Option<PrRef>

An open pull request that would close this issue, whoever opened it.

spar’s own branch naming is checked first because it is exact and cheap. Falling back to GitHub’s own issue linkage is what lets spar pick up a pull request a person started on a branch named anything at all.

Source

pub fn pr_view(&self, number: i64) -> Result<PrView>

Source

pub fn pr_state(&self, number: i64) -> String

Source

pub fn create_pr( &self, cwd: &Path, branch: &str, base: &str, title: &str, body: &str, ) -> Result<PrRef>

Source

pub fn comment_pr(&self, number: i64, body: &str) -> Result<()>

Source

pub fn comment_issue(&self, number: i64, body: &str) -> Result<()>

Source

pub fn close_issue(&self, number: i64, body: &str) -> Result<()>

Comment, then close as not planned.

Only ever called when both agents independently declined the issue: one agent’s opinion is not enough to close somebody’s report.

Source

pub fn create_issue(&self, title: &str, body: &str) -> Result<String>

Source§

impl Repo

Source

pub fn find_similar_issue( &self, title: &str, body: &str, ) -> Option<ExistingIssue>

An issue that already describes this defect, however it was worded.

Exact title matching let duplicates through: two agents, or two runs a week apart, never word one defect identically. A real run filed two duplicates that way, and each had to be closed by hand afterwards. Titles alone are too thin to match on, so this compares titles and bodies together.

Source

pub fn find_issue_by_title(&self, title: &str) -> Option<String>

Avoid filing a duplicate when a follow-up already exists.

Source

pub fn mark_ready(&self, number: i64) -> bool

Squash merge, tolerating cleanup failures after a successful merge.

Take a pull request out of draft, once the review has converged.

Best effort. A draft that stayed a draft is a cosmetic problem, and failing the run over it would throw away a review that has already finished and been posted.

Source

pub fn merge_pr(&self, number: i64) -> Result<()>

gh pr merge --delete-branch exits non-zero when it cannot delete the local branch, which happens after the merge has already landed. Treating that as a failure reports work as lost when it is not.

Source

pub fn followups_path(&self) -> PathBuf

The queue of follow-ups recorded locally rather than filed, which spar followup works.

Source

pub fn worked_followups_path(&self) -> PathBuf

What spar followup already dealt with, kept beside the queue.

Two jobs. It is what stops append_local_followup re-recording a follow-up whose entry has since left the queue, which would otherwise turn the file into a ring buffer of things already filed. And it keeps the text of an entry a screening pass ruled stale, so a wrong verdict costs a re-read rather than the only copy of a real defect.

Source

pub fn checkin_state_path(&self, number: i64) -> PathBuf

What spar checkin has already answered on one pull request or issue.

Source

pub fn append_local_followup(&self, title: &str, body: &str) -> Followup

Append a follow-up to a local note instead of the tracker.

Deduplicated on the title, matching the issue path. The body arrives with its provenance already stamped by the caller, so nothing is added here.

A write that did not happen is reported as such rather than as a duplicate: the caller settles the point on the strength of this answer, and settling it on a failed write is how a real defect is lost.

Both files are checked, because spar followup removes an entry from the queue once it has filed it. Checking only the queue would let the next run that rediscovers the same defect append it again, on top of the issue that now exists for it.

Source

pub fn archive_followup(&self, title: &str, body: &str, verdict: &str)

Record what spar followup did with an entry, and why.

Best effort: an archive that could not be written is not a reason to stop, since the entry has already been filed or ruled on.

Source

pub fn pending_comment_path(&self, number: i64) -> PathBuf

Where a comment spar produced but did not post is kept.

Source

pub fn save_pending_comment(&self, number: i64, text: &str) -> Result<PathBuf>

Keep a comment spar decided not to post.

A dry run that prints and forgets means agreeing with what you read costs a second full review. Saving it makes the whole point of reading it first: look, edit if you like, then post what you already paid for.

Source

pub fn read_pending_comment(&self, number: i64) -> Option<String>

Source

pub fn state_path(&self, number: i64) -> PathBuf

Source

pub fn read_state(&self, pr: &PrView) -> Option<PersistedState>

Source

pub fn write_state(&self, number: i64, state: &PersistedState) -> Result<()>

Source

pub fn issue_comments(&self, number: i64) -> Vec<Value>

Top level comments. Works for issues and pull requests alike, because GitHub serves both from the issues endpoint.

Source

pub fn clear_state(&self, number: i64)

Drop state once the PR is finished and there is nothing to resume.

Source

pub fn prune_state(&self) -> Vec<String>

Remove state files whose PR is merged or closed.

Source

pub fn prune_pr_state(&self, numbers: Option<Vec<i64>>) -> Vec<String>

Delete state comments from PRs that are finished.

Open PRs are left alone: their state may still be live.

Source

pub fn prune_worktrees(&self, force_all: bool) -> Vec<String>

Drop worktrees whose PR is finished, then the branches they left behind.

With auto_merge off, which is the default, a run ends at “approved”, so nothing would ever clean these up on its own and they accumulate one per run. A stranded worktree also holds its branch checked out, which makes a later gh pr merge --delete-branch fail to clean up.

Source

pub fn prune_branches(&self, force_all: bool) -> Vec<String>

Delete leftover branches spar created whose worktree is already gone.

Deletion is driven by the ledger of branches spar actually created, not by a name pattern. Names default to issue-N, which is exactly what a person would call a branch themselves, so a name alone can never establish ownership. This is the data loss guard.

Trait Implementations§

Source§

impl Debug for Repo

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !Freeze for Repo

§

impl RefUnwindSafe for Repo

§

impl Send for Repo

§

impl Sync for Repo

§

impl Unpin for Repo

§

impl UnsafeUnpin for Repo

§

impl UnwindSafe for Repo

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.