12 Commits

Author SHA1 Message Date
119831481e Bump to v2.2.0
All checks were successful
/ Compile and upload a release build (release) Successful in 44s
2025-07-03 18:13:09 -05:00
7246c7afb6 Oops, missed one 2025-07-03 18:05:18 -05:00
84eaaa1dbd Autoformat 2025-07-03 18:03:33 -05:00
c9dda5760c Prefix unused variables to quiet the linter 2025-07-03 17:56:07 -05:00
336f1453b9 Address most of the cargo-clippy lints 2025-07-03 17:56:07 -05:00
f068e8233e Release.colorized(), not std::fmt::Display
I don't know for sure if the string-ified version of a Release struct is
being printed to the terminal. As such, I don't know if the user wants,
does not want, or has mixed intentions for the stringification of this
thing.

No Display impl, instead just a `colorized()` method.
2025-07-03 17:47:50 -05:00
d4ef21e243 Change to free-fn intersperse for stdlib compat
Itertools warns that the standard library may be stabilizing the
intersperse method soon and recommends using this function instead.
2025-07-02 22:44:06 -05:00
d94c350cde Galaxy-brained newline intersperse function
Itertools already has an intersperse method for me. Why would I build my
own when I can do this? There's even a `fold()` over the units that come
out of the print routine.
2025-07-02 22:29:07 -05:00
8120cb0489 Remove trailing newline in Release item printout 2025-07-02 22:08:45 -05:00
b82cfdb822 Colorize the output! 2025-07-02 22:06:36 -05:00
ea046c929f Print releases in reverse order for easier reading
The result list has the newest item first, but I want to print them the
other way around. This way the newest (and presumably most interesting)
release is always the visible item, regardless of how many others have
printed and scrolled off screen.
2025-07-02 21:42:41 -05:00
135acf09b7 Basic impl Display for the Release struct
I'm not certain what info I want to present when listing the Releases.

The idea is that the release version is the most important, and that it
matches the git-tag associated with the release. I'll print that first.

Next, the name of the release followed by the body text. The list of
releases will become quite large for some projects, and the body text
may include a changelog. Both of these will cause the output to become
quite large. I will need to create a size limiter, but I'm ignoring that
for now.

Who created the release and when may be useful when searching for a
release, so I've included that as the final section.
2025-07-02 12:56:17 -05:00
15 changed files with 66 additions and 230 deletions

View File

@@ -23,39 +23,5 @@ jobs:
target/release/gt-tool-${{ github.ref_name }}-$(arch)
env:
RELEASE_KEY_GITEA: ${{ secrets.RELEASE_KEY_GITEA }}
debian-release:
name: Build and upload the Debian 12 package
runs-on: ubuntu-latest
container:
image: debian:12
steps:
- name: Install Tools
run: >
apt-get update;
apt-get install -y --no-install-recommends
build-essential
git
git-buildpackage;
- name: Checkout Repo (can't use actions/checkout@v4, no NodeJS)
run: git clone ${{ github.event.repository.clone_url }}
- name: Switch to Debian package branch
run: git checkout deb
working-directory: ./gt-tool
- name: Install build-deps
run: apt-get build-dep -y .
working-directory: ./gt-tool
- name: Build the package
run: gbp buildpackage
working-directory: ./gt-tool
- name: Install the tool we just built
run: dpkg -i gt-tool*.deb # TODO: Pick out the exact version instead of globbing
- name: Upload the packaging parts
run: > # The file globs are like that to avoid matching the gt-tool/ folder. I don't want that uploaded.
gt-tool
-u ${{ vars.DEST_GITEA }} -r ${{ vars.DEST_REPO }}
upload-release
"${{ github.ref_name }}"
gt-tool-*
gt-tool_*
...

View File

@@ -1,10 +1,12 @@
[package]
name = "gt-tool"
version = "1.0.0"
version = "2.2.0"
edition = "2024"
[dependencies]
clap = { version = "4.0.7", features = ["derive", "env"] }
colored = "2.0.0"
itertools = "0.10.0"
reqwest = { version = "0.11.13", features = ["json", "stream", "multipart"] }
serde = { version = "1.0.152", features = ["derive"] }
tokio = { version = "1.24.2", features = ["macros", "rt-multi-thread"] }

29
debian/changelog vendored
View File

@@ -1,29 +0,0 @@
gt-tool (2.1.0-1) unstable; urgency=medium
* Fix: incorrect field names for `Attachment`
-- Robert Garrett <robertgarrett404@gmail.com> Thu, 12 Jun 2025 17:51:12 -0500
gt-tool (2.0.0-1) unstable; urgency=medium
* Interrogate list_releases result more closely
* Interrogate create_release result more closely
* Drop unused imports
* "Fix" the test case
* Interrogate create_release_attachment result
* Fold client-error-decode into a util function
* Add `Attachment` struct, new iface for create-rel
* Update main.rs to use new attachment iface
* Delete the unit tests
* ... and the unit testing notes in README.md
* Drop unused import in api/release.rs
* Use pre Rust 1.81 compatible file-exists test
* Rediff patches
-- Robert Garrett <robertgarrett404@gmail.com> Thu, 12 Jun 2025 16:28:18 -0500
gt-tool (1.0.0-1) unstable; urgency=low
* Experimental release.
-- Robert Garrett <robertgarrett404@gmail.com> Sun, 1 Jun 2025 16:05:00 -0500

30
debian/control vendored
View File

@@ -1,30 +0,0 @@
Source: gt-tool
Maintainer: Robert Garrett <robertgarrett404@gmail.com>
Section: misc
Priority: optional
Standards-Version: 4.6.2
Build-Depends:
debhelper-compat (= 13),
dh-cargo,
librust-clap-dev,
librust-reqwest-dev,
librust-tokio-dev,
librust-serde-dev,
Homepage: https://git.gelvin.dev/robert/gt-tool
Vcs-Git: https://git.gelvin.dev/robert/gt-tool
Vcs-Browser: https://git.gelvin.dev/robert/gt-tool
Rules-Requires-Root: no
Package: gt-tool
Architecture: any
Depends:
${misc:Depends},
${shlibs:Depends},
Description: CLI tools for interacting with the Gitea API.
Use interactively to talk to your Gitea instance, or automatically via a CI/CD
pipeline. Currently supports:
.
- showing the Releases for a project
- creating a new Release for a project
- attaching files to a release

43
debian/copyright vendored
View File

@@ -1,43 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: gt-tools
Upstream-Contact: Robert Garrett <robertgarrett404@gmail.com>
Source: https://source.mnt.re/reform/mnt-reform-setup-wizard
Files: *
Copyright: 2025 Robert Garrett <robertgarrett404@gmail.com>
License: GPL-3+
Files: debian/*
Copyright: 2025 Robert Garrett <robertgarrett404@gmail.com>
License: GPL-3+
Files: debian/rules
Copyright:
Johannes Schauer Marin Rodrigues <josch@debian.org>
2025 Robert Garrett <robertgarrett404@gmail.com>
License: GPL-3+
Comment:
The debian/rules file is liften directly from the tuigreet package. It was
linked in the Debian Rust Team Book as a pretty simple example package. The
only change I've made is to remove the documentation generation target.
.
https://salsa.debian.org/debian/tuigreet/-/blob/master/debian/rules?ref_type=heads
License: GPL-3+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
.
You should have received a copy of the GNU General Public License
along with it. If not, see <http://www.gnu.org/licenses/>.
.
On Debian systems, the full text of the GNU General Public License version 3
can be found in the file /usr/share/common-licenses/GPL-3.

6
debian/gbp.conf vendored
View File

@@ -1,6 +0,0 @@
[DEFAULT]
compression = xz
compression-level = 9
upstream-tag = v%(version)s
debian-branch = deb/bookworm

View File

@@ -1,23 +0,0 @@
From: Robert Garrett <robertgarrett404@gmail.com>
Date: Sun, 1 Jun 2025 17:59:20 -0500
Subject: Rust edition downgrade to 2021
Debian Bookworm uses Rust 1.64 which only supports up to the 2021
edition.
---
Cargo.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Cargo.toml b/Cargo.toml
index febccc4..cf52754 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "gt-tool"
version = "1.0.0"
-edition = "2024"
+edition = "2021"
[dependencies]
clap = { version = "4.0.7", features = ["derive", "env"] }

View File

@@ -1 +0,0 @@
0001-Rust-edition-downgrade-to-2021.patch

26
debian/rules vendored
View File

@@ -1,26 +0,0 @@
#!/usr/bin/make -f
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
DPKG_EXPORT_BUILDFLAGS = 1
include /usr/share/dpkg/default.mk
include /usr/share/rustc/architecture.mk
export DEB_HOST_RUST_TYPE
export PATH:=/usr/share/cargo/bin:$(PATH)
export CARGO=/usr/share/cargo/bin/cargo
export CARGO_HOME=$(CURDIR)/debian/cargo_home
export CARGO_REGISTRY=$(CURDIR)/debian/cargo_registry
export DEB_CARGO_CRATE=$(DEB_SOURCE)_$(DEB_VERSION_UPSTREAM)
%:
dh $@ --buildsystem=cargo
execute_after_dh_auto_clean:
$(CARGO) clean
rm -rf $(CARGO_HOME)
rm -rf $(CARGO_REGISTRY)
rm -f debian/cargo-checksum.json
execute_before_dh_auto_configure:
$(CARGO) prepare-debian $(CARGO_REGISTRY) --link-from-system
rm -f Cargo.lock
touch debian/cargo-checksum.json

View File

@@ -1 +0,0 @@
3.0 (quilt)

View File

@@ -1,12 +1,9 @@
use crate::{
Result,
structs::{
release::{CreateReleaseOption, Release},
},
structs::release::{CreateReleaseOption, Release},
};
pub fn get_release(id: u64) -> Result<Release> {
pub fn get_release(_id: u64) -> Result<Release> {
todo!();
}
pub fn get_latest_release() -> Result<Release> {
@@ -20,7 +17,7 @@ pub async fn list_releases(
) -> Result<Vec<Release>> {
let request_url = format!("{gitea_url}/api/v1/repos/{repo}/releases/");
let req = client.get(request_url).send().await;
let response = req.map_err(|reqwest_err| crate::Error::WrappedReqwestErr(reqwest_err))?;
let response = req.map_err(crate::Error::WrappedReqwestErr)?;
if response.status().is_success() {
let release_list = response
.json::<Vec<Release>>()
@@ -50,22 +47,22 @@ pub async fn create_release(
.json(&submission)
.send()
.await
.map_err(|e| crate::Error::from(e))?;
.map_err(crate::Error::from)?;
if response.status().is_success() {
let new_release = response
.json::<Release>()
.await
.map_err(|e| crate::Error::from(e))?;
.map_err(crate::Error::from)?;
return Ok(new_release);
} else if response.status().is_client_error() {
let mesg = crate::decode_client_error(response).await?;
return Err(crate::Error::ApiErrorMessage(mesg))
return Err(crate::Error::ApiErrorMessage(mesg));
}
panic!("Reached end of create_release without matching a return path");
}
pub fn edit_release(id: u64) -> Result<Release> {
pub fn edit_release(_id: u64) -> Result<Release> {
todo!();
}
pub fn delete_release(id: u64) -> Result<()> {
pub fn delete_release(_id: u64) -> Result<()> {
todo!();
}

View File

@@ -22,8 +22,10 @@ pub async fn create_release_attachment(
Ok(false) => return Err(crate::Error::NoSuchFile),
Err(e) => {
eprintln!("Uh oh! The file-exists check couldn't be done: {e}");
panic!("TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)");
},
panic!(
"TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)"
);
}
}
println!("Uploading file {}", &file);
@@ -44,7 +46,7 @@ pub async fn create_release_attachment(
let attachment_desc = response
.json::<Attachment>()
.await
.map_err(|e| crate::Error::from(e))?;
.map_err(crate::Error::from)?;
return Ok(attachment_desc);
} else if response.status().is_client_error() {
let mesg = crate::decode_client_error(response).await?;

View File

@@ -14,9 +14,7 @@ pub (crate) async fn decode_client_error(response: reqwest::Response) -> Result<
response
.json::<ApiError>()
.await
.map_err(|reqwest_err| {
crate::Error::WrappedReqwestErr(reqwest_err)
})
.map_err(crate::Error::WrappedReqwestErr)
}
#[derive(Debug)]

View File

@@ -1,4 +1,3 @@
use std::path;
use gt_tool::cli::Args;
@@ -22,10 +21,7 @@ async fn main() -> Result<(), gt_tool::Error> {
headers.append("Authorization", token.parse().unwrap());
}
let client = reqwest::Client::builder()
.user_agent(format!(
"gt-tools-agent-{}",
env!("CARGO_PKG_VERSION")
))
.user_agent(format!("gt-tools-agent-{}", env!("CARGO_PKG_VERSION")))
.default_headers(headers)
.build()?;
@@ -33,9 +29,15 @@ async fn main() -> Result<(), gt_tool::Error> {
gt_tool::cli::Commands::ListReleases => {
let releases =
gt_tool::api::release::list_releases(&client, &args.gitea_url, &args.repo).await?;
for release in releases {
println!("{:?}", release);
}
// Print in reverse order so the newest items are closest to the
// user's command prompt. Otherwise the newest item scrolls off the
// screen and can't be seen.
itertools::Itertools::intersperse(
releases.iter().rev().map(|release| release.colorized()),
String::from(""),
)
.map(|release| println!("{}", release))
.fold((), |_, _| ());
}
gt_tool::cli::Commands::CreateRelease {
name,
@@ -52,12 +54,7 @@ async fn main() -> Result<(), gt_tool::Error> {
tag_name,
target_commitish,
};
gt_tool::api::release::create_release(
&client,
&args.gitea_url,
&args.repo,
submission,
)
gt_tool::api::release::create_release(&client, &args.gitea_url, &args.repo, submission)
.await?;
}
gt_tool::cli::Commands::UploadRelease {
@@ -88,8 +85,10 @@ async fn main() -> Result<(), gt_tool::Error> {
Ok(false) => return Err(gt_tool::Error::NoSuchFile),
Err(e) => {
eprintln!("Uh oh! The file-exists check couldn't be done: {e}");
panic!("TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)");
},
panic!(
"TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)"
);
}
}
}
for file in files {
@@ -143,5 +142,5 @@ fn match_release_by_tag(tag: &String, releases: Vec<Release>) -> Option<Release>
}
}
}
return release;
release
}

View File

@@ -1,3 +1,4 @@
use colored::Colorize;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
@@ -19,6 +20,36 @@ pub struct Release {
author: Author,
}
impl Release {
pub fn colorized(&self) -> String {
let tag = "Tag:".green().bold();
let name = "Name:".green();
let published = "Published:".bright_green();
let created = "Created:".green().dimmed();
let author = "Author:".blue();
let body = if !self.body.is_empty() {
&self.body.white()
} else {
&String::from("(empty body)").dimmed()
};
format!(
"{tag} {}
{name} {}
{}
{published} {} ({created} {})
{author} {} ({})",
self.tag_name.bold(),
self.name,
body,
self.published_at,
self.created_at.dimmed(),
self.author.login,
self.author.email,
)
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Author {
id: usize,