Compare commits

..

No commits in common. "main" and "0.1.0" have entirely different histories.
main ... 0.1.0

39 changed files with 687 additions and 1719 deletions

View File

@ -1,109 +1,9 @@
kind: pipeline kind: pipeline
name: tests/release name: tools
steps: steps:
- name: rust tests - name: rust tests
image: "reg.tulpa.dev/rust:1" image: "rust:1"
pull: always pull: always
commands: commands:
- cargo test --all - cargo test
environment:
GITEA_SERVER: https://tulpa.dev
DOMO_GITEA_TOKEN:
from_secret: DOMO_GITEA_TOKEN
when:
event:
- push
- name: auto-release
image: xena/gitea-release
pull: always
settings:
auth_username: cadey
gitea_server: https://tulpa.dev
gitea_token:
from_secret: GITEA_TOKEN
when:
event:
- push
branch:
- main
---
kind: pipeline
name: cargo publish
steps:
- name: publish elfs
image: rust:1
commands:
- cd elfs
- cargo login $CARGO_TOKEN
- "cargo publish ||:"
environment:
CARGO_TOKEN:
from_secret: CARGO_TOKEN
when:
event:
- tag
- name: publish gitea
image: rust:1
commands:
- cd gitea
- cargo login $CARGO_TOKEN
- "cargo publish ||:"
environment:
CARGO_TOKEN:
from_secret: CARGO_TOKEN
when:
event:
- tag
---
kind: pipeline
name: docker
steps:
- name: build docker image
image: "monacoremo/nix:2020-04-05-05f09348-circleci"
environment:
USER: root
commands:
- true # cachix use xe
- nix-build docker.nix
- cp $(readlink result) /result/docker.tgz
volumes:
- name: image
path: /result
when:
event:
- tag
- name: push docker image
image: docker:dind
volumes:
- name: image
path: /result
- name: dockersock
path: /var/run/docker.sock
commands:
- docker load -i /result/docker.tgz
- echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
- docker push xena/gitea-release
environment:
DOCKER_USERNAME:
from_secret: DOCKER_USERNAME
DOCKER_PASSWORD:
from_secret: DOCKER_PASSWORD
when:
event:
- tag
volumes:
- name: image
temp: {}
- name: dockersock
host:
path: /var/run/docker.sock

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
/target /target
.env .env
/result

View File

@ -6,161 +6,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
## 0.7.1
Fix a minor parsing bug
## 0.7.0
Support reading version data from `Cargo.toml`
## 0.6.1
Fixed a logic error in release cutting.
## 0.5.2
Detect tags better.
## 0.5.1
Initialize `pretty_env_logger` on app boot. Oops.
## 0.5.0
A lot of internals to this program were exposed to external consumers. This was
used to provide an integration test.
Support for bracketed versions was added, a-la:
```markdown
## [0.1.0]
Hi there this is a test!
### ADDED
- something
```
The gitea crate was brought up to version `0.2.0` and includes a lot more
functionality.
## 0.4.0
This is a functionality-breaking release that removes untested/extraneous parts
of this project.
### ADDED
- The gitea client embedded into this repo is now a more generic crate that can
be reused across other projects.
- Gitea types have been simplified and redundant types have been removed.
- Simple tests for the gitea crate.
- A name generator `elfs` was created for future use in integration testing.
### CHANGED
- `release` is renamed to `cut`, but there is an alias for the old `release`
subcommand name.
### REMOVED
- All functionality but the drone plugin and release commands.
## 0.3.2
Automagically fetch tags when running as a drone plugin.
## 0.3.1
Hotfix in json parsing
## 0.3.0
Allows for a customizable default branch name for the drone plugin using either
a hard-coded value or the Gitea api to fetch it. For compatibility's sake, the
default behavior is to fetch the default branch name from the Gitea api. If you
need to hard-code your default branch name, add the config like this:
```yaml
- name: auto-release
image: xena/gitea-release:0.3.1
settings:
auth_username: cadey
default_branch: trunk
gitea_server: https://tulpa.dev
gitea_token:
from_secret: GITEA_TOKEN
when:
event:
- push
branch:
- trunk
```
Also fixed a suggestion to fetch tags over git before trying to run this in Drone.
## 0.2.7
### FIXED
Exit if this version already exists
## 0.2.6
A fix from @kivikakk to remove the use of RefCells in markdown parsing
## 0.2.5
CD fix
## 0.2.4
I need to make some kind of drone CI validator
## 0.2.3
Typo in the CD script :D
## 0.2.2
Update README, automatically push docker images
## 0.2.1
Hotfix for typos in the docker manifest.
## 0.2.0
### ADDED
- Added [Drone plugin](https://docs.drone.io/plugins/overview/) support
### Drone Plugin Support
To use this as a drone plugin, use the following config:
```yaml
- name: auto-release
image: xena/gitea-release:0.2.0
settings:
auth_username: cadey
gitea_server: https://tulpa.dev
gitea_token:
from_secret: GITEA_TOKEN
when:
branch:
include:
- master
```
## 0.1.1
### FIXED
- Fixed 500 error when creating a new release
## 0.1.0 ## 0.1.0
This is the first release of `gitea-release`! This is the first release of `gitea-release`!

View File

@ -1,25 +0,0 @@
# Code of Conduct
We are sapient.
To be sapient is to be limited.
In our limitation, we make choices that are unwise or are flawed.
If we make unwise choices because of our limitation,
we cannot judge others for the same reason.
So, we cannot judge,
thus we forgive.
This project and its results are intended as:
a place of learning,
a place of understanding,
a place of teaching,
a place of sharing,
a place of creators creating the tools for other creators to create complicated things elegantly.
Be well, Creator. Be well and create.
---
Based on the [Creator's Code v1](https://github.com/Xe/creators-code). Please
read the link for more information.

210
Cargo.lock generated
View File

@ -86,6 +86,12 @@ 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 = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "byte-unit"
version = "3.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55390dbbf21ce70683f3e926dace00a21da373e35e44a60cafd232e3e9bf2041"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.3.4" version = "1.3.4"
@ -98,17 +104,6 @@ version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
[[package]]
name = "cargo_toml"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513d17226888c7b8283ac02a1c1b0d8a9d4cbf6db65dfadb79f598f5d7966fe9"
dependencies = [
"serde",
"serde_derive",
"toml",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.54" version = "1.0.54"
@ -139,6 +134,16 @@ dependencies = [
"vec_map", "vec_map",
] ]
[[package]]
name = "cli-table"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd782cbfda62468ed8f94f2c00496ff909ad4916f4411ab9ec7bdced5414a699"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]] [[package]]
name = "comrak" name = "comrak"
version = "0.7.0" version = "0.7.0"
@ -187,13 +192,6 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
[[package]]
name = "elfs"
version = "0.1.0"
dependencies = [
"names",
]
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.23" version = "0.8.23"
@ -209,19 +207,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -249,12 +234,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]] [[package]]
name = "fuchsia-zircon" name = "fuchsia-zircon"
version = "0.3.3" version = "0.3.3"
@ -345,40 +324,23 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "gitea"
version = "0.2.0"
dependencies = [
"anyhow",
"reqwest",
"serde",
"serde_json",
"thiserror",
"tokio",
]
[[package]] [[package]]
name = "gitea-release" name = "gitea-release"
version = "0.7.1" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cargo_toml", "byte-unit",
"cli-table",
"comrak", "comrak",
"elfs",
"git2", "git2",
"gitea",
"http", "http",
"kankyo", "kankyo",
"log",
"pretty_env_logger",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"structopt", "structopt",
"tempfile", "tempfile",
"tokio", "tokio",
"toml",
"url",
] ]
[[package]] [[package]]
@ -445,15 +407,6 @@ 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 = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.13.6" version = "0.13.6"
@ -686,15 +639,6 @@ dependencies = [
"ws2_32-sys", "ws2_32-sys",
] ]
[[package]]
name = "names"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef320dab323286b50fb5cdda23f61c796a72a89998ab565ca32525c5c556f2da"
dependencies = [
"rand 0.3.23",
]
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.4" version = "0.2.4"
@ -856,16 +800,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 = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [
"env_logger",
"log",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.2" version = "1.0.2"
@ -894,19 +828,13 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.24" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.6" version = "1.0.6"
@ -916,29 +844,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
dependencies = [
"libc",
"rand 0.4.6",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi 0.3.8",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.7.3" version = "0.7.3"
@ -948,7 +853,7 @@ dependencies = [
"getrandom", "getrandom",
"libc", "libc",
"rand_chacha", "rand_chacha",
"rand_core 0.5.1", "rand_core",
"rand_hc", "rand_hc",
] ]
@ -959,24 +864,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core 0.5.1", "rand_core",
] ]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.5.1" version = "0.5.1"
@ -992,16 +882,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 = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [ dependencies = [
"rand_core 0.5.1", "rand_core",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
] ]
[[package]] [[package]]
@ -1113,18 +994,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.117" version = "1.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.117" version = "1.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1222,9 +1103,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.48" version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1250,7 +1131,7 @@ checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"rand 0.7.3", "rand",
"redox_syscall", "redox_syscall",
"remove_dir_all", "remove_dir_all",
"winapi 0.3.8", "winapi 0.3.8",
@ -1274,26 +1155,6 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "thiserror"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.0.1" version = "1.0.1"
@ -1366,15 +1227,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.0" version = "0.3.0"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "gitea-release" name = "gitea-release"
version = "0.7.1" version = "0.1.0"
authors = ["Christine Dodrill <me@christine.website>"] authors = ["Christine Dodrill <me@christine.website>"]
edition = "2018" edition = "2018"
@ -8,33 +8,20 @@ edition = "2018"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
cargo_toml = "0.8.1" byte-unit = "3"
cli-table = "0.3"
comrak = "0.7" comrak = "0.7"
git2 = "0.13" git2 = "0.13"
http = "0.2" http = "0.2"
kankyo = "0.3" kankyo = "0.3"
log = "0.4"
pretty_env_logger = "0"
reqwest = { version = "0.10", features = ["json"] } reqwest = { version = "0.10", features = ["json"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
structopt = { version = "0.3", default-features = false } structopt = { version = "0.3", default-features = false }
tokio = { version = "0.2", features = ["macros"] } tokio = { version = "0.2", features = ["macros"] }
toml = "0.5.7"
url = "2"
gitea = { path = "./gitea" }
[dev-dependencies] [dev-dependencies]
tempfile = "3" tempfile = "3"
elfs = { path = "./elfs" }
pretty_env_logger = "0.4"
[profile.release] [profile.release]
lto = true lto = true
[workspace]
members = [
"./elfs",
"./gitea"
]

100
README.md
View File

@ -8,103 +8,3 @@ repositories that reads from CHANGELOG and VERSION files. This is a clone of
[github-release](https://github.com/github-release/github-release), but more [github-release](https://github.com/github-release/github-release), but more
suited for my individual needs. This may also turn into a generic webhook suited for my individual needs. This may also turn into a generic webhook
handler, but one thing at a time. :) handler, but one thing at a time. :)
## Installation
### With Nix
```console
$ nix-env -if https://tulpa.dev/cadey/gitea-release/archive/master.tar.gz
```
### With cargo
```console
$ cargo install --git https://tulpa.dev/cadey/gitea-release.git
```
## Drone Plugin
To use this as a drone plugin, add the following to your `.drone.yml` under the
`steps` key:
```yaml
- name: auto-release
image: xena/gitea-release:latest
pull: always
settings:
auth_username: cadey
changelog_path: ./CHANGELOG.md
gitea_server: https://tulpa.dev
gitea_token:
from_secret: GITEA_TOKEN
when:
event:
- push
branch:
- master
```
Replace the values of the settings as makes sense for your gitea server. The
`changelog_path` attribute is optional, and will be `./CHANGELOG.md` by default.
The default branch will automatically be derived from the Gitea API. If you need
to hard-code your default branch name for some reason, add the `default_branch`
setting like this:
```yaml
- name: auto-release
image: xena/gitea-release:latest
pull: always
settings:
auth_username: cadey
default_branch: trunk
gitea_server: https://tulpa.dev
gitea_token:
from_secret: GITEA_TOKEN
when:
event:
- push
branch:
- trunk
```
## CHANGELOG.md and VERSION files
The `CHANGELOG.md` file is based on the [Keep a Changelog][kacl] format, but
modified slightly to make it easier for this tool. Here is an example changelog
that this tool accepts:
[kacl]: https://keepachangelog.com/en/1.0.0/
```markdown
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.1.0
Hi there this is a test!
### ADDED
- something
```
The `VERSION` file plays into this as well. The `VERSION` file MUST be a single
line containing a [semantic version][semver] string. When this tool is run with
the `release` subcommand, the following actions take place:
[semver]: https://semver.org/spec/v2.0.0.html
- The `VERSION` file is read and loaded as the desired tag for the repo
- The `CHANGELOG.md` file is read and the changes for the `VERSION` are
cherry-picked out of the file
- The git repo is checked to see if that tag already exists
- If the tag exists, the tool exits and does nothing
- If the tag does not exist, it is created (with the changelog fragment as the
body of the tag) and pushed to the gitea server
- A gitea release is created using the changelog fragment and the release name
is generated from the `VERSION` string

View File

@ -13,8 +13,7 @@
CLOSED: [2020-05-31 Sun 12:50] CLOSED: [2020-05-31 Sun 12:50]
** DONE upload ** DONE upload
CLOSED: [2020-05-30 Sat 15:15] CLOSED: [2020-05-30 Sat 15:15]
** DONE drone plugin
CLOSED: [2020-06-01 Mon 10:10]
* Core Features * Core Features
** DONE Gitea API client ** DONE Gitea API client
CLOSED: [2020-05-30 Sat 10:52] CLOSED: [2020-05-30 Sat 10:52]

1
VERSION Normal file
View File

@ -0,0 +1 @@
0.1.0

View File

@ -1,16 +0,0 @@
{ pkgs ? import <nixpkgs> { }, sources ? import ./nix/sources.nix
, naersk ? import sources.naersk { } }:
with pkgs;
let
srcNoTarget = dir:
builtins.filterSource
(path: type: type != "directory" || builtins.baseNameOf path != "target")
dir;
naersk = pkgs.callPackage sources.naersk { };
src = srcNoTarget ./.;
remapPathPrefix = true;
in naersk.buildPackage {
inherit src remapPathPrefix;
buildInputs = with pkgs; [ pkg-config openssl libgit2 ];
}

View File

@ -1,21 +0,0 @@
{ system ? builtins.currentSystem }:
let
pkgs = import <nixpkgs> { };
callPackage = pkgs.lib.callPackageWith pkgs;
gitea-release = callPackage ./default.nix { };
dockerImage = pkg:
pkgs.dockerTools.buildLayeredImage {
name = "xena/gitea-release";
tag = "latest";
contents = [ pkgs.cacert pkg ];
config = {
Cmd = [ "/bin/gitea-release" "drone-plugin" ];
WorkingDir = "/";
};
};
in dockerImage gitea-release

View File

@ -1,16 +0,0 @@
[package]
name = "elfs"
version = "0.1.0"
authors = ["Christine Dodrill <me@christine.website>"]
edition = "2018"
homepage = "https://tulpa.dev/cadey/gitea-release/src/branch/main/elfs"
repository = "https://tulpa.dev/cadey/gitea-release"
keywords = ["namegen"]
license = "MIT"
readme = "README.md"
description = "A simple name generator for Rust programs."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
names = "0.11"

View File

@ -1,16 +0,0 @@
# elfs
A simple name generator based on [Pokemon Vietnamese Crystal](https://tvtropes.org/pmwiki/pmwiki.php/VideoGame/PokemonVietnameseCrystal).
## Usage
Add the following to your `Cargo.toml`:
```toml
[dependencies]
elfs = "0.1"
```
```rust
let name = elfs::next();
```

View File

@ -1,75 +0,0 @@
/// This one-function crate generates names based on [Pokemon Vietnamese Crystal](https://tvtropes.org/pmwiki/pmwiki.php/VideoGame/PokemonVietnameseCrystal).
use names::{Generator, Name};
fn moves() -> &'static [&'static str] {
&[
"ABLE", "ABNORMA", "AGAIN", "AIREXPL", "ANG", "ANGER", "ASAIL", "ATTACK", "AURORA", "AWL",
"BAN", "BAND", "BARE", "BEAT", "BEATED", "BELLY", "BIND", "BITE", "BLOC", "BLOOD", "BODY",
"BOOK", "BREATH", "BUMP", "CAST", "CHAM", "CLAMP", "CLAP", "CLAW", "CLEAR", "CLI", "CLIP",
"CLOUD", "CONTRO", "CONVY", "COOLHIT", "CRASH", "CRY", "CUT", "DESCRI", "D-FIGHT", "DIG",
"DITCH", "DIV", "DOZ", "DRE", "DUL", "DU-PIN", "DYE", "EARTH", "EDU", "EG-BOMB", "EGG",
"ELEGY", "ELE-HIT", "EMBODY", "EMPLI", "ENGL", "ERUPT", "EVENS", "EXPLOR", "EYES", "FALL",
"FAST", "F-CAR", "F-DANCE", "FEARS", "F-FIGHT", "FIGHT", "FIR", "FIRE", "FIREHIT", "FLAME",
"FLAP", "FLASH", "FLEW", "FORCE", "FRA", "FREEZE", "FROG", "G-BIRD", "GENKISS", "GIFT",
"G-KISS", "G-MOUSE", "GRADE", "GROW", "HAMMER", "HARD", "HAT", "HATE", "H-BOMB", "HELL-R",
"HEMP", "HINT", "HIT", "HU", "HUNT", "HYPNOSI", "INHA", "IRO", "IRONBAR", "IR-WING",
"J-GUN", "KEE", "KICK", "KNIF", "KNIFE", "KNOCK", "LEVEL", "LIGH", "LIGHHIT", "LIGHT",
"LIVE", "L-WALL", "MAD", "MAJUS", "MEL", "MELO", "MESS", "MILK", "MIMI", "MISS", "MIXING",
"MOVE", "MUD", "NI-BED", "NOISY", "NOONLI", "NULL", "N-WAVE", "PAT", "PEACE", "PIN",
"PLAN", "PLANE", "POIS", "POL", "POWDE", "POWE", "POWER", "PRIZE", "PROTECT", "PROUD",
"RAGE", "RECOR", "REFLAC", "REFREC", "REGR", "RELIV", "RENEW", "R-FIGHT", "RING", "RKICK",
"ROCK", "ROUND", "RUS", "RUSH", "SAND", "SAW", "SCISSOR", "SCRA", "SCRIPT", "SEEN",
"SERVER", "SHADOW", "SHELL", "SHINE", "SHO", "SIGHT", "SIN", "SMALL", "SMELT", "SMOK",
"SNAKE", "SNO", "SNOW", "SOU", "SO-WAVE", "SPAR", "SPEC", "SPID", "S-PIN", "SPRA", "STAM",
"STARE", "STEA", "STONE", "STORM", "STRU", "STRUG", "STUDEN", "SUBS", "SUCID", "SUN-LIG",
"SUNRIS", "SUPLY", "S-WAVE", "TAILS", "TANGL", "TASTE", "TELLI", "THANK", "TONKICK",
"TOOTH", "TORL", "TRAIN", "TRIKICK", "TUNGE", "VOLT", "WA-GUN", "WATCH", "WAVE", "W-BOMB",
"WFALL", "WFING", "WHIP", "WHIRL", "WIND", "WOLF", "WOOD", "WOR", "YUJA",
]
}
fn names() -> &'static [&'static str] {
&[
"SEED", "GRASS", "FLOWE", "SHAD", "CABR", "SNAKE", "GOLD", "COW", "GUIKI", "PEDAL",
"DELAN", "B-FLY", "BIDE", "KEYU", "FORK", "LAP", "PIGE", "PIJIA", "CAML", "LAT", "BIRD",
"BABOO", "VIV", "ABOKE", "PIKAQ", "RYE", "SAN", "BREAD", "LIDEL", "LIDE", "PIP", "PIKEX",
"ROK", "JUGEN", "PUD", "BUDE", "ZHIB", "GELU", "GRAS", "FLOW", "LAFUL", "ATH", "BALA",
"CORN", "MOLUF", "DESP", "DAKED", "MIMI", "BOLUX", "KODA", "GELUD", "MONK", "SUMOY",
"GEDI", "WENDI", "NILEM", "NILE", "NILEC", "KEZI", "YONGL", "HUDE", "WANLI", "GELI",
"GUAIL", "MADAQ", "WUCI", "WUCI", "MUJEF", "JELLY", "SICIB", "GELU", "NELUO", "BOLI",
"JIALE", "YED", "YEDE", "CLO", "SCARE", "AOCO", "DEDE", "DEDEI", "BAWU", "JIUG", "BADEB",
"BADEB", "HOLE", "BALUX", "GES", "FANT", "QUAR", "YIHE", "SWAB", "SLIPP", "CLU", "DEPOS",
"BILIY", "YUANO", "SOME", "NO", "YELA", "EMPT", "ZECUN", "XIAHE", "BOLEL", "DEJI", "MACID",
"XIHON", "XITO", "LUCK", "MENJI", "GELU", "DECI", "XIDE", "DASAJ", "DONGN", "RICUL",
"MINXI", "BALIY", "ZENDA", "LUZEL", "HELE5", "0FENB", "KAIL", "JIAND", "CARP", "JINDE",
"LAPU", "MUDE", "YIFU", "LINLI", "SANDI", "HUSI", "JINC", "OUMU", "OUMUX", "CAP", "KUIZA",
"PUD", "TIAO", "FRMAN", "CLAU", "SPARK", "DRAGO", "BOLIU", "GUAIL", "MIYOU", "MIY",
"QIAOK", "BEIL", "MUKEI", "RIDED", "MADAM", "BAGEP", "CROC", "ALIGE", "OUDAL", "OUD",
"DADA", "HEHE", "YEDEA", "NUXI", "NUXIN", "ROUY", "ALIAD", "STICK", "QIANG", "LAAND",
"PIQI", "PI", "PUPI", "DEKE", "DEKEJ", "NADI", "NADIO", "MALI", "PEA", "ELECT", "FLOWE",
"MAL", "MALI", "HUSHU", "NILEE", "YUZI", "POPOZ", "DUZI", "HEBA", "XIAN", "SHAN", "YEYEA",
"WUY", "LUO", "KEFE", "HULA", "CROW", "YADEH", "MOW", "ANNAN", "SUONI", "KYLI", "HULU",
"HUDEL", "YEHE", "GULAE", "YEHE", "BLU", "GELAN", "BOAT", "NIP", "POIT", "HELAK", "XINL",
"BEAR", "LINB", "MAGEH", "MAGEJ", "WULI", "YIDE", "RIVE", "FISH", "AOGU", "DELIE", "MANTE",
"KONMU", "DELU", "HELU", "HUAN", "HUMA", "DONGF", "JINCA", "HEDE", "DEFU", "LIBY", "JIAPA",
"MEJI", "HELE", "BUHU", "MILK", "HABI", "THUN", "GARD", "DON", "YANGQ", "SANAQ", "BANQ",
"LUJ", "PHIX", "SIEI", "EGG",
]
}
/// Generate a new name based on [Pokemon Vietnamese Crystal](https://tvtropes.org/pmwiki/pmwiki.php/VideoGame/PokemonVietnameseCrystal)
///
/// ```rust
/// let name = elfs::next();
/// ```
pub fn next() -> String {
let mut generator = Generator::new(moves(), names(), Name::Numbered);
generator.next().unwrap()
}
#[cfg(test)]
#[test]
fn name() {
assert_ne!(next(), "".to_string());
}

View File

@ -1,23 +0,0 @@
[package]
name = "gitea"
version = "0.2.0"
authors = ["Christine Dodrill <me@christine.website>"]
edition = "2018"
homepage = "https://tulpa.dev/cadey/gitea-release/src/branch/main/gitea"
repository = "https://tulpa.dev/cadey/gitea-release"
keywords = ["gitea", "api", "http"]
license = "MIT"
readme = "README.md"
description = "A Gitea client for Rust programs."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1"
reqwest = { version = "0.10", features = ["json"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
[dev-dependencies]
anyhow = "1"
tokio = { version = "0.2", features = ["macros"] }

View File

@ -1,8 +0,0 @@
# gitea
A simple Gitea client for Rust programs. You will need an API token as described
[here](https://docs.gitea.io/en-us/api-usage/).
```toml
gitea = "0.1.0"
```

View File

@ -1,425 +0,0 @@
/// The main Gitea client. You will need an API token as described [here](https://docs.gitea.io/en-us/api-usage/).
use reqwest::header;
use serde::{Deserialize, Serialize};
use std::result::Result as StdResult;
use thiserror::Error;
/// Error represents all of the possible errors that can happen with the Gitea
/// API. Most of these errors boil down to user error.
#[derive(Error, Debug)]
pub enum Error {
#[error("error from reqwest: {0:#?}")]
Reqwest(#[from] reqwest::Error),
#[error("bad API token: {0:#?}")]
BadAPIToken(#[from] reqwest::header::InvalidHeaderValue),
#[error("error parsing/serializing json: {0:#?}")]
Json(#[from] serde_json::Error),
#[error("tag not found: {0}")]
TagNotFound(String),
}
/// A handy alias for Result like `anyhow::Result`.
pub type Result<T> = StdResult<T, Error>;
/// A repository release.
/// https://try.gitea.io/api/swagger#model-Release
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Release {
pub id: i64,
pub tag_name: String,
pub target_commitish: String,
pub name: String,
pub body: String,
pub url: String,
pub tarball_url: String,
pub zipball_url: String,
pub draft: bool,
pub prerelease: bool,
pub created_at: String,
pub published_at: String,
pub author: User,
pub assets: Vec<Attachment>,
}
/// The inputs to create a repository release.
/// https://try.gitea.io/api/swagger#model-CreateReleaseOption
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateRelease {
pub body: String,
pub draft: bool,
pub name: String,
pub prerelease: bool,
pub tag_name: String,
pub target_commitish: String,
}
/// An attachment to a release, such as a pre-built package.
/// https://try.gitea.io/api/swagger#model-Attachment
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attachment {
pub id: i64,
pub name: String,
pub size: i64,
pub download_count: i64,
pub created_at: String,
pub uuid: String,
pub browser_download_url: String,
}
/// Inputs to create a gitea repo.
/// https://try.gitea.io/api/swagger#model-CreateRepoOption
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateRepo {
pub auto_init: bool,
pub description: String,
pub gitignores: String,
pub issue_labels: String,
pub license: String,
pub name: String,
pub private: bool,
pub readme: String,
}
/// A git repository.
/// https://try.gitea.io/api/swagger#model-Repository
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Repo {
pub allow_merge_commits: bool,
pub allow_rebase: bool,
pub allow_rebase_explicit: bool,
pub allow_squash_merge: bool,
pub archived: bool,
pub avatar_url: String,
pub clone_url: String,
pub created_at: String,
pub default_branch: String,
pub description: String,
pub empty: bool,
pub fork: bool,
pub forks_count: i64,
pub full_name: String,
pub has_issues: bool,
pub has_pull_requests: bool,
pub has_wiki: bool,
pub html_url: String,
pub id: i64,
pub ignore_whitespace_conflicts: bool,
pub mirror: bool,
pub name: String,
pub open_issues_count: i64,
pub open_pr_counter: i64,
pub original_url: String,
pub owner: User,
pub permissions: Permissions,
pub private: bool,
pub release_counter: i64,
pub size: i64,
pub ssh_url: String,
pub stars_count: i64,
pub template: bool,
pub updated_at: String,
pub watchers_count: i64,
pub website: String,
}
/// A user.
/// https://try.gitea.io/api/swagger#model-User
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct User {
pub avatar_url: String,
pub created: String,
pub email: String,
pub full_name: String,
pub id: i64,
pub is_admin: bool,
pub language: String,
pub last_login: String,
pub login: String,
}
/// The permission set that a given user has on a Repo.
/// https://try.gitea.io/api/swagger#model-Permission
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Permissions {
pub admin: bool,
pub pull: bool,
pub push: bool,
}
/// The version of Gitea.
/// https://try.gitea.io/api/swagger#model-ServerVersion
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Version {
pub version: String,
}
/// The gitea client that all gitea calls will go through. This wraps
/// [reqwest::Client](https://docs.rs/reqwest/0.10.6/reqwest/struct.Client.html)
/// and operates asyncronously.
pub struct Client {
cli: reqwest::Client,
base_url: String,
}
impl Client {
/// Create a new API client with the given base URL, token and user agent.
/// If you need inspiration for a user agent, try this:
///
/// ```rust
/// const APP_USER_AGENT: &'static str =
/// concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
/// gitea::Client::new("https://tulpa.dev".into(), "ayylmao".into(), APP_USER_AGENT).unwrap();
/// ```
pub fn new<T>(base_url: String, token: String, user_agent: T) -> Result<Self>
where
T: Into<String>,
{
let mut headers = header::HeaderMap::new();
let auth = format!("token {}", token);
let auth = auth.as_str();
headers.insert(header::AUTHORIZATION, header::HeaderValue::from_str(auth)?);
let cli = reqwest::Client::builder()
.user_agent(user_agent.into())
.default_headers(headers)
.build()?;
Ok(Self {
cli: cli,
base_url: base_url,
})
}
/// Gets the current version of gitea.
///
/// ```rust
/// use gitea::Result;
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let cli = gitea::Client::new("https://tulpa.dev".into(), "ayylmao".into(), "test/test")?;
/// println!("{:?}", cli.version().await?);
/// Ok(())
/// }
/// ```
pub async fn version(&self) -> Result<Version> {
Ok(self
.cli
.get(&format!("{}/api/v1/version", self.base_url))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
/// Gets a release of a repo by its tag name.
///
/// ```rust
/// use gitea::Result;
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let cli = gitea::Client::new("https://tulpa.dev".into(), "ayylmao".into(), "test/test")?;
/// let release = cli.get_release_by_tag("cadey".into(), "gitea-release".into(), "0.3.2".into()).await;
/// Ok(())
/// }
/// ```
pub async fn get_release_by_tag(
&self,
owner: String,
repo: String,
tag: String,
) -> Result<Release> {
let releases: Vec<Release> = self.get_releases(owner, repo).await?;
let mut release: Option<Release> = None;
for rls in releases {
if *tag == rls.tag_name {
release = Some(rls);
}
}
match release {
None => Err(Error::TagNotFound(tag)),
Some(release) => Ok(release),
}
}
/// Creates a new gitea repo for the currently authenticated user with given details.
pub async fn create_user_repo(&self, cr: CreateRepo) -> Result<Repo> {
Ok(self
.cli
.post(&format!("{}/api/v1/user/repos", self.base_url))
.json(&cr)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
/// Creates a new gitea repo for a given organization with given details.
pub async fn create_org_repo(&self, org: String, cr: CreateRepo) -> Result<Repo> {
Ok(self
.cli
.post(&format!("{}/api/v1/org/{}/repos", self.base_url, org))
.json(&cr)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
/// Deletes a gitea repo by owner and name.
pub async fn delete_repo(&self, owner: String, repo: String) -> Result<()> {
self.cli
.delete(&format!(
"{}/api/v1/repos/{}/{}",
self.base_url, owner, repo
))
.send()
.await?
.error_for_status()?;
Ok(())
}
/// Gets a gitea repo by name.
///
/// ```rust
/// use gitea::Result;
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let cli = gitea::Client::new("https://tulpa.dev".into(), "ayylmao".into(), "test/test")?;
/// let repo = cli.get_repo("cadey".into(), "gitea-release".into()).await;
/// Ok(())
/// }
/// ```
pub async fn get_repo(&self, owner: String, repo: String) -> Result<Repo> {
Ok(self
.cli
.get(&format!(
"{}/api/v1/repos/{}/{}",
self.base_url, owner, repo
))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
/// Gets all of the releases for a given gitea repo.
///
/// ```rust
/// use gitea::Result;
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let cli = gitea::Client::new("https://tulpa.dev".into(), "ayylmao".into(), "test/test")?;
/// let repo = cli.get_releases("cadey".into(), "gitea-release".into()).await;
/// Ok(())
/// }
/// ```
pub async fn get_releases(&self, owner: String, repo: String) -> Result<Vec<Release>> {
Ok(self
.cli
.get(&format!(
"{}/api/v1/repos/{}/{}/releases",
self.base_url, owner, repo
))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
/// Creates a new gitea release.
///
/// ```rust
/// use gitea::{CreateRelease, Result};
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let cli = gitea::Client::new("https://tulpa.dev".into(), "ayylmao".into(), "test/test")?;
/// let repo = cli.create_release(
/// "cadey".into(),
/// "gitea-release".into(),
/// CreateRelease{
/// body: "This is a cool release".into(),
/// draft: false,
/// name: "test".into(),
/// prerelease: false,
/// tag_name: "v4.2.0".into(),
/// target_commitish: "HEAD".into(),
/// },
/// ).await;
/// Ok(())
/// }
/// ```
pub async fn create_release(
&self,
owner: String,
repo: String,
cr: CreateRelease,
) -> Result<Release> {
Ok(self
.cli
.post(&format!(
"{}/api/v1/repos/{}/{}/releases",
self.base_url, owner, repo
))
.json(&cr)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
/// Deletes a given release by tag name.
///
/// ```rust
/// use gitea::Result;
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let cli = gitea::Client::new("https://tulpa.dev".into(), "ayylmao".into(), "test/test")?;
/// let _ = cli.delete_release("cadey".into(), "gitea-release".into(), "4.2.0".into()).await;
/// Ok(())
/// }
/// ```
pub async fn delete_release(&self, owner: String, repo: String, tag: String) -> Result<()> {
let release = self
.get_release_by_tag(owner.clone(), repo.clone(), tag)
.await?;
self.cli
.delete(&format!(
"{}/api/v1/repos/{}/{}/releases/{}",
self.base_url, owner, repo, release.id
))
.send()
.await?
.error_for_status()?;
Ok(())
}
/// Returns information about the currently authenticated user.
pub async fn whoami(&self) -> Result<User> {
Ok(self
.cli
.get(&format!("{}/api/v1/user", self.base_url))
.send()
.await?
.error_for_status()?
.json()
.await?)
}
}

View File

@ -1,16 +0,0 @@
use anyhow::Result;
use gitea::Client;
#[tokio::test]
async fn version() -> Result<()> {
let cli = Client::new(
std::env::var("GITEA_SERVER")?,
std::env::var("DOMO_GITEA_TOKEN")?,
"gitea/tests",
)?;
let vers = cli.version().await?;
println!("gitea version {}", vers.version);
Ok(())
}

View File

@ -1,26 +1,14 @@
{ {
"naersk": {
"branch": "master",
"description": "Build rust crates in Nix. No configuration, no code generation, no IFD. Sandbox friendly.",
"homepage": "",
"owner": "nmattia",
"repo": "naersk",
"rev": "529e910a3f423a8211f8739290014b754b2555b6",
"sha256": "0bcy9nmyaan5jvp0wg80wkizc9j166ns685rdr1kbhkvdpywv46y",
"type": "tarball",
"url": "https://github.com/nmattia/naersk/archive/529e910a3f423a8211f8739290014b754b2555b6.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs-mozilla": { "nixpkgs-mozilla": {
"branch": "master", "branch": "master",
"description": "mozilla related nixpkgs (extends nixos/nixpkgs repo)", "description": "mozilla related nixpkgs (extends nixos/nixpkgs repo)",
"homepage": null, "homepage": null,
"owner": "mozilla", "owner": "mozilla",
"repo": "nixpkgs-mozilla", "repo": "nixpkgs-mozilla",
"rev": "efda5b357451dbb0431f983cca679ae3cd9b9829", "rev": "e912ed483e980dfb4666ae0ed17845c4220e5e7c",
"sha256": "11wqrg86g3qva67vnk81ynvqyfj0zxk83cbrf0p9hsvxiwxs8469", "sha256": "08fvzb8w80bkkabc1iyhzd15f4sm7ra10jn32kfch5klgl0gj3j3",
"type": "tarball", "type": "tarball",
"url": "https://github.com/mozilla/nixpkgs-mozilla/archive/efda5b357451dbb0431f983cca679ae3cd9b9829.tar.gz", "url": "https://github.com/mozilla/nixpkgs-mozilla/archive/e912ed483e980dfb4666ae0ed17845c4220e5e7c.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz" "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
} }
} }

View File

@ -4,9 +4,7 @@ let
in pkgs.mkShell { in pkgs.mkShell {
buildInputs = with pkgs; [ buildInputs = with pkgs; [
latest.rustChannels.stable.rust latest.rustChannels.stable.rust
cargo-watch
openssl openssl
pkg-config pkg-config
libgit2
]; ];
} }

View File

@ -1,6 +1,7 @@
use anyhow::Result; use anyhow::Result;
use comrak::nodes::{AstNode, NodeValue}; use comrak::nodes::{AstNode, NodeValue};
use comrak::{format_commonmark, parse_document, Arena, ComrakOptions}; use comrak::{format_commonmark, parse_document, Arena, ComrakOptions};
use std::cell::RefCell;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::path::PathBuf; use std::path::PathBuf;
@ -9,35 +10,41 @@ pub(crate) fn read(fname: PathBuf, tag: String) -> Result<String> {
let arena = Arena::new(); let arena = Arena::new();
let mut root = parse_document(&arena, &data, &ComrakOptions::default()); let mut root = parse_document(&arena, &data, &ComrakOptions::default());
let mut collect = false; let collect = RefCell::new(false);
let mut buf = Vec::<u8>::new(); let buf = RefCell::new(Vec::<u8>::new());
iter_nodes(&mut root, &mut |node| { iter_nodes(&mut root, &|node| {
let nd = node.data.borrow(); let nd = node.data.borrow();
match nd.value { match nd.value {
NodeValue::Heading(ref hdr) => { NodeValue::Heading(ref hdr) => {
if hdr.level == 2 { if hdr.level == 2 {
if collect { if *collect.borrow() {
collect = false; collect.swap(&RefCell::new(false));
} }
let found_tag = String::from_utf8(nd.content.clone())?; let found_tag = String::from_utf8(nd.content.clone())?;
if found_tag == tag || found_tag == format!("[{}]", tag) { if found_tag == tag {
collect = true; collect.swap(&RefCell::new(true));
} }
} else { } else {
if collect { if *collect.borrow() {
format_commonmark(&node, &ComrakOptions::default(), &mut buf)?; let mut apd = buf.borrow_mut();
let mut new_buf = Vec::<u8>::new();
format_commonmark(&node, &ComrakOptions::default(), &mut new_buf)?;
apd.append(&mut new_buf);
} }
} }
Ok(()) Ok(())
} }
NodeValue::Item(_) => Ok(()), NodeValue::Item(_) => Ok(()),
_ => { _ => {
if collect { if *collect.borrow() {
format_commonmark(&node, &ComrakOptions::default(), &mut buf)?; let mut apd = buf.borrow_mut();
let mut new_buf = Vec::<u8>::new();
format_commonmark(&node, &ComrakOptions::default(), &mut new_buf)?;
apd.append(&mut new_buf);
} }
Ok(()) Ok(())
@ -45,12 +52,12 @@ pub(crate) fn read(fname: PathBuf, tag: String) -> Result<String> {
} }
})?; })?;
Ok(String::from_utf8(buf)?) Ok(String::from_utf8(buf.into_inner())?)
} }
fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &mut F) -> Result<()> fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &F) -> Result<()>
where where
F: FnMut(&'a AstNode<'a>) -> Result<()>, F: Fn(&'a AstNode<'a>) -> Result<()>,
{ {
f(node)?; f(node)?;
for c in node.children() { for c in node.children() {
@ -70,24 +77,10 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[test] #[test]
fn basic() { fn read_changelog() {
let res = super::read("testdata/basic.md".into(), "0.1.0".into()); let res = super::read("testdata/basic.md".into(), "0.1.0".into());
assert!(res.is_ok()); assert!(res.is_ok());
let delta = res.unwrap(); let delta = res.unwrap();
assert_eq!( assert_eq!(delta, "Hi there this is a test\\!\n### ADDED\n - something\n")
delta,
"Hi there this is a test\\!\n### ADDED\n - something\n"
)
}
#[test]
fn brackets() {
let res = super::read("testdata/brackets.md".into(), "0.1.0".into());
assert!(res.is_ok());
let delta = res.unwrap();
assert_eq!(
delta,
"Hi there this is a test\\!\n### ADDED\n - something\n"
)
} }
} }

View File

@ -1,40 +0,0 @@
use crate::{changelog, cmd::*, git, version};
use anyhow::Result;
use std::path::PathBuf;
pub async fn run(
common: Common,
fname: PathBuf,
tag: Option<String>,
rm: ReleaseMeta,
) -> Result<()> {
let repo = git2::Repository::open(".")?;
let tag = tag.unwrap_or(version::read("VERSION".into())?);
let vtag = format!("v{}", tag);
if git::has_tag(&repo, vtag.clone())? {
println!("release {} already released", vtag);
return Ok(());
}
let desc = changelog::read(fname, tag.clone())?;
let cli = gitea::Client::new(common.server, common.token, crate::APP_USER_AGENT)?;
let repo = cli
.get_repo(common.owner.clone(), common.repo.clone())
.await?;
let cr = gitea::CreateRelease {
body: desc,
draft: rm.draft,
name: rm.name.or(Some(format!("Version {}", tag))).unwrap(),
prerelease: rm.pre_release,
tag_name: vtag,
target_commitish: repo.default_branch,
};
let _ = cli.create_release(common.owner, common.repo, cr).await?;
println!("Created release {}", tag);
Ok(())
}

25
src/cmd/delete.rs Normal file
View File

@ -0,0 +1,25 @@
use crate::{gitea::*, *};
use anyhow::{anyhow, Result};
pub(crate) async fn run(common: Common, tag: String) -> Result<()> {
let cli = client(&common)?;
let release =
get_release_by_tag(&cli, &common.server, &common.owner, &common.repo, &tag).await?;
let resp = cli
.delete(
format!(
"{}/api/v1/repos/{}/{}/releases/{}",
&common.server, &common.owner, &common.repo, release.id
)
.as_str(),
)
.send()
.await?;
if resp.status() != http::StatusCode::from_u16(204)? {
Err(anyhow!("wanted 204, got {}", resp.status()))
} else {
Ok(())
}
}

59
src/cmd/download.rs Normal file
View File

@ -0,0 +1,59 @@
use crate::{gitea::*, *};
use anyhow::{anyhow, Result};
use cli_table::{Cell, Row, Table};
use std::fs::File;
use std::io::Write;
pub(crate) async fn run(common: Common, fname: Option<PathBuf>, tag: String) -> Result<()> {
let cli = client(&common)?;
let release =
get_release_by_tag(&cli, &common.server, &common.owner, &common.repo, &tag).await?;
let attachments = get_attachments_for_release(
&cli,
&common.server,
&common.owner,
&common.repo,
&release.id,
)
.await?;
match fname {
None => {
let mut rows: Vec<Row> = vec![Row::new(vec![
Cell::new(&"name", Default::default()),
Cell::new(&"size", Default::default()),
Cell::new(&"url", Default::default()),
])];
for attachment in attachments {
rows.push(attachment.row())
}
let table = Table::new(rows, Default::default())?;
table.print_stdout()?;
Ok(())
}
Some(fname) => {
let mut url: Option<String> = None;
let fname = fname.into_os_string().into_string().unwrap();
for attachment in attachments {
if &fname == &attachment.name {
url = Some(attachment.browser_download_url);
}
}
if url.is_none() {
return Err(anyhow!("no attachment named {}", fname));
}
let data = &cli.get(url.unwrap().as_str()).send().await?.bytes().await?;
let mut fout = File::create(&fname)?;
fout.write(data)?;
Ok(())
}
}
}

View File

@ -1,46 +0,0 @@
use crate::cmd::*;
use anyhow::Result;
use git2::Repository;
use url::Url;
pub async fn run(env: DroneEnv) -> Result<()> {
let common: Common = env.clone().into();
let default_branch = {
let common = common.clone();
match &env.default_branch {
None => {
let cli =
gitea::Client::new(common.server, common.token, crate::APP_USER_AGENT)?;
let repo = cli.get_repo(common.owner, common.repo).await?;
repo.default_branch
}
Some(branch) => branch.to_string(),
}
};
if env.branch != default_branch {
return Ok(());
}
let repo = Repository::open(".")?;
let mut u = Url::parse(&env.push_url)?;
u.set_username(&env.auth_user).unwrap();
u.set_password(Some(&env.token)).unwrap();
repo.remote_delete("origin")?;
let mut origin = repo.remote("origin", u.as_str())?;
origin.connect(git2::Direction::Fetch)?;
origin.fetch(&["refs/tags/*:refs/tags/*"], None, None)?;
cut::run(
common,
env.changelog_path,
None,
ReleaseMeta {
name: None,
draft: false,
pre_release: false,
},
)
.await
}

50
src/cmd/edit.rs Normal file
View File

@ -0,0 +1,50 @@
use crate::{gitea::*, *};
use anyhow::{anyhow, Result};
pub(crate) async fn run(
common: Common,
description: Option<String>,
rm: ReleaseMeta,
tag: String,
) -> Result<()> {
let cli = client(&common)?;
let release =
get_release_by_tag(&cli, &common.server, &common.owner, &common.repo, &tag).await?;
let mut cr = CreateRelease {
body: release.body,
draft: release.draft,
name: release.name,
prerelease: release.prerelease,
tag_name: release.tag_name,
target_commitish: release.target_commitish,
};
if let Some(description) = description {
cr.body = description;
}
if let Some(name) = rm.name {
cr.name = name;
}
cr.draft = rm.draft;
cr.prerelease = rm.pre_release;
let resp = cli
.post(
&format!(
"{}/api/v1/repos/{}/{}/releases/{}",
common.server, common.owner, common.repo, release.id
),
)
.json(&cr)
.send()
.await?;
if !resp.status().is_success() {
return Err(anyhow!("{:?}", resp.status()));
}
Ok(())
}

97
src/cmd/info.rs Normal file
View File

@ -0,0 +1,97 @@
use crate::{gitea::*, *};
use anyhow::{anyhow, Result};
use cli_table::{Cell, Row, Table};
pub(crate) async fn run(common: Common, json: bool, tag: Option<String>) -> Result<()> {
let cli = client(&common)?;
let releases: Vec<Release> = cli
.get(
format!(
"{}/api/v1/repos/{}/{}/releases",
&common.server, &common.owner, &common.repo
)
.as_str(),
)
.send()
.await?
.json()
.await?;
match tag {
Some(tag) => {
let mut release: Option<Release> = None;
for rls in releases {
if tag == rls.tag_name {
release = Some(rls);
}
}
if release.is_none() {
return Err(anyhow!("tag {} not found", tag));
}
if json {
println!("{}", serde_json::to_string_pretty(&release)?);
} else {
let rls = release.unwrap();
let table = Table::new(
vec![
Row::new(vec![
Cell::new(&"id", Default::default()),
Cell::new(&rls.id, Default::default()),
]),
Row::new(vec![
Cell::new(&"author", Default::default()),
Cell::new(
&format!("{} - {}", rls.author.full_name, rls.author.username),
Default::default(),
),
]),
Row::new(vec![
Cell::new(&"tag", Default::default()),
Cell::new(&rls.tag_name, Default::default()),
]),
Row::new(vec![
Cell::new(&"created at", Default::default()),
Cell::new(&rls.created_at, Default::default()),
]),
Row::new(vec![
Cell::new(&"name", Default::default()),
Cell::new(&rls.name, Default::default()),
]),
Row::new(vec![
Cell::new(&"body", Default::default()),
Cell::new(&rls.body, Default::default()),
]),
],
Default::default(),
)?;
table.print_stdout()?;
}
}
None => {
if json {
println!("{}", serde_json::to_string_pretty(&releases)?);
} else {
let mut rows: Vec<Row> = vec![Row::new(vec![
Cell::new(&"id", Default::default()),
Cell::new(&"tag", Default::default()),
Cell::new(&"created at", Default::default()),
Cell::new(&"commit", Default::default()),
Cell::new(&"author", Default::default()),
Cell::new(&"name", Default::default()),
])];
for release in releases {
rows.push(release.row())
}
let table = Table::new(rows, Default::default())?;
table.print_stdout()?;
}
}
}
Ok(())
}

View File

@ -1,136 +1,6 @@
use std::path::PathBuf; pub(crate) mod delete;
use structopt::StructOpt; pub(crate) mod download;
pub(crate) mod edit;
pub mod cut; pub(crate) mod info;
pub mod drone_plugin; pub(crate) mod release;
pub(crate) mod upload;
#[derive(StructOpt, Debug, Clone)]
pub struct Common {
/// The gitea server to connect to
#[structopt(short, long, env = "GITEA_SERVER")]
pub server: String,
/// The gitea token to authenticate with
#[structopt(long, env = "GITEA_TOKEN")]
pub token: String,
/// The gitea user to authenticate as
#[structopt(short, long, env = "GITEA_AUTH_USER")]
pub auth_user: String,
/// The owner of the gitea repo
#[structopt(short, long, env = "GITEA_OWNER")]
pub owner: String,
/// The gitea repo to operate on
#[structopt(short, long, env = "GITEA_REPO")]
pub repo: String,
/// Git signature email
#[structopt(long, short = "E", env = "SIGNATURE_EMAIL", default_value = "domo@tulpa.dev")]
pub email: String,
/// Git signature username
#[structopt(long, short = "U", env = "SIGNATURE_NAME", default_value = "Domo Arigato")]
pub username: String,
}
#[derive(StructOpt, Debug, Clone)]
pub struct DroneEnv {
// Given by drone
/// push URL
#[structopt(long, env = "DRONE_GIT_HTTP_URL")]
pub push_url: String,
/// repo owner
#[structopt(long, env = "DRONE_REPO_OWNER")]
pub owner: String,
/// repo name
#[structopt(long, env = "DRONE_REPO_NAME")]
pub repo: String,
/// branch
#[structopt(long, env = "DRONE_REPO_BRANCH")]
pub branch: String,
// Given by the user
/// auth username
#[structopt(long, env = "PLUGIN_AUTH_USERNAME")]
pub auth_user: String,
/// Gitea server
#[structopt(long, env = "PLUGIN_GITEA_SERVER")]
pub server: String,
/// Gitea token
#[structopt(long, env = "PLUGIN_GITEA_TOKEN")]
pub token: String,
/// CHANGELOG path
#[structopt(long, env = "PLUGIN_CHANGELOG_PATH", default_value = "./CHANGELOG.md")]
pub changelog_path: PathBuf,
/// Default branch name
#[structopt(long, env = "PLUGIN_DEFAULT_BRANCH")]
pub default_branch: Option<String>,
/// Git signature email
#[structopt(long, short = "E", env = "PLUGIN_SIGNATURE_EMAIL", default_value = "domo@tulpa.dev")]
pub email: String,
/// Git signature username
#[structopt(long, short = "U", env = "PLUGIN_SIGNATURE_NAME", default_value = "Domo Arigato")]
pub username: String,
}
impl Into<Common> for DroneEnv {
fn into(self) -> Common {
Common {
server: self.server,
token: self.token,
auth_user: self.auth_user,
owner: self.owner,
repo: self.repo,
email: self.email,
username: self.username,
}
}
}
#[derive(StructOpt, Debug)]
pub struct ReleaseMeta {
/// Release name
#[structopt(short, long)]
pub name: Option<String>,
/// Draft release
#[structopt(long)]
pub draft: bool,
/// Pre-release (not suitable for production)
#[structopt(short, long)]
pub pre_release: bool,
}
#[derive(StructOpt, Debug)]
#[structopt(about = "Gitea release assistant")]
pub enum Cmd {
/// Create a new tag and release on Gitea
#[structopt(alias = "release")]
Cut {
#[structopt(flatten)]
common: Common,
/// Changelog file to read from to create the release description
#[structopt(short, long, default_value = "./CHANGELOG.md")]
changelog: PathBuf,
/// The version tag to operate on
tag: Option<String>,
#[structopt(flatten)]
release_meta: ReleaseMeta,
},
/// Runs the release process as a drone plugin
DronePlugin {
#[structopt(flatten)]
env: DroneEnv,
},
}

45
src/cmd/release.rs Normal file
View File

@ -0,0 +1,45 @@
use crate::{gitea::*, *};
use anyhow::Result;
use std::path::PathBuf;
pub(crate) async fn run(
common: Common,
fname: PathBuf,
tag: Option<String>,
rm: ReleaseMeta,
) -> Result<()> {
let repo = git2::Repository::open(".")?;
let tag = tag.unwrap_or(version::read_version("VERSION".into())?);
let desc = changelog::read(fname.clone(), tag.clone())?;
if !git::has_tag(&repo, tag.clone())? {
git::tag_version(&repo, tag.clone(), desc.clone())?;
let _ = git::push_tags(&repo);
}
let desc = changelog::read(fname, tag.clone())?;
let cli = client(&common)?;
let cr = CreateRelease {
body: desc,
draft: rm.draft,
name: rm.name.or(Some(format!("Version {}", tag))).unwrap(),
prerelease: rm.pre_release,
tag_name: tag.clone(),
target_commitish: tag,
};
let resp = cli
.post(&format!(
"{}/api/v1/repos/{}/{}/releases",
common.server, common.owner, common.repo
))
.json(&cr)
.send()
.await?;
if !resp.status().is_success() {
return Err(anyhow!("{:?}", resp.status()));
}
Ok(())
}

32
src/cmd/upload.rs Normal file
View File

@ -0,0 +1,32 @@
use crate::{gitea::*, *};
use anyhow::Result;
use reqwest::multipart;
use std::fs::File;
use std::io::Read;
pub(crate) async fn run(common: Common, fname: PathBuf, tag: String) -> Result<()> {
let cli = client(&common)?;
let bytes = {
let mut fin = File::open(&fname)?;
let mut buffer = Vec::new();
fin.read_to_end(&mut buffer)?;
buffer
};
let form = multipart::Form::new().part("attachment", multipart::Part::bytes(bytes));
let release =
get_release_by_tag(&cli, &common.server, &common.owner, &common.repo, &tag).await?;
cli.post(
format!(
"{}/api/v1/repos/{}/{}/releases/{}",
&common.server, &common.owner, &common.repo, release.id,
)
.as_str(),
)
.query(&[("name", fname)])
.multipart(form)
.send()
.await?;
Ok(())
}

View File

@ -1,67 +1,22 @@
use anyhow::Result; use anyhow::Result;
use git2::{ use git2::{Repository, Signature};
Cred, CredentialType, Direction, FetchOptions, PushOptions, RemoteCallbacks, Repository,
Signature,
};
use std::path::Path;
pub const TAGS: &'static [&'static str] = &["refs/tags/*:refs/tags/*"]; pub(crate) fn push_tags(repo: &Repository) -> Result<()> {
pub fn pull(repo: &Repository, token: String, what: &[&str]) -> Result<()> {
let mut remote = repo.find_remote("origin")?; let mut remote = repo.find_remote("origin")?;
let mut fo = FetchOptions::new(); remote.connect(git2::Direction::Push)?;
remote.push(&["refs/tags/*:refs/tags/*"], None)?;
remote.connect_auth(Direction::Fetch, Some(callbacks(token.clone())), None)?;
fo.remote_callbacks(callbacks(token));
remote.fetch(what, Some(&mut fo), None)?;
Ok(()) Ok(())
} }
fn callbacks<'a>(token: String) -> RemoteCallbacks<'a> { pub(crate) fn tag_version(repo: &Repository, tag: String, desc: String) -> Result<()> {
let mut callbacks = RemoteCallbacks::new(); let sig = &Signature::now("Gitea Release Tool", "gitea-release@tulpa.dev")?;
callbacks.credentials(move |_u, _username, allowed_types| {
if allowed_types.contains(CredentialType::SSH_KEY) {
let user = "git";
Cred::ssh_key_from_agent(user)
} else {
Cred::userpass_plaintext(&token, "x-oauth-basic")
}
});
callbacks
}
pub fn push(repo: &Repository, token: String, what: &[&str]) -> Result<()> {
let mut remote = repo.find_remote("origin")?;
remote.connect_auth(Direction::Push, Some(callbacks(token.clone())), None)?;
let mut po = PushOptions::new();
po.remote_callbacks(callbacks(token));
remote.push(what, Some(&mut po))?;
Ok(())
}
pub fn commit(repo: &Repository, sig: &Signature, msg: &str, files: &[&str]) -> Result<()> {
let mut index = repo.index()?;
for file in files {
index.add_path(Path::new(file))?;
}
let oid = index.write_tree()?;
let tree = repo.find_tree(oid)?;
repo.commit(Some("HEAD"), &sig, &sig, &msg, &tree, &[])?;
Ok(())
}
pub fn tag_version(repo: &Repository, sig: &Signature, tag: String, desc: String) -> Result<()> {
let obj = repo.revparse_single("HEAD")?; let obj = repo.revparse_single("HEAD")?;
repo.tag(&tag, &obj, &sig, &desc, false)?; repo.tag(&tag, &obj, &sig, &desc, false)?;
Ok(()) Ok(())
} }
pub fn has_tag(repo: &Repository, tag: String) -> Result<bool> { pub(crate) fn has_tag(repo: &Repository, tag: String) -> Result<bool> {
let tags = repo.tag_names(Some(&tag))?; let tags = repo.tag_names(Some(&tag))?;
for tag_obj in tags.iter() { for tag_obj in tags.iter() {
@ -70,7 +25,6 @@ pub fn has_tag(repo: &Repository, tag: String) -> Result<bool> {
} }
let tag_name = tag_obj.unwrap(); let tag_name = tag_obj.unwrap();
log::debug!("tag: {}", tag_name.to_string());
if tag == tag_name.to_string() { if tag == tag_name.to_string() {
return Ok(true); return Ok(true);
} }
@ -83,7 +37,7 @@ pub fn has_tag(repo: &Repository, tag: String) -> Result<bool> {
mod tests { mod tests {
use anyhow::Result; use anyhow::Result;
use git2::*; use git2::*;
use std::{fs::File, io::Write}; use std::{fs::File, io::Write, path::Path};
use tempfile::tempdir; use tempfile::tempdir;
#[test] #[test]
@ -94,10 +48,22 @@ mod tests {
let mut fout = File::create(&dir.path().join("VERSION"))?; let mut fout = File::create(&dir.path().join("VERSION"))?;
write!(fout, "{}", TAG)?; write!(fout, "{}", TAG)?;
drop(fout); drop(fout);
let mut index = repo.index()?;
index.add_path(Path::new("VERSION"))?;
let oid = index.write_tree()?;
let tree = repo.find_tree(oid)?;
let sig = &Signature::now("Gitea Release Tool", "gitea-release@tulpa.dev")?; let sig = &Signature::now("Gitea Release Tool", "gitea-release@tulpa.dev")?;
super::commit(&repo, &sig, "test commit please ignore", &["VERSION"])?; repo.commit(
super::tag_version(&repo, &sig, TAG.into(), format!("version {}", TAG))?; Some("HEAD"),
&sig,
&sig,
"test commit please ignore",
&tree,
&[],
)?;
super::tag_version(&repo, TAG.into(), format!("version {}", TAG))?;
assert!(super::has_tag(&repo, TAG.into())?); assert!(super::has_tag(&repo, TAG.into())?);
Ok(()) Ok(())

137
src/gitea.rs Normal file
View File

@ -0,0 +1,137 @@
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Author {
pub id: i64,
pub login: String,
pub full_name: String,
pub email: String,
pub avatar_url: String,
pub language: String,
pub is_admin: bool,
pub last_login: String,
pub created: String,
pub username: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Release {
pub id: i64,
pub tag_name: String,
pub target_commitish: String,
pub name: String,
pub body: String,
pub url: String,
pub tarball_url: String,
pub zipball_url: String,
pub draft: bool,
pub prerelease: bool,
pub created_at: String,
pub published_at: String,
pub author: Author,
pub assets: Vec<Attachment>,
}
use cli_table::{Cell, Row};
impl Release {
pub fn row(&self) -> Row {
Row::new(vec![
Cell::new(&format!("{}", self.id), Default::default()),
Cell::new(&self.tag_name, Default::default()),
Cell::new(&self.created_at, Default::default()),
Cell::new(&self.target_commitish, Default::default()),
Cell::new(&self.author.username, Default::default()),
Cell::new(&self.name, Default::default()),
])
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateRelease {
pub body: String,
pub draft: bool,
pub name: String,
pub prerelease: bool,
pub tag_name: String,
pub target_commitish: String,
}
pub(crate) async fn get_release_by_tag(
cli: &reqwest::Client,
server: &String,
owner: &String,
repo: &String,
tag: &String,
) -> Result<Release> {
let releases: Vec<Release> = cli
.get(&format!(
"{}/api/v1/repos/{}/{}/releases",
server, owner, repo
))
.send()
.await?
.json()
.await?;
let mut release: Option<Release> = None;
for rls in releases {
if *tag == rls.tag_name {
release = Some(rls);
}
}
if release.is_none() {
return Err(anyhow!("tag {} not found", tag));
}
Ok(release.unwrap())
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attachment {
pub id: i64,
pub name: String,
pub size: i64,
pub download_count: i64,
pub created_at: String,
pub uuid: String,
pub browser_download_url: String,
}
impl Attachment {
pub fn row(&self) -> Row {
let size = {
let bytes = byte_unit::Byte::from_bytes(self.size as u128);
let unit = bytes.get_appropriate_unit(false);
unit.to_string()
};
Row::new(vec![
Cell::new(&self.name, Default::default()),
Cell::new(&size, Default::default()),
Cell::new(&self.browser_download_url, Default::default()),
])
}
}
pub(crate) async fn get_attachments_for_release(
cli: &reqwest::Client,
server: &String,
owner: &String,
repo: &String,
id: &i64,
) -> Result<Vec<Attachment>> {
let attachments: Vec<Attachment> = cli
.get(&format!(
"{}/api/v1/repos/{}/{}/releases/{}/assets",
server, owner, repo, id
))
.send()
.await?
.json()
.await?;
Ok(attachments)
}

View File

@ -1,8 +0,0 @@
pub mod changelog;
pub mod cmd;
pub mod git;
pub mod version;
// Name your user agent after your app?
pub static APP_USER_AGENT: &str =
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));

View File

@ -1,21 +1,148 @@
use anyhow::Result; use anyhow::{anyhow, Result};
use reqwest::{header, Client};
use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
use ::gitea_release::{cmd::{self, Cmd}}; mod changelog;
mod cmd;
mod git;
mod gitea;
mod version;
#[derive(StructOpt, Debug)]
pub(crate) struct Common {
/// The gitea server to connect to
#[structopt(short, long, env = "GITEA_SERVER")]
server: String,
/// The gitea token to authenticate with
#[structopt(long, env = "GITEA_TOKEN")]
token: String,
/// The gitea user to authenticate as
#[structopt(short, long, env = "GITEA_AUTH_USER")]
auth_user: String,
/// The owner of the gitea repo
#[structopt(short, long, env = "GITEA_OWNER")]
owner: String,
/// The gitea repo to operate on
#[structopt(short, long, env = "GITEA_REPO")]
repo: String,
}
// Name your user agent after your app?
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
pub(crate) fn client(c: &Common) -> Result<Client> {
let mut headers = header::HeaderMap::new();
let auth = format!("token {}", &c.token);
let auth = auth.as_str();
headers.insert(header::AUTHORIZATION, header::HeaderValue::from_str(auth)?);
Ok(Client::builder()
.user_agent(APP_USER_AGENT)
.default_headers(headers)
.build()?)
}
#[derive(StructOpt, Debug)]
pub(crate) struct ReleaseMeta {
/// Release name
#[structopt(short, long)]
name: Option<String>,
/// Draft release
#[structopt(long)]
draft: bool,
/// Pre-release (not suitable for production)
#[structopt(short, long)]
pre_release: bool,
}
#[derive(StructOpt, Debug)]
#[structopt(about = "Gitea release assistant")]
pub(crate) enum Cmd {
/// Delete a given release from Gitea
Delete {
#[structopt(flatten)]
common: Common,
/// The version tag to operate on
#[structopt(short, long)]
tag: String,
},
/// Downloads release artifacts
Download {
#[structopt(flatten)]
common: Common,
/// File to download
fname: Option<PathBuf>,
/// The version tag to operate on
#[structopt(short, long)]
tag: String,
},
/// Edits a release's description, name and other flags
Edit {
#[structopt(flatten)]
common: Common,
/// Release description
#[structopt(short, long)]
description: Option<String>,
#[structopt(flatten)]
release_meta: ReleaseMeta,
/// The version tag to operate on
tag: String,
},
/// Gets release info
Info {
#[structopt(flatten)]
common: Common,
#[structopt(long, short)]
json: bool,
/// The version tag to operate on
#[structopt(short, long)]
tag: Option<String>,
},
/// Create a new tag and release on Gitea
Release {
#[structopt(flatten)]
common: Common,
/// Changelog file to read from to create the release description
#[structopt(short, long, default_value = "./CHANGELOG.md")]
changelog: PathBuf,
/// The version tag to operate on
tag: Option<String>,
#[structopt(flatten)]
release_meta: ReleaseMeta,
},
/// Uploads release artifacts to Gitea
Upload {
#[structopt(flatten)]
common: Common,
/// The version tag to operate on
#[structopt(short, long)]
tag: String,
/// The location of the file on the disk
fname: PathBuf,
},
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let _ = kankyo::init(); let _ = kankyo::init();
pretty_env_logger::init();
let cmd = Cmd::from_args(); let cmd = Cmd::from_args();
match cmd { match cmd {
Cmd::Cut { Cmd::Delete { common, tag } => cmd::delete::run(common, tag).await,
Cmd::Download { common, fname, tag } => cmd::download::run(common, fname, tag).await,
Cmd::Edit {
common,
description,
release_meta,
tag,
} => cmd::edit::run(common, description, release_meta, tag).await,
Cmd::Info { common, json, tag } => cmd::info::run(common, json, tag).await,
Cmd::Release {
common, common,
changelog, changelog,
tag, tag,
release_meta, release_meta,
} => cmd::cut::run(common, changelog, tag, release_meta).await, } => cmd::release::run(common, changelog, tag, release_meta).await,
Cmd::DronePlugin { env } => cmd::drone_plugin::run(env).await, Cmd::Upload { common, fname, tag } => cmd::upload::run(common, fname, tag).await,
} }
} }

16
src/version.rs Normal file
View File

@ -0,0 +1,16 @@
use anyhow::Result;
use std::{fs, path::PathBuf};
pub(crate) fn read_version(fname: PathBuf) -> Result<String> {
let version = fs::read_to_string(fname)?;
Ok(version.trim().into())
}
#[cfg(test)]
mod tests {
#[test]
fn read_version() {
let version = super::read_version("./testdata/VERSION".into()).unwrap();
assert_eq!(version, "0.1.0");
}
}

View File

@ -1,53 +0,0 @@
use anyhow::Result;
use cargo_toml::Manifest;
use std::fs::{self, File};
use std::io::Read;
fn get_file_as_byte_vec(filename: &str) -> Option<Vec<u8>> {
let f = File::open(&filename);
if f.is_err() {
log::debug!("can't read from Cargo.toml: {:?}", f.unwrap_err());
return None;
}
let mut f = f.unwrap();
let metadata = fs::metadata(&filename).expect("unable to read metadata");
let mut buffer = vec![0; metadata.len() as usize];
f.read(&mut buffer).expect("buffer overflow");
Some(buffer)
}
pub fn read() -> Result<Option<String>> {
log::debug!("reading version from Cargo.toml");
let bytes = get_file_as_byte_vec("Cargo.toml");
log::debug!("{:?}", bytes);
match bytes {
Some(bytes) => {
log::trace!("reading toml");
let pkg : Result<Manifest, _> = toml::from_slice(&bytes);
match pkg {
Err(why) => {
log::error!("error parsing Cargo.toml: {:?}", why);
Err(why.into())
}
Ok(pkg) => {
let version = pkg.package.unwrap().version;
log::trace!("got version {}", version);
Ok(Some(version))
}
}
}
None => Ok(None)
}
}
#[cfg(test)]
mod tests {
#[test]
fn read() {
use super::read;
let _ = pretty_env_logger::try_init();
read().unwrap().unwrap();
}
}

View File

@ -1,30 +0,0 @@
use anyhow::Result;
use std::{fs, path::PathBuf};
mod cargo;
pub(crate) fn read(fname: PathBuf) -> Result<String> {
let version = match read_fs(fname.clone()) {
Ok(version) => version,
Err(why) => {
log::debug!("can't read {:?}: {:?}", fname, why);
cargo::read().unwrap().unwrap()
}
};
Ok(version)
}
fn read_fs(fname: PathBuf) -> Result<String> {
log::debug!("reading version data from {:?}", fname);
Ok(fs::read_to_string(fname)?.trim().into())
}
#[cfg(test)]
mod tests {
#[test]
fn read_version() {
let _ = pretty_env_logger::try_init();
let version = super::read_fs("./testdata/VERSION".into()).unwrap();
assert_eq!(version, "0.1.0");
}
}

View File

@ -1,4 +0,0 @@
## [0.1.0]
Hi there this is a test!
### ADDED
- something

View File

@ -1,96 +0,0 @@
use ::gitea_release::{cmd, git, APP_USER_AGENT};
use anyhow::Result;
use git2::{Repository, Signature};
use log::debug;
use std::{fs::File, io::Write};
const TAG: &'static str = "0.1.0";
#[tokio::test]
async fn cut() -> Result<()> {
let _ = pretty_env_logger::try_init();
let name = elfs::next();
let token = std::env::var("DOMO_GITEA_TOKEN").expect("wanted envvar DOMO_GITEA_TOKEN");
let cli = gitea::Client::new("https://tulpa.dev".into(), token.clone(), APP_USER_AGENT)?;
debug!("created gitea client");
let gitea_repo = cli
.create_user_repo(gitea::CreateRepo {
auto_init: false,
description: format!("https://tulpa.dev/cadey/gitea-release test repo"),
gitignores: "".into(),
issue_labels: "".into(),
license: "".into(),
name: name.clone(),
private: true,
readme: "".into(),
})
.await?;
debug!("created repo domo/{}", name);
let dir = tempfile::tempdir()?;
let repo = Repository::init(&dir)?;
let sig = &Signature::now("Domo Arigato", "domo@tulpa.dev")?;
debug!("initialized repo in {:?}", dir.path());
let mut fout = File::create(&dir.path().join("VERSION"))?;
write!(fout, "{}", TAG)?;
drop(fout);
let mut fout = File::create(&dir.path().join("CHANGELOG.md"))?;
fout.write_all(include_bytes!("../testdata/basic.md"))?;
drop(fout);
git::commit(&repo, &sig, TAG.into(), &["VERSION", "CHANGELOG.md"])?;
debug!("committed files");
repo.remote("origin", &gitea_repo.clone_url)?;
debug!("set up origin remote to {}", gitea_repo.clone_url);
git::push(
&repo,
token.clone(),
&["refs/heads/master:refs/heads/master"],
)?;
debug!("pushed to {} with token {}", gitea_repo.clone_url, token);
std::env::set_current_dir(dir.path())?;
cmd::cut::run(
cmd::Common {
server: "https://tulpa.dev".into(),
token: token,
auth_user: gitea_repo.owner.login.clone(),
owner: gitea_repo.owner.login.clone(),
repo: gitea_repo.name,
email: "domo@tulpa.dev".into(),
username: "Domo Arigato".into(),
},
"CHANGELOG.md".into(),
None,
cmd::ReleaseMeta {
name: None,
draft: false,
pre_release: false,
},
)
.await?;
let rls = cli
.get_release_by_tag(
gitea_repo.owner.login.clone(),
name.clone(),
format!("v{}", TAG),
)
.await?;
assert_eq!(
rls.body,
"Hi there this is a test\\!\n### ADDED\n - something\n"
);
cli.delete_repo(gitea_repo.owner.login, name).await?;
debug!("deleted repo {}", gitea_repo.clone_url);
Ok(())
}