1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "scripts"))
import denylist_check as D # noqa: E402
ENV = {**os.environ, "GIT_AUTHOR_NAME": "t", "GIT_AUTHOR_EMAIL": "t@t", "GIT_COMMITTER_NAME": "t",
"GIT_COMMITTER_EMAIL": "t@t", "GIT_CONFIG_GLOBAL": "/dev/null", "GIT_CONFIG_NOSYSTEM": "1"}
def git(cwd, *args, check=True):
return subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True, env=ENV, check=check)
class ParseTest(unittest.TestCase):
def test_words_comments_exemptions(self):
words, exempt = D.parse("# c\nSecretProj\n\n Bob \n!bobcat\n")
self.assertEqual(words, [(2, "secretproj"), (4, "bob")])
self.assertEqual(exempt, ["bobcat"])
def test_hits_case_insensitive_and_exempt(self):
words, exempt = [(1, "bob"), (2, "secretproj")], ["bobcat"]
self.assertEqual(D.hits("a BOB and a Bobcat", words, exempt), [1])
self.assertEqual(D.hits("only a bobcat here", words, exempt), [])
self.assertEqual(D.hits("x SecretProj y", words, exempt), [2])
self.assertEqual(D.hits("clean", words, exempt), [])
class HookTest(unittest.TestCase):
"""Real temp repo with the committed hook dir; commits through git itself."""
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.repo = Path(self.tmp.name) / "r"
self.repo.mkdir()
git(self.repo, "init", "-q", "-b", "master")
shutil.copytree(ROOT / ".githooks", self.repo / ".githooks")
(self.repo / "scripts").mkdir()
shutil.copy(ROOT / "scripts" / "denylist_check.py", self.repo / "scripts")
(self.repo / ".gitignore").write_text("publish-denylist.local\n")
git(self.repo, "config", "core.hooksPath", ".githooks")
(self.repo / "publish-denylist.local").write_text("# private\nSecretProj\n!secretprojector\n")
self.put("a.txt", "hello\n")
git(self.repo, "add", ".githooks", "scripts", ".gitignore")
r = self.commit("init")
self.assertEqual(r.returncode, 0, r.stderr)
def tearDown(self):
self.tmp.cleanup()
def put(self, path, text):
f = self.repo / path
f.parent.mkdir(parents=True, exist_ok=True)
f.write_text(text)
git(self.repo, "add", path)
def commit(self, msg, cwd=None):
return git(cwd or self.repo, "commit", "-q", "-m", msg, check=False)
def head(self, cwd=None):
return git(cwd or self.repo, "log", "--format=%s").stdout.splitlines()
def test_content_hit_blocks_without_printing_word(self):
self.put("b.txt", "x\nsee secretproj docs\n")
r = self.commit("add b")
self.assertEqual(r.returncode, 1)
self.assertIn("b.txt:2: word #2", r.stderr)
self.assertNotIn("secretproj", r.stderr.lower())
self.assertEqual(self.head(), ["init"])
def test_path_hit_blocks(self):
self.put("docs/SECRETPROJ-notes.md", "clean\n")
r = self.commit("add notes")
self.assertEqual(r.returncode, 1)
self.assertIn("path docs/***-notes.md: word #2", r.stderr)
self.assertNotIn("secretproj", r.stderr.lower())
def test_message_hit_blocks(self):
self.put("b.txt", "clean\n")
r = self.commit("port from SecretProj")
self.assertEqual(r.returncode, 1)
self.assertIn("commit message:1: word #2", r.stderr)
self.assertEqual(self.head(), ["init"])
def test_clean_and_exempt_pass(self):
self.put("b.txt", "the secretprojector is fine\n")
r = self.commit("clean msg")
self.assertEqual(r.returncode, 0, r.stderr)
self.assertEqual(self.head(), ["clean msg", "init"])
def test_only_added_lines_count(self):
(self.repo / "publish-denylist.local").write_text("") # let an old hit in
self.put("old.txt", "SecretProj legacy\n")
self.assertEqual(self.commit("old").returncode, 0)
(self.repo / "publish-denylist.local").write_text("SecretProj\n")
self.put("old.txt", "SecretProj legacy\nnew clean line\n")
r = self.commit("touch old")
self.assertEqual(r.returncode, 0, r.stderr)
def test_missing_denylist_warns_not_fails(self):
(self.repo / "publish-denylist.local").unlink()
self.put("b.txt", "SecretProj\n")
r = self.commit("no list")
self.assertEqual(r.returncode, 0, r.stderr)
self.assertIn("warning: no publish-denylist.local", r.stderr)
def test_linked_worktree_uses_main_tree_list(self):
wt = Path(self.tmp.name) / "wt"
git(self.repo, "worktree", "add", "-q", str(wt), "-b", "topic")
self.assertFalse((wt / "publish-denylist.local").exists())
(wt / "c.txt").write_text("SecretProj\n")
git(wt, "add", "c.txt")
r = self.commit("wt", cwd=wt)
self.assertEqual(r.returncode, 1)
self.assertIn("c.txt:1: word #2", r.stderr)
def test_extra_list_from_git_config_merged(self):
glob = Path(self.tmp.name) / "global.txt"
glob.write_text("Alpha\n")
git(self.repo, "config", "--add", "denylist.file", str(glob))
self.put("b.txt", "alpha\n")
r = self.commit("b")
self.assertEqual(r.returncode, 1)
self.assertIn("b.txt:1: global.txt word #1", r.stderr)
self.assertNotIn("alpha", r.stderr.lower())
def test_tree_mode_scans_tracked_files(self):
(self.repo / "publish-denylist.local").write_text("")
self.put("old.txt", "x SecretProj\n")
self.assertEqual(self.commit("old").returncode, 0)
(self.repo / "publish-denylist.local").write_text("SecretProj\n")
r = subprocess.run([sys.executable, "scripts/denylist_check.py", "tree"], cwd=self.repo,
capture_output=True, text=True, env=ENV)
self.assertEqual(r.returncode, 1)
self.assertIn("old.txt:1: word #1", r.stderr)
if __name__ == "__main__":
unittest.main()
|