Skip to content

Instantly share code, notes, and snippets.

@guinetik
Last active September 23, 2025 07:27
Show Gist options
  • Select an option

  • Save guinetik/f8be35b0cc3eb618f86245038e9e628a to your computer and use it in GitHub Desktop.

Select an option

Save guinetik/f8be35b0cc3eb618f86245038e9e628a to your computer and use it in GitHub Desktop.
clone stuff with PAT
<#
.SYNOPSIS
Clona repositórios do GitHub baseado em critérios de pesquisa usando um Personal Access Token (PAT).
.DESCRIPTION
Este script utiliza a API do GitHub para buscar repositórios que correspondem aos critérios especificados
e os clona localmente. Por padrão, força a busca apenas por repositórios privados, mas pode ser
customizado através do parâmetro Query.
.PARAMETER Pat
Personal Access Token do GitHub com permissões de acesso aos repositórios.
Para gerar um PAT, acesse: https://github.com/settings/tokens
.PARAMETER Query
Critério de busca para filtrar repositórios. Suporta a sintaxe de pesquisa do GitHub.
Exemplos:
- "language:python"
- "org:minha-org language:javascript"
- "topic:machine-learning"
.PARAMETER OutDir
Diretório onde os repositórios serão clonados. Padrão: ".\clones"
.PARAMETER Proxy
URL do servidor proxy HTTP/HTTPS (opcional).
Formato: "http://proxy.empresa.com:8080"
.PARAMETER IncludePublic
Inclui repositórios públicos na busca. Por padrão, apenas repositórios privados são considerados.
.PARAMETER MaxRepos
Número máximo de repositórios a serem clonados. Padrão: ilimitado.
.PARAMETER Verbose
Exibe informações detalhadas durante a execução.
.EXAMPLE
.\Clone-GitHubRepos.ps1 -Pat "ghp_xxxxxxxxxxxxxxxxxxxx" -Query "language:python"
.EXAMPLE
.\Clone-GitHubRepos.ps1 -Pat "ghp_xxxxxxxxxxxxxxxxxxxx" -Query "org:minha-empresa" -OutDir "C:\repos" -IncludePublic
.EXAMPLE
.\Clone-GitHubRepos.ps1 -Pat "ghp_xxxxxxxxxxxxxxxxxxxx" -Query "topic:api" -Proxy "http://proxy:8080" -ProxyUser "usuario" -ProxyPassword (ConvertTo-SecureString "senha" -AsPlainText -Force) -MaxRepos 10
.NOTES
Versão: 2.0
Autor: Guinetik 🚀
Data: 2025-09-23
Requisitos:
- PowerShell 5.1 ou superior
- Git instalado e configurado
- Acesso à internet (ou proxy configurado)
- PAT com permissões adequadas
.LINK
https://docs.github.com/en/rest/search#search-repositories
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $true, HelpMessage = "Personal Access Token do GitHub")]
[ValidateNotNullOrEmpty()]
[string] $Pat,
[Parameter(Mandatory = $true, HelpMessage = "Critério de busca para repositórios")]
[ValidateNotNullOrEmpty()]
[string] $Query,
[Parameter(HelpMessage = "Diretório de destino para clones")]
[ValidateNotNullOrEmpty()]
[string] $OutDir = ".\clones",
[Parameter(HelpMessage = "URL do servidor proxy (opcional)")]
[string] $Proxy = "",
[Parameter(HelpMessage = "Usuário para autenticação do proxy")]
[string] $ProxyUser = "",
[Parameter(HelpMessage = "Senha para autenticação do proxy")]
[Security.SecureString] $ProxyPassword,
[Parameter(HelpMessage = "Incluir repositórios públicos na busca")]
[switch] $IncludePublic,
[Parameter(HelpMessage = "Número máximo de repositórios a clonar")]
[ValidateRange(1, 1000)]
[int] $MaxRepos = [int]::MaxValue
)
#Requires -Version 5.1
# Configurações e constantes
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$script:CONFIG = @{
ApiBaseUrl = "https://api.github.com"
PerPage = 100
MaxRetries = 3
RetryDelay = 2
TimeoutSeconds = 30
UserAgent = "PowerShell-GitHub-Cloner/2.0"
}
# Cores para output
$script:COLORS = @{
Info = 'Cyan'
Success = 'Green'
Warning = 'Yellow'
Error = 'Red'
Progress = 'Magenta'
}
#region Funções Auxiliares
# Definir constantes para SetThreadExecutionState
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class PowerManagement {
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint SetThreadExecutionState(uint esFlags);
public const uint ES_CONTINUOUS = 0x80000000;
public const uint ES_SYSTEM_REQUIRED = 0x00000001;
public const uint ES_DISPLAY_REQUIRED = 0x00000002;
}
"@
function Set-PreventSleep {
<#
.SYNOPSIS
Previne o sistema de entrar em sleep/hibernação.
#>
param([bool] $Prevent = $true)
if ($Prevent) {
# Manter sistema e display ativos
$result = [PowerManagement]::SetThreadExecutionState(
[PowerManagement]::ES_CONTINUOUS -bor
[PowerManagement]::ES_SYSTEM_REQUIRED -bor
[PowerManagement]::ES_DISPLAY_REQUIRED
)
if ($result -ne 0) {
Write-ColorOutput "💡 Prevenção de sleep ativada" -Color $script:COLORS.Info
} else {
Write-ColorOutput "⚠️ Não foi possível ativar prevenção de sleep" -Color $script:COLORS.Warning
}
} else {
# Restaurar comportamento normal
$result = [PowerManagement]::SetThreadExecutionState([PowerManagement]::ES_CONTINUOUS)
Write-ColorOutput "😴 Prevenção de sleep desativada" -Color $script:COLORS.Info
}
}
function Write-ColorOutput {
<#
.SYNOPSIS
Escreve mensagem colorida no console com timestamp.
#>
param(
[string] $Message,
[string] $Color = 'White',
[switch] $NoNewline
)
$timestamp = Get-Date -Format "HH:mm:ss"
$prefix = "[$timestamp]"
if ($NoNewline) {
Write-Host "$prefix $Message" -ForegroundColor $Color -NoNewline
} else {
Write-Host "$prefix $Message" -ForegroundColor $Color
}
}
function Test-Prerequisites {
<#
.SYNOPSIS
Verifica se todos os pré-requisitos estão atendidos.
#>
Write-ColorOutput "🔍 Verificando pré-requisitos..." -Color $script:COLORS.Info
# Verificar se o Git está instalado
try {
$gitVersion = git --version 2>$null
if (-not $gitVersion) { throw "Git não encontrado" }
Write-Verbose "Git encontrado: $gitVersion"
} catch {
throw "❌ Git não está instalado ou não está no PATH. Instale o Git em: https://git-scm.com/"
}
# Verificar conectividade com GitHub
try {
$testUrl = "$($script:CONFIG.ApiBaseUrl)/rate_limit"
$headers = @{
"Authorization" = "token $Pat"
"User-Agent" = $script:CONFIG.UserAgent
}
$proxyParams = @{}
if ($Proxy) {
$proxyParams.Proxy = $Proxy
if ($ProxyUser -and $ProxyPassword) {
$credential = New-Object System.Management.Automation.PSCredential($ProxyUser, $ProxyPassword)
$proxyParams.ProxyCredential = $credential
}
}
Invoke-RestMethod -Uri $testUrl -Headers $headers -TimeoutSec 10 @proxyParams | Out-Null
Write-ColorOutput "✅ Conectividade com GitHub API verificada" -Color $script:COLORS.Success
} catch {
throw "❌ Não foi possível conectar com a API do GitHub. Verifique sua conexão e PAT: $($_.Exception.Message)"
}
# Verificar/criar diretório de saída
try {
if (-not (Test-Path $OutDir)) {
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
Write-ColorOutput "📁 Diretório criado: $OutDir" -Color $script:COLORS.Info
} else {
Write-ColorOutput "📁 Usando diretório existente: $OutDir" -Color $script:COLORS.Info
}
} catch {
throw "❌ Erro ao criar/acessar diretório: $OutDir - $($_.Exception.Message)"
}
}
function Invoke-GitHubApiWithRetry {
<#
.SYNOPSIS
Chama a API do GitHub com retry automático e tratamento de rate limit.
#>
param(
[string] $Url,
[hashtable] $Headers,
[hashtable] $ProxyParams = @{}
)
for ($attempt = 1; $attempt -le $script:CONFIG.MaxRetries; $attempt++) {
try {
Write-Verbose "Tentativa $attempt de $($script:CONFIG.MaxRetries) para: $Url"
$response = Invoke-RestMethod -Uri $Url -Headers $Headers -TimeoutSec $script:CONFIG.TimeoutSeconds @ProxyParams
# Log de rate limit (se disponível nos headers)
if ($response.PSObject.Properties['rate_limit']) {
Write-Verbose "Rate limit restante: $($response.rate_limit.remaining)/$($response.rate_limit.limit)"
}
return $response
} catch {
$errorDetails = $_.Exception.Message
# Tratamento específico para rate limit
if ($_.Exception.Response.StatusCode -eq 403) {
Write-ColorOutput "⚠️ Rate limit atingido. Aguardando..." -Color $script:COLORS.Warning
Start-Sleep -Seconds 60
continue
}
# Tratamento para erros temporários
if ($attempt -lt $script:CONFIG.MaxRetries -and
($_.Exception.Response.StatusCode -ge 500 -or $_.Exception -is [System.Net.WebException])) {
Write-ColorOutput "⚠️ Erro temporário (tentativa $attempt): $errorDetails" -Color $script:COLORS.Warning
Start-Sleep -Seconds ($script:CONFIG.RetryDelay * $attempt)
continue
}
# Erro final
throw "Falha na chamada da API após $attempt tentativas: $errorDetails"
}
}
}
function Get-SafeDirectoryName {
<#
.SYNOPSIS
Gera nome de diretório seguro removendo caracteres inválidos.
#>
param([string] $Name)
$invalidChars = [IO.Path]::GetInvalidFileNameChars() -join ''
$safeName = $Name -replace "[$invalidChars]", '_'
return $safeName
}
function Start-GitClone {
<#
.SYNOPSIS
Clona um repositório Git com tratamento robusto de erros.
#>
param(
[string] $CloneUrl,
[string] $TargetPath,
[string] $RepoName,
[hashtable] $ProxyParams = @{}
)
try {
Write-ColorOutput "📥 Clonando: $RepoName" -Color $script:COLORS.Progress
# Preparar argumentos do git
$gitArgs = @('clone', $CloneUrl, $TargetPath, '--depth=1')
if ($Proxy) {
# Configurar proxy para Git
if ($ProxyUser -and $ProxyPassword) {
$plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ProxyPassword))
$proxyWithAuth = $Proxy -replace "://", "://$ProxyUser`:$plainPassword@"
$gitArgs = @('-c', "http.proxy=$proxyWithAuth") + $gitArgs
} else {
$gitArgs = @('-c', "http.proxy=$Proxy") + $gitArgs
}
}
# Executar git clone usando operador &
Write-Verbose "Executando: git $($gitArgs -join ' ')"
& git @gitArgs
if ($LASTEXITCODE -eq 0) {
Write-ColorOutput "✅ Sucesso: $RepoName" -Color $script:COLORS.Success
return $true
} else {
Write-ColorOutput "❌ Falha ao clonar: $RepoName (Exit Code: $LASTEXITCODE)" -Color $script:COLORS.Error
return $false
}
} catch {
Write-ColorOutput "❌ Erro no clone de $RepoName : $($_.Exception.Message)" -Color $script:COLORS.Error
return $false
}
}
#endregion
#region Função Principal
function Start-RepositoryCloning {
<#
.SYNOPSIS
Função principal que orquestra todo o processo de clonagem.
#>
# Banner inicial
Write-Host @"
╔══════════════════════════════════════════════════════════════╗
║ 🚀 GitHub Repository Cloner v2.0 ║
║ por Guinetik ║
╚══════════════════════════════════════════════════════════════╝
"@ -ForegroundColor $script:COLORS.Info
# Verificar pré-requisitos
Test-Prerequisites
# Ativar prevenção de sleep
Set-PreventSleep -Prevent $true
# Configurar headers da API
$headers = @{
"Authorization" = "token $Pat"
"Accept" = "application/vnd.github.v3+json"
"User-Agent" = $script:CONFIG.UserAgent
}
# Configurar proxy se especificado
$proxyParams = @{}
if ($Proxy) {
$proxyParams.Proxy = $Proxy
# Configurar credenciais do proxy se fornecidas
if ($ProxyUser -and $ProxyPassword) {
$credential = New-Object System.Management.Automation.PSCredential($ProxyUser, $ProxyPassword)
$proxyParams.ProxyCredential = $credential
}
Write-ColorOutput "🌐 Usando proxy: $Proxy" -Color $script:COLORS.Info
if ($ProxyUser) {
Write-ColorOutput "👤 Proxy com autenticação: $ProxyUser" -Color $script:COLORS.Info
}
}
# Construir query de busca
$searchQuery = $Query
if (-not $IncludePublic) {
$searchQuery += " is:private"
}
Write-ColorOutput "🔍 Critério de busca: $searchQuery" -Color $script:COLORS.Info
Write-ColorOutput "📂 Diretório de destino: $OutDir" -Color $script:COLORS.Info
Write-ColorOutput "🎯 Limite máximo: $MaxRepos repositórios" -Color $script:COLORS.Info
# Contadores e estatísticas
$stats = @{
Total = 0
Cloned = 0
Skipped = 0
Failed = 0
StartTime = Get-Date
}
$page = 1
$repositories = @()
# Buscar repositórios paginados
Write-ColorOutput "🔄 Iniciando busca de repositórios..." -Color $script:COLORS.Info
do {
$encodedQuery = [System.Uri]::EscapeDataString($searchQuery)
$apiUrl = "$($script:CONFIG.ApiBaseUrl)/search/repositories?q=$encodedQuery&per_page=$($script:CONFIG.PerPage)&page=$page"
Write-Verbose "Buscando página $page..."
try {
$response = Invoke-GitHubApiWithRetry -Url $apiUrl -Headers $headers -ProxyParams $proxyParams
if ($response.items -and $response.items.Count -gt 0) {
$repositories += $response.items
Write-ColorOutput "📄 Página $page : $($response.items.Count) repositórios encontrados" -Color $script:COLORS.Info
# Verificar se atingiu o limite máximo
if ($repositories.Count -ge $MaxRepos) {
$repositories = $repositories[0..($MaxRepos - 1)]
break
}
} else {
break
}
$page++
} catch {
Write-ColorOutput "❌ Erro na busca: $($_.Exception.Message)" -Color $script:COLORS.Error
break
}
} while ($response.items.Count -eq $script:CONFIG.PerPage)
$stats.Total = $repositories.Count
Write-ColorOutput "📊 Total de repositórios encontrados: $($stats.Total)" -Color $script:COLORS.Info
if ($stats.Total -eq 0) {
Write-ColorOutput "ℹ️ Nenhum repositório encontrado com os critérios especificados." -Color $script:COLORS.Warning
return
}
# Processar clonagem
Write-ColorOutput "🚀 Iniciando processo de clonagem..." -Color $script:COLORS.Info
foreach ($repo in $repositories) {
$owner = $repo.owner.login
$name = $repo.name
$fullName = "$owner/$name"
# Usar apenas o nome do repositório como diretório
$safeDirName = Get-SafeDirectoryName $name
$targetDir = Join-Path $OutDir $safeDirName
# Verificar se já existe
if (Test-Path $targetDir) {
Write-ColorOutput "⏭️ Pulando (já existe): $fullName" -Color $script:COLORS.Warning
$stats.Skipped++
continue
}
# Construir URL de clone com autenticação
$cloneUrl = $repo.clone_url -replace "https://", "https://oauth:$Pat@"
# Executar clone
if ($PSCmdlet.ShouldProcess($fullName, "Clonar repositório")) {
$cloneResult = Start-GitClone -CloneUrl $cloneUrl -TargetPath $targetDir -RepoName $fullName -ProxyParams $proxyParams
if ($cloneResult) {
$stats.Cloned++
} else {
$stats.Failed++
}
}
# Exibir progresso
$completed = $stats.Cloned + $stats.Skipped + $stats.Failed
$percent = [math]::Round(($completed / $stats.Total) * 100, 1)
Write-Progress -Activity "Clonando repositórios" -Status "$completed de $($stats.Total) processados ($percent%)" -PercentComplete $percent
}
# Limpar barra de progresso
Write-Progress -Activity "Clonando repositórios" -Completed
# Relatório final
$duration = (Get-Date) - $stats.StartTime
Write-Host @"
╔══════════════════════════════════════════════════════════════╗
║ 📊 RELATÓRIO FINAL ║
╠══════════════════════════════════════════════════════════════╣
║ Total encontrados: $($stats.Total.ToString().PadLeft(3)) ║
║ Clonados com sucesso: $($stats.Cloned.ToString().PadLeft(3)) ║
║ Pulados (já existem): $($stats.Skipped.ToString().PadLeft(3)) ║
║ Falharam: $($stats.Failed.ToString().PadLeft(3)) ║
║ Tempo total: $($duration.ToString('hh\:mm\:ss')) ║
║ Diretório: $($OutDir.PadRight(42)) ║
╚══════════════════════════════════════════════════════════════╝
"@ -ForegroundColor $script:COLORS.Success
if ($stats.Failed -gt 0) {
Write-ColorOutput "⚠️ Alguns repositórios falharam. Verifique os logs acima para detalhes." -Color $script:COLORS.Warning
} else {
Write-ColorOutput "🎉 Processo concluído com sucesso!" -Color $script:COLORS.Success
}
}
#endregion
#region Execução Principal
try {
Start-RepositoryCloning
} catch {
Write-ColorOutput "💥 Erro fatal: $($_.Exception.Message)" -Color $script:COLORS.Error
Write-Verbose $_.ScriptStackTrace
exit 1
} finally {
# Restaurar comportamento normal de energia
Set-PreventSleep -Prevent $false
Write-ColorOutput "🏁 Script finalizado." -Color $script:COLORS.Info
}
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment