-
Notifications
You must be signed in to change notification settings - Fork 96
fix(tools): enrich_git.py を列名対応にし、台帳の更新手順へ組み込む #1908
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,20 +1,45 @@ | ||||||
| # -*- coding: utf-8 -*- | ||||||
| """TSV の 36-39列 (last_commit / date / subject / release_tag) を git から埋める。 | ||||||
| """台帳の git 由来4列を引き直す。 | ||||||
|
|
||||||
| 使い方: python3 enrich_git.py <in.tsv> <out.tsv> | ||||||
| - 14列目 impl_file (repo相対), 15列目 impl_line を見て、その行を含む | ||||||
| def/class の行範囲を AST で特定し `git log -1 -L a,b:file` で最終コミットを取る。 | ||||||
| - release_tag は `git tag --sort=creatordate --contains <sha>` の先頭 (最初に入ったリリース)。 | ||||||
| last_commit / last_commit_date / last_commit_subject / release_tag | ||||||
|
|
||||||
| python3 enrich_git.py # 差分を表示するだけ | ||||||
| python3 enrich_git.py --write # 台帳に書き戻す | ||||||
| python3 enrich_git.py --tsv in.tsv --out out.tsv # 別ファイルへ出す(初回生成向け) | ||||||
|
|
||||||
| `impl_file`(リポジトリ相対) と `impl_line` が指す def/class の行範囲を AST で特定し、 | ||||||
| `git log -1 -L <開始>,<終了>:<file>` でその範囲を最後に変更したコミットを取る | ||||||
| (ファイル単位で見るより正確)。`release_tag` は | ||||||
| `git tag --sort=creatordate --contains <sha>` の先頭 = 最初に入ったリリース。 | ||||||
| コミットがどのタグにも入っていなければ `(未リリース)`。 | ||||||
|
|
||||||
| `impl_file` が実ファイルでない行(Flask-Admin ModelView の総称表記 / framework 自動生成 / | ||||||
| site-packages)は git で追えないので4列とも `-` にする。 | ||||||
|
|
||||||
| ★ `impl_line` がずれていると手前の関数のコミットを拾う。**必ず `refresh_impl.py --write` | ||||||
| を先に回すこと。** バージョンアップでデコレータが増えると行番号は簡単にずれる。 | ||||||
|
|
||||||
| 解析対象リポジトリは `WEKO_ROOT`、台帳は `WEKO_API_INVENTORY_DIR` で指す。 | ||||||
| """ | ||||||
| import ast, os, subprocess, sys, functools | ||||||
| import argparse | ||||||
| import ast | ||||||
| import functools | ||||||
| import os | ||||||
| import subprocess | ||||||
| import sys | ||||||
|
|
||||||
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | ||||||
| from paths import data_path # noqa: E402 | ||||||
| from changed_rows import default_weko_root # noqa: E402 | ||||||
|
Comment on lines
+32
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3. Local imports are unsorted The local imports place paths before changed_rows, contrary to case-insensitive alphabetical module ordering. Running isort with the repository's Black profile would reorder these imports. Agent Prompt
|
||||||
|
|
||||||
| COLS = ('last_commit', 'last_commit_date', 'last_commit_subject', 'release_tag') | ||||||
| EMPTY = ('-', '-', '-', '-') | ||||||
|
|
||||||
| ROOT = '/home/mhaya/wekov2' | ||||||
| NCOL = 41 | ||||||
|
|
||||||
| @functools.lru_cache(maxsize=None) | ||||||
| def def_ranges(path): | ||||||
| """ファイル内の全 def/class の (start, end) をリストで返す。""" | ||||||
| fp = os.path.join(ROOT, path) | ||||||
| def def_ranges(root, path): | ||||||
| """ファイル内の全 def/class の (開始, 終了) を返す。開始はデコレータ行を含む。""" | ||||||
| fp = os.path.join(root, path) | ||||||
| if not os.path.isfile(fp): | ||||||
| return () | ||||||
| try: | ||||||
|
|
@@ -28,69 +53,103 @@ def def_ranges(path): | |||||
| out.append((s, getattr(n, 'end_lineno', n.lineno))) | ||||||
| return tuple(out) | ||||||
|
|
||||||
| def enclosing(path, line): | ||||||
|
|
||||||
| def enclosing(root, path, line): | ||||||
| """line を含む最小の def/class 範囲。無ければ (line, line)。""" | ||||||
| best = None | ||||||
| for s, e in def_ranges(path): | ||||||
| if s <= line <= e: | ||||||
| if best is None or (e - s) < (best[1] - best[0]): | ||||||
| best = (s, e) | ||||||
| for s, e in def_ranges(root, path): | ||||||
| if s <= line <= e and (best is None or (e - s) < (best[1] - best[0])): | ||||||
| best = (s, e) | ||||||
| return best or (line, line) | ||||||
|
|
||||||
|
|
||||||
| @functools.lru_cache(maxsize=None) | ||||||
| def git_last(path, start, end): | ||||||
| def git_last(root, path, start, end): | ||||||
| try: | ||||||
| r = subprocess.run( | ||||||
| ['git', '-C', ROOT, 'log', '-1', '--format=%h\x1f%ad\x1f%s', '--date=short', | ||||||
| ['git', '-C', root, 'log', '-1', '--format=%h\x1f%ad\x1f%s', '--date=short', | ||||||
| '-L', f'{start},{end}:{path}'], | ||||||
| capture_output=True, text=True, timeout=60) | ||||||
| capture_output=True, text=True, timeout=120) | ||||||
|
Comment on lines
69
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): A failed Triggers: When the configured repository is invalid or a git subprocess fails or times out during a write run. Suggested fix: Check |
||||||
| except Exception: | ||||||
| return ('', '', '') | ||||||
| line = r.stdout.split('\n', 1)[0] if r.stdout else '' | ||||||
| parts = line.split('\x1f') | ||||||
| if len(parts) != 3: | ||||||
| return ('', '', '') | ||||||
| subj = parts[2].replace('\t', ' ').strip() | ||||||
| return (parts[0], parts[1], subj[:120]) | ||||||
| return (parts[0], parts[1], parts[2].replace('\t', ' ').strip()[:120]) | ||||||
|
|
||||||
|
|
||||||
| @functools.lru_cache(maxsize=None) | ||||||
| def git_tag(sha): | ||||||
| def git_tag(root, sha): | ||||||
| if not sha: | ||||||
| return '' | ||||||
| r = subprocess.run(['git', '-C', ROOT, 'tag', '--sort=creatordate', '--contains', sha], | ||||||
| capture_output=True, text=True, timeout=60) | ||||||
| tags = [t for t in r.stdout.split('\n') if t.strip()] | ||||||
| try: | ||||||
| r = subprocess.run(['git', '-C', root, 'tag', '--sort=creatordate', '--contains', sha], | ||||||
| capture_output=True, text=True, timeout=120) | ||||||
| except Exception: | ||||||
| return '' | ||||||
| tags = [t.strip() for t in r.stdout.split('\n') if t.strip()] | ||||||
| return tags[0] if tags else '(未リリース)' | ||||||
|
|
||||||
| def main(src, dst): | ||||||
| out = [] | ||||||
| bad = 0 | ||||||
| for i, raw in enumerate(open(src, encoding='utf-8'), 1): | ||||||
| raw = raw.rstrip('\n') | ||||||
| if not raw.strip(): | ||||||
| continue | ||||||
| c = raw.split('\t') | ||||||
| if len(c) < NCOL: | ||||||
| c += [''] * (NCOL - len(c)) | ||||||
| elif len(c) > NCOL: | ||||||
| sys.stderr.write(f'WARN line {i}: {len(c)} cols (>41), truncating tail into notes\n') | ||||||
| c = c[:NCOL - 1] + [' | '.join(c[NCOL - 1:])] | ||||||
| bad += 1 | ||||||
| path, ln = c[13].strip(), c[14].strip() | ||||||
| sha = date = subj = tag = '' | ||||||
|
|
||||||
| def main(): | ||||||
| p = argparse.ArgumentParser() | ||||||
| p.add_argument('--tsv', default=None, help='入力の台帳(既定: $WEKO_API_INVENTORY_DIR の57列版)') | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2. enrich_git.py is not black-formatted The added p.add_argument('--tsv', ...) declaration exceeds both the mandated 79-character
code-line limit and Black's default 88-character line length, with no approved exception marker.
Black would split it into a multiline call, so the committed Python diff is not Black-compliant.
Agent Prompt
|
||||||
| p.add_argument('--out', default=None, help='出力先(既定: --tsv と同じ = 上書き)') | ||||||
| p.add_argument('--write', action='store_true', help='書き戻す(付けないと差分表示のみ)') | ||||||
|
Comment on lines
+96
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. enrich_git changes lack tests The PR substantially changes enrich_git.py CLI, file-writing, column-selection, and Git lookup behavior without adding or modifying a corresponding automated test. Manual verification in the PR description does not satisfy the requirement for test-file coverage of modified executable logic. Agent Prompt
|
||||||
| a = p.parse_args() | ||||||
|
|
||||||
| tsv = a.tsv or data_path('weko3_api_list_full.tsv') | ||||||
| dst = a.out or tsv | ||||||
| root = default_weko_root() | ||||||
|
|
||||||
| rows = [l.rstrip('\n').split('\t') for l in open(tsv, encoding='utf-8') if l.rstrip('\n')] | ||||||
| head = {n: i for i, n in enumerate(rows[0])} | ||||||
| for c in ('impl_file', 'impl_line') + COLS: | ||||||
| if c not in head: | ||||||
| sys.exit(f'列が無い: {c}') | ||||||
| i_file, i_line = head['impl_file'], head['impl_line'] | ||||||
| idx = [head[c] for c in COLS] | ||||||
|
|
||||||
| changed, same, nofile = [], 0, 0 | ||||||
| for r in rows[1:]: | ||||||
| if len(r) < len(rows[0]): | ||||||
| r += [''] * (len(rows[0]) - len(r)) | ||||||
| path, ln = r[i_file].strip(), r[i_line].strip() | ||||||
| try: | ||||||
| line = int(ln) | ||||||
| except ValueError: | ||||||
| line = None | ||||||
| if path and line and os.path.isfile(os.path.join(ROOT, path)): | ||||||
| s, e = enclosing(path, line) | ||||||
| sha, date, subj = git_last(path, s, e) | ||||||
| tag = git_tag(sha) | ||||||
| c[35], c[36], c[37], c[38] = sha or '-', date or '-', subj or '-', tag or '-' | ||||||
| out.append('\t'.join(x.replace('\t', ' ') for x in c)) | ||||||
| with open(dst, 'w', encoding='utf-8') as f: | ||||||
| f.write('\n'.join(out) + '\n') | ||||||
| print(f'rows={len(out)} col_fixups={bad}') | ||||||
| if path and line and os.path.isfile(os.path.join(root, path)): | ||||||
| s, e = enclosing(root, path, line) | ||||||
| sha, date, subj = git_last(root, path, s, e) | ||||||
| new = (sha or '-', date or '-', subj or '-', git_tag(root, sha) or '-') | ||||||
| else: | ||||||
| nofile += 1 | ||||||
| new = EMPTY | ||||||
|
Comment on lines
+127
to
+129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 5. Invalid root erases metadata If WEKO_ROOT is misspelled, missing, or points at the wrong checkout, every implementation fails the file check and is assigned EMPTY; --write then replaces all four previously valid Git columns with -. The preceding refresh_impl.py does not prevent this because it safely skips missing files rather than validating the root. Agent Prompt
|
||||||
| old = tuple(r[i] for i in idx) | ||||||
| if old != new: | ||||||
| changed.append((r[0], path, ln, old, new)) | ||||||
| for i, v in zip(idx, new): | ||||||
| r[i] = v | ||||||
| else: | ||||||
| same += 1 | ||||||
|
|
||||||
| print(f'{tsv} (root={root})') | ||||||
| print(f' 更新 {len(changed)} / 変化なし {same} / 追えない行 {nofile}') | ||||||
| for no, path, ln, old, new in changed[:40]: | ||||||
| print(f' no={no:<5} {path}:{ln}') | ||||||
| print(f' {old[0]} / {old[3]} -> {new[0]} ({new[1]}) / {new[3]}') | ||||||
| if len(changed) > 40: | ||||||
| print(f' ... 他 {len(changed) - 40} 件') | ||||||
|
|
||||||
| if a.write: | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Proposed fix- if a.write:
+ if a.write or a.out is not None:📝 Committable suggestion
Suggested change
🧰 Tools🪛 ast-grep (0.45.2)[warning] 146-146: File path is request-/variable-derived; validate and normalize to prevent path traversal. (open-filename-from-request) 🤖 Prompt for AI Agents |
||||||
| with open(dst, 'w', encoding='utf-8') as f: | ||||||
| f.write('\n'.join('\t'.join(x.replace('\t', ' ') for x in r) for r in rows) + '\n') | ||||||
|
Comment on lines
+146
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4. Output file is never written The documented --tsv input --out output workflow never creates the output because all writing is additionally guarded by --write. This breaks the retained initial-generation use case while exiting successfully after only displaying a diff. Agent Prompt
|
||||||
| print(f' → {dst} に書き戻した') | ||||||
| else: | ||||||
| print(' (--write を付けると書き戻す)') | ||||||
|
|
||||||
|
|
||||||
| if __name__ == '__main__': | ||||||
| main(sys.argv[1], sys.argv[2]) | ||||||
| main() | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): The documented
python3 enrich_git.py --tsv in.tsv --out out.tsvcommand does not writeout.tsvbecause output is performed only when--writeis also supplied. The initial-generation workflow therefore silently produces no output file.Triggers: When the retained TSV input/output mode is used without
--write, as shown in the module usage documentation.Suggested fix: Make specifying
--outimply output generation, or update the documented command to include--writeand validate that the destination was written.