# How gh-stack Navigation Commands Work: up, down, top, bottom, and trunk Explained

> Understand gh-stack navigation commands like up, down, top, bottom, and trunk. Learn how they treat branches as a linear stack and skip merged branches.

- Repository: [GitHub/gh-stack](https://github.com/github/gh-stack)
- Tags: internals
- Published: 2026-08-03

---

**The gh-stack navigation commands treat branches as a linear stack starting from the trunk, allowing you to move up, down, or jump to ends while automatically skipping merged branches.**

The `github/gh-stack` extension models your feature branches as an ordered list anchored to a base branch. Understanding how the **gh-stack navigation commands** function helps you move efficiently between branches without manually typing checkout commands or worrying about merged branches cluttering your workflow.

## Understanding the Stack Structure

`gh-stack` conceptualizes your work as a linear stack that begins at the **trunk** (typically `main` or `develop`) and extends upward, with each successive branch farther from the base. Every branch in this structure tracks whether it has been merged, allowing navigation commands to skip inactive branches automatically.

## The Five Core Navigation Commands

### up [n] - Move Away From the Trunk

The `up` command checks out a branch *n* steps farther from the trunk. If you omit the number, it defaults to one step.

According to the source code in [`cmd/navigate.go`](https://github.com/github/gh-stack/blob/main/cmd/navigate.go), the `UpCmd` function calls `runNavigate(cfg, n)` with a positive delta. The implementation at lines 11-34 calculates the new position, filters out merged branches using `s.ActiveBranchIndices()`, and checks out the target via `git.CheckoutBranch`.

### down [n] - Move Toward the Trunk

The `down` command checks out a branch *n* steps closer to the trunk, defaulting to one step if no number is provided.

As implemented in [`cmd/navigate.go`](https://github.com/github/gh-stack/blob/main/cmd/navigate.go) at lines 38-62, `DownCmd` invokes `runNavigate(cfg, -n)` with a negative delta. The same core logic handles the movement but in the reverse direction, ensuring merged branches are bypassed.

### top - Jump to the Farthest Branch

The `top` command jumps directly to the branch farthest from the trunk that remains part of the stack.

In [`cmd/navigate.go`](https://github.com/github/gh-stack/blob/main/cmd/navigate.go) at lines 65-78, `TopCmd` calls `runNavigateToEnd(cfg, true)`, which selects the last branch in the ordered slice returned by `loadStack`.

### bottom - Jump to the Closest Active Branch

The `bottom` command jumps to the branch closest to the trunk that is still active (not merged). If every branch has been merged, it falls back to the first branch with a warning message.

This logic resides in [`cmd/navigate.go`](https://github.com/github/gh-stack/blob/main/cmd/navigate.go) at lines 79-92, where `BottomCmd` invokes `runNavigateToEnd(cfg, false)` to select the first active branch rather than the absolute first branch.

### trunk - Return to the Base Branch

The `trunk` command switches to the trunk branch of the current stack (e.g., `main` or `develop`). If the trunk does not exist locally, the command fetches the appropriate remote and creates a local copy before checking it out.

As shown in [`cmd/trunk.go`](https://github.com/github/gh-stack/blob/main/cmd/trunk.go) at lines 11-66, `TrunkCmd` calls `runTrunk`, which resolves the trunk name, ensures the branch exists locally using `git.BranchExists`, and performs the checkout.

## Internal Implementation Details

### Loading the Stack State

All navigation commands begin by calling `loadStack(cfg, "")` from [`cmd/utils.go`](https://github.com/github/gh-stack/blob/main/cmd/utils.go). This function reads the `.git/gh-stack` file and returns a `Stack` object containing:

- **Trunk**: The base branch name
- **Branches**: An ordered slice of `BranchRef` objects, each tracking merge status

### Position Calculation and Merge Skipping

The `runNavigate` function in [`cmd/navigate.go`](https://github.com/github/gh-stack/blob/main/cmd/navigate.go) determines your current position using `s.IndexOf(currentBranch)`. If you are currently on the trunk, special handling moves you to the first active branch.

When the current branch is not merged, the code builds a list of active indices via `s.ActiveBranchIndices()`. Navigation occurs within this filtered list, and the system counts how many merged branches were bypassed to display a helpful message to the user.

### Boundary Handling and Checkout

The navigation logic clamps target indices to prevent moving beyond the stack boundaries. If the calculated target equals your current branch, the system displays a friendly "already at the..." message via `cfg.Printf`.

Once a valid target is determined, the branch name is passed to `git.CheckoutBranch` from [`internal/git/gitops.go`](https://github.com/github/gh-stack/blob/main/internal/git/gitops.go), which executes `git checkout <branch>`. Success and warning messages are rendered through `cfg.Successf` and `cfg.Warningf` defined in [`internal/config/config.go`](https://github.com/github/gh-stack/blob/main/internal/config/config.go).

## Practical Usage Examples

```bash

# Move one branch up (skip any merged branches)

gh stack up

# Move three branches up the stack

gh stack up 3

# Move one branch down toward trunk

gh stack down

# Jump straight to the topmost branch

gh stack top

# Jump to the bottom (first active) branch

gh stack bottom

# Switch to the trunk (e.g., main) of the current stack

gh stack trunk

```

## Summary

- **gh-stack** models branches as a linear stack starting from the trunk and extending upward.
- The **`up`** and **`down`** commands move relative to your current position, accepting optional step counts and automatically skipping merged branches.
- **`top`** and **`bottom`** provide absolute navigation to the ends of the stack, with `bottom` specifically targeting the first non-merged branch.
- **`trunk`** checks out the base branch, fetching it from remote if it does not exist locally.
- Navigation logic in [`cmd/navigate.go`](https://github.com/github/gh-stack/blob/main/cmd/navigate.go) handles boundary clamping, merge detection via `ActiveBranchIndices()`, and delegates checkout operations to [`internal/git/gitops.go`](https://github.com/github/gh-stack/blob/main/internal/git/gitops.go).

## Frequently Asked Questions

### What happens if I try to navigate past the end of the stack?

The `runNavigate` function clamps the target index to the bounds of the active branch list. If you are already at the top or bottom, the command prints a message indicating you are already at that position and does not attempt an invalid checkout.

### How does gh-stack handle merged branches during navigation?

When executing `up` or `down`, the system calls `s.ActiveBranchIndices()` to build a list of non-merged branches. Navigation moves through this filtered list, effectively skipping merged branches. The system tracks how many merged branches were bypassed and reports this count to provide visibility into the skipped entries.

### What is the difference between the `bottom` and `trunk` commands?

The **`trunk`** command always checks out the base branch (e.g., `main`) defined in the stack configuration. The **`bottom`** command checks out the first active feature branch—the one closest to the trunk but not yet merged. If all feature branches are merged, `bottom` falls back to the first branch with a warning, whereas `trunk` always targets the base branch.

### Why does the `trunk` command fetch from remote?

As implemented in [`cmd/trunk.go`](https://github.com/github/gh-stack/blob/main/cmd/trunk.go), the `runTrunk` function checks if the trunk branch exists locally using `git.BranchExists`. If the branch is missing, it fetches the appropriate remote and creates a local tracking branch before checking it out. This ensures you can always return to the trunk even if you have not checked it out previously in the current repository state.