<# 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 }