Skip to content

Commit 90dcf31

Browse files
Copilotalexr00
andauthored
Add githubPullRequests.ignoreSubmodules setting to filter submodules from PR management (#7624)
* Initial plan * Add ignoreSubmodules setting and implementation * Add gitignore entry for manual test files * Add unit tests for isSubmodule function * Polish --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Alex Ross <38270282+alexr00@users.noreply.github.com>
1 parent 8260cee commit 90dcf31

6 files changed

Lines changed: 172 additions & 1 deletion

File tree

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,11 @@
410410
},
411411
"description": "%githubPullRequests.ignoredPullRequestBranches.description%"
412412
},
413+
"githubPullRequests.ignoreSubmodules": {
414+
"type": "boolean",
415+
"default": false,
416+
"description": "%githubPullRequests.ignoreSubmodules.description%"
417+
},
413418
"githubPullRequests.neverIgnoreDefaultBranch": {
414419
"type": "boolean",
415420
"description": "%githubPullRequests.neverIgnoreDefaultBranch.description%"

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"githubPullRequests.allowFetch.description": "Allows `git fetch` to be run for checked-out pull request branches when checking for updates to the pull request.",
6868
"githubPullRequests.ignoredPullRequestBranches.description": "Prevents branches that are associated with a pull request from being automatically detected. This will prevent review mode from being entered on these branches.",
6969
"githubPullRequests.ignoredPullRequestBranches.items": "Branch name",
70+
"githubPullRequests.ignoreSubmodules.description": "Prevents repositories that are submodules from being managed by the GitHub Pull Requests extension.",
7071
"githubPullRequests.neverIgnoreDefaultBranch.description": "Never offer to ignore a pull request associated with the default branch of a repository.",
7172
"githubPullRequests.overrideDefaultBranch.description": "The default branch for a repository is set on github.com. With this setting, you can override that default with another branch.",
7273
"githubPullRequests.postCreate.description": "The action to take after creating a pull request.",

src/common/gitUtils.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import { Repository } from '../api/api';
8+
import { GitApiImpl } from '../api/api1';
9+
10+
/**
11+
* Determines if a repository is a submodule by checking if its path
12+
* appears in any other repository's submodules list.
13+
*/
14+
export function isSubmodule(repo: Repository, git: GitApiImpl): boolean {
15+
const repoPath = repo.rootUri.fsPath;
16+
17+
// Check all other repositories to see if this repo is listed as a submodule
18+
for (const otherRepo of git.repositories) {
19+
if (otherRepo.rootUri.toString() === repo.rootUri.toString()) {
20+
continue; // Skip self
21+
}
22+
23+
// Check if this repo's path appears in the other repo's submodules
24+
for (const submodule of otherRepo.state.submodules) {
25+
// The submodule path is relative to the parent repo, so we need to resolve it
26+
const submodulePath = vscode.Uri.joinPath(otherRepo.rootUri, submodule.path).fsPath;
27+
if (submodulePath === repoPath) {
28+
return true;
29+
}
30+
}
31+
}
32+
33+
return false;
34+
}

src/common/settingKeys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const FILE_LIST_LAYOUT = 'fileListLayout';
1111
export const ASSIGN_TO = 'assignCreated';
1212
export const PUSH_BRANCH = 'pushBranch';
1313
export const IGNORE_PR_BRANCHES = 'ignoredPullRequestBranches';
14+
export const IGNORE_SUBMODULES = 'ignoreSubmodules';
1415
export const NEVER_IGNORE_DEFAULT_BRANCH = 'neverIgnoreDefaultBranch';
1516
export const OVERRIDE_DEFAULT_BRANCH = 'overrideDefaultBranch';
1617
export const PULL_BRANCH = 'pullBranch';

src/extension.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import { GitApiImpl } from './api/api1';
1212
import { registerCommands } from './commands';
1313
import { COPILOT_SWE_AGENT } from './common/copilot';
1414
import { commands } from './common/executeCommands';
15+
import { isSubmodule } from './common/gitUtils';
1516
import Logger from './common/logger';
1617
import * as PersistentState from './common/persistentState';
1718
import { parseRepositoryRemotes } from './common/remote';
1819
import { Resource } from './common/resources';
19-
import { BRANCH_PUBLISH, EXPERIMENTAL_CHAT, FILE_LIST_LAYOUT, GIT, OPEN_DIFF_ON_CLICK, PR_SETTINGS_NAMESPACE, SHOW_INLINE_OPEN_FILE_ACTION } from './common/settingKeys';
20+
import { BRANCH_PUBLISH, EXPERIMENTAL_CHAT, FILE_LIST_LAYOUT, GIT, IGNORE_SUBMODULES, OPEN_DIFF_ON_CLICK, PR_SETTINGS_NAMESPACE, SHOW_INLINE_OPEN_FILE_ACTION } from './common/settingKeys';
2021
import { initBasedOnSettingChange } from './common/settingsUtils';
2122
import { TemporaryState } from './common/temporaryState';
2223
import { Schemes } from './common/uri';
@@ -185,6 +186,14 @@ async function init(
185186
Logger.appendLine(`Repo ${repo.rootUri} has already been setup.`, ACTIVATION);
186187
return;
187188
}
189+
190+
// Check if submodules should be ignored
191+
const ignoreSubmodules = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<boolean>(IGNORE_SUBMODULES, false);
192+
if (ignoreSubmodules && isSubmodule(repo, git)) {
193+
Logger.appendLine(`Repo ${repo.rootUri} is a submodule and will be ignored due to ${IGNORE_SUBMODULES} setting.`, ACTIVATION);
194+
return;
195+
}
196+
188197
const newFolderManager = new FolderRepositoryManager(reposManager.folderManagers.length, context, repo, telemetry, git, credentialStore, createPrHelper, themeWatcher);
189198
reposManager.insertFolderManager(newFolderManager);
190199
const newReviewManager = new ReviewManager(
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { default as assert } from 'assert';
7+
import * as vscode from 'vscode';
8+
import { Repository, Submodule } from '../api/api';
9+
import { GitApiImpl } from '../api/api1';
10+
import { isSubmodule } from '../common/gitUtils'
11+
12+
describe('isSubmodule Tests', function () {
13+
it('should return false for repositories with no submodules', () => {
14+
const mockRepo: Repository = {
15+
rootUri: vscode.Uri.file('/home/user/repo1'),
16+
state: {
17+
submodules: [],
18+
remotes: [],
19+
HEAD: undefined,
20+
rebaseCommit: undefined,
21+
mergeChanges: [],
22+
indexChanges: [],
23+
workingTreeChanges: [],
24+
onDidChange: new vscode.EventEmitter<void>().event,
25+
},
26+
} as Partial<Repository> as Repository;
27+
28+
const mockGit: GitApiImpl = {
29+
repositories: [mockRepo],
30+
} as GitApiImpl;
31+
32+
const result = isSubmodule(mockRepo, mockGit);
33+
assert.strictEqual(result, false);
34+
});
35+
36+
it('should return true when repository is listed as submodule in another repo', () => {
37+
const submoduleRepo: Repository = {
38+
rootUri: vscode.Uri.file('/home/user/parent/submodule'),
39+
state: {
40+
submodules: [],
41+
remotes: [],
42+
HEAD: undefined,
43+
rebaseCommit: undefined,
44+
mergeChanges: [],
45+
indexChanges: [],
46+
workingTreeChanges: [],
47+
onDidChange: new vscode.EventEmitter<void>().event,
48+
},
49+
} as Partial<Repository> as Repository;
50+
51+
const parentRepo: Repository = {
52+
rootUri: vscode.Uri.file('/home/user/parent'),
53+
state: {
54+
submodules: [
55+
{
56+
name: 'submodule',
57+
path: 'submodule',
58+
url: 'https://github.com/example/submodule.git'
59+
} as Submodule
60+
],
61+
remotes: [],
62+
HEAD: undefined,
63+
rebaseCommit: undefined,
64+
mergeChanges: [],
65+
indexChanges: [],
66+
workingTreeChanges: [],
67+
onDidChange: new vscode.EventEmitter<void>().event,
68+
},
69+
} as Partial<Repository> as Repository;
70+
71+
const mockGit: GitApiImpl = {
72+
repositories: [parentRepo, submoduleRepo],
73+
} as GitApiImpl;
74+
75+
const result = isSubmodule(submoduleRepo, mockGit);
76+
assert.strictEqual(result, true);
77+
});
78+
79+
it('should return false when repository is not listed as submodule', () => {
80+
const repo1: Repository = {
81+
rootUri: vscode.Uri.file('/home/user/repo1'),
82+
state: {
83+
submodules: [],
84+
remotes: [],
85+
HEAD: undefined,
86+
rebaseCommit: undefined,
87+
mergeChanges: [],
88+
indexChanges: [],
89+
workingTreeChanges: [],
90+
onDidChange: new vscode.EventEmitter<void>().event,
91+
},
92+
} as Partial<Repository> as Repository;
93+
94+
const repo2: Repository = {
95+
rootUri: vscode.Uri.file('/home/user/repo2'),
96+
state: {
97+
submodules: [
98+
{
99+
name: 'different-submodule',
100+
path: 'different-submodule',
101+
url: 'https://github.com/example/different.git'
102+
} as Submodule
103+
],
104+
remotes: [],
105+
HEAD: undefined,
106+
rebaseCommit: undefined,
107+
mergeChanges: [],
108+
indexChanges: [],
109+
workingTreeChanges: [],
110+
onDidChange: new vscode.EventEmitter<void>().event,
111+
},
112+
} as Partial<Repository> as Repository;
113+
114+
const mockGit: GitApiImpl = {
115+
repositories: [repo1, repo2],
116+
} as GitApiImpl;
117+
118+
const result = isSubmodule(repo1, mockGit);
119+
assert.strictEqual(result, false);
120+
});
121+
});

0 commit comments

Comments
 (0)