Skip to content

Instantly share code, notes, and snippets.

@sellout
Created October 27, 2022 05:00
Show Gist options
  • Save sellout/3361145fac9bf2dfdc6a9bc18dcdff36 to your computer and use it in GitHub Desktop.
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.

@T3sT3ro
Copy link

T3sT3ro commented Apr 19, 2025

Yeah, I also don't have a clear answer to that. Workflow with git worktrees is as messy as the git CLI itself. Personally I use this structure:

<PROJECT NAME>
├── main                  # git clone <repo> main
│   └── .git              # regular repo
├── dev                   # one worktree
│   ├── .git              # file for worktree
│   └── ...
└── refactor-something    # yet another worktree
    ├── .git              # another file for worktree
    └── ...    

I don't liek this setup, but it gave m less headaches than a bare repo or nested worktrees. I remember having issues with external tools (for example lazygit) or more manual work with refs when I tried using bare repo, so I abandoned that approach.

@sellout
Copy link
Author

sellout commented Apr 27, 2025

@T3sT3ro Yeah, that’s what I’m doing these days, too.

Having the clone on main / trunk / master whatever is nice for me to have a place to just try to reproduce something that someone brings up in a conversation, without having to take the time to make a new worktree (and if that worktree hangs around, then I try to do the same thing again, it complains “you already have main checked out in some other worktree”). So the clone is sort of just a clean version that I keep in sync, and all actual work happens on the worktrees.

It’s not terrible, but I do have various tooling (e.g., Magit) that isn’t keen on this structure. I’ve had to redefine some things so that my-project/some-worktree gets identified with the project name “my-project” and not “some-worktree” … because it’s annoying when you’re looking at a bunch of different repos and they all have the name “main” because of this directory structure.

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