How Lazygit Manages State When Switching Between Repositories
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, the map is defined as:
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:
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 determines whether to reuse cached state or initialize fresh UI components:
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 as:
RepoPathStack *utils.StringStack
This stack records movement through submodules and worktrees. Controllers in 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:
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:
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:
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: CentralGuistruct,RepoStateMap,RepoPathStack, and switching logic (onSwitchToNewRepo,resetState)pkg/commands/git_commands/repo_paths.go: Worktree path resolution used as map keyspkg/gui/controllers/helpers/repos_helper.go: Navigation stack operations for submodules and worktrees
Summary
- Lazygit caches a complete
GuiRepoStateobject for every repository inRepoStateMap - The
resetStatefunction checksRepoStateMapusing the worktree path to restore previous UI context - Repository switching is instantaneous because the UI reuses existing state objects rather than rebuilding them
- The
RepoPathStackmaintains navigation history for complex submodule and worktree hierarchies - All state management logic resides in the
pkg/guipackage 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →