Repository: cargo-generate/cargo-generate (rust)

Issue:
Feature request: Set default VCS from within templates
It would be nice if it was possible to make templates default to a different or no VCS initialisation. In my case this would be useful for a "minimal" template meant for messing around inside. Could also be useful for templates which are meant to be a smaller part of a larger vcs repository.

As a workaround I tried to delete the initialised vcs repository from rhai, but that doesn't work as rhai scripts run before vcs initalisation.

Pull request:
Feature request: Set default VCS from within templates
Fixes #635

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2cfdde7..02859f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,8 @@
 # Changelog
 
 ## [Unreleased](https://github.com/cargo-generate/cargo-generate/compare/v0.15.2...HEAD)
-
 ### ✨ Features
+- [Set default VCS in the favorite or template config](https://github.com/cargo-generate/cargo-generate/issues/635)
 - [Add case filters to `liquid` and functions to `Rhai`. Enables case changing functionallity from both `liquid` and `Rhai`](https://github.com/cargo-generate/cargo-generate/issues/638)
 - [Add placeholder `{{is_init}}`. Enables templates to adust if they are expanded with the `--init` arg for `cargo-generate`](https://github.com/cargo-generate/cargo-generate/issues/649)
 
diff --git a/guide/src/favorites.md b/guide/src/favorites.md
index eb432d7..0c1c3c2 100644
--- a/guide/src/favorites.md
+++ b/guide/src/favorites.md
@@ -13,6 +13,7 @@ description = "<optional description, visible with --list-favorites>"
 git = "https://github.com/ashleygwilliams/wasm-pack-template"
 branch = "<optional-branch>"
 subfolder = "<optional-subfolder>"
+vcs = "<optional: None|Git>"
 init = optional true|false
 ```
 
diff --git a/src/app_config.rs b/src/app_config.rs
index 15a9727..0a085e3 100644
--- a/src/app_config.rs
+++ b/src/app_config.rs
@@ -7,7 +7,7 @@ use std::{
     path::{Path, PathBuf},
 };
 
-use crate::info;
+use crate::{info, Vcs};
 
 pub const CONFIG_FILE_NAME: &str = "cargo-generate.toml";
 
@@ -33,6 +33,7 @@ pub struct FavoriteConfig {
     pub subfolder: Option<String>,
     pub path: Option<PathBuf>,
     pub values: Option<HashMap<String, toml::Value>>,
+    pub vcs: Option<Vcs>,
     pub init: Option<bool>,
 }
 
diff --git a/src/args.rs b/src/args.rs
index cf024ba..40f7b8a 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -5,6 +5,7 @@ use std::{
 
 use anyhow::{anyhow, Result};
 use clap::{Args, Parser};
+use serde::Deserialize;
 
 use crate::git;
 
@@ -77,10 +78,10 @@ pub struct GenerateArgs {
     pub config: Option<PathBuf>,
 
     /// Specify the VCS used to initialize the generated template.
-    #[clap(long, default_value = "git", value_parser)]
-    pub vcs: Vcs,
+    #[clap(long, value_parser)]
+    pub vcs: Option<Vcs>,
 
-    /// Populates a template variable `crate_type` with value `"lib"`
+    /// Populates template variable `crate_type` with value `"lib"`
     #[clap(long, conflicts_with = "bin", action)]
     pub lib: bool,
 
@@ -199,7 +200,7 @@ impl TemplatePath {
     }
 }
 
-#[derive(Debug, Parser, Clone, Copy)]
+#[derive(Debug, Parser, Clone, Copy, PartialEq, Eq, Deserialize)]
 pub enum Vcs {
     None,
     Git,
diff --git a/src/config.rs b/src/config.rs
index 31b5111..df016c5 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -6,6 +6,8 @@ use std::{collections::HashMap, fs};
 use std::{convert::TryFrom, io::ErrorKind};
 use walkdir::WalkDir;
 
+use crate::Vcs;
+
 pub const CONFIG_FILE_NAME: &str = "cargo-generate.toml";
 
 #[derive(Deserialize, Debug, PartialEq, Default, Clone)]
@@ -28,6 +30,7 @@ pub struct TemplateConfig {
     pub include: Option<Vec<String>>,
     pub exclude: Option<Vec<String>>,
     pub ignore: Option<Vec<String>>,
+    pub vcs: Option<Vcs>,
     pub init: Option<bool>,
 }
 
@@ -184,6 +187,7 @@ mod tests {
                 include: Some(vec!["Cargo.toml".into()]),
                 exclude: None,
                 ignore: None,
+                vcs: None,
                 init: None,
             })
         );
diff --git a/src/lib.rs b/src/lib.rs
index cc0cd45..e5cffc0 100755
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -132,7 +132,7 @@ pub fn generate(mut args: GenerateArgs) -> Result<PathBuf> {
         &project_dir,
         &project_name,
         &template_folder,
-        template_config,
+        &template_config,
         &args,
     )?;
 
@@ -145,10 +145,13 @@ pub fn generate(mut args: GenerateArgs) -> Result<PathBuf> {
     );
     copy_dir_all(&template_folder, &project_dir)?;
 
-    if !args.vcs.is_none() && (!source_template.init() || args.force_git_init) {
+    let vcs = template_config
+        .template
+        .and_then(|t| t.vcs)
+        .unwrap_or_else(|| source_template.vcs());
+    if !vcs.is_none() && (!source_template.init || args.force_git_init) {
         info!("{}", style("Initializing a fresh Git repository").bold());
-        args.vcs
-            .initialize(&project_dir, branch, args.force_git_init)?;
+        vcs.initialize(&project_dir, branch, args.force_git_init)?;
     }
 
     println!(
@@ -415,7 +418,7 @@ fn expand_template(
     project_dir: &Path,
     project_name: &ProjectName,
     template_dir: &Path,
-    template_config: Config,
+    template_config: &Config,
     args: &GenerateArgs,
 ) -> Result<()> {
     let template_values = source_template.template_values();
@@ -428,7 +431,7 @@ fn expand_template(
         source_template,
     )?;
     let liquid_object =
-        project_variables::fill_project_variables(liquid_object, &template_config, |slot| {
+        project_variables::fill_project_variables(liquid_object, template_config, |slot| {
             let provided_value = template_values.get(&slot.var_name).and_then(|v| v.as_str());
             if provided_value.is_none() && args.silent {
                 anyhow::bail!(ConversionError::MissingPlaceholderVariable {
@@ -439,7 +442,7 @@ fn expand_template(
         })?;
     let liquid_object = add_missing_provided_values(liquid_object, template_values)?;
     let (mut template_cfg, liquid_object) =
-        merge_conditionals(&template_config, liquid_object, args)?;
+        merge_conditionals(template_config, liquid_object, args)?;
 
     let all_hook_files = template_config.get_hook_files();
 
@@ -448,7 +451,7 @@ fn expand_template(
     execute_pre_hooks(
         template_dir,
         Rc::clone(&liquid_object),
-        &template_config,
+        template_config,
         args.allow_commands,
         args.silent,
     )?;
@@ -471,7 +474,7 @@ fn expand_template(
     execute_post_hooks(
         template_dir,
         Rc::clone(&liquid_object),
-        &template_config,
+        template_config,
         args.allow_commands,
         args.silent,
     )?;
diff --git a/src/user_parsed_input.rs b/src/user_parsed_input.rs
index 00f2b5d..000a5ac 100644
--- a/src/user_parsed_input.rs
+++ b/src/user_parsed_input.rs
@@ -8,7 +8,7 @@ use std::{
 use console::style;
 use regex::Regex;
 
-use crate::{app_config::AppConfig, warn, GenerateArgs};
+use crate::{app_config::AppConfig, warn, GenerateArgs, Vcs};
 
 // Contains parsed information from user.
 #[derive(Debug)]
@@ -22,6 +22,7 @@ pub struct UserParsedInput {
     // 2. configuration file
     // 3. cli arguments --define
     template_values: HashMap<String, toml::Value>,
+    vcs: Vcs,
     pub init: bool,
     //TODO:
     // 1. This structure should be used instead of args
@@ -34,12 +35,14 @@ impl UserParsedInput {
         template_location: impl Into<TemplateLocation>,
         subfolder: Option<T>,
         default_values: HashMap<String, toml::Value>,
+        vcs: Vcs,
         init: bool,
     ) -> Self {
         Self {
             template_location: template_location.into(),
             subfolder: subfolder.map(|s| s.as_ref().to_owned()),
             template_values: default_values,
+            vcs,
             init,
         }
     }
@@ -50,6 +53,8 @@ impl UserParsedInput {
     /// This function assume that Args and AppConfig are verfied eariler and are logicly correct
     /// For example if both `--git` and `--path` are set this function will panic
     pub fn try_from_args_and_config(app_config: &AppConfig, args: &GenerateArgs) -> Self {
+        const DEFAULT_VCS: Vcs = Vcs::Git;
+
         let mut default_values = app_config.values.clone().unwrap_or_default();
         let ssh_identity = app_config
             .defaults
@@ -70,6 +75,7 @@ impl UserParsedInput {
                 git_user_in,
                 args.template_path.subfolder(),
                 default_values,
+                args.vcs.unwrap_or(DEFAULT_VCS),
                 args.init,
             );
         }
@@ -80,6 +86,7 @@ impl UserParsedInput {
                 path.as_ref(),
                 args.template_path.subfolder(),
                 default_values,
+                args.vcs.unwrap_or(DEFAULT_VCS),
                 args.init,
             );
         }
@@ -126,6 +133,8 @@ impl UserParsedInput {
                     .map(|s| s.as_ref().to_owned())
                     .or_else(|| fav_cfg.subfolder.clone()),
                 default_values,
+                args.vcs
+                    .unwrap_or_else(|| fav_cfg.vcs.unwrap_or(DEFAULT_VCS)),
                 args.init
                     .then(|| true)
                     .unwrap_or_else(|| fav_cfg.init.unwrap_or(false)),
@@ -186,6 +195,7 @@ impl UserParsedInput {
                 .subfolder()
                 .map(|s| s.as_ref().to_owned()),
             default_values,
+            args.vcs.unwrap_or(DEFAULT_VCS),
             args.init,
         )
     }
@@ -206,6 +216,10 @@ impl UserParsedInput {
         &mut self.template_values
     }
 
+    pub const fn vcs(&self) -> Vcs {
+        self.vcs
+    }
+
     pub const fn init(&self) -> bool {
         self.init
     }


Test patch:
diff --git a/tests/integration/basics.rs b/tests/integration/basics.rs
index f2db288..62ea85e 100644
--- a/tests/integration/basics.rs
+++ b/tests/integration/basics.rs
@@ -1277,6 +1277,46 @@ version = "0.1.0"
     assert!(Repository::open(dir.path().join("foobar-project")).is_err());
 }
 
+#[test]
+fn vsc_none_can_be_specified_in_the_template() {
+    // Build and commit on branch named 'main'
+    let template = tmp_dir()
+        .file(
+            "Cargo.toml",
+            r#"[package]
+name = "{{project-name}}"
+description = "A wonderful project"
+version = "0.1.0"
+"#,
+        )
+        .file(
+            "cargo-generate.toml",
+            r#"[template]
+vcs = "None"
+"#,
+        )
+        .init_git()
+        .build();
+
+    let dir = tmp_dir().build();
+
+    binary()
+        .arg("generate")
+        .arg("--git")
+        .arg(template.path())
+        .arg("--name")
+        .arg("foobar-project")
+        .current_dir(&dir.path())
+        .assert()
+        .success()
+        .stdout(predicates::str::contains("Done!").from_utf8());
+
+    assert!(dir
+        .read("foobar-project/Cargo.toml")
+        .contains("foobar-project"));
+    assert!(Repository::open(dir.path().join("foobar-project")).is_err());
+}
+
 #[test]
 fn it_provides_crate_type_lib() {
     // Build and commit on branch named 'main'
diff --git a/tests/integration/config_file/favorites.rs b/tests/integration/config_file/favorites.rs
index 91a913c..9cbc0c3 100644
--- a/tests/integration/config_file/favorites.rs
+++ b/tests/integration/config_file/favorites.rs
@@ -1,3 +1,5 @@
+use cargo_generate::Vcs;
+use git2::Repository;
 use predicates::prelude::*;
 
 use crate::helpers::project::binary;
@@ -7,7 +9,11 @@ use assert_cmd::prelude::*;
 use indoc::indoc;
 use std::path::PathBuf;
 
-fn create_favorite_config(name: &str, template_path: &Project) -> (Project, PathBuf) {
+fn create_favorite_config(
+    name: &str,
+    template_path: &Project,
+    vcs: Option<Vcs>,
+) -> (Project, PathBuf) {
     let project = tmp_dir()
         .file(
             "cargo-generate",
@@ -17,10 +23,16 @@ fn create_favorite_config(name: &str, template_path: &Project) -> (Project, Path
                     description = "Favorite for the {name} template"
                     git = "{git}"
                     branch = "{branch}"
+                    {vcs}
                     "#},
                 name = name,
                 git = template_path.path().display().to_string().escape_default(),
-                branch = "main"
+                branch = "main",
+                vcs = if let Some(vcs) = vcs {
+                    format!(r#"vcs = "{vcs:?}""#)
+                } else {
+                    String::from("")
+                }
             ),
         )
         .build();
@@ -32,7 +44,7 @@ fn create_favorite_config(name: &str, template_path: &Project) -> (Project, Path
 fn favorite_with_git_becomes_subfolder() {
     let favorite_template = create_template("favorite-template");
     let git_template = create_template("git-template");
-    let (_config, config_path) = create_favorite_config("test", &favorite_template);
+    let (_config, config_path) = create_favorite_config("test", &favorite_template, None);
     let working_dir = tmp_dir().build();
 
     binary()
@@ -134,7 +146,7 @@ fn favorite_with_subfolder() -> anyhow::Result<()> {
 #[test]
 fn it_can_use_favorites() {
     let favorite_template = create_template("favorite-template");
-    let (_config, config_path) = create_favorite_config("test", &favorite_template);
+    let (_config, config_path) = create_favorite_config("test", &favorite_template, None);
     let working_dir = tmp_dir().build();
 
     binary()
@@ -149,15 +161,38 @@ fn it_can_use_favorites() {
         .success()
         .stdout(predicates::str::contains("Done!").from_utf8());
 
+    assert!(Repository::open(working_dir.path().join("favorite-project")).is_ok());
     assert!(working_dir
         .read("favorite-project/Cargo.toml")
         .contains(r#"description = "favorite-template""#));
 }
 
+#[test]
+fn a_favorite_can_set_vcs_to_none_by_default() {
+    let favorite_template = create_template("favorite-template");
+    let (_config, config_path) =
+        create_favorite_config("test", &favorite_template, Some(Vcs::None));
+    let working_dir = tmp_dir().build();
+
+    binary()
+        .arg("generate")
+        .arg("--config")
+        .arg(config_path)
+        .arg("--name")
+        .arg("favorite-project")
+        .arg("test")
+        .current_dir(&working_dir.path())
+        .assert()
+        .success()
+        .stdout(predicates::str::contains("Done!").from_utf8());
+
+    assert!(Repository::open(working_dir.path().join("favorite-project")).is_err());
+}
+
 #[test]
 fn favorites_default_to_git_if_not_defined() {
     let favorite_template = create_template("favorite-template");
-    let (_config, config_path) = create_favorite_config("test", &favorite_template);
+    let (_config, config_path) = create_favorite_config("test", &favorite_template, None);
     let working_dir = tmp_dir().build();
 
     binary()
diff --git a/tests/integration/library.rs b/tests/integration/library.rs
index 68a1195..24801a7 100644
--- a/tests/integration/library.rs
+++ b/tests/integration/library.rs
@@ -1,5 +1,5 @@
 use crate::helpers::project_builder::tmp_dir;
-use cargo_generate::{generate, GenerateArgs, TemplatePath, Vcs};
+use cargo_generate::{generate, GenerateArgs, TemplatePath};
 
 #[test]
 fn it_allows_generate_call_with_public_args_and_returns_the_generated_path() {
@@ -19,7 +19,7 @@ fn it_allows_generate_call_with_public_args_and_returns_the_generated_path() {
         },
         name: Some(String::from("foobar_project")),
         force: true,
-        vcs: Vcs::Git,
+        vcs: None,
         verbose: true,
         template_values_file: None,
         silent: false,