Skip to content

Commit a2361bc

Browse files
committed
feat(src-tauri): 添加和更新仓库相关功能
更新了 build.yml 工作流;在 src-tauri 目录下添加和更新了多个命令和领域相关文件;在 src/components 目录下添加了 CreateTagDialog.tsx, TagList.tsx 和 Textarea.tsx 组件;更新了 src/store/repoStore.ts 和 src/types/index.ts 文件。
1 parent 35fdbfe commit a2361bc

12 files changed

Lines changed: 608 additions & 23 deletions

File tree

.github/workflows/build.yml

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,36 @@ permissions:
1313
contents: write
1414

1515
jobs:
16-
tag-version:
16+
create-tag:
1717
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[skip-release]')
1818
runs-on: ubuntu-latest
1919
outputs:
20-
tag: ${{ steps.tag.outputs.tag }}
20+
new_tag: ${{ steps.create_tag.outputs.tag }}
2121
steps:
2222
- name: Checkout repository
2323
uses: actions/checkout@v4
2424
with:
2525
fetch-depth: 0
2626

27-
- name: Get version from tauri.conf.json
27+
- name: Get version
2828
id: version
2929
run: |
3030
VERSION=$(jq -r '.version' src-tauri/tauri.conf.json)
3131
echo "version=$VERSION" >> $GITHUB_OUTPUT
32-
echo "Version: $VERSION"
3332
3433
- name: Create and push tag
35-
id: tag
34+
id: create_tag
3635
env:
3736
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3837
run: |
39-
VERSION="${{ steps.version.outputs.version }}"
40-
TAG="v$VERSION"
41-
echo "tag=$TAG" >> $GITHUB_OUTPUT
38+
TAG="v${{ steps.version.outputs.version }}"
39+
echo "Creating tag: $TAG"
4240
git tag -f "$TAG"
4341
git push origin "$TAG" --force
42+
echo "tag=$TAG" >> $GITHUB_OUTPUT
4443
4544
build:
46-
needs: tag-version
47-
if: |
48-
always() &&
49-
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') || needs.tag-version.result == 'success')
45+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
5046
strategy:
5147
fail-fast: false
5248
matrix:
@@ -93,7 +89,3 @@ jobs:
9389
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
9490
with:
9591
args: ${{ matrix.args }}
96-
releaseTag: ${{ needs.tag-version.outputs.tag || github.ref_name }}
97-
releaseBody: 'See the assets to download and install this version.'
98-
releaseDraft: false
99-
prerelease: false

src-tauri/src/commands/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pub mod commit;
33
pub mod stash;
44
pub mod clone;
55

6-
pub use repo::{scan_repositories, get_repo_status, get_branch_info, get_commit_history, get_local_branches, switch_branch, publish_branch, push_branch, get_git_username, delete_branch, rename_branch, create_branch, get_file_diff, merge_branch, fetch_remote, pull_branch};
6+
pub use repo::{scan_repositories, get_repo_status, get_branch_info, get_commit_history, get_local_branches, switch_branch, publish_branch, push_branch, get_git_username, delete_branch, rename_branch, create_branch, get_file_diff, merge_branch, fetch_remote, pull_branch, get_tags, create_tag, delete_tag, push_tag, delete_remote_tag};
77
pub use commit::{stage_files, unstage_files, stage_all, unstage_all, commit, revoke_latest_commit, batch_commit, generate_commit_message, review_code};
88
pub use stash::{get_stash_list, stash_save, stash_apply, stash_pop, stash_drop};
99
pub use clone::clone_repository;

src-tauri/src/commands/repo.rs

Lines changed: 204 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::domain::{RepositoryInfo, RepoStatus, StatusItem, BranchInfo, CommitInfo, LocalBranch};
1+
use crate::domain::{RepositoryInfo, RepoStatus, StatusItem, BranchInfo, CommitInfo, LocalBranch, TagInfo};
22
use crate::error::{AppError, Result};
33
use git2::{Repository, StatusOptions};
44
use ignore::WalkBuilder;
@@ -917,3 +917,206 @@ fn pull_branch_impl(
917917

918918
Ok(())
919919
}
920+
921+
/// Get all tags for a repository
922+
#[tauri::command]
923+
pub async fn get_tags(path: String) -> std::result::Result<Vec<TagInfo>, String> {
924+
let repo = Repository::open(&path).map_err(|e| e.to_string())?;
925+
get_tags_impl(&repo).map_err(|e| e.to_string())
926+
}
927+
928+
fn get_tags_impl(repo: &Repository) -> Result<Vec<TagInfo>> {
929+
let tag_names = repo.tag_names(None)?;
930+
let mut tags = Vec::new();
931+
932+
for name in tag_names.iter().flatten() {
933+
if let Ok(obj) = repo.revparse_single(name) {
934+
let mut message: Option<String> = None;
935+
let mut tagger: Option<String> = None;
936+
let mut date: Option<i64> = None;
937+
938+
// Check if it's an annotated tag
939+
if let Some(tag) = obj.as_tag() {
940+
message = tag.message().map(|s| s.to_string());
941+
if let Some(sig) = tag.tagger() {
942+
tagger = sig.name().map(|s| s.to_string());
943+
date = Some(sig.when().seconds());
944+
}
945+
} else if let Ok(_commit) = obj.peel_to_commit() {
946+
// Lightweight tag points directly to commit
947+
}
948+
949+
// Target commit SHA
950+
let target = obj.peel_to_commit()?.id().to_string();
951+
952+
tags.push(TagInfo {
953+
name: name.to_string(),
954+
message,
955+
target,
956+
tagger,
957+
date,
958+
});
959+
}
960+
}
961+
962+
// Sort tags by date (descending) or name
963+
tags.sort_by(|a, b| {
964+
// Prefer date if available
965+
match (a.date, b.date) {
966+
(Some(da), Some(db)) => db.cmp(&da), // Descending
967+
_ => b.name.cmp(&a.name), // Fallback to name
968+
}
969+
});
970+
971+
Ok(tags)
972+
}
973+
974+
/// Create a new tag
975+
#[tauri::command]
976+
pub async fn create_tag(
977+
path: String,
978+
name: String,
979+
message: Option<String>,
980+
target: Option<String>,
981+
) -> std::result::Result<(), String> {
982+
let repo = Repository::open(&path).map_err(|e| e.to_string())?;
983+
create_tag_impl(&repo, &name, message, target).map_err(|e| e.to_string())
984+
}
985+
986+
fn create_tag_impl(repo: &Repository, name: &str, message: Option<String>, target: Option<String>) -> Result<()> {
987+
// Get the target object
988+
let obj = if let Some(oid_str) = target {
989+
repo.find_object(git2::Oid::from_str(&oid_str)?, None)?
990+
} else {
991+
repo.head()?.peel(git2::ObjectType::Any)?
992+
};
993+
994+
if let Some(msg) = message {
995+
// Annotated tag
996+
let signature = repo.signature()?;
997+
repo.tag(name, &obj, &signature, &msg, false)?;
998+
} else {
999+
// Lightweight tag
1000+
repo.tag_lightweight(name, &obj, false)?;
1001+
}
1002+
1003+
Ok(())
1004+
}
1005+
1006+
/// Delete a tag
1007+
#[tauri::command]
1008+
pub async fn delete_tag(path: String, name: String) -> std::result::Result<(), String> {
1009+
let repo = Repository::open(&path).map_err(|e| e.to_string())?;
1010+
repo.tag_delete(&name).map_err(|e| e.to_string())?;
1011+
Ok(())
1012+
}
1013+
1014+
/// Push a tag to remote
1015+
#[tauri::command]
1016+
pub async fn push_tag(
1017+
path: String,
1018+
tag_name: String,
1019+
remote: String,
1020+
username: Option<String>,
1021+
password: Option<String>,
1022+
) -> std::result::Result<(), String> {
1023+
let repo = Repository::open(&path).map_err(|e| e.to_string())?;
1024+
push_tag_impl(&repo, &tag_name, &remote, username, password).map_err(|e| e.to_string())
1025+
}
1026+
1027+
fn push_tag_impl(
1028+
repo: &Repository,
1029+
tag_name: &str,
1030+
remote: &str,
1031+
username: Option<String>,
1032+
password: Option<String>,
1033+
) -> Result<()> {
1034+
let mut remote_obj = repo.find_remote(remote)
1035+
.map_err(|_| AppError::InvalidInput(format!("Remote '{}' not found", remote)))?;
1036+
1037+
// Refspec for pushing a tag
1038+
let refspec = format!("refs/tags/{}:refs/tags/{}", tag_name, tag_name);
1039+
1040+
let config = repo.config()?;
1041+
let auth_username = username.clone();
1042+
let auth_password = password.clone();
1043+
1044+
let mut callbacks = git2::RemoteCallbacks::new();
1045+
callbacks.credentials(move |url, username_from_url, allowed_types| {
1046+
let default_username = username_from_url.unwrap_or("git");
1047+
if let (Some(user), Some(pass)) = (&auth_username, &auth_password) {
1048+
return git2::Cred::userpass_plaintext(user, pass);
1049+
}
1050+
if allowed_types.contains(git2::CredentialType::SSH_KEY) {
1051+
git2::Cred::ssh_key_from_agent(default_username)
1052+
} else if allowed_types.contains(git2::CredentialType::USER_PASS_PLAINTEXT) {
1053+
git2::Cred::credential_helper(&config, url, Some(default_username))
1054+
} else if allowed_types.contains(git2::CredentialType::DEFAULT) {
1055+
git2::Cred::credential_helper(&config, url, Some(default_username))
1056+
} else {
1057+
Err(git2::Error::from_str("no authentication method available"))
1058+
}
1059+
});
1060+
1061+
let mut push_options = git2::PushOptions::new();
1062+
push_options.remote_callbacks(callbacks);
1063+
1064+
remote_obj.push(&[&refspec], Some(&mut push_options))?;
1065+
1066+
Ok(())
1067+
}
1068+
1069+
/// Delete a remote tag
1070+
#[tauri::command]
1071+
pub async fn delete_remote_tag(
1072+
path: String,
1073+
tag_name: String,
1074+
remote: String,
1075+
username: Option<String>,
1076+
password: Option<String>,
1077+
) -> std::result::Result<(), String> {
1078+
let repo = Repository::open(&path).map_err(|e| e.to_string())?;
1079+
delete_remote_tag_impl(&repo, &tag_name, &remote, username, password).map_err(|e| e.to_string())
1080+
}
1081+
1082+
fn delete_remote_tag_impl(
1083+
repo: &Repository,
1084+
tag_name: &str,
1085+
remote: &str,
1086+
username: Option<String>,
1087+
password: Option<String>,
1088+
) -> Result<()> {
1089+
let mut remote_obj = repo.find_remote(remote)
1090+
.map_err(|_| AppError::InvalidInput(format!("Remote '{}' not found", remote)))?;
1091+
1092+
// Refspec for deleting a remote ref
1093+
let refspec = format!(":refs/tags/{}", tag_name);
1094+
1095+
let config = repo.config()?;
1096+
let auth_username = username.clone();
1097+
let auth_password = password.clone();
1098+
1099+
let mut callbacks = git2::RemoteCallbacks::new();
1100+
callbacks.credentials(move |url, username_from_url, allowed_types| {
1101+
let default_username = username_from_url.unwrap_or("git");
1102+
if let (Some(user), Some(pass)) = (&auth_username, &auth_password) {
1103+
return git2::Cred::userpass_plaintext(user, pass);
1104+
}
1105+
if allowed_types.contains(git2::CredentialType::SSH_KEY) {
1106+
git2::Cred::ssh_key_from_agent(default_username)
1107+
} else if allowed_types.contains(git2::CredentialType::USER_PASS_PLAINTEXT) {
1108+
git2::Cred::credential_helper(&config, url, Some(default_username))
1109+
} else if allowed_types.contains(git2::CredentialType::DEFAULT) {
1110+
git2::Cred::credential_helper(&config, url, Some(default_username))
1111+
} else {
1112+
Err(git2::Error::from_str("no authentication method available"))
1113+
}
1114+
});
1115+
1116+
let mut push_options = git2::PushOptions::new();
1117+
push_options.remote_callbacks(callbacks);
1118+
1119+
remote_obj.push(&[&refspec], Some(&mut push_options))?;
1120+
1121+
Ok(())
1122+
}

src-tauri/src/domain/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pub mod repository;
22
pub mod status;
33

4-
pub use repository::{BranchInfo, BatchCommitResult, BatchFailure, CommitInfo, LocalBranch, RepositoryInfo, StashInfo};
4+
pub use repository::{BranchInfo, BatchCommitResult, BatchFailure, CommitInfo, LocalBranch, RepositoryInfo, StashInfo, TagInfo};
55
pub use status::{CommitSuggestion, CommitType, RepoStatus, StatusItem};

src-tauri/src/domain/repository.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,13 @@ pub struct StashInfo {
6666
pub message: String,
6767
pub id: String,
6868
}
69+
70+
#[derive(Debug, Clone, Serialize, Deserialize)]
71+
#[serde(rename_all = "camelCase")]
72+
pub struct TagInfo {
73+
pub name: String,
74+
pub message: Option<String>,
75+
pub target: String,
76+
pub tagger: Option<String>,
77+
pub date: Option<i64>,
78+
}

src-tauri/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ pub fn run() {
4646
stash_drop,
4747
// Clone command
4848
clone_repository,
49+
// Tag commands
50+
get_tags,
51+
create_tag,
52+
delete_tag,
53+
push_tag,
54+
delete_remote_tag,
4955
])
5056
.run(tauri::generate_context!())
5157
.expect("error while running tauri application");

0 commit comments

Comments
 (0)