Skip to content

Commit dc44e7a

Browse files
Git Repo URL telemetry link fix: only Https (RooCodeInc#5202)
* https parsing in URL sanatizing for git repo * fix: remove duplicate JSDoc and add SSH to HTTPS conversion test - Remove duplicate JSDoc comment from convertGitUrlToHttps function - Add test case to verify getGitRepositoryInfo converts SSH URLs to HTTPS format --------- Co-authored-by: Daniel Riccio <[email protected]>
1 parent 8bb7ef0 commit dc44e7a

File tree

2 files changed

+124
-1
lines changed

2 files changed

+124
-1
lines changed

src/utils/__tests__/git.spec.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
extractRepositoryName,
1313
getWorkspaceGitInfo,
1414
GitRepositoryInfo,
15+
convertGitUrlToHttps,
1516
} from "../git"
1617
import { truncateOutput } from "../../integrations/misc/extract-text"
1718

@@ -560,6 +561,89 @@ describe("getGitRepositoryInfo", () => {
560561
repositoryName: "RooCodeInc/Roo-Code",
561562
})
562563
})
564+
565+
it("should convert SSH URLs to HTTPS format", async () => {
566+
// Clear previous mocks
567+
vitest.clearAllMocks()
568+
569+
// Create a spy to track the implementation
570+
const gitSpy = vitest.spyOn(fs.promises, "readFile")
571+
572+
// Mock successful access to .git directory
573+
vitest.mocked(fs.promises.access).mockResolvedValue(undefined)
574+
575+
// Mock git config file with SSH URL
576+
const mockConfig = `
577+
[core]
578+
repositoryformatversion = 0
579+
filemode = true
580+
bare = false
581+
[remote "origin"]
582+
url = [email protected]:RooCodeInc/Roo-Code.git
583+
fetch = +refs/heads/*:refs/remotes/origin/*
584+
[branch "main"]
585+
remote = origin
586+
merge = refs/heads/main
587+
`
588+
// Mock HEAD file content
589+
const mockHead = "ref: refs/heads/main"
590+
591+
// Setup the readFile mock to return different values based on the path
592+
gitSpy.mockImplementation((path: any, encoding: any) => {
593+
if (path === configPath) {
594+
return Promise.resolve(mockConfig)
595+
} else if (path === headPath) {
596+
return Promise.resolve(mockHead)
597+
}
598+
return Promise.reject(new Error(`Unexpected path: ${path}`))
599+
})
600+
601+
const result = await getGitRepositoryInfo(workspaceRoot)
602+
603+
// Verify that the SSH URL was converted to HTTPS
604+
expect(result).toEqual({
605+
repositoryUrl: "https://github.com/RooCodeInc/Roo-Code.git",
606+
repositoryName: "RooCodeInc/Roo-Code",
607+
defaultBranch: "main",
608+
})
609+
})
610+
})
611+
612+
describe("convertGitUrlToHttps", () => {
613+
it("should leave HTTPS URLs unchanged", () => {
614+
const url = "https://github.com/RooCodeInc/Roo-Code.git"
615+
const converted = convertGitUrlToHttps(url)
616+
617+
expect(converted).toBe("https://github.com/RooCodeInc/Roo-Code.git")
618+
})
619+
620+
it("should convert SSH URLs to HTTPS format", () => {
621+
const url = "[email protected]:RooCodeInc/Roo-Code.git"
622+
const converted = convertGitUrlToHttps(url)
623+
624+
expect(converted).toBe("https://github.com/RooCodeInc/Roo-Code.git")
625+
})
626+
627+
it("should convert SSH URLs with ssh:// prefix to HTTPS format", () => {
628+
const url = "ssh://[email protected]/RooCodeInc/Roo-Code.git"
629+
const converted = convertGitUrlToHttps(url)
630+
631+
expect(converted).toBe("https://github.com/RooCodeInc/Roo-Code.git")
632+
})
633+
634+
it("should handle URLs without git@ prefix", () => {
635+
const url = "ssh://github.com/RooCodeInc/Roo-Code.git"
636+
const converted = convertGitUrlToHttps(url)
637+
638+
expect(converted).toBe("https://github.com/RooCodeInc/Roo-Code.git")
639+
})
640+
641+
it("should handle invalid URLs gracefully", () => {
642+
const url = "not-a-valid-url"
643+
const converted = convertGitUrlToHttps(url)
644+
645+
expect(converted).toBe("not-a-valid-url")
646+
})
563647
})
564648

565649
describe("sanitizeGitUrl", () => {

src/utils/git.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export async function getGitRepositoryInfo(workspaceRoot: string): Promise<GitRe
5151

5252
if (urlMatch && urlMatch[1]) {
5353
const url = urlMatch[1].trim()
54-
gitInfo.repositoryUrl = sanitizeGitUrl(url)
54+
// Sanitize the URL and convert to HTTPS format for telemetry
55+
gitInfo.repositoryUrl = convertGitUrlToHttps(sanitizeGitUrl(url))
5556
const repositoryName = extractRepositoryName(url)
5657
if (repositoryName) {
5758
gitInfo.repositoryName = repositoryName
@@ -88,6 +89,44 @@ export async function getGitRepositoryInfo(workspaceRoot: string): Promise<GitRe
8889
}
8990
}
9091

92+
/**
93+
* Converts a git URL to HTTPS format
94+
* @param url The git URL to convert
95+
* @returns The URL in HTTPS format, or the original URL if conversion is not possible
96+
*/
97+
export function convertGitUrlToHttps(url: string): string {
98+
try {
99+
// Already HTTPS, just return it
100+
if (url.startsWith("https://")) {
101+
return url
102+
}
103+
104+
// Handle SSH format: git@github.com:user/repo.git -> https://github.com/user/repo.git
105+
if (url.startsWith("git@")) {
106+
const match = url.match(/git@([^:]+):(.+)/)
107+
if (match && match.length === 3) {
108+
const [, host, path] = match
109+
return `https://${host}/${path}`
110+
}
111+
}
112+
113+
// Handle SSH with protocol: ssh://git@github.com/user/repo.git -> https://github.com/user/repo.git
114+
if (url.startsWith("ssh://")) {
115+
const match = url.match(/ssh:\/\/(?:git@)?([^\/]+)\/(.+)/)
116+
if (match && match.length === 3) {
117+
const [, host, path] = match
118+
return `https://${host}/${path}`
119+
}
120+
}
121+
122+
// Return original URL if we can't convert it
123+
return url
124+
} catch {
125+
// If parsing fails, return original
126+
return url
127+
}
128+
}
129+
91130
/**
92131
* Sanitizes a git URL to remove sensitive information like tokens
93132
* @param url The original git URL

0 commit comments

Comments
 (0)