mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
support annotated tags (#1073)
This commit is contained in:
parent
d6ace56288
commit
132559ea7f
12 changed files with 206 additions and 47 deletions
|
|
@ -79,6 +79,7 @@ These are the high level goals before calling out `1.0`:
|
||||||
* notify-based change detection ([#1](https://github.com/extrawurst/gitui/issues/1))
|
* notify-based change detection ([#1](https://github.com/extrawurst/gitui/issues/1))
|
||||||
* interactive rebase ([#32](https://github.com/extrawurst/gitui/issues/32))
|
* interactive rebase ([#32](https://github.com/extrawurst/gitui/issues/32))
|
||||||
* popup history and back button ([#846](https://github.com/extrawurst/gitui/issues/846))
|
* popup history and back button ([#846](https://github.com/extrawurst/gitui/issues/846))
|
||||||
|
* delete tag on remote ([#1074](https://github.com/extrawurst/gitui/issues/1074))
|
||||||
|
|
||||||
## 5. <a name="limitations"></a> Known Limitations <small><sup>[Top ▲](#table-of-contents)</sup></small>
|
## 5. <a name="limitations"></a> Known Limitations <small><sup>[Top ▲](#table-of-contents)</sup></small>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,27 +95,35 @@ pub fn commit(repo_path: &RepoPath, msg: &str) -> Result<CommitId> {
|
||||||
///
|
///
|
||||||
/// This function will return an `Err(…)` variant if the tag’s name is refused
|
/// This function will return an `Err(…)` variant if the tag’s name is refused
|
||||||
/// by git or if the tag already exists.
|
/// by git or if the tag already exists.
|
||||||
pub fn tag(
|
pub fn tag_commit(
|
||||||
repo_path: &RepoPath,
|
repo_path: &RepoPath,
|
||||||
commit_id: &CommitId,
|
commit_id: &CommitId,
|
||||||
tag: &str,
|
tag: &str,
|
||||||
|
message: Option<&str>,
|
||||||
) -> Result<CommitId> {
|
) -> Result<CommitId> {
|
||||||
scope_time!("tag");
|
scope_time!("tag_commit");
|
||||||
|
|
||||||
let repo = repo(repo_path)?;
|
let repo = repo(repo_path)?;
|
||||||
|
|
||||||
let signature = signature_allow_undefined_name(&repo)?;
|
|
||||||
let object_id = commit_id.get_oid();
|
let object_id = commit_id.get_oid();
|
||||||
let target =
|
let target =
|
||||||
repo.find_object(object_id, Some(ObjectType::Commit))?;
|
repo.find_object(object_id, Some(ObjectType::Commit))?;
|
||||||
|
|
||||||
Ok(repo.tag(tag, &target, &signature, "", false)?.into())
|
let c = if let Some(message) = message {
|
||||||
|
let signature = signature_allow_undefined_name(&repo)?;
|
||||||
|
repo.tag(tag, &target, &signature, message, false)?.into()
|
||||||
|
} else {
|
||||||
|
repo.tag_lightweight(tag, &target, false)?.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
use crate::sync::tags::Tag;
|
||||||
use crate::sync::RepoPath;
|
use crate::sync::RepoPath;
|
||||||
use crate::sync::{
|
use crate::sync::{
|
||||||
commit, get_commit_details, get_commit_files, stage_add_file,
|
commit, get_commit_details, get_commit_files, stage_add_file,
|
||||||
|
|
@ -124,7 +132,7 @@ mod tests {
|
||||||
utils::get_head,
|
utils::get_head,
|
||||||
LogWalker,
|
LogWalker,
|
||||||
};
|
};
|
||||||
use commit::{amend, tag};
|
use commit::{amend, tag_commit};
|
||||||
use git2::Repository;
|
use git2::Repository;
|
||||||
use std::{fs::File, io::Write, path::Path};
|
use std::{fs::File, io::Write, path::Path};
|
||||||
|
|
||||||
|
|
@ -238,25 +246,56 @@ mod tests {
|
||||||
|
|
||||||
let new_id = commit(repo_path, "commit msg")?;
|
let new_id = commit(repo_path, "commit msg")?;
|
||||||
|
|
||||||
tag(repo_path, &new_id, "tag")?;
|
tag_commit(repo_path, &new_id, "tag", None)?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_tags(repo_path).unwrap()[&new_id],
|
get_tags(repo_path).unwrap()[&new_id],
|
||||||
vec!["tag"]
|
vec![Tag::new("tag")]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(matches!(tag(repo_path, &new_id, "tag"), Err(_)));
|
assert!(matches!(
|
||||||
|
tag_commit(repo_path, &new_id, "tag", None),
|
||||||
|
Err(_)
|
||||||
|
));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_tags(repo_path).unwrap()[&new_id],
|
get_tags(repo_path).unwrap()[&new_id],
|
||||||
vec!["tag"]
|
vec![Tag::new("tag")]
|
||||||
);
|
);
|
||||||
|
|
||||||
tag(repo_path, &new_id, "second-tag")?;
|
tag_commit(repo_path, &new_id, "second-tag", None)?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_tags(repo_path).unwrap()[&new_id],
|
get_tags(repo_path).unwrap()[&new_id],
|
||||||
vec!["second-tag", "tag"]
|
vec![Tag::new("second-tag"), Tag::new("tag")]
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tag_with_message() -> Result<()> {
|
||||||
|
let file_path = Path::new("foo");
|
||||||
|
let (_td, repo) = repo_init_empty().unwrap();
|
||||||
|
let root = repo.path().parent().unwrap();
|
||||||
|
let repo_path: &RepoPath =
|
||||||
|
&root.as_os_str().to_str().unwrap().into();
|
||||||
|
|
||||||
|
File::create(&root.join(file_path))?
|
||||||
|
.write_all(b"test\nfoo")?;
|
||||||
|
|
||||||
|
stage_add_file(repo_path, file_path)?;
|
||||||
|
|
||||||
|
let new_id = commit(repo_path, "commit msg")?;
|
||||||
|
|
||||||
|
tag_commit(repo_path, &new_id, "tag", Some("tag-message"))?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_tags(repo_path).unwrap()[&new_id][0]
|
||||||
|
.annotation
|
||||||
|
.as_ref()
|
||||||
|
.unwrap(),
|
||||||
|
"tag-message"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ pub use branch::{
|
||||||
merge_rebase::merge_upstream_rebase, rename::rename_branch,
|
merge_rebase::merge_upstream_rebase, rename::rename_branch,
|
||||||
validate_branch_name, BranchCompare, BranchInfo,
|
validate_branch_name, BranchCompare, BranchInfo,
|
||||||
};
|
};
|
||||||
pub use commit::{amend, commit, tag};
|
pub use commit::{amend, commit, tag_commit};
|
||||||
pub use commit_details::{
|
pub use commit_details::{
|
||||||
get_commit_details, CommitDetails, CommitMessage, CommitSignature,
|
get_commit_details, CommitDetails, CommitMessage, CommitSignature,
|
||||||
};
|
};
|
||||||
|
|
@ -80,7 +80,7 @@ pub use stash::{
|
||||||
};
|
};
|
||||||
pub use state::{repo_state, RepoState};
|
pub use state::{repo_state, RepoState};
|
||||||
pub use tags::{
|
pub use tags::{
|
||||||
delete_tag, get_tags, get_tags_with_metadata, CommitTags,
|
delete_tag, get_tags, get_tags_with_metadata, CommitTags, Tag,
|
||||||
TagWithMetadata, Tags,
|
TagWithMetadata, Tags,
|
||||||
};
|
};
|
||||||
pub use tree::{tree_file_content, tree_files, TreeFile};
|
pub use tree::{tree_file_content, tree_files, TreeFile};
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ mod tests {
|
||||||
let commit1 =
|
let commit1 =
|
||||||
write_commit_file(&clone1, "test.txt", "test", "commit1");
|
write_commit_file(&clone1, "test.txt", "test", "commit1");
|
||||||
|
|
||||||
sync::tag(clone1_dir, &commit1, "tag1").unwrap();
|
sync::tag_commit(clone1_dir, &commit1, "tag1", None).unwrap();
|
||||||
|
|
||||||
push(
|
push(
|
||||||
clone1_dir, "origin", "master", false, false, None, None,
|
clone1_dir, "origin", "master", false, false, None, None,
|
||||||
|
|
@ -229,7 +229,7 @@ mod tests {
|
||||||
let commit1 =
|
let commit1 =
|
||||||
write_commit_file(&clone1, "test.txt", "test", "commit1");
|
write_commit_file(&clone1, "test.txt", "test", "commit1");
|
||||||
|
|
||||||
sync::tag(clone1_dir, &commit1, "tag1").unwrap();
|
sync::tag_commit(clone1_dir, &commit1, "tag1", None).unwrap();
|
||||||
|
|
||||||
push(
|
push(
|
||||||
clone1_dir, "origin", "master", false, false, None, None,
|
clone1_dir, "origin", "master", false, false, None, None,
|
||||||
|
|
@ -263,7 +263,7 @@ mod tests {
|
||||||
let commit1 =
|
let commit1 =
|
||||||
write_commit_file(&clone1, "test.txt", "test", "commit1");
|
write_commit_file(&clone1, "test.txt", "test", "commit1");
|
||||||
|
|
||||||
sync::tag(clone1_dir, &commit1, "tag1").unwrap();
|
sync::tag_commit(clone1_dir, &commit1, "tag1", None).unwrap();
|
||||||
|
|
||||||
push(
|
push(
|
||||||
clone1_dir, "origin", "master", false, false, None, None,
|
clone1_dir, "origin", "master", false, false, None, None,
|
||||||
|
|
@ -305,7 +305,7 @@ mod tests {
|
||||||
|
|
||||||
// clone1 - creates tag
|
// clone1 - creates tag
|
||||||
|
|
||||||
sync::tag(clone1_dir, &commit1, "tag1").unwrap();
|
sync::tag_commit(clone1_dir, &commit1, "tag1", None).unwrap();
|
||||||
|
|
||||||
let tags1 = sync::get_tags(clone1_dir).unwrap();
|
let tags1 = sync::get_tags(clone1_dir).unwrap();
|
||||||
|
|
||||||
|
|
@ -345,7 +345,7 @@ mod tests {
|
||||||
|
|
||||||
// clone1 - creates tag
|
// clone1 - creates tag
|
||||||
|
|
||||||
sync::tag(clone1_dir, &commit1, "tag1").unwrap();
|
sync::tag_commit(clone1_dir, &commit1, "tag1", None).unwrap();
|
||||||
|
|
||||||
let tags1 = sync::get_tags(clone1_dir).unwrap();
|
let tags1 = sync::get_tags(clone1_dir).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,32 @@
|
||||||
use super::{get_commits_info, CommitId, RepoPath};
|
use super::{get_commits_info, CommitId, RepoPath};
|
||||||
use crate::{error::Result, sync::repository::repo};
|
use crate::{
|
||||||
|
error::Result,
|
||||||
|
sync::{repository::repo, utils::bytes2string},
|
||||||
|
};
|
||||||
use scopetime::scope_time;
|
use scopetime::scope_time;
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
|
|
||||||
|
///
|
||||||
|
#[derive(Clone, Hash, PartialEq, Debug)]
|
||||||
|
pub struct Tag {
|
||||||
|
/// tag name
|
||||||
|
pub name: String,
|
||||||
|
/// tag annotation
|
||||||
|
pub annotation: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
///
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
annotation: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// all tags pointing to a single commit
|
/// all tags pointing to a single commit
|
||||||
pub type CommitTags = Vec<String>;
|
pub type CommitTags = Vec<Tag>;
|
||||||
/// hashmap of tag target commit hash to tag names
|
/// hashmap of tag target commit hash to tag names
|
||||||
pub type Tags = BTreeMap<CommitId, CommitTags>;
|
pub type Tags = BTreeMap<CommitId, CommitTags>;
|
||||||
|
|
||||||
|
|
@ -29,7 +51,7 @@ pub fn get_tags(repo_path: &RepoPath) -> Result<Tags> {
|
||||||
scope_time!("get_tags");
|
scope_time!("get_tags");
|
||||||
|
|
||||||
let mut res = Tags::new();
|
let mut res = Tags::new();
|
||||||
let mut adder = |key, value: String| {
|
let mut adder = |key, value: Tag| {
|
||||||
if let Some(key) = res.get_mut(&key) {
|
if let Some(key) = res.get_mut(&key) {
|
||||||
key.push(value);
|
key.push(value);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -44,17 +66,31 @@ pub fn get_tags(repo_path: &RepoPath) -> Result<Tags> {
|
||||||
// skip the `refs/tags/` part
|
// skip the `refs/tags/` part
|
||||||
String::from_utf8(name[10..name.len()].into())
|
String::from_utf8(name[10..name.len()].into())
|
||||||
{
|
{
|
||||||
//NOTE: find_tag (git_tag_lookup) only works on annotated tags
|
//NOTE: find_tag (using underlying git_tag_lookup) only
|
||||||
// lightweight tags `id` already points to the target commit
|
// works on annotated tags lightweight tags `id` already
|
||||||
|
// points to the target commit
|
||||||
// see https://github.com/libgit2/libgit2/issues/5586
|
// see https://github.com/libgit2/libgit2/issues/5586
|
||||||
if let Ok(commit) = repo
|
let commit = if let Ok(commit) = repo
|
||||||
.find_tag(id)
|
.find_tag(id)
|
||||||
.and_then(|tag| tag.target())
|
.and_then(|tag| tag.target())
|
||||||
.and_then(|target| target.peel_to_commit())
|
.and_then(|target| target.peel_to_commit())
|
||||||
{
|
{
|
||||||
adder(CommitId::new(commit.id()), name);
|
Some(CommitId::new(commit.id()))
|
||||||
} else if repo.find_commit(id).is_ok() {
|
} else if repo.find_commit(id).is_ok() {
|
||||||
adder(CommitId::new(id), name);
|
Some(CommitId::new(id))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let annotation = repo
|
||||||
|
.find_tag(id)
|
||||||
|
.ok()
|
||||||
|
.as_ref()
|
||||||
|
.and_then(git2::Tag::message_bytes)
|
||||||
|
.and_then(|msg| bytes2string(msg).ok());
|
||||||
|
|
||||||
|
if let Some(commit) = commit {
|
||||||
|
adder(commit, Tag { name, annotation });
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -78,7 +114,7 @@ pub fn get_tags_with_metadata(
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(commit_id, tags)| {
|
.flat_map(|(commit_id, tags)| {
|
||||||
tags.iter()
|
tags.iter()
|
||||||
.map(|tag| (tag.as_ref(), commit_id))
|
.map(|tag| (tag.name.as_ref(), commit_id))
|
||||||
.collect::<Vec<(&str, &CommitId)>>()
|
.collect::<Vec<(&str, &CommitId)>>()
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
@ -167,7 +203,10 @@ mod tests {
|
||||||
repo.tag("b", &target, &sig, "", false).unwrap();
|
repo.tag("b", &target, &sig, "", false).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_tags(repo_path).unwrap()[&CommitId::new(head_id)],
|
get_tags(repo_path).unwrap()[&CommitId::new(head_id)]
|
||||||
|
.iter()
|
||||||
|
.map(|t| &t.name)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
vec!["a", "b"]
|
vec!["a", "b"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use asyncgit::sync::{
|
use asyncgit::sync::{
|
||||||
self, CommitDetails, CommitId, CommitMessage, RepoPathRef,
|
self, CommitDetails, CommitId, CommitMessage, RepoPathRef, Tag,
|
||||||
};
|
};
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
|
|
@ -31,7 +31,7 @@ use super::style::Detail;
|
||||||
pub struct DetailsComponent {
|
pub struct DetailsComponent {
|
||||||
repo: RepoPathRef,
|
repo: RepoPathRef,
|
||||||
data: Option<CommitDetails>,
|
data: Option<CommitDetails>,
|
||||||
tags: Vec<String>,
|
tags: Vec<Tag>,
|
||||||
theme: SharedTheme,
|
theme: SharedTheme,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
current_width: Cell<u16>,
|
current_width: Cell<u16>,
|
||||||
|
|
@ -224,7 +224,7 @@ impl DetailsComponent {
|
||||||
itertools::Itertools::intersperse(
|
itertools::Itertools::intersperse(
|
||||||
self.tags.iter().map(|tag| {
|
self.tags.iter().map(|tag| {
|
||||||
Span::styled(
|
Span::styled(
|
||||||
Cow::from(tag),
|
Cow::from(&tag.name),
|
||||||
self.theme.text(true, false),
|
self.theme.text(true, false),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use anyhow::Result;
|
||||||
use asyncgit::sync::{CommitId, Tags};
|
use asyncgit::sync::{CommitId, Tags};
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
|
use itertools::Itertools;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow, cell::Cell, cmp, convert::TryFrom, time::Instant,
|
borrow::Cow, cell::Cell, cmp, convert::TryFrom, time::Instant,
|
||||||
};
|
};
|
||||||
|
|
@ -320,11 +321,10 @@ impl CommitList {
|
||||||
.take(height)
|
.take(height)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let tags = self
|
let tags =
|
||||||
.tags
|
self.tags.as_ref().and_then(|t| t.get(&e.id)).map(
|
||||||
.as_ref()
|
|tags| tags.iter().map(|t| &t.name).join(" "),
|
||||||
.and_then(|t| t.get(&e.id))
|
);
|
||||||
.map(|tags| tags.join(" "));
|
|
||||||
|
|
||||||
let marked = if any_marked {
|
let marked = if any_marked {
|
||||||
self.is_marked(&e.id)
|
self.is_marked(&e.id)
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,14 @@ use asyncgit::sync::{self, CommitId, RepoPathRef};
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use tui::{backend::Backend, layout::Rect, Frame};
|
use tui::{backend::Backend, layout::Rect, Frame};
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
Name,
|
||||||
|
Annotation { tag_name: String },
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TagCommitComponent {
|
pub struct TagCommitComponent {
|
||||||
repo: RepoPathRef,
|
repo: RepoPathRef,
|
||||||
|
mode: Mode,
|
||||||
input: TextInputComponent,
|
input: TextInputComponent,
|
||||||
commit_id: Option<CommitId>,
|
commit_id: Option<CommitId>,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
|
|
@ -47,8 +53,14 @@ impl Component for TagCommitComponent {
|
||||||
strings::commands::tag_commit_confirm_msg(
|
strings::commands::tag_commit_confirm_msg(
|
||||||
&self.key_config,
|
&self.key_config,
|
||||||
),
|
),
|
||||||
|
self.is_valid_tag(),
|
||||||
true,
|
true,
|
||||||
true,
|
));
|
||||||
|
|
||||||
|
out.push(CommandInfo::new(
|
||||||
|
strings::commands::tag_annotate_msg(&self.key_config),
|
||||||
|
self.is_valid_tag(),
|
||||||
|
matches!(self.mode, Mode::Name),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,8 +74,26 @@ impl Component for TagCommitComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Event::Key(e) = ev {
|
if let Event::Key(e) = ev {
|
||||||
if e == self.key_config.keys.enter {
|
if e == self.key_config.keys.enter
|
||||||
|
&& self.is_valid_tag()
|
||||||
|
{
|
||||||
self.tag();
|
self.tag();
|
||||||
|
} else if e == self.key_config.keys.tag_annotate
|
||||||
|
&& self.is_valid_tag()
|
||||||
|
{
|
||||||
|
let tag_name: String =
|
||||||
|
self.input.get_text().into();
|
||||||
|
|
||||||
|
self.input.clear();
|
||||||
|
self.input.set_title(
|
||||||
|
strings::tag_popup_annotation_title(
|
||||||
|
&tag_name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.input.set_default_msg(
|
||||||
|
strings::tag_popup_annotation_msg(),
|
||||||
|
);
|
||||||
|
self.mode = Mode::Annotation { tag_name };
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(EventState::Consumed);
|
return Ok(EventState::Consumed);
|
||||||
|
|
@ -81,6 +111,9 @@ impl Component for TagCommitComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self) -> Result<()> {
|
fn show(&mut self) -> Result<()> {
|
||||||
|
self.mode = Mode::Name;
|
||||||
|
self.input.set_title(strings::tag_popup_name_title());
|
||||||
|
self.input.set_default_msg(strings::tag_popup_name_msg());
|
||||||
self.input.show()?;
|
self.input.show()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -100,13 +133,14 @@ impl TagCommitComponent {
|
||||||
input: TextInputComponent::new(
|
input: TextInputComponent::new(
|
||||||
theme,
|
theme,
|
||||||
key_config.clone(),
|
key_config.clone(),
|
||||||
&strings::tag_commit_popup_title(&key_config),
|
&strings::tag_popup_name_title(),
|
||||||
&strings::tag_commit_popup_msg(&key_config),
|
&strings::tag_popup_name_msg(),
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
commit_id: None,
|
commit_id: None,
|
||||||
key_config,
|
key_config,
|
||||||
repo,
|
repo,
|
||||||
|
mode: Mode::Name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,13 +152,29 @@ impl TagCommitComponent {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_valid_tag(&self) -> bool {
|
||||||
|
!self.input.get_text().is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag_info(&self) -> (String, Option<String>) {
|
||||||
|
match &self.mode {
|
||||||
|
Mode::Name => (self.input.get_text().into(), None),
|
||||||
|
Mode::Annotation { tag_name } => {
|
||||||
|
(tag_name.clone(), Some(self.input.get_text().into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn tag(&mut self) {
|
pub fn tag(&mut self) {
|
||||||
|
let (tag_name, tag_annotation) = self.tag_info();
|
||||||
|
|
||||||
if let Some(commit_id) = self.commit_id {
|
if let Some(commit_id) = self.commit_id {
|
||||||
let result = sync::tag(
|
let result = sync::tag_commit(
|
||||||
&self.repo.borrow(),
|
&self.repo.borrow(),
|
||||||
&commit_id,
|
&commit_id,
|
||||||
self.input.get_text(),
|
&tag_name,
|
||||||
|
tag_annotation.as_deref(),
|
||||||
);
|
);
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
|
@ -136,7 +186,10 @@ impl TagCommitComponent {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
// go back to tag name if something goes wrong
|
||||||
|
self.input.set_text(tag_name);
|
||||||
self.hide();
|
self.hide();
|
||||||
|
|
||||||
log::error!("e: {}", e,);
|
log::error!("e: {}", e,);
|
||||||
self.queue.push(InternalEvent::ShowErrorMsg(
|
self.queue.push(InternalEvent::ShowErrorMsg(
|
||||||
format!("tag error:\n{}", e,),
|
format!("tag error:\n{}", e,),
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,11 @@ impl TextInputComponent {
|
||||||
self.title = t;
|
self.title = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn set_default_msg(&mut self, v: String) {
|
||||||
|
self.default_msg = v;
|
||||||
|
}
|
||||||
|
|
||||||
fn get_draw_text(&self) -> Text {
|
fn get_draw_text(&self) -> Text {
|
||||||
let style = self.theme.text(true, false);
|
let style = self.theme.text(true, false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ pub struct KeysList {
|
||||||
pub abort_merge: KeyEvent,
|
pub abort_merge: KeyEvent,
|
||||||
pub undo_commit: KeyEvent,
|
pub undo_commit: KeyEvent,
|
||||||
pub stage_unstage_item: KeyEvent,
|
pub stage_unstage_item: KeyEvent,
|
||||||
|
pub tag_annotate: KeyEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
@ -150,6 +151,7 @@ impl Default for KeysList {
|
||||||
open_file_tree: KeyEvent { code: KeyCode::Char('F'), modifiers: KeyModifiers::SHIFT},
|
open_file_tree: KeyEvent { code: KeyCode::Char('F'), modifiers: KeyModifiers::SHIFT},
|
||||||
file_find: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
|
file_find: KeyEvent { code: KeyCode::Char('f'), modifiers: KeyModifiers::empty()},
|
||||||
stage_unstage_item: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()},
|
stage_unstage_item: KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::empty()},
|
||||||
|
tag_annotate: KeyEvent { code: KeyCode::Char('a'), modifiers: KeyModifiers::CONTROL},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ pub struct KeysListFile {
|
||||||
pub abort_merge: Option<KeyEvent>,
|
pub abort_merge: Option<KeyEvent>,
|
||||||
pub undo_commit: Option<KeyEvent>,
|
pub undo_commit: Option<KeyEvent>,
|
||||||
pub stage_unstage_item: Option<KeyEvent>,
|
pub stage_unstage_item: Option<KeyEvent>,
|
||||||
|
pub tag_annotate: Option<KeyEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeysListFile {
|
impl KeysListFile {
|
||||||
|
|
@ -163,6 +164,7 @@ impl KeysListFile {
|
||||||
abort_merge: self.abort_merge.unwrap_or(default.abort_merge),
|
abort_merge: self.abort_merge.unwrap_or(default.abort_merge),
|
||||||
undo_commit: self.undo_commit.unwrap_or(default.undo_commit),
|
undo_commit: self.undo_commit.unwrap_or(default.undo_commit),
|
||||||
stage_unstage_item: self.stage_unstage_item.unwrap_or(default.stage_unstage_item),
|
stage_unstage_item: self.stage_unstage_item.unwrap_or(default.stage_unstage_item),
|
||||||
|
tag_annotate: self.tag_annotate.unwrap_or(default.tag_annotate),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -263,13 +263,17 @@ pub fn log_title(_key_config: &SharedKeyConfig) -> String {
|
||||||
pub fn blame_title(_key_config: &SharedKeyConfig) -> String {
|
pub fn blame_title(_key_config: &SharedKeyConfig) -> String {
|
||||||
"Blame".to_string()
|
"Blame".to_string()
|
||||||
}
|
}
|
||||||
pub fn tag_commit_popup_title(
|
pub fn tag_popup_name_title() -> String {
|
||||||
_key_config: &SharedKeyConfig,
|
|
||||||
) -> String {
|
|
||||||
"Tag".to_string()
|
"Tag".to_string()
|
||||||
}
|
}
|
||||||
pub fn tag_commit_popup_msg(_key_config: &SharedKeyConfig) -> String {
|
pub fn tag_popup_name_msg() -> String {
|
||||||
"type tag".to_string()
|
"type tag name".to_string()
|
||||||
|
}
|
||||||
|
pub fn tag_popup_annotation_title(name: &str) -> String {
|
||||||
|
format!("Tag Annotation ({})", name)
|
||||||
|
}
|
||||||
|
pub fn tag_popup_annotation_msg() -> String {
|
||||||
|
"type tag annotation".to_string()
|
||||||
}
|
}
|
||||||
pub fn stashlist_title(_key_config: &SharedKeyConfig) -> String {
|
pub fn stashlist_title(_key_config: &SharedKeyConfig) -> String {
|
||||||
"Stashes".to_string()
|
"Stashes".to_string()
|
||||||
|
|
@ -1078,6 +1082,20 @@ pub mod commands {
|
||||||
CMD_GROUP_LOG,
|
CMD_GROUP_LOG,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tag_annotate_msg(
|
||||||
|
key_config: &SharedKeyConfig,
|
||||||
|
) -> CommandText {
|
||||||
|
CommandText::new(
|
||||||
|
format!(
|
||||||
|
"Annotate [{}]",
|
||||||
|
key_config.get_hint(key_config.keys.tag_annotate),
|
||||||
|
),
|
||||||
|
"annotate tag",
|
||||||
|
CMD_GROUP_LOG,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_branch_confirm_msg(
|
pub fn create_branch_confirm_msg(
|
||||||
key_config: &SharedKeyConfig,
|
key_config: &SharedKeyConfig,
|
||||||
) -> CommandText {
|
) -> CommandText {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue