Skip to content
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

feat: filter context with .cody/.ignore #1382

Merged
merged 35 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
84d6cda
feat: filter context from .codyignore
abeatrix Oct 12, 2023
d00e1d2
Apply suggestions from code review
abeatrix Oct 13, 2023
107fa0b
use ignore npm package
abeatrix Oct 13, 2023
3c09549
update test
abeatrix Oct 13, 2023
f9490b7
use fileWatcher
abeatrix Oct 13, 2023
defa542
move removeCodyIgnoredFiles in Interaction
abeatrix Oct 13, 2023
09d75fa
Merge branch 'main' into bee/codyignore
abeatrix Nov 3, 2023
8bfec0b
Update isCodyIgnoredFile to handle relative file paths
abeatrix Nov 3, 2023
3ee3520
Support nested ignore files (#1634)
DanTup Nov 11, 2023
16bcf9b
Merge branch 'main' into bee/codyignore
abeatrix Nov 13, 2023
49be541
clean up vscode-context
abeatrix Nov 14, 2023
ffb8a42
Merge branch 'main' of https://github.com/sourcegraph/cody
abeatrix Nov 14, 2023
7ee2824
Merge branch 'main' into bee/codyignore
abeatrix Nov 14, 2023
179db5d
filter embedding results from current ws
abeatrix Nov 15, 2023
17940c2
New cody.internal.unstable feature flag
abeatrix Nov 15, 2023
621ed9a
Merge branch 'main' into bee/codyignore
abeatrix Nov 15, 2023
8139eb7
fix tests
abeatrix Nov 15, 2023
8ae6f6f
set ws in isIgnored instead of setIgnoreFiles
abeatrix Nov 15, 2023
a38fab6
map .cody/.ignore file per codebase in workspace (#1769)
abeatrix Nov 22, 2023
300f2fc
Merge branch 'bee/codyignore'
abeatrix Nov 22, 2023
9ba681d
Merge branch 'bee/codyignore' of https://github.com/sourcegraph/cody …
abeatrix Nov 22, 2023
52afe1e
Merge branch 'bee/codyignore' of https://github.com/sourcegraph/cody …
abeatrix Dec 28, 2023
55cbcaf
Merge branch 'main' into bee/codyignore
abeatrix Dec 28, 2023
20a0449
Merge branch 'main' into bee/codyignore
abeatrix Dec 28, 2023
b836727
codyignore to work with simple chat
abeatrix Dec 28, 2023
45ab0d4
use string for findFiles
abeatrix Dec 29, 2023
8a3762b
move setup logic to git startup
abeatrix Dec 29, 2023
d9fab6f
remove 'Unstable Experimental Features' flag
abeatrix Dec 29, 2023
b7b3d7d
add error logging
abeatrix Dec 29, 2023
28689c6
document code
abeatrix Dec 29, 2023
974fb08
move gitAPIinit to main
abeatrix Dec 29, 2023
e62c34d
update gitAPIinit
abeatrix Dec 29, 2023
e1ccb5b
add error handling
abeatrix Dec 29, 2023
58c252f
refactor gitAPIinit
abeatrix Dec 29, 2023
6eb1823
add changelog entry
abeatrix Dec 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions agent/src/editor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type * as vscode from 'vscode'

import { isCodyIgnoredFile } from '@sourcegraph/cody-shared/src/chat/context-filter'
import {
ActiveTextEditor,
ActiveTextEditorDiagnostic,
Expand Down Expand Up @@ -36,6 +37,9 @@ export class AgentEditor implements Editor {
if (this.agent.workspace.activeDocumentFilePath === null) {
return undefined
}
if (isCodyIgnoredFile(this.agent.workspace.activeDocumentFilePath)) {
return undefined
}
return this.agent.workspace.getDocument(this.agent.workspace.activeDocumentFilePath)
}

Expand Down
65 changes: 65 additions & 0 deletions lib/shared/src/chat/context-filter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { describe, expect, it } from 'vitest'

import { isCodyIgnoredFile, setCodyIgnoreList } from './context-filter'

describe('isCodyIgnoredFile', () => {
// Set the ignore list to the following content:
const codyignoreFileContent = `
node_modules/
**/cody
**/foo/**
DanTup marked this conversation as resolved.
Show resolved Hide resolved
/bar
fooz
barz/*
.git
`
setCodyIgnoreList(codyignoreFileContent)

it('returns false for no file name', () => {
expect(isCodyIgnoredFile()).toBe(false)
})

it('returns true for .env file even if it is not in the ignore list', () => {
expect(isCodyIgnoredFile('.env')).toBe(true)
})

it.each([
'node_modules/foo',
'cody',
'cody/test.ts',
'foo/foobarz.js',
'foo/bar',
'fooz',
'.git',
'barz/index.css',
'barz/foo/index.css',
'foo/bar/index.css',
'foo/.git',
'.git/foo',
])('returns true for file in ignore list %s', (file: string) => {
expect(isCodyIgnoredFile(file)).toBe(true)
})

it.each(['src/app.ts', 'barz', 'env/foobarz.js', 'foobar.go', '.barz', '.gitignore', 'cody.ts'])(
'returns false for file not in ignore list %s',
(file: string) => {
expect(isCodyIgnoredFile(file)).toBe(false)
}
)

it('returns updated value after modifying the ignore list', () => {
const beforeModifiedCodyIgnoreFileContent = `
node_modules
cody/
`
setCodyIgnoreList(beforeModifiedCodyIgnoreFileContent)

expect(isCodyIgnoredFile('cody/index.html')).toBe(true)

const afterModifiedCodyIgnoreFileContent = `
node_modules
`
setCodyIgnoreList(afterModifiedCodyIgnoreFileContent)
expect(isCodyIgnoredFile('cody/index.html')).toBe(false)
})
})
44 changes: 44 additions & 0 deletions lib/shared/src/chat/context-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import path from 'path'

import ignore from 'ignore'

export const CODY_IGNORE_FILENAME = '.cody/.ignore'

let codyIgnored = ignore().add(['.env'])

/**
* Checks if a file should be ignored by Cody based on the .codyignore rules.
*/
export function isCodyIgnoredFile(fileName?: string): boolean {
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
if (!fileName) {
return false
}
// file must be path.relative - remove '/' from the start of the path
return codyIgnored.ignores(path.join(fileName.replace(/^\//, '')))
DanTup marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Sets the ignore rules for Cody by parsing a .codyignore file.
*
* * NOTE: Each client must call this function at startup + every time the .cody/.ignore file & workspace changes.
*
* Takes the contents of the .codyignore file and the workspace root directory as input.
* Splits the file contents into lines, trims whitespace, and adds each non-empty line to a Set of ignore patterns.
* Also adds .env files to the ignore patterns by default.
* Converts the Set into an Array and passes it to the ignore() library to generate the ignore rules.
* Stores the ignore instance and workspace root globally for use later in checking if a file path is ignored.
*/
export function setCodyIgnoreList(codyIgnoreFileContent: string): void {
// Get a list of files to exclude from the codyignore file
const patternList = new Set<string>()
patternList.add('.env')
// Split the content of the file by new lines
const codyIgnoreFileLines = codyIgnoreFileContent.toString().split('\n')
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
// Loop through each line of the gitignore file
for (const line of codyIgnoreFileLines) {
if (line.trim()) {
patternList.add(line.trim())
}
}
codyIgnored = ignore().add(Array.from(patternList))
DanTup marked this conversation as resolved.
Show resolved Hide resolved
}
32 changes: 30 additions & 2 deletions lib/shared/src/chat/transcript/interaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ContextFile, ContextMessage, PreciseContext } from '../../codebase-context/messages'
import { isCodyIgnoredFile } from '../context-filter'

import { ChatMessage, InteractionMessage } from './messages'

Expand All @@ -24,6 +25,33 @@ export class Interaction {
public readonly timestamp: string = new Date().toISOString()
) {}

/**
* Removes context messages for files that should be ignored.
*
* Loops through the context messages and builds a new array, omitting any
* messages for files that match the CODY_IGNORE files filter.
* Also omits the assistant message after any ignored human message.
*
* This ensures context from ignored files does not get used.
*/
private async removeCodyIgnoredFiles(): Promise<ContextMessage[]> {
const contextMessages = await this.fullContext
abeatrix marked this conversation as resolved.
Show resolved Hide resolved
const newMessages = []
for (let i = 0; i < contextMessages.length; i++) {
const message = contextMessages[i]
if (message.speaker === 'human' && message.file?.fileName) {
// find filesToExclude to see if the filename matches
if (isCodyIgnoredFile(message.file?.fileName)) {
i++
continue
}
}
newMessages.push(message)
}
this.fullContext = Promise.resolve(newMessages)
return newMessages
}

public getAssistantMessage(): InteractionMessage {
return { ...this.assistantMessage }
}
Expand All @@ -37,12 +65,12 @@ export class Interaction {
}

public async getFullContext(): Promise<ContextMessage[]> {
const msgs = await this.fullContext
const msgs = await this.removeCodyIgnoredFiles()
return msgs.map(msg => ({ ...msg }))
}

public async hasContext(): Promise<boolean> {
const contextMessages = await this.fullContext
const contextMessages = await this.removeCodyIgnoredFiles()
return contextMessages.length > 0
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"vitest": "^0.33.0"
},
"dependencies": {
"ignore": "^5.2.4",
"react": "18.2.0",
"react-dom": "18.2.0"
},
Expand Down
8 changes: 6 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions vscode/src/completions/context/context-graph.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as vscode from 'vscode'

import { isCodyIgnoredFile } from '@sourcegraph/cody-shared/src/chat/context-filter'

import { logDebug } from '../../log'
import { ContextSnippet } from '../types'

Expand Down Expand Up @@ -45,6 +47,10 @@ export async function getContextFromGraph(options: Options): Promise<GetContextR
let totalChars = 0
let includedGraphMatches = 0
for (const match of graphMatches) {
// skip file that is on the ignore list
if (isCodyIgnoredFile(match.fileName)) {
continue
}
if (totalChars + match.content.length > options.maxChars) {
continue
}
Expand Down
16 changes: 16 additions & 0 deletions vscode/src/completions/context/context-local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import path from 'path'

import * as vscode from 'vscode'

import { isCodyIgnoredFile } from '@sourcegraph/cody-shared/src/chat/context-filter'

import { ContextSnippet } from '../types'

import { bestJaccardMatch, JaccardMatch } from './bestJaccardMatch'
Expand All @@ -21,12 +23,21 @@ interface Options {

export async function getContextFromCurrentEditor(options: Options): Promise<ContextSnippet[]> {
const { document, history, prefix, jaccardDistanceWindowSize } = options
// Return early if current document is on the ignore list
if (isCodyIgnoredFile(document.uri.fsPath)) {
return []
}

const targetText = lastNLines(prefix, jaccardDistanceWindowSize)
const files = await getRelevantFiles(document, history)

const matches: JaccardMatchWithFilename[] = []
for (const { uri, contents } of files) {
// skip file that is on the ignore list
if (isCodyIgnoredFile(uri.fsPath)) {
continue
}

const match = bestJaccardMatch(targetText, contents, jaccardDistanceWindowSize)
if (!match) {
continue
Expand Down Expand Up @@ -81,6 +92,11 @@ async function getRelevantFiles(
return
}

// Do not add files that are on the codyignore list
if (isCodyIgnoredFile(document.uri.fsPath)) {
return
}

if (baseLanguageId(document.languageId) !== baseLanguageId(curLang)) {
return
}
Expand Down
5 changes: 5 additions & 0 deletions vscode/src/completions/context/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode'

import { isCodyIgnoredFile } from '@sourcegraph/cody-shared/src/chat/context-filter'
import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context'

import { ContextSnippet } from '../types'
Expand Down Expand Up @@ -55,6 +56,10 @@ export async function getContext(options: GetContextOptions): Promise<GetContext
let totalChars = 0
function addMatch(match: ContextSnippet): boolean {
// TODO(@philipp-spiess): We should de-dupe on the snippet range and not
// return early if the file is on ignore list
if (isCodyIgnoredFile(match.fileName)) {
return false
}
// the file name to allow for more than one snippet of the same file
if (usedFilenames.has(match.fileName)) {
return false
Expand Down
6 changes: 6 additions & 0 deletions vscode/src/completions/inline-completion-item-provider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { formatDistance } from 'date-fns'
import * as vscode from 'vscode'

import { isCodyIgnoredFile } from '@sourcegraph/cody-shared/src/chat/context-filter'
import { CodebaseContext } from '@sourcegraph/cody-shared/src/codebase-context'
import { FeatureFlag, featureFlagProvider } from '@sourcegraph/cody-shared/src/experimentation/FeatureFlagProvider'
import { RateLimitError } from '@sourcegraph/cody-shared/src/sourcegraph-api/errors'
Expand Down Expand Up @@ -126,6 +127,11 @@ export class InlineCompletionItemProvider implements vscode.InlineCompletionItem
// Making it optional here to execute multiple suggestion in parallel from the CLI script.
token?: vscode.CancellationToken
): Promise<AutocompleteResult | null> {
// Do not create item for files that are on the cody ignore list
if (isCodyIgnoredFile(document.fileName)) {
return null
}

// Update the last request
const lastCompletionRequest = this.lastCompletionRequest
const completionRequest: CompletionRequest = { document, position, context }
Expand Down
4 changes: 4 additions & 0 deletions vscode/src/editor/vscode-editor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode'

import { isCodyIgnoredFile } from '@sourcegraph/cody-shared/src/chat/context-filter'
import type {
ActiveTextEditor,
ActiveTextEditorDiagnostic,
Expand Down Expand Up @@ -56,6 +57,9 @@ export class VSCodeEditor implements Editor<InlineController, FixupController, C
if (!activeEditor) {
return null
}
if (isCodyIgnoredFile(activeEditor.document.fileName)) {
return null
}
const documentUri = activeEditor.document.uri
const documentText = activeEditor.document.getText()
const documentSelection = activeEditor.selection
Expand Down
7 changes: 7 additions & 0 deletions vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { configureExternalServices } from './external-services'
import { FixupController } from './non-stop/FixupController'
import { showSetupNotification } from './notifications/setup-notification'
import { AuthProvider } from './services/AuthProvider'
import { getCodyignoreFileWatcher } from './services/context-filter'
import { showFeedbackSupportQuickPick } from './services/FeedbackOptions'
import { GuardrailsProvider } from './services/GuardrailsProvider'
import { Comment, InlineController } from './services/InlineController'
Expand Down Expand Up @@ -79,6 +80,12 @@ const register = async (
context.extensionMode === vscode.ExtensionMode.Test
await createOrUpdateEventLogger(initialConfig, isExtensionModeDevOrTest)

// Set codyignore list on startup
const codyIgnoreWatcher = await getCodyignoreFileWatcher()
if (codyIgnoreWatcher) {
disposables.push(codyIgnoreWatcher)
}

// Controller for inline Chat
const commentController = new InlineController(context.extensionPath)
// Controller for Non-Stop Cody
Expand Down
Loading