<# .SYNOPSIS Upload the FormScout source tree to both the model repo and the Space (Windows). .DESCRIPTION PowerShell port of scripts/hf_upload.sh. `hf upload` does NOT read .hfignore — it only honors .gitignore at commit time. So we parse .hfignore ourselves into --exclude globs and pass them explicitly. If the filtered file count exceeds LargeThreshold, we fall back to `hf upload-large-folder` (resumable, multi-threaded). That mode commits directly to main in multiple commits — no --create-pr, no custom message. .PARAMETER Message Commit message. Defaults to the last git commit subject. .PARAMETER DryRun Print what would be uploaded (file count, mode, excludes) without uploading. .EXAMPLE .\scripts\hf_upload.ps1 .\scripts\hf_upload.ps1 "feat: my change" .\scripts\hf_upload.ps1 -DryRun #> param( [string]$Message, [switch]$DryRun ) $ErrorActionPreference = "Stop" # cd to repo root (parent of this script's directory) $RepoRoot = Split-Path -Parent $PSScriptRoot Set-Location $RepoRoot $ModelRepo = "silas-therapy/small-functional-movement-screening" $SpaceRepo = "spaces/silas-therapy/small-functional-movement-screening" if (-not $Message) { $Message = (git log -1 --pretty=%s).Trim() } $LargeThreshold = if ($env:FORMSCOUT_HF_LARGE_THRESHOLD) { [int]$env:FORMSCOUT_HF_LARGE_THRESHOLD } else { 500 } # Belt-and-suspenders extras on top of .hfignore. $Patterns = [System.Collections.Generic.List[string]]::new() $Patterns.Add("*.pdf") $Patterns.Add("**/node_modules/**") $Patterns.Add(".cache/**") # Parse .hfignore into fnmatch-style globs. A bare name like `.DS_Store` or # `dir/` only matches at the root, so emit both the rooted and `**/`-prefixed # forms. if (Test-Path ".hfignore") { foreach ($raw in Get-Content ".hfignore") { $line = $raw -replace '#.*$', '' $line = $line.Trim() if ([string]::IsNullOrEmpty($line)) { continue } if ($line.EndsWith("/")) { $Patterns.Add("${line}**") $Patterns.Add("**/${line}**") } else { $Patterns.Add($line) $Patterns.Add("**/$line") } } } # Build --exclude args $Excludes = [System.Collections.Generic.List[string]]::new() foreach ($p in $Patterns) { $Excludes.Add("--exclude=$p") } # Count what would actually be uploaded, using the same filter the hub client # applies, so the mode decision matches reality. $PatternArgs = $Patterns -join "`n" $NFiles = $PatternArgs | python -c @" import sys from pathlib import Path from huggingface_hub.utils import filter_repo_objects patterns = sys.stdin.read().splitlines() files = ( str(p) for p in Path('.').rglob('*') if p.is_file() and p.parts[0] != '.git' ) print(len(list(filter_repo_objects(files, ignore_patterns=patterns)))) "@ $NFiles = [int]($NFiles.Trim()) Write-Host "-- $NFiles files to upload after .hfignore filtering" if ($NFiles -eq 0) { Write-Error "nothing to upload -- check .hfignore" exit 1 } function Upload-Repo { param([string]$Repo) if ($NFiles -gt $LargeThreshold) { Write-Host "-- ${Repo}: $NFiles files > $LargeThreshold, using upload-large-folder" Write-Host " (resumable; commits directly to main -- no PR, no custom message)" if ($DryRun) { Write-Host " [dry-run] hf upload-large-folder $Repo . <$($Excludes.Count) excludes>" return } hf upload-large-folder $Repo . @Excludes } else { Write-Host "-- uploading to: $Repo" if ($DryRun) { Write-Host " [dry-run] hf upload $Repo . . <$($Excludes.Count) excludes> --create-pr --commit-message=`"$Message`"" return } hf upload $Repo . . @Excludes --create-pr --commit-message="$Message" } if ($LASTEXITCODE -ne 0) { Write-Error "upload to $Repo failed (exit $LASTEXITCODE)" exit $LASTEXITCODE } } if ($DryRun) { Write-Host "" Write-Host "=== DRY RUN ===" Write-Host "Commit message : $Message" Write-Host "Large threshold: $LargeThreshold" Write-Host "Mode : $(if ($NFiles -gt $LargeThreshold) { 'upload-large-folder' } else { 'upload + create-pr' })" Write-Host "Exclude globs : $($Excludes.Count)" foreach ($e in $Excludes) { Write-Host " $e" } Write-Host "" } Upload-Repo $ModelRepo Upload-Repo $SpaceRepo if ($DryRun) { Write-Host "OK -- dry run complete (nothing uploaded)" } else { Write-Host "OK -- done" }