mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 00:48:35 +00:00
parent
bea7edf90e
commit
2ed6f53dcf
13 changed files with 599 additions and 7 deletions
|
|
@ -68,6 +68,14 @@ impl AsyncLog {
|
|||
Ok(list[min..max].to_vec())
|
||||
}
|
||||
|
||||
///
|
||||
pub fn position(&self, id: CommitId) -> Result<Option<usize>> {
|
||||
let list = self.current.lock()?;
|
||||
let position = list.iter().position(|&x| x == id);
|
||||
|
||||
Ok(position)
|
||||
}
|
||||
|
||||
///
|
||||
pub fn is_pending(&self) -> bool {
|
||||
self.pending.load(Ordering::Relaxed)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,10 @@ pub use stash::{
|
|||
get_stashes, stash_apply, stash_drop, stash_pop, stash_save,
|
||||
};
|
||||
pub use state::{repo_state, RepoState};
|
||||
pub use tags::{get_tags, CommitTags, Tags};
|
||||
pub use tags::{
|
||||
delete_tag, get_tags, get_tags_with_metadata, CommitTags,
|
||||
TagWithMetadata, Tags,
|
||||
};
|
||||
pub use tree::{tree_file_content, tree_files, TreeFile};
|
||||
pub use utils::{
|
||||
get_head, get_head_tuple, is_bare_repo, is_repo, repo_dir,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,29 @@
|
|||
use super::{utils::repo, CommitId};
|
||||
use super::{get_commits_info, utils::repo, CommitId};
|
||||
use crate::error::Result;
|
||||
use scopetime::scope_time;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
|
||||
/// all tags pointing to a single commit
|
||||
pub type CommitTags = Vec<String>;
|
||||
/// hashmap of tag target commit hash to tag names
|
||||
pub type Tags = BTreeMap<CommitId, CommitTags>;
|
||||
|
||||
///
|
||||
pub struct TagWithMetadata {
|
||||
///
|
||||
pub name: String,
|
||||
///
|
||||
pub author: String,
|
||||
///
|
||||
pub time: i64,
|
||||
///
|
||||
pub message: String,
|
||||
///
|
||||
pub commit_id: CommitId,
|
||||
}
|
||||
|
||||
static MAX_MESSAGE_WIDTH: usize = 100;
|
||||
|
||||
/// returns `Tags` type filled with all tags found in repo
|
||||
pub fn get_tags(repo_path: &str) -> Result<Tags> {
|
||||
scope_time!("get_tags");
|
||||
|
|
@ -31,8 +47,12 @@ pub fn get_tags(repo_path: &str) -> Result<Tags> {
|
|||
//NOTE: find_tag (git_tag_lookup) only works on annotated tags
|
||||
// lightweight tags `id` already points to the target commit
|
||||
// see https://github.com/libgit2/libgit2/issues/5586
|
||||
if let Ok(tag) = repo.find_tag(id) {
|
||||
adder(CommitId::new(tag.target_id()), name);
|
||||
if let Ok(commit) = repo
|
||||
.find_tag(id)
|
||||
.and_then(|tag| tag.target())
|
||||
.and_then(|target| target.peel_to_commit())
|
||||
{
|
||||
adder(CommitId::new(commit.id()), name);
|
||||
} else if repo.find_commit(id).is_ok() {
|
||||
adder(CommitId::new(id), name);
|
||||
}
|
||||
|
|
@ -45,6 +65,69 @@ pub fn get_tags(repo_path: &str) -> Result<Tags> {
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
///
|
||||
pub fn get_tags_with_metadata(
|
||||
repo_path: &str,
|
||||
) -> Result<Vec<TagWithMetadata>> {
|
||||
scope_time!("get_tags_with_metadata");
|
||||
|
||||
let tags_grouped_by_commit_id = get_tags(repo_path)?;
|
||||
|
||||
let tags_with_commit_id: Vec<(&str, &CommitId)> =
|
||||
tags_grouped_by_commit_id
|
||||
.iter()
|
||||
.flat_map(|(commit_id, tags)| {
|
||||
tags.iter()
|
||||
.map(|tag| (tag.as_ref(), commit_id))
|
||||
.collect::<Vec<(&str, &CommitId)>>()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let unique_commit_ids: HashSet<_> = tags_with_commit_id
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|(_, &commit_id)| commit_id)
|
||||
.collect();
|
||||
let mut commit_ids = Vec::with_capacity(unique_commit_ids.len());
|
||||
commit_ids.extend(unique_commit_ids);
|
||||
|
||||
let commit_infos =
|
||||
get_commits_info(repo_path, &commit_ids, MAX_MESSAGE_WIDTH)?;
|
||||
let unique_commit_infos: HashMap<_, _> = commit_infos
|
||||
.iter()
|
||||
.map(|commit_info| (commit_info.id, commit_info))
|
||||
.collect();
|
||||
|
||||
let mut tags: Vec<TagWithMetadata> = tags_with_commit_id
|
||||
.into_iter()
|
||||
.filter_map(|(tag, commit_id)| {
|
||||
unique_commit_infos.get(commit_id).map(|commit_info| {
|
||||
TagWithMetadata {
|
||||
name: String::from(tag),
|
||||
author: commit_info.author.clone(),
|
||||
time: commit_info.time,
|
||||
message: commit_info.message.clone(),
|
||||
commit_id: *commit_id,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
tags.sort_unstable_by(|a, b| b.time.cmp(&a.time));
|
||||
|
||||
Ok(tags)
|
||||
}
|
||||
|
||||
///
|
||||
pub fn delete_tag(repo_path: &str, tag_name: &str) -> Result<()> {
|
||||
scope_time!("delete_tag");
|
||||
|
||||
let repo = repo(repo_path)?;
|
||||
repo.tag_delete(tag_name)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -82,5 +165,26 @@ mod tests {
|
|||
get_tags(repo_path).unwrap()[&CommitId::new(head_id)],
|
||||
vec!["a", "b"]
|
||||
);
|
||||
|
||||
let tags = get_tags_with_metadata(repo_path).unwrap();
|
||||
|
||||
assert_eq!(tags.len(), 2);
|
||||
assert_eq!(tags[0].name, "a");
|
||||
assert_eq!(tags[0].message, "initial");
|
||||
assert_eq!(tags[1].name, "b");
|
||||
assert_eq!(tags[1].message, "initial");
|
||||
assert_eq!(tags[0].commit_id, tags[1].commit_id);
|
||||
|
||||
delete_tag(repo_path, "a").unwrap();
|
||||
|
||||
let tags = get_tags(repo_path).unwrap();
|
||||
|
||||
assert_eq!(tags.len(), 1);
|
||||
|
||||
delete_tag(repo_path, "b").unwrap();
|
||||
|
||||
let tags = get_tags(repo_path).unwrap();
|
||||
|
||||
assert_eq!(tags.len(), 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
38
src/app.rs
38
src/app.rs
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
InspectCommitComponent, MsgComponent, PullComponent,
|
||||
PushComponent, PushTagsComponent, RenameBranchComponent,
|
||||
ResetComponent, RevisionFilesComponent, StashMsgComponent,
|
||||
TagCommitComponent,
|
||||
TagCommitComponent, TagListComponent,
|
||||
},
|
||||
input::{Input, InputEvent, InputState},
|
||||
keys::{KeyConfig, SharedKeyConfig},
|
||||
|
|
@ -54,6 +54,7 @@ pub struct App {
|
|||
create_branch_popup: CreateBranchComponent,
|
||||
rename_branch_popup: RenameBranchComponent,
|
||||
select_branch_popup: BranchListComponent,
|
||||
tags_popup: TagListComponent,
|
||||
cmdbar: RefCell<CommandBar>,
|
||||
tab: usize,
|
||||
revlog: Revlog,
|
||||
|
|
@ -162,6 +163,11 @@ impl App {
|
|||
theme.clone(),
|
||||
key_config.clone(),
|
||||
),
|
||||
tags_popup: TagListComponent::new(
|
||||
&queue,
|
||||
theme.clone(),
|
||||
key_config.clone(),
|
||||
),
|
||||
do_quit: false,
|
||||
cmdbar: RefCell::new(CommandBar::new(
|
||||
theme.clone(),
|
||||
|
|
@ -397,6 +403,7 @@ impl App {
|
|||
rename_branch_popup,
|
||||
select_branch_popup,
|
||||
revision_files_popup,
|
||||
tags_popup,
|
||||
help,
|
||||
revlog,
|
||||
status_tab,
|
||||
|
|
@ -550,11 +557,26 @@ impl App {
|
|||
InternalEvent::SelectBranch => {
|
||||
self.select_branch_popup.open()?;
|
||||
}
|
||||
InternalEvent::Tags => {
|
||||
self.tags_popup.open()?;
|
||||
}
|
||||
InternalEvent::TabSwitch => self.set_tab(0)?,
|
||||
InternalEvent::InspectCommit(id, tags) => {
|
||||
self.inspect_commit_popup.open(id, tags)?;
|
||||
flags.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS)
|
||||
}
|
||||
InternalEvent::SelectCommitInRevlog(id) => {
|
||||
if let Err(error) = self.revlog.select_commit(id) {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(
|
||||
error.to_string(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
self.tags_popup.hide();
|
||||
flags.insert(NeedsUpdate::ALL)
|
||||
}
|
||||
}
|
||||
InternalEvent::OpenExternalEditor(path) => {
|
||||
self.input.set_polling(false);
|
||||
self.external_editor_popup.show()?;
|
||||
|
|
@ -620,6 +642,18 @@ impl App {
|
|||
self.select_branch_popup.update_branches()?;
|
||||
}
|
||||
}
|
||||
Action::DeleteTag(tag_name) => {
|
||||
if let Err(error) = sync::delete_tag(CWD, &tag_name) {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(
|
||||
error.to_string(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
flags.insert(NeedsUpdate::ALL);
|
||||
self.tags_popup.update_tags()?;
|
||||
}
|
||||
}
|
||||
Action::ForcePush(branch, force) => self
|
||||
.queue
|
||||
.borrow_mut()
|
||||
|
|
@ -696,6 +730,7 @@ impl App {
|
|||
|| self.push_tags_popup.is_visible()
|
||||
|| self.pull_popup.is_visible()
|
||||
|| self.select_branch_popup.is_visible()
|
||||
|| self.tags_popup.is_visible()
|
||||
|| self.rename_branch_popup.is_visible()
|
||||
|| self.revision_files_popup.is_visible()
|
||||
}
|
||||
|
|
@ -723,6 +758,7 @@ impl App {
|
|||
self.external_editor_popup.draw(f, size)?;
|
||||
self.tag_commit_popup.draw(f, size)?;
|
||||
self.select_branch_popup.draw(f, size)?;
|
||||
self.tags_popup.draw(f, size)?;
|
||||
self.create_branch_popup.draw(f, size)?;
|
||||
self.rename_branch_popup.draw(f, size)?;
|
||||
self.revision_files_popup.draw(f, size)?;
|
||||
|
|
|
|||
|
|
@ -287,6 +287,10 @@ impl CommitList {
|
|||
fn relative_selection(&self) -> usize {
|
||||
self.selection.saturating_sub(self.items.index_offset())
|
||||
}
|
||||
|
||||
pub fn select_entry(&mut self, position: usize) {
|
||||
self.selection = position;
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for CommitList {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ mod revision_files;
|
|||
mod stashmsg;
|
||||
mod syntax_text;
|
||||
mod tag_commit;
|
||||
mod taglist;
|
||||
mod textinput;
|
||||
mod utils;
|
||||
|
||||
|
|
@ -48,6 +49,7 @@ pub use revision_files::RevisionFilesComponent;
|
|||
pub use stashmsg::StashMsgComponent;
|
||||
pub use syntax_text::SyntaxTextComponent;
|
||||
pub use tag_commit::TagCommitComponent;
|
||||
pub use taglist::TagListComponent;
|
||||
pub use textinput::{InputType, TextInputComponent};
|
||||
pub use utils::filetree::FileTreeItemKind;
|
||||
|
||||
|
|
|
|||
|
|
@ -168,6 +168,15 @@ impl ResetComponent {
|
|||
branch_ref,
|
||||
),
|
||||
),
|
||||
Action::DeleteTag(tag_name) => (
|
||||
strings::confirm_title_delete_tag(
|
||||
&self.key_config,
|
||||
),
|
||||
strings::confirm_msg_delete_tag(
|
||||
&self.key_config,
|
||||
tag_name,
|
||||
),
|
||||
),
|
||||
Action::ForcePush(branch, _force) => (
|
||||
strings::confirm_title_force_push(
|
||||
&self.key_config,
|
||||
|
|
|
|||
339
src/components/taglist.rs
Normal file
339
src/components/taglist.rs
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
use super::{
|
||||
utils, visibility_blocking, CommandBlocking, CommandInfo,
|
||||
Component, DrawableComponent, EventState,
|
||||
};
|
||||
use crate::{
|
||||
components::ScrollType,
|
||||
keys::SharedKeyConfig,
|
||||
queue::{Action, InternalEvent, Queue},
|
||||
strings,
|
||||
ui::{self, Size},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::{
|
||||
sync::{get_tags_with_metadata, TagWithMetadata},
|
||||
CWD,
|
||||
};
|
||||
use crossterm::event::Event;
|
||||
use std::convert::TryInto;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Constraint, Margin, Rect},
|
||||
text::Span,
|
||||
widgets::{
|
||||
Block, BorderType, Borders, Cell, Clear, Row, Table,
|
||||
TableState,
|
||||
},
|
||||
Frame,
|
||||
};
|
||||
use ui::style::SharedTheme;
|
||||
|
||||
///
|
||||
pub struct TagListComponent {
|
||||
theme: SharedTheme,
|
||||
queue: Queue,
|
||||
tags: Option<Vec<TagWithMetadata>>,
|
||||
visible: bool,
|
||||
table_state: std::cell::Cell<TableState>,
|
||||
current_height: std::cell::Cell<usize>,
|
||||
key_config: SharedKeyConfig,
|
||||
}
|
||||
|
||||
impl DrawableComponent for TagListComponent {
|
||||
fn draw<B: Backend>(
|
||||
&self,
|
||||
f: &mut Frame<B>,
|
||||
rect: Rect,
|
||||
) -> Result<()> {
|
||||
if self.visible {
|
||||
const PERCENT_SIZE: Size = Size::new(80, 50);
|
||||
const MIN_SIZE: Size = Size::new(60, 20);
|
||||
|
||||
let area = ui::centered_rect(
|
||||
PERCENT_SIZE.width,
|
||||
PERCENT_SIZE.height,
|
||||
f.size(),
|
||||
);
|
||||
let area =
|
||||
ui::rect_inside(MIN_SIZE, f.size().into(), area);
|
||||
let area = area.intersection(rect);
|
||||
|
||||
let tag_name_width =
|
||||
self.tags.as_ref().map_or(0, |tags| {
|
||||
tags.iter()
|
||||
.fold(0, |acc, tag| acc.max(tag.name.len()))
|
||||
});
|
||||
|
||||
let constraints = [
|
||||
// tag name
|
||||
Constraint::Length(tag_name_width.try_into()?),
|
||||
// commit date
|
||||
Constraint::Length(10),
|
||||
// author width
|
||||
Constraint::Length(19),
|
||||
// commit id
|
||||
Constraint::Min(0),
|
||||
];
|
||||
|
||||
let rows = self.get_rows();
|
||||
let number_of_rows = rows.len();
|
||||
|
||||
let table = Table::new(rows)
|
||||
.widths(&constraints)
|
||||
.column_spacing(1)
|
||||
.highlight_style(self.theme.text(true, true))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(Span::styled(
|
||||
strings::title_tags(),
|
||||
self.theme.title(true),
|
||||
))
|
||||
.border_style(self.theme.block(true))
|
||||
.border_type(BorderType::Thick),
|
||||
);
|
||||
|
||||
let mut table_state = self.table_state.take();
|
||||
|
||||
f.render_widget(Clear, area);
|
||||
f.render_stateful_widget(table, area, &mut table_state);
|
||||
|
||||
let area = area.inner(&Margin {
|
||||
vertical: 1,
|
||||
horizontal: 0,
|
||||
});
|
||||
|
||||
ui::draw_scrollbar(
|
||||
f,
|
||||
area,
|
||||
&self.theme,
|
||||
number_of_rows,
|
||||
table_state.selected().unwrap_or(0),
|
||||
);
|
||||
|
||||
self.table_state.set(table_state);
|
||||
self.current_height.set(area.height.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for TagListComponent {
|
||||
fn commands(
|
||||
&self,
|
||||
out: &mut Vec<CommandInfo>,
|
||||
force_all: bool,
|
||||
) -> CommandBlocking {
|
||||
if self.visible || force_all {
|
||||
out.clear();
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::scroll(&self.key_config),
|
||||
true,
|
||||
true,
|
||||
));
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::close_popup(&self.key_config),
|
||||
true,
|
||||
true,
|
||||
));
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::delete_tag_popup(&self.key_config),
|
||||
self.valid_selection(),
|
||||
true,
|
||||
));
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::select_tag(&self.key_config),
|
||||
self.valid_selection(),
|
||||
true,
|
||||
));
|
||||
}
|
||||
visibility_blocking(self)
|
||||
}
|
||||
|
||||
fn event(&mut self, event: Event) -> Result<EventState> {
|
||||
if self.visible {
|
||||
if let Event::Key(key) = event {
|
||||
if key == self.key_config.exit_popup {
|
||||
self.hide()
|
||||
} else if key == self.key_config.move_up {
|
||||
self.move_selection(ScrollType::Up);
|
||||
} else if key == self.key_config.move_down {
|
||||
self.move_selection(ScrollType::Down);
|
||||
} else if key == self.key_config.shift_up
|
||||
|| key == self.key_config.home
|
||||
{
|
||||
self.move_selection(ScrollType::Home);
|
||||
} else if key == self.key_config.shift_down
|
||||
|| key == self.key_config.end
|
||||
{
|
||||
self.move_selection(ScrollType::End);
|
||||
} else if key == self.key_config.page_down {
|
||||
self.move_selection(ScrollType::PageDown);
|
||||
} else if key == self.key_config.page_up {
|
||||
self.move_selection(ScrollType::PageUp);
|
||||
} else if key == self.key_config.delete_tag {
|
||||
return self.selected_tag().map_or(
|
||||
Ok(EventState::NotConsumed),
|
||||
|tag| {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ConfirmAction(
|
||||
Action::DeleteTag(
|
||||
tag.name.clone(),
|
||||
),
|
||||
),
|
||||
);
|
||||
Ok(EventState::Consumed)
|
||||
},
|
||||
);
|
||||
} else if key == self.key_config.select_tag {
|
||||
return self.selected_tag().map_or(
|
||||
Ok(EventState::NotConsumed),
|
||||
|tag| {
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::SelectCommitInRevlog(
|
||||
tag.commit_id,
|
||||
),
|
||||
);
|
||||
Ok(EventState::Consumed)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(EventState::Consumed)
|
||||
} else {
|
||||
Ok(EventState::NotConsumed)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
fn hide(&mut self) {
|
||||
self.visible = false
|
||||
}
|
||||
|
||||
fn show(&mut self) -> Result<()> {
|
||||
self.visible = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TagListComponent {
|
||||
pub fn new(
|
||||
queue: &Queue,
|
||||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
) -> Self {
|
||||
Self {
|
||||
theme,
|
||||
queue: queue.clone(),
|
||||
tags: None,
|
||||
visible: false,
|
||||
table_state: std::cell::Cell::new(TableState::default()),
|
||||
current_height: std::cell::Cell::new(0),
|
||||
key_config,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn open(&mut self) -> Result<()> {
|
||||
self.table_state.get_mut().select(Some(0));
|
||||
self.show()?;
|
||||
|
||||
self.update_tags()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// fetch list of tags
|
||||
pub fn update_tags(&mut self) -> Result<()> {
|
||||
let tags = get_tags_with_metadata(CWD)?;
|
||||
|
||||
self.tags = Some(tags);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
fn move_selection(&mut self, scroll_type: ScrollType) -> bool {
|
||||
let mut table_state = self.table_state.take();
|
||||
|
||||
let old_selection = table_state.selected().unwrap_or(0);
|
||||
let max_selection =
|
||||
self.tags.as_ref().map_or(0, |tags| tags.len() - 1);
|
||||
|
||||
let new_selection = match scroll_type {
|
||||
ScrollType::Up => old_selection.saturating_sub(1),
|
||||
ScrollType::Down => {
|
||||
old_selection.saturating_add(1).min(max_selection)
|
||||
}
|
||||
ScrollType::Home => 0,
|
||||
ScrollType::End => max_selection,
|
||||
ScrollType::PageUp => old_selection.saturating_sub(
|
||||
self.current_height.get().saturating_sub(1),
|
||||
),
|
||||
ScrollType::PageDown => old_selection
|
||||
.saturating_add(
|
||||
self.current_height.get().saturating_sub(1),
|
||||
)
|
||||
.min(max_selection),
|
||||
};
|
||||
|
||||
let needs_update = new_selection != old_selection;
|
||||
|
||||
table_state.select(Some(new_selection));
|
||||
self.table_state.set(table_state);
|
||||
|
||||
needs_update
|
||||
}
|
||||
|
||||
///
|
||||
fn get_rows(&self) -> Vec<Row> {
|
||||
if let Some(ref tags) = self.tags {
|
||||
tags.iter().map(|tag| self.get_row(tag)).collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
fn get_row(&self, tag: &TagWithMetadata) -> Row {
|
||||
let cells: Vec<Cell> = vec![
|
||||
Cell::from(tag.name.clone())
|
||||
.style(self.theme.text(true, false)),
|
||||
Cell::from(utils::time_to_string(tag.time, true))
|
||||
.style(self.theme.commit_time(false)),
|
||||
Cell::from(tag.author.clone())
|
||||
.style(self.theme.commit_author(false)),
|
||||
Cell::from(tag.message.clone())
|
||||
.style(self.theme.text(true, false)),
|
||||
];
|
||||
|
||||
Row::new(cells)
|
||||
}
|
||||
|
||||
fn valid_selection(&self) -> bool {
|
||||
self.selected_tag().is_some()
|
||||
}
|
||||
|
||||
fn selected_tag(&self) -> Option<&TagWithMetadata> {
|
||||
self.tags.as_ref().and_then(|tags| {
|
||||
let table_state = self.table_state.take();
|
||||
|
||||
let tag = table_state
|
||||
.selected()
|
||||
.and_then(|selected| tags.get(selected));
|
||||
|
||||
self.table_state.set(table_state);
|
||||
|
||||
tag
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +72,9 @@ pub struct KeyConfig {
|
|||
pub select_branch: KeyEvent,
|
||||
pub delete_branch: KeyEvent,
|
||||
pub merge_branch: KeyEvent,
|
||||
pub tags: KeyEvent,
|
||||
pub delete_tag: KeyEvent,
|
||||
pub select_tag: KeyEvent,
|
||||
pub push: KeyEvent,
|
||||
pub open_file_tree: KeyEvent,
|
||||
pub force_push: KeyEvent,
|
||||
|
|
@ -134,6 +137,9 @@ impl Default for KeyConfig {
|
|||
select_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()},
|
||||
delete_branch: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT},
|
||||
merge_branch: KeyEvent { code: KeyCode::Char('m'), modifiers: KeyModifiers::empty()},
|
||||
tags: KeyEvent { code: KeyCode::Char('T'), modifiers: KeyModifiers::SHIFT},
|
||||
delete_tag: KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT},
|
||||
select_tag: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()},
|
||||
push: KeyEvent { code: KeyCode::Char('p'), modifiers: KeyModifiers::empty()},
|
||||
force_push: KeyEvent { code: KeyCode::Char('P'), modifiers: KeyModifiers::SHIFT},
|
||||
pull: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ pub enum Action {
|
|||
StashDrop(CommitId),
|
||||
StashPop(CommitId),
|
||||
DeleteBranch(String),
|
||||
DeleteTag(String),
|
||||
ForcePush(String, bool),
|
||||
PullMerge { incoming: usize, rebase: bool },
|
||||
AbortMerge,
|
||||
|
|
@ -59,8 +60,12 @@ pub enum InternalEvent {
|
|||
///
|
||||
InspectCommit(CommitId, Option<CommitTags>),
|
||||
///
|
||||
SelectCommitInRevlog(CommitId),
|
||||
///
|
||||
TagCommit(CommitId),
|
||||
///
|
||||
Tags,
|
||||
///
|
||||
BlameFile(String),
|
||||
///
|
||||
CreateBranch,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ pub static PUSH_TAGS_STATES_DONE: &str = "done";
|
|||
pub fn title_branches() -> String {
|
||||
"Branches".to_string()
|
||||
}
|
||||
pub fn title_tags() -> String {
|
||||
"Tags".to_string()
|
||||
}
|
||||
pub fn title_status(_key_config: &SharedKeyConfig) -> String {
|
||||
"Unstaged Changes".to_string()
|
||||
}
|
||||
|
|
@ -165,6 +168,17 @@ pub fn confirm_msg_delete_branch(
|
|||
) -> String {
|
||||
format!("Confirm deleting branch: '{}' ?", branch_ref)
|
||||
}
|
||||
pub fn confirm_title_delete_tag(
|
||||
_key_config: &SharedKeyConfig,
|
||||
) -> String {
|
||||
"Delete Tag".to_string()
|
||||
}
|
||||
pub fn confirm_msg_delete_tag(
|
||||
_key_config: &SharedKeyConfig,
|
||||
tag_name: &str,
|
||||
) -> String {
|
||||
format!("Confirm deleting Tag: '{}' ?", tag_name)
|
||||
}
|
||||
pub fn confirm_title_force_push(
|
||||
_key_config: &SharedKeyConfig,
|
||||
) -> String {
|
||||
|
|
@ -999,6 +1013,41 @@ pub mod commands {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn open_tags_popup(
|
||||
key_config: &SharedKeyConfig,
|
||||
) -> CommandText {
|
||||
CommandText::new(
|
||||
format!(
|
||||
"Tags [{}]",
|
||||
key_config.get_hint(key_config.tags),
|
||||
),
|
||||
"open tags popup",
|
||||
CMD_GROUP_GENERAL,
|
||||
)
|
||||
}
|
||||
pub fn delete_tag_popup(
|
||||
key_config: &SharedKeyConfig,
|
||||
) -> CommandText {
|
||||
CommandText::new(
|
||||
format!(
|
||||
"Delete [{}]",
|
||||
key_config.get_hint(key_config.delete_tag),
|
||||
),
|
||||
"delete a tag",
|
||||
CMD_GROUP_GENERAL,
|
||||
)
|
||||
}
|
||||
pub fn select_tag(key_config: &SharedKeyConfig) -> CommandText {
|
||||
CommandText::new(
|
||||
format!(
|
||||
"Select commit [{}]",
|
||||
key_config.get_hint(key_config.select_tag),
|
||||
),
|
||||
"Select commit in revlog",
|
||||
CMD_GROUP_GENERAL,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn status_push(key_config: &SharedKeyConfig) -> CommandText {
|
||||
CommandText::new(
|
||||
format!(
|
||||
|
|
|
|||
|
|
@ -166,6 +166,18 @@ impl Revlog {
|
|||
tags.and_then(|tags| tags.get(&commit).cloned())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn select_commit(&mut self, id: CommitId) -> Result<()> {
|
||||
let position = self.git_log.position(id)?;
|
||||
|
||||
if let Some(position) = position {
|
||||
self.list.select_entry(position);
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
anyhow::bail!("Could not select commit in revlog. It might not be loaded yet or it might be on a different branch.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrawableComponent for Revlog {
|
||||
|
|
@ -259,6 +271,11 @@ impl Component for Revlog {
|
|||
Ok(EventState::Consumed)
|
||||
},
|
||||
);
|
||||
} else if k == self.key_config.tags {
|
||||
self.queue
|
||||
.borrow_mut()
|
||||
.push_back(InternalEvent::Tags);
|
||||
return Ok(EventState::Consumed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -302,6 +319,12 @@ impl Component for Revlog {
|
|||
self.visible || force_all,
|
||||
));
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::open_tags_popup(&self.key_config),
|
||||
true,
|
||||
self.visible || force_all,
|
||||
));
|
||||
|
||||
out.push(CommandInfo::new(
|
||||
strings::commands::copy_hash(&self.key_config),
|
||||
self.selected_commit().is_some(),
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
status_stage_all: ( code: Char('a'), modifiers: ( bits: 0,),),
|
||||
status_reset_item: ( code: Char('U'), modifiers: ( bits: 1,),),
|
||||
status_ignore_file: ( code: Char('i'), modifiers: ( bits: 0,),),
|
||||
|
||||
|
||||
diff_reset_lines: ( code: Char('u'), modifiers: ( bits: 0,),),
|
||||
diff_stage_lines: ( code: Char('s'), modifiers: ( bits: 0,),),
|
||||
|
||||
|
|
@ -79,6 +79,10 @@
|
|||
merge_branch: ( code: Char('m'), modifiers: ( bits: 0,),),
|
||||
abort_merge: ( code: Char('M'), modifiers: ( bits: 1,),),
|
||||
|
||||
tags: ( code: Char('T'), modifiers: ( bits: 1,),),
|
||||
delete_tag: ( code: Char('D'), modifiers: ( bits: 1,),),
|
||||
select_tag: ( code: Enter, modifiers: ( bits: 0,),),
|
||||
|
||||
push: ( code: Char('p'), modifiers: ( bits: 0,),),
|
||||
force_push: ( code: Char('P'), modifiers: ( bits: 1,),),
|
||||
pull: ( code: Char('f'), modifiers: ( bits: 0,),),
|
||||
|
|
|
|||
Loading…
Reference in a new issue