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.
Files changed (228) hide show
  1. aider/__init__.py +20 -0
  2. aider/__main__.py +4 -0
  3. aider/_version.py +21 -0
  4. aider/analytics.py +250 -0
  5. aider/args.py +926 -0
  6. aider/args_formatter.py +228 -0
  7. aider/coders/__init__.py +34 -0
  8. aider/coders/architect_coder.py +48 -0
  9. aider/coders/architect_prompts.py +40 -0
  10. aider/coders/ask_coder.py +9 -0
  11. aider/coders/ask_prompts.py +35 -0
  12. aider/coders/base_coder.py +2483 -0
  13. aider/coders/base_prompts.py +60 -0
  14. aider/coders/chat_chunks.py +64 -0
  15. aider/coders/context_coder.py +53 -0
  16. aider/coders/context_prompts.py +75 -0
  17. aider/coders/editblock_coder.py +657 -0
  18. aider/coders/editblock_fenced_coder.py +10 -0
  19. aider/coders/editblock_fenced_prompts.py +143 -0
  20. aider/coders/editblock_func_coder.py +141 -0
  21. aider/coders/editblock_func_prompts.py +27 -0
  22. aider/coders/editblock_prompts.py +174 -0
  23. aider/coders/editor_diff_fenced_coder.py +9 -0
  24. aider/coders/editor_diff_fenced_prompts.py +11 -0
  25. aider/coders/editor_editblock_coder.py +8 -0
  26. aider/coders/editor_editblock_prompts.py +18 -0
  27. aider/coders/editor_whole_coder.py +8 -0
  28. aider/coders/editor_whole_prompts.py +10 -0
  29. aider/coders/help_coder.py +16 -0
  30. aider/coders/help_prompts.py +46 -0
  31. aider/coders/patch_coder.py +706 -0
  32. aider/coders/patch_prompts.py +161 -0
  33. aider/coders/search_replace.py +757 -0
  34. aider/coders/shell.py +37 -0
  35. aider/coders/single_wholefile_func_coder.py +102 -0
  36. aider/coders/single_wholefile_func_prompts.py +27 -0
  37. aider/coders/udiff_coder.py +429 -0
  38. aider/coders/udiff_prompts.py +115 -0
  39. aider/coders/udiff_simple.py +14 -0
  40. aider/coders/udiff_simple_prompts.py +25 -0
  41. aider/coders/wholefile_coder.py +144 -0
  42. aider/coders/wholefile_func_coder.py +134 -0
  43. aider/coders/wholefile_func_prompts.py +27 -0
  44. aider/coders/wholefile_prompts.py +67 -0
  45. aider/commands.py +1665 -0
  46. aider/copypaste.py +72 -0
  47. aider/deprecated.py +126 -0
  48. aider/diffs.py +128 -0
  49. aider/dump.py +29 -0
  50. aider/editor.py +147 -0
  51. aider/exceptions.py +107 -0
  52. aider/format_settings.py +26 -0
  53. aider/gui.py +545 -0
  54. aider/help.py +163 -0
  55. aider/help_pats.py +19 -0
  56. aider/history.py +143 -0
  57. aider/io.py +1175 -0
  58. aider/linter.py +304 -0
  59. aider/llm.py +47 -0
  60. aider/main.py +1267 -0
  61. aider/mdstream.py +243 -0
  62. aider/models.py +1286 -0
  63. aider/onboarding.py +428 -0
  64. aider/openrouter.py +128 -0
  65. aider/prompts.py +64 -0
  66. aider/queries/tree-sitter-language-pack/README.md +7 -0
  67. aider/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  68. aider/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  69. aider/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  70. aider/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  71. aider/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  72. aider/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  73. aider/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  74. aider/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  75. aider/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  76. aider/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  77. aider/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  78. aider/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  79. aider/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  80. aider/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  81. aider/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  82. aider/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  83. aider/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  84. aider/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  85. aider/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  86. aider/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  87. aider/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  88. aider/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  89. aider/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  90. aider/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  91. aider/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  92. aider/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  93. aider/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  94. aider/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  95. aider/queries/tree-sitter-languages/README.md +23 -0
  96. aider/queries/tree-sitter-languages/c-tags.scm +9 -0
  97. aider/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  98. aider/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  99. aider/queries/tree-sitter-languages/dart-tags.scm +91 -0
  100. aider/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  101. aider/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  102. aider/queries/tree-sitter-languages/elm-tags.scm +19 -0
  103. aider/queries/tree-sitter-languages/go-tags.scm +30 -0
  104. aider/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  105. aider/queries/tree-sitter-languages/java-tags.scm +20 -0
  106. aider/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  107. aider/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  108. aider/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  109. aider/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  110. aider/queries/tree-sitter-languages/php-tags.scm +26 -0
  111. aider/queries/tree-sitter-languages/python-tags.scm +12 -0
  112. aider/queries/tree-sitter-languages/ql-tags.scm +26 -0
  113. aider/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  114. aider/queries/tree-sitter-languages/rust-tags.scm +60 -0
  115. aider/queries/tree-sitter-languages/scala-tags.scm +65 -0
  116. aider/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  117. aider/reasoning_tags.py +82 -0
  118. aider/repo.py +623 -0
  119. aider/repomap.py +847 -0
  120. aider/report.py +200 -0
  121. aider/resources/__init__.py +3 -0
  122. aider/resources/model-metadata.json +468 -0
  123. aider/resources/model-settings.yml +1767 -0
  124. aider/run_cmd.py +132 -0
  125. aider/scrape.py +284 -0
  126. aider/sendchat.py +61 -0
  127. aider/special.py +203 -0
  128. aider/urls.py +17 -0
  129. aider/utils.py +338 -0
  130. aider/versioncheck.py +113 -0
  131. aider/voice.py +187 -0
  132. aider/waiting.py +221 -0
  133. aider/watch.py +318 -0
  134. aider/watch_prompts.py +12 -0
  135. aider/website/Gemfile +8 -0
  136. aider/website/_includes/blame.md +162 -0
  137. aider/website/_includes/get-started.md +22 -0
  138. aider/website/_includes/help-tip.md +5 -0
  139. aider/website/_includes/help.md +24 -0
  140. aider/website/_includes/install.md +5 -0
  141. aider/website/_includes/keys.md +4 -0
  142. aider/website/_includes/model-warnings.md +67 -0
  143. aider/website/_includes/multi-line.md +22 -0
  144. aider/website/_includes/python-m-aider.md +5 -0
  145. aider/website/_includes/recording.css +228 -0
  146. aider/website/_includes/recording.md +34 -0
  147. aider/website/_includes/replit-pipx.md +9 -0
  148. aider/website/_includes/works-best.md +1 -0
  149. aider/website/_sass/custom/custom.scss +103 -0
  150. aider/website/docs/config/adv-model-settings.md +1881 -0
  151. aider/website/docs/config/aider_conf.md +527 -0
  152. aider/website/docs/config/api-keys.md +90 -0
  153. aider/website/docs/config/dotenv.md +478 -0
  154. aider/website/docs/config/editor.md +127 -0
  155. aider/website/docs/config/model-aliases.md +103 -0
  156. aider/website/docs/config/options.md +843 -0
  157. aider/website/docs/config/reasoning.md +209 -0
  158. aider/website/docs/config.md +44 -0
  159. aider/website/docs/faq.md +378 -0
  160. aider/website/docs/git.md +76 -0
  161. aider/website/docs/index.md +47 -0
  162. aider/website/docs/install/codespaces.md +39 -0
  163. aider/website/docs/install/docker.md +57 -0
  164. aider/website/docs/install/optional.md +100 -0
  165. aider/website/docs/install/replit.md +8 -0
  166. aider/website/docs/install.md +115 -0
  167. aider/website/docs/languages.md +264 -0
  168. aider/website/docs/legal/contributor-agreement.md +111 -0
  169. aider/website/docs/legal/privacy.md +104 -0
  170. aider/website/docs/llms/anthropic.md +77 -0
  171. aider/website/docs/llms/azure.md +48 -0
  172. aider/website/docs/llms/bedrock.md +132 -0
  173. aider/website/docs/llms/cohere.md +34 -0
  174. aider/website/docs/llms/deepseek.md +32 -0
  175. aider/website/docs/llms/gemini.md +49 -0
  176. aider/website/docs/llms/github.md +105 -0
  177. aider/website/docs/llms/groq.md +36 -0
  178. aider/website/docs/llms/lm-studio.md +39 -0
  179. aider/website/docs/llms/ollama.md +75 -0
  180. aider/website/docs/llms/openai-compat.md +39 -0
  181. aider/website/docs/llms/openai.md +58 -0
  182. aider/website/docs/llms/openrouter.md +78 -0
  183. aider/website/docs/llms/other.md +103 -0
  184. aider/website/docs/llms/vertex.md +50 -0
  185. aider/website/docs/llms/warnings.md +10 -0
  186. aider/website/docs/llms/xai.md +53 -0
  187. aider/website/docs/llms.md +54 -0
  188. aider/website/docs/more/analytics.md +122 -0
  189. aider/website/docs/more/edit-formats.md +116 -0
  190. aider/website/docs/more/infinite-output.md +137 -0
  191. aider/website/docs/more-info.md +8 -0
  192. aider/website/docs/recordings/auto-accept-architect.md +31 -0
  193. aider/website/docs/recordings/dont-drop-original-read-files.md +35 -0
  194. aider/website/docs/recordings/index.md +21 -0
  195. aider/website/docs/recordings/model-accepts-settings.md +69 -0
  196. aider/website/docs/recordings/tree-sitter-language-pack.md +80 -0
  197. aider/website/docs/repomap.md +112 -0
  198. aider/website/docs/scripting.md +100 -0
  199. aider/website/docs/troubleshooting/aider-not-found.md +24 -0
  200. aider/website/docs/troubleshooting/edit-errors.md +76 -0
  201. aider/website/docs/troubleshooting/imports.md +62 -0
  202. aider/website/docs/troubleshooting/models-and-keys.md +54 -0
  203. aider/website/docs/troubleshooting/support.md +79 -0
  204. aider/website/docs/troubleshooting/token-limits.md +96 -0
  205. aider/website/docs/troubleshooting/warnings.md +12 -0
  206. aider/website/docs/troubleshooting.md +11 -0
  207. aider/website/docs/usage/browser.md +57 -0
  208. aider/website/docs/usage/caching.md +49 -0
  209. aider/website/docs/usage/commands.md +132 -0
  210. aider/website/docs/usage/conventions.md +119 -0
  211. aider/website/docs/usage/copypaste.md +121 -0
  212. aider/website/docs/usage/images-urls.md +48 -0
  213. aider/website/docs/usage/lint-test.md +118 -0
  214. aider/website/docs/usage/modes.md +211 -0
  215. aider/website/docs/usage/not-code.md +179 -0
  216. aider/website/docs/usage/notifications.md +87 -0
  217. aider/website/docs/usage/tips.md +79 -0
  218. aider/website/docs/usage/tutorials.md +30 -0
  219. aider/website/docs/usage/voice.md +121 -0
  220. aider/website/docs/usage/watch.md +294 -0
  221. aider/website/docs/usage.md +92 -0
  222. aider/website/share/index.md +101 -0
  223. chatmcp_cli-0.1.0.dist-info/METADATA +502 -0
  224. chatmcp_cli-0.1.0.dist-info/RECORD +228 -0
  225. chatmcp_cli-0.1.0.dist-info/WHEEL +5 -0
  226. chatmcp_cli-0.1.0.dist-info/entry_points.txt +3 -0
  227. chatmcp_cli-0.1.0.dist-info/licenses/LICENSE.txt +202 -0
  228. 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