style: 格式化代码并优化导入顺序
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
use aes_gcm::{
|
||||
aead::{Aead, KeyInit},
|
||||
Aes256Gcm, Nonce,
|
||||
aead::{Aead, KeyInit},
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
|
||||
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
||||
use rand::Rng;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -18,63 +18,62 @@ pub fn encrypt(data: &[u8], password: &str) -> Result<String> {
|
||||
rand::thread_rng().fill(&mut salt);
|
||||
let mut nonce_bytes = [0u8; NONCE_LEN];
|
||||
rand::thread_rng().fill(&mut nonce_bytes);
|
||||
|
||||
|
||||
let key = derive_key(password, &salt)?;
|
||||
let cipher = Aes256Gcm::new_from_slice(&key)
|
||||
.context("Failed to create cipher")?;
|
||||
let cipher = Aes256Gcm::new_from_slice(&key).context("Failed to create cipher")?;
|
||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||
|
||||
|
||||
let encrypted = cipher
|
||||
.encrypt(nonce, data)
|
||||
.map_err(|e| anyhow::anyhow!("Encryption failed: {:?}", e))?;
|
||||
|
||||
|
||||
// Combine salt + nonce + encrypted data
|
||||
let mut result = Vec::with_capacity(SALT_LEN + NONCE_LEN + encrypted.len());
|
||||
result.extend_from_slice(&salt);
|
||||
result.extend_from_slice(&nonce_bytes);
|
||||
result.extend_from_slice(&encrypted);
|
||||
|
||||
|
||||
Ok(BASE64.encode(&result))
|
||||
}
|
||||
|
||||
/// Decrypt data with password
|
||||
pub fn decrypt(encrypted_data: &str, password: &str) -> Result<Vec<u8>> {
|
||||
let data = BASE64.decode(encrypted_data)
|
||||
let data = BASE64
|
||||
.decode(encrypted_data)
|
||||
.context("Invalid base64 encoding")?;
|
||||
|
||||
|
||||
if data.len() < SALT_LEN + NONCE_LEN {
|
||||
anyhow::bail!("Invalid encrypted data format");
|
||||
}
|
||||
|
||||
|
||||
let salt = &data[..SALT_LEN];
|
||||
let nonce_bytes = &data[SALT_LEN..SALT_LEN + NONCE_LEN];
|
||||
let encrypted = &data[SALT_LEN + NONCE_LEN..];
|
||||
|
||||
|
||||
let key = derive_key(password, salt)?;
|
||||
let cipher = Aes256Gcm::new_from_slice(&key)
|
||||
.context("Failed to create cipher")?;
|
||||
let cipher = Aes256Gcm::new_from_slice(&key).context("Failed to create cipher")?;
|
||||
let nonce = Nonce::from_slice(nonce_bytes);
|
||||
|
||||
|
||||
let decrypted = cipher
|
||||
.decrypt(nonce, encrypted)
|
||||
.map_err(|e| anyhow::anyhow!("Decryption failed: {:?}", e))?;
|
||||
|
||||
|
||||
Ok(decrypted)
|
||||
}
|
||||
|
||||
/// Derive key from password using simple method
|
||||
fn derive_key(password: &str, salt: &[u8]) -> Result<[u8; KEY_LEN]> {
|
||||
use sha2::{Sha256, Digest};
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(salt);
|
||||
hasher.update(password.as_bytes());
|
||||
hasher.update(b"quicommit_key_derivation_v1");
|
||||
|
||||
|
||||
let hash = hasher.finalize();
|
||||
let mut key = [0u8; KEY_LEN];
|
||||
key.copy_from_slice(&hash[..KEY_LEN]);
|
||||
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
@@ -97,7 +96,7 @@ pub fn decrypt_from_file(path: &Path, password: &str) -> Result<Vec<u8>> {
|
||||
pub fn generate_token(length: usize) -> String {
|
||||
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
|
||||
(0..length)
|
||||
.map(|_| {
|
||||
let idx = rng.gen_range(0..CHARSET.len());
|
||||
@@ -122,10 +121,10 @@ mod tests {
|
||||
fn test_encrypt_decrypt() {
|
||||
let data = b"Hello, World!";
|
||||
let password = "my_secret_password";
|
||||
|
||||
|
||||
let encrypted = encrypt(data, password).unwrap();
|
||||
let decrypted = decrypt(&encrypted, password).unwrap();
|
||||
|
||||
|
||||
assert_eq!(data.to_vec(), decrypted);
|
||||
}
|
||||
|
||||
@@ -133,7 +132,7 @@ mod tests {
|
||||
fn test_wrong_password() {
|
||||
let data = b"Hello, World!";
|
||||
let encrypted = encrypt(data, "correct_password").unwrap();
|
||||
|
||||
|
||||
assert!(decrypt(&encrypted, "wrong_password").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,12 @@ pub fn edit_content(initial_content: &str) -> Result<String> {
|
||||
|
||||
/// Edit file in user's default editor
|
||||
pub fn edit_file(path: &Path) -> Result<String> {
|
||||
let content = fs::read_to_string(path)
|
||||
.unwrap_or_default();
|
||||
|
||||
let edited = edit::edit(&content)
|
||||
.context("Failed to open editor")?;
|
||||
|
||||
fs::write(path, &edited)
|
||||
.with_context(|| format!("Failed to write file: {:?}", path))?;
|
||||
|
||||
let content = fs::read_to_string(path).unwrap_or_default();
|
||||
|
||||
let edited = edit::edit(&content).context("Failed to open editor")?;
|
||||
|
||||
fs::write(path, &edited).with_context(|| format!("Failed to write file: {:?}", path))?;
|
||||
|
||||
Ok(edited)
|
||||
}
|
||||
|
||||
@@ -27,11 +24,10 @@ pub fn edit_temp(initial_content: &str, extension: &str) -> Result<String> {
|
||||
.suffix(&format!(".{}", extension))
|
||||
.tempfile()
|
||||
.context("Failed to create temp file")?;
|
||||
|
||||
|
||||
let path = temp_file.path();
|
||||
fs::write(path, initial_content)
|
||||
.context("Failed to write temp file")?;
|
||||
|
||||
fs::write(path, initial_content).context("Failed to write temp file")?;
|
||||
|
||||
edit_file(path)
|
||||
}
|
||||
|
||||
@@ -65,7 +61,6 @@ pub fn get_editor() -> String {
|
||||
/// Check if editor is available
|
||||
pub fn check_editor() -> Result<()> {
|
||||
let editor = get_editor();
|
||||
which::which(&editor)
|
||||
.with_context(|| format!("Editor '{}' not found in PATH", editor))?;
|
||||
which::which(&editor).with_context(|| format!("Editor '{}' not found in PATH", editor))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub fn format_conventional_commit(
|
||||
breaking: bool,
|
||||
) -> String {
|
||||
let mut message = String::new();
|
||||
|
||||
|
||||
message.push_str(commit_type);
|
||||
if let Some(s) = scope {
|
||||
message.push_str(&format!("({})", s));
|
||||
@@ -19,15 +19,15 @@ pub fn format_conventional_commit(
|
||||
message.push('!');
|
||||
}
|
||||
message.push_str(&format!(": {}", description));
|
||||
|
||||
|
||||
if let Some(b) = body {
|
||||
message.push_str(&format!("\n\n{}", b));
|
||||
}
|
||||
|
||||
|
||||
if let Some(f) = footer {
|
||||
message.push_str(&format!("\n\n{}", f));
|
||||
}
|
||||
|
||||
|
||||
message
|
||||
}
|
||||
|
||||
@@ -41,27 +41,27 @@ pub fn format_commitlint_commit(
|
||||
references: Option<&[&str]>,
|
||||
) -> String {
|
||||
let mut message = String::new();
|
||||
|
||||
|
||||
message.push_str(commit_type);
|
||||
if let Some(s) = scope {
|
||||
message.push_str(&format!("({})", s));
|
||||
}
|
||||
message.push_str(&format!(": {}", subject));
|
||||
|
||||
|
||||
if let Some(refs) = references {
|
||||
for reference in refs {
|
||||
message.push_str(&format!(" #{}", reference));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let Some(b) = body {
|
||||
message.push_str(&format!("\n\n{}", b));
|
||||
}
|
||||
|
||||
|
||||
if let Some(f) = footer {
|
||||
message.push_str(&format!("\n\n{}", f));
|
||||
}
|
||||
|
||||
|
||||
message
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ pub fn wrap_text(text: &str, width: usize) -> String {
|
||||
/// Clean commit message (remove comments, extra whitespace)
|
||||
pub fn clean_message(message: &str) -> String {
|
||||
let comment_regex = Regex::new(r"^#.*$").unwrap();
|
||||
|
||||
|
||||
message
|
||||
.lines()
|
||||
.filter(|line| !comment_regex.is_match(line.trim()))
|
||||
@@ -97,7 +97,7 @@ mod tests {
|
||||
Some("Closes #123"),
|
||||
false,
|
||||
);
|
||||
|
||||
|
||||
assert!(msg.contains("feat(auth): add login functionality"));
|
||||
assert!(msg.contains("This adds OAuth2 login support."));
|
||||
assert!(msg.contains("Closes #123"));
|
||||
@@ -113,7 +113,7 @@ mod tests {
|
||||
Some("BREAKING CHANGE: response format changed"),
|
||||
true,
|
||||
);
|
||||
|
||||
|
||||
assert!(msg.starts_with("feat!: change API response format"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use std::env;
|
||||
|
||||
const SERVICE_NAME: &str = "quicommit";
|
||||
@@ -78,7 +78,8 @@ impl KeyringManager {
|
||||
let entry = keyring::Entry::new(SERVICE_NAME, provider)
|
||||
.context("Failed to create keyring entry")?;
|
||||
|
||||
entry.set_password(api_key)
|
||||
entry
|
||||
.set_password(api_key)
|
||||
.context("Failed to store API key")?;
|
||||
|
||||
Ok(())
|
||||
@@ -86,9 +87,10 @@ impl KeyringManager {
|
||||
|
||||
pub fn get_api_key(&self, provider: &str) -> Result<Option<String>> {
|
||||
if let Ok(key) = env::var(ENV_API_KEY)
|
||||
&& !key.is_empty() {
|
||||
return Ok(Some(key));
|
||||
}
|
||||
&& !key.is_empty()
|
||||
{
|
||||
return Ok(Some(key));
|
||||
}
|
||||
|
||||
if !self.is_available() {
|
||||
return Ok(None);
|
||||
@@ -112,7 +114,8 @@ impl KeyringManager {
|
||||
let entry = keyring::Entry::new(SERVICE_NAME, provider)
|
||||
.context("Failed to create keyring entry")?;
|
||||
|
||||
entry.delete_credential()
|
||||
entry
|
||||
.delete_credential()
|
||||
.context("Failed to delete API key")?;
|
||||
|
||||
Ok(())
|
||||
@@ -126,7 +129,13 @@ impl KeyringManager {
|
||||
format!("{}/{}", PAT_SERVICE_PREFIX, profile_name)
|
||||
}
|
||||
|
||||
pub fn store_pat(&self, profile_name: &str, user_email: &str, service: &str, token: &str) -> Result<()> {
|
||||
pub fn store_pat(
|
||||
&self,
|
||||
profile_name: &str,
|
||||
user_email: &str,
|
||||
service: &str,
|
||||
token: &str,
|
||||
) -> Result<()> {
|
||||
if !self.is_available() {
|
||||
bail!("Keyring is not available on this system");
|
||||
}
|
||||
@@ -137,15 +146,24 @@ impl KeyringManager {
|
||||
let entry = keyring::Entry::new(&keyring_service, &keyring_user)
|
||||
.context("Failed to create keyring entry for PAT")?;
|
||||
|
||||
entry.set_password(token)
|
||||
entry
|
||||
.set_password(token)
|
||||
.context("Failed to store PAT in keyring")?;
|
||||
|
||||
eprintln!("[DEBUG] PAT stored in keyring: service={}, user={}", keyring_service, keyring_user);
|
||||
eprintln!(
|
||||
"[DEBUG] PAT stored in keyring: service={}, user={}",
|
||||
keyring_service, keyring_user
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_pat(&self, profile_name: &str, user_email: &str, service: &str) -> Result<Option<String>> {
|
||||
pub fn get_pat(
|
||||
&self,
|
||||
profile_name: &str,
|
||||
user_email: &str,
|
||||
service: &str,
|
||||
) -> Result<Option<String>> {
|
||||
if !self.is_available() {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -158,11 +176,17 @@ impl KeyringManager {
|
||||
|
||||
match entry.get_password() {
|
||||
Ok(token) => {
|
||||
eprintln!("[DEBUG] PAT retrieved from keyring: service={}, user={}", keyring_service, keyring_user);
|
||||
eprintln!(
|
||||
"[DEBUG] PAT retrieved from keyring: service={}, user={}",
|
||||
keyring_service, keyring_user
|
||||
);
|
||||
Ok(Some(token))
|
||||
}
|
||||
Err(keyring::Error::NoEntry) => {
|
||||
eprintln!("[DEBUG] PAT not found in keyring: service={}, user={}", keyring_service, keyring_user);
|
||||
eprintln!(
|
||||
"[DEBUG] PAT not found in keyring: service={}, user={}",
|
||||
keyring_service, keyring_user
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
@@ -180,22 +204,36 @@ impl KeyringManager {
|
||||
let entry = keyring::Entry::new(&keyring_service, &keyring_user)
|
||||
.context("Failed to create keyring entry for PAT")?;
|
||||
|
||||
entry.delete_credential()
|
||||
entry
|
||||
.delete_credential()
|
||||
.context("Failed to delete PAT from keyring")?;
|
||||
|
||||
eprintln!("[DEBUG] PAT deleted from keyring: service={}, user={}", keyring_service, keyring_user);
|
||||
eprintln!(
|
||||
"[DEBUG] PAT deleted from keyring: service={}, user={}",
|
||||
keyring_service, keyring_user
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn has_pat(&self, profile_name: &str, user_email: &str, service: &str) -> bool {
|
||||
self.get_pat(profile_name, user_email, service).unwrap_or(None).is_some()
|
||||
self.get_pat(profile_name, user_email, service)
|
||||
.unwrap_or(None)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn delete_all_pats_for_profile(&self, profile_name: &str, user_email: &str, services: &[String]) -> Result<()> {
|
||||
pub fn delete_all_pats_for_profile(
|
||||
&self,
|
||||
profile_name: &str,
|
||||
user_email: &str,
|
||||
services: &[String],
|
||||
) -> Result<()> {
|
||||
for service in services {
|
||||
if let Err(e) = self.delete_pat(profile_name, user_email, service) {
|
||||
eprintln!("[DEBUG] Failed to delete PAT for service '{}': {}", service, e);
|
||||
eprintln!(
|
||||
"[DEBUG] Failed to delete PAT for service '{}': {}",
|
||||
service, e
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -259,7 +297,14 @@ pub fn get_default_model(provider: &str) -> &'static str {
|
||||
}
|
||||
|
||||
pub fn get_supported_providers() -> &'static [&'static str] {
|
||||
&["ollama", "openai", "anthropic", "kimi", "deepseek", "openrouter"]
|
||||
&[
|
||||
"ollama",
|
||||
"openai",
|
||||
"anthropic",
|
||||
"kimi",
|
||||
"deepseek",
|
||||
"openrouter",
|
||||
]
|
||||
}
|
||||
|
||||
pub fn provider_needs_api_key(provider: &str) -> bool {
|
||||
@@ -273,10 +318,19 @@ mod tests {
|
||||
#[test]
|
||||
fn test_get_default_base_url() {
|
||||
assert_eq!(get_default_base_url("openai"), "https://api.openai.com/v1");
|
||||
assert_eq!(get_default_base_url("anthropic"), "https://api.anthropic.com/v1");
|
||||
assert_eq!(
|
||||
get_default_base_url("anthropic"),
|
||||
"https://api.anthropic.com/v1"
|
||||
);
|
||||
assert_eq!(get_default_base_url("kimi"), "https://api.moonshot.cn/v1");
|
||||
assert_eq!(get_default_base_url("deepseek"), "https://api.deepseek.com/v1");
|
||||
assert_eq!(get_default_base_url("openrouter"), "https://openrouter.ai/api/v1");
|
||||
assert_eq!(
|
||||
get_default_base_url("deepseek"),
|
||||
"https://api.deepseek.com/v1"
|
||||
);
|
||||
assert_eq!(
|
||||
get_default_base_url("openrouter"),
|
||||
"https://openrouter.ai/api/v1"
|
||||
);
|
||||
assert_eq!(get_default_base_url("ollama"), "http://localhost:11434");
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,10 @@ pub fn print_info(msg: &str) {
|
||||
pub fn confirm(prompt: &str) -> Result<bool> {
|
||||
print!("{} [y/N] ", prompt);
|
||||
io::stdout().flush()?;
|
||||
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
|
||||
|
||||
Ok(input.trim().to_lowercase().starts_with('y'))
|
||||
}
|
||||
|
||||
@@ -43,17 +43,17 @@ pub fn confirm(prompt: &str) -> Result<bool> {
|
||||
pub fn input(prompt: &str) -> Result<String> {
|
||||
print!("{}: ", prompt);
|
||||
io::stdout().flush()?;
|
||||
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
|
||||
|
||||
Ok(input.trim().to_string())
|
||||
}
|
||||
|
||||
/// Get password input (hidden)
|
||||
pub fn password_input(prompt: &str) -> Result<String> {
|
||||
use dialoguer::Password;
|
||||
|
||||
|
||||
Password::new()
|
||||
.with_prompt(prompt)
|
||||
.interact()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{Result, bail};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
@@ -67,7 +67,7 @@ lazy_static! {
|
||||
/// Validate conventional commit message
|
||||
pub fn validate_conventional_commit(message: &str) -> Result<()> {
|
||||
let first_line = message.lines().next().unwrap_or("");
|
||||
|
||||
|
||||
if !CONVENTIONAL_COMMIT_REGEX.is_match(first_line) {
|
||||
bail!(
|
||||
"Invalid conventional commit format. Expected: <type>[optional scope]: <description>\n\
|
||||
@@ -75,32 +75,32 @@ pub fn validate_conventional_commit(message: &str) -> Result<()> {
|
||||
CONVENTIONAL_TYPES.join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if first_line.len() > 100 {
|
||||
bail!("Commit subject too long (max 100 characters)");
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate @commitlint commit message
|
||||
pub fn validate_commitlint_commit(message: &str) -> Result<()> {
|
||||
let first_line = message.lines().next().unwrap_or("");
|
||||
|
||||
|
||||
let parts: Vec<&str> = first_line.splitn(2, ':').collect();
|
||||
if parts.len() != 2 {
|
||||
bail!("Invalid commit format. Expected: <type>[optional scope]: <subject>");
|
||||
}
|
||||
|
||||
|
||||
let type_part = parts[0];
|
||||
let subject = parts[1].trim();
|
||||
|
||||
|
||||
let commit_type = type_part
|
||||
.split('(')
|
||||
.next()
|
||||
.unwrap_or("")
|
||||
.trim_end_matches('!');
|
||||
|
||||
|
||||
if !COMMITLINT_TYPES.contains(&commit_type) {
|
||||
bail!(
|
||||
"Invalid commit type: '{}'. Valid types: {}",
|
||||
@@ -108,27 +108,32 @@ pub fn validate_commitlint_commit(message: &str) -> Result<()> {
|
||||
COMMITLINT_TYPES.join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if subject.is_empty() {
|
||||
bail!("Commit subject cannot be empty");
|
||||
}
|
||||
|
||||
|
||||
if subject.len() < 4 {
|
||||
bail!("Commit subject too short (min 4 characters)");
|
||||
}
|
||||
|
||||
|
||||
if subject.len() > 100 {
|
||||
bail!("Commit subject too long (max 100 characters)");
|
||||
}
|
||||
|
||||
if subject.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) {
|
||||
|
||||
if subject
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_uppercase())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
bail!("Commit subject should not start with uppercase letter");
|
||||
}
|
||||
|
||||
|
||||
if subject.ends_with('.') {
|
||||
bail!("Commit subject should not end with a period");
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -137,25 +142,25 @@ pub fn validate_scope(scope: &str) -> Result<()> {
|
||||
if scope.is_empty() {
|
||||
bail!("Scope cannot be empty");
|
||||
}
|
||||
|
||||
|
||||
if !SCOPE_REGEX.is_match(scope) {
|
||||
bail!("Invalid scope format. Use lowercase letters, numbers, and hyphens only");
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate semantic version tag
|
||||
pub fn validate_semver(version: &str) -> Result<()> {
|
||||
let version = version.trim_start_matches('v');
|
||||
|
||||
|
||||
if !SEMVER_REGEX.is_match(version) {
|
||||
bail!(
|
||||
"Invalid semantic version format. Expected: MAJOR.MINOR.PATCH[-prerelease][+build]\n\
|
||||
Examples: 1.0.0, 1.2.3-beta, v2.0.0+build123"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -164,7 +169,7 @@ pub fn validate_email(email: &str) -> Result<()> {
|
||||
if !EMAIL_REGEX.is_match(email) {
|
||||
bail!("Invalid email address format");
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -173,7 +178,7 @@ pub fn validate_gpg_key_id(key_id: &str) -> Result<()> {
|
||||
if !GPG_KEY_ID_REGEX.is_match(key_id) {
|
||||
bail!("Invalid GPG key ID format. Expected 16-40 hexadecimal characters");
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -182,15 +187,18 @@ pub fn validate_profile_name(name: &str) -> Result<()> {
|
||||
if name.is_empty() {
|
||||
bail!("Profile name cannot be empty");
|
||||
}
|
||||
|
||||
|
||||
if name.len() > 50 {
|
||||
bail!("Profile name too long (max 50 characters)");
|
||||
}
|
||||
|
||||
if !name.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
|
||||
|
||||
if !name
|
||||
.chars()
|
||||
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
|
||||
{
|
||||
bail!("Profile name can only contain letters, numbers, hyphens, and underscores");
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -201,7 +209,7 @@ pub fn is_valid_commit_type(commit_type: &str, use_commitlint: bool) -> bool {
|
||||
} else {
|
||||
CONVENTIONAL_TYPES
|
||||
};
|
||||
|
||||
|
||||
types.contains(&commit_type)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user