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