Skip to content

Commit

Permalink
feat: list repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
benpueschel committed Jul 6, 2024
1 parent ac065cd commit f0401ed
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 71 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 30 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,36 @@ async fn main() -> Result<(), Box<dyn Error>> {
let command = Args::from_args();

match command {
Args::List { remote: _ } => {
println!("Listing repositories...");
todo!()
Args::List { remote } => {
println!("Listing repositories on remote '{remote}'");
let remote = load_remote(&remote).await?;
let repos = remote.list_repos().await?;
println!("* denotes private repositories");
let mut longest_name = 0;
for repo in &repos {
if repo.name.len() > longest_name {
longest_name = repo.name.len();
}
}
for repo in &repos {
if repo.private {
print!("* ");
} else {
print!(" ");
}
let padding = " ".repeat(longest_name - repo.name.len());
print!("{}{padding}", repo.name);
if repo.last_commits.is_empty() {
print!(" - no commits");
} else {
let last = &repo.last_commits[0];
let date = &last.date;
let sha = last.sha.split_at(8).0;
let message = last.message.split('\n').next().unwrap_or(&last.message);
print!(" - {date}: {sha}: {message}");
}
println!();
}
}
Args::Create {
private,
Expand Down
80 changes: 51 additions & 29 deletions src/remote/gitea.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,65 @@ impl Remote for GiteaRemote {
Ok(repo.clone_url)
}

async fn list_repos(&self) -> Result<Vec<Repository>, Error> {
let owner = self.client.get_authenticated_user().await.map_err(map_error)?;
let search_option = teatime::SearchRepositoriesOption {
uid: Some(owner.id),
limit: Some(100),
..Default::default()
};
let repos = self.client.search_repositories(&search_option).await.map_err(map_error)?;
let mut result = Vec::new();
for repo in repos {
result.push(self.get_repo_info(repo).await?);
}
Ok(result)
}

async fn get_repo_info(&self, name: &str) -> Result<Repository, Error> {
let owner = &self.config.username;
let repo = self
.client
.get_repository(owner, name)
.await
.map_err(map_error)?;
self.get_repo_info(repo).await
}

async fn delete_repo(&self, name: &str) -> Result<(), Error> {
self.client
.delete_repository(&self.config.username, name)
.await
.map_err(map_error)
}

async fn clone_repo(&self, name: &str, path: &str) -> Result<(), Error> {
let url = format!(
"git@{}/{}:{}.git",
self.config.url, self.config.username, name
);

let status = std::process::Command::new("git")
.arg("clone")
.arg(url)
.arg(path)
.status()?;

if !status.success() {
return Err(Error::new(
ErrorKind::Other,
format!("Failed to clone repository '{}'", name),
));
}

Ok(())
}
}

impl GiteaRemote {
async fn get_repo_info(&self, repo: teatime::Repository) -> Result<Repository, Error> {
let owner = &self.config.username;
let name = &repo.name;
// disable stats, verification, and files to speed up the request.
// We only care about the commit messages.
let commit_option = GetCommitsOption {
Expand Down Expand Up @@ -88,33 +139,4 @@ impl Remote for GiteaRemote {
last_commits,
})
}

async fn delete_repo(&self, name: &str) -> Result<(), Error> {
self.client
.delete_repository(&self.config.username, name)
.await
.map_err(map_error)
}

async fn clone_repo(&self, name: &str, path: &str) -> Result<(), Error> {
let url = format!(
"git@{}/{}:{}.git",
self.config.url, self.config.username, name
);

let status = std::process::Command::new("git")
.arg("clone")
.arg(url)
.arg(path)
.status()?;

if !status.success() {
return Err(Error::new(
ErrorKind::Other,
format!("Failed to clone repository '{}'", name),
));
}

Ok(())
}
}
110 changes: 74 additions & 36 deletions src/remote/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ use std::io::{Error, ErrorKind};
use async_trait::async_trait;
use chrono::DateTime;
use octocrab::{
models::repos::{CommitAuthor, GitUserTime},
GitHubError, Octocrab,
models::{
self,
repos::{CommitAuthor, GitUserTime},
},
repos::RepoHandler,
Octocrab,
};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -49,32 +53,95 @@ impl Remote for GitHubRemote {
Ok(res.clone_url)
}

async fn list_repos(&self) -> Result<Vec<Repository>, Error> {
let repos = self
.crab
.search()
.repositories(&format!("user:{}", &self.config.username))
.per_page(100)
.sort("updated")
.order("desc")
.send()
.await
.map_err(map_error)?;
let mut result = Vec::new();
for repo in repos {
let base = self
.crab
.repos(self.config.username.clone(), repo.name.clone());
result.push(self.get_repo_info(base, repo).await?);
}
Ok(result)
}

async fn get_repo_info(&self, name: &str) -> Result<Repository, Error> {
let base = self.crab.repos(self.config.username.clone(), name);

let repo = base.get().await.map_err(map_error)?;
self.get_repo_info(base, repo).await
}

async fn delete_repo(&self, name: &str) -> Result<(), Error> {
self.crab
.repos(self.config.username.clone(), name)
.delete()
.await
.map_err(map_error)?;

Ok(())
}

async fn clone_repo(&self, name: &str, path: &str) -> Result<(), Error> {
let url = format!("git@github.com/{}:{}.git", self.config.username, name);

let status = std::process::Command::new("git")
.arg("clone")
.arg(url)
.arg(path)
.status()?;

if !status.success() {
return Err(Error::new(
ErrorKind::Other,
format!("Failed to clone repository '{}'", name),
));
}

Ok(())
}
}

impl GitHubRemote {
async fn get_repo_info(
&self,
base: RepoHandler<'_>,
repo: models::Repository,
) -> Result<Repository, Error> {
static COMMIT_COUNT: u8 = 25;
let commits = base.list_commits().per_page(COMMIT_COUNT).send().await;
let ssh_url = match repo.ssh_url {
Some(url) => url.to_string(),
None => format!(
"git@{}/{}:{}.git",
self.config.url, self.config.username, name
self.config.url, self.config.username, repo.name
),
};

let clone_url = match repo.clone_url {
Some(url) => url.to_string(),
None => format!(
"https://{}/{}/{}.git",
self.config.url, self.config.username, name
self.config.url, self.config.username, repo.name
),
};

let commits = base.list_commits().per_page(25).send().await;
let commits = match commits {
Ok(x) => x,
Err(err) => {
if let octocrab::Error::GitHub { source, backtrace: _ } = &err {
if let octocrab::Error::GitHub {
source,
backtrace: _,
} = &err
{
// If the repository is empty, return an empty list of commits
// TODO: this is a bit hacky, is there a better way to handle this?
if source.message == "Git Repository is empty." {
Expand Down Expand Up @@ -119,33 +186,4 @@ impl Remote for GitHubRemote {
clone_url,
})
}

async fn delete_repo(&self, name: &str) -> Result<(), Error> {
self.crab
.repos(self.config.username.clone(), name)
.delete()
.await
.map_err(map_error)?;

Ok(())
}

async fn clone_repo(&self, name: &str, path: &str) -> Result<(), Error> {
let url = format!("git@github.com/{}:{}.git", self.config.username, name);

let status = std::process::Command::new("git")
.arg("clone")
.arg(url)
.arg(path)
.status()?;

if !status.success() {
return Err(Error::new(
ErrorKind::Other,
format!("Failed to clone repository '{}'", name),
));
}

Ok(())
}
}
2 changes: 2 additions & 0 deletions src/remote/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ pub trait Remote {
/// Create a new repository on the remote.
/// Returns the URL of the new repository.
async fn create_repo(&self, create_info: RepoCreateInfo) -> Result<String, Error>;
/// List all repositories.
async fn list_repos(&self) -> Result<Vec<Repository>, Error>;
/// Get the information of a repository.
async fn get_repo_info(&self, name: &str) -> Result<Repository, Error>;
/// Delete a repository.
Expand Down

0 comments on commit f0401ed

Please sign in to comment.