@revotools/cli 0.2.2 → 0.4.0
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.
- package/README.md +41 -5
- package/dist/revo +552 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,8 +8,13 @@ Revo is a fork of [Mars](https://github.com/dean0x/mars). It keeps everything
|
|
|
8
8
|
Mars gives you — tag-based filtering, coordinated branches, zero dependencies —
|
|
9
9
|
and adds features designed for working with coding agents:
|
|
10
10
|
|
|
11
|
+
- **`revo init`** auto-detects existing git repos in the current directory and
|
|
12
|
+
bootstraps a workspace around them, no manual `revo add` required.
|
|
13
|
+
- **`revo detect`** does the same on demand for an already-populated folder
|
|
14
|
+
full of clones.
|
|
11
15
|
- **`revo context`** scans your repos and writes a root-level `CLAUDE.md` so
|
|
12
|
-
Claude Code can understand the whole workspace at a glance
|
|
16
|
+
Claude Code can understand the whole workspace at a glance — including
|
|
17
|
+
active feature briefs and a built-in revo command reference.
|
|
13
18
|
- **`revo feature <name>`** creates a coordinated branch and shared context
|
|
14
19
|
file across matching repos.
|
|
15
20
|
- **`revo commit`**, **`revo push`**, **`revo pr`** let you commit, push, and
|
|
@@ -22,6 +27,8 @@ Pure bash 3.2+, works on macOS out of the box, no runtime dependencies beyond
|
|
|
22
27
|
|
|
23
28
|
## Quick Start
|
|
24
29
|
|
|
30
|
+
### From an empty directory
|
|
31
|
+
|
|
25
32
|
```bash
|
|
26
33
|
mkdir my-project && cd my-project
|
|
27
34
|
revo init
|
|
@@ -30,10 +37,23 @@ revo add git@github.com:org/shared-types.git --tags shared
|
|
|
30
37
|
revo add git@github.com:org/backend.git --tags backend,api --depends-on shared-types
|
|
31
38
|
revo add git@github.com:org/frontend.git --tags frontend,web --depends-on backend
|
|
32
39
|
|
|
33
|
-
revo clone # CLAUDE.md is
|
|
40
|
+
revo clone # CLAUDE.md is regenerated after each clone batch
|
|
34
41
|
revo context # regenerate it any time repos change
|
|
35
42
|
```
|
|
36
43
|
|
|
44
|
+
### From a folder you already populated with clones
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd ~/code/my-project # already has frontend/, backend/, shared/ as git repos
|
|
48
|
+
revo init # auto-detects existing repos, links them, writes CLAUDE.md
|
|
49
|
+
# — or —
|
|
50
|
+
revo detect # same idea, runs on its own without prompting
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`init` and `detect` both link root-level repos into `repos/` via relative
|
|
54
|
+
symlinks so the rest of revo's data model keeps working without moving any
|
|
55
|
+
files around.
|
|
56
|
+
|
|
37
57
|
Then point Claude Code at the workspace directory and it will read `CLAUDE.md`
|
|
38
58
|
on its own.
|
|
39
59
|
|
|
@@ -61,13 +81,14 @@ revo pr "Clock endpoint for students" ── coordinated PRs via gh
|
|
|
61
81
|
|
|
62
82
|
## Commands
|
|
63
83
|
|
|
64
|
-
### Workspace
|
|
84
|
+
### Workspace
|
|
65
85
|
|
|
66
86
|
| Command | Description |
|
|
67
87
|
|---------|-------------|
|
|
68
|
-
| `revo init` | Initialize a new workspace |
|
|
88
|
+
| `revo init` | Initialize a new workspace; auto-detects existing git repos in cwd |
|
|
89
|
+
| `revo detect` | Bootstrap revo around git repos already present in cwd |
|
|
69
90
|
| `revo add <url> [--tags t1,t2] [--depends-on r1,r2]` | Add a repository to config |
|
|
70
|
-
| `revo clone [--tag TAG]` | Clone configured repositories |
|
|
91
|
+
| `revo clone [--tag TAG]` | Clone configured repositories (regenerates `CLAUDE.md` after) |
|
|
71
92
|
| `revo list [--tag TAG]` | List configured repositories |
|
|
72
93
|
| `revo status [--tag TAG]` | Show status of all repositories |
|
|
73
94
|
| `revo sync [--tag TAG] [--rebase]` | Pull latest changes |
|
|
@@ -85,6 +106,21 @@ revo pr "Clock endpoint for students" ── coordinated PRs via gh
|
|
|
85
106
|
| `revo push [--tag TAG]` | Push branches across repos |
|
|
86
107
|
| `revo pr <title> [--tag TAG] [--body BODY]` | Create coordinated PRs via `gh` |
|
|
87
108
|
|
|
109
|
+
### What `revo context` detects per repo
|
|
110
|
+
|
|
111
|
+
- **Node.js** — Next.js, Nuxt, Remix, SvelteKit, Astro, Vite, NestJS, Fastify,
|
|
112
|
+
Express, Hono, Angular, React, Vue, Svelte, React Native, Expo
|
|
113
|
+
- **Python** — Django, FastAPI, Flask, Starlette
|
|
114
|
+
- **Go** (module name), **Rust** (Cargo), **Java/Kotlin** (Gradle), **Java**
|
|
115
|
+
(Maven), **Ruby** (Rails, Sinatra), **Swift** (Package.swift)
|
|
116
|
+
- API route directories (`src/routes/`, `src/api/`, `app/api/`, `routes/`,
|
|
117
|
+
`pages/api/`, etc.)
|
|
118
|
+
- Whether the repo ships its own `CLAUDE.md`, `Dockerfile`, or
|
|
119
|
+
`docker-compose.yml`
|
|
120
|
+
- The first non-trivial line of `README.md` as a one-line description
|
|
121
|
+
- Any `.revo/features/*.md` briefs at the workspace root, listed as
|
|
122
|
+
`## Active Features`
|
|
123
|
+
|
|
88
124
|
## Tag Filtering
|
|
89
125
|
|
|
90
126
|
Target subsets of repos using `--tag`:
|
package/dist/revo
CHANGED
|
@@ -8,7 +8,7 @@ set -euo pipefail
|
|
|
8
8
|
# Exit cleanly on SIGPIPE (e.g., revo clone | grep, revo status | head)
|
|
9
9
|
trap 'exit 0' PIPE
|
|
10
10
|
|
|
11
|
-
REVO_VERSION="0.
|
|
11
|
+
REVO_VERSION="0.4.0"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
# === lib/ui.sh ===
|
|
@@ -794,8 +794,52 @@ config_init() {
|
|
|
794
794
|
# Write config
|
|
795
795
|
yaml_write "$REVO_CONFIG_FILE"
|
|
796
796
|
|
|
797
|
-
#
|
|
798
|
-
|
|
797
|
+
# Merge revo's required entries into .gitignore without clobbering
|
|
798
|
+
# any existing user entries.
|
|
799
|
+
config_ensure_gitignore "$dir/.gitignore"
|
|
800
|
+
|
|
801
|
+
return 0
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
# Ensure the given .gitignore contains the entries revo needs
|
|
805
|
+
# (`repos/` and `.revo/`). Existing content is preserved; only missing
|
|
806
|
+
# entries are appended. Creates the file if it doesn't exist.
|
|
807
|
+
# Usage: config_ensure_gitignore "/path/to/.gitignore"
|
|
808
|
+
config_ensure_gitignore() {
|
|
809
|
+
local gitignore="$1"
|
|
810
|
+
local needed
|
|
811
|
+
needed=$(printf 'repos/\n.revo/\n')
|
|
812
|
+
|
|
813
|
+
if [[ ! -f "$gitignore" ]]; then
|
|
814
|
+
printf '%s\n' "$needed" > "$gitignore"
|
|
815
|
+
return 0
|
|
816
|
+
fi
|
|
817
|
+
|
|
818
|
+
# Append only the entries that aren't already present (exact line match,
|
|
819
|
+
# ignoring leading/trailing whitespace and comments).
|
|
820
|
+
local entry has_entry needs_newline=0
|
|
821
|
+
# If the file is non-empty and doesn't end in a newline, we need to add
|
|
822
|
+
# one before appending so we don't merge into an existing line.
|
|
823
|
+
if [[ -s "$gitignore" ]] && [[ -n "$(tail -c1 "$gitignore" 2>/dev/null)" ]]; then
|
|
824
|
+
needs_newline=1
|
|
825
|
+
fi
|
|
826
|
+
|
|
827
|
+
local appended=0
|
|
828
|
+
while IFS= read -r entry; do
|
|
829
|
+
[[ -z "$entry" ]] && continue
|
|
830
|
+
has_entry=$(awk -v e="$entry" '
|
|
831
|
+
{ sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+$/, "") }
|
|
832
|
+
$0 == e { found = 1; exit }
|
|
833
|
+
END { exit !found }
|
|
834
|
+
' "$gitignore" && printf 'yes' || printf 'no')
|
|
835
|
+
if [[ "$has_entry" == "no" ]]; then
|
|
836
|
+
if [[ $appended -eq 0 ]] && [[ $needs_newline -eq 1 ]]; then
|
|
837
|
+
printf '\n' >> "$gitignore"
|
|
838
|
+
fi
|
|
839
|
+
printf '%s\n' "$entry" >> "$gitignore"
|
|
840
|
+
appended=1
|
|
841
|
+
fi
|
|
842
|
+
done <<< "$needed"
|
|
799
843
|
|
|
800
844
|
return 0
|
|
801
845
|
}
|
|
@@ -1232,6 +1276,10 @@ _scan_node_framework() {
|
|
|
1232
1276
|
printf 'SvelteKit'
|
|
1233
1277
|
elif _scan_pkg_has_dep "$file" "astro"; then
|
|
1234
1278
|
printf 'Astro'
|
|
1279
|
+
elif _scan_pkg_has_dep "$file" "expo"; then
|
|
1280
|
+
printf 'Expo (React Native)'
|
|
1281
|
+
elif _scan_pkg_has_dep "$file" "react-native"; then
|
|
1282
|
+
printf 'React Native'
|
|
1235
1283
|
elif _scan_pkg_has_dep "$file" "vite"; then
|
|
1236
1284
|
printf 'Vite'
|
|
1237
1285
|
elif _scan_pkg_has_dep "$file" "nestjs" || _scan_pkg_has_dep "$file" "@nestjs/core"; then
|
|
@@ -1242,6 +1290,8 @@ _scan_node_framework() {
|
|
|
1242
1290
|
printf 'Express'
|
|
1243
1291
|
elif _scan_pkg_has_dep "$file" "hono"; then
|
|
1244
1292
|
printf 'Hono'
|
|
1293
|
+
elif _scan_pkg_has_dep "$file" "@angular/core"; then
|
|
1294
|
+
printf 'Angular'
|
|
1245
1295
|
elif _scan_pkg_has_dep "$file" "react"; then
|
|
1246
1296
|
printf 'React'
|
|
1247
1297
|
elif _scan_pkg_has_dep "$file" "vue"; then
|
|
@@ -1251,6 +1301,16 @@ _scan_node_framework() {
|
|
|
1251
1301
|
fi
|
|
1252
1302
|
}
|
|
1253
1303
|
|
|
1304
|
+
# Detect framework from a Gemfile
|
|
1305
|
+
_scan_ruby_framework() {
|
|
1306
|
+
local file="$1"
|
|
1307
|
+
if grep -q "rails" "$file" 2>/dev/null; then
|
|
1308
|
+
printf 'Rails'
|
|
1309
|
+
elif grep -q "sinatra" "$file" 2>/dev/null; then
|
|
1310
|
+
printf 'Sinatra'
|
|
1311
|
+
fi
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1254
1314
|
# Detect framework from Python dependency files
|
|
1255
1315
|
_scan_python_framework() {
|
|
1256
1316
|
local repo_dir="$1"
|
|
@@ -1387,6 +1447,27 @@ scan_repo() {
|
|
|
1387
1447
|
SCAN_NAME=$(grep -m1 -E '^[[:space:]]*name[[:space:]]*=' "$repo_dir/Cargo.toml" 2>/dev/null | sed -E 's/.*=[[:space:]]*"([^"]*)".*/\1/' || true)
|
|
1388
1448
|
fi
|
|
1389
1449
|
|
|
1450
|
+
# Java/Kotlin (Gradle)
|
|
1451
|
+
if [[ -z "$SCAN_LANG" ]] && { [[ -f "$repo_dir/build.gradle" ]] || [[ -f "$repo_dir/build.gradle.kts" ]]; }; then
|
|
1452
|
+
SCAN_LANG="Java/Kotlin (Gradle)"
|
|
1453
|
+
fi
|
|
1454
|
+
|
|
1455
|
+
# Java (Maven)
|
|
1456
|
+
if [[ -z "$SCAN_LANG" ]] && [[ -f "$repo_dir/pom.xml" ]]; then
|
|
1457
|
+
SCAN_LANG="Java (Maven)"
|
|
1458
|
+
fi
|
|
1459
|
+
|
|
1460
|
+
# Ruby
|
|
1461
|
+
if [[ -z "$SCAN_LANG" ]] && [[ -f "$repo_dir/Gemfile" ]]; then
|
|
1462
|
+
SCAN_LANG="Ruby"
|
|
1463
|
+
SCAN_FRAMEWORK=$(_scan_ruby_framework "$repo_dir/Gemfile")
|
|
1464
|
+
fi
|
|
1465
|
+
|
|
1466
|
+
# Swift
|
|
1467
|
+
if [[ -z "$SCAN_LANG" ]] && [[ -f "$repo_dir/Package.swift" ]]; then
|
|
1468
|
+
SCAN_LANG="Swift"
|
|
1469
|
+
fi
|
|
1470
|
+
|
|
1390
1471
|
# Routes
|
|
1391
1472
|
SCAN_ROUTES=$(_scan_list_routes "$repo_dir")
|
|
1392
1473
|
|
|
@@ -1412,12 +1493,149 @@ scan_repo() {
|
|
|
1412
1493
|
|
|
1413
1494
|
# === lib/commands/init.sh ===
|
|
1414
1495
|
# Revo CLI - init command
|
|
1415
|
-
#
|
|
1496
|
+
# Initializes a workspace and auto-detects existing git repos in the current
|
|
1497
|
+
# directory (and in repos/), so that running `revo init` in a folder you
|
|
1498
|
+
# already populated with clones bootstraps a usable workspace immediately.
|
|
1499
|
+
|
|
1500
|
+
# Suggest tags for an auto-detected repo based on its package contents.
|
|
1501
|
+
# Always returns the repo name as a tag, plus an optional category tag
|
|
1502
|
+
# (frontend/backend) when one can be inferred. Skips the category when it
|
|
1503
|
+
# would be a duplicate of the name.
|
|
1504
|
+
_init_auto_tags() {
|
|
1505
|
+
local dir="$1"
|
|
1506
|
+
local name="$2"
|
|
1507
|
+
local category=""
|
|
1508
|
+
|
|
1509
|
+
if [[ -f "$dir/package.json" ]]; then
|
|
1510
|
+
if grep -qE '"(next|nuxt|react|vue|svelte|@angular/core|astro|@remix-run/react|@sveltejs/kit|expo|react-native)"' "$dir/package.json" 2>/dev/null; then
|
|
1511
|
+
category="frontend"
|
|
1512
|
+
elif grep -qE '"(express|fastify|hono|@nestjs/core|nestjs|koa)"' "$dir/package.json" 2>/dev/null; then
|
|
1513
|
+
category="backend"
|
|
1514
|
+
fi
|
|
1515
|
+
elif [[ -f "$dir/go.mod" ]] || [[ -f "$dir/Cargo.toml" ]] || [[ -f "$dir/pom.xml" ]] || [[ -f "$dir/build.gradle" ]] || [[ -f "$dir/build.gradle.kts" ]]; then
|
|
1516
|
+
category="backend"
|
|
1517
|
+
elif [[ -f "$dir/pyproject.toml" ]] || [[ -f "$dir/requirements.txt" ]]; then
|
|
1518
|
+
category="backend"
|
|
1519
|
+
fi
|
|
1520
|
+
|
|
1521
|
+
if [[ -n "$category" ]] && [[ "$category" != "$name" ]]; then
|
|
1522
|
+
printf '%s, %s' "$name" "$category"
|
|
1523
|
+
else
|
|
1524
|
+
printf '%s' "$name"
|
|
1525
|
+
fi
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
# Scan for git repos in the current directory and in repos/.
|
|
1529
|
+
# Sets _INIT_FOUND_DIRS to a newline-separated list of repo paths.
|
|
1530
|
+
_INIT_FOUND_DIRS=""
|
|
1531
|
+
_init_scan_existing() {
|
|
1532
|
+
_INIT_FOUND_DIRS=""
|
|
1533
|
+
|
|
1534
|
+
local d
|
|
1535
|
+
for d in */; do
|
|
1536
|
+
d="${d%/}"
|
|
1537
|
+
[[ "$d" == "repos" ]] && continue
|
|
1538
|
+
[[ "$d" == ".revo" ]] && continue
|
|
1539
|
+
if [[ -d "$d/.git" ]]; then
|
|
1540
|
+
if [[ -z "$_INIT_FOUND_DIRS" ]]; then
|
|
1541
|
+
_INIT_FOUND_DIRS="$d"
|
|
1542
|
+
else
|
|
1543
|
+
_INIT_FOUND_DIRS="$_INIT_FOUND_DIRS"$'\n'"$d"
|
|
1544
|
+
fi
|
|
1545
|
+
fi
|
|
1546
|
+
done
|
|
1547
|
+
|
|
1548
|
+
if [[ -d "repos" ]]; then
|
|
1549
|
+
for d in repos/*/; do
|
|
1550
|
+
d="${d%/}"
|
|
1551
|
+
if [[ -d "$d/.git" ]]; then
|
|
1552
|
+
if [[ -z "$_INIT_FOUND_DIRS" ]]; then
|
|
1553
|
+
_INIT_FOUND_DIRS="$d"
|
|
1554
|
+
else
|
|
1555
|
+
_INIT_FOUND_DIRS="$_INIT_FOUND_DIRS"$'\n'"$d"
|
|
1556
|
+
fi
|
|
1557
|
+
fi
|
|
1558
|
+
done
|
|
1559
|
+
fi
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
# Write a Claude-first onboarding CLAUDE.md if and only if the workspace
|
|
1563
|
+
# root has no CLAUDE.md yet. Existing files are left alone — `revo context`
|
|
1564
|
+
# will append its marker-wrapped auto block once repos are added.
|
|
1565
|
+
_init_write_claude_md() {
|
|
1566
|
+
local out="$REVO_WORKSPACE_ROOT/CLAUDE.md"
|
|
1567
|
+
|
|
1568
|
+
if [[ -f "$out" ]]; then
|
|
1569
|
+
return 0
|
|
1570
|
+
fi
|
|
1571
|
+
|
|
1572
|
+
cat > "$out" << 'EOF'
|
|
1573
|
+
# Workspace managed by revo
|
|
1574
|
+
|
|
1575
|
+
This is a multi-repo workspace managed by revo.
|
|
1576
|
+
revo is installed and available in the terminal.
|
|
1577
|
+
|
|
1578
|
+
## Quick reference
|
|
1579
|
+
|
|
1580
|
+
### If repos are not yet added
|
|
1581
|
+
The user may give you repo URLs or descriptions. Use these commands to set up:
|
|
1582
|
+
|
|
1583
|
+
```bash
|
|
1584
|
+
revo add <git-url> --tags <tag1,tag2> [--depends-on <repo-name>]
|
|
1585
|
+
revo clone
|
|
1586
|
+
revo context # regenerates this file with full repo details
|
|
1587
|
+
```
|
|
1588
|
+
|
|
1589
|
+
Example:
|
|
1590
|
+
```bash
|
|
1591
|
+
revo add git@github.com:org/shared.git --tags shared,types
|
|
1592
|
+
revo add git@github.com:org/backend.git --tags backend,api --depends-on shared
|
|
1593
|
+
revo add git@github.com:org/frontend.git --tags frontend,web --depends-on backend
|
|
1594
|
+
revo clone
|
|
1595
|
+
```
|
|
1596
|
+
|
|
1597
|
+
### If repos are already set up
|
|
1598
|
+
Use `revo status` to see all repos, branches, and dirty state.
|
|
1599
|
+
|
|
1600
|
+
## Available commands
|
|
1601
|
+
- `revo status` — branch and dirty state across all repos
|
|
1602
|
+
- `revo sync` — pull latest across all repos
|
|
1603
|
+
- `revo feature <name>` — create feature branch across all repos
|
|
1604
|
+
- `revo commit "msg"` — commit all dirty repos with same message
|
|
1605
|
+
- `revo push` — push all repos
|
|
1606
|
+
- `revo pr "title"` — create coordinated PRs via gh CLI
|
|
1607
|
+
- `revo exec "cmd" --tag <tag>` — run command in filtered repos
|
|
1608
|
+
- `revo context` — regenerate this file after repos change
|
|
1609
|
+
- `revo add <url> --tags <t> --depends-on <d>` — add a repo
|
|
1610
|
+
|
|
1611
|
+
### Tag filtering
|
|
1612
|
+
All commands support `--tag <tag>` to target specific repos:
|
|
1613
|
+
|
|
1614
|
+
```bash
|
|
1615
|
+
revo exec "npm test" --tag frontend
|
|
1616
|
+
revo sync --tag backend
|
|
1617
|
+
revo branch hotfix --tag api
|
|
1618
|
+
```
|
|
1619
|
+
|
|
1620
|
+
## Working in this workspace
|
|
1621
|
+
- Repos are in the repos/ subdirectory (or as configured in revo.yaml)
|
|
1622
|
+
- Edit files across repos/ directly — you have full access
|
|
1623
|
+
- When making cross-repo changes, follow the dependency order below
|
|
1624
|
+
- Check .revo/features/ for active feature briefs
|
|
1625
|
+
- Use revo commands instead of manually running git in each repo
|
|
1626
|
+
|
|
1627
|
+
## Dependency order
|
|
1628
|
+
<!-- revo context will fill this in once repos are cloned -->
|
|
1629
|
+
Run `revo context` after cloning to populate repo details and dependency order.
|
|
1630
|
+
EOF
|
|
1631
|
+
|
|
1632
|
+
ui_step_done "Created CLAUDE.md (Claude reads this automatically)"
|
|
1633
|
+
}
|
|
1416
1634
|
|
|
1417
1635
|
cmd_init() {
|
|
1418
1636
|
local workspace_name=""
|
|
1419
1637
|
|
|
1420
|
-
#
|
|
1638
|
+
# Already initialized? Bail out.
|
|
1421
1639
|
if [[ -f "revo.yaml" ]] || [[ -f "mars.yaml" ]]; then
|
|
1422
1640
|
ui_step_error "Workspace already initialized in this directory"
|
|
1423
1641
|
return 1
|
|
@@ -1425,20 +1643,21 @@ cmd_init() {
|
|
|
1425
1643
|
|
|
1426
1644
|
ui_intro "Revo - Claude-first Multi-Repo Workspace"
|
|
1427
1645
|
|
|
1428
|
-
#
|
|
1646
|
+
# Default workspace name to current directory basename so init can run
|
|
1647
|
+
# non-interactively. The user can still override by typing a value.
|
|
1648
|
+
local default_name
|
|
1649
|
+
default_name=$(basename "$PWD")
|
|
1429
1650
|
ui_step "Workspace name?"
|
|
1430
1651
|
printf '%s ' "$(ui_bar)"
|
|
1431
1652
|
read -r workspace_name
|
|
1432
|
-
|
|
1433
1653
|
if [[ -z "$workspace_name" ]]; then
|
|
1434
|
-
|
|
1435
|
-
return 1
|
|
1654
|
+
workspace_name="$default_name"
|
|
1436
1655
|
fi
|
|
1437
1656
|
|
|
1438
1657
|
ui_step_done "Workspace:" "$workspace_name"
|
|
1439
1658
|
ui_bar_line
|
|
1440
1659
|
|
|
1441
|
-
# Initialize
|
|
1660
|
+
# Initialize the on-disk config.
|
|
1442
1661
|
if ! config_init "$workspace_name"; then
|
|
1443
1662
|
ui_step_error "Failed to initialize workspace"
|
|
1444
1663
|
return 1
|
|
@@ -1448,8 +1667,179 @@ cmd_init() {
|
|
|
1448
1667
|
ui_step_done "Created .gitignore"
|
|
1449
1668
|
ui_step_done "Created repos/ directory"
|
|
1450
1669
|
|
|
1670
|
+
# Auto-detect existing git repos in the current directory.
|
|
1671
|
+
_init_scan_existing
|
|
1672
|
+
local detected_count=0
|
|
1673
|
+
if [[ -n "$_INIT_FOUND_DIRS" ]]; then
|
|
1674
|
+
local dir name remote tags path
|
|
1675
|
+
while IFS= read -r dir; do
|
|
1676
|
+
[[ -z "$dir" ]] && continue
|
|
1677
|
+
name=$(basename "$dir")
|
|
1678
|
+
remote=$(cd "$dir" && git remote get-url origin 2>/dev/null || true)
|
|
1679
|
+
if [[ -z "$remote" ]]; then
|
|
1680
|
+
ui_step_error "Skipping $name (no git remote)"
|
|
1681
|
+
continue
|
|
1682
|
+
fi
|
|
1683
|
+
|
|
1684
|
+
path="$name"
|
|
1685
|
+
|
|
1686
|
+
# If the repo lives at the workspace root (not under repos/),
|
|
1687
|
+
# link it into repos/ so the rest of revo's data model — which
|
|
1688
|
+
# always resolves $REVO_REPOS_DIR/$path — works without copying
|
|
1689
|
+
# or moving files.
|
|
1690
|
+
if [[ "$dir" != repos/* ]]; then
|
|
1691
|
+
if [[ ! -e "repos/$name" ]]; then
|
|
1692
|
+
ln -s "../$name" "repos/$name"
|
|
1693
|
+
fi
|
|
1694
|
+
fi
|
|
1695
|
+
|
|
1696
|
+
tags=$(_init_auto_tags "$dir" "$name")
|
|
1697
|
+
yaml_add_repo "$remote" "$path" "$tags" ""
|
|
1698
|
+
ui_step_done "Detected:" "$name → $remote"
|
|
1699
|
+
detected_count=$((detected_count + 1))
|
|
1700
|
+
done <<< "$_INIT_FOUND_DIRS"
|
|
1701
|
+
|
|
1702
|
+
if [[ $detected_count -gt 0 ]]; then
|
|
1703
|
+
config_save
|
|
1704
|
+
ui_info "$(ui_dim "Edit revo.yaml to adjust tags or add depends_on relationships")"
|
|
1705
|
+
fi
|
|
1706
|
+
fi
|
|
1707
|
+
|
|
1708
|
+
# If we detected repos, hand off to cmd_context — it now wraps its output
|
|
1709
|
+
# in BEGIN/END markers and preserves any user content in CLAUDE.md, so we
|
|
1710
|
+
# don't need the onboarding placeholder. When no repos were detected,
|
|
1711
|
+
# write the placeholder so Claude has something to read.
|
|
1712
|
+
if [[ $detected_count -gt 0 ]]; then
|
|
1713
|
+
ui_bar_line
|
|
1714
|
+
cmd_context
|
|
1715
|
+
return 0
|
|
1716
|
+
fi
|
|
1717
|
+
|
|
1718
|
+
_init_write_claude_md
|
|
1719
|
+
|
|
1451
1720
|
ui_outro "Workspace initialized! Run 'revo add <url>' to add repositories."
|
|
1721
|
+
return 0
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
# === lib/commands/detect.sh ===
|
|
1725
|
+
# Revo CLI - detect command
|
|
1726
|
+
# Bootstraps a workspace around git repos that already exist in the current
|
|
1727
|
+
# directory. Use this when you have a parent folder full of clones and want
|
|
1728
|
+
# revo to wrap them without re-cloning.
|
|
1452
1729
|
|
|
1730
|
+
cmd_detect() {
|
|
1731
|
+
while [[ $# -gt 0 ]]; do
|
|
1732
|
+
case "$1" in
|
|
1733
|
+
--help|-h)
|
|
1734
|
+
printf 'Usage: revo detect\n\n'
|
|
1735
|
+
printf 'Auto-detect git repositories in the current directory and\n'
|
|
1736
|
+
printf 'bootstrap a revo workspace around them. Generates revo.yaml\n'
|
|
1737
|
+
printf 'and CLAUDE.md from what it finds.\n'
|
|
1738
|
+
return 0
|
|
1739
|
+
;;
|
|
1740
|
+
*)
|
|
1741
|
+
ui_step_error "Unknown option: $1"
|
|
1742
|
+
return 1
|
|
1743
|
+
;;
|
|
1744
|
+
esac
|
|
1745
|
+
done
|
|
1746
|
+
|
|
1747
|
+
if [[ -f "revo.yaml" ]] || [[ -f "mars.yaml" ]]; then
|
|
1748
|
+
ui_step_error "Workspace already initialized — run 'revo context' to regenerate CLAUDE.md"
|
|
1749
|
+
return 1
|
|
1750
|
+
fi
|
|
1751
|
+
|
|
1752
|
+
ui_intro "Revo - Detect Existing Repositories"
|
|
1753
|
+
ui_step "Scanning for git repos..."
|
|
1754
|
+
|
|
1755
|
+
# Initialize the workspace using the cwd basename. This sets
|
|
1756
|
+
# REVO_WORKSPACE_ROOT/REVO_CONFIG_FILE/REVO_REPOS_DIR and writes an empty
|
|
1757
|
+
# revo.yaml + .gitignore.
|
|
1758
|
+
local default_name
|
|
1759
|
+
default_name=$(basename "$PWD")
|
|
1760
|
+
if ! config_init "$default_name"; then
|
|
1761
|
+
ui_step_error "Failed to initialize workspace"
|
|
1762
|
+
return 1
|
|
1763
|
+
fi
|
|
1764
|
+
|
|
1765
|
+
local found=0
|
|
1766
|
+
local d name remote category tags
|
|
1767
|
+
|
|
1768
|
+
for d in */; do
|
|
1769
|
+
d="${d%/}"
|
|
1770
|
+
[[ "$d" == "repos" ]] && continue
|
|
1771
|
+
[[ "$d" == ".revo" ]] && continue
|
|
1772
|
+
[[ -d "$d/.git" ]] || continue
|
|
1773
|
+
|
|
1774
|
+
name="$d"
|
|
1775
|
+
remote=$(cd "$d" && git remote get-url origin 2>/dev/null || echo "local://$d")
|
|
1776
|
+
|
|
1777
|
+
# Auto-categorize from package contents.
|
|
1778
|
+
category=""
|
|
1779
|
+
if [[ -f "$d/package.json" ]]; then
|
|
1780
|
+
if grep -qE '"(next|nuxt|react|vue|svelte|@angular/core|astro|@remix-run/react|@sveltejs/kit|expo|react-native)"' "$d/package.json" 2>/dev/null; then
|
|
1781
|
+
category="frontend"
|
|
1782
|
+
elif grep -qE '"(express|fastify|hono|@nestjs/core|nestjs|koa)"' "$d/package.json" 2>/dev/null; then
|
|
1783
|
+
category="backend"
|
|
1784
|
+
fi
|
|
1785
|
+
elif [[ -f "$d/go.mod" ]] || [[ -f "$d/Cargo.toml" ]] || [[ -f "$d/pom.xml" ]] || [[ -f "$d/build.gradle" ]] || [[ -f "$d/build.gradle.kts" ]]; then
|
|
1786
|
+
category="backend"
|
|
1787
|
+
elif [[ -f "$d/pyproject.toml" ]] || [[ -f "$d/requirements.txt" ]]; then
|
|
1788
|
+
category="backend"
|
|
1789
|
+
fi
|
|
1790
|
+
|
|
1791
|
+
if [[ -n "$category" ]] && [[ "$category" != "$name" ]]; then
|
|
1792
|
+
tags="$name, $category"
|
|
1793
|
+
else
|
|
1794
|
+
tags="$name"
|
|
1795
|
+
fi
|
|
1796
|
+
|
|
1797
|
+
# Link the root-level repo into repos/ so the rest of revo's data
|
|
1798
|
+
# model (which always resolves $REVO_REPOS_DIR/$path) works.
|
|
1799
|
+
if [[ ! -e "repos/$name" ]]; then
|
|
1800
|
+
ln -s "../$name" "repos/$name"
|
|
1801
|
+
fi
|
|
1802
|
+
|
|
1803
|
+
yaml_add_repo "$remote" "$name" "$tags" ""
|
|
1804
|
+
ui_step_done "Found:" "$name ($remote)"
|
|
1805
|
+
found=$((found + 1))
|
|
1806
|
+
done
|
|
1807
|
+
|
|
1808
|
+
if [[ -d "repos" ]]; then
|
|
1809
|
+
for d in repos/*/; do
|
|
1810
|
+
d="${d%/}"
|
|
1811
|
+
[[ -d "$d/.git" ]] || continue
|
|
1812
|
+
name=$(basename "$d")
|
|
1813
|
+
# Skip repos already added via symlink in the loop above.
|
|
1814
|
+
local already=0
|
|
1815
|
+
local i
|
|
1816
|
+
for ((i = 0; i < YAML_REPO_COUNT; i++)); do
|
|
1817
|
+
if [[ "$(yaml_get_path "$i")" == "$name" ]]; then
|
|
1818
|
+
already=1
|
|
1819
|
+
break
|
|
1820
|
+
fi
|
|
1821
|
+
done
|
|
1822
|
+
[[ $already -eq 1 ]] && continue
|
|
1823
|
+
remote=$(cd "$d" && git remote get-url origin 2>/dev/null || echo "local://$d")
|
|
1824
|
+
yaml_add_repo "$remote" "$name" "$name" ""
|
|
1825
|
+
ui_step_done "Found:" "$name ($remote)"
|
|
1826
|
+
found=$((found + 1))
|
|
1827
|
+
done
|
|
1828
|
+
fi
|
|
1829
|
+
|
|
1830
|
+
if [[ $found -eq 0 ]]; then
|
|
1831
|
+
rm -f "$REVO_CONFIG_FILE"
|
|
1832
|
+
ui_step_error "No git repos found in current directory. Use 'revo init' instead."
|
|
1833
|
+
ui_outro_cancel "Nothing to detect"
|
|
1834
|
+
return 1
|
|
1835
|
+
fi
|
|
1836
|
+
|
|
1837
|
+
config_save
|
|
1838
|
+
ui_step_done "Detected $found repository(ies)"
|
|
1839
|
+
ui_info "$(ui_dim "Edit revo.yaml to adjust tags or add depends_on relationships")"
|
|
1840
|
+
ui_bar_line
|
|
1841
|
+
|
|
1842
|
+
cmd_context
|
|
1453
1843
|
return 0
|
|
1454
1844
|
}
|
|
1455
1845
|
|
|
@@ -1548,9 +1938,10 @@ cmd_clone() {
|
|
|
1548
1938
|
fi
|
|
1549
1939
|
done <<< "$repos"
|
|
1550
1940
|
|
|
1551
|
-
#
|
|
1552
|
-
|
|
1553
|
-
|
|
1941
|
+
# Always regenerate the workspace CLAUDE.md after a clone batch so that
|
|
1942
|
+
# newly cloned repos appear in the context immediately.
|
|
1943
|
+
if [[ $fail_count -eq 0 ]] && { [[ $success_count -gt 0 ]] || [[ $skip_count -gt 0 ]]; }; then
|
|
1944
|
+
context_regenerate_silent
|
|
1554
1945
|
fi
|
|
1555
1946
|
|
|
1556
1947
|
# Summary
|
|
@@ -2375,15 +2766,75 @@ _context_topo_sort() {
|
|
|
2375
2766
|
return 0
|
|
2376
2767
|
}
|
|
2377
2768
|
|
|
2378
|
-
#
|
|
2769
|
+
# Markers used to delimit revo's auto-generated block inside the workspace
|
|
2770
|
+
# CLAUDE.md. Anything between these markers is owned by `revo context` and
|
|
2771
|
+
# replaced on every regeneration. Anything outside is preserved untouched.
|
|
2772
|
+
CONTEXT_BEGIN_MARKER='<!-- BEGIN revo:auto - regenerated by `revo context`, do not edit between markers -->'
|
|
2773
|
+
CONTEXT_END_MARKER='<!-- END revo:auto -->'
|
|
2774
|
+
|
|
2775
|
+
# Splice an auto-generated block into the target CLAUDE.md without
|
|
2776
|
+
# clobbering user content.
|
|
2777
|
+
# - If target doesn't exist: copy auto file to target.
|
|
2778
|
+
# - If target has BEGIN+END markers: replace the marker block in place.
|
|
2779
|
+
# - Otherwise: append the auto block (with a separator) at the end.
|
|
2780
|
+
# Usage: _context_merge_into "/tmp/auto" "/path/to/CLAUDE.md"
|
|
2781
|
+
_context_merge_into() {
|
|
2782
|
+
local auto_file="$1"
|
|
2783
|
+
local target="$2"
|
|
2784
|
+
|
|
2785
|
+
if [[ ! -f "$target" ]]; then
|
|
2786
|
+
cp "$auto_file" "$target"
|
|
2787
|
+
return 0
|
|
2788
|
+
fi
|
|
2789
|
+
|
|
2790
|
+
if grep -qF "$CONTEXT_BEGIN_MARKER" "$target" 2>/dev/null \
|
|
2791
|
+
&& grep -qF "$CONTEXT_END_MARKER" "$target" 2>/dev/null; then
|
|
2792
|
+
local merged
|
|
2793
|
+
merged=$(mktemp -t revo-merge.XXXXXX)
|
|
2794
|
+
awk -v auto="$auto_file" -v begin="$CONTEXT_BEGIN_MARKER" -v end="$CONTEXT_END_MARKER" '
|
|
2795
|
+
BEGIN { in_block = 0 }
|
|
2796
|
+
index($0, begin) == 1 {
|
|
2797
|
+
in_block = 1
|
|
2798
|
+
while ((getline line < auto) > 0) print line
|
|
2799
|
+
close(auto)
|
|
2800
|
+
next
|
|
2801
|
+
}
|
|
2802
|
+
index($0, end) == 1 && in_block {
|
|
2803
|
+
in_block = 0
|
|
2804
|
+
next
|
|
2805
|
+
}
|
|
2806
|
+
!in_block { print }
|
|
2807
|
+
' "$target" > "$merged"
|
|
2808
|
+
mv "$merged" "$target"
|
|
2809
|
+
return 0
|
|
2810
|
+
fi
|
|
2811
|
+
|
|
2812
|
+
# No markers — append at the end with a blank-line separator. If the
|
|
2813
|
+
# existing file doesn't end in a newline, add one first so we don't merge
|
|
2814
|
+
# into the user's last line.
|
|
2815
|
+
if [[ -s "$target" ]] && [[ -n "$(tail -c1 "$target" 2>/dev/null)" ]]; then
|
|
2816
|
+
printf '\n' >> "$target"
|
|
2817
|
+
fi
|
|
2818
|
+
printf '\n' >> "$target"
|
|
2819
|
+
cat "$auto_file" >> "$target"
|
|
2820
|
+
return 0
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
# Write the workspace CLAUDE.md to the given path. The auto-generated content
|
|
2824
|
+
# is wrapped in BEGIN/END markers and spliced into any existing CLAUDE.md so
|
|
2825
|
+
# user content above and below the markers is preserved across regenerations.
|
|
2379
2826
|
# Usage: _context_write_file "/path/to/CLAUDE.md"
|
|
2380
2827
|
_context_write_file() {
|
|
2381
|
-
local
|
|
2828
|
+
local target="$1"
|
|
2382
2829
|
local workspace
|
|
2383
2830
|
workspace=$(config_workspace_name)
|
|
2384
2831
|
|
|
2385
|
-
|
|
2832
|
+
local output
|
|
2833
|
+
output=$(mktemp -t revo-auto.XXXXXX)
|
|
2834
|
+
|
|
2835
|
+
# Start auto block (markers + header)
|
|
2386
2836
|
{
|
|
2837
|
+
printf '%s\n' "$CONTEXT_BEGIN_MARKER"
|
|
2387
2838
|
printf '# Workspace Context (auto-generated by revo)\n'
|
|
2388
2839
|
printf '\n'
|
|
2389
2840
|
printf 'This workspace contains multiple repositories managed by revo.\n'
|
|
@@ -2391,8 +2842,9 @@ _context_write_file() {
|
|
|
2391
2842
|
printf 'Workspace: **%s**\n' "$workspace"
|
|
2392
2843
|
fi
|
|
2393
2844
|
printf '\n'
|
|
2394
|
-
printf '> This
|
|
2395
|
-
printf '>
|
|
2845
|
+
printf '> This block is auto-generated by `revo context`. Edits between the\n'
|
|
2846
|
+
printf '> BEGIN/END markers will be lost on regeneration. Add your own content\n'
|
|
2847
|
+
printf '> above or below the markers.\n'
|
|
2396
2848
|
printf '\n'
|
|
2397
2849
|
printf '## Repos\n'
|
|
2398
2850
|
printf '\n'
|
|
@@ -2489,7 +2941,29 @@ _context_write_file() {
|
|
|
2489
2941
|
printf '\n> Warning: a dependency cycle was detected. Listed in best-effort order.\n' >> "$output"
|
|
2490
2942
|
fi
|
|
2491
2943
|
|
|
2492
|
-
#
|
|
2944
|
+
# Active features (any .revo/features/*.md files)
|
|
2945
|
+
local features_dir="$REVO_WORKSPACE_ROOT/.revo/features"
|
|
2946
|
+
if [[ -d "$features_dir" ]]; then
|
|
2947
|
+
local has_features=0
|
|
2948
|
+
local f
|
|
2949
|
+
for f in "$features_dir"/*.md; do
|
|
2950
|
+
[[ -f "$f" ]] || continue
|
|
2951
|
+
if [[ $has_features -eq 0 ]]; then
|
|
2952
|
+
{
|
|
2953
|
+
printf '\n'
|
|
2954
|
+
printf '## Active Features\n'
|
|
2955
|
+
printf '\n'
|
|
2956
|
+
} >> "$output"
|
|
2957
|
+
has_features=1
|
|
2958
|
+
fi
|
|
2959
|
+
local fname rel
|
|
2960
|
+
fname=$(basename "$f" .md)
|
|
2961
|
+
rel=".revo/features/$(basename "$f")"
|
|
2962
|
+
printf -- '- **%s** — see [%s](%s)\n' "$fname" "$rel" "$rel" >> "$output"
|
|
2963
|
+
done
|
|
2964
|
+
fi
|
|
2965
|
+
|
|
2966
|
+
# Agent instructions + revo tool docs
|
|
2493
2967
|
{
|
|
2494
2968
|
printf '\n'
|
|
2495
2969
|
printf '## Agent Instructions\n'
|
|
@@ -2504,7 +2978,56 @@ _context_write_file() {
|
|
|
2504
2978
|
printf '6. Use `revo commit "msg"` to commit across all repos at once\n'
|
|
2505
2979
|
printf '7. Use `revo feature <name>` to start a coordinated feature workspace\n'
|
|
2506
2980
|
printf '8. Use `revo pr "title"` to open coordinated pull requests\n'
|
|
2981
|
+
printf '\n'
|
|
2982
|
+
printf '## Workspace Tool: revo\n'
|
|
2983
|
+
printf '\n'
|
|
2984
|
+
printf 'This workspace is managed by revo.\n'
|
|
2985
|
+
printf 'Source: https://github.com/jippylong12/revo\n'
|
|
2986
|
+
printf '\n'
|
|
2987
|
+
printf '### Available commands (run in terminal)\n'
|
|
2988
|
+
printf '\n'
|
|
2989
|
+
printf '**Setup:**\n'
|
|
2990
|
+
printf -- '- `revo add <git-url> --tags <tags> [--depends-on <repo>]` — add a repo\n'
|
|
2991
|
+
printf -- '- `revo clone` — clone all configured repos\n'
|
|
2992
|
+
printf -- '- `revo context` — regenerate this file\n'
|
|
2993
|
+
printf -- '- `revo detect` — bootstrap around existing repos in cwd\n'
|
|
2994
|
+
printf '\n'
|
|
2995
|
+
printf '**Daily workflow:**\n'
|
|
2996
|
+
printf -- '- `revo status` — branch and dirty state across all repos\n'
|
|
2997
|
+
printf -- '- `revo sync` — pull latest across all repos\n'
|
|
2998
|
+
printf -- '- `revo feature <name> [--tag t]` — create feature branch across repos\n'
|
|
2999
|
+
printf -- '- `revo commit "msg" [--tag t]` — commit all dirty repos\n'
|
|
3000
|
+
printf -- '- `revo push [--tag t]` — push all repos\n'
|
|
3001
|
+
printf -- '- `revo pr "title" [--tag t]` — create coordinated PRs via gh CLI\n'
|
|
3002
|
+
printf -- '- `revo exec "cmd" [--tag t]` — run command in filtered repos\n'
|
|
3003
|
+
printf -- '- `revo checkout <branch> [--tag t]` — switch branch across repos\n'
|
|
3004
|
+
printf '\n'
|
|
3005
|
+
printf '### Tag filtering\n'
|
|
3006
|
+
printf '\n'
|
|
3007
|
+
printf 'All commands support `--tag <tag>` to target specific repos:\n'
|
|
3008
|
+
printf '\n'
|
|
3009
|
+
printf '```\n'
|
|
3010
|
+
printf 'revo exec "npm test" --tag frontend\n'
|
|
3011
|
+
printf 'revo sync --tag backend\n'
|
|
3012
|
+
printf 'revo branch hotfix --tag api\n'
|
|
3013
|
+
printf '```\n'
|
|
3014
|
+
printf '\n'
|
|
3015
|
+
printf '### Feature workflow\n'
|
|
3016
|
+
printf '\n'
|
|
3017
|
+
printf '```\n'
|
|
3018
|
+
printf 'revo feature my-feature # branches all repos\n'
|
|
3019
|
+
printf '# edit .revo/features/my-feature.md to describe scope\n'
|
|
3020
|
+
printf '# work across repos\n'
|
|
3021
|
+
printf 'revo commit "feat: my feature" # commits all dirty repos\n'
|
|
3022
|
+
printf 'revo pr "My feature" # coordinated PRs\n'
|
|
3023
|
+
printf '```\n'
|
|
3024
|
+
printf '\n'
|
|
3025
|
+
printf '%s\n' "$CONTEXT_END_MARKER"
|
|
2507
3026
|
} >> "$output"
|
|
3027
|
+
|
|
3028
|
+
# Splice the auto block into the target file, preserving any user content.
|
|
3029
|
+
_context_merge_into "$output" "$target"
|
|
3030
|
+
rm -f "$output"
|
|
2508
3031
|
}
|
|
2509
3032
|
|
|
2510
3033
|
cmd_context() {
|
|
@@ -2551,15 +3074,15 @@ cmd_context() {
|
|
|
2551
3074
|
return 0
|
|
2552
3075
|
}
|
|
2553
3076
|
|
|
2554
|
-
# Called automatically from cmd_clone
|
|
2555
|
-
|
|
3077
|
+
# Called automatically from cmd_clone after a successful clone batch.
|
|
3078
|
+
# Always rewrites the workspace CLAUDE.md so newly cloned repos show up.
|
|
3079
|
+
context_regenerate_silent() {
|
|
2556
3080
|
[[ -z "$REVO_WORKSPACE_ROOT" ]] && return 0
|
|
2557
|
-
local output="$REVO_WORKSPACE_ROOT/CLAUDE.md"
|
|
2558
|
-
[[ -f "$output" ]] && return 0
|
|
2559
3081
|
[[ $YAML_REPO_COUNT -eq 0 ]] && return 0
|
|
2560
3082
|
|
|
3083
|
+
local output="$REVO_WORKSPACE_ROOT/CLAUDE.md"
|
|
2561
3084
|
_context_write_file "$output"
|
|
2562
|
-
ui_info "$(ui_dim "
|
|
3085
|
+
ui_info "$(ui_dim "Regenerated CLAUDE.md for Claude Code")"
|
|
2563
3086
|
return 0
|
|
2564
3087
|
}
|
|
2565
3088
|
|
|
@@ -3144,7 +3667,8 @@ Revo - Claude-first multi-repo workspace manager v$REVO_VERSION
|
|
|
3144
3667
|
Usage: revo <command> [options]
|
|
3145
3668
|
|
|
3146
3669
|
Workspace commands:
|
|
3147
|
-
init Initialize a new workspace
|
|
3670
|
+
init Initialize a new workspace (auto-detects existing repos)
|
|
3671
|
+
detect Bootstrap revo around existing git repos in cwd
|
|
3148
3672
|
add URL [--tags TAGS] Add a repository to the workspace
|
|
3149
3673
|
clone [--tag TAG] Clone configured repositories
|
|
3150
3674
|
list [--tag TAG] List configured repositories
|
|
@@ -3207,6 +3731,9 @@ main() {
|
|
|
3207
3731
|
init)
|
|
3208
3732
|
cmd_init "$@"
|
|
3209
3733
|
;;
|
|
3734
|
+
detect)
|
|
3735
|
+
cmd_detect "$@"
|
|
3736
|
+
;;
|
|
3210
3737
|
clone)
|
|
3211
3738
|
cmd_clone "$@"
|
|
3212
3739
|
;;
|