Skip to content

Commit

Permalink
Merge branch 'release/v0.1.0' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
kilpkonn committed Jan 22, 2021
2 parents 83a08ac + 4fad1b9 commit aa2d668
Show file tree
Hide file tree
Showing 18 changed files with 781 additions and 33 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/develop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Develop
on:
push:
branches:
- develop
- main
pull_request:
branches:
- develop
- main

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install latest nightly
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo build
uses: actions-rs/cargo@v1
with:
command: build
args: --package gtm-sync --bin gtm-sync --release
- uses: actions/upload-artifact@v2
with:
name: gtm-sync
path: ./target/release/gtm-sync
53 changes: 53 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Release

on:
workflow_dispatch:
inputs:
release_version:
description: 'Release version'
required: true

jobs:
build-linux:
runs-on: ubuntu-latest
env:
APP_NAME: 'gtm-sync'
MAINTAINER: 'DEVELOPEST'
DESC: 'gtm-sync client for linux'
steps:
- uses: actions/checkout@v2
- name: Install latest nightly
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run cargo build
uses: actions-rs/cargo@v1
with:
command: build
args: --package gtm-sync --bin gtm-sync --release
- uses: actions/upload-artifact@v2
with:
name: gtm-sync
path: ./target/release/gtm-sync
- name: Prepare deb package
run: |
mkdir -p .debpkg/usr/bin
cp ./target/release/gtm-sync .debpkg/usr/bin
chmod +x .debpkg/usr/bin
- name: Build deb package
uses: jiro4989/build-deb-action@v2
with:
package: ${{ env.APP_NAME }}
package_root: .debpkg
maintainer: ${{ env.MAINTAINER }}
version: ${{ github.event.inputs.release_version }}
arch: 'amd64'
desc: ${{ env.DESC }}
- name: Upload deb package
uses: actions/upload-artifact@v2
with:
name: gtm-sync-debian
path: |
./*.deb
13 changes: 10 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ edition = "2018"
[dependencies]
rocket = "0.4.6"
git2 = "0.13"
serde = "1.0"
serde_derive = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.44"
toml = "0.5.7"
regex = "1"
lazy_static = "1.4.0"
lazy_static = "1.4.0"
reqwest = { version = "0.10.9", features = ["json"] }
tokio = { version = "0.2", features = ["full"] }

[dependencies.rocket_contrib]
version = "0.4.2"
default-features = false
features = ["json"]
3 changes: 3 additions & 0 deletions Rocket.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[development]
address = "localhost"
port = 8090
61 changes: 46 additions & 15 deletions src/config/config.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,63 @@

use serde_derive::{Serialize, Deserialize};
use std::fs;

#[derive(Serialize, Deserialize)]
pub struct SyncConfig {
pub target_host: String,
pub target_port: Option<u16>,
pub port: Option<u16>,
pub repositories_base_path: String,
pub repositories: Vec<Repository>,
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};

use crate::config::repository::{generate_credentials_from_clone_url, Repository};

lazy_static! {
pub static ref CONFIG_PATH: String = "./config.toml".to_string();
}


#[derive(Serialize, Deserialize)]
pub struct Repository {
pub url: String,
pub path: String,
pub ssh_private_key: Option<String>,
pub struct Config {
target_host: String,
target_port: Option<u16>,
pub port: Option<u16>,
pub address: Option<String>,
pub access_token: Option<String>,
pub ssh_public_key: Option<String>,
pub ssh_private_key: Option<String>,
pub ssh_user: Option<String>,
pub ssh_passphrase: Option<String>,
pub repositories_base_path: String,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub repositories: Vec<Repository>,
}

pub fn load(config_file: &String) -> SyncConfig {
pub fn load(config_file: &String) -> Config {
let content = fs::read_to_string(config_file).expect("Unable to read config!");
return toml::from_str(&content).expect("Unable to deserialize config!");
}

pub fn save(config_file: &String, config: &SyncConfig) {
pub fn save(config_file: &String, config: &Config) {
let content = toml::to_string(config).expect("Unable to serialize config!");
fs::write(config_file, content).expect("Unable to save config!");
}

impl Config {
pub fn get_target_url(&self) -> String {
return format!("{}:{}", self.target_host, self.target_port.unwrap_or(8000));
}

pub fn get_sync_url(&self) -> String {
return format!("{}:{}",
self.address.clone().unwrap_or("localhost".to_string()),
self.port.clone().unwrap_or(8000)
);
}

pub fn generate_path_from_git_url(&self, url: &String) -> String {
let (provider, user, repo) = generate_credentials_from_clone_url(url);
return format!("{}/{}/{}/{}", self.repositories_base_path.trim_end_matches("/"), provider, user, repo);
}

pub fn generate_path_from_provider_user_repo(&self,
provider: &String,
user: &String,
repo: &String,
) -> String {
return format!("{}/{}/{}/{}", self.repositories_base_path.trim_end_matches("/"), provider, user, repo);
}
}
2 changes: 2 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod config;
pub mod repository;
27 changes: 27 additions & 0 deletions src/config/repository.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};

lazy_static! {
static ref PATH_FROM_URL_REGEX: Regex =
Regex::new(r#"(git@|https://)([a-zA-Z0-9.]+)[:/]([a-zA-Z0-9-]+)/([a-zA-Z0-9-]+)\.git"#).unwrap();
}

#[derive(Serialize, Deserialize, Clone)]
pub struct Repository {
pub url: String,
pub path: String,
pub ssh_private_key: Option<String>,
pub ssh_public_key: Option<String>,
pub ssh_user: Option<String>,
pub ssh_passphrase: Option<String>,
}

pub fn generate_credentials_from_clone_url(url: &String) -> (String, String, String) {
let caps = PATH_FROM_URL_REGEX.captures(url).unwrap();
return (caps.get(2).map_or("provider".to_string(), |m| m.as_str().to_string()),
caps.get(3).map_or("user".to_string(), |m| m.as_str().to_string()),
caps.get(4).map_or("repo".to_string(), |m| m.as_str().to_string()));
}

impl Repository {}
130 changes: 130 additions & 0 deletions src/gtm/git.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#![deny(warnings)]

use std::fs;
use std::path::Path;

use git2::{BranchType, Error, FetchOptions, Note, RemoteCallbacks, Repository};
use git2::build::RepoBuilder;

use crate::gtm::gtm;
use crate::config::repository;
use crate::config::config::Config;

static GTM_NOTES_REF: &str = "refs/notes/gtm-data";
static GTM_NOTES_REF_SPEC: &str = "+refs/notes/gtm-data:refs/notes/gtm-data";
static DEFAULT_ORIGIN: &str = "origin";
static ORIGIN_PREFIX: &str = "refs/remotes/origin/";
static ORIGIN_HEAD: &str = "refs/remotes/origin/HEAD";

pub fn clone_or_open(repo_config: &repository::Repository, cfg: &Config) -> Result<Repository, Error> {
let path = Path::new(&repo_config.path);

if path.exists() {
let repo = Repository::open(path);
if repo.is_ok() {
return repo;
}
let _remove = fs::remove_dir_all(&path)
.expect(&*format!("Unable to remove dir: {}", repo_config.path));
return clone_or_open(&repo_config, &cfg);
}

let fo = generate_fetch_options(&repo_config, &cfg);

return RepoBuilder::new()
.fetch_options(fo)
.clone(&repo_config.url, Path::new(&repo_config.path));
}

fn generate_fetch_options<'a>(repo_config: &'a repository::Repository, cfg: &'a Config) -> FetchOptions<'a> {
let mut cb = RemoteCallbacks::new();
let repo_config = repo_config.clone();
cb.credentials(move |_c, _o, t| {
if t.is_ssh_key() {
return git2::Cred::ssh_key(
&repo_config.ssh_user.as_ref()
.unwrap_or(cfg.ssh_user.as_ref().unwrap_or(&"git".to_string())),
Option::from(Path::new(&repo_config.ssh_public_key.as_ref()
.unwrap_or(cfg.ssh_public_key.as_ref().unwrap_or(&"".to_string())))),
&Path::new(&repo_config.ssh_private_key.as_ref()
.unwrap_or(cfg.ssh_private_key.as_ref().unwrap_or(&"".to_string()))),
repo_config.ssh_passphrase.as_ref()
.or(cfg.ssh_passphrase.as_ref()).map(|x| &**x),
)
}
return git2::Cred::default();
});

let mut fo = FetchOptions::new();
fo.remote_callbacks(cb);
return fo;
}

pub fn fetch(repo: &Repository, repo_config: &repository::Repository, cfg: &Config) {
let mut remote = repo.find_remote(DEFAULT_ORIGIN)
.expect("Unable to find remote 'origin'");
let mut ref_added = false;
let refs = remote.fetch_refspecs().unwrap();
for i in 0..refs.len() {
if refs.get(i).unwrap() == GTM_NOTES_REF_SPEC {
ref_added = true;
break;
}
}
if !ref_added {
repo.remote_add_fetch(DEFAULT_ORIGIN, GTM_NOTES_REF_SPEC)
.expect("Unable to add fetch ref spec for gtm-data!");
remote = repo.find_remote(DEFAULT_ORIGIN)
.expect("Unable to find remote 'origin'");
}

let branches = repo.branches(Option::from(BranchType::Remote)).unwrap();
let mut fetch_refs: Vec<String> = vec![];
for branch in branches {
let (branch, _) = branch.unwrap();
let refspec = branch.get()
.name()
.unwrap()
.strip_prefix(ORIGIN_PREFIX)
.unwrap();
if refspec != "HEAD" {
fetch_refs.push(format!("refs/heads/{}", refspec.to_string()));
}
}
fetch_refs.push(GTM_NOTES_REF.parse().unwrap());

let mut fo = generate_fetch_options(repo_config, cfg);
remote.fetch(&fetch_refs, Option::from(&mut fo), None)
.expect("Error fetching data!");
remote.disconnect().unwrap();
}

pub fn read_commits(repo: &Repository) -> Result<Vec<gtm::Commit>, Error> {
let mut commits: Vec<gtm::Commit> = Vec::new();
let mut revwalk = repo.revwalk().expect("Unable to revwalk!");
revwalk.set_sorting(git2::Sort::TOPOLOGICAL).expect("Unable to set revwalk sorting!");
revwalk.set_sorting(git2::Sort::REVERSE).expect("Unable to reverse revalk sorting!");
let branches = repo.branches(Option::from(BranchType::Remote)).unwrap();
for branch in branches {
let (branch, _) = branch.unwrap();
let refspec = branch.get().name().unwrap();
if refspec == ORIGIN_HEAD {
continue
}
let _ = revwalk.push_ref(refspec);
}

for commit_oid in revwalk {
let commit_oid = commit_oid?;
let commit = repo.find_commit(commit_oid)?;
let notes: Vec<Note> = repo.notes(Option::from(GTM_NOTES_REF))?
.map(|n| n.unwrap())
.filter(|n| n.1 == commit_oid)
.map(|n| repo.find_note(Option::from(GTM_NOTES_REF), n.1).unwrap())
.collect();

let res = gtm::parse_commit(&repo, &commit, &notes)?;
commits.push(res);
}
return Result::Ok(commits);
}
Loading

0 comments on commit aa2d668

Please sign in to comment.