Skip to content

Commit 5be0f2e

Browse files
authored
Merge pull request #13331 from gitbutlerapp/dp-tui-replace-amend-with-reverse-rub
Replace amend with reverse-rub in the TUI
2 parents a9d7818 + dbdb156 commit 5be0f2e

77 files changed

Lines changed: 1565 additions & 1393 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/but/src/command/legacy/status/tui/details/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,6 @@ impl Details {
201201
| Message::ShrinkDetails
202202
| Message::RegisterMessageOnDrop(_)
203203
| Message::WithOneFrameDelay(_)
204-
| Message::Amend
205204
| Message::EnterNormalMode => false,
206205

207206
Message::MoveCursorUp
@@ -220,7 +219,9 @@ impl Details {
220219
| CommitMessage::ToggleEmptyMessage => false,
221220
},
222221
Message::Rub(rub_message) => match rub_message {
223-
RubMessage::Start | RubMessage::StartWithSource { .. } => false,
222+
RubMessage::Start
223+
| RubMessage::StartReverse
224+
| RubMessage::StartWithSource { .. } => false,
224225
RubMessage::Confirm => true,
225226
},
226227
Message::Reword(reword_message) => match reword_message {

crates/but/src/command/legacy/status/tui/key_bind.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,15 @@ fn register_normal_mode_key_binds(key_binds: &mut KeyBinds) {
307307
hide_from_hotbar: false,
308308
});
309309

310+
key_binds.register(StaticKeyBind {
311+
short_description: "reverse rub",
312+
chord_display: "shift+r",
313+
key_matcher: press().shift().code(KeyCode::Char('R')),
314+
modes: Vec::from([ModeDiscriminant::Normal]),
315+
message: Message::Rub(RubMessage::StartReverse),
316+
hide_from_hotbar: false,
317+
});
318+
310319
key_binds.register(StaticKeyBind {
311320
short_description: "commit",
312321
chord_display: "c",
@@ -370,15 +379,6 @@ fn register_normal_mode_key_binds(key_binds: &mut KeyBinds) {
370379
hide_from_hotbar: false,
371380
});
372381

373-
key_binds.register(StaticKeyBind {
374-
short_description: "amend",
375-
chord_display: "a",
376-
key_matcher: press().code(KeyCode::Char('a')),
377-
modes: Vec::from([ModeDiscriminant::Normal]),
378-
message: Message::Amend,
379-
hide_from_hotbar: false,
380-
});
381-
382382
key_binds.register(StaticKeyBind {
383383
short_description: "files",
384384
chord_display: "f",

crates/but/src/command/legacy/status/tui/mod.rs

Lines changed: 78 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ use crate::{
3333
CliId,
3434
command::legacy::{
3535
reword::get_branch_name_from_editor,
36-
rub::{
37-
RubOperation, RubOperationDiscriminants, StackToCommitOperation,
38-
UnassignedToCommitOperation,
39-
},
36+
rub::RubOperationDiscriminants,
4037
status::{
4138
CommitLineContent, FileLineContent, StatusFlags, StatusOutputLine, TuiLaunchOptions,
4239
output::BranchLineContent,
@@ -604,6 +601,9 @@ impl App {
604601
} => {
605602
self.handle_start_rub_with_source(source, unlock_details);
606603
}
604+
RubMessage::StartReverse => {
605+
self.handle_rub_start_reverse(ctx)?;
606+
}
607607
RubMessage::Confirm => self.handle_confirm_rub(ctx, messages)?,
608608
},
609609
Message::EnterNormalMode => {
@@ -727,9 +727,6 @@ impl App {
727727
terminal_area,
728728
);
729729
}
730-
Message::Amend => {
731-
self.handle_amend(ctx)?;
732-
}
733730
}
734731

735732
self.ensure_cursor_visible(visible_height);
@@ -813,6 +810,23 @@ impl App {
813810
self.handle_start_rub_with_source(RubSource::CliId(Arc::clone(cli_id)), None);
814811
}
815812

813+
fn available_targets_for_rub_mode(&self, source: &RubSource) -> Vec<Arc<CliId>> {
814+
self.status_lines
815+
.iter()
816+
.filter_map(|line| line.data.cli_id())
817+
.filter(|target| {
818+
*source == ***target
819+
|| match &source {
820+
RubSource::CliId(source) => rub::route_operation(source, target).is_some(),
821+
RubSource::CommittedHunk(hunk) => {
822+
rub_from_detail_view::route_operation(hunk, target).is_some()
823+
}
824+
}
825+
})
826+
.cloned()
827+
.collect::<Vec<_>>()
828+
}
829+
816830
fn handle_start_rub_with_source(
817831
&mut self,
818832
source: RubSource,
@@ -827,21 +841,7 @@ impl App {
827841
RubSource::CommittedHunk(..) => {}
828842
}
829843

830-
let available_targets = self
831-
.status_lines
832-
.iter()
833-
.filter_map(|line| line.data.cli_id())
834-
.filter(|target| {
835-
source == ***target
836-
|| match &source {
837-
RubSource::CliId(source) => rub::route_operation(source, target).is_some(),
838-
RubSource::CommittedHunk(hunk) => {
839-
rub_from_detail_view::route_operation(hunk, target).is_some()
840-
}
841-
}
842-
})
843-
.cloned()
844-
.collect::<Vec<_>>();
844+
let available_targets = self.available_targets_for_rub_mode(&source);
845845

846846
self.mode = Mode::Rub(RubMode {
847847
source,
@@ -872,6 +872,61 @@ impl App {
872872
}
873873
}
874874

875+
fn handle_rub_start_reverse(&mut self, ctx: &mut Context) -> anyhow::Result<()> {
876+
let Some(selection) = self
877+
.cursor
878+
.selected_line(&self.status_lines)
879+
.and_then(|line| line.data.cli_id())
880+
else {
881+
return Ok(());
882+
};
883+
884+
let CliId::Commit { commit_id, .. } = &**selection else {
885+
return Ok(());
886+
};
887+
888+
let stack_id = {
889+
let (_guard, _, ws, _) = ctx.workspace_and_db()?;
890+
ws.find_commit_and_containers(*commit_id)
891+
.and_then(|(stack, _, _)| stack.id)
892+
};
893+
894+
let source = if let Some(stack_id) = stack_id
895+
&& operations::stack_has_assigned_changes(ctx, stack_id)?
896+
&& let Some(id) = self
897+
.status_lines
898+
.iter()
899+
.filter_map(|line| line.data.cli_id())
900+
.find_map(|id| {
901+
if let CliId::Stack { id, stack_id: sid } = &**id
902+
&& *sid == stack_id
903+
{
904+
Some(id)
905+
} else {
906+
None
907+
}
908+
}) {
909+
RubSource::CliId(Arc::new(CliId::Stack {
910+
id: id.to_owned(),
911+
stack_id,
912+
}))
913+
} else {
914+
RubSource::CliId(Arc::new(CliId::Unassigned {
915+
id: UNASSIGNED.to_owned(),
916+
}))
917+
};
918+
919+
let available_targets = self.available_targets_for_rub_mode(&source);
920+
921+
self.mode = Mode::Rub(RubMode {
922+
source,
923+
available_targets,
924+
_unlock_details: None,
925+
});
926+
927+
Ok(())
928+
}
929+
875930
/// Handles toggling file visibility and requests a status reload.
876931
fn handle_toggle_global_files_list(&mut self, messages: &mut Vec<Message>) {
877932
self.flags.show_files = match self.flags.show_files {
@@ -2695,57 +2750,6 @@ impl App {
26952750
let details_viewport = self.details_viewport(terminal_area);
26962751
self.details.ensure_selection_visible(details_viewport);
26972752
}
2698-
2699-
fn handle_amend(&mut self, ctx: &mut Context) -> anyhow::Result<()> {
2700-
let Some(selection) = self
2701-
.cursor
2702-
.selected_line(&self.status_lines)
2703-
.and_then(|line| line.data.cli_id())
2704-
else {
2705-
return Ok(());
2706-
};
2707-
2708-
let CliId::Commit { commit_id, .. } = &**selection else {
2709-
return Ok(());
2710-
};
2711-
2712-
let stack_id = {
2713-
let (_guard, _, ws, _) = ctx.workspace_and_db()?;
2714-
ws.find_commit_and_containers(*commit_id)
2715-
.and_then(|(stack, _, _)| stack.id)
2716-
};
2717-
2718-
let (operation, confirm_message) = if let Some(stack_id) = stack_id
2719-
&& operations::stack_has_assigned_changes(ctx, stack_id)?
2720-
{
2721-
(
2722-
RubOperation::StackToCommit(StackToCommitOperation {
2723-
from: stack_id,
2724-
to: *commit_id,
2725-
}),
2726-
format!(
2727-
"Amend changes assigned to stack into {}?",
2728-
commit_id.to_hex_with_len(7)
2729-
),
2730-
)
2731-
} else {
2732-
(
2733-
RubOperation::UnassignedToCommit(UnassignedToCommitOperation { oid: *commit_id }),
2734-
format!("Amend unassigned into {}?", commit_id.to_hex_with_len(7)),
2735-
)
2736-
};
2737-
2738-
self.confirm = Some(Confirm::new(
2739-
confirm_message,
2740-
run_after_confirmation_msg(move |_, ctx, messages| {
2741-
let what_to_select = operations::rub(ctx, &operation)?;
2742-
messages.push(Message::Reload(what_to_select));
2743-
Ok(())
2744-
}),
2745-
));
2746-
2747-
Ok(())
2748-
}
27492753
}
27502754

27512755
fn event_to_messages(ev: Event, key_binds: &KeyBinds, mode: &Mode, messages: &mut Vec<Message>) {
@@ -2832,7 +2836,6 @@ enum Message {
28322836
EnterDetailsMode,
28332837
LeaveDetailsMode,
28342838
NewBranch,
2835-
Amend,
28362839

28372840
// Utilities
28382841
CopySelection,
@@ -2887,6 +2890,7 @@ enum RubMessage {
28872890
source: RubSource,
28882891
unlock_details: Option<MessageOnDrop>,
28892892
},
2893+
StartReverse,
28902894
Confirm,
28912895
}
28922896

crates/but/src/command/legacy/status/tui/tests/mod.rs

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -564,46 +564,6 @@ fn key_b_creates_new_branch_from_selected_branch() {
564564
.assert_current_line_eq(str!["┊╭┄br [c-branch-1] (no commits)"]);
565565
}
566566

567-
#[test]
568-
fn key_a_amends_selected_commit() {
569-
let env = Sandbox::init_scenario_with_target_and_default_settings("one-stack").unwrap();
570-
env.setup_metadata(&["A"]).unwrap();
571-
572-
let mut tui = test_tui(env);
573-
574-
tui.env().file("test.txt", "content");
575-
576-
tui.input_then_render(None)
577-
.assert_current_line_eq(str!["╭┄zz [unassigned changes]"]);
578-
579-
tui.input_then_render([KeyCode::Down, KeyCode::Down, KeyCode::Down])
580-
.assert_current_line_eq(str!["┊● [..] add A"]);
581-
582-
tui.input_then_render('a')
583-
.assert_current_line_eq(str!["┊● [..] add A"])
584-
.assert_rendered_contains("Amend unassigned into");
585-
586-
tui.input_then_render('y')
587-
.assert_current_line_eq(str!["┊● [..] add A"]);
588-
589-
let status = tui.env().invoke_git("status --porcelain");
590-
assert_eq!(status, "", "expected amend to consume all worktree changes");
591-
}
592-
593-
#[test]
594-
fn key_a_on_non_commit_selection_is_noop() {
595-
let env = Sandbox::init_scenario_with_target_and_default_settings("one-stack").unwrap();
596-
env.setup_metadata(&["A"]).unwrap();
597-
598-
let mut tui = test_tui(env);
599-
600-
tui.input_then_render(None)
601-
.assert_current_line_eq(str!["╭┄zz [unassigned changes] (no changes)"]);
602-
603-
tui.input_then_render('a')
604-
.assert_current_line_eq(str!["╭┄zz [unassigned changes] (no changes)"]);
605-
}
606-
607567
#[test]
608568
fn rubbing() {
609569
let env = Sandbox::init_scenario_with_target_and_default_settings("one-stack").unwrap();

crates/but/src/command/legacy/status/tui/tests/rub_tests.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,71 @@ fn rub_api_cannot_rub_into_branches() {
111111
]);
112112
}
113113

114+
// Tests RubMessage::StartReverse on a commit when unassigned has changes.
115+
#[test]
116+
fn rub_api_reverse_rub_uses_unassigned_source_when_unassigned_has_changes() {
117+
let env = Sandbox::init_scenario_with_target_and_default_settings("one-stack").unwrap();
118+
env.setup_metadata(&["A"]).unwrap();
119+
120+
let mut tui = test_tui(env);
121+
122+
tui.env().file("test.txt", "content");
123+
tui.env().invoke_git("add test.txt");
124+
125+
tui.input_then_render(None)
126+
.assert_current_line_eq(str!["╭┄zz [unassigned changes]"]);
127+
128+
tui.input_then_render((KeyModifiers::SHIFT, KeyCode::Char('J')))
129+
.assert_current_line_eq(str!["┊╭┄g0 [A]"]);
130+
131+
tui.input_then_render(KeyCode::Down)
132+
.assert_current_line_eq(str!["┊● [..] add A"]);
133+
134+
tui.input_then_render((KeyModifiers::SHIFT, KeyCode::Char('R')))
135+
.assert_current_line_eq(str!["┊● << amend >> [..] add A"]);
136+
137+
tui.input_then_render([KeyCode::Up, KeyCode::Up])
138+
.assert_current_line_eq(str!["╭┄<< source >> << noop >> zz [unassigned changes]"]);
139+
}
140+
141+
// Tests RubMessage::StartReverse with unassigned source when stack has no assigned changes.
142+
#[test]
143+
fn rub_api_reverse_rub_uses_unassigned_source_when_stack_has_no_assigned_changes() {
144+
let env = Sandbox::init_scenario_with_target_and_default_settings("one-stack").unwrap();
145+
env.setup_metadata(&["A"]).unwrap();
146+
147+
let mut tui = test_tui(env);
148+
149+
tui.input_then_render(None)
150+
.assert_current_line_eq(str!["╭┄zz [unassigned changes] (no changes)"]);
151+
152+
tui.input_then_render([KeyCode::Down, KeyCode::Down])
153+
.assert_current_line_eq(str!["┊● [..] add A"]);
154+
155+
tui.input_then_render((KeyModifiers::SHIFT, KeyCode::Char('R')))
156+
.assert_current_line_eq(str!["┊● << amend >> [..] add A"]);
157+
158+
tui.input_then_render([KeyCode::Up, KeyCode::Up])
159+
.assert_current_line_eq(str![
160+
"╭┄<< source >> << noop >> zz [unassigned changes] (no changes)"
161+
]);
162+
}
163+
164+
// Tests RubMessage::StartReverse is a no-op when not selecting a commit.
165+
#[test]
166+
fn rub_api_reverse_rub_is_noop_on_non_commit_selection() {
167+
let env = Sandbox::init_scenario_with_target_and_default_settings("one-stack").unwrap();
168+
env.setup_metadata(&["A"]).unwrap();
169+
170+
let mut tui = test_tui(env);
171+
172+
tui.input_then_render(None)
173+
.assert_current_line_eq(str!["╭┄zz [unassigned changes] (no changes)"]);
174+
175+
tui.input_then_render((KeyModifiers::SHIFT, KeyCode::Char('R')))
176+
.assert_current_line_eq(str!["╭┄zz [unassigned changes] (no changes)"]);
177+
}
178+
114179
// Tests RubOperation::UndoCommit.
115180
#[test]
116181
fn rub_api_undo_commit_operation() {

0 commit comments

Comments
 (0)