Open-GitRepo.psm1
#Region './Private/Get-GitBranchWithCommitHash.ps1' -1 function Get-GitBranchWithCommitHash { param([string]$TargetPath) $startingLocation = Get-Location if (Test-Path ($TargetPath || "") -PathType Container) { Set-Location -Path $TargetPath } elseif (![string]::IsNullOrEmpty($TargetPath)) { Write-Error "The specified path '$TargetPath' is not a valid directory." return $null } try { $targetBranch = (git for-each-ref --format='%(refname:short)|%(objectname)' refs/heads/ 2>$null || @()) | ForEach-Object { $parts = $_ -split '\|' if ($parts.Count -eq 2) { [PSCustomObject]@{ Branch = $parts[0]; CommitHash = $parts[1] } } return [PSCustomObject]@{ Branch = $null CommitHash = $null } } | Where-Object { $_.Branch -eq $Branch } | Select-Object -First 1 if ($null -eq $targetBranch -or -not $targetBranch.CommitHash) { Write-Error "Branch '$Branch' not found for thiss repository." return $null } # build up a url which contains the branch name and commit hash, e.g. # https://bitbucket.org/mybbworkspace/mybbrepo/src/acc31fe8745678bc987b123d87d7ac72fec220e52/?at=hotfix%2Fmy-test-branch return $targetBranch } finally { if ($startingLocation -ne (Get-Location)) { Set-Location -Path $startingLocation } } } #EndRegion './Private/Get-GitBranchWithCommitHash.ps1' 44 #Region './Private/Get-GitCurrentBranch.ps1' -1 function Get-GitCurrentBranch { param([string]$TargetPath, [string]$DefaultBranch) # Check if the target path is a valid git repository if (-not (Test-Path "$TargetPath/.git")) { Write-Error "The specified path '$TargetPath' is not a valid git repository." return $DefaultBranch } # get the current branch name for the current directory if no target path is specified if ([string]::IsNullOrEmpty($TargetPath)) { return git branch --show-current 2>$null } # Get the current branch name for the directory given $currentBranch = git -C $TargetPath rev-parse --abbrev-ref HEAD 2>$null if (-not $currentBranch) { Write-Error "Could not retrieve current branch. Are you in a git repository?" return $DefaultBranch } return $currentBranch } #EndRegion './Private/Get-GitCurrentBranch.ps1' 24 #Region './Private/Get-GitRemoteAndBranch.ps1' -1 function Get-GitRemoteAndBranch { param([string]$TargetPath) $remoteUrl = git -C $TargetPath remote get-url origin 2>$null if (-not $remoteUrl) { Write-Error "Could not retrieve remote 'origin' URL. Are you in a git repository with an 'origin' remote?" return $null } $currentBranch = git -C $TargetPath rev-parse --abbrev-ref HEAD 2>$null if (-not $currentBranch) { Write-Error "Could not retrieve current branch. Are you in a git repository?" return $null } return @{ Url = $remoteUrl; Branch = $currentBranch } } #EndRegion './Private/Get-GitRemoteAndBranch.ps1' 15 #Region './Private/Get-GitRemoteUrl.ps1' -1 function Get-GitRemoteUrl { param([string]$TargetPath) if ([string]::IsNullOrEmpty($TargetPath)) { return git remote get-url origin 2>$null } return git -C $TargetPath remote get-url origin 2>$null } #EndRegion './Private/Get-GitRemoteUrl.ps1' 10 #Region './Private/Get-PrimaryGitBranch.ps1' -1 function Get-PrimaryGitBranch { return git config --list | Where-Object { $_ -like 'branch.*.merge=*' } | ForEach-Object { $_ -replace '^(.+)=(refs/heads/)?', '' } | Select-Object -First 1 } #EndRegion './Private/Get-PrimaryGitBranch.ps1' 7 #Region './Private/Get-RepoWebUrl.ps1' -1 function Get-RepoWebUrl { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$RemoteUrl, [Parameter(Mandatory)] [string]$Branch ) $url = $RemoteUrl -replace 'git@', 'https://' -replace 'http://', 'https://' -replace 'com:', 'com/' -replace 'org:', 'org/' if (-not $url.StartsWith('https://')) { $url = "https://$url" } try { $uri = [uri]::new($url) } catch { # check the ssh profiles for an alias used on the remote if ($RemoteUrl -match '^git@([^:]+):') { $sshAlias = $Matches[1] $hostName = Get-SshAliasHostnameUrl -SshAlias $sshAlias if ($hostName) { $uri = [uri]::new("https://$hostName/$($RemoteUrl -replace '^git@[^:]+:', '')") } else { Write-Error "Could not resolve SSH alias '$sshAlias' to a hostname." return $null } } else { Write-Error "Invalid remote URL format: $RemoteUrl" return $null } } if (-not $uri.Host) { Write-Error "Invalid remote URL format: $RemoteUrl" return $null } switch ($uri.Host) { 'github.com' { $path = $uri.AbsolutePath.TrimEnd('.git') return "https://github.com$path/tree/$Branch" } 'bitbucket.org' { # use `git for-each-ref --format='%(refname:short)|%(objectname)' refs/heads/` to list out (branch|commit_hash) pairs # use `git config --list | ? {$_ -like 'branch.*.merge=*'} | Select-Object -First 1 | ForEach-Object {$_ -replace '^(.+)=(refs/heads/)?', ''}` to know whether we're working with the primary branch or not $primaryBranch = Get-PrimaryGitBranch if ($primaryBranch -eq $Branch) { $path = $uri.AbsolutePath.TrimEnd('.git') return "https://bitbucket.org$path/src/$Branch" } # if primary branch, we don't need to specifi $branchWithHash = Get-GitBranchWithCommitHash $path = $uri.AbsolutePath.TrimEnd('.git') return "https://bitbucket.org{0}/src/{1}/?at={2}" -f $path, $branchWithHash.CommitHash, $branchWithHash.Branch } default { Write-Error "Unsupported Git provider: $($uri.Host)" return $null } } } #EndRegion './Private/Get-RepoWebUrl.ps1' 73 #Region './Private/Get-SshAliasHostnameUrl.ps1' -1 function Get-SshAliasHostnameUrl { param([string]$SshAlias) $location = Join-Path $env:HOME ".ssh" "config" if (Test-Path $location) { $sshConfig = Get-Content $location $inHostBlock = $false foreach ($line in $sshConfig) { if ($line -match '^\s*Host\s+(\S+)') { $inHostBlock = ($Matches[1] -eq $SshAlias) } elseif ($inHostBlock -and $line -match '^\s*HostName\s+(\S+)') { return $Matches[1] } elseif ($line -match '^\s*$') { $inHostBlock = $false } } } return $null } #EndRegion './Private/Get-SshAliasHostnameUrl.ps1' 23 #Region './Private/Resolve-NormalizedPath.ps1' -1 function Resolve-NormalizedPath { param( [Parameter(Mandatory)] [string]$Path, [string]$BasePath = (Get-Location).Path ) # Expand tilde to home directory if ($Path -like '~*') { $Path = $Path -replace '^~', $env:HOME } if (-not [System.IO.Path]::IsPathRooted($Path)) { $Path = [System.IO.Path]::Combine($BasePath, $Path) } try { $resolved = [System.IO.Path]::GetFullPath($Path) } catch { $resolved = $Path } return $resolved } #EndRegion './Private/Resolve-NormalizedPath.ps1' 25 #Region './Public/Open-GitRepo.ps1' -1 function Open-GitRepo { <# .SYNOPSIS Opens a Git repository's web interface in your default browser across any platform. .DESCRIPTION The Open-GitRepo cmdlet opens the web interface for a GitHub or Bitbucket repository in your default browser. It works cross-platform (Windows, macOS, Linux) and supports: - The current directory (default behavior) - A provided local directory path (looks up the git remote in that directory) - A provided git remote URL (parses and opens directly) The cmdlet can accept input via parameters or from the pipeline, making it easy to use in scripts or with multiple repositories. .PARAMETER Path The path to a local directory containing a git repository. If specified, the cmdlet will use the git remote and branch from this directory. .PARAMETER Url A git remote URL (HTTPS or SSH) to open directly. If specified, the cmdlet will parse the URL and open the corresponding web interface. .PARAMETER Branch The branch name to use when constructing the web URL. If not specified, the cmdlet will attempt to determine the current branch from the repository. .INPUTS [String] You can pipe a local directory path or git remote URL to this cmdlet. .OUTPUTS None. This cmdlet does not generate any output. .EXAMPLE PS C:\MyRepo> Open-GitRepo Opens the current repository's web page in your default browser (Windows). .EXAMPLE PS /home/user/MyRepo> Open-GitRepo -Path /home/user/otherrepo Opens the web page for the repository in /home/user/otherrepo (Linux). .EXAMPLE PS> 'https://github.com/user/repo.git' | Open-GitRepo Opens the GitHub repository web page for the provided URL. .EXAMPLE PS> '/Users/username/anotherrepo' | Open-GitRepo Opens the repository web page for the local directory (macOS). .EXAMPLE PS> Open-GitRepo -Url 'git@bitbucket.org:user/repo.git' Opens the Bitbucket repository web page for the provided SSH URL. .NOTES Author: Jonathan Havens Version: 0.0.2 Cross-platform: Windows, macOS, Linux Supports GitHub and Bitbucket repositories. Requires PowerShell 7+ and git in PATH. .LINK https://github.com/jhavenz/open-gitrepo #> [Alias('ogr', 'git-open', 'gitopen', 'git-browse', 'gitbrowse')] [CmdletBinding(DefaultParameterSetName = 'Path')] param( [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Path')] [string]$Path, [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Url')] [string]$Url, [Parameter(Position = 1)] [string]$Branch ) process { $targetUrl = $null $usedBranch = $Branch #normalize the input $_url, $_path = $null, $null if ($Url -is [string] -and (Test-Path $Url)) { $_path = $Url } if ($Path -is [string] -and $Path -match '^https?://|git@') { $_url = $Path } if ($null -ne $_url) { $Url = $_url } if ($null -ne $_path) { $Path = $_path } if ($Url) { if (-not $usedBranch) { $usedBranch = 'main' } $branch = Get-GitCurrentBranch -DefaultBranch $usedBranch $targetUrl = Get-RepoWebUrl -RemoteUrl $Url -Branch $Branch if (-not $targetUrl) { return } } elseif ($Path) { $branch = Get-GitCurrentBranch -TargetPath $Path -DefaultBranch $Branch $url = Get-GitRemoteUrl -TargetPath $Path if ($branch -and $url) { $usedBranch = $info.Branch || $usedBranch $targetUrl = Get-RepoWebUrl -RemoteUrl $url -Branch $branch if (-not $targetUrl) { return } } } elseif ($PSItem) { if (Test-Path $PSItem -PathType Container) { $url = Get-GitRemoteUrl -TargetPath $PSItem $branch = Get-GitCurrentBranch -TargetPath $PSItem -DefaultBranch $usedBranch if ($url -and $branch) { $targetUrl = Get-RepoWebUrl -RemoteUrl $url -Branch $branch } } elseif ($PSItem -match '^https?://|git@') { if (-not $usedBranch) { $usedBranch = 'main' } $targetUrl = Get-RepoWebUrl -RemoteUrl $PSItem -Branch $usedBranch if (-not $targetUrl) { return } } else { Write-Error "Unsupported Git provider or unrecognized remote URL format: $PSItem" return } } else { $url = Get-GitRemoteUrl $branch = Get-GitCurrentBranch if ([string]::IsNullOrEmpty($url)) { Write-Error "Could not determine a URL for the remote repository. Have you run 'git remote add origin <url>'?" return } if ([string]::IsNullOrEmpty($branch)) { Write-Error "Could not determine the current branch. Are you in a git repository?" return } $targetUrl = Get-RepoWebUrl -RemoteUrl $url -Branch $branch if (-not $targetUrl) { return } } if (-not $targetUrl) { Write-Error "Could not determine repository URL. Please provide a valid path or URL." return } Start-Process $targetUrl } end { } } #EndRegion './Public/Open-GitRepo.ps1' 157 |