Powershell 自动提交代码到 Github

警告
本文最后更新于 2023-10-22,文中内容可能已过时。

Powershell 自动提交代码到 Github

前提

  • 本地已安装 powershell,且配置 ps1 文件可执行

  • 本地已安装 git,且你监控的文件夹已经跟远程 Github 仓库建立联系,在次目录下执行git remote -v结果不为空

当前 Powershell 版本:

当前 Git 版本:

远程仓库:

监听特定的文件夹下的文件修改,并写入日志

参考博客:Monitoring Folders for File Changes - powershell.one

以下为我的脚本,替换>>>开头的注释所在的变量,然后保存为.ps1文件即可使用

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# >>> 指定监控路径
$BlogRepoPath = 'path2monitoredFolder'

# >>> 指定日志文件夹
$basicLogPath = 'path2logFolder'

# >>> 指定监控路径对应的远程仓库名
$remoteReposity = 'remoteRepositoryName'

# specify which files you want to monitor
$FileFilter = '*'  

# specify whether you want to monitor subfolders as well:
$IncludeSubfolders = $true

# specify the file or folder properties you want to monitor:
$AttributeFilter = [IO.NotifyFilters]::FileName, [IO.NotifyFilters]::LastWrite 

try {
    $watcher = New-Object -TypeName System.IO.FileSystemWatcher -Property @{
        Path                  = $BlogRepoPath
        Filter                = $FileFilter
        IncludeSubdirectories = $IncludeSubfolders
        NotifyFilter          = $AttributeFilter
    }

    # define the code that should execute when a change occurs:
    $action = {
        # the code is receiving this to work with:

        # change type information:
        $details = $event.SourceEventArgs
        $Name = $details.Name
        $OldName = $details.OldName
        $FullPath = $details.FullPath
        $OldFullPath = $details.OldFullPath

        if ($FullPath.StartsWith("$BlogRepoPath\.git") -and -not($FullPath.Contains(".github")) ){
            <# Action to perform if the condition is true #>
            return
        }
        # node_modules/
        # if ($FullPath.StartsWith("$BlogRepoPath\node_modules")){
        #     <# Action to perform if the condition is true #>
        #     return
        # }
        # public/
        if ($FullPath.StartsWith("$BlogRepoPath\public")){
            <# Action to perform if the condition is true #>
            return
        }
        # db.json
        if ($FullPath.StartsWith("$BlogRepoPath\db.json")){
            <# Action to perform if the condition is true #>
            return
        }

        # 准备日志
        if (-not (Get-Item -Path $basicLogPath)) {
            return 
        }
        $year = (get-date).Year
        $mouth = (get-date).Month
        $day = (get-date).Day
        $filePath = "$basicLogPath\$year-$mouth"
        if (-not (Get-Item -Path $filePath)) {
            New-Item -ItemType Directory -Path $filePath 
        }
        $logFile = "$filePath\$year-$mouth-$day.log"
        if (-not (Get-Item -Path $logFile)) {
            New-Item -ItemType File -Path $logFile 
        }

        # type of change:
        $ChangeType = $details.ChangeType

        # when the change occured:
        $Timestamp = $event.TimeGenerated

        # save information to a global variable for testing purposes
        # so you can examine it later
        # MAKE SURE YOU REMOVE THIS IN PRODUCTION!
        $global:all = $details

        # now you can define some action to take based on the
        # details about the change event:

        # let's compose a message:
        $text = "{0} was {1} at {2}" -f $FullPath, $ChangeType, $Timestamp
        # Write-Host ""
        # Write-Host $text -ForegroundColor DarkYellow
        # Out-File -Append -FilePath  $logFile -InputObject ""
        Out-File -Append -FilePath  $logFile -InputObject $text

        # you can also execute code based on change type here:
        switch ($ChangeType) {
            'Changed' { 
                "CHANGE" 
                # 修改的时候提交也要等 5 秒
                Start-Sleep -Seconds 1    
            }
            'Created' { "CREATED" }
            'Deleted' {
                "DELETED"
                # to illustrate that ALL changes are picked up even if
                # handling an event takes a lot of time, we artifically
                # extend the time the handler needs whenever a file is deleted
                # Write-Host "Deletion Handler Start" -ForegroundColor Gray
                Out-File -Append -FilePath  $logFile -InputObject "Deletion Handler Start"
                Start-Sleep -Seconds 1    
                # Write-Host "Deletion Handler End" -ForegroundColor Gray
                Out-File -Append -FilePath  $logFile -InputObject "Deletion Handler End"
            }
            'Renamed' { 
                # this executes only when a file was renamed
                $text = "File {0} was renamed to {1}" -f $OldName, $Name
                # Write-Host $text -ForegroundColor Yellow
                Out-File -Append -FilePath  $logFile -InputObject $text
            }

            # any unhandled change types surface here:
            default { 
                # Write-Host $_ -ForegroundColor Red -BackgroundColor White
                Out-File -Append -FilePath  $logFile -InputObject $_
             }
        }

        #  git 先更新 再提交 
        #  日志添加时间,
        #  目录添加忽略条件。不监听 .git目录下的文件
        #  添加一个延时,不要提交太频繁
        git -C $BlogRepoPath pull $remoteReposity master
        git -C $BlogRepoPath add -A
        git -C $BlogRepoPath commit -m 'auto commit'
        git -C $BlogRepoPath push $remoteReposity
        Out-File -Append -FilePath  $logFile -InputObject "push to github $remoteReposity"

    }

    # subscribe your event handler to all event types that are
    # important to you. Do this as a scriptblock so all returned
    # event handlers can be easily stored in $handlers:
    $handlers = . {
        Register-ObjectEvent -InputObject $watcher -EventName Changed  -Action $action 
        Register-ObjectEvent -InputObject $watcher -EventName Created  -Action $action 
        Register-ObjectEvent -InputObject $watcher -EventName Deleted  -Action $action 
        Register-ObjectEvent -InputObject $watcher -EventName Renamed  -Action $action 
    }

    # monitoring starts now:
    $watcher.EnableRaisingEvents = $true

    # Write-Host "Watching for changes to $BlogRepoPath"
    # Out-File -Append -FilePath  $logFile -InputObject '------------------------------------------------------------------------------------------------------------------------'
    # Out-File -Append -FilePath  $logFile -InputObject "Watching for changes to $BlogRepoPath"


    # since the FileSystemWatcher is no longer blocking PowerShell
    # we need a way to pause PowerShell while being responsive to
    # incoming events. Use an endless loop to keep PowerShell busy:
    do {
        # Wait-Event waits for a second and stays responsive to events
        # Start-Sleep in contrast would NOT work and ignore incoming events
        Wait-Event -Timeout 60

        # write a dot to indicate we are still monitoring:
        # Write-Host "." -NoNewline
        # Out-File -Append -FilePath  $logFile -InputObject "."


    } while ($true)
}
finally {
    # this gets executed when user presses CTRL+C:

    # stop monitoring
    $watcher.EnableRaisingEvents = $false

    # remove the event handlers
    $handlers | ForEach-Object {
        Unregister-Event -SourceIdentifier $_.Name
    }

    # event handlers are technically implemented as a special kind
    # of background job, so remove the jobs now:
    $handlers | Remove-Job

    # properly dispose the FileSystemWatcher:
    $watcher.Dispose()

    # Write-Warning "Event Handler disabled, monitoring ends."
    Out-File -Append -FilePath  $logFile -InputObject "Event Handler disabled, monitoring ends."

}

对此,相比于参考博客中的源码,这里做出了几点改进:

  • 将 Write-Host 打印日志改为通过 Out-File 将日志写入文件,且日志文件按天区分,自动创建。

  • 在.git 文件夹、public 文件夹和 db.json 文件出现修改的时候不提交到 GitHub,避免出现提交循环,同时节约内存。

将此 ps1 脚本设置为开机自启

设置 powershell 脚本开机启动有点麻烦,需要在外面套一层.bat文件,同时还会有闪屏的问题,即打开一个 cmd 窗口执行完语句之后又快速关闭。为了解决这个问题,在.bat文件的外面再套一层.vbs即可解决,最终文件如下:

  1. 创建script.vbs

    1
    2
    3
    4
    
    Dim WinScriptHost
    Set WinScriptHost = CreateObject("WScript.Shell")
    WinScriptHost.Run Chr(34) & "path2batfile\script.bat" & Chr(34), 0
    Set WinScriptHost = Nothing
  2. 创建script.bat

    1
    
    pwsh.exe -file path2powershellScript\script.ps1
  3. 将上一小节的脚本保存为script.ps1,放到 path2powershellScript 指定的位置即可。

  4. script.vbs创建快捷方式,然后放到C:\Users\<user_name>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup文件夹,系统开机的时候,会自动执行此文件夹下的脚本或者软件。

0%