# How Lazygit Manages State When Switching Between Repositories

> Discover how Lazygit manages state between repositories. Learn how it instantly restores scroll positions selections and UI context for seamless workflow.

- Repository: [Jesse Duffield/lazygit](https://github.com/jesseduffield/lazygit)
- Tags: internals
- Published: 2026-03-02

---

**Lazygit maintains a separate GUI state object for each repository in a `RepoStateMap` and restores the appropriate state instantly when users switch repos, preserving scroll positions, selections, and UI context.**

When working across multiple git repositories, developers need their terminal UI to remember exactly where they left off. This article explains how lazygit manages state when switching between repositories using a sophisticated per-repo state caching system implemented in the `pkg/gui` package.

## Per-Repository State Architecture

### The GuiRepoState Container

All UI data—including panel selections, scroll positions, search states, and mode objects—is encapsulated in a **GuiRepoState** instance. Each state object is bound to a specific repository identifier defined as `type Repo string` within the lazygit source.

### The RepoStateMap Registry

The central `Gui` struct maintains a lookup table that associates repository paths with their corresponding state objects. In [`pkg/gui/gui.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/gui.go), the map is defined as:

```go
type Gui struct {
    // …
    RepoStateMap map[Repo]*GuiRepoState
}

```

When a user opens a repository for the first time, lazygit creates a new `GuiRepoState` and stores it in this map. Subsequent switches retrieve the existing entry, ensuring no UI context is lost between sessions.

## The Repository Switching Workflow

### Entry Point: onSwitchToNewRepo

The switching process begins in `onSwitchToNewRepo`, which delegates to `onNewRepo` to reinitialize git commands before resetting the UI state. From [`pkg/gui/gui.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/gui.go):

```go
func (gui *Gui) onSwitchToNewRepo(startArgs appTypes.StartArgs, contextKey types.ContextKey) error {
    return gui.onNewRepo(startArgs, contextKey)
}

```

### State Restoration Logic in resetState

The `resetState` method in [`pkg/gui/gui.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/gui.go) determines whether to reuse cached state or initialize fresh UI components:

```go
func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
    worktreePath := gui.git.RepoPaths.WorktreePath()
    if state := gui.RepoStateMap[Repo(worktreePath)]; state != nil {
        // Re‑use stored state
        gui.State = state
        // …reset a few transient fields
        return gui.c.Context().Current()
    }
    // …otherwise create a brand‑new GuiRepoState
}

```

When a stored state exists, the function clears transient UI flags (such as lingering popups) and returns the previously active context, effectively teleporting the user back to their exact previous position in that repository.

## Navigation Stack Management

### Tracking Repository Paths with RepoPathStack

Beyond state caching, lazygit tracks the navigation hierarchy using a thread-safe **RepoPathStack**. Defined in [`pkg/gui/gui.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/gui.go) as:

```go
RepoPathStack *utils.StringStack

```

This stack records movement through submodules and worktrees. Controllers in [`pkg/gui/controllers/helpers/repos_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/repos_helper.go) push and pop entries when entering or leaving nested repositories, ensuring the correct path is used for state lookups in `RepoStateMap`.

## Practical Implementation Examples

### Switching Repositories Programmatically

To switch repos from a custom command or test:

```go
func switchToRepo(gui *gui.Gui, repoPath string) error {
    startArgs := appTypes.StartArgs{}
    gui.git.RepoPaths = git_commands.MockRepoPaths(repoPath)
    return gui.onSwitchToNewRepo(startArgs, context.NO_CONTEXT)
}

```

### Inspecting Cached State

Debug or inspect stored state for a specific repository:

```go
func printRepoState(gui *gui.Gui, repoPath string) {
    key := gui.RepoStateMap[gui.Repo(Repo(repoPath))]
    if key == nil {
        fmt.Println("No state cached for:", repoPath)
        return
    }
    fmt.Printf("State for %s – current view: %s, selected line: %d\n",
        repoPath,
        key.CurrentViewName(),
        key.Contexts.Files.GetSelectedLine(),
    )
}

```

### Forcing State Reinitialization

Clear cached state to force a fresh UI rebuild:

```go
func forceReinit(gui *gui.Gui) error {
    delete(gui.RepoStateMap, Repo(gui.git.RepoPaths.WorktreePath()))
    return gui.resetState(appTypes.StartArgs{})
}

```

## Key Source Files

- **[`pkg/gui/gui.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/gui.go)**: Central `Gui` struct, `RepoStateMap`, `RepoPathStack`, and switching logic (`onSwitchToNewRepo`, `resetState`)
- **[`pkg/commands/git_commands/repo_paths.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/repo_paths.go)**: Worktree path resolution used as map keys
- **[`pkg/gui/controllers/helpers/repos_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/repos_helper.go)**: Navigation stack operations for submodules and worktrees

## Summary

- Lazygit caches a complete `GuiRepoState` object for every repository in `RepoStateMap`
- The `resetState` function checks `RepoStateMap` using the worktree path to restore previous UI context
- Repository switching is instantaneous because the UI reuses existing state objects rather than rebuilding them
- The `RepoPathStack` maintains navigation history for complex submodule and worktree hierarchies
- All state management logic resides in the `pkg/gui` package with clear separation between state storage and switching workflows

## Frequently Asked Questions

### Does lazygit preserve my scroll position when switching back to a previous repository?

Yes. Because lazygit stores the complete `GuiRepoState` object—including the selected line index and view context—in `RepoStateMap`, returning to a repository restores your exact scroll position and cursor location instantly without requiring a full refresh.

### How does lazygit handle state for git worktrees?

Each worktree is treated as a distinct repository entry in `RepoStateMap` because the map keys are derived from `WorktreePath()` as implemented in [`pkg/commands/git_commands/repo_paths.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/repo_paths.go). This ensures separate UI states for each worktree of the same repository.

### What happens to open popups or modal dialogs when I switch repositories?

The `resetState` function clears transient UI flags and popup states when restoring a cached state, ensuring that lingering modals from a previous session do not persist when you return to a repository.

### Can I manually clear the cached state for a specific repository?

Yes. You can delete the entry from `RepoStateMap` using the worktree path as the key, then call `resetState` to force lazygit to rebuild the UI state from scratch, as shown in the `forceReinit` example above.