diff --git a/docs/project-overview-pdr.md b/docs/project-overview-pdr.md index e42a00c..be93ce1 100644 --- a/docs/project-overview-pdr.md +++ b/docs/project-overview-pdr.md @@ -41,7 +41,8 @@ Use `TLC_CODE_DIR` environment variable to customize the base directory. ### 2. Repository Management (`pull-repos`) Clones or updates Odoo and OCA repositories: - **Sources**: Official Odoo repos and OCA GitHub repos -- **Speed**: Shallow clones (depth=1) for bandwidth efficiency +- **Speed**: Shallow clones (depth=1) by default for bandwidth efficiency +- **Full History**: Use `--full-history` flag to fetch complete commit history - **Parallelization**: Multiple repos cloned/updated simultaneously - **Operations**: Clone new repos, fetch and hard-reset existing ones @@ -233,15 +234,40 @@ Clone missing repositories and update existing ones. **Options**: - `-f, --filter`: Filter repositories by name (repeatable, e.g., `-f server-tools -f web`) - `--dry-run`: Preview operations without executing +- `--full-history`: Fetch full commit history instead of shallow clone (depth=1) - `--newcomer / --no-newcomer`: Enable interactive mode (default: True) **Behavior**: - Reads `{CODE_ROOT}/config.toml` (default: `~/code/config.toml`) and loads repo definitions -- For each version, clones missing repos (shallow, depth=1) or updates existing ones +- For each version, clones missing repos (shallow, depth=1 by default) or updates existing ones - Updates via: git fetch, checkout branch, hard reset to origin/branch - Executes up to 4 repos in parallel - Uses GitProgress for progress reporting +**Examples**: +```bash +# Shallow clone (default, faster, less bandwidth) +tlc pull-repos + +# Full clone (complete commit history) +tlc pull-repos --full-history + +# Filter specific repos with full history +tlc pull-repos --filter odoo --full-history +``` + +**When to use --full-history**: +- Need to browse full git history +- Performing git bisect operations +- Analyzing commit patterns over time +- Contributing patches based on historical context + +**Default behavior (depth=1)**: +- Faster downloads (less data transferred) +- Smaller disk usage +- Sufficient for most development workflows +- Can be "unshallowed" later with `git fetch --unshallow` + **Exit Codes**: 0 on success, 1 if any repo operation fails --- diff --git a/docs/system-architecture.md b/docs/system-architecture.md index df11561..5e47472 100644 --- a/docs/system-architecture.md +++ b/docs/system-architecture.md @@ -102,7 +102,9 @@ pull-repos command │ git checkout {branch} │ git reset --hard origin/{branch} │ else: - │ git clone --depth=1 --branch={branch} {url} {path} + │ # Depth controlled by --full-history flag + │ depth = None if full_history else 1 + │ git clone --depth={depth} --branch={branch} {url} {path} │ with GitProgress callback │ ├─ Aggregate TaskResults diff --git a/tests/test_pull_repos.py b/tests/test_pull_repos.py index 134d874..768d925 100644 --- a/tests/test_pull_repos.py +++ b/tests/test_pull_repos.py @@ -144,6 +144,78 @@ def test_pull_repo_handles_git_exception(tmp_path, mock_git_repo_calls): assert found_error_description +def test_pull_repo_clones_with_shallow_clone_by_default(tmp_path, mock_git_repo_calls, mock_main_git_progress): + """Test that repos are cloned with depth=1 by default.""" + mock_clone_from, _ = mock_git_repo_calls + progress_mock = MagicMock(spec=Progress) + + repo_path = tmp_path / "code" / "odoo" / "odoo" / "16.0" + repo_path.parent.mkdir(parents=True, exist_ok=True) + + repo_info = { + "repo_name": "odoo", + "repo_path": repo_path, + "repo_url": ODOO_URLS["odoo"], + "version": "16.0", + } + + # Call without full_history parameter (default behavior) + _pull_repo(progress_mock, task, repo_info) + + # Verify depth=1 is used + mock_clone_from.assert_called_once() + call_kwargs = mock_clone_from.call_args.kwargs + assert call_kwargs["depth"] == 1 + + +def test_pull_repo_clones_with_full_history_when_flag_set(tmp_path, mock_git_repo_calls, mock_main_git_progress): + """Test that repos are cloned with full history when --full-history flag is used.""" + mock_clone_from, _ = mock_git_repo_calls + progress_mock = MagicMock(spec=Progress) + + repo_path = tmp_path / "code" / "odoo" / "odoo" / "16.0" + repo_path.parent.mkdir(parents=True, exist_ok=True) + + repo_info = { + "repo_name": "odoo", + "repo_path": repo_path, + "repo_url": ODOO_URLS["odoo"], + "version": "16.0", + } + + # Call with full_history=True + _pull_repo(progress_mock, task, repo_info, full_history=True) + + # Verify depth=None is used (full clone) + mock_clone_from.assert_called_once() + call_kwargs = mock_clone_from.call_args.kwargs + assert call_kwargs["depth"] is None + + +def test_pull_repo_update_existing_ignores_full_history_flag(tmp_path, mock_git_repo_calls, mock_main_git_progress): + """Test that existing repo updates ignore the full_history flag.""" + _, mock_repo_class = mock_git_repo_calls + progress_mock = MagicMock(spec=Progress) + + repo_path = tmp_path / "code" / "odoo" / "odoo" / "16.0" + repo_path.mkdir(parents=True, exist_ok=True) + + repo_info = { + "repo_name": "odoo", + "repo_path": repo_path, + "repo_url": ODOO_URLS["odoo"], + "version": "16.0", + } + + # Call with full_history=True on existing repo + _pull_repo(progress_mock, task, repo_info, full_history=True) + + # Verify fetch/reset was called (not clone) + mock_repo_instance = mock_repo_class.return_value + mock_repo_instance.remotes.origin.fetch.assert_called_once_with("16.0") + mock_repo_instance.git.reset.assert_called_once_with("--hard", "origin/16.0") + + def test_get_tasks_generates_correct_list(mock_config, tmp_path): odoo_versions = ["16.0", "17.0"] repos_config = {"odoo": ["odoo"], "oca": ["server-tools"]} diff --git a/trobz_local/main.py b/trobz_local/main.py index a885dec..7258f16 100644 --- a/trobz_local/main.py +++ b/trobz_local/main.py @@ -119,6 +119,11 @@ def pull_repos( # noqa: C901 ), ] = None, dry_run: bool = typer.Option(False, "--dry-run", help="Prints actions without running."), + full_history: bool = typer.Option( + False, + "--full-history", + help="Fetch full commit history instead of shallow clone (depth=1).", + ), ): """ Pull/clone Odoo and OCA repos based on config @@ -167,7 +172,7 @@ def pull_repos( # noqa: C901 concurrency_tasks.append({ "name": f"{repo_info['repo_name']} ({repo_info['version']})", "func": _pull_repo, - "args": {"repo_info": repo_info}, + "args": {"repo_info": repo_info, "full_history": full_history}, }) results = run_tasks(concurrency_tasks) @@ -205,7 +210,7 @@ def _get_tasks(odoo_versions, repos_config, code_root, repo_filter): return tasks -def _pull_repo(progress: Progress, task_id: TaskID, repo_info: dict): +def _pull_repo(progress: Progress, task_id: TaskID, repo_info: dict, full_history: bool = False): repo_name = repo_info["repo_name"] repo_path = repo_info["repo_path"] repo_url = repo_info["repo_url"] @@ -216,12 +221,16 @@ def _pull_repo(progress: Progress, task_id: TaskID, repo_info: dict): if not repo_path.exists(): progress.update(task_id, description=f"Cloning {repo_name} ({version})") repo_path.parent.mkdir(parents=True, exist_ok=True) + + # Calculate depth based on full_history flag + depth = None if full_history else 1 + git.Repo.clone_from( repo_url, to_path=repo_path, branch=version, progress=GitProgress(progress, task_id, f"Cloning {repo_name} {version}"), # ty: ignore[invalid-argument-type] - depth=1, + depth=depth, ) else: progress.update(task_id, description=f"Fetching {repo_name} ({version})")