consolidate git operations into a Repo type

This commit is contained in:
2026-10-03 12:46:44 +07:00
parent 4656f1340b
commit 49d351fe06
21 changed files with 2243 additions and 2203 deletions
+27 -13
View File
@@ -12,6 +12,7 @@ use nostr_connect::prelude::*;
use nostr_sdk::client::SyncSummary;
use nostr_sdk::prelude::*;
use signed_core::{Announcement, Filters, RepoAddr, RepoState, filters};
use signed_git::{GitCache, Repo};
use signed_nostr::{SignedAuthUrlHandler, UniversalSigner, Update};
use crate::git_store::repo_mirror_path;
@@ -408,7 +409,7 @@ impl Backend {
// Initialize directly at the user's chosen destination.
let destination = {
let dir_name = signed_git::sanitize_path_component(&name);
let dir_name = GitCache::sanitize_path_component(&name);
let dir_name = if dir_name.is_empty() {
"repository".to_owned()
} else {
@@ -431,11 +432,11 @@ impl Backend {
bail!("destination {} already exists", destination.display());
}
let commit = signed_git::init_repository(&destination, &name, &description)?;
let commit = Repo::init(&destination, &name, &description)?;
if let Some(base) = servers.first().and_then(grasp_base_url) {
let url = format!("{base}/{owner}/{repo_id}.git");
signed_git::set_origin(&destination, &url)?;
Repo::open(&destination)?.set_origin(&url)?;
}
Ok::<_, Error>(commit)
@@ -500,7 +501,9 @@ impl Backend {
&owner,
&servers,
&executor,
signed_git::push_main,
|path, base, owner, repo_id| {
Repo::open(path)?.push_main(base, owner, repo_id)
},
)
.await
}
@@ -585,8 +588,9 @@ impl Backend {
let work = cx.background_spawn({
let path = path.clone();
async move {
let state = signed_git::worktree_ref_state(&path)?;
let euc = signed_git::root_commit(&path)?;
let repo = Repo::open(&path)?;
let state = repo.ref_state()?;
let euc = repo.root_commit()?;
Ok::<_, Error>((state, euc))
}
});
@@ -651,7 +655,9 @@ impl Backend {
&owner,
&servers,
&executor,
signed_git::push_all,
|path, base, owner, repo_id| {
Repo::open(path)?.push_all(base, owner, repo_id)
},
)
.await
}
@@ -687,7 +693,9 @@ impl Backend {
let url = format!("{base}/{owner}/{repo_id}.git");
let path = path.clone();
cx.background_spawn(async move {
signed_git::ensure_origin(&path, &url).ok();
Repo::open(&path)
.and_then(|repo| repo.ensure_origin(&url))
.ok();
})
.await;
}
@@ -699,7 +707,9 @@ impl Backend {
Ok(naddr) => {
let path = path.clone();
cx.background_spawn(async move {
if let Err(error) = signed_git::set_nostr_repo(&path, &naddr) {
if let Err(error) =
Repo::open(&path).and_then(|repo| repo.set_nostr_repo(&naddr))
{
log::warn!(
"failed to record the NIP-34 marker for {}: {error}",
path.display()
@@ -783,7 +793,7 @@ impl Backend {
let mut state = {
let work = cx.background_spawn({
let path = path.clone();
async move { signed_git::worktree_ref_state(&path) }
async move { Repo::open(&path).and_then(|repo| repo.ref_state()) }
});
work.await?
};
@@ -837,7 +847,9 @@ impl Backend {
&owner,
&relays,
&executor,
signed_git::push_all,
|path, base, owner, repo_id| {
Repo::open(path)?.push_all(base, owner, repo_id)
},
)
.await
}
@@ -1571,7 +1583,7 @@ async fn push_staged_to_grasps(
owner: &str,
servers: &[RelayUrl],
executor: &BackgroundExecutor,
push: fn(&Path, &str, &str, &str) -> Result<(), Error>,
push: impl Fn(&Path, &str, &str, &str) -> Result<(), Error>,
) -> PushOutcome {
let mut outcome = PushOutcome::default();
@@ -1647,7 +1659,9 @@ async fn push_staged_to_grasps(
// pushed data is already there.
if let Some(last_reason) = &reason
&& is_stale_advertisement_race(last_reason)
&& signed_git::remote_has_refs(path, &git_url, refs).unwrap_or(false)
&& Repo::open(path)
.and_then(|repo| repo.remote_has_refs(&git_url, refs))
.unwrap_or(false)
{
if let Some(event) = staged_event {
keep_newest(&mut outcome.state_event, event);
+27 -17
View File
@@ -7,6 +7,7 @@ use gpui::{App, AppContext, Context, Entity, Global, Subscription};
use nostr::prelude::*;
use settings::{CheckoutRecord, SettingsStore};
use signed_core::{Announcement, RepoAddr};
use signed_git::Repo;
use utils::same_repo_url;
use crate::backend::{Backend, BackendEvent};
@@ -338,8 +339,14 @@ impl CheckoutsStore {
{
continue;
}
let origin = signed_git::origin_url(path).ok().flatten();
let root = signed_git::root_commit(path).ok().flatten();
let origin = Repo::open(path)
.and_then(|repo| repo.origin_url())
.ok()
.flatten();
let root = Repo::open(path)
.and_then(|repo| repo.root_commit())
.ok()
.flatten();
facts.push((path.clone(), origin, root));
}
@@ -555,14 +562,15 @@ fn resolve_associations<'a>(
}
fn checkout_status(path: &Path, announced_head: Option<&str>) -> Option<CheckoutStatus> {
let branches = signed_git::worktree_branches(path).ok()?;
let repo = Repo::try_open(path)?;
let branches = repo.branches().ok()?;
if branches.is_empty() || signed_git::worktree_dirty(path) {
if branches.is_empty() || repo.is_dirty() {
return None;
}
let branch = signed_git::worktree_current_branch(path)?;
let head = signed_git::head_commit_id(path).ok().flatten()?;
let branch = repo.current_branch()?;
let head = repo.head()?;
let base = announced_head
.filter(|name| branches.iter().any(|b| b == name))
.map(str::to_owned)
@@ -573,7 +581,7 @@ fn checkout_status(path: &Path, announced_head: Option<&str>) -> Option<Checkout
return None;
}
let ahead = signed_git::worktree_commits_ahead(path, &base, &branch);
let ahead = repo.commits_ahead(&base, &branch);
(ahead > 0).then_some(CheckoutStatus {
path: path.to_path_buf(),
branch,
@@ -585,31 +593,33 @@ fn checkout_status(path: &Path, announced_head: Option<&str>) -> Option<Checkout
/// The `ready to push` status of one checkout of the user's own repository.
fn checkout_push_status(path: &Path, fetch: bool) -> Option<CheckoutStatus> {
if signed_git::worktree_dirty(path) {
let repo = Repo::try_open(path)?;
if repo.is_dirty() {
return None;
}
let branch = signed_git::worktree_current_branch(path)?;
let head = signed_git::head_commit_id(path).ok().flatten()?;
let origin = signed_git::origin_url(path).ok().flatten()?;
let branch = repo.current_branch()?;
let head = repo.head()?;
let origin = repo.origin_url().ok().flatten()?;
if fetch {
signed_git::fetch_repo_refs(path, &[origin], "+refs/heads/*:refs/remotes/origin/*").ok();
repo.fetch_refs(&[origin], "+refs/heads/*:refs/remotes/origin/*")
.ok();
}
let remote = format!("refs/remotes/origin/{branch}");
// A branch never fetched or pushed yet compares against the remote HEAD.
// The remote HEAD is the fork point in practice.
let base = if signed_git::worktree_ref_exists(path, &remote) {
let base = if repo.ref_exists(&remote) {
remote
} else if signed_git::worktree_ref_exists(path, "refs/remotes/origin/HEAD") {
} else if repo.ref_exists("refs/remotes/origin/HEAD") {
"refs/remotes/origin/HEAD".to_owned()
} else {
return None;
};
let ahead = signed_git::worktree_commits_ahead(path, &base, &branch);
let ahead = repo.commits_ahead(&base, &branch);
(ahead > 0).then_some(CheckoutStatus {
path: path.to_path_buf(),
@@ -730,7 +740,7 @@ mod tests {
fn checkout_status_reports_ahead_branches_only() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("repo");
let _initial = signed_git::init_repository(&path, "My Repo", "").expect("init");
let _initial = Repo::init(&path, "My Repo", "").expect("init");
let run = |args: &[&str]| {
let status = Command::new("git")
.current_dir(&path)
@@ -777,7 +787,7 @@ mod tests {
// Act like a grasp server and allow them.
let dir = tempfile::tempdir().expect("tempdir");
let remote = dir.path().join("remote");
signed_git::init_repository(&remote, "My Repo", "").expect("init");
Repo::init(&remote, "My Repo", "").expect("init");
let config = Command::new("git")
.args(["config", "receive.denyCurrentBranch", "ignore"])
.current_dir(&remote)
+3 -4
View File
@@ -2,10 +2,9 @@ use std::path::PathBuf;
use std::sync::OnceLock;
use anyhow::Result;
use gix::Repository;
use nostr::prelude::Url;
use signed_core::RepoAddr;
use signed_git::GitCache;
use signed_git::{GitCache, Repo};
static GIT_CACHE: OnceLock<GitCache> = OnceLock::new();
@@ -26,12 +25,12 @@ pub fn repo_mirror_path(addr: &RepoAddr) -> PathBuf {
}
/// Open the mirror of `addr`, if it has been cloned.
pub fn open_repo_mirror(addr: &RepoAddr) -> Result<Option<Repository>> {
pub fn open_repo_mirror(addr: &RepoAddr) -> Result<Option<Repo>> {
git_cache().open(addr)
}
/// Open the mirror of `addr`, cloning it first when it does not exist yet.
pub fn ensure_repo_mirror(addr: &RepoAddr, clone_urls: &[Url]) -> Result<Repository> {
pub fn ensure_repo_mirror(addr: &RepoAddr, clone_urls: &[Url]) -> Result<Repo> {
git_cache().ensure_clone(addr, clone_urls)
}
+11 -10
View File
@@ -12,7 +12,7 @@ use signed_core::{
Announcement, Deletions, Filters, GitEvent, PullRequest, RepoAddr, RepoState, RepoStatus,
filters,
};
use signed_git::Nip34Binding;
use signed_git::{Nip34Binding, PatchParser, Repo};
use signed_nostr::UniversalSigner;
use crate::backend::{
@@ -700,7 +700,7 @@ impl RepoStore {
return;
};
let series: Vec<String> = signed_git::split_patch_series(&patch)
let series: Vec<String> = PatchParser::split_patch_series(&patch)
.into_iter()
.map(str::to_owned)
.collect();
@@ -906,9 +906,9 @@ impl RepoStore {
let mut pushed = 0;
for (url, label) in &targets {
match signed_git::push_commit_ref(
&path, url, &tip, &reference,
) {
match Repo::open(&path)
.and_then(|repo| repo.push_ref(url, &tip, &reference))
{
Ok(()) => pushed += 1,
Err(e) => failures.push(format!("{label}: {e}")),
}
@@ -986,7 +986,7 @@ impl RepoStore {
let merge_base = merge_base.clone();
let compare_ref = compare_ref.clone();
async move {
signed_git::format_patch_between(&repo_path, &merge_base, &compare_ref)
Repo::open(&repo_path)?.format_patch_between(&merge_base, &compare_ref)
}
})
.await;
@@ -1037,7 +1037,7 @@ impl RepoStore {
return;
}
let series: Vec<String> = signed_git::split_patch_series(&patch)
let series: Vec<String> = PatchParser::split_patch_series(&patch)
.into_iter()
.map(str::to_owned)
.collect();
@@ -1368,7 +1368,9 @@ impl RepoStore {
let clone = {
let destination = destination.clone();
cx.background_spawn(async move { signed_git::clone_repo(&clone_urls, &destination) })
// The clone's repository handle is dropped in the task: gix handles
// are not `Send`, they must not cross the spawn boundary.
cx.background_spawn(async move { Repo::clone(&clone_urls, &destination).map(|_| ()) })
};
cx.spawn(async move |this, cx| {
@@ -1596,11 +1598,10 @@ fn comment_builder(
#[cfg(test)]
mod tests {
use signed_core::GitEvent;
use std::collections::HashSet;
use nostr_sdk::prelude::*;
use signed_core::GitEvent;
use super::{RepoStore, comment_builder, patch_current_commit};