crates/cli/git-ents/src/commands/issue.rs
issue.rshistorycomment on this file
| 1 | //! `git ents issue`: a thin wrapper around `ents_forge::issue`'s business |
| 2 | //! logic — the `$GIT_EDITOR` fallback for an omitted `--title` lives in |
| 3 | //! [`crate::compose`], driven by the `ents::compose` attributes on |
| 4 | //! [`ents_forge::issue::IssueAction`], not here (`lens.parity`). |
| 5 | |
| 6 | use std::path::PathBuf; |
| 7 | |
| 8 | use ents_forge::Issue; |
| 9 | use ents_forge::issue::{self, EditIssue, NewIssue}; |
| 10 | use ents_model::MemberId; |
| 11 | use ents_receive::Identity; |
| 12 | |
| 13 | use super::{actor, signer}; |
| 14 | use crate::error::Result; |
| 15 | use crate::mutate::outcome_to_result; |
| 16 | use crate::root::LocalRoot; |
| 17 | |
| 18 | /// `git ents issue list`: every issue recorded in this repository. |
| 19 | /// |
| 20 | /// # Errors |
| 21 | /// |
| 22 | /// Propagates a ref-store or object read failure. |
| 23 | pub fn list(root: &LocalRoot) -> Result<Vec<(String, Issue)>> { |
| 24 | Ok(issue::list(&root.refs, &root.objects)?) |
| 25 | } |
| 26 | |
| 27 | /// `git ents issue show`: `id`'s issue. |
| 28 | /// |
| 29 | /// # Errors |
| 30 | /// |
| 31 | /// [`crate::error::Error::Forge`] (wrapping [`ents_forge::Error::NotFound`]) |
| 32 | /// if `id` has no issue ref. |
| 33 | pub fn show(root: &LocalRoot, id: &str) -> Result<Issue> { |
| 34 | Ok(issue::show(&root.refs, &root.objects, id)?) |
| 35 | } |
| 36 | |
| 37 | /// `git ents issue new`: create an issue. |
| 38 | /// |
| 39 | /// # Errors |
| 40 | /// |
| 41 | /// See [`crate::mutate::outcome_to_result`]. |
| 42 | pub fn new( |
| 43 | root: &LocalRoot, |
| 44 | title: String, |
| 45 | body: String, |
| 46 | state: String, |
| 47 | labels: Vec<String>, |
| 48 | assignees: Vec<String>, |
| 49 | key: Option<PathBuf>, |
| 50 | ) -> Result<String> { |
| 51 | let signer = signer(root, key)?; |
| 52 | let identity = Identity { |
| 53 | actor: actor(&signer), |
| 54 | author: None, |
| 55 | sign: &|payload| signer.sign(payload), |
| 56 | }; |
| 57 | let new = NewIssue { |
| 58 | title, |
| 59 | body, |
| 60 | state, |
| 61 | assignees: assignees.into_iter().map(MemberId::new).collect(), |
| 62 | labels, |
| 63 | }; |
| 64 | let (id, outcome) = issue::new( |
| 65 | &root.refs, |
| 66 | &root.objects, |
| 67 | &root.events, |
| 68 | new, |
| 69 | &identity, |
| 70 | root.mode(), |
| 71 | )?; |
| 72 | outcome_to_result(outcome, None)?; |
| 73 | Ok(id) |
| 74 | } |
| 75 | |
| 76 | /// `git ents issue edit`: mutate `id`'s state, assignees, and/or labels. |
| 77 | /// Assignees/labels replace the previous set entirely when at least one |
| 78 | /// value is given; an empty list leaves that field unchanged. |
| 79 | /// |
| 80 | /// # Errors |
| 81 | /// |
| 82 | /// See [`crate::mutate::outcome_to_result`]. |
| 83 | pub fn edit( |
| 84 | root: &LocalRoot, |
| 85 | id: &str, |
| 86 | state: Option<String>, |
| 87 | labels: Vec<String>, |
| 88 | assignees: Vec<String>, |
| 89 | key: Option<PathBuf>, |
| 90 | ) -> Result<()> { |
| 91 | let signer = signer(root, key)?; |
| 92 | let identity = Identity { |
| 93 | actor: actor(&signer), |
| 94 | author: None, |
| 95 | sign: &|payload| signer.sign(payload), |
| 96 | }; |
| 97 | let edit = EditIssue { |
| 98 | state, |
| 99 | labels: (!labels.is_empty()).then_some(labels), |
| 100 | assignees: (!assignees.is_empty()) |
| 101 | .then(|| assignees.into_iter().map(MemberId::new).collect()), |
| 102 | }; |
| 103 | let outcome = issue::edit( |
| 104 | &root.refs, |
| 105 | &root.objects, |
| 106 | &root.events, |
| 107 | id, |
| 108 | edit, |
| 109 | &identity, |
| 110 | root.mode(), |
| 111 | )?; |
| 112 | outcome_to_result(outcome, None)?; |
| 113 | Ok(()) |
| 114 | } |