Skip to content

gptel-context: Allow exclusion of gitignored files #665

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

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b75a0b8
gptel-rewrite: Exclude gitignored files (first pass)
benthamite Jan 26, 2025
9fbf51a
gptel-context: improve performance by getting list of exclusions once
benthamite Jan 26, 2025
60208d1
gptel-context: improve handling of gitignored files
benthamite Jan 29, 2025
450b1ba
Merge remote-tracking branch 'upstream/master' into exclude-gitignored
benthamite Feb 2, 2025
68fdd0a
Merge branch 'exclude-gitignored'
benthamite Feb 23, 2025
7b2dd6d
Merge remote-tracking branch 'upstream/master' into exclude-gitignored
benthamite Feb 26, 2025
df85051
Merge remote-tracking branch 'upstream/master'
benthamite Feb 26, 2025
c898d46
Merge branch 'master' into exclude-gitignored
benthamite Feb 26, 2025
57c079a
gptel-context: Improve user option docstring
benthamite Feb 26, 2025
2f1f234
gptel-context: Remove caching while preserving performance
benthamite Feb 26, 2025
b69a4c0
README: Document how to exclude gitignored files
benthamite Feb 26, 2025
1b252db
gptel-context: Fix typo
benthamite Feb 26, 2025
7ba8543
Merge branch 'master' into exclude-gitignored
benthamite Mar 10, 2025
a8b67c5
gptel-context: Inform user that git-ignored files can be included
benthamite Mar 12, 2025
834a07e
gptel-context: Replace "unignored" with "git-tracked"
benthamite Mar 12, 2025
ef4a10c
gptel-context: Replace "gitignored" with "git-ignored"
benthamite Mar 12, 2025
20e2ee5
gptel-context: Improve performance of git-ignore exclusion via caching
benthamite Mar 12, 2025
df3a24c
README: Clarify default value of gptel-context-exclude-git-ignored
benthamite Mar 12, 2025
c13ad53
gptel-context: improve git error handling
benthamite Mar 12, 2025
9dfbda9
gptel-context: Restore missing indentation
benthamite Mar 12, 2025
b62d08e
gptel-context: Replace "git-tracked" with "git-unignored" in symbol n…
benthamite Mar 13, 2025
48919bf
gptel-context: Rename gptel-context--skip-file-p
benthamite Mar 13, 2025
b7cd02a
gptel-context: Move relevant defuns to ‘git tracking functions’
benthamite Mar 13, 2025
9cb8cf2
gptel-context: Revise docstrings
benthamite Mar 13, 2025
e2ca876
gptel-context: Check buffer-file-name before passing it as argument
benthamite Mar 13, 2025
9b5a630
gptel-context: Handle possibly nil git-cache argument
benthamite Mar 13, 2025
03cebac
gptel-context: Rename gptel-context--is-git-ignored-p
benthamite Mar 13, 2025
10356a8
gptel-context: Remove needless comment
benthamite Mar 13, 2025
e02f44e
gptel-context: Never exclude git-ignored remote files
benthamite Mar 13, 2025
11e7cf5
gptel-context: Refactor using project.el
benthamite Mar 20, 2025
e9fa7e7
gptel-context: Remove redundancy
benthamite Mar 20, 2025
450cf56
README: Update to reflect project.el refactor
benthamite Mar 20, 2025
1290416
README: Update user option name
benthamite Mar 21, 2025
280a829
gptel-context: pass correct value to gptel-context--get-project-files
benthamite Mar 22, 2025
578fb5f
gptel-context: Simplify gptel-context--message-skipped
benthamite Mar 27, 2025
4b99e1e
gptel-context: Store project files in a local variable
benthamite Apr 7, 2025
c197a74
gptel-context: Reverse hunks with accidentally changed indentation
benthamite Apr 7, 2025
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
2 changes: 2 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,8 @@ You can examine the active context from the menu:
And then browse through or remove context from the context buffer:
#+html: <img src="https://github.com/karthink/gptel/assets/8607532/79a5ffe8-3d63-4bf7-9bf6-0457ab61bf2a" align="center" alt="Image showing gptel's context buffer.">

By default, files in a version control system that are not project files will not be added to the context. To be able to add these files, set =gptel-context-restrict-to-project-files= to =nil=. Note that remote files are always included, regardless of the value of =gptel-context-restrict-to-project-files=.

*** Handle "reasoning" content

Some LLMs include in their response a "thinking" or "reasoning" block. This text improves the quality of the LLM’s final output, but may not be interesting to you by itself. You can decide how you would like this "reasoning" content to be handled by gptel by setting the user option =gptel-include-reasoning=. You can include it in the LLM response (the default), omit it entirely, include it in the buffer but ignore it on subsequent conversation turns, or redirect it to another buffer. As with most options, you can specify this behvaior from gptel's transient menu globally, buffer-locally or for the next request only.
Expand Down
103 changes: 86 additions & 17 deletions gptel-context.el
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
;;; -*- lexical-binding: t -*-
(require 'gptel)
(require 'cl-lib)
(require 'project)

(declare-function gptel-menu "gptel-transient")
(declare-function dired-get-marked-files "dired")
Expand Down Expand Up @@ -84,6 +85,16 @@ context chunk. This is accessible as, for example:
:group 'gptel
:type 'function)

(defcustom gptel-context-restrict-to-project-files t
"Whether to restrict files eligible to be added to the context to project files.
When set to t, files in a VCS that are not project files (such as files
listed in `.gitignore' in a Git repository) will not be added to the context."
:group 'gptel
:type 'boolean)

(defvar-local gptel-context--project-files nil
"Cache of project files for the current directory.")

(defun gptel-context-add (&optional arg confirm)
"Add context to gptel in a DWIM fashion.

Expand Down Expand Up @@ -124,11 +135,15 @@ context chunk. This is accessible as, for example:
(y-or-n-p (format "Recursively add files from %d director%s? "
(length dirs)
(if (= (length dirs) 1) "y" "ies"))))
(unless remove-p
(gptel-context--ensure-project-files default-directory))
(mapc action-fn files))))
;; If in an image buffer
((and (derived-mode-p 'image-mode)
(gptel--model-capable-p 'media)
(buffer-file-name))
(gptel--model-capable-p 'media)
(buffer-file-name)
(not (gptel-context--skip-p (buffer-file-name))))
(gptel-context--ensure-project-files default-directory)
(funcall (if (and arg (< (prefix-numeric-value arg) 0))
#'gptel-context-remove
#'gptel-context-add-file)
Expand Down Expand Up @@ -198,35 +213,89 @@ Return PATH if added, nil if ignored."
(defun gptel-context--add-directory (path action)
"Process all files in directory at PATH according to ACTION.
ACTION should be either `add' or `remove'."
(let ((files (directory-files-recursively path "." t)))
(mapc (lambda (file)
(unless (file-directory-p file)
(pcase-exhaustive action
('add
(if (gptel--file-binary-p file)
(gptel-context--add-binary-file file)
(gptel-context--add-text-file file)))
('remove
(setf (alist-get file gptel-context--alist nil 'remove #'equal) nil)))))
files)
(let* ((root (car-safe gptel-context--project-files)))
(dolist (file (directory-files-recursively path "." t))
(unless (file-directory-p file)
(if (and gptel-context-restrict-to-project-files
root
(eq action 'add)
(not (member file gptel-context--project-files)))
(gptel-context--message-skipped file)
(pcase-exhaustive action
('add
(if (gptel--file-binary-p file)
(gptel-context--add-binary-file file)
(gptel-context--add-text-file file)))
('remove
(setf (alist-get file gptel-context--alist nil 'remove #'equal) nil))))))
(when (eq action 'remove)
(message "Directory \"%s\" removed from context." path))))

(defun gptel-context-add-file (path)
"Add the file at PATH to the gptel context.

If PATH is a directory, recursively add all files in it.
PATH should be readable as text."
If PATH is a directory, recursively add all files in it. PATH should
be readable as text."
(interactive "fChoose file to add to context: ")
(gptel-context--ensure-project-files path)
(cond ((file-directory-p path)
(gptel-context--add-directory path 'add))
(gptel-context--add-directory path 'add))
((and gptel-context-restrict-to-project-files
(not (file-remote-p path))
(if gptel-context--project-files
(when-let ((project (project-current nil (car-safe gptel-context--project-files))))
(let ((root (project-root project)))
(and (file-in-directory-p path root)
(not (member path gptel-context--project-files)))))
;; Otherwise check individually
(gptel-context--skip-p path)))
(gptel-context--message-skipped path))
((gptel--file-binary-p path)
(gptel-context--add-binary-file path))
((gptel-context--add-text-file path))))
(t (gptel-context--add-text-file path))))

;;;###autoload (autoload 'gptel-add-file "gptel-context" "Add files to gptel's context." t)
(defalias 'gptel-add-file #'gptel-context-add-file)

;;; project-related functions

(defun gptel-context--ensure-project-files (path)
"Ensure project files are loaded for PATH if needed.
PATH can be a file or directory.

Only loads project files if `gptel-context-restrict-to-project-files' is non-nil
and `gptel-context--project-files' is not already set."
(unless gptel-context--project-files
(when gptel-context-restrict-to-project-files
(setq gptel-context--project-files
(gptel-context--get-project-files
(if (and path (file-directory-p path))
path
(file-name-directory (or path default-directory))))))))

(defun gptel-context--get-project-files (dir)
"Return a list of files in the project DIR, or nil if no project is found."
(when-let ((project (project-current nil dir)))
(project-files project)))

(defun gptel-context--skip-p (file)
"Return non-nil if FILE should not be added to the context."
(when (and gptel-context-restrict-to-project-files
(not (file-remote-p file)))
(when-let* ((project (project-current nil file)))
(not (member file (gptel-context--get-project-files (project-root project)))))))

(defun gptel-context--message-skipped (file)
"Message that FILE is skipped because it is not a project file."
(let* ((type (if (file-directory-p file) "directory" "file"))
(reminder (format "To include this file, unset %S."
'gptel-context-restrict-to-project-files)))
(if-let* ((project (project-current nil (car-safe gptel-context--project-files)))
(rel-file (file-relative-name file (project-root project))))
(message "Skipping %s \"%s\" in project \"%s\". %s"
type rel-file (project-name project) reminder)
(message "Skipping %s \"%s\". %s" type file reminder))))

(defun gptel-context-remove (&optional context)
"Remove the CONTEXT overlay from the contexts list.

Expand Down