diff --git a/Cargo.lock b/Cargo.lock index 6459e77..3cd698b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 7ce781a..53bd44b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,27 +3,33 @@ name = "fedhub" version = "0.1.0" authors = ["Michael Zhang "] 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" diff --git a/README.md b/README.md new file mode 100644 index 0000000..d5edbd5 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +fedhub +====== + +Federated git forge. + +Contact +------- + +Author: Michael Zhang + +License: MIT/Apache Dual License diff --git a/async-git/.ignore b/async-git/.ignore new file mode 100644 index 0000000..d5217ec --- /dev/null +++ b/async-git/.ignore @@ -0,0 +1 @@ +/tests \ No newline at end of file diff --git a/async-git/Cargo.toml b/async-git/Cargo.toml new file mode 100644 index 0000000..f9016cd --- /dev/null +++ b/async-git/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "async-git" +version = "0.1.0" +authors = ["Michael Zhang "] +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" } \ No newline at end of file diff --git a/async-git/bin/git.rs b/async-git/bin/git.rs new file mode 100644 index 0000000..a22edf5 --- /dev/null +++ b/async-git/bin/git.rs @@ -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(()) +} diff --git a/async-git/src/config.rs b/async-git/src/config.rs new file mode 100644 index 0000000..9c299dc --- /dev/null +++ b/async-git/src/config.rs @@ -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 `` is logged to the file `$GIT_DIR/logs/`, 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/` 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 { + Ok(Config::default()) + } + + pub async fn to_string(self) -> Result { + 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(&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(()) + } +} diff --git a/async-git/src/lib.rs b/async-git/src/lib.rs new file mode 100644 index 0000000..c4e31bc --- /dev/null +++ b/async-git/src/lib.rs @@ -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}; diff --git a/async-git/src/plumbing/commit.rs b/async-git/src/plumbing/commit.rs new file mode 100644 index 0000000..e8e4e23 --- /dev/null +++ b/async-git/src/plumbing/commit.rs @@ -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(self, w: W) -> Result<(), IOError> { + Ok(()) + } +} diff --git a/async-git/src/plumbing/hash_object.rs b/async-git/src/plumbing/hash_object.rs new file mode 100644 index 0000000..7f12e7b --- /dev/null +++ b/async-git/src/plumbing/hash_object.rs @@ -0,0 +1,16 @@ +use anyhow::Result; +use sha1::{Digest, Sha1}; +use tokio::io::{AsyncRead, AsyncReadExt}; + +pub async fn hash_object(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()) +} diff --git a/async-git/src/plumbing/mod.rs b/async-git/src/plumbing/mod.rs new file mode 100644 index 0000000..2bfb3e8 --- /dev/null +++ b/async-git/src/plumbing/mod.rs @@ -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; diff --git a/async-git/src/plumbing/object.rs b/async-git/src/plumbing/object.rs new file mode 100644 index 0000000..221d75f --- /dev/null +++ b/async-git/src/plumbing/object.rs @@ -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::(&self.id.0[1..]); + self.repo_path.join("objects").join(first_byte).join(rest) + } + + pub async fn peel(&self) -> Result { + 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 { + 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 { + 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, +} diff --git a/async-git/src/plumbing/repo.rs b/async-git/src/plumbing/repo.rs new file mode 100644 index 0000000..5614ac7 --- /dev/null +++ b/async-git/src/plumbing/repo.rs @@ -0,0 +1,7 @@ +use std::path::Path; + +pub struct Repository {} + +impl Repository { + pub async fn open(path: impl AsRef) {} +} diff --git a/async-git/src/porcelain/init.rs b/async-git/src/porcelain/init.rs new file mode 100644 index 0000000..7b3b900 --- /dev/null +++ b/async-git/src/porcelain/init.rs @@ -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, + + /// 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, +} + +#[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(()) +} diff --git a/async-git/src/porcelain/log.rs b/async-git/src/porcelain/log.rs new file mode 100644 index 0000000..3fa6547 --- /dev/null +++ b/async-git/src/porcelain/log.rs @@ -0,0 +1,8 @@ +use crate::plumbing::Oid; + +#[derive(StructOpt)] +pub struct LogOptions { + commit: Oid, +} + +pub async fn log_cmd(options: LogOptions) {} diff --git a/async-git/src/porcelain/mod.rs b/async-git/src/porcelain/mod.rs new file mode 100644 index 0000000..febd503 --- /dev/null +++ b/async-git/src/porcelain/mod.rs @@ -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}; diff --git a/async-git/src/porcelain/show.rs b/async-git/src/porcelain/show.rs new file mode 100644 index 0000000..509b367 --- /dev/null +++ b/async-git/src/porcelain/show.rs @@ -0,0 +1,15 @@ +use crate::plumbing::Oid; + +#[derive(StructOpt)] +pub struct ShowOptions { + /// The names of objects to show (defaults to HEAD). + objects: Vec, +} + +pub async fn show_cmd(options: ShowOptions) { + let mut objects = options.objects; + + for id in objects { + + } +} diff --git a/async-git/src/util.rs b/async-git/src/util.rs new file mode 100644 index 0000000..a58e20e --- /dev/null +++ b/async-git/src/util.rs @@ -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 ZlibIO for ZlibDecoder {} + +#[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(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, contents: impl AsRef) -> Result<(), io::Error> { + let mut file = File::create(path.as_ref()).await?; + file.write_all(contents.as_ref().as_bytes()).await?; + Ok(()) +} diff --git a/async-zlib/Cargo.toml b/async-zlib/Cargo.toml new file mode 100644 index 0000000..f712159 --- /dev/null +++ b/async-zlib/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "async-zlib" +version = "0.1.0" +authors = ["Michael Zhang "] +edition = "2018" + +[dependencies] +tokio = { version = "0.2.18", default-features = false, features = ["fs", "macros"] } diff --git a/async-zlib/src/decoder.rs b/async-zlib/src/decoder.rs new file mode 100644 index 0000000..aeb8c42 --- /dev/null +++ b/async-zlib/src/decoder.rs @@ -0,0 +1 @@ +pub struct ZlibDecoder(R); diff --git a/async-zlib/src/encoder.rs b/async-zlib/src/encoder.rs new file mode 100644 index 0000000..cc520a7 --- /dev/null +++ b/async-zlib/src/encoder.rs @@ -0,0 +1 @@ +pub struct ZlibEncoder(W); diff --git a/async-zlib/src/lib.rs b/async-zlib/src/lib.rs new file mode 100644 index 0000000..a908f83 --- /dev/null +++ b/async-zlib/src/lib.rs @@ -0,0 +1,5 @@ +mod decoder; +mod encoder; + +pub use crate::decoder::ZlibDecoder; +pub use crate::encoder::ZlibEncoder; diff --git a/fedhub-hooks/Cargo.toml b/fedhub-hooks/Cargo.toml new file mode 100644 index 0000000..f9b6355 --- /dev/null +++ b/fedhub-hooks/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "fedhub-hooks" +version = "0.1.0" +authors = ["Michael Zhang "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/fedhub-hooks/src/main.rs b/fedhub-hooks/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/fedhub-hooks/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/gitutil.rs b/src/gitutil.rs new file mode 100644 index 0000000..240295a --- /dev/null +++ b/src/gitutil.rs @@ -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) -> 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, +) -> 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::()) + } + _ => Ok((entry.id(), kind)), + } + } else { + anyhow::bail!("doesn't exist bro: {:?}", next_file); + } + } else { + Ok((tree.id(), ObjectType::Tree)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9a46633..eb5a6ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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::>()); 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::(); @@ -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) -> 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, -) -> 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::()) - } - _ => Ok((entry.id(), kind)), - } - } else { - anyhow::bail!("doesn't exist bro: {:?}", next_file); - } - } else { - Ok((tree.id(), ObjectType::Tree)) - } -} diff --git a/templates/repo_index.html b/templates/repo_index.html index 983e3cf..5b04bbf 100644 --- a/templates/repo_index.html +++ b/templates/repo_index.html @@ -6,7 +6,7 @@

branches:

{% endblock %} diff --git a/templates/repo_tree.html b/templates/repo_tree.html index 73c7b77..0e1ed52 100644 --- a/templates/repo_tree.html +++ b/templates/repo_tree.html @@ -6,7 +6,13 @@

entries:

+ + {% if readme %} +
+ {{ readme.rendered | safe }} +
+ {% endif %} {% endblock %}