git-repokit 0.9.10__py3-none-any.whl
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.
- git_repokit-0.9.10.dist-info/METADATA +422 -0
- git_repokit-0.9.10.dist-info/RECORD +174 -0
- git_repokit-0.9.10.dist-info/WHEEL +5 -0
- git_repokit-0.9.10.dist-info/entry_points.txt +2 -0
- git_repokit-0.9.10.dist-info/licenses/LICENSE +9 -0
- git_repokit-0.9.10.dist-info/top_level.txt +1 -0
- repokit/__init__.py +8 -0
- repokit/__main__.py +12 -0
- repokit/__version__.py +4 -0
- repokit/_vendor/__init__.py +0 -0
- repokit/_vendor/ailib/__init__.py +1 -0
- repokit/_vendor/ailib/analyzer.py +177 -0
- repokit/_vendor/ailib/backend.py +95 -0
- repokit/_vendor/ailib/cache.py +97 -0
- repokit/_vendor/ailib/parsers.py +52 -0
- repokit/_vendor/ailib/transports/__init__.py +16 -0
- repokit/_vendor/ailib/transports/cli.py +312 -0
- repokit/_vendor/ailib/transports/openai_compat.py +383 -0
- repokit/_vendor/ailib/transports/prompt_file.py +41 -0
- repokit/_vendor/ailib/types.py +224 -0
- repokit/auth_integration.py +244 -0
- repokit/badges.py +183 -0
- repokit/branch_utils.py +364 -0
- repokit/cli.py +2997 -0
- repokit/common_kit.py +202 -0
- repokit/config.py +686 -0
- repokit/defaults.py +220 -0
- repokit/directory_analyzer.py +1244 -0
- repokit/directory_profiles.py +423 -0
- repokit/git_config.py +370 -0
- repokit/github_create.py +142 -0
- repokit/github_setup.py +248 -0
- repokit/guardrails.py +707 -0
- repokit/history_cleaner.py +449 -0
- repokit/history_protection.py +434 -0
- repokit/hooks/pre-commit-guardrails +122 -0
- repokit/hooks/pre-commit-template +28 -0
- repokit/layers.py +510 -0
- repokit/licenses/0bsd.txt +14 -0
- repokit/licenses/LICENSES.md +27 -0
- repokit/licenses/__init__.py +142 -0
- repokit/licenses/agpl-3.0.txt +661 -0
- repokit/licenses/all-rights-reserved.txt +10 -0
- repokit/licenses/apache-2.0.txt +201 -0
- repokit/licenses/bsd-2-clause.txt +24 -0
- repokit/licenses/bsd-3-clause.txt +28 -0
- repokit/licenses/bsl-1.0.txt +23 -0
- repokit/licenses/cc-by-4.0.txt +395 -0
- repokit/licenses/cc-by-sa-4.0.txt +427 -0
- repokit/licenses/cc0-1.0.txt +121 -0
- repokit/licenses/epl-2.0.txt +277 -0
- repokit/licenses/gpl-2.0.txt +339 -0
- repokit/licenses/gpl-3.0.txt +674 -0
- repokit/licenses/isc.txt +15 -0
- repokit/licenses/lgpl-2.1.txt +504 -0
- repokit/licenses/lgpl-3.0.txt +165 -0
- repokit/licenses/mit.txt +21 -0
- repokit/licenses/mpl-2.0.txt +373 -0
- repokit/licenses/unlicense.txt +24 -0
- repokit/readme_blocks.py +112 -0
- repokit/remote_integration.py +627 -0
- repokit/repo_manager.py +2796 -0
- repokit/template_engine.py +391 -0
- repokit/templates/ai/claude/CLAUDE-nested.md.template +25 -0
- repokit/templates/ai/claude/CLAUDE.md.template +98 -0
- repokit/templates/ai/claude/instructions/step1_context_rebuilder.md +33 -0
- repokit/templates/ai/claude/instructions/step2_dev_workflow_process.md +67 -0
- repokit/templates/ai/claude/instructions/step3_context_bridge.md +52 -0
- repokit/templates/common/CHANGELOG.md.template +15 -0
- repokit/templates/common/CONTRIBUTING.md.template +31 -0
- repokit/templates/common/README.md.template +30 -0
- repokit/templates/common/gitattributes.template +17 -0
- repokit/templates/common/gitignore.template +141 -0
- repokit/templates/extras/FUNDING.yml.template +13 -0
- repokit/templates/extras/ROADMAP.md.template +17 -0
- repokit/templates/extras/dependabot.yml.template +22 -0
- repokit/templates/extras/one-offs-README.md.template +28 -0
- repokit/templates/extras/platform-support.md.template +11 -0
- repokit/templates/extras/pull_request_template.md.template +25 -0
- repokit/templates/extras/release-template.md.template +34 -0
- repokit/templates/extras/settings.json.template +11 -0
- repokit/templates/extras/stale.yml.template +14 -0
- repokit/templates/github/CODEOWNERS.template +11 -0
- repokit/templates/github/ISSUE_TEMPLATE/bug-report.md.template +29 -0
- repokit/templates/github/ISSUE_TEMPLATE/feature-request.md.template +19 -0
- repokit/templates/github/issues/notes.md.template +18 -0
- repokit/templates/github/issues/roadmap.md.template +15 -0
- repokit/templates/hooks/pre-commit.template +64 -0
- repokit/templates/layers/archetypes/any/claude-plugin/.claude-plugin/marketplace.json +20 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/.claude-plugin/plugin.json +15 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/README.md +28 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.github/workflows/ci-c-cpp.yml +24 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.repokit-common.toml.rkappend +4 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/launch.json +29 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/tasks.json +26 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakeLists.txt +47 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakePresets.json +23 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/cmake/GenerateStamp.cmake +45 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/src/main.cpp +27 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/tests/test_version.cpp +20 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/vcpkg.json +5 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/.vscode/launch.json +27 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/Makefile +10 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/build.bat +7 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/src/__RK_SNAKE__.c +26 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/launch.json +17 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/tasks.json +20 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.sln +19 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.vcxproj +41 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/src/main.cpp +26 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.repokit-common.toml +28 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/scripts/session-start.mjs +23 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/tests/manifest.test.js +30 -0
- repokit/templates/layers/archetypes/js-ts/node/.repokit-common.toml.rkappend +6 -0
- repokit/templates/layers/archetypes/js-ts/node/package.json +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/.github/workflows/release.yml +39 -0
- repokit/templates/layers/archetypes/js-ts/node-package/package.json +32 -0
- repokit/templates/layers/archetypes/js-ts/node-package/src/index.ts +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/tests/index.test.ts +11 -0
- repokit/templates/layers/archetypes/js-ts/script/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/script/__RK_PROJECT__.js +37 -0
- repokit/templates/layers/archetypes/js-ts/script/tests/version.test.js +26 -0
- repokit/templates/layers/archetypes/js-ts/ts/package.json +18 -0
- repokit/templates/layers/archetypes/js-ts/ts/tsconfig.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.github/workflows/ci-python.yml +44 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.repokit-common.toml +21 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/claude-plugin/VERSION +1 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/run-hook.mjs +42 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/session_start.py +31 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/tests/test_manifest.py +42 -0
- repokit/templates/layers/archetypes/python/pypi/.github/workflows/release.yml +63 -0
- repokit/templates/layers/archetypes/python/pypi/.vscode/launch.json +43 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__init__.py +5 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__main__.py +6 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/_version.py +96 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/cli.py +18 -0
- repokit/templates/layers/archetypes/python/pypi/pyproject.toml +71 -0
- repokit/templates/layers/archetypes/python/pypi/tests/conftest.py +11 -0
- repokit/templates/layers/archetypes/python/pypi/tests/test_version.py +50 -0
- repokit/templates/layers/archetypes/python/script/.github/workflows/ci-python.yml +43 -0
- repokit/templates/layers/archetypes/python/script/.repokit-common.toml +14 -0
- repokit/templates/layers/archetypes/python/script/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/script/VERSION +1 -0
- repokit/templates/layers/archetypes/python/script/__RK_PROJECT__.py +40 -0
- repokit/templates/layers/archetypes/python/script/tests/test_version.py +25 -0
- repokit/templates/layers/archetypes/python/setuppy/requirements.txt +8 -0
- repokit/templates/layers/archetypes/python/setuppy/setup.py +23 -0
- repokit/templates/layers/languages/c-cpp/.gitignore.rkappend +20 -0
- repokit/templates/layers/languages/c-cpp/.repokit-common.toml +7 -0
- repokit/templates/layers/languages/c-cpp/include/__RK_SNAKE__/version.h +20 -0
- repokit/templates/layers/languages/c-cpp/src/app.rc +25 -0
- repokit/templates/layers/languages/js-ts/.editorconfig +15 -0
- repokit/templates/layers/languages/js-ts/.github/workflows/ci-js-ts.yml +41 -0
- repokit/templates/layers/languages/js-ts/.gitignore.rkappend +13 -0
- repokit/templates/layers/languages/js-ts/.repokit-common.toml +19 -0
- repokit/templates/layers/languages/js-ts/.vscode/launch.json +24 -0
- repokit/templates/layers/languages/js-ts/VERSION +1 -0
- repokit/templates/layers/languages/python/.github/workflows/ci-python.yml +76 -0
- repokit/templates/layers/languages/python/.gitignore.rkappend +15 -0
- repokit/templates/layers/languages/python/.vscode/launch.json +33 -0
- repokit/templates/layers/manifest.toml +302 -0
- repokit/templates/vault/CLAUDE.md +29 -0
- repokit/templates/vault/claude/_maps/MOC-project.md +15 -0
- repokit/templates/vault/claude/_oracle/backlinks.md +3 -0
- repokit/templates/vault/claude/_oracle/concepts.md +12 -0
- repokit/templates/vault/claude/_oracle/manifest.md +10 -0
- repokit/templates/vault/claude/_todo/ACTIVE-WORK.md +18 -0
- repokit/templates/vault/claude/_todo/OPEN-LOOPS.md +14 -0
- repokit/utils.py +316 -0
- repokit/vault.py +248 -0
|
@@ -0,0 +1,1244 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Directory Analyzer for RepoKit.
|
|
4
|
+
|
|
5
|
+
Analyzes directories for migration to the RepoKit structure.
|
|
6
|
+
Supports universal project migration for any project type.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import fnmatch
|
|
11
|
+
import shutil
|
|
12
|
+
import logging
|
|
13
|
+
import subprocess
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Dict, List, Set, Tuple, Optional, Any
|
|
17
|
+
|
|
18
|
+
from .defaults import DEFAULT_PRIVATE_DIRS, DEFAULT_BRANCH_STRATEGIES
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GitManager:
|
|
22
|
+
"""
|
|
23
|
+
Manages Git repository state detection and operations.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, target_dir: str, verbose: int = 0):
|
|
27
|
+
"""Initialize GitManager."""
|
|
28
|
+
self.target_dir = os.path.abspath(target_dir)
|
|
29
|
+
self.verbose = verbose
|
|
30
|
+
self.logger = logging.getLogger("repokit.git")
|
|
31
|
+
|
|
32
|
+
def run_git(self, args: List[str], check: bool = True) -> Optional[str]:
|
|
33
|
+
"""Run a git command and return output."""
|
|
34
|
+
cmd = ["git"] + args
|
|
35
|
+
|
|
36
|
+
if self.verbose >= 2:
|
|
37
|
+
self.logger.debug(f"Running: {' '.join(cmd)} in {self.target_dir}")
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
result = subprocess.run(
|
|
41
|
+
cmd, cwd=self.target_dir, check=check, capture_output=True, text=True
|
|
42
|
+
)
|
|
43
|
+
return result.stdout.strip()
|
|
44
|
+
except subprocess.CalledProcessError as e:
|
|
45
|
+
if self.verbose >= 1:
|
|
46
|
+
self.logger.warning(f"Git command failed: {e.stderr}")
|
|
47
|
+
if check:
|
|
48
|
+
raise
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def is_git_repo(self) -> bool:
|
|
52
|
+
"""Check if directory is a Git repository."""
|
|
53
|
+
return os.path.exists(os.path.join(self.target_dir, ".git"))
|
|
54
|
+
|
|
55
|
+
def get_repo_state(self) -> Dict[str, Any]:
|
|
56
|
+
"""Get comprehensive Git repository state."""
|
|
57
|
+
if not self.is_git_repo():
|
|
58
|
+
return {"is_repo": False}
|
|
59
|
+
|
|
60
|
+
state = {"is_repo": True}
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Get current branch
|
|
64
|
+
state["current_branch"] = self.run_git(
|
|
65
|
+
["branch", "--show-current"], check=False
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Get all branches
|
|
69
|
+
branches_output = self.run_git(["branch", "-a"], check=False)
|
|
70
|
+
if branches_output:
|
|
71
|
+
branches = []
|
|
72
|
+
for line in branches_output.split("\n"):
|
|
73
|
+
line = line.strip()
|
|
74
|
+
if line and not line.startswith("remotes/"):
|
|
75
|
+
branch = line.lstrip("* ").strip()
|
|
76
|
+
if branch:
|
|
77
|
+
branches.append(branch)
|
|
78
|
+
state["branches"] = branches
|
|
79
|
+
|
|
80
|
+
# Check for uncommitted changes
|
|
81
|
+
status_output = self.run_git(["status", "--porcelain"], check=False)
|
|
82
|
+
state["has_uncommitted_changes"] = bool(
|
|
83
|
+
status_output and status_output.strip()
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Get remotes
|
|
87
|
+
remotes_output = self.run_git(["remote", "-v"], check=False)
|
|
88
|
+
remotes = {}
|
|
89
|
+
if remotes_output:
|
|
90
|
+
for line in remotes_output.split("\n"):
|
|
91
|
+
if line.strip():
|
|
92
|
+
parts = line.split()
|
|
93
|
+
if len(parts) >= 2:
|
|
94
|
+
remote_name = parts[0]
|
|
95
|
+
remote_url = parts[1]
|
|
96
|
+
if remote_name not in remotes:
|
|
97
|
+
remotes[remote_name] = remote_url
|
|
98
|
+
state["remotes"] = remotes
|
|
99
|
+
|
|
100
|
+
# Check if repo has commits
|
|
101
|
+
try:
|
|
102
|
+
self.run_git(["rev-parse", "HEAD"], check=True)
|
|
103
|
+
state["has_commits"] = True
|
|
104
|
+
except:
|
|
105
|
+
state["has_commits"] = False
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
self.logger.warning(f"Error getting Git state: {str(e)}")
|
|
109
|
+
|
|
110
|
+
return state
|
|
111
|
+
|
|
112
|
+
def get_branch_strategy(self) -> str:
|
|
113
|
+
"""Detect existing branch strategy."""
|
|
114
|
+
state = self.get_repo_state()
|
|
115
|
+
if not state["is_repo"]:
|
|
116
|
+
return "none"
|
|
117
|
+
|
|
118
|
+
branches = set(state.get("branches", []))
|
|
119
|
+
|
|
120
|
+
# Check for GitFlow pattern
|
|
121
|
+
if "develop" in branches and any(b.startswith("feature/") for b in branches):
|
|
122
|
+
return "gitflow"
|
|
123
|
+
|
|
124
|
+
# Check for standard RepoKit pattern
|
|
125
|
+
repokit_branches = {"main", "dev", "staging", "test", "live"}
|
|
126
|
+
if len(repokit_branches & branches) >= 3:
|
|
127
|
+
return "standard"
|
|
128
|
+
|
|
129
|
+
# Check for simple pattern
|
|
130
|
+
if "main" in branches and "dev" in branches:
|
|
131
|
+
return "simple"
|
|
132
|
+
|
|
133
|
+
# Single branch or unknown
|
|
134
|
+
return "minimal"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ProjectAnalyzer:
|
|
138
|
+
"""
|
|
139
|
+
Universal project analyzer for any type of project.
|
|
140
|
+
|
|
141
|
+
Analyzes project structure, language, Git state, and provides
|
|
142
|
+
comprehensive migration recommendations for RepoKit adoption.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
# Language detection patterns
|
|
146
|
+
LANGUAGE_PATTERNS = {
|
|
147
|
+
"python": {
|
|
148
|
+
"files": [
|
|
149
|
+
"setup.py",
|
|
150
|
+
"pyproject.toml",
|
|
151
|
+
"requirements.txt",
|
|
152
|
+
"Pipfile",
|
|
153
|
+
"poetry.lock",
|
|
154
|
+
],
|
|
155
|
+
"extensions": [".py", ".pyw"],
|
|
156
|
+
"directories": ["src", "lib", "tests"],
|
|
157
|
+
},
|
|
158
|
+
"javascript": {
|
|
159
|
+
"files": [
|
|
160
|
+
"package.json",
|
|
161
|
+
"package-lock.json",
|
|
162
|
+
"yarn.lock",
|
|
163
|
+
"webpack.config.js",
|
|
164
|
+
],
|
|
165
|
+
"extensions": [".js", ".jsx", ".ts", ".tsx"],
|
|
166
|
+
"directories": ["src", "lib", "node_modules", "dist"],
|
|
167
|
+
},
|
|
168
|
+
"java": {
|
|
169
|
+
"files": ["pom.xml", "build.gradle", "build.xml"],
|
|
170
|
+
"extensions": [".java", ".class"],
|
|
171
|
+
"directories": ["src", "target", "build"],
|
|
172
|
+
},
|
|
173
|
+
"csharp": {
|
|
174
|
+
"files": ["*.csproj", "*.sln", "packages.config"],
|
|
175
|
+
"extensions": [".cs", ".vb"],
|
|
176
|
+
"directories": ["bin", "obj"],
|
|
177
|
+
},
|
|
178
|
+
"go": {
|
|
179
|
+
"files": ["go.mod", "go.sum"],
|
|
180
|
+
"extensions": [".go"],
|
|
181
|
+
"directories": ["cmd", "pkg", "internal"],
|
|
182
|
+
},
|
|
183
|
+
"rust": {
|
|
184
|
+
"files": ["Cargo.toml", "Cargo.lock"],
|
|
185
|
+
"extensions": [".rs"],
|
|
186
|
+
"directories": ["src", "target"],
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
def __init__(self, target_dir: str, verbose: int = 0):
|
|
191
|
+
"""Initialize ProjectAnalyzer."""
|
|
192
|
+
self.target_dir = os.path.abspath(target_dir)
|
|
193
|
+
self.verbose = verbose
|
|
194
|
+
self.logger = logging.getLogger("repokit.project_analyzer")
|
|
195
|
+
|
|
196
|
+
# Initialize Git manager
|
|
197
|
+
self.git_manager = GitManager(target_dir, verbose)
|
|
198
|
+
|
|
199
|
+
# Initialize directory analyzer for RepoKit-specific analysis
|
|
200
|
+
self.directory_analyzer = DirectoryAnalyzer(target_dir, verbose)
|
|
201
|
+
|
|
202
|
+
# Project analysis results
|
|
203
|
+
self.project_type = None
|
|
204
|
+
self.language = None
|
|
205
|
+
self.git_state = None
|
|
206
|
+
self.migration_complexity = None
|
|
207
|
+
|
|
208
|
+
# Perform analysis
|
|
209
|
+
self._analyze_project()
|
|
210
|
+
|
|
211
|
+
def _analyze_project(self) -> None:
|
|
212
|
+
"""Perform comprehensive project analysis."""
|
|
213
|
+
self.logger.info(f"Analyzing project: {self.target_dir}")
|
|
214
|
+
|
|
215
|
+
# Detect project type and language
|
|
216
|
+
self.language = self._detect_language()
|
|
217
|
+
self.project_type = self._determine_project_type()
|
|
218
|
+
|
|
219
|
+
# Analyze Git state
|
|
220
|
+
self.git_state = self.git_manager.get_repo_state()
|
|
221
|
+
|
|
222
|
+
# Assess migration complexity
|
|
223
|
+
self.migration_complexity = self._assess_migration_complexity()
|
|
224
|
+
|
|
225
|
+
def _detect_language(self) -> str:
|
|
226
|
+
"""Detect primary programming language."""
|
|
227
|
+
scores = {lang: 0 for lang in self.LANGUAGE_PATTERNS}
|
|
228
|
+
|
|
229
|
+
# Check for characteristic files
|
|
230
|
+
for lang, patterns in self.LANGUAGE_PATTERNS.items():
|
|
231
|
+
for file_pattern in patterns["files"]:
|
|
232
|
+
if "*" in file_pattern:
|
|
233
|
+
# Handle glob patterns
|
|
234
|
+
import glob
|
|
235
|
+
|
|
236
|
+
matches = glob.glob(os.path.join(self.target_dir, file_pattern))
|
|
237
|
+
if matches:
|
|
238
|
+
scores[lang] += 10
|
|
239
|
+
else:
|
|
240
|
+
# Direct file check
|
|
241
|
+
if os.path.exists(os.path.join(self.target_dir, file_pattern)):
|
|
242
|
+
scores[lang] += 10
|
|
243
|
+
|
|
244
|
+
# Count files by extension
|
|
245
|
+
ext_count = 0
|
|
246
|
+
for root, dirs, files in os.walk(self.target_dir):
|
|
247
|
+
if ".git" in root:
|
|
248
|
+
continue
|
|
249
|
+
for file in files:
|
|
250
|
+
if any(file.endswith(ext) for ext in patterns["extensions"]):
|
|
251
|
+
ext_count += 1
|
|
252
|
+
scores[lang] += ext_count
|
|
253
|
+
|
|
254
|
+
# Check for characteristic directories
|
|
255
|
+
for dir_name in patterns["directories"]:
|
|
256
|
+
if os.path.exists(os.path.join(self.target_dir, dir_name)):
|
|
257
|
+
scores[lang] += 5
|
|
258
|
+
|
|
259
|
+
# Return language with highest score
|
|
260
|
+
if max(scores.values()) > 0:
|
|
261
|
+
return max(scores, key=scores.get)
|
|
262
|
+
return "generic"
|
|
263
|
+
|
|
264
|
+
def _determine_project_type(self) -> str:
|
|
265
|
+
"""Determine project type based on structure and content."""
|
|
266
|
+
# Check if it's definitively a RepoKit-managed project
|
|
267
|
+
repokit_config_path = os.path.join(self.target_dir, ".repokit.json")
|
|
268
|
+
if os.path.exists(repokit_config_path):
|
|
269
|
+
try:
|
|
270
|
+
with open(repokit_config_path, 'r') as f:
|
|
271
|
+
config = json.load(f)
|
|
272
|
+
|
|
273
|
+
# Check for RepoKit signature fields
|
|
274
|
+
if config.get("repokit_managed") or config.get("generated_by") == "repokit":
|
|
275
|
+
return "repokit"
|
|
276
|
+
|
|
277
|
+
# Even non-empty .repokit.json suggests RepoKit intent
|
|
278
|
+
if config: # Non-empty config file
|
|
279
|
+
return "repokit"
|
|
280
|
+
|
|
281
|
+
except (json.JSONDecodeError, IOError):
|
|
282
|
+
# If .repokit.json exists but is invalid, treat as RepoKit attempt
|
|
283
|
+
return "repokit"
|
|
284
|
+
|
|
285
|
+
# Check for RepoKit-like structure (but not definitively RepoKit-managed)
|
|
286
|
+
if self._has_repokit_structure():
|
|
287
|
+
return "repokit_like"
|
|
288
|
+
|
|
289
|
+
# Check for empty directory
|
|
290
|
+
try:
|
|
291
|
+
contents = os.listdir(self.target_dir)
|
|
292
|
+
# Filter out hidden files for empty check
|
|
293
|
+
visible_contents = [item for item in contents if not item.startswith(".")]
|
|
294
|
+
if not visible_contents:
|
|
295
|
+
return "empty"
|
|
296
|
+
except OSError:
|
|
297
|
+
return "inaccessible"
|
|
298
|
+
|
|
299
|
+
# Check if it's a Git repository
|
|
300
|
+
if self.git_state and self.git_state.get("is_repo"):
|
|
301
|
+
if self.git_state.get("has_commits"):
|
|
302
|
+
return "git_with_history"
|
|
303
|
+
else:
|
|
304
|
+
return "git_new"
|
|
305
|
+
|
|
306
|
+
# Check if it has source files
|
|
307
|
+
has_source = False
|
|
308
|
+
for root, dirs, files in os.walk(self.target_dir):
|
|
309
|
+
for file in files:
|
|
310
|
+
if any(
|
|
311
|
+
file.endswith(ext)
|
|
312
|
+
for lang_patterns in self.LANGUAGE_PATTERNS.values()
|
|
313
|
+
for ext in lang_patterns["extensions"]
|
|
314
|
+
):
|
|
315
|
+
has_source = True
|
|
316
|
+
break
|
|
317
|
+
if has_source:
|
|
318
|
+
break
|
|
319
|
+
|
|
320
|
+
if has_source:
|
|
321
|
+
return "source_no_git"
|
|
322
|
+
else:
|
|
323
|
+
return "files_no_git"
|
|
324
|
+
|
|
325
|
+
def _assess_migration_complexity(self) -> str:
|
|
326
|
+
"""Assess migration complexity level."""
|
|
327
|
+
complexity_score = 0
|
|
328
|
+
|
|
329
|
+
# Git repository complexity
|
|
330
|
+
if self.git_state and self.git_state.get("is_repo"):
|
|
331
|
+
complexity_score += 2
|
|
332
|
+
|
|
333
|
+
# Multiple branches add complexity
|
|
334
|
+
branches = self.git_state.get("branches", [])
|
|
335
|
+
if len(branches) > 2:
|
|
336
|
+
complexity_score += 2
|
|
337
|
+
|
|
338
|
+
# Uncommitted changes add complexity
|
|
339
|
+
if self.git_state.get("has_uncommitted_changes"):
|
|
340
|
+
complexity_score += 1
|
|
341
|
+
|
|
342
|
+
# Existing remotes add complexity
|
|
343
|
+
if self.git_state.get("remotes"):
|
|
344
|
+
complexity_score += 1
|
|
345
|
+
|
|
346
|
+
# File structure complexity
|
|
347
|
+
dir_summary = self.directory_analyzer.get_summary()
|
|
348
|
+
if dir_summary.get("has_template_conflicts"):
|
|
349
|
+
complexity_score += 2
|
|
350
|
+
|
|
351
|
+
# Large number of files
|
|
352
|
+
if dir_summary.get("total_files", 0) > 100:
|
|
353
|
+
complexity_score += 1
|
|
354
|
+
|
|
355
|
+
if complexity_score <= 2:
|
|
356
|
+
return "low"
|
|
357
|
+
elif complexity_score <= 5:
|
|
358
|
+
return "medium"
|
|
359
|
+
else:
|
|
360
|
+
return "high"
|
|
361
|
+
|
|
362
|
+
def get_comprehensive_summary(self) -> Dict[str, Any]:
|
|
363
|
+
"""Get comprehensive project analysis summary."""
|
|
364
|
+
# Get base directory analysis
|
|
365
|
+
dir_summary = self.directory_analyzer.get_summary()
|
|
366
|
+
|
|
367
|
+
# Enhance with project analysis
|
|
368
|
+
summary = {
|
|
369
|
+
"target_dir": self.target_dir,
|
|
370
|
+
"project_type": self.project_type,
|
|
371
|
+
"detected_language": self.language,
|
|
372
|
+
"migration_complexity": self.migration_complexity,
|
|
373
|
+
"git_state": self.git_state,
|
|
374
|
+
"branch_strategy": self.git_manager.get_branch_strategy(),
|
|
375
|
+
# Directory analysis results
|
|
376
|
+
"existing_dirs": dir_summary["existing_dirs"],
|
|
377
|
+
"missing_dirs": dir_summary["missing_dirs"],
|
|
378
|
+
"special_dirs": dir_summary["special_dirs"],
|
|
379
|
+
"has_git_repository": dir_summary["has_git_repository"],
|
|
380
|
+
"has_template_conflicts": dir_summary["has_template_conflicts"],
|
|
381
|
+
"file_counts": dir_summary["file_counts"],
|
|
382
|
+
"total_files": dir_summary["total_files"],
|
|
383
|
+
# Recommendations
|
|
384
|
+
"recommended_strategy": self._recommend_migration_strategy(),
|
|
385
|
+
"recommended_branch_strategy": self._recommend_branch_strategy(),
|
|
386
|
+
"migration_steps": self._generate_migration_steps(),
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return summary
|
|
390
|
+
|
|
391
|
+
def _recommend_migration_strategy(self) -> str:
|
|
392
|
+
"""Recommend migration strategy based on analysis."""
|
|
393
|
+
if self.project_type == "empty":
|
|
394
|
+
return "create_new"
|
|
395
|
+
elif self.project_type == "repokit":
|
|
396
|
+
return "already_repokit"
|
|
397
|
+
elif self.migration_complexity == "low":
|
|
398
|
+
return "safe"
|
|
399
|
+
elif self.git_state and self.git_state.get("has_uncommitted_changes"):
|
|
400
|
+
return "safe" # Always safe when there are uncommitted changes
|
|
401
|
+
else:
|
|
402
|
+
return "merge"
|
|
403
|
+
|
|
404
|
+
def _recommend_branch_strategy(self) -> str:
|
|
405
|
+
"""Recommend branch strategy based on project characteristics."""
|
|
406
|
+
if self.project_type == "empty":
|
|
407
|
+
return "standard" # Default for new projects
|
|
408
|
+
|
|
409
|
+
current_strategy = self.git_manager.get_branch_strategy()
|
|
410
|
+
|
|
411
|
+
if current_strategy in ["gitflow", "standard", "simple"]:
|
|
412
|
+
return current_strategy # Keep existing if recognized
|
|
413
|
+
|
|
414
|
+
# For small projects, recommend simple
|
|
415
|
+
dir_summary = self.directory_analyzer.get_summary()
|
|
416
|
+
if dir_summary.get("total_files", 0) < 50:
|
|
417
|
+
return "simple"
|
|
418
|
+
|
|
419
|
+
return "standard" # Default recommendation
|
|
420
|
+
|
|
421
|
+
def _generate_migration_steps(self) -> List[str]:
|
|
422
|
+
"""Generate step-by-step migration recommendations."""
|
|
423
|
+
steps = []
|
|
424
|
+
|
|
425
|
+
if self.project_type == "empty":
|
|
426
|
+
steps = [
|
|
427
|
+
"Initialize new RepoKit project structure",
|
|
428
|
+
"Set up chosen branch strategy",
|
|
429
|
+
"Generate language-specific templates",
|
|
430
|
+
"Create remote repository if requested",
|
|
431
|
+
]
|
|
432
|
+
elif self.project_type == "repokit":
|
|
433
|
+
steps = ["Project is already using RepoKit structure"]
|
|
434
|
+
elif self.git_state and self.git_state.get("has_uncommitted_changes"):
|
|
435
|
+
steps = [
|
|
436
|
+
"WARNING: Uncommitted changes detected",
|
|
437
|
+
"Commit or stash changes before migration",
|
|
438
|
+
"Run migration with 'safe' strategy",
|
|
439
|
+
]
|
|
440
|
+
else:
|
|
441
|
+
steps = [
|
|
442
|
+
"Backup existing project (recommended)",
|
|
443
|
+
"Analyze and resolve any template conflicts",
|
|
444
|
+
"Apply RepoKit directory structure",
|
|
445
|
+
"Initialize or update Git repository",
|
|
446
|
+
"Set up branch strategy and worktrees",
|
|
447
|
+
"Generate appropriate templates",
|
|
448
|
+
"Create remote repository if requested",
|
|
449
|
+
]
|
|
450
|
+
|
|
451
|
+
return steps
|
|
452
|
+
|
|
453
|
+
def _has_repokit_structure(self) -> bool:
|
|
454
|
+
"""
|
|
455
|
+
Check for RepoKit-specific directory structure patterns.
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
True if directory structure suggests RepoKit-like organization
|
|
459
|
+
"""
|
|
460
|
+
# Check for RepoKit standard directories
|
|
461
|
+
repokit_dirs = ["private", "logs", "scripts", "tests", "docs"]
|
|
462
|
+
found_dirs = 0
|
|
463
|
+
|
|
464
|
+
for directory in repokit_dirs:
|
|
465
|
+
if os.path.exists(os.path.join(self.target_dir, directory)):
|
|
466
|
+
found_dirs += 1
|
|
467
|
+
|
|
468
|
+
# Require majority of RepoKit directories to suggest RepoKit structure
|
|
469
|
+
return found_dirs >= len(repokit_dirs) * 0.6
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
class DirectoryAnalyzer:
|
|
473
|
+
"""
|
|
474
|
+
Analyzes directories for migration to RepoKit structure.
|
|
475
|
+
|
|
476
|
+
This class helps identify existing files and directories that match
|
|
477
|
+
RepoKit's expected structure, detects conflicts, and provides migration
|
|
478
|
+
strategies.
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
# Standard RepoKit directories
|
|
482
|
+
STANDARD_DIRS = [
|
|
483
|
+
"convos",
|
|
484
|
+
"docs",
|
|
485
|
+
"logs",
|
|
486
|
+
"private",
|
|
487
|
+
"revisions",
|
|
488
|
+
"scripts",
|
|
489
|
+
"tests",
|
|
490
|
+
]
|
|
491
|
+
|
|
492
|
+
# Private directories that shouldn't be checked in
|
|
493
|
+
PRIVATE_DIRS = DEFAULT_PRIVATE_DIRS
|
|
494
|
+
|
|
495
|
+
# Special directories that need special handling
|
|
496
|
+
SPECIAL_DIRS = [".git", ".github", ".vscode"]
|
|
497
|
+
|
|
498
|
+
# Template directories to look for in the target
|
|
499
|
+
TEMPLATE_DIRS = [".github"]
|
|
500
|
+
|
|
501
|
+
def __init__(self, target_dir: str, verbose: int = 0):
|
|
502
|
+
"""
|
|
503
|
+
Initialize the directory analyzer.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
target_dir: Directory to analyze
|
|
507
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
508
|
+
"""
|
|
509
|
+
self.target_dir = os.path.abspath(target_dir)
|
|
510
|
+
self.verbose = verbose
|
|
511
|
+
self.logger = logging.getLogger("repokit.analyzer")
|
|
512
|
+
|
|
513
|
+
# Analysis results
|
|
514
|
+
self.existing_dirs = set()
|
|
515
|
+
self.missing_dirs = set()
|
|
516
|
+
self.special_dirs = {}
|
|
517
|
+
self.template_conflicts = {}
|
|
518
|
+
|
|
519
|
+
# Scan target directory
|
|
520
|
+
self._scan_directory()
|
|
521
|
+
|
|
522
|
+
def _scan_directory(self) -> None:
|
|
523
|
+
"""Scan the target directory and categorize content."""
|
|
524
|
+
if not os.path.exists(self.target_dir):
|
|
525
|
+
self.logger.warning(f"Target directory does not exist: {self.target_dir}")
|
|
526
|
+
return
|
|
527
|
+
|
|
528
|
+
self.logger.info(f"Scanning directory: {self.target_dir}")
|
|
529
|
+
|
|
530
|
+
# Check for standard directories
|
|
531
|
+
for dirname in self.STANDARD_DIRS:
|
|
532
|
+
dirpath = os.path.join(self.target_dir, dirname)
|
|
533
|
+
if os.path.exists(dirpath) and os.path.isdir(dirpath):
|
|
534
|
+
self.existing_dirs.add(dirname)
|
|
535
|
+
else:
|
|
536
|
+
self.missing_dirs.add(dirname)
|
|
537
|
+
|
|
538
|
+
# Check for special directories
|
|
539
|
+
for dirname in self.SPECIAL_DIRS:
|
|
540
|
+
dirpath = os.path.join(self.target_dir, dirname)
|
|
541
|
+
if os.path.exists(dirpath) and os.path.isdir(dirpath):
|
|
542
|
+
self.special_dirs[dirname] = dirpath
|
|
543
|
+
|
|
544
|
+
# Check for template conflicts
|
|
545
|
+
for template_dir in self.TEMPLATE_DIRS:
|
|
546
|
+
dirpath = os.path.join(self.target_dir, template_dir)
|
|
547
|
+
if os.path.exists(dirpath) and os.path.isdir(dirpath):
|
|
548
|
+
self.template_conflicts[template_dir] = self._scan_template_conflicts(
|
|
549
|
+
template_dir
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
if self.verbose >= 1:
|
|
553
|
+
self._log_scan_results()
|
|
554
|
+
|
|
555
|
+
def _scan_template_conflicts(self, template_dir: str) -> Dict[str, str]:
|
|
556
|
+
"""
|
|
557
|
+
Scan a template directory for potential conflicts.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
template_dir: Template directory name (e.g., ".github")
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Dictionary of {filename: path} for conflicting files
|
|
564
|
+
"""
|
|
565
|
+
conflicts = {}
|
|
566
|
+
dirpath = os.path.join(self.target_dir, template_dir)
|
|
567
|
+
|
|
568
|
+
if not os.path.exists(dirpath):
|
|
569
|
+
return conflicts
|
|
570
|
+
|
|
571
|
+
for root, dirs, files in os.walk(dirpath):
|
|
572
|
+
for file in files:
|
|
573
|
+
filepath = os.path.join(root, file)
|
|
574
|
+
rel_path = os.path.relpath(filepath, dirpath)
|
|
575
|
+
conflicts[rel_path] = filepath
|
|
576
|
+
|
|
577
|
+
return conflicts
|
|
578
|
+
|
|
579
|
+
def _log_scan_results(self) -> None:
|
|
580
|
+
"""Log the results of the directory scan."""
|
|
581
|
+
self.logger.info(
|
|
582
|
+
f"Existing standard directories: {', '.join(self.existing_dirs) or 'None'}"
|
|
583
|
+
)
|
|
584
|
+
self.logger.info(
|
|
585
|
+
f"Missing standard directories: {', '.join(self.missing_dirs) or 'None'}"
|
|
586
|
+
)
|
|
587
|
+
self.logger.info(
|
|
588
|
+
f"Special directories found: {', '.join(self.special_dirs.keys()) or 'None'}"
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
if self.template_conflicts:
|
|
592
|
+
for template_dir, conflicts in self.template_conflicts.items():
|
|
593
|
+
self.logger.info(
|
|
594
|
+
f"Potential conflicts in {template_dir}: {len(conflicts)} files"
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
if self.verbose >= 2:
|
|
598
|
+
for rel_path in conflicts:
|
|
599
|
+
self.logger.debug(f" - {rel_path}")
|
|
600
|
+
|
|
601
|
+
def has_git_repository(self) -> bool:
|
|
602
|
+
"""Check if the target directory is a Git repository."""
|
|
603
|
+
return ".git" in self.special_dirs
|
|
604
|
+
|
|
605
|
+
def has_template_conflicts(self) -> bool:
|
|
606
|
+
"""Check if there are any template conflicts."""
|
|
607
|
+
return any(len(conflicts) > 0 for conflicts in self.template_conflicts.values())
|
|
608
|
+
|
|
609
|
+
def get_migration_plan(self, strategy: str = "safe") -> Dict[str, Any]:
|
|
610
|
+
"""
|
|
611
|
+
Generate a migration plan based on the analysis.
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
strategy: Migration strategy ("safe", "replace", "merge")
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
Dictionary with migration plan details
|
|
618
|
+
"""
|
|
619
|
+
plan = {
|
|
620
|
+
"target_dir": self.target_dir,
|
|
621
|
+
"create_dirs": list(self.missing_dirs),
|
|
622
|
+
"move_dirs": {},
|
|
623
|
+
"special_dirs": {
|
|
624
|
+
k: {"path": v, "action": self._get_special_dir_action(k, strategy)}
|
|
625
|
+
for k, v in self.special_dirs.items()
|
|
626
|
+
},
|
|
627
|
+
"template_conflicts": {
|
|
628
|
+
k: {
|
|
629
|
+
"conflicts": len(v),
|
|
630
|
+
"action": self._get_template_conflict_action(k, strategy),
|
|
631
|
+
}
|
|
632
|
+
for k, v in self.template_conflicts.items()
|
|
633
|
+
},
|
|
634
|
+
"strategy": strategy,
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return plan
|
|
638
|
+
|
|
639
|
+
def _get_special_dir_action(self, dirname: str, strategy: str) -> str:
|
|
640
|
+
"""
|
|
641
|
+
Determine action for a special directory based on strategy.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
dirname: Directory name
|
|
645
|
+
strategy: Migration strategy
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
Action to take ("keep", "backup", "replace", "merge")
|
|
649
|
+
"""
|
|
650
|
+
if strategy == "safe":
|
|
651
|
+
if dirname == ".git":
|
|
652
|
+
return "keep"
|
|
653
|
+
else:
|
|
654
|
+
return "backup"
|
|
655
|
+
elif strategy == "replace":
|
|
656
|
+
return "replace"
|
|
657
|
+
elif strategy == "merge":
|
|
658
|
+
return "merge"
|
|
659
|
+
else:
|
|
660
|
+
return "keep"
|
|
661
|
+
|
|
662
|
+
def _get_template_conflict_action(self, template_dir: str, strategy: str) -> str:
|
|
663
|
+
"""
|
|
664
|
+
Determine action for template conflicts based on strategy.
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
template_dir: Template directory name
|
|
668
|
+
strategy: Migration strategy
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
Action to take ("keep", "backup", "replace", "merge")
|
|
672
|
+
"""
|
|
673
|
+
if strategy == "safe":
|
|
674
|
+
return "backup"
|
|
675
|
+
elif strategy == "replace":
|
|
676
|
+
return "replace"
|
|
677
|
+
elif strategy == "merge":
|
|
678
|
+
return "merge"
|
|
679
|
+
else:
|
|
680
|
+
return "keep"
|
|
681
|
+
|
|
682
|
+
def execute_migration_plan(
|
|
683
|
+
self, plan: Dict[str, Any], dry_run: bool = False
|
|
684
|
+
) -> bool:
|
|
685
|
+
"""
|
|
686
|
+
Execute a migration plan.
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
plan: Migration plan from get_migration_plan()
|
|
690
|
+
dry_run: If True, only log actions without executing
|
|
691
|
+
|
|
692
|
+
Returns:
|
|
693
|
+
True if successful, False otherwise
|
|
694
|
+
"""
|
|
695
|
+
self.logger.info(f"Executing migration plan (dry_run={dry_run})")
|
|
696
|
+
|
|
697
|
+
# Create missing directories
|
|
698
|
+
for dirname in plan["create_dirs"]:
|
|
699
|
+
dirpath = os.path.join(self.target_dir, dirname)
|
|
700
|
+
self.logger.info(f"Creating directory: {dirpath}")
|
|
701
|
+
if not dry_run:
|
|
702
|
+
os.makedirs(dirpath, exist_ok=True)
|
|
703
|
+
|
|
704
|
+
# Handle special directories
|
|
705
|
+
for dirname, info in plan["special_dirs"].items():
|
|
706
|
+
action = info["action"]
|
|
707
|
+
path = info["path"]
|
|
708
|
+
|
|
709
|
+
if action == "keep":
|
|
710
|
+
self.logger.info(f"Keeping {dirname} as is")
|
|
711
|
+
elif action == "backup":
|
|
712
|
+
backup_path = f"{path}_backup"
|
|
713
|
+
self.logger.info(f"Backing up {dirname} to {backup_path}")
|
|
714
|
+
if not dry_run:
|
|
715
|
+
if os.path.exists(backup_path):
|
|
716
|
+
shutil.rmtree(backup_path)
|
|
717
|
+
shutil.copytree(path, backup_path)
|
|
718
|
+
elif action == "replace":
|
|
719
|
+
self.logger.info(f"Replacing {dirname}")
|
|
720
|
+
if not dry_run:
|
|
721
|
+
shutil.rmtree(path)
|
|
722
|
+
elif action == "merge":
|
|
723
|
+
self.logger.info(
|
|
724
|
+
f"Marking {dirname} for merge (requires template content)"
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
# Handle template conflicts
|
|
728
|
+
for template_dir, info in plan["template_conflicts"].items():
|
|
729
|
+
action = info["action"]
|
|
730
|
+
conflicts = self.template_conflicts[template_dir]
|
|
731
|
+
|
|
732
|
+
if action == "keep":
|
|
733
|
+
self.logger.info(f"Keeping existing {template_dir} files")
|
|
734
|
+
elif action == "backup":
|
|
735
|
+
backup_dir = os.path.join(self.target_dir, f"{template_dir}_backup")
|
|
736
|
+
self.logger.info(
|
|
737
|
+
f"Backing up {len(conflicts)} files from {template_dir} to {backup_dir}"
|
|
738
|
+
)
|
|
739
|
+
if not dry_run:
|
|
740
|
+
if not os.path.exists(backup_dir):
|
|
741
|
+
os.makedirs(backup_dir, exist_ok=True)
|
|
742
|
+
for rel_path, filepath in conflicts.items():
|
|
743
|
+
backup_path = os.path.join(backup_dir, rel_path)
|
|
744
|
+
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
|
|
745
|
+
shutil.copy2(filepath, backup_path)
|
|
746
|
+
elif action == "replace":
|
|
747
|
+
self.logger.info(f"Replacing {len(conflicts)} files in {template_dir}")
|
|
748
|
+
dirpath = os.path.join(self.target_dir, template_dir)
|
|
749
|
+
if not dry_run:
|
|
750
|
+
shutil.rmtree(dirpath)
|
|
751
|
+
elif action == "merge":
|
|
752
|
+
self.logger.info(
|
|
753
|
+
f"Marking {template_dir} for merge (requires template content)"
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
return True
|
|
757
|
+
|
|
758
|
+
def categorize_files(self, path: str = None) -> Dict[str, List[str]]:
|
|
759
|
+
"""
|
|
760
|
+
Categorize files in a directory by type.
|
|
761
|
+
|
|
762
|
+
Args:
|
|
763
|
+
path: Directory path to categorize (default: target_dir)
|
|
764
|
+
|
|
765
|
+
Returns:
|
|
766
|
+
Dictionary with categorized files
|
|
767
|
+
"""
|
|
768
|
+
path = path or self.target_dir
|
|
769
|
+
|
|
770
|
+
categories = {
|
|
771
|
+
"python": [], # Python source files
|
|
772
|
+
"javascript": [], # JavaScript source files
|
|
773
|
+
"html": [], # HTML files
|
|
774
|
+
"css": [], # CSS files
|
|
775
|
+
"markdown": [], # Markdown files
|
|
776
|
+
"images": [], # Image files
|
|
777
|
+
"documents": [], # Document files
|
|
778
|
+
"data": [], # Data files
|
|
779
|
+
"config": [], # Configuration files
|
|
780
|
+
"other": [], # Other files
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
for root, dirs, files in os.walk(path):
|
|
784
|
+
# Skip .git directory
|
|
785
|
+
if ".git" in root.split(os.sep):
|
|
786
|
+
continue
|
|
787
|
+
|
|
788
|
+
for file in files:
|
|
789
|
+
filepath = os.path.join(root, file)
|
|
790
|
+
|
|
791
|
+
# Categorize by extension
|
|
792
|
+
if file.endswith((".py", ".pyw")):
|
|
793
|
+
categories["python"].append(filepath)
|
|
794
|
+
elif file.endswith((".js", ".jsx", ".ts", ".tsx")):
|
|
795
|
+
categories["javascript"].append(filepath)
|
|
796
|
+
elif file.endswith((".html", ".htm")):
|
|
797
|
+
categories["html"].append(filepath)
|
|
798
|
+
elif file.endswith((".css", ".scss", ".sass")):
|
|
799
|
+
categories["css"].append(filepath)
|
|
800
|
+
elif file.endswith((".md", ".markdown")):
|
|
801
|
+
categories["markdown"].append(filepath)
|
|
802
|
+
elif file.endswith((".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico")):
|
|
803
|
+
categories["images"].append(filepath)
|
|
804
|
+
elif file.endswith((".doc", ".docx", ".pdf", ".txt", ".rtf")):
|
|
805
|
+
categories["documents"].append(filepath)
|
|
806
|
+
elif file.endswith((".csv", ".json", ".xml", ".yaml", ".yml")):
|
|
807
|
+
categories["data"].append(filepath)
|
|
808
|
+
elif file.endswith((".ini", ".cfg", ".conf", ".config", ".toml")):
|
|
809
|
+
categories["config"].append(filepath)
|
|
810
|
+
else:
|
|
811
|
+
categories["other"].append(filepath)
|
|
812
|
+
|
|
813
|
+
return categories
|
|
814
|
+
|
|
815
|
+
def suggest_language(self) -> str:
|
|
816
|
+
"""
|
|
817
|
+
Suggest a programming language based on the file categories.
|
|
818
|
+
|
|
819
|
+
Returns:
|
|
820
|
+
Suggested language ("python", "javascript", or "generic")
|
|
821
|
+
"""
|
|
822
|
+
categories = self.categorize_files()
|
|
823
|
+
|
|
824
|
+
# Count files by language
|
|
825
|
+
python_count = len(categories["python"])
|
|
826
|
+
javascript_count = len(categories["javascript"])
|
|
827
|
+
|
|
828
|
+
if python_count > javascript_count:
|
|
829
|
+
return "python"
|
|
830
|
+
elif javascript_count > python_count:
|
|
831
|
+
return "javascript"
|
|
832
|
+
else:
|
|
833
|
+
return "generic"
|
|
834
|
+
|
|
835
|
+
def get_summary(self) -> Dict[str, Any]:
|
|
836
|
+
"""
|
|
837
|
+
Get a summary of the directory analysis.
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
Summary dictionary
|
|
841
|
+
"""
|
|
842
|
+
categories = self.categorize_files()
|
|
843
|
+
|
|
844
|
+
summary = {
|
|
845
|
+
"target_dir": self.target_dir,
|
|
846
|
+
"existing_dirs": sorted(list(self.existing_dirs)),
|
|
847
|
+
"missing_dirs": sorted(list(self.missing_dirs)),
|
|
848
|
+
"special_dirs": list(self.special_dirs.keys()),
|
|
849
|
+
"has_git_repository": self.has_git_repository(),
|
|
850
|
+
"has_template_conflicts": self.has_template_conflicts(),
|
|
851
|
+
"suggested_language": self.suggest_language(),
|
|
852
|
+
"file_counts": {
|
|
853
|
+
category: len(files) for category, files in categories.items()
|
|
854
|
+
},
|
|
855
|
+
"total_files": sum(len(files) for files in categories.values()),
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return summary
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
class MigrationManager:
|
|
862
|
+
"""
|
|
863
|
+
Manages migration of directories to RepoKit structure.
|
|
864
|
+
"""
|
|
865
|
+
|
|
866
|
+
def __init__(self, source_dir: str, target_dir: str = None, verbose: int = 0):
|
|
867
|
+
"""
|
|
868
|
+
Initialize the migration manager.
|
|
869
|
+
|
|
870
|
+
Args:
|
|
871
|
+
source_dir: Source directory
|
|
872
|
+
target_dir: Target directory (default: source_dir)
|
|
873
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
874
|
+
"""
|
|
875
|
+
self.source_dir = os.path.abspath(source_dir)
|
|
876
|
+
self.target_dir = os.path.abspath(target_dir) if target_dir else self.source_dir
|
|
877
|
+
self.verbose = verbose
|
|
878
|
+
self.logger = logging.getLogger("repokit.migration")
|
|
879
|
+
|
|
880
|
+
# Initialize analyzer
|
|
881
|
+
self.analyzer = DirectoryAnalyzer(self.source_dir, verbose)
|
|
882
|
+
|
|
883
|
+
def analyze(self) -> Dict[str, Any]:
|
|
884
|
+
"""
|
|
885
|
+
Analyze the source directory.
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
Analysis summary
|
|
889
|
+
"""
|
|
890
|
+
return self.analyzer.get_summary()
|
|
891
|
+
|
|
892
|
+
def plan_migration(self, strategy: str = "safe") -> Dict[str, Any]:
|
|
893
|
+
"""
|
|
894
|
+
Plan migration from source to target.
|
|
895
|
+
|
|
896
|
+
Args:
|
|
897
|
+
strategy: Migration strategy ("safe", "replace", "merge")
|
|
898
|
+
|
|
899
|
+
Returns:
|
|
900
|
+
Migration plan
|
|
901
|
+
"""
|
|
902
|
+
return self.analyzer.get_migration_plan(strategy)
|
|
903
|
+
|
|
904
|
+
def execute_migration(self, plan: Dict[str, Any], dry_run: bool = False) -> bool:
|
|
905
|
+
"""
|
|
906
|
+
Execute a migration plan.
|
|
907
|
+
|
|
908
|
+
Args:
|
|
909
|
+
plan: Migration plan from plan_migration()
|
|
910
|
+
dry_run: If True, only log actions without executing
|
|
911
|
+
|
|
912
|
+
Returns:
|
|
913
|
+
True if successful, False otherwise
|
|
914
|
+
"""
|
|
915
|
+
return self.analyzer.execute_migration_plan(plan, dry_run)
|
|
916
|
+
|
|
917
|
+
def migrate(self, strategy: str = "safe", dry_run: bool = False) -> bool:
|
|
918
|
+
"""
|
|
919
|
+
Analyze, plan, and execute migration in one step.
|
|
920
|
+
|
|
921
|
+
Args:
|
|
922
|
+
strategy: Migration strategy ("safe", "replace", "merge")
|
|
923
|
+
dry_run: If True, only log actions without executing
|
|
924
|
+
|
|
925
|
+
Returns:
|
|
926
|
+
True if successful, False otherwise
|
|
927
|
+
"""
|
|
928
|
+
plan = self.plan_migration(strategy)
|
|
929
|
+
return self.execute_migration(plan, dry_run)
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def analyze_directory(directory: str, verbose: int = 0) -> Dict[str, Any]:
|
|
933
|
+
"""
|
|
934
|
+
Analyze a directory for RepoKit compatibility.
|
|
935
|
+
|
|
936
|
+
Args:
|
|
937
|
+
directory: Directory to analyze
|
|
938
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
939
|
+
|
|
940
|
+
Returns:
|
|
941
|
+
Analysis summary
|
|
942
|
+
"""
|
|
943
|
+
analyzer = DirectoryAnalyzer(directory, verbose)
|
|
944
|
+
return analyzer.get_summary()
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
def plan_migration(
|
|
948
|
+
directory: str, strategy: str = "safe", verbose: int = 0
|
|
949
|
+
) -> Dict[str, Any]:
|
|
950
|
+
"""
|
|
951
|
+
Plan migration for a directory.
|
|
952
|
+
|
|
953
|
+
Args:
|
|
954
|
+
directory: Directory to analyze
|
|
955
|
+
strategy: Migration strategy ("safe", "replace", "merge")
|
|
956
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
957
|
+
|
|
958
|
+
Returns:
|
|
959
|
+
Migration plan
|
|
960
|
+
"""
|
|
961
|
+
analyzer = DirectoryAnalyzer(directory, verbose)
|
|
962
|
+
return analyzer.get_migration_plan(strategy)
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
def migrate_directory(
|
|
966
|
+
directory: str, strategy: str = "safe", dry_run: bool = False, verbose: int = 0
|
|
967
|
+
) -> bool:
|
|
968
|
+
"""
|
|
969
|
+
Migrate a directory to RepoKit structure.
|
|
970
|
+
|
|
971
|
+
Args:
|
|
972
|
+
directory: Directory to migrate
|
|
973
|
+
strategy: Migration strategy ("safe", "replace", "merge")
|
|
974
|
+
dry_run: If True, only log actions without executing
|
|
975
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
976
|
+
|
|
977
|
+
Returns:
|
|
978
|
+
True if successful, False otherwise
|
|
979
|
+
"""
|
|
980
|
+
migration = MigrationManager(directory, verbose=verbose)
|
|
981
|
+
return migration.migrate(strategy, dry_run)
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def analyze_project(directory: str, verbose: int = 0) -> Dict[str, Any]:
|
|
985
|
+
"""
|
|
986
|
+
Comprehensive project analysis for universal migration.
|
|
987
|
+
|
|
988
|
+
Args:
|
|
989
|
+
directory: Directory to analyze
|
|
990
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
991
|
+
|
|
992
|
+
Returns:
|
|
993
|
+
Comprehensive analysis summary
|
|
994
|
+
"""
|
|
995
|
+
analyzer = ProjectAnalyzer(directory, verbose)
|
|
996
|
+
return analyzer.get_comprehensive_summary()
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
def universal_migrate_project(
|
|
1000
|
+
directory: str,
|
|
1001
|
+
target_name: Optional[str] = None,
|
|
1002
|
+
strategy: str = "safe",
|
|
1003
|
+
branch_strategy: str = "auto",
|
|
1004
|
+
publish_to: Optional[str] = None,
|
|
1005
|
+
dry_run: bool = False,
|
|
1006
|
+
verbose: int = 0,
|
|
1007
|
+
) -> bool:
|
|
1008
|
+
"""
|
|
1009
|
+
Universal project migration to RepoKit structure.
|
|
1010
|
+
|
|
1011
|
+
This function handles any type of project and migrates it to RepoKit structure
|
|
1012
|
+
with appropriate branch strategies and remote publishing.
|
|
1013
|
+
|
|
1014
|
+
Args:
|
|
1015
|
+
directory: Source directory to migrate
|
|
1016
|
+
target_name: Target repository name (default: use directory name)
|
|
1017
|
+
strategy: Migration strategy ("safe", "replace", "merge", "auto")
|
|
1018
|
+
branch_strategy: Branch strategy ("standard", "simple", "gitflow", "auto")
|
|
1019
|
+
publish_to: Remote service to publish to ("github", "gitlab")
|
|
1020
|
+
dry_run: If True, only log actions without executing
|
|
1021
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
1022
|
+
|
|
1023
|
+
Returns:
|
|
1024
|
+
True if successful, False otherwise
|
|
1025
|
+
"""
|
|
1026
|
+
# Use ProjectAnalyzer for comprehensive analysis
|
|
1027
|
+
analyzer = ProjectAnalyzer(directory, verbose)
|
|
1028
|
+
summary = analyzer.get_comprehensive_summary()
|
|
1029
|
+
|
|
1030
|
+
# Determine target name
|
|
1031
|
+
if not target_name:
|
|
1032
|
+
target_name = os.path.basename(os.path.abspath(directory))
|
|
1033
|
+
|
|
1034
|
+
# Auto-select strategies if requested
|
|
1035
|
+
if strategy == "auto":
|
|
1036
|
+
strategy = summary["recommended_strategy"]
|
|
1037
|
+
|
|
1038
|
+
if branch_strategy == "auto":
|
|
1039
|
+
branch_strategy = summary["recommended_branch_strategy"]
|
|
1040
|
+
|
|
1041
|
+
# Use UniversalMigrationExecutor for the actual migration
|
|
1042
|
+
executor = UniversalMigrationExecutor(directory, target_name, verbose)
|
|
1043
|
+
return executor.execute_migration(strategy, branch_strategy, publish_to, dry_run)
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
def adopt_project(
|
|
1047
|
+
directory: str = ".",
|
|
1048
|
+
strategy: str = "auto",
|
|
1049
|
+
branch_strategy: str = "auto",
|
|
1050
|
+
publish_to: Optional[str] = None,
|
|
1051
|
+
dry_run: bool = False,
|
|
1052
|
+
verbose: int = 0,
|
|
1053
|
+
) -> bool:
|
|
1054
|
+
"""
|
|
1055
|
+
Adopt an existing project in-place to use RepoKit structure.
|
|
1056
|
+
|
|
1057
|
+
This function converts the current directory to use RepoKit structure
|
|
1058
|
+
without creating a new directory.
|
|
1059
|
+
|
|
1060
|
+
Args:
|
|
1061
|
+
directory: Directory to adopt (default: current directory)
|
|
1062
|
+
strategy: Migration strategy ("safe", "replace", "merge", "auto")
|
|
1063
|
+
branch_strategy: Branch strategy ("standard", "simple", "gitflow", "auto")
|
|
1064
|
+
publish_to: Remote service to publish to ("github", "gitlab")
|
|
1065
|
+
dry_run: If True, only log actions without executing
|
|
1066
|
+
verbose: Verbosity level (0=normal, 1=info, 2=debug)
|
|
1067
|
+
|
|
1068
|
+
Returns:
|
|
1069
|
+
True if successful, False otherwise
|
|
1070
|
+
"""
|
|
1071
|
+
# Use the directory name as the project name
|
|
1072
|
+
target_name = os.path.basename(os.path.abspath(directory))
|
|
1073
|
+
|
|
1074
|
+
# For adoption, we modify in-place rather than creating a new structure
|
|
1075
|
+
return universal_migrate_project(
|
|
1076
|
+
directory=directory,
|
|
1077
|
+
target_name=target_name,
|
|
1078
|
+
strategy=strategy,
|
|
1079
|
+
branch_strategy=branch_strategy,
|
|
1080
|
+
publish_to=publish_to,
|
|
1081
|
+
dry_run=dry_run,
|
|
1082
|
+
verbose=verbose,
|
|
1083
|
+
)
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
class UniversalMigrationExecutor:
|
|
1087
|
+
"""
|
|
1088
|
+
Executes universal project migrations to RepoKit structure.
|
|
1089
|
+
|
|
1090
|
+
This class handles the actual migration process for any type of project,
|
|
1091
|
+
including Git repository setup, file migration, and remote publishing.
|
|
1092
|
+
"""
|
|
1093
|
+
|
|
1094
|
+
def __init__(self, source_dir: str, target_name: str, verbose: int = 0):
|
|
1095
|
+
"""Initialize UniversalMigrationExecutor."""
|
|
1096
|
+
self.source_dir = os.path.abspath(source_dir)
|
|
1097
|
+
self.target_name = target_name
|
|
1098
|
+
self.verbose = verbose
|
|
1099
|
+
self.logger = logging.getLogger("repokit.universal_executor")
|
|
1100
|
+
|
|
1101
|
+
# Initialize project analyzer
|
|
1102
|
+
self.analyzer = ProjectAnalyzer(source_dir, verbose)
|
|
1103
|
+
|
|
1104
|
+
def execute_migration(
|
|
1105
|
+
self,
|
|
1106
|
+
strategy: str = "safe",
|
|
1107
|
+
branch_strategy: str = "standard",
|
|
1108
|
+
publish_to: Optional[str] = None,
|
|
1109
|
+
dry_run: bool = False,
|
|
1110
|
+
) -> bool:
|
|
1111
|
+
"""
|
|
1112
|
+
Execute the universal migration.
|
|
1113
|
+
|
|
1114
|
+
Args:
|
|
1115
|
+
strategy: Migration strategy
|
|
1116
|
+
branch_strategy: Branch strategy to implement
|
|
1117
|
+
publish_to: Remote service to publish to ("github", "gitlab")
|
|
1118
|
+
dry_run: If True, only log actions without executing
|
|
1119
|
+
|
|
1120
|
+
Returns:
|
|
1121
|
+
True if successful, False otherwise
|
|
1122
|
+
"""
|
|
1123
|
+
if dry_run:
|
|
1124
|
+
return self._simulate_migration(strategy, branch_strategy, publish_to)
|
|
1125
|
+
|
|
1126
|
+
try:
|
|
1127
|
+
# Step 1: Prepare target structure
|
|
1128
|
+
if not self._prepare_target_structure():
|
|
1129
|
+
return False
|
|
1130
|
+
|
|
1131
|
+
# Step 2: Migrate files based on strategy
|
|
1132
|
+
if not self._migrate_files(strategy):
|
|
1133
|
+
return False
|
|
1134
|
+
|
|
1135
|
+
# Step 3: Set up Git repository
|
|
1136
|
+
if not self._setup_git_repository(branch_strategy):
|
|
1137
|
+
return False
|
|
1138
|
+
|
|
1139
|
+
# Step 4: Generate templates
|
|
1140
|
+
if not self._generate_templates():
|
|
1141
|
+
return False
|
|
1142
|
+
|
|
1143
|
+
# Step 5: Publish to remote if requested
|
|
1144
|
+
if publish_to and not self._publish_to_remote(publish_to):
|
|
1145
|
+
return False
|
|
1146
|
+
|
|
1147
|
+
self.logger.info("Universal migration completed successfully")
|
|
1148
|
+
return True
|
|
1149
|
+
|
|
1150
|
+
except Exception as e:
|
|
1151
|
+
self.logger.error(f"Migration failed: {str(e)}")
|
|
1152
|
+
return False
|
|
1153
|
+
|
|
1154
|
+
def _simulate_migration(
|
|
1155
|
+
self, strategy: str, branch_strategy: str, publish_to: Optional[str]
|
|
1156
|
+
) -> bool:
|
|
1157
|
+
"""Simulate migration for dry run."""
|
|
1158
|
+
summary = self.analyzer.get_comprehensive_summary()
|
|
1159
|
+
|
|
1160
|
+
self.logger.info("=== MIGRATION SIMULATION ===")
|
|
1161
|
+
self.logger.info(f"Source: {self.source_dir}")
|
|
1162
|
+
self.logger.info(f"Target: {self.target_name}")
|
|
1163
|
+
self.logger.info(f"Project type: {summary['project_type']}")
|
|
1164
|
+
self.logger.info(f"Language: {summary['detected_language']}")
|
|
1165
|
+
self.logger.info(f"Migration strategy: {strategy}")
|
|
1166
|
+
self.logger.info(f"Branch strategy: {branch_strategy}")
|
|
1167
|
+
|
|
1168
|
+
if publish_to:
|
|
1169
|
+
self.logger.info(f"Would publish to: {publish_to}")
|
|
1170
|
+
|
|
1171
|
+
self.logger.info("Migration steps:")
|
|
1172
|
+
for i, step in enumerate(summary["migration_steps"], 1):
|
|
1173
|
+
self.logger.info(f" {i}. {step}")
|
|
1174
|
+
|
|
1175
|
+
return True
|
|
1176
|
+
|
|
1177
|
+
def _prepare_target_structure(self) -> bool:
|
|
1178
|
+
"""Prepare the target RepoKit structure."""
|
|
1179
|
+
self.logger.info("Preparing target structure...")
|
|
1180
|
+
# Implementation would go here
|
|
1181
|
+
return True
|
|
1182
|
+
|
|
1183
|
+
def _migrate_files(self, strategy: str) -> bool:
|
|
1184
|
+
"""Migrate files based on strategy."""
|
|
1185
|
+
self.logger.info(f"Migrating files using strategy: {strategy}")
|
|
1186
|
+
# Implementation would go here
|
|
1187
|
+
return True
|
|
1188
|
+
|
|
1189
|
+
def _setup_git_repository(self, branch_strategy: str) -> bool:
|
|
1190
|
+
"""Set up Git repository with chosen branch strategy."""
|
|
1191
|
+
self.logger.info(f"Setting up Git repository with strategy: {branch_strategy}")
|
|
1192
|
+
|
|
1193
|
+
try:
|
|
1194
|
+
git_manager = GitManager(self.source_dir, self.verbose)
|
|
1195
|
+
|
|
1196
|
+
# Define branch strategies
|
|
1197
|
+
# Use centralized branch configurations from defaults.py
|
|
1198
|
+
branches = DEFAULT_BRANCH_STRATEGIES.get(branch_strategy, ["private", "dev", "main"])
|
|
1199
|
+
|
|
1200
|
+
# Get current repo state
|
|
1201
|
+
repo_state = git_manager.get_repo_state()
|
|
1202
|
+
current_branch = repo_state.get("current_branch")
|
|
1203
|
+
existing_branches = set(repo_state.get("branches", []))
|
|
1204
|
+
|
|
1205
|
+
self.logger.info(f"Current branch: {current_branch}")
|
|
1206
|
+
self.logger.info(f"Existing branches: {existing_branches}")
|
|
1207
|
+
self.logger.info(f"Target branches for {branch_strategy}: {branches}")
|
|
1208
|
+
|
|
1209
|
+
# Create missing branches
|
|
1210
|
+
for branch in branches:
|
|
1211
|
+
if branch not in existing_branches:
|
|
1212
|
+
try:
|
|
1213
|
+
git_manager.run_git(["checkout", "-b", branch])
|
|
1214
|
+
self.logger.info(f"Created branch: {branch}")
|
|
1215
|
+
existing_branches.add(branch)
|
|
1216
|
+
except Exception as e:
|
|
1217
|
+
self.logger.warning(f"Failed to create branch {branch}: {e}")
|
|
1218
|
+
|
|
1219
|
+
# Switch to main/default branch
|
|
1220
|
+
default_branch = "main" if "main" in branches else branches[-1]
|
|
1221
|
+
if default_branch in existing_branches:
|
|
1222
|
+
try:
|
|
1223
|
+
git_manager.run_git(["checkout", default_branch])
|
|
1224
|
+
self.logger.info(f"Switched to default branch: {default_branch}")
|
|
1225
|
+
except Exception as e:
|
|
1226
|
+
self.logger.warning(f"Failed to switch to {default_branch}: {e}")
|
|
1227
|
+
|
|
1228
|
+
return True
|
|
1229
|
+
|
|
1230
|
+
except Exception as e:
|
|
1231
|
+
self.logger.error(f"Failed to setup Git repository: {e}")
|
|
1232
|
+
return False
|
|
1233
|
+
|
|
1234
|
+
def _generate_templates(self) -> bool:
|
|
1235
|
+
"""Generate appropriate templates."""
|
|
1236
|
+
self.logger.info("Generating templates...")
|
|
1237
|
+
# Implementation would go here
|
|
1238
|
+
return True
|
|
1239
|
+
|
|
1240
|
+
def _publish_to_remote(self, service: str) -> bool:
|
|
1241
|
+
"""Publish to remote service."""
|
|
1242
|
+
self.logger.info(f"Publishing to {service}...")
|
|
1243
|
+
# Implementation would go here
|
|
1244
|
+
return True
|