Skip to main content

git_disjoint/
cli.rs

1use clap::{ArgAction, Parser};
2
3#[derive(Copy, Clone, Debug, Eq, PartialEq)]
4pub enum CommitsToConsider {
5    All,
6    WithTrailer,
7}
8
9impl From<&str> for CommitsToConsider {
10    fn from(value: &str) -> Self {
11        match value {
12            "true" => Self::All,
13            "false" => Self::WithTrailer,
14            _ => unreachable!(),
15        }
16    }
17}
18
19#[derive(Copy, Clone, Debug, Eq, PartialEq)]
20pub enum PromptUserToChooseCommits {
21    Yes,
22    No,
23}
24
25impl From<&str> for PromptUserToChooseCommits {
26    fn from(value: &str) -> Self {
27        match value {
28            "true" => Self::Yes,
29            "false" => Self::No,
30            _ => unreachable!(),
31        }
32    }
33}
34
35#[derive(Copy, Clone, Debug, Eq, PartialEq)]
36pub enum OverlayCommitsIntoOnePullRequest {
37    Yes,
38    No,
39}
40
41impl From<&str> for OverlayCommitsIntoOnePullRequest {
42    fn from(value: &str) -> Self {
43        match value {
44            "true" => Self::Yes,
45            "false" => Self::No,
46            _ => unreachable!(),
47        }
48    }
49}
50
51#[derive(Copy, Clone, Debug, Eq, PartialEq)]
52pub enum CommitGrouping {
53    Individual,
54    ByIssue,
55}
56
57impl From<&str> for CommitGrouping {
58    fn from(value: &str) -> Self {
59        match value {
60            "true" => Self::Individual,
61            "false" => Self::ByIssue,
62            _ => unreachable!(),
63        }
64    }
65}
66
67#[derive(Clone, Debug, Parser)]
68#[command(author, version, about)]
69pub struct Cli {
70    /// Do not ignore commits without an issue trailer.
71    ///
72    /// Commits without an issue trailer are considered to be their own
73    /// group, so will be the only commit in their PR.
74    ///
75    /// There is no change to commits with an issue trailer.
76    ///
77    /// This flag can be combined with the --choose flag.
78    #[arg(
79        short,
80        long,
81        help = "Consider every commit, even commits without an issue trailer",
82        action = ArgAction::SetTrue,
83    )]
84    pub all: CommitsToConsider,
85
86    /// The starting point (exclusive) of commits to act on.
87    ///
88    /// Defaults to the repository's default branch.
89    #[arg(
90        short,
91        long,
92        help = "The starting point (exclusive) of commits to act on",
93        value_name = "REF"
94    )]
95    pub base: Option<String>,
96
97    /// Prompt the user to select which issues to create PRs for.
98    ///
99    /// Select a whitelist of issues (or commits, if the --all flag is active)
100    /// in a terminal UI.
101    #[arg(
102        short,
103        long,
104        help = "Prompt the user to select which issues to create PRs for",
105        action = ArgAction::SetTrue,
106    )]
107    pub choose: PromptUserToChooseCommits,
108
109    /// Show the work that would be performed without taking any action.
110    #[arg(
111        short,
112        long,
113        env = "GIT_DISJOINT_DRY_RUN",
114        help = "Show the work that would be performed without taking any action"
115    )]
116    pub dry_run: bool,
117
118    /// GitHub API token with repo permissions.
119    ///
120    /// If not provided, git-disjoint will attempt to resolve a token
121    /// from the GitHub CLI (`gh auth token`).
122    #[arg(
123        long,
124        env = "GITHUB_TOKEN",
125        help = "GitHub API token [default: resolved from gh auth token]",
126        value_name = "TOKEN"
127    )]
128    pub github_token: Option<String>,
129
130    /// Combine multiple issue groups into one PR.
131    ///
132    /// When this flag is active, git-disjoint will create only one PR.
133    ///
134    /// This can be useful when you have multiple commits with no trailer that
135    /// would be better reviewed or merged together.
136    #[arg(
137        short,
138        long,
139        help = "Combine multiple issue groups into one PR",
140        action = ArgAction::SetTrue,
141    )]
142    pub overlay: OverlayCommitsIntoOnePullRequest,
143
144    /// Create pull requests as ready for review.
145    ///
146    /// By default, git-disjoint creates draft pull requests. Use this flag
147    /// to mark pull requests as ready for review immediately.
148    #[arg(
149        short,
150        long,
151        help = "Create pull requests as ready for review instead of draft"
152    )]
153    pub ready: bool,
154
155    /// Do not group commits by issue.
156    ///
157    /// Treat each commit independently, regardless of issue trailer. Each
158    /// PR created will have one and only one commit associated with it.
159    ///
160    /// This is the same behavior as when no commit has an issue trailer and
161    /// the --all flag is active.
162    ///
163    /// This can be useful when you have numerous changes that belong under
164    /// one issue, but would be better reviewed independently.
165    #[arg(
166        short,
167        long,
168        help = "Treat every commit separately; do not group by issue",
169        action = ArgAction::SetTrue,
170    )]
171    pub separate: CommitGrouping,
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn parse_without_token_returns_none() {
180        let cli = Cli::try_parse_from(["git-disjoint"]).unwrap();
181        assert_eq!(cli.github_token, None);
182    }
183
184    #[test]
185    fn parse_with_token_flag_returns_some() {
186        let cli = Cli::try_parse_from(["git-disjoint", "--github-token", "ghp_abc123"]).unwrap();
187        assert_eq!(cli.github_token, Some("ghp_abc123".into()));
188    }
189}