- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
。
前言:本文主要记录了基于低版本gitlab(v3 api)实现in-line comment功能的过程中踩过的坑及相应的解决方案,理论上其他低版本gitlab不具备的API都可以参照此类方法进行实现(只要能通过页面操作抓取到相关接口),从而让低版本gitlab焕发新生~ 。
。
近期我们落地了AI Code review实践,在CICD流程中当merge request创建时,会基于MR的diff代码进行AICR并将问题以comment的形式提交,如下图所示:
目前我们希望进一步改进体验:除了整体的comment,也希望能将所有问题分别以in-line comment的形式插入到对应代码片段的对应行号中,以便形成研发CR时边看代码边看问题的无缝体验(无需频繁在Discussion问题列表和代码Changes面板之间切换) 。
但因为一些历史原因,我们公司目前还存在新旧两套gitlab,其中旧的gitlab版本较低(8.16 v3 API),没有现成的in-line comment API。下文将分为两个阶段来陈述如何在低版本gitlab上实现in-line comment功能以及如何兼容一些特殊场景以增强用户体验 。
。
根据AI返回的行号,将对应问题in-line comment到代码changes面板上,方便开发CR代码时就能看到相关问题,期望效果如下图所示:
低版本gitlab没有in-line comment API 。
通过抓取页面操作相关接口,最终通过模拟登录态并获取所有所需参数,从而构造出与在web页面上操作等价的in-line comment请求 。
通过抓取web页面上的in-line comment请求,发现有一个notes请求。经过分析与回放尝试,发现有如下必传参数是需要根据情况动态获取的:
。
按相关结构组装这些参数,构造与页面操作等价的in-line comment请求.
至此,便完成了基础的in-line comment功能 。
def gitlab_v3_in_line_comment(self, project_id, mr_id, mr_iid, comment, file_name, old_line, new_line):
'''gitlab低版本无现成的in-line comment API,故通过页面抓取的接口曲线进行(需要模拟登录态并构造相关参数)
comment: 要备注的内容,即代码问题描述
file_name: AI返回的文件名
code_line: AI返回的行号
'''
# 获取gitlab session
session = self.gitlab_v3_get_login_session()
# Step 3: 重新提取评论页面的 authenticity_token(不能沿用登录页面的authenticity_token)
path_with_namespace = self.gitlab_get_project_by_id(project_id).get('path_with_namespace', '')
comment_page = session.get(f"https://git.kugou.net/{path_with_namespace}/merge_requests/{mr_iid}")
soup = BeautifulSoup(comment_page.text, "html.parser")
authenticity_token = soup.find("input", {"name": "authenticity_token"})["value"]
# print("MR页面authenticity_token", authenticity_token)
# step 4: 获取MR相关信息
base_sha, start_sha, head_sha = self.gitlab_v3_get_mr_diff_versions(project_id, mr_id) # 获取MR的diff版本信息
# 根据AI返回的文件名,获取old_path、new_path
result = self.gitlab_v3_get_single_mr_changes(project_id, mr_id, file_name)
if result:
old_path = result[0].get('old_path', '')
new_path = result[0].get('new_path', '')
else:
old_path = new_path = file_name
# Step 5: 构造评论请求
comment_url = f"https://git.kugou.net/{path_with_namespace}/notes"
comment_payload = {
"utf8": "✓",
"authenticity_token": authenticity_token,
"view": "inline",
"line_type": "", # 新增代码传new,已有代码传空(但是都为空也能成功)
"merge_request_diff_head_sha": head_sha,
"target_type": "merge_request",
"target_id": f"{mr_id}", # Merge Request 的唯一 ID
"note[commit_id]": "",
# "note[line_code]": "25d1157f6eee34be77947760c3d83e1f34efeb31_235_238", # 页面锚点id_oldline_newline,非必须参数
"note[noteable_id]": f"{mr_id}", # [必须]与 target_id 一致
"note[noteable_type]": "MergeRequest",
"note[type]": "DiffNote",
# "note[position]": '{"old_path":"GuessSongActivityService.java","new_path":"GuessSongActivityService.java","new_line":55,"base_sha":"29f230c853e957e049c7ef3cf8ba7435f82479ef","start_sha":"29f230c853e957e049c7ef3cf8ba7435f82479ef","head_sha":"3160984ed116026742a911ec7d8cf332e4fd4c3c"}',
"note[position]": json.dumps({
"old_path": old_path, # 旧文件路径,通过gitlab_v3_get_single_mr_changes接口获取
"new_path": new_path, # 新文件路径,同上
"old_line": old_line, # 若是新旧行号不一致的情况,old_line/new_line均必传,否则会报错(采用页面解析方案获取,第一阶段未传该参数踩坑了)
"new_line": new_line, # 针对新增的代码进行in-line comment,只需要传入该参数即可,old_line可不传
"base_sha": base_sha, # MR的diff版本信息,通过gitlab_v3_get_mr_diff_versions接口获取
"start_sha": start_sha, # 与base_sha获取方法一致
"head_sha": head_sha # 与base_sha获取方法一致
}),
"note[note]": comment, # 具体的评论内容
"commit": "Comment"
}
# print('\n -----构造的评论参数----- ', comment_payload)
# Step 6: 发送评论请求
response = session.post(comment_url, data=comment_payload)
# print('评论结果 : ', response.status_code, response.text)
return response
def gitlab_v3_get_login_session(self):
'''
gitlab登录态
若缓存中的session数据未失效,则直接从缓存中获取相关数据并构造session
若已失效,则重新登录获取session,并缓存
'''
SESSION_CACHE_KEY = "gitlab_session_for_AICR"
SESSION_TIMEOUT = 3600 * 8
# 获取已认证的session,如果缓存存在则直接使用
session_data = cache.get(SESSION_CACHE_KEY)
if session_data:
# 从缓存恢复session
session = requests.Session()
session.cookies.update(session_data.get("cookies", {}))
session.headers.update(session_data.get("headers", {}))
return session
# 如果缓存不存在,重新登录并缓存
# Step 1: 获取登录页面并提取 authenticity_token
login_url = "https://git.kugou.net/users/sign_in"
session = requests.Session()
login_page = session.get(login_url)
soup = BeautifulSoup(login_page.text, "html.parser")
auth_token_input = soup.find('input', {'name': 'authenticity_token', 'type': 'hidden'})
if auth_token_input:
authenticity_token = auth_token_input['value']
else:
return ''
# Step 2: 登录
login_payload = {
"user[login]": settings.AICR_USER, # AI-CodeReviewer用户名
"user[password]": settings.AICR_PASS, # AI-CodeReviewer密码
"authenticity_token": authenticity_token # 登录页面的authenticity_token
}
response = session.post(login_url, data=login_payload)
# 检查是否登录成功(根据 GitLab 的页面返回内容或状态码判断)
if response.status_code != 200 or "Sign in" in response.text:
logger.error("gitlab Login failed. Please check credentials.")
else:
# 缓存session(Session对象无法直接序列化,需要提取cookies和headers,以便后续构造)
session_data = {
"cookies": session.cookies.get_dict(),
"headers": dict(session.headers)
}
cache.set(SESSION_CACHE_KEY, session_data, timeout=SESSION_TIMEOUT)
return session
。
第一阶段功能上线运行一段时间后,我们发现很多AICR问题没有成功提交in-line comment,经过分析主要是以下两种原因:
针对上述问题,我们希望达成如下效果,以改进用户体验 。
1)当AI返回的行号在MR changes面板被隐藏折叠时,将相关AICR问题备注到最相邻的那一行,如下所示 。
2)针对变更代码 old_line、new_line不一致的情况,获取页面上实际展示的新/旧行号构造请求 。
。
1)构造登录态请求diffs.json接口,拿到所有diff文件的html源码 。
2)结合BeautifulSoup解析步骤1拿到的html源码,找到对应文件的div,然后拿到所有行号的data-position信息 。
3)构造line_mapping_dict,用来判断传入的行号参数的有效性,并做兼容性处理 。
。
MR Changes面板每一个文件就是一个大的div块,其中每一行代码对应一个tr,然后每个tr中有相关行号信息,我们只需要解析整个页面然后获取相关行号即可 。
过程中还有一个小插曲:实际应用中发现行号在MR页面上不展示的情况下,对应in-line comment的行号参数也必须传null,如果传了<tr>标签中展示的原行号也会报错... 故继续在页面上找规律,发现在<td>标签中的data-position属性符合需求(页面上不展示的行号其值为null),故进一步调整解析逻辑 。
另外过程中还发现MR的change页面不是一个纯静态页面,代码diff信息是通过接口动态获取的。故没法直接通过访问MR的URL来拿到代码diff内容,需要调用相应的diffs.json接口获取,所以我们需要解析该接口的返回内容(好在接口返回内容也是html格式,可以结合BeautifulSoup快速处理) 。
将每个文件在页面上展示的新/旧行号都存储下来,然后基于AI给定的行号进行有效性判定:
。
踩完各种坑之后,最终效果如下:
。
def get_mr_changefile_display_lines(self, project_id, mr_iid, file_name):
'''
获取MR changes板块对应文件的所有展示出来的行号,用于后续处理兼容以下特殊情况:
1. AI返回的行号在MR changes板块被折叠隐藏
2. 修改代码的情况下,代码行号发生变化,in-line comment的行号需要同时传对应的old_line和new_line
return: line_mapping_dict eg: {4: (4, 4), 5: (5, 5), 357: (357, 360), 360: (357, 360)}
'''
session = self.gitlab_v3_get_login_session()
path_with_namespace = self.gitlab_get_project_by_id(project_id).get('path_with_namespace', '')
resp = session.get(f"https://git.kugou.net/{path_with_namespace}/merge_requests/{mr_iid}/diffs.json")
html_content = resp.json().get('html', '')
# 使用 BeautifulSoup解析HTML片段
soup = BeautifulSoup(html_content, 'html.parser')
# 定义目标文件的关键字
target_keyword = f"{file_name}/diff" # 如 services/numeric/numeric_service.go/diff
# 查找 data-blob-diff-path属性包含特定字符串的 div标签
target_div = soup.find(
'div',
attrs={"data-blob-diff-path": lambda value: value and target_keyword in value}
)
# 如果找到目标div,则进一步解析
if target_div:
# 查找该div下符合条件的所有class包含line_content和noteable_line的<td>,形如<td class="line_content new noteable_line old">
td_elements = target_div.find_all(
'td',
attrs={'class': lambda x: x and set(['line_content', 'noteable_line']).issubset(x.split())}
)
# 提取所有行号,并构造行号映射字典,形如:{4: (4, 4), 5: (5, 5), 357: (357, 360), 360: (357, 360)}
line_mapping_dict = {}
for td in td_elements:
# 获取 data-position 属性
data_position = td.get('data-position')
if data_position:
try:
# 将 JSON 字符串解析为字典
position_data = json.loads(data_position)
old_line = position_data.get('old_line')
new_line = position_data.get('new_line')
# 以old_line、new_line作为key分别存储一次,方便后续可以根据任意匹配行号查询到相应的old_line、new_line对
if old_line: line_mapping_dict[old_line] = (old_line, new_line) # 避免存这种 None: (None, 357)
if new_line: line_mapping_dict[new_line] = (old_line, new_line)
except json.JSONDecodeError:
logger.error("Failed to decode JSON in data-position: %s", data_position)
# print(line_mapping_dict)
return line_mapping_dict
else:
# print(f"未找到包含{target_keyword}的div")
return None
。
def find_line_mapping(line_number, line_mapping_dict):
"""
根据AI提供的行号在line_mapping_dict中查找相关联的新/旧行号。
如果行号存在,直接返回;如果不存在,返回最接近的行号。
return: (old_line, new_line)
"""
if not line_number or not line_mapping_dict:
return None
# print("line_number : ", line_number)
if line_number in line_mapping_dict:
return line_mapping_dict[line_number]
# 获取所有行号并排序
sorted_lines = sorted(line_mapping_dict.keys())
# 找到最接近的行号
closest_line = None
min_diff = float('inf') # 初始设置为无穷大,以便首次比较
for line in sorted_lines:
diff = abs(line - line_number)
if diff < min_diff:
min_diff = diff
closest_line = line
return line_mapping_dict[closest_line]
。
以上就是在低版本gitlab上实现in-line comment全过程了,希望能对也有此类需求的朋友提供一个参考思路 。
。
最后此篇关于让低版本gitlab焕新——如何在低版本gitlab上实现高版本API功能的文章就讲到这里了,如果你想了解更多关于让低版本gitlab焕新——如何在低版本gitlab上实现高版本API功能的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在寻找一种方法来创建根据价格选择我的产品的过滤器(选择下拉菜单)。 我知道这样的查询是完全可能的: SELECT * FROM products ORDER BY price ASC SELECT
函数参数中或显示尺寸时(高度,宽度)的顺序是否有约定? 最佳答案 我不知道大量的语言,但我使用过的语言(宽度,高度)。它更适合沿着 (x, y) 坐标线。 关于language-agnostic -
在我的表单中,我让用户输入房间的长度高度和宽度以获得 m2、m3 和瓦特的计算值。但是用户也应该能够直接输入 height 和 m2 来获取值。我尝试了很多语法,但 if else 不能正常工作。我知
我在 Elasticsearch 中创建了一个索引,看起来像 {"amazingdocs":{"aliases":{},"mappings":{"properties":{"Adj Close":{"
我有以下功能,我需要清除数据库中的所有图片列并移动到文件系统。当我一次性完成这一切时,内存太多并且会崩溃。我切换到递归函数并执行 20 次写入和批量操作。 我需要为大约 6 个表执行此操作。我的 Re
我正在编写一个函数来计算 PI 的值,并将其作为 double 值返回。到目前为止,一切都很好。但是一旦函数到达小数点后14位,它就不能再保存了。我假设这是因为 double 有限。我应该怎么做才能继
2020年是中国CDN行业从98年诞生到今天快速发展的第二十四年,相关数据显示,全国感知网速持续上扬,达到了3.29兆/秒,标志着在宽带中国的政策指导下,中国的网速水平正在大步赶上世界发达国家的水平
在 aerospike 集合中,我们有四个 bin userId、adId、timestamp、eventype,主键是 userId:timestamp。在 userId 上创建二级索引以获取特定用
$('#container').highcharts('Map', { title : { text : 'Highmaps basic demo'
有没有办法显示自定义宽度/高度的YouTube视频? 最佳答案 在YouTube网站上的this link中: You can resize the player by editing the obj
我使用 Highcharts ,我想在 Highcharts 状态下悬停时制作动态不同的颜色。 正如你可以看到不同的颜色,这就是我做的 var usMapChart , data = [] ; va
在所有节点上运行 tpstats 后。我看到很多节点都有大量的 ALL TIME BLOCKED NTR。我们有一个 4 节点集群,NTR ALL TIME BLOCKED 的值为: 节点 1:239
我发现 APC 上存在大量碎片 (>80%),但实际上性能似乎相当不错。我有 read another post这建议在 wordpress/w3tc 中禁用对象缓存,但我想知道减少碎片是否比首先缓存
对于我的脚本类(class),我们必须制作更高/更低的游戏。到目前为止,这是我的代码: import random seedVal = int(input("What seed should be u
我发现 APC 上存在大量碎片 (>80%),但实际上性能似乎相当不错。我有 read another post这建议在 wordpress/w3tc 中禁用对象缓存,但我想知道减少碎片是否比首先缓存
对于我的脚本类(class),我们必须制作更高/更低的游戏。到目前为止,这是我的代码: import random seedVal = int(input("What seed should be u
我已经 seen >2 字节的 unicode 代码点,如 U+10000 可以成对编写,如 \uD800\uDC00。它们似乎以半字节 d 开头,但我只注意到了这一点。 这个 split Actio
有人可以帮我理解为什么我的饼图百分比计算不正确吗?看截图: 根据我的计算,如 RHS 上所示,支出百分比应为 24.73%。传递给 Highcharts 的值如下:- 花费:204827099.36-
我阅读了有关该问题的所有答案,但我还没有找到任何解决方案。 我有一个应用程序,由我的 api 服务器提供。 Wildfly 8.1 和 Mysql 5.6。当查看时间到来时(Wildfly 服务器连接
我正在用选定的项目创建圆形导航。当用户单击任何项目时,它将移动到定义的特定点。一切都很好,除了当你继续点击项目时,当动画表现不同并且项目在 360 度圆中移动并且它被重置直到你重复场景时,我希望它
我是一名优秀的程序员,十分优秀!