Skip to content

Instantly share code, notes, and snippets.

@perXautomatik
Forked from mhagger/README.md
Last active August 7, 2023 10:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save perXautomatik/42bb0a6d1bdd0adf75ad5638905268fd to your computer and use it in GitHub Desktop.
Save perXautomatik/42bb0a6d1bdd0adf75ad5638905268fd to your computer and use it in GitHub Desktop.
Tools for repository repair

Ideas for git-fix project

We would like to have a way to fix up repositories' history; e.g., to remove corruption that may have happened some time in the past.

The standard tool for mass-rewriting of Git history is git filter-branch. But it is awkward to use and has a number of limitations:

  • It is a shell script, and correspondingly slow
  • It cannot deal with some kinds of corruption, because it tries to check out all historical revisions into the index and/or working tree
  • It can make "grafts" and "replace references" permanent, but only at the commit level. It cannot make "replace references" for trees or blobs permanent.

Fixing repository corruption has three steps: detection and characterization of the corruption, creation of replacement objects, and history rewriting to make the repairs permanent. Here is some brainstorming about how each of these stages could be improved:

  1. Detection of problems
    • Improve git fsck output (or maybe build this into another program, like git rev-list --objects).
      • Support a traversal-based check, whose range can be defined (ideally, also at object granularity).
      • Display the paths that led to broken objects.
    • Add new validity tests (to the extent that they don't already exist):
      • Prohibit empty filenames in trees.
      • Require filenames in trees to be distinct and sorted correctly.
      • Optionally require tree filenames to be unique even modulo Unicode normalization.
      • Optionally require tree filenames to be valid UTF-8 and normalized in form NFC.
    • git fsck --interactive: allow objects to be fixed while git fsck is running, to avoid having to restart all of the time (see below).
  2. Creation of replacement objects
    • Make git hash-object stricter by default. It should run all of the object tests that git fsck would usually run. Add a --no-strict mode for backwards compatibility.
    • Add git hash-object --overwrite command, which writes a new loose object regardless of whether an object with that name already exists. (This could be used for replacing corrupt objects.)
    • Add git hash-object --batch, for efficiency. Its input should be similar to the output of git cat-file --batch.
    • Use git ls-tree -z and git mktree -z for trees instead of git cat-file and git hash-object.
    • Add git ls-tree --batch, for efficiency.
  3. git-fix: Rewrite history, incorporating replace objects
    • Allow fixing all of the history reachable from a single tip
    • Allow references to be fixed
    • Handle fixing of only part of the history (e.g., via <rev-list options> like those of git filter-branch).
    • Handle tags. Replace signed tags with unsigned tags where necessary.

git fsck --interactive

Another idea was to implement git fsck --interactive, in which every time it found a problem, it would report the bad object in some machine-readable format, and wait for commands on stdin. The commands could be things like

replace SP <SHA-1> LF

to use as a replacement for the bad object (fsck would create a replace record but not do healing up the tree).

add SP <type> SP <size> LF
<contents> LF

to add a new object to the DB.

replace SP <type> SP <size> LF
<contents> LF

to add a new object to the DB and use it as a replacement for the bad object.

continue LF

to just go on without repairing the broken object.

The point of all this would be to avoid the "fsck; fix reported problem; repeat" loop that is so expensive when there is a chain of bad objects.

begin
{
Push-Location
# Validate the arguments
if (-not (Test-Path -LiteralPath $modules)) {
Write-Error "Invalid modules path: $modules"
exit 1
}
if (-not (Test-Path -LiteralPath $folder)) {
Write-Error "Invalid folder path: $folder"
exit 1
}
# Redirect the standard error output of git commands to the standard output stream
$env:GIT_REDIRECT_STDERR = '2>&1'
# Initialize a counter variable
$i = 0;
# Define parameters for Write-Progress cmdlet
$progressParams = @{
Activity = "Processing files"
Status = "Starting"
PercentComplete = 0
}
}
process {
# Get all the subdirectories in $folder, excluding any .git files or folders
$subdirs = Get-ChildItem -LiteralPath $folder -Directory -Recurse -Filter "*"
# Loop through each subdirectory
foreach ($subdir in $subdirs) {
# Increment the counter
$i++;
# Change the current directory to the subdirectory
Set-Location $subdir.FullName
# Run git status and capture the output
$output = git status
# Check if the output is fatal
if($output -like "fatal*")
{
# Print a message indicating fatal status
Write-Output "fatal status for $subdir"
# Get the .git file or folder in that subdirectory
$gitFile = Get-ChildItem -LiteralPath "$subdir\*" -Force | Where-Object { $_.Name -eq ".git" }
# Check if it is a file or a folder
if( $gitFile -is [System.IO.FileInfo] )
{
# Define parameters for Move-Item cmdlet
$moveParams = @{
Path = Join-Path -Path $modules -ChildPath $gitFile.Directory.Name
Destination = $gitFile
Force = $true
PassThru = $true
}
# Move the module folder to replace the .git file and return the moved item
$movedItem = Move-Item @moveParams
# Print a message indicating successful move
Write-Output "moved $($movedItem.Name) to $($movedItem.DirectoryName)"
}
elseif( $gitFile -is [System.IO.DirectoryInfo] )
{
# Get the path to the git config file
$configFile = Join-Path -Path $gitFile -ChildPath "\config"
# Check if it exists
if (-not (Test-Path -LiteralPath $configFile)) {
Write-Error "Invalid folder path: $gitFile"
}
else
{
# Read the config file content as a single string
$configContent = Get-Content -LiteralPath $configFile -Raw
# Remove any line that contains worktree and store the new content in a variable
$newConfigContent = $configContent -Replace "(?m)^.*worktree.*$\r?\n?"
# Check if there are any lines to remove
if ($configContent -ne $newConfigContent)
{
# Write the new config file content
$newConfigContent | Set-Content -LiteralPath $configFile -Force
# Print a message indicating successful removal
Write-Output "removed worktree from $configFile"
}
}
}
else
{
# Print an error message if it is not a file or a folder
Write-Error "not a .git file or folder: $gitFile"
}
}
# Calculate the percentage of directories processed
$percentComplete = ($i / ($subdirs.count+$i) ) * 100
# Update the progress bar
$progressParams.PercentComplete = $percentComplete
Write-Progress @progressParams
}
}
end {
# Restore the original location
Pop-Location
# Complete the progress bar
$progressParams.Status = "Finished"
Write-Progress @progressParams
}
# A function to validate the arguments
function Validate-Arguments ($modules, $folder) {
if (-not (Test-Path $modules)) {
Write-Error "Invalid modules path: $modules"
exit 1
}
if (-not (Test-Path $folder)) {
Write-Error "Invalid folder path: $folder"
exit 1
}
}
# A function to check the git status of a folder
function Check-GitStatus ($folder) {
# Change the current directory to the folder
Set-Location $folder.FullName
Write-Output "checking $folder"
if ((Get-ChildItem -force | ?{ $_.name -eq ".git" } ))
{
# Run git status and capture the output
$output = git status
if(($output -like "fatal*"))
{
Write-Output "fatal status for $folder"
Repair-GitFolder $folder
}
else
{
Write-Output @($output)[0]
}
}
else
{
Write-Output "$folder not yet initialized"
}
}
# A function to repair a corrupted git folder
function Repair-GitFolder ($folder) {
$folder | Get-ChildItem -force | ?{ $_.name -eq ".git" } | % {
$toRepair = $_
if( $toRepair -is [System.IO.FileInfo] )
{
Move-GitFile $toRepair
}
elseif( $toRepair -is [System.IO.DirectoryInfo] )
{
Fix-GitConfig $toRepair
}
else
{
Write-Error "not a .git file or folder: $toRepair"
}
}
}
# A function to move a .git file to the corresponding module folder
function Move-GitFile ($file) {
global:$modules | Get-ChildItem -Directory | ?{ $_.name -eq $file.Directory.Name } | select -First 1 | % {
# Move the folder to the target folder
rm $file -force ; Move-Item -Path $_.fullname -Destination $file -force
}
}
# A function to fix the worktree setting in a .git config file
function Fix-GitConfig ($folder) {
# Get the path to the git config file
$configFile = Join-Path -Path $folder -ChildPath "\config"
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $folder"
}
else
{
# Read the config file content as an array of lines
$configLines = Get-Content -Path $configFile
# Filter out the lines that contain worktree
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
if (($configLines | Where-Object { $_ -match "worktree" }))
{
# Write the new config file content
Set-Content -Path $configFile -Value $newConfigLines -Force
}
}
}
# The main function that calls the other functions
function fix-CorruptedGitModules ($folder = "C:\ProgramData\scoop\persist", global:$modules = "C:\ProgramData\scoop\persist\.git\modules")
{
begin {
Push-Location
# Validate the arguments
Validate-Arguments $modules $folder
# Set the environment variable for git error redirection
$env:GIT_REDIRECT_STDERR = '2>&1'
}
process {
# Get the list of folders in $folder
$folders = Get-ChildItem -Path $folder -Directory
# Loop through each folder and check the git status
foreach ($f in $folders) {
Check-GitStatus $f
}
}
end {
Pop-Location
}
}

# Define a function that moves the module folder to the repository path, replacing the .git file
function Move-ModuleFolder {
param (
[System.IO.FileInfo]$GitFile,
[string]$ModulesPath
)
# Get the corresponding module folder from the modules path
$moduleFolder = Get-ChildItem -Path $ModulesPath -Directory | Where-Object { $_.Name -eq $GitFile.Directory.Name } | Select-Object -First 1
# Move the module folder to the repository path, replacing the .git file
Remove-Item -Path $GitFile -Force
Move-Item -Path $moduleFolder.FullName -Destination $GitFile -Force
}
# Define a function that removes the worktree lines from the git config file
function Remove-WorktreeLines {
param (
[System.IO.DirectoryInfo]$GitFolder
)
# Get the path to the git config file
$configFile = Join-Path -Path $GitFolder -ChildPath "\config"
# Check if the config file exists
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $GitFolder"
}
else {
# Read the config file content as an array of lines
$configLines = Get-Content -Path $configFile
# Filter out the lines that contain worktree, which is a setting that can cause problems with scoop
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
# Check if there are any lines that contain worktree
if ($configLines | Where-Object { $_ -match "worktree" }) {
# Write the new config file content, removing the worktree lines
Set-Content -Path $configFile -Value $newConfigLines -Force
}
}
}
# Define a function that checks the status of a git repository and repairs it if needed
function Repair-ScoopGitRepository {
param (
[string]$RepositoryPath,
[string]$ModulesPath
)
# Change the current directory to the repository path
Set-Location $RepositoryPath
# Run git status and capture the output
$output = git status
# Check if the output is fatal, meaning the repository is corrupted
if ($output -like "fatal*") {
Write-Output "fatal status for $RepositoryPath"
# Get the .git file or folder in the repository path
$toRepair = Get-ChildItem -Path $RepositoryPath -Force | Where-Object { $_.Name -eq ".git" }
# Check if the .git item is a file
if ($toRepair -is [System.IO.FileInfo]) {
Move-ModuleFolder -GitFile $toRepair -ModulesPath $ModulesPath
}
else {
Write-Error "not a .git file: $toRepair"
}
# Check if the .git item is a folder
if ($toRepair -is [System.IO.DirectoryInfo]) {
Remove-WorktreeLines -GitFolder $toRepair
}
else {
Write-Error "not a .git folder: $toRepair"
}
}
else {
Write-Output @($output)[0]
}
}
# Define a function that validates the paths, sets the error redirection, and repairs the git repositories in the given folder
function Repair-ScoopGit {
param (
# Validate that the modules path exists
[ValidateScript({Test-Path $_})]
[string]$ModulesPath,
# Validate that the folder path exists
[ValidateScript({Test-Path $_})]
[string]$FolderPath
)
# Redirect the standard error output of git commands to the standard output stream
$env:GIT_REDIRECT_STDERR = '2>&1'
# Get the list of subfolders in the folder path
$subfolders = Get-ChildItem -Path $FolderPath -Directory
# Loop through each subfolder and repair its git repository
foreach ($subfolder in $subfolders) {
Write-Output "checking $subfolder"
# Check if the subfolder has a .git file or folder
if (Get-ChildItem -Path $subfolder.FullName -Force | Where-Object { $_.Name -eq ".git" }) {
Repair-ScoopGitRepository -RepositoryPath $subfolder.FullName -ModulesPath $ModulesPath
}
else {
Write-Output "$subfolder not yet initialized"
}
}
}
# Call the main function with the modules and folder paths as arguments
Initialize-ScoopGitRepair -ModulesPath "C:\ProgramData\scoop\persist\.git\modules" -FolderPath "C:\ProgramData\scoop\persist"
Repair-ScoopGitRepositories -FolderPath "C:\ProgramData\scoop\persist" -ModulesPath "C:\ProgramData\scoop\persist\.git\modules"
<#
========================================================================================================================
Name : <Name>.ps1
Description : This script ............................
Created Date : %Date%
Created By : %UserName%
Dependencies : 1) Windows PowerShell 5.1
2) .................
Revision History
Date Release Change By Description
%Date% 1.0 %UserName% Initial Release
========================================================================================================================
#>
# Define a function to check if a .git file exists in a directory
<#
.SYNOPSIS
Checks if a .git file exists in a directory and returns true or false.
.PARAMETER Directory
The directory path to check.
.EXAMPLE
Test-GitFile -Directory "B:\PF\NoteTakingProjectFolder"
Output: True
#>
function Test-GitFile {
param(
# The directory path to check
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Directory
)
# Get the full path of the .git file in the directory
$gitFile = Join-Path $Directory ".git"
# Test if the .git file exists and return the result
return Test-Path $gitFile
}
# Define a function to get the name of the work folder from a directory path
<#
.SYNOPSIS
Gets the name of the work folder from a directory path by removing the drive letter and any trailing backslashes.
.PARAMETER Directory
The directory path to get the name from.
.EXAMPLE
Get-WorkFolderName -Directory "B:\PF\NoteTakingProjectFolder"
Output: PF\NoteTakingProjectFolder
#>
function Get-WorkFolderName {
param(
# The directory path to get the name from
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Directory
)
# Remove the drive letter and any trailing backslashes from the directory path and return the result
return $Directory.TrimStart("B:\").TrimEnd("\")
}
# Define a function to search with void tools everything for the folder name and "child:config" and get the first result
<#
.SYNOPSIS
Searches with void tools everything for the folder name and "child:config" and returns the first result as an object with properties Name, Path, and FullPath.
.PARAMETER FolderName
The folder name to search with.
.EXAMPLE
Search-Everything -FolderName "PF\NoteTakingProjectFolder"
Output: @{Name=config; Path=B:\PF\NoteTakingProjectFolder\.git\modules\NoteTakingProjectFolder; FullPath=B:\PF\NoteTakingProjectFolder\.git\modules\NoteTakingProjectFolder\config}
#>
function Ensure-Everything {
param(
# The folder name to search with
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$folderPath,
# The filter to apply to the search
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$filter
)
# Install and import the pseverything module if not already loaded
if (-not (Get-Module -Name pseverything)) {
Install-Module pseverything -Scope CurrentUser -Force
Import-Module pseverything
}
$folderPath = $folderPath.trim("\\")
$filter = """" + ($folderPath | Split-Path -Leaf) + """" + " " + $filter
# Use Everything to find all folders in the folder path that match the filter
$results = Search-Everything -Filter $filter -global
# If there are any results, then return the first one as an object with properties Name, Path, and FullPath
if ($results) {
$firstResult = $results
return [PSCustomObject]@{
Path = $firstResult
}
}
else {
# Throw an error if no results are found
throw "No results found for folder path '$folderPath' and filter '$filter'"
}
}
# Define a function to overwrite git file content with "gitdir:" followed by a path
<#
.SYNOPSIS
Overwrites git file content with "gitdir:" followed by a path.
.PARAMETER GitFile
The git file path to overwrite.
.PARAMETER Path
The path to append after "gitdir:".
.EXAMPLE
Overwrite-GitFile -GitFile "B:\PF\NoteTakingProjectFolder\.git" -Path "B:\PF\NoteTakingProjectFolder\.git\modules\NoteTakingProjectFolder"
Output: The content of B:\PF\NoteTakingProjectFolder\.git is overwritten with "gitdir: B:\PF\NoteTakingProjectFolder\.git\modules\NoteTakingProjectFolder"
#>
function Overwrite-GitFile {
param(
# The git file path to overwrite
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$GitFile,
# The path to append after "gitdir:"
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Path
)
# Create a new content string with "gitdir:" followed by the path
$newContent = "gitdir: $Path"
# Overwrite the git file content with the new content string using Set-Content cmdlet
Set-Content -Path $GitFile -Value $newContent -Force
}
function FIX {
param (
$directory = "B:\PF\chris\autohotkey\script\fork\MACRO RECORDER"
)
# Get the directory path from the user input and store it in a variable
# Check if a .git file exists in the directory using Test-GitFile function and store the result in a variable
$gitFileExists = Test-GitFile -Directory $directory
# If the result is true, then proceed with the rest of the script
if ($gitFileExists) {
# Get the name of the work folder from the directory path using Get-WorkFolderName function and store it in a variable
$workFolderName = Get-WorkFolderName -Directory $directory
# Search with void tools everything for the folder name and "child:config" and get the first result using Search-Everything function and store it in a variable
$firstResult = Ensure-Everything -folderPath $workFolderName -filter 'child:config count:1'
# If there is a first result, then proceed with the rest of the script
if ($firstResult) {
# Get the full path of the .git file in the directory and store it in a variable
$gitFile = Join-Path $directory ".git"
# Get the path property of the first result and store it in a variable
$path = $firstResult.Path
# Overwrite git file content with "gitdir:" followed by the path using Overwrite-GitFile function
Overwrite-GitFile -GitFile $gitFile -Path $path
}
}
}
$patj = "B:\PF\chris\autohotkey\script\fork\"
$q = Get-ChildItem -Path $patj
$q | % {
cd $_.FullName -PassThru
$t = invoke-expression "git status 2>&1"
if($t -like "fatal: not a git repository:*" )
{fix -directory $_.FullName}
}
# \fix-CorruptedGitModules.ps1
<#
This code is a PowerShell script that checks the status of git repositories in a given folder and repairs
them if they are corrupted. It does the following steps:
It defines a begin block that runs once before processing any input. In this block, it sets some variables
for the modules and folder paths, validates them, and redirects the standard error output of git commands
to the standard output stream.
It defines a process block that runs for each input object. In this block, it loops through each subfolder
in the folder path and runs git status on it. If the output is fatal, it means the repository is corrupted
and needs to be repaired. To do that, it moves the corresponding module folder from the modules path to the
subfolder, replacing the existing .git file or folder. Then, it reads the config file of the repository and
removes any line that contains worktree, which is a setting that can cause problems with scoop. It prints
the output of each step to the console.
It defines an end block that runs once after processing all input. In this block, it restores the original
location of the script.#>
. $PSScriptRoot\Invoke-Git.ps1
. $PSScriptRoot\Split-TextByRegex.ps1
. $PSScriptRoot\git-GetSubmodulePathsUrls.ps1
. $PSScriptRoot\config-to-gitmodules.ps1
# \fix-CorruptedGitModulesCombinedWithQue.ps1
function Validate-Path {
param (
[Parameter(Mandatory=$true)]
[string]$Path
)
if (-not (Test-Path $Path)) {
Write-Error "Invalid path: $Path"
exit 1
}
}
# \GetWorktreeSubmodules.ps1
# Define a function to convert key-value pairs to custom objects
# Define a function to get the URL of a submodule
function byPath-RepoUrl {
param(
[string]$Path # The path of the submodule directory
)
# Change the current location to the submodule directory
Push-Location -Path $Path -ErrorAction Stop
# Get the URL of the origin remote
$url = invoke-git "config remote.origin.url" -ErrorAction Stop
# Parse the URL to get the part after the colon
$parsedUrl = ($url -split ':')[1]
# Return to the previous location
Pop-Location -ErrorAction Stop
# Return the parsed URL as output
return $parsedUrl
}
function get-gitUnhide ($Path)
{
Get-ChildItem -Path "$Path\*" -Force | Where-Object { $_.Name -eq ".git" }
}
# requries gitmodulesfile
# \GitSyncSubmoduleWithConfig.ps1
<#
.SYNOPSIS
Synchronizes the submodules with the config file.
.DESCRIPTION
This function synchronizes the submodules with the config file, using the Git-helper and ini-helper modules. The function checks the remote URLs of the submodules and updates them if they are empty or local paths. The function also handles conflicts and errors.
.PARAMETER GitDirPath
The path of the git directory where the config file is located.
.PARAMETER GitRootPath
The path of the git root directory where the submodules are located.
.PARAMETER FlagConfigDecides
A switch parameter that indicates whether to use the config file as the source of truth in case of conflicting URLs.
#>
# A function to move a .git Folder into the current directory and remove any gitfiles present
function unearthiffind ()
{
param (
[Parameter(Mandatory=$true)]
[string]$toRepair,
[Parameter(Mandatory=$true)]
[string]$Modules
)
# Get the module folder that matches the name of the parent directory
Get-ChildItem -Path $Modules -Directory | Where-Object { $_.Name -eq $toRepair.Directory.Name } | Select-Object -First 1 | % {
# Move the module folder to replace the .git file
Remove-Item -Path $toRepair -Force
Move-Item -Path $_.FullName -Destination $toRepair -Force
}
}
# A function to check the git status of a folder
function Check-GitStatus ($folder) {
# Change the current directory to the folder
Set-Location $folder.FullName
Write-Output "checking $folder"
if ((Get-ChildItem -force | ?{ $_.name -eq ".git" } ))
{
# Run git status and capture the output
$output = Invoke-Git "git status"
if(($output -like "fatal*"))
{
Write-Output "fatal status for $folder"
#UnabosrbeOrRmWorktree $folder
}
else
{
Write-Output @($output)[0]
}
}
else
{
Write-Output "$folder not yet initialized"
}
}
# Define a function to remove the worktree from a config file
function Remove-Worktree {
param(
[string]$ConfigPath # The path of the config file
)
if(Test-Package "Get-IniContent")
{
# Get the content of the config file as an ini object
$iniContent = Get-IniContent -FilePath $ConfigPath
# Remove the worktree property from the core section
$iniContent.core.Remove("worktree")
# Write the ini object back to the config file
$iniContent | Out-IniFile -FilePath $ConfigPath -Force
}
else
{
# Read the config file content as an array of lines
$configLines = Get-Content -Path $ConfigPath
# Filter out the lines that contain worktree
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
if (($configLines | Where-Object { $_ -match "worktree" }))
{
# Write the new config file content
Set-Content -Path $ConfigPath -Value $newConfigLines -Force
}
}
}
function Remove-WorktreeHere {
param(
[string]$ConfigPath, # The path of the config file
[alias]$folder,$toRepair
)
# Get the path to the git config file
$configFile = Join-Path -Path $toRepair -ChildPath "\config"
# Check if it exists
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $toRepair"
}
else
{
Remove-Worktree -ConfigPath $toRepair
}
}
# A function to repair a corrupted git folder
# param folder: where to look for replacement module to unearth with
function UnabosrbeOrRmWorktree ($folder) {
get-gitUnhide $folder | % {
if( $_ -is [System.IO.FileInfo] )
{
unearthIffind $_ $folder
}
elseif( $_ -is [System.IO.DirectoryInfo] )
{
Remove-WorktreeHere $_
}
else
{
Write-Error "not a .git file or folder: $_"
}
}
}
# A function to repair a fatal git status
function UnabosrbeOrRmWorktree {
param (
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter(Mandatory=$true)]
[string]$Modules
)
# Print a message indicating fatal status
write-verbos "fatal status for $Path, atempting repair"
cd $Path
UnabosrbeOrRmWorktree -folder $Modules
}
function get-Origin
{
# Get the remote url of the git repository
$ref = (git remote get-url origin)
# Write some information to the console
Write-Verbos '************************** ref *****************************'
Write-Verbos $ref.ToString()
Write-Verbos '************************** ref *****************************'
return $ref
}
function get-Relative {
param (
$path
,$targetFolder
)
Set-Location $path
$gitRoot = Get-GitRoot
# Get the relative path of the target folder from the root of the git repository
return (Resolve-Path -Path $targetFolder.FullName -Relative).TrimStart('.\').Replace('\', '/')
# Write some information to the console
Write-Verbos '******************************* bout to read as submodule ****************************************'
Write-Verbos $relative.ToString()
Write-Verbos $ref.ToString()
Write-Verbos '****************************** relative path ****************************************************'
}
# Define a function to get the root of the git repository
function Get-GitRoot {
(git rev-parse --show-toplevel)
}
function git-root {
$gitrootdir = (git rev-parse --show-toplevel)
if ($gitrootdir) {
Set-Location $gitrootdir
}
}
# Define a function to move a folder to a new destination
function Move-Folder {
param (
[Parameter(Mandatory=$true)][string]$Source,
[ValidateScript({Test-Path $_})]
# Check if the destination already exists
[Parameter(Mandatory=$true, HelpMessage="Enter A empty path to move to")]
[ValidateScript({!(Test-Path $_)})]
[string]$Destination
)
try {
Move-Item -Path $Source -Destination $Destination -ErrorAction Stop
Write-Verbos "Moved $Source to $Destination"
}
catch {
Write-Warning "Failed to move $Source to $Destination"
Write-Warning $_.Exception.Message
}
}
# Define a function to add and absorb a submodule
function Add-AbsorbSubmodule {
param (
[Parameter(Mandatory=$true)]
[string]$Ref,
[Parameter(Mandatory=$true)]
[string]$Relative
)
try {
Git submodule add $Ref $Relative
git commit -m "as submodule $Relative"
Git submodule absorbgitdirs $Relative
Write-Verbos "Added and absorbed submodule $Relative"
}
catch {
Write-Warning "Failed to add and absorb submodule $Relative"
Write-Warning $_.Exception.Message
}
}
function index-Remove ($name,$path)
{
try {
# Change to the parent path and forget about the files in the target folder
Set-Location $path
# Check if the files in the target folder are already ignored by git
if ((git ls-files --error-unmatch --others --exclude-standard --directory --no-empty-directory -- "$name") -eq "") {
Write-Warning "The files in $name are already ignored by git"
}
else {
git rm -r --cached $name
git commit -m "forgot about $name"
}
}
catch {
Write-Warning "Failed to forget about files in $name"
Write-Warning $_.Exception.Message
}
}
function about-Repo()
{
$vb = ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
# Write some information to the console
Write-Verbos '************************************************************' -Verbose: $vb
Write-Verbos $targetFolder.ToString() -Verbose: $vb
Write-Verbos $name.ToString() -Verbose: $vb
Write-Verbos $path.ToString() -Verbose: $vb
Write-Verbos $configFile.ToString() -Verbose: $vb
Write-Verbos '************************************************************'-Verbose: $vb
}
<#
.SYNOPSIS
Gets the paths of all submodules in a git repository.
.DESCRIPTION
Gets the paths of all submodules in a git repository by parsing the output of git ls-files --stage.
.OUTPUTS
System.String[]
#>
function Get-SubmodulePaths {
# run git ls-files --stage and filter by mode 160000
git ls-files --stage | Select-String -Pattern "^160000"
# loop through each line of output
foreach ($Line in $Input) {
# split the line by whitespace and get the last element as the path
$Line -split "\s+" | Select-Object -Last 1
}
}
<#
.SYNOPSIS
Gets the absolute path of the .git directory for a submodule.
.DESCRIPTION
Gets the absolute path of the .git directory for a submodule by reading the .git file and running git rev-parse --absolute-git-dir.
.PARAMETER Path
The path of the submodule.
.OUTPUTS
System.String
#>
function Get-GitDir {
param (
[Parameter(Mandatory)]
[string]$Path
)
# read the .git file and get the value after "gitdir: "
$GitFile = Get-Content -Path "$Path/.git"
$GitDir = $GitFile -replace "^gitdir: "
# run git rev-parse --absolute-git-dir to get the absolute path of the .git directory
git -C $Path rev-parse --absolute-git-dir | Select-Object -First 1
}
<#
.SYNOPSIS
Unsets the core.worktree configuration for a submodule.
.DESCRIPTION
Unsets the core.worktree configuration for a submodule by running git config --local --path --unset core.worktree.
.PARAMETER Path
The path of the submodule.
#>
function Unset-CoreWorktree {
param (
[Parameter(Mandatory)]
[string]$Path
)
# run git config --local --path --unset core.worktree for the submodule
git --work-tree=$Path --git-dir="$Path/.git" config --local --path --unset core.worktree
}
<#
.SYNOPSIS
Hides the .git directory on Windows.
.DESCRIPTION
Hides the .git directory on Windows by running attrib.exe +H /D.
.PARAMETER Path
The path of the submodule.
#>
function Hide-GitDir {
param (
[Parameter(Mandatory)]
[string]$Path
)
# check if attrib.exe is available on Windows
if (Get-Command attrib.exe) {
# run attrib.exe +H /D to hide the .git directory
MSYS2_ARG_CONV_EXCL="*" attrib.exe "+H" "/D" "$Path/.git"
}
}
<# .SYNOPSIS
Lists all the ignored files that are cached in the index.
.DESCRIPTION
This function uses git ls-files with the -c, --ignored and --exclude-standard options to list all the files that are ignored by
Use git ls-files with the -c, --ignored and --exclude-standard options
.gitignore or other exclude files, and also have their content cached in the index. #>
function Get-IgnoredFiles {
git ls-files -s --ignored --exclude-standard -c }
<# .SYNOPSIS
Removes files from the index and the working tree.
.DESCRIPTION
This function takes an array of file names as input and uses git rm to remove them from the index and the working tree.
Define a function that removes files from the index and the working tree
.PARAMETER
Files An array of file names to be removed. #>
function Remove-Files { param( # Accept an array of file names as input
[string[]]$Files )
#Use git rm with the file names as arguments
git rm $Files --ignore-unmatch }
<# .SYNOPSIS
Rewrites the history of the current branch by removing all the ignored files.
.DESCRIPTION
This function uses git filter-branch with the -f, --index-filter and --prune-empty options to rewrite the history of the current branch by removing all the ignored files from each revision, and also removing any empty commits that result from this operation. It does this by modifying only the index, not the working tree, of each revision.
#Define a function that rewrites the history of the current branch by removing all the ignored files
#Use git filter-branch with the -f, --index-filter and --prune-empty options#>
function Rewrite-History { # Call the Get-IgnoredFiles function and pipe the output to Remove-Files function
git filter-branch -f --index-filter {
Get-IgnoredFiles | Remove-Files
} --prune-empty }
#Call the Rewrite-History function
<# .SYNOPSIS
Lists all the ignored files that are cached in the index.
.DESCRIPTION
This function uses git ls-files with the -c, --ignored and --exclude-standard options to list all the files that are ignored by
Use git ls-files with the -c, --ignored and --exclude-standard options
.gitignore or other exclude files, and also have their content cached in the index. #>
function Get-IgnoredFiles {
git ls-files -s --ignored --exclude-standard -c }
<# .SYNOPSIS
Removes files from the index and the working tree.
.DESCRIPTION
This function takes an array of file names as input and uses git rm to remove them from the index and the working tree.
Define a function that removes files from the index and the working tree
.PARAMETER
Files An array of file names to be removed. #>
function Remove-Files { param( # Accept an array of file names as input
[string[]]$Files )
#Use git rm with the file names as arguments
git rm $Files --ignore-unmatch }
<# .SYNOPSIS
Rewrites the history of the current branch by removing all the ignored files.
.DESCRIPTION
This function uses git filter-branch with the -f, --index-filter and --prune-empty options to rewrite the history of the current branch by removing all the ignored files from each revision, and also removing any empty commits that result from this operation. It does this by modifying only the index, not the working tree, of each revision.
#Define a function that rewrites the history of the current branch by removing all the ignored files
#Use git filter-branch with the -f, --index-filter and --prune-empty options#>
function Rewrite-History { # Call the Get-IgnoredFiles function and pipe the output to Remove-Files function
git filter-branch -f --index-filter {
Get-IgnoredFiles | Remove-Files
} --prune-empty }
#Call the Rewrite-History function Rewrite-History
# A function that parses the output of git ls-tree command and returns a custom object with properties
function Parse-GitLsTreeOutput
{
[CmdletBinding()]
param(
# The script or file path to parse
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]$LsTreeOutput
)
process {
# Extract the blob type from the output line
$blobType = $_.substring(7,4)
# Set the hash start position based on the blob type
$hashStartPos = 12
if ($blobType -ne 'blob') { $hashStartPos+=2 }
# Set the relative path start position based on the blob type
$relativePathStartPos = 53
if ($blobType -ne 'blob') { $relativePathStartPos+=2 }
# Create a custom object with properties for unknown, blob, hash and relative path
[pscustomobject]@{unknown=$_.substring(0,6);blob=$blobType; hash=$_.substring($hashStartPos,40);relativePath=$_.substring($relativePathStartPos)}
}
}
# A function that resolves the absolute path of a file from its relative path
function Resolve-AbsolutePath
{
param(
[Parameter(Mandatory)] [string]$RelativePath
)
# Escape the backslash character for regex matching
$backslash = [regex]::escape('\')
# Define a regex pattern for matching octal escape sequences in the relative path
$octalPattern = $backslash+'\d{3}'+$backslash+'\d{3}'
# Trim the double quotes from the relative path
$relativePath = $RelativePath.Trim('"')
# Try to resolve the relative path to an absolute path
$absolutePath = Resolve-Path $relativePath -ErrorAction SilentlyContinue
# If the absolute path is not found and the relative path contains octal escape sequences, try to resolve it with wildcard matching
if(!$absolutePath -and $relativePath -match ($octalPattern))
{
$absolutePath = Resolve-Path (($relativePath -split($octalPattern) ) -join('*'))
}
# Return the absolute path or null if not found
return $absolutePath
}
# A function that takes a collection of parsed git ls-tree output objects and adds more properties to them such as absolute path, file name and parent folder
function Add-MorePropertiesToGitLsTreeOutput
{
param(
[Parameter(Mandatory)]
[psobject[]]$GitLsTreeOutputObjects
)
# For each object in the collection, add more properties using calculated expressions
$GitLsTreeOutputObjects | Select-Object -Property *,@{Name = 'absolute'; Expression = {Resolve-AbsolutePath $_.relativePath}},@{Name = 'FileName'; Expression = {$path = $_.absolute;$filename = [System.IO.Path]::GetFileNameWithoutExtension("$path");if(!($filename)) { $filename = [System.IO.Path]::GetFileName("$path") };$filename}},@{Name = 'Parent'; Expression = {Split-Path -Path $_.relativePath}}
}
# A function that joins two collections of parsed git ls-tree output objects based on their file names and returns a custom object with properties for hash and absolute paths of both collections
function Join-GitLsTreeOutputCollectionsByFileName
{
param(
[Parameter(Mandatory)]
[psobject[]]$Collection1,
[Parameter(Mandatory)]
[psobject[]]$Collection2
)
# Define a delegate function that returns the file name of an object as the join key
$KeyDelegate = [System.Func[Object,string]] {$args[0].FileName}
# Define a delegate function that returns a custom object with properties for hash and absolute paths of both collections as the join result
$resultDelegate = [System.Func[Object,Object,Object]]{
param ($x,$y);
New-Object -TypeName PSObject -Property @{
Hash = $x.hash;
AbsoluteX = $x.absolute;
AbsoluteY = $y.absolute
}
}
# Use LINQ Join method to join the two collections by file name and return an array of custom objects as the result
$joinedDataset = [System.Linq.Enumerable]::Join( $Collection1, $Collection2, #tableReference
$KeyDelegate,$KeyDelegate, #onClause
$resultDelegate
)
$OutputArray = [System.Linq.Enumerable]::ToArray($joinedDataset)
return $OutputArray
}
# A function that creates a lookup table from a collection of parsed git ls-tree output objects based on their hash values
function Create-LookupTableByHash
{
param(
[Parameter(Mandatory)]
[psobject[]]$GitLsTreeOutputObjects
)
# Define a delegate function that returns the hash value of an object as the lookup key
$HashDelegate = [system.Func[Object,String]] { $args[0].hash }
# Define a delegate function that returns the object itself as the lookup element
$ElementDelegate = [system.Func[Object]] { $args[0] }
# Use LINQ ToLookup method to create a lookup table from the collection by hash value and return an array of lookup groups as the result
$lookup = [system.Linq.Enumerable]::ToLookup($GitLsTreeOutputObjects, $HashDelegate,$ElementDelegate)
return [Linq.Enumerable]::ToArray($lookup)
}
# This function takes an array of objects and splits it into smaller chunks of a given size
# It also executes a script block on each chunk if provided
function Split-Array
{
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)] [object[]] $InputObject,
[Parameter()] [scriptblock] $Process,
[Parameter()] [int] $ChunkSize
)
Begin { #run once
# Initialize an empty array to store the chunks
$cache = @();
# Initialize an index to keep track of the chunk size
$index = 0;
}
Process { #run each entry
if($cache.Length -eq $ChunkSize) {
# if the cache array is full, send it out to the pipe line
write-host '{' –NoNewline
write-host $cache –NoNewline
write-host '}'
# Then we add the current pipe line object to the cache array and reset the index
$cache = @($_);
$index = 1;
}
else {
# Otherwise, we append the current pipe line object to the cache array and increment the index
$cache += $_;
$index++;
}
}
End { #run once
# Here we check if there are any remaining objects in the cache array, if so, send them out to pipe line
if($cache) {
Write-Output ($cache );
}
}
}
# This function parses the output of git ls-tree and converts it into a custom object with properties
function Parse-LsTree
{
[CmdletBinding()]
param(
# The script or file path to parse
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]$LsTree
)
process {
# Extract the blob type from the input string
$blobType = $_.substring(7,4)
# Set the starting positions of the hash and relative path based on the blob type
$hashStartPos = 12
$relativePathStartPos = 53
if ($blobType -ne 'blob')
{
$hashStartPos+=2
$relativePathStartPos+=2
}
# Create a custom object with properties for unknown, blob, hash and relative path
[pscustomobject]@{unkown=$_.substring(0,6);blob=$blobType; hash=$_.substring($hashStartPos,40);relativePath=$_.substring($relativePathStartPos)}
}
}
# This function lists the duplicate object hashes in a git repository using git ls-tree and Parse-LsTree functions
function List-Git-DuplicateHashes
{
param([string]$path)
# Save the current working directory
$current = $PWD
# Change to the given path
cd $path
# Use git ls-tree to list all the objects in the HEAD revision
git ls-tree -r HEAD |
# Parse the output using Parse-LsTree function
Parse-LsTree |
# Group the objects by hash and filter out the ones that have only one occurrence
Group-Object -Property hash |
? { $_.count -ne 1 } |
# Sort the groups by count in descending order
Sort-Object -Property count -Descending
# Change back to the original working directory
cd $current
}
# This function adds an index property to each object in an array using a counter variable
function Add-Index { #https://stackoverflow.com/questions/33718168/exclude-index-in-powershell
begin {
# Initialize the counter variable as -1
$i=-1
}
process {
if($_ -ne $null) {
# Increment the counter variable and add it as an index property to the input object
Add-Member Index (++$i) -InputObject $_ -PassThru
}
}
}
# This function displays the indexed groups of duplicate hashes in a clear format
function Show-Duplicates
{
[cmdletbinding()]
param(
[parameter(ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[object[]] $input
)
Clear-Host
Write-Host "================ k for keep all ================"
# Add an index property to each group using Add-Index function
$indexed = ( $input | %{$_.group} | Add-Index )
# Display the index and relative path of each group and store the output in a variable
$indexed | Tee-Object -variable re |
% {
$index = $_.index
$relativePath = $_.relativePath
Write-Host "$index $relativePath"
}
# Return the output variable
$re
}
# This function allows the user to choose which duplicate hashes to keep or delete
function Choose-Duplicates
{
[cmdletbinding()]
param(
[parameter(ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[object[]] $input
)
# Split the input array into smaller chunks using Split-Array function
$options = $input | %{$_.index} | Split-Array
# Prompt the user to choose from the alternatives and store the input in a variable
$selection = Read-Host "choose from the alternativs " ($input | measure-object).count
# If the user chooses to keep all, return nothing
if ($selection -eq 'k' ) {
return
}
else {
# Otherwise, filter out the objects that have the same index as the selection and store them in a variable
$q = $input | ?{ $_.index -ne $selection }
}
# Return the filtered variable
$q
}
# This function deletes the chosen duplicate hashes using git rm command
function Delete-Duplicates
{
[cmdletbinding()]
param(
[parameter(ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[object[]] $input
)
if($input -ne $null)
{
# Split the input array into smaller chunks using Split-Array function
$toDelete = $input | %{$_.relativepath} | Split-Array
# For each chunk, use git rm to delete the files
$toDelete | % { git rm $_ }
# Wait for 2 seconds before proceeding
sleep 2
}
}
function Get-Commits {
param (
# The date parameter specifies the cut-off date for the commits
[Parameter(Mandatory=$true)]
[string]$Date
)
# Use git log to get all commit hashes before the date in a table format
$commits = git log --all --before="$Date" --pretty=format:"%H"
# Return the table of commits
return $commits
}
# Define a function to search for a string in a commit using git grep
function Search-Commit {
param (
# The commit parameter specifies the commit hash to search in
[Parameter(Mandatory=$true)]
[string]$Commit,
# The string parameter specifies the string to search for
[Parameter(Mandatory=$true)]
[string]$String
)
# Use git grep to search for the string in the commit and return a boolean value
$result = git grep --ignore-case --word-regexp --fixed-strings -o $String -- $Commit
return $result
}
# Define a function to search for a string in a commit using git log and regex
function Search-Commit-Regex {
param (
# The commit parameter specifies the commit hash to search in
[Parameter(Mandatory=$true)]
[string]$Commit,
# The regex parameter specifies the regex pattern to search for
[Parameter(Mandatory=$true)]
[string]$Regex
)
# Use git log to search for the regex pattern in the commit and return a boolean value
$result = git log -G $Regex -- $Commit
return $result
}
# Define a function to create a hash table of commits and their frequencies of matching the search string
function Get-HashTable {
param (
# The commits parameter specifies the table of commits to process
[Parameter(Mandatory=$true)]
[array]$Commits,
# The string parameter specifies the string to search for in each commit
[Parameter(Mandatory=$true)]
[string]$String,
# The regex parameter specifies whether to use regex or not for searching (default is false)
[Parameter(Mandatory=$false)]
[bool]$Regex = $false
)
# Create an empty hash table to store the results
$hashTable = @{}
# Loop through each commit in the table of commits
foreach ($commit in $commits) {
# If regex is true, use Search-Commit-Regex function, otherwise use Search-Commit function
if ($Regex) {
$match = Search-Commit-Regex -Commit $commit -Regex $String
}
else {
$match = Search-Commit -Commit $commit -String $String
}
# If there is a match, increment the frequency of the commit in the hash table, otherwise set it to zero
if ($match) {
$hashTable[$commit]++
}
else {
$hashTable[$commit] = 0
}
}
# Return the hash table of commits and frequencies
return $hashTable
}
# Define a function to display a progress bar while processing a table of commits
function Show-Progress {
param (
[Parameter(Mandatory=$true)]
[array]$Commits,
# The activity parameter specifies the activity name for the progress bar (default is "Searching Events")
[Parameter(Mandatory=$false)]
[string]$Activity = "Searching Events",
# The status parameter specifies the status name for the progress bar (default is "Progress:")
[Parameter(Mandatory=$false)]
[string]$Status = "Progress:"
)
# Set the counter variable to zero
$i = 0
# Loop through each commit in the table of commits
foreach ($commit in $commits) {
# Increment the counter variable
$i = $i + 1
# Determine the completion percentage
$Completed = ($i / $commits.count * 100)
# Use Write-Progress to output a progress bar with the activity and status parameters
Write-Progress -Activity $Activity -Status $Status -PercentComplete $Completed
}
}
function GitGrep {
param ([string]$range, [string]$grepThis)
git log --pretty=format:"%H" $range --no-merges --grep="$grepThis" | ForEach-Object {
$Body = git log -1 --pretty=format:"%b" $_ | Select-String "$grepThis"
if($Body) {
git log -1 --pretty=format:"%H,%s" $_
Write-Host $Body
}
}
}
function Git-LsTree {
param ([string]$range, [string]$grepThis)
$Body = git ls-tree $range -r
$body | % {
$spl = $_ -split ' ',3
[pscustomobject]@{
hash = $range
q = $spl[0].trim()
type = $spl[1].trim()
objectID = $spl[2].Substring(0,40).trim()
relative = $spl[2].Substring(40).trim()
}
}
}

# \fix-CorruptedGitModules.ps1
<#
This code is a PowerShell script that checks the status of git repositories in a given folder and repairs
them if they are corrupted. It does the following steps:
It defines a begin block that runs once before processing any input. In this block, it sets some variables
for the modules and folder paths, validates them, and redirects the standard error output of git commands
to the standard output stream.
It defines a process block that runs for each input object. In this block, it loops through each subfolder
in the folder path and runs git status on it. If the output is fatal, it means the repository is corrupted
and needs to be repaired. To do that, it moves the corresponding module folder from the modules path to the
subfolder, replacing the existing .git file or folder. Then, it reads the config file of the repository and
removes any line that contains worktree, which is a setting that can cause problems with scoop. It prints
the output of each step to the console.
It defines an end block that runs once after processing all input. In this block, it restores the original
location of the script.#>
# A function to run git commands and check the exit code
function Invoke-Git {
param(
[Parameter(Mandatory=$true)]
[string]$Command # The git command to run
)
# Run the command and capture the output
$output = Invoke-Expression -Command "git $Command" -ErrorAction Stop
# return the output to the host
$output
# Check the exit code and throw an exception if not zero
if ($LASTEXITCODE -ne 0) {
throw "Git command failed: git $Command"
}
}
# \fix-CorruptedGitModulesCombinedWithQue.ps1
# Define a function to split text by a regular expression
# Define a function to split text by a regular expression
function Split-TextByRegex {
<#
.SYNOPSIS
Splits text by a regular expression and returns an array of objects with the start index, end index, and value of each match.
.DESCRIPTION
This function takes a path to a text file and a regular expression as parameters, and returns an array of objects with the start index, end index, and value of each match. The function uses the Select-String cmdlet to find the matches, and then creates custom objects with the properties of each match.
.PARAMETER Path
The path to the text file to be split.
.PARAMETER Regx
The regular expression to use for splitting.
.EXAMPLE
Split-TextByRegex -Path ".\test.txt" -Regx "submodule"
This example splits the text in the test.txt file by the word "submodule" and returns an array of objects with the start index, end index, and value of each match.
#>
# Validate the parameters
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_})]
[string]$Path,
[Parameter(Mandatory=$true)]
[string]$Regx
)
# Try to read the text from the file
try {
$Content = Get-Content $Path -Raw
}
catch {
Write-Error "Could not read the file: $_"
return
}
# Try to split the content by the regular expression
try {
$Matchez = [regex]::Matches($Content, $Regx)
$NonMatches = [regex]::Split($Content, $Regx)
$single = Select-String -InputObject $Content -Pattern $Regx -AllMatches | Select-Object -ExpandProperty Matches
}
catch {
Write-Error "Could not split the content by $Regx"
return
}
# Create an array to store the results
$Results = @()
if($IncNonmatches)
{
# Loop through the matches and non-matches and create custom objects with the index and value properties
for ($i = 0; $i -lt $Matchez.Count; $i++) {
$Results += [PSCustomObject]@{
index = $Matchez[$i].Index
value = $Matchez[$i].Value
}
$Results += [PSCustomObject]@{
index = $Matchez[$i].Index + $Matches[$i].Length
value = $NonMatches[$i + 1]
}
}
}
else {
# Loop through each match and create a custom object with its properties
foreach ($match in $single) {
$result = [PSCustomObject]@{
StartIndex = $match.Index
EndIndex = $match.Index + $match.Length - 1
Value = $match.Value
}
# Add the result to the array
$results += $result
}
}
# Return the results
return $Results
}
function Validate-Path {
param (
[Parameter(Mandatory=$true)]
[string]$Path
)
if (-not (Test-Path $Path)) {
Write-Error "Invalid path: $Path"
exit 1
}
}
# \GetWorktreeSubmodules.ps1
# Define a function to convert key-value pairs to custom objects
function keyPairTo-PsCustom {
<#
.SYNOPSIS
Converts key-value pairs to custom objects with properties corresponding to the keys and values.
.DESCRIPTION
This function takes an array of strings containing key-value pairs as a parameter, and returns an array of custom objects with properties corresponding to the keys and values. The function uses the ConvertFrom-StringData cmdlet to parse the key-value pairs, and then creates custom objects with the properties.
.PARAMETER KeyPairStrings
The array of strings containing key-value pairs.
.EXAMPLE
keyPairTo-PsCustom -KeyPairStrings @("name=John", "age=25")
This example converts the key-value pairs in the array to custom objects with properties name and age.
#>
# Validate the parameter
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string[]]$KeyPairStrings
)
# Create an array to store the results
$results = @()
# Loop through each string in the array
foreach ($string in $KeyPairStrings) {
# Try to parse the key-value pairs using ConvertFrom-StringData cmdlet
try {
$data = ConvertFrom-StringData $string
}
catch {
Write-Error "Could not parse the string: $_"
continue
}
# Create a custom object with properties from the data hashtable
$result = New-Object -TypeName PSObject -Property $data
# Add the result to the array
$results += $result
}
# Return the array of results
return $results
}
# Define a function to get the URL of a submodule
function byPath-RepoUrl {
param(
[string]$Path # The path of the submodule directory
)
# Change the current location to the submodule directory
Push-Location -Path $Path -ErrorAction Stop
# Get the URL of the origin remote
$url = invoke-git "config remote.origin.url" -ErrorAction Stop
# Parse the URL to get the part after the colon
$parsedUrl = ($url -split ':')[1]
# Return to the previous location
Pop-Location -ErrorAction Stop
# Return the parsed URL as output
return $parsedUrl
}
function get-gitUnhide ($Path)
{
Get-ChildItem -Path "$Path\*" -Force | Where-Object { $_.Name -eq ".git" }
}
# requries gitmodulesfile
function git-GetSubmodulePathsUrls
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path -Path "$_\.gitmodules"})]
[string]
$RepoPath
)
try {
if(validGitRepo)
{
$zz = (git config -f .gitmodules --get-regexp '^submodule\..*\.path$')
}
else{
$rgx = "submodule" # Set the regular expression to use for splitting
# Change the current directory to the working path
Set-Location $RepoPath
# Set the path to the .gitmodules file
$p = Join-Path $RepoPath ".gitmodules"
# Split the text in the .gitmodules file by the regular expression and store the results in a variable
$TextRanges = Split-TextByRegex -Path $p -Regx $rgx
$zz = $TextRanges | keyPairTo-PsCustom
if(! ($zz))
{
# Convert the key-value pairs in the text ranges to custom objects and store the results in a variable
$zz = $TextRanges | ForEach-Object {
try {
# Trim and join the values in each text range
$q = $_.value.trim() -join ","
}
catch {
# If trimming fails, just join the values
$q = $_.value -join ","
}
try {
# Split the string by commas and equal signs and create a hashtable with the path and url keys
$t = @{
path = $q.Split(',')[0].Split('=')[1].trim()
url = $q.Split(',')[1].Split('=')[1].trim()
}
}
catch {
# If splitting fails, just use the string as it is
$t = $q
}
# Convert the hashtable to a JSON string and then back to a custom object
$t | ConvertTo-Json | ConvertFrom-Json
}
}
}
$zz| % {
$path_key, $path = $_.split(" ")
$prop = [ordered]@{
Path = $path
Url = git config -f .gitmodules --get ("$path_key" -replace "\.path",".url")
NonRelative = Join-Path $RepoPath $path
}
return New-Object –TypeName PSObject -Property $prop
}
}
catch{
Throw "$($_.Exception.Message)"
}
}
# \GitSyncSubmoduleWithConfig.ps1
<#
.SYNOPSIS
Synchronizes the submodules with the config file.
.DESCRIPTION
This function synchronizes the submodules with the config file, using the Git-helper and ini-helper modules. The function checks the remote URLs of the submodules and updates them if they are empty or local paths. The function also handles conflicts and errors.
.PARAMETER GitDirPath
The path of the git directory where the config file is located.
.PARAMETER GitRootPath
The path of the git root directory where the submodules are located.
.PARAMETER FlagConfigDecides
A switch parameter that indicates whether to use the config file as the source of truth in case of conflicting URLs.
#>
function config-to-gitmodules {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$GitDirPath,
[Parameter(Mandatory = $true)]
[string]
$GitRootPath,
[Parameter(Mandatory = $false)]
[switch]
$FlagConfigDecides
)
# Import the helper modules
Import-Module Git-helper
Import-Module ini-helper
$configPath = (Join-Path -Path $GitDirPath -ChildPath "config")
# Get the config file content and select the submodule section
$rootKnowledge = Get-IniContent -Path $configPath | Select-Object -Property submodule
# Change the current location to the git root directory
Set-Location -Path $GitRootPath
# Loop through each submodule in the config file
foreach ($rootx in $rootKnowledge) {
try {
# Change the current location to the submodule path
Set-Location -Path $rootx.path
# Get submodule name and path from ini object properties $submoduleName = $submodule.submodule.Name
if(Import-Module PsIni)
{
# Import the PsIni module
$submodules = Get-IniContent -Path ".gitmodules" | Select-Object -Property submodule
}
if(Import-Module PsIni)
{
# Import the PsIni module
$submodulePath = Join-Path -Path (Split-Path -Path ".gitmodules") -ChildPath ($submodule.submodule.path)
}
# Get the remote URL of the submodule
$q = Get-GitRemoteUrl
# Check if the remote URL is a local path or empty
$isPath = Test-Path -Path $q -IsValid
$isEmpty = [string]::IsNullOrEmpty($q)
if ($isPath -or $isEmpty) {
# Set the remote URL to the one in the config file and overwrite it
Set-GitRemote -Url $rootx.url -Overwrite
}
else {
# Check if the URL in the config file is a local path or empty
$isConfigPath = Test-Path -Path $rootx.url -IsValid
$isConfigEmpty = [string]::IsNullOrEmpty($rootx.url)
if ($isConfigEmpty) {
# Append the submodule to the config file
$rootx | Add-IniElement -Path $configPath
}
elseif ($isConfigPath) {
# Append the remote URL to the submodule and replace it in the config file
# ($rootx + AppendProperty($q)) | Set-IniElement -Path $configPath -OldElement $rootx
}
elseif ($rootx.url -notin $q.url) {
# Handle conflicting URLs
if ($FlagConfigDecides) {
# Use the config file as the source of truth and replace it in the submodule path
$rootx | Set-IniElement -Path (Join-Path -Path $rootx.path -ChildPath ".gitmodules") -OldElement $q
}
else {
# Throw an error for conflicting URLs
throw "Conflicting URLs: $($rootx.url) and $($q.url)"
}
}
}
}
catch {
# Handle errors based on their messages
switch ($_.Exception.Message) {
"path not existing" {
return "uninitialized"
}
"path existing but no subroot present" {
return "already in index"
}
"path existing, git root existing, but git not recognized" {
return "corrupted"
}
default {
return $_.Exception.Message
}
}
}
}
}
# A function to move a .git Folder into the current directory and remove any gitfiles present
function unearthiffind ()
{
param (
[Parameter(Mandatory=$true)]
[string]$toRepair,
[Parameter(Mandatory=$true)]
[string]$Modules
)
# Get the module folder that matches the name of the parent directory
Get-ChildItem -Path $Modules -Directory | Where-Object { $_.Name -eq $toRepair.Directory.Name } | Select-Object -First 1 | % {
# Move the module folder to replace the .git file
Remove-Item -Path $toRepair -Force
Move-Item -Path $_.FullName -Destination $toRepair -Force
}
}
# A function to check the git status of a folder
function Check-GitStatus ($folder) {
# Change the current directory to the folder
Set-Location $folder.FullName
Write-Output "checking $folder"
if ((Get-ChildItem -force | ?{ $_.name -eq ".git" } ))
{
# Run git status and capture the output
$output = Invoke-Git "git status"
if(($output -like "fatal*"))
{
Write-Output "fatal status for $folder"
#UnabosrbeOrRmWorktree $folder
}
else
{
Write-Output @($output)[0]
}
}
else
{
Write-Output "$folder not yet initialized"
}
}
# Define a function to remove the worktree from a config file
function Remove-Worktree {
param(
[string]$ConfigPath # The path of the config file
)
if(Test-Package "Get-IniContent")
{
# Get the content of the config file as an ini object
$iniContent = Get-IniContent -FilePath $ConfigPath
# Remove the worktree property from the core section
$iniContent.core.Remove("worktree")
# Write the ini object back to the config file
$iniContent | Out-IniFile -FilePath $ConfigPath -Force
}
else
{
# Read the config file content as an array of lines
$configLines = Get-Content -Path $ConfigPath
# Filter out the lines that contain worktree
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
if (($configLines | Where-Object { $_ -match "worktree" }))
{
# Write the new config file content
Set-Content -Path $ConfigPath -Value $newConfigLines -Force
}
}
}
function Remove-WorktreeHere {
param(
[string]$ConfigPath, # The path of the config file
[alias]$folder,$toRepair
)
# Get the path to the git config file
$configFile = Join-Path -Path $toRepair -ChildPath "\config"
# Check if it exists
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $toRepair"
}
else
{
Remove-Worktree -ConfigPath $toRepair
}
}
# A function to repair a corrupted git folder
# param folder: where to look for replacement module to unearth with
function UnabosrbeOrRmWorktree ($folder) {
get-gitUnhide $folder | % {
if( $_ -is [System.IO.FileInfo] )
{
unearthIffind $_ $folder
}
elseif( $_ -is [System.IO.DirectoryInfo] )
{
Remove-WorktreeHere $_
}
else
{
Write-Error "not a .git file or folder: $_"
}
}
}
# A function to repair a fatal git status
function UnabosrbeOrRmWorktree {
param (
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter(Mandatory=$true)]
[string]$Modules
)
# Print a message indicating fatal status
write-verbos "fatal status for $Path, atempting repair"
cd $Path
UnabosrbeOrRmWorktree -folder $Modules
}
function RepairWithQue-N-RepairFolder {
# Synopsis: A script to process files with git status and repair them if needed
# Parameter: Start - The start path to process
# Parameter: Modules - The path to the modules folder
param (
[Parameter(Mandatory=$true)]
[string]$Start,
[Parameter(Mandatory=$true)]
[string]$Modules
)
begin {
Push-Location
# Validate the arguments
Validate-Path -Path $Start
Validate-Path -Path $Modules
# Redirect the standard error output of git commands to the standard output stream
$env:GIT_REDIRECT_STDERR = '2>&1'
Write-Progress -Activity "Processing files" -Status "Starting" -PercentComplete 0
# Create a queue to store the paths
$que = New-Object System.Collections.Queue
# Enqueue the start path
$Start | % { $que.Enqueue($_) }
# Initialize a counter variable
$i = 0;
}
process {
# Loop through the queue until it is empty
do
{
# Increment the counter
$i++;
# Dequeue a path from the queue
$path = $que.Dequeue()
# Change the current directory to the path
Set-Location $path;
# Run git status and capture the output
$output = Check-GitStatus $path
# Check if the output is fatal
if($output -like "fatal*")
{
ActOnError -Path $path -Modules $modules
# Get the subdirectories of the path and enqueue them, excluding any .git folders
if ($continueOnError)
{
$toEnque = Get-ChildItem -Path "$path\*" -Directory -Exclude "*.git*"
}
}
else
{
$toEnque = git-GetSubmodulePathsUrls
}
$toEnque | % { $que.Enqueue($_.FullName) }
# Calculate the percentage of directories processed
$percentComplete = ($i / ($que.count+$i) ) * 100
# Update the progress bar
Write-Progress -Activity "Processing files" -PercentComplete $percentComplete
} while ($que.Count -gt 0)
}
end {
# Restore the original location
Pop-Location
Write-Progress -Activity "Processing files" -Status "Finished" -PercentComplete 100
}
}
function ActOnError {
<#todo
fallback solutions
* if everything fails,
set git dir path to abbsolute value and edit work tree in place
* if comsumption fails,
due to modulefolder exsisting, revert move and trye to use exsisting folder instead,
if this ressults in error, re revert to initial
move inplace module to x prefixed
atempt to consume again
* if no module is provided, utelyse everything to find possible folders
use hamming distance like priorit order
where
1. exact parrentmatch rekative to root
order resukts by total exact
take first precedance
2. predefined patterns taken
and finaly sort rest by hamming
#>
# This script converts a git folder into a submodule and absorbs its git directory
[CmdletBinding()]
param ( # Validate the arguments
$folder = "C:\ProgramData\scoop\persist",
$repairAlternatives = "C:\ProgramData\scoop\persist\.git\modules")
begin {
Get-ChildItem -path B:\GitPs1Module\* -Filter '*.ps1' | % { . $_.FullName }
Validate-Path $repairAlternatives
Validate-Path $folder
Push-Location
$pastE = $error #past error saved for later
$error.Clear()
# Save the previous error action preference
$previousErrorAction = $ErrorActionPreference
$ErrorActionPreference = "Stop"
# Set the environment variable for git error redirection
$env:GIT_REDIRECT_STDERR = '2>&1'
}
process {
# Get the list of folders in $folder # Loop through each folder and run git status
foreach ($f in (git-GetSubmodulePathsUrls)) {
# Change the current directory to the folder
Set-Location $f.FullName
Write-Verbos "checking $f"
if (!(Get-ChildItem -force | ?{ $_.name -eq ".git" } )) { Write-Verbos "$f not yet initialized" }
else {
# Run git status and capture the output
$output = Check-GitStatus $f.FullName
if(!($output -like "fatal*")) {Write-Output @($output)[0] }
else {
Write-Output "fatal status for $f"
$f | Get-ChildItem -force | ?{ $_.name -eq ".git" } | % {
$toRepair = $_
if( $toRepair -is [System.IO.FileInfo] )
{
$repairAlternatives | Get-ChildItem -Directory | ?{ $_.name -eq $toRepair.Directory.Name } | select -First 1 |
% {
# Move the folder to the target folder Move-Folder -Source $GitFolder -Destination (Join-Path $targetFolder 'x.git')
rm $toRepair -force ;
# Move the submodule folder to replace the git folder
Move-Item -Path $_.fullname -Destination $toRepair -force
}
}
else-if( $toRepair -is [System.IO.DirectoryInfo] )
{
# Remove the worktree line from the config file (Get-Content -Path $configFile | Where-Object { ! ($_ -match 'worktree') }) | Set-Content -Path $configFile
Remove-Worktree "$toRepair/config"
}
else
{
Write-Error "not a .git folder: $toRepair"
Write-Error "not a .git file: $toRepair"
}
removeAtPathReadToIndex
}
}
}
}
} end { Pop-Location }
}
<#
.SYNOPSIS
#This script adds git submodules to a working path based on the .gitmodules file
.PARAMETER WorkPath
The working path where the .gitmodules file is located.
.EXAMPLE
GitInitializeBySubmodule -WorkPath 'B:\ToGit\Projectfolder\NewWindows\scoopbucket-1'
#>
# requires functional git repo
# A function to get the submodules recursively for a given repo path
# should return the submodules in reverse order, deepest first, when not providing flag?
function Get-SubmoduleDeep {
param(
[Parameter(Mandatory=$true)]
[string]$RepoPath # The path to the repo
)
begin {
# Validate the repo path
Validate-PathW -Path $RepoPath
# Change the current directory to the repo path
Set-Location $RepoPath
# Initialize an empty array for the result
$result = @()
}
process {
# Run git submodule foreach and capture the output as an array of lines
$list = @(Invoke-Git "submodule foreach --recursive 'git rev-parse --git-dir'")
# Loop through the list and skip the last line (which is "Entering '...'")
foreach ($i in 0.. ($list.count-2)) {
# Check if the index is even, which means it is a relative path line
if ($i % 2 -eq 0)
{
# Create a custom object with the base, relative and gitDir properties and add it to the result array
$result += , [PSCustomObject]@{
base = $RepoPath
relative = $list[$i]
gitDir = $list[$i+1]
}
}
}
}
end {
# Return the result array
$result
}
}
Function Git-InitializeSubmodules {
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
Param(
# File to Create
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_})]
[string]
$RepoPath
)
begin{
# Validate the parameter
# Set the execution policy to bypass for the current process
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
Write-Verbose "[Add Git Submodule from .gitmodules]"
}
process{
# Filter out the custom objects that have a path property and loop through them
Get-SubmoduleDeep | Where-Object {($_.path)} |
%{
$url = $_.url
$path = $_.path
try {
if( New-Item -ItemType dir -Name $path -WhatIf -ErrorAction SilentlyContinue)
{
if($PSCmdlet.ShouldProcess($path,"clone $url -->")){
}
else
{
invoke-git "submodule add $url $path"
}
}
else
{
if($PSCmdlet.ShouldProcess($path,"folder already exsists, will trye to clone $url --> "))
{
}
else
{
Invoke-Git "submodule add -f $url $path"
}
}
# Try to add a git submodule using the path and url properties
}
catch {
Write-Error "Could not add git submodule: $_"
}
}
}
}
<#
.SYNOPSIS
Unabsorbe-ValidGitmodules from a git repository.
.DESCRIPTION
Unabsorbe-ValidGitmodules from a git repository by moving the .git directories from the submodules to the parent repository and updating the configuration.
.PARAMETER Paths
The paths of the submodules to extract. If not specified, all submodules are extracted.
.EXAMPLE
Extract-Submodules
.EXAMPLE
Extract-Submodules "foo" "bar"
[alias]
extract-submodules = "!gitextractsubmodules() { set -e && { if [ 0 -lt \"$#\" ]; then printf \"%s\\n\" \"$@\"; else git ls-files --stage | sed -n \"s/^160000 [a-fA-F0-9]\\+ [0-9]\\+\\s*//p\"; fi; } | { local path && while read -r path; do if [ -f \"${path}/.git\" ]; then local git_dir && git_dir=\"$(git -C \"${path}\" rev-parse --absolute-git-dir)\" && if [ -d \"${git_dir}\" ]; then printf \"%s\t%s\n\" \"${git_dir}\" \"${path}/.git\" && mv --no-target-directory --backup=simple -- \"${git_dir}\" \"${path}/.git\" && git --work-tree=\"${path}\" --git-dir=\"${path}/.git\" config --local --path --unset core.worktree && rm -f -- \"${path}/.git~\" && if 1>&- command -v attrib.exe; then MSYS2_ARG_CONV_EXCL=\"*\" attrib.exe \"+H\" \"/D\" \"${path}/.git\"; fi; fi; fi; done; }; } && gitextractsubmodules"
git extract-submodules [<path>...]
#>
function Unabsorbe-ValidGitmodules {
param (
[string[]]$Paths
)
# get the paths of all submodules if not specified
if (-not $Paths) {
$Paths = Get-SubmoduleDeep
}
# loop through each submodule path
foreach ($Path in $Paths) {
$gg = "$Path/.git"
# check if the submodule has a .git file
if (Test-Path -Path "$gg" -PathType Leaf) {
# get the absolute path of the .git directory
$GitDir = Get-GitDir -Path $Path
# check if the .git directory exists
if (Test-Path -Path $GitDir -PathType Container) {
# display the .git directory and the .git file
Write-Host "$GitDir`t$gg"
# move the .git directory to the submodule path
Move-Item -Path $GitDir -Destination "$gg" -Force -Backup
# unset the core.worktree config for the submodule
Remove-Worktree -ConfigPath "$gg/config"
# remove the backup file if any
Remove-Item -Path "$gg~" -Force -ErrorAction SilentlyContinue
# hide the .git directory on Windows
Hide-GitDir -Path $Path
}
}
}
}
# \GitUpdateSubmodulesAutomatically.ps1
<#
.SYNOPSIS
Updates the submodules of a git repository.
.DESCRIPTION
This function updates the submodules of a git repository, using the PsIni module and the git commands. The function removes any broken submodules, adds any new submodules, syncs the submodule URLs with the .gitmodules file, and pushes the changes to the remote repository.
.PARAMETER RepositoryPath
The path of the git repository where the submodules are located.
.PARAMETER SubmoduleNames
An array of submodule names that will be updated. If not specified, all submodules will be updated.
#>
function Update-Git-Submodules {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$RepositoryPath,
[Parameter(Mandatory = $false)]
[string[]]
$SubmoduleNames
)
# Set the error action preference to stop on any error
$ErrorActionPreference = "Stop"
# Change the current location to the repository path
Set-Location -Path $RepositoryPath
#update .gitmodules
config-to-gitmodules
$submodules = Get-SubmoduleDeep $RepositoryPath
# If submodule names are specified, filter out only those submodules from the array
if ($SubmoduleNames) {
$submodules = $submodules | Where-Object { $_.submodule.Name -in $SubmoduleNames }
}
# Loop through each submodule in the array and update it
foreach ($submodule in $submodules) {
# Get all submodules from the .gitmodules file as an array of objects
$submodulePath = $submodule.path
# Check if submodule directory exists
if (Test-Path -Path $submodulePath) {
# Change current location to submodule directory
Push-Location -Path $submodulePath
# Get submodule URL from git config
$submoduleUrl = Get-GitRemoteUrl
# Check if submodule URL is empty or local path
if ([string]::IsNullOrEmpty($submoduleUrl) -or (Test-Path -Path $submoduleUrl)) {
# Set submodule URL to remote origin URL
$submoduleUrl = (byPath-RepoUrl -Path $submodulePath)
if(!($submoduleUrl))
{
$submoduleUrl = $submodule.url
}
Set-GitRemoteUrl -Url $submoduleUrl
}
# Return to previous location
Pop-Location
# Update submodule recursively
Invoke-Git "submodule update --init --recursive $submodulePath"
}
else {
# Add submodule from remote URL
Invoke-Git "submodule add $(byPath-RepoUrl -Path $submodulePath) $submodulePath"
}
}
# Sync the submodule URLs with the .gitmodules file
Invoke-Git "submodule sync"
# Push the changes to the remote repository
Invoke-Git "push origin master"
}
function Healthy-GetSubmodules {
# Synopsis: A script to get the submodules recursively for a given repo path or a list of repo paths
# Parameter: RepoPaths - The path or paths to the repos
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string[]]$RepoPaths # The path or paths to the repos
)
# A function to validate a path argument
# Call the main function for each repo path in the pipeline
foreach ($RepoPath in $RepoPaths) {
Get-SubmoduleDeep -RepoPath $RepoPath
}
}
# \git_add_submodule.ps1
#Get-Content .\.gitmodules | ? { $_ -match 'url' } | % { ($_ -split "=")[1].trim() }
function git_add_submodule () {
Write-Host "[Add Git Submodule from .gitmodules]" -ForegroundColor Green
Write-Host "... Dump git_add_submodule.temp ..." -ForegroundColor DarkGray
git config -f .gitmodules --get-regexp '^submodule\..*\.path$' > git_add_submodule.temp
Get-content git_add_submodule.temp | ForEach-Object {
try {
$path_key, $path = $_.split(" ")
$url_key = "$path_key" -replace "\.path",".url"
$url= git config -f .gitmodules --get "$url_key"
Write-Host "$url --> $path" -ForegroundColor DarkCyan
Invoke-Git "submodule add $url $path"
} catch {
Write-Host $_.Exception.Message -ForegroundColor Red
continue
}
}
Write-Host "... Remove git_add_submodule.temp ..." -ForegroundColor DarkGray
Remove-Item git_add_submodule.temp
}
#Git-InitializeSubmodules -repoPath 'G:\ToGit\projectFolderBare\scoopbucket-presist'
# \removeAtPathReadToIndex.ps1
function removeAtPathReadToIndex {
param (
[Parameter(Mandatory=$true, HelpMessage=".git, The path of the git folder to convert")]
[ValidateScript({Test-Path $_ -PathType Container ; Resolve-Path -Path $_ -ErrorAction Stop})]
[Alias("GitFolder")][string]$errorus,[Parameter(Mandatory=$true,HelpMessage="subModuleRepoDir, The path of the submodule folder to replace the git folder")]
#can be done with everything and menu
[Parameter(Mandatory=$true,HelpMessage="subModuleDirInsideGit")]
[ValidateScript({Test-Path $_ -PathType Container ; Resolve-Path -Path $_ -ErrorAction Stop})]
[Alias("SubmoduleFolder")][string]$toReplaceWith
)
# Get the config file path from the git folder
$configFile = Join-Path $GitFolder 'config'
# Push the current location and change to the target folder
# Get the target folder, name and parent path from the git folder
Write-Verbos "#---- asFile"
$asFile = ([System.IO.Fileinfo]$errorus.trim('\'))
Write-Verbos $asFile
$targetFolder = $asFile.Directory
$name = $targetFolder.Name
$path = $targetFolder.Parent.FullName
about-Repo #does nothing without -verbos
Push-Location
Set-Location $targetFolder
index-Remove $name $path
# Change to the parent path and get the root of the git repository
# Add and absorb the submodule using the relative path and remote url
$relative = get-Relative $path $targetFolder
Add-AbsorbSubmodule -Ref ( get-Origin) -Relative $relative
# Pop back to the previous location
Pop-Location
# Restore the previous error action preference
$ErrorActionPreference = $previousErrorAction
}
<#
.SYNOPSIS
Removes all files except those of a given name.
.DESCRIPTION
Removes all files except those of a given name from the Git history using filter-branch.
.PARAMETER FileName
The name of the file to keep.
.EXAMPLE
Remove-AllFilesExcept -FileName "readme.md"
#>
function Remove-AllFilesExcept {
param (
[Parameter(Mandatory)]
[string]$FileName
)
git filter-branch --prune-empty -f --index-filter "git ls-tree -r --name-only --full-tree $GIT_COMMIT | grep -v '$FileName' | xargs git rm -r"
}
<#
.SYNOPSIS
Moves a file to a new directory.
.DESCRIPTION
Moves a file from the current directory to a new directory using filter-branch.
.PARAMETER FileName
The name of the file to move.
.PARAMETER NewDir
The name of the new directory.
.EXAMPLE
Move-File -FileName "my-file" -NewDir "new-dir"
#>
function Move-File {
param (
[Parameter(Mandatory)]
[string]$FileName,
[Parameter(Mandatory)]
[string]$NewDir
)
git filter-branch --tree-filter "
if [ -f current-dir/$FileName ]; then
mv current-dir/$FileName $NewDir/
fi" --force HEAD
}
<#
.SYNOPSIS
Moves a directory to a new location.
.DESCRIPTION
Moves a directory to a new location using filter-branch and subdirectory-filter.
.PARAMETER DirName
The name of the directory to move.
.EXAMPLE
Move-Directory -DirName "foo"
#>
function Move-Directory {
param (
[Parameter(Mandatory)]
[string]$DirName
)
set -eux
mkdir -p __github-migrate__
mvplz="if [ -d $DirName ]; then mv $DirName __github-migrate__/; fi;"
git filter-branch -f --tree-filter "$mvplz" HEAD
git filter-branch -f --subdirectory-filter __github-migrate__
}
<#
.SYNOPSIS
Renames all occurrences of a word in file names.
.DESCRIPTION
Renames all occurrences of a word in file names using filter-branch and string replacement.
.PARAMETER OldWord
The word to replace.
.PARAMETER NewWord
The word to use instead.
.EXAMPLE
Rename-WordInFileNames -OldWord "Result" -NewWord "Stat"
#>
function Rename-WordInFileNames {
param (
[Parameter(Mandatory)]
[string]$OldWord,
[Parameter(Mandatory)]
[string]$NewWord
)
git filter-branch --tree-filter '
for file in $(find . ! -path "*.git*" ! -path "*.idea*")
do
if [ "$file" != "${file/$OldWord/$NewWord}" ]
then
mv "$file" "${file/$OldWord/$NewWord}"
fi
done
' --force HEAD
}
<#
.SYNOPSIS
Replaces all occurrences of a word in file contents.
.DESCRIPTION
Replaces all occurrences of a word in file contents using filter-branch and sed.
.PARAMETER OldWord
The word to replace.
.PARAMETER NewWord
The word to use instead.
.EXAMPLE
Replace-WordInFileContents -OldWord "Result" -NewWord "Stat"
#>
function Replace-WordInFileContents {
param (
[Parameter(Mandatory)]
[string]$OldWord,
[Parameter(Mandatory)]
[string]$NewWord
)
git filter-branch --tree-filter '
for file in $(find . -type f ! -path "*.git*" ! -path "*.idea*")
do
sed -i "" -e s/$OldWord/$NewWord/g $file;
done
' --force HEAD
}
<#
.SYNOPSIS
Gets the names of modified files between two commits.
.DESCRIPTION
Gets the names of modified files between two commits using git diff and regex.
.PARAMETER ReferenceCommitId
The commit id of the reference commit.
.EXAMPLE
Get-ModifiedFileNames 65c0ce6a8e041b78c032f5efbdd0fd3ec9bc96f5
#>
function Get-ModifiedFileNames {
param (
[Parameter(Mandatory)]
[string]$ReferenceCommitId
)
$regex = 'diff --git a.|\sb[/]'
git diff --diff-filter=MRC HEAD $ReferenceCommitId | ?{ $_ -match '^diff.*' } | % { $_ -split($regex) }
}
Import-Module PsIni
function gitRemoveWorktree ($configPath)
{
$iniContent = Get-IniContent -FilePath $configPath
$iniContent.core.Remove("worktree") ;
$iniContent | Out-IniFile -FilePath $configPath -Force
}
<#
.SYNOPSIS
Removes lines that match a pattern from a file.
.DESCRIPTION
Removes lines that match a pattern from a file using Get-Content and Set-Content.
.PARAMETER Path
The path of the file to modify.
.PARAMETER Pattern
The pattern to match and remove.
.EXAMPLE
Remove-Lines -Path "D:\Project Shelf\PowerShellProjectFolder\scripts\Modules\Personal\migration\Export-Inst-Choco\.git\config" -Pattern "worktree"
#>
function Remove-Lines {
param (
[Parameter(Mandatory)]
[string]$Path,
[Parameter(Mandatory)]
[string]$Pattern
)
# putting get content in parentheses makes it run as a separate thread and doesn't lock the file further down the pipe
(Get-Content -Path $Path | ? { ! ($_ -match $Pattern) }) | Set-Content -Path $Path
}
function git-root {
$gitrootdir = (git rev-parse --show-toplevel)
if ($gitrootdir) {
Set-Location $gitrootdir
}
}
function git-status ($path)
{
begin
{
# Validate the arguments
if (-not (Test-Path $path)) {
Write-Error "Invalid path: $path"
exit 1
}
Push-Location
# Redirect the standard error output of git commands to the standard output stream
$env:GIT_REDIRECT_STDERR = '2>&1'
function Invoke-Git {
param(
[string]$Command # The git command to run
)
# Run the command and capture the output
$output = Invoke-Expression -Command "git $Command" -ErrorAction Stop
# return the output to the host
$output
# Check the exit code and throw an exception if not zero
if ($LASTEXITCODE -ne 0) {
throw "Git command failed: git $Command"
}
}
}
process {
# Change the current directory to the path
Set-Location $path;
# Define a function to run git commands and check the exit code
# Run git status and capture the output
$output = invoke-git 'status'
# Check if the output is fatal
if($output -like "fatal*")
{
# Print a message indicating fatal status
Write-Output "fatal status for $path"
}
else
{
$path | Add-Member -MemberType NoteProperty -Name GitStatus -Value $output -PassThru
}
}
end {
# Restore the original location
Pop-Location
}
}
function New-TemporaryDirectory {
$parent = [System.IO.Path]::GetTempPath()
[string] $name = [System.Guid]::NewGuid()
$returnString = New-Item -ItemType Directory -Path (Join-Path $parent $name)
$returnString
}
cls
function Show-ProgressV3{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[PSObject[]]$InputObject,
[string]$Activity = "Processing items"
)
[int]$TotItems = $Input.Count
[int]$Count = 0
$Input | foreach {
'inside'+$TotItems
$_
$Count++
[int]$percentComplete = ($Count/$TotItems* 100)
Write-Progress -Activity $Activity -PercentComplete $percentComplete -Status ("Working - " + $percentComplete + "%")
}
}
function enque {
param([Parameter(ValueFromPipeline)][psobject]$InputObjectx)
$output = $prop.name+$InputObjectx.length
$depth++
$InputObjectx | ?{$_.name -ne 'SyncRoot'} | % { $queue.Enqueue($_) }
}
function Resolve-Properties
{
param([Parameter(ValueFromPipeline)][psobject]$InputObject)
begin {
$i = 0
$queue = [System.Collections.Queue]::new() # get a new queue
$toParse = $InputObject.psobject.Properties
}
process {
$toParse | % { $queue.Enqueue($_) }
$i = 0
$depth = 0
$tree = '\----\'
$queue.Count
$output = ''
$iLessThan = 200
while ($queue.Count -gt 0 -and $i -le $iLessThan)
{
$i++; $queue.Count
$itemOfQue = $queue.Dequeue() # get one item off the queue
$prop = $itemOfQue.psobject.Properties
$subValue1 = $prop.value.children
$subValue2 = $prop.children
if( $subValue1.length -gt 0)
{
$subValue1 | enque
}
elseif ( $subValue2.length -gt 0 )
{
$subValue2 | enque
}
else
{
$output = $prop.value
}
$treex = $tree * $depth
$treex + $output
}
}
}
<# Define a custom type for submodule entries
submodule-entry inherents ini,git
property : submodule
type: ini headlineg
property : path
type: relative path string, unix or windows style
property : branch
type: git branch name
#>
class SubmoduleEntry {
# Inherit from ini and git types
[ini]$ini
[git]$git
# Define properties for submodule, path and branch
[string]$submodule
[string]$path
[string]$branch
# Define a constructor that takes an ini headline and a git branch name as parameters
SubmoduleEntry([string]$headline, [string]$branchName) {
# Initialize the ini and git objects
$this.ini = [ini]::new()
$this.git = [git]::new()
# Set the submodule property to the headline
$this.submodule = $headline
# Set the path property to the value of the "path" key in the ini section
$this.path = $this.ini[$headline]["path"]
# Set the branch property to the branch name
$this.branch = $branchName
}
# Define a ToString method that returns a string representation of the object
[string]ToString() {
# Create a string builder object to append strings
$sb = [System.Text.StringBuilder]::new()
# Append the submodule headline in square brackets
$sb.AppendLine("[$this.submodule]")
# Append the url and path properties of the git and ini objects respectively
$sb.AppendLine("url = $($this.git.url)")
$sb.AppendLine("path = $($this.ini[$this.submodule]["path"])")
# Append a blank line for separation
$sb.AppendLine()
# Return the string builder object as a string
return $sb.ToString()
}
}
# Define a function to create a new project
function new-project
createa a temporary folder --pasthrough | cd
git initialize
function New-Project {
# Create a temporary folder and change directory to it
New-Item -ItemType Directory -Path "$env:TEMP\NewProject" | Push-Location
# Initialize a git repository
git init
}
# Define a function to fetch submodules
function Fetch-Submodules {
# Get the submodule entries from the .gitmodules file
$submodules = Get-Content .gitmodules | ConvertFrom-Ini | ForEach-Object {
# Create a SubmoduleEntry object for each ini section
[SubmoduleEntry]::new($_.Name, $_.Value["branch"])
}
# Loop through each submodule entry
foreach ($submodule in $submodules) {
# Write a verbose message with the submodule url
Write-Verbose "Adding submodule $($submodule.ini[$submodule.submodule]["url"])"
# Try to add the submodule with error action stop
try {
git submodule add -f $submodule.ini[$submodule.submodule]["url"] -b $submodule.branch -q
}
catch {
# If an error occurs, write an error message with the url and continue the loop
Write-Error "Failed to add submodule $($submodule.ini[$submodule.submodule]["url"]): $_"
continue
}
# Write a verbose message with the submodule path
Write-Verbose "Fetching submodule $($submodule.path)"
# Try to fetch the submodule with error action stop
try {
git submodule update --init --recursive --remote --quiet $submodule.path
}
catch {
# If an error occurs, write an error message with the path and continue the loop
Write-Error "Failed to fetch submodule $($submodule.path): $_"
continue
}
}
}
# Define a function to append urls to the .gitmodules file
function UrlsTo-GitModule {
param(
[Parameter(Mandatory)]
[string[]]$urls # Validate that urls is not null or empty
)
# If in a git repository, change directory to the git root, else continue
if (git rev-parse --is-inside-work-tree) {
Push-Location (git rev-parse --show-toplevel)
}
# Ensure that the .gitmodules file exists, or create it if not
if (-not (Test-Path .gitmodules)) {
New-Item -ItemType File -Path .gitmodules | Out-Null
}
# Loop through each url in urls
foreach ($url in $urls) {
# Get the repo name from the url by splitting on slashes and taking the last element without the .git extension
$repoName = ($url -split "/")[-1] -replace "\.git$"
# Create a new SubmoduleEntry object with the repo name as the headline and the current branch name as the branch name
$submoduleEntry = [SubmoduleEntry]::new($repoName, (git rev-parse --abbrev-ref HEAD))
# Set the url property of the git object to the url
$submoduleEntry.git.url = $url
# Set the path property of the ini object to submodules/repoName
$submoduleEntry.ini[$repoName]["path"] = "submodules/$repoName"
# Append the string representation of the SubmoduleEntry object to the .gitmodules file
Add-Content -Path .gitmodules -Value $submoduleEntry.ToString()
}
}
# Define a function to find the .gitmodules file
function Find-GitModule {
param(
[string]$file # File is not mandatory and should be validated as a path
)
# If file is not null, set moduleFile to file, else set it to .\.gitmodules by default
if ($file) {
$moduleFile = $file
}
else {
$moduleFile = ".\.gitmodules"
}
# Repeat until moduleFile is found or an error is thrown
do {
# If moduleFile does not exist in the current directory
if (-not (Test-Path $moduleFile)) {
# If the current directory is a git repository and not in the git root
if ((git rev-parse --is-inside-work-tree) -and (-not (git rev-parse --is-inside-git-dir))) {
# Change to the git root and try again
Push-Location (git rev-parse --show-toplevel)
}
else {
# Throw an error and exit the loop
throw "Error: no gitmodule file found"
break
}
}
} while (-not (Test-Path $moduleFile))
# Return the moduleFile path
return $moduleFile
}
# Define a function to convert from ini format to psobject
function ConvertFrom-Ini {
# Read the input as an array of lines
$lines = @($input)
# Initialize an empty array for the output objects
$output = @()
# Initialize an empty hashtable for the current section
$section = @{}
# Loop through each line in lines
foreach ($line in $lines) {
# Trim any leading or trailing whitespace from the line
$line = $line.Trim()
# If the line is empty or starts with a comment character, skip it
if ($line -eq "" -or $line.StartsWith(";") -or $line.StartsWith("#")) {
continue
}
# If the line matches the pattern of an ini section header
if ($line -match "^\[(.+)\]$") {
# If the section hashtable is not empty, create a psobject from it and add it to the output array
if ($section.Count -gt 0) {
$output += [pscustomobject]$section
}
# Clear the section hashtable and set its name property to the matched group in the line
$section.Clear()
$section.Name = $Matches[1]
}
# Else if the line matches the pattern of an ini key-value pair
elseif ($line -match "^([^=]+)=(.+)$") {
# Add the key-value pair to the section hashtable, trimming any whitespace from the key and value
$section[$Matches[1].Trim()] = $Matches[2].Trim()
}
else {
# Write a warning message that the line is invalid and skip it
Write-Warning "Invalid ini line: $line"
continue
}
}
# If the section hashtable is not empty, create a psobject from it and add it to the output array
if ($section.Count -gt 0) {
$output += [pscustomobject]$section
}
# Return the output array
return $output
}
# Define a function to convert from psobject to ini format
function ConvertTo-Ini {
# Read the input as an array of objects
$objects = @($input)
# Initialize an empty array for the output lines
$output = @()
# Loop through each object in objects
foreach ($object in $objects) {
# Add a line with the object name as an ini section header to the output array
$output += "[$($object.Name)]"
# Loop through each property of the object, excluding the name property
foreach ($property in $object.PSObject.Properties | Where-Object {$_.Name -ne "Name"}) {
# Add a line with the property name and value as an ini key-value pair to the output array
$output += "$($property.Name) = $($property.Value)"
}
# Add an empty line to separate sections in the output array
$output += ""
}
# Return the output array as a single string joined by newlines
return ($output -join "`n")
}
# Define a function to set branch names in submodule entries in the .gitmodules file
function Set-Branch-GitModule {
param(
[string]$file, # File is not mandatory and should be validated as a path
[string]$filterPath, # FilterPath is not mandatory
[string]$filterUrl, # FilterUrl is not mandatory
[string]$filterBranch, # FilterBranch is not mandatory
[Parameter(Mandatory)]
[string]$newBranch # NewBranch is mandatory, should be validated as non null or empty and as a valid git branch name
)
# Find the .gitmodules file and store its path in moduleFile
$moduleFile = Find-GitModule -file $file
# Convert the .gitmodules file content to an array of psobjects and store it in psobject
$psobject = Get-Content -Path $moduleFile | ConvertFrom-Ini
# Initialize a flag to indicate whether any update was done or not
$updated = $false
# Loop through each entry in psobject
foreach ($entry in $psobject) {
# Check if the entry matches the filter criteria
if (
($null -eq $filterPath -or $entry.path -match $filterPath) -and
($null -eq $filterUrl -or $entry.url -match $filterUrl) -and
($null -eq $filterBranch -or $entry.branch -match $filterBranch)
) {
# Set the branch property of the entry to newBranch
$entry.branch = $newBranch
# Set the updated flag to true
$updated = $true
# If verbose, print the updated entry with the new branch property
if ($VerbosePreference -eq "Continue") {
Write-Verbose "$($entry.ToString())branch = $newBranch"
}
}
}
# If verbose and no update was done, print a message
if ($VerbosePreference -eq "Continue" -and -not $updated) {
Write-Verbose "No module entry updated"
}
# Convert the psobject array back to ini format and overwrite the .gitmodules file with error action stop
$psobject | ConvertTo-Ini | Out-File -FilePath $moduleFile -ErrorAction Stop
}
 # A function to run git commands and check the exit code
function Invoke-Git {
param(
[Parameter(Mandatory=$true)]
[string]$Command # The git command to run
)
# Run the command and capture the output
$output = Invoke-Expression -Command "git $Command 2>&1" -ErrorAction Stop
# return the output to the host
$output
# Check the exit code and throw an exception if not zero
# if ($LASTEXITCODE -ne 0) {
# throw "Git command failed: git $Command"
# }
}
function Check-Value { [CmdletBinding()] param (
# Validate that the ref name is not null or empty
[ValidateNotNullOrEmpty()]
[string]$refName
)
# Define a regex pattern to match the bad ref output
$regex = 'bad ref (.*) [\(]'
# Invoke the git show-ref command and redirect the error output to the standard output
$srefs= (Invoke-git 'show-ref')
if( $srefs.count -gt 1 )
{
$d = $srefs[0] -match $regex
$srefs = $srefs[0]
}
else {
$d = $srefs -match $regex
}
# Check if the output matches the regex pattern
if ($d) {
# Get the matched group value
try {
$matchedRef = (($srefs.toString() -split "\(")[0] -split "bad ref ")[1].trim()
}
catch {
Write-Output $output
}
if ($matchedRef)
{
# Check if the matched ref is the same as the ref name parameter
if ($matchedRef -eq $refName) {
# Write an error message to indicate that the ref name does match the bad ref output
Write-Error "The ref name $refName does match the bad ref output: $output"
}
else {
# Construct a message to update the ref to HEAD
$message = "Updating reference $matchedRef to HEAD"
# Write the message to the output
Write-Output $message
# Invoke the git update-ref command to update the ref to HEAD
git update-ref $matchedRef HEAD
# Recursively call the function with the new last value
Check-Value -refName $matchedRef
}
}
}
else {
# Write an error message to indicate that the git show-ref command did not produce a bad ref output
Write-Error "The git show-ref command did not produce a bad ref output: $output"
}
}

# Define a function that takes a list of paths as input and returns an array of objects with their status (error message or tracking status)
<#
.SYNOPSIS
Takes a list of paths as input and returns an array of objects with their status (error message or tracking status).
.DESCRIPTION
This function takes a list of paths as input and checks if they are valid git repositories.
It also checks if they are tracked by any other path in the list as a normal part of repository or as a submodule using the Test-GitTracking function.
It returns an array of objects with the path and status (error message or tracking status) as properties.
.PARAMETER Paths
The list of paths to check.
.EXAMPLE
Get-NonTrackedPaths -Paths @("C:\path1", "C:\path2", "C:\path3", "C:\path4")
This example takes a list of four paths and returns an array of objects with their status (error message or tracking status).
#>
function Get-NonTrackedPaths {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string[]]$Paths # The list of paths to check
)
# Initialize an empty array to store the non-tracked paths
$NonTrackedPaths = @()
# Initialize a queue to store the paths to be checked
$PathQueue = New-Object System.Collections.Queue
# Sort the paths alphabetically and enqueue them
foreach ($Path in $Paths | Sort-Object) {
$PathQueue.Enqueue($Path)
}
# Initialize an empty array to store the output objects
$OutputObjects = @()
# Loop through the queue until it is empty
while ($PathQueue.Count -gt 0) {
# Dequeue the first path from the queue
$Path = $PathQueue.Dequeue()
# Create an output object with the path as a property
$OutputObject = New-Object PSObject -Property @{Path = $Path}
# Check if the path is a valid git repository using the Test-GitRepository function
if (Test-GitRepository -Path $Path) {
# Assume the path is not tracked by any other path
$IsTracked = $false
# Filter the remaining paths in the queue to only include those that are relative to the current path or vice versa
$FilteredPaths = @($PathQueue | Where-Object {($_.StartsWith($Path) -or $Path.StartsWith($_))})
# Loop through the filtered paths with a progress bar
$j = 0
foreach ($OtherPath in $FilteredPaths) {
# Update the progress bar for the inner loop
$j++
Write-Progress -Activity "Checking other paths" -Status "Processing other path $j of $($FilteredPaths.Count)" -PercentComplete ($j / $FilteredPaths.Count * 100) -Id 1
# Check if the other path is a valid git repository using the Test-GitRepository function
if (Test-GitRepository -Path $OtherPath) {
# Check if the current path is tracked by the other path using the Test-GitTracking function
try {
if (Test-GitTracking -Path $Path -OtherPath $OtherPath) {
# Set the flag to indicate the current path is tracked by the other path
$IsTracked = $true
# Add a status property to the output object of the current path with the tracking status
Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name Status -Value "Tracked by $($OtherPath)"
# Break the inner loop
break
}
}
catch {
# Print the error as a verbose message and continue
Write-Verbose $_.Exception.Message
# Remove the error prone repo from the queue
$d = ([System.Collections.ArrayList]@($PathQueue | Where-Object {$_ -ne $OtherPath}))
$PathQueue = New-Object System.Collections.Queue
foreach ($Path in $d) {
$PathQueue.Enqueue($Path)
}
# Add a status property to the output object of the other path with the error message
foreach ($OutputObject in $OutputObjects) {
if ($OutputObject.Path -eq $OtherPath) {
Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name Status -Value $_.Exception.Message
}
}
continue
}
}
}
# If the flag is still false, add the current path to the non-tracked paths array and set its status as untracked
if (-not $IsTracked) {
$NonTrackedPaths += $Path
Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name Status -Value "Untracked" -ErrorAction SilentlyContinue
}
}
else {
# Add a status property to the output object of the current path with an error message
Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name Status -Value "Invalid git repository"
}
# Add the output object to the output objects array
$OutputObjects += $OutputObject
}
$OutputObjects
}
# Define a function to run git commands and check the exit code
<#
.SYNOPSIS
Runs a git command and checks the exit code.
.DESCRIPTION
This function runs a git command using Invoke-Expression and captures the output.
It returns the output to the host and prints a verbose message if the exit code is not zero.
It also prints the output to verbose if the verbose flag is set.
.PARAMETER Command
The git command to run.
.PARAMETER Verbose
The flag to indicate whether to print the output to verbose or not.
.EXAMPLE
Invoke-Git -Command "status --porcelain --untracked-files=no" -Verbose $true
This example runs the git status command with some options and returns and prints the output to verbose.
#>
function Invoke-Git {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Command, # The git command to run
[Parameter(Mandatory=$false)]
[bool]$Verbose # The flag to indicate whether to print the output to verbose or not
)
# Run the command and capture the output
$output = Invoke-Expression -Command "git $Command" -ErrorAction Stop
# return the output to the host
$output
# Check the exit code and print a verbose message if not zero
if ($LASTEXITCODE -ne 0) {
Write-Verbose "Git command failed: git $Command"
}
# Print the output to verbose if the flag is set
if ($Verbose) {
Write-Verbose $output
}
}
# Define a function to check if a path is a valid git repository
<#
.SYNOPSIS
Checks if a path is a valid git repository.
.DESCRIPTION
This function checks if a path is a valid directory and contains a .git folder.
It returns $true if both conditions are met, otherwise it returns $false.
.PARAMETER Path
The path to check.
.EXAMPLE
Test-GitRepository -Path "C:\path1"
This example checks if C:\path1 is a valid git repository and returns $true or $false.
#>
function Test-GitRepository {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Path # The path to check
)
# Check if the path is a valid directory and contains a .git folder
if (Test-Path -Path $Path -PathType Container -ErrorAction SilentlyContinue) {
if (Test-Path -Path "$Path\.git" -ErrorAction SilentlyContinue) {
return $true
}
}
return $false
}
# Define a function to check if a path is tracked by another path as a normal part of repository or as a submodule
<#
.SYNOPSIS
Checks if a path is tracked by another path as a normal part of repository or as a submodule.
.DESCRIPTION
This function changes the current location to another path and invokes the git status command using the Invoke-Git function.
It checks if the output contains the path as a normal part of repository or as a submodule using regular expression matching.
It returns $true if the path is tracked, otherwise it returns $false.
It also restores the original location after checking.
.PARAMETER Path
The path to check.
.PARAMETER OtherPath
The other path to compare with.
.EXAMPLE
Test-GitTracking -Path "C:\path1" -OtherPath "C:\path2"
This example checks if C:\path1 is tracked by C:\path2 as a normal part of repository or as a submodule and returns $true or $false.
#>
function Test-GitTracking {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Path, # The path to check
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$OtherPath # The other path to compare with
)
# Change the current location to the other path
Push-Location -Path $OtherPath
# Invoke git status command using the Invoke-Git function and capture the output
try {
$GitStatus = Invoke-Git -Command "status --porcelain --untracked-files=no" -verbos
}
catch {
# Print the error as a verbose message and rethrow it
Write-Verbose $_.Exception.Message
throw $_.Exception.Message
}
# Restore the original location
Pop-Location
# Check if the output contains the path as a normal part of repository or as a submodule using regular expression matching
if ($GitStatus -match [regex]::Escape($Path)) {
return $true
}
return $false
}
# Example usage: pass a list of paths as input and get the non-tracked paths as output
$Paths = Get-Clipboard | % { $_ | Split-Path -Parent }
$NonTrackedPaths = Get-NonTrackedPaths -Paths @($Paths)
$NonTrackedPaths | format-table
# Define the list of paths to .git files
$paths = @(Get-Clipboard)
# Define an empty array to store the output
$output = @()
# Define a progress counter
$progress = 0
# For each path, go to the parent folder, check git status, and return status or error
foreach ($path in $paths) {
# Get the parent folder of the .git file
$parentFolder = Split-Path $path -Parent
# Change the current location to the parent folder
Set-Location $parentFolder
# Try to check the git status and capture the output or error
try {
# Wrap the git call with invoke-expression and 2>&1 to redirect stderr to stdout
$status = Invoke-Expression "git status 2>&1"
$errorX = $null
}
catch {
$status = $null
$errorX = $_.Exception.Message
}
# Create a custom object with the path and the status or error
$object = [PSCustomObject]@{
Path = $path
Status = if ($status) { $status } else { $errorX }
}
# Add the object to the output array
$output += $object
# Increment the progress counter
$progress++
# Write a progress bar with the current percentage and path
Write-Progress -Activity "Checking git status" -Status "$path" -PercentComplete ($progress / $paths.Count * 100)
}
# Group the output by status and then list the paths of each group
$output | ForEach-Object {
# If status is an object, convert it to string
# Get the first two words of status
$words = $_.Status.ToString().Split()
$firstTwoWords = $words[0..1] -join " "
# Add a new property to store the first two words of status
$_ | Add-Member -MemberType NoteProperty -Name FirstTwoWords -Value $firstTwoWords
} | Group-Object -Property FirstTwoWords | ForEach-Object {
# Write a separator line
Write-Host (“-” * 80)
# Write the group name (first two words of status)
Write-Host $_.Name
# Write the paths of the group members
$_.Group | Select-Object -ExpandProperty Path | Write-Host
}
<#
This code is a PowerShell script that checks the status of git repositories in a given folder and repairs
them if they are corrupted. It does the following steps:
It defines a begin block that runs once before processing any input. In this block, it sets some variables
for the modules and folder paths, validates them, and redirects the standard error output of git commands
to the standard output stream.
It defines a process block that runs for each input object. In this block, it loops through each subfolder
in the folder path and runs git status on it. If the output is fatal, it means the repository is corrupted
and needs to be repaired. To do that, it moves the corresponding module folder from the modules path to the
subfolder, replacing the existing .git file or folder. Then, it reads the config file of the repository and
removes any line that contains worktree, which is a setting that can cause problems with scoop. It prints
the output of each step to the console.
It defines an end block that runs once after processing all input. In this block, it restores the original
location of the script.#>
function fix-CorruptedGitModules ($folder = "C:\ProgramData\scoop\persist", $modules = "C:\ProgramData\scoop\persist\.git\modules")
{
begin {
Push-Location
# Validate the arguments
if (-not (Test-Path $modules)) {
Write-Error "Invalid modules path: $modules"
exit 1
}
if (-not (Test-Path $folder)) {
Write-Error "Invalid folder path: $folder"
exit 1
}
$env:GIT_REDIRECT_STDERR = '2>&1'
}
process
{
# Get the list of folders in $folder
$folders = Get-ChildItem -Path $folder -Directory
# Loop through each folder and run git status
foreach ($f in $folders) {
# Change the current directory to the folder
Set-Location $f.FullName
Write-Output "checking $f"
if ((Get-ChildItem -force | ?{ $_.name -eq ".git" } ))
{
# Run git status and capture the output
$output = git status
if(($output -like "fatal*"))
{
Write-Output "fatal status for $f"
$f | Get-ChildItem -force | ?{ $_.name -eq ".git" } | % {
$toRepair = $_
if( $toRepair -is [System.IO.FileInfo] )
{
$modules | Get-ChildItem -Directory | ?{ $_.name -eq $toRepair.Directory.Name } | select -First 1 | % {
# Move the folder to the target folder
rm $toRepair -force ; Move-Item -Path $_.fullname -Destination $toRepair -force }
}
else
{
Write-Error "not a .git file: $toRepair"
}
if( $toRepair -is [System.IO.DirectoryInfo] )
{
# Get the path to the git config file
$configFile = Join-Path -Path $toRepair -ChildPath "\config"
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $toRepair"
}
else
{
# Read the config file content as an array of lines
$configLines = Get-Content -Path $configFile
# Filter out the lines that contain worktree
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
if (($configLines | Where-Object { $_ -match "worktree" }))
{
# Write the new config file content
Set-Content -Path $configFile -Value $newConfigLines -Force
}
}
}
else
{
Write-Error "not a .git folder: $toRepair"
}
}
}
else
{
Write-Output @($output)[0]
}
}
else
{
Write-Output "$f not yet initialized"
}
}
}
end
{
Pop-Location
}
}

# Define a function that moves the module folder to the repository path, replacing the .git file
function Move-ModuleFolder {
param (
[System.IO.FileInfo]$GitFile,
[string]$ModulesPath
)
# Get the corresponding module folder from the modules path
$moduleFolder = Get-ChildItem -Path $ModulesPath -Directory | Where-Object { $_.Name -eq $GitFile.Directory.Name } | Select-Object -First 1
# Move the module folder to the repository path, replacing the .git file
Remove-Item -Path $GitFile -Force
Move-Item -Path $moduleFolder.FullName -Destination $GitFile -Force
}
# Define a function that removes the worktree lines from the git config file
function Remove-WorktreeLines {
param (
[System.IO.DirectoryInfo]$GitFolder
)
# Get the path to the git config file
$configFile = Join-Path -Path $GitFolder -ChildPath "\config"
# Check if the config file exists
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $GitFolder"
}
else {
# Read the config file content as an array of lines
$configLines = Get-Content -Path $configFile
# Filter out the lines that contain worktree, which is a setting that can cause problems with scoop
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
# Check if there are any lines that contain worktree
if ($configLines | Where-Object { $_ -match "worktree" }) {
# Write the new config file content, removing the worktree lines
Set-Content -Path $configFile -Value $newConfigLines -Force
}
}
}
# Define a function that checks the status of a git repository and repairs it if needed
function Repair-ScoopGitRepository {
param (
[string]$RepositoryPath,
[string]$ModulesPath
)
# Change the current directory to the repository path
Set-Location $RepositoryPath
# Run git status and capture the output
$output = git status
# Check if the output is fatal, meaning the repository is corrupted
if ($output -like "fatal*") {
Write-Output "fatal status for $RepositoryPath"
# Get the .git file or folder in the repository path
$toRepair = Get-ChildItem -Path $RepositoryPath -Force | Where-Object { $_.Name -eq ".git" }
# Check if the .git item is a file
if ($toRepair -is [System.IO.FileInfo]) {
Move-ModuleFolder -GitFile $toRepair -ModulesPath $ModulesPath
}
else {
Write-Error "not a .git file: $toRepair"
}
# Check if the .git item is a folder
if ($toRepair -is [System.IO.DirectoryInfo]) {
Remove-WorktreeLines -GitFolder $toRepair
}
else {
Write-Error "not a .git folder: $toRepair"
}
}
else {
Write-Output @($output)[0]
}
}
# Define a function that validates the paths, sets the error redirection, and repairs the git repositories in the given folder
function Repair-ScoopGit {
param (
# Validate that the modules path exists
[ValidateScript({Test-Path $_})]
[string]$ModulesPath,
# Validate that the folder path exists
[ValidateScript({Test-Path $_})]
[string]$FolderPath
)
# Redirect the standard error output of git commands to the standard output stream
$env:GIT_REDIRECT_STDERR = '2>&1'
# Get the list of subfolders in the folder path
$subfolders = Get-ChildItem -Path $FolderPath -Directory
# Loop through each subfolder and repair its git repository
foreach ($subfolder in $subfolders) {
Write-Output "checking $subfolder"
# Check if the subfolder has a .git file or folder
if (Get-ChildItem -Path $subfolder.FullName -Force | Where-Object { $_.Name -eq ".git" }) {
Repair-ScoopGitRepository -RepositoryPath $subfolder.FullName -ModulesPath $ModulesPath
}
else {
Write-Output "$subfolder not yet initialized"
}
}
}
# Call the main function with the modules and folder paths as arguments
Initialize-ScoopGitRepair -ModulesPath "C:\ProgramData\scoop\persist\.git\modules" -FolderPath "C:\ProgramData\scoop\persist"
Repair-ScoopGitRepositories -FolderPath "C:\ProgramData\scoop\persist" -ModulesPath "C:\ProgramData\scoop\persist\.git\modules"
# A function to validate a path argument
function Validate-Path {
param (
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter(Mandatory=$true)]
[string]$Name
)
if (-not (Test-Path $Path)) {
Write-Error "Invalid $Name path: $Path"
exit 1
}
}
# A function to repair a fatal git status
function Repair-Git {
param (
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter(Mandatory=$true)]
[string]$Modules
)
# Print a message indicating fatal status
Write-Output "fatal status for $Path"
# Get the .git file or folder in that path
$toRepair = Get-ChildItem -Path "$Path\*" -Force | Where-Object { $_.Name -eq ".git" }
# Check if it is a file or a folder
if( $toRepair -is [System.IO.FileInfo] )
{
# Get the module folder that matches the name of the parent directory
$module = Get-ChildItem -Path $Modules -Directory | Where-Object { $_.Name -eq $toRepair.Directory.Name } | Select-Object -First 1
# Move the module folder to replace the .git file
Remove-Item -Path $toRepair -Force
Move-Item -Path $module.FullName -Destination $toRepair -Force
}
elseif( $toRepair -is [System.IO.DirectoryInfo] )
{
# Get the path to the git config file
$configFile = Join-Path -Path $toRepair -ChildPath "\config"
# Check if it exists
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $toRepair"
}
else
{
# Read the config file content as an array of lines
$configLines = Get-Content -Path $configFile
# Filter out the lines that contain worktree
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
# Check if there are any lines to remove
if (($configLines | Where-Object { $_ -match "worktree" }))
{
# Write the new config file content
Set-Content -Path $configFile -Value $newConfigLines -Force
}
}
}
else
{
# Print an error message if it is not a file or a folder
Write-Error "not a .git file or folder: $toRepair"
}
}
# A function to process files with git status and repair them if needed
function Process-Files {
param (
[Parameter(Mandatory=$true)]
[string]$Start,
[Parameter(Mandatory=$true)]
[string]$Modules
)
begin {
Push-Location
# Validate the arguments
Validate-Path -Path $Start -Name "start"
Validate-Path -Path $Modules -Name "modules"
# Redirect the standard error output of git commands to the standard output stream
$env:GIT_REDIRECT_STDERR = '2>&1'
Write-Progress -Activity "Processing files" -Status "Starting" -PercentComplete 0
# Create a queue to store the paths
$que = New-Object System.Collections.Queue
# Enqueue the start path
$Start | % { $que.Enqueue($_) }
# Initialize a counter variable
$i = 0;
}
process {
# Loop through the queue until it is empty
do
{
# Increment the counter
$i++;
# Dequeue a path from the queue
$path = $que.Dequeue()
# Change the current directory to the path
Set-Location $path;
# Run git status and capture the output
$output = git status
# Check if the output is fatal
if($output -like "fatal*")
{
Repair-Git -Path $path -Modules $modules
}
else
{
# Get the subdirectories of the path and enqueue them, excluding any .git folders
Get-ChildItem -Path "$path\*" -Directory -Exclude "*.git*" | % { $que.Enqueue($_.FullName) }
}
# Calculate the percentage of directories processed
$percentComplete = ($i / ($que.count+$i) ) * 100
# Update the progress bar
Write-Progress -Activity "Processing files" -PercentComplete $percentComplete
} while ($que.Count -gt 0)
}
end {
# Restore the original location
Pop-Location
Write-Progress -Activity "Processing files" -Status "Finished" -PercentComplete 100
}
}
# Synopsis: A script to process files with git status and repair them if needed
# Parameter: Start - The start path to process
# Parameter: Modules - The path to the modules folder
param (
[Parameter(Mandatory=$true)]
[string]$Start,
[Parameter(Mandatory=$true)]
[string]$Modules
)
# Call the main function
Process-Files -Start $Start -Modules $Modules
$env:GIT_REDIRECT_STDERR = '2>&1'
<#
This code is a PowerShell function that checks the status of git
repositories in a given directory and its subdirectories.
It uses a queue data structure to store the paths of the
directories and loops through them until the queue is empty.
It displays the git status of each directory and skips the
ones that are not git repositories. It also shows a progress
bar with the percentage of directories processed.#>
function checkPathTree($start)
{
begin
{
Write-Progress -Activity "Processing files" -Status "Starting" -PercentComplete 0
$que = New-Object System.Collections.Queue
$start | % { $que.Enqueue($_) }
$i = 0;
}
process {
do
{
$i++;
$path = $que.Dequeue()
cd $path;
$st = (git status).toString() -join ";"
$object = [PSCustomObject]@{ index = $i ; queCount = $que.count ; path = $path ; procent = ($i / ($que.count+$i) ) }
$object | Add-Member -MemberType NoteProperty -Name gitStatus -Value $st
if($st -like "fatal*")
{
try {
($object | Select-Object -Property quecount, path, gitStatus | format-table )
}
catch {}
}
if($i -lt 5 -or !($st -like "fatal*"))
{
Get-ChildItem -Path "$path\*" -Directory -Exclude "*.git*" | % { $que.Enqueue($_.FullName) }
}
$percentComplete = ($i / ($que.count+$i) ) * 100
Write-Progress -Activity "Processing files" -PercentComplete $percentComplete
} while ($que.Count -gt 0)
}
end {
Write-Progress -Activity "Processing files" -Status "Finished" -PercentComplete 100
}
}
checkPathTree "B:\toGit"
# This function creates a queue of directories to process
function createQueue($start) {
$que = New-Object System.Collections.Queue
$start | % { $que.Enqueue($_) }
return $que
}
# This function changes the current directory to a given path and returns the git status as a string
function getGitStatus($path) {
cd $path
$st = (git status).toString() -join ";"
return $st
}
# This function creates a custom object with the index, queue count, path, percentage and git status of a directory
function createObject($i, $que, $path, $st) {
$object = [PSCustomObject]@{ index = $i ; queCount = $que.count ; path = $path ; procent = ($i / ($que.count+$i) ) }
$object | Add-Member -MemberType NoteProperty -Name gitStatus -Value $st
return $object
}
# This function displays the custom object if the git status is fatal (meaning not a git repository)
function displayObject($object) {
if($object.gitStatus -like "fatal*")
{
try {
($object | Select-Object -Property quecount, path, gitStatus | format-table )
}
catch {}
}
}
# This function adds the subdirectories of a given path to the queue if the git status is not fatal and the index is less than 5
function addSubdirectories($i, $que, $path, $st) {
if($i -lt 5 -or !($st -like "fatal*"))
{
Get-ChildItem -Path "$path\*" -Directory -Exclude "*.git*" | % { $que.Enqueue($_.FullName) }
}
}
# This function updates the progress bar with the percentage of directories processed
function updateProgress($i, $que) {
$percentComplete = ($i / ($que.count+$i) ) * 100
Write-Progress -Activity "Processing files" -PercentComplete $percentComplete
}
# This function checks the status of git repositories in a given directory and its subdirectories using the above functions
function checkPathTree($start)
{
begin
{
Write-Progress -Activity "Processing files" -Status "Starting" -PercentComplete 0
# Create a queue of directories to process
$que = createQueue($start)
$i = 0;
}
process {
do
{
$i++;
# Dequeue a path from the queue
$path = $que.Dequeue()
# Get the git status of the path
$st = getGitStatus($path)
# Create a custom object with the relevant information
$object = createObject($i, $que, $path, $st)
# Display the object if the git status is fatal
displayObject($object)
# Add the subdirectories of the path to the queue if the git status is not fatal and the index is less than 5
addSubdirectories($i, $que, $path, $st)
# Update the progress bar with the percentage of directories processed
updateProgress($i, $que)
} while ($que.Count -gt 0)
}
end {
Write-Progress -Activity "Processing files" -Status "Finished" -PercentComplete 100
}
}
# Define the path to search for .git files
$path = "B:\PF\"
# Define the path to the Everything command line tool
$es = "C:\ProgramData\scoop\shims\es.exe"
$f = "fatal"
# Get the list of .git files using Everything
$git_files = & $es -p $path -s -regex "[.]git$"
# Define two array variables to store the files by their path type
$container_files = @()
$non_container_files = @()
# Define a function to extract the path to the git repository from the content of a .git file
function Get-RepoPath {
<#
.SYNOPSIS
Extracts the path to the git repository from the content of a .git file.
.DESCRIPTION
This function takes the content of a .git file as a parameter and returns the path to the git repository.
It assumes that the content starts with "gitdir:" followed by the relative or absolute path to the repository.
If the path is relative, it converts it to an absolute path using System.IO.Path.GetFullPath method.
.PARAMETER Content
The content of a .git file as a string.
.EXAMPLE
PS C:\> Get-RepoPath "gitdir: ../.git/modules/project1"
C:\Users\user\.git\modules\project1
.EXAMPLE
PS C:\> Get-RepoPath "gitdir: C:\Users\user\Documents\project2\.git"
C:\Users\user\Documents\project2\.git
#>
# Validate the parameter
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Content
)
# Define a regular expression to match the gitdir prefix
$regex = "^gitdir:"
# Check if the content matches the regex
if ($Content -match $regex) {
# Extract the path to the git repository
$rp = ($Content -replace "$regex\s*")
# Check if the rp is not empty
if ($rp) {
# Check if the rp is a valid path
try {
# Try to get the full path of the rp
$repo_path = [System.IO.Path]::GetFullPath($rp)
}
catch {
# Catch any exception and throw an error
throw "Invalid path. The path '$rp' is not a valid path."
}
}
else {
# Throw an error if the rp is empty
throw "Empty path. The content must contain a non-empty path after 'gitdir:'."
}
# Return the repo_path
if((Get-RepoName $repo_path) -like "*.git")
{
# Get the parent folder name as the repo name using Split-Path cmdlet
$repo_path = Split-Path $repo_path -Parent
}
return $repo_path
}
else {
# The content does not match the regex, throw an error
throw "Invalid content. The content must start with 'gitdir:' followed by the path to the git repository."
}
}
# Define a function to get the repo name from the repo path
function Get-RepoName {
<#
.SYNOPSIS
Gets the repo name from the repo path.
.DESCRIPTION
This function takes the repo path as a parameter and returns the repo name.
It uses Split-Path cmdlet to get the last part of the repo path as the repo name.
If the repo name is ".git", it uses Split-Path cmdlet again to get the parent folder name as the repo name.
.PARAMETER RepoPath
The repo path as a string.
.EXAMPLE
PS C:\> Get-RepoName "C:\Users\user\.git\modules\project1"
project1
.EXAMPLE
PS C:\> Get-RepoName "C:\Users\user\Documents\project2\.git"
project2
#>
# Validate the parameter
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$RepoPath
)
# Get the last part of the repo path as the repo name using Split-Path cmdlet
$repoName = Split-Path $RepoPath -Leaf
# Check if the repo name is ".git"
if($repoName -like ".git")
{
# Get the parent folder name as the repo name using Split-Path cmdlet
$repoName = Split-Path (Split-Path $RepoPath -Parent) -Leaf
}
# Return the repo name
return $repoName
}
# Define a function to get the content of a .git file
function Get-GitFileContent {
<#
.SYNOPSIS
Gets the content of a .git file.
.DESCRIPTION
This function takes the path of a .git file as a parameter and returns its content as a string.
It uses Get-Content cmdlet to read the file content.
.PARAMETER GitFile
The path of a .git file as a string.
.EXAMPLE
PS C:\> Get-GitFileContent "B:\PF\project1\.git"
gitdir: ../.git/modules/project1
#>
# Validate the parameter
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$GitFile
)
# Check if the GitFile exists and is not a directory
if (Test-Path $GitFile -PathType Leaf) {
# Get the content of the GitFile using Get-Content cmdlet
$content = Get-Content $GitFile
# Return the content
return $content
}
else {
# Throw an error if the GitFile does not exist or is a directory
throw "Invalid file. The GitFile '$GitFile' does not exist or is a directory."
}
}
# Define a function to extract the path to the git repository from the content of a .git file
function Get-RepoPath {
<#
.SYNOPSIS
Extracts the path to the git repository from the content of a .git file.
.DESCRIPTION
This function takes the content of a .git file as a parameter and returns the path to the git repository.
It assumes that the content starts with "gitdir:" followed by the relative or absolute path to the repository.
If the path is relative, it converts it to an absolute path using System.IO.Path.GetFullPath method.
.PARAMETER Content
The content of a .git file as a string.
.EXAMPLE
PS C:\> Get-RepoPath "gitdir: ../.git/modules/project1"
C:\Users\user\.git\modules\project1
.EXAMPLE
PS C:\> Get-RepoPath "gitdir: C:\Users\user\Documents\project2\.git"
C:\Users\user\Documents\project2\.git
#>
# Validate the parameter
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Content
)
# Define a regular expression to match the gitdir prefix
$regex = "^gitdir:"
# Check if the content matches the regex
if ($Content -match $regex) {
# Extract the path to the git repository
$rp = ($Content -replace "$regex\s*")
# Check if the rp is not empty
if ($rp) {
# Check if the rp is a valid path
try {
# Try to get the full path of the rp using System.IO.Path.GetFullPath method
$repo_path = [System.IO.Path]::GetFullPath($rp)
}
catch {
# Catch any exception and throw an error
throw "Invalid path. The path '$rp' is not a valid path."
}
}
else {
# Throw an error if the rp is empty
throw "Empty path. The content must contain a non-empty path after 'gitdir:'."
}
# Return the repo_path
return $repo_path
}
else {
# The content does not match the regex, throw an error
throw "Invalid content. The content must start with 'gitdir:' followed by the path to the git repository."
}
}
# Define a function to get the repo name from the repo path
function Get-RepoName {
<#
.SYNOPSIS
Gets the repo name from the repo path.
.DESCRIPTION
This function takes the repo path as a parameter and returns the repo name.
It uses Split-Path cmdlet to get the last part of the repo path as the repo name.
If the repo name is ".git", it uses Split-Path cmdlet again to get the parent folder name as the repo name.
.PARAMETER RepoPath
The repo path as a string.
.EXAMPLE
PS C:\> Get-RepoName "C:\Users\user\.git\modules\project1"
project1
.EXAMPLE
PS C:\> Get-RepoName "C:\Users\user\Documents\project2\.git"
project2
#>
# Validate the parameter
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$RepoPath
)
# Get the last part of the repo path as the repo name using Split-Path cmdlet
$repoName = Split-Path $RepoPath -Leaf
# Check if the repo name is ".git"
if($repoName -eq ".git")
{
# Get the parent folder name as the repo name using Split-Path cmdlet
$repoName = Split-Path (Split-Path $RepoPath -Parent) -Leaf
}
# Return the repo name
return $repoName
}
# Define a function to get the git status of a repo path
function Get-GitStatus {
<#
.SYNOPSIS
Gets the git status of a repo path.
.DESCRIPTION
This function takes the repo path and the parent folder of the .git file as parameters and returns the git status as a string.
It uses git status command to get the status of the repository and captures its output.
It returns the first two words from the output as the status.
.PARAMETER RepoPath
The repo path as a string.
.PARAMETER ParentFolder
The parent folder of the .git file as a string.
.EXAMPLE
PS C:\> Get-GitStatus "C:\Users\user\.git\modules\project1" "B:\PF\project1"
On branch
.EXAMPLE
PS C:\> Get-GitStatus "C:\Users\user\Documents\project2\.git" "C:\Users\user\Documents\project2"
fatal: not
#>
# Validate the parameters
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$ParentFolder
)
# Navigate to the ParentFolder using Push-Location cmdlet
Push-Location $ParentFolder
# Invoke the git status command and capture its output using Invoke-Expression cmdlet
$status = Invoke-Expression "git status" 2>&1
# Pop back to the original location using Pop-Location cmdlet
Pop-Location
# Capture the first two words from the status output using split and slice methods
$status_words = (($status -join '') -split "\s+")[0..1] -join " "
# Return the status words
return $status_words
}
# Define a function to get the content of a .git file
function Get-GitFileContent {
<#
.SYNOPSIS
Gets the content of a .git file.
.DESCRIPTION
This function takes the path of a .git file as a parameter and returns its content as a string.
It uses Get-Content cmdlet to read the file content.
.PARAMETER GitFile
The path of a .git file as a string.
.EXAMPLE
PS C:\> Get-GitFileContent "B:\PF\project1\.git"
gitdir: ../.git/modules/project1
#>
# Validate the parameter
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$GitFile
)
# Check if the GitFile exists and is not a directory
if (Test-Path $GitFile -PathType Leaf) {
# Get the content of the GitFile using Get-Content cmdlet
$content = Get-Content $GitFile
# Return the content
return $content
}
else {
# Throw an error if the GitFile does not exist or is a directory
throw "Invalid file. The GitFile '$GitFile' does not exist or is a directory."
}
}
# Define a function to add or update the hashtable with the content by repo name
function Add-ContentByRepoName {
<#
.SYNOPSIS
Adds or updates the hashtable with the content by repo name.
.DESCRIPTION
This function takes a result object, a hashtable, and a regex as parameters and adds or updates the hashtable with the content by repo name.
It uses Get-GitFileContent function to get the content of the git file of the result.
It uses Test-Path cmdlet to check if the result is successful or not.
.PARAMETER Result
A result object that contains the properties GitFile, Success, RepoName, and RepoPath.
.PARAMETER Hashtable
A hashtable that stores the content by repo name.
.PARAMETER Regex
A regular expression that matches the success pattern.
.EXAMPLE
PS C:\> Add-ContentByRepoName $result $success_content "fatal"
#>
# Validate the parameters
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[PSCustomObject]$Result,
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[hashtable]$Hashtable,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Regex
)
# Get the repo name of the result
$repoName = $Result.RepoName
# Check if the result is successful or not using Test-Path cmdlet and Regex parameter
if ((Test-Path $Result.RepoPath -PathType Container) -and $Result.Success -notmatch $Regex) {
# Get the content of the git file of the result using Get-GitFileContent function
$content = Get-GitFileContent $Result.GitFile
# Add or update the hashtable with the content by repo name
$Hashtable[$repoName] = $content
}
}
# Define a function to create an object for repairing a failed result
function New-RepairObject {
<#
.SYNOPSIS
Creates an object for repairing a failed result.
.DESCRIPTION
This function takes a result object, a hashtable, and a regex as parameters and creates an object for repairing a failed result.
It uses Get-GitFileContent function to get the content of the git file of the result.
It uses Test-Path cmdlet and ContainsKey method to check if there is a successful content for the same repo name in the hashtable.
.PARAMETER Result
A result object that contains the properties GitFile, Success, RepoName, and RepoPath.
.PARAMETER Hashtable
A hashtable that stores the content by repo name.
.PARAMETER Regex
A regular expression that matches the failure pattern.
.EXAMPLE
PS C:\> New-RepairObject $result $success_content "fatal"
#>
# Validate the parameters
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[PSCustomObject]$Result,
[Parameter(Mandatory=$true)]
[ValidateNotNull()]
[hashtable]$Hashtable,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Regex
)
# Get the repo name of the result
$repoName = $Result.RepoName
# Check if the result is not successful using Test-Path cmdlet and Regex parameter
if (-not (Test-Path $Result.RepoPath -PathType Container) -or $Result.Success -match $Regex) {
# Check if there is a successful content for the same repo name in the hashtable using ContainsKey method
if ($Hashtable.ContainsKey($repoName)) {
# Get the successful content from the hashtable
$content = $Hashtable[$repoName]
# Get the failed content of the git file of the result using Get-GitFileContent function
$failed = Get-GitFileContent $Result.GitFile
# Create an object for repairing the failed result with properties RepoName, toReplace, failed, and GitFile
$repairObject = [PSCustomObject]@{
RepoName = $repoName
toReplace = $content
failed = $failed
GitFile = $Result.GitFile
}
# Return the repair object
return $repairObject
}
}
}
# Loop through the files and check their path type using Test-Path
foreach ($file in $git_files) {
if (Test-Path $file -PathType Container) {
# Add the file to the container array
$container_files += $file
} else {
# Add the file to the non-container array
$non_container_files += $file
}
}
# Display the arrays and their counts
Write-Host "Container files: $($container_files.Count)"
Write-Host "Non-container files: $($non_container_files.Count)"
# Initialize an empty array to store the results
$results = @()
$toRepair = @()
# Initialize a counter variable to track the progress
$counter = 0
# Loop through each .git file using ForEach-Object cmdlet
$non_container_files | ForEach-Object {
# Increment the counter by one
$counter++
# Write a progress message using Write-Progress cmdlet
Write-Progress -Activity "Processing .git files" -Status "Processing file $counter of $($non_container_files.Count)" -PercentComplete ($counter / $non_container_files.Count * 100)
# Get the content of the .git file using Get-GitFileContent function
try {
$content = Get-GitFileContent $_
# Extract the path to the git repository using Get-RepoPath function
try {
$repo_path = Get-RepoPath $content
# Get the repo name from the repo path using Get-RepoName function
try {
$repoName = Get-RepoName $repo_path
# Get the git status of the repo path using Get-GitStatus function
try {
# Check if the repo_path exists and is a directory
if (Test-Path $repo_path -PathType Container) {
# Get the parent folder of the repo_path
$success = Get-GitStatus -ParentFolder ($_ | split-path -parent )
}
else {
$success = "fatal"
}
# Add the result to the array with a success flag and a repo name column
$results += [PSCustomObject]@{
GitFile = $_
Success = $success
RepoName = $repoName
RepoPath = $repo_path
}
}catch {}
}catch {}
}catch {}
}catch {}
}
# Initialize a hashtable to store the successful git file contents by parent folder
$success_content = @{}
# Display a summary of different success states by grouping and counting them using Group-Object cmdlet[^1^][1]
Write-Host "Summary of success states:"
$results | Group-Object -Property Success | Format-Table -Property Name, Count
# Display a summary of different parent folders by grouping and counting them using Group-Object cmdlet[^1^][1]
Write-Host "Summary of parent folders:"
$results | Group-Object -Property RepoName | ?{$_.Count -gt 1 } | Sort-Object -Property Count | Format-Table -Property Name, Count
# Display all results in a table format sorted by parent folder using Sort-Object cmdlet[^2^][2]
Write-Host "All results:"
$results | Sort-Object -Property RepoName,Success,RepoPath,GitFile | Format-Table -AutoSize
# Use the LINQ ToDictionary method to create a hashtable from the success results# Use the LINQ Where method to filter the success results by content
$success_results = [Linq.Enumerable]::Where($results, [Func[object,bool]] { param($r) $r.Success -notmatch $f -and $r.Success -ne "" -and (Get-Content $r.GitFile) -ne "gitdir: " })
$failed_results = [Linq.Enumerable]::Where($results, [Func[object,bool]] { param($r) !($r.Success -notmatch $f -and $r.Success -ne "" -and (Get-Content $r.GitFile) -ne "gitdir: ") })
# Initialize a hashtable to store the successful git file contents by repo name
$success_content = @{}
# Initialize an empty array to store the objects for repairing the failed results
$toRepair = @()
# Loop through each result using ForEach-Object cmdlet
$success_results | ForEach-Object {
# Add or update the hashtable with the content by repo name using Add-ContentByRepoName function
Add-ContentByRepoName $_ $success_content "fatal"
# Create an object for repairing a failed result using New-RepairObject function
$repairObject = New-RepairObject $_ $success_content "fatal"
# Check if the repair object is not null
if ($repairObject) {
# Add the repair object to the array
$toRepair += $repairObject
}
}
# Display the array of repair objects in a table format sorted by repo name, to replace, failed, and git file using Sort-Object and Format-Table cmdlets
$toRepair | Sort-Object -Property RepoName,toReplace,failed,GitFile | Format-Table -AutoSize
# Loop through each result that is not successful
foreach ($resultx in $toRepair) {
# Get the parent folder of the result
$repoName = $resultx.RepoName
# Get the successful content from the hashtable
$content = $resultx.toReplace
# Create a backup of the old git file by appending ".bak" to its name
Copy-Item -Path $resultx.GitFile -Destination "$($resultx.GitFile).bak"
# Set the content of the git file of the result to be the same as the successful one
Set-Content -Path $resultx.GitFile -Value $content
# Update the result to be successful and have a valid repo path
}
# Summarize the results by counting the success and failure cases, excluding fatal errors
$success_count = ($results | Where-Object {$_.Success -notmatch $f}).Count
$failure_count = ($results | Where-Object {$_.Success -match $f}).Count
Write-Host "Out of $($results.Count) .git files, excluding fatal errors, $success_count point to real git repositories and $failure_count do not."
cls
function Show-ProgressV3{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[PSObject[]]$InputObject,
[string]$Activity = "Processing items"
)
[int]$TotItems = $Input.Count
[int]$Count = 0
$Input | foreach {
'inside'+$TotItems
$_
$Count++
[int]$percentComplete = ($Count/$TotItems* 100)
Write-Progress -Activity $Activity -PercentComplete $percentComplete -Status ("Working - " + $percentComplete + "%")
}
}
function enque {
param([Parameter(ValueFromPipeline)][psobject]$InputObjectx)
$output = $prop.name+$InputObjectx.length
$depth++
$InputObjectx | ?{$_.name -ne 'SyncRoot'} | % { $queue.Enqueue($_) }
}
function Resolve-Properties
{
param([Parameter(ValueFromPipeline)][psobject]$InputObject)
begin {
$i = 0
$queue = [System.Collections.Queue]::new() # get a new queue
$toParse = $InputObject.psobject.Properties
}
process {
$toParse | % { $queue.Enqueue($_) }
$i = 0
$depth = 0
$tree = '\----\'
$queue.Count
$output = ''
$iLessThan = 200
while ($queue.Count -gt 0 -and $i -le $iLessThan)
{
$i++; $queue.Count
$itemOfQue = $queue.Dequeue() # get one item off the queue
$prop = $itemOfQue.psobject.Properties
$subValue1 = $prop.value.children
$subValue2 = $prop.children
if( $subValue1.length -gt 0)
{
$subValue1 | enque
}
elseif ( $subValue2.length -gt 0 )
{
$subValue2 | enque
}
else
{
$output = $prop.value
}
$treex = $tree * $depth
$treex + $output
}
}
}
cd 'D:\Project Shelf\NoteTakingProjectFolder\GistNotebook\xMarks'
cd 'B:\ToGit'
Set-Alias -Name 'invoke-everything' -Value "C:\Program Files\Everything\Everything.exe"
everything -help
#Get-Content .\.gitmodules | ? { $_ -match 'url' } | % { ($_ -split "=")[1].trim() }
function prevImplementaton () {
Write-Host "[Add Git Submodule from .gitmodules]" -ForegroundColor Green
Write-Host "... Dump git_add_submodule.temp ..." -ForegroundColor DarkGray
git config -f .gitmodules --get-regexp '^submodule\..*\.path$' > git_add_submodule.temp
Get-content git_add_submodule.temp | ForEach-Object {
try {
$path_key, $path = $_.split(" ")
$url_key = "$path_key" -replace "\.path",".url"
$url= git config -f .gitmodules --get "$url_key"
Write-Host "$url --> $path" -ForegroundColor DarkCyan
git submodule add $url $path
} catch {
Write-Host $_.Exception.Message -ForegroundColor Red
continue
}
}
Write-Host "... Remove git_add_submodule.temp ..." -ForegroundColor DarkGray
Remove-Item git_add_submodule.temp
}
function git-GetSubmodulePathsUrls
{ [CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path -Path "$_\.gitmodules"})]
[string]
$RepoPath
)
try {
(git config -f .gitmodules --get-regexp '^submodule\..*\.path$') |
% {
$path_key, $path = $_.split(" ")
$prop = [ordered]@{
Path = $path
Url = git config -f .gitmodules --get ("$path_key" -replace "\.path",".url")
NonRelative = Join-Path $RepoPath $path
}
return New-Object –TypeName PSObject -Property $prop
}
}
catch{
Throw "$($_.Exception.Message)"
}
}
Function Git-InitializeSubmodules {
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
Param(
# File to Create
[Parameter(Mandatory=$true)]
[string]
$RepoPath
)
begin{
Write-Verbose "[Add Git Submodule from .gitmodules]"
}
process{
git-GetSubmodulePathsUrls $RepoPath | %{ $url = $_.url
$path = $_.path
if( New-Item -ItemType dir -Name $path -WhatIf -ErrorAction SilentlyContinue)
{
if($PSCmdlet.ShouldProcess($path,"clone $url -->")){
}
else
{
git submodule add $url $path
}
}
else
{
if($PSCmdlet.ShouldProcess($path,"folder already exsists, will trye to clone $url --> "))
{
}
else
{
git submodule add $url $path
}
}
}
}
}
Git-InitializeSubmodules -repoPath 'G:\ToGit\projectFolderBare\scoopbucket-presist'
Write-Host "[Add Git Submodule from .gitmodules]" -ForegroundColor Green
Write-Host "... Dump git_add_submodule.temp ..." -ForegroundColor DarkGray
git config -f .gitmodules --get-regexp '^submodule\..*\.path$' > git_add_submodule.temp
Get-content git_add_submodule.temp | ForEach-Object {
try {
$path_key, $path = $_.split(" ")
$url_key = "$path_key" -replace "\.path",".url"
$url= git config -f .gitmodules --get "$url_key"
Write-Host "$url --> $path" -ForegroundColor DarkCyan
git submodule add $url $path
} catch {
Write-Host $_.Exception.Message -ForegroundColor Red
continue
}
}
Write-Host "... Remove git_add_submodule.temp ..." -ForegroundColor DarkGray
Remove-Item git_add_submodule.temp
#Sure, I can try to do that. Here is one possible way to break up the script into smaller functions:
<#
.SYNOPSIS
Clones a repository with a reference to another repository and filters it by file names.
.DESCRIPTION
This script clones a repository with a reference to another repository and filters it by file names, using the CloneWithReference, branch-byPattern and Revert-byPattern functions. The script also uses the git ls-files and git branch commands to get the list of files and branches in the repository.
.PARAMETER Repo
The path of the original repository.
.PARAMETER ClonedRepo
The path of the cloned repository.
.PARAMETER Files
The list of file names to filter by.
#>
param (
[Parameter(Mandatory = $true)]
[string]
$Repo,
[Parameter(Mandatory = $true)]
[string]
$ClonedRepo,
[Parameter(Mandatory = $true)]
[string[]]
$Files
)
# Get the list of file names from a text file
#$files = Get-Clipboard
#$files = $files | select -Unique
# Load the ps1 files from a folder
Get-ChildItem -path B:\GitPs1Module\* -Filter '*.ps1' | % { . $_.FullName }
# Set the uploadpack.allowFilter option for the original repository
cd $Repo; git config --local uploadpack.allowFilter true
# Create a folder to store the filtered repositories
cd $Repo
try {
# Check the status of the original repository
git-status -path $Repo
#git-status -path $repox
# Clone the original repository with a reference to itself
$to = CloneWithReference -repo $Repo -objectRepo $Repo -path $ClonedRepo -ErrorAction Continue
# Change the current directory to the cloned repository
cd $ClonedRepo
Write-Output "---"
}
catch {
Write-Error $_
Write-Error "Failed to clone into $ClonedRepo"
}
# Get the list of files that match "git" in the cloned repository
$files = git ls-files | ? { $_ -match "git" }
cd $ClonedRepo
# Loop through each file name in the list
foreach ($file in $files) {
# Change the current directory to the subfolder
try {
# Create a branch for each file name and filter by it
branch-byPattern -pattern $file -ErrorAction Continue
}
catch {
Write-Error "Failed to Filter for $file"
Write-Error $_
}
}
"----------------after branches created-----------------"
# Get the list of branches in the cloned repository
$branches = Invoke-Expression "git branch"
# Loop through each branch except master
$branches | ? { $_ -ne "master"} | forEAch-object {
$branch = $_ ;
if($branch)
{
try {
# Revert the changes in each branch by its name
Revert-byPattern -pattern $branch -branch $branch -ErrorAction Continue
}
catch {
Write-Error "Failed to Revert for $file"
Write-Error $_
}
}
}
# Return the folder with the filtered repositories
#Write-Output $folder
# Get the list of file names from a text file
function Get-FileNames {
param (
[Parameter(Mandatory=$true)]
[string]$Path
)
# Validate the path parameter
if (-not (Test-Path $Path)) {
throw "Invalid path: $Path"
}
# Return the unique file names from the text file
Get-Content $Path | Select-Object -Unique
}
# Get the path of the original repository
function Get-RepoPath {
param (
[Parameter(Mandatory=$true)]
[string]$Repo
)
# Validate the repo parameter
if (-not (Test-Path $Repo)) {
throw "Invalid repository: $Repo"
}
# Return the full path of the repository
Resolve-Path $Repo
}
# Create a folder to store the filtered repositories
function Create-Folder {
param (
[Parameter(Mandatory=$true)]
[string]$Folder
)
# Validate the folder parameter
if (Test-Path $Folder) {
throw "Folder already exists: $Folder"
}
# Create the folder and return it
New-Item -ItemType Directory -Path $Folder | Out-Null
Write-Output $Folder
}
# Clone a repository with reference to another repository
function CloneWithReference {
param (
[Parameter(Mandatory=$true)]
[string]$Repo,
[Parameter(Mandatory=$true)]
[string]$ObjectRepo,
[Parameter(Mandatory=$true)]
[string]$Path,
[switch]$AllowFilter
)
# Validate the parameters
if (-not (Test-Path $Repo)) {
throw "Invalid repository: $Repo"
}
if (-not (Test-Path $ObjectRepo)) {
throw "Invalid object repository: $ObjectRepo"
}
if (Test-Path $Path) {
throw "Path already exists: $Path"
}
# Set the git config for allow filter if specified
if ($AllowFilter) {
cd $Repo; git config --local uploadpack.allowFilter true
}
# Clone the repository with reference to the object repository and return the path
git clone --reference $ObjectRepo --dissociate $Repo $Path | Out-Null
Write-Output $Path
}
# Create a branch based on a pattern in the file names
function BranchByPattern {
param (
[Parameter(Mandatory=$true)]
[string]$Pattern,
[switch]$Force
)
# Validate the pattern parameter
if (-not ($Pattern)) {
throw "Invalid pattern: $Pattern"
}
# Get the matching file names from the current directory
$files = git ls-files | Where-Object { $_ -match $Pattern }
# Check if there are any matching files
if ($files) {
# Create a branch name based on the pattern
$branch = "branch-$Pattern"
# Check if the branch already exists and force delete it if specified
if (git branch --list $branch) {
if ($Force) {
git branch -D $branch | Out-Null
}
else {
throw "Branch already exists: $branch"
}
}
# Create a new branch and check out to it
git checkout -b $branch | Out-Null
# Filter out the non-matching files from the branch
git ls-files | Where-Object { $_ -notmatch $Pattern } | ForEach-Object { git rm --cached $_ } | Out-Null
# Commit the changes to the branch
git commit -m "Filtered for $Pattern" | Out-Null
# Return the branch name
Write-Output $branch
}
}
# Main script
# Get the list of file names from a text file or clipboard
#$files = Get-FileNames -Path "B:\GitPs1Module\filelist.txt"
$files = Get-Clipboard
$files = $files | Select-Object -Unique
# Get the path of the original repository and its clone destination
$repo = Get-RepoPath -Repo "B:\PF\Archive\ps1"
$clonedRepo = "B:\ps1"
# Create a folder to store the filtered repositories
Create-Folder -Folder "$repo\filtered"
# Clone the repository with reference and allow filter
try {
CloneWithReference -Repo $repo -ObjectRepo $repo -Path $clonedRepo -AllowFilter
cd $clonedRepo
Write-Output "---"
}
catch {
Write-Error $_
Write-Error "Failed to clone into $clonedRepo"
}
# Loop through each file name in the list
foreach ($file in $files) {
# Change the current directory to the subfolder
cd $clonedRepo
try {
# Create a branch based on the file name pattern
BranchByPattern -Pattern $file -Force
}
catch {
Write-Error "Failed to Filter for $file"
Write-Error $_
}
}
# Return the folder with the filtered repositories
Write-Output "$repo\filtered"
<#
This code is a PowerShell script that checks the status of git repositories in a given folder and repairs
them if they are corrupted. It does the following steps:
It defines a begin block that runs once before processing any input. In this block, it sets some variables
for the modules and folder paths, validates them, and redirects the standard error output of git commands
to the standard output stream.
It defines a process block that runs for each input object. In this block, it loops through each subfolder
in the folder path and runs git status on it. If the output is fatal, it means the repository is corrupted
and needs to be repaired. To do that, it moves the corresponding module folder from the modules path to the
subfolder, replacing the existing .git file or folder. Then, it reads the config file of the repository and
removes any line that contains worktree, which is a setting that can cause problems with scoop. It prints
the output of each step to the console.
It defines an end block that runs once after processing all input. In this block, it restores the original
location of the script.#>
function fix-CorruptedGitModules ($folder = "C:\ProgramData\scoop\persist", $modules = "C:\ProgramData\scoop\persist\.git\modules")
{
begin {
Push-Location
# A function to validate the arguments
function Validate-Arguments ($modules, $folder) {
if (-not (Test-Path $modules)) {
Write-Error "Invalid modules path: $modules"
exit 1
}
if (-not (Test-Path $folder)) {
Write-Error "Invalid folder path: $folder"
exit 1
}
}
# Validate the arguments
Validate-Arguments $modules $folder
# Set the environment variable for git error redirection
$env:GIT_REDIRECT_STDERR = '2>&1'
}
process
{
# Get the list of folders in $folder
$folders = Get-ChildItem -Path $folder -Directory
# Loop through each folder and run git status
foreach ($f in $folders) {
# Change the current directory to the folder
Set-Location $f.FullName
Write-Output "checking $f"
if ((Get-ChildItem -force | ?{ $_.name -eq ".git" } ))
{
# Run git status and capture the output
$output = git status
if(($output -like "fatal*"))
{
Write-Output "fatal status for $f"
$f | Get-ChildItem -force | ?{ $_.name -eq ".git" } | % {
$toRepair = $_
if( $toRepair -is [System.IO.FileInfo] )
{
$modules | Get-ChildItem -Directory | ?{ $_.name -eq $toRepair.Directory.Name } | select -First 1 | % {
# Move the folder to the target folder
rm $toRepair -force ; Move-Item -Path $_.fullname -Destination $toRepair -force }
}
else
{
Write-Error "not a .git file: $toRepair"
}
if( $toRepair -is [System.IO.DirectoryInfo] )
{
# Get the path to the git config file
$configFile = Join-Path -Path $toRepair -ChildPath "\config"
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $toRepair"
}
else
{
# Read the config file content as an array of lines
$configLines = Get-Content -Path $configFile
# Filter out the lines that contain worktree
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
if (($configLines | Where-Object { $_ -match "worktree" }))
{
# Write the new config file content
Set-Content -Path $configFile -Value $newConfigLines -Force
}
}
}
else
{
Write-Error "not a .git folder: $toRepair"
}
}
}
else
{
Write-Output @($output)[0]
}
}
else
{
Write-Output "$f not yet initialized"
}
}
}
end
{
Pop-Location
}
}
# A function to check the git status of a folder
function Check-GitStatus ($folder) {
# Change the current directory to the folder
Set-Location $folder.FullName
Write-Output "checking $folder"
if ((Get-ChildItem -force | ?{ $_.name -eq ".git" } ))
{
# Run git status and capture the output
$output = git status
if(($output -like "fatal*"))
{
Write-Output "fatal status for $folder"
Repair-GitFolder $folder
}
else
{
Write-Output @($output)[0]
}
}
else
{
Write-Output "$folder not yet initialized"
}
}
# A function to repair a corrupted git folder
function Repair-GitFolder ($folder) {
$folder | Get-ChildItem -force | ?{ $_.name -eq ".git" } | % {
$toRepair = $_
if( $toRepair -is [System.IO.FileInfo] )
{
Move-GitFile $toRepair
}
elseif( $toRepair -is [System.IO.DirectoryInfo] )
{
Fix-GitConfig $toRepair
}
else
{
Write-Error "not a .git file or folder: $toRepair"
}
}
}
# A function to move a .git file to the corresponding module folder
function Move-GitFile ($file) {
global:$modules | Get-ChildItem -Directory | ?{ $_.name -eq $file.Directory.Name } | select -First 1 | % {
# Move the folder to the target folder
rm $file -force ; Move-Item -Path $_.fullname -Destination $file -force
}
}
# A function to fix the worktree setting in a .git config file
function Fix-GitConfig ($folder) {
# Get the path to the git config file
$configFile = Join-Path -Path $folder -ChildPath "\config"
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $folder"
}
else
{
# Read the config file content as an array of lines
$configLines = Get-Content -Path $configFile
# Filter out the lines that contain worktree
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
if (($configLines | Where-Object { $_ -match "worktree" }))
{
# Write the new config file content
Set-Content -Path $configFile -Value $newConfigLines -Force
}
}
}

# Synopsis: A script to get the submodules recursively for a given repo path or a list of repo paths
# Parameter: RepoPaths - The path or paths to the repos
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string[]]$RepoPaths # The path or paths to the repos
)
# A function to validate a path argument
function Validate-Path {
param (
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter(Mandatory=$true)]
[string]$Name
)
if (-not (Test-Path $Path)) {
Write-Error "Invalid $Name path: $Path"
exit 1
}
}
# A function to run git commands and check the exit code
function Invoke-Git {
param(
[Parameter(Mandatory=$true)]
[string]$Command # The git command to run
)
# Run the command and capture the output
$output = Invoke-Expression -Command "git $Command" -ErrorAction Stop
# return the output to the host
$output
# Check the exit code and throw an exception if not zero
if ($LASTEXITCODE -ne 0) {
throw "Git command failed: git $Command"
}
}
# A function to get the submodules recursively for a given repo path
function Get-SubmodulesRecursive {
param(
[Parameter(Mandatory=$true)]
[string]$RepoPath # The path to the repo
)
begin {
# Validate the repo path
Validate-Path -Path $RepoPath -Name "repo"
# Change the current directory to the repo path
Set-Location $RepoPath
# Initialize an empty array for the result
$result = @()
}
process {
# Run git submodule foreach and capture the output as an array of lines
$list = @(Invoke-Git "submodule foreach --recursive 'git rev-parse --git-dir'")
# Loop through the list and skip the last line (which is "Entering '...'")
foreach ($i in 0.. ($list.count-2)) {
# Check if the index is even, which means it is a relative path line
if ($i % 2 -eq 0)
{
# Create a custom object with the base, relative and gitDir properties and add it to the result array
$result += , [PSCustomObject]@{
base = $RepoPath
relative = $list[$i]
gitDir = $list[$i+1]
}
}
}
}
end {
# Return the result array
$result
}
}
# Call the main function for each repo path in the pipeline
foreach ($RepoPath in $RepoPaths) {
Get-SubmodulesRecursive -RepoPath $RepoPath
}
import-submodule Git-helper
import-submodule ini-helper
#git syncronize submodules with config
$root = get-GitDirPath {git dir path}
$rootKnowledge = get-IniContent($root + '\config') | select submodule
cd get-GitRootDir { git root path }
for each $rootx in $rootKnowledge
try {
cd $rootx.path
$q = get-GitRemoteUrl
$isPath = pathNotUrl($q)
if ($isPath -or $isEmpty)
set-gitRemote($rootx) -overwrite
else
if $isEmpty
$rep | append-Ini($root+'\config')
elseif pathNotUrl($rootx.url)
($rootx+ appendProperty($q)) | replace-iniElement($root+'\config',$rootx)
if $rootx.url not in $q.url
if ( $flagConfigDecides )
$rootx | replace-iniElement($rootx.path,$rep)
else
throw "conflicting url"
}
catch {
if error due to path not exsisting
return "unitinilized"
if error due to path exsisting but no subroot present
return "already in index"
if error due to path exsisting, git root exsisting, but git not recognized
return "corrupted"
}
gitmodules = inifile
subrepo remote
<#
.SYNOPSIS
Converts a gitmodules file into a custom object with submodule properties.
.DESCRIPTION
This function converts a gitmodules file into a custom object with submodule properties, such as url and path. The function uses two helper functions, Split-TextByRegex and keyPairTo-PsCustom, to parse the file and create the object.
.PARAMETER GitmodulesPath
The path of the gitmodules file.
.PARAMETER SubmoduleRegex
The regular expression that matches the submodule sections in the file.
#>
function Convert-Gitmodules {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$GitmodulesPath,
[Parameter(Mandatory = $true)]
[string]
$SubmoduleRegex
)
# Bypass the execution policy for the current process
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# Import the helper functions from their paths
. "Z:\Project Shelf\Archive\ps1\Split-TextByRegex.ps1"
. "Z:\Project Shelf\Archive\ps1\keyPairTo-PsCustom.ps1"
# Change the current location to the directory of the gitmodules file
Set-Location -Path (Split-Path -Path $GitmodulesPath)
# Split the text of the gitmodules file by the submodule regex
$TextRanges = Split-TextByRegex -path $GitmodulesPath -regx $SubmoduleRegex
# Convert each text range into a custom object with key-value pairs
$TextRanges | ForEach-Object { keyPairTo-PsCustom -keyPairStrings $_.values }
}
# A function to validate a path argument
function Validate-Path {
param (
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter(Mandatory=$true)]
[string]$Name
)
if (-not (Test-Path $Path)) {
Write-Error "Invalid $Name path: $Path"
exit 1
}
}
# A function to repair a fatal git status
function Repair-Git {
param (
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter(Mandatory=$true)]
[string]$Modules
)
begin
{
Push-Location
# Validate the arguments
if (-not (Test-Path $modules)) {
Write-Error "Invalid modules path: $modules"
exit 1
}
if (-not (Test-Path $start)) {
Write-Error "Invalid start path: $start"
exit 1
}
# Redirect the standard error output of git commands to the standard output stream
$env:GIT_REDIRECT_STDERR = '2>&1'
Write-Progress -Activity "Processing files" -Status "Starting" -PercentComplete 0
# Create a queue to store the paths
$que = New-Object System.Collections.Queue
# Enqueue the start path
$start | % { $que.Enqueue($_) }
# Initialize a counter variable
$i = 0;
}
process {
# Loop through the queue until it is empty
do
{
# Increment the counter
$i++;
# Dequeue a path from the queue
$path = $que.Dequeue()
# Change the current directory to the path
Set-Location $path;
# Run git status and capture the output
$output = git status
# Check if the output is fatal
if($output -like "fatal*")
{
# Print a message indicating fatal status
Write-Output "fatal status for $Path"
# Get the .git file or folder in that path
$toRepair = Get-ChildItem -Path "$Path\*" -Force | Where-Object { $_.Name -eq ".git" }
# Check if it is a file or a folder
if( $toRepair -is [System.IO.FileInfo] )
{
# Get the module folder that matches the name of the parent directory
$module = Get-ChildItem -Path $Modules -Directory | Where-Object { $_.Name -eq $toRepair.Directory.Name } | Select-Object -First 1
# Move the module folder to replace the .git file
Remove-Item -Path $toRepair -Force
Move-Item -Path $module.FullName -Destination $toRepair -Force
}
elseif( $toRepair -is [System.IO.DirectoryInfo] )
{
# Get the path to the git config file
$configFile = Join-Path -Path $toRepair -ChildPath "\config"
# Check if it exists
if (-not (Test-Path $configFile)) {
Write-Error "Invalid folder path: $toRepair"
}
else
{
# Read the config file content as an array of lines
$configLines = Get-Content -Path $configFile
# Filter out the lines that contain worktree
$newConfigLines = $configLines | Where-Object { $_ -notmatch "worktree" }
# Check if there are any lines to remove
if (($configLines | Where-Object { $_ -match "worktree" }))
{
# Write the new config file content
Set-Content -Path $configFile -Value $newConfigLines -Force
}
}
}
else
{
# Print an error message if it is not a file or a folder
Write-Error "not a .git file or folder: $toRepair"
}
}
# A function to process files with git status and repair them if needed
function Process-Files {
param (
[Parameter(Mandatory=$true)]
[string]$Start,
[Parameter(Mandatory=$true)]
[string]$Modules
)
begin {
Push-Location
# Validate the arguments
Validate-Path -Path $Start -Name "start"
Validate-Path -Path $Modules -Name "modules"
# Redirect the standard error output of git commands to the standard output stream
$env:GIT_REDIRECT_STDERR = '2>&1'
Write-Progress -Activity "Processing files" -Status "Starting" -PercentComplete 0
# Create a queue to store the paths
$que = New-Object System.Collections.Queue
# Enqueue the start path
$Start | % { $que.Enqueue($_) }
# Initialize a counter variable
$i = 0;
}
process {
# Loop through the queue until it is empty
do
{
# Increment the counter
$i++;
# Dequeue a path from the queue
$path = $que.Dequeue()
# Change the current directory to the path
Set-Location $path;
# Run git status and capture the output
$output = git status
# Check if the output is fatal
if($output -like "fatal*")
{
Repair-Git -Path $path -Modules $modules
}
else
{
# Get the subdirectories of the path and enqueue them, excluding any .git folders
Get-ChildItem -Path "$path\*" -Directory -Exclude "*.git*" | % { $que.Enqueue($_.FullName) }
}
# Calculate the percentage of directories processed
$percentComplete = ($i / ($que.count+$i) ) * 100
# Update the progress bar
Write-Progress -Activity "Processing files" -PercentComplete $percentComplete
} while ($que.Count -gt 0)
}
end {
# Restore the original location
Pop-Location
# Complete the progress bar
Write-Progress -Activity "Processing files" -Status "Finished" -PercentComplete 100
}
}
# Synopsis: A script to process files with git status and repair them if needed
# Parameter: Start - The start path to process
# Parameter: Modules - The path to the modules folder
param (
[Parameter(Mandatory=$true)]
[string]$Start,
[Parameter(Mandatory=$true)]
[string]$Modules
)
# Call the main function
Process-Files -Start $Start -Modules $Modules
function ActOnError {
<#todo
fallback solutions
* if everything fails,
set git dir path to abbsolute value and edit work tree in place
* if comsumption fails,
due to modulefolder exsisting, revert move and trye to use exsisting folder instead,
if this ressults in error, re revert to initial
move inplace module to x prefixed
atempt to consume again
* if no module is provided, utelyse everything to find possible folders
use hamming distance like priorit order
where
1. exact parrentmatch rekative to root
order resukts by total exact
take first precedance
2. predefined patterns taken
and finaly sort rest by hamming
#>
# This script converts a git folder into a submodule and absorbs its git directory
[CmdletBinding()]
param ( # Validate the arguments
$folder = "C:\ProgramData\scoop\persist",
$repairAlternatives = "C:\ProgramData\scoop\persist\.git\modules")
begin {
Get-ChildItem -path B:\GitPs1Module\* -Filter '*.ps1' | % { . $_.FullName }
Validate-Path $repairAlternatives
Validate-Path $folder
Push-Location
$pastE = $error #past error saved for later
$error.Clear()
# Save the previous error action preference
$previousErrorAction = $ErrorActionPreference
$ErrorActionPreference = "Stop"
# Set the environment variable for git error redirection
$env:GIT_REDIRECT_STDERR = '2>&1'
}
process {
# Get the list of folders in $folder # Loop through each folder and run git status
foreach ($f in (git-GetSubmodulePathsUrls)) {
# Change the current directory to the folder
Set-Location $f.FullName
Write-Verbos "checking $f"
if (!(Get-ChildItem -force | ?{ $_.name -eq ".git" } )) { Write-Verbos "$f not yet initialized" }
else {
# Run git status and capture the output
$output = Check-GitStatus $f.FullName
if(!($output -like "fatal*")) {Write-Output @($output)[0] }
else {
Write-Output "fatal status for $f"
$f | Get-ChildItem -force | ?{ $_.name -eq ".git" } | % {
$toRepair = $_
if( $toRepair -is [System.IO.FileInfo] )
{
$repairAlternatives | Get-ChildItem -Directory | ?{ $_.name -eq $toRepair.Directory.Name } | select -First 1 |
% {
# Move the folder to the target folder Move-Folder -Source $GitFolder -Destination (Join-Path $targetFolder 'x.git')
rm $toRepair -force ;
# Move the submodule folder to replace the git folder
Move-Item -Path $_.fullname -Destination $toRepair -force
}
}
else-if( $toRepair -is [System.IO.DirectoryInfo] )
{
# Remove the worktree line from the config file (Get-Content -Path $configFile | Where-Object { ! ($_ -match 'worktree') }) | Set-Content -Path $configFile
Remove-Worktree "$toRepair/config"
}
else
{
Write-Error "not a .git folder: $toRepair"
Write-Error "not a .git file: $toRepair"
}
removeAtPathReadToIndex
}
}
}
}
} end { Pop-Location }
}
# Import the module or script that contains the function to test
Import-Module .\ActOnError.ps1
# Use the Describe block to group your tests
Describe 'ActOnError' {
# Use the BeforeAll block to define some variables and mocks that are common for all tests
BeforeAll {
# Define a valid folder path that contains a git folder
$validFolderPath = "C:\t\Planets"
# Define a valid repair alternatives path that contains a .git\modules folder
$validRepairPath = "C:\t\Planets\.git\modules"
# Define an invalid folder path that does not contain a git folder
$invalidFolderPath = "C:\t\Invalid"
# Define an invalid repair alternatives path that does not contain a .git\modules folder
$invalidRepairPath = "C:\t\Invalid\.git\modules"
# Mock the Git-helper module to return a predefined output for the valid folder path and throw an exception for any other path
Mock Git-helper {
param($folder)
if ($folder -eq $validFolderPath) {
return @(
[pscustomobject]@{
Name = 'Mercury'
FullName = 'C:\t\Planets\Mercury'
},
[pscustomobject]@{
Name = 'Venus'
FullName = 'C:\t\Planets\Venus'
},
[pscustomobject]@{
Name = 'Earth'
FullName = 'C:\t\Planets\Earth'
}
)
} else {
throw "path not existing"
}
}
# Mock the ini-helper module to return a predefined output for the valid folder path and throw an exception for any other path
Mock ini-helper {
param($folder)
if ($folder -eq $validFolderPath) {
return @(
[pscustomobject]@{
Name = 'Mercury'
FullName = 'C:\t\Planets\Mercury'
},
[pscustomobject]@{
Name = 'Venus'
FullName = 'C:\t\Planets\Venus'
},
[pscustomobject]@{
Name = 'Earth'
FullName = 'C:\t\Planets\Earth'
}
)
} else {
throw "path not existing"
}
}
# Mock the Check-GitStatus function to return a predefined output for the valid folder path and throw an exception for any other path
Mock Check-GitStatus {
param($folder)
if ($folder -eq $validFolderPath) {
return @(
[pscustomobject]@{
Name = 'Mercury'
Status = 'On branch master'
},
[pscustomobject]@{
Name = 'Venus'
Status = 'On branch master'
},
[pscustomobject]@{
Name = 'Earth'
Status = 'On branch master'
}
)
} else {
throw "fatal: not in a git directory"
}
}
# Mock the Remove-Worktree function to do nothing
Mock Remove-Worktree {}
}
# Use the It block to write individual tests
It 'Given valid folder and repair paths, it converts the git folder into a submodule and absorbs its git directory' {
# Call the function and store the output
$output = ActOnError -folder $validFolderPath -repairAlternatives $validRepairPath
# Use the Should keyword to verify the expected behavior
$output | Should -Not -BeNullOrEmpty # Check that the output is not null or empty
$output | Should -HaveCount 3 # Check that the output has three elements
$output | Should -BeExactly @('On branch master', 'On branch master', 'On branch master') # Check that the output matches the expected output
}
It 'Given invalid folder or repair paths, it throws an error' {
# Call the function and catch the error
try {
ActOnError -folder $invalidFolderPath -repairAlternatives $invalidRepairPath
$errord = $null
}
catch {
$errord = $_
}
# Use the Should keyword to verify the expected behavior
$errord | Should -Not -BeNullOrEmpty # Check that the error is not null or empty
$errord | Should -Match "path not existing" # Check that the error message matches the expected one
}
}
<#
.SYNOPSIS
#This script adds git submodules to a working path based on the .gitmodules file
.PARAMETER WorkPath
The working path where the .gitmodules file is located.
.EXAMPLE
GitInitializeBySubmodule -WorkPath 'B:\ToGit\Projectfolder\NewWindows\scoopbucket-1'
#>
# requires functional git repo
# A function to get the submodules recursively for a given repo path
# should return the submodules in reverse order, deepest first, when not providing flag?
function Get-SubmoduleDeep {
param(
[Parameter(Mandatory=$true)]
[string]$RepoPath # The path to the repo
)
begin {
# Validate the repo path
Validate-PathW -Path $RepoPath
# Change the current directory to the repo path
Set-Location $RepoPath
# Initialize an empty array for the result
$result = @()
}
process {
# Run git submodule foreach and capture the output as an array of lines
$list = @(Invoke-Git "submodule foreach --recursive 'git rev-parse --git-dir'")
# Loop through the list and skip the last line (which is "Entering '...'")
foreach ($i in 0.. ($list.count-2)) {
# Check if the index is even, which means it is a relative path line
if ($i % 2 -eq 0)
{
# Create a custom object with the base, relative and gitDir properties and add it to the result array
$result += , [PSCustomObject]@{
base = $RepoPath
relative = $list[$i]
gitDir = $list[$i+1]
}
}
}
}
end {
# Return the result array
$result
}
}
Function Git-InitializeSubmodules {
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
Param(
# File to Create
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_})]
[string]
$RepoPath
)
begin{
# Validate the parameter
# Set the execution policy to bypass for the current process
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
Write-Verbose "[Add Git Submodule from .gitmodules]"
}
process{
# Filter out the custom objects that have a path property and loop through them
Get-SubmoduleDeep | Where-Object {($_.path)} |
%{
$url = $_.url
$path = $_.path
try {
if( New-Item -ItemType dir -Name $path -WhatIf -ErrorAction SilentlyContinue)
{
if($PSCmdlet.ShouldProcess($path,"clone $url -->")){
}
else
{
invoke-git "submodule add $url $path"
}
}
else
{
if($PSCmdlet.ShouldProcess($path,"folder already exsists, will trye to clone $url --> "))
{
}
else
{
Invoke-Git "submodule add -f $url $path"
}
}
# Try to add a git submodule using the path and url properties
}
catch {
Write-Error "Could not add git submodule: $_"
}
}
}
}
<#
.SYNOPSIS
Unabsorbe-ValidGitmodules from a git repository.
.DESCRIPTION
Unabsorbe-ValidGitmodules from a git repository by moving the .git directories from the submodules to the parent repository and updating the configuration.
.PARAMETER Paths
The paths of the submodules to extract. If not specified, all submodules are extracted.
.EXAMPLE
Extract-Submodules
.EXAMPLE
Extract-Submodules "foo" "bar"
[alias]
extract-submodules = "!gitextractsubmodules() { set -e && { if [ 0 -lt \"$#\" ]; then printf \"%s\\n\" \"$@\"; else git ls-files --stage | sed -n \"s/^160000 [a-fA-F0-9]\\+ [0-9]\\+\\s*//p\"; fi; } | { local path && while read -r path; do if [ -f \"${path}/.git\" ]; then local git_dir && git_dir=\"$(git -C \"${path}\" rev-parse --absolute-git-dir)\" && if [ -d \"${git_dir}\" ]; then printf \"%s\t%s\n\" \"${git_dir}\" \"${path}/.git\" && mv --no-target-directory --backup=simple -- \"${git_dir}\" \"${path}/.git\" && git --work-tree=\"${path}\" --git-dir=\"${path}/.git\" config --local --path --unset core.worktree && rm -f -- \"${path}/.git~\" && if 1>&- command -v attrib.exe; then MSYS2_ARG_CONV_EXCL=\"*\" attrib.exe \"+H\" \"/D\" \"${path}/.git\"; fi; fi; fi; done; }; } && gitextractsubmodules"
git extract-submodules [<path>...]
#>
function Unabsorbe-ValidGitmodules {
param (
[string[]]$Paths
)
# get the paths of all submodules if not specified
if (-not $Paths) {
$Paths = Get-SubmoduleDeep
}
# loop through each submodule path
foreach ($Path in $Paths) {
$gg = "$Path/.git"
# check if the submodule has a .git file
if (Test-Path -Path "$gg" -PathType Leaf) {
# get the absolute path of the .git directory
$GitDir = Get-GitDir -Path $Path
# check if the .git directory exists
if (Test-Path -Path $GitDir -PathType Container) {
# display the .git directory and the .git file
Write-Host "$GitDir`t$gg"
# move the .git directory to the submodule path
Move-Item -Path $GitDir -Destination "$gg" -Force -Backup
# unset the core.worktree config for the submodule
Remove-Worktree -ConfigPath "$gg/config"
# remove the backup file if any
Remove-Item -Path "$gg~" -Force -ErrorAction SilentlyContinue
# hide the .git directory on Windows
Hide-GitDir -Path $Path
}
}
}
}
# \GitUpdateSubmodulesAutomatically.ps1
<#
.SYNOPSIS
Updates the submodules of a git repository.
.DESCRIPTION
This function updates the submodules of a git repository, using the PsIni module and the git commands. The function removes any broken submodules, adds any new submodules, syncs the submodule URLs with the .gitmodules file, and pushes the changes to the remote repository.
.PARAMETER RepositoryPath
The path of the git repository where the submodules are located.
.PARAMETER SubmoduleNames
An array of submodule names that will be updated. If not specified, all submodules will be updated.
#>
function Update-Git-Submodules {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$RepositoryPath,
[Parameter(Mandatory = $false)]
[string[]]
$SubmoduleNames
)
# Set the error action preference to stop on any error
$ErrorActionPreference = "Stop"
# Change the current location to the repository path
Set-Location -Path $RepositoryPath
#update .gitmodules
config-to-gitmodules
$submodules = Get-SubmoduleDeep $RepositoryPath
# If submodule names are specified, filter out only those submodules from the array
if ($SubmoduleNames) {
$submodules = $submodules | Where-Object { $_.submodule.Name -in $SubmoduleNames }
}
# Loop through each submodule in the array and update it
foreach ($submodule in $submodules) {
# Get all submodules from the .gitmodules file as an array of objects
$submodulePath = $submodule.path
# Check if submodule directory exists
if (Test-Path -Path $submodulePath) {
# Change current location to submodule directory
Push-Location -Path $submodulePath
# Get submodule URL from git config
$submoduleUrl = Get-GitRemoteUrl
# Check if submodule URL is empty or local path
if ([string]::IsNullOrEmpty($submoduleUrl) -or (Test-Path -Path $submoduleUrl)) {
# Set submodule URL to remote origin URL
$submoduleUrl = (byPath-RepoUrl -Path $submodulePath)
if(!($submoduleUrl))
{
$submoduleUrl = $submodule.url
}
Set-GitRemoteUrl -Url $submoduleUrl
}
# Return to previous location
Pop-Location
# Update submodule recursively
Invoke-Git "submodule update --init --recursive $submodulePath"
}
else {
# Add submodule from remote URL
Invoke-Git "submodule add $(byPath-RepoUrl -Path $submodulePath) $submodulePath"
}
}
# Sync the submodule URLs with the .gitmodules file
Invoke-Git "submodule sync"
# Push the changes to the remote repository
Invoke-Git "push origin master"
}
function Healthy-GetSubmodules {
# Synopsis: A script to get the submodules recursively for a given repo path or a list of repo paths
# Parameter: RepoPaths - The path or paths to the repos
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string[]]$RepoPaths # The path or paths to the repos
)
# A function to validate a path argument
# Call the main function for each repo path in the pipeline
foreach ($RepoPath in $RepoPaths) {
Get-SubmoduleDeep -RepoPath $RepoPath
}
}
# \git_add_submodule.ps1
#Get-Content .\.gitmodules | ? { $_ -match 'url' } | % { ($_ -split "=")[1].trim() }
function git_add_submodule () {
Write-Host "[Add Git Submodule from .gitmodules]" -ForegroundColor Green
Write-Host "... Dump git_add_submodule.temp ..." -ForegroundColor DarkGray
git config -f .gitmodules --get-regexp '^submodule\..*\.path$' > git_add_submodule.temp
Get-content git_add_submodule.temp | ForEach-Object {
try {
$path_key, $path = $_.split(" ")
$url_key = "$path_key" -replace "\.path",".url"
$url= git config -f .gitmodules --get "$url_key"
Write-Host "$url --> $path" -ForegroundColor DarkCyan
Invoke-Git "submodule add $url $path"
} catch {
Write-Host $_.Exception.Message -ForegroundColor Red
continue
}
}
Write-Host "... Remove git_add_submodule.temp ..." -ForegroundColor DarkGray
Remove-Item git_add_submodule.temp
}
#Git-InitializeSubmodules -repoPath 'G:\ToGit\projectFolderBare\scoopbucket-presist'
# \removeAtPathReadToIndex.ps1
function removeAtPathReadToIndex {
param (
[Parameter(Mandatory=$true, HelpMessage=".git, The path of the git folder to convert")]
[ValidateScript({Test-Path $_ -PathType Container ; Resolve-Path -Path $_ -ErrorAction Stop})]
[Alias("GitFolder")][string]$errorus,[Parameter(Mandatory=$true,HelpMessage="subModuleRepoDir, The path of the submodule folder to replace the git folder")]
#can be done with everything and menu
[Parameter(Mandatory=$true,HelpMessage="subModuleDirInsideGit")]
[ValidateScript({Test-Path $_ -PathType Container ; Resolve-Path -Path $_ -ErrorAction Stop})]
[Alias("SubmoduleFolder")][string]$toReplaceWith
)
# Get the config file path from the git folder
$configFile = Join-Path $GitFolder 'config'
# Push the current location and change to the target folder
# Get the target folder, name and parent path from the git folder
Write-Verbos "#---- asFile"
$asFile = ([System.IO.Fileinfo]$errorus.trim('\'))
Write-Verbos $asFile
$targetFolder = $asFile.Directory
$name = $targetFolder.Name
$path = $targetFolder.Parent.FullName
about-Repo #does nothing without -verbos
Push-Location
Set-Location $targetFolder
index-Remove $name $path
# Change to the parent path and get the root of the git repository
# Add and absorb the submodule using the relative path and remote url
$relative = get-Relative $path $targetFolder
Add-AbsorbSubmodule -Ref ( get-Origin) -Relative $relative
# Pop back to the previous location
Pop-Location
# Restore the previous error action preference
$ErrorActionPreference = $previousErrorAction
}
<#
.SYNOPSIS
Synchronizes the submodules with the config file.
.DESCRIPTION
This function synchronizes the submodules with the config file, using the Git-helper and ini-helper modules. The function checks the remote URLs of the submodules and updates them if they are empty or local paths. The function also handles conflicts and errors.
.PARAMETER GitDirPath
The path of the git directory where the config file is located.
.PARAMETER GitRootPath
The path of the git root directory where the submodules are located.
.PARAMETER FlagConfigDecides
A switch parameter that indicates whether to use the config file as the source of truth in case of conflicting URLs.
#>
function config-to-gitmodules {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$GitDirPath,
[Parameter(Mandatory = $true)]
[string]
$GitRootPath,
[Parameter(Mandatory = $false)]
[switch]
$FlagConfigDecides
)
# Import the helper modules
Import-Module Git-helper
Import-Module ini-helper
$configPath = (Join-Path -Path $GitDirPath -ChildPath "config")
# Get the config file content and select the submodule section
$rootKnowledge = Get-IniContent -Path $configPath | Select-Object -Property submodule
# Change the current location to the git root directory
Set-Location -Path $GitRootPath
# Loop through each submodule in the config file
foreach ($rootx in $rootKnowledge) {
try {
# Change the current location to the submodule path
Set-Location -Path $rootx.path
# Get submodule name and path from ini object properties $submoduleName = $submodule.submodule.Name
if(Import-Module PsIni)
{
# Import the PsIni module
$submodules = Get-IniContent -Path ".gitmodules" | Select-Object -Property submodule
}
if(Import-Module PsIni)
{
# Import the PsIni module
$submodulePath = Join-Path -Path (Split-Path -Path ".gitmodules") -ChildPath ($submodule.submodule.path)
}
# Get the remote URL of the submodule
$q = Get-GitRemoteUrl
# Check if the remote URL is a local path or empty
$isPath = Test-Path -Path $q -IsValid
$isEmpty = [string]::IsNullOrEmpty($q)
if ($isPath -or $isEmpty) {
# Set the remote URL to the one in the config file and overwrite it
Set-GitRemote -Url $rootx.url -Overwrite
}
else {
# Check if the URL in the config file is a local path or empty
$isConfigPath = Test-Path -Path $rootx.url -IsValid
$isConfigEmpty = [string]::IsNullOrEmpty($rootx.url)
if ($isConfigEmpty) {
# Append the submodule to the config file
$rootx | Add-IniElement -Path $configPath
}
elseif ($isConfigPath) {
# Append the remote URL to the submodule and replace it in the config file
# ($rootx + AppendProperty($q)) | Set-IniElement -Path $configPath -OldElement $rootx
}
elseif ($rootx.url -notin $q.url) {
# Handle conflicting URLs
if ($FlagConfigDecides) {
# Use the config file as the source of truth and replace it in the submodule path
$rootx | Set-IniElement -Path (Join-Path -Path $rootx.path -ChildPath ".gitmodules") -OldElement $q
}
else {
# Throw an error for conflicting URLs
throw "Conflicting URLs: $($rootx.url) and $($q.url)"
}
}
}
}
catch {
# Handle errors based on their messages
switch ($_.Exception.Message) {
"path not existing" {
return "uninitialized"
}
"path existing but no subroot present" {
return "already in index"
}
"path existing, git root existing, but git not recognized" {
return "corrupted"
}
default {
return $_.Exception.Message
}
}
}
}
}
# Import the module or script that contains the function to test
. $PSScriptRoot\config-to-gitmodules.ps1
# Use the Describe block to group your tests
Describe 'config-to-gitmodules' {
# Use the BeforeAll block to define some variables and mocks that are common for all tests
BeforeAll {
# Define a valid git directory path that contains a config file
$validGitDirPath = "C:\t\Planets\.git"
# Define a valid git root directory path that contains a .gitmodules file
$validGitRootPath = "C:\t\Planets"
# Define an invalid git directory path that does not contain a config file
$invalidGitDirPath = "C:\t\Invalid\.git"
# Define an invalid git root directory path that does not contain a .gitmodules file
$invalidGitRootPath = "C:\t\Invalid"
# Define an expected output for the valid git directory and root paths
$expectedOutput = @(
[pscustomobject]@{
Path = "Mercury"
Url = "https://github.com/planets/Mercury.git"
NonRelative = "C:\t\Planets\Mercury"
},
[pscustomobject]@{
Path = "Venus"
Url = "https://github.com/planets/Venus.git"
NonRelative = "C:\t\Planets\Venus"
},
[pscustomobject]@{
Path = "Earth"
Url = "https://github.com/planets/Earth.git"
NonRelative = "C:\t\Planets\Earth"
}
)
# Mock the Git-helper module to return a predefined output for the valid git directory and root paths and throw an exception for any other paths
Mock Git-helper {
param($GitDirPath, $GitRootPath)
if ($GitDirPath -eq $validGitDirPath -and $GitRootPath -eq $validGitRootPath) {
return @(
[pscustomobject]@{
submodule.Mercury.path = "Mercury"
submodule.Mercury.url = "https://github.com/planets/Mercury.git"
},
[pscustomobject]@{
submodule.Venus.path = "Venus"
submodule.Venus.url = "https://github.com/planets/Venus.git"
},
[pscustomobject]@{
submodule.Earth.path = "Earth"
submodule.Earth.url = "https://github.com/planets/Earth.git"
}
)
} else {
throw "Invalid git directory or root path"
}
}
# Mock the ini-helper module to return a predefined output for the valid git directory and root paths and throw an exception for any other paths
Mock ini-helper {
param($GitDirPath, $GitRootPath)
if ($GitDirPath -eq $validGitDirPath -and $GitRootPath -eq $validGitRootPath) {
return @(
[pscustomobject]@{
submodule.Mercury.path = "Mercury"
submodule.Mercury.url = "https://github.com/planets/Mercury.git"
},
[pscustomobject]@{
submodule.Venus.path = "Venus"
submodule.Venus.url = "https://github.com/planets/Venus.git"
},
[pscustomobject]@{
submodule.Earth.path = "Earth"
submodule.Earth.url = "https://github.com/planets/Earth.git"
}
)
} else {
throw "Invalid git directory or root path"
}
}
# Mock the PsIni module to return a predefined output for the valid git directory and root paths and throw an exception for any other paths
Mock PsIni {
param($GitDirPath, $GitRootPath)
if ($GitDirPath -eq $validGitDirPath -and $GitRootPath -eq $validGitRootPath) {
return @(
[pscustomobject]@{
submodule.Mercury.path = "Mercury"
submodule.Mercury.url = "https://github.com/planets/Mercury.git"
},
[pscustomobject]@{
submodule.Venus.path = "Venus"
submodule.Venus.url = "https://github.com/planets/Venus.git"
},
[pscustomobject]@{
submodule.Earth.path = "Earth"
submodule.Earth.url = "https://github.com/planets/Earth.git"
}
)
} else {
throw "Invalid git directory or root path"
}
}
}
# Use the It block to write individual tests
It 'Given valid git directory and root paths, it updates the .gitmodules file and the config file with the submodule information' {
# Call the function and store the output
$output = config-to-gitmodules -GitDirPath $validGitDirPath -GitRootPath $validGitRootPath
# Use the Should keyword to verify the expected behavior
$output | Should -Not -BeNullOrEmpty # Check that the output is not null or empty
$output | Should -HaveCount 3 # Check that the output has three elements
$output | Should -BeExactly $expectedOutput # Check that the output matches the expected output
}
It 'Given invalid git directory or root paths, it throws an error' {
# Call the function and catch the error
try {
config-to-gitmodules -GitDirPath $invalidGitDirPath -GitRootPath $invalidGitRootPath
$errorq = $null
}
catch {
$errorq = $_
}
# Use the Should keyword to verify the expected behavior
$errorq | Should -Not -BeNullOrEmpty # Check that the error is not null or empty
$errorq | Should -Match "Invalid git directory or root path" # Check that the error message matches the expected one
}
}
function git-GetSubmodulePathsUrls
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path -Path "$_\.gitmodules"})]
[string]
$RepoPath
)
try {
if(validGitRepo)
{
$zz = (git config -f .gitmodules --get-regexp '^submodule\..*\.path$')
}
else{
$rgx = "submodule" # Set the regular expression to use for splitting
# Change the current directory to the working path
Set-Location $RepoPath
# Set the path to the .gitmodules file
$p = Join-Path $RepoPath ".gitmodules"
# Split the text in the .gitmodules file by the regular expression and store the results in a variable
$TextRanges = Split-TextByRegex -Path $p -Regx $rgx
$zz = $TextRanges | keyPairTo-PsCustom
if(! ($zz))
{
# Convert the key-value pairs in the text ranges to custom objects and store the results in a variable
$zz = $TextRanges | ForEach-Object {
try {
# Trim and join the values in each text range
$q = $_.value.trim() -join ","
}
catch {
# If trimming fails, just join the values
$q = $_.value -join ","
}
try {
# Split the string by commas and equal signs and create a hashtable with the path and url keys
$t = @{
path = $q.Split(',')[0].Split('=')[1].trim()
url = $q.Split(',')[1].Split('=')[1].trim()
}
}
catch {
# If splitting fails, just use the string as it is
$t = $q
}
# Convert the hashtable to a JSON string and then back to a custom object
$t | ConvertTo-Json | ConvertFrom-Json
}
}
}
$zz| % {
$path_key, $path = $_.split(" ")
$prop = [ordered]@{
Path = $path
Url = git config -f .gitmodules --get ("$path_key" -replace "\.path",".url")
NonRelative = Join-Path $RepoPath $path
}
return New-Object –TypeName PSObject -Property $prop
}
}
catch{
Throw "$($_.Exception.Message)"
}
}
# Import the module or script that contains the function to test
Import-Module -Name Pester
. $PSScriptRoot\git-GetSubmodulePathsUrls.ps1
# Use the Describe block to group your tests
Describe 'git-GetSubmodulePathsUrls' {
# Use the BeforeAll block to define some variables and mocks that are common for all tests
BeforeAll {
# Define a valid repository path that contains a .gitmodules file
$validRepoPath = "C:\t\Planets"
# Define an invalid repository path that does not contain a .gitmodules file
$invalidRepoPath = "C:\t\Invalid"
# Define an expected output for the valid repository path
$expectedOutput = @(
[pscustomobject]@{
Path = "Mercury"
Url = "https://github.com/planets/Mercury.git"
NonRelative = "C:\t\Planets\Mercury"
},
[pscustomobject]@{
Path = "Venus"
Url = "https://github.com/planets/Venus.git"
NonRelative = "C:\t\Planets\Venus"
},
[pscustomobject]@{
Path = "Earth"
Url = "https://github.com/planets/Earth.git"
NonRelative = "C:\t\Planets\Earth"
}
)
# Mock the validGitRepo function to return true for the valid repository path and false for any other path
Mock validGitRepo {
param($RepoPath)
if ($RepoPath -eq $validRepoPath) {
return $true
} else {
return $false
}
}
# Mock the git config command to return a predefined output for the valid repository path and throw an exception for any other path
Mock git {
param($config, $f, $getRegexp)
if ($f -eq "$validRepoPath\.gitmodules") {
return @(
"submodule.Mercury.path Mercury",
"submodule.Venus.path Venus",
"submodule.Earth.path Earth",
"submodule.Mercury.url https://github.com/planets/Mercury.git",
"submodule.Venus.url https://github.com/planets/Venus.git",
"submodule.Earth.url https://github.com/planets/Earth.git"
)
} else {
throw "fatal: not in a git directory"
}
}
}
# Use the It block to write individual tests
It 'Given a valid repository path, it returns an array of custom objects with submodule information' {
# Call the function and store the output
$output = git-GetSubmodulePathsUrls -RepoPath $validRepoPath
# Use the Should keyword to verify the expected behavior
$output | Should -Not -BeNullOrEmpty # Check that the output is not null or empty
$output | Should -HaveCount 3 # Check that the output has three elements
$output | Should -BeExactly $expectedOutput # Check that the output matches the expected output
}
It 'Given an invalid repository path, it throws an error' {
# Call the function and catch the error
try {
git-GetSubmodulePathsUrls -RepoPath $invalidRepoPath
$errorc = $null
}
catch {
$errorc = $_
}
# Use the Should keyword to verify the expected behavior
$errorc | Should -Not -BeNullOrEmpty # Check that the error is not null or empty
$errorc | Should -Match "Could not parse" # Check that the error message matches the expected one
}
}
# A function to run git commands and check the exit code
function Invoke-Git {
param(
[Parameter(Mandatory=$true)]
[string]$Command # The git command to run
)
# Run the command and capture the output
$output = Invoke-Expression -Command "git $Command" -ErrorAction Stop
# return the output to the host
$output
# Check the exit code and throw an exception if not zero
if ($LASTEXITCODE -ne 0) {
throw "Git command failed: git $Command"
}
}
# Import the module or script that contains the function to test
Import-Module -Name Pester
. $PSScriptRoot\Invoke-Git.ps1
# Use the Describe block to group your tests
Describe 'Invoke-Git' {
# Use the It block to write individual tests
It 'Runs git commands and returns the output' {
# Use the Mock keyword to replace any command with a custom implementation
Mock Invoke-Expression { "git status" } -Verifiable
# Call the function and store the output
$output = Invoke-Git -Command "status"
# Use the Should keyword to verify the expected behavior
$output | Should -Be "git status"
Assert-VerifiableMocks # Check that the mock was called
}
It 'Throws an exception if the git command fails' {
# Use the Mock keyword to replace any command with a custom implementation
Mock Invoke-Expression { throw "git error" } -Verifiable
# Call the function and catch the exception
try {
Invoke-Git -Command "error"
#$error = $null
}
catch {
#$error = $_
}
# Use the Should keyword to verify the expected behavior
$error | Should -Not -BeNullOrEmpty
$error | Should -Match "Git command failed: git error"
Assert-VerifiableMocks # Check that the mock was called
}
}
function RepairWithQue-N-RepairFolder {
# Synopsis: A script to process files with git status and repair them if needed
# Parameter: Start - The start path to process
# Parameter: Modules - The path to the modules folder
param (
[Parameter(Mandatory=$true)]
[string]$Start,
[Parameter(Mandatory=$true)]
[string]$Modules
)
begin {
Push-Location
# Validate the arguments
Validate-Path -Path $Start
Validate-Path -Path $Modules
# Redirect the standard error output of git commands to the standard output stream
$env:GIT_REDIRECT_STDERR = '2>&1'
Write-Progress -Activity "Processing files" -Status "Starting" -PercentComplete 0
# Create a queue to store the paths
$que = New-Object System.Collections.Queue
# Enqueue the start path
$Start | % { $que.Enqueue($_) }
# Initialize a counter variable
$i = 0;
}
process {
# Loop through the queue until it is empty
do
{
# Increment the counter
$i++;
# Dequeue a path from the queue
$path = $que.Dequeue()
# Change the current directory to the path
Set-Location $path;
# Run git status and capture the output
$output = Check-GitStatus $path
# Check if the output is fatal
if($output -like "fatal*")
{
ActOnError -Path $path -Modules $modules
# Get the subdirectories of the path and enqueue them, excluding any .git folders
if ($continueOnError)
{
$toEnque = Get-ChildItem -Path "$path\*" -Directory -Exclude "*.git*"
}
}
else
{
$toEnque = git-GetSubmodulePathsUrls
}
$toEnque | % { $que.Enqueue($_.FullName) }
# Calculate the percentage of directories processed
$percentComplete = ($i / ($que.count+$i) ) * 100
# Update the progress bar
Write-Progress -Activity "Processing files" -PercentComplete $percentComplete
} while ($que.Count -gt 0)
}
end {
# Restore the original location
Pop-Location
Write-Progress -Activity "Processing files" -Status "Finished" -PercentComplete 100
}
}
# Import the module or script that contains the function to test
Import-Module .\RepairWithQue-N-RepairFolder.ps1
# Use the Describe block to group your tests
Describe 'RepairWithQue-N-RepairFolder' {
# Use the BeforeAll block to define some variables and mocks that are common for all tests
BeforeAll {
# Define a valid start path that contains a git folder
$validStartPath = "C:\t\Planets"
# Define a valid modules path that contains a .git\modules folder
$validModulesPath = "C:\t\Planets\.git\modules"
# Define an invalid start path that does not contain a git folder
$invalidStartPath = "C:\t\Invalid"
# Define an invalid modules path that does not contain a .git\modules folder
$invalidModulesPath = "C:\t\Invalid\.git\modules"
# Mock the Git-helper module to return a predefined output for the valid start path and throw an exception for any other path
Mock Git-helper {
param($Start)
if ($Start -eq $validStartPath) {
return @(
[pscustomobject]@{
Name = 'Mercury'
FullName = 'C:\t\Planets\Mercury'
},
[pscustomobject]@{
Name = 'Venus'
FullName = 'C:\t\Planets\Venus'
},
[pscustomobject]@{
Name = 'Earth'
FullName = 'C:\t\Planets\Earth'
}
)
} else {
throw "path not existing"
}
}
# Mock the ini-helper module to return a predefined output for the valid start path and throw an exception for any other path
Mock ini-helper {
param($Start)
if ($Start -eq $validStartPath) {
return @(
[pscustomobject]@{
Name = 'Mercury'
FullName = 'C:\t\Planets\Mercury'
},
[pscustomobject]@{
Name = 'Venus'
FullName = 'C:\t\Planets\Venus'
},
[pscustomobject]@{
Name = 'Earth'
FullName = 'C:\t\Planets\Earth'
}
)
} else {
throw "path not existing"
}
}
# Mock the Check-GitStatus function to return a predefined output for the valid start path and throw an exception for any other path
Mock Check-GitStatus {
param($Start)
if ($Start -eq $validStartPath) {
return @(
[pscustomobject]@{
Name = 'Mercury'
Status = 'On branch master'
},
[pscustomobject]@{
Name = 'Venus'
Status = 'On branch master'
},
[pscustomobject]@{
Name = 'Earth'
Status = 'On branch master'
}
)
} else {
throw "fatal: not in a git directory"
}
}
# Mock the ActOnError function to do nothing
Mock ActOnError {}
}
# Use the It block to write individual tests
It 'Given valid start and modules paths, it processes files with git status and repairs them if needed' {
# Call the function and store the output
$output = RepairWithQue-N-RepairFolder -Start $validStartPath -Modules $validModulesPath
# Use the Should keyword to verify the expected behavior
$output | Should -Not -BeNullOrEmpty # Check that the output is not null or empty
$output | Should -HaveCount 3 # Check that the output has three elements
$output | Should -BeExactly @('On branch master', 'On branch master', 'On branch master') # Check that the output matches the expected output
}
It 'Given invalid start or modules paths, it throws an error' {
# Call the function and catch the error
try {
RepairWithQue-N-RepairFolder -Start $invalidStartPath -Modules $invalidModulesPath
$error = $null
}
catch {
$error = $_
}
# Use the Should keyword to verify the expected behavior
$error | Should -Not -BeNullOrEmpty # Check that the error is not null or empty
$error | Should -Match "path not existing" # Check that the error message matches the expected one
}
}

cd 'B:\Users\chris\Documents\'
if dir is not a git dir
initialize
Get-ChildItem -Recurse -Filter '.git'
for each .git folder in path
recursivly
to each git repo in path which does not yeat have path as submodule
starting from depethest repo
,
each repo,
identify remote origion url
identify paths to currently known submodules, and if this submodules is damaged, throw error and exclude it from the original que.
identify remote url to known submodules
submodules git does not exsist but folder does exsist with content, then intialise the folder.
add as submodule
each repo located underneth
assume empty
reset index
reset head
add .
commit all
then absorbe the git dir
identify comming merges, "what if"
simulate
$errorus = 'D:\Project Shelf\PowerShellProjectFolder\scripts\Modules\Personal\migration\Export-Inst-Choco\.git'
$toReplaceWith = 'D:\Project Shelf\.git\modules\PowerShellProjectFolder\modules\scripts\modules\windowsAdmin\modules\Export-Inst-Choco' #can be done with everything and menu
#([System.IO.FileInfo]$errorus) | get-member
$asFile = ([System.IO.FileInfo]$errorus)
$targetFolder = ($asFile | select Directory).Directory
$target = $targetFolder | Join-Path -ChildPath 'x.git'
$asFile.MoveTo($target)
$asFile = ([System.IO.FileInfo]$toReplaceWith)
$target = $targetFolder | Join-Path -ChildPath '.git'
$asFile.MoveTo($target)
$errorus = 'D:\Project Shelf\PowerShellProjectFolder\scripts\Modules\Personal\migration\Export-Inst-Choco\.git'
$toReplaceWith = 'D:\Project Shelf\.git\modules\PowerShellProjectFolder\modules\scripts\modules\windowsAdmin\modules\Export-Inst-Choco' #can be done with everything and menu
#([System.IO.FileInfo]$errorus) | get-member
$asFile = ([System.IO.FileInfo]$errorus)
$targetFolder = ($asFile | select Directory).Directory
$target = $targetFolder | Join-Path -ChildPath 'x.git'
$asFile.MoveTo($target)
$asFile = ([System.IO.FileInfo]$toReplaceWith)
$target = $targetFolder | Join-Path -ChildPath '.git'
$asFile.MoveTo($target)
<#
.SYNOPSIS
Updates the submodules of a git repository.
.DESCRIPTION
This function updates the submodules of a git repository, using the PsIni module and the git commands. The function removes any broken submodules, adds any new submodules, syncs the submodule URLs with the .gitmodules file, and pushes the changes to the remote repository.
.PARAMETER RepositoryPath
The path of the git repository where the submodules are located.
.PARAMETER SubmoduleNames
An array of submodule names that will be updated. If not specified, all submodules will be updated.
#>
function Update-Git-Submodules {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$RepositoryPath,
[Parameter(Mandatory = $false)]
[string[]]
$SubmoduleNames
)
# Set the error action preference to stop on any error
# Import the PsIni module
# Define a function to remove the worktree from a config file
function Remove-Worktree {
param(
[string]$ConfigPath # The path of the config file
)
# Get the content of the config file as an ini object
$iniContent = Get-IniContent -FilePath $ConfigPath
# Remove the worktree property from the core section
$iniContent.core.Remove("worktree")
# Write the ini object back to the config file
$iniContent | Out-IniFile -FilePath $ConfigPath -Force
}
# Define a function to get the URL of a submodule
function Get-SubmoduleUrl {
param(
[string]$Path # The path of the submodule directory
)
# Change the current location to the submodule directory
Push-Location -Path $Path -ErrorAction Stop
# Get the URL of the origin remote
$url = git config remote.origin.url -ErrorAction Stop
# Write the URL to the host
Write-Host $url
# Parse the URL to get the part after the colon
$parsedUrl = ($url -split ':')[1]
# Write the parsed URL to the host
Write-Host $parsedUrl
# Return to the previous location
Pop-Location -ErrorAction Stop
# Return the parsed URL as output
return $parsedUrl
}
# Define a function to run git commands and check the exit code
function Invoke-Git {
param(
[string]$Command # The git command to run
)
# Run the command and capture the output
$output = Invoke-Expression -Command "git $Command" -ErrorAction Stop
# Write the output to the host
Write-Host $output
# Check the exit code and throw an exception if not zero
if ($LASTEXITCODE -ne 0) {
throw "Git command failed: git $Command"
}
}
# Call the function with a submodule path
Get-SubmoduleUrl "B:\ToGit\.git\modules\BucketTemplate"
# Check the status of the submodules
Invoke-Git "submodule status"
# Change the current location to the repository path
Set-Location -Path $RepositoryPath
# Get all submodules from the .gitmodules file as an array of objects
$submodules = Get-IniContent -Path ".gitmodules" | Select-Object -Property submodule
# If submodule names are specified, filter out only those submodules from the array
if ($SubmoduleNames) {
$submodules = $submodules | Where-Object { $_.submodule.Name -in $SubmoduleNames }
}
# Loop through each submodule in the array and update it
foreach ($submodule in $submodules) {
# Get submodule name and path from ini object properties
$submoduleName = $submodule.submodule.Name
$submodulePath = Join-Path -Path (Split-Path -Path ".gitmodules") -ChildPath ($submodule.submodule.path)
# Check if submodule directory exists
if (Test-Path -Path $submodulePath) {
# Change current location to submodule directory
Push-Location -Path $submodulePath
# Get submodule URL from git config
$submoduleUrl = Get-GitRemoteUrl
# Check if submodule URL is empty or local path
if ([string]::IsNullOrEmpty($submoduleUrl) -or (Test-Path -Path $submoduleUrl)) {
# Set submodule URL to remote origin URL
Set-GitRemoteUrl -Url (Get-SubmoduleUrl -Path $submodulePath)
}
# Return to previous location
Pop-Location
# Update the submodules recursively
Invoke-Git "submodule update --init --recursive"
}
else {
# Add submodule from remote URL
Invoke-Git "submodule add $(Get-SubmoduleUrl -Path $submodulePath) $submodulePath"
}
}
# Sync the submodule URLs with the .gitmodules file
# Remove any broken submodules manually or with a loop
# For example, to remove a submodule named foo:
Invoke-Git "rm --cached foo"
Remove-Item -Path ".git/modules/foo" -Recurse -Force
# Add any new submodules manually or with a loop
# For example, to add a submodule named bar:
Invoke-Git "add bar"
Invoke-Git "submodule update --init --recursive"
# Push the changes to the remote repository
}
<#todo
fallback solutions
* if everything fails,
set got dir path to abbsolute value and edit work tree in place
* if comsumption fails,
due to modulefolder exsisting, revert move and trye to use exsisting folder instead,
if this ressults in error, re revert to initial
move inplace module to x prefixed
atempt to consume again
* if no module is provided, utelyse everything to find possible folders
use hamming distance like priorit order
where
1. exact parrentmatch
rekative to root
order resukts by total exact
take first precedance
2. predefined patterns taken
and finaly sort rest by hamming
#>
# This script converts a git folder into a submodule and absorbs its git directory
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, HelpMessage=".git, The path of the git folder to convert")]
[ValidateScript({Test-Path $_ -PathType Container ; Resolve-Path -Path $_ -ErrorAction Stop})]
[Alias("GitFolder")][string]$errorus,[Parameter(Mandatory=$true,HelpMessage="subModuleRepoDir, The path of the submodule folder to replace the git folder")]
#can be done with everything and menu
[Parameter(Mandatory=$true,HelpMessage="subModuleDirInsideGit")]
[ValidateScript({Test-Path $_ -PathType Container ; Resolve-Path -Path $_ -ErrorAction Stop})]
[Alias("SubmoduleFolder")][string]$toReplaceWith
)
begin {
$pastE = $error
$error.Clear()
Get-ChildItem -path B:\GitPs1Module\* -Filter '*.ps1' | % { . $_.FullName }
# Save the previous error action preference
$previousErrorAction = $ErrorActionPreference
$ErrorActionPreference = "Stop"
}
process {
# Get the target folder, name and parent path from the git folder
Write-Verbos "#---- asFile"
$asFile = ([System.IO.Fileinfo]$errorus.trim('\'))
Write-Verbos $asFile
$targetFolder = $asFile.Directory
$name = $targetFolder.Name
$path = $targetFolder.Parent.FullName
# Get the config file path from the git folder
$configFile = Join-Path $GitFolder 'config'
about-Repo #does nothing without -verbos
# Move the git folder to a temporary name
Move-Folder -Source $GitFolder -Destination (Join-Path $targetFolder 'x.git')
# Move the submodule folder to replace the git folder
Move-Folder -Source $SubmoduleFolder -Destination (Join-Path $targetFolder '.git')
# Remove the worktree line from the config file
(Get-Content -Path $configFile | Where-Object { ! ($_ -match 'worktree') }) | Set-Content -Path $configFile
# Push the current location and change to the target folder
Push-Location
Set-Location $targetFolder
index-Remove $name $path
# Change to the parent path and get the root of the git repository
# Add and absorb the submodule using the relative path and remote url
$relative = get-Relative $path $targetFolder
Add-AbsorbSubmodule -Ref ( get-Origin) -Relative $relative
# Pop back to the previous location
Pop-Location
# Restore the previous error action preference
$ErrorActionPreference = $previousErrorAction
}
<#
.SYNOPSIS
Synchronizes the submodules with the config file.
.DESCRIPTION
This function synchronizes the submodules with the config file, using the Git-helper and ini-helper modules. The function checks the remote URLs of the submodules and updates them if they are empty or local paths. The function also handles conflicts and errors.
.PARAMETER GitDirPath
The path of the git directory where the config file is located.
.PARAMETER GitRootPath
The path of the git root directory where the submodules are located.
.PARAMETER FlagConfigDecides
A switch parameter that indicates whether to use the config file as the source of truth in case of conflicting URLs.
#>
function Sync-Git-Submodules {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$GitDirPath,
[Parameter(Mandatory = $true)]
[string]
$GitRootPath,
[Parameter(Mandatory = $false)]
[switch]
$FlagConfigDecides
)
# Import the helper modules
Import-Module Git-helper
Import-Module ini-helper
# Get the config file content and select the submodule section
$root = get-GitDirPath {git dir path}
$rootKnowledge = Get-IniContent -Path (Join-Path -Path $GitDirPath -ChildPath "config") | Select-Object -Property submodule
# Change the current location to the git root directory
Set-Location -Path $GitRootPath
# Loop through each submodule in the config file
foreach ($rootx in $rootKnowledge) {
try {
# Change the current location to the submodule path
Set-Location -Path $rootx.path
# Get the remote URL of the submodule
$q = Get-GitRemoteUrl
# Check if the remote URL is a local path or empty
$isPath = Test-Path -Path $q -IsValid
$isPath = pathNotUrl($q)
$isEmpty = [string]::IsNullOrEmpty($q)
if ($isPath -or $isEmpty) {
# Set the remote URL to the one in the config file and overwrite it
Set-GitRemote -Url $rootx.url -Overwrite
}
else {
# Check if the URL in the config file is a local path or empty
$isConfigPath = Test-Path -Path $rootx.url -IsValid
$isConfigEmpty = [string]::IsNullOrEmpty($rootx.url)
if $isEmpty
$rep | append-Ini($root+'\config')
elseif pathNotUrl($rootx.url)
($rootx+ appendProperty($q)) | replace-iniElement($root+'\config',$rootx)
$rep | append-Ini($root+'\config')
$rootx | Add-IniElement -Path (Join-Path -Path $GitDirPath -ChildPath "config")
}
# Append the remote URL to the submodule and replace it in the config file
}
elseif ($rootx.url -notin $q.url) {
# Handle conflicting URLs
if ($FlagConfigDecides) {
# Use the config file as the source of truth and replace it in the submodule path
$rootx | replace-iniElement($rootx.path,$rep)
$rootx | Set-IniElement -Path (Join-Path -Path $rootx.path -ChildPath ".gitmodules") -OldElement $q
}
else {
# Throw an error for conflicting URLs
throw "Conflicting URLs: $($rootx.url) and $($q.url)"
}
}
}
}
catch {
# Handle errors based on their messages
switch ($_.Exception.Message) {
"path not existing" {
return "uninitialized"
}
"path existing but no subroot present" {
return "already in index"
}
"path existing, git root existing, but git not recognized" {
return "corrupted"
}
default {
return $_.Exception.Message
}
}
}
}
}
gitmodules = inifile
subrepo remote
Get-ChildItem -path B:\GitPs1Module\* -Filter '*.ps1' | % { . $_.FullName }
<#
.SYNOPSIS
Extracts submodules from a git repository.
.DESCRIPTION
Extracts submodules from a git repository by moving the .git directories from the submodules to the parent repository and updating the configuration.
.PARAMETER Paths
The paths of the submodules to extract. If not specified, all submodules are extracted.
.INPUTS
System.String
You can pipe one or more submodule paths to this function.
.EXAMPLE
Extract-Submodules
.nix
"!gitextractsubmodules() { set -e && { if [ 0 -lt \"$#\" ]; then printf \"%s\\n\" \"$@\"; else git ls-files --stage | sed -n \"s/^160000 [a-fA-F0-9]\\+ [0-9]\\+\\s*//p\"; fi; } | { local path && while read -r path; do if [ -f \"${path}/.git\" ]; then local git_dir && git_dir=\"$(git -C \"${path}\" rev-parse --absolute-git-dir)\" && if [ -d \"${git_dir}\" ]; then printf \"%s\t%s\n\" \"${git_dir}\" \"${path}/.git\" && mv --no-target-directory --backup=simple -- \"${git_dir}\" \"${path}/.git\" && git --work-tree=\"${path}\" --git-dir=\"${path}/.git\" config --local --path --unset core.worktree && rm -f -- \"${path}/.git~\" && if 1>&- command -v attrib.exe; then MSYS2_ARG_CONV_EXCL=\"*\" attrib.exe \"+H\" \"/D\" \"${path}/.git\"; fi; fi; fi; done; }; } && gitextractsubmodules"
.EXAMPLE
Extract-Submodules "foo" "bar"
[alias]
extract-submodules = "!gitextractsubmodules() { set -e && { if [ 0 -lt \"$#\" ]; then printf \"%s\\n\" \"$@\"; else git ls-files --stage | sed -n \"s/^160000 [a-fA-F0-9]\\+ [0-9]\\+\\s*//p\"; fi; } | { local path && while read -r path; do if [ -f \"${path}/.git\" ]; then local git_dir && git_dir=\"$(git -C \"${path}\" rev-parse --absolute-git-dir)\" && if [ -d \"${git_dir}\" ]; then printf \"%s\t%s\n\" \"${git_dir}\" \"${path}/.git\" && mv --no-target-directory --backup=simple -- \"${git_dir}\" \"${path}/.git\" && git --work-tree=\"${path}\" --git-dir=\"${path}/.git\" config --local --path --unset core.worktree && rm -f -- \"${path}/.git~\" && if 1>&- command -v attrib.exe; then MSYS2_ARG_CONV_EXCL=\"*\" attrib.exe \"+H\" \"/D\" \"${path}/.git\"; fi; fi; fi; done; }; } && gitextractsubmodules"
git extract-submodules [<path>...]
#>
Get-ChildItem -path B:\GitPs1Module\* -Filter '*.ps1' | % {
$p = $_
$fullName = $p.FullName
try {
invoke-expression ". $fullName" -ErrorAction Stop
}
catch {
Write-Output "could not load $p"
}
}
#load dependensies
<#
.SYNOPSIS
Determines whether all elements of a path exist.
.DESCRIPTION
The Test-Path cmdlet determines whether all elements of the path exist. It returns $true if all elements exist and $false if any are missing. It can also return the item at the specified path if the PassThru switch is used.
.PARAMETER Path
Specifies the path to test. Wildcards are permitted.
.PARAMETER LiteralPath
Specifies a path to test, but unlike Path, the value of LiteralPath is used exactly as it is typed. No characters are interpreted as wildcards. If the path includes escape characters, enclose it in single quotation marks.
.PARAMETER PassThru
Returns an object representing the item at the specified path. By default, this cmdlet does not generate any output.
.INPUTS
System.String
You can pipe a string that contains a path to this cmdlet.
.OUTPUTS
System.Boolean or System.Management.Automation.PathInfo
This cmdlet returns a Boolean value that indicates whether the path exists or an object representing the item at the path if PassThru is used.
.EXAMPLE
Test-Path "C:\Windows"
This command tests whether the C:\Windows directory exists.
.EXAMPLE
Test-Path "C:\Windows\*.exe" -PassThru
This command tests whether there are any files with the .exe extension in the C:\Windows directory and returns them as objects.
#>
function Test-Path {
[CmdletBinding()]
param (
[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string[]]$Path,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string[]]$LiteralPath,
[switch]$PassThru
)
begin {
# initialize an empty array to store the paths
$PathsToTest = @()
}
process {
# add the pipeline input to the array
if ($Path) {
$PathsToTest += $Path
}
if ($LiteralPath) {
$PathsToTest += $LiteralPath
}
}
end {
# loop through each path in the array
foreach ($P in $PathsToTest) {
# resolve any wildcards in the path
$ResolvedPaths = Resolve-Path -Path $P -ErrorAction SilentlyContinue
# check if any paths were resolved
if ($ResolvedPaths) {
# return true or the resolved paths depending on PassThru switch
if ($PassThru) {
$ResolvedPaths | Get-Item
}
else {
$true
}
}
else {
# return false or nothing depending on PassThru switch
if ($PassThru) {
# do nothing
}
else {
$false
}
}
}
}
}
function Extract-Submodules {
param (
[Parameter(ValueFromPipeline = $true)]
[string[]]$Paths
)
begin {
# initialize an empty array to store the paths
$SubmodulePaths = @()
# get the paths of all submodules if not specified, at current path
if (-not $SubmodulePaths) {
$SubmodulePaths = Get-SubmodulePaths
}
# add the pipeline input to the array
$SubmodulePaths += $Paths
}
process {
# loop through each submodule path
foreach ($Path in $SubmodulePaths) {
# check if the path ends with /.git and append it if not
if (-not $Path.EndsWith("/.git")) {
$orgPath = $path
$Path = Test-Path -Path "$Path/.git" -PassThru
if(!($Path))
{
$path = $orgPath
}
}
# check if the submodule has a .git file
if (Test-Path -Path "$Path/.git" -PathType Leaf) {
# get the absolute path of the .git directory
$GitDir = Get-GitDir -Path $Path
# check if the .git directory exists
if (Test-Path -Path $GitDir -PathType Container) {
# display the .git directory and the .git file
Write-Host "$GitDir`t$Path/.git"
# move the .git directory to the submodule path
Move-Item -Path $GitDir -Destination "$Path/.git" -Force -Backup
# unset the core.worktree config for the submodule
Unset-CoreWorktree -Path $Path
# remove the backup file if any
Remove-Item -Path "$Path/.git~" -Force -ErrorAction SilentlyContinue
# hide the .git directory on Windows
Hide-GitDir -Path $Path
}
}
else {
# throw an error if the .git file is not present
throw "Could not find $Path"
}
}
}
end {
}
}
<#
.SYNOPSIS
Updates the submodules of a git repository.
.DESCRIPTION
This function updates the submodules of a git repository, using the PsIni module and the git commands. The function removes any broken submodules, adds any new submodules, syncs the submodule URLs with the .gitmodules file, and pushes the changes to the remote repository.
.PARAMETER RepositoryPath
The path of the git repository where the submodules are located.
.PARAMETER SubmoduleNames
An array of submodule names that will be updated. If not specified, all submodules will be updated.
#>
function Update-Git-Submodules {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$RepositoryPath,
[Parameter(Mandatory = $false)]
[string[]]
$SubmoduleNames
)
# Set the error action preference to stop on any error
$ErrorActionPreference = "Stop"
# Import the PsIni module
Import-Module PsIni
# Define a function to remove the worktree from a config file
function Remove-Worktree {
param(
[string]$ConfigPath # The path of the config file
)
# Get the content of the config file as an ini object
$iniContent = Get-IniContent -FilePath $ConfigPath
# Remove the worktree property from the core section
$iniContent.core.Remove("worktree")
# Write the ini object back to the config file
$iniContent | Out-IniFile -FilePath $ConfigPath -Force
}
# Define a function to get the URL of a submodule
function Get-SubmoduleUrl {
param(
[string]$Path # The path of the submodule directory
)
# Change the current location to the submodule directory
Push-Location -Path $Path -ErrorAction Stop
# Get the URL of the origin remote
$url = git config remote.origin.url -ErrorAction Stop
# Parse the URL to get the part after the colon
$parsedUrl = ($url -split ':')[1]
# Return to the previous location
Pop-Location -ErrorAction Stop
# Return the parsed URL as output
return $parsedUrl
}
# Define a function to run git commands and check the exit code
function Invoke-Git {
param(
[string]$Command # The git command to run
)
# Run the command and capture the output
$output = Invoke-Expression -Command "git $Command" -ErrorAction Stop
# Write the output to the host
Write-Host $output
# Check the exit code and throw an exception if not zero
if ($LASTEXITCODE -ne 0) {
throw "Git command failed: git $Command"
}
}
# Change the current location to the repository path
Set-Location -Path $RepositoryPath
# Get all submodules from the .gitmodules file as an array of objects
$submodules = Get-IniContent -Path ".gitmodules" | Select-Object -Property submodule
# If submodule names are specified, filter out only those submodules from the array
if ($SubmoduleNames) {
$submodules = $submodules | Where-Object { $_.submodule.Name -in $SubmoduleNames }
}
# Loop through each submodule in the array and update it
foreach ($submodule in $submodules) {
# Get submodule name and path from ini object properties
$submoduleName = $submodule.submodule.Name
$submodulePath = Join-Path -Path (Split-Path -Path ".gitmodules") -ChildPath ($submodule.submodule.path)
# Check if submodule directory exists
if (Test-Path -Path $submodulePath) {
# Change current location to submodule directory
Push-Location -Path $submodulePath
# Get submodule URL from git config
$submoduleUrl = Get-GitRemoteUrl
# Check if submodule URL is empty or local path
if ([string]::IsNullOrEmpty($submoduleUrl) -or (Test-Path -Path $submoduleUrl)) {
# Set submodule URL to remote origin URL
Set-GitRemoteUrl -Url (Get-SubmoduleUrl -Path $submodulePath)
}
# Return to previous location
Pop-Location
# Update submodule recursively
Invoke-Git "submodule update --init --recursive $submodulePath"
}
else {
# Add submodule from remote URL
Invoke-Git "submodule add $(Get-SubmoduleUrl -Path $submodulePath) $submodulePath"
}
}
# Sync the submodule URLs with the .gitmodules file
Invoke-Git "submodule sync"
# Push the changes to the remote repository
Invoke-Git "push origin master"
}
function gitRemoveWorktree ($configPath)
{
$iniContent = Get-IniContent -FilePath $configPath
$iniContent.core.Remove("worktree") ;
$iniContent | Out-IniFile -FilePath $configPath -Force
}
# Define a function to get the URL of a submodule
function Get-SubmoduleUrl {
param(
[string]$Path # The path of the submodule directory
)
# Change the current location to the submodule directory
Push-Location -Path $Path -ErrorAction Stop
# Get the URL of the origin remote
$url = git config remote.origin.url -ErrorAction Stop
# Write the URL to the host
Write-Host $url
# Parse the URL to get the part after the colon
$parsedUrl = ($url -split ':')[1]
# Write the parsed URL to the host
Write-Host $parsedUrl
# Return to the previous location
Pop-Location -ErrorAction Stop
}
# Define a function to run git commands and check the exit code
function Invoke-Git {
param(
[string]$Command # The git command to run
)
# Run the command and capture the output
$output = Invoke-Expression -Command "git $Command" -ErrorAction Stop
# return the output to the host
$output
# Check the exit code and throw an exception if not zero
if ($LASTEXITCODE -ne 0) {
throw "Git command failed: git $Command"
}
}
<#todo
fallback solutions
* if everything fails,
set got dir path to abbsolute value and edit work tree in place
* if comsumption fails,
due to modulefolder exsisting, revert move and trye to use exsisting folder instead,
if this ressults in error, re revert to initial
move inplace module to x prefixed
atempt to consume again
* if no module is provided, utelyse everything to find possible folders
use hamming distance like priorit order
where
1. exact parrentmatch
rekative to root
order resukts by total exact
take first precedance
2. predefined patterns taken
and finaly sort rest by hamming
#>
[CmdletBinding()]
param (
[Parameter(Mandatory=$true,
HelpMessage=".git")]
[ValidateNotNullOrEmpty()]
[string]$errorus,
[Parameter(Mandatory=$true,
HelpMessage="subModuleRepoDir")]
#can be done with everything and menu
[Parameter(Mandatory=$true,
HelpMessage="subModuleDirInsideGit")]
[ValidateNotNullOrEmpty()]
[string]$toReplaceWith
)
$pastE = $error
$error.Clear()
Try {
$null = Resolve-Path -Path $errorus -ErrorAction Stop
$null = Resolve-Path -Path $toReplaceWith -ErrorAction Stop
$null = Test-Path -Path $errorus -ErrorAction Stop
$null = Test-Path -Path $toReplaceWith -ErrorAction Stop
}
catch {
ECHO "paths was unresolvable"
echo $error
exit
}
$previousErrorAction = $ErrorActionPreference
$ErrorActionPreference = "Stop"
function git-root {
$gitrootdir = (git rev-parse --show-toplevel)
if ($gitrootdir) {
Set-Location $gitrootdir
}
}
#---- move --- probably faile due to .git being a folder, and or module folder not exsiting, not critical
#([System.IO.FileInfo]$errorus) | get-member
echo '************************************************************'
echo "#---- asFile"
$asFile = ([System.IO.Fileinfo]$errorus.trim('\'))
$childy = [System.IO.Fileinfo] $asFile.FullName
echo $asFile
echo "#---- targetFolder"
$targetFolder = $asFile.Directory
echo $targetFolder.ToString()
$name = $targetFolder.Name
echo $name.ToString()
$parentY = ($asFile | Split-Path -Parent)
$path = $targetFolder.Parent.FullName
echo $path.ToString()
$configFile = ($errorus + '\config')
echo $configFile.ToString()
echo "#---- target"
$target = $targetFolder | Join-Path -ChildPath 'x.git'
echo "#---- asFile"
$parentY = ($asFile | Split-Path -Parent)
$childy = [System.IO.Fileinfo] (".\"+($asFile | Split-Path -leaf))
cd $parentY
echo $target
echo '************************************************************'
# cd $parentY
try{
$childy.MoveTo($target)
}
catch
{
echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx failed xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
echo "xxxxx to move $childy"
echo "xxxxx target $target"
}
try{
$q = ($targetFolder | Join-Path -ChildPath '.git')
echo "#---- toReplaceWith"
$asFile = ([System.IO.FileInfo]$toReplaceWith)
echo "#---- target"
$target = $targetFolder | Join-Path -ChildPath '.git'
echo "#---- asFile"
mv $toReplaceWith -Destination $q -Verbose -PassThru
#([System.IO.FileInfo]$toReplaceWith).MoveTo(($targetFolder | Join-Path -ChildPath '.git'))
}
catch {
echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx failed to move xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
echo "xxxxx module dir $toReplaceWith"
echo "xxxxx target $q"
echo $Error
exit
}
$path = $targetFolder.Parent.FullName
$configFile = ($errorus + '\config')
echo '************************************************************'
echo $targetFolder.ToString()
echo $name.ToString()
echo $path.ToString()
echo $configFile.ToString()
#--- remove worktree line
echo "#---- path"
$path = $errorus + '\config'
# putting get-content in paranteses makes it run as a separate thread and doesn't lock the file further down the pipe
echo "#---- Get-Content"
(Get-Content -Path $path | ? { ! ($_ -match 'worktree') }) | Set-Content -Path $path
# --- forget about files in path
echo "#---- Push-Location"
Push-Location
cd $targetFolder
echo "#---- get-url"
out-null -InputObject($ref = (git remote get-url origin) 2>1 )
echo '************************** ref *****************************'
echo $ref.ToString()
echo '************************** ref *****************************'
if($ref)
{
if(!($ref.IndexOf('\') -gt 0))
{
$ref = $targetFolder
}
}
else {
$ref = $targetFolder
}
echo "#---- name"
$name = $targetFolder.Name
echo "#---- path"
$path = $targetFolder.Parent.FullName
echo '************************************************************'
function git-root {
$gitrootdir = (git rev-parse --show-toplevel)
if ($gitrootdir) {
Set-Location $gitrootdir
}
}
#---- move --- probably faile due to .git being a folder, and or module folder not exsiting, not critical
#([System.IO.FileInfo]$errorus) | get-member
try{
([System.IO.FileInfo]$errorus).MoveTo(($targetFolder | Join-Path -ChildPath 'x.git'))
}
catch
{
echo $errorus
}
try{
$q = ($targetFolder | Join-Path -ChildPath '.git')
mv $toReplaceWith -Destination $q -ErrorAction Stop
#([System.IO.FileInfo]$toReplaceWith).MoveTo(($targetFolder | Join-Path -ChildPath '.git'))
}
catch {
echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx failed to move module dir xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
echo $toReplaceWith
echo $q
}
#--- remove worktree line -- ! error, not critical, continue
# putting get-content in paranteses makes it run as a separate thread and doesn't lock the file further down the pipe
(Get-Content -Path $configFile | ? { ! ($_ -match 'worktree') }) | Set-Content -Path $configFile
# --- forget about files in path, error if git already ignore path, not critical
Push-Location
cd $targetFolder
$ref = (git remote get-url origin)
echo '************************** ref *****************************'
echo $ref.ToString()
echo '************************** ref *****************************'
cd $path
echo "#---- cached"
git rm -r --cached $name
echo "#---- commit"
git commit -m "forgot about $name"
# --- Read submodule
echo "#---- path"
echo '******************************* bout to read as submodule ****************************************'
cd $path
cd $path ; Git-root # (outside of ref)
echo "#---- git-root"
$relative = ((Resolve-Path -Path $targetFolder.FullName -Relative) -replace([regex]::Escape('\'),'/')).Substring(2)
echo "#---- submodu"
Git submodule add $ref $relative
echo "#---- commit"
git commit -m "as submodule $relative"
echo "#---- absorbgitdirs"
Git submodule absorbgitdirs $relative
=======
# --- Read submodule
echo '******************************* bout to read as submodule ****************************************'
cd $path ; Git-root # (outside of ref)
$relative = ((Resolve-Path -Path $targetFolder.FullName -Relative) -replace([regex]::Escape('\'),'/')).Substring(2)
echo $relative
echo $ref
echo '****************************** relative path ****************************************************'
function AddNabsorb ([string]$ref, [string]$relative) {
Git submodule add $ref $relative
echo "#---- commit"
git commit -m "as submodule $relative"
echo "#---- absorbgitdirs"
Git submodule absorbgitdirs $relative
}
AddNabsorb -ref $ref -relative $relative
Pop-Location
$ErrorActionPreference = $previousErrorAction
$error = $pastE
<#
.SYNOPSIS
Synchronizes the submodules with the config file.
.DESCRIPTION
This function synchronizes the submodules with the config file, using the Git-helper and ini-helper modules. The function checks the remote URLs of the submodules and updates them if they are empty or local paths. The function also handles conflicts and errors.
.PARAMETER GitDirPath
The path of the git directory where the config file is located.
.PARAMETER GitRootPath
The path of the git root directory where the submodules are located.
.PARAMETER FlagConfigDecides
A switch parameter that indicates whether to use the config file as the source of truth in case of conflicting URLs.
#>
function Sync-Git-Submodules {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$GitDirPath,
[Parameter(Mandatory = $true)]
[string]
$GitRootPath,
[Parameter(Mandatory = $false)]
[switch]
$FlagConfigDecides
)
# Import the helper modules
Import-Module Git-helper
Import-Module ini-helper
# Get the config file content and select the submodule section
$root = get-GitDirPath {git dir path}
$rootKnowledge = Get-IniContent -Path (Join-Path -Path $GitDirPath -ChildPath "config") | Select-Object -Property submodule
# Change the current location to the git root directory
Set-Location -Path $GitRootPath
# Loop through each submodule in the config file
foreach ($rootx in $rootKnowledge) {
try {
# Change the current location to the submodule path
Set-Location -Path $rootx.path
# Get the remote URL of the submodule
$q = Get-GitRemoteUrl
# Check if the remote URL is a local path or empty
$isPath = Test-Path -Path $q -IsValid
$isPath = pathNotUrl($q)
$isEmpty = [string]::IsNullOrEmpty($q)
if ($isPath -or $isEmpty) {
# Set the remote URL to the one in the config file and overwrite it
Set-GitRemote -Url $rootx.url -Overwrite
}
else {
# Check if the URL in the config file is a local path or empty
$isConfigPath = Test-Path -Path $rootx.url -IsValid
$isConfigEmpty = [string]::IsNullOrEmpty($rootx.url)
if $isEmpty
$rep | append-Ini($root+'\config')
elseif pathNotUrl($rootx.url)
($rootx+ appendProperty($q)) | replace-iniElement($root+'\config',$rootx)
$rep | append-Ini($root+'\config')
$rootx | Add-IniElement -Path (Join-Path -Path $GitDirPath -ChildPath "config")
}
# Append the remote URL to the submodule and replace it in the config file
}
elseif ($rootx.url -notin $q.url) {
# Handle conflicting URLs
if ($FlagConfigDecides) {
# Use the config file as the source of truth and replace it in the submodule path
$rootx | replace-iniElement($rootx.path,$rep)
$rootx | Set-IniElement -Path (Join-Path -Path $rootx.path -ChildPath ".gitmodules") -OldElement $q
}
else {
# Throw an error for conflicting URLs
throw "Conflicting URLs: $($rootx.url) and $($q.url)"
}
}
}
}
catch {
# Handle errors based on their messages
switch ($_.Exception.Message) {
"path not existing" {
return "uninitialized"
}
"path existing but no subroot present" {
return "already in index"
}
"path existing, git root existing, but git not recognized" {
return "corrupted"
}
default {
return $_.Exception.Message
}
}
}
}
}
gitmodules = inifile
subrepo remote
<#
========================================================================================================================
Name : <Name>.ps1
Description : This script ............................
Created Date : %Date%
Created By : %UserName%
Dependencies : 1) Windows PowerShell 5.1
2) .................
Revision History
Date Release Change By Description
%Date% 1.0 %UserName% Initial Release
========================================================================================================================
#>
# Get all the .git files in the current directory and its subdirectories
$gitFiles = Get-ChildItem -Filter ".git" -Recurse
# Loop through each .git file
foreach ($gitFile in $gitFiles) {
# Read the content of the .git file
$content = Get-Content $gitFile
# Extract the "gitdir" path using a regular expression
$pattern = "gitdir: (.*)"
if ($content -match $pattern) {
$gitDir = $Matches[1]
}
else {
# Skip this file if it does not contain a "gitdir" reference
continue
}
# Resolve the "gitdir" path to an absolute path
$absolutePath = Resolve-Path -Path $gitDir -RelativeTo $gitFile.DirectoryName
# Check if the "gitdir" path exists
if (Test-Path $absolutePath) {
# Output a success message
Write-Output "$($gitFile.FullName) has a valid gitdir reference to $($absolutePath)"
}
else {
# Output an error message
Write-Output "$($gitFile.FullName) has an invalid gitdir reference to $($absolutePath)"
}
}
cls
function Show-ProgressV3{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[PSObject[]]$InputObject,
[string]$Activity = "Processing items"
)
[int]$TotItems = $Input.Count
[int]$Count = 0
$Input | foreach {
'inside'+$TotItems
$_
$Count++
[int]$percentComplete = ($Count/$TotItems* 100)
Write-Progress -Activity $Activity -PercentComplete $percentComplete -Status ("Working - " + $percentComplete + "%")
}
}
function enque {
param([Parameter(ValueFromPipeline)][psobject]$InputObjectx)
$output = $prop.name+$InputObjectx.length
$depth++
$InputObjectx | ?{$_.name -ne 'SyncRoot'} | % { $queue.Enqueue($_) }
}
function Resolve-Properties
{
param([Parameter(ValueFromPipeline)][psobject]$InputObject)
begin {
$i = 0
$queue = [System.Collections.Queue]::new() # get a new queue
$toParse = $InputObject.psobject.Properties
}
process {
$toParse | % { $queue.Enqueue($_) }
$i = 0
$depth = 0
$tree = '\----\'
$queue.Count
$output = ''
$iLessThan = 200
while ($queue.Count -gt 0 -and $i -le $iLessThan)
{
$i++; $queue.Count
$itemOfQue = $queue.Dequeue() # get one item off the queue
$prop = $itemOfQue.psobject.Properties
$subValue1 = $prop.value.children
$subValue2 = $prop.children
if( $subValue1.length -gt 0)
{
$subValue1 | enque
}
elseif ( $subValue2.length -gt 0 )
{
$subValue2 | enque
}
else
{
$output = $prop.value
}
$treex = $tree * $depth
$treex + $output
}
}
}
cd 'D:\Project Shelf\NoteTakingProjectFolder\GistNotebook\xMarks'
cd 'B:\ToGit'
Set-Alias -Name 'invoke-everything' -Value "C:\Program Files\Everything\Everything.exe"
everything -help
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment