new 'create branch' popup (#254)

closes #253
This commit is contained in:
Stephan Dilly 2020-08-29 14:15:06 +02:00 committed by GitHub
parent 7da34eb3e4
commit 4907e8b727
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 307 additions and 16 deletions

View File

@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
![scrollbar](assets/scrollbar.gif)
- allow creating new branch ([#253](https://github.com/extrawurst/gitui/issues/253))
### Fixed
- selection error in stashlist when deleting last element ([#223](https://github.com/extrawurst/gitui/issues/223))

View File

@ -58,4 +58,5 @@
log_tag_commit: ( code: Char('t'), modifiers: ( bits: 0,),),
commit_amend: ( code: Char('A'), modifiers: ( bits: 0,),),
copy: ( code: Char('y'), modifiers: ( bits: 0,),),
create_branch: ( code: Char('b'), modifiers: ( bits: 0,),),
)

View File

@ -1,11 +1,9 @@
use crate::{
error::Result,
sync::{self, CommitId},
};
use crate::{error::Result, sync};
use sync::Head;
///
pub struct BranchName {
last_result: Option<(CommitId, String)>,
last_result: Option<(Head, String)>,
repo_path: String,
}
@ -20,7 +18,8 @@ impl BranchName {
///
pub fn lookup(&mut self) -> Result<String> {
let current_head = sync::get_head(self.repo_path.as_str())?;
let current_head =
sync::get_head_tuple(self.repo_path.as_str())?;
if let Some((last_head, branch_name)) =
self.last_result.as_ref()
@ -33,7 +32,7 @@ impl BranchName {
self.fetch(current_head)
}
fn fetch(&mut self, head: CommitId) -> Result<String> {
fn fetch(&mut self, head: Head) -> Result<String> {
let name = sync::get_branch_name(self.repo_path.as_str())?;
self.last_result = Some((head, name.clone()));
Ok(name)

View File

@ -1,3 +1,4 @@
use std::string::FromUtf8Error;
use thiserror::Error;
#[derive(Error, Debug)]
@ -13,6 +14,9 @@ pub enum Error {
#[error("git error:{0}")]
Git(#[from] git2::Error),
#[error("utf8 error:{0}")]
Utf8Error(#[from] FromUtf8Error),
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -5,6 +5,7 @@ use crate::{
sync::utils,
};
use scopetime::scope_time;
use utils::get_head_repo;
/// returns the branch-name head is currently pointing to
/// this might be expensive, see `cached::BranchName`
@ -27,8 +28,26 @@ pub(crate) fn get_branch_name(repo_path: &str) -> Result<String> {
Err(Error::NoHead)
}
/// creates a new branch pointing to current HEAD commit and updating HEAD to new branch
pub fn create_branch(repo_path: &str, name: &str) -> Result<()> {
scope_time!("create_branch");
let repo = utils::repo(repo_path)?;
let head_id = get_head_repo(&repo)?;
let head_commit = repo.find_commit(head_id.into())?;
let branch = repo.branch(name, &head_commit, false)?;
let branch_ref = branch.into_reference();
let branch_ref_name =
String::from_utf8(branch_ref.name_bytes().to_vec())?;
repo.set_head(branch_ref_name.as_str())?;
Ok(())
}
#[cfg(test)]
mod tests {
mod tests_branch_name {
use super::*;
use crate::sync::tests::{repo_init, repo_init_empty};
@ -56,3 +75,23 @@ mod tests {
));
}
}
#[cfg(test)]
mod tests_create_branch {
use super::*;
use crate::sync::tests::repo_init;
#[test]
fn test_smoke() {
let (_td, repo) = repo_init().unwrap();
let root = repo.path().parent().unwrap();
let repo_path = root.as_os_str().to_str().unwrap();
create_branch(repo_path, "branch1").unwrap();
assert_eq!(
get_branch_name(repo_path).unwrap().as_str(),
"branch1"
);
}
}

View File

@ -16,8 +16,8 @@ pub mod status;
mod tags;
pub mod utils;
pub use branch::create_branch;
pub(crate) use branch::get_branch_name;
pub use commit::{amend, commit, tag};
pub use commit_details::{
get_commit_details, CommitDetails, CommitMessage,
@ -33,8 +33,8 @@ pub use reset::{reset_stage, reset_workdir};
pub use stash::{get_stashes, stash_apply, stash_drop, stash_save};
pub use tags::{get_tags, CommitTags, Tags};
pub use utils::{
get_head, is_bare_repo, is_repo, stage_add_all, stage_add_file,
stage_addremoved,
get_head, get_head_tuple, is_bare_repo, is_repo, stage_add_all,
stage_add_file, stage_addremoved, Head,
};
#[cfg(test)]

View File

@ -6,6 +6,15 @@ use git2::{IndexAddOption, Repository, RepositoryOpenFlags};
use scopetime::scope_time;
use std::path::Path;
///
#[derive(PartialEq, Debug, Clone)]
pub struct Head {
///
pub name: String,
///
pub id: CommitId,
}
///
pub fn is_repo(repo_path: &str) -> bool {
Repository::open_ext(
@ -63,6 +72,24 @@ pub fn get_head(repo_path: &str) -> Result<CommitId> {
get_head_repo(&repo)
}
///
pub fn get_head_tuple(repo_path: &str) -> Result<Head> {
let repo = repo(repo_path)?;
let id = get_head_repo(&repo)?;
let name = get_head_refname(&repo)?;
Ok(Head { name, id })
}
///
pub fn get_head_refname(repo: &Repository) -> Result<String> {
let head = repo.head()?;
let name_bytes = head.name_bytes();
let ref_name = String::from_utf8(name_bytes.to_vec())?;
Ok(ref_name)
}
///
pub fn get_head_repo(repo: &Repository) -> Result<CommitId> {
scope_time!("get_head_repo");

View File

@ -3,9 +3,10 @@ use crate::{
cmdbar::CommandBar,
components::{
event_pump, CommandBlocking, CommandInfo, CommitComponent,
Component, DrawableComponent, ExternalEditorComponent,
HelpComponent, InspectCommitComponent, MsgComponent,
ResetComponent, StashMsgComponent, TagCommitComponent,
Component, CreateBranchComponent, DrawableComponent,
ExternalEditorComponent, HelpComponent,
InspectCommitComponent, MsgComponent, ResetComponent,
StashMsgComponent, TagCommitComponent,
},
input::{Input, InputEvent, InputState},
keys::{KeyConfig, SharedKeyConfig},
@ -41,6 +42,7 @@ pub struct App {
inspect_commit_popup: InspectCommitComponent,
external_editor_popup: ExternalEditorComponent,
tag_commit_popup: TagCommitComponent,
create_branch_popup: CreateBranchComponent,
cmdbar: RefCell<CommandBar>,
tab: usize,
revlog: Revlog,
@ -101,6 +103,11 @@ impl App {
theme.clone(),
key_config.clone(),
),
create_branch_popup: CreateBranchComponent::new(
queue.clone(),
theme.clone(),
key_config.clone(),
),
do_quit: false,
cmdbar: RefCell::new(CommandBar::new(
theme.clone(),
@ -331,6 +338,7 @@ impl App {
inspect_commit_popup,
external_editor_popup,
tag_commit_popup,
create_branch_popup,
help,
revlog,
status_tab,
@ -459,6 +467,9 @@ impl App {
InternalEvent::TagCommit(id) => {
self.tag_commit_popup.open(id)?;
}
InternalEvent::CreateBranch => {
self.create_branch_popup.open()?;
}
InternalEvent::TabSwitch => self.set_tab(0)?,
InternalEvent::InspectCommit(id, tags) => {
self.inspect_commit_popup.open(id, tags)?;
@ -527,6 +538,7 @@ impl App {
|| self.inspect_commit_popup.is_visible()
|| self.external_editor_popup.is_visible()
|| self.tag_commit_popup.is_visible()
|| self.create_branch_popup.is_visible()
}
fn draw_popups<B: Backend>(
@ -552,6 +564,7 @@ impl App {
self.msg.draw(f, size)?;
self.external_editor_popup.draw(f, size)?;
self.tag_commit_popup.draw(f, size)?;
self.create_branch_popup.draw(f, size)?;
Ok(())
}

View File

@ -0,0 +1,144 @@
use super::{
textinput::TextInputComponent, visibility_blocking,
CommandBlocking, CommandInfo, Component, DrawableComponent,
};
use crate::{
keys::SharedKeyConfig,
queue::{InternalEvent, NeedsUpdate, Queue},
strings,
ui::style::SharedTheme,
};
use anyhow::Result;
use asyncgit::{
sync::{self, CommitId},
CWD,
};
use crossterm::event::Event;
use tui::{backend::Backend, layout::Rect, Frame};
pub struct CreateBranchComponent {
input: TextInputComponent,
commit_id: Option<CommitId>,
queue: Queue,
key_config: SharedKeyConfig,
}
impl DrawableComponent for CreateBranchComponent {
fn draw<B: Backend>(
&self,
f: &mut Frame<B>,
rect: Rect,
) -> Result<()> {
self.input.draw(f, rect)?;
Ok(())
}
}
impl Component for CreateBranchComponent {
fn commands(
&self,
out: &mut Vec<CommandInfo>,
force_all: bool,
) -> CommandBlocking {
if self.is_visible() || force_all {
self.input.commands(out, force_all);
out.push(CommandInfo::new(
strings::commands::create_branch_confirm_msg(
&self.key_config,
),
true,
true,
));
}
visibility_blocking(self)
}
fn event(&mut self, ev: Event) -> Result<bool> {
if self.is_visible() {
if self.input.event(ev)? {
return Ok(true);
}
if let Event::Key(e) = ev {
if e == self.key_config.enter {
self.create_branch();
}
return Ok(true);
}
}
Ok(false)
}
fn is_visible(&self) -> bool {
self.input.is_visible()
}
fn hide(&mut self) {
self.input.hide()
}
fn show(&mut self) -> Result<()> {
self.input.show()?;
Ok(())
}
}
impl CreateBranchComponent {
///
pub fn new(
queue: Queue,
theme: SharedTheme,
key_config: SharedKeyConfig,
) -> Self {
Self {
queue,
input: TextInputComponent::new(
theme,
key_config.clone(),
&strings::create_branch_popup_title(&key_config),
&strings::create_branch_popup_msg(&key_config),
),
commit_id: None,
key_config,
}
}
///
pub fn open(&mut self) -> Result<()> {
self.commit_id = None;
self.show()?;
Ok(())
}
///
pub fn create_branch(&mut self) {
let res =
sync::create_branch(CWD, self.input.get_text().as_str());
self.input.clear();
self.hide();
match res {
Ok(_) => {
self.queue.borrow_mut().push_back(
InternalEvent::Update(NeedsUpdate::ALL),
);
}
Err(e) => {
log::error!("create branch: {}", e,);
self.queue.borrow_mut().push_back(
InternalEvent::ShowErrorMsg(format!(
"create branch error:\n{}",
e,
)),
);
}
}
}
}

View File

@ -3,6 +3,7 @@ mod command;
mod commit;
mod commit_details;
mod commitlist;
mod create_branch;
mod diff;
mod externaleditor;
mod filetree;
@ -23,6 +24,7 @@ pub use command::{CommandInfo, CommandText};
pub use commit::CommitComponent;
pub use commit_details::CommitDetailsComponent;
pub use commitlist::CommitList;
pub use create_branch::CreateBranchComponent;
pub use diff::DiffComponent;
pub use externaleditor::ExternalEditorComponent;
pub use filetree::FileTreeComponent;

View File

@ -59,6 +59,7 @@ pub struct KeyConfig {
pub log_tag_commit: KeyEvent,
pub commit_amend: KeyEvent,
pub copy: KeyEvent,
pub create_branch: KeyEvent,
}
#[rustfmt::skip]
@ -106,7 +107,8 @@ impl Default for KeyConfig {
cmd_bar_toggle: KeyEvent { code: KeyCode::Char('.'), modifiers: KeyModifiers::empty()},
log_tag_commit: KeyEvent { code: KeyCode::Char('t'), modifiers: KeyModifiers::empty()},
commit_amend: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::CONTROL},
copy: KeyEvent { code: KeyCode::Char('y'), modifiers: KeyModifiers::empty()},
copy: KeyEvent { code: KeyCode::Char('y'), modifiers: KeyModifiers::empty()},
create_branch: KeyEvent { code: KeyCode::Char('b'), modifiers: KeyModifiers::empty()},
}
}
}

View File

@ -51,6 +51,8 @@ pub enum InternalEvent {
///
TagCommit(CommitId),
///
CreateBranch,
///
OpenExternalEditor(Option<String>),
}

View File

@ -110,6 +110,16 @@ pub fn stashing_options_title(
pub fn loading_text(_key_config: &SharedKeyConfig) -> String {
"Loading ...".to_string()
}
pub fn create_branch_popup_title(
_key_config: &SharedKeyConfig,
) -> String {
"Branch".to_string()
}
pub fn create_branch_popup_msg(
_key_config: &SharedKeyConfig,
) -> String {
"type branch name".to_string()
}
pub mod commit {
use crate::keys::SharedKeyConfig;
@ -565,4 +575,25 @@ pub mod commands {
CMD_GROUP_LOG,
)
}
pub fn create_branch_confirm_msg(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!("Create Branch [{}]", get_hint(key_config.enter),),
"create branch",
CMD_GROUP_GENERAL,
)
}
pub fn open_branch_create_popup(
key_config: &SharedKeyConfig,
) -> CommandText {
CommandText::new(
format!(
"Branch [{}]",
get_hint(key_config.create_branch),
),
"open create branch popup",
CMD_GROUP_GENERAL,
)
}
}

View File

@ -231,7 +231,11 @@ impl Component for Revlog {
Ok(true)
},
);
} else {
} else if k == self.key_config.create_branch {
self.queue
.borrow_mut()
.push_back(InternalEvent::CreateBranch);
return Ok(true);
}
}
}
@ -267,6 +271,14 @@ impl Component for Revlog {
self.visible || force_all,
));
out.push(CommandInfo::new(
strings::commands::open_branch_create_popup(
&self.key_config,
),
true,
self.visible || force_all,
));
visibility_blocking(self)
}

View File

@ -362,6 +362,14 @@ impl Component for Status {
));
}
out.push(CommandInfo::new(
strings::commands::open_branch_create_popup(
&self.key_config,
),
true,
true,
));
out.push(
CommandInfo::new(
strings::commands::select_status(&self.key_config),
@ -438,6 +446,11 @@ impl Component for Status {
&& !self.index_wd.is_empty()
{
self.switch_focus(Focus::WorkDir)
} else if k == self.key_config.create_branch {
self.queue
.borrow_mut()
.push_back(InternalEvent::CreateBranch);
Ok(true)
} else {
Ok(false)
};