Skip to content

fix(plugin-git): support submodule#621

Open
mtfhx wants to merge 16 commits into
vuepress:mainfrom
mtfhx:plugin-git
Open

fix(plugin-git): support submodule#621
mtfhx wants to merge 16 commits into
vuepress:mainfrom
mtfhx:plugin-git

Conversation

@mtfhx
Copy link
Copy Markdown

@mtfhx mtfhx commented Mar 2, 2026

Before submitting the PR, please make sure you do the following

  • Read the Contributing Guidelines.
  • Provide a description in this PR that addresses what the PR is solving. If this PR is going to solve an existing issue, please reference the issue (e.g. close #123).

What is the purpose of this pull request?

  • Bug fix
  • New feature
  • Other

Description

When VuePress is built from a subdirectory or within a monorepo, plugin-git may fail to locate files correctly.

In my case, the documentation content of the site is maintained in an independent private repository and linked via Git submodules (the site itself is public, but the content repository is private). In this setup, the Git plugin is unable to correctly retrieve commit metadata such as author and date.

This PR improves the behavior with the following workflow:
1. Detect the Git repository root using: git rev-parse --show-toplevel
2. Resolve the target file path:
• Convert to absolute path first
• Then transform it into a path relative to the detected repository root
3. Execute git log from the repository root directory instead of the current working directory

Screenshots

Before

After

- 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
Comment thread plugins/development/plugin-git/src/node/utils/getCommits.ts Outdated
Copy link
Copy Markdown
Member

@Mister-Hope Mister-Hope left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Git plugin is slow enough. I believe a directory cache can be stored here. A directory shall be resolved only once.

e.g.: /path/a.md triggers a check for the /path/ directory, and /path/b.md shall reuse the result from the cache later

Since extendsPage hooks are async, you may need to handle promises here in case the commands are still executed several times due to the delayed return of the first one.

Adding extra memory here shall be worthy for a cache, and the cache map shall be cleared in the onPrepared stage (to save build memory usage), as at this moment we can no longer add pages.

@Mister-Hope Mister-Hope requested a review from Copilot March 2, 2026 09:15
@mtfhx mtfhx changed the title Plugin git Plugin git support submodule Mar 2, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes @vuepress/plugin-git commit collection when VuePress runs from a subdirectory / monorepo setup by ensuring git commands run from the actual repository root and file paths are resolved relative to that root.

Changes:

  • Add a helper to detect the git repository root via git rev-parse --show-toplevel.
  • Convert the target file path to repo-relative form and run git log from the detected repo root.

Comment on lines 151 to 153
const format = getGitLogFormat(options)
const gitRoot = await getGitRepoRoot(filepath, cwd)

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getGitRepoRoot() is executed for every file in getRawCommits(), which adds an extra git spawn per file and can noticeably slow down builds on large sites (especially since getCommits() runs for every page). Consider memoizing repo roots by directory (e.g. a Map keyed by dir/cwd) or computing the repo root once per getCommits() call and reusing it for all filepaths.

Copilot uses AI. Check for mistakes.
Comment thread plugins/development/plugin-git/src/node/utils/getCommits.ts Outdated
if (code === 0) {
resolve(path.normalize(stdoutData.trim()))
} else {
logger.error(
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getGitRepoRoot() logs an error for any non-zero git rev-parse exit, but git rev-parse --show-toplevel can fail in expected situations (e.g. included files outside the repo / wrong directory). Logging at error level here can create noisy, duplicated output (this function then returns null and git log may fail and be logged again). Consider treating common non-repo failures as a non-error (return null silently or at debug level) and only surface an error once with full context when commit collection ultimately fails.

Suggested change
logger.error(
logger.debug(

Copilot uses AI. Check for mistakes.
@Mister-Hope
Copy link
Copy Markdown
Member

It seems that copilot shares the same view with me.

A cache is necessary

@mtfhx mtfhx changed the title Plugin git support submodule fix(plugin-git): support submodule Mar 3, 2026
@Mister-Hope
Copy link
Copy Markdown
Member

Thanks for the update, will review this later.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comment on lines 173 to +200
const format = getGitLogFormat(options)
const gitRoot = await getGitRepoRoot(filepath, cwd)

try {
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)

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 stdout = await runGitLog(
[
'--max-count=-1',
`--format=${format}${COMMIT_SPLITTER}`,
'--date=unix',
'--follow',
'--',
filepath,
repoRelativeFilePath,
],
cwd,
gitRoot || cwd,
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCommits() can now fetch commit histories from different git roots within a single call (because getGitRepoRoot() is computed per filepath). However, mergeRawCommits() still merges commits solely by hash, which assumes all commits come from the same repository. If filepaths ever span multiple repos (e.g. via gitInclude pointing outside a submodule), identical hashes across repos could be merged incorrectly. Consider either enforcing that all filepaths resolve to the same gitRoot (and warn/skip otherwise), or include the repo identity (e.g. gitRoot) in the merge key / raw commit data so merges remain correct across repos.

Copilot uses AI. Check for mistakes.
Comment on lines +105 to +106
if (cachedResult !== undefined || gitRepoRootResultCache.has(dir))
return Promise.resolve(cachedResult ?? null)
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cache hit check is overly complex: gitRepoRootResultCache stores only string | null, so get(dir) returning undefined already means “not cached”. The extra || gitRepoRootResultCache.has(dir) branch is redundant and makes it harder to reason about caching null vs “missing”. Consider rewriting this to a single has(dir) check (or a single cachedResult !== undefined check) for clarity.

Suggested change
if (cachedResult !== undefined || gitRepoRootResultCache.has(dir))
return Promise.resolve(cachedResult ?? null)
if (cachedResult !== undefined) return Promise.resolve(cachedResult)

Copilot uses AI. Check for mistakes.
Comment on lines +137 to +140
logger.error(
`git rev-parse failed (code=${code}): ${stderrData.trim()}`,
)
resolve(null)
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When git rev-parse --show-toplevel fails, the error log currently doesn’t include which directory it was executed in. Including dir (or the original filePath) in the logged message would make it much easier to diagnose submodule / monorepo edge cases, especially since failures may be intermittent depending on which file is being processed.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@Mister-Hope Mister-Hope left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure how the logger.error will be triggered, can you plz provide some examples? Could it be too noisy in some cases?

Mister-Hope
Mister-Hope previously approved these changes Mar 10, 2026
@Mister-Hope Mister-Hope requested a review from pengzhanbo March 11, 2026 08:46
@Mister-Hope
Copy link
Copy Markdown
Member

@pengzhanbo Please review it.

I think the changelog might be broken after this is merged with submodules, but previously it will be broken too.

We might need another PR to fix that.

@pengzhanbo
Copy link
Copy Markdown
Member

The related Vue component of the plugin renders a link pointing to the repository's commit URL, which consists of the repository address + hash. If the file belongs to a sub-repository, this commit record will lead to an incorrect main repository commit link.

@mtfhx
Copy link
Copy Markdown
Author

mtfhx commented Mar 18, 2026

I am not sure how the logger.error will be triggered, can you plz provide some examples? Could it be too noisy in some cases?

During the document compilation process, the version repository was corrupted (deleting the .git directory during compilation can generate logs). Of course, this is an extreme case, and there might also be noise. If necessary, I can delete this part of the log and keep only the error-handling section.

@Mister-Hope
Copy link
Copy Markdown
Member

Can you try to fix changelog in the same PR?

@mtfhx
Copy link
Copy Markdown
Author

mtfhx commented May 13, 2026

I’ve tried to fix the link issue. @Mister-Hope @pengzhanbo

Please review it when convenient. Thanks!

@Mister-Hope
Copy link
Copy Markdown
Member

Mister-Hope commented May 14, 2026

Appreciated, I am not an expert at Git, so let me review it carefully across different agents with their explanations about these commands.

@Mister-Hope
Copy link
Copy Markdown
Member

Mister-Hope commented May 14, 2026

@pengzhanbo Local security check suggests git plugin is now dangerous as filePath are passed to command args without validating.

It would be dangerous if the filePath is rewritten to something like a.md && cat /etc/passwd or a.md && rm -rf /. in functions like getGitCreateDate.

Could you check this part once this PR is merged? I will currently leave these security issue here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants