Skip to content

Commit 1d868c0

Browse files
ReadMe
1 parent 50526ab commit 1d868c0

2 files changed

Lines changed: 161 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,9 +418,8 @@ STuple(
418418
## Sponsorship
419419

420420

421-
If you find **PackOS** useful and would like to support its development, you can donate via **Litecoin (LTC)**.
421+
If you find **PackOS** useful and would like to support, you can donate via **Litecoin (LTC)**.
422422

423423
**LTC Address:** `ltc1qqx8e6yl95gxlwqv52mwdt9f76euvls5fvt4ckd`
424-
<img width="340" height="340" alt="lite coin" src="https://github.com/user-attachments/assets/94d720a0-e886-42f5-bf42-36b27327626a" />
425424
> [!IMPORTANT]
426425
> Please ensure you are sending via the **Litecoin (LTC) network** only.

report_git.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import subprocess
2+
import requests
3+
from collections import defaultdict, Counter
4+
import os
5+
import re
6+
from datetime import datetime, timedelta
7+
8+
# Config
9+
import sys
10+
11+
scope = sys.argv[1] if len(sys.argv) > 1 else "weekly"
12+
13+
if scope == "weekly":
14+
SINCE = '--since="1 week ago"'
15+
elif scope == "monthly":
16+
SINCE = '--since="1 month ago"'
17+
elif scope == "yearly":
18+
SINCE = '--since="1 year ago"'
19+
elif scope == "all":
20+
SINCE = ""
21+
else:
22+
print(f"❌ Unknown scope: {scope}. Use 'weekly', 'monthly', 'yearly', or 'all'.")
23+
sys.exit(1)
24+
25+
scope=scope.capitalize()
26+
DISCORD_WEBHOOK = "https://discord.com/api/webhooks/1403424863783227532/Ltc-F2WQJvHnvmGID61BstnICwsTOEbNU_HJYUHG6gpBEtd1rY-bJDpiIdXzytpz3cTP"
27+
28+
29+
mailmap_cache = {}
30+
31+
def resolve_mailmap(name, email):
32+
contact = f"{name} <{email}>"
33+
print(f"Resolving mailmap for: {contact}")
34+
if contact in mailmap_cache:
35+
return mailmap_cache[contact]
36+
try:
37+
result = subprocess.run(
38+
['git', 'check-mailmap', contact],
39+
stdout=subprocess.PIPE,
40+
stderr=subprocess.DEVNULL,
41+
text=True
42+
)
43+
resolved = result.stdout.strip()
44+
# Extract name only (before the first '<')
45+
resolved_name = resolved.split('<')[0].strip() if resolved else name
46+
mailmap_cache[contact] = resolved_name
47+
48+
print(f"Resolved to: {resolved_name}")
49+
return resolved_name
50+
except Exception:
51+
mailmap_cache[contact] = name
52+
return name
53+
54+
55+
# Git command with commit marker
56+
cmd = f'git log {SINCE} --pretty=format:"--COMMIT--%n%h|%an|%ae|%s%n%b" --numstat'
57+
result = subprocess.run(cmd, stdout=subprocess.PIPE, text=True, shell=True)
58+
blocks = result.stdout.split('--COMMIT--\n')
59+
60+
author_commits = defaultdict(int)
61+
total_additions = 0
62+
total_deletions = 0
63+
changed_files = set()
64+
merged_prs = []
65+
66+
for block in blocks:
67+
lines = block.strip().splitlines()
68+
if not lines:
69+
continue
70+
71+
try:
72+
commit_hash, name, email, subject = lines[0].split('|', 3)
73+
author = resolve_mailmap(name.strip(), email.strip())
74+
author_commits[author] += 1
75+
except ValueError:
76+
continue
77+
78+
body_lines = []
79+
stat_lines = []
80+
81+
for line in lines[1:]:
82+
if re.match(r'^\d+\s+\d+\s+.+$', line):
83+
stat_lines.append(line)
84+
else:
85+
body_lines.append(line)
86+
87+
body = "\n".join(body_lines)
88+
89+
# Co-authors
90+
coauthors = re.findall(r'Co-authored-by:\s*(.+?)\s*<(.+?)>', body)
91+
resolved_coauthors = []
92+
for cname, cemail in coauthors:
93+
coauthor = resolve_mailmap(cname.strip(), cemail.strip())
94+
author_commits[coauthor] += 1
95+
resolved_coauthors.append(coauthor)
96+
97+
# PR detection
98+
pr_number = None
99+
pr_branch = None
100+
match_subject = re.search(r'(?:Merge pull request #|#)(\d+)(?: from ([\w\-/]+))?', subject)
101+
if match_subject:
102+
pr_number = match_subject.group(1)
103+
pr_branch = match_subject.group(2) if match_subject.group(2) else None
104+
else:
105+
match_body = re.search(r'(?:Closes|Fixes|Resolves)\s+#(\d+)', body)
106+
if match_body:
107+
pr_number = match_body.group(1)
108+
109+
if pr_number:
110+
merged_prs.append((commit_hash, pr_number, pr_branch, author, resolved_coauthors, subject))
111+
112+
# File stats
113+
for stat in stat_lines:
114+
match = re.match(r'^(\d+)\s+(\d+)\s+(.+)$', stat)
115+
if match:
116+
additions = int(match.group(1))
117+
deletions = int(match.group(2))
118+
file = match.group(3).strip()
119+
total_additions += additions
120+
total_deletions += deletions
121+
changed_files.add(file)
122+
123+
# Extension summary
124+
ext_counts = Counter()
125+
for f in changed_files:
126+
ext = os.path.splitext(f)[1].lower().strip()
127+
ext = re.sub(r'[^\w.]+$', '', ext) or "(no_ext)"
128+
ext_counts[ext] += 1
129+
130+
# Build summary
131+
summary = f"🧾 **PackOS {scope} Git Summary**\n----------------------"
132+
for author, count in sorted(author_commits.items()):
133+
summary += f"\n👤 **{author}**: {count} commits"
134+
135+
summary += f"\n\n➕ **Total additions**: {total_additions}"
136+
summary += f"\n➖ **Total deletions**: {total_deletions}"
137+
138+
if ext_counts:
139+
summary += "\n📄 **Files changed by type:**\n" + "\n".join(
140+
f"• `{ext}`: {count}" for ext, count in sorted(ext_counts.items())
141+
)
142+
143+
if merged_prs:
144+
summary += "\n📦 **Merged or squash PRs:**"
145+
for commit_hash, pr_number, pr_branch, author, coauthors, subject in merged_prs:
146+
co_list = ", ".join(coauthors)
147+
summary += f"\n• PR #{pr_number} from `{pr_branch or '-'}` by **{author}**"
148+
if co_list:
149+
summary += f" (with co-authors: {co_list})"
150+
151+
print(summary)
152+
153+
# Post to Discord
154+
payload = {"content": summary[:1999]} # Discord message limit
155+
response = requests.post(DISCORD_WEBHOOK, json=payload)
156+
157+
if response.status_code == 204:
158+
print("✅ Summary posted to Discord.")
159+
else:
160+
print(f"❌ Failed to post: {response.status_code} - {response.text}")

0 commit comments

Comments
 (0)