PowerShell 自动化实战:自动化为 Git Staged 内容添加 Issue 注释标记 (2)

【Git + PowerShell】自动为 Git stage 新增修改添加注释 marker 的完整实现解析 🎯

在团队协作开发中,我们常常希望追踪某些文件的变更来源。本文基于 PowerShell 脚本与 Git diff ,演示如何在每次提交前自动插入注释 marker。

🧩 业务需求说明

我们希望通过脚本自动完成如下几件事:

🎯 自动化目标

  1. 使用 git diff --cached 获取当前 staged 的代码变更;

  2. 提取这些变更所涉及的具体 code block(补丁区块)

  3. 为每一个 block 生成一个 marker 注释,标注此次更改类型为:

    • Addition(新增)
    • Modification(修改)
  4. 将这些 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;
+    }

✈️ 处理步骤简述:

  1. 拆分 diff 内每一段 patch(hunk)
  2. 构建两个版本:old text vs new text
  3. 判断是否修改(compare 文本内容)
  4. 若能找到旧版本匹配的行,则在此位置插入 marker
  5. 写入新内容 + 注释

🔄 核心数据流程实例

变量名 值(例子)
$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
        }
    }
}
相关推荐
胡gh1 小时前
什么是瀑布流?用大白话给你讲明白!
前端·javascript·面试
C4程序员1 小时前
北京JAVA基础面试30天打卡06
java·开发语言·面试
Mike_小新1 小时前
【Mike随想】未来更看重架构能力和业务经验,而非单纯编码能力
后端·程序员
掘金安东尼1 小时前
前端周刊第426期(2025年8月4日–8月10日)
前端·javascript·面试
Abadbeginning1 小时前
FastSoyAdmin导出excel报错‘latin-1‘ codec can‘t encode characters in position 41-54
前端·javascript·后端
很小心的小新2 小时前
五、SpringBoot工程打包与运行
java·spring boot·后端
ACGkaka_2 小时前
SpringBoot 集成 MapStruct
java·spring boot·后端
anthem372 小时前
12、Python项目实战
后端
anthem372 小时前
7、Python高级特性 - 提升代码质量与效率
后端