ace-git-copilot 0.3.2__tar.gz → 0.3.3__tar.gz
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.
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/PKG-INFO +13 -1
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/README.md +12 -0
- ace_git_copilot-0.3.3/ace/__init__.py +1 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/conflict_resolver.py +35 -4
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/llm_factory.py +26 -8
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/cli.py +3 -2
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/core/config.py +12 -3
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/core/context.py +5 -1
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/core/git_ops.py +33 -7
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/pyproject.toml +1 -1
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/e2e/test_tier2_boundaries.py +7 -0
- ace_git_copilot-0.3.3/tests/test_config_atomic_write.py +41 -0
- ace_git_copilot-0.3.3/tests/test_conflict_resolver_backup.py +49 -0
- ace_git_copilot-0.3.3/tests/test_context_gitdir_detection.py +45 -0
- ace_git_copilot-0.3.3/tests/test_git_ops_execute.py +41 -0
- ace_git_copilot-0.3.3/tests/test_llm_factory_ollama.py +48 -0
- ace_git_copilot-0.3.2/ace/__init__.py +0 -1
- ace_git_copilot-0.3.2/importtime.txt +0 -0
- ace_git_copilot-0.3.2/importtime_optimized.txt +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/AGENTS.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/e2e_testing_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/e2e_testing_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/e2e_testing_track/SCOPE.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/e2e_testing_track/progress.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/emojis_list.txt +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/find_unused_modules.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/handoff.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/measure_lazy_startup.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/measure_startup.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/profile_imports.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/progress.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/run_importtime.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/search_banner.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/search_emojis.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/search_git_usages.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/search_usages.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/test_import_profiler.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/test_mocked_sys.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/handoff.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/implementation_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/implementation_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/implementation_track/explorer_initial_report.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/implementation_track/progress.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/orchestrator/.gitkeep +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/orchestrator/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/orchestrator/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/orchestrator/PROJECT.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/orchestrator/progress.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/teamwork_preview_explorer_e2e_explore/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/teamwork_preview_explorer_e2e_explore/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/teamwork_preview_explorer_e2e_explore/handoff.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/teamwork_preview_explorer_e2e_explore/progress.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/worker_e2e_testing/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/worker_e2e_testing/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/worker_e2e_testing/progress.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/worker_m1_startup/BRIEFING.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/worker_m1_startup/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/worker_m1_startup/progress.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.env.example +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.github/workflows/tests.yml +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.gitignore +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/CODE_OF_CONDUCT.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/CONTRIBUTING.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/LICENSE +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/PROJECT.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/SECURITY.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/SUPPORT.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/TEST_INFRA.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/TEST_READY.md +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/__main__.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/changelog_generator.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/code_reviewer.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/commit_generator.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/gitignore_generator.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/history_analyzer.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/intent_parser.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/pr_drafter.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/changelog.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/commit.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/conflict.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/doctor.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/explain.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/ignore.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/intent.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/pr.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/rebase.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/review.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/search.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/prompts/undo.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ai/rebase_helper.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/core/diagnostics.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/core/hooks.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/core/safety.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ui/banner.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ui/dashboard.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ui/display.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ui/prompts.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/ui/themes.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/utils/conflict_parser.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/utils/diff_parser.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/ace/utils/json_utils.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/conftest.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/e2e/conftest.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/e2e/test_tier1_features.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/e2e/test_tier3_combinations.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/e2e/test_tier4_workloads.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_changelog_generator.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_code_reviewer.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_conflict_resolver.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_diagnostics.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_diff_trimmer.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_git_ops.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_help.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_history_analyzer.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_hooks.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_ignore.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_intent_parser.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_llm_factory.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_pr_drafter.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_rebase_helper.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_safety.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_search.py +0 -0
- {ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/tests/test_undo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ace-git-copilot
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: AI-powered Git copilot — talk to Git in plain English
|
|
5
5
|
Project-URL: Homepage, https://github.com/jachinsamuel/Ace
|
|
6
6
|
Project-URL: Documentation, https://github.com/jachinsamuel/Ace#readme
|
|
@@ -198,6 +198,18 @@ Distributed under the MIT License. See [LICENSE](LICENSE) for more details.
|
|
|
198
198
|
|
|
199
199
|
## Changelog
|
|
200
200
|
|
|
201
|
+
### v0.3.3 — Stability & Resilience (2026-07-04)
|
|
202
|
+
* Implemented streaming progress and download percentages for Ollama model pulls to prevent thread hangs.
|
|
203
|
+
* Added atomic file saves and rollback backups for merge conflict resolutions and config modifications to prevent data corruption.
|
|
204
|
+
* Fixed merge/rebase detection in git worktrees and submodules by resolving the authoritative `git_dir` dynamically.
|
|
205
|
+
* Added `shlex` command splitting to safely parse and execute quoted arguments in Git commands.
|
|
206
|
+
* Created python module entry point (`python -m ace`) for nested execution support.
|
|
207
|
+
|
|
208
|
+
### v0.3.2 — Windows Compatibility & E2E Fixes (2026-07-01)
|
|
209
|
+
* Resolved Windows-specific UTF-8 encoding issues in E2E tests and log parsers.
|
|
210
|
+
* Fixed subprocess natural language command execution paths to execute nested ace commands through Python interpreter contexts.
|
|
211
|
+
* Refactored CLI error panel formatting to match expected exception names.
|
|
212
|
+
|
|
201
213
|
### v0.3.1 — Patch (2026-06-30)
|
|
202
214
|
* Fixed invisible key labels (`[c]`, `[r]`, etc.) in the dashboard and search menus caused by Rich markup tag conflicts.
|
|
203
215
|
* Fixed `[Y/n]` confirmation prompt rendering invisibly.
|
|
@@ -165,6 +165,18 @@ Distributed under the MIT License. See [LICENSE](LICENSE) for more details.
|
|
|
165
165
|
|
|
166
166
|
## Changelog
|
|
167
167
|
|
|
168
|
+
### v0.3.3 — Stability & Resilience (2026-07-04)
|
|
169
|
+
* Implemented streaming progress and download percentages for Ollama model pulls to prevent thread hangs.
|
|
170
|
+
* Added atomic file saves and rollback backups for merge conflict resolutions and config modifications to prevent data corruption.
|
|
171
|
+
* Fixed merge/rebase detection in git worktrees and submodules by resolving the authoritative `git_dir` dynamically.
|
|
172
|
+
* Added `shlex` command splitting to safely parse and execute quoted arguments in Git commands.
|
|
173
|
+
* Created python module entry point (`python -m ace`) for nested execution support.
|
|
174
|
+
|
|
175
|
+
### v0.3.2 — Windows Compatibility & E2E Fixes (2026-07-01)
|
|
176
|
+
* Resolved Windows-specific UTF-8 encoding issues in E2E tests and log parsers.
|
|
177
|
+
* Fixed subprocess natural language command execution paths to execute nested ace commands through Python interpreter contexts.
|
|
178
|
+
* Refactored CLI error panel formatting to match expected exception names.
|
|
179
|
+
|
|
168
180
|
### v0.3.1 — Patch (2026-06-30)
|
|
169
181
|
* Fixed invisible key labels (`[c]`, `[r]`, etc.) in the dashboard and search menus caused by Rich markup tag conflicts.
|
|
170
182
|
* Fixed `[Y/n]` confirmation prompt rendering invisibly.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.3"
|
|
@@ -83,10 +83,15 @@ class ConflictResolver:
|
|
|
83
83
|
|
|
84
84
|
def apply_resolution(self, file_path: str, block_replacements: List[Tuple[str, str]]) -> None:
|
|
85
85
|
"""
|
|
86
|
-
Apply resolutions to a conflicted file.
|
|
86
|
+
Apply resolutions to a conflicted file safely.
|
|
87
87
|
|
|
88
88
|
block_replacements: List of tuples (full_conflict_block, replacement_content)
|
|
89
89
|
"""
|
|
90
|
+
import shutil
|
|
91
|
+
import tempfile
|
|
92
|
+
import os
|
|
93
|
+
import time
|
|
94
|
+
|
|
90
95
|
full_path = Path(self.git_ops.working_dir) / file_path
|
|
91
96
|
if not full_path.exists():
|
|
92
97
|
raise FileNotFoundError(f"File not found: {file_path}")
|
|
@@ -95,13 +100,13 @@ class ConflictResolver:
|
|
|
95
100
|
|
|
96
101
|
for full_block, replacement in block_replacements:
|
|
97
102
|
if full_block in content:
|
|
98
|
-
content = content.replace(full_block, replacement)
|
|
103
|
+
content = content.replace(full_block, replacement, 1)
|
|
99
104
|
else:
|
|
100
105
|
# Try with normalized line endings
|
|
101
106
|
norm_block = full_block.replace("\r\n", "\n")
|
|
102
107
|
norm_content = content.replace("\r\n", "\n")
|
|
103
108
|
if norm_block in norm_content:
|
|
104
|
-
norm_content = norm_content.replace(norm_block, replacement)
|
|
109
|
+
norm_content = norm_content.replace(norm_block, replacement, 1)
|
|
105
110
|
# Restore Windows line endings if they were originally present
|
|
106
111
|
if "\r\n" in content:
|
|
107
112
|
content = norm_content.replace("\n", "\r\n")
|
|
@@ -112,4 +117,30 @@ class ConflictResolver:
|
|
|
112
117
|
"Conflict block not found in file. Has it been edited already?"
|
|
113
118
|
)
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
# Create backup copy
|
|
121
|
+
backup_path = full_path.with_suffix(full_path.suffix + f".bak-{int(time.time())}")
|
|
122
|
+
try:
|
|
123
|
+
shutil.copy2(full_path, backup_path)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
raise ConflictResolverError(f"Failed to create backup copy of {file_path}: {e}")
|
|
126
|
+
|
|
127
|
+
# Atomic replacement
|
|
128
|
+
try:
|
|
129
|
+
fd, temp_path_str = tempfile.mkstemp(dir=full_path.parent, prefix="resolved-", suffix=".tmp")
|
|
130
|
+
temp_path = Path(temp_path_str)
|
|
131
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
132
|
+
f.write(content)
|
|
133
|
+
os.replace(temp_path, full_path)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
# Restore from backup
|
|
136
|
+
try:
|
|
137
|
+
shutil.copy2(backup_path, full_path)
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
raise ConflictResolverError(f"Failed to apply conflict resolution to {file_path}: {e}")
|
|
141
|
+
finally:
|
|
142
|
+
if backup_path.exists():
|
|
143
|
+
try:
|
|
144
|
+
backup_path.unlink()
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
@@ -51,17 +51,35 @@ def ensure_ollama_model(base_url: str, model_name: str) -> None:
|
|
|
51
51
|
if confirm(f"Would you like Ace to automatically pull '{model_name}' from the Ollama registry?", default=True):
|
|
52
52
|
try:
|
|
53
53
|
url = f"{base_url.rstrip('/')}/api/pull"
|
|
54
|
-
payload = json.dumps({"name": model_name, "stream":
|
|
54
|
+
payload = json.dumps({"name": model_name, "stream": True}).encode("utf-8")
|
|
55
55
|
req = urllib.request.Request(url, data=payload, method="POST")
|
|
56
56
|
req.add_header("Content-Type", "application/json")
|
|
57
57
|
|
|
58
|
-
with spinner(f"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
with spinner(f"Initiating download of model '{model_name}'..."):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
with urllib.request.urlopen(req, timeout=60) as response:
|
|
62
|
+
import sys
|
|
63
|
+
for line in response:
|
|
64
|
+
if not line.strip():
|
|
65
|
+
continue
|
|
66
|
+
try:
|
|
67
|
+
data = json.loads(line.decode("utf-8"))
|
|
68
|
+
status = data.get("status", "")
|
|
69
|
+
completed = data.get("completed", 0)
|
|
70
|
+
total = data.get("total", 0)
|
|
71
|
+
if total > 0:
|
|
72
|
+
pct = (completed / total) * 100
|
|
73
|
+
sys.stdout.write(f"\r\033[K[Ollama] {status} ({pct:.1f}%)")
|
|
74
|
+
sys.stdout.flush()
|
|
75
|
+
else:
|
|
76
|
+
sys.stdout.write(f"\r\033[K[Ollama] {status}")
|
|
77
|
+
sys.stdout.flush()
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
sys.stdout.write("\n")
|
|
81
|
+
sys.stdout.flush()
|
|
82
|
+
print_success(f"Successfully downloaded '{model_name}'!\n")
|
|
65
83
|
except Exception as e:
|
|
66
84
|
print_error(f"Failed to pull model: {e}")
|
|
67
85
|
print_info(f"Please run 'ollama pull {model_name}' manually in your shell.\n")
|
|
@@ -193,9 +193,10 @@ def main(
|
|
|
193
193
|
try:
|
|
194
194
|
import subprocess
|
|
195
195
|
import sys
|
|
196
|
-
|
|
196
|
+
import shlex
|
|
197
|
+
args = shlex.split(cmd)[1:]
|
|
197
198
|
res_proc = subprocess.run(
|
|
198
|
-
[sys.executable, "-
|
|
199
|
+
[sys.executable, "-m", "ace"] + args,
|
|
199
200
|
stdout=subprocess.PIPE,
|
|
200
201
|
stderr=subprocess.PIPE,
|
|
201
202
|
text=True,
|
|
@@ -154,10 +154,19 @@ def get_config() -> Config:
|
|
|
154
154
|
return Config(data)
|
|
155
155
|
|
|
156
156
|
def save_config(config: Config) -> None:
|
|
157
|
-
"""Save the current configuration back to ~/.ace/config.toml."""
|
|
157
|
+
"""Save the current configuration back to ~/.ace/config.toml atomically."""
|
|
158
|
+
import tempfile
|
|
159
|
+
import os
|
|
158
160
|
try:
|
|
159
161
|
DEFAULT_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
fd, temp_path = tempfile.mkstemp(dir=DEFAULT_CONFIG_DIR, prefix="config-", suffix=".tmp")
|
|
163
|
+
try:
|
|
164
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
165
|
+
toml.dump(config.to_dict(), f)
|
|
166
|
+
os.replace(temp_path, DEFAULT_CONFIG_PATH)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
if os.path.exists(temp_path):
|
|
169
|
+
os.unlink(temp_path)
|
|
170
|
+
raise e
|
|
162
171
|
except Exception as e:
|
|
163
172
|
raise IOError(f"Could not save configuration: {e}")
|
|
@@ -61,7 +61,11 @@ class RepoContext:
|
|
|
61
61
|
|
|
62
62
|
def check_merge_rebase_state(self) -> Dict[str, Any]:
|
|
63
63
|
"""Check if the repository is currently in a merge, rebase, or cherry-pick state."""
|
|
64
|
-
|
|
64
|
+
try:
|
|
65
|
+
git_dir = Path(self.git_ops.repo.git_dir)
|
|
66
|
+
except Exception:
|
|
67
|
+
git_dir = Path(self.git_ops.working_dir) / ".git"
|
|
68
|
+
|
|
65
69
|
state = {
|
|
66
70
|
"in_progress": False,
|
|
67
71
|
"type": None, # 'merge', 'rebase', 'cherry-pick', 'revert'
|
|
@@ -118,7 +118,13 @@ class GitOps:
|
|
|
118
118
|
def get_branches(self, remote: bool = False) -> List[str]:
|
|
119
119
|
"""List local or remote branches."""
|
|
120
120
|
if remote:
|
|
121
|
-
|
|
121
|
+
branches = []
|
|
122
|
+
for r in self.repo.remotes:
|
|
123
|
+
try:
|
|
124
|
+
branches.extend([b.name for b in r.refs])
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
127
|
+
return branches
|
|
122
128
|
return [b.name for b in self.repo.branches]
|
|
123
129
|
|
|
124
130
|
def get_conflicts(self) -> List[str]:
|
|
@@ -153,14 +159,34 @@ class GitOps:
|
|
|
153
159
|
|
|
154
160
|
def execute(self, command: str) -> str:
|
|
155
161
|
"""Run an arbitrary git command safely (the command string shouldn't include 'git ')."""
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
import shlex
|
|
163
|
+
if not command.strip():
|
|
164
|
+
raise ValueError("Empty Git command provided.")
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
parts = shlex.split(command.strip())
|
|
168
|
+
except ValueError:
|
|
169
|
+
parts = command.strip().split()
|
|
170
|
+
|
|
158
171
|
if parts and parts[0] == "git":
|
|
159
172
|
parts = parts[1:]
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
173
|
+
|
|
174
|
+
if not parts:
|
|
175
|
+
raise ValueError("Empty Git command provided.")
|
|
176
|
+
|
|
177
|
+
subcommand = parts[0]
|
|
178
|
+
args = parts[1:]
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
git_func = getattr(self.repo.git, subcommand.replace("-", "_"))
|
|
182
|
+
return git_func(*args)
|
|
183
|
+
except AttributeError:
|
|
184
|
+
try:
|
|
185
|
+
return self.repo.git.execute(["git", subcommand] + args)
|
|
186
|
+
except Exception as e:
|
|
187
|
+
raise ValueError(f"Failed to execute Git command '{command}': {e}")
|
|
188
|
+
except Exception as e:
|
|
189
|
+
raise ValueError(f"Failed to execute Git command '{command}': {e}")
|
|
164
190
|
|
|
165
191
|
def get_upstream_tracking(self) -> Optional[str]:
|
|
166
192
|
"""Get the remote tracking branch of the current branch, e.g. 'origin/main'."""
|
|
@@ -379,6 +379,13 @@ def test_undo_nothing_to_undo(git_workspace):
|
|
|
379
379
|
assert "Nothing to undo" in res.stdout
|
|
380
380
|
|
|
381
381
|
def test_undo_destructive_confirm_no(git_workspace):
|
|
382
|
+
# Setup commit and ORIG_HEAD to trigger high risk undo
|
|
383
|
+
test_file = git_workspace.workspace / "test.txt"
|
|
384
|
+
test_file.write_text("initial")
|
|
385
|
+
git_workspace.repo.index.add([str(test_file)])
|
|
386
|
+
git_workspace.repo.index.commit("initial commit")
|
|
387
|
+
git_workspace.repo.git.update_ref("ORIG_HEAD", "HEAD")
|
|
388
|
+
|
|
382
389
|
# Mock server triggers destructive plan
|
|
383
390
|
res = git_workspace.run(["undo"], stdin_data="n\n")
|
|
384
391
|
assert res.returncode == 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from unittest.mock import patch, MagicMock
|
|
4
|
+
from ace.core.config import save_config, Config
|
|
5
|
+
|
|
6
|
+
@patch("ace.core.config.DEFAULT_CONFIG_DIR")
|
|
7
|
+
@patch("ace.core.config.DEFAULT_CONFIG_PATH")
|
|
8
|
+
def test_save_config_atomic(mock_config_path, mock_config_dir, tmp_path):
|
|
9
|
+
# Setup tmp directory for config file
|
|
10
|
+
config_dir = tmp_path / ".ace"
|
|
11
|
+
config_dir.mkdir()
|
|
12
|
+
config_file = config_dir / "config.toml"
|
|
13
|
+
|
|
14
|
+
mock_config_dir.mkdir = MagicMock()
|
|
15
|
+
mock_config_dir.__truediv__ = MagicMock(return_value=config_file)
|
|
16
|
+
# Configure path patch
|
|
17
|
+
mock_config_path.open = config_file.open
|
|
18
|
+
mock_config_path.parent = config_dir
|
|
19
|
+
mock_config_path.exists = config_file.exists
|
|
20
|
+
|
|
21
|
+
# We patch DEFAULT_CONFIG_DIR and DEFAULT_CONFIG_PATH directly inside the function
|
|
22
|
+
with patch("ace.core.config.DEFAULT_CONFIG_DIR", config_dir), \
|
|
23
|
+
patch("ace.core.config.DEFAULT_CONFIG_PATH", config_file):
|
|
24
|
+
|
|
25
|
+
cfg = Config({
|
|
26
|
+
"ai": {
|
|
27
|
+
"provider": "openai",
|
|
28
|
+
"openai_model": "gpt-4o-mini"
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
save_config(cfg)
|
|
33
|
+
|
|
34
|
+
# Verify content was written
|
|
35
|
+
assert config_file.exists()
|
|
36
|
+
content = config_file.read_text(encoding="utf-8")
|
|
37
|
+
assert "provider = \"openai\"" in content
|
|
38
|
+
|
|
39
|
+
# Verify no temp files are left in the directory
|
|
40
|
+
temp_files = list(config_dir.glob("config-*.tmp"))
|
|
41
|
+
assert len(temp_files) == 0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
from ace.ai.conflict_resolver import ConflictResolver, ConflictResolverError
|
|
5
|
+
|
|
6
|
+
def test_apply_resolution_creates_backup_and_atomic_swap(tmp_path):
|
|
7
|
+
# Setup mock git_ops working dir
|
|
8
|
+
mock_git_ops = MagicMock()
|
|
9
|
+
mock_git_ops.working_dir = str(tmp_path)
|
|
10
|
+
|
|
11
|
+
# Create a test conflicted file
|
|
12
|
+
test_file = tmp_path / "conflict.txt"
|
|
13
|
+
test_content = "<<<<<<< HEAD\nlocal changes\n=======\nincoming changes\n>>>>>>> branch\n"
|
|
14
|
+
test_file.write_text(test_content, encoding="utf-8")
|
|
15
|
+
|
|
16
|
+
resolver = ConflictResolver(mock_git_ops)
|
|
17
|
+
|
|
18
|
+
# Replacement block
|
|
19
|
+
block = "<<<<<<< HEAD\nlocal changes\n=======\nincoming changes\n>>>>>>> branch"
|
|
20
|
+
replacement = "resolved changes"
|
|
21
|
+
|
|
22
|
+
# Run resolution
|
|
23
|
+
resolver.apply_resolution("conflict.txt", [(block, replacement)])
|
|
24
|
+
|
|
25
|
+
# Assert resolution succeeded
|
|
26
|
+
assert test_file.read_text(encoding="utf-8") == "resolved changes\n"
|
|
27
|
+
|
|
28
|
+
# Assert backup file was cleaned up (no extra .bak files remain)
|
|
29
|
+
bak_files = list(tmp_path.glob("conflict.txt.bak-*"))
|
|
30
|
+
assert len(bak_files) == 0
|
|
31
|
+
|
|
32
|
+
def test_apply_resolution_fails_and_restores(tmp_path):
|
|
33
|
+
mock_git_ops = MagicMock()
|
|
34
|
+
mock_git_ops.working_dir = str(tmp_path)
|
|
35
|
+
|
|
36
|
+
test_file = tmp_path / "conflict.txt"
|
|
37
|
+
test_content = "<<<<<<< HEAD\noriginal content\n>>>>>>> branch"
|
|
38
|
+
test_file.write_text(test_content, encoding="utf-8")
|
|
39
|
+
|
|
40
|
+
resolver = ConflictResolver(mock_git_ops)
|
|
41
|
+
|
|
42
|
+
# Block that doesn't exist to force failure
|
|
43
|
+
non_existent_block = "no match"
|
|
44
|
+
|
|
45
|
+
with pytest.raises(ConflictResolverError, match="Conflict block not found"):
|
|
46
|
+
resolver.apply_resolution("conflict.txt", [(non_existent_block, "bad replacement")])
|
|
47
|
+
|
|
48
|
+
# Assert original content was preserved/restored
|
|
49
|
+
assert test_file.read_text(encoding="utf-8") == test_content
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
from ace.core.context import RepoContext
|
|
5
|
+
|
|
6
|
+
def test_check_merge_rebase_state_with_git_dir_resolution(tmp_path):
|
|
7
|
+
# Create mock git_dir (which in real git might be inside worktrees/submodules)
|
|
8
|
+
mock_git_dir = tmp_path / "worktree_gitdir"
|
|
9
|
+
mock_git_dir.mkdir()
|
|
10
|
+
|
|
11
|
+
# Simulate a merge in progress
|
|
12
|
+
merge_head = mock_git_dir / "MERGE_HEAD"
|
|
13
|
+
merge_head.write_text("commit123", encoding="utf-8")
|
|
14
|
+
|
|
15
|
+
# Setup mock git_ops and repo
|
|
16
|
+
mock_git_ops = MagicMock()
|
|
17
|
+
mock_git_ops.repo.git_dir = str(mock_git_dir)
|
|
18
|
+
mock_git_ops.working_dir = str(tmp_path)
|
|
19
|
+
|
|
20
|
+
context_builder = RepoContext(mock_git_ops)
|
|
21
|
+
state = context_builder.check_merge_rebase_state()
|
|
22
|
+
|
|
23
|
+
assert state["in_progress"] is True
|
|
24
|
+
assert state["type"] == "merge"
|
|
25
|
+
assert "Merge conflict" in state["detail"]
|
|
26
|
+
|
|
27
|
+
def test_check_merge_rebase_state_fallback(tmp_path):
|
|
28
|
+
# Setup mock git_ops that raises error when accessing git_dir
|
|
29
|
+
mock_git_ops = MagicMock()
|
|
30
|
+
# Delete git_dir attribute to force AttributeError on access
|
|
31
|
+
del mock_git_ops.repo.git_dir
|
|
32
|
+
mock_git_ops.working_dir = str(tmp_path)
|
|
33
|
+
|
|
34
|
+
# Setup local fallback .git directory
|
|
35
|
+
fallback_git = tmp_path / ".git"
|
|
36
|
+
fallback_git.mkdir()
|
|
37
|
+
|
|
38
|
+
rebase_apply = fallback_git / "rebase-apply"
|
|
39
|
+
rebase_apply.mkdir()
|
|
40
|
+
|
|
41
|
+
context_builder = RepoContext(mock_git_ops)
|
|
42
|
+
state = context_builder.check_merge_rebase_state()
|
|
43
|
+
|
|
44
|
+
assert state["in_progress"] is True
|
|
45
|
+
assert state["type"] == "rebase"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import MagicMock
|
|
3
|
+
from ace.core.git_ops import GitOps
|
|
4
|
+
|
|
5
|
+
def test_git_ops_execute_quoted_args():
|
|
6
|
+
# Setup mock repo
|
|
7
|
+
mock_repo = MagicMock()
|
|
8
|
+
mock_git_func = MagicMock(return_value="commit success")
|
|
9
|
+
mock_repo.git.commit = mock_git_func
|
|
10
|
+
|
|
11
|
+
git_ops = GitOps.__new__(GitOps)
|
|
12
|
+
git_ops.repo = mock_repo
|
|
13
|
+
|
|
14
|
+
# Run execute with quotes
|
|
15
|
+
res = git_ops.execute("commit -m 'hello world'")
|
|
16
|
+
|
|
17
|
+
assert res == "commit success"
|
|
18
|
+
mock_git_func.assert_called_once_with("-m", "hello world")
|
|
19
|
+
|
|
20
|
+
def test_git_ops_execute_unknown_command():
|
|
21
|
+
mock_repo = MagicMock()
|
|
22
|
+
# Simulate AttributeError when getting direct attribute
|
|
23
|
+
del mock_repo.git.nonexistent
|
|
24
|
+
|
|
25
|
+
# Mock fallback git.execute
|
|
26
|
+
mock_execute = MagicMock(return_value="executed custom")
|
|
27
|
+
mock_repo.git.execute = mock_execute
|
|
28
|
+
|
|
29
|
+
git_ops = GitOps.__new__(GitOps)
|
|
30
|
+
git_ops.repo = mock_repo
|
|
31
|
+
|
|
32
|
+
res = git_ops.execute("nonexistent -a --foo")
|
|
33
|
+
assert res == "executed custom"
|
|
34
|
+
mock_execute.assert_called_once_with(["git", "nonexistent", "-a", "--foo"])
|
|
35
|
+
|
|
36
|
+
def test_git_ops_execute_empty():
|
|
37
|
+
git_ops = GitOps.__new__(GitOps)
|
|
38
|
+
with pytest.raises(ValueError, match="Empty Git command"):
|
|
39
|
+
git_ops.execute("")
|
|
40
|
+
with pytest.raises(ValueError, match="Empty Git command"):
|
|
41
|
+
git_ops.execute(" ")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import json
|
|
3
|
+
from unittest.mock import patch, MagicMock
|
|
4
|
+
from ace.ai.llm_factory import ensure_ollama_model, _checked_ollama_models
|
|
5
|
+
|
|
6
|
+
@pytest.fixture(autouse=True)
|
|
7
|
+
def clear_ollama_cache():
|
|
8
|
+
_checked_ollama_models.clear()
|
|
9
|
+
|
|
10
|
+
@patch("urllib.request.urlopen")
|
|
11
|
+
@patch("urllib.request.Request")
|
|
12
|
+
def test_ensure_ollama_model_streaming_success(mock_request, mock_urlopen):
|
|
13
|
+
# First response: GET /api/tags (model list doesn't contain test-model)
|
|
14
|
+
mock_response_tags = MagicMock()
|
|
15
|
+
mock_response_tags.__enter__.return_value = mock_response_tags
|
|
16
|
+
mock_response_tags.read.return_value = b'{"models": []}'
|
|
17
|
+
|
|
18
|
+
# Second response: POST /api/pull (streaming download chunks)
|
|
19
|
+
mock_response_pull = MagicMock()
|
|
20
|
+
mock_response_pull.__enter__.return_value = mock_response_pull
|
|
21
|
+
mock_response_pull.__iter__.return_value = [
|
|
22
|
+
b'{"status": "pulling manifest"}\n',
|
|
23
|
+
b'{"status": "downloading digest", "completed": 50, "total": 100}\n',
|
|
24
|
+
b'{"status": "success"}\n'
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
mock_urlopen.side_effect = [mock_response_tags, mock_response_pull]
|
|
28
|
+
|
|
29
|
+
# Mock user input to confirm pulling
|
|
30
|
+
with patch("ace.ui.prompts.confirm", return_value=True):
|
|
31
|
+
ensure_ollama_model("http://localhost:11434", "test-model")
|
|
32
|
+
|
|
33
|
+
assert mock_urlopen.call_count == 2
|
|
34
|
+
|
|
35
|
+
@patch("urllib.request.urlopen")
|
|
36
|
+
@patch("urllib.request.Request")
|
|
37
|
+
def test_ensure_ollama_model_already_exists(mock_request, mock_urlopen):
|
|
38
|
+
# Model is listed in local models, so no pull should be requested
|
|
39
|
+
mock_response_tags = MagicMock()
|
|
40
|
+
mock_response_tags.__enter__.return_value = mock_response_tags
|
|
41
|
+
mock_response_tags.read.return_value = b'{"models": [{"name": "test-model:latest"}]}'
|
|
42
|
+
mock_urlopen.return_value = mock_response_tags
|
|
43
|
+
|
|
44
|
+
with patch("ace.ui.prompts.confirm", return_value=True) as mock_confirm:
|
|
45
|
+
ensure_ollama_model("http://localhost:11434", "test-model")
|
|
46
|
+
mock_confirm.assert_not_called()
|
|
47
|
+
|
|
48
|
+
assert mock_urlopen.call_count == 1
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.2"
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/e2e_testing_track/ORIGINAL_REQUEST.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/find_unused_modules.py
RENAMED
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/measure_lazy_startup.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/explorer_init/test_import_profiler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/implementation_track/ORIGINAL_REQUEST.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/worker_e2e_testing/ORIGINAL_REQUEST.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.3.2 → ace_git_copilot-0.3.3}/.agents/worker_m1_startup/ORIGINAL_REQUEST.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|