initial commit
This commit is contained in:
commit
94a0431ed5
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
.env
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "gitea-release"
|
||||
version = "0.1.0"
|
||||
authors = ["Christine Dodrill <me@christine.website>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
cli-table = "0.3"
|
||||
comrak = "0.7"
|
||||
git2 = "0.13"
|
||||
kankyo = "0.3"
|
||||
reqwest = { version = "0.10", features = ["json"] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
structopt = { version = "0.3", default-features = false }
|
||||
tokio = { version = "0.2", features = ["macros"] }
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2020 Christine Dodrill <me@christine.website>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,4 @@
|
|||
# gitea-release
|
||||
|
||||
A small command line tool to automate releases for [Gitea](https://gitea.io)
|
||||
repositories that reads from CHANGELOG and VERSION files.
|
|
@ -0,0 +1,16 @@
|
|||
#+TITLE: TODO
|
||||
|
||||
* Commands
|
||||
** TODO delete
|
||||
** TODO download
|
||||
** TODO edit
|
||||
** DONE info
|
||||
CLOSED: [2020-05-30 Sat 10:52]
|
||||
** TODO release
|
||||
** TODO upload
|
||||
|
||||
* Core Features
|
||||
** DONE Gitea API client
|
||||
CLOSED: [2020-05-30 Sat 10:52]
|
||||
** TODO CHANGELOG.md parsing
|
||||
** TODO VERSION parsing
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"nixpkgs-mozilla": {
|
||||
"branch": "master",
|
||||
"description": "mozilla related nixpkgs (extends nixos/nixpkgs repo)",
|
||||
"homepage": null,
|
||||
"owner": "mozilla",
|
||||
"repo": "nixpkgs-mozilla",
|
||||
"rev": "e912ed483e980dfb4666ae0ed17845c4220e5e7c",
|
||||
"sha256": "08fvzb8w80bkkabc1iyhzd15f4sm7ra10jn32kfch5klgl0gj3j3",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/mozilla/nixpkgs-mozilla/archive/e912ed483e980dfb4666ae0ed17845c4220e5e7c.tar.gz",
|
||||
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
# This file has been generated by Niv.
|
||||
|
||||
let
|
||||
|
||||
#
|
||||
# The fetchers. fetch_<type> fetches specs of type <type>.
|
||||
#
|
||||
|
||||
fetch_file = pkgs: spec:
|
||||
if spec.builtin or true then
|
||||
builtins_fetchurl { inherit (spec) url sha256; }
|
||||
else
|
||||
pkgs.fetchurl { inherit (spec) url sha256; };
|
||||
|
||||
fetch_tarball = pkgs: spec:
|
||||
if spec.builtin or true then
|
||||
builtins_fetchTarball { inherit (spec) url sha256; }
|
||||
else
|
||||
pkgs.fetchzip { inherit (spec) url sha256; };
|
||||
|
||||
fetch_git = spec:
|
||||
builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; };
|
||||
|
||||
fetch_builtin-tarball = spec:
|
||||
builtins.trace
|
||||
''
|
||||
WARNING:
|
||||
The niv type "builtin-tarball" will soon be deprecated. You should
|
||||
instead use `builtin = true`.
|
||||
|
||||
$ niv modify <package> -a type=tarball -a builtin=true
|
||||
''
|
||||
builtins_fetchTarball { inherit (spec) url sha256; };
|
||||
|
||||
fetch_builtin-url = spec:
|
||||
builtins.trace
|
||||
''
|
||||
WARNING:
|
||||
The niv type "builtin-url" will soon be deprecated. You should
|
||||
instead use `builtin = true`.
|
||||
|
||||
$ niv modify <package> -a type=file -a builtin=true
|
||||
''
|
||||
(builtins_fetchurl { inherit (spec) url sha256; });
|
||||
|
||||
#
|
||||
# Various helpers
|
||||
#
|
||||
|
||||
# The set of packages used when specs are fetched using non-builtins.
|
||||
mkPkgs = sources:
|
||||
let
|
||||
sourcesNixpkgs =
|
||||
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {};
|
||||
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
|
||||
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
|
||||
in
|
||||
if builtins.hasAttr "nixpkgs" sources
|
||||
then sourcesNixpkgs
|
||||
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
|
||||
import <nixpkgs> {}
|
||||
else
|
||||
abort
|
||||
''
|
||||
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
|
||||
add a package called "nixpkgs" to your sources.json.
|
||||
'';
|
||||
|
||||
# The actual fetching function.
|
||||
fetch = pkgs: name: spec:
|
||||
|
||||
if ! builtins.hasAttr "type" spec then
|
||||
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
|
||||
else if spec.type == "file" then fetch_file pkgs spec
|
||||
else if spec.type == "tarball" then fetch_tarball pkgs spec
|
||||
else if spec.type == "git" then fetch_git spec
|
||||
else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec
|
||||
else if spec.type == "builtin-url" then fetch_builtin-url spec
|
||||
else
|
||||
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
|
||||
|
||||
# Ports of functions for older nix versions
|
||||
|
||||
# a Nix version of mapAttrs if the built-in doesn't exist
|
||||
mapAttrs = builtins.mapAttrs or (
|
||||
f: set: with builtins;
|
||||
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
|
||||
);
|
||||
|
||||
# fetchTarball version that is compatible between all the versions of Nix
|
||||
builtins_fetchTarball = { url, sha256 }@attrs:
|
||||
let
|
||||
inherit (builtins) lessThan nixVersion fetchTarball;
|
||||
in
|
||||
if lessThan nixVersion "1.12" then
|
||||
fetchTarball { inherit url; }
|
||||
else
|
||||
fetchTarball attrs;
|
||||
|
||||
# fetchurl version that is compatible between all the versions of Nix
|
||||
builtins_fetchurl = { url, sha256 }@attrs:
|
||||
let
|
||||
inherit (builtins) lessThan nixVersion fetchurl;
|
||||
in
|
||||
if lessThan nixVersion "1.12" then
|
||||
fetchurl { inherit url; }
|
||||
else
|
||||
fetchurl attrs;
|
||||
|
||||
# Create the final "sources" from the config
|
||||
mkSources = config:
|
||||
mapAttrs (
|
||||
name: spec:
|
||||
if builtins.hasAttr "outPath" spec
|
||||
then abort
|
||||
"The values in sources.json should not have an 'outPath' attribute"
|
||||
else
|
||||
spec // { outPath = fetch config.pkgs name spec; }
|
||||
) config.sources;
|
||||
|
||||
# The "config" used by the fetchers
|
||||
mkConfig =
|
||||
{ sourcesFile ? ./sources.json
|
||||
, sources ? builtins.fromJSON (builtins.readFile sourcesFile)
|
||||
, pkgs ? mkPkgs sources
|
||||
}: rec {
|
||||
# The sources, i.e. the attribute set of spec name to spec
|
||||
inherit sources;
|
||||
|
||||
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
|
||||
inherit pkgs;
|
||||
};
|
||||
in
|
||||
mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }
|
|
@ -0,0 +1,10 @@
|
|||
let
|
||||
sources = import ./nix/sources.nix;
|
||||
pkgs = import <nixpkgs> { overlays = [ (import sources.nixpkgs-mozilla) ]; };
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
latest.rustChannels.stable.rust
|
||||
openssl
|
||||
pkg-config
|
||||
];
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
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<::serde_json::Value>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use reqwest::{header, Client};
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
mod gitea;
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
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,
|
||||
/// The version tag to operate on
|
||||
#[structopt(short, long)]
|
||||
tag: Option<String>,
|
||||
}
|
||||
|
||||
// Name your user agent after your app?
|
||||
static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
|
||||
|
||||
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)]
|
||||
struct ReleaseMeta {
|
||||
/// Release name
|
||||
#[structopt(short, long, default_value = "")]
|
||||
name: 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")]
|
||||
enum Cmd {
|
||||
/// Delete a given release from Gitea
|
||||
Delete {
|
||||
#[structopt(flatten)]
|
||||
common: Common,
|
||||
},
|
||||
/// Downloads release artifacts
|
||||
Download {
|
||||
#[structopt(flatten)]
|
||||
common: Common,
|
||||
/// Folder to download release artifacts to
|
||||
#[structopt(short, long)]
|
||||
fname: PathBuf,
|
||||
},
|
||||
/// Edits a release's description, name and other flags
|
||||
Edit {
|
||||
#[structopt(flatten)]
|
||||
common: Common,
|
||||
/// Release description
|
||||
#[structopt(short, long, default_value = "")]
|
||||
description: String,
|
||||
#[structopt(flatten)]
|
||||
release_meta: ReleaseMeta,
|
||||
},
|
||||
/// Gets release info
|
||||
Info {
|
||||
#[structopt(flatten)]
|
||||
common: Common,
|
||||
#[structopt(long, short)]
|
||||
json: bool,
|
||||
},
|
||||
/// 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,
|
||||
#[structopt(flatten)]
|
||||
release_meta: ReleaseMeta,
|
||||
},
|
||||
/// Uploads release artifacts to Gitea
|
||||
Upload {
|
||||
#[structopt(flatten)]
|
||||
common: Common,
|
||||
/// The name of the file
|
||||
#[structopt(short, long, default_value = "")]
|
||||
name: String,
|
||||
/// The description of the file
|
||||
#[structopt(short, long, default_value = "")]
|
||||
label: String,
|
||||
/// The location of the file on the disk
|
||||
#[structopt(short, long)]
|
||||
fname: PathBuf,
|
||||
/// Replace existing release artifacts?
|
||||
#[structopt(long)]
|
||||
replace: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let _ = kankyo::init();
|
||||
let cmd = Cmd::from_args();
|
||||
|
||||
match cmd {
|
||||
Cmd::Delete { common } => {
|
||||
let cli = client(&common);
|
||||
}
|
||||
|
||||
Cmd::Info { common, json } => {
|
||||
use cli_table::{Cell, Row, Table};
|
||||
let cli = client(&common)?;
|
||||
|
||||
let releases: Vec<gitea::Release> = cli
|
||||
.get(
|
||||
format!(
|
||||
"{}/api/v1/repos/{}/{}/releases",
|
||||
&common.server, &common.owner, &common.repo
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
match common.tag {
|
||||
Some(tag) => {
|
||||
let mut release: Option<gitea::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()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
println!("{:?}", cmd);
|
||||
println!("not implemented yet")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue