|
@@ -2,7 +2,7 @@ import os, json,hashlib,re
|
|
|
from fastapi import APIRouter, BackgroundTasks
|
|
|
from base_config import path, avatar_url
|
|
|
|
|
|
-from git import Repo
|
|
|
+from git import Repo, GitCommandError
|
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
@@ -67,21 +67,88 @@ async def log(request: RequestBody):
|
|
|
local_path, _ = generate_repo_path(request.uuid, request.repo_url)
|
|
|
repo = get_repo(request.uuid, request.repo_url)
|
|
|
if not repo:
|
|
|
- return {"status": "404", "msg": "仓库不存在", "uuid": request.uuid, "repo_url": request.repo_url,
|
|
|
- "local_path": local_path}
|
|
|
-
|
|
|
- log_ = repo.git.log('--pretty={"commit":"%h","author":"%an","email":"%ce","summary":"%s","date":"%cd"}', max_count=50,
|
|
|
- date='format:%Y-%m-%d %H:%M').split("\n")
|
|
|
- log = list(map(json.loads, log_))
|
|
|
- for i in log:
|
|
|
- email = i["email"]
|
|
|
- email_md5 = hashlib.md5(email.encode(encoding='UTF-8')).hexdigest()
|
|
|
- i["avatar"] = avatar_url+email_md5+"?d=identicon"
|
|
|
- status=repo.git.execute(["git", "show",i["commit"] , "--shortstat"]).split("\n")[-1]
|
|
|
- i["change"]=git_stats_to_json(status)
|
|
|
- response = {"status": "200", "msg": "成功获取日志", "uuid": request.uuid, "repo_url": request.repo_url,
|
|
|
- "local_path": local_path, "git_log": log}
|
|
|
- return response
|
|
|
+ return {
|
|
|
+ "status": "404",
|
|
|
+ "msg": "仓库不存在",
|
|
|
+ "uuid": request.uuid,
|
|
|
+ "repo_url": request.repo_url,
|
|
|
+ "local_path": local_path
|
|
|
+ }
|
|
|
+
|
|
|
+ # 使用git log --numstat一次性获取所有必要信息
|
|
|
+ git_log_format = '--pretty=format:%h|%an|%ce|%s|%cd'
|
|
|
+ try:
|
|
|
+ log_output = repo.git.log(
|
|
|
+ git_log_format,
|
|
|
+ '--numstat',
|
|
|
+ '--no-renames',
|
|
|
+ date='format:%Y-%m-%d %H:%M'
|
|
|
+ )
|
|
|
+ except GitCommandError as e:
|
|
|
+ return {"status": "500", "msg": f"获取日志失败: {str(e)}"}
|
|
|
+
|
|
|
+ log = []
|
|
|
+ current_commit = None
|
|
|
+
|
|
|
+ for line in log_output.split('\n'):
|
|
|
+ if not line.strip():
|
|
|
+ continue # 跳过空行
|
|
|
+
|
|
|
+ if '\t' in line and len(line.split('\t')) == 3:
|
|
|
+ # 处理numstat行,例如 "10\t5\tfile.txt"
|
|
|
+ if current_commit is None:
|
|
|
+ continue # 防止数据错误
|
|
|
+ insertions_str, deletions_str, _ = line.split('\t')
|
|
|
+ try:
|
|
|
+ insertions = int(insertions_str) if insertions_str != '-' else 0
|
|
|
+ deletions = int(deletions_str) if deletions_str != '-' else 0
|
|
|
+ except ValueError:
|
|
|
+ insertions, deletions = 0, 0
|
|
|
+ current_commit['change']['insertions'] += insertions
|
|
|
+ current_commit['change']['deletions'] += deletions
|
|
|
+ current_commit['change']['files'] += 1
|
|
|
+ else:
|
|
|
+ # 处理提交信息行,例如 "abc123|Author|email|summary|2023-10-01 12:34"
|
|
|
+ if current_commit is not None:
|
|
|
+ # 生成avatar的md5
|
|
|
+ email = current_commit['email']
|
|
|
+ email_md5 = hashlib.md5(email.encode('utf-8')).hexdigest()
|
|
|
+ current_commit['avatar'] = f"{avatar_url}{email_md5}?d=identicon"
|
|
|
+ log.append(current_commit)
|
|
|
+ try:
|
|
|
+ commit_hash, author, email, summary, date = line.split('|', 4)
|
|
|
+ current_commit = {
|
|
|
+ "commit": commit_hash,
|
|
|
+ "author": author,
|
|
|
+ "email": email,
|
|
|
+ "summary": summary,
|
|
|
+ "date": date,
|
|
|
+ "avatar": "",
|
|
|
+ "change": {
|
|
|
+ "insertions": 0,
|
|
|
+ "deletions": 0,
|
|
|
+ "files": 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ except ValueError:
|
|
|
+ current_commit = None # 忽略格式错误行
|
|
|
+
|
|
|
+ # 添加最后一个提交
|
|
|
+ if current_commit is not None:
|
|
|
+ email = current_commit['email']
|
|
|
+ email_md5 = hashlib.md5(email.encode('utf-8')).hexdigest()
|
|
|
+ current_commit['avatar'] = f"{avatar_url}{email_md5}?d=identicon"
|
|
|
+ log.append(current_commit)
|
|
|
+
|
|
|
+ # 按时间倒序排列(git log默认最新在前)
|
|
|
+ return {
|
|
|
+ "status": "200",
|
|
|
+ "msg": "成功获取日志",
|
|
|
+ "uuid": request.uuid,
|
|
|
+ "repo_url": request.repo_url,
|
|
|
+ "local_path": local_path,
|
|
|
+ "git_log": log
|
|
|
+ }
|
|
|
|
|
|
|
|
|
@gitrouter.post("/status")
|