Skip to content

Commit 4a09d10

Browse files
committed
Use git repo information for default branch name and repo name
1 parent 882300f commit 4a09d10

File tree

6 files changed

+115
-51
lines changed

6 files changed

+115
-51
lines changed

src/commands/ci/handle-ci.mts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { logger } from '@socketsecurity/registry/lib/logger'
22

33
import { getDefaultOrgSlug } from './fetch-default-org-slug.mts'
4+
import { getRepoName, gitBranch } from '../../utils/git.mts'
45
import { serializeResultJson } from '../../utils/serialize-result-json.mts'
56
import { handleCreateNewScan } from '../scan/handle-create-new-scan.mts'
67

@@ -17,10 +18,12 @@ export async function handleCI(autoManifest: boolean): Promise<void> {
1718
return
1819
}
1920

21+
const cwd = process.cwd()
22+
2023
// TODO: does it makes sense to use custom branch/repo names here? probably socket.yml, right
2124
await handleCreateNewScan({
2225
autoManifest,
23-
branchName: 'socket-default-branch',
26+
branchName: (await gitBranch(cwd)) || 'socket-default-branch',
2427
commitMessage: '',
2528
commitHash: '',
2629
committers: '',
@@ -29,12 +32,14 @@ export async function handleCI(autoManifest: boolean): Promise<void> {
2932
interactive: false,
3033
orgSlug: result.data,
3134
outputKind: 'json',
32-
pendingHead: true, // when true, requires branch name set, tmp false
35+
// When 'pendingHead' is true, it requires 'branchName' set and 'tmp' false.
36+
pendingHead: true,
3337
pullRequest: 0,
34-
repoName: 'socket-default-repository',
38+
repoName: (await getRepoName(cwd)) || 'socket-default-repository',
3539
readOnly: false,
3640
report: true,
3741
targets: ['.'],
38-
tmp: false, // don't set when pendingHead is true
42+
// Don't set 'tmp' when 'pendingHead' is true.
43+
tmp: false,
3944
})
4045
}

src/commands/fix/fix-env-helpers.mts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { debugFn } from '@socketsecurity/registry/lib/debug'
22

33
import { getSocketPrs } from './pull-request.mts'
44
import constants from '../../constants.mts'
5-
import { getBaseGitBranch, gitRepoInfo } from '../../utils/git.mts'
5+
import { getBaseBranch, getRepoInfo } from '../../utils/git.mts'
66

77
import type { PrMatch } from './pull-request.mts'
88
import type { RepoInfo } from '../../utils/git.mts'
@@ -35,7 +35,7 @@ export interface FixEnv {
3535
}
3636

3737
export async function getFixEnv(): Promise<FixEnv> {
38-
const baseBranch = await getBaseGitBranch()
38+
const baseBranch = await getBaseBranch()
3939
const gitEmail = constants.ENV.SOCKET_CLI_GIT_USER_EMAIL
4040
const gitUser = constants.ENV.SOCKET_CLI_GIT_USER_NAME
4141
const githubToken = constants.ENV.SOCKET_CLI_GITHUB_TOKEN
@@ -48,7 +48,7 @@ export async function getFixEnv(): Promise<FixEnv> {
4848
if (isCi) {
4949
debugFn('notice', 'falling back to `git remote get-url origin`')
5050
}
51-
repoInfo = await gitRepoInfo()
51+
repoInfo = await getRepoInfo()
5252
}
5353
const prs =
5454
isCi && repoInfo

src/commands/scan/cmd-scan-create.mts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { commonFlags, outputFlags } from '../../flags.mts'
1111
import { checkCommandInput } from '../../utils/check-input.mts'
1212
import { determineOrgSlug } from '../../utils/determine-org-slug.mts'
1313
import { getOutputKind } from '../../utils/get-output-kind.mts'
14+
import { getRepoName, gitBranch } from '../../utils/git.mts'
1415
import { meowOrExit } from '../../utils/meow-with-subcommands.mts'
1516
import { getFlagListOutput } from '../../utils/output-formatting.mts'
1617
import { hasDefaultToken } from '../../utils/sdk.mts'
@@ -255,15 +256,15 @@ async function run(
255256
branchName = sockJson.defaults.scan.create.branch
256257
logger.info('Using default --branch from socket.json:', branchName)
257258
} else {
258-
branchName = 'socket-default-branch'
259+
branchName = (await gitBranch(cwd)) || 'socket-default-branch'
259260
}
260261
}
261262
if (!repoName) {
262263
if (sockJson.defaults?.scan?.create?.repo) {
263264
repoName = sockJson.defaults.scan.create.repo
264265
logger.info('Using default --repo from socket.json:', repoName)
265266
} else {
266-
repoName = 'socket-default-repository'
267+
repoName = (await getRepoName(cwd)) || 'socket-default-repository'
267268
}
268269
}
269270
if (typeof report !== 'boolean') {

src/commands/scan/fetch-create-org-full-scan.mts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { handleApiCall } from '../../utils/api.mts'
2+
import { getRepoName } from '../../utils/git.mts'
23
import { setupSdk } from '../../utils/sdk.mts'
34

45
import type { CResult } from '../../types.mts'
@@ -43,7 +44,9 @@ export async function fetchCreateOrgFullScan(
4344
...(committers ? { committers } : {}),
4445
make_default_branch: String(defaultBranch),
4546
...(pullRequest ? { pull_request: String(pullRequest) } : {}),
46-
repo: repoName || 'socket-default-repository', // mandatory, this is server default for repo
47+
// The repo is mandatory, this is server default for repo.
48+
repo:
49+
repoName || (await getRepoName(cwd)) || 'socket-default-repository',
4750
set_as_pending_head: String(pendingHead),
4851
tmp: String(tmp),
4952
},

src/commands/scan/setup-scan-config.mts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'
55
import { input, select } from '@socketsecurity/registry/lib/prompts'
66

77
import constants from '../../constants.mts'
8+
import { getRepoName, gitBranch } from '../../utils/git.mts'
89
import {
910
type SocketJson,
1011
readSocketJson,
@@ -77,7 +78,7 @@ export async function setupScanConfig(
7778
if (!sockJson.defaults.scan.create) {
7879
sockJson.defaults.scan.create = {}
7980
}
80-
const result = await configureScan(sockJson.defaults.scan.create)
81+
const result = await configureScan(sockJson.defaults.scan.create, cwd)
8182
if (!result.ok || result.data.canceled) {
8283
return result
8384
}
@@ -129,11 +130,13 @@ async function configureScan(
129130
config: NonNullable<
130131
NonNullable<NonNullable<SocketJson['defaults']>['scan']>['create']
131132
>,
133+
cwd = process.cwd(),
132134
): Promise<CResult<{ canceled: boolean }>> {
133135
const defaultRepoName = await input({
134136
message:
135137
'(--repo) What repo name (slug) should be reported to Socket for this dir?',
136-
default: config.repo || 'socket-default-repository',
138+
default:
139+
config.repo || (await getRepoName(cwd)) || 'socket-default-repository',
137140
required: false,
138141
// validate: async string => bool
139142
})
@@ -151,7 +154,7 @@ async function configureScan(
151154
const defaultBranchName = await input({
152155
message:
153156
'(--branch) What branch name (slug) should be reported to Socket for this dir?',
154-
default: config.branch || 'socket-default-branch',
157+
default: config.branch || (await gitBranch(cwd)) || 'socket-default-branch',
155158
required: false,
156159
// validate: async string => bool
157160
})

src/utils/git.mts

Lines changed: 90 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import constants from '../constants.mts'
77
import type { CResult } from '../types.mts'
88
import type { SpawnOptions } from '@socketsecurity/registry/lib/spawn'
99

10-
export async function getBaseGitBranch(cwd = process.cwd()): Promise<string> {
10+
export async function getBaseBranch(cwd = process.cwd()): Promise<string> {
1111
// Lazily access constants.ENV properties.
1212
const { GITHUB_BASE_REF, GITHUB_REF_NAME, GITHUB_REF_TYPE } = constants.ENV
1313
// 1. In a pull request, this is always the base branch.
@@ -30,11 +30,67 @@ export async function getBaseGitBranch(cwd = process.cwd()): Promise<string> {
3030
return match[0].trim()
3131
}
3232
} catch {}
33-
// GitHub defaults to branch name "main"
33+
// GitHub and GitLab default to branch name "main"
3434
// https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches#about-the-default-branch
3535
return 'main'
3636
}
3737

38+
export type RepoInfo = {
39+
owner: string
40+
repo: string
41+
}
42+
43+
export async function getRepoInfo(
44+
cwd = process.cwd(),
45+
): Promise<RepoInfo | null> {
46+
let info = null
47+
try {
48+
const remoteUrl = (
49+
await spawn('git', ['remote', 'get-url', 'origin'], { cwd })
50+
).stdout
51+
info = parseGitRemoteUrl(remoteUrl)
52+
if (!info) {
53+
debugFn('error', 'git: unmatched git remote URL format')
54+
debugDir('inspect', { remoteUrl })
55+
}
56+
} catch (e) {
57+
debugFn('error', 'caught: `git remote get-url origin` failed')
58+
debugDir('inspect', { error: e })
59+
}
60+
return info
61+
}
62+
63+
export async function getRepoName(cwd = process.cwd()): Promise<string | null> {
64+
const repoInfo = await getRepoInfo(cwd)
65+
return repoInfo?.repo ?? null
66+
}
67+
68+
export async function getRepoOwner(
69+
cwd = process.cwd(),
70+
): Promise<string | null> {
71+
const repoInfo = await getRepoInfo(cwd)
72+
return repoInfo?.owner ?? null
73+
}
74+
75+
export async function gitBranch(cwd = process.cwd()): Promise<string | null> {
76+
const stdioPipeOptions: SpawnOptions = { cwd }
77+
// Try symbolic-ref first which returns the branch name or fails in a
78+
// detached HEAD state.
79+
try {
80+
return (
81+
await spawn('git', ['symbolic-ref', '--short', 'HEAD'], stdioPipeOptions)
82+
).stdout
83+
} catch {}
84+
// Fallback to using rev-parse to get the short commit hash in a
85+
// detached HEAD state.
86+
try {
87+
return (
88+
await spawn('git', ['rev-parse', '--short', 'HEAD'], stdioPipeOptions)
89+
).stdout
90+
} catch {}
91+
return null
92+
}
93+
3894
export type GitCreateAndPushBranchOptions = {
3995
cwd?: string | undefined
4096
email?: string | undefined
@@ -227,42 +283,6 @@ export async function gitRemoteBranchExists(
227283
return false
228284
}
229285

230-
export type RepoInfo = {
231-
owner: string
232-
repo: string
233-
}
234-
235-
export async function gitRepoInfo(
236-
cwd = process.cwd(),
237-
): Promise<RepoInfo | null> {
238-
try {
239-
const remoteUrl = (
240-
await spawn('git', ['remote', 'get-url', 'origin'], { cwd })
241-
).stdout
242-
// 1. Handle SSH-style, e.g. git@github.com:owner/repo.git
243-
const sshMatch = /^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/.exec(remoteUrl)
244-
if (sshMatch) {
245-
return { owner: sshMatch[1]!, repo: sshMatch[2]! }
246-
}
247-
// 2. Handle HTTPS/URL-style, e.g. https://github.com/owner/repo.git
248-
try {
249-
const parsed = new URL(remoteUrl)
250-
const segments = parsed.pathname.split('/')
251-
const owner = segments.at(-2)
252-
const repo = segments.at(-1)?.replace(/\.git$/, '')
253-
if (owner && repo) {
254-
return { owner, repo }
255-
}
256-
} catch {}
257-
debugFn('error', 'git: unmatched git remote URL format')
258-
debugDir('inspect', { remoteUrl })
259-
} catch (e) {
260-
debugFn('error', 'caught: `git remote get-url origin` failed')
261-
debugDir('inspect', { error: e })
262-
}
263-
return null
264-
}
265-
266286
export async function gitResetAndClean(
267287
branch = 'HEAD',
268288
cwd = process.cwd(),
@@ -307,3 +327,35 @@ export async function gitUnstagedModifiedFiles(
307327
}
308328
}
309329
}
330+
331+
const parsedGitRemoteUrlCache = new Map<string, RepoInfo | null>()
332+
333+
export function parseGitRemoteUrl(remoteUrl: string): RepoInfo | null {
334+
let result = parsedGitRemoteUrlCache.get(remoteUrl) ?? null
335+
if (result) {
336+
return { ...result }
337+
}
338+
// Handle SSH-style
339+
const sshMatch = /^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/.exec(remoteUrl)
340+
// 1. Handle SSH-style, e.g. git@github.com:owner/repo.git
341+
if (sshMatch) {
342+
result = { owner: sshMatch[1]!, repo: sshMatch[2]! }
343+
}
344+
if (result) {
345+
// 2. Handle HTTPS/URL-style, e.g. https://github.com/owner/repo.git
346+
try {
347+
const parsed = new URL(remoteUrl)
348+
// Remove leading slashes from pathname and split by "/" to extract segments.
349+
const segments = parsed.pathname.replace(/^\/+/, '').split('/')
350+
// The second-to-last segment is expected to be the owner (e.g., "owner" in /owner/repo.git).
351+
const owner = segments.at(-2)
352+
// The last segment is expected to be the repo name, so we remove the ".git" suffix if present.
353+
const repo = segments.at(-1)?.replace(/\.git$/, '')
354+
if (owner && repo) {
355+
result = { owner, repo }
356+
}
357+
} catch {}
358+
}
359+
parsedGitRemoteUrlCache.set(remoteUrl, result)
360+
return { ...result } as RepoInfo | null
361+
}

0 commit comments

Comments
 (0)