drop multiple stashes (#855)

This commit is contained in:
Stephan Dilly 2021-08-18 01:41:33 +02:00 committed by GitHub
parent 5c694bd696
commit 2b85b81a3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 137 additions and 49 deletions

View File

@ -7,12 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
**drop multiple stashes**
![drop-multiple-stashes](assets/drop-multiple-stashes.gif)
**branch name validation**
![name-validation](assets/branch-validation.gif)
## Added
- mark and drop multiple stashes ([#854](https://github.com/extrawurst/gitui/issues/854))
- check branch name validity while typing ([#559](https://github.com/extrawurst/gitui/issues/559))
- support deleting remote branch [[@zcorniere](https://github.com/zcorniere)] ([#622](https://github.com/extrawurst/gitui/issues/622))

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -4,12 +4,12 @@ use crate::{
components::{
event_pump, BlameFileComponent, BranchListComponent,
CommandBlocking, CommandInfo, CommitComponent, Component,
CreateBranchComponent, DrawableComponent,
ConfirmComponent, CreateBranchComponent, DrawableComponent,
ExternalEditorComponent, HelpComponent,
InspectCommitComponent, MsgComponent, PullComponent,
PushComponent, PushTagsComponent, RenameBranchComponent,
ResetComponent, RevisionFilesPopup, StashMsgComponent,
TagCommitComponent, TagListComponent,
RevisionFilesPopup, StashMsgComponent, TagCommitComponent,
TagListComponent,
},
input::{Input, InputEvent, InputState},
keys::{KeyConfig, SharedKeyConfig},
@ -42,7 +42,7 @@ pub struct App {
do_quit: bool,
help: HelpComponent,
msg: MsgComponent,
reset: ResetComponent,
reset: ConfirmComponent,
commit: CommitComponent,
blame_file_popup: BlameFileComponent,
stashmsg_popup: StashMsgComponent,
@ -91,7 +91,7 @@ impl App {
Self {
input,
reset: ResetComponent::new(
reset: ConfirmComponent::new(
queue.clone(),
theme.clone(),
key_config.clone(),
@ -682,9 +682,13 @@ impl App {
}
}
Action::StashDrop(_) | Action::StashPop(_) => {
if self.stashlist_tab.action_confirmed(&action) {
flags.insert(NeedsUpdate::ALL);
if let Err(e) = StashList::action_confirmed(&action) {
self.queue.push(InternalEvent::ShowErrorMsg(
e.to_string(),
));
}
flags.insert(NeedsUpdate::ALL);
}
Action::ResetHunk(path, hash) => {
sync::reset_hunk(CWD, &path, hash)?;

View File

@ -5,7 +5,7 @@ use crate::{
Component, DrawableComponent, EventState, ScrollType,
},
keys::SharedKeyConfig,
strings,
strings::{self, symbol},
ui::calc_scroll_top,
ui::style::{SharedTheme, Theme},
};
@ -120,6 +120,23 @@ impl CommitList {
)
}
///
pub fn selected_entry_marked(&self) -> bool {
self.selected_entry()
.and_then(|e| self.is_marked(&e.id))
.unwrap_or_default()
}
///
pub fn marked_count(&self) -> usize {
self.marked.len()
}
///
pub fn marked(&self) -> &[CommitId] {
&self.marked
}
pub fn copy_entry_hash(&self) -> Result<()> {
if let Some(e) = self.items.iter().nth(
self.selection.saturating_sub(self.items.index_offset()),
@ -223,14 +240,18 @@ impl CommitList {
ELEMENTS_PER_LINE + if marked.is_some() { 2 } else { 0 },
);
let splitter_txt = Cow::from(" ");
let splitter_txt = Cow::from(symbol::EMPTY_SPACE);
let splitter =
Span::styled(splitter_txt, theme.text(true, selected));
// marker
if let Some(marked) = marked {
txt.push(Span::styled(
Cow::from(if marked { "\u{2713}" } else { " " }),
Cow::from(if marked {
symbol::CHECKMARK
} else {
symbol::EMPTY_SPACE
}),
theme.log_marker(selected),
));
txt.push(splitter.clone());
@ -433,6 +454,14 @@ impl Component for CommitList {
self.selected_entry().is_some(),
true,
));
out.push(CommandInfo::new(
strings::commands::commit_list_mark(
&self.key_config,
self.selected_entry_marked(),
),
true,
true,
));
CommandBlocking::PassingOn
}
}

View File

@ -45,7 +45,7 @@ pub use pull::PullComponent;
pub use push::PushComponent;
pub use push_tags::PushTagsComponent;
pub use rename_branch::RenameBranchComponent;
pub use reset::ResetComponent;
pub use reset::ConfirmComponent;
pub use revision_files::RevisionFilesComponent;
pub use revision_files_popup::RevisionFilesPopup;
pub use stashmsg::StashMsgComponent;

View File

@ -16,7 +16,7 @@ use tui::{
use ui::style::SharedTheme;
///
pub struct ResetComponent {
pub struct ConfirmComponent {
target: Option<Action>,
visible: bool,
queue: Queue,
@ -24,7 +24,7 @@ pub struct ResetComponent {
key_config: SharedKeyConfig,
}
impl DrawableComponent for ResetComponent {
impl DrawableComponent for ConfirmComponent {
fn draw<B: Backend>(
&self,
f: &mut Frame<B>,
@ -50,7 +50,7 @@ impl DrawableComponent for ResetComponent {
}
}
impl Component for ResetComponent {
impl Component for ConfirmComponent {
fn commands(
&self,
out: &mut Vec<CommandInfo>,
@ -101,7 +101,7 @@ impl Component for ResetComponent {
}
}
impl ResetComponent {
impl ConfirmComponent {
///
pub fn new(
queue: Queue,
@ -139,11 +139,11 @@ impl ResetComponent {
strings::confirm_title_reset(),
strings::confirm_msg_reset(),
),
Action::StashDrop(_) => (
Action::StashDrop(ids) => (
strings::confirm_title_stashdrop(
&self.key_config,
&self.key_config,ids.len()>1
),
strings::confirm_msg_stashdrop(&self.key_config),
strings::confirm_msg_stashdrop(&self.key_config,ids),
),
Action::StashPop(_) => (
strings::confirm_title_stashpop(&self.key_config),

View File

@ -1,3 +1,4 @@
use crate::strings::symbol;
use crate::ui::Size;
use crate::{
components::{
@ -169,7 +170,7 @@ impl TextInputComponent {
let cursor_highlighting = {
let mut h = HashMap::with_capacity(2);
h.insert("\n", "\u{21b5}\n\r");
h.insert(" ", "\u{00B7}");
h.insert(" ", symbol::WHITESPACE);
h
};
@ -470,7 +471,10 @@ mod tests {
get_style(&txt.lines[0].0[0]),
Some(&not_underlined)
);
assert_eq!(get_text(&txt.lines[0].0[1]), Some("\u{00B7}"));
assert_eq!(
get_text(&txt.lines[0].0[1]),
Some(symbol::WHITESPACE)
);
assert_eq!(
get_style(&txt.lines[0].0[1]),
Some(&underlined_whitespace)

View File

@ -15,7 +15,7 @@ use std::{
rc::Rc,
};
use crate::args::get_app_config_path;
use crate::{args::get_app_config_path, strings::symbol};
pub type SharedKeyConfig = Rc<KeyConfig>;
@ -248,6 +248,7 @@ impl KeyConfig {
self.get_key_symbol(ev.code)
)
}
KeyCode::Char(' ') => String::from(symbol::SPACE),
KeyCode::Char(c) => {
format!(
"{}{}",

View File

@ -30,7 +30,7 @@ pub enum Action {
Reset(ResetItem),
ResetHunk(String, u64),
ResetLines(String, Vec<DiffLinePosition>),
StashDrop(CommitId),
StashDrop(Vec<CommitId>),
StashPop(CommitId),
DeleteBranch(String, bool),
DeleteTag(String),

View File

@ -1,3 +1,5 @@
use asyncgit::sync::CommitId;
use crate::keys::SharedKeyConfig;
pub mod order {
@ -20,6 +22,13 @@ pub static PUSH_TAGS_STATES_FETCHING: &str = "fetching";
pub static PUSH_TAGS_STATES_PUSHING: &str = "pushing";
pub static PUSH_TAGS_STATES_DONE: &str = "done";
pub mod symbol {
pub const WHITESPACE: &str = "\u{00B7}"; //·
pub const CHECKMARK: &str = "\u{2713}"; //✓
pub const SPACE: &str = "\u{02FD}"; //˽
pub const EMPTY_SPACE: &str = " ";
}
pub fn title_branches() -> String {
"Branches".to_string()
}
@ -103,8 +112,9 @@ pub fn confirm_title_reset() -> String {
}
pub fn confirm_title_stashdrop(
_key_config: &SharedKeyConfig,
multiple: bool,
) -> String {
"Drop".to_string()
format!("Drop Stash{}", if multiple { "es" } else { "" })
}
pub fn confirm_title_stashpop(
_key_config: &SharedKeyConfig,
@ -151,8 +161,21 @@ pub fn confirm_msg_reset_lines(lines: usize) -> String {
}
pub fn confirm_msg_stashdrop(
_key_config: &SharedKeyConfig,
ids: &[CommitId],
) -> String {
"confirm stash drop?".to_string()
format!(
"Sure you want to drop following {}stash{}?\n\n{}",
if ids.len() > 1 {
format!("{} ", ids.len())
} else {
String::default()
},
if ids.len() > 1 { "es" } else { "" },
ids.iter()
.map(CommitId::get_short_string)
.collect::<Vec<_>>()
.join(", ")
)
}
pub fn confirm_msg_stashpop(_key_config: &SharedKeyConfig) -> String {
"The stash will be applied and removed from the stash list. Confirm stash pop?"
@ -399,6 +422,20 @@ pub mod commands {
CMD_GROUP_GENERAL,
)
}
pub fn commit_list_mark(
key_config: &SharedKeyConfig,
marked: bool,
) -> CommandText {
CommandText::new(
format!(
"{} [{}]",
if marked { "Unmark" } else { "Mark" },
key_config.get_hint(key_config.log_mark_commit),
),
"mark multiple commits",
CMD_GROUP_GENERAL,
)
}
pub fn copy(key_config: &SharedKeyConfig) -> CommandText {
CommandText::new(
format!(
@ -828,10 +865,16 @@ pub mod commands {
}
pub fn stashlist_drop(
key_config: &SharedKeyConfig,
marked: usize,
) -> CommandText {
CommandText::new(
format!(
"Drop [{}]",
"Drop{} [{}]",
if marked == 0 {
String::default()
} else {
format!(" {}", marked)
},
key_config.get_hint(key_config.stash_drop),
),
"drop selected stash",

View File

@ -71,9 +71,13 @@ impl StashList {
}
fn drop_stash(&mut self) {
if let Some(e) = self.list.selected_entry() {
if self.list.marked_count() > 0 {
self.queue.push(InternalEvent::ConfirmAction(
Action::StashDrop(e.id),
Action::StashDrop(self.list.marked().to_vec()),
));
} else if let Some(e) = self.list.selected_entry() {
self.queue.push(InternalEvent::ConfirmAction(
Action::StashDrop(vec![e.id]),
));
}
}
@ -93,31 +97,27 @@ impl StashList {
}
/// Called when a pending stash action has been confirmed
pub fn action_confirmed(&self, action: &Action) -> bool {
match *action {
Action::StashDrop(id) => Self::drop(id),
Action::StashPop(id) => self.pop(id),
_ => false,
}
pub fn action_confirmed(action: &Action) -> Result<()> {
match action {
Action::StashDrop(ids) => Self::drop(ids)?,
Action::StashPop(id) => Self::pop(*id)?,
_ => (),
};
Ok(())
}
fn drop(id: CommitId) -> bool {
sync::stash_drop(CWD, id).is_ok()
fn drop(ids: &[CommitId]) -> Result<()> {
for id in ids {
sync::stash_drop(CWD, *id)?;
}
fn pop(&self, id: CommitId) -> bool {
match sync::stash_pop(CWD, id) {
Ok(_) => {
self.queue.push(InternalEvent::TabSwitch);
true
}
Err(e) => {
self.queue.push(InternalEvent::ShowErrorMsg(
format!("stash pop error:\n{}", e,),
));
true
}
Ok(())
}
fn pop(id: CommitId) -> Result<()> {
sync::stash_pop(CWD, id)?;
Ok(())
}
}
@ -155,7 +155,10 @@ impl Component for StashList {
true,
));
out.push(CommandInfo::new(
strings::commands::stashlist_drop(&self.key_config),
strings::commands::stashlist_drop(
&self.key_config,
self.list.marked_count(),
),
selection_valid,
true,
));