1use std::path::{Path, PathBuf};
7
8use colored::Colorize;
9use git2::{Commit, Cred, Error, ObjectType, RemoteCallbacks, Repository};
11
12use crate::{
13 models::GlobalConfig,
14 prompts::{boolean_prompt, password_prompt, text_prompt},
15};
16
17pub fn get_callbacks(
18 global_config: &mut GlobalConfig,
19 ssh_key: Option<PathBuf>,
20 key_needs_pw: bool,
21) -> RemoteCallbacks {
22 update_git_repo_path(global_config).expect("Could not create git repo directory");
23 let mut callbacks = RemoteCallbacks::new();
24 callbacks.credentials(move |_url, username_from_url, allowed_types| {
25 let mut username = String::from("");
26 if let Some(name_from_url) = username_from_url {
27 username = String::from(name_from_url);
28 } else if let Some(user) = text_prompt("Git Username: ") {
29 username = user;
30 }
31
32 if allowed_types.is_user_pass_plaintext() {
33 if let Some(password) = password_prompt("Git Password: ") {
34 Cred::userpass_plaintext(&username, &password)
35 } else {
36 Cred::default()
37 }
38 } else if allowed_types.is_ssh_key() {
39 if let Some(ssh_key) = &ssh_key {
40 if key_needs_pw {
41 if let Some(key_pw) = password_prompt("SSH key password: ") {
42 Cred::ssh_key(&username, None, ssh_key, Some(&key_pw))
43 } else {
44 Cred::default()
45 }
46 } else {
47 Cred::ssh_key(&username, None, ssh_key, None)
48 }
49 } else if let Some(key_path) = text_prompt("SSH Key Path: ") {
50 let key_path = shellexpand::tilde(&key_path).to_string();
51 global_config.ssh_key = Some(key_path.clone());
52 let key_path = Path::new(&key_path);
53 let key_needs_pw = boolean_prompt("Is the key password protected? ");
54 global_config.key_needs_pw = key_needs_pw;
55 if key_needs_pw {
56 if let Some(key_pw) = password_prompt("SSH key password: ") {
57 Cred::ssh_key(&username, None, key_path, Some(&key_pw))
58 } else {
59 Cred::default()
60 }
61 } else {
62 Cred::ssh_key(&username, None, key_path, None)
63 }
64 } else {
65 Cred::default()
66 }
67 } else {
68 Cred::default()
69 }
70 });
71 callbacks
72}
73
74pub fn check_out_modules_with_key(
75 global_config: &mut GlobalConfig,
76 remote: &str,
77 ssh_key: &Path,
78) -> Result<(), Error> {
79 let key_needs_pw = boolean_prompt("Does key need password? ");
80 global_config.key_needs_pw = key_needs_pw;
81 global_config.ssh_key = Some(String::from(ssh_key.to_string_lossy()));
82 let git_modules = global_config.home.join("git_modules");
83 let callbacks = get_callbacks(global_config, Some(ssh_key.into()), false);
84 check_out(git_modules, remote, callbacks)?;
85 Ok(())
86}
87
88pub fn check_out_modules_with_pw(
89 global_config: &mut GlobalConfig,
90 remote: &str,
91) -> Result<(), Error> {
92 let git_modules = global_config.home.join("git_modules");
93 let callbacks = get_callbacks(global_config, None, false);
94 check_out(git_modules, remote, callbacks)?;
95 Ok(())
96}
97
98pub fn update_git_repo_path(global_config: &mut GlobalConfig) -> Result<(), Error> {
99 if Path::new(&global_config.git_repo).exists() {
100 return Ok(());
101 }
102 if !global_config.home.join("git_modules").exists() {
103 match std::fs::create_dir_all(global_config.home.join("git_modules")) {
104 Ok(_) => {}
105 Err(_) => {
106 eprintln!("Could not create git_modules");
107 std::process::exit(1);
108 }
109 }
110 }
111 global_config.git_repo = global_config
112 .home
113 .join("git_modules")
114 .to_string_lossy()
115 .to_string();
116 if global_config.save().is_err() {
117 eprintln!("{}", "Could not write config".red());
118 }
119 Ok(())
120}
121
122pub fn check_out<P: AsRef<Path>>(
123 git_modules: P,
124 remote: &str,
125 callbacks: RemoteCallbacks,
126) -> Result<(), Error> {
127 let mut fo = git2::FetchOptions::new();
128 fo.remote_callbacks(callbacks);
129
130 let mut builder = git2::build::RepoBuilder::new();
131 builder.fetch_options(fo);
132
133 builder.clone(remote, git_modules.as_ref())?;
134
135 Ok(())
136}
137
138pub fn update_modules(global_config: &mut GlobalConfig) -> Result<(), Error> {
139 let mut fo = git2::FetchOptions::new();
140 let mut ssh_key: Option<PathBuf> = None;
141 if let Some(key) = global_config.ssh_key.clone() {
142 ssh_key = Some(Path::new(&key).into());
143 }
144 let git_repo = global_config.git_repo.clone();
145 let branch = global_config.git_main_branch.clone();
146 let callbacks = get_callbacks(global_config, ssh_key, global_config.key_needs_pw);
147 fo.remote_callbacks(callbacks);
148 match Repository::open(shellexpand::tilde(&git_repo).to_string()) {
149 Ok(repo) => {
150 fetch_origin_master(&repo, fo, &branch)?;
151 fast_forward(&repo, &branch)?;
152 println!("{}", "Updated repo to newest revision".green());
153 }
154 Err(e) => {
155 eprintln!("Could not update repo, manual update needed: {:?}", e);
156 }
157 }
158 Ok(())
159}
160
161pub fn fetch_origin_master(
162 repo: &git2::Repository,
163 mut opts: git2::FetchOptions,
164 branch: &str,
165) -> Result<(), git2::Error> {
166 repo.find_remote("origin")?
167 .fetch(&[branch], Some(&mut opts), None)
168}
169
170pub fn fast_forward(repo: &Repository, branch: &str) -> Result<(), Error> {
171 let fetch_head = repo.find_reference("FETCH_HEAD")?;
172 let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?;
173 let analysis = repo.merge_analysis(&[&fetch_commit])?;
174 if analysis.0.is_up_to_date() {
175 Ok(())
176 } else if analysis.0.is_fast_forward() {
177 let refname = format!("refs/heads/{}", branch);
178 let mut reference = repo.find_reference(&refname)?;
179 reference.set_target(fetch_commit.id(), "Fast-Forward")?;
180 repo.set_head(&refname)?;
181 repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))
182 } else {
183 Err(Error::from_str("Fast-forward only!"))
184 }
185}
186pub fn find_last_commit(repo: &Repository) -> Result<Commit, git2::Error> {
187 let obj = repo.head()?.resolve()?.peel(ObjectType::Commit)?;
188 match obj.into_commit() {
189 Ok(c) => Ok(c),
190 Err(_) => Err(Error::from_str("commit error")),
191 }
192}