foam_sync.ps1 (LIFEBALANCE) 2025-06-18T23:55:52Z
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -21,5 +21,5 @@
|
||||
"markdown.styles": [
|
||||
".vscode/custom-tag-style.css"
|
||||
],
|
||||
"gitdoc.enabled": true
|
||||
"gitdoc.enabled": false
|
||||
}
|
||||
|
||||
236
foam_sync.ps1
Normal file
236
foam_sync.ps1
Normal file
@ -0,0 +1,236 @@
|
||||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A PowerShell script to periodically synchronize a Git repository, similar to the foam_sync.sh bash script.
|
||||
It stages all changes, commits if there are any, and then syncs with the 'origin/main' remote.
|
||||
It also attempts to set up a Windows Scheduled Task to run itself periodically.
|
||||
|
||||
.DESCRIPTION
|
||||
This script performs the following actions:
|
||||
1. Configures a Scheduled Task to run this script at a defined frequency.
|
||||
- The first time this script is run, it might require Administrator privileges to register the task.
|
||||
2. Navigates to the script's directory (assumed to be the Git repository root).
|
||||
3. Stages all changes using 'git add .'.
|
||||
4. Checks for actual staged changes using 'git diff --staged --quiet'.
|
||||
5. If changes exist, commits them with a timestamped message.
|
||||
6. If no meaningful changes were staged, resets the staging area.
|
||||
7. Updates the local knowledge of the remote 'origin'.
|
||||
8. Compares local HEAD with 'origin/main'.
|
||||
9. If they differ, it sleeps for a random interval to avoid race conditions.
|
||||
10. Determines if local is ahead, remote is ahead, or they have diverged.
|
||||
11. Performs 'git pull --rebase' or 'git push' accordingly.
|
||||
12. In case of divergence, it attempts a rebase, preferring the newer commit (based on timestamp)
|
||||
or 'ours' if timestamps are equal. Then pushes.
|
||||
|
||||
.NOTES
|
||||
Author: Gemini Code Assist (Translated from bash)
|
||||
Version: 1.0
|
||||
Prerequisites: Git must be installed and in the system PATH.
|
||||
Running for the first time: You may need to run this script as an Administrator
|
||||
to allow the Scheduled Task to be registered.
|
||||
#>
|
||||
|
||||
# --- Configuration ---
|
||||
$frequencyMinutes = 2 # How often the Scheduled Task should attempt to run this script
|
||||
|
||||
# --- Script Setup ---
|
||||
$scriptPath = $MyInvocation.MyCommand.Path
|
||||
$scriptDir = Split-Path -Path $scriptPath -Parent
|
||||
$scriptName = (Get-Item $scriptPath).Name
|
||||
Write-Host "Script: $scriptName at $scriptPath"
|
||||
Write-Host "Repository directory: $scriptDir"
|
||||
Write-Host "Sync frequency: Every $frequencyMinutes minutes"
|
||||
|
||||
# --- Scheduled Task Setup ---
|
||||
$taskName = "FoamGitSync"
|
||||
$taskDescription = "Periodically synchronizes the Git repository at $scriptDir using $scriptName."
|
||||
|
||||
# Run as the user who executes this script.
|
||||
$taskPrincipal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType InteractiveOrPassword
|
||||
|
||||
# Trigger configuration
|
||||
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $frequencyMinutes) -RepetitionDuration ([TimeSpan]::MaxValue)
|
||||
|
||||
# Action configuration: run this script
|
||||
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$scriptPath`""
|
||||
|
||||
# Task settings
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
|
||||
-StopIfGoingOnBatteries:$false # Explicitly ensure it doesn't stop
|
||||
|
||||
# Check and configure the scheduled task
|
||||
try {
|
||||
$existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
||||
|
||||
$needsUpdateOrCreation = $true
|
||||
if ($existingTask) {
|
||||
Write-Host "Scheduled task '$taskName' already exists. Checking configuration..."
|
||||
$currentTrigger = $existingTask.Triggers[0]
|
||||
$currentAction = $existingTask.Actions[0]
|
||||
|
||||
$triggerMatches = $false
|
||||
if ($currentTrigger -is [Microsoft.Management.Infrastructure.CimInstance] `
|
||||
-and $currentTrigger.RepetitionInterval.TotalMinutes -eq $frequencyMinutes) {
|
||||
$triggerMatches = $true
|
||||
}
|
||||
|
||||
$actionMatches = $false
|
||||
if ($currentAction -is [Microsoft.Management.Infrastructure.CimInstance] `
|
||||
-and $currentAction.Execute -eq "powershell.exe" `
|
||||
-and $currentAction.Argument -eq ("-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$scriptPath`"")) {
|
||||
$actionMatches = $true
|
||||
}
|
||||
|
||||
if ($triggerMatches -and $actionMatches) {
|
||||
Write-Host "Scheduled task '$taskName' is already correctly configured."
|
||||
$needsUpdateOrCreation = $false
|
||||
}
|
||||
else {
|
||||
Write-Host "Scheduled task '$taskName' configuration differs. It will be updated."
|
||||
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
if ($needsUpdateOrCreation) {
|
||||
if ($existingTask -and -not ($triggerMatches -and $actionMatches)) {
|
||||
Write-Host "Updating scheduled task '$taskName'..."
|
||||
}
|
||||
else {
|
||||
Write-Host "Creating scheduled task '$taskName'..."
|
||||
}
|
||||
Register-ScheduledTask -TaskName $taskName -Description $taskDescription -Principal $taskPrincipal -Trigger $trigger -Action $action -Settings $settings -ErrorAction Stop
|
||||
Write-Host "Scheduled task '$taskName' registered/updated successfully."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to register or update scheduled task '$taskName'. Error: $($_.Exception.Message)"
|
||||
Write-Warning "You may need to run this script as Administrator once to register the scheduled task."
|
||||
}
|
||||
|
||||
# --- Git Operations ---
|
||||
Write-Host "Navigating to repository: $scriptDir"
|
||||
try {
|
||||
Set-Location -Path $scriptDir -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
Write-Error "Unable to find repository at $scriptDir. Exiting script."
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Staging all changes with 'git add .'"
|
||||
git add .
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "'git add .' command failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
Write-Host "Checking for staged changes with 'git diff --staged --quiet'..."
|
||||
git diff --staged --quiet
|
||||
$changesStaged = ($LASTEXITCODE -ne 0)
|
||||
|
||||
if ($changesStaged) {
|
||||
Write-Host "Staged changes detected. Creating commit..."
|
||||
$commitMessage = "$scriptName ($($env:COMPUTERNAME)) $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')"
|
||||
Write-Host "Commit message: $commitMessage"
|
||||
git commit -m $commitMessage
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "'git commit' command failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
else {
|
||||
Write-Host "Commit created successfully."
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "No relevant changes detected to commit."
|
||||
Write-Host "Resetting staging area with 'git reset HEAD --quiet'."
|
||||
git reset HEAD --quiet
|
||||
}
|
||||
|
||||
Write-Host "Updating remote 'origin' with 'git remote update origin --prune'..."
|
||||
git remote update origin --prune
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Unable to update remote 'origin'. Exiting script."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$localCommit = (git rev-parse HEAD 2>$null).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or -not $localCommit) { Write-Error "Failed to get local HEAD commit. Exiting script."; exit 1 }
|
||||
|
||||
$remoteBranch = "origin/main"
|
||||
$remoteCommit = (git rev-parse $remoteBranch 2>$null).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or -not $remoteCommit) { Write-Error "Failed to get remote '$remoteBranch' commit. Exiting script."; exit 1 }
|
||||
|
||||
Write-Host "Local HEAD commit: $localCommit"
|
||||
Write-Host "Remote '$remoteBranch' commit: $remoteCommit"
|
||||
|
||||
if ($localCommit -eq $remoteCommit) {
|
||||
Write-Host "Local and remote are already in sync."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$sleepyTime = Get-Random -Minimum 1 -Maximum 15
|
||||
Write-Host "Local and remote differ. Sleeping for $sleepyTime seconds..."
|
||||
Start-Sleep -Seconds $sleepyTime
|
||||
|
||||
$localCommitAfterSleep = (git rev-parse HEAD 2>$null).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or -not $localCommitAfterSleep) { Write-Error "Failed to re-get local HEAD commit. Exiting script."; exit 1 }
|
||||
$remoteCommitAfterSleep = (git rev-parse $remoteBranch 2>$null).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or -not $remoteCommitAfterSleep) { Write-Error "Failed to re-get remote '$remoteBranch' commit. Exiting script."; exit 1 }
|
||||
|
||||
if ($localCommitAfterSleep -eq $remoteCommitAfterSleep) {
|
||||
Write-Host "Local and remote became synchronized during sleep."
|
||||
exit 0
|
||||
}
|
||||
$localCommit = $localCommitAfterSleep
|
||||
$remoteCommit = $remoteCommitAfterSleep
|
||||
|
||||
Write-Host "Proceeding with sync logic..."
|
||||
|
||||
git merge-base --is-ancestor $localCommit $remoteCommit
|
||||
$localIsAncestorOfRemote = ($LASTEXITCODE -eq 0)
|
||||
|
||||
git merge-base --is-ancestor $remoteCommit $localCommit
|
||||
$remoteIsAncestorOfLocal = ($LASTEXITCODE -eq 0)
|
||||
|
||||
if ($localIsAncestorOfRemote) {
|
||||
Write-Host "Remote '$remoteBranch' is ahead. Pulling with rebase..."
|
||||
git pull --rebase origin main
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "'git pull --rebase' failed. Manual intervention may be required."; exit 1 }
|
||||
}
|
||||
elseif ($remoteIsAncestorOfLocal) {
|
||||
Write-Host "Local HEAD is ahead. Pushing..."
|
||||
git push origin main
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "'git push' failed. Manual intervention may be required."; exit 1 }
|
||||
}
|
||||
else {
|
||||
Write-Host "Local HEAD and remote '$remoteBranch' have diverged."
|
||||
$remoteTimestampStr = (git log --pretty=format:"%at" -n 1 $remoteBranch 2>$null).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or -not $remoteTimestampStr) { Write-Error "Failed to get remote commit timestamp for '$remoteBranch'. Exiting script."; exit 1 }
|
||||
$remoteTimestamp = [long]$remoteTimestampStr
|
||||
|
||||
$localTimestampStr = (git log --pretty=format:"%at" -n 1 HEAD 2>$null).Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or -not $localTimestampStr) { Write-Error "Failed to get local commit timestamp for HEAD. Exiting script."; exit 1 }
|
||||
$localTimestamp = [long]$localTimestampStr
|
||||
|
||||
# It's good practice to check if conversion was successful, though [long] will error on failure.
|
||||
|
||||
Write-Host "Local timestamp: $localTimestamp, Remote timestamp: $remoteTimestamp"
|
||||
|
||||
if ($remoteTimestamp -gt $localTimestamp) {
|
||||
Write-Host "Remote is newer. Pulling with rebase, strategy 'theirs'..."
|
||||
git pull --rebase -X theirs origin main
|
||||
}
|
||||
else {
|
||||
Write-Host "Local is newer or same age. Pulling with rebase, strategy 'ours'..."
|
||||
git pull --rebase -X ours origin main
|
||||
}
|
||||
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "Rebase during divergence failed. Manual intervention may be required."; exit 1 }
|
||||
|
||||
Write-Host "Pushing changes after rebase..."
|
||||
git push origin main
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "'git push' after rebase failed. Manual intervention may be required."; exit 1 }
|
||||
}
|
||||
|
||||
Write-Host "Synchronization process completed successfully."
|
||||
exit 0
|
||||
Reference in New Issue
Block a user