Skip to content

Commit 5fe2af0

Browse files
committed
Maintainership converter command line tool
1 parent a13a40b commit 5fe2af0

2 files changed

Lines changed: 162 additions & 0 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import osc.commandline_git
2+
3+
4+
class MaintainershipCommand(osc.commandline_git.GitObsCommand):
5+
"""
6+
Read _maintainership.json and convert it from legacy format to the current format
7+
"""
8+
9+
name = "maintainership-converter"
10+
11+
def init_arguments(self):
12+
self.add_argument(
13+
"path",
14+
nargs="?",
15+
default="_maintainership.json",
16+
help="Path to the _maintainership.json file (default: %(default)s)",
17+
)
18+
19+
def run(self, args):
20+
from osc.gitea_api import maintainership
21+
22+
with open(args.path, "r", encoding="utf-8") as f:
23+
data = f.read()
24+
25+
obj = maintainership.Maintainership.from_string(data)
26+
print(obj.to_string())
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import json
2+
import os
3+
import sys
4+
import io
5+
import tempfile
6+
import unittest
7+
8+
9+
class TestMaintainershipConverter(unittest.TestCase):
10+
def setUp(self):
11+
self.tmpdir = tempfile.mkdtemp(prefix="osc_test_maintainership_converter_")
12+
self.orig_stdout = sys.stdout
13+
sys.stdout = io.StringIO()
14+
15+
def tearDown(self):
16+
sys.stdout = self.orig_stdout
17+
import shutil
18+
19+
shutil.rmtree(self.tmpdir, ignore_errors=True)
20+
21+
def _write_file(self, data):
22+
path = os.path.join(self.tmpdir, "_maintainership.json")
23+
with open(path, "w", encoding="utf-8") as f:
24+
json.dump(data, f)
25+
return path
26+
27+
def _run_converter(self, path):
28+
from osc.commands_git.maintainership_converter import MaintainershipCommand
29+
30+
cmd = MaintainershipCommand.__new__(MaintainershipCommand)
31+
32+
args = type("Args", (), {"path": path})()
33+
cmd.run(args)
34+
return sys.stdout.getvalue()
35+
36+
def test_legacy_format_converted(self):
37+
"""Legacy format is converted to v1.0 format."""
38+
legacy_data = {
39+
"": ["project-maintainer", "@project-group"],
40+
"package1": ["alice", "@pkg1-group"],
41+
"package2": ["bob"],
42+
}
43+
path = self._write_file(legacy_data)
44+
output = self._run_converter(path)
45+
result = json.loads(output)
46+
47+
self.assertEqual(result["header"]["document"], "obs-maintainers")
48+
self.assertEqual(result["header"]["version"], "1.0")
49+
50+
self.assertEqual(result["project"]["users"], ["project-maintainer"])
51+
self.assertEqual(result["project"]["groups"], ["project-group"])
52+
53+
self.assertEqual(result["packages"]["package1"]["users"], ["alice"])
54+
self.assertEqual(result["packages"]["package1"]["groups"], ["pkg1-group"])
55+
56+
self.assertEqual(result["packages"]["package2"]["users"], ["bob"])
57+
self.assertIsNone(result["packages"]["package2"]["groups"])
58+
59+
def test_v1_format_passthrough(self):
60+
"""v1.0 format is printed unchanged."""
61+
v1_data = {
62+
"header": {
63+
"document": "obs-maintainers",
64+
"version": "1.0",
65+
},
66+
"project": {
67+
"users": ["alice"],
68+
"groups": None,
69+
},
70+
"packages": {
71+
"pkg1": {
72+
"users": ["bob"],
73+
"groups": ["team"],
74+
},
75+
"pkg2": {
76+
"users": None,
77+
"groups": ["team"],
78+
},
79+
},
80+
}
81+
path = self._write_file(v1_data)
82+
output = self._run_converter(path)
83+
result = json.loads(output)
84+
85+
self.assertEqual(result["header"]["document"], "obs-maintainers")
86+
self.assertEqual(result["header"]["version"], "1.0")
87+
self.assertEqual(result["project"]["users"], ["alice"])
88+
self.assertEqual(result["packages"]["pkg1"]["users"], ["bob"])
89+
self.assertEqual(result["packages"]["pkg1"]["groups"], ["team"])
90+
91+
def test_legacy_empty_project_maintainers(self):
92+
"""Legacy format with no project-level maintainers."""
93+
legacy_data = {
94+
"package1": ["alice"],
95+
}
96+
path = self._write_file(legacy_data)
97+
output = self._run_converter(path)
98+
result = json.loads(output)
99+
100+
self.assertEqual(result["header"]["document"], "obs-maintainers")
101+
self.assertIsNone(result["project"]["users"])
102+
self.assertIsNone(result["project"]["groups"])
103+
self.assertEqual(result["packages"]["package1"]["users"], ["alice"])
104+
105+
def test_legacy_groups_only(self):
106+
"""Legacy format with only groups, no users."""
107+
legacy_data = {
108+
"": ["@admins", "@maintainers"],
109+
"pkg": ["@team"],
110+
}
111+
path = self._write_file(legacy_data)
112+
output = self._run_converter(path)
113+
result = json.loads(output)
114+
115+
self.assertIsNone(result["project"]["users"])
116+
self.assertEqual(result["project"]["groups"], ["admins", "maintainers"])
117+
self.assertIsNone(result["packages"]["pkg"]["users"])
118+
self.assertEqual(result["packages"]["pkg"]["groups"], ["team"])
119+
120+
def test_file_not_found(self):
121+
"""Non-existent file raises an error."""
122+
path = os.path.join(self.tmpdir, "nonexistent.json")
123+
with self.assertRaises(FileNotFoundError):
124+
self._run_converter(path)
125+
126+
def test_invalid_json(self):
127+
"""Invalid JSON raises an error."""
128+
path = os.path.join(self.tmpdir, "_maintainership.json")
129+
with open(path, "w") as f:
130+
f.write("not valid json")
131+
with self.assertRaises(json.JSONDecodeError):
132+
self._run_converter(path)
133+
134+
135+
if __name__ == "__main__":
136+
unittest.main()

0 commit comments

Comments
 (0)