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 println!("Committing files to remote...");
88 git(repo_path, &["add", "."])?;
89 git(
90 repo_path,
91 &["commit", "-m", &format!("Add course: {}", conf.title)],
92 )?;
93 git(repo_path, &["push"])?;
94
95 config::update_time(conf)?;
96
97 println!("Project successfully pushed to remote.");
98
99 match github::get_config_from_user_git("user.name") {
102 Some(github_username) => {
103 let pull_request_url = format!(
104 "https://github.com/oseda-dev/oseda-lib/compare/main...{}:oseda-lib:main?expand=1",
105 github_username
106 );
107
108 println!("Add your presentation to oseda.net by making a Pull Request at:");
109 println!();
110 println!("{}", pull_request_url);
111
112 if open::that(pull_request_url.clone()).is_err() {
113 return Err(format!("Please visit {pull_request_url} in a browser and submit a pull-request by hand").into());
114 };
115 }
116 None => {
117 println!("Error: could not get github username");
118 return Err("Deployment failed due to missing github credential. Pleas ensure user.name matches your github username".into());
119 }
120 }
121
122 Ok(())
123}
124
125fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
131 let cwd = env::current_dir()?;
136 let name = cwd
137 .file_name()
138 .ok_or("couldn't get directory name")?
139 .to_string_lossy()
140 .to_string();
141 Ok(name)
142}
143
144fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
147 let src = src.as_ref();
148 let dst = dst.as_ref();
149
150 fs::create_dir_all(dst)?;
151
152 for entry in fs::read_dir(src)? {
153 let entry = entry?;
154 let entry_path = entry.path();
155
156 if entry_path.ends_with(".git") {
158 continue;
159 }
160
161 let ty = entry.file_type()?;
162
163 if ty.is_dir() {
164 copy_dir_all(&entry_path, dst.join(entry.file_name()))?;
165 } else {
166 fs::copy(&entry_path, dst.join(entry.file_name()))?;
167 }
168 }
169
170 Ok(())
171}