From 08ad89f33a8351b30c4837170d7cd172b7dfb1c7 Mon Sep 17 00:00:00 2001 From: Happilys Date: Tue, 24 Feb 2026 17:48:28 +0800 Subject: [PATCH 01/13] fix(plugin-git): resolve git log file paths against repository root - add helper to detect git repository root via `git rev-parse --show-toplevel` - normalize/resolve target file path to repo-relative before calling `git log` - run `git log` from repository root fallback to current cwd w --- .../plugin-git/src/node/utils/getCommits.ts | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index f8523d8465..ca2033eb2f 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -2,6 +2,7 @@ import { spawn } from 'node:child_process' import type { GitContributorInfo } from '../../shared/index.js' import type { GitPluginOptions } from '../options.js' import type { MergedRawCommit, RawCommit } from '../typings.js' +import { path } from 'vuepress/utils' import { logger } from './logger.js' const INFO_SPLITTER = '[|]' @@ -77,6 +78,52 @@ const runGitLog = (args: string[], cwd: string): Promise => }) }) +/** + * Get git repository root directory for a given file path. + * + * This function runs `git rev-parse --show-toplevel` in the directory of the + * target file to determine the top-level directory of the git repository. + * + * @param filePath - File path (relative or absolute) whose repository root is requested + * @param cwd - Current working directory + * @returns Promise that resolves to normalized git root path, or null if not in a git repository + */ +const getGitRepoRoot = (filePath: string, cwd: string): Promise => + new Promise((resolve) => { + const dir = path.dirname(path.join(cwd, filePath)) + const gitProcess = spawn('git', ['rev-parse', '--show-toplevel'], { + cwd: dir, + stdio: ['ignore', 'pipe', 'pipe'], + }) + + let stdoutData = '' + let stderrData = '' + + gitProcess.stdout.on('data', (chunk: Buffer) => { + stdoutData += chunk.toString('utf-8') + }) + + gitProcess.stderr.on('data', (chunk: Buffer) => { + stderrData += chunk.toString('utf-8') + }) + + gitProcess.on('error', (error) => { + logger.error(`Failed to spawn git rev-parse: ${error.message}`) + resolve('') + }) + + gitProcess.on('close', (code) => { + if (code === 0) { + resolve(path.normalize(stdoutData.trim())) + } else { + logger.error( + `git rev-parse failed (code=${code}): ${stderrData.trim()}`, + ) + resolve('') + } + }) + }) + /** * Get raw commits for a specific file * @@ -96,8 +143,21 @@ export const getRawCommits = async ( options: GitPluginOptions, ): Promise => { const format = getGitLogFormat(options) + const gitRoot = await getGitRepoRoot(filepath, cwd) try { + let _filepath = filepath + if (gitRoot) { + // Resolve to absolute path first, then convert to repo-relative path + const absFilePath = path.isAbsolute(_filepath) + ? _filepath + : path.resolve(cwd, _filepath) + + _filepath = path.relative(gitRoot, absFilePath) + } else { + logger.warn('Get git repo root error!') + } + const stdout = await runGitLog( [ '--max-count=-1', @@ -105,9 +165,9 @@ export const getRawCommits = async ( '--date=unix', '--follow', '--', - filepath, + _filepath, ], - cwd, + gitRoot || cwd, ) return stdout From ee60c9e606d60117c188c45b5d91b0a4e4d744d9 Mon Sep 17 00:00:00 2001 From: Happilys Date: Mon, 2 Mar 2026 15:47:42 +0800 Subject: [PATCH 02/13] fix(plugin-git): stabilize getGitRepoRoot path resolution --- .../plugin-git/src/node/utils/getCommits.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index ca2033eb2f..dd2dd62967 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -88,9 +88,15 @@ const runGitLog = (args: string[], cwd: string): Promise => * @param cwd - Current working directory * @returns Promise that resolves to normalized git root path, or null if not in a git repository */ -const getGitRepoRoot = (filePath: string, cwd: string): Promise => +const getGitRepoRoot = ( + filePath: string, + cwd: string, +): Promise => new Promise((resolve) => { - const dir = path.dirname(path.join(cwd, filePath)) + const absFilePath = path.isAbsolute(filePath) + ? filePath + : path.resolve(cwd, filePath) + const dir = path.dirname(absFilePath) const gitProcess = spawn('git', ['rev-parse', '--show-toplevel'], { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'], @@ -109,7 +115,7 @@ const getGitRepoRoot = (filePath: string, cwd: string): Promise => gitProcess.on('error', (error) => { logger.error(`Failed to spawn git rev-parse: ${error.message}`) - resolve('') + resolve(null) }) gitProcess.on('close', (code) => { @@ -119,7 +125,7 @@ const getGitRepoRoot = (filePath: string, cwd: string): Promise => logger.error( `git rev-parse failed (code=${code}): ${stderrData.trim()}`, ) - resolve('') + resolve(null) } }) }) From eeafcb87416d4882880c58df63f6dc6903634d89 Mon Sep 17 00:00:00 2001 From: Happilys Date: Mon, 2 Mar 2026 17:48:09 +0800 Subject: [PATCH 03/13] perf(plugin-git): improve fallback warning --- plugins/development/plugin-git/src/node/utils/getCommits.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index dd2dd62967..aa95e68069 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -161,7 +161,9 @@ export const getRawCommits = async ( _filepath = path.relative(gitRoot, absFilePath) } else { - logger.warn('Get git repo root error!') + logger.warn( + `Failed to resolve git repo root for "${filepath}" under cwd "${cwd}", falling back to cwd; git history may be incomplete.`, + ) } const stdout = await runGitLog( From aaa7746f041d6da3331a693d6954393b6cf9d1a0 Mon Sep 17 00:00:00 2001 From: Happilys Date: Mon, 2 Mar 2026 18:05:31 +0800 Subject: [PATCH 04/13] refactor(plugin-git): refining the variable name --- .../plugin-git/src/node/utils/getCommits.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index aa95e68069..61c3db92c2 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -152,14 +152,14 @@ export const getRawCommits = async ( const gitRoot = await getGitRepoRoot(filepath, cwd) try { - let _filepath = filepath + let repoRelativeFilePath = filepath if (gitRoot) { // Resolve to absolute path first, then convert to repo-relative path - const absFilePath = path.isAbsolute(_filepath) - ? _filepath - : path.resolve(cwd, _filepath) + const absFilePath = path.isAbsolute(repoRelativeFilePath) + ? repoRelativeFilePath + : path.resolve(cwd, repoRelativeFilePath) - _filepath = path.relative(gitRoot, absFilePath) + repoRelativeFilePath = path.relative(gitRoot, absFilePath) } else { logger.warn( `Failed to resolve git repo root for "${filepath}" under cwd "${cwd}", falling back to cwd; git history may be incomplete.`, @@ -173,7 +173,7 @@ export const getRawCommits = async ( '--date=unix', '--follow', '--', - _filepath, + repoRelativeFilePath, ], gitRoot || cwd, ) From bc24cb1b694f5ea03c746f7a37a1d5a0b7b13ba0 Mon Sep 17 00:00:00 2001 From: Happilys Date: Tue, 3 Mar 2026 18:30:55 +0800 Subject: [PATCH 05/13] perf(plugin-git): cache git repo root lookups and clear cache onPrepared --- .../plugin-git/src/node/gitPlugin.ts | 5 +++ .../plugin-git/src/node/utils/getCommits.ts | 39 ++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/plugins/development/plugin-git/src/node/gitPlugin.ts b/plugins/development/plugin-git/src/node/gitPlugin.ts index a7a79b9136..004ec31de0 100644 --- a/plugins/development/plugin-git/src/node/gitPlugin.ts +++ b/plugins/development/plugin-git/src/node/gitPlugin.ts @@ -14,6 +14,7 @@ import { resolveContributors } from './resolveContributors.js' import { PLUGIN_NAME, checkGitRepo, + clearGitRepoRootCache, getCommits, inferGitProvider, injectGitOptions, @@ -144,6 +145,10 @@ export const gitPlugin = }) }, + onPrepared: () => { + clearGitRepoRootCache() + }, + clientConfigFile: () => prepareClientConfigFile(app, { changelog, contributors }), } diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index 61c3db92c2..35cef6e8bb 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -9,6 +9,9 @@ const INFO_SPLITTER = '[|]' const COMMIT_SPLITTER = String.raw`\|/` const RE_CO_AUTHOR = /^ *Co-authored-by: ?([^<]*)<([^>]*)> */gim +const gitRepoRootResultCache = new Map() +const gitRepoRootTaskCache = new Map>() + const getCoAuthorsFromCommitBody = ( body: string, ): Pick[] => @@ -91,12 +94,21 @@ const runGitLog = (args: string[], cwd: string): Promise => const getGitRepoRoot = ( filePath: string, cwd: string, -): Promise => - new Promise((resolve) => { - const absFilePath = path.isAbsolute(filePath) - ? filePath - : path.resolve(cwd, filePath) - const dir = path.dirname(absFilePath) +): Promise => { + const absFilePath = path.isAbsolute(filePath) + ? filePath + : path.resolve(cwd, filePath) + + const dir = path.normalize(path.dirname(absFilePath)) + const cachedResult = gitRepoRootResultCache.get(dir) + + if (cachedResult !== undefined || gitRepoRootResultCache.has(dir)) + return Promise.resolve(cachedResult ?? null) + + const cachedTask = gitRepoRootTaskCache.get(dir) + if (cachedTask) return cachedTask + + const task = new Promise((resolve) => { const gitProcess = spawn('git', ['rev-parse', '--show-toplevel'], { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'], @@ -130,6 +142,16 @@ const getGitRepoRoot = ( }) }) + const cached = task.then((gitRoot) => { + gitRepoRootResultCache.set(dir, gitRoot) + gitRepoRootTaskCache.delete(dir) + return gitRoot + }) + gitRepoRootTaskCache.set(dir, cached) + + return cached +} + /** * Get raw commits for a specific file * @@ -256,3 +278,8 @@ export const getCommits = async ( return mergeRawCommits(rawCommits).sort((a, b) => b.time - a.time) } + +export const clearGitRepoRootCache = (): void => { + gitRepoRootResultCache.clear() + gitRepoRootTaskCache.clear() +} From 52e6a367cc43b323d30e4892e5c12b849d50cae4 Mon Sep 17 00:00:00 2001 From: Happilys Date: Tue, 12 May 2026 18:34:33 +0800 Subject: [PATCH 06/13] fix: try to fix commit link --- .../src/client/composables/useChangelog.ts | 4 ++- .../plugin-git/src/node/resolveChangelog.ts | 5 ++- .../plugin-git/src/node/typings.ts | 4 ++- .../plugin-git/src/node/utils/getCommits.ts | 31 ++++++++++++------- .../src/node/utils/inferGitProvider.ts | 26 ++++++++++++++++ .../plugin-git/src/shared/index.ts | 25 +++++++++++++++ 6 files changed, 81 insertions(+), 14 deletions(-) diff --git a/plugins/development/plugin-git/src/client/composables/useChangelog.ts b/plugins/development/plugin-git/src/client/composables/useChangelog.ts index e6b7d08c99..db67848a52 100644 --- a/plugins/development/plugin-git/src/client/composables/useChangelog.ts +++ b/plugins/development/plugin-git/src/client/composables/useChangelog.ts @@ -42,7 +42,7 @@ export const useChangelog = >() const { pattern = {}, provider } = gitOptions - const repo = resolveRepoLink(gitOptions.repo, provider) + const mainRepo = resolveRepoLink(gitOptions.repo, provider) return computed(() => { if (frontmatter.value.changelog === false || !toValue(enabled)) @@ -58,6 +58,8 @@ export const useChangelog = item, ) + const repo = item.submodule?.repoUrl ?? mainRepo + if (pattern.issue && repo) { res.message = res.message.replace( RE_ISSUE, diff --git a/plugins/development/plugin-git/src/node/resolveChangelog.ts b/plugins/development/plugin-git/src/node/resolveChangelog.ts index 73a2c8d877..07477e9241 100644 --- a/plugins/development/plugin-git/src/node/resolveChangelog.ts +++ b/plugins/development/plugin-git/src/node/resolveChangelog.ts @@ -46,7 +46,8 @@ export const resolveChangelog = ( : commits for (const commit of sliceCommits) { - const { hash, message, time, author, email, refs, coAuthors } = commit + const { hash, message, time, author, email, refs, coAuthors, submodule } = + commit const tag = parseTagName(refs) const contributor = getContributorInfo( { name: getUserNameWithNoreplyEmail(email) ?? author, email }, @@ -64,6 +65,8 @@ export const resolveChangelog = ( if (tag) resolved.tag = tag + if (submodule) resolved.submodule = submodule + result.push(resolved) } diff --git a/plugins/development/plugin-git/src/node/typings.ts b/plugins/development/plugin-git/src/node/typings.ts index 1c82945baa..a7a5711a24 100644 --- a/plugins/development/plugin-git/src/node/typings.ts +++ b/plugins/development/plugin-git/src/node/typings.ts @@ -1,4 +1,4 @@ -import type { CoAuthorInfo } from '../shared/index.js' +import type { CoAuthorInfo, SubmoduleInfo } from '../shared/index.js' export interface RawCommit { /** File path */ @@ -20,6 +20,8 @@ export interface RawCommit { /** The co-authors of the commit */ coAuthors: CoAuthorInfo[] + + submodule: SubmoduleInfo | null } export interface MergedRawCommit extends Omit { diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index ea16719c28..548f7d9040 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -1,9 +1,11 @@ import { spawn } from 'node:child_process' -import type { GitContributorInfo } from '../../shared/index.js' +import { path } from 'vuepress/utils' + +import type { GitContributorInfo, SubmoduleInfo } from '../../shared/index.js' import type { GitPluginOptions } from '../options.js' import type { MergedRawCommit, RawCommit } from '../typings.js' -import { path } from 'vuepress/utils' +import { getRemoteUrl, normalizeRepoUrl } from './inferGitProvider.js' import { logger } from './logger.js' const INFO_SPLITTER = '[|]' @@ -174,18 +176,24 @@ export const getRawCommits = async ( const gitRoot = await getGitRepoRoot(filepath, cwd) try { + const absFilePath = path.isAbsolute(filepath) + ? filepath + : path.resolve(cwd, filepath) + let repoRelativeFilePath = filepath - if (gitRoot) { - // Resolve to absolute path first, then convert to repo-relative path - const absFilePath = path.isAbsolute(repoRelativeFilePath) - ? repoRelativeFilePath - : path.resolve(cwd, repoRelativeFilePath) + let submodule: SubmoduleInfo | null = null + if (gitRoot) { repoRelativeFilePath = path.relative(gitRoot, absFilePath) - } else { - logger.warn( - `Failed to resolve git repo root for "${filepath}" under cwd "${cwd}", falling back to cwd; git history may be incomplete.`, - ) + + const isSubmodule = + gitRoot !== cwd && gitRoot.startsWith(`${cwd}${path.sep}`) + + if (isSubmodule) { + submodule = { + repoUrl: normalizeRepoUrl(getRemoteUrl(gitRoot) || ''), + } + } } const stdout = await runGitLog( @@ -225,6 +233,7 @@ export const getRawCommits = async ( author, email, coAuthors: getCoAuthorsFromCommitBody(body), + submodule, } }) } catch (err) { diff --git a/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts b/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts index ea06f67437..d0dd8e3a7d 100644 --- a/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts +++ b/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts @@ -47,6 +47,32 @@ export const getRemoteUrl = (cwd: string): string | null => { } } +/** + * Normalize git remote URL to clean HTTPS format + * + * 将 Git 远程 URL 规范化为干净的 HTTPS 格式 + * + * @param url - The git remote URL / Git 远程 URL + * @returns Normalized HTTPS URL / 规范化后的 HTTPS URL + * + * @example + * normalizeRepoUrl('https://github.com/user/repo.git') // 'https://github.com/user/repo' + * normalizeRepoUrl('git@github.com:user/repo.git') // 'https://github.com/user/repo' + * normalizeRepoUrl('ssh://git@github.com/user/repo.git') // 'https://github.com/user/repo' + */ +export const normalizeRepoUrl = (url: string): string => { + const normalized = url.replace(/\.git$/, '') + + const sshMatch = /^git@([^:]+):(.+)$/.exec(normalized) + if (sshMatch) return `https://${sshMatch[1]}/${sshMatch[2]}` + + const sshProtocolMatch = /^ssh:\/\/git@([^/]+)\/(.+)$/.exec(normalized) + if (sshProtocolMatch) + return `https://${sshProtocolMatch[1]}/${sshProtocolMatch[2]}` + + return normalized +} + /** * Infer git provider from remote URL * diff --git a/plugins/development/plugin-git/src/shared/index.ts b/plugins/development/plugin-git/src/shared/index.ts index dd23eb71e2..1386210958 100644 --- a/plugins/development/plugin-git/src/shared/index.ts +++ b/plugins/development/plugin-git/src/shared/index.ts @@ -72,6 +72,29 @@ export interface GitContributorInfo { url?: string } +/** + * Submodule information + * + * 子模块信息 + */ +export interface SubmoduleInfo { + /** + * Submodule repository URL + * + * 子模块仓库地址 + */ + repoUrl?: string + + /** + * Git provider + * + * Git 提供商 + */ + provider?: KnownGitProvider | null + + pattern?: GitUrlPattern +} + /** * Git changelog information * @@ -133,6 +156,8 @@ export interface GitChangelogInfo { * 提交协同作者列表 */ coAuthors?: CoAuthorInfo[] + + submodule?: SubmoduleInfo } /** From 901b1ced397f51b5176a6128e234c6066fd8e26a Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Thu, 21 May 2026 10:56:54 +0800 Subject: [PATCH 07/13] fix: only set submodule if there is actual repo url --- .../plugin-git/src/node/utils/getCommits.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index c7f6436827..30b619071a 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -90,9 +90,11 @@ const runGitLog = (args: string[], cwd: string): Promise => * This function runs `git rev-parse --show-toplevel` in the directory of the * target file to determine the top-level directory of the git repository. * - * @param filePath - File path (relative or absolute) whose repository root is requested + * @param filePath - File path (relative or absolute) whose repository root is + * requested * @param cwd - Current working directory - * @returns Promise that resolves to normalized git root path, or null if not in a git repository + * @returns Promise that resolves to normalized git root path, or null if not in + * a git repository */ const getGitRepoRoot = ( filePath: string, @@ -190,9 +192,9 @@ export const getRawCommits = async ( gitRoot !== cwd && gitRoot.startsWith(`${cwd}${path.sep}`) if (isSubmodule) { - submodule = { - repoUrl: normalizeRepoUrl(getRemoteUrl(gitRoot) || ''), - } + const remoteUrl = getRemoteUrl(gitRoot) + + if (remoteUrl) submodule = { repoUrl: normalizeRepoUrl(remoteUrl) } } } From 5616503472950352204345a3edb6d51e1ca63d7a Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Thu, 21 May 2026 10:57:40 +0800 Subject: [PATCH 08/13] fix: handle git:// protocols --- .../src/node/utils/inferGitProvider.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts b/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts index d0dd8e3a7d..ed3c1c0447 100644 --- a/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts +++ b/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts @@ -52,24 +52,28 @@ export const getRemoteUrl = (cwd: string): string | null => { * * 将 Git 远程 URL 规范化为干净的 HTTPS 格式 * + * @example + * normalizeRepoUrl('https://github.com/user/repo.git') // 'https://github.com/user/repo' + * normalizeRepoUrl('git@github.com:user/repo.git') // 'https://github.com/user/repo' + * normalizeRepoUrl('ssh://git@github.com/user/repo.git') // 'https://github.com/user/repo' + * * @param url - The git remote URL / Git 远程 URL * @returns Normalized HTTPS URL / 规范化后的 HTTPS URL - * - * @example - * normalizeRepoUrl('https://github.com/user/repo.git') // 'https://github.com/user/repo' - * normalizeRepoUrl('git@github.com:user/repo.git') // 'https://github.com/user/repo' - * normalizeRepoUrl('ssh://git@github.com/user/repo.git') // 'https://github.com/user/repo' */ export const normalizeRepoUrl = (url: string): string => { - const normalized = url.replace(/\.git$/, '') + const normalized = url.replace(/\.git$/u, '') - const sshMatch = /^git@([^:]+):(.+)$/.exec(normalized) + const sshMatch = /^git@([^:]+):(.+)$/u.exec(normalized) if (sshMatch) return `https://${sshMatch[1]}/${sshMatch[2]}` - const sshProtocolMatch = /^ssh:\/\/git@([^/]+)\/(.+)$/.exec(normalized) + const sshProtocolMatch = /^ssh:\/\/git@([^/]+)\/(.+)$/u.exec(normalized) if (sshProtocolMatch) return `https://${sshProtocolMatch[1]}/${sshProtocolMatch[2]}` + const gitProtocolMatch = /^git:\/\/([^/]+)\/(.+)$/u.exec(normalized) + if (gitProtocolMatch) + return `https://${gitProtocolMatch[1]}/${gitProtocolMatch[2]}` + return normalized } From 6f707d3fc7b961ed9b66b783d3088eb471af10e2 Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Thu, 21 May 2026 11:34:23 +0800 Subject: [PATCH 09/13] fix: stricter remote check --- .../development/plugin-git/src/node/utils/inferGitProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts b/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts index ed3c1c0447..ddbb8c64ae 100644 --- a/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts +++ b/plugins/development/plugin-git/src/node/utils/inferGitProvider.ts @@ -31,7 +31,7 @@ export const getRemoteUrl = (cwd: string): string | null => { const remotesOutput = execSync('git remote', execOptions) const firstRemote = remotesOutput.split('\n')[0]?.trim() - if (firstRemote) { + if (firstRemote && /^[\w.-]+$/u.test(firstRemote)) { const remoteUrl = execSync( `git remote get-url ${firstRemote}`, execOptions, From 209247f4b1b96dfcf7e9ad083a3fe7a663d13613 Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Thu, 21 May 2026 12:33:05 +0800 Subject: [PATCH 10/13] refactor: refine codes --- .../plugin-git/src/node/utils/getCommits.ts | 78 ++++++++----------- .../plugin-git/src/node/utils/safePath.ts | 13 ++++ 2 files changed, 46 insertions(+), 45 deletions(-) create mode 100644 plugins/development/plugin-git/src/node/utils/safePath.ts diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index 30b619071a..c0b8710b42 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -7,6 +7,7 @@ import type { GitPluginOptions } from '../options.js' import type { MergedRawCommit, RawCommit } from '../typings.js' import { getRemoteUrl, normalizeRepoUrl } from './inferGitProvider.js' import { logger } from './logger.js' +import { isSafePath } from './safePath.js' const INFO_SPLITTER = '[|]' const COMMIT_SPLITTER = String.raw`\|/` @@ -49,9 +50,9 @@ const getGitLogFormat = ({ * @param cwd - The working directory to run the git command in * @returns A promise that resolves with the stdout of the git command */ -const runGitLog = (args: string[], cwd: string): Promise => +const runGit = (args: string[], cwd: string): Promise => new Promise((resolve, reject) => { - const gitProcess = spawn('git', ['log', ...args], { + const gitProcess = spawn('git', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'], }) @@ -68,7 +69,7 @@ const runGitLog = (args: string[], cwd: string): Promise => }) gitProcess.on('error', (error) => { - reject(new Error(`Failed to spawn 'git log': ${error.message}`)) + reject(new Error(`Failed to spawn 'git ${args[0]}': ${error.message}`)) }) gitProcess.on('close', (code) => { @@ -77,7 +78,7 @@ const runGitLog = (args: string[], cwd: string): Promise => } else { reject( new Error( - `'git log' failed with exit code ${code}: ${stderrData.trim()}`, + `'git ${args[0]}' failed with exit code ${code}: ${stderrData.trim()}`, ), ) } @@ -96,65 +97,44 @@ const runGitLog = (args: string[], cwd: string): Promise => * @returns Promise that resolves to normalized git root path, or null if not in * a git repository */ -const getGitRepoRoot = ( +const getGitRepoRoot = async ( filePath: string, cwd: string, ): Promise => { + if (!isSafePath(filePath)) return null + const absFilePath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath) const dir = path.normalize(path.dirname(absFilePath)) - const cachedResult = gitRepoRootResultCache.get(dir) - if (cachedResult !== undefined || gitRepoRootResultCache.has(dir)) - return Promise.resolve(cachedResult ?? null) + if (gitRepoRootResultCache.has(dir)) + return gitRepoRootResultCache.get(dir) ?? null const cachedTask = gitRepoRootTaskCache.get(dir) if (cachedTask) return cachedTask - const task = new Promise((resolve) => { - const gitProcess = spawn('git', ['rev-parse', '--show-toplevel'], { - cwd: dir, - stdio: ['ignore', 'pipe', 'pipe'], - }) - - let stdoutData = '' - let stderrData = '' - - gitProcess.stdout.on('data', (chunk: Buffer) => { - stdoutData += chunk.toString('utf-8') - }) + const task = runGit(['rev-parse', '--show-toplevel'], dir) + .then((stdout) => path.normalize(stdout.trim())) + // oxlint-disable-next-line promise/prefer-await-to-callbacks + .catch((err: unknown) => { + logger.error(err instanceof Error ? err.message : String(err)) - gitProcess.stderr.on('data', (chunk: Buffer) => { - stderrData += chunk.toString('utf-8') + return null }) - gitProcess.on('error', (error) => { - logger.error(`Failed to spawn git rev-parse: ${error.message}`) - resolve(null) - }) + gitRepoRootTaskCache.set(dir, task) - gitProcess.on('close', (code) => { - if (code === 0) { - resolve(path.normalize(stdoutData.trim())) - } else { - logger.error( - `git rev-parse failed (code=${code}): ${stderrData.trim()}`, - ) - resolve(null) - } - }) - }) + try { + const gitRoot = await task - const cached = task.then((gitRoot) => { gitRepoRootResultCache.set(dir, gitRoot) - gitRepoRootTaskCache.delete(dir) - return gitRoot - }) - gitRepoRootTaskCache.set(dir, cached) - return cached + return gitRoot + } finally { + gitRepoRootTaskCache.delete(dir) + } } /** @@ -188,8 +168,9 @@ export const getRawCommits = async ( if (gitRoot) { repoRelativeFilePath = path.relative(gitRoot, absFilePath) + const relative = path.relative(cwd, gitRoot) const isSubmodule = - gitRoot !== cwd && gitRoot.startsWith(`${cwd}${path.sep}`) + gitRoot !== cwd && relative !== '' && !relative.startsWith('..') if (isSubmodule) { const remoteUrl = getRemoteUrl(gitRoot) @@ -198,8 +179,15 @@ export const getRawCommits = async ( } } - const stdout = await runGitLog( + if (!isSafePath(repoRelativeFilePath)) { + logger.warn(`Skipping unsafe file path: ${filepath}`) + + return [] + } + + const stdout = await runGit( [ + 'log', '--max-count=-1', `--format=${format}${COMMIT_SPLITTER}`, '--date=unix', diff --git a/plugins/development/plugin-git/src/node/utils/safePath.ts b/plugins/development/plugin-git/src/node/utils/safePath.ts new file mode 100644 index 0000000000..7b2cc538a9 --- /dev/null +++ b/plugins/development/plugin-git/src/node/utils/safePath.ts @@ -0,0 +1,13 @@ +// oxlint-disable-next-line no-control-regex +const RE_UNSAFE_PATH = /[\u0000\n\r;|&`$(){}[\]<>!]/u + +/** + * Check if a file path is safe to pass to git commands + * + * 检查文件路径是否可以安全传递给 git 命令 + * + * @param filePath - The file path to validate / 要验证的文件路径 + * @returns Whether the path is safe / 路径是否安全 + */ +export const isSafePath = (filePath: string): boolean => + !RE_UNSAFE_PATH.test(filePath) && !filePath.startsWith('..') From 99fa0e40ce2919d28135342e86c05fe814e977dc Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Thu, 21 May 2026 12:35:50 +0800 Subject: [PATCH 11/13] refactor: complete jsdocs --- plugins/development/plugin-git/src/node/typings.ts | 4 ++++ plugins/development/plugin-git/src/shared/index.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/plugins/development/plugin-git/src/node/typings.ts b/plugins/development/plugin-git/src/node/typings.ts index a7a5711a24..3786a5c8b1 100644 --- a/plugins/development/plugin-git/src/node/typings.ts +++ b/plugins/development/plugin-git/src/node/typings.ts @@ -21,6 +21,10 @@ export interface RawCommit { /** The co-authors of the commit */ coAuthors: CoAuthorInfo[] + /** + * Information about the submodule if the commit is related to a submodule + * update, otherwise null. + */ submodule: SubmoduleInfo | null } diff --git a/plugins/development/plugin-git/src/shared/index.ts b/plugins/development/plugin-git/src/shared/index.ts index 1386210958..d4c4c56cec 100644 --- a/plugins/development/plugin-git/src/shared/index.ts +++ b/plugins/development/plugin-git/src/shared/index.ts @@ -92,6 +92,11 @@ export interface SubmoduleInfo { */ provider?: KnownGitProvider | null + /** + * Git URL pattern for the submodule + * + * 子模块的 Git URL 模式 + */ pattern?: GitUrlPattern } From 9f9b7d4b942d677a8d5ae01ff10d35c130397367 Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Thu, 21 May 2026 12:38:46 +0800 Subject: [PATCH 12/13] build: update deps --- pnpm-lock.yaml | 154 +++++++++++-------------------------------------- 1 file changed, 35 insertions(+), 119 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 479e124714..46832b28c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -253,7 +253,7 @@ importers: version: 1.99.0 sass-loader: specifier: 'catalog:' - version: 17.0.0(sass-embedded@1.99.0)(sass@1.99.0)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + version: 17.0.0(sass-embedded@1.99.0)(sass@1.99.0)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) vue: specifier: 'catalog:' version: 3.5.34(typescript@6.0.3) @@ -4443,12 +4443,6 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/eslint-scope@3.7.7': - resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - - '@types/eslint@9.6.1': - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -9617,16 +9611,6 @@ packages: webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - webpack@5.106.2: - resolution: {integrity: sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - webpack@5.107.0: resolution: {integrity: sha512-PSxeHk/dmLYZlnTU+vL1Gej6Evg5RNtl3flhxBresfznFnzxinHMzHKloHnywM/3ouQv7/AlZCswWDIkNSggUA==} engines: {node: '>=10.13.0'} @@ -12605,16 +12589,6 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/eslint-scope@3.7.7': - dependencies: - '@types/eslint': 9.6.1 - '@types/estree': 1.0.9 - - '@types/eslint@9.6.1': - dependencies: - '@types/estree': 1.0.9 - '@types/json-schema': 7.0.15 - '@types/estree@1.0.8': {} '@types/estree@1.0.9': {} @@ -13032,22 +13006,22 @@ snapshots: '@vuepress/shared': 2.0.0-rc.30 '@vuepress/utils': 2.0.0-rc.30 autoprefixer: 10.5.0(postcss@8.5.15) - copy-webpack-plugin: 14.0.0(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) - css-loader: 7.1.4(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) - css-minimizer-webpack-plugin: 8.0.0(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) - esbuild-loader: 4.4.3(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + copy-webpack-plugin: 14.0.0(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + css-loader: 7.1.4(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + css-minimizer-webpack-plugin: 8.0.0(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + esbuild-loader: 4.4.3(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) express: 4.22.2 - html-webpack-plugin: 5.6.7(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + html-webpack-plugin: 5.6.7(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) lightningcss: 1.32.0 - mini-css-extract-plugin: 2.10.2(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + mini-css-extract-plugin: 2.10.2(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) postcss: 8.5.15 - postcss-loader: 8.2.1(postcss@8.5.15)(typescript@6.0.3)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) - style-loader: 4.0.0(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + postcss-loader: 8.2.1(postcss@8.5.15)(typescript@6.0.3)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + style-loader: 4.0.0(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) vue: 3.5.34(typescript@6.0.3) - vue-loader: 17.4.2(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3))(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + vue-loader: 17.4.2(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3))(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) vue-router: 5.0.7(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3)) - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) - webpack-dev-server: 5.2.4(tslib@2.8.1)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack-dev-server: 5.2.4(tslib@2.8.1)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) webpack-merge: 6.0.1 webpack-v5-chain: 1.1.0 transitivePeerDependencies: @@ -13885,14 +13859,14 @@ snapshots: cookie@0.7.2: {} - copy-webpack-plugin@14.0.0(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + copy-webpack-plugin@14.0.0(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 schema-utils: 4.3.3 serialize-javascript: 7.0.5 tinyglobby: 0.2.16 - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) core-js-compat@3.49.0: dependencies: @@ -13936,7 +13910,7 @@ snapshots: css-functions-list@3.3.3: {} - css-loader@7.1.4(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + css-loader@7.1.4(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: icss-utils: 5.1.0(postcss@8.5.15) postcss: 8.5.15 @@ -13947,9 +13921,9 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.8.0 optionalDependencies: - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) - css-minimizer-webpack-plugin@8.0.0(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + css-minimizer-webpack-plugin@8.0.0(esbuild@0.28.0)(lightningcss@1.32.0)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: '@jridgewell/trace-mapping': 0.3.31 cssnano: 7.1.9(postcss@8.5.15) @@ -13957,7 +13931,7 @@ snapshots: postcss: 8.5.15 schema-utils: 4.3.3 serialize-javascript: 7.0.5 - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) optionalDependencies: esbuild: 0.28.0 lightningcss: 1.32.0 @@ -14523,12 +14497,12 @@ snapshots: es-toolkit@1.46.1: {} - esbuild-loader@4.4.3(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + esbuild-loader@4.4.3(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: esbuild: 0.27.7 get-tsconfig: 4.14.0 loader-utils: 2.0.4 - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) webpack-sources: 3.4.1 esbuild@0.27.7: @@ -15109,7 +15083,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.7(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + html-webpack-plugin@5.6.7(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -15117,7 +15091,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.3.3 optionalDependencies: - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) htmlparser2@10.1.0: dependencies: @@ -16192,11 +16166,11 @@ snapshots: mimic-function@5.0.1: {} - mini-css-extract-plugin@2.10.2(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + mini-css-extract-plugin@2.10.2(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: schema-utils: 4.3.3 tapable: 2.3.3 - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) minimalistic-assert@1.0.1: {} @@ -16741,14 +16715,14 @@ snapshots: postcss: 8.5.15 yaml: 2.9.0 - postcss-loader@8.2.1(postcss@8.5.15)(typescript@6.0.3)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + postcss-loader@8.2.1(postcss@8.5.15)(typescript@6.0.3)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: cosmiconfig: 9.0.1(typescript@6.0.3) jiti: 2.7.0 postcss: 8.5.15 semver: 7.8.0 optionalDependencies: - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) transitivePeerDependencies: - typescript @@ -17386,12 +17360,6 @@ snapshots: sass-embedded-win32-arm64: 1.99.0 sass-embedded-win32-x64: 1.99.0 - sass-loader@17.0.0(sass-embedded@1.99.0)(sass@1.99.0)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): - optionalDependencies: - sass: 1.99.0 - sass-embedded: 1.99.0 - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) - sass-loader@17.0.0(sass-embedded@1.99.0)(sass@1.99.0)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): optionalDependencies: sass: 1.99.0 @@ -17829,9 +17797,9 @@ snapshots: strip-json-comments@2.0.1: {} - style-loader@4.0.0(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + style-loader@4.0.0(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) stylehacks@7.0.11(postcss@8.5.15): dependencies: @@ -18009,18 +17977,6 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser-webpack-plugin@5.6.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.3.3 - terser: 5.47.1 - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) - optionalDependencies: - esbuild: 0.28.0 - lightningcss: 1.32.0 - postcss: 8.5.15 - terser-webpack-plugin@5.6.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -18421,12 +18377,12 @@ snapshots: transitivePeerDependencies: - msw - vue-loader@17.4.2(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3))(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + vue-loader@17.4.2(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@6.0.3))(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: chalk: 4.1.2 hash-sum: 2.0.0 watchpack: 2.5.1 - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) optionalDependencies: '@vue/compiler-sfc': 3.5.34 vue: 3.5.34(typescript@6.0.3) @@ -18527,7 +18483,7 @@ snapshots: webidl-conversions@4.0.2: {} - webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: colorette: 2.0.20 memfs: 4.57.2(tslib@2.8.1) @@ -18536,11 +18492,11 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) transitivePeerDependencies: - tslib - webpack-dev-server@5.2.4(tslib@2.8.1)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): + webpack-dev-server@5.2.4(tslib@2.8.1)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -18568,10 +18524,10 @@ snapshots: serve-index: 1.9.2 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) ws: 8.20.1 optionalDependencies: - webpack: 5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) + webpack: 5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15) transitivePeerDependencies: - bufferutil - debug @@ -18594,46 +18550,6 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15): - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.9 - '@types/json-schema': 7.0.15 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.16.0 - acorn-import-phases: 1.0.4(acorn@8.16.0) - browserslist: 4.28.2 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.21.5 - es-module-lexer: 2.1.0 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - loader-runner: 4.3.2 - mime-db: 1.54.0 - neo-async: 2.6.2 - schema-utils: 4.3.3 - tapable: 2.3.3 - terser-webpack-plugin: 5.6.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)(webpack@5.106.2(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15)) - watchpack: 2.5.1 - webpack-sources: 3.4.1 - transitivePeerDependencies: - - '@minify-html/node' - - '@swc/core' - - '@swc/css' - - '@swc/html' - - clean-css - - cssnano - - csso - - esbuild - - html-minifier-terser - - lightningcss - - postcss - - uglify-js - webpack@5.107.0(esbuild@0.28.0)(lightningcss@1.32.0)(postcss@8.5.15): dependencies: '@types/estree': 1.0.9 From fa93008f3e3d579cc99070e7ddf73e09c03c49ad Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Thu, 21 May 2026 14:54:40 +0800 Subject: [PATCH 13/13] fix(plugin-git): only get commits in same git root --- .../plugin-git/src/node/utils/getCommits.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/plugins/development/plugin-git/src/node/utils/getCommits.ts b/plugins/development/plugin-git/src/node/utils/getCommits.ts index c0b8710b42..6c9e22b965 100644 --- a/plugins/development/plugin-git/src/node/utils/getCommits.ts +++ b/plugins/development/plugin-git/src/node/utils/getCommits.ts @@ -97,7 +97,7 @@ const runGit = (args: string[], cwd: string): Promise => * @returns Promise that resolves to normalized git root path, or null if not in * a git repository */ -const getGitRepoRoot = async ( +export const getGitRepoRoot = async ( filePath: string, cwd: string, ): Promise => { @@ -269,9 +269,26 @@ export const getCommits = async ( cwd: string, options: GitPluginOptions, ): Promise => { + if (filepaths.length === 0) return [] + + const roots = await Promise.all( + filepaths.map((filepath) => getGitRepoRoot(filepath, cwd)), + ) + const [primaryRoot] = roots + + const validFilepaths = filepaths.filter((filepath, index) => { + if (roots[index] === primaryRoot) return true + + logger.warn( + `Skipping '${filepath}': file belongs to a different git repository`, + ) + + return false + }) + const rawCommits = ( await Promise.all( - filepaths.map((filepath) => getRawCommits(filepath, cwd, options)), + validFilepaths.map((filepath) => getRawCommits(filepath, cwd, options)), ) ).flat()