From 838be72dd2915bed63b9813b72d9f8dd36336029 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Sat, 13 Jun 2026 15:28:14 +0800 Subject: [PATCH] refactor(deps): replace git2 with system git and add vcs support --- Cargo.lock | 151 +------------------------------------------ Cargo.toml | 17 +---- build.rs | 33 ++++++++++ src/cli/init.rs | 57 +++++++++++++++- src/cli/new.rs | 61 +++++++++++++++-- src/core/deps/git.rs | 40 +----------- src/core/mod.rs | 1 + src/core/vcs/git.rs | 32 +++++++++ src/core/vcs/mod.rs | 61 +++++++++++++++++ src/main.rs | 7 +- src/utils/git.rs | 112 ++++++++++++++++++++++++++++++++ src/utils/mod.rs | 1 + tests/cli_basic.rs | 54 ++++++++++++++++ 13 files changed, 415 insertions(+), 212 deletions(-) create mode 100644 src/core/vcs/git.rs create mode 100644 src/core/vcs/mod.rs create mode 100644 src/utils/git.rs diff --git a/Cargo.lock b/Cargo.lock index 4429d44..a946026 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,8 +63,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -112,12 +110,10 @@ dependencies = [ [[package]] name = "dcr" -version = "0.7.2" +version = "0.7.3" dependencies = [ "ctrlc", - "git2", "glob", - "openssl", "reqwest", "self-replace", "serde", @@ -193,21 +189,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -316,21 +297,6 @@ dependencies = [ "wasip3", ] -[[package]] -name = "git2" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" -dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "openssl-probe", - "openssl-sys", - "url", -] - [[package]] name = "glob" version = "0.3.3" @@ -589,16 +555,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "js-sys" version = "0.3.99" @@ -623,46 +579,6 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" -[[package]] -name = "libgit2-sys" -version = "0.18.5+1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" -dependencies = [ - "cc", - "libc", - "libssh2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", -] - -[[package]] -name = "libssh2-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -737,59 +653,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "openssl" -version = "0.10.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-src" -version = "300.6.0+3.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -802,12 +665,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - [[package]] name = "potential_utf" version = "0.1.5" @@ -1466,12 +1323,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 85c448e..24d99b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dcr" -version = "0.7.2" +version = "0.7.3" edition = "2024" build = "build.rs" description = "DCR is a utility for managing C/C++ projects in a Cargo-like style." @@ -69,21 +69,6 @@ toml = "1.0.3" serde_json = "1" ctrlc = "3" -[features] -default = ["vendored"] -vendored = [ - "git2/vendored-libgit2", - "git2/vendored-openssl", - "openssl/vendored", -] - -[target.'cfg(not(windows))'.dependencies] -git2 = "0.20.4" -openssl = "0.10" - -[target.'cfg(windows)'.dependencies] -git2 = "0.20.4" - [profile.release] opt-level = "z" lto = "thin" diff --git a/build.rs b/build.rs index 3e0ba11..b18c764 100644 --- a/build.rs +++ b/build.rs @@ -18,4 +18,37 @@ fn main() { let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown-target".to_string()); println!("cargo:rustc-env=DCR_TARGET={target}"); + + // Let's try to retrieve the commit hash for the version + let commit = std::process::Command::new("git") + .args(["rev-parse", "--short", "HEAD"]) + .output() + .ok() + .and_then(|o| { + if o.status.success() { + String::from_utf8(o.stdout) + .ok() + .map(|s| s.trim().to_string()) + } else { + None + } + }); + + // Check if there are any uncommitted changes in the repository + let is_dirty = std::process::Command::new("git") + .args(["status", "--porcelain"]) + .output() + .ok() + .map(|o| o.status.success() && !o.stdout.is_empty()) + .unwrap_or(false); + + if let Some(commit_hash) = commit { + let dirty_suffix = if is_dirty { "-dirty" } else { "" }; + println!( + "cargo:rustc-env=DCR_GIT_INFO= (git {}{})", + commit_hash, dirty_suffix + ); + } else { + println!("cargo:rustc-env=DCR_GIT_INFO="); + } } diff --git a/src/cli/init.rs b/src/cli/init.rs index 28fa7d3..2183131 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -17,6 +17,7 @@ use crate::config::FILE_MAIN_C; use crate::core::config::{Config, validate_package_name}; +use crate::core::vcs::VcsKind; use crate::utils::fs::check_dir; use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc}; @@ -27,7 +28,7 @@ use toml::Value; pub fn init(args: &[String]) -> i32 { if args.first().is_some_and(|a| a == "--help") { printc("USAGE:", BOLD_GREEN); - printc(" dcr init", BOLD_CYAN); + printc(" dcr init [--vcs ]", BOLD_CYAN); println!(); printc("DESCRIPTION:", BOLD_GREEN); println!(" Initializes the current directory as a DCR project."); @@ -35,7 +36,25 @@ pub fn init(args: &[String]) -> i32 { return 0; } - if !args.is_empty() { + let mut vcs_str = None; + let mut clean_args = Vec::new(); + let mut iter = args.iter(); + while let Some(arg) = iter.next() { + if arg == "--vcs" { + if let Some(val) = iter.next() { + vcs_str = Some(val.clone()); + } else { + error("--vcs requires a value"); + return 1; + } + } else if let Some(stripped) = arg.strip_prefix("--vcs=") { + vcs_str = Some(stripped.to_string()); + } else { + clean_args.push(arg.clone()); + } + } + + if !clean_args.is_empty() { warn("Command does not support additional arguments"); return 1; } @@ -106,6 +125,40 @@ pub fn init(args: &[String]) -> i32 { colored("src/main.c", BOLD_CYAN) ); + // settings VCS + let mut vcs_kind = VcsKind::Git; + if let Some(ref vcs_val) = vcs_str { + match VcsKind::parse(vcs_val) { + Ok(kind) => vcs_kind = kind, + Err(e) => { + error(&e); + return 1; + } + } + } else if let Ok(cwd_path) = std::env::current_dir() + && crate::core::vcs::find_existing_vcs(&cwd_path).is_some() + { + vcs_kind = VcsKind::None; + } + + if vcs_kind == VcsKind::Git { + if crate::utils::git::is_git_available() { + let project_path = std::path::Path::new("."); + if let Err(e) = crate::core::vcs::init_vcs(vcs_kind, project_path) { + warn(&format!("Failed to initialize git repository: {}", e)); + } else { + println!( + " {} Initialized git repository", + colored("✔", BOLD_GREEN) + ); + } + } else { + warn( + "Git is not installed or not found in PATH. Skipping Git repository initialization.", + ); + } + } + println!( "Project `{}` successfully created\n", colored(project_name.as_str(), BOLD_GREEN) diff --git a/src/cli/new.rs b/src/cli/new.rs index 661c610..599af78 100644 --- a/src/cli/new.rs +++ b/src/cli/new.rs @@ -17,6 +17,7 @@ use crate::config::FILE_MAIN_C; use crate::core::config::{Config, validate_package_name}; +use crate::core::vcs::VcsKind; use crate::utils::fs::check_dir; use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc}; @@ -27,7 +28,7 @@ use toml::Value; pub fn new(args: &[String]) -> i32 { if args.first().is_some_and(|a| a == "--help") { printc("USAGE:", BOLD_GREEN); - printc(" dcr new ", BOLD_CYAN); + printc(" dcr new [--vcs ]", BOLD_CYAN); println!(); printc("DESCRIPTION:", BOLD_GREEN); println!(" Creates a new C/C++ project with the given name."); @@ -37,16 +38,34 @@ pub fn new(args: &[String]) -> i32 { let items = check_dir(None).unwrap_or_default(); - if args.is_empty() { + let mut vcs_str = None; + let mut clean_args = Vec::new(); + let mut iter = args.iter(); + while let Some(arg) = iter.next() { + if arg == "--vcs" { + if let Some(val) = iter.next() { + vcs_str = Some(val.clone()); + } else { + error("--vcs requires a value"); + return 1; + } + } else if let Some(stripped) = arg.strip_prefix("--vcs=") { + vcs_str = Some(stripped.to_string()); + } else { + clean_args.push(arg.clone()); + } + } + + if clean_args.is_empty() { error("Project name not specified"); return 1; } - if args.len() > 1 { + if clean_args.len() > 1 { warn("Command does not support additional arguments"); return 1; } - let project_name = &args[0]; + let project_name = &clean_args[0]; println!( "Creating a Project `{}`...", colored(project_name, BOLD_CYAN) @@ -127,6 +146,40 @@ pub fn new(args: &[String]) -> i32 { colored("src/main.c", BOLD_CYAN) ); + // settings VCS + let mut vcs_kind = VcsKind::Git; + if let Some(ref vcs_val) = vcs_str { + match VcsKind::parse(vcs_val) { + Ok(kind) => vcs_kind = kind, + Err(e) => { + error(&e); + return 1; + } + } + } else if let Ok(cwd) = std::env::current_dir() + && crate::core::vcs::find_existing_vcs(&cwd).is_some() + { + vcs_kind = VcsKind::None; + } + + if vcs_kind == VcsKind::Git { + if crate::utils::git::is_git_available() { + let project_path = std::path::Path::new(project_name); + if let Err(e) = crate::core::vcs::init_vcs(vcs_kind, project_path) { + warn(&format!("Failed to initialize git repository: {}", e)); + } else { + println!( + " {} Initialized git repository", + colored("✔", BOLD_GREEN) + ); + } + } else { + warn( + "Git is not installed or not found in PATH. Skipping Git repository initialization.", + ); + } + } + println!( "Project `{}` successfully created\n", colored(project_name, BOLD_GREEN) diff --git a/src/core/deps/git.rs b/src/core/deps/git.rs index 710ceea..2ca84e8 100644 --- a/src/core/deps/git.rs +++ b/src/core/deps/git.rs @@ -29,43 +29,5 @@ pub fn fetch_git_dep( return Ok(()); } - let mut builder = git2::build::RepoBuilder::new(); - if let Some(b) = branch { - builder.branch(b); - } - - let repo = builder - .clone(url, target_dir) - .map_err(|e| format!("failed to clone {}: {}", url, e))?; - - if let Some(t) = tag { - checkout_ref(&repo, &format!("refs/tags/{}", t))?; - } else if let Some(r) = rev { - checkout_ref(&repo, r)?; - } - - Ok(()) -} - -#[allow(dead_code)] -fn checkout_ref(repo: &git2::Repository, reference_str: &str) -> Result<(), String> { - let (object, reference) = repo - .revparse_ext(reference_str) - .map_err(|e| format!("failed to find ref {}: {}", reference_str, e))?; - - repo.checkout_tree(&object, None).map_err(|e| { - let ref_name = reference.as_ref().and_then(|r| r.name()).unwrap_or("HEAD"); - format!("failed to checkout {}: {}", ref_name, e) - })?; - - match reference { - Some(gref) => { - let ref_name = gref.name().unwrap_or("HEAD"); - repo.set_head(ref_name) - } - None => repo.set_head_detached(object.id()), - } - .map_err(|e| format!("failed to set HEAD: {}", e))?; - - Ok(()) + crate::utils::git::git_clone_and_checkout(url, target_dir, branch, tag, rev) } diff --git a/src/core/mod.rs b/src/core/mod.rs index 43eec38..ed33f0d 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -20,4 +20,5 @@ pub mod config; pub mod deps; pub mod registry; pub mod runner; +pub mod vcs; pub mod workspace; diff --git a/src/core/vcs/git.rs b/src/core/vcs/git.rs new file mode 100644 index 0000000..83696f1 --- /dev/null +++ b/src/core/vcs/git.rs @@ -0,0 +1,32 @@ +// DCR — Cargo-like C/C++ project manager. +// +// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::fs; +use std::path::Path; + +// creating git init and .gitignore +pub fn init(path: &Path) -> Result<(), String> { + crate::utils::git::git_init(path)?; + + let gitignore_path = path.join(".gitignore"); + if !gitignore_path.exists() { + fs::write(&gitignore_path, "/target\n") + .map_err(|e| format!("failed to create .gitignore: {}", e))?; + } + + Ok(()) +} diff --git a/src/core/vcs/mod.rs b/src/core/vcs/mod.rs new file mode 100644 index 0000000..8d2e593 --- /dev/null +++ b/src/core/vcs/mod.rs @@ -0,0 +1,61 @@ +// DCR — Cargo-like C/C++ project manager. +// +// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub mod git; + +use std::path::Path; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VcsKind { + Git, + None, +} + +impl VcsKind { + pub fn parse(s: &str) -> Result { + match s.to_lowercase().as_str() { + "git" => Ok(Self::Git), + "none" => Ok(Self::None), + _ => Err(format!("Unknown VCS: {}", s)), + } + } +} + +pub fn init_vcs(kind: VcsKind, path: &Path) -> Result<(), String> { + match kind { + VcsKind::Git => git::init(path), + VcsKind::None => Ok(()), + } +} + +// look for git repo in parents to avoid nested repos +pub fn find_existing_vcs(path: &Path) -> Option { + let mut current = if path.is_file() { + path.parent() + } else { + Some(path) + }; + + while let Some(dir) = current { + if dir.join(".git").exists() { + return Some(VcsKind::Git); + } + current = dir.parent(); + } + + None +} diff --git a/src/main.rs b/src/main.rs index fcb0ae7..361029e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,12 @@ fn main() { "clean" => cli::clean::clean(rest), "gen" => cli::r#gen::r#gen(rest), "--version" => { - println!("dcr {} ({})", env!("CARGO_PKG_VERSION"), env!("DCR_TARGET")); + println!( + "dcr {}{} ({})", + env!("CARGO_PKG_VERSION"), + env!("DCR_GIT_INFO"), + env!("DCR_TARGET") + ); 0 } "--help" => cli::help::help(), diff --git a/src/utils/git.rs b/src/utils/git.rs new file mode 100644 index 0000000..ed83dc1 --- /dev/null +++ b/src/utils/git.rs @@ -0,0 +1,112 @@ +// DCR — Cargo-like C/C++ project manager. +// +// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::path::Path; +use std::process::Command; + +// checking installed git on system +pub fn is_git_available() -> bool { + Command::new("git") + .arg("--version") + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +// warning of git not in system +#[allow(dead_code)] +pub fn warn_if_git_missing() { + if !is_git_available() { + crate::utils::log::warn( + "Git is not installed or not found in PATH. Some features (like dependency cloning and VCS initialization) may fail.", + ); + } +} + +// init repo in folder +pub fn git_init(path: &Path) -> Result<(), String> { + if !is_git_available() { + return Err("git executable not found in PATH".to_string()); + } + + let status = Command::new("git") + .arg("init") + .current_dir(path) + .status() + .map_err(|e| format!("failed to execute git init: {}", e))?; + + if !status.success() { + return Err("git init failed".to_string()); + } + + Ok(()) +} + +// Clone the dependency and switch to the branch/tag/commit +#[allow(dead_code)] +pub fn git_clone_and_checkout( + url: &str, + target_dir: &Path, + branch: Option<&str>, + tag: Option<&str>, + rev: Option<&str>, +) -> Result<(), String> { + if !is_git_available() { + return Err("git executable not found in PATH".to_string()); + } + + let mut args = vec!["clone"]; + + if let Some(b) = branch { + args.push("--branch"); + args.push(b); + args.push("--single-branch"); + } else if let Some(t) = tag { + args.push("--branch"); + args.push(t); + args.push("--single-branch"); + } + + args.push(url); + + let target_dir_str = target_dir.to_string_lossy().to_string(); + args.push(&target_dir_str); + + let status = Command::new("git") + .args(&args) + .status() + .map_err(|e| format!("failed to execute git clone: {}", e))?; + + if !status.success() { + return Err(format!("git clone of {} failed", url)); + } + + if let Some(r) = rev { + let checkout_status = Command::new("git") + .arg("checkout") + .arg(r) + .current_dir(target_dir) + .status() + .map_err(|e| format!("failed to execute git checkout: {}", e))?; + + if !checkout_status.success() { + return Err(format!("git checkout to revision {} failed", r)); + } + } + + Ok(()) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c4cdcdd..1a955c3 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -17,5 +17,6 @@ pub mod build; pub mod fs; +pub mod git; pub mod log; pub mod text; diff --git a/tests/cli_basic.rs b/tests/cli_basic.rs index fd55ce9..9fc3704 100644 --- a/tests/cli_basic.rs +++ b/tests/cli_basic.rs @@ -99,6 +99,60 @@ fn new_creates_project_layout() { ); } +fn is_git_in_path() -> bool { + std::process::Command::new("git") + .arg("--version") + .output() + .map(|o| o.status.success()) + .unwrap_or(false) +} + +#[test] +fn new_vcs_options_work() { + // 1. Test --vcs none + let dir = unique_sandbox_dir("new_vcs_none"); + let out = run_dcr(&["new", "hello_none", "--vcs", "none"], &dir); + assert!(out.status.success(), "dcr new --vcs none should succeed"); + let project_dir = dir.join("hello_none"); + assert!(project_dir.is_dir()); + assert!(!project_dir.join(".git").exists()); + assert!(!project_dir.join(".gitignore").exists()); + + // 2. Test --vcs git + if is_git_in_path() { + let dir = unique_sandbox_dir("new_vcs_git"); + let out = run_dcr(&["new", "hello_git", "--vcs", "git"], &dir); + assert!(out.status.success(), "dcr new --vcs git should succeed"); + let project_dir = dir.join("hello_git"); + assert!(project_dir.is_dir()); + assert!(project_dir.join(".git").is_dir()); + assert!(project_dir.join(".gitignore").is_file()); + let gitignore = std::fs::read_to_string(project_dir.join(".gitignore")).unwrap(); + assert!(gitignore.contains("/target")); + } +} + +#[test] +fn init_vcs_options_work() { + // 1. Test --vcs none + let dir = unique_sandbox_dir("init_vcs_none"); + let out = run_dcr(&["init", "--vcs", "none"], &dir); + assert!(out.status.success(), "dcr init --vcs none should succeed"); + assert!(!dir.join(".git").exists()); + assert!(!dir.join(".gitignore").exists()); + + // 2. Test --vcs git + if is_git_in_path() { + let dir = unique_sandbox_dir("init_vcs_git"); + let out = run_dcr(&["init", "--vcs", "git"], &dir); + assert!(out.status.success(), "dcr init --vcs git should succeed"); + assert!(dir.join(".git").is_dir()); + assert!(dir.join(".gitignore").is_file()); + let gitignore = std::fs::read_to_string(dir.join(".gitignore")).unwrap(); + assert!(gitignore.contains("/target")); + } +} + #[test] fn init_and_clean_remove_target() { let dir = unique_sandbox_dir("init");