Files
orca/src/main/git/hosted-remote-url.test.ts
T
Trevin ChowandJinjing a325ac467c feat: expandable commits and actions in the git history panel (#5419)
* feat: expandable commits and actions in the git history panel

Expand a commit row in the Commits panel to see its changed files inline; click a file to open that file's commit diff. Author and date surface on expand, so the dense row itself stays subject-only.

Right-click a commit for: open in the in-app browser, copy hash, copy message, and explain changes (spawns the default agent seeded with the commit context).

Open-in-browser resolves the provider commit URL in the main process via a new remoteCommitUrl resolver (GitHub/GitLab/Bitbucket), mirroring the existing remoteFileUrl chain end-to-end (repo, IPC, SSH provider, runtime RPC, preload) so it works for local and SSH/remote workspaces.

Layout: subject-first single-line rows with a tighter graph, refs moved inline, and local/remote ref pills deduped when they point at the same commit.

* fix: address git history review feedback

* fix: address PR review feedback on the git history panel

- Trim commit SHA before building the remote URL so whitespace input returns null instead of an invalid %20 URL.
- Gate commit-row expansion on the file loader (onLoadCommitFiles) so a row can't expand into a perpetual loading state.
- Harden the explain prompt: treat the commit subject and diff as untrusted data and run git show --no-ext-diff.
- Keep ambiguous multi-segment remote refs instead of mis-deduping them against a local branch.
- Use standard 10-char i18n keys for the new commit-history strings and translate them into es/ja/ko/zh.

* refactor: extract commit-history actions into useGitHistoryCommitActions hook

Moves the commit load/open/context-menu action callbacks (and the per-commit compare cache) out of SourceControl.tsx — which already carries a max-lines disable — into a focused hook, addressing the PR review nitpick. Behavior is unchanged.

* Refine git history row rendering, ref deduplication, and OID validation

- Prevent deduplication of remote branch badges in the history view
  when multiple remotes exist or when a ref is explicitly preserved.
- Render GitHistoryRow as an accessible button with dynamic ARIA labels
  for expansion states.
- Add double-click handler on commit files to open them permanently.
- Validate commit SHAs as full 40-character Git object IDs before
  requesting remote commit URLs.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-06-15 19:28:45 -07:00

108 lines
3.7 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
buildHostedRemoteCommitUrl,
buildHostedRemoteFileUrl,
parseHostedRemote
} from './hosted-remote-url'
describe('hosted remote URLs', () => {
it('parses common GitHub remote formats', () => {
expect(parseHostedRemote('https://github.com/Org/Repo.git')).toEqual({
host: 'github.com',
path: 'Org/Repo',
provider: 'github'
})
expect(parseHostedRemote('git@github.com:Org/Repo.git')).toEqual({
host: 'github.com',
path: 'Org/Repo',
provider: 'github'
})
expect(parseHostedRemote('ssh://git@github.com/Org/Repo.git')).toEqual({
host: 'github.com',
path: 'Org/Repo',
provider: 'github'
})
expect(parseHostedRemote('ssh://git@ssh.github.com:443/Org/Repo.git')).toEqual({
host: 'github.com',
path: 'Org/Repo',
provider: 'github'
})
expect(parseHostedRemote('github:Org/Repo')).toEqual({
host: 'github.com',
path: 'Org/Repo',
provider: 'github'
})
})
it('parses nested GitLab and Bitbucket remotes', () => {
expect(parseHostedRemote('git@gitlab.com:group/sub/repo.git')).toEqual({
host: 'gitlab.com',
path: 'group/sub/repo',
provider: 'gitlab'
})
expect(parseHostedRemote('https://bitbucket.org/team/repo.git')).toEqual({
host: 'bitbucket.org',
path: 'team/repo',
provider: 'bitbucket'
})
})
it('builds file URLs with encoded branches and paths', () => {
expect(
buildHostedRemoteFileUrl('git@github.com:Org/Repo.git', 'src/a file.ts', 'feature/x', 42)
).toBe('https://github.com/Org/Repo/blob/feature%2Fx/src/a%20file.ts#L42')
expect(
buildHostedRemoteFileUrl('git@gitlab.com:group/sub/repo.git', 'src/a.ts', 'feature/x', 9)
).toBe('https://gitlab.com/group/sub/repo/-/blob/feature%2Fx/src/a.ts#L9')
expect(
buildHostedRemoteFileUrl('git@bitbucket.org:team/repo.git', 'src/a.ts', 'feature/x', 7)
).toBe('https://bitbucket.org/team/repo/src/feature%2Fx/src/a.ts#a.ts-7')
expect(
buildHostedRemoteFileUrl(
'ssh://git@ssh.github.com:443/Org/Repo.git',
'src/a.ts',
'feature/x',
5
)
).toBe('https://github.com/Org/Repo/blob/feature%2Fx/src/a.ts#L5')
})
it('builds Bitbucket line fragments from the target file name', () => {
expect(
buildHostedRemoteFileUrl('https://bitbucket.org/team/repo.git', 'src/a file.ts', 'main', 29)
).toBe('https://bitbucket.org/team/repo/src/main/src/a%20file.ts#a%20file.ts-29')
})
it('builds commit URLs per provider from ssh and https remotes', () => {
const sha = '0123456789abcdef0123456789abcdef01234567'
expect(buildHostedRemoteCommitUrl('git@github.com:Org/Repo.git', sha)).toBe(
`https://github.com/Org/Repo/commit/${sha}`
)
expect(buildHostedRemoteCommitUrl('https://gitlab.com/group/sub/repo.git', sha)).toBe(
`https://gitlab.com/group/sub/repo/-/commit/${sha}`
)
expect(buildHostedRemoteCommitUrl('git@bitbucket.org:team/repo.git', sha)).toBe(
`https://bitbucket.org/team/repo/commits/${sha}`
)
})
it('returns null for unsupported commit remotes or missing sha', () => {
expect(
buildHostedRemoteCommitUrl(
'git@example.com:team/repo.git',
'0123456789abcdef0123456789abcdef01234567'
)
).toBeNull()
expect(buildHostedRemoteCommitUrl('git@github.com:Org/Repo.git', '')).toBeNull()
})
it('rejects unsupported hosts and incomplete repo paths', () => {
expect(parseHostedRemote('git@example.com:team/repo.git')).toBeNull()
expect(parseHostedRemote('git@github.com:repo.git')).toBeNull()
expect(parseHostedRemote('ftp://github.com/Org/Repo.git')).toBeNull()
})
})