Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

common: Add script for showing build/commit history #895

Merged
merged 2 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/script_lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
- name: flake8 Lint
uses: py-actions/flake8@v2
with:
path: common/CI common/Hooks
path: common/CI common/Hooks common/Scripts/worklog.py
- name: mypy lint
uses: jpetrucciani/mypy-check@master
with:
path: common/CI common/Hooks
path: common/CI common/Hooks common/Scripts/worklog.py
python_version: "3.10"
requirements: types-PyYAML
56 changes: 0 additions & 56 deletions common/Scripts/repos-modified-since-days.sh

This file was deleted.

203 changes: 203 additions & 0 deletions common/Scripts/worklog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#!/usr/bin/env python3
import argparse
import json
import subprocess
import sys
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Dict, List, Optional, Sequence
from urllib import request


class TTY:
Reset = '\033[0m'
Red = '\033[31m'
Green = '\033[32m'
Yellow = '\033[33m'
Blue = '\033[34m'
White = '\033[97m'

@staticmethod
def url(name: str, ref: str) -> str:
return f'\033]8;;{ref}\033\\{name}\033]8;;\033\\'


class Listable:
@property
def package(self) -> str:
raise NotImplementedError

@property
def date(self) -> datetime:
raise NotImplementedError

def to_md(self) -> str:
raise NotImplementedError

def to_tty(self) -> str:
raise NotImplementedError


@dataclass
class Build(Listable):
id: int
pkg: str
version: str
release: str
ref: str
tag: str
tag_url: str
log_url: str
status: str
builder: str
finished: Optional[str]

@property
def package(self) -> str:
return self.pkg

@property
def date(self) -> datetime:
if self.finished is None:
return datetime.now(tz=timezone.utc)

return datetime.fromisoformat(self.finished).astimezone(timezone.utc)

def to_md(self) -> str:
return f'[{self.pkg} {self.version}-{self.release}]({self.tag_url})'

def to_tty(self) -> str:
return (f'{TTY.Green}{self.pkg}{TTY.Reset} {self.version}-{self.release} ' +
f'{TTY.Blue}[{self.builder}]{TTY.Reset} ' +
TTY.url('[🡕]', self.tag_url))


class Builds:
_url = 'https://build.getsol.us/builds.json'
_builds: Optional[List[Build]] = None

@property
def all(self) -> List[Build]:
if self._builds is None:
with request.urlopen(self._url) as data:
self._builds = json.load(data, object_hook=lambda d: Build(**d))

return self._builds

@property
def packages(self) -> List[Build]:
return list({b.pkg: b for b in self.all}.values())

def during(self, start: datetime, end: datetime) -> List[Build]:
return self._filter(self.all, start, end)

def updates(self, start: datetime, end: datetime) -> List[Build]:
return self._filter(self.packages, start, end)

@staticmethod
def _filter(builds: List[Build], start: datetime, end: datetime) -> List[Build]:
return list(filter(lambda b: start <= b.date <= end, builds))


@dataclass
class Commit(Listable):
ref: str
ts: str
msg: str
author: str

@staticmethod
def from_line(line: str) -> 'Commit':
return Commit(*line.split('\x1e'))

@property
def date(self) -> datetime:
return datetime.fromisoformat(self.ts).astimezone(timezone.utc)

@property
def package(self) -> str:
return self.msg.split(':', 2)[0].strip()

@property
def change(self) -> str:
return self.msg.split(':', 2)[1].strip()

@property
def url(self) -> str:
return f'https://github.com/getsolus/packages/commit/{self.ref}'

def to_md(self) -> str:
return f'[{self.msg}]({self.url})'

def to_tty(self) -> str:
return (f'{TTY.Yellow}{TTY.url(self.ref, self.url)}{TTY.Reset} '
f'{self.date} '
f'{TTY.Green}{self.package}: {TTY.Reset}{self.change} '
f'{TTY.Blue}[{self.author}]{TTY.Reset} ' +
TTY.url('[🡕]', self.url))


class Git:
_cmd = ['git', '-c', 'color.ui=always', 'log', '--date=iso-strict', '--no-merges',
'--reverse', '--pretty=format:%h%x1e%ad%x1e%s%x1e%an']

def commits_by_pkg(self, start: datetime, end: datetime) -> Dict[str, List[Commit]]:
commits: Dict[str, List[Commit]] = {}
for commit in self.commits(start, end):
commits[commit.package] = commits.get(commit.package, []) + [commit]

return commits

def commits(self, start: datetime, end: datetime) -> List[Commit]:
return [Commit.from_line(line) for line in self._commits(start, end)]

def _commits(self, start: datetime, end: datetime) -> List[str]:
out = subprocess.Popen(self._cmd + [f'--after={start}', f'--before={end}'],
stdout=subprocess.PIPE, stderr=sys.stderr).stdout
if out is None:
return []

return out.read().decode('utf8').strip().split("\n")


def parse_date(date: str) -> datetime:
out = subprocess.Popen(['date', '-u', '--iso-8601=s', '--date=' + date],
stdout=subprocess.PIPE).stdout
if out is None:
raise ValueError(f'invalid date: {repr(date)}')

return datetime.fromisoformat(out.read().decode('utf8').strip())


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('command', type=str, choices=['builds', 'updates', 'commits'])
parser.add_argument('after', type=str, help='Show builds after this date')
parser.add_argument('before', type=str, help='Show builds before this date')
parser.add_argument('--format', '-f', type=str, choices=['md', 'tty'], default='tty')

cli_args = parser.parse_args()
start = parse_date(cli_args.after)
end = parse_date(cli_args.before)
builds = Builds()
git = Git()
items: Sequence[Listable] = []

match cli_args.command:
case 'builds':
items = builds.during(start, end)
case 'updates':
items = builds.updates(start, end)
case 'commits':
items = git.commits(start, end)

items = sorted(items, key=lambda item: (item.package, item.date))

match cli_args.format:
case 'tty':
lines = [item.to_tty() for item in items]
case 'md':
lines = [f'- {item.to_md()}' for item in items]

print(f'{len(lines)} {cli_args.command}:')
print("\n".join(lines))