Skip to main content

cli/cli/commands/
oss.rs

1// SPDX-License-Identifier: Apache-2.0
2use std::process::Command;
3
4use anyhow::Result;
5use repo::Repository;
6use serde::Serialize;
7
8#[cfg(feature = "git-overlay")]
9use crate::cli::style;
10use crate::cli::{should_output_json, Cli};
11
12#[derive(Debug, Serialize)]
13struct VersionOutput {
14    version: &'static str,
15    profile: &'static str,
16    features: Vec<&'static str>,
17    git_version: Option<String>,
18    repository_capability: Option<String>,
19    repository_root: Option<String>,
20}
21
22#[cfg(feature = "git-overlay")]
23pub fn cmd_git_overlay_guide(cli: &Cli) -> Result<()> {
24    if should_output_json(cli, None) {
25        println!(
26            "{}",
27            serde_json::json!({
28                "topic": "git-overlay",
29                "summary": "Use Heddle beside Git: start lightweight, import history when you need it, and isolate risky work in threads.",
30                "steps": [
31                    "heddle status",
32                    "heddle bridge git import --ref <branch>",
33                    "heddle start <name> --path <dir>",
34                    "heddle merge <name>",
35                    "heddle sync"
36                ]
37            })
38        );
39        return Ok(());
40    }
41
42    println!("{}", style::bold("Git-overlay quick start"));
43    println!("Use Heddle beside Git first. Import deeper history only when a command needs it.");
44    println!();
45    println!("1. Inspect the repo");
46    println!("   {}", style::bold("heddle status"));
47    println!("2. Import the current branch when history-oriented commands ask for it");
48    println!(
49        "   {}",
50        style::bold("heddle bridge git import --ref <branch>")
51    );
52    println!("3. Start isolated work without disturbing your Git checkout");
53    println!(
54        "   {}",
55        style::bold("heddle start <topic> --path ../<topic>")
56    );
57    println!("4. Merge, resolve, and keep moving");
58    println!("   {}", style::bold("heddle merge <topic>"));
59    println!("   {}", style::bold("heddle continue"));
60    println!("5. Rejoin upstream Git");
61    println!("   {}", style::bold("heddle sync"));
62    println!();
63    println!("When unsure, run {}", style::bold("heddle doctor"));
64    Ok(())
65}
66
67pub fn cmd_version(cli: &Cli, verbose: bool) -> Result<()> {
68    if !verbose {
69        println!("heddle {}", env!("CARGO_PKG_VERSION"));
70        return Ok(());
71    }
72
73    let git_version = Command::new("git")
74        .arg("--version")
75        .output()
76        .ok()
77        .and_then(|output| {
78            output
79                .status
80                .success()
81                .then(|| String::from_utf8_lossy(&output.stdout).trim().to_string())
82        });
83
84    let repo = std::env::current_dir()
85        .ok()
86        .and_then(|cwd| Repository::open(&cwd).ok());
87    let repository_capability = repo
88        .as_ref()
89        .map(|repo| repo.capability_label().to_string());
90    let repository_root = repo.as_ref().map(|repo| repo.root().display().to_string());
91
92    let output = VersionOutput {
93        version: env!("CARGO_PKG_VERSION"),
94        profile: if cfg!(debug_assertions) {
95            "debug"
96        } else {
97            "release"
98        },
99        features: enabled_features(),
100        git_version,
101        repository_capability,
102        repository_root,
103    };
104
105    if should_output_json(cli, None) {
106        println!("{}", serde_json::to_string(&output)?);
107        return Ok(());
108    }
109
110    println!("Heddle {}", output.version);
111    println!("Build profile: {}", output.profile);
112    println!("Features: {}", output.features.join(", "));
113    if let Some(git_version) = &output.git_version {
114        println!("Git: {git_version}");
115    } else {
116        println!("Git: unavailable");
117    }
118    if let Some(capability) = &output.repository_capability {
119        println!("Repository: {capability}");
120    } else {
121        println!("Repository: not inside a Heddle/Git worktree");
122    }
123    if let Some(root) = &output.repository_root {
124        println!("Root: {root}");
125    }
126    Ok(())
127}
128
129// Each cfg-conditional push expands to either `features.push(...)` or
130// nothing depending on which features are enabled at compile time.
131// Clippy's `vec_init_then_push` would have us collapse these into a
132// single `vec![...]`, but that would force every variant to be either
133// always-present or unconditional. Suppress the lint at this site.
134#[allow(clippy::vec_init_then_push)]
135fn enabled_features() -> Vec<&'static str> {
136    let mut features = Vec::new();
137    #[cfg(feature = "weft-client")]
138    features.push("hosted-client");
139    #[cfg(feature = "ingest")]
140    features.push("ingest");
141    #[cfg(feature = "local")]
142    features.push("local");
143    #[cfg(feature = "mount")]
144    features.push("mount");
145    #[cfg(feature = "observability")]
146    features.push("observability");
147    #[cfg(feature = "s3")]
148    features.push("s3");
149    #[cfg(feature = "semantic")]
150    features.push("semantic");
151    #[cfg(feature = "semantic-extended")]
152    features.push("semantic-extended");
153    #[cfg(feature = "zstd")]
154    features.push("zstd");
155    if features.is_empty() {
156        features.push("none");
157    }
158    features
159}