gac 0.16.2__tar.gz → 0.17.1__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.
Potentially problematic release.
This version of gac might be problematic. Click here for more details.
- {gac-0.16.2 → gac-0.17.1}/PKG-INFO +2 -2
- {gac-0.16.2 → gac-0.17.1}/README.md +1 -1
- {gac-0.16.2 → gac-0.17.1}/src/gac/__version__.py +1 -1
- {gac-0.16.2 → gac-0.17.1}/src/gac/cli.py +7 -1
- {gac-0.16.2 → gac-0.17.1}/src/gac/config.py +2 -0
- {gac-0.16.2 → gac-0.17.1}/src/gac/constants.py +3 -2
- {gac-0.16.2 → gac-0.17.1}/src/gac/git.py +18 -5
- {gac-0.16.2 → gac-0.17.1}/src/gac/main.py +49 -12
- {gac-0.16.2 → gac-0.17.1}/src/gac/prompt.py +95 -4
- {gac-0.16.2 → gac-0.17.1}/src/gac/utils.py +6 -7
- {gac-0.16.2 → gac-0.17.1}/.gitignore +0 -0
- {gac-0.16.2 → gac-0.17.1}/LICENSE +0 -0
- {gac-0.16.2 → gac-0.17.1}/pyproject.toml +0 -0
- {gac-0.16.2 → gac-0.17.1}/src/gac/__init__.py +0 -0
- {gac-0.16.2 → gac-0.17.1}/src/gac/ai.py +0 -0
- {gac-0.16.2 → gac-0.17.1}/src/gac/config_cli.py +0 -0
- {gac-0.16.2 → gac-0.17.1}/src/gac/diff_cli.py +0 -0
- {gac-0.16.2 → gac-0.17.1}/src/gac/errors.py +0 -0
- {gac-0.16.2 → gac-0.17.1}/src/gac/init_cli.py +0 -0
- {gac-0.16.2 → gac-0.17.1}/src/gac/preprocess.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gac
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.1
|
|
4
4
|
Summary: AI-powered Git commit message generator with multi-provider support
|
|
5
5
|
Project-URL: Homepage, https://github.com/cellwebb/gac
|
|
6
6
|
Project-URL: Documentation, https://github.com/cellwebb/gac#readme
|
|
@@ -66,7 +66,7 @@ Description-Content-Type: text/markdown
|
|
|
66
66
|
- **Seamless Git Workflow:** Integrates smoothly into your existing Git routine as a simple drop-in replacement for `git commit`.
|
|
67
67
|
- **Extensive Customization:** Tailor commit messages to your needs with a rich set of flags, including one-liners (`-o`), AI hints (`-h`), commit scope (`-s`), and specific model selection (`-m`).
|
|
68
68
|
- **Streamlined Workflow Commands:** Boost your productivity with convenient options to stage all changes (`-a`), auto-confirm commits (`-y`), and push to your remote repository (`-p`) in a single step.
|
|
69
|
-
- **Reroll
|
|
69
|
+
- **Interactive Reroll with Feedback:** Not satisfied with the generated commit message? Use `r` for a simple regeneration, or `r <feedback>` to provide specific improvement suggestions (e.g., `r make it shorter`, `r focus on the bug fix`).
|
|
70
70
|
- **Token Usage Tracking:** Display token consumption statistics (prompt, completion, and total tokens).
|
|
71
71
|
|
|
72
72
|
## How It Works
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
- **Seamless Git Workflow:** Integrates smoothly into your existing Git routine as a simple drop-in replacement for `git commit`.
|
|
19
19
|
- **Extensive Customization:** Tailor commit messages to your needs with a rich set of flags, including one-liners (`-o`), AI hints (`-h`), commit scope (`-s`), and specific model selection (`-m`).
|
|
20
20
|
- **Streamlined Workflow Commands:** Boost your productivity with convenient options to stage all changes (`-a`), auto-confirm commits (`-y`), and push to your remote repository (`-p`) in a single step.
|
|
21
|
-
- **Reroll
|
|
21
|
+
- **Interactive Reroll with Feedback:** Not satisfied with the generated commit message? Use `r` for a simple regeneration, or `r <feedback>` to provide specific improvement suggestions (e.g., `r make it shorter`, `r focus on the bug fix`).
|
|
22
22
|
- **Token Usage Tracking:** Display token consumption statistics (prompt, completion, and total tokens).
|
|
23
23
|
|
|
24
24
|
## How It Works
|
|
@@ -87,6 +87,12 @@ def cli(
|
|
|
87
87
|
effective_log_level = "ERROR"
|
|
88
88
|
setup_logging(effective_log_level)
|
|
89
89
|
logger.info("Starting gac")
|
|
90
|
+
|
|
91
|
+
# Apply always_include_scope setting if no explicit scope provided
|
|
92
|
+
effective_scope = scope
|
|
93
|
+
if scope is None and config.get("always_include_scope", False):
|
|
94
|
+
effective_scope = "" # Empty string triggers scope inference
|
|
95
|
+
|
|
90
96
|
try:
|
|
91
97
|
main(
|
|
92
98
|
stage_all=add_all,
|
|
@@ -94,7 +100,7 @@ def cli(
|
|
|
94
100
|
hint=hint,
|
|
95
101
|
one_liner=one_liner,
|
|
96
102
|
show_prompt=show_prompt,
|
|
97
|
-
scope=
|
|
103
|
+
scope=effective_scope,
|
|
98
104
|
require_confirmation=not yes,
|
|
99
105
|
push=push,
|
|
100
106
|
quiet=quiet,
|
|
@@ -33,6 +33,8 @@ def load_config() -> dict[str, str | int | float | bool]:
|
|
|
33
33
|
"max_retries": int(os.getenv("GAC_RETRIES", EnvDefaults.MAX_RETRIES)),
|
|
34
34
|
"log_level": os.getenv("GAC_LOG_LEVEL", Logging.DEFAULT_LEVEL),
|
|
35
35
|
"warning_limit_tokens": int(os.getenv("GAC_WARNING_LIMIT_TOKENS", EnvDefaults.WARNING_LIMIT_TOKENS)),
|
|
36
|
+
"always_include_scope": os.getenv("GAC_ALWAYS_INCLUDE_SCOPE", str(EnvDefaults.ALWAYS_INCLUDE_SCOPE)).lower()
|
|
37
|
+
in ("true", "1", "yes", "on"),
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
return config
|
|
@@ -23,6 +23,7 @@ class EnvDefaults:
|
|
|
23
23
|
TEMPERATURE: float = 1
|
|
24
24
|
MAX_OUTPUT_TOKENS: int = 512
|
|
25
25
|
WARNING_LIMIT_TOKENS: int = 16384
|
|
26
|
+
ALWAYS_INCLUDE_SCOPE: bool = False
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class Logging:
|
|
@@ -102,8 +103,8 @@ class FileTypeImportance:
|
|
|
102
103
|
".ini": 3.5, # INI config
|
|
103
104
|
".env": 3.5, # Environment variables
|
|
104
105
|
# Documentation
|
|
105
|
-
".md":
|
|
106
|
-
".rst":
|
|
106
|
+
".md": 2.5, # Markdown (reduced to prioritize code changes)
|
|
107
|
+
".rst": 2.5, # reStructuredText (reduced to prioritize code changes)
|
|
107
108
|
# Web
|
|
108
109
|
".html": 3.5, # HTML
|
|
109
110
|
".css": 3.5, # CSS
|
|
@@ -127,12 +127,25 @@ def run_pre_commit_hooks() -> bool:
|
|
|
127
127
|
|
|
128
128
|
# Run pre-commit hooks on staged files
|
|
129
129
|
logger.info("Running pre-commit hooks...")
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
# Run pre-commit and capture both stdout and stderr
|
|
131
|
+
result = subprocess.run(["pre-commit", "run"], capture_output=True, text=True, check=False)
|
|
132
|
+
|
|
133
|
+
if result.returncode == 0:
|
|
134
|
+
# All hooks passed
|
|
133
135
|
return True
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
else:
|
|
137
|
+
# Pre-commit hooks failed - show the output
|
|
138
|
+
output = result.stdout if result.stdout else ""
|
|
139
|
+
error = result.stderr if result.stderr else ""
|
|
140
|
+
|
|
141
|
+
# Combine outputs (pre-commit usually outputs to stdout)
|
|
142
|
+
full_output = output + ("\n" + error if error else "")
|
|
143
|
+
|
|
144
|
+
if full_output.strip():
|
|
145
|
+
# Show which hooks failed and why
|
|
146
|
+
logger.error(f"Pre-commit hooks failed:\n{full_output}")
|
|
147
|
+
else:
|
|
148
|
+
logger.error(f"Pre-commit hooks failed with exit code {result.returncode}")
|
|
136
149
|
return False
|
|
137
150
|
except Exception as e:
|
|
138
151
|
logger.debug(f"Error running pre-commit: {e}")
|
|
@@ -121,7 +121,7 @@ def main(
|
|
|
121
121
|
if warning_limit and prompt_tokens > warning_limit:
|
|
122
122
|
console = Console()
|
|
123
123
|
console.print(
|
|
124
|
-
f"[yellow]⚠️
|
|
124
|
+
f"[yellow]⚠️ WARNING: Prompt contains {prompt_tokens} tokens, which exceeds the warning limit of "
|
|
125
125
|
f"{warning_limit} tokens.[/yellow]"
|
|
126
126
|
)
|
|
127
127
|
if require_confirmation:
|
|
@@ -159,23 +159,58 @@ def main(
|
|
|
159
159
|
)
|
|
160
160
|
|
|
161
161
|
if require_confirmation:
|
|
162
|
-
# Custom prompt that accepts y/n/r
|
|
162
|
+
# Custom prompt that accepts y/n/r or "r <feedback (optional)>"
|
|
163
163
|
while True:
|
|
164
|
-
response = (
|
|
165
|
-
|
|
166
|
-
)
|
|
164
|
+
response = click.prompt(
|
|
165
|
+
"Proceed with commit above? [y/n/r <feedback>]", type=str, show_default=False
|
|
166
|
+
).strip()
|
|
167
167
|
|
|
168
|
-
if response in ["y", "yes"]:
|
|
168
|
+
if response.lower() in ["y", "yes"]:
|
|
169
169
|
break # Exit both loops and proceed with commit
|
|
170
|
-
elif response in ["n", "no"]:
|
|
170
|
+
elif response.lower() in ["n", "no"]:
|
|
171
171
|
console.print("[yellow]Prompt not accepted. Exiting...[/yellow]")
|
|
172
172
|
sys.exit(0)
|
|
173
|
-
elif response
|
|
174
|
-
|
|
173
|
+
elif response.lower() == "r" or response.lower().startswith("r ") or response.lower() == "reroll":
|
|
174
|
+
# Parse the reroll command for optional feedback
|
|
175
|
+
if response.lower() == "r" or response.lower() == "reroll":
|
|
176
|
+
# Simple reroll without feedback
|
|
177
|
+
reroll_feedback = ""
|
|
178
|
+
console.print("[cyan]Regenerating commit message...[/cyan]")
|
|
179
|
+
else:
|
|
180
|
+
# Extract feedback from "r <feedback>"
|
|
181
|
+
reroll_feedback = response[2:].strip() # Remove "r " prefix
|
|
182
|
+
console.print(f"[cyan]Regenerating commit message with feedback: {reroll_feedback}[/cyan]")
|
|
183
|
+
|
|
184
|
+
# Combine hints if reroll feedback provided
|
|
185
|
+
combined_hint = hint
|
|
186
|
+
if reroll_feedback:
|
|
187
|
+
# Create conversational prompt with previous attempt and feedback
|
|
188
|
+
conversational_hint = f"Previous attempt: '{commit_message}'. User feedback: {reroll_feedback}. Please revise accordingly."
|
|
189
|
+
|
|
190
|
+
if hint:
|
|
191
|
+
combined_hint = f"{hint}. {conversational_hint}"
|
|
192
|
+
else:
|
|
193
|
+
combined_hint = conversational_hint
|
|
194
|
+
|
|
195
|
+
# Regenerate prompt with conversational feedback
|
|
196
|
+
reroll_prompt = build_prompt(
|
|
197
|
+
status=status,
|
|
198
|
+
processed_diff=processed_diff,
|
|
199
|
+
diff_stat=diff_stat,
|
|
200
|
+
one_liner=one_liner,
|
|
201
|
+
hint=combined_hint,
|
|
202
|
+
scope=scope,
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
# No hint given, just reroll with same prompt
|
|
206
|
+
reroll_prompt = prompt
|
|
207
|
+
|
|
208
|
+
console.print() # Add blank line for readability
|
|
209
|
+
|
|
175
210
|
# Generate new message
|
|
176
211
|
commit_message = generate_commit_message(
|
|
177
212
|
model=model,
|
|
178
|
-
prompt=
|
|
213
|
+
prompt=reroll_prompt,
|
|
179
214
|
temperature=temperature,
|
|
180
215
|
max_tokens=max_output_tokens,
|
|
181
216
|
max_retries=max_retries,
|
|
@@ -184,10 +219,12 @@ def main(
|
|
|
184
219
|
commit_message = clean_commit_message(commit_message)
|
|
185
220
|
break # Exit inner loop, continue outer loop
|
|
186
221
|
else:
|
|
187
|
-
console.print(
|
|
222
|
+
console.print(
|
|
223
|
+
"[red]Invalid response. Please enter y (yes), n (no), r (reroll), or r <feedback>.[/red]"
|
|
224
|
+
)
|
|
188
225
|
|
|
189
226
|
# If we got here with 'y', break the outer loop
|
|
190
|
-
if response in ["y", "yes"]:
|
|
227
|
+
if response.lower() in ["y", "yes"]:
|
|
191
228
|
break
|
|
192
229
|
else:
|
|
193
230
|
# No confirmation required, exit loop
|
|
@@ -15,6 +15,22 @@ DEFAULT_TEMPLATE = """<role>
|
|
|
15
15
|
You are an expert git commit message generator. Your task is to analyze code changes and create a concise, meaningful git commit message. You will receive git status and diff information. Your entire response will be used directly as a git commit message.
|
|
16
16
|
</role>
|
|
17
17
|
|
|
18
|
+
<focus>
|
|
19
|
+
Your commit message must reflect the core purpose and impact of these changes.
|
|
20
|
+
Prioritize the primary intent over implementation details.
|
|
21
|
+
Consider what future developers need to understand about this change.
|
|
22
|
+
Identify if this introduces new capabilities, fixes problems, or improves existing code.
|
|
23
|
+
</focus>
|
|
24
|
+
|
|
25
|
+
<mixed_changes>
|
|
26
|
+
When changes span multiple areas:
|
|
27
|
+
- Choose the commit type based on the PRIMARY purpose, not the largest file count
|
|
28
|
+
- Feature additions with supporting tests/docs should use 'feat'
|
|
29
|
+
- Bug fixes with added tests should use 'fix'
|
|
30
|
+
- Refactoring that improves multiple components should use 'refactor'
|
|
31
|
+
- Documentation updates are 'docs' only when that's the sole purpose
|
|
32
|
+
</mixed_changes>
|
|
33
|
+
|
|
18
34
|
<format>
|
|
19
35
|
<one_liner>
|
|
20
36
|
Create a single-line commit message (50-72 characters if possible).
|
|
@@ -31,10 +47,17 @@ You are an expert git commit message generator. Your task is to analyze code cha
|
|
|
31
47
|
</format>
|
|
32
48
|
|
|
33
49
|
<conventions_no_scope>
|
|
34
|
-
You MUST start your commit message with the most appropriate conventional commit prefix
|
|
50
|
+
You MUST start your commit message with the most appropriate conventional commit prefix.
|
|
51
|
+
|
|
52
|
+
IMPORTANT: Check file types FIRST when determining the commit type:
|
|
53
|
+
- If changes are ONLY to documentation files (*.md, *.rst, *.txt in docs/, README*, CHANGELOG*, etc.), ALWAYS use 'docs:'
|
|
54
|
+
- Use 'docs:' ONLY when ALL changes are documentation files - INCLUDING README updates, regardless of how significant the changes are
|
|
55
|
+
- If changes include both documentation and code, use the prefix for the code changes, unless it is a documentation-only change
|
|
56
|
+
|
|
57
|
+
Commit type prefixes:
|
|
35
58
|
- feat: A new feature or functionality addition
|
|
36
59
|
- fix: A bug fix or error correction
|
|
37
|
-
- docs: Documentation changes only
|
|
60
|
+
- docs: Documentation changes only (INCLUDING README updates, regardless of how significant)
|
|
38
61
|
- style: Changes to code style/formatting without logic changes
|
|
39
62
|
- refactor: Code restructuring without behavior changes
|
|
40
63
|
- perf: Performance improvements
|
|
@@ -55,10 +78,14 @@ You MUST write a conventional commit message with EXACTLY ONE type and the REQUI
|
|
|
55
78
|
|
|
56
79
|
FORMAT: type({scope}): description
|
|
57
80
|
|
|
81
|
+
IMPORTANT: Check file types FIRST when determining the commit type:
|
|
82
|
+
- If changes are ONLY to documentation files (*.md, *.rst, *.txt in docs/, README*, CHANGELOG*, etc.), ALWAYS use 'docs'
|
|
83
|
+
- If changes include both documentation and code, use the prefix for the code changes, unless it is a documentation-only change
|
|
84
|
+
|
|
58
85
|
Select ONE type from this list that best matches the primary purpose of the changes:
|
|
59
86
|
- feat: A new feature or functionality addition
|
|
60
87
|
- fix: A bug fix or error correction
|
|
61
|
-
- docs: Documentation changes only
|
|
88
|
+
- docs: Documentation changes only (INCLUDING README and CHANGELOG updates, regardless of how significant)
|
|
62
89
|
- style: Changes to code style/formatting without logic changes
|
|
63
90
|
- refactor: Code restructuring without behavior changes
|
|
64
91
|
- perf: Performance improvements
|
|
@@ -87,10 +114,14 @@ You MUST write a conventional commit message with EXACTLY ONE type and an inferr
|
|
|
87
114
|
|
|
88
115
|
FORMAT: type(scope): description
|
|
89
116
|
|
|
117
|
+
IMPORTANT: Check file types FIRST when determining the commit type:
|
|
118
|
+
- If changes are ONLY to documentation files (*.md, *.rst, *.txt in docs/, README*, CHANGELOG*, etc.), ALWAYS use 'docs'
|
|
119
|
+
- If changes include both documentation and code, use the prefix for the code changes, unless it is a documentation-only change
|
|
120
|
+
|
|
90
121
|
Select ONE type from this list that best matches the primary purpose of the changes:
|
|
91
122
|
- feat: A new feature or functionality addition
|
|
92
123
|
- fix: A bug fix or error correction
|
|
93
|
-
- docs: Documentation changes only
|
|
124
|
+
- docs: Documentation changes only (INCLUDING README and CHANGELOG updates, regardless of how significant)
|
|
94
125
|
- style: Changes to code style/formatting without logic changes
|
|
95
126
|
- refactor: Code restructuring without behavior changes
|
|
96
127
|
- perf: Performance improvements
|
|
@@ -100,6 +131,16 @@ Select ONE type from this list that best matches the primary purpose of the chan
|
|
|
100
131
|
- chore: Miscellaneous changes not affecting src/test files
|
|
101
132
|
|
|
102
133
|
You MUST infer an appropriate scope from the changes. A good scope is concise (usually one word) and indicates the component or area that was changed.
|
|
134
|
+
|
|
135
|
+
<scope_rules>
|
|
136
|
+
For scope inference, select the most specific component affected:
|
|
137
|
+
- Use module/component names from the codebase (auth, api, cli, core)
|
|
138
|
+
- Use functional areas for cross-cutting changes (config, build, test)
|
|
139
|
+
- Keep scopes consistent with existing commit history when possible
|
|
140
|
+
- Prefer established patterns over creating new scope names
|
|
141
|
+
- Use singular form (auth, not auths; test, not tests)
|
|
142
|
+
</scope_rules>
|
|
143
|
+
|
|
103
144
|
Examples of good scopes: api, auth, ui, core, docs, build, prompt, config
|
|
104
145
|
|
|
105
146
|
CORRECT EXAMPLES (these formats are correct):
|
|
@@ -133,6 +174,44 @@ Additional context provided by the user: <hint_text></hint_text>
|
|
|
133
174
|
<diff></diff>
|
|
134
175
|
</git_diff>
|
|
135
176
|
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
<examples_no_scope>
|
|
180
|
+
Good commit messages (no scope):
|
|
181
|
+
[OK] feat: add OAuth2 integration with Google and GitHub
|
|
182
|
+
[OK] fix: resolve race condition in user session management
|
|
183
|
+
[OK] docs: add troubleshooting section for common installation issues
|
|
184
|
+
[OK] refactor: extract validation logic into reusable utilities
|
|
185
|
+
[OK] test: add comprehensive unit tests for token validation
|
|
186
|
+
[OK] build: upgrade to latest security patches
|
|
187
|
+
|
|
188
|
+
Bad commit messages:
|
|
189
|
+
[ERROR] fix stuff
|
|
190
|
+
[ERROR] update code
|
|
191
|
+
[ERROR] feat(auth): add login (scope included when not requested)
|
|
192
|
+
[ERROR] WIP: still working on this
|
|
193
|
+
[ERROR] Fixed bug
|
|
194
|
+
[ERROR] Changes
|
|
195
|
+
</examples_no_scope>
|
|
196
|
+
|
|
197
|
+
<examples_with_scope>
|
|
198
|
+
Good commit messages (with scope):
|
|
199
|
+
[OK] feat(auth): add OAuth2 integration with Google and GitHub
|
|
200
|
+
[OK] fix(api): resolve race condition in user session management
|
|
201
|
+
[OK] docs(readme): add troubleshooting section for common installation issues
|
|
202
|
+
[OK] refactor(core): extract validation logic into reusable utilities
|
|
203
|
+
[OK] test(auth): add comprehensive unit tests for token validation
|
|
204
|
+
[OK] build(deps): upgrade to latest security patches
|
|
205
|
+
|
|
206
|
+
Bad commit messages:
|
|
207
|
+
[ERROR] fix stuff
|
|
208
|
+
[ERROR] update code
|
|
209
|
+
[ERROR] feat: fix(auth): add login (double prefix)
|
|
210
|
+
[ERROR] WIP: still working on this
|
|
211
|
+
[ERROR] Fixed bug
|
|
212
|
+
[ERROR] Changes
|
|
213
|
+
</examples_with_scope>
|
|
214
|
+
|
|
136
215
|
<instructions>
|
|
137
216
|
IMMEDIATELY AFTER ANALYZING THE CHANGES, RESPOND WITH ONLY THE COMMIT MESSAGE.
|
|
138
217
|
DO NOT include any preamble, reasoning, explanations or anything other than the commit message itself.
|
|
@@ -237,6 +316,18 @@ def build_prompt(
|
|
|
237
316
|
else:
|
|
238
317
|
template = re.sub(r"<one_liner>.*?</one_liner>", "", template, flags=re.DOTALL)
|
|
239
318
|
|
|
319
|
+
# Clean up examples sections based on scope settings
|
|
320
|
+
if scope is None:
|
|
321
|
+
# No scope - keep no_scope examples, remove scope examples
|
|
322
|
+
template = re.sub(r"<examples_with_scope>.*?</examples_with_scope>\n?", "", template, flags=re.DOTALL)
|
|
323
|
+
template = template.replace("<examples_no_scope>", "<examples>")
|
|
324
|
+
template = template.replace("</examples_no_scope>", "</examples>")
|
|
325
|
+
else:
|
|
326
|
+
# With scope (either provided or inferred) - keep scope examples, remove no_scope examples
|
|
327
|
+
template = re.sub(r"<examples_no_scope>.*?</examples_no_scope>\n?", "", template, flags=re.DOTALL)
|
|
328
|
+
template = template.replace("<examples_with_scope>", "<examples>")
|
|
329
|
+
template = template.replace("</examples_with_scope>", "</examples>")
|
|
330
|
+
|
|
240
331
|
# Clean up extra whitespace, collapsing blank lines that may contain spaces
|
|
241
332
|
template = re.sub(r"\n(?:[ \t]*\n){2,}", "\n\n", template)
|
|
242
333
|
|
|
@@ -102,12 +102,12 @@ def run_subprocess(
|
|
|
102
102
|
timeout=timeout,
|
|
103
103
|
)
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
should_raise = result.returncode != 0 and (check or raise_on_error)
|
|
106
|
+
|
|
107
|
+
if should_raise:
|
|
106
108
|
if not silent:
|
|
107
109
|
logger.debug(f"Command stderr: {result.stderr}")
|
|
108
|
-
|
|
109
|
-
error = subprocess.CalledProcessError(result.returncode, command, result.stdout, result.stderr)
|
|
110
|
-
raise error
|
|
110
|
+
raise subprocess.CalledProcessError(result.returncode, command, result.stdout, result.stderr)
|
|
111
111
|
|
|
112
112
|
output = result.stdout
|
|
113
113
|
if strip_output:
|
|
@@ -119,7 +119,7 @@ def run_subprocess(
|
|
|
119
119
|
raise GacError(f"Command timed out: {' '.join(command)}") from e
|
|
120
120
|
except subprocess.CalledProcessError as e:
|
|
121
121
|
if not silent:
|
|
122
|
-
logger.error(f"Command failed: {e.stderr.strip() if
|
|
122
|
+
logger.error(f"Command failed: {e.stderr.strip() if e.stderr else str(e)}")
|
|
123
123
|
if raise_on_error:
|
|
124
124
|
raise
|
|
125
125
|
return ""
|
|
@@ -128,6 +128,5 @@ def run_subprocess(
|
|
|
128
128
|
logger.debug(f"Command error: {e}")
|
|
129
129
|
if raise_on_error:
|
|
130
130
|
# Convert generic exceptions to CalledProcessError for consistency
|
|
131
|
-
|
|
132
|
-
raise error from e
|
|
131
|
+
raise subprocess.CalledProcessError(1, command, "", str(e)) from e
|
|
133
132
|
return ""
|
|
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
|