Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
Add --commented flags to `rad issue list` and `rad patch list`
Draft did:key:z6MkwcUR...q1kL opened 8 months ago

This patchset adds --commented flags to both rad issue list and rad patch list, so users can filter listed issues/patches by someone who commented on them.

Please tell me what you think!

6 files changed +104 -6 646d4360 906a7b97
modified crates/radicle-cli/src/commands/issue.rs
@@ -40,7 +40,7 @@ Usage
    rad issue [<option>...]
    rad issue delete <issue-id> [<option>...]
    rad issue edit <issue-id> [--title <title>] [--description <text>] [<option>...]
-
    rad issue list [--assigned <did>] [--all | --closed | --open | --solved] [<option>...]
+
    rad issue list [--assigned <did>] [--commented <did>] [--all | --closed | --open | --solved] [<option>...]
    rad issue open [--title <title>] [--description <text>] [--label <label>] [<option>...]
    rad issue react <issue-id> [--emoji <char>] [--to <comment>] [<option>...]
    rad issue assign <issue-id> [--add <did>] [--delete <did>] [<option>...]
@@ -102,6 +102,14 @@ pub enum Assigned {
    Peer(Did),
}

+
/// Command line "--commented" argument.
+
#[derive(Default, Debug, PartialEq, Eq)]
+
pub enum Commented {
+
    #[default]
+
    Me,
+
    Peer(Did),
+
}
+

#[derive(Debug, PartialEq, Eq)]
pub enum Operation {
    Edit {
@@ -152,6 +160,7 @@ pub enum Operation {
    },
    List {
        assigned: Option<Assigned>,
+
        commented: Option<Commented>,
        state: Option<State>,
    },
    Cache {
@@ -188,6 +197,7 @@ impl Args for Options {
        let mut op: Option<OperationName> = None;
        let mut id: Option<Rev> = None;
        let mut assigned: Option<Assigned> = None;
+
        let mut commented: Option<Commented> = None;
        let mut title: Option<Title> = None;
        let mut reaction: Option<Reaction> = None;
        let mut comment_id: Option<thread::CommentId> = None;
@@ -337,6 +347,15 @@ impl Args for Options {
                    }
                }

+
                Long("commented") if commented.is_none() => {
+
                    if let Ok(val) = parser.value() {
+
                        let peer = term::args::did(&val)?;
+
                        commented = Some(Commented::Peer(peer));
+
                    } else {
+
                        commented = Some(Commented::Me);
+
                    }
+
                }
+

                // Label options
                Short('a') | Long("add") if matches!(op, Some(OperationName::Label)) => {
                    let val = parser.value()?;
@@ -452,7 +471,11 @@ impl Args for Options {
                id: id.ok_or_else(|| anyhow!("an issue to label must be provided"))?,
                opts: label_opts,
            },
-
            OperationName::List => Operation::List { assigned, state },
+
            OperationName::List => Operation::List {
+
                assigned,
+
                commented,
+
                state,
+
            },
            OperationName::Cache => Operation::Cache {
                id,
                storage: cache_storage,
@@ -676,8 +699,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
                .collect::<Vec<_>>();
            issue.label(labels, &signer)?;
        }
-
        Operation::List { assigned, state } => {
-
            list(issues, &assigned, &state, &profile)?;
+
        Operation::List {
+
            assigned,
+
            commented,
+
            state,
+
        } => {
+
            list(issues, &assigned, &commented, &state, &profile)?;
        }
        Operation::Delete { id } => {
            let signer = term::signer(&profile)?;
@@ -717,6 +744,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
fn list<C>(
    cache: C,
    assigned: &Option<Assigned>,
+
    commented: &Option<Commented>,
    state: &Option<State>,
    profile: &profile::Profile,
) -> anyhow::Result<()>
@@ -734,6 +762,11 @@ where
        None => None,
    };

+
    let commented = commented.as_ref().map(|c| match c {
+
        Commented::Me => profile.did(),
+
        Commented::Peer(id) => *id,
+
    });
+

    let mut all = cache
        .list()?
        .filter_map(|result| {
@@ -760,6 +793,12 @@ where

            Some((id, issue))
        })
+
        .filter(|(_id, issue)| {
+
            commented
+
                .as_ref()
+
                .map(|did| issue.is_commented_by(did))
+
                .unwrap_or(true)
+
        })
        .collect::<Vec<_>>();

    all.sort_by(|(id1, i1), (id2, i2)| {
modified crates/radicle-cli/src/commands/patch.rs
@@ -43,7 +43,7 @@ pub const HELP: Help = Help {
Usage

    rad patch [<option>...]
-
    rad patch list [--all|--merged|--open|--archived|--draft|--authored] [--author <did>]... [<option>...]
+
    rad patch list [--all|--merged|--open|--archived|--draft|--authored] [--author <did>]... [--commented <did>] [<option>...]
    rad patch show <patch-id> [<option>...]
    rad patch diff <patch-id> [<option>...]
    rad patch archive <patch-id> [--undo] [<option>...]
@@ -135,6 +135,7 @@ List options
        --open                 Show only open patches (default)
        --draft                Show only draft patches
        --authored             Show only patches that you have authored
+
        --commented <did>      Show only patches that someone has commented on
        --author <did>         Show only patched where the given user is an author
                               (may be specified multiple times)

@@ -208,6 +209,14 @@ pub struct LabelOptions {
    pub delete: BTreeSet<Label>,
}

+
/// Command line "--commented" argument.
+
#[derive(Default, Debug, PartialEq, Eq)]
+
pub enum Commenter {
+
    #[default]
+
    Me,
+
    Peer(Did),
+
}
+

#[derive(Debug)]
pub enum Operation {
    Show {
@@ -341,6 +350,7 @@ pub struct Options {
    pub quiet: bool,
    pub authored: bool,
    pub authors: Vec<Did>,
+
    pub commenters: Vec<Commenter>,
}

impl Args for Options {
@@ -353,6 +363,7 @@ impl Args for Options {
        let mut quiet = false;
        let mut authored = false;
        let mut authors = vec![];
+
        let mut commenters = vec![];
        let mut announce = true;
        let mut patch_id = None;
        let mut revision_id = None;
@@ -639,6 +650,14 @@ impl Args for Options {
                Long("author") if op == Some(OperationName::List) => {
                    authors.push(term::args::did(&parser.value()?)?);
                }
+
                Long("commented") if op == Some(OperationName::List) => {
+
                    if let Ok(val) = parser.value() {
+
                        let peer = term::args::did(&val)?;
+
                        commenters.push(Commenter::Peer(peer));
+
                    } else {
+
                        commenters.push(Commenter::Me);
+
                    }
+
                }

                // Cache options.
                Long("storage") if op == Some(OperationName::Cache) => {
@@ -829,6 +848,7 @@ impl Args for Options {
                announce,
                authored,
                authors,
+
                commenters,
            },
            vec![],
        ))
@@ -856,7 +876,20 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            if options.authored {
                authors.insert(profile.did());
            }
-
            list::run(filter.as_ref(), authors, &repository, &profile)?;
+
            list::run(
+
                filter.as_ref(),
+
                authors,
+
                options
+
                    .commenters
+
                    .iter()
+
                    .map(|c| match c {
+
                        Commenter::Me => profile.did(),
+
                        Commenter::Peer(did) => *did,
+
                    })
+
                    .collect(),
+
                &repository,
+
                &profile,
+
            )?;
        }
        Operation::Show {
            patch_id,
modified crates/radicle-cli/src/commands/patch/list.rs
@@ -20,6 +20,7 @@ use itertools::Itertools as _;
pub fn run(
    filter: Option<&patch::Status>,
    authors: BTreeSet<Did>,
+
    commenters: Vec<Did>,
    repository: &Repository,
    profile: &Profile,
) -> anyhow::Result<()> {
@@ -44,6 +45,12 @@ pub fn run(
                continue;
            }
        }
+

+
        if !commenters.is_empty() {
+
            if !commenters.iter().any(|c| patch.has_comment_by(*c)) {
+
                continue;
+
            }
+
        }
        all.push((id, patch));
    }

modified crates/radicle/src/cob/issue.rs
@@ -317,6 +317,10 @@ impl Issue {
        self.thread.comments()
    }

+
    pub fn is_commented_by(&self, commenter: &Did) -> bool {
+
        self.thread.has_participant(*commenter.as_key())
+
    }
+

    /// Get replies to a specific comment.
    pub fn replies_to<'a>(
        &'a self,
modified crates/radicle/src/cob/patch.rs
@@ -781,6 +781,13 @@ impl Patch {
        };
        Ok(outcome)
    }
+

+
    pub fn has_comment_by(&self, c: Did) -> bool {
+
        self.revisions
+
            .iter()
+
            .flat_map(|(_rid, rev)| rev)
+
            .any(|revision| revision.discussion().has_participant(*c.as_key()))
+
    }
}

impl Patch {
modified crates/radicle/src/cob/thread.rs
@@ -359,6 +359,14 @@ impl<T> Thread<T> {
    }
}

+
impl<CodeLoc> Thread<Comment<CodeLoc>> {
+
    pub fn has_participant(&self, actor: ActorId) -> bool {
+
        self.comments
+
            .iter()
+
            .any(|(_, c)| c.as_ref().is_some_and(|c| c.author() == actor))
+
    }
+
}
+

impl Thread {
    /// Apply a single action to the thread.
    fn action<R: ReadRepository>(