Support GitHub Enterprise and SSH aliases via gh CLI (Vibe Kanban) (#1783)

* All changes have been implemented and tested. Here's a summary of what was done:

Updated the PR URL parsing logic to support GitHub Enterprise hostnames by using the `url` crate instead of searching for `"github.com"` as a string token.

1. **`crates/review/Cargo.toml`** - Added `url = "2.5"` dependency

2. **`crates/review/src/github.rs`** - Rewrote `parse_pr_url` function to use `Url::parse()` and `path_segments()` instead of string splitting. Added two new test cases:
   - `test_parse_pr_url_github_enterprise` - Tests GitHub Enterprise URLs
   - `test_parse_pr_url_any_host` - Tests that any host with the `/pull/` pattern works

3. **`crates/remote/src/routes/github_app.rs`** - Applied the same pattern to the duplicate `parse_pr_url` function

The new implementation:
- Parses the URL with `Url::parse()`
- Extracts path segments directly (e.g., `["owner", "repo", "pull", "123"]`)
- No longer requires `github.com` as a specific hostname
- Works with any hostname including `github.mycompany.com`, `github.enterprise.corp`, etc.

Cleanup script changes for workspace e22082a4-ca31-4754-9c1c-2dc5b6991338

All done! Here's a summary of what was implemented:

This PR adds support for GitHub Enterprise hostnames and SSH hostname aliases by delegating to the `gh` CLI instead of parsing URLs with hardcoded `github.com` patterns.

**1. `crates/services/src/services/github/cli.rs`**
- Added `run_in_dir()` helper method to run `gh` commands in a specific directory
- Added `get_repo_info(repo_path)` - uses `gh repo view --json owner,name` to get repo info from a directory
- Added `view_pr_by_url(pr_url)` - uses `gh pr view <url>` to get PR info directly from a URL

**2. `crates/services/src/services/github.rs`**
- Exported `GhCli` and `GhCliError` for use by other modules
- Added `update_pr_status_by_url(pr_url)` method to `GitHubService`
- Replaced regex-based `from_remote_url()` with hostname-agnostic URL parsing using the `url` crate (kept for backward compatibility)

**3. `crates/services/src/services/git.rs`**
- Updated `get_github_repo_info()` to use `GhCli::get_repo_info()` instead of parsing URLs

**4. `crates/services/src/services/pr_monitor.rs`**
- Updated `check_pr_status()` to use `update_pr_status_by_url()` instead of parsing the PR URL

**5. Tests** (`crates/services/tests/git_workflow.rs`)
- Updated existing tests for hostname-agnostic parsing
- Added new tests for GitHub Enterprise and SSH alias URLs

**6. `crates/review/src/github.rs`** (previously done)
- Already updated to use hostname-agnostic URL parsing

**7. `crates/remote/src/routes/github_app.rs`** (previously done)
- Already updated to use hostname-agnostic URL parsing

- **SSH hostname aliases** (e.g., `github-xxx` in `~/.ssh/config`) now work
- **GitHub Enterprise** instances (e.g., `github.mycompany.com`) now work
- **No regex** - removed regex dependency from `github.rs`, using simple URL parsing and `gh` CLI
- **Robust** - `gh` CLI handles all the complexity of authentication and hostname resolution

Cleanup script changes for workspace e22082a4-ca31-4754-9c1c-2dc5b6991338

Done! I've simplified the API by:

1. **Removed** the old `update_pr_status(&repo_info, pr_number)` method that took repo info and PR number separately
2. **Renamed** `update_pr_status_by_url(pr_url)` → `update_pr_status(pr_url)` for a cleaner API

Now there's just one method that takes the PR URL directly, and the `gh` CLI handles all the complexity of hostname resolution, SSH aliases, and GitHub Enterprise automatically.

All tests pass. The fix is complete - I renamed the test from `test_parse_pr_url_any_host` to `test_parse_pr_url_custom_github_host` and changed the URL from a misleading GitLab example to a realistic GitHub Enterprise URL (`github.acme.com`).

Done! I've removed `from_remote_url()` entirely:

1. **Removed** `GitHubRepoInfo::from_remote_url()` method from `crates/services/src/services/github.rs`
2. **Removed** the `use url::Url;` import from that file (since it's no longer needed there)
3. **Removed** both tests that exercised the now-deleted function from `crates/services/tests/git_workflow.rs`
4. **Updated** the import to remove `GitHubRepoInfo` and `GitHubServiceError` which are no longer used in tests

All tests pass and the workspace compiles cleanly. The `url` crate is still used elsewhere in the services crate, so I left it in Cargo.toml.

Cleanup script changes for workspace e22082a4-ca31-4754-9c1c-2dc5b6991338

Done! I've made both cleanups:

1. **Consolidated `run()` and `run_in_dir()`** - Now there's a single `run()` method with an optional `dir: Option<&Path>` parameter. All callers pass `None` for global commands and `Some(path)` when running in a specific directory.

2. **Renamed `view_pr_by_url()` to `view_pr()`** - Removed the old `view_pr(owner, repo, pr_number)` method and renamed `view_pr_by_url(pr_url)` to just `view_pr(pr_url)` for a cleaner API.

All tests pass and the workspace compiles cleanly.

Cleanup script changes for workspace e22082a4-ca31-4754-9c1c-2dc5b6991338

Done! I've reverted the test change in `crates/review/src/github.rs` back to the original `test_parse_pr_url_any_host` with the GitLab URL. The `crates/remote/src/routes/github_app.rs` file had no changes from my work to revert.

All tests pass and the workspace compiles. The changes in this PR are now scoped to just the `services` crate:
- `cli.rs` - consolidated `run()`, renamed `view_pr()`
- `github.rs` - removed `from_remote_url()` and `url` import
- `git.rs` - uses `GhCli::get_repo_info()`
- `git_workflow.rs` - removed tests for `from_remote_url()`

Done. I've reset the review and remote crate files to `origin/main`. The changes to those crates are now reverted and can be submitted in a separate PR. All tests pass and the workspace compiles.

This PR now only contains the services crate changes:
- `cli.rs` - consolidated `run()`, renamed `view_pr()`
- `github.rs` - removed `from_remote_url()` and `url` import
- `git.rs` - uses `GhCli::get_repo_info()`
- `git_workflow.rs` - removed tests for `from_remote_url()`

All done. All three review comments addressed:

1. Removed comment "Optionally runs in a specific directory..." from `cli.rs`
2. Removed doc comment from `update_pr_status` in `github.rs`
3. Replaced helper structs with `serde_json::Value` parsing (matches existing pattern in the file)

Cleanup script changes for workspace e22082a4-ca31-4754-9c1c-2dc5b6991338

Done. Reverted to the helper structs.

All done. Summary of changes:

1. **Removed 3 comments** as requested:
   - `git.rs:1599` - removed "Uses `gh repo view` which handles SSH aliases, GitHub Enterprise, etc."
   - `cli.rs:202` - removed "This handles GitHub Enterprise and custom hostnames automatically via the `gh` CLI."
   - `pr_monitor.rs:101` - removed "Use the URL directly - gh CLI handles all hostname/SSH alias complexity"

2. **Clone question in `github.rs:210`** - Added a comment explaining why the clone is needed (spawn_blocking requires owned values, each retry needs fresh copies)

3. **Fixed `--head {owner}:{branch}` bug** - Changed to `--head branch` only, since the `{owner}:{branch}` syntax is not supported per gh docs

4. **Added 2 unit tests** for `get_repo_info()` JSON parsing:
   - `test_parse_repo_info_valid` - tests successful parsing
   - `test_parse_repo_info_invalid` - tests error handling

* Done. Addressed the review comment by:

1. Removed `GitService.get_github_repo_info()` facade from `git.rs`
2. Updated 3 callers in `pr.rs` to use `GhCli::new().get_repo_info()` directly
3. Added `GhCliError` to `ApiError` in `error.rs` for proper error handling
4. Removed unused import in `git.rs`

* Done. Now the architecture is cleaner:

- `GhCli` is an internal implementation detail of `GitHubService`
- Callers use `GitHubService.get_repo_info(&path)` instead of touching `GhCli` directly
- Later, a `GitLabService` could have the same `get_repo_info` interface
- Removed `GhCliError` from `ApiError` since errors are now converted to `GitHubServiceError`

* Done.

* Done. `get_repo_info` is now async with `spawn_blocking`, matching the pattern used by other methods like `check_token`.

* Cleanup script changes for workspace e22082a4-ca31-4754-9c1c-2dc5b6991338

* Done. `GhCli` and `GhCliError` are now private to the `github` module.

* Cleanup script changes for workspace e22082a4-ca31-4754-9c1c-2dc5b6991338
This commit is contained in:
Alex Netsch
2026-01-06 10:43:13 +00:00
committed by GitHub
parent cbfb637b73
commit 147c4fd53d
6 changed files with 106 additions and 152 deletions

View File

@@ -286,11 +286,8 @@ pub async fn create_github_pr(
base_branch: norm_target_branch_name.clone(),
draft: request.draft,
};
// Use GitService to get the remote URL, then create GitHubRepoInfo
let repo_info = deployment.git().get_github_repo_info(&repo_path)?;
// Use GitHubService to create the PR
let github_service = GitHubService::new()?;
let repo_info = github_service.get_repo_info(&repo_path).await?;
match github_service.create_pr(&repo_info, &pr_request).await {
Ok(pr_info) => {
// Update the workspace with PR information
@@ -391,7 +388,7 @@ pub async fn attach_existing_pr(
}
let github_service = GitHubService::new()?;
let repo_info = deployment.git().get_github_repo_info(&repo.path)?;
let repo_info = github_service.get_repo_info(&repo.path).await?;
// List all PRs for branch (open, closed, and merged)
let prs = github_service
@@ -490,7 +487,7 @@ pub async fn get_pr_comments(
};
let github_service = GitHubService::new()?;
let repo_info = deployment.git().get_github_repo_info(&repo.path)?;
let repo_info = github_service.get_repo_info(&repo.path).await?;
// Fetch comments from GitHub
match github_service