【Git + PowerShell】自动为 Git stage 新增修改添加注释 marker 的完整实现解析 🎯
在团队协作开发中,我们常常希望追踪某些文件的变更来源。本文基于 PowerShell 脚本与 Git diff ,演示如何在每次提交前自动插入注释 marker。
🧩 业务需求说明
我们希望通过脚本自动完成如下几件事:
🎯 自动化目标
-
使用
git diff --cached
获取当前 staged 的代码变更; -
提取这些变更所涉及的具体 code block(补丁区块) ;
-
为每一个 block 生成一个 marker 注释,标注此次更改类型为:
- Addition(新增)
- Modification(修改)
-
将这些 marker 插入到项目中的对应
.js
,.ts
,.html
等源代码文件中,以便后续通过搜索快速识别变更来源。
🔁 整体架构图示
css
[获取 git diff]
↓
[解析 hunk 信息]
↓
[构建 patch blocks]
↓
文件遍历 → 匹配 patch → 插入 marker
↓
[更新文件内容 -> 完成写入]
🔍 详细分析代码结构
我们将对关键模块进行详细解读,帮助你理解每一部分的功能和作用。
✅ Step 1:获取 Git Diff 并拆分为块处理
bash
$gitDiff = git diff --cached -U0
if (-not $gitDiff) {
Write-Host "No changes are currently staged."
exit
}
--cached
表示只查看已 stage 的修改。-U0
设置上下文行为,不显示额外 context 行。
随后利用正则匹配方式,解析出 diff 内容中各"hunk"位置和文本,判断其属于哪一类变更(Addition/Modification),保存至 $patchBlocks
中:
✨ Patch Block 示例格式:
vbnet
File: dealProductHierarchy.js
Type: Modification
OldText: [ "get isModalHeader()", ... ]
NewText: [ "get isModalContsent()", ... ]
✅ Step 2:尝试在源文件中找到匹配的 code block
使用下面这个函数来判断某段代码是否存在于当前打开的源文档中:
php
function Try-MatchPatchInFile {
param(
[string[]]$fileLines,
[string[]]$patchLines,
[int]$fromIndex
)
for ($i = 0; $i -lt $patchLines.Length; $i++) {
if ($fromIndex + $i -ge $fileLines.Length -or $fileLines[$fromIndex + i] -ne $patchLines[i]) {
return False
}
}
return True
}
⚠️ ⚠️ 注意:目前该函数是查找并覆盖原文本的效果,我们将在后面对此作优化处理(插入而非覆盖)。
✅ Step 3:插入 marker 到原始文件中
一旦找到匹配位置,就构造一段类似如下形式的内容,并插入到原文件中:
csharp
<JAVASCRIPT>
// Start: Modified for <ISSUE_ID>
// get isModalHeader()
// return ...
get isModalContsent() {
// content
}
// End: Modified for <ISSUE_ID>
构造方法如下:
perl
$insertedLines = @("$markerStart Original Code Below:")
foreach ($line in $block.OldText) {
$insertedLines += " // $line"
}
foreach ($line in $block.NewText) {
$insertedLines += $line
}
$insertedLines += "$markerEnd"
WriteOutputAndRewriteFile -filePath $filePath -insertPosMap $insertPosMap
💡 举例说明------具体算法运行过程
🌟 输入内容(Git Diff)
csharp
<DIFF>
+ get isDetailsSectionClosed() {
+ return !this.partnerUsers ? true : false;
+ }
- get isModalHeader() {
- return this.partnerUsers ? ...
- }
+ get isModalHeadContent() {
+ return something;
+ }
✈️ 处理步骤简述:
- 拆分 diff 内每一段 patch(hunk)
- 构建两个版本:old text vs new text
- 判断是否修改(compare 文本内容)
- 若能找到旧版本匹配的行,则在此位置插入 marker
- 写入新内容 + 注释
🔄 核心数据流程实例
变量名 | 值(例子) |
---|---|
$fileArr |
[ " function abc()", "[...]"," get isModalHeader()" ] |
$block |
File = "dealProductHierarchy.js", OldText = @[ "get isModalHeader()\n{...}" ], NewText =@[ "get isModalContsent()" ... ] |
$insertPos |
从第 69 行开始 |
$insertedLines |
包含了 "// Start...", 旧文本注释,新文本,以及 "// End..." |
🧾 最终成果
执行此脚本后,你的 JavaScript 或 TypeScript 文件将被注入如下的 marker 信息,有助于后期追溯代码修改来源:
scss
<JAVASCRIPT>
// Start: Modified for <ISSUE_ID>
// function oldOne() {
// return something();
// }
function newOne() {
return updatedLogic();
}
// End: Modified for <ISSUE_ID>
🧑🏻💻 完整代码
你可以直接复制下来在本地测试使用:
perl
# 定义 issue 编号
$issueNumber = "<ISSUE_ID>"
Write-Host "Analyzing staged changes..."
# 获取 git diff --cached 的结果
$gitDiff = git diff --cached -U0
if (-not $gitDiff) {
Write-Host "No changes are currently staged."
exit
}
# 拆分为行进行处理
$lines = $gitDiff -split "`n"
$currentFile = $null
$inHunk = $false
$patchBlocks = @()
$currentPatchOld = @()
$currentPatchNew = @()
foreach ($line in $lines) {
if ($line.StartsWith("diff ") -or $line -match "^index .+") {
continue
}
if ($line -like "+++ b/*") {
$currentFile = $line.Substring(5).Trim()
continue
}
if ($line -match "^@@ -(?\d+),?(\d*) +(?\d+),?(\d*) @@") {
if ($inHunk -and ($currentPatchOld.Count -gt 0 -or $currentPatchNew.Count -gt 0)) {
$hasModifiedLine = $false
for ($i = 0; $i -lt [Math]::Min($currentPatchOld.Count, $currentPatchNew.Count); $i++) {
if ($currentPatchOld[$i].Text.Trim() -ne $currentPatchNew[$i].Text.Trim()) {
$hasModifiedLine = $true
break
}
}
$type = if ($hasModifiedLine) { "Modification" } else { "Addition" }
$patchBlocks += [PSCustomObject]@{
File = $currentFile
Type = $type
OldText = $currentPatchOld.Text
NewText = $currentPatchNew.Text
}
}
$currentPatchOld = @()
$currentPatchNew = @()
$inHunk = $true
continue
}
if ($inHunk) {
if ($line.StartsWith("-")) {
$currentPatchOld += [PSCustomObject]@{ Text = $line.Substring(1); Type = '-' }
} elseif ($line.StartsWith("+") -and -not ($line.StartsWith("++"))) {
if($line.Substring(1).Trim() -ne "") {
$currentPatchNew += [PSCustomObject]@{ Text = $line.Substring(1); Type = '+' }
}
}
}
}
if ($inHunk -and ($currentPatchOld.Count -gt 0 -or $currentPatchNew.Count -gt 0)) {
$hasModifiedLine = $false
for ($i = 0; $i -lt [Math]::Min($currentPatchOld.Count, $currentPatchNew.Count); $i++) {
if ($currentPatchOld[$i].Text.Trim() -ne $currentPatchNew[$i].Text.Trim()) {
$hasModifiedLine = $true
break
}
}
$type = if ($hasModifiedLine) { "Modification" } else { "Addition" }
$patchBlocks += [PSCustomObject]@{
File = $currentFile
Type = $type
OldText = $currentPatchOld.Text
NewText = $currentPatchNew.Text
}
}
function Try-MatchPatchInFile {
param([string[]]$fileLines,[string[]]$patchLines,[int]$fromIndex)
for ($i=0; $i -lt $patchLines.Length; $i++) {
if ($fromIndex+$i -ge $fileLines.Length -or $fileLines[$fromIndex+$i] -ne $patchLines[$i]) {
return $false
}
}
return $true
}
function WriteOutputAndRewriteFile {
param([string]$filePath, [string[]]$insertLines, [int]$fromIndex)
$contentArr = Get-Content $filePath
$newContent = @()
for ($k = 0; $k -lt $fromIndex; $k++) {
$newContent += $contentArr[$k]
}
for ($k = 0; $k -lt $insertLines.Count; $k++) {
$newContent += $insertLines[$k]
}
for ($k = $fromIndex + $insertLines.Count; $k -lt $contentArr.Length; $k++) {
$newContent += $contentArr[$k]
}
Set-Content -Value $newContent -Path $filePath
}
foreach ($block in $patchBlocks) {
$filePath = Join-Path (Get-Location).ProviderPath $block.File
if (-not (Test-Path $filePath)) {
Write-Warning "File not found: $filePath"
continue
}
$fileArr = Get-Content $filePath
$foundMatch = $false
for ($i = 0; $i -lt $fileArr.Length; $i++) {
if (Try-MatchPatchInFile -fileLines $fileArr -patchLines $block.NewText -fromIndex $i) {
$markerStart = " // Start:"
$markerEnd = " // End:"
switch ($block.Type) {
"Addition" { $markerStart += " Added"; $markerEnd += " Added" }
"Modification" { $markerStart += " Modified"; $markerEnd += " Modified" }
}
$markerStart += " for $issueNumber Original Code Below:"
$markerEnd += " for $issueNumber"
$insertedLines = @("$markerStart")
if ($block.OldText) {
foreach ($ln in $block.OldText) {
$insertedLines += " // $ln"
}
}
foreach ($ln in $block.NewText) {
$insertedLines += $ln
}
$insertedLines += "$markerEnd"
Write-Host "Inserted at line: $i"
WriteOutputAndRewriteFile -filePath $filePath -insertLines $insertedLines -fromIndex $i
$foundMatch = $true
break
}
}
}