Skip to content

Commit 944d01b

Browse files
refactor(auto-fork): deduplicate code for better maintainability
Code quality improvements: - Extract _record_fork() helper to eliminate 3 duplicated code blocks - Remove dead code (redundant validation in detect_unforkable_repos) - Update all docstrings to reflect GitLab support - Update SKILL.md: remove outdated "GitLab skipped" notices - Update README.md: add GL_USER_NAME env var, fix outdated docs All tests pass (24/24). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 6ebda30 commit 944d01b

3 files changed

Lines changed: 31 additions & 30 deletions

File tree

.claude/skills/auto-fork/README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
# Auto-Fork Skill
22

3-
Automatically fork repos and update configuration. Detects repos in `project-repos.json` without forks, creates forks under bot's GitHub account, and updates the config file with the new fork URLs.
3+
Automatically fork repos and update configuration. Detects repos in `project-repos.json` without forks, creates forks under bot's GitHub or GitLab account, and updates the config file with the new fork URLs.
44

55
## Features
66

77
- Scans `project-repos.json` for repos needing forks
8-
- Creates forks using `gh repo fork` (GitHub only)
8+
- Creates forks using `gh repo fork` (GitHub) or `glab repo fork` (GitLab)
99
- Updates config file with fork URLs
1010
- Commits changes to new branch
11-
- GitLab repos logged and skipped (manual forking required)
1211

1312
## Usage
1413

@@ -33,6 +32,7 @@ python3 auto_fork.py --dry-run
3332
Environment variables (auto-provided by bot runtime):
3433

3534
- `GH_USER_NAME` — bot GitHub username
35+
- `GL_USER_NAME` — bot GitLab username
3636
- `BOT_CONFIG_PATH` — config directory (default `rehor-config`)
3737
- `BOT_INSTANCE_ID` — optional instance ID (affects branch name)
3838

@@ -69,14 +69,14 @@ uv run ruff check .
6969

7070
1. **detect_unforkable_repos** - Scans `project-repos.json`:
7171
- Identifies repos with `upstream` field
72-
- Checks if `url` matches `GH_USER_NAME`
72+
- Checks if `url` matches bot account (`GH_USER_NAME` for GitHub, `GL_USER_NAME` for GitLab)
7373
- Skips repos already forked
74-
- Skips GitLab repos (GitHub only)
7574

7675
2. **fork_repos** - Creates forks:
77-
- Uses `gh repo fork --clone=false`
76+
- GitHub: Uses `gh repo fork --clone=false`
77+
- GitLab: Uses `glab repo fork --clone=false --hostname gitlab.cee.redhat.com`
7878
- Handles already-forked repos gracefully
79-
- Generates fork URLs: `https://github.com/{bot-username}/{repo-name}.git`
79+
- Generates fork URLs with bot username
8080

8181
3. **update_and_commit** - Updates config:
8282
- Updates `url` field in `project-repos.json`

.claude/skills/auto-fork/SKILL.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: auto-fork
33
description: >
4-
Auto-fork repos + update config. Scans project-repos.json for missing forks → forks via gh → updates config → commits.
4+
Auto-fork repos + update config. Scans project-repos.json for missing forks → forks via gh/glab → updates config → commits.
55
when_to_use: >
66
Triage when repo needs fork or setting up instances. Triggers: "fork repo", "auto fork", "setup fork", "missing fork".
77
user-invocable: true
@@ -21,13 +21,11 @@ python3 .claude/skills/auto-fork/auto_fork.py 2>&1
2121
The script executes these operations:
2222

2323
1. **detect_unforkable_repos** - Scan project-repos.json for repos needing forks
24-
2. **fork_repos** - Create forks using `gh repo fork` for GitHub repos
24+
2. **fork_repos** - Create forks using `gh repo fork` (GitHub) or `glab repo fork` (GitLab)
2525
3. **update_and_commit** - Update project-repos.json with new fork URLs and commit changes
2626

2727
After this completes successfully, use the **push-and-pr** skill to push and create PR.
2828

29-
GitLab repos are skipped with a logged notice (manual forking required).
30-
3129
## Configuration
3230

3331
Env vars (auto-provided by runtime):
@@ -52,4 +50,4 @@ Repos w/ `upstream` but `url` not matching bot account → need fork.
5250
## Error Handling
5351

5452
Fail-fast: if any fork operation fails, stops and reports error.
55-
Idempotent: safe to re-run if forks already exist (gh repo fork handles gracefully).
53+
Idempotent: safe to re-run if forks already exist (gh/glab repo fork handle gracefully).

.claude/skills/auto-fork/auto_fork.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@
22
"""
33
Auto-fork workflow for repos in project-repos.json.
44
5-
Detects repos without forks, creates forks under bot's GitHub account,
5+
Detects repos without forks, creates forks under bot's GitHub or GitLab account,
66
and updates project-repos.json locally. After committing changes, use
77
push-and-pr skill to create the PR.
88
99
Operations:
1010
1. detect_unforkable_repos - scan for repos needing forks
11-
2. fork_repos - create forks using gh repo fork
11+
2. fork_repos - create forks using gh repo fork (GitHub) or glab repo fork (GitLab)
1212
3. update_and_commit - update project-repos.json and commit changes
1313
1414
After this script completes, use push-and-pr skill to create the PR.
15-
GitLab repos are skipped with logged notice.
1615
"""
1716

1817
import argparse
@@ -135,19 +134,11 @@ def detect_unforkable_repos(self) -> OperationResult:
135134
A repo needs a fork if:
136135
- It has an 'upstream' field
137136
- Its 'url' field doesn't match bot's account pattern
138-
- It's a GitHub repo (GitLab skipped for now)
137+
- Supports both GitHub and GitLab repos
139138
140139
Returns:
141140
OperationResult with list of repos needing forks
142141
"""
143-
if not self.bot_username:
144-
error_msg = "BOT_GITHUB_USERNAME env var not set"
145-
logger.error(error_msg)
146-
return OperationResult(
147-
operation="detect_unforkable_repos",
148-
status=OperationStatus.FAILED,
149-
message=error_msg,
150-
)
151142

152143
if not self.project_repos_path.exists():
153144
error_msg = f"project-repos.json not found at {self.project_repos_path}"
@@ -239,6 +230,21 @@ def _get_fork_url(self, repo_name: str, host: str) -> str:
239230
return f"https://{GITLAB_HOST}/{self.gl_username}/{repo_name}.git"
240231
return f"https://github.com/{self.bot_username}/{repo_name}.git"
241232

233+
def _record_fork(self, repo_name: str, host: str) -> str:
234+
"""
235+
Record successful fork and return fork URL.
236+
237+
Args:
238+
repo_name: Name of the repository
239+
host: Host type ("github" or "gitlab")
240+
241+
Returns:
242+
Fork URL
243+
"""
244+
fork_url = self._get_fork_url(repo_name, host)
245+
self.forked_repos[repo_name] = fork_url
246+
return fork_url
247+
242248
def fork_repos(self) -> OperationResult:
243249
"""
244250
Create forks for detected repos using gh or glab.
@@ -264,8 +270,7 @@ def fork_repos(self) -> OperationResult:
264270
logger.info(f"Forking {path} ({repo.host})...")
265271

266272
if self.dry_run:
267-
fork_url = self._get_fork_url(repo.name, repo.host)
268-
self.forked_repos[repo.name] = fork_url
273+
fork_url = self._record_fork(repo.name, repo.host)
269274
logger.info(f"[DRY RUN] Would fork {path} to {fork_url}")
270275
continue
271276

@@ -297,16 +302,14 @@ def fork_repos(self) -> OperationResult:
297302
# Check if already forked (not an error)
298303
if "already exists" in result.stderr.lower() or "already forked" in result.stderr.lower():
299304
logger.info(f"{path} already forked")
300-
fork_url = self._get_fork_url(repo.name, repo.host)
301-
self.forked_repos[repo.name] = fork_url
305+
self._record_fork(repo.name, repo.host)
302306
else:
303307
error_msg = f"Failed to fork {path}: {result.stderr}"
304308
logger.error(error_msg)
305309
failed.append(repo.name)
306310
continue
307311
else:
308-
fork_url = self._get_fork_url(repo.name, repo.host)
309-
self.forked_repos[repo.name] = fork_url
312+
fork_url = self._record_fork(repo.name, repo.host)
310313
logger.info(f"Forked {path} to {fork_url}")
311314

312315
except Exception as e:

0 commit comments

Comments
 (0)