Skip to content

Commit 176a60b

Browse files
[WIP] feat: add reply w/ Reviewed-by to subset of patches
Draft commit to implement the possibility to only reply to a subset of patches from a patchset with the `Reviewed-by` tag. Previously, it was only possible to reply to every patch in the series. Now, hitting the lowercase `r` key results in the current patch being (un)staged to be replied with the tag, whilst hitting the uppercase `R` key results in all patches being (un)staged. Beside the button "[x] reviewed-by" in the "Actions" tab there is a display of the number of the patches that are staged in gray, e.g., if the patches 1, 2, and 7 are staged, the button will look like ``` [x] reviewed-by (1,2,7) ``` This commit maybe will need to broken in two or more commit, but will probably need some refactoring. The user experience must be reviewed and I can't say for sure if I am not complicating the implementation unnecessarily, as I had to introduce considerable changes (maybe a bad smell of the project quality...). Closes: #78 Signed-off-by: David Tadokoro <[email protected]>
1 parent 20d9c0e commit 176a60b

File tree

6 files changed

+95
-23
lines changed

6 files changed

+95
-23
lines changed

src/app.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,16 +135,20 @@ impl App {
135135

136136
match log_on_error!(lore_session::split_patchset(&patchset_path)) {
137137
Ok(patches) => {
138+
let has_cover_letter = representative_patch.number_in_series() == 0;
139+
let patches_to_reply = vec![false; patches.len()];
138140
self.patchset_details_and_actions_state = Some(PatchsetDetailsAndActionsState {
139141
representative_patch,
140142
patches,
143+
has_cover_letter,
144+
patches_to_reply,
141145
preview_index: 0,
142146
preview_scroll_offset: 0,
143147
preview_pan: 0,
144148
preview_fullscreen: false,
145149
patchset_actions: HashMap::from([
146150
(PatchsetAction::Bookmark, is_patchset_bookmarked),
147-
(PatchsetAction::ReplyWithReviewedBy, false),
151+
(PatchsetAction::ReplyAllWithReviewedBy, false),
148152
]),
149153
last_screen: current_screen,
150154
lore_api_client: self.lore_api_client.clone(),
@@ -184,7 +188,7 @@ impl App {
184188
self.config.bookmarked_patchsets_path(),
185189
)?;
186190

187-
if *actions.get(&PatchsetAction::ReplyWithReviewedBy).unwrap() {
191+
if details_and_actions.patches_to_reply.contains(&true) {
188192
let successful_indexes = details_and_actions
189193
.reply_patchset_with_reviewed_by("all", self.config.git_send_email_options())?;
190194

@@ -203,7 +207,7 @@ impl App {
203207
self.patchset_details_and_actions_state
204208
.as_mut()
205209
.unwrap()
206-
.toggle_action(PatchsetAction::ReplyWithReviewedBy);
210+
.reset_reply_with_reviewed_by_action();
207211
}
208212

209213
Ok(())

src/app/screens/details_actions.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ use std::{collections::HashMap, path::Path, process::Command};
66
pub struct PatchsetDetailsAndActionsState {
77
pub representative_patch: Patch,
88
pub patches: Vec<String>,
9+
/// Indicates if patchset has a cover letter
10+
pub has_cover_letter: bool,
11+
/// Which patches to reply
12+
pub patches_to_reply: Vec<bool>,
913
pub preview_index: usize,
1014
pub preview_scroll_offset: usize,
1115
/// Horizontal offset
@@ -22,7 +26,7 @@ const LAST_LINE_PADDING: usize = 10;
2226
#[derive(Hash, Eq, PartialEq)]
2327
pub enum PatchsetAction {
2428
Bookmark,
25-
ReplyWithReviewedBy,
29+
ReplyAllWithReviewedBy,
2630
}
2731

2832
impl PatchsetDetailsAndActionsState {
@@ -96,8 +100,24 @@ impl PatchsetDetailsAndActionsState {
96100
self.toggle_action(PatchsetAction::Bookmark);
97101
}
98102

99-
pub fn toggle_reply_with_reviewed_by_action(&mut self) {
100-
self.toggle_action(PatchsetAction::ReplyWithReviewedBy);
103+
pub fn toggle_reply_with_reviewed_by_action(&mut self, all: bool) {
104+
if all {
105+
for entry in &self.patches_to_reply {
106+
// If there is at least one patch not to be replied, set all to be
107+
if !*entry {
108+
self.patches_to_reply = vec![true; self.patches_to_reply.len()];
109+
return;
110+
}
111+
}
112+
// If all patches are set to be replied, set none to be
113+
self.patches_to_reply = vec![false; self.patches_to_reply.len()];
114+
} else if let Some(entry) = self.patches_to_reply.get_mut(self.preview_index) {
115+
*entry = !*entry;
116+
}
117+
}
118+
119+
pub fn reset_reply_with_reviewed_by_action(&mut self) {
120+
self.patches_to_reply = vec![false; self.patches_to_reply.len()];
101121
}
102122

103123
pub fn toggle_action(&mut self, patchset_action: PatchsetAction) {
@@ -107,10 +127,7 @@ impl PatchsetDetailsAndActionsState {
107127
}
108128

109129
pub fn actions_require_user_io(&self) -> bool {
110-
*self
111-
.patchset_actions
112-
.get(&PatchsetAction::ReplyWithReviewedBy)
113-
.unwrap()
130+
self.patches_to_reply.contains(&true)
114131
}
115132

116133
pub fn reply_patchset_with_reviewed_by(
@@ -134,6 +151,7 @@ impl PatchsetDetailsAndActionsState {
134151
tmp_dir,
135152
target_list,
136153
&self.patches,
154+
&self.patches_to_reply,
137155
&format!("{git_user_name} <{git_user_email}>"),
138156
git_send_email_options,
139157
) {
@@ -143,11 +161,17 @@ impl PatchsetDetailsAndActionsState {
143161
}
144162
};
145163

146-
for (index, mut command) in git_reply_commands.into_iter().enumerate() {
164+
let reply_indexes: Vec<usize> = self
165+
.patches_to_reply
166+
.iter()
167+
.enumerate()
168+
.filter_map(|(i, &val)| if val { Some(i) } else { None })
169+
.collect();
170+
for (i, mut command) in git_reply_commands.into_iter().enumerate() {
147171
let mut child = command.spawn().unwrap();
148172
let exit_status = child.wait().unwrap();
149173
if exit_status.success() {
150-
successful_indexes.push(index);
174+
successful_indexes.push(reply_indexes[i]);
151175
}
152176
}
153177

src/handler/details_actions.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@ pub fn handle_patchset_details<B: Backend>(
2020
let patchset_details_and_actions = app.patchset_details_and_actions_state.as_mut().unwrap();
2121

2222
if key.modifiers.contains(KeyModifiers::SHIFT) {
23-
if let KeyCode::Char('G') = key.code {
24-
patchset_details_and_actions.go_to_last_line()
23+
match key.code {
24+
KeyCode::Char('G') => patchset_details_and_actions.go_to_last_line(),
25+
KeyCode::Char('R') => {
26+
patchset_details_and_actions.toggle_reply_with_reviewed_by_action(true);
27+
}
28+
_ => {}
2529
}
2630
return Ok(());
2731
}
@@ -86,7 +90,7 @@ pub fn handle_patchset_details<B: Backend>(
8690
patchset_details_and_actions.toggle_bookmark_action();
8791
}
8892
KeyCode::Char('r') => {
89-
patchset_details_and_actions.toggle_reply_with_reviewed_by_action();
93+
patchset_details_and_actions.toggle_reply_with_reviewed_by_action(false);
9094
}
9195
KeyCode::Enter => {
9296
if patchset_details_and_actions.actions_require_user_io() {

src/lore/lore_session.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ pub fn prepare_reply_patchset_with_reviewed_by<T>(
367367
tmp_dir: &Path,
368368
target_list: &str,
369369
patches: &[String],
370+
patches_to_reply: &[bool],
370371
git_signature: &str,
371372
git_send_email_options: &str,
372373
) -> Result<Vec<Command>, LoreSessionError>
@@ -376,7 +377,11 @@ where
376377
let mut git_reply_commands: Vec<Command> = Vec::new();
377378
let re_message_id = Regex::new(r#"(?m)^Message-Id: <(.*?)>"#).unwrap();
378379

379-
for patch in patches.iter() {
380+
for (i, patch) in patches.iter().enumerate() {
381+
if !patches_to_reply[i] {
382+
continue;
383+
}
384+
380385
let message_id = re_message_id
381386
.captures(patch)
382387
.unwrap()

src/lore/lore_session/tests.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,11 +534,14 @@ fn should_prepare_reply_patchset_with_reviewed_by() {
534534
.unwrap(),
535535
];
536536

537+
let patches_to_reply = vec![true; patches.len()];
538+
537539
let git_reply_commands = prepare_reply_patchset_with_reviewed_by(
538540
&lore_api_client,
539541
tmp_dir,
540542
target_list,
541543
&patches,
544+
&patches_to_reply,
542545
"Bar Foo <[email protected]>",
543546
"--dry-run --suppress-cc=all",
544547
)

src/ui/details_actions.rs

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,29 @@ fn render_details_and_actions(f: &mut Frame, app: &App, details_chunk: Rect, act
6565

6666
f.render_widget(patchset_details, details_chunk);
6767

68+
let mut patches_to_reply = String::new();
69+
if patchset_details_and_actions
70+
.patches_to_reply
71+
.contains(&true)
72+
{
73+
patches_to_reply.push('(');
74+
let number_offset = if patchset_details_and_actions.has_cover_letter {
75+
0
76+
} else {
77+
1
78+
};
79+
let patches_to_reply_numbers: Vec<usize> = patchset_details_and_actions
80+
.patches_to_reply
81+
.iter()
82+
.enumerate()
83+
.filter_map(|(i, &val)| if val { Some(i + number_offset) } else { None })
84+
.collect();
85+
for number in patches_to_reply_numbers {
86+
patches_to_reply.push_str(&format!("{number},"));
87+
}
88+
patches_to_reply = format!("{})", &patches_to_reply[..patches_to_reply.len() - 1]);
89+
}
90+
6891
let patchset_actions = &patchset_details_and_actions.patchset_actions;
6992
let patchset_actions = vec![
7093
Line::from(vec![
@@ -83,8 +106,9 @@ fn render_details_and_actions(f: &mut Frame, app: &App, details_chunk: Rect, act
83106
Span::styled("ookmark", Style::default().fg(Color::Cyan)),
84107
]),
85108
Line::from(vec![
86-
if *patchset_actions
87-
.get(&PatchsetAction::ReplyWithReviewedBy)
109+
if *patchset_details_and_actions
110+
.patches_to_reply
111+
.get(patchset_details_and_actions.preview_index)
88112
.unwrap()
89113
{
90114
Span::styled("[x] ", Style::default().fg(Color::Green))
@@ -98,7 +122,8 @@ fn render_details_and_actions(f: &mut Frame, app: &App, details_chunk: Rect, act
98122
.add_modifier(Modifier::UNDERLINED)
99123
.add_modifier(Modifier::BOLD),
100124
),
101-
Span::styled("eviewed-by", Style::default().fg(Color::Cyan)),
125+
Span::styled("eviewed-by ", Style::default().fg(Color::Cyan)),
126+
Span::styled(patches_to_reply, Style::default().fg(Color::DarkGray)),
102127
]),
103128
];
104129
let patchset_actions = Paragraph::new(patchset_actions)
@@ -124,10 +149,17 @@ fn render_preview(f: &mut Frame, app: &App, chunk: Rect) {
124149
.message_id()
125150
.href;
126151
let mut preview_title = String::from(" Preview ");
127-
if let Some(successful_indexes) = app.reviewed_patchsets.get(representative_patch_message_id) {
128-
if successful_indexes.contains(&preview_index) {
129-
preview_title = " Preview [REVIEWED] ".to_string();
130-
}
152+
if matches!(
153+
app.reviewed_patchsets.get(representative_patch_message_id),
154+
Some(successful_indexes) if successful_indexes.contains(&preview_index)
155+
) {
156+
preview_title = " Preview [REVIEWED-BY] ".to_string();
157+
} else if *patchset_details_and_actions
158+
.patches_to_reply
159+
.get(preview_index)
160+
.unwrap()
161+
{
162+
preview_title = " Preview [REVIEWED-BY]* ".to_string();
131163
};
132164

133165
let preview_offset = patchset_details_and_actions.preview_scroll_offset;

0 commit comments

Comments
 (0)