Compare commits

...

121 Commits

Author SHA1 Message Date
e574f4bdd0 Run tests also on windows
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-22 13:41:50 +01:00
5c8e3fa35d Add badge test
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-22 13:34:26 +01:00
4c0df1fa19 Add config so tests will work 2021-03-22 13:34:18 +01:00
f6b46a1e90 Add config so tests will work
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-22 13:33:56 +01:00
5eb16ac38e Remove unused code
All checks were successful
continuous-integration/drone/push Build is passing
2021-03-22 12:52:24 +01:00
3486d44bc5 Restructure for testability and implement first tests 2021-03-22 12:46:10 +01:00
1311e724ce Bump version (v0.17.1)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-03-22 08:38:45 +01:00
5da9e74992 Merge pull request #248 from strdr4605/patch-1
fix: remove redundant param in md url
2021-03-22 08:37:53 +01:00
8dadc0ddea fix: remove redundant param in md url 2021-03-22 00:16:31 +02:00
5284249acf Bump version (v0.17.0)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2021-02-26 16:38:26 +01:00
d28f56dac0 Updgrade serde_derive and futures
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-26 16:35:14 +01:00
ab02a45bf6 Update dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-26 16:28:04 +01:00
837c1c160b Merge pull request #234 from vbrandl/dependabot/cargo/vergen-4.1.0
Bump vergen from 3.2.0 to 4.1.0
2021-02-26 16:17:56 +01:00
3d26823425 Fix breaking changes in vergen
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-26 16:12:58 +01:00
d9553871fa Deactivate unused features of vergen 2021-02-26 16:12:47 +01:00
d2bc9c2149 Bump vergen from 3.2.0 to 4.1.0
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.2.0 to 4.1.0.
- [Release notes](https://github.com/rustyhorde/vergen/releases)
- [Commits](https://github.com/rustyhorde/vergen/compare/v3.2.0...4.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-26 15:06:33 +00:00
b0b97653a9 Rollback reqwest to 0.10.10
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-26 16:04:58 +01:00
0b1bc60790 Merge pull request #238 from vbrandl/revert-236-dependabot/cargo/actix-rt-2.1.0
Revert "Bump actix-rt from 1.1.1 to 2.1.0"
2021-02-26 15:51:46 +01:00
e93588a6d9 Revert "Bump actix-rt from 1.1.1 to 2.1.0" 2021-02-26 15:51:35 +01:00
cd5046a276 Merge pull request #236 from vbrandl/dependabot/cargo/actix-rt-2.1.0
Bump actix-rt from 1.1.1 to 2.1.0
2021-02-26 15:41:54 +01:00
919348d49d Merge pull request #229 from vbrandl/dependabot/cargo/tracing-futures-0.2.5
Bump tracing-futures from 0.2.4 to 0.2.5
2021-02-26 15:31:26 +01:00
8378b0b51b Bump tracing-futures from 0.2.4 to 0.2.5
Bumps [tracing-futures](https://github.com/tokio-rs/tracing) from 0.2.4 to 0.2.5.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-futures-0.2.4...tracing-futures-0.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-26 14:31:17 +00:00
0394ae52e9 Merge pull request #235 from vbrandl/dependabot/cargo/tracing-0.1.25
Bump tracing from 0.1.23 to 0.1.25
2021-02-26 15:29:55 +01:00
fb34f6bf2f Bump tracing from 0.1.23 to 0.1.25
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.23 to 0.1.25.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.23...tracing-0.1.25)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-26 14:29:17 +00:00
36622ad322 Merge pull request #232 from vbrandl/dependabot/cargo/tracing-subscriber-0.2.16
Bump tracing-subscriber from 0.2.15 to 0.2.16
2021-02-26 15:28:11 +01:00
89bb27e137 Merge pull request #237 from vbrandl/dependabot/cargo/serde_json-1.0.63
Bump serde_json from 1.0.62 to 1.0.63
2021-02-26 15:27:53 +01:00
7d4385764c Bump serde_json from 1.0.62 to 1.0.63
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.62 to 1.0.63.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.62...v1.0.63)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-26 04:19:16 +00:00
5df97f0e64 Bump actix-rt from 1.1.1 to 2.1.0
Bumps [actix-rt](https://github.com/actix/actix-net) from 1.1.1 to 2.1.0.
- [Release notes](https://github.com/actix/actix-net/releases)
- [Commits](https://github.com/actix/actix-net/compare/rt-1.1.1...rt-v2.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-26 04:18:49 +00:00
a3d98a326e Bump tracing-subscriber from 0.2.15 to 0.2.16
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.2.15 to 0.2.16.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.2.15...tracing-subscriber-0.2.16)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-22 04:14:56 +00:00
76bf5f2f98 Merge pull request #221 from vbrandl/dependabot/cargo/serde-1.0.123
Bump serde from 1.0.118 to 1.0.123
2021-02-11 11:17:34 +01:00
e827d2ef39 Bump serde from 1.0.118 to 1.0.123
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.118 to 1.0.123.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.118...v1.0.123)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-11 10:12:45 +00:00
65576140ce Merge pull request #212 from vbrandl/dependabot/cargo/reqwest-0.11.0
Bump reqwest from 0.10.10 to 0.11.0
2021-02-11 11:11:14 +01:00
cea54cf2f3 Bump reqwest from 0.10.10 to 0.11.0
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.10.10 to 0.11.0.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.10.10...v0.11.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-11 10:10:09 +00:00
8c28c68c17 Merge pull request #215 from vbrandl/dependabot/cargo/bytes-1.0.1
Bump bytes from 1.0.0 to 1.0.1
2021-02-11 11:09:27 +01:00
b86a080ead Merge pull request #223 from vbrandl/dependabot/cargo/tracing-0.1.23
Bump tracing from 0.1.22 to 0.1.23
2021-02-11 11:08:42 +01:00
1b0c959a8d Bump bytes from 1.0.0 to 1.0.1
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.0.0...v1.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-10 17:07:29 +00:00
806baf7460 Bump tracing from 0.1.22 to 0.1.23
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.22 to 0.1.23.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.22...tracing-0.1.23)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-10 17:06:26 +00:00
080f7379ea Merge pull request #216 from vbrandl/dependabot/cargo/tempfile-3.2.0
Bump tempfile from 3.1.0 to 3.2.0
2021-02-10 18:06:14 +01:00
7409de5dba Merge pull request #219 from vbrandl/dependabot/cargo/git2-0.13.17
Bump git2 from 0.13.14 to 0.13.17
2021-02-10 18:05:52 +01:00
07f4ef518a Merge pull request #225 from vbrandl/dependabot/cargo/serde_json-1.0.62
Bump serde_json from 1.0.61 to 1.0.62
2021-02-10 18:04:58 +01:00
820ebdd899 Bump tempfile from 3.1.0 to 3.2.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/Stebalien/tempfile/releases)
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/NEWS)
- [Commits](https://github.com/Stebalien/tempfile/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-10 17:04:57 +00:00
527d793914 Merge pull request #226 from vbrandl/dependabot/cargo/vergen-3.2.0
Bump vergen from 3.1.0 to 3.2.0
2021-02-10 18:03:26 +01:00
7df6c3fc75 Bump vergen from 3.1.0 to 3.2.0
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/rustyhorde/vergen/releases)
- [Commits](https://github.com/rustyhorde/vergen/compare/v3.1.0...v3.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-10 04:16:04 +00:00
17e1af1dd7 Bump serde_json from 1.0.61 to 1.0.62
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.61 to 1.0.62.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.61...v1.0.62)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-08 04:17:19 +00:00
baade8fbc2 Bump git2 from 0.13.14 to 0.13.17
Bumps [git2](https://github.com/rust-lang/git2-rs) from 0.13.14 to 0.13.17.
- [Release notes](https://github.com/rust-lang/git2-rs/releases)
- [Commits](https://github.com/rust-lang/git2-rs/compare/0.13.14...0.13.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-01-25 04:16:33 +00:00
cfc07664fc Merge pull request #211 from vbrandl/dependabot/cargo/serde_json-1.0.61
Bump serde_json from 1.0.60 to 1.0.61
2021-01-06 18:11:44 +01:00
52938c9106 Bump serde_json from 1.0.60 to 1.0.61
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.60 to 1.0.61.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.60...v1.0.61)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-29 04:18:23 +00:00
b64f573a38 Bump version (v0.16.0)
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 21:44:46 +01:00
ebbd1ea8cb Merge pull request #204 from vbrandl/dependabot/cargo/reqwest-0.10.10
Bump reqwest from 0.10.9 to 0.10.10
2020-12-23 21:33:54 +01:00
93a0147a78 Merge pull request #208 from vbrandl/dependabot/cargo/bytes-1.0.0
Bump bytes from 0.6.0 to 1.0.0
2020-12-23 21:33:27 +01:00
2040e718d7 Bump reqwest from 0.10.9 to 0.10.10
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.10.9 to 0.10.10.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.10.9...v0.10.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-23 20:33:00 +00:00
5679f67de0 Bump bytes from 0.6.0 to 1.0.0
Bumps [bytes](https://github.com/tokio-rs/bytes) from 0.6.0 to 1.0.0.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v0.6.0...v1.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-23 20:32:36 +00:00
8babdb978e Merge pull request #207 from vbrandl/dependabot/cargo/git2-0.13.14
Bump git2 from 0.13.12 to 0.13.14
2020-12-23 21:32:20 +01:00
b3388912c2 Merge pull request #203 from vbrandl/dependabot/cargo/serde-1.0.118
Bump serde from 1.0.117 to 1.0.118
2020-12-23 21:31:41 +01:00
bce511ce0b Merge pull request #202 from vbrandl/dependabot/cargo/serde_json-1.0.60
Bump serde_json from 1.0.59 to 1.0.60
2020-12-23 21:31:25 +01:00
50e36769d1 Merge pull request #201 from vbrandl/dependabot/cargo/actix-web-3.3.2
Bump actix-web from 3.3.1 to 3.3.2
2020-12-23 21:31:09 +01:00
9477338331 Bump git2 from 0.13.12 to 0.13.14
Bumps [git2](https://github.com/rust-lang/git2-rs) from 0.13.12 to 0.13.14.
- [Release notes](https://github.com/rust-lang/git2-rs/releases)
- [Commits](https://github.com/rust-lang/git2-rs/compare/0.13.12...0.13.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-22 04:18:08 +00:00
c1d0930354 Bump serde from 1.0.117 to 1.0.118
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.117 to 1.0.118.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.117...v1.0.118)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-07 04:17:13 +00:00
688067e5c5 Bump serde_json from 1.0.59 to 1.0.60
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.59 to 1.0.60.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.59...v1.0.60)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-03 04:16:41 +00:00
529f36dc40 Bump actix-web from 3.3.1 to 3.3.2
Bumps [actix-web](https://github.com/actix/actix-web) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/actix/actix-web/releases)
- [Changelog](https://github.com/actix/actix-web/blob/master/CHANGES.md)
- [Commits](https://github.com/actix/actix-web/compare/web-v3.3.1...web-v3.3.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-02 04:17:45 +00:00
d8fdabfca6 Merge pull request #200 from vbrandl/dependabot/cargo/actix-web-3.3.1
Bump actix-web from 3.2.0 to 3.3.1
2020-11-30 23:37:56 +01:00
8060b80ae6 Bump actix-web from 3.2.0 to 3.3.1
Bumps [actix-web](https://github.com/actix/actix-web) from 3.2.0 to 3.3.1.
- [Release notes](https://github.com/actix/actix-web/releases)
- [Changelog](https://github.com/actix/actix-web/blob/master/CHANGES.md)
- [Commits](https://github.com/actix/actix-web/compare/web-v3.2.0...web-v3.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-30 04:17:12 +00:00
15965c5b5f Bump version (v0.15.0)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-11-24 19:34:20 +01:00
1eade03b63 Load env in docker-compose setup
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-24 19:33:12 +01:00
f41fbc8baf Fix test
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-24 19:24:35 +01:00
14cd21dc1c Make paths consistent
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-24 19:13:08 +01:00
1bdee4ee36 Document new config mechanism
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-24 19:07:21 +01:00
9e33742d81 Use new config 2020-11-24 19:06:49 +01:00
8e78d13443 Make full base URL configurable 2020-11-24 19:06:42 +01:00
391fa39470 Use config and dotenv over structopt 2020-11-24 19:06:18 +01:00
e6e90214e2 Add example configurations 2020-11-24 19:05:56 +01:00
6f734b103a Ignore actual configs 2020-11-24 19:05:46 +01:00
413bb824e1 Merge pull request #198 from vbrandl/dependabot/cargo/tracing-0.1.22
All checks were successful
continuous-integration/drone/push Build is passing
Bump tracing from 0.1.21 to 0.1.22
2020-11-24 18:22:29 +01:00
58ad13dbad Bump tracing from 0.1.21 to 0.1.22
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.21 to 0.1.22.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.21...tracing-0.1.22)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-24 04:17:21 +00:00
9f8b781f7b Bump version (v0.14.5)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-11-23 14:47:22 +01:00
175b7c828b Cleanup cache keys
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-23 14:36:43 +01:00
002119324f Remove unused dependencies 2020-11-23 14:35:12 +01:00
df78b6f1e5 Revert "Dummy modification for audit"
All checks were successful
continuous-integration/drone/push Build is passing
This reverts commit e1bff0d280.
2020-11-23 14:21:46 +01:00
e1bff0d280 Dummy modification for audit
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-23 14:19:39 +01:00
4c9454aa9e Use different cache keys per pipeline 2020-11-23 14:19:11 +01:00
e9e57495a6 Disable nix-build
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-23 14:13:02 +01:00
b6505f6a37 Disable nix-build
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-23 14:12:32 +01:00
a2463bf657 Release binaries on github release page
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-23 14:11:36 +01:00
53cb73cd9b Use checkout@v2 2020-11-23 14:11:18 +01:00
423a3aa0b0 Split formatting and clippy checks into separate pipelines 2020-11-23 14:11:01 +01:00
7a2c6b6f06 Merge pull request #197 from vbrandl/dependabot/cargo/reqwest-0.10.9
Bump reqwest from 0.10.8 to 0.10.9
2020-11-23 12:13:19 +01:00
ffb306a7a8 Merge pull request #196 from vbrandl/dependabot/cargo/ructe-0.13.0
Bump ructe from 0.12.0 to 0.13.0
2020-11-23 12:13:08 +01:00
cef2ae2299 Merge pull request #195 from vbrandl/dependabot/cargo/futures-0.3.8
Bump futures from 0.3.7 to 0.3.8
2020-11-23 12:07:59 +01:00
b91de72d19 Remove travis
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-23 12:07:26 +01:00
fd08489587 Bump futures from 0.3.7 to 0.3.8
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.7...0.3.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-23 10:57:43 +00:00
ce0d6041ea Bump reqwest from 0.10.8 to 0.10.9
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.10.8 to 0.10.9.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.10.8...v0.10.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-23 10:57:41 +00:00
3af60a82ce Merge pull request #194 from vbrandl/dependabot/cargo/tracing-subscriber-0.2.15
Bump tracing-subscriber from 0.2.14 to 0.2.15
2020-11-23 11:56:47 +01:00
bcdf7db549 Merge pull request #189 from vbrandl/dependabot/cargo/actix-web-3.2.0
Bump actix-web from 3.1.0 to 3.2.0
2020-11-23 11:56:24 +01:00
9b2f1f4ebb Bump ructe from 0.12.0 to 0.13.0
Bumps [ructe](https://github.com/kaj/ructe) from 0.12.0 to 0.13.0.
- [Release notes](https://github.com/kaj/ructe/releases)
- [Changelog](https://github.com/kaj/ructe/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/ructe/compare/v0.12.0...v0.13.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-16 04:19:05 +00:00
84e47237de Bump tracing-subscriber from 0.2.14 to 0.2.15
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.2.14 to 0.2.15.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.2.14...tracing-subscriber-0.2.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-03 04:18:03 +00:00
909f6585b5 Always return a SVG badge
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-01 13:57:56 +01:00
b48d7f1492 Bump actix-web from 3.1.0 to 3.2.0
Bumps [actix-web](https://github.com/actix/actix-web) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/actix/actix-web/releases)
- [Changelog](https://github.com/actix/actix-web/blob/master/CHANGES.md)
- [Commits](https://github.com/actix/actix-web/compare/web-v3.1.0...web-v3.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-30 20:02:12 +00:00
f1e9d1806f Bump version (v0.14.4)
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-10-30 21:01:37 +01:00
8c62d01f3c Merge pull request #191 from vbrandl/feature/tracing
Use `tracing` instead of `slog` for better ergonomics
2020-10-30 21:00:46 +01:00
26a5025a32 Fix state in test
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-30 17:15:09 +01:00
6f931ce46f Use tracing instead of slog for better ergonomics
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-30 16:40:35 +01:00
2d46592c4a Merge pull request #190 from vbrandl/feature/structured-logging
Feature/structured logging
2020-10-30 13:24:45 +01:00
c2d496f2b4 Add logger to test state
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-30 13:20:24 +01:00
19d37806f2 Remove unused import
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-30 13:11:05 +01:00
b4bd9b8830 Refactor to use slog for structured logging
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-30 13:03:06 +01:00
3c8227d0e9 Add slog dependencies 2020-10-30 13:02:53 +01:00
d6409c21ec Bump version (v0.14.3)
All checks were successful
continuous-integration/drone/tag Build is passing
2020-10-28 09:08:30 +01:00
990b5acbda Merge pull request #184 from vbrandl/dependabot/cargo/git2-0.13.12
Bump git2 from 0.13.11 to 0.13.12
2020-10-28 09:07:35 +01:00
f9e14e2ffd Bump git2 from 0.13.11 to 0.13.12
Bumps [git2](https://github.com/rust-lang/git2-rs) from 0.13.11 to 0.13.12.
- [Release notes](https://github.com/rust-lang/git2-rs/releases)
- [Commits](https://github.com/rust-lang/git2-rs/compare/0.13.11...0.13.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-28 08:05:44 +00:00
a73afe6851 Merge pull request #186 from vbrandl/dependabot/cargo/bytes-0.6.0
Bump bytes from 0.5.6 to 0.6.0
2020-10-28 09:04:22 +01:00
20544b27d9 Bump bytes from 0.5.6 to 0.6.0
Bumps [bytes](https://github.com/tokio-rs/bytes) from 0.5.6 to 0.6.0.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v0.5.6...v0.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-27 09:32:39 +00:00
288573b1a4 Merge pull request #178 from vbrandl/dependabot/cargo/actix-web-3.1.0
Bump actix-web from 3.0.2 to 3.1.0
2020-10-27 10:31:01 +01:00
a12755d7be Merge pull request #182 from vbrandl/dependabot/cargo/structopt-0.3.20
Bump structopt from 0.3.17 to 0.3.20
2020-10-27 09:10:24 +01:00
a248531ce2 Bump actix-web from 3.0.2 to 3.1.0
Bumps [actix-web](https://github.com/actix/actix-web) from 3.0.2 to 3.1.0.
- [Release notes](https://github.com/actix/actix-web/releases)
- [Changelog](https://github.com/actix/actix-web/blob/master/CHANGES.md)
- [Commits](https://github.com/actix/actix-web/compare/web-v3.0.2...web-v3.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-27 08:07:18 +00:00
de7919a031 Bump structopt from 0.3.17 to 0.3.20
Bumps [structopt](https://github.com/TeXitoi/structopt) from 0.3.17 to 0.3.20.
- [Release notes](https://github.com/TeXitoi/structopt/releases)
- [Changelog](https://github.com/TeXitoi/structopt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TeXitoi/structopt/compare/v0.3.17...v0.3.20)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-27 08:07:15 +00:00
3913039010 Merge pull request #183 from vbrandl/dependabot/cargo/serde_json-1.0.59
Bump serde_json from 1.0.57 to 1.0.59
2020-10-27 09:05:57 +01:00
078d3cdcf9 Merge pull request #185 from vbrandl/dependabot/cargo/serde-1.0.117
Bump serde from 1.0.116 to 1.0.117
2020-10-27 09:05:49 +01:00
c552a84870 Merge pull request #188 from vbrandl/dependabot/cargo/futures-0.3.7
Bump futures from 0.3.5 to 0.3.7
2020-10-27 09:05:37 +01:00
7c1a14b6ad Bump futures from 0.3.5 to 0.3.7
Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.5 to 0.3.7.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.5...0.3.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-26 04:17:33 +00:00
c69b8207b8 Bump serde from 1.0.116 to 1.0.117
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.116 to 1.0.117.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.116...v1.0.117)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-16 04:22:26 +00:00
a319f400e9 Bump serde_json from 1.0.57 to 1.0.59
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.57 to 1.0.59.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.57...v1.0.59)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-13 04:19:16 +00:00
32 changed files with 1879 additions and 1592 deletions

7
.env.example Normal file
View File

@ -0,0 +1,7 @@
HOC_REPODIR='./repos'
HOC_CACHEDIR='./cache'
HOC_PORT=8080
HOC_HOST='0.0.0.0'
HOC_WORKERS=4
HOC_BASE_URL='http://0.0.0.0:8080'

View File

@ -4,27 +4,24 @@ on:
- cron: '0 1 * * *'
push:
paths:
- 'Cargo.toml'
- 'Cargo.lock'
- '**/Cargo.toml'
- '**/Cargo.lock'
pull_request:
jobs:
security_audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Cache cargo registry
uses: actions/cache@v1
- name: Cache cargo registry, index and build directory
uses: actions/cache@v2
with:
path: /usr/share/rust/.cargo/registry
key: ${{ runner.os }}-cargo-registry
- name: Cache cargo index
uses: actions/cache@v1
with:
path: /usr/share/rust/.cargo/git
key: ${{ runner.os }}-cargo-index
path: |
~/.cargo/registry
~/.cargo/git
./target
key: audit-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/audit-check@v1
with:

View File

@ -1,20 +0,0 @@
name: "Nix Build"
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: cachix/install-nix-action@v8
- name: Cache nix store
uses: actions/cache@v1
with:
path: /nix
key: ${{ runner.os }}-nix-store
- uses: cachix/cachix-action@v5
with:
name: hitsofcode
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
attributes: package

76
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,76 @@
name: Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
publish:
name: Publishing for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
rust: [stable]
include:
- os: macos-latest
artifact_prefix: macos
target: x86_64-apple-darwin
binary_postfix: ""
- os: ubuntu-latest
artifact_prefix: linux
target: x86_64-unknown-linux-gnu
binary_postfix: ""
- os: windows-latest
artifact_prefix: windows
target: x86_64-pc-windows-msvc
binary_postfix: ".exe"
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Cache cargo registry, index and build directory
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
./target
key: release-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Cargo build
uses: actions-rs/cargo@v1
with:
command: build
toolchain: ${{ matrix.rust }}
args: --release --target ${{ matrix.target }}
- name: Packaging final binary
shell: bash
run: |
cd target/${{ matrix.target }}/release
strip hoc${{ matrix.binary_postfix }}
tar czvf hoc-${{ matrix.artifact_prefix }}.tar.gz hoc${{ matrix.binary_postfix }}
if [[ ${{ runner.os }} == 'Windows' ]]; then
certutil -hashfile hoc-${{ matrix.artifact_prefix }}.tar.gz sha256 | grep -E [A-Fa-f0-9]{64} > hoc-${{ matrix.artifact_prefix }}.sha256
else
shasum -a 256 hoc-${{ matrix.artifact_prefix }}.tar.gz > hoc-${{ matrix.artifact_prefix }}.sha256
fi
- name: Releasing assets
uses: softprops/action-gh-release@v1
with:
files: |
target/${{ matrix.target }}/release/hoc-${{ matrix.artifact_prefix }}.tar.gz
target/${{ matrix.target }}/release/hoc-${{ matrix.artifact_prefix }}.sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,41 +1,30 @@
name: Rust
on: [push, pull_request]
jobs:
lint:
name: Linting and Formatting Checks
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: rustfmt
- name: Install rustfmt
run: rustup component add rustfmt
- name: Cache cargo registry
uses: actions/cache@v1
- name: Cache cargo registry, index and build directory
uses: actions/cache@v2
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
path: |
~/.cargo/registry
~/.cargo/git
./target
key: rustfmt-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Check Formatting
uses: actions-rs/cargo@v1
@ -43,8 +32,29 @@ jobs:
command: fmt
args: --all -- --check
- name: Install clippy
run: rustup component add clippy
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: rustfmt
- name: Cache cargo registry, index and build directory
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
./target
key: clippy-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Clippy Linting
uses: actions-rs/cargo@v1
@ -52,41 +62,34 @@ jobs:
command: clippy
args: -- -D warnings
test:
name: Run Tests
name: Test Suite
runs-on: ${{ matrix.os }}
strategy:
matrix:
# add windows-latest when it is clear why tests are failing
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout sources
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Cache cargo registry
uses: actions/cache@v1
- name: Cache cargo registry, index and build directory
uses: actions/cache@v2
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v1
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v1
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
path: |
~/.cargo/registry
~/.cargo/git
./target
key: test-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Run Tests
uses: actions-rs/cargo@v1

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ repos
cache
hoc.log
result
.env

View File

@ -1,17 +0,0 @@
language: rust
rust:
- stable
- beta
- nightly
cache:
- cargo
matrix:
allow_failures:
- rust: nightly
notifications:
email:
on_failure: always

1742
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,45 @@
[package]
name = "hoc"
version = "0.14.2"
version = "0.17.1"
authors = ["Valentin Brandl <vbrandl@riseup.net>"]
edition = "2018"
build = "build.rs"
[lib]
path = "src/lib.rs"
[[bin]]
path = "src/main.rs"
name = "hoc"
[dependencies]
actix-web = "3.0.2"
actix-rt = "1.1.1"
actix-web = "3.3.2"
badge = "0.3.0"
bytes = "0.5.6"
futures = "0.3.5"
git2 = "0.13.11"
bytes = "1.0.1"
config = { version = "0.10.1", features = ["toml"] }
dotenv = "0.15.0"
futures = "0.3.13"
git2 = "0.13.17"
lazy_static = "1.4.0"
log = "0.4.11"
log4rs = "0.13.0"
number_prefix = "0.4.0"
openssl-probe = "0.1.2"
reqwest = "0.10.8"
serde = "1.0.116"
serde_derive = "1.0.103"
serde_json = "1.0.57"
structopt = "0.3.17"
actix-rt = "1.1.1"
reqwest = "0.10.10"
serde = "1.0.123"
serde_derive = "1.0.123"
serde_json = "1.0.63"
tracing = "0.1.25"
tracing-actix-web = "0.2.1"
tracing-bunyan-formatter = "0.1.7"
tracing-futures = "0.2.5"
tracing-log = "0.1.2"
tracing-subscriber = { version = "0.2.17", features = ["registry", "env-filter"] }
[build-dependencies]
ructe = "0.12.0"
vergen = "3.1.0"
ructe = "0.13.0"
vergen = { version = "4.1.0", default-features = false, features = ["git"] }
[dev-dependencies]
tempfile = "3.1.0"
ructe = "0.13.0"
tempfile = "3.2.0"
tokio = "0.2.25"

View File

@ -1,9 +1,8 @@
# Hits-of-Code
[![Hits-of-Code](https://hitsofcode.com/github/vbrandl/hoc)](https://hitsofcode.com/view/github/vbrandl/hoc)
[![Hits-of-Code](https://hitsofcode.com/github/vbrandl/hoc)](https://hitsofcode.com/github/vbrandl/hoc/view)
[![Drone build](https://drone.vbrandl.net/api/badges/vbrandl/hoc/status.svg)](https://drone.vbrandl.net/vbrandl/hoc)
[![Gitlab build](https://gitlab.com/vbrandl/hoc/badges/master/pipeline.svg)](https://gitlab.com/vbrandl/hoc/pipelines)
[![Travis build](https://travis-ci.org/vbrandl/hoc.svg?branch=master)](https://travis-ci.org/vbrandl/hoc)
[![dependency status](https://deps.rs/repo/github/vbrandl/hoc/status.svg)](https://deps.rs/repo/github/vbrandl/hoc)
Small webservice, that returns a badge of the Hits-of-Code of a git repository, as described by [Yegor
@ -27,7 +26,7 @@ where `<service>` is one of `gitub`, `gitlab` or `bitbucket`. The HoC data can a
https://<host>/<service>/<user>/<repo>/json
```
There is also an overview page available via `https://<host>/view/<service>/<user>/<repo>`
There is also an overview page available via `https://<host>/<service>/<user>/<repo>/view`
To delete a repository and the cache from the server, send a `POST` request to
`https://<host>/<service>/<user>/<repo>/delete`. On the overview page, there is a button to perform this operation. It
@ -49,21 +48,29 @@ $ docker build .
inside the repository.
I'm currently working on migrating to [nix](https://nixos.org/nix). To get a
development shell, run `nix-shell`, to build the package run `nix-build --attr
package` and to build the Docker image, run `nix-build --attr dockerImage`.
I'm currently working on migrating to [nix](https://nixos.org/nix). To get a development shell, run `nix-shell`, to
build the package run `nix-build --attr package` and to build the Docker image, run `nix-build --attr dockerImage`.
## Running
Run either the binary produced by cargo, the Docker container you just built (using docker-compose) or pull the image
from [Docker Hub](https://hub.docker.com/r/vbrandl/hits-of-code)
Rename [`hoc.toml.example`](./hoc.toml.example) to `hoc.toml` or [`.env.example`](./.env.example) to `.env` and set the
correct value for `base_url`/`HOC_BASE_URL`. If you don't want to use a configuration or dotenv file, you can pass all
parameters directly via environment variables. For variable names see [`.env.example`](./.env.example).
To start a local instance of the service just run:
```
$ docker run -it --rm vbrandl/hits-of-code --help
$ HOC_BASE_URL='http://0.0.0.0:8080' ./hoc
```
When running the binary directly, you need a git binary in your `PATH`.
You can also use the Docker image:
```
$ docker run -p 8080:8080 --env HOC_BASE_URL='http://0.0.0.0:8080' -it --rm vbrandl/hits-of-code
```
When running the binary directly, you need a `git` binary in your `PATH`.
## License

View File

@ -2,11 +2,11 @@ extern crate ructe;
extern crate vergen;
use ructe::Ructe;
use vergen::{generate_cargo_keys, ConstantsFlags};
use vergen::{gen, ConstantsFlags};
fn main() {
let flags = ConstantsFlags::SHA_SHORT;
generate_cargo_keys(flags).expect("Unable to generate the cargo keys!");
gen(flags).expect("Unable to generate the cargo keys!");
Ructe::from_env()
.expect("ructe")
.compile_templates("templates")

View File

@ -1,4 +1,4 @@
version: "2"
version: "3"
services:
hoc:
@ -9,3 +9,5 @@ services:
# ports:
# - "127.0.0.1:8080:8080"
restart: always
env_file:
- ./.env

15
hoc.toml Normal file
View File

@ -0,0 +1,15 @@
# every parameter can also be set (or overwritten) by passing an environment
# variable namend `HOC_<PARAMETERNAME>`, e.g.
# `HOC_BASE_URL='https://hitsofcode.com' ./hoc`
# these config parameters have default values and must not explicitly be set
repodir = "./repos"
cachedir = "./cache"
port = 8080
host = "0.0.0.0"
workers = 4
# these parameters don't have default values and must be set
# this should be the public base URL of the service, e.g. `https://hitsofcode.com`
base_url = "http://0.0.0.0:8080"

15
hoc.toml.example Normal file
View File

@ -0,0 +1,15 @@
# every parameter can also be set (or overwritten) by passing an environment
# variable namend `HOC_<PARAMETERNAME>`, e.g.
# `HOC_BASE_URL='https://hitsofcode.com' ./hoc`
# these config parameters have default values and must not explicitly be set
repodir = "./repos"
cachedir = "./cache"
port = 8080
host = "0.0.0.0"
workers = 4
# these parameters don't have default values and must be set
# this should be the public base URL of the service, e.g. `https://hitsofcode.com`
base_url = "http://0.0.0.0:8080"

View File

@ -8,6 +8,7 @@ use std::{
};
/// Enum to indicate the state of the cache
#[derive(Debug)]
pub(crate) enum CacheState<'a> {
/// Current head and cached head are the same
Current {
@ -26,11 +27,13 @@ pub(crate) enum CacheState<'a> {
}
impl<'a> CacheState<'a> {
#[instrument]
pub(crate) fn read_from_file(
path: impl AsRef<Path>,
path: impl AsRef<Path> + std::fmt::Debug,
branch: &str,
head: &str,
) -> Result<CacheState<'a>> {
trace!("Reading cache");
if path.as_ref().exists() {
let cache: Cache = serde_json::from_reader(BufReader::new(File::open(path)?))?;
Ok(cache
@ -38,6 +41,7 @@ impl<'a> CacheState<'a> {
.get(branch)
.map(|c| {
if c.head == head {
trace!("Cache is up to date");
CacheState::Current {
count: c.count,
commits: c.commits,
@ -45,6 +49,7 @@ impl<'a> CacheState<'a> {
cache: cache.clone(),
}
} else {
trace!("Cache is out of date");
CacheState::Old {
head: c.head.to_string(),
// TODO: get rid of clone
@ -59,6 +64,7 @@ impl<'a> CacheState<'a> {
}
}
#[instrument]
pub(crate) fn calculate_new_cache(
self,
count: u64,
@ -66,6 +72,7 @@ impl<'a> CacheState<'a> {
head: Cow<'a, str>,
branch: &'a str,
) -> Cache<'a> {
trace!("Calculating new cache");
match self {
CacheState::Old { mut cache, .. } => {
if let Some(mut cache) = cache.entries.get_mut(branch) {
@ -77,6 +84,7 @@ impl<'a> CacheState<'a> {
}
CacheState::Current { cache, .. } => cache,
CacheState::NoneForBranch(mut cache) => {
trace!("Creating new cache for branch");
cache.entries.insert(
branch.into(),
CacheEntry {
@ -88,6 +96,7 @@ impl<'a> CacheState<'a> {
cache
}
CacheState::No => {
trace!("Creating new cache file");
let mut entries = HashMap::with_capacity(1);
entries.insert(
branch.into(),
@ -103,12 +112,12 @@ impl<'a> CacheState<'a> {
}
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub(crate) struct Cache<'a> {
pub entries: HashMap<Cow<'a, str>, CacheEntry<'a>>,
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub(crate) struct CacheEntry<'a> {
/// HEAD commit ref
pub head: Cow<'a, str>,
@ -119,7 +128,9 @@ pub(crate) struct CacheEntry<'a> {
}
impl<'a> Cache<'a> {
pub(crate) fn write_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
#[instrument]
pub(crate) fn write_to_file(&self, path: impl AsRef<Path> + std::fmt::Debug) -> Result<()> {
trace!("Persisting cache to disk");
create_dir_all(path.as_ref().parent().ok_or(Error::Internal)?)?;
serde_json::to_writer(
OpenOptions::new()

View File

@ -1,70 +1,38 @@
use crate::{error::Result, statics::OPT};
use log::LevelFilter;
use log4rs::{
append::{console::ConsoleAppender, file::FileAppender},
config::{Appender, Config, Root},
encode::pattern::PatternEncoder,
};
use config::{Config, ConfigError, Environment, File};
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
pub(crate) struct Opt {
#[structopt(
short = "o",
long = "outdir",
parse(from_os_str),
default_value = "./repos"
)]
#[derive(Debug, Deserialize)]
pub struct Settings {
/// Path to store cloned repositories
pub(crate) outdir: PathBuf,
#[structopt(
short = "c",
long = "cachedir",
parse(from_os_str),
default_value = "./cache"
)]
pub repodir: PathBuf,
/// Path to store cache
pub(crate) cachedir: PathBuf,
#[structopt(short = "p", long = "port", default_value = "8080")]
pub cachedir: PathBuf,
/// Port to listen on
pub(crate) port: u16,
#[structopt(short = "h", long = "host", default_value = "0.0.0.0")]
pub port: u16,
/// Interface to listen on
pub(crate) host: String,
#[structopt(short = "d", long = "domain", default_value = "hitsofcode.com")]
/// Interface to listen on
pub(crate) domain: String,
#[structopt(short = "w", long = "workers", default_value = "4")]
pub host: String,
/// Base URL
pub base_url: String,
/// Number of worker threads
pub(crate) workers: usize,
#[structopt(
short = "l",
long = "logfile",
parse(from_os_str),
default_value = "./hoc.log"
)]
/// The logfile
pub(crate) logfile: PathBuf,
pub workers: usize,
}
pub(crate) async fn init() -> Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info,hoc=info");
openssl_probe::init_ssl_cert_env_vars();
let stdout = ConsoleAppender::builder().build();
let file = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
.build(&OPT.logfile)
.unwrap();
let config = Config::builder()
.appender(Appender::builder().build("stdout", Box::new(stdout)))
.appender(Appender::builder().build("file", Box::new(file)))
.build(
Root::builder()
.appender("stdout")
.appender("file")
.build(LevelFilter::Info),
)?;
log4rs::init_config(config)?;
Ok(())
impl Settings {
#[deprecated]
pub fn new() -> Result<Self, ConfigError> {
Self::load()
}
pub fn load() -> Result<Self, ConfigError> {
let mut config = Config::new();
config
.merge(File::with_name("hoc.toml").required(false))?
.merge(Environment::with_prefix("hoc"))?
.set_default("repodir", "./repos")?
.set_default("cachedir", "./cache")?
.set_default("workers", 4)?
.set_default("port", 8080)?
.set_default("host", "0.0.0.0")?;
config.try_into()
}
}

View File

@ -1,10 +1,12 @@
use crate::error::Result;
use std::{fs::read_dir, path::Path, result::Result as StdResult};
#[instrument]
pub(crate) fn count_repositories<P>(repo_path: P) -> Result<usize>
where
P: AsRef<Path>,
P: AsRef<Path> + std::fmt::Debug,
{
trace!("Counting repositories");
std::fs::create_dir_all(&repo_path)?;
Ok(read_dir(repo_path)?
.filter_map(StdResult::ok)

View File

@ -1,9 +1,6 @@
use crate::{
statics::{REPO_COUNT, VERSION_INFO},
templates,
};
use actix_web::{HttpResponse, ResponseError};
use std::{fmt, sync::atomic::Ordering};
use crate::{statics::VERSION_INFO, templates};
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
use std::fmt;
pub(crate) type Result<T> = std::result::Result<T, Error>;
@ -14,8 +11,6 @@ pub(crate) enum Error {
Git(git2::Error),
Internal,
Io(std::io::Error),
Log(log::SetLoggerError),
LogBuilder(log4rs::config::Errors),
Parse(std::num::ParseIntError),
Serial(serde_json::Error),
BranchNotFound,
@ -29,8 +24,6 @@ impl fmt::Display for Error {
Error::Git(e) => write!(fmt, "Git({})", e),
Error::Internal => write!(fmt, "Internal Error"),
Error::Io(e) => write!(fmt, "Io({})", e),
Error::Log(e) => write!(fmt, "Log({})", e),
Error::LogBuilder(e) => write!(fmt, "LogBuilder({})", e),
Error::Parse(e) => write!(fmt, "Parse({})", e),
Error::Serial(e) => write!(fmt, "Serial({})", e),
Error::BranchNotFound => write!(fmt, "Repo doesn't have master branch"),
@ -39,21 +32,22 @@ impl fmt::Display for Error {
}
impl ResponseError for Error {
fn status_code(&self) -> StatusCode {
match self {
Error::BranchNotFound => StatusCode::NOT_FOUND,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
let mut buf = Vec::new();
match self {
Error::BranchNotFound => {
templates::p404_no_master(
&mut buf,
VERSION_INFO,
REPO_COUNT.load(Ordering::Relaxed),
)
.unwrap();
templates::p404_no_master(&mut buf, VERSION_INFO, 0).unwrap();
HttpResponse::NotFound().content_type("text/html").body(buf)
}
_ => {
templates::p500(&mut buf, VERSION_INFO, REPO_COUNT.load(Ordering::Relaxed))
.unwrap();
templates::p500(&mut buf, VERSION_INFO, 0).unwrap();
HttpResponse::InternalServerError()
.content_type("text/html")
.body(buf)
@ -76,12 +70,6 @@ impl From<git2::Error> for Error {
}
}
impl From<log::SetLoggerError> for Error {
fn from(err: log::SetLoggerError) -> Self {
Error::Log(err)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io(err)
@ -100,12 +88,6 @@ impl From<reqwest::Error> for Error {
}
}
impl From<log4rs::config::Errors> for Error {
fn from(err: log4rs::config::Errors) -> Self {
Error::LogBuilder(err)
}
}
impl From<std::num::ParseIntError> for Error {
fn from(err: std::num::ParseIntError) -> Self {
Error::Parse(err)

525
src/lib.rs Normal file
View File

@ -0,0 +1,525 @@
#![type_length_limit = "2257138"]
#[macro_use]
extern crate actix_web;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate tracing;
mod cache;
pub mod config;
mod count;
mod error;
mod service;
mod statics;
pub mod telemetry;
mod template;
use crate::{
cache::CacheState,
config::Settings,
error::{Error, Result},
service::{Bitbucket, FormService, GitHub, Gitlab, Service},
statics::{CLIENT, CSS, FAVICON, VERSION_INFO},
template::RepoInfo,
};
use actix_web::{
dev::Server,
http::header::{CacheControl, CacheDirective, Expires, LOCATION},
middleware::{self, normalize::TrailingSlash},
web, App, HttpResponse, HttpServer, Responder,
};
use badge::{Badge, BadgeOptions};
use git2::{BranchType, Repository};
use number_prefix::NumberPrefix;
use std::{
borrow::Cow,
fs::create_dir_all,
io,
net::TcpListener,
path::Path,
process::Command,
sync::atomic::AtomicUsize,
sync::atomic::Ordering,
time::{Duration, SystemTime},
};
use tracing::Instrument;
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
#[derive(Deserialize, Serialize)]
struct GeneratorForm<'a> {
service: FormService,
user: Cow<'a, str>,
repo: Cow<'a, str>,
}
#[derive(Debug)]
pub(crate) struct State {
settings: Settings,
}
impl State {
fn repos(&self) -> String {
self.settings.repodir.display().to_string()
}
fn cache(&self) -> String {
self.settings.cachedir.display().to_string()
}
}
#[derive(Serialize)]
struct JsonResponse<'a> {
head: &'a str,
branch: &'a str,
count: u64,
commits: u64,
}
#[derive(Deserialize, Debug)]
struct BranchQuery {
branch: Option<String>,
}
fn pull(path: impl AsRef<Path>) -> Result<()> {
let repo = Repository::open_bare(path)?;
let mut origin = repo.find_remote("origin")?;
origin.fetch(&["refs/heads/*:refs/heads/*"], None, None)?;
Ok(())
}
fn hoc(repo: &str, repo_dir: &str, cache_dir: &str, branch: &str) -> Result<(u64, String, u64)> {
let repo_dir = format!("{}/{}", repo_dir, repo);
let cache_dir = format!("{}/{}.json", cache_dir, repo);
let cache_dir = Path::new(&cache_dir);
let repo = Repository::open_bare(&repo_dir)?;
// TODO: do better...
let head = repo
.find_branch(branch, BranchType::Local)
.map_err(|_| Error::BranchNotFound)?
.into_reference();
let head = format!("{}", head.target().ok_or(Error::BranchNotFound)?);
let mut arg_commit_count = vec!["rev-list".to_string(), "--count".to_string()];
let mut arg = vec![
"log".to_string(),
"--pretty=tformat:".to_string(),
"--numstat".to_string(),
"--ignore-space-change".to_string(),
"--ignore-all-space".to_string(),
"--ignore-submodules".to_string(),
"--no-color".to_string(),
"--find-copies-harder".to_string(),
"-M".to_string(),
"--diff-filter=ACDM".to_string(),
];
let cache = CacheState::read_from_file(&cache_dir, branch, &head)?;
match &cache {
CacheState::Current { count, commits, .. } => {
info!("Using cache");
return Ok((*count, head, *commits));
}
CacheState::Old { head, .. } => {
info!("Updating cache");
arg.push(format!("{}..{}", head, branch));
arg_commit_count.push(format!("{}..{}", head, branch));
}
CacheState::No | CacheState::NoneForBranch(..) => {
info!("Creating cache");
arg.push(branch.to_string());
arg_commit_count.push(branch.to_string());
}
};
arg.push("--".to_string());
arg.push(".".to_string());
let output = Command::new("git")
.args(&arg)
.current_dir(&repo_dir)
.output()?
.stdout;
let output = String::from_utf8_lossy(&output);
let output_commits = Command::new("git")
.args(&arg_commit_count)
.current_dir(&repo_dir)
.output()?
.stdout;
let output_commits = String::from_utf8_lossy(&output_commits);
let commits: u64 = output_commits.trim().parse()?;
let count: u64 = output
.lines()
.map(|s| {
s.split_whitespace()
.take(2)
.map(str::parse::<u64>)
.filter_map(std::result::Result::ok)
.sum::<u64>()
})
.sum();
let cache = cache.calculate_new_cache(count, commits, (&head).into(), branch);
cache.write_to_file(cache_dir)?;
Ok((count, head, commits))
}
async fn remote_exists(url: &str) -> Result<bool> {
let resp = CLIENT.head(url).send().await?;
Ok(resp.status() == reqwest::StatusCode::OK)
}
enum HocResult {
Hoc {
hoc: u64,
commits: u64,
hoc_pretty: String,
head: String,
url: String,
repo: String,
service_path: String,
},
NotFound,
}
async fn delete_repo_and_cache<T>(
state: web::Data<State>,
repo_count: web::Data<AtomicUsize>,
data: web::Path<(String, String)>,
) -> Result<impl Responder>
where
T: Service,
{
let data = data.into_inner();
let span = info_span!(
"deleting repository and cache",
service = T::domain(),
user = data.0.as_str(),
repo = data.1.as_str()
);
let future = async {
let repo = format!(
"{}/{}/{}",
T::domain(),
data.0.to_lowercase(),
data.1.to_lowercase()
);
info!("Deleting cache and repository");
let cache_dir = format!("{}/{}.json", &state.cache(), repo);
let repo_dir = format!("{}/{}", &state.repos(), repo);
std::fs::remove_file(&cache_dir).or_else(|e| {
if e.kind() == io::ErrorKind::NotFound {
Ok(())
} else {
Err(e)
}
})?;
std::fs::remove_dir_all(&repo_dir).or_else(|e| {
if e.kind() == io::ErrorKind::NotFound {
Ok(())
} else {
Err(e)
}
})?;
repo_count.fetch_sub(1, Ordering::Relaxed);
Ok(HttpResponse::TemporaryRedirect()
.header(
LOCATION,
format!("/{}/{}/{}/view", T::url_path(), data.0, data.1),
)
.finish())
};
future.instrument(span).await
}
async fn handle_hoc_request<T, F>(
state: web::Data<State>,
repo_count: web::Data<AtomicUsize>,
data: web::Path<(String, String)>,
branch: &str,
mapper: F,
) -> Result<HttpResponse>
where
T: Service,
F: FnOnce(HocResult) -> Result<HttpResponse>,
{
let data = data.into_inner();
let span = info_span!(
"handling hoc calculation",
service = T::domain(),
user = data.0.as_str(),
repo = data.1.as_str(),
branch
);
let future = async {
let repo = format!("{}/{}", data.0.to_lowercase(), data.1.to_lowercase());
let service_path = format!("{}/{}", T::url_path(), repo);
let service_url = format!("{}/{}", T::domain(), repo);
let path = format!("{}/{}", state.repos(), service_url);
let url = format!("https://{}", service_url);
let remote_exists = remote_exists(&url).await?;
let file = Path::new(&path);
if !file.exists() {
if !remote_exists {
warn!("Repository does not exist");
return mapper(HocResult::NotFound);
}
info!("Cloning for the first time");
create_dir_all(file)?;
let repo = Repository::init_bare(file)?;
repo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*")?;
repo.remote_set_url("origin", &url)?;
repo_count.fetch_add(1, Ordering::Relaxed);
}
pull(&path)?;
let (hoc, head, commits) = hoc(&service_url, &state.repos(), &state.cache(), branch)?;
let hoc_pretty = match NumberPrefix::decimal(hoc as f64) {
NumberPrefix::Standalone(hoc) => hoc.to_string(),
NumberPrefix::Prefixed(prefix, hoc) => format!("{:.1}{}", hoc, prefix),
};
let res = HocResult::Hoc {
hoc,
commits,
hoc_pretty,
head,
url,
repo,
service_path,
};
mapper(res)
};
future.instrument(span).await
}
pub(crate) async fn json_hoc<T: Service>(
state: web::Data<State>,
repo_count: web::Data<AtomicUsize>,
data: web::Path<(String, String)>,
branch: web::Query<BranchQuery>,
) -> Result<HttpResponse> {
let branch = branch.branch.as_deref().unwrap_or("master");
let rc_clone = repo_count.clone();
let mapper = move |r| match r {
HocResult::NotFound => p404(rc_clone),
HocResult::Hoc {
hoc, head, commits, ..
} => Ok(HttpResponse::Ok().json(JsonResponse {
branch,
head: &head,
count: hoc,
commits,
})),
};
handle_hoc_request::<T, _>(state, repo_count, data, branch, mapper).await
}
fn no_cache_response(body: Vec<u8>) -> HttpResponse {
let expiration = SystemTime::now() + Duration::from_secs(30);
HttpResponse::Ok()
.content_type("image/svg+xml")
.set(Expires(expiration.into()))
.set(CacheControl(vec![
CacheDirective::MaxAge(0u32),
CacheDirective::MustRevalidate,
CacheDirective::NoCache,
CacheDirective::NoStore,
]))
.body(body)
}
pub(crate) async fn calculate_hoc<T: Service>(
state: web::Data<State>,
repo_count: web::Data<AtomicUsize>,
data: web::Path<(String, String)>,
branch: web::Query<BranchQuery>,
) -> HttpResponse {
let rc_clone = repo_count.clone();
let mapper = move |r| match r {
HocResult::NotFound => p404(rc_clone),
HocResult::Hoc { hoc_pretty, .. } => {
let badge_opt = BadgeOptions {
subject: "Hits-of-Code".to_string(),
color: "#007ec6".to_string(),
status: hoc_pretty,
};
let badge = Badge::new(badge_opt)?;
// TODO: remove clone
let body = badge.to_svg().as_bytes().to_vec();
Ok(no_cache_response(body))
}
};
let branch = branch.branch.as_deref().unwrap_or("master");
let error_badge = |_| {
let error_badge = Badge::new(BadgeOptions {
subject: "Hits-of-Code".to_string(),
color: "#ff0000".to_string(),
status: "error".to_string(),
})
.unwrap();
let body = error_badge.to_svg().as_bytes().to_vec();
no_cache_response(body)
};
handle_hoc_request::<T, _>(state, repo_count, data, branch, mapper)
.await
.unwrap_or_else(error_badge)
}
async fn overview<T: Service>(
state: web::Data<State>,
repo_count: web::Data<AtomicUsize>,
data: web::Path<(String, String)>,
branch: web::Query<BranchQuery>,
) -> Result<HttpResponse> {
let branch = branch.branch.as_deref().unwrap_or("master");
let base_url = state.settings.base_url.clone();
let rc_clone = repo_count.clone();
let mapper = move |r| match r {
HocResult::NotFound => p404(rc_clone),
HocResult::Hoc {
hoc,
commits,
hoc_pretty,
url,
head,
repo,
service_path,
} => {
let mut buf = Vec::new();
let repo_info = RepoInfo {
commit_url: &T::commit_url(&repo, &head),
commits,
base_url: &base_url,
head: &head,
hoc,
hoc_pretty: &hoc_pretty,
path: &service_path,
url: &url,
branch,
};
templates::overview(
&mut buf,
VERSION_INFO,
rc_clone.load(Ordering::Relaxed),
repo_info,
)?;
Ok(HttpResponse::Ok().content_type("text/html").body(buf))
}
};
handle_hoc_request::<T, _>(state, repo_count, data, branch, mapper).await
}
#[get("/health_check")]
async fn health_check() -> HttpResponse {
HttpResponse::Ok().finish()
}
#[get("/")]
async fn index(
state: web::Data<State>,
repo_count: web::Data<AtomicUsize>,
) -> Result<HttpResponse> {
let mut buf = Vec::new();
templates::index(
&mut buf,
VERSION_INFO,
repo_count.load(Ordering::Relaxed),
&state.settings.base_url,
)?;
Ok(HttpResponse::Ok().content_type("text/html").body(buf))
}
#[post("/generate")]
async fn generate(
params: web::Form<GeneratorForm<'_>>,
state: web::Data<State>,
repo_count: web::Data<AtomicUsize>,
) -> Result<HttpResponse> {
let repo = format!("{}/{}", params.user, params.repo);
let mut buf = Vec::new();
templates::generate(
&mut buf,
VERSION_INFO,
repo_count.load(Ordering::Relaxed),
&state.settings.base_url,
params.service.url(),
params.service.service(),
&repo,
)?;
Ok(HttpResponse::Ok().content_type("text/html").body(buf))
}
fn p404(repo_count: web::Data<AtomicUsize>) -> Result<HttpResponse> {
let mut buf = Vec::new();
templates::p404(&mut buf, VERSION_INFO, repo_count.load(Ordering::Relaxed))?;
Ok(HttpResponse::NotFound().content_type("text/html").body(buf))
}
async fn async_p404(repo_count: web::Data<AtomicUsize>) -> Result<HttpResponse> {
p404(repo_count)
}
fn css() -> HttpResponse {
HttpResponse::Ok().content_type("text/css").body(CSS)
}
fn favicon32() -> HttpResponse {
HttpResponse::Ok().content_type("image/png").body(FAVICON)
}
async fn start_server(listener: TcpListener, settings: Settings) -> std::io::Result<Server> {
let workers = settings.workers;
let repo_count =
// TODO: errorhandling
web::Data::new(AtomicUsize::new(count::count_repositories(&settings.repodir).unwrap()));
let state = web::Data::new(State { settings });
Ok(HttpServer::new(move || {
App::new()
.app_data(state.clone())
.app_data(repo_count.clone())
.wrap(tracing_actix_web::TracingLogger)
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
.service(index)
.service(health_check)
.service(web::resource("/tacit-css.min.css").route(web::get().to(css)))
.service(web::resource("/favicon.ico").route(web::get().to(favicon32)))
.service(generate)
.service(web::resource("/github/{user}/{repo}").to(calculate_hoc::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}").to(calculate_hoc::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}").to(calculate_hoc::<Bitbucket>))
.service(
web::resource("/github/{user}/{repo}/delete")
.route(web::post().to(delete_repo_and_cache::<GitHub>)),
)
.service(
web::resource("/gitlab/{user}/{repo}/delete")
.route(web::post().to(delete_repo_and_cache::<Gitlab>)),
)
.service(
web::resource("/bitbucket/{user}/{repo}/delete")
.route(web::post().to(delete_repo_and_cache::<Bitbucket>)),
)
.service(web::resource("/github/{user}/{repo}/json").to(json_hoc::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}/json").to(json_hoc::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}/json").to(json_hoc::<Bitbucket>))
.service(web::resource("/view/github/{user}/{repo}").to(overview::<GitHub>))
.service(web::resource("/view/gitlab/{user}/{repo}").to(overview::<Gitlab>))
.service(web::resource("/view/bitbucket/{user}/{repo}").to(overview::<Bitbucket>))
.service(web::resource("/github/{user}/{repo}/view").to(overview::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}/view").to(overview::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}/view").to(overview::<Bitbucket>))
.default_service(web::resource("").route(web::get().to(async_p404)))
})
.workers(workers)
.listen(listener)?
.run())
}
pub async fn run(listener: TcpListener, settings: Settings) -> std::io::Result<Server> {
let span = info_span!("hoc", version = env!("CARGO_PKG_VERSION"));
let _ = span.enter();
start_server(listener, settings).instrument(span).await
}

View File

@ -1,455 +1,26 @@
#![type_length_limit = "2257138"]
use hoc::{config::Settings, telemetry};
#[macro_use]
extern crate actix_web;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
use std::net::TcpListener;
mod cache;
mod config;
mod count;
mod error;
mod service;
mod statics;
mod template;
fn init() {
dotenv::dotenv().ok();
openssl_probe::init_ssl_cert_env_vars();
#[cfg(test)]
mod tests;
use crate::{
cache::CacheState,
error::{Error, Result},
service::{Bitbucket, FormService, GitHub, Gitlab, Service},
statics::{CLIENT, CSS, FAVICON, OPT, REPO_COUNT, VERSION_INFO},
template::RepoInfo,
};
use actix_web::{
http::header::{CacheControl, CacheDirective, Expires, LOCATION},
middleware::{self, normalize::TrailingSlash},
web, App, HttpResponse, HttpServer, Responder,
};
use badge::{Badge, BadgeOptions};
use git2::{BranchType, Repository};
use number_prefix::NumberPrefix;
use std::{
borrow::Cow,
fs::create_dir_all,
io,
path::Path,
process::Command,
sync::atomic::Ordering,
sync::Arc,
time::{Duration, SystemTime},
};
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
#[derive(Deserialize, Serialize)]
struct GeneratorForm<'a> {
service: FormService,
user: Cow<'a, str>,
repo: Cow<'a, str>,
}
#[derive(Debug)]
pub(crate) struct State {
repos: String,
cache: String,
}
#[derive(Serialize)]
struct JsonResponse<'a> {
head: &'a str,
branch: &'a str,
count: u64,
commits: u64,
}
#[derive(Deserialize, Debug)]
struct BranchQuery {
branch: Option<String>,
}
fn pull(path: impl AsRef<Path>) -> Result<()> {
let repo = Repository::open_bare(path)?;
let mut origin = repo.find_remote("origin")?;
origin.fetch(&["refs/heads/*:refs/heads/*"], None, None)?;
Ok(())
}
fn hoc(repo: &str, repo_dir: &str, cache_dir: &str, branch: &str) -> Result<(u64, String, u64)> {
let repo_dir = format!("{}/{}", repo_dir, repo);
let cache_dir = format!("{}/{}.json", cache_dir, repo);
let cache_dir = Path::new(&cache_dir);
let repo = Repository::open_bare(&repo_dir)?;
// TODO: do better...
let head = repo
.find_branch(branch, BranchType::Local)
.map_err(|_| Error::BranchNotFound)?
.into_reference();
let head = format!("{}", head.target().ok_or(Error::BranchNotFound)?);
let mut arg_commit_count = vec!["rev-list".to_string(), "--count".to_string()];
let mut arg = vec![
"log".to_string(),
"--pretty=tformat:".to_string(),
"--numstat".to_string(),
"--ignore-space-change".to_string(),
"--ignore-all-space".to_string(),
"--ignore-submodules".to_string(),
"--no-color".to_string(),
"--find-copies-harder".to_string(),
"-M".to_string(),
"--diff-filter=ACDM".to_string(),
];
let cache = CacheState::read_from_file(&cache_dir, branch, &head)?;
match &cache {
CacheState::Current { count, commits, .. } => {
info!("Using cache for {}", repo_dir);
return Ok((*count, head, *commits));
}
CacheState::Old { head, .. } => {
info!("Updating cache for {}", repo_dir);
arg.push(format!("{}..{}", head, branch));
arg_commit_count.push(format!("{}..{}", head, branch));
}
CacheState::No | CacheState::NoneForBranch(..) => {
info!("Creating cache for {}", repo_dir);
arg.push(branch.to_string());
arg_commit_count.push(branch.to_string());
}
};
arg.push("--".to_string());
arg.push(".".to_string());
let output = Command::new("git")
.args(&arg)
.current_dir(&repo_dir)
.output()?
.stdout;
let output = String::from_utf8_lossy(&output);
let output_commits = Command::new("git")
.args(&arg_commit_count)
.current_dir(&repo_dir)
.output()?
.stdout;
let output_commits = String::from_utf8_lossy(&output_commits);
let commits: u64 = output_commits.trim().parse()?;
let count: u64 = output
.lines()
.map(|s| {
s.split_whitespace()
.take(2)
.map(str::parse::<u64>)
.filter_map(std::result::Result::ok)
.sum::<u64>()
})
.sum();
let cache = cache.calculate_new_cache(count, commits, (&head).into(), branch);
cache.write_to_file(cache_dir)?;
Ok((count, head, commits))
}
async fn remote_exists(url: &str) -> Result<bool> {
let resp = CLIENT.head(url).send().await?;
Ok(resp.status() == reqwest::StatusCode::OK)
}
enum HocResult {
Hoc {
hoc: u64,
commits: u64,
hoc_pretty: String,
head: String,
url: String,
repo: String,
service_path: String,
},
NotFound,
}
async fn delete_repo_and_cache<T>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
) -> Result<impl Responder>
where
T: Service,
{
let data = data.into_inner();
let repo = format!(
"{}/{}/{}",
T::domain(),
data.0.to_lowercase(),
data.1.to_lowercase()
);
info!("Deleting cache and repository for {}", repo);
let cache_dir = format!("{}/{}.json", &state.cache, repo);
let repo_dir = format!("{}/{}", &state.repos, repo);
std::fs::remove_file(&cache_dir).or_else(|e| {
if e.kind() == io::ErrorKind::NotFound {
Ok(())
} else {
Err(e)
}
})?;
std::fs::remove_dir_all(&repo_dir).or_else(|e| {
if e.kind() == io::ErrorKind::NotFound {
Ok(())
} else {
Err(e)
}
})?;
REPO_COUNT.fetch_sub(1, Ordering::Relaxed);
Ok(HttpResponse::TemporaryRedirect()
.header(
LOCATION,
format!("/view/{}/{}/{}", T::url_path(), data.0, data.1),
)
.finish())
}
async fn handle_hoc_request<T, F>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
branch: &str,
mapper: F,
) -> Result<HttpResponse>
where
T: Service,
F: Fn(HocResult) -> Result<HttpResponse>,
{
let data = data.into_inner();
let repo = format!("{}/{}", data.0.to_lowercase(), data.1.to_lowercase());
let service_path = format!("{}/{}", T::url_path(), repo);
let service_url = format!("{}/{}", T::domain(), repo);
let path = format!("{}/{}", state.repos, service_url);
let url = format!("https://{}", service_url);
let remote_exists = remote_exists(&url).await?;
let file = Path::new(&path);
if !file.exists() {
if !remote_exists {
warn!("Repository does not exist: {}", url);
return mapper(HocResult::NotFound);
}
info!("Cloning {} for the first time", url);
create_dir_all(file)?;
let repo = Repository::init_bare(file)?;
repo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*")?;
repo.remote_set_url("origin", &url)?;
REPO_COUNT.fetch_add(1, Ordering::Relaxed);
}
pull(&path)?;
let (hoc, head, commits) = hoc(&service_url, &state.repos, &state.cache, branch)?;
let hoc_pretty = match NumberPrefix::decimal(hoc as f64) {
NumberPrefix::Standalone(hoc) => hoc.to_string(),
NumberPrefix::Prefixed(prefix, hoc) => format!("{:.1}{}", hoc, prefix),
};
let res = HocResult::Hoc {
hoc,
commits,
hoc_pretty,
head,
url,
repo,
service_path,
};
mapper(res)
}
pub(crate) async fn json_hoc<T: Service>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
branch: web::Query<BranchQuery>,
) -> Result<HttpResponse> {
let branch = branch.branch.as_deref().unwrap_or("master");
let mapper = |r| match r {
HocResult::NotFound => p404(),
HocResult::Hoc {
hoc, head, commits, ..
} => Ok(HttpResponse::Ok().json(JsonResponse {
branch,
head: &head,
count: hoc,
commits,
})),
};
handle_hoc_request::<T, _>(state, data, branch, mapper).await
}
pub(crate) async fn calculate_hoc<T: Service>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
branch: web::Query<BranchQuery>,
) -> Result<HttpResponse> {
let mapper = move |r| match r {
HocResult::NotFound => p404(),
HocResult::Hoc { hoc_pretty, .. } => {
let badge_opt = BadgeOptions {
subject: "Hits-of-Code".to_string(),
color: "#007ec6".to_string(),
status: hoc_pretty,
};
let badge = Badge::new(badge_opt)?;
// TODO: remove clone
let body = badge.to_svg().as_bytes().to_vec();
let expiration = SystemTime::now() + Duration::from_secs(30);
Ok(HttpResponse::Ok()
.content_type("image/svg+xml")
.set(Expires(expiration.into()))
.set(CacheControl(vec![
CacheDirective::MaxAge(0u32),
CacheDirective::MustRevalidate,
CacheDirective::NoCache,
CacheDirective::NoStore,
]))
.body(body))
}
};
let branch = branch.branch.as_deref().unwrap_or("master");
handle_hoc_request::<T, _>(state, data, branch, mapper).await
}
async fn overview<T: Service>(
state: web::Data<Arc<State>>,
data: web::Path<(String, String)>,
branch: web::Query<BranchQuery>,
) -> Result<HttpResponse> {
let branch = branch.branch.as_deref().unwrap_or("master");
let mapper = |r| match r {
HocResult::NotFound => p404(),
HocResult::Hoc {
hoc,
commits,
hoc_pretty,
url,
head,
repo,
service_path,
} => {
let mut buf = Vec::new();
let repo_info = RepoInfo {
commit_url: &T::commit_url(&repo, &head),
commits,
domain: &OPT.domain,
head: &head,
hoc,
hoc_pretty: &hoc_pretty,
path: &service_path,
url: &url,
branch,
};
templates::overview(
&mut buf,
VERSION_INFO,
REPO_COUNT.load(Ordering::Relaxed),
repo_info,
)?;
Ok(HttpResponse::Ok().content_type("text/html").body(buf))
}
};
handle_hoc_request::<T, _>(state, data, branch, mapper).await
}
#[get("/")]
async fn index() -> Result<HttpResponse> {
let mut buf = Vec::new();
templates::index(
&mut buf,
VERSION_INFO,
REPO_COUNT.load(Ordering::Relaxed),
&OPT.domain,
)?;
Ok(HttpResponse::Ok().content_type("text/html").body(buf))
}
#[post("/generate")]
async fn generate(params: web::Form<GeneratorForm<'_>>) -> Result<HttpResponse> {
let repo = format!("{}/{}", params.user, params.repo);
let mut buf = Vec::new();
templates::generate(
&mut buf,
VERSION_INFO,
REPO_COUNT.load(Ordering::Relaxed),
&OPT.domain,
params.service.url(),
params.service.service(),
&repo,
)?;
Ok(HttpResponse::Ok().content_type("text/html").body(buf))
}
fn p404() -> Result<HttpResponse> {
let mut buf = Vec::new();
templates::p404(&mut buf, VERSION_INFO, REPO_COUNT.load(Ordering::Relaxed))?;
Ok(HttpResponse::NotFound().content_type("text/html").body(buf))
}
async fn async_p404() -> Result<HttpResponse> {
p404()
}
fn css() -> HttpResponse {
HttpResponse::Ok().content_type("text/css").body(CSS)
}
fn favicon32() -> HttpResponse {
HttpResponse::Ok().content_type("image/png").body(FAVICON)
}
async fn start_server() -> std::io::Result<()> {
let interface = format!("{}:{}", OPT.host, OPT.port);
let state = Arc::new(State {
repos: OPT.outdir.display().to_string(),
cache: OPT.cachedir.display().to_string(),
});
HttpServer::new(move || {
App::new()
.data(state.clone())
.wrap(middleware::Logger::default())
.wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
.service(index)
.service(web::resource("/tacit-css.min.css").route(web::get().to(css)))
// TODO
.service(web::resource("/favicon.ico").route(web::get().to(favicon32)))
.service(generate)
.service(web::resource("/github/{user}/{repo}").to(calculate_hoc::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}").to(calculate_hoc::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}").to(calculate_hoc::<Bitbucket>))
.service(
web::resource("/github/{user}/{repo}/delete")
.route(web::post().to(delete_repo_and_cache::<GitHub>)),
)
.service(
web::resource("/gitlab/{user}/{repo}/delete")
.route(web::post().to(delete_repo_and_cache::<Gitlab>)),
)
.service(
web::resource("/bitbucket/{user}/{repo}/delete")
.route(web::post().to(delete_repo_and_cache::<Bitbucket>)),
)
.service(web::resource("/github/{user}/{repo}/json").to(json_hoc::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}/json").to(json_hoc::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}/json").to(json_hoc::<Bitbucket>))
.service(web::resource("/view/github/{user}/{repo}").to(overview::<GitHub>))
.service(web::resource("/view/gitlab/{user}/{repo}").to(overview::<Gitlab>))
.service(web::resource("/view/bitbucket/{user}/{repo}").to(overview::<Bitbucket>))
.default_service(web::resource("").route(web::get().to(async_p404)))
})
.workers(OPT.workers)
.bind(interface)?
.run()
.await
telemetry::init_subscriber(telemetry::get_subscriber("hoc", "info"))
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
config::init().await.unwrap();
start_server().await
init();
// TODO: error handling
let settings = Settings::load().expect("Cannot load config");
let address = format!("{}:{}", settings.host, settings.port);
// TODO: error handling
let listener = TcpListener::bind(address)?;
hoc::run(listener, settings)
.await
.expect("Server error")
.await
}

View File

@ -1,14 +1,10 @@
use crate::{config::Opt, count::count_repositories};
use std::sync::atomic::AtomicUsize;
use structopt::StructOpt;
pub struct VersionInfo<'a> {
pub commit: &'a str,
pub version: &'a str,
}
pub(crate) const VERSION_INFO: VersionInfo = VersionInfo {
commit: env!("VERGEN_SHA_SHORT"),
commit: env!("VERGEN_GIT_SHA_SHORT"),
version: env!("CARGO_PKG_VERSION"),
};
pub(crate) const CSS: &str = include_str!("../static/tacit-css.min.css");
@ -16,7 +12,4 @@ pub(crate) const FAVICON: &[u8] = include_bytes!("../static/favicon32.png");
lazy_static! {
pub(crate) static ref CLIENT: reqwest::Client = reqwest::Client::new();
pub(crate) static ref OPT: Opt = Opt::from_args();
pub(crate) static ref REPO_COUNT: AtomicUsize =
AtomicUsize::new(count_repositories(&OPT.outdir).unwrap());
}

21
src/telemetry.rs Normal file
View File

@ -0,0 +1,21 @@
use tracing::{subscriber::set_global_default, Subscriber};
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
use tracing_log::LogTracer;
use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
pub fn get_subscriber(name: &str, env_filter: &str) -> impl Subscriber + Send + Sync {
let env_filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(env_filter));
let formatting_layer = BunyanFormattingLayer::new(name.to_string(), std::io::stdout);
Registry::default()
.with(env_filter)
.with(JsonStorageLayer)
.with(formatting_layer)
}
pub fn init_subscriber(subscriber: impl Subscriber + Send + Sync) {
LogTracer::init().expect("Failed to set logger");
set_global_default(subscriber).expect("Failed to set tracing subscriber");
}

View File

@ -1,7 +1,7 @@
pub struct RepoInfo<'a> {
pub commit_url: &'a str,
pub commits: u64,
pub domain: &'a str,
pub base_url: &'a str,
pub head: &'a str,
pub hoc: u64,
pub hoc_pretty: &'a str,

View File

@ -1,66 +0,0 @@
use crate::{
calculate_hoc, index, json_hoc,
service::{Bitbucket, GitHub, Gitlab, Service},
State,
};
use actix_web::{http, test, web, App};
use tempfile::tempdir;
macro_rules! test_app {
($path: expr) => {
test::init_service(App::new().service($path)).await
};
($state: expr, $path: expr) => {
test::init_service(App::new().data($state).service($path)).await
};
}
macro_rules! test_service {
($name: ident, $path: tt, $what: ident) => {
async fn $name<T: 'static + Service>(req_path: &str) {
let repo_dir = dbg!(tempdir().unwrap());
let cache_dir = dbg!(tempdir().unwrap());
let repos = format!("{}/", repo_dir.path().display());
let cache = format!("{}/", cache_dir.path().display());
let state = dbg!(State { repos, cache });
let mut app = test_app!(state, web::resource($path).to($what::<T>));
let req = dbg!(test::TestRequest::with_uri(req_path).to_request());
let resp = dbg!(test::call_service(&mut app, req).await);
assert_eq!(resp.status(), http::StatusCode::OK);
}
};
}
#[actix_rt::test]
async fn test_index() {
let mut app = test::init_service(App::new().service(index)).await;
let req = dbg!(test::TestRequest::with_uri("/").to_request());
let resp = dbg!(test::call_service(&mut app, req).await);
assert_eq!(resp.status(), http::StatusCode::OK);
}
// TODO: fix this test
// #[actix_rt::test]
async fn test_json() {
test_service!(test_json_service, "/service/{user}/{repo}/json", json_hoc);
test_json_service::<Gitlab>("/service/vbrandl/hoc/json").await;
test_json_service::<GitHub>("/service/vbrandl/hoc/json").await;
test_json_service::<Bitbucket>("/service/vbrandl/hoc/json").await;
}
// TODO: fix this test
// #[actix_rt::test]
async fn test_badge() {
test_service!(test_badge_service, "/service/{user}/{repo}", calculate_hoc);
test_badge_service::<Gitlab>("/service/vbrandl/hoc").await;
test_badge_service::<GitHub>("/service/vbrandl/hoc").await;
test_badge_service::<Bitbucket>("/service/vbrandl/hoc").await;
}

View File

@ -1,7 +1,7 @@
@use super::base;
@use crate::statics::VersionInfo;
@(version_info: VersionInfo, repo_count: usize, domain: &str, url: &str, service: &str, path: &str)
@(version_info: VersionInfo, repo_count: usize, base_url: &str, url: &str, service: &str, path: &str)
@:base("Hits-of-Code Badges", "Badge Generator", {
@ -10,7 +10,7 @@ Here is the markdown for the badge for <a href="https://@url/@path">@url/@path</
</p>
<pre>
[![Hits-of-Code](https://@domain/@service/@path)](https://@domain/view/@service/@path)
[![Hits-of-Code](@base_url/@service/@path)](@base_url/@service/@path/view)
</pre>
<p>
@ -18,6 +18,6 @@ It will be rendered like this
</p>
<pre>
<a href="https://@domain/view/@service/@path"><img src="https://@domain/@service/@path" alt="example badge" /></a>
<a href="@base_url/@service/@path/view"><img src="@base_url/@service/@path" alt="example badge" /></a>
</pre>
}, version_info, repo_count)

View File

@ -1,7 +1,7 @@
@use super::base;
@use crate::statics::VersionInfo;
@(version_info: VersionInfo, repo_count: usize, domain: &str)
@(version_info: VersionInfo, repo_count: usize, base_url: &str)
@:base("Hits-of-Code Badges", "Hits-of-Code Badges", {
@ -24,7 +24,7 @@ used for GitHub, GitLab and Bitbucket repositories. Just put the following code
</p>
<pre>
[![Hits-of-Code](https://@domain/&lt;service&gt;/&lt;user&gt;/&lt;repo&gt;)](https://@domain/view/&lt;service&gt;/&lt;user&gt;/&lt;repo&gt;)
[![Hits-of-Code](@base_url/&lt;service&gt;/&lt;user&gt;/&lt;repo&gt;)](@base_url/&lt;service&gt;/&lt;user&gt;/&lt;repo&gt;/view)
</pre>
<p>
@ -33,7 +33,7 @@ following Markdown
</p>
<pre>
[![Hits-of-Code](https://@domain/github/vbrandl/hoc)](https://@domain/view/github/vbrandl/hoc)
[![Hits-of-Code](@base_url/github/vbrandl/hoc)](@base_url/github/vbrandl/hoc/view)
</pre>
<p>
@ -41,7 +41,7 @@ would render this badge:
</p>
<pre>
<a href="https://@domain/view/github/vbrandl/hoc"><img src="https://@domain/github/vbrandl/hoc"
<a href="@base_url/github/vbrandl/hoc/view"><img src="@base_url/github/vbrandl/hoc"
alt="example badge" /></a>
</pre>
@ -55,7 +55,7 @@ in your repository or you want a badge for another branch of your repository, ju
You can also request the HoC as JSON by appending <code>/json</code> to the request path. This will return a JSON object
with three fields: <code>count</code> (the HoC value), <code>commits</code> (the number of commits) and
<code>head</code> (the commit ref of HEAD). Requesting <a
href="https://@domain/github/vbrandl/hoc/json">https://@domain/github/vbrandl/hoc/json</a> might return something along
href="@base_url/github/vbrandl/hoc/json">@base_url/github/vbrandl/hoc/json</a> might return something along
the lines of
</p>

View File

@ -19,7 +19,7 @@ To include the badge in your readme, use the following markdown:
</p>
<pre>
[![Hits-of-Code](https://@repo_info.domain/@repo_info.path?branch=@repo_info.branch)](https://@repo_info.domain/view/@repo_info.path?branch=@repo_info.branch)
[![Hits-of-Code](@repo_info.base_url/@repo_info.path?branch=@repo_info.branch)](@repo_info.base_url/@repo_info.path/view?branch=@repo_info.branch)
</pre>

18
tests/badge.rs Normal file
View File

@ -0,0 +1,18 @@
mod util;
use actix_web::client;
#[actix_rt::test]
async fn badge_succeeds() {
let test_app = util::spawn_app().await;
let client = client::Client::default();
let response = client
.get(&format!("{}/github/vbrandl/hoc", test_app.address))
.send()
.await
.expect("Failed to execute request");
assert!(response.status().is_success());
}

18
tests/health_check.rs Normal file
View File

@ -0,0 +1,18 @@
mod util;
use actix_web::client;
#[actix_rt::test]
async fn health_check_works() {
let test_app = util::spawn_app().await;
let client = client::Client::default();
let response = client
.get(&format!("{}/health_check", test_app.address))
.send()
.await
.expect("Failed to execute request");
assert!(response.status().is_success());
}

18
tests/index.rs Normal file
View File

@ -0,0 +1,18 @@
mod util;
use actix_web::client;
#[actix_rt::test]
async fn index_returns_success() {
let test_app = util::spawn_app().await;
let client = client::Client::default();
let response = client
.get(&format!("{}/", test_app.address))
.send()
.await
.expect("Failed to execute request");
assert!(response.status().is_success());
}

18
tests/json.rs Normal file
View File

@ -0,0 +1,18 @@
mod util;
use actix_web::client;
#[actix_rt::test]
async fn json_returns_success() {
let test_app = util::spawn_app().await;
let client = client::Client::default();
let response = client
.get(&format!("{}/github/vbrandl/hoc/json", test_app.address))
.send()
.await
.expect("Failed to execute request");
assert!(response.status().is_success());
}

50
tests/util/mod.rs Normal file
View File

@ -0,0 +1,50 @@
use hoc::{config::Settings, telemetry};
use std::net::TcpListener;
use tempfile::{tempdir, TempDir};
lazy_static::lazy_static! {
static ref TRACING: () = {
let filter = if std::env::var("TEST_LOG").is_ok() { "debug" } else { "" };
let subscriber = telemetry::get_subscriber("test", filter);
telemetry::init_subscriber(subscriber);
};
}
pub struct TestApp {
pub address: String,
repo_dir: TempDir,
cache_dir: TempDir,
}
pub async fn spawn_app() -> TestApp {
lazy_static::initialize(&TRACING);
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind random port");
let port = listener.local_addr().unwrap().port();
let address = format!("http://127.0.0.1:{}", port);
let repo_dir = tempdir().expect("Cannot create repo_dir");
let cache_dir = tempdir().expect("Cannot create cache_dir");
let mut settings = Settings::load().expect("Failed to read configuration.");
settings.repodir = repo_dir.path().to_path_buf();
settings.cachedir = cache_dir.path().to_path_buf();
// configuration.database.database_name = Uuid::new_v4().to_string();
// let connection_pool = configure_database(&configuration.database).await;
let server = hoc::run(listener, settings)
.await
.expect("Failed to bind address");
let _ = tokio::spawn(server);
TestApp {
address,
repo_dir,
cache_dir,
}
}