feat(git): add commit-note support and previews in history

This change adds Git notes support to the history UI.
Commits now carry a has_note flag which triggers a note indicator.
Notes can be previewed on hover and loaded on demand,
then the history can be refreshed after edits.

- Adds has_note support on commits and parses from logs.
- Renders a note indicator in history rows with a hover preview.
- Triggers history refresh after note-related actions.
This commit is contained in:
Christoph Brandau
2026-08-13 22:51:24 +02:00
parent c442b3735f
commit a27a8666ee
Notes: Christoph Brandau 2026-08-13 22:56:29 +02:00
one more test
5 changed files with 314 additions and 13 deletions
+55 -3
View File
@@ -119,6 +119,7 @@ pub struct GitCommit {
pub refs: Vec<String>,
pub parents: Vec<String>,
pub files: Vec<GitCommitFile>,
pub has_note: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
@@ -3503,7 +3504,10 @@ fn commit_page_for_repo(
repo,
[
"log",
"--all",
"--branches",
"--remotes",
"--tags",
"HEAD",
"--topo-order",
"--decorate=short",
"--name-status",
@@ -3518,7 +3522,28 @@ fn commit_page_for_repo(
],
)?;
parse_commit_log_inline(&output)
let mut commits = parse_commit_log_inline(&output)?;
mark_commits_with_notes(repo, &mut commits)?;
Ok(commits)
}
fn mark_commits_with_notes(repo: &Path, commits: &mut [GitCommit]) -> Result<(), String> {
if commits.is_empty() || !ref_exists(repo, COMMIT_NOTES_REF)? {
return Ok(());
}
let output = run_git(repo, ["notes", "--ref", COMMIT_NOTES_REF, "list"])?;
let noted_commits: BTreeSet<String> = String::from_utf8_lossy(&output)
.lines()
.filter_map(|line| line.split_whitespace().nth(1))
.map(ToString::to_string)
.collect();
for commit in commits {
commit.has_note = noted_commits.contains(&commit.hash);
}
Ok(())
}
#[tauri::command]
@@ -3704,7 +3729,9 @@ fn list_file_history_core(
let output = run_git_cancellable(repo, args, cancellation, "Git file history failed")?;
check_search_cancelled(cancellation)?;
parse_commit_log(repo, &output)
let mut commits = parse_commit_log(repo, &output)?;
mark_commits_with_notes(repo, &mut commits)?;
Ok(commits)
}
#[tauri::command]
@@ -5139,6 +5166,7 @@ fn parse_commit_log_inline(output: &[u8]) -> Result<Vec<GitCommit>, String> {
parents,
summary: String::from_utf8_lossy(parts[7]).to_string(),
files: parse_commit_files(files_bytes)?,
has_note: false,
});
}
@@ -5187,6 +5215,7 @@ fn parse_commit_log(repo: &Path, output: &[u8]) -> Result<Vec<GitCommit>, String
parents,
summary: fields[7].to_string(),
files,
has_note: false,
});
}
@@ -5233,6 +5262,7 @@ fn parse_commit_log_metadata(output: &[u8]) -> Result<Vec<GitCommit>, String> {
parents,
summary: fields[7].to_string(),
files: Vec::new(),
has_note: false,
});
}
@@ -6369,6 +6399,19 @@ mod tests {
"Review: sieht gut aus\nBuild: 42",
)
.expect("note should be created");
let commits = commits_for_repo(&repo.path, Some(20)).expect("history should load");
assert!(
commits
.iter()
.find(|commit| commit.hash == commit_before)
.expect("annotated commit should be in history")
.has_note
);
assert!(
commits
.iter()
.all(|commit| commit.summary != "Notes added by 'git notes add'")
);
assert_eq!(
commit_note_for_repo(&repo.path, &commit_before).expect("note should load"),
Some("Review: sieht gut aus\nBuild: 42".to_string())
@@ -6382,6 +6425,14 @@ mod tests {
);
delete_commit_note_for_repo(&repo.path, &commit_before).expect("note should be deleted");
let commits = commits_for_repo(&repo.path, Some(20)).expect("history should reload");
assert!(
!commits
.iter()
.find(|commit| commit.hash == commit_before)
.expect("commit should remain in history")
.has_note
);
assert_eq!(
commit_note_for_repo(&repo.path, &commit_before)
.expect("deleted note lookup should work"),
@@ -6782,6 +6833,7 @@ mod tests {
],
summary: "Add history panel".to_string(),
files: Vec::new(),
has_note: false,
}]
);
}