Skip to content
This repository was archived by the owner on Dec 5, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ pub enum StError {
/// A generic decoding error occurred.
#[error("Decoding error: {}", .0)]
DecodingError(String),
/// No stack found or stack is empty.
#[error(
"No stack found. The current branch might be the trunk branch or no branches are tracked."
)]
NoStackFound,

// ---- [ `st` application errors (remote) ] ----
/// A remote pull request could not be found.
Expand Down
39 changes: 39 additions & 0 deletions src/subcommands/local/bottom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! `bottom` subcommand.

use crate::{
ctx::StContext,
errors::{StError, StResult},
git::RepositoryExt,
};
use clap::Args;
use nu_ansi_term::Color;

/// CLI arguments for the `bottom` subcommand.
#[derive(Debug, Clone, Eq, PartialEq, Args)]
pub struct BottomCmd;

impl BottomCmd {
/// Run the `bottom` subcommand.
pub fn run(self, ctx: StContext<'_>) -> StResult<()> {
// Discover the current stack
let stack = ctx.discover_stack()?;

// If the stack only has the trunk branch, there's no bottom branch to move to
if stack.len() <= 1 {
return Err(StError::NoStackFound);
}

// The bottom branch is the first branch after the trunk
let bottom_branch = &stack[1];

// Check out the bottom branch
ctx.repository.checkout_branch(bottom_branch)?;

println!(
"Moved to bottom branch of stack: `{}`",
Color::Green.paint(bottom_branch)
);

Ok(())
}
}
53 changes: 53 additions & 0 deletions src/subcommands/local/down.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! `down` subcommand.

use crate::{
ctx::StContext,
errors::{StError, StResult},
git::RepositoryExt,
};
use clap::Args;
use nu_ansi_term::Color;

/// CLI arguments for the `down` subcommand.
#[derive(Debug, Clone, Eq, PartialEq, Args)]
pub struct DownCmd;

impl DownCmd {
/// Run the `down` subcommand.
pub fn run(self, ctx: StContext<'_>) -> StResult<()> {
// Discover the current stack
let stack = ctx.discover_stack()?;

// If the stack only has the trunk branch, there's no place to move up to
if stack.len() <= 1 {
return Err(StError::NoStackFound);
}

// Get the current branch name
let current_branch = ctx.repository.current_branch_name()?;

// Find the current branch in the stack
let current_index = stack
.iter()
.position(|branch| branch == &current_branch)
.ok_or(StError::BranchNotTracked(current_branch.clone()))?;

// If we're already at the trunk branch (index 0), can't go up any further
if current_index == 0 {
println!(
"Already at trunk branch `{}`. Cannot move down further.",
Color::Green.paint(&current_branch)
);
return Ok(());
}

let down_branch = &stack[current_index - 1];

// Check out the branch below
ctx.repository.checkout_branch(down_branch)?;

println!("Moved down to: `{}`", Color::Green.paint(down_branch));

Ok(())
}
}
12 changes: 12 additions & 0 deletions src/subcommands/local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,15 @@ pub use untrack::UntrackCmd;

mod config;
pub use config::ConfigCmd;

mod bottom;
pub use bottom::BottomCmd;

mod top;
pub use top::TopCmd;

mod up;
pub use up::UpCmd;

mod down;
pub use down::DownCmd;
39 changes: 39 additions & 0 deletions src/subcommands/local/top.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! `top` subcommand.

use crate::{
ctx::StContext,
errors::{StError, StResult},
git::RepositoryExt,
};
use clap::Args;
use nu_ansi_term::Color;

/// CLI arguments for the `top` subcommand.
#[derive(Debug, Clone, Eq, PartialEq, Args)]
pub struct TopCmd;

impl TopCmd {
/// Run the `top` subcommand.
pub fn run(self, ctx: StContext<'_>) -> StResult<()> {
// Discover the current stack
let stack = ctx.discover_stack()?;

// If the stack only has the trunk branch, there's no top branch to move to
if stack.len() <= 1 {
return Err(StError::NoStackFound);
}

// The top branch is the last branch in the stack
let top_branch = &stack[stack.len() - 1];

// Check out the top branch
ctx.repository.checkout_branch(top_branch)?;

println!(
"Moved to top branch of stack: `{}`",
Color::Green.paint(top_branch)
);

Ok(())
}
}
53 changes: 53 additions & 0 deletions src/subcommands/local/up.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! `up` subcommand.

use crate::{
ctx::StContext,
errors::{StError, StResult},
git::RepositoryExt,
};
use clap::Args;
use nu_ansi_term::Color;

/// CLI arguments for the `up` subcommand.
#[derive(Debug, Clone, Eq, PartialEq, Args)]
pub struct UpCmd;

impl UpCmd {
/// Run the `up` subcommand.
pub fn run(self, ctx: StContext<'_>) -> StResult<()> {
// Discover the current stack
let stack = ctx.discover_stack()?;

// If the stack only has the trunk branch, there's no place to move up to
if stack.len() <= 1 {
return Err(StError::NoStackFound);
}

// Get the current branch name
let current_branch = ctx.repository.current_branch_name()?;

// Find the current branch in the stack
let current_index = stack
.iter()
.position(|branch| branch == &current_branch)
.ok_or(StError::BranchNotTracked(current_branch.clone()))?;

// If we're already at the trunk branch (index 0), can't go up any further
if current_index == stack.len() - 1 {
println!(
"Already at topmost branch `{}`. Cannot move up further.",
Color::Green.paint(&current_branch)
);
return Ok(());
}

let up_branch = &stack[current_index + 1];

// Check out the branch above
ctx.repository.checkout_branch(up_branch)?;

println!("Moved up to: `{}`", Color::Green.paint(up_branch));

Ok(())
}
}
19 changes: 18 additions & 1 deletion src/subcommands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use clap::Subcommand;

mod local;
use local::{
CheckoutCmd, ConfigCmd, CreateCmd, DeleteCmd, LogCmd, RestackCmd, TrackCmd, UntrackCmd,
BottomCmd, CheckoutCmd, ConfigCmd, CreateCmd, DeleteCmd, DownCmd, LogCmd, RestackCmd, TopCmd,
TrackCmd, UntrackCmd, UpCmd,
};

mod remote;
Expand Down Expand Up @@ -46,6 +47,18 @@ pub enum Subcommands {
/// Configure the st application.
#[clap(visible_alias = "cfg")]
Config(ConfigCmd),
/// Move to the bottom branch of the stack (first branch after trunk).
#[clap(visible_alias = "b")]
Bottom(BottomCmd),
/// Move to the top branch of the stack (tip of the stack).
#[clap(visible_alias = "t")]
Top(TopCmd),
/// Move to the branch above the current branch.
#[clap(visible_alias = "u")]
Up(UpCmd),
/// Move to the branch below the current branch.
#[clap(visible_alias = "d")]
Down(DownCmd),
}

impl Subcommands {
Expand All @@ -65,6 +78,10 @@ impl Subcommands {
Self::Track(args) => args.run(ctx),
Self::Untrack(args) => args.run(ctx),
Self::Config(args) => args.run(ctx),
Self::Bottom(args) => args.run(ctx),
Self::Top(args) => args.run(ctx),
Self::Up(args) => args.run(ctx),
Self::Down(args) => args.run(ctx),
}
}
}