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
repokit/git_config.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Git Configuration Manager for RepoKit
|
|
4
|
+
|
|
5
|
+
Enhanced git configuration detection with multi-level cascade support.
|
|
6
|
+
Provides comprehensive user.name and user.email detection across different
|
|
7
|
+
git config levels.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Dict, Optional, Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GitConfigManager:
|
|
17
|
+
"""
|
|
18
|
+
Manages git configuration detection with multi-level cascade support.
|
|
19
|
+
|
|
20
|
+
Detection levels (in order of precedence):
|
|
21
|
+
1. Repository-specific config (.git/config)
|
|
22
|
+
2. Global user config (~/.gitconfig)
|
|
23
|
+
3. System config (/etc/gitconfig)
|
|
24
|
+
4. Environment variables
|
|
25
|
+
5. Interactive prompts (if enabled)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, repo_path: Optional[str] = None, verbose: int = 0):
|
|
29
|
+
"""
|
|
30
|
+
Initialize GitConfigManager.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
repo_path: Path to git repository (for repo-specific config)
|
|
34
|
+
verbose: Verbosity level for logging
|
|
35
|
+
"""
|
|
36
|
+
self.repo_path = repo_path
|
|
37
|
+
self.verbose = verbose
|
|
38
|
+
self.logger = logging.getLogger("repokit.git_config")
|
|
39
|
+
|
|
40
|
+
def get_comprehensive_user_info(self, interactive: bool = True) -> Dict[str, str]:
|
|
41
|
+
"""
|
|
42
|
+
Get git user information using comprehensive detection cascade.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
interactive: Whether to prompt user if information is missing
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dictionary with 'name' and 'email' keys
|
|
49
|
+
"""
|
|
50
|
+
user_info = {}
|
|
51
|
+
|
|
52
|
+
# Level 1: Repository-specific config
|
|
53
|
+
if self.repo_path:
|
|
54
|
+
repo_config = self._get_repo_git_config()
|
|
55
|
+
user_info.update(repo_config)
|
|
56
|
+
if self.verbose >= 2:
|
|
57
|
+
self.logger.debug(f"Repository config: {repo_config}")
|
|
58
|
+
|
|
59
|
+
# Level 2: Global git config (if missing data)
|
|
60
|
+
if not user_info.get('name') or not user_info.get('email'):
|
|
61
|
+
global_config = self._get_global_git_config()
|
|
62
|
+
for key, value in global_config.items():
|
|
63
|
+
if not user_info.get(key):
|
|
64
|
+
user_info[key] = value
|
|
65
|
+
if self.verbose >= 2:
|
|
66
|
+
self.logger.debug(f"Global config: {global_config}")
|
|
67
|
+
|
|
68
|
+
# Level 3: System git config (if still missing data)
|
|
69
|
+
if not user_info.get('name') or not user_info.get('email'):
|
|
70
|
+
system_config = self._get_system_git_config()
|
|
71
|
+
for key, value in system_config.items():
|
|
72
|
+
if not user_info.get(key):
|
|
73
|
+
user_info[key] = value
|
|
74
|
+
if self.verbose >= 2:
|
|
75
|
+
self.logger.debug(f"System config: {system_config}")
|
|
76
|
+
|
|
77
|
+
# Level 4: Environment variables
|
|
78
|
+
if not user_info.get('name') or not user_info.get('email'):
|
|
79
|
+
env_config = self._get_env_git_config()
|
|
80
|
+
for key, value in env_config.items():
|
|
81
|
+
if not user_info.get(key):
|
|
82
|
+
user_info[key] = value
|
|
83
|
+
if self.verbose >= 2:
|
|
84
|
+
self.logger.debug(f"Environment config: {env_config}")
|
|
85
|
+
|
|
86
|
+
# Level 5: Interactive prompts (if enabled and data still missing)
|
|
87
|
+
missing_info = (not user_info.get('name') or
|
|
88
|
+
not user_info.get('email'))
|
|
89
|
+
if interactive and missing_info:
|
|
90
|
+
interactive_config = self._prompt_for_git_config(user_info)
|
|
91
|
+
user_info.update(interactive_config)
|
|
92
|
+
if self.verbose >= 2:
|
|
93
|
+
self.logger.debug(f"Interactive config: {interactive_config}")
|
|
94
|
+
|
|
95
|
+
# Log final result
|
|
96
|
+
if self.verbose >= 1:
|
|
97
|
+
name = user_info.get('name', 'NOT SET')
|
|
98
|
+
email = user_info.get('email', 'NOT SET')
|
|
99
|
+
self.logger.info(f"Final git user config: name='{name}', "
|
|
100
|
+
f"email='{email}'")
|
|
101
|
+
|
|
102
|
+
return user_info
|
|
103
|
+
|
|
104
|
+
def _get_repo_git_config(self) -> Dict[str, str]:
|
|
105
|
+
"""Get user config from repository-specific .git/config."""
|
|
106
|
+
if not self.repo_path:
|
|
107
|
+
return {}
|
|
108
|
+
|
|
109
|
+
git_path = os.path.join(self.repo_path, ".git")
|
|
110
|
+
if not os.path.exists(git_path):
|
|
111
|
+
return {}
|
|
112
|
+
|
|
113
|
+
config = {}
|
|
114
|
+
|
|
115
|
+
# Get user.name from repo config
|
|
116
|
+
try:
|
|
117
|
+
result = subprocess.run(
|
|
118
|
+
["git", "config", "--local", "user.name"],
|
|
119
|
+
cwd=self.repo_path,
|
|
120
|
+
capture_output=True,
|
|
121
|
+
text=True,
|
|
122
|
+
check=False,
|
|
123
|
+
)
|
|
124
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
125
|
+
config["name"] = result.stdout.strip()
|
|
126
|
+
except Exception as e:
|
|
127
|
+
if self.verbose >= 3:
|
|
128
|
+
self.logger.debug(f"Failed to get repo user.name: {e}")
|
|
129
|
+
|
|
130
|
+
# Get user.email from repo config
|
|
131
|
+
try:
|
|
132
|
+
result = subprocess.run(
|
|
133
|
+
["git", "config", "--local", "user.email"],
|
|
134
|
+
cwd=self.repo_path,
|
|
135
|
+
capture_output=True,
|
|
136
|
+
text=True,
|
|
137
|
+
check=False,
|
|
138
|
+
)
|
|
139
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
140
|
+
config["email"] = result.stdout.strip()
|
|
141
|
+
except Exception as e:
|
|
142
|
+
if self.verbose >= 3:
|
|
143
|
+
self.logger.debug(f"Failed to get repo user.email: {e}")
|
|
144
|
+
|
|
145
|
+
return config
|
|
146
|
+
|
|
147
|
+
def _get_global_git_config(self) -> Dict[str, str]:
|
|
148
|
+
"""Get user config from global ~/.gitconfig."""
|
|
149
|
+
config = {}
|
|
150
|
+
|
|
151
|
+
# Get user.name from global config
|
|
152
|
+
try:
|
|
153
|
+
result = subprocess.run(
|
|
154
|
+
["git", "config", "--global", "user.name"],
|
|
155
|
+
capture_output=True,
|
|
156
|
+
text=True,
|
|
157
|
+
check=False,
|
|
158
|
+
)
|
|
159
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
160
|
+
config["name"] = result.stdout.strip()
|
|
161
|
+
except Exception as e:
|
|
162
|
+
if self.verbose >= 3:
|
|
163
|
+
self.logger.debug(f"Failed to get global user.name: {e}")
|
|
164
|
+
|
|
165
|
+
# Get user.email from global config
|
|
166
|
+
try:
|
|
167
|
+
result = subprocess.run(
|
|
168
|
+
["git", "config", "--global", "user.email"],
|
|
169
|
+
capture_output=True,
|
|
170
|
+
text=True,
|
|
171
|
+
check=False,
|
|
172
|
+
)
|
|
173
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
174
|
+
config["email"] = result.stdout.strip()
|
|
175
|
+
except Exception as e:
|
|
176
|
+
if self.verbose >= 3:
|
|
177
|
+
self.logger.debug(f"Failed to get global user.email: {e}")
|
|
178
|
+
|
|
179
|
+
return config
|
|
180
|
+
|
|
181
|
+
def _get_system_git_config(self) -> Dict[str, str]:
|
|
182
|
+
"""Get user config from system /etc/gitconfig."""
|
|
183
|
+
config = {}
|
|
184
|
+
|
|
185
|
+
# Get user.name from system config
|
|
186
|
+
try:
|
|
187
|
+
result = subprocess.run(
|
|
188
|
+
["git", "config", "--system", "user.name"],
|
|
189
|
+
capture_output=True,
|
|
190
|
+
text=True,
|
|
191
|
+
check=False,
|
|
192
|
+
)
|
|
193
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
194
|
+
config["name"] = result.stdout.strip()
|
|
195
|
+
except Exception as e:
|
|
196
|
+
if self.verbose >= 3:
|
|
197
|
+
self.logger.debug(f"Failed to get system user.name: {e}")
|
|
198
|
+
|
|
199
|
+
# Get user.email from system config
|
|
200
|
+
try:
|
|
201
|
+
result = subprocess.run(
|
|
202
|
+
["git", "config", "--system", "user.email"],
|
|
203
|
+
capture_output=True,
|
|
204
|
+
text=True,
|
|
205
|
+
check=False,
|
|
206
|
+
)
|
|
207
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
208
|
+
config["email"] = result.stdout.strip()
|
|
209
|
+
except Exception as e:
|
|
210
|
+
if self.verbose >= 3:
|
|
211
|
+
self.logger.debug(f"Failed to get system user.email: {e}")
|
|
212
|
+
|
|
213
|
+
return config
|
|
214
|
+
|
|
215
|
+
def _get_env_git_config(self) -> Dict[str, str]:
|
|
216
|
+
"""Get user config from environment variables."""
|
|
217
|
+
config = {}
|
|
218
|
+
|
|
219
|
+
# Check for git-specific environment variables
|
|
220
|
+
if os.environ.get('GIT_AUTHOR_NAME'):
|
|
221
|
+
config["name"] = os.environ['GIT_AUTHOR_NAME']
|
|
222
|
+
elif os.environ.get('GIT_COMMITTER_NAME'):
|
|
223
|
+
config["name"] = os.environ['GIT_COMMITTER_NAME']
|
|
224
|
+
|
|
225
|
+
if os.environ.get('GIT_AUTHOR_EMAIL'):
|
|
226
|
+
config["email"] = os.environ['GIT_AUTHOR_EMAIL']
|
|
227
|
+
elif os.environ.get('GIT_COMMITTER_EMAIL'):
|
|
228
|
+
config["email"] = os.environ['GIT_COMMITTER_EMAIL']
|
|
229
|
+
|
|
230
|
+
# Check for RepoKit-specific environment variables
|
|
231
|
+
if os.environ.get('REPOKIT_USER_NAME'):
|
|
232
|
+
config["name"] = os.environ['REPOKIT_USER_NAME']
|
|
233
|
+
|
|
234
|
+
if os.environ.get('REPOKIT_USER_EMAIL'):
|
|
235
|
+
config["email"] = os.environ['REPOKIT_USER_EMAIL']
|
|
236
|
+
|
|
237
|
+
return config
|
|
238
|
+
|
|
239
|
+
def _prompt_for_git_config(self, existing_config: Dict[str, str]) -> Dict[str, str]:
|
|
240
|
+
"""
|
|
241
|
+
Interactively prompt user for missing git configuration.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
existing_config: Already detected configuration
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Dictionary with user-provided values
|
|
248
|
+
"""
|
|
249
|
+
config = {}
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
# Prompt for name if missing
|
|
253
|
+
if not existing_config.get('name'):
|
|
254
|
+
print("\n" + "="*60)
|
|
255
|
+
print("Git User Configuration Required")
|
|
256
|
+
print("="*60)
|
|
257
|
+
print("RepoKit needs your git user information for commits "
|
|
258
|
+
"and templates.")
|
|
259
|
+
print("This will be saved to your project's .repokit.json file.")
|
|
260
|
+
print()
|
|
261
|
+
|
|
262
|
+
name = input("Enter your full name (for git commits): ").strip()
|
|
263
|
+
if name:
|
|
264
|
+
config["name"] = name
|
|
265
|
+
|
|
266
|
+
# Prompt for email if missing
|
|
267
|
+
if not existing_config.get('email'):
|
|
268
|
+
if not existing_config.get('name'):
|
|
269
|
+
print() # Add spacing if we already prompted for name
|
|
270
|
+
|
|
271
|
+
email = input("Enter your email address (for git commits): ").strip()
|
|
272
|
+
if email:
|
|
273
|
+
config["email"] = email
|
|
274
|
+
|
|
275
|
+
if config:
|
|
276
|
+
print()
|
|
277
|
+
print("✓ Configuration collected. This will be saved for future use.")
|
|
278
|
+
print("="*60)
|
|
279
|
+
|
|
280
|
+
except (KeyboardInterrupt, EOFError):
|
|
281
|
+
# User cancelled input
|
|
282
|
+
if self.verbose >= 1:
|
|
283
|
+
self.logger.info("User cancelled git config input")
|
|
284
|
+
|
|
285
|
+
return config
|
|
286
|
+
|
|
287
|
+
def get_template_context(self, base_context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
288
|
+
"""
|
|
289
|
+
Get enhanced template context with git user information.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
base_context: Existing template context to enhance
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Enhanced context dictionary with git user info
|
|
296
|
+
"""
|
|
297
|
+
context = base_context.copy() if base_context else {}
|
|
298
|
+
|
|
299
|
+
# Get comprehensive user info
|
|
300
|
+
user_info = self.get_comprehensive_user_info(interactive=False)
|
|
301
|
+
|
|
302
|
+
# Add git-specific template variables
|
|
303
|
+
context.update({
|
|
304
|
+
'author': user_info.get('name', 'Unknown Author'),
|
|
305
|
+
'author_email': user_info.get('email', 'unknown@example.com'),
|
|
306
|
+
'git_user_name': user_info.get('name'),
|
|
307
|
+
'git_user_email': user_info.get('email'),
|
|
308
|
+
|
|
309
|
+
# Backwards compatibility
|
|
310
|
+
'user_name': user_info.get('name'),
|
|
311
|
+
'user_email': user_info.get('email'),
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
return context
|
|
315
|
+
|
|
316
|
+
def configure_repo_git_user(self, user_info: Optional[Dict[str, str]] = None) -> bool:
|
|
317
|
+
"""
|
|
318
|
+
Configure git user settings in the repository.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
user_info: User info dict, or None to detect automatically
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
True if configuration was successful
|
|
325
|
+
"""
|
|
326
|
+
if not self.repo_path:
|
|
327
|
+
self.logger.warning("Cannot configure git user: no repository path")
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
git_path = os.path.join(self.repo_path, ".git")
|
|
331
|
+
if not os.path.exists(git_path):
|
|
332
|
+
self.logger.warning("Cannot configure git user: not a git repository")
|
|
333
|
+
return False
|
|
334
|
+
|
|
335
|
+
if not user_info:
|
|
336
|
+
user_info = self.get_comprehensive_user_info(interactive=True)
|
|
337
|
+
|
|
338
|
+
success = True
|
|
339
|
+
|
|
340
|
+
# Configure user.name
|
|
341
|
+
if user_info.get('name'):
|
|
342
|
+
try:
|
|
343
|
+
subprocess.run(
|
|
344
|
+
["git", "config", "user.name", user_info['name']],
|
|
345
|
+
cwd=self.repo_path,
|
|
346
|
+
check=True,
|
|
347
|
+
capture_output=True
|
|
348
|
+
)
|
|
349
|
+
if self.verbose >= 1:
|
|
350
|
+
self.logger.info(f"Set git user.name to: {user_info['name']}")
|
|
351
|
+
except subprocess.CalledProcessError as e:
|
|
352
|
+
self.logger.error(f"Failed to set git user.name: {e}")
|
|
353
|
+
success = False
|
|
354
|
+
|
|
355
|
+
# Configure user.email
|
|
356
|
+
if user_info.get('email'):
|
|
357
|
+
try:
|
|
358
|
+
subprocess.run(
|
|
359
|
+
["git", "config", "user.email", user_info['email']],
|
|
360
|
+
cwd=self.repo_path,
|
|
361
|
+
check=True,
|
|
362
|
+
capture_output=True
|
|
363
|
+
)
|
|
364
|
+
if self.verbose >= 1:
|
|
365
|
+
self.logger.info(f"Set git user.email to: {user_info['email']}")
|
|
366
|
+
except subprocess.CalledProcessError as e:
|
|
367
|
+
self.logger.error(f"Failed to set git user.email: {e}")
|
|
368
|
+
success = False
|
|
369
|
+
|
|
370
|
+
return success
|
repokit/github_create.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
`repokit create --github`: the repository on GitHub, through the gh CLI.
|
|
4
|
+
|
|
5
|
+
gh holds the login (`gh auth login`), so git-repokit never handles a token on
|
|
6
|
+
this path -- the ease the GitHub template used to give, where GitHub made the
|
|
7
|
+
repository and the tooling only configured it (djdarcy, 2026-10-07).
|
|
8
|
+
|
|
9
|
+
Only public branches are pushed: in the private-branch model the private
|
|
10
|
+
branch never leaves the machine, so `gh repo create --push` (which pushes the
|
|
11
|
+
current branch -- the private one there) is not used; branches are pushed
|
|
12
|
+
explicitly.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import shutil
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from typing import List, Optional, Tuple
|
|
20
|
+
|
|
21
|
+
from .defaults import strategy_push_branches
|
|
22
|
+
|
|
23
|
+
GH_ENV = "REPOKIT_GH" # override the gh executable (tests use a stub)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def gh_command() -> Optional[List[str]]:
|
|
27
|
+
override = os.environ.get(GH_ENV)
|
|
28
|
+
if override:
|
|
29
|
+
return [sys.executable, override] if override.endswith(".py") else [override]
|
|
30
|
+
found = shutil.which("gh")
|
|
31
|
+
return [found] if found else None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _run(cmd: List[str], cwd: str) -> Tuple[int, str]:
|
|
35
|
+
try:
|
|
36
|
+
result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True,
|
|
37
|
+
encoding="utf-8", errors="replace")
|
|
38
|
+
except OSError as e: # the executable does not exist or cannot run
|
|
39
|
+
return 127, str(e)
|
|
40
|
+
return result.returncode, (result.stdout + result.stderr).strip()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def gh_login(cwd: str) -> Optional[str]:
|
|
44
|
+
"""The account gh is logged into, or None when gh is absent, logged out or
|
|
45
|
+
unreadable. Read before any file is written (0.9.6), so the project's URLs,
|
|
46
|
+
CODEOWNERS and FUNDING carry the real owner rather than a placeholder;
|
|
47
|
+
create_on_github still checks the login itself and reports any problem."""
|
|
48
|
+
gh = gh_command()
|
|
49
|
+
if gh is None:
|
|
50
|
+
return None
|
|
51
|
+
rc, out = _run(gh + ["api", "user", "-q", ".login"], cwd)
|
|
52
|
+
login = out.splitlines()[-1].strip() if rc == 0 and out else ""
|
|
53
|
+
return login or None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def branches_to_push(config: dict, private_branch: Optional[str], existing: List[str]) -> List[str]:
|
|
57
|
+
"""The strategy's public branches that exist locally; never the private branch."""
|
|
58
|
+
wanted = strategy_push_branches(config.get("branch_strategy", "main-only")) or ["main"]
|
|
59
|
+
return [b for b in wanted if b in existing and b != private_branch]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def create_on_github(repo_root: str, name: str, config: dict, private_branch: Optional[str],
|
|
63
|
+
organization: Optional[str] = None, public: bool = False,
|
|
64
|
+
logger=None, report: Optional[List[str]] = None) -> Optional[str]:
|
|
65
|
+
"""Create <owner>/<name> on GitHub, run the GitHub-half steps the config
|
|
66
|
+
names (`github_setup`, 0.9.3: labels and the two issues before the push,
|
|
67
|
+
the rest after), and push the public branches.
|
|
68
|
+
|
|
69
|
+
Returns None on success, or what went wrong (the local repository is
|
|
70
|
+
left as it is either way). The steps' report lines go into ``report``.
|
|
71
|
+
"""
|
|
72
|
+
from . import github_setup
|
|
73
|
+
steps = github_setup.setup_names(config.get("github_setup"))
|
|
74
|
+
report = report if report is not None else []
|
|
75
|
+
|
|
76
|
+
gh = gh_command()
|
|
77
|
+
if gh is None:
|
|
78
|
+
return "gh (the GitHub CLI) is not installed; see https://cli.github.com"
|
|
79
|
+
|
|
80
|
+
rc, out = _run(gh + ["auth", "status"], repo_root)
|
|
81
|
+
if rc != 0:
|
|
82
|
+
return f"gh is not logged in (run `gh auth login`): {out}"
|
|
83
|
+
|
|
84
|
+
rc, out = _run(gh + ["api", "user", "-q", ".login"], repo_root)
|
|
85
|
+
login = out.splitlines()[-1].strip() if rc == 0 and out else None
|
|
86
|
+
if not organization and not login:
|
|
87
|
+
return f"could not read the GitHub user from gh: {out}"
|
|
88
|
+
# Refuse on another account: the config names the person's handle, and gh
|
|
89
|
+
# is logged into someone else (0.9.3).
|
|
90
|
+
github_cfg = config.get("github") if isinstance(config.get("github"), dict) else {}
|
|
91
|
+
configured = (github_cfg or {}).get("username")
|
|
92
|
+
if configured and login and configured.lower() != login.lower():
|
|
93
|
+
return (f"gh is logged into {login!r} but the config's github.username is {configured!r}; "
|
|
94
|
+
"switch with `gh auth switch` or fix the config")
|
|
95
|
+
owner = organization or login
|
|
96
|
+
|
|
97
|
+
create = gh + ["repo", "create", f"{owner}/{name}",
|
|
98
|
+
"--public" if public else "--private",
|
|
99
|
+
"--source", repo_root, "--remote", "origin"]
|
|
100
|
+
description = config.get("description")
|
|
101
|
+
if description:
|
|
102
|
+
create += ["--description", description]
|
|
103
|
+
rc, out = _run(create, repo_root)
|
|
104
|
+
if rc != 0:
|
|
105
|
+
return f"gh repo create failed: {out}"
|
|
106
|
+
if logger:
|
|
107
|
+
logger.info(f"Created {owner}/{name} on GitHub ({'public' if public else 'private'})")
|
|
108
|
+
|
|
109
|
+
# Before the first push: the labels and the two issues, so they are #1 and #2.
|
|
110
|
+
setup = github_setup.GhSetup(gh, f"{owner}/{name}", lambda cmd: _run(cmd, repo_root))
|
|
111
|
+
project = config.get("name") or name
|
|
112
|
+
if "labels" in steps:
|
|
113
|
+
report += setup.labels(github_setup.referenced_labels(repo_root))
|
|
114
|
+
if "issues" in steps:
|
|
115
|
+
drafts = os.path.join(repo_root, "private", "claude", "issues")
|
|
116
|
+
report += setup.issues(project, drafts if os.path.isdir(drafts) else None)
|
|
117
|
+
if "pin" in steps:
|
|
118
|
+
report += setup.pin()
|
|
119
|
+
|
|
120
|
+
rc, out = _run(["git", "for-each-ref", "--format=%(refname:short)", "refs/heads"], repo_root)
|
|
121
|
+
existing = out.split()
|
|
122
|
+
pushed = []
|
|
123
|
+
for branch in branches_to_push(config, private_branch, existing):
|
|
124
|
+
rc, out = _run(["git", "push", "-u", "origin", branch], repo_root)
|
|
125
|
+
if rc != 0:
|
|
126
|
+
return f"git push of '{branch}' failed: {out}"
|
|
127
|
+
pushed.append(branch)
|
|
128
|
+
if logger:
|
|
129
|
+
logger.info(f"Pushed: {', '.join(pushed) or 'nothing'}")
|
|
130
|
+
|
|
131
|
+
# After the push: the settings that need the repository's content or no ordering.
|
|
132
|
+
if "discussions" in steps:
|
|
133
|
+
report += setup.discussions()
|
|
134
|
+
if "topics" in steps:
|
|
135
|
+
report += setup.topics(list(config.get("topics") or []))
|
|
136
|
+
if "pypi-environment" in steps:
|
|
137
|
+
report += setup.pypi_environment(config.get("archetype"))
|
|
138
|
+
if "traffic" in steps:
|
|
139
|
+
report += setup.traffic(project)
|
|
140
|
+
if steps:
|
|
141
|
+
report += github_setup.reminders(f"{owner}/{name}", list(config.get("extras") or []))
|
|
142
|
+
return None
|