From 1abf8efb48ba310e6437aa7a969401f11015dd9c Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Sun, 10 May 2026 14:20:41 -0400 Subject: [PATCH] Initial commit --- README.md | 55 +++++++++++++++++++++++++++++++++++ UNLICENSE | 24 +++++++++++++++ flake.nix | 54 ++++++++++++++++++++++++++++++++++ gw.sh | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 README.md create mode 100644 UNLICENSE create mode 100644 flake.nix create mode 100644 gw.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..a26adc0 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# 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 [dir] # clone into managed structure +gw add [-n] # add worktree (-n creates new branch) +gw remove # remove worktree +gw list # list worktrees +gw switch # print path (auto-creates if missing) + +gws # 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/ +``` diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..3c577b0 --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +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 \ No newline at end of file diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..630fc67 --- /dev/null +++ b/flake.nix @@ -0,0 +1,54 @@ +{ + 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; + }; + }; +} diff --git a/gw.sh b/gw.sh new file mode 100644 index 0000000..f93c810 --- /dev/null +++ b/gw.sh @@ -0,0 +1,87 @@ +#!/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 [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 [-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 " + 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 " + 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 [args]"; exit 1 ;; +esac -- 2.51.2