Add optional ref query param to /api/git/{changes,diff}#3116
Conversation
Both endpoints currently auto-detect the comparison ref via
get_valid_ref() (origin/<current_branch> -> origin/<default_branch> ->
merge-base -> empty tree), which is the correct behaviour for diffing
a feature branch against the remote. There is no way to ask for
'git status'-style diffs against the local HEAD, which a UI like
agent-canvas's Changes tab needs.
This adds an optional 'ref' query parameter to both endpoints:
GET /api/git/changes?path=<repo>&ref=HEAD
GET /api/git/diff?path=<file>&ref=HEAD
When provided it is resolved via 'git rev-parse --verify <ref>^{commit}'
and used directly as the comparison base, so 'ref=HEAD' yields working
tree + index vs the latest commit (i.e. exactly what 'git status' /
'git diff HEAD' would show). Invalid refs surface a GitCommandError so
callers can distinguish 'no changes' from 'ref not found'. When omitted,
the existing auto-detection behaviour is preserved verbatim, so this is
a purely additive, backwards-compatible change.
The ref is plumbed through get_valid_ref(override=...),
get_changes_in_repo(ref=...), get_git_changes(ref=...) and
get_git_diff(ref=...). New SDK-level tests exercise real temporary
repos with multiple commits to confirm ref='HEAD' yields the
git-status semantics (and that an invalid ref raises). Router tests
verify the parameter is forwarded and that the OpenAPI schema
advertises it as an optional query param.
Co-authored-by: openhands <openhands@all-hands.dev>
Python API breakage checks — ✅ PASSEDResult: ✅ PASSED |
REST API breakage checks (OpenAPI) — ✅ PASSEDResult: ✅ PASSED |
Coverage Report •
|
||||||||||||||||||||||||||||||
all-hands-bot
left a comment
There was a problem hiding this comment.
Taste Rating: 🟢 Good taste
Clean, backward-compatible addition that solves a real need (git-status-style diffs) without affecting existing behavior. The implementation is straightforward:
- When
ref=None(default), preserves existing auto-detection behavior - When
ref='HEAD'(or other ref), usesgit rev-parse --verifyto validate and compare against that ref - Invalid refs surface as
GitCommandErrorso callers can distinguish 'ref not found' from 'no changes'
The tests are comprehensive - they use real git repos to verify the exact semantics rather than just mocking calls.
[RISK ASSESSMENT]
Purely additive change with comprehensive test coverage. No security concerns - git validates the ref parameter via rev-parse --verify. No breaking changes.
VERDICT:
✅ Worth merging
KEY INSIGHT:
Using override parameter in get_valid_ref() elegantly preserves the existing auto-detection fallback chain while enabling explicit ref specification when needed.
all-hands-bot
left a comment
There was a problem hiding this comment.
✅ QA Report: PASS
This PR successfully adds an optional ref query parameter to both git endpoints, enabling git status-style diffs when ref=HEAD is provided while preserving backward compatibility when omitted.
Does this PR achieve its stated goal?
Yes. The PR set out to add an optional ref query parameter to /api/git/changes and /api/git/diff that allows callers to request git status-style diffs against a specific ref (e.g., ref=HEAD) instead of relying on auto-detection. I verified this works correctly by:
- Creating a test repository with multiple commits and uncommitted changes
- Testing SDK functions (
get_changes_in_repo(),get_git_diff()) with and without therefparameter - Starting the agent server and making real HTTP requests to both endpoints
- Verifying that
ref=HEADcorrectly shows only uncommitted changes (git status behavior) - Confirming that omitting
refpreserves the original auto-detection behavior - Validating that invalid refs raise
GitCommandErroras documented
All tests passed, demonstrating the feature works exactly as described in the PR.
| Phase | Result |
|---|---|
| Environment Setup | ✅ Dependencies installed, agent server started successfully |
| CI Status | ✅ All critical checks passing (sdk-tests, agent-server-tests, API validation, pre-commit) |
| Functional Verification | ✅ Both SDK functions and API endpoints work correctly with new ref parameter |
Functional Verification
Test Setup
Created a test git repository with:
- 2 committed files:
committed.txt(first commit),second.txt(second commit) - 1 modified file:
committed.txt(modified in working tree) - 1 untracked file:
untracked.txt
cd /tmp/test-git-ref && git init
echo "baseline content" > committed.txt && git add . && git commit -m "first commit"
echo "second commit content" > second.txt && git add . && git commit -m "second commit"
echo "modified" > committed.txt && echo "untracked content" > untracked.txtSDK-Level Verification
Test 1: get_changes_in_repo() without ref parameter (baseline behavior)
Ran:
changes = get_changes_in_repo("/tmp/test-git-ref")Result:
Found 3 changes:
- ADDED committed.txt
- ADDED second.txt
- ADDED untracked.txt
Interpretation: Without the ref parameter, auto-detection compares against the empty tree (since no remote exists), showing all files as added. This is the original behavior and confirms backward compatibility.
Test 2: get_changes_in_repo() WITH ref='HEAD' (new behavior)
Ran:
changes = get_changes_in_repo("/tmp/test-git-ref", ref="HEAD")Result:
Found 2 changes:
- UPDATED committed.txt
- ADDED untracked.txt
Interpretation: ✅ With ref='HEAD', only uncommitted changes are shown:
committed.txtappears as UPDATED (modified in working tree)untracked.txtappears as ADDED (new file)second.txtis correctly excluded (it's committed at HEAD)
This confirms the feature delivers git status-style behavior.
Test 3: Invalid ref raises GitCommandError
Ran:
changes = get_changes_in_repo("/tmp/test-git-ref", ref="definitely-not-a-real-ref")Result:
GitCommandError: Git command failed: git --no-pager rev-parse --verify 'definitely-not-a-real-ref^{commit}'
Interpretation: ✅ Invalid refs properly raise GitCommandError, allowing callers to distinguish "ref not found" from "no changes".
Test 4: get_git_diff() with ref='HEAD'
Ran:
os.chdir("/tmp/test-git-ref")
diff = get_git_diff("committed.txt", ref="HEAD")Result:
Original content: 'baseline content'
Modified content: 'modified'
Interpretation: ✅ Diff correctly shows HEAD's content as original ("baseline content") and working tree content as modified ("modified").
API Endpoint Verification
Started agent server on http://127.0.0.1:8000 and made real HTTP requests.
Test 5: GET /api/git/changes without ref parameter
Ran:
curl "http://127.0.0.1:8000/api/git/changes?path=/tmp/test-git-ref"Result:
[
{"status": "ADDED", "path": "committed.txt"},
{"status": "ADDED", "path": "second.txt"},
{"status": "ADDED", "path": "untracked.txt"}
]Interpretation: ✅ Auto-detection behavior preserved (backward compatible).
Test 6: GET /api/git/changes?ref=HEAD
Ran:
curl "http://127.0.0.1:8000/api/git/changes?path=/tmp/test-git-ref&ref=HEAD"Result:
[
{"status": "UPDATED", "path": "committed.txt"},
{"status": "ADDED", "path": "untracked.txt"}
]Interpretation: ✅ Only uncommitted changes returned. second.txt correctly excluded.
Test 7: Invalid ref returns 500 with error message
Ran:
curl "http://127.0.0.1:8000/api/git/changes?path=/tmp/test-git-ref&ref=invalid-ref-xyz"Result:
{"detail":"Internal Server Error","exception":"Git command failed: git --no-pager rev-parse --verify 'invalid-ref-xyz^{commit}'"}Status: 500
Interpretation: ✅ Invalid refs properly surface as 500 errors with clear error messages.
Test 8: GET /api/git/diff?ref=HEAD
Ran:
curl "http://127.0.0.1:8000/api/git/diff?path=/tmp/test-git-ref/committed.txt&ref=HEAD"Result:
{"original": "baseline content", "modified": "modified"}Interpretation: ✅ Diff endpoint correctly uses ref=HEAD as comparison base.
Test 9: OpenAPI schema includes ref parameter
Ran:
curl "http://127.0.0.1:8000/openapi.json" | jq '.paths["/api/git/changes"].get.parameters'Result:
[
{"name": "path", "in": "query", "required": true, ...},
{"name": "ref", "in": "query", "required": false,
"description": "Optional git ref to diff against (e.g. 'HEAD' for git status-style changes, or a commit hash). When omitted, the upstream/default branch is auto-detected."}
]Interpretation: ✅ Both /api/git/changes and /api/git/diff properly document the new optional ref parameter in the OpenAPI schema.
Unit Test Verification
All new unit tests pass:
✅ test_get_changes_in_repo_ref_head_shows_only_uncommitted PASSED
✅ test_get_changes_in_repo_invalid_ref_raises PASSED
✅ test_get_git_changes_propagates_ref PASSED
✅ test_get_git_diff_ref_head_compares_against_latest_commit PASSED
✅ test_get_git_diff_invalid_ref_raises PASSED
✅ test_git_changes_forwards_ref_query_param PASSED
✅ test_git_diff_forwards_ref_query_param PASSED
✅ test_git_endpoints_expose_ref_query_param PASSEDExisting tests (without ref parameter) also pass, confirming backward compatibility:
✅ test_git_changes_query_param_success PASSED
✅ test_git_diff_query_param_success PASSEDIssues Found
None.
Co-authored-by: openhands <openhands@all-hands.dev>
Both endpoints currently auto-detect the comparison ref via get_valid_ref() (origin/<current_branch> -> origin/<default_branch> -> merge-base -> empty tree), which is the correct behaviour for diffing a feature branch against the remote. There is no way to ask for 'git status'-style diffs against the local HEAD, which a UI like agent-canvas's Changes tab needs.
This adds an optional 'ref' query parameter to both endpoints:
GET /api/git/changes?path=&ref=HEAD
GET /api/git/diff?path=&ref=HEAD
When provided it is resolved via 'git rev-parse --verify ^{commit}' and used directly as the comparison base, so 'ref=HEAD' yields working tree + index vs the latest commit (i.e. exactly what 'git status' / 'git diff HEAD' would show). Invalid refs surface a GitCommandError so callers can distinguish 'no changes' from 'ref not found'. When omitted, the existing auto-detection behaviour is preserved verbatim, so this is a purely additive, backwards-compatible change.
The ref is plumbed through get_valid_ref(override=...), get_changes_in_repo(ref=...), get_git_changes(ref=...) and get_git_diff(ref=...). New SDK-level tests exercise real temporary repos with multiple commits to confirm ref='HEAD' yields the git-status semantics (and that an invalid ref raises). Router tests verify the parameter is forwarded and that the OpenAPI schema advertises it as an optional query param.
Why
Summary
Issue Number
How to Test
Video/Screenshots
Type
Notes
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:0c65267-pythonRun
All tags pushed for this build
About Multi-Architecture Support
0c65267-python) is a multi-arch manifest supporting both amd64 and arm640c65267-python-amd64) are also available if needed