【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
        }
    }
}