wini_cli/init/
git.rs

1use {
2    super::err::InitError,
3    crate::{
4        init::{select, sep, SEP},
5        utils::generate_random_string,
6    },
7    git2::{BranchType, Cred, CredentialType, IndexAddOption, Repository, Signature, Time},
8    inquire::{Password, Text},
9    std::{
10        borrow::Cow,
11        fmt::Write,
12        path::Path,
13        time::{SystemTime, UNIX_EPOCH},
14    },
15};
16
17
18/// Interactively authenticate a user
19pub fn auth(_: &str, username: Option<&str>, _: CredentialType) -> Result<Cred, git2::Error> {
20    println!("{}", InitError::CloneNeedsAuthentification);
21    sep();
22
23    let selection = select("Authenticate with", vec!["Key", "Password"])
24        .map_err(|_| git2::Error::from_str("Manual Exit."))?;
25
26    let username = if let Some(username) = username {
27        Cow::Borrowed(username)
28    } else {
29        let username = Text::new("Username:")
30            .prompt()
31            .map_err(|_| git2::Error::from_str("Manual Exit."))?;
32
33        Cow::Owned(username)
34    };
35
36    let credentials = match selection {
37        0 => {
38            let mut path_to_key: Option<String> = None;
39
40            while path_to_key.is_none() {
41                let imaginary_path = Text::new("Path to key:")
42                    .prompt()
43                    .map_err(|_| git2::Error::from_str("Manual Exit."))?;
44
45                if Path::new(&imaginary_path).exists() {
46                    path_to_key = Some(imaginary_path);
47                } else {
48                    eprintln!("{}", InitError::InvalidPath(imaginary_path));
49                    sep();
50                }
51            }
52
53            Cred::ssh_key(&username, None, Path::new(&path_to_key.unwrap()), None)
54        },
55        1 => {
56            let mut password: Option<String> = None;
57
58            while password.is_none() {
59                let imaginary_path = Password::new("Password:")
60                    .without_confirmation()
61                    .prompt()
62                    .map_err(|_| git2::Error::from_str("Manual Exit."))?;
63                password = Some(imaginary_path);
64            }
65
66            Cred::userpass_plaintext(&username, &password.unwrap())
67        },
68        _ => unreachable!(),
69    };
70
71    credentials
72}
73
74
75
76/// Switch to a git branch, remove all other branch, and remove the remote repository.
77pub fn use_branch(repo_path: &str, branch_name: &str) -> Result<String, git2::Error> {
78    let repo = Repository::open(repo_path)?;
79
80    let branch = repo.find_branch(&format!("origin/{branch_name}"), BranchType::Remote)?;
81
82    // Reset the state to the current branch status
83    let (object, _) = repo
84        .revparse_ext(&format!("origin/{branch_name}"))
85        .expect("Object not found");
86    repo.reset(&object, git2::ResetType::Hard, None)?;
87
88    // Create a local branch from the remote one
89    let target_commit = branch.get().peel_to_commit()?;
90    // Can error if already exists, in this case, do nothing.
91    let _ = repo.branch(branch_name, &target_commit, false);
92
93    let branch_ref = format!("refs/heads/{branch_name}");
94    let branch_ref = repo.find_reference(&branch_ref)?;
95    repo.set_head(branch_ref.name().unwrap_or_default())?;
96
97
98    // Delete all other branches.
99    let branch_to_keep = branch_name;
100    let branches = repo.branches(Some(BranchType::Local))?;
101    for branch in branches {
102        let (mut branch, _) = branch?;
103        let curr_branch_name = branch.name()?.unwrap_or_default();
104        if curr_branch_name != branch_to_keep {
105            branch.delete()?;
106        }
107    }
108
109    // Delete remote and rename to main
110    repo.remote_delete("origin")?;
111    let mut branch = repo.find_branch(branch_to_keep, BranchType::Local)?;
112    branch.rename("main", true)?;
113
114    let last_commit_oid = target_commit.id();
115    let mut last_commit_sha = String::with_capacity(40);
116    for byte in last_commit_oid.as_bytes() {
117        write!(&mut last_commit_sha, "{:02x}", byte).unwrap();
118    }
119
120
121    Ok(last_commit_sha)
122}
123
124
125
126pub fn clone(url: &str) -> Result<String, InitError> {
127    let clone_to = generate_random_string(64);
128
129    let mut callbacks = git2::RemoteCallbacks::new();
130    callbacks.credentials(auth);
131
132    let mut opts = git2::FetchOptions::new();
133    opts.remote_callbacks(callbacks);
134    opts.download_tags(git2::AutotagOption::All);
135
136    let mut builder = git2::build::RepoBuilder::new();
137    builder.fetch_options(opts);
138
139
140    match builder.clone(url, Path::new(&clone_to)) {
141        Ok(_repo) => Ok(clone_to),
142        Err(why) => {
143            match why.code() {
144                git2::ErrorCode::Auth => {
145                    println!("{}", InitError::BadCredentials);
146                    println!("{SEP}");
147                    clone(url)
148                },
149                _ => Err(InitError::OtherGitError(why)),
150            }
151        },
152    }
153}
154
155
156/// Makes the first commit that is unique to this repository.
157/// There is content to commit because of the modification of Cargo.toml and wini.toml
158pub fn first_commit(repo_path: &str) -> Result<(), git2::Error> {
159    let repo = Repository::open(repo_path)?;
160    let mut index = repo.index()?;
161    index.add_all(["*"].iter(), IndexAddOption::DEFAULT, None)?;
162    index.write()?;
163
164
165    let now = SystemTime::now()
166        .duration_since(UNIX_EPOCH)
167        .expect("UNIX_EPOCH is always a valid date.")
168        .as_secs();
169
170    let author = Signature::new("Wini", "wini", &Time::new(now as i64, 0))?;
171
172    let tree_id = index.write_tree()?;
173    let tree = repo.find_tree(tree_id)?;
174
175    let parent_ids = repo.head()?.peel_to_commit()?;
176
177    repo.commit(
178        Some("HEAD"),
179        &author,
180        &author,
181        "Creation of project",
182        &tree,
183        &[&parent_ids],
184    )?;
185
186
187    Ok(())
188}