mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 21:59:37 +00:00
refactor(deps): replace git2 with system git and add vcs support
This commit is contained in:
@@ -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 <git|none>]", 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)
|
||||
|
||||
@@ -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 <name>", BOLD_CYAN);
|
||||
printc(" dcr new <name> [--vcs <git|none>]", 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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -20,4 +20,5 @@ pub mod config;
|
||||
pub mod deps;
|
||||
pub mod registry;
|
||||
pub mod runner;
|
||||
pub mod vcs;
|
||||
pub mod workspace;
|
||||
|
||||
32
src/core/vcs/git.rs
Normal file
32
src/core/vcs/git.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
// DCR — Cargo-like C/C++ project manager.
|
||||
//
|
||||
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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(())
|
||||
}
|
||||
61
src/core/vcs/mod.rs
Normal file
61
src/core/vcs/mod.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
// DCR — Cargo-like C/C++ project manager.
|
||||
//
|
||||
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<Self, String> {
|
||||
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<VcsKind> {
|
||||
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
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
112
src/utils/git.rs
Normal file
112
src/utils/git.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
// DCR — Cargo-like C/C++ project manager.
|
||||
//
|
||||
// Copyright (C) 2026 Dexoron (Bezotechestvo Vladimir) <main@dexoron.su>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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(())
|
||||
}
|
||||
@@ -17,5 +17,6 @@
|
||||
|
||||
pub mod build;
|
||||
pub mod fs;
|
||||
pub mod git;
|
||||
pub mod log;
|
||||
pub mod text;
|
||||
|
||||
Reference in New Issue
Block a user