Something went wrong. Try again.
atproto git client
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179//! Issues: everything `atgc issue` does.//!//! A directory rather than a file from the start, because the seam that//! eventually split [`crate::cmd::pr`] into halves is already here and is//! already the same seam. It is worth stating rather than inheriting://!//! - [`mod@read`] answers "what issues are there": `issue list` and//! `issue view`. Every request it makes is public, so it needs no token and//! no session and works for accounts nobody here has ever logged in to. Its//! whole difficulty is that an issue record lives in the PDS of whoever//! *filed* it, so one account's issues are complete and immediate while a//! repo's are scattered across PDSes nothing enumerates. Answering the//! repo question at all therefore means asking an index, which is what//! `--source bobbin` does and why the two halves of a listing carry//! different guarantees.//! - [`mod@write`] changes them: `issue create`, `edit`, `close`, `reopen`//! and `comment`. All of it selects an account, announces which one,//! honours `--dry-run` and lands a record in that account's own PDS. Its//! difficulties are the other kind: which issue the user meant, whether a//! state record this account writes would be honoured by anybody, and not//! clobbering a record something else wrote between the read and the write.//!//! The write half calls into the read half and not the other way round://! `read::classify_issue_ref`, `read::fetch_issue` and `read::state_of` are//! the three it borrows, which is the same direction `pr` settled on and for//! the same reason: naming a thing and reading its state are questions a//! writer has to answer first, and neither needs a session to answer.//!//! # What is not here//!//! Issue *numbers*, image blobs in bodies, `mentions`/`references`, labels,//! and any listing that spans authors. Each is recorded in TODO.md with the//! reason; the first two matter most, because a number is what the web UI//! shows and a cross-author listing is what a maintainer actually wants.
pub(crate) mod read;pub(crate) mod write;
#[cfg(test)]mod fixtures;
/// The `atgc issue` verbs.#[derive(clap::Subcommand, Debug)]pub(crate) enum Command { /// Open an issue on a repo /// /// The issue is a record in your own PDS naming the repo by its DID, so /// this needs no permission on the repo and no membership of anything: /// exactly as `pr create` does not. The repo is the one this checkout's /// `origin` points at unless `--remote` says otherwise. /// /// A body is required, though the lexicon says otherwise: tangled.org's /// ingester drops an issue whose body is empty, so a title-only record /// would federate and never appear. There is no editor: use /// `--body-file -` and a heredoc for a long one. /// /// A local image path in the body: : is /// uploaded to your PDS and embedded in the issue, exactly as in a pull /// body; https:// URLs and blob+at:// URIs pass through untouched. A /// path that names no file, a non-image, or a file over 1 MB is refused /// before anything is sent. /// /// Examples: /// atgc issue create --title 'pr list hangs' --body 'on a 40k-commit repo' /// atgc issue create --title 'typo in README' --body 'second paragraph' /// atgc issue create --title 'it looks like this' --body '' /// atgc issue create --title 'long one' --body-file - <<'EOF' #[command(verbatim_doc_comment)] Create(write::CreateArgs), /// List issues filed by one account /// /// One account's issues, read straight from that account's PDS: yours by /// default, anybody's with `--author`, and no session needed for either. /// Scoped to this checkout's repo unless `--all`. /// /// Bare, this is not "every issue on this repo". An issue record lives in /// the PDS of whoever filed it and nothing enumerates the people who have /// filed against a repo, so that question needs the appview index: /// `--source bobbin` asks it, and the rows come back with their authors, /// their states and a comment count. The index is alpha and its ingest /// stalls, which is why it is opt-in here exactly as it is for `pr list`. /// Your own issues still come from your own PDS and outrank it. /// /// `--json` prints an array of objects. Still no `number`: that one is /// the appview's own id and is in no record and no XRPC response. /// /// Examples: /// atgc issue list /// atgc issue list --source bobbin --state all /// atgc issue list --state all --json | jq -r '.[] | "\(.rkey) \(.title)"' /// atgc issue list --all --author permadeath.com #[command(verbatim_doc_comment)] List(read::ListArgs), /// View an issue /// /// Named by record key or at:// URI: a bare key is looked up in the /// acting account's PDS, or in `--author`'s. A Tangled issue *number* is /// refused rather than guessed at, and the refusal says why: the number /// is the appview's own id and is in no record. /// /// `--comments` adds the discussion. A comment is a record in its /// commenter's PDS and nothing enumerates who has commented, so the /// thread can only come from the appview index: pass `--source bobbin` /// with it, and read what it shows as "what Bobbin has indexed". /// /// Examples: /// atgc issue view 3msg7w7l6hs2x /// atgc issue view 3msg7w7l6hs2x --comments --source bobbin /// atgc issue view at://did:plc:xyz/sh.tangled.repo.issue/3msg7w7l6hs2x /// atgc issue view 3msg7w7l6hs2x --json | jq .state #[command(verbatim_doc_comment)] View(read::ViewArgs), /// Comment on an issue /// /// The comment goes in your own PDS as a `sh.tangled.feed.comment` /// record, so it needs no access to the repo and no permission from /// anyone: unlike `issue close`, Tangled accepts a comment from any /// account. /// /// There is no editor. Use `--body-file -` and a heredoc for a long one. /// A local image path is uploaded and embedded, as in an issue body. /// /// Examples: /// atgc issue comment 3msg7w7l6hs2x --body 'still happens on 0.15' /// atgc issue comment 3msg7w7l6hs2x --body '' /// atgc issue comment 3msg7w7l6hs2x --body-file notes.md #[command(verbatim_doc_comment)] Comment(write::CommentArgs), /// Close an issue /// /// Works on somebody else's issue when you own the repo it was filed on: /// the record this writes is your own, and Tangled decides whose state /// records to honour. /// /// Examples: /// atgc issue close 3msg7w7l6hs2x --dry-run /// atgc issue close at://did:plc:xyz/sh.tangled.repo.issue/3msg7w7l6hs2x #[command(verbatim_doc_comment)] Close(write::StateArgs), /// Reopen a closed issue /// /// Appends an `open` state record rather than deleting the `closed` one: /// state is a log and the newest record wins. /// /// Examples: /// atgc issue reopen 3msg7w7l6hs2x --dry-run /// atgc issue reopen 3msg7w7l6hs2x #[command(verbatim_doc_comment)] Reopen(write::StateArgs), /// Change the title or body of one of your issues /// /// Author-only, unlike `issue close`: the issue record lives in its /// author's PDS and there is no way to write to somebody else's. Neither /// field can be emptied: tangled.org drops an update whose body is /// empty, so clearing one would leave the appview showing the old text /// with nothing saying why. /// /// A new body naming a local image uploads it, and the images the issue /// already embeds stay retained: the record's blob list is merged rather /// than replaced, since an earlier edit's images are still referenced by /// the parts of the body this one did not touch. A body whose text is /// unchanged is still rewritten when it names a local path, because /// sending it is what uploads the file. Edit(write::EditArgs),}
/// Run whichever `issue` verb was parsed.pub(crate) async fn run(command: Command) -> anyhow::Result<()> { match command { Command::Create(args) => write::create(args).await, Command::List(args) => read::list(args).await, Command::View(args) => read::view(args).await, Command::Comment(args) => write::comment(args).await, Command::Close(args) => write::close(args).await, Command::Reopen(args) => write::reopen(args).await, Command::Edit(args) => write::edit(args).await, }}