A fancy git worktree manager. You call it as mg through a shell wrapper.
Projects live at <project>/trunk, and every other worktree (PRs, feature branches) lives as a sibling folder next to trunk:
myproject/
βββ trunk/ # main worktree
βββ feat-login/ # mg work feat/login
βββ fix-typo/ # mg pr 42 (branch: fix/typo)
- Submodules cache
- Automatic submodules pulling on worktree creation
- Seamless integration with GitHub PRs
- Easy worktree pruning
- Custom hook on worktree creation
cargo install --git https://github.com/marc2332/gitThen wire the mg shell function into your rc:
# bash
eval "$(marcgit init bash)"
# zsh
eval "$(marcgit init zsh)"
# fish
marcgit init fish | source
# nushell β add to config.nu
marcgit init nushell | save -f ~/.config/nushell/marcgit.nu
# then: source ~/.config/nushell/marcgit.nuThe binary prints paths; the wrapper cds to them. Same trick zoxide and direnv use.
mg pr requires gh (GitHub CLI) to look up the PR's branch name.
| Command | What it does |
|---|---|
mg new <name> / mg n |
Create ./<name>/trunk and git init it. Jump in. |
mg clone <url> <name> / mg c |
Clone a repo into ./<name>/trunk. Jump in. |
mg work <branch> / mg w |
Create (or jump to) a sibling worktree for <branch>. Creates branch if new. --still to set it up without jumping in. |
mg pr <number> / mg p |
Fetch the PR via gh, create a worktree named after its head branch, jump in. --still to set it up without jumping in. |
mg trunk / mg t |
Jump to the project's trunk from any sibling worktree. |
mg list / mg l |
Print git worktree list for the current project. |
mg remove <branch> / mg r |
Remove the worktree and delete its branch. Jump back to trunk. |
mg prune <days> |
Remove sibling worktrees whose last commit is older than <days> days. Asks for confirmation; skips worktrees with modified or stashed files. -n / --dry-run to preview. |
mg init <shell> |
Print the shell wrapper for bash / zsh / fish / nushell. |
mg config init |
Write a default marcgit.toml at the project root. Print its path. |
mg config path |
Print the path of the marcgit.toml reachable from the current directory. |
mg new myproject # β ./myproject/trunk
mg clone https://github.com/user/repo.git foo # β ./foo/trunk
mg work feat/login # β ./myproject/feat-login (branch feat/login)
mg pr 42 # β ./myproject/fix-typo (PR #42's head branch)
mg pr 42 --still # same, but stay in the current directory
mg trunk # β ./myproject/trunk
mg remove feat/login # removes worktree + branch
mg prune 30 # prune worktrees idle β₯ 30 days (asks first)
mg prune 30 -n # dry-run: just list what would be pruned- Slashes in a branch name become dashes in the folder β
feat/loginβfeat-login. No nested subfolders. mg workandmg prare idempotent: if the worktree already exists, they just jump.--stillonmg work/mg prprints the worktree path to stderr instead of stdout, so the shell wrapper has nothing tocdinto and you stay put.mg prrequiresgh(GitHub CLI) to look up the PR's head branch name.mg prunemeasures age by each worktree's last commit date (git log -1). It refuses to remove worktrees with uncommitted changes, untracked files, or stashes recorded against their branch.
Optional marcgit.toml at the project root (next to trunk/). mg config init writes the default:
init-submodules = true # pull submodules on `mg work` (default: true)
cache-submodules = true # reuse submodule clones from ~/.mg/cache/submodules (default: true)
# create-hook = "npm install" # shell command run inside a newly created worktreecreate-hook runs after the worktree (and its submodules) are set up, with the
new worktree as working directory. Its output goes to stderr so it doesn't
interfere with the cd wiring. A failing hook prints a warning but doesn't
abort β you still land in the new worktree.
With cache-submodules on, each submodule's git directory is cached globally in
~/.mg/cache/submodules/<repo>/<commit-sha>/ (up to 5 versions per repo, LRU).
When a new worktree initializes submodules, a cached copy is seeded into place
first so git submodule update checks out from local objects instead of
cloning over the network. The result is still a completely normal submodule.
Every command other than new, clone, and init needs to know which project you're in. It runs git worktree list --porcelain and picks the worktree whose folder is literally named trunk. That's the anchor β so don't rename it.
MIT Β© Marc EspΓn Sanz