diff --git a/CHANGELOG.md b/CHANGELOG.md index f5205126ba9..f44f76ee33f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ All notable changes to Sourcegraph are documented in this file. - Expiry to access tokens. Users can now select a maximum timespan for which a token is valid. Tokens will automatically lose access after this period. Default timeframes and an override to allow access tokens without expiration can be configured in the `auth.accessTokens` section of the site configuration. [#59565](https://github.com/sourcegraph/sourcegraph/pull/59565) - Gerrit code host connections now support an 'exclude' field that prevents repos in this list from being synced. [#59739](https://github.com/sourcegraph/sourcegraph/pull/59739) - Limit the number of active access tokens for a user. By default users are able to have 25 active access tokens. This limit can be configured using the `maxTokensPerUser` setting in the `auth.accessTokens` section of the site configuration. [#59731](https://github.com/sourcegraph/sourcegraph/pull/59731) +- Add experimental support for .cody/ignore when retrieving remote context. To enable it, set `experimentalFeatures.codyContextIgnore: true` in the site configuration. [#59836](https://github.com/sourcegraph/sourcegraph/pull/59836), [#59907](https://github.com/sourcegraph/sourcegraph/pull/59907) ### Changed diff --git a/client/web/BUILD.bazel b/client/web/BUILD.bazel index 1ace9b1aa62..5e0c01b38b8 100644 --- a/client/web/BUILD.bazel +++ b/client/web/BUILD.bazel @@ -203,6 +203,7 @@ ts_project( "src/cody/chat/index.tsx", "src/cody/components/ChatUI/ChatUi.tsx", "src/cody/components/ChatUI/index.tsx", + "src/cody/components/ChatUI/useIsFileIgnored.ts", "src/cody/components/CodeMirrorEditor.ts", "src/cody/components/CodyIcon.tsx", "src/cody/components/CodyLogo.tsx", @@ -1799,6 +1800,7 @@ ts_project( "//:node_modules/graphql", # keep "//:node_modules/history", "//:node_modules/http-status-codes", + "//:node_modules/ignore", "//:node_modules/is-absolute-url", "//:node_modules/js-cookie", "//:node_modules/js-yaml", diff --git a/client/web/src/cody/components/ChatUI/ChatUi.tsx b/client/web/src/cody/components/ChatUI/ChatUi.tsx index 12a407fead7..e5ccafe64f2 100644 --- a/client/web/src/cody/components/ChatUI/ChatUi.tsx +++ b/client/web/src/cody/components/ChatUI/ChatUi.tsx @@ -34,6 +34,8 @@ import { GettingStarted } from '../GettingStarted' import { ScopeSelector } from '../ScopeSelector' import type { ScopeSelectorProps } from '../ScopeSelector/ScopeSelector' +import { useIsFileIgnored } from './useIsFileIgnored' + import styles from './ChatUi.module.scss' export const SCROLL_THRESHOLD = 100 @@ -82,6 +84,8 @@ export const ChatUI: React.FC = ({ codyChatStore, isCodyChatPage, const onSubmit = useCallback((text: string) => submitMessage(text), [submitMessage]) const onEdit = useCallback((text: string) => editMessage(text), [editMessage]) + const isFileIgnored = useIsFileIgnored() + const scopeSelectorProps: ScopeSelectorProps = useMemo( () => ({ scope, @@ -92,6 +96,7 @@ export const ChatUI: React.FC = ({ codyChatStore, isCodyChatPage, transcriptHistory, className: 'mt-2', authenticatedUser, + isFileIgnored, }), [ scope, @@ -101,6 +106,7 @@ export const ChatUI: React.FC = ({ codyChatStore, isCodyChatPage, logTranscriptEvent, transcriptHistory, authenticatedUser, + isFileIgnored, ] ) diff --git a/client/web/src/cody/components/ChatUI/useIsFileIgnored.ts b/client/web/src/cody/components/ChatUI/useIsFileIgnored.ts new file mode 100644 index 00000000000..f7eaffc66aa --- /dev/null +++ b/client/web/src/cody/components/ChatUI/useIsFileIgnored.ts @@ -0,0 +1,57 @@ +import { useCallback, useEffect, useState } from 'react' + +import type { Ignore } from 'ignore' +import { useLocation } from 'react-router-dom' + +import { useQuery, gql } from '@sourcegraph/http-client' + +import type { CodyIgnoreContentResult, CodyIgnoreContentVariables } from '../../../graphql-operations' +import { parseBrowserRepoURL } from '../../../util/url' + +const CODY_IGNORE_CONTENT = gql` + query CodyIgnoreContent($repoName: String!, $repoRev: String!, $filePath: String!) { + repository(name: $repoName) { + commit(rev: $repoRev) { + blob(path: $filePath) { + content + } + } + } + } +` + +const CODY_IGNORE_PATH = '.cody/ignore' + +export const useIsFileIgnored = (): ((path: string) => boolean) => { + const location = useLocation() + const { repoName, revision } = parseBrowserRepoURL(location.pathname + location.search + location.hash) + const { data } = useQuery(CODY_IGNORE_CONTENT, { + skip: !window.context?.experimentalFeatures.codyContextIgnore, + variables: { repoName, repoRev: revision || '', filePath: CODY_IGNORE_PATH }, + }) + const [ignoreManager, setIgnoreManager] = useState() + + const content = data?.repository?.commit?.blob?.content + useEffect(() => { + const loadIgnore = async (): Promise => { + if (content) { + const ignore = (await import('ignore')).default + setIgnoreManager(ignore().add(content)) + } + } + + void loadIgnore() + }, [content]) + + const isFileIgnored = useCallback( + (path: string): boolean => { + if (ignoreManager) { + return ignoreManager.ignores(path) + } + return false + }, + [ignoreManager] + ) + + return isFileIgnored +} diff --git a/client/web/src/cody/components/GettingStarted.tsx b/client/web/src/cody/components/GettingStarted.tsx index 7730727c955..95cc96b8432 100644 --- a/client/web/src/cody/components/GettingStarted.tsx +++ b/client/web/src/cody/components/GettingStarted.tsx @@ -6,9 +6,9 @@ import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' import { H4, H5, RadioButton, Text, Button, Grid, Icon, Link } from '@sourcegraph/wildcard' import { CodyColorIcon, CodySpeechBubbleIcon } from '../chat/CodyPageIcon' -import type { CodyChatStore } from '../useCodyChat' import { ScopeSelector } from './ScopeSelector' +import { ScopeSelectorProps } from './ScopeSelector/ScopeSelector' import styles from './GettingStarted.module.scss' @@ -19,15 +19,7 @@ const DEFAULT_VERTICAL_OFFSET = '1rem' /* eslint-disable @sourcegraph/sourcegraph/check-help-links */ export const GettingStarted: React.FC< - Pick< - CodyChatStore, - | 'scope' - | 'logTranscriptEvent' - | 'transcriptHistory' - | 'setScope' - | 'toggleIncludeInferredRepository' - | 'toggleIncludeInferredFile' - > & { + ScopeSelectorProps & { isCodyChatPage?: boolean submitInput: (input: string, submitType: 'user' | 'suggestion' | 'example') => void authenticatedUser: AuthenticatedUser | null diff --git a/client/web/src/cody/components/ScopeSelector/ScopeSelector.tsx b/client/web/src/cody/components/ScopeSelector/ScopeSelector.tsx index 6eee7998978..7f5264ca9a3 100644 --- a/client/web/src/cody/components/ScopeSelector/ScopeSelector.tsx +++ b/client/web/src/cody/components/ScopeSelector/ScopeSelector.tsx @@ -29,6 +29,7 @@ export interface ScopeSelectorProps { // rather than collapsing or flipping position. encourageOverlap?: boolean authenticatedUser: AuthenticatedUser | null + isFileIgnored: (path: string) => boolean } export const ScopeSelector: React.FC = React.memo(function ScopeSelectorComponent({ @@ -42,6 +43,7 @@ export const ScopeSelector: React.FC = React.memo(function S renderHint, encourageOverlap, authenticatedUser, + isFileIgnored, }) { const [loadReposStatus, { data: newReposStatusData, previousData: previousReposStatusData }] = useLazyQuery< ReposStatusResult, @@ -52,6 +54,14 @@ export const ScopeSelector: React.FC = React.memo(function S const activeEditor = useMemo(() => scope.editor.getActiveTextEditor(), [scope.editor]) + const isCurrentFileIgnored = activeEditor?.filePath ? isFileIgnored(activeEditor.filePath) : false + const inferredFilePath = (!isCurrentFileIgnored && activeEditor?.filePath) || null + useEffect(() => { + if (isCurrentFileIgnored && scope.includeInferredFile) { + setScope({ ...scope, includeInferredFile: false, includeInferredRepository: true }) + } + }, [isCurrentFileIgnored, scope, setScope]) + useEffect(() => { const repoNames = [...scope.repositories] @@ -122,7 +132,7 @@ export const ScopeSelector: React.FC = React.memo(function S includeInferredRepository={scope.includeInferredRepository} includeInferredFile={scope.includeInferredFile} inferredRepository={inferredRepository} - inferredFilePath={activeEditor?.filePath || null} + inferredFilePath={inferredFilePath} additionalRepositories={additionalRepositories} addRepository={addRepository} resetScope={resetScope} @@ -133,9 +143,10 @@ export const ScopeSelector: React.FC = React.memo(function S transcriptHistory={transcriptHistory} authenticatedUser={authenticatedUser} /> - {scope.includeInferredFile && activeEditor?.filePath && ( + + {scope.includeInferredFile && inferredFilePath && ( - {getFileName(activeEditor.filePath)} + {getFileName(inferredFilePath)} )} diff --git a/package.json b/package.json index f360a4fc73f..e232f38ac7d 100644 --- a/package.json +++ b/package.json @@ -351,6 +351,7 @@ "http-status-codes": "^2.1.4", "https-browserify": "^1.0.0", "https-proxy-agent": "^5.0.1", + "ignore": "^5.3.0", "is-absolute-url": "^3.0.3", "isomorphic-fetch": "^3.0.0", "iterare": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2dde97cfd03..c2e2e438d84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,6 +271,9 @@ importers: https-proxy-agent: specifier: ^5.0.1 version: 5.0.1 + ignore: + specifier: ^5.3.0 + version: 5.3.0 is-absolute-url: specifier: ^3.0.3 version: 3.0.3 @@ -3886,7 +3889,7 @@ packages: debug: 4.3.4 espree: 9.6.1 globals: 13.23.0 - ignore: 5.2.4 + ignore: 5.3.0 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -8227,7 +8230,7 @@ packages: '@typescript-eslint/scope-manager': 5.4.0 debug: 4.3.4 eslint: 8.52.0 - ignore: 5.2.4 + ignore: 5.3.0 regexpp: 3.2.0 semver: 7.5.4 tsutils: 3.21.0(typescript@5.2.2) @@ -8301,7 +8304,7 @@ packages: stylelint: ^14.3.0 dependencies: '@manypkg/find-root': 1.1.0 - ignore: 5.2.4 + ignore: 5.3.0 postcss-value-parser: 4.2.0 stylelint: 14.3.0 dev: true @@ -11129,7 +11132,7 @@ packages: debug: 4.3.4 eslint: 8.52.0 graphemer: 1.4.0 - ignore: 5.2.4 + ignore: 5.3.0 natural-compare: 1.4.0 semver: 7.5.4 ts-api-utils: 1.0.3(typescript@5.2.2) @@ -15415,7 +15418,7 @@ packages: glob-parent: 6.0.2 globals: 13.23.0 graphemer: 1.4.0 - ignore: 5.2.4 + ignore: 5.3.0 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -16488,7 +16491,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.2.11 - ignore: 5.2.4 + ignore: 5.3.0 merge2: 1.4.1 slash: 3.0.0 dev: true @@ -16499,7 +16502,7 @@ packages: dependencies: dir-glob: 3.0.1 fast-glob: 3.2.11 - ignore: 5.2.4 + ignore: 5.3.0 merge2: 1.4.1 slash: 4.0.0 @@ -17279,6 +17282,11 @@ packages: /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} + dev: true + + /ignore@5.3.0: + resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} + engines: {node: '>= 4'} /image-size@1.0.2: resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==} @@ -23540,7 +23548,7 @@ packages: globby: 11.1.0 globjoin: 0.1.4 html-tags: 3.1.0 - ignore: 5.2.4 + ignore: 5.3.0 import-lazy: 4.0.0 imurmurhash: 0.1.4 is-plain-object: 5.0.0