start async git implementation

This commit is contained in:
Michael Zhang 2020-04-21 19:21:28 -05:00
parent a0f56e58cf
commit 84d6ffe709
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
28 changed files with 700 additions and 225 deletions

275
Cargo.lock generated
View file

@ -1,5 +1,11 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "adler32"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
[[package]]
name = "aho-corasick"
version = "0.7.10"
@ -9,15 +15,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "anyhow"
version = "1.0.28"
@ -46,14 +43,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
name = "async-git"
version = "0.1.0"
dependencies = [
"hermit-abi",
"libc",
"winapi 0.3.8",
"anyhow",
"async-zlib",
"flate2",
"futures 0.3.4",
"sha-1",
"structopt",
"thiserror",
"tokio",
"typenum",
]
[[package]]
name = "async-zlib"
version = "0.1.0"
dependencies = [
"tokio",
]
[[package]]
@ -116,6 +124,16 @@ version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "bytes"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
dependencies = [
"byteorder",
"iovec",
]
[[package]]
name = "bytes"
version = "0.5.4"
@ -137,40 +155,15 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "chrono"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
dependencies = [
"num-integer",
"num-traits",
"time",
]
[[package]]
name = "chrono-tz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0e430fad0384e4defc3dc6b1223d1b886087a8bf9b7080e5ae027f73851ea15"
dependencies = [
"chrono",
"parse-zoneinfo",
]
[[package]]
name = "clap"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
@ -201,7 +194,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c"
dependencies = [
"clap",
"entities",
"lazy_static",
"pest",
@ -212,6 +204,15 @@ dependencies = [
"unicode_categories",
]
[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.4.2"
@ -233,12 +234,6 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "deunicode"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690"
[[package]]
name = "digest"
version = "0.8.1"
@ -277,8 +272,9 @@ name = "fedhub"
version = "0.1.0"
dependencies = [
"anyhow",
"async-git",
"comrak",
"futures",
"futures 0.3.4",
"git2",
"hyper",
"lazy_static",
@ -296,6 +292,24 @@ dependencies = [
"walkdir",
]
[[package]]
name = "fedhub-hooks"
version = "0.1.0"
[[package]]
name = "flate2"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
dependencies = [
"cfg-if",
"crc32fast",
"futures 0.1.29",
"libc",
"miniz_oxide",
"tokio-io",
]
[[package]]
name = "fnv"
version = "1.0.6"
@ -318,6 +332,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futures"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
[[package]]
name = "futures"
version = "0.3.4"
@ -459,7 +479,7 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42"
dependencies = [
"bytes",
"bytes 0.5.4",
"fnv",
"futures-core",
"futures-sink",
@ -496,7 +516,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
dependencies = [
"bytes",
"bytes 0.5.4",
"fnv",
"itoa",
]
@ -507,7 +527,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
dependencies = [
"bytes",
"bytes 0.5.4",
"http",
]
@ -517,19 +537,13 @@ version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
[[package]]
name = "humansize"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
[[package]]
name = "hyper"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14"
dependencies = [
"bytes",
"bytes 0.5.4",
"futures-channel",
"futures-core",
"futures-util",
@ -711,6 +725,15 @@ version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "miniz_oxide"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5"
dependencies = [
"adler32",
]
[[package]]
name = "mio"
version = "0.6.21"
@ -896,15 +919,6 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "parse-zoneinfo"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feece9d0113b400182a7d00adcff81ccf29158c49c5abd11e2eed8589bf6ff07"
dependencies = [
"regex",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -1089,7 +1103,7 @@ version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eeb1fe3fc011cde97315f370bc88e4db3c23b08709a04915921e02b1d363b20"
dependencies = [
"bytes",
"bytes 0.5.4",
"combine",
"dtoa",
"futures-executor",
@ -1188,15 +1202,15 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.105"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff"
checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
[[package]]
name = "serde_derive"
version = "1.0.105"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8"
checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
dependencies = [
"proc-macro2",
"quote",
@ -1248,15 +1262,6 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "slug"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
dependencies = [
"deunicode",
]
[[package]]
name = "smallvec"
version = "1.2.0"
@ -1281,12 +1286,6 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.13"
@ -1339,20 +1338,13 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d25bed9d2cf684de6ffdaa9ac35373739eeed0d6bef1de545bbe6cf571ad07"
dependencies = [
"chrono",
"chrono-tz",
"globwalk",
"humansize",
"lazy_static",
"percent-encoding",
"pest",
"pest_derive",
"rand",
"regex",
"serde",
"serde_json",
"slug",
"unic-segment",
]
[[package]]
@ -1364,6 +1356,26 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b3d3d2ff68104100ab257bb6bb0cb26c901abe4bd4ba15961f3bf867924012"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca972988113b7715266f91250ddb98070d033c62a011fa0fcc57434a649310dd"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.0.1"
@ -1390,7 +1402,7 @@ version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713"
dependencies = [
"bytes",
"bytes 0.5.4",
"fnv",
"futures-core",
"iovec",
@ -1408,6 +1420,17 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "tokio-io"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
dependencies = [
"bytes 0.4.12",
"futures 0.1.29",
"log",
]
[[package]]
name = "tokio-macros"
version = "0.2.5"
@ -1425,7 +1448,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930"
dependencies = [
"bytes",
"bytes 0.5.4",
"futures-core",
"futures-sink",
"log",
@ -1439,7 +1462,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
dependencies = [
"bytes",
"bytes 0.5.4",
"futures-core",
"futures-sink",
"log",
@ -1502,56 +1525,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
[[package]]
name = "unic-char-property"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-segment"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
dependencies = [
"unic-ucd-segment",
]
[[package]]
name = "unic-ucd-segment"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]]
name = "unicode-bidi"
version = "0.3.4"
@ -1620,12 +1593,6 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
[[package]]
name = "version_check"
version = "0.9.1"

View file

@ -3,27 +3,33 @@ name = "fedhub"
version = "0.1.0"
authors = ["Michael Zhang <iptq@protonmail.com>"]
edition = "2018"
license = "MIT/Apache-2.0"
[workspace]
members = [
"async-git",
"fedhub-hooks",
]
[dependencies]
anyhow = "1.0.28"
comrak = "0.7.0"
anyhow = { version = "1.0.28", default-features = false, features = ["std"] }
async-git = { path = "async-git" }
comrak = { version = "0.7.0", default-features = false }
futures = { version = "0.3.4", default-features = false }
git2 = { version = "0.13.0", default-features = false }
hyper = "0.13.5"
lazy_static = "1.4.0"
parking_lot = "0.10.2"
redis = "0.15.1"
serde = "1.0.105"
serde_derive = "1.0.105"
serde_json = "1.0.48"
structopt = "0.3.13"
tera = "1.2.0"
tokio = { version = "0.2.18", features = ["full"] }
toml = "0.5.6"
walkdir = "2.3.1"
packer = "0.5.3"
hyper = { version = "0.13.5", default-features = false, features = ["runtime"] }
lazy_static = { version = "1.4.0", default-features = false }
packer = { version = "0.5.3", default-features = false }
parking_lot = { version = "0.10.2", default-features = false }
redis = { version = "0.15.1", default-features = false }
serde = { version = "1.0.105", default-features = false }
serde_derive = { version = "1.0.105", default-features = false }
serde_json = { version = "1.0.48", default-features = false }
structopt = { version = "0.3.13", default-features = false }
tera = { version = "1.2.0", default-features = false }
tokio = { version = "0.2.18", default-features = false, features = ["full"] }
toml = { version = "0.5.6", default-features = false }
walkdir = { version = "2.3.1", default-features = false }
[build-dependencies]
rsass = "0.13.0"

11
README.md Normal file
View file

@ -0,0 +1,11 @@
fedhub
======
Federated git forge.
Contact
-------
Author: Michael Zhang
License: MIT/Apache Dual License

1
async-git/.ignore Normal file
View file

@ -0,0 +1 @@
/tests

20
async-git/Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
[package]
name = "async-git"
version = "0.1.0"
authors = ["Michael Zhang <iptq@protonmail.com>"]
edition = "2018"
[[bin]]
name = "git"
path = "bin/git.rs"
[dependencies]
tokio = { version = "0.2.18", default-features = false, features = ["fs", "macros"] }
typenum = { version = "1.12.0", default-features = false }
structopt = { version = "0.3.13", default-features = false }
anyhow = { version = "1.0.28", default-features = false }
thiserror = { version = "1.0.15", default-features = false }
futures = { version = "0.3.4", default-features = false }
sha-1 = { version = "0.8.2", default-features = false }
flate2 = { version = "1.0.14", features = ["tokio"] }
async-zlib = { path = "../async-zlib" }

35
async-git/bin/git.rs Normal file
View file

@ -0,0 +1,35 @@
use anyhow::Result;
use structopt::StructOpt;
macro_rules! define_commands {
($(($name:expr, $cmd_fn:ident, $cmd_variant:ident, $opt_struct:ident),)*) => {
#[derive(Debug, StructOpt)]
enum Command {
$(
#[structopt(name = $name)]
$cmd_variant(async_git::porcelain::$opt_struct),
)*
}
async fn handle_command(cmd: Command) -> Result<()> {
match cmd {
$(
Command::$cmd_variant(options) => async_git::porcelain::$cmd_fn(options).await?,
)*
};
Ok(())
}
};
}
define_commands!(
("init", init_cmd, Init, InitOptions),
("log", log_cmd, Log, LogOptions),
);
#[tokio::main]
async fn main() -> Result<()> {
handle_command(Command::from_args()).await?;
Ok(())
}

82
async-git/src/config.rs Normal file
View file

@ -0,0 +1,82 @@
use std::io::{Cursor, Error as IOError};
use anyhow::Result;
use tokio::io::{AsyncWrite, AsyncWriteExt};
#[derive(Clone, Debug, Default)]
pub struct Config {
pub core: CoreConfig,
}
#[derive(Clone, Debug)]
pub struct CoreConfig {
/// If `true` this repository is assumed to be `bare` and has no working directory associated with it. If this is the case a number of commands that require a working directory will be disabled, such as git-add or git-merge.
///
/// This setting is automatically guessed by git-clone or git-init when the repository was created. By default a repository that ends in `/.git` is assumed to be not bare (bare = `false`), while all other repositories are assumed to be bare (bare = `true`).
pub bare: bool,
/// Tells Git if the executable bit of files in the working tree is to be honored.
///
/// Some filesystems lose the executable bit when a file that is marked as executable is checked out, or checks out a non-executable file with executable bit on. git-clone or git-init probe the filesystem to see if it handles the executable bit correctly and this variable is automatically set as necessary.
///
/// A repository, however, may be on a filesystem that handles the filemode correctly, and this variable is set to `true` when created, but later may be made accessible from another environment that loses the filemode (e.g. exporting ext4 via CIFS mount, visiting a Cygwin created repository with Git for Windows or Eclipse). In such a case it may be necessary to set this variable to `false`. See git-update-index.
///
/// The default is `true` (when core.filemode is not specified in the config file).
pub file_mode: bool,
/// Enable the reflog. Updates to a ref `<ref>` is logged to the file `$GIT_DIR/logs/<ref>`, by appending the new and old SHA-1, the date/time and the reason of the update, but only when the file exists. If this configuration variable is set to `true`, missing `$GIT_DIR/logs/<ref>` file is automatically created for branch heads (i.e. under `refs/heads/`), remote refs (i.e. under `refs/remotes/`), note refs (i.e. under `refs/notes/`), and the symbolic ref HEAD. If it is set to always, then a missing reflog is automatically created for any ref under `refs/`.
///
/// This information can be used to determine what commit was the tip of a branch "2 days ago".
///
/// This value is `true` by default in a repository that has a working directory associated with it, and `false` by default in a bare repository.
pub log_all_ref_updates: bool,
/// Internal variable identifying the repository format and layout version.
pub repository_format_version: u32,
}
impl Default for CoreConfig {
fn default() -> Self {
CoreConfig {
bare: false,
file_mode: true,
log_all_ref_updates: true,
repository_format_version: 0,
}
}
}
impl Config {
pub async fn parse() -> Result<Self> {
Ok(Config::default())
}
pub async fn to_string(self) -> Result<String, IOError> {
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
self.write(&mut cursor).await?;
Ok(String::from_utf8(buffer).unwrap())
}
pub async fn write<W: AsyncWrite + Unpin>(&self, mut w: W) -> Result<(), IOError> {
{
let core = &self.core;
w.write_all(b"[core]\n").await?;
w.write_all(format!("bare = {}\n", core.bare).as_bytes())
.await?;
w.write_all(format!("fileMode = {}\n", core.file_mode).as_bytes())
.await?;
w.write_all(format!("logAllRefUpdates = {}\n", core.log_all_ref_updates).as_bytes())
.await?;
w.write_all(
format!(
"repositoryFormatVersion = {}\n",
core.repository_format_version
)
.as_bytes(),
)
.await?;
}
Ok(())
}
}

15
async-git/src/lib.rs Normal file
View file

@ -0,0 +1,15 @@
#[macro_use]
extern crate anyhow;
#[macro_use]
extern crate structopt;
#[macro_use]
extern crate thiserror;
#[macro_use]
extern crate tokio;
mod config;
pub mod plumbing;
pub mod porcelain;
mod util;
pub use crate::config::{Config, CoreConfig};

View file

@ -0,0 +1,21 @@
use std::io::Error as IOError;
use tokio::io::{AsyncWrite, AsyncRead};
use crate::util::ZlibIO;
use crate::plumbing::Oid;
#[derive(Clone)]
pub struct Commit {
tree: Oid,
parent: Oid,
author: String,
committer: String,
gpgsig: String,
}
impl Commit {
pub async fn write<W: AsyncWrite + Unpin>(self, w: W) -> Result<(), IOError> {
Ok(())
}
}

View file

@ -0,0 +1,16 @@
use anyhow::Result;
use sha1::{Digest, Sha1};
use tokio::io::{AsyncRead, AsyncReadExt};
pub async fn hash_object<R: AsyncRead + Unpin>(mut r: R) -> Result<[u8; 20]> {
let mut hasher = Sha1::new();
let mut buffer = [0; 64];
loop {
let bytes = r.read(&mut buffer).await?;
if bytes == 0 {
break;
}
hasher.input(&buffer[..bytes]);
}
Ok(hasher.result().into())
}

View file

@ -0,0 +1,9 @@
mod commit;
mod hash_object;
mod object;
mod repo;
pub use self::commit::Commit;
pub use self::hash_object::hash_object;
pub use self::object::{Object, Oid};
pub use self::repo::Repository;

View file

@ -0,0 +1,91 @@
use std::fs::File;
use std::path::PathBuf;
use std::pin::Pin;
use std::str::FromStr;
use std::task::{Context, Poll};
use anyhow::Error;
use flate2::read::ZlibDecoder;
use tokio::io::{AsyncReadExt};
use typenum::U19;
use crate::plumbing::Commit;
use crate::util::{ZlibIO, self};
pub struct Object {
repo_path: PathBuf,
id: Oid,
}
impl Object {
fn get_path_on_disk(&self) -> PathBuf {
let first_byte = format!("{:02x}", self.id.0[0]);
let rest = util::hexdump::<U19>(&self.id.0[1..]);
self.repo_path.join("objects").join(first_byte).join(rest)
}
pub async fn peel(&self) -> Result<ParsedObject, Error> {
let file = File::open(self.get_path_on_disk())?;
let r = ZlibDecoder::new(file);
// we're going to determine the type of object based on the first character
let c = r.read_u8().await?;
}
}
#[derive(Clone)]
pub struct Oid([u8; 20]);
impl FromStr for Oid {
type Err = Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
let mut buffer = [0; 20];
let bytes = string.as_bytes();
if bytes.len() != 40 {
bail!("object ids must be 40 characters long");
}
let byte_conv = |byte: u8| -> Result<u8, Self::Err> {
Ok(match byte {
b'0' => 0,
b'1' => 1,
b'2' => 2,
b'3' => 3,
b'4' => 4,
b'5' => 5,
b'6' => 6,
b'7' => 7,
b'8' => 8,
b'9' => 9,
b'a' => 10,
b'b' => 11,
b'c' => 12,
b'd' => 13,
b'e' => 14,
b'f' => 15,
_ => bail!("invalid hex byte: {:?}", byte),
})
};
let mut ctr = 0;
for window in string.as_bytes().windows(2) {
if let &[first, second] = window {
let first_byte = byte_conv(first)?;
let second_byte = byte_conv(second)?;
buffer[ctr] = first_byte << 4 | second_byte;
ctr += 1;
}
}
Ok(Oid(buffer))
}
}
pub enum ParsedObject {
Blob,
Commit(Commit),
Tag,
Tree,
}

View file

@ -0,0 +1,7 @@
use std::path::Path;
pub struct Repository {}
impl Repository {
pub async fn open(path: impl AsRef<Path>) {}
}

View file

@ -0,0 +1,65 @@
use std::env;
use std::path::PathBuf;
use futures::future::TryFutureExt;
use tokio::fs::{self};
use crate::config::Config;
#[derive(Debug, StructOpt)]
pub struct InitOptions {
/// Specify the directory from which templates will be used.
#[structopt(long = "template")]
template: Option<PathBuf>,
/// Create a bare repository.
#[structopt(long = "bare")]
bare: bool,
/// The directory where the git repository should be initialized.
///
/// If you don't provide this value, the current working directory will be used.
/// If the directory you provided doesn't exist, it will be created.
directory: Option<PathBuf>,
}
#[derive(Debug, Error)]
pub enum Error {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
pub async fn init_cmd(options: InitOptions) -> Result<(), Error> {
let mut target_directory = match options.directory {
Some(dir) => dir,
None => env::current_dir()?,
};
if !options.bare {
target_directory.push(".git");
}
fs::create_dir_all(&target_directory).await?;
// make contents of the git directory
try_join!(
fs::create_dir(target_directory.join("branches")),
fs::create_dir(target_directory.join("objects")),
fs::create_dir(target_directory.join("refs")).and_then(|_| async {
try_join!(
fs::create_dir(target_directory.join("refs").join("tags")),
fs::create_dir(target_directory.join("refs").join("heads"))
)
}),
crate::util::init_file(
target_directory.join("description"),
"Unnamed repository; edit this file 'description' to name the repository.\n"
),
crate::util::init_file(target_directory.join("HEAD"), "ref: refs/heads/master\n"),
Config::default()
.to_string()
.and_then(|config| crate::util::init_file(target_directory.join("config"), config)),
)?;
Ok(())
}

View file

@ -0,0 +1,8 @@
use crate::plumbing::Oid;
#[derive(StructOpt)]
pub struct LogOptions {
commit: Oid,
}
pub async fn log_cmd(options: LogOptions) {}

View file

@ -0,0 +1,7 @@
mod init;
mod show;
mod log;
pub use self::init::{init_cmd, InitOptions};
pub use self::show::{show_cmd, ShowOptions};
pub use self::log::{log_cmd, LogOptions};

View file

@ -0,0 +1,15 @@
use crate::plumbing::Oid;
#[derive(StructOpt)]
pub struct ShowOptions {
/// The names of objects to show (defaults to HEAD).
objects: Vec<Oid>,
}
pub async fn show_cmd(options: ShowOptions) {
let mut objects = options.objects;
for id in objects {
}
}

37
async-git/src/util.rs Normal file
View file

@ -0,0 +1,37 @@
use std::io;
use std::path::Path;
use tokio::{fs::File, io::AsyncWriteExt};
use flate2::read::ZlibDecoder;
use typenum::Unsigned;
/// Marker trait to ensure compression/decompression is already happening transparently.
pub trait ZlibIO {}
impl<R> ZlibIO for ZlibDecoder<R> {}
#[inline]
fn hexchar(b: u8) -> [u8; 2] {
let a = b >> 4;
let b = b & 0xf;
[
if a < 10 { a + 0x30 } else { (a - 10) + 0x61 },
if b < 10 { b + 0x30 } else { (b - 10) + 0x61 },
]
}
pub fn hexdump<N: Unsigned>(slice: &[u8]) -> String {
let mut buffer = vec![0u8; N::to_usize() * 2];
for byte in 0..N::to_usize() {
let [a, b] = hexchar(slice[byte]);
buffer[byte * 2] = a;
buffer[byte * 2 + 1] = b;
}
unsafe { String::from_utf8_unchecked(buffer) }
}
pub async fn init_file(path: impl AsRef<Path>, contents: impl AsRef<str>) -> Result<(), io::Error> {
let mut file = File::create(path.as_ref()).await?;
file.write_all(contents.as_ref().as_bytes()).await?;
Ok(())
}

8
async-zlib/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "async-zlib"
version = "0.1.0"
authors = ["Michael Zhang <iptq@protonmail.com>"]
edition = "2018"
[dependencies]
tokio = { version = "0.2.18", default-features = false, features = ["fs", "macros"] }

View file

@ -0,0 +1 @@
pub struct ZlibDecoder<R>(R);

View file

@ -0,0 +1 @@
pub struct ZlibEncoder<W>(W);

5
async-zlib/src/lib.rs Normal file
View file

@ -0,0 +1,5 @@
mod decoder;
mod encoder;
pub use crate::decoder::ZlibDecoder;
pub use crate::encoder::ZlibEncoder;

9
fedhub-hooks/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "fedhub-hooks"
version = "0.1.0"
authors = ["Michael Zhang <iptq@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

3
fedhub-hooks/src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

42
src/gitutil.rs Normal file
View file

@ -0,0 +1,42 @@
use std::path::{Path, PathBuf};
use anyhow::Result;
use git2::{ObjectType, Oid, Repository, Tree};
pub fn get_tree_from_name(repo: &Repository, name: impl AsRef<str>) -> Result<(Tree, String)> {
Ok(
if let Ok(commit_ref) = repo.resolve_reference_from_short_name(name.as_ref()) {
let tree_name = commit_ref.shorthand().unwrap().to_string();
(commit_ref.peel_to_commit()?.tree()?, tree_name)
} else if let Ok(commit) = repo.find_commit(Oid::from_str(name.as_ref())?) {
let tree_name = name.as_ref().to_string();
(commit.tree()?, tree_name)
} else {
anyhow::bail!("Fuck!");
},
)
}
pub fn get_path_from_tree<'a>(
repo: &Repository,
curr_oid: Oid,
path: impl AsRef<Path>,
) -> Result<(Oid, ObjectType)> {
let mut components = path.as_ref().components();
let tree = repo.find_tree(curr_oid)?;
if let Some(next_file) = components.next() {
if let Some(entry) = tree.get_name(next_file.as_os_str().to_str().unwrap()) {
let kind = entry.kind().unwrap();
match kind {
ObjectType::Tree => {
get_path_from_tree(repo, entry.id(), components.collect::<PathBuf>())
}
_ => Ok((entry.id(), kind)),
}
} else {
anyhow::bail!("doesn't exist bro: {:?}", next_file);
}
} else {
Ok((tree.id(), ObjectType::Tree))
}
}

View file

@ -4,12 +4,14 @@ extern crate serde_derive;
extern crate serde_json;
mod config;
mod gitutil;
use std::path::{Component as PathComponent, Path, PathBuf};
use std::sync::Arc;
use anyhow::Result;
use git2::{ObjectType, Oid, Repository, Tree};
use comrak::{markdown_to_html, ComrakOptions};
use git2::{ObjectType, Oid, Repository};
use hyper::{Body, Request, Response, StatusCode};
use lazy_static::lazy_static;
use packer::Packer;
@ -20,9 +22,20 @@ use walkdir::WalkDir;
pub use crate::config::Config;
#[derive(Packer)]
#[packer(source = "static", prefixed = false)]
pub struct Statics;
#[derive(Packer)]
#[packer(source = "templates", prefixed = false)]
pub struct Templates;
macro_rules! template {
($file:expr) => {
($file, include_str!(concat!("../templates/", $file)))
(
$file,
std::str::from_utf8(Templates::get($file).unwrap()).unwrap(),
)
};
}
@ -41,10 +54,6 @@ lazy_static! {
};
}
#[derive(Packer)]
#[packer(source = "static", prefixed = false)]
pub struct Statics;
#[derive(Clone)]
pub struct Fedhub {
config: Config,
@ -72,7 +81,7 @@ impl Fedhub {
.site_name
.as_ref()
.map(|s| s.as_ref())
.unwrap_or_else(|| "Fedhub");
.unwrap_or_else(|| "fedhub");
ctx.insert(
"fedhub",
&json!({
@ -159,7 +168,14 @@ impl Fedhub {
ctx.insert("tree_name", &tree_name);
ctx.insert("filepath", &filepath);
let mut entries = Vec::new();
let mut readme = None;
for entry in tree.iter() {
if let (Some(ObjectType::Blob), Some("README.md")) = (entry.kind(), entry.name()) {
let object = entry.to_object(&repo)?;
let blob = object.as_blob().unwrap();
let str_contents = String::from_utf8(blob.content().to_vec())?;
readme = Some(str_contents);
}
let url = match &filepath {
Some(filepath) => format!(
"/{}/+/{}/{}/{}",
@ -181,6 +197,15 @@ impl Fedhub {
}));
}
ctx.insert("entries", &entries);
if let Some(readme) = readme {
ctx.insert(
"readme",
&json!({
"source": &readme,
"rendered": markdown_to_html(&readme, &ComrakOptions::default()),
}),
);
}
return Ok(Response::new(TERA.render("repo_tree.html", &ctx)?.into()));
}
@ -197,16 +222,18 @@ impl Fedhub {
let mut ctx = self.context();
ctx.insert("repo_name", &path);
ctx.insert("tree_name", &tree_name);
let contents = if blob.is_binary() {
json!(null)
} else {
let str_contents = String::from_utf8(blob.content().to_vec())?;
json!(str_contents)
};
ctx.insert(
"blob",
&json!({
"name": filepath,
"binary": blob.is_binary(),
"contents": if blob.is_binary() {
json!(null)
} else {
json!(String::from_utf8(blob.content().to_vec())?)
},
"contents": contents,
}),
);
return Ok(Response::new(TERA.render("repo_blob.html", &ctx)?.into()));
@ -223,8 +250,6 @@ impl Fedhub {
if path.starts_with("/static") {
let rest = path.trim_start_matches("/static/");
println!("REST: {:?}", rest);
println!("LIST: {:?}", Statics::list().collect::<Vec<_>>());
return match Statics::get(rest) {
Some(data) => Ok(Response::builder().body(data.into())?),
None => Ok(Response::builder()
@ -267,7 +292,7 @@ impl Fedhub {
tree_name
};
let (tree_id, shortname) = {
let (tree, shortname) = get_tree_from_name(&repo, tree_name)?;
let (tree, shortname) = gitutil::get_tree_from_name(&repo, tree_name)?;
(tree.id(), shortname)
};
let filepath = iter.collect::<PathBuf>();
@ -276,7 +301,7 @@ impl Fedhub {
return self.render_repo_tree(path, &repo, tree_id, shortname, None);
} else {
let (object_id, object_type) =
get_path_from_tree(&repo, tree_id, &filepath)?;
gitutil::get_path_from_tree(&repo, tree_id, &filepath)?;
println!("{:?} {:?}", object_id, object_type);
match object_type {
ObjectType::Tree => {
@ -304,41 +329,3 @@ impl Fedhub {
.body("not found".into())?)
}
}
fn get_tree_from_name(repo: &Repository, name: impl AsRef<str>) -> Result<(Tree, String)> {
Ok(
if let Ok(commit_ref) = repo.resolve_reference_from_short_name(name.as_ref()) {
let tree_name = commit_ref.shorthand().unwrap().to_string();
(commit_ref.peel_to_commit()?.tree()?, tree_name)
} else if let Ok(commit) = repo.find_commit(Oid::from_str(name.as_ref())?) {
let tree_name = name.as_ref().to_string();
(commit.tree()?, tree_name)
} else {
anyhow::bail!("Fuck!");
},
)
}
fn get_path_from_tree<'a>(
repo: &Repository,
curr_oid: Oid,
path: impl AsRef<Path>,
) -> Result<(Oid, ObjectType)> {
let mut components = path.as_ref().components();
let tree = repo.find_tree(curr_oid)?;
if let Some(next_file) = components.next() {
if let Some(entry) = tree.get_name(next_file.as_os_str().to_str().unwrap()) {
let kind = entry.kind().unwrap();
match kind {
ObjectType::Tree => {
get_path_from_tree(repo, entry.id(), components.collect::<PathBuf>())
}
_ => Ok((entry.id(), kind)),
}
} else {
anyhow::bail!("doesn't exist bro: {:?}", next_file);
}
} else {
Ok((tree.id(), ObjectType::Tree))
}
}

View file

@ -6,7 +6,7 @@
<h3>branches:</h3>
<ul>
{% for branch in branches %}
<a href="{{ branch.url }}">{{ branch.name }}</a>
<a href="{{ branch.url | safe }}">{{ branch.name }}</a>
{% endfor %}
</ul>
{% endblock %}

View file

@ -6,7 +6,13 @@
<h3>entries:</h3>
<ul>
{% for entry in entries %}
<li><a href="{{ entry.url }}">{{ entry.name }}</a></li>
<li><a href="{{ entry.url | safe }}">{{ entry.name }}</a></li>
{% endfor %}
</ul>
{% if readme %}
<div>
{{ readme.rendered | safe }}
</div>
{% endif %}
{% endblock %}