-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix: allow checkpoints with nested git repos by excluding them #8434
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -70,20 +70,6 @@ export abstract class ShadowCheckpointService extends EventEmitter { | |||||||||||||
| throw new Error("Shadow git repo already initialized") | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const nestedGitPath = await this.getNestedGitRepository() | ||||||||||||||
|
|
||||||||||||||
| if (nestedGitPath) { | ||||||||||||||
| // Show persistent error message with the offending path | ||||||||||||||
| const relativePath = path.relative(this.workspaceDir, nestedGitPath) | ||||||||||||||
| const message = t("common:errors.nested_git_repos_warning", { path: relativePath }) | ||||||||||||||
| vscode.window.showErrorMessage(message) | ||||||||||||||
|
|
||||||||||||||
| throw new Error( | ||||||||||||||
| `Checkpoints are disabled because a nested git repository was detected at: ${relativePath}. ` + | ||||||||||||||
| "Please remove or relocate nested git repositories to use the checkpoints feature.", | ||||||||||||||
| ) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| await fs.mkdir(this.checkpointsDir, { recursive: true }) | ||||||||||||||
| const git = simpleGit(this.checkpointsDir) | ||||||||||||||
| const gitVersion = await git.version() | ||||||||||||||
|
|
@@ -102,6 +88,7 @@ export abstract class ShadowCheckpointService extends EventEmitter { | |||||||||||||
| ) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Write exclude file which will include nested git repos | ||||||||||||||
| await this.writeExcludeFile() | ||||||||||||||
| this.baseHash = await git.revparse(["HEAD"]) | ||||||||||||||
| } else { | ||||||||||||||
|
|
@@ -111,6 +98,7 @@ export abstract class ShadowCheckpointService extends EventEmitter { | |||||||||||||
| await git.addConfig("commit.gpgSign", "false") // Disable commit signing for shadow repo. | ||||||||||||||
| await git.addConfig("user.name", "Roo Code") | ||||||||||||||
| await git.addConfig("user.email", "[email protected]") | ||||||||||||||
| // Write exclude file which will include nested git repos | ||||||||||||||
| await this.writeExcludeFile() | ||||||||||||||
| await this.stageAll(git) | ||||||||||||||
| const { commit } = await git.commit("initial commit", { "--allow-empty": null }) | ||||||||||||||
|
|
@@ -147,6 +135,16 @@ export abstract class ShadowCheckpointService extends EventEmitter { | |||||||||||||
| protected async writeExcludeFile() { | ||||||||||||||
| await fs.mkdir(path.join(this.dotGitDir, "info"), { recursive: true }) | ||||||||||||||
| const patterns = await getExcludePatterns(this.workspaceDir) | ||||||||||||||
|
|
||||||||||||||
| // Add nested git repositories to exclude patterns | ||||||||||||||
| const nestedGitPaths = await this.getNestedGitRepositories() | ||||||||||||||
| for (const gitPath of nestedGitPaths) { | ||||||||||||||
| const relativePath = path.relative(this.workspaceDir, gitPath) | ||||||||||||||
| // Add the directory and all its contents to exclude patterns | ||||||||||||||
| patterns.push(relativePath + "/") | ||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Normalize to POSIX-style separators before writing to exclude to ensure Git matching across platforms.
Suggested change
|
||||||||||||||
| this.log(`[${this.constructor.name}#writeExcludeFile] excluding nested git repo: ${relativePath}`) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| await fs.writeFile(path.join(this.dotGitDir, "info", "exclude"), patterns.join("\n")) | ||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Add a trailing newline when writing .git/info/exclude for consistency with POSIX tools and Git defaults.
Suggested change
|
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -160,7 +158,7 @@ export abstract class ShadowCheckpointService extends EventEmitter { | |||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| private async getNestedGitRepository(): Promise<string | null> { | ||||||||||||||
| private async getNestedGitRepositories(): Promise<string[]> { | ||||||||||||||
| try { | ||||||||||||||
| // Find all .git/HEAD files that are not at the root level. | ||||||||||||||
| const args = ["--files", "--hidden", "--follow", "-g", "**/.git/HEAD", this.workspaceDir] | ||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This only finds nested repos with a .git directory containing HEAD; submodules/worktrees often have .git as a file pointing to a gitdir. Consider also detecting .git files and resolving their gitdir targets to exclude them. |
||||||||||||||
|
|
@@ -182,32 +180,46 @@ export abstract class ShadowCheckpointService extends EventEmitter { | |||||||||||||
| ) | ||||||||||||||
| }) | ||||||||||||||
|
|
||||||||||||||
| if (nestedGitPaths.length > 0) { | ||||||||||||||
| // Get the first nested git repository path | ||||||||||||||
| const repoDirs: string[] = [] | ||||||||||||||
| for (const gitPath of nestedGitPaths) { | ||||||||||||||
| // Remove .git/HEAD from the path to get the repository directory | ||||||||||||||
| const headPath = nestedGitPaths[0].path | ||||||||||||||
| const headPath = gitPath.path | ||||||||||||||
|
|
||||||||||||||
| // Use path module to properly extract the repository directory | ||||||||||||||
| // The HEAD file is at .git/HEAD, so we need to go up two directories | ||||||||||||||
| const gitDir = path.dirname(headPath) // removes HEAD, gives us .git | ||||||||||||||
| const repoDir = path.dirname(gitDir) // removes .git, gives us the repo directory | ||||||||||||||
|
|
||||||||||||||
| const absolutePath = path.join(this.workspaceDir, repoDir) | ||||||||||||||
| repoDirs.push(absolutePath) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (repoDirs.length > 0) { | ||||||||||||||
| this.log( | ||||||||||||||
| `[${this.constructor.name}#getNestedGitRepository] found ${nestedGitPaths.length} nested git repositories, first at: ${repoDir}`, | ||||||||||||||
| `[${this.constructor.name}#getNestedGitRepositories] found ${repoDirs.length} nested git repositories`, | ||||||||||||||
| ) | ||||||||||||||
| return absolutePath | ||||||||||||||
|
|
||||||||||||||
| // Show informational message to user | ||||||||||||||
| if (repoDirs.length === 1) { | ||||||||||||||
| const relativePath = path.relative(this.workspaceDir, repoDirs[0]) | ||||||||||||||
| vscode.window.showInformationMessage( | ||||||||||||||
| t("common:info.nested_git_repo_excluded", { path: relativePath }), | ||||||||||||||
| ) | ||||||||||||||
| } else { | ||||||||||||||
| vscode.window.showInformationMessage( | ||||||||||||||
| t("common:info.nested_git_repos_excluded", { count: repoDirs.length }), | ||||||||||||||
| ) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return null | ||||||||||||||
| return repoDirs | ||||||||||||||
| } catch (error) { | ||||||||||||||
| this.log( | ||||||||||||||
| `[${this.constructor.name}#getNestedGitRepository] failed to check for nested git repos: ${error instanceof Error ? error.message : String(error)}`, | ||||||||||||||
| `[${this.constructor.name}#getNestedGitRepositories] failed to check for nested git repos: ${error instanceof Error ? error.message : String(error)}`, | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| // If we can't check, assume there are no nested repos to avoid blocking the feature. | ||||||||||||||
| return null | ||||||||||||||
| // If we can't check, return empty array to avoid blocking the feature. | ||||||||||||||
| return [] | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo/wording concern: The comment on line 101 says "Write exclude file which will include nested git repos", but based on the commit message, the intention is to exclude nested git repos. Please verify if "include" should be changed to "exclude" to maintain consistency.