strip redundant doc comments from signed_core and signed_git
This commit is contained in:
@@ -18,11 +18,8 @@ use crate::repos::RepoListStore;
|
||||
|
||||
const REFRESH_DEBOUNCE: Duration = Duration::from_millis(300);
|
||||
|
||||
/// How often the statuses are recomputed against the local refs.
|
||||
const LOCAL_POLL: Duration = Duration::from_secs(2);
|
||||
/// How often a full pass refreshes the remotes while any repository panel is open.
|
||||
const STATUS_POLL: Duration = Duration::from_secs(15);
|
||||
/// Remote refresh interval for the `ready to push` badges of the user's own repositories.
|
||||
const PUSH_POLL: Duration = Duration::from_secs(60);
|
||||
|
||||
const MAX_STATUS_CHECKOUTS: usize = 8;
|
||||
@@ -31,63 +28,37 @@ struct GlobalCheckoutsStore(Entity<CheckoutsStore>);
|
||||
|
||||
impl Global for GlobalCheckoutsStore {}
|
||||
|
||||
/// One associated local checkout of a repository.
|
||||
///
|
||||
/// Carries the git facts needed to suggest a pull request.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CheckoutStatus {
|
||||
pub path: PathBuf,
|
||||
/// The branch checked out. A detached checkout is idle and yields no status.
|
||||
// A detached checkout is idle and yields no status.
|
||||
pub branch: String,
|
||||
/// Commit the branch points at, for tip-based PR dedupe.
|
||||
// For tip-based PR dedupe.
|
||||
pub head: String,
|
||||
/// What the branch is compared against.
|
||||
///
|
||||
/// It is `refs/remotes/origin/<branch>`, else `origin/HEAD` for new branches.
|
||||
// `refs/remotes/origin/<branch>`, else `origin/HEAD` for new branches.
|
||||
pub base: String,
|
||||
/// Commits in `base..branch`.
|
||||
///
|
||||
/// Zero-ahead checkouts are dropped, so this is always above zero.
|
||||
// Zero-ahead checkouts are dropped, so always above zero.
|
||||
pub ahead: u32,
|
||||
}
|
||||
|
||||
/// A remembered record, with the address already parsed.
|
||||
struct Remembered {
|
||||
path: PathBuf,
|
||||
addr: RepoAddr,
|
||||
last_used: u64,
|
||||
}
|
||||
|
||||
/// Global store of local-checkout associations and per-checkout statuses.
|
||||
pub struct CheckoutsStore {
|
||||
/// Checkout paths per announced repository.
|
||||
by_repo: HashMap<RepoAddr, Vec<PathBuf>>,
|
||||
/// Ready-to-contribute statuses of the requested repositories.
|
||||
///
|
||||
/// Those are the repository detail panels currently open.
|
||||
statuses: HashMap<RepoAddr, Vec<CheckoutStatus>>,
|
||||
/// Repositories whose statuses are recomputed on every input change.
|
||||
///
|
||||
/// Those are the repository detail panels currently open.
|
||||
status_requested: HashSet<RepoAddr>,
|
||||
/// Repositories whose `ready to push` statuses are recomputed on the same cycle.
|
||||
///
|
||||
/// The sidebar rows of the user's own repositories and their detail panels.
|
||||
push_requested: HashSet<RepoAddr>,
|
||||
/// The ready-to-push statuses of the requested own repositories.
|
||||
push_statuses: HashMap<RepoAddr, Vec<CheckoutStatus>>,
|
||||
/// Last announced head branch per requested repository.
|
||||
///
|
||||
/// A recompute defaults the base the same way.
|
||||
requested_head: HashMap<RepoAddr, Option<String>>,
|
||||
refresh: RefreshGate,
|
||||
/// True while the timer between a scheduled refresh and its run is pending.
|
||||
debounce_pending: bool,
|
||||
local_pending: bool,
|
||||
/// When the last full pass (with a remote refresh) completed.
|
||||
///
|
||||
/// The local pass runs a full pass again once this is older than the
|
||||
/// reconciliation cadence, so remote moves still land.
|
||||
// The local pass runs a full pass again once this is older than the
|
||||
// reconciliation cadence, so remote moves still land.
|
||||
last_full_sync: Option<Instant>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
@@ -189,17 +160,10 @@ impl CheckoutsStore {
|
||||
});
|
||||
}
|
||||
|
||||
/// The associated checkouts of `addr`, freshest first.
|
||||
///
|
||||
/// Empty when none are known or the resolution has not run yet.
|
||||
pub fn associations_of(&self, addr: &RepoAddr) -> Vec<PathBuf> {
|
||||
self.by_repo.get(addr).cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Ask for the `ready to contribute` statuses of `addr` to stay current.
|
||||
/// Called while the repository's detail panel is open.
|
||||
///
|
||||
/// `announced_head` is the announced HEAD branch, used to default the base.
|
||||
pub fn request_statuses(
|
||||
&mut self,
|
||||
addr: &RepoAddr,
|
||||
@@ -213,26 +177,18 @@ impl CheckoutsStore {
|
||||
self.refresh(cx);
|
||||
}
|
||||
|
||||
/// The ready-to-contribute statuses of `addr`.
|
||||
///
|
||||
/// Empty while none are known or nothing is ahead.
|
||||
pub fn ready_statuses_of(&self, addr: &RepoAddr) -> Vec<CheckoutStatus> {
|
||||
self.statuses.get(addr).cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Ask for the `ready to push` statuses of `addr` to stay current.
|
||||
pub fn request_push_statuses(&mut self, addr: &RepoAddr, cx: &mut Context<Self>) {
|
||||
self.push_requested.insert(addr.clone());
|
||||
self.refresh(cx);
|
||||
}
|
||||
|
||||
/// The checkout at `path` was just pushed to the remote.
|
||||
///
|
||||
/// Its ready-to-push status is obsolete. Drop it from the cached statuses
|
||||
/// and notify observers right away, so the sidebar badge and the push
|
||||
/// banner update immediately instead of waiting for the next background
|
||||
/// pass, which re-scans and re-fetches the remote. The debounced refresh
|
||||
/// reconciles the remaining checkouts of the repository afterwards.
|
||||
// Drop the stale ready-to-push status and notify observers right away, so
|
||||
// the sidebar badge updates immediately instead of waiting for the next
|
||||
// background pass. The debounced refresh reconciles the remaining checkouts.
|
||||
pub fn checkout_pushed(&mut self, addr: &RepoAddr, path: &Path, cx: &mut Context<Self>) {
|
||||
let mut removed = false;
|
||||
|
||||
@@ -255,9 +211,6 @@ impl CheckoutsStore {
|
||||
self.request_push_statuses(addr, cx);
|
||||
}
|
||||
|
||||
/// The ready-to-push statuses of `addr`.
|
||||
///
|
||||
/// Empty while none are known or nothing is unpushed.
|
||||
pub fn push_statuses_of(&self, addr: &RepoAddr) -> Vec<CheckoutStatus> {
|
||||
self.push_statuses.get(addr).cloned().unwrap_or_default()
|
||||
}
|
||||
@@ -269,7 +222,6 @@ impl CheckoutsStore {
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Re-resolve the associations and the requested statuses.
|
||||
pub fn refresh(&mut self, cx: &mut Context<Self>) {
|
||||
if self.debounce_pending || self.refresh.request() != RefreshRequest::Schedule {
|
||||
return;
|
||||
@@ -284,7 +236,6 @@ impl CheckoutsStore {
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// One full resolve and apply cycle, the debounced entry point.
|
||||
fn run_refresh(&mut self, cx: &mut Context<Self>) {
|
||||
self.debounce_pending = false;
|
||||
self.refresh.begin();
|
||||
@@ -325,14 +276,11 @@ impl CheckoutsStore {
|
||||
let poll = !self.status_requested.is_empty() || !self.push_requested.is_empty();
|
||||
|
||||
let work = cx.background_spawn(async move {
|
||||
// Read the git facts of every scanned repository off the main thread.
|
||||
//
|
||||
// The facts are the origin URL and the root commit, both CLI reads.
|
||||
let mut facts: Vec<(PathBuf, Option<String>, Option<String>)> = Vec::new();
|
||||
for scanned in scanned.iter() {
|
||||
let path = &scanned.path;
|
||||
|
||||
// The browser's mirror clones share the announce URLs and EUCs. They are not user checkouts.
|
||||
// The browser's mirror clones are not user checkouts.
|
||||
if cache_root
|
||||
.as_ref()
|
||||
.is_some_and(|root| path.starts_with(root))
|
||||
@@ -353,7 +301,6 @@ impl CheckoutsStore {
|
||||
let associations =
|
||||
CheckoutsStore::resolve_associations(&remembered, &facts, announcements.iter());
|
||||
|
||||
// Missing directories are stale records, drop them.
|
||||
let associations: HashMap<RepoAddr, Vec<PathBuf>> = associations
|
||||
.into_iter()
|
||||
.map(|(addr, paths)| (addr, paths.into_iter().filter(|p| p.is_dir()).collect()))
|
||||
@@ -369,7 +316,6 @@ impl CheckoutsStore {
|
||||
let (associations, statuses, push_statuses) = match work.await {
|
||||
Ok(results) => results,
|
||||
Err(_) => {
|
||||
// Git reads are best-effort, keep the last results.
|
||||
return this.update(cx, |this, cx| {
|
||||
this.refresh.abort();
|
||||
if poll {
|
||||
@@ -388,8 +334,7 @@ impl CheckoutsStore {
|
||||
this.statuses = statuses;
|
||||
this.push_statuses = push_statuses;
|
||||
|
||||
// Notify only when something actually changed, so observers
|
||||
// skip the no-op heartbeats.
|
||||
// Notify only when something actually changed.
|
||||
if associations_changed || statuses_changed || push_statuses_changed {
|
||||
cx.notify();
|
||||
}
|
||||
@@ -402,9 +347,6 @@ impl CheckoutsStore {
|
||||
this.update(cx, |this, cx| this.refresh(cx))?;
|
||||
}
|
||||
|
||||
// Restart the fast local pass so the freshly resolved
|
||||
// associations drive it. The pass itself decides when the next
|
||||
// full pass runs.
|
||||
this.update(cx, |this, cx| {
|
||||
if poll {
|
||||
this.schedule_local_pass(cx);
|
||||
@@ -416,7 +358,6 @@ impl CheckoutsStore {
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Schedule the fast local status pass, unless one is already pending.
|
||||
fn schedule_local_pass(&mut self, cx: &mut Context<Self>) {
|
||||
if self.local_pending {
|
||||
return;
|
||||
@@ -433,20 +374,17 @@ impl CheckoutsStore {
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// The fast local status pass.
|
||||
fn local_tick(&mut self, cx: &mut Context<Self>) {
|
||||
// Nothing watched: the chain idles out until a new request restarts it.
|
||||
// Nothing watched: the pass idles until a new request restarts it.
|
||||
if self.status_requested.is_empty() && self.push_requested.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// A full pass or a fresh request covers this tick, skip it.
|
||||
if self.refresh.running() || self.debounce_pending {
|
||||
self.schedule_local_pass(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open panels get the faster remote cadence.
|
||||
let cadence = if self.status_requested.is_empty() {
|
||||
PUSH_POLL
|
||||
} else {
|
||||
@@ -467,7 +405,6 @@ impl CheckoutsStore {
|
||||
self.schedule_local_pass(cx);
|
||||
}
|
||||
|
||||
/// Recompute the requested statuses against the tracking refs only.
|
||||
fn run_local_statuses(&mut self, cx: &mut Context<Self>) {
|
||||
let associations = self.by_repo.clone();
|
||||
|
||||
@@ -492,13 +429,12 @@ impl CheckoutsStore {
|
||||
|
||||
let task: gpui::Task<Result<(), Error>> = cx.spawn(async move |this, cx| {
|
||||
let Ok((statuses, push_statuses)) = work.await else {
|
||||
// Git reads are best-effort, keep the last results.
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
// A full pass or a fresh request will apply fresher data
|
||||
// (the tracking refs move only when a full pass fetches).
|
||||
// The tracking refs move only when a full pass fetches; a full
|
||||
// pass or a fresh request will apply fresher data.
|
||||
if this.refresh.running() || this.debounce_pending {
|
||||
return;
|
||||
}
|
||||
@@ -593,7 +529,7 @@ impl CheckoutsStore {
|
||||
})
|
||||
}
|
||||
|
||||
/// The `ready to push` status of one checkout of the user's own repository.
|
||||
// Never fetches the checked-out refs; reads the tracking refs as-is.
|
||||
fn checkout_push_status(path: &Path, fetch: bool) -> Option<CheckoutStatus> {
|
||||
let repo = Repo::try_open(path)?;
|
||||
if repo.is_dirty() {
|
||||
@@ -611,8 +547,8 @@ impl CheckoutsStore {
|
||||
|
||||
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.
|
||||
// A branch never fetched or pushed yet compares against the remote
|
||||
// HEAD, the fork point in practice.
|
||||
let base = if repo.ref_exists(&remote) {
|
||||
remote
|
||||
} else if repo.ref_exists("refs/remotes/origin/HEAD") {
|
||||
@@ -632,7 +568,6 @@ impl CheckoutsStore {
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute the requested statuses against the checkout paths of `associations`.
|
||||
fn compute_statuses(
|
||||
associations: &HashMap<RepoAddr, Vec<PathBuf>>,
|
||||
requested: &[(RepoAddr, Option<String>)],
|
||||
@@ -716,7 +651,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn same_repo_url_ignores_the_transport_scheme() {
|
||||
// grasp announce vs https origin, with and without `.git`.
|
||||
assert!(same_repo_url(
|
||||
"grasp://relay.ngit.dev/npub1test/repo",
|
||||
"https://relay.ngit.dev/npub1test/repo.git"
|
||||
@@ -725,7 +659,6 @@ mod tests {
|
||||
"ws://localhost:8080/npub1test/repo",
|
||||
"http://localhost:8080/npub1test/repo"
|
||||
));
|
||||
// The port and the path matter.
|
||||
assert!(!same_repo_url(
|
||||
"wss://localhost:8081/npub1test/repo",
|
||||
"wss://localhost:8080/npub1test/repo"
|
||||
@@ -734,7 +667,6 @@ mod tests {
|
||||
"wss://host/npub1test/repo",
|
||||
"wss://host/npub1other/repo"
|
||||
));
|
||||
// Unparseable URLs compare literally.
|
||||
assert!(same_repo_url("/local/path", "/local/path"));
|
||||
assert!(!same_repo_url("/local/path", "/local/other"));
|
||||
}
|
||||
@@ -762,7 +694,6 @@ mod tests {
|
||||
run(&["commit", "-m", message]);
|
||||
};
|
||||
|
||||
// A feature branch ahead of main, ready to contribute.
|
||||
run(&["checkout", "-b", "feature"]);
|
||||
std::fs::write(path.join("feature.txt"), "x\n").expect("write");
|
||||
commit("feature work");
|
||||
@@ -772,22 +703,16 @@ mod tests {
|
||||
assert_eq!(status.ahead, 1);
|
||||
assert_eq!(status.head.len(), 40);
|
||||
|
||||
// Dirty worktrees are never suggested.
|
||||
std::fs::write(path.join("uncommitted.txt"), "y\n").expect("write");
|
||||
assert!(CheckoutsStore::checkout_status(&path, Some("main")).is_none());
|
||||
run(&["checkout", "--", "."]);
|
||||
|
||||
// Even on main, nothing to propose.
|
||||
run(&["checkout", "main"]);
|
||||
assert_eq!(CheckoutsStore::checkout_status(&path, Some("main")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checkout_push_status_counts_unpushed_commits_only() {
|
||||
// The `grasp remote` is a plain repository the checkout clones from.
|
||||
// Its origin URL is a local path, so the whole cycle runs offline.
|
||||
// Git refuses pushes to a checked-out branch by default.
|
||||
// Act like a grasp server and allow them.
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let remote = dir.path().join("remote");
|
||||
Repo::init(&remote, "My Repo", "").expect("init");
|
||||
@@ -827,7 +752,6 @@ mod tests {
|
||||
// A fresh clone has nothing to push.
|
||||
assert_eq!(CheckoutsStore::checkout_push_status(&checkout, true), None);
|
||||
|
||||
// One local commit, ready to push, counted against the remote.
|
||||
std::fs::write(checkout.join("work.txt"), "x\n").expect("write");
|
||||
run(&["add", "-A"]);
|
||||
run(&["commit", "-m", "local work"]);
|
||||
@@ -837,17 +761,12 @@ mod tests {
|
||||
assert_eq!(status.ahead, 1);
|
||||
assert_eq!(status.head.len(), 40);
|
||||
|
||||
// The local-only pass reads the tracking refs, no fetch needed:
|
||||
// a commit lands locally long before the remote is reconciled.
|
||||
let local = CheckoutsStore::checkout_push_status(&checkout, false).expect("local status");
|
||||
assert_eq!(local.ahead, 1);
|
||||
|
||||
// After the push the same commit is on the remote, idle again.
|
||||
run(&["push", "origin", "main"]);
|
||||
assert_eq!(CheckoutsStore::checkout_push_status(&checkout, true), None);
|
||||
|
||||
// A commit made by someone else on the remote must not count as local work.
|
||||
// It is behind, not ahead.
|
||||
let remote_run = |args: &[&str]| {
|
||||
let status = Command::new("git")
|
||||
.current_dir(&remote)
|
||||
|
||||
Reference in New Issue
Block a user