ace-git-copilot 0.4.4__tar.gz → 0.4.6__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.4.4 → ace_git_copilot-0.4.6}/PKG-INFO +1 -1
- ace_git_copilot-0.4.6/ace/__init__.py +1 -0
- ace_git_copilot-0.4.6/ace/ai/push_resolver.py +172 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/cli.py +19 -8
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/pyproject.toml +1 -1
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_interceptor.py +20 -0
- ace_git_copilot-0.4.6/tests/test_push_resolver.py +32 -0
- ace_git_copilot-0.4.4/ace/__init__.py +0 -1
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/AGENTS.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/BRIEFING.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/e2e_testing_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/e2e_testing_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/e2e_testing_track/SCOPE.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/e2e_testing_track/progress.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/BRIEFING.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/emojis_list.txt +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/find_unused_modules.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/handoff.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/measure_lazy_startup.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/measure_startup.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/profile_imports.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/progress.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/run_importtime.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/search_banner.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/search_emojis.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/search_git_usages.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/search_usages.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/test_import_profiler.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/test_mocked_sys.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/handoff.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/implementation_track/BRIEFING.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/implementation_track/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/implementation_track/explorer_initial_report.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/implementation_track/progress.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/orchestrator/.gitkeep +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/orchestrator/BRIEFING.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/orchestrator/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/orchestrator/PROJECT.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/orchestrator/progress.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/teamwork_preview_explorer_e2e_explore/BRIEFING.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/teamwork_preview_explorer_e2e_explore/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/teamwork_preview_explorer_e2e_explore/handoff.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/teamwork_preview_explorer_e2e_explore/progress.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/worker_e2e_testing/BRIEFING.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/worker_e2e_testing/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/worker_e2e_testing/progress.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/worker_m1_startup/BRIEFING.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/worker_m1_startup/ORIGINAL_REQUEST.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.agents/worker_m1_startup/progress.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.env.example +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.github/workflows/tests.yml +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.gitignore +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/CODE_OF_CONDUCT.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/CONTRIBUTING.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/LICENSE +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/PROJECT.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/README.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/SECURITY.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/SUPPORT.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/TEST_INFRA.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/TEST_READY.md +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/__main__.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/changelog_generator.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/code_reviewer.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/commit_generator.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/conflict_resolver.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/gitignore_generator.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/history_analyzer.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/intent_parser.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/llm_factory.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/pr_drafter.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/blame.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/changelog.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/commit.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/conflict.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/doctor.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/explain.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/ignore.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/intent.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/pr.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/rebase.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/review.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/search.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/standup.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/prompts/undo.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ai/rebase_helper.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/core/config.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/core/context.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/core/diagnostics.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/core/git_ops.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/core/hooks.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/core/safety.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ui/banner.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ui/dashboard.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ui/display.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ui/prompts.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/ui/themes.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/utils/conflict_parser.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/utils/diff_parser.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/utils/json_utils.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/ace/utils/string_utils.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/media/cli_in_action.svg +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/media/dashboard.svg +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/media/stats.svg +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/conftest.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/e2e/conftest.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/e2e/test_tier1_features.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/e2e/test_tier2_boundaries.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/e2e/test_tier3_combinations.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/e2e/test_tier4_workloads.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_blame.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_changelog_generator.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_code_reviewer.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_config_atomic_write.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_conflict_resolver.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_conflict_resolver_backup.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_context_gitdir_detection.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_diagnostics.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_diff_trimmer.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_git_ops.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_git_ops_execute.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_help.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_history_analyzer.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_hooks.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_ignore.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_intent_parser.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_llm_factory.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_llm_factory_ollama.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_merge_state.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_pr_drafter.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_rebase_helper.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_safety.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_search.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_standup.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_string_utils.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_undo.py +0 -0
- {ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ace-git-copilot
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.6"
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from typing import Dict, Any, List, Optional
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.text import Text
|
|
4
|
+
from rich import box
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from ace.core.git_ops import GitOps
|
|
8
|
+
from ace.ui.display import console, spinner, print_success, print_warning, show_error_panel
|
|
9
|
+
|
|
10
|
+
def categorize_push_error(error_msg: str) -> Dict[str, Any]:
|
|
11
|
+
"""Categorize git push error into actionable problem types and remedies."""
|
|
12
|
+
err_lower = error_msg.lower()
|
|
13
|
+
|
|
14
|
+
if any(pattern in err_lower for pattern in ["fetch first", "non-fast-forward", "behind", "rejected", "updates were rejected because the remote contains work"]):
|
|
15
|
+
return {
|
|
16
|
+
"type": "non_fast_forward",
|
|
17
|
+
"title": "Remote Branch Has New Commits (Non-Fast-Forward)",
|
|
18
|
+
"description": "The remote branch contains commits that you do not have locally. You must pull remote changes before pushing.",
|
|
19
|
+
"actions": [
|
|
20
|
+
{"key": "1", "label": "Pull & Rebase remote changes (git pull --rebase)", "cmd": "pull_rebase"},
|
|
21
|
+
{"key": "2", "label": "Pull & Merge remote changes (git pull)", "cmd": "pull_merge"},
|
|
22
|
+
{"key": "3", "label": "Force push safely with lease (git push --force-with-lease)", "cmd": "force_lease"},
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
elif any(pattern in err_lower for pattern in ["no upstream", "has no upstream", "set-upstream"]):
|
|
26
|
+
return {
|
|
27
|
+
"type": "no_upstream",
|
|
28
|
+
"title": "Missing Upstream Tracking Branch",
|
|
29
|
+
"description": "Your local branch does not have an upstream tracking branch on the remote.",
|
|
30
|
+
"actions": [
|
|
31
|
+
{"key": "1", "label": "Set upstream and push (git push -u)", "cmd": "set_upstream"},
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
elif any(pattern in err_lower for pattern in ["permission denied", "authentication failed", "could not read from remote", "publickey"]):
|
|
35
|
+
return {
|
|
36
|
+
"type": "permission_denied",
|
|
37
|
+
"title": "Authentication / Access Permission Denied",
|
|
38
|
+
"description": "Git could not authenticate with the remote repository. Check your SSH keys or HTTPS access token.",
|
|
39
|
+
"actions": [
|
|
40
|
+
{"key": "1", "label": "Show SSH / Token troubleshooting guide", "cmd": "help_auth"},
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
else:
|
|
44
|
+
return {
|
|
45
|
+
"type": "unknown",
|
|
46
|
+
"title": "Git Push Error",
|
|
47
|
+
"description": error_msg.strip()[:200],
|
|
48
|
+
"actions": [
|
|
49
|
+
{"key": "1", "label": "Pull & Rebase remote changes (git pull --rebase)", "cmd": "pull_rebase"},
|
|
50
|
+
{"key": "2", "label": "Force push safely with lease (git push --force-with-lease)", "cmd": "force_lease"},
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def handle_push_failure(git_ops: GitOps, error_msg: str, remote: str = "origin", branch: Optional[str] = None, offline: bool = False) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Interactively diagnose a git push failure and prompt the user with recovery options.
|
|
57
|
+
Returns True if the push was resolved successfully, False otherwise.
|
|
58
|
+
"""
|
|
59
|
+
curr_branch = branch or git_ops.get_current_branch() or "master"
|
|
60
|
+
category = categorize_push_error(error_msg)
|
|
61
|
+
|
|
62
|
+
console.print()
|
|
63
|
+
diag_text = Text()
|
|
64
|
+
diag_text.append(f"Problem: {category['description']}\n", style="white")
|
|
65
|
+
diag_text.append(f"Target Branch: {remote}/{curr_branch}\n", style="dim #9E9E9E")
|
|
66
|
+
|
|
67
|
+
console.print(Panel(
|
|
68
|
+
diag_text,
|
|
69
|
+
title=f"[bold #FF1744]✘ {category['title']}[/bold #FF1744]",
|
|
70
|
+
border_style="#FF1744",
|
|
71
|
+
padding=(1, 2)
|
|
72
|
+
))
|
|
73
|
+
|
|
74
|
+
console.print("[bold cyan]Choose resolution action:[/bold cyan]")
|
|
75
|
+
for act in category["actions"]:
|
|
76
|
+
console.print(f" [{act['key']}] {act['label']}")
|
|
77
|
+
console.print(" [s] Skip / Cancel push")
|
|
78
|
+
|
|
79
|
+
choice = typer.prompt("Select option", default="1").lower().strip()
|
|
80
|
+
|
|
81
|
+
if choice == "s":
|
|
82
|
+
print_warning("Push resolution cancelled.")
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
selected_act = None
|
|
86
|
+
for act in category["actions"]:
|
|
87
|
+
if act["key"] == choice:
|
|
88
|
+
selected_act = act
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
if not selected_act and category["actions"]:
|
|
92
|
+
selected_act = category["actions"][0]
|
|
93
|
+
|
|
94
|
+
if not selected_act:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
cmd_type = selected_act["cmd"]
|
|
98
|
+
|
|
99
|
+
if cmd_type == "pull_rebase":
|
|
100
|
+
try:
|
|
101
|
+
with spinner(f"Pulling and rebasing from {remote}/{curr_branch}..."):
|
|
102
|
+
git_ops.execute(f"pull --rebase {remote} {curr_branch}")
|
|
103
|
+
print_success("Successfully pulled and rebased remote changes!")
|
|
104
|
+
|
|
105
|
+
# Retry push
|
|
106
|
+
with spinner(f"Retrying push to {remote}/{curr_branch}..."):
|
|
107
|
+
push_res = git_ops.push(remote=remote, branch=curr_branch)
|
|
108
|
+
print_success("Pushed to remote successfully!")
|
|
109
|
+
if push_res.strip():
|
|
110
|
+
console.print(f"[dim]{push_res}[/dim]")
|
|
111
|
+
return True
|
|
112
|
+
except Exception as e:
|
|
113
|
+
show_error_panel(f"Failed during pull & rebase or retry push: {e}", "Resolution Error")
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
elif cmd_type == "pull_merge":
|
|
117
|
+
try:
|
|
118
|
+
with spinner(f"Pulling and merging from {remote}/{curr_branch}..."):
|
|
119
|
+
git_ops.execute(f"pull {remote} {curr_branch}")
|
|
120
|
+
print_success("Successfully merged remote changes!")
|
|
121
|
+
|
|
122
|
+
# Retry push
|
|
123
|
+
with spinner(f"Retrying push to {remote}/{curr_branch}..."):
|
|
124
|
+
push_res = git_ops.push(remote=remote, branch=curr_branch)
|
|
125
|
+
print_success("Pushed to remote successfully!")
|
|
126
|
+
if push_res.strip():
|
|
127
|
+
console.print(f"[dim]{push_res}[/dim]")
|
|
128
|
+
return True
|
|
129
|
+
except Exception as e:
|
|
130
|
+
show_error_panel(f"Failed during pull or retry push: {e}", "Resolution Error")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
elif cmd_type == "force_lease":
|
|
134
|
+
from ace.ui.prompts import confirm
|
|
135
|
+
if confirm("Force push with lease will overwrite remote commits if your local branch is out of sync. Continue?", default=False):
|
|
136
|
+
try:
|
|
137
|
+
with spinner(f"Force pushing (--force-with-lease) to {remote}/{curr_branch}..."):
|
|
138
|
+
push_res = git_ops.execute(f"push --force-with-lease {remote} {curr_branch}")
|
|
139
|
+
print_success("Force pushed with lease successfully!")
|
|
140
|
+
if push_res.strip():
|
|
141
|
+
console.print(f"[dim]{push_res}[/dim]")
|
|
142
|
+
return True
|
|
143
|
+
except Exception as e:
|
|
144
|
+
show_error_panel(f"Force push failed: {e}", "Resolution Error")
|
|
145
|
+
return False
|
|
146
|
+
else:
|
|
147
|
+
print_warning("Force push cancelled.")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
elif cmd_type == "set_upstream":
|
|
151
|
+
try:
|
|
152
|
+
with spinner(f"Setting upstream and pushing to {remote}/{curr_branch}..."):
|
|
153
|
+
push_res = git_ops.push(remote=remote, branch=curr_branch, set_upstream=True)
|
|
154
|
+
print_success("Set upstream and pushed successfully!")
|
|
155
|
+
if push_res.strip():
|
|
156
|
+
console.print(f"[dim]{push_res}[/dim]")
|
|
157
|
+
return True
|
|
158
|
+
except Exception as e:
|
|
159
|
+
show_error_panel(f"Push with set-upstream failed: {e}", "Resolution Error")
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
elif cmd_type == "help_auth":
|
|
163
|
+
auth_guide = (
|
|
164
|
+
"Authentication Troubleshooting Guide:\n"
|
|
165
|
+
"1. SSH Key setup: Run 'ssh -T git@github.com' to verify key access.\n"
|
|
166
|
+
"2. HTTPS Token: Run 'git config --global credential.helper manager' to update saved passwords/tokens.\n"
|
|
167
|
+
"3. Remote URL: Verify remote URL with 'git remote -v'."
|
|
168
|
+
)
|
|
169
|
+
console.print(Panel(auth_guide, title="[bold cyan]Authentication Help[/bold cyan]", border_style="#00D5FF"))
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
return False
|
|
@@ -86,17 +86,25 @@ def main(
|
|
|
86
86
|
|
|
87
87
|
# Intercept subcommand if the user typed an English sentence (e.g. 'ace add and commit')
|
|
88
88
|
intercepted_query = None
|
|
89
|
-
|
|
89
|
+
ALL_INTERCEPT_SUBCOMMANDS = (
|
|
90
|
+
"add", "stage", "commit", "review", "explain", "resolve", "search",
|
|
91
|
+
"undo", "workspace", "ws", "standup", "blame", "pr", "squash", "ignore", "doctor"
|
|
92
|
+
)
|
|
93
|
+
if ctx.invoked_subcommand in ALL_INTERCEPT_SUBCOMMANDS:
|
|
90
94
|
import sys
|
|
91
95
|
sub_cmd = ctx.invoked_subcommand
|
|
92
96
|
sub_idx = -1
|
|
93
|
-
for idx, arg in enumerate(sys.argv):
|
|
94
|
-
if arg == sub_cmd
|
|
97
|
+
for idx, arg in enumerate(sys.argv[1:], start=1):
|
|
98
|
+
if arg == sub_cmd:
|
|
95
99
|
sub_idx = idx
|
|
96
100
|
break
|
|
97
101
|
if sub_idx != -1:
|
|
98
102
|
sub_args = sys.argv[sub_idx + 1:]
|
|
99
|
-
nl_indicators = {
|
|
103
|
+
nl_indicators = {
|
|
104
|
+
"and", "with", "to", "the", "a", "an", "in", "on", "new", "my", "all",
|
|
105
|
+
"but", "yesterday", "last", "from", "for", "changed", "everything",
|
|
106
|
+
"changes", "repo", "repos", "commits", "files"
|
|
107
|
+
}
|
|
100
108
|
if any(arg.lower() in nl_indicators for arg in sub_args):
|
|
101
109
|
query_parts = []
|
|
102
110
|
for arg in sys.argv[1:]:
|
|
@@ -105,6 +113,7 @@ def main(
|
|
|
105
113
|
query_parts.append(arg)
|
|
106
114
|
intercepted_query = " ".join(query_parts)
|
|
107
115
|
|
|
116
|
+
|
|
108
117
|
if intercepted_query:
|
|
109
118
|
query = intercepted_query
|
|
110
119
|
elif ctx.invoked_subcommand is not None:
|
|
@@ -447,20 +456,22 @@ def commit_cmd(
|
|
|
447
456
|
print_success("Pushed and set upstream branch successfully!")
|
|
448
457
|
console.print(f"[dim]{push_res}[/dim]")
|
|
449
458
|
except Exception as e:
|
|
450
|
-
|
|
459
|
+
from ace.ai.push_resolver import handle_push_failure
|
|
460
|
+
handle_push_failure(git_ops, str(e), remote=selected_remote, branch=current_branch, offline=offline)
|
|
451
461
|
else:
|
|
452
462
|
# Upstream exists
|
|
453
463
|
msg_push = f"Push to upstream branch '{upstream}'? (Your branch is {ahead} commit(s) ahead of remote)"
|
|
454
464
|
if confirm(msg_push, default=True):
|
|
465
|
+
remote_name = upstream.split("/")[0] if "/" in upstream else "origin"
|
|
455
466
|
try:
|
|
456
|
-
# Find the remote name from upstream (e.g. 'origin/main' -> 'origin')
|
|
457
|
-
remote_name = upstream.split("/")[0] if "/" in upstream else "origin"
|
|
458
467
|
with spinner(f"Pushing to {upstream}..."):
|
|
459
468
|
push_res = git_ops.push(remote=remote_name)
|
|
460
469
|
print_success("Pushed to remote successfully!")
|
|
461
470
|
console.print(f"[dim]{push_res}[/dim]")
|
|
462
471
|
except Exception as e:
|
|
463
|
-
|
|
472
|
+
from ace.ai.push_resolver import handle_push_failure
|
|
473
|
+
handle_push_failure(git_ops, str(e), remote=remote_name, branch=current_branch, offline=offline)
|
|
474
|
+
|
|
464
475
|
|
|
465
476
|
@app.command(name="setup", help="Initial configuration wizard for Ace.")
|
|
466
477
|
def setup_cmd():
|
|
@@ -40,3 +40,23 @@ def test_subcommand_interceptor_normal_subcmd(mock_git_ops_class):
|
|
|
40
40
|
# Normal execution path: it routes to the add subcommand, tries to run git add, and fails
|
|
41
41
|
assert result.exit_code == 1
|
|
42
42
|
mock_git_ops.execute.assert_called_once_with("add file1.txt")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@patch("ace.core.git_ops.GitOps")
|
|
46
|
+
@patch("ace.ai.llm_factory.get_llm")
|
|
47
|
+
@patch("ace.ai.intent_parser.IntentParser")
|
|
48
|
+
def test_subcommand_interceptor_with_flags(mock_parser_class, mock_get_llm, mock_git_ops_class):
|
|
49
|
+
mock_git_ops = MagicMock()
|
|
50
|
+
mock_git_ops_class.return_value = mock_git_ops
|
|
51
|
+
|
|
52
|
+
mock_parser = MagicMock()
|
|
53
|
+
mock_parser.parse_intent.return_value = {"commands": [], "explanation": "NL query with flag matched"}
|
|
54
|
+
mock_parser_class.return_value = mock_parser
|
|
55
|
+
|
|
56
|
+
# Simulate: ace -y add and commit
|
|
57
|
+
with patch.object(sys, "argv", ["ace", "-y", "add", "and", "commit"]):
|
|
58
|
+
result = runner.invoke(app, ["-y", "add", "and", "commit"])
|
|
59
|
+
|
|
60
|
+
assert result.exit_code == 0
|
|
61
|
+
mock_parser.parse_intent.assert_called_once_with("add and commit", offline=False)
|
|
62
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
from ace.ai.push_resolver import categorize_push_error, handle_push_failure
|
|
3
|
+
|
|
4
|
+
def test_categorize_push_error_non_fast_forward():
|
|
5
|
+
err = "error: failed to push some refs to 'github.com:repo/app.git'\nhint: Updates were rejected because the remote contains work that you do not have locally."
|
|
6
|
+
cat = categorize_push_error(err)
|
|
7
|
+
assert cat["type"] == "non_fast_forward"
|
|
8
|
+
assert len(cat["actions"]) == 3
|
|
9
|
+
|
|
10
|
+
def test_categorize_push_error_no_upstream():
|
|
11
|
+
err = "fatal: The current branch master has no upstream branch."
|
|
12
|
+
cat = categorize_push_error(err)
|
|
13
|
+
assert cat["type"] == "no_upstream"
|
|
14
|
+
assert cat["actions"][0]["cmd"] == "set_upstream"
|
|
15
|
+
|
|
16
|
+
def test_categorize_push_error_permission_denied():
|
|
17
|
+
err = "Permission denied (publickey). fatal: Could not read from remote repository."
|
|
18
|
+
cat = categorize_push_error(err)
|
|
19
|
+
assert cat["type"] == "permission_denied"
|
|
20
|
+
|
|
21
|
+
@patch("ace.ai.push_resolver.typer.prompt", return_value="1")
|
|
22
|
+
def test_handle_push_failure_pull_rebase(mock_prompt):
|
|
23
|
+
mock_git_ops = MagicMock()
|
|
24
|
+
mock_git_ops.get_current_branch.return_value = "master"
|
|
25
|
+
mock_git_ops.push.return_value = "Everything up-to-date"
|
|
26
|
+
|
|
27
|
+
err = "Updates were rejected because the remote contains work"
|
|
28
|
+
res = handle_push_failure(mock_git_ops, err, remote="origin", branch="master")
|
|
29
|
+
|
|
30
|
+
assert res is True
|
|
31
|
+
mock_git_ops.execute.assert_called_once_with("pull --rebase origin master")
|
|
32
|
+
mock_git_ops.push.assert_called_once_with(remote="origin", branch="master")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.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.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/find_unused_modules.py
RENAMED
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.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.4.4 → ace_git_copilot-0.4.6}/.agents/explorer_init/test_import_profiler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.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.4.4 → ace_git_copilot-0.4.6}/.agents/worker_e2e_testing/ORIGINAL_REQUEST.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ace_git_copilot-0.4.4 → ace_git_copilot-0.4.6}/.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
|
|
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
|