start async git implementation
This commit is contained in:
parent
a0f56e58cf
commit
84d6ffe709
28 changed files with 700 additions and 225 deletions
275
Cargo.lock
generated
275
Cargo.lock
generated
|
@ -1,5 +1,11 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# 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]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.10"
|
version = "0.7.10"
|
||||||
|
@ -9,15 +15,6 @@ dependencies = [
|
||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.28"
|
version = "1.0.28"
|
||||||
|
@ -46,14 +43,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
|
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "async-git"
|
||||||
version = "0.2.14"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"anyhow",
|
||||||
"libc",
|
"async-zlib",
|
||||||
"winapi 0.3.8",
|
"flate2",
|
||||||
|
"futures 0.3.4",
|
||||||
|
"sha-1",
|
||||||
|
"structopt",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-zlib"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -116,6 +124,16 @@ version = "1.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
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]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "0.5.4"
|
version = "0.5.4"
|
||||||
|
@ -137,40 +155,15 @@ version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
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]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "2.33.0"
|
version = "2.33.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
|
||||||
"atty",
|
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"strsim",
|
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"vec_map",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -201,7 +194,6 @@ version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c"
|
checksum = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
|
||||||
"entities",
|
"entities",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"pest",
|
"pest",
|
||||||
|
@ -212,6 +204,15 @@ dependencies = [
|
||||||
"unicode_categories",
|
"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]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
@ -233,12 +234,6 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deunicode"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
@ -277,8 +272,9 @@ name = "fedhub"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-git",
|
||||||
"comrak",
|
"comrak",
|
||||||
"futures",
|
"futures 0.3.4",
|
||||||
"git2",
|
"git2",
|
||||||
"hyper",
|
"hyper",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -296,6 +292,24 @@ dependencies = [
|
||||||
"walkdir",
|
"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]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
@ -318,6 +332,12 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.1.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
@ -459,7 +479,7 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42"
|
checksum = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes 0.5.4",
|
||||||
"fnv",
|
"fnv",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
|
@ -496,7 +516,7 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
|
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes 0.5.4",
|
||||||
"fnv",
|
"fnv",
|
||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
@ -507,7 +527,7 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
|
checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes 0.5.4",
|
||||||
"http",
|
"http",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -517,19 +537,13 @@ version = "1.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humansize"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.13.5"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14"
|
checksum = "96816e1d921eca64d208a85aab4f7798455a8e34229ee5a88c935bdee1b78b14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes 0.5.4",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -711,6 +725,15 @@ version = "2.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
|
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]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.6.21"
|
version = "0.6.21"
|
||||||
|
@ -896,15 +919,6 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"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]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
@ -1089,7 +1103,7 @@ version = "0.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3eeb1fe3fc011cde97315f370bc88e4db3c23b08709a04915921e02b1d363b20"
|
checksum = "3eeb1fe3fc011cde97315f370bc88e4db3c23b08709a04915921e02b1d363b20"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes 0.5.4",
|
||||||
"combine",
|
"combine",
|
||||||
"dtoa",
|
"dtoa",
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
|
@ -1188,15 +1202,15 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.105"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff"
|
checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.105"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8"
|
checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1248,15 +1262,6 @@ version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "slug"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
|
|
||||||
dependencies = [
|
|
||||||
"deunicode",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -1281,12 +1286,6 @@ version = "0.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
|
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "structopt"
|
name = "structopt"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
|
@ -1339,20 +1338,13 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19d25bed9d2cf684de6ffdaa9ac35373739eeed0d6bef1de545bbe6cf571ad07"
|
checksum = "19d25bed9d2cf684de6ffdaa9ac35373739eeed0d6bef1de545bbe6cf571ad07"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
|
||||||
"chrono-tz",
|
|
||||||
"globwalk",
|
"globwalk",
|
||||||
"humansize",
|
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"percent-encoding",
|
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"rand",
|
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"slug",
|
|
||||||
"unic-segment",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1364,6 +1356,26 @@ dependencies = [
|
||||||
"unicode-width",
|
"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]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -1390,7 +1402,7 @@ version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713"
|
checksum = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes 0.5.4",
|
||||||
"fnv",
|
"fnv",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"iovec",
|
"iovec",
|
||||||
|
@ -1408,6 +1420,17 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"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]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -1425,7 +1448,7 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930"
|
checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes 0.5.4",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1439,7 +1462,7 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
|
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes 0.5.4",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1502,56 +1525,6 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
|
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]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
@ -1620,12 +1593,6 @@ version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
|
checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vec_map"
|
|
||||||
version = "0.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
|
|
36
Cargo.toml
36
Cargo.toml
|
@ -3,27 +3,33 @@ name = "fedhub"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Michael Zhang <iptq@protonmail.com>"]
|
authors = ["Michael Zhang <iptq@protonmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"async-git",
|
||||||
|
"fedhub-hooks",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.28"
|
anyhow = { version = "1.0.28", default-features = false, features = ["std"] }
|
||||||
comrak = "0.7.0"
|
async-git = { path = "async-git" }
|
||||||
|
comrak = { version = "0.7.0", default-features = false }
|
||||||
futures = { version = "0.3.4", default-features = false }
|
futures = { version = "0.3.4", default-features = false }
|
||||||
git2 = { version = "0.13.0", default-features = false }
|
git2 = { version = "0.13.0", default-features = false }
|
||||||
hyper = "0.13.5"
|
hyper = { version = "0.13.5", default-features = false, features = ["runtime"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = { version = "1.4.0", default-features = false }
|
||||||
parking_lot = "0.10.2"
|
packer = { version = "0.5.3", default-features = false }
|
||||||
redis = "0.15.1"
|
parking_lot = { version = "0.10.2", default-features = false }
|
||||||
serde = "1.0.105"
|
redis = { version = "0.15.1", default-features = false }
|
||||||
serde_derive = "1.0.105"
|
serde = { version = "1.0.105", default-features = false }
|
||||||
serde_json = "1.0.48"
|
serde_derive = { version = "1.0.105", default-features = false }
|
||||||
structopt = "0.3.13"
|
serde_json = { version = "1.0.48", default-features = false }
|
||||||
tera = "1.2.0"
|
structopt = { version = "0.3.13", default-features = false }
|
||||||
tokio = { version = "0.2.18", features = ["full"] }
|
tera = { version = "1.2.0", default-features = false }
|
||||||
toml = "0.5.6"
|
tokio = { version = "0.2.18", default-features = false, features = ["full"] }
|
||||||
walkdir = "2.3.1"
|
toml = { version = "0.5.6", default-features = false }
|
||||||
packer = "0.5.3"
|
walkdir = { version = "2.3.1", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
rsass = "0.13.0"
|
rsass = "0.13.0"
|
||||||
|
|
11
README.md
Normal file
11
README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
fedhub
|
||||||
|
======
|
||||||
|
|
||||||
|
Federated git forge.
|
||||||
|
|
||||||
|
Contact
|
||||||
|
-------
|
||||||
|
|
||||||
|
Author: Michael Zhang
|
||||||
|
|
||||||
|
License: MIT/Apache Dual License
|
1
async-git/.ignore
Normal file
1
async-git/.ignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/tests
|
20
async-git/Cargo.toml
Normal file
20
async-git/Cargo.toml
Normal 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
35
async-git/bin/git.rs
Normal 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
82
async-git/src/config.rs
Normal 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
15
async-git/src/lib.rs
Normal 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};
|
21
async-git/src/plumbing/commit.rs
Normal file
21
async-git/src/plumbing/commit.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
16
async-git/src/plumbing/hash_object.rs
Normal file
16
async-git/src/plumbing/hash_object.rs
Normal 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())
|
||||||
|
}
|
9
async-git/src/plumbing/mod.rs
Normal file
9
async-git/src/plumbing/mod.rs
Normal 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;
|
91
async-git/src/plumbing/object.rs
Normal file
91
async-git/src/plumbing/object.rs
Normal 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,
|
||||||
|
}
|
7
async-git/src/plumbing/repo.rs
Normal file
7
async-git/src/plumbing/repo.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub struct Repository {}
|
||||||
|
|
||||||
|
impl Repository {
|
||||||
|
pub async fn open(path: impl AsRef<Path>) {}
|
||||||
|
}
|
65
async-git/src/porcelain/init.rs
Normal file
65
async-git/src/porcelain/init.rs
Normal 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(())
|
||||||
|
}
|
8
async-git/src/porcelain/log.rs
Normal file
8
async-git/src/porcelain/log.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use crate::plumbing::Oid;
|
||||||
|
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
pub struct LogOptions {
|
||||||
|
commit: Oid,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn log_cmd(options: LogOptions) {}
|
7
async-git/src/porcelain/mod.rs
Normal file
7
async-git/src/porcelain/mod.rs
Normal 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};
|
15
async-git/src/porcelain/show.rs
Normal file
15
async-git/src/porcelain/show.rs
Normal 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
37
async-git/src/util.rs
Normal 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
8
async-zlib/Cargo.toml
Normal 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"] }
|
1
async-zlib/src/decoder.rs
Normal file
1
async-zlib/src/decoder.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub struct ZlibDecoder<R>(R);
|
1
async-zlib/src/encoder.rs
Normal file
1
async-zlib/src/encoder.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub struct ZlibEncoder<W>(W);
|
5
async-zlib/src/lib.rs
Normal file
5
async-zlib/src/lib.rs
Normal 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
9
fedhub-hooks/Cargo.toml
Normal 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
3
fedhub-hooks/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
42
src/gitutil.rs
Normal file
42
src/gitutil.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
95
src/lib.rs
95
src/lib.rs
|
@ -4,12 +4,14 @@ extern crate serde_derive;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod gitutil;
|
||||||
|
|
||||||
use std::path::{Component as PathComponent, Path, PathBuf};
|
use std::path::{Component as PathComponent, Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
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 hyper::{Body, Request, Response, StatusCode};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use packer::Packer;
|
use packer::Packer;
|
||||||
|
@ -20,9 +22,20 @@ use walkdir::WalkDir;
|
||||||
|
|
||||||
pub use crate::config::Config;
|
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 {
|
macro_rules! template {
|
||||||
($file:expr) => {
|
($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)]
|
#[derive(Clone)]
|
||||||
pub struct Fedhub {
|
pub struct Fedhub {
|
||||||
config: Config,
|
config: Config,
|
||||||
|
@ -72,7 +81,7 @@ impl Fedhub {
|
||||||
.site_name
|
.site_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.as_ref())
|
.map(|s| s.as_ref())
|
||||||
.unwrap_or_else(|| "Fedhub");
|
.unwrap_or_else(|| "fedhub");
|
||||||
ctx.insert(
|
ctx.insert(
|
||||||
"fedhub",
|
"fedhub",
|
||||||
&json!({
|
&json!({
|
||||||
|
@ -159,7 +168,14 @@ impl Fedhub {
|
||||||
ctx.insert("tree_name", &tree_name);
|
ctx.insert("tree_name", &tree_name);
|
||||||
ctx.insert("filepath", &filepath);
|
ctx.insert("filepath", &filepath);
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
|
let mut readme = None;
|
||||||
for entry in tree.iter() {
|
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 {
|
let url = match &filepath {
|
||||||
Some(filepath) => format!(
|
Some(filepath) => format!(
|
||||||
"/{}/+/{}/{}/{}",
|
"/{}/+/{}/{}/{}",
|
||||||
|
@ -181,6 +197,15 @@ impl Fedhub {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
ctx.insert("entries", &entries);
|
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()));
|
return Ok(Response::new(TERA.render("repo_tree.html", &ctx)?.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,16 +222,18 @@ impl Fedhub {
|
||||||
let mut ctx = self.context();
|
let mut ctx = self.context();
|
||||||
ctx.insert("repo_name", &path);
|
ctx.insert("repo_name", &path);
|
||||||
ctx.insert("tree_name", &tree_name);
|
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(
|
ctx.insert(
|
||||||
"blob",
|
"blob",
|
||||||
&json!({
|
&json!({
|
||||||
"name": filepath,
|
"name": filepath,
|
||||||
"binary": blob.is_binary(),
|
"binary": blob.is_binary(),
|
||||||
"contents": if blob.is_binary() {
|
"contents": contents,
|
||||||
json!(null)
|
|
||||||
} else {
|
|
||||||
json!(String::from_utf8(blob.content().to_vec())?)
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return Ok(Response::new(TERA.render("repo_blob.html", &ctx)?.into()));
|
return Ok(Response::new(TERA.render("repo_blob.html", &ctx)?.into()));
|
||||||
|
@ -223,8 +250,6 @@ impl Fedhub {
|
||||||
|
|
||||||
if path.starts_with("/static") {
|
if path.starts_with("/static") {
|
||||||
let rest = path.trim_start_matches("/static/");
|
let rest = path.trim_start_matches("/static/");
|
||||||
println!("REST: {:?}", rest);
|
|
||||||
println!("LIST: {:?}", Statics::list().collect::<Vec<_>>());
|
|
||||||
return match Statics::get(rest) {
|
return match Statics::get(rest) {
|
||||||
Some(data) => Ok(Response::builder().body(data.into())?),
|
Some(data) => Ok(Response::builder().body(data.into())?),
|
||||||
None => Ok(Response::builder()
|
None => Ok(Response::builder()
|
||||||
|
@ -267,7 +292,7 @@ impl Fedhub {
|
||||||
tree_name
|
tree_name
|
||||||
};
|
};
|
||||||
let (tree_id, shortname) = {
|
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)
|
(tree.id(), shortname)
|
||||||
};
|
};
|
||||||
let filepath = iter.collect::<PathBuf>();
|
let filepath = iter.collect::<PathBuf>();
|
||||||
|
@ -276,7 +301,7 @@ impl Fedhub {
|
||||||
return self.render_repo_tree(path, &repo, tree_id, shortname, None);
|
return self.render_repo_tree(path, &repo, tree_id, shortname, None);
|
||||||
} else {
|
} else {
|
||||||
let (object_id, object_type) =
|
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);
|
println!("{:?} {:?}", object_id, object_type);
|
||||||
match object_type {
|
match object_type {
|
||||||
ObjectType::Tree => {
|
ObjectType::Tree => {
|
||||||
|
@ -304,41 +329,3 @@ impl Fedhub {
|
||||||
.body("not found".into())?)
|
.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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<h3>branches:</h3>
|
<h3>branches:</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{% for branch in branches %}
|
{% for branch in branches %}
|
||||||
<a href="{{ branch.url }}">{{ branch.name }}</a>
|
<a href="{{ branch.url | safe }}">{{ branch.name }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -6,7 +6,13 @@
|
||||||
<h3>entries:</h3>
|
<h3>entries:</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{% for entry in entries %}
|
{% for entry in entries %}
|
||||||
<li><a href="{{ entry.url }}">{{ entry.name }}</a></li>
|
<li><a href="{{ entry.url | safe }}">{{ entry.name }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{% if readme %}
|
||||||
|
<div>
|
||||||
|
{{ readme.rendered | safe }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue