gitbackup 0.2.6

Backup all your git repositories with a single command
use crate::helpers::sync_repositories;
use crate::types::{GenericRepository, Provider};
use anyhow::{Context, Result};
use regex::RegexSetBuilder;
use reqwest::blocking;
use serde::Deserialize;
use std::path::Path;

pub fn run(backup_path: &Path, provider: &Provider) -> Result<()> {
    let exclusions = RegexSetBuilder::new(&provider.exclude)
        .case_insensitive(true)
        .build()?;

    let client = blocking::Client::new();
    let mut repositories: Vec<GitlabRepository> = vec![];
    let mut page = 1;

    loop {
        let mut values = client
            .get(format!(
                "https://{}/api/v4/projects?membership=true&page={}&per_page=100",
                provider.host, page
            ))
            .header("Authorization", format!("Bearer {}", &provider.token))
            .send()
            .with_context(|| {
                format!(
                    "Unable to fetch {} API, please check your credentials",
                    provider.host
                )
            })?
            .json::<Vec<GitlabRepository>>()
            .context("Unable to deserialize repositories")?
            .into_iter()
            .filter(|r| !exclusions.is_match(&r.full_name()))
            .collect::<Vec<GitlabRepository>>();

        if values.is_empty() {
            break;
        }

        repositories.append(&mut values);
        page += 1;
    }

    sync_repositories(backup_path, provider, &repositories);

    Ok(())
}

#[allow(dead_code)]
#[derive(Deserialize, Debug)]
struct GitlabRepository {
    pub id: usize,
    pub name: String,
    pub path_with_namespace: String,
    pub visibility: String, // public, internal, private
    pub default_branch: String,
    pub ssh_url_to_repo: String,
    pub http_url_to_repo: String,
    pub web_url: String,
    pub archived: bool,
    pub created_at: String,
    pub updated_at: String,
    pub last_activity_at: String,
}

impl GenericRepository for GitlabRepository {
    fn full_name(&self) -> String {
        self.path_with_namespace.to_string()
    }

    fn default_branch(&self) -> String {
        self.default_branch.to_string()
    }
}