From bbddd07babb4909e2129865fae17900e6b55d705 Mon Sep 17 00:00:00 2001 From: Peer Sommerlund Date: Sat, 19 Jul 2025 12:34:16 +0200 Subject: [PATCH 1/5] feat: Simple rebase (close #4) This works similar to squash: @ is the source rev, highlight is the target rev. --- src/commander/jj.rs | 12 ++ src/keybinds/log_tab.rs | 3 + src/keybinds/mod.rs | 1 + src/keybinds/rebase_popup.rs | 73 +++++++++ src/ui/log_tab.rs | 35 ++++- src/ui/mod.rs | 1 + src/ui/rebase_popup.rs | 279 +++++++++++++++++++++++++++++++++++ 7 files changed, 403 insertions(+), 1 deletion(-) create mode 100644 src/keybinds/rebase_popup.rs create mode 100644 src/ui/rebase_popup.rs diff --git a/src/commander/jj.rs b/src/commander/jj.rs index c97c6ae8..42bfffbb 100644 --- a/src/commander/jj.rs +++ b/src/commander/jj.rs @@ -44,6 +44,18 @@ impl Commander { .context("Failed executing jj describe") } + /// Rebase changes. Maps to `jj rebase -s -d ` or similar + #[instrument(level = "trace", skip(self))] + pub fn run_rebase( + &mut self, + src_mode: &str, + src_rev: &str, + tgt_mode: &str, + tgt_rev: &str, + ) -> Result<()> { + Ok(self.execute_void_jj_command(vec!["rebase", src_mode, src_rev, tgt_mode, tgt_rev])?) + } + /// Squash changes. Maps to `jj squash -u --into ` #[instrument(level = "trace", skip(self))] pub fn run_squash(&mut self, revision: &str, ignore_immutable: bool) -> Result<()> { diff --git a/src/keybinds/log_tab.rs b/src/keybinds/log_tab.rs index 667752e4..dd9682e9 100644 --- a/src/keybinds/log_tab.rs +++ b/src/keybinds/log_tab.rs @@ -31,6 +31,7 @@ pub enum LogTabEvent { CreateNew { describe: bool, }, + Rebase, Squash { ignore_immutable: bool, }, @@ -77,6 +78,7 @@ impl Default for LogTabKeybinds { LogTabEvent::Refresh => "f5", LogTabEvent::CreateNew { describe: false } => "n", LogTabEvent::CreateNew { describe: true } => "shift+n", + LogTabEvent::Rebase => "ctrl+r", LogTabEvent::Squash { ignore_immutable: false } => "s", LogTabEvent::Squash { ignore_immutable: true } => "shift+s", LogTabEvent::EditChange { ignore_immutable: false } => "e", @@ -156,6 +158,7 @@ impl LogTabKeybinds { LogTabEvent::CreateNew { describe: false } => "new change", LogTabEvent::CreateNew { describe: true } => "new with message", LogTabEvent::Abandon => "abandon change", + LogTabEvent::Rebase => "rebase @ to the selected change", LogTabEvent::Squash { ignore_immutable: false } => "squash @ into the selected change", LogTabEvent::Squash { ignore_immutable: true } => "squash @ into the selected change ignoring immutability", LogTabEvent::SetBookmark => "set bookmark", diff --git a/src/keybinds/mod.rs b/src/keybinds/mod.rs index e8c5f9a8..1c5aca02 100644 --- a/src/keybinds/mod.rs +++ b/src/keybinds/mod.rs @@ -8,6 +8,7 @@ pub use log_tab::{LogTabEvent, LogTabKeybinds}; mod config; mod keybinds_store; mod log_tab; +pub mod rebase_popup; /*#[derive(Debug)] pub struct Keybinds { diff --git a/src/keybinds/rebase_popup.rs b/src/keybinds/rebase_popup.rs new file mode 100644 index 00000000..d7fd50bf --- /dev/null +++ b/src/keybinds/rebase_popup.rs @@ -0,0 +1,73 @@ +/*! Key bindings specific for rebase popup */ + +use ratatui::crossterm::event::KeyEvent; +use std::str::FromStr; // used by set_keybinds macro + +use super::{Shortcut, keybinds_store::KeybindsStore}; +use crate::set_keybinds; + +/// How should rebase cut revisions from source +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum CutOption { + IncludeDescendants, // -s + IncludeBranch, // -b + SingleRevision, // -r +} + +/// How should rebase paste revisions at target +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum PasteOption { + NewBranch, // -d + InsertAfter, // -A + InsertBefore, // -B +} + +/// Actions available inside a RebasePopup +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum PopupAction { + None, + Ok, + Cancel, + SetSourceMode(CutOption), + SetTargetMode(PasteOption), +} + +fn default_keybinds() -> KeybindsStore { + let mut keys = KeybindsStore::::default(); + set_keybinds!( + keys, + PopupAction::Ok => "enter", + PopupAction::Cancel => "esc", + PopupAction::Cancel => "q", + PopupAction::SetSourceMode(CutOption::IncludeDescendants) => "s", + PopupAction::SetSourceMode(CutOption::IncludeBranch) => "b", + PopupAction::SetSourceMode(CutOption::SingleRevision) => "r", + PopupAction::SetTargetMode(PasteOption::NewBranch) => "d", + PopupAction::SetTargetMode(PasteOption::InsertAfter) => "shift+a", + PopupAction::SetTargetMode(PasteOption::InsertBefore) => "shift+b", + ); + keys +} + +#[derive(Debug)] +pub struct Keybinds { + keys: KeybindsStore, +} + +impl Default for Keybinds { + fn default() -> Self { + Self { + keys: default_keybinds(), + } + } +} + +impl Keybinds { + pub fn match_event(&self, event: KeyEvent) -> PopupAction { + if let Some(action) = self.keys.match_event(event) { + action + } else { + PopupAction::None + } + } +} diff --git a/src/ui/log_tab.rs b/src/ui/log_tab.rs index cc69aaa6..3df2ad43 100644 --- a/src/ui/log_tab.rs +++ b/src/ui/log_tab.rs @@ -24,6 +24,7 @@ use crate::{ message_popup::MessagePopup, panel::DetailsPanel, panel::LogPanel, + rebase_popup::RebasePopup, utils::{centered_rect, centered_rect_line_height, tabs_to_spaces}, }, }; @@ -66,6 +67,8 @@ pub struct LogTab<'a> { describe_textarea: Option>, describe_after_new: bool, + rebase_popup: Option, + squash_ignore_immutable: bool, edit_ignore_immutable: bool, @@ -121,6 +124,8 @@ impl<'a> LogTab<'a> { describe_textarea: None, describe_after_new: false, + rebase_popup: None, + squash_ignore_immutable: false, edit_ignore_immutable: false, @@ -195,6 +200,14 @@ impl<'a> LogTab<'a> { .open(); self.describe_after_new = describe; } + LogTabEvent::Rebase => { + let source_change = commander.get_current_head()?.commit_id; + let target_change = &self.head.commit_id; + self.rebase_popup = Some(RebasePopup::new( + source_change.clone(), + target_change.clone(), + )); + } LogTabEvent::Squash { ignore_immutable } => { if self.head.change_id == commander.get_current_head()?.change_id { return Ok(ComponentInputResult::HandledAction( @@ -604,6 +617,13 @@ impl Component for LogTab<'_> { } } + // Draw rebase popup + { + if let Some(log_rebase_popup) = &mut self.rebase_popup { + log_rebase_popup.render_widget(f) + } + } + Ok(()) } @@ -657,7 +677,20 @@ impl Component for LogTab<'_> { return Ok(ComponentInputResult::Handled); } - if let Event::Key(key) = event { + if let Some(rebase_popup) = &mut self.rebase_popup { + if rebase_popup.handle_input(commander, event.clone()) { + // when handle_input returns true, + // the popup should be closed + self.rebase_popup = None; + return Ok(ComponentInputResult::HandledAction( + ComponentAction::RefreshTab(), + )); + }; + return Ok(ComponentInputResult::Handled); + } + + if let Event::Key(key) = &event { + let key = *key; if key.kind != KeyEventKind::Press { return Ok(ComponentInputResult::Handled); } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ef64e7d6..2fd8c769 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -7,6 +7,7 @@ pub mod help_popup; pub mod log_tab; pub mod message_popup; pub mod panel; +pub mod rebase_popup; pub mod styles; pub mod utils; diff --git a/src/ui/rebase_popup.rs b/src/ui/rebase_popup.rs new file mode 100644 index 00000000..70db1dd7 --- /dev/null +++ b/src/ui/rebase_popup.rs @@ -0,0 +1,279 @@ +/*! The rebase popup allows the user to pick a rebase configuration and + start rebase, or cancel the opreation. + + The UI looks like this + ~~~ + Source (zsztoxlv) + ( ) -s this and descendants + ( ) -b whole branch + (*) -r only one change moves + Target @ (umrpslui) + (*) -d rebase onto @ as new branch + ( ) -A rebase after @ + ( ) -B rebase before @ + + Esc: Cancel Enter: Rebase +~~~ +It has keyboard shortcuts s, b, r, d, shift+a, shift+b for selecting +a radiobutton, and shortcuts Enter, Esc, q for closing the popup. + + +*/ + +use anyhow::Result; +use ratatui::{ + Frame, + crossterm::event::Event, + layout::{Alignment, Rect}, + prelude::{Buffer, Constraint, Direction, Layout, Size}, + style::{Color, Style, Stylize}, + text::{Line, Span, Text}, + widgets::{Block, BorderType, Clear, Paragraph, StatefulWidget}, +}; + +use crate::{ + ComponentInputResult, + commander::{Commander, ids::CommitId}, + keybinds::rebase_popup::{CutOption, PasteOption, PopupAction}, + ui::Component, +}; + +type Keybinds = crate::keybinds::rebase_popup::Keybinds; + +/// A transient popup for configuring a rebase command +pub struct RebasePopup { + pub keybinds: Keybinds, + + pub source_change: CommitId, + pub target_change: CommitId, + + pub source_mode: CutOption, + pub target_mode: PasteOption, +} + +impl RebasePopup { + pub fn new(source_change: CommitId, target_change: CommitId) -> Self { + Self { + keybinds: Keybinds::default(), + source_change, + target_change, + source_mode: CutOption::SingleRevision, + target_mode: PasteOption::NewBranch, + } + } + + /// Collect all the rendering code that would have been in + /// log_tab.rs/draw + pub fn render_widget(&mut self, frame: &mut Frame) { + let area = center_rect( + frame.area(), + Size { + width: 32, + height: 12, + }, + ); + self.draw(frame, area) + .expect("Expected drawing without failues"); + } + + /// Map an event to a popup action + // TODO: This should be done by a custom keybinds object + fn match_event(&self, event: Event) -> PopupAction { + if let Event::Key(key) = event { + return self.keybinds.match_event(key); + } + PopupAction::None + } + + /// Run the command that the popup is currently configured to do + fn run_command(&self, commander: &mut Commander) { + let src_rev = self.source_change.as_str(); + let tgt_rev = self.target_change.as_str(); + let src_mode = match self.source_mode { + CutOption::IncludeDescendants => "-s", + CutOption::IncludeBranch => "-b", + CutOption::SingleRevision => "-r", + }; + let tgt_mode = match self.target_mode { + PasteOption::NewBranch => "-d", + PasteOption::InsertAfter => "-A", + PasteOption::InsertBefore => "-B", + }; + commander + .run_rebase(src_mode, src_rev, tgt_mode, tgt_rev) + .expect("jj rebase should run without errors"); + } + + /// Process the input event. If this function returns true, + /// then the popup should be closed. Either a rebase was executed + /// or the operation was cancelled. + pub fn handle_input(&mut self, commander: &mut Commander, event: Event) -> bool { + match self.match_event(event) { + PopupAction::Ok => { + self.run_command(commander); + return true; + } + PopupAction::Cancel => return true, + PopupAction::SetSourceMode(m) => self.source_mode = m, + PopupAction::SetTargetMode(m) => self.target_mode = m, + PopupAction::None => (), + } + false + } +} + +impl Component for RebasePopup { + /// Render the dialog into the area. + fn draw(&mut self, frame: &mut Frame<'_>, area: Rect) -> Result<()> { + // The border of the dialog + let block = Block::bordered() + .title(Span::styled(" Rebase ", Style::new().bold().cyan())) + .title_alignment(Alignment::Center) + .border_type(BorderType::Rounded) + .border_style(Style::default().fg(Color::Green)); + frame.render_widget(Clear, area); + frame.render_widget(&block, area); + + // Split area into chunks. Even though the area size is constant, + // we pretend it can change in the future. + let chunks = Layout::default() + .direction(Direction::Vertical) + .vertical_margin(1) + .horizontal_margin(2) + .constraints( + [ + Constraint::Length(1), // title "Source" + Constraint::Min(3), // buttons for source mode + Constraint::Length(1), // title "Target" + Constraint::Min(3), // buttons for target mode + Constraint::Length(2), // help text + ] + .as_ref(), + ) + .split(area); + + // Radio buttons for source + let src_rev: String = self.source_change.as_str().chars().take(8).collect(); + let src_options = vec![ + "-s this and descendants", + "-b whole branch", + "-r only one change moves", + ]; + let mut src_select: usize = match self.source_mode { + CutOption::IncludeDescendants => 0, + CutOption::IncludeBranch => 1, + CutOption::SingleRevision => 2, + }; + frame.render_widget( + Paragraph::new(Span::raw(format!("Source @ ({src_rev})"))), + chunks[0], + ); + frame.render_stateful_widget(RadioButton::new(src_options), chunks[1], &mut src_select); + + // Radio buttons for target + let tgt_rev: String = self.target_change.as_str().chars().take(8).collect(); + let tgt_options = vec![ + "-d rebase as new branch", + "-A rebase after", + "-B rebase before", + ]; + let mut tgt_select: usize = match self.target_mode { + PasteOption::NewBranch => 0, + PasteOption::InsertAfter => 1, + PasteOption::InsertBefore => 2, + }; + frame.render_widget( + Paragraph::new(Span::raw(format!("Target ({tgt_rev})"))), + chunks[2], + ); + frame.render_stateful_widget(RadioButton::new(tgt_options), chunks[3], &mut tgt_select); + + // Help on terminating dialog + frame.render_widget( + Paragraph::new(Text::from(vec![ + Line::raw(""), + Line::raw("Esc: Cancel Enter: Rebase"), + ])), + chunks[4], + ); + + Ok(()) + } + + fn input(&mut self, _commander: &mut Commander, _event: Event) -> Result { + unreachable!(); + //return Ok(ComponentInputResult::Handled); + } +} + +/****************************************************************/ +// TODO: Move this function to ui::utils + +/// Find a rect of the given size at the center of an outside rect +fn center_rect(outside: Rect, area: Size) -> Rect { + Rect { + x: outside.x + (outside.width - area.width) / 2, + y: outside.y + (outside.height - area.height) / 2, + width: area.width, + height: area.height, + } +} + +/****************************************************************/ +// TODO: Move this widget to a separate file + +/** A widget for a group of radio buttons. + +It is a stateful widget. +The state is an usize number that indicates which label is +selected. + +Example: +~~~ +( ) apples +( ) bananas +(*) lemons +~~~ +*/ +struct RadioButton { + /// Button labels + pub labels: Vec, + /// Button style can be modified before drawing + pub button_style: Style, + /// Label style can be modified before drawing + pub label_style: Style, +} + +impl RadioButton { + pub fn new(labels: Vec<&str>) -> Self { + let button_style = Style::default().fg(Color::White); + let label_style = Style::default().fg(Color::White); + Self { + labels: labels.iter().map(|s| s.to_string()).collect(), + button_style, + label_style, + } + } +} + +impl StatefulWidget for RadioButton { + type State = usize; + + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + for (row, label) in self.labels.iter().enumerate() { + let button = if row == *state { "(*)" } else { "( )" }; + buf.set_string( + area.left(), + area.top() + row as u16, + button, + self.button_style, + ); + buf.set_string( + area.left() + 4_u16, + area.top() + row as u16, + label, + self.label_style, + ); + } + } +} From adb5f6c5909bab8596a973718d4a473ae147dfff Mon Sep 17 00:00:00 2001 From: Peer Sommerlund Date: Mon, 24 Nov 2025 16:22:01 +0100 Subject: [PATCH 2/5] Show a message on rebase error For example rebase onto yourself. --- src/ui/log_tab.rs | 18 ++++++++++++++++-- src/ui/rebase_popup.rs | 22 ++++++++++++---------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/ui/log_tab.rs b/src/ui/log_tab.rs index 3df2ad43..08af0172 100644 --- a/src/ui/log_tab.rs +++ b/src/ui/log_tab.rs @@ -678,14 +678,28 @@ impl Component for LogTab<'_> { } if let Some(rebase_popup) = &mut self.rebase_popup { - if rebase_popup.handle_input(commander, event.clone()) { + let handled = rebase_popup.handle_input(commander, event.clone()); + if handled.is_err() { + // Close popup and show error message + self.rebase_popup = None; + let msg = handled.err().unwrap(); + let error_message = msg.to_string().into_text()?; + return Ok(ComponentInputResult::HandledAction( + ComponentAction::SetPopup(Some(Box::new(MessagePopup { + title: "Error".into(), + messages: error_message, + text_align: None, + }))), + )); + } + if handled.ok() == Some(true) { // when handle_input returns true, // the popup should be closed self.rebase_popup = None; return Ok(ComponentInputResult::HandledAction( ComponentAction::RefreshTab(), )); - }; + } return Ok(ComponentInputResult::Handled); } diff --git a/src/ui/rebase_popup.rs b/src/ui/rebase_popup.rs index 70db1dd7..aeeb37eb 100644 --- a/src/ui/rebase_popup.rs +++ b/src/ui/rebase_popup.rs @@ -86,7 +86,7 @@ impl RebasePopup { } /// Run the command that the popup is currently configured to do - fn run_command(&self, commander: &mut Commander) { + fn run_command(&self, commander: &mut Commander) -> Result<()> { let src_rev = self.source_change.as_str(); let tgt_rev = self.target_change.as_str(); let src_mode = match self.source_mode { @@ -99,26 +99,28 @@ impl RebasePopup { PasteOption::InsertAfter => "-A", PasteOption::InsertBefore => "-B", }; - commander - .run_rebase(src_mode, src_rev, tgt_mode, tgt_rev) - .expect("jj rebase should run without errors"); + commander.run_rebase(src_mode, src_rev, tgt_mode, tgt_rev)?; + Ok(()) } - /// Process the input event. If this function returns true, + /// Process the input event. If this function returns Ok(true), /// then the popup should be closed. Either a rebase was executed /// or the operation was cancelled. - pub fn handle_input(&mut self, commander: &mut Commander, event: Event) -> bool { + /// If the result is Ok(false) the function did handle + /// the input, but the popup should not be closed yet. + /// Err(_) will be returned if the jj command failed. + pub fn handle_input(&mut self, commander: &mut Commander, event: Event) -> Result { match self.match_event(event) { PopupAction::Ok => { - self.run_command(commander); - return true; + self.run_command(commander)?; + return Ok(true); } - PopupAction::Cancel => return true, + PopupAction::Cancel => return Ok(true), PopupAction::SetSourceMode(m) => self.source_mode = m, PopupAction::SetTargetMode(m) => self.target_mode = m, PopupAction::None => (), } - false + Ok(false) } } From 250fc44b2824ddc11d7b4e94daeccf7e7b4d03cb Mon Sep 17 00:00:00 2001 From: epaez Date: Thu, 13 Nov 2025 12:43:02 -0500 Subject: [PATCH 3/5] keybindings for rebase --- src/keybinds/config.rs | 1 + src/keybinds/log_tab.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/keybinds/config.rs b/src/keybinds/config.rs index 38f4329c..b11e2161 100644 --- a/src/keybinds/config.rs +++ b/src/keybinds/config.rs @@ -41,6 +41,7 @@ pub struct LogTabKeybindsConfig { pub edit_revset: Option, pub set_bookmark: Option, pub open_files: Option, + pub rebase: Option, pub push: Option, pub push_new: Option, diff --git a/src/keybinds/log_tab.rs b/src/keybinds/log_tab.rs index dd9682e9..cc43f517 100644 --- a/src/keybinds/log_tab.rs +++ b/src/keybinds/log_tab.rs @@ -133,6 +133,7 @@ impl LogTabKeybinds { LogTabEvent::EditRevset => config.edit_revset, LogTabEvent::SetBookmark => config.set_bookmark, LogTabEvent::OpenFiles => config.open_files, + LogTabEvent::Rebase => config.rebase, event_push(false, false) => config.push, event_push(false, true) => config.push_new, event_push(true, false) => config.push_all, From ef2261e1c7d383dba2705a43f3f384d5d2386943 Mon Sep 17 00:00:00 2001 From: epaez Date: Wed, 3 Dec 2025 17:58:44 -0500 Subject: [PATCH 4/5] show change id and commit id in rebase --- src/ui/log_tab.rs | 4 ++-- src/ui/rebase_popup.rs | 28 ++++++++++++++++------------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/ui/log_tab.rs b/src/ui/log_tab.rs index 08af0172..1f5d98de 100644 --- a/src/ui/log_tab.rs +++ b/src/ui/log_tab.rs @@ -201,8 +201,8 @@ impl<'a> LogTab<'a> { self.describe_after_new = describe; } LogTabEvent::Rebase => { - let source_change = commander.get_current_head()?.commit_id; - let target_change = &self.head.commit_id; + let source_change = commander.get_current_head()?; + let target_change = &self.head; self.rebase_popup = Some(RebasePopup::new( source_change.clone(), target_change.clone(), diff --git a/src/ui/rebase_popup.rs b/src/ui/rebase_popup.rs index aeeb37eb..ddd6ebf8 100644 --- a/src/ui/rebase_popup.rs +++ b/src/ui/rebase_popup.rs @@ -33,7 +33,7 @@ use ratatui::{ use crate::{ ComponentInputResult, - commander::{Commander, ids::CommitId}, + commander::{Commander, log::Head}, keybinds::rebase_popup::{CutOption, PasteOption, PopupAction}, ui::Component, }; @@ -44,19 +44,19 @@ type Keybinds = crate::keybinds::rebase_popup::Keybinds; pub struct RebasePopup { pub keybinds: Keybinds, - pub source_change: CommitId, - pub target_change: CommitId, + pub source_rev: Head, + pub target_rev: Head, pub source_mode: CutOption, pub target_mode: PasteOption, } impl RebasePopup { - pub fn new(source_change: CommitId, target_change: CommitId) -> Self { + pub fn new(source_rev: Head, target_rev: Head) -> Self { Self { keybinds: Keybinds::default(), - source_change, - target_change, + source_rev, + target_rev, source_mode: CutOption::SingleRevision, target_mode: PasteOption::NewBranch, } @@ -87,8 +87,8 @@ impl RebasePopup { /// Run the command that the popup is currently configured to do fn run_command(&self, commander: &mut Commander) -> Result<()> { - let src_rev = self.source_change.as_str(); - let tgt_rev = self.target_change.as_str(); + let src_rev = self.source_rev.commit_id.as_str(); + let tgt_rev = self.target_rev.commit_id.as_str(); let src_mode = match self.source_mode { CutOption::IncludeDescendants => "-s", CutOption::IncludeBranch => "-b", @@ -155,7 +155,8 @@ impl Component for RebasePopup { .split(area); // Radio buttons for source - let src_rev: String = self.source_change.as_str().chars().take(8).collect(); + let src_change_id: String = self.source_rev.change_id.as_str().chars().take(8).collect(); + let src_commit_id: String = self.source_rev.commit_id.as_str().chars().take(8).collect(); let src_options = vec![ "-s this and descendants", "-b whole branch", @@ -167,13 +168,16 @@ impl Component for RebasePopup { CutOption::SingleRevision => 2, }; frame.render_widget( - Paragraph::new(Span::raw(format!("Source @ ({src_rev})"))), + Paragraph::new(Span::raw(format!( + "Source @ {src_change_id} {src_commit_id}" + ))), chunks[0], ); frame.render_stateful_widget(RadioButton::new(src_options), chunks[1], &mut src_select); // Radio buttons for target - let tgt_rev: String = self.target_change.as_str().chars().take(8).collect(); + let tgt_change_id: String = self.target_rev.change_id.as_str().chars().take(8).collect(); + let tgt_commit_id: String = self.target_rev.commit_id.as_str().chars().take(8).collect(); let tgt_options = vec![ "-d rebase as new branch", "-A rebase after", @@ -185,7 +189,7 @@ impl Component for RebasePopup { PasteOption::InsertBefore => 2, }; frame.render_widget( - Paragraph::new(Span::raw(format!("Target ({tgt_rev})"))), + Paragraph::new(Span::raw(format!("Target {tgt_change_id} {tgt_commit_id}"))), chunks[2], ); frame.render_stateful_widget(RadioButton::new(tgt_options), chunks[3], &mut tgt_select); From 849d8c6a4068050df6edb43595b6bf4bb5a4007d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinbrink?= Date: Mon, 8 Dec 2025 13:22:55 +0100 Subject: [PATCH 5/5] Assigned open TODOs to @peso --- src/ui/rebase_popup.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/rebase_popup.rs b/src/ui/rebase_popup.rs index ddd6ebf8..7a3a8646 100644 --- a/src/ui/rebase_popup.rs +++ b/src/ui/rebase_popup.rs @@ -213,7 +213,7 @@ impl Component for RebasePopup { } /****************************************************************/ -// TODO: Move this function to ui::utils +// TODO(@peso): Move this function to ui::utils /// Find a rect of the given size at the center of an outside rect fn center_rect(outside: Rect, area: Size) -> Rect { @@ -226,7 +226,7 @@ fn center_rect(outside: Rect, area: Size) -> Rect { } /****************************************************************/ -// TODO: Move this widget to a separate file +// TODO(@peso): Move this widget to a separate file /** A widget for a group of radio buttons.