gitmux 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitmux-0.2.0/DESIGN.md +163 -0
- gitmux-0.2.0/PKG-INFO +426 -0
- gitmux-0.2.0/README.md +393 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/pyproject.toml +5 -1
- {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/__init__.py +1 -1
- gitmux-0.2.0/src/gitmux/cli.py +511 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/config.py +59 -25
- {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/git_ops.py +6 -0
- gitmux-0.2.0/src/gitmux/mcp_server.py +313 -0
- gitmux-0.2.0/src/gitmux/models.py +199 -0
- gitmux-0.2.0/src/gitmux/registry.py +79 -0
- gitmux-0.2.0/src/gitmux/service.py +426 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/tests/test_config.py +77 -21
- {gitmux-0.1.0 → gitmux-0.2.0}/tests/test_executor.py +1 -1
- gitmux-0.2.0/tests/test_mcp_server.py +117 -0
- gitmux-0.2.0/tests/test_registry.py +92 -0
- gitmux-0.2.0/tests/test_service.py +391 -0
- gitmux-0.1.0/DESIGN.md +0 -96
- gitmux-0.1.0/PKG-INFO +0 -221
- gitmux-0.1.0/README.md +0 -190
- gitmux-0.1.0/src/gitmux/cli.py +0 -535
- gitmux-0.1.0/src/gitmux/models.py +0 -63
- {gitmux-0.1.0 → gitmux-0.2.0}/.github/workflows/publish.yml +0 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/.gitignore +0 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/LICENSE +0 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/executor.py +0 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/hooks.py +0 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/src/gitmux/output.py +0 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/tests/__init__.py +0 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/tests/test_git_ops.py +0 -0
- {gitmux-0.1.0 → gitmux-0.2.0}/tests/test_hooks.py +0 -0
gitmux-0.2.0/DESIGN.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# gitmux - Design Document
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
gitmux 是一个 Python CLI 工具,通过 YAML 配置文件管理多个 git 仓库,支持批量 git 操作、分组管理、前后置 hook、并行/串行执行模式。
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/gitmux/
|
|
11
|
+
├── cli.py # Typer CLI 入口,子命令定义(Rich 输出 + --json)
|
|
12
|
+
├── mcp_server.py # MCP server(stdio,无 token)— 给 AI agent 用的工具集
|
|
13
|
+
├── service.py # Rich-free 核心逻辑(target/branch 解析、运行操作、配置增删)
|
|
14
|
+
├── registry.py # 本地 config→workspace 映射(~/.config/gitmux/workspaces.yaml)
|
|
15
|
+
├── config.py # YAML 配置加载/验证/保存
|
|
16
|
+
├── models.py # 数据模型(Repo, Group, Hook, Template)
|
|
17
|
+
├── executor.py # 命令执行引擎(串行/并行)
|
|
18
|
+
├── git_ops.py # Git 操作封装
|
|
19
|
+
├── hooks.py # Hook 执行逻辑
|
|
20
|
+
└── output.py # 输出格式化(Rich 表格、进度条)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 分层说明
|
|
24
|
+
|
|
25
|
+
- `service.py` 是**不含终端输出**的核心层:解析 target、解析 branch 别名、运行操作
|
|
26
|
+
(复用 `executor._execute_one`,含 hook)、状态汇总、配置增删/初始化。返回结构化数据。
|
|
27
|
+
- `cli.py`(给人用)在 service 之上叠加 Rich 输出;`--json` 直接吐 service 的结构化结果。
|
|
28
|
+
- `mcp_server.py`(给 agent 用)在 service 之上暴露 MCP 工具,返回结构化 JSON。
|
|
29
|
+
- 分支别名解析逻辑只在 `service.resolve_branch` 一处,CLI 与 MCP 共用,避免重复。
|
|
30
|
+
|
|
31
|
+
## Configuration Format
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
workspace: ~/projects
|
|
35
|
+
|
|
36
|
+
templates:
|
|
37
|
+
node-app:
|
|
38
|
+
post_pull:
|
|
39
|
+
- npm install
|
|
40
|
+
pre_push:
|
|
41
|
+
- npm test
|
|
42
|
+
|
|
43
|
+
groups:
|
|
44
|
+
backend:
|
|
45
|
+
repos:
|
|
46
|
+
- name: api-server
|
|
47
|
+
url: git@github.com:user/api-server.git
|
|
48
|
+
template: node-app
|
|
49
|
+
- name: auth-service
|
|
50
|
+
url: git@github.com:user/auth-service.git
|
|
51
|
+
path: ~/custom/path/auth
|
|
52
|
+
hooks:
|
|
53
|
+
post_pull:
|
|
54
|
+
- pip install -r requirements.txt
|
|
55
|
+
branches:
|
|
56
|
+
prod: "auth-plan-*"
|
|
57
|
+
dev: "auth-dev-main"
|
|
58
|
+
frontend:
|
|
59
|
+
repos:
|
|
60
|
+
- name: web-app
|
|
61
|
+
url: git@github.com:user/web-app.git
|
|
62
|
+
template: node-app
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## CLI Commands
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
gitmux init # 初始化配置文件
|
|
69
|
+
gitmux add <url> --group=<group> # 添加仓库
|
|
70
|
+
gitmux remove <name> # 移除仓库
|
|
71
|
+
gitmux list # 列出所有仓库
|
|
72
|
+
gitmux status [--group=<group>] # 状态总览
|
|
73
|
+
gitmux clone [--group=<group>] # 克隆未克隆的仓库
|
|
74
|
+
gitmux fetch [--group=<group>] [--branches] [--parallel]
|
|
75
|
+
gitmux pull [--group=<group>] [--branch=<alias|alias:latest|alias:value>] [--parallel]
|
|
76
|
+
gitmux push [--group=<group>] [--parallel]
|
|
77
|
+
gitmux exec <command> [--group=<group>] [--parallel]
|
|
78
|
+
gitmux group list
|
|
79
|
+
gitmux group create <name>
|
|
80
|
+
gitmux group remove <name>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Execution Flow
|
|
84
|
+
|
|
85
|
+
1. 用户执行命令
|
|
86
|
+
2. 查找配置文件:`--config` 指定 → 当前目录 `.gitmux.yaml` → `~/.gitmux.yaml`
|
|
87
|
+
3. 加载配置,解析目标仓库列表(按 --group 过滤)
|
|
88
|
+
3. 判断串行/并行模式
|
|
89
|
+
4. 对每个仓库执行:pre-hook → git 操作 → post-hook
|
|
90
|
+
5. 输出结果
|
|
91
|
+
|
|
92
|
+
### Hook 错误处理
|
|
93
|
+
- pre-hook 失败 → 中断该仓库的 git 操作
|
|
94
|
+
- post-hook 失败 → 标记仓库失败
|
|
95
|
+
|
|
96
|
+
### 输出模式
|
|
97
|
+
- 串行:实时输出,显示当前仓库名和执行步骤
|
|
98
|
+
- 并行:Rich 进度条,完成后汇总(成功/失败/详情)
|
|
99
|
+
|
|
100
|
+
## Technical Stack
|
|
101
|
+
- CLI: Typer
|
|
102
|
+
- Terminal UI: Rich
|
|
103
|
+
- Config: PyYAML
|
|
104
|
+
- Parallelism: concurrent.futures.ThreadPoolExecutor
|
|
105
|
+
- Git: subprocess
|
|
106
|
+
- MCP: `mcp` SDK (>=2, `MCPServer`) — optional extra `gitmux[mcp]`
|
|
107
|
+
- Testing: pytest
|
|
108
|
+
- Build: hatchling
|
|
109
|
+
|
|
110
|
+
## Workspace 解析设计
|
|
111
|
+
|
|
112
|
+
`workspace`(仓库本地根目录)与"仓库清单"分离,让配置文件能当作可共享文档(不含机器路径)。
|
|
113
|
+
|
|
114
|
+
**解析优先级(高→低):**
|
|
115
|
+
1. `--workspace` flag(CLI)/ `--workspace`(MCP)—— 一次性覆盖
|
|
116
|
+
2. 配置文件 `workspace:` 字段 —— 支持 `${ENV_VAR}` / `${ENV_VAR:-/default}` 占位符
|
|
117
|
+
3. **registry 绑定** —— `~/.config/gitmux/workspaces.yaml`,按配置文件**绝对路径**为 key 映射到本地 workspace
|
|
118
|
+
4. 都无 → `WorkspaceError`
|
|
119
|
+
|
|
120
|
+
**registry(`registry.py`):**
|
|
121
|
+
- 机器本地状态(类比 `~/.gitconfig`),不入库;位置用 `Path.home()` 解析,不用字符串 `~`。
|
|
122
|
+
- key = 配置文件 `Path(...).expanduser().resolve()` 绝对路径。
|
|
123
|
+
- CLI `gitmux workspace set/show/unset` 与 MCP `workspace_set/show/unset` 维护它。
|
|
124
|
+
|
|
125
|
+
**设计动机:** 配置文件可省略 `workspace` 成为纯仓库清单文档(跨机器/容器共享),每个环境用
|
|
126
|
+
`workspace set` 绑定一次本地路径。这样 `gitmux -c xxx.yaml`(含 MCP `gitmux-mcp --config xxx`)
|
|
127
|
+
**无需环境变量、无需 `--workspace`** 即可自动解析 workspace——尤其适合容器内 agent(不依赖
|
|
128
|
+
环境变量传导)。`config_path` 在 load 时写入 `GitmuxConfig`,供 `resolve_workspace` 查 registry。
|
|
129
|
+
|
|
130
|
+
## Markets 层设计
|
|
131
|
+
|
|
132
|
+
为在**单一配置文件**中管理多个市场/项目(让一个 MCP server 就能服务全部),在 group 之上
|
|
133
|
+
增加可选的 `markets` 层:`markets → groups → repos`。
|
|
134
|
+
|
|
135
|
+
- **数据模型**:`MarketConfig(name, groups)`;`GroupConfig` 带 `market` 反向引用;
|
|
136
|
+
`GitmuxConfig.markets` 为真实存储,`groups` 属性为跨市场的扁平视图(向后兼容读取)。
|
|
137
|
+
- **路径布局**:有 market → `{workspace}/{market}/{group}/{repo}`;无 market(旧配置)→
|
|
138
|
+
`{workspace}/{group}/{repo}`。`get_repo_path` 依据 `group.market` 自动选择,**无需改任何
|
|
139
|
+
op 签名**(市场信息随 group 携带)。
|
|
140
|
+
- **target 语法**:有 markets 时三段 `market/group/repo`;旧配置仍支持 `group/repo` / `repo`。
|
|
141
|
+
另有 `--market` / `-m` 选择器(CLI)与 `market` 参数(MCP 工具)。
|
|
142
|
+
- **向后兼容**:顶层 `groups:`(无 `markets:`)加载为一个隐式 `NO_MARKET` 市场,行为与
|
|
143
|
+
改造前完全一致(两段 target、三段路径)。`save_config` 按是否含真实市场决定写 `markets:`
|
|
144
|
+
还是 `groups:`。
|
|
145
|
+
- **MCP**:新增 `list_markets` 工具;`status/fetch/clone/pull/push/list_repos/add_repo`
|
|
146
|
+
均支持 `market` 参数。**一个 config + 一个 server 管所有市场**——不需要多 server、不需要
|
|
147
|
+
给工具传 config 路径。
|
|
148
|
+
|
|
149
|
+
## MCP Server 设计
|
|
150
|
+
|
|
151
|
+
面向 AI agent 的接入。核心原则是**最小权限 / 能力即边界**:agent 不需要 shell
|
|
152
|
+
权限,只能调用显式定义的工具,因此**不暴露通用 `exec`**。
|
|
153
|
+
|
|
154
|
+
- **传输**:stdio,无 token。server 作为 agent 的子进程在同一机器运行,安全边界是
|
|
155
|
+
OS/进程;无网络监听,无需鉴权。(远程/跨机才需要 HTTP + 鉴权,本项目不提供。)
|
|
156
|
+
- **无状态**:每次工具调用读 YAML → 跑 git → 返回结构化结果,不保留 per-session 状态。
|
|
157
|
+
- **实现**:`mcp_server.py` 用 `mcp.server.mcpserver.MCPServer`,工具直接调用
|
|
158
|
+
`service.py` 的纯逻辑并返回 JSON。入口 `gitmux-mcp`(`--config` 可指定配置)。
|
|
159
|
+
- **工具集**:list_repos / list_groups / status / fetch / clone / pull / push /
|
|
160
|
+
add_repo / remove_repo / init_config。git 类工具用 `target` | `group` | `all_repos`
|
|
161
|
+
三选一选择仓库。
|
|
162
|
+
- **扩展**:将来若需 HTTP 传输,只需在 `main()` 加分支切换 `transport` 并叠加 Bearer
|
|
163
|
+
鉴权,工具逻辑不变。
|
gitmux-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gitmux
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Manage multiple git repositories with ease
|
|
5
|
+
Project-URL: Homepage, https://github.com/ryan/gitmux
|
|
6
|
+
Project-URL: Repository, https://github.com/ryan/gitmux
|
|
7
|
+
Project-URL: Issues, https://github.com/ryan/gitmux/issues
|
|
8
|
+
Author: Ryan
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,devtools,git,multi-repo
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
24
|
+
Requires-Dist: rich>=13.9.4
|
|
25
|
+
Requires-Dist: typer>=0.15.4
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-mock==3.14.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest==8.3.4; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff==0.11.12; extra == 'dev'
|
|
30
|
+
Provides-Extra: mcp
|
|
31
|
+
Requires-Dist: mcp>=2.0.0; extra == 'mcp'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# gitmux
|
|
35
|
+
|
|
36
|
+
Manage multiple git repositories with ease. Clone, pull, push, and run commands across repos with a single command.
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **YAML configuration** — declarative repo management
|
|
41
|
+
- **Group management** — organize repos into groups
|
|
42
|
+
- **Batch git operations** — clone/pull/push across repos
|
|
43
|
+
- **Pre/post hooks** — run commands before/after git operations (e.g., `npm install` after pull)
|
|
44
|
+
- **Template system** — share hook configs across similar repos
|
|
45
|
+
- **Parallel execution** — speed up operations with `--parallel` flag
|
|
46
|
+
- **Status overview** — see all repos' git status at a glance
|
|
47
|
+
- **Arbitrary command execution** — run any shell command across repos
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install gitmux
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Initialize config in current directory
|
|
59
|
+
gitmux init
|
|
60
|
+
|
|
61
|
+
# Add repos (default group if --group omitted)
|
|
62
|
+
gitmux add git@github.com:user/api-server.git --group backend
|
|
63
|
+
gitmux add git@github.com:user/auth-service.git --group backend
|
|
64
|
+
|
|
65
|
+
# Clone all repos
|
|
66
|
+
gitmux clone --all
|
|
67
|
+
|
|
68
|
+
# Pull a single repo
|
|
69
|
+
gitmux pull backend/api-server
|
|
70
|
+
|
|
71
|
+
# Pull entire group (parallel)
|
|
72
|
+
gitmux pull --group backend --parallel
|
|
73
|
+
|
|
74
|
+
# Check status of all repos
|
|
75
|
+
gitmux status
|
|
76
|
+
|
|
77
|
+
# Run command on a specific repo
|
|
78
|
+
gitmux exec "git checkout main" --target backend/api-server
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
Config file lookup order (used for both reading and writing):
|
|
84
|
+
1. `--config / -c` flag (explicit path)
|
|
85
|
+
2. `.gitmux.yaml` in current directory
|
|
86
|
+
3. `~/.gitmux.yaml` (global fallback)
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
workspace: ~/projects
|
|
90
|
+
|
|
91
|
+
templates:
|
|
92
|
+
node-app:
|
|
93
|
+
post_pull:
|
|
94
|
+
- npm install
|
|
95
|
+
pre_push:
|
|
96
|
+
- npm test
|
|
97
|
+
|
|
98
|
+
groups:
|
|
99
|
+
backend:
|
|
100
|
+
repos:
|
|
101
|
+
- name: api-server
|
|
102
|
+
url: git@github.com:user/api-server.git
|
|
103
|
+
template: node-app
|
|
104
|
+
- name: auth-service
|
|
105
|
+
url: git@github.com:user/auth-service.git
|
|
106
|
+
path: ~/custom/path/auth # override default path
|
|
107
|
+
hooks:
|
|
108
|
+
post_pull:
|
|
109
|
+
- pip install -r requirements.txt
|
|
110
|
+
frontend:
|
|
111
|
+
repos:
|
|
112
|
+
- name: web-app
|
|
113
|
+
url: git@github.com:user/web-app.git
|
|
114
|
+
template: node-app
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Path Resolution
|
|
118
|
+
|
|
119
|
+
- With markets: `{workspace}/{market}/{group}/{repo_name}`
|
|
120
|
+
- Legacy (no markets): `{workspace}/{group}/{repo_name}`
|
|
121
|
+
- Override per-repo with the `path` field
|
|
122
|
+
|
|
123
|
+
### Markets (optional top layer)
|
|
124
|
+
|
|
125
|
+
For managing multiple markets/projects in **one config file** (so a single MCP server can
|
|
126
|
+
serve them all), wrap groups under a `markets:` layer:
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
workspace: /home/ryan/project/CIL/ReadOnly # or bind via registry / --workspace
|
|
130
|
+
markets:
|
|
131
|
+
mastercard:
|
|
132
|
+
groups:
|
|
133
|
+
base:
|
|
134
|
+
repos:
|
|
135
|
+
- name: map
|
|
136
|
+
url: https://code.example.net/mastercard/base/map.git
|
|
137
|
+
branches: { prod: "icbcMap-plan-*" }
|
|
138
|
+
rebate:
|
|
139
|
+
repos:
|
|
140
|
+
- name: bonus
|
|
141
|
+
branches: { prod: "bonus-plan-*", cmbProd: "cmbRebate-plan-*" }
|
|
142
|
+
projectx:
|
|
143
|
+
groups:
|
|
144
|
+
core:
|
|
145
|
+
repos: [ { name: svc, url: https://code.example.net/projectx/core/svc.git } ]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- **Target becomes three-segment**: `market/group/repo`
|
|
149
|
+
```bash
|
|
150
|
+
gitmux pull mastercard/rebate/bonus -b prod:~20260902
|
|
151
|
+
gitmux status --market mastercard # scope to a market
|
|
152
|
+
gitmux status -m mastercard -g rebate # a group within a market
|
|
153
|
+
```
|
|
154
|
+
- **Path layout**: `{workspace}/{market}/{group}/{repo}`
|
|
155
|
+
- **Backward compatible**: a config with a top-level `groups:` (no `markets:`) keeps the
|
|
156
|
+
old two-segment targets (`group/repo`) and `{workspace}/{group}/{repo}` layout.
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
#### Workspace resolution (env placeholders & overrides)
|
|
161
|
+
|
|
162
|
+
The `workspace` value (and per-repo `path`) supports `${ENV_VAR}` placeholders, so a
|
|
163
|
+
config file can be a shareable "repo manifest" that contains **no machine-specific
|
|
164
|
+
path** — each environment supplies the real location:
|
|
165
|
+
|
|
166
|
+
```yaml
|
|
167
|
+
# mastercard.yaml — a portable document; the real path comes from the environment
|
|
168
|
+
workspace: ${MASTERCARD_WORKSPACE}
|
|
169
|
+
groups:
|
|
170
|
+
rebate:
|
|
171
|
+
repos:
|
|
172
|
+
- name: bonus
|
|
173
|
+
url: https://code.example.net/mastercard/rebate/bonus.git
|
|
174
|
+
branches:
|
|
175
|
+
prod: "bonus-plan-*"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Resolution precedence (highest first):
|
|
179
|
+
|
|
180
|
+
1. `--workspace / -w` flag (CLI) or `--workspace` (MCP server)
|
|
181
|
+
2. `workspace:` field in the config, with `${VAR}` / `${VAR:-/default}` expanded from the environment
|
|
182
|
+
3. **Registry binding** — a local `config → workspace` map (see below)
|
|
183
|
+
4. Otherwise → clear error (unset variable, no default, no flag, no binding)
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# supply the path via environment
|
|
187
|
+
export MASTERCARD_WORKSPACE=/home/ryan/project/CIL/MasterCard/ReadOnly
|
|
188
|
+
gitmux pull rebate/bonus -b prod:~20260902 -c mastercard.yaml
|
|
189
|
+
|
|
190
|
+
# or override for one invocation
|
|
191
|
+
gitmux status -c mastercard.yaml --workspace /tmp/scratch
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
A literal path (`workspace: /abs/path`) still works unchanged. This lets one manifest
|
|
195
|
+
map to different local workspaces per machine/container (e.g. your dev box vs. an agent
|
|
196
|
+
container), and lets multiple manifests each declare their own `${..._WORKSPACE}` var.
|
|
197
|
+
|
|
198
|
+
#### Workspace registry (bind once, no env vars)
|
|
199
|
+
|
|
200
|
+
For the cleanest setup, **omit `workspace` from the manifest entirely** (making it a pure,
|
|
201
|
+
shareable repo document) and bind the local path once per machine. The binding lives in a
|
|
202
|
+
machine-local registry — `~/.config/gitmux/workspaces.yaml` — keyed by the config's
|
|
203
|
+
absolute path. No environment variables involved.
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# bind once on this machine
|
|
207
|
+
gitmux workspace set -c mastercard.yaml /home/ryan/project/CIL/MasterCard/ReadOnly
|
|
208
|
+
|
|
209
|
+
# from then on, --config alone resolves the workspace
|
|
210
|
+
gitmux pull rebate/bonus -b prod:~20260902 -c mastercard.yaml
|
|
211
|
+
|
|
212
|
+
gitmux workspace show # list all bindings + registry location
|
|
213
|
+
gitmux workspace show -c mastercard.yaml # effective workspace for one config
|
|
214
|
+
gitmux workspace unset -c mastercard.yaml # remove a binding
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Registry file:
|
|
218
|
+
|
|
219
|
+
```yaml
|
|
220
|
+
# ~/.config/gitmux/workspaces.yaml (machine-local; do not commit)
|
|
221
|
+
mappings:
|
|
222
|
+
/home/ryan/gitmux/mastercard.yaml: /home/ryan/project/CIL/MasterCard/ReadOnly
|
|
223
|
+
/home/ryan/gitmux/projectx.yaml: /home/ryan/project/CIL/ProjectX/ReadOnly
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Because the manifest carries no path, the same file works on your dev box and inside an
|
|
227
|
+
agent container — each environment binds its own local path once (in the container, run
|
|
228
|
+
`gitmux workspace set` / the `workspace_set` MCP tool with the container's mount path).
|
|
229
|
+
|
|
230
|
+
### Branch Management
|
|
231
|
+
|
|
232
|
+
Configure named branch aliases per repo:
|
|
233
|
+
|
|
234
|
+
```yaml
|
|
235
|
+
repos:
|
|
236
|
+
- name: map
|
|
237
|
+
url: https://code.example.com/base/map.git
|
|
238
|
+
branches:
|
|
239
|
+
prod: "bInfinite-plan-*" # pattern (contains *)
|
|
240
|
+
dev: "bInfinite-dev-main" # fixed branch name
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Usage:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
gitmux pull map --branch dev # checkout fixed branch → pull
|
|
247
|
+
gitmux pull map --branch prod:latest # fetch → find newest matching branch → checkout → pull
|
|
248
|
+
gitmux pull map --branch prod:260515 # replace * → checkout bInfinite-plan-260515 → pull
|
|
249
|
+
gitmux pull map --branch prod:~260520 # fetch → find latest branch with date <= 260520 → checkout → pull
|
|
250
|
+
gitmux pull --group base --branch dev # checkout fixed branch for all repos in group
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Rules:
|
|
254
|
+
- `--branch <alias>` — alias must be a fixed branch (no `*`), otherwise error
|
|
255
|
+
- `--branch <alias>:latest` — alias must be a pattern (has `*`), picks newest by commit date
|
|
256
|
+
- `--branch <alias>:~<date>` — alias must be a pattern, picks newest matching branch with date ≤ `<date>`
|
|
257
|
+
- `--branch <alias>:<value>` — alias must be a pattern, replaces `*` with `<value>`
|
|
258
|
+
|
|
259
|
+
### Hook System
|
|
260
|
+
|
|
261
|
+
Hooks run shell commands before/after git operations:
|
|
262
|
+
|
|
263
|
+
- `pre_clone`, `post_clone`
|
|
264
|
+
- `pre_pull`, `post_pull`
|
|
265
|
+
- `pre_push`, `post_push`
|
|
266
|
+
|
|
267
|
+
**Error handling:**
|
|
268
|
+
- Pre-hook failure → git operation is skipped
|
|
269
|
+
- Post-hook failure → repo marked as failed
|
|
270
|
+
|
|
271
|
+
**Template merging:** Repo-level hooks override template hooks per hook type.
|
|
272
|
+
|
|
273
|
+
## Commands
|
|
274
|
+
|
|
275
|
+
| Command | Description |
|
|
276
|
+
|---------|-------------|
|
|
277
|
+
| `gitmux init` | Create `.gitmux.yaml` in current dir (`--global` for `~/.gitmux.yaml`) |
|
|
278
|
+
| `gitmux add <url> --group <g>` | Add a repository (group auto-created) |
|
|
279
|
+
| `gitmux remove <name>` | Remove a repository |
|
|
280
|
+
| `gitmux list` | List all repositories |
|
|
281
|
+
| `gitmux status [target]` | Show git status overview (defaults to all) |
|
|
282
|
+
| `gitmux clone <target>` | Clone unclosed repositories |
|
|
283
|
+
| `gitmux fetch <target>` | Fetch remote data (`--branches` to list branches) |
|
|
284
|
+
| `gitmux pull <target>` | Pull latest changes |
|
|
285
|
+
| `gitmux push <target>` | Push local commits |
|
|
286
|
+
| `gitmux exec <cmd>` | Run command in repos (`--target` to specify) |
|
|
287
|
+
| `gitmux group list` | List groups |
|
|
288
|
+
| `gitmux group create <name>` | Create a group |
|
|
289
|
+
| `gitmux group remove <name>` | Remove a group |
|
|
290
|
+
| `gitmux workspace set <path> -c <cfg>` | Bind a config to a local workspace (registry) |
|
|
291
|
+
| `gitmux workspace show [-c <cfg>]` | Show workspace bindings |
|
|
292
|
+
| `gitmux workspace unset -c <cfg>` | Remove a config→workspace binding |
|
|
293
|
+
|
|
294
|
+
### Target Syntax
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
gitmux pull map # repo 'map' in default group
|
|
298
|
+
gitmux pull base/map # repo 'map' in group 'base'
|
|
299
|
+
gitmux pull --group base # all repos in group 'base'
|
|
300
|
+
gitmux pull --all # all repos (explicit)
|
|
301
|
+
gitmux pull # error: specify target, --group, or --all
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
With a **markets** config, targets are three-segment and `--market` scopes operations:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
gitmux pull mastercard/base/map # repo 'map' in market/group
|
|
308
|
+
gitmux pull --market mastercard # all repos in a market
|
|
309
|
+
gitmux pull -m mastercard -g rebate # a group within a market
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Note: `gitmux add <url>` without `--group` places the repo in the `default` group.
|
|
313
|
+
|
|
314
|
+
### Common Options
|
|
315
|
+
|
|
316
|
+
- `--group, -g` — operate on entire group
|
|
317
|
+
- `--market, -m` — scope to a market (configs using the markets layer)
|
|
318
|
+
- `--all, -a` — operate on all repositories (required for write operations without target)
|
|
319
|
+
- `--parallel, -p` — run in parallel (clone/fetch/pull/push/exec)
|
|
320
|
+
- `--json` — emit structured JSON instead of formatted tables (for scripts/CI; `list`, `status`, `clone`, `fetch`, `pull`, `push`, `exec`)
|
|
321
|
+
- `--workspace, -w` — override the workspace directory (highest precedence; also settable via `${ENV_VAR}` in the config)
|
|
322
|
+
- `--config, -c` — custom config file path
|
|
323
|
+
|
|
324
|
+
## MCP Server (for AI agents)
|
|
325
|
+
|
|
326
|
+
gitmux ships an [MCP](https://modelcontextprotocol.io) server so AI agents can manage
|
|
327
|
+
repositories through a **fixed set of tools** instead of being granted shell access.
|
|
328
|
+
The tool set *is* the capability boundary — there is deliberately **no generic
|
|
329
|
+
`exec`/shell tool**, so an agent literally cannot run arbitrary commands.
|
|
330
|
+
|
|
331
|
+
### Install
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
pip install "gitmux[mcp]"
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Transport & security
|
|
338
|
+
|
|
339
|
+
- **stdio only, no authentication.** The server runs as a child process of the
|
|
340
|
+
agent on the same machine; the security boundary is the OS/process. There is no
|
|
341
|
+
network listener, so no token is needed or used.
|
|
342
|
+
- For remote/cross-machine use you would need an HTTP transport with auth — not
|
|
343
|
+
provided here by design (keeps the local least-privilege model simple).
|
|
344
|
+
|
|
345
|
+
### Run
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
gitmux-mcp # config lookup: ./.gitmux.yaml → ~/.gitmux.yaml
|
|
349
|
+
gitmux-mcp --config /path/to/.gitmux.yaml
|
|
350
|
+
gitmux-mcp --config /path/to/mastercard.yaml --workspace /repos/mastercard
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
The `--workspace` flag (or a `${ENV_VAR}` placeholder in the config) lets one shared
|
|
354
|
+
manifest resolve to the correct local path inside the agent's environment — e.g. a
|
|
355
|
+
different path in a Crew container than on your dev machine.
|
|
356
|
+
|
|
357
|
+
### Agent configuration (example)
|
|
358
|
+
|
|
359
|
+
Kiro / Claude Desktop style `mcpServers` entry:
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{
|
|
363
|
+
"mcpServers": {
|
|
364
|
+
"gitmux": {
|
|
365
|
+
"command": "gitmux-mcp",
|
|
366
|
+
"args": ["--config", "/home/you/.gitmux.yaml"],
|
|
367
|
+
"env": { "MASTERCARD_WORKSPACE": "/home/you/repos/mastercard" }
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The config's `workspace: ${MASTERCARD_WORKSPACE}` is then resolved from `env` above
|
|
374
|
+
(or pass `--workspace /path` in `args` instead). One MCP server per manifest keeps each
|
|
375
|
+
market/project isolated — the agent only sees what that config exposes.
|
|
376
|
+
|
|
377
|
+
### Exposed tools
|
|
378
|
+
|
|
379
|
+
| Tool | Kind | Description |
|
|
380
|
+
|------|------|-------------|
|
|
381
|
+
| `list_repos` | read | List repos (optional `group` filter) |
|
|
382
|
+
| `list_groups` | read | List groups and repo counts |
|
|
383
|
+
| `list_markets` | read | List markets and group counts (empty for legacy configs) |
|
|
384
|
+
| `status` | read | Git status per repo (branch/clean/ahead/behind/last commit) |
|
|
385
|
+
| `fetch` | read | Fetch remotes (optional `show_branches`) |
|
|
386
|
+
| `clone` | action | Clone repos not yet cloned (idempotent) |
|
|
387
|
+
| `pull` | action | Pull; supports branch aliases (`dev`, `prod:latest`, `prod:~260520`, `prod:260524`) |
|
|
388
|
+
| `push` | action | Push local commits |
|
|
389
|
+
| `add_repo` | config | Add a repo declaration to the YAML (does not clone) |
|
|
390
|
+
| `remove_repo` | config | Remove a repo declaration (does not delete cloned dir) |
|
|
391
|
+
| `init_config` | config | Create a new config file (non-interactive) |
|
|
392
|
+
| `workspace_set` | config | Bind this config to a local workspace (registry) |
|
|
393
|
+
| `workspace_show` | read | Show workspace registry bindings |
|
|
394
|
+
| `workspace_unset` | config | Remove a config→workspace binding |
|
|
395
|
+
|
|
396
|
+
Selection args on the git tools: pass one of `target` (`"market/group/repo"` with markets,
|
|
397
|
+
or `"group/repo"` / `"repo"` legacy), `market`, `group`, or `all_repos=True`. Every tool
|
|
398
|
+
returns structured JSON.
|
|
399
|
+
|
|
400
|
+
**Not exposed:** arbitrary shell/`exec`. If a specific command is ever needed, add a
|
|
401
|
+
purpose-built, parameter-constrained tool rather than a generic shell escape hatch.
|
|
402
|
+
|
|
403
|
+
## Development
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
git clone https://github.com/ryan/gitmux.git
|
|
407
|
+
cd gitmux
|
|
408
|
+
pip install -e ".[dev]"
|
|
409
|
+
pytest
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Code Quality
|
|
413
|
+
|
|
414
|
+
Uses [Ruff](https://docs.astral.sh/ruff/) for linting and formatting:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
ruff check . # lint
|
|
418
|
+
ruff check --fix . # auto-fix
|
|
419
|
+
ruff format . # format
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Rules: `E`, `F`, `W`, `I` (isort), `N`, `UP` (modern Python), `B` (bugbear), `SIM`.
|
|
423
|
+
|
|
424
|
+
## License
|
|
425
|
+
|
|
426
|
+
MIT
|