mirror of
https://github.com/extrawurst/gitui.git
synced 2024-11-24 23:21:19 +03:00
drop multiple stashes (#855)
This commit is contained in:
parent
5c694bd696
commit
2b85b81a3e
@ -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))
|
||||
|
||||
|
BIN
assets/drop-multiple-stashes.gif
Normal file
BIN
assets/drop-multiple-stashes.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
18
src/app.rs
18
src/app.rs
@ -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)?;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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(¬_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)
|
||||
|
@ -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!(
|
||||
"{}{}",
|
||||
|
@ -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),
|
||||
|
@ -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",
|
||||
|
@ -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)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
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,
|
||||
));
|
||||
|
Loading…
Reference in New Issue
Block a user