diff --git a/scripts/method_core.py b/scripts/method_core.py index 4037344..4b2577c 100644 --- a/scripts/method_core.py +++ b/scripts/method_core.py @@ -75,7 +75,8 @@ def check(root, *, allow_transaction=False): path = contained(root,item['path']) if not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != item['sha256']: raise ValueError(f'core drift: {item["path"]}') - if path.stat().st_mode & 0o777 != int(item['mode'],8) & 0o777: + # Git records only the executable bit; group/other bits follow the installing umask. + if bool(path.stat().st_mode & 0o100) != (item['mode'] == '100755'): raise ValueError(f'core mode drift: {item["path"]}') return lock diff --git a/tests/test_core_sync.py b/tests/test_core_sync.py index 41f6fb7..1b09ba1 100644 --- a/tests/test_core_sync.py +++ b/tests/test_core_sync.py @@ -67,6 +67,13 @@ def test_malformed_lock_and_mode_drift_are_not_overwritten(self): self.assertNotEqual(0,self.sync('--source-root',str(self.source)).returncode) (self.target/'method-core.lock.json').write_text('{"files": []}') self.assertNotEqual(0,self.sync('--check').returncode) + def test_umask_dependent_permission_bits_are_not_mode_drift(self): + # A host umask of 0002 installs 664/775 copies; only the executable bit is recorded by Git. + self.install() + (self.target/'shared/platform/codex.md').chmod(0o664);(self.target/'scripts/project-state').chmod(0o775) + r=self.sync('--check');self.assertEqual(0,r.returncode,r.stdout+r.stderr) + (self.target/'shared/platform/codex.md').chmod(0o755) + self.assertNotEqual(0,self.sync('--check').returncode) def test_update_adds_and_removes_only_owned_exports(self): self.install();self.put('new.txt',b'new');self.manifest(['new.txt','scripts/project-state']);self.commit();self.install() self.assertFalse((self.target/'shared/platform/codex.md').exists());self.assertEqual(b'new',(self.target/'new.txt').read_bytes())