#!powershell #AnsibleRequires -CSharpUtil Ansible.Basic #AnsibleRequires -PowerShell Ansible.ModuleUtils.CommandUtil $module = [Ansible.Basic.AnsibleModule]::Create($args, @{ options = @{ dest = @{type = 'path'} repo = @{required = $true; aliases = @('name')} version = @{default = 'HEAD'; aliases = @('branch')} remote = @{default = 'origin'} recursive = @{default = $true; type = 'bool'} executable = @{default = $null; type = 'path'} } supports_check_mode = $false }) $dest = $module.Params.dest $repo = $module.Params.repo $version = $module.Params.version $remote = $module.Params.remote $git = $module.Params.executable if (!$git) { $git = Get-ExecutablePath 'git' } # ================================= Utilities ================================== function Get-AbsolutePath { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $path ) try { $result = Resolve-Path $path } catch { return $_[0].TargetObject } return $result } function Get-GitDir { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $path ) $git_dir = Join-Path $path '.git' # Check if this .git is a file. if ([System.IO.File]::Exists($git_dir)) { # Extract the gitdir: path from the .git file. $groups = Get-Content "$git_dir" | ` Select-String '(gitdir:) (.*)' | ` ForEach { $_.Matches[0].Groups[1..2] } $ref_prefix = $groups[0] $gitdir = $groups[1] if ($ref_prefix -ne 'gitdir:') { $module.FailJson('The .git file has invalid gitdir reference format.') } # Check if the repo path is absolute. if ([System.IO.Path]::IsPathRooted($gitdir)) { $git_dir = $gitdir } else { # Join with the input path to construct an absolute path. $git_dir = Join-Path $path $gitdir } if (![System.IO.Directory]::Exists($git_dir)) { throw "$git_dir is not a directory." } } return $git_dir } function Test-GitLocalChanges { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [String] $dest ) $command = "`"$git`" status --porcelain" $result = Run-Command -command $command -working_directory $dest $changes = $result.stdout.Split([System.Environment]::NewLine, ` [System.StringSplitOptions]::RemoveEmptyEntries) | ` Where-Object { -not $_.StartsWith('??') } | Measure-Object -Line return $changes.Lines -gt 0 } function Get-GitRemoteHeadBranch { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] $dest, [Parameter(Mandatory = $true)] [String] $remote ) $command = "`"$git`" symbolic-ref --short refs/remotes/$remote/HEAD" $result = Run-Command -command $command -working_directory $dest if ($result.rc -ne 0) { $module.FailJson("Could not determine the default HEAD branch of remote: $remote" ` + "$result.stdout $result.stderr") } return $result.stdout.Trim().Replace("$remote/", '') } function Get-GitCurrentSha { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] $dest ) $command = "`"$git`" rev-parse HEAD" $result = Run-Command -command $command -working_directory $dest return $result.stdout.Trim() } function Invoke-GitClone { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] $repo, [Parameter(Mandatory = $true)] [String] $remote, [Parameter(Mandatory = $true)] [String] $dest ) $dest_parent = Split-Path -Path $dest -Parent if (!(Test-Path $dest_parent)) { New-Item -Path $dest_parent -ItemType Directory } $command = "`"$git`" clone --recursive --origin $remote $repo $dest" if ($version -ne "HEAD") { $command += " --branch $version" } $result = Run-Command -command $command -working_directory $cwd if ($result.rc -ne 0) { $module.FailJson($result.stderr) } } function Invoke-GitCheckout { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] $dest, [Parameter(Mandatory = $true)] [String] $remote, [Parameter(Mandatory = $true)] [String] $version ) if ($version -eq "HEAD") { $branch = Get-GitRemoteHeadBranch $dest $remote } else { $branch = $version } $result = Run-Command -command "`"$git`" checkout $branch" -working_directory $dest if ($result.rc -ne 0) { $module.FailJson("Failed to checkout version '$version'`n" + $result.stderr) } } function Invoke-GitFetch { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] $dest, [Parameter(Mandatory = $true)] [String] $remote, [Parameter(Mandatory = $true)] [String] $version ) $command = "`"$git`" fetch --tags $remote" $result = Run-Command -command $command -working_directory $dest if ($result.rc -ne 0) { $module.FailJson("Failed to download remote objects and refs:`n" + ` $result.stderr) } } function Invoke-GitPull { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] $dest, [Parameter(Mandatory = $true)] [String] $remote, [Parameter(Mandatory = $true)] [String] $version ) $result = Run-Command -command "`"$git`" pull $remote $version" -working_directory $dest if ($result.rc -ne 0) { $module.FailJson("Failed to pull version '$version' from remote '$origin':`n" + ` $result.stderr) } } function Invoke-GitSubmoduleUpdate { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] $dest ) $result = Run-Command -command "`"$git`" submodule update --init" -working_directory $dest if ($result.rc -ne 0) { $module.FailJson("Failed to initialized/update submodules:`n" + $result.stderr) } } # ================================ Start logic ================================= if (!$dest) { $module.FailJson('The destination directory must be specified.') } $dest = Get-AbsolutePath $dest $git_dir = Get-GitDir $dest $gitconfig = Join-Path $git_dir 'config' $module.Result.before = $null if (($dest -and ![System.IO.File]::Exists($gitconfig))) { Invoke-GitClone $repo $remote $dest Invoke-GitCheckout $dest $remote $version $module.Result.changed = $true } else { $module.Result.before = Get-GitCurrentSha $dest if (Test-GitLocalChanges $dest) { $module.FailJson('Local modifications exist in repository.') } Invoke-GitFetch $dest $remote $version Invoke-GitCheckout $dest $remote $version Invoke-GitPull $dest $remote $version $module.Result.after = Get-GitCurrentSha $dest if ($module.Result.before -ne $module.Result.after) { $module.Result.changed = $true } } if ($recursive) { Invoke-GitSubmoduleUpdate $dest } # Ensure the repository has the correct owner $userName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name $idRef = [System.Security.Principal.NTAccount]::new($userName) Get-Item $dest | foreach { ` $_ ; $_ | Get-ChildItem -Force -Recurse ` } | foreach { ` $acl = $_ | Get-Acl; $acl.SetOwner($idRef); $_ | Set-Acl -AclObject $acl ` } $module.ExitJson()