Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,10 +552,11 @@ Exit status is meaningful:
| `2` | corruption found: something referenced is missing, or an entry could not be identified |
| `3` | **the check could not be completed**, so the report says nothing about the repository |

A usage error — a flag that does not exist, a value that will not parse, an unknown
subcommand — also exits `3`, for every `gfs` command and not just this one. It is
the same statement: the command did not run, so nothing here describes your
repository. `--help` and `--version` exit `0`; they are requests, not errors.
A usage error — a flag that does not exist, a value that will not parse, an argument
that is not valid UTF-8, an unknown subcommand — also exits `3`, for every `gfs`
command and not just this one. It is the same statement: the command did not run, so
nothing here describes your repository. `--help` and `--version` exit `0`; they are
requests, not errors.

`3` is the one to handle first. It is not a statement about the repository — an
unreadable object, a `refs/heads` entry that could not be read or is absent, a
Expand Down
69 changes: 35 additions & 34 deletions crates/applications/cli/src/commands/cmd_branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! - `gfs branch <name> <start>` — create a new branch at a specific commit/branch
//! - `gfs branch -d <name>` — delete a branch

use crate::println_safe;
use std::path::PathBuf;
use std::sync::Arc;

Expand Down Expand Up @@ -72,13 +73,13 @@ fn list_branches(repo_path: &std::path::Path, json_output: bool) -> Result<()> {
let branches = list_branch_tips(repo_path, false)?;
if branches.is_empty() {
if json_output {
println!(
println_safe!(
"{}",
serde_json::to_string_pretty(&json!({ "branches": [] }))?
);
)?;
return Ok(());
}
println!(" (no branches)");
println_safe!(" (no branches)")?;
return Ok(());
}

Expand Down Expand Up @@ -115,10 +116,10 @@ fn list_branches(repo_path: &std::path::Path, json_output: bool) -> Result<()> {
})
})
.collect();
println!(
println_safe!(
"{}",
serde_json::to_string_pretty(&json!({ "branches": out }))?
);
)?;
return Ok(());
}

Expand All @@ -135,15 +136,15 @@ fn list_branches(repo_path: &std::path::Path, json_output: bool) -> Result<()> {
};

if *name == current {
println!(
println_safe!(
" {} {} {} {}",
gold("*"),
green(name),
dimmed(short_hash),
subject
);
)?;
} else {
println!(" {} {} {}", cyan(name), dimmed(short_hash), subject);
println_safe!(" {} {} {}", cyan(name), dimmed(short_hash), subject)?;
}
}

Expand Down Expand Up @@ -201,26 +202,26 @@ async fn create_branch(
let start_label = start_point.unwrap_or("HEAD");

if json_output {
println!(
println_safe!(
"{}",
serde_json::to_string_pretty(&json!({
"action": "create",
"branch": name,
"hash": commit_hash,
"start_point": start_label,
}))?
);
)?;
return Ok(());
}

let short_hash = &commit_hash[..7.min(commit_hash.len())];
println!(
println_safe!(
"{} Created branch '{}' at {} ({})",
green("✓"),
cyan(name),
start_label,
dimmed(short_hash)
);
)?;
}

Ok(())
Expand Down Expand Up @@ -277,12 +278,12 @@ fn delete_branch(repo_path: &std::path::Path, name: &str, json_output: bool) ->
// a repository that already carries one still can, which is why this guard
// exists as well as the refusal.
if workspace.exists() && name == DETACHED_WORKSPACE_SEGMENT {
println!(
println_safe!(
" its workspace path is {}, which is the directory holding every detached \
working copy — left in place rather than removed.\n Move anything you need \
out of it; `gfs destroy` reclaims it with the repository",
workspace.display()
);
)?;
} else if workspace.exists() {
#[cfg(unix)]
let _ = std::process::Command::new("chmod")
Expand Down Expand Up @@ -312,25 +313,25 @@ fn delete_branch(repo_path: &std::path::Path, name: &str, json_output: bool) ->
}

if json_output {
println!(
println_safe!(
"{}",
serde_json::to_string_pretty(&json!({
"action": "delete",
"branch": name,
"commit": deleted.commit_hash,
"recoverable": true,
}))?
);
)?;
return Ok(());
}

println!("{} Deleted branch '{}'", green("✓"), name);
println!(
println_safe!("{} Deleted branch '{}'", green("✓"), name)?;
println_safe!(
" {}",
dimmed(format!(
"restore with: gfs branch --restore {name} (restores committed work)"
))
);
)?;
Ok(())
}

Expand Down Expand Up @@ -403,25 +404,25 @@ fn list_deleted(repo_path: &std::path::Path, json_output: bool) -> Result<()> {
})
})
.collect();
println!(
println_safe!(
"{}",
serde_json::to_string_pretty(&json!({ "deleted": rows }))?
);
)?;
return Ok(());
}

if entries.is_empty() {
println!("{}", dimmed("No deleted branches are recoverable."));
println_safe!("{}", dimmed("No deleted branches are recoverable."))?;
return Ok(());
}

println!(
println_safe!(
"{}",
dimmed(format!(
"Recoverable for {} days after deletion:",
deleted_retention_days(repo_path)
))
);
)?;
// Entries are newest-first, so the first occurrence of a name is the one
// `--restore` would pick. Repeated deletions of the same name are shown
// rather than collapsed, but only one of them is actionable.
Expand All @@ -430,16 +431,16 @@ fn list_deleted(repo_path: &std::path::Path, json_output: bool) -> Result<()> {
let short: String = d.commit_hash.chars().take(7).collect();
let newest = !seen.contains(&d.name.as_str());
seen.push(d.name.as_str());
println!(
println_safe!(
" {} {} {}{}",
cyan(&d.name),
gold(&short),
dimmed(format_age(d.deleted_at_ms)),
if newest { "" } else { " (older deletion)" }
);
)?;
}
println!();
println!("{}", dimmed("restore with: gfs branch --restore <name>"));
println_safe!()?;
println_safe!("{}", dimmed("restore with: gfs branch --restore <name>"))?;
Ok(())
}

Expand Down Expand Up @@ -541,27 +542,27 @@ fn restore_branch(repo_path: &std::path::Path, name: &str, json_output: bool) ->
.with_context(|| format!("failed to restore branch '{}'", name))?;

if json_output {
println!(
println_safe!(
"{}",
serde_json::to_string_pretty(&json!({
"action": "restore",
"branch": restored.name,
"commit": restored.commit_hash,
}))?
);
)?;
return Ok(());
}

let short: String = restored.commit_hash.chars().take(7).collect();
println!(
println_safe!(
"{} Restored branch '{}' at {}",
green("\u{2713}"),
restored.name,
gold(&short)
);
println!(
)?;
println_safe!(
" {}",
dimmed(format!("check it out with: gfs checkout {}", restored.name))
);
)?;
Ok(())
}
13 changes: 7 additions & 6 deletions crates/applications/cli/src/commands/cmd_checkout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! When the repo has a compute container, the use case stops it before checkout
//! and starts (or recreates with the new workspace mount) after checkout.

use crate::println_safe;
use std::path::PathBuf;
use std::sync::Arc;

Expand Down Expand Up @@ -175,30 +176,30 @@ pub async fn checkout(
};

if json_output {
println!(
println_safe!(
"{}",
json!({
"hash": commit_hash,
"branch": create_branch.as_deref().unwrap_or(&revision),
"new_branch": create_branch.is_some(),
})
);
)?;
} else {
let short_hash = &commit_hash[..7.min(commit_hash.len())];
if let Some(ref name) = create_branch {
println!(
println_safe!(
"{} Switched to new branch '{}' ({})",
green("✓"),
green(name.trim()),
dimmed(short_hash)
);
)?;
} else {
println!(
println_safe!(
"{} Switched to {} ({})",
green("✓"),
cyan(revision.trim()),
dimmed(short_hash)
);
)?;
}
}
Ok(())
Expand Down
25 changes: 13 additions & 12 deletions crates/applications/cli/src/commands/cmd_clone.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::println_safe;
use std::path::PathBuf;
use std::sync::Arc;

Expand Down Expand Up @@ -64,7 +65,7 @@ pub async fn clone(
None => match use_case.detect_remote_version(&remote).await {
Ok(v) => {
if !json_output {
println!(" {} Detected remote version {}", green("✓"), cyan(&v));
println_safe!(" {} Detected remote version {}", green("✓"), cyan(&v))?;
}
Some(v)
}
Expand Down Expand Up @@ -110,25 +111,25 @@ pub async fn clone(
let output = use_case.run(&target_path, remote).await?;

if json_output && !snapshot {
println!(
println_safe!(
"{}",
serde_json::to_string_pretty(&json!({
"path": target_path.display().to_string(),
"remote": output.remote,
"mode": "lazy-clone",
}))?
);
)?;
} else if !json_output {
println!();
println!(
println_safe!()?;
println_safe!(
" {} Lazy clone ready from {}",
green("✓"),
cyan(output.remote)
);
println!(
)?;
println_safe!(
" {:<16} copy-on-read (data fetched on first read)",
dimmed("Mode")
);
)?;
}
if !output.stderr.is_empty() {
eprintln!("{}", output.stderr.trim_end());
Expand All @@ -140,8 +141,8 @@ pub async fn clone(
// clone, and the error says so instead of pretending the clone broke.
if snapshot {
if !json_output {
println!();
println!(" {} taking the snapshot (gfs freeze)...", dimmed("\u{b7}"));
println_safe!()?;
println_safe!(" {} taking the snapshot (gfs freeze)...", dimmed("\u{b7}"))?;
}
if let Err(e) = crate::commands::cmd_freeze::freeze(
Some(target_path.clone()),
Expand All @@ -158,11 +159,11 @@ pub async fn clone(
.into());
}
if !json_output {
println!(
println_safe!(
" {} Snapshot clone ready {}",
green("\u{2713}"),
dimmed("(a point in time, detached from its source)")
);
)?;
}
}

Expand Down
9 changes: 5 additions & 4 deletions crates/applications/cli/src/commands/cmd_commit.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::println_safe;
use std::path::PathBuf;
use std::sync::Arc;

Expand Down Expand Up @@ -115,23 +116,23 @@ async fn run(
.map_err(|e| anyhow::anyhow!("{e}"))?;

if json_output {
println!(
println_safe!(
"{}",
json!({
"hash": commit_hash,
"branch": branch,
"message": message,
})
);
)?;
} else {
let short = &commit_hash[..7.min(commit_hash.len())];
println!(
println_safe!(
"{} [{}] {} {}",
green("✓"),
cyan(&branch),
dimmed(short),
message
);
)?;
}
Ok(())
}
Loading