]> git.project802.io - gw.git/commitdiff
Initial commit
authorEthan Smith <esmith@project802.io>
Sun, 10 May 2026 18:20:41 +0000 (14:20 -0400)
committerEthan Smith <esmith@project802.io>
Sun, 10 May 2026 18:20:41 +0000 (14:20 -0400)
README.md [new file with mode: 0644]
UNLICENSE [new file with mode: 0644]
flake.nix [new file with mode: 0644]
gw.sh [new file with mode: 0644]

diff --git a/README.md b/README.md
new file mode 100644 (file)
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 <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/
+```
diff --git a/UNLICENSE b/UNLICENSE
new file mode 100644 (file)
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 <https://unlicense.org>
\ No newline at end of file
diff --git a/flake.nix b/flake.nix
new file mode 100644 (file)
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 (file)
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 <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