Compare commits

...
4 Commits
Author SHA1 Message Date
cinco 36aaa65945 📝 docs: add changelog entry for 0.5.1
continuous-integration/drone/tag Build is passing
2026-03-01 03:38:04 -03:00
cinco 10a38a1f85 🔧 ci: delete existing release before recreating on retag
When a tag is deleted and recreated, the CI tried to POST a new
release that already existed, getting 409 and leaving RELEASE_ID
null, which caused asset uploads to fail with 405. Now checks for
an existing release by tag and deletes it before creating a new one.
2026-03-01 03:34:27 -03:00
cinco 42bdc1d409 🐛 fix: correct asset name and bump version to 0.5.1
detect_arch was returning "x86_64-linux" but CI uploads assets as
"tmuxido-x86_64-linux", causing 404 on self-update. Also bumps
Cargo.toml to 0.5.1 which was missing from the hotfix tag.
2026-03-01 03:31:10 -03:00
cinco a592c99375 🐛 fix: target tmux windows by name instead of numeric index
Removes base-index detection which was unreliable and defaulted to 0
when tmux's actual base-index was 1, causing "index in use" and
"can't find window" errors on session creation.
2026-03-01 03:17:28 -03:00
6 changed files with 48 additions and 60 deletions
+10
View File
@@ -76,6 +76,16 @@ steps:
commands:
- apk add --no-cache curl jq
- |
# Delete existing release for this tag if present (handles retag scenarios)
EXISTING_ID=$(curl -fsSL \
-H "Authorization: token $GITEA_TOKEN" \
"https://git.cincoeuzebio.com/api/v1/repos/cinco/Tmuxido/releases/tags/$DRONE_TAG" \
| jq -r '.id // empty')
if [ -n "$EXISTING_ID" ]; then
curl -fsSL -X DELETE \
-H "Authorization: token $GITEA_TOKEN" \
"https://git.cincoeuzebio.com/api/v1/repos/cinco/Tmuxido/releases/$EXISTING_ID"
fi
# Read DRONE_TAG via ENVIRON inside awk to avoid Drone's ${VAR} substitution
# which would replace ${TAG} with an empty string before the shell runs.
BODY=$(awk '
+10
View File
@@ -4,6 +4,16 @@ 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/).
## [0.5.1] - 2026-03-01
### Fixed
- Tmux window creation now targets windows by name instead of numeric index, eliminating
"index in use" and "can't find window" errors when `base-index` is not 0
- Self-update asset name corrected from `x86_64-linux` to `tmuxido-x86_64-linux` to match
what CI actually uploads, fixing 404 on `--update`
- CI release pipeline now deletes any existing release for the tag before recreating,
preventing 409 Conflict errors on retagged releases
## [0.5.0] - 2026-03-01
### Added
Generated
+1 -1
View File
@@ -864,7 +864,7 @@ dependencies = [
[[package]]
name = "tmuxido"
version = "0.5.0"
version = "0.5.1"
dependencies = [
"anyhow",
"clap",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tmuxido"
version = "0.5.0"
version = "0.5.1"
edition = "2024"
[dev-dependencies]
+2 -2
View File
@@ -20,8 +20,8 @@ pub fn current_version() -> &'static str {
fn detect_arch() -> Result<&'static str> {
let arch = std::env::consts::ARCH;
match arch {
"x86_64" => Ok("x86_64-linux"),
"aarch64" => Ok("aarch64-linux"),
"x86_64" => Ok("tmuxido-x86_64-linux"),
"aarch64" => Ok("tmuxido-aarch64-linux"),
_ => Err(anyhow::anyhow!("Unsupported architecture: {}", arch)),
}
}
+24 -56
View File
@@ -41,7 +41,6 @@ impl SessionConfig {
pub struct TmuxSession {
pub(crate) session_name: String,
project_path: String,
base_index: usize,
}
impl TmuxSession {
@@ -53,34 +52,12 @@ impl TmuxSession {
.replace('.', "_")
.replace(' ', "-");
let base_index = Self::get_base_index();
Self {
session_name,
project_path: project_path.display().to_string(),
base_index,
}
}
fn get_base_index() -> usize {
// Try to get base-index from tmux
let output = Command::new("tmux")
.args(["show-options", "-gv", "base-index"])
.output();
if let Ok(output) = output
&& output.status.success()
{
let index_str = String::from_utf8_lossy(&output.stdout);
if let Ok(index) = index_str.trim().parse::<usize>() {
return index;
}
}
// Default to 0 if we can't determine
0
}
pub fn create(&self, config: &SessionConfig) -> Result<()> {
// Check if we're already inside a tmux session
let inside_tmux = std::env::var("TMUX").is_ok();
@@ -167,25 +144,23 @@ impl TmuxSession {
.status()
.context("Failed to create tmux session")?;
// Create panes for first window if specified
let first_target = format!("{}:{}", self.session_name, first_window.name);
if !first_window.panes.is_empty() {
self.create_panes(self.base_index, &first_window.panes)?;
self.create_panes(&first_target, &first_window.panes)?;
}
// Apply layout for first window if specified
if let Some(layout) = &first_window.layout {
self.apply_layout(self.base_index, layout)?;
self.apply_layout(&first_target, layout)?;
}
// Create additional windows
for (index, window) in config.windows.iter().skip(1).enumerate() {
let window_index = self.base_index + index + 1;
// Create additional windows, targeting by session name so tmux auto-assigns the index
for window in config.windows.iter().skip(1) {
Command::new("tmux")
.args([
"new-window",
"-t",
&format!("{}:{}", self.session_name, window_index),
&self.session_name,
"-n",
&window.name,
"-c",
@@ -194,46 +169,44 @@ impl TmuxSession {
.status()
.with_context(|| format!("Failed to create window: {}", window.name))?;
// Create panes if specified
let target = format!("{}:{}", self.session_name, window.name);
if !window.panes.is_empty() {
self.create_panes(window_index, &window.panes)?;
self.create_panes(&target, &window.panes)?;
}
// Apply layout if specified
if let Some(layout) = &window.layout {
self.apply_layout(window_index, layout)?;
self.apply_layout(&target, layout)?;
}
}
// Select the first window
// Select the first window by name
Command::new("tmux")
.args([
"select-window",
"-t",
&format!("{}:{}", self.session_name, self.base_index),
])
.args(["select-window", "-t", &first_target])
.status()
.context("Failed to select first window")?;
Ok(())
}
fn create_panes(&self, window_index: usize, panes: &[String]) -> Result<()> {
fn create_panes(&self, window_target: &str, panes: &[String]) -> Result<()> {
for (pane_index, command) in panes.iter().enumerate() {
let target = format!("{}:{}", self.session_name, window_index);
// First pane already exists (created with the window), skip split
if pane_index > 0 {
// Create new pane by splitting
Command::new("tmux")
.args(["split-window", "-t", &target, "-c", &self.project_path])
.args([
"split-window",
"-t",
window_target,
"-c",
&self.project_path,
])
.status()
.context("Failed to split pane")?;
}
// Send the command to the pane if it's not empty
if !command.is_empty() {
let pane_target = format!("{}:{}.{}", self.session_name, window_index, pane_index);
let pane_target = format!("{}.{}", window_target, pane_index);
Command::new("tmux")
.args(["send-keys", "-t", &pane_target, command, "Enter"])
.status()
@@ -244,14 +217,9 @@ impl TmuxSession {
Ok(())
}
fn apply_layout(&self, window_index: usize, layout: &str) -> Result<()> {
fn apply_layout(&self, window_target: &str, layout: &str) -> Result<()> {
Command::new("tmux")
.args([
"select-layout",
"-t",
&format!("{}:{}", self.session_name, window_index),
layout,
])
.args(["select-layout", "-t", window_target, layout])
.status()
.with_context(|| format!("Failed to apply layout: {}", layout))?;