Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

- Prevent standalone HTML exports from publishing dead forge source links for
local-only graph commits. Immutable links now require the recorded commit to
be reachable from a local `origin` remote-tracking ref.

- Make community detail graphs easier to scan in both exported HTML and VS
Code by grouping node kinds into accessible color-and-shape families,
coloring edges by relationship purpose while retaining confidence strokes,
Expand Down
2 changes: 1 addition & 1 deletion crates/compass-cli/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ const PAGES: &[Page] = &[
"export html",
"Generate the interactive graph HTML report",
["compass export html [OPTIONS]"],
"Options:\n --graph <PATH> Graph JSON [default: compass-out/graph.json]\n --labels <PATH> Community-label JSON\n --node-limit <N> Maximum nodes rendered [default: 5000]\n --no-viz Skip visualization output\n\nExamples:\n compass export html\n compass export html --node-limit 2000\n\nNotes:\n Large exports embed a bounded set of complete community details; use VS Code or export json --community ID for an omitted detail. Source actions open immutable commit links for recognized GitHub, GitLab, and Bitbucket origins when the graph records a full source commit. Interactive terminals ask before opening the generated HTML; scripts and --no-viz never prompt or open a browser."
"Options:\n --graph <PATH> Graph JSON [default: compass-out/graph.json]\n --labels <PATH> Community-label JSON\n --node-limit <N> Maximum nodes rendered [default: 5000]\n --no-viz Skip visualization output\n\nExamples:\n compass export html\n compass export html --node-limit 2000\n\nNotes:\n Large exports embed a bounded set of complete community details; use VS Code or export json --community ID for an omitted detail. Source actions open immutable commit links for recognized GitHub, GitLab, and Bitbucket origins when the graph records a full source commit reachable from a local origin-tracking ref. Interactive terminals ask before opening the generated HTML; scripts and --no-viz never prompt or open a browser."
),
page!(
"export callflow-html",
Expand Down
21 changes: 20 additions & 1 deletion crates/compass-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4235,7 +4235,26 @@ fn export_source_navigation(inputs: &ExportInputs, graph_path: &Path) -> Option<
if remote.code != 0 {
return None;
}
SourceNavigation::from_git_remote(remote.stdout.trim(), revision)
let navigation = SourceNavigation::from_git_remote(remote.stdout.trim(), revision)?;
let remote_reachability = SystemRunner
.run(
"git",
&[
"-C".to_owned(),
root.to_owned(),
"for-each-ref".to_owned(),
"--count=1".to_owned(),
"--format=%(refname)".to_owned(),
format!("--contains={revision}"),
"refs/remotes/origin".to_owned(),
],
GIT_SOURCE_LINK_TIMEOUT,
)
.ok()?;
if remote_reachability.code != 0 || remote_reachability.stdout.trim().is_empty() {
return None;
}
Some(navigation)
}

#[allow(clippy::too_many_arguments)]
Expand Down
93 changes: 93 additions & 0 deletions crates/compass-cli/tests/viewer_export_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,11 @@ fn html_export_embeds_one_workbench_for_multiple_views() -> Result<(), Box<dyn E
.output()?;
assert!(source_commit.status.success());
let source_commit = String::from_utf8(source_commit.stdout)?.trim().to_owned();
let remote_ref = Command::new("git")
.args(["update-ref", "refs/remotes/origin/main", &source_commit])
.current_dir(directory.path())
.status()?;
assert!(remote_ref.success());
let graph = directory.path().join("graph.json");
let html = directory.path().join("review.html");
std::fs::write(
Expand Down Expand Up @@ -482,6 +487,94 @@ fn html_export_embeds_one_workbench_for_multiple_views() -> Result<(), Box<dyn E
Ok(())
}

#[test]
fn html_export_omits_dead_links_for_local_only_commits() -> Result<(), Box<dyn Error>> {
let directory = tempfile::tempdir()?;
let initialized = Command::new("git")
.args(["init", "--quiet"])
.current_dir(directory.path())
.status()?;
assert!(initialized.success());
let remote = Command::new("git")
.args([
"remote",
"add",
"origin",
"https://github.com/acme/compass.git",
])
.current_dir(directory.path())
.status()?;
assert!(remote.success());
std::fs::create_dir_all(directory.path().join("src"))?;
std::fs::write(directory.path().join("src/lib.rs"), "fn caller() {}\n")?;
let added = Command::new("git")
.args(["add", "src/lib.rs"])
.current_dir(directory.path())
.status()?;
assert!(added.success());
let committed = Command::new("git")
.args([
"-c",
"user.name=Compass Test",
"-c",
"[email protected]",
"commit",
"--quiet",
"-m",
"local-only fixture",
])
.current_dir(directory.path())
.status()?;
assert!(committed.success());
let source_commit = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(directory.path())
.output()?;
assert!(source_commit.status.success());
let source_commit = String::from_utf8(source_commit.stdout)?.trim().to_owned();
let graph = directory.path().join("graph.json");
let html = directory.path().join("review.html");
std::fs::write(
&graph,
serde_json::to_vec(&json!({
"directed": true,
"multigraph": false,
"graph": {
"schema":"compass.graph/1",
"build":{"sourceCommit":source_commit}
},
"nodes": [
{"id":"caller","label":"caller","kind":"function","community":0,"source_file":"src/lib.rs","line_start":1}
],
"links": []
}))?,
)?;
let output = support::compass_command()
.args([
"export",
"html",
"--graph",
graph.to_string_lossy().as_ref(),
"--output",
html.to_string_lossy().as_ref(),
"--code-graph",
])
.current_dir(directory.path())
.output()?;
assert_eq!(
output.status.code(),
Some(0),
"{}",
String::from_utf8_lossy(&output.stderr)
);
let document = std::fs::read_to_string(html)?;
assert!(!document.contains("id=\"compass-source-navigation\""));
assert!(!document.contains(&format!(
"https://github.com/acme/compass/blob/{source_commit}/"
)));
Ok(())
}

#[test]
fn export_rejects_unknown_and_view_incompatible_options_before_io() -> Result<(), Box<dyn Error>> {
let unknown = support::compass_command()
Expand Down
8 changes: 5 additions & 3 deletions docs/reference/outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,11 @@ file and highlights its recorded lines in the VS Code extension. A standalone
HTML export instead opens an immutable forge permalink when all required
evidence is available: the graph records a full source commit, the graph is
inside a Git worktree with a recognized `origin`, and that origin is GitHub,
GitLab, or Bitbucket. The link uses the graph's commit rather than a mutable
branch; historical comparisons use the commit for the selected side. If any
part of that evidence is absent or unsafe, the viewer does not invent a link.
GitLab, or Bitbucket. The recorded commit must also be reachable from a local
`origin` remote-tracking ref, preventing a local-only object from becoming a
dead forge URL. The link uses the graph's commit rather than a mutable branch;
historical comparisons use the commit for the selected side. If any part of
that evidence is absent or unsafe, the viewer does not invent a link.
No repository URL is added to `compass.viewer.workbench/1` or
`workbench-json`; standalone HTML carries the optional presentation metadata
separately.
Expand Down
Loading