7 Commits

Author SHA1 Message Date
4553f60150 Drop the on-push-branch trigger, prep for full test 2025-07-02 15:17:33 -05:00
077ad67c99 The workdir worked! Add it to the gbp call, too 2025-07-02 15:17:33 -05:00
41277146a0 Always install build-deps, don't ask for user input 2025-07-02 15:17:33 -05:00
6c0578e466 Set workdir on the git-checkout step. Stupid 2025-07-02 15:17:33 -05:00
15d5f9872c Try relative pathing to get into the project dir 2025-07-02 15:17:33 -05:00
660ca7353d Fix: remove extra indentation in the run string
I think YAML is leaving the spaces in there and passing `  build-essential` to the shell. There is no package with a space in the name, especially not a leading space.
2025-07-02 15:17:33 -05:00
67e5e4de0f Add experimental Debian package autobuild 2025-07-02 15:17:33 -05:00
13 changed files with 76 additions and 139 deletions

View File

@@ -23,5 +23,39 @@ jobs:
target/release/gt-tool-${{ github.ref_name }}-$(arch) target/release/gt-tool-${{ github.ref_name }}-$(arch)
env: env:
RELEASE_KEY_GITEA: ${{ secrets.RELEASE_KEY_GITEA }} 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,12 +1,10 @@
[package] [package]
name = "gt-tool" name = "gt-tool"
version = "2.2.0" version = "1.0.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
clap = { version = "4.0.7", features = ["derive", "env"] } 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"] } reqwest = { version = "0.11.13", features = ["json", "stream", "multipart"] }
serde = { version = "1.0.152", features = ["derive"] } serde = { version = "1.0.152", features = ["derive"] }
tokio = { version = "1.24.2", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.24.2", features = ["macros", "rt-multi-thread"] }

26
debian/changelog vendored
View File

@@ -1,29 +1,3 @@
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 gt-tool (2.1.0-1) unstable; urgency=medium
* Fix: incorrect field names for `Attachment` * Fix: incorrect field names for `Attachment`

2
debian/control vendored
View File

@@ -7,8 +7,6 @@ Build-Depends:
debhelper-compat (= 13), debhelper-compat (= 13),
dh-cargo, dh-cargo,
librust-clap-dev, librust-clap-dev,
librust-colored-dev,
librust-itertools-dev,
librust-reqwest-dev, librust-reqwest-dev,
librust-tokio-dev, librust-tokio-dev,
librust-serde-dev, librust-serde-dev,

4
debian/copyright vendored
View File

@@ -1,7 +1,7 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: gt-tools Upstream-Name: gt-tools
Upstream-Contact: Robert Garrett <robertgarrett404@gmail.com> Upstream-Contact: Robert Garrett <robertgarrett404@gmail.com>
Source: https://git.gelvin.dev/robert/gt-tool Source: https://source.mnt.re/reform/mnt-reform-setup-wizard
Files: * Files: *
Copyright: 2025 Robert Garrett <robertgarrett404@gmail.com> Copyright: 2025 Robert Garrett <robertgarrett404@gmail.com>
@@ -17,7 +17,7 @@ Copyright:
2025 Robert Garrett <robertgarrett404@gmail.com> 2025 Robert Garrett <robertgarrett404@gmail.com>
License: GPL-3+ License: GPL-3+
Comment: Comment:
The debian/rules file is lifted directly from the tuigreet package. It was 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 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. only change I've made is to remove the documentation generation target.
. .

View File

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

View File

@@ -1,39 +0,0 @@
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!(

View File

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

View File

@@ -1,9 +1,12 @@
use crate::{ use crate::{
Result, 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!(); todo!();
} }
pub fn get_latest_release() -> Result<Release> { pub fn get_latest_release() -> Result<Release> {
@@ -17,7 +20,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(crate::Error::WrappedReqwestErr)?; let response = req.map_err(|reqwest_err| crate::Error::WrappedReqwestErr(reqwest_err))?;
if response.status().is_success() { if response.status().is_success() {
let release_list = response let release_list = response
.json::<Vec<Release>>() .json::<Vec<Release>>()
@@ -47,22 +50,22 @@ pub async fn create_release(
.json(&submission) .json(&submission)
.send() .send()
.await .await
.map_err(crate::Error::from)?; .map_err(|e| crate::Error::from(e))?;
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(crate::Error::from)?; .map_err(|e| crate::Error::from(e))?;
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,10 +22,8 @@ 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!( panic!("TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)");
"TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)" },
);
}
} }
println!("Uploading file {}", &file); println!("Uploading file {}", &file);
@@ -46,7 +44,7 @@ pub async fn create_release_attachment(
let attachment_desc = response let attachment_desc = response
.json::<Attachment>() .json::<Attachment>()
.await .await
.map_err(crate::Error::from)?; .map_err(|e| crate::Error::from(e))?;
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,11 +10,13 @@ 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(crate::Error::WrappedReqwestErr) .map_err(|reqwest_err| {
crate::Error::WrappedReqwestErr(reqwest_err)
})
} }
#[derive(Debug)] #[derive(Debug)]

View File

@@ -1,3 +1,4 @@
use std::path; use std::path;
use gt_tool::cli::Args; use gt_tool::cli::Args;
@@ -21,7 +22,10 @@ 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!("gt-tools-agent-{}", env!("CARGO_PKG_VERSION"))) .user_agent(format!(
"gt-tools-agent-{}",
env!("CARGO_PKG_VERSION")
))
.default_headers(headers) .default_headers(headers)
.build()?; .build()?;
@@ -29,15 +33,9 @@ async fn main() -> Result<(), gt_tool::Error> {
gt_tool::cli::Commands::ListReleases => { gt_tool::cli::Commands::ListReleases => {
let releases = let releases =
gt_tool::api::release::list_releases(&client, &args.gitea_url, &args.repo).await?; gt_tool::api::release::list_releases(&client, &args.gitea_url, &args.repo).await?;
// Print in reverse order so the newest items are closest to the for release in releases {
// user's command prompt. Otherwise the newest item scrolls off the println!("{:?}", release);
// 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 { gt_tool::cli::Commands::CreateRelease {
name, name,
@@ -54,8 +52,13 @@ async fn main() -> Result<(), gt_tool::Error> {
tag_name, tag_name,
target_commitish, target_commitish,
}; };
gt_tool::api::release::create_release(&client, &args.gitea_url, &args.repo, submission) gt_tool::api::release::create_release(
.await?; &client,
&args.gitea_url,
&args.repo,
submission,
)
.await?;
} }
gt_tool::cli::Commands::UploadRelease { gt_tool::cli::Commands::UploadRelease {
tag_name, tag_name,
@@ -85,10 +88,8 @@ 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!( panic!("TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)");
"TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)" },
);
}
} }
} }
for file in files { for file in files {
@@ -142,5 +143,5 @@ fn match_release_by_tag(tag: &String, releases: Vec<Release>) -> Option<Release>
} }
} }
} }
release return release;
} }

View File

@@ -1,4 +1,3 @@
use colored::Colorize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
@@ -20,36 +19,6 @@ pub struct Release {
author: Author, 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)] #[derive(Debug, Deserialize, Serialize)]
pub struct Author { pub struct Author {
id: usize, id: usize,