1#!/usr/bin/python3
2
3import argparse
4import json
5import os
6import re
7from collections import defaultdict
8from typing import Dict
9
10import libvoters.acceptable as acceptable
11from libvoters import UserChanges, changes_factory
12from libvoters.time import TimeOfDay, timestamp
13
14
15class subcmd:
16    def __init__(self, parser: argparse._SubParsersAction) -> None:
17        p = parser.add_parser(
18            "analyze-commits", help="Determine points for commits"
19        )
20
21        p.add_argument(
22            "--before",
23            "-b",
24            help="Before timestamp (YYYY-MM-DD)",
25            required=True,
26        )
27        p.add_argument(
28            "--after",
29            "-a",
30            help="After timestamp (YYYY-MM-DD)",
31            required=True,
32        )
33
34        p.set_defaults(cmd=self)
35
36    def run(self, args: argparse.Namespace) -> int:
37        before = timestamp(args.before, TimeOfDay.AM)
38        after = timestamp(args.after, TimeOfDay.PM)
39
40        changes_per_user: Dict[str, UserChanges] = defaultdict(changes_factory)
41
42        for f in sorted(os.listdir(args.dir)):
43            path = os.path.join(args.dir, f)
44            if not os.path.isfile(path):
45                continue
46
47            if not re.match(r"[0-9]*\.json", f):
48                continue
49
50            with open(path, "r") as file:
51                data = json.load(file)
52
53            if data["status"] != "MERGED":
54                continue
55
56            merged_at = 0
57            for c in data["comments"]:
58                if "timestamp" not in c:
59                    continue
60                if "message" in c and re.match(
61                    "Change has been successfully .*", c["message"]
62                ):
63                    merged_at = c["timestamp"]
64
65            if merged_at == 0:
66                raise RuntimeError(f"Missing merge timestamp on {f}")
67
68            if merged_at > before or merged_at < after:
69                continue
70
71            project = data["project"]
72            id_number = data["number"]
73            username = data["owner"]["username"]
74
75            if not acceptable.project(project):
76                print("Rejected project:", project, id_number)
77                continue
78
79            changes = 0
80            touched_files = []
81            for file_data in sorted(
82                data["patchSets"], key=lambda x: x["number"]
83            )[-1][
84                "files"
85            ]:  # type: Dict[str, Any]
86                if not acceptable.file(project, file_data["file"]):
87                    continue
88                changes += int(file_data["insertions"]) + abs(
89                    int(file_data["deletions"])
90                )
91                touched_files.append(file_data["file"])
92
93            if changes < 10:
94                print("Rejected for limited changes:", project, id_number)
95                continue
96
97            print(project, id_number, username)
98            for f in touched_files:
99                print(f"    {f}")
100
101            user = changes_per_user[username]
102            user["name"] = data["owner"]["name"]
103            user["email"] = data["owner"]["email"]
104            user["changes"].append(id_number)
105
106        with open(os.path.join(args.dir, "commits.json"), "w") as outfile:
107            outfile.write(json.dumps(changes_per_user, indent=4))
108
109        return 0
110