--- /dev/null
+# gw
+
+Git worktree manager that mirrors branch names to directory paths.
+
+## Install
+
+```bash
+# Copy to PATH
+cp gw.sh ~/.local/bin/gw
+chmod +x ~/.local/bin/gw
+
+# Add to ~/.bashrc or ~/.zshrc
+gws() { local p; p=$(gw switch "$@") && cd "$p"; }
+```
+
+Or with Nix flakes:
+
+```nix
+inputs.gw.url = "git+https://codeberg.org/binaryspy/gw.git";
+# then: inputs.gw.packages.${system}.gw
+```
+
+## Usage
+
+```bash
+gw init <repo-url> [dir] # clone into managed structure
+gw add <branch> [-n] # add worktree (-n creates new branch)
+gw remove <branch> # remove worktree
+gw list # list worktrees
+gw switch <branch> # print path (auto-creates if missing)
+
+gws <branch> # switch + cd (shell function)
+```
+
+## Example
+
+```bash
+gw init git@github.com:user/repo.git
+cd repo/main
+
+gw add feature/auth -n # create new branch + worktree
+gws feature/auth # cd into it
+```
+
+## Layout
+
+```
+repo/
+├── .bare/ # bare git repo
+├── .git # points to .bare
+├── .gwroot # marker file
+├── main/
+└── feature/
+ └── auth/
+```
--- /dev/null
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
\ No newline at end of file
--- /dev/null
+{
+ description = "gw - git worktree manager";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ };
+
+ outputs = { self, nixpkgs }:
+ let
+ supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
+ forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
+ in
+ {
+ packages = forAllSystems (system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in
+ {
+ gw = pkgs.stdenvNoCC.mkDerivation {
+ pname = "gw";
+ version = "0.1.0";
+
+ src = ./.;
+
+ nativeBuildInputs = [ pkgs.makeWrapper ];
+
+ installPhase = ''
+ runHook preInstall
+
+ install -Dm755 gw.sh $out/bin/gw
+
+ wrapProgram $out/bin/gw \
+ --prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.git ]}
+
+ runHook postInstall
+ '';
+
+ meta = with pkgs.lib; {
+ description = "Git worktree manager - manages worktrees mirroring branch paths as directory structure";
+ homepage = "https://codeberg.org/binaryspy/gw";
+ license = licenses.unlicense;
+ platforms = platforms.unix;
+ mainProgram = "gw";
+ };
+ };
+
+ default = self.packages.${system}.gw;
+ });
+
+ overlays.default = final: prev: {
+ gw = self.packages.${prev.system}.gw;
+ };
+ };
+}
--- /dev/null
+#!/usr/bin/env bash
+# gw - git worktree manager (slim version)
+set -euo pipefail
+
+die() { echo "error: $*" >&2; exit 1; }
+
+find_root() {
+ local d="$PWD"
+ while [[ "$d" != "/" ]]; do
+ [[ -f "$d/.gwroot" ]] && echo "$d" && return
+ d="$(dirname "$d")"
+ done
+ die "not in a gw repo (no .gwroot found)"
+}
+
+cmd_init() {
+ local url="${1:-}" dir="${2:-}"
+ [[ -z "$url" ]] && die "usage: gw init <repo-url> [dir]"
+ [[ -z "$dir" ]] && dir="$(basename "$url" .git)"
+ [[ "$dir" != /* ]] && dir="$PWD/$dir"
+
+ mkdir -p "$dir"
+ git clone --bare "$url" "$dir/.bare"
+ echo "gitdir: ./.bare" > "$dir/.git"
+ git -C "$dir/.bare" config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
+ git -C "$dir/.bare" fetch --all
+ touch "$dir/.gwroot"
+
+ local branch
+ branch=$(git -C "$dir/.bare" symbolic-ref --short HEAD 2>/dev/null || echo main)
+ git -C "$dir/.bare" worktree add "$dir/$branch" "$branch"
+
+ echo "ready: cd $dir/$branch"
+}
+
+cmd_add() {
+ local branch="" new=false
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -n|--new) new=true; shift ;;
+ *) branch="$1"; shift ;;
+ esac
+ done
+ [[ -z "$branch" ]] && die "usage: gw add <branch> [-n|--new]"
+
+ local path="$(find_root)/${branch#/}"
+ [[ -d "$path" ]] && { echo "$path"; return; }
+
+ mkdir -p "$(dirname "$path")"
+ if $new; then
+ git worktree add -b "$branch" "$path" >&2
+ else
+ git worktree add "$path" "$branch" >&2
+ fi
+ echo "$path"
+}
+
+cmd_remove() {
+ local branch="${1:-}"
+ [[ -z "$branch" ]] && die "usage: gw remove <branch>"
+ local path="$(find_root)/${branch#/}"
+ git worktree remove "$path" || git worktree remove --force "$path"
+}
+
+cmd_list() {
+ git worktree list
+}
+
+cmd_switch() {
+ local branch="${1:-}"
+ [[ -z "$branch" ]] && die "usage: gw switch <branch>"
+ local path="$(find_root)/${branch#/}"
+ [[ -d "$path" ]] || {
+ mkdir -p "$(dirname "$path")"
+ git worktree add "$path" "$branch" >&2
+ }
+ echo "$path"
+}
+
+case "${1:-}" in
+ init) shift; cmd_init "$@" ;;
+ add) shift; cmd_add "$@" ;;
+ remove|rm) shift; cmd_remove "$@" ;;
+ list|ls) cmd_list ;;
+ switch) shift; cmd_switch "$@" ;;
+ *) echo "usage: gw <init|add|remove|list|switch> [args]"; exit 1 ;;
+esac