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 AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ GWW is a CLI tool (`gww`) that wraps git worktree functionality with:
- `gww add <branch> [-c] [--tag key=value]...` - Add worktree
- `gww remove <branch|path> [-f]` - Remove worktree
- `gww pull` - Update source repository
- `gww migrate <path> [--dry-run] [--move]` - Migrate repositories
- `gww migrate <path>... [--dry-run] [--copy | --inplace]` - Migrate repositories
- `gww init config` - Create default config
- `gww init shell <shell>` - Install shell completion

Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,31 +131,31 @@ gww migrate ~/old-repos --dry-run
# Output:
# Would migrate 5 repositories:
# ~/old-repos/repo1 -> ~/Developer/sources/github/user/repo1
# ~/old-repos/repo2 -> ~/Developer/sources/github/user/repo2
# ...

gww migrate ~/old-repos
# Output:
# Migrated 5 repositories
# Repaired 2 worktrees
# Copy (default): list, copy sources then worktrees, repair, summary

gww migrate ~/old-repos --inplace
# Move worktrees then sources, repair, clean empty folders
```

The `migrate` command scans a directory for git repositories and migrates them to locations based on your current configuration. It's useful when:
The `migrate` command scans one or more directories for git repositories and migrates them to locations based on your current configuration. It's useful when:
- You've updated your configuration and want to reorganize existing repositories
- You're moving from manual repository management to GWW
- You need to consolidate repositories from different locations

**Options**:
- `--dry-run`, `-n`: Show what would be migrated without making changes
- `--move`: Move repositories instead of copying (default is copy)
- `--copy` (default): Copy repositories to new locations; list, validate, copy sources then worktrees, run `git worktree repair`, then report summary. No folder cleanup.
- `--inplace`: Move repositories in place (worktrees first, then sources), run `git worktree repair`, then recursively clean empty source folders.

**Behavior**:
- Recursively scans the specified directory for git repositories
- Extracts the remote URI from each repository
- Calculates the expected location using your current config
- Migrates repositories that are in different locations than expected
- Automatically repairs worktree paths if migrating worktrees
- Skips repositories without remotes or that are already in the correct location
- Accepts one or more paths; scans each and merges repo lists (deduplicated)
- Classifies each repo as source or worktree; uses source path template for sources and worktree path template for worktrees
- **--inplace**: Two passes (worktrees then sources), move and repair, then remove vacated dirs and empty parents up to input roots
- **--copy**: List sources and worktrees, validate destinations, copy sources then worktrees, repair relations, report summary
- Skips repositories without remotes, detached HEAD worktrees, or already at target

## Tutorial

Expand Down Expand Up @@ -292,7 +292,7 @@ gwa feature-branch --tag review
| `gwa <branch> [-c] [--tag key=value]...` | ➕ Add worktree for branch (optionally create branch, tags available in templates/conditions) |
| `gwr <branch\|path> [-f]` | ➖ Remove worktree |
| `gww pull` | 🔄 Update source repository (works from worktrees if source is clean and on main/master) |
| `gww migrate <path> [--dry-run] [--move]` | 🚚 Migrate repositories to new locations |
| `gww migrate <path>... [--dry-run] [--copy \| --inplace]` | 🚚 Migrate repositories to new locations |
| `gww init config` | ⚙️ Create default configuration file |
| `gww init shell <shell>` | 🐚 Install shell completion (bash/zsh/fish) |

Expand Down
26 changes: 13 additions & 13 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,31 +131,31 @@ gww migrate ~/old-repos --dry-run
# Вывод:
# Would migrate 5 repositories:
# ~/old-repos/repo1 -> ~/Developer/sources/github/user/repo1
# ~/old-repos/repo2 -> ~/Developer/sources/github/user/repo2
# ...

gww migrate ~/old-repos
# Вывод:
# Migrated 5 repositories
# Repaired 2 worktrees
# Копирование (по умолчанию): список, копирование sources затем worktrees, repair, итог

gww migrate ~/old-repos --inplace
# Перемещение worktrees затем sources, repair, очистка пустых папок
```

Команда `migrate` сканирует директорию на наличие git-репозиториев и мигрирует их в локации на основе вашей текущей конфигурации. Полезна когда:
Команда `migrate` сканирует одну или несколько директорий на наличие git-репозиториев и мигрирует их в локации на основе вашей текущей конфигурации. Полезна когда:
- Вы обновили конфигурацию и хотите реорганизовать существующие репозитории
- Вы переходите с ручного управления репозиториями на GWW
- Вам нужно объединить репозитории из разных локаций

**Опции**:
- `--dry-run`, `-n`: Показать что будет мигрировано без внесения изменений
- `--move`: Переместить репозитории вместо копирования (по умолчанию — копирование)
- `--copy` (по умолчанию): Копировать репозитории в новые локации; список, проверка, копирование sources затем worktrees, `git worktree repair`, итог. Без очистки папок.
- `--inplace`: Переместить репозитории на место (сначала worktrees, затем sources), `git worktree repair`, затем рекурсивно очистить пустые исходные папки.

**Поведение**:
- Рекурсивно сканирует указанную директорию на наличие git-репозиториев
- Извлекает URI удаленного репозитория из каждого репозитория
- Вычисляет ожидаемую локацию используя вашу текущую конфигурацию
- Мигрирует репозитории, которые находятся в других локациях, чем ожидается
- Автоматически исправляет пути worktree при миграции worktree
- Пропускает репозитории без удаленных репозиториев или уже находящиеся в правильной локации
- Принимает один или несколько путей; сканирует каждый и объединяет списки репозиториев (без дубликатов)
- Классифицирует каждый репозиторий как source или worktree; для sources используется шаблон пути source, для worktrees — шаблон worktree
- **--inplace**: Два прохода (worktrees затем sources), перемещение и repair, затем удаление освободившихся директорий и пустых родителей до корней ввода
- **--copy**: Список sources и worktrees, проверка назначений, копирование sources затем worktrees, восстановление связей через repair, итог
- Пропускает репозитории без remote, worktrees с detached HEAD или уже в целевой локации

## Tutorial

Expand Down Expand Up @@ -292,7 +292,7 @@ gwa feature-branch --tag review
| `gwa <branch> [-c] [--tag key=value]...` | ➕ Добавить worktree для ветки (опционально создать ветку, теги доступны в шаблонах/условиях) |
| `gwr <branch\|path> [-f]` | ➖ Удалить worktree |
| `gww pull` | 🔄 Обновить исходный репозиторий (работает из worktree, если исходный репозиторий чист и на main/master) |
| `gww migrate <path> [--dry-run] [--move]` | 🚚 Мигрировать репозитории в новые локации |
| `gww migrate <path>... [--dry-run] [--copy \| --inplace]` | 🚚 Мигрировать репозитории в новые локации |
| `gww init config` | ⚙️ Создать конфиг по умолчанию |
| `gww init shell <shell>` | 🐚 Установить автодополнение (bash/zsh/fish) |

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ gww remove <branch|worktree folder> [--force] - remove worktree folder by branch

gww pull - check that sources has main / master branch checkout, it's clean and if it is execute git pull. Can be executed from source or worktree folder. If executed from inside worktree folder will update source folder

gww migrate <old-repos> [--dry-run] [--move] - scan old-repos folder, check it against current config and if location is incorrent copy (default) or move (if --move specified) to new position
gww migrate <path>... [--dry-run] [--copy | --inplace] - scan path(s) for repos, merge and dedupe; copy (default) or inplace move (worktrees then sources, repair, clean empty folders) to new positions

gww init config - create default settings file, gww.yml in $XDG_CONFIG_HOME compliant location. Came up with simple config with default_sources and default_worktrees filled and large comment block with examples covering other cases and function with documentation.

Expand Down
53 changes: 29 additions & 24 deletions specs/001-git-worktree-wrapper/contracts/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,32 +220,33 @@ gww pull

---

### 5. `gww migrate <old_repos> [--dry-run] [--move]`
### 5. `gww migrate <path>... [--dry-run] [--copy | --inplace]`

**Purpose**: Scan old repositories directory and migrate them to new locations based on current configuration.
**Purpose**: Scan one or more directories for git repositories and migrate them to new locations based on current configuration.

**Arguments**:
- `old_repos` (str, required): Path to directory containing old repositories
- `path` (one or more, required): Path(s) to directory(ies) containing old repositories. Multiple paths are processed as a single combined set (repos from all paths are merged and deduplicated).

**Options**:
- `--dry-run`, `-n`: Show what would be migrated without making changes
- `--move`: Move repositories instead of copying (default is copy)
- `--copy` (default): Copy repositories to new locations. List sources and worktrees, validate destinations, copy sources then worktrees, run `git worktree repair` to recover relations, then report summary. No folder cleanup.
- `--inplace`: Move repositories in place. First pass: move worktrees and run `git worktree repair` in each source. Second pass: move sources and run `git worktree repair` in each moved source (for sources that had worktrees). Then recursively clean empty source folders (remove vacated dirs and empty parents up to input roots). Dry-run outputs destination path when changed or "Already at target: \<path\>" when not.

**Behavior**:
1. Verify `old_repos` path exists and is a directory
2. Recursively scan directory tree for git repositories (directories containing `.git`)
3. For each repository found:
- Extract URI from remote origin (if available)
- Calculate expected location using current config
- Compare current location with expected location
- If different:
- If `--dry-run`: Print migration plan
- Else: Copy or move repository to expected location
- If the repository being migrated is a worktree:
- After moving/copying, call `git worktree repair` on the source repository to update the worktree path
- Handle repair errors gracefully (log warning, don't fail migration)
- If the repository is a source repository: No repair action needed
4. Report summary: repositories scanned, migrated, repaired, skipped
1. Verify each `path` exists and is a directory
2. Recursively scan each directory for git repositories and worktrees (paths where `.git` exists; submodules excluded). Do not descend into repository or worktree interiors—each repo/worktree is treated as a single unit. Merge and deduplicate repo lists by resolved path. During the scan, when not `--quiet`, output the current examining directory to stderr on a single line (overwriting), updating at most once per second; clear the line after the scan completes.
3. Classify each repo as **source** or **worktree**. Expected path: sources use `resolve_source_path`; worktrees use `resolve_worktree_path` (branch from current branch; detached HEAD worktrees are skipped).
4. **If `--inplace`**:
- First pass: for each worktree whose path differs, move to new path and run `git worktree repair` in its source (at current path).
- Second pass: for each source whose path differs, move to new path and run `git worktree repair` in the moved source (only for sources that had worktrees moved in pass 1).
- Then (if not dry-run): recursively remove vacated directories and empty parents up to input roots.
- Dry-run: output destination path per item when changed, or "Already at target: \<path\>" when same.
5. **If `--copy`** (default):
- Output each found source and worktree (e.g. "Source: \<path\> -> \<path\>", "Worktree: ...").
- Validate that each destination does not exist (plans with "destination exists" are skipped; rest are migrated).
- If not dry-run: copy sources, then copy worktrees (symbolic links are copied as symlinks, not resolved); fix copied worktrees' `.git` to point to new source and run `git worktree repair`; report summary (N repositories, M worktrees, skipped, already at target). No folder cleanup.
- Dry-run: list and validate only; output "Would migrate N repositories".
6. Report summary: repositories migrated/moved, repaired, skipped, already at target

**Exit Codes**:
- `0`: Success
Expand All @@ -254,20 +255,24 @@ gww pull

**Output**:
- Success: Print summary to stdout
- Progress: When not `--quiet`, during directory scan print current examining folder to stderr on a single line (at most once per second); clear the line after the scan
- Error: Print error message to stderr

**Examples**:
```bash
gww migrate ~/old-repos --dry-run
# Output:
# Would migrate 5 repositories:
# Output (each path, then summary):
# ~/old-repos/repo1 -> ~/Developer/sources/github/user/repo1
# ...
# Would migrate 5 repositories

gww migrate ~/old-repos --move
# Output:
# Moved 5 repositories
# Repaired 2 worktrees
gww migrate ~/old-repos
# Copy (default): list, copy sources then worktrees, repair, summary

gww migrate ~/old-repos --inplace
# Move worktrees then sources, repair, clean empty folders
# Moved N repositories
# Already at target: M repositories
```

---
Expand Down
8 changes: 4 additions & 4 deletions specs/001-git-worktree-wrapper/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,11 +338,11 @@ gww migrate ~/old-repos --dry-run
# ~/old-repos/repo2 -> ~/Developer/sources/gitlab/group/repo2
# ~/old-repos/repo3 -> ~/Developer/sources/default/org/repo3

# Actual migration (copy)
# Actual migration (copy, default)
gww migrate ~/old-repos

# Or move instead of copy
gww migrate ~/old-repos --move
# Or move in place and clean empty folders
gww migrate ~/old-repos --inplace
```

**Test Case**:
Expand All @@ -353,7 +353,7 @@ gww migrate ~/old-repos --move
**Test Case**:
- **Given**: Old repositories
- **When**: `gww migrate ~/old-repos`
- **Then**: Repositories copied to new locations based on current config
- **Then**: Repositories copied to new locations based on current config; symbolic links are copied as symlinks (not resolved)

## Template Function Examples

Expand Down
34 changes: 15 additions & 19 deletions specs/001-git-worktree-wrapper/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,34 +144,30 @@ gww pull
Users can migrate existing repositories from old locations to new locations based on current configuration.

**Acceptance Criteria**:
- Command: `gww migrate <old_repos> [--dry-run] [--move]`
- Verify `old_repos` path exists and is a directory
- Recursively scan directory tree for git repositories
- For each repository found:
- Extract URI from remote origin (if available)
- Calculate expected location using current config
- Compare current location with expected location
- If different:
- If `--dry-run`: Print migration plan
- Else: Copy or move repository to expected location
- If repository is a worktree: After moving/copying, call `git worktree repair` on the source repository to update worktree paths
- If repository is a source repository: No repair needed (worktrees are not migrated with source)
- Report summary: repositories scanned, migrated, skipped
- Command: `gww migrate <path>... [--dry-run] [--copy | --inplace]`
- Accept one or more paths; verify each exists and is a directory
- Recursively scan each directory for git repositories and worktrees (exclude submodules); do not descend into repository or worktree interiors (treat each as a single unit). Merge and deduplicate repo lists. During the scan, when not quiet: output current examining folder to stderr on a single line, updating at most once per second; clear the line after the scan.
- Classify each repo as source or worktree; expected path: sources via `resolve_source_path`, worktrees via `resolve_worktree_path` (branch from current branch; skip detached HEAD worktrees)
- **--inplace**: First pass move worktrees and run `git worktree repair` in each source; second pass move sources and run repair in moved sources that had worktrees; then recursively clean empty source folders (vacated dirs and empty parents up to input roots). Dry-run outputs destination or "Already at target".
- **--copy** (default): List sources and worktrees; validate destinations; copy sources then worktrees (symbolic links copied as symlinks, not resolved); run `git worktree repair` to recover relations; report summary. No folder cleanup.
- Report summary: repositories migrated/moved, repaired, skipped, already at target
- Handle errors: invalid path, migration failed (exit code 1)
- Handle configuration errors (exit code 2)

**Examples**:
```bash
gww migrate ~/old-repos --dry-run
# Output:
# Would migrate 5 repositories:
# Output (each path, then summary):
# ~/old-repos/repo1 -> ~/Developer/sources/github/user/repo1
# ...
# Would migrate 5 repositories

gww migrate ~/old-repos --move
# Output:
# Moved 5 repositories
# Repaired 2 worktrees
gww migrate ~/old-repos
# Copy (default): list, copy sources then worktrees, repair, summary

gww migrate ~/old-repos --inplace
# Move worktrees then sources, repair, clean empty folders
# Moved N repositories
```

---
Expand Down
2 changes: 2 additions & 0 deletions specs/001-git-worktree-wrapper/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@
- [X] T059 [US5] Integrate migrate command into main CLI parser in src/gww/cli/main.py
- [X] T060 [US5] Add error handling for invalid paths, migration failures, and worktree updates in src/gww/cli/commands/migrate.py
- [X] T061 [US5] Add output formatting (print migration summary to stdout, errors to stderr) in src/gww/cli/commands/migrate.py
- [X] [US5] Migrate refactor: immediate path output when processing each repo; "Already at target: \<path\>" when source equals destination; submodules excluded from scan; dry-run outputs paths first then "Would migrate N repositories" at end; unit test for is_submodule; integration tests for submodules
- [X] [US5] Migrate copy: preserve symbolic links (copy as symlinks, do not resolve); integration test test_migrate_copy_preserves_symlinks

**Checkpoint**: At this point, User Stories 1-5 should all work independently. Users can clone, add, remove worktrees, update sources, and migrate repositories.

Expand Down
Loading