22 Commits

Author SHA1 Message Date
f9673b715f Update changelog for 2.2.0-2 release 2025-07-13 17:00:26 -05:00
820a9daaed Fix typo in copyright comment 2025-07-13 16:56:00 -05:00
8d71b81271 Fix: "Source:" URL in debian/copyright
That's what I get for copying other peoples' homework. I should have
used one of the `dh*` commands to build out the packaging boilerplate.
Oh well.
2025-07-13 16:51:22 -05:00
17bde93259 Update changelog for 2.2.0-1 release 2025-07-04 10:11:04 -05:00
cdb312fe61 Add the new dependencies to debian/control
Debian's package machinery also needs to know about these things, so
throw them into the control file.
2025-07-04 10:07:47 -05:00
cfaa0ceb3f Lift the empty-body string outside the let-if
Patch the string reference lifetime issue, and rediff the other patch
while we're at it.
2025-07-04 09:57:16 -05:00
c0a0181074 Merge tag 'v2.2.0' into deb/bookworm 2025-07-04 09:43:25 -05:00
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
bbae6b4395 Update changelog for 2.1.0-1 release 2025-06-12 17:51:23 -05:00
2c03c5ba4d Merge tag 'v2.1.0' into deb/bookworm
Hotfix for Attachment decode error
2025-06-12 17:48:09 -05:00
6a9ec25d1a Update changelog for 2.0.0-1 release 2025-06-12 16:28:43 -05:00
395ce8a804 Rediff patches
Drop 0002-Use-pre-Rust-1.81-compatible-file-exists-test.patch: <REASON>
2025-06-12 16:24:48 -05:00
616be020f0 Merge tag 'v2.0.0' into deb/bookworm
Mark release for v2

Now with better error diagnostics!... better... They're still not great.
2025-06-12 16:22:33 -05:00
324c7e67a7 Re-assign v1.0.0-1 to 'unstable'
I should have done this before publishing the build, but it's too late
now. Instead, I'll update it here and continue forward with gbp-dch to
help generate more correct changelogs.
2025-06-12 12:55:16 -05:00
984974c240 Update debian/gbp.conf to use new branch name
I'm switching to use nested tags for the packaging branches. I want to
have a deb/bookworm and a deb/experimental branch. The gbp-pq tool
already seems to do this, so I'll follow it's lead.
2025-06-12 12:50:48 -05:00
4c05749d02 Rediff patches
Add 0002-Use-pre-Rust-1.81-compatible-file-exists-test.patch: <REASON>
Add 0001-Rust-edition-downgrade-to-2021.patch: <REASON>
2025-06-06 19:22:09 -05:00
c0e6c5d89d Create Debian packaging files 2025-06-06 19:19:39 -05:00
15 changed files with 262 additions and 50 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "gt-tool" name = "gt-tool"
version = "1.0.0" version = "2.2.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

55
debian/changelog vendored Normal file
View File

@@ -0,0 +1,55 @@
gt-tool (2.2.0-2) unstable; urgency=medium
* Fix: "Source:" URL in debian/copyright
* Fix typo in copyright comment
-- Robert Garrett <robertgarrett404@gmail.com> Sun, 13 Jul 2025 16:59:25 -0500
gt-tool (2.2.0-1) unstable; urgency=medium
* Basic impl Display for the Release struct
* Print releases in reverse order for easier reading
* Colorize the output!
* Remove trailing newline in Release item printout
* Galaxy-brained newline intersperse function
* Change to free-fn intersperse for stdlib compat
* `Release.colorized()`, not std::fmt::Display
* Address most of the cargo-clippy lints
* Prefix unused variables to quiet the linter
* Autoformat
* Oops, missed one
* Bump to v2.2.0
* Lift the empty-body string outside the let-if
* Add the new dependencies to debian/control
-- Robert Garrett <robertgarrett404@gmail.com> Fri, 04 Jul 2025 10:10:54 -0500
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

32
debian/control vendored Normal file
View File

@@ -0,0 +1,32 @@
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-colored-dev,
librust-itertools-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 Normal file
View File

@@ -0,0 +1,43 @@
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://git.gelvin.dev/robert/gt-tool
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 lifted 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 Normal file
View File

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

View File

@@ -0,0 +1,23 @@
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.63 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 4fd569c..8b67a52 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "gt-tool"
version = "2.2.0"
-edition = "2024"
+edition = "2021"
[dependencies]
clap = { version = "4.0.7", features = ["derive", "env"] }

View File

@@ -0,0 +1,39 @@
From: Robert Garrett <robertgarrett404@gmail.com>
Date: Fri, 4 Jul 2025 09:36:52 -0500
Subject: Lift the empty-body string outside the let-if
The if-else block that selects between the body of the Release or a
placeholder is returning references to variables that only exist
*inside* the body of the if-else blocks. Newer Rust versions seem to
understand the intent and do The Right Thing anyway (or they have some
other rule for how if-else block scopes work).
Manually lifting the variable to an outer scope resolves the problem.
---
src/structs/release.rs | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/structs/release.rs b/src/structs/release.rs
index 9ed537e..3c4a434 100644
--- a/src/structs/release.rs
+++ b/src/structs/release.rs
@@ -1,3 +1,5 @@
+use std::io::empty;
+
use colored::Colorize;
use serde::{Deserialize, Serialize};
@@ -27,10 +29,11 @@ impl Release {
let published = "Published:".bright_green();
let created = "Created:".green().dimmed();
let author = "Author:".blue();
+ let empty_body = String::from("(empty body)").dimmed();
let body = if !self.body.is_empty() {
- &self.body.white()
+ self.body.white()
} else {
- &String::from("(empty body)").dimmed()
+ empty_body
};
format!(

2
debian/patches/series vendored Normal file
View File

@@ -0,0 +1,2 @@
0001-Rust-edition-downgrade-to-2021.patch
0002-Lift-the-empty-body-string-outside-the-let-if.patch

26
debian/rules vendored Executable file
View File

@@ -0,0 +1,26 @@
#!/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

1
debian/source/format vendored Normal file
View File

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

View File

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

View File

@@ -22,8 +22,10 @@ pub async fn create_release_attachment(
Ok(false) => return Err(crate::Error::NoSuchFile), Ok(false) => return Err(crate::Error::NoSuchFile),
Err(e) => { Err(e) => {
eprintln!("Uh oh! The file-exists check couldn't be done: {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); println!("Uploading file {}", &file);
@@ -44,7 +46,7 @@ pub async fn create_release_attachment(
let attachment_desc = response let attachment_desc = response
.json::<Attachment>() .json::<Attachment>()
.await .await
.map_err(|e| crate::Error::from(e))?; .map_err(crate::Error::from)?;
return Ok(attachment_desc); return Ok(attachment_desc);
} else if response.status().is_client_error() { } else if response.status().is_client_error() {
let mesg = crate::decode_client_error(response).await?; let mesg = crate::decode_client_error(response).await?;

View File

@@ -10,13 +10,11 @@ pub struct ApiError {
url: String, url: String,
} }
pub (crate) async fn decode_client_error(response: reqwest::Response) -> Result<ApiError> { pub(crate) async fn decode_client_error(response: reqwest::Response) -> Result<ApiError> {
response response
.json::<ApiError>() .json::<ApiError>()
.await .await
.map_err(|reqwest_err| { .map_err(crate::Error::WrappedReqwestErr)
crate::Error::WrappedReqwestErr(reqwest_err)
})
} }
#[derive(Debug)] #[derive(Debug)]

View File

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

View File

@@ -1,5 +1,3 @@
use std::fmt::Display;
use colored::Colorize; use colored::Colorize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -22,21 +20,21 @@ pub struct Release {
author: Author, author: Author,
} }
impl Display for Release { impl Release {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { pub fn colorized(&self) -> String {
let tag = "Tag:".green().bold(); let tag = "Tag:".green().bold();
let name = "Name:".green(); let name = "Name:".green();
let published = "Published:".bright_green(); let published = "Published:".bright_green();
let created = "Created:".green().dimmed(); let created = "Created:".green().dimmed();
let author = "Author:".blue(); let author = "Author:".blue();
let body = if self.body.len() > 0 { let body = if !self.body.is_empty() {
&self.body.white() &self.body.white()
} else { } else {
&String::from("(empty body)").dimmed() &String::from("(empty body)").dimmed()
}; };
write!(f, format!(
"{tag} {} "{tag} {}
{name} {} {name} {}
{} {}
{published} {} ({created} {}) {published} {} ({created} {})