fix(consistency): correct behind-remote detection to not trigger on local-ahead
_detect_behind_remote was comparing HEAD != @{u} which incorrectly
triggered C-16 when the local repo had unpushed commits. Fixed to use
git rev-list --count HEAD..@{u} which only counts commits the remote
has that local lacks. Adds test_returns_false_when_local_ahead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -748,8 +748,10 @@ def _check_ghost_duplicates(
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _detect_behind_remote(repo_path: str) -> bool:
|
||||
"""Return True if the local repo is behind its remote tracking branch.
|
||||
"""Return True if the remote tracking branch has commits the local repo lacks.
|
||||
|
||||
"Ahead" (local has unpushed commits) is NOT considered behind.
|
||||
"Diverged" is treated as behind (remote progress could be lost).
|
||||
Best-effort: returns False on any error (offline, no remote, etc.) so that
|
||||
check-only mode is never blocked by network issues.
|
||||
"""
|
||||
@@ -758,15 +760,15 @@ def _detect_behind_remote(repo_path: str) -> bool:
|
||||
["git", "-C", repo_path, "fetch", "--quiet", "origin"],
|
||||
capture_output=True, timeout=15,
|
||||
)
|
||||
local = subprocess.run(
|
||||
["git", "-C", repo_path, "rev-parse", "HEAD"],
|
||||
# Count commits in remote that are not in local.
|
||||
# git rev-list HEAD..@{u} → commits remote has that local lacks.
|
||||
result = subprocess.run(
|
||||
["git", "-C", repo_path, "rev-list", "--count", "HEAD..@{u}"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
).stdout.strip()
|
||||
remote = subprocess.run(
|
||||
["git", "-C", repo_path, "rev-parse", "@{u}"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
).stdout.strip()
|
||||
return bool(local and remote and local != remote)
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return False # no upstream configured or other error
|
||||
return int(result.stdout.strip() or "0") > 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@@ -450,6 +450,26 @@ class TestDetectBehindRemote:
|
||||
subprocess.run(["git", "-C", str(clone), "push", "origin", "HEAD"], capture_output=True)
|
||||
assert _detect_behind_remote(str(clone)) is False
|
||||
|
||||
def test_returns_false_when_local_ahead(self, tmp_path):
|
||||
"""Local has unpushed commits — NOT considered behind."""
|
||||
import subprocess
|
||||
bare = tmp_path / "bare.git"
|
||||
clone = tmp_path / "clone"
|
||||
bare.mkdir()
|
||||
subprocess.run(["git", "init", "--bare", str(bare)], capture_output=True)
|
||||
subprocess.run(["git", "clone", str(bare), str(clone)], capture_output=True)
|
||||
subprocess.run(
|
||||
["git", "-C", str(clone), "commit", "--allow-empty", "-m", "init"],
|
||||
capture_output=True,
|
||||
)
|
||||
subprocess.run(["git", "-C", str(clone), "push", "origin", "HEAD:main"], capture_output=True)
|
||||
# Add a local-only commit (not pushed)
|
||||
subprocess.run(
|
||||
["git", "-C", str(clone), "commit", "--allow-empty", "-m", "local only"],
|
||||
capture_output=True,
|
||||
)
|
||||
assert _detect_behind_remote(str(clone)) is False
|
||||
|
||||
def test_returns_true_when_behind(self, tmp_path):
|
||||
"""Remote has a commit the clone doesn't → clone is behind."""
|
||||
import subprocess
|
||||
|
||||
Reference in New Issue
Block a user