Repository: cargo-generate/cargo-generate
Pull request #547 (2022-02-13 14:11:58)

Refactor of handling favorites, git and path
Hi,

I wanted to implement #522 but I found it difficult. When I dig into code I found  that some of it should in my opinion be refactored:

The first problem is with `--git` flag. If you set it to local path it will act differently than `git clone` what is a little bit unexpected. This is because `src/git.rs` try to check if the value in `--git` is not a local path and if it's it will just copy it. `git2` already handle local path so there is no necessary of additional code. 

The second problem is with **favorite arg**. If there is no **favorite configuration** in config file. cargo-generate will use **favorite arg** as the `--git` flag. In my opinion we should here distinguish if this is a **git** or **path** and handle it differently.

I would also expect that:
```
cargo generate --git <repository> <template_name>
```
and
```
git clone <repository> <local_path>
cargo generate --path <local_path> <template_name>
```
work the same. But now when ` --path` is used it just ignore **subfolder arg** (and ask about witch template is should actually use)

https://github.com/cargo-generate/cargo-generate/blob/d95863bd5f05471c0c529a129f584af0b73f9857/src/lib.rs#L263-L272 
 
 I would suggest this approach:
 1. [ ] `--git` is handled as much as possible by `git2` library 
 2. [ ] `--path` is used when we want to plain copy of some location. 
 3. [ ] `favorite` should be decoded in this order
     1. look for defined favorites in configuration and use one if name match
     2. if there is a `--branch` flag used consider **favorite arg** as `--git`
     3. if **favorite arg** starts with abbreviation  consider it as `--git`
     4. try to use **favorite arg** as `--path` (check if such path exist, is dir etc.)
     5. fallback to `--git` 
 
This changes are against 0.12.0 ( I made them some day ago but decided to publish today) and at this moment are just an idea of refactoring if you find it right I will continue to work on it.
The current changes also contains additional level of credentials through ssh-agent. 




sassman: Hi @xoac,

let me try to clarify a couple of things.

> I would also expect that:
>
> cargo generate --git <repository> <template_name>
> 
> and
> 
> git clone <repository> <local_path>
> cargo generate --path <local_path> <template_name>
> 
> work the same. But now when --path is used it just ignore subfolder arg (and ask about witch template is should actually use)

`--path` works with a local path but very different than `git clone <local-path>` works and that is intended. For example `git clone` uses the `HEAD` that is committed. On the other side `cargo generate --path` takes the folder as is, that means if there are changes that are not commited in the template repo it will just take them. 

That makes it the perfect tool for locally testing an WIP template repo, without the need of committing the changes in the template repo over and over. 

> The second problem is with favorite arg. If there is no favorite configuration in config file. cargo-generate will use favorite arg as the --git flag. In my opinion we should here distinguish if this is a git or path and handle it differently.

that is also intended because it would allow to have the following resolution order for example `cargo generate sassman/demo`
- `sassman/demo` is first checked if there is a favorite config for it, if not
- it will check if there is any local folder matching it, if not
- it checks if there is GitHub repo that matches it

so far that has been working out quite nicely, at least for me and others.

But I do see that the behaviour might be confusing, especially when there is `--path` that should be explicitly used.
xoac: > --path works with a local path but very different than git clone <local-path> works and that is intended. For example git clone uses the HEAD that is committed. On the other side cargo generate --path takes the folder as is, that means if there are changes that are not commited in the template repo it will just take them.

I don't want anything different. I want very similar behavior (to current one) - mostly refactor just the code. 

> that is also intended because it would allow to have the following resolution order for example cargo generate sassman/demo

I want parse `<favorite>` to try guess as correct as possible (current behavior) what user want but assume that user know what is doing when it pass `--git` or `--path` and not modify his input. 

I will try my best to explain myself but I still learning English. 

## Example of current usage (as of 0.12.0)

Some simple scenarios that should help understand what is in my mind. I have cloned https://github.com/xoac/cargo-generate-two-templates into `xoac/cargo-generate-two-templates`. (The output below contains 3 levels of structure tree). 
```
$ tree -L 3
.
└── xoac
    └── cargo-generate-two-templates
        ├── logo.svg
        ├── template
        └── template2
```
_This is actually how I store all project on my computer `~/projects/github.com/<org-name>/<repo>`_

Try to predict the output. Be aware that `xoac/cargo-generate-two-templates` is a valid local path and valid org/repo on github:
1. `cargo generate --git xoac/cargo-generate-two-templates template` should clone the github repo, the local repo, or plain path (what you expect)?
2. `cargo generate --git github.com/xoac/cargo-generate-two-templates` should it work?
3. `cargo generate --path xoac/cargo-generate-two-templates template`
    <details>
    
    ![image](https://user-images.githubusercontent.com/1863020/153766160-ec9c2111-0eaa-4d64-819e-4b26444a1d39.png)
    I have already specified subfolder in CLI
    </details>
4. `cargo generate --git xoac/cargo-generate-two-templates template` the same out as with `--path` or different?
5. know better that user wanted path rather than git and this is why he wrote `--git`
    ```
    cp -r xoac/cargo-generate-two-templates/template local-template-without-git
    cargo generate --git local-template-without-git
    ```
    <details>
        
    The output could be. **The `<path>` exists but it's not a git repository. Maybe you wanted `--path`?**
    </details>
6. `cargo generate --path https://github.com/xoac/cargo-generate-two-templates `
    <details>
     
    why `--git` assume that user could wanted path but `--path` do not assume it can wanted git? This approach could nearly lead as to a place where `--git` `--path` and `<favorite>` could be all the same.
    </details>

Now the harder part `cargo generate  xoac/cargo-generate-two-templates` (so `<favorite>`) should expend to?

1.  `xoac/cargo-generate-two-templates` is first checked if there is a favorite config for it, if not
2. it will check if there is any local folder matching it, (if yes, will it be equivalent of **`cargo generate --git` or `cargo generate --path`**)?
3. it checks if there is GitHub repo that matches it 

So the problem is that `<favorite>` is not parsed instead is [directly mapped into `--git`](https://github.com/cargo-generate/cargo-generate/blob/v0.12.0/src/favorites.rs#L81)  

## Proposal

1. `cargo generate --git <repo>` should only accept what `git clone` would accept
2. `cargo generate --path <local_path>` should use local files, but sub-folder should also work as with `--git`
3. `cargo generate <favorite>` (no configuration) should be parsed:
    1. If contains abbreviation like `gh`, `gl` etc. and if create a correct link (use `cargo generate --git <link>`)
    2. Check if `<favorite>` could be a valid path to template on local system if yes then should behave exactly like `cargo generate --path <favorite>`
    3. use it as user would call `cargo generate --git <favorite>`

### Braking changes

This will make the idea simpler (and very close to the current behavior). The braking changes are:
1.  `--git` will be more restricted but user still can use cargo generate without this flag and get old behavior. 
2.  `org/repo` will not be parsed as github - this complicate parsing and promote Microsoft for no reason (this is optional change)

## Motivation
This crate was evolved in quite a big piece of good software now. I think it would be nice to improve a little quality of code. 

Here are the main modification that I would like to made. Just git module was the simplest for me to start:

1. The user input `args: Args` is [modified](https://github.com/cargo-generate/cargo-generate/blob/d95863bd5f05471c0c529a129f584af0b73f9857/src/lib.rs#L79) and passed to all other modules what make them less independent. (It's hard to jump into one module - there is a need to understand all code to work with one). So it will be harder and harder to adding new functionality.
2. The code that works with `--path` and `--git` is very similar and could be unified or simplified. For example [first time](https://github.com/cargo-generate/cargo-generate/blob/v0.12.0/src/lib.rs#L139) dealing with local path and the [second](https://github.com/cargo-generate/cargo-generate/blob/v0.12.0/src/git.rs#L50)
3. The modules should be independent as much as possible. Should be passed variables instead of `Args` (it is difficult to know which variables are used in function).
4. Make possible in less line of documentation explain how `<favorite>` , `--git` and `--path` work with each other

sassman: Thanks again for the very detailed and well written explanation. And I agree to almost everything you've mentioned.
The one exception we should really keep as is at the moment:

> 2. org/repo will not be parsed as github - this complicate parsing and promote Microsoft for no reason (this is optional change)

I argue to keep this behavior with mostly convenience and the fact that most users use template from github and also the most easiest entry point is to not to tell cargo-generate to use `--git` because a short form of `org/repo` means already implicitly it should be used git. 

All the other arguments 
- to make `--path` more strict
- make `--path` and `--git` accept both the subfolder inside the repo
- etc..

Make sense to me. 

When it comes to refactoring, I appreciate the desire to reduce dependencies and improve the code structure.
Some modules did really grow over time and have not the best decoupling. 

However please keep in mind that also other PRs are in the making and a very huge refactoring will put quite some burden to the people that have to review it, and the others that have to deal with the merge of the refactoring. 
This is why I would propose a minimal invasive approach, for example focus for now only on the `git.rs` module, introduce new structs and not rewrite the whole thing.

So finally I'm very curious how this will work out. In the end refactorings should be always very reasonable and the tests should a) not break and b) must be written for new paths and code.
xoac: So some updates:
1. `github.com/org/repo` is not accepted with git clone - it was my bad. But we could implemented `cargo generate github.com/org/repo` to be handled as `cargo generate --git git://github.com/org/repo.git` in seperate PR if you think this should be correct behavior.
2. Still WIP but wanted to show what is direction to get feedback about question I asked in code. Not yet necessary to review everything. 
3. `--git`, `--path` and `<favorite>` are handled as we agreed.

The general changes:
1. It was impossible to deal only with git module. I refactored the `favorite`, `path`, and `git`. I have created a module `user_parsed_input` to distinguish user input (cli, env, config files) from already parsed. Args is no longer mut.
2. **All old test are passing.** I added additional one that pass with `CARGO_GENERATE_GIT_WITH_CLI=true cargo test`
3. Some returning error changed.

> However please keep in mind that also other PRs are in the making and a very huge refactoring will put quite some burden to the people that have to review it, and the others that have to deal with the merge of the refactoring.

Yep my first proposal was against 0.12 so I know the pain. But I think this changes will make maintaining code easier

sassman: So the general refactoring looks good to me. 
However, I must say that I very much dislike the idea to shell out to git. 

IHMO: a tool like `cargo-generate` should not depend on ANY other binary that a user should have installed. That's the great thing about libraries that we can use to embed functionality and reuse great things.


xoac: 
I agree but

1. There will probably be a long time before **full**, **working** git implementation will be provided in rust
2. For example now cargo-generate will **NOT** honorary:
   1. ssh-agent authorization
   2. most of .gitconfig configuration
   3. will no ask about passphrase to encrypted ssh-keys that is specified with `--identity`
   4. probably more less common situations
3. Most of users use git. (The same argument as most users use github.com - for `org/repo` worked ;) )

----
On the other hand

1. By default you still use git2 implementation
2. Using `CARGO_GENERATE_GIT_WITH_CLI=true cargo generate` will just work as expected by git user. No need to create issues and struggle with git2 details. 
3. Document `CARGO_GENERATE_GIT_WITH_CLI=true` as solution that will be removed in the future when full git implementation will be provided internally. Or even document it as experimental
4. Testing `CARGO_GENERATE_GIT_WITH_CLI=true cargo test` -- and that's all

xoac: Let me know what is your decision about `CARGO_GENERATE_GIT_WITH_CLI` if you want it included then some documentation should be also added
sassman: > Let me know what is your decision about `CARGO_GENERATE_GIT_WITH_CLI` if you want it included then some documentation should be also added

Actually, I'd really appreciate if we could handle that as a feature in a followup PR.

That way the review would also be easier.
taurr: Another issue, now that we're touching the CLI stuff... I know this is a breaking change, but our current use of clap/structopt is not utilizing subcommands properly, which stops us from defining options that are suitable for only some commands etc... it would be nice to define it properly :-/
xoac: >Actually, I'd really appreciate if we could handle that as a feature in a followup PR. 

100% agree. I removed code for optional git from shell. 


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5a50c0d..e5ae02b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -126,7 +126,7 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "cargo-generate"
-version = "0.13.0"
+version = "0.14.0-alpha.1"
 dependencies = [
  "anyhow",
  "assert_cmd",
diff --git a/Cargo.toml b/Cargo.toml
index a41a360..ab3327e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,10 +1,8 @@
 [package]
 name = "cargo-generate"
 description = "cargo, make me a project"
-version = "0.13.0"
-authors = [
-    "Ashley Williams <ashley666ashley@gmail.com>"
-]
+version = "0.14.0-alpha.1"
+authors = ["Ashley Williams <ashley666ashley@gmail.com>"]
 license = "MIT/Apache-2.0"
 repository = "https://github.com/cargo-generate/cargo-generate"
 edition = "2021"
@@ -27,7 +25,6 @@ liquid-derive = "0.24"
 walkdir = "2.3"
 remove_dir_all = "0.7"
 ignore = "0.4"
-url = "2.2"
 structopt = "0.3"
 anyhow = "1.0"
 toml = "0.5"
@@ -54,11 +51,17 @@ features = ["derive"]
 predicates = "2.1"
 assert_cmd = "2.0"
 indoc = "1.0"
+url = "2.2"
 
 [dev-dependencies.cargo-husky]
 version = "1"
 default-features = false
-features = ["prepush-hook", "run-cargo-test", "run-cargo-clippy", "run-cargo-fmt"]
+features = [
+  "prepush-hook",
+  "run-cargo-test",
+  "run-cargo-clippy",
+  "run-cargo-fmt",
+]
 
 [features]
 vendored-openssl = ['openssl/vendored']
diff --git a/src/app_config.rs b/src/app_config.rs
index a681484..5af0c44 100644
--- a/src/app_config.rs
+++ b/src/app_config.rs
@@ -18,6 +18,12 @@ pub struct AppConfig {
     pub values: Option<HashMap<String, toml::Value>>,
 }
 
+impl AppConfig {
+    pub fn get_favorite_cfg(&self, favorite_name: &str) -> Option<&FavoriteConfig> {
+        self.favorites.as_ref().and_then(|f| f.get(favorite_name))
+    }
+}
+
 #[derive(Deserialize, Default)]
 pub struct FavoriteConfig {
     pub description: Option<String>,
diff --git a/src/args.rs b/src/args.rs
index 0cf68ef..7fd38f0 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -149,7 +149,9 @@ impl Vcs {
     pub fn initialize(&self, project_dir: &Path, branch: String, force: bool) -> Result<()> {
         match self {
             Self::None => Ok(()),
-            Self::Git => git::init(project_dir, &branch, force).map(|_| ()),
+            Self::Git => git::init(project_dir, &branch, force)
+                .map(|_| ())
+                .map_err(anyhow::Error::from),
         }
     }
 }
diff --git a/src/favorites.rs b/src/favorites.rs
index 7469a1a..ed2c044 100644
--- a/src/favorites.rs
+++ b/src/favorites.rs
@@ -1,10 +1,10 @@
-use std::collections::HashMap;
+//! Module dealing with <favorite> arg passed to cargo-generate
 
 use crate::{
     app_config::{AppConfig, FavoriteConfig},
-    emoji, warn, Args,
+    emoji, Args,
 };
-use anyhow::{anyhow, Result};
+use anyhow::Result;
 use console::style;
 
 pub fn list_favorites(app_config: &AppConfig, args: &Args) -> Result<()> {
@@ -47,66 +47,3 @@ pub fn list_favorites(app_config: &AppConfig, args: &Args) -> Result<()> {
 
     Ok(())
 }
-
-pub fn resolve_favorite_args_and_default_values(
-    app_config: &AppConfig,
-    args: &mut Args,
-) -> Result<Option<HashMap<String, toml::Value>>> {
-    if args.git.is_some() {
-        args.subfolder = args.favorite.take();
-        return Ok(app_config.values.clone());
-    }
-
-    if args.path.is_some() {
-        return Ok(app_config.values.clone());
-    }
-
-    let favorite_name = args
-        .favorite
-        .as_ref()
-        .ok_or_else(|| anyhow!("Please specify either --git option, or a predefined favorite"))?;
-
-    let (values, git, branch, subfolder, path) = app_config
-        .favorites
-        .as_ref()
-        .and_then(|f| f.get(favorite_name.as_str()))
-        .map_or_else(
-            || {
-                warn!(
-                    "Favorite {} not found in config, using it as a git repo url",
-                    style(&favorite_name).bold()
-                );
-                (
-                    None,
-                    Some(favorite_name.clone()),
-                    args.branch.as_ref().cloned(),
-                    args.subfolder.clone(),
-                    None,
-                )
-            },
-            |f| {
-                let values = match app_config.values.clone() {
-                    Some(mut values) => {
-                        values.extend(f.values.clone().unwrap_or_default());
-                        Some(values)
-                    }
-                    None => f.values.clone(),
-                };
-
-                (
-                    values,
-                    f.git.clone(),
-                    args.branch.as_ref().or(f.branch.as_ref()).cloned(),
-                    args.subfolder.as_ref().or(f.subfolder.as_ref()).cloned(),
-                    f.path.clone(),
-                )
-            },
-        );
-
-    args.git = git;
-    args.branch = branch;
-    args.subfolder = subfolder;
-    args.path = path;
-
-    Ok(values)
-}
diff --git a/src/git/creds.rs b/src/git/creds.rs
new file mode 100644
index 0000000..27c07be
--- /dev/null
+++ b/src/git/creds.rs
@@ -0,0 +1,43 @@
+use crate::git::utils::home;
+use crate::git::{identity_path::IdentityPath, utils::canonicalize_path};
+use crate::info;
+use anyhow::Result;
+use console::style;
+use git2::{Cred, RemoteCallbacks};
+use std::path::PathBuf;
+
+// Ok(None) is returned when identity was None and .ssh/id_rsa was not present
+pub fn git_ssh_credentials_callback<'a>(
+    identity: Option<PathBuf>,
+) -> Result<Option<RemoteCallbacks<'a>>> {
+    let private_key = if let Some(identity) = identity {
+        let identity = canonicalize_path(identity)?;
+        IdentityPath::try_from(identity)?
+    } else {
+        // if .ssh/id_rsa not exist its not error
+        let default_ssh_key = home()?.join(".ssh/id_rsa");
+        match IdentityPath::try_from(default_ssh_key) {
+            Ok(v) => v,
+            Err(_e) => return Ok(None),
+        }
+    };
+
+    info!(
+        "{} `{}` {}",
+        style("Using private key:").bold(),
+        style(format!("{}", &private_key)).bold().yellow(),
+        style("for git-ssh checkout").bold()
+    );
+    let mut cb = RemoteCallbacks::new();
+    cb.credentials(
+        move |_url, username_from_url: Option<&str>, _allowed_types| {
+            Cred::ssh_key(
+                username_from_url.unwrap_or("git"),
+                None,
+                private_key.as_ref(),
+                None,
+            )
+        },
+    );
+    Ok(Some(cb))
+}
diff --git a/src/git/gitconfig.rs b/src/git/gitconfig.rs
new file mode 100644
index 0000000..ca7e014
--- /dev/null
+++ b/src/git/gitconfig.rs
@@ -0,0 +1,64 @@
+use crate::git::utils::home;
+use anyhow::Context;
+use anyhow::Result;
+use git_config::file::GitConfig as GitConfigParser;
+use git_config::parser::Key;
+use std::path::{Path, PathBuf};
+
+pub fn find_gitconfig() -> Result<Option<PathBuf>> {
+    let gitconfig = home().map(|home| home.join(".gitconfig"))?;
+    if gitconfig.exists() {
+        return Ok(Some(gitconfig));
+    }
+
+    Ok(None)
+}
+
+/// trades urls, to replace a given repo remote url with the right on based
+/// on the `[url]` section in the `~/.gitconfig`
+pub fn resolve_instead_url(
+    remote: impl AsRef<str>,
+    gitconfig: impl AsRef<Path>,
+) -> Result<Option<String>> {
+    let gitconfig = gitconfig.as_ref();
+    let remote = remote.as_ref().to_string();
+    let config = GitConfigParser::open(gitconfig).context("Cannot read or parse .gitconfig")?;
+    Ok(config
+        .sections_by_name_with_header("url")
+        .iter()
+        .map(|(head, body)| {
+            let url = head.subsection_name.as_ref();
+            let instead_of = body
+                .value(&Key::from("insteadOf"))
+                .map(|x| std::str::from_utf8(&x[..]).unwrap().to_owned());
+            (instead_of, url)
+        })
+        .filter(|(old, new)| new.is_some() && old.is_some())
+        .find_map(|(old, new)| {
+            let old = old.unwrap();
+            let new = new.unwrap().to_string();
+            remote
+                .starts_with(old.as_str())
+                .then(|| remote.replace(old.as_str(), new.as_str()))
+        }))
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn should_resolve_instead_url() {
+        let sample_config = r#"
+[url "ssh://git@github.com:"]
+    insteadOf = https://github.com/
+"#;
+        let where_gitconfig_lives = tempfile::tempdir().unwrap();
+        let gitconfig = where_gitconfig_lives.path().join(".gitconfig");
+        std::fs::write(&gitconfig, sample_config).unwrap();
+
+        // SSH, aka git@github.com: or ssh://git@github.com/
+        let x = resolve_instead_url("https://github.com/foo/bar.git", &gitconfig).unwrap();
+        assert_eq!(x.unwrap().as_str(), "ssh://git@github.com:foo/bar.git")
+    }
+}
diff --git a/src/git/identity_path.rs b/src/git/identity_path.rs
index 7889509..c8ffc5b 100644
--- a/src/git/identity_path.rs
+++ b/src/git/identity_path.rs
@@ -1,21 +1,33 @@
-use crate::git::utils::{canonicalize_path, home};
+use crate::git::utils::home;
 use std::fmt::{Display, Formatter};
 
-use anyhow::{bail, Result};
+use anyhow::Result;
 use std::path::{Path, PathBuf};
+use thiserror::Error;
+
+//FIXME: this could be io::Error
+#[derive(Debug, Clone, Error)]
+pub enum IdentityPathErr {
+    #[error("identity file do not exist")]
+    FileNoExist,
+    #[error("path is not a file")]
+    NotFile,
+}
 
 pub struct IdentityPath(PathBuf);
 
 impl TryFrom<PathBuf> for IdentityPath {
-    type Error = anyhow::Error;
+    type Error = IdentityPathErr;
 
     fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
-        let path_can = canonicalize_path(&path)?;
-
-        if path_can.exists() {
-            Ok(Self(path_can))
+        if path.exists() {
+            if path.is_file() {
+                Ok(Self(path))
+            } else {
+                Err(IdentityPathErr::NotFile)
+            }
         } else {
-            bail!("Invalid ssh identity path: {}", path.as_path().display())
+            Err(IdentityPathErr::FileNoExist)
         }
     }
 }
diff --git a/src/git/mod.rs b/src/git/mod.rs
index 1b292a5..0214db2 100644
--- a/src/git/mod.rs
+++ b/src/git/mod.rs
@@ -1,5 +1,164 @@
+//! Handle `--git` and related flags
+
+mod creds;
+mod gitconfig;
 mod identity_path;
-mod remote;
 mod utils;
 
-pub use utils::{create, init, remove_history, GitConfig};
+use anyhow::Result;
+use std::path::{Path, PathBuf};
+
+use git2::{build::RepoBuilder, FetchOptions, ProxyOptions, Repository, RepositoryInitOptions};
+pub use utils::clone_git_template_into_temp;
+
+// cargo-generate (as application) whant from git module:
+// 1. cloning remote
+// 2. initialize freshly generated template
+// 3. remove history from cloned template
+
+// Assumptions:
+// * `--git <url>` should only be parse in the same way as `git clone <url>` would
+// * submodules should be clone by default
+// * `.git` should be removed to make clear repository
+// * if `<url>` is the local path on system the clone should also be done the same way as `git clone` there is `--path`
+//    for different behavior
+
+// basicly we want to call:
+// git clone --recurse-submodules --depth 1 --branch <branch> <url> <tmp_dir>
+
+/// Default branch to use if not specified but required
+pub const DEFAULT_BRANCH: &str = "main";
+
+type Git2Result<T> = Result<T, git2::Error>;
+
+struct RepoCloneBuilder<'cb> {
+    builder: RepoBuilder<'cb>,
+    fetch_options: FetchOptions<'cb>,
+    identity: Option<PathBuf>,
+    url: String,
+}
+
+impl<'cb> RepoCloneBuilder<'cb> {
+    pub fn new(url: &str) -> Result<Self> {
+        let mut po = ProxyOptions::new();
+        po.auto();
+        let mut fo = FetchOptions::new();
+        fo.proxy_options(po);
+
+        let url = gitconfig::find_gitconfig()?.map_or_else(
+            || url.to_owned(),
+            |gitcfg| {
+                gitconfig::resolve_instead_url(url, gitcfg)
+                    .expect("correct configuration")
+                    .unwrap_or_else(|| url.to_owned())
+            },
+        );
+
+        Ok(Self {
+            builder: RepoBuilder::new(),
+            fetch_options: fo,
+            identity: None,
+            url,
+        })
+    }
+
+    pub fn new_with(url: &str, branch: Option<&str>, identity_path: Option<&Path>) -> Result<Self> {
+        let mut builer = Self::new(url)?;
+        if let Some(branch) = branch {
+            builer.set_branch(branch);
+        }
+
+        if let Some(identity_path) = identity_path {
+            builer.set_identity(identity_path);
+        }
+
+        Ok(builer)
+    }
+
+    pub fn set_identity(&mut self, identity_path: &Path) {
+        self.identity = Some(PathBuf::from(identity_path));
+    }
+
+    pub fn set_branch(&mut self, branch: &str) {
+        self.builder.branch(branch);
+    }
+
+    fn clone(mut self, dest_path: &Path) -> Result<Repository> {
+        if let Some(callbacks) = crate::git::creds::git_ssh_credentials_callback(self.identity)? {
+            self.fetch_options.remote_callbacks(callbacks);
+        }
+        self.builder.fetch_options(self.fetch_options);
+        self.builder
+            .clone(&self.url, dest_path)
+            .map_err(anyhow::Error::from)
+    }
+
+    pub fn clone_with_submodules(self, dest_path: &Path) -> Result<Repository> {
+        self.clone(dest_path).and_then(|repo| {
+            for mut sub in repo.submodules()? {
+                sub.update(true, None)?;
+            }
+
+            Ok(repo)
+        })
+    }
+}
+
+/// Init project_dir with fresh repository on branch
+///
+/// Arguments:
+/// - `force` - enforce a fresh git init
+pub fn init(project_dir: &Path, branch: &str, force: bool) -> Git2Result<Repository> {
+    fn just_init(project_dir: &Path, branch: &str) -> Git2Result<Repository> {
+        let mut opts = RepositoryInitOptions::new();
+        opts.bare(false);
+        opts.initial_head(branch);
+        Repository::init_opts(project_dir, &opts)
+    }
+
+    match Repository::discover(project_dir) {
+        Ok(repo) => {
+            if force {
+                Repository::open(project_dir).or_else(|_| just_init(project_dir, branch))
+            } else {
+                Ok(repo)
+            }
+        }
+        Err(_) => just_init(project_dir, branch),
+    }
+}
+
+use crate::warn;
+use remove_dir_all::remove_dir_all;
+use std::{io, ops::Sub, thread::sleep, time::Duration};
+
+/// remove context of repository by removing `.git` from filesystem
+pub fn remove_history(project_dir: &Path) -> io::Result<()> {
+    let git_dir = project_dir.join(".git");
+    if git_dir.exists() && git_dir.is_dir() {
+        let mut attempt = 0_u8;
+
+        loop {
+            attempt += 1;
+            if let Err(e) = remove_dir_all(&git_dir) {
+                if attempt == 5 {
+                    return Err(e);
+                }
+
+                if e.to_string().contains("The process cannot access the file because it is being used by another process.") {
+                    let wait_for = Duration::from_secs(2_u64.pow(attempt.sub(1).into()));
+                    warn!("Git history cleanup failed with a windows process blocking error. [Retry in {:?}]", wait_for);
+                    sleep(wait_for);
+                } else {
+                    return Err(e);
+                }
+            } else {
+                return Ok(());
+            }
+        }
+    } else {
+        //FIXME should we assume this is expected by caller?
+        // panic!("tmp panic");
+        Ok(())
+    }
+}
diff --git a/src/git/remote.rs b/src/git/remote.rs
deleted file mode 100644
index 61b24fe..0000000
--- a/src/git/remote.rs
+++ /dev/null
@@ -1,112 +0,0 @@
-use anyhow::Result;
-use console::style;
-use core::convert::{AsRef, TryFrom};
-use core::result::Result::Ok;
-use std::borrow::Cow;
-use std::path::PathBuf;
-
-use crate::git::utils::{canonicalize_path, determine_repo_kind, resolve_instead_url, RepoKind};
-use crate::info;
-
-/// url and gitconfig content
-pub struct GitUrlAndConfig<'a>(pub Cow<'a, str>, pub PathBuf);
-
-impl<'a> TryFrom<GitUrlAndConfig<'a>> for GitRemote<'a> {
-    type Error = anyhow::Error;
-
-    fn try_from(value: GitUrlAndConfig<'a>) -> Result<Self, Self::Error> {
-        let r = value.0.as_ref();
-        let url = if let Ok(Some(url)) = resolve_instead_url(r, &value.1) {
-            info!(
-                "Using gitconfig `{}` for {:?} -> {:?}",
-                style("insteadOf").bold(),
-                style(r).bold().yellow(),
-                style(&url).bold().yellow()
-            );
-            url
-        } else {
-            r.to_owned()
-        };
-
-        url.try_into()
-    }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct GitRemote<'a> {
-    url: Cow<'a, str>,
-    kind: RepoKind,
-}
-
-impl<'a> AsRef<str> for GitRemote<'a> {
-    fn as_ref(&self) -> &str {
-        self.url.as_ref()
-    }
-}
-
-impl<'a> TryFrom<Cow<'a, str>> for GitRemote<'a> {
-    type Error = anyhow::Error;
-
-    fn try_from(url: Cow<'a, str>) -> Result<Self, Self::Error> {
-        let url = url.as_ref();
-        let (remote, kind) = match determine_repo_kind(url) {
-            RepoKind::Invalid => anyhow::bail!("Invalid git remote '{}'", url),
-            RepoKind::LocalFolder => {
-                let full_path = canonicalize_path(&url)?;
-                if !full_path.exists() {
-                    anyhow::bail!("The given git remote {:?} does not exist.", url);
-                }
-                (full_path.display().to_string(), RepoKind::LocalFolder)
-            }
-            k => (url.to_string(), k),
-        };
-        Ok(Self {
-            url: Cow::from(remote),
-            kind,
-        })
-    }
-}
-
-impl<'a> TryFrom<String> for GitRemote<'a> {
-    type Error = anyhow::Error;
-
-    fn try_from(url: String) -> Result<Self, Self::Error> {
-        Cow::from(url).try_into()
-    }
-}
-
-impl<'a> TryFrom<&'a str> for GitRemote<'a> {
-    type Error = anyhow::Error;
-
-    fn try_from(url: &'a str) -> Result<Self, Self::Error> {
-        Cow::from(url).try_into()
-    }
-}
-
-impl<'a> AsRef<RepoKind> for GitRemote<'a> {
-    fn as_ref(&self) -> &RepoKind {
-        &self.kind
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_provide_a_remove() {
-        let sample_config = r#"
-[url "ssh://git@github.com:"]
-    insteadOf = https://github.com/
-"#;
-        let where_gitconfig_lives = tempfile::tempdir().unwrap();
-        let gitconfig = where_gitconfig_lives.path().join(".gitconfig");
-        std::fs::write(&gitconfig, sample_config).unwrap();
-
-        let remote = GitUrlAndConfig(Cow::from("https://github.com/sassman/t-rec-rs"), gitconfig);
-        let remote: GitRemote = remote.try_into().unwrap();
-        let remote: &str = remote.as_ref();
-
-        assert_eq!(remote, "ssh://git@github.com:sassman/t-rec-rs");
-    }
-}
diff --git a/src/git/utils.rs b/src/git/utils.rs
index 8d3a0f7..e2fc7de 100644
--- a/src/git/utils.rs
+++ b/src/git/utils.rs
@@ -1,109 +1,11 @@
-use crate::git::remote::{GitRemote, GitUrlAndConfig};
-use crate::warn;
-use crate::{copy_dir_all, info};
-
-use crate::git::identity_path::IdentityPath;
 use anyhow::Context;
 use anyhow::Result;
-use console::style;
-use git2::build::RepoBuilder;
-use git2::{Cred, FetchOptions, ProxyOptions, RemoteCallbacks, Repository, RepositoryInitOptions};
-use git2::{ErrorClass, ErrorCode};
-use git_config::file::GitConfig as GitConfigParser;
-use git_config::parser::Key;
-use remove_dir_all::remove_dir_all;
-use std::borrow::Cow;
-use std::ops::{Add, Sub};
 use std::path::{Path, PathBuf};
-use std::thread::sleep;
-use std::time::Duration;
-
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum RepoKind {
-    LocalFolder,
-    RemoteHttp,
-    RemoteHttps,
-    RemoteSsh,
-    RemoteGit,
-    Invalid,
-}
-
-/// Information to find a specific commit in a Git repository.
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum GitReference {
-    /// From a branch.
-    Branch(String),
-    /// The default branch of the repository, the reference named `HEAD`.
-    DefaultBranch,
-}
-
-pub struct GitConfig<'a> {
-    remote: GitRemote<'a>,
-    branch: GitReference,
-    identity: Option<PathBuf>,
-}
-
-impl<'a> GitConfig<'a> {
-    /// Creates a new `GitConfig` by parsing `git` as a URL or a local path.
-    pub fn new(
-        git: impl AsRef<str> + 'a,
-        branch: Option<String>,
-        identity: Option<PathBuf>,
-    ) -> Result<Self> {
-        let git = Cow::from(git.as_ref().to_string());
-        let gitconfig = find_gitconfig()?;
-        let remote = if let Some(gitconfig) = gitconfig {
-            GitRemote::try_from(GitUrlAndConfig(git, gitconfig))?
-        } else {
-            GitRemote::try_from(git)?
-        };
-
-        Ok(GitConfig {
-            remote,
-            identity,
-            branch: branch
-                .map(GitReference::Branch)
-                .unwrap_or(GitReference::DefaultBranch),
-        })
-    }
 
-    /// Creates a new `GitConfig`, first with `new` and then as a GitHub `owner/repo` remote, like
-    /// [hub].
-    ///
-    /// [hub]: https://github.com/github/hub
-    pub fn new_abbr(
-        git: impl AsRef<str>,
-        branch: Option<String>,
-        identity: Option<PathBuf>,
-    ) -> Result<Self> {
-        let full_remote = abbreviated_git_url_to_full_remote(&git);
-        Self::new(full_remote, branch.clone(), identity.clone()).or_else(|_| {
-            let full_remote = abbreviated_git_url_to_full_remote(format!("gh:{}", git.as_ref()));
-            Self::new(full_remote, branch, identity)
-        })
-    }
-}
+use git2::Repository;
+use tempfile::TempDir;
 
-pub fn abbreviated_git_url_to_full_remote(git: impl AsRef<str>) -> String {
-    let git = git.as_ref();
-    if git.len() >= 3 {
-        match &git[..3] {
-            "gl:" => format!("https://gitlab.com/{}.git", &git[3..]),
-            "bb:" => format!("https://bitbucket.org/{}.git", &git[3..]),
-            "gh:" => format!("https://github.com/{}.git", &git[3..]),
-            _ => git.to_owned(),
-        }
-    } else {
-        git.to_owned()
-    }
-}
-
-pub fn create(project_dir: &Path, args: GitConfig) -> Result<String> {
-    let branch = git_clone_all(project_dir, args)?;
-    remove_history(project_dir, None)?;
-
-    Ok(branch)
-}
+use super::{remove_history, RepoCloneBuilder};
 
 /// deals with `~/` and `$HOME/` prefixes
 pub fn canonicalize_path(p: impl AsRef<Path>) -> Result<PathBuf> {
@@ -119,6 +21,49 @@ pub fn canonicalize_path(p: impl AsRef<Path>) -> Result<PathBuf> {
     p.canonicalize().context("path does not exist")
 }
 
+/// home path wrapper
+pub fn home() -> Result<PathBuf> {
+    dirs::home_dir().context("$HOME was not set")
+}
+
+// clone git reposiotry into temp using libgit2
+pub fn clone_git_template_into_temp(
+    git: &str,
+    branch: Option<&str>,
+    identity: Option<&Path>,
+) -> anyhow::Result<(TempDir, String)> {
+    let git_clone_dir = tempfile::tempdir()?;
+
+    let builder = RepoCloneBuilder::new_with(git, branch, identity)?;
+
+    let repo = builder
+        .clone_with_submodules(git_clone_dir.path())
+        .context("Please check if the Git user / repository exists.")?;
+    let branch = get_branch_name_repo(&repo)?;
+
+    // change from git repository into normal folder.
+    remove_history(git_clone_dir.path())?;
+
+    Ok((git_clone_dir, branch))
+}
+
+/// thanks to @extrawurst for pointing this out
+/// <https://github.com/extrawurst/gitui/blob/master/asyncgit/src/sync/branch/mod.rs#L38>
+fn get_branch_name_repo(repo: &Repository) -> anyhow::Result<String> {
+    let iter = repo.branches(None)?;
+
+    for b in iter {
+        let b = b?;
+
+        if b.0.is_head() {
+            let name = b.0.name()?.unwrap_or("");
+            return Ok(name.into());
+        }
+    }
+
+    anyhow::bail!("A repo has no Head")
+}
+
 #[test]
 fn should_canonicalize() {
     #[cfg(target_os = "macos")]
@@ -148,371 +93,3 @@ fn should_canonicalize() {
         .unwrap()
         .starts_with("\\\\?\\"));
 }
-
-/// takes care of `~/` paths, defaults to `$HOME/.ssh/id_rsa` and resolves symlinks.
-fn get_private_key_path(identity: Option<PathBuf>) -> Result<IdentityPath> {
-    let private_key = identity.unwrap_or(home()?.join(".ssh/id_rsa"));
-    private_key.try_into()
-}
-
-fn git_ssh_credentials_callback<'a>(identity: Option<PathBuf>) -> Result<RemoteCallbacks<'a>> {
-    let private_key = get_private_key_path(identity)?;
-    info!(
-        "{} `{}` {}",
-        style("Using private key:").bold(),
-        style(format!("{}", &private_key)).bold().yellow(),
-        style("for git-ssh checkout").bold()
-    );
-    let mut cb = RemoteCallbacks::new();
-    cb.credentials(
-        move |_url, username_from_url: Option<&str>, _allowed_types| {
-            Cred::ssh_key(
-                username_from_url.unwrap_or("git"),
-                None,
-                private_key.as_ref(),
-                None,
-            )
-        },
-    );
-    Ok(cb)
-}
-
-/// home path wrapper
-pub fn home() -> Result<PathBuf> {
-    canonicalize_path(&dirs::home_dir().context("$HOME was not set")?)
-}
-
-/// thanks to @extrawurst for pointing this out
-/// <https://github.com/extrawurst/gitui/blob/master/asyncgit/src/sync/branch/mod.rs#L38>
-fn get_branch_name_repo(repo: &Repository) -> Result<String> {
-    let iter = repo.branches(None)?;
-
-    for b in iter {
-        let b = b?;
-
-        if b.0.is_head() {
-            let name = b.0.name()?.unwrap_or("");
-            return Ok(name.into());
-        }
-    }
-
-    anyhow::bail!("A repo has no Head")
-}
-
-fn init_all_submodules(repo: &Repository) -> Result<()> {
-    for mut sub in repo.submodules().unwrap() {
-        sub.update(true, None)?;
-    }
-
-    Ok(())
-}
-
-fn git_clone_all(project_dir: &Path, args: GitConfig) -> Result<String> {
-    let mut builder = RepoBuilder::new();
-    if let GitReference::Branch(branch_name) = &args.branch {
-        builder.branch(branch_name.as_str());
-    }
-
-    let mut fo = FetchOptions::new();
-    match args.remote.as_ref() {
-        RepoKind::LocalFolder => {}
-        RepoKind::RemoteHttp | RepoKind::RemoteHttps => {
-            let mut proxy = ProxyOptions::new();
-            proxy.auto();
-            fo.proxy_options(proxy);
-        }
-        RepoKind::RemoteSsh => {
-            let callbacks = git_ssh_credentials_callback(args.identity)?;
-            fo.remote_callbacks(callbacks);
-        }
-        RepoKind::RemoteGit => {
-            // todo: verify if that would just work as is
-        }
-        RepoKind::Invalid => {
-            unreachable!()
-        }
-    }
-    builder.fetch_options(fo);
-
-    match builder.clone(args.remote.as_ref(), project_dir) {
-        Ok(repo) => {
-            let branch = get_branch_name_repo(&repo)?;
-            init_all_submodules(&repo)?;
-            Ok(branch)
-        }
-        Err(e) => {
-            {
-                // super wired that the windows libgit2 does behave different for this case,
-                // both things actually mean the same!
-                #[cfg(windows)]
-                if e.class() == ErrorClass::Http
-                    && e.code() == ErrorCode::GenericError
-                    && e.message().contains("request failed with status code: 401")
-                {
-                    return Err(anyhow::Error::msg(
-                        "Please check if the Git user / repository exists.",
-                    ));
-                }
-                #[cfg(not(windows))]
-                if e.code() == ErrorCode::Auth && e.class() == ErrorClass::Http {
-                    return Err(anyhow::Error::msg(
-                        "Please check if the Git user / repository exists.",
-                    ));
-                }
-            }
-            if e.code() != ErrorCode::NotFound {
-                return Err(e.into());
-            }
-
-            let path: &str = args.remote.as_ref();
-            let path = Path::new(path);
-            if !path.exists() || !path.is_dir() {
-                return Err(e.into());
-            }
-
-            warn!("Template does not seem to be a git repository, using as a plain folder");
-            copy_dir_all(path, project_dir)?;
-            Ok("".to_string())
-        }
-    }
-}
-
-pub fn remove_history(project_dir: &Path, attempt: Option<u8>) -> Result<()> {
-    let git_dir = project_dir.join(".git");
-    if git_dir.exists() && git_dir.is_dir() {
-        if let Err(e) = remove_dir_all(git_dir) {
-            // see https://github.com/cargo-generate/cargo-generate/issues/375
-            if e.to_string().contains(
-                "The process cannot access the file because it is being used by another process.",
-            ) {
-                let attempt = attempt.unwrap_or(1);
-                if attempt == 5 {
-                    warn!("cargo-generate was not able to delete the git history after {} retries. Please delete the `.git` sub-folder manually", attempt);
-                    return Ok(());
-                }
-                let wait_for = Duration::from_secs(2_u64.pow(attempt.sub(1).into()));
-                warn!("Git history cleanup failed with a windows process blocking error. [Retry in {:?}]", wait_for);
-                sleep(wait_for);
-                remove_history(project_dir, Some(attempt.add(1)))?
-            }
-        }
-    }
-    Ok(())
-}
-
-pub fn init(project_dir: &Path, branch: &str, force: bool) -> Result<Repository> {
-    fn just_init(project_dir: &Path, branch: &str) -> Result<Repository> {
-        let mut opts = RepositoryInitOptions::new();
-        opts.bare(false);
-        opts.initial_head(branch);
-        Repository::init_opts(project_dir, &opts).context("Couldn't init new repository")
-    }
-
-    match Repository::discover(project_dir) {
-        Ok(repo) => {
-            if force {
-                Repository::open(project_dir).or_else(|_| just_init(project_dir, branch))
-            } else {
-                Ok(repo)
-            }
-        }
-        Err(_) => just_init(project_dir, branch),
-    }
-}
-
-/// determines what kind of repository we got
-pub fn determine_repo_kind(remote_url: &str) -> RepoKind {
-    if remote_url.starts_with("git@") || remote_url.starts_with("ssh://") {
-        RepoKind::RemoteSsh
-    } else if remote_url.starts_with("git://") {
-        RepoKind::RemoteGit
-    } else if remote_url.starts_with("http://") {
-        RepoKind::RemoteHttp
-    } else if remote_url.starts_with("https://") {
-        RepoKind::RemoteHttps
-    } else if Path::new(remote_url).exists() {
-        RepoKind::LocalFolder
-    } else {
-        RepoKind::Invalid
-    }
-}
-
-pub fn find_gitconfig() -> Result<Option<PathBuf>> {
-    let gitconfig = home().map(|home| home.join(".gitconfig"))?;
-    if gitconfig.exists() {
-        return Ok(Some(gitconfig));
-    }
-
-    Ok(None)
-}
-
-/// trades urls, to replace a given repo remote url with the right on based
-/// on the `[url]` section in the `~/.gitconfig`
-pub fn resolve_instead_url(
-    remote: impl AsRef<str>,
-    gitconfig: impl AsRef<Path>,
-) -> Result<Option<String>> {
-    let gitconfig = gitconfig.as_ref();
-    let remote = remote.as_ref().to_string();
-    let config = GitConfigParser::open(gitconfig).context("Cannot read or parse .gitconfig")?;
-    Ok(config
-        .sections_by_name_with_header("url")
-        .iter()
-        .map(|(head, body)| {
-            let url = head.subsection_name.as_ref();
-            let instead_of = body
-                .value(&Key::from("insteadOf"))
-                .map(|x| std::str::from_utf8(&x[..]).unwrap().to_owned());
-            (instead_of, url)
-        })
-        .filter(|(old, new)| new.is_some() && old.is_some())
-        .filter_map(|(old, new)| {
-            let old = old.unwrap();
-            let new = new.unwrap().to_string();
-            remote
-                .starts_with(old.as_str())
-                .then(|| remote.replace(old.as_str(), new.as_str()))
-        })
-        .next())
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use std::env::current_dir;
-    use url::Url;
-
-    const REPO_URL: &str = "https://github.com/cargo-generate/cargo-generate.git";
-    const REPO_URL_SSH: &str = "git@github.com:cargo-generate/cargo-generate.git";
-
-    #[test]
-    fn should_determine_repo_kind() {
-        for (u, k) in &[
-            (REPO_URL, RepoKind::RemoteHttps),
-            (
-                "http://github.com/cargo-generate/cargo-generate.git",
-                RepoKind::RemoteHttp,
-            ),
-            (REPO_URL_SSH, RepoKind::RemoteSsh),
-            ("./", RepoKind::LocalFolder),
-            ("ftp://foobar.bak", RepoKind::Invalid),
-        ] {
-            let kind = determine_repo_kind(u);
-            assert_eq!(&kind, k, "{} is not a {:?}", u, k);
-        }
-    }
-
-    #[test]
-    fn should_not_fail_for_ssh_remote_urls() {
-        let config = GitConfig::new(REPO_URL_SSH, None, None).unwrap();
-        let kind: &RepoKind = config.remote.as_ref();
-        assert_eq!(kind, &RepoKind::RemoteSsh);
-    }
-
-    #[test]
-    #[should_panic(expected = "Invalid git remote 'aslkdgjlaskjdglskj'")]
-    fn should_fail_for_non_existing_local_path() {
-        GitConfig::new("aslkdgjlaskjdglskj", None, None).unwrap();
-    }
-
-    #[test]
-    fn should_support_a_local_relative_path() {
-        let config = GitConfig::new("src", None, None).unwrap();
-        let remote: &str = config.remote.as_ref();
-        #[cfg(unix)]
-        assert!(
-            remote.ends_with("/src"),
-            "remote {} ends with /src",
-            &remote
-        );
-        #[cfg(windows)]
-        assert!(
-            remote.ends_with("\\src"),
-            "remote {} ends with \\src",
-            &remote
-        );
-
-        #[cfg(unix)]
-        assert!(remote.starts_with('/'), "remote {} starts with /", &remote);
-        #[cfg(windows)]
-        assert!(
-            remote.starts_with("\\\\?\\"),
-            "remote {} starts with \\\\?\\",
-            &remote
-        );
-    }
-
-    #[test]
-    fn should_support_a_local_absolute_path() {
-        // Absolute path.
-        // If this fails because you cloned this repository into a non-UTF-8 directory... all
-        // I can say is you probably had it comin'.
-        let config =
-            GitConfig::new(current_dir().unwrap().display().to_string(), None, None).unwrap();
-        let remote: &str = config.remote.as_ref();
-        #[cfg(unix)]
-        assert!(remote.starts_with('/'), "remote {} starts with /", &remote);
-        #[cfg(windows)]
-        assert!(
-            remote.starts_with("\\\\?\\"),
-            "remote {} starts with \\\\?\\ then the drive letter",
-            &remote
-        );
-    }
-
-    #[test]
-    fn should_test_happy_path() {
-        // Remote HTTPS URL.
-        let cfg = GitConfig::new(REPO_URL, Some("main".to_owned()), None).unwrap();
-        let url: &str = cfg.remote.as_ref();
-
-        assert_eq!(url, Url::parse(REPO_URL).unwrap().as_str());
-        assert_eq!(cfg.branch, GitReference::Branch("main".to_owned()));
-    }
-
-    #[test]
-    fn should_support_abbreviated_repository_short_urls_like() {
-        let config = GitConfig::new_abbr("cargo-generate/cargo-generate", None, None).unwrap();
-        let url: &str = config.remote.as_ref();
-        assert_eq!(url, Url::parse(REPO_URL).unwrap().as_str());
-    }
-
-    #[test]
-    fn should_support_abbreviated_repository_short_urls_like_for_github() {
-        let config = GitConfig::new_abbr("gh:cargo-generate/cargo-generate", None, None).unwrap();
-        let url: &str = config.remote.as_ref();
-        assert_eq!(url, Url::parse(REPO_URL).unwrap().as_str());
-    }
-
-    #[test]
-    fn should_support_bb_gl_gh_abbreviations() {
-        assert_eq!(
-            &abbreviated_git_url_to_full_remote("gh:foo/bar"),
-            "https://github.com/foo/bar.git"
-        );
-        assert_eq!(
-            &abbreviated_git_url_to_full_remote("bb:foo/bar"),
-            "https://bitbucket.org/foo/bar.git"
-        );
-        assert_eq!(
-            &abbreviated_git_url_to_full_remote("gl:foo/bar"),
-            "https://gitlab.com/foo/bar.git"
-        );
-        assert_eq!(&abbreviated_git_url_to_full_remote("foo/bar"), "foo/bar");
-    }
-
-    #[test]
-    fn should_resolve_instead_url() {
-        let sample_config = r#"
-[url "ssh://git@github.com:"]
-    insteadOf = https://github.com/
-"#;
-        let where_gitconfig_lives = tempfile::tempdir().unwrap();
-        let gitconfig = where_gitconfig_lives.path().join(".gitconfig");
-        std::fs::write(&gitconfig, sample_config).unwrap();
-
-        // SSH, aka git@github.com: or ssh://git@github.com/
-        let x = resolve_instead_url("https://github.com/foo/bar.git", &gitconfig).unwrap();
-        assert_eq!(x.unwrap().as_str(), "ssh://git@github.com:foo/bar.git")
-    }
-}
diff --git a/src/lib.rs b/src/lib.rs
index 3406c6f..fa3920f 100755
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -39,13 +39,15 @@ mod project_variables;
 mod template;
 mod template_filters;
 mod template_variables;
+mod user_parsed_input;
 
 pub use args::*;
 
 use anyhow::{anyhow, bail, Context, Result};
 use config::{locate_template_configs, Config, CONFIG_FILE_NAME};
 use console::style;
-use favorites::{list_favorites, resolve_favorite_args_and_default_values};
+use favorites::list_favorites;
+use git::DEFAULT_BRANCH;
 use hooks::{execute_post_hooks, execute_pre_hooks};
 use ignore_me::remove_dir_files;
 use interactive::prompt_for_variable;
@@ -59,15 +61,16 @@ use std::{
     path::{Path, PathBuf},
     rc::Rc,
 };
+use user_parsed_input::{TemplateLocation, UserParsedInput};
 
 use tempfile::TempDir;
 
+use crate::template_variables::load_env_and_args_template_values;
 use crate::{
     app_config::{app_config_path, AppConfig},
     project_variables::ConversionError,
     template_variables::{CrateType, ProjectName},
 };
-use crate::{git::GitConfig, template_variables::resolve_template_values};
 
 /// # Panics
 pub fn generate(mut args: Args) -> Result<()> {
@@ -96,9 +99,12 @@ pub fn generate(mut args: Args) -> Result<()> {
         }
     }
 
-    let default_values = resolve_favorite_args_and_default_values(&app_config, &mut args)?;
+    let mut source_template = UserParsedInput::try_from_args_and_config(&app_config, &args);
+    source_template
+        .template_values_mut()
+        .extend(load_env_and_args_template_values(&args)?);
 
-    let (template_base_dir, template_folder, branch) = prepare_local_template(&args)?;
+    let (template_base_dir, template_folder, branch) = prepare_local_template(&source_template)?;
 
     let template_config = Config::from_path(
         &locate_template_file(CONFIG_FILE_NAME, &template_base_dir, &template_folder).ok(),
@@ -106,7 +112,6 @@ pub fn generate(mut args: Args) -> Result<()> {
     .unwrap_or_default();
 
     check_cargo_generate_version(&template_config)?;
-    let template_values = resolve_template_values(default_values, &args)?;
 
     let project_name = resolve_project_name(&args)?;
     let project_dir = resolve_project_dir(&project_name, &args)?;
@@ -121,7 +126,7 @@ pub fn generate(mut args: Args) -> Result<()> {
     expand_template(
         &project_name,
         &template_folder,
-        &template_values,
+        source_template.template_values(),
         template_config,
         &args,
     )?;
@@ -151,58 +156,34 @@ pub fn generate(mut args: Args) -> Result<()> {
     Ok(())
 }
 
-fn prepare_local_template(args: &Args) -> Result<(TempDir, PathBuf, String), anyhow::Error> {
-    let (template_base_dir, template_folder, branch) = match (&args.git, &args.path) {
-        (Some(_), None) => {
-            let (template_base_dir, branch) = clone_git_template_into_temp(args)?;
-            let template_folder = resolve_template_dir(&template_base_dir, args)?;
-            (template_base_dir, template_folder, branch)
-        }
-        (None, Some(_)) => {
-            let template_base_dir = copy_path_template_into_temp(args)?;
-            let branch = args.branch.clone().unwrap_or_else(|| String::from("main"));
-            let template_folder =
-                auto_locate_template_dir(template_base_dir.path(), prompt_for_variable)?;
-            (template_base_dir, template_folder, branch)
-        }
-        _ => bail!(
-            "{} {} {} {} {}",
-            emoji::ERROR,
-            style("Please specify either").bold(),
-            style("--git <repo>").bold().yellow(),
-            style("or").bold(),
-            style("--path <path>").bold().yellow(),
-        ),
-    };
+fn prepare_local_template(
+    source_template: &UserParsedInput,
+) -> Result<(TempDir, PathBuf, String), anyhow::Error> {
+    let (temp_dir, branch) = get_source_template_into_temp(source_template.location())?;
+    let template_folder = resolve_template_dir(&temp_dir, source_template.subfolder())?;
 
-    Ok((template_base_dir, template_folder, branch))
+    Ok((temp_dir, template_folder, branch))
 }
 
-fn check_cargo_generate_version(template_config: &Config) -> Result<(), anyhow::Error> {
-    if let Config {
-        template:
-            Some(config::TemplateConfig {
-                cargo_generate_version: Some(requirement),
-                ..
-            }),
-        ..
-    } = template_config
-    {
-        let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))?;
-        if !requirement.matches(&version) {
-            bail!(
-                "{} {} {} {} {}",
-                emoji::ERROR,
-                style("Required cargo-generate version not met. Required:")
-                    .bold()
-                    .red(),
-                style(requirement).yellow(),
-                style(" was:").bold().red(),
-                style(version).yellow(),
-            );
+fn get_source_template_into_temp(
+    template_location: &TemplateLocation,
+) -> Result<(TempDir, String)> {
+    let temp_dir: TempDir;
+    let branch: String;
+    match template_location {
+        TemplateLocation::Git(git) => {
+            let (temp_dir2, branch2) =
+                git::clone_git_template_into_temp(git.url(), git.branch(), git.identity())?;
+            temp_dir = temp_dir2;
+            branch = branch2;
         }
-    }
-    Ok(())
+        TemplateLocation::Path(path) => {
+            temp_dir = copy_path_template_into_temp(path)?;
+            branch = String::from(DEFAULT_BRANCH); // FIXME is here any reason to set branch when path is used?
+        }
+    };
+
+    Ok((temp_dir, branch))
 }
 
 fn resolve_project_name(args: &Args) -> Result<ProjectName> {
@@ -220,49 +201,46 @@ fn resolve_project_name(args: &Args) -> Result<ProjectName> {
     }
 }
 
-fn resolve_template_dir(template_base_dir: &TempDir, args: &Args) -> Result<PathBuf> {
-    match &args.subfolder {
-        Some(subfolder) => {
-            let template_base_dir = fs::canonicalize(template_base_dir.path())?;
-            let template_dir =
-                fs::canonicalize(template_base_dir.join(subfolder)).with_context(|| {
-                    format!(
-                        "not able to find subfolder '{}' in repository {}",
-                        subfolder,
-                        args.git
-                            .as_ref()
-                            .map(|v| v.to_owned())
-                            .unwrap_or_else(|| args.path.as_ref().unwrap().display().to_string()),
-                    )
-                })?;
-
-            if !template_dir.starts_with(&template_base_dir) {
-                return Err(anyhow!(
-                    "{} {} {}",
-                    emoji::ERROR,
-                    style("Subfolder Error:").bold().red(),
-                    style("Invalid subfolder. Must be part of the template folder structure.")
-                        .bold()
-                        .red(),
-                ));
-            }
-            if !template_dir.is_dir() {
-                return Err(anyhow!(
-                    "{} {} {}",
-                    emoji::ERROR,
-                    style("Subfolder Error:").bold().red(),
-                    style("The specified subfolder must be a valid folder.")
-                        .bold()
-                        .red(),
-                ));
-            }
+fn resolve_template_dir(template_base_dir: &TempDir, subfolder: Option<&str>) -> Result<PathBuf> {
+    if let Some(subfolder) = subfolder {
+        let template_base_dir = fs::canonicalize(template_base_dir.path())?;
+        let template_dir =
+            fs::canonicalize(template_base_dir.join(subfolder)).with_context(|| {
+                format!(
+                    "not able to find subfolder '{}' in source template",
+                    subfolder
+                )
+            })?;
 
-            Ok(auto_locate_template_dir(
-                &template_dir,
-                prompt_for_variable,
-            )?)
+        // make sure subfolder is not `../../subfolder`
+        if !template_dir.starts_with(&template_base_dir) {
+            return Err(anyhow!(
+                "{} {} {}",
+                emoji::ERROR,
+                style("Subfolder Error:").bold().red(),
+                style("Invalid subfolder. Must be part of the template folder structure.")
+                    .bold()
+                    .red(),
+            ));
         }
-        None => auto_locate_template_dir(template_base_dir.path(), prompt_for_variable),
+
+        if !template_dir.is_dir() {
+            return Err(anyhow!(
+                "{} {} {}",
+                emoji::ERROR,
+                style("Subfolder Error:").bold().red(),
+                style("The specified subfolder must be a valid folder.")
+                    .bold()
+                    .red(),
+            ));
+        }
+
+        Ok(auto_locate_template_dir(
+            &template_dir,
+            prompt_for_variable,
+        )?)
+    } else {
+        auto_locate_template_dir(template_base_dir.path(), prompt_for_variable)
     }
 }
 
@@ -292,42 +270,14 @@ fn auto_locate_template_dir(
     }
 }
 
-fn copy_path_template_into_temp(args: &Args) -> Result<TempDir> {
+fn copy_path_template_into_temp(src_path: &Path) -> Result<TempDir> {
     let path_clone_dir = tempfile::tempdir()?;
-    copy_dir_all(
-        args.path
-            .as_ref()
-            .with_context(|| "Missing option git, path or a favorite")?,
-        path_clone_dir.path(),
-    )?;
-    git::remove_history(path_clone_dir.path(), None)?;
+    copy_dir_all(src_path, path_clone_dir.path())?;
+    git::remove_history(path_clone_dir.path())?;
 
     Ok(path_clone_dir)
 }
 
-fn clone_git_template_into_temp(args: &Args) -> Result<(TempDir, String)> {
-    let git_clone_dir = tempfile::tempdir()?;
-
-    let remote = args
-        .git
-        .clone()
-        .with_context(|| "Missing option git, path or a favorite")?;
-
-    let git_config =
-        GitConfig::new_abbr(remote, args.branch.to_owned(), args.ssh_identity.clone())?;
-
-    let branch = git::create(git_clone_dir.path(), git_config).map_err(|e| {
-        anyhow!(
-            "{} {} {}",
-            emoji::ERROR,
-            style("Git Error:").bold().red(),
-            style(e).bold().red(),
-        )
-    })?;
-
-    Ok((git_clone_dir, branch))
-}
-
 pub(crate) fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
     fn check_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
         if !dst.as_ref().exists() {
@@ -597,6 +547,33 @@ fn rename_warning(name: &ProjectName) {
     }
 }
 
+fn check_cargo_generate_version(template_config: &Config) -> Result<(), anyhow::Error> {
+    if let Config {
+        template:
+            Some(config::TemplateConfig {
+                cargo_generate_version: Some(requirement),
+                ..
+            }),
+        ..
+    } = template_config
+    {
+        let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))?;
+        if !requirement.matches(&version) {
+            bail!(
+                "{} {} {} {} {}",
+                emoji::ERROR,
+                style("Required cargo-generate version not met. Required:")
+                    .bold()
+                    .red(),
+                style(requirement).yellow(),
+                style(" was:").bold().red(),
+                style(version).yellow(),
+            );
+        }
+    }
+    Ok(())
+}
+
 #[cfg(test)]
 mod tests {
     use crate::{auto_locate_template_dir, project_variables::VarInfo};
diff --git a/src/template_values.rs b/src/template_values.rs
new file mode 100644
index 0000000..bc14d46
--- /dev/null
+++ b/src/template_values.rs
@@ -0,0 +1,2 @@
+/// represents key value storage of defined values
+pub type TemplateValues = HashMap<String, toml::Value>;
diff --git a/src/template_variables/mod.rs b/src/template_variables/mod.rs
index 95b092f..3c2e5f1 100644
--- a/src/template_variables/mod.rs
+++ b/src/template_variables/mod.rs
@@ -17,42 +17,39 @@ pub use crate_type::CrateType;
 pub use os_arch::get_os_arch;
 pub use project_name::ProjectName;
 
-pub fn resolve_template_values(
-    favorite_values: Option<HashMap<String, toml::Value>>,
-    args: &Args,
-) -> Result<HashMap<String, Value>> {
-    let mut values = favorite_values.unwrap_or_default();
-
-    values.extend(
-        std::env::var("CARGO_GENERATE_TEMPLATE_VALUES_FILE")
-            .ok()
-            .map_or(Ok(Default::default()), |path| {
-                read_template_values_file(Path::new(&path))
-            })?,
-    );
+fn load_env_template_values() -> Result<HashMap<String, toml::Value>> {
+    //FIXME: use this variable to be in sync with args
+    let mut values = std::env::var("CARGO_GENERATE_TEMPLATE_VALUES_FILE")
+        .ok()
+        .map_or(Ok(Default::default()), |path| {
+            read_template_values_file(Path::new(&path))
+        })?;
 
     values.extend(std::env::vars().filter_map(|(key, value)| {
         key.strip_prefix("CARGO_GENERATE_VALUE_")
             .map(|key| (key.to_lowercase(), Value::from(value)))
     }));
 
-    values.extend(
-        args.template_values_file
-            .as_ref()
-            .map(Path::new)
-            .map_or(Ok(Default::default()), |path| {
-                read_template_values_file(path)
-            })?,
-    );
+    Ok(values)
+}
 
-    add_cli_defined_values(&mut values, &args.define)?;
+fn load_args_template_values(args: &Args) -> Result<HashMap<String, toml::Value>> {
+    let mut values = args
+        .template_values_file
+        .as_ref()
+        .map(Path::new)
+        .map_or(Ok(Default::default()), |path| {
+            read_template_values_file(path)
+        })?;
 
+    values.extend(read_template_values_from_definitions(&args.define)?);
     Ok(values)
 }
 
-#[derive(Deserialize, Debug, PartialEq)]
-struct TemplateValuesToml {
-    pub(crate) values: HashMap<String, toml::Value>,
+pub fn load_env_and_args_template_values(args: &Args) -> Result<HashMap<String, toml::Value>> {
+    let mut template_variables = load_env_template_values()?;
+    template_variables.extend(load_args_template_values(args)?);
+    Ok(template_variables)
 }
 
 fn read_template_values_file(path: &Path) -> Result<HashMap<String, Value>> {
@@ -69,109 +66,101 @@ fn read_template_values_file(path: &Path) -> Result<HashMap<String, Value>> {
     }
 }
 
-fn add_cli_defined_values<S: AsRef<str> + Display>(
-    template_values: &mut HashMap<String, Value>,
+fn read_template_values_from_definitions<S: AsRef<str> + Display>(
     definitions: &[S],
-) -> Result<()> {
+) -> Result<HashMap<String, toml::Value>> {
+    let mut values = HashMap::with_capacity(definitions.len());
     let key_value_regex = Regex::new(r"^([a-zA-Z]+[a-zA-Z0-9\-_]*)\s*=\s*(.+)$").unwrap();
 
-    definitions
-        .iter()
-        .try_fold(
-            template_values,
-            |template_values, definition| match key_value_regex.captures(definition.as_ref()) {
-                Some(cap) => {
-                    let key = cap.get(1).unwrap().as_str().to_string();
-                    let value = cap.get(2).unwrap().as_str().to_string();
-                    println!("{} => '{}'", key, value);
-                    template_values.insert(key, Value::from(value));
-                    Ok(template_values)
-                }
-                None => Err(anyhow::anyhow!(
-                    "{} {} {}",
-                    emoji::ERROR,
-                    style("Failed to parse value:").bold().red(),
-                    style(definition).bold().red(),
-                )),
-            },
-        )?;
-    Ok(())
+    definitions.iter().try_fold(
+        &mut values,
+        |template_values, definition| match key_value_regex.captures(definition.as_ref()) {
+            Some(cap) => {
+                let key = cap.get(1).unwrap().as_str().to_string();
+                let value = cap.get(2).unwrap().as_str().to_string();
+                println!("{} => '{}'", key, value);
+                template_values.insert(key, Value::from(value));
+                Ok(template_values)
+            }
+            None => Err(anyhow::anyhow!(
+                "{} {} {}",
+                emoji::ERROR,
+                style("Failed to parse value:").bold().red(),
+                style(definition).bold().red(),
+            )),
+        },
+    )?;
+    Ok(values)
+}
+
+#[derive(Deserialize, Debug, PartialEq)]
+struct TemplateValuesToml {
+    pub(crate) values: HashMap<String, toml::Value>,
 }
 
 #[cfg(test)]
 mod test {
-    use super::add_cli_defined_values;
-    use std::collections::HashMap;
+    use super::read_template_values_from_definitions;
 
     #[test]
     fn names_must_start_with_word_char() {
-        let mut template_values = HashMap::new();
         let definitions = vec!["0key=42"];
-        let result = add_cli_defined_values(&mut template_values, &definitions);
+        let result = read_template_values_from_definitions(&definitions);
         assert!(result.is_err());
 
         let definitions = vec!["$key=42"];
-        let result = add_cli_defined_values(&mut template_values, &definitions);
+        let result = read_template_values_from_definitions(&definitions);
         assert!(result.is_err());
 
         let definitions = vec!["-key=42"];
-        let result = add_cli_defined_values(&mut template_values, &definitions);
+        let result = read_template_values_from_definitions(&definitions);
         assert!(result.is_err());
 
         let definitions = vec!["_key=42"];
-        let result = add_cli_defined_values(&mut template_values, &definitions);
+        let result = read_template_values_from_definitions(&definitions);
         assert!(result.is_err());
     }
 
     #[test]
     fn names_may_contain_digits() {
-        let mut template_values = HashMap::new();
         let definitions = vec!["my0123456789key=42"];
-        let result = add_cli_defined_values(&mut template_values, &definitions);
-        assert!(result.is_ok());
+        let result = read_template_values_from_definitions(&definitions).unwrap();
 
-        let val = template_values["my0123456789key"].as_str().unwrap();
+        let val = result["my0123456789key"].as_str().unwrap();
         assert_eq!(val, "42");
     }
 
     #[test]
     fn names_may_contain_dash() {
-        let mut template_values = HashMap::new();
         let definitions = vec!["my-key=42"];
-        let result = add_cli_defined_values(&mut template_values, &definitions);
-        assert!(result.is_ok());
+        let result = read_template_values_from_definitions(&definitions).unwrap();
 
-        let val = template_values["my-key"].as_str().unwrap();
+        let val = result["my-key"].as_str().unwrap();
         assert_eq!(val, "42");
     }
 
     #[test]
     fn names_may_contain_underscore() {
-        let mut template_values = HashMap::new();
         let definitions = vec!["my_key=42"];
-        let result = add_cli_defined_values(&mut template_values, &definitions);
-        assert!(result.is_ok());
+        let result = read_template_values_from_definitions(&definitions).unwrap();
 
-        let val = template_values["my_key"].as_str().unwrap();
+        let val = result["my_key"].as_str().unwrap();
         assert_eq!(val, "42");
     }
 
     #[test]
     fn spaces_are_not_allowed_in_names() {
-        let mut template_values = HashMap::new();
         let definitions = vec!["my key=42"];
-        let result = add_cli_defined_values(&mut template_values, &definitions);
+        let result = read_template_values_from_definitions(&definitions);
         assert!(result.is_err());
     }
 
     #[test]
     fn spaces_around_assignment_is_ok() {
-        let mut template_values = HashMap::new();
         let definitions = vec!["key   =      42"];
-        let result = add_cli_defined_values(&mut template_values, &definitions);
-        assert!(result.is_ok());
+        let result = read_template_values_from_definitions(&definitions).unwrap();
 
-        let val = template_values["key"].as_str().unwrap();
+        let val = result["key"].as_str().unwrap();
         assert_eq!(val, "42");
     }
 }
diff --git a/src/user_parsed_input.rs b/src/user_parsed_input.rs
new file mode 100644
index 0000000..8c5df05
--- /dev/null
+++ b/src/user_parsed_input.rs
@@ -0,0 +1,293 @@
+//! Input from user but after prase
+
+use std::{
+    collections::HashMap,
+    path::{Path, PathBuf},
+};
+
+use console::style;
+use regex::Regex;
+
+use crate::{app_config::AppConfig, warn, Args};
+
+// Contains parsed information from user.
+pub struct UserParsedInput {
+    // from where clone or copy template?
+    template_location: TemplateLocation,
+    // if template_location contains many templates user already specified one
+    subfolder: Option<String>,
+    // all values that user defined through:
+    // 1. envirnoment variables
+    // 2. configuration file
+    // 3. cli arguments --define
+    template_values: HashMap<String, toml::Value>,
+    //TODO:
+    // 1. This structure should be used instead of args
+    // 2. This struct can contains internaly args and app_config to not confuse
+    //    other developer with parsing configuration and args by themself
+}
+
+impl UserParsedInput {
+    fn new(
+        template_location: impl Into<TemplateLocation>,
+        subfolder: Option<String>,
+        default_values: HashMap<String, toml::Value>,
+    ) -> Self {
+        Self {
+            template_location: template_location.into(),
+            subfolder,
+            template_values: default_values,
+        }
+    }
+
+    /// Try create `UserParsedInput` reading in order [`AppConfig`] and [`Args`]
+    ///
+    /// # Panics
+    /// 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: &Args) -> Self {
+        // --git and --path can not be set at the same time
+        assert!(args.git.is_none() || args.path.is_none());
+        let mut default_values = app_config.values.clone().unwrap_or_default();
+        let ssh_identity = app_config
+            .defaults
+            .as_ref()
+            .and_then(|dcfg| dcfg.ssh_identity.clone())
+            .or_else(|| args.ssh_identity.clone());
+
+        // --git
+        if let Some(git_url) = &args.git {
+            assert!(args.subfolder.is_none());
+
+            let git_user_in = GitUserInput::new(
+                git_url,
+                args.branch.clone(),
+                ssh_identity,
+                args.force_git_init,
+            );
+            return Self::new(git_user_in, args.favorite.clone(), default_values);
+        }
+
+        // --path
+        if let Some(path) = &args.path {
+            assert!(args.subfolder.is_none());
+
+            return Self::new(path, args.favorite.clone(), default_values);
+        }
+
+        // check if favorite is favorite configuration
+        assert!(args.favorite.is_some());
+        let fav_name = args.favorite.as_ref().unwrap();
+
+        if let Some(fav_cfg) = app_config.get_favorite_cfg(fav_name) {
+            assert!(fav_cfg.git.is_none() || fav_cfg.path.is_none());
+
+            let temp_location = fav_cfg.git.as_ref().map_or_else(
+                || fav_cfg.path.as_ref().map(TemplateLocation::from).unwrap(),
+                |git_url| {
+                    let branch = args.branch.clone().or_else(|| fav_cfg.branch.clone());
+                    let git_user_input =
+                        GitUserInput::new(git_url, branch, ssh_identity, args.force_git_init);
+
+                    TemplateLocation::from(git_user_input)
+                },
+            );
+
+            if let Some(fav_default_values) = &fav_cfg.values {
+                default_values.extend(fav_default_values.clone());
+            }
+
+            return Self::new(
+                temp_location,
+                args.subfolder.clone().or_else(|| fav_cfg.subfolder.clone()),
+                default_values,
+            );
+        }
+
+        // there is no specified favorite in configuration
+        // this part try to guess what user wanted in order:
+
+        // 1. look for abbrevations like gh:, gl: etc.
+        let temp_location = abbreviated_git_url_to_full_remote(fav_name).map(|git_url| {
+            let git_user_in = GitUserInput::with_git_url_and_args(&git_url, args);
+            TemplateLocation::from(git_user_in)
+        });
+
+        // 2. check if template directory exist
+        let temp_location =
+            temp_location.or_else(|| local_path(fav_name).map(TemplateLocation::from));
+
+        // 3. check if the input is in form org/repo<> (map to github)
+        let temp_location = temp_location.or_else(|| {
+            abbreviated_github(fav_name).map(|git_url| {
+                let git_user_in = GitUserInput::with_git_url_and_args(&git_url, args);
+                TemplateLocation::from(git_user_in)
+            })
+        });
+
+        // 4. assume user wanted use --git
+        let temp_location = temp_location.unwrap_or_else(|| {
+            let git_user_in = GitUserInput::new(
+                fav_name,
+                args.branch.clone(),
+                ssh_identity,
+                args.force_git_init,
+            );
+            TemplateLocation::from(git_user_in)
+        });
+
+        // Print information what happend to user
+        let location_msg = match &temp_location {
+            TemplateLocation::Git(git_user_input) => {
+                format!("git repository: {}", style(git_user_input.url()).bold())
+            }
+            TemplateLocation::Path(path) => {
+                format!("local path: {}", style(path.display()).bold())
+            }
+        };
+        warn!(
+            "Favorite `{}` not found in config, using it as a {}",
+            style(&fav_name).bold(),
+            location_msg
+        );
+
+        Self::new(temp_location, args.subfolder.clone(), default_values)
+    }
+
+    pub const fn location(&self) -> &TemplateLocation {
+        &self.template_location
+    }
+
+    pub fn subfolder(&self) -> Option<&str> {
+        self.subfolder.as_deref()
+    }
+
+    pub const fn template_values(&self) -> &HashMap<String, toml::Value> {
+        &self.template_values
+    }
+
+    pub fn template_values_mut(&mut self) -> &mut HashMap<String, toml::Value> {
+        &mut self.template_values
+    }
+}
+
+// favorite can be in form with abbrevation what means that input is git repositoru
+pub fn abbreviated_git_url_to_full_remote(git: impl AsRef<str>) -> Option<String> {
+    let git = git.as_ref();
+    if git.len() >= 3 {
+        match &git[..3] {
+            "gl:" => Some(format!("https://gitlab.com/{}.git", &git[3..])),
+            "bb:" => Some(format!("https://bitbucket.org/{}.git", &git[3..])),
+            "gh:" => Some(format!("https://github.com/{}.git", &git[3..])),
+            _ => None,
+        }
+    } else {
+        None
+    }
+}
+
+// favorite can be in form of org/repo what should be parsed as github.com
+pub fn abbreviated_github(fav: &str) -> Option<String> {
+    let org_repo_regex = Regex::new(r"^[a-zA-Z0-9_]+/[a-zA-Z0-9_%-]+$").unwrap();
+    org_repo_regex
+        .is_match(fav)
+        .then(|| format!("https://github.com/{fav}.git"))
+}
+
+pub fn local_path(fav: &str) -> Option<PathBuf> {
+    let path = PathBuf::from(fav);
+    (path.exists() && path.is_dir()).then(|| path)
+}
+
+// Template should be cloned with git
+pub struct GitUserInput {
+    url: String,
+    branch: Option<String>,
+    identity: Option<PathBuf>,
+    _force_init: bool,
+}
+
+impl GitUserInput {
+    fn new(url: &str, branch: Option<String>, identity: Option<PathBuf>, force_init: bool) -> Self {
+        Self {
+            url: url.to_owned(),
+            branch,
+            identity,
+            _force_init: force_init,
+        }
+    }
+
+    // when git was used as abbreviation but other flags still could be passed
+    fn with_git_url_and_args(url: &str, args: &Args) -> Self {
+        Self::new(
+            url,
+            args.branch.clone(),
+            args.ssh_identity.clone(),
+            args.force_git_init,
+        )
+    }
+
+    pub fn url(&self) -> &str {
+        self.url.as_ref()
+    }
+
+    pub fn branch(&self) -> Option<&str> {
+        self.branch.as_deref()
+    }
+
+    pub fn identity(&self) -> Option<&Path> {
+        self.identity.as_deref()
+    }
+}
+
+// Distinguish between plain copy and clone
+pub enum TemplateLocation {
+    Git(GitUserInput),
+    Path(PathBuf),
+}
+
+impl From<GitUserInput> for TemplateLocation {
+    fn from(source: GitUserInput) -> Self {
+        Self::Git(source)
+    }
+}
+
+impl<T> From<T> for TemplateLocation
+where
+    T: AsRef<Path>,
+{
+    fn from(source: T) -> Self {
+        Self::Path(PathBuf::from(source.as_ref()))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn should_support_bb_gl_gh_abbreviations() {
+        assert_eq!(
+            &abbreviated_git_url_to_full_remote("gh:foo/bar").unwrap(),
+            "https://github.com/foo/bar.git"
+        );
+        assert_eq!(
+            &abbreviated_git_url_to_full_remote("bb:foo/bar").unwrap(),
+            "https://bitbucket.org/foo/bar.git"
+        );
+        assert_eq!(
+            &abbreviated_git_url_to_full_remote("gl:foo/bar").unwrap(),
+            "https://gitlab.com/foo/bar.git"
+        );
+        assert!(&abbreviated_git_url_to_full_remote("foo/bar").is_none());
+    }
+
+    #[test]
+    fn should_appreviation_org_repo_to_github() {
+        assert_eq!(
+            &abbreviated_github("org/repo").unwrap(),
+            "https://github.com/org/repo.git"
+        );
+        assert!(&abbreviated_github("path/to/a/sth").is_none());
+    }
+}


Test patch:
diff --git a/tests/integration/basics.rs b/tests/integration/basics.rs
index 89c2bf7..bffe035 100644
--- a/tests/integration/basics.rs
+++ b/tests/integration/basics.rs
@@ -33,9 +33,14 @@ version = "0.1.0"
         .current_dir(&dir.path())
         .assert()
         .success()
-        .stdout(predicates::str::contains("Done!").from_utf8().and(
-            predicates::str::contains("Template does not seem to be a git repository").from_utf8(),
-        ));
+        .stdout(
+            predicates::str::contains("Done!")
+                .and(predicates::str::contains(format!(
+                    "Favorite `{}` not found in config, using it as a local path",
+                    template.path().display()
+                )))
+                .from_utf8(),
+        );
 
     let repo = git2::Repository::open(&dir.path().join("foobar-project")).unwrap();
     let references = repo.references().unwrap().count();
@@ -1382,86 +1387,7 @@ fn error_message_for_invalid_repo_or_user() {
         .assert()
         .failure()
         .stderr(
-            predicates::str::contains(
-                r#"Git Error: Please check if the Git user / repository exists."#,
-            )
-            .from_utf8(),
+            predicates::str::contains(r#"Error: Please check if the Git user / repository exists"#)
+                .from_utf8(),
         );
 }
-
-#[cfg(test)]
-#[cfg(unix)]
-mod ssh_remote {
-    use super::*;
-
-    #[test]
-    #[ignore]
-    // for now only locally working
-    fn it_should_support_a_public_repo() {
-        let dir = tmp_dir().build();
-
-        binary()
-            .arg("generate")
-            .arg("--git")
-            .arg("git@github.com:ashleygwilliams/wasm-pack-template.git")
-            .arg("--name")
-            .arg("foobar-project")
-            .current_dir(&dir.path())
-            .assert()
-            .success()
-            .stdout(predicates::str::contains("Done!").from_utf8());
-
-        let cargo_toml = dir.read("foobar-project/Cargo.toml");
-        assert!(cargo_toml.contains("foobar-project"));
-    }
-
-    #[test]
-    #[ignore]
-    // for now only locally working
-    fn it_should_support_a_private_repo() {
-        let dir = tmp_dir().build();
-
-        binary()
-            .arg("generate")
-            .arg("--git")
-            .arg("git@github.com:cargo-generate/wasm-pack-template.git")
-            .arg("--name")
-            .arg("foobar-project")
-            .current_dir(&dir.path())
-            .assert()
-            .success()
-            .stdout(predicates::str::contains("Done!").from_utf8());
-
-        let cargo_toml = dir.read("foobar-project/Cargo.toml");
-        assert!(cargo_toml.contains("foobar-project"));
-    }
-
-    #[test]
-    #[ignore]
-    // for now only locally working
-    fn it_should_support_a_custom_ssh_key() {
-        let dir = tmp_dir().build();
-
-        binary()
-            .arg("generate")
-            .arg("-i")
-            .arg("~/workspaces/rust/cargo-generate-org/.env/id_rsa_ci")
-            .arg("--git")
-            .arg("git@github.com:cargo-generate/wasm-pack-template.git")
-            .arg("--name")
-            .arg("foobar-project")
-            .current_dir(&dir.path())
-            .assert()
-            .success()
-            .stdout(
-                predicates::str::contains("Using private key:")
-                    .and(predicates::str::contains(
-                        "cargo-generate-org/.env/id_rsa_ci",
-                    ))
-                    .from_utf8(),
-            );
-
-        let cargo_toml = dir.read("foobar-project/Cargo.toml");
-        assert!(cargo_toml.contains("foobar-project"));
-    }
-}
diff --git a/tests/integration/config_file/defaults.rs b/tests/integration/config_file/defaults.rs
index a5f200b..0a83372 100644
--- a/tests/integration/config_file/defaults.rs
+++ b/tests/integration/config_file/defaults.rs
@@ -9,7 +9,7 @@ use indoc::indoc;
 
 #[test]
 fn it_uses_ssh_identity_from_defaults_config() {
-    let working_dir = tmp_dir().file("fake_ssh", "fake stuff").build();
+    let working_dir = tmp_dir().file("fake_ssh_identity", "fake stuff").build();
     let fake_ssh_id = working_dir.path().join("fake_ssh_identity");
     let config_dir = tmp_dir()
         .file(
@@ -22,7 +22,7 @@ fn it_uses_ssh_identity_from_defaults_config() {
                 id = fake_ssh_id.display().to_string().escape_default()
             ),
         )
-        .file("fake_ssh_identity", "random foo")
+        .file("fake_ssh", "random foo") // FIXME: what is for?
         .build();
     let some_template = create_template("some-template");
 
diff --git a/tests/integration/config_file/favorites.rs b/tests/integration/config_file/favorites.rs
index 015a5b5..d3c8e8e 100644
--- a/tests/integration/config_file/favorites.rs
+++ b/tests/integration/config_file/favorites.rs
@@ -170,7 +170,10 @@ fn favorites_default_to_git_if_not_defined() {
         .current_dir(&working_dir.path())
         .assert()
         .failure()
-        .stderr(predicates::str::contains(r#"status code: 404"#).from_utf8());
+        .stderr(
+            predicates::str::contains(r#"Please check if the Git user / repository exists"#)
+                .from_utf8(),
+        );
 }
 
 #[test]
diff --git a/tests/integration/main.rs b/tests/integration/main.rs
index a90d112..7f5cb86 100644
--- a/tests/integration/main.rs
+++ b/tests/integration/main.rs
@@ -7,3 +7,4 @@ mod filenames;
 mod git;
 mod hooks;
 mod library;
+mod online;
diff --git a/tests/integration/online.rs b/tests/integration/online.rs
new file mode 100644
index 0000000..856c078
--- /dev/null
+++ b/tests/integration/online.rs
@@ -0,0 +1,155 @@
+//! Thest that use real connection and working third part service.
+//!
+//! The test are ignored by default
+//! You can run them with:
+//! ```
+//! cargo test -- --include-ignored
+//! ````
+
+use predicates::prelude::*;
+
+use crate::helpers::project::binary;
+use crate::helpers::project_builder::tmp_dir;
+
+use assert_cmd::prelude::*;
+
+#[test]
+#[ignore]
+fn git_flag_can_be_skipped_and_cargo_will_use_correct_implementation() {
+    // with --git
+    let dir = tmp_dir().build();
+    binary()
+        .arg("generate")
+        .arg("--name")
+        .arg("my-proj")
+        .arg("--init")
+        .arg("git://github.com/ashleygwilliams/wasm-pack-template")
+        .current_dir(&dir.path())
+        .assert()
+        .success()
+        .stdout(predicates::str::contains("Done!").from_utf8());
+}
+
+#[test]
+#[ignore]
+fn plain_git_repo_works() {
+    let possible_urls = vec![
+        "git://github.com/ashleygwilliams/wasm-pack-template",
+        "git://github.com/ashleygwilliams/wasm-pack-template.git",
+        "https://github.com/ashleygwilliams/wasm-pack-template.git",
+        "https://github.com/ashleygwilliams/wasm-pack-template",
+        "http://github.com/ashleygwilliams/wasm-pack-template.git",
+        "http://github.com/ashleygwilliams/wasm-pack-template",
+    ];
+
+    // with --git
+    for remote in possible_urls {
+        let dir = tmp_dir().build();
+        binary()
+            .arg("generate")
+            .arg("--git")
+            .arg(remote)
+            .arg("--name")
+            .arg("my-proj")
+            .arg("--init")
+            .current_dir(&dir.path())
+            .assert()
+            .success()
+            .stdout(predicates::str::contains("Done!").from_utf8());
+    }
+}
+
+#[test]
+#[ignore]
+fn abbreviation_for_github_works() {
+    let dir = tmp_dir().build();
+    binary()
+        .arg("generate")
+        .arg("--name")
+        .arg("my-proj")
+        .arg("ashleygwilliams/wasm-pack-template")
+        .current_dir(&dir.path())
+        .assert()
+        .success()
+        .stdout(predicates::str::contains("Done!").and(
+            predicates::str::contains(
+                "Favorite `ashleygwilliams/wasm-pack-template` not found in config, using it as a git repository: https://github.com/ashleygwilliams/wasm-pack-template.git"
+            )).from_utf8());
+}
+
+#[cfg(test)]
+#[cfg(unix)]
+mod ssh_remote {
+    use super::*;
+
+    #[test]
+    #[ignore]
+    // for now only locally working
+    fn it_should_support_a_public_repo() {
+        let dir = tmp_dir().build();
+
+        binary()
+            .arg("generate")
+            .arg("--git")
+            .arg("git@github.com:ashleygwilliams/wasm-pack-template.git")
+            .arg("--name")
+            .arg("foobar-project")
+            .current_dir(&dir.path())
+            .assert()
+            .success()
+            .stdout(predicates::str::contains("Done!").from_utf8());
+
+        let cargo_toml = dir.read("foobar-project/Cargo.toml");
+        assert!(cargo_toml.contains("foobar-project"));
+    }
+
+    #[test]
+    #[ignore]
+    // for now only locally working
+    fn it_should_support_a_private_repo() {
+        let dir = tmp_dir().build();
+
+        binary()
+            .arg("generate")
+            .arg("--git")
+            .arg("git@github.com:cargo-generate/wasm-pack-template.git")
+            .arg("--name")
+            .arg("foobar-project")
+            .current_dir(&dir.path())
+            .assert()
+            .success()
+            .stdout(predicates::str::contains("Done!").from_utf8());
+
+        let cargo_toml = dir.read("foobar-project/Cargo.toml");
+        assert!(cargo_toml.contains("foobar-project"));
+    }
+
+    #[test]
+    #[ignore]
+    // for now only locally working
+    fn it_should_support_a_custom_ssh_key() {
+        let dir = tmp_dir().build();
+
+        binary()
+            .arg("generate")
+            .arg("-i")
+            .arg("~/workspaces/rust/cargo-generate-org/.env/id_rsa_ci")
+            .arg("--git")
+            .arg("git@github.com:cargo-generate/wasm-pack-template.git")
+            .arg("--name")
+            .arg("foobar-project")
+            .current_dir(&dir.path())
+            .assert()
+            .success()
+            .stdout(
+                predicates::str::contains("Using private key:")
+                    .and(predicates::str::contains(
+                        "cargo-generate-org/.env/id_rsa_ci",
+                    ))
+                    .from_utf8(),
+            );
+
+        let cargo_toml = dir.read("foobar-project/Cargo.toml");
+        assert!(cargo_toml.contains("foobar-project"));
+    }
+}