1use std::{env, error::Error, fs, path::Path};
2
3use clap::Args;
4
5use crate::{
6 config,
7 github::{self, git},
8};
9
10#[derive(Args, Debug)]
12pub struct DeployOptions {
13 fork_url: String,
14}
15
16struct SshUrl(String);
17
18impl std::ops::Deref for SshUrl {
20 type Target = String;
21
22 fn deref(&self) -> &Self::Target {
23 &self.0
24 }
25}
26
27impl TryFrom<String> for SshUrl {
36 type Error = Box<dyn Error>;
37
38 fn try_from(value: String) -> Result<Self, Self::Error> {
39 let suffix = value
43 .strip_prefix("https://github.com/")
44 .ok_or("Could not get SSH URL")?;
45
46 Ok(SshUrl(format!(
47 "git@github.com:{}.git",
48 suffix.trim_end_matches('/')
49 )))
50 }
51}
52
53pub fn deploy(opts: DeployOptions) -> Result<(), Box<dyn Error>> {
62 let tmp_dir = tempfile::tempdir()?;
63 let repo_path = tmp_dir.path();
64
65 let ssh_url: SshUrl = opts.fork_url.try_into()?;
66
67 git(
68 repo_path,
69 &["clone", "--no-checkout", ssh_url.0.as_str(), "."],
70 )?;
71
72 println!("Running git with sparse checkout");
73 git(repo_path, &["sparse-checkout", "init", "--cone"])?;
74 git(repo_path, &["sparse-checkout", "set", "courses"])?;
75 git(repo_path, &["checkout"])?;
76
77 let course_name = get_current_dir_name()?;
78 let new_course_dir = repo_path.join("courses").join(&course_name);
79
80 copy_dir_all(env::current_dir()?, &new_course_dir)?;
81
82 let conf = config::read_and_validate_config()?;
86
87 config::update_time(conf)?;
88 println!("Committing files to remote...");
89 git(repo_path, &["add", "."])?;
90 git(repo_path, &["commit", "-m", "Add new course"])?;
91 git(repo_path, &["push"])?;
92
93 println!("Project successfully pushed to remote.");
94
95 match github::get_config_from_user_git("user.name") {
98 Some(github_username) => {
99 let pull_request_url = format!(
100 "https://github.com/oseda-dev/oseda-lib/compare/main...{}:oseda-lib:main?expand=1",
101 github_username
102 );
103
104 println!("Add your presentation to oseda.net by making a Pull Request at:");
105 println!();
106 println!("{}", pull_request_url);
107
108 if open::that(pull_request_url.clone()).is_err() {
109 return Err(format!("Please visit {pull_request_url} in a browser and submit a pull-request by hand").into());
110 };
111
112 }
113 None => {
114 println!("Error: could not get github username");
115 return Err("Deployment failed due to missing github credential. Pleas ensure user.name matches your github username".into());
116 }
117 }
118
119 Ok(())
120}
121
122fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
128 let cwd = env::current_dir()?;
133 let name = cwd
134 .file_name()
135 .ok_or("couldn't get directory name")?
136 .to_string_lossy()
137 .to_string();
138 Ok(name)
139}
140
141fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
144 let src = src.as_ref();
145 let dst = dst.as_ref();
146
147 fs::create_dir_all(dst)?;
148
149 for entry in fs::read_dir(src)? {
150 let entry = entry?;
151 let entry_path = entry.path();
152
153 if entry_path.ends_with(".git") {
155 continue;
156 }
157
158 let ty = entry.file_type()?;
159
160 if ty.is_dir() {
161 copy_dir_all(&entry_path, dst.join(entry.file_name()))?;
162 } else {
163 fs::copy(&entry_path, dst.join(entry.file_name()))?;
164 }
165 }
166
167 Ok(())
168}