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,
    pub fork_when_denied: bool,
    /* private fields */
}

Fields§

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

Publish a new branch from a fork when origin refuses the push.

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. Nothing when they cannot be read.

Source

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

Every submitted review, unfiltered, and an error when GitHub could not be read. The duplicate check before a verdict is posted needs to tell “no reviews” from “no answer”.

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 try_pr_review_comments(&self, number: i64) -> Result<Vec<Value>>

The same, and an error when GitHub could not be read.

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 failure does not stop the remaining replies, but it is included in the final write summary and makes the command return non-zero after that work finishes.

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 branch_for_split(&self, parent: i64, index: usize) -> String

One part of a split, numbered from 1 within its parent.

Its own namespace rather than issue-N, because the parts of a split pull request have no issue of their own and would otherwise collide with the branch of the issue that happens to share the parent’s number.

The name a part is tried on first. worktree_for_split may end up on a suffixed one, because this name is not free forever.

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) -> bool

Source

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

Check an existing PR branch out into an isolated worktree, from wherever target says the branch is published.

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 worktree_for_split( &self, parent: i64, index: usize, start: &str, ) -> Result<(PathBuf, String)>

A worktree for one part of a split, on a new branch off start.

start is the base branch for independent parts and the previous part’s branch for stacked ones, which is the only difference between the two shapes at this level.

The branch is whatever name was free, which is why it is returned rather than derived by the caller. Splitting the same pull request a second time would otherwise target the branch behind the first run’s pull request. Split pushes are create-only and would refuse that target, but a repeated split still needs distinct branches rather than a name that can never be created.

Source

pub fn has_remote_split_branch(&self, parent: i64) -> Result<bool>

Whether a previous attempt pushed any branch for this split.

The parent comment is the normal retry marker. A branch is the fallback when that comment or the pull request creation failed after the push. Reading origin directly makes the guard survive a fresh clone.

Source

pub fn release_split_worktree(&self, dir: &Path, branch: &str)

Throw one part away: its worktree, its branch, and its record.

For a part that would not stand on its own. Nothing has been pushed at that point, so this leaves no trace anywhere but the log. Takes what worktree_for_split returned, since the name it settled on is not derivable from the parent and the index.

Source

pub fn discard_split_worktree( &self, dir: &Path, branch: &str, disposable_head: &str, ) -> bool

Discard one exact mechanical slice that the split workflow just made.

Unlike ordinary release, this intentionally removes an unpushed commit. The caller supplies the exact disposable tip, and every file, worktree, ownership, and ref check must still match before anything is removed.

Source

pub fn release_review_worktree(&self, number: i64)

Source

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

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 commits_since( &self, cwd: &Path, earlier: &str, later: &str, ) -> Option<Vec<String>>

The commits later carries that earlier does not, oldest first, when earlier is genuinely behind it.

None when it is not an ancestor, which is not the same as nothing having landed. rewrite_commits_if_needed rewrites hashes from the first offending commit spar made onward, so a head recorded part way through a round can still be a readable object and no longer be on the branch. git log answers that with every commit on the branch, so without the check the one caller would report the whole branch as unread, which is the widest possible wrong answer.

No base_ref resolution, unlike its neighbours: these are commits rather than branch names, and putting a sha through it logs a fallback line every time.

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 changed_files(&self, cwd: &Path, base: &str) -> Vec<String>

The paths this checkout changes relative to the base, sorted.

A three dot range, matching diff_stat: what the branch did, not what the base has done since.

--no-renames because a rename reported as its destination alone leaves the source out of the list, and a part carrying only the destination would be a copy. As a deletion and an addition it is two paths, which a part can carry together or leave to the leftover report.

-z because without it git writes paths for display: anything non-ASCII comes back escaped and wrapped in quotes, and that string is not a path. A part built from one carries a pathspec matching no file, so the file never reaches the slice while every list still says the part took it. It also keeps a path with a space at either end intact.

Source

pub fn files_changed_between( &self, cwd: &Path, from: &str, to: &str, ) -> Vec<String>

The files one commit range touched, for checking a claim against the diff rather than against the report that made it.

Source

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

Where refname left the base: the commit its own change is measured from, and the one a slice of that change has to be taken against.

Source

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

Source

pub fn rewrite_commits_if_needed( &self, cwd: &Path, base: &str, floor: Option<&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.

floor is the head spar found when it started, and nothing at or below it is rewritten. The style gate is for what spar writes, and a person’s commit message is not that: rewriting one changes a published SHA, which diverges the branch under somebody’s own checkout and is exactly the history rewrite this design promises never to do. A None floor means every commit above the base is spar’s own, which is true of a branch spar created in this invocation and of nothing else.

Source

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

Push by explicit refspec from HEAD, leasing the head spar reviewed.

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.

expected is the remote head spar last read. A bare --force-with-lease leases the implicit tracking ref instead, which is shared across every worktree and is updated by any git fetch. The editing agent is a real CLI with shell access, and fetching is a normal thing for it to do: one git fetch mid round moves the tracking ref to whatever the remote holds and the lease then passes against a head spar never reviewed, which silently drops somebody’s commit. Naming the head makes the push refuse exactly that case.

Source

pub fn push_to( &self, cwd: &Path, target: &PushTarget, branch: &str, expected: Option<&str>, ) -> Result<()>

push, to wherever the branch was first published.

Source

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

Publish a branch created in this invocation, from a fork if origin refuses it.

Only a new branch can move. A pull request that already exists has its branch where it is, and pushing it anywhere else would not update it. A new one has no pull request yet, so a fork is just where it is opened from, which is how anybody without write access contributes.

One write whichever way it goes. The refusal is not counted as a failure when the fork takes the branch: the run did what it set out to, and a failed write makes the exit status say otherwise.

Source

pub fn pr_push_target(&self, pr: &PrView) -> Option<PushTarget>

Where spar can push a pull request’s branch: origin for a branch in this repository, the fork for one on the authenticated user’s own fork, such as one spar opened after origin refused it, and nowhere for anybody else’s.

Source

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

Create one remote branch for a split without ever moving an existing ref.

worktree_for_split chooses a name that is free locally and on origin, but another writer can still take it before the push. An empty expected value in the lease makes this an atomic create: it creates an absent ref, accepts an identical ref as a no-op, and never moves an existing ref. The shared push method cannot be used because its lease permits updating a ref fetched earlier.

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_stdin(&self, args: &[&str], stdin: &str) -> Result<String>

Run gh with something on its stdin.

A tracker body is far too long to pass on argv, and --body-file - is how gh takes one. proc::exec already wires the pipe, so this is a sibling of gh_at rather than anything new.

Source

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

Source

pub fn gh_api_raw( &self, endpoint: &str, headers: &[(&str, &str)], ) -> Result<ApiResponse>

gh api -i: the status line and headers with the body, and the exit code ignored on purpose. A 304 Not Modified, which is the answer a conditional poll wants, makes gh exit 1 while printing exactly what is needed. Only a spawn failure or a timeout is an error here.

Source

pub fn sweep_open_prs(&self) -> Result<Vec<PrSweepRow>>

Every open pull request, with what a sweep needs to know about each.

Source

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

One pull request, open or not, with what a sweep needs to know.

Source

pub fn team_has_member(&self, team: &str, login: &str) -> Option<bool>

Whether login is in a team, named org/slug as gh gives a team in reviewRequests. GitHub answers 404 for a team the login is not in, and a team the viewer is in is one it can always see. None when GitHub did not answer either way.

Source

pub fn review_requests_made(&self, number: i64) -> Option<Vec<ReviewRequested>>

Every review request made on a pull request, oldest first. None when GitHub did not answer.

A review spar posts as a comment leaves the request it answered open, so reviewRequests alone cannot tell that request from one made after the review. When it was made can.

Source

pub fn commits_between(&self, from: &str, to: &str) -> Option<u64>

How many commits lie between two SHAs, when GitHub will say.

Source

pub fn repo_name(&self) -> Option<String>

owner/name, for the line that says what is being watched.

Source

pub fn listen_state_path(&self) -> PathBuf

Source

pub fn listen_lock_path(&self) -> PathBuf

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 read_issue(&self, number: i64) -> Result<Issue>

One issue as it stands, open or closed.

fetch_issues reads a queue to work: it drops a closed issue and fails when nothing survives. Both are wrong for reading one issue back, where closed is an answer and the empty case cannot arise.

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 try_pr_for_branch( &self, branch: &str, base: &str, ) -> Result<Option<PrRef>>

The open pull request for a branch, preserving a failed lookup as an error when the caller is deciding whether a write already landed.

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 try_open_pr_for_issue( &self, issue: i64, base: &str, ) -> Result<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.

An error rather than None when the question could not be asked. The two answers are not interchangeable: “no pull request exists” is the one that makes spar implement over the top of somebody’s work and open a second pull request for the same issue.

Source

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

Source

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

Source

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

The commit currently exposed as a pull request’s head.

Source

pub fn live_head(&self, pr: &PrView) -> Result<LiveHead>

Where a pull request’s head stands now: what GitHub’s pull request names, and, for a branch in this repository, where origin has the branch. The two differ until GitHub catches up with a push.

Source

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

Open a pull request from branch, published at target.

The lookup that confirms it landed goes by branch name alone, which gh pr list --head matches on a fork’s pull request too.

Source

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

Source

pub fn submit_review( &self, number: i64, verdict: Option<ReviewVerdict>, body: &str, commit: &str, ) -> Result<()>

Submit a formal review, or a comment carrying the verdict’s words when GitHub will not take the verdict: the viewer’s own pull request, or no verdict at all.

Mirrors comment_pr step for step: the style gate, the signature, a duplicate check against the viewer’s own reviews, the write, and a read back on failure so an unverifiable write is reported as one.

commit is the commit the reviewers read. GitHub attaches a review that names no commit to whatever the head is when it arrives, so a push made while a review ran would otherwise receive an approval nobody gave it. gh pr review cannot name one, hence the REST call.

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 edit_issue_body( &self, number: i64, expected: &str, body: &str, inserted: &str, ) -> Result<()>

Replace an issue body, refusing unless it is still byte for byte what the caller read and validating only the fragment spar inserted.

The only place spar rewrites text somebody else wrote, so the check is the whole point: an edit computed from a body that has since moved would silently delete whatever moved it. The caller decides whether another attempt is safe for its workflow.

Deliberately not through clean_issue_body. The body is mostly a person’s own prose, and the scrub would rewrite their punctuation while the length budget could truncate the end of a long report. inserted is the only text here spar is answerable for, so it still passes through the style gate. The full body travels over stdin because a tracker can be far too long for one argument.

Source

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

One issue’s body, exactly as GitHub holds it.

Source

pub fn open_issue_rows(&self) -> Vec<Issue>

Every open issue with its title and body, in one call.

For a screen that has to say something about each of twenty items before anything expensive happens. One call rather than one per issue.

Source

pub fn open_pr_rows(&self) -> Vec<PrRow>

Every open pull request with its size, in one call.

Source

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

Source

pub fn create_issue_apart_from( &self, title: &str, body: &str, apart_from: Option<i64>, ) -> 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_similar_issue_apart_from( &self, title: &str, body: &str, apart_from: Option<i64>, ) -> Option<ExistingIssue>

The same search, with one issue that cannot be its own duplicate.

A tracker’s body quotes every item in its checklist, so searching for an item’s words matches the tracker before it matches anything else. That would link an item to the issue it is written in. The search a tracker runs, which wants closed issues too: a checklist item matching a closed issue is a done item that wants a link.

Source

pub fn try_find_similar_issue_apart_from( &self, title: &str, body: &str, apart_from: Option<i64>, ) -> Result<Option<ExistingIssue>>

The search a filing runs, over open issues only.

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 for the remaining workflow. A failure does not discard the review or stop later independent work, but the final write summary reports it and the command returns non-zero.

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 merge_pr_at_head( &self, number: i64, expected_head: &str, delete_branch: bool, ) -> Result<()>

Squash merge only if the pull request still exposes the reviewed head.

Source

pub fn followups_path(&self) -> PathBuf

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

Source

pub fn brainstorms_dir(&self) -> PathBuf

Where spar brainstorm writes a session unless told otherwise.

Under the state directory, so it is kept out of the repository’s own git status; --out puts one somewhere a person would commit.

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 spend_path(&self) -> PathBuf

What spar checkin has already answered on one pull request or issue. Where what a run spent is kept.

Source

pub fn record_spend(&self, calls: &[Spent]) -> Result<()>

Append this run’s calls to the record.

Appended rather than replaced, so a resumed pull request adds to the count of what it has cost so far rather than starting it again, and so anybody charting this has a file to chart.

Source

pub fn implementors_path(&self) -> PathBuf

Who wrote the first draft of each issue, by number.

Custody alternates within a pull request and the record of it lives in that pull request’s state. This is the other question, which nothing answered: over a queue, which model authored what.

Source

pub fn read_implementors(&self) -> BTreeMap<i64, String>

Source

pub fn remember_implementor(&self, issue: i64, agent: &str)

Best effort: this is a record for a person, not something the loop reads back, so a failure to write it is not a reason to stop.

Source

pub fn contested_path(&self) -> PathBuf

Where the issues triage disagreed about are remembered.

One file rather than one per issue: it is read once per run, before any triage call, to decide what not to ask about again.

Source

pub fn read_contested(&self) -> BTreeMap<i64, Contested>

The issues a person has been asked to decide, by number.

Source

pub fn remember_contested(&self, items: &[ContestedItem])

Remember an issue triage could not agree on.

Best effort: a run that cannot write this asks again next time, which is what it did before this existed.

Source

pub fn forget_contested(&self, numbers: &[i64])

Ask about them again, because a person has acted on them.

Source

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

Source

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

Where spar review records what it reviewed, and a second look reads it.

Source

pub fn read_review_record(&self, number: i64) -> Option<ReviewRecord>

None when there is no record, or when it cannot be read. A record spar cannot read is one it reviews afresh over, and the log says so.

Source

pub fn write_review_record( &self, number: i64, record: &ReviewRecord, ) -> Result<()>

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.

Nothing when they cannot be read, which suits a reader that is going to go on regardless. A caller deciding whether it has already written here wants try_issue_comments, since for that one no comments and no answer are opposite answers.

Source

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

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: the custody loop’s state, the review record, and the check-in watermark alike. Grouped by number so each finished pull request costs one gh read however many files it left.

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, !>

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.