# Marcgit

> Use when the user wants to manage git worktrees with marcgit (a.k.a. `mg`) — creating a worktree for a feature/branch/PR, switching to trunk, listing or pruning worktrees, or moving in-progress edits into a fresh worktree. Triggers on phrases like "marcgit", "mg work", "create a worktree", "make a worktree for this feature", "spin up a branch worktree", "checkout PR N as a worktree", "prune old worktrees".

- Skill: `marc2332/marcgit` (Agent Skill)
- Install (CLI): `npx skillmds@latest add marc2332/marcgit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/marc2332/marcgit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: marc2332 (https://skillmd.com/u/marc2332)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/marc2332/marcgit

---


# marcgit (`mg`)

`marcgit` is a git worktree manager. Each project is laid out as a `trunk/`
directory (the main worktree) plus **sibling** folders next to it, one per
branch / PR:

```
myproject/
├── trunk/        # main worktree — the anchor, never rename it
├── feat-login/   # mg work feat/login
└── fix-typo/     # mg pr 42  (PR head branch fix/typo)
```

A branch name's slashes become dashes in the folder name: `feat/login` →
`feat-login`. No nested subfolders are created.

## CRITICAL: call the binary directly, not `mg`

`mg` is a **shell function** users install into their interactive shell
(`eval "$(marcgit init bash)"`). It will **not** exist in the non-interactive
shell you run Bash commands in. **Always invoke the real binary `marcgit`**, and
handle the printed path yourself.

How output works:
- **Navigating** commands (`work`, `pr`, `new`, `clone`, `trunk`) print the
  **target directory to stdout** (the `mg` wrapper would `cd` there). Capture
  stdout to learn where the worktree is.
- Everything else (status/info, `🌱`/`🧹` messages) goes to **stderr**.

So instead of relying on `cd`, capture the path and act on it explicitly:

```bash
dir="$(marcgit work feat/login)"   # stdout = new/existing worktree path
git -C "$dir" status               # operate without changing your own cwd
```

If `marcgit` is not on PATH, tell the user to install it
(`cargo install --git https://github.com/marc2332/git`); do not silently fall
back to raw `git worktree` unless they ask.

## Command reference

| Command | Alias | Effect | stdout |
| --- | --- | --- | --- |
| `marcgit new <project>` | `n` | Create `<project>/trunk` and `git init -b main` it. | trunk path |
| `marcgit clone <url> <project>` | `c` | Clone `<url>` into `<project>/trunk`. | trunk path |
| `marcgit work <branch>` | `w` | Create (or jump to) a sibling worktree for `<branch>`; creates the branch off trunk's HEAD if it doesn't exist. Idempotent. | worktree path |
| `marcgit pr <number>` | `p` | Use `gh` to resolve PR #N's head branch, fetch it (`pull/N/head`), add a worktree named after that branch. Idempotent. Needs `gh`. | worktree path |
| `marcgit trunk` | `t` | Resolve the project's `trunk` path. | trunk path |
| `marcgit list` | `l` | `git worktree list` for the current project. | listing |
| `marcgit remove <branch>` | `r` | Force-remove the worktree and delete its branch, then point back at trunk. | trunk path |
| `marcgit prune <days> [-n\|--dry-run]` | — | Remove sibling worktrees whose last commit is older than `<days>` days. **Prompts `[y/N]` on stdin** and skips dirty/stashed worktrees. `-n` previews only. | — |
| `marcgit init <bash\|zsh\|fish\|nushell>` | — | Print the shell wrapper code. | wrapper |
| `marcgit config init` | — | Write a default `marcgit.toml` at the project root. | config path |
| `marcgit config path` | — | Print the `marcgit.toml` reachable from cwd. | config path |

Notes:
- Every command except `new`, `clone`, and `init` must run **inside a project**
  (any worktree of it). marcgit finds the project by locating the worktree
  literally named `trunk`. If it errors with "no `trunk` worktree found", you're
  not in a marcgit project — use `marcgit new`/`clone` first, or `cd` into one.
- `work` and `pr` are idempotent: if the folder already exists they just return
  its path.
- `work` and `pr` accept `-s` / `--still`, which moves the worktree path to stderr so
  the user's `mg` wrapper doesn't `cd`. Don't pass it yourself — you need the
  path on stdout. Mention it when the user wants a worktree set up without
  leaving their current directory.
- `prune` blocks on a `y/N` confirmation prompt. When running it
  non-interactively, **always do a `--dry-run` first**, show the user what would
  be pruned, and only then run the real prune (piping `yes` only with explicit
  user approval). Never auto-confirm destructive prunes.
- `config init`: `init-submodules = true` (default) makes `work`/`pr` run
  `git submodule update --init --recursive` when a `.gitmodules` exists.

## Workflow: "create a worktree for this feature and copy my changes into it"

This is the headline use case. The user is working in one worktree (often
`trunk`), has in-progress edits, and wants a clean feature worktree that
contains those same edits. **Literally copy the file edits across.**

1. **Identify the source worktree** — your current location, or the path the
   user names. Capture its absolute path:
   ```bash
   src="$(git rev-parse --show-toplevel)"
   ```
2. **Pick the branch name** from what the user said (e.g. "login feature" →
   `feat/login`). Confirm the name if it's ambiguous.
3. **Create the worktree** and capture its path:
   ```bash
   dst="$(marcgit work feat/login)"
   ```
4. **Copy the edits** from `src` into `dst`. Cover both tracked and untracked:
   ```bash
   # tracked, staged + unstaged modifications → as a patch
   git -C "$src" diff HEAD > /tmp/mg-feature.patch
   if [ -s /tmp/mg-feature.patch ]; then
       git -C "$dst" apply /tmp/mg-feature.patch
   fi
   # untracked files → copy verbatim, preserving relative paths
   git -C "$src" ls-files --others --exclude-standard -z |
       while IFS= read -r -d '' f; do
           mkdir -p "$dst/$(dirname "$f")"
           cp -p "$src/$f" "$dst/$f"
       done
   ```
   If `git apply` fails (e.g. the worktree branched from a different base), fall
   back to a 3-way apply: `git -C "$dst" apply --3way /tmp/mg-feature.patch`, and
   if that still conflicts, tell the user which files conflicted instead of
   guessing.
5. **Verify** the copy landed and report the destination:
   ```bash
   git -C "$dst" status --short
   ```
   Tell the user the new worktree path (`$dst`) and what was copied.

**Decide tracked-vs-everything by intent:** `git diff HEAD` carries every change
relative to the last commit (staged + unstaged). If the user only wants specific
files, scope the patch with pathspecs: `git -C "$src" diff HEAD -- path/a path/b`.

**Do NOT remove the edits from the source** unless the user asks to *move* (not
copy) them. If they want a move, after verifying the copy you can
`git -C "$src" checkout -- .` / clean untracked — but confirm first, it's
destructive.

When the user instead describes a feature in prose (no existing edits), just
create the worktree (step 3), `cd`/operate inside `$dst`, and implement the work
there fresh.

## Other common requests

- **"work on PR 42"** → `dst="$(marcgit pr 42)"`, then operate in `$dst`.
  Requires `gh` authenticated for the repo.
- **"go back to trunk"** → `marcgit trunk` (path on stdout).
- **"what worktrees exist"** → `marcgit list`.
- **"delete the login worktree"** → `marcgit remove feat/login` (force-removes
  worktree + branch; confirm with the user since it deletes the branch).
- **"clean up stale worktrees older than 30 days"** → `marcgit prune 30 -n`
  first, show the list, then `marcgit prune 30` with user sign-off.
- **"start a new project"** → `marcgit new <name>` or
  `marcgit clone <url> <name>`.

## Gotchas

- The shell wrapper only `cd`s when stdout is a single existing directory;
  multi-line output (like `list`) is printed, not `cd`'d. You read stdout
  directly anyway, so this only matters when you advise the user.
- Folder names are sanitized (`/` → `-`); when you need the folder for a branch,
  derive it the same way, or just trust the path marcgit prints.
- `prune` measures age by each worktree's last commit (`git log -1`) and refuses
  worktrees with uncommitted changes, untracked files, or stashes on their
  branch — so "nothing to prune" can mean "all candidates were dirty."

