chore: remove unnecessary optimization #20
@@ -39,12 +39,7 @@ impl Assets {
|
|||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
let data = Self::get(path.as_ref())?;
|
let data = Self::get(path.as_ref())?;
|
||||||
let name = path.strip_prefix("themes/").unwrap_or(path.as_ref());
|
let name = path.strip_prefix("themes/").unwrap_or(path.as_ref());
|
||||||
let content = match data.data {
|
let content = std::str::from_utf8(data.data.as_ref()).ok()?.to_owned();
|
||||||
std::borrow::Cow::Borrowed(bytes) => {
|
|
||||||
std::str::from_utf8(bytes).ok()?.to_owned()
|
|
||||||
}
|
|
||||||
std::borrow::Cow::Owned(bytes) => String::from_utf8(bytes).ok()?,
|
|
||||||
};
|
|
||||||
Some((name.to_owned(), content))
|
Some((name.to_owned(), content))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ pub struct InboxItem {
|
|||||||
pub root: EventId,
|
pub root: EventId,
|
||||||
/// The root event itself, when it is known locally.
|
/// The root event itself, when it is known locally.
|
||||||
pub root_event: Option<Event>,
|
pub root_event: Option<Event>,
|
||||||
/// Kind of the root event, when it is known locally.
|
|
||||||
pub root_kind: Option<Kind>,
|
|
||||||
/// Repository the root belongs to, from the root's `a` tag.
|
/// Repository the root belongs to, from the root's `a` tag.
|
||||||
pub address: Option<RepoAddr>,
|
pub address: Option<RepoAddr>,
|
||||||
/// Notification events directed at the user, newest first.
|
/// Notification events directed at the user, newest first.
|
||||||
@@ -45,13 +43,11 @@ impl InboxItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> Option<Kind> {
|
pub fn kind(&self) -> Option<Kind> {
|
||||||
self.root_kind.or_else(|| {
|
self.root_event
|
||||||
self.root_event
|
.as_ref()
|
||||||
.as_ref()
|
.or_else(|| self.own_events.first())
|
||||||
.or_else(|| self.own_events.first())
|
.or_else(|| self.events.first())
|
||||||
.or_else(|| self.events.first())
|
.map(|event| event.kind)
|
||||||
.map(|event| event.kind)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Timestamp of the newest event in the thread.
|
/// Timestamp of the newest event in the thread.
|
||||||
@@ -202,7 +198,6 @@ where
|
|||||||
|
|
||||||
let mut item = InboxItem {
|
let mut item = InboxItem {
|
||||||
root,
|
root,
|
||||||
root_kind: root_event.as_ref().map(|event| event.kind),
|
|
||||||
address: root_event
|
address: root_event
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|event| event.tags.coordinates().next()),
|
.and_then(|event| event.tags.coordinates().next()),
|
||||||
|
|||||||
@@ -41,12 +41,10 @@ pub struct Upstream {
|
|||||||
/// Upstream repository coordinate when the `u` tag names a NIP-34 repository.
|
/// Upstream repository coordinate when the `u` tag names a NIP-34 repository.
|
||||||
/// `None` for the git-URL form.
|
/// `None` for the git-URL form.
|
||||||
pub addr: Option<RepoAddr>,
|
pub addr: Option<RepoAddr>,
|
||||||
/// Relay hint for the upstream, if the `u` tag carries one.
|
|
||||||
pub relay_hint: Option<RelayUrl>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Upstream {
|
impl Upstream {
|
||||||
fn parse(raw: &str, relay_hint: Option<&str>) -> Self {
|
fn parse(raw: &str) -> Self {
|
||||||
let coordinate = raw.split('|').next().unwrap_or(raw);
|
let coordinate = raw.split('|').next().unwrap_or(raw);
|
||||||
let addr = coordinate
|
let addr = coordinate
|
||||||
.parse::<Coordinate>()
|
.parse::<Coordinate>()
|
||||||
@@ -55,7 +53,6 @@ impl Upstream {
|
|||||||
Self {
|
Self {
|
||||||
raw: raw.to_owned(),
|
raw: raw.to_owned(),
|
||||||
addr,
|
addr,
|
||||||
relay_hint: relay_hint.and_then(|hint| RelayUrl::parse(hint).ok()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +314,7 @@ impl Announcement {
|
|||||||
let values = tag.as_slice();
|
let values = tag.as_slice();
|
||||||
let raw = values.get(1).map(String::as_str).unwrap_or_default();
|
let raw = values.get(1).map(String::as_str).unwrap_or_default();
|
||||||
if !raw.is_empty() {
|
if !raw.is_empty() {
|
||||||
upstream = Some(Upstream::parse(raw, values.get(2).map(String::as_str)));
|
upstream = Some(Upstream::parse(raw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,10 +508,6 @@ mod tests {
|
|||||||
upstream.raw,
|
upstream.raw,
|
||||||
"30617:68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272:upstream|https://example.com/upstream.git"
|
"30617:68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272:upstream|https://example.com/upstream.git"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
upstream.relay_hint,
|
|
||||||
Some(RelayUrl::parse("wss://relay.example.com").expect("valid relay"))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
upstream.display().to_string(),
|
upstream.display().to_string(),
|
||||||
"30617:68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272:upstream"
|
"30617:68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272:upstream"
|
||||||
|
|||||||
@@ -72,17 +72,6 @@ fn file_commit_with_description(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the most recent commit that changed `rel`, a path relative to the worktree.
|
|
||||||
///
|
|
||||||
/// `Ok(None)` when no commit touched the file, e.g. an untracked file.
|
|
||||||
pub fn last_commit(repo: &gix::Repository, rel: &Path) -> Result<Option<FileCommit>> {
|
|
||||||
let rel = rel.to_path_buf();
|
|
||||||
Ok(last_commits(repo, std::slice::from_ref(&rel))?
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.map(|(_, commit)| commit))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Newest commit touching each of `rels`, like `git log -1 -- <rel>` per path.
|
/// Newest commit touching each of `rels`, like `git log -1 -- <rel>` per path.
|
||||||
/// `rels` are paths relative to the worktree.
|
/// `rels` are paths relative to the worktree.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ pub use diff::{
|
|||||||
worktree_commit_range_diff,
|
worktree_commit_range_diff,
|
||||||
};
|
};
|
||||||
pub use history::{
|
pub use history::{
|
||||||
CommitList, FileCommit, MAX_LISTED_COMMITS, all_commits, head_commit, last_commit,
|
CommitList, FileCommit, MAX_LISTED_COMMITS, all_commits, head_commit, worktree_all_commits,
|
||||||
worktree_all_commits, worktree_commit, worktree_commit_range_commits, worktree_last_commits,
|
worktree_commit, worktree_commit_range_commits, worktree_last_commits,
|
||||||
};
|
};
|
||||||
pub use patch::{
|
pub use patch::{
|
||||||
apply_patch, format_patch_between, patch_commits, patch_diffs, split_patch_series,
|
apply_patch, format_patch_between, patch_commits, patch_diffs, split_patch_series,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use crate::history::open_with_cache;
|
use crate::history::open_with_cache;
|
||||||
use crate::worktree::{force_checkout, worktree_dirty};
|
use crate::worktree::{force_checkout, worktree_dirty};
|
||||||
@@ -171,12 +171,7 @@ pub fn init_repository(path: &Path, name: &str, description: &str) -> Result<Str
|
|||||||
let mut index = repo.index_from_tree(&tree)?;
|
let mut index = repo.index_from_tree(&tree)?;
|
||||||
index.write(gix::index::write::Options::default())?;
|
index.write(gix::index::write::Options::default())?;
|
||||||
|
|
||||||
let commit = commit.to_string();
|
Ok(commit.to_string())
|
||||||
if commit.len() != 40 {
|
|
||||||
bail!("unexpected initial commit id: {commit}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(commit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The earliest unique commit of the repository at `repo_path`.
|
/// The earliest unique commit of the repository at `repo_path`.
|
||||||
@@ -201,8 +196,7 @@ pub fn root_commit(repo_path: &Path) -> Result<Option<String>> {
|
|||||||
{
|
{
|
||||||
let info = info?;
|
let info = info?;
|
||||||
if info.parent_ids().next().is_none() {
|
if info.parent_ids().next().is_none() {
|
||||||
let id = info.id().to_string();
|
return Ok(Some(info.id().to_string()));
|
||||||
return Ok((id.len() == 40).then_some(id));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -578,7 +578,7 @@ fn git_run(dir: &Path, args: &[&str]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn last_commit_returns_most_recent_change() {
|
fn worktree_last_commits_returns_most_recent_change() {
|
||||||
let (dir, repo) = fixture(&[("a.txt", b"one")]);
|
let (dir, repo) = fixture(&[("a.txt", b"one")]);
|
||||||
commit_all(&repo, "initial");
|
commit_all(&repo, "initial");
|
||||||
|
|
||||||
@@ -589,9 +589,12 @@ fn last_commit_returns_most_recent_change() {
|
|||||||
std::fs::write(dir.path().join("b.txt"), b"other").expect("write");
|
std::fs::write(dir.path().join("b.txt"), b"other").expect("write");
|
||||||
commit_all(&repo, "add b");
|
commit_all(&repo, "add b");
|
||||||
|
|
||||||
let commit = last_commit(&repo, Path::new("a.txt"))
|
let commit = worktree_last_commits(dir.path(), &[PathBuf::from("a.txt")])
|
||||||
.expect("lookup")
|
.expect("lookup")
|
||||||
.expect("found");
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.expect("found")
|
||||||
|
.1;
|
||||||
assert_eq!(commit.summary, "change a");
|
assert_eq!(commit.summary, "change a");
|
||||||
assert_eq!(commit.author, "Test Author");
|
assert_eq!(commit.author, "Test Author");
|
||||||
assert!(!commit.id.is_empty());
|
assert!(!commit.id.is_empty());
|
||||||
@@ -621,7 +624,7 @@ fn all_commits_lists_every_commit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn last_commit_reports_merge_commits() {
|
fn worktree_last_commits_reports_merge_commits() {
|
||||||
let (dir, repo) = fixture(&[("a.txt", b"base")]);
|
let (dir, repo) = fixture(&[("a.txt", b"base")]);
|
||||||
commit_all(&repo, "initial");
|
commit_all(&repo, "initial");
|
||||||
|
|
||||||
@@ -645,9 +648,12 @@ fn last_commit_reports_merge_commits() {
|
|||||||
// `--no-ff` forces a merge commit, it is the latest commit changing a.txt.
|
// `--no-ff` forces a merge commit, it is the latest commit changing a.txt.
|
||||||
run(&["merge", "--no-ff", "--no-edit", "feature"]);
|
run(&["merge", "--no-ff", "--no-edit", "feature"]);
|
||||||
|
|
||||||
let commit = last_commit(&repo, Path::new("a.txt"))
|
let commit = worktree_last_commits(dir.path(), &[PathBuf::from("a.txt")])
|
||||||
.expect("lookup")
|
.expect("lookup")
|
||||||
.expect("found");
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.expect("found")
|
||||||
|
.1;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
commit.id,
|
commit.id,
|
||||||
repo.head_id().expect("head").shorten_or_id().to_string()
|
repo.head_id().expect("head").shorten_or_id().to_string()
|
||||||
|
|||||||
@@ -64,20 +64,10 @@ pub fn worktree_commits_ahead(workdir: &Path, base: &str, branch: &str) -> u32 {
|
|||||||
walk.filter_map(Result::ok).count().min(u32::MAX as usize) as u32
|
walk.filter_map(Result::ok).count().min(u32::MAX as usize) as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve `rev` to a commit id, accepting full refs,
|
/// Resolve `rev` to a commit id, accepting full refs or the bare branch names
|
||||||
/// symbolic refs and the bare branch names callers pass, like git's DWIM.
|
/// callers pass. `gix`'s revision parser already applies git's ref DWIM.
|
||||||
fn resolve_commit<'a>(repo: &'a gix::Repository, rev: &str) -> Option<gix::Id<'a>> {
|
fn resolve_commit<'a>(repo: &'a gix::Repository, rev: &str) -> Option<gix::Id<'a>> {
|
||||||
if let Ok(id) = repo.rev_parse_single(rev.as_bytes()) {
|
repo.rev_parse_single(rev.as_bytes()).ok()
|
||||||
return Some(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Branch names arrive bare, like git resolving `main`.
|
|
||||||
if rev.contains('/') {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
repo.rev_parse_single(format!("refs/heads/{rev}").as_bytes())
|
|
||||||
.ok()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Relative paths of all entries in the worktree, files and directories.
|
/// Relative paths of all entries in the worktree, files and directories.
|
||||||
|
|||||||
@@ -5,32 +5,9 @@ use std::pin::Pin;
|
|||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use nostr_connect::client::AuthUrlHandler;
|
use nostr_connect::client::AuthUrlHandler;
|
||||||
|
use nostr_sdk::error::Error as SignerError;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UniversalSignerError(Box<dyn Error + Send + Sync + 'static>);
|
|
||||||
|
|
||||||
impl fmt::Display for UniversalSignerError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for UniversalSignerError {
|
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
||||||
Some(&*self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UniversalSignerError {
|
|
||||||
pub fn new<E>(err: E) -> Self
|
|
||||||
where
|
|
||||||
E: Error + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
UniversalSignerError(Box::new(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A type-erased signer whose inner signer can be swapped in-place.
|
/// A type-erased signer whose inner signer can be swapped in-place.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct UniversalSigner {
|
pub struct UniversalSigner {
|
||||||
@@ -65,21 +42,21 @@ impl UniversalSigner {
|
|||||||
trait InnerSigner: fmt::Debug + Send + Sync + 'static {
|
trait InnerSigner: fmt::Debug + Send + Sync + 'static {
|
||||||
fn get_public_key_async(
|
fn get_public_key_async(
|
||||||
&self,
|
&self,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<PublicKey, UniversalSignerError>> + Send + '_>>;
|
) -> Pin<Box<dyn Future<Output = Result<PublicKey, SignerError>> + Send + '_>>;
|
||||||
fn sign_event_async(
|
fn sign_event_async(
|
||||||
&self,
|
&self,
|
||||||
unsigned: UnsignedEvent,
|
unsigned: UnsignedEvent,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Event, UniversalSignerError>> + Send + '_>>;
|
) -> Pin<Box<dyn Future<Output = Result<Event, SignerError>> + Send + '_>>;
|
||||||
fn nip44_encrypt_async<'a>(
|
fn nip44_encrypt_async<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
public_key: &'a PublicKey,
|
public_key: &'a PublicKey,
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<String, UniversalSignerError>> + Send + 'a>>;
|
) -> Pin<Box<dyn Future<Output = Result<String, SignerError>> + Send + 'a>>;
|
||||||
fn nip44_decrypt_async<'a>(
|
fn nip44_decrypt_async<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
public_key: &'a PublicKey,
|
public_key: &'a PublicKey,
|
||||||
payload: &'a str,
|
payload: &'a str,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<String, UniversalSignerError>> + Send + 'a>>;
|
) -> Pin<Box<dyn Future<Output = Result<String, SignerError>> + Send + 'a>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -94,22 +71,22 @@ where
|
|||||||
{
|
{
|
||||||
fn get_public_key_async(
|
fn get_public_key_async(
|
||||||
&self,
|
&self,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<PublicKey, UniversalSignerError>> + Send + '_>> {
|
) -> Pin<Box<dyn Future<Output = Result<PublicKey, SignerError>> + Send + '_>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
AsyncGetPublicKey::get_public_key_async(&self.0)
|
AsyncGetPublicKey::get_public_key_async(&self.0)
|
||||||
.await
|
.await
|
||||||
.map_err(UniversalSignerError::new)
|
.map_err(SignerError::other)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_event_async(
|
fn sign_event_async(
|
||||||
&self,
|
&self,
|
||||||
unsigned: UnsignedEvent,
|
unsigned: UnsignedEvent,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Event, UniversalSignerError>> + Send + '_>> {
|
) -> Pin<Box<dyn Future<Output = Result<Event, SignerError>> + Send + '_>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
AsyncSignEvent::sign_event_async(&self.0, unsigned)
|
AsyncSignEvent::sign_event_async(&self.0, unsigned)
|
||||||
.await
|
.await
|
||||||
.map_err(UniversalSignerError::new)
|
.map_err(SignerError::other)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,11 +94,11 @@ where
|
|||||||
&'a self,
|
&'a self,
|
||||||
public_key: &'a PublicKey,
|
public_key: &'a PublicKey,
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<String, UniversalSignerError>> + Send + 'a>> {
|
) -> Pin<Box<dyn Future<Output = Result<String, SignerError>> + Send + 'a>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
AsyncNip44::nip44_encrypt_async(&self.0, public_key, content)
|
AsyncNip44::nip44_encrypt_async(&self.0, public_key, content)
|
||||||
.await
|
.await
|
||||||
.map_err(UniversalSignerError::new)
|
.map_err(SignerError::other)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,17 +106,17 @@ where
|
|||||||
&'a self,
|
&'a self,
|
||||||
public_key: &'a PublicKey,
|
public_key: &'a PublicKey,
|
||||||
payload: &'a str,
|
payload: &'a str,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<String, UniversalSignerError>> + Send + 'a>> {
|
) -> Pin<Box<dyn Future<Output = Result<String, SignerError>> + Send + 'a>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
AsyncNip44::nip44_decrypt_async(&self.0, public_key, payload)
|
AsyncNip44::nip44_decrypt_async(&self.0, public_key, payload)
|
||||||
.await
|
.await
|
||||||
.map_err(UniversalSignerError::new)
|
.map_err(SignerError::other)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncGetPublicKey for UniversalSigner {
|
impl AsyncGetPublicKey for UniversalSigner {
|
||||||
type Error = UniversalSignerError;
|
type Error = SignerError;
|
||||||
|
|
||||||
fn get_public_key_async(
|
fn get_public_key_async(
|
||||||
&self,
|
&self,
|
||||||
@@ -150,7 +127,7 @@ impl AsyncGetPublicKey for UniversalSigner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncSignEvent for UniversalSigner {
|
impl AsyncSignEvent for UniversalSigner {
|
||||||
type Error = UniversalSignerError;
|
type Error = SignerError;
|
||||||
|
|
||||||
fn sign_event_async(
|
fn sign_event_async(
|
||||||
&self,
|
&self,
|
||||||
@@ -162,7 +139,7 @@ impl AsyncSignEvent for UniversalSigner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncNip44 for UniversalSigner {
|
impl AsyncNip44 for UniversalSigner {
|
||||||
type Error = UniversalSignerError;
|
type Error = SignerError;
|
||||||
|
|
||||||
fn nip44_encrypt_async<'a>(
|
fn nip44_encrypt_async<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
|
|||||||
@@ -1490,24 +1490,21 @@ const GRASP_RETRY_DELAY: Duration = Duration::from_secs(1);
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GraspServerResult {
|
pub struct GraspServerResult {
|
||||||
pub relay: RelayUrl,
|
pub relay: RelayUrl,
|
||||||
pub git_url: String,
|
|
||||||
/// `None` when the server accepted the data, the reason otherwise.
|
/// `None` when the server accepted the data, the reason otherwise.
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraspServerResult {
|
impl GraspServerResult {
|
||||||
fn ok(relay: RelayUrl, git_url: String) -> Self {
|
fn ok(relay: RelayUrl) -> Self {
|
||||||
Self {
|
Self {
|
||||||
relay,
|
relay,
|
||||||
git_url,
|
|
||||||
reason: None,
|
reason: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn failed(relay: RelayUrl, git_url: String, reason: impl Into<String>) -> Self {
|
fn failed(relay: RelayUrl, reason: impl Into<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
relay,
|
relay,
|
||||||
git_url,
|
|
||||||
reason: Some(reason.into()),
|
reason: Some(reason.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1709,11 +1706,9 @@ async fn push_staged_to_grasps(
|
|||||||
|
|
||||||
for relay in servers {
|
for relay in servers {
|
||||||
let Some(base) = grasp_base_url(relay) else {
|
let Some(base) = grasp_base_url(relay) else {
|
||||||
outcome.servers.push(GraspServerResult::failed(
|
outcome
|
||||||
relay.clone(),
|
.servers
|
||||||
relay.to_string(),
|
.push(GraspServerResult::failed(relay.clone(), "no domain"));
|
||||||
"no domain",
|
|
||||||
));
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let git_url = format!("{base}/{owner}/{repo_id}.git");
|
let git_url = format!("{base}/{owner}/{repo_id}.git");
|
||||||
@@ -1794,11 +1789,9 @@ async fn push_staged_to_grasps(
|
|||||||
log::warn!("grasp push failed: {relay}: {reason}");
|
log::warn!("grasp push failed: {relay}: {reason}");
|
||||||
outcome
|
outcome
|
||||||
.servers
|
.servers
|
||||||
.push(GraspServerResult::failed(relay.clone(), git_url, reason));
|
.push(GraspServerResult::failed(relay.clone(), reason));
|
||||||
}
|
}
|
||||||
None => outcome
|
None => outcome.servers.push(GraspServerResult::ok(relay.clone())),
|
||||||
.servers
|
|
||||||
.push(GraspServerResult::ok(relay.clone(), git_url)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1956,13 +1949,9 @@ mod tests {
|
|||||||
fn push_outcome_reports_partial_failures() {
|
fn push_outcome_reports_partial_failures() {
|
||||||
let outcome = PushOutcome {
|
let outcome = PushOutcome {
|
||||||
servers: vec![
|
servers: vec![
|
||||||
GraspServerResult::ok(
|
GraspServerResult::ok(RelayUrl::parse("wss://gitnostr.com").expect("url")),
|
||||||
RelayUrl::parse("wss://gitnostr.com").expect("url"),
|
|
||||||
"https://gitnostr.com/npub1owner/repo.git".to_owned(),
|
|
||||||
),
|
|
||||||
GraspServerResult::failed(
|
GraspServerResult::failed(
|
||||||
RelayUrl::parse("wss://relay.ngit.dev").expect("url"),
|
RelayUrl::parse("wss://relay.ngit.dev").expect("url"),
|
||||||
"https://relay.ngit.dev/npub1owner/repo.git".to_owned(),
|
|
||||||
"remote: ERR authorisation failed: No state events in purgatory\nfatal: ...",
|
"remote: ERR authorisation failed: No state events in purgatory\nfatal: ...",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ impl InboxView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let root = item.root;
|
let root = item.root;
|
||||||
let kind = item.root_kind;
|
let kind = item.root_event.as_ref().map(|event| event.kind);
|
||||||
let address = section.address.clone();
|
let address = section.address.clone();
|
||||||
let first = entry_ix == 0;
|
let first = entry_ix == 0;
|
||||||
let last = entry_ix + 1 == section.entries.len();
|
let last = entry_ix + 1 == section.entries.len();
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
Deferred from `docs/over-engineering-cleanup-plan.md`.
|
|
||||||
|
|
||||||
## `BackendEvent::SyncProgress`
|
## `BackendEvent::SyncProgress`
|
||||||
|
|
||||||
File: `crates/signed_state/src/backend.rs`
|
File: `crates/signed_state/src/backend.rs`
|
||||||
|
|||||||
@@ -1,300 +0,0 @@
|
|||||||
# Over-engineering cleanup plan
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Remove code that exists but cannot be reached, and machinery that guards states no
|
|
||||||
caller can produce. Findings came from a four-way read-only audit of
|
|
||||||
`signed_state`, `workspace/views`, `signed_git`/`signed_core`, and
|
|
||||||
`dock`/`signed_ui`/misc, cross-checked against the pinned dependency sources in
|
|
||||||
`~/.cargo/git/checkouts/`.
|
|
||||||
|
|
||||||
The main claim in each task below was verified by grepping callers, not by
|
|
||||||
reading the definition alone. Items that were only reported by the audit and not
|
|
||||||
independently reproduced are in Phase 4 and must be verified before deletion.
|
|
||||||
|
|
||||||
## Rules for every step
|
|
||||||
|
|
||||||
- Line numbers are from the current working tree and will drift. Re-grep before
|
|
||||||
editing; do not trust a number from this file after other tasks land.
|
|
||||||
- Delete code, do not comment it out, do not add `#[allow(dead_code)]`.
|
|
||||||
- If a symbol looks dead but is part of a public API or a feature that is only
|
|
||||||
not wired yet, stop and ask.
|
|
||||||
- Do not reintroduce the `views/repo` generation counters that were removed in
|
|
||||||
this working tree.
|
|
||||||
- Keep comments out. Remove any comment that describes the code being deleted.
|
|
||||||
|
|
||||||
## Verification commands
|
|
||||||
|
|
||||||
Always pass `--offline`; a plain `cargo` invocation re-resolves and fails in the
|
|
||||||
sandbox.
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo fmt -p <crate> 2>/dev/null
|
|
||||||
cargo check --offline --workspace --all-targets
|
|
||||||
cargo clippy --offline --workspace --all-targets
|
|
||||||
cargo test --offline --workspace
|
|
||||||
```
|
|
||||||
|
|
||||||
`cargo fmt -- --check` prints unrelated "unstable features" noise on stable.
|
|
||||||
Filter with `grep -E "^Diff in"`. Never hand-reformat; rustfmt is authoritative.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 1 - delete dead code
|
|
||||||
|
|
||||||
No behavior change. Each task is independent; commit per crate.
|
|
||||||
|
|
||||||
### 1.1 `RepoStore::version`
|
|
||||||
|
|
||||||
File: `crates/signed_state/src/repo.rs`
|
|
||||||
|
|
||||||
- [x] Remove field `version: u64` and its doc comment (the claim that views key
|
|
||||||
caches to it is false).
|
|
||||||
- [x] Remove initializers `version: 0` in both constructors.
|
|
||||||
- [x] Remove the bump `this.version = this.version.wrapping_add(1);`.
|
|
||||||
- [x] Remove `pub fn version(&self) -> u64`.
|
|
||||||
|
|
||||||
Evidence: `grep -rn "\.version()" crates` has no call sites; the only read is the
|
|
||||||
accessor itself.
|
|
||||||
|
|
||||||
Acceptance: `grep -rn "version" crates/signed_state/src/repo.rs` shows only
|
|
||||||
unrelated uses (none of the four removed sites).
|
|
||||||
|
|
||||||
### 1.2 `clone_url` module
|
|
||||||
|
|
||||||
Files: `crates/signed_core/src/clone_url.rs`, `crates/signed_core/src/lib.rs`
|
|
||||||
|
|
||||||
- [x] Delete `clone_url.rs`.
|
|
||||||
- [x] Remove `mod clone_url;` and the `pub use clone_url::{CloneTarget, parse_clone_url};`
|
|
||||||
re-export.
|
|
||||||
|
|
||||||
Evidence: only definition, its own test, and the re-export reference these. It
|
|
||||||
also reimplements percent-decoding.
|
|
||||||
|
|
||||||
### 1.3 NIP-32 labels / cover-note helpers
|
|
||||||
|
|
||||||
Files: `crates/signed_core/src/annotations.rs`, `crates/signed_core/src/filters.rs`,
|
|
||||||
`crates/signed_core/src/lib.rs`
|
|
||||||
|
|
||||||
- [x] Verify each of `labels`, `subject_override`, `labels_and_subject`,
|
|
||||||
`cover_note`, `COVER_NOTE_KIND`, `annotations_for` for references outside
|
|
||||||
this crate.
|
|
||||||
- [x] Delete the ones with no production caller and drop them from the `lib.rs`
|
|
||||||
re-exports.
|
|
||||||
- [x] Keep anything still needed (for example `cover_note` /
|
|
||||||
`COVER_NOTE_KIND` may be used by the inbox view).
|
|
||||||
|
|
||||||
Evidence: the UI uses `tags.hashtags()` directly in `views/discussion.rs`, not
|
|
||||||
these helpers.
|
|
||||||
|
|
||||||
### 1.4 `Backend::emit_error`
|
|
||||||
|
|
||||||
File: `crates/signed_state/src/backend.rs`
|
|
||||||
|
|
||||||
- [x] Delete the method. No callers.
|
|
||||||
|
|
||||||
### 1.5 `Backend::pushing_repos()` accessor
|
|
||||||
|
|
||||||
File: `crates/signed_state/src/backend.rs`
|
|
||||||
|
|
||||||
- [x] Delete the getter. The `Entity<HashSet<RepoAddr>>` is used internally; only
|
|
||||||
the accessor is unused.
|
|
||||||
- [ ] Optional follow-up (separate task): nothing observes that entity, so it
|
|
||||||
could be a plain `HashSet` field. Defer; it is a refactor, not a deletion.
|
|
||||||
|
|
||||||
### 1.6 `DropdownButton` speculative knobs
|
|
||||||
|
|
||||||
File: `crates/signed_ui/src/dropdown_button.rs`
|
|
||||||
|
|
||||||
- [x] Remove the `caret: Option<CaretBuilder>` field and the `CaretBuilder` type
|
|
||||||
alias; it is never set, so the `unwrap_or_else` default always runs.
|
|
||||||
Inline the default caret.
|
|
||||||
- [x] Remove the `anchor()` builder method (never called; it is already marked
|
|
||||||
`#[allow(dead_code)]`). Keep the `anchor` field, which is set in the
|
|
||||||
constructor and used when rendering.
|
|
||||||
|
|
||||||
### 1.7 `utils::shorten_pubkey`
|
|
||||||
|
|
||||||
File: `crates/utils/src/pubkey.rs`
|
|
||||||
|
|
||||||
- [x] Drop the `len` parameter; its only call site passes `4`.
|
|
||||||
- [x] Rename to a fixed-width helper if that reads better, or leave the name.
|
|
||||||
|
|
||||||
It duplicates `signed_ui::middle_truncate` conceptually, but `utils` has no gpui
|
|
||||||
dependency, so do not move `middle_truncate`; just remove the speculative
|
|
||||||
parameter.
|
|
||||||
|
|
||||||
### 1.8 Comment artifacts
|
|
||||||
|
|
||||||
- [x] `crates/signed_state/src/repo.rs` - delete the comment that describes
|
|
||||||
querying cover notes and labels per root; no such query exists.
|
|
||||||
- [x] Remove any comment left dangling by the tasks above.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 2 - remove guards that cannot fire
|
|
||||||
|
|
||||||
Each changes behavior on paper but not in practice. Smoke test after each.
|
|
||||||
|
|
||||||
### 2.1 `SignedDockSkin::render_dock` early return and frame duplication
|
|
||||||
|
|
||||||
File: `crates/dock/src/dock_area.rs`
|
|
||||||
|
|
||||||
Base (`gpui_base::dock`) computes `dock_extent`, returns before calling the
|
|
||||||
renderer when the extent is `px(0.)`, and wraps the renderer's output in
|
|
||||||
`dock_frame`. `dock_extent` is `px(0.)` exactly when `!open && !is_bottom`, which
|
|
||||||
is precisely the condition of the early return here.
|
|
||||||
|
|
||||||
- [x] Delete the `if !open && !placement.is_bottom() { return div(); }` guard.
|
|
||||||
- [x] Stop re-applying the box. The chrome is now `.flex().size_full().relative()`,
|
|
||||||
the same shape the pinned reference skin (`crates/ui/src/dock/dock.rs`) uses;
|
|
||||||
base's `dock_frame` supplies the extent and the overflow clip.
|
|
||||||
- [x] Keep the closed-bottom strip height override. Note: base's `dock_frame`
|
|
||||||
hard-codes `CLOSED_BOTTOM_STRIP` (29px) with `overflow_hidden`, so the
|
|
||||||
`TAB_BAR_HEIGHT` (44px) override is clipped and has no visible effect. The
|
|
||||||
strip is 29px today; changing it needs an upstream change.
|
|
||||||
- [ ] Smoke test: open and close left, bottom, and right docks; check widths,
|
|
||||||
the bottom strip height, and resize handles.
|
|
||||||
|
|
||||||
### 2.2 `push_staged_to_grasps` empty-refs guard
|
|
||||||
|
|
||||||
File: `crates/signed_state/src/backend.rs`
|
|
||||||
|
|
||||||
- [x] Delete the `if refs.is_empty() { return outcome; }` guard.
|
|
||||||
|
|
||||||
All three call sites pass a non-empty `refs`: one passes a literal one-element
|
|
||||||
vec, one is inside `if !refs.is_empty()`, one is the `else` of that check.
|
|
||||||
|
|
||||||
### 2.3 `InboxView` per-view debounce
|
|
||||||
|
|
||||||
File: `crates/workspace/src/views/inbox.rs`
|
|
||||||
|
|
||||||
The backend pump already coalesces relay bursts into one `NostrUpdate`, and
|
|
||||||
`query_inbox` reads only the local database.
|
|
||||||
|
|
||||||
- [x] Delete the `REFRESH_DEBOUNCE` constant.
|
|
||||||
- [x] In `refresh`, drop the spawned timer; call `run_refresh` directly after
|
|
||||||
`refresh.request()` returns `Schedule`, matching `RepoStore::refresh`.
|
|
||||||
- [x] Keep `RefreshGate` for fold/overlap.
|
|
||||||
- [ ] Smoke test: inbox updates live as relay events land, with no added delay.
|
|
||||||
|
|
||||||
### 2.4 `DockPlacement::Center` arms in `dock_toggle_button` (optional)
|
|
||||||
|
|
||||||
File: `crates/dock/src/tab_panel.rs`
|
|
||||||
|
|
||||||
- [x] The only call sites pass `Left`, `Bottom`, `Right`. The `designated`
|
|
||||||
match's `Center` arm now returns early instead of yielding a dead `None`;
|
|
||||||
the icon match keeps `Center => return None` for exhaustiveness.
|
|
||||||
|
|
||||||
Low value; skip if it makes the match less readable.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 3 - consolidate duplication (needs a decision)
|
|
||||||
|
|
||||||
Verify the duplication before extracting; each could be intentional.
|
|
||||||
|
|
||||||
### 3.1 `PullRequestsView` and `IssuesView`
|
|
||||||
|
|
||||||
Files: `crates/workspace/src/views/pull_requests/mod.rs`,
|
|
||||||
`crates/workspace/src/views/issues/mod.rs`
|
|
||||||
|
|
||||||
- [x] Confirm the shape. The two `rebuild`s are the same mechanic: one pass over a
|
|
||||||
root list, per-status counts, keep matching indices, early-return when
|
|
||||||
filter/indices/counts are unchanged, resize the item sizes, notify.
|
|
||||||
- [x] Extract `crates/workspace/src/views/status_list.rs` with `StatusCounts` and
|
|
||||||
`filter_by_status`. Both views now use it; the tuple counts were replaced by
|
|
||||||
`StatusCounts`. The notify decision stays local because it would need a trait
|
|
||||||
over the two filter enums.
|
|
||||||
|
|
||||||
### 3.2 Relay URL normalize/display
|
|
||||||
|
|
||||||
Files: `crates/workspace/src/views/sidebar/settings_dialog.rs`,
|
|
||||||
`crates/workspace/src/views/sidebar/grasp_servers.rs`
|
|
||||||
|
|
||||||
- [x] Confirm both pairs do prepend-scheme, parse, dedupe, and host-display.
|
|
||||||
- [x] Extract `normalize_server` and `server_host` into `sidebar/mod.rs`. They live
|
|
||||||
in `workspace`, not `signed_ui`: `signed_ui` does not depend on `nostr`, and
|
|
||||||
these are used only by the two sidebar modules. Dedupe differs per caller
|
|
||||||
(`Vec<RelayUrl>` vs persisted `Vec<String>`) and stays at the call site.
|
|
||||||
|
|
||||||
### 3.3 `crates/dock` vs the pinned `gpui_component` dock renderer
|
|
||||||
|
|
||||||
Files: `crates/dock/src/*` vs the pinned rev's `crates/ui/src/dock/*`
|
|
||||||
|
|
||||||
- [x] Spike: `SignedTabGroupSkin` cannot delegate to the pinned upstream skin.
|
|
||||||
- `TabGroupSkin`, `TilesSkin` and `SkinShared` are `pub(crate)` in
|
|
||||||
`gpui_component::ui`; only the opaque `DockSkin` renderer is public, and it
|
|
||||||
holds that private shared state.
|
|
||||||
- `TabGroupRenderer`/`TilesRenderer` are all-or-nothing per method. The
|
|
||||||
Signed deltas (window controls, prev/next, plain-sidebar detection, i18n)
|
|
||||||
live *inside* `render_tab_bar` and `frame`. There is no hook below the whole
|
|
||||||
method, so "delegate and keep only the deltas" has no seam to hang on.
|
|
||||||
- Composing `Rc<DockSkin>` would still leave `render_tab_bar` a near-full
|
|
||||||
reimplementation while adding a dependency on upstream internals, for no
|
|
||||||
line reduction.
|
|
||||||
- [x] Effort/risk: high effort, high churn, no achievable reduction on this rev.
|
|
||||||
`SignedTilesSkin` is the same shape. Recommend keeping the fork as-is. A
|
|
||||||
future upstream change (public `DockSkin` with per-part hooks) would be the
|
|
||||||
precondition for any delegation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 4 - triage flagged items
|
|
||||||
|
|
||||||
Verify each, then delete or dismiss. These were reported by the audit but not
|
|
||||||
independently reproduced.
|
|
||||||
|
|
||||||
- [ ] `signed_git/src/worktree.rs` - manual `refs/heads/{rev}` fallback; check
|
|
||||||
whether gix's ref DWIM already covers it.
|
|
||||||
- [ ] `signed_git/src/repo.rs` - `refs_with_prefix`; check
|
|
||||||
`repo.references()?.prefixed(prefix)`.
|
|
||||||
- [ ] `signed_git/src/patch.rs` - the mbox envelope is scanned twice; check
|
|
||||||
whether `patch_commits` can consume `split_patch_series` output, and
|
|
||||||
whether the hard-coded 40-hex checks should use `gix::ObjectId::from_hex`.
|
|
||||||
- [ ] `assets/src/lib.rs` - `themes` handles a `Cow::Owned` case the build
|
|
||||||
features cannot produce.
|
|
||||||
- [ ] `signed_state/src/backend.rs` - `GraspServerResult::git_url` populated but
|
|
||||||
never read.
|
|
||||||
- [ ] `signed_nostr/src/signer.rs` - `UniversalSignerError` vs
|
|
||||||
`nostr::Error::other`. Keep the `InnerSigner` erasure shim; only the error
|
|
||||||
wrapper is replaceable.
|
|
||||||
- [ ] `signed_git/src/history.rs` - `last_commit` referenced only from tests.
|
|
||||||
- [ ] `signed_git/src/repo.rs` - `init_repository` / `root_commit` 40-length
|
|
||||||
guards on an `ObjectId` string.
|
|
||||||
- [ ] `signed_core/src/model.rs` - `Upstream.relay_hint` parsed but unused in
|
|
||||||
production.
|
|
||||||
- [ ] `signed_core/src/inbox.rs` - `root_kind` duplicates `root_event`; confirm
|
|
||||||
before removing, it is read by the inbox view.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Do not touch
|
|
||||||
|
|
||||||
- The two `pull_requests` generation counters (`load_generation` in
|
|
||||||
`detail.rs`, `compare_generation` in `new.rs`). Both were verified reachable.
|
|
||||||
- Tasks stored in a `Vec<Task<..>>` for lifetime cancellation. This is
|
|
||||||
intentional.
|
|
||||||
- `RefreshGate` on `CheckoutsStore`; its timer-driven debounce is load-bearing.
|
|
||||||
- The `dev`-time `init_dialog.rs` `.detach()`; the task owns a window-scoped
|
|
||||||
dialog and has no owning struct.
|
|
||||||
|
|
||||||
## Acceptance criteria
|
|
||||||
|
|
||||||
- Every removed symbol returns empty for a repo-wide grep.
|
|
||||||
- No new `#[allow(dead_code)]`.
|
|
||||||
- `cargo fmt -- --check` diff-free, `cargo check --offline --workspace
|
|
||||||
--all-targets` clean, `cargo clippy --offline --workspace --all-targets`
|
|
||||||
clean, `cargo test --offline --workspace` green.
|
|
||||||
- Manual smoke: open and close docks, watch the inbox update live, open a repo
|
|
||||||
and switch branches, publish a repo.
|
|
||||||
|
|
||||||
## Suggested commit sequence
|
|
||||||
|
|
||||||
1. `signed_state`: 1.1, 1.4, 1.5 (dead code), plus 1.8 comments in the same files.
|
|
||||||
2. `signed_core`: 1.2, 1.3 (dead modules).
|
|
||||||
3. `signed_ui` + `utils`: 1.6, 1.7.
|
|
||||||
4. `dock`: 2.1, 2.4.
|
|
||||||
5. `signed_state`: 2.2.
|
|
||||||
6. `workspace`: 2.3.
|
|
||||||
7. Phases 3 and 4 as separate, individually reviewed changes.
|
|
||||||
Reference in New Issue
Block a user