Skip to content

Commit f41f090

Browse files
author
lpb-docs
committed
fix(lpb-config): status reported ahead as behind (swapped is-ancestor check)
cmd_status used 'merge-base --is-ancestor HEAD origin/<ref>': exit 0 means local is BEHIND (or equal) but was reported 'up to date (or ahead)', while local AHEAD fell into the 'behind remote' branch. Replace with 'rev-list --left-right --count' so status distinguishes up-to-date / ahead / behind / diverged with exact counts. Also use 'rev-parse --verify' for origin/<ref> in status/update/reset/merge: plain rev-parse echoes the ref name to stdout on failure, producing a fake remote head ('origin/main' string) that bypassed the empty guards. Tests: status ahead/behind/up-to-date/diverged + missing-ref regression.
1 parent 638294e commit f41f090

2 files changed

Lines changed: 98 additions & 8 deletions

File tree

scripts/lpb-config

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ def cmd_status(agent_dir: str | Path, remote: str, ref: str, cons: Console) -> i
298298
cons.warn("No remote configured")
299299
return 0
300300

301-
remote_head = git(agent_dir, "rev-parse", f"origin/{ref}")[0].strip()
301+
remote_head = git(agent_dir, "rev-parse", "--verify", f"origin/{ref}")[0].strip()
302302
if remote_head:
303303
cons.info(f"Remote: {remote_head[:8]} origin/{ref} (last fetch)")
304304

@@ -311,11 +311,28 @@ def cmd_status(agent_dir: str | Path, remote: str, ref: str, cons: Console) -> i
311311
cons.info("Working tree: clean")
312312

313313
if remote_head:
314-
_, _, code = git(agent_dir, "merge-base", "--is-ancestor", cur, remote_head)
315-
if code == 0:
316-
cons.info("Status: up to date (or ahead)")
314+
out, _, code = git(agent_dir, "rev-list", "--left-right", "--count",
315+
f"HEAD...origin/{ref}")
316+
if code:
317+
cons.warn(f"Status: could not compare with origin/{ref}")
317318
else:
318-
cons.warn("Status: behind remote — run 'lpb-config update' to fetch")
319+
parts = out.split()
320+
ahead_n = int(parts[0]) if len(parts) > 0 else 0
321+
behind_n = int(parts[1]) if len(parts) > 1 else 0
322+
if ahead_n == 0 and behind_n == 0:
323+
cons.info(f"Status: up to date with origin/{ref}")
324+
elif ahead_n == 0:
325+
cons.warn(f"Status: behind origin/{ref} by {behind_n} — "
326+
f"run 'lpb-config update' to fetch")
327+
elif behind_n == 0:
328+
cons.info(f"Status: ahead of origin/{ref} by {ahead_n} "
329+
f"(unpushed commit(s))")
330+
else:
331+
cons.warn(f"Status: diverged from origin/{ref} (ahead {ahead_n}, "
332+
f"behind {behind_n}) — run 'lpb-config merge'")
333+
else:
334+
cons.warn(f"Status: origin/{ref} not found locally — "
335+
f"run 'lpb-config update' to fetch")
319336
return 0
320337

321338

@@ -334,7 +351,7 @@ def cmd_update(agent_dir: str | Path, remote: str, ref: str, cons: Console) -> i
334351
cons.error(f"Failed to fetch from {remote}: {err.strip() or out.strip()}")
335352
return 1
336353

337-
remote_head = git(agent_dir, "rev-parse", f"origin/{ref}")[0].strip()
354+
remote_head = git(agent_dir, "rev-parse", "--verify", f"origin/{ref}")[0].strip()
338355
cur_head = git(agent_dir, "rev-parse", "HEAD")[0].strip()
339356
if remote_head == cur_head:
340357
cons.info("Already up to date.")
@@ -387,7 +404,7 @@ def cmd_reset(
387404

388405
cons.warn(f"This will destroy ALL local changes in {agent_dir}.")
389406
cons.raw(f" Current: {head_short(agent_dir)}")
390-
remote_head = git(agent_dir, "rev-parse", f"origin/{ref}")[0].strip()[:8]
407+
remote_head = git(agent_dir, "rev-parse", "--verify", f"origin/{ref}")[0].strip()[:8]
391408
cons.raw(f" Remote: {remote_head}")
392409

393410
if not force and not confirm(" Continue?", default=False, inp=inp):
@@ -422,7 +439,7 @@ def cmd_merge(agent_dir: str | Path, remote: str, ref: str, cons: Console) -> in
422439
cons.info(f"Fetching latest from {remote}...")
423440
git(agent_dir, "fetch", "origin", ref)
424441

425-
remote_head = git(agent_dir, "rev-parse", f"origin/{ref}")[0].strip()
442+
remote_head = git(agent_dir, "rev-parse", "--verify", f"origin/{ref}")[0].strip()
426443
cur_head = git(agent_dir, "rev-parse", "HEAD")[0].strip()
427444
if remote_head == cur_head:
428445
cons.info("Already up to date.")

scripts/test_localpibox_config.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,79 @@ def test_lpb_config_status_states(tmpdir):
141141
assert "No config repo" in (out2.getvalue() + err2.getvalue())
142142

143143

144+
def _status_text(tmpdir, agent, remote):
145+
out, err = io.StringIO(), io.StringIO()
146+
cons = log_mod.Console(color=False, out=out, err=err)
147+
assert lc.cmd_status(agent, str(remote), "main", cons) == 0
148+
return out.getvalue() + err.getvalue()
149+
150+
151+
def test_lpb_config_status_ahead_not_behind(tmpdir):
152+
"""Local commits not pushed must report ahead, not 'behind remote'."""
153+
remote = _setup_git_remote(tmpdir)
154+
agent = tmpdir / "agent"
155+
lc.cmd_update(agent, str(remote), "main", _quiet_console())
156+
(agent / "f").write_text("local only")
157+
subprocess.run(["git", "-C", str(agent), "config", "user.email", "t@t"], check=True)
158+
subprocess.run(["git", "-C", str(agent), "config", "user.name", "t"], check=True)
159+
subprocess.run(["git", "-C", str(agent), "commit", "-qam", "local"], check=True)
160+
text = _status_text(tmpdir, agent, remote)
161+
assert "ahead of origin/main by 1" in text
162+
assert "behind" not in text
163+
164+
165+
def test_lpb_config_status_behind(tmpdir):
166+
remote = _setup_git_remote(tmpdir)
167+
agent = tmpdir / "agent"
168+
lc.cmd_update(agent, str(remote), "main", _quiet_console())
169+
_push_commit(tmpdir / "work", "two", "two")
170+
subprocess.run(["git", "-C", str(agent), "fetch", "-q", "origin", "main"], check=True)
171+
text = _status_text(tmpdir, agent, remote)
172+
assert "behind origin/main by 1" in text
173+
assert "run 'lpb-config update'" in text
174+
assert "ahead" not in text
175+
176+
177+
def test_lpb_config_status_up_to_date(tmpdir):
178+
remote = _setup_git_remote(tmpdir)
179+
agent = tmpdir / "agent"
180+
lc.cmd_update(agent, str(remote), "main", _quiet_console())
181+
text = _status_text(tmpdir, agent, remote)
182+
assert "up to date with origin/main" in text
183+
184+
185+
def test_lpb_config_status_missing_remote_ref(tmpdir):
186+
"""origin/<ref> not fetched locally: no fake remote head, no crash, no 'behind'."""
187+
remote = _setup_git_remote(tmpdir)
188+
agent = tmpdir / "agent"
189+
# .git present but no origin ref at all (ref name the remote doesn't have)
190+
agent.mkdir()
191+
subprocess.run(["git", "-C", str(agent), "init", "-q", "-b", "main"], check=True)
192+
subprocess.run(["git", "-C", str(agent), "remote", "add", "origin", str(remote)], check=True)
193+
subprocess.run(["git", "-C", str(agent), "config", "user.email", "t@t"], check=True)
194+
subprocess.run(["git", "-C", str(agent), "config", "user.name", "t"], check=True)
195+
(agent / "f").write_text("x")
196+
subprocess.run(["git", "-C", str(agent), "add", "."], check=True)
197+
subprocess.run(["git", "-C", str(agent), "commit", "-qm", "x"], check=True)
198+
text = _status_text(tmpdir, agent, remote)
199+
assert "behind" not in text
200+
assert "not found locally" in text
201+
202+
203+
def test_lpb_config_status_diverged(tmpdir):
204+
remote = _setup_git_remote(tmpdir)
205+
agent = tmpdir / "agent"
206+
lc.cmd_update(agent, str(remote), "main", _quiet_console())
207+
(agent / "f").write_text("local only")
208+
subprocess.run(["git", "-C", str(agent), "config", "user.email", "t@t"], check=True)
209+
subprocess.run(["git", "-C", str(agent), "config", "user.name", "t"], check=True)
210+
subprocess.run(["git", "-C", str(agent), "commit", "-qam", "local"], check=True)
211+
_push_commit(tmpdir / "work", "remote", "remote")
212+
subprocess.run(["git", "-C", str(agent), "fetch", "-q", "origin", "main"], check=True)
213+
text = _status_text(tmpdir, agent, remote)
214+
assert "diverged" in text and "ahead 1" in text and "behind 1" in text
215+
216+
144217
def test_lpb_config_merge_uptodate(tmpdir):
145218
remote = _setup_git_remote(tmpdir)
146219
agent = tmpdir / "agent"

0 commit comments

Comments
 (0)