summaryrefslogtreecommitdiffstats
path: root/src/repo.rs
blob: a98e1f5e6cf3b25114072cab8b6a80309c96ff05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use clap::Subcommand;
use eyre::eyre;
use forgejo_api::CreateRepoOption;
use url::Url;

pub struct RepoInfo {
    owner: String,
    name: String,
    url: Url,
}

impl RepoInfo {
    pub fn get_current() -> eyre::Result<Self> {
        let repo = git2::Repository::open(".")?;
        let url = get_remote(&repo)?;

        let mut path = url.path_segments().ok_or_else(|| eyre!("bad path"))?;
        let owner = path
            .next()
            .ok_or_else(|| eyre!("path does not have owner name"))?
            .to_string();
        let name = path
            .next()
            .ok_or_else(|| eyre!("path does not have repo name"))?;
        let name = name.strip_suffix(".git").unwrap_or(name).to_string();

        let repo_info = RepoInfo { owner, name, url };
        Ok(repo_info)
    }
    pub fn owner(&self) -> &str {
        &self.owner
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn url(&self) -> &Url {
        &self.url
    }

    pub fn host_url(&self) -> Url {
        let mut url = self.url.clone();
        url.path_segments_mut()
            .expect("invalid url: cannot be a base")
            .pop()
            .pop();
        url
    }
}

fn get_remote(repo: &git2::Repository) -> eyre::Result<Url> {
    let head = repo.head()?;
    let branch_name = head.name().ok_or_else(|| eyre!("branch name not UTF-8"))?;
    let remote_name = repo.branch_upstream_remote(branch_name)?;
    let remote_name = remote_name
        .as_str()
        .ok_or_else(|| eyre!("remote name not UTF-8"))?;
    let remote = repo.find_remote(remote_name)?;
    let url = Url::parse(std::str::from_utf8(remote.url_bytes())?)?;
    Ok(url)
}

#[derive(Subcommand, Clone, Debug)]
pub enum RepoCommand {
    Create {
        host: String,
        repo: String,

        // flags
        #[clap(long, short)]
        description: Option<String>,
        #[clap(long, short = 'P')]
        private: bool,
        /// Sets the new repo to be the `origin` remote of the current local repo.
        #[clap(long, short)]
        set_upstream: Option<String>,
        /// Pushes the current branch to the default branch on the new repo.
        /// Implies `--set-upstream=origin` (setting upstream manual overrides this)
        #[clap(long, short)]
        push: bool,
    },
    Info,
    Browse,
}

impl RepoCommand {
    pub async fn run(self, keys: &crate::KeyInfo) -> eyre::Result<()> {
        match self {
            RepoCommand::Create {
                host,
                repo,

                description,
                private,
                set_upstream,
                push,
            } => {
                let host = Url::parse(&host)?;
                let api = keys.get_api(&host)?;
                let repo_spec = CreateRepoOption {
                    auto_init: false,
                    default_branch: "main".into(),
                    description,
                    gitignores: String::new(),
                    issue_labels: String::new(),
                    license: String::new(),
                    name: repo.clone(),
                    private,
                    readme: String::new(),
                    template: false,
                    trust_model: forgejo_api::TrustModel::Default,
                };
                let new_repo = api.create_repo(repo_spec).await?;
                eprintln!("created new repo at {}", host.join(&new_repo.full_name)?);

                if set_upstream.is_some() || push {
                    let repo = git2::Repository::open(".")?;

                    let upstream = set_upstream.as_deref().unwrap_or("origin");
                    let mut remote = repo.remote(upstream, new_repo.clone_url.as_str())?;

                    if push {
                        let head = repo.head()?;
                        if !head.is_branch() {
                            eyre::bail!("HEAD is not on a branch; cannot push to remote");
                        }
                        let branch_shorthand = head.shorthand().ok_or_else(|| eyre!("branch name invalid utf-8"))?.to_owned();
                        let branch_name = std::str::from_utf8(head.name_bytes())?.to_owned();
                        let mut current_branch = git2::Branch::wrap(head);
                        current_branch.set_upstream(Some(&dbg!(format!("{upstream}/{branch_shorthand}"))))?;

                        let auth = auth_git2::GitAuthenticator::new();
                        auth.push(&repo, &mut remote, &[&branch_name])?;
                    }
                }
            }
            RepoCommand::Info => {
                let repo = RepoInfo::get_current()?;
                let api = keys.get_api(&repo.host_url())?;
                let repo = api.get_repo(repo.owner(), repo.name()).await?;
                match repo {
                    Some(repo) => {
                        dbg!(repo);
                    }
                    None => eprintln!("repo not found"),
                }
            }
            RepoCommand::Browse => {
                let repo = RepoInfo::get_current()?;
                let mut url = repo.host_url().clone();
                let new_path = format!(
                    "{}/{}/{}",
                    url.path().strip_suffix("/").unwrap_or(url.path()),
                    repo.owner(),
                    repo.name(),
                );
                url.set_path(&new_path);
                open::that(url.as_str())?;
            }
        };
        Ok(())
    }
}