Skip to content

Commit 2e3bd39

Browse files
committed
Add popup for tags
This closes #483.
1 parent bea7edf commit 2e3bd39

File tree

13 files changed

+599
-7
lines changed

13 files changed

+599
-7
lines changed

asyncgit/src/revlog.rs

+8
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ impl AsyncLog {
6868
Ok(list[min..max].to_vec())
6969
}
7070

71+
///
72+
pub fn position(&self, id: CommitId) -> Result<Option<usize>> {
73+
let list = self.current.lock()?;
74+
let position = list.iter().position(|&x| x == id);
75+
76+
Ok(position)
77+
}
78+
7179
///
7280
pub fn is_pending(&self) -> bool {
7381
self.pending.load(Ordering::Relaxed)

asyncgit/src/sync/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ pub use stash::{
6464
get_stashes, stash_apply, stash_drop, stash_pop, stash_save,
6565
};
6666
pub use state::{repo_state, RepoState};
67-
pub use tags::{get_tags, CommitTags, Tags};
67+
pub use tags::{
68+
delete_tag, get_tags, get_tags_with_metadata, CommitTags,
69+
TagWithMetadata, Tags,
70+
};
6871
pub use tree::{tree_file_content, tree_files, TreeFile};
6972
pub use utils::{
7073
get_head, get_head_tuple, is_bare_repo, is_repo, repo_dir,

asyncgit/src/sync/tags.rs

+108-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
1-
use super::{utils::repo, CommitId};
1+
use super::{get_commits_info, utils::repo, CommitId};
22
use crate::error::Result;
33
use scopetime::scope_time;
4-
use std::collections::BTreeMap;
4+
use std::collections::{BTreeMap, HashMap, HashSet};
55

66
/// all tags pointing to a single commit
77
pub type CommitTags = Vec<String>;
88
/// hashmap of tag target commit hash to tag names
99
pub type Tags = BTreeMap<CommitId, CommitTags>;
1010

11+
///
12+
pub struct TagWithMetadata {
13+
///
14+
pub name: String,
15+
///
16+
pub author: String,
17+
///
18+
pub time: i64,
19+
///
20+
pub message: String,
21+
///
22+
pub commit_id: CommitId,
23+
}
24+
25+
static MAX_MESSAGE_WIDTH: usize = 100;
26+
1127
/// returns `Tags` type filled with all tags found in repo
1228
pub fn get_tags(repo_path: &str) -> Result<Tags> {
1329
scope_time!("get_tags");
@@ -31,8 +47,12 @@ pub fn get_tags(repo_path: &str) -> Result<Tags> {
3147
//NOTE: find_tag (git_tag_lookup) only works on annotated tags
3248
// lightweight tags `id` already points to the target commit
3349
// see https://github.com/libgit2/libgit2/issues/5586
34-
if let Ok(tag) = repo.find_tag(id) {
35-
adder(CommitId::new(tag.target_id()), name);
50+
if let Ok(commit) = repo
51+
.find_tag(id)
52+
.and_then(|tag| tag.target())
53+
.and_then(|target| target.peel_to_commit())
54+
{
55+
adder(CommitId::new(commit.id()), name);
3656
} else if repo.find_commit(id).is_ok() {
3757
adder(CommitId::new(id), name);
3858
}
@@ -45,6 +65,69 @@ pub fn get_tags(repo_path: &str) -> Result<Tags> {
4565
Ok(res)
4666
}
4767

68+
///
69+
pub fn get_tags_with_metadata(
70+
repo_path: &str,
71+
) -> Result<Vec<TagWithMetadata>> {
72+
scope_time!("get_tags_with_metadata");
73+
74+
let tags_grouped_by_commit_id = get_tags(repo_path)?;
75+
76+
let tags_with_commit_id: Vec<(&str, &CommitId)> =
77+
tags_grouped_by_commit_id
78+
.iter()
79+
.flat_map(|(commit_id, tags)| {
80+
tags.iter()
81+
.map(|tag| (tag.as_ref(), commit_id))
82+
.collect::<Vec<(&str, &CommitId)>>()
83+
})
84+
.collect();
85+
86+
let unique_commit_ids: HashSet<_> = tags_with_commit_id
87+
.iter()
88+
.copied()
89+
.map(|(_, &commit_id)| commit_id)
90+
.collect();
91+
let mut commit_ids = Vec::with_capacity(unique_commit_ids.len());
92+
commit_ids.extend(unique_commit_ids);
93+
94+
let commit_infos =
95+
get_commits_info(repo_path, &commit_ids, MAX_MESSAGE_WIDTH)?;
96+
let unique_commit_infos: HashMap<_, _> = commit_infos
97+
.iter()
98+
.map(|commit_info| (commit_info.id, commit_info))
99+
.collect();
100+
101+
let mut tags: Vec<TagWithMetadata> = tags_with_commit_id
102+
.into_iter()
103+
.filter_map(|(tag, commit_id)| {
104+
unique_commit_infos.get(commit_id).map(|commit_info| {
105+
TagWithMetadata {
106+
name: String::from(tag),
107+
author: commit_info.author.clone(),
108+
time: commit_info.time,
109+
message: commit_info.message.clone(),
110+
commit_id: *commit_id,
111+
}
112+
})
113+
})
114+
.collect();
115+
116+
tags.sort_unstable_by(|a, b| b.time.cmp(&a.time));
117+
118+
Ok(tags)
119+
}
120+
121+
///
122+
pub fn delete_tag(repo_path: &str, tag_name: &str) -> Result<()> {
123+
scope_time!("delete_tag");
124+
125+
let repo = repo(repo_path)?;
126+
repo.tag_delete(tag_name)?;
127+
128+
Ok(())
129+
}
130+
48131
#[cfg(test)]
49132
mod tests {
50133
use super::*;
@@ -82,5 +165,26 @@ mod tests {
82165
get_tags(repo_path).unwrap()[&CommitId::new(head_id)],
83166
vec!["a", "b"]
84167
);
168+
169+
let tags = get_tags_with_metadata(repo_path).unwrap();
170+
171+
assert_eq!(tags.len(), 2);
172+
assert_eq!(tags[0].name, "a");
173+
assert_eq!(tags[0].message, "initial");
174+
assert_eq!(tags[1].name, "b");
175+
assert_eq!(tags[1].message, "initial");
176+
assert_eq!(tags[0].commit_id, tags[1].commit_id);
177+
178+
delete_tag(repo_path, "a").unwrap();
179+
180+
let tags = get_tags(repo_path).unwrap();
181+
182+
assert_eq!(tags.len(), 1);
183+
184+
delete_tag(repo_path, "b").unwrap();
185+
186+
let tags = get_tags(repo_path).unwrap();
187+
188+
assert_eq!(tags.len(), 0);
85189
}
86190
}

src/app.rs

+37-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
InspectCommitComponent, MsgComponent, PullComponent,
1010
PushComponent, PushTagsComponent, RenameBranchComponent,
1111
ResetComponent, RevisionFilesComponent, StashMsgComponent,
12-
TagCommitComponent,
12+
TagCommitComponent, TagListComponent,
1313
},
1414
input::{Input, InputEvent, InputState},
1515
keys::{KeyConfig, SharedKeyConfig},
@@ -54,6 +54,7 @@ pub struct App {
5454
create_branch_popup: CreateBranchComponent,
5555
rename_branch_popup: RenameBranchComponent,
5656
select_branch_popup: BranchListComponent,
57+
tags_popup: TagListComponent,
5758
cmdbar: RefCell<CommandBar>,
5859
tab: usize,
5960
revlog: Revlog,
@@ -162,6 +163,11 @@ impl App {
162163
theme.clone(),
163164
key_config.clone(),
164165
),
166+
tags_popup: TagListComponent::new(
167+
&queue,
168+
theme.clone(),
169+
key_config.clone(),
170+
),
165171
do_quit: false,
166172
cmdbar: RefCell::new(CommandBar::new(
167173
theme.clone(),
@@ -397,6 +403,7 @@ impl App {
397403
rename_branch_popup,
398404
select_branch_popup,
399405
revision_files_popup,
406+
tags_popup,
400407
help,
401408
revlog,
402409
status_tab,
@@ -550,11 +557,26 @@ impl App {
550557
InternalEvent::SelectBranch => {
551558
self.select_branch_popup.open()?;
552559
}
560+
InternalEvent::Tags => {
561+
self.tags_popup.open()?;
562+
}
553563
InternalEvent::TabSwitch => self.set_tab(0)?,
554564
InternalEvent::InspectCommit(id, tags) => {
555565
self.inspect_commit_popup.open(id, tags)?;
556566
flags.insert(NeedsUpdate::ALL | NeedsUpdate::COMMANDS)
557567
}
568+
InternalEvent::SelectCommitInRevlog(id) => {
569+
if let Err(error) = self.revlog.select_commit(id) {
570+
self.queue.borrow_mut().push_back(
571+
InternalEvent::ShowErrorMsg(
572+
error.to_string(),
573+
),
574+
)
575+
} else {
576+
self.tags_popup.hide();
577+
flags.insert(NeedsUpdate::ALL)
578+
}
579+
}
558580
InternalEvent::OpenExternalEditor(path) => {
559581
self.input.set_polling(false);
560582
self.external_editor_popup.show()?;
@@ -620,6 +642,18 @@ impl App {
620642
self.select_branch_popup.update_branches()?;
621643
}
622644
}
645+
Action::DeleteTag(tag_name) => {
646+
if let Err(error) = sync::delete_tag(CWD, &tag_name) {
647+
self.queue.borrow_mut().push_back(
648+
InternalEvent::ShowErrorMsg(
649+
error.to_string(),
650+
),
651+
)
652+
} else {
653+
flags.insert(NeedsUpdate::ALL);
654+
self.tags_popup.update_tags()?;
655+
}
656+
}
623657
Action::ForcePush(branch, force) => self
624658
.queue
625659
.borrow_mut()
@@ -696,6 +730,7 @@ impl App {
696730
|| self.push_tags_popup.is_visible()
697731
|| self.pull_popup.is_visible()
698732
|| self.select_branch_popup.is_visible()
733+
|| self.tags_popup.is_visible()
699734
|| self.rename_branch_popup.is_visible()
700735
|| self.revision_files_popup.is_visible()
701736
}
@@ -723,6 +758,7 @@ impl App {
723758
self.external_editor_popup.draw(f, size)?;
724759
self.tag_commit_popup.draw(f, size)?;
725760
self.select_branch_popup.draw(f, size)?;
761+
self.tags_popup.draw(f, size)?;
726762
self.create_branch_popup.draw(f, size)?;
727763
self.rename_branch_popup.draw(f, size)?;
728764
self.revision_files_popup.draw(f, size)?;

src/components/commitlist.rs

+4
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ impl CommitList {
287287
fn relative_selection(&self) -> usize {
288288
self.selection.saturating_sub(self.items.index_offset())
289289
}
290+
291+
pub fn select_entry(&mut self, position: usize) {
292+
self.selection = position;
293+
}
290294
}
291295

292296
impl DrawableComponent for CommitList {

src/components/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod revision_files;
2222
mod stashmsg;
2323
mod syntax_text;
2424
mod tag_commit;
25+
mod taglist;
2526
mod textinput;
2627
mod utils;
2728

@@ -48,6 +49,7 @@ pub use revision_files::RevisionFilesComponent;
4849
pub use stashmsg::StashMsgComponent;
4950
pub use syntax_text::SyntaxTextComponent;
5051
pub use tag_commit::TagCommitComponent;
52+
pub use taglist::TagListComponent;
5153
pub use textinput::{InputType, TextInputComponent};
5254
pub use utils::filetree::FileTreeItemKind;
5355

src/components/reset.rs

+9
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,15 @@ impl ResetComponent {
168168
branch_ref,
169169
),
170170
),
171+
Action::DeleteTag(tag_name) => (
172+
strings::confirm_title_delete_tag(
173+
&self.key_config,
174+
),
175+
strings::confirm_msg_delete_tag(
176+
&self.key_config,
177+
tag_name,
178+
),
179+
),
171180
Action::ForcePush(branch, _force) => (
172181
strings::confirm_title_force_push(
173182
&self.key_config,

0 commit comments

Comments
 (0)