Skip to content

Instantly share code, notes, and snippets.

@Kenya-West
Last active May 30, 2026 11:16
Show Gist options
  • Select an option

  • Save Kenya-West/0cc008b3d6d8baf68361fbbe28effa06 to your computer and use it in GitHub Desktop.

Select an option

Save Kenya-West/0cc008b3d6d8baf68361fbbe28effa06 to your computer and use it in GitHub Desktop.
PowerShell utility that copies **Git dirty files** (modified, staged, and untracked files) from a single repository or from multiple repositories discovered at a specific directory depth

Copy-GitDirtyFilesBulk.ps1

PowerShell utility that copies Git dirty files (modified, staged, and untracked files) from a single repository or from multiple repositories discovered at a specific directory depth.

This is useful when you want to:

  • Back up work-in-progress changes.
  • Collect uncommitted changes before risky operations.
  • Transfer local modifications between machines.
  • Snapshot dirty files across many repositories.
  • Avoid copying files above a specific size.

Features

Single Repository Mode

Copies:

  • Modified tracked files (unstaged)
  • Modified tracked files (staged)
  • Untracked files
  • Git-ignored dotfiles such as .env and .env.development

Optional:

  • Other ignored files (-IncludeIgnored)
  • File size limit in bytes (-MaxFileSizeBytes)

Bulk Repository Mode

Searches for Git repositories at an exact folder depth and copies dirty files from every repository found.

Example:

Z:\Dev
├─ TeamA
│  ├─ Repo1
│  └─ Repo2
├─ TeamB
│  ├─ Repo3
│  └─ Repo4

Using:

-From Z:\Dev -Depth 2

will process:

Z:\Dev\TeamA\Repo1
Z:\Dev\TeamA\Repo2
Z:\Dev\TeamB\Repo3
Z:\Dev\TeamB\Repo4

What Gets Copied

The script collects:

Modified Files

git diff --name-only

Staged Files

git diff --cached --name-only

Untracked Files

git ls-files -o --exclude-standard

Ignored Files (Optional)

git ls-files -o -i --exclude-standard

when -IncludeIgnored is specified.

Ignored dotfiles such as .env, .env.development, and nested .toolrc files are copied by default when they are present on disk. This covers common local configuration files without also copying large ignored directories.


Built-In Exclusions

To avoid copying large dependency, cache, and build directories, the script automatically excludes common folders such as:

node_modules
dist
build
vendor
bin
obj
target
__pycache__
.venv
venv
.nuxt
.next
.parcel-cache
.gradle
Pods
DerivedData
.git
.svn
.hg

Exclusions are path-segment based, so nested occurrences are also skipped.


File Size Limit

Use -MaxFileSizeBytes to skip files larger than a specific byte size.

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "C:\src\myrepo" `
  -To "D:\backup\myrepo-dirty" `
  -MaxFileSizeBytes 1048576

The limit is inclusive, so -MaxFileSizeBytes 1048576 copies files up to and including 1,048,576 bytes. Omit the parameter, or set it to -1, to copy files without a size limit.


Requirements

  • PowerShell 5.1+ or PowerShell 7+
  • Git installed and available in PATH

Verify Git:

git --version

Usage

Single Repository Mode

Copy dirty files from one repository:

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "C:\src\myrepo" `
  -To "D:\backup\myrepo-dirty"

Include ignored files:

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "C:\src\myrepo" `
  -To "D:\backup\myrepo-dirty" `
  -IncludeIgnored

Bulk Repository Mode

Search repositories at an exact depth and copy dirty files from all of them.

Depth 1

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "Z:\Dev" `
  -To "D:\backup\Dev-dirty" `
  -Depth 1

Searches:

Z:\Dev\*

Depth 2

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "Z:\Dev" `
  -To "D:\backup\Dev-dirty" `
  -Depth 2

Searches:

Z:\Dev\*\*

Depth 3

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "Z:\Dev" `
  -To "D:\backup\Dev-dirty" `
  -Depth 3

Searches:

Z:\Dev\*\*\*

Bulk Mode with Ignored Files

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "Z:\Dev" `
  -To "D:\backup\Dev-dirty" `
  -Depth 2 `
  -IncludeIgnored

Destination Structure

Suppose:

Source Root:
Z:\Dev

Repository:
Z:\Dev\TeamA\Repo1

Destination:

D:\backup\Dev-dirty

Output:

D:\backup\Dev-dirty\TeamA\Repo1\

The repository hierarchy relative to the search root is preserved.


Parameters

Parameter Description
-From Source repository or search root
-To Destination folder
-Depth Enables bulk mode and searches repositories exactly at this depth
-IncludeIgnored Also copies ignored files beyond the ignored dotfiles included by default
-MaxFileSizeBytes Skips files larger than this byte limit; defaults to -1 for unlimited

Examples

Backup Current Work

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "C:\Projects\Api" `
  -To "E:\Backups\Api-WIP"

Collect Changes Across All Team Repositories

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "D:\Repos" `
  -To "E:\DirtyRepos" `
  -Depth 2

Include Ignored Build Outputs

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "D:\Repos" `
  -To "E:\DirtyRepos" `
  -Depth 2 `
  -IncludeIgnored

Skip Files Larger Than 10 MB

.\Copy-GitDirtyFilesBulk.ps1 `
  -From "D:\Repos" `
  -To "E:\DirtyRepos" `
  -Depth 2 `
  -MaxFileSizeBytes 10485760

Output Summary

Bulk mode produces a summary similar to:

Bulk copy summary
-----------------
Repositories found:     12
Repositories processed: 12
Repositories failed:    0
Dirty paths detected:   84
Files copied:           67
Skipped by ignore list: 14
Skipped by size limit:  2
Missing or non-file:    3
Done.

Notes

  • The script only copies files currently present on disk.
  • Deleted files are reported as missing and are not copied.
  • Duplicate dirty paths are automatically de-duplicated.
  • Repository discovery in bulk mode only accepts repositories whose Git top-level directory exactly matches the discovered folder.
  • Large dependency and cache folders are intentionally excluded to keep backups compact and fast.

License

MIT License.

<#
.SYNOPSIS
Copy Git-untracked or Git-modified files from one Git repository,
or from many Git repositories found at an exact folder depth.
.DESCRIPTION
- Copies files that are modified: staged or unstaged.
- Copies untracked files.
- Copies Git-ignored dotfiles, such as .env and .env.development.
- Optionally includes ignored files via -IncludeIgnored.
- Optionally skips files larger than -MaxFileSizeBytes.
- Skips common cache/build/package directories:
node_modules, bin, obj, __pycache__, vendor, dist, build, etc.
- Original single-repo mode is preserved when -Depth is not provided.
- Bulk mode is enabled when -Depth is provided.
- In bulk mode, repositories are searched exactly at the given depth below -From.
.EXAMPLE
.\Copy-GitDirtyFiles.ps1 -From "C:\src\myrepo" -To "D:\backup\myrepo-dirty"
.EXAMPLE
.\Copy-GitDirtyFiles.ps1 -From "C:\src\myrepo" -To "D:\backup\myrepo-dirty" -IncludeIgnored
.EXAMPLE
.\Copy-GitDirtyFiles.ps1 -From "Z:\Dev" -To "D:\backup\Dev-dirty" -Depth 2
Searches exactly two directory levels below Z:\Dev and copies dirty files
from every Git repository found there.
.EXAMPLE
.\Copy-GitDirtyFiles.ps1 -From "Z:\Dev" -To "D:\backup\Dev-dirty" -Depth 2 -IncludeIgnored
.EXAMPLE
.\Copy-GitDirtyFiles.ps1 -From "C:\src\myrepo" -To "D:\backup\myrepo-dirty" -MaxFileSizeBytes 1048576
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string] $From,
[Parameter(Mandatory = $false)]
[string] $To,
[Parameter(Mandatory = $false)]
[switch] $IncludeIgnored,
# Optional.
# If omitted, script behaves like the original single-repository script.
# If provided, script searches for Git repositories exactly this many
# directory levels below -From.
#
# Example:
# -From Z:\Dev -Depth 1 => Z:\Dev\*
# -From Z:\Dev -Depth 2 => Z:\Dev\*\*
# -From Z:\Dev -Depth 3 => Z:\Dev\*\*\*
[Parameter(Mandatory = $false)]
[int] $Depth = -1,
# Optional.
# If set to 0 or greater, files larger than this many bytes are skipped.
# If omitted or set to -1, no file-size limit is applied.
[Parameter(Mandatory = $false)]
[long] $MaxFileSizeBytes = -1
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
if ($MaxFileSizeBytes -lt -1) {
throw "MaxFileSizeBytes cannot be less than -1. Use -1 for unlimited, or a non-negative byte limit."
}
function Resolve-AbsolutePath {
param([Parameter(Mandatory)] [string] $Path)
$expanded = [Environment]::ExpandEnvironmentVariables($Path)
$resolved = Resolve-Path -LiteralPath $expanded -ErrorAction Stop
return $resolved.Path
}
function Ensure-Directory {
param([Parameter(Mandatory)] [string] $Dir)
if (-not (Test-Path -LiteralPath $Dir -PathType Container)) {
New-Item -ItemType Directory -Path $Dir -Force | Out-Null
}
}
function Assert-GitAvailable {
$git = Get-Command git -ErrorAction SilentlyContinue
if (-not $git) {
throw "git executable not found in PATH."
}
}
function Test-GitRepo {
param([Parameter(Mandatory)] [string] $RepoRoot)
$isRepo = & git -C $RepoRoot rev-parse --is-inside-work-tree 2>$null
return ($LASTEXITCODE -eq 0 -and $isRepo -eq "true")
}
function Assert-GitRepo {
param([Parameter(Mandatory)] [string] $RepoRoot)
Assert-GitAvailable
if (-not (Test-GitRepo -RepoRoot $RepoRoot)) {
throw "No Git repository found at '$RepoRoot' or inside it. Initialize Git first or choose another source folder."
}
}
function Get-GitTopLevel {
param([Parameter(Mandatory)] [string] $RepoAnyPath)
$top = & git -C $RepoAnyPath rev-parse --show-toplevel 2>$null
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($top)) {
throw "Failed to resolve Git top-level directory for '$RepoAnyPath'."
}
return $top.Trim()
}
# Common folders that should never be copied.
# Match is path-segment based, so it catches nested occurrences.
$IgnoredDirNames = @(
# AWS
".aws-sam", ".serverless",
# Node / JS
"node_modules", "bower_components", ".yarn", ".pnp", ".pnpm-store", ".npm", ".turbo", ".next", ".nuxt", ".output",
"dist", "build", ".cache", "cache", ".parcel-cache", ".vite", ".svelte-kit", ".angular", "out"
# Python
"__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache", ".tox", ".nox",
".venv", "venv", "env", "site-packages", ".eggs", "egg-info",
# .NET
"bin", "obj", ".vs", "TestResults", "packages", ".nuget",
# Java / JVM
"target", ".gradle", "build", ".idea", ".classpath", ".project", ".settings",
# Go
"vendor",
# Rust
"target",
# PHP / Composer
"vendor",
# Ruby
"vendor", ".bundle",
# iOS / macOS
"Pods", "DerivedData",
# Misc VCS / tooling caches
".git", ".svn", ".hg",
# Visual Studio
".vsdbg", ".vs", "TestResults"
) | Sort-Object -Unique
function Should-IgnorePath {
param([Parameter(Mandatory)] [string] $RelativePath)
$p = $RelativePath.Replace('\', '/')
while ($p.StartsWith('./', [System.StringComparison]::Ordinal)) {
$p = $p.Substring(2)
}
$p = $p.TrimStart('/')
$segments = $p.Split('/', [System.StringSplitOptions]::RemoveEmptyEntries)
foreach ($seg in $segments) {
if ($IgnoredDirNames -contains $seg) {
return $true
}
}
return $false
}
function Get-RepoDotFilePaths {
param([Parameter(Mandatory)] [string] $RepoRoot)
$results = New-Object System.Collections.Generic.List[string]
$stack = New-Object System.Collections.Generic.Stack[System.IO.DirectoryInfo]
$stack.Push((Get-Item -LiteralPath $RepoRoot))
while ($stack.Count -gt 0) {
$dir = $stack.Pop()
$children = @(Get-ChildItem -LiteralPath $dir.FullName -Force -ErrorAction SilentlyContinue)
foreach ($child in $children) {
$relative = (Get-RelativePath -BasePath $RepoRoot -ChildPath $child.FullName).Replace('\', '/')
if ($child.PSIsContainer) {
if (-not (Should-IgnorePath -RelativePath $relative)) {
$stack.Push($child)
}
continue
}
if ($child.Name.StartsWith(".", [System.StringComparison]::Ordinal) -and
-not (Should-IgnorePath -RelativePath $relative)) {
$results.Add($relative)
}
}
}
return @($results)
}
function Get-IgnoredDotFilePaths {
param([Parameter(Mandatory)] [string] $RepoRoot)
$dotFiles = @(Get-RepoDotFilePaths -RepoRoot $RepoRoot)
if ($dotFiles.Count -eq 0) {
return @()
}
$ignoredDotFiles = New-Object System.Collections.Generic.List[string]
$batchSize = 100
for ($i = 0; $i -lt $dotFiles.Count; $i += $batchSize) {
$last = [Math]::Min($i + $batchSize - 1, $dotFiles.Count - 1)
$batch = @($dotFiles[$i..$last])
$batchIgnoredDotFiles = @(& git -C $RepoRoot check-ignore -- $batch 2>$null)
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 1) {
throw "Failed in '$RepoRoot': git check-ignore"
}
foreach ($p in $batchIgnoredDotFiles) {
if (-not [string]::IsNullOrWhiteSpace($p)) {
$ignoredDotFiles.Add($p.Trim())
}
}
}
return @($ignoredDotFiles)
}
function Get-DirtyPaths {
param(
[Parameter(Mandatory)] [string] $RepoRoot,
[Parameter(Mandatory)] [bool] $IncludeIgnoredFiles
)
$set = New-Object System.Collections.Generic.HashSet[string]
# 1) Modified tracked files: unstaged
$modifiedUnstaged = & git -C $RepoRoot diff --name-only 2>$null
if ($LASTEXITCODE -ne 0) {
throw "Failed in '$RepoRoot': git diff --name-only"
}
foreach ($p in $modifiedUnstaged) {
if (-not [string]::IsNullOrWhiteSpace($p)) {
[void] $set.Add($p.Trim())
}
}
# 2) Modified tracked files: staged
$modifiedStaged = & git -C $RepoRoot diff --cached --name-only 2>$null
if ($LASTEXITCODE -ne 0) {
throw "Failed in '$RepoRoot': git diff --cached --name-only"
}
foreach ($p in $modifiedStaged) {
if (-not [string]::IsNullOrWhiteSpace($p)) {
[void] $set.Add($p.Trim())
}
}
# 3) Untracked, but not ignored
$untracked = & git -C $RepoRoot ls-files -o --exclude-standard 2>$null
if ($LASTEXITCODE -ne 0) {
throw "Failed in '$RepoRoot': git ls-files -o --exclude-standard"
}
foreach ($p in $untracked) {
if (-not [string]::IsNullOrWhiteSpace($p)) {
[void] $set.Add($p.Trim())
}
}
# 4) Ignored dotfiles, such as .env and .env.development.
$ignoredDotFiles = @(Get-IgnoredDotFilePaths -RepoRoot $RepoRoot)
foreach ($p in $ignoredDotFiles) {
if (-not [string]::IsNullOrWhiteSpace($p)) {
[void] $set.Add($p.Trim())
}
}
# 5) Ignored files, if requested
if ($IncludeIgnoredFiles) {
$ignored = & git -C $RepoRoot ls-files -o -i --exclude-standard 2>$null
if ($LASTEXITCODE -ne 0) {
throw "Failed in '$RepoRoot': git ls-files -o -i --exclude-standard"
}
foreach ($p in $ignored) {
if (-not [string]::IsNullOrWhiteSpace($p)) {
[void] $set.Add($p.Trim())
}
}
}
return @($set)
}
function Copy-DirtyFiles {
param(
[Parameter(Mandatory)] [string] $RepoRoot,
[Parameter(Mandatory)] [string] $DestinationRoot,
[Parameter(Mandatory)] [bool] $IncludeIgnoredFiles,
[Parameter(Mandatory)] [long] $MaxFileSizeBytes
)
$dirty = @(Get-DirtyPaths -RepoRoot $RepoRoot -IncludeIgnoredFiles $IncludeIgnoredFiles)
if ($dirty.Count -eq 0) {
if ($IncludeIgnoredFiles) {
Write-Host "No modified, untracked, or ignored files found. Nothing to copy."
} else {
Write-Host "No modified, untracked, or ignored dotfiles found. Other ignored files are hidden unless -IncludeIgnored is set."
}
return @{
Copied = 0
Skipped = 0
SkippedSize = 0
Missing = 0
Dirty = 0
}
}
$copied = 0
$skipped = 0
$skippedSize = 0
$missing = 0
foreach ($rel in $dirty) {
if (Should-IgnorePath -RelativePath $rel) {
$skipped++
continue
}
$src = Join-Path -Path $RepoRoot -ChildPath $rel
$srcItem = Get-Item -LiteralPath $src -ErrorAction SilentlyContinue
if (-not $srcItem -or $srcItem.PSIsContainer) {
$missing++
continue
}
if ($MaxFileSizeBytes -ge 0 -and $srcItem.Length -gt $MaxFileSizeBytes) {
$skippedSize++
continue
}
$dst = Join-Path -Path $DestinationRoot -ChildPath $rel
$dstDir = Split-Path -Path $dst -Parent
Ensure-Directory -Dir $dstDir
Copy-Item -LiteralPath $srcItem.FullName -Destination $dst -Force
$copied++
}
Write-Host "Copied: $copied"
Write-Host "Skipped by ignored dirs list: $skipped"
Write-Host "Skipped by file size limit: $skippedSize"
Write-Host "Missing or non-file: $missing"
Write-Host "Done."
return @{
Copied = $copied
Skipped = $skipped
SkippedSize = $skippedSize
Missing = $missing
Dirty = $dirty.Count
}
}
function Get-RelativePath {
param(
[Parameter(Mandatory)] [string] $BasePath,
[Parameter(Mandatory)] [string] $ChildPath
)
$baseFull = [System.IO.Path]::GetFullPath($BasePath)
$childFull = [System.IO.Path]::GetFullPath($ChildPath)
if (-not $baseFull.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
$baseFull += [System.IO.Path]::DirectorySeparatorChar
}
$baseUri = [System.Uri]::new($baseFull)
$childUri = [System.Uri]::new($childFull)
$relativeUri = $baseUri.MakeRelativeUri($childUri)
$relativePath = [System.Uri]::UnescapeDataString($relativeUri.ToString())
return ($relativePath -replace '/', [System.IO.Path]::DirectorySeparatorChar)
}
function Get-DirectoriesAtExactDepth {
param(
[Parameter(Mandatory)] [string] $Root,
[Parameter(Mandatory)] [int] $ExactDepth
)
if ($ExactDepth -lt 0) {
throw "Depth cannot be negative."
}
if ($ExactDepth -eq 0) {
return @((Get-Item -LiteralPath $Root))
}
$currentLevel = @((Get-Item -LiteralPath $Root))
for ($level = 1; $level -le $ExactDepth; $level++) {
$nextLevel = @()
foreach ($dir in $currentLevel) {
# Do not descend into already detected Git working trees.
# This keeps the search focused on project folders and avoids scanning
# huge dependency/cache trees inside repositories.
if ($level -gt 1 -and (Test-Path -LiteralPath (Join-Path $dir.FullName ".git"))) {
continue
}
$children = Get-ChildItem -LiteralPath $dir.FullName -Directory -Force -ErrorAction SilentlyContinue |
Where-Object {
$IgnoredDirNames -notcontains $_.Name
}
foreach ($child in $children) {
$nextLevel += $child
}
}
$currentLevel = $nextLevel
}
return @($currentLevel)
}
function Find-GitReposAtExactDepth {
param(
[Parameter(Mandatory)] [string] $Root,
[Parameter(Mandatory)] [int] $ExactDepth
)
Assert-GitAvailable
$candidateDirs = @(Get-DirectoriesAtExactDepth -Root $Root -ExactDepth $ExactDepth)
$repos = New-Object System.Collections.Generic.HashSet[string]
foreach ($dir in $candidateDirs) {
if (-not (Test-GitRepo -RepoRoot $dir.FullName)) {
continue
}
$top = Get-GitTopLevel -RepoAnyPath $dir.FullName
# Important:
# Only accept repositories whose top-level is exactly the candidate path.
# This prevents a child folder inside a repository from being counted as
# the repository when searching a deeper level.
$candidateFull = [System.IO.Path]::GetFullPath($dir.FullName).TrimEnd('\', '/')
$topFull = [System.IO.Path]::GetFullPath($top).TrimEnd('\', '/')
if ([string]::Equals($candidateFull, $topFull, [System.StringComparison]::OrdinalIgnoreCase)) {
[void] $repos.Add($topFull)
}
}
return @($repos)
}
# --- Main flow ---
if ([string]::IsNullOrWhiteSpace($From)) {
$From = Read-Host "Source folder"
}
if ([string]::IsNullOrWhiteSpace($To)) {
$To = Read-Host "Destination folder"
}
$FromAbs = Resolve-AbsolutePath -Path $From
$ToExpanded = [Environment]::ExpandEnvironmentVariables($To)
if (-not (Test-Path -LiteralPath $ToExpanded)) {
Ensure-Directory -Dir $ToExpanded
}
$ToAbs = Resolve-AbsolutePath -Path $ToExpanded
# Single-repository mode: original behavior.
if ($Depth -lt 0) {
Assert-GitRepo -RepoRoot $FromAbs
$repoTop = Get-GitTopLevel -RepoAnyPath $FromAbs
Write-Host "Mode: Single repository"
Write-Host "Git repo top-level: $repoTop"
Write-Host "Copy destination: $ToAbs"
Write-Host "Ignored dotfiles: Included"
Write-Host ("Other ignored files: " + $(if ($IncludeIgnored.IsPresent) { "Included" } else { "Hidden" }))
Write-Host ("File size limit: " + $(if ($MaxFileSizeBytes -ge 0) { "$MaxFileSizeBytes bytes" } else { "Unlimited" }))
Copy-DirtyFiles `
-RepoRoot $repoTop `
-DestinationRoot $ToAbs `
-IncludeIgnoredFiles $IncludeIgnored.IsPresent `
-MaxFileSizeBytes $MaxFileSizeBytes | Out-Null
exit 0
}
# Bulk mode.
Write-Host "Mode: Bulk repositories"
Write-Host "Search root: $FromAbs"
Write-Host "Search depth: $Depth"
Write-Host "Copy destination: $ToAbs"
Write-Host "Ignored dotfiles: Included"
Write-Host ("Other ignored files: " + $(if ($IncludeIgnored.IsPresent) { "Included" } else { "Hidden" }))
Write-Host ("File size limit: " + $(if ($MaxFileSizeBytes -ge 0) { "$MaxFileSizeBytes bytes" } else { "Unlimited" }))
Write-Host ""
$repos = @(Find-GitReposAtExactDepth -Root $FromAbs -ExactDepth $Depth | Sort-Object)
if ($repos.Count -eq 0) {
Write-Host "No Git repositories found exactly at depth $Depth below '$FromAbs'."
exit 0
}
Write-Host "Git repositories found: $($repos.Count)"
Write-Host ""
$totalCopied = 0
$totalSkipped = 0
$totalSkippedSize = 0
$totalMissing = 0
$totalDirty = 0
$processed = 0
$failed = 0
foreach ($repo in $repos) {
$processed++
$relativeRepoPath = Get-RelativePath -BasePath $FromAbs -ChildPath $repo
$repoDestination = Join-Path -Path $ToAbs -ChildPath $relativeRepoPath
Write-Host "[$processed/$($repos.Count)] Repository: $repo"
Write-Host "Destination: $repoDestination"
try {
Ensure-Directory -Dir $repoDestination
$result = Copy-DirtyFiles `
-RepoRoot $repo `
-DestinationRoot $repoDestination `
-IncludeIgnoredFiles $IncludeIgnored.IsPresent `
-MaxFileSizeBytes $MaxFileSizeBytes
$totalCopied += [int] $result.Copied
$totalSkipped += [int] $result.Skipped
$totalSkippedSize += [int] $result.SkippedSize
$totalMissing += [int] $result.Missing
$totalDirty += [int] $result.Dirty
}
catch {
$failed++
Write-Warning "Failed to process repository '$repo': $($_.Exception.Message)"
}
Write-Host ""
}
Write-Host "Bulk copy summary"
Write-Host "-----------------"
Write-Host "Repositories found: $($repos.Count)"
Write-Host "Repositories processed: $processed"
Write-Host "Repositories failed: $failed"
Write-Host "Dirty paths detected: $totalDirty"
Write-Host "Files copied: $totalCopied"
Write-Host "Skipped by ignore list: $totalSkipped"
Write-Host "Skipped by size limit: $totalSkippedSize"
Write-Host "Missing or non-file: $totalMissing"
Write-Host "Done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment