From 8da9961a957460d8baeb80c90ac7f140b6d87fa2 Mon Sep 17 00:00:00 2001 From: cheat2001 Date: Tue, 22 Sep 2026 21:30:58 +0700 Subject: [PATCH 1/2] fix(inspector): use an open folder icon for the All files view --- CLAUDE.md | 3 ++- apps/desktop/src/features/inspector/Inspector.tsx | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3cf39d5..e5f1132 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1399,7 +1399,8 @@ features/ │ like GitKraken"): ui.fileView 'list' | 'tree' | 'all' │ (persisted; the persist merge maps the old boolean fileTree │ → 'tree'), three aria-pressed header buttons List/FolderTree/ -│ Files. 'all' renders EVERY file — commit: ipc.treeFiles +│ FolderOpen (lucide `Files` was tried first and read as a +│ copy icon, owner 2026-09-22). 'all' renders EVERY file — commit: ipc.treeFiles │ (engine files.rs) unioned with the change list via core │ allFiles (deleted files stay, keyed by path); working copy: │ ipc.indexFiles ∪ status paths, reloaded on statusVersion diff --git a/apps/desktop/src/features/inspector/Inspector.tsx b/apps/desktop/src/features/inspector/Inspector.tsx index ddfee49..9e23e89 100644 --- a/apps/desktop/src/features/inspector/Inspector.tsx +++ b/apps/desktop/src/features/inspector/Inspector.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { toast } from 'sonner'; import type { CommitFileInfo, CommitInfo } from '@angkorgit/core'; -import { Files, FolderTree, List, Search, X } from 'lucide-react'; +import { FolderOpen, FolderTree, List, Search, X } from 'lucide-react'; import { Hint, Button, cn } from '@angkorgit/design-system'; import { useGraph } from '@/features/graph/store'; import { useRepo } from '@/features/repository/store'; @@ -129,7 +129,7 @@ export function Inspector() { className={cn(fileView === 'all' && 'bg-surface-raised text-foreground')} onClick={() => setFileView('all')} > - + {(commit || commitError) && ( From 96de163b19c8390b7a679011ab60e6207985fd0f Mon Sep 17 00:00:00 2001 From: noyobo Date: Sun, 20 Sep 2026 22:20:01 +0800 Subject: [PATCH 2/2] fix(remote): send named tag refspecs instead of refs/tags/* libgit2 Remote::push looks up the glob as a literal ref. Enumerate local tags and push refs/tags/{name}:refs/tags/{name} so Push with tags works. --- CHANGELOG.md | 2 ++ apps/desktop/src-tauri/src/core/remote.rs | 37 ++++++++++++++++------ apps/desktop/src-tauri/tests/git_engine.rs | 21 ++++++++++++ 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df0b561..c644967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ All notable changes to AngKorGit are documented here. The format follows to fall apart once the view scrolled: copy returned one line and the highlight moved when you scrolled back. The selection now grows to the last visible line while you hold the mouse below the diff, and copy returns every selected line. +- **Push with tags failed** with `not a valid reference 'refs/tags/*'`. libgit2 does not + expand the glob git uses, so the engine now names each local tag in its own refspec. (#34) ## [0.16.0] — 2026-09-20 diff --git a/apps/desktop/src-tauri/src/core/remote.rs b/apps/desktop/src-tauri/src/core/remote.rs index a8a277c..97fd1ab 100644 --- a/apps/desktop/src-tauri/src/core/remote.rs +++ b/apps/desktop/src-tauri/src/core/remote.rs @@ -511,11 +511,25 @@ fn pull_rebase_configured(repo: &Repository) -> bool { .unwrap_or(false) } -pub(crate) fn push_refspecs(branch: &str, force: bool, with_tags: bool) -> Vec { +fn local_tag_names(repo: &Repository) -> AppResult> { + let mut names = Vec::new(); + repo.tag_foreach(|_oid, name_bytes| { + let full = String::from_utf8_lossy(name_bytes); + if let Some(name) = full.strip_prefix("refs/tags/") { + if !name.is_empty() && !name.contains(':') && !name.contains('\0') { + names.push(name.to_string()); + } + } + true + })?; + Ok(names) +} + +pub(crate) fn push_refspecs(branch: &str, force: bool, tags: &[String]) -> Vec { let prefix = if force { "+" } else { "" }; let mut refspecs = vec![format!("{prefix}refs/heads/{branch}:refs/heads/{branch}")]; - if with_tags { - refspecs.push("refs/tags/*:refs/tags/*".to_string()); + for tag in tags { + refspecs.push(format!("refs/tags/{tag}:refs/tags/{tag}")); } refspecs } @@ -538,7 +552,12 @@ pub fn push( .to_string(), }; - let refspecs = push_refspecs(&branch_name, force, with_tags); + let tags = if with_tags { + local_tag_names(&repo)? + } else { + Vec::new() + }; + let refspecs = push_refspecs(&branch_name, force, &tags); let track_upstream = |repo: &Repository| -> AppResult<()> { if set_upstream { @@ -916,10 +935,10 @@ mod tests { #[test] fn plain_push_refspecs_have_no_force_prefix() { assert_eq!( - push_refspecs("main", false, true), + push_refspecs("main", false, &["v1".into()]), vec![ "refs/heads/main:refs/heads/main".to_string(), - "refs/tags/*:refs/tags/*".to_string(), + "refs/tags/v1:refs/tags/v1".to_string(), ] ); } @@ -927,10 +946,10 @@ mod tests { #[test] fn force_push_forces_only_the_branch_refspec() { assert_eq!( - push_refspecs("main", true, true), + push_refspecs("main", true, &["v1".into()]), vec![ "+refs/heads/main:refs/heads/main".to_string(), - "refs/tags/*:refs/tags/*".to_string(), + "refs/tags/v1:refs/tags/v1".to_string(), ] ); } @@ -938,7 +957,7 @@ mod tests { #[test] fn push_without_tags_sends_only_the_branch() { assert_eq!( - push_refspecs("feature/x", true, false), + push_refspecs("feature/x", true, &[]), vec!["+refs/heads/feature/x:refs/heads/feature/x".to_string()] ); } diff --git a/apps/desktop/src-tauri/tests/git_engine.rs b/apps/desktop/src-tauri/tests/git_engine.rs index 4903edc..1f1f56e 100644 --- a/apps/desktop/src-tauri/tests/git_engine.rs +++ b/apps/desktop/src-tauri/tests/git_engine.rs @@ -2324,6 +2324,27 @@ fn push_reports_up_to_date_instead_of_pushing_again() { let _ = std::fs::remove_dir_all(&origin); } +#[test] +fn push_with_tags_sends_named_tag_refs() { + let local = TempRepo::new(); + local.write("a.txt", "one\n"); + commit_all(&local, "one"); + let origin = bare_origin(&local); + + core::tag_create(local.path(), "v1.0.0", None, None).unwrap(); + let outcome = core::push(local.path(), "origin", None, false, true, true).unwrap(); + assert_eq!(outcome.status, "ok"); + + let listed = Command::new("git") + .args(["tag", "-l", "v1.0.0"]) + .current_dir(&origin) + .output() + .unwrap(); + assert_eq!(String::from_utf8_lossy(&listed.stdout).trim(), "v1.0.0"); + + let _ = std::fs::remove_dir_all(&origin); +} + fn clone_of(origin: &std::path::Path, local: &TempRepo, suffix: &str) -> TempRepo { let dir = local.dir.with_file_name(format!( "{}-{suffix}",