diff --git a/.gitignore b/.gitignore index 7691a66..2beebd2 100755 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,6 @@ __pycache__/ .Trashes ehthumbs.db Thumbs.db +desktop.ini .~lock* diff --git a/Audit-RepoHealth.ps1 b/Audit-RepoHealth.ps1 new file mode 100644 index 0000000..e7ae16a --- /dev/null +++ b/Audit-RepoHealth.ps1 @@ -0,0 +1,320 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Audits a Git repository for Windows-incompatible and system files. + +.DESCRIPTION + Scans the repo for problematic files: + - Windows system metadata (desktop.ini, Thumbs.db, etc.) + - Reserved Windows filenames (CON, NUL, PRN, AUX, LPT1-9, COM1-9) + - Untracked system files that shouldn't be committed + - Symlink anomalies + +.PARAMETER RepoPath + Path to the Git repository. Defaults to current working directory. + +.PARAMETER FixMode + If specified, automatically removes problematic files and updates .gitignore. + +.EXAMPLE + .\Audit-RepoHealth.ps1 + .\Audit-RepoHealth.ps1 -RepoPath "C:\repos\my-project" + .\Audit-RepoHealth.ps1 -RepoPath "." -FixMode + +.NOTES + Author: GitHub Copilot + Date: December 2025 +#> + +param( + [string]$RepoPath = (Get-Location).Path, + [switch]$FixMode = $false +) + +# Define problematic files and patterns +$WindowsSystemFiles = @( + "desktop.ini" + "Thumbs.db" + "ehthumbs.db" + ".DS_Store" + "._*" + ".Trashes" + ".Spotlight-V100" + "Zone.Identifier" + ".fseventsd" +) + +$ReservedNames = @( + "CON", "PRN", "AUX", "NUL" + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9" + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" +) + +function Test-IsGitRepo { + param([string]$Path) + + try { + Push-Location $Path + $null = git rev-parse --git-dir 2>$null + $result = $LASTEXITCODE -eq 0 + Pop-Location + return $result + } + catch { + return $false + } +} + +function Get-GitTrackedFiles { + param([string]$Path) + + try { + Push-Location $Path + $files = @(git ls-files 2>$null) + Pop-Location + return $files + } + catch { + return @() + } +} + +function Get-UntrackedFiles { + param([string]$Path) + + try { + Push-Location $Path + $files = @(git ls-files --others --exclude-standard 2>$null) + Pop-Location + return $files + } + catch { + return @() + } +} + +function Find-ProblematicFiles { + param( + [string]$Path, + [string[]]$Patterns, + [string]$SearchType = "untracked" # "tracked", "untracked", "all" + ) + + $problematic = @() + + if ($SearchType -in @("tracked", "all")) { + $tracked = Get-GitTrackedFiles -Path $Path + foreach ($pattern in $Patterns) { + $matching = $tracked | Where-Object { $_ -match $pattern } + $problematic += $matching + } + } + + if ($SearchType -in @("untracked", "all")) { + $untracked = Get-UntrackedFiles -Path $Path + foreach ($pattern in $Patterns) { + $matching = $untracked | Where-Object { $_ -match $pattern } + $problematic += $matching + } + } + + return $problematic | Select-Object -Unique +} + +function Test-ReservedName { + param([string]$Filename) + + $baseName = [System.IO.Path]::GetFileNameWithoutExtension($Filename) + return $baseName -in $ReservedNames +} + +function Find-ReservedNames { + param([string]$Path) + + $problematic = @() + + try { + Push-Location $Path + $allFiles = Get-ChildItem -Recurse -Force -ErrorAction SilentlyContinue | + Where-Object { -not $_.PSIsContainer } + + foreach ($file in $allFiles) { + $baseName = [System.IO.Path]::GetFileNameWithoutExtension($file.Name) + if (Test-ReservedName -Filename $file.Name) { + $problematic += $file.FullName + } + } + + Pop-Location + } + catch { + Write-Warning "Error scanning for reserved names: $_" + } + + return $problematic +} + +function Remove-ProblematicFile { + param( + [string]$FilePath, + [string]$RepoPath + ) + + try { + $fullPath = Join-Path $RepoPath $FilePath + + if (Test-Path $fullPath) { + # Try to remove from git first if tracked + Push-Location $RepoPath + $tracked = @(git ls-files) | Where-Object { $_ -eq $FilePath } + if ($tracked) { + git rm --cached $FilePath 2>$null | Out-Null + Write-Host " āœ“ Untracked from git: $FilePath" + } + Pop-Location + + # Remove local file + Remove-Item $fullPath -Force -ErrorAction SilentlyContinue + Write-Host " āœ“ Deleted: $FilePath" + return $true + } + } + catch { + Write-Warning " āœ— Failed to remove $FilePath : $_" + return $false + } + + return $true +} + +function Update-Gitignore { + param( + [string]$RepoPath, + [string[]]$Entries + ) + + $gitignorePath = Join-Path $RepoPath ".gitignore" + $existingContent = @() + + if (Test-Path $gitignorePath) { + $existingContent = @(Get-Content $gitignorePath) + } + + $toAdd = @() + foreach ($entry in $Entries) { + if ($existingContent -notcontains $entry) { + $toAdd += $entry + } + } + + if ($toAdd.Count -gt 0) { + Add-Content -Path $gitignorePath -Value "" -ErrorAction SilentlyContinue + Add-Content -Path $gitignorePath -Value "# System files (auto-added by Audit-RepoHealth.ps1)" -ErrorAction SilentlyContinue + Add-Content -Path $gitignorePath -Value $toAdd -ErrorAction SilentlyContinue + Write-Host "āœ“ Updated .gitignore with $($ toAdd.Count) entries" + return $true + } + + return $false +} + +# ===== MAIN SCRIPT ===== + +Write-Host "`nšŸ” Git Repository Health Audit`n" -ForegroundColor Cyan +Write-Host "Repository: $RepoPath`n" + +# Verify it's a git repo +if (-not (Test-IsGitRepo -Path $RepoPath)) { + Write-Host "āŒ Error: Not a valid Git repository!" -ForegroundColor Red + exit 1 +} + +# Build regex patterns +$patterns = $WindowsSystemFiles | ForEach-Object { + [regex]::Escape($_) -replace '\\\*', '.*' +} + +# ===== SCAN FOR TRACKED SYSTEM FILES ===== +Write-Host "šŸ“‹ Scanning for tracked system files..." -ForegroundColor Yellow +$trackedProblematic = Find-ProblematicFiles -Path $RepoPath -Patterns $patterns -SearchType "tracked" + +if ($trackedProblematic.Count -gt 0) { + Write-Host "`nāš ļø Found tracked system files:" -ForegroundColor Red + $trackedProblematic | ForEach-Object { Write-Host " - $_" } + + if ($FixMode) { + Write-Host "`nšŸ”§ Removing tracked system files..." -ForegroundColor Yellow + foreach ($file in $trackedProblematic) { + Remove-ProblematicFile -FilePath $file -RepoPath $RepoPath + } + } +} +else { + Write-Host "āœ“ No tracked system files found" -ForegroundColor Green +} + +# ===== SCAN FOR UNTRACKED SYSTEM FILES ===== +Write-Host "`nšŸ“‹ Scanning for untracked system files..." -ForegroundColor Yellow +$untrackedProblematic = Find-ProblematicFiles -Path $RepoPath -Patterns $patterns -SearchType "untracked" + +if ($untrackedProblematic.Count -gt 0) { + Write-Host "`nāš ļø Found untracked system files:" -ForegroundColor Yellow + $untrackedProblematic | ForEach-Object { Write-Host " - $_" } + + if ($FixMode) { + Write-Host "`nšŸ”§ Removing untracked system files..." -ForegroundColor Yellow + foreach ($file in $untrackedProblematic) { + $fullPath = Join-Path $RepoPath $file + if (Test-Path $fullPath) { + Remove-Item $fullPath -Force -ErrorAction SilentlyContinue + Write-Host " āœ“ Deleted: $file" + } + } + } +} +else { + Write-Host "āœ“ No untracked system files found" -ForegroundColor Green +} + +# ===== SCAN FOR RESERVED WINDOWS NAMES ===== +Write-Host "`nšŸ“‹ Scanning for reserved Windows filenames..." -ForegroundColor Yellow +$reserved = Find-ReservedNames -Path $RepoPath + +if ($reserved.Count -gt 0) { + Write-Host "`nāš ļø Found files with reserved Windows names:" -ForegroundColor Red + $reserved | ForEach-Object { + $relPath = $_.Replace($RepoPath, "").TrimStart("\") + Write-Host " - $relPath" + } + + if ($FixMode) { + Write-Host "`nšŸ”§ These files should be renamed manually (Windows reserved names):`n" -ForegroundColor Yellow + $reserved | ForEach-Object { + $relPath = $_.Replace($RepoPath, "").TrimStart("\") + Write-Host " TODO: Rename $relPath" + } + } +} +else { + Write-Host "āœ“ No reserved Windows filenames found" -ForegroundColor Green +} + +# ===== UPDATE .GITIGNORE ===== +if ($FixMode -and ($trackedProblematic.Count -gt 0 -or $untrackedProblematic.Count -gt 0)) { + Write-Host "`nšŸ“ Updating .gitignore..." -ForegroundColor Yellow + Update-Gitignore -RepoPath $RepoPath -Entries $WindowsSystemFiles +} + +# ===== FINAL REPORT ===== +$totalIssues = $trackedProblematic.Count + $untrackedProblematic.Count + $reserved.Count + +Write-Host "`n" + ("=" * 60) +if ($totalIssues -eq 0) { + Write-Host "āœ… Repository is clean! No issues detected." -ForegroundColor Green +} +else { + Write-Host "āš ļø Found $totalIssues issue(s). Use -FixMode to remediate." -ForegroundColor Yellow +} +Write-Host "=" * 60 + "`n" + +exit $totalIssues diff --git a/SkyboxManager.cs b/SkyboxManager.cs new file mode 100644 index 0000000..2c2ce02 --- /dev/null +++ b/SkyboxManager.cs @@ -0,0 +1,165 @@ +using UnityEngine; +using UnityEngine.Rendering; + +/// +/// SkyboxManager: Loads and manages skybox materials programmatically. +/// Attach to any GameObject in your scene to use. +/// +public class SkyboxManager : MonoBehaviour +{ + [System.Serializable] + public class SkyboxEntry + { + public string name; + public Material skyboxMaterial; + } + + [SerializeField] + private SkyboxEntry[] availableSkyboxes; + + [SerializeField] + private int defaultSkyboxIndex = 0; + + private int currentSkyboxIndex = -1; + + private void Start() + { + if (availableSkyboxes != null && availableSkyboxes.Length > 0) + { + SetSkybox(defaultSkyboxIndex); + } + else + { + Debug.LogWarning("No skyboxes assigned to SkyboxManager!"); + } + } + + /// + /// Sets the skybox by index. + /// + public void SetSkybox(int index) + { + if (availableSkyboxes == null || index < 0 || index >= availableSkyboxes.Length) + { + Debug.LogError($"Invalid skybox index: {index}"); + return; + } + + Material skyboxMaterial = availableSkyboxes[index].skyboxMaterial; + if (skyboxMaterial == null) + { + Debug.LogError($"Skybox at index {index} is null!"); + return; + } + + RenderSettings.skybox = skyboxMaterial; + DynamicGI.UpdateEnvironment(); + currentSkyboxIndex = index; + + Debug.Log($"Skybox changed to: {availableSkyboxes[index].name}"); + } + + /// + /// Sets the skybox by name. + /// + public void SetSkyboxByName(string skyboxName) + { + for (int i = 0; i < availableSkyboxes.Length; i++) + { + if (availableSkyboxes[i].name == skyboxName) + { + SetSkybox(i); + return; + } + } + + Debug.LogError($"Skybox '{skyboxName}' not found!"); + } + + /// + /// Loads a skybox material from Resources folder. + /// Place your materials in: Assets/Resources/Skyboxes/ + /// + public void LoadSkyboxFromResources(string resourcePath) + { + Material skyboxMaterial = Resources.Load(resourcePath); + if (skyboxMaterial == null) + { + Debug.LogError($"Could not load skybox from Resources: {resourcePath}"); + return; + } + + RenderSettings.skybox = skyboxMaterial; + DynamicGI.UpdateEnvironment(); + Debug.Log($"Skybox loaded from Resources: {resourcePath}"); + } + + /// + /// Cycles to the next skybox. + /// + public void NextSkybox() + { + if (availableSkyboxes == null || availableSkyboxes.Length == 0) + return; + + int nextIndex = (currentSkyboxIndex + 1) % availableSkyboxes.Length; + SetSkybox(nextIndex); + } + + /// + /// Cycles to the previous skybox. + /// + public void PreviousSkybox() + { + if (availableSkyboxes == null || availableSkyboxes.Length == 0) + return; + + int prevIndex = (currentSkyboxIndex - 1 + availableSkyboxes.Length) % availableSkyboxes.Length; + SetSkybox(prevIndex); + } + + /// + /// Gets the name of the currently active skybox. + /// + public string GetCurrentSkyboxName() + { + if (currentSkyboxIndex >= 0 && currentSkyboxIndex < availableSkyboxes.Length) + { + return availableSkyboxes[currentSkyboxIndex].name; + } + return "None"; + } + + /// + /// Gets the total number of available skyboxes. + /// + public int GetSkyboxCount() + { + return availableSkyboxes != null ? availableSkyboxes.Length : 0; + } + + /// + /// Returns the current skybox index. + /// + public int GetCurrentSkyboxIndex() + { + return currentSkyboxIndex; + } + + /// + /// Sets the skybox rotation (useful for animated or time-based rotations). + /// + public void SetSkyboxRotation(float rotationAmount) + { + if (RenderSettings.skybox != null) + { + RenderSettings.skybox.SetFloat("_Rotation", rotationAmount); + } + } + + // Call from UI button OnClick event: + public void OnCycleSkyboxButtonClicked() + { + GetComponent().NextSkybox(); + } +}