Skip to content

Instantly share code, notes, and snippets.

@sellout
Created October 27, 2022 05:00
Show Gist options
  • Select an option

  • Save sellout/3361145fac9bf2dfdc6a9bc18dcdff36 to your computer and use it in GitHub Desktop.

Select an option

Save sellout/3361145fac9bf2dfdc6a9bc18dcdff36 to your computer and use it in GitHub Desktop.
Git worktree layout

I use git worktrees a lot, and I’ve been annoyed by the repo itself having effectively a privileged worktree that’s always there (and refuses to let any other worktree check out the branch it’s on). And then not having a good place to put my other worktrees. If I clone to $PROJECTS/this-project, where do I put my worktrees?

  • If I put them in the directory, the worktree directories are scattered among all the files in the clone’s working tree (and I seem to remember git having some issues with worktrees inside a working tree), but
  • if I put them next to the directory, then the worktree names need to be prefixed with the repo name (and if there are repos foo and foo-charts, it’s easy to forget that foo-charts isn’t just a worktree of foo.

So, my projects tended to look like this:

$PROJECT_DIR
├── my-project                           # a repo
│   ├── .git
│   └── ...                              # but also a working tree
├── my-project-add-some-feature          # a worktree
│   └── ...
├── my-project-charts                    # a different repo, but with a worktree-looking name
│   ├── .git
│   └── ...                              # which also has its own working tree
├── my-project-charts-refactor-something # a worktree in the different repo
│   └── ...
└── my-project-fix-this-bug              # another worktree in the first repo
    └── ...

But now I have a new script for cloning a repo (abbreviated a bit here), which does this:

CLONE_DIR=$PROJECT_DIR/$REPO/repo
git clone $DOMAIN/$ORGANIZATION/$REPO $CLONE_DIR
cd $CLONE_DIR
git checkout $(git commit-tree $(git hash-object -t tree /dev/null) < /dev/null)

which is what I wish git clone --bare did, but doesn’t quite. Basically, you end up with a repo without a working tree. So there’s no branch locked by the repo, and no privileged working tree.

So then I end up with something like this:

$PROJECT_DIR
├── my-project
│   ├── add-some-feature  # a worktree
│   │   └── ...
│   ├── fix-this-bug      # another worktree
│   │   └── ...
│   └── repo              # the “bare” repo
│       └── .git          # the only thing in the “bare” repo
└── my-project-charts     # a different project with a worktree-looking name
    ├── refactor-something
    │   └── ...
    └── repo
        └── .git

It’s annoying to have that script for cloning, so I’ll probably try to get rid of it. But after the initial clone, I really like working like this.

@sellout
Copy link
Author

sellout commented Feb 27, 2026

FWIW, I now use a very different approach (managed by a tool of my own called astrolabe).

$HOME
├── .local/state/astrolabe/clones
│   ├── buge                         # bare repo
│   ├── duoids                       #     〃
│   ├── pathway                      #     〃
│   ├── yaya                         #     〃
│   └── ...
└── Projects
    ├── community                    # broad “organization”
    │   ├── fix-directory-operations # single task (~ branch name)
    │   │   ├── buge                 # worktree for primary project of the task
    │   │   ├── pathway              # worktree for dependency of buge that needs changes
    │   │   └── yaya                 #                    〃
    │   └── ...
    ├── personal                     # another “organization”
    │   └── ...                      # tasks for that organization
    └── Some Client                  # another “organization”
        └── ...                      # tasks for that organization

This still uses bare repos (actually --bare now), but shifts my development to be task-centric, rather that repo-centric.

The bare repos are all hidden away as Astrolabe’s internal state, and each task directory has worktrees that reference those bare repos.

This helps with a few things

  1. the bare repos are effectively hidden away.
  2. I like to partition my work by whom it’s for (like, a client)
  3. but there are projects that I work on for multiple “organizations”, and my previous approach didn’t handle that well
  4. this assists with my preferred style of poly-repo development. I can have small projects, but still work on them as one1
  5. when one task is done, all of the worktrees associated with it are cleaned up together, rather than scattered around.

Footnotes

  1. Astrolabe does a bunch of stuff for me, like:

    • adding removing repos from a task;
    • letting me know which tasks are clean, or in what state;
    • cleaning up after finished tasks;
    • overriding dependencies so that all of the worktrees in a task depend on each other rather than their usual published dependencies, etc.; and
    • cleaning up bare repos that don’t have any worktrees or stashes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment