Skip to content

Fix but pr new for repository names containing ".git" (#15302) - #15311

Open
zkasuran wants to merge 1 commit into
gitbutlerapp:masterfrom
zkasuran:fix/pr-repo-name-containing-dotgit
Open

Fix but pr new for repository names containing ".git" (#15302)#15311
zkasuran wants to merge 1 commit into
gitbutlerapp:masterfrom
zkasuran:fix/pr-repo-name-containing-dotgit

Conversation

@zkasuran

Copy link
Copy Markdown

🧢 Changes

but pr new failed with HTTP 404 ("Failed to list open pull requests") for any
GitHub repo whose name contains the substring .git, for example a
*.github.io pages repo cloned over its normal .git URL.

This strips a single trailing .git from the remote URL in
derive_forge_repo_info (crates/but-forge/src/lib.rs) before it is parsed, so
the repository name is kept intact. One line of code plus a comment and a
table-driven regression test.

pub fn derive_forge_repo_info(url: &str) -> Option<ForgeRepoInfo> {
    // git-url-parse 0.6.0's GenericProvider strips the git-suffix with
    // `take_until(".git")`, which stops at the FIRST ".git" substring rather
    // than the terminal suffix. So `org/org.github.io.git` yields repo="org" ...
    let url = url.strip_suffix(".git").unwrap_or(url);
    let git_url = GitUrl::parse(url).ok()?;
    ...

☕️ Reasoning

derive_forge_repo_info parses the remote URL into (owner, repo) with
git-url-parse 0.6.0. When the URL ends in .git, that crate reads the repo
segment with take_until(".git"), which stops at the first .git
substring, not the terminal suffix. For the path org/org.github.io.git it
stops inside .github and returns repo = "org". The value then flows through
list_forge_reviews to but_github's list_open_pulls, which issues
GET /repos/{owner}/{repo}/pulls. With the corrupted pair that becomes
GET /repos/org/org/pulls, which 404s.

The bug is inside the dependency and cannot be edited there, so the fix
normalizes the input at our boundary. Once the URL no longer ends in .git, the
parser takes its other branch (is_not("/")), which reads the repo name up to
the next slash and keeps org.github.io whole.

Why strip_suffix specifically:

  • It removes exactly one trailing occurrence, which is the single .git
    clone suffix GitHub appends. str::replace(".git", "") would delete the
    .git inside .github and recreate the bug.
  • It is a no-op for URLs that already parsed correctly, so nothing regresses.
  • It is safe because GitHub does not allow a repository name to end in .git,
    so a trailing .git is always the clone suffix.
  • SSH and HTTPS remotes are handled identically, since the strip runs on the raw
    string before parsing.

The same (owner, repo) feeds the web base URL and the commit, PR and compare
links, so those are repaired too.

🎫 Affected issues

Fixes: #15302

✅ Verification

Run in crates/but-forge:

  • cargo test -p but-forge with the fix: 104 passed, 0 failed.
  • Same run with only the fix line removed and the new test kept:
    103 passed, 1 failed, failing exactly on
    repo for git@github.com:org/org.github.io.git (left: "org",
    right: "org.github.io"). This confirms the test detects the real defect and
    the one line is what fixes it.
  • cargo clippy -p but-forge --all-targets: 0 warnings.
  • cargo fmt -- --check: clean, no diff.

The new test repo_name_containing_dotgit_is_parsed_correctly is table-driven
over nine URL cases: SSH and HTTPS, each plain and with a trailing .git, plus
the *.github.io family that carries .git as a substring, with and without
the trailing suffix.


AI assistance (Claude, Anthropic) was used in developing this change. The
diagnosis, design, review and verification were done by the author. Verified
locally before submitting: cargo test -p but-forge (104 passed, plus the new
test fails 1/104 with the fix reverted), cargo clippy -p but-forge --all-targets (0 warnings) and cargo fmt -- --check (clean).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PR creation fails for repository names containing .git

1 participant