Skip to content

Commit 70423c5

Browse files
cruesslerStephan Dilly
authored andcommitted
Add command for tagging commit
1 parent aa068f8 commit 70423c5

File tree

9 files changed

+244
-4
lines changed

9 files changed

+244
-4
lines changed

asyncgit/src/sync/commit.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{get_head, utils::repo, CommitId};
22
use crate::error::Result;
3-
use git2::{ErrorCode, Repository, Signature};
3+
use git2::{ErrorCode, ObjectType, Repository, Signature};
44
use scopetime::scope_time;
55

66
///
@@ -80,17 +80,39 @@ pub fn commit(repo_path: &str, msg: &str) -> Result<CommitId> {
8080
.into())
8181
}
8282

83+
/// Tag a commit.
84+
///
85+
/// This function will return an `Err(…)` variant if the tag’s name is refused
86+
/// by git or if the tag already exists.
87+
pub fn tag(
88+
repo_path: &str,
89+
commit_id: &CommitId,
90+
tag: &str,
91+
) -> Result<CommitId> {
92+
scope_time!("tag");
93+
94+
let repo = repo(repo_path)?;
95+
96+
let signature = signature_allow_undefined_name(&repo)?;
97+
let object_id = commit_id.get_oid();
98+
let target =
99+
repo.find_object(object_id, Some(ObjectType::Commit))?;
100+
101+
Ok(repo.tag(tag, &target, &signature, "", false)?.into())
102+
}
103+
83104
#[cfg(test)]
84105
mod tests {
85106

86107
use crate::error::Result;
87108
use crate::sync::{
88109
commit, get_commit_details, get_commit_files, stage_add_file,
110+
tags::get_tags,
89111
tests::{get_statuses, repo_init, repo_init_empty},
90112
utils::get_head,
91113
LogWalker,
92114
};
93-
use commit::amend;
115+
use commit::{amend, tag};
94116
use git2::Repository;
95117
use std::{fs::File, io::Write, path::Path};
96118

@@ -185,4 +207,42 @@ mod tests {
185207

186208
Ok(())
187209
}
210+
211+
#[test]
212+
fn test_tag() -> Result<()> {
213+
let file_path = Path::new("foo");
214+
let (_td, repo) = repo_init_empty().unwrap();
215+
let root = repo.path().parent().unwrap();
216+
let repo_path = root.as_os_str().to_str().unwrap();
217+
218+
File::create(&root.join(file_path))?
219+
.write_all(b"test\nfoo")?;
220+
221+
stage_add_file(repo_path, file_path)?;
222+
223+
let new_id = commit(repo_path, "commit msg")?;
224+
225+
tag(repo_path, &new_id, "tag")?;
226+
227+
assert_eq!(
228+
get_tags(repo_path).unwrap()[&new_id],
229+
vec!["tag"]
230+
);
231+
232+
assert!(matches!(tag(repo_path, &new_id, "tag"), Err(_)));
233+
234+
assert_eq!(
235+
get_tags(repo_path).unwrap()[&new_id],
236+
vec!["tag"]
237+
);
238+
239+
tag(repo_path, &new_id, "second-tag")?;
240+
241+
assert_eq!(
242+
get_tags(repo_path).unwrap()[&new_id],
243+
vec!["second-tag", "tag"]
244+
);
245+
246+
Ok(())
247+
}
188248
}

asyncgit/src/sync/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub mod utils;
1818

1919
pub(crate) use branch::get_branch_name;
2020

21-
pub use commit::{amend, commit};
21+
pub use commit::{amend, commit, tag};
2222
pub use commit_details::{get_commit_details, CommitDetails};
2323
pub use commit_files::get_commit_files;
2424
pub use commits_info::{get_commits_info, CommitId, CommitInfo};

src/app.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
event_pump, CommandBlocking, CommandInfo, CommitComponent,
66
Component, DrawableComponent, ExternalEditorComponent,
77
HelpComponent, InspectCommitComponent, MsgComponent,
8-
ResetComponent, StashMsgComponent,
8+
ResetComponent, StashMsgComponent, TagCommitComponent,
99
},
1010
input::{Input, InputEvent, InputState},
1111
keys,
@@ -40,6 +40,7 @@ pub struct App {
4040
stashmsg_popup: StashMsgComponent,
4141
inspect_commit_popup: InspectCommitComponent,
4242
external_editor_popup: ExternalEditorComponent,
43+
tag_commit_popup: TagCommitComponent,
4344
cmdbar: RefCell<CommandBar>,
4445
tab: usize,
4546
revlog: Revlog,
@@ -85,6 +86,10 @@ impl App {
8586
external_editor_popup: ExternalEditorComponent::new(
8687
theme.clone(),
8788
),
89+
tag_commit_popup: TagCommitComponent::new(
90+
queue.clone(),
91+
theme.clone(),
92+
),
8893
do_quit: false,
8994
cmdbar: RefCell::new(CommandBar::new(theme.clone())),
9095
help: HelpComponent::new(theme.clone()),
@@ -296,6 +301,7 @@ impl App {
296301
stashmsg_popup,
297302
inspect_commit_popup,
298303
external_editor_popup,
304+
tag_commit_popup,
299305
help,
300306
revlog,
301307
status_tab,
@@ -419,6 +425,9 @@ impl App {
419425
self.stashmsg_popup.options(opts);
420426
self.stashmsg_popup.show()?
421427
}
428+
InternalEvent::TagCommit(id) => {
429+
self.tag_commit_popup.open(id)?;
430+
}
422431
InternalEvent::TabSwitch => self.set_tab(0)?,
423432
InternalEvent::InspectCommit(id, tags) => {
424433
self.inspect_commit_popup.open(id, tags)?;
@@ -484,6 +493,7 @@ impl App {
484493
|| self.stashmsg_popup.is_visible()
485494
|| self.inspect_commit_popup.is_visible()
486495
|| self.external_editor_popup.is_visible()
496+
|| self.tag_commit_popup.is_visible()
487497
}
488498

489499
fn draw_popups<B: Backend>(
@@ -508,6 +518,7 @@ impl App {
508518
self.msg.draw(f, size)?;
509519
self.inspect_commit_popup.draw(f, size)?;
510520
self.external_editor_popup.draw(f, size)?;
521+
self.tag_commit_popup.draw(f, size)?;
511522

512523
Ok(())
513524
}

src/components/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod inspect_commit;
1111
mod msg;
1212
mod reset;
1313
mod stashmsg;
14+
mod tag_commit;
1415
mod textinput;
1516
mod utils;
1617

@@ -30,6 +31,8 @@ pub use inspect_commit::InspectCommitComponent;
3031
pub use msg::MsgComponent;
3132
pub use reset::ResetComponent;
3233
pub use stashmsg::StashMsgComponent;
34+
pub use tag_commit::TagCommitComponent;
35+
pub use textinput::TextInputComponent;
3336
pub use utils::filetree::FileTreeItemKind;
3437

3538
use crate::ui::style::Theme;

src/components/tag_commit.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use super::{
2+
textinput::TextInputComponent, visibility_blocking,
3+
CommandBlocking, CommandInfo, Component, DrawableComponent,
4+
};
5+
use crate::{
6+
queue::{InternalEvent, NeedsUpdate, Queue},
7+
strings::{self, commands},
8+
ui::style::SharedTheme,
9+
};
10+
use anyhow::Result;
11+
use asyncgit::{
12+
sync::{self, CommitId},
13+
CWD,
14+
};
15+
use crossterm::event::{Event, KeyCode};
16+
use tui::{backend::Backend, layout::Rect, Frame};
17+
18+
pub struct TagCommitComponent {
19+
input: TextInputComponent,
20+
commit_id: Option<CommitId>,
21+
queue: Queue,
22+
}
23+
24+
impl DrawableComponent for TagCommitComponent {
25+
fn draw<B: Backend>(
26+
&self,
27+
f: &mut Frame<B>,
28+
rect: Rect,
29+
) -> Result<()> {
30+
self.input.draw(f, rect)?;
31+
32+
Ok(())
33+
}
34+
}
35+
36+
impl Component for TagCommitComponent {
37+
fn commands(
38+
&self,
39+
out: &mut Vec<CommandInfo>,
40+
force_all: bool,
41+
) -> CommandBlocking {
42+
if self.is_visible() || force_all {
43+
self.input.commands(out, force_all);
44+
45+
out.push(CommandInfo::new(
46+
commands::TAG_COMMIT_CONFIRM_MSG,
47+
true,
48+
true,
49+
));
50+
}
51+
52+
visibility_blocking(self)
53+
}
54+
55+
fn event(&mut self, ev: Event) -> Result<bool> {
56+
if self.is_visible() {
57+
if self.input.event(ev)? {
58+
return Ok(true);
59+
}
60+
61+
if let Event::Key(e) = ev {
62+
if let KeyCode::Enter = e.code {
63+
self.tag()
64+
}
65+
66+
return Ok(true);
67+
}
68+
}
69+
Ok(false)
70+
}
71+
72+
fn is_visible(&self) -> bool {
73+
self.input.is_visible()
74+
}
75+
76+
fn hide(&mut self) {
77+
self.input.hide()
78+
}
79+
80+
fn show(&mut self) -> Result<()> {
81+
self.input.show()?;
82+
83+
Ok(())
84+
}
85+
}
86+
87+
impl TagCommitComponent {
88+
///
89+
pub fn new(queue: Queue, theme: SharedTheme) -> Self {
90+
Self {
91+
queue,
92+
input: TextInputComponent::new(
93+
theme,
94+
strings::TAG_COMMIT_POPUP_TITLE,
95+
strings::TAG_COMMIT_POPUP_MSG,
96+
),
97+
commit_id: None,
98+
}
99+
}
100+
101+
///
102+
pub fn open(&mut self, id: CommitId) -> Result<()> {
103+
self.commit_id = Some(id);
104+
self.show()?;
105+
106+
Ok(())
107+
}
108+
109+
///
110+
pub fn tag(&mut self) {
111+
if let Some(commit_id) = self.commit_id {
112+
match sync::tag(CWD, &commit_id, self.input.get_text()) {
113+
Ok(_) => {
114+
self.input.clear();
115+
self.hide();
116+
117+
self.queue.borrow_mut().push_back(
118+
InternalEvent::Update(NeedsUpdate::ALL),
119+
);
120+
}
121+
Err(e) => {
122+
self.hide();
123+
log::error!("e: {}", e,);
124+
self.queue.borrow_mut().push_back(
125+
InternalEvent::ShowErrorMsg(format!(
126+
"tag error:\n{}",
127+
e,
128+
)),
129+
);
130+
}
131+
}
132+
}
133+
}
134+
}

src/keys.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ pub const STASH_DROP: KeyEvent =
6666
with_mod(KeyCode::Char('D'), KeyModifiers::SHIFT);
6767
pub const CMD_BAR_TOGGLE: KeyEvent = no_mod(KeyCode::Char('.'));
6868
pub const LOG_COMMIT_DETAILS: KeyEvent = no_mod(KeyCode::Enter);
69+
pub const LOG_TAG_COMMIT: KeyEvent = no_mod(KeyCode::Char('t'));
6970
pub const COMMIT_AMEND: KeyEvent =
7071
with_mod(KeyCode::Char('a'), KeyModifiers::CONTROL);

src/queue.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub enum InternalEvent {
4949
///
5050
InspectCommit(CommitId, Option<CommitTags>),
5151
///
52+
TagCommit(CommitId),
53+
///
5254
OpenExternalEditor(Option<String>),
5355
}
5456

src/strings.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ pub static CONFIRM_MSG_STASHDROP: &str = "confirm stash drop?";
2727
pub static CONFIRM_MSG_RESETHUNK: &str = "confirm reset hunk?";
2828

2929
pub static LOG_TITLE: &str = "Commit";
30+
31+
pub static TAG_COMMIT_POPUP_TITLE: &str = "Tag";
32+
pub static TAG_COMMIT_POPUP_MSG: &str = "type tag";
33+
3034
pub static STASHLIST_TITLE: &str = "Stashes";
3135

3236
pub static HELP_TITLE: &str = "Help: all commands";
@@ -295,4 +299,10 @@ pub mod commands {
295299
"inspect selected commit in detail",
296300
CMD_GROUP_LOG,
297301
);
302+
///
303+
pub static LOG_TAG_COMMIT: CommandText =
304+
CommandText::new("Tag [t]", "tag commit", CMD_GROUP_LOG);
305+
///
306+
pub static TAG_COMMIT_CONFIRM_MSG: CommandText =
307+
CommandText::new("Tag [enter]", "tag commit", CMD_GROUP_LOG);
298308
}

src/tabs/revlog.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,19 @@ impl Component for Revlog {
207207
return Ok(true);
208208
}
209209

210+
Event::Key(keys::LOG_TAG_COMMIT) => {
211+
return if let Some(id) =
212+
self.selected_commit()
213+
{
214+
self.queue.borrow_mut().push_back(
215+
InternalEvent::TagCommit(id),
216+
);
217+
Ok(true)
218+
} else {
219+
Ok(false)
220+
};
221+
}
222+
210223
Event::Key(keys::FOCUS_RIGHT)
211224
if self.commit_details.is_visible() =>
212225
{
@@ -257,6 +270,12 @@ impl Component for Revlog {
257270
|| force_all,
258271
));
259272

273+
out.push(CommandInfo::new(
274+
commands::LOG_TAG_COMMIT,
275+
true,
276+
self.visible || force_all,
277+
));
278+
260279
visibility_blocking(self)
261280
}
262281

0 commit comments

Comments
 (0)