xtask_toolkit/
git.rs

1use std::path::PathBuf;
2
3use chrono::{DateTime, Utc};
4use xshell::{Shell, cmd};
5
6pub fn create_and_push_tag(tag: &str) -> Result<(), xshell::Error> {
7    let sh = Shell::new()?;
8
9    cmd!(sh, "git tag {tag}").run()?;
10    cmd!(sh, "git push origin {tag}").run()?;
11    Ok(())
12}
13
14pub fn has_tag(tag: &str) -> Result<bool, xshell::Error> {
15    let sh = Shell::new()?;
16
17    let tags = cmd!(sh, "git tag")
18        .read()?
19        .split('\n')
20        .map(|x| x.trim().to_string())
21        .collect::<Vec<_>>();
22
23    Ok(tags.iter().any(|x| x == tag))
24}
25
26pub fn unstaged_changes() -> Result<bool, xshell::Error> {
27    let sh = Shell::new()?;
28    Ok(!cmd!(sh, "git status --porcelain").read()?.is_empty())
29}
30
31#[derive(Debug, thiserror::Error)]
32pub enum LastCommitError {
33    #[error(transparent)]
34    XShellError(xshell::Error),
35    #[error("Could not parse timestamp value. Not an integer")]
36    ParseIntError,
37    #[error("Not a timestamp")]
38    NotATimestamp,
39}
40
41impl From<xshell::Error> for LastCommitError {
42    fn from(value: xshell::Error) -> Self {
43        LastCommitError::XShellError(value)
44    }
45}
46
47pub fn last_commit_date() -> Result<DateTime<Utc>, LastCommitError> {
48    let sh = Shell::new()?;
49    DateTime::from_timestamp(
50        cmd!(sh, "git show --no-patch --format=%ct HEAD")
51            .read()?
52            .parse()
53            .map_err(|_| LastCommitError::ParseIntError)?,
54        0,
55    )
56    .ok_or_else(|| LastCommitError::NotATimestamp)
57}
58
59pub fn get_root_path() -> Result<PathBuf, xshell::Error> {
60    let sh = Shell::new()?;
61    Ok(PathBuf::from(cmd!(sh, "git rev-parse --show-toplevel").read()?))
62}
63
64pub struct OriginUrl(pub String);
65
66impl OriginUrl {
67    pub fn new() -> Result<OriginUrl, xshell::Error> {
68        let sh = Shell::new()?;
69        Ok(OriginUrl(cmd!(sh, "git remote get-url origin").read()?))
70    }
71
72    pub fn to_http(mut self) -> Result<Self, ()> {
73        if self.0.starts_with("https://") || self.0.starts_with("http://") {
74            Ok(self)
75        } else if self.0.starts_with("git@") && self.0.contains("github") {
76            self.0 = self.0.replace("git@", "https://").replacen(":", "/", 1);
77            Ok(self)
78        } else {
79            Err(())
80        }
81    }
82
83    pub fn get(&self) -> &str {
84        &self.0
85    }
86}