mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-30 01:39:13 +00:00
fix(update): address review of the index.lock fix
- The automatic update's status check now sets GIT_OPTIONAL_LOCKS=0 whichever runner it is given, not only the default one. - Update Code no longer runs `git rebase --abort` on any rebase dir: a rebase in progress (detached HEAD) holds the user's conflict work and blocks the update; an abandoned one is dropped with `rebase --quit`, as the automatic update does, and a failure to clear it stops the update. - run_git waits out the lock-freshness window after stopping a timed-out git, so a lock created just before the timeout is not left behind. - Mark the Popen/subprocess use with the repo's nosec comments. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
e506c1b576
commit
67b373fca0
@@ -243,3 +243,83 @@ def test_run_git_timeout_stops_the_group_and_clears_the_lock(tmp_path, monkeypat
|
||||
|
||||
def test_read_only_env_disables_optional_locks():
|
||||
assert gil.read_only_env()['GIT_OPTIONAL_LOCKS'] == '0'
|
||||
|
||||
|
||||
def test_run_git_waits_out_the_freshness_window_before_giving_up_on_the_lock(tmp_path, monkeypatch):
|
||||
# The lock is created a moment before the timeout, so on age alone it is
|
||||
# still "in use" once git is stopped; it must not be left to block the retry.
|
||||
(tmp_path / '.git').mkdir()
|
||||
lock = tmp_path / '.git' / 'index.lock'
|
||||
script = tmp_path / 'slow.sh'
|
||||
script.write_text(f'#!/bin/sh\n: > "{lock}"\nexec sleep 60\n')
|
||||
script.chmod(0o755)
|
||||
monkeypatch.setattr(gil, 'MIN_STALE_AGE_SECONDS', 2)
|
||||
monkeypatch.setattr(gil, 'TERM_GRACE_SECONDS', 1)
|
||||
with pytest.raises(subprocess.TimeoutExpired):
|
||||
gil.run_git([str(script)], cwd=str(tmp_path), timeout=1)
|
||||
assert not lock.exists()
|
||||
|
||||
|
||||
# -- a rebase in the checkout ----------------------------------------------------
|
||||
|
||||
def _rebase_checkout(tmp_path, monkeypatch):
|
||||
from web_interface.blueprints.api_v3 import system
|
||||
(tmp_path / '.git' / 'rebase-merge').mkdir(parents=True)
|
||||
monkeypatch.setattr(system, 'PROJECT_ROOT', tmp_path)
|
||||
return tmp_path / '.git' / 'rebase-merge'
|
||||
|
||||
|
||||
def _rebase_git(calls, on_branch, quit_rc=0, remove_dir=None):
|
||||
base = _fake_git(calls)
|
||||
|
||||
def run(args, **kwargs):
|
||||
if args[:2] == ['git', 'symbolic-ref']:
|
||||
calls.append(list(args))
|
||||
return subprocess.CompletedProcess(args, 0 if on_branch else 1, stdout='', stderr='')
|
||||
if args[:3] == ['git', 'rebase', '--quit']:
|
||||
calls.append(list(args))
|
||||
if quit_rc == 0 and remove_dir is not None:
|
||||
remove_dir.rmdir()
|
||||
return subprocess.CompletedProcess(args, quit_rc, stdout='', stderr='cannot quit' if quit_rc else '')
|
||||
return base(args, **kwargs)
|
||||
return run
|
||||
|
||||
|
||||
def test_a_rebase_in_progress_is_left_alone(client, tmp_path, monkeypatch):
|
||||
_rebase_checkout(tmp_path, monkeypatch)
|
||||
calls = []
|
||||
with _patched(_rebase_git(calls, on_branch=False)):
|
||||
data = _update(client)
|
||||
assert data['status'] == 'error' and 'rebase is in progress' in data['message']
|
||||
assert not any(c[:2] in (['git', 'pull'], ['git', 'checkout']) or c[:3] == ['git', 'rebase', '--quit']
|
||||
for c in calls), "a paused rebase holds the user's conflict work"
|
||||
|
||||
|
||||
def test_an_abandoned_rebase_is_quit_before_the_pull(client, tmp_path, monkeypatch):
|
||||
rebase = _rebase_checkout(tmp_path, monkeypatch)
|
||||
calls = []
|
||||
with _patched(_rebase_git(calls, on_branch=True, remove_dir=rebase)):
|
||||
_update(client)
|
||||
names = [c[1] for c in calls]
|
||||
assert ['git', 'rebase', '--quit'] in calls
|
||||
assert names.index('rebase') < names.index('pull')
|
||||
|
||||
|
||||
def test_a_rebase_that_cannot_be_cleared_stops_the_update(client, tmp_path, monkeypatch):
|
||||
_rebase_checkout(tmp_path, monkeypatch)
|
||||
calls = []
|
||||
with _patched(_rebase_git(calls, on_branch=True, quit_rc=1)):
|
||||
data = _update(client)
|
||||
assert data['status'] == 'error' and 'could not be cleared' in data['message']
|
||||
assert not any(c[:2] == ['git', 'pull'] for c in calls)
|
||||
|
||||
|
||||
def test_the_automatic_status_check_is_read_only_whatever_the_runner(tmp_path):
|
||||
from web_interface import auto_update
|
||||
seen = {}
|
||||
|
||||
def run(args, **kwargs):
|
||||
seen.update(kwargs)
|
||||
return subprocess.CompletedProcess(args, 0, stdout='', stderr='')
|
||||
auto_update.local_changes(tmp_path, run=run)
|
||||
assert seen['env']['GIT_OPTIONAL_LOCKS'] == '0'
|
||||
|
||||
Reference in New Issue
Block a user