diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index cfa6fc94b1e..65bc74ad18d 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -197,22 +197,24 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary` and `--no-pager` immediately after `git` + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+status\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+log\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+show\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+diff\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+ls-files\\b/': true, // git grep // - `--open-files-in-pager`: This is the configured pager, so no risk of code execution // - See notes on `grep` - 'git grep': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+grep\\b/': true, // git branch // - `-d`, `-D`, `--delete`: Prevent branch deletion // - `-m`, `-M`: Prevent branch renaming // - `--force`: Generally dangerous - 'git branch': true, - '/^git branch\\b.*-(d|D|m|M|-delete|-force)\\b/': false, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+branch\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+branch\\b.*-(d|D|m|M|-delete|-force)\\b/': false, // #endregion diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts index f9d03d797f6..c36e07aa673 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/commandLineAutoApprover.test.ts @@ -202,6 +202,62 @@ suite('CommandLineAutoApprover', () => { ok(!await isAutoApproved('kill process')); }); + test('should handle git patterns with -C and --no-pager', async () => { + setAutoApprove({ + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+status\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+log\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+show\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+diff\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+ls-files\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+grep\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+branch\\b/': true, + '/^git(\\s+(-C\\s+\\S+|--no-pager))*\\s+branch\\b.*-(d|D|m|M|-delete|-force)\\b/': false, + }); + + // Basic commands + ok(await isAutoApproved('git status')); + ok(await isAutoApproved('git log')); + ok(await isAutoApproved('git show HEAD')); + ok(await isAutoApproved('git diff')); + ok(await isAutoApproved('git ls-files')); + ok(await isAutoApproved('git grep pattern')); + ok(await isAutoApproved('git branch')); + + // ls-files with options + ok(await isAutoApproved('git ls-files --cached')); + ok(await isAutoApproved('git -C /path ls-files')); + ok(await isAutoApproved('git --no-pager ls-files')); + + // With -C path + ok(await isAutoApproved('git -C /some/path status')); + ok(await isAutoApproved('git -C ../relative log')); + ok(await isAutoApproved('git -C . diff')); + + // With --no-pager + ok(await isAutoApproved('git --no-pager status')); + ok(await isAutoApproved('git --no-pager log')); + ok(await isAutoApproved('git --no-pager diff HEAD~1')); + + // With both -C and --no-pager + ok(await isAutoApproved('git -C /path --no-pager status')); + ok(await isAutoApproved('git --no-pager -C /path log')); + ok(await isAutoApproved('git -C /path1 -C /path2 status')); + ok(await isAutoApproved('git --no-pager --no-pager log')); + + // Branch deletion should be denied + ok(!await isAutoApproved('git branch -d feature')); + ok(!await isAutoApproved('git branch -D feature')); + ok(!await isAutoApproved('git branch --delete feature')); + ok(!await isAutoApproved('git -C /path branch -d feature')); + ok(!await isAutoApproved('git --no-pager branch -D feature')); + ok(!await isAutoApproved('git -C /path --no-pager branch --force')); + + // Branch rename should be denied + ok(!await isAutoApproved('git branch -m old new')); + ok(!await isAutoApproved('git branch -M old new')); + ok(!await isAutoApproved('git -C /path branch -m old new')); + }); + suite('flags', () => { test('should handle case-insensitive regex patterns with i flag', async () => { setAutoApprove({