一、背景
公司之前有统计下发人员在Gitlab上的代码合并情况,但不知什么原因已经停止好几个月了;然而团队内的代码管理不能停,于是想着让AI帮忙搞个工具来统计,自己把控下统计的业务、范围和时机。
二、实现过程
- 向AI问询要实现这种统计的方法
- 得知可以使用gitlab提供的Rest API进行操作
- 实现的开发语言选择,Java虽然也可以但显然python干这种事情更方便、灵活。
- 整理统计的业务和步骤,让AI进行代码实现。
- 逐步提问、验证,最终形成最终的python脚本。
- 执行脚本并导出到Excel查看。
最终实现:指定group在某段时间范围内合并代码到develop分支的情况,并输出到Excel。
三、关键设计
- 常量参数定义:有些参数是动态的,需要进行常量的定义,以方便每次执行的调整。包含顶级的group名称(数组)、查询的开始时间、结束时间、合并的目标分支、导出的文件名称以及gitlab服务地址和认证信息。
- Rest API的认证:有两种方式,建议token方式
-
Access Token:登录个人的gitlab账号,点击个人图像-Edit profile进行个人设置界面,然后点击Access Tokens,添加一个新的token,注意Scopes要选择api,确认好过期时间并随即保存生成的token;并在请求api时直接在headers指定"PRIVATE-TOKEN: 生成的token"。
-
Cookie:若只是临时的使用,在登录gitlab后直接f12在cookie中获取一个叫“_gitlab_session”的值,并在请求api时在headers指定"cookie: _gitlab_session=f6f4959a5296e06535b3166d0ef91bfd"。
-
-
Gitlab的group处理:在gitlab中一个顶级group是可以进行多级嵌套的,需要进行子group的递归处理,避免group和其下项目统计的遗漏。
-
Rest API调用逻辑:
-
1、获取顶级group的id:/api/v4/groups/demo_group
-
2、查询其下子group:/api/v4/groups/:groupId/subgroups
-
3、查询group的项目列表:/api/v4/groups/:groupId/projects
-
4、查询项目的合并情况:/api/v4/projects/:project/merge_requests?state=merged&target_branch=develop&author_username=zhangsan&created_after=2025-03-01T00:00:00Z&created_before=2025-03-31T00:00:00Z&per_page=100
-
-
Excel输出的信息:要求包含合并请求ID、项目ID、合并请求标题、合并请求说明、合并时间、合并作者、代码Review人、合并请求地址等等信息
四、代码示例
import urllib3
import requests
import pandas as pd
from datetime import datetime
# 关闭 https 安全告警(如果你用 verify=False)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# === 配置项 ===
PRIVATE_TOKEN = "xxxx" # 替换为你的 GitLab Access Token
GITLAB_URL = "https://xxx.gitlab.com/" # 替换为你的 GitLab 实际地址
TOP_GROUPS = ["demo_group"] # 替换为目标 group 的名称列表
START_DATE = "2025-03-01T00:00:00Z" # 指定合并请求的开始时间
END_DATE = "2025-03-31T23:59:59Z" # 指定合并请求的结束时间
TARGET_BRANCH = "develop" # 默认为开发分支
EXPORT_FILENAME = "3月份代码合并情况-demo_group.xlsx" # 默认导出的文件名称
HEADERS = {
"PRIVATE-TOKEN": PRIVATE_TOKEN
}
# === 获取 group ID ===
def get_group_id(group_path):
try:
url = f"{GITLAB_URL}/api/v4/groups/{group_path}"
resp = requests.get(url, headers=HEADERS, timeout=30, verify=False)
resp.raise_for_status()
return resp.json().get("id")
except requests.exceptions.RequestException as e:
print(f"Error getting group ID for {group_path}: {str(e)}")
return None
# === 获取 group 下所有项目 ===
def get_projects_by_group_id(group_id):
projects = []
page = 1
while True:
url = f"{GITLAB_URL}/api/v4/groups/{group_id}/projects?per_page=100&page={page}"
resp = requests.get(url, headers=HEADERS, verify=False)
if not resp.ok or not resp.json():
break
projects.extend(resp.json())
page += 1
return projects
# === 获取子 group 列表 ===
def get_subgroups(group_id):
subgroups = []
page = 1
while True:
url = f"{GITLAB_URL}/api/v4/groups/{group_id}/subgroups?per_page=100&page={page}"
resp = requests.get(url, headers=HEADERS, verify=False)
if not resp.ok or not resp.json():
break
subgroups.extend(resp.json())
page += 1
return [{"id": g["id"], "full_path": g["full_path"]} for g in subgroups]
# === 递归获取所有项目 ===
def get_all_projects_recursively(group_id):
all_projects = get_projects_by_group_id(group_id)
subgroups = get_subgroups(group_id)
for subgroup in subgroups:
all_projects.extend(get_all_projects_recursively(subgroup["id"]))
print(f" ▶ 获取到子group: {subgroup['full_path']}")
return all_projects
# === 从多个顶级 group 名称中获取所有项目 ===
def get_all_projects_from_group_names(group_names):
all_projects = []
for group_name in group_names:
print(f" ▶ 正在处理 group: {group_name}")
group_id = get_group_id(group_name)
if group_id:
all_projects.extend(get_all_projects_recursively(group_id))
else:
print(f"⚠️ 无法获取 group ID:{group_name}")
return all_projects
# === 获取指定项目的合并请求信息 ===
def get_merge_requests(project_id):
merge_requests = []
page = 1
while True:
url = (
f"{GITLAB_URL}/api/v4/projects/{project_id}/merge_requests?state=merged"
f"&target_branch={TARGET_BRANCH}&updated_after={START_DATE}&updated_before={END_DATE}"
f"&per_page=100&page={page}"
)
resp = requests.get(url, headers=HEADERS, verify=False)
if not resp.ok or not resp.json():
break
for mr in resp.json():
merge_requests.append({
"id": mr.get("id"),
"project_id": mr.get("project_id"),
"title": mr.get("title"),
"description": mr.get("description"),
"created_at": mr.get("created_at"),
"updated_at": mr.get("updated_at"),
"author_username": mr.get("author", {}).get("username"),
"author_name": mr.get("author", {}).get("name"),
"reviewer": (mr.get("reviewers")[0]["name"] if mr.get("reviewers") else None),
"web_url": mr.get("web_url"),
})
page += 1
return merge_requests
# === 执行统计流程 ===
def main():
print("[1/3] 正在获取 group 下的所有项目...")
projects = get_all_projects_from_group_names(TOP_GROUPS)
print(f"[2/3] 共获取到 {len(projects)} 个项目,开始抓取合并请求...")
all_merge_requests = []
for idx, project in enumerate(projects):
print(f" -> ({idx+1}/{len(projects)}) 处理项目: {project['name']}")
all_merge_requests.extend(get_merge_requests(project["id"]))
print(f"[3/3] 共获取到符合条件的合并请求: {len(all_merge_requests)} 条,正在导出 Excel...")
df = pd.DataFrame(all_merge_requests)
df.to_excel(EXPORT_FILENAME, index=False)
print(f"✅ 导出完成,文件路径:{EXPORT_FILENAME}")
if __name__ == "__main__":
main()
五、问题
1、很多企业内部 GitLab 服务器使用了非公开 CA 签发的 SSL 证书,这就会导致 SSL 验证失败。目前在请求中禁用证书验证,引入了urllib3进行设置和verify=False的配置。
2、当前AI生成的python代码其实是存在一些问题的,比如:进行group和project列表收集时使用的是数组的提前定义,在信息量大的时间占用的内存会比较大,甚至会出现中断。有兴趣的小伙伴可以尝试着优化下。
3、通用AI的能力没有想象那么全能,在过程中也经历过了多次错误,人工检查核对不能丢,目前当工具来使用挺好,我们做好把控和审核挺好。
六、后续扩展
1、后续可以同时统计个人每次合并请求新增、删除的代码行数,以此统计人员的代码行数产量。
比如可以使用GET /projects/:id/merge_requests/:merge_request_iid/changes接口
2、/api/v4/groups/demo_group的接口不必查询,直接到group详情页面获取ID?
3、附上官方Rest API文档地址:Merge requests API | GitLab Docs

7676

被折叠的 条评论
为什么被折叠?



