chatmcp-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aider/__init__.py +20 -0
- aider/__main__.py +4 -0
- aider/_version.py +21 -0
- aider/analytics.py +250 -0
- aider/args.py +926 -0
- aider/args_formatter.py +228 -0
- aider/coders/__init__.py +34 -0
- aider/coders/architect_coder.py +48 -0
- aider/coders/architect_prompts.py +40 -0
- aider/coders/ask_coder.py +9 -0
- aider/coders/ask_prompts.py +35 -0
- aider/coders/base_coder.py +2483 -0
- aider/coders/base_prompts.py +60 -0
- aider/coders/chat_chunks.py +64 -0
- aider/coders/context_coder.py +53 -0
- aider/coders/context_prompts.py +75 -0
- aider/coders/editblock_coder.py +657 -0
- aider/coders/editblock_fenced_coder.py +10 -0
- aider/coders/editblock_fenced_prompts.py +143 -0
- aider/coders/editblock_func_coder.py +141 -0
- aider/coders/editblock_func_prompts.py +27 -0
- aider/coders/editblock_prompts.py +174 -0
- aider/coders/editor_diff_fenced_coder.py +9 -0
- aider/coders/editor_diff_fenced_prompts.py +11 -0
- aider/coders/editor_editblock_coder.py +8 -0
- aider/coders/editor_editblock_prompts.py +18 -0
- aider/coders/editor_whole_coder.py +8 -0
- aider/coders/editor_whole_prompts.py +10 -0
- aider/coders/help_coder.py +16 -0
- aider/coders/help_prompts.py +46 -0
- aider/coders/patch_coder.py +706 -0
- aider/coders/patch_prompts.py +161 -0
- aider/coders/search_replace.py +757 -0
- aider/coders/shell.py +37 -0
- aider/coders/single_wholefile_func_coder.py +102 -0
- aider/coders/single_wholefile_func_prompts.py +27 -0
- aider/coders/udiff_coder.py +429 -0
- aider/coders/udiff_prompts.py +115 -0
- aider/coders/udiff_simple.py +14 -0
- aider/coders/udiff_simple_prompts.py +25 -0
- aider/coders/wholefile_coder.py +144 -0
- aider/coders/wholefile_func_coder.py +134 -0
- aider/coders/wholefile_func_prompts.py +27 -0
- aider/coders/wholefile_prompts.py +67 -0
- aider/commands.py +1665 -0
- aider/copypaste.py +72 -0
- aider/deprecated.py +126 -0
- aider/diffs.py +128 -0
- aider/dump.py +29 -0
- aider/editor.py +147 -0
- aider/exceptions.py +107 -0
- aider/format_settings.py +26 -0
- aider/gui.py +545 -0
- aider/help.py +163 -0
- aider/help_pats.py +19 -0
- aider/history.py +143 -0
- aider/io.py +1175 -0
- aider/linter.py +304 -0
- aider/llm.py +47 -0
- aider/main.py +1267 -0
- aider/mdstream.py +243 -0
- aider/models.py +1286 -0
- aider/onboarding.py +428 -0
- aider/openrouter.py +128 -0
- aider/prompts.py +64 -0
- aider/queries/tree-sitter-language-pack/README.md +7 -0
- aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
- aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
- aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
- aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
- aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
- aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
- aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
- aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
- aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
- aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
- aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
- aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
- aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
- aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
- aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
- aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
- aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
- aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
- aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
- aider/queries/tree-sitter-languages/README.md +23 -0
- aider/queries/tree-sitter-languages/c-tags.scm +9 -0
- aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
- aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
- aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
- aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
- aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
- aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
- aider/queries/tree-sitter-languages/go-tags.scm +30 -0
- aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
- aider/queries/tree-sitter-languages/java-tags.scm +20 -0
- aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
- aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
- aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
- aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
- aider/queries/tree-sitter-languages/php-tags.scm +26 -0
- aider/queries/tree-sitter-languages/python-tags.scm +12 -0
- aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
- aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
- aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
- aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
- aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
- aider/reasoning_tags.py +82 -0
- aider/repo.py +623 -0
- aider/repomap.py +847 -0
- aider/report.py +200 -0
- aider/resources/__init__.py +3 -0
- aider/resources/model-metadata.json +468 -0
- aider/resources/model-settings.yml +1767 -0
- aider/run_cmd.py +132 -0
- aider/scrape.py +284 -0
- aider/sendchat.py +61 -0
- aider/special.py +203 -0
- aider/urls.py +17 -0
- aider/utils.py +338 -0
- aider/versioncheck.py +113 -0
- aider/voice.py +187 -0
- aider/waiting.py +221 -0
- aider/watch.py +318 -0
- aider/watch_prompts.py +12 -0
- aider/website/Gemfile +8 -0
- aider/website/_includes/blame.md +162 -0
- aider/website/_includes/get-started.md +22 -0
- aider/website/_includes/help-tip.md +5 -0
- aider/website/_includes/help.md +24 -0
- aider/website/_includes/install.md +5 -0
- aider/website/_includes/keys.md +4 -0
- aider/website/_includes/model-warnings.md +67 -0
- aider/website/_includes/multi-line.md +22 -0
- aider/website/_includes/python-m-aider.md +5 -0
- aider/website/_includes/recording.css +228 -0
- aider/website/_includes/recording.md +34 -0
- aider/website/_includes/replit-pipx.md +9 -0
- aider/website/_includes/works-best.md +1 -0
- aider/website/_sass/custom/custom.scss +103 -0
- aider/website/docs/config/adv-model-settings.md +1881 -0
- aider/website/docs/config/aider_conf.md +527 -0
- aider/website/docs/config/api-keys.md +90 -0
- aider/website/docs/config/dotenv.md +478 -0
- aider/website/docs/config/editor.md +127 -0
- aider/website/docs/config/model-aliases.md +103 -0
- aider/website/docs/config/options.md +843 -0
- aider/website/docs/config/reasoning.md +209 -0
- aider/website/docs/config.md +44 -0
- aider/website/docs/faq.md +378 -0
- aider/website/docs/git.md +76 -0
- aider/website/docs/index.md +47 -0
- aider/website/docs/install/codespaces.md +39 -0
- aider/website/docs/install/docker.md +57 -0
- aider/website/docs/install/optional.md +100 -0
- aider/website/docs/install/replit.md +8 -0
- aider/website/docs/install.md +115 -0
- aider/website/docs/languages.md +264 -0
- aider/website/docs/legal/contributor-agreement.md +111 -0
- aider/website/docs/legal/privacy.md +104 -0
- aider/website/docs/llms/anthropic.md +77 -0
- aider/website/docs/llms/azure.md +48 -0
- aider/website/docs/llms/bedrock.md +132 -0
- aider/website/docs/llms/cohere.md +34 -0
- aider/website/docs/llms/deepseek.md +32 -0
- aider/website/docs/llms/gemini.md +49 -0
- aider/website/docs/llms/github.md +105 -0
- aider/website/docs/llms/groq.md +36 -0
- aider/website/docs/llms/lm-studio.md +39 -0
- aider/website/docs/llms/ollama.md +75 -0
- aider/website/docs/llms/openai-compat.md +39 -0
- aider/website/docs/llms/openai.md +58 -0
- aider/website/docs/llms/openrouter.md +78 -0
- aider/website/docs/llms/other.md +103 -0
- aider/website/docs/llms/vertex.md +50 -0
- aider/website/docs/llms/warnings.md +10 -0
- aider/website/docs/llms/xai.md +53 -0
- aider/website/docs/llms.md +54 -0
- aider/website/docs/more/analytics.md +122 -0
- aider/website/docs/more/edit-formats.md +116 -0
- aider/website/docs/more/infinite-output.md +137 -0
- aider/website/docs/more-info.md +8 -0
- aider/website/docs/recordings/auto-accept-architect.md +31 -0
- aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
- aider/website/docs/recordings/index.md +21 -0
- aider/website/docs/recordings/model-accepts-settings.md +69 -0
- aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
- aider/website/docs/repomap.md +112 -0
- aider/website/docs/scripting.md +100 -0
- aider/website/docs/troubleshooting/aider-not-found.md +24 -0
- aider/website/docs/troubleshooting/edit-errors.md +76 -0
- aider/website/docs/troubleshooting/imports.md +62 -0
- aider/website/docs/troubleshooting/models-and-keys.md +54 -0
- aider/website/docs/troubleshooting/support.md +79 -0
- aider/website/docs/troubleshooting/token-limits.md +96 -0
- aider/website/docs/troubleshooting/warnings.md +12 -0
- aider/website/docs/troubleshooting.md +11 -0
- aider/website/docs/usage/browser.md +57 -0
- aider/website/docs/usage/caching.md +49 -0
- aider/website/docs/usage/commands.md +132 -0
- aider/website/docs/usage/conventions.md +119 -0
- aider/website/docs/usage/copypaste.md +121 -0
- aider/website/docs/usage/images-urls.md +48 -0
- aider/website/docs/usage/lint-test.md +118 -0
- aider/website/docs/usage/modes.md +211 -0
- aider/website/docs/usage/not-code.md +179 -0
- aider/website/docs/usage/notifications.md +87 -0
- aider/website/docs/usage/tips.md +79 -0
- aider/website/docs/usage/tutorials.md +30 -0
- aider/website/docs/usage/voice.md +121 -0
- aider/website/docs/usage/watch.md +294 -0
- aider/website/docs/usage.md +92 -0
- aider/website/share/index.md +101 -0
- chatmcp_cli-0.1.0.dist-info/METADATA +502 -0
- chatmcp_cli-0.1.0.dist-info/RECORD +228 -0
- chatmcp_cli-0.1.0.dist-info/WHEEL +5 -0
- chatmcp_cli-0.1.0.dist-info/entry_points.txt +3 -0
- chatmcp_cli-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
- chatmcp_cli-0.1.0.dist-info/top_level.txt +1 -0
aider/repo.py
ADDED
@@ -0,0 +1,623 @@
|
|
1
|
+
import contextlib
|
2
|
+
import os
|
3
|
+
import time
|
4
|
+
from pathlib import Path, PurePosixPath
|
5
|
+
|
6
|
+
try:
|
7
|
+
import git
|
8
|
+
|
9
|
+
ANY_GIT_ERROR = [
|
10
|
+
git.exc.ODBError,
|
11
|
+
git.exc.GitError,
|
12
|
+
git.exc.InvalidGitRepositoryError,
|
13
|
+
git.exc.GitCommandNotFound,
|
14
|
+
]
|
15
|
+
except ImportError:
|
16
|
+
git = None
|
17
|
+
ANY_GIT_ERROR = []
|
18
|
+
|
19
|
+
import pathspec
|
20
|
+
|
21
|
+
from aider import prompts, utils
|
22
|
+
|
23
|
+
from .dump import dump # noqa: F401
|
24
|
+
from .waiting import WaitingSpinner
|
25
|
+
|
26
|
+
ANY_GIT_ERROR += [
|
27
|
+
OSError,
|
28
|
+
IndexError,
|
29
|
+
BufferError,
|
30
|
+
TypeError,
|
31
|
+
ValueError,
|
32
|
+
AttributeError,
|
33
|
+
AssertionError,
|
34
|
+
TimeoutError,
|
35
|
+
]
|
36
|
+
ANY_GIT_ERROR = tuple(ANY_GIT_ERROR)
|
37
|
+
|
38
|
+
|
39
|
+
@contextlib.contextmanager
|
40
|
+
def set_git_env(var_name, value, original_value):
|
41
|
+
"""Temporarily set a Git environment variable."""
|
42
|
+
os.environ[var_name] = value
|
43
|
+
try:
|
44
|
+
yield
|
45
|
+
finally:
|
46
|
+
if original_value is not None:
|
47
|
+
os.environ[var_name] = original_value
|
48
|
+
elif var_name in os.environ:
|
49
|
+
del os.environ[var_name]
|
50
|
+
|
51
|
+
|
52
|
+
class GitRepo:
|
53
|
+
repo = None
|
54
|
+
aider_ignore_file = None
|
55
|
+
aider_ignore_spec = None
|
56
|
+
aider_ignore_ts = 0
|
57
|
+
aider_ignore_last_check = 0
|
58
|
+
subtree_only = False
|
59
|
+
ignore_file_cache = {}
|
60
|
+
git_repo_error = None
|
61
|
+
|
62
|
+
def __init__(
|
63
|
+
self,
|
64
|
+
io,
|
65
|
+
fnames,
|
66
|
+
git_dname,
|
67
|
+
aider_ignore_file=None,
|
68
|
+
models=None,
|
69
|
+
attribute_author=True,
|
70
|
+
attribute_committer=True,
|
71
|
+
attribute_commit_message_author=False,
|
72
|
+
attribute_commit_message_committer=False,
|
73
|
+
commit_prompt=None,
|
74
|
+
subtree_only=False,
|
75
|
+
git_commit_verify=True,
|
76
|
+
attribute_co_authored_by=False, # Added parameter
|
77
|
+
):
|
78
|
+
self.io = io
|
79
|
+
self.models = models
|
80
|
+
|
81
|
+
self.normalized_path = {}
|
82
|
+
self.tree_files = {}
|
83
|
+
|
84
|
+
self.attribute_author = attribute_author
|
85
|
+
self.attribute_committer = attribute_committer
|
86
|
+
self.attribute_commit_message_author = attribute_commit_message_author
|
87
|
+
self.attribute_commit_message_committer = attribute_commit_message_committer
|
88
|
+
self.attribute_co_authored_by = attribute_co_authored_by # Assign from parameter
|
89
|
+
self.commit_prompt = commit_prompt
|
90
|
+
self.subtree_only = subtree_only
|
91
|
+
self.git_commit_verify = git_commit_verify
|
92
|
+
self.ignore_file_cache = {}
|
93
|
+
|
94
|
+
if git_dname:
|
95
|
+
check_fnames = [git_dname]
|
96
|
+
elif fnames:
|
97
|
+
check_fnames = fnames
|
98
|
+
else:
|
99
|
+
check_fnames = ["."]
|
100
|
+
|
101
|
+
repo_paths = []
|
102
|
+
for fname in check_fnames:
|
103
|
+
fname = Path(fname)
|
104
|
+
fname = fname.resolve()
|
105
|
+
|
106
|
+
if not fname.exists() and fname.parent.exists():
|
107
|
+
fname = fname.parent
|
108
|
+
|
109
|
+
try:
|
110
|
+
repo_path = git.Repo(fname, search_parent_directories=True).working_dir
|
111
|
+
repo_path = utils.safe_abs_path(repo_path)
|
112
|
+
repo_paths.append(repo_path)
|
113
|
+
except ANY_GIT_ERROR:
|
114
|
+
pass
|
115
|
+
|
116
|
+
num_repos = len(set(repo_paths))
|
117
|
+
|
118
|
+
if num_repos == 0:
|
119
|
+
raise FileNotFoundError
|
120
|
+
if num_repos > 1:
|
121
|
+
self.io.tool_error("Files are in different git repos.")
|
122
|
+
raise FileNotFoundError
|
123
|
+
|
124
|
+
# https://github.com/gitpython-developers/GitPython/issues/427
|
125
|
+
self.repo = git.Repo(repo_paths.pop(), odbt=git.GitDB)
|
126
|
+
self.root = utils.safe_abs_path(self.repo.working_tree_dir)
|
127
|
+
|
128
|
+
if aider_ignore_file:
|
129
|
+
self.aider_ignore_file = Path(aider_ignore_file)
|
130
|
+
|
131
|
+
def commit(self, fnames=None, context=None, message=None, aider_edits=False, coder=None):
|
132
|
+
"""
|
133
|
+
Commit the specified files or all dirty files if none are specified.
|
134
|
+
|
135
|
+
Args:
|
136
|
+
fnames (list, optional): List of filenames to commit. Defaults to None (commit all
|
137
|
+
dirty files).
|
138
|
+
context (str, optional): Context for generating commit message. Defaults to None.
|
139
|
+
message (str, optional): Explicit commit message. Defaults to None (generate message).
|
140
|
+
aider_edits (bool, optional): Whether the changes were made by Aider. Defaults to False.
|
141
|
+
This affects attribution logic.
|
142
|
+
coder (Coder, optional): The Coder instance, used for config and model info.
|
143
|
+
Defaults to None.
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
tuple(str, str) or None: The commit hash and commit message if successful,
|
147
|
+
else None.
|
148
|
+
|
149
|
+
Attribution Logic:
|
150
|
+
------------------
|
151
|
+
This method handles Git commit attribution based on configuration flags and whether
|
152
|
+
Aider generated the changes (`aider_edits`).
|
153
|
+
|
154
|
+
Key Concepts:
|
155
|
+
- Author: The person who originally wrote the code changes.
|
156
|
+
- Committer: The person who last applied the commit to the repository.
|
157
|
+
- aider_edits=True: Changes were generated by Aider (LLM).
|
158
|
+
- aider_edits=False: Commit is user-driven (e.g., /commit manually staged changes).
|
159
|
+
- Explicit Setting: A flag (--attribute-...) is set to True or False
|
160
|
+
via command line or config file.
|
161
|
+
- Implicit Default: A flag is not explicitly set, defaulting to None in args, which is
|
162
|
+
interpreted as True unless overridden by other logic.
|
163
|
+
|
164
|
+
Flags:
|
165
|
+
- --attribute-author: Modify Author name to "User Name (aider)".
|
166
|
+
- --attribute-committer: Modify Committer name to "User Name (aider)".
|
167
|
+
- --attribute-co-authored-by: Add
|
168
|
+
"Co-authored-by: aider (<model>) <noreply@aider.chat>" trailer to commit message.
|
169
|
+
|
170
|
+
Behavior Summary:
|
171
|
+
|
172
|
+
1. When aider_edits = True (AI Changes):
|
173
|
+
- If --attribute-co-authored-by=True:
|
174
|
+
- Co-authored-by trailer IS ADDED.
|
175
|
+
- Author/Committer names are NOT modified by default (co-authored-by takes precedence).
|
176
|
+
- EXCEPTION: If --attribute-author/--attribute-committer is EXPLICITLY True, the
|
177
|
+
respective name IS modified (explicit overrides precedence).
|
178
|
+
- If --attribute-co-authored-by=False:
|
179
|
+
- Co-authored-by trailer is NOT added.
|
180
|
+
- Author/Committer names ARE modified by default (implicit True).
|
181
|
+
- EXCEPTION: If --attribute-author/--attribute-committer is EXPLICITLY False,
|
182
|
+
the respective name is NOT modified.
|
183
|
+
|
184
|
+
2. When aider_edits = False (User Changes):
|
185
|
+
- --attribute-co-authored-by is IGNORED (trailer never added).
|
186
|
+
- Author name is NEVER modified (--attribute-author ignored).
|
187
|
+
- Committer name IS modified by default (implicit True, as Aider runs `git commit`).
|
188
|
+
- EXCEPTION: If --attribute-committer is EXPLICITLY False, the name is NOT modified.
|
189
|
+
|
190
|
+
Resulting Scenarios:
|
191
|
+
- Standard AI edit (defaults): Co-authored-by=False -> Author=You(aider),
|
192
|
+
Committer=You(aider)
|
193
|
+
- AI edit with Co-authored-by (default): Co-authored-by=True -> Author=You,
|
194
|
+
Committer=You, Trailer added
|
195
|
+
- AI edit with Co-authored-by + Explicit Author: Co-authored-by=True,
|
196
|
+
--attribute-author -> Author=You(aider), Committer=You, Trailer added
|
197
|
+
- User commit (defaults): aider_edits=False -> Author=You, Committer=You(aider)
|
198
|
+
- User commit with explicit no-committer: aider_edits=False,
|
199
|
+
--no-attribute-committer -> Author=You, Committer=You
|
200
|
+
"""
|
201
|
+
if not fnames and not self.repo.is_dirty():
|
202
|
+
return
|
203
|
+
|
204
|
+
diffs = self.get_diffs(fnames)
|
205
|
+
if not diffs:
|
206
|
+
return
|
207
|
+
|
208
|
+
if message:
|
209
|
+
commit_message = message
|
210
|
+
else:
|
211
|
+
user_language = None
|
212
|
+
if coder:
|
213
|
+
user_language = coder.commit_language
|
214
|
+
if not user_language:
|
215
|
+
user_language = coder.get_user_language()
|
216
|
+
commit_message = self.get_commit_message(diffs, context, user_language)
|
217
|
+
|
218
|
+
# Retrieve attribute settings, prioritizing coder.args if available
|
219
|
+
if coder and hasattr(coder, "args"):
|
220
|
+
attribute_author = coder.args.attribute_author
|
221
|
+
attribute_committer = coder.args.attribute_committer
|
222
|
+
attribute_commit_message_author = coder.args.attribute_commit_message_author
|
223
|
+
attribute_commit_message_committer = coder.args.attribute_commit_message_committer
|
224
|
+
attribute_co_authored_by = coder.args.attribute_co_authored_by
|
225
|
+
else:
|
226
|
+
# Fallback to self attributes (initialized from config/defaults)
|
227
|
+
attribute_author = self.attribute_author
|
228
|
+
attribute_committer = self.attribute_committer
|
229
|
+
attribute_commit_message_author = self.attribute_commit_message_author
|
230
|
+
attribute_commit_message_committer = self.attribute_commit_message_committer
|
231
|
+
attribute_co_authored_by = self.attribute_co_authored_by
|
232
|
+
|
233
|
+
# Determine explicit settings (None means use default behavior)
|
234
|
+
author_explicit = attribute_author is not None
|
235
|
+
committer_explicit = attribute_committer is not None
|
236
|
+
|
237
|
+
# Determine effective settings (apply default True if not explicit)
|
238
|
+
effective_author = True if attribute_author is None else attribute_author
|
239
|
+
effective_committer = True if attribute_committer is None else attribute_committer
|
240
|
+
|
241
|
+
# Determine commit message prefixing
|
242
|
+
prefix_commit_message = aider_edits and (
|
243
|
+
attribute_commit_message_author or attribute_commit_message_committer
|
244
|
+
)
|
245
|
+
|
246
|
+
# Determine Co-authored-by trailer
|
247
|
+
commit_message_trailer = ""
|
248
|
+
if aider_edits and attribute_co_authored_by:
|
249
|
+
model_name = "unknown-model"
|
250
|
+
if coder and hasattr(coder, "main_model") and coder.main_model.name:
|
251
|
+
model_name = coder.main_model.name
|
252
|
+
commit_message_trailer = (
|
253
|
+
f"\n\nCo-authored-by: aider ({model_name}) <noreply@aider.chat>"
|
254
|
+
)
|
255
|
+
|
256
|
+
# Determine if author/committer names should be modified
|
257
|
+
# Author modification applies only to aider edits.
|
258
|
+
# It's used if effective_author is True AND
|
259
|
+
# (co-authored-by is False OR author was explicitly set).
|
260
|
+
use_attribute_author = (
|
261
|
+
aider_edits and effective_author and (not attribute_co_authored_by or author_explicit)
|
262
|
+
)
|
263
|
+
|
264
|
+
# Committer modification applies regardless of aider_edits (based on tests).
|
265
|
+
# It's used if effective_committer is True AND
|
266
|
+
# (it's not an aider edit with co-authored-by OR committer was explicitly set).
|
267
|
+
use_attribute_committer = effective_committer and (
|
268
|
+
not (aider_edits and attribute_co_authored_by) or committer_explicit
|
269
|
+
)
|
270
|
+
|
271
|
+
if not commit_message:
|
272
|
+
commit_message = "(no commit message provided)"
|
273
|
+
|
274
|
+
if prefix_commit_message:
|
275
|
+
commit_message = "aider: " + commit_message
|
276
|
+
|
277
|
+
full_commit_message = commit_message + commit_message_trailer
|
278
|
+
|
279
|
+
cmd = ["-m", full_commit_message]
|
280
|
+
if not self.git_commit_verify:
|
281
|
+
cmd.append("--no-verify")
|
282
|
+
if fnames:
|
283
|
+
fnames = [str(self.abs_root_path(fn)) for fn in fnames]
|
284
|
+
for fname in fnames:
|
285
|
+
try:
|
286
|
+
self.repo.git.add(fname)
|
287
|
+
except ANY_GIT_ERROR as err:
|
288
|
+
self.io.tool_error(f"Unable to add {fname}: {err}")
|
289
|
+
cmd += ["--"] + fnames
|
290
|
+
else:
|
291
|
+
cmd += ["-a"]
|
292
|
+
|
293
|
+
original_user_name = self.repo.git.config("--get", "user.name")
|
294
|
+
original_committer_name_env = os.environ.get("GIT_COMMITTER_NAME")
|
295
|
+
original_author_name_env = os.environ.get("GIT_AUTHOR_NAME")
|
296
|
+
committer_name = f"{original_user_name} (aider)"
|
297
|
+
|
298
|
+
try:
|
299
|
+
# Use context managers to handle environment variables
|
300
|
+
with contextlib.ExitStack() as stack:
|
301
|
+
if use_attribute_committer:
|
302
|
+
stack.enter_context(
|
303
|
+
set_git_env(
|
304
|
+
"GIT_COMMITTER_NAME", committer_name, original_committer_name_env
|
305
|
+
)
|
306
|
+
)
|
307
|
+
if use_attribute_author:
|
308
|
+
stack.enter_context(
|
309
|
+
set_git_env("GIT_AUTHOR_NAME", committer_name, original_author_name_env)
|
310
|
+
)
|
311
|
+
|
312
|
+
# Perform the commit
|
313
|
+
self.repo.git.commit(cmd)
|
314
|
+
commit_hash = self.get_head_commit_sha(short=True)
|
315
|
+
self.io.tool_output(f"Commit {commit_hash} {commit_message}", bold=True)
|
316
|
+
return commit_hash, commit_message
|
317
|
+
|
318
|
+
except ANY_GIT_ERROR as err:
|
319
|
+
self.io.tool_error(f"Unable to commit: {err}")
|
320
|
+
# No return here, implicitly returns None
|
321
|
+
|
322
|
+
def get_rel_repo_dir(self):
|
323
|
+
try:
|
324
|
+
return os.path.relpath(self.repo.git_dir, os.getcwd())
|
325
|
+
except (ValueError, OSError):
|
326
|
+
return self.repo.git_dir
|
327
|
+
|
328
|
+
def get_commit_message(self, diffs, context, user_language=None):
|
329
|
+
diffs = "# Diffs:\n" + diffs
|
330
|
+
|
331
|
+
content = ""
|
332
|
+
if context:
|
333
|
+
content += context + "\n"
|
334
|
+
content += diffs
|
335
|
+
|
336
|
+
system_content = self.commit_prompt or prompts.commit_system
|
337
|
+
|
338
|
+
language_instruction = ""
|
339
|
+
if user_language:
|
340
|
+
language_instruction = f"\n- Is written in {user_language}."
|
341
|
+
system_content = system_content.format(language_instruction=language_instruction)
|
342
|
+
|
343
|
+
commit_message = None
|
344
|
+
for model in self.models:
|
345
|
+
spinner_text = f"Generating commit message with {model.name}"
|
346
|
+
with WaitingSpinner(spinner_text):
|
347
|
+
if model.system_prompt_prefix:
|
348
|
+
current_system_content = model.system_prompt_prefix + "\n" + system_content
|
349
|
+
else:
|
350
|
+
current_system_content = system_content
|
351
|
+
|
352
|
+
messages = [
|
353
|
+
dict(role="system", content=current_system_content),
|
354
|
+
dict(role="user", content=content),
|
355
|
+
]
|
356
|
+
|
357
|
+
num_tokens = model.token_count(messages)
|
358
|
+
max_tokens = model.info.get("max_input_tokens") or 0
|
359
|
+
|
360
|
+
if max_tokens and num_tokens > max_tokens:
|
361
|
+
continue
|
362
|
+
|
363
|
+
commit_message = model.simple_send_with_retries(messages)
|
364
|
+
if commit_message:
|
365
|
+
break # Found a model that could generate the message
|
366
|
+
|
367
|
+
if not commit_message:
|
368
|
+
self.io.tool_error("Failed to generate commit message!")
|
369
|
+
return
|
370
|
+
|
371
|
+
commit_message = commit_message.strip()
|
372
|
+
if commit_message and commit_message[0] == '"' and commit_message[-1] == '"':
|
373
|
+
commit_message = commit_message[1:-1].strip()
|
374
|
+
|
375
|
+
return commit_message
|
376
|
+
|
377
|
+
def get_diffs(self, fnames=None):
|
378
|
+
# We always want diffs of index and working dir
|
379
|
+
|
380
|
+
current_branch_has_commits = False
|
381
|
+
try:
|
382
|
+
active_branch = self.repo.active_branch
|
383
|
+
try:
|
384
|
+
commits = self.repo.iter_commits(active_branch)
|
385
|
+
current_branch_has_commits = any(commits)
|
386
|
+
except ANY_GIT_ERROR:
|
387
|
+
pass
|
388
|
+
except (TypeError,) + ANY_GIT_ERROR:
|
389
|
+
pass
|
390
|
+
|
391
|
+
if not fnames:
|
392
|
+
fnames = []
|
393
|
+
|
394
|
+
diffs = ""
|
395
|
+
for fname in fnames:
|
396
|
+
if not self.path_in_repo(fname):
|
397
|
+
diffs += f"Added {fname}\n"
|
398
|
+
|
399
|
+
try:
|
400
|
+
if current_branch_has_commits:
|
401
|
+
args = ["HEAD", "--"] + list(fnames)
|
402
|
+
diffs += self.repo.git.diff(*args, stdout_as_string=False).decode(
|
403
|
+
self.io.encoding, "replace"
|
404
|
+
)
|
405
|
+
return diffs
|
406
|
+
|
407
|
+
wd_args = ["--"] + list(fnames)
|
408
|
+
index_args = ["--cached"] + wd_args
|
409
|
+
|
410
|
+
diffs += self.repo.git.diff(*index_args, stdout_as_string=False).decode(
|
411
|
+
self.io.encoding, "replace"
|
412
|
+
)
|
413
|
+
diffs += self.repo.git.diff(*wd_args, stdout_as_string=False).decode(
|
414
|
+
self.io.encoding, "replace"
|
415
|
+
)
|
416
|
+
|
417
|
+
return diffs
|
418
|
+
except ANY_GIT_ERROR as err:
|
419
|
+
self.io.tool_error(f"Unable to diff: {err}")
|
420
|
+
|
421
|
+
def diff_commits(self, pretty, from_commit, to_commit):
|
422
|
+
args = []
|
423
|
+
if pretty:
|
424
|
+
args += ["--color"]
|
425
|
+
else:
|
426
|
+
args += ["--color=never"]
|
427
|
+
|
428
|
+
args += [from_commit, to_commit]
|
429
|
+
diffs = self.repo.git.diff(*args, stdout_as_string=False).decode(
|
430
|
+
self.io.encoding, "replace"
|
431
|
+
)
|
432
|
+
|
433
|
+
return diffs
|
434
|
+
|
435
|
+
def get_tracked_files(self):
|
436
|
+
if not self.repo:
|
437
|
+
return []
|
438
|
+
|
439
|
+
try:
|
440
|
+
commit = self.repo.head.commit
|
441
|
+
except ValueError:
|
442
|
+
commit = None
|
443
|
+
except ANY_GIT_ERROR as err:
|
444
|
+
self.git_repo_error = err
|
445
|
+
self.io.tool_error(f"Unable to list files in git repo: {err}")
|
446
|
+
self.io.tool_output("Is your git repo corrupted?")
|
447
|
+
return []
|
448
|
+
|
449
|
+
files = set()
|
450
|
+
if commit:
|
451
|
+
if commit in self.tree_files:
|
452
|
+
files = self.tree_files[commit]
|
453
|
+
else:
|
454
|
+
try:
|
455
|
+
iterator = commit.tree.traverse()
|
456
|
+
blob = None # Initialize blob
|
457
|
+
while True:
|
458
|
+
try:
|
459
|
+
blob = next(iterator)
|
460
|
+
if blob.type == "blob": # blob is a file
|
461
|
+
files.add(blob.path)
|
462
|
+
except IndexError:
|
463
|
+
# Handle potential index error during tree traversal
|
464
|
+
# without relying on potentially unassigned 'blob'
|
465
|
+
self.io.tool_warning(
|
466
|
+
"GitRepo: Index error encountered while reading git tree object."
|
467
|
+
" Skipping."
|
468
|
+
)
|
469
|
+
continue
|
470
|
+
except StopIteration:
|
471
|
+
break
|
472
|
+
except ANY_GIT_ERROR as err:
|
473
|
+
self.git_repo_error = err
|
474
|
+
self.io.tool_error(f"Unable to list files in git repo: {err}")
|
475
|
+
self.io.tool_output("Is your git repo corrupted?")
|
476
|
+
return []
|
477
|
+
files = set(self.normalize_path(path) for path in files)
|
478
|
+
self.tree_files[commit] = set(files)
|
479
|
+
|
480
|
+
# Add staged files
|
481
|
+
index = self.repo.index
|
482
|
+
try:
|
483
|
+
staged_files = [path for path, _ in index.entries.keys()]
|
484
|
+
files.update(self.normalize_path(path) for path in staged_files)
|
485
|
+
except ANY_GIT_ERROR as err:
|
486
|
+
self.io.tool_error(f"Unable to read staged files: {err}")
|
487
|
+
|
488
|
+
res = [fname for fname in files if not self.ignored_file(fname)]
|
489
|
+
|
490
|
+
return res
|
491
|
+
|
492
|
+
def normalize_path(self, path):
|
493
|
+
orig_path = path
|
494
|
+
res = self.normalized_path.get(orig_path)
|
495
|
+
if res:
|
496
|
+
return res
|
497
|
+
|
498
|
+
path = str(Path(PurePosixPath((Path(self.root) / path).relative_to(self.root))))
|
499
|
+
self.normalized_path[orig_path] = path
|
500
|
+
return path
|
501
|
+
|
502
|
+
def refresh_aider_ignore(self):
|
503
|
+
if not self.aider_ignore_file:
|
504
|
+
return
|
505
|
+
|
506
|
+
current_time = time.time()
|
507
|
+
if current_time - self.aider_ignore_last_check < 1:
|
508
|
+
return
|
509
|
+
|
510
|
+
self.aider_ignore_last_check = current_time
|
511
|
+
|
512
|
+
if not self.aider_ignore_file.is_file():
|
513
|
+
return
|
514
|
+
|
515
|
+
mtime = self.aider_ignore_file.stat().st_mtime
|
516
|
+
if mtime != self.aider_ignore_ts:
|
517
|
+
self.aider_ignore_ts = mtime
|
518
|
+
self.ignore_file_cache = {}
|
519
|
+
lines = self.aider_ignore_file.read_text().splitlines()
|
520
|
+
self.aider_ignore_spec = pathspec.PathSpec.from_lines(
|
521
|
+
pathspec.patterns.GitWildMatchPattern,
|
522
|
+
lines,
|
523
|
+
)
|
524
|
+
|
525
|
+
def git_ignored_file(self, path):
|
526
|
+
if not self.repo:
|
527
|
+
return
|
528
|
+
try:
|
529
|
+
if self.repo.ignored(path):
|
530
|
+
return True
|
531
|
+
except ANY_GIT_ERROR:
|
532
|
+
return False
|
533
|
+
|
534
|
+
def ignored_file(self, fname):
|
535
|
+
self.refresh_aider_ignore()
|
536
|
+
|
537
|
+
if fname in self.ignore_file_cache:
|
538
|
+
return self.ignore_file_cache[fname]
|
539
|
+
|
540
|
+
result = self.ignored_file_raw(fname)
|
541
|
+
self.ignore_file_cache[fname] = result
|
542
|
+
return result
|
543
|
+
|
544
|
+
def ignored_file_raw(self, fname):
|
545
|
+
if self.subtree_only:
|
546
|
+
try:
|
547
|
+
fname_path = Path(self.normalize_path(fname))
|
548
|
+
cwd_path = Path.cwd().resolve().relative_to(Path(self.root).resolve())
|
549
|
+
except ValueError:
|
550
|
+
# Issue #1524
|
551
|
+
# ValueError: 'C:\\dev\\squid-certbot' is not in the subpath of
|
552
|
+
# 'C:\\dev\\squid-certbot'
|
553
|
+
# Clearly, fname is not under cwd... so ignore it
|
554
|
+
return True
|
555
|
+
|
556
|
+
if cwd_path not in fname_path.parents and fname_path != cwd_path:
|
557
|
+
return True
|
558
|
+
|
559
|
+
if not self.aider_ignore_file or not self.aider_ignore_file.is_file():
|
560
|
+
return False
|
561
|
+
|
562
|
+
try:
|
563
|
+
fname = self.normalize_path(fname)
|
564
|
+
except ValueError:
|
565
|
+
return True
|
566
|
+
|
567
|
+
return self.aider_ignore_spec.match_file(fname)
|
568
|
+
|
569
|
+
def path_in_repo(self, path):
|
570
|
+
if not self.repo:
|
571
|
+
return
|
572
|
+
if not path:
|
573
|
+
return
|
574
|
+
|
575
|
+
tracked_files = set(self.get_tracked_files())
|
576
|
+
return self.normalize_path(path) in tracked_files
|
577
|
+
|
578
|
+
def abs_root_path(self, path):
|
579
|
+
res = Path(self.root) / path
|
580
|
+
return utils.safe_abs_path(res)
|
581
|
+
|
582
|
+
def get_dirty_files(self):
|
583
|
+
"""
|
584
|
+
Returns a list of all files which are dirty (not committed), either staged or in the working
|
585
|
+
directory.
|
586
|
+
"""
|
587
|
+
dirty_files = set()
|
588
|
+
|
589
|
+
# Get staged files
|
590
|
+
staged_files = self.repo.git.diff("--name-only", "--cached").splitlines()
|
591
|
+
dirty_files.update(staged_files)
|
592
|
+
|
593
|
+
# Get unstaged files
|
594
|
+
unstaged_files = self.repo.git.diff("--name-only").splitlines()
|
595
|
+
dirty_files.update(unstaged_files)
|
596
|
+
|
597
|
+
return list(dirty_files)
|
598
|
+
|
599
|
+
def is_dirty(self, path=None):
|
600
|
+
if path and not self.path_in_repo(path):
|
601
|
+
return True
|
602
|
+
|
603
|
+
return self.repo.is_dirty(path=path)
|
604
|
+
|
605
|
+
def get_head_commit(self):
|
606
|
+
try:
|
607
|
+
return self.repo.head.commit
|
608
|
+
except (ValueError,) + ANY_GIT_ERROR:
|
609
|
+
return None
|
610
|
+
|
611
|
+
def get_head_commit_sha(self, short=False):
|
612
|
+
commit = self.get_head_commit()
|
613
|
+
if not commit:
|
614
|
+
return
|
615
|
+
if short:
|
616
|
+
return commit.hexsha[:7]
|
617
|
+
return commit.hexsha
|
618
|
+
|
619
|
+
def get_head_commit_message(self, default=None):
|
620
|
+
commit = self.get_head_commit()
|
621
|
+
if not commit:
|
622
|
+
return default
|
623
|
+
return commit.message
|