bsc

Comprehensive codebase and cou...
Log | Files | Refs | Activity | README | LICENSE

root / changelog.py

changelog.py (7650B)


      1 import os
      2 from datetime import datetime, timezone
      3 
      4 sha = os.environ.get('BUILD_SHA', 'unknown')
      5 actor = os.environ.get('BUILD_ACTOR', 'unknown')
      6 ref = os.environ.get('BUILD_REF', 'unknown')
      7 timestamp = os.environ.get('BUILD_TIMESTAMP', '')
      8 
      9 try:
     10     dt = datetime.fromisoformat(timestamp).astimezone(timezone.utc)
     11     build_time = dt.strftime('%B %d, %Y at %H:%M UTC')
     12 except Exception:
     13     build_time = timestamp
     14 
     15 STATUS_LABELS = {
     16     'A': 'Added',
     17     'M': 'Modified',
     18     'D': 'Removed',
     19     'T': 'Type changed',
     20     'U': 'Unmerged',
     21 }
     22 
     23 
     24 def status_label(code):
     25     letter = code[0]
     26     if letter == 'R':
     27         return f'Renamed ({code[1:]}%)' if len(code) > 1 else 'Renamed'
     28     if letter == 'C':
     29         return f'Copied ({code[1:]}%)' if len(code) > 1 else 'Copied'
     30     return STATUS_LABELS.get(letter, letter)
     31 
     32 
     33 # ---- Pass 1: commit metadata + numstat (line/diff counts) ----
     34 commits = []
     35 commit_index = {}  # full_sha -> index in commits
     36 
     37 try:
     38     with open('git_log_numstat.txt', 'r', encoding='utf-8') as f:
     39         current = None
     40         for raw_line in f:
     41             line = raw_line.rstrip('\n')
     42             if line.startswith('COMMIT|'):
     43                 parts = line.split('|')
     44                 if len(parts) == 5:
     45                     _, c_sha, c_msg, c_author, c_time = parts
     46                     try:
     47                         c_dt = datetime.fromisoformat(c_time).astimezone(timezone.utc)
     48                         c_time_fmt = c_dt.strftime('%b %d, %Y %H:%M UTC')
     49                     except Exception:
     50                         c_time_fmt = c_time
     51                     current = {
     52                         'sha': c_sha[:7],
     53                         'full_sha': c_sha,
     54                         'msg': c_msg,
     55                         'author': c_author,
     56                         'time': c_time_fmt,
     57                         'files': {},  # path -> {'add': int, 'del': int, 'status': str}
     58                         'order': [],  # preserve file order
     59                     }
     60                     commit_index[c_sha] = len(commits)
     61                     commits.append(current)
     62                 continue
     63             if not line.strip() or current is None:
     64                 continue
     65             # numstat line: "<added>\t<deleted>\t<path>" (binary files use '-')
     66             cols = line.split('\t')
     67             if len(cols) == 3:
     68                 add, dele, path = cols
     69                 add_n = int(add) if add.isdigit() else 0
     70                 del_n = int(dele) if dele.isdigit() else 0
     71                 current['files'][path] = {'add': add_n, 'del': del_n, 'status': 'M'}
     72                 current['order'].append(path)
     73 except FileNotFoundError:
     74     pass
     75 
     76 # ---- Pass 2: name-status (A/M/D/R/C) merged into the same commits ----
     77 try:
     78     with open('git_log_namestatus.txt', 'r', encoding='utf-8') as f:
     79         current = None
     80         for raw_line in f:
     81             line = raw_line.rstrip('\n')
     82             if line.startswith('COMMIT|'):
     83                 parts = line.split('|')
     84                 if len(parts) == 2:
     85                     c_sha = parts[1]
     86                     idx = commit_index.get(c_sha)
     87                     current = commits[idx] if idx is not None else None
     88                 continue
     89             if not line.strip() or current is None:
     90                 continue
     91             cols = line.split('\t')
     92             if len(cols) == 2:
     93                 code, path = cols
     94                 if path in current['files']:
     95                     current['files'][path]['status'] = code
     96                 else:
     97                     current['files'][path] = {'add': 0, 'del': 0, 'status': code}
     98                     current['order'].append(path)
     99             elif len(cols) == 3:
    100                 code, old_path, new_path = cols
    101                 target = new_path
    102                 stats = current['files'].pop(old_path, {'add': 0, 'del': 0, 'status': code})
    103                 stats['status'] = code
    104                 current['files'][target] = stats
    105                 if old_path in current['order']:
    106                     current['order'][current['order'].index(old_path)] = target
    107                 else:
    108                     current['order'].append(target)
    109 except FileNotFoundError:
    110     pass
    111 
    112 ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline; margin-bottom:-2px; margin-right:6px;" class="lucide lucide-history"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/></svg>'
    113 
    114 
    115 def build_commit_block(c):
    116     commit_url = f"https://github.com/notamitgamer/bsc/commit/{c['full_sha']}"
    117     total_add = sum(v['add'] for v in c['files'].values())
    118     total_del = sum(v['del'] for v in c['files'].values())
    119     file_count = len(c['files'])
    120 
    121     header = (
    122         f"### [`{c['sha']}`]({commit_url}) {c['msg']}\n\n"
    123         f"**{c['author']}** committed on {c['time']}"
    124     )
    125 
    126     if file_count:
    127         stat_bits = []
    128         if total_add or total_del:
    129             stat_bits.append(f"+{total_add} / -{total_del} lines")
    130         stat_bits.append(f"{file_count} file{'s' if file_count != 1 else ''} changed")
    131         header += f" · {' · '.join(stat_bits)}"
    132 
    133     if not file_count:
    134         return header + "\n"
    135 
    136     file_lines = []
    137     for path in c['order']:
    138         info = c['files'][path]
    139         label = status_label(info['status'])
    140         diff_bits = []
    141         if info['add']:
    142             diff_bits.append(f"+{info['add']}")
    143         if info['del']:
    144             diff_bits.append(f"-{info['del']}")
    145         diff_str = ' '.join(diff_bits) if diff_bits else 'binary/no diff'
    146         file_lines.append(f"| `{path}` | {label} | {diff_str} |")
    147 
    148     files_table = (
    149         "| File | Change | Lines |\n"
    150         "| --- | --- | --- |\n"
    151         + '\n'.join(file_lines)
    152     )
    153 
    154     details = (
    155         "<details>\n"
    156         f"<summary>Show {file_count} changed file{'s' if file_count != 1 else ''}</summary>\n\n"
    157         f"{files_table}\n"
    158         "</details>\n"
    159     )
    160 
    161     return f"{header}\n\n{details}"
    162 
    163 
    164 commit_blocks = [build_commit_block(c) for c in commits]
    165 commit_section = '\n\n---\n\n'.join(commit_blocks) if commit_blocks else 'No commit history available.'
    166 
    167 # VitePress docs/changelog.md
    168 docs_content = f"""---
    169 title: '{ICON} Changelog'
    170 description: 'Current build information and recent commit history.'
    171 ---
    172 
    173 # Build Info
    174 
    175 ::: tip Important
    176 Compare the Build ID (listed below) against the one in the [GitHub Changelog](https://github.com/notamitgamer/bsc/blob/main/CHANGELOG.md#latest-build) to verify that your browser is displaying the latest version.
    177 :::
    178 
    179 - **Build ID** — <span style="word-break: break-all;">`{sha}`</span>
    180 - **Triggered by** — [@{actor}](https://github.com/{actor})
    181 - **Branch** — `{ref}`
    182 - **Build time** — {build_time}
    183 
    184 ## Recent Commits
    185 
    186 {commit_section}
    187 """
    188 
    189 with open('docs/changelog.md', 'w', encoding='utf-8') as f:
    190     f.write(docs_content)
    191 
    192 # Root CHANGELOG.md for GitHub
    193 root_content = f"""# Changelog
    194 
    195 > Last build: {build_time}
    196 
    197 ## Latest Build
    198 
    199 - **Build ID** — `{sha}`
    200 - **Triggered by** — [@{actor}](https://github.com/{actor})
    201 - **Branch** — `{ref}`
    202 - **Build time** — {build_time}
    203 
    204 ## Recent Commits
    205 
    206 {commit_section}
    207 
    208 ---
    209 
    210 *This file is auto-generated on every deployment. For the live site, visit [code.amit.is-a.dev](https://code.amit.is-a.dev).*
    211 *Check [/changelog](https://code.amit.is-a.dev/changelog) on the site to verify your browser is showing the latest build.*
    212 """
    213 
    214 with open('CHANGELOG.md', 'w', encoding='utf-8') as f:
    215     f.write(root_content)
    216 
    217 print(f"Generated changelog with {len(commits)} commits.")
© notamitgamer • Site Built: 2026-09-05 01:53:16 UTC • git-mirror commit: c170d72 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror