Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4

- name: Create test temp directory
run: mkdir -p /tmp/claude
run: mkdir -p /tmp/mars

- name: Run YAML parser tests
run: bash test/test_yaml.sh
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v4

- name: Create test temp directory
run: mkdir -p /tmp/claude
run: mkdir -p /tmp/mars

- name: Run YAML parser tests
run: bash test/test_yaml.sh
Expand Down
1 change: 0 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,5 @@
- Cross-repo command execution (`mars exec`)
- Tag-based filtering for all operations
- Parallel cloning (4 concurrent jobs)
- Shared Claude configuration (`claude.md`, `.claude/`)
- Clack-style terminal UI with Unicode/ASCII fallback
- Distribution via npm, Homebrew, and curl installer
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ defaults:
- Avoid subshells where possible (breaks global variable updates)
- Check return codes explicitly and propagate errors
- Use `ui_step_error()`/`ui_step_done()` for user feedback
- Tests use `/tmp/claude/` for temporary files
- Tests use `/tmp/mars/` for temporary files
- Parallel operations limited to 4 concurrent jobs (`CLONE_PARALLEL_LIMIT`)

## Release Process
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ bash test/test_config.sh
bash test/test_integration.sh
```

Tests use `/tmp/claude/` for temporary files.
Tests use `/tmp/mars/` for temporary files.

## Building

Expand Down
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

Manage multiple Git repositories as one workspace.

Tag-based filtering, parallel operations, shared Claude configuration.
Tag-based filtering, parallel operations, zero dependencies.

<p align="center">
<img src="demo.gif" alt="Mars CLI demo" width="800" />
Expand All @@ -16,7 +16,6 @@ Tag-based filtering, parallel operations, shared Claude configuration.

- **Polyrepo without the pain** — one CLI for status, branching, syncing across all repos
- **Tag-based filtering** — target subsets of repos (`--tag frontend`, `--tag backend`)
- **Shared Claude config** — `claude.md` and `.claude/` directory shared across repos
- **Zero dependencies** — pure bash 3.2+, works on macOS out of the box

## Quick Install
Expand Down Expand Up @@ -96,8 +95,6 @@ defaults:
```
my-project/
├── mars.yaml # Workspace configuration
├── claude.md # Shared Claude config (optional)
├── .claude/ # Shared Claude folder (optional)
├── .gitignore # Contains 'repos/'
└── repos/ # Cloned repositories (gitignored)
├── frontend/
Expand Down
Binary file modified demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 0 additions & 4 deletions demo.tape
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ Type "my-project"
Enter
Sleep 1s

# Confirm Claude config (press enter for yes)
Enter
Sleep 1s

# Step 2: Add repos with tags
Type "mars add https://github.com/dean0x/mars-example-frontend.git --tags frontend,web"
Enter
Expand Down
85 changes: 50 additions & 35 deletions dist/mars
Original file line number Diff line number Diff line change
Expand Up @@ -366,36 +366,75 @@ ui_progress() {
printf '] %d%%' "$percent"
}

# --- ANSI-aware padding ---

_ui_visible_len() {
local str="$1"
local stripped
stripped=$(printf '%s' "$str" | sed $'s/\033\\[[0-9;]*m//g')
printf '%d' "${#stripped}"
}

_ui_pad() {
local str="$1"
local target_width="$2"
local visible_len
visible_len=$(_ui_visible_len "$str")
local pad=$((target_width - visible_len))
[[ $pad -lt 0 ]] && pad=0
printf '%s%*s' "$str" "$pad" ""
}

# --- Table ---
# Usage: ui_table_header "Col1" "Col2" "Col3"
# ui_table_row "val1" "val2" "val3"
_TABLE_WIDTHS=()
# Usage: ui_table_widths 24 20 12 14
# ui_table_header "Col1" "Col2" "Col3" "Col4"
# ui_table_row "val1" "val2" "val3" "val4"
_TABLE_COL_WIDTHS=()

ui_table_widths() {
_TABLE_COL_WIDTHS=("$@")
}

ui_table_header() {
local cols=("$@")
_TABLE_WIDTHS=()
local i=0

printf '%s ' "$(ui_bar)"
for col in "${cols[@]}"; do
printf '%-20s' "$(ui_bold "$col")"
_TABLE_WIDTHS+=("20")
local w="${_TABLE_COL_WIDTHS[$i]:-20}"
local padded
padded=$(printf "%-${w}s" "$col")
printf '%s' "$(ui_bold "$padded")"
i=$((i + 1))
done
printf '\n'

# Separator
printf '%s ' "$(ui_bar)"
i=0
for _ in "${cols[@]}"; do
printf '%-20s' "$(ui_dim "────────────────────")"
local w="${_TABLE_COL_WIDTHS[$i]:-20}"
local dashes=""
local j=0
while [[ $j -lt $w ]]; do
dashes+="─"
j=$((j + 1))
done
printf '%s' "$(ui_dim "$dashes")"
i=$((i + 1))
done
printf '\n'
}

ui_table_row() {
local vals=("$@")
local i=0

printf '%s ' "$(ui_bar)"
for val in "${vals[@]}"; do
printf '%-20s' "$val"
local w="${_TABLE_COL_WIDTHS[$i]:-20}"
_ui_pad "$val" "$w"
i=$((i + 1))
done
printf '\n'
}
Expand Down Expand Up @@ -1089,7 +1128,6 @@ git_exec() {

cmd_init() {
local workspace_name=""
local create_claude=0

# Check if already initialized
if [[ -f "mars.yaml" ]]; then
Expand All @@ -1112,35 +1150,12 @@ cmd_init() {
ui_step_done "Workspace:" "$workspace_name"
ui_bar_line

# Ask about claude config
if ui_confirm "Create claude.md and .claude/ for shared config?"; then
create_claude=1
ui_step_done "Claude config: enabled"
else
ui_step_done "Claude config: skipped"
fi

ui_bar_line

# Initialize workspace
if ! config_init "$workspace_name"; then
ui_step_error "Failed to initialize workspace"
return 1
fi

# Create claude files if requested
if [[ $create_claude -eq 1 ]]; then
mkdir -p ".claude"
cat > "claude.md" << 'EOF'
# Workspace Configuration

This is a shared Claude configuration for the workspace.
Add your project-specific instructions here.
EOF
ui_step_done "Created claude.md"
ui_step_done "Created .claude/"
fi

ui_step_done "Created mars.yaml"
ui_step_done "Created .gitignore"
ui_step_done "Created repos/ directory"
Expand Down Expand Up @@ -1297,6 +1312,7 @@ cmd_status() {
fi

# Table header
ui_table_widths 24 20 12 14
ui_table_header "Repository" "Branch" "Status" "Sync"

local not_cloned=0
Expand Down Expand Up @@ -1558,7 +1574,6 @@ cmd_checkout() {

if [[ ${#dirty_repos[@]} -gt 0 ]]; then
ui_info "$(ui_yellow "Hint: Use --force to checkout despite uncommitted changes")"
ui_bar_line
fi

if [[ $fail_count -eq 0 ]]; then
Expand Down Expand Up @@ -1662,9 +1677,8 @@ cmd_sync() {
if [[ ${#conflict_repos[@]} -gt 0 ]]; then
ui_info "$(ui_yellow "Repositories with conflicts:")"
for r in "${conflict_repos[@]}"; do
ui_info " - $r"
ui_info " $(ui_yellow "$r")"
done
ui_bar_line
fi

if [[ $fail_count -eq 0 ]]; then
Expand Down Expand Up @@ -1927,6 +1941,7 @@ cmd_list() {
fi

# Table header
ui_table_widths 24 24 10
ui_table_header "Path" "Tags" "Cloned"

local total=0
Expand Down
1 change: 0 additions & 1 deletion lib/commands/checkout.sh
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ cmd_checkout() {

if [[ ${#dirty_repos[@]} -gt 0 ]]; then
ui_info "$(ui_yellow "Hint: Use --force to checkout despite uncommitted changes")"
ui_bar_line
fi

if [[ $fail_count -eq 0 ]]; then
Expand Down
24 changes: 0 additions & 24 deletions lib/commands/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

cmd_init() {
local workspace_name=""
local create_claude=0

# Check if already initialized
if [[ -f "mars.yaml" ]]; then
Expand All @@ -27,35 +26,12 @@ cmd_init() {
ui_step_done "Workspace:" "$workspace_name"
ui_bar_line

# Ask about claude config
if ui_confirm "Create claude.md and .claude/ for shared config?"; then
create_claude=1
ui_step_done "Claude config: enabled"
else
ui_step_done "Claude config: skipped"
fi

ui_bar_line

# Initialize workspace
if ! config_init "$workspace_name"; then
ui_step_error "Failed to initialize workspace"
return 1
fi

# Create claude files if requested
if [[ $create_claude -eq 1 ]]; then
mkdir -p ".claude"
cat > "claude.md" << 'EOF'
# Workspace Configuration

This is a shared Claude configuration for the workspace.
Add your project-specific instructions here.
EOF
ui_step_done "Created claude.md"
ui_step_done "Created .claude/"
fi

ui_step_done "Created mars.yaml"
ui_step_done "Created .gitignore"
ui_step_done "Created repos/ directory"
Expand Down
1 change: 1 addition & 0 deletions lib/commands/list.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ cmd_list() {
fi

# Table header
ui_table_widths 24 24 10
ui_table_header "Path" "Tags" "Cloned"

local total=0
Expand Down
1 change: 1 addition & 0 deletions lib/commands/status.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ cmd_status() {
fi

# Table header
ui_table_widths 24 20 12 14
ui_table_header "Repository" "Branch" "Status" "Sync"

local not_cloned=0
Expand Down
3 changes: 1 addition & 2 deletions lib/commands/sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,8 @@ cmd_sync() {
if [[ ${#conflict_repos[@]} -gt 0 ]]; then
ui_info "$(ui_yellow "Repositories with conflicts:")"
for r in "${conflict_repos[@]}"; do
ui_info " - $r"
ui_info " $(ui_yellow "$r")"
done
ui_bar_line
fi

if [[ $fail_count -eq 0 ]]; then
Expand Down
Loading