-
Notifications
You must be signed in to change notification settings - Fork 45
Feature/add libra tag #775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
aefda9a
e95cd9c
6f0cb78
3d6511e
e351e23
af38ac0
f8c78e0
c447054
b777ef9
d49b5f7
997ab35
1484b25
b81c128
9a4d482
7ee4c17
8d9ca9e
9a71a47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,178 @@ | ||||||
use crate::{ | ||||||
command::get_target_commit, internal::{tag::TagInfo, head::Head}, internal::config | ||||||
}; | ||||||
use clap::Parser; | ||||||
use mercury::internal::object::{commit::Commit, signature::SignatureType, tag::Tag, types::ObjectType}; | ||||||
use mercury::internal::object::signature::Signature; | ||||||
use mercury::hash::SHA1; | ||||||
|
||||||
|
||||||
use crate::command::load_object; | ||||||
use super::save_object; | ||||||
|
||||||
#[derive(Parser, Debug)] | ||||||
pub struct TagArgs { | ||||||
/// Name of the tag to create, delete, or inspect | ||||||
#[clap(group = "create")] | ||||||
tag_name: Option<String>, | ||||||
|
||||||
/// Commit hash or tag name to tag (default is HEAD) | ||||||
#[clap(requires = "create")] | ||||||
commit_hash: Option<String>, | ||||||
|
||||||
/// List all tags | ||||||
#[clap(short, long, group = "sub", default_value = "true")] | ||||||
list: bool, | ||||||
|
||||||
/// Create a new tag (lightweight or annotated) | ||||||
#[clap(short = 'a', long, group = "sub", group = "create")] | ||||||
annotate: Option<String>, | ||||||
|
||||||
/// The message for an annotated tag | ||||||
#[clap(short = 'm', long, requires = "annotate")] | ||||||
message: Option<String>, | ||||||
|
||||||
/// Delete a tag | ||||||
#[clap(short = 'd', long, group = "sub", requires = "tag_name")] | ||||||
delete: bool, | ||||||
|
||||||
/// change current tag | ||||||
#[clap(short = 'f', long)] | ||||||
force: bool, | ||||||
} | ||||||
|
||||||
pub async fn execute(args: TagArgs) { | ||||||
if args.delete { | ||||||
delete_tag(args.tag_name.unwrap()).await; | ||||||
} else if args.tag_name.is_some() { | ||||||
create_tag(args.tag_name.unwrap(), args.commit_hash, args.force).await; | ||||||
} else if args.annotate.is_some() { | ||||||
create_annotated_tag(args.annotate.unwrap(), args.message, args.commit_hash, args.force).await; | ||||||
} else if args.list { | ||||||
// default behavior | ||||||
list_tags().await; | ||||||
} else { | ||||||
panic!("should not reach here") | ||||||
} | ||||||
} | ||||||
|
||||||
pub async fn create_tag(tag_name: String, commit_hash: Option<String>, force: bool){ | ||||||
tracing::debug!("create tag: {} from {:?}", tag_name, commit_hash); | ||||||
|
||||||
if !is_valid_git_tag_name(&tag_name) { | ||||||
eprintln!("fatal: invalid tag name: {}", tag_name); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Returning an error would be more appropriate than just printing and returning.
Suggested change
Copilot is powered by AI, so mistakes are possible. Review output carefully before use. Positive FeedbackNegative Feedback |
||||||
return; | ||||||
} | ||||||
|
||||||
// check if tag exists | ||||||
let tag = TagInfo::find_tag(&tag_name).await; | ||||||
if tag.is_some() && !force { | ||||||
panic!("fatal: A tag named '{}' already exists.", tag_name); | ||||||
} | ||||||
|
||||||
let commit_id = match commit_hash { | ||||||
Some(commit_hash) => { | ||||||
let commit = get_target_commit(&commit_hash).await; | ||||||
match commit { | ||||||
Ok(commit) => commit, | ||||||
Err(e) => { | ||||||
eprintln!("fatal: {}", e); | ||||||
return; | ||||||
} | ||||||
} | ||||||
} | ||||||
None => Head::current_commit().await.unwrap(), | ||||||
}; | ||||||
tracing::debug!("base commit_id: {}", commit_id); | ||||||
|
||||||
// check if commit_hash exists | ||||||
let _ = load_object::<Commit>(&commit_id) | ||||||
.unwrap_or_else(|_| panic!("fatal: not a valid object name: '{}'", commit_id)); | ||||||
|
||||||
// create tag | ||||||
TagInfo::update_tag(&tag_name, &commit_id.to_string()).await; | ||||||
} | ||||||
|
||||||
|
||||||
async fn create_annotated_tag(tag_name: String, message: Option<String>, commit_hash: Option<String>, force: bool) { | ||||||
create_tag(tag_name.clone(), commit_hash.clone(), force).await; | ||||||
let commit_id = match commit_hash { | ||||||
Some(commit_hash) => { | ||||||
let commit = get_target_commit(&commit_hash).await; | ||||||
match commit { | ||||||
Ok(commit) => commit, | ||||||
Err(e) => { | ||||||
eprintln!("fatal: {}", e); | ||||||
return; | ||||||
} | ||||||
} | ||||||
} | ||||||
None => Head::current_commit().await.unwrap(), | ||||||
}; | ||||||
let author = config::Config::get("user", None, "name").await.unwrap(); | ||||||
let email = config::Config::get("user", None, "email").await.unwrap(); | ||||||
let tag = Tag { | ||||||
id: SHA1::default(), | ||||||
object_hash: commit_id, | ||||||
object_type: ObjectType::Tag, | ||||||
tag_name, | ||||||
tagger: Signature::new(SignatureType::Tagger, author.to_owned(), email.to_owned()), | ||||||
message: message.unwrap_or_default() | ||||||
}; | ||||||
save_object(&tag, &tag.id).unwrap(); | ||||||
} | ||||||
|
||||||
async fn delete_tag(tag_name: String) { | ||||||
let _ = TagInfo::find_tag(&tag_name) | ||||||
.await | ||||||
.unwrap_or_else(|| panic!("fatal: tag '{}' not found", tag_name)); | ||||||
|
||||||
TagInfo::delete_tag(&tag_name).await; | ||||||
} | ||||||
|
||||||
async fn list_tags() { | ||||||
let tags = TagInfo::list_tags().await; | ||||||
for tag in tags { | ||||||
println!("{}", tag.name); | ||||||
} | ||||||
} | ||||||
|
||||||
|
||||||
|
||||||
fn is_valid_git_tag_name(tag_name: &str) -> bool { | ||||||
// Check for empty or invalid length | ||||||
if tag_name.is_empty() || tag_name.len() > 255 { | ||||||
return false; | ||||||
} | ||||||
|
||||||
// Reserved names | ||||||
let reserved_names = [ | ||||||
"HEAD", "@", "FETCH_HEAD", "ORIG_HEAD", "MERGE_HEAD", "REBASE_HEAD", | ||||||
]; | ||||||
if reserved_names.contains(&tag_name) { | ||||||
return false; | ||||||
} | ||||||
|
||||||
// Check for forbidden characters | ||||||
let forbidden_chars = [' ', '~', '^', ':', '?', '*', '[', '\x00', '\x7f']; | ||||||
if tag_name.chars().any(|c| forbidden_chars.contains(&c) || c.is_control()) { | ||||||
return false; | ||||||
} | ||||||
|
||||||
// Check for invalid start or end characters | ||||||
if tag_name.starts_with('.') || tag_name.starts_with('/') || tag_name.ends_with('.') || tag_name.ends_with('/') { | ||||||
return false; | ||||||
} | ||||||
|
||||||
// Check for double slashes | ||||||
if tag_name.contains("//") { | ||||||
return false; | ||||||
} | ||||||
|
||||||
// Check for trailing '@' | ||||||
if tag_name.ends_with('@') { | ||||||
return false; | ||||||
} | ||||||
|
||||||
true | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ pub mod db; | |
pub mod head; | ||
pub mod model; | ||
pub mod protocol; | ||
pub mod tag; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,99 @@ | ||||||
use std::str::FromStr; | ||||||
|
||||||
use sea_orm::ActiveModelTrait; | ||||||
use sea_orm::ActiveValue::Set; | ||||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; | ||||||
|
||||||
use mercury::hash::SHA1; | ||||||
|
||||||
use crate::internal::db::get_db_conn_instance; | ||||||
use crate::internal::model::reference; | ||||||
|
||||||
pub struct TagInfo { | ||||||
pub name: String, | ||||||
pub commit: SHA1, | ||||||
} | ||||||
|
||||||
impl TagInfo { | ||||||
|
||||||
pub async fn query_reference(tag_name: &str) -> Option<reference::Model> { | ||||||
let db_conn = get_db_conn_instance().await; | ||||||
reference::Entity::find() | ||||||
.filter(reference::Column::Name.eq(tag_name)) | ||||||
.filter(reference::Column::Kind.eq(reference::ConfigKind::Tag)) | ||||||
.one(db_conn) | ||||||
.await | ||||||
.unwrap() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using unwrap() can cause a panic if the result is None. Consider handling the None case more gracefully.
Suggested change
Copilot is powered by AI, so mistakes are possible. Review output carefully before use. Positive FeedbackNegative Feedback |
||||||
} | ||||||
|
||||||
/// list all tags | ||||||
pub async fn list_tags() -> Vec<TagInfo> { | ||||||
let db_conn = get_db_conn_instance().await; | ||||||
let tags = reference::Entity::find() | ||||||
.filter(reference::Column::Kind.eq(reference::ConfigKind::Tag)) | ||||||
.all(db_conn) | ||||||
.await | ||||||
.unwrap(); | ||||||
|
||||||
tags | ||||||
.iter() | ||||||
.map(|tag| TagInfo { | ||||||
name: tag.name.as_ref().unwrap().clone(), | ||||||
commit: SHA1::from_str(tag.commit.as_ref().unwrap()).unwrap(), | ||||||
}) | ||||||
.collect() | ||||||
} | ||||||
|
||||||
/// is the tag exists | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment should be 'Check if the tag exists'.
Suggested change
Copilot is powered by AI, so mistakes are possible. Review output carefully before use. Positive FeedbackNegative Feedback |
||||||
pub async fn exists(tag_name: &str) -> bool { | ||||||
let tag = Self::find_tag(tag_name).await; | ||||||
tag.is_some() | ||||||
} | ||||||
|
||||||
/// get the tag by name | ||||||
pub async fn find_tag(tag_name: &str) -> Option<TagInfo> { | ||||||
let tag = Self::query_reference(tag_name).await; | ||||||
match tag { | ||||||
Some(tag) => Some(TagInfo { | ||||||
name: tag.name.as_ref().unwrap().clone(), | ||||||
commit: SHA1::from_str(tag.commit.as_ref().unwrap()).unwrap(), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using unwrap() can cause a panic if the result is None. Consider handling the None case more gracefully.
Suggested change
Copilot is powered by AI, so mistakes are possible. Review output carefully before use. Positive FeedbackNegative Feedback |
||||||
}), | ||||||
None => None, | ||||||
} | ||||||
} | ||||||
|
||||||
/// update the tag | ||||||
pub async fn update_tag(tag_name: &str, commit_hash: &str) { | ||||||
let db_conn = get_db_conn_instance().await; | ||||||
// check if tag exists | ||||||
let tag = Self::query_reference(tag_name).await; | ||||||
|
||||||
match tag { | ||||||
Some(tag) => { | ||||||
let mut tag: reference::ActiveModel = tag.into(); | ||||||
tag.commit = Set(Some(commit_hash.to_owned())); | ||||||
tag.update(db_conn).await.unwrap(); | ||||||
} | ||||||
None => { | ||||||
reference::ActiveModel { | ||||||
name: Set(Some(tag_name.to_owned())), | ||||||
kind: Set(reference::ConfigKind::Tag), | ||||||
commit: Set(Some(commit_hash.to_owned())), | ||||||
..Default::default() | ||||||
} | ||||||
.insert(db_conn) | ||||||
.await | ||||||
.unwrap(); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
pub async fn delete_tag(tag_name: &str) { | ||||||
let db_conn = get_db_conn_instance().await; | ||||||
let tag: reference::ActiveModel = | ||||||
Self::query_reference(tag_name).await.unwrap().into(); | ||||||
tag.delete(db_conn).await.unwrap(); | ||||||
} | ||||||
|
||||||
|
||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unexpected cases should be handled more gracefully instead of panicking.
Copilot is powered by AI, so mistakes are possible. Review output carefully before use.