Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/utils/__tests__/git.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
extractRepositoryName,
getWorkspaceGitInfo,
GitRepositoryInfo,
convertGitUrlToHttps,
} from "../git"
import { truncateOutput } from "../../integrations/misc/extract-text"

Expand Down Expand Up @@ -560,6 +561,89 @@ describe("getGitRepositoryInfo", () => {
repositoryName: "RooCodeInc/Roo-Code",
})
})

it("should convert SSH URLs to HTTPS format", async () => {
// Clear previous mocks
vitest.clearAllMocks()

// Create a spy to track the implementation
const gitSpy = vitest.spyOn(fs.promises, "readFile")

// Mock successful access to .git directory
vitest.mocked(fs.promises.access).mockResolvedValue(undefined)

// Mock git config file with SSH URL
const mockConfig = `
[core]
repositoryformatversion = 0
filemode = true
bare = false
[remote "origin"]
url = [email protected]:RooCodeInc/Roo-Code.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main
`
// Mock HEAD file content
const mockHead = "ref: refs/heads/main"

// Setup the readFile mock to return different values based on the path
gitSpy.mockImplementation((path: any, encoding: any) => {
if (path === configPath) {
return Promise.resolve(mockConfig)
} else if (path === headPath) {
return Promise.resolve(mockHead)
}
return Promise.reject(new Error(`Unexpected path: ${path}`))
})

const result = await getGitRepositoryInfo(workspaceRoot)

// Verify that the SSH URL was converted to HTTPS
expect(result).toEqual({
repositoryUrl: "https://github.com/RooCodeInc/Roo-Code.git",
repositoryName: "RooCodeInc/Roo-Code",
defaultBranch: "main",
})
})
})

describe("convertGitUrlToHttps", () => {
it("should leave HTTPS URLs unchanged", () => {
const url = "https://github.com/RooCodeInc/Roo-Code.git"
const converted = convertGitUrlToHttps(url)

expect(converted).toBe("https://github.com/RooCodeInc/Roo-Code.git")
})

it("should convert SSH URLs to HTTPS format", () => {
const url = "[email protected]:RooCodeInc/Roo-Code.git"
const converted = convertGitUrlToHttps(url)

expect(converted).toBe("https://github.com/RooCodeInc/Roo-Code.git")
})

it("should convert SSH URLs with ssh:// prefix to HTTPS format", () => {
const url = "ssh://[email protected]/RooCodeInc/Roo-Code.git"
const converted = convertGitUrlToHttps(url)

expect(converted).toBe("https://github.com/RooCodeInc/Roo-Code.git")
})

it("should handle URLs without git@ prefix", () => {
const url = "ssh://github.com/RooCodeInc/Roo-Code.git"
const converted = convertGitUrlToHttps(url)

expect(converted).toBe("https://github.com/RooCodeInc/Roo-Code.git")
})

it("should handle invalid URLs gracefully", () => {
const url = "not-a-valid-url"
const converted = convertGitUrlToHttps(url)

expect(converted).toBe("not-a-valid-url")
})
})

describe("sanitizeGitUrl", () => {
Expand Down
41 changes: 40 additions & 1 deletion src/utils/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export async function getGitRepositoryInfo(workspaceRoot: string): Promise<GitRe

if (urlMatch && urlMatch[1]) {
const url = urlMatch[1].trim()
gitInfo.repositoryUrl = sanitizeGitUrl(url)
// Sanitize the URL and convert to HTTPS format for telemetry
gitInfo.repositoryUrl = convertGitUrlToHttps(sanitizeGitUrl(url))
const repositoryName = extractRepositoryName(url)
if (repositoryName) {
gitInfo.repositoryName = repositoryName
Expand Down Expand Up @@ -88,6 +89,44 @@ export async function getGitRepositoryInfo(workspaceRoot: string): Promise<GitRe
}
}

/**
* Converts a git URL to HTTPS format
* @param url The git URL to convert
* @returns The URL in HTTPS format, or the original URL if conversion is not possible
*/
export function convertGitUrlToHttps(url: string): string {
try {
// Already HTTPS, just return it
if (url.startsWith("https://")) {
return url
}

// Handle SSH format: [email protected]:user/repo.git -> https://github.com/user/repo.git
if (url.startsWith("git@")) {
const match = url.match(/git@([^:]+):(.+)/)
if (match && match.length === 3) {
const [, host, path] = match
return `https://${host}/${path}`
}
}

// Handle SSH with protocol: ssh://[email protected]/user/repo.git -> https://github.com/user/repo.git
if (url.startsWith("ssh://")) {
const match = url.match(/ssh:\/\/(?:git@)?([^\/]+)\/(.+)/)
if (match && match.length === 3) {
const [, host, path] = match
return `https://${host}/${path}`
}
}

// Return original URL if we can't convert it
return url
} catch {
// If parsing fails, return original
return url
}
}

/**
* Sanitizes a git URL to remove sensitive information like tokens
* @param url The original git URL
Expand Down
Loading