jj-vine 0.3.5

Stacked pull requests for jj (jujutsu). Supports GitLab and bookmark-based flow.
Documentation
use futures::{StreamExt, stream::FuturesUnordered};

use crate::{
    bookmark::{BookmarkGraph, BookmarkRef},
    error::{Error, Result, make_whatever},
    forge::Forge,
    submit::execute::{
        ActionResultData,
        ExecuteAction,
        ExecutionActionContext,
        MRUpdate,
        MRUpdateType,
    },
};

pub struct SyncDependentMergeRequestsAction<'a> {
    pub bookmark: String,
    pub bookmark_graph: BookmarkGraph<'a>,
}

impl<'a> SyncDependentMergeRequestsAction<'a> {
    pub fn new(bookmark: String, bookmark_graph: BookmarkGraph<'a>) -> Self {
        Self {
            bookmark,
            bookmark_graph,
        }
    }
}

impl<'a> ExecuteAction for SyncDependentMergeRequestsAction<'a> {
    async fn execute(&self, ctx: ExecutionActionContext<'_, '_>) -> Result<ActionResultData> {
        if ctx.plan.dry_run {
            return Ok(ActionResultData::DryRun);
        }

        let mr = ctx
            .forge
            .find_merge_request_by_source_branch(&self.bookmark)
            .await?
            .ok_or_else::<Error, _>(|| {
                make_whatever!("No merge request found for {}", self.bookmark)
            })?;

        let dependent_merge_request_iids: Vec<_> = self
            .bookmark_graph
            .find_bookmark_in_components(&self.bookmark)
            .expect("Bookmark not found in graph")
            .parents
            .iter()
            .filter_map(|p| match p {
                BookmarkRef::Bookmark(b) => Some(b.name()),
                BookmarkRef::Trunk => None,
            })
            .map(|parent_bookmark| async move {
                let mr = ctx
                    .forge
                    .find_merge_request_by_source_branch(parent_bookmark)
                    .await?
                    .ok_or_else::<Error, _>(|| {
                        make_whatever!("No merge request found for {}", parent_bookmark)
                    })?;
                Ok(mr.iid().to_string())
            })
            .collect::<FuturesUnordered<_>>()
            .collect::<Vec<Result<String>>>()
            .await
            .into_iter()
            .collect::<Result<Vec<_>>>()?;

        let changed = ctx
            .forge
            .sync_dependent_merge_requests(
                mr.iid(),
                dependent_merge_request_iids
                    .iter()
                    .map(|s| s.into())
                    .collect::<Vec<_>>()
                    .as_slice(),
            )
            .await?;

        Ok(ActionResultData::MRUpdated(Box::new(MRUpdate {
            mr,
            bookmark: self.bookmark.clone(),
            update_type: if changed {
                MRUpdateType::SyncedDependentMergeRequests
            } else {
                MRUpdateType::Unchanged
            },
        })))
    }
}