implement git store sync (maybe)

This commit is contained in:
Brian Picciano 2023-05-04 14:56:31 +02:00
parent 72df0f0b21
commit 2663838af0
10 changed files with 2598 additions and 64 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
.cargo

2432
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,3 +8,8 @@ edition = "2021"
[dependencies] [dependencies]
sha2 = "0.10.6" sha2 = "0.10.6"
hex = "0.4.3" hex = "0.4.3"
gix = { version = "0.44.1", features = [
"blocking-network-client",
"blocking-http-transport-reqwest-rust-tls",
]}
tempdir = "0.3.7"

View File

@ -35,16 +35,16 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1683020768, "lastModified": 1683103914,
"narHash": "sha256-ZyZl6k9NWS5QPwD3NoAVz/eSgodQDvl+y+fu8MVbrHc=", "narHash": "sha256-Mbrst3sLaiL55eOlZOEL8kB+XTWffaQVfcI03YWiryg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "44f30edf5661d86fb3a95841c35127f3d0ea8b0f", "rev": "54495a4eafe99c537695a87fe04cb50bf17e651d",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixpkgs-unstable", "ref": "nixos-22.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View File

@ -1,7 +1,7 @@
{ {
inputs = { inputs = {
naersk.url = "github:nix-community/naersk/master"; naersk.url = "github:nix-community/naersk/master";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.11";
utils.url = "github:numtide/flake-utils"; utils.url = "github:numtide/flake-utils";
}; };
@ -16,6 +16,9 @@
devShell = with pkgs; mkShell { devShell = with pkgs; mkShell {
buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy ]; buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy ];
RUST_SRC_PATH = rustPlatform.rustLibSrc; RUST_SRC_PATH = rustPlatform.rustLibSrc;
shellHook = ''
export CARGO_HOME=$(pwd)/.cargo
'';
}; };
}); });
} }

1
src/lib.rs Normal file
View File

@ -0,0 +1 @@
pub mod origin;

View File

@ -1,13 +0,0 @@
mod origin;
use crate::origin::Descr;
fn main() {
let d = Descr::Git{
url: "foo",
branch: "bar",
};
println!("{}", d.id());
}

View File

@ -1,44 +1,10 @@
use sha2::{Sha256, Digest}; mod descr;
use hex::ToHex; pub use self::descr::Descr;
/// A unique description of an origin, from where a domain might be served. pub mod store;
pub enum Descr<'a> {
Git{url: &'a str, branch: &'a str}, /// A View of an origin is used to read the files of that origin at a particular point in time. A
/// single instance of a View should remain consistent across its lifetime.
pub trait View {
fn open(&self, path: &str) -> &dyn std::io::Read;
} }
impl<'a> Descr<'a> {
/// Returns a globally unique string for the Descr.
pub fn id(&'a self) -> String {
let mut h = Sha256::new();
let mut h_update = |b: &str| {
h.update(b);
h.update("\n");
};
match self {
Descr::Git{url, branch} => {
h_update("git");
h_update(url);
h_update(branch);
}
}
h.finalize().encode_hex::<String>()
}
}
#[cfg(test)]
mod descr_tests {
#[test]
fn id() {
assert_eq!(
super::Descr::Git{url:"foo", branch:"bar"}.id(),
"424130b9e6c1675c983b4b97579877e16d8a6377e4fe10970e6d210811c3b7ac",
)
}
//}

View File

@ -1,9 +1,10 @@
use sha2::{Sha256, Digest}; use sha2::{Sha256, Digest};
use hex::ToHex; use hex::ToHex;
#[derive(Clone,Copy)]
/// A unique description of an origin, from where a domain might be served. /// A unique description of an origin, from where a domain might be served.
pub enum Descr<'a> { pub enum Descr<'a> {
Git{url: &'a str, branch: &'a str}, Git{url: &'a str, branch_name: &'a str},
} }
impl<'a> Descr<'a> { impl<'a> Descr<'a> {
@ -19,10 +20,10 @@ impl<'a> Descr<'a> {
}; };
match self { match self {
Descr::Git{url, branch} => { Descr::Git{url, branch_name} => {
h_update("git"); h_update("git");
h_update(url); h_update(url);
h_update(branch); h_update(branch_name);
} }
} }
@ -37,7 +38,7 @@ mod descr_tests {
fn id() { fn id() {
assert_eq!( assert_eq!(
super::Descr::Git{url:"foo", branch:"bar"}.id(), super::Descr::Git{url:"foo", branch_name:"bar"}.id(),
"424130b9e6c1675c983b4b97579877e16d8a6377e4fe10970e6d210811c3b7ac", "424130b9e6c1675c983b4b97579877e16d8a6377e4fe10970e6d210811c3b7ac",
) )
} }

138
src/origin/store.rs Normal file
View File

@ -0,0 +1,138 @@
use std::error::Error;
use super::Descr;
use super::View;
#[derive(Clone,Copy)]
pub struct Limits {}
pub enum GetViewError {
TODO,
Unexpected(Box<dyn Error>),
}
#[derive(Debug)]
pub enum SyncError {
InvalidURL,
InvalidBranchName,
Unexpected(Box<dyn Error>),
}
pub trait Store {
fn get_view(&self, descr: Descr) -> Result<&dyn View, GetViewError>;
fn sync(&self, descr: Descr, limits: Limits) -> Result<(), SyncError>;
}
pub mod git {
use std::path::Path;
use super::{Store as originStore, *};
use gix::clone::Error as gixCloneErr;
use gix::progress::Discard;
// Catching error from gix::prepare_clone_bare
impl From<gixCloneErr> for SyncError {
fn from(e: gixCloneErr) -> SyncError {
match e {
gixCloneErr::Init(gix::init::Error::InvalidBranchName{..}) =>
SyncError::InvalidBranchName,
gixCloneErr::UrlParse(_) |
gixCloneErr::CanonicalizeUrl{..} =>
SyncError::InvalidURL,
_ =>
SyncError::Unexpected(Box::from(e)),
}
}
}
pub struct Store<'a> {
dir_path: &'a Path,
}
impl<'a> Store<'a> {
pub fn new(dir_path: &Path) -> std::io::Result<Store> {
std::fs::create_dir_all(dir_path)?;
println!("{}", &dir_path.display());
Ok(Store{dir_path})
}
}
impl<'a> originStore for Store<'a> {
fn sync(&self, descr: Descr, _limits: Limits) -> Result<(), SyncError> {
let should_interrupt = &core::sync::atomic::AtomicBool::new(false);
let Descr::Git{url, ..} = descr;
let path = &self.dir_path.clone().join(descr.id());
// if the path doesn't exist then use the gix clone feature to clone it into the
// directory.
if std::fs::read_dir(path).is_err() {
std::fs::create_dir_all(path)
.map_err(|e| SyncError::Unexpected(Box::from(e)))?;
gix::prepare_clone_bare(url, path)?
.fetch_only(Discard, should_interrupt)
.map_err(|_| SyncError::InvalidURL)?;
return Ok(());
}
let direction = gix::remote::Direction::Fetch;
let repo = gix::open(path)
.map_err(|e| SyncError::Unexpected(Box::from(e)))?;
let remote = repo.find_default_remote(direction)
.ok_or(SyncError::Unexpected(Box::from("no default configured")))?
.map_err(|e| SyncError::Unexpected(Box::from(e)))?;
remote
.connect(direction)
.map_err(|e| SyncError::Unexpected(Box::from(e)))?
.prepare_fetch(Discard, Default::default())
.map_err(|e| SyncError::Unexpected(Box::from(e)))?
.receive(Discard, should_interrupt)
.map_err(|e| SyncError::Unexpected(Box::from(e)))?;
Ok(())
}
fn get_view(&self, _descr: Descr) -> Result<&dyn View, GetViewError> {
Err(GetViewError::TODO)
}
}
#[cfg(test)]
mod tests {
use super::Descr;
use super::super::Store;
use tempdir::TempDir;
#[test]
fn sync() {
let tmp_dir = TempDir::new("origin_store_git").unwrap();
let descr = Descr::Git{
url: "https://github.com/mediocregopher/priority-finder.git",
branch_name: "master",
};
let limits = super::Limits{};
let store = super::Store::new(tmp_dir.path()).expect("store created");
store.sync(descr, limits).expect("sync should succeed");
store.sync(descr, limits).expect("second sync should succeed");
}
}
}