-
Notifications
You must be signed in to change notification settings - Fork 14
Fix branch error handling #468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
β¦ser guidance why: When vcspull encounters branch-related errors (like local branches with no remote tracking), it fails with cryptic git error messages that don't help users understand how to fix their configuration. what: - Add BRANCH_ERROR_MSG constant with clear, actionable error template - Add handle_branch_error() function to detect available remote branches - Enhance update_repo() to catch CommandError exceptions for branch issues - Show helpful instructions for adding 'rev' field to configuration - Display available remote branches when git is accessible - Gracefully handle cases where git commands fail refs: Addresses issue where 'vcspull sync zod' failed with "invalid upstream" errors
why: Need to ensure branch error detection and user guidance works correctly across different scenarios and edge cases. what: - Add test_handle_branch_error() to verify error message content and branch detection - Add test_handle_branch_error_no_git() to test graceful handling when git fails - Add test_update_repo_branch_error() for integration testing of error flow - Mock subprocess.run and GitSync.update_repo for controlled testing - Test error message formatting and available branch display - Verify CommandError exceptions are properly caught and re-raised refs: Tests for branch error handling feature implementation
β¦t all upstream issues why: The initial implementation only flagged branches that didn't exist on remote, but missed the more common case where branches exist remotely but lack upstream tracking, which still causes libvcs to construct invalid remote references. what: - Update check_branch_state() to flag ANY branch without upstream tracking - This catches both cases: branches that don't exist remotely AND branches that exist remotely but have no upstream configured - Add comprehensive tests for both problematic and good branch states - Fix linting issues and improve code formatting - Test validates the fix works with real repository (zod) showing proper error detection and user guidance refs: Fixes the root cause where vcspull sync would fail silently on branches with upstream tracking issues
Reviewer's GuideAdds proactive branch error detection and user-friendly guidance for branch-related failures in vcspull sync operations, with pre-sync validation, fallback handling, and comprehensive tests. Class diagram for new branch error handling functionsclassDiagram
class SyncHandler {
+check_branch_state(repo_dict: dict) bool
+handle_branch_error(repo_dict: dict) void
+update_repo(repo_dict: dict, ...) Repo
}
SyncHandler <|.. check_branch_state
SyncHandler <|.. handle_branch_error
SyncHandler <|.. update_repo
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codecov Reportβ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #468 +/- ##
==========================================
- Coverage 78.93% 78.58% -0.35%
==========================================
Files 8 8
Lines 413 467 +54
Branches 85 95 +10
==========================================
+ Hits 326 367 +41
- Misses 52 59 +7
- Partials 35 41 +6 β View full report in Codecov by Sentry. π New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes and they look great!
Blocking issues:
- Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location> `src/vcspull/cli/sync.py:225` </location>
<code_context>
+ # Even if the remote branch exists, the lack of upstream tracking
+ # can cause libvcs to construct invalid remote references
+ # Check if there's a remote branch with the same name
+ remote_ref = f"origin/{branch_name}"
+ remote_branch = subprocess.run(
+ ["git", "-C", str(repo_path), "rev-parse", "--verify", remote_ref],
+ capture_output=True,
</code_context>
<issue_to_address>
Assumes 'origin' as remote; may not be correct for all repos.
This approach may fail in repositories where the remote is not named 'origin'. Allow configuring the remote name or detect it automatically.
Suggested implementation:
```python
# Even if the remote branch exists, the lack of upstream tracking
# can cause libvcs to construct invalid remote references
# Check if there's a remote branch with the same name
# Detect remote name automatically
remote_name_proc = subprocess.run(
["git", "-C", str(repo_path), "remote"],
capture_output=True,
text=True,
timeout=5,
check=False,
)
remote_name = "origin"
if remote_name_proc.returncode == 0:
remotes = remote_name_proc.stdout.strip().splitlines()
if remotes:
remote_name = remotes[0] # Use the first remote found
remote_ref = f"{remote_name}/{branch_name}"
remote_branch = subprocess.run(
["git", "-C", str(repo_path), "rev-parse", "--verify", remote_ref],
capture_output=True,
```
If you want to allow the remote name to be configured (e.g., via a function argument or environment variable), you should:
1. Add a parameter to the relevant function to accept `remote_name`, defaulting to None.
2. If `remote_name` is provided, use it; otherwise, detect as above.
3. Optionally, check for an environment variable (e.g., `VCS_REMOTE_NAME`) before falling back to detection.
You may need to propagate this parameter through function calls or configuration.
</issue_to_address>
### Comment 2
<location> `src/vcspull/cli/sync.py:246` </location>
<code_context>
+ return False # Git command failed or not available
+
+
+def handle_branch_error(repo_dict: dict[str, t.Any]) -> None:
+ """Handle branch-related errors by showing helpful instructions.
+
</code_context>
<issue_to_address>
Consider logging errors instead of only printing to stderr.
Using the logger will improve error traceability and make integration with monitoring tools easier.
Suggested implementation:
```python
import logging
import subprocess
import sys
import typing as t
from copy import deepcopy
logger = logging.getLogger(__name__)
```
```python
def handle_branch_error(repo_dict: dict[str, t.Any]) -> None:
"""Handle branch-related errors by showing helpful instructions.
logger.error(
"Branch-related error detected for repository: %s\n"
"Please check your branch configuration and ensure upstream tracking is set correctly.",
repo_dict.get("name", "<unknown>")
)
```
</issue_to_address>
### Comment 3
<location> `tests/test_sync.py:322` </location>
<code_context>
+def test_check_branch_state(tmp_path: pathlib.Path) -> None:
</code_context>
<issue_to_address>
Consider adding tests for detached HEAD and missing repo path edge cases.
Tests currently cover typical branch states but not detached HEAD or missing 'path' in repo_dict. Including these cases will improve coverage and error handling.
</issue_to_address>
### Comment 4
<location> `tests/test_sync.py:411` </location>
<code_context>
+def test_handle_branch_error(
</code_context>
<issue_to_address>
Test only checks first 5 branches; consider parametrizing for different branch counts.
Parametrize the test to cover scenarios with fewer, exactly, and more than 5 branches to improve coverage.
Suggested implementation:
```python
import pytest
@pytest.mark.parametrize(
"branches",
[
["main", "dev"], # fewer than 5
["main", "dev", "feature1", "feature2", "feature3"], # exactly 5
["main", "dev", "feature1", "feature2", "feature3", "hotfix"], # more than 5
],
)
def test_handle_branch_error(
branches,
tmp_path: pathlib.Path,
capsys: pytest.CaptureFixture[str]
) -> None:
"""Test that handle_branch_error shows helpful error message for different branch counts."""
repo_dict = {
"name": "test-repo",
"url": "[email protected]:user/repo.git",
"path": tmp_path / "test-repo",
}
# Create a mock git directory for testing
# Simulate branches in the repo
# You may need to adjust the following lines to fit your repo setup logic
repo_path = repo_dict["path"]
repo_path.mkdir(parents=True, exist_ok=True)
(repo_path / ".git").mkdir()
# Simulate branch list
# If your code uses subprocess to get branches, you may need to patch subprocess.run
# For now, we just show how to use the branches variable in the test
# Example: pass branches to the function or mock the function that lists branches
# ... rest of your test logic here, using the branches variable ...
```
You will need to adjust the test logic to use the `branches` variable, depending on how your code fetches branch names (e.g., patching subprocess or passing directly). If you use subprocess to get branches, consider patching it to return the `branches` list for each test case.
</issue_to_address>
## Security Issues
### Issue 1
<location> `src/vcspull/cli/sync.py:262` </location>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
*Source: opengrep*
</issue_to_address>
Help me be more useful! Please click π or π on each comment and I'll use the feedback to improve your reviews.
remote_ref = f"origin/{branch_name}" | ||
remote_branch = subprocess.run( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Assumes 'origin' as remote; may not be correct for all repos.
This approach may fail in repositories where the remote is not named 'origin'. Allow configuring the remote name or detect it automatically.
Suggested implementation:
# Even if the remote branch exists, the lack of upstream tracking
# can cause libvcs to construct invalid remote references
# Check if there's a remote branch with the same name
# Detect remote name automatically
remote_name_proc = subprocess.run(
["git", "-C", str(repo_path), "remote"],
capture_output=True,
text=True,
timeout=5,
check=False,
)
remote_name = "origin"
if remote_name_proc.returncode == 0:
remotes = remote_name_proc.stdout.strip().splitlines()
if remotes:
remote_name = remotes[0] # Use the first remote found
remote_ref = f"{remote_name}/{branch_name}"
remote_branch = subprocess.run(
["git", "-C", str(repo_path), "rev-parse", "--verify", remote_ref],
capture_output=True,
If you want to allow the remote name to be configured (e.g., via a function argument or environment variable), you should:
- Add a parameter to the relevant function to accept
remote_name
, defaulting to None. - If
remote_name
is provided, use it; otherwise, detect as above. - Optionally, check for an environment variable (e.g.,
VCS_REMOTE_NAME
) before falling back to detection.
You may need to propagate this parameter through function calls or configuration.
return False # Git command failed or not available | ||
|
||
|
||
def handle_branch_error(repo_dict: dict[str, t.Any]) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Consider logging errors instead of only printing to stderr.
Using the logger will improve error traceability and make integration with monitoring tools easier.
Suggested implementation:
import logging
import subprocess
import sys
import typing as t
from copy import deepcopy
logger = logging.getLogger(__name__)
def handle_branch_error(repo_dict: dict[str, t.Any]) -> None:
"""Handle branch-related errors by showing helpful instructions.
logger.error(
"Branch-related error detected for repository: %s\n"
"Please check your branch configuration and ensure upstream tracking is set correctly.",
repo_dict.get("name", "<unknown>")
)
def test_check_branch_state(tmp_path: pathlib.Path) -> None: | ||
"""Test that check_branch_state detects problematic branches.""" | ||
repo_dict = { | ||
"name": "test-repo", | ||
"url": "[email protected]:user/repo.git", | ||
"path": tmp_path / "test-repo", | ||
} | ||
|
||
# Create a mock git directory for testing | ||
(tmp_path / "test-repo" / ".git").mkdir(parents=True, exist_ok=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Consider adding tests for detached HEAD and missing repo path edge cases.
Tests currently cover typical branch states but not detached HEAD or missing 'path' in repo_dict. Including these cases will improve coverage and error handling.
def test_handle_branch_error( | ||
tmp_path: pathlib.Path, capsys: pytest.CaptureFixture[str] | ||
) -> None: | ||
"""Test that handle_branch_error shows helpful error message.""" | ||
repo_dict = { | ||
"name": "test-repo", | ||
"url": "[email protected]:user/repo.git", | ||
"path": tmp_path / "test-repo", | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Test only checks first 5 branches; consider parametrizing for different branch counts.
Parametrize the test to cover scenarios with fewer, exactly, and more than 5 branches to improve coverage.
Suggested implementation:
import pytest
@pytest.mark.parametrize(
"branches",
[
["main", "dev"], # fewer than 5
["main", "dev", "feature1", "feature2", "feature3"], # exactly 5
["main", "dev", "feature1", "feature2", "feature3", "hotfix"], # more than 5
],
)
def test_handle_branch_error(
branches,
tmp_path: pathlib.Path,
capsys: pytest.CaptureFixture[str]
) -> None:
"""Test that handle_branch_error shows helpful error message for different branch counts."""
repo_dict = {
"name": "test-repo",
"url": "[email protected]:user/repo.git",
"path": tmp_path / "test-repo",
}
# Create a mock git directory for testing
# Simulate branches in the repo
# You may need to adjust the following lines to fit your repo setup logic
repo_path = repo_dict["path"]
repo_path.mkdir(parents=True, exist_ok=True)
(repo_path / ".git").mkdir()
# Simulate branch list
# If your code uses subprocess to get branches, you may need to patch subprocess.run
# For now, we just show how to use the branches variable in the test
# Example: pass branches to the function or mock the function that lists branches
# ... rest of your test logic here, using the branches variable ...
You will need to adjust the test logic to use the branches
variable, depending on how your code fetches branch names (e.g., patching subprocess or passing directly). If you use subprocess to get branches, consider patching it to return the branches
list for each test case.
result = subprocess.run( | ||
cmd, capture_output=True, text=True, timeout=5, check=False | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
Source: opengrep
return False | ||
|
||
try: | ||
# Check if we're in a git repository |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): We've found these issues:
- Swap if/else branches (
swap-if-else-branches
) - Remove unnecessary else after guard condition (
remove-unnecessary-else
) - Extract code out into function (
extract-method
) - Lift code into else after jump in control flow (
reintroduce-else
) - Hoist repeated code outside conditional statement (
hoist-statement-from-if
)
branches = [ | ||
line.strip().replace("origin/", "") | ||
for line in result.stdout.splitlines() | ||
if "origin/" in line and "->" not in line | ||
][:5] # Show first 5 branches |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (code-quality): Use named expression to simplify assignment and conditional (use-named-expression
)
Implements proactive branch error detection and helpful user guidance for vcspull sync operations. When users encounter branch-related sync issues, vcspull now provides clear, actionable instructions instead of cryptic git error messages.
Problem
Users experienced confusing failures when running
vcspull sync
on repositories with branch issues:The root cause was that libvcs handles certain git errors internally without re-raising them, so vcspull never had a chance to provide helpful guidance.
Solution
Pre-Validation Approach
Added proactive branch state checking that detects problematic conditions before attempting sync operations:
check_branch_state()
: Validates git repository branch configurationhandle_branch_error()
: Shows clear, actionable error messagesUser Experience Improvement
Before:
After:
Technical Implementation
Core Components
Branch State Detection (
check_branch_state
)Error Message Handler (
handle_branch_error
)Integration (
update_repo
)Error Detection Logic
The function detects problematic branch states by:
Any branch without proper upstream tracking is flagged as potentially problematic, as it can cause libvcs to construct invalid remote references.
Testing
Comprehensive Test Coverage
test_check_branch_state()
: Tests detection of problematic branchestest_check_branch_state_good_branch()
: Verifies good branches aren't flaggedtest_handle_branch_error()
: Tests error message formatting and branch listingtest_handle_branch_error_no_git()
: Tests graceful handling when git unavailabletest_update_repo_branch_error_precheck()
: Tests pre-validation integrationtest_update_repo_branch_error_fallback()
: Tests CommandError fallback handlingReal-World Validation
Tested with actual repository experiencing the issue:
β― uv run vcspull sync zod β οΈ Error syncing 'zod': Branch issue detected [...helpful guidance shown...]
Quality Assurance
Configuration Example
Users can now easily fix branch issues by adding a
rev
field:Backwards Compatibility
Files Changed
src/vcspull/cli/sync.py
: Added branch detection and error handlingtests/test_sync.py
: Added comprehensive test coverageBenefits
Summary by Sourcery
Improve vcspull sync error handling by proactively detecting branch configuration issues and providing actionable user guidance, integrated into the sync workflow with thorough test coverage.
New Features:
Enhancements:
Tests: