feat(git): add upstream tracking and local-only branch UI

This release adds upstream tracking for branches and a local-only
indicator in the UI.
The Git integration now exposes upstreams and supports publish flows.
This enables publishing and remote-tracking configuration.

- Introduces upstream tracking and local-only branch UI markers
- Updates toolbar to reflect publish-local state and status indicators
- Bumps version to 2026.8.3 and updates changelog
This commit is contained in:
Christoph Brandau
2026-08-13 19:32:26 +02:00
parent f43fe00873
commit b6b9964a93
11 changed files with 420 additions and 45 deletions
+61 -4
View File
@@ -66,6 +66,7 @@ pub struct GitBranch {
pub name: String,
pub current: bool,
pub remote: bool,
pub upstream: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
@@ -842,7 +843,7 @@ fn branches_for_repo(repo: &Path) -> Result<Vec<GitBranch>, String> {
repo,
[
"for-each-ref",
"--format=%(refname)\t%(HEAD)",
"--format=%(refname)\t%(HEAD)\t%(upstream:short)",
"refs/heads",
"refs/remotes",
],
@@ -851,9 +852,10 @@ fn branches_for_repo(repo: &Path) -> Result<Vec<GitBranch>, String> {
let mut branches = Vec::new();
for line in text.lines() {
let Some((ref_name, head_marker)) = line.split_once('\t') else {
continue;
};
let mut parts = line.splitn(3, '\t');
let ref_name = parts.next().unwrap_or_default();
let head_marker = parts.next().unwrap_or_default();
let configured_upstream = parts.next().unwrap_or_default().trim();
let (name, remote) = if let Some(name) = ref_name.strip_prefix("refs/heads/") {
(name, false)
@@ -870,6 +872,11 @@ fn branches_for_repo(repo: &Path) -> Result<Vec<GitBranch>, String> {
name: name.to_string(),
current: head_marker.trim() == "*",
remote,
upstream: if remote || configured_upstream.is_empty() {
None
} else {
Some(configured_upstream.to_string())
},
});
}
@@ -6240,6 +6247,56 @@ mod tests {
run_git_test(repo, ["commit", "-q", "-m", "init"]);
}
#[test]
fn branches_report_configured_upstream_and_local_only_state() {
let repo = init_temp_repo("branch_upstream_state");
commit_initial_file(&repo.path);
run_git_test(&repo.path, ["branch", "feature/local-only"]);
run_git_test(&repo.path, ["branch", "feature/tracked"]);
run_git_test(&repo.path, ["remote", "add", "origin", "."]);
run_git_test(
&repo.path,
[
"update-ref",
"refs/remotes/origin/feature/published",
"HEAD",
],
);
run_git_test(
&repo.path,
["config", "branch.feature/tracked.remote", "origin"],
);
run_git_test(
&repo.path,
[
"config",
"branch.feature/tracked.merge",
"refs/heads/feature/published",
],
);
let branches = branches_for_repo(&repo.path).expect("branches should load");
let local_only = branches
.iter()
.find(|branch| branch.name == "feature/local-only")
.expect("local-only branch should exist");
let tracked = branches
.iter()
.find(|branch| branch.name == "feature/tracked")
.expect("tracked branch should exist");
let remote = branches
.iter()
.find(|branch| branch.name == "origin/feature/published")
.expect("remote branch should exist");
assert_eq!(local_only.upstream, None);
assert_eq!(
tracked.upstream.as_deref(),
Some("origin/feature/published")
);
assert_eq!(remote.upstream, None);
}
#[test]
fn commit_notes_can_be_created_updated_and_deleted_without_changing_commit() {
let repo = init_temp_repo("commit_notes_crud");
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Gitty",
"version": "2026.8.2",
"version": "2026.8.3",
"identifier": "com.gitty",
"build": {
"beforeDevCommand": "npm run dev",