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 #[arg(value_name = "FORK_URL")]
15 fork_url: String,
16 #[arg(long, value_name = "QUIET")]
18 quiet: bool,
19}
20
21struct SshUrl(String);
22
23impl std::ops::Deref for SshUrl {
25 type Target = String;
26
27 fn deref(&self) -> &Self::Target {
28 &self.0
29 }
30}
31
32impl TryFrom<String> for SshUrl {
41 type Error = Box<dyn Error>;
42
43 fn try_from(value: String) -> Result<Self, Self::Error> {
44 let suffix = value
48 .strip_prefix("https://github.com/")
49 .ok_or("Could not get SSH URL")?;
50
51 Ok(SshUrl(format!(
52 "git@github.com:{}.git",
53 suffix.trim_end_matches('/')
54 )))
55 }
56}
57
58pub fn deploy(opts: DeployOptions) -> Result<(), Box<dyn Error>> {
67 let tmp_dir = tempfile::tempdir()?;
68 let repo_path = tmp_dir.path();
69
70 let ssh_url: SshUrl = opts.fork_url.try_into()?;
71
72 git(
73 repo_path,
74 &["clone", "--no-checkout", ssh_url.0.as_str(), "."],
75 )?;
76
77 println!("Running git with sparse checkout");
78 git(repo_path, &["sparse-checkout", "init", "--cone"])?;
79 git(repo_path, &["sparse-checkout", "set", "courses"])?;
80 git(repo_path, &["checkout"])?;
81
82 let course_name = get_current_dir_name()?;
83 let new_course_dir = repo_path.join("courses").join(&course_name);
84
85 copy_dir_all(env::current_dir()?, &new_course_dir)?;
86
87 let conf = config::read_and_validate_config()?;
91
92 println!("Committing files to remote...");
93 git(repo_path, &["add", "."])?;
94 git(
95 repo_path,
96 &["commit", "-m", &format!("Add course: {}", conf.title)],
97 )?;
98 git(repo_path, &["push"])?;
99
100 config::update_time(conf)?;
101
102 println!("Project successfully pushed to remote.");
103
104 match github::get_config_from_user_git("user.name") {
107 Some(github_username) => {
108 let pull_request_url = format!(
109 "https://github.com/oseda-dev/oseda-lib/compare/main...{}:oseda-lib:main?expand=1",
110 github_username
111 );
112
113 println!("Add your presentation to oseda.net by making a Pull Request at:");
114 println!();
115 println!("{}", pull_request_url);
116
117 if !opts.quiet {
118 open::that(pull_request_url.clone()).map_err(|_| {
119 format!("Please visit {pull_request_url} in a browser and submit a pull-request by hand")
120 })?;
121 }
122 }
123 None => {
124 println!("Error: could not get github username");
125 return Err("Deployment failed due to missing github credential. Pleas ensure user.name matches your github username".into());
126 }
127 }
128
129 Ok(())
130}
131
132fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
138 let cwd = env::current_dir()?;
143 let name = cwd
144 .file_name()
145 .ok_or("couldn't get directory name")?
146 .to_string_lossy()
147 .to_string();
148 Ok(name)
149}
150
151fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
154 let src = src.as_ref();
155 let dst = dst.as_ref();
156
157 fs::create_dir_all(dst)?;
158
159 for entry in fs::read_dir(src)? {
160 let entry = entry?;
161 let entry_path = entry.path();
162
163 if entry_path.ends_with(".git") {
165 continue;
166 }
167
168 let ty = entry.file_type()?;
169
170 if ty.is_dir() {
171 copy_dir_all(&entry_path, dst.join(entry.file_name()))?;
172 } else {
173 fs::copy(&entry_path, dst.join(entry.file_name()))?;
174 }
175 }
176
177 Ok(())
178}