chore: upgrade gpui-component to 0.7 and gix to 0.88
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
- Add an identity import dialog, importing an existing identity by pasting its `nsec` secret key (encrypted with a new passphrase), NIP-49 `ncryptsec` (unlocked with its passphrase), or `bunker://` NIP-46 URI
|
- Add an identity import dialog, importing an existing identity by pasting its `nsec` secret key (encrypted with a new passphrase), NIP-49 `ncryptsec` (unlocked with its passphrase), or `bunker://` NIP-46 URI
|
||||||
- Add a dropdown menu on the signed-in user button in the sidebar
|
- Add a dropdown menu on the signed-in user button in the sidebar
|
||||||
- Add sign-out feature, confirmed by an alert dialog before deleting the stored session credential
|
- Add sign-out feature, confirmed by an alert dialog before deleting the stored session credential
|
||||||
|
- Add a quit action, bound to `secondary-q` (cmd on macOS, ctrl on others)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
Generated
+328
-348
File diff suppressed because it is too large
Load Diff
+6
-10
@@ -9,13 +9,13 @@ edition = "2024"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
gpui = { package = "gpui-pre", version = "0.3.6" }
|
gpui = { package = "gpui-pre", version = "0.3.8" }
|
||||||
gpui_platform = { package = "gpui-pre-platform", version = "0.3.6", features = ["font-kit", "x11", "wayland"] }
|
gpui_platform = { package = "gpui-pre-platform", version = "0.3.8", features = ["font-kit", "x11", "wayland"] }
|
||||||
reqwest_client = { package = "gpui-pre-reqwest-client", version = "0.3.6" }
|
reqwest_client = { package = "gpui-pre-reqwest-client", version = "0.3.8" }
|
||||||
|
|
||||||
# GPUI Kit
|
# GPUI Kit
|
||||||
gpui-component = { version = "0.6.6", features = ["tree-sitter-languages"] }
|
gpui-component = { version = "0.7.1", features = ["tree-sitter-languages"] }
|
||||||
gpui-base = "0.6.6"
|
gpui-base = "0.7.1"
|
||||||
|
|
||||||
dock = { path = "crates/dock" }
|
dock = { path = "crates/dock" }
|
||||||
settings = { path = "crates/settings" }
|
settings = { path = "crates/settings" }
|
||||||
@@ -27,7 +27,7 @@ nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" }
|
|||||||
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-sdk = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
|
|
||||||
gix = { version = "0.87.1", default-features = false, features = ["sha1", "blocking-network-client", "blocking-http-transport-reqwest-rust-tls", "worktree-mutation", "status"] }
|
gix = { version = "0.88", default-features = false, features = ["sha1", "blocking-network-client", "blocking-http-transport-reqwest-rust-tls", "worktree-mutation", "status"] }
|
||||||
|
|
||||||
smol = "2"
|
smol = "2"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
@@ -44,10 +44,6 @@ tracing = "0.1"
|
|||||||
errno = { version = "0.3.14", default-features = false }
|
errno = { version = "0.3.14", default-features = false }
|
||||||
instant = "0.1"
|
instant = "0.1"
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
# Use stacker's psm version which may have better WASM support
|
|
||||||
psm = { git = "https://github.com/rust-lang/stacker", branch = "master" }
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
strip = true
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use gpui_base::dock::{
|
|||||||
DockArea, DockAreaRenderer, DockContext, DockEvent, DockPlacement, NodeId, PanelState,
|
DockArea, DockAreaRenderer, DockContext, DockEvent, DockPlacement, NodeId, PanelState,
|
||||||
PanelView, TabGroupRenderer,
|
PanelView, TabGroupRenderer,
|
||||||
};
|
};
|
||||||
use gpui_base::resize_handle;
|
use gpui_base::{HandleEdge, resize_handle};
|
||||||
use gpui_component::{ActiveTheme as _, Side};
|
use gpui_component::ActiveTheme as _;
|
||||||
|
|
||||||
use crate::invalid_panel::InvalidPanel;
|
use crate::invalid_panel::InvalidPanel;
|
||||||
use crate::panel_handle;
|
use crate::panel_handle;
|
||||||
@@ -164,7 +164,12 @@ impl SignedDockSkin {
|
|||||||
let shared = self.shared().clone();
|
let shared = self.shared().clone();
|
||||||
|
|
||||||
resize_handle("resize-handle", placement.axis())
|
resize_handle("resize-handle", placement.axis())
|
||||||
.when(placement.is_left(), |this| this.placement(Side::Left))
|
.when(placement.is_left(), |this| {
|
||||||
|
this.inside(HandleEdge::Trailing)
|
||||||
|
})
|
||||||
|
.when(placement.is_right() || placement.is_bottom(), |this| {
|
||||||
|
this.inside(HandleEdge::Leading)
|
||||||
|
})
|
||||||
.on_drag(ResizePanel, move |info, _, _, cx| {
|
.on_drag(ResizePanel, move |info, _, _, cx| {
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
shared.resizing_dock().set(Some(placement));
|
shared.resizing_dock().set(Some(placement));
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ nostr.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
gix = { workspace = true, features = ["revision", "blob-diff"] }
|
gix = { workspace = true, features = ["revision", "blob-diff"] }
|
||||||
gix-worktree = "0.56"
|
gix-worktree = "0.57"
|
||||||
gix-worktree-state = "0.34"
|
gix-worktree-state = "0.35"
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
diffy = "0.5"
|
diffy = "0.5"
|
||||||
ignore = "0.4"
|
ignore = "0.4"
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ impl GitCache {
|
|||||||
let path = self.repo_path(addr);
|
let path = self.repo_path(addr);
|
||||||
match gix::open(&path) {
|
match gix::open(&path) {
|
||||||
Ok(repo) => Ok(Some(Repo { inner: repo })),
|
Ok(repo) => Ok(Some(Repo { inner: repo })),
|
||||||
Err(gix::open::Error::NotARepository { .. }) => Ok(None),
|
// A missing path and a directory that is no repository are both classified `NotFound`.
|
||||||
Err(gix::open::Error::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
Err(e) if e.is_not_found() => Ok(None),
|
||||||
Err(e) => Err(e.into()),
|
Err(e) => Err(e.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gix::diff::blob::unified_diff::{ConsumeHunk, DiffLineKind as GixLineKind, HunkHeader};
|
use gix::diff::blob::unified_diff::{ConsumeHunk, DiffLineKind as GixLineKind, HunkHeader};
|
||||||
|
|
||||||
|
use crate::GixResultExt as _;
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -164,7 +165,7 @@ impl Repo {
|
|||||||
.resource_cache
|
.resource_cache
|
||||||
.options
|
.options
|
||||||
.skip_internal_diff_if_external_is_configured = true;
|
.skip_internal_diff_if_external_is_configured = true;
|
||||||
let outcome = platform.resource_cache.prepare_diff()?;
|
let outcome = platform.resource_cache.prepare_diff().into_anyhow()?;
|
||||||
|
|
||||||
let (binary, hunks, insertions, deletions) = match outcome.operation {
|
let (binary, hunks, insertions, deletions) = match outcome.operation {
|
||||||
Operation::InternalDiff { algorithm } => {
|
Operation::InternalDiff { algorithm } => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::GixResultExt as _;
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -29,8 +30,8 @@ impl FileCommit {
|
|||||||
commit: &gix::Commit<'_>,
|
commit: &gix::Commit<'_>,
|
||||||
include_description: bool,
|
include_description: bool,
|
||||||
) -> Result<FileCommit> {
|
) -> Result<FileCommit> {
|
||||||
let author = commit.author()?;
|
let author = commit.author().into_anyhow()?;
|
||||||
let message = commit.message()?;
|
let message = commit.message().into_anyhow()?;
|
||||||
|
|
||||||
Ok(FileCommit {
|
Ok(FileCommit {
|
||||||
id: commit.id().shorten_or_id().to_string(),
|
id: commit.id().shorten_or_id().to_string(),
|
||||||
@@ -44,7 +45,7 @@ impl FileCommit {
|
|||||||
None
|
None
|
||||||
},
|
},
|
||||||
author: String::from_utf8_lossy(author.name).trim().to_string(),
|
author: String::from_utf8_lossy(author.name).trim().to_string(),
|
||||||
time: author.time()?.seconds,
|
time: author.time().into_anyhow()?.seconds,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,19 @@ pub use repo::{Repo, RepoRefState};
|
|||||||
pub use scan::{LocalRepo, find_git_repos};
|
pub use scan::{LocalRepo, find_git_repos};
|
||||||
pub use worktree::WorktreeSnapshot;
|
pub use worktree::WorktreeSnapshot;
|
||||||
|
|
||||||
|
pub(crate) trait GixResultExt<T> {
|
||||||
|
fn into_anyhow(self) -> anyhow::Result<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> GixResultExt<T> for Result<T, gix::Exn<E>>
|
||||||
|
where
|
||||||
|
E: std::error::Error + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn into_anyhow(self) -> anyhow::Result<T> {
|
||||||
|
self.map_err(|exn| anyhow::Error::from(exn.into_error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn git_in(dir: &std::path::Path, args: &[&str]) -> anyhow::Result<String> {
|
fn git_in(dir: &std::path::Path, args: &[&str]) -> anyhow::Result<String> {
|
||||||
let output = Repo::run_git(dir, args, "git")?;
|
let output = Repo::run_git(dir, args, "git")?;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use anyhow::Result;
|
|||||||
use gix::bstr::ByteSlice;
|
use gix::bstr::ByteSlice;
|
||||||
use nostr::prelude::*;
|
use nostr::prelude::*;
|
||||||
|
|
||||||
|
use crate::GixResultExt as _;
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
|
|
||||||
/// The kind of NIP-34 relationship a local repository has on disk.
|
/// The kind of NIP-34 relationship a local repository has on disk.
|
||||||
@@ -186,7 +187,7 @@ impl Repo {
|
|||||||
|
|
||||||
pub fn set_nostr_repo(&self, naddr: &str) -> Result<()> {
|
pub fn set_nostr_repo(&self, naddr: &str) -> Result<()> {
|
||||||
self.edit_local_config(|config| {
|
self.edit_local_config(|config| {
|
||||||
config.set_raw_value("nostr.repo", naddr)?;
|
config.set_raw_value("nostr.repo", naddr).into_anyhow()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use crate::repo::Repo;
|
|||||||
pub struct PatchParser;
|
pub struct PatchParser;
|
||||||
|
|
||||||
impl PatchParser {
|
impl PatchParser {
|
||||||
// A malformed input yields one element covering it.
|
|
||||||
pub fn split_patch_series(patch: &str) -> Vec<&str> {
|
pub fn split_patch_series(patch: &str) -> Vec<&str> {
|
||||||
Self::envelopes(patch)
|
Self::envelopes(patch)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -20,8 +19,6 @@ impl PatchParser {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backed by `diffy::patch_set`, which implements git's extended diff format:
|
|
||||||
// rename and copy detection, binary detection, quoted/escaped paths.
|
|
||||||
pub fn patch_diffs(patch: &str) -> Result<CommitDiff> {
|
pub fn patch_diffs(patch: &str) -> Result<CommitDiff> {
|
||||||
if !patch.lines().any(|line| line.starts_with("diff --git ")) {
|
if !patch.lines().any(|line| line.starts_with("diff --git ")) {
|
||||||
return Ok(CommitDiff { files: Vec::new() });
|
return Ok(CommitDiff { files: Vec::new() });
|
||||||
@@ -36,8 +33,6 @@ impl PatchParser {
|
|||||||
Ok(CommitDiff { files })
|
Ok(CommitDiff { files })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entries appear in patch order, oldest first as `git format-patch`
|
|
||||||
// produces them.
|
|
||||||
pub fn patch_commits(patch: &str) -> Vec<FileCommit> {
|
pub fn patch_commits(patch: &str) -> Vec<FileCommit> {
|
||||||
Self::envelopes(patch)
|
Self::envelopes(patch)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -62,8 +57,6 @@ impl PatchParser {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The one parser `split_patch_series` and `patch_commits` share.
|
|
||||||
// A malformed input yields one message covering the whole input.
|
|
||||||
fn envelopes(patch: &str) -> Vec<Envelope<'_>> {
|
fn envelopes(patch: &str) -> Vec<Envelope<'_>> {
|
||||||
let mut messages: Vec<Envelope<'_>> = Vec::new();
|
let mut messages: Vec<Envelope<'_>> = Vec::new();
|
||||||
let mut current: Option<(usize, &str, Vec<&str>)> = None;
|
let mut current: Option<(usize, &str, Vec<&str>)> = None;
|
||||||
@@ -136,7 +129,7 @@ impl PatchParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matches `[PATCH]`, `[PATCH 1/2]`, `[RFC PATCH]`, etc.
|
/// Matches `[PATCH]`, `[PATCH 1/2]`, `[RFC PATCH]`, etc.
|
||||||
fn strip_patch_prefix(subject: &str) -> String {
|
fn strip_patch_prefix(subject: &str) -> String {
|
||||||
let trimmed = subject.trim();
|
let trimmed = subject.trim();
|
||||||
let Some(rest) = trimmed.strip_prefix('[') else {
|
let Some(rest) = trimmed.strip_prefix('[') else {
|
||||||
@@ -153,9 +146,6 @@ impl PatchParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn file_diff(file: FilePatch<'_, str>) -> Result<FileDiff> {
|
fn file_diff(file: FilePatch<'_, str>) -> Result<FileDiff> {
|
||||||
// The `---`/`+++` paths carry the `a/`/`b/` prefix, dropped the same way
|
|
||||||
// `git apply -p1` does; rename and copy paths come from their own
|
|
||||||
// headers, unprefixed.
|
|
||||||
let stripped;
|
let stripped;
|
||||||
let operation = match file.operation() {
|
let operation = match file.operation() {
|
||||||
operation @ (FileOperation::Rename { .. } | FileOperation::Copy { .. }) => operation,
|
operation @ (FileOperation::Rename { .. } | FileOperation::Copy { .. }) => operation,
|
||||||
@@ -213,8 +203,6 @@ impl PatchParser {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// `diffy` reports only the hunk header ranges; the per-line numbers are
|
|
||||||
// counted from them the way the header encodes them.
|
|
||||||
fn hunk_diff(hunk: &Hunk<'_, str>) -> DiffHunk {
|
fn hunk_diff(hunk: &Hunk<'_, str>) -> DiffHunk {
|
||||||
let old_range = hunk.old_range();
|
let old_range = hunk.old_range();
|
||||||
let new_range = hunk.new_range();
|
let new_range = hunk.new_range();
|
||||||
@@ -272,7 +260,7 @@ impl PatchParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A `git format-patch` mbox message, split on its `From <40-hex> <date>` envelope.
|
/// A `git format-patch` mbox message, split on its `From <40-hex> <date>` envelope.
|
||||||
struct Envelope<'a> {
|
struct Envelope<'a> {
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
id: &'a str,
|
id: &'a str,
|
||||||
@@ -290,8 +278,6 @@ impl Envelope<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Repo {
|
impl Repo {
|
||||||
// The git CLI handles the mbox format natively.
|
|
||||||
// TODO: replace with a pure-Rust implementation later without changing callers.
|
|
||||||
pub fn apply_patch(&self, patch: &str) -> Result<()> {
|
pub fn apply_patch(&self, patch: &str) -> Result<()> {
|
||||||
let workdir = self
|
let workdir = self
|
||||||
.inner
|
.inner
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use anyhow::{Context, Result, bail};
|
|||||||
use gix::interrupt::IS_INTERRUPTED;
|
use gix::interrupt::IS_INTERRUPTED;
|
||||||
use gix::progress::Discard;
|
use gix::progress::Discard;
|
||||||
|
|
||||||
|
use crate::GixResultExt as _;
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
|
|
||||||
impl Repo {
|
impl Repo {
|
||||||
@@ -15,7 +16,8 @@ impl Repo {
|
|||||||
gix::refspec::parse(
|
gix::refspec::parse(
|
||||||
gix::bstr::BStr::new("+refs/nostr/*:refs/nostr/*"),
|
gix::bstr::BStr::new("+refs/nostr/*:refs/nostr/*"),
|
||||||
gix::refspec::parse::Operation::Fetch,
|
gix::refspec::parse::Operation::Fetch,
|
||||||
)?
|
)
|
||||||
|
.into_anyhow()?
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -87,10 +89,6 @@ impl Repo {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// The convergence probe for a push that lost the compare-and-swap race to
|
|
||||||
// the grasp server's own background ref alignment. Extra advertised refs
|
|
||||||
// are ignored: the question is whether the pushed data is already there,
|
|
||||||
// not whether the remote is an exact mirror.
|
|
||||||
pub fn remote_has_refs(&self, url: &str, expected: &[(String, String)]) -> Result<bool> {
|
pub fn remote_has_refs(&self, url: &str, expected: &[(String, String)]) -> Result<bool> {
|
||||||
if expected.is_empty() {
|
if expected.is_empty() {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
@@ -98,8 +96,6 @@ impl Repo {
|
|||||||
|
|
||||||
let url = Self::transport_url(url);
|
let url = Self::transport_url(url);
|
||||||
|
|
||||||
// A URL-created remote has no configured fetch refspecs, and `ref_map`
|
|
||||||
// only keeps refs matching one; match each expected ref by exact name.
|
|
||||||
let refspecs = expected
|
let refspecs = expected
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, _)| {
|
.map(|(name, _)| {
|
||||||
@@ -110,6 +106,7 @@ impl Repo {
|
|||||||
.map(|spec| spec.to_owned())
|
.map(|spec| spec.to_owned())
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.into_anyhow()
|
||||||
.context("invalid refspec")?;
|
.context("invalid refspec")?;
|
||||||
|
|
||||||
let options = gix::remote::ref_map::Options {
|
let options = gix::remote::ref_map::Options {
|
||||||
@@ -126,8 +123,8 @@ impl Repo {
|
|||||||
.ref_map(Discard, options)
|
.ref_map(Discard, options)
|
||||||
.with_context(|| format!("listing refs of {url} failed"))?;
|
.with_context(|| format!("listing refs of {url} failed"))?;
|
||||||
|
|
||||||
// Peeled tag entries carry the tag object in their direct oid, matching
|
// Peeled tag entries carry the tag object in their direct oid,
|
||||||
// `git ls-remote` while skipping the duplicated `^{}` lines.
|
// matching `git ls-remote` while skipping the duplicated `^{}` lines.
|
||||||
let advertised: HashMap<String, String> = refs
|
let advertised: HashMap<String, String> = refs
|
||||||
.remote_refs
|
.remote_refs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -149,23 +146,30 @@ impl Repo {
|
|||||||
|
|
||||||
// `git remote add` also configures the default fetch refspec.
|
// `git remote add` also configures the default fetch refspec.
|
||||||
self.edit_local_config(|config| {
|
self.edit_local_config(|config| {
|
||||||
config.set_raw_value("remote.origin.url", url)?;
|
config
|
||||||
config.set_raw_value("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")?;
|
.set_raw_value("remote.origin.url", url)
|
||||||
|
.into_anyhow()?;
|
||||||
|
config
|
||||||
|
.set_raw_value("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")
|
||||||
|
.into_anyhow()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// A working copy cloned from a local mirror is re-targeted at the
|
// A working copy cloned from a local mirror is re-targeted at the grasp server,
|
||||||
// grasp server; a pre-existing fetch refspec is left untouched.
|
// a pre-existing fetch refspec is left untouched.
|
||||||
pub fn set_origin(&self, url: &str) -> Result<()> {
|
pub fn set_origin(&self, url: &str) -> Result<()> {
|
||||||
let had_origin = self.inner.find_remote("origin").is_ok();
|
let had_origin = self.inner.find_remote("origin").is_ok();
|
||||||
|
|
||||||
self.edit_local_config(|config| {
|
self.edit_local_config(|config| {
|
||||||
config.set_raw_value("remote.origin.url", url)?;
|
config
|
||||||
|
.set_raw_value("remote.origin.url", url)
|
||||||
|
.into_anyhow()?;
|
||||||
|
|
||||||
if !had_origin {
|
if !had_origin {
|
||||||
config
|
config
|
||||||
.set_raw_value("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")?;
|
.set_raw_value("remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*")
|
||||||
|
.into_anyhow()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -182,13 +186,12 @@ impl Repo {
|
|||||||
.map(|url| url.to_string()))
|
.map(|url| url.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never touches the checked-out refs or the worktree. The last error is
|
|
||||||
// returned when no URL works.
|
|
||||||
pub fn fetch_refs<U: AsRef<str>>(&self, urls: &[U], refspec: &str) -> Result<()> {
|
pub fn fetch_refs<U: AsRef<str>>(&self, urls: &[U], refspec: &str) -> Result<()> {
|
||||||
let refspec = gix::refspec::parse(
|
let refspec = gix::refspec::parse(
|
||||||
gix::bstr::BStr::new(refspec),
|
gix::bstr::BStr::new(refspec),
|
||||||
gix::refspec::parse::Operation::Fetch,
|
gix::refspec::parse::Operation::Fetch,
|
||||||
)
|
)
|
||||||
|
.into_anyhow()
|
||||||
.context("invalid fetch refspec")?
|
.context("invalid fetch refspec")?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
@@ -236,18 +239,19 @@ impl Repo {
|
|||||||
gix::lock::acquire::Fail::Immediately,
|
gix::lock::acquire::Fail::Immediately,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
.into_anyhow()
|
||||||
.context("failed to lock repository config")?;
|
.context("failed to lock repository config")?;
|
||||||
|
|
||||||
let mut config =
|
let mut config =
|
||||||
match gix::config::File::from_path_no_includes(config_path, gix::config::Source::Local)
|
match gix::config::File::from_path_no_includes(config_path, gix::config::Source::Local)
|
||||||
{
|
{
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(gix::config::file::init::from_paths::Error::Io { source, .. })
|
Err(error) if error.is_not_found() => gix::config::File::default(),
|
||||||
if source.kind() == std::io::ErrorKind::NotFound =>
|
Err(error) => {
|
||||||
{
|
return Err(error)
|
||||||
gix::config::File::default()
|
.into_anyhow()
|
||||||
|
.context("failed to read repository config");
|
||||||
}
|
}
|
||||||
Err(error) => return Err(error).context("failed to read repository config"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
edit(&mut config)?;
|
edit(&mut config)?;
|
||||||
|
|||||||
@@ -2,11 +2,23 @@ use std::path::Path;
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
// History walks re-decode the same commit objects without one; sized
|
use crate::GixResultExt as _;
|
||||||
// generously, a walk can cover a large portion of the history.
|
|
||||||
const OBJECT_CACHE_BYTES: usize = 64 * 1024 * 1024;
|
const OBJECT_CACHE_BYTES: usize = 64 * 1024 * 1024;
|
||||||
|
|
||||||
// One `open` per operation instead of every helper re-opening by path.
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct RepoRefState {
|
||||||
|
pub refs: Vec<(String, String)>,
|
||||||
|
pub head: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepoRefState {
|
||||||
|
fn new(refs: Vec<(String, String)>, head: Option<String>) -> Self {
|
||||||
|
Self { refs, head }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One `open` per operation instead of every helper re-opening by path.
|
||||||
pub struct Repo {
|
pub struct Repo {
|
||||||
pub(crate) inner: gix::Repository,
|
pub(crate) inner: gix::Repository,
|
||||||
}
|
}
|
||||||
@@ -22,8 +34,6 @@ impl Repo {
|
|||||||
Self::open(workdir).ok()
|
Self::open(workdir).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only history walks benefit from the object cache, they re-decode the
|
|
||||||
// same commit objects repeatedly; single-object reads open plain.
|
|
||||||
pub fn open_cached(workdir: &Path) -> Result<Self> {
|
pub fn open_cached(workdir: &Path) -> Result<Self> {
|
||||||
let mut repo = gix::open(workdir)?;
|
let mut repo = gix::open(workdir)?;
|
||||||
repo.object_cache_size_if_unset(OBJECT_CACHE_BYTES);
|
repo.object_cache_size_if_unset(OBJECT_CACHE_BYTES);
|
||||||
@@ -41,8 +51,6 @@ impl Repo {
|
|||||||
let (signature, mut time_buf) = Self::repository_signature();
|
let (signature, mut time_buf) = Self::repository_signature();
|
||||||
let signature = signature.to_ref(&mut time_buf);
|
let signature = signature.to_ref(&mut time_buf);
|
||||||
|
|
||||||
// The initial branch is `main` regardless of `init.defaultBranch`:
|
|
||||||
// point the unborn HEAD there.
|
|
||||||
let head = gix::refs::FullName::try_from("HEAD")
|
let head = gix::refs::FullName::try_from("HEAD")
|
||||||
.map_err(|e| anyhow::anyhow!("invalid ref name: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("invalid ref name: {e}"))?;
|
||||||
|
|
||||||
@@ -98,12 +106,14 @@ impl Repo {
|
|||||||
// Populate the index so the fresh repository is clean, as `git add`
|
// Populate the index so the fresh repository is clean, as `git add`
|
||||||
// and `git commit` would leave it.
|
// and `git commit` would leave it.
|
||||||
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())
|
||||||
|
.into_anyhow()?;
|
||||||
|
|
||||||
Ok(commit.to_string())
|
Ok(commit.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not kept in any cache, unlike `GitCache::ensure_clone`.
|
/// Not kept in any cache, unlike `GitCache::ensure_clone`.
|
||||||
pub fn clone<U: AsRef<str>>(clone_urls: &[U], path: &Path) -> Result<Self> {
|
pub fn clone<U: AsRef<str>>(clone_urls: &[U], path: &Path) -> Result<Self> {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
anyhow::bail!("destination {} already exists", path.display());
|
anyhow::bail!("destination {} already exists", path.display());
|
||||||
@@ -130,7 +140,9 @@ impl Repo {
|
|||||||
|
|
||||||
fn clone_from(url: &str, path: &Path) -> Result<Self> {
|
fn clone_from(url: &str, path: &Path) -> Result<Self> {
|
||||||
let url = Self::transport_url(url);
|
let url = Self::transport_url(url);
|
||||||
let url = gix::url::parse(url).context("invalid clone URL")?;
|
let url = gix::url::parse(url)
|
||||||
|
.into_anyhow()
|
||||||
|
.context("invalid clone URL")?;
|
||||||
|
|
||||||
let mut prepare = gix::prepare_clone(url, path)?;
|
let mut prepare = gix::prepare_clone(url, path)?;
|
||||||
let (mut checkout, _fetch) =
|
let (mut checkout, _fetch) =
|
||||||
@@ -149,24 +161,20 @@ impl Repo {
|
|||||||
self.inner.workdir()
|
self.inner.workdir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// `None` for an unborn HEAD.
|
/// `None` for an unborn HEAD.
|
||||||
pub fn head(&self) -> Option<String> {
|
pub fn head(&self) -> Option<String> {
|
||||||
self.inner.head_id().ok().map(|id| id.to_string())
|
self.inner.head_id().ok().map(|id| id.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
// `Ok(None)` when the revisions share no common ancestor — a valid
|
|
||||||
// outcome for a proposal; unresolvable revisions are errors.
|
|
||||||
pub fn merge_base(&self, a: &str, b: &str) -> Result<Option<String>> {
|
pub fn merge_base(&self, a: &str, b: &str) -> Result<Option<String>> {
|
||||||
let a = self.inner.rev_parse_single(a.as_bytes())?;
|
let a = self.inner.rev_parse_single(a.as_bytes())?;
|
||||||
let b = self.inner.rev_parse_single(b.as_bytes())?;
|
let b = self.inner.rev_parse_single(b.as_bytes())?;
|
||||||
match self.inner.merge_base(a, b) {
|
// `merge_base` reports a missing base as an unclassified error, while the
|
||||||
Ok(id) => Ok(Some(id.to_string())),
|
// many-bases variant keeps the distinction as an empty result.
|
||||||
Err(gix::repository::merge_base::Error::NotFound { .. }) => Ok(None),
|
let bases = self.inner.merge_bases_many(a.detach(), &[b.detach()])?;
|
||||||
Err(e) => Err(e.into()),
|
Ok(bases.first().map(|id| id.to_string()))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Oldest first: the order `git am` creates them.
|
|
||||||
pub fn commits_since(&self, base: Option<&str>) -> Result<Vec<String>> {
|
pub fn commits_since(&self, base: Option<&str>) -> Result<Vec<String>> {
|
||||||
let head = match self.inner.head_id() {
|
let head = match self.inner.head_id() {
|
||||||
Ok(head) => head,
|
Ok(head) => head,
|
||||||
@@ -198,7 +206,6 @@ impl Repo {
|
|||||||
Ok(commits)
|
Ok(commits)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The NIP-34 announcement's `euc` marker; `None` without commits.
|
|
||||||
pub fn root_commit(&self) -> Result<Option<String>> {
|
pub fn root_commit(&self) -> Result<Option<String>> {
|
||||||
let Ok(head) = self.inner.head_id() else {
|
let Ok(head) = self.inner.head_id() else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -331,11 +338,9 @@ impl Repo {
|
|||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(RepoRefState { refs, head })
|
Ok(RepoRefState::new(refs, head))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast-forward only: local-only commits or diverged history must never
|
|
||||||
// be rewritten by a refresh. Returns whether any branch moved.
|
|
||||||
pub fn fast_forward_branches(&self) -> Result<bool> {
|
pub fn fast_forward_branches(&self) -> Result<bool> {
|
||||||
use gix::refs::transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog};
|
use gix::refs::transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog};
|
||||||
|
|
||||||
@@ -437,8 +442,6 @@ impl Repo {
|
|||||||
Ok(moved)
|
Ok(moved)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like `git -c user.name=… -c user.email=…` per invocation: the repository
|
|
||||||
// works without a global git identity, and `gix` runs no hooks and never signs.
|
|
||||||
pub(crate) fn repository_signature() -> (gix::actor::Signature, gix::date::parse::TimeBuf) {
|
pub(crate) fn repository_signature() -> (gix::actor::Signature, gix::date::parse::TimeBuf) {
|
||||||
let seconds = std::time::SystemTime::now()
|
let seconds = std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
@@ -454,17 +457,9 @@ impl Repo {
|
|||||||
(signature, gix::date::parse::TimeBuf::default())
|
(signature, gix::date::parse::TimeBuf::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GRASP servers announce `grasp://` clone URLs but the transport is git
|
|
||||||
// smart HTTP, so the scheme is rewritten for gix.
|
|
||||||
pub(crate) fn transport_url(url: &str) -> String {
|
pub(crate) fn transport_url(url: &str) -> String {
|
||||||
url.strip_prefix("grasp://")
|
url.strip_prefix("grasp://")
|
||||||
.map(|rest| format!("https://{rest}"))
|
.map(|rest| format!("https://{rest}"))
|
||||||
.unwrap_or_else(|| url.to_owned())
|
.unwrap_or_else(|| url.to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct RepoRefState {
|
|
||||||
pub refs: Vec<(String, String)>,
|
|
||||||
pub head: Option<String>,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use ignore::WalkBuilder;
|
|||||||
use crate::nip34::Nip34Binding;
|
use crate::nip34::Nip34Binding;
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
|
|
||||||
// Caps nesting so pathological trees can't stall the scan.
|
/// Caps nesting so pathological trees can't stall the scan.
|
||||||
const SCAN_MAX_DEPTH: usize = 12;
|
const SCAN_MAX_DEPTH: usize = 12;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|||||||
@@ -4,21 +4,51 @@ use std::path::{Path, PathBuf};
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use gix::progress::Discard;
|
use gix::progress::Discard;
|
||||||
|
|
||||||
|
use crate::GixResultExt as _;
|
||||||
use crate::history::FileCommit;
|
use crate::history::FileCommit;
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
|
|
||||||
|
pub struct WorktreeSnapshot {
|
||||||
|
pub entries: Vec<PathBuf>,
|
||||||
|
pub readme_path: Option<PathBuf>,
|
||||||
|
pub readme: Option<Vec<u8>>,
|
||||||
|
// `None` when detached, for example on a tag.
|
||||||
|
pub current_branch: Option<String>,
|
||||||
|
pub head_commit: Option<FileCommit>,
|
||||||
|
pub branches: Vec<String>,
|
||||||
|
pub tags: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorktreeSnapshot {
|
||||||
|
fn new(
|
||||||
|
entries: Vec<PathBuf>,
|
||||||
|
readme_path: Option<PathBuf>,
|
||||||
|
readme: Option<Vec<u8>>,
|
||||||
|
current_branch: Option<String>,
|
||||||
|
head_commit: Option<FileCommit>,
|
||||||
|
branches: Vec<String>,
|
||||||
|
tags: Vec<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
entries,
|
||||||
|
readme_path,
|
||||||
|
readme,
|
||||||
|
current_branch,
|
||||||
|
head_commit,
|
||||||
|
branches,
|
||||||
|
tags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Repo {
|
impl Repo {
|
||||||
// Best-effort: any read failure is reported as clean.
|
|
||||||
pub fn is_dirty(&self) -> bool {
|
pub fn is_dirty(&self) -> bool {
|
||||||
// Tracked files, staged or not; untracked files are handled below.
|
|
||||||
match self.inner.is_dirty() {
|
match self.inner.is_dirty() {
|
||||||
Ok(true) => return true,
|
Ok(true) => return true,
|
||||||
Ok(false) => {}
|
Ok(false) => {}
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Untracked files surface as `DirectoryContents` items of the
|
|
||||||
// index-vs-worktree walk; tracked files only appear there when modified.
|
|
||||||
let Ok(platform) = self.inner.status(Discard) else {
|
let Ok(platform) = self.inner.status(Discard) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@@ -39,7 +69,6 @@ impl Repo {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Best-effort: 0 when the range cannot be computed.
|
|
||||||
pub fn commits_ahead(&self, base: &str, branch: &str) -> u32 {
|
pub fn commits_ahead(&self, base: &str, branch: &str) -> u32 {
|
||||||
let (Some(base), Some(branch)) = (self.resolve_commit(base), self.resolve_commit(branch))
|
let (Some(base), Some(branch)) = (self.resolve_commit(base), self.resolve_commit(branch))
|
||||||
else {
|
else {
|
||||||
@@ -53,13 +82,10 @@ impl Repo {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accepts full refs or the bare branch names callers pass; `gix`'s
|
|
||||||
// revision parser already applies git's ref DWIM.
|
|
||||||
fn resolve_commit<'a>(&'a self, rev: &str) -> Option<gix::Id<'a>> {
|
fn resolve_commit<'a>(&'a self, rev: &str) -> Option<gix::Id<'a>> {
|
||||||
self.inner.rev_parse_single(rev.as_bytes()).ok()
|
self.inner.rev_parse_single(rev.as_bytes()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The `.git` directory is skipped.
|
|
||||||
pub fn entries(&self) -> Result<Vec<PathBuf>> {
|
pub fn entries(&self) -> Result<Vec<PathBuf>> {
|
||||||
let workdir = self.inner.workdir().context("repository has no worktree")?;
|
let workdir = self.inner.workdir().context("repository has no worktree")?;
|
||||||
|
|
||||||
@@ -74,7 +100,6 @@ impl Repo {
|
|||||||
Ok(entries.into_iter().map(|(path, _)| path).collect())
|
Ok(entries.into_iter().map(|(path, _)| path).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
// `Ok(None)` when the path is missing or not a regular file.
|
|
||||||
pub fn read(&self, rel: &Path) -> Result<Option<Vec<u8>>> {
|
pub fn read(&self, rel: &Path) -> Result<Option<Vec<u8>>> {
|
||||||
let workdir = self.inner.workdir().context("repository has no worktree")?;
|
let workdir = self.inner.workdir().context("repository has no worktree")?;
|
||||||
let path = workdir.join(rel);
|
let path = workdir.join(rel);
|
||||||
@@ -87,7 +112,6 @@ impl Repo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Falls back to any other file whose name starts with `readme`.
|
|
||||||
pub fn find_readme(&self) -> Result<Option<PathBuf>> {
|
pub fn find_readme(&self) -> Result<Option<PathBuf>> {
|
||||||
let Some(workdir) = self.inner.workdir() else {
|
let Some(workdir) = self.inner.workdir() else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -129,18 +153,17 @@ impl Repo {
|
|||||||
Some(path) => self.read(path)?,
|
Some(path) => self.read(path)?,
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
Ok(WorktreeSnapshot {
|
Ok(WorktreeSnapshot::new(
|
||||||
entries: self.entries()?,
|
self.entries()?,
|
||||||
readme_path,
|
readme_path,
|
||||||
readme,
|
readme,
|
||||||
current_branch: self.current_branch(),
|
self.current_branch(),
|
||||||
head_commit: self.head_commit().unwrap_or(None),
|
self.head_commit().unwrap_or(None),
|
||||||
branches: self.branches().unwrap_or_default(),
|
self.branches().unwrap_or_default(),
|
||||||
tags: self.tags().unwrap_or_default(),
|
self.tags().unwrap_or_default(),
|
||||||
})
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HEAD stays attached to the branch.
|
|
||||||
pub fn checkout_branch(&self, name: &str) -> Result<()> {
|
pub fn checkout_branch(&self, name: &str) -> Result<()> {
|
||||||
let full = format!("refs/heads/{name}");
|
let full = format!("refs/heads/{name}");
|
||||||
|
|
||||||
@@ -164,7 +187,6 @@ impl Repo {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// HEAD becomes detached at the tagged commit.
|
|
||||||
pub fn checkout_tag(&self, name: &str) -> Result<()> {
|
pub fn checkout_tag(&self, name: &str) -> Result<()> {
|
||||||
let full = format!("refs/tags/{name}");
|
let full = format!("refs/tags/{name}");
|
||||||
|
|
||||||
@@ -196,8 +218,6 @@ impl Repo {
|
|||||||
|
|
||||||
let mut index = self.inner.index_from_tree(tree)?;
|
let mut index = self.inner.index_from_tree(tree)?;
|
||||||
|
|
||||||
// Files the previous index tracked but `tree` no longer contains are
|
|
||||||
// removed, like git deleting files that vanish between branches.
|
|
||||||
if let Ok(previous) = self.inner.index_or_empty() {
|
if let Ok(previous) = self.inner.index_or_empty() {
|
||||||
let keep: HashSet<PathBuf> = index
|
let keep: HashSet<PathBuf> = index
|
||||||
.entries()
|
.entries()
|
||||||
@@ -244,9 +264,12 @@ impl Repo {
|
|||||||
&bytes,
|
&bytes,
|
||||||
&gix::interrupt::IS_INTERRUPTED,
|
&gix::interrupt::IS_INTERRUPTED,
|
||||||
options,
|
options,
|
||||||
)?;
|
)
|
||||||
|
.into_anyhow()?;
|
||||||
|
|
||||||
index.write(gix::index::write::Options::default())?;
|
index
|
||||||
|
.write(gix::index::write::Options::default())
|
||||||
|
.into_anyhow()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -301,14 +324,3 @@ impl Repo {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WorktreeSnapshot {
|
|
||||||
pub entries: Vec<PathBuf>,
|
|
||||||
pub readme_path: Option<PathBuf>,
|
|
||||||
pub readme: Option<Vec<u8>>,
|
|
||||||
// `None` when detached, for example on a tag.
|
|
||||||
pub current_branch: Option<String>,
|
|
||||||
pub head_commit: Option<FileCommit>,
|
|
||||||
pub branches: Vec<String>,
|
|
||||||
pub tags: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use dock::{DockArea, DockEvent, DockLayout, DockPlacement, SignedDockSkin, panel
|
|||||||
use gpui::prelude::*;
|
use gpui::prelude::*;
|
||||||
use gpui::{Context, Entity, Render, Subscription, Window, div, px};
|
use gpui::{Context, Entity, Render, Subscription, Window, div, px};
|
||||||
use gpui_component::notification::NotificationType;
|
use gpui_component::notification::NotificationType;
|
||||||
use gpui_component::{Root, StyledExt, Theme, WindowExt};
|
use gpui_component::{StyledExt, Theme, WindowExt};
|
||||||
use settings::{AppearanceMode, SettingsStore};
|
use settings::{AppearanceMode, SettingsStore};
|
||||||
use signed_state::{Backend, BackendEvent};
|
use signed_state::{Backend, BackendEvent};
|
||||||
|
|
||||||
@@ -106,17 +106,12 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Workspace {
|
impl Render for Workspace {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||||
let dialog_layer = Root::render_dialog_layer(window, cx);
|
|
||||||
let notification_layer = Root::render_notification_layer(window, cx);
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id("workspace")
|
.id("workspace")
|
||||||
.v_flex()
|
.v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.relative()
|
.relative()
|
||||||
.child(self.dock.clone())
|
.child(self.dock.clone())
|
||||||
.children(notification_layer)
|
|
||||||
.children(dialog_layer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user