# How LazyGit Handles Git Worktree Creation and Management: A Complete Guide

> Discover how LazyGit simplifies git worktree creation and management. Learn to create switch and delete worktrees directly from your terminal with this comprehensive guide.

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

---

**LazyGit implements git worktree creation and management through a layered architecture that separates data models, git command wrappers, loading logic, and UI controllers, enabling users to create, switch, and delete worktrees without leaving the terminal.**

LazyGit, the popular terminal UI for Git operations, provides comprehensive support for git worktree creation and management. Understanding how LazyGit handles worktrees reveals a well-architected system that bridges low-level git commands with an intuitive interface. This guide examines the source code to explain how LazyGit discovers, creates, switches, and removes worktrees.

## LazyGit Git Worktree Creation and Management Architecture

The implementation follows a strict separation of concerns across five distinct layers. Each layer handles a specific aspect of the worktree lifecycle, from raw data representation to user interaction.

| Layer | Responsibility | Key Types / Functions |
|-------|----------------|-----------------------|
| **Model** | Simple data struct that mirrors `git worktree list` output. | `models.Worktree` |
| **Git Commands** | Direct wrappers around `git worktree` CLI. | `WorktreeCommands.New`, `WorktreeCommands.Delete`, `WorktreeCommands.Detach` |
| **Git Loader** | Parses `git worktree list --porcelain`, enriches each entry (git dir, branch name, rebasing/bisect status). | `WorktreeLoader.GetWorktrees` |
| **Helper** | UI‑level orchestration (prompting, validation, dispatching to the repo helper). | `WorktreeHelper.NewWorktree`, `WorktreeHelper.Switch`, `WorktreeHelper.Remove`, `WorktreeHelper.Detach` |
| **Controller** | Binds keyboard shortcuts to helper actions and renders worktree details. | `WorktreesController`, `WorktreeOptionsController` |

The flow moves through these layers sequentially: **Load** → **Display** → **Create** → **Switch** → **Delete/Detach**.

## The Worktree Data Model

At the foundation of lazygit git worktree creation and management lies the `Worktree` struct defined in [`pkg/commands/models/worktree.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/models/worktree.go). This model captures all metadata necessary to represent a worktree in the UI.

```go
type Worktree struct {
    IsMain         bool   // true for the main repository worktree
    IsCurrent      bool   // true if this worktree is the one the user is currently in
    Path           string // absolute path to the worktree's working tree
    IsPathMissing  bool   // true if the directory no longer exists on disk
    GitDir         string // absolute path to the .git directory for the worktree
    Branch         string // name of the checked‑out branch (or empty for detached)
    Name           string // human‑readable unique name, derived from the path
}

```

Source: [[`pkg/commands/models/worktree.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/models/worktree.go)](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/models/worktree.go)

This struct enables the UI to display worktree status, detect missing directories, and determine which worktree represents the user's current working directory.

## Loading and Discovering Worktrees

The `WorktreeLoader` in [`pkg/commands/git_commands/worktree_loader.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/worktree_loader.go) handles the discovery of existing worktrees. It executes `git worktree list --porcelain` and transforms the output into a slice of `models.Worktree` instances.

The loading process follows these steps:

1. **Execute porcelain command** to get structured output
2. **Parse worktree entries** to extract path, main worktree status, and current worktree status
3. **Parallel fetch git directories** using `git rev-parse --absolute-git-dir` for each worktree
4. **Detect special states** by checking for rebase or bisect files in the git directory
5. **Generate unique display names** based on paths to prevent UI collisions

```go
cmdArgs := NewGitCmd("worktree").Arg("list", "--porcelain").ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
...
if strings.HasPrefix(line, "worktree ") { // start of a new entry
    // fill IsMain, IsCurrent, IsPathMissing
}
...
wg.Add(len(worktrees))
for _, wt := range worktrees { go fetchGitDir(wt) }
...
// Resolve unique display names
names := getUniqueNamesFromPaths(...)
for i, wt := range worktrees { wt.Name = names[i] }

```

Source: [[`pkg/commands/git_commands/worktree_loader.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/worktree_loader.go)](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/git_commands/worktree_loader.go)

## Creating New Worktrees

Creating a worktree in LazyGit involves the `WorktreeHelper` orchestrating user prompts before invoking `WorktreeCommands.New`. This process supports both branch-based and detached worktree creation.

### The Command Wrapper

The low-level creation logic resides in [`pkg/commands/git_commands/worktree.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/worktree.go):

```go
func (self *WorktreeCommands) New(opts NewWorktreeOpts) error {
    // `git worktree add [--detach] [-b <branch>] <path> <base>`
    cmdArgs := NewGitCmd("worktree").Arg("add").
        ArgIf(opts.Detach, "--detach").
        ArgIf(opts.Branch != "", "-b", opts.Branch).
        Arg(opts.Path, opts.Base)
    return self.cmd.New(cmdArgs.ToArgv()).Run()
}

```

Source: [[`pkg/commands/git_commands/worktree.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/worktree.go)](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/git_commands/worktree.go)

### The UI Orchestration

The `WorktreeHelper.NewWorktree` method in [`pkg/gui/controllers/helpers/worktree_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/worktree_helper.go) handles the interactive flow:

```go
func (self *WorktreeHelper) NewWorktree() error {
    // Prompt for the base ref (branch/commit) and whether to detach
    f := func(detached bool) {
        self.c.Prompt(types.PromptOpts{
            Title: self.c.Tr.NewWorktreeBase,
            InitialContent: currentBranchName,
            FindSuggestionsFunc: self.suggestionsHelper.GetRefsSuggestionsFunc(),
            HandleConfirm: func(base string) error {
                return self.NewWorktreeCheckout(base, true, detached, context.WORKTREES_CONTEXT_KEY)
            },
        })
    }
    // Show a menu offering "Create from branch" vs "Create detached"
    return self.c.Menu(types.CreateMenuOptions{
        Title: self.c.Tr.WorktreeTitle,
        Items: []*types.MenuItem{
            {LabelColumns: []string{self.c.Tr.CreateWorktreeFrom}, OnPress: func() error { f(false); return nil }},
            {LabelColumns: []string{self.c.Tr.CreateWorktreeFromDetached}, OnPress: func() error { f(true); return nil }},
        },
    })
}

```

Source: [[`pkg/gui/controllers/helpers/worktree_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/worktree_helper.go)](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/controllers/helpers/worktree_helper.go)

This implementation allows users to create worktrees from existing branches or in detached HEAD state, with automatic path suggestions and validation.

## Switching Between Worktrees

Switching to a different worktree involves changing the process working directory and reinitializing the UI context. The `WorktreeHelper.Switch` method delegates the heavy lifting to `ReposHelper.DispatchSwitchTo`.

```go
func (self *WorktreeHelper) Switch(worktree *models.Worktree, contextKey types.ContextKey) error {
    if worktree.IsCurrent { return errors.New(self.c.Tr.AlreadyInWorktree) }
    self.c.LogAction(self.c.Tr.SwitchToWorktree)
    return self.reposHelper.DispatchSwitchTo(worktree.Path, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey)
}

```

Source: [[`pkg/gui/controllers/helpers/worktree_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/worktree_helper.go)](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/controllers/helpers/worktree_helper.go)

The `ReposHelper.DispatchSwitchTo` function in [`pkg/gui/controllers/helpers/repos_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/repos_helper.go) performs the following:

1. Sets a waiting status (`Switching`)
2. Changes the process cwd to the worktree path using `os.Chdir`
3. Verifies the directory contains a valid Git repository
4. Records the directory for recent repos tracking
5. Invokes the global `onNewRepo` callback to reinitialize the UI with the new repository context

This approach ensures that when you switch worktrees in LazyGit, the entire UI context updates to reflect the new working directory, branches, and state.

## Deleting and Detaching Worktrees

LazyGit provides two distinct removal operations: standard deletion and detaching. The `WorktreeHelper` handles both with appropriate safety checks.

### Removing a Worktree

The `WorktreeHelper.Remove` method implements a confirmation flow with automatic force-retry logic:

```go
func (self *WorktreeHelper) Remove(worktree *models.Worktree, force bool) error {
    // Confirmation dialog → git worktree remove (optionally –force)
    self.c.Confirm(types.ConfirmOpts{
        Title:  self.c.Tr.RemoveWorktreeTitle,
        Prompt: utils.ResolvePlaceholderString(...),
        HandleConfirm: func() error {
            return self.c.WithWaitingStatus(self.c.Tr.RemovingWorktree, func(gocui.Task) error {
                if err := self.c.Git().Worktree.Delete(worktree.Path, force); err != nil {
                    // If not forced and error suggests a need for –force, retry with force.
                    if !force && shouldForce(err) { return self.Remove(worktree, true) }
                    return err
                }
                self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
                return nil
            })
        },
    })
    return nil
}

```

Source: [[`pkg/gui/controllers/helpers/worktree_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/worktree_helper.go)](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/controllers/helpers/worktree_helper.go)

This implementation detects when a worktree contains uncommitted changes or submodules that require force removal, automatically prompting the user to retry with the `-f` flag.

### Detaching a Worktree

Detaching removes the worktree entry from git's management without deleting the working directory files:

```go
func (self *WorktreeHelper) Detach(worktree *models.Worktree) error {
    return self.c.WithWaitingStatus(self.c.Tr.DetachingWorktree, func(gocui.Task) error {
        self.c.LogAction(self.c.Tr.RemovingWorktree)
        return self.c.Git().Worktree.Detach(worktree.Path)
    })
}

```

Source: [[`pkg/gui/controllers/helpers/worktree_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/worktree_helper.go)](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/controllers/helpers/worktree_helper.go)

## Controller Layer and UI Integration

The controller layer binds keyboard shortcuts to the helper methods and renders the worktree list. The `WorktreesController` in [`pkg/gui/controllers/worktrees_controller.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/worktrees_controller.go) manages the primary worktree view.

Key bindings include:

- **New** (`n` by default): Triggers `WorktreeHelper.NewWorktree()` to create a worktree
- **Select/Enter**: Invokes `WorktreeHelper.Switch()` to change to the selected worktree
- **Remove** (`Ctrl-d`): Calls `WorktreeHelper.Remove()` for deletion
- **Open**: Launches the worktree path in an external editor

The controller renders the list using a tabwriter to display columns for name, branch, path, and status flags (main, missing).

Additionally, the `WorktreeOptionsController` in [`pkg/gui/controllers/worktree_options_controller.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/worktree_options_controller.go) provides context-menu access to create worktrees from the currently selected branch or ref, streamlining the creation workflow.

## Summary

LazyGit's approach to **git worktree creation and management** follows a clean architectural separation that ensures reliability and responsiveness:

- **Data Layer**: The `Worktree` model in [`pkg/commands/models/worktree.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/models/worktree.go) encapsulates all worktree metadata including path, branch, git directory, and status flags.
- **Loading Mechanism**: `WorktreeLoader.GetWorktrees` in [`pkg/commands/git_commands/worktree_loader.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/worktree_loader.go) parses porcelain output and enriches entries with git directory paths and rebase/bisect status.
- **Command Wrappers**: `WorktreeCommands` in [`pkg/commands/git_commands/worktree.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/worktree.go) provides thin, testable wrappers around `git worktree add`, `remove`, and `detach` operations.
- **UI Orchestration**: `WorktreeHelper` in [`pkg/gui/controllers/helpers/worktree_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/worktree_helper.go) manages prompts, validation, and the complex logic for switching worktrees via `ReposHelper.DispatchSwitchTo`.
- **Controller Layer**: `WorktreesController` and `WorktreeOptionsController` bind keyboard shortcuts and render the worktree list with status indicators.

This pipeline enables users to seamlessly create worktrees from branches or detached states, switch between them with automatic UI context updates, and safely remove them with intelligent force-detection.

## Frequently Asked Questions

### How does LazyGit discover existing git worktrees?

LazyGit discovers worktrees by executing `git worktree list --porcelain` through the `WorktreeLoader.GetWorktrees` function in [`pkg/commands/git_commands/worktree_loader.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/worktree_loader.go). The loader parses the porcelain output to extract paths, then parallel-fetches each worktree's git directory using `git rev-parse --absolute-git-dir`. It also detects special states like rebasing or bisecting by examining files within each worktree's git directory, finally generating unique display names based on the paths to prevent UI collisions.

### What happens when I switch to a different worktree in LazyGit?

When you switch worktrees, `WorktreeHelper.Switch` in [`pkg/gui/controllers/helpers/worktree_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/worktree_helper.go) validates that you're not already in the target worktree, then delegates to `ReposHelper.DispatchSwitchTo`. This helper changes the process's current working directory to the worktree path using `os.Chdir`, verifies the directory contains a valid Git repository, records the path for recent repository tracking, and invokes the global `onNewRepo` callback. This callback reinitializes the entire UI context, refreshing views for branches, files, and commits to reflect the new worktree's state.

### Can LazyGit create detached worktrees?

Yes, LazyGit supports creating detached worktrees through the `WorktreeHelper.NewWorktree` function. When initiating creation, the UI presents a menu offering "Create worktree from branch" or "Create detached worktree". Selecting the detached option sets the `Detach` flag to `true` in `NewWorktreeOpts`, which the `WorktreeCommands.New` method in [`pkg/commands/git_commands/worktree.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/worktree.go) translates into the `--detach` flag when executing `git worktree add`. This allows you to create a worktree pointing to a specific commit or tag without creating a local branch.

### How does LazyGit handle errors when deleting worktrees?

LazyGit implements intelligent error handling for worktree deletion in `WorktreeHelper.Remove` within [`pkg/gui/controllers/helpers/worktree_helper.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/helpers/worktree_helper.go). When `WorktreeCommands.Delete` returns an error, the helper checks if the error indicates that the worktree contains uncommitted changes or submodules that require force removal. If the initial attempt was made without the force flag, the function recursively calls itself with `force=true`, which translates to the `-f` flag in `git worktree remove`. The UI displays appropriate confirmation dialogs before deletion and refreshes the worktree, branch, and file views upon successful completion.