Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 28 additions & 9 deletions apps/desktop/src-tauri/src/core/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
fn local_tag_names(repo: &Repository) -> AppResult<Vec<String>> {
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<String> {
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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -916,29 +935,29 @@ 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(),
]
);
}

#[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(),
]
);
}

#[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()]
);
}
Expand Down
21 changes: 21 additions & 0 deletions apps/desktop/src-tauri/tests/git_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}",
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/features/inspector/Inspector.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -129,7 +129,7 @@ export function Inspector() {
className={cn(fileView === 'all' && 'bg-surface-raised text-foreground')}
onClick={() => setFileView('all')}
>
<Files className="size-3.5" />
<FolderOpen className="size-3.5" />
</Button>
</Hint>
{(commit || commitError) && (
Expand Down
Loading